@kapeta/local-cluster-service 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/definitions.d.ts +7 -0
  3. package/dist/cjs/src/config/routes.js +1 -1
  4. package/dist/cjs/src/containerManager.d.ts +2 -1
  5. package/dist/cjs/src/containerManager.js +125 -21
  6. package/dist/cjs/src/definitionsManager.d.ts +1 -0
  7. package/dist/cjs/src/definitionsManager.js +7 -4
  8. package/dist/cjs/src/instanceManager.d.ts +12 -2
  9. package/dist/cjs/src/instanceManager.js +253 -200
  10. package/dist/cjs/src/operatorManager.d.ts +2 -0
  11. package/dist/cjs/src/operatorManager.js +69 -67
  12. package/dist/cjs/src/socketManager.d.ts +1 -0
  13. package/dist/cjs/src/socketManager.js +3 -0
  14. package/dist/cjs/src/types.d.ts +1 -0
  15. package/dist/cjs/src/utils/BlockInstanceRunner.js +2 -2
  16. package/dist/esm/src/config/routes.js +1 -1
  17. package/dist/esm/src/containerManager.d.ts +2 -1
  18. package/dist/esm/src/containerManager.js +126 -22
  19. package/dist/esm/src/definitionsManager.d.ts +1 -0
  20. package/dist/esm/src/definitionsManager.js +8 -5
  21. package/dist/esm/src/instanceManager.d.ts +12 -2
  22. package/dist/esm/src/instanceManager.js +253 -200
  23. package/dist/esm/src/operatorManager.d.ts +2 -0
  24. package/dist/esm/src/operatorManager.js +67 -65
  25. package/dist/esm/src/socketManager.d.ts +1 -0
  26. package/dist/esm/src/socketManager.js +3 -0
  27. package/dist/esm/src/types.d.ts +1 -0
  28. package/dist/esm/src/utils/BlockInstanceRunner.js +2 -2
  29. package/dist/esm/src/utils/utils.js +1 -1
  30. package/package.json +3 -1
  31. package/src/config/routes.ts +1 -1
  32. package/src/containerManager.ts +178 -43
  33. package/src/definitionsManager.ts +9 -5
  34. package/src/instanceManager.ts +288 -228
  35. package/src/instances/routes.ts +1 -1
  36. package/src/operatorManager.ts +72 -70
  37. package/src/socketManager.ts +4 -0
  38. package/src/types.ts +1 -1
  39. package/src/utils/BlockInstanceRunner.ts +12 -22
  40. package/src/utils/utils.ts +2 -2
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.operatorManager = void 0;
6
+ exports.operatorManager = exports.KIND_OPERATOR = void 0;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const md5_1 = __importDefault(require("md5"));
9
9
  const nodejs_utils_1 = require("@kapeta/nodejs-utils");
@@ -14,7 +14,8 @@ const fs_extra_1 = __importDefault(require("fs-extra"));
14
14
  const definitionsManager_1 = require("./definitionsManager");
15
15
  const utils_1 = require("./utils/utils");
16
16
  const lodash_1 = __importDefault(require("lodash"));
