4b2bb4c7fbe66a288861bbb282569a7319090b7b
[osm/riftware.git] /
1 #!/usr/bin/env python3
2 import argparse
3 import hashlib
4 import ipaddress
5 import itertools
6 import jujuclient
7 import logging
8 import sys
9 import time
10 import yaml
11
12
13 logging.basicConfig(filename="/tmp/rift_ns_add_corp.log", level=logging.DEBUG)
14 logger = logging.getLogger()
15
16 ch = logging.StreamHandler()
17 ch.setLevel(logging.INFO)
18
19 # create formatter and add it to the handlers
20 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
21 ch.setFormatter(formatter)
22 logger.addHandler(ch)
23
24
25 dry_run = False
26
27 class JujuActionError(Exception):
28     pass
29
30
31 class JujuClient(object):
32     """Class for executing Juju actions """
33     def __init__(self, ip, port, user, passwd):
34         self._ip = ip
35         self._port = port
36         self._user = user
37         self._passwd = passwd
38
39         endpoint = 'wss://%s:%d' % (ip, port)
40         logger.debug("Using endpoint=%s", endpoint)
41         if dry_run:
42             return
43         self.env = jujuclient.Environment(endpoint)
44         self.env.login(passwd, user)
45
46     def get_service(self, name):
47         return self.env.get_service(name)
48
49     def _get_units(self, name):
50         """
51         Get the units associated with service
52         """
53         units = self.env.status(name)['Services'][name]['Units']
54         units = list(units.keys())
55
56         # convert to a friendly format for juju-python-client
57         units[:] = [('unit-%s' % u).replace('/', '-') for u in units]
58         return units
59
60     def exec_action(self, name, action_name, params, block=False):
61         logger.debug("execute actiion %s using params %s", action_name, params)
62         if dry_run:
63             return
64
65         actions = jujuclient.Actions(self.env)
66         results = actions.enqueue_units(self._get_units(name),
67                                         action_name,
68                                         params)
69         if not block:
70             return results
71
72         if 'error' in results['results'][0].keys():
73             raise JujuActionError("Juju action error: %s" % results['results'][0])
74
75         action = results['results'][0]['action']
76         info = actions.info([action])
77         i = 0
78         logging.debug("Initial action results: %s", results['results'][0])
79         while info['results'][0]['status'] not in ['completed', 'failed']:
80             time.sleep(1)
81             info = actions.info([action])
82
83             # break out if the action doesn't complete in 10 secs
84             i += 1
85             if i == 10:
86                 raise JujuActionError("Juju action timed out after 30 seconds")
87
88         if info['results'][0]['status'] != 'completed':
89             raise JujuActionError("Action %s failure: %s" % (action_name, info['results'][0]))
90
91         return info
92
93
94 class CharmAction(object):
95     def __init__(self, deployed_name, action_name, action_params=None):
96         self._deployed_name = deployed_name
97         self._action_name = action_name
98         self._params = action_params if action_params is not None else []
99
100     def execute(self, juju_client):
101         logger.info("Executing charm (%s) action (%s) with params (%s)",
102                      self._deployed_name, self._action_name, self._params)
103         try:
104             info = juju_client.exec_action(
105                     name=self._deployed_name,
106                     action_name=self._action_name,
107                     params=self._params,
108                     block=True
109                     )
110
111         except JujuActionError as e:
112             logger.error("Juju charm (%s) action (%s) failed: %s",
113                          self._deployed_name, self._action_name, str(e))
114             raise
115
116         logger.debug("Juju charm (%s) action (%s) success.",
117                      self._deployed_name, self._action_name)
118
119
120 class DeployedProxyCharm(object):
121     def __init__(self, juju_client, service_name, mgmt_ip=None, charm_name=None):
122         self._juju_client = juju_client
123         self.service_name = service_name
124         self.mgmt_ip = mgmt_ip
125         self.charm_name = charm_name
126
127     def do_action(self, action_name, action_params={}):
128         action = CharmAction(self.service_name, action_name, action_params)
129         action.execute(self._juju_client)
130
131
132 class SixWindPEProxyCharm(DeployedProxyCharm):
133     USER = "root"
134     PASSWD = "6windos"
135
136     def configure_interface(self, iface_name, ipv4_interface_str=None):
137         action = "configure-interface"
138         params = {'iface-name', iface_name}
139
140         if ipv4_interface_str is None:
141             # Use ipaddress module to validate ipv4 interface string
142             ip_intf = ipaddress.IPv4Interface(ipv4_interface_str)
143             params["cidr"] = ip_intf.with_prefixlen
144
145             self.do_action(action, params)
146         else:
147             self.do_action(action, params)
148
149
150     def add_corporation(self, domain_name, user_iface_name, vlan_id, corp_gw,
151                         corp_net, local_net="10.255.255.0/24", local_net_area="0"):
152         logger.debug("Add corporation called with params: %s", locals())
153
154         action = "add-corporation"
155         params = {
156                 "domain-name": domain_name,
157                 "iface-name": user_iface_name,
158                 "vlan-id": int(vlan_id),
159                 "cidr": corp_net,
160                 "area": corp_gw,
161                 "subnet-cidr":local_net,
162                 "subnet-area":local_net_area,
163                 }
164
165         self.do_action(action, params)
166
167     def connect_domains(self, domain_name, core_iface_name, local_ip, remote_ip,
168                         internal_local_ip, internal_remote_ip, tunnel_name,
169                         tunnel_key, tunnel_type="gre"):
170
171         logger.debug("Connect domains called with params: %s", locals())
172
173         action = "connect-domains"
174         params = {
175                 "domain-name": domain_name,
176                 "iface-name": core_iface_name,
177                 "tunnel-name": tunnel_name,
178                 "local-ip": local_ip,
179                 "remote-ip": remote_ip,
180                 "tunnel-key": tunnel_key,
181                 "internal-local-ip": internal_local_ip,
182                 "internal-remote-ip": internal_remote_ip,
183                 "tunnel-type":tunnel_type,
184                 }
185
186         self.do_action(action, params)
187
188
189 class PEGroupConfig(object):
190     def __init__(self, pe_group_cfg):
191         self._pe_group_cfg = pe_group_cfg
192
193     def _get_param_value(self, param_name):
194         for param in self._pe_group_cfg["parameter"]:
195             if param["name"] == param_name:
196                 return param["value"]
197
198         raise ValueError("PE param not found: %s" % param_name)
199
200     @property
201     def vlan_id(self):
202         return self._get_param_value("Vlan ID")
203
204     @property
205     def interface_name(self):
206         return self._get_param_value("Interface Name")
207
208     @property
209     def corp_network(self):
210         return self._get_param_value("Corp. Network")
211
212     @property
213     def corp_gateway(self):
214         return self._get_param_value("Corp. Gateway")
215
216
217 class AddCorporationRequest(object):
218     def __init__(self, add_corporation_rpc):
219         self._add_corporation_rpc = add_corporation_rpc
220
221     @property
222     def name(self):
223         return self._add_corporation_rpc["name"]
224
225     @property
226     def param_groups(self):
227         return self._add_corporation_rpc["parameter_group"]
228
229     @property
230     def params(self):
231         return self._add_corporation_rpc["parameter"]
232
233     @property
234     def corporation_name(self):
235         for param in self.params:
236             if param["name"] == "Corporation Name":
237                 return param["value"]
238
239         raise ValueError("Could not find 'Corporation Name' field")
240
241     @property
242     def tunnel_key(self):
243         for param in self.params:
244             if param["name"] == "Tunnel Key":
245                 return param["value"]
246
247         raise ValueError("Could not find 'Tunnel Key' field")
248
249     def get_pe_parameter_group_map(self):
250         group_name_map = {}
251         for group in self.param_groups:
252             group_name_map[group["name"]] = group
253
254         return group_name_map
255
256     def get_parameter_name_map(self):
257         name_param_map = {}
258         for param in self.params:
259             name_param_map[param["name"]] = param
260
261         return name_param_map
262
263     @classmethod
264     def from_yaml_cfg(cls, yaml_hdl):
265         config = yaml.load(yaml_hdl)
266         return cls(
267                 config["rpc_ip"],
268                 )
269
270
271 class JujuVNFConfig(object):
272     def __init__(self, vnfr_index_map, vnf_name_map, vnf_init_config_map):
273         self._vnfr_index_map = vnfr_index_map
274         self._vnf_name_map = vnf_name_map
275         self._vnf_init_config_map = vnf_name_map
276
277     def get_service_name(self, vnf_index):
278         for vnfr_id, index in self._vnfr_index_map.items():
279             if index != vnf_index:
280                 continue
281
282             return self._vnf_name_map[vnfr_id]
283
284         raise ValueError("VNF Index not found: %s" % vnf_index)
285
286     def get_vnfr_id(self, vnf_index):
287         for vnfr_id, index in self._vnfr_index_map.items():
288             if index != vnf_index:
289                 continue
290
291             return vnfr_id
292
293         raise ValueError("VNF Index not found: %s" % vnf_index)
294
295     @classmethod
296     def from_yaml_cfg(cls, yaml_hdl):
297         config = yaml.load(yaml_hdl)
298         return cls(
299                 config["vnfr_index_map"],
300                 config["unit_names"],
301                 config["init_config"],
302                 )
303
304
305 class JujuClientConfig(object):
306     def __init__(self, juju_ctrl_cfg):
307         self._juju_ctrl_cfg = juju_ctrl_cfg
308
309     @property
310     def name(self):
311         return self._juju_ctrl_cfg["name"]
312
313     @property
314     def host(self):
315         return self._juju_ctrl_cfg["host"]
316
317     @property
318     def port(self):
319         return self._juju_ctrl_cfg["port"]
320
321     @property
322     def user(self):
323         return self._juju_ctrl_cfg["user"]
324
325     @property
326     def secret(self):
327         return self._juju_ctrl_cfg["secret"]
328
329     @classmethod
330     def from_yaml_cfg(cls, yaml_hdl):
331         config = yaml.load(yaml_hdl)
332         return cls(
333                 config["config_agent"],
334                 )
335
336
337 class OSM_MWC_Demo(object):
338     VNF_INDEX_NAME_MAP = {
339             "PE1": 1,
340             "PE2": 2,
341             "PE3": 3,
342         }
343
344     CORE_PE_CONN_MAP = {
345             "PE1": {
346                 "PE2": {
347                     "ifacename": "eth1",
348                     "ip": "10.10.10.9",
349                     "mask": "30",
350                     "internal_local_ip": "10.255.255.1"
351                 },
352                 "PE3": {
353                     "ifacename": "eth2",
354                     "ip": "10.10.10.1",
355                     "mask": "30",
356                     "internal_local_ip": "10.255.255.1"
357                 },
358             },
359             "PE2": {
360                 "PE1": {
361                     "ifacename": "eth1",
362                     "ip": "10.10.10.10",
363                     "mask": "30",
364                     "internal_local_ip": "10.255.255.2"
365                 },
366                 "PE3": {
367                     "ifacename": "eth2",
368                     "ip": "10.10.10.6",
369                     "mask": "30",
370                     "internal_local_ip": "10.255.255.2"
371                 }
372             },
373             "PE3": {
374                 "PE1": {
375                     "ifacename": "eth1",
376                     "ip": "10.10.10.2",
377                     "mask": "30",
378                     "internal_local_ip": "10.255.255.3"
379                 },
380                 "PE2": {
381                     "ifacename": "eth2",
382                     "ip": "10.10.10.5",
383                     "mask": "30",
384                     "internal_local_ip": "10.255.255.3"
385                 }
386             }
387         }
388
389     @staticmethod
390     def get_pe_vnf_index(pe_name):
391         if pe_name not in OSM_MWC_Demo.VNF_INDEX_NAME_MAP:
392             raise ValueError("Could not find PE name: %s", pe_name)
393
394         return OSM_MWC_Demo.VNF_INDEX_NAME_MAP[pe_name]
395
396     @staticmethod
397     def get_src_core_iface(src_pe_name, dest_pe_name):
398         return OSM_MWC_Demo.CORE_PE_CONN_MAP[src_pe_name][dest_pe_name]["ifacename"]
399
400     @staticmethod
401     def get_local_ip(src_pe_name, dest_pe_name):
402         return OSM_MWC_Demo.CORE_PE_CONN_MAP[src_pe_name][dest_pe_name]["ip"]
403
404     @staticmethod
405     def get_remote_ip(src_pe_name, dest_pe_name):
406         return OSM_MWC_Demo.CORE_PE_CONN_MAP[dest_pe_name][src_pe_name]["ip"]
407
408     @staticmethod
409     def get_internal_local_ip(src_pe_name, dest_pe_name):
410         return OSM_MWC_Demo.CORE_PE_CONN_MAP[src_pe_name][dest_pe_name]["internal_local_ip"]
411
412     @staticmethod
413     def get_internal_remote_ip(src_pe_name, dest_pe_name):
414         return OSM_MWC_Demo.CORE_PE_CONN_MAP[dest_pe_name][src_pe_name]["internal_local_ip"]
415
416
417 def add_pe_corporation(src_pe_name, src_pe_charm, src_pe_group_cfg, corporation_name):
418     domain_name = corporation_name
419     vlan_id = src_pe_group_cfg.vlan_id
420     corp_gw = src_pe_group_cfg.corp_gateway
421     corp_net = src_pe_group_cfg.corp_network
422
423     user_iface = src_pe_group_cfg.interface_name
424
425     src_pe_charm.add_corporation(domain_name, user_iface, vlan_id, corp_gw, corp_net)
426
427
428 def connect_pe_domains(src_pe_name, src_pe_charm, dest_pe_name, corporation_name, tunnel_key):
429     domain_name = corporation_name
430     core_iface_name = OSM_MWC_Demo.get_src_core_iface(src_pe_name, dest_pe_name)
431     local_ip = OSM_MWC_Demo.get_local_ip(src_pe_name, dest_pe_name)
432     remote_ip = OSM_MWC_Demo.get_remote_ip(src_pe_name, dest_pe_name)
433     internal_local_ip = OSM_MWC_Demo.get_internal_local_ip(src_pe_name, dest_pe_name)
434     internal_remote_ip = OSM_MWC_Demo.get_internal_remote_ip(src_pe_name, dest_pe_name)
435
436
437     src_pe_idx = OSM_MWC_Demo.get_pe_vnf_index(src_pe_name)
438     dest_pe_idx = OSM_MWC_Demo.get_pe_vnf_index(dest_pe_name)
439
440     # Create a 4 digit hash of the corporation name
441     hash_object = hashlib.md5(corporation_name.encode())
442     corp_hash = hash_object.hexdigest()[-4:]
443
444     # Tunnel name is the 4 digit corporation name hash followed by
445     # src index and dest index. When there are less than 10 PE's
446     # this creates a 8 character tunnel name which is the limit.
447     tunnel_name = "".join([corp_hash, "_", str(src_pe_idx), str(dest_pe_idx)])
448
449     src_pe_charm.connect_domains(domain_name, core_iface_name, local_ip, remote_ip,
450                                  internal_local_ip, internal_remote_ip, tunnel_name,
451                                  tunnel_key)
452
453
454 def main(argv=sys.argv[1:]):
455     parser = argparse.ArgumentParser()
456     parser.add_argument("yaml_cfg_file", type=argparse.FileType('r'))
457     parser.add_argument("--dry-run", action="store_true")
458     parser.add_argument("--quiet", "-q", dest="verbose", action="store_false")
459     args = parser.parse_args()
460     if args.verbose:
461         ch.setLevel(logging.DEBUG)
462
463     global dry_run
464     dry_run = args.dry_run
465
466     yaml_str = args.yaml_cfg_file.read()
467
468     juju_cfg = JujuClientConfig.from_yaml_cfg(yaml_str)
469     juju_client = JujuClient(juju_cfg.host, juju_cfg.port, juju_cfg.user, juju_cfg.secret)
470
471     juju_vnf_config = JujuVNFConfig.from_yaml_cfg(yaml_str)
472
473     rpc_request = AddCorporationRequest.from_yaml_cfg(yaml_str)
474     pe_param_group_map = rpc_request.get_pe_parameter_group_map()
475
476     pe_name_charm_map = {}
477     for pe_name, pe_group_cfg in pe_param_group_map.items():
478         # The PE name (i.e. PE1) must be in the parameter group name so we can correlate
479         # to an actual VNF in the descriptor.
480         pe_vnf_index = OSM_MWC_Demo.get_pe_vnf_index(pe_name)
481
482         # Get the deployed VNFR charm service name
483         pe_charm_service_name = juju_vnf_config.get_service_name(pe_vnf_index)
484
485         pe_name_charm_map[pe_name] = SixWindPEProxyCharm(juju_client, pe_charm_service_name)
486
487     # At this point we have SixWindPEProxyCharm() instances for each PE and each
488     # PE param group configuration.
489     for src_pe_name in pe_param_group_map:
490         add_pe_corporation(
491                 src_pe_name=src_pe_name,
492                 src_pe_charm=pe_name_charm_map[src_pe_name],
493                 src_pe_group_cfg=PEGroupConfig(pe_param_group_map[src_pe_name]),
494                 corporation_name=rpc_request.corporation_name
495                 )
496
497     # Create a permutation of all PE's involved in this topology and connect
498     # them together by creating tunnels with matching keys
499     for src_pe_name, dest_pe_name in itertools.permutations(pe_name_charm_map, 2):
500         connect_pe_domains(
501                 src_pe_name=src_pe_name,
502                 src_pe_charm=pe_name_charm_map[src_pe_name],
503                 dest_pe_name=dest_pe_name,
504                 corporation_name=rpc_request.corporation_name,
505                 tunnel_key=rpc_request.tunnel_key,
506                 )
507
508 if __name__ == "__main__":
509     try:
510         main()
511     except Exception as e:
512         logger.exception("Caught exception when executing add_corporation ns")
513         raise