"use strict" ;
const Promise = require ( "bluebird" ) ;
const { createDataObject , LocalProperties , ID , Dynamic } = require ( "../../packages/graphql-interface/data-object" ) ;
const upperSnakeCase = require ( "../../packages/upper-snake-case" ) ;
const treecutter = require ( "../../packages/treecutter" ) ;
const deviceNameFromPath = require ( "../../util/device-name-from-path" ) ;
/ * T O I M P L E M E N T :
- resolveProperty
- resolveProperties
- resolveDataSource
- Dynamic
* /
module . exports = function ( types ) {
return function Drive ( { path } ) {
return createDataObject ( {
[ LocalProperties ] : {
path : path ,
/* FIXME: allBlockDevices, for representing every single block device that's hosted on this physical drive, linearly. Need to figure out how that works with representation of mdraid arrays, LVM volumes, etc. */
} ,
[ Dynamic ] : {
// FIXME: namespaces
blockDevice : ( _ , { resolveProperty } ) => {
return Promise . try ( ( ) => {
return resolveProperty ( "interface" ) ;
} ) . then ( ( interface _ ) => {
if ( interface _ === "nvme" ) {
/* NVMe drives do not have a single block device, they have zero or more namespaces */
return null ;
} else {
return types . BlockDevice ( { path : path } ) ;
}
} ) ;
} ,
allBlockDevices : ( { type } , { resolveProperty , resolveDataSource } ) => {
// FIXME: Figure out how to semantically represent that data cannot be stored directly onto an NVMe device (only onto a namespace), but *can* be directly stored on a *non-NVMe* device... usually, anyway.
return Promise . try ( ( ) => {
return resolveProperty ( "interface" ) ;
} ) . then ( ( interface _ ) => {
if ( interface _ === "nvme" ) {
// Dynamic data source lookup: nvme list-ns -> Drive
return Promise . try ( ( ) => {
return resolveDataSource ( "nvmeListNamespaces" , path ) ;
} ) . map ( ( namespaceId ) => {
return ` ${ path } n ${ namespaceId } ` ;
} ) ;
} else {
return [ path ] ;
}
} ) . map ( ( rootPath ) => {
return resolveDataSource ( "lsblk" , { path : rootPath } ) ;
} ) . then ( ( blockDeviceTrees ) => {
let blockDevices = treecutter . flatten ( blockDeviceTrees )
. map ( ( device ) => types . BlockDevice ( device ) ) ;
// MARKER: Find a way to reassemble this tree on the client side, for display
// MARKER: Why are most of the mounts (erroneously) empty?
if ( type != null ) {
return Promise . filter ( blockDevices , ( device ) => {
return Promise . try ( ( ) => {
return resolveProperty ( "type" , device . item ) ;
} ) . then ( ( deviceType ) => {
return ( deviceType === type ) ;
} ) ;
} ) ;
} else {
return blockDevices ;
}
} ) ;
}
} ,
lsblk : {
[ ID ] : { path } ,
// TODO: Implement [DependsOn], for cases where a source data mapper depends on data from more than one source, so it can reference properties defined elsewhere?
// FIXME: Figure out a nice way to make a source lookup conditional upon something else (like only do a `lsblk` if not an NVMe drive, and for NVMe drives return a hardcoded thing)
// allBlockDevices: function (rootDevice, { type }, context) {
// let devices = treecutter.flatten([rootDevice])
// .map((device) => types.BlockDevice({ name: device.name }));
// if (type != null) {
// return Promise.filter(devices, (device) => {
// return Promise.try(() => {
// return device.type({}, context);
// }).then((deviceType) => {
// return (deviceType === type);
// });
// });
// } else {
// return devices;
// }
// }
} ,
smartctlScan : {
[ ID ] : path ,
interface : "interface"
} ,
smartctlInfo : {
[ ID ] : path ,
model : "model" ,
modelFamily : "modelFamily" ,
smartAvailable : "smartAvailable" ,
smartEnabled : "smartEnabled" ,
serialNumber : "serialNumber" ,
wwn : "wwn" ,
firmwareVersion : "firmwareVersion" ,
size : "size" ,
rpm : "rpm" ,
logicalSectorSize : ( device ) => device . sectorSizes . logical ,
physicalSectorSize : ( device ) => device . sectorSizes . physical ,
formFactor : "formFactor" ,
ataVersion : "ataVersion" ,
sataVersion : "sataVersion"
} ,
smartctlAttributes : {
[ ID ] : path ,
smartAttributes : ( attributes ) => {
return attributes . map ( ( attribute ) => {
return Object . assign ( { } , attribute , {
type : upperSnakeCase ( attribute . type ) ,
updatedWhen : upperSnakeCase ( attribute . updatedWhen )
} ) ;
} ) ;
} ,
smartHealth : ( attributes ) => {
let failed = attributes . filter ( ( item ) => {
return ( item . failingNow === true || item . failedBefore === true ) ;
} ) ;
let deteriorating = attributes . filter ( ( item ) => {
return ( item . type === "preFail" && item . worstValueSeen < 100 ) ;
} ) ;
if ( failed . length > 0 ) {
return "FAILING" ;
} else if ( deteriorating . length > 0 ) {
return "DETERIORATING" ;
} else {
return "HEALTHY" ;
}
}
}
} ) ;
} ;
} ;