MigrationSource.ts 12 KB

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