diff --git a/doc/source/command-objects/network-agent.rst b/doc/source/command-objects/network-agent.rst index 947e07cf6c..d764f3de1c 100644 --- a/doc/source/command-objects/network-agent.rst +++ b/doc/source/command-objects/network-agent.rst @@ -10,6 +10,31 @@ agent is "True". Network v2 +network agent add network +------------------------- + +Add network to an agent + +.. program:: network agent add network +.. code:: bash + + openstack network agent add network + [--dhcp] + + + +.. describe:: --dhcp + + Add a network to DHCP agent. + +.. describe:: + + Agent to which a network is added. (ID only) + +.. describe:: + + Network to be added to an agent. (ID or name) + network agent delete -------------------- @@ -37,6 +62,7 @@ List network agents openstack network agent list [--agent-type ] [--host ] + [--network ] .. option:: --agent-type @@ -49,6 +75,10 @@ List network agents List only agents running on the specified host +.. option:: --network + + List agents hosting a network. (ID or name) + network agent set ----------------- @@ -94,3 +124,28 @@ Display network agent details .. describe:: Network agent to display (ID only) + +network agent remove network +---------------------------- + +Remove network from an agent + +.. program:: network agent remove network +.. code:: bash + + openstack network agent remove network + [--dhcp] + + + +.. describe:: --dhcp + + Remove network from DHCP agent. + +.. describe:: + + Agent to which a network is removed. (ID only) + +.. describe:: + + Network to be removed from an agent. (ID or name) diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index dc597443ce..9162dbff01 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -203,6 +203,7 @@ List networks [--provider-network-type ] [--provider-physical-network ] [--provider-segment ] + [--agent ] .. option:: --external @@ -290,6 +291,10 @@ List networks *Network version 2 only* +.. option:: --agent + + List networks hosted by agent (ID only) + network set ----------- diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 3aa8ec8442..3e0bb7764b 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -60,12 +60,10 @@ def _get_network_columns(item): def _get_columns(item): - columns = list(item.keys()) - if 'tenant_id' in columns: - columns.remove('tenant_id') - if 'project_id' not in columns: - columns.append('project_id') - return tuple(sorted(columns)) + column_map = { + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_attrs(client_manager, parsed_args): @@ -305,9 +303,9 @@ class CreateNetwork(common.NetworkAndComputeShowOne): def take_action_compute(self, client, parsed_args): attrs = _get_attrs_compute(self.app.client_manager, parsed_args) obj = client.networks.create(**attrs) - columns = _get_columns(obj._info) + display_columns, columns = _get_columns(obj._info) data = utils.get_dict_properties(obj._info, columns) - return (columns, data) + return (display_columns, data) class DeleteNetwork(common.NetworkAndComputeDelete): @@ -420,7 +418,11 @@ class ListNetwork(common.NetworkAndComputeLister): help=_("List networks according to VLAN ID for VLAN networks " "or Tunnel ID for GENEVE/GRE/VXLAN networks") ) - + parser.add_argument( + '--agent', + metavar='', + dest='agent_id', + help=_('List networks hosted by agent (ID only)')) return parser def take_action_network(self, client, parsed_args): @@ -450,6 +452,26 @@ class ListNetwork(common.NetworkAndComputeLister): 'Router Type', 'Availability Zones', ) + elif parsed_args.agent_id: + columns = ( + 'id', + 'name', + 'subnet_ids' + ) + column_headers = ( + 'ID', + 'Name', + 'Subnets', + ) + client = self.app.client_manager.network + dhcp_agent = client.get_agent(parsed_args.agent_id) + data = client.dhcp_agent_hosting_networks(dhcp_agent) + + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters=_formatters, + ) for s in data)) else: columns = ( 'id', @@ -665,6 +687,6 @@ class ShowNetwork(common.NetworkAndComputeShowOne): client.networks, parsed_args.network, ) - columns = _get_columns(obj._info) + display_columns, columns = _get_columns(obj._info) data = utils.get_dict_properties(obj._info, columns) - return (columns, data) + return (display_columns, data) diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index d429fa0830..c23d3e8bb6 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -29,7 +29,6 @@ LOG = logging.getLogger(__name__) def _format_admin_state(state): return 'UP' if state else 'DOWN' - _formatters = { 'admin_state_up': _format_admin_state, 'is_admin_state_up': _format_admin_state, @@ -45,6 +44,40 @@ def _get_network_columns(item): return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) +class AddNetworkToAgent(command.Command): + _description = _("Add network to an agent") + + def get_parser(self, prog_name): + parser = super(AddNetworkToAgent, self).get_parser(prog_name) + parser.add_argument( + '--dhcp', + action='store_true', + help=_('Add network to a DHCP agent')) + parser.add_argument( + 'agent_id', + metavar='', + help=_('Agent to which a network is added. (ID only)')) + parser.add_argument( + 'network', + metavar='', + help=_('Network to be added to an agent. (ID or name)')) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + agent = client.get_agent(parsed_args.agent_id) + if parsed_args.dhcp: + network = client.find_network( + parsed_args.network, ignore_missing=False) + try: + client.add_dhcp_agent_to_network(agent, network) + except Exception: + msg = 'Failed to add {} to {}'.format( + network.name, agent.agent_type) + exceptions.CommandError(msg) + + class DeleteNetworkAgent(command.Command): _description = _("Delete network agent(s)") @@ -101,6 +134,11 @@ class ListNetworkAgent(command.Lister): metavar='', help=_("List only agents running on the specified host") ) + parser.add_argument( + '--network', + metavar='', + help=_('List agents hosting a network (name or ID)') + ) return parser def take_action(self, parsed_args): @@ -138,16 +176,72 @@ class ListNetworkAgent(command.Lister): } filters = {} - if parsed_args.agent_type is not None: - filters['agent_type'] = key_value[parsed_args.agent_type] - if parsed_args.host is not None: - filters['host'] = parsed_args.host + if parsed_args.network is not None: + columns = ( + 'id', + 'host', + 'is_admin_state_up', + 'is_alive', + ) + column_headers = ( + 'ID', + 'Host', + 'Admin State Up', + 'Alive', + ) + network = client.find_network( + parsed_args.network, ignore_missing=False) + data = client.network_hosting_dhcp_agents(network) - data = client.agents(**filters) - return (column_headers, - (utils.get_item_properties( - s, columns, formatters=_formatters, - ) for s in data)) + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters=_formatters, + ) for s in data)) + else: + if parsed_args.agent_type is not None: + filters['agent_type'] = key_value[parsed_args.agent_type] + if parsed_args.host is not None: + filters['host'] = parsed_args.host + + data = client.agents(**filters) + return (column_headers, + (utils.get_item_properties( + s, columns, formatters=_formatters, + ) for s in data)) + + +class RemoveNetworkFromAgent(command.Command): + _description = _("Remove network from an agent.") + + def get_parser(self, prog_name): + parser = super(RemoveNetworkFromAgent, self).get_parser(prog_name) + parser.add_argument( + '--dhcp', + action='store_true', + help=_('Remove network from DHCP agent')) + parser.add_argument( + 'agent_id', + metavar='', + help=_('Agent to which a network is removed. (ID only)')) + parser.add_argument( + 'network', + metavar='', + help=_('Network to be removed from an agent. (ID or name)')) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + agent = client.get_agent(parsed_args.agent_id) + if parsed_args.dhcp: + network = client.find_network( + parsed_args.network, ignore_missing=False) + try: + client.remove_dhcp_agent_from_network(agent, network) + except Exception: + msg = 'Failed to remove {} to {}'.format( + network.name, agent.agent_type) + exceptions.CommandError(msg) # TODO(huanxuan): Use the SDK resource mapped attribute names once the diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py index 126365581a..0e10bfce1a 100644 --- a/openstackclient/tests/functional/network/v2/test_network.py +++ b/openstackclient/tests/functional/network/v2/test_network.py @@ -238,6 +238,49 @@ class NetworkTests(base.TestCase): self.assertIn(name1, col_name) self.assertNotIn(name2, col_name) + def test_network_dhcp_agent(self): + name1 = uuid.uuid4().hex + cmd_output1 = json.loads(self.openstack( + 'network create -f json ' + + '--description aaaa ' + + name1 + )) + + self.addCleanup(self.openstack, 'network delete ' + name1) + + # Get network ID + network_id = cmd_output1['id'] + + # Get DHCP Agent ID + cmd_output2 = json.loads(self.openstack( + 'network agent list -f json --agent-type dhcp' + )) + agent_id = cmd_output2[0]['ID'] + + # Add Agent to Network + self.openstack( + 'network agent add network --dhcp ' + + agent_id + ' ' + network_id + ) + + # Test network list --agent + cmd_output3 = json.loads(self.openstack( + 'network list -f json --agent ' + agent_id + )) + + # Cleanup + # Remove Agent from Network + self.openstack( + 'network agent remove network --dhcp ' + + agent_id + ' ' + network_id + ) + + # Assert + col_name = [x["ID"] for x in cmd_output3] + self.assertIn( + network_id, col_name + ) + def test_network_set(self): """Tests create options, set, show, delete""" name = uuid.uuid4().hex diff --git a/openstackclient/tests/functional/network/v2/test_network_agent.py b/openstackclient/tests/functional/network/v2/test_network_agent.py index dd6112e72e..6da721d1e7 100644 --- a/openstackclient/tests/functional/network/v2/test_network_agent.py +++ b/openstackclient/tests/functional/network/v2/test_network_agent.py @@ -10,6 +10,9 @@ # License for the specific language governing permissions and limitations # under the License. +import json +import uuid + from openstackclient.tests.functional import base @@ -39,3 +42,52 @@ class NetworkAgentTests(base.TestCase): self.openstack('network agent set --enable ' + self.IDs[0]) raw_output = self.openstack('network agent show ' + self.IDs[0] + opts) self.assertEqual("UP\n", raw_output) + + +class NetworkAgentListTests(base.TestCase): + """Functional test for network agent list --network. """ + + def test_network_dhcp_agent_list(self): + """Test network agent list""" + + name1 = uuid.uuid4().hex + cmd_output1 = json.loads(self.openstack( + 'network create -f json ' + + '--description aaaa ' + + name1 + )) + + self.addCleanup(self.openstack, 'network delete ' + name1) + + # Get network ID + network_id = cmd_output1['id'] + + # Get DHCP Agent ID + cmd_output2 = json.loads(self.openstack( + 'network agent list -f json --agent-type dhcp' + )) + agent_id = cmd_output2[0]['ID'] + + # Add Agent to Network + self.openstack( + 'network agent add network --dhcp ' + + agent_id + ' ' + network_id + ) + + # Test network agent list --network + cmd_output3 = json.loads(self.openstack( + 'network agent list -f json --network ' + network_id + )) + + # Cleanup + # Remove Agent from Network + self.openstack( + 'network agent remove network --dhcp ' + + agent_id + ' ' + network_id + ) + + # Assert + col_name = [x["ID"] for x in cmd_output3] + self.assertIn( + agent_id, col_name + ) diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index b405bef9a8..bc1279ecbe 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -491,6 +491,13 @@ class TestListNetwork(TestNetwork): self.network.networks = mock.Mock(return_value=self._network) + self._agent = \ + network_fakes.FakeNetworkAgent.create_one_network_agent() + self.network.get_agent = mock.Mock(return_value=self._agent) + + self.network.dhcp_agent_hosting_networks = mock.Mock( + return_value=self._network) + def test_network_list_no_options(self): arglist = [] verifylist = [ @@ -765,6 +772,25 @@ class TestListNetwork(TestNetwork): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_network_list_dhcp_agent(self): + arglist = [ + '--agent', self._agent.id + ] + verifylist = [ + ('agent_id', self._agent.id), + ] + + attrs = {self._agent, } + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.dhcp_agent_hosting_networks.assert_called_once_with( + *attrs) + + self.assertEqual(self.columns, columns) + self.assertEqual(list(data), list(self.data)) + class TestSetNetwork(TestNetwork): diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py index 2fc0c04328..0d741e06a9 100644 --- a/openstackclient/tests/unit/network/v2/test_network_agent.py +++ b/openstackclient/tests/unit/network/v2/test_network_agent.py @@ -31,6 +31,48 @@ class TestNetworkAgent(network_fakes.TestNetworkV2): self.network = self.app.client_manager.network +class TestAddNetworkToAgent(TestNetworkAgent): + + net = network_fakes.FakeNetwork.create_one_network() + agent = network_fakes.FakeNetworkAgent.create_one_network_agent() + + def setUp(self): + super(TestAddNetworkToAgent, self).setUp() + + self.network.get_agent = mock.Mock(return_value=self.agent) + self.network.find_network = mock.Mock(return_value=self.net) + self.network.name = self.network.find_network.name + self.network.add_dhcp_agent_to_network = mock.Mock() + self.cmd = network_agent.AddNetworkToAgent( + self.app, self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_add_network_to_dhcp_agent(self): + arglist = [ + '--dhcp', + self.agent.id, + self.net.id + ] + verifylist = [ + ('dhcp', True), + ('agent_id', self.agent.id), + ('network', self.net.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.network.add_dhcp_agent_to_network.assert_called_once_with( + self.agent, self.net) + + class TestDeleteNetworkAgent(TestNetworkAgent): network_agents = ( @@ -66,7 +108,6 @@ class TestDeleteNetworkAgent(TestNetworkAgent): def test_multi_network_agents_delete(self): arglist = [] - verifylist = [] for n in self.network_agents: arglist.append(n.id) @@ -141,11 +182,37 @@ class TestListNetworkAgent(TestNetworkAgent): agent.binary, )) + network_agent_columns = ( + 'ID', + 'Host', + 'Admin State Up', + 'Alive', + ) + + network_agent_data = [] + + for agent in network_agents: + network_agent_data.append(( + agent.id, + agent.host, + network_agent._format_admin_state(agent.admin_state_up), + agent.alive, + )) + def setUp(self): super(TestListNetworkAgent, self).setUp() self.network.agents = mock.Mock( return_value=self.network_agents) + _testagent = \ + network_fakes.FakeNetworkAgent.create_one_network_agent() + self.network.get_agent = mock.Mock(return_value=_testagent) + + self._testnetwork = network_fakes.FakeNetwork.create_one_network() + self.network.find_network = mock.Mock(return_value=self._testnetwork) + self.network.network_hosting_dhcp_agents = mock.Mock( + return_value=self.network_agents) + # Get the command object to test self.cmd = network_agent.ListNetworkAgent(self.app, self.namespace) @@ -194,6 +261,66 @@ class TestListNetworkAgent(TestNetworkAgent): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_network_agents_list_networks(self): + arglist = [ + '--network', self._testnetwork.id, + ] + verifylist = [ + ('network', self._testnetwork.id), + ] + + attrs = {self._testnetwork, } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.network_hosting_dhcp_agents.assert_called_once_with( + *attrs) + self.assertEqual(self.network_agent_columns, columns) + self.assertEqual(list(self.network_agent_data), list(data)) + + +class TestRemoveNetworkFromAgent(TestNetworkAgent): + + net = network_fakes.FakeNetwork.create_one_network() + agent = network_fakes.FakeNetworkAgent.create_one_network_agent() + + def setUp(self): + super(TestRemoveNetworkFromAgent, self).setUp() + + self.network.get_agent = mock.Mock(return_value=self.agent) + self.network.find_network = mock.Mock(return_value=self.net) + self.network.name = self.network.find_network.name + self.network.remove_dhcp_agent_from_network = mock.Mock() + self.cmd = network_agent.RemoveNetworkFromAgent( + self.app, self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_network_from_dhcp_agent(self): + arglist = [ + '--dhcp', + self.agent.id, + self.net.id + ] + verifylist = [ + ('dhcp', True), + ('agent_id', self.agent.id), + ('network', self.net.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.network.remove_dhcp_agent_from_network.assert_called_once_with( + self.agent, self.net) + # TODO(huanxuan): Also update by the new attribute name # "is_admin_state_up" after sdk 0.9.12 diff --git a/releasenotes/notes/bp-network-dhcp-adv-commands-e61bf8757f46dc93.yaml b/releasenotes/notes/bp-network-dhcp-adv-commands-e61bf8757f46dc93.yaml new file mode 100644 index 0000000000..ce3ab644f4 --- /dev/null +++ b/releasenotes/notes/bp-network-dhcp-adv-commands-e61bf8757f46dc93.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add network dhcp-agent related commands ``network agent add network``, + ``network agent remove network``, ``network agent list --network`` and + ``network list --agent`` for adding/removing network to dhcp agent. + [Blueprint :oscbp:`network-dhcp-adv-commands`] diff --git a/setup.cfg b/setup.cfg index e18aa5c1e7..7ebe624a3f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -355,8 +355,10 @@ openstack.network.v2 = ip_floating_pool_list = openstackclient.network.v2.floating_ip_pool:ListIPFloatingPool + network_agent_add_network = openstackclient.network.v2.network_agent:AddNetworkToAgent network_agent_delete = openstackclient.network.v2.network_agent:DeleteNetworkAgent network_agent_list = openstackclient.network.v2.network_agent:ListNetworkAgent + network_agent_remove_network = openstackclient.network.v2.network_agent:RemoveNetworkFromAgent network_agent_set = openstackclient.network.v2.network_agent:SetNetworkAgent network_agent_show = openstackclient.network.v2.network_agent:ShowNetworkAgent