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 assert = require('assert');
|
20 | const q = require('q');
|
21 | const util = require('util');
|
22 | const BigIp = require('./bigIp');
|
23 | const DnsProvider = require('./dnsProvider');
|
24 |
|
25 | util.inherits(GtmDnsProvider, DnsProvider);
|
26 |
|
27 | /**
|
28 | * Constructor.
|
29 | * @class
|
30 | *
|
31 | * @param {Ojbect} [options] - Options for the instance.
|
32 | * @param {Object} [options.clOptions] - Command line options if called from a script.
|
33 | * @param {Object} [options.logger] - Logger to use. Or, pass loggerOptions to get your own logger.
|
34 | * @param {Object} [options.loggerOptions] - Options for the logger.
|
35 | * See {@link module:logger.getLogger} for details.
|
36 | */
|
37 | function GtmDnsProvider(options) {
|
38 | const logger = options ? options.logger : undefined;
|
39 | const loggerOptions = options ? options.loggerOptions : undefined;
|
40 |
|
41 | GtmDnsProvider.super_.call(this, options);
|
42 | this.bigIp = new BigIp({
|
43 | logger,
|
44 | loggerOptions
|
45 | });
|
46 | }
|
47 |
|
48 | /**
|
49 | * Initialize class
|
50 | *
|
51 | * Override for implementation specific initialization needs (read info
|
52 | * from cloud provider, read database, etc.). Called at the start of
|
53 | * processing.
|
54 | *
|
55 | * @param {Object} providerOptions - Provider specific options.
|
56 | * @param {String} providerOptions.host - BIG-IP GTM management IP or hostname
|
57 | * to which to send commands.
|
58 | * @param {String} providerOptions.user - BIG-IP GTM admin user name.
|
59 | * @param {String} providerOptions.port - BIG-IP GTM management SSL port to connect to.
|
60 | * Default 443.
|
61 | * @param {String} providerOptions.password - BIG-IP GTM admin user password.
|
62 | * Use this or passwordUrl.
|
63 | * @param {String} providerOptions.passwordUrl - URL (file, http(s), arn) to location that contains
|
64 | * BIG-IP GTM admin user password. Use this or password.
|
65 | * @param {String} [providerOptions.passwordEncrypted] - Indicates that the BIG-IP GTM password is encrypted
|
66 | * (either with encryptDataToFile or generatePassword).
|
67 | * @param {String} providerOptions.serverName - GSLB server name.
|
68 | * @param {String} providerOptions.poolName - GSLB pool name.
|
69 | * @param {String} [providerOptions.datacenter] - GSLB data center. Required if creating the
|
70 | * GTM server.
|
71 | * @param {String} [providerOptions.vsMonitor] - Full path to monitor for the virtual server.
|
72 | * Default is existing monitor.
|
73 | * @param {String} [providerOptions.poolMonitor] - Full path to monitor for the pool.
|
74 | * Default is existing monitor.
|
75 | * @param {String} [providerOptions.loadBalancingMode] - Load balancing mode for the pool.
|
76 | * Default is existing load balancing mode.
|
77 | * @param {String} [providerOptions.partition] - Partition of pool and server. Default is Common.
|
78 | *
|
79 | * @returns {Promise} A promise which will be resolved when init is complete.
|
80 | */
|
81 | GtmDnsProvider.prototype.init = function init(providerOptions) {
|
82 | assert.equal(typeof providerOptions, 'object', 'providerOptions is required');
|
83 | assert.equal(typeof providerOptions.serverName, 'string', 'providerOptions.serverName is required');
|
84 | assert.equal(typeof providerOptions.poolName, 'string', 'providerOptions.poolName is required');
|
85 |
|
86 | this.providerOptions = providerOptions;
|
87 | return q();
|
88 | };
|
89 |
|
90 | /**
|
91 | * Updates DNS records with the given instances
|
92 | *
|
93 | * @param {Object} instances - Array of instances, each having the form
|
94 | *
|
95 | * {
|
96 | * name: name for instance,
|
97 | * ip: ip address,
|
98 | * port: port
|
99 | * }
|
100 | *
|
101 | * @returns {Promise} A promise which will be resolved with the instance ID of the
|
102 | * elected primary.
|
103 | */
|
104 | GtmDnsProvider.prototype.update = function update(instances) {
|
105 | this.logger.info('Initializing BIG-IP.');
|
106 | return this.bigIp.init(
|
107 | this.providerOptions.host,
|
108 | this.providerOptions.user,
|
109 | this.providerOptions.password || this.providerOptions.passwordUrl,
|
110 | {
|
111 | port: this.providerOptions.port,
|
112 | passwordIsUrl: typeof this.providerOptions.passwordUrl !== 'undefined',
|
113 | passwordEncrypted: this.providerOptions.passwordEncrypted
|
114 | }
|
115 | )
|
116 | .then(() => {
|
117 | return this.bigIp.ready();
|
118 | })
|
119 | .then(() => {
|
120 | // Create the datacenter if it does not exist
|
121 | return verifyDatacenter.call(this);
|
122 | })
|
123 | .then(() => {
|
124 | // Create the GTM server if it does not exist
|
125 | return verifyGtmServer.call(this);
|
126 | })
|
127 | .then(() => {
|
128 | // Create the pool if it does not exist
|
129 | return verifyPool.call(this);
|
130 | })
|
131 | .then(() => {
|
132 | const options = {
|
133 | datacenter: this.providerOptions.datacenter,
|
134 | monitor: this.providerOptions.vsMonitor
|
135 | };
|
136 |
|
137 | if (this.providerOptions.partition) {
|
138 | this.bigIp.gtm.setPartition(this.providerOptions.partition);
|
139 | }
|
140 |
|
141 | return this.bigIp.gtm.updateServer(this.providerOptions.serverName, instances, options);
|
142 | })
|
143 | .then(() => {
|
144 | const options = {
|
145 | monitor: this.providerOptions.poolMonitor,
|
146 | loadBalancingMode: this.providerOptions.loadBalancingMode
|
147 | };
|
148 | return this.bigIp.gtm.updatePool(
|
149 | this.providerOptions.poolName,
|
150 | this.providerOptions.serverName,
|
151 | instances,
|
152 | options
|
153 | );
|
154 | });
|
155 | };
|
156 |
|
157 | function verifyDatacenter() {
|
158 | if (this.providerOptions.datacenter) {
|
159 | return this.bigIp.list('/tm/gtm/datacenter')
|
160 | .then((datacenters) => {
|
161 | if (datacenters) {
|
162 | for (let i = 0; i < datacenters.length; i++) {
|
163 | if (datacenters[i].name === this.providerOptions.datacenter) {
|
164 | return q();
|
165 | }
|
166 | }
|
167 | }
|
168 |
|
169 | return this.bigIp.create(
|
170 | '/tm/gtm/datacenter',
|
171 | {
|
172 | name: this.providerOptions.datacenter
|
173 | }
|
174 | );
|
175 | })
|
176 | .catch((err) => {
|
177 | this.logger.info('Error verifying GTM datacenter', err);
|
178 | return q.reject(new Error(`verifyDatacenter: ${err}`));
|
179 | });
|
180 | }
|
181 | return q();
|
182 | }
|
183 |
|
184 | function verifyGtmServer() {
|
185 | const usedAddresses = [];
|
186 |
|
187 | function collectAddresses(server) {
|
188 | server.addresses.forEach((address) => {
|
189 | usedAddresses.push(address.name);
|
190 | });
|
191 | }
|
192 |
|
193 | // when creating the server, we have to give it an IP address
|
194 | // we use 192.0.2.X as that is defined in https://tools.ietf.org/html/rfc5737
|
195 | // for use as a documentation server and is not likely to be in use
|
196 | function getFirstAvailableAddress() {
|
197 | let highestLastOctet = 0;
|
198 | usedAddresses.forEach((address) => {
|
199 | const octets = address.split('.');
|
200 | const thisLastOctet = parseInt(octets[3], 10);
|
201 | if (thisLastOctet > highestLastOctet) {
|
202 | highestLastOctet = thisLastOctet;
|
203 | }
|
204 | });
|
205 | highestLastOctet += 1;
|
206 | if (highestLastOctet > 255) {
|
207 | this.logger.error('No available addresses for GTM server');
|
208 | return null;
|
209 | }
|
210 | return `192.0.2.${highestLastOctet}`;
|
211 | }
|
212 |
|
213 | return this.bigIp.list('/tm/gtm/server')
|
214 | .then((servers) => {
|
215 | if (servers) {
|
216 | for (let i = 0; i < servers.length; i++) {
|
217 | if (servers[i].name === this.providerOptions.serverName) {
|
218 | return q();
|
219 | }
|
220 |
|
221 | collectAddresses(servers[i]);
|
222 | }
|
223 | }
|
224 |
|
225 | if (!this.providerOptions.datacenter) {
|
226 | return q.reject(new Error('datacenter is required when creating the server'));
|
227 | }
|
228 |
|
229 | const dummyAddress = getFirstAvailableAddress();
|
230 | return this.bigIp.create(
|
231 | '/tm/gtm/server',
|
232 | {
|
233 | name: this.providerOptions.serverName,
|
234 | datacenter: this.providerOptions.datacenter,
|
235 | product: 'generic-host',
|
236 | addresses: [dummyAddress]
|
237 | }
|
238 | );
|
239 | })
|
240 | .catch((err) => {
|
241 | this.logger.info('Error verifying GTM server', err);
|
242 | return q.reject(new Error(`verifyGtmServer: ${err}`));
|
243 | });
|
244 | }
|
245 |
|
246 | function verifyPool() {
|
247 | return this.bigIp.list('/tm/gtm/pool/a')
|
248 | .then((pools) => {
|
249 | if (pools) {
|
250 | for (let i = 0; i < pools.length; i++) {
|
251 | if (pools[i].name === this.providerOptions.poolName) {
|
252 | return q();
|
253 | }
|
254 | }
|
255 | }
|
256 |
|
257 | return this.bigIp.create(
|
258 | '/tm/gtm/pool/a',
|
259 | {
|
260 | name: this.providerOptions.poolName
|
261 | }
|
262 | );
|
263 | })
|
264 | .catch((err) => {
|
265 | this.logger.info('Error verifying GTM pool', err);
|
266 | return q.reject(new Error(`verifyPool: ${err}`));
|
267 | });
|
268 | }
|
269 |
|
270 | module.exports = GtmDnsProvider;
|