132 lines
4.6 KiB
TypeScript
132 lines
4.6 KiB
TypeScript
|
import * as plugins from '../plugins.js';
|
||
|
import { Cloudly } from '../classes.cloudly.js';
|
||
|
import { Cluster } from '../manager.cluster/classes.cluster.js';
|
||
|
import { ClusterNode } from './classes.clusternode.js';
|
||
|
import { CurlFresh } from './classes.curlfresh.js';
|
||
|
|
||
|
export class CloudlyNodeManager {
|
||
|
public cloudlyRef: Cloudly;
|
||
|
public typedRouter = new plugins.typedrequest.TypedRouter();
|
||
|
public curlfreshInstance = new CurlFresh(this);
|
||
|
|
||
|
public hetznerAccount: plugins.hetznercloud.HetznerAccount;
|
||
|
|
||
|
public get db() {
|
||
|
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||
|
}
|
||
|
public CClusterNode = plugins.smartdata.setDefaultManagerForDoc(this, ClusterNode);
|
||
|
|
||
|
constructor(cloudlyRefArg: Cloudly) {
|
||
|
this.cloudlyRef = cloudlyRefArg;
|
||
|
|
||
|
/**
|
||
|
* is used be serverconfig module on the node to get the actual node config
|
||
|
*/
|
||
|
this.typedRouter.addTypedHandler(
|
||
|
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetNodeConfig>(
|
||
|
'getNodeConfig',
|
||
|
async (requestData) => {
|
||
|
const nodeId = requestData.nodeId;
|
||
|
const node = await this.CClusterNode.getInstance({
|
||
|
id: nodeId,
|
||
|
});
|
||
|
return {
|
||
|
configData: await node.createSavableObject(),
|
||
|
};
|
||
|
},
|
||
|
),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
public async start() {
|
||
|
const hetznerToken = await this.cloudlyRef.settingsManager.getSetting('hetznerToken');
|
||
|
|
||
|
if (!hetznerToken) {
|
||
|
console.log('warn', 'No Hetzner token configured in settings. Hetzner features will be disabled.');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this.hetznerAccount = new plugins.hetznercloud.HetznerAccount(hetznerToken);
|
||
|
}
|
||
|
|
||
|
public async stop() {}
|
||
|
|
||
|
/**
|
||
|
* creates the node infrastructure on hetzner
|
||
|
* ensures that there are exactly the resources that are needed
|
||
|
* no more, no less
|
||
|
*/
|
||
|
public async ensureNodeInfrastructure() {
|
||
|
// get all clusters
|
||
|
const allClusters = await this.cloudlyRef.clusterManager.getAllClusters();
|
||
|
for (const cluster of allClusters) {
|
||
|
// Skip clusters that are not set up for Hetzner auto-provisioning
|
||
|
if (cluster.data.setupMode !== 'hetzner') {
|
||
|
console.log(`Skipping node provisioning for cluster ${cluster.id} - setupMode is ${cluster.data.setupMode || 'manual'}`);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// get existing nodes
|
||
|
const nodes = await this.getNodesByCluster(cluster);
|
||
|
|
||
|
// if there is no node, create one
|
||
|
if (nodes.length === 0) {
|
||
|
const hetznerServer = await this.hetznerAccount.createServer({
|
||
|
name: plugins.smartunique.uniSimple('node'),
|
||
|
location: 'nbg1',
|
||
|
type: 'cpx41',
|
||
|
labels: {
|
||
|
clusterId: cluster.id,
|
||
|
priority: '1',
|
||
|
},
|
||
|
userData: await this.curlfreshInstance.getServerUserData(),
|
||
|
});
|
||
|
|
||
|
// First create BareMetal record
|
||
|
const baremetal = await this.cloudlyRef.baremetalManager.createBaremetalFromHetznerServer(hetznerServer);
|
||
|
|
||
|
const newNode = await ClusterNode.createFromHetznerServer(hetznerServer, cluster.id, baremetal.id);
|
||
|
await baremetal.assignNode(newNode.id);
|
||
|
console.log(`cluster created new node for cluster ${cluster.id}`);
|
||
|
} else {
|
||
|
console.log(
|
||
|
`cluster ${cluster.id} already has nodes. Making sure that they actually exist in the real world...`,
|
||
|
);
|
||
|
// if there is a node, make sure that it exists
|
||
|
for (const node of nodes) {
|
||
|
const hetznerServers = await this.hetznerAccount.getServersByLabel({
|
||
|
clusterId: cluster.id,
|
||
|
});
|
||
|
if (!hetznerServers || hetznerServers.length === 0) {
|
||
|
console.log(`node ${node.id} does not exist in the real world. Creating it now...`);
|
||
|
const hetznerServer = await this.hetznerAccount.createServer({
|
||
|
name: plugins.smartunique.uniSimple('node'),
|
||
|
location: 'nbg1',
|
||
|
type: 'cpx41',
|
||
|
labels: {
|
||
|
clusterId: cluster.id,
|
||
|
priority: '1',
|
||
|
},
|
||
|
});
|
||
|
|
||
|
// First create BareMetal record
|
||
|
const baremetal = await this.cloudlyRef.baremetalManager.createBaremetalFromHetznerServer(hetznerServer);
|
||
|
|
||
|
const newNode = await ClusterNode.createFromHetznerServer(hetznerServer, cluster.id, baremetal.id);
|
||
|
await baremetal.assignNode(newNode.id);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public async getNodesByCluster(clusterArg: Cluster) {
|
||
|
const results = await this.CClusterNode.getInstances({
|
||
|
data: {
|
||
|
clusterId: clusterArg.id,
|
||
|
},
|
||
|
});
|
||
|
return results;
|
||
|
}
|
||
|
}
|