UNPKG

9.8 kBJavaScriptView Raw
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'use strict';
18
19const q = require('q');
20const util = require('./util');
21const IControl = require('./iControl');
22
23/**
24 * Functions that only pertain to BIG-IQ clustering, not BIG-IP
25 *
26 * @mixin
27 */
28const 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
210function getFailoverPeerFingerprint(ipAddress) {
211 return this.core.list(`/shared/ssh-trust-setup?ipAddress=${ipAddress}`)
212 .then((response) => {
213 return q(response.fingerprint);
214 });
215}
216
217function 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}
233module.exports = bigIqClusterMixins;