Răsfoiți Sursa

Merge pull request #625 from smiclea/ovm-source-schema

Add 'Use OVM Exporter' field support
Nashwan Azhari 5 ani în urmă
părinte
comite
17bb5028b7

+ 1 - 1
src/@types/Field.ts

@@ -53,7 +53,7 @@ export type Field = {
   title?: string,
   description?: string,
   subFields?: Field[],
-  groupName?: string,
+  groupName?: string
 }
 
 const migrationImageOsTypes = ['windows', 'linux']

+ 2 - 1
src/components/organisms/EditReplica/EditReplica.tsx

@@ -482,7 +482,7 @@ class EditReplica extends React.Component<Props, State> {
       data[field.name] = value
     }
 
-    if (field.enum && field.subFields) {
+    if (field.subFields) {
       field.subFields.forEach(subField => {
         const subFieldKeys = Object.keys(data).filter(k => k.indexOf(subField.name) > -1)
         subFieldKeys.forEach(k => {
@@ -519,6 +519,7 @@ class EditReplica extends React.Component<Props, State> {
       try {
         await replicaStore.update({
           replica: this.props.replica as any,
+          sourceEndpoint: this.props.sourceEndpoint,
           destinationEndpoint: this.props.destinationEndpoint,
           updateData,
           defaultStorage: this.getDefaultStorage(),

+ 17 - 7
src/components/organisms/WizardOptions/WizardOptions.tsx

@@ -213,12 +213,14 @@ class WizardOptions extends React.Component<Props> {
       const dictionaryLabel = LabelDictionary.get('separate_vm')
       const label = this.props.wizardType === 'migration' ? dictionaryLabel : dictionaryLabel.replace('Migration', 'Replica')
       fieldsSchema.push({
-        name: 'separate_vm', label, type: 'boolean', default: true,
+        name: 'separate_vm', label, type: 'boolean', default: true, nullableBoolean: false,
       })
     }
 
     if (this.props.wizardType === 'migration' || this.props.wizardType === 'migration-destination-options-edit') {
-      fieldsSchema.push({ name: 'skip_os_morphing', type: 'boolean', default: false })
+      fieldsSchema.push({
+        name: 'skip_os_morphing', type: 'boolean', default: false, nullableBoolean: false,
+      })
     }
 
     if (this.props.wizardType === 'migration' || this.props.wizardType === 'replica'
@@ -236,7 +238,9 @@ class WizardOptions extends React.Component<Props> {
     }
 
     if (this.props.wizardType === 'replica') {
-      fieldsSchema.push({ name: 'execute_now', type: 'boolean', default: true })
+      fieldsSchema.push({
+        name: 'execute_now', type: 'boolean', default: true, nullableBoolean: false,
+      })
       const executeNowValue = this.getFieldValue('execute_now', true)
       fieldsSchema.push({
         name: 'execute_now_options',
@@ -385,7 +389,6 @@ class WizardOptions extends React.Component<Props> {
     }
 
     let fieldsSchema: Field[] = this.getDefaultSimpleFieldsSchema()
-    const nonNullableBooleans: string[] = fieldsSchema.filter(f => f.type === 'boolean').map(f => f.name)
 
     fieldsSchema = fieldsSchema.concat(this.props.fields.filter(f => f.required))
 
@@ -394,18 +397,25 @@ class WizardOptions extends React.Component<Props> {
       fieldsSchema = fieldsSchema.concat(this.props.fields.filter(f => !f.required))
     }
 
+    const nonNullableBooleans: string[] = fieldsSchema.filter(f => f.type === 'boolean' && f.nullableBoolean === false).map(f => f.name)
+
     // Add subfields for enums which have them
     let subFields: any[] = []
     fieldsSchema.forEach(f => {
-      if (!f.enum || !f.subFields) {
+      if (!f.subFields) {
         return
       }
       const value = this.getFieldValue(f.name, f.default)
       if (!f.subFields) {
         return
       }
-      const subField = f.subFields.find(sf => sf.name === `${String(value)}_options`)
-      if (subField && subField.properties) {
+      let subField: Field | undefined
+      if (f.type === 'boolean') {
+        subField = value ? f.subFields[1] : f.subFields[0]
+      } else {
+        subField = f.subFields.find(sf => sf.name === `${String(value)}_options`)
+      }
+      if (subField?.properties) {
         subFields = [...subFields, ...subField.properties]
       }
     })

+ 2 - 0
src/constants.ts

@@ -66,6 +66,7 @@ export const executionOptions = [
     name: 'shutdown_instances',
     type: 'boolean',
     defaultValue: false,
+    nullableBoolean: false,
   },
 ]
 
@@ -74,6 +75,7 @@ export const migrationFields = [
     name: 'shutdown_instances',
     type: 'boolean',
     default: false,
+    nullableBoolean: false,
     description: 'Whether or not Coriolis should power off the source VM before performing the final incremental sync. This guarantees consistency of the exported VM\'s filesystems, but implies downtime for the source VM during the final sync.',
   },
   {

+ 14 - 0
src/plugins/endpoint/default/OptionsSchemaPlugin.ts

@@ -161,6 +161,20 @@ export default class OptionsSchemaParser {
     return defaultSchemaToFields(schema, schemaDefinitions, dictionaryKey)
   }
 
+  static sortFields(fields: Field[]) {
+    fields.sort((a, b) => {
+      if (a.required && !b.required) {
+        return -1
+      }
+
+      if (!a.required && b.required) {
+        return 1
+      }
+
+      return a.name.localeCompare(b.name)
+    })
+  }
+
   static fillFieldValues(field: Field, options: OptionValues[], customFieldName?: string) {
     const option = options
       .find(f => (customFieldName ? f.name === customFieldName : f.name === field.name))

+ 20 - 16
src/plugins/endpoint/openstack/OptionsSchemaPlugin.ts

@@ -34,27 +34,31 @@ export default class OptionsSchemaParser {
     schemaDefinitions: SchemaDefinitions | null | undefined,
     dictionaryKey: string,
   ) {
-    const fields = DefaultOptionsSchemaPlugin
-      .parseSchemaToFields(schema, schemaDefinitions, dictionaryKey)
+    const fields = DefaultOptionsSchemaPlugin.parseSchemaToFields(schema, schemaDefinitions, dictionaryKey)
     const exportMechField = fields.find(f => f.name === 'replica_export_mechanism')
-    if (exportMechField) {
-      exportMechField.subFields = []
-      exportMechField.enum.forEach((exportType: any) => {
-        const exportTypeFieldIdx = fields.findIndex(f => f.name === `${exportType}_options`)
-        if (exportTypeFieldIdx > -1) {
-          const subField = fields[exportTypeFieldIdx]
-          if (subField.properties && subField.properties.length) {
-            subField.properties = subField.properties
-              .map((p: any) => ({ ...p, groupName: subField.name }))
-          }
-          exportMechField.subFields.push(subField)
-          fields.splice(exportTypeFieldIdx, 1)
-        }
-      })
+    if (!exportMechField) {
+      return fields
     }
+    exportMechField.subFields = []
+    exportMechField.enum.forEach((exportType: any) => {
+      const exportTypeFieldIdx = fields.findIndex(f => f.name === `${exportType}_options`)
+      if (exportTypeFieldIdx === -1) {
+        return
+      }
+      const subField = fields[exportTypeFieldIdx]
+      if (subField.properties?.length) {
+        subField.properties = subField.properties.map((p: Field) => ({ ...p, groupName: subField.name }))
+      }
+      exportMechField.subFields.push(subField)
+      fields.splice(exportTypeFieldIdx, 1)
+    })
     return fields
   }
 
+  static sortFields(fields: Field[]) {
+    DefaultOptionsSchemaPlugin.sortFields(fields)
+  }
+
   static fillFieldValues(field: Field, options: OptionValues[]) {
     if (field.name === 'replica_export_mechanism' && field.subFields) {
       field.subFields.forEach(sf => {

+ 58 - 14
src/plugins/endpoint/ovm/OptionsSchemaPlugin.ts

@@ -34,8 +34,31 @@ export default class OptionsSchemaParser {
     schemaDefinitions: SchemaDefinitions | null | undefined,
     dictionaryKey: string,
   ) {
-    const fields = DefaultOptionsSchemaPlugin
-      .parseSchemaToFields(schema, schemaDefinitions, dictionaryKey)
+    const fields: Field[] = DefaultOptionsSchemaPlugin.parseSchemaToFields(schema, schemaDefinitions, dictionaryKey)
+    const useCoriolisExporterField = fields.find(f => f.name === 'use_coriolis_exporter')
+    if (useCoriolisExporterField) {
+      const usableFields: Field[] = [
+        {
+          ...useCoriolisExporterField,
+          nullableBoolean: false,
+          default: false,
+          subFields: [
+            {
+              name: 'generic_exporter_options',
+              type: 'object',
+              properties: fields.filter(f => f.name !== 'use_coriolis_exporter')
+                .map(f => ({ ...f, groupName: 'generic_exporter_options' })),
+            },
+            {
+              name: 'ovm_exporter_options',
+              type: 'object',
+              properties: [],
+            },
+          ],
+        },
+      ]
+      return usableFields
+    }
     fields.forEach(f => {
       if (
         f.name !== 'migr_template_username_map'
@@ -63,25 +86,46 @@ export default class OptionsSchemaParser {
     return fields
   }
 
+  static sortFields(fields: Field[]) {
+    DefaultOptionsSchemaPlugin.sortFields(fields)
+  }
+
   static fillFieldValues(field: Field, options: OptionValues[]) {
-    const option = options.find(f => f.name === field.name)
-    if (!option) {
-      return
-    }
-    if (!defaultFillMigrationImageMapValues(
-      field,
-      option,
-      this.migrationImageMapFieldName,
-    )) {
-      defaultFillFieldValues(field, option)
+    if (field.name === 'use_coriolis_exporter') {
+      field.subFields?.forEach(sf => {
+        if (sf.properties) {
+          sf.properties.forEach(f => {
+            DefaultOptionsSchemaPlugin.fillFieldValues(f, options, f.name.split('/')[1])
+          })
+        }
+      })
+    } else {
+      const option = options.find(f => f.name === field.name)
+      if (!option) {
+        return
+      }
+      if (!defaultFillMigrationImageMapValues(
+        field,
+        option,
+        this.migrationImageMapFieldName,
+      )) {
+        defaultFillFieldValues(field, option)
+      }
     }
   }
 
   static getDestinationEnv(options: { [prop: string]: any } | null, oldOptions?: any) {
+    let newOptions: any = { ...options }
+    if (newOptions.use_coriolis_exporter != null) {
+      newOptions = { use_coriolis_exporter: newOptions.use_coriolis_exporter }
+    }
+    if (options?.generic_exporter_options) {
+      newOptions = { ...options.generic_exporter_options, use_coriolis_exporter: false }
+    }
     const env = {
-      ...defaultGetDestinationEnv(options, oldOptions),
+      ...defaultGetDestinationEnv(newOptions, oldOptions),
       ...defaultGetMigrationImageMap(
-        options,
+        newOptions,
         oldOptions,
         this.migrationImageMapFieldName,
       ),

+ 8 - 5
src/sources/ReplicaSource.ts

@@ -213,6 +213,7 @@ class ReplicaSource {
 
   async update(options: {
     replica: ReplicaItemDetails,
+    sourceEndpoint: Endpoint,
     destinationEndpoint: Endpoint,
     updateData: UpdateData,
     defaultStorage: { value: string | null, busType?: string | null },
@@ -224,9 +225,11 @@ class ReplicaSource {
       updateData,
       defaultStorage,
       storageConfigDefault,
+      sourceEndpoint,
     } = options
 
-    const parser = OptionsSchemaPlugin.for(destinationEndpoint.type)
+    const sourceParser = OptionsSchemaPlugin.for(sourceEndpoint.type)
+    const destinationParser = OptionsSchemaPlugin.for(destinationEndpoint.type)
     const payload: any = { replica: {} }
 
     if (updateData.destination.title) {
@@ -234,10 +237,10 @@ class ReplicaSource {
     }
 
     if (updateData.network.length > 0) {
-      payload.replica.network_map = parser.getNetworkMap(updateData.network)
+      payload.replica.network_map = destinationParser.getNetworkMap(updateData.network)
     }
     if (Object.keys(updateData.source).length > 0) {
-      const sourceEnv = parser.getDestinationEnv(updateData.source, replica.source_environment)
+      const sourceEnv = sourceParser.getDestinationEnv(updateData.source, replica.source_environment)
       if (updateData.source.minion_pool_id !== undefined) {
         payload.replica.origin_minion_pool_id = updateData.source.minion_pool_id
       }
@@ -247,7 +250,7 @@ class ReplicaSource {
     }
 
     if (Object.keys(updateData.destination).length > 0) {
-      const destEnv = parser.getDestinationEnv(updateData.destination,
+      const destEnv = destinationParser.getDestinationEnv(updateData.destination,
         { ...replica, ...replica.destination_environment })
 
       const newMinionMappings = destEnv[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS]
@@ -267,7 +270,7 @@ class ReplicaSource {
     }
 
     if (defaultStorage || updateData.storage.length > 0) {
-      payload.replica.storage_mappings = parser
+      payload.replica.storage_mappings = destinationParser
         .getStorageMap(defaultStorage, updateData.storage, storageConfigDefault)
     }
 

+ 1 - 11
src/sources/Schemas.ts

@@ -35,17 +35,7 @@ class SchemaParser {
     const parser = OptionsSchemaPlugin.for(provider)
     const schemaRoot = schema.oneOf ? schema.oneOf[0] : schema
     const fields = parser.parseSchemaToFields(schemaRoot, schema.definitions, dictionaryKey)
-    fields.sort((a, b) => {
-      if (a.required && !b.required) {
-        return -1
-      }
-
-      if (!a.required && b.required) {
-        return 1
-      }
-
-      return a.name.localeCompare(b.name)
-    })
+    parser.sortFields(fields)
     return fields
   }
 

+ 1 - 0
src/stores/ReplicaStore.ts

@@ -241,6 +241,7 @@ class ReplicaStore {
 
   async update(options: {
     replica: ReplicaItemDetails,
+    sourceEndpoint: Endpoint,
     destinationEndpoint: Endpoint,
     updateData: UpdateData,
     defaultStorage: { value: string | null, busType?: string | null },

+ 1 - 1
src/stores/WizardStore.ts

@@ -51,7 +51,7 @@ const updateOptions = (
     options[data.field.name] = data.value
   }
 
-  if (data.field.enum && data.field.subFields) {
+  if (data.field.subFields) {
     data.field.subFields.forEach(subField => {
       const subFieldKeys = Object.keys(options).filter(k => k.indexOf(subField.name) > -1)
       subFieldKeys.forEach(k => {