Actions that expose various neutron resources
New actions: * show-routers * show-dhcp-networks * show-loadbalancers Partial-Bug: #1916231 Closes-Bug: #1917401 Closes-Bug: #1917403 Closes-Bug: #1917405 Change-Id: Ie59c2a7d5c1ee9c51a0f7db4e8f38229812ac84a func-test-pr: https://github.com/openstack-charmers/zaza-openstack-tests/pull/611
This commit is contained in:
parent
6a22df54a7
commit
c6f970673b
@ -49,5 +49,11 @@ run-deferred-hooks:
|
|||||||
.
|
.
|
||||||
NOTE: Service will be restarted as needed irrespective of enable-auto-restarts
|
NOTE: Service will be restarted as needed irrespective of enable-auto-restarts
|
||||||
show-deferred-events:
|
show-deferred-events:
|
||||||
descrpition: |
|
description: |
|
||||||
Show the outstanding restarts
|
Show the outstanding restarts
|
||||||
|
show-routers:
|
||||||
|
description: Shows a list of routers hosted on the neutron-gateway unit.
|
||||||
|
show-dhcp-networks:
|
||||||
|
description: Shows a list of DHCP networks hosted on the neutron-gateway unit.
|
||||||
|
show-loadbalancers:
|
||||||
|
description: Shows a list of LBaasV2 load-balancers hosted on the neutron-gateway unit.
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import socket
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from keystoneauth1 import identity
|
||||||
|
from keystoneauth1 import session
|
||||||
|
from neutronclient.v2_0 import client
|
||||||
|
import yaml
|
||||||
|
|
||||||
_path = os.path.dirname(os.path.realpath(__file__))
|
_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
_hooks_dir = os.path.abspath(os.path.join(_path, "..", "hooks"))
|
_hooks_dir = os.path.abspath(os.path.join(_path, "..", "hooks"))
|
||||||
|
|
||||||
@ -14,12 +20,18 @@ def _add_path(path):
|
|||||||
|
|
||||||
_add_path(_hooks_dir)
|
_add_path(_hooks_dir)
|
||||||
|
|
||||||
|
from charmhelpers.core.hookenv import (
|
||||||
|
relation_get,
|
||||||
|
relation_ids,
|
||||||
|
related_units,
|
||||||
|
)
|
||||||
|
|
||||||
import charmhelpers.contrib.openstack.utils as os_utils
|
import charmhelpers.contrib.openstack.utils as os_utils
|
||||||
from charmhelpers.core.hookenv import (
|
from charmhelpers.core.hookenv import (
|
||||||
DEBUG,
|
DEBUG,
|
||||||
action_get,
|
action_get,
|
||||||
action_fail,
|
action_fail,
|
||||||
|
function_set,
|
||||||
log,
|
log,
|
||||||
)
|
)
|
||||||
from neutron_utils import (
|
from neutron_utils import (
|
||||||
@ -87,11 +99,207 @@ def show_deferred_events(args):
|
|||||||
os_utils.show_deferred_events_action_helper()
|
os_utils.show_deferred_events_action_helper()
|
||||||
|
|
||||||
|
|
||||||
|
def get_neutron():
|
||||||
|
"""Return authenticated neutron client.
|
||||||
|
|
||||||
|
:return: neutron client
|
||||||
|
:rtype: neutronclient.v2_0.client.Client
|
||||||
|
:raises RuntimeError: Exception is raised if authentication of neutron
|
||||||
|
client fails. This can be either because this unit does not have a
|
||||||
|
"neutron-plugin-api" relation which should contain credentials or
|
||||||
|
the credentials are wrong or keystone service is not available.
|
||||||
|
"""
|
||||||
|
rel_name = "neutron-plugin-api"
|
||||||
|
neutron = None
|
||||||
|
|
||||||
|
for rid in relation_ids(rel_name):
|
||||||
|
for unit in related_units(rid):
|
||||||
|
rdata = relation_get(rid=rid, unit=unit)
|
||||||
|
if rdata is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
protocol = rdata.get("auth_protocol")
|
||||||
|
host = rdata.get("auth_host")
|
||||||
|
port = rdata.get("auth_port")
|
||||||
|
username = rdata.get("service_username")
|
||||||
|
password = rdata.get("service_password")
|
||||||
|
project = rdata.get("service_tenant")
|
||||||
|
project_domain = rdata.get("service_domain", "default")
|
||||||
|
user_domain_name = rdata.get("service_domain", "default")
|
||||||
|
if protocol and host and port \
|
||||||
|
and username and password and project:
|
||||||
|
auth_url = "{}://{}:{}/".format(protocol,
|
||||||
|
host,
|
||||||
|
port)
|
||||||
|
auth = identity.Password(auth_url=auth_url,
|
||||||
|
username=username,
|
||||||
|
password=password,
|
||||||
|
project_name=project,
|
||||||
|
project_domain_name=project_domain,
|
||||||
|
user_domain_name=user_domain_name)
|
||||||
|
sess = session.Session(auth=auth)
|
||||||
|
neutron = client.Client(session=sess)
|
||||||
|
break
|
||||||
|
|
||||||
|
if neutron is not None:
|
||||||
|
break
|
||||||
|
|
||||||
|
if neutron is None:
|
||||||
|
raise RuntimeError("Relation '{}' is either missing or does not "
|
||||||
|
"contain neutron credentials".format(rel_name))
|
||||||
|
return neutron
|
||||||
|
|
||||||
|
|
||||||
|
def get_network_agents_on_host(hostname, neutron, agent_type=None):
|
||||||
|
"""Fetch list of neutron agents on specified host.
|
||||||
|
|
||||||
|
:param hostname: name of host on which the agents are running
|
||||||
|
:param neutron: authenticated neutron client
|
||||||
|
:param agent_type: If provided, filter only agents of selected type
|
||||||
|
:return: List of agents matching given criteria
|
||||||
|
:rtype: list[dict]
|
||||||
|
"""
|
||||||
|
params = {'host': hostname}
|
||||||
|
if agent_type is not None:
|
||||||
|
params['agent_type'] = agent_type
|
||||||
|
|
||||||
|
agent_list = neutron.list_agents(**params)["agents"]
|
||||||
|
|
||||||
|
return agent_list
|
||||||
|
|
||||||
|
|
||||||
|
def get_resource_list_on_agents(agent_list,
|
||||||
|
list_resource_function,
|
||||||
|
resource_name):
|
||||||
|
"""Fetch resources hosted on neutron agents combined into single list.
|
||||||
|
|
||||||
|
:param agent_list: List of agents on which to search for resources
|
||||||
|
:type agent_list: list[dict]
|
||||||
|
:param list_resource_function: function that takes agent ID and returns
|
||||||
|
resources present on that agent.
|
||||||
|
:type list_resource_function: Callable
|
||||||
|
:param resource_name: filter only resources with given name (e.g.:
|
||||||
|
"networks", "routers", ...)
|
||||||
|
:type resource_name: str
|
||||||
|
:return: List of neutron resources.
|
||||||
|
:rtype: list[dict]
|
||||||
|
"""
|
||||||
|
agent_id_list = [agent["id"] for agent in agent_list]
|
||||||
|
resource_list = []
|
||||||
|
for agent_id in agent_id_list:
|
||||||
|
resource_list.extend(list_resource_function(agent_id)[resource_name])
|
||||||
|
return resource_list
|
||||||
|
|
||||||
|
|
||||||
|
def clean_resource_list(resource_list, allowed_keys=None):
|
||||||
|
"""Strip resources of all fields except those in 'allowed_keys.'
|
||||||
|
|
||||||
|
resource_list is a list where each resources is represented as a dict with
|
||||||
|
many attributes. This function strips all but those defined in
|
||||||
|
'allowed_keys'
|
||||||
|
:param resource_list: List of resources to strip
|
||||||
|
:param allowed_keys: keys allowed in the resulting dictionary for each
|
||||||
|
resource
|
||||||
|
:return: List of stripped resources
|
||||||
|
:rtype: list[dict]
|
||||||
|
"""
|
||||||
|
if allowed_keys is None:
|
||||||
|
allowed_keys = ["id", "status"]
|
||||||
|
|
||||||
|
clean_data_list = []
|
||||||
|
for resource in resource_list:
|
||||||
|
clean_data = {key: value for key, value in resource.items()
|
||||||
|
if key in allowed_keys}
|
||||||
|
clean_data_list.append(clean_data)
|
||||||
|
return clean_data_list
|
||||||
|
|
||||||
|
|
||||||
|
def format_status_output(data, key_attribute="id"):
|
||||||
|
"""Reformat data from the neutron api into human-readable yaml.
|
||||||
|
|
||||||
|
Input data are expected to be list of dictionaries (as returned by neutron
|
||||||
|
api). This list is transformed into dict where "id" of a resource is key
|
||||||
|
and rest of the resource attributes are value (in form of dictionary). The
|
||||||
|
resulting structure is dumped as yaml text.
|
||||||
|
|
||||||
|
:param data: List of dictionaires representing neutron resources
|
||||||
|
:param key_attribute: attribute that will be used as a key in the
|
||||||
|
result. (default=id)
|
||||||
|
:return: yaml string representing input data.
|
||||||
|
"""
|
||||||
|
output = {}
|
||||||
|
for entry in data:
|
||||||
|
header = entry.pop(key_attribute)
|
||||||
|
output[header] = {}
|
||||||
|
for attribute, value in entry.items():
|
||||||
|
output[header][attribute] = value
|
||||||
|
|
||||||
|
return yaml.dump(output)
|
||||||
|
|
||||||
|
|
||||||
|
def get_routers(args):
|
||||||
|
"""Implementation of 'show-routers' action."""
|
||||||
|
neutron = get_neutron()
|
||||||
|
agent_list = get_network_agents_on_host(socket.gethostname(), neutron,
|
||||||
|
"L3 agent")
|
||||||
|
router_list = get_resource_list_on_agents(agent_list,
|
||||||
|
neutron.list_routers_on_l3_agent,
|
||||||
|
"routers")
|
||||||
|
|
||||||
|
clean_data = clean_resource_list(router_list,
|
||||||
|
allowed_keys=["id",
|
||||||
|
"status",
|
||||||
|
"ha",
|
||||||
|
"name"])
|
||||||
|
|
||||||
|
function_set({"router-list": format_status_output(clean_data)})
|
||||||
|
|
||||||
|
|
||||||
|
def get_dhcp_networks(args):
|
||||||
|
"""Implementation of 'show-dhcp-networks' action."""
|
||||||
|
neutron = get_neutron()
|
||||||
|
agent_list = get_network_agents_on_host(socket.gethostname(), neutron,
|
||||||
|
"DHCP agent")
|
||||||
|
list_func = neutron.list_networks_on_dhcp_agent
|
||||||
|
dhcp_network_list = get_resource_list_on_agents(agent_list,
|
||||||
|
list_func,
|
||||||
|
"networks")
|
||||||
|
|
||||||
|
clean_data = clean_resource_list(dhcp_network_list,
|
||||||
|
allowed_keys=["id", "status", "name"])
|
||||||
|
|
||||||
|
function_set({"dhcp-networks": format_status_output(clean_data)})
|
||||||
|
|
||||||
|
|
||||||
|
def get_lbaasv2_lb(args):
|
||||||
|
"""Implementation of 'show-loadbalancers' action."""
|
||||||
|
neutron = get_neutron()
|
||||||
|
agent_list = get_network_agents_on_host(socket.gethostname(),
|
||||||
|
neutron,
|
||||||
|
"Loadbalancerv2 agent")
|
||||||
|
list_func = neutron.list_loadbalancers_on_lbaas_agent
|
||||||
|
lb_list = get_resource_list_on_agents(agent_list,
|
||||||
|
list_func,
|
||||||
|
"loadbalancers")
|
||||||
|
|
||||||
|
clean_data = clean_resource_list(lb_list, allowed_keys=["id",
|
||||||
|
"status",
|
||||||
|
"name"])
|
||||||
|
|
||||||
|
function_set({"load-balancers": format_status_output(clean_data)})
|
||||||
|
|
||||||
|
|
||||||
# A dictionary of all the defined actions to callables (which take
|
# A dictionary of all the defined actions to callables (which take
|
||||||
# parsed arguments).
|
# parsed arguments).
|
||||||
ACTIONS = {"pause": pause, "resume": resume, "restart-services": restart,
|
ACTIONS = {"pause": pause,
|
||||||
|
"resume": resume,
|
||||||
|
"restart-services": restart,
|
||||||
"show-deferred-events": show_deferred_events,
|
"show-deferred-events": show_deferred_events,
|
||||||
"run-deferred-hooks": run_deferred_hooks}
|
"run-deferred-hooks": run_deferred_hooks,
|
||||||
|
"show-routers": get_routers,
|
||||||
|
"show-dhcp-networks": get_dhcp_networks,
|
||||||
|
"show-loadbalancers": get_lbaasv2_lb,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def main(args):
|
def main(args):
|
||||||
|
1
actions/show-dhcp-networks
Symbolic link
1
actions/show-dhcp-networks
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
actions.py
|
1
actions/show-loadbalancers
Symbolic link
1
actions/show-loadbalancers
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
actions.py
|
1
actions/show-routers
Symbolic link
1
actions/show-routers
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
actions.py
|
@ -115,6 +115,7 @@ def install():
|
|||||||
status_set('maintenance', 'Installing apt packages')
|
status_set('maintenance', 'Installing apt packages')
|
||||||
apt_update(fatal=True)
|
apt_update(fatal=True)
|
||||||
apt_install('python-six', fatal=True) # Force upgrade
|
apt_install('python-six', fatal=True) # Force upgrade
|
||||||
|
apt_install('python3-neutronclient')
|
||||||
if valid_plugin():
|
if valid_plugin():
|
||||||
apt_install(filter_installed_packages(get_early_packages()),
|
apt_install(filter_installed_packages(get_early_packages()),
|
||||||
fatal=True)
|
fatal=True)
|
||||||
|
@ -68,6 +68,7 @@ configure_options:
|
|||||||
tests:
|
tests:
|
||||||
- zaza.openstack.charm_tests.neutron.tests.NeutronGatewayDeferredRestartTest
|
- zaza.openstack.charm_tests.neutron.tests.NeutronGatewayDeferredRestartTest
|
||||||
- zaza.openstack.charm_tests.neutron.tests.NeutronGatewayTest
|
- zaza.openstack.charm_tests.neutron.tests.NeutronGatewayTest
|
||||||
|
- zaza.openstack.charm_tests.neutron.tests.NeutronGatewayShowActionsTest
|
||||||
- zaza.openstack.charm_tests.neutron.tests.SecurityTest
|
- zaza.openstack.charm_tests.neutron.tests.SecurityTest
|
||||||
- zaza.openstack.charm_tests.neutron.tests.NeutronNetworkingTest
|
- zaza.openstack.charm_tests.neutron.tests.NeutronNetworkingTest
|
||||||
- zaza.openstack.charm_tests.neutron.tests.NeutronOvsVsctlTest
|
- zaza.openstack.charm_tests.neutron.tests.NeutronOvsVsctlTest
|
||||||
@ -75,6 +76,7 @@ tests:
|
|||||||
- migrate-ovn:
|
- migrate-ovn:
|
||||||
- zaza.openstack.charm_tests.neutron.tests.NeutronGatewayDeferredRestartTest
|
- zaza.openstack.charm_tests.neutron.tests.NeutronGatewayDeferredRestartTest
|
||||||
- zaza.openstack.charm_tests.neutron.tests.NeutronGatewayTest
|
- zaza.openstack.charm_tests.neutron.tests.NeutronGatewayTest
|
||||||
|
- zaza.openstack.charm_tests.neutron.tests.NeutronGatewayShowActionsTest
|
||||||
- zaza.openstack.charm_tests.neutron.tests.SecurityTest
|
- zaza.openstack.charm_tests.neutron.tests.SecurityTest
|
||||||
- zaza.openstack.charm_tests.neutron.tests.NeutronOvsVsctlTest
|
- zaza.openstack.charm_tests.neutron.tests.NeutronOvsVsctlTest
|
||||||
- zaza.openstack.charm_tests.neutron.tests.NeutronBridgePortMappingTest
|
- zaza.openstack.charm_tests.neutron.tests.NeutronBridgePortMappingTest
|
||||||
|
@ -46,6 +46,72 @@ class ResumeTestCase(CharmTestCase):
|
|||||||
self.resume_unit_helper.assert_called_once_with('test-config')
|
self.resume_unit_helper.assert_called_once_with('test-config')
|
||||||
|
|
||||||
|
|
||||||
|
class GetStatusTestCase(CharmTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(GetStatusTestCase, self).setUp(actions, [])
|
||||||
|
|
||||||
|
def test_clean_resource_list(self):
|
||||||
|
data = [{"id": 1, "x": "data", "z": "data"},
|
||||||
|
{"id": 2, "y": "data", "z": "data"}]
|
||||||
|
|
||||||
|
clean_data = actions.clean_resource_list(data)
|
||||||
|
for resource in clean_data:
|
||||||
|
self.assertTrue("id" in resource)
|
||||||
|
self.assertTrue("x" not in resource)
|
||||||
|
self.assertTrue("y" not in resource)
|
||||||
|
self.assertTrue("z" not in resource)
|
||||||
|
|
||||||
|
# test allowed keys
|
||||||
|
clean_data = actions.clean_resource_list(data,
|
||||||
|
allowed_keys=["id", "z"])
|
||||||
|
for resource in clean_data:
|
||||||
|
self.assertTrue("id" in resource)
|
||||||
|
self.assertTrue("x" not in resource)
|
||||||
|
self.assertTrue("y" not in resource)
|
||||||
|
self.assertTrue("z" in resource)
|
||||||
|
|
||||||
|
def test_get_resource_list_on_agents(self):
|
||||||
|
list_function = MagicMock()
|
||||||
|
agent_list = [{"id": 1}, {"id": 2}]
|
||||||
|
list_results = [{"results": ["a", "b"]}, {"results": ["c"], "x": [""]}]
|
||||||
|
|
||||||
|
expected_resource_length = 0
|
||||||
|
for r in list_results:
|
||||||
|
expected_resource_length += len(r.get("results", []))
|
||||||
|
|
||||||
|
list_function.side_effect = list_results
|
||||||
|
resource_list = actions.get_resource_list_on_agents(agent_list,
|
||||||
|
list_function,
|
||||||
|
"results")
|
||||||
|
assert list_function.call_count > 0
|
||||||
|
self.assertEqual(len(resource_list), expected_resource_length)
|
||||||
|
|
||||||
|
@mock.patch("actions.function_set")
|
||||||
|
@mock.patch("actions.get_resource_list_on_agents")
|
||||||
|
@mock.patch("actions.get_neutron")
|
||||||
|
def test_action_get_status(self, mock_get_neutron,
|
||||||
|
mock_get_resource_list_on_agents,
|
||||||
|
mock_function_set):
|
||||||
|
data = [{"id": 1, "x": "data", "z": "data"},
|
||||||
|
{"id": 2, "y": "data", "z": "data"}]
|
||||||
|
mock_get_resource_list_on_agents.return_value = data
|
||||||
|
|
||||||
|
clean_data = actions.clean_resource_list(data)
|
||||||
|
yaml_clean_data = actions.format_status_output(clean_data)
|
||||||
|
|
||||||
|
actions.get_routers(None)
|
||||||
|
mock_function_set.assert_called_with({"router-list": yaml_clean_data})
|
||||||
|
|
||||||
|
actions.get_dhcp_networks(None)
|
||||||
|
mock_function_set.assert_called_with({"dhcp-networks":
|
||||||
|
yaml_clean_data})
|
||||||
|
|
||||||
|
actions.get_lbaasv2_lb(None)
|
||||||
|
mock_function_set.assert_called_with({"load-balancers":
|
||||||
|
yaml_clean_data})
|
||||||
|
|
||||||
|
|
||||||
class MainTestCase(CharmTestCase):
|
class MainTestCase(CharmTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user