comparison env/lib/python3.9/site-packages/bioblend/cloudman/__init__.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 """
2 API for interacting with a CloudMan instance.
3 """
4 import functools
5 import json
6 import time
7 from urllib.parse import urlparse
8
9 import requests
10
11 import bioblend
12 from bioblend.cloudman.launch import CloudManLauncher
13 from bioblend.util import Bunch
14
15
16 def block_until_vm_ready(func):
17 """
18 This decorator exists to make sure that a launched VM is
19 ready and has received a public IP before allowing the wrapped
20 function call to continue. If the VM is not ready, the function will
21 block until the VM is ready. If the VM does not become ready
22 until the vm_ready_timeout elapses or the VM status returns an error,
23 a VMLaunchException will be thrown.
24
25 This decorator relies on the wait_until_instance_ready method defined in
26 class GenericVMInstance. All methods to which this decorator is applied
27 must be members of a class which inherit from GenericVMInstance.
28
29 The following two optional keyword arguments are recognized by this decorator:
30
31 :type vm_ready_timeout: int
32 :param vm_ready_timeout: Maximum length of time to block before timing out.
33 Once the timeout is reached, a VMLaunchException
34 will be thrown.
35
36 :type vm_ready_check_interval: int
37 :param vm_ready_check_interval: The number of seconds to pause between consecutive
38 calls when polling the VM's ready status.
39 """
40 @functools.wraps(func)
41 def wrapper(*args, **kwargs):
42 obj = args[0]
43 timeout = kwargs.pop('vm_ready_timeout', 300)
44 interval = kwargs.pop('vm_ready_check_interval', 10)
45 try:
46 obj.wait_until_instance_ready(timeout, interval)
47 except AttributeError:
48 raise VMLaunchException("Decorated object does not define a wait_until_instance_ready method."
49 "Make sure that the object is of type GenericVMInstance.")
50 return func(*args, **kwargs)
51 return wrapper
52
53
54 class VMLaunchException(Exception):
55 def __init__(self, value):
56 self.value = value
57
58 def __str__(self):
59 return repr(self.value)
60
61
62 class CloudManConfig:
63
64 def __init__(self,
65 access_key=None,
66 secret_key=None,
67 cluster_name=None,
68 image_id=None,
69 instance_type='m1.medium',
70 password=None,
71 cloud_metadata=None,
72 cluster_type=None,
73 galaxy_data_option='',
74 initial_storage_size=10,
75 key_name='cloudman_key_pair',
76 security_groups=None,
77 placement='',
78 kernel_id=None,
79 ramdisk_id=None,
80 block_until_ready=False,
81 **kwargs):
82 """
83 Initializes a CloudMan launch configuration object.
84
85 :type access_key: str
86 :param access_key: Access credentials.
87
88 :type secret_key: str
89 :param secret_key: Access credentials.
90
91 :type cluster_name: str
92 :param cluster_name: Name used to identify this CloudMan cluster.
93
94 :type image_id: str
95 :param image_id: Machine image ID to use when launching this
96 CloudMan instance.
97
98 :type instance_type: str
99 :param instance_type: The type of the machine instance, as understood by
100 the chosen cloud provider. (e.g., ``m1.medium``)
101
102 :type password: str
103 :param password: The administrative password for this CloudMan instance.
104
105 :type cloud_metadata: Bunch
106 :param cloud_metadata: This object must define the properties required
107 to establish a `boto <https://github.com/boto/boto/>`_
108 connection to that cloud. See this method's implementation
109 for an example of the required fields. Note that as
110 long the as provided object defines the required fields,
111 it can really by implemented as anything (e.g.,
112 a Bunch, a database object, a custom class). If no
113 value for the ``cloud`` argument is provided, the
114 default is to use the Amazon cloud.
115
116 :type kernel_id: str
117 :param kernel_id: The ID of the kernel with which to launch the
118 instances
119
120 :type ramdisk_id: str
121 :param ramdisk_id: The ID of the RAM disk with which to launch the
122 instances
123
124 :type key_name: str
125 :param key_name: The name of the key pair with which to launch instances
126
127 :type security_groups: list of str
128 :param security_groups: The IDs of the security groups with which to
129 associate instances
130
131 :type placement: str
132 :param placement: The availability zone in which to launch the instances
133
134 :type cluster_type: str
135 :param cluster_type: The ``type``, either 'Galaxy', 'Data', or
136 'Test', defines the type of cluster platform to initialize.
137
138 :type galaxy_data_option: str
139 :param galaxy_data_option: The storage type to use for this instance.
140 May be 'transient', 'custom_size' or ''. The default is '',
141 which will result in ignoring the bioblend specified
142 initial_storage_size. 'custom_size' must be used for
143 initial_storage_size to come into effect.
144
145 :type initial_storage_size: int
146 :param initial_storage_size: The initial storage to allocate for the instance.
147 This only applies if ``cluster_type`` is set
148 to either ``Galaxy`` or ``Data`` and ``galaxy_data_option``
149 is set to ``custom_size``
150
151 :type block_until_ready: bool
152 :param block_until_ready: Specifies whether the launch method will block
153 until the instance is ready and only return once
154 all initialization is complete. The default is False.
155 If False, the launch method will return immediately
156 without blocking. However, any subsequent calls
157 made will automatically block if the instance is
158 not ready and initialized. The blocking timeout
159 and polling interval can be configured by providing
160 extra parameters to the ``CloudManInstance.launch_instance``
161 method.
162 """
163 if security_groups is None:
164 security_groups = ['CloudMan']
165 self.set_connection_parameters(access_key, secret_key, cloud_metadata)
166 self.set_pre_launch_parameters(
167 cluster_name, image_id, instance_type,
168 password, kernel_id, ramdisk_id, key_name, security_groups,
169 placement, block_until_ready)
170 self.set_post_launch_parameters(cluster_type, galaxy_data_option, initial_storage_size)
171 self.set_extra_parameters(**kwargs)
172
173 def set_connection_parameters(self, access_key, secret_key, cloud_metadata=None):
174 self.access_key = access_key
175 self.secret_key = secret_key
176 self.cloud_metadata = cloud_metadata
177
178 def set_pre_launch_parameters(
179 self, cluster_name, image_id, instance_type, password,
180 kernel_id=None, ramdisk_id=None, key_name='cloudman_key_pair',
181 security_groups=None, placement='', block_until_ready=False):
182 if security_groups is None:
183 security_groups = ['CloudMan']
184 self.cluster_name = cluster_name
185 self.image_id = image_id
186 self.instance_type = instance_type
187 self.password = password
188 self.kernel_id = kernel_id
189 self.ramdisk_id = ramdisk_id
190 self.key_name = key_name
191 self.security_groups = security_groups
192 self.placement = placement
193 self.block_until_ready = block_until_ready
194
195 def set_post_launch_parameters(self, cluster_type=None, galaxy_data_option='', initial_storage_size=10):
196 self.cluster_type = cluster_type
197 self.galaxy_data_option = galaxy_data_option
198 self.initial_storage_size = initial_storage_size
199
200 def set_extra_parameters(self, **kwargs):
201 self.kwargs = kwargs
202
203 class CustomTypeEncoder(json.JSONEncoder):
204 def default(self, obj):
205 if isinstance(obj, (CloudManConfig, Bunch)):
206 key = '__%s__' % obj.__class__.__name__
207 return {key: obj.__dict__}
208 return json.JSONEncoder.default(self, obj)
209
210 @staticmethod
211 def CustomTypeDecoder(dct):
212 if '__CloudManConfig__' in dct:
213 return CloudManConfig(**dct['__CloudManConfig__'])
214 elif '__Bunch__' in dct:
215 return Bunch(**dct['__Bunch__'])
216 else:
217 return dct
218
219 @staticmethod
220 def load_config(fp):
221 return json.load(fp, object_hook=CloudManConfig.CustomTypeDecoder)
222
223 def save_config(self, fp):
224 json.dump(self, fp, cls=self.CustomTypeEncoder)
225
226 def validate(self):
227 if self.access_key is None:
228 return "Access key must not be null"
229 elif self.secret_key is None:
230 return "Secret key must not be null"
231 elif self.cluster_name is None:
232 return "Cluster name must not be null"
233 elif self.image_id is None:
234 return "Image ID must not be null"
235 elif self.instance_type is None:
236 return "Instance type must not be null"
237 elif self.password is None:
238 return "Password must not be null"
239 elif self.cluster_type not in [None, 'Test', 'Data', 'Galaxy', 'Shared_cluster']:
240 return f"Unrecognized cluster type ({self.cluster_type})"
241 elif self.galaxy_data_option not in [None, '', 'custom-size', 'transient']:
242 return f"Unrecognized galaxy data option ({self.galaxy_data_option})"
243 elif self.key_name is None:
244 return "Key-pair name must not be null"
245 else:
246 return None
247
248
249 class GenericVMInstance:
250
251 def __init__(self, launcher, launch_result):
252 """
253 Create an instance of the CloudMan API class, which is to be used when
254 manipulating that given CloudMan instance.
255
256 The ``url`` is a string defining the address of CloudMan, for
257 example "http://115.146.92.174". The ``password`` is CloudMan's password,
258 as defined in the user data sent to CloudMan on instance creation.
259 """
260 # Make sure the url scheme is defined (otherwise requests will not work)
261 self.vm_error = None
262 self.vm_status = None
263 self.host_name = None
264 self.launcher = launcher
265 self.launch_result = launch_result
266
267 def _update_host_name(self, host_name):
268 if self.host_name != host_name:
269 self.host_name = host_name
270
271 @property
272 def instance_id(self):
273 """
274 Returns the ID of this instance (e.g., ``i-87ey32dd``) if launch was
275 successful or ``None`` otherwise.
276 """
277 return None if self.launch_result is None else self.launch_result['instance_id']
278
279 @property
280 def key_pair_name(self):
281 """
282 Returns the name of the key pair used by this instance. If instance was
283 not launched properly, returns ``None``.
284 """
285 return None if self.launch_result is None else self.launch_result['kp_name']
286
287 @property
288 def key_pair_material(self):
289 """
290 Returns the private portion of the generated key pair. It does so only
291 if the instance was properly launched and key pair generated; ``None``
292 otherwise.
293 """
294 return None if self.launch_result is None else self.launch_result['kp_material']
295
296 def get_machine_status(self):
297 """
298 Check on the underlying VM status of an instance. This can be used to
299 determine whether the VM has finished booting up and if CloudMan
300 is up and running.
301
302 Return a ``state`` dict with the current ``instance_state``, ``public_ip``,
303 ``placement``, and ``error`` keys, which capture the current state (the
304 values for those keys default to empty string if no data is available from
305 the cloud).
306 """
307 if self.launcher:
308 return self.launcher.get_status(self.instance_id)
309 # elif self.host_name:
310
311 else:
312 state = {'instance_state': "",
313 'public_ip': "",
314 'placement': "",
315 'error': "No reference to the instance object"}
316 return state
317
318 def _init_instance(self, host_name):
319 self._update_host_name(host_name)
320
321 def wait_until_instance_ready(self, vm_ready_timeout=300, vm_ready_check_interval=10):
322 """
323 Wait until the VM state changes to ready/error or timeout elapses.
324 Updates the host name once ready.
325 """
326 assert vm_ready_timeout > 0
327 assert vm_ready_timeout > vm_ready_check_interval
328 assert vm_ready_check_interval > 0
329
330 if self.host_name: # Host name available. Therefore, instance is ready
331 return
332
333 for time_left in range(vm_ready_timeout, 0, -vm_ready_check_interval):
334 status = self.get_machine_status()
335 if status['public_ip'] != '' and status['error'] == '':
336 self._init_instance(status['public_ip'])
337 return
338 elif status['error'] != '':
339 msg = "Error launching an instance: {}".format(status['error'])
340 bioblend.log.error(msg)
341 raise VMLaunchException(msg)
342 else:
343 bioblend.log.warning("Instance not ready yet (it's in state '{}'); waiting another {} seconds..."
344 .format(status['instance_state'], time_left))
345 time.sleep(vm_ready_check_interval)
346
347 raise VMLaunchException("Waited too long for instance to become ready. Instance Id: %s"
348 % self.instance_id)
349
350
351 class CloudManInstance(GenericVMInstance):
352
353 def __init__(self, url, password, **kwargs):
354 """
355 Create an instance of the CloudMan API class, which is to be used when
356 manipulating that given CloudMan instance.
357
358 The ``url`` is a string defining the address of CloudMan, for
359 example "http://115.146.92.174". The ``password`` is CloudMan's password,
360 as defined in the user data sent to CloudMan on instance creation.
361 """
362 self.initialized = False
363 if kwargs.get('launch_result', None) is not None: # Used internally by the launch_instance method
364 super().__init__(kwargs['launcher'], kwargs['launch_result'])
365 else:
366 super().__init__(None, None)
367 self.config = kwargs.pop('cloudman_config', None)
368 if not self.config:
369 self.password = password
370 else:
371 self.password = self.config.password
372 self._set_url(url)
373
374 def __repr__(self):
375 if self.cloudman_url:
376 return f"CloudMan instance at {self.cloudman_url}"
377 else:
378 return "Waiting for this CloudMan instance to start..."
379
380 def _update_host_name(self, host_name):
381 """
382 Overrides the super-class method and makes sure that the ``cloudman_url``
383 is kept in sync with the host name.
384 """
385 self._set_url(host_name)
386
387 def _init_instance(self, hostname):
388 super()._init_instance(hostname)
389 if self.config.cluster_type:
390 self.initialize(self.config.cluster_type, galaxy_data_option=self.config.galaxy_data_option, initial_storage_size=self.config.initial_storage_size)
391
392 def _set_url(self, url):
393 """
394 Keeps the CloudMan URL as well and the hostname in sync.
395 """
396 if url:
397 parse_result = urlparse(url)
398 # Make sure the URL scheme is defined (otherwise requests will not work)
399 if not parse_result.scheme:
400 url = "http://" + url
401 # Parse the corrected URL again to extract the hostname
402 parse_result = urlparse(url)
403 super()._update_host_name(parse_result.hostname)
404 self.url = url
405
406 @property
407 def galaxy_url(self):
408 """
409 Returns the base URL for this instance, which by default happens to be
410 the URL for Galaxy application.
411 """
412 return self.url
413
414 @property
415 def cloudman_url(self):
416 """
417 Returns the URL for accessing this instance of CloudMan.
418 """
419 if self.url:
420 return self.url + '/cloud'
421 return None
422
423 @staticmethod
424 def launch_instance(cfg, **kwargs):
425 """
426 Launches a new instance of CloudMan on the specified cloud infrastructure.
427
428 :type cfg: CloudManConfig
429 :param cfg: A CloudManConfig object containing the initial parameters
430 for this launch.
431 """
432 validation_result = cfg.validate()
433 if validation_result is not None:
434 raise VMLaunchException(
435 "Invalid CloudMan configuration provided: {}"
436 .format(validation_result))
437 launcher = CloudManLauncher(cfg.access_key, cfg.secret_key, cfg.cloud_metadata)
438 result = launcher.launch(
439 cfg.cluster_name, cfg.image_id, cfg.instance_type, cfg.password,
440 cfg.kernel_id, cfg.ramdisk_id, cfg.key_name, cfg.security_groups,
441 cfg.placement, **cfg.kwargs)
442 if result['error'] is not None:
443 raise VMLaunchException("Error launching cloudman instance: {}".format(result['error']))
444 instance = CloudManInstance(None, None, launcher=launcher,
445 launch_result=result, cloudman_config=cfg)
446 if cfg.block_until_ready and cfg.cluster_type:
447 instance.get_status() # this will indirect result in initialize being invoked
448 return instance
449
450 def update(self):
451 """
452 Update the local object's fields to be in sync with the actual state
453 of the CloudMan instance the object points to. This method should be
454 called periodically to ensure you are looking at the current data.
455
456 .. versionadded:: 0.2.2
457 """
458 ms = self.get_machine_status()
459 # Check if the machine is running and update IP and state
460 self.vm_status = ms.get('instance_state', None)
461 self.vm_error = ms.get('error', None)
462 public_ip = ms.get('public_ip', None)
463 # Update url if we don't have it or is different than what we have
464 if not self.url and (public_ip and self.url != public_ip):
465 self._set_url(public_ip)
466 # See if the cluster has been initialized
467 if self.vm_status == 'running' or self.url:
468 ct = self.get_cluster_type()
469 if ct.get('cluster_type', None):
470 self.initialized = True
471 if self.vm_error:
472 bioblend.log.error(self.vm_error)
473
474 @block_until_vm_ready
475 def get_cloudman_version(self):
476 """
477 Returns the cloudman version from the server. Versions prior to Cloudman 2 does not
478 support this call, and therefore, the default is to return 1
479 """
480 try:
481 r = self._make_get_request("cloudman_version")
482 return r['version']
483 except Exception:
484 return 1
485
486 @block_until_vm_ready
487 def initialize(self, cluster_type, galaxy_data_option='', initial_storage_size=None, shared_bucket=None):
488 """
489 Initialize CloudMan platform. This needs to be done before the cluster
490 can be used.
491
492 The ``cluster_type``, either 'Galaxy', 'Data', or 'Test', defines the type
493 of cluster platform to initialize.
494 """
495 if not self.initialized:
496 if self.get_cloudman_version() < 2:
497 r = self._make_get_request(
498 "initialize_cluster",
499 parameters={
500 'startup_opt': cluster_type,
501 'g_pss': initial_storage_size,
502 'shared_bucket': shared_bucket
503 })
504 else:
505 r = self._make_get_request(
506 "initialize_cluster",
507 parameters={
508 'startup_opt': cluster_type,
509 'galaxy_data_option': galaxy_data_option,
510 'pss': initial_storage_size,
511 'shared_bucket': shared_bucket
512 })
513 self.initialized = True
514 return r
515
516 @block_until_vm_ready
517 def get_cluster_type(self):
518 """
519 Get the ``cluster type`` for this CloudMan instance. See the
520 CloudMan docs about the available types. Returns a dictionary,
521 for example: ``{'cluster_type': 'Test'}``.
522 """
523 cluster_type = self._make_get_request("cluster_type")
524 if cluster_type['cluster_type']:
525 self.initialized = True
526 return cluster_type
527
528 @block_until_vm_ready
529 def get_status(self):
530 """
531 Get status information on this CloudMan instance.
532 """
533 return self._make_get_request("instance_state_json")
534
535 @block_until_vm_ready
536 def get_nodes(self):
537 """
538 Get a list of nodes currently running in this CloudMan cluster.
539 """
540 instance_feed_json = self._make_get_request("instance_feed_json")
541 return instance_feed_json['instances']
542
543 @block_until_vm_ready
544 def get_cluster_size(self):
545 """
546 Get the size of the cluster in terms of the number of nodes; this count
547 includes the master node.
548 """
549 return len(self.get_nodes())
550
551 @block_until_vm_ready
552 def get_static_state(self):
553 """
554 Get static information on this CloudMan instance.
555 i.e. state that doesn't change over the lifetime of the cluster
556 """
557 return self._make_get_request("static_instance_state_json")
558
559 @block_until_vm_ready
560 def get_master_ip(self):
561 """
562 Returns the public IP of the master node in this CloudMan cluster
563 """
564 status_json = self.get_static_state()
565 return status_json['master_ip']
566
567 @block_until_vm_ready
568 def get_master_id(self):
569 """
570 Returns the instance ID of the master node in this CloudMan cluster
571 """
572 status_json = self.get_static_state()
573 return status_json['master_id']
574
575 @block_until_vm_ready
576 def add_nodes(self, num_nodes, instance_type='', spot_price=''):
577 """
578 Add a number of worker nodes to the cluster, optionally specifying
579 the type for new instances. If ``instance_type`` is not specified,
580 instance(s) of the same type as the master instance will be started.
581 Note that the ``instance_type`` must match the type of instance
582 available on the given cloud.
583
584 ``spot_price`` applies only to AWS and, if set, defines the maximum
585 price for Spot instances, thus turning this request for more instances
586 into a Spot request.
587 """
588 payload = {'number_nodes': num_nodes,
589 'instance_type': instance_type,
590 'spot_price': spot_price}
591 return self._make_get_request("add_instances", parameters=payload)
592
593 @block_until_vm_ready
594 def remove_nodes(self, num_nodes, force=False):
595 """
596 Remove worker nodes from the cluster.
597
598 The ``num_nodes`` parameter defines the number of worker nodes to remove.
599 The ``force`` parameter (defaulting to False), is a boolean indicating
600 whether the nodes should be forcibly removed rather than gracefully removed.
601 """
602 payload = {'number_nodes': num_nodes, 'force_termination': force}
603 result = self._make_get_request("remove_instances", parameters=payload)
604 return result
605
606 @block_until_vm_ready
607 def remove_node(self, instance_id, force=False):
608 """
609 Remove a specific worker node from the cluster.
610
611 The ``instance_id`` parameter defines the ID, as a string, of a worker node
612 to remove from the cluster. The ``force`` parameter (defaulting to False),
613 is a boolean indicating whether the node should be forcibly removed rather
614 than gracefully removed.
615
616 """
617 payload = {'instance_id': instance_id}
618 return self._make_get_request("remove_instance", parameters=payload)
619
620 @block_until_vm_ready
621 def reboot_node(self, instance_id):
622 """
623 Reboot a specific worker node.
624
625 The ``instance_id`` parameter defines the ID, as a string, of a worker node
626 to reboot.
627 """
628 payload = {'instance_id': instance_id}
629 return self._make_get_request("reboot_instance", parameters=payload)
630
631 @block_until_vm_ready
632 def autoscaling_enabled(self):
633 """
634 Returns a boolean indicating whether autoscaling is enabled.
635 """
636 return bool(self.get_status()['autoscaling']['use_autoscaling'])
637
638 @block_until_vm_ready
639 def enable_autoscaling(self, minimum_nodes=0, maximum_nodes=19):
640 """
641 Enable cluster autoscaling, allowing the cluster to automatically add,
642 or remove, worker nodes, as needed.
643
644 The number of worker nodes in the cluster is bounded by the ``minimum_nodes``
645 (default is 0) and ``maximum_nodes`` (default is 19) parameters.
646 """
647 if not self.autoscaling_enabled():
648 payload = {'as_min': minimum_nodes, 'as_max': maximum_nodes}
649 self._make_get_request("toggle_autoscaling", parameters=payload)
650
651 @block_until_vm_ready
652 def disable_autoscaling(self):
653 """
654 Disable autoscaling, meaning that worker nodes will need to be manually
655 added and removed.
656 """
657 if self.autoscaling_enabled():
658 self._make_get_request("toggle_autoscaling")
659
660 @block_until_vm_ready
661 def adjust_autoscaling(self, minimum_nodes=None, maximum_nodes=None):
662 """
663 Adjust the autoscaling configuration parameters.
664
665 The number of worker nodes in the cluster is bounded by the optional
666 ``minimum_nodes`` and ``maximum_nodes`` parameters. If a parameter is
667 not provided then its configuration value does not change.
668 """
669 if self.autoscaling_enabled():
670 payload = {'as_min_adj': minimum_nodes, 'as_max_adj': maximum_nodes}
671 self._make_get_request("adjust_autoscaling", parameters=payload)
672
673 @block_until_vm_ready
674 def is_master_execution_host(self):
675 """
676 Checks whether the master node has job execution enabled.
677
678 """
679 status = self._make_get_request("get_all_services_status")
680 return bool(status['master_is_exec_host'])
681
682 @block_until_vm_ready
683 def set_master_as_execution_host(self, enable):
684 """
685 Enables/disables master as execution host.
686
687 """
688 if not self.is_master_execution_host():
689 self._make_get_request("toggle_master_as_exec_host")
690
691 @block_until_vm_ready
692 def get_galaxy_state(self):
693 """
694 Get the current status of Galaxy running on the cluster.
695 """
696 payload = {'srvc': 'Galaxy'}
697 status = self._make_get_request("get_srvc_status", parameters=payload)
698 return {'status': status['status']}
699
700 @block_until_vm_ready
701 def terminate(self, terminate_master_instance=True, delete_cluster=False):
702 """
703 Terminate this CloudMan cluster. There is an option to also terminate the
704 master instance (all worker instances will be terminated in the process
705 of cluster termination), and delete the whole cluster.
706
707 .. warning::
708 Deleting a cluster is irreversible - all of the data will be
709 permanently deleted.
710 """
711 payload = {'terminate_master_instance': terminate_master_instance,
712 'delete_cluster': delete_cluster}
713 result = self._make_get_request("kill_all", parameters=payload,
714 timeout=15)
715 return result
716
717 def _make_get_request(self, url, parameters=None, timeout=None):
718 """
719 Private function that makes a GET request to the nominated ``url``,
720 with the provided GET ``parameters``. Optionally, set the ``timeout``
721 to stop waiting for a response after a given number of seconds. This is
722 particularly useful when terminating a cluster as it may terminate
723 before sending a response.
724 """
725 if parameters is None:
726 parameters = {}
727 req_url = '/'.join((self.cloudman_url, 'root', url))
728 r = requests.get(req_url, params=parameters, auth=("", self.password), timeout=timeout)
729 try:
730 json = r.json()
731 return json
732 except Exception:
733 return r.text