Jelajahi Sumber

Merge pull request #555 from smiclea/recreate-migrations

Recreate migrations in bulk in migrations list CORWEB-238
Nashwan Azhari 5 tahun lalu
induk
melakukan
b98ea79320

+ 3 - 16
src/components/organisms/ReplicaMigrationOptions/ReplicaMigrationOptions.tsx

@@ -26,6 +26,7 @@ import KeyboardManager from '../../../utils/KeyboardManager'
 import StyleProps from '../../styleUtils/StyleProps'
 
 import replicaMigrationImage from './images/replica-migration.svg'
+import replicaMigrationFields from './replicaMigrationFields'
 
 import type { Field } from '../../../@types/Field'
 import type { Instance, InstanceScript } from '../../../@types/Instance'
@@ -86,21 +87,7 @@ type State = {
   selectedBarButton: string,
   uploadedScripts: InstanceScript[],
 }
-const defaultFields: Field[] = [
-  {
-    name: 'clone_disks',
-    type: 'boolean',
-    value: true,
-  },
-  {
-    name: 'force',
-    type: 'boolean',
-  },
-  {
-    name: 'skip_os_morphing',
-    type: 'boolean',
-  },
-]
+
 @observer
 class ReplicaMigrationOptions extends React.Component<Props, State> {
   state: State = {
@@ -113,7 +100,7 @@ class ReplicaMigrationOptions extends React.Component<Props, State> {
 
   UNSAFE_componentWillMount() {
     this.setState({
-      fields: defaultFields.map(f => (f.name === 'skip_os_morphing' ? (
+      fields: replicaMigrationFields.map(f => (f.name === 'skip_os_morphing' ? (
         { ...f, value: this.props.defaultSkipOsMorphing || null }
       ) : f)),
     })

+ 33 - 0
src/components/organisms/ReplicaMigrationOptions/replicaMigrationFields.ts

@@ -0,0 +1,33 @@
+/*
+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/>.
+*/
+
+import { Field } from '../../../@types/Field'
+
+const replicaMigrationFields: Field[] = [
+  {
+    name: 'clone_disks',
+    type: 'boolean',
+    value: true,
+  },
+  {
+    name: 'force',
+    type: 'boolean',
+  },
+  {
+    name: 'skip_os_morphing',
+    type: 'boolean',
+  },
+]
+
+export default replicaMigrationFields

+ 1 - 0
src/components/pages/MigrationDetailsPage/MigrationDetailsPage.tsx

@@ -310,6 +310,7 @@ class MigrationDetailsPage extends React.Component<Props, State> {
       },
       {
         label: 'Recreate Migration',
+        color: Palette.primary,
         action: () => { this.handleRecreateClick() },
       },
       {

+ 47 - 9
src/components/pages/MigrationsPage/MigrationsPage.tsx

@@ -34,6 +34,7 @@ import notificationStore from '../../../stores/NotificationStore'
 import configLoader from '../../../utils/Config'
 
 import Palette from '../../styleUtils/Palette'
+import replicaMigrationFields from '../../organisms/ReplicaMigrationOptions/replicaMigrationFields'
 
 const Wrapper = styled.div<any>``
 
@@ -42,12 +43,14 @@ type State = {
   modalIsOpen: boolean,
   showDeleteMigrationModal: boolean,
   showCancelMigrationModal: boolean,
+  showRecreateMigrationsModal: boolean,
 }
 @observer
 class MigrationsPage extends React.Component<{ history: any }, State> {
   state: State = {
     showDeleteMigrationModal: false,
     showCancelMigrationModal: false,
+    showRecreateMigrationsModal: false,
     selectedMigrations: [],
     modalIsOpen: false,
   }
@@ -81,6 +84,7 @@ class MigrationsPage extends React.Component<{ history: any }, State> {
       { label: 'Running', value: 'RUNNING' },
       { label: 'Error', value: 'ERROR' },
       { label: 'Completed', value: 'COMPLETED' },
+      { label: 'Canceled', value: 'CANCELED' },
     ]
   }
 
@@ -125,6 +129,21 @@ class MigrationsPage extends React.Component<{ history: any }, State> {
     this.setState({ showCancelMigrationModal: false })
   }
 
+  async recreateMigrations() {
+    notificationStore.alert('Recreating migrations')
+    this.setState({ showRecreateMigrationsModal: false })
+
+    await Promise.all(this.state.selectedMigrations.map(async migration => {
+      if (migration.replica_id) {
+        await migrationStore.migrateReplica(migration.replica_id, replicaMigrationFields, [])
+      } else {
+        await migrationStore.recreateFullCopy(migration)
+      }
+    }))
+
+    migrationStore.getMigrations()
+  }
+
   handleEmptyListButtonClick() {
     this.props.history.push('/wizard/migration')
   }
@@ -183,15 +202,24 @@ class MigrationsPage extends React.Component<{ history: any }, State> {
     this.state.selectedMigrations.forEach(migration => {
       atLeaseOneIsRunning = atLeaseOneIsRunning || this.getStatus(migration.id) === 'RUNNING'
     })
-    const BulkActions = [{
-      label: 'Cancel',
-      disabled: !atLeaseOneIsRunning,
-      action: () => { this.setState({ showCancelMigrationModal: true }) },
-    }, {
-      label: 'Delete Migration',
-      color: Palette.alert,
-      action: () => { this.setState({ showDeleteMigrationModal: true }) },
-    }]
+    const BulkActions = [
+      {
+        label: 'Cancel',
+        disabled: !atLeaseOneIsRunning,
+        action: () => { this.setState({ showCancelMigrationModal: true }) },
+      },
+      {
+        label: 'Recreate Migrations',
+        disabled: atLeaseOneIsRunning,
+        color: Palette.primary,
+        action: () => { this.setState({ showRecreateMigrationsModal: true }) },
+      },
+      {
+        label: 'Delete Migrations',
+        color: Palette.alert,
+        action: () => { this.setState({ showDeleteMigrationModal: true }) },
+      },
+    ]
 
     return (
       <Wrapper>
@@ -263,6 +291,16 @@ class MigrationsPage extends React.Component<{ history: any }, State> {
             onRequestClose={() => { this.setState({ showCancelMigrationModal: false }) }}
           />
         ) : null}
+        {this.state.showRecreateMigrationsModal ? (
+          <AlertModal
+            isOpen
+            title="Recreate Selected Migrations?"
+            message="Are you sure you want to recreate the selected migrations?"
+            extraMessage="Migrations created from replicas will be recreated using default options and regular migrations will be recreated using their original source and destination environment options."
+            onConfirmation={() => { this.recreateMigrations() }}
+            onRequestClose={() => { this.setState({ showRecreateMigrationsModal: false }) }}
+          />
+        ) : null}
       </Wrapper>
     )
   }

+ 37 - 0
src/sources/MigrationSource.ts

@@ -72,6 +72,43 @@ class MigrationSource {
     return migration
   }
 
+  async recreateFullCopy(migration: MainItem): Promise<MainItem> {
+    const {
+      origin_endpoint_id, destination_endpoint_id, destination_environment,
+      network_map, instances, storage_mappings, notes,
+    } = migration
+
+    const payload: any = {
+      migration: {
+        origin_endpoint_id,
+        destination_endpoint_id,
+        destination_environment,
+        network_map,
+        instances,
+        storage_mappings,
+        notes,
+      },
+    }
+
+    if (migration.skip_os_morphing != null) {
+      payload.migration.skip_os_morphing = migration.skip_os_morphing
+    }
+
+    if (migration.source_environment) {
+      payload.migration.source_environment = migration.source_environment
+    }
+
+    payload.migration.shutdown_instances = Boolean(migration.shutdown_instances)
+    payload.migration.replication_count = migration.replication_count || 2
+
+    const response = await Api.send({
+      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations`,
+      method: 'POST',
+      data: payload,
+    })
+    return response.data.migration
+  }
+
   async recreate(opts: {
     sourceEndpoint: Endpoint,
     destEndpoint: Endpoint,

+ 4 - 0
src/stores/MigrationStore.ts

@@ -65,6 +65,10 @@ class MigrationStore {
     return null
   }
 
+  @action async recreateFullCopy(migration: MainItem) {
+    return MigrationSource.recreateFullCopy(migration)
+  }
+
   @action async recreate(
     migration: MainItem,
     sourceEndpoint: Endpoint,