Преглед на файлове

Remove references to API's migration 'info' field

The structure or existence of the 'info' field in a replica or a
migration can't be trusted. An alternative method is used to retrieve
the VM instances connected to a specific network, which involves making
an API call for each instance of a replica or a migration.

This affects only the replica and migration details page and the
following changes are visible:

 - a 'Loading...' label is display in the 'Connected VMs' field of the
network table while the migration / replica instances details are
loading.

 - an error message may be shown if there are issues retrieving the
instances details.
Sergiu Miclea преди 7 години
родител
ревизия
4339c7f2fb

+ 16 - 10
src/components/organisms/MainDetails/MainDetails.jsx

@@ -25,6 +25,7 @@ import StatusImage from '../../atoms/StatusImage'
 import Table from '../../molecules/Table'
 import Table from '../../molecules/Table'
 import CopyMultilineValue from '../../atoms/CopyMultilineValue'
 import CopyMultilineValue from '../../atoms/CopyMultilineValue'
 
 
+import type { Instance } from '../../../types/Instance'
 import type { MainItem } from '../../../types/MainItem'
 import type { MainItem } from '../../../types/MainItem'
 import type { Endpoint } from '../../../types/Endpoint'
 import type { Endpoint } from '../../../types/Endpoint'
 import StyleProps from '../../styleUtils/StyleProps'
 import StyleProps from '../../styleUtils/StyleProps'
