Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: add flashsystem external storage #9602

Merged
Expand Up @@ -9,12 +9,6 @@
"Create new claim": "Create new claim",
"Create": "Create",
"Cancel": "Cancel",
"Enabled": "Enabled",
"Disabled": "Disabled",
"Last synced": "Last synced",
"BlockPool List": "BlockPool List",
"Edit BlockPool": "Edit BlockPool",
"Delete BlockPool": "Delete BlockPool",
"{{replica}} Replication": "{{replica}} Replication",
"Pool name": "Pool name",
"my-block-pool": "my-block-pool",
Expand Down Expand Up @@ -156,33 +150,17 @@
"Can be used on any platform having nodes with local devices. The infrastructure storage class is provided by Local Storage Operator on top of the local devices.": "Can be used on any platform having nodes with local devices. The infrastructure storage class is provided by Local Storage Operator on top of the local devices.",
"Connect a new external storage system": "Connect a new external storage system",
"Can be used to connect an external storage platform to OpenShift Data Foundation.": "Can be used to connect an external storage platform to OpenShift Data Foundation.",
"{{nodeCount, number}} node": "{{nodeCount, number}} node",
"{{nodeCount, number}} node_plural": "{{nodeCount, number}} nodes",
"selected ({{cpu}} CPU and {{memory}} on ": "selected ({{cpu}} CPU and {{memory}} on ",
"{{zoneCount, number}} zone": "{{zoneCount, number}} zone",
"{{zoneCount, number}} zone_plural": "{{zoneCount, number}} zones",
"Select Capacity": "Select Capacity",
"Requested Capacity": "Requested Capacity",
"Select Nodes": "Select Nodes",
"Select at least 3 nodes preferably in 3 different zones. It is recommended to start with at least 14 CPUs and 34 GiB per node.": "Select at least 3 nodes preferably in 3 different zones. It is recommended to start with at least 14 CPUs and 34 GiB per node.",
"Search by node name...": "Search by node name...",
"Search by node label...": "Search by node label...",
"Available Raw Capacity": "Available Raw Capacity",
"The available capacity is based on all attached disks associated with the selectedStorageClass <2>\"{{storageClassName}}\"</2>": "The available capacity is based on all attached disks associated with the selectedStorageClass <2>\"{{storageClassName}}\"</2>",
"Selected Nodes": "Selected Nodes",
"Role": "Role",
"CPU": "CPU",
"Memory": "Memory",
"Zone": "Zone",
"Selected nodes table": "Selected nodes table",
"Connection details": "Connection details",
"StorageClass name": "StorageClass name",
"Backing storage": "Backing storage",
"StorageClass:": "StorageClass:",
"Capacity and nodes": "Capacity and nodes",
"Requested Cluster Capacity:": "Requested Cluster Capacity:",
"Total CPU and memory of {{cpu, number}} CPU and {{memory}}": "Total CPU and memory of {{cpu, number}} CPU and {{memory}}",
"An error has occurred: {{error}}": "An error has occurred: {{error}}",
"Endpoint": "Endpoint",
"Rest API IP address of IBM Storage FlashSystem.": "Rest API IP address of IBM Storage FlashSystem.",
"The endpoint is not a valid URL": "The endpoint is not a valid URL",
"Username": "Username",
"Password": "Password",
"Hide Values": "Hide Values",
"Reveal Values": "Reveal Values",
"Poolname": "Poolname",
"External storage metadata": "External storage metadata",
"Download <1>{{SCRIPT_NAME}}</1> script and run on the RHCS cluster, then upload the results (JSON).": "Download <1>{{SCRIPT_NAME}}</1> script and run on the RHCS cluster, then upload the results (JSON).",
"Download script": "Download script",
Expand Down Expand Up @@ -384,12 +362,14 @@
"Attach OBC to a Deployment": "Attach OBC to a Deployment",
"Deployment Name": "Deployment Name",
"Attach": "Attach",
"Delete BlockPool": "Delete BlockPool",
"<0><0>{{poolName}}</0> cannot be deleted. When a pool is bounded to PVC it cannot be deleted. Please detach all the resources from StorageClass(es):</0>": "<0><0>{{poolName}}</0> cannot be deleted. When a pool is bounded to PVC it cannot be deleted. Please detach all the resources from StorageClass(es):</0>",
"<0>Deleting <1>{{poolName}}</1> will remove all the saved data of this pool. Are you sure want to delete?</0>": "<0>Deleting <1>{{poolName}}</1> will remove all the saved data of this pool. Are you sure want to delete?</0>",
"BlockPool Delete Modal": "BlockPool Delete Modal",
"Try Again": "Try Again",
"Finish": "Finish",
"Go To Pvc List": "Go To Pvc List",
"Edit BlockPool": "Edit BlockPool",
"BlockPool Update Form": "BlockPool Update Form",
"replacement disallowed: disk {{diskName}} is {{replacingDiskStatus}}": "replacement disallowed: disk {{diskName}} is {{replacingDiskStatus}}",
"replacement disallowed: disk {{diskName}} is {{replacementStatus}}": "replacement disallowed: disk {{diskName}} is {{replacementStatus}}",
Expand Down Expand Up @@ -430,7 +410,6 @@
"Account key": "Account key",
"Secret key": "Secret key",
"Region Dropdown": "Region Dropdown",
"Endpoint": "Endpoint",
"Endpoint Address": "Endpoint Address",
"Secret": "Secret",
"Switch to Credentials": "Switch to Credentials",
Expand All @@ -450,8 +429,6 @@
"Namespace": "Namespace",
"OBCTableHeader": "OBCTableHeader",
"Object Bucket Claim Data": "Object Bucket Claim Data",
"Hide Values": "Hide Values",
"Reveal Values": "Reveal Values",
"Data": "Data",
"Create Object Bucket": "Create Object Bucket",
"Object Bucket Name": "Object Bucket Name",
Expand All @@ -478,23 +455,34 @@
"{{disks, number}} Disk_plural": "{{disks, number}} Disks",
"Selected versus Available Capacity": "Selected versus Available Capacity",
"Out of {{capacity}}": "Out of {{capacity}}",
"Selected Nodes": "Selected Nodes",
"Review StorageCluster": "Review StorageCluster",
"Storage and nodes": "Storage and nodes",
"Arbiter zone:": "Arbiter zone:",
"None": "None",
"{{nodeCount, number}} node": "{{nodeCount, number}} node",
"{{nodeCount, number}} node_plural": "{{nodeCount, number}} nodes",
"selected based on the created StorageClass:": "selected based on the created StorageClass:",
"Total CPU and memory of {{cpu, number}} CPU and {{memory}}": "Total CPU and memory of {{cpu, number}} CPU and {{memory}}",
"{{zoneCount, number}} zone": "{{zoneCount, number}} zone",
"{{zoneCount, number}} zone_plural": "{{zoneCount, number}} zones",
"Configure": "Configure",
"Enable Encryption": "Enable Encryption",
"Connect to external key management service: {{name}}": "Connect to external key management service: {{name}}",
"Encryption Level: {{level}}": "Encryption Level: {{level}}",
"Using {{networkLabel}}": "Using {{networkLabel}}",
"Discover disks": "Discover disks",
"Capacity and nodes": "Capacity and nodes",
"Security and network": "Security and network",
"Review and create": "Review and create",
"Internal - Attached devices": "Internal - Attached devices",
"Can be used on any platform where there are attached devices to the nodes, using the Local Storage Operator. The infrastructure StorageClass is provided by Local Storage Operator, on top of the attached drives.": "Can be used on any platform where there are attached devices to the nodes, using the Local Storage Operator. The infrastructure StorageClass is provided by Local Storage Operator, on top of the attached drives.",
"Local Storage Operator not installed": "Local Storage Operator not installed",
"Before we can create a StorageCluster, the Local Storage operator needs to be installed. When installation is finished come back to OpenShift Container Storage to create a StorageCluster.<1><0>Install</0></1>": "Before we can create a StorageCluster, the Local Storage operator needs to be installed. When installation is finished come back to OpenShift Container Storage to create a StorageCluster.<1><0>Install</0></1>",
"Role": "Role",
"CPU": "CPU",
"Memory": "Memory",
"Zone": "Zone",
"Node Table": "Node Table",
"StorageCluster exists": "StorageCluster exists",
"Back to operator page": "Back to operator page",
Expand All @@ -514,8 +502,9 @@
"Create StorageCluster": "Create StorageCluster",
"OpenShift Container Storage runs as a cloud-native service for optimal integration with applications in need of storage and handles the scenes such as provisioning and management.": "OpenShift Container Storage runs as a cloud-native service for optimal integration with applications in need of storage and handles the scenes such as provisioning and management.",
"Select mode:": "Select mode:",
"If not labeled, the selected nodes are labeled <1>{{label}}</1> to make them target hosts for OpenShift Container Storage's components.": "If not labeled, the selected nodes are labeled <1>{{label}}</1> to make them target hosts for OpenShift Container Storage's components.",
"Enable arbiter": "Enable arbiter",
"If not labeled, the selected nodes are labeled <1>{{label}}</1> to make them target hosts for OpenShift Container Storage's components.": "If not labeled, the selected nodes are labeled <1>{{label}}</1> to make them target hosts for OpenShift Container Storage's components.",
"selected ({{cpu}} CPU and {{memory}} on ": "selected ({{cpu}} CPU and {{memory}} on ",
"Mark nodes as dedicated": "Mark nodes as dedicated",
"This will taint the nodes with the<1>key: node.ocs.openshift.io/storage</1>, <3>value: true</3>, and <6>effect: NoSchedule</6>": "This will taint the nodes with the<1>key: node.ocs.openshift.io/storage</1>, <3>value: true</3>, and <6>effect: NoSchedule</6>",
"Selected nodes will be dedicated to OpenShift Container Storage use only": "Selected nodes will be dedicated to OpenShift Container Storage use only",
Expand All @@ -541,6 +530,14 @@
"Public Network Interface": "Public Network Interface",
"Select a network": "Select a network",
"Cluster Network Interface": "Cluster Network Interface",
"Requested Cluster Capacity:": "Requested Cluster Capacity:",
"StorageClass:": "StorageClass:",
"Select Capacity": "Select Capacity",
"Requested Capacity": "Requested Capacity",
"Select Nodes": "Select Nodes",
"Select at least 3 nodes preferably in 3 different zones. It is recommended to start with at least 14 CPUs and 34 GiB per node.": "Select at least 3 nodes preferably in 3 different zones. It is recommended to start with at least 14 CPUs and 34 GiB per node.",
"Search by node name...": "Search by node name...",
"Search by node label...": "Search by node label...",
"create internal mode StorageCluster wizard": "create internal mode StorageCluster wizard",
"Can be used on any platform, except bare metal. It means that OpenShift Container Storage uses an infrastructure StorageClass, provided by the hosting platform. For example, gp2 on AWS, thin on VMWare, etc.": "Can be used on any platform, except bare metal. It means that OpenShift Container Storage uses an infrastructure StorageClass, provided by the hosting platform. For example, gp2 on AWS, thin on VMWare, etc.",
"{{title}} steps": "{{title}} steps",
Expand Down Expand Up @@ -585,6 +582,7 @@
"The namespace bucket will serve reads from several selected backing stores, creating a virtual namespace on top of them and will write to one of those as its chosen write target": "The namespace bucket will serve reads from several selected backing stores, creating a virtual namespace on top of them and will write to one of those as its chosen write target",
"Cache NamespaceStore": "Cache NamespaceStore",
"The caching bucket will serve data from a large raw data out of a local caching tiering.": "The caching bucket will serve data from a large raw data out of a local caching tiering.",
"Backing storage": "Backing storage",
"Create storage class": "Create storage class",
"Create local volume set": "Create local volume set",
"Logical used capacity per account": "Logical used capacity per account",
Expand Down Expand Up @@ -644,6 +642,5 @@
"2 TiB": "2 TiB",
"LargeScale": "LargeScale",
"4 TiB": "4 TiB",
"{{osdSize, number}} TiB": "{{osdSize, number}} TiB",
"Help": "Help"
"{{osdSize, number}} TiB": "{{osdSize, number}} TiB"
}
@@ -0,0 +1,173 @@
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import {
Form,
FormGroup,
TextInput,
Button,
ValidatedOptions,
} from '@patternfly/react-core';
import { EyeSlashIcon, EyeIcon } from '@patternfly/react-icons';
import {
SecretKind,
apiVersionForModel,
} from '@console/internal/module/k8s';
import {
SecretModel
} from '@console/internal/models';
import { isValidUrl } from '@console/shared';
import { CreatePayload, ExternalComponentProps, CanGoToNextStep } from '../types';
import { FlashsystemState, IBMFlashsystemKind } from './type';
import { IBMFlashsystemModel } from './models';

