From 23d5b86b1bd8f95bc79d2b7573d32c955f52831c Mon Sep 17 00:00:00 2001 From: Jason Kulatunga Date: Fri, 18 Sep 2020 19:27:55 -0600 Subject: [PATCH] new device detection engine (OS aware). Uses smartctl --scan to detect drives (and conditionally uses jaypipes/ghw). WWN is calculated from smartctl data, then retrieved from GHW, and fallsback to serial number. WWN calcuation code is based on IEEE spec, for "Registered" IEEE format - NAA5. TODO: support NAA6 and other formats? --- collector/pkg/collector/base.go | 101 ---------------- collector/pkg/collector/metrics.go | 14 ++- collector/pkg/common/exec.go | 33 +++++ .../base_test.go => common/exec_test.go} | 13 +- collector/pkg/detect/detect.go | 114 ++++++++++++++++++ collector/pkg/detect/devices_darwin.go | 106 ++++++++++++++++ collector/pkg/detect/devices_linux.go | 38 ++++++ collector/pkg/detect/devices_windows.go | 29 +++++ collector/pkg/detect/wwn.go | 58 +++++++++ collector/pkg/detect/wwn_test.go | 35 ++++++ go.mod | 3 +- go.sum | 2 + webapp/backend/pkg/models/collector/smart.go | 6 +- 13 files changed, 436 insertions(+), 116 deletions(-) create mode 100644 collector/pkg/common/exec.go rename collector/pkg/{collector/base_test.go => common/exec_test.go} (70%) create mode 100644 collector/pkg/detect/detect.go create mode 100644 collector/pkg/detect/devices_darwin.go create mode 100644 collector/pkg/detect/devices_linux.go create mode 100644 collector/pkg/detect/devices_windows.go create mode 100644 collector/pkg/detect/wwn.go create mode 100644 collector/pkg/detect/wwn_test.go diff --git a/collector/pkg/collector/base.go b/collector/pkg/collector/base.go index c35cd53..ff1801f 100644 --- a/collector/pkg/collector/base.go +++ b/collector/pkg/collector/base.go @@ -3,15 +3,8 @@ package collector import ( "bytes" "encoding/json" - "errors" - "github.com/analogj/scrutiny/collector/pkg/models" - "github.com/jaypipes/ghw" "github.com/sirupsen/logrus" - "io" "net/http" - "os" - "os/exec" - "path" "time" ) @@ -21,77 +14,6 @@ type BaseCollector struct { logger *logrus.Entry } -func (c *BaseCollector) DetectStorageDevices() ([]models.Device, error) { - - //availableDisksJson, err := c.ExecCmd("smartctl", []string{"-j", "--scan"}, "", os.Environ()) - //if err != nil { - // c.logger.Errorf("Error getting block storage info: %v", err) - // return nil, err - //} - // - //var smartctlScan models.Scan - //err = json.Unmarshal([]byte(availableDisksJson), &smartctlScan) - //if err != nil { - // return nil, err - //} - - block, err := ghw.Block() - if err != nil { - c.logger.Errorf("Error getting block storage info: %v", err) - return nil, err - } - - approvedDisks := []models.Device{} - for _, disk := range block.Disks { - - // ignore optical drives and floppy disks - if disk.DriveType == ghw.DRIVE_TYPE_FDD || disk.DriveType == ghw.DRIVE_TYPE_ODD { - c.logger.Debugf(" => Ignore: Optical or floppy disk - (found %s)\n", disk.DriveType.String()) - continue - } - - // ignore removable disks - if disk.IsRemovable { - c.logger.Debugf(" => Ignore: Removable disk (%v)\n", disk.IsRemovable) - continue - } - - // ignore virtual disks & mobile phone storage devices - if disk.StorageController == ghw.STORAGE_CONTROLLER_VIRTIO || disk.StorageController == ghw.STORAGE_CONTROLLER_MMC { - c.logger.Debugf(" => Ignore: Virtual/multi-media storage controller - (found %s)\n", disk.StorageController.String()) - continue - } - - // Skip unknown storage controllers, not usually S.M.A.R.T compatible. - if disk.StorageController == ghw.STORAGE_CONTROLLER_UNKNOWN { - c.logger.Debugf(" => Ignore: Unknown storage controller - (found %s)\n", disk.StorageController.String()) - continue - } - - diskModel := models.Device{ - WWN: disk.WWN, - Manufacturer: disk.Vendor, - ModelName: disk.Model, - InterfaceType: disk.StorageController.String(), - //InterfaceSpeed: string - SerialNumber: disk.SerialNumber, - Capacity: int64(disk.SizeBytes), - //Firmware string - //RotationSpeed int - - DeviceName: disk.Name, - } - if len(diskModel.WWN) == 0 { - //(macOS and some other os's) do not provide a WWN, so we're going to fallback to - //diskname as identifier if WWN is not present - diskModel.WWN = disk.Name - } - - approvedDisks = append(approvedDisks, diskModel) - } - return approvedDisks, nil -} - func (c *BaseCollector) getJson(url string, target interface{}) error { r, err := httpClient.Get(url) @@ -118,29 +40,6 @@ func (c *BaseCollector) postJson(url string, body interface{}, target interface{ return json.NewDecoder(r.Body).Decode(target) } -func (c *BaseCollector) ExecCmd(cmdName string, cmdArgs []string, workingDir string, environ []string) (string, error) { - - cmd := exec.Command(cmdName, cmdArgs...) - var stdBuffer bytes.Buffer - mw := io.MultiWriter(os.Stdout, &stdBuffer) - - cmd.Stdout = mw - cmd.Stderr = mw - - if environ != nil { - cmd.Env = environ - } - if workingDir != "" && path.IsAbs(workingDir) { - cmd.Dir = workingDir - } else if workingDir != "" { - return "", errors.New("Working Directory must be an absolute path") - } - - err := cmd.Run() - return stdBuffer.String(), err - -} - func (c *BaseCollector) LogSmartctlExitCode(exitCode int) { if exitCode&0x01 != 0 { c.logger.Errorln("smartctl could not parse commandline") diff --git a/collector/pkg/collector/metrics.go b/collector/pkg/collector/metrics.go index 90b13b4..10b8348 100644 --- a/collector/pkg/collector/metrics.go +++ b/collector/pkg/collector/metrics.go @@ -2,7 +2,10 @@ package collector import ( "bytes" + "encoding/json" "fmt" + "github.com/analogj/scrutiny/collector/pkg/common" + "github.com/analogj/scrutiny/collector/pkg/detect" "github.com/analogj/scrutiny/collector/pkg/errors" "github.com/analogj/scrutiny/collector/pkg/models" "github.com/sirupsen/logrus" @@ -43,13 +46,18 @@ func (mc *MetricsCollector) Run() error { apiEndpoint.Path = "/api/devices/register" deviceRespWrapper := new(models.DeviceWrapper) - detectedStorageDevices, err := mc.DetectStorageDevices() + + deviceDetector := detect.Detect{ + Logger: mc.logger, + } + detectedStorageDevices, err := deviceDetector.Start() if err != nil { return err } mc.logger.Infoln("Sending detected devices to API, for filtering & validation") - mc.logger.Debugf("Detected devices: %v", detectedStorageDevices) + jsonObj, _ := json.Marshal(detectedStorageDevices) + mc.logger.Debugf("Detected devices: %v", string(jsonObj)) err = mc.postJson(apiEndpoint.String(), models.DeviceWrapper{ Data: detectedStorageDevices, }, &deviceRespWrapper) @@ -93,7 +101,7 @@ func (mc *MetricsCollector) Collect(wg *sync.WaitGroup, deviceWWN string, device defer wg.Done() mc.logger.Infof("Collecting smartctl results for %s\n", deviceName) - result, err := mc.ExecCmd("smartctl", []string{"-a", "-j", fmt.Sprintf("/dev/%s", deviceName)}, "", nil) + result, err := common.ExecCmd("smartctl", []string{"-a", "-j", fmt.Sprintf("/dev/%s", deviceName)}, "", nil) resultBytes := []byte(result) if err != nil { if exitError, ok := err.(*exec.ExitError); ok { diff --git a/collector/pkg/common/exec.go b/collector/pkg/common/exec.go new file mode 100644 index 0000000..97a28c1 --- /dev/null +++ b/collector/pkg/common/exec.go @@ -0,0 +1,33 @@ +package common + +import ( + "bytes" + "errors" + "io" + "os" + "os/exec" + "path" +) + +func ExecCmd(cmdName string, cmdArgs []string, workingDir string, environ []string) (string, error) { + + cmd := exec.Command(cmdName, cmdArgs...) + var stdBuffer bytes.Buffer + mw := io.MultiWriter(os.Stdout, &stdBuffer) + + cmd.Stdout = mw + cmd.Stderr = mw + + if environ != nil { + cmd.Env = environ + } + if workingDir != "" && path.IsAbs(workingDir) { + cmd.Dir = workingDir + } else if workingDir != "" { + return "", errors.New("Working Directory must be an absolute path") + } + + err := cmd.Run() + return stdBuffer.String(), err + +} diff --git a/collector/pkg/collector/base_test.go b/collector/pkg/common/exec_test.go similarity index 70% rename from collector/pkg/collector/base_test.go rename to collector/pkg/common/exec_test.go index def6dff..ef75a3e 100644 --- a/collector/pkg/collector/base_test.go +++ b/collector/pkg/common/exec_test.go @@ -1,7 +1,7 @@ -package collector_test +package common_test import ( - "github.com/analogj/scrutiny/collector/pkg/collector" + "github.com/analogj/scrutiny/collector/pkg/common" "github.com/stretchr/testify/require" "os/exec" "testing" @@ -11,10 +11,9 @@ func TestExecCmd(t *testing.T) { t.Parallel() //setup - bc := collector.BaseCollector{} //test - result, err := bc.ExecCmd("echo", []string{"hello world"}, "", nil) + result, err := common.ExecCmd("echo", []string{"hello world"}, "", nil) //assert require.NoError(t, err) @@ -25,10 +24,9 @@ func TestExecCmd_Date(t *testing.T) { t.Parallel() //setup - bc := collector.BaseCollector{} //test - _, err := bc.ExecCmd("date", []string{}, "", nil) + _, err := common.ExecCmd("date", []string{}, "", nil) //assert require.NoError(t, err) @@ -56,10 +54,9 @@ func TestExecCmd_InvalidCommand(t *testing.T) { t.Parallel() //setup - bc := collector.BaseCollector{} //test - _, err := bc.ExecCmd("invalid_binary", []string{}, "", nil) + _, err := common.ExecCmd("invalid_binary", []string{}, "", nil) //assert _, castOk := err.(*exec.ExitError) diff --git a/collector/pkg/detect/detect.go b/collector/pkg/detect/detect.go new file mode 100644 index 0000000..2735218 --- /dev/null +++ b/collector/pkg/detect/detect.go @@ -0,0 +1,114 @@ +package detect + +import ( + "encoding/json" + "fmt" + "github.com/analogj/scrutiny/collector/pkg/common" + "github.com/analogj/scrutiny/collector/pkg/models" + "github.com/analogj/scrutiny/webapp/backend/pkg/models/collector" + "github.com/denisbrodbeck/machineid" + "github.com/sirupsen/logrus" + "os" + "strings" +) + +type Detect struct { + Logger *logrus.Entry +} + +//private/common functions + +// This function calls smartctl --scan which can be used to detect storage devices. +// It has a couple of issues however: +// - --scan does not return any results on mac +// +// To handle these issues, we have OS specific wrapper functions that update/modify these detected devices. +// models.Device returned from this function only contain the minimum data for smartctl to execute: device type and device name (device file). +func (d *Detect) smartctlScan() ([]models.Device, error) { + //we use smartctl to detect all the drives available. + detectedDeviceConnJson, err := common.ExecCmd("smartctl", []string{"--scan", "-j"}, "", os.Environ()) + if err != nil { + d.Logger.Errorf("Error scanning for devices: %v", err) + return nil, err + } + + var detectedDeviceConns models.Scan + err = json.Unmarshal([]byte(detectedDeviceConnJson), &detectedDeviceConns) + if err != nil { + d.Logger.Errorf("Error decoding detected devices: %v", err) + return nil, err + } + + detectedDevices := []models.Device{} + + for _, detectedDevice := range detectedDeviceConns.Devices { + detectedDevices = append(detectedDevices, models.Device{ + DeviceType: detectedDevice.Type, + DeviceName: strings.TrimPrefix(detectedDevice.Name, d.devicePrefix()), + }) + } + + return detectedDevices, nil +} + +//updates a device model with information from smartctl --scan +// It has a couple of issues however: +// - WWN is provided as component data, rather than a "string". We'll have to generate the WWN value ourselves +// - WWN from smartctl only provided for ATA protocol drives, NVMe and SCSI drives do not include WWN. +func (d *Detect) smartCtlInfo(device *models.Device) error { + + args := []string{"--info", "-j"} + if len(device.DeviceType) > 0 { + args = append(args, "-d", device.DeviceType) + } + args = append(args, fmt.Sprintf("%s%s", d.devicePrefix(), device.DeviceName)) + + availableDeviceInfoJson, err := common.ExecCmd("smartctl", args, "", os.Environ()) + if err != nil { + d.Logger.Errorf("Could not retrieve device information for %s: %v", device.DeviceName, err) + return err + } + + var availableDeviceInfo collector.SmartInfo + err = json.Unmarshal([]byte(availableDeviceInfoJson), &availableDeviceInfo) + if err != nil { + d.Logger.Errorf("Could not decode device information for %s: %v", device.DeviceName, err) + return err + } + + //DeviceType and DeviceName are already populated. + //WWN + device.ModelName = availableDeviceInfo.ModelName + //InterfaceType: + device.InterfaceSpeed = availableDeviceInfo.InterfaceSpeed.Current.String + device.SerialNumber = availableDeviceInfo.SerialNumber + device.Firmware = availableDeviceInfo.FirmwareVersion + device.RotationSpeed = availableDeviceInfo.RotationRate + device.Capacity = availableDeviceInfo.UserCapacity.Bytes + device.FormFactor = availableDeviceInfo.FormFactor.Name + device.DeviceProtocol = availableDeviceInfo.Device.Protocol + if len(availableDeviceInfo.Vendor) > 0 { + device.Manufacturer = availableDeviceInfo.Vendor + } + + //populate WWN is possible if present + if availableDeviceInfo.Wwn.Naa != 0 { //valid values are 1-6 (5 is what we handle correctly) + d.Logger.Info("Generating WWN") + wwn := Wwn{ + Naa: availableDeviceInfo.Wwn.Naa, + Oui: availableDeviceInfo.Wwn.Oui, + Id: availableDeviceInfo.Wwn.ID, + } + device.WWN = wwn.ToString() + } else { + d.Logger.Info("Using WWN Fallback") + d.wwnFallback(device) + } + + return nil +} + +//uses https://github.com/denisbrodbeck/machineid to get a OS specific unique machine ID. +func (d *Detect) getMachineId() (string, error) { + return machineid.ProtectedID("scrutiny") +} diff --git a/collector/pkg/detect/devices_darwin.go b/collector/pkg/detect/devices_darwin.go new file mode 100644 index 0000000..ee37c8d --- /dev/null +++ b/collector/pkg/detect/devices_darwin.go @@ -0,0 +1,106 @@ +package detect + +import ( + "github.com/analogj/scrutiny/collector/pkg/models" + "github.com/jaypipes/ghw" + "strings" +) + +func (d *Detect) devicePrefix() string { + return "/dev/" +} + +func (d *Detect) Start() ([]models.Device, error) { + // call the base/common functionality to get a list of devicess + detectedDevices, err := d.smartctlScan() + if err != nil { + return nil, err + } + + //smartctl --scan doesn't seem to detect mac nvme drives, lets see if we can detect them manually. + missingDevices, err := d.findMissingDevices(detectedDevices) //we dont care about the error here, just continue retrieving device info. + if err == nil { + detectedDevices = append(detectedDevices, missingDevices...) + } + + //inflate device info for detected devices. + for ndx, _ := range detectedDevices { + d.smartCtlInfo(&detectedDevices[ndx]) //ignore errors. + } + + return detectedDevices, nil +} + +func (d *Detect) findMissingDevices(detectedDevices []models.Device) ([]models.Device, error) { + + missingDevices := []models.Device{} + + block, err := ghw.Block() + if err != nil { + d.Logger.Errorf("Error getting block storage info: %v", err) + return nil, err + } + + for _, disk := range block.Disks { + + // ignore optical drives and floppy disks + if disk.DriveType == ghw.DRIVE_TYPE_FDD || disk.DriveType == ghw.DRIVE_TYPE_ODD { + d.Logger.Debugf(" => Ignore: Optical or floppy disk - (found %s)\n", disk.DriveType.String()) + continue + } + + // ignore removable disks + if disk.IsRemovable { + d.Logger.Debugf(" => Ignore: Removable disk (%v)\n", disk.IsRemovable) + continue + } + + // ignore virtual disks & mobile phone storage devices + if disk.StorageController == ghw.STORAGE_CONTROLLER_VIRTIO || disk.StorageController == ghw.STORAGE_CONTROLLER_MMC { + d.Logger.Debugf(" => Ignore: Virtual/multi-media storage controller - (found %s)\n", disk.StorageController.String()) + continue + } + + // Skip unknown storage controllers, not usually S.M.A.R.T compatible. + if disk.StorageController == ghw.STORAGE_CONTROLLER_UNKNOWN { + d.Logger.Debugf(" => Ignore: Unknown storage controller - (found %s)\n", disk.StorageController.String()) + continue + } + + //check if device is already detected. + alreadyDetected := false + diskName := strings.TrimPrefix(disk.Name, d.devicePrefix()) + for _, detectedDevice := range detectedDevices { + + if detectedDevice.DeviceName == diskName { + alreadyDetected = true + break + } + } + if !alreadyDetected { + missingDevices = append(missingDevices, models.Device{ + DeviceName: diskName, + DeviceType: "", + }) + } + } + return missingDevices, nil +} + +//WWN values NVMe and SCSI +func (d *Detect) wwnFallback(detectedDevice *models.Device) { + block, err := ghw.Block() + if err == nil { + for _, disk := range block.Disks { + if disk.Name == detectedDevice.DeviceName { + detectedDevice.WWN = disk.WWN + break + } + } + } + + //no WWN found, or could not open Block devices. Either way, fallback to serial number + if len(detectedDevice.WWN) == 0 { + detectedDevice.WWN = detectedDevice.SerialNumber + } +} diff --git a/collector/pkg/detect/devices_linux.go b/collector/pkg/detect/devices_linux.go new file mode 100644 index 0000000..c38894c --- /dev/null +++ b/collector/pkg/detect/devices_linux.go @@ -0,0 +1,38 @@ +package detect + +func (d *Detect) devicePrefix() string { + return "/dev/" +} + +func (d *Detect) Start() ([]models.Device, error) { + // call the base/common functionality to get a list of devices + detectedDevices, err := d.smartctlScan() + if err != nil { + return nil, err + } + + //inflate device info for detected devices. + for ndx, _ := range detectedDevices { + d.smartCtlInfo(&detectedDevices[ndx]) //ignore errors. + } + + return detectedDevices, nil +} + +//WWN values NVMe and SCSI +func (d *Detect) wwnFallback(detectedDevice *models.Device) { + block, err := ghw.Block() + if err == nil { + for _, disk := range block.Disks { + if disk.Name == detectedDevice.DeviceName { + detectedDevice.WWN = disk.WWN + break + } + } + } + + //no WWN found, or could not open Block devices. Either way, fallback to serial number + if len(detectedDevice.WWN) == 0 { + detectedDevice.WWN = detectedDevice.SerialNumber + } +} diff --git a/collector/pkg/detect/devices_windows.go b/collector/pkg/detect/devices_windows.go new file mode 100644 index 0000000..54b8519 --- /dev/null +++ b/collector/pkg/detect/devices_windows.go @@ -0,0 +1,29 @@ +package detect + +func (d *Detect) devicePrefix() string { + return "" +} + +func (d *Detect) Start() ([]models.Device, error) { + // call the base/common functionality to get a list of devices + detectedDevices, err := d.smartctlScan() + if err != nil { + return nil, err + } + + //inflate device info for detected devices. + for ndx, _ := range detectedDevices { + d.smartCtlInfo(&detectedDevices[ndx]) //ignore errors. + } + + return detectedDevices, nil +} + +//WWN values NVMe and SCSI +func (d *Detect) wwnFallback(detectedDevice *models.Device) { + + //fallback to serial number + if len(detectedDevice.WWN) == 0 { + detectedDevice.WWN = detectedDevice.SerialNumber + } +} diff --git a/collector/pkg/detect/wwn.go b/collector/pkg/detect/wwn.go new file mode 100644 index 0000000..d9163ca --- /dev/null +++ b/collector/pkg/detect/wwn.go @@ -0,0 +1,58 @@ +package detect + +import ( + "fmt" +) + +type Wwn struct { + Naa uint64 `json:"naa"` + Oui uint64 `json:"oui"` + Id uint64 `json:"id"` + VendorCode string `json:"vendor_code"` +} + +// this is an incredibly basic converter, that only works for "Registered" IEEE format - NAA5 +// https://standards.ieee.org/content/dam/ieee-standards/standards/web/documents/tutorials/fibre.pdf +// references: +// - https://metacpan.org/pod/Device::WWN +// - https://en.wikipedia.org/wiki/World_Wide_Name +// - https://storagemeat.blogspot.com/2012/08/decoding-wwids-or-how-to-tell-whats-what.html +// - https://bryanchain.com/2016/01/20/breaking-down-an-naa-id-world-wide-name/ + +/* ++----------+---+---+---+---+---+---+---+---+ +| Byte/Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | ++----------+---+---+---+---+---+---+---+---+ +| 0 | NAA (5h) | (MSB) | ++----------+---------------+ + +| 1 | | ++----------+ IEEE OUI | +| 2 | | ++----------+ +---------------+ +| 3 | (LSB) | (MSB) | ++----------+---------------+ + +| 4 | | +| | | ++----------+ | +| 5 | Vendor ID | ++----------+ | +| 6 | | ++----------+ | +| 7 | (LSB) | ++----------+-------------------------------+ + + +*/ + +func (wwn *Wwn) ToString() string { + + var wwnBuffer uint64 + + wwnBuffer = wwn.Id //start with vendor ID + wwnBuffer += (wwn.Oui << 36) //add left-shifted OUI + wwnBuffer += (wwn.Naa << 60) //NAA is a number from 1-6, so decimal == hex. + + //TODO: may need to support additional versions in the future. + + return fmt.Sprintf("%#x", wwnBuffer) +} diff --git a/collector/pkg/detect/wwn_test.go b/collector/pkg/detect/wwn_test.go new file mode 100644 index 0000000..d059035 --- /dev/null +++ b/collector/pkg/detect/wwn_test.go @@ -0,0 +1,35 @@ +package detect_test + +import ( + "fmt" + "github.com/analogj/scrutiny/collector/pkg/detect" + "github.com/stretchr/testify/require" + "testing" +) + +func TestWwn_FromStringTable(t *testing.T) { + + //setup + var tests = []struct { + wwnStr string + wwn detect.Wwn + }{ + + {"0x5002538e40a22954", detect.Wwn{Naa: 5, Oui: 9528, Id: 61213911380}}, //sda + {"0x5000cca264eb01d7", detect.Wwn{Naa: 5, Oui: 3274, Id: 10283057623}}, //sdb + {"0x5000cca264ec3183", detect.Wwn{Naa: 5, Oui: 3274, Id: 10283135363}}, //sdc + {"0x5000cca252c859cc", detect.Wwn{Naa: 5, Oui: 3274, Id: 9978796492}}, //sdd + {"0x50014ee20b2a72a9", detect.Wwn{Naa: 5, Oui: 5358, Id: 8777265833}}, //sde + {"0x5000cca264ebc248", detect.Wwn{Naa: 5, Oui: 3274, Id: 10283106888}}, //sdf + {"0x5000c500673e6b5f", detect.Wwn{Naa: 5, Oui: 3152, Id: 1732143967}}, //sdg + } + //test + for _, tt := range tests { + testname := fmt.Sprintf("%s", tt.wwnStr) + t.Run(testname, func(t *testing.T) { + str := tt.wwn.ToString() + require.Equal(t, tt.wwnStr, str) + }) + } + +} diff --git a/go.mod b/go.mod index e9211df..4b9bbb7 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,10 @@ module github.com/analogj/scrutiny go 1.13 require ( - github.com/AnalogJ/go-util v0.0.0-20200905200945-3b93d31215ae + github.com/AnalogJ/go-util v0.0.0-20200905200945-3b93d31215ae // indirect github.com/analogj/go-util v0.0.0-20190301173314-5295e364eb14 github.com/containrrr/shoutrrr v0.0.0-20200828202222-1da53231b05a + github.com/denisbrodbeck/machineid v1.0.1 github.com/fatih/color v1.9.0 github.com/gin-gonic/gin v1.6.3 github.com/golang/mock v1.4.3 diff --git a/go.sum b/go.sum index 47ec7ef..e32dd06 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= +github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= diff --git a/webapp/backend/pkg/models/collector/smart.go b/webapp/backend/pkg/models/collector/smart.go index c17b0a4..04d5e4c 100644 --- a/webapp/backend/pkg/models/collector/smart.go +++ b/webapp/backend/pkg/models/collector/smart.go @@ -23,9 +23,9 @@ type SmartInfo struct { ModelName string `json:"model_name"` SerialNumber string `json:"serial_number"` Wwn struct { - Naa int `json:"naa"` - Oui int `json:"oui"` - ID int64 `json:"id"` + Naa uint64 `json:"naa"` + Oui uint64 `json:"oui"` + ID uint64 `json:"id"` } `json:"wwn"` FirmwareVersion string `json:"firmware_version"` UserCapacity struct {