1 | /**
|
2 | * Copyright 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 IControl = require('./iControl');
|
22 |
|
23 | /**
|
24 | * Functions that only pertain to BIG-IQ clustering, not BIG-IP
|
25 | *
|
26 | * @mixin
|
27 | */
|
28 | const bigIqClusterMixins = {
|
29 |
|
30 | /**
|
31 | * Configures the specified failover peer as a secondary in a BIQ-IQ High Availability configuration
|
32 | *
|
33 | * @param {String} failoverPeerIp - IP address of the failover peer
|
34 | * @param {String} failoverPeerUsername - Username of the admin user on the failover peer
|
35 | * @param {String} failoverPeerPassword - Password of the admin user on the failover peer
|
36 | * @param {String} rootPassword - Password of the root user on the failover peer
|
37 | * @param {Object} [testOpts] - testOpts - Options used during testing
|
38 | * @param {Object} [testOpts.bigIp] - BigIp object to use for testing
|
39 | *
|
40 | * @returns {Promise} A Promise which is resolved with the status of the BIG-IQ peering task
|
41 | */
|
42 | addSecondary(failoverPeerIp, failoverPeerUsername, failoverPeerPassword, rootPassword, testOpts) {
|
43 | return q()
|
44 | .then(() => {
|
45 | let remoteBigIp;
|
46 | if (testOpts && typeof testOpts.bigIp !== 'undefined') {
|
47 | remoteBigIp = testOpts.bigIp;
|
48 | } else {
|
49 | const BigIp = require('./bigIp'); // eslint-disable-line global-require
|
50 | remoteBigIp = new BigIp({ loggerOptions: this.loggerOptions });
|
51 | }
|
52 | // wait for peer to be ready
|
53 | return this.waitForPeerReady(
|
54 | remoteBigIp,
|
55 | failoverPeerIp,
|
56 | failoverPeerUsername,
|
57 | failoverPeerPassword,
|
58 | {
|
59 | maxRetries: 12,
|
60 | retryIntervalMs: 75000
|
61 | }
|
62 | );
|
63 | })
|
64 | .then(() => {
|
65 | return getFailoverPeerFingerprint.call(this, failoverPeerIp);
|
66 | })
|
67 | .then((fingerprint) => {
|
68 | this.logger.debug('Retrieved ssh fingerprint for failover peer');
|
69 | return addFailoverPeer.call(
|
70 | this,
|
71 | fingerprint,
|
72 | failoverPeerUsername,
|
73 | failoverPeerPassword,
|
74 | rootPassword,
|
75 | failoverPeerIp
|
76 | );
|
77 | })
|
78 | .then((response) => {
|
79 | this.logger.debug('waiting for devices to complete peering');
|
80 | return this.waitForPeered(response);
|
81 | })
|
82 | .catch((err) => {
|
83 | return q.reject(err);
|
84 | });
|
85 | },
|
86 |
|
87 | /**
|
88 | * Polls the status of a BIG-IQ high-availability peering task.
|
89 | *
|
90 | * @param {String} task - Task ID value from BIG-IQ add-peer-task call
|
91 | * @param {Object} [retryOptions] - Options for retrying the request.
|
92 | * @param {Integer} [retryOptions.maxRetries] - Number of times to retry if first
|
93 | * @param {Integer} [retryOptions.retryIntervalMs] - Milliseconds between retries.
|
94 | *
|
95 | * @returns {Promise} A Promise which is resolved with the peering task response
|
96 | * or a peering task failure message
|
97 | */
|
98 | waitForPeered(task, retryOptions) {
|
99 | const taskId = task.id;
|
100 |
|
101 | const func = function () {
|
102 | return this.core.list(`/shared/ha/add-peer-task/${taskId}`)
|
103 | .then((response) => {
|
104 | this.logger.silly(`${response.progress} for task ${taskId}, on step ${response.step}`);
|
105 | if (response.status === 'FINISHED') {
|
106 | this.logger.silly('peering task completed');
|
107 | return q(response);
|
108 | } else if (response.status === 'FAILED') {
|
109 | // force 400 error to break util.tryUntil loop
|
110 | const taskError = {
|
111 | message: response && response.errorMessage
|
112 | ? response.errorMessage
|
113 | : 'peering task FAILED',
|
114 | code: 400
|
115 | };
|
116 | return q.reject(taskError);
|
117 | }
|
118 | this.logger.silly(`peering task ${taskId} not yet complete`);
|
119 | return q.reject();
|
120 | })
|
121 | .catch((err) => {
|
122 | this.logger.debug(
|
123 | 'peering task not yet complete',
|
124 | err && err.message ? err.message : err
|
125 | );
|
126 | return q.reject(err);
|
127 | });
|
128 | };
|
129 |
|
130 | return util.tryUntil(this, retryOptions || util.DEFAULT_RETRY, func);
|
131 | },
|
132 |
|
133 | /**
|
134 | * Polls the failover peer BIG-IQ in a high-availabilty configuration to see if the failover peer is
|
135 | * ready to accept a peering task request.
|
136 | *
|
137 | * @param {Object} remoteBigIp - BIG-IQ instance for the failover peer BIG-IQ
|
138 | * @param {String} failoverPeerIp - IP address of the failover peer
|
139 | * @param {String} failoverPeerUsername - Username of the admin user on the failover peer
|
140 | * @param {String} failoverPeerPassword - Password of the admin user on the failover peer
|
141 | * @param {Integer} [retryOptions.maxRetries] - Number of times to retry if first
|
142 | * @param {Integer} [retryOptions.retryIntervalMs] - Milliseconds between retries.
|
143 | *
|
144 | * @returns {Promise} A Promise which is resolved when the failover peer BIG-IQ is ready, or an error
|
145 | * if the BIG-IQ is not ready within the retry period
|
146 | */
|
147 | waitForPeerReady(remoteBigIp, failoverPeerIp, failoverPeerUsername, failoverPeerPassword, retryOptions) {
|
148 | const func = function () {
|
149 | return q()
|
150 | .then(() => {
|
151 | // Determine if user is enabled on failover peer, using a very long retry
|
152 | // so we do not lock out the user on the failover peer
|
153 | const icontrol = this.icontrol || new IControl(
|
154 | {
|
155 | port: '443',
|
156 | host: failoverPeerIp.trim(),
|
157 | user: failoverPeerUsername.trim(),
|
158 | password: failoverPeerPassword.trim(),
|
159 | basePath: '/mgmt',
|
160 | strict: false,
|
161 | }
|
162 | );
|
163 | icontrol.authToken = null;
|
164 | return icontrol.create('/shared/authn/login',
|
165 | {
|
166 | username: failoverPeerUsername,
|
167 | password: failoverPeerPassword
|
168 | });
|
169 | })
|
170 | .then(() => {
|
171 | return remoteBigIp.init(
|
172 | failoverPeerIp,
|
173 | failoverPeerUsername,
|
174 | failoverPeerPassword,
|
175 | {
|
176 | port: 443,
|
177 | passwordIsUrl: false,
|
178 | passwordEncrypted: false
|
179 | }
|
180 | );
|
181 | })
|
182 | .then(() => {
|
183 | // Check to see if secondary BIG-IQ is configured for clustering
|
184 | return remoteBigIp.list('/shared/identified-devices/config/discovery');
|
185 | })
|
186 | .then((response) => {
|
187 | if (response && response.discoveryAddress === failoverPeerIp) {
|
188 | this.logger.debug('Failover peer is ready for peering');
|
189 | } else {
|
190 | const message = 'Failover peer not ready for peering. Root not yet enabled on peer';
|
191 | this.logger.silly(message);
|
192 |
|
193 | return q.reject({ message });
|
194 | }
|
195 | return q(response);
|
196 | })
|
197 | .catch((err) => {
|
198 | this.logger.silly(
|
199 | 'Failover peer not yet ready for peering.',
|
200 | err && err.message ? err.message : err
|
201 | );
|
202 | return q.reject(err);
|
203 | });
|
204 | };
|
205 |
|
206 | return util.tryUntil(this, retryOptions || util.DEFAULT_RETRY, func);
|
207 | }
|
208 | };
|
209 |
|
210 | function getFailoverPeerFingerprint(ipAddress) {
|
211 | return this.core.list(`/shared/ssh-trust-setup?ipAddress=${ipAddress}`)
|
212 | .then((response) => {
|
213 | return q(response.fingerprint);
|
214 | });
|
215 | }
|
216 |
|
217 | function addFailoverPeer(fingerprint, userName, password, rootPassword, ipAddress) {
|
218 | this.logger.debug('Adding BIG-IQ as failover peer');
|
219 | return this.core.create(
|
220 | '/shared/ha/add-peer-task',
|
221 | {
|
222 | fingerprint,
|
223 | userName,
|
224 | password,
|
225 | rootPassword,
|
226 | ipAddress
|
227 | },
|
228 | null,
|
229 | null,
|
230 | { silent: true }
|
231 | );
|
232 | }
|
233 | module.exports = bigIqClusterMixins;
|