export const FlashsystemConnectionDetails: React.FC<ExternalComponentProps<FlashsystemState>> = ({
setFormState,
formState,
}) => {
const { t } = useTranslation();
const [reveal, setReveal] = React.useState(false);
const [endpointValid, setEndpointValid] = React.useState(ValidatedOptions.default);
const onChange = (value: string) => {
shdn-ibm marked this conversation as resolved.
Show resolved Hide resolved
setFormState( 'endpoint', value );
if (value ){
if (isValidUrl(value )){
setEndpointValid(ValidatedOptions.success );
} else {
setEndpointValid(ValidatedOptions.error );
}
};
};
shdn-ibm marked this conversation as resolved.
Show resolved Hide resolved

return (
<Form>
<FormGroup
label={t('ceph-storage-plugin~Endpoint')}
fieldId="endpoint-input"
isRequired
validated = {endpointValid}
helperText={
t('ceph-storage-plugin~Rest API IP address of IBM Storage FlashSystem.')
}
helperTextInvalid={t('ceph-storage-plugin~The endpoint is not a valid URL')}
>
<TextInput
id="endpoint-input"
value={formState.endpoint}
type="text"
onChange={onChange}
isRequired
/>
</FormGroup>
<FormGroup label={t('ceph-storage-plugin~Username')} isRequired fieldId="username-input">
<TextInput
id="username-input"
value={formState.username}
type="text"
onChange={(value: string) => setFormState('username', value)}
isRequired
/>
</FormGroup>
<FormGroup label={t('ceph-storage-plugin~Password')} isRequired fieldId="password-input" >
<TextInput
id="password-input"
value={formState.password}
type={reveal ? 'text' : 'password'}
onChange={(value: string) => setFormState('password', value)}
isRequired
/>
<Button
type="button"
onClick={() => setReveal(!reveal)}
variant="link"
className="pf-m-link--align-right"
>
{reveal ? (
<>
<EyeSlashIcon className="co-icon-space-r" />
{t('ceph-storage-plugin~Hide Values')}
</>
) : (
<>
<EyeIcon className="co-icon-space-r" />
{t('ceph-storage-plugin~Reveal Values')}
</>
)}
</Button>
</FormGroup>
<FormGroup label={t('ceph-storage-plugin~Poolname')} isRequired fieldId="poolname-input">
<TextInput
id="poolname-input"
value={formState.poolname}
type="text"
onChange={(value: string) => setFormState('poolname', value)}
isRequired
/>
</FormGroup>
</Form>
);
};

