Bug 54 : Fix PE NS instantiation error
[osm/SO.git] / rwcm / plugins / rwconman / rift / tasklets / rwconmantasklet / jujuconf.py
1 #
2 # Copyright 2016 RIFT.IO Inc
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 #
16
17 import asyncio
18 import re
19 import tempfile
20 import yaml
21 import os
22
23 import rift.mano.utils.juju_api as juju
24 from . import riftcm_config_plugin
25
26
27 # Charm service name accepts only a to z and -.
28 def get_vnf_unique_name(nsr_name, vnfr_short_name, member_vnf_index):
29 name = "{}-{}-{}".format(nsr_name, vnfr_short_name, member_vnf_index)
30 new_name = ''
31 for c in name:
32 if c.isdigit():
33 c = chr(97 + int(c))
34 elif not c.isalpha():
35 c = "-"
36 new_name += c
37 return new_name.lower()
38
39
40 class JujuConfigPlugin(riftcm_config_plugin.RiftCMConfigPluginBase):
41 """
42 Juju implementation of the riftcm_config_plugin.RiftCMConfigPluginBase
43 """
44 def __init__(self, dts, log, loop, account):
45 riftcm_config_plugin.RiftCMConfigPluginBase.__init__(self, dts, log, loop, account)
46 self._name = account.name
47 self._type = 'juju'
48 self._ip_address = account.juju.ip_address
49 self._port = account.juju.port
50 self._user = account.juju.user
51 self._secret = account.juju.secret
52 self._rift_install_dir = os.environ['RIFT_INSTALL']
53 self._rift_artif_dir = os.environ['RIFT_ARTIFACTS']
54
55 ############################################################
56 # This is wrongfully overloaded with 'juju' private data. #
57 # Really need to separate agent_vnfr from juju vnfr data. #
58 # Currently, this holds agent_vnfr, which has actual vnfr, #
59 # then this juju overloads actual vnfr with its own #
60 # dictionary elemetns (WRONG!!!) #
61 self._juju_vnfs = {}
62 ############################################################
63
64 self._tasks = {}
65 self._api = juju.JujuApi(log, loop,
66 self._ip_address, self._port,
67 self._user, self._secret)
68
69 @property
70 def name(self):
71 return self._name
72
73 @property
74 def agent_type(self):
75 return self._type
76
77 @property
78 def api(self):
79 return self._api
80
81 @property
82 def agent_data(self):
83 return dict(
84 type=self.agent_type,
85 name=self.name,
86 host=self._ip_address,
87 port=self._port,
88 user=self._user,
89 secret=self._secret
90 )
91
92 def vnfr(self, vnfr_id):
93 try:
94 vnfr = self._juju_vnfs[vnfr_id].vnfr
95 except KeyError:
96 self._log.error("jujuCA: Did not find VNFR %s in juju plugin", vnfr_id)
97 return None
98
99 return vnfr
100
101 def get_service_name(self, vnfr_id):
102 vnfr = self.vnfr(vnfr_id)
103 if vnfr and 'vnf_juju_name' in vnfr:
104 return vnfr['vnf_juju_name']
105 return None
106
107 def juju_log(self, level, name, log_str, *args):
108 if name is not None:
109 g_log_str = 'jujuCA:({}) {}'.format(name, log_str)
110 else:
111 g_log_str = 'jujuCA: {}'.format(log_str)
112 getattr(self._log, level)(g_log_str, *args)
113
114 # TBD: Do a better, similar to config manager
115 def xlate(self, tag, tags):
116 # TBD
117 if tag is None:
118 return tag
119 val = tag
120 if re.search('<.*>', tag):
121 self._log.debug("jujuCA: Xlate value %s", tag)
122 try:
123 if tag == '<rw_mgmt_ip>':
124 val = tags['rw_mgmt_ip']
125 except KeyError as e:
126 self._log.info("jujuCA: Did not get a value for tag %s, e=%s",
127 tag, e)
128 return val
129
130 @asyncio.coroutine
131 def notify_create_vlr(self, agent_nsr, agent_vnfr, vld, vlr):
132 """
133 Notification of create VL record
134 """
135 return True
136
137 @asyncio.coroutine
138 def notify_create_vnfr(self, agent_nsr, agent_vnfr):
139 """
140 Notification of create Network VNF record
141 Returns True if configured using config_agent
142 """
143 # Deploy the charm if specified for the vnf
144 self._log.debug("jujuCA: create vnfr nsr=%s vnfr=%s",
145 agent_nsr.name, agent_vnfr.name)
146 self._log.debug("jujuCA: Config = %s",
147 agent_vnfr.vnf_configuration)
148 try:
149 vnf_config = agent_vnfr.vnfr_msg.vnf_configuration
150 self._log.debug("jujuCA: vnf_configuration = %s", vnf_config)
151 if not vnf_config.has_field('juju'):
152 return False
153 charm = vnf_config.juju.charm
154 self._log.debug("jujuCA: charm = %s", charm)
155 except Exception as e:
156 self._log.Error("jujuCA: vnf_configuration error for vnfr {}: {}".
157 format(agent_vnfr.name, e))
158 return False
159
160 # Prepare unique name for this VNF
161 vnf_unique_name = get_vnf_unique_name(agent_nsr.name,
162 agent_vnfr.name,
163 agent_vnfr.member_vnf_index)
164 if vnf_unique_name in self._tasks:
165 self._log.warn("jujuCA: Service %s already deployed",
166 vnf_unique_name)
167
168 vnfr_dict = agent_vnfr.vnfr
169 vnfr_dict.update({'vnf_juju_name': vnf_unique_name,
170 'charm': charm,
171 'nsr_id': agent_nsr.id,
172 'member_vnf_index': agent_vnfr.member_vnf_index,
173 'tags': {},
174 'active': False,
175 'config': vnf_config,
176 'vnfr_name' : agent_vnfr.name})
177 self._log.debug("jujuCA: Charm %s for vnf %s to be deployed as %s",
178 charm, agent_vnfr.name, vnf_unique_name)
179
180 # Find the charm directory
181 try:
182 path = os.path.join(self._rift_artif_dir,
183 'launchpad/libs',
184 agent_vnfr.vnfr_msg.vnfd_ref,
185 'charms/trusty',
186 charm)
187 self._log.debug("jujuCA: Charm dir is {}".format(path))
188 if not os.path.isdir(path):
189 self._log.error("jujuCA: Did not find the charm directory at {}".
190 format(path))
191 path = None
192 except Exception as e:
193 self.log.exception(e)
194 return False
195
196 if vnf_unique_name not in self._tasks:
197 self._tasks[vnf_unique_name] = {}
198
199 self._tasks[vnf_unique_name]['deploy'] = self.loop.create_task(
200 self.api.deploy_service(charm, vnf_unique_name, path=path))
201
202 self._log.debug("jujuCA: Deploying service %s",
203 vnf_unique_name)
204
205 return True
206
207 @asyncio.coroutine
208 def notify_instantiate_vnfr(self, agent_nsr, agent_vnfr):
209 """
210 Notification of Instantiate NSR with the passed nsr id
211 """
212 return True
213
214 @asyncio.coroutine
215 def notify_instantiate_vlr(self, agent_nsr, agent_vnfr, vlr):
216 """
217 Notification of Instantiate NSR with the passed nsr id
218 """
219 return True
220
221 @asyncio.coroutine
222 def notify_terminate_nsr(self, agent_nsr, agent_vnfr):
223 """
224 Notification of Terminate the network service
225 """
226 return True
227
228 @asyncio.coroutine
229 def notify_terminate_vnfr(self, agent_nsr, agent_vnfr):
230 """
231 Notification of Terminate the network service
232 """
233 self._log.debug("jujuCA: Terminate VNFr {}, current vnfrs={}".
234 format(agent_vnfr.name, self._juju_vnfs))
235 try:
236 vnfr = agent_vnfr.vnfr
237 service = vnfr['vnf_juju_name']
238
239 self._log.debug ("jujuCA: Terminating VNFr %s, %s",
240 agent_vnfr.name, service)
241 self._tasks[service]['destroy'] = self.loop.create_task(
242 self.api.destroy_service(service)
243 )
244
245 del self._juju_vnfs[agent_vnfr.id]
246 self._log.debug ("jujuCA: current vnfrs={}".
247 format(self._juju_vnfs))
248 if service in self._tasks:
249 tasks = []
250 for action in self._tasks[service].keys():
251 #if self.check_task_status(service, action):
252 tasks.append(action)
253 del tasks
254 except KeyError as e:
255 self._log.debug ("jujuCA: Termiating charm service for VNFr {}, e={}".
256 format(agent_vnfr.name, e))
257 except Exception as e:
258 self._log.error("jujuCA: Exception terminating charm service for VNFR {}: {}".
259 format(agent_vnfr.name, e))
260
261 return True
262
263 @asyncio.coroutine
264 def notify_terminate_vlr(self, agent_nsr, agent_vnfr, vlr):
265 """
266 Notification of Terminate the virtual link
267 """
268 return True
269
270 def check_task_status(self, service, action):
271 #self.log.debug("jujuCA: check task status for %s, %s" % (service, action))
272 try:
273 task = self._tasks[service][action]
274 if task.done():
275 self.log.debug("jujuCA: Task for %s, %s done" % (service, action))
276 e = task.exception()
277 if e:
278 self.log.error("jujuCA: Error in task for {} and {} : {}".
279 format(service, action, e))
280 raise Exception(e)
281 r= task.result()
282 if r:
283 self.log.debug("jujuCA: Task for {} and {}, returned {}".
284 format(service, action,r))
285 return True
286 else:
287 self.log.debug("jujuCA: task {}, {} not done".
288 format(service, action))
289 return False
290 except KeyError as e:
291 self.log.error("jujuCA: KeyError for task for {} and {}: {}".
292 format(service, action, e))
293 except Exception as e:
294 self.log.error("jujuCA: Error for task for {} and {}: {}".
295 format(service, action, e))
296 raise
297 return True
298
299 @asyncio.coroutine
300 def vnf_config_primitive(self, nsr_id, vnfr_id, primitive, output):
301 self._log.debug("jujuCA: VNF config primititve {} for nsr {}, vnfr_id {}".
302 format(primitive, nsr_id, vnfr_id))
303 output.execution_status = "failed"
304 output.execution_id = ''
305 output.execution_error_details = ''
306
307 try:
308 vnfr = self._juju_vnfs[vnfr_id].vnfr
309 except KeyError:
310 self._log.error("jujuCA: Did not find VNFR %s in juju plugin",
311 vnfr_id)
312 return
313
314 try:
315 service = vnfr['vnf_juju_name']
316 vnf_config = vnfr['config']
317 self._log.debug("VNF config %s", vnf_config)
318 configs = vnf_config.service_primitive
319 for config in configs:
320 if config.name == primitive.name:
321 self._log.debug("jujuCA: Found the config primitive %s",
322 config.name)
323 params = {}
324 for parameter in primitive.parameter:
325 if parameter.value:
326 val = self.xlate(parameter.value, vnfr['tags'])
327 # TBD do validation of the parameters
328 data_type = 'string'
329 found = False
330 for ca_param in config.parameter:
331 if ca_param.name == parameter.name:
332 data_type = ca_param.data_type
333 found = True
334 break
335 if data_type == 'integer':
336 val = int(parameter.value)
337 if not found:
338 self._log.warn("jujuCA: Did not find parameter {} for {}".
339 format(parameter, config.name))
340 params.update({parameter.name: val})
341
342 if config.name == 'config':
343 if len(params):
344 self._log.debug("jujuCA: applying config with params {} for service {}".
345 format(params, service))
346
347 rc = yield from self.api.apply_config(params, service=service)
348
349 if rc:
350 output.execution_status = "completed"
351 self._log.debug("jujuCA: applied config {} on {}".
352 format(params, service))
353 else:
354 output.execution_status = 'failed'
355 output.execution_error_details = \
356 'Failed to apply config: {}'.format(params)
357 self._log.error("jujuCA: Error applying config {} on service {}".
358 format(params, service))
359 else:
360 self._log.warn("jujuCA: Did not find valid paramaters for config : {}".
361 format(primitive.parameter))
362 else:
363 self._log.debug("jujuCA: Execute action {} on service {} with params {}".
364 format(config.name, service, params))
365
366 resp = yield from self.api.execute_action(config.name,
367 params,
368 service=service)
369
370 if resp:
371 if 'error' in resp:
372 output.execution_error_details = resp['error']['Message']
373 else:
374 output.execution_id = resp['action']['tag']
375 output.execution_status = resp['status']
376 if output.execution_status == 'failed':
377 output.execution_error_details = resp['message']
378 self._log.debug("jujuCA: execute action {} on service {} returned {}".
379 format(config.name, service, output.execution_status))
380 else:
381 self._log.error("jujuCA: error executing action {} for {} with {}".
382 format(config.name, service, params))
383 output.execution_id = ''
384 output.execution_status = 'failed'
385 output.execution_error_details = "Failed to queue the action"
386 break
387
388 except KeyError as e:
389 self._log.info("VNF %s does not have config primititves, e=%s", vnfr_id, e)
390
391 @asyncio.coroutine
392 def apply_config(self, agent_nsr, agent_vnfr, config, rpc_ip):
393 """ Notification on configuration of an NSR """
394 pass
395
396 @asyncio.coroutine
397 def apply_ns_config(self, agent_nsr, agent_vnfrs, rpc_ip):
398 """
399
400 ###### TBD - This really does not belong here. Looks more like NS level script ####
401 ###### apply_config should be called for a particular VNF only here ###############
402
403 Hook: Runs the user defined script. Feeds all the necessary data
404 for the script thro' yaml file.
405
406 Args:
407 rpc_ip (YangInput_Nsr_ExecNsConfigPrimitive): The input data.
408 nsr (NetworkServiceRecord): Description
409 vnfrs (dict): VNFR ID => VirtualNetworkFunctionRecord
410
411 """
412 def get_meta(agent_nsr):
413 unit_names, initial_params, vnfr_index_map = {}, {}, {}
414
415 for vnfr_id in agent_nsr.vnfr_ids:
416 juju_vnf = self._juju_vnfs[vnfr_id].vnfr
417
418 # Vnfr -> index ref
419 vnfr_index_map[vnfr_id] = juju_vnf['member_vnf_index']
420
421 # Unit name
422 unit_names[vnfr_id] = juju_vnf['vnf_juju_name']
423
424 # Flatten the data for simplicity
425 param_data = {}
426 self._log.debug("Juju Config:%s", juju_vnf['config'])
427 for primitive in juju_vnf['config'].initial_config_primitive:
428 for parameter in primitive.parameter:
429 value = self.xlate(parameter.value, juju_vnf['tags'])
430 param_data[parameter.name] = value
431
432 initial_params[vnfr_id] = param_data
433
434
435 return unit_names, initial_params, vnfr_index_map
436
437 unit_names, init_data, vnfr_index_map = get_meta(agent_nsr)
438
439 # The data consists of 4 sections
440 # 1. Account data
441 # 2. The input passed.
442 # 3. Juju unit names (keyed by vnfr ID).
443 # 4. Initial config data (keyed by vnfr ID).
444 data = dict()
445 data['config_agent'] = dict(
446 name=self._name,
447 host=self._ip_address,
448 port=self._port,
449 user=self._user,
450 secret=self._secret
451 )
452 data["rpc_ip"] = rpc_ip.as_dict()
453 data["unit_names"] = unit_names
454 data["init_config"] = init_data
455 data["vnfr_index_map"] = vnfr_index_map
456
457 tmp_file = None
458 with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
459 tmp_file.write(yaml.dump(data, default_flow_style=True)
460 .encode("UTF-8"))
461
462 self._log.debug("jujuCA: Creating a temp file: {} with input data".format(
463 tmp_file.name))
464
465 # Get the full path to the script
466 script = ''
467 if rpc_ip.user_defined_script[0] == '/':
468 # The script has full path, use as is
469 script = rpc_ip.user_defined_script
470 else:
471 script = os.path.join(self._rift_artif_dir, 'launchpad/libs', agent_nsr.id, 'scripts',
472 rpc_ip.user_defined_script)
473 self.log.debug("jujuCA: Checking for script in %s", script)
474 if not os.path.exists(script):
475 script = os.path.join(self._rift_install_dir, 'usr/bin', rpc_ip.user_defined_script)
476
477 cmd = "{} {}".format(rpc_ip.user_defined_script, tmp_file.name)
478 self._log.debug("jujuCA: Running the CMD: {}".format(cmd))
479
480 coro = asyncio.create_subprocess_shell(cmd, loop=self._loop,
481 stderr=asyncio.subprocess.PIPE)
482 process = yield from coro
483 err = yield from process.stderr.read()
484 task = self._loop.create_task(process.wait())
485
486 return task, err
487
488 @asyncio.coroutine
489 def apply_initial_config(self, agent_nsr, agent_vnfr):
490 """
491 Apply the initial configuration
492 Expect config directives mostly, not actions
493 Actions in initial config may not work based on charm design
494 """
495
496 vnfr = agent_vnfr.vnfr
497 service = vnfr['vnf_juju_name']
498
499 rc = yield from self.api.is_service_up(service=service)
500 if not rc:
501 return False
502
503 action_ids = []
504 try:
505 vnf_cat = agent_vnfr.vnfr_msg
506 if vnf_cat and vnf_cat.mgmt_interface.ip_address:
507 vnfr['tags'].update({'rw_mgmt_ip': vnf_cat.mgmt_interface.ip_address})
508 self._log.debug("jujuCA:(%s) tags: %s", vnfr['vnf_juju_name'], vnfr['tags'])
509
510 config = {}
511 try:
512 for primitive in vnfr['config'].initial_config_primitive:
513 self._log.debug("jujuCA:(%s) Initial config primitive %s", vnfr['vnf_juju_name'], primitive)
514 if primitive.name == 'config':
515 for param in primitive.parameter:
516 if vnfr['tags']:
517 val = self.xlate(param.value, vnfr['tags'])
518 config.update({param.name: val})
519 except KeyError as e:
520 self._log.exception("jujuCA:(%s) Initial config error(%s): config=%s",
521 vnfr['vnf_juju_name'], str(e), config)
522 config = None
523 return False
524
525 if config:
526 self.juju_log('info', vnfr['vnf_juju_name'],
527 "Applying Initial config:%s",
528 config)
529
530 rc = yield from self.api.apply_config(config, service=service)
531 if rc is False:
532 self.log.error("Service {} is in error state".format(service))
533 return False
534
535
536 # Apply any actions specified as part of initial config
537 for primitive in vnfr['config'].initial_config_primitive:
538 if primitive.name != 'config':
539 self._log.debug("jujuCA:(%s) Initial config action primitive %s",
540 vnfr['vnf_juju_name'], primitive)
541 action = primitive.name
542 params = {}
543 for param in primitive.parameter:
544 val = self.xlate(param.value, vnfr['tags'])
545 params.update({param.name: val})
546
547 self._log.info("jujuCA:(%s) Action %s with params %s",
548 vnfr['vnf_juju_name'], action, params)
549
550 resp = yield from self.api.execute_action(action, params,
551 service=service)
552 if 'error' in resp:
553 self._log.error("Applying initial config failed for {} with {}: {}".
554 format(action, params, resp))
555 return False
556
557 action_ids.append(resp['action']['tag'])
558
559 except KeyError as e:
560 self._log.info("Juju config agent(%s): VNFR %s not managed by Juju",
561 vnfr['vnf_juju_name'], agent_vnfr.id)
562 return False
563 except Exception as e:
564 self._log.exception("jujuCA:(%s) Exception juju apply_initial_config for VNFR {}: {}".
565 format(vnfr['vnf_juju_name'], agent_vnfr.id, e))
566 return False
567
568 # Check if all actions completed
569 pending = True
570 while pending:
571 pending = False
572 for act in action_ids:
573 resp = yield from self.api.get_action_status(act)
574 if 'error' in resp:
575 self._log.error("Initial config failed: {}".format(resp))
576 return False
577
578 if resp['status'] == 'failed':
579 self._log.error("Initial config action failed: {}".format(resp))
580 return False
581
582 if resp['status'] == 'pending':
583 pending = True
584
585 return True
586
587 def add_vnfr_managed(self, agent_vnfr):
588 if agent_vnfr.id not in self._juju_vnfs.keys():
589 self._log.info("juju config agent: add vnfr={}/{}".
590 format(agent_vnfr.name, agent_vnfr.id))
591 self._juju_vnfs[agent_vnfr.id] = agent_vnfr
592
593 def is_vnfr_managed(self, vnfr_id):
594 try:
595 if vnfr_id in self._juju_vnfs:
596 return True
597 except Exception as e:
598 self._log.debug("jujuCA: Is VNFR {} managed: {}".
599 format(vnfr_id, e))
600 return False
601
602 @asyncio.coroutine
603 def is_configured(self, vnfr_id):
604 try:
605 agent_vnfr = self._juju_vnfs[vnfr_id]
606 vnfr = agent_vnfr.vnfr
607 if vnfr['active']:
608 return True
609
610 vnfr = self._juju_vnfs[vnfr_id].vnfr
611 service = vnfr['vnf_juju_name']
612 resp = self.api.is_service_active(service=service)
613 self._juju_vnfs[vnfr_id]['active'] = resp
614 self._log.debug("jujuCA: Service state for {} is {}".
615 format(service, resp))
616 return resp
617
618 except KeyError:
619 self._log.debug("jujuCA: VNFR id {} not found in config agent".
620 format(vnfr_id))
621 return False
622 except Exception as e:
623 self._log.error("jujuCA: VNFR id {} is_configured: {}".
624 format(vnfr_id, e))
625 return False
626
627 @asyncio.coroutine
628 def get_config_status(self, agent_nsr, agent_vnfr):
629 """Get the configuration status for the VNF"""
630 rc = 'unknown'
631
632 try:
633 vnfr = agent_vnfr.vnfr
634 service = vnfr['vnf_juju_name']
635 except KeyError:
636 # This VNF is not managed by Juju
637 return rc
638
639 rc = 'configuring'
640
641 if not self.check_task_status(service, 'deploy'):
642 return rc
643
644 try:
645 resp = yield from self.api.get_service_status(service=service)
646 self._log.debug("jujuCA: Get service %s status? %s", service, resp)
647
648 if resp == 'error':
649 return 'error'
650 if resp == 'active':
651 return 'configured'
652 except KeyError:
653 self._log.error("jujuCA: Check unknown service %s status", service)
654 except Exception as e:
655 self._log.error("jujuCA: Caught exception when checking for service is active: %s", e)
656 self._log.exception(e)
657
658 return rc
659
660 def get_action_status(self, execution_id):
661 ''' Get the action status for an execution ID
662 *** Make sure this is NOT a asyncio coroutine function ***
663 '''
664
665 try:
666 self._log.debug("jujuCA: Get action status for {}".format(execution_id))
667 resp = self.api._get_action_status(execution_id)
668 self._log.debug("jujuCA: Action status: {}".format(resp))
669 return resp
670 except Exception as e:
671 self._log.error("jujuCA: Error fetching execution status for %s",
672 execution_id)
673 self._log.exception(e)
674 raise e