Identify drives by a Scrutiny UUID instead of wwn (#960)
* Generate a UUIDv5 from a random namespace based on WWN, model name, and serial number * Migrate sqlite and influxdb data accordingly * Update frontend API routes and components * Fixes #923
This commit is contained in:
@@ -1,17 +1,26 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/database"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func ArchiveDevice(c *gin.Context) {
|
||||
logger := c.MustGet("LOGGER").(*logrus.Entry)
|
||||
deviceRepo := c.MustGet("DEVICE_REPOSITORY").(database.DeviceRepo)
|
||||
|
||||
err := deviceRepo.UpdateDeviceArchived(c, c.Param("wwn"), true)
|
||||
scrutiny_uuid, err := uuid.FromString(c.Param("scrutiny_uuid"))
|
||||
if err != nil {
|
||||
logger.Errorln("Invalid scrutiny uuid", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
return
|
||||
}
|
||||
|
||||
err = deviceRepo.UpdateDeviceArchived(c, scrutiny_uuid, true)
|
||||
if err != nil {
|
||||
logger.Errorln("An error occurred while archiving device", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/database"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func DeleteDevice(c *gin.Context) {
|
||||
logger := c.MustGet("LOGGER").(*logrus.Entry)
|
||||
deviceRepo := c.MustGet("DEVICE_REPOSITORY").(database.DeviceRepo)
|
||||
|
||||
err := deviceRepo.DeleteDevice(c, c.Param("wwn"))
|
||||
scrutiny_uuid, err := uuid.FromString(c.Param("scrutiny_uuid"))
|
||||
if err != nil {
|
||||
logger.Errorln("Invalid scrutiny uuid", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
return
|
||||
}
|
||||
err = deviceRepo.DeleteDevice(c, scrutiny_uuid)
|
||||
if err != nil {
|
||||
logger.Errorln("An error occurred while deleting device", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
|
||||
@@ -6,14 +6,20 @@ import (
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/database"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/thresholds"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func GetDeviceDetails(c *gin.Context) {
|
||||
logger := c.MustGet("LOGGER").(*logrus.Entry)
|
||||
deviceRepo := c.MustGet("DEVICE_REPOSITORY").(database.DeviceRepo)
|
||||
|
||||
device, err := deviceRepo.GetDeviceDetails(c, c.Param("wwn"))
|
||||
scrutiny_uuid, err := uuid.FromString(c.Param("scrutiny_uuid"))
|
||||
if err != nil {
|
||||
logger.Errorln("Invalid scrutiny uuid", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
return
|
||||
}
|
||||
device, err := deviceRepo.GetDeviceDetails(c, scrutiny_uuid)
|
||||
if err != nil {
|
||||
logger.Errorln("An error occurred while retrieving device details", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
@@ -25,7 +31,7 @@ func GetDeviceDetails(c *gin.Context) {
|
||||
durationKey = "forever"
|
||||
}
|
||||
|
||||
smartResults, err := deviceRepo.GetSmartAttributeHistory(c, c.Param("wwn"), durationKey, 0, 0, nil)
|
||||
smartResults, err := deviceRepo.GetSmartAttributeHistory(c, scrutiny_uuid, durationKey, 0, 0, nil)
|
||||
if err != nil {
|
||||
logger.Errorln("An error occurred while retrieving device smart results", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/database"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func GetDevicesSummary(c *gin.Context) {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/database"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/samber/lo"
|
||||
"github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// register devices that are detected by various collectors.
|
||||
@@ -23,9 +24,9 @@ func RegisterDevices(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
//filter any device with empty wwn (they are invalid)
|
||||
// Filter any device without a scrutiny UUID. This should never happen...
|
||||
detectedStorageDevices := lo.Filter[models.Device](collectorDeviceWrapper.Data, func(dev models.Device, _ int) bool {
|
||||
return len(dev.WWN) > 0
|
||||
return !dev.ScrutinyUUID.IsNil()
|
||||
})
|
||||
|
||||
errs := []error{}
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/database"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func UnarchiveDevice(c *gin.Context) {
|
||||
logger := c.MustGet("LOGGER").(*logrus.Entry)
|
||||
deviceRepo := c.MustGet("DEVICE_REPOSITORY").(database.DeviceRepo)
|
||||
|
||||
err := deviceRepo.UpdateDeviceArchived(c, c.Param("wwn"), false)
|
||||
scrutiny_uuid, err := uuid.FromString(c.Param("scrutiny_uuid"))
|
||||
if err != nil {
|
||||
logger.Errorln("Invalid scrutiny uuid", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
return
|
||||
}
|
||||
err = deviceRepo.UpdateDeviceArchived(c, scrutiny_uuid, false)
|
||||
if err != nil {
|
||||
logger.Errorln("An error occurred while unarchiving device", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models/collector"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/notify"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -22,12 +23,15 @@ func UploadDeviceMetrics(c *gin.Context) {
|
||||
|
||||
//appConfig := c.MustGet("CONFIG").(config.Interface)
|
||||
|
||||
if c.Param("wwn") == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"success": false})
|
||||
scrutiny_uuid, err := uuid.FromString(c.Param("scrutiny_uuid"))
|
||||
if err != nil {
|
||||
logger.Errorln("Invalid scrutiny uuid", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
return
|
||||
}
|
||||
|
||||
var collectorSmartData collector.SmartInfo
|
||||
err := c.BindJSON(&collectorSmartData)
|
||||
err = c.BindJSON(&collectorSmartData)
|
||||
if err != nil {
|
||||
logger.Errorln("Cannot parse SMART data", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
@@ -35,7 +39,7 @@ func UploadDeviceMetrics(c *gin.Context) {
|
||||
}
|
||||
|
||||
//update the device information if necessary
|
||||
updatedDevice, err := deviceRepo.UpdateDevice(c, c.Param("wwn"), collectorSmartData)
|
||||
updatedDevice, err := deviceRepo.UpdateDevice(c, scrutiny_uuid, collectorSmartData)
|
||||
if err != nil {
|
||||
logger.Errorln("An error occurred while updating device data from smartctl metrics:", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
@@ -43,7 +47,7 @@ func UploadDeviceMetrics(c *gin.Context) {
|
||||
}
|
||||
|
||||
// insert smart info
|
||||
smartData, err := deviceRepo.SaveSmartAttributes(c, c.Param("wwn"), collectorSmartData)
|
||||
smartData, err := deviceRepo.SaveSmartAttributes(c, scrutiny_uuid, collectorSmartData)
|
||||
if err != nil {
|
||||
logger.Errorln("An error occurred while saving smartctl metrics", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
@@ -52,7 +56,7 @@ func UploadDeviceMetrics(c *gin.Context) {
|
||||
|
||||
if smartData.Status != pkg.DeviceStatusPassed {
|
||||
//there is a failure detected by Scrutiny, update the device status on the homepage.
|
||||
updatedDevice, err = deviceRepo.UpdateDeviceStatus(c, c.Param("wwn"), smartData.Status)
|
||||
updatedDevice, err = deviceRepo.UpdateDeviceStatus(c, scrutiny_uuid, smartData.Status)
|
||||
if err != nil {
|
||||
logger.Errorln("An error occurred while updating device status", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
@@ -61,7 +65,7 @@ func UploadDeviceMetrics(c *gin.Context) {
|
||||
}
|
||||
|
||||
// save smart temperature data (ignore failures)
|
||||
err = deviceRepo.SaveSmartTemperature(c, c.Param("wwn"), updatedDevice.DeviceProtocol, collectorSmartData, appConfig.GetBool(fmt.Sprintf("%s.collector.discard_sct_temp_history", config.DB_USER_SETTINGS_SUBKEY)))
|
||||
err = deviceRepo.SaveSmartTemperature(c, scrutiny_uuid, updatedDevice.DeviceProtocol, collectorSmartData, appConfig.GetBool(fmt.Sprintf("%s.collector.discard_sct_temp_history", config.DB_USER_SETTINGS_SUBKEY)))
|
||||
if err != nil {
|
||||
logger.Errorln("An error occurred while saving smartctl temp data", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
@@ -73,6 +77,7 @@ func UploadDeviceMetrics(c *gin.Context) {
|
||||
logger,
|
||||
updatedDevice,
|
||||
smartData,
|
||||
scrutiny_uuid,
|
||||
pkg.MetricsStatusThreshold(appConfig.GetInt(fmt.Sprintf("%s.metrics.status_threshold", config.DB_USER_SETTINGS_SUBKEY))),
|
||||
pkg.MetricsStatusFilterAttributes(appConfig.GetInt(fmt.Sprintf("%s.metrics.status_filter_attributes", config.DB_USER_SETTINGS_SUBKEY))),
|
||||
appConfig.GetBool(fmt.Sprintf("%s.metrics.repeat_notifications", config.DB_USER_SETTINGS_SUBKEY)),
|
||||
|
||||
@@ -2,6 +2,10 @@ package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/analogj/go-util/utils"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/config"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/errors"
|
||||
@@ -9,9 +13,6 @@ import (
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/web/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type AppEngine struct {
|
||||
@@ -37,15 +38,15 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine {
|
||||
api.GET("/health", handler.HealthCheck)
|
||||
api.POST("/health/notify", handler.SendTestNotification) //check if notifications are configured correctly
|
||||
|
||||
api.POST("/devices/register", handler.RegisterDevices) //used by Collector to register new devices and retrieve filtered list
|
||||
api.GET("/summary", handler.GetDevicesSummary) //used by Dashboard
|
||||
api.GET("/summary/temp", handler.GetDevicesSummaryTempHistory) //used by Dashboard (Temperature history dropdown)
|
||||
api.POST("/device/:wwn/smart", handler.UploadDeviceMetrics) //used by Collector to upload data
|
||||
api.POST("/device/:wwn/selftest", handler.UploadDeviceSelfTests)
|
||||
api.GET("/device/:wwn/details", handler.GetDeviceDetails) //used by Details
|
||||
api.POST("/device/:wwn/archive", handler.ArchiveDevice) //used by UI to archive device
|
||||
api.POST("/device/:wwn/unarchive", handler.UnarchiveDevice) //used by UI to unarchive device
|
||||
api.DELETE("/device/:wwn", handler.DeleteDevice) //used by UI to delete device
|
||||
api.POST("/devices/register", handler.RegisterDevices) //used by Collector to register new devices and retrieve filtered list
|
||||
api.GET("/summary", handler.GetDevicesSummary) //used by Dashboard
|
||||
api.GET("/summary/temp", handler.GetDevicesSummaryTempHistory) //used by Dashboard (Temperature history dropdown)
|
||||
api.POST("/device/:scrutiny_uuid/smart", handler.UploadDeviceMetrics) //used by Collector to upload data
|
||||
api.POST("/device/:scrutiny_uuid/selftest", handler.UploadDeviceSelfTests)
|
||||
api.GET("/device/:scrutiny_uuid/details", handler.GetDeviceDetails) //used by Details
|
||||
api.POST("/device/:scrutiny_uuid/archive", handler.ArchiveDevice) //used by UI to archive device
|
||||
api.POST("/device/:scrutiny_uuid/unarchive", handler.UnarchiveDevice) //used by UI to unarchive device
|
||||
api.DELETE("/device/:scrutiny_uuid", handler.DeleteDevice) //used by UI to delete device
|
||||
|
||||
api.GET("/settings", handler.GetSettings) //used to get settings
|
||||
api.POST("/settings", handler.SaveSettings) //used to save settings
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models/collector"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/web"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
@@ -35,7 +36,7 @@ docker run --rm -it -p 8086:8086 \
|
||||
-e DOCKER_INFLUXDB_INIT_ORG=scrutiny \
|
||||
-e DOCKER_INFLUXDB_INIT_BUCKET=metrics \
|
||||
-e DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=my-super-secret-auth-token \
|
||||
influxdb:2.0
|
||||
influxdb:2.2
|
||||
*/
|
||||
|
||||
//func TestMain(m *testing.M) {
|
||||
@@ -216,7 +217,7 @@ func (suite *ServerTestSuite) TestUploadDeviceMetricsRoute() {
|
||||
require.Equal(suite.T(), 200, wr.Code)
|
||||
|
||||
mr := httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("POST", suite.Basepath+"/api/device/0x5000cca264eb01d7/smart", metricsfile)
|
||||
req, _ = http.NewRequest("POST", suite.Basepath+"/api/device/9a4d34b5-b2ee-51ef-8506-90eea09be417/smart", metricsfile)
|
||||
router.ServeHTTP(mr, req)
|
||||
require.Equal(suite.T(), 200, mr.Code)
|
||||
|
||||
@@ -275,28 +276,31 @@ func (suite *ServerTestSuite) TestPopulateMultiple() {
|
||||
router.ServeHTTP(wr, req)
|
||||
require.Equal(suite.T(), 200, wr.Code)
|
||||
|
||||
// NOTE: The scrutiny_uuid's below must come from devicesfile because those get inserted into the database.
|
||||
// They don't match the scrutiny_uuid that would be derived from the smart info files because the drives
|
||||
// in those files don't match those in the registration. Currently, scrutiny does not reconcile the two.
|
||||
mr := httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("POST", suite.Basepath+"/api/device/0x5000cca264eb01d7/smart", metricsfile)
|
||||
req, _ = http.NewRequest("POST", suite.Basepath+"/api/device/ecfaaf20-d1f6-558b-b33a-3e8db19a6c2c/smart", metricsfile)
|
||||
router.ServeHTTP(mr, req)
|
||||
require.Equal(suite.T(), 200, mr.Code)
|
||||
|
||||
fr := httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("POST", suite.Basepath+"/api/device/0x5000cca264ec3183/smart", failfile)
|
||||
req, _ = http.NewRequest("POST", suite.Basepath+"/api/device/3ea22b35-682b-49fb-a655-abffed108e48/smart", failfile)
|
||||
router.ServeHTTP(fr, req)
|
||||
require.Equal(suite.T(), 200, fr.Code)
|
||||
|
||||
nr := httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("POST", suite.Basepath+"/api/device/0x5002538e40a22954/smart", nvmefile)
|
||||
req, _ = http.NewRequest("POST", suite.Basepath+"/api/device/d8796fe7-2422-520c-8991-e970993dad3e/smart", nvmefile)
|
||||
router.ServeHTTP(nr, req)
|
||||
require.Equal(suite.T(), 200, nr.Code)
|
||||
|
||||
sr := httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("POST", suite.Basepath+"/api/device/0x5000cca252c859cc/smart", scsifile)
|
||||
req, _ = http.NewRequest("POST", suite.Basepath+"/api/device/00328b73-9f8a-53ad-8f20-8d0b1be00f47/smart", scsifile)
|
||||
router.ServeHTTP(sr, req)
|
||||
require.Equal(suite.T(), 200, sr.Code)
|
||||
|
||||
s2r := httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("POST", suite.Basepath+"/api/device/0x5000cca264ebc248/smart", scsi2file)
|
||||
req, _ = http.NewRequest("POST", suite.Basepath+"/api/device/e5ccc378-24fc-5a9d-b1ce-8732096a9ea5/smart", scsi2file)
|
||||
router.ServeHTTP(s2r, req)
|
||||
require.Equal(suite.T(), 200, s2r.Code)
|
||||
|
||||
@@ -555,7 +559,7 @@ func (suite *ServerTestSuite) TestGetDevicesSummaryRoute_Nvme() {
|
||||
require.Equal(suite.T(), 200, wr.Code)
|
||||
|
||||
mr := httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("POST", suite.Basepath+"/api/device/a4c8e8ed-11a0-4c97-9bba-306440f1b944/smart", metricsfile)
|
||||
req, _ = http.NewRequest("POST", suite.Basepath+"/api/device/bde1d2d2-7e5c-525a-8327-6adbfa382637/smart", metricsfile)
|
||||
router.ServeHTTP(mr, req)
|
||||
require.Equal(suite.T(), 200, mr.Code)
|
||||
|
||||
@@ -568,6 +572,8 @@ func (suite *ServerTestSuite) TestGetDevicesSummaryRoute_Nvme() {
|
||||
require.NoError(suite.T(), err)
|
||||
|
||||
//assert
|
||||
require.Equal(suite.T(), "a4c8e8ed-11a0-4c97-9bba-306440f1b944", deviceSummary.Data.Summary["a4c8e8ed-11a0-4c97-9bba-306440f1b944"].Device.WWN)
|
||||
require.Equal(suite.T(), pkg.DeviceStatusPassed, deviceSummary.Data.Summary["a4c8e8ed-11a0-4c97-9bba-306440f1b944"].Device.DeviceStatus)
|
||||
deviceUUIDString := "bde1d2d2-7e5c-525a-8327-6adbfa382637"
|
||||
deviceUUID := uuid.Must(uuid.FromString(deviceUUIDString))
|
||||
require.Equal(suite.T(), deviceUUID, deviceSummary.Data.Summary[deviceUUIDString].Device.ScrutinyUUID)
|
||||
require.Equal(suite.T(), pkg.DeviceStatusPassed, deviceSummary.Data.Summary[deviceUUIDString].Device.DeviceStatus)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
"form_factor": "",
|
||||
"smart_support": false,
|
||||
"device_protocol": "NVMe",
|
||||
"device_type": "nvme"
|
||||
"device_type": "nvme",
|
||||
"scrutiny_uuid": "bde1d2d2-7e5c-525a-8327-6adbfa382637"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+17
-10
@@ -12,27 +12,29 @@
|
||||
"rotational_speed": 0,
|
||||
"capacity": 500107862016,
|
||||
"form_factor": "",
|
||||
"smart_support": false
|
||||
"smart_support": false,
|
||||
"scrutiny_uuid": "ecfaaf20-d1f6-558b-b33a-3e8db19a6c2c"
|
||||
},
|
||||
{
|
||||
"wwn": "0x5000cca264eb01d7",
|
||||
"device_name": "sdb",
|
||||
"manufacturer": "ATA",
|
||||
"model_name": "WDC_WD140EDFZ-11A0VA0",
|
||||
"model_name": "WDC WD140EDFZ-11A0VA0",
|
||||
"interface_type": "SCSI",
|
||||
"interface_speed": "",
|
||||
"serial_number": "9RK1XXXXX",
|
||||
"serial_number": "9RK1XXXX",
|
||||
"firmware": "",
|
||||
"rotational_speed": 0,
|
||||
"capacity": 14000519643136,
|
||||
"form_factor": "",
|
||||
"smart_support": false
|
||||
"smart_support": false,
|
||||
"scrutiny_uuid": "3ea22b35-682b-49fb-a655-abffed108e48"
|
||||
},
|
||||
{
|
||||
"wwn": "0x5000cca264ec3183",
|
||||
"device_name": "sdc",
|
||||
"manufacturer": "ATA",
|
||||
"model_name": "WDC_WD140EDFZ-11A0VA0",
|
||||
"model_name": "WDC WD140EDFZ-11A0VA0",
|
||||
"interface_type": "SCSI",
|
||||
"interface_speed": "",
|
||||
"serial_number": "9RK4XXXXX",
|
||||
@@ -40,7 +42,8 @@
|
||||
"rotational_speed": 0,
|
||||
"capacity": 14000519643136,
|
||||
"form_factor": "",
|
||||
"smart_support": false
|
||||
"smart_support": false,
|
||||
"scrutiny_uuid": "42caca8a-9b95-5c75-b059-305771a2a193"
|
||||
},
|
||||
{
|
||||
"wwn": "0x5000cca252c859cc",
|
||||
@@ -54,7 +57,8 @@
|
||||
"rotational_speed": 0,
|
||||
"capacity": 8001563222016,
|
||||
"form_factor": "",
|
||||
"smart_support": false
|
||||
"smart_support": false,
|
||||
"scrutiny_uuid": "d8796fe7-2422-520c-8991-e970993dad3e"
|
||||
},
|
||||
{
|
||||
"wwn": "0x5000cca264ebc248",
|
||||
@@ -68,7 +72,8 @@
|
||||
"rotational_speed": 0,
|
||||
"capacity": 14000519643136,
|
||||
"form_factor": "",
|
||||
"smart_support": false
|
||||
"smart_support": false,
|
||||
"scrutiny_uuid": "00328b73-9f8a-53ad-8f20-8d0b1be00f47"
|
||||
},
|
||||
{
|
||||
"wwn": "0x50014ee20b2a72a9",
|
||||
@@ -82,7 +87,8 @@
|
||||
"rotational_speed": 0,
|
||||
"capacity": 6001175126016,
|
||||
"form_factor": "",
|
||||
"smart_support": false
|
||||
"smart_support": false,
|
||||
"scrutiny_uuid": "e5ccc378-24fc-5a9d-b1ce-8732096a9ea5"
|
||||
},
|
||||
{
|
||||
"wwn": "0x5000c500673e6b5f",
|
||||
@@ -96,7 +102,8 @@
|
||||
"rotational_speed": 0,
|
||||
"capacity": 6001175126016,
|
||||
"form_factor": "",
|
||||
"smart_support": false
|
||||
"smart_support": false,
|
||||
"scrutiny_uuid": "acfbce7d-0e19-579b-895e-85809dab63fb"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,15 +4,16 @@
|
||||
"wwn": "0x5000cca264eb01d7",
|
||||
"device_name": "sdb",
|
||||
"manufacturer": "ATA",
|
||||
"model_name": "WDC_WD140EDFZ-11A0VA0",
|
||||
"model_name": "WDC WD140EDFZ-11A0VA0",
|
||||
"interface_type": "SCSI",
|
||||
"interface_speed": "",
|
||||
"serial_number": "9RK1XXXXX",
|
||||
"serial_number": "9RK1XXXX",
|
||||
"firmware": "",
|
||||
"rotational_speed": 0,
|
||||
"capacity": 14000519643136,
|
||||
"form_factor": "",
|
||||
"smart_support": false
|
||||
"smart_support": false,
|
||||
"scrutiny_uuid": "9a4d34b5-b2ee-51ef-8506-90eea09be417"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user