2
0

MigrationSource.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  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 {
  26. MigrationItem, MigrationItemOptions, MigrationItemDetails, UserScriptData,
  27. } from '../@types/MainItem'
  28. import { INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS } from '../components/organisms/WizardOptions/WizardOptions'
  29. class MigrationSourceUtils {
  30. static sortTaskUpdates(updates: ProgressUpdate[]) {
  31. if (!updates) {
  32. return
  33. }
  34. updates.sort((a, b) => {
  35. const sortNull = !a && b ? 1 : a && !b ? -1 : !a && !b ? 0 : false
  36. if (sortNull !== false) {
  37. return sortNull
  38. }
  39. return a.index - b.index
  40. })
  41. }
  42. static sortMigrations(migrations: any[]) {
  43. migrations.sort((a: any, b: any) => moment(b.created_at).diff(moment(a.created_at)))
  44. migrations.forEach((migration: { tasks: Task[] }) => {
  45. sortTasks(migration.tasks, MigrationSourceUtils.sortTaskUpdates)
  46. })
  47. }
  48. }
  49. class MigrationSource {
  50. async getMigrations(skipLog?: boolean): Promise<MigrationItem[]> {
  51. const response = await Api.send({
  52. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations`,
  53. skipLog,
  54. })
  55. const migrations = response.data.migrations
  56. MigrationSourceUtils.sortMigrations(migrations)
  57. return migrations
  58. }
  59. async getMigration(migrationId: string, skipLog?: boolean): Promise<MigrationItemDetails> {
  60. const response = await Api.send({
  61. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations/${migrationId}`,
  62. skipLog,
  63. })
  64. const migration = response.data.migration
  65. sortTasks(migration.tasks, MigrationSourceUtils.sortTaskUpdates)
  66. return migration
  67. }
  68. async recreateFullCopy(migration: MigrationItemOptions): Promise<MigrationItem> {
  69. const {
  70. origin_endpoint_id, destination_endpoint_id, destination_environment,
  71. network_map, instances, storage_mappings, notes, destination_minion_pool_id,
  72. origin_minion_pool_id, instance_osmorphing_minion_pool_mappings,
  73. } = migration
  74. const payload: any = {
  75. migration: {
  76. origin_endpoint_id,
  77. destination_endpoint_id,
  78. destination_environment,
  79. network_map,
  80. instances,
  81. storage_mappings,
  82. notes,
  83. destination_minion_pool_id,
  84. origin_minion_pool_id,
  85. instance_osmorphing_minion_pool_mappings,
  86. },
  87. }
  88. if (migration.skip_os_morphing != null) {
  89. payload.migration.skip_os_morphing = migration.skip_os_morphing
  90. }
  91. if (migration.source_environment) {
  92. payload.migration.source_environment = migration.source_environment
  93. }
  94. payload.migration.shutdown_instances = Boolean(migration.shutdown_instances)
  95. payload.migration.replication_count = migration.replication_count || 2
  96. const response = await Api.send({
  97. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations`,
  98. method: 'POST',
  99. data: payload,
  100. })
  101. return response.data.migration
  102. }
  103. async recreate(opts: {
  104. sourceEndpoint: Endpoint,
  105. destEndpoint: Endpoint,
  106. instanceNames: string[],
  107. destEnv: { [prop: string]: any } | null,
  108. updatedDestEnv: { [prop: string]: any } | null,
  109. sourceEnv?: { [prop: string]: any } | null,
  110. updatedSourceEnv?: { [prop: string]: any } | null,
  111. storageMappings?: { [prop: string]: any } | null,
  112. updatedStorageMappings: StorageMap[] | null,
  113. defaultStorage?: string | null,
  114. updatedDefaultStorage?: string | null,
  115. networkMappings?: any,
  116. updatedNetworkMappings: NetworkMap[] | null,
  117. defaultSkipOsMorphing: boolean | null,
  118. replicationCount?: number | null,
  119. migration: MigrationItemDetails,
  120. uploadedScripts: InstanceScript[]
  121. removedScripts: InstanceScript[]
  122. }): Promise<MigrationItemDetails> {
  123. const getValue = (fieldName: string): string | null => {
  124. const updatedDestEnv = opts.updatedDestEnv && opts.updatedDestEnv[fieldName]
  125. return updatedDestEnv != null ? updatedDestEnv
  126. : (opts.destEnv && opts.destEnv[fieldName])
  127. }
  128. const sourceParser = OptionsSchemaPlugin.for(opts.sourceEndpoint.type)
  129. const destParser = OptionsSchemaPlugin.for(opts.destEndpoint.type)
  130. const payload: any = {}
  131. payload.migration = {
  132. origin_endpoint_id: opts.sourceEndpoint.id,
  133. destination_endpoint_id: opts.destEndpoint.id,
  134. shutdown_instances: Boolean(opts.updatedDestEnv && opts.updatedDestEnv.shutdown_instances),
  135. replication_count: (opts.updatedDestEnv
  136. && opts.updatedDestEnv.replication_count) || opts.replicationCount || 2,
  137. instances: opts.instanceNames,
  138. notes: getValue('title') || getValue('notes') || '',
  139. }
  140. const skipOsMorphingValue = getValue('skip_os_morphing')
  141. if (skipOsMorphingValue != null) {
  142. payload.migration.skip_os_morphing = skipOsMorphingValue
  143. } else if (opts.defaultSkipOsMorphing != null) {
  144. payload.migration.skip_os_morphing = opts.defaultSkipOsMorphing
  145. }
  146. if (opts.networkMappings
  147. || (opts.updatedNetworkMappings && opts.updatedNetworkMappings.length)) {
  148. payload.migration.network_map = {
  149. ...opts.networkMappings,
  150. ...destParser.getNetworkMap(opts.updatedNetworkMappings),
  151. }
  152. }
  153. if ((opts.storageMappings && Object.keys(opts.storageMappings).length)
  154. || (opts.updatedStorageMappings && opts.updatedStorageMappings.length)) {
  155. payload.migration.storage_mappings = {
  156. ...opts.storageMappings,
  157. ...destParser
  158. .getStorageMap(opts.updatedDefaultStorage
  159. || opts.defaultStorage, opts.updatedStorageMappings),
  160. }
  161. }
  162. const { migration } = opts
  163. const sourceEnv: any = {
  164. ...opts.sourceEnv,
  165. }
  166. const updatedSourceEnv = opts.updatedSourceEnv
  167. ? sourceParser.getDestinationEnv(opts.updatedSourceEnv) : {}
  168. const sourceMinionPoolId = opts?.updatedSourceEnv?.minion_pool_id
  169. || migration.origin_minion_pool_id
  170. if (sourceMinionPoolId) {
  171. payload.migration.origin_minion_pool_id = sourceMinionPoolId
  172. }
  173. payload.migration.source_environment = {
  174. ...sourceEnv,
  175. ...updatedSourceEnv,
  176. }
  177. const destEnv: any = {
  178. ...opts.destEnv,
  179. }
  180. const updatedDestEnv = opts.updatedDestEnv
  181. ? sourceParser.getDestinationEnv(opts.updatedDestEnv) : {}
  182. const destMinionPoolId = opts?.updatedDestEnv?.minion_pool_id
  183. || migration.destination_minion_pool_id
  184. if (destMinionPoolId) {
  185. payload.migration.destination_minion_pool_id = destMinionPoolId
  186. }
  187. const updatedDestEnvMappings = updatedDestEnv[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] || {}
  188. const oldMappings = migration[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] || {}
  189. const mergedMappings = { ...oldMappings, ...updatedDestEnvMappings }
  190. if (Object.keys(mergedMappings).length) {
  191. const newMappings: any = {}
  192. Object.keys(mergedMappings).forEach(k => {
  193. if (mergedMappings[k] !== null) {
  194. newMappings[k] = mergedMappings[k]
  195. }
  196. })
  197. payload.migration[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] = newMappings
  198. }
  199. delete updatedDestEnv[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS]
  200. payload.migration.destination_environment = {
  201. ...destEnv,
  202. ...updatedDestEnv,
  203. }
  204. if (opts.uploadedScripts?.length || opts.removedScripts?.length || migration.user_scripts) {
  205. payload.migration.user_scripts = DefaultOptionsSchemaPlugin
  206. .getUserScripts(
  207. opts.uploadedScripts || [],
  208. opts.removedScripts || [],
  209. migration.user_scripts,
  210. )
  211. }
  212. const response = await Api.send({
  213. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations`,
  214. method: 'POST',
  215. data: payload,
  216. })
  217. return response.data.migration
  218. }
  219. async cancel(migrationId: string, force?: boolean | null): Promise<string> {
  220. const data: any = { cancel: null }
  221. if (force) {
  222. data.cancel = { force: true }
  223. }
  224. await Api.send({
  225. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations/${migrationId}/actions`,
  226. method: 'POST',
  227. data,
  228. })
  229. return migrationId
  230. }
  231. async delete(migrationId: string): Promise<string> {
  232. await Api.send({
  233. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations/${migrationId}`,
  234. method: 'DELETE',
  235. })
  236. return migrationId
  237. }
  238. async migrateReplica(
  239. replicaId: string,
  240. options: Field[],
  241. uploadedUserScripts: InstanceScript[],
  242. removedUserScripts: InstanceScript[],
  243. userScriptData: UserScriptData | null | undefined,
  244. minionPoolMappings: { [instance: string]: string },
  245. ): Promise<MigrationItem> {
  246. const payload: any = {
  247. migration: {
  248. replica_id: replicaId,
  249. },
  250. }
  251. options.forEach(o => {
  252. payload.migration[o.name] = o.value || o.default || false
  253. })
  254. if (uploadedUserScripts.length || removedUserScripts.length || userScriptData) {
  255. payload.migration.user_scripts = DefaultOptionsSchemaPlugin
  256. .getUserScripts(uploadedUserScripts, removedUserScripts, userScriptData)
  257. }
  258. if (Object.keys(minionPoolMappings).length) {
  259. const newMappings: any = {}
  260. Object.keys(minionPoolMappings).forEach(k => {
  261. if (minionPoolMappings[k] !== null) {
  262. newMappings[k] = minionPoolMappings[k]
  263. }
  264. })
  265. payload.migration[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] = newMappings
  266. } else {
  267. payload.migration[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] = null
  268. }
  269. const response = await Api.send({
  270. url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations`,
  271. method: 'POST',
  272. data: payload,
  273. })
  274. return response.data.migration
  275. }
  276. }
  277. export default new MigrationSource()