kayobe/kayobe/plugins/action/kolla_ansible_host_vars.py

169 lines
7.2 KiB
Python

# Copyright (c) 2020 StackHPC Ltd.
#
# 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 ansible.plugins.action import ActionBase
class ConfigError(Exception):
pass
class ActionModule(ActionBase):
"""Kolla Ansible host vars action plugin
This class provides an Ansible action module that returns facts
representing host variables to be passed to Kolla Ansible.
"""
TRANSFERS_FILES = False
def run(self, tmp=None, task_vars=None):
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
del tmp # tmp no longer has any effect
# Module arguments:
# interfaces: a list of dicts, each containing 'network', 'required',
# and 'description' keys. Each describes a Kolla Ansible
# interface variable.
# external_networks: a list of dicts, each containing 'network', and
# 'required' keys. Each describes an external
# network.
interfaces = self._task.args["interfaces"]
external_networks = self._task.args.get('external_networks', [])
result.update(self._run(interfaces, external_networks))
return result
def _run(self, interfaces, external_networks):
result = {}
facts = {}
errors = []
# Kolla Ansible interface facts.
for interface in interfaces:
try:
iface = self._get_interface_fact(interface["network"],
interface["required"],
interface["description"])
if iface:
facts[interface["var_name"]] = iface
except ConfigError as e:
errors.append(str(e))
# Build a list of external network interfaces.
external_interfaces = []
for network in external_networks:
try:
iface = self._get_external_interface(network["network"],
network["required"])
if iface and iface not in external_interfaces:
external_interfaces.append(iface)
except ConfigError as e:
errors.append(str(e))
if external_interfaces:
facts.update(self._get_external_interface_facts(
external_interfaces))
result['changed'] = False
if errors:
result['failed'] = True
result['msg'] = "; ".join(errors)
else:
result['ansible_facts'] = facts
result['_ansible_facts_cacheable'] = False
return result
def _get_interface_fact(self, net_name, required, description):
# Check whether the network is mapped to this host.
condition = "{{ '%s' in network_interfaces }}" % net_name
condition = self._templar.template(condition)
if condition:
# Get the network interface for this network.
iface = ("{{ '%s' | net_interface }}" % net_name)
iface = self._templar.template(iface)
if iface:
# Ansible fact names replace dashes with underscores.
# FIXME(mgoddard): Is this still required?
iface = iface.replace('-', '_')
if required and not iface:
msg = ("Required network '%s' (%s) does not have an interface "
"configured for this host" % (net_name, description))
raise ConfigError(msg)
return iface
elif required:
msg = ("Required network '%s' (%s) is not mapped to this host" %
(net_name, description))
raise ConfigError(msg)
def _get_external_interface(self, net_name, required):
condition = "{{ '%s' in network_interfaces }}" % net_name
condition = self._templar.template(condition)
if condition:
iface = self._templar.template("{{ '%s' | net_interface }}" %
net_name)
if iface:
# When these networks are VLANs, we need to use the
# underlying tagged bridge interface rather than the
# untagged interface. We therefore strip the .<vlan> suffix
# of the interface name. We use a union here as a single
# tagged interface may be shared between these networks.
vlan = self._templar.template("{{ '%s' | net_vlan }}" %
net_name)
parent = self._templar.template("{{ '%s' | net_parent }}" %
net_name)
if vlan and parent:
iface = parent
elif vlan and iface.endswith(".%s" % vlan):
iface = iface.replace(".%s" % vlan, "")
return iface
elif required:
raise ConfigError("Required external network '%s' does not "
"have an interface configured for this host"
% net_name)
elif required:
raise ConfigError("Required external network '%s' is not mapped "
"to this host" % net_name)
def _get_external_interface_facts(self, external_interfaces):
neutron_bridge_names = []
neutron_external_interfaces = []
bridge_suffix = self._templar.template(
"{{ network_bridge_suffix_ovs }}")
patch_prefix = self._templar.template("{{ network_patch_prefix }}")
patch_suffix = self._templar.template("{{ network_patch_suffix_ovs }}")
for interface in external_interfaces:
is_bridge = ("{{ '%s' in (network_interfaces |"
"net_select_bridges |"
"map('net_interface')) }}" % interface)
is_bridge = self._templar.template(is_bridge)
neutron_bridge_names.append(interface + bridge_suffix)
# For a bridge, use a veth pair connected to the bridge. Otherwise
# use the interface directly.
if is_bridge:
# interface names can't be longer than 15 characters
char_limit = 15 - len(patch_prefix) - len(patch_suffix)
external_interface = patch_prefix + interface[:char_limit] + \
patch_suffix
else:
external_interface = interface
neutron_external_interfaces.append(external_interface)
return {
"kolla_neutron_bridge_names": ",".join(neutron_bridge_names),
"kolla_neutron_external_interfaces": ",".join(
neutron_external_interfaces),
}