UNPKG

10.4 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 assert = require('assert');
20const q = require('q');
21const util = require('util');
22const BigIp = require('./bigIp');
23const DnsProvider = require('./dnsProvider');
24
25util.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 */
37function 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 */
81GtmDnsProvider.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 */
104GtmDnsProvider.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
157function 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
184function 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
246function 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
270module.exports = GtmDnsProvider;