Merge branch 'master' into 3618-column-ordering
[arvados.git] / services / nodemanager / arvnodeman / computenode / ec2.py
1 #!/usr/bin/env python
2
3 from __future__ import absolute_import, print_function
4
5 import time
6
7 import libcloud.compute.base as cloud_base
8 import libcloud.compute.providers as cloud_provider
9 import libcloud.compute.types as cloud_types
10 from libcloud.compute.drivers import ec2 as cloud_ec2
11
12 from . import BaseComputeNodeDriver, arvados_node_fqdn
13
14 ### Monkeypatch libcloud to support AWS' new SecurityGroup API.
15 # These classes can be removed when libcloud support specifying
16 # security groups with the SecurityGroupId parameter.
17 class ANMEC2Connection(cloud_ec2.EC2Connection):
18     def request(self, *args, **kwargs):
19         params = kwargs.get('params')
20         if (params is not None) and (params.get('Action') == 'RunInstances'):
21             for key in params.keys():
22                 if key.startswith('SecurityGroup.'):
23                     new_key = key.replace('Group.', 'GroupId.', 1)
24                     params[new_key] = params.pop(key).id
25             kwargs['params'] = params
26         return super(ANMEC2Connection, self).request(*args, **kwargs)
27
28
29 class ANMEC2NodeDriver(cloud_ec2.EC2NodeDriver):
30     connectionCls = ANMEC2Connection
31
32
33 class ComputeNodeDriver(BaseComputeNodeDriver):
34     """Compute node driver wrapper for EC2.
35
36     This translates cloud driver requests to EC2's specific parameters.
37     """
38     DEFAULT_DRIVER = ANMEC2NodeDriver
39 ### End monkeypatch
40     SEARCH_CACHE = {}
41
42     def __init__(self, auth_kwargs, list_kwargs, create_kwargs,
43                  driver_class=DEFAULT_DRIVER):
44         # We need full lists of keys up front because these loops modify
45         # dictionaries in-place.
46         for key in list_kwargs.keys():
47             list_kwargs[key.replace('_', ':')] = list_kwargs.pop(key)
48         self.tags = {key[4:]: value
49                      for key, value in list_kwargs.iteritems()
50                      if key.startswith('tag:')}
51         super(ComputeNodeDriver, self).__init__(
52             auth_kwargs, {'ex_filters': list_kwargs}, create_kwargs,
53             driver_class)
54         for key in self.create_kwargs.keys():
55             init_method = getattr(self, '_init_' + key, None)
56             if init_method is not None:
57                 new_pair = init_method(self.create_kwargs.pop(key))
58                 if new_pair is not None:
59                     self.create_kwargs[new_pair[0]] = new_pair[1]
60
61     def _init_image_id(self, image_id):
62         return 'image', self.search_for(image_id, 'list_images')
63
64     def _init_ping_host(self, ping_host):
65         self.ping_host = ping_host
66
67     def _init_security_groups(self, group_names):
68         return 'ex_security_groups', [
69             self.search_for(gname.strip(), 'ex_get_security_groups')
70             for gname in group_names.split(',')]
71
72     def _init_subnet_id(self, subnet_id):
73         return 'ex_subnet', self.search_for(subnet_id, 'ex_list_subnets')
74
75     def _init_ssh_key(self, filename):
76         with open(filename) as ssh_file:
77             key = cloud_base.NodeAuthSSHKey(ssh_file.read())
78         return 'auth', key
79
80     def arvados_create_kwargs(self, arvados_node):
81         result = {'ex_metadata': self.tags.copy(),
82                   'name': arvados_node_fqdn(arvados_node)}
83         ping_secret = arvados_node['info'].get('ping_secret')
84         if ping_secret is not None:
85             ping_url = ('https://{}/arvados/v1/nodes/{}/ping?ping_secret={}'.
86                         format(self.ping_host, arvados_node['uuid'],
87                                ping_secret))
88             result['ex_userdata'] = ping_url
89         return result
90
91     def sync_node(self, cloud_node, arvados_node):
92         metadata = self.arvados_create_kwargs(arvados_node)
93         tags = metadata['ex_metadata']
94         tags['Name'] = metadata['name']
95         self.real.ex_create_tags(cloud_node, tags)
96
97     @classmethod
98     def node_start_time(cls, node):
99         time_str = node.extra['launch_time'].split('.', 2)[0] + 'UTC'
100         return time.mktime(time.strptime(
101                 time_str,'%Y-%m-%dT%H:%M:%S%Z')) - time.timezone