From 9dda2e892ceaff799adf970791b63fbd9dc4910c Mon Sep 17 00:00:00 2001 From: Jiri Tomasek Date: Wed, 12 Jun 2019 14:49:17 +0200 Subject: [PATCH] Add Host Status to hosts listing * Introduces BaremetalHostStatus component which renders appropriate status component based on host status * Introduces utility functions to determine host status (optionally also from host's machine and node) --- .../src/components/host-status.tsx | 59 +++++++++++++++ .../metal3-plugin/src/components/host.tsx | 26 ++++--- .../src/components/machine-cell.tsx | 36 ++++----- .../packages/metal3-plugin/src/constants.ts | 74 +++++++++++++++++++ .../metal3-plugin/src/selectors/index.ts | 36 +++++---- .../metal3-plugin/src/utils/host-status.ts | 59 +++++++++++++++ 6 files changed, 245 insertions(+), 45 deletions(-) create mode 100644 frontend/packages/metal3-plugin/src/components/host-status.tsx create mode 100644 frontend/packages/metal3-plugin/src/constants.ts create mode 100644 frontend/packages/metal3-plugin/src/utils/host-status.ts diff --git a/frontend/packages/metal3-plugin/src/components/host-status.tsx b/frontend/packages/metal3-plugin/src/components/host-status.tsx new file mode 100644 index 00000000000..c4cebe967b8 --- /dev/null +++ b/frontend/packages/metal3-plugin/src/components/host-status.tsx @@ -0,0 +1,59 @@ +import * as React from 'react'; +import { Button } from 'patternfly-react'; + +import { K8sResourceKind } from '@console/internal/module/k8s'; +import { StatusIconAndText } from '@console/internal/components/utils/status-icon'; +import { RequireCreatePermission } from '@console/internal/components/utils'; +import { getHostStatus } from '../utils/host-status'; + +import { + HOST_STATUS_DISCOVERED, + HOST_PROGRESS_STATES, + HOST_ERROR_STATES, + HOST_SUCCESS_STATES, +} from '../constants'; +import { BaremetalHostModel } from '../models'; + +// TODO(jtomasek): Update this with onClick handler once add discovered host functionality +// is available +export const AddDiscoveredHostButton: React.FC<{ host: K8sResourceKind }> = ( + { host }, // eslint-disable-line @typescript-eslint/no-unused-vars +) => { + const { + metadata: { namespace }, + } = host; + + return ( + + + + ); +}; + +type BaremetalHostStatusProps = { + host: K8sResourceKind; + machine?: K8sResourceKind; + node?: K8sResourceKind; +}; + +const BaremetalHostStatus = ({ host }: BaremetalHostStatusProps) => { + const hostStatus = getHostStatus(host); + const { status, title } = hostStatus; + + switch (true) { + case status === HOST_STATUS_DISCOVERED: + return ; + case HOST_PROGRESS_STATES.includes(status): + return ; + case HOST_SUCCESS_STATES.includes(status): + return ; + case HOST_ERROR_STATES.includes(status): + return ; + default: + return ; + } +}; + +export default BaremetalHostStatus; diff --git a/frontend/packages/metal3-plugin/src/components/host.tsx b/frontend/packages/metal3-plugin/src/components/host.tsx index ee99dc115e9..1b1e3aa30fa 100644 --- a/frontend/packages/metal3-plugin/src/components/host.tsx +++ b/frontend/packages/metal3-plugin/src/components/host.tsx @@ -19,9 +19,10 @@ import { BaremetalHostModel } from '../models'; import { getHostBMCAddress, getHostMachine } from '../selectors'; import { BaremetalHostRole } from './host-role'; import MachineCell from './machine-cell'; +import BaremetalHostStatus from './host-status'; const tableColumnClasses = [ - classNames('col-lg-5', 'col-md-8', 'col-sm-12', 'col-xs-12'), + classNames('col-lg-3', 'col-md-4', 'col-sm-12', 'col-xs-12'), classNames('col-lg-2', 'col-md-4', 'col-sm-6', 'hidden-xs'), classNames('col-lg-3', 'col-md-4', 'hidden-sm', 'hidden-xs'), classNames('col-lg-2', 'hidden-md', 'hidden-sm', 'hidden-xs'), @@ -36,6 +37,13 @@ const HostsTableHeader = () => [ transforms: [sortable], props: { className: tableColumnClasses[0] }, }, + { + title: 'Status', + // TODO(jtomasek): enable this once it is possible to pass sort function + // sortFunc: getSimpleHostStatus, + // transforms: [sortable], + props: { className: tableColumnClasses[1] }, + }, { title: 'Machine', sortField: 'spec.machineRef.name', @@ -72,7 +80,7 @@ const HostsTableRow: React.FC = ({ obj: host, index, key, st const namespace = getNamespace(host); // const machineName = getHostMachineName(host); const address = getHostBMCAddress(host); - const { machine } = host; + const { machine, node } = host; // TODO(jtomasek): other resource references will be updated as a subsequent change // const machineResource = { @@ -102,9 +110,9 @@ const HostsTableRow: React.FC = ({ obj: host, index, key, st namespace={namespace} /> - {/* - - */} + + + @@ -120,13 +128,7 @@ const HostsTableRow: React.FC = ({ obj: host, index, key, st }; const HostList: React.FC> = (props) => ( - +
); // TODO(jtomasek): re-enable filters once the extension point for list.tsx is in place diff --git a/frontend/packages/metal3-plugin/src/components/machine-cell.tsx b/frontend/packages/metal3-plugin/src/components/machine-cell.tsx index 75cfd60883a..d0ab62cacdb 100644 --- a/frontend/packages/metal3-plugin/src/components/machine-cell.tsx +++ b/frontend/packages/metal3-plugin/src/components/machine-cell.tsx @@ -1,13 +1,17 @@ import * as React from 'react'; -// import { Link } from 'react-router-dom'; -// import { Icon } from 'patternfly-react'; +import { Link } from 'react-router-dom'; import { DASH } from '@console/shared'; import { MachineModel } from '@console/internal/models'; -import { ResourceLink /* , RequireCreatePermission */ } from '@console/internal/components/utils'; +import { + ResourceLink, + RequireCreatePermission, + StatusIconAndText, +} from '@console/internal/components/utils'; import { referenceForModel, K8sResourceKind } from '@console/internal/module/k8s'; import { getHostMachineName } from '../selectors'; +import { canHostAddMachine } from '../utils/host-status'; interface MachineCellProps { host: K8sResourceKind; @@ -30,21 +34,17 @@ const MachineCell: React.FC = ({ host }) => { /> ); } - // TODO(jtomasek): Re-enable this once host status is added - // if (canHostAddMachine(host)) { - // const ns = namespace || 'default'; - // const href = `/k8s/ns/${ns}/${referenceForModel(MachineModel)}/~new`; - // return ( - // - // - // - // - // Add machine - // - // - // - // ); - // } + if (canHostAddMachine(host)) { + const ns = namespace || 'default'; + const href = `/k8s/ns/${ns}/${referenceForModel(MachineModel)}/~new`; + return ( + + + + + + ); + } return <>{DASH}; }; diff --git a/frontend/packages/metal3-plugin/src/constants.ts b/frontend/packages/metal3-plugin/src/constants.ts new file mode 100644 index 00000000000..c500be2e2c7 --- /dev/null +++ b/frontend/packages/metal3-plugin/src/constants.ts @@ -0,0 +1,74 @@ +export const HOST_STATUS_READY = 'ready'; +export const HOST_STATUS_DISCOVERED = 'discovered'; +export const HOST_STATUS_OK = 'OK'; +export const HOST_STATUS_EXTERNALLY_PROVISIONED = 'externally provisioned'; +export const HOST_STATUS_PROVISIONED = 'provisioned'; +export const HOST_STATUS_DEPROVISIONED = 'deprovisioned'; +export const HOST_STATUS_REGISTERING = 'registering'; +export const HOST_STATUS_INSPECTING = 'inspecting'; +export const HOST_STATUS_PREPARING_TO_PROVISION = 'preparing to provision'; +export const HOST_STATUS_PROVISIONING = 'provisioning'; +export const HOST_STATUS_DEPROVISIONING = 'deprovisioning'; +export const HOST_STATUS_MAKING_HOST_AVAILABLE = 'making host available'; +export const HOST_STATUS_MATCH_PROFILE = 'match profile'; +export const HOST_STATUS_STARTING_MAINTENANCE = 'starting maintenance'; +export const HOST_STATUS_STOPPING_MAINTENANCE = 'stopping maintenance'; +export const HOST_STATUS_MAINTENANCE = 'maintenance'; +export const HOST_STATUS_VALIDATION_ERROR = 'validation error'; +export const HOST_STATUS_REGISTRATION_ERROR = 'registration error'; +export const HOST_STATUS_PROVISIONING_ERROR = 'provisioning error'; +export const HOST_STATUS_POWER_MANAGEMENT_ERROR = 'power management error'; + +export const HOST_STATUS_TITLES = { + [HOST_STATUS_READY]: 'Ready', + [HOST_STATUS_DISCOVERED]: 'Discovered', + [HOST_STATUS_OK]: 'OK', + [HOST_STATUS_PROVISIONED]: 'Provisioned', + [HOST_STATUS_EXTERNALLY_PROVISIONED]: 'Externally provisioned', + [HOST_STATUS_DEPROVISIONED]: 'Deprovisioned', + [HOST_STATUS_REGISTERING]: 'Registering', + [HOST_STATUS_INSPECTING]: 'Inspecting', + [HOST_STATUS_PREPARING_TO_PROVISION]: 'Preparing to provision', + [HOST_STATUS_PROVISIONING]: 'Provisioning', + [HOST_STATUS_DEPROVISIONING]: 'Deprovisioning', + [HOST_STATUS_MAKING_HOST_AVAILABLE]: 'Making host available', + [HOST_STATUS_VALIDATION_ERROR]: 'Validation Error(s)', + [HOST_STATUS_REGISTRATION_ERROR]: 'Registration Error', + [HOST_STATUS_PROVISIONING_ERROR]: 'Provisioning Error', + [HOST_STATUS_POWER_MANAGEMENT_ERROR]: 'Power Management Error', + [HOST_STATUS_STARTING_MAINTENANCE]: 'Starting maintenance', + [HOST_STATUS_STOPPING_MAINTENANCE]: 'Stopping maintenance', + [HOST_STATUS_MAINTENANCE]: 'Maintenance', + [HOST_STATUS_MATCH_PROFILE]: 'Matching profile', +}; + +export const HOST_ERROR_STATES = [ + HOST_STATUS_REGISTRATION_ERROR, + HOST_STATUS_PROVISIONING_ERROR, + HOST_STATUS_VALIDATION_ERROR, + HOST_STATUS_POWER_MANAGEMENT_ERROR, +]; + +export const HOST_WARN_STATES = []; + +export const HOST_PROGRESS_STATES = [ + HOST_STATUS_INSPECTING, + HOST_STATUS_PREPARING_TO_PROVISION, + HOST_STATUS_PREPARING_TO_PROVISION, + HOST_STATUS_PROVISIONING, + HOST_STATUS_DEPROVISIONING, + HOST_STATUS_MAKING_HOST_AVAILABLE, + HOST_STATUS_REGISTERING, + HOST_STATUS_STARTING_MAINTENANCE, + HOST_STATUS_STOPPING_MAINTENANCE, + HOST_STATUS_MATCH_PROFILE, +]; + +export const HOST_SUCCESS_STATES = [ + HOST_STATUS_READY, + HOST_STATUS_DISCOVERED, + HOST_STATUS_OK, + HOST_STATUS_PROVISIONED, + HOST_STATUS_EXTERNALLY_PROVISIONED, + HOST_STATUS_DEPROVISIONED, +]; diff --git a/frontend/packages/metal3-plugin/src/selectors/index.ts b/frontend/packages/metal3-plugin/src/selectors/index.ts index 275fbd56e1f..f6de8701870 100644 --- a/frontend/packages/metal3-plugin/src/selectors/index.ts +++ b/frontend/packages/metal3-plugin/src/selectors/index.ts @@ -3,19 +3,25 @@ import * as _ from 'lodash'; import { K8sResourceKind, MachineKind } from '@console/internal/module/k8s'; import { getName } from '@console/shared'; -export const getOperationalStatus = (host) => _.get(host, 'status.operationalStatus'); -export const getProvisioningState = (host) => _.get(host, 'status.provisioning.state'); -export const getHostMachineName = (host) => _.get(host, 'spec.machineRef.name'); -export const getHostBMCAddress = (host) => _.get(host, 'spec.bmc.address'); -export const isHostOnline = (host) => _.get(host, 'spec.online', false); -export const getHostNICs = (host) => _.get(host, 'status.hardware.nics', []); -export const getHostStorage = (host) => _.get(host, 'status.hardware.storage', []); -export const getHostCPU = (host) => _.get(host, 'status.hardware.cpu', {}); -export const getHostRAM = (host) => _.get(host, 'status.hardware.ramGiB'); -export const getHostErrorMessage = (host) => _.get(host, 'status.errorMessage'); -export const getHostDescription = (host) => _.get(host, 'spec.description', ''); -export const isHostPoweredOn = (host) => _.get(host, 'status.poweredOn', false); -export const getHostTotalStorageCapacity = (host) => - _.reduce(getHostStorage(host), (sum, disk) => sum + disk.sizeGiB, 0); +type BaremetalHostDisk = { + sizeGiB: number; +}; + +export const getHostOperationalStatus = (host: K8sResourceKind) => + _.get(host, 'status.operationalStatus'); +export const getHostProvisioningState = (host: K8sResourceKind) => + _.get(host, 'status.provisioning.state'); +export const getHostMachineName = (host: K8sResourceKind) => _.get(host, 'spec.machineRef.name'); +export const getHostBMCAddress = (host: K8sResourceKind) => _.get(host, 'spec.bmc.address'); +export const isHostOnline = (host: K8sResourceKind) => _.get(host, 'spec.online', false); +export const getHostNICs = (host: K8sResourceKind) => _.get(host, 'status.hardware.nics', []); +export const getHostStorage = (host: K8sResourceKind) => _.get(host, 'status.hardware.storage', []); +export const getHostCPU = (host: K8sResourceKind) => _.get(host, 'status.hardware.cpu', {}); +export const getHostRAM = (host: K8sResourceKind) => _.get(host, 'status.hardware.ramGiB'); +export const getHostErrorMessage = (host: K8sResourceKind) => _.get(host, 'status.errorMessage'); +export const getHostDescription = (host: K8sResourceKind) => _.get(host, 'spec.description', ''); +export const isHostPoweredOn = (host: K8sResourceKind) => _.get(host, 'status.poweredOn', false); +export const getHostTotalStorageCapacity = (host: K8sResourceKind) => + _.reduce(getHostStorage(host), (sum: number, disk: BaremetalHostDisk) => sum + disk.sizeGiB, 0); export const getHostMachine = (host: K8sResourceKind, machines: MachineKind[]) => - machines.find((machine) => getHostMachineName(host) === getName(machine)); + machines.find((machine: MachineKind) => getHostMachineName(host) === getName(machine)); diff --git a/frontend/packages/metal3-plugin/src/utils/host-status.ts b/frontend/packages/metal3-plugin/src/utils/host-status.ts new file mode 100644 index 00000000000..4dbf298aa45 --- /dev/null +++ b/frontend/packages/metal3-plugin/src/utils/host-status.ts @@ -0,0 +1,59 @@ +import { K8sResourceKind } from '@console/internal/module/k8s'; + +import { + getHostOperationalStatus, + getHostProvisioningState, + getHostErrorMessage, + // isNodeUnschedulable, +} from '../selectors'; + +import { + HOST_STATUS_TITLES, + HOST_STATUS_READY, + // HOST_STATUS_STARTING_MAINTENANCE, +} from '../constants'; + +// import { NOT_HANDLED } from '../common'; + +// const isStartingMaintenance = (node) => { +// if (isNodeUnschedulable(node)) { +// return { +// status: HOST_STATUS_STARTING_MAINTENANCE, +// text: HOST_STATUS_TITLES[HOST_STATUS_STARTING_MAINTENANCE], +// }; +// } +// return NOT_HANDLED; +// }; + +const getBaremetalHostStatus = (host: K8sResourceKind) => { + const operationalStatus = getHostOperationalStatus(host); + const provisioningState = getHostProvisioningState(host); + + const hostStatus = provisioningState || operationalStatus || undefined; + return { + status: hostStatus, + title: HOST_STATUS_TITLES[hostStatus] || hostStatus, + errorMessage: getHostErrorMessage(host), + }; +}; + +export const getHostStatus = ( + host: K8sResourceKind, + // machine?: MachineKind, + // node?: NodeKind, +) => { + // TODO(jtomasek): make this more robust by including node/machine status + // return isStartingMaintenance(node) || getBaremetalHostStatus(host); + return getBaremetalHostStatus(host); +}; + +export const getSimpleHostStatus = ( + host: K8sResourceKind, + // machine?: MachineKind, + // node?: NodeKind, +): string => getHostStatus(host).status; + +export const canHostAddMachine = (host: K8sResourceKind): boolean => + [HOST_STATUS_READY].includes(getSimpleHostStatus(host)); +// export const canHostStartMaintenance = (hostNode: NodeKind) => hostNode && !isNodeUnschedulable(hostNode); +// export const canHostStopMaintenance = (hostNode: NodeKind) => isNodeUnschedulable(hostNode);