Просмотр исходного кода

Disable disk mapping for OCI's root disk

An instance info parsing plugin was developed to handle OCI's special
case where the first disk returned in the instance info is the root
disk, which can't be mapped.
Sergiu Miclea 6 лет назад
Родитель
Сommit
bb12bd8ee8

+ 43 - 25
src/components/organisms/WizardStorage/WizardStorage.jsx

@@ -118,6 +118,11 @@ const NoStorageSubtitle = styled.div`
   color: ${Palette.grayscale[4]};
   text-align: center;
 `
+const DiskDisabledMessage = styled.div`
+  width: 224px;
+  text-align: center;
+  color: gray;
+`
 
 export const getDisks = (instancesDetails: Instance[], type: 'backend' | 'disk', storageMap?: ?StorageMap[]): Disk[] => {
   let fieldName = type === 'backend' ? 'storage_backend_identifier' : 'id'
@@ -162,6 +167,42 @@ class WizardStorage extends React.Component<Props> {
     )
   }
 
+  renderStorageDropdown(
+    storageItems: Array<StorageBackend | { id: ?string, name: string }>,
+    selectedItem: ?StorageBackend,
+    disk: Disk,
+    type: 'backend' | 'disk'
+  ) {
+    if (disk.disabled && type === 'disk') {
+      return <DiskDisabledMessage>{disk.disabled}</DiskDisabledMessage>
+    }
+
+    return storageItems.length > 10 ? (
+      <AutocompleteDropdown
+        width={StyleProps.inputSizes.large.width}
+        selectedItem={selectedItem}
+        items={storageItems}
+        onChange={(item: StorageBackend) => { this.props.onChange(disk, item, type) }}
+        labelField="name"
+        valueField="id"
+      />
+    ) :
+      (
+        <Dropdown
+          width={StyleProps.inputSizes.large.width}
+          centered
+          noSelectionMessage="Default"
+          noItemsMessage="No storage found"
+          selectedItem={selectedItem}
+          items={storageItems}
+          labelField="name"
+          valueField="id"
+          onChange={(item: StorageBackend) => { this.props.onChange(disk, item, type) }}
+          data-test-id={`${TEST_ID}-${type}-destination`}
+        />
+      )
+  }
+
   renderStorageWrapper(disks: Disk[], type: 'backend' | 'disk') {
     let title = type === 'backend' ? 'Storage Backend Mapping' : 'Disk Mapping'
     let diskFieldName = type === 'backend' ? 'storage_backend_identifier' : 'id'
@@ -172,7 +213,7 @@ class WizardStorage extends React.Component<Props> {
     ]
 
     disks = disks.filter(d => d[diskFieldName])
-    disks.sort((d1, d2) => String(d1[diskFieldName]).localeCompare(String(d2[diskFieldName])))
+
     let parseDiskName = (name: ?string): [?string, boolean] => {
       if (!name) {
         return [null, false]
@@ -225,30 +266,7 @@ class WizardStorage extends React.Component<Props> {
                   ) : null}
                 </StorageTitle>
                 <ArrowImage />
-                {storageItems.length > 10 ? (
-                  <AutocompleteDropdown
-                    width={StyleProps.inputSizes.large.width}
-                    selectedItem={selectedItem}
-                    items={storageItems}
-                    onChange={(item: StorageBackend) => { this.props.onChange(disk, item, type) }}
-                    labelField="name"
-                    valueField="id"
-                  />
-                ) :
-                  (
-                    <Dropdown
-                      width={StyleProps.inputSizes.large.width}
-                      centered
-                      noSelectionMessage="Default"
-                      noItemsMessage="No storage found"
-                      selectedItem={selectedItem}
-                      items={storageItems}
-                      labelField="name"
-                      valueField="id"
-                      onChange={(item: StorageBackend) => { this.props.onChange(disk, item, type) }}
-                      data-test-id={`${TEST_ID}-${type}-destination`}
-                    />
-                  )}
+                {this.renderStorageDropdown(storageItems, selectedItem, disk, type)}
               </StorageItem>
             )
           })}

+ 1 - 0
src/components/pages/AssessmentDetailsPage/AssessmentDetailsPage.jsx

@@ -408,6 +408,7 @@ class AssessmentDetailsPage extends React.Component<Props, State> {
         location: localData.locationName,
         resource_group: localData.resourceGroupName,
       },
+      targetProvider: 'azure',
     })
   }
 

+ 3 - 0
src/components/pages/MigrationDetailsPage/MigrationDetailsPage.jsx

@@ -129,6 +129,8 @@ class MigrationDetailsPage extends React.Component<Props, State> {
       quietError: true,
       cache,
     })
+
+    let targetEndpoint = endpointStore.endpoints.find(e => e.id === details.destination_endpoint_id)
     instanceStore.loadInstancesDetails({
       endpointId: details.origin_endpoint_id,
       // $FlowIgnore
@@ -136,6 +138,7 @@ class MigrationDetailsPage extends React.Component<Props, State> {
       cache,
       quietError: false,
       env: details.source_environment,
+      targetProvider: targetEndpoint ? targetEndpoint.type : '',
     })
   }
 

+ 3 - 0
src/components/pages/ReplicaDetailsPage/ReplicaDetailsPage.jsx

@@ -168,6 +168,8 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
       quietError: true,
       cache,
     })
