diff --git a/pkg/cloudproxy/agent/worker/worker.go b/pkg/cloudproxy/agent/worker/worker.go index 77fa455faef..1da8a43ccf9 100644 --- a/pkg/cloudproxy/agent/worker/worker.go +++ b/pkg/cloudproxy/agent/worker/worker.go @@ -16,13 +16,12 @@ package worker import ( "context" + "net" "runtime" "runtime/debug" "sync" "time" - "github.com/vishvananda/netlink" - "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/version" @@ -99,14 +98,15 @@ func (w *Worker) initProxyAgent_(ctx context.Context) error { } bindAddrExist := func(addr string) bool { - as, err := netlink.AddrList(nil, netlink.FAMILY_ALL) + addrs, err := net.InterfaceAddrs() if err != nil { log.Fatalf("list system available addresses: %v", err) } - for _, a := range as { - ipstr := a.IPNet.IP.String() - if addr == ipstr { - return true + for _, a := range addrs { + if ipnet, ok := a.(*net.IPNet); ok { + if addr == ipnet.IP.String() { + return true + } } } return false diff --git a/pkg/hostimage/nbd.go b/pkg/hostimage/nbd.go index 68e077537b2..68243636406 100644 --- a/pkg/hostimage/nbd.go +++ b/pkg/hostimage/nbd.go @@ -122,7 +122,7 @@ func (m *SNbdExportManager) QemuNbdStartExport(imageInfo qemuimg.SImageInfo, dis cmdStr := strings.Join(cmd, " ") err = procutils.NewRemoteCommandAsFarAsPossible("sh", "-c", cmdStr).Run() if err != nil { - log.Errorf("qemu-nbd connect failed %s %s", err.Error()) + log.Errorf("qemu-nbd connect failed %s %s", cmdStr, err.Error()) return -1, errors.Wrapf(err, "qemu-nbd connect failed") } return nbdPort, nil diff --git a/pkg/hostman/container/snapshot_service/interfaces.go b/pkg/hostman/container/snapshot_service/interfaces.go new file mode 100644 index 00000000000..8eb9ef13b71 --- /dev/null +++ b/pkg/hostman/container/snapshot_service/interfaces.go @@ -0,0 +1,9 @@ +package snapshot_service + +type IGuestManager interface { + GetContainerManager(serverId string) (ISnapshotContainerManager, error) +} + +type ISnapshotContainerManager interface { + GetRootFsMountPath(containerId string) (string, error) +} diff --git a/pkg/hostman/container/snapshot_service/snapshot_service.go b/pkg/hostman/container/snapshot_service/snapshot_service_linux.go similarity index 95% rename from pkg/hostman/container/snapshot_service/snapshot_service.go rename to pkg/hostman/container/snapshot_service/snapshot_service_linux.go index 6c9064ee3d9..37366cafee6 100644 --- a/pkg/hostman/container/snapshot_service/snapshot_service.go +++ b/pkg/hostman/container/snapshot_service/snapshot_service_linux.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + package snapshot_service import ( @@ -38,14 +41,6 @@ func StartService(guestMan IGuestManager, root string) error { return rpc.Serve(listener) } -type IGuestManager interface { - GetContainerManager(serverId string) (ISnapshotContainerManager, error) -} - -type ISnapshotContainerManager interface { - GetRootFsMountPath(containerId string) (string, error) -} - func NewSnapshotter(guestMan IGuestManager, root string, opts ...overlay.Opt) (snapshots.Snapshotter, error) { sn, err := overlay.NewSnapshotter(root, opts...) if err != nil { diff --git a/pkg/hostman/container/snapshot_service/snapshot_service_others.go b/pkg/hostman/container/snapshot_service/snapshot_service_others.go new file mode 100644 index 00000000000..97ff9b0b716 --- /dev/null +++ b/pkg/hostman/container/snapshot_service/snapshot_service_others.go @@ -0,0 +1,12 @@ +//go:build !linux +// +build !linux + +package snapshot_service + +import ( + "yunion.io/x/pkg/errors" +) + +func StartService(guestMan IGuestManager, root string) error { + return errors.ErrNotImplemented +} diff --git a/pkg/hostman/guestman/qemu-kvm.go b/pkg/hostman/guestman/qemu-kvm.go index 0ee436aaf1b..aa671bbb35e 100644 --- a/pkg/hostman/guestman/qemu-kvm.go +++ b/pkg/hostman/guestman/qemu-kvm.go @@ -2561,21 +2561,31 @@ func (s *SKVMGuestInstance) onNicChange(oldNic, newNic *desc.SGuestNetwork) erro } else { // bridge not changed if oldifname == newifname { - if newvlan > 1 { - output, err := procutils.NewRemoteCommandAsFarAsPossible("ovs-vsctl", "set", "port", newifname, fmt.Sprintf("tag=%d", newvlan)).Output() - if err != nil { - return errors.Wrapf(err, "NewRemoteCommandAsFarAsPossible change vlan tag to %d: %s", newvlan, output) + if oldNic.Bw != newNic.Bw { + log.Infof("Nic %s bw changed %d -> %d", newNic.Mac, oldNic.Bw, newNic.Bw) + if err := s.generateNicScripts(newNic); err != nil { + return errors.Wrap(err, "generateNicScripts update bandwidth") } - } else { - // clear vlan - output, err := procutils.NewRemoteCommandAsFarAsPossible("ovs-vsctl", "get", "port", newifname, "tag").Output() - if err != nil { - return errors.Wrapf(err, "NewRemoteCommandAsFarAsPossible get vlan tag: %s", output) + if err := s.SetNicUp(newNic); err != nil { + return errors.Wrap(err, "SetNicUp update bandwidth") } - tagStr := strings.TrimSpace(string(output)) - if tag, err := strconv.Atoi(tagStr); err == nil && tag > 1 { - if output, err := procutils.NewRemoteCommandAsFarAsPossible("ovs-vsctl", "remove", "port", newifname, "tag", tagStr).Output(); err != nil { - return errors.Wrapf(err, "NewRemoteCommandAsFarAsPossible remove vlan tag %s: %s", tagStr, output) + } else { + if newvlan > 1 { + output, err := procutils.NewRemoteCommandAsFarAsPossible("ovs-vsctl", "set", "port", newifname, fmt.Sprintf("tag=%d", newvlan)).Output() + if err != nil { + return errors.Wrapf(err, "NewRemoteCommandAsFarAsPossible change vlan tag to %d: %s", newvlan, output) + } + } else { + // clear vlan + output, err := procutils.NewRemoteCommandAsFarAsPossible("ovs-vsctl", "get", "port", newifname, "tag").Output() + if err != nil { + return errors.Wrapf(err, "NewRemoteCommandAsFarAsPossible get vlan tag: %s", output) + } + tagStr := strings.TrimSpace(string(output)) + if tag, err := strconv.Atoi(tagStr); err == nil && tag > 1 { + if output, err := procutils.NewRemoteCommandAsFarAsPossible("ovs-vsctl", "remove", "port", newifname, "tag", tagStr).Output(); err != nil { + return errors.Wrapf(err, "NewRemoteCommandAsFarAsPossible remove vlan tag %s: %s", tagStr, output) + } } } } diff --git a/pkg/hostman/storageman/imagecache_local.go b/pkg/hostman/storageman/imagecache_local.go index a2be7bbd3e3..cc42483fe35 100644 --- a/pkg/hostman/storageman/imagecache_local.go +++ b/pkg/hostman/storageman/imagecache_local.go @@ -137,8 +137,9 @@ func (l *SLocalImageCache) Load() error { if fi != nil { desc.SizeMb = fi.Size() / 1024 / 1024 if fi.Sys() != nil { - atime := fi.Sys().(*syscall.Stat_t).Atim - desc.AccessAt = time.Unix(atime.Sec, atime.Nsec) + if stat, ok := fi.Sys().(*syscall.Stat_t); ok { + desc.AccessAt = fileutils2.GetAtim(stat) + } } } diff --git a/pkg/hostman/storageman/remotefile/remotefile.go b/pkg/hostman/storageman/remotefile/remotefile.go index 6263307e3e3..8de57adb357 100644 --- a/pkg/hostman/storageman/remotefile/remotefile.go +++ b/pkg/hostman/storageman/remotefile/remotefile.go @@ -121,8 +121,9 @@ func (r *SRemoteFile) GetInfo() (*SImageDesc, error) { var atime time.Time if fi.Sys() != nil { - atm := fi.Sys().(*syscall.Stat_t).Atim - atime = time.Unix(atm.Sec, atm.Nsec) + if stat, ok := fi.Sys().(*syscall.Stat_t); ok { + atime = fileutils2.GetAtim(stat) + } } return &SImageDesc{ diff --git a/pkg/util/cgrouputils/cgroupv1/cpuutils.go b/pkg/util/cgrouputils/cgroupv1/cpuutils.go index e252bf5a644..45b2cbbe4c8 100644 --- a/pkg/util/cgrouputils/cgroupv1/cpuutils.go +++ b/pkg/util/cgrouputils/cgroupv1/cpuutils.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + // Copyright 2019 Yunion // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/pkg/util/cgrouputils/cgroupv1/cpuutils_test.go b/pkg/util/cgrouputils/cgroupv1/cpuutils_test.go index 7de05e222aa..d6d48f3d5cb 100644 --- a/pkg/util/cgrouputils/cgroupv1/cpuutils_test.go +++ b/pkg/util/cgrouputils/cgroupv1/cpuutils_test.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + // Copyright 2019 Yunion // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/pkg/util/cgrouputils/cgroupv1/doc.go b/pkg/util/cgrouputils/cgroupv1/doc.go index 44691d57fd1..f60da5d740a 100644 --- a/pkg/util/cgrouputils/cgroupv1/doc.go +++ b/pkg/util/cgrouputils/cgroupv1/doc.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + // Copyright 2019 Yunion // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/pkg/util/cgrouputils/cgroupv1/manager.go b/pkg/util/cgrouputils/cgroupv1/manager.go index 01406c5b3fe..20d344bdcaf 100644 --- a/pkg/util/cgrouputils/cgroupv1/manager.go +++ b/pkg/util/cgrouputils/cgroupv1/manager.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + // Copyright 2019 Yunion // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/pkg/util/cgrouputils/manager.go b/pkg/util/cgrouputils/manager.go index 29070983320..51b496a8c42 100644 --- a/pkg/util/cgrouputils/manager.go +++ b/pkg/util/cgrouputils/manager.go @@ -15,6 +15,7 @@ package cgrouputils import ( + "runtime" "strings" "yunion.io/x/pkg/errors" @@ -89,29 +90,41 @@ func Init(ioScheduler string) error { return nil } - cgroupPath := "" - if fileutils2.Exists(CGROUP_PATH_SYSFS) { - cgroupPath = CGROUP_PATH_SYSFS - } else if fileutils2.Exists("CGROUP_PATH_ROOT") { - cgroupPath = CGROUP_PATH_ROOT - } - if cgroupPath == "" { - return errors.Errorf("Can't detect cgroup path") - } - output, err := procutils.NewCommand("stat", "-fc", "%T", cgroupPath).Output() - if err != nil { - return errors.Wrapf(err, "stat cgroup path %s", cgroupPath) - } - cgroupfs := strings.TrimSpace(string(output)) - if cgroupfs == "cgroup2fs" { - // cgroup v2 - cgroupManager, err = cgroupv2.Init(cgroupPath, ioScheduler) + if runtime.GOOS == "linux" { + var cgroupPath string + if fileutils2.Exists(CGROUP_PATH_SYSFS) { + cgroupPath = CGROUP_PATH_SYSFS + } else if fileutils2.Exists("CGROUP_PATH_ROOT") { + cgroupPath = CGROUP_PATH_ROOT + } + if cgroupPath == "" { + return errors.Errorf("Can't detect cgroup path") + } + + output, err := procutils.NewCommand("stat", "-fc", "%T", cgroupPath).Output() + if err != nil { + return errors.Wrapf(err, "stat cgroup path %s", cgroupPath) + } + cgroupfs := strings.TrimSpace(string(output)) + var mgr ICgroupManager + if cgroupfs == "cgroup2fs" { + // cgroup v2 + mgr, err = cgroupv2.Init(cgroupPath, ioScheduler) + } else { + // cgroup v1 + mgr, err = cgroupv1.Init(cgroupPath, ioScheduler) + } + if err != nil { + return errors.Wrap(err, "init cgroup") + } + cgroupManager = mgr } else { - // cgroup v1 - cgroupManager, err = cgroupv1.Init(cgroupPath, ioScheduler) - } - if err != nil { - return errors.Wrap(err, "init cgroup") + // fallback for non-linux + mgr, err := cgroupv1.Init("", ioScheduler) + if err != nil { + return errors.Wrap(err, "init cgroup stub") + } + cgroupManager = mgr } return nil } diff --git a/pkg/util/fileutils2/fileutils_darwin.go b/pkg/util/fileutils2/fileutils_darwin.go new file mode 100644 index 00000000000..cc93fa27d3b --- /dev/null +++ b/pkg/util/fileutils2/fileutils_darwin.go @@ -0,0 +1,13 @@ +//go:build !linux +// +build !linux + +package fileutils2 + +import ( + "syscall" + "time" +) + +func GetAtim(stat *syscall.Stat_t) time.Time { + return time.Unix(stat.Atimespec.Sec, stat.Atimespec.Nsec) +} diff --git a/pkg/util/fileutils2/fileutils_linux.go b/pkg/util/fileutils2/fileutils_linux.go new file mode 100644 index 00000000000..354b127251c --- /dev/null +++ b/pkg/util/fileutils2/fileutils_linux.go @@ -0,0 +1,13 @@ +//go:build linux +// +build linux + +package fileutils2 + +import ( + "syscall" + "time" +) + +func GetAtim(stat *syscall.Stat_t) time.Time { + return time.Unix(stat.Atim.Sec, stat.Atim.Nsec) +} diff --git a/pkg/util/iproute2/address_linux.go b/pkg/util/iproute2/address_linux.go index 9f1cb86fc9e..5bb4bc79774 100644 --- a/pkg/util/iproute2/address_linux.go +++ b/pkg/util/iproute2/address_linux.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + // Copyright 2019 Yunion // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/pkg/util/iproute2/address_others.go b/pkg/util/iproute2/address_others.go index 0f52d063461..0889778f218 100644 --- a/pkg/util/iproute2/address_others.go +++ b/pkg/util/iproute2/address_others.go @@ -83,3 +83,17 @@ func (address *Address) Add() *Address { } return address } + +func (address *Address) Del() *Address { + link, ok := address.link() + if !ok { + return address + } + for _, addr := range address.addrs { + err := netlink.AddrDel(link, addr) + if err != nil { + address.addErr(err, "Del: AddrDel %s ", addr) + } + } + return address +} diff --git a/pkg/util/iproute2/address_test.go b/pkg/util/iproute2/address_test.go index be883021efb..0d4bb166e0e 100644 --- a/pkg/util/iproute2/address_test.go +++ b/pkg/util/iproute2/address_test.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + // Copyright 2019 Yunion // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/pkg/util/iproute2/link_test.go b/pkg/util/iproute2/link_test.go index f35548d9a80..b98beb73896 100644 --- a/pkg/util/iproute2/link_test.go +++ b/pkg/util/iproute2/link_test.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + // Copyright 2019 Yunion // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/pkg/util/netutils2/netutils_others.go b/pkg/util/netutils2/netutils_others.go index 511e203d6e9..e53c9b8dc76 100644 --- a/pkg/util/netutils2/netutils_others.go +++ b/pkg/util/netutils2/netutils_others.go @@ -1,3 +1,6 @@ +//go:build !linux +// +build !linux + // Copyright 2019 Yunion // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,9 +15,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build !linux -// +build !linux - package netutils2 import ( @@ -42,6 +42,10 @@ func (n *SNetInterface) ClearAddrs() error { return nil } +func (n *SNetInterface) FlushAddrs() error { + return nil +} + func DefaultSrcIpDev() (srcIp net.IP, ifname string, err error) { err = errors.ErrNotImplemented return diff --git a/pkg/util/netutils2/netutils_test.go b/pkg/util/netutils2/netutils_test.go index 616d3035bc0..b6b0a8202fe 100644 --- a/pkg/util/netutils2/netutils_test.go +++ b/pkg/util/netutils2/netutils_test.go @@ -17,6 +17,7 @@ package netutils2 import ( "os" "reflect" + "runtime" "testing" "yunion.io/x/jsonutils" @@ -97,6 +98,9 @@ func TestNewNetInterface(t *testing.T) { } func TestMyDefault(t *testing.T) { + if runtime.GOOS != "linux" { + t.Skip("DefaultSrcIpDev not implemented on non-linux") + } myip, err := MyIP() if err != nil { // Skip if it's no route to host diff --git a/pkg/util/pod/cadvisor/cadvisor.go b/pkg/util/pod/cadvisor/cadvisor.go index b3e05840b27..72da737fc74 100644 --- a/pkg/util/pod/cadvisor/cadvisor.go +++ b/pkg/util/pod/cadvisor/cadvisor.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + // Copyright 2019 Yunion // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/pkg/util/pod/cadvisor/cadvisor_others.go b/pkg/util/pod/cadvisor/cadvisor_others.go new file mode 100644 index 00000000000..638b9d709c2 --- /dev/null +++ b/pkg/util/pod/cadvisor/cadvisor_others.go @@ -0,0 +1,12 @@ +//go:build !linux +// +build !linux + +package cadvisor + +import ( + "yunion.io/x/pkg/errors" +) + +func New(imageFsInfoProvider ImageFsInfoProvider, rootPath string, cgroupRoots []string) (Interface, error) { + return nil, errors.ErrNotImplemented +} diff --git a/pkg/util/pod/cadvisor/doc.go b/pkg/util/pod/cadvisor/doc.go index 6436eca1b4d..4989e7f599c 100644 --- a/pkg/util/pod/cadvisor/doc.go +++ b/pkg/util/pod/cadvisor/doc.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + // Copyright 2019 Yunion // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/pkg/util/pod/cadvisor/helpers_linux.go b/pkg/util/pod/cadvisor/helpers_linux.go index cb33e9779e3..4eb96321be4 100644 --- a/pkg/util/pod/cadvisor/helpers_linux.go +++ b/pkg/util/pod/cadvisor/helpers_linux.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + // Copyright 2019 Yunion // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/pkg/util/pod/cadvisor/interface.go b/pkg/util/pod/cadvisor/interface.go index c6ffd990f67..a3d9af002e8 100644 --- a/pkg/util/pod/cadvisor/interface.go +++ b/pkg/util/pod/cadvisor/interface.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + // Copyright 2019 Yunion // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/pkg/util/pod/cadvisor/interface_others.go b/pkg/util/pod/cadvisor/interface_others.go new file mode 100644 index 00000000000..920850996c1 --- /dev/null +++ b/pkg/util/pod/cadvisor/interface_others.go @@ -0,0 +1,36 @@ +//go:build !linux +// +build !linux + +package cadvisor + +import ( + "github.com/google/cadvisor/events" + cadvisorapi "github.com/google/cadvisor/info/v1" + cadvisorapiv2 "github.com/google/cadvisor/info/v2" +) + +type Interface interface { + Start() error + ContainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) + ContainerInfoV2(name string, options cadvisorapiv2.RequestOptions) (map[string]cadvisorapiv2.ContainerInfo, error) + MachineInfo() (*cadvisorapi.MachineInfo, error) + VersionInfo() (*cadvisorapi.VersionInfo, error) + + // Returns usage information about the filesystem holding container images. + ImagesFsInfo() (cadvisorapiv2.FsInfo, error) + + // Returns usage information about the root filesystem. + RootFsInfo() (cadvisorapiv2.FsInfo, error) + + // Get events streamed through passedChannel that fit the request. + WatchEvents(request *events.Request) (*events.EventChannel, error) + + // Get filesystem information for the filesystem that contains the given file. + GetDirFsInfo(path string) (cadvisorapiv2.FsInfo, error) +} + +// ImageFsInfoProvider informs cAdvisor how to find imagefs for container images. +type ImageFsInfoProvider interface { + // ImageFsInfoLabel returns the label cAdvisor should use to find the filesystem holding container images. + ImageFsInfoLabel() (string, error) +} diff --git a/pkg/util/procutils/remote_stat.go b/pkg/util/procutils/remote_stat.go index 04298d79bf3..fdb008e7821 100644 --- a/pkg/util/procutils/remote_stat.go +++ b/pkg/util/procutils/remote_stat.go @@ -51,7 +51,7 @@ func (s *sFileStat) ModTime() time.Time { } func (s *sFileStat) IsDir() bool { - return s.FileType == "directory" + return strings.ToLower(s.FileType) == "directory" } func (s *sFileStat) Sys() interface{} { @@ -62,7 +62,7 @@ func RemoteStat(filename string) (os.FileInfo, error) { args := []string{} switch runtime.GOOS { case "darwin": - args = []string{"-f", `{"file_size":%z,"file_name":"%N","file_type":"%T"}`, filename} + args = []string{"-f", `{"file_size":%z,"file_name":"%N","file_type":"%HT"}`, filename} default: args = []string{"-c", `{"file_size":%s,"file_name":"%n","file_type":"%F"}`, filename} } diff --git a/pkg/util/sysutils/hugepages_test.go b/pkg/util/sysutils/hugepages_test.go index a757462684b..8568b17c0a9 100644 --- a/pkg/util/sysutils/hugepages_test.go +++ b/pkg/util/sysutils/hugepages_test.go @@ -15,12 +15,16 @@ package sysutils import ( + "runtime" "testing" "yunion.io/x/jsonutils" ) func TestGetHugepages(t *testing.T) { + if runtime.GOOS != "linux" { + t.Skip("Skipping GetHugepages test on non-linux") + } hp, err := GetHugepages() if err != nil { t.Errorf("GetHugepages fail %s", err) diff --git a/pkg/util/sysutils/nics_test.go b/pkg/util/sysutils/nics_test.go index 8bb884084c3..e23f89cb7c1 100644 --- a/pkg/util/sysutils/nics_test.go +++ b/pkg/util/sysutils/nics_test.go @@ -15,12 +15,16 @@ package sysutils import ( + "runtime" "testing" "yunion.io/x/jsonutils" ) func TestNics(t *testing.T) { + if runtime.GOOS != "linux" { + t.Skip("Skipping Nics test on non-linux") + } nics, err := Nics() if err != nil { t.Errorf("error %s", err) diff --git a/pkg/util/sysutils/storages_test.go b/pkg/util/sysutils/storages_test.go index 5cec9ecc650..7991e3b0e7c 100644 --- a/pkg/util/sysutils/storages_test.go +++ b/pkg/util/sysutils/storages_test.go @@ -14,9 +14,15 @@ package sysutils -import "testing" +import ( + "runtime" + "testing" +) func TestDetectStorageType(t *testing.T) { + if runtime.GOOS != "linux" { + t.Skip("Skipping DetectStorageType test on non-linux") + } s, err := DetectStorageType() if err != nil { t.Errorf("DetectStorageType fail %s", err)