17
- const KIND_OPERATOR = 'core/resource-type-operator';
17
+ const async_lock_1 = __importDefault(require("async-lock"));
18
+ exports.KIND_OPERATOR = 'core/resource-type-operator';
18
19
  class Operator {
19
20
  _data;
20
21
  constructor(data) {
@@ -29,6 +30,7 @@ class Operator {
29
30
  }
30
31
  class OperatorManager {
31
32
  _mountDir;
33
+ operatorLock = new async_lock_1.default();
32
34
  constructor() {
33
35
  this._mountDir = path_1.default.join(storageService_1.storageService.getKapetaBasedir(), 'mounts');
34
36
  fs_extra_1.default.mkdirpSync(this._mountDir);
@@ -44,7 +46,7 @@ class OperatorManager {
44
46
  * @return {Operator}
45
47
  */
46
48
  getOperator(resourceType, version) {
47
- const operators = definitionsManager_1.definitionsManager.getDefinitions(KIND_OPERATOR);
49
+ const operators = definitionsManager_1.definitionsManager.getDefinitions(exports.KIND_OPERATOR);
48
50
  const operator = operators.find((operator) => operator.definition &&
49
51
  operator.definition.metadata &&
50
52
  operator.definition.metadata.name &&
@@ -113,74 +115,74 @@ class OperatorManager {
113
115
  * @return {Promise<ContainerInfo>}
114
116
  */
115
117
  async ensureResource(systemId, resourceType, version) {
116
- const operator = this.getOperator(resourceType, version);
117
- const operatorData = operator.getData();
118
- const portTypes = Object.keys(operatorData.ports);
119
- portTypes.sort();
120
- const ports = {};
121
- for (let i = 0; i < portTypes.length; i++) {
122
- const portType = portTypes[i];
123
- let containerPortInfo = operatorData.ports[portType];
124
- const hostPort = await serviceManager_1.serviceManager.ensureServicePort(systemId, resourceType, portType);
125
- if (typeof containerPortInfo === 'number' || typeof containerPortInfo === 'string') {
126
- containerPortInfo = { port: containerPortInfo, type: 'tcp' };
127
- }
128
- if (!containerPortInfo.type) {
129
- containerPortInfo.type = 'tcp';
118
+ systemId = (0, utils_1.normalizeKapetaUri)(systemId);
119
+ const key = `${systemId}#${resourceType}:${version}`;
120
+ return await this.operatorLock.acquire(key, async () => {
121
+ const operator = this.getOperator(resourceType, version);
122
+ const operatorData = operator.getData();
123
+ const portTypes = Object.keys(operatorData.ports);
124
+ portTypes.sort();
125
+ const ports = {};
126
+ for (let i = 0; i < portTypes.length; i++) {
127
+ const portType = portTypes[i];
128
+ let containerPortInfo = operatorData.ports[portType];
129
+ const hostPort = await serviceManager_1.serviceManager.ensureServicePort(systemId, resourceType, portType);
130
+ if (typeof containerPortInfo === 'number' || typeof containerPortInfo === 'string') {
131
+ containerPortInfo = { port: containerPortInfo, type: 'tcp' };
132
+ }
133
+ if (!containerPortInfo.type) {
134
+ containerPortInfo.type = 'tcp';
135
+ }
136
+ const portId = containerPortInfo.port + '/' + containerPortInfo.type;
137
+ ports[portId] = {
138
+ type: portType,
139
+ hostPort,
140
+ };
130
141
  }
131
- const portId = containerPortInfo.port + '/' + containerPortInfo.type;
132
- ports[portId] = {
133
- type: portType,
134
- hostPort,
142
+ const mounts = await containerManager_1.containerManager.createMounts(systemId, resourceType, operatorData.mounts);
143
+ const nameParts = [systemId, resourceType.toLowerCase(), version];
144
+ const containerName = `kapeta-resource-${(0, md5_1.default)(nameParts.join('_'))}`;
145
+ const PortBindings = {};
146
+ const Env = [];
147
+ const Labels = {
148
+ kapeta: 'true',
135
149
  };
136
- }
137
- const mounts = await containerManager_1.containerManager.createMounts(systemId, resourceType, operatorData.mounts);
138
- const nameParts = [
139
- systemId,
140
- resourceType.toLowerCase(),
141
- version
142
- ];
143
- const containerName = `kapeta-resource-${(0, md5_1.default)(nameParts.join('_'))}`;
144
- const PortBindings = {};
145
- const Env = [];
146
- const Labels = {
147
- kapeta: 'true',
148
- };
149
- const bindHost = (0, utils_1.getBindHost)();
150
- const ExposedPorts = {};
151
- lodash_1.default.forEach(ports, (portInfo, containerPort) => {
152
- ExposedPorts['' + containerPort] = {};
153
- PortBindings['' + containerPort] = [
154
- {
155
- HostPort: '' + portInfo.hostPort,
156
- HostIp: bindHost,
150
+ const bindHost = (0, utils_1.getBindHost)();
151
+ const ExposedPorts = {};
152
+ lodash_1.default.forEach(ports, (portInfo, containerPort) => {
153
+ ExposedPorts['' + containerPort] = {};
154
+ PortBindings['' + containerPort] = [
155
+ {
156
+ HostPort: '' + portInfo.hostPort,
157
+ HostIp: bindHost,
158
+ },
159
+ ];
160
+ Labels[containerManager_1.CONTAINER_LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
161
+ });
162
+ const Mounts = containerManager_1.containerManager.toDockerMounts(mounts);
163
+ lodash_1.default.forEach(operatorData.env, (value, name) => {
164
+ Env.push(name + '=' + value);
165
+ });
166
+ let HealthCheck = undefined;
167
+ if (operatorData.health) {
168
+ HealthCheck = containerManager_1.containerManager.toDockerHealth(operatorData.health);
169
+ }
170
+ const container = await containerManager_1.containerManager.ensureContainer({
171
+ name: containerName,
172
+ Image: operatorData.image,
173
+ Hostname: containerName + '.kapeta',
174
+ Labels,
175
+ Cmd: operatorData.cmd,
176
+ ExposedPorts,
177
+ Env,
178
+ HealthCheck,
179
+ HostConfig: {
180
+ PortBindings,
181
+ Mounts,
157
182
  },
158
- ];
159
- Labels[containerManager_1.CONTAINER_LABEL_PORT_PREFIX + portInfo.hostPort] = portInfo.type;
160
- });
161
- const Mounts = containerManager_1.containerManager.toDockerMounts(mounts);
162
- lodash_1.default.forEach(operatorData.env, (value, name) => {
163
- Env.push(name + '=' + value);
164
- });
165
- let HealthCheck = undefined;
166
- if (operatorData.health) {
167
- HealthCheck = containerManager_1.containerManager.toDockerHealth(operatorData.health);
168
- }
169
- const container = await containerManager_1.containerManager.ensureContainer({
170
- name: containerName,
171
- Image: operatorData.image,
172
- Hostname: containerName + '.kapeta',
173
- Labels,
174
- Cmd: operatorData.cmd,
175
- ExposedPorts,
176
- Env,
177
- HealthCheck,
178
- HostConfig: {
179
- PortBindings,
180
- Mounts,
181
- },
183
+ });
184
+ return new containerManager_1.ContainerInfo(container);
182
185
  });
183
- return new containerManager_1.ContainerInfo(container);
184
186
  }
185
187
  }
186
188
  exports.operatorManager = new OperatorManager();
@@ -7,6 +7,7 @@ export declare class SocketManager {
7
7
  isAlive(): boolean;
8
8
  private get io();
9
9
  emit(context: string, type: string, payload: any): void;
10
+ emitGlobal(type: string, payload: any): void;
10
11
  _bindIO(): void;
11
12
  _handleSocketCreated(socket: Socket): void;
12
13
  _bindSocket(socket: Socket): void;
@@ -29,6 +29,9 @@ class SocketManager {
29
29
  emit(context, type, payload) {
30
30
  this.io.to(context).emit(type, { context, payload });
31
31
  }
32
+ emitGlobal(type, payload) {
33
+ this.io.emit(type, { payload });
34
+ }
32
35
  _bindIO() {
33
36
  this.io.on('connection', (socket) => this._handleSocketCreated(socket));
34
37
  }
@@ -58,6 +58,7 @@ export type InstanceInfo = {
58
58
  type: InstanceType;
59
59
  owner: InstanceOwner;
60
60
  status: InstanceStatus;
61
+ errorMessage?: string;
61
62
  desiredStatus: DesiredInstanceStatus;
62
63
  address?: string;
63
64
  startedAt?: number;
@@ -200,7 +200,7 @@ class BlockInstanceRunner {
200
200
  `KAPETA_LOCAL_CLUSTER_PORT=${clusterService_1.clusterService.getClusterServicePort()}`,
201
201
  ...Object.entries({
202
202
  ...env,
203
- ...addonEnv
203
+ ...addonEnv,
204
204
  }).map(([key, value]) => `${key}=${value}`),
205
205
  ],
206
206
  HostConfig: {
@@ -330,7 +330,7 @@ class BlockInstanceRunner {
330
330
  async _handleContainer(container) {
331
331
  return {
332
332
  type: types_1.InstanceType.DOCKER,
333
- pid: container.id
333
+ pid: container.id,
334
334
  };
335
335
  }
336
336
  }
@@ -32,7 +32,7 @@ router.put('/instance', async (req, res) => {
32
32
  if (req.kapeta.instanceId) {
33
33
  configManager.setConfigForSection(req.kapeta.systemId, req.kapeta.instanceId, config);
34
34
  //Restart the instance if it is running after config change
35
- await instanceManager.restart(req.kapeta.systemId, req.kapeta.instanceId);
35
+ await instanceManager.prepareForRestart(req.kapeta.systemId, req.kapeta.instanceId);
36
36
  }
37
37
  else {
38
38
  configManager.setConfigForSystem(req.kapeta.systemId, config);
@@ -1,6 +1,6 @@
1
1
  import { Docker } from 'node-docker-api';
2
2
  import { Container } from 'node-docker-api/lib/container';
3
- import { InstanceInfo, LogEntry } from "./types";
3
+ import { InstanceInfo, LogEntry } from './types';
4
4
  type StringMap = {
5
5
  [key: string]: string;
6
6
  };
@@ -49,6 +49,7 @@ declare class ContainerManager {
49
49
  private _alive;
50
50
  private _mountDir;
51
51
  private _version;
52
+ private _lastDockerAccessCheck;
52
53
  constructor();
53
54
  initialize(): Promise<void>;
54
55
  checkAlive(): Promise<boolean>;
@@ -8,7 +8,10 @@ import { parseKapetaUri } from '@kapeta/nodejs-utils';
8
8
  import ClusterConfiguration from '@kapeta/local-cluster-config';
9
9
  import uuid from 'node-uuid';
10
10
  import md5 from 'md5';
11
- import { getBlockInstanceContainerName } from "./utils/utils";
11
+ import { getBlockInstanceContainerName } from './utils/utils';
12
+ import { socketManager } from './socketManager';
13
+ import { KapetaAPI } from '@kapeta/nodejs-api-client';
14
+ const EVENT_IMAGE_PULL = 'docker-image-pull';
12
15
  export const CONTAINER_LABEL_PORT_PREFIX = 'kapeta_port-';
13
16
  const NANO_SECOND = 1000000;
14
17
  const HEALTH_CHECK_INTERVAL = 3000;
@@ -26,6 +29,7 @@ class ContainerManager {
26
29
  _alive;
27
30
  _mountDir;
28
31
  _version;
32
+ _lastDockerAccessCheck = 0;
29
33
  constructor() {
30
34
  this._docker = null;
31
35
  this._alive = false;
@@ -160,17 +164,121 @@ class ContainerManager {
160
164
  console.log('Image found: %s', image);
161
165
  return false;
162
166
  }
163
- console.log('Pulling image: %s', image);
164
- const stream = await this.docker()
165
- .image.create({}, {
167
+ const timeStarted = Date.now();
168
+ socketManager.emitGlobal(EVENT_IMAGE_PULL, { image, percent: 0 });
169
+ const api = new KapetaAPI();
170
+ const accessToken = await api.getAccessToken();
171
+ const auth = image.startsWith('docker.kapeta.com/')
172
+ ? {
173
+ username: 'kapeta',
174
+ password: accessToken,
175
+ serveraddress: 'docker.kapeta.com',
176
+ }
177
+ : {};
178
+ const stream = (await this.docker().image.create(auth, {
166
179
  fromImage: imageName,
167
180
  tag: tag,
168
- });
169
- await promisifyStream(stream, (chunk) => {
170
- console.log('Data from docker: "%s"', chunk.toString());
181
+ }));
182
+ const chunks = {};
183
+ let lastEmitted = Date.now();
184
+ await promisifyStream(stream, (rawData) => {
185
+ const lines = rawData.toString().trim().split('\n');
186
+ lines.forEach((line) => {
187
+ const data = JSON.parse(line);
188
+ if (![
189
+ 'Waiting',
190
+ 'Downloading',
191
+ 'Extracting',
192
+ 'Download complete',
193
+ 'Pull complete',
194
+ 'Already exists',
195
+ ].includes(data.status)) {
196
+ return;
197
+ }
198
+ if (!chunks[data.id]) {
199
+ chunks[data.id] = {
200
+ downloading: {
201
+ total: 0,
202
+ current: 0,
203
+ },
204
+ extracting: {
205
+ total: 0,
206
+ current: 0,
207
+ },
208
+ done: false,
209
+ };
210
+ }
211
+ const chunk = chunks[data.id];
212
+ switch (data.status) {
213
+ case 'Downloading':
214
+ chunk.downloading = data.progressDetail;
215
+ break;
216
+ case 'Extracting':
217
+ chunk.extracting = data.progressDetail;
218
+ break;
219
+ case 'Download complete':
220
+ chunk.downloading.current = chunks[data.id].downloading.total;
221
+ break;
222
+ case 'Pull complete':
223
+ chunk.extracting.current = chunks[data.id].extracting.total;
224
+ chunk.done = true;
225
+ break;
226
+ case 'Already exists':
227
+ // Force layer to be done
228
+ chunk.downloading.current = 1;
229
+ chunk.downloading.total = 1;
230
+ chunk.extracting.current = 1;
231
+ chunk.extracting.total = 1;
232
+ chunk.done = true;
233
+ break;
234
+ }
235
+ });
236
+ if (Date.now() - lastEmitted < 1000) {
237
+ return;
238
+ }
239
+ const chunkList = Object.values(chunks);
240
+ let totals = {
241
+ downloading: {
242
+ total: 0,
243
+ current: 0,
244
+ },
245
+ extracting: {
246
+ total: 0,
247
+ current: 0,
248
+ },
249
+ total: chunkList.length,
250
+ done: 0,
251
+ };
252
+ chunkList.forEach((chunk) => {
253
+ if (chunk.downloading.current > 0) {
254
+ totals.downloading.current += chunk.downloading.current;
255
+ }
256
+ if (chunk.downloading.total > 0) {
257
+ totals.downloading.total += chunk.downloading.total;
258
+ }
259
+ if (chunk.extracting.current > 0) {
260
+ totals.extracting.current += chunk.extracting.current;
261
+ }
262
+ if (chunk.extracting.total > 0) {
263
+ totals.extracting.total += chunk.extracting.total;
264
+ }
265
+ if (chunk.done) {
266
+ totals.done++;
267
+ }
268
+ });
269
+ const percent = totals.total > 0 ? (totals.done / totals.total) * 100 : 0;
270
+ //We emit at most every second to not spam the client
271
+ socketManager.emitGlobal(EVENT_IMAGE_PULL, {
272
+ image,
273
+ percent,
274
+ status: totals,
275
+ timeTaken: Date.now() - timeStarted,
276
+ });
277
+ lastEmitted = Date.now();
278
+ //console.log('Pulling image %s: %s % [done: %s, total: %s]', image, Math.round(percent), totals.done, totals.total);
171
279
  });
172
280
  IMAGE_PULL_CACHE[image] = Date.now();
173
- console.log('Image pulled: %s', image);
281
+ socketManager.emitGlobal(EVENT_IMAGE_PULL, { image, percent: 100, timeTaken: Date.now() - timeStarted });
174
282
  return true;
175
283
  }
176
284
  toDockerMounts(mounts) {
@@ -210,13 +318,7 @@ class ContainerManager {
210
318
  return container;
211
319
  }
212
320
  async createOrUpdateContainer(opts) {
213
- let imagePulled = false;
214
- try {
215
- imagePulled = await this.pull(opts.Image);
216
- }
217
- catch (e) {
218
- console.warn('Failed to pull image. Continuing...', e);
219
- }
321
+ let imagePulled = await this.pull(opts.Image);
220
322
  this.applyHash(opts);
221
323
  if (!opts.name) {
222
324
  console.log('Starting unnamed container: %s', opts.Image);
@@ -341,12 +443,14 @@ class ContainerManager {
341
443
  const containerName = getBlockInstanceContainerName(instance.systemId, instance.instanceId);
342
444
  const containerInfo = await this.getContainerByName(containerName);
343
445
  if (!containerInfo) {
344
- return [{
345
- source: "stdout",
346
- level: "ERROR",
446
+ return [
447
+ {
448
+ source: 'stdout',
449
+ level: 'ERROR',
347
450
  time: Date.now(),
348
- message: "Container not found"
349
- }];
451
+ message: 'Container not found',
452
+ },
453
+ ];
350
454
  }
351
455
  return containerInfo.getLogs();
352
456
  }
@@ -434,13 +538,13 @@ export class ContainerInfo {
434
538
  return ports;
435
539
  }
436
540
  async getLogs() {
437
- const logStream = await this.native.logs({
541
+ const logStream = (await this.native.logs({
438
542
  stdout: true,
439
543
  stderr: true,
440
544
  follow: false,
441
545
  tail: 100,
442
546
  timestamps: true,
443
- });
547
+ }));
444
548
  const out = [];
445
549
  await promisifyStream(logStream, (data) => {
446
550
  const buf = data;
@@ -7,6 +7,7 @@ declare class DefinitionsManager {
7
7
  getDefinitions(kindFilter?: string | string[]): DefinitionInfo[];
8
8
  exists(ref: string): boolean;
9
9
  getProviderDefinitions(): DefinitionInfo[];
10
+ getDefinition(ref: string): DefinitionInfo | undefined;
10
11
  }
11
12
  export declare const definitionsManager: DefinitionsManager;
12
13
  export {};
@@ -1,5 +1,5 @@
1
1
  import ClusterConfiguration from '@kapeta/local-cluster-config';
2
- import { parseKapetaUri } from "@kapeta/nodejs-utils";
2
+ import { parseKapetaUri } from '@kapeta/nodejs-utils';
3
3
  const CACHE_TTL = 60 * 1000; // 1 min
4
4
  class DefinitionsManager {
5
5
  cache = {};
@@ -33,13 +33,16 @@ class DefinitionsManager {
33
33
  return this.doCached(key, () => ClusterConfiguration.getDefinitions(kindFilter));
34
34
  }
35
35
  exists(ref) {
36
- const uri = parseKapetaUri(ref);
37
- return !!this.getDefinitions().find((d) => {
38
- return parseKapetaUri(`${d.definition.metadata.name}:${d.version}`).id === uri.id;
39
- });
36
+ return !!this.getDefinition(ref);
40
37
  }
41
38
  getProviderDefinitions() {
42
39
  return this.doCached('providers', () => ClusterConfiguration.getProviderDefinitions());
43
40
  }
41
+ getDefinition(ref) {
42
+ const uri = parseKapetaUri(ref);
43
+ return this.getDefinitions().find((d) => {
44
+ return parseKapetaUri(`${d.definition.metadata.name}:${d.version}`).id === uri.id;
45
+ });
46
+ }
44
47
  }
45
48
  export const definitionsManager = new DefinitionsManager();
@@ -2,11 +2,13 @@ import { InstanceInfo, LogEntry } from './types';
2
2
  export declare class InstanceManager {
3
3
  private _interval;
4
4
  private readonly _instances;
5
+ private readonly instanceLocks;
5
6
  constructor();
6
7
  private checkInstancesLater;
7
8
  getInstances(): InstanceInfo[];
8
9
  getInstancesForPlan(systemId: string): InstanceInfo[];
9
10
  getInstance(systemId: string, instanceId: string): InstanceInfo | undefined;
11
+ private exclusive;
10
12
  getLogs(systemId: string, instanceId: string): Promise<LogEntry[]>;
11
13
  saveInternalInstance(instance: InstanceInfo): Promise<InstanceInfo>;
12
14
  /**
@@ -15,12 +17,20 @@ export declare class InstanceManager {
15
17
  */
16
18
  registerInstanceFromSDK(systemId: string, instanceId: string, info: Omit<InstanceInfo, 'systemId' | 'instanceId'>): Promise<InstanceInfo | undefined>;
17
19
  private getHealthUrl;
18
- markAsStopped(systemId: string, instanceId: string): void;
20
+ markAsStopped(systemId: string, instanceId: string): Promise<void>;
19
21
  startAllForPlan(systemId: string): Promise<InstanceInfo[]>;
20
22
  stop(systemId: string, instanceId: string): Promise<void>;
23
+ private stopInner;
21
24
  stopAllForPlan(systemId: string): Promise<void>;
22
25
  start(systemId: string, instanceId: string): Promise<InstanceInfo>;
23
- restart(systemId: string, instanceId: string): Promise<InstanceInfo>;
26
+ /**
27
+ * Stops an instance but does not remove it from the list of active instances
28
+ *
29
+ * It will be started again next time the system checks the status of the instance
30
+ *
31
+ * We do it this way to not cause the user to wait for the instance to start again
32
+ */
33
+ prepareForRestart(systemId: string, instanceId: string): Promise<void>;
24
34
  stopAll(): Promise<void>;
25
35
  private stopInstances;
26
36
  private save;