PROJECT_MOVED -> https://lab.nexedi.com/nexedi/slapos
[slapos.git] / slapos / recipe / slapconfiguration.py
1 ##############################################################################
2 #
3 # Copyright (c) 2012 Vifib SARL and Contributors. All Rights Reserved.
4 #
5 # WARNING: This program as such is intended to be used by professional
6 # programmers who take the whole responsibility of assessing all potential
7 # consequences resulting from its eventual inadequacies and bugs
8 # End users who are looking for a ready-to-use solution with commercial
9 # guarantees and support are strongly adviced to contract a Free Software
10 # Service Company
11 #
12 # This program is Free Software; you can redistribute it and/or
13 # modify it under the terms of the GNU General Public License
14 # as published by the Free Software Foundation; either version 3
15 # of the License, or (at your option) any later version.
16 #
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software
24 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 #
26 ##############################################################################
27
28 import json
29 import os
30
31 import slapos.slap
32 from slapos.recipe.librecipe import unwrap
33 from ConfigParser import RawConfigParser
34 from netaddr import valid_ipv4, valid_ipv6
35 from slapos.util import mkdir_p
36
37 class Recipe(object):
38 """
39 Retrieves slap partition parameters, and makes them available to other
40 buildout section in various ways, and in various encodings.
41 Populates the buildout section it is used in with all slap partition
42 parameters.
43 Also provides access to partition properties: all IPv4, IPv6 and tap
44 interfaces it is allowed to use.
45
46 Input:
47 url
48 Slap server url.
49 Example:
50 ${slap-connection:server-url}
51 key & cert (optional)
52 Path of files containing key and certificate for secure connection to
53 slap server.
54 Example:
55 ${slap-connection:key-file}
56 ${slap-connection:cert-file}
57 computer
58 Computer identifier.
59 Example:
60 ${slap-connection:computer-id}
61 partition
62 Partition identifier.
63 Example:
64 ${slap-connection:partition-id}
65 storage-home
66 Path of folder configured for data storage
67 Example:
68 ${storage-configuration:storage-home}
69
70 Output:
71 slap-software-type
72 Current partition's software type.
73 ipv4
74 Set of IPv4 addresses.
75 ipv6
76 Set of IPv6 addresses.
77 ipv4-random
78 One of the IPv4 addresses.
79 ipv6-random
80 One of the IPv6 addresses.
81 tap
82 Set of TAP interfaces.
83 tap-network-information-dict
84 Dict of set of all TAP network information
85 tap-ipv4
86 ipv4 allowed for this TAP
87 tap-gateway
88 ipv4 of gateway interface of this TAP
89 tap-netmask
90 ipv4 netmask address of this TAP
91 tap-network
92 ipv4 network address of this TAP
93 configuration
94 Dict of all parameters.
95 storage-dict
96 Dict of partition data path when it is configured
97 configuration.<key>
98 One key per partition parameter.
99 Partition parameter whose name cannot be represented unambiguously in
100 buildout syntax are ignored. They cannot be accessed from buildout syntax
101 anyway, and are available through "configuration" output key.
102 instance-state
103 The instance state.
104 """
105
106 # XXX: used to detect if a configuration key is a valid section key. This
107 # assumes buildout uses ConfigParser - which is currently the case.
108 OPTCRE_match = RawConfigParser.OPTCRE.match
109
110 def __init__(self, buildout, name, options):
111 parameter_dict = self.fetch_parameter_dict(options,
112 buildout['buildout']['directory'])
113
114 match = self.OPTCRE_match
115 for key, value in parameter_dict.iteritems():
116 if match(key) is not None:
117 continue
118 options['configuration.' + key] = value
119
120 def fetch_parameter_dict(self, options, instance_root):
121 slap = slapos.slap.slap()
122 slap.initializeConnection(
123 options['url'],
124 options.get('key'),
125 options.get('cert'),
126 )
127 computer_partition = slap.registerComputerPartition(
128 options['computer'],
129 options['partition'],
130 )
131 parameter_dict = computer_partition.getInstanceParameterDict()
132 options['instance-state'] = computer_partition.getState()
133 # XXX: those are not partition parameters, strictly speaking.
134 # Make them available as individual section keys.
135 for his_key in (
136 'slap_software_type',
137 'slap_computer_partition_id',
138 'slap_computer_id',
139 'slap_software_release_url',
140 'slave_instance_list',
141 'timestamp',
142 ):
143 try:
144 value = parameter_dict.pop(his_key)
145 except KeyError:
146 pass
147 else:
148 options[his_key.replace('_', '-')] = value
149 ipv4_set = set()
150 v4_add = ipv4_set.add
151 ipv6_set = set()
152 v6_add = ipv6_set.add
153 tap_set = set()
154 tap_add = tap_set.add
155 route_gw_set = set()
156 route_gw_add = route_gw_set.add
157 route_mask_set = set()
158 route_mask_add = route_mask_set.add
159 route_ipv4_set = set()
160 route_v4_add = route_ipv4_set.add
161 route_network_set = set()
162 route_net_add = route_network_set.add
163 for tap, ip in parameter_dict.pop('ip_list'):
164 tap_add(tap)
165 if valid_ipv4(ip):
166 v4_add(ip)
167 elif valid_ipv6(ip):
168 v6_add(ip)
169 # XXX: emit warning on unknown address type ?
170
171 if 'full_ip_list' in parameter_dict:
172 for item in parameter_dict.pop('full_ip_list'):
173 if len(item) == 5:
174 tap, ip, gw, netmask, network = item
175 if tap.startswith('route_'):
176 if valid_ipv4(gw):
177 route_gw_add(gw)
178 if valid_ipv4(netmask):
179 route_mask_add(netmask)
180 if valid_ipv4(ip):
181 route_v4_add(ip)
182 if valid_ipv4(network):
183 route_net_add(network)
184
185 options['ipv4'] = ipv4_set
186 options['ipv6'] = ipv6_set
187
188 # also export single ip values for those recipes that don't support sets.
189 if ipv4_set:
190 options['ipv4-random'] = list(ipv4_set)[0].encode('UTF-8')
191 if ipv6_set:
192 options['ipv6-random'] = list(ipv6_set)[0].encode('UTF-8')
193 if route_ipv4_set:
194 options['tap-ipv4'] = list(route_ipv4_set)[0].encode('UTF-8')
195 options['tap-network-information-dict'] = dict(ipv4=route_ipv4_set,
196 netmask=route_mask_set,
197 gateway=route_gw_set,
198 network=route_network_set)
199 else:
200 options['tap-network-information-dict'] = {}
201 if route_gw_set:
202 options['tap-gateway'] = list(route_gw_set)[0].encode('UTF-8')
203 if route_mask_set:
204 options['tap-netmask'] = list(route_mask_set)[0].encode('UTF-8')
205 if route_network_set:
206 options['tap-network'] = list(route_network_set)[0].encode('UTF-8')
207
208 storage_home = options.get('storage-home')
209 storage_dict = {}
210 if storage_home and os.path.exists(storage_home) and \
211 os.path.isdir(storage_home):
212 for filename in os.listdir(storage_home):
213 storage_path = os.path.join(storage_home, filename,
214 options['slap-computer-partition-id'])
215 if os.path.exists(storage_path) and os.path.isdir(storage_path):
216 storage_link = os.path.join(instance_root, 'DATA', filename)
217 mkdir_p(os.path.join(instance_root, 'DATA'))
218 if not os.path.lexists(storage_link):
219 os.symlink(storage_path, storage_link)
220 storage_dict[filename] = storage_link
221 options['storage-dict'] = storage_dict
222
223 options['tap'] = tap_set
224 return self._expandParameterDict(options, parameter_dict)
225
226 def _expandParameterDict(self, options, parameter_dict):
227 options['configuration'] = parameter_dict
228 return parameter_dict
229
230 install = update = lambda self: []
231
232 class Serialised(Recipe):
233 def _expandParameterDict(self, options, parameter_dict):
234 options['configuration'] = parameter_dict = unwrap(parameter_dict)
235 if isinstance(parameter_dict, dict):
236 return parameter_dict
237 else:
238 return {}
239
240 class JsonDump(Recipe):
241 def __init__(self, buildout, name, options):
242 parameter_dict = self.fetch_parameter_dict(options)
243 self._json_output = options['json-output']
244 with os.fdopen(os.open(self._json_output, os.O_WRONLY | os.O_CREAT, 0600), 'w') as fout:
245 fout.write(json.dumps(parameter_dict, indent=2, sort_keys=True))
246
247 def install(self):
248 return [self._json_output]
249
250 update = install
251