+
+    let targetEndpoint = endpointStore.endpoints.find(e => e.id === details.destination_endpoint_id)
     instanceStore.loadInstancesDetails({
       endpointId: details.origin_endpoint_id,
       // $FlowIgnore
@@ -175,6 +177,7 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
       cache,
       quietError: false,
       env: details.source_environment,
+      targetProvider: targetEndpoint ? targetEndpoint.type : '',
     })
   }
 

+ 2 - 1
src/components/pages/WizardPage/WizardPage.jsx

@@ -465,12 +465,13 @@ class WizardPage extends React.Component<Props, State> {
   }
 
   loadNetworks(cache: boolean) {
-    if (wizardStore.data.source && wizardStore.data.selectedInstances) {
+    if (wizardStore.data.source && wizardStore.data.selectedInstances && wizardStore.data.target) {
       instanceStore.loadInstancesDetails({
         endpointId: wizardStore.data.source.id,
         instancesInfo: wizardStore.data.selectedInstances,
         env: wizardStore.data.sourceOptions,
         cache,
+        targetProvider: wizardStore.data.target.type,
       })
     }
     if (wizardStore.data.target) {

+ 23 - 0
src/plugins/endpoint/default/InstanceInfoPlugin.js

@@ -0,0 +1,23 @@
+/*
+Copyright (C) 2020  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// @flow
+
+import type { Instance } from '../../../types/Instance'
+
+export default class InstanceInfoPlugin {
+  static parseInstance(instance: Instance): Instance {
+    return instance
+  }
+}

+ 8 - 0
src/plugins/endpoint/index.js

@@ -26,6 +26,9 @@ import DefaultOptionsSchemaPlugin from './default/OptionsSchemaPlugin'
 import OvmOptionsSchemaPlugin from './ovm/OptionsSchemaPlugin'
 import OpenstackOptionsSchemaPlugin from './openstack/OptionsSchemaPlugin'
 
+import DefaultInstanceInfoPlugin from './default/InstanceInfoPlugin'
+import OciInstanceInfoPlugin from './oci/InstanceInfoPlugin'
+
 export const ConnectionSchemaPlugin = {
   default: DefaultConnectionSchemaPlugin,
   azure: AzureConnectionSchemaPlugin,
@@ -44,3 +47,8 @@ export const ContentPlugin = {
   azure: AzureContentPlugin,
   openstack: OpenstackContentPlugin,
 }
+
+export const InstanceInfoPlugin = {
+  default: DefaultInstanceInfoPlugin,
+  oci: OciInstanceInfoPlugin,
+}

+ 27 - 0
src/plugins/endpoint/oci/InstanceInfoPlugin.js

@@ -0,0 +1,27 @@
+/*
+Copyright (C) 2020  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// @flow
+
+import type { Instance } from '../../../types/Instance'
+
+export default class InstanceInfoPlugin {
+  static parseInstance(instance: Instance): Instance {
+    let rootDisk = instance.devices.disks[0]
+    if (rootDisk) {
+      rootDisk.disabled = 'Storage types cannot be selected for root disks on OCI'
+    }
+    return instance
+  }
+}

+ 9 - 1
src/sources/InstanceSource.js

@@ -19,6 +19,8 @@ import type { Instance } from '../types/Instance'
 
 import { servicesUrl } from '../constants'
 
+import { InstanceInfoPlugin } from '../plugins/endpoint'
+
 class InstanceSource {
   async loadInstancesChunk(
     endpointId: string,
@@ -76,6 +78,7 @@ class InstanceSource {
   async loadInstanceDetails(
     endpointId: string,
     instanceName: string,
+    targetProvider: string,
     reqId: number,
     quietError?: boolean,
     env?: any,
@@ -91,7 +94,12 @@ class InstanceSource {
       quietError,
       cache,
     })
-    return { instance: response.data.instance, reqId }
+
+
+    let instanceInfoParser = InstanceInfoPlugin[targetProvider] || InstanceInfoPlugin.default
+    let instance = instanceInfoParser.parseInstance(response.data.instance)
+
+    return { instance, reqId }
   }
 
   cancelInstancesDetailsRequests(reqId: number) {

+ 3 - 2
src/stores/InstanceStore.js

@@ -250,8 +250,9 @@ class InstanceStore {
     cache?: boolean,
     quietError?: boolean,
     env?: any,
+    targetProvider: string,
   }): Promise<void> {
-    let { endpointId, instancesInfo, cache, quietError, env } = opts
+    let { endpointId, instancesInfo, cache, quietError, env, targetProvider } = opts
     // Use reqId to be able to uniquely identify the request so all but the latest request can be igonred and canceled
     this.reqId = !this.reqId ? 1 : this.reqId + 1
     InstanceSource.cancelInstancesDetailsRequests(this.reqId - 1)
@@ -270,7 +271,7 @@ class InstanceStore {
         try {
           let resp: { instance: Instance, reqId: number } =
             await InstanceSource.loadInstanceDetails(endpointId, instanceInfo.instance_name || instanceInfo.name,
-              this.reqId, quietError, env, cache)
+              targetProvider, this.reqId, quietError, env, cache)
           if (resp.reqId !== this.reqId) {
             return
           }

+ 1 - 0
src/types/Instance.js

@@ -29,6 +29,7 @@ export type Disk = {
   format?: string,
   guest_device?: string,
   size_bytes?: number,
+  disabled?: string,
 }
 
 export type Instance = {