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