Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions libs/ui-lib/lib/common/api/assisted-service/HostsAPI.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
Cluster,
Host,
HostUpdateParams,
InfraEnv,
Expand Down Expand Up @@ -62,6 +63,18 @@ const HostsAPI = {
`${HostsAPI.makeActionsBaseURI(infraEnvId, hostId)}/install`,
);
},
bindHost(infraEnvId: InfraEnv['id'], hostId: Host['id'], clusterId: Cluster['id']) {
return client.post<Host, AxiosResponse<Host>, { cluster_id: string }>(
`${HostsAPI.makeActionsBaseURI(infraEnvId, hostId)}/bind`,
{ cluster_id: clusterId },
{
headers: {
accept: 'application/json',
'Content-Type': 'application/json',
},
},
);
},
};

export default HostsAPI;
6 changes: 6 additions & 0 deletions libs/ui-lib/lib/common/api/assisted-service/InfraEnvsAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
InfraEnvCreateParams,
PresignedUrl,
InfraEnvUpdateParams,
Host,
} from '@openshift-assisted/types/assisted-installer-service';
import { AxiosResponse } from 'axios';

Expand Down Expand Up @@ -67,6 +68,11 @@ const InfraEnvsAPI = {
`${InfraEnvsAPI.makeBaseURI(infraEnvId)}/downloads/files-presigned?file_name=ipxe-script`,
);
},
getHosts(infraEnvId: InfraEnv['id']) {
return client.get<Host[]>(`${InfraEnvsAPI.makeBaseURI(infraEnvId)}/hosts`, {
signal: _getRequestAbortController.signal,
});
},
};

export default InfraEnvsAPI;
3 changes: 2 additions & 1 deletion libs/ui-lib/lib/common/reducers/alertsSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type AlertPayload = {
title: string;
message?: string;
variant?: AlertVariant;
key?: string;
};

