ui/app/models/node.js

349 lines
8.9 KiB
JavaScript

import { computed, get, set } from '@ember/object';
import { or, alias } from '@ember/object/computed';
import Resource from '@rancher/ember-api-store/models/resource';
import { download } from 'shared/utils/util';
import C from 'ui/utils/constants';
import StateCounts from 'ui/mixins/state-counts';
import { inject as service } from '@ember/service';
import { hasMany, reference } from '@rancher/ember-api-store/utils/denormalize';
import ResourceUsage from 'shared/mixins/resource-usage';
import Grafana from 'shared/mixins/grafana';
import { next } from '@ember/runloop';
const UNSCHEDULABLE_KEYS = ['node-role.kubernetes.io/etcd', 'node-role.kubernetes.io/controlplane'];
const UNSCHEDULABLE_EFFECTS = ['NoExecute', 'NoSchedule'];
const CONTAINERD = 'containerd://';
var Node = Resource.extend(Grafana, StateCounts, ResourceUsage, {
modalService: service('modal'),
settings: service(),
prefs: service(),
router: service(),
globalStore: service(),
clusterStore: service(),
intl: service(),
nodes: hasMany('clusterId', 'node', 'clusterId'),
type: 'node',
containerD: CONTAINERD,
isContainerD: false,
grafanaDashboardName: 'Nodes',
grafanaResourceId: alias('ipAddress'),
cluster: reference('clusterId', 'cluster'),
nodePool: reference('nodePoolId'),
displayIp: or('externalIpAddress', 'ipAddress'),
init() {
this._super(...arguments);
this.defineStateCounts('arrangedInstances', 'instanceStates', 'instanceCountSort');
},
availableActions: computed('actionLinks.{cordon,drain,uncordon}', 'canScaleDown', 'links.nodeConfig', function() {
let l = this.links;
const a = this.actionLinks || {};
let out = [
{
label: 'action.cordon',
icon: 'icon icon-pause',
action: 'cordon',
enabled: !!a.cordon,
bulkable: true
},
{
label: 'action.uncordon',
icon: 'icon icon-play',
action: 'uncordon',
enabled: !!a.uncordon,
bulkable: true
},
{
label: 'action.drain',
icon: 'icon icon-snapshot',
action: 'drain',
enabled: !!a.drain,
bulkable: true
},
{
label: 'action.scaledown',
icon: 'icon icon-arrow-circle-up icon-rotate-180',
action: 'scaledown',
enabled: this.canScaleDown,
bulkable: true
},
{
label: 'action.stopDrain',
icon: 'icon icon-stop',
action: 'stopDrain',
enabled: !!a.stopDrain,
bulkable: true,
},
{ divider: true },
{
label: 'action.nodeConfig',
icon: 'icon icon-download',
action: 'nodeConfig',
enabled: !!l.nodeConfig
},
{ divider: true },
];
return out;
}),
canScaleDown: computed('actionLinks.scaledown', 'nodePool.quantity', function() {
const actions = this.actionLinks;
const nodePool = this.nodePool;
return !!actions?.scaledown && nodePool?.quantity > 1;
}),
displayName: computed('id', 'name', 'nodeName.length', 'nodes', 'requestedHostname', function() {
let name = this.name;
if ( name ) {
return name;
}
name = this.nodeName;
if ( name ) {
if ( name.match(/[a-z]/i) ) {
name = this.parseNodeName(name);
}
return name;
}
name = this.requestedHostname;
if ( name ) {
return name;
}
return `(${ this.id })`;
}),
rolesArray: computed('etcd', 'controlPlane', 'worker', function() {
return ['etcd', 'controlPlane', 'worker'].filter((x) => !!get(this, x));
}),
displayRoles: computed('intl.locale', 'rolesArray.[]', function() {
const intl = this.intl;
const roles = this.rolesArray;
if ( roles.length >= 3 ) {
return [intl.t('generic.all')];
}
return roles.map((role) => {
let key = `model.machine.role.${ role }`;
if ( intl.exists(key) ) {
return intl.t(key);
}
return key;
});
}),
sortRole: computed('rolesArray.[]', function() {
let roles = this.rolesArray;
if ( roles.length >= 3 ) {
return 1;
}
if ( roles.includes('controlPlane') ) {
return 2;
}
if ( roles.includes('etcd') ) {
return 3;
}
return 4;
}),
isUnschedulable: computed('taints.@each.{effect,key}', function(){
const taints = this.taints || [];
return taints.some((taint) => UNSCHEDULABLE_KEYS.includes(taint.key) && UNSCHEDULABLE_EFFECTS.includes(taint.effect));
}),
isK3sNode: computed('labels', function() {
const labels = this.labels || {};
return Object.prototype.hasOwnProperty.call(labels, C.LABEL.NODE_INSTANCE_TYPE);
}),
k3sNodeArgs: computed('annotations', function() {
const { annotations } = this;
const nodeArgs = annotations[C.LABEL.K3S_NODE_ARGS] ? JSON.parse(annotations[C.LABEL.K3S_NODE_ARGS]) : [];
return nodeArgs.join(' ');
}),
k3sNodeEnvVar: computed('annotations', function() {
const { annotations } = this;
const nodeEnv = annotations[C.LABEL.K3S_NODE_ENV] ? JSON.parse(annotations[C.LABEL.K3S_NODE_ENV]) : {};
const nodeEnvArr = [];
Object.keys(nodeEnv).forEach((envKey) => {
const out = {
key: envKey,
value: nodeEnv[envKey]
};
nodeEnvArr.push(out)
})
return nodeEnvArr;
}),
osBlurb: computed('info.os.operatingSystem', function() {
var out = get(this, 'info.os.operatingSystem') || '';
out = out.replace(/\s+\(.*?\)/, ''); // Remove details in parens
out = out.replace(/;.*$/, ''); // Or after semicolons
// RHEL 7 uses 'Red Hat Enterprise Linux Server', RHEL 8 uses 'Red Hat Enterprise Linux'
out = out.replace(/(Red Hat Enterprise Linux(\sServer|))/, 'RHEL'); // That's kinda long
return out;
}),
engineIcon: computed('info.os.dockerVersion', function() {
if ( (get(this, 'info.os.dockerVersion') || '').startsWith(CONTAINERD) ) {
return 'icon-container-d';
}
return 'icon-docker';
}),
versionBlurb: computed('info.os.dockerVersion', 'isContainerD', function() {
let version = get(this, 'info.os.dockerVersion') || '';
if ( version.startsWith(CONTAINERD) ) {
version = version.substr(CONTAINERD.length);
if (!this.isContainerD) {
next(() => set(this, 'isContainerD', true));
}
} else {
if (this.isContainerD) {
next(() => set(this, 'isContainerD', false));
}
}
const idx = version.indexOf('+');
if ( idx > 0 ) {
version = version.substr(0, idx);
}
return version;
}),
osInfo: computed('labels', function() {
const labels = this.labels || {};
return labels['beta.kubernetes.io/os'];
}),
// or they will not be pulled in correctly.
displayEndpoints: computed('publicEndpoints.@each.{ipAddress,port,serviceId,instanceId}', function() {
var store = this.clusterStore;
return (this.publicEndpoints || []).map((endpoint) => {
if ( !endpoint.service ) {
endpoint.service = store.getById('service', endpoint.serviceId);
}
endpoint.instance = store.getById('instance', endpoint.instanceId);
return endpoint;
});
}),
// If you use this you must ensure that services and containers are already in the store
requireAnyLabelStrings: computed(`labels.${ C.LABEL.REQUIRE_ANY }`, 'labels', function() {
return ((this.labels || {})[C.LABEL.REQUIRE_ANY] || '')
.split(/\s*,\s*/)
.filter((x) => x.length > 0 && x !== C.LABEL.SYSTEM_TYPE);
}),
parseNodeName(nameIn) {
const suffix = nameIn.split('.').slice(1).join('.');
const nodesWithSameSuffix = (this.nodes || []).filter((node) => (node.nodeName || '').endsWith(suffix));
if (nodesWithSameSuffix.length === 1) {
return this.nodeName;
} else if (nodesWithSameSuffix.length > 1) {
const neu = nameIn.replace(/\..*$/, '');
if ( neu.match(/^\d+$/) ) {
return this.nodeName;
} else {
return neu;
}
}
return nameIn;
},
actions: {
activate() {
return this.doAction('activate');
},
deactivate() {
return this.doAction('deactivate');
},
cordon() {
return this.doAction('cordon');
},
uncordon() {
return this.doAction('uncordon');
},
scaledown() {
return this.doAction('scaledown');
},
drain() {
this.modalService.toggleModal('modal-drain-node', {
escToClose: true,
resources: [this],
});
},
stopDrain() {
return this.doAction('stopDrain');
},
newContainer() {
this.router.transitionTo('containers.run', { queryParams: { hostId: get(this, 'model.id') } });
},
edit() {
this.modalService.toggleModal('modal-edit-host', this);
},
nodeConfig() {
var url = this.linkFor('nodeConfig');
if ( url ) {
download(url);
}
}
},
});
Node.reopenClass({ defaultSortBy: 'name,hostname', });
export default Node;