export const FlashsystemPayload: CreatePayload<FlashsystemState> = (systemName, form, model, storageClassName) => {
const namespace = 'openshift-storage';
const defaultFilesystem = 'ext4';
const defaultVolumeMode = 'thick';
const defaultVolumePrefix = 'odf';

const IBMFlashsystemTemplate: IBMFlashsystemKind = {
shdn-ibm marked this conversation as resolved.
Show resolved Hide resolved
apiVersion: apiVersionForModel(IBMFlashsystemModel),
kind: IBMFlashsystemModel.kind,
shdn-ibm marked this conversation as resolved.
Show resolved Hide resolved
metadata: {
name: systemName,
namespace: namespace,
},
spec: {
name: systemName,
insecureSkipVerify: true,
secret:{
name: systemName,
namespace: namespace,
},
defaultPool:{
poolName: form.poolname,
storageclassName: storageClassName,
spaceEfficiency: defaultVolumeMode,
fsType: defaultFilesystem,
volumeNamePrefix:defaultVolumePrefix,
}
},
};
const flashsystemPayload = {
model: model,
payload: IBMFlashsystemTemplate,
shdn-ibm marked this conversation as resolved.
Show resolved Hide resolved
};

const storageSecretTemplate: SecretKind = {
apiVersion: apiVersionForModel(SecretModel),
stringData:{
management_address: form.endpoint,
password: form.password,
username: form.username,
},
kind: 'Secret',
metadata:{
name: systemName,
namespace: namespace,
},
type: 'Opaque',
};
const { apiVersion, apiGroup, kind, plural } = SecretModel;
const secretPayload = {
model: {
apiGroup,
apiVersion,
kind,
plural,
},
payload: storageSecretTemplate,
};

return [ secretPayload, flashsystemPayload ];
};

