Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/bioblend/cloudman/__init__.py @ 0:d30785e31577 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author | guerler |
---|---|
date | Fri, 31 Jul 2020 00:18:57 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:d30785e31577 |
---|---|
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(object): | |
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 "Unrecognized cluster type ({0})".format(self.cluster_type) | |
241 elif self.galaxy_data_option not in [None, '', 'custom-size', 'transient']: | |
242 return "Unrecognized galaxy data option ({0})".format(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(object): | |
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: {0}".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 '{0}'); waiting another {1} 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 "CloudMan instance at {0}".format(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: {0}" | |
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: {0}".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 |