Add a Sample Ansible Driver
Added a sample Ansible Driver as an option for users who want to use ansible for configuration of VNFs. In this Sample Ansible Driver, the following key LCMs are supported: - instantiate_end - scale_start - scale_end - heal_end - terminate_start Implements: blueprint add-ansible-mgmt-driver-sample Spec: https://review.opendev.org/c/openstack/tacker-specs/+/814689 Change-Id: I539f1ab5442196865155f12fe0b2b4106feedeae
This commit is contained in:
parent
b89ad00b58
commit
611b8b1043
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Add a Sample Ansible Driver as an option for users who want to use ansible
|
||||||
|
for configuration of VNFs. This Ansible Driver supports the key LCMs such as
|
||||||
|
instantiate_end, scale_start, scale_end, heal_end and terminate_start.
|
||||||
|
A Sample VNF package which contains sample usage of Ansible Driver is
|
||||||
|
provided. User manual is also provided to explain the steps in preparing the
|
||||||
|
environment to use Ansible Driver.
|
||||||
|
issues:
|
||||||
|
- |
|
||||||
|
Regarding Sample Ansible Driver, currently, deployment flavors share only
|
||||||
|
one config.yaml due to a limitation in Management Driver.
|
0
samples/mgmt_driver/ansible/__init__.py
Normal file
0
samples/mgmt_driver/ansible/__init__.py
Normal file
144
samples/mgmt_driver/ansible/ansible.py
Normal file
144
samples/mgmt_driver/ansible/ansible.py
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import encodeutils
|
||||||
|
from tacker.common import log
|
||||||
|
from tacker.vnfm.mgmt_drivers import constants as mgmt_constants
|
||||||
|
from tacker.vnfm.mgmt_drivers import vnflcm_abstract_driver
|
||||||
|
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible import ansible_driver
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def get_class():
|
||||||
|
'''Returns the class name of the action driver'''
|
||||||
|
return 'DeviceMgmtAnsible'
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceMgmtAnsible(vnflcm_abstract_driver.VnflcmMgmtAbstractDriver):
|
||||||
|
def get_type(self):
|
||||||
|
return "ansible_driver"
|
||||||
|
|
||||||
|
def get_name(self):
|
||||||
|
return "ansible_driver"
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return "Tacker VNFMgmt Ansible Driver"
|
||||||
|
|
||||||
|
def instantiate_start(self, context, vnf_instance,
|
||||||
|
instantiate_vnf_request, grant,
|
||||||
|
grant_request, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@log.log
|
||||||
|
def instantiate_end(self, context, vnf_instance,
|
||||||
|
instantiate_vnf_request, grant,
|
||||||
|
grant_request, **kwargs):
|
||||||
|
try:
|
||||||
|
LOG.debug("Start of Ansible Driver")
|
||||||
|
driver = ansible_driver.AnsibleDriver()
|
||||||
|
driver._driver_process_flow(context, vnf_instance,
|
||||||
|
mgmt_constants.ACTION_INSTANTIATE_VNF,
|
||||||
|
instantiate_vnf_request, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception("Ansible Driver Error: %s",
|
||||||
|
encodeutils.exception_to_unicode(e))
|
||||||
|
|
||||||
|
@log.log
|
||||||
|
def terminate_start(self, context, vnf_instance,
|
||||||
|
terminate_vnf_request, grant,
|
||||||
|
grant_request, **kwargs):
|
||||||
|
try:
|
||||||
|
LOG.debug("Start of Ansible Driver")
|
||||||
|
driver = ansible_driver.AnsibleDriver()
|
||||||
|
driver._driver_process_flow(context, vnf_instance,
|
||||||
|
mgmt_constants.ACTION_TERMINATE_VNF,
|
||||||
|
terminate_vnf_request, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception("Ansible Driver Error: %s",
|
||||||
|
encodeutils.exception_to_unicode(e))
|
||||||
|
|
||||||
|
def terminate_end(self, context, vnf_instance,
|
||||||
|
terminate_vnf_request, grant,
|
||||||
|
grant_request, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@log.log
|
||||||
|
def scale_start(self, context, vnf_instance,
|
||||||
|
scale_vnf_request, grant,
|
||||||
|
grant_request, **kwargs):
|
||||||
|
try:
|
||||||
|
LOG.debug("Start of Ansible Driver")
|
||||||
|
driver = ansible_driver.AnsibleDriver()
|
||||||
|
driver._driver_process_flow(context, vnf_instance,
|
||||||
|
mgmt_constants.ACTION_SCALE_IN_VNF,
|
||||||
|
scale_vnf_request, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception("Ansible Driver Error: %s",
|
||||||
|
encodeutils.exception_to_unicode(e))
|
||||||
|
|
||||||
|
@log.log
|
||||||
|
def scale_end(self, context, vnf_instance,
|
||||||
|
scale_vnf_request, grant,
|
||||||
|
grant_request, **kwargs):
|
||||||
|
try:
|
||||||
|
LOG.debug("Start of Ansible Driver")
|
||||||
|
driver = ansible_driver.AnsibleDriver()
|
||||||
|
driver._driver_process_flow(context, vnf_instance,
|
||||||
|
mgmt_constants.ACTION_SCALE_OUT_VNF,
|
||||||
|
scale_vnf_request, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception("Ansible Driver Error: %s",
|
||||||
|
encodeutils.exception_to_unicode(e))
|
||||||
|
|
||||||
|
def heal_start(self, context, vnf_instance,
|
||||||
|
heal_vnf_request, grant,
|
||||||
|
grant_request, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@log.log
|
||||||
|
def heal_end(self, context, vnf_instance,
|
||||||
|
heal_vnf_request, grant,
|
||||||
|
grant_request, **kwargs):
|
||||||
|
try:
|
||||||
|
LOG.debug("Start of Ansible Driver")
|
||||||
|
driver = ansible_driver.AnsibleDriver()
|
||||||
|
driver._driver_process_flow(context, vnf_instance,
|
||||||
|
mgmt_constants.ACTION_HEAL_VNF,
|
||||||
|
heal_vnf_request, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception("Ansible Driver Error: %s",
|
||||||
|
encodeutils.exception_to_unicode(e))
|
||||||
|
|
||||||
|
def change_external_connectivity_start(
|
||||||
|
self, context, vnf_instance,
|
||||||
|
change_ext_conn_request, grant,
|
||||||
|
grant_request, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def change_external_connectivity_end(
|
||||||
|
self, context, vnf_instance,
|
||||||
|
change_ext_conn_request, grant,
|
||||||
|
grant_request, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def modify_information_start(
|
||||||
|
self, context, vnf_instance,
|
||||||
|
modify_vnf_request, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def modify_information_end(
|
||||||
|
self, context, vnf_instance,
|
||||||
|
modify_vnf_request, **kwargs):
|
||||||
|
pass
|
468
samples/mgmt_driver/ansible/ansible_config_parser.py
Normal file
468
samples/mgmt_driver/ansible/ansible_config_parser.py
Normal file
@ -0,0 +1,468 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import datetime
|
||||||
|
import re
|
||||||
|
import six
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from heatclient import client as hclient
|
||||||
|
from keystoneauth1 import loading
|
||||||
|
from keystoneauth1 import session
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible import event_handler
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible import exceptions
|
||||||
|
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible.ansible_config_parser_cfg\
|
||||||
|
import CONFIG_PARSER_MAP
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
EVENT_HANDLER = event_handler.AnsibleEventHandler()
|
||||||
|
|
||||||
|
RE_PATTERN = r"(_VAR_[a-zA-Z_.0-9-<>]+)"
|
||||||
|
STND_MSG = "Ansible Config Parser: {}"
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigParser():
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._vim_id = ""
|
||||||
|
self._auth_url = ""
|
||||||
|
self._username = ""
|
||||||
|
self._password = ""
|
||||||
|
self._project_name = ""
|
||||||
|
self._project_domain_name = ""
|
||||||
|
self._user_domain_name = ""
|
||||||
|
self._sess = ""
|
||||||
|
self._handle_map = {}
|
||||||
|
self._vnf = {}
|
||||||
|
self._config_yaml = {}
|
||||||
|
self._current_target_ip = ""
|
||||||
|
self._current_target_vdu = ""
|
||||||
|
self._failed_vdu_name = ""
|
||||||
|
self._context = {}
|
||||||
|
|
||||||
|
def configure(self, context, vnf, plugin, config_yaml, stack_map):
|
||||||
|
LOG.info(STND_MSG.format("Configuring parser"))
|
||||||
|
try:
|
||||||
|
# load context
|
||||||
|
self._context = context
|
||||||
|
|
||||||
|
# load configurable_properties
|
||||||
|
self._config_yaml = config_yaml
|
||||||
|
|
||||||
|
# get vim info
|
||||||
|
access_info = plugin.get_vim(context, vnf)
|
||||||
|
|
||||||
|
# get vim details
|
||||||
|
vim_auth = access_info["vim_auth"]
|
||||||
|
self._auth_url = vim_auth["auth_url"]
|
||||||
|
self._username = vim_auth["username"]
|
||||||
|
self._password = vim_auth["password"]
|
||||||
|
self._project_name = vim_auth["project_name"]
|
||||||
|
self._project_domain_name = vim_auth["project_domain_name"]
|
||||||
|
self._user_domain_name = vim_auth["user_domain_name"]
|
||||||
|
|
||||||
|
# changed password type bytes -> string
|
||||||
|
if isinstance(self._password, bytes):
|
||||||
|
self._password = self._password.decode('utf-8')
|
||||||
|
|
||||||
|
# append failed_vdu_name,failed_vdu_instance_ip to vnf_dict
|
||||||
|
failed_vdu_name = vnf.get('failed_vdu_name', "")
|
||||||
|
|
||||||
|
# make sure vnf['mgmt_ip_address'] is of type string
|
||||||
|
vnf_mgmt_ip_address = vnf['mgmt_ip_address']
|
||||||
|
|
||||||
|
# make sure vnf['mgmt_ip_address'] is of type string
|
||||||
|
if isinstance(vnf_mgmt_ip_address, bytes):
|
||||||
|
vnf['mgmt_ip_address'] = vnf_mgmt_ip_address.decode('utf-8')
|
||||||
|
|
||||||
|
# if vnf has scaling policy,
|
||||||
|
# get failed vdu instance ip from heat stack_map
|
||||||
|
# else the vnf has no scaling policy,
|
||||||
|
# directly get mgmt_ip address from vnf_dict
|
||||||
|
if stack_map:
|
||||||
|
vnf['failed_vdu_instance_ip'] = stack_map.get(failed_vdu_name,
|
||||||
|
[""])[0] if failed_vdu_name else ""
|
||||||
|
else:
|
||||||
|
if vnf_mgmt_ip_address:
|
||||||
|
failed_vdu_mgmt_ip_list = ast.literal_eval(
|
||||||
|
vnf['mgmt_ip_address']).get(failed_vdu_name, "")
|
||||||
|
if isinstance(failed_vdu_mgmt_ip_list, list):
|
||||||
|
vnf['failed_vdu_instance_ip'] = \
|
||||||
|
(failed_vdu_mgmt_ip_list[0]
|
||||||
|
if failed_vdu_name else "")
|
||||||
|
else:
|
||||||
|
vnf['failed_vdu_instance_ip'] = \
|
||||||
|
(failed_vdu_mgmt_ip_list
|
||||||
|
if failed_vdu_name else "")
|
||||||
|
|
||||||
|
# load vnf dict
|
||||||
|
self._vnf = vnf
|
||||||
|
|
||||||
|
LOG.debug("Auth: {} {} {} {} {} {}".format(
|
||||||
|
self._auth_url, self._username, self._password,
|
||||||
|
self._project_name, self._project_domain_name,
|
||||||
|
self._user_domain_name))
|
||||||
|
|
||||||
|
# validate config file
|
||||||
|
config = CONFIG_PARSER_MAP
|
||||||
|
if not config and not type(config):
|
||||||
|
raise exceptions.InternalErrorException(
|
||||||
|
details="Configuration file is not valid.")
|
||||||
|
|
||||||
|
for section, options in config.items():
|
||||||
|
create_function = getattr(self, "_handle_{}".format(section))
|
||||||
|
self._handle_map[section] = ConfigHandle(
|
||||||
|
value_map=options, create_function=create_function)
|
||||||
|
LOG.info(STND_MSG.format("Parser configured"))
|
||||||
|
|
||||||
|
except exceptions.AnsibleDriverException:
|
||||||
|
raise
|
||||||
|
except Exception as ex:
|
||||||
|
raise exceptions.ConfigParserConfigurationError(
|
||||||
|
ex_type=type(ex), details=ex)
|
||||||
|
|
||||||
|
def substitute(self, command, **kwargs):
|
||||||
|
try:
|
||||||
|
int_flag = False
|
||||||
|
list_flag = False
|
||||||
|
dict_flag = False
|
||||||
|
|
||||||
|
if isinstance(command, int):
|
||||||
|
int_flag = True
|
||||||
|
elif isinstance(command, list):
|
||||||
|
list_flag = True
|
||||||
|
elif isinstance(command, dict):
|
||||||
|
dict_flag = True
|
||||||
|
elif not isinstance(command, six.string_types):
|
||||||
|
raise Exception(
|
||||||
|
"Value '{}' of type '{}' is not yet supported "
|
||||||
|
" for parameter substitution."
|
||||||
|
.format(command, type(command)))
|
||||||
|
txt = str(command)
|
||||||
|
target_dict = {}
|
||||||
|
|
||||||
|
# check for variables fetched @ runtime
|
||||||
|
self._current_target_ip = kwargs.get("mgmt_ip_address", "")
|
||||||
|
self._current_target_vdu = kwargs.get("vdu", "")
|
||||||
|
|
||||||
|
# get equivalent value for each _VAR_XXX
|
||||||
|
res = re.findall(RE_PATTERN, txt)
|
||||||
|
|
||||||
|
# remove duplicates and sorted the _VAR_XXX in
|
||||||
|
# descending order based on the string length
|
||||||
|
res = list(dict.fromkeys(res))
|
||||||
|
res.sort(key=len, reverse=True)
|
||||||
|
|
||||||
|
for key in res:
|
||||||
|
target_dict[key] = self._get_value(key)
|
||||||
|
|
||||||
|
# substitute text with fetch value
|
||||||
|
for key, val in target_dict.items():
|
||||||
|
txt = re.sub(key, val, txt)
|
||||||
|
|
||||||
|
if int_flag:
|
||||||
|
txt = int(txt)
|
||||||
|
if list_flag or dict_flag:
|
||||||
|
txt = eval(txt)
|
||||||
|
return txt
|
||||||
|
|
||||||
|
except Exception as ex:
|
||||||
|
raise exceptions.ConfigParserParsingError(
|
||||||
|
cmd=command, ex_type=type(ex), details=ex)
|
||||||
|
|
||||||
|
def _get_value(self, key, **kwargs):
|
||||||
|
val = ""
|
||||||
|
|
||||||
|
# Get Resource Type, and varible name
|
||||||
|
raw_var = key.rsplit("_VAR_", 1)
|
||||||
|
if len(raw_var) != 2:
|
||||||
|
raise Exception("{} is not valid".format(key))
|
||||||
|
|
||||||
|
# handle key with no attributes, default handle to 'default'
|
||||||
|
if raw_var[1].find(".") == -1:
|
||||||
|
handle_name = 'default'
|
||||||
|
var = raw_var[1]
|
||||||
|
|
||||||
|
if var == 'VDU_INSTANCE_IP':
|
||||||
|
return self._get_vdu_instance_ip()
|
||||||
|
|
||||||
|
if var == 'VDU_INSTANCE_NAME':
|
||||||
|
return self._get_vdu_instance_name()
|
||||||
|
|
||||||
|
else:
|
||||||
|
handle_name, var = raw_var[1].split(".", 1)
|
||||||
|
|
||||||
|
# get Handle based on resource type, and get value based on parameter
|
||||||
|
# Return Handle Execution Result
|
||||||
|
try:
|
||||||
|
val = self._handle_map[handle_name].get_value(var)
|
||||||
|
except KeyError:
|
||||||
|
LOG.error("Cannot get value for {}".format(var))
|
||||||
|
raise
|
||||||
|
|
||||||
|
return val
|
||||||
|
|
||||||
|
def _get_vdu_instance_ip(self):
|
||||||
|
if not self._current_target_ip:
|
||||||
|
LOG.error("Cannot get value for {}".format('VDU_INSTANCE_IP'))
|
||||||
|
raise KeyError('VDU_INSTANCE_IP')
|
||||||
|
return self._current_target_ip
|
||||||
|
|
||||||
|
def _get_vdu_instance_name(self):
|
||||||
|
if not self._current_target_vdu:
|
||||||
|
LOG.error("Cannot get value for {}".format('VDU_INSTANCE_NAME'))
|
||||||
|
raise KeyError('VDU_INSTANCE_NAME')
|
||||||
|
return self._current_target_vdu
|
||||||
|
|
||||||
|
# The following defines handle create functions, parameter
|
||||||
|
# -> value_map, return value_dict
|
||||||
|
def _handle_vnf_resource(self, value_map=None):
|
||||||
|
value_dict = {}
|
||||||
|
source = yaml.safe_load(self._vnf['attributes']['heat_template'])
|
||||||
|
|
||||||
|
if not source:
|
||||||
|
raise ValueError("Data source is not valid!")
|
||||||
|
for option, values in value_map.items():
|
||||||
|
value_dict[option] = self._generate_data(source, values)
|
||||||
|
|
||||||
|
return value_dict
|
||||||
|
|
||||||
|
def _handle_vnf(self, value_map=None):
|
||||||
|
source = self._vnf
|
||||||
|
value_dict = {}
|
||||||
|
|
||||||
|
if not source:
|
||||||
|
raise ValueError("Data source is not valid!")
|
||||||
|
# if no specific value map, load everything
|
||||||
|
if not value_map:
|
||||||
|
for option, values in source.items():
|
||||||
|
value_dict[option] = self._generate_data(source, option)
|
||||||
|
return value_dict
|
||||||
|
|
||||||
|
for roption, values in value_map.items():
|
||||||
|
# check if item is optional,'*'
|
||||||
|
if len(roption.split('*', 1)) == 2:
|
||||||
|
option = roption.split('*', 1)[1]
|
||||||
|
required = False
|
||||||
|
else:
|
||||||
|
option = roption
|
||||||
|
required = True
|
||||||
|
|
||||||
|
value_dict[option] = self._generate_data(source, values, required)
|
||||||
|
|
||||||
|
return value_dict
|
||||||
|
|
||||||
|
def _handle_resource(self, value_map=None):
|
||||||
|
# get necessary details to create resource tree
|
||||||
|
value_dict = {}
|
||||||
|
vnf_instance_id = self._vnf['instance_id']
|
||||||
|
loader = loading.get_plugin_loader('password')
|
||||||
|
auth = loader.load_from_options(
|
||||||
|
auth_url=self._auth_url,
|
||||||
|
username=self._username,
|
||||||
|
password=self._password,
|
||||||
|
project_name=self._project_name,
|
||||||
|
user_domain_name=self._user_domain_name,
|
||||||
|
project_domain_name=self._project_domain_name)
|
||||||
|
sess = session.Session(auth=auth)
|
||||||
|
heat = hclient.Client('1', session=sess)
|
||||||
|
|
||||||
|
# if no scaling group defined
|
||||||
|
if not self._vnf['attributes'].get('scaling_group_names'):
|
||||||
|
# get stack id
|
||||||
|
target_stack = next(
|
||||||
|
heat.stacks.list(filters={'id': vnf_instance_id}))
|
||||||
|
if not target_stack:
|
||||||
|
raise ValueError("Internal Error: Target Stack not found!")
|
||||||
|
# create resource tree from resource names
|
||||||
|
resources = yaml.safe_load(
|
||||||
|
self._vnf['attributes']['heat_template'])['resources']
|
||||||
|
if not resources:
|
||||||
|
raise ValueError("Internal Error: Resources not fetched!")
|
||||||
|
for resource in resources.keys():
|
||||||
|
resource_info = \
|
||||||
|
heat.resources.get(target_stack.id, resource).to_dict()
|
||||||
|
value_dict[resource] = resource_info['attributes']
|
||||||
|
|
||||||
|
return value_dict
|
||||||
|
|
||||||
|
def _handle_default(self, value_map=None):
|
||||||
|
value_dict = {}
|
||||||
|
configurable_properties = \
|
||||||
|
self._config_yaml.get("configurable_properties", {})
|
||||||
|
|
||||||
|
if not configurable_properties:
|
||||||
|
return value_dict
|
||||||
|
|
||||||
|
for option_raw, value in configurable_properties.items():
|
||||||
|
# len(re.findall(RE_PATTERN,option_raw)) > 0
|
||||||
|
option = option_raw.rsplit("_VAR_", 1)
|
||||||
|
if len(option) != 2:
|
||||||
|
msg = \
|
||||||
|
"Config file validation error. {} is not valid "\
|
||||||
|
"configurable_properties key".format(option_raw)
|
||||||
|
LOG.error(msg)
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
value_dict[option[1]] = value
|
||||||
|
|
||||||
|
return value_dict
|
||||||
|
|
||||||
|
def _generate_data(self, source, value, required=True):
|
||||||
|
# get filters, "... where key=value"
|
||||||
|
if len(value.rsplit(" where ", 1)) == 2:
|
||||||
|
raw_attributes, raw_filter = value.rsplit(" where ", 1)
|
||||||
|
else:
|
||||||
|
raw_filter = ""
|
||||||
|
raw_attributes = value.strip()
|
||||||
|
|
||||||
|
# get qualifiers, "(key/val) in ..."
|
||||||
|
if len(raw_attributes.split(" in ", 1)) == 2:
|
||||||
|
attribute_option, attributes = raw_attributes.split(" in ", 1)
|
||||||
|
else:
|
||||||
|
# default None, return everything
|
||||||
|
attribute_option = ""
|
||||||
|
attributes = raw_attributes.strip()
|
||||||
|
|
||||||
|
builder = "{}".format('source')
|
||||||
|
|
||||||
|
for attribute in attributes.strip().split('.'):
|
||||||
|
builder = builder + "['{}']".format(attribute)
|
||||||
|
|
||||||
|
# evalute string as python expression
|
||||||
|
try:
|
||||||
|
items = eval(builder)
|
||||||
|
except KeyError:
|
||||||
|
if required:
|
||||||
|
raise KeyError("{} not found".format(builder))
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# create filter funciton
|
||||||
|
def filter_func(target_item):
|
||||||
|
if raw_filter and len(raw_filter.strip().split('=')) == 2:
|
||||||
|
filter_key, filter_val = raw_filter.strip().split('=')
|
||||||
|
if target_item[1][filter_key] == filter_val:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# apply filters
|
||||||
|
if isinstance(items, dict):
|
||||||
|
raw_result = dict(filter(filter_func, items.items()))
|
||||||
|
elif isinstance(items, six.string_types):
|
||||||
|
# check if instance is string dict, else return as string,
|
||||||
|
# since item is a single value
|
||||||
|
try:
|
||||||
|
raw_result = dict(
|
||||||
|
filter(filter_func, ast.literal_eval(items).items()))
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.warning(
|
||||||
|
'Defaulting item to \'{}\' since cannot be evaluated. "\
|
||||||
|
"Error: {}'.format(items, ex))
|
||||||
|
return items
|
||||||
|
elif isinstance(items, (datetime.datetime, type(None))):
|
||||||
|
return items
|
||||||
|
else:
|
||||||
|
raise Exception(
|
||||||
|
"Source items data struct of type {} is not supported."
|
||||||
|
.format(type(items)))
|
||||||
|
# apply qualifiers
|
||||||
|
result = []
|
||||||
|
|
||||||
|
# apply qualifier if present and return result as list
|
||||||
|
for raw_res_key, raw_res_val in raw_result.items():
|
||||||
|
if attribute_option.strip() == 'key':
|
||||||
|
result.append(raw_res_key)
|
||||||
|
elif attribute_option.strip() == 'val':
|
||||||
|
result.append(raw_res_val)
|
||||||
|
|
||||||
|
# else default, return all as dict
|
||||||
|
if not attribute_option.strip():
|
||||||
|
result = {}
|
||||||
|
for raw_res_key, raw_res_val in raw_result.items():
|
||||||
|
result[raw_res_key] = raw_res_val
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigHandle():
|
||||||
|
|
||||||
|
def __init__(self, value_map, create_function=None):
|
||||||
|
"""parameter
|
||||||
|
|
||||||
|
value_map: contains hash map for finding value
|
||||||
|
value_dict: contains the fetch value based on value_map
|
||||||
|
create_function:
|
||||||
|
ld the function to generate value_dict, parameter is value_map
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._value_map = value_map
|
||||||
|
self._value_dict = {}
|
||||||
|
self._create_function = create_function
|
||||||
|
|
||||||
|
# Populate Value Dict
|
||||||
|
if not create_function:
|
||||||
|
raise ValueError("Config Handle: Create function is Null!")
|
||||||
|
self._value_dict = self._create_function(self._value_map)
|
||||||
|
|
||||||
|
LOG.debug("Value Dict: {}".format(self._value_dict))
|
||||||
|
|
||||||
|
def get_value(self, key):
|
||||||
|
# this function returns the string equivalent of the data requested
|
||||||
|
|
||||||
|
# handle exec <custom_function>
|
||||||
|
if key in self._value_dict.keys():
|
||||||
|
if isinstance(self._value_dict[key], str) and\
|
||||||
|
self._value_dict[key].find(" exec ") != -1:
|
||||||
|
custom_func = \
|
||||||
|
self._value_dict[key].rsplit(" exec ", 1)[1].strip()
|
||||||
|
# execute custom function
|
||||||
|
try:
|
||||||
|
res = getattr(self, "_custom_{}".format(custom_func))()
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.exception(ex)
|
||||||
|
raise Exception(
|
||||||
|
"Config Handle: error encountered on "
|
||||||
|
"executing custom function {}".format(custom_func))
|
||||||
|
return res
|
||||||
|
|
||||||
|
# for normal operation
|
||||||
|
res = ""
|
||||||
|
builder = "{}".format('self._value_dict')
|
||||||
|
for attribute in key.strip().split('.'):
|
||||||
|
builder = builder + "['{}']".format(attribute)
|
||||||
|
|
||||||
|
items = eval(builder)
|
||||||
|
|
||||||
|
# if item is a type of dict, tuple, or list, seriliaze values
|
||||||
|
# into space delimeted string
|
||||||
|
# else if string, return as is
|
||||||
|
if isinstance(items, (dict, list, tuple)):
|
||||||
|
for item in items:
|
||||||
|
if not res:
|
||||||
|
res = res + "{}".format(item)
|
||||||
|
else:
|
||||||
|
res = res + ",{}".format(item)
|
||||||
|
elif isinstance(items, six.string_types):
|
||||||
|
res = items
|
||||||
|
else:
|
||||||
|
raise Exception(
|
||||||
|
"Result items of type {} data struct is not supported."
|
||||||
|
.format(type(items)))
|
||||||
|
return res
|
40
samples/mgmt_driver/ansible/ansible_config_parser_cfg.py
Normal file
40
samples/mgmt_driver/ansible/ansible_config_parser_cfg.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
CONFIG_PARSER_MAP = {
|
||||||
|
'vnf': {
|
||||||
|
# Data source -> vnf_dict
|
||||||
|
# '*' Denotes optional parameters
|
||||||
|
# 'Comment all entries to load everything from vnf_dict
|
||||||
|
'mgmt_ip_address': 'mgmt_ip_address',
|
||||||
|
'*failed_vdu_name': 'failed_vdu_name',
|
||||||
|
'*failed_vdu_instance_ip': 'failed_vdu_instance_ip',
|
||||||
|
'*vduname': 'vdu_name'
|
||||||
|
},
|
||||||
|
'vnf_resource': {
|
||||||
|
# Data source -> yaml.safe_load(
|
||||||
|
# vnf_dict['attributes']['heat_template'])
|
||||||
|
'RESOURCE_LIST': 'key in resources',
|
||||||
|
'VDUNAME_LIST': 'key in resources where type=OS::Nova::Server',
|
||||||
|
'CPNAME_LIST': 'key in resources where type=OS::Neutron::Port',
|
||||||
|
'ALARMNAME_LIST': 'key in resources where type=OS::Aodh::EventAlarm',
|
||||||
|
'VBNAME_LIST': 'key in resources where type=OS::Cinder::Volume',
|
||||||
|
'CBNAME_LIST': 'key in resources where '
|
||||||
|
'type=OS::Cinder::VolumeAttachment',
|
||||||
|
},
|
||||||
|
'resource': {},
|
||||||
|
# Data source -> heat.resources.get(
|
||||||
|
# target_stack.id,<RESOURCE NAME>).to_dict()
|
||||||
|
# Loads all resources
|
||||||
|
'default': {}
|
||||||
|
# Default Data source -> config_yaml.get("configurable_properties", {})
|
||||||
|
}
|
290
samples/mgmt_driver/ansible/ansible_driver.py
Normal file
290
samples/mgmt_driver/ansible/ansible_driver.py
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from oslo_serialization import jsonutils
|
||||||
|
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible import ansible_config_parser
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible import config_validator
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible import event_handler
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible import exceptions
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible import heat_client
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible import utils
|
||||||
|
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible.config_actions.\
|
||||||
|
vm_app_config import vm_app_config
|
||||||
|
|
||||||
|
from tacker.vnflcm import utils as vnflcm_utils
|
||||||
|
from tacker.vnfm.mgmt_drivers import constants as mgmt_constants
|
||||||
|
from tacker.vnfm import plugin
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
EVENT_HANDLER = event_handler.AnsibleEventHandler()
|
||||||
|
SUPPORTED_ACTIONS = [
|
||||||
|
mgmt_constants.ACTION_INSTANTIATE_VNF,
|
||||||
|
mgmt_constants.ACTION_TERMINATE_VNF,
|
||||||
|
mgmt_constants.ACTION_HEAL_VNF,
|
||||||
|
mgmt_constants.ACTION_UPDATE_VNF,
|
||||||
|
mgmt_constants.ACTION_SCALE_IN_VNF,
|
||||||
|
mgmt_constants.ACTION_SCALE_OUT_VNF,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class AnsibleDriver(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._config_queue = {}
|
||||||
|
self._has_error = False
|
||||||
|
self._cfg_parser = ansible_config_parser.ConfigParser()
|
||||||
|
self._cfg_validator = config_validator.AnsibleConfigValidator()
|
||||||
|
|
||||||
|
self._config_actions = {}
|
||||||
|
self._config_actions["vm_app_config"] = \
|
||||||
|
vm_app_config.VmAppConfigAction()
|
||||||
|
|
||||||
|
self._vnf = None
|
||||||
|
self._plugin = plugin.VNFMPlugin()
|
||||||
|
self._vnf_instance = None
|
||||||
|
self._context = None
|
||||||
|
|
||||||
|
LOG.debug("Ansible Driver initialized successfully!")
|
||||||
|
|
||||||
|
def get_type(self):
|
||||||
|
"""Mgmt driver for ansible"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_name(self):
|
||||||
|
"""Ansible Mgmt Driver"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _driver_process_flow(self, context, vnf_instance, action,
|
||||||
|
request_obj, **kwargs):
|
||||||
|
# set global, prevent passing for every function call
|
||||||
|
self._vnf = kwargs['vnf']
|
||||||
|
self._vnf_instance = vnf_instance
|
||||||
|
self._context = context
|
||||||
|
|
||||||
|
start_msg = ("Ansible Management Driver invoked for configuration of"
|
||||||
|
"VNF: {}".format(self._vnf.get("name")))
|
||||||
|
|
||||||
|
insta_info = self._vnf_instance.instantiated_vnf_info
|
||||||
|
|
||||||
|
if action == mgmt_constants.ACTION_HEAL_VNF:
|
||||||
|
for vnfc_info in insta_info.vnfc_resource_info:
|
||||||
|
is_exist = [instance_id for instance_id in
|
||||||
|
request_obj.vnfc_instance_id
|
||||||
|
if instance_id == vnfc_info.id]
|
||||||
|
|
||||||
|
if (not len(request_obj.vnfc_instance_id)
|
||||||
|
or len(is_exist)):
|
||||||
|
self._vnf['failed_vdu_name'] = vnfc_info.vdu_id
|
||||||
|
break
|
||||||
|
|
||||||
|
EVENT_HANDLER.create_event(context, self._vnf,
|
||||||
|
utils.get_event_by_action(action,
|
||||||
|
self._vnf.get("failed_vdu_name", None)),
|
||||||
|
start_msg)
|
||||||
|
|
||||||
|
# get the mgmt_url
|
||||||
|
vnf_mgmt_ip_address = self._vnf.get("mgmt_ip_address", None)
|
||||||
|
if vnf_mgmt_ip_address is not None:
|
||||||
|
mgmt_url = jsonutils.loads(vnf_mgmt_ip_address)
|
||||||
|
LOG.debug("mgmt_url %s", mgmt_url)
|
||||||
|
else:
|
||||||
|
LOG.info("Unable to retrieve mgmt_ip_address of VNF")
|
||||||
|
return
|
||||||
|
|
||||||
|
# load the configuration file
|
||||||
|
config_yaml = self._load_ansible_config(request_obj)
|
||||||
|
if not config_yaml:
|
||||||
|
return
|
||||||
|
|
||||||
|
# validate config file
|
||||||
|
self._cfg_validator.validate(config_yaml)
|
||||||
|
|
||||||
|
# filter VDUs
|
||||||
|
if (action != mgmt_constants.ACTION_SCALE_IN_VNF and
|
||||||
|
action != mgmt_constants.ACTION_SCALE_OUT_VNF):
|
||||||
|
config_yaml = self._cfg_validator.filter_vdus(context, self._vnf,
|
||||||
|
utils.get_event_by_action(action,
|
||||||
|
self._vnf.get("failed_vdu_name", None)), mgmt_url,
|
||||||
|
config_yaml)
|
||||||
|
|
||||||
|
# load stack map
|
||||||
|
stack_map = self._get_stack_map(action, **kwargs)
|
||||||
|
|
||||||
|
# configure config parser for vnf parameter passing
|
||||||
|
self._cfg_parser.configure(self._context, self._vnf, self._plugin,
|
||||||
|
config_yaml, stack_map)
|
||||||
|
|
||||||
|
self._sort_config(config_yaml)
|
||||||
|
|
||||||
|
self._process_config(stack_map, config_yaml, mgmt_url, action)
|
||||||
|
|
||||||
|
def _sort_config(self, config_yaml):
|
||||||
|
self._config_queue = {}
|
||||||
|
for vdu, vdu_dict in config_yaml.get("vdus", {}).items():
|
||||||
|
self._add_to_config_queue(vdu, vdu_dict.get("config", {}))
|
||||||
|
|
||||||
|
def _process_config(self, stack_map, config_yaml, mgmt_url, action):
|
||||||
|
for vdu_order in sorted(self._config_queue):
|
||||||
|
config_info_list = self._config_queue[vdu_order]
|
||||||
|
for config_info in config_info_list:
|
||||||
|
vdu = config_info["vdu"]
|
||||||
|
config = config_info["config"]
|
||||||
|
|
||||||
|
for key, conf_value in config.items():
|
||||||
|
if key not in self._config_actions:
|
||||||
|
continue
|
||||||
|
|
||||||
|
LOG.debug("Processing configuration: {}".format(key))
|
||||||
|
self._config_actions[key].execute(
|
||||||
|
vdu=vdu,
|
||||||
|
vnf=self._vnf,
|
||||||
|
context=self._context,
|
||||||
|
conf_value=conf_value,
|
||||||
|
mgmt_url=mgmt_url,
|
||||||
|
cfg_parser=self._cfg_parser,
|
||||||
|
stack_map=stack_map,
|
||||||
|
config_yaml=config_yaml,
|
||||||
|
action=action,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _add_to_config_queue(self, vdu, config):
|
||||||
|
if "order" not in config:
|
||||||
|
raise exceptions.MandatoryKeyNotDefinedError(vdu=vdu, key="order")
|
||||||
|
|
||||||
|
try:
|
||||||
|
order = int(config["order"])
|
||||||
|
except ValueError:
|
||||||
|
raise exceptions.InvalidValueError(vdu=vdu, key="order")
|
||||||
|
|
||||||
|
config_info = {
|
||||||
|
"vdu": vdu,
|
||||||
|
"config": config
|
||||||
|
}
|
||||||
|
|
||||||
|
if order in self._config_queue:
|
||||||
|
self._config_queue[order].append(config_info)
|
||||||
|
else:
|
||||||
|
entity_list = []
|
||||||
|
entity_list.append(config_info)
|
||||||
|
self._config_queue[order] = entity_list
|
||||||
|
|
||||||
|
def _get_stack_map(self, action, **kwargs):
|
||||||
|
stack_id_map = {}
|
||||||
|
stack_id = None
|
||||||
|
stack_id_list = None
|
||||||
|
scaling_actions = [
|
||||||
|
mgmt_constants.ACTION_SCALE_IN_VNF,
|
||||||
|
mgmt_constants.ACTION_SCALE_OUT_VNF,
|
||||||
|
]
|
||||||
|
|
||||||
|
if action in scaling_actions:
|
||||||
|
stack_id = kwargs["scale_stack_id"]
|
||||||
|
else:
|
||||||
|
stack_id = self._vnf_instance.instantiated_vnf_info.instance_id
|
||||||
|
|
||||||
|
LOG.debug("stack_id: {}".format(stack_id))
|
||||||
|
if stack_id:
|
||||||
|
if not isinstance(stack_id, list):
|
||||||
|
stack_id_list = [stack_id]
|
||||||
|
else:
|
||||||
|
stack_id_list = stack_id
|
||||||
|
|
||||||
|
hc = heat_client.AnsibleHeatClient(self._context, self._plugin,
|
||||||
|
self._vnf)
|
||||||
|
|
||||||
|
for stack_ids in stack_id_list:
|
||||||
|
parent_stack_id = hc.get_parent_stack_id(stack_ids)
|
||||||
|
for resource in hc.get_resource_list(parent_stack_id):
|
||||||
|
if resource.physical_resource_id == stack_ids:
|
||||||
|
attr = hc.get_resource_attributes(
|
||||||
|
parent_stack_id, resource.resource_name)
|
||||||
|
stack_id_map = self._add_to_stack_map(stack_id_map, attr)
|
||||||
|
|
||||||
|
LOG.debug("stack_id_map: {}".format(stack_id_map))
|
||||||
|
return stack_id_map
|
||||||
|
|
||||||
|
def _add_to_stack_map(self, map, attributes):
|
||||||
|
for key, value in attributes.items():
|
||||||
|
if "mgmt_ip-" in key:
|
||||||
|
vdu_name = key.replace("mgmt_ip-", "")
|
||||||
|
if vdu_name in map:
|
||||||
|
map[vdu_name].append(value)
|
||||||
|
else:
|
||||||
|
map[vdu_name] = [value]
|
||||||
|
return map
|
||||||
|
|
||||||
|
def _get_config(self, config_data, config_params, vnf_package_path):
|
||||||
|
configurable_properties = config_data.get('configurable_properties')
|
||||||
|
|
||||||
|
# add vnf_package_path to vnf_configurable properties
|
||||||
|
if not configurable_properties:
|
||||||
|
configurable_properties = {
|
||||||
|
'_VAR_vnf_package_path': vnf_package_path + '/'
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
configurable_properties.update(
|
||||||
|
{'_VAR_vnf_package_path': vnf_package_path + '/'})
|
||||||
|
|
||||||
|
for k, v in config_params.items():
|
||||||
|
var_key = '_VAR_' + k
|
||||||
|
configurable_properties.update({var_key: v})
|
||||||
|
|
||||||
|
config_data.update(
|
||||||
|
{'configurable_properties': configurable_properties})
|
||||||
|
|
||||||
|
LOG.debug('Modified config {}'.format(config_data))
|
||||||
|
|
||||||
|
return yaml.dump(config_data)
|
||||||
|
|
||||||
|
def _load_ansible_config(self, request_obj):
|
||||||
|
# load vnf package path
|
||||||
|
vnf_package_path = vnflcm_utils._get_vnf_package_path(self._context,
|
||||||
|
self._vnf_instance.vnfd_id)
|
||||||
|
script_ansible_path = os.path.join(vnf_package_path,
|
||||||
|
utils.CONFIG_FOLDER)
|
||||||
|
|
||||||
|
script_ansible_config = None
|
||||||
|
|
||||||
|
# load ScriptANSIBLE/config.yaml
|
||||||
|
if os.path.exists(script_ansible_path):
|
||||||
|
for file in os.listdir(script_ansible_path):
|
||||||
|
if file.endswith('yaml') and file.startswith('config'):
|
||||||
|
with open(
|
||||||
|
os.path.join(
|
||||||
|
script_ansible_path, file)) as file_obj:
|
||||||
|
script_ansible_config = yaml.safe_load(file_obj)
|
||||||
|
|
||||||
|
if script_ansible_config is None:
|
||||||
|
LOG.error("not defined ansible script config")
|
||||||
|
|
||||||
|
config_params = {}
|
||||||
|
|
||||||
|
if hasattr(self._vnf_instance.instantiated_vnf_info,
|
||||||
|
'additional_params'):
|
||||||
|
config_params = self._vnf_instance.instantiated_vnf_info.\
|
||||||
|
additional_params
|
||||||
|
elif hasattr(request_obj, 'additional_params'):
|
||||||
|
config_params = request_obj.additional_params
|
||||||
|
|
||||||
|
self._vnf['attributes']['config'] = self._get_config(
|
||||||
|
script_ansible_config, config_params, vnf_package_path)
|
||||||
|
|
||||||
|
return script_ansible_config
|
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
class AbstractConfigAction(object):
|
||||||
|
def execute(self, **kwargs):
|
||||||
|
raise NotImplementedError
|
@ -0,0 +1,160 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from six import iteritems
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible import event_handler
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible import exceptions
|
||||||
|
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible.config_actions.\
|
||||||
|
vm_app_config import executor
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
EVENT_HANDLER = event_handler.AnsibleEventHandler()
|
||||||
|
|
||||||
|
|
||||||
|
class AnsiblePlaybookExecutor(executor.Executor):
|
||||||
|
def _get_default_execute_host(self, conf_value=None):
|
||||||
|
user = self._conf_opts["user"]
|
||||||
|
password = self._conf_opts["password"]
|
||||||
|
host = self._conf_opts["host"]
|
||||||
|
host_private_key_file = self._conf_opts["private_key_file"]
|
||||||
|
return user, password, host, host_private_key_file
|
||||||
|
|
||||||
|
def _get_target_host(self, conf_value):
|
||||||
|
target_user = conf_value.get("username", "")
|
||||||
|
target_password = conf_value.get("password", "")
|
||||||
|
target_private_key_file = conf_value.get("priv_key_file", "")
|
||||||
|
target_host = {
|
||||||
|
"target_user": target_user,
|
||||||
|
"target_password": target_password,
|
||||||
|
"target_private_key_file": target_private_key_file
|
||||||
|
}
|
||||||
|
|
||||||
|
return target_host
|
||||||
|
|
||||||
|
def _get_playbook_target_hosts(self, playbook_cmd):
|
||||||
|
target_hosts = playbook_cmd.get("target_hosts", "")
|
||||||
|
host_ips = ""
|
||||||
|
if not target_hosts:
|
||||||
|
host_ips = ",{}".format(self._mgmt_ip_address)
|
||||||
|
else:
|
||||||
|
for host_name in target_hosts:
|
||||||
|
# process host
|
||||||
|
if host_name in self._mgmt_url:
|
||||||
|
# the given config value "host" is a VDU name
|
||||||
|
host_ip = self._mgmt_url.get(host_name, "")
|
||||||
|
else:
|
||||||
|
# the given config value "host" is an address
|
||||||
|
host_ip = host_name
|
||||||
|
host_ips = host_ips + ",{}".format(host_ip)
|
||||||
|
return host_ips
|
||||||
|
|
||||||
|
def _get_node_pair_ip(self, conf_value, mgmt_url):
|
||||||
|
node_pair_ip = None
|
||||||
|
if "node_pair" in conf_value:
|
||||||
|
node_pair_ip = mgmt_url.get(conf_value["node_pair"], "")
|
||||||
|
|
||||||
|
return node_pair_ip
|
||||||
|
|
||||||
|
def _get_params(self, playbook_cmd):
|
||||||
|
params = ""
|
||||||
|
obj_params = playbook_cmd.get("params", "")
|
||||||
|
if obj_params:
|
||||||
|
for key, value in iteritems(obj_params):
|
||||||
|
if isinstance(value, dict):
|
||||||
|
str_value = json.dumps(
|
||||||
|
value, separators=(',', ':')).replace('"', '\\"')
|
||||||
|
else:
|
||||||
|
str_value = "{}".format(value)
|
||||||
|
|
||||||
|
params += "{}={} ".format(key, str_value)
|
||||||
|
return params
|
||||||
|
|
||||||
|
def _convert_mgmt_url_to_extra_vars(self, mgmt_url):
|
||||||
|
return json.dumps(mgmt_url)
|
||||||
|
|
||||||
|
def _get_playbook_path(self, playbook_cmd):
|
||||||
|
path = playbook_cmd.get("path", "")
|
||||||
|
if not path:
|
||||||
|
raise exceptions.ConfigValidationError(
|
||||||
|
vdu=self._vdu,
|
||||||
|
details="Playbook {} did not specify path".format(playbook_cmd)
|
||||||
|
)
|
||||||
|
return path
|
||||||
|
|
||||||
|
def _get_final_command(self, playbook_cmd):
|
||||||
|
init_cmd = ("cd {} ; ansible-playbook -i {} -vvv {} "
|
||||||
|
"--extra-vars \"host={} node_pair_ip={}".format(
|
||||||
|
os.path.dirname(self._get_playbook_path(playbook_cmd)),
|
||||||
|
self._get_playbook_target_hosts(playbook_cmd),
|
||||||
|
self._get_playbook_path(playbook_cmd),
|
||||||
|
self._mgmt_ip_address,
|
||||||
|
self._node_pair_ip))
|
||||||
|
ssh_args = ("ansible_ssh_extra_args='-o StrictHostKeyChecking=no "
|
||||||
|
"-o UserKnownHostsFile=/dev/null'")
|
||||||
|
|
||||||
|
target_host_param = False
|
||||||
|
ssh_creds = ""
|
||||||
|
|
||||||
|
if self._target_host:
|
||||||
|
if self._target_host["target_user"]:
|
||||||
|
ssh_creds = "ansible_ssh_user={}".format(
|
||||||
|
self._target_host["target_user"]
|
||||||
|
)
|
||||||
|
target_host_param = True
|
||||||
|
|
||||||
|
if self._target_host["target_private_key_file"]:
|
||||||
|
if not target_host_param:
|
||||||
|
ssh_creds = "ansible_ssh_private_key_file={}".format(
|
||||||
|
self._target_host["target_private_key_file"]
|
||||||
|
)
|
||||||
|
target_host_param = True
|
||||||
|
else:
|
||||||
|
ssh_creds = ssh_creds + " ansible_ssh_private_key_"
|
||||||
|
"file={}".format(
|
||||||
|
self._target_host["target_private_key_file"])
|
||||||
|
|
||||||
|
if self._target_host["target_password"]:
|
||||||
|
if not target_host_param:
|
||||||
|
ssh_creds = "ansible_ssh_pass={}".format(
|
||||||
|
self._target_host["target_password"]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ssh_creds = ssh_creds + " ansible_ssh_pass={}".format(
|
||||||
|
self._target_host["target_password"]
|
||||||
|
)
|
||||||
|
|
||||||
|
ssh_creds = ssh_creds + "\""
|
||||||
|
mgmt_url_vars = " --extra-vars '{}'".format(
|
||||||
|
self._convert_mgmt_url_to_extra_vars(self._mgmt_url))
|
||||||
|
|
||||||
|
cmd_raw = "{} {} {} {} {}".format(
|
||||||
|
init_cmd,
|
||||||
|
ssh_args,
|
||||||
|
self._get_params(playbook_cmd),
|
||||||
|
ssh_creds,
|
||||||
|
mgmt_url_vars
|
||||||
|
)
|
||||||
|
|
||||||
|
# substitute passed VNF parameter to its corresponding value
|
||||||
|
inline_param = {
|
||||||
|
'mgmt_ip_address': self._mgmt_ip_address,
|
||||||
|
'vdu': self._vdu
|
||||||
|
}
|
||||||
|
return self._cfg_parser.substitute(cmd_raw, **inline_param)
|
||||||
|
|
||||||
|
def _is_execution_error(self, res_code):
|
||||||
|
return False if res_code == 0 else True
|
@ -0,0 +1,45 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible import exceptions
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class VmAppConfigWalker(object):
|
||||||
|
def __init__(self):
|
||||||
|
self._config = None
|
||||||
|
|
||||||
|
def set_config(self, config_value):
|
||||||
|
self._config = config_value.get("vdus", {})
|
||||||
|
|
||||||
|
def get_creds_from_vdu(self, vdu, vdu_name):
|
||||||
|
vdu_dict = self._config.get(vdu_name, {}).get("config", {}).get(
|
||||||
|
"vm_app_config", {})
|
||||||
|
if not vdu_dict:
|
||||||
|
exceptions.DataRetrievalError(
|
||||||
|
vdu=vdu,
|
||||||
|
details=("Unable to retrieve configuration information"
|
||||||
|
"for VDU: {}".format(vdu_name))
|
||||||
|
)
|
||||||
|
LOG.debug("vdu_dict: {}".format(vdu_dict))
|
||||||
|
|
||||||
|
creds = {
|
||||||
|
"username": vdu_dict.get("username", ""),
|
||||||
|
"password": vdu_dict.get("password", ""),
|
||||||
|
"priv_key_file": vdu_dict.get("priv_key_file", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("creds: {}".format(creds))
|
||||||
|
return creds
|
@ -0,0 +1,361 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible import event_handler
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible import exceptions
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible import utils
|
||||||
|
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible.config_actions.\
|
||||||
|
vm_app_config import config_walker
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
EVENT_HANDLER = event_handler.AnsibleEventHandler()
|
||||||
|
OPTS = [
|
||||||
|
cfg.StrOpt("user", default="root",
|
||||||
|
help="user name to login ansible server"),
|
||||||
|
cfg.StrOpt("password", default="root123",
|
||||||
|
help="password to login ansible server"),
|
||||||
|
cfg.StrOpt("host", default="127.0.0.1",
|
||||||
|
help="host of the ansible server"),
|
||||||
|
cfg.StrOpt("private_key_file", default="",
|
||||||
|
help="private_key_file of the ansible server"),
|
||||||
|
cfg.IntOpt("retry_count", default=120, help="maximum no. of retries"),
|
||||||
|
cfg.IntOpt("retry_interval", default=30,
|
||||||
|
help="time in seconds before next retry"),
|
||||||
|
cfg.IntOpt("connection_wait_timeout", default=3600,
|
||||||
|
help="time in seconds before ssh timeout"),
|
||||||
|
cfg.IntOpt("command_execution_wait_timeout", default=3600,
|
||||||
|
help="maximum time allocated to a command to return result"),
|
||||||
|
]
|
||||||
|
cfg.CONF.register_opts(OPTS, "ansible")
|
||||||
|
|
||||||
|
|
||||||
|
class Executor(config_walker.VmAppConfigWalker):
|
||||||
|
def __init__(self):
|
||||||
|
self._queue = {}
|
||||||
|
self._execute_host = {}
|
||||||
|
self._local_execute_host = {}
|
||||||
|
self._target_host = {}
|
||||||
|
self._conf_opts = {}
|
||||||
|
self._node_pair_ip = None
|
||||||
|
|
||||||
|
self._action_key = ""
|
||||||
|
self._skip_execute = False
|
||||||
|
self._failed_vdu_name = None
|
||||||
|
|
||||||
|
self._vdu = None
|
||||||
|
self._vnf = None
|
||||||
|
self._context = None
|
||||||
|
self._mgmt_ip_address = None
|
||||||
|
self._conf_value = None
|
||||||
|
self._mgmt_url = None
|
||||||
|
self._cfg_parser = None
|
||||||
|
self._mgmt_executor_type = None
|
||||||
|
|
||||||
|
super(Executor, self).__init__()
|
||||||
|
|
||||||
|
def execute(self, **kwargs):
|
||||||
|
self._vdu = kwargs["vdu"]
|
||||||
|
self._vnf = kwargs["vnf"]
|
||||||
|
self._context = kwargs["context"]
|
||||||
|
self._mgmt_ip_address = kwargs["mgmt_ip_address"]
|
||||||
|
self._conf_value = kwargs["conf_value"]
|
||||||
|
self._mgmt_url = kwargs["mgmt_url"]
|
||||||
|
self._cfg_parser = kwargs["cfg_parser"]
|
||||||
|
self._action_key = kwargs["action_key"]
|
||||||
|
self._skip_execute = kwargs["skip_execute"]
|
||||||
|
self._failed_vdu_name = kwargs["failed_vdu_name"]
|
||||||
|
self._mgmt_executor_type = kwargs["mgmt_executor_type"]
|
||||||
|
self.set_config(kwargs["config_yaml"])
|
||||||
|
|
||||||
|
if self._skip_execute:
|
||||||
|
LOG.debug("Skip execution for VDU: {}".format(self._vdu))
|
||||||
|
return
|
||||||
|
|
||||||
|
self._conf_opts = {
|
||||||
|
"user": cfg.CONF.ansible.user,
|
||||||
|
"password": cfg.CONF.ansible.password,
|
||||||
|
"host": cfg.CONF.ansible.host,
|
||||||
|
"private_key_file": cfg.CONF.ansible.private_key_file
|
||||||
|
}
|
||||||
|
retry_count = self._conf_value.get("retry_count",
|
||||||
|
cfg.CONF.ansible.retry_count)
|
||||||
|
retry_interval = self._conf_value.get("retry_interval",
|
||||||
|
cfg.CONF.ansible.retry_interval)
|
||||||
|
connection_wait_timeout = self._conf_value.get(
|
||||||
|
"connection_wait_timeout",
|
||||||
|
cfg.CONF.ansible.connection_wait_timeout)
|
||||||
|
command_execution_wait_timeout = \
|
||||||
|
self._conf_value.get("command_execution_wait_timeout",
|
||||||
|
cfg.CONF.ansible.command_execution_wait_timeout)
|
||||||
|
|
||||||
|
self._execute_host = self._get_execute_host(
|
||||||
|
execute_host=self._conf_value.get("execute-host", {}),
|
||||||
|
conf_value=self._conf_value)
|
||||||
|
self._target_host = self._get_target_host(self._conf_value)
|
||||||
|
self._node_pair_ip = self._get_node_pair_ip(self._conf_value,
|
||||||
|
self._mgmt_url)
|
||||||
|
|
||||||
|
# translate some params
|
||||||
|
inline_param = {
|
||||||
|
'mgmt_ip_address': self._mgmt_ip_address,
|
||||||
|
'vdu': self._vdu
|
||||||
|
}
|
||||||
|
|
||||||
|
retry_count = self._cfg_parser.substitute(retry_count, **inline_param)
|
||||||
|
retry_interval = self._cfg_parser.substitute(retry_interval,
|
||||||
|
**inline_param)
|
||||||
|
connection_wait_timeout = self._cfg_parser.substitute(
|
||||||
|
connection_wait_timeout, **inline_param)
|
||||||
|
command_execution_wait_timeout = self._cfg_parser.substitute(
|
||||||
|
command_execution_wait_timeout, **inline_param)
|
||||||
|
|
||||||
|
LOG.debug("Command execution settings - retry count: {}".format(
|
||||||
|
retry_count))
|
||||||
|
LOG.debug("Command execution settings - retry interval: {}".format(
|
||||||
|
retry_interval))
|
||||||
|
LOG.debug("Command execution settings - "
|
||||||
|
"connection_wait_timeout: {}".format(connection_wait_timeout))
|
||||||
|
LOG.debug("Command execution settings - "
|
||||||
|
"command_execution_wait_timeout: {}".format(
|
||||||
|
command_execution_wait_timeout))
|
||||||
|
|
||||||
|
playbook_cmd_list = self._conf_value.get(self._action_key, None)
|
||||||
|
if not playbook_cmd_list:
|
||||||
|
msg = "No '{}' configuration defined for VDU '{}' "
|
||||||
|
"with IP Address '{}'"
|
||||||
|
EVENT_HANDLER.create_event(
|
||||||
|
self._context,
|
||||||
|
self._vnf,
|
||||||
|
utils.get_event_by_action_key(self._action_key),
|
||||||
|
msg.format(self._action_key, self._vdu, self._mgmt_ip_address)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
LOG.debug("conf_value @ {} {}".format(self._action_key,
|
||||||
|
playbook_cmd_list))
|
||||||
|
|
||||||
|
self._sort_playbook_cmd_list(playbook_cmd_list)
|
||||||
|
LOG.debug("Sorted playbooks/commands: {}".format(
|
||||||
|
self._queue.values()))
|
||||||
|
|
||||||
|
self._execute(retry_count, retry_interval,
|
||||||
|
connection_wait_timeout, command_execution_wait_timeout)
|
||||||
|
|
||||||
|
def _execute(self, retry_count, retry_interval, connection_wait_timeout,
|
||||||
|
command_execution_wait_timeout):
|
||||||
|
for order in sorted(self._queue):
|
||||||
|
playbook_cmd_list = self._queue[order]
|
||||||
|
|
||||||
|
for playbook_cmd in playbook_cmd_list:
|
||||||
|
LOG.debug("playbook/command: {}".format(playbook_cmd))
|
||||||
|
|
||||||
|
self._pre_execution(playbook_cmd)
|
||||||
|
|
||||||
|
cmd = self._get_final_command(playbook_cmd)
|
||||||
|
LOG.debug("command for execution: {}".format(cmd))
|
||||||
|
|
||||||
|
res_code = -1
|
||||||
|
try:
|
||||||
|
res_code, host = self._execute_cmd(
|
||||||
|
cmd,
|
||||||
|
retry_count,
|
||||||
|
retry_interval,
|
||||||
|
connection_wait_timeout,
|
||||||
|
command_execution_wait_timeout
|
||||||
|
)
|
||||||
|
except exceptions.AnsibleDriverException:
|
||||||
|
raise
|
||||||
|
except Exception as ex:
|
||||||
|
raise exceptions.CommandExecutionError(vdu=self._vdu,
|
||||||
|
details=ex)
|
||||||
|
|
||||||
|
self._post_execution(cmd, res_code, host)
|
||||||
|
|
||||||
|
if self._is_execution_error(res_code):
|
||||||
|
raise exceptions.CommandExecutionError(
|
||||||
|
vdu=self._vdu,
|
||||||
|
details="Non-zero return code"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _sort_playbook_cmd_list(self, playbook_cmd_list):
|
||||||
|
self._queue = {}
|
||||||
|
for playbook_cmd in playbook_cmd_list:
|
||||||
|
self._add_to_queue(playbook_cmd)
|
||||||
|
|
||||||
|
def _add_to_queue(self, playbook_cmd):
|
||||||
|
if "order" not in playbook_cmd:
|
||||||
|
raise exceptions.MandatoryKeyNotDefinedError(vdu=self._vdu,
|
||||||
|
key="order")
|
||||||
|
|
||||||
|
try:
|
||||||
|
order = int(playbook_cmd["order"])
|
||||||
|
except ValueError:
|
||||||
|
raise exceptions.InvalidValueError(vdu=self._vdu, key="order")
|
||||||
|
|
||||||
|
if order in self._queue:
|
||||||
|
self._queue[order].append(playbook_cmd)
|
||||||
|
else:
|
||||||
|
entity_list = []
|
||||||
|
entity_list.append(playbook_cmd)
|
||||||
|
self._queue[order] = entity_list
|
||||||
|
|
||||||
|
def _execute_cmd(self, cmd, retry_count, retry_interval,
|
||||||
|
connection_wait_timeout, command_execution_wait_timeout):
|
||||||
|
|
||||||
|
if self._local_execute_host:
|
||||||
|
user = self._local_execute_host["user"]
|
||||||
|
host = self._local_execute_host["host"]
|
||||||
|
password = self._local_execute_host["password"]
|
||||||
|
host_private_key_file = \
|
||||||
|
self._local_execute_host["host_private_key_file"]
|
||||||
|
|
||||||
|
else:
|
||||||
|
user = self._execute_host["user"]
|
||||||
|
host = self._execute_host["host"]
|
||||||
|
password = self._execute_host["password"]
|
||||||
|
host_private_key_file = \
|
||||||
|
self._execute_host["host_private_key_file"]
|
||||||
|
|
||||||
|
LOG.debug("Executing command: {} for VDU {}, host: {}, "
|
||||||
|
"username: {}, password: {}, private_key_file {}".format(
|
||||||
|
cmd, self._vdu, host, user, password, host_private_key_file))
|
||||||
|
|
||||||
|
# create command executor
|
||||||
|
result = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE, shell=True, universal_newlines=True)
|
||||||
|
std_out, std_err = result.communicate()
|
||||||
|
|
||||||
|
LOG.debug("command execution result code: {}".format(
|
||||||
|
result.returncode))
|
||||||
|
LOG.debug("command execution result code: {}".format(std_out))
|
||||||
|
LOG.debug("command execution result code: {}".format(std_err))
|
||||||
|
|
||||||
|
return result.returncode, host
|
||||||
|
|
||||||
|
def _post_execution(self, cmd, res_code, host):
|
||||||
|
|
||||||
|
msg = ("Command executed for the VDU '{}' with IP Address "
|
||||||
|
"'{}' on execute-host '{}' and result code '{}' => {}".format(
|
||||||
|
self._vdu,
|
||||||
|
self._mgmt_ip_address,
|
||||||
|
host,
|
||||||
|
res_code,
|
||||||
|
cmd))
|
||||||
|
|
||||||
|
EVENT_HANDLER.create_event(
|
||||||
|
self._context,
|
||||||
|
self._vnf,
|
||||||
|
utils.get_event_by_action_key(self._action_key),
|
||||||
|
msg,
|
||||||
|
self._is_execution_error(res_code)
|
||||||
|
)
|
||||||
|
|
||||||
|
# reset local execute-host details
|
||||||
|
self._local_execute_host = None
|
||||||
|
|
||||||
|
def _get_execute_host(self, execute_host, conf_value=None,
|
||||||
|
use_default=True):
|
||||||
|
# some inline variables for parameter translation
|
||||||
|
inline_param = {
|
||||||
|
'mgmt_ip_address': self._mgmt_ip_address,
|
||||||
|
'vdu': self._vdu
|
||||||
|
}
|
||||||
|
|
||||||
|
if not execute_host:
|
||||||
|
if use_default:
|
||||||
|
user, password, host, host_private_key_file = \
|
||||||
|
self._get_default_execute_host(conf_value)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
host_name = execute_host.get("host", "")
|
||||||
|
# process host
|
||||||
|
if host_name in self._mgmt_url:
|
||||||
|
# the given config value "host" is a VDU name
|
||||||
|
host = self._mgmt_url.get(host_name, "")
|
||||||
|
vdu_creds = self.get_creds_from_vdu(self._vdu, host_name)
|
||||||
|
user = vdu_creds.get("username", "")
|
||||||
|
password = vdu_creds.get("password", "")
|
||||||
|
host_private_key_file = vdu_creds.get("priv_key_file", "")
|
||||||
|
else:
|
||||||
|
# the given config value "host" is an address
|
||||||
|
host = host_name
|
||||||
|
user = execute_host.get("username", "")
|
||||||
|
password = execute_host.get("password", "")
|
||||||
|
host_private_key_file = execute_host.get("priv_key_file", "")
|
||||||
|
|
||||||
|
# validate config
|
||||||
|
if not host:
|
||||||
|
raise exceptions.DataRetrievalError(
|
||||||
|
vdu=self._vdu,
|
||||||
|
details="Unable to retrieve 'host' for execute-host"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
raise exceptions.DataRetrievalError(
|
||||||
|
vdu=self._vdu,
|
||||||
|
details="Unable to retrieve 'username' for execute-host"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not password and not host_private_key_file:
|
||||||
|
raise exceptions.DataRetrievalError(
|
||||||
|
vdu=self._vdu,
|
||||||
|
details="Unable to retrieve either 'password' or "
|
||||||
|
"'priv_key_file' for execute-host"
|
||||||
|
)
|
||||||
|
|
||||||
|
user = self._cfg_parser.substitute(user, **inline_param)
|
||||||
|
password = self._cfg_parser.substitute(password, **inline_param)
|
||||||
|
host = self._cfg_parser.substitute(host, **inline_param)
|
||||||
|
host_private_key_file = self._cfg_parser.substitute(
|
||||||
|
host_private_key_file, **inline_param)
|
||||||
|
|
||||||
|
execute_host = {
|
||||||
|
"user": user,
|
||||||
|
"password": password,
|
||||||
|
"host": host,
|
||||||
|
"host_private_key_file": host_private_key_file
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("execute-host->user: {}".format(user))
|
||||||
|
LOG.debug("execute-host->password: {}".format(password))
|
||||||
|
LOG.debug("execute-host->host: {}".format(host))
|
||||||
|
LOG.debug("execute-host->pkeyfile: {}".format(host_private_key_file))
|
||||||
|
|
||||||
|
return execute_host
|
||||||
|
|
||||||
|
def _pre_execution(self, playbook_cmd):
|
||||||
|
local_execute_host = playbook_cmd.get("execute-host", {})
|
||||||
|
LOG.debug('conf_value local_execute_host: {}'.format(
|
||||||
|
local_execute_host))
|
||||||
|
self._local_execute_host = self._get_execute_host(
|
||||||
|
execute_host=local_execute_host, use_default=False)
|
||||||
|
|
||||||
|
def _get_final_command(self, playbook_cmd):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _is_execution_error(self, res_code):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _get_target_host(self, conf_value):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _get_node_pair_ip(self, conf_value, mgmt_url):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _get_default_execute_host(self, conf_value=None):
|
||||||
|
raise NotImplementedError
|
@ -0,0 +1,143 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from tacker.vnfm.mgmt_drivers import constants
|
||||||
|
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible import event_handler
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible import exceptions
|
||||||
|
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible.config_actions import abstract_config
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible.config_actions.\
|
||||||
|
vm_app_config import ansible_playbook_exec
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
EVENT_HANDLER = event_handler.AnsibleEventHandler()
|
||||||
|
|
||||||
|
|
||||||
|
class VmAppConfigAction(abstract_config.AbstractConfigAction):
|
||||||
|
def __init__(self):
|
||||||
|
self._mgmt_executors = {}
|
||||||
|
self._mgmt_executors["ansible"] = \
|
||||||
|
ansible_playbook_exec.AnsiblePlaybookExecutor()
|
||||||
|
|
||||||
|
self._vdu = None
|
||||||
|
self._vnf = None
|
||||||
|
self._context = None
|
||||||
|
self._mgmt_executor_type = None
|
||||||
|
|
||||||
|
def execute(self, **kwargs):
|
||||||
|
vdu = kwargs["vdu"]
|
||||||
|
vnf = kwargs["vnf"]
|
||||||
|
context = kwargs["context"]
|
||||||
|
conf_value = kwargs["conf_value"]
|
||||||
|
mgmt_url = kwargs["mgmt_url"]
|
||||||
|
action = kwargs["action"]
|
||||||
|
failed_vdu_name = vnf.get("failed_vdu_name", "")
|
||||||
|
cfg_parser = kwargs["cfg_parser"]
|
||||||
|
stack_map = kwargs["stack_map"]
|
||||||
|
config_yaml = kwargs["config_yaml"]
|
||||||
|
|
||||||
|
self._vdu = vdu
|
||||||
|
self._vnf = vnf
|
||||||
|
self._context = context
|
||||||
|
|
||||||
|
mgmt_executor = \
|
||||||
|
self._get_mgmt_executor(conf_value.get("type", "ansible"))
|
||||||
|
mgmt_executor_type = self._mgmt_executor_type
|
||||||
|
action_key = self._get_action_key(action, failed_vdu_name)
|
||||||
|
skip_execute = self._is_skipped(action_key, failed_vdu_name, stack_map)
|
||||||
|
|
||||||
|
mgmt_ip_address = ""
|
||||||
|
|
||||||
|
mgmt_ip_address_list = self._get_mgmt_ip_address(skip_execute,
|
||||||
|
mgmt_url, stack_map)
|
||||||
|
|
||||||
|
for mgmt_ip_address in mgmt_ip_address_list:
|
||||||
|
mgmt_executor.execute(
|
||||||
|
vdu=vdu,
|
||||||
|
vnf=vnf,
|
||||||
|
context=context,
|
||||||
|
mgmt_ip_address=mgmt_ip_address,
|
||||||
|
conf_value=conf_value,
|
||||||
|
mgmt_url=mgmt_url,
|
||||||
|
cfg_parser=cfg_parser,
|
||||||
|
failed_vdu_name=failed_vdu_name,
|
||||||
|
action_key=action_key,
|
||||||
|
skip_execute=skip_execute,
|
||||||
|
config_yaml=config_yaml,
|
||||||
|
mgmt_executor_type=mgmt_executor_type
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_mgmt_executor(self, mgmt_type):
|
||||||
|
self._mgmt_executor_type = mgmt_type
|
||||||
|
mgmt_executor = self._mgmt_executors.get(mgmt_type, None)
|
||||||
|
|
||||||
|
if not mgmt_executor:
|
||||||
|
raise exceptions.InvalidKeyError(vdu=self._vdu, key=mgmt_type)
|
||||||
|
|
||||||
|
return mgmt_executor
|
||||||
|
|
||||||
|
def _get_mgmt_ip_address(self, skip_execute, mgmt_url, stack_map):
|
||||||
|
mgmt_ip_address = []
|
||||||
|
if not skip_execute:
|
||||||
|
if stack_map:
|
||||||
|
mgmt_ip_address = stack_map.get(self._vdu, "")
|
||||||
|
else:
|
||||||
|
mgmt_ip_address = mgmt_url.get(self._vdu, "")
|
||||||
|
|
||||||
|
if not mgmt_ip_address:
|
||||||
|
raise exceptions.DataRetrievalError(
|
||||||
|
vdu=self._vdu,
|
||||||
|
details="Cannot get mgmt address for the VDU: {}".format(
|
||||||
|
self._vdu)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not isinstance(mgmt_ip_address, list):
|
||||||
|
# put ip inside list
|
||||||
|
mgmt_ip_address = [mgmt_ip_address]
|
||||||
|
return mgmt_ip_address
|
||||||
|
|
||||||
|
def _get_action_key(self, action, failed_vdu_name):
|
||||||
|
# if no key for a particular VNF operation is found,
|
||||||
|
# use the default 'playbooks'
|
||||||
|
action_key = None
|
||||||
|
if action == constants.ACTION_HEAL_VNF:
|
||||||
|
if failed_vdu_name:
|
||||||
|
LOG.debug("failed vdu name: %s", failed_vdu_name)
|
||||||
|
action_key = "healing"
|
||||||
|
elif action == constants.ACTION_INSTANTIATE_VNF:
|
||||||
|
action_key = "instantiation"
|
||||||
|
elif action == constants.ACTION_TERMINATE_VNF:
|
||||||
|
action_key = "termination"
|
||||||
|
elif action == constants.ACTION_SCALE_IN_VNF:
|
||||||
|
action_key = "scale-in"
|
||||||
|
elif action == constants.ACTION_SCALE_OUT_VNF:
|
||||||
|
action_key = "scale-out"
|
||||||
|
return action_key
|
||||||
|
|
||||||
|
def _is_skipped(self, action_key, failed_vdu_name, stack_map):
|
||||||
|
is_skipped = False
|
||||||
|
if action_key:
|
||||||
|
if action_key == "healing":
|
||||||
|
if self._vdu != failed_vdu_name:
|
||||||
|
is_skipped = True
|
||||||
|
LOG.debug("VDU %s did not fail.", self._vdu)
|
||||||
|
elif action_key == "scale-in" or action_key == "scale-out":
|
||||||
|
if self._vdu not in stack_map:
|
||||||
|
is_skipped = True
|
||||||
|
LOG.debug("VDU is not scaling target: {}".format(
|
||||||
|
self._vdu))
|
||||||
|
else:
|
||||||
|
is_skipped = True
|
||||||
|
LOG.debug("Action not supported.")
|
||||||
|
return is_skipped
|
60
samples/mgmt_driver/ansible/config_validator.py
Normal file
60
samples/mgmt_driver/ansible/config_validator.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from jsonschema import exceptions as json_exp
|
||||||
|
from jsonschema import validate
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible import event_handler
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible import exceptions
|
||||||
|
|
||||||
|
from tacker.vnfm.mgmt_drivers.ansible.config_validator_schema import SCHEMA
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
EVENT_HANDLER = event_handler.AnsibleEventHandler()
|
||||||
|
|
||||||
|
|
||||||
|
class AnsibleConfigValidator(object):
|
||||||
|
def __init__(self):
|
||||||
|
self._schema = yaml.safe_load(SCHEMA)
|
||||||
|
|
||||||
|
def validate(self, config_yaml):
|
||||||
|
try:
|
||||||
|
validate(config_yaml, self._schema)
|
||||||
|
except json_exp.ValidationError as err:
|
||||||
|
raise exceptions.ConfigValidationError(
|
||||||
|
details=str(err).splitlines()[0])
|
||||||
|
|
||||||
|
def filter_vdus(self, context, vnf, event, mgmt_url, config_yaml):
|
||||||
|
vdu_config = config_yaml.get("vdus", {})
|
||||||
|
filtered_vdu_config = {}
|
||||||
|
|
||||||
|
if vdu_config:
|
||||||
|
for vdu in mgmt_url:
|
||||||
|
if vdu in vdu_config:
|
||||||
|
filtered_vdu_config[vdu] = vdu_config.get(vdu)
|
||||||
|
else:
|
||||||
|
msg = ("Could not find configuration entry for VDU '{}' "
|
||||||
|
"with IP Address '{}': skipping configuration")
|
||||||
|
EVENT_HANDLER.create_event(
|
||||||
|
context,
|
||||||
|
vnf,
|
||||||
|
event,
|
||||||
|
msg.format(vdu, json.dumps(mgmt_url.get(vdu, "")))
|
||||||
|
)
|
||||||
|
|
||||||
|
config_yaml["vdus"] = filtered_vdu_config
|
||||||
|
LOG.debug("filtered config yaml: {}".format(config_yaml))
|
||||||
|
return config_yaml
|
135
samples/mgmt_driver/ansible/config_validator_schema.py
Normal file
135
samples/mgmt_driver/ansible/config_validator_schema.py
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
SCHEMA = """
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- vdus
|
||||||
|
properties:
|
||||||
|
vdus:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
patternProperties:
|
||||||
|
^[!-~]+$:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- config
|
||||||
|
properties:
|
||||||
|
config:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- order
|
||||||
|
- vm_app_config
|
||||||
|
properties:
|
||||||
|
order:
|
||||||
|
type: integer
|
||||||
|
vm_app_config:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
anyOf:
|
||||||
|
- required:
|
||||||
|
- instantiation
|
||||||
|
- required:
|
||||||
|
- termination
|
||||||
|
- required:
|
||||||
|
- healing
|
||||||
|
- required:
|
||||||
|
- scale-in
|
||||||
|
- required:
|
||||||
|
- scale-out
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- ansible
|
||||||
|
- remote-command
|
||||||
|
node_pair:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
priv_key_file:
|
||||||
|
type: string
|
||||||
|
retry_count:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
retry_interval:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
connection_wait_timeout:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
command_execution_wait_timeout:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
execute-host:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- host
|
||||||
|
properties:
|
||||||
|
host:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
priv_key_file:
|
||||||
|
type: string
|
||||||
|
patternProperties:
|
||||||
|
^instantiation|termination|healing|scale-in|scale-out$:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- order
|
||||||
|
oneOf:
|
||||||
|
- required:
|
||||||
|
- path
|
||||||
|
- required:
|
||||||
|
- command
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
params:
|
||||||
|
type: object
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
order:
|
||||||
|
type: integer
|
||||||
|
target_hosts:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
execute-host:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- host
|
||||||
|
properties:
|
||||||
|
host:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
priv_key_file:
|
||||||
|
type: string
|
||||||
|
"""
|
25
samples/mgmt_driver/ansible/event_handler.py
Normal file
25
samples/mgmt_driver/ansible/event_handler.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from tacker.plugins.common import constants
|
||||||
|
from tacker.vnfm import utils
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AnsibleEventHandler(object):
|
||||||
|
def create_event(self, context, vnf, event, msg, error_flag=False):
|
||||||
|
vnf["status"] = constants.ERROR if error_flag else vnf["status"]
|
||||||
|
utils.log_events(context, vnf, event, msg)
|
144
samples/mgmt_driver/ansible/exceptions.py
Normal file
144
samples/mgmt_driver/ansible/exceptions.py
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
class AnsibleDriverException(Exception):
|
||||||
|
def __init__(self, vdu=None, **kwargs):
|
||||||
|
if "details" not in kwargs or not kwargs["details"]:
|
||||||
|
kwargs["details"] = "No error information available."
|
||||||
|
|
||||||
|
self.vdu = vdu
|
||||||
|
self.message = self.message % kwargs
|
||||||
|
super(AnsibleDriverException, self).__init__(self.message)
|
||||||
|
|
||||||
|
|
||||||
|
class InternalErrorException(AnsibleDriverException):
|
||||||
|
"""Internal Error.
|
||||||
|
|
||||||
|
Define the following upon using this exception:
|
||||||
|
- details: the exception message or error information
|
||||||
|
"""
|
||||||
|
message = "Management driver internal error: %(details)s"
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigParserConfigurationError(AnsibleDriverException):
|
||||||
|
"""Config parser configuration error.
|
||||||
|
|
||||||
|
Define the following upon using this exception:
|
||||||
|
- ex_type: the exception type
|
||||||
|
- details: the exception message or error information
|
||||||
|
"""
|
||||||
|
message = "Parameter conversion error. "
|
||||||
|
"Error encountered in configuring parser: [%(ex_type)s, %(details)s]"
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigParserParsingError(AnsibleDriverException):
|
||||||
|
"""Config parser parsing error.
|
||||||
|
|
||||||
|
Define the following upon using this exception:
|
||||||
|
- cmd: the command being parsed that resulted in error
|
||||||
|
- ex_type: the exception type
|
||||||
|
- details: the exception message or error information
|
||||||
|
"""
|
||||||
|
message = "Parameter conversion error. "
|
||||||
|
"Encountered error in parsing '%(cmd)s': [%(ex_type)s, %(details)s]"
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigValidationError(AnsibleDriverException):
|
||||||
|
"""Config file validation error.
|
||||||
|
|
||||||
|
Define the following upon using this exception:
|
||||||
|
- details: the exception message or error information
|
||||||
|
"""
|
||||||
|
message = "Config file validation error: %(details)s"
|
||||||
|
|
||||||
|
|
||||||
|
class MandatoryKeyNotDefinedError(AnsibleDriverException):
|
||||||
|
"""Config file validation error. Mandatory key is not defined.
|
||||||
|
|
||||||
|
Define the following upon using this exception:
|
||||||
|
- key: the offending key
|
||||||
|
"""
|
||||||
|
message = "Config file validation error. "
|
||||||
|
"The key '%(key)s' is not defined."
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidValueError(AnsibleDriverException):
|
||||||
|
"""Config file validation error. Invalid value for a key is defined.
|
||||||
|
|
||||||
|
Define the following upon using this exception:
|
||||||
|
- key: the offending key
|
||||||
|
"""
|
||||||
|
message = "Config file validation error. "
|
||||||
|
"Invalid value of '%(key)s' is defined."
|
||||||
|
|
||||||
|
|
||||||
|
class PlaybooksCommandsNotFound(AnsibleDriverException):
|
||||||
|
"""Config file validation error. Playbooks or commands not found.
|
||||||
|
|
||||||
|
Define the following upon using this exception:
|
||||||
|
- key: the offending action key
|
||||||
|
"""
|
||||||
|
message = "Config file validation error. "
|
||||||
|
"Playbooks or commands not found for action key: %(key)s"
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidKeyError(AnsibleDriverException):
|
||||||
|
"""Config file validation error. Invalid key error.
|
||||||
|
|
||||||
|
Define the following upon using this exception:
|
||||||
|
- key: the offending key
|
||||||
|
"""
|
||||||
|
message = "Config file validation error. The key '%(key)s' is not valid."
|
||||||
|
|
||||||
|
|
||||||
|
class DataRetrievalError(AnsibleDriverException):
|
||||||
|
"""Data retrieval error.
|
||||||
|
|
||||||
|
Define the following upon using this exception:
|
||||||
|
- details: the exception message or error information
|
||||||
|
"""
|
||||||
|
message = "Data retrieval error: %(details)s"
|
||||||
|
|
||||||
|
|
||||||
|
class CommandExecutionError(AnsibleDriverException):
|
||||||
|
"""Command execution error.
|
||||||
|
|
||||||
|
Define the following upon using this exception:
|
||||||
|
- details: the exception message or error information
|
||||||
|
"""
|
||||||
|
message = "Command execution error: %(details)s"
|
||||||
|
|
||||||
|
|
||||||
|
class CommandExecutionTimeoutError(AnsibleDriverException):
|
||||||
|
"""Command execution timeout error.
|
||||||
|
|
||||||
|
Define the following upon using this exception:
|
||||||
|
- host: the target host for execution
|
||||||
|
- cmd: the command executed that caused the error
|
||||||
|
"""
|
||||||
|
message = "Command execution has reached timeout. "
|
||||||
|
"Target: %(host)s Command: %(cmd)s"
|
||||||
|
|
||||||
|
|
||||||
|
class CommandConnectionLimitReached(AnsibleDriverException):
|
||||||
|
"""Command connection attempt limit reached."""
|
||||||
|
message = "Connection attempt has reached its limit."
|
||||||
|
|
||||||
|
|
||||||
|
class CommandConnectionError(AnsibleDriverException):
|
||||||
|
"""Command connection error.
|
||||||
|
|
||||||
|
Define the following upon using this exception:
|
||||||
|
- details: the exception message or error information
|
||||||
|
"""
|
||||||
|
message = "Connection attempt error: %(details)s"
|
56
samples/mgmt_driver/ansible/heat_client.py
Normal file
56
samples/mgmt_driver/ansible/heat_client.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from tacker.common import clients
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AnsibleHeatClient():
|
||||||
|
|
||||||
|
def __init__(self, context, plugin, vnf):
|
||||||
|
access_info = plugin.get_vim(context, vnf)
|
||||||
|
|
||||||
|
vim_auth = access_info["vim_auth"]
|
||||||
|
auth_attr = {
|
||||||
|
"username": vim_auth["username"],
|
||||||
|
"password": vim_auth["password"],
|
||||||
|
"project_name": vim_auth["project_name"],
|
||||||
|
"cert_verify": vim_auth["cert_verify"],
|
||||||
|
"user_domain_name": vim_auth["user_domain_name"],
|
||||||
|
"auth_url": vim_auth["auth_url"],
|
||||||
|
"project_id": vim_auth["project_id"],
|
||||||
|
"project_domain_name": vim_auth["project_domain_name"]
|
||||||
|
}
|
||||||
|
|
||||||
|
region_name = vnf.get('placement_attr', {}).get('region_name', None)
|
||||||
|
|
||||||
|
self._heat_client = \
|
||||||
|
clients.OpenstackClients(auth_attr, region_name).heat
|
||||||
|
|
||||||
|
def get_parent_stack_id(self, stack_id):
|
||||||
|
stack = self._heat_client.stacks.get(stack_id)
|
||||||
|
return stack.parent
|
||||||
|
|
||||||
|
def get_resource_list(self, stack_id):
|
||||||
|
resource_list = []
|
||||||
|
if stack_id:
|
||||||
|
resource_list = self._heat_client.resources.list(stack_id)
|
||||||
|
return resource_list
|
||||||
|
|
||||||
|
def get_resource(self, stack_id, resource_name):
|
||||||
|
return self._heat_client.resources.get(stack_id, resource_name)
|
||||||
|
|
||||||
|
def get_resource_attributes(self, stack_id, resource_name):
|
||||||
|
return self.get_resource(stack_id, resource_name).attributes
|
47
samples/mgmt_driver/ansible/utils.py
Normal file
47
samples/mgmt_driver/ansible/utils.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from tacker.plugins.common import constants as plg_constants
|
||||||
|
from tacker.vnfm.mgmt_drivers import constants as mgmt_constants
|
||||||
|
|
||||||
|
CONFIG_FOLDER = "ScriptANSIBLE"
|
||||||
|
|
||||||
|
|
||||||
|
def get_event_by_action(action, failed_vdu_name):
|
||||||
|
event_type = None
|
||||||
|
if action == mgmt_constants.ACTION_INSTANTIATE_VNF:
|
||||||
|
event_type = plg_constants.RES_EVT_INSTANTIATE
|
||||||
|
elif action == mgmt_constants.ACTION_TERMINATE_VNF:
|
||||||
|
event_type = plg_constants.RES_EVT_TERMINATE
|
||||||
|
elif action == mgmt_constants.ACTION_HEAL_VNF:
|
||||||
|
if failed_vdu_name:
|
||||||
|
event_type = plg_constants.RES_EVT_HEAL
|
||||||
|
elif action == mgmt_constants.ACTION_SCALE_IN_VNF:
|
||||||
|
event_type = plg_constants.RES_EVT_SCALE
|
||||||
|
elif action == mgmt_constants.ACTION_SCALE_OUT_VNF:
|
||||||
|
event_type = plg_constants.RES_EVT_SCALE
|
||||||
|
return event_type
|
||||||
|
|
||||||
|
|
||||||
|
def get_event_by_action_key(action_key):
|
||||||
|
event_type = None
|
||||||
|
if action_key == "instantiation":
|
||||||
|
event_type = plg_constants.RES_EVT_INSTANTIATE
|
||||||
|
elif action_key == "termination":
|
||||||
|
event_type = plg_constants.RES_EVT_TERMINATE
|
||||||
|
elif action_key == "healing":
|
||||||
|
event_type = plg_constants.RES_EVT_HEAL
|
||||||
|
elif action_key == "scale-in":
|
||||||
|
event_type = plg_constants.RES_EVT_SCALE
|
||||||
|
elif action_key == "scale-out":
|
||||||
|
event_type = plg_constants.RES_EVT_SCALE
|
||||||
|
return event_type
|
Loading…
Reference in New Issue
Block a user