diff --git a/collector/pkg/detect/devices_darwin.go b/collector/pkg/detect/devices_darwin.go index 928a38d..62a6ba2 100644 --- a/collector/pkg/detect/devices_darwin.go +++ b/collector/pkg/detect/devices_darwin.go @@ -13,7 +13,7 @@ func DevicePrefix() string { func (d *Detect) Start() ([]models.Device, error) { d.Shell = shell.Create() - // call the base/common functionality to get a list of devicess + // call the base/common functionality to get a list of devices detectedDevices, err := d.SmartctlScan() if err != nil { return nil, err diff --git a/collector/pkg/detect/devices_linux.go b/collector/pkg/detect/devices_linux.go index ebe8e88..0c4c7a0 100644 --- a/collector/pkg/detect/devices_linux.go +++ b/collector/pkg/detect/devices_linux.go @@ -1,9 +1,12 @@ package detect import ( + "fmt" "github.com/analogj/scrutiny/collector/pkg/common/shell" "github.com/analogj/scrutiny/collector/pkg/models" "github.com/jaypipes/ghw" + "io/ioutil" + "path/filepath" "strings" ) @@ -22,6 +25,7 @@ func (d *Detect) Start() ([]models.Device, error) { //inflate device info for detected devices. for ndx, _ := range detectedDevices { d.SmartCtlInfo(&detectedDevices[ndx]) //ignore errors. + populateUdevInfo(&detectedDevices[ndx]) //ignore errors. } return detectedDevices, nil @@ -49,3 +53,51 @@ func (d *Detect) wwnFallback(detectedDevice *models.Device) { //wwn must always be lowercase. detectedDevice.WWN = strings.ToLower(detectedDevice.WWN) } + +// as discussed in +// - https://github.com/AnalogJ/scrutiny/issues/225 +// - https://github.com/jaypipes/ghw/issues/59#issue-361915216 +// udev exposes its data in a standardized way under /run/udev/data/.... +func populateUdevInfo(detectedDevice *models.Device) error { + // Get device major:minor numbers + // `cat /sys/class/block/sda/dev` + devNo, err := ioutil.ReadFile(filepath.Join("/sys/class/block/", detectedDevice.DeviceName, "dev")) + if err != nil { + return err + } + + // Look up block device in udev runtime database + // `cat /run/udev/data/b8:0` + udevID := "b" + strings.TrimSpace(string(devNo)) + udevBytes, err := ioutil.ReadFile(filepath.Join("/run/udev/data/", udevID)) + if err != nil { + return err + } + + deviceMountPaths := []string{} + udevInfo := make(map[string]string) + for _, udevLine := range strings.Split(string(udevBytes), "\n") { + if strings.HasPrefix(udevLine, "E:") { + if s := strings.SplitN(udevLine[2:], "=", 2); len(s) == 2 { + udevInfo[s[0]] = s[1] + } + } else if strings.HasPrefix(udevLine, "S:") { + deviceMountPaths = append(deviceMountPaths, udevLine[2:]) + } + } + + //Set additional device information. + if deviceLabel, exists := udevInfo["ID_FS_LABEL"]; exists { + detectedDevice.DeviceLabel = deviceLabel + } + if deviceUUID, exists := udevInfo["ID_FS_UUID"]; exists { + detectedDevice.DeviceUUID = deviceUUID + } + if deviceSerialID, exists := udevInfo["ID_SERIAL"]; exists { + detectedDevice.DeviceSerialID = fmt.Sprintf("%s-%s", udevInfo["ID_BUS"], deviceSerialID) + } + + + return nil +} + diff --git a/collector/pkg/models/device.go b/collector/pkg/models/device.go index dd77794..f06aec8 100644 --- a/collector/pkg/models/device.go +++ b/collector/pkg/models/device.go @@ -1,10 +1,13 @@ package models type Device struct { - WWN string `json:"wwn"` - HostId string `json:"host_id"` + WWN string `json:"wwn"` DeviceName string `json:"device_name"` + DeviceUUID string `json:"device_uuid"` + DeviceSerialID string `json:"device_serial_id"` + DeviceLabel string `json:"device_label"` + Manufacturer string `json:"manufacturer"` ModelName string `json:"model_name"` InterfaceType string `json:"interface_type"` @@ -17,6 +20,10 @@ type Device struct { SmartSupport bool `json:"smart_support"` DeviceProtocol string `json:"device_protocol"` //protocol determines which smart attribute types are available (ATA, NVMe, SCSI) DeviceType string `json:"device_type"` //device type is used for querying with -d/t flag, should only be used by collector. + + // User provided metadata + Label string `json:"label"` + HostId string `json:"host_id"` } type DeviceWrapper struct { diff --git a/webapp/backend/pkg/database/migrations/m20220503120000/device.go b/webapp/backend/pkg/database/migrations/m20220503120000/device.go index bbcdebd..72dd27f 100644 --- a/webapp/backend/pkg/database/migrations/m20220503120000/device.go +++ b/webapp/backend/pkg/database/migrations/m20220503120000/device.go @@ -5,6 +5,7 @@ import ( "time" ) +// Deprecated: m20220503120000.Device is deprecated, only used by db migrations type Device struct { //GORM attributes, see: http://gorm.io/docs/conventions.html CreatedAt time.Time diff --git a/webapp/backend/pkg/database/scrutiny_repository_device.go b/webapp/backend/pkg/database/scrutiny_repository_device.go index 27346f3..ed7d22d 100644 --- a/webapp/backend/pkg/database/scrutiny_repository_device.go +++ b/webapp/backend/pkg/database/scrutiny_repository_device.go @@ -18,7 +18,7 @@ import ( func (sr *scrutinyRepository) RegisterDevice(ctx context.Context, dev models.Device) error { if err := sr.gormClient.WithContext(ctx).Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "wwn"}}, - DoUpdates: clause.AssignmentColumns([]string{"host_id", "device_name", "device_type"}), + DoUpdates: clause.AssignmentColumns([]string{"host_id", "device_name", "device_type", "device_uuid", "device_serial_id", "device_label"}), }).Create(&dev).Error; err != nil { return err } diff --git a/webapp/backend/pkg/database/scrutiny_repository_migrations.go b/webapp/backend/pkg/database/scrutiny_repository_migrations.go index e3e902a..47aeb23 100644 --- a/webapp/backend/pkg/database/scrutiny_repository_migrations.go +++ b/webapp/backend/pkg/database/scrutiny_repository_migrations.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/analogj/scrutiny/webapp/backend/pkg/database/migrations/m20201107210306" "github.com/analogj/scrutiny/webapp/backend/pkg/database/migrations/m20220503120000" + "github.com/analogj/scrutiny/webapp/backend/pkg/database/migrations/m20220509170100" "github.com/analogj/scrutiny/webapp/backend/pkg/models" "github.com/analogj/scrutiny/webapp/backend/pkg/models/collector" "github.com/analogj/scrutiny/webapp/backend/pkg/models/measurements" @@ -257,10 +258,19 @@ func (sr *scrutinyRepository) Migrate(ctx context.Context) error { return err } - //migrate the device database to the current version + //migrate the device database return tx.AutoMigrate(m20220503120000.Device{}) }, }, + { + ID: "m20220509170100", // addl udev device data + Migrate: func(tx *gorm.DB) error { + + //migrate the device database. + // adding addl columns (device_label, device_uuid, device_serial_id) + return tx.AutoMigrate(m20220509170100.Device{}) + }, + }, }) if err := m.Migrate(); err != nil { diff --git a/webapp/backend/pkg/models/device.go b/webapp/backend/pkg/models/device.go index c5272b4..28dd150 100644 --- a/webapp/backend/pkg/models/device.go +++ b/webapp/backend/pkg/models/device.go @@ -21,6 +21,10 @@ type Device struct { WWN string `json:"wwn" gorm:"primary_key"` DeviceName string `json:"device_name"` + DeviceUUID string `json:"device_uuid"` + DeviceSerialID string `json:"device_serial_id"` + DeviceLabel string `json:"device_label"` + Manufacturer string `json:"manufacturer"` ModelName string `json:"model_name"` InterfaceType string `json:"interface_type"`