2 # Copyright 2016 RIFT.IO Inc
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
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
24 import rift
.mano
.utils
.juju_api
as juju
25 from . import riftcm_config_plugin
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
)
39 return re
.sub('\-+', '-', new_name
.lower())
42 class JujuConfigPlugin(riftcm_config_plugin
.RiftCMConfigPluginBase
):
44 Juju implementation of the riftcm_config_plugin.RiftCMConfigPluginBase
46 def __init__(self
, dts
, log
, loop
, project
, account
):
47 riftcm_config_plugin
.RiftCMConfigPluginBase
.__init
__(self
, dts
, log
, loop
,
49 self
._name
= account
.name
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']
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!!!) #
65 ############################################################
68 self
._api
= juju
.JujuApi(log
, loop
,
69 self
._ip
_address
, self
._port
,
70 self
._user
, self
._secret
)
89 host
=self
._ip
_address
,
95 def vnfr(self
, vnfr_id
):
97 vnfr
= self
._juju
_vnfs
[vnfr_id
].vnfr
99 self
._log
.error("jujuCA: Did not find VNFR %s in juju plugin", vnfr_id
)
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']
110 def juju_log(self
, level
, name
, log_str
, *args
):
112 g_log_str
= 'jujuCA:({}) {}'.format(name
, log_str
)
114 g_log_str
= 'jujuCA: {}'.format(log_str
)
115 getattr(self
._log
, level
)(g_log_str
, *args
)
117 # TBD: Do a better, similar to config manager
118 def xlate(self
, tag
, tags
):
123 if re
.search('<.*>', tag
):
124 self
._log
.debug("jujuCA: Xlate value %s", tag
)
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",
134 def notify_create_vlr(self
, agent_nsr
, agent_vnfr
, vld
, vlr
):
136 Notification of create VL record
141 def notify_create_vnfr(self
, agent_nsr
, agent_vnfr
):
143 Notification of create Network VNF record
144 Returns True if configured using config_agent
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
)
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'):
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
))
163 # Prepare unique name for this VNF
164 vnf_unique_name
= get_vnf_unique_name(agent_nsr
.name
,
166 agent_vnfr
.member_vnf_index
)
167 if vnf_unique_name
in self
._tasks
:
168 self
._log
.warn("jujuCA: Service %s already deployed",
171 vnfr_dict
= agent_vnfr
.vnfr
172 vnfr_dict
.update({'vnf_juju_name': vnf_unique_name
,
174 'nsr_id': agent_nsr
.id,
175 'member_vnf_index': agent_vnfr
.member_vnf_index
,
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
)
183 # Find the charm directory
185 path
= os
.path
.join(self
._rift
_var
_root
_dir
,
186 'launchpad/packages/vnfd',
188 agent_vnfr
.vnfr_msg
.vnfd
.id,
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
))
195 except Exception as e
:
196 self
.log
.exception(e
)
199 if vnf_unique_name
not in self
._tasks
:
200 self
._tasks
[vnf_unique_name
] = {}
202 self
._tasks
[vnf_unique_name
]['deploy'] = self
.loop
.create_task(
203 self
.api
.deploy_application(charm
, vnf_unique_name
, path
=path
))
205 self
._log
.debug("jujuCA: Deploying service %s",
211 def notify_instantiate_vnfr(self
, agent_nsr
, agent_vnfr
):
213 Notification of Instantiate NSR with the passed nsr id
218 def notify_instantiate_vlr(self
, agent_nsr
, agent_vnfr
, vlr
):
220 Notification of Instantiate NSR with the passed nsr id
225 def notify_terminate_nsr(self
, agent_nsr
, agent_vnfr
):
227 Notification of Terminate the network service
232 def notify_terminate_vnfr(self
, agent_nsr
, agent_vnfr
):
234 Notification of Terminate the network service
236 self
._log
.debug("jujuCA: Terminate VNFr {}, current vnfrs={}".
237 format(agent_vnfr
.name
, self
._juju
_vnfs
))
239 vnfr
= agent_vnfr
.vnfr
240 service
= vnfr
['vnf_juju_name']
242 self
._log
.debug ("jujuCA: Terminating VNFr %s, %s",
243 agent_vnfr
.name
, service
)
244 self
._tasks
[service
]['destroy'] = self
.loop
.create_task(
245 self
.api
.remove_application(service
)
248 del self
._juju
_vnfs
[agent_vnfr
.id]
249 self
._log
.debug ("jujuCA: current vnfrs={}".
250 format(self
._juju
_vnfs
))
251 if service
in self
._tasks
:
253 for action
in self
._tasks
[service
].keys():
256 except KeyError as e
:
257 self
._log
.debug ("jujuCA: Terminating charm service for VNFr {}, e={}".
258 format(agent_vnfr
.name
, e
))
259 except Exception as e
:
260 self
._log
.error("jujuCA: Exception terminating charm service for VNFR {}: {}".
261 format(agent_vnfr
.name
, e
))
266 def notify_terminate_vlr(self
, agent_nsr
, agent_vnfr
, vlr
):
268 Notification of Terminate the virtual link
273 def _vnf_config_primitive(self
, nsr_id
, vnfr_id
, primitive
,
274 vnf_config
=None, wait
=False):
275 self
._log
.debug("jujuCA: VNF config primitive {} for nsr {}, "
277 format(primitive
, nsr_id
, vnfr_id
))
279 if vnf_config
is None:
280 vnfr_msg
= yield from self
.get_vnfr(vnfr_id
)
282 msg
= "Unable to get VNFR {} through DTS".format(vnfr_id
)
286 vnf_config
= vnfr_msg
.vnf_configuration
287 self
._log
.debug("VNF config= %s", vnf_config
.as_dict())
290 service
= vnfr
['vnf_juju_name']
291 self
._log
.debug("VNF config %s", vnf_config
)
292 configs
= vnf_config
.config_primitive
293 for config
in configs
:
294 if config
.name
== primitive
.name
:
295 self
._log
.debug("jujuCA: Found the config primitive %s",
298 for parameter
in config
.parameter
:
300 for p
in primitive
.parameter
:
301 if p
.name
== parameter
.name
:
303 val
= self
.xlate(p
.value
, vnfr
['tags'])
307 val
= parameter
.default_value
310 # Check if mandatory parameter
311 if parameter
.mandatory
:
312 msg
= "VNFR {}: Primitive {} called " \
313 "without mandatory parameter {}". \
314 format(vnfr_msg
.name
, config
.name
,
317 return 'failed', '', msg
320 val
= self
.convert_value(val
, parameter
.data_type
)
321 params
.update({parameter
.name
: val
})
326 if config
.name
== 'config':
329 self
._log
.debug("jujuCA: applying config with "
330 "params {} for service {}".
331 format(params
, service
))
333 rc
= yield from self
.api
.apply_config(params
, application
=service
)
337 self
._log
.debug("jujuCA: applied config {} "
338 "on {}".format(params
, service
))
342 'Failed to apply config: {}'.format(params
)
343 self
._log
.error("jujuCA: Error applying "
344 "config {} on service {}".
345 format(params
, service
))
347 self
._log
.warn("jujuCA: Did not find valid "
348 "parameters for config : {}".
349 format(primitive
.parameter
))
352 self
._log
.debug("jujuCA: Execute action {} on "
353 "service {} with params {}".
354 format(config
.name
, service
, params
))
356 resp
= yield from self
.api
.execute_action(
364 details
= resp
['error']['message']
366 exec_id
= resp
['action']['tag']
369 details
= resp
['message']
371 self
._log
.debug("jujuCA: execute action {} on "
372 "service {} returned {}".
373 format(config
.name
, service
, rc
))
375 self
._log
.error("jujuCA: error executing action "
377 format(config
.name
, service
,
381 details
= "Failed to queue the action"
384 except KeyError as e
:
385 msg
= "VNF %s does not have config primitives, e=%s", \
387 self
._log
.exception(msg
)
388 raise ValueError(msg
)
390 while wait
and (rc
in ['pending', 'running']):
391 self
._log
.debug("JujuCA: action {}, rc {}".
393 yield from asyncio
.sleep(0.2, loop
=self
._loop
)
394 status
= yield from self
.api
.get_action_status(exec_id
)
395 rc
= status
['status']
397 return rc
, exec_id
, details
400 def vnf_config_primitive(self
, nsr_id
, vnfr_id
, primitive
, output
):
402 vnfr
= self
._juju
_vnfs
[vnfr_id
].vnfr
404 msg
= "Did not find VNFR {} in Juju plugin".format(vnfr_id
)
408 output
.execution_status
= "failed"
409 output
.execution_id
= ''
410 output
.execution_error_details
= ''
412 rc
, exec_id
, err
= yield from self
._vnf
_config
_primitive
(
417 self
._log
.debug("VNFR {} primitive {} exec status: {}".
418 format(vnfr
.name
, primitive
.name
, rc
))
419 output
.execution_status
= rc
420 output
.execution_id
= exec_id
421 output
.execution_error_details
= err
424 def apply_config(self
, agent_nsr
, agent_vnfr
, config
, rpc_ip
):
425 """ Notification on configuration of an NSR """
429 def apply_ns_config(self
, agent_nsr
, agent_vnfrs
, rpc_ip
):
432 ###### TBD - This really does not belong here. Looks more like NS level script ####
433 ###### apply_config should be called for a particular VNF only here ###############
435 Hook: Runs the user defined script. Feeds all the necessary data
436 for the script thro' yaml file.
439 rpc_ip (YangInput_Nsr_ExecNsConfigPrimitive): The input data.
440 nsr (NetworkServiceRecord): Description
441 vnfrs (dict): VNFR ID => VirtualNetworkFunctionRecord
444 def get_meta(agent_nsr
):
445 unit_names
, initial_params
, vnfr_index_map
= {}, {}, {}
447 for vnfr_id
in agent_nsr
.vnfr_ids
:
448 juju_vnf
= self
._juju
_vnfs
[vnfr_id
].vnfr
451 vnfr_index_map
[vnfr_id
] = juju_vnf
['member_vnf_index']
454 unit_names
[vnfr_id
] = juju_vnf
['vnf_juju_name']
456 # Flatten the data for simplicity
458 self
._log
.debug("Juju Config:%s", juju_vnf
['config'])
459 for primitive
in juju_vnf
['config'].initial_config_primitive
:
460 for parameter
in primitive
.parameter
:
461 value
= self
.xlate(parameter
.value
, juju_vnf
['tags'])
462 param_data
[parameter
.name
] = value
464 initial_params
[vnfr_id
] = param_data
467 return unit_names
, initial_params
, vnfr_index_map
469 unit_names
, init_data
, vnfr_index_map
= get_meta(agent_nsr
)
471 # The data consists of 4 sections
473 # 2. The input passed.
474 # 3. Juju unit names (keyed by vnfr ID).
475 # 4. Initial config data (keyed by vnfr ID).
477 data
['config_agent'] = dict(
479 host
=self
._ip
_address
,
484 data
["rpc_ip"] = rpc_ip
.as_dict()
485 data
["unit_names"] = unit_names
486 data
["init_config"] = init_data
487 data
["vnfr_index_map"] = vnfr_index_map
490 with tempfile
.NamedTemporaryFile(delete
=False) as tmp_file
:
491 tmp_file
.write(yaml
.dump(data
, default_flow_style
=True)
494 self
._log
.debug("jujuCA: Creating a temp file: {} with input data".format(
497 # Get the full path to the script
499 if rpc_ip
.user_defined_script
[0] == '/':
500 # The script has full path, use as is
501 script
= rpc_ip
.user_defined_script
503 script
= os
.path
.join(self
._rift
_var
_root
_dir
, 'launchpad/nsd',
505 agent_nsr
.id, 'scripts',
506 rpc_ip
.user_defined_script
)
507 self
.log
.debug("jujuCA: Checking for script in %s", script
)
508 if not os
.path
.exists(script
):
509 script
= os
.path
.join(self
._rift
_install
_dir
, 'usr/bin', rpc_ip
.user_defined_script
)
511 cmd
= "{} {}".format(rpc_ip
.user_defined_script
, tmp_file
.name
)
512 self
._log
.debug("jujuCA: Running the CMD: {}".format(cmd
))
514 coro
= asyncio
.create_subprocess_shell(cmd
, loop
=self
._loop
,
515 stderr
=asyncio
.subprocess
.PIPE
)
516 process
= yield from coro
517 err
= yield from process
.stderr
.read()
518 task
= self
._loop
.create_task(process
.wait())
523 def apply_initial_config(self
, agent_nsr
, agent_vnfr
):
525 Apply the initial configuration
526 Expect config directives mostly, not actions
527 Actions in initial config may not work based on charm design
531 vnfr
= self
._juju
_vnfs
[agent_vnfr
.id].vnfr
532 service
= vnfr
['vnf_juju_name']
534 self
._log
.debug("Did not find VNFR %s in Juju plugin",
538 vnfr_msg
= yield from self
.get_vnfr(agent_vnfr
.id)
540 msg
= "Unable to get VNFR {} ({}) through DTS". \
541 format(agent_vnfr
.id, agent_vnfr
.name
)
543 raise RuntimeError(msg
)
545 vnf_config
= vnfr_msg
.vnf_configuration
546 self
._log
.debug("VNFR %s config: %s", vnfr_msg
.name
,
547 vnf_config
.as_dict())
549 # Sort the primitive based on the sequence number
550 primitives
= sorted(vnf_config
.initial_config_primitive
,
553 self
._log
.debug("VNFR {}: No initial-config-primitive specified".
554 format(vnfr_msg
.name
))
557 rc
= yield from self
.api
.is_application_up(application
=service
)
562 if vnfr_msg
.mgmt_interface
.ip_address
:
563 vnfr
['tags'].update({'rw_mgmt_ip': vnfr_msg
.mgmt_interface
.ip_address
})
564 self
._log
.debug("jujuCA:(%s) tags: %s", vnfr
['vnf_juju_name'], vnfr
['tags'])
566 for primitive
in primitives
:
567 self
._log
.debug("(%s) Initial config primitive %s",
568 vnfr
['vnf_juju_name'], primitive
.as_dict())
569 if primitive
.config_primitive_ref
:
570 # Reference to a primitive in config primitive
572 def __init__(self
, name
):
577 prim
= Primitive(primitive
.config_primitive_ref
)
578 rc
, eid
, err
= yield from self
._vnf
_config
_primitive
(
586 msg
= "Error executing initial config primitive" \
587 " {} in VNFR {}: rc={}, stderr={}". \
588 format(prim
.name
, vnfr_msg
.name
, rc
, err
)
594 if primitive
.name
== 'config':
595 for param
in primitive
.parameter
:
597 val
= self
.xlate(param
.value
,
599 config
.update({param
.name
: val
})
602 self
.juju_log('info', vnfr
['vnf_juju_name'],
603 "Applying Initial config:%s",
606 rc
= yield from self
.api
.apply_config(
611 self
.log
.error("Service {} is in error state".format(service
))
614 # Apply any actions specified as part of initial config
615 for primitive
in vnfr
['config'].initial_config_primitive
:
616 if primitive
.name
!= 'config':
617 self
._log
.debug("jujuCA:(%s) Initial config action primitive %s",
618 vnfr
['vnf_juju_name'], primitive
)
619 action
= primitive
.name
621 for param
in primitive
.parameter
:
622 val
= self
.xlate(param
.value
, vnfr
['tags'])
623 params
.update({param
.name
: val
})
625 self
._log
.info("jujuCA:(%s) Action %s with params %s",
626 vnfr
['vnf_juju_name'], action
, params
)
627 self
._log
.debug("executing action")
628 resp
= yield from self
.api
.execute_action(
633 self
._log
.debug("executed action")
635 self
._log
.error("Applying initial config on {} failed for {} with {}: {}".
636 format(vnfr
['vnf_juju_name'], action
, params
, resp
))
638 except KeyError as e
:
639 self
._log
.info("Juju config agent(%s): VNFR %s not managed by Juju",
640 vnfr
['vnf_juju_name'], agent_vnfr
.id)
642 except Exception as e
:
643 self
._log
.exception("jujuCA:(%s) Exception juju "
644 "apply_initial_config for VNFR {}: {}".
645 format(vnfr
['vnf_juju_name'],
651 def add_vnfr_managed(self
, agent_vnfr
):
652 if agent_vnfr
.id not in self
._juju
_vnfs
.keys():
653 self
._log
.info("juju config agent: add vnfr={}/{}".
654 format(agent_vnfr
.name
, agent_vnfr
.id))
655 self
._juju
_vnfs
[agent_vnfr
.id] = agent_vnfr
657 def is_vnfr_managed(self
, vnfr_id
):
659 if vnfr_id
in self
._juju
_vnfs
:
661 except Exception as e
:
662 self
._log
.debug("jujuCA: Is VNFR {} managed: {}".
667 def is_configured(self
, vnfr_id
):
669 agent_vnfr
= self
._juju
_vnfs
[vnfr_id
]
670 vnfr
= agent_vnfr
.vnfr
674 vnfr
= self
._juju
_vnfs
[vnfr_id
].vnfr
675 service
= vnfr
['vnf_juju_name']
676 resp
= self
.api
.is_application_active(application
=service
)
677 self
._juju
_vnfs
[vnfr_id
]['active'] = resp
678 self
._log
.debug("jujuCA: Service state for {} is {}".
679 format(service
, resp
))
683 self
._log
.debug("jujuCA: VNFR id {} not found in config agent".
686 except Exception as e
:
687 self
._log
.error("jujuCA: VNFR id {} is_configured: {}".
692 def get_config_status(self
, agent_nsr
, agent_vnfr
):
693 """Get the configuration status for the VNF"""
697 vnfr
= agent_vnfr
.vnfr
698 service
= vnfr
['vnf_juju_name']
700 # This VNF is not managed by Juju
706 # Get the status of the application
707 resp
= yield from self
.api
.get_application_status(service
)
709 # No status means the application is still pending deployment
718 self
._log
.error("jujuCA: Check unknown service %s status", service
)
719 except Exception as e
:
720 self
._log
.error("jujuCA: Caught exception when checking for service is active: %s", e
)
721 self
._log
.exception(e
)
725 def get_action_status(self
, execution_id
):
726 ''' Get the action status for an execution ID
727 *** Make sure this is NOT a asyncio coroutine function ***
731 self
._log
.debug("jujuCA: Get action status for {}".format(execution_id
))
732 resp
= self
.api
._get
_action
_status
(execution_id
)
733 self
._log
.debug("jujuCA: Action status: {}".format(resp
))
735 except Exception as e
:
736 self
._log
.error("jujuCA: Error fetching execution status for %s",
738 self
._log
.exception(e
)
741 def get_service_status(self
, vnfr_id
):
742 '''Get the service status, used by job status handle
743 Make sure this is NOT a coroutine
745 service
= self
.get_service_name(vnfr_id
)
747 self
._log
.error("jujuCA: VNFR {} not managed by this Juju agent".
751 # Delay for 3 seconds before checking as config apply takes a
752 # few seconds to transfer to the service
754 return self
.api
._get
_service
_status
(service
=service
)