UNPKG

10.2 kBJavaScriptView Raw
1/**
2 * Copyright 2016-2018 F5 Networks, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17'use strict';
18
19const q = require('q');
20const util = require('./util');
21const Logger = require('./logger');
22
23const LICENSE_PATH = '/cm/device/tasks/licensing/pool/member-management/';
24const LICENSE_TIMEOUT = { maxRetries: 40, retryIntervalMs: 5000 };
25const ALREADY_LICENSED_LIMIT = 5;
26
27let alreadyLicensedCount = 0;
28
29/**
30 * BigIq 5.3 license provider constructor
31 *
32 * @class
33 * @classdesc
34 * Provides ability to get licenses from BIG-IQ 5.3 (and compatible versions).
35 *
36 * @param {Object} bigIp - Base {@link BigIp} object.
37 * @param {Object} [options] - Optional parameters.
38 * @param {Object} [options.logger] - Logger to use. Or, pass loggerOptions to get your own logger.
39 * @param {Object} [options.loggerOptions] - Options for the logger.
40 * See {@link module:logger.getLogger} for details.
41*/
42function BigIq53LicenseProvider(bigIp, options) {
43 const injectedLogger = options ? options.logger : undefined;
44 let loggerOptions = options ? options.loggerOptions : undefined;
45
46 if (injectedLogger) {
47 this.logger = injectedLogger;
48 util.setLogger(injectedLogger);
49 } else {
50 loggerOptions = loggerOptions || { logLevel: 'none' };
51 loggerOptions.module = module;
52 this.logger = Logger.getLogger(loggerOptions);
53 util.setLoggerOptions(loggerOptions);
54 }
55
56 this.bigIp = bigIp;
57}
58
59/**
60 * Gets a license from BIG-IQ for an unmanaged BIG-IP
61 *
62 * @param {Object} bigIqControl - iControl object for BIG-IQ
63 * @param {String} poolName - Name of the BIG-IQ license pool to use
64 * @param {String} bigIpMgmtAddress - IP address of BIG-IP management port.
65 * @param {String} bigIpMgmtPort - IP port of BIG-IP management port.
66 * @param {Object} [options] - Optional parameters
67 * @param {String} [options.skuKeyword1] - skuKeyword1 parameter for CLPv2 licensing. Default none.
68 * @param {String} [options.skuKeyword2] - skuKeyword2 parameter for CLPv2 licensing. Default none.
69 * @param {String} [options.unitOfMeasure] - unitOfMeasure parameter for CLPv2 licensing. Default none.
70 *
71 * @returns {Promise} A promise which is resolved when the BIG-IP has been licensed
72 * or rejected if an error occurs.
73 */
74BigIq53LicenseProvider.prototype.getUnmanagedDeviceLicense = function getUnmanagedDeviceLicense(
75 bigIqControl,
76 poolName,
77 bigIpMgmtAddress,
78 bigIpMgmtPort,
79 options
80) {
81 this.logger.debug('Licensing from pool', poolName);
82 return licenseFromPool.call(this, bigIqControl, poolName, bigIpMgmtAddress, bigIpMgmtPort, options);
83};
84
85/**
86 * Revokes a license from a BIG-IP
87 *
88 * @param {Object} bigIqControl - iControl object for BIG-IQ
89 * @param {String} poolName - Name of the BIG-IQ license pool to use. Not used in this
90 * API. Here for consistency.
91 * @param {String} instance - {@link AutoscaleInstance} to revoke license for
92 *
93 * @returns {Promise} A promise which is resolved when the BIG-IP license has
94 * been revoked, or rejected if an error occurs.
95 */
96BigIq53LicenseProvider.prototype.revoke = function revoke(bigIqControl, poolName, instance) {
97 this.logger.silly('BigIq53LicenseProvider.prototype.revoke');
98
99 // get the self link of the reg key to delete
100 /* eslint-disable max-len */
101 let query = '/shared/index/config?$filter=( ( ';
102 query += "'kind' eq 'cm:device:licensing:pool:purchased-pool:licenses:licensepoolmemberstate'";
103 query += " or 'kind' eq 'cm:device:licensing:pool:utility:licenses:regkey:offerings:offering:members:grantmemberstate'";
104 query += " or 'kind' eq 'cm:device:licensing:pool:volume:licenses:regkey:offerings:offering:members:memberstate'";
105 query += " or 'kind' eq 'cm:device:licensing:pool:regkey:licenses:item:offerings:regkey:members:regkeypoollicensememberstate' )";
106 query += ` and 'deviceMachineId' eq '${instance.machineId}')`;
107 query += '&$select=deviceAddress,deviceMachineId,deviceName,selfLink';
108 /* eslint-enable max-len */
109
110 try {
111 query = encodeURI(query);
112 } catch (err) {
113 this.logger.warn('BigIq53LicenseProvider unable to encode revoke query');
114 return q.reject(err);
115 }
116
117 this.logger.debug('Revoking license for machineId', instance.machineId);
118 return bigIqControl.list(query)
119 .then((data) => {
120 if (data && data.length === 1 && data[0].selfLink) {
121 this.logger.silly('revoke found device', data[0]);
122
123 /* eslint-disable max-len */
124 // get the selfLink and issue a delete
125 // selfLink looks like:
126 // "https://localhost/mgmt/cm/device/licensing/pool/regkey/licenses/65521190-d762-4bd9-b644-96488b90e8dc/offerings/KUZOO-WTLAB-CYKDY-NKIPL-WINYJUF/members/c04267f4-b3e8-426a-adfe-46249e8151ce"
127 // we just want the part after /mgmt to the end
128 // we also have to put the last segment in the body for some reason
129 /* eslint-enable max-len */
130
131 const selfLink = data[0].selfLink;
132 const prefix = '/mgmt';
133 const pathIndex = selfLink.indexOf(prefix);
134 const path = selfLink.substr(pathIndex + prefix.length);
135
136 const idIndex = selfLink.lastIndexOf('/');
137 const id = selfLink.substr(idIndex + 1);
138
139 const body = {
140 id,
141 username: this.bigIp.user || 'dummyUser',
142 password: this.bigIp.password || 'dummyPassword'
143 };
144
145 return bigIqControl.delete(path, body);
146 }
147
148 this.logger.debug('revoke data length not as expected:', data.length, 'devices');
149 return q();
150 });
151};
152
153/**
154 * Gets the license timeout to use
155 *
156 * This is here so that it can be overridden by test code
157 *
158 * @returns the license timeout
159 */
160BigIq53LicenseProvider.prototype.getLicenseTimeout = function getLicenseTimeout() {
161 return LICENSE_TIMEOUT;
162};
163
164function licenseFromPool(bigIqControl, poolName, bigIpMgmtAddress, bigIpMgmtPort, options) {
165 const skuKeyword1 = options ? options.skuKeyword1 : undefined;
166 const skuKeyword2 = options ? options.skuKeyword2 : undefined;
167 const unitOfMeasure = options ? options.unitOfMeasure : undefined;
168
169 return bigIqControl.create(
170 LICENSE_PATH,
171 {
172 skuKeyword1,
173 skuKeyword2,
174 unitOfMeasure,
175 command: 'assign',
176 licensePoolName: poolName,
177 address: bigIpMgmtAddress,
178 port: bigIpMgmtPort,
179 user: this.bigIp.user,
180 password: this.bigIp.password
181 }
182 )
183 .then((response) => {
184 this.logger.debug(response);
185
186 const taskId = response.id;
187
188 const isLicensed = function () {
189 return bigIqControl.list(LICENSE_PATH + taskId)
190 .then((taskResponse) => {
191 const status = taskResponse.status;
192 this.logger.verbose('Current licensing task status:', status);
193 if (status === 'FINISHED') {
194 return q(
195 {
196 success: true
197 }
198 );
199 } else if (status === 'FAILED') {
200 return q(
201 {
202 success: false,
203 errorMessage: taskResponse.errorMessage
204 }
205 );
206 }
207 return q.reject(new Error(taskResponse.errorMessage));
208 });
209 };
210
211 return util.tryUntil(this, this.getLicenseTimeout(), isLicensed)
212 .then((isLicensedResponse) => {
213 if (isLicensedResponse.success) {
214 this.logger.info('Successfully licensed');
215 return q();
216 }
217
218 this.logger.info('Licensing failed', isLicensedResponse.errorMessage);
219
220 // If we run into the race condition of 2 BIG-IPs trying to license at the
221 // same time, try a different license
222 if (isLicensedResponse.errorMessage.indexOf('already been granted to a BIG-IP') !== -1) {
223 alreadyLicensedCount += 1;
224 if (alreadyLicensedCount <= ALREADY_LICENSED_LIMIT) {
225 this.logger.debug('Got a license that is already in use. Retrying.');
226 return licenseFromPool.call(
227 this,
228 bigIqControl,
229 poolName,
230 bigIpMgmtAddress,
231 bigIpMgmtPort
232 );
233 }
234
235 return q.reject();
236 }
237
238 return q.reject(new Error(isLicensedResponse.errorMessage));
239 })
240 .catch((err) => {
241 this.logger.info('Failed to license:', err && err.message ? err.message : err);
242 return q.reject(err);
243 });
244 });
245}
246
247module.exports = BigIq53LicenseProvider;