8784: Fix test for latest firefox.
[arvados.git] / services / nodemanager / arvnodeman / computenode / driver / 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
13 from .. import arvados_node_fqdn
14
15 ### Monkeypatch libcloud to support AWS' new SecurityGroup API.
16 # These classes can be removed when libcloud support specifying
17 # security groups with the SecurityGroupId parameter.
18 class ANMEC2Connection(cloud_ec2.EC2Connection):
19     def request(self, *args, **kwargs):
20         params = kwargs.get('params')
21         if (params is not None) and (params.get('Action') == 'RunInstances'):
22             for key in params.keys():
23                 if key.startswith('SecurityGroup.'):
24                     new_key = key.replace('Group.', 'GroupId.', 1)
25                     params[new_key] = params.pop(key).id
26             kwargs['params'] = params
27         return super(ANMEC2Connection, self).request(*args, **kwargs)
28
29
30 class ANMEC2NodeDriver(cloud_ec2.EC2NodeDriver):
31     connectionCls = ANMEC2Connection
32
33
34 class ComputeNodeDriver(BaseComputeNodeDriver):
35     """Compute node driver wrapper for EC2.
36
37     This translates cloud driver requests to EC2's specific parameters.
38     """
39     DEFAULT_DRIVER = ANMEC2NodeDriver
40 ### End monkeypatch
41     SEARCH_CACHE = {}
42
43     def __init__(self, auth_kwargs, list_kwargs, create_kwargs,
44                  driver_class=DEFAULT_DRIVER):
45         # We need full lists of keys up front because these loops modify
46         # dictionaries in-place.
47         for key in list_kwargs.keys():
48             list_kwargs[key.replace('_', ':')] = list_kwargs.pop(key)
49         self.tags = {key[4:]: value
50                      for key, value in list_kwargs.iteritems()
51                      if key.startswith('tag:')}
52         super(ComputeNodeDriver, self).__init__(
53             auth_kwargs, {'ex_filters': list_kwargs}, create_kwargs,
54             driver_class)
55
56     def _init_image_id(self, image_id):
57         return 'image', self.search_for(image_id, 'list_images')
58
59     def _init_security_groups(self, group_names):
60         return 'ex_security_groups', [
61             self.search_for(gname.strip(), 'ex_get_security_groups')
62             for gname in group_names.split(',')]
63
64     def _init_subnet_id(self, subnet_id):
65         return 'ex_subnet', self.search_for(subnet_id, 'ex_list_subnets')
66
67     create_cloud_name = staticmethod(arvados_node_fqdn)
68
69     def arvados_create_kwargs(self, size, arvados_node):
70         kw = {'name': self.create_cloud_name(arvados_node),
71                 'ex_userdata': self._make_ping_url(arvados_node)}
72         # libcloud/ec2 disk sizes are in GB, Arvados/SLURM "scratch" value is in MB
73         scratch = int(size.scratch / 1000) + 1
74         if scratch > size.disk:
75             volsize = scratch - size.disk
76             if volsize > 16384:
77                 # Must be 1-16384 for General Purpose SSD (gp2) devices
78                 # https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_EbsBlockDevice.html
79                 self._logger.warning("Requested EBS volume size %d is too large, capping size request to 16384 GB", volsize)
80                 volsize = 16384
81             kw["ex_blockdevicemappings"] = [{
82                 "DeviceName": "/dev/xvdt",
83                 "Ebs": {
84                     "DeleteOnTermination": True,
85                     "VolumeSize": volsize,
86                     "VolumeType": "gp2"
87                 }}]
88         return kw
89
90     def post_create_node(self, cloud_node):
91         self.real.ex_create_tags(cloud_node, self.tags)
92
93     def sync_node(self, cloud_node, arvados_node):
94         self.real.ex_create_tags(cloud_node,
95                                  {'Name': arvados_node_fqdn(arvados_node)})
96
97     def list_nodes(self):
98         # Need to populate Node.size
99         nodes = super(ComputeNodeDriver, self).list_nodes()
100         for n in nodes:
101             if not n.size:
102                 n.size = self.sizes[n.extra["instance_type"]]
103         return nodes
104
105     @classmethod
106     def node_fqdn(cls, node):
107         return node.name
108
109     @classmethod
110     def node_start_time(cls, node):
111         time_str = node.extra['launch_time'].split('.', 2)[0] + 'UTC'
112         return time.mktime(time.strptime(
113                 time_str,'%Y-%m-%dT%H:%M:%S%Z')) - time.timezone
114
115     @classmethod
116     def node_id(cls, node):
117         return node.id