UNPKG

26.5 kBJavaScriptView Raw
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'use strict';
18
19const q = require('q');
20const Logger = require('./logger');
21const BigIq = require('./bigIq');
22
23const MAX_STORED_INSTANCE_AGE = 60000 * 60 * 24; // 1 day
24
25/**
26 * Constructor.
27 * @class
28 * @classdesc
29 * Abstract cloud provider implementation.
30 *
31 * This class should be inherited from to implement cloud-provider
32 * specific implementations. Any method in this class that throws
33 * must be overridded. Methods that do not throw may be optionally
34 * overridden.
35 *
36 * @param {Ojbect} [options] - Options for the instance.
37 * @param {Object} [options.clOptions] - Command line options if called from a script.
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 */
42function CloudProvider(options) {
43 const logger = options ? options.logger : undefined;
44 let loggerOptions = options ? options.loggerOptions : undefined;
45
46 this.options = {};
47 if (options) {
48 Object.keys(options).forEach((option) => {
49 this.options[option] = options[option];
50 });
51 }
52
53 this.clOptions = {};
54 if (options && options.clOptions) {
55 Object.keys(options.clOptions).forEach((option) => {
56 this.clOptions[option] = options.clOptions[option];
57 });
58 }
59
60 // Holder for supported features. If an implementation supports an features,
61 // set them to true in this map
62 this.features = {};
63
64 if (logger) {
65 this.logger = logger;
66 } else {
67 loggerOptions = loggerOptions || { logLevel: 'none' };
68 loggerOptions.module = module;
69 this.logger = Logger.getLogger(loggerOptions);
70 this.loggerOptions = loggerOptions;
71 }
72}
73
74// Public constants...
75// optional features that a provider can support...
76
77// ability to message other instances in the scale set
78CloudProvider.FEATURE_MESSAGING = 'FEATURE_MESSAGING';
79// supports stroing/retrieving public/private keys
80CloudProvider.FEATURE_ENCRYPTION = 'FEATURE_ENCRYPTION';
81// password is the same for all members of a cluster
82CloudProvider.FEATURE_SHARED_PASSWORD = 'FEATURE_SHARED_PASSWORD';
83
84// messages that can be sent if FEATURE_MESSAGING is supported...
85
86// add an instance to the cluster
87CloudProvider.MESSAGE_ADD_TO_CLUSTER = 'ADD_TO_CLUSTER';
88// a sync has been completed, you may need to update your password
89CloudProvider.MESSAGE_SYNC_COMPLETE = 'SYNC_COMPLETE';
90
91// For use in getPrimaryStatus...
92CloudProvider.STATUS_OK = 'OK';
93CloudProvider.STATUS_NOT_EXTERNAL = 'NOT_EXTERNAL';
94CloudProvider.STATUS_NOT_IN_CLOUD_LIST = 'NOT_IN_CLOUD_LIST';
95CloudProvider.STATUS_VERSION_NOT_UP_TO_DATE = 'VERSION_NOT_UP_TO_DATE';
96CloudProvider.STATUS_UNKNOWN = 'UNKNOWN';
97
98/**
99 * Initialize class
100 *
101 * Override for implementation specific initialization needs (read info
102 * from cloud provider, read database, etc.). Called at the start of
103 * processing.
104 *
105 * @param {Object} providerOptions - Provider specific options.
106 * @param {Object} [options] - Options for this instance.
107 * @param {Boolean} [options.autoscale] - Whether or not this instance will be used for autoscaling.
108 *
109 * @returns {Promise} A promise which will be resolved when init is complete.
110 */
111CloudProvider.prototype.init = function init(providerOptions, options) {
112 this.logger.debug('No override for CloudProvider.init', providerOptions, options);
113 return q();
114};
115
116/**
117 * BIG-IP is now ready and providers can run BIG-IP functions
118 * if necessary
119 *
120 * @returns {Promise} A promise which will be resolved when init is complete.
121 */
122CloudProvider.prototype.bigIpReady = function bigIpReady() {
123 this.logger.debug('No override for CloudProvider.bigIpReady');
124 return q();
125};
126
127/**
128 * Gets data from a provider specific URI
129 *
130 * Override for implementations that wish to allow retrieval of data from a
131 * provider specific URI (for example, an ARN to an S3 bucket).
132 *
133 * @abstract
134 *
135 * @param {String} uri - The cloud-specific URI of the resource.
136 *
137 * @returns {Promise} A promise which will be resolved with the data from the URI
138 * or rejected if an error occurs.
139 */
140CloudProvider.prototype.getDataFromUri = function getDataFromUri(uri) {
141 throw new Error('Unimplemented abstract method CloudProvider.getDataFromUri', uri);
142};
143
144/**
145 * Gets the instance ID of this instance
146 *
147 * @abstract
148 *
149 * @returns {Promise} A promise which will be resolved with the instance ID of this instance
150 * or rejected if an error occurs;
151 */
152CloudProvider.prototype.getInstanceId = function getInstanceId() {
153 throw new Error('Unimplemented abstract method CloudProvider.getInstanceId');
154};
155
156/**
157 * Gets info for each instance
158 *
159 * Retrieval is cloud specific. Likely either from the cloud infrastructure
160 * itself, stored info that we have in a database, or both.
161 *
162 * @abstract
163 *
164 * @param {Object} [options] - Optional parameters
165 * @param {String} [options.externalTag] - Also look for instances with this
166 * tag (outside of the autoscale group/set)
167 *
168 * @returns {Promise} A promise which will be resolved with a dictionary of instances
169 * keyed by instance ID. Each instance value should be:
170 *
171 * {
172 * isPrimary: <Boolean>,
173 * hostname: <String>,
174 * mgmtIp: <String>,
175 * privateIp: <String>,
176 * publicIp: <String>,
177 * providerVisible: <Boolean> (does the cloud provider know about this instance),
178 * external: <Boolean> (true if this instance is external to the autoscale group/set)
179 * }
180 */
181CloudProvider.prototype.getInstances = function getInstances(options) {
182 throw new Error('Unimplemented abstract method CloudProvider.getInstances', options);
183};
184
185
186/**
187 * Called to delete a stored UCS file based on filename
188 *
189 * @param {String} UCS filename
190 *
191 * @returns {Promise} returns a promise which resolves with status of delete operation
192 * or gets rejected in a case of failures
193 *
194 */
195CloudProvider.prototype.deleteStoredUcs = function deleteStoredUcs() {
196 this.logger.debug('No override for method CloudProvider.deleteStoredUcs');
197 return q.resolve();
198};
199
200/**
201 * Searches for NICs that have a given tag.
202 *
203 * @param {Object} tag - Tag to search for. Tag is of the format:
204 *
205 * {
206 * key: optional key
207 * value: value to search for
208 * }
209 *
210 * @returns {Promise} A promise which will be resolved with an array of instances.
211 * Each instance value should be:
212 *
213 * {
214 * id: NIC ID,
215 * ip: {
216 * public: public IP (or first public IP on the NIC),
217 * private: private IP (or first private IP on the NIC)
218 * }
219 * }
220 */
221CloudProvider.prototype.getNicsByTag = function getNicsByTag(tag) {
222 this.logger.debug('No override for CloudProvider.getNicsByTag', tag);
223 return q();
224};
225
226/**
227 * Searches for VMs that have a given tag.
228 *
229 * @param {Object} tag - Tag to search for. Tag is of the format:
230 *
231 * {
232 * key: optional key
233 * value: value to search for
234 * }
235 *
236 * @returns {Promise} A promise which will be resolved with an array of instances.
237 * Each instance value should be:
238 *
239 * {
240 * id: instance ID,
241 * ip: {
242 * public: public IP (or first public IP on the first NIC),
243 * private: private IP (or first private IP on the first NIC)
244 * }
245 * }
246 */
247CloudProvider.prototype.getVmsByTag = function getVmsByTag(tag) {
248 this.logger.debug('No override for CloudProvider.getVmsByTag', tag);
249 return q();
250};
251
252/**
253 * Elects a new primary instance from the available instances
254 *
255 * @abstract
256 *
257 * @param {Object} instances - Dictionary of instances as returned by getInstances.
258 *
259 * @returns {Promise} A promise which will be resolved with the instance ID of the
260 * elected primary.
261 */
262CloudProvider.prototype.electPrimary = function electPrimary(instances) {
263 throw new Error('Unimplemented abstract method CloudProvider.electPrimary', instances);
264};
265
266/**
267 * Called to retrieve primary instance credentials
268 *
269 * Must be implemented if FEATURE_MESSAGING is not supported.
270 *
271 * If FEATURE_MESSAGING is not supported, when joining a cluster we need the
272 * username and password for the primary instance.
273 *
274 * If FEATURE_MESSAGING is supported, the primary will be sent a message to
275 * add an instance.
276 *
277 * Management IP and port are passed in so that credentials can be
278 * validated desired.
279 *
280 * @abstract
281 *
282 * @param {String} mgmtIp - Management IP of primary.
283 * @param {String} port - Management port of primary.
284 *
285 * @returns {Promise} A promise which will be resolved with:
286 *
287 * {
288 * username: <admin_user>,
289 * password: <admin_password>
290 * }
291 */
292CloudProvider.prototype.getPrimaryCredentials = function getPrimaryCredentials(mgmtIp, mgmtPort) {
293 if (!this.hasFeature(CloudProvider.FEATURE_MESSAGING)) {
294 throw new Error(
295 'Unimplemented abstract method CloudProvider.getPrimaryCredentials',
296 mgmtIp,
297 mgmtPort
298 );
299 } else {
300 this.logger.debug('No override for CloudProvider.getPrimaryCredentials');
301 return q(true);
302 }
303};
304
305/**
306 * Called to store primary credentials
307 *
308 * When joining a cluster we need the username and password for the
309 * primary instance. This method is called to tell us that we are
310 * the primary and we should store our credentials if we need to store
311 * them for later retrieval in getPrimaryCredentials.
312 *
313 * @returns {Promise} A promise which will be resolved when the operation
314 * is complete
315 */
316CloudProvider.prototype.putPrimaryCredentials = function putPrimaryCredentials() {
317 this.logger.debug('No override for CloudProvider.putPrimaryCredentials');
318 return q();
319};
320
321/**
322 * Gets info on what this instance thinks the primary status is
323 *
324 * Info is retrieval is cloud specific. Likely either from the cloud infrastructure
325 * itself, stored info that we have in a database, or both.
326 *
327 * @returns {Promise} A promise which will be resolved with a dictionary of primary
328 * status:
329 *
330 * {
331 * "instanceId": primaryInstanceId
332 * "status": CloudProvider.STATUS_*
333 * "lastUpdate": Date,
334 * "lastStatusChange": Date
335 * }
336 *
337 */
338CloudProvider.prototype.getPrimaryStatus = function getPrimaryStatus() {
339 this.logger.debug('No override for CloudProvider.getPrimaryStatus');
340 return q();
341};
342
343/**
344 * Gets the public key for an instanceId.
345 *
346 * @param {String} instanceId - Instance ID to validate as a valid primary.
347 *
348 * @returns {Promise} A promise which will be resolved when the operation
349 * is complete
350 */
351CloudProvider.prototype.getPublicKey = function getPublicKey(instanceId) {
352 if (this.hasFeature(CloudProvider.FEATURE_ENCRYPTION)) {
353 throw new Error('Unimplemented abstract method CloudProvider.getPublicKey', instanceId);
354 } else {
355 this.logger.debug('No override for CloudProvider.getPublicKey');
356 return q(true);
357 }
358};
359
360/**
361 * Determines if the provider supports a feature.
362 *
363 * @param {String} feature - Feature to check for
364 *
365 * @returns {Boolean} Whether or not the provider supports the feature
366 */
367CloudProvider.prototype.hasFeature = function hasFeature(feature) {
368 return !!this.features[feature];
369};
370
371/**
372 * Stores the public key for an instanceId.
373 *
374 * The public key should later be able to retrieved given the instanceId.
375 * Must be implemented if provider supports FEATURE_ENCRYPTION.
376 *
377 * @param {String} instanceId - Instance ID to validate as a valid primary.
378 * @param {String} publicKey - The public key
379 *
380 * @returns {Promise} A promise which will be resolved when the operation
381 * is complete
382 */
383CloudProvider.prototype.putPublicKey = function putPublicKey(instanceId, publicKey) {
384 if (this.hasFeature(CloudProvider.FEATURE_ENCRYPTION)) {
385 throw new Error(
386 'Unimplemented abstract method CloudProvider.putPublicKey',
387 instanceId,
388 publicKey
389 );
390 } else {
391 this.logger.debug('No override for CloudProvider.putPublicKey');
392 return q(true);
393 }
394};
395
396/**
397 * Determines if a given instanceId is a valid primary
398 *
399 * In some cloud environments, the primary may change unexpectedly.
400 * Override this method if implementing such a cloud provider.
401 *
402 * @param {String} instanceId - Instance ID to validate as a valid primary.
403 * @param {Object} instances - Dictionary of instances as returned by getInstances.
404 *
405 * @returns {Promise} A promise which will be resolved with a boolean indicating
406 * wether or not the given instanceId is a valid primary.
407 */
408CloudProvider.prototype.isValidPrimary = function isValidPrimary(instanceId, instances) {
409 this.logger.debug('No override for CloudProvider.isValidPrimary', instanceId, instances);
410 return q(true);
411};
412
413/**
414 * Called when a primary has been elected
415 *
416 * In some cloud environments, information about the primary needs to be
417 * stored in persistent storage. Override this method if implementing
418 * such a cloud provider.
419 *
420 * @param {String} instancId - Instance ID that was elected primary.
421 *
422 * @returns {Promise} A promise which will be resolved when processing is complete.
423 */
424CloudProvider.prototype.primaryElected = function primaryElected(instanceId) {
425 this.logger.debug('No override for CloudProvider.primaryElected', instanceId);
426 return q();
427};
428
429/**
430 * Called after a primary has been elected.
431 *
432 * In some cloud environments, instances running a primary should be tagged through
433 * cloud provider specific tagging. Override this method for cloud providers that support
434 * instance tagging for instances running primarys.
435 *
436 * @param {String} instanceId - Instance ID that was elected primary.
437 * @param {Object} instances - Dictionary of instances as returned by getInstances
438 *
439 * @returns {Promise} A promise which will be resolved when processing is complete.
440 */
441CloudProvider.prototype.tagPrimaryInstance = function tagPrimaryInstance(instanceId, instances) {
442 this.logger.debug('No override for CloudProvider.tagPrimaryInstance', instanceId, instances);
443 return q();
444};
445
446/**
447 * Indicates that an instance that was primary is now invalid
448 *
449 * Override for cloud providers that need to take some action when a primary
450 * becomes invalid.
451 *
452 * @param {String} instanceId - Instance ID of instnace that is no longer a valid
453 * primary.
454 * @param {Object} instances - Dictionary of instances as returned by getInstances.
455 *
456 * @returns {Promise} A promise which will be resolved when processing is complete.
457 */
458CloudProvider.prototype.primaryInvalidated = function primaryInvalidated(instanceId, instances) {
459 this.logger.debug('No override for CloudProvider.primaryInvalidated', instanceId, instances);
460 return q();
461};
462
463/**
464 * Called to get check for and retrieve a stored UCS file
465 *
466 * Provider implementations can optionally store a UCS to be
467 * used to restore a primary instance to a last known good state
468 *
469 * @returns {Promise} A promise which will be resolved with a Buffer containing
470 * the UCS data if it is present, resolved with undefined if not
471 * found, or rejected if an error occurs.
472 */
473CloudProvider.prototype.getStoredUcs = function getStoredUcs() {
474 this.logger.debug('No override for CloudProvider.putPrimaryCredentials');
475 return q();
476};
477
478/**
479 * Stores a UCS file in cloud storage
480 *
481 * @param {String} file - Full path to file to store.
482 * @param {Number} maxCopies - Number of files to store. Oldest files over
483 * this number should be deleted.
484 * @param {String} prefix - The common prefix for autosaved UCS files
485 *
486 * @returns {Promise} A promise which is resolved when processing is complete.
487 */
488CloudProvider.prototype.storeUcs = function storeUcs(file, maxCopies, prefix) {
489 this.logger.debug('No override for CloudProvider.storeUcs', file, maxCopies, prefix);
490 return q();
491};
492
493/**
494 * Saves instance info
495 *
496 * Override for cloud implementations which store instance information.
497 *
498 * @param {String} instanceId - ID of instance
499 * @param {Object} instance - Instance information as returned by getInstances.
500 *
501 * @returns {Promise} A promise which will be resolved with instance info.
502 */
503CloudProvider.prototype.putInstance = function putInstance(instanceId, instance) {
504 this.logger.debug('No override for CloudProvider.putInstance', instance);
505 return q();
506};
507
508/**
509 * Sends a message to other instances in the scale set
510 *
511 * Must be implemented if FEATURE_MESSAGING is supported
512 *
513 * @abstract
514 *
515 * @param {String} action - Action id of message to send
516 * @param {Object} [options] - Optional parameters
517 * @param {String} [options.toInstanceId] - Instance ID that message is for
518 * @param {String} [options.fromInstanceId] - Instance ID that message is from
519 * @param {Object} [options.data] - Message specific data
520 *
521 * @returns {Promise} A promise which will be resolved when the message
522 * has been sent or rejected if an error occurs
523 */
524CloudProvider.prototype.sendMessage = function sendMessage(action, data) {
525 if (this.hasFeature(CloudProvider.FEATURE_MESSAGING)) {
526 throw new Error('Unimplemented abstract method CloudProvider.sendMessage', action, data);
527 } else {
528 this.logger.debug('No override for CloudProvider.sendMessage');
529 return q(true);
530 }
531};
532
533/**
534 * Revokes licenses for instances licensed from BIG-IQ
535 *
536 * We only make a best effort here. If revoke fails, this still succeeds. This allows
537 * us not to care if the license even can be revoked (perhaps it is not issued by BIG-IQ).
538 *
539 * @param {Object[]} instances - Instances for which to revoke licenses. Instances
540 * should be as returned by getInstances
541 * @param {Object} [options] - Original command line options
542 * @param {Object} [options.bigIp] - Base {@link BigIp} object.
543 *
544 * @returns {Promise} A promise which will be resolved when processing is complete.
545 */
546CloudProvider.prototype.revokeLicenses = function revokeLicenses(instances, options) {
547 const promises = [];
548
549 if (instances.length > 0) {
550 if (!this.clOptions.licensePool) {
551 this.logger.debug('Can only revoke licenses retrieved from BIG-IQ. Ignoring.');
552 return q.resolve();
553 }
554
555 // this.bigIq can be set for testing
556 const bigIq = this.bigIq || new BigIq(this.options);
557
558 return bigIq.init(
559 this.clOptions.bigIqHost,
560 this.clOptions.bigIqUser,
561 this.clOptions.bigIqPassword || this.clOptions.bigIqPasswordUri,
562 {
563 passwordIsUri: typeof this.clOptions.bigIqPasswordUri !== 'undefined',
564 passwordEncrypted: this.clOptions.bigIqPasswordEncrypted,
565 bigIp: options.bigIp
566 }
567 )
568 .then(() => {
569 instances.forEach((instance) => {
570 const noUnreachable = !(this.clOptions.unreachable);
571 promises.push(bigIq.revokeLicense(
572 this.clOptions.licensePoolName, instance, { noUnreachable }
573 ));
574 });
575 return q.all(promises);
576 })
577 .catch((err) => {
578 this.logger.debug('Could not revoke all licenses', err);
579 return q.reject(err);
580 });
581 }
582
583 this.logger.silly('No licenses to revoke');
584 return q();
585};
586
587/**
588 * Gets messages from other instances in the scale set
589 *
590 * @param {String[]} actions - Array of actions to get. Other messages will be ignored.
591 * Default (empty or undefined) is all actions.
592 * @param {Object} [options] - Optional parameters
593 * @param {String} [options.toInstanceId] - toInstanceId of messsages we are interested in
594 *
595 * @returns {Promise} A promise which will be resolved when the messages
596 * have been received and processed. Promise should be
597 * resolved with an array of messages of the form
598 *
599 * {
600 * action: message action id,
601 * toInstanceId: instanceId,
602 * fromInstanceId: instanceId,
603 * data: message specific data used in sendMessage,
604 * completionHandler: optional completionHandler to call wnen done processing
605 * {
606 * this: this arg for callback context,
607 * callback: function to call,
608 * data: data to send to function
609 * }
610 * }
611 */
612CloudProvider.prototype.getMessages = function getMessages(actions, options) {
613 if (this.hasFeature(CloudProvider.FEATURE_MESSAGING)) {
614 throw new Error('Unimplemented abstract method CloudProvider.getMessages', actions, options);
615 } else {
616 this.logger.debug('No override for CloudProvider.getMessages');
617 return q(true);
618 }
619};
620
621/**
622 * Informs the provider that a sync has completed in case the
623 * password needs to be updated
624 *
625 * When a sync is complete, the user and password will exist on
626 * the synced to device.
627 *
628 * @param {String} fromUser - User that was synced from
629 * @param {String} fromPassword - Password that was synced from
630 *
631 * @returns {Promise} A promise which will be resolved when the messages
632 * have been received and processed
633 */
634// eslint-disable-next-line no-unused-vars
635CloudProvider.prototype.syncComplete = function syncComplete(fromUser, fromPassword) {
636 this.logger.debug('No override for CloudProvider.syncComplete', fromUser);
637 return q(true);
638};
639
640/**
641 * Informs the provider that the instance has been provisioned
642 *
643 * @param {String} instanceId - Instance ID of instance to mark as provisioned. If not provided,
644 * instanceId will be instanceId as set by init().
645 *
646 * @returns {Promise} A promise which will be resolved when the instance has been signalled to the
647 * provider as provisioned
648 */
649CloudProvider.prototype.signalInstanceProvisioned = function signalInstanceProvisioned(instanceId) {
650 this.logger.debug('No override for CloudProvider.signalInstanceProvisioned', instanceId);
651 return q(true);
652};
653
654/**
655 * Determines whether a stored instance is so old it should not be considered
656 *
657 * @param {Object} instance - Instance data
658 *
659 * @returns {Boolean} Whether or not the instance is expired
660 */
661CloudProvider.prototype.isInstanceExpired = function isInstanceExpired(instance) {
662 let isExpired = false;
663
664 const lastUpdate = instance.lastUpdate || new Date();
665 const age = new Date() - new Date(lastUpdate);
666
667 if (age > MAX_STORED_INSTANCE_AGE) {
668 isExpired = true;
669 }
670
671 return isExpired;
672};
673
674/**
675 * Gets nodes from the provided URI. The resource should be in JSON
676 * format as an array of objects. JSON strings that parse to an array
677 * of objects are also supported.
678 *
679 * @param {String} uri - The URI of the resource.
680 * @param {Object} [options] - Optional parameters
681 * @param {Object} [options.headers] - Map of headers to add to the request. Format:
682 *
683 * {
684 * <header1_name>: <header1_value>,
685 * <header2_name>: <header2_value>
686 * }
687 *
688 * @returns {Promise} A promise which will be resolved with an array of instances.
689 * Each instance value should be:
690 *
691 * {
692 * id: Node ID,
693 * ip: {
694 * public: public IP,
695 * private: private IP
696 * }
697 * }
698 */
699CloudProvider.prototype.getNodesFromUri = function getNodesFromUri(uri, options) {
700 this.logger.debug('No override for CloudProvider.getNodesFromUri', uri, options);
701 return q();
702};
703
704/**
705 * Gets nodes by a resourceId. The resourceId is a string and its meaning is
706 * provider specific. The meaning is interpreted by the provider by setting a resourceType,
707 * which is also provider specific.
708 *
709 * @param {String} resourceId - The ID of the resource.
710 * @param {Object} resourceType - The type of resource. Provider specific.
711 * @param {Object} [options] - Optional parameters
712 *
713 * @returns {Promise} A promise which will be resolved with an array of instances.
714 * Each instance value should be:
715 *
716 * {
717 * id: Node ID,
718 * ip: {
719 * public: public IP,
720 * private: private IP
721 * }
722 * }
723 */
724CloudProvider.prototype.getNodesByResourceId = function getNodesFromUri(resourceId, resourceType, options) {
725 this.logger.debug('No override for CloudProvider.getNodesFromUri', resourceId, resourceType, options);
726 return q();
727};
728
729module.exports = CloudProvider;