Skip to content

Commit d2c7f39

Browse files
author
ZorTik
committed
Make power requests processing async, added power status route, options route and reboot route
1 parent b70e4c5 commit d2c7f39

12 files changed

+413
-98
lines changed

app.ts

+3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ export type AppContext = {
2121
}
2222
// TODO: Změnit hledání portu podle toho, aby nebyl zabraný jiným kontejnerem.
2323
// TODO: Clearnout zdroje vypnutých služeb při startu
24+
// TODO: Možnost změnit options pro službu
25+
// TODO: Přidat možnost bin IP adresy do options
26+
// TODO: Opravit volumes
2427
let currentContext: AppContext;
2528

2629
// App orchestration code

engine/asyncp.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const statuses = {};
2+
3+
export async function asyncServiceRun<T>(id: string, f: () => Promise<T>): Promise<T> {
4+
let e_ = null;
5+
try {
6+
statuses[id] = true;
7+
return await f();
8+
} catch (e) {
9+
e_ = e;
10+
} finally {
11+
delete statuses[id];
12+
}
13+
if (e_) {
14+
throw e_;
15+
}
16+
}
17+
18+
export function isServicePending(id: string): boolean {
19+
return statuses[id] || false;
20+
}

engine/manager.ts

+120-51
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import {Database} from "../app";
1+
import {currentContext, Database} from "../app";
22
import createEngine, {ServiceEngine} from "./engine";
33
import loadTemplate, {Template} from "./template";
44
import crypto from "crypto";
55
import {randomPort as retrieveRandomPort} from "../util/port";
66
import {loadYamlFile} from "../util/yaml";
77
import * as fs from "fs";
8+
import {PermaModel} from "../database";
9+
import {asyncServiceRun, isServicePending} from "./asyncp";
810

