Merge branch '7832-pysdk-use-slots' refs #7832
[arvados.git] / services / nodemanager / arvnodeman / config.py
1 #!/usr/bin/env python
2
3 from __future__ import absolute_import, print_function
4
5 import ConfigParser
6 import importlib
7 import logging
8 import ssl
9 import sys
10
11 import arvados
12 import httplib2
13 import pykka
14 from apiclient import errors as apierror
15
16 # IOError is the base class for socket.error and friends.
17 # It seems like it hits the sweet spot for operations we want to retry:
18 # it's low-level, but unlikely to catch code bugs.
19 NETWORK_ERRORS = (IOError, ssl.SSLError)
20 ARVADOS_ERRORS = NETWORK_ERRORS + (apierror.Error,)
21
22 actor_class = pykka.ThreadingActor
23
24 class NodeManagerConfig(ConfigParser.SafeConfigParser):
25     """Node Manager Configuration class.
26
27     This a standard Python ConfigParser, with additional helper methods to
28     create objects instantiated with configuration information.
29     """
30
31     LOGGING_NONLEVELS = frozenset(['file'])
32
33     def __init__(self, *args, **kwargs):
34         # Can't use super() because SafeConfigParser is an old-style class.
35         ConfigParser.SafeConfigParser.__init__(self, *args, **kwargs)
36         for sec_name, settings in {
37             'Arvados': {'insecure': 'no',
38                         'timeout': '15'},
39             'Daemon': {'min_nodes': '0',
40                        'max_nodes': '1',
41                        'poll_time': '60',
42                        'max_poll_time': '300',
43                        'poll_stale_after': '600',
44                        'max_total_price': '0',
45                        'boot_fail_after': str(sys.maxint),
46                        'node_stale_after': str(60 * 60 * 2)},
47             'Logging': {'file': '/dev/stderr',
48                         'level': 'WARNING'},
49         }.iteritems():
50             if not self.has_section(sec_name):
51                 self.add_section(sec_name)
52             for opt_name, value in settings.iteritems():
53                 if not self.has_option(sec_name, opt_name):
54                     self.set(sec_name, opt_name, value)
55
56     def get_section(self, section, transformer=None):
57         result = self._dict()
58         for key, value in self.items(section):
59             if transformer is not None:
60                 try:
61                     value = transformer(value)
62                 except (TypeError, ValueError):
63                     pass
64             result[key] = value
65         return result
66
67     def log_levels(self):
68         return {key: getattr(logging, self.get('Logging', key).upper())
69                 for key in self.options('Logging')
70                 if key not in self.LOGGING_NONLEVELS}
71
72     def dispatch_classes(self):
73         mod_name = 'arvnodeman.computenode.dispatch'
74         if self.has_option('Daemon', 'dispatcher'):
75             mod_name = '{}.{}'.format(mod_name,
76                                       self.get('Daemon', 'dispatcher'))
77         module = importlib.import_module(mod_name)
78         return (module.ComputeNodeSetupActor,
79                 module.ComputeNodeShutdownActor,
80                 module.ComputeNodeUpdateActor,
81                 module.ComputeNodeMonitorActor)
82
83     def new_arvados_client(self):
84         if self.has_option('Daemon', 'certs_file'):
85             certs_file = self.get('Daemon', 'certs_file')
86         else:
87             certs_file = None
88         insecure = self.getboolean('Arvados', 'insecure')
89         http = httplib2.Http(timeout=self.getint('Arvados', 'timeout'),
90                              ca_certs=certs_file,
91                              disable_ssl_certificate_validation=insecure)
92         return arvados.api(version='v1',
93                            host=self.get('Arvados', 'host'),
94                            token=self.get('Arvados', 'token'),
95                            insecure=insecure,
96                            http=http)
97
98     def new_cloud_client(self):
99         module = importlib.import_module('arvnodeman.computenode.driver.' +
100                                          self.get('Cloud', 'provider'))
101         auth_kwargs = self.get_section('Cloud Credentials')
102         if 'timeout' in auth_kwargs:
103             auth_kwargs['timeout'] = int(auth_kwargs['timeout'])
104         return module.ComputeNodeDriver(auth_kwargs,
105                                         self.get_section('Cloud List'),
106                                         self.get_section('Cloud Create'))
107
108     def node_sizes(self, all_sizes):
109         """Finds all acceptable NodeSizes for our installation.
110
111         Returns a list of (NodeSize, kwargs) pairs for each NodeSize object
112         returned by libcloud that matches a size listed in our config file.
113         """
114
115         size_kwargs = {}
116         for sec_name in self.sections():
117             sec_words = sec_name.split(None, 2)
118             if sec_words[0] != 'Size':
119                 continue
120             size_kwargs[sec_words[1]] = self.get_section(sec_name, int)
121         # EC2 node sizes are identified by id. GCE sizes are identified by name.
122         matching_sizes = []
123         for size in all_sizes:
124             if size.id in size_kwargs:
125                 matching_sizes.append((size, size_kwargs[size.id]))
126             elif size.name in size_kwargs:
127                 matching_sizes.append((size, size_kwargs[size.name]))
128         return matching_sizes
129
130     def shutdown_windows(self):
131         return [int(n)
132                 for n in self.get('Cloud', 'shutdown_windows').split(',')]