export const FlashsystemCanGoToNextStep: CanGoToNextStep<FlashsystemState> = (state) =>
shdn-ibm marked this conversation as resolved.
Show resolved Hide resolved
!!state.endpoint && !!state.username && !!state.password && !!state.poolname;
@@ -0,0 +1,13 @@
import { K8sKind } from '@console/internal/module/k8s';

export const IBMFlashsystemModel: K8sKind = {
shdn-ibm marked this conversation as resolved.
Show resolved Hide resolved
label: 'IBM Flash System',
labelPlural: 'IBM Flash Systems',
apiVersion: 'v1alpha1',
apiGroup: 'odf.ibm.com',
plural: 'flashsystemclusters',
abbr: 'FS',
namespaced: true,
kind: 'FlashSystemCluster',
crd: true,
};
@@ -0,0 +1,42 @@
import {
K8sResourceCommon,
} from '@console/internal/module/k8s';

export type IBMFlashsystemStatus = {
shdn-ibm marked this conversation as resolved.
Show resolved Hide resolved
capacity?: {
maxCapacity: string;
usedCapacity: string;
};
id?: string;
state?: string;
phase?: string;
version?: string;
};

export type IBMFlashsystemSpec = {
shdn-ibm marked this conversation as resolved.
Show resolved Hide resolved
name?: string;
defaultPool?:{
fsType: string;
poolName: string;
spaceEfficiency: string;
storageclassName: string;
volumeNamePrefix: string;
}
insecureSkipVerify: boolean;
secret?:{
name?: string;
namespace?: string;
};
};

export type IBMFlashsystemKind = {
shdn-ibm marked this conversation as resolved.
Show resolved Hide resolved
spec: IBMFlashsystemSpec;
status?: IBMFlashsystemStatus;
} & K8sResourceCommon;

export type FlashsystemState = {
shdn-ibm marked this conversation as resolved.
Show resolved Hide resolved
username: string;
password: string;
endpoint: string;
poolname: string;
};