911
export type Options = {
1012
/**
@@ -74,13 +76,32 @@ export type ServiceManager = {
7476
* @returns Whether the service was deleted
7577
*/
7678
deleteService(id: string): Promise<boolean>;
79+
/**
80+
* Update the options of a service.
81+
*
82+
* @param id The service ID
83+
* @param options The new options
84+
*/
85+
updateOptions(id: string, options: Options): Promise<boolean>;
7786
/**
7887
* Get the template by ID.
7988
*
8089
* @param id The template ID
8190
* @returns The template wrapper
8291
*/
8392
getTemplate(id: string): Template|undefined;
93+
/**
94+
* Get the service by ID.
95+
*
96+
* @param id The service ID
97+
*/
98+
getService(id: string): Promise<PermaModel|undefined>;
99+
/**
100+
* Get the last power error of a service.
101+
*
102+
* @param id The service ID
103+
*/
104+
getLastPowerError(id: string): Error|undefined;
84105
/**
85106
* List all available services.
86107
*
@@ -95,6 +116,14 @@ export type ServiceManager = {
95116
listTemplates(): Promise<string[]>;
96117
}
97118

119+
// Utils
120+
121+
function reqNoPending(id: string) {
122+
if (isServicePending(id)) {
123+
throw new Error('Service is pending another action.');
124+
}
125+
}
126+
98127
// Implementation
99128

100129
export default async function (db: Database, appConfig: any): Promise<ServiceManager> {
@@ -112,6 +141,9 @@ export default async function (db: Database, appConfig: any): Promise<ServiceMan
112141
const settings = (template: string) => {
113142
return loadYamlFile(buildDir(template) + '/settings.yml');
114143
}
144+
// Save errors somewhere else?
145+
// Could it be a memory leak if there are tons of them??
146+
const errors = {};
115147
return { // Manager
116148
engine,
117149

@@ -129,40 +161,47 @@ export default async function (db: Database, appConfig: any): Promise<ServiceMan
129161
port_range.max as number
130162
);
131163
const serviceId = crypto.randomUUID(); // Create new unique service id
132-
// Container id
133-
const containerId = await engine.build(
134-
buildDir(template),
135-
volumeDir(serviceId),
136-
{
137-
ram: ram ?? defaults.ram as number,
138-
cpu: cpu ?? defaults.cpu as number,
139-
disk: disk ?? defaults.disk as number,
140-
env: env ?? {},
141-
port: port,
142-
ports: ports ?? []
164+
asyncServiceRun(serviceId, async () => {
165+
// Container id
166+
const containerId = await engine.build(
167+
buildDir(template),
168+
volumeDir(serviceId),
169+
{
170+
ram: ram ?? defaults.ram as number,
171+
cpu: cpu ?? defaults.cpu as number,
172+
disk: disk ?? defaults.disk as number,
173+
env: env ?? {},
174+
port: port,
175+
ports: ports ?? []
176+
}
177+
);
178+
if (!containerId) {
179+
throw new Error('Failed to create container');
143180
}
144-
);
145-
if (!containerId) {
146-
throw new Error('Failed to create container');
147-
}
148-
const rollback = async () => {
149-
await engine.delete(containerId);
150-
}
151-
const options = {ram, cpu, ports};
152-
// Save permanent info
153-
if (!await db.savePerma({ serviceId, template, nodeId, port, options, env })) {
154-
await rollback();
155-
throw new Error('Failed to save perma info to database');
156-
}
157-
// Save this session's info
158-
if (!await db.saveSession({ serviceId, nodeId, containerId })) {
159-
await rollback();
160-
throw new Error('Failed to save session info to database');
161-
}
181+
const rollback = async () => {
182+
await engine.delete(containerId);
183+
}
184+
const options = {ram, cpu, ports};
185+
// Save permanent info
186+
if (!await db.savePerma({ serviceId, template, nodeId, port, options, env })) {
187+
await rollback();
188+
throw new Error('Failed to save perma info to database');
189+
}
190+
// Save this session's info
191+
if (!await db.saveSession({ serviceId, nodeId, containerId })) {
192+
await rollback();
193+
throw new Error('Failed to save session info to database');
194+
}
195+
}).catch(e => {
196+
// Save to be later retrieved
197+
errors[serviceId] = e;
198+
currentContext.logger.error(e.message);
199+
});
162200
return serviceId;
163201
},
164202

165203
async resumeService(id) {
204+
reqNoPending(id);
166205
const perma_ = await db.getPerma(id);
167206
if (!perma_) {
168207
return false;
@@ -173,31 +212,39 @@ export default async function (db: Database, appConfig: any): Promise<ServiceMan
173212
if (!perma) {
174213
return false;
175214
}
176-
// Rebuild container using existing volume directory,
177-
// stored options and custom env variables.
178-
const containerId = await engine.build(
179-
buildDir(template),
180-
volumeDir(id),
181-
{
182-
ram: options.ram ?? defaults.ram as number,
183-
cpu: options.cpu ?? defaults.cpu as number,
184-
disk: options.disk ?? defaults.disk as number,
185-
env: env ?? defaults.env as {[key: string]: string},
186-
port: perma_.port,
187-
ports: options.ports ?? []
215+
asyncServiceRun(id, async () => {
216+
// Rebuild container using existing volume directory,
217+
// stored options and custom env variables.
218+
const containerId = await engine.build(
219+
buildDir(template),
220+
volumeDir(id),
221+
{
222+
ram: options.ram ?? defaults.ram as number,
223+
cpu: options.cpu ?? defaults.cpu as number,
224+
disk: options.disk ?? defaults.disk as number,
225+
env: env ?? defaults.env as {[key: string]: string},
226+
port: perma_.port,
227+
ports: options.ports ?? []
228+
}
229+
)
230+
// Save new session info
231+
if (await db.saveSession({ serviceId: id, nodeId, containerId })) {
232+
return true;
233+
} else {
234+
// Cleanup if err with db
235+
await engine.stop(containerId);
236+
return false;
188237
}
189-
)
190-
// Save new session info
191-
if (await db.saveSession({ serviceId: id, nodeId, containerId })) {
192-
return true;
193-
} else {
194-
// Cleanup if err with db
195-
await engine.stop(containerId);
196-
return false;
197-
}
238+
}).then(success => {
239+
if (!success) {
240+
errors[id] = new Error('Failed to resume service');
241+
}
242+
});
243+
return true;
198244
},
199245

200246
async stopService(id) {
247+
reqNoPending(id);
201248
const session = await db.getSession(id);
202249
if (!session) {
203250
return false;
@@ -213,10 +260,32 @@ export default async function (db: Database, appConfig: any): Promise<ServiceMan
213260
return db.deletePerma(id);
214261
},
215262

263+
async updateOptions(id: string, options: Options): Promise<boolean> {
264+
reqNoPending(id);
265+
const perma = await db.getPerma(id);
266+
const data: PermaModel = {
267+
...perma,
268+
...options,
269+
env: {
270+
...perma.env,
271+
...options.env
272+
}
273+
};
274+
return db.savePerma(data);
275+
},
276+
216277
getTemplate(id: string): Template|undefined {
217278
return loadTemplate(id);
218279
},
219280

281+
async getService(id: string): Promise<PermaModel | undefined> {
282+
return db.getPerma(id);
283+
},
284+
285+
getLastPowerError(id: string): Error | undefined {
286+
return errors[id];
287+
},
288+
220289
listServices(): Promise<string[]> {
221290
return db.list(nodeId);
222291
},

0 commit comments

Comments
 (0)