import Component from '@ember/component'; import layout from './template'; import { get, set, computed, observer } from '@ember/object'; import { inject as service } from '@ember/service'; import { all as PromiseAll } from 'rsvp'; import { alias } from '@ember/object/computed'; const headers = [ { name: 'hostnamePrefix', sort: ['hostnamePrefix', 'id'], translationKey: 'clusterNew.rke.nodes.hostnamePrefix', scope: 'embedded', }, { name: 'count', sort: ['quantity', 'displayName.id'], translationKey: 'clusterNew.rke.nodes.count', width: 100, }, { name: 'nodeTemplate', sort: ['nodeTemplate.displayName', 'nodeTemplate.id'], translationKey: 'clusterNew.rke.nodes.template', }, { name: 'deleteNotReadyAfterSecs', sort: ['deleteNotReadyAfterSecs'], translationKey: 'clusterNew.rke.nodes.deleteNotReadyAfterSecs', width: 155, }, { name: 'aggressivedrain', sort: false, scope: 'embedded', translationKey: 'clusterNew.rke.nodes.drainBeforeDelete', classNames: ['pl-5'], width: 150, }, { name: 'etcd', sort: false, translationKey: 'clusterNew.rke.role.header.etcd', classNames: ['text-center'], width: 100, }, { name: 'controlplane', sort: false, translationKey: 'clusterNew.rke.role.header.controlplane', classNames: ['text-center'], width: 100, }, { name: 'worker', sort: false, translationKey: 'clusterNew.rke.role.header.worker', scope: 'embedded', classNames: ['text-center'], width: 100, }, { name: 'advanced', sort: false, scope: 'embedded', translationKey: 'clusterNew.rke.nodes.taints', classNames: ['text-center'], width: 100, }, { name: 'remove', sort: false, classNames: ['text-center'], width: 35, } ]; const HARVESTER = 'harvester' export default Component.extend({ globalStore: service(), modalService: service('modal'), intl: service(), scope: service(), layout, cluster: null, nodeTemplates: null, driver: null, // docker-machine driver model: null, originalPools: null, nodePools: null, errors: null, taintCapabilites: alias('scope.currentCluster.capabilities.taintSupport'), init() { this._super(...arguments); const originalPools = (get(this, 'cluster.nodePools') || []).slice(); set(this, 'originalPools', originalPools); set(this, 'nodePools', originalPools.slice().map((p) => p.clone())); if ( get(this, 'mode') === 'new' && get(originalPools, 'length') === 0 ) { get(this, 'nodePools').pushObject(get(this, 'globalStore').createRecord({ type: 'nodePool', quantity: 1, worker: true, })); } if (this.registerHook) { this.registerHook(this.savePools.bind(this), 'savePools') } this.setDefaultNodeTemplate(); }, didReceiveAttrs() { this.validate(); }, actions: { addPool() { let nodePools = get(this, 'nodePools'); let templateId = null; const lastNode = nodePools[nodePools.length - 1]; if ( lastNode ) { templateId = get(lastNode, 'nodeTemplateId'); } nodePools.pushObject(get(this, 'globalStore').createRecord({ type: 'nodePool', nodeTemplateId: templateId, worker: true, })); }, removePool(pool) { get(this, 'nodePools').removeObject(pool); }, addNodeTemplate(node) { get(this, 'modalService').toggleModal('modal-edit-node-template', { nodeTemplate: null, driver: get(this, 'driver'), onAdd(nodeTemplate) { set(node, 'nodeTemplateId', get(nodeTemplate, 'id')); }, }); }, configAdvancedOptions(pool) { get(this, 'modalService').toggleModal('modal-edit-node-pool', pool); }, }, setDefaultNodeTemplate: observer('driver', function() { const templates = get(this, 'filteredNodeTemplates') || []; if ( templates.length === 1 ) { (get(this, 'nodePools') || []).forEach((pool) => { if ( !get(pool, 'nodeTemplateId') ) { set(pool, 'nodeTemplateId', get(templates, 'firstObject.id')); } }); if (get(this, 'driver') === HARVESTER) { this.nodeTemplateIdChanged() } } }), driverChanged: observer('driver', function() { const driver = get(this, 'driver'); get(this, 'nodePools').forEach((pool) => { const tpl = get(pool, 'nodeTemplate'); if ( tpl && get(tpl, 'driver') !== driver ) { set(pool, 'nodeTemplateId', null); } }); }), nodeTemplateIdChanged: observer('nodePools.@each.{nodeTemplateId}', 'driver', function() { if (get(this, 'driver') === HARVESTER) { set(this, 'model.harvesterNodeTemplateId', get(this, 'nodePools.firstObject.nodeTemplateId')) } }), validate: observer('etcdOk', 'controlPlaneOk', 'workerOk', 'nodePools.@each.{quantity,hostnamePrefix,nodeTemplateId}', 'nodePools.[]', 'driver', function() { const intl = get(this, 'intl'); const driver = get(this, 'driver'); const errors = []; if ( get(this, 'mode') === 'new' && !get(this, 'etcdOk') ) { errors.push(intl.t('clusterNew.rke.errors.etcd')); } if ( !get(this, 'controlPlaneOk') ) { errors.push(intl.t('clusterNew.rke.errors.controlPlane')); } if ( !get(this, 'workerOk') ) { errors.push(intl.t('clusterNew.rke.errors.worker')); } if ( driver === 'digitalocean') { const nodePools = get(this, 'nodePools'); let isError = false; nodePools.map((p) => { const { hostnamePrefix = '' } = p; if (hostnamePrefix.includes('_')) { isError = true; } }) if (isError) { errors.push(intl.t('nodeDriver.digitalocean.errors.hostnamePrefix')); } } if (this.uniqueHostNameError) { errors.push(intl.t('clusterNew.nodePools.errors.hostnamePrefix')); } get(this, 'nodePools').forEach((pool) => { // ClusterId is required but not known yet if ( !get(pool, 'clusterId') ) { set(pool, 'clusterId', '__later__'); } errors.pushObjects(pool.validationErrors()); }); if (this.setNodePoolErrors) { this.setNodePoolErrors(errors); } }), showIamWarning: computed('driver', 'nodePools.@each.nodeTemplateId', 'cluster.rancherKubernetesEngineConfig.cloudProvider.name', function() { const cloudProvider = get(this, 'cluster.rancherKubernetesEngineConfig.cloudProvider.name'); const driver = get(this, 'driver'); if ( driver === 'amazonec2' && cloudProvider === 'aws' ) { const found = (get(this, 'nodePools') || []).some((pool) => { const nodeTemplate = get(pool, 'nodeTemplate'); return nodeTemplate && !get(nodeTemplate, 'amazonec2Config.iamInstanceProfile'); }); return found ? true : false; } return false; }), filteredNodeTemplates: computed('driver', 'nodePools.@each.nodeTemplateId', 'nodePools.firstObject.nodeTemplate.harvesterConfig.vmNamespace', 'nodeTemplates.@each.{driver,state}', function() { const driver = get(this, 'driver'); let templates = get(this, 'nodeTemplates').filterBy('state', 'active').filterBy('driver', driver); (get(this, 'nodePools') || []).forEach((pool) => { const templateId = get(pool, 'nodeTemplateId'); if ( templateId && !templates.findBy('id', templateId) ) { templates.push(get(this, 'globalStore').createRecord({ type: 'nodetemplate', id: templateId, name: `(${ templateId })`, })); } }); if (driver === HARVESTER && get(this, 'nodePools.length') > 1) { templates.map((t) => { const namespace = get(this, 'nodePools.firstObject.nodeTemplate.harvesterConfig.vmNamespace') if ( namespace ) { if (get(t, 'harvesterConfig.vmNamespace') !== namespace) { set(t, 'disabled', true); } else { set(t, 'disabled', false); } } else { set(t, 'disabled', false); } return t }) } else { templates.map((t) => set(t, 'disabled', null)) } return templates; }), uniqueHostNameError: computed('nodePools.@each.hostnamePrefix', function() { // Host name prefixes must be unique const nodePools = get(this, 'nodePools'); const names = {}; let isError = false; nodePools.map((p) => { const { hostnamePrefix = '' } = p; if (names[hostnamePrefix]) { isError = true; } else { names[hostnamePrefix] = true; } }); return isError; }), etcdOk: computed('nodePools.@each.{quantity,etcd}', function() { let count = this._nodeCountFor('etcd'); return count === 1 || count === 3 || count === 5 }), etcdCount: computed('nodePools.@each.{quantity,etcd}', function() { return this._nodeCountFor('etcd'); }), controlPlaneOk: computed('nodePools.@each.{quantity,controlPlane}', function() { let count = this._nodeCountFor('controlPlane'); return count >= 1; }), workerOk: computed('nodePools.@each.{quantity,worker}', function() { let count = this._nodeCountFor('worker'); return count >= 1; }), headers: computed('driver', 'mode', 'taintCapabilites', function() { const { taintCapabilites, mode } = this const nodeTemplate = headers.find((h) => h.name === 'nodeTemplate') || {} if (this.driver === HARVESTER) { nodeTemplate.tooltip = this.intl.t('nodeDriver.harvester.nodeTemplate.tooltip') } else { delete nodeTemplate.tooltip } if (taintCapabilites || mode === 'new') { return headers } else { return headers.filter((h) => h.name !== 'advanced') } }), savePools() { if (this.isDestroyed || this.isDestroying || get(this, 'driver') === 'custom' ) { return; } const nodePools = get(this, 'nodePools'); const original = get(this, 'originalPools'); const remove = []; original.forEach((pool) => { if ( !nodePools.some((p) => p.id === pool.id) ) { // Remove remove.push(pool); } }); const clusterId = get(this, 'cluster.id'); nodePools.forEach((pool) => { const { controlPlane, etcd, worker } = pool; set(pool, 'clusterId', clusterId); if (!controlPlane && !etcd && !worker) { set(pool, 'worker', true); } }); return PromiseAll(nodePools.map((x) => x.save())).then(() => { return PromiseAll(remove.map((x) => x.delete())).then(() => { return get(this, 'cluster'); }); }); }, _nodeCountFor(role) { let count = 0; (get(this, 'nodePools') || []).filterBy(role, true).forEach((pool) => { let more = get(pool, 'quantity'); if ( more ) { more = parseInt(more, 10); } count += more; }); return count; }, });