export type AlertProps = {
Expand All @@ -22,7 +23,7 @@ export const alertsSlice = createSlice({
name: 'alerts',
reducers: {
addAlert: (state, action: PayloadAction<AlertPayload>) => [
{ key: uuidv4(), variant: AlertVariant.danger, ...action.payload },
{ key: action.payload.key || uuidv4(), variant: AlertVariant.danger, ...action.payload },
...state,
],
removeAlert: (state, action: PayloadAction<string>) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,22 @@ import {
Cluster,
V2ClusterUpdateParams,
} from '@openshift-assisted/types/assisted-installer-service';
import useLateBinding from '../../hooks/useLateBinding';

const HostDiscoveryForm = ({ cluster }: { cluster: Cluster }) => {
const { alerts } = useAlerts();
const { errors, touched, isSubmitting, isValid } = useFormikContext<HostDiscoveryValues>();
const clusterWizardContext = useClusterWizardContext();
const isAutoSaveRunning = useFormikAutoSave();
const errorFields = getFormikErrorFields(errors, touched);
const isBinding = useLateBinding(cluster);

const isNextDisabled =
!isValid ||
!!alerts.length ||
isAutoSaveRunning ||
isSubmitting ||
isBinding ||
!canNextHostDiscovery({ cluster });

const footer = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const HostsDiscoveryTable = ({ cluster }: HostsDiscoveryTableProps) => {
onMassDeleteHost,
...modalProps
} = useHostsTable(cluster);

const { wizardPerPage, setWizardPerPage } = useClusterWizardContext();

const { isViewerMode } = useSelector(selectCurrentClusterPermissionsState);
Expand Down
1 change: 1 addition & 0 deletions libs/ui-lib/lib/ocm/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export { default as usePullSecret } from './usePullSecret';
export { default as useClusterPreflightRequirements } from './useClusterPreflightRequirements';
export { default as useUISettings } from './useUISettings';
export { default as useInfraEnv } from './useInfraEnv';
export { default as useInfraEnvHosts } from './useInfraEnvHosts';
export { useFeatureDetection } from './use-feature-detection';
60 changes: 60 additions & 0 deletions libs/ui-lib/lib/ocm/hooks/useInfraEnvHosts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react';
import useInfraEnvId from './useInfraEnvId';
import { CpuArchitecture, DEFAULT_POLLING_INTERVAL } from '../../common';
import { getErrorMessage } from '../../common/utils';
import { InfraEnvsAPI } from '../services/apis';
import InfraEnvIdsCacheService from '../services/InfraEnvIdsCacheService';
import { Cluster, Host } from '@openshift-assisted/types/assisted-installer-service';

const useInfraEnvHosts = (
clusterId: Cluster['id'],
cpuArchitecture: CpuArchitecture,
clusterName?: string,
pullSecret?: string,
openshiftVersion?: string,
) => {
const [hosts, setHosts] = React.useState<Host[]>();
const [error, setError] = React.useState('');
const { infraEnvId, error: infraEnvIdError } = useInfraEnvId(
clusterId,
cpuArchitecture,
clusterName,
pullSecret,
openshiftVersion,
);

const getHosts = React.useCallback(async () => {
try {
if (infraEnvId) {
const { data: hostsData } = await InfraEnvsAPI.getHosts(infraEnvId);
setHosts(hostsData);
}
} catch (e) {
// Invalidate this cluster's cached data
InfraEnvIdsCacheService.removeInfraEnvId(clusterId, cpuArchitecture);
setError(getErrorMessage(e));
}
}, [clusterId, cpuArchitecture, infraEnvId]);

React.useEffect(() => {
if (infraEnvIdError) {
setHosts(undefined);
setError(infraEnvIdError);
} else {
if (infraEnvId) {
// Initial fetch
void getHosts();

// Set up polling to refetch hosts periodically
const intervalId = setInterval(() => {
void getHosts();
}, DEFAULT_POLLING_INTERVAL);

return () => clearInterval(intervalId);
}
}
}, [getHosts, infraEnvId, infraEnvIdError]);

return { hosts, error, isLoading: !hosts && !error };
};
export default useInfraEnvHosts;
71 changes: 71 additions & 0 deletions libs/ui-lib/lib/ocm/hooks/useLateBinding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { useCallback, useEffect, useState } from 'react';
import { Cluster } from '@openshift-assisted/types/assisted-installer-service';
import { CpuArchitecture, useAlerts } from '../../common';
import useInfraEnvHosts from './useInfraEnvHosts';
import HostsService from '../services/HostsService';
import { getErrorMessage } from '../../common/utils';
import { useFeature } from './use-feature';

const useLateBinding = (cluster: Cluster): boolean => {
const [isBinding, setIsBinding] = useState(false);
const { addAlert, removeAlert, alerts } = useAlerts();
const isSingleClusterFeatureEnabled = useFeature('ASSISTED_INSTALLER_SINGLE_CLUSTER_FEATURE');

const {
hosts: infraEnvHosts,
error: infraEnvError,
isLoading: infraEnvLoading,
} = useInfraEnvHosts(
isSingleClusterFeatureEnabled ? cluster.id : '',
cluster.cpuArchitecture
? (cluster.cpuArchitecture as CpuArchitecture)
: CpuArchitecture.USE_DAY1_ARCHITECTURE,
cluster.name,
undefined,
cluster.openshiftVersion,
);

const bindHosts = useCallback(async () => {
if (infraEnvHosts && !infraEnvError && !infraEnvLoading && isSingleClusterFeatureEnabled) {
for (const host of infraEnvHosts) {
if (host.clusterId !== cluster.id) {
setIsBinding(true);
try {
const alertKey = alerts.find((alert) => alert.key === host.id)?.key;
if (alertKey) {
removeAlert(alertKey);
}
await HostsService.bind(host, cluster.id);
} catch (error) {
addAlert({
title: `Failed to bind host ${host.requestedHostname || ''}`,
message: getErrorMessage(error),
key: host.id,
});
} finally {
setIsBinding(false);
}
}
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
infraEnvHosts,
infraEnvError,
infraEnvLoading,
isSingleClusterFeatureEnabled,
cluster.id,
addAlert,
removeAlert,
]);

useEffect(() => {
if (isSingleClusterFeatureEnabled) {
void bindHosts();
}
}, [isSingleClusterFeatureEnabled, bindHosts]);

return infraEnvLoading || isBinding;
};

export default useLateBinding;
9 changes: 9 additions & 0 deletions libs/ui-lib/lib/ocm/services/HostsService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
Cluster,
Disk,
DiskRole,
Host,
Expand Down Expand Up @@ -84,6 +85,14 @@ const HostsService = {
return HostsAPI.installHost(host.infraEnvId, host.id);
},

bind(host: Host, clusterId: Cluster['id']) {
if (!host.infraEnvId) {
throw new Error(`Cannot bind host ${host.id}, missing infraEnvId`);
}

return HostsAPI.bindHost(host.infraEnvId, host.id, clusterId);
},

installAll(hosts: Host[]) {
const promises = [];
for (const host of hosts) {
Expand Down
Loading