| /* |
| Copyright(c) 2018 AT&T Intellectual Property. All rights reserved. |
| Copyright(c) 2020 Nokia Intellectual Property. All rights reserved. |
| Copyright(c) 2021 J. F. Lucas. All rights reserved. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR |
| CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and limitations under the License. |
| */ |
| |
| /* |
| * Query Kubernetes for status of deployments and extract readiness information |
| */ |
| |
| const fs = require('fs'); |
| const https = require('https'); |
| const log = require('./log'); |
| |
| const K8S_CREDS = '/var/run/secrets/kubernetes.io/serviceaccount'; |
| const K8S_HOST = 'kubernetes.default.svc.cluster.local'; // Full name to match cert for TLS |
| const K8S_PATH = 'apis/apps/v1/namespaces/'; |
| |
| const MAX_DEPS = 1000; // Maximum number of k8s deployments to return from a query to k8s |
| |
| //Get token and CA cert |
| const ca = fs.readFileSync(K8S_CREDS + '/ca.crt'); |
| const token = fs.readFileSync(K8S_CREDS + '/token'); |
| |
| const summarizeDeploymentList = function(list) { |
| // list is a DeploymentList object returned by k8s |
| // Individual deployments are in the array 'items' |
| |
| let ret = |
| { |
| type: 'summary', |
| count: 0, |
| ready: 0, |
| items: [] |
| }; |
| |
| // Extract readiness information |
| for (let deployment of list.items) { |
| ret.items.push( |
| { |
| name: deployment.metadata.name, |
| ready: deployment.status.readyReplicas || 0, |
| unavailable: deployment.status.unavailableReplicas || 0 |
| } |
| ); |
| ret.count ++; |
| ret.ready = ret.ready + (deployment.status.readyReplicas || 0); |
| } |
| |
| return ret; |
| }; |
| |
| const queryKubernetes = function(path, callback) { |
| // Make GET request to Kubernetes API |
| const options = { |
| host: K8S_HOST, |
| path: '/' + path, |
| ca : ca, |
| headers: { |
| Authorization: 'bearer ' + token |
| } |
| }; |
| |
| const req = https.get(options, function(resp) { |
| let rawBody = ''; |
| resp.on('data', function(data) { |
| rawBody += data; |
| }); |
| resp.on('error', function (error) { |
| log.debug(`k8s API query: ${options.host}${options.path} -- error: ${error}`); |
| callback(error, null, null) |
| }); |
| resp.on('end', function() { |
| const deployment = JSON.parse(rawBody); |
| log.debug (`k8s API query: ${options.host}${options.path} -- status: ${resp.statusCode ? resp.statusCode: 'NONE'}`); |
| callback(null, resp, JSON.parse(rawBody)); |
| }); |
| }); |
| req.end(); |
| }; |
| |
| const getStatusSinglePromise = function (item) { |
| // Expect item to be of the form {namespace: "namespace", deployment: "deployment_name"} |
| return new Promise(function(resolve, reject){ |
| const path = K8S_PATH + item.namespace + '/deployments/' + item.deployment; |
| queryKubernetes(path, function(error, res, body) { |
| if (error) { |
| reject(error); |
| } |
| else if (res.statusCode === 404) { |
| // Treat absent deployment as if it's an unhealthy deployment |
| resolve ({ |
| metadata: {name: item.deployment}, |
| status: {unavailableReplicas: 1} |
| }); |
| } |
| else if (res.statusCode != 200) { |
| reject(body); |
| } |
| else { |
| resolve(body); |
| } |
| }); |
| }); |
| } |
| |
| exports.getStatusListPromise = function (list) { |
| // List is of the form [{namespace: "namespace", deployment: "deployment_name"}, ... ] |
| const p = Promise.all(list.map(getStatusSinglePromise)) |
| return p.then(function(results) { |
| return summarizeDeploymentList({items: results}); |
| }); |
| } |
| |
| exports.getLabeledDeploymentsPromise = function (namespace, label) { |
| // Return list of the form [{namespace: "namespace", deployment: "deployment_name"}]. |
| // List contains all k8s deployments in the specified namespace that were deployed |
| // with the specified 'label'. (The check is for the presence of the label--its |
| // values is not important.) In DCAE, this is used to find deployments created |
| // by Cloudify, based on Cloudify's use of a "marker" label on each k8s deployment that |
| // the k8s plugin created. |
| // If 'label' is unspecified or has zero length, returns an empty list. |
| |
| return new Promise(function(resolve, reject) { |
| if (!label || label.length < 1) { |
| resolve([]); |
| } |
| else { |
| const path = K8S_PATH + namespace + '/deployments?labelSelector=' + label + '&limit=' + MAX_DEPS |
| queryKubernetes(path, function(error, res, body){ |
| if (error) { |
| reject(error); |
| } |
| else if (res.statusCode !== 200) { |
| reject(body); |
| } |
| else { |
| resolve(body.items.map(function(i) {return {namespace : namespace, deployment: i.metadata.name};})); |
| } |
| }); |
| } |
| }); |
| }; |