11345: Simplify and consolodate retry for API throttling errors.
[arvados.git] / services / nodemanager / arvnodeman / computenode / driver / azure.py
1 #!/usr/bin/env python
2
3 from __future__ import absolute_import, print_function
4
5 import pipes
6 import time
7
8 import libcloud.compute.base as cloud_base
9 import libcloud.compute.providers as cloud_provider
10 import libcloud.compute.types as cloud_types
11 from libcloud.common.exceptions import BaseHTTPError
12
13 from . import BaseComputeNodeDriver
14 from .. import arvados_node_fqdn, arvados_timestamp, ARVADOS_TIMEFMT
15
16 class ComputeNodeDriver(BaseComputeNodeDriver):
17
18     DEFAULT_DRIVER = cloud_provider.get_driver(cloud_types.Provider.AZURE_ARM)
19     SEARCH_CACHE = {}
20     CLOUD_ERRORS = BaseComputeNodeDriver.CLOUD_ERRORS
21
22     def __init__(self, auth_kwargs, list_kwargs, create_kwargs,
23                  driver_class=DEFAULT_DRIVER):
24
25         if not list_kwargs.get("ex_resource_group"):
26             raise Exception("Must include ex_resource_group in Cloud List configuration (list_kwargs)")
27
28         create_kwargs["ex_resource_group"] = list_kwargs["ex_resource_group"]
29
30         self.tags = {key[4:]: value
31                      for key, value in create_kwargs.iteritems()
32                      if key.startswith('tag_')}
33         # filter out tags from create_kwargs
34         create_kwargs = {key: value
35                          for key, value in create_kwargs.iteritems()
36                          if not key.startswith('tag_')}
37         super(ComputeNodeDriver, self).__init__(
38             auth_kwargs, list_kwargs, create_kwargs,
39             driver_class)
40
41     def create_cloud_name(self, arvados_node):
42         uuid_parts = arvados_node['uuid'].split('-', 2)
43         return 'compute-{parts[2]}-{parts[0]}'.format(parts=uuid_parts)
44
45     def arvados_create_kwargs(self, size, arvados_node):
46         tags = {
47             'booted_at': time.strftime(ARVADOS_TIMEFMT, time.gmtime()),
48             'arv-ping-url': self._make_ping_url(arvados_node)
49         }
50         tags.update(self.tags)
51
52         name = self.create_cloud_name(arvados_node)
53         customdata = """#!/bin/sh
54 mkdir -p    /var/tmp/arv-node-data/meta-data
55 echo %s > /var/tmp/arv-node-data/arv-ping-url
56 echo %s > /var/tmp/arv-node-data/meta-data/instance-id
57 echo %s > /var/tmp/arv-node-data/meta-data/instance-type
58 """ % (pipes.quote(tags['arv-ping-url']),
59        pipes.quote(name),
60        pipes.quote(size.id))
61
62         return {
63             'name': name,
64             'ex_tags': tags,
65             'ex_customdata': customdata
66         }
67
68     def sync_node(self, cloud_node, arvados_node):
69         try:
70             self.real.ex_create_tags(cloud_node,
71                                      {'hostname': arvados_node_fqdn(arvados_node)})
72             return True
73         except BaseHTTPError as b:
74             return False
75
76     def _init_image(self, urn):
77         return "image", self.get_image(urn)
78
79     def list_nodes(self):
80         # Azure only supports filtering node lists by resource group.
81         # Do our own filtering based on tag.
82         nodes = [node for node in
83                 super(ComputeNodeDriver, self).list_nodes(ex_fetch_nic=False)
84                 if node.extra["tags"].get("arvados-class") == self.tags["arvados-class"]]
85         for n in nodes:
86             # Need to populate Node.size
87             if not n.size:
88                 n.size = self.sizes[n.extra["properties"]["hardwareProfile"]["vmSize"]]
89         return nodes
90
91     def broken(self, cloud_node):
92         """Return true if libcloud has indicated the node is in a "broken" state."""
93         # UNKNOWN means the node state is unrecognized, which in practice means some combination
94         # of failure that the Azure libcloud driver doesn't know how to interpret.
95         return (cloud_node.state in (cloud_types.NodeState.ERROR, cloud_types.NodeState.UNKNOWN))
96
97     @classmethod
98     def node_fqdn(cls, node):
99         return node.extra["tags"].get("hostname")
100
101     @classmethod
102     def node_start_time(cls, node):
103         return arvados_timestamp(node.extra["tags"].get("booted_at"))
104
105     @classmethod
106     def node_id(cls, node):
107         return node.name