1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-02 20:35:25 +02:00

feat(k8s/application): expose tolerations and affinities (#4063)

* feat(k8s/application): expose placement conditions

* feat(k8s/applications): minor UI update

* feat(k8s/application): update message for admin and non admin users

* feat(kubernetes/applications): minor UI update

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>
This commit is contained in:
xAt0mZ 2020-07-30 00:25:59 +02:00 committed by GitHub
parent 63bf654d8d
commit 4431d748c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 635 additions and 112 deletions

View file

@ -0,0 +1,67 @@
import _ from 'lodash-es';
import { KubernetesNode, KubernetesNodeDetails } from 'Kubernetes/node/models';
import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper';
class KubernetesNodeConverter {
static apiToNode(data, res) {
if (!res) {
res = new KubernetesNode();
}
res.Id = data.metadata.uid;
const hostName = _.find(data.status.addresses, { type: 'Hostname' });
res.Name = hostName ? hostName.address : data.metadata.Name;
res.Labels = data.metadata.labels;
res.Role = _.has(data.metadata.labels, 'node-role.kubernetes.io/master') ? 'Master' : 'Worker';
const ready = _.find(data.status.conditions, { type: KubernetesNodeConditionTypes.READY });
const memoryPressure = _.find(data.status.conditions, { type: KubernetesNodeConditionTypes.MEMORY_PRESSURE });
const PIDPressure = _.find(data.status.conditions, { type: KubernetesNodeConditionTypes.PID_PRESSURE });
const diskPressure = _.find(data.status.conditions, { type: KubernetesNodeConditionTypes.DISK_PRESSURE });
const networkUnavailable = _.find(data.status.conditions, { type: KubernetesNodeConditionTypes.NETWORK_UNAVAILABLE });
res.Conditions = {
MemoryPressure: memoryPressure && memoryPressure.status === 'True',
PIDPressure: PIDPressure && PIDPressure.status === 'True',
DiskPressure: diskPressure && diskPressure.status === 'True',
NetworkUnavailable: networkUnavailable && networkUnavailable.status === 'True',
};
if (ready.status === 'False') {
res.Status = 'Unhealthy';
} else if (ready.status === 'Unknown' || res.Conditions.MemoryPressure || res.Conditions.PIDPressure || res.Conditions.DiskPressure || res.Conditions.NetworkUnavailable) {
res.Status = 'Warning';
} else {
res.Status = 'Ready';
}
res.CPU = KubernetesResourceReservationHelper.parseCPU(data.status.allocatable.cpu);
res.Memory = data.status.allocatable.memory;
res.Version = data.status.nodeInfo.kubeletVersion;
const internalIP = _.find(data.status.addresses, { type: 'InternalIP' });
res.IPAddress = internalIP ? internalIP.address : '-';
res.Taints = data.spec.taints ? data.spec.taints : [];
return res;
}
static apiToNodeDetails(data, yaml) {
let res = new KubernetesNodeDetails();
res = KubernetesNodeConverter.apiToNode(data, res);
res.CreationDate = data.metadata.creationTimestamp;
res.OS.Architecture = data.status.nodeInfo.architecture;
res.OS.Platform = data.status.nodeInfo.operatingSystem;
res.OS.Image = data.status.nodeInfo.osImage;
res.Yaml = yaml ? yaml.data : '';
return res;
}
}
export const KubernetesNodeConditionTypes = Object.freeze({
READY: 'Ready',
MEMORY_PRESSURE: 'MemoryPressure',
PID_PRESSURE: 'PIDPressure',
DISK_PRESSURE: 'DiskPressure',
NETWORK_UNAVAILABLE: 'NetworkUnavailable',
});
export default KubernetesNodeConverter;

View file

@ -0,0 +1,35 @@
import _ from 'lodash-es';
angular
.module('portainer.kubernetes')
.filter('kubernetesNodeStatusColor', function () {
'use strict';
return function (text) {
var status = _.toLower(text);
switch (status) {
case 'ready':
return 'success';
case 'warning':
return 'warning';
default:
return 'danger';
}
};
})
.filter('kubernetesNodeConditionsMessage', function () {
'use strict';
return function (conditions) {
if (conditions.MemoryPressure) {
return 'Node memory is running low';
}
if (conditions.PIDPressure) {
return 'Too many processes running on the node';
}
if (conditions.DiskPressure) {
return 'Node disk capacity is running low';
}
if (conditions.NetworkUnavailable) {
return 'Incorrect node network configuration';
}
};
});

View file

@ -0,0 +1,42 @@
/**
* KubernetesNode Model
*/
const _KubernetesNode = Object.freeze({
Id: '',
Name: '',
Labels: {},
Role: '',
Status: '',
CPU: 0,
Memory: '',
Version: '',
IPAddress: '',
Taints: [],
});
export class KubernetesNode {
constructor() {
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesNode)));
}
}
/**
* KubernetesNodeDetails Model
*/
const _KubernetesNodeDetails = Object.freeze({
CreationDate: '',
OS: {
Architecture: '',
Platform: '',
Image: '',
},
Conditions: [],
Yaml: '',
});
export class KubernetesNodeDetails {
constructor() {
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesNode)));
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesNodeDetails)));
}
}

View file

@ -0,0 +1,37 @@
import { rawResponse } from 'Kubernetes/rest/response/transform';
angular.module('portainer.kubernetes').factory('KubernetesNodes', [
'$resource',
'API_ENDPOINT_ENDPOINTS',
'EndpointProvider',
function KubernetesNodesFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
'use strict';
return function () {
const url = API_ENDPOINT_ENDPOINTS + '/:endpointId/kubernetes/api/v1/nodes/:id/:action';
return $resource(
url,
{
endpointId: EndpointProvider.endpointID,
},
{
get: {
method: 'GET',
timeout: 15000,
ignoreLoadingBar: true,
},
getYaml: {
method: 'GET',
headers: {
Accept: 'application/yaml',
},
transformResponse: rawResponse,
ignoreLoadingBar: true,
},
create: { method: 'POST' },
update: { method: 'PUT' },
delete: { method: 'DELETE' },
}
);
};
},
]);

View file

@ -0,0 +1,50 @@
import angular from 'angular';
import _ from 'lodash-es';
import PortainerError from 'Portainer/error';
import KubernetesNodeConverter from 'Kubernetes/node/converter';
import { KubernetesCommonParams } from 'Kubernetes/models/common/params';
class KubernetesNodeService {
/* @ngInject */
constructor($async, KubernetesNodes) {
this.$async = $async;
this.KubernetesNodes = KubernetesNodes;
this.getAsync = this.getAsync.bind(this);
this.getAllAsync = this.getAllAsync.bind(this);
}
/**
* GET
*/
async getAsync(name) {
try {
const params = new KubernetesCommonParams();
params.id = name;
const [details, yaml] = await Promise.all([this.KubernetesNodes().get(params).$promise, this.KubernetesNodes().getYaml(params).$promise]);
return KubernetesNodeConverter.apiToNodeDetails(details, yaml);
} catch (err) {
throw new PortainerError('Unable to retrieve node details', err);
}
}
async getAllAsync() {
try {
const data = await this.KubernetesNodes().get().$promise;
return _.map(data.items, (item) => KubernetesNodeConverter.apiToNode(item));
} catch (err) {
throw { msg: 'Unable to retrieve nodes', err: err };
}
}
get(name) {
if (name) {
return this.$async(this.getAsync, name);
}
return this.$async(this.getAllAsync);
}
}
export default KubernetesNodeService;
angular.module('portainer.kubernetes').service('KubernetesNodeService', KubernetesNodeService);