UNPKG

15.7 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/licensing/pool/regkey/licenses/';
24const LICENSE_TIMEOUT = { maxRetries: 40, retryIntervalMs: 5000 };
25
26let logger;
27
28/**
29 * BigIq 5.2 license provider constructor
30 *
31 * @class
32 * @classdesc
33 * Provides ability to get licenses from BIG-IQ 5.2 (and compatible versions).
34 *
35 * @param {Object} bigIp - Base {@link BigIp} object.
36 * @param {Object} [options] - Optional parameters.
37 * @param {Object} [options.logger] - Logger to use. Or, pass loggerOptions to get your own logger.
38 * @param {Object} [options.loggerOptions] - Options for the logger.
39 * See {@link module:logger.getLogger} for details.
40 * @param {String} [options.version] - The version of BIG-IQ.
41 */
42function BigIq52LicenseProvider(bigIp, options) {
43 const version = options ? options.version : '5.2.0';
44 const injectedLogger = options ? options.logger : undefined;
45 let loggerOptions = options ? options.loggerOptions : undefined;
46
47 if (injectedLogger) {
48 this.logger = injectedLogger;
49 util.setLogger(injectedLogger);
50 } else {
51 loggerOptions = loggerOptions || { logLevel: 'none' };
52 loggerOptions.module = module;
53 this.logger = Logger.getLogger(loggerOptions);
54 util.setLoggerOptions(loggerOptions);
55 }
56
57 logger = this.logger;
58 this.bigIp = bigIp;
59 this.version = version || '5.2.0';
60}
61
62/**
63 * Gets a license from BIG-IQ for an unmanaged BIG-IP
64 *
65 * @param {Object} bigIqControl - iControl object for BIG-IQ
66 * @param {String} poolName - Name of the BIG-IQ license pool to use
67 * @param {String} bigIpMgmtAddress - IP address of BIG-IP management port.
68 * @param {String} bigIpMgmtPort - IP port of BIG-IP management port.
69 *
70 * @returns {Promise} A promise which is resolved when the BIG-IP has been licensed
71 * or rejected if an error occurs.
72 */
73BigIq52LicenseProvider.prototype.getUnmanagedDeviceLicense = function getUnmanagedDeviceLicense(
74 bigIqControl,
75 poolName,
76 bigIpMgmtAddress,
77 bigIpMgmtPort
78) {
79 this.logger.debug('Getting BIG-IP license pool UUID.');
80
81 return getPoolUuid(bigIqControl, poolName)
82 .then((poolUuid) => {
83 this.logger.debug('Got pool UUID:', poolUuid);
84 return util.tryUntil(
85 this,
86 util.MEDIUM_RETRY,
87 licenseFromPool,
88 [
89 bigIqControl,
90 bigIpMgmtAddress,
91 bigIpMgmtPort,
92 poolUuid
93 ]
94 );
95 })
96 .catch((err) => {
97 this.logger.warn(err);
98 return q.reject(err);
99 });
100};
101
102/**
103 * Revokes a license from a BIG-IP
104 *
105 * @param {Object} bigIqControl - iControl object for BIG-IQ
106 * @param {String} poolName - Name of the BIG-IQ license pool to use
107 * @param {String} instance - {@link AutoscaleInstance} to revoke license for
108 *
109 * @returns {Promise} A promise which is resolved when the BIG-IP license has
110 * been revoked, or rejected if an error occurs.
111 */
112BigIq52LicenseProvider.prototype.revoke = function revoke(bigIqControl, poolName, instance) {
113 let poolUuid;
114
115 return getPoolUuid(bigIqControl, poolName)
116 .then((uuid) => {
117 poolUuid = uuid;
118 return getLicensesInPool(bigIqControl, poolUuid);
119 })
120 .then((licensesInPool) => {
121 const deferred = q.defer();
122 let licenses;
123
124 if (!licensesInPool) {
125 licenses = [];
126 } else if (!Array.isArray(licensesInPool)) {
127 licenses = [licensesInPool];
128 } else {
129 licenses = licensesInPool.slice();
130 }
131
132 const findRegKeyForHostname = function (index) {
133 let currentIndex = index;
134 let license;
135
136 if (currentIndex > licenses.length - 1) {
137 logger.info('License for host not found.');
138 deferred.reject(new Error('License for host not found.'));
139 } else {
140 license = licenses[currentIndex];
141 if (license.licenseState) {
142 getMembersForKey(bigIqControl, poolUuid, license.licenseState.registrationKey)
143 .then((membersForKey) => {
144 let found = false;
145 let members;
146
147 if (!membersForKey) {
148 members = [];
149 } else if (!Array.isArray(membersForKey)) {
150 members = [membersForKey];
151 } else {
152 members = membersForKey.slice();
153 }
154
155 logger.silly(
156 'reg key members',
157 license.licenseState.registrationKey,
158 'members',
159 members
160 );
161
162 for (let i = 0; i < members.length; i++) {
163 if (members[i].deviceName === instance.hostname) {
164 found = true;
165 deferred.resolve(
166 {
167 regKey: license.licenseState.registrationKey,
168 member: members[i]
169 }
170 );
171 }
172 }
173
174 if (!found) {
175 currentIndex += 1;
176 findRegKeyForHostname(currentIndex);
177 }
178 })
179 .catch((err) => {
180 logger.debug('error while iterating licenses', err);
181 currentIndex += 1;
182 findRegKeyForHostname(currentIndex);
183 });
184 }
185 }
186 };
187
188 findRegKeyForHostname(0);
189
190 return deferred.promise;
191 })
192 .then((regKeyMember) => {
193 if (regKeyMember) {
194 // If we have the password, use it. Otherwise, use dummy values. This still makes
195 // the license available on BIG-IQ, but does not inform the BIG-IP (it's likely down anyway)
196 const body = {
197 username: this.bigIp.user || 'dummyUser',
198 password: this.bigIp.password || 'dummyPassword',
199 id: regKeyMember.member.id
200 };
201
202 return bigIqControl.delete(
203 // eslint-disable-next-line max-len
204 `${LICENSE_PATH}${poolUuid}/offerings/${regKeyMember.regKey}/members/${regKeyMember.member.id}`,
205 body
206 );
207 }
208 return q();
209 });
210};
211
212/**
213 * Gets the license timeout to use
214 *
215 * This is here so that it can be overridden by test code
216 *
217 * @returns the license timeout
218 */
219BigIq52LicenseProvider.prototype.getLicenseTimeout = function getLicenseTimeout() {
220 return LICENSE_TIMEOUT;
221};
222
223function licenseFromPool(bigIqControl, bigIpMgmtAddress, bigIpMgmtPort, poolUuid) {
224 const deferred = q.defer();
225
226 getValidRegKey.call(this, bigIqControl, poolUuid)
227 .then((regKey) => { // eslint-disable-line consistent-return
228 if (regKey) {
229 return tryRegKey.call(this, bigIqControl, bigIpMgmtAddress, bigIpMgmtPort, poolUuid, regKey);
230 }
231 deferred.reject(new Error('No valid reg keys found.'));
232 })
233 .then(() => {
234 deferred.resolve();
235 })
236 .catch((err) => {
237 this.logger.info(err);
238 deferred.reject(err);
239 });
240
241 return deferred.promise;
242}
243
244function getPoolUuid(bigIqControl, poolName) {
245 let poolUuid;
246
247 return bigIqControl.list(`${LICENSE_PATH}?$select=id,name`)
248 .then((poolResponse) => {
249 let pools;
250
251 if (!poolResponse) {
252 pools = [];
253 } else if (!Array.isArray(poolResponse)) {
254 pools = [poolResponse];
255 } else {
256 pools = poolResponse.slice();
257 }
258
259 for (let i = 0; i < pools.length; i++) {
260 if (pools[i].name === poolName) {
261 poolUuid = pools[i].id;
262 break;
263 }
264 }
265
266 if (poolUuid) {
267 return poolUuid;
268 }
269 return q.reject(new Error(`No license pool found with name: ${poolName}`));
270 });
271}
272
273function getValidRegKey(bigIqControl, poolUuid) {
274 this.logger.debug('Getting reg keys in pool');
275 return getLicensesInPool(bigIqControl, poolUuid)
276 .then((licensesResponse) => {
277 const now = new Date();
278 const deferred = q.defer();
279
280 let licenses;
281
282 if (!licensesResponse) {
283 licenses = [];
284 } else if (!Array.isArray(licensesResponse)) {
285 licenses = [licensesResponse];
286 } else {
287 licenses = licensesResponse.slice();
288 }
289
290 const findValidLicense = function (index) {
291 let currentIndex = index;
292
293 let license;
294
295 if (index > licenses.length - 1) {
296 logger.info('No valid licenses available.');
297 deferred.resolve();
298 } else {
299 license = licenses[currentIndex];
300 if (
301 license.licenseState &&
302 license.licenseState.licenseStartDateTime &&
303 license.licenseState.licenseEndDateTime &&
304 new Date(license.licenseState.licenseStartDateTime) < now &&
305 now < new Date(license.licenseState.licenseEndDateTime)
306 ) {
307 logger.silly(license.licenseState.registrationKey, 'is active');
308 getMembersForKey(bigIqControl, poolUuid, license.licenseState.registrationKey)
309 .then((response) => {
310 logger.silly(
311 'reg key',
312 license.licenseState.registrationKey,
313 'members',
314 response
315 );
316
317 if (Array.isArray(response) && response.length === 0) {
318 logger.silly(license.licenseState.registrationKey, 'is available');
319 deferred.resolve(license.licenseState.registrationKey);
320 } else {
321 currentIndex += 1;
322 findValidLicense(currentIndex);
323 }
324 })
325 .catch((err) => {
326 logger.debug('error while iterating licenses', err);
327 currentIndex += 1;
328 findValidLicense(currentIndex);
329 });
330 } else {
331 logger.debug(license.licenseState.registrationKey, 'is not active');
332 currentIndex += 1;
333 findValidLicense(currentIndex);
334 }
335 }
336 };
337
338 findValidLicense(0);
339
340 return deferred.promise;
341 });
342}
343
344function getLicensesInPool(bigIqControl, poolUuid) {
345 return bigIqControl.list(`${LICENSE_PATH}${poolUuid}/offerings?$select=licenseState`);
346}
347
348function getMembersForKey(bigIqControl, poolUuid, regKey) {
349 return bigIqControl.list(`${LICENSE_PATH}${poolUuid}/offerings/${regKey}/members`);
350}
351
352function tryRegKey(bigIqControl, bigIpMgmtAddress, bigIpMgmtPort, poolUuid, regKey) {
353 this.logger.info('Requesting license using', regKey);
354
355 // Even though this is the 5.2 license provider, the same API exists on newer versions.
356 // The only change is that instead of putting the port in the deviceAddress, it is a
357 // separate field
358 let deviceAddress;
359 let port;
360
361 if (util.versionCompare(this.version, '5.2.0') > 0) {
362 deviceAddress = bigIpMgmtAddress;
363 port = bigIpMgmtPort;
364 } else {
365 deviceAddress = `${bigIpMgmtAddress}:${bigIpMgmtPort}`;
366 }
367
368 const body = {
369 deviceAddress,
370 username: this.bigIp.user,
371 password: this.bigIp.password
372 };
373
374 if (port) {
375 body.httpsPort = port;
376 }
377
378 return bigIqControl.create(
379 `${LICENSE_PATH}${poolUuid}/offerings/${regKey}/members`,
380 body
381 )
382 .then((response) => {
383 this.logger.debug(response);
384
385 let status;
386 let memberId;
387
388 const isLicensed = function () {
389 return bigIqControl.list(`${LICENSE_PATH}${poolUuid}/offerings/${regKey}/members/${memberId}`)
390 .then((membersResponse) => {
391 status = membersResponse.status;
392 logger.verbose('Current licensing status:', status);
393 if (status === 'LICENSED') {
394 return q();
395 }
396
397 return q.reject();
398 });
399 };
400
401 if (response) {
402 status = response.status;
403 memberId = response.id;
404 this.logger.debug('Current licensing state:', status);
405 this.logger.silly('Member UUID:', memberId);
406
407 if (status === 'LICENSED') {
408 return q();
409 }
410
411 this.logger.verbose('Waiting to be LICENSED.');
412 return util.tryUntil(this, this.getLicenseTimeout(), isLicensed)
413 .then(() => {
414 this.logger.info('Successfully licensed');
415 return q();
416 })
417 .catch((err) => {
418 this.logger.info('Failed to license', err);
419 return q.reject(new Error('Giving up on licensing via BIG-IQ.'));
420 });
421 }
422
423 return q.reject(new Error('No resposnse for pool/offerings/key/members'));
424 });
425}
426
427module.exports = BigIq52LicenseProvider;