MigrationSource.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. /*
  2. Copyright (C) 2017 Cloudbase Solutions SRL
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. import moment from 'moment'
  15. import { OptionsSchemaPlugin } from '../plugins/endpoint'
  16. import DefaultOptionsSchemaPlugin from '../plugins/endpoint/default/OptionsSchemaPlugin'
  17. import { sortTasks } from './ReplicaSource'
  18. import Api from '../utils/ApiCaller'
  19. import type { InstanceScript } from '../@types/Instance'
  20. import type { Field } from '../@types/Field'
  21. import type { NetworkMap } from '../@types/Network'
  22. import type { Endpoint, StorageMap } from '../@types/Endpoint'
  23. import configLoader from '../utils/Config'
  24. import { ProgressUpdate, Task } from '../@types/Task'
  25. import { MigrationItem, MigrationItemOptions, MigrationItemDetails } from '../@types/MainItem'
  26. import { INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS } from '../components/organisms/WizardOptions/WizardOptions'
  27. class MigrationSourceUtils {
  28. static sortTaskUpdates(updates: ProgressUpdate[]) {
  29. if (!updates) {
  30. return
  31. }
  32. updates.sort((a, b) => {
  33. const sortNull = !a && b ? 1 : a && !b ? -1 : !a && !b ? 0 : false
  34. if (sortNull !== false) {
  35. return sortNull
  36. }
  37. return a.index - b.index
  38. })
  39. }
  40. static sortMigrations(migrations: any[]) {
  41. migrations.sort((a: any, b: any) => moment(b.created_at).diff(moment(a.created_at)))
  42. migrations.forEach((migration: { tasks: Task[] }) => {
  43. sortTasks(migration.tasks, MigrationSourceUtils.sortTaskUpdates)
  44. })
  45. }
  46. }
  47. class MigrationSource {
  48. async getMigrations(skipLog?: boolean): Promise<MigrationItem[]> {
  49. const response = await Api.send({
  50. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations`,
  51. skipLog,
  52. })
  53. const migrations = response.data.migrations
  54. MigrationSourceUtils.sortMigrations(migrations)
  55. return migrations
  56. }
  57. async getMigration(migrationId: string, skipLog?: boolean): Promise<MigrationItemDetails> {
  58. const response = await Api.send({
  59. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations/${migrationId}`,
  60. skipLog,
  61. })
  62. const migration = response.data.migration
  63. sortTasks(migration.tasks, MigrationSourceUtils.sortTaskUpdates)
  64. return migration
  65. }
  66. async recreateFullCopy(migration: MigrationItemOptions): Promise<MigrationItem> {
  67. const {
  68. origin_endpoint_id, destination_endpoint_id, destination_environment,
  69. network_map, instances, storage_mappings, notes, destination_minion_pool_id,
  70. origin_minion_pool_id, instance_osmorphing_minion_pool_mappings,
  71. } = migration
  72. const payload: any = {
  73. migration: {
  74. origin_endpoint_id,
  75. destination_endpoint_id,
  76. destination_environment,
  77. network_map,
  78. instances,
  79. storage_mappings,
  80. notes,
  81. destination_minion_pool_id,
  82. origin_minion_pool_id,
  83. instance_osmorphing_minion_pool_mappings,
  84. },
  85. }
  86. if (migration.skip_os_morphing != null) {
  87. payload.migration.skip_os_morphing = migration.skip_os_morphing
  88. }
  89. if (migration.source_environment) {
  90. payload.migration.source_environment = migration.source_environment
  91. }
  92. payload.migration.shutdown_instances = Boolean(migration.shutdown_instances)
  93. payload.migration.replication_count = migration.replication_count || 2
  94. const response = await Api.send({
  95. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations`,
  96. method: 'POST',
  97. data: payload,
  98. })
  99. return response.data.migration
  100. }
  101. async recreate(opts: {
  102. sourceEndpoint: Endpoint,
  103. destEndpoint: Endpoint,
  104. instanceNames: string[],
  105. destEnv: { [prop: string]: any } | null,
  106. updatedDestEnv: { [prop: string]: any } | null,
  107. sourceEnv?: { [prop: string]: any } | null,
  108. updatedSourceEnv?: { [prop: string]: any } | null,
  109. storageMappings?: { [prop: string]: any } | null,
  110. updatedStorageMappings: StorageMap[] | null,
  111. defaultStorage?: string | null,
  112. updatedDefaultStorage?: string | null,
  113. networkMappings?: any,
  114. updatedNetworkMappings: NetworkMap[] | null,
  115. defaultSkipOsMorphing: boolean | null,
  116. replicationCount?: number | null,
  117. migration: MigrationItemDetails
  118. }): Promise<MigrationItemDetails> {
  119. const getValue = (fieldName: string): string | null => {
  120. const updatedDestEnv = opts.updatedDestEnv && opts.updatedDestEnv[fieldName]
  121. return updatedDestEnv != null ? updatedDestEnv
  122. : (opts.destEnv && opts.destEnv[fieldName])
  123. }
  124. const sourceParser = OptionsSchemaPlugin.for(opts.sourceEndpoint.type)
  125. const destParser = OptionsSchemaPlugin.for(opts.destEndpoint.type)
  126. const payload: any = {}
  127. payload.migration = {
  128. origin_endpoint_id: opts.sourceEndpoint.id,
  129. destination_endpoint_id: opts.destEndpoint.id,
  130. shutdown_instances: Boolean(opts.updatedDestEnv && opts.updatedDestEnv.shutdown_instances),
  131. replication_count: (opts.updatedDestEnv
  132. && opts.updatedDestEnv.replication_count) || opts.replicationCount || 2,
  133. instances: opts.instanceNames,
  134. notes: getValue('description') || '',
  135. }
  136. const skipOsMorphingValue = getValue('skip_os_morphing')
  137. if (skipOsMorphingValue != null) {
  138. payload.migration.skip_os_morphing = skipOsMorphingValue
  139. } else if (opts.defaultSkipOsMorphing != null) {
  140. payload.migration.skip_os_morphing = opts.defaultSkipOsMorphing
  141. }
  142. if (opts.networkMappings
  143. || (opts.updatedNetworkMappings && opts.updatedNetworkMappings.length)) {
  144. payload.migration.network_map = {
  145. ...opts.networkMappings,
  146. ...destParser.getNetworkMap(opts.updatedNetworkMappings),
  147. }
  148. }
  149. if ((opts.storageMappings && Object.keys(opts.storageMappings).length)
  150. || (opts.updatedStorageMappings && opts.updatedStorageMappings.length)) {
  151. payload.migration.storage_mappings = {
  152. ...opts.storageMappings,
  153. ...destParser
  154. .getStorageMap(opts.updatedDefaultStorage
  155. || opts.defaultStorage, opts.updatedStorageMappings),
  156. }
  157. }
  158. const { migration } = opts
  159. const sourceEnv: any = {
  160. ...opts.sourceEnv,
  161. }
  162. const updatedSourceEnv = opts.updatedSourceEnv
  163. ? sourceParser.getDestinationEnv(opts.updatedSourceEnv) : {}
  164. const sourceMinionPoolId = updatedSourceEnv.minion_pool_id || migration.origin_minion_pool_id
  165. if (sourceMinionPoolId) {
  166. payload.migration.origin_minion_pool_id = sourceMinionPoolId
  167. }
  168. delete updatedSourceEnv.minion_pool_id
  169. payload.migration.source_environment = {
  170. ...sourceEnv,
  171. ...updatedSourceEnv,
  172. }
  173. const destEnv: any = {
  174. ...opts.destEnv,
  175. }
  176. const updatedDestEnv = opts.updatedDestEnv
  177. ? sourceParser.getDestinationEnv(opts.updatedDestEnv) : {}
  178. const destMinionPoolId = updatedDestEnv.minion_pool_id || migration.destination_minion_pool_id
  179. if (destMinionPoolId) {
  180. payload.migration.destination_minion_pool_id = destMinionPoolId
  181. }
  182. delete updatedDestEnv.minion_pool_id
  183. const updatedDestEnvMappings = updatedDestEnv[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] || {}
  184. const oldMappings = migration[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] || {}
  185. const mergedMappings = { ...oldMappings, ...updatedDestEnvMappings }
  186. if (Object.keys(mergedMappings).length) {
  187. const newMappings: any = {}
  188. Object.keys(mergedMappings).forEach(k => {
  189. if (mergedMappings[k] !== null) {
  190. newMappings[k] = mergedMappings[k]
  191. }
  192. })
  193. payload.migration[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] = newMappings
  194. }
  195. delete updatedDestEnv[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS]
  196. payload.migration.destination_environment = {
  197. ...destEnv,
  198. ...updatedDestEnv,
  199. }
  200. const response = await Api.send({
  201. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations`,
  202. method: 'POST',
  203. data: payload,
  204. })
  205. return response.data.migration
  206. }
  207. async cancel(migrationId: string, force?: boolean | null): Promise<string> {
  208. const data: any = { cancel: null }
  209. if (force) {
  210. data.cancel = { force: true }
  211. }
  212. await Api.send({
  213. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations/${migrationId}/actions`,
  214. method: 'POST',
  215. data,
  216. })
  217. return migrationId
  218. }
  219. async delete(migrationId: string): Promise<string> {
  220. await Api.send({
  221. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations/${migrationId}`,
  222. method: 'DELETE',
  223. })
  224. return migrationId
  225. }
  226. async migrateReplica(
  227. replicaId: string,
  228. options: Field[],
  229. userScripts: InstanceScript[],
  230. minionPoolMappings: { [instance: string]: string },
  231. ): Promise<MigrationItem> {
  232. const payload: any = {
  233. migration: {
  234. replica_id: replicaId,
  235. },
  236. }
  237. options.forEach(o => {
  238. payload.migration[o.name] = o.value || o.default || false
  239. })
  240. if (userScripts.length) {
  241. payload.migration.user_scripts = DefaultOptionsSchemaPlugin.getUserScripts(userScripts)
  242. }
  243. if (Object.keys(minionPoolMappings).length) {
  244. const newMappings: any = {}
  245. Object.keys(minionPoolMappings).forEach(k => {
  246. if (minionPoolMappings[k] !== null) {
  247. newMappings[k] = minionPoolMappings[k]
  248. }
  249. })
  250. payload.migration[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] = newMappings
  251. } else {
  252. payload.migration[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] = null
  253. }
  254. const response = await Api.send({
  255. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations`,
  256. method: 'POST',
  257. data: payload,
  258. })
  259. return response.data.migration
  260. }
  261. }
  262. export default new MigrationSource()