From 83aef9c8a044da1faef4825f52aa5e0428ea5f7f Mon Sep 17 00:00:00 2001 From: Michael Gugino Date: Thu, 12 May 2016 16:08:35 -0400 Subject: [PATCH] Implement network agents This commit adds api support for network agents. Change-Id: I9755637f76787d5fac8ff295ae273b308fcb98d0 Implements: blueprint implement-network-agents --- doc/source/users/guides/network.rst | 15 +++- doc/source/users/resources/network/index.rst | 1 + .../users/resources/network/v2/agent.rst | 12 +++ examples/network/list.py | 7 ++ openstack/network/v2/_proxy.py | 54 ++++++++++++++ openstack/network/v2/agent.py | 61 ++++++++++++++++ .../tests/functional/network/v2/test_agent.py | 51 +++++++++++++ openstack/tests/unit/network/v2/test_agent.py | 73 +++++++++++++++++++ openstack/tests/unit/network/v2/test_proxy.py | 14 ++++ 9 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 doc/source/users/resources/network/v2/agent.rst create mode 100644 openstack/network/v2/agent.py create mode 100644 openstack/tests/functional/network/v2/test_agent.py create mode 100644 openstack/tests/unit/network/v2/test_agent.py diff --git a/doc/source/users/guides/network.rst b/doc/source/users/guides/network.rst index 6cc56c37e..e0701bc59 100644 --- a/doc/source/users/guides/network.rst +++ b/doc/source/users/guides/network.rst @@ -76,6 +76,19 @@ provide external network access for servers on project networks. Full example: `network resource list`_ +List Network Agents +------------ + +A **network agent** is a plugin that handles various tasks used to +implement virtual networks. These agents include neutron-dhcp-agent, +neutron-l3-agent, neutron-metering-agent, and neutron-lbaas-agent, +among others. + +.. literalinclude:: ../examples/network/list.py + :pyobject: list_network_agents + +Full example: `network resource list`_ + Create Network -------------- @@ -100,4 +113,4 @@ Full example: `network resource delete`_ .. _network resource create: http://git.openstack.org/cgit/openstack/python-openstacksdk/tree/examples/network/create.py .. _network resource delete: http://git.openstack.org/cgit/openstack/python-openstacksdk/tree/examples/network/delete.py -.. _network resource list: http://git.openstack.org/cgit/openstack/python-openstacksdk/tree/examples/network/list.py \ No newline at end of file +.. _network resource list: http://git.openstack.org/cgit/openstack/python-openstacksdk/tree/examples/network/list.py diff --git a/doc/source/users/resources/network/index.rst b/doc/source/users/resources/network/index.rst index cd4c09282..feaf29016 100644 --- a/doc/source/users/resources/network/index.rst +++ b/doc/source/users/resources/network/index.rst @@ -5,6 +5,7 @@ Network Resources :maxdepth: 1 v2/address_scope + v2/agent v2/availability_zone v2/extension v2/floating_ip diff --git a/doc/source/users/resources/network/v2/agent.rst b/doc/source/users/resources/network/v2/agent.rst new file mode 100644 index 000000000..f83856a62 --- /dev/null +++ b/doc/source/users/resources/network/v2/agent.rst @@ -0,0 +1,12 @@ +openstack.network.v2.network +============================ + +.. automodule:: openstack.network.v2.agent + +The Agent Class +----------------- + +The ``Agent`` class inherits from :class:`~openstack.resource.Resource`. + +.. autoclass:: openstack.network.v2.agent.Agent + :members: diff --git a/examples/network/list.py b/examples/network/list.py index 9ab952984..ff85e1c27 100644 --- a/examples/network/list.py +++ b/examples/network/list.py @@ -50,3 +50,10 @@ def list_routers(conn): for router in conn.network.routers(): print(router) + + +def list_network_agents(conn): + print("List Network Agents:") + + for agent in conn.network.agents(): + print(agent) diff --git a/openstack/network/v2/_proxy.py b/openstack/network/v2/_proxy.py index 9e810efda..53761c892 100644 --- a/openstack/network/v2/_proxy.py +++ b/openstack/network/v2/_proxy.py @@ -11,6 +11,7 @@ # under the License. from openstack.network.v2 import address_scope as _address_scope +from openstack.network.v2 import agent as _agent from openstack.network.v2 import availability_zone from openstack.network.v2 import extension from openstack.network.v2 import floating_ip as _floating_ip @@ -122,6 +123,59 @@ class Proxy(proxy.BaseProxy): address_scope, **attrs) + def agents(self, **query): + """Return a generator of network agents + + :param kwargs \*\*query: Optional query parameters to be sent to limit + the resources being returned. + + :returns: A generator of agents + :rtype: :class:`~openstack.network.v2.agent.Agent` + """ + return self._list(_agent.Agent, paginated=False, **query) + + def delete_agent(self, agent, ignore_missing=True): + """Delete a network agent + + :param agent: The value can be the ID of a agent or a + :class:`~openstack.network.v2.agent.Agent` instance. + :param bool ignore_missing: When set to ``False`` + :class:`~openstack.exceptions.ResourceNotFound` will be + raised when the agent does not exist. + When set to ``True``, no exception will be set when + attempting to delete a nonexistent agent. + + :returns: ``None`` + """ + self._delete(_agent.Agent, agent, + ignore_missing=ignore_missing) + + def get_agent(self, agent, ignore_missing=True): + """Get a single network agent + + :param agent: The value can be the ID of a agent or a + :class:`~openstack.network.v2.agent.Agent` instance. + + :returns: One :class:`~openstack.network.v2.agent.Agent` + :rtype: :class:`~openstack.network.v2.agent.Agent` + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + """ + return self._get(_agent.Agent, agent) + + def update_agent(self, agent, **attrs): + """Update a network agent + + :param agent: The value can be the ID of a agent or a + :class:`~openstack.network.v2.agent.Agent` instance. + :attrs kwargs: The attributes to update on the agent represented + by ``value``. + + :returns: One :class:`~openstack.network.v2.agent.Agent` + :rtype: :class:`~openstack.network.v2.agent.Agent` + """ + return self._update(_agent.Agent, agent, **attrs) + def availability_zones(self): """Return a generator of availability zones diff --git a/openstack/network/v2/agent.py b/openstack/network/v2/agent.py new file mode 100644 index 000000000..95ade0cad --- /dev/null +++ b/openstack/network/v2/agent.py @@ -0,0 +1,61 @@ +# 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 openstack import format +from openstack.network import network_service +from openstack import resource + + +class Agent(resource.Resource): + resource_key = 'agent' + resources_key = 'agents' + base_path = '/agents' + service = network_service.NetworkService() + + # capabilities + allow_create = False + allow_retrieve = True + allow_update = True + allow_delete = True + allow_list = True + + # Properties + #: The type of network agent. + agent_type = resource.prop('agent_type') + #: Availability zone for the network agent. + availability_zone = resource.prop('availability_zone') + #: The name of the network agent's application binary. + binary = resource.prop('binary') + #: Network agent configuration data specific to the agent_type. + configuration = resource.prop('configurations') + #: Timestamp when the network agent was created. + #: *Type: datetime object parsed from ISO 8601 formatted string* + created_at = resource.prop('created_at', type=format.ISO8601) + #: The network agent description. + description = resource.prop('description') + #: Timestamp when the network agent's heartbeat was last seen. + #: *Type: datetime object parsed from ISO 8601 formatted string* + last_heartbeat_at = resource.prop('heartbeat_timestamp', + type=format.ISO8601) + #: The host the agent is running on. + host = resource.prop('host') + #: The administrative state of the network agent, which is up + #: ``True`` or down ``False``. *Type: bool* + is_admin_state_up = resource.prop('admin_state_up', type=bool) + #: Whether or not the network agent is alive. + #: *Type: bool* + is_alive = resource.prop('alive', type=bool) + #: Timestamp when the network agent was last started. + #: *Type: datetime object parsed from ISO 8601 formatted string* + started_at = resource.prop('started_at', type=format.ISO8601) + #: The messaging queue topic the network agent subscribes to. + topic = resource.prop('topic') diff --git a/openstack/tests/functional/network/v2/test_agent.py b/openstack/tests/functional/network/v2/test_agent.py new file mode 100644 index 000000000..2cc96525b --- /dev/null +++ b/openstack/tests/functional/network/v2/test_agent.py @@ -0,0 +1,51 @@ +# 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 uuid + +from openstack.network.v2 import agent +from openstack.tests.functional import base + + +class TestAgent(base.BaseFunctionalTest): + + AGENT = None + DESC = 'test descrition' + + def validate_uuid(self, s): + try: + uuid.UUID(s) + except Exception: + return False + return True + + @classmethod + def setUpClass(cls): + super(TestAgent, cls).setUpClass() + agent_list = list(cls.conn.network.agents()) + cls.AGENT = agent_list[0] + assert isinstance(cls.AGENT, agent.Agent) + + def test_list(self): + agent_list = list(self.conn.network.agents()) + self.AGENT = agent_list[0] + assert isinstance(self.AGENT, agent.Agent) + self.assertTrue(self.validate_uuid(self.AGENT.id)) + + def test_get(self): + sot = self.conn.network.get_agent(self.AGENT.id) + self.assertEqual(self.AGENT.id, sot.id) + + def test_update(self): + sot = self.conn.network.update_agent(self.AGENT.id, + description=self.DESC) + self.assertEqual(self.DESC, sot.description) diff --git a/openstack/tests/unit/network/v2/test_agent.py b/openstack/tests/unit/network/v2/test_agent.py new file mode 100644 index 000000000..28c582d75 --- /dev/null +++ b/openstack/tests/unit/network/v2/test_agent.py @@ -0,0 +1,73 @@ +# 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 datetime + +import testtools + +from openstack.network.v2 import agent + +IDENTIFIER = 'IDENTIFIER' +EXAMPLE = { + 'admin_state_up': True, + 'agent_type': 'Test Agent', + 'alive': True, + 'availability_zone': 'az1', + 'binary': 'test-binary', + 'configurations': {'attr1': 'value1', 'attr2': 'value2'}, + 'created_at': '2016-03-09T12:14:57.233772', + 'description': 'test description', + 'heartbeat_timestamp': '2016-08-09T12:14:57.233772', + 'host': 'test-host', + 'id': IDENTIFIER, + 'started_at': '2016-07-09T12:14:57.233772', + 'topic': 'test-topic' +} + + +class TestAgent(testtools.TestCase): + + def test_basic(self): + sot = agent.Agent() + self.assertEqual('agent', sot.resource_key) + self.assertEqual('agents', sot.resources_key) + self.assertEqual('/agents', sot.base_path) + self.assertEqual('network', sot.service.service_type) + self.assertFalse(sot.allow_create) + self.assertTrue(sot.allow_retrieve) + self.assertTrue(sot.allow_update) + self.assertTrue(sot.allow_delete) + self.assertTrue(sot.allow_list) + + def test_make_it(self): + sot = agent.Agent(EXAMPLE) + self.assertTrue(sot.is_admin_state_up) + self.assertEqual(EXAMPLE['agent_type'], sot.agent_type) + self.assertTrue(sot.is_alive) + self.assertEqual(EXAMPLE['availability_zone'], + sot.availability_zone) + self.assertEqual(EXAMPLE['binary'], sot.binary) + self.assertEqual(EXAMPLE['configurations'], sot.configuration) + dt = datetime.datetime(2016, 3, 9, 12, 14, 57, 233772).replace( + tzinfo=None) + self.assertEqual(dt, sot.created_at.replace(tzinfo=None)) + self.assertEqual(EXAMPLE['description'], sot.description) + dt = datetime.datetime(2016, 8, 9, 12, 14, 57, 233772).replace( + tzinfo=None) + self.assertEqual(dt, + sot.last_heartbeat_at.replace(tzinfo=None)) + self.assertEqual(EXAMPLE['host'], sot.host) + self.assertEqual(EXAMPLE['id'], sot.id) + dt = datetime.datetime(2016, 7, 9, 12, 14, 57, 233772).replace( + tzinfo=None) + self.assertEqual(dt, sot.started_at.replace(tzinfo=None)) + self.assertEqual(EXAMPLE['topic'], sot.topic) diff --git a/openstack/tests/unit/network/v2/test_proxy.py b/openstack/tests/unit/network/v2/test_proxy.py index cf848120d..34660eeb5 100644 --- a/openstack/tests/unit/network/v2/test_proxy.py +++ b/openstack/tests/unit/network/v2/test_proxy.py @@ -14,6 +14,7 @@ import mock from openstack.network.v2 import _proxy from openstack.network.v2 import address_scope +from openstack.network.v2 import agent from openstack.network.v2 import availability_zone from openstack.network.v2 import extension from openstack.network.v2 import floating_ip @@ -73,6 +74,19 @@ class TestNetworkProxy(test_proxy_base.TestProxyBase): self.verify_update(self.proxy.update_address_scope, address_scope.AddressScope) + def test_agent_delete(self): + self.verify_delete(self.proxy.delete_agent, agent.Agent, True) + + def test_agent_get(self): + self.verify_get(self.proxy.get_agent, agent.Agent) + + def test_agents(self): + self.verify_list(self.proxy.agents, agent.Agent, + paginated=False) + + def test_agent_update(self): + self.verify_update(self.proxy.update_agent, agent.Agent) + def test_availability_zones(self): self.verify_list_no_kwargs(self.proxy.availability_zones, availability_zone.AvailabilityZone,