2 ###############################################################################
3 # Copyright (c) 2015, 2016 Ericsson AB and others.
4 # szilard.cserey@ericsson.com
5 # peter.barabas@ericsson.com
6 # All rights reserved. This program and the accompanying materials
7 # are made available under the terms of the Apache License, Version 2.0
8 # which accompanies this distribution, and is available at
9 # http://www.apache.org/licenses/LICENSE-2.0
10 ###############################################################################
36 title: Deployment Environment Adapter (DEA)
37 # DEA API version supported
44 title: Deployment Hardware Adapter (DHA)
45 # DHA API version supported
50 # Adapter to use for this definition
51 # adapter: [ipmi|libvirt]
55 # Mandatory properties are id and role.
56 # All other properties are adapter specific.
57 # For Non-Fuel nodes controlled by:
58 # - ipmi adapter you need to provide:
63 # and you *MAY* provide (optional, not added by reap.py):
65 # - libvirt adapter you need to provide:
66 # libvirtName: <whatever>
67 # libvirtTemplate: [libvirt/vms/controller.xml | libvirt/vms/compute.xml]
69 # For the Fuel Node you need to provide:
70 # libvirtName: <whatever>
71 # libvirtTemplate: libvirt/vms/fuel.xml
78 # Adding the Fuel node as node id {node_id}
79 # which may not be correct - please adjust as needed.
82 TEMPLATER = 'templater.py'
84 DISKS = {'fuel': '100G',
91 def __init__(self, dea_file, dha_file, comment, base_dea, template):
92 self.dea_file = dea_file
93 self.dha_file = dha_file
94 self.comment = comment
95 self.base_dea = base_dea
96 self.template = template
100 self.last_node = None
103 env_list = parse(exec_cmd('fuel env'))
104 if len(env_list) == 0:
105 err('No environment deployed')
106 elif len(env_list) > 1:
107 err('More than 1 environment deployed')
108 self.env = env_list[0]
109 self.env_id = self.env[E['id']]
111 def download_config(self, config_type):
112 log('Download %s config for environment %s'
113 % (config_type, self.env_id))
114 exec_cmd('fuel %s --env %s --download --dir %s'
115 % (config_type, self.env_id, self.temp_dir))
117 def download_node_config(self, nodeid):
118 log('Download node %s config for environment %s to %s'
119 % (nodeid, self.env_id,self.temp_dir))
120 exec_cmd('fuel deployment --node-id %s --env %s --default --dir %s'
121 % (nodeid, self.env_id, self.temp_dir))
123 def write(self, file, text, newline=True):
124 mode = 'a' if os.path.isfile(file) else 'w'
125 with open(file, mode) as f:
126 f.write('%s%s' % (text, ('\n' if newline else '')))
128 def write_yaml(self, file, data, newline=True):
129 self.write(file, yaml.dump(data, default_flow_style=False).strip(),
132 def get_node_by_id(self, node_list, node_id):
133 for node in node_list:
134 if node[N['id']] == node_id:
137 def reap_interface(self, node_id, interfaces):
138 interface, mac = self.get_interface(node_id)
141 if_name = self.check_dict_exists(interfaces, interface)
143 if_name = 'interfaces_%s' % str(len(interfaces) + 1)
144 interfaces[if_name] = interface
147 def reap_transformation(self, node_id, roles, transformations):
148 main_role = 'controller' if 'controller' in roles else 'compute'
149 node_file = glob.glob('%s/deployment_%s/%s.yaml'
150 % (self.temp_dir, self.env_id, node_id))
152 with open(node_file[0]) as f:
153 node_config = yaml.load(f)
154 transformation = {'transformations':
155 node_config['network_scheme']['transformations']}
157 tr_name = self.check_dict_exists(transformations, transformation)
159 tr_name = 'transformations_%s' % str(len(transformations) + 1)
160 transformations[tr_name] = transformation
163 def check_dict_exists(self, main_dict, dict):
164 for key, val in main_dict.iteritems():
165 if cmp(dict, val) == 0:
168 def reap_nodes_interfaces_transformations(self):
169 node_list = parse(exec_cmd('fuel node'))
170 real_node_ids = [node[N['id']] for node in node_list]
172 min_node = real_node_ids[0]
178 for real_node_id in real_node_ids:
179 node_id = int(real_node_id) - int(min_node) + 1
180 self.last_node = node_id
181 node = self.get_node_by_id(node_list, real_node_id)
182 roles = commafy(node[N['roles']])
184 err('Fuel Node %s has no role' % real_node_id)
185 dea_node = {'id': node_id,
187 dha_node = {'id': node_id}
188 if_name, mac = self.reap_interface(real_node_id, interfaces)
189 log('reap transformation for node %s' % real_node_id)
190 tr_name = self.reap_transformation(real_node_id, roles,
193 {'interfaces': if_name,
194 'transformations': tr_name})
197 {'pxeMac': mac if mac else None,
202 'libvirtTemplate': None})
204 dea_nodes.append(dea_node)
205 dha_nodes.append(dha_node)
207 self.write_yaml(self.dha_file, {'nodes': dha_nodes}, False)
208 self.write_yaml(self.dea_file, {'nodes': dea_nodes})
209 self.write_yaml(self.dea_file, interfaces)
210 self.write_yaml(self.dea_file, transformations)
211 self.reap_fuel_node_info()
212 self.write_yaml(self.dha_file, {'disks': DISKS})
214 def reap_fuel_node_info(self):
217 'id': self.last_node + 1,
219 'libvirtTemplate': None,
222 'password': 'r00tme'}
224 dha_nodes.append(dha_node)
226 self.write(self.dha_file, DHA_2.format(node_id=dha_node['id']), False)
227 self.write_yaml(self.dha_file, dha_nodes)
229 def reap_environment_info(self):
230 network_file = ('%s/network_%s.yaml'
231 % (self.temp_dir, self.env_id))
232 network = self.read_yaml(network_file)
234 env = {'environment':
235 {'name': self.env[E['name']],
237 network['networking_parameters']['segmentation_type']}}
238 self.write_yaml(self.dea_file, env)
239 wanted_release = None
240 rel_list = parse(exec_cmd('fuel release'))
242 if rel[R['id']] == self.env[E['release_id']]:
243 wanted_release = rel[R['name']]
244 self.write_yaml(self.dea_file, {'wanted_release': wanted_release})
246 def reap_fuel_settings(self):
247 data = self.read_yaml('/etc/fuel/astute.yaml')
249 del data['ADMIN_NETWORK']['mac']
250 del data['ADMIN_NETWORK']['interface']
251 for key in ['ADMIN_NETWORK', 'HOSTNAME', 'DNS_DOMAIN', 'DNS_SEARCH',
252 'DNS_UPSTREAM', 'NTP1', 'NTP2', 'NTP3', 'FUEL_ACCESS']:
253 fuel[key] = data[key]
254 for key in fuel['ADMIN_NETWORK'].keys():
255 if key not in ['ipaddress', 'netmask',
256 'dhcp_pool_start', 'dhcp_pool_end', 'ssh_network']:
257 del fuel['ADMIN_NETWORK'][key]
259 ## FIXME(armband): Factor in support for adding public/other interfaces.
260 ## TODO: Following block expects interface name(s) to be lowercase only
261 interfaces_list = exec_cmd('ip -o -4 a | grep -e "e[nt][hopsx].*"')
262 for interface in re.split('\n', interfaces_list):
263 # Sample output line from above cmd:
264 # 3: eth1 inet 10.0.2.10/24 scope global eth1 valid_lft forever ...
265 ifcfg = re.split(r'\s+', interface)
266 ifcfg_name = ifcfg[1]
267 ifcfg_ipaddr = ifcfg[3]
269 # Filter out admin interface (device name is not known, match IP)
270 current_network = netaddr.IPNetwork(ifcfg_ipaddr)
271 if str(current_network.ip) == fuel['ADMIN_NETWORK']['ipaddress']:
274 # Read ifcfg-* network interface config file, write IFCFG_<IFNAME>
275 ifcfg_sec = 'IFCFG_%s' % ifcfg_name.upper()
278 ifcfg_f = ('/etc/sysconfig/network-scripts/ifcfg-%s' % ifcfg_name)
279 with open(ifcfg_f) as f:
281 if line.startswith('#'):
283 (key, val) = line.split('=')
284 ifcfg_data[key.lower()] = val.rstrip()
286 # Keep only needed info (e.g. filter-out type=Ethernet).
287 fuel[ifcfg_sec]['ipaddress'] = ifcfg_data['ipaddr']
288 fuel[ifcfg_sec]['device'] = ifcfg_data['device']
289 fuel[ifcfg_sec]['netmask'] = str(current_network.netmask)
290 fuel[ifcfg_sec]['gateway'] = ifcfg_data['gateway']
292 self.write_yaml(self.dea_file, {'fuel': fuel})
294 def reap_network_settings(self):
295 network_file = ('%s/network_%s.yaml'
296 % (self.temp_dir, self.env_id))
297 data = self.read_yaml(network_file)
299 network['networking_parameters'] = data['networking_parameters']
300 network['networks'] = data['networks']
301 for net in network['networks']:
304 self.write_yaml(self.dea_file, {'network': network})
306 def reap_settings(self):
307 settings_file = '%s/settings_%s.yaml' % (self.temp_dir, self.env_id)
308 settings = self.read_yaml(settings_file)
309 self.write_yaml(self.dea_file, {'settings': settings})
311 def get_interface(self, real_node_id):
312 exec_cmd('fuel node --node-id %s --network --download --dir %s'
313 % (real_node_id, self.temp_dir))
314 interface_file = ('%s/node_%s/interfaces.yaml'
315 % (self.temp_dir, real_node_id))
316 interfaces = self.read_yaml(interface_file)
317 interface_config = {}
319 for interface in interfaces:
321 for network in interface['assigned_networks']:
322 networks.append(network['name'])
323 if network['name'] == 'fuelweb_admin':
324 pxe_mac = interface['mac']
326 interface_config[interface['name']] = networks
327 return interface_config, pxe_mac
329 def read_yaml(self, yaml_file):
330 with open(yaml_file) as f:
335 delete(self.dea_file)
336 delete(self.dha_file)
338 self.temp_dir = tempfile.mkdtemp()
339 date = time.strftime('%c')
340 self.write(self.dea_file,
341 DEA_1.format(date=date, comment=self.comment), False)
342 self.write(self.dha_file,
343 DHA_1.format(date=date, comment=self.comment))
346 # Need to download deployment with explicit node ids
347 node_list = parse(exec_cmd('fuel node'))
348 real_node_ids = [node[N['id']] for node in node_list]
350 self.download_node_config(','.join(real_node_ids))
352 self.download_config('settings')
353 self.download_config('network')
355 def create_base_dea(self):
356 exec_cmd('python %s %s %s %s'
357 % (TEMPLATER, self.dea_file, self.template, self.base_dea))
360 log('DEA file is available at %s' % self.dea_file)
361 log('DHA file is available at %s (this is just a template)'
364 log('DEA base file is available at %s' % self.base_dea)
365 shutil.rmtree(self.temp_dir)
369 self.reap_environment_info()
370 self.reap_nodes_interfaces_transformations()
371 self.reap_fuel_settings()
372 self.reap_network_settings()
375 self.create_base_dea()
379 def parse_arguments():
380 parser = ArgParser(prog='python %s' % __file__)
381 parser.add_argument('dea_file', nargs='?', action='store',
383 help='Deployment Environment Adapter: dea.yaml')
384 parser.add_argument('dha_file', nargs='?', action='store',
386 help='Deployment Hardware Adapter: dha.yaml')
387 parser.add_argument('comment', nargs='?', action='store', help='Comment')
388 parser.add_argument('-base_dea',
390 help='Create specified base DEA file from "dea_file"')
391 parser.add_argument('-template',
394 default='base_dea_template.yaml',
395 help='Base DEA is generated from this template')
396 args = parser.parse_args()
397 return (args.dea_file,
405 dea_file, dha_file, comment, base_dea, template = parse_arguments()
407 r = Reap(dea_file, dha_file, comment, base_dea, template)
411 if __name__ == '__main__':