169 lines
7.2 KiB
Python
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),
|
|
}
|