tacker/samples/mgmt_driver/ansible/ansible_config_parser.py
Takashi Kajinami db2dee07a3 Remove six from sample scripts
six is no longer needed since python 2 support was removed.

Change-Id: I87eef25a2e9f81aa6c6a31f4dcb9c900b8984162
2024-09-06 05:55:38 +00:00

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