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 | ;
|
18 |
|
19 | const q = require('q');
|
20 | const util = require('./util');
|
21 | const Logger = require('./logger');
|
22 |
|
23 | const LICENSE_PATH = '/cm/device/tasks/licensing/pool/member-management/';
|
24 | const LICENSE_TIMEOUT = { maxRetries: 40, retryIntervalMs: 5000 };
|
25 | const ALREADY_LICENSED_LIMIT = 5;
|
26 |
|
27 | let 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 | */
|
42 | function 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 | */
|
74 | BigIq53LicenseProvider.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 | */
|
96 | BigIq53LicenseProvider.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 | */
|
160 | BigIq53LicenseProvider.prototype.getLicenseTimeout = function getLicenseTimeout() {
|
161 | return LICENSE_TIMEOUT;
|
162 | };
|
163 |
|
164 | function 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 |
|
247 | module.exports = BigIq53LicenseProvider;
|