@@ -113,6 +114,8 @@ const PropertyValue = styled.div`
 
 
 type Props = {
 type Props = {
   item: ?MainItem,
   item: ?MainItem,
+  instancesDetails: Instance[],
+  instancesDetailsLoading: boolean,
   endpoints: Endpoint[],
   endpoints: Endpoint[],
   bottomControls: React.Node,
   bottomControls: React.Node,
   loading: boolean,
   loading: boolean,
@@ -138,19 +141,22 @@ class MainDetails extends React.Component<Props> {
   }
   }
 
 
   getConnectedVms(networkId: string) {
   getConnectedVms(networkId: string) {
-    let vms = []
+    if (this.props.instancesDetailsLoading) {
+      return 'Loading...'
+    }
+
     if (!this.props.item) {
     if (!this.props.item) {
       return '-'
       return '-'
     }
     }
-    Object.keys(this.props.item.info).forEach(key => {
-      // $FlowIssue
-      let instance = this.props.item.info[key]
-      if (instance.export_info && instance.export_info.devices.nics.length) {
-        instance.export_info.devices.nics.forEach(nic => {
-          if (nic.network_name === networkId) {
-            vms.push(key)
-          }
-        })
+
+    let vms: string[] = []
+
+    this.props.instancesDetails.forEach(instanceDet => {
+      if (
+        instanceDet.devices && instanceDet.devices.nics && instanceDet.devices.nics.find &&
+        instanceDet.devices.nics.find(n => n.network_name === networkId)
+      ) {
+        vms.push(instanceDet.instance_name)
       }
       }
     })
     })
 
 

+ 5 - 0
src/components/organisms/MigrationDetailsContent/MigrationDetailsContent.jsx

@@ -24,6 +24,7 @@ import MainDetails from '../../organisms/MainDetails'
 import Tasks from '../../organisms/Tasks'
 import Tasks from '../../organisms/Tasks'
 import StyleProps from '../../styleUtils/StyleProps'
 import StyleProps from '../../styleUtils/StyleProps'
 
 
+import type { Instance } from '../../../types/Instance'
 import type { MainItem } from '../../../types/MainItem'
 import type { MainItem } from '../../../types/MainItem'
 import type { Endpoint } from '../../../types/Endpoint'
 import type { Endpoint } from '../../../types/Endpoint'
 
 
@@ -54,6 +55,8 @@ const NavigationItems = [
 type Props = {
 type Props = {
   item: ?MainItem,
   item: ?MainItem,
   detailsLoading: boolean,
   detailsLoading: boolean,
+  instancesDetails: Instance[],
+  instancesDetailsLoading: boolean,
   endpoints: Endpoint[],
   endpoints: Endpoint[],
   page: string,
   page: string,
   onDeleteMigrationClick: () => void,
   onDeleteMigrationClick: () => void,
@@ -80,6 +83,8 @@ class MigrationDetailsContent extends React.Component<Props> {
     return (
     return (
       <MainDetails
       <MainDetails
         item={this.props.item}
         item={this.props.item}
+        instancesDetails={this.props.instancesDetails}
+        instancesDetailsLoading={this.props.instancesDetailsLoading}
         endpoints={this.props.endpoints}
         endpoints={this.props.endpoints}
         bottomControls={this.renderBottomControls()}
         bottomControls={this.renderBottomControls()}
         loading={this.props.detailsLoading}
         loading={this.props.detailsLoading}

+ 5 - 0
src/components/organisms/ReplicaDetailsContent/ReplicaDetailsContent.jsx

@@ -24,6 +24,7 @@ import DetailsNavigation from '../../molecules/DetailsNavigation'
 import MainDetails from '../../organisms/MainDetails'
 import MainDetails from '../../organisms/MainDetails'
 import Executions from '../../organisms/Executions'
 import Executions from '../../organisms/Executions'
 import Schedule from '../../organisms/Schedule'
 import Schedule from '../../organisms/Schedule'
+import type { Instance } from '../../../types/Instance'
 import type { MainItem } from '../../../types/MainItem'
 import type { MainItem } from '../../../types/MainItem'
 import type { Endpoint } from '../../../types/Endpoint'
 import type { Endpoint } from '../../../types/Endpoint'
 import type { Execution } from '../../../types/Execution'
 import type { Execution } from '../../../types/Execution'
@@ -71,6 +72,8 @@ type TimezoneValue = 'utc' | 'local'
 type Props = {
 type Props = {
   item: ?MainItem,
   item: ?MainItem,
   endpoints: Endpoint[],
   endpoints: Endpoint[],
+  instancesDetails: Instance[],
+  instancesDetailsLoading: boolean,
   scheduleStore: typeof scheduleStore,
   scheduleStore: typeof scheduleStore,
   page: string,
   page: string,
   detailsLoading: boolean,
   detailsLoading: boolean,
@@ -154,6 +157,8 @@ class ReplicaDetailsContent extends React.Component<Props, State> {
     return (
     return (
       <MainDetails
       <MainDetails
         item={this.props.item}
         item={this.props.item}
+        instancesDetails={this.props.instancesDetails}
+        instancesDetailsLoading={this.props.instancesDetailsLoading}
         loading={this.props.detailsLoading}
         loading={this.props.detailsLoading}
         endpoints={this.props.endpoints}
         endpoints={this.props.endpoints}
         bottomControls={this.renderBottomControls()}
         bottomControls={this.renderBottomControls()}

+ 18 - 4
src/components/pages/MigrationDetailsPage/MigrationDetailsPage.jsx

@@ -28,6 +28,7 @@ import migrationStore from '../../../stores/MigrationStore'
 import userStore from '../../../stores/UserStore'
 import userStore from '../../../stores/UserStore'
 import endpointStore from '../../../stores/EndpointStore'
 import endpointStore from '../../../stores/EndpointStore'
 import notificationStore from '../../../stores/NotificationStore'
 import notificationStore from '../../../stores/NotificationStore'
+import instanceStore from '../../../stores/InstanceStore'
 import { requestPollTimeout } from '../../../config'
 import { requestPollTimeout } from '../../../config'
 
 
 import migrationImage from './images/migration.svg'
 import migrationImage from './images/migration.svg'
@@ -54,13 +55,13 @@ class MigrationDetailsPage extends React.Component<Props, State> {
     document.title = 'Migration Details'
     document.title = 'Migration Details'
 
 
     endpointStore.getEndpoints()
     endpointStore.getEndpoints()
-    this.pollData(true)
+    this.loadMigrationWithInstances()
     this.pollInterval = setInterval(() => { this.pollData() }, requestPollTimeout)
     this.pollInterval = setInterval(() => { this.pollData() }, requestPollTimeout)
   }
   }
 
 
   componentWillReceiveProps(newProps: any) {
   componentWillReceiveProps(newProps: any) {
     if (newProps.match.params.id !== this.props.match.params.id) {
     if (newProps.match.params.id !== this.props.match.params.id) {
-      migrationStore.getMigration(newProps.match.params.id, true)
+      this.loadMigrationWithInstances()
     }
     }
   }
   }
 
 
@@ -69,6 +70,17 @@ class MigrationDetailsPage extends React.Component<Props, State> {
     clearInterval(this.pollInterval)
     clearInterval(this.pollInterval)
   }
   }
 
 
+  loadMigrationWithInstances() {
+    migrationStore.getMigration(this.props.match.params.id, true).then(() => {
+      if (migrationStore.migrationDetails) {
+        instanceStore.loadInstancesDetails(
+          migrationStore.migrationDetails.origin_endpoint_id,
+          // $FlowIgnore
+          migrationStore.migrationDetails.instances.map(n => { return { instance_name: n } }))
+      }
+    })
+  }
+
   handleUserItemClick(item: { value: string }) {
   handleUserItemClick(item: { value: string }) {
     switch (item.value) {
     switch (item.value) {
       case 'signout':
       case 'signout':
@@ -123,8 +135,8 @@ class MigrationDetailsPage extends React.Component<Props, State> {
     })
     })
   }
   }
 
 
-  pollData(showLoading?: boolean) {
-    migrationStore.getMigration(this.props.match.params.id, showLoading || false)
+  pollData() {
+    migrationStore.getMigration(this.props.match.params.id, false)
   }
   }
 
 
   render() {
   render() {
@@ -144,6 +156,8 @@ class MigrationDetailsPage extends React.Component<Props, State> {
           />}
           />}
           contentComponent={<MigrationDetailsContent
           contentComponent={<MigrationDetailsContent
             item={migrationStore.migrationDetails}
             item={migrationStore.migrationDetails}
+            instancesDetails={instanceStore.instancesDetails}
+            instancesDetailsLoading={instanceStore.loadingInstancesDetails}
             endpoints={endpointStore.endpoints}
             endpoints={endpointStore.endpoints}
             page={this.props.match.params.page || ''}
             page={this.props.match.params.page || ''}
             detailsLoading={endpointStore.loading || migrationStore.detailsLoading}
             detailsLoading={endpointStore.loading || migrationStore.detailsLoading}

+ 16 - 2
src/components/pages/ReplicaDetailsPage/ReplicaDetailsPage.jsx

@@ -36,6 +36,7 @@ import migrationStore from '../../../stores/MigrationStore'
 import userStore from '../../../stores/UserStore'
 import userStore from '../../../stores/UserStore'
 import endpointStore from '../../../stores/EndpointStore'
 import endpointStore from '../../../stores/EndpointStore'
 import scheduleStore from '../../../stores/ScheduleStore'
 import scheduleStore from '../../../stores/ScheduleStore'
+import instanceStore from '../../../stores/InstanceStore'
 import { requestPollTimeout } from '../../../config'
 import { requestPollTimeout } from '../../../config'
 
 
 import replicaImage from './images/replica.svg'
 import replicaImage from './images/replica.svg'
@@ -71,7 +72,7 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
   componentDidMount() {
   componentDidMount() {
     document.title = 'Replica Details'
     document.title = 'Replica Details'
 
 
-    replicaStore.getReplica(this.props.match.params.id)
+    this.loadReplicaWithInstances()
     endpointStore.getEndpoints()
     endpointStore.getEndpoints()
     scheduleStore.getSchedules(this.props.match.params.id)
     scheduleStore.getSchedules(this.props.match.params.id)
     this.pollData(true)
     this.pollData(true)
@@ -79,7 +80,7 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
 
 
   componentWillReceiveProps(newProps: any) {
   componentWillReceiveProps(newProps: any) {
     if (newProps.match.params.id !== this.props.match.params.id) {
     if (newProps.match.params.id !== this.props.match.params.id) {
-      replicaStore.getReplica(newProps.match.params.id)
+      this.loadReplicaWithInstances()
       scheduleStore.getSchedules(newProps.match.params.id)
       scheduleStore.getSchedules(newProps.match.params.id)
     }
     }
   }
   }
@@ -90,6 +91,17 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
     clearTimeout(this.pollTimeout)
     clearTimeout(this.pollTimeout)
   }
   }
 
 
+  loadReplicaWithInstances() {
+    replicaStore.getReplica(this.props.match.params.id).then(() => {
+      if (replicaStore.replicaDetails) {
+        instanceStore.loadInstancesDetails(
+          replicaStore.replicaDetails.origin_endpoint_id,
+          // $FlowIgnore
+          replicaStore.replicaDetails.instances.map(n => { return { instance_name: n } }))
+      }
+    })
+  }
+
   isActionButtonDisabled() {
   isActionButtonDisabled() {
     let originEndpoint = endpointStore.endpoints.find(e => replicaStore.replicaDetails && e.id === replicaStore.replicaDetails.origin_endpoint_id)
     let originEndpoint = endpointStore.endpoints.find(e => replicaStore.replicaDetails && e.id === replicaStore.replicaDetails.origin_endpoint_id)
     let targetEndpoint = endpointStore.endpoints.find(e => replicaStore.replicaDetails && e.id === replicaStore.replicaDetails.destination_endpoint_id)
     let targetEndpoint = endpointStore.endpoints.find(e => replicaStore.replicaDetails && e.id === replicaStore.replicaDetails.destination_endpoint_id)
@@ -264,6 +276,8 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
           />}
           />}
           contentComponent={<ReplicaDetailsContent
           contentComponent={<ReplicaDetailsContent
             item={replicaStore.replicaDetails}
             item={replicaStore.replicaDetails}
+            instancesDetails={instanceStore.instancesDetails}
+            instancesDetailsLoading={instanceStore.loadingInstancesDetails}
             endpoints={endpointStore.endpoints}
             endpoints={endpointStore.endpoints}
             scheduleStore={scheduleStore}
             scheduleStore={scheduleStore}
             detailsLoading={replicaStore.detailsLoading || endpointStore.loading}
             detailsLoading={replicaStore.detailsLoading || endpointStore.loading}

+ 5 - 3
src/stores/InstanceStore.js

@@ -165,7 +165,7 @@ class InstanceStore {
     InstanceSource.cancelInstancesDetailsRequests(this.reqId - 1)
     InstanceSource.cancelInstancesDetailsRequests(this.reqId - 1)
 
 
     instancesInfo.sort((a, b) => a.instance_name.localeCompare(b.instance_name))
     instancesInfo.sort((a, b) => a.instance_name.localeCompare(b.instance_name))
-    let hash = i => `${i.instance_name}-${i.id}`
+    let hash = i => `${i.instance_name}-${i.id || endpointId}`
     if (this.instancesDetails.map(hash).join('_') === instancesInfo.map(hash).join('_')) {
     if (this.instancesDetails.map(hash).join('_') === instancesInfo.map(hash).join('_')) {
       return Promise.resolve()
       return Promise.resolve()
     }
     }
@@ -202,11 +202,13 @@ class InstanceStore {
             resolve()
             resolve()
           }
           }
         }).catch((resp?: { reqId: number }) => {
         }).catch((resp?: { reqId: number }) => {
+          this.instancesDetailsRemaining -= 1
+          this.loadingInstancesDetails = this.instancesDetailsRemaining > 0
+
           if (!resp || resp.reqId !== this.reqId) {
           if (!resp || resp.reqId !== this.reqId) {
             return
             return
           }
           }
-          this.instancesDetailsRemaining -= 1
-          this.loadingInstancesDetails = this.instancesDetailsRemaining > 0
+
           if (count === 0) {
           if (count === 0) {
             resolve()
             resolve()
           }
           }