![Takashi Kajinami](/assets/img/avatar_default.png)
six is no longer needed since python 2 support was removed. Change-Id: I87eef25a2e9f81aa6c6a31f4dcb9c900b8984162
468 lines
17 KiB
Python
468 lines
17 KiB
Python
# 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 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, str):
|
|
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, str):
|
|
# 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, str):
|
|
res = items
|
|
else:
|
|
raise Exception(
|
|
"Result items of type {} data struct is not supported."
|
|
.format(type(items)))
|
|
return res
|