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 # Charm service name accepts only a to z and -.
29 def get_vnf_unique_name(nsr_name
, vnfr_short_name
, member_vnf_index
):
30 name
= "{}-{}-{}".format(nsr_name
, vnfr_short_name
, member_vnf_index
)
38 return new_name
.lower()
41 class JujuConfigPlugin(riftcm_config_plugin
.RiftCMConfigPluginBase
):
43 Juju implementation of the riftcm_config_plugin.RiftCMConfigPluginBase
45 def __init__(self
, dts
, log
, loop
, account
):
46 riftcm_config_plugin
.RiftCMConfigPluginBase
.__init
__(self
, dts
, log
, loop
, account
)
47 self
._name
= account
.name
49 self
._ip
_address
= account
.juju
.ip_address
50 self
._port
= account
.juju
.port
51 self
._user
= account
.juju
.user
52 self
._secret
= account
.juju
.secret
53 self
._rift
_install
_dir
= os
.environ
['RIFT_INSTALL']
54 self
._rift
_artif
_dir
= os
.environ
['RIFT_ARTIFACTS']
56 ############################################################
57 # This is wrongfully overloaded with 'juju' private data. #
58 # Really need to separate agent_vnfr from juju vnfr data. #
59 # Currently, this holds agent_vnfr, which has actual vnfr, #
60 # then this juju overloads actual vnfr with its own #
61 # dictionary elemetns (WRONG!!!) #
63 ############################################################
66 self
._api
= juju
.JujuApi(log
, loop
,
67 self
._ip
_address
, self
._port
,
68 self
._user
, self
._secret
)
87 host
=self
._ip
_address
,
93 def vnfr(self
, vnfr_id
):
95 vnfr
= self
._juju
_vnfs
[vnfr_id
].vnfr
97 self
._log
.error("jujuCA: Did not find VNFR %s in juju plugin", vnfr_id
)
102 def get_service_name(self
, vnfr_id
):
103 vnfr
= self
.vnfr(vnfr_id
)
104 if vnfr
and 'vnf_juju_name' in vnfr
:
105 return vnfr
['vnf_juju_name']
108 def juju_log(self
, level
, name
, log_str
, *args
):
110 g_log_str
= 'jujuCA:({}) {}'.format(name
, log_str
)
112 g_log_str
= 'jujuCA: {}'.format(log_str
)
113 getattr(self
._log
, level
)(g_log_str
, *args
)
115 # TBD: Do a better, similar to config manager
116 def xlate(self
, tag
, tags
):
121 if re
.search('<.*>', tag
):
122 self
._log
.debug("jujuCA: Xlate value %s", tag
)
124 if tag
== '<rw_mgmt_ip>':
125 val
= tags
['rw_mgmt_ip']
126 except KeyError as e
:
127 self
._log
.info("jujuCA: Did not get a value for tag %s, e=%s",
132 def notify_create_vlr(self
, agent_nsr
, agent_vnfr
, vld
, vlr
):
134 Notification of create VL record
139 def notify_create_vnfr(self
, agent_nsr
, agent_vnfr
):
141 Notification of create Network VNF record
142 Returns True if configured using config_agent
144 # Deploy the charm if specified for the vnf
145 self
._log
.debug("jujuCA: create vnfr nsr=%s vnfr=%s",
146 agent_nsr
.name
, agent_vnfr
.name
)
147 self
._log
.debug("jujuCA: Config = %s",
148 agent_vnfr
.vnf_configuration
)
150 vnf_config
= agent_vnfr
.vnfr_msg
.vnf_configuration
151 self
._log
.debug("jujuCA: vnf_configuration = %s", vnf_config
)
152 if not vnf_config
.has_field('juju'):
154 charm
= vnf_config
.juju
.charm
155 self
._log
.debug("jujuCA: charm = %s", charm
)
156 except Exception as e
:
157 self
._log
.Error("jujuCA: vnf_configuration error for vnfr {}: {}".
158 format(agent_vnfr
.name
, e
))
161 # Prepare unique name for this VNF
162 vnf_unique_name
= get_vnf_unique_name(agent_nsr
.name
,
164 agent_vnfr
.member_vnf_index
)
165 if vnf_unique_name
in self
._tasks
:
166 self
._log
.warn("jujuCA: Service %s already deployed",
169 vnfr_dict
= agent_vnfr
.vnfr
170 vnfr_dict
.update({'vnf_juju_name': vnf_unique_name
,
172 'nsr_id': agent_nsr
.id,
173 'member_vnf_index': agent_vnfr
.member_vnf_index
,
176 'config': vnf_config
,
177 'vnfr_name' : agent_vnfr
.name
})
178 self
._log
.debug("jujuCA: Charm %s for vnf %s to be deployed as %s",
179 charm
, agent_vnfr
.name
, vnf_unique_name
)
181 # Find the charm directory
183 path
= os
.path
.join(self
._rift
_artif
_dir
,
185 agent_vnfr
.vnfr_msg
.vnfd
.id,
188 self
._log
.debug("jujuCA: Charm dir is {}".format(path
))
189 if not os
.path
.isdir(path
):
190 self
._log
.error("jujuCA: Did not find the charm directory at {}".
193 except Exception as e
:
194 self
.log
.exception(e
)
197 if vnf_unique_name
not in self
._tasks
:
198 self
._tasks
[vnf_unique_name
] = {}
200 self
._tasks
[vnf_unique_name
]['deploy'] = self
.loop
.create_task(
201 self
.api
.deploy_service(charm
, vnf_unique_name
, path
=path
))
203 self
._log
.debug("jujuCA: Deploying service %s",
209 def notify_instantiate_vnfr(self
, agent_nsr
, agent_vnfr
):
211 Notification of Instantiate NSR with the passed nsr id
216 def notify_instantiate_vlr(self
, agent_nsr
, agent_vnfr
, vlr
):
218 Notification of Instantiate NSR with the passed nsr id
223 def notify_terminate_nsr(self
, agent_nsr
, agent_vnfr
):
225 Notification of Terminate the network service
230 def notify_terminate_vnfr(self
, agent_nsr
, agent_vnfr
):
232 Notification of Terminate the network service
234 self
._log
.debug("jujuCA: Terminate VNFr {}, current vnfrs={}".
235 format(agent_vnfr
.name
, self
._juju
_vnfs
))
237 vnfr
= agent_vnfr
.vnfr
238 service
= vnfr
['vnf_juju_name']
240 self
._log
.debug ("jujuCA: Terminating VNFr %s, %s",
241 agent_vnfr
.name
, service
)
242 self
._tasks
[service
]['destroy'] = self
.loop
.create_task(
243 self
.api
.destroy_service(service
)
246 del self
._juju
_vnfs
[agent_vnfr
.id]
247 self
._log
.debug ("jujuCA: current vnfrs={}".
248 format(self
._juju
_vnfs
))
249 if service
in self
._tasks
:
251 for action
in self
._tasks
[service
].keys():
252 #if self.check_task_status(service, action):
255 except KeyError as e
:
256 self
._log
.debug ("jujuCA: Termiating charm service for VNFr {}, e={}".
257 format(agent_vnfr
.name
, e
))
258 except Exception as e
:
259 self
._log
.error("jujuCA: Exception terminating charm service for VNFR {}: {}".
260 format(agent_vnfr
.name
, e
))
265 def notify_terminate_vlr(self
, agent_nsr
, agent_vnfr
, vlr
):
267 Notification of Terminate the virtual link
271 def check_task_status(self
, service
, action
):
272 #self.log.debug("jujuCA: check task status for %s, %s" % (service, action))
274 task
= self
._tasks
[service
][action
]
276 self
.log
.debug("jujuCA: Task for %s, %s done" % (service
, action
))
279 self
.log
.error("jujuCA: Error in task for {} and {} : {}".
280 format(service
, action
, e
))
284 self
.log
.debug("jujuCA: Task for {} and {}, returned {}".
285 format(service
, action
,r
))
288 self
.log
.debug("jujuCA: task {}, {} not done".
289 format(service
, action
))
291 except KeyError as e
:
292 self
.log
.error("jujuCA: KeyError for task for {} and {}: {}".
293 format(service
, action
, e
))
294 except Exception as e
:
295 self
.log
.error("jujuCA: Error for task for {} and {}: {}".
296 format(service
, action
, e
))
301 def vnf_config_primitive(self
, nsr_id
, vnfr_id
, primitive
, output
):
302 self
._log
.debug("jujuCA: VNF config primititve {} for nsr {}, vnfr_id {}".
303 format(primitive
, nsr_id
, vnfr_id
))
305 vnfr
= self
._juju
_vnfs
[vnfr_id
].vnfr
307 self
._log
.error("jujuCA: Did not find VNFR %s in juju plugin",
311 output
.execution_status
= "failed"
312 output
.execution_id
= ''
313 output
.execution_error_details
= ''
316 service
= vnfr
['vnf_juju_name']
317 vnf_config
= vnfr
['config']
318 self
._log
.debug("VNF config %s", vnf_config
)
319 configs
= vnf_config
.service_primitive
320 for config
in configs
:
321 if config
.name
== primitive
.name
:
322 self
._log
.debug("jujuCA: Found the config primitive %s",
325 for parameter
in primitive
.parameter
:
327 val
= self
.xlate(parameter
.value
, vnfr
['tags'])
328 # TBD do validation of the parameters
331 for ca_param
in config
.parameter
:
332 if ca_param
.name
== parameter
.name
:
333 data_type
= ca_param
.data_type
336 if data_type
== 'integer':
337 val
= int(parameter
.value
)
339 self
._log
.warn("jujuCA: Did not find parameter {} for {}".
340 format(parameter
, config
.name
))
341 params
.update({parameter
.name
: val
})
343 if config
.name
== 'config':
344 output
.execution_id
= 'config'
346 self
._log
.debug("jujuCA: applying config with params {} for service {}".
347 format(params
, service
))
349 rc
= yield from self
.api
.apply_config(params
, service
=service
, wait
=False)
352 # Mark as pending and check later for the status
353 output
.execution_status
= "pending"
354 self
._log
.debug("jujuCA: applied config {} on {}".
355 format(params
, service
))
357 output
.execution_status
= 'failed'
358 output
.execution_error_details
= \
359 'Failed to apply config: {}'.format(params
)
360 self
._log
.error("jujuCA: Error applying config {} on service {}".
361 format(params
, service
))
363 self
._log
.warn("jujuCA: Did not find valid parameters for config : {}".
364 format(primitive
.parameter
))
365 output
.execution_status
= "completed"
367 self
._log
.debug("jujuCA: Execute action {} on service {} with params {}".
368 format(config
.name
, service
, params
))
370 resp
= yield from self
.api
.execute_action(config
.name
,
376 output
.execution_error_details
= resp
['error']['Message']
378 output
.execution_id
= resp
['action']['tag']
379 output
.execution_status
= resp
['status']
380 if output
.execution_status
== 'failed':
381 output
.execution_error_details
= resp
['message']
382 self
._log
.debug("jujuCA: execute action {} on service {} returned {}".
383 format(config
.name
, service
, output
.execution_status
))
385 self
._log
.error("jujuCA: error executing action {} for {} with {}".
386 format(config
.name
, service
, params
))
387 output
.execution_id
= ''
388 output
.execution_status
= 'failed'
389 output
.execution_error_details
= "Failed to queue the action"
392 except KeyError as e
:
393 self
._log
.info("VNF %s does not have config primititves, e=%s", vnfr_id
, e
)
396 def apply_config(self
, agent_nsr
, agent_vnfr
, config
, rpc_ip
):
397 """ Notification on configuration of an NSR """
401 def apply_ns_config(self
, agent_nsr
, agent_vnfrs
, rpc_ip
):
404 ###### TBD - This really does not belong here. Looks more like NS level script ####
405 ###### apply_config should be called for a particular VNF only here ###############
407 Hook: Runs the user defined script. Feeds all the necessary data
408 for the script thro' yaml file.
411 rpc_ip (YangInput_Nsr_ExecNsConfigPrimitive): The input data.
412 nsr (NetworkServiceRecord): Description
413 vnfrs (dict): VNFR ID => VirtualNetworkFunctionRecord
416 def get_meta(agent_nsr
):
417 unit_names
, initial_params
, vnfr_index_map
= {}, {}, {}
419 for vnfr_id
in agent_nsr
.vnfr_ids
:
420 juju_vnf
= self
._juju
_vnfs
[vnfr_id
].vnfr
423 vnfr_index_map
[vnfr_id
] = juju_vnf
['member_vnf_index']
426 unit_names
[vnfr_id
] = juju_vnf
['vnf_juju_name']
428 # Flatten the data for simplicity
430 self
._log
.debug("Juju Config:%s", juju_vnf
['config'])
431 for primitive
in juju_vnf
['config'].initial_config_primitive
:
432 for parameter
in primitive
.parameter
:
433 value
= self
.xlate(parameter
.value
, juju_vnf
['tags'])
434 param_data
[parameter
.name
] = value
436 initial_params
[vnfr_id
] = param_data
439 return unit_names
, initial_params
, vnfr_index_map
441 unit_names
, init_data
, vnfr_index_map
= get_meta(agent_nsr
)
443 # The data consists of 4 sections
445 # 2. The input passed.
446 # 3. Juju unit names (keyed by vnfr ID).
447 # 4. Initial config data (keyed by vnfr ID).
449 data
['config_agent'] = dict(
451 host
=self
._ip
_address
,
456 data
["rpc_ip"] = rpc_ip
.as_dict()
457 data
["unit_names"] = unit_names
458 data
["init_config"] = init_data
459 data
["vnfr_index_map"] = vnfr_index_map
462 with tempfile
.NamedTemporaryFile(delete
=False) as tmp_file
:
463 tmp_file
.write(yaml
.dump(data
, default_flow_style
=True)
466 self
._log
.debug("jujuCA: Creating a temp file: {} with input data".format(
469 # Get the full path to the script
471 if rpc_ip
.user_defined_script
[0] == '/':
472 # The script has full path, use as is
473 script
= rpc_ip
.user_defined_script
475 script
= os
.path
.join(self
._rift
_artif
_dir
, 'launchpad/libs', agent_nsr
.id, 'scripts',
476 rpc_ip
.user_defined_script
)
477 self
.log
.debug("jujuCA: Checking for script in %s", script
)
478 if not os
.path
.exists(script
):
479 script
= os
.path
.join(self
._rift
_install
_dir
, 'usr/bin', rpc_ip
.user_defined_script
)
481 cmd
= "{} {}".format(rpc_ip
.user_defined_script
, tmp_file
.name
)
482 self
._log
.debug("jujuCA: Running the CMD: {}".format(cmd
))
484 coro
= asyncio
.create_subprocess_shell(cmd
, loop
=self
._loop
,
485 stderr
=asyncio
.subprocess
.PIPE
)
486 process
= yield from coro
487 err
= yield from process
.stderr
.read()
488 task
= self
._loop
.create_task(process
.wait())
493 def apply_initial_config(self
, agent_nsr
, agent_vnfr
):
495 Apply the initial configuration
496 Expect config directives mostly, not actions
497 Actions in initial config may not work based on charm design
500 vnfr
= agent_vnfr
.vnfr
501 service
= vnfr
['vnf_juju_name']
503 rc
= yield from self
.api
.is_service_up(service
=service
)
509 vnf_cat
= agent_vnfr
.vnfr_msg
510 if vnf_cat
and vnf_cat
.mgmt_interface
.ip_address
:
511 vnfr
['tags'].update({'rw_mgmt_ip': vnf_cat
.mgmt_interface
.ip_address
})
512 self
._log
.debug("jujuCA:(%s) tags: %s", vnfr
['vnf_juju_name'], vnfr
['tags'])
516 for primitive
in vnfr
['config'].initial_config_primitive
:
517 self
._log
.debug("jujuCA:(%s) Initial config primitive %s", vnfr
['vnf_juju_name'], primitive
)
518 if primitive
.name
== 'config':
519 for param
in primitive
.parameter
:
521 val
= self
.xlate(param
.value
, vnfr
['tags'])
522 config
.update({param
.name
: val
})
523 except KeyError as e
:
524 self
._log
.exception("jujuCA:(%s) Initial config error(%s): config=%s",
525 vnfr
['vnf_juju_name'], str(e
), config
)
530 self
.juju_log('info', vnfr
['vnf_juju_name'],
531 "Applying Initial config:%s",
534 rc
= yield from self
.api
.apply_config(config
, service
=service
)
536 self
.log
.error("Service {} is in error state".format(service
))
540 # Apply any actions specified as part of initial config
541 for primitive
in vnfr
['config'].initial_config_primitive
:
542 if primitive
.name
!= 'config':
543 self
._log
.debug("jujuCA:(%s) Initial config action primitive %s",
544 vnfr
['vnf_juju_name'], primitive
)
545 action
= primitive
.name
547 for param
in primitive
.parameter
:
548 val
= self
.xlate(param
.value
, vnfr
['tags'])
549 params
.update({param
.name
: val
})
551 self
._log
.info("jujuCA:(%s) Action %s with params %s",
552 vnfr
['vnf_juju_name'], action
, params
)
554 resp
= yield from self
.api
.execute_action(action
, params
,
557 self
._log
.error("Applying initial config on {} failed for {} with {}: {}".
558 format(vnfr
['vnf_juju_name'], action
, params
, resp
))
561 action_ids
.append(resp
['action']['tag'])
563 except KeyError as e
:
564 self
._log
.info("Juju config agent(%s): VNFR %s not managed by Juju",
565 vnfr
['vnf_juju_name'], agent_vnfr
.id)
567 except Exception as e
:
568 self
._log
.exception("jujuCA:(%s) Exception juju apply_initial_config for VNFR {}: {}".
569 format(vnfr
['vnf_juju_name'], agent_vnfr
.id, e
))
572 # Check if all actions completed
576 for act
in action_ids
:
577 resp
= yield from self
.api
.get_action_status(act
)
579 self
._log
.error("Initial config failed: {}".format(resp
))
582 if resp
['status'] == 'failed':
583 self
._log
.error("Initial config action failed: {}".format(resp
))
586 if resp
['status'] == 'pending':
591 def add_vnfr_managed(self
, agent_vnfr
):
592 if agent_vnfr
.id not in self
._juju
_vnfs
.keys():
593 self
._log
.info("juju config agent: add vnfr={}/{}".
594 format(agent_vnfr
.name
, agent_vnfr
.id))
595 self
._juju
_vnfs
[agent_vnfr
.id] = agent_vnfr
597 def is_vnfr_managed(self
, vnfr_id
):
599 if vnfr_id
in self
._juju
_vnfs
:
601 except Exception as e
:
602 self
._log
.debug("jujuCA: Is VNFR {} managed: {}".
607 def is_configured(self
, vnfr_id
):
609 agent_vnfr
= self
._juju
_vnfs
[vnfr_id
]
610 vnfr
= agent_vnfr
.vnfr
614 vnfr
= self
._juju
_vnfs
[vnfr_id
].vnfr
615 service
= vnfr
['vnf_juju_name']
616 resp
= self
.api
.is_service_active(service
=service
)
617 self
._juju
_vnfs
[vnfr_id
]['active'] = resp
618 self
._log
.debug("jujuCA: Service state for {} is {}".
619 format(service
, resp
))
623 self
._log
.debug("jujuCA: VNFR id {} not found in config agent".
626 except Exception as e
:
627 self
._log
.error("jujuCA: VNFR id {} is_configured: {}".
632 def get_config_status(self
, agent_nsr
, agent_vnfr
):
633 """Get the configuration status for the VNF"""
637 vnfr
= agent_vnfr
.vnfr
638 service
= vnfr
['vnf_juju_name']
640 # This VNF is not managed by Juju
645 if not self
.check_task_status(service
, 'deploy'):
649 resp
= yield from self
.api
.get_service_status(service
=service
)
650 self
._log
.debug("jujuCA: Get service %s status? %s", service
, resp
)
657 self
._log
.error("jujuCA: Check unknown service %s status", service
)
658 except Exception as e
:
659 self
._log
.error("jujuCA: Caught exception when checking for service is active: %s", e
)
660 self
._log
.exception(e
)
664 def get_action_status(self
, execution_id
):
665 ''' Get the action status for an execution ID
666 *** Make sure this is NOT a asyncio coroutine function ***
670 self
._log
.debug("jujuCA: Get action status for {}".format(execution_id
))
671 resp
= self
.api
._get
_action
_status
(execution_id
)
672 self
._log
.debug("jujuCA: Action status: {}".format(resp
))
674 except Exception as e
:
675 self
._log
.error("jujuCA: Error fetching execution status for %s",
677 self
._log
.exception(e
)
680 def get_service_status(self
, vnfr_id
):
681 '''Get the service status, used by job status handle
682 Make sure this is NOT a coroutine
684 service
= self
.get_service_name(vnfr_id
)
686 self
._log
.error("jujuCA: VNFR {} not managed by this Juju agent".
690 # Delay for 3 seconds before checking as config apply takes a
691 # few seconds to transfer to the service
693 return self
.api
._get
_service
_status
(service
=service
)