2 # Copyright (C) The Arvados Authors. All rights reserved.
4 # SPDX-License-Identifier: AGPL-3.0
6 from __future__ import absolute_import, print_function
16 from apiclient import errors as apierror
18 from .baseactor import BaseNodeManagerActor
20 from libcloud.common.types import LibcloudError
21 from libcloud.common.exceptions import BaseHTTPError
23 # IOError is the base class for socket.error, ssl.SSLError, and friends.
24 # It seems like it hits the sweet spot for operations we want to retry:
25 # it's low-level, but unlikely to catch code bugs.
26 NETWORK_ERRORS = (IOError,)
27 ARVADOS_ERRORS = NETWORK_ERRORS + (apierror.Error,)
28 CLOUD_ERRORS = NETWORK_ERRORS + (LibcloudError, BaseHTTPError)
30 actor_class = BaseNodeManagerActor
32 class NodeManagerConfig(ConfigParser.SafeConfigParser):
33 """Node Manager Configuration class.
35 This a standard Python ConfigParser, with additional helper methods to
36 create objects instantiated with configuration information.
39 LOGGING_NONLEVELS = frozenset(['file'])
41 def __init__(self, *args, **kwargs):
42 # Can't use super() because SafeConfigParser is an old-style class.
43 ConfigParser.SafeConfigParser.__init__(self, *args, **kwargs)
44 for sec_name, settings in {
45 'Arvados': {'insecure': 'no',
50 'Daemon': {'min_nodes': '0',
53 'max_poll_time': '300',
54 'poll_stale_after': '600',
55 'max_total_price': '0',
56 'boot_fail_after': str(sys.maxint),
57 'node_stale_after': str(60 * 60 * 2),
59 'node_mem_scaling': '0.95'},
60 'Manage': {'address': '127.0.0.1',
62 'ManagementToken': ''},
63 'Logging': {'file': '/dev/stderr',
66 if not self.has_section(sec_name):
67 self.add_section(sec_name)
68 for opt_name, value in settings.iteritems():
69 if not self.has_option(sec_name, opt_name):
70 self.set(sec_name, opt_name, value)
72 def get_section(self, section, transformer=None):
74 for key, value in self.items(section):
75 if transformer is not None:
77 value = transformer(value)
78 except (TypeError, ValueError):
84 return {key: getattr(logging, self.get('Logging', key).upper())
85 for key in self.options('Logging')
86 if key not in self.LOGGING_NONLEVELS}
88 def dispatch_classes(self):
89 mod_name = 'arvnodeman.computenode.dispatch'
90 if self.has_option('Daemon', 'dispatcher'):
91 mod_name = '{}.{}'.format(mod_name,
92 self.get('Daemon', 'dispatcher'))
93 module = importlib.import_module(mod_name)
94 return (module.ComputeNodeSetupActor,
95 module.ComputeNodeShutdownActor,
96 module.ComputeNodeUpdateActor,
97 module.ComputeNodeMonitorActor)
99 def new_arvados_client(self):
100 if self.has_option('Daemon', 'certs_file'):
101 certs_file = self.get('Daemon', 'certs_file')
104 insecure = self.getboolean('Arvados', 'insecure')
105 http = httplib2.Http(timeout=self.getint('Arvados', 'timeout'),
107 disable_ssl_certificate_validation=insecure)
108 return arvados.api(version='v1',
109 host=self.get('Arvados', 'host'),
110 token=self.get('Arvados', 'token'),
114 def new_cloud_client(self):
115 module = importlib.import_module('arvnodeman.computenode.driver.' +
116 self.get('Cloud', 'provider'))
117 driver_class = module.ComputeNodeDriver.DEFAULT_DRIVER
118 if self.has_option('Cloud', 'driver_class'):
119 d = self.get('Cloud', 'driver_class').split('.')
120 mod = '.'.join(d[:-1])
122 driver_class = importlib.import_module(mod).__dict__[cls]
123 auth_kwargs = self.get_section('Cloud Credentials')
124 if 'timeout' in auth_kwargs:
125 auth_kwargs['timeout'] = int(auth_kwargs['timeout'])
126 return module.ComputeNodeDriver(auth_kwargs,
127 self.get_section('Cloud List'),
128 self.get_section('Cloud Create'),
129 driver_class=driver_class)
131 def node_sizes(self, all_sizes):
132 """Finds all acceptable NodeSizes for our installation.
134 Returns a list of (NodeSize, kwargs) pairs for each NodeSize object
135 returned by libcloud that matches a size listed in our config file.
139 for sec_name in self.sections():
140 sec_words = sec_name.split(None, 2)
141 if sec_words[0] != 'Size':
143 size_spec = self.get_section(sec_name, int)
144 if 'price' in size_spec:
145 size_spec['price'] = float(size_spec['price'])
146 size_kwargs[sec_words[1]] = size_spec
147 # EC2 node sizes are identified by id. GCE sizes are identified by name.
149 for size in all_sizes:
150 if size.id in size_kwargs:
151 matching_sizes.append((size, size_kwargs[size.id]))
152 elif size.name in size_kwargs:
153 matching_sizes.append((size, size_kwargs[size.name]))
154 return matching_sizes
156 def shutdown_windows(self):
158 for n in self.get('Cloud', 'shutdown_windows').split(',')]