WIP: Move SMART health logic to GraphQL API, and further complete SMART support in API

feature/node-rewrite
Sven Slootweg 5 years ago
parent e9619f387a
commit 85c7c9a803

@ -44,6 +44,7 @@
"pug": "^2.0.0-beta6", "pug": "^2.0.0-beta6",
"rfr": "^1.2.3", "rfr": "^1.2.3",
"scrypt-for-humans": "^2.0.5", "scrypt-for-humans": "^2.0.5",
"snake-case": "^2.1.0",
"split": "^1.0.0", "split": "^1.0.0",
"sse-channel": "^3.1.1", "sse-channel": "^3.1.1",
"through2": "^2.0.1", "through2": "^2.0.1",

@ -12,6 +12,7 @@ const matchOrError = require("./match-or-error");
const lsblk = require("./wrappers/lsblk"); const lsblk = require("./wrappers/lsblk");
const smartctl = require("./wrappers/smartctl"); const smartctl = require("./wrappers/smartctl");
const lvm = require("./wrappers/lvm"); const lvm = require("./wrappers/lvm");
const upperSnakeCase = require("./upper-snake-case");
function gql(strings) { function gql(strings) {
return strings.join(""); return strings.join("");
@ -312,6 +313,34 @@ function createDrive({ path }) {
formFactor: "formFactor", formFactor: "formFactor",
ataVersion: "ataVersion", ataVersion: "ataVersion",
sataVersion: "sataVersion" 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";
}
}
} }
}); });
} }
@ -402,39 +431,52 @@ return Promise.try(() => {
let query = gql` let query = gql`
# query SomeDrives($drivePaths: [String]) { # query SomeDrives($drivePaths: [String]) {
query SomeDrives { query SomeDrives {
# hardware { hardware {
# drives(paths: $drivePaths) { drives {
# path path
# interface interface
# model model
# modelFamily modelFamily
# smartAvailable smartAvailable
# smartEnabled smartEnabled
# serialNumber serialNumber
# wwn wwn
# firmwareVersion firmwareVersion
# size size
# rpm rpm
# logicalSectorSize logicalSectorSize
# physicalSectorSize physicalSectorSize
# formFactor formFactor
# ataVersion ataVersion
# sataVersion sataVersion
# blockDevice { smartHealth
# removable # smartAttributes {
# name
# children { # type
# name # value
# mountpoint # failingNow
# size
# } # flags {
# } # affectsPerformance
# } # indicatesFailure
# } # }
# }
# blockDevice {
# removable
# children {
# name
# mountpoint
# size
# }
# }
}
}
resources { # resources {
# blockDevices { # blockDevices {
# name # name
# mountpoint # mountpoint
@ -455,30 +497,30 @@ return Promise.try(() => {
# } # }
# } # }
lvm { # lvm {
physicalVolumes { # physicalVolumes {
path # path
blockDevice { # blockDevice {
name # name
deviceNumber # deviceNumber
} # }
volumeGroup { # volumeGroup {
name # name
} # }
format # format
size # size
freeSpace # freeSpace
duplicate # duplicate
allocatable # allocatable
used # used
exported # exported
missing # missing
} # }
} # }
} # }
} }
`; `;

@ -7,29 +7,11 @@ const smartctl = require("../wrappers/smartctl");
const lvm = require("../wrappers/lvm"); const lvm = require("../wrappers/lvm");
const {B} = require("../units/bytes/iec"); const {B} = require("../units/bytes/iec");
/* FIXME: Move this to GraphQL API */
function getSmartStatus(smartData) {
let failed = smartData.filter((item) => {
return (item.failingNow === true || item.failedBefore === true);
});
let deteriorating = smartData.filter((item) => {
return (item.type === "preFail" && item.worstValueSeen < 100);
});
if (failed.length > 0) {
return "failed";
} else if (deteriorating.length > 0) {
return "deteriorating";
} else {
return "healthy";
}
}
function getStorageDevices() { function getStorageDevices() {
return Promise.try(() => { return Promise.try(() => {
return lsblk(); return lsblk();
}).filter((device) => { }).filter((device) => {
/* FIXME: Move device type filter to GraphQL? */
return (device.type === "disk"); return (device.type === "disk");
}).map((device) => { }).map((device) => {
return Object.assign({}, device, { return Object.assign({}, device, {

@ -252,10 +252,34 @@ type SmartAttributeFlags {
indicatesFailure: Boolean! indicatesFailure: Boolean!
} }
enum SmartAttributeType {
PRE_FAIL
OLD_AGE
}
enum SmartAttributeUpdateType {
ALWAYS
OFFLINE
}
enum SmartHealth {
HEALTHY
DETERIORATING
FAILING
}
type SmartAttribute { type SmartAttribute {
id: Int! id: Int!
name: String! name: String!
flags: SmartAttributeFlags flags: SmartAttributeFlags!
value: Int!
rawValue: String!
worstValueSeen: Int!
failureThreshold: Int!
type: SmartAttributeType!
failingNow: Boolean!
failedBefore: Boolean!
updatedWhen: SmartAttributeUpdateType!
} }
type BlockDevice { type BlockDevice {
@ -276,6 +300,8 @@ type PhysicalDrive {
blockDevice: BlockDevice! blockDevice: BlockDevice!
smartAvailable: Boolean! smartAvailable: Boolean!
smartEnabled: Boolean smartEnabled: Boolean
smartHealth: SmartHealth
smartAttributes: [SmartAttribute!]!
model: String model: String
modelFamily: String modelFamily: String
serialNumber: String serialNumber: String
@ -288,7 +314,6 @@ type PhysicalDrive {
formFactor: String formFactor: String
ataVersion: String ataVersion: String
sataVersion: String sataVersion: String
smartAttributes: [SmartAttribute!]!
} }
type LVMPhysicalVolume { type LVMPhysicalVolume {

@ -0,0 +1,7 @@
"use strict";
const snakeCase = require("snake-case");
module.exports = function upperSnakeCase(value) {
return snakeCase(value).toUpperCase();
};

@ -53,6 +53,7 @@ module.exports = {
"Old_age": "oldAge" "Old_age": "oldAge"
}), }),
failingNow: (failedWhen === "FAILING_NOW"), failingNow: (failedWhen === "FAILING_NOW"),
/* TODO: Should the below include the FAILING_NOW state? */
failedBefore: (failedWhen === "In_the_past"), failedBefore: (failedWhen === "In_the_past"),
updatedWhen: mapValue(updatedWhen, { updatedWhen: mapValue(updatedWhen, {
"Always": "always", "Always": "always",

@ -6193,6 +6193,11 @@ loud-rejection@^1.0.0:
currently-unhandled "^0.4.1" currently-unhandled "^0.4.1"
signal-exit "^3.0.0" signal-exit "^3.0.0"
lower-case@^1.1.1:
version "1.1.4"
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw=
lowercase-keys@^1.0.0: lowercase-keys@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
@ -6624,6 +6629,13 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
no-case@^2.2.0:
version "2.3.2"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac"
integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==
dependencies:
lower-case "^1.1.1"
node-gyp@^3.3.1: node-gyp@^3.3.1:
version "3.8.0" version "3.8.0"
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c"
@ -8671,6 +8683,13 @@ slice-ansi@^2.1.0:
astral-regex "^1.0.0" astral-regex "^1.0.0"
is-fullwidth-code-point "^2.0.0" is-fullwidth-code-point "^2.0.0"
snake-case@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-2.1.0.tgz#41bdb1b73f30ec66a04d4e2cad1b76387d4d6d9f"
integrity sha1-Qb2xtz8w7GagTU4srRt2OH1NbZ8=
dependencies:
no-case "^2.2.0"
snapdragon-node@^2.0.1: snapdragon-node@^2.0.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"

Loading…
Cancel
Save