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