"use strict"; const Promise = require("bluebird"); const graphql = require("graphql"); const fs = require("fs"); const path = require("path"); const matchOrError = require("../match-or-error"); const upperSnakeCase = require("../upper-snake-case"); const All = require("../graphql/symbols/all"); const createGraphQLInterface = require("../graphql/index"); const {ID, LocalProperties, createDataObject} = require("../graphql/data-object"); const createLoaders = require("./loaders"); /* FIXME: This seems to be added into a global registry somehow? How to specify this explicitly on a query without relying on globals? */ new graphql.GraphQLScalarType({ name: "ByteSize", description: "A value that represents a value on a byte scale", serialize: (value) => { return JSON.stringify(value); }, parseValue: (value) => { return JSON.parse(value); }, parseLiteral: (value) => { return JSON.parse(value); }, }); new graphql.GraphQLScalarType({ name: "TimeSize", description: "A value that represents a value on a time scale", serialize: (value) => { return JSON.stringify(value); }, parseValue: (value) => { return JSON.parse(value); }, parseLiteral: (value) => { return JSON.parse(value); }, }); let schema = graphql.buildSchema(fs.readFileSync(path.resolve(__dirname, "../schemas/main.gql"), "utf8")); function createBlockDevice({ name, path }) { if (name != null) { path = `/dev/${name}`; } else if (path != null) { let match = matchOrError(/^\/dev\/(.+)$/, path); name = match[0]; } /* FIXME: parent */ return createDataObject({ [LocalProperties]: { path: path }, lsblk: { [ID]: name, name: "name", size: "size", mountpoint: "mountpoint", deviceNumber: "deviceNumber", removable: "removable", readOnly: "readOnly", children: (device) => { return device.children.map((child) => { return createBlockDevice({ name: child.name }); }); } } }); } function createPhysicalVolume({ path }) { return createDataObject({ [LocalProperties]: { path: path, blockDevice: () => { return createBlockDevice({ path: path }); } }, lvmPhysicalVolumes: { [ID]: path, volumeGroup: (volume) => { if (volume.volumeGroup != null) { return createVolumeGroup({ name: volume.volumeGroup }); } }, format: "format", size: "totalSpace", freeSpace: "freeSpace", duplicate: "isDuplicate", allocatable: "isAllocatable", used: "isUsed", exported: "isExported", missing: "isMissing" } }); } function createVolumeGroup({ name }) { return createDataObject({ [LocalProperties]: { name: name } }); } function createDrive({ path }) { return createDataObject({ [LocalProperties]: { path: path, blockDevice: () => { return createBlockDevice({ 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. */ }, 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"; } } } }); } module.exports = function () { return createGraphQLInterface(schema, { loaderFactory: createLoaders }, { hardware: { drives: function ({ paths }, { data }) { return Promise.try(() => { if (paths != null) { return data.smartctlScan.loadMany(paths); } else { return data.smartctlScan.load(All); } }).then((devices) => { return devices.map((device) => { return createDrive({ path: device.path }); }); }); } }, resources: { blockDevices: function ({ names }, { data }) { return Promise.try(() => { if (names != null) { return data.lsblk.loadMany(names); } else { return data.lsblk.load(All); } }).then((devices) => { return devices.map((device) => { return createBlockDevice({ name: device.name }); }); }); }, lvm: { physicalVolumes: function ({ paths }, { data }) { return Promise.try(() => { if (paths != null) { return data.lvmPhysicalVolumes.loadMany(paths); } else { return data.lvmPhysicalVolumes.load(All); } }).then((volumes) => { return volumes.map((volume) => { return createPhysicalVolume({ path: volume.path }); }); }); } } } }); };