From 35d30bcf3d4af2571ee63f55af1149326b94e3f0 Mon Sep 17 00:00:00 2001 From: Sayali Naval Date: Mon, 19 Oct 2020 13:21:58 -0700 Subject: [PATCH] Add APIC fields in Openstack CLI Add APIC fields for below resources in Openstack CLI: Network, Subnet, Address Scope, Router Change-Id: I9a7fc4c098f0e58e008a8eeec29f84094ebafa7f --- gbpclient/gbp/v2_0/address_scope.py | 89 +++++++ gbpclient/gbp/v2_0/network.py | 265 +++++++++++++++++++++ gbpclient/gbp/v2_0/router.py | 115 +++++++++ gbpclient/gbp/v2_0/subnet.py | 127 ++++++++++ gbpclient/tests/unit/test_address_scope.py | 110 +++++++++ gbpclient/tests/unit/test_cli20.py | 24 ++ gbpclient/tests/unit/test_network.py | 241 +++++++++++++++++++ gbpclient/tests/unit/test_router.py | 137 +++++++++++ gbpclient/tests/unit/test_subnet.py | 154 ++++++++++++ setup.cfg | 36 +++ 10 files changed, 1298 insertions(+) create mode 100644 gbpclient/gbp/v2_0/address_scope.py create mode 100644 gbpclient/gbp/v2_0/network.py create mode 100644 gbpclient/gbp/v2_0/router.py create mode 100644 gbpclient/gbp/v2_0/subnet.py create mode 100644 gbpclient/tests/unit/test_address_scope.py create mode 100644 gbpclient/tests/unit/test_network.py create mode 100644 gbpclient/tests/unit/test_router.py create mode 100644 gbpclient/tests/unit/test_subnet.py diff --git a/gbpclient/gbp/v2_0/address_scope.py b/gbpclient/gbp/v2_0/address_scope.py new file mode 100644 index 0000000..125e42a --- /dev/null +++ b/gbpclient/gbp/v2_0/address_scope.py @@ -0,0 +1,89 @@ +# 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. +# + +""" +Address Scope extension implementations +""" + +from oslo_serialization import jsonutils + +from cliff import hooks +from openstack.network.v2 import address_scope as address_scope_sdk +from openstack import resource +from openstackclient.network.v2 import address_scope + +from openstackclient.i18n import _ + + +_get_attrs_address_scope_new = address_scope._get_attrs + + +def _get_attrs_address_scope_extension(client_manager, parsed_args): + attrs = _get_attrs_address_scope_new(client_manager, parsed_args) + if parsed_args.apic_distinguished_names: + attrs['apic:distinguished_names' + ] = jsonutils.loads(parsed_args.apic_distinguished_names) + if parsed_args.apic_synchronization_state: + attrs['apic:synchronization_state' + ] = parsed_args.apic_synchronization_state + return attrs + + +address_scope._get_attrs = _get_attrs_address_scope_extension + +address_scope_sdk.AddressScope.apic_distinguished_names = resource.Body( + 'apic:distinguished_names') +address_scope_sdk.AddressScope.apic_synchronization_state = resource.Body( + 'apic:synchronization_state') + + +class CreateAndSetAddressScopeExtension(hooks.CommandHook): + + def get_parser(self, parser): + parser.add_argument( + '--apic-distinguished-names', + metavar="", + dest='apic_distinguished_names', + help=_("Apic distinguished names") + ) + parser.add_argument( + '--apic-synchronization-state', + metavar="", + dest='apic_synchronization_state', + help=_("Apic synchronization state") + ) + return parser + + def get_epilog(self): + return '' + + def before(self, parsed_args): + return parsed_args + + def after(self, parsed_args, return_code): + return return_code + + +class ShowAddressScopeExtension(hooks.CommandHook): + + def get_parser(self, parser): + return parser + + def get_epilog(self): + return '' + + def before(self, parsed_args): + return parsed_args + + def after(self, parsed_args, return_code): + return return_code diff --git a/gbpclient/gbp/v2_0/network.py b/gbpclient/gbp/v2_0/network.py new file mode 100644 index 0000000..aaf4782 --- /dev/null +++ b/gbpclient/gbp/v2_0/network.py @@ -0,0 +1,265 @@ +# 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. +# + +""" +Network extension implementations +""" + +import ast + +from oslo_serialization import jsonutils + +from cliff import hooks +from openstack.network.v2 import network as network_sdk +from openstack import resource +from openstackclient.network.v2 import network + +from openstackclient.i18n import _ + + +_get_attrs_network_new = network._get_attrs_network + + +def _get_attrs_network_extension(client_manager, parsed_args): + attrs = _get_attrs_network_new(client_manager, parsed_args) + if parsed_args.apic_synchronization_state: + attrs['apic:synchronization_state' + ] = parsed_args.apic_synchronization_state + if parsed_args.apic_svi_enable: + attrs['apic:svi'] = True + if parsed_args.apic_svi_disable: + attrs['apic:svi'] = False + if parsed_args.apic_bgp_enable: + attrs['apic:bgp_enable'] = True + if parsed_args.apic_bgp_disable: + attrs['apic:bgp_enable'] = False + if parsed_args.apic_bgp_type: + attrs['apic:bgp_type'] = parsed_args.apic_bgp_type + if parsed_args.apic_bgp_asn: + attrs['apic:bgp_asn'] = parsed_args.apic_bgp_asn + if parsed_args.apic_nested_domain_name: + attrs['apic:nested_domain_name' + ] = parsed_args.apic_nested_domain_name + if parsed_args.apic_nested_domain_type: + attrs['apic:nested_domain_type' + ] = parsed_args.apic_nested_domain_type + if parsed_args.apic_nested_domain_infra_vlan: + attrs['apic:nested_domain_infra_vlan' + ] = parsed_args.apic_nested_domain_infra_vlan + if parsed_args.apic_nested_domain_service_vlan: + attrs['apic:nested_domain_service_vlan' + ] = parsed_args.apic_nested_domain_service_vlan + if parsed_args.apic_nested_domain_node_network_vlan: + attrs['apic:nested_domain_node_network_vlan' + ] = parsed_args.apic_nested_domain_node_network_vlan + if parsed_args.apic_nested_domain_allowed_vlans: + attrs['apic:nested_domain_allowed_vlans' + ] = ast.literal_eval( + parsed_args.apic_nested_domain_allowed_vlans) + if parsed_args.apic_extra_provided_contracts: + attrs['apic:extra_provided_contracts' + ] = ast.literal_eval(parsed_args.apic_extra_provided_contracts) + if parsed_args.apic_extra_consumed_contracts: + attrs['apic:extra_consumed_contracts' + ] = ast.literal_eval(parsed_args.apic_extra_consumed_contracts) + if parsed_args.apic_epg_contract_masters: + attrs['apic:epg_contract_masters' + ] = ast.literal_eval(parsed_args.apic_epg_contract_masters) + if parsed_args.apic_distinguished_names: + attrs['apic:distinguished_names' + ] = jsonutils.loads(parsed_args.apic_distinguished_names) + if parsed_args.external: + if parsed_args.apic_nat_type: + attrs['apic:nat_type'] = parsed_args.apic_nat_type + if parsed_args.apic_external_cidrs: + attrs['apic:external_cidrs' + ] = ast.literal_eval(parsed_args.apic_external_cidrs) + return attrs + + +network._get_attrs_network = _get_attrs_network_extension + +network_sdk.Network.apic_synchronization_state = resource.Body( + 'apic:synchronization_state') +network_sdk.Network.apic_svi = resource.Body('apic:svi') +network_sdk.Network.apic_bgp = resource.Body('apic:bgp_enable') +network_sdk.Network.apic_bgp_type = resource.Body('apic:bgp_type') +network_sdk.Network.apic_bgp_asn = resource.Body('apic:bgp_asn') +network_sdk.Network.apic_nested_domain_name = resource.Body( + 'apic:nested_domain_name') +network_sdk.Network.apic_nested_domain_type = resource.Body( + 'apic:nested_domain_type') +network_sdk.Network.apic_nested_domain_infra_vlan = resource.Body( + 'apic:nested_domain_infra_vlan') +network_sdk.Network.apic_nested_domain_service_vlan = resource.Body( + 'apic:nested_domain_service_vlan') +network_sdk.Network.apic_nested_domain_node_network_vlan = resource.Body( + 'apic:nested_domain_node_network_vlan') +network_sdk.Network.apic_nested_domain_allowed_vlans = resource.Body( + 'apic:nested_domain_allowed_vlans') +network_sdk.Network.apic_extra_provided_contracts = resource.Body( + 'apic:extra_provided_contracts') +network_sdk.Network.apic_extra_consumed_contracts = resource.Body( + 'apic:extra_consumed_contracts') +network_sdk.Network.apic_epg_contract_masters = resource.Body( + 'apic:epg_contract_masters') +network_sdk.Network.apic_distinguished_names = resource.Body( + 'apic:distinguished_names') +network_sdk.Network.apic_nat_type = resource.Body('apic:nat_type') +network_sdk.Network.apic_external_cidrs = resource.Body('apic:external_cidrs') + + +class CreateAndSetNetworkExtension(hooks.CommandHook): + + def get_parser(self, parser): + parser.add_argument( + '--apic-synchronization-state', + metavar="", + dest='apic_synchronization_state', + help=_("Apic synchronization state") + ) + parser.add_argument( + '--apic-svi-enable', + action='store_true', + default=None, + dest='apic_svi_enable', + help=_("Set Apic SVI to true") + ) + parser.add_argument( + '--apic-svi-disable', + action='store_true', + dest='apic_svi_disable', + help=_("Set Apic SVI to false") + ) + parser.add_argument( + '--apic-bgp-enable', + action='store_true', + default=None, + dest='apic_bgp_enable', + help=_("Set Apic BGP to true") + ) + parser.add_argument( + '--apic-bgp-disable', + action='store_true', + dest='apic_bgp_disable', + help=_("Set Apic BGP to false") + ) + parser.add_argument( + '--apic-bgp-type', + metavar="", + dest='apic_bgp_type', + help=_("Apic BGP Type") + ) + parser.add_argument( + '--apic-bgp-asn', + metavar="", + dest='apic_bgp_asn', + help=_("Apic BGP ASN") + ) + parser.add_argument( + '--apic-nested-domain-name', + metavar="", + dest='apic_nested_domain_name', + help=_("Apic nested domain name") + ) + parser.add_argument( + '--apic-nested-domain-type', + metavar="", + dest='apic_nested_domain_type', + help=_("Apic nested domain type") + ) + parser.add_argument( + '--apic-nested-domain-infra-vlan', + metavar="", + dest='apic_nested_domain_infra_vlan', + help=_("Apic nested domain infra vlan") + ) + parser.add_argument( + '--apic-nested-domain-service-vlan', + metavar="", + dest='apic_nested_domain_service_vlan', + help=_("Apic nested domain service vlan") + ) + parser.add_argument( + '--apic-nested-domain-node-network-vlan', + metavar="", + dest='apic_nested_domain_node_network_vlan', + help=_("Apic nested domain node network vlan") + ) + parser.add_argument( + '--apic-nested-domain-allowed-vlans', + metavar="", + dest='apic_nested_domain_allowed_vlans', + help=_("Apic nested domain allowed vlans") + ) + parser.add_argument( + '--apic-extra-provided-contracts', + metavar="", + dest='apic_extra_provided_contracts', + help=_("Apic extra provided contracts") + ) + parser.add_argument( + '--apic-extra-consumed-contracts', + metavar="", + dest='apic_extra_consumed_contracts', + help=_("Apic extra consumed contracts") + ) + parser.add_argument( + '--apic-epg-contract-masters', + metavar="", + dest='apic_epg_contract_masters', + help=_("Apic epg contract masters") + ) + parser.add_argument( + '--apic-distinguished-names', + metavar="", + dest='apic_distinguished_names', + help=_("Apic distinguished names") + ) + parser.add_argument( + '--apic-nat-type', + metavar="", + dest='apic_nat_type', + help=_("Apic nat type for external network") + ) + parser.add_argument( + '--apic-external-cidrs', + metavar="", + dest='apic_external_cidrs', + help=_("Apic external CIDRS for external network") + ) + return parser + + def get_epilog(self): + return '' + + def before(self, parsed_args): + return parsed_args + + def after(self, parsed_args, return_code): + return return_code + + +class ShowNetworkExtension(hooks.CommandHook): + + def get_parser(self, parser): + return parser + + def get_epilog(self): + return '' + + def before(self, parsed_args): + return parsed_args + + def after(self, parsed_args, return_code): + return return_code diff --git a/gbpclient/gbp/v2_0/router.py b/gbpclient/gbp/v2_0/router.py new file mode 100644 index 0000000..e87461d --- /dev/null +++ b/gbpclient/gbp/v2_0/router.py @@ -0,0 +1,115 @@ +# 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. +# + +""" +Router extension implementations +""" + +import ast + +from oslo_serialization import jsonutils + +from cliff import hooks +from openstack.network.v2 import router as router_sdk +from openstack import resource +from openstackclient.network.v2 import router + +from openstackclient.i18n import _ + + +_get_attrs_router_new = router._get_attrs + + +def _get_attrs_router_extension(client_manager, parsed_args): + attrs = _get_attrs_router_new(client_manager, parsed_args) + if parsed_args.apic_distinguished_names: + attrs['apic:distinguished_names' + ] = jsonutils.loads(parsed_args.apic_distinguished_names) + if parsed_args.apic_synchronization_state: + attrs['apic:synchronization_state' + ] = parsed_args.apic_synchronization_state + if parsed_args.apic_external_provided_contracts: + attrs['apic:external_provided_contracts' + ] = ast.literal_eval( + parsed_args.apic_external_provided_contracts) + if parsed_args.apic_external_consumed_contracts: + attrs['apic:external_consumed_contracts' + ] = ast.literal_eval( + parsed_args.apic_external_consumed_contracts) + return attrs + + +router._get_attrs = _get_attrs_router_extension + +router_sdk.Router.apic_distinguished_names = resource.Body( + 'apic:distinguished_names') +router_sdk.Router.apic_synchronization_state = resource.Body( + 'apic:synchronization_state') +router_sdk.Router.apic_external_provided_contracts = resource.Body( + 'apic:external_provided_contracts') +router_sdk.Router.apic_external_consumed_contracts = resource.Body( + 'apic:external_consumed_contracts') + + +class CreateAndSetRouterExtension(hooks.CommandHook): + + def get_parser(self, parser): + parser.add_argument( + '--apic-distinguished-names', + metavar="", + dest='apic_distinguished_names', + help=_("Apic distinguished names") + ) + parser.add_argument( + '--apic-synchronization-state', + metavar="", + dest='apic_synchronization_state', + help=_("Apic synchronization state") + ) + parser.add_argument( + '--apic-external-provided-contracts', + metavar="", + dest='apic_external_provided_contracts', + help=_("Apic external provided contracts") + ) + parser.add_argument( + '--apic-external-consumed-contracts', + metavar="", + dest='apic_external_consumed_contracts', + help=_("Apic external consumed contracts") + ) + return parser + + def get_epilog(self): + return '' + + def before(self, parsed_args): + return parsed_args + + def after(self, parsed_args, return_code): + return return_code + + +class ShowRouterExtension(hooks.CommandHook): + + def get_parser(self, parser): + return parser + + def get_epilog(self): + return '' + + def before(self, parsed_args): + return parsed_args + + def after(self, parsed_args, return_code): + return return_code diff --git a/gbpclient/gbp/v2_0/subnet.py b/gbpclient/gbp/v2_0/subnet.py new file mode 100644 index 0000000..24b3639 --- /dev/null +++ b/gbpclient/gbp/v2_0/subnet.py @@ -0,0 +1,127 @@ +# 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. +# + +""" +Subnet extension implementations +""" + +from oslo_serialization import jsonutils + +from cliff import hooks +from openstack.network.v2 import subnet as subnet_sdk +from openstack import resource +from openstackclient.network.v2 import subnet + +from openstackclient.i18n import _ + + +_get_attrs_subnet_new = subnet._get_attrs + + +def _get_attrs_subnet_extension(client_manager, parsed_args, is_create=True): + attrs = _get_attrs_subnet_new(client_manager, parsed_args, is_create) + if parsed_args.apic_distinguished_names: + attrs['apic:distinguished_names' + ] = jsonutils.loads(parsed_args.apic_distinguished_names) + if parsed_args.apic_synchronization_state: + attrs['apic:synchronization_state' + ] = parsed_args.apic_synchronization_state + if parsed_args.apic_snat_host_pool_enable: + attrs['apic:snat_host_pool'] = True + if parsed_args.apic_snat_host_pool_disable: + attrs['apic:snat_host_pool'] = False + if parsed_args.apic_active_active_aap_enable: + attrs['apic:active_active_aap'] = True + if parsed_args.apic_active_active_aap_disable: + attrs['apic:active_active_aap'] = False + return attrs + + +subnet._get_attrs = _get_attrs_subnet_extension + +subnet_sdk.Subnet.apic_distinguished_names = resource.Body( + 'apic:distinguished_names') +subnet_sdk.Subnet.apic_synchronization_state = resource.Body( + 'apic:synchronization_state') +subnet_sdk.Subnet.apic_snat_host_pool = resource.Body( + 'apic:snat_host_pool') +subnet_sdk.Subnet.apic_active_active_aap = resource.Body( + 'apic:active_active_aap') + + +class CreateAndSetSubnetExtension(hooks.CommandHook): + + def get_parser(self, parser): + parser.add_argument( + '--apic-distinguished-names', + metavar="", + dest='apic_distinguished_names', + help=_("Apic distinguished names") + ) + parser.add_argument( + '--apic-synchronization-state', + metavar="", + dest='apic_synchronization_state', + help=_("Apic synchronization state") + ) + parser.add_argument( + '--apic-snat-host-pool-enable', + action='store_true', + default=None, + dest='apic_snat_host_pool_enable', + help=_("Set Apic snat host pool to true") + ) + parser.add_argument( + '--apic-snat-host-pool-disable', + action='store_true', + dest='apic_snat_host_pool_disable', + help=_("Set Apic snat host pool to false") + ) + parser.add_argument( + '--apic-active-active-aap-enable', + action='store_true', + default=None, + dest='apic_active_active_aap_enable', + help=_("Set Apic active active aap to true") + ) + parser.add_argument( + '--apic-active-active-aap-disable', + action='store_true', + dest='apic_active_active_aap_disable', + help=_("Set Apic active active aap to false") + ) + return parser + + def get_epilog(self): + return '' + + def before(self, parsed_args): + return parsed_args + + def after(self, parsed_args, return_code): + return return_code + + +class ShowSubnetExtension(hooks.CommandHook): + + def get_parser(self, parser): + return parser + + def get_epilog(self): + return '' + + def before(self, parsed_args): + return parsed_args + + def after(self, parsed_args, return_code): + return return_code diff --git a/gbpclient/tests/unit/test_address_scope.py b/gbpclient/tests/unit/test_address_scope.py new file mode 100644 index 0000000..7941585 --- /dev/null +++ b/gbpclient/tests/unit/test_address_scope.py @@ -0,0 +1,110 @@ +# 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 unittest import mock + +from gbpclient.gbp.v2_0 import address_scope as address_scope_ext +from gbpclient.tests.unit import test_cli20 +from openstackclient.network.v2 import address_scope +from openstackclient.tests.unit.network.v2 import test_address_scope + + +# Tests for address scope create for APIC extensions +# +class TestAddressScopeCreate( + test_address_scope.TestAddressScope, test_cli20.CLITestV20Base): + + def setUp(self): + super(TestAddressScopeCreate, self).setUp() + self.new_address_scope = ( + test_address_scope.TestCreateAddressScope.new_address_scope) + self.network.create_address_scope = mock.Mock( + return_value=self.new_address_scope) + + self.cmd = address_scope.CreateAddressScope(self.app, self.namespace) + + def test_create_default_options(self): + arglist = [ + test_address_scope.TestCreateAddressScope.new_address_scope.name, + ] + verifylist = [ + ('name', self.new_address_scope.name), + ('apic_distinguished_names', None), + ('apic_synchronization_state', None), + ] + create_ext = address_scope_ext.CreateAndSetAddressScopeExtension( + self.app) + parsed_args = self.check_parser_ext( + self.cmd, arglist, verifylist, create_ext) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_address_scope.assert_called_once_with(**{ + 'ip_version': self.new_address_scope.ip_version, + 'name': self.new_address_scope.name, + }) + + def test_create_all_options(self): + arglist = [ + self.new_address_scope.name, + "--apic-distinguished-names", '{"disttest1": "test1"}', + ] + verifylist = [ + ('name', self.new_address_scope.name), + ('apic_distinguished_names', '{"disttest1": "test1"}'), + ('apic_synchronization_state', None), + ] + create_ext = address_scope_ext.CreateAndSetAddressScopeExtension( + self.app) + parsed_args = self.check_parser_ext( + self.cmd, arglist, verifylist, create_ext) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_address_scope.assert_called_once_with(**{ + 'ip_version': self.new_address_scope.ip_version, + 'apic:distinguished_names': {"disttest1": "test1"}, + 'name': self.new_address_scope.name, + }) + + +# Tests for address scope set for APIC extensions +# +class TestAddressScopeSet( + test_address_scope.TestAddressScope, test_cli20.CLITestV20Base): + + _address_scope = test_address_scope.TestSetAddressScope._address_scope + + def setUp(self): + super(TestAddressScopeSet, self).setUp() + self.network.update_address_scope = mock.Mock(return_value=None) + self.network.find_address_scope = mock.Mock( + return_value=self._address_scope) + self.cmd = address_scope.SetAddressScope(self.app, self.namespace) + + def test_set_no_options(self): + arglist = [ + self._address_scope.name, + ] + verifylist = [ + ('address_scope', self._address_scope.name), + ('apic_distinguished_names', None), + ('apic_synchronization_state', None), + ] + set_ext = address_scope_ext.CreateAndSetAddressScopeExtension(self.app) + parsed_args = self.check_parser_ext( + self.cmd, arglist, verifylist, set_ext) + result = self.cmd.take_action(parsed_args) + + attrs = {} + self.network.update_address_scope.assert_called_with( + self._address_scope, **attrs) + self.assertIsNone(result) diff --git a/gbpclient/tests/unit/test_cli20.py b/gbpclient/tests/unit/test_cli20.py index ceade00..7bc900e 100644 --- a/gbpclient/tests/unit/test_cli20.py +++ b/gbpclient/tests/unit/test_cli20.py @@ -13,6 +13,7 @@ from unittest import mock +import fixtures import requests from neutronclient.common import exceptions @@ -21,6 +22,8 @@ from neutronclient.tests.unit import test_cli20 as neutron_test_cli20 from gbpclient import gbpshell from gbpclient.v2_0 import client as gbpclient +from six.moves import StringIO + API_VERSION = neutron_test_cli20.API_VERSION TOKEN = neutron_test_cli20.TOKEN ENDURL = neutron_test_cli20.ENDURL @@ -28,6 +31,10 @@ capture_std_streams = neutron_test_cli20.capture_std_streams end_url = neutron_test_cli20.end_url +class ParserException(Exception): + pass + + class FakeStdout(neutron_test_cli20.FakeStdout): pass @@ -115,6 +122,23 @@ class CLITestV20Base(neutron_test_cli20.CLITestV20Base): if name: self.assertIn(name, _str) + def check_parser_ext(self, cmd, args, verify_args, ext): + cmd_parser = self.cmd.get_parser('check_parser') + cmd_parser = ext.get_parser(cmd_parser) + stderr = StringIO() + with fixtures.MonkeyPatch('sys.stderr', stderr): + try: + parsed_args = cmd_parser.parse_args(args) + except SystemExit: + raise ParserException("Argument parse failed: %s" % + stderr.getvalue()) + for av in verify_args: + attr, value = av + if attr: + self.assertIn(attr, parsed_args) + self.assertEqual(value, getattr(parsed_args, attr)) + return parsed_args + class ClientV2TestJson(CLITestV20Base): diff --git a/gbpclient/tests/unit/test_network.py b/gbpclient/tests/unit/test_network.py new file mode 100644 index 0000000..8a4297d --- /dev/null +++ b/gbpclient/tests/unit/test_network.py @@ -0,0 +1,241 @@ +# 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 unittest import mock + +from gbpclient.gbp.v2_0 import network as network_ext +from gbpclient.tests.unit import test_cli20 +from openstackclient.network.v2 import network +from openstackclient.tests.unit.network.v2 import test_network + + +# Tests for network create with APIC extensions +# +class TestNetworkCreate(test_network.TestNetwork, test_cli20.CLITestV20Base): + + _network = test_network.TestCreateNetworkIdentityV3._network + + def setUp(self): + super(TestNetworkCreate, self).setUp() + self.network.create_network = mock.Mock( + return_value=self._network) + self.cmd = network.CreateNetwork(self.app, self.namespace) + + def test_create_default_options(self): + arglist = [ + self._network.name, + ] + verifylist = [ + ('name', self._network.name), + ('apic_nested_domain_name', None), + ('apic_nested_domain_type', None), + ('apic_distinguished_names', None), + ('apic_synchronization_state', None), + ('apic_nat_type', None), + ('apic_external_cidrs', None), + ('apic_svi_enable', None), + ('apic_bgp_enable', None), + ('apic_bgp_asn', None), + ('apic_bgp_type', None), + ('apic_nested_domain_infra_vlan', None), + ('apic_nested_domain_allowed_vlans', None), + ('apic_nested_domain_service_vlan', None), + ('apic_nested_domain_node_network_vlan', None), + ('apic_extra_provided_contracts', None), + ('apic_extra_consumed_contracts', None), + ] + create_ext = network_ext.CreateAndSetNetworkExtension(self.app) + parsed_args = self.check_parser_ext( + self.cmd, arglist, verifylist, create_ext) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_network.assert_called_once_with(**{ + 'admin_state_up': True, + 'name': self._network.name, + }) + + def test_create_all_options(self): + arglist = [ + self._network.name, + "--external", + "--apic-nested-domain-name", "dntest1", + "--apic-nested-domain-type", "dntype1", + "--apic-distinguished-names", '{"disttest1": "test1"}', + "--apic-nat-type", "edge", + "--apic-external-cidrs", "['20.20.20.0/8']", + "--apic-svi-enable", + "--apic-bgp-enable", + "--apic-bgp-asn", '1', + "--apic-bgp-type", "bgptest1", + "--apic-nested-domain-infra-vlan", '1', + "--apic-nested-domain-allowed-vlans", "[2]", + "--apic-nested-domain-service-vlan", '3', + "--apic-nested-domain-node-network-vlan", '4', + "--apic-extra-provided-contracts", "['pcontest1']", + "--apic-extra-consumed-contracts", "['contest1']", + ] + verifylist = [ + ('name', self._network.name), + ('external', True), + ('apic_nested_domain_name', "dntest1"), + ('apic_nested_domain_type', "dntype1"), + ('apic_distinguished_names', '{"disttest1": "test1"}'), + ('apic_synchronization_state', None), + ('apic_nat_type', "edge"), + ('apic_external_cidrs', "['20.20.20.0/8']"), + ('apic_svi_enable', True), + ('apic_bgp_enable', True), + ('apic_bgp_asn', '1'), + ('apic_bgp_type', "bgptest1"), + ('apic_nested_domain_infra_vlan', '1'), + ('apic_nested_domain_allowed_vlans', "[2]"), + ('apic_nested_domain_service_vlan', '3'), + ('apic_nested_domain_node_network_vlan', '4'), + ('apic_extra_provided_contracts', "['pcontest1']"), + ('apic_extra_consumed_contracts', "['contest1']"), + ] + create_ext = network_ext.CreateAndSetNetworkExtension(self.app) + parsed_args = self.check_parser_ext( + self.cmd, arglist, verifylist, create_ext) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_network.assert_called_once_with(**{ + 'admin_state_up': True, + 'name': self._network.name, + 'router:external': True, + 'apic:nested_domain_name': 'dntest1', + 'apic:distinguished_names': {"disttest1": "test1"}, + 'apic:external_cidrs': ['20.20.20.0/8'], + 'apic:nat_type': 'edge', + 'apic:nested_domain_name': 'dntest1', + 'apic:nested_domain_type': 'dntype1', + 'apic:svi': True, + 'apic:bgp_enable': True, + 'apic:bgp_asn': '1', + 'apic:bgp_type': 'bgptest1', + 'apic:extra_consumed_contracts': ['contest1'], + 'apic:extra_provided_contracts': ['pcontest1'], + 'apic:nested_domain_allowed_vlans': [2], + 'apic:nested_domain_infra_vlan': '1', + 'apic:nested_domain_node_network_vlan': '4', + 'apic:nested_domain_service_vlan': '3', + }) + + +# Tests for network set with APIC extensions +# +class TestNetworkSet(test_network.TestNetwork, test_cli20.CLITestV20Base): + + _network = test_network.TestSetNetwork._network + + def setUp(self): + super(TestNetworkSet, self).setUp() + self.network.update_network = mock.Mock(return_value=None) + self.network.find_network = mock.Mock(return_value=self._network) + self.cmd = network.SetNetwork(self.app, self.namespace) + + def test_set_no_options(self): + arglist = [ + self._network.name, + ] + verifylist = [ + ('network', self._network.name), + ('apic_nested_domain_name', None), + ('apic_nested_domain_type', None), + ('apic_distinguished_names', None), + ('apic_synchronization_state', None), + ('apic_nat_type', None), + ('apic_external_cidrs', None), + ('apic_svi_enable', None), + ('apic_bgp_enable', None), + ('apic_bgp_asn', None), + ('apic_bgp_type', None), + ('apic_nested_domain_infra_vlan', None), + ('apic_nested_domain_allowed_vlans', None), + ('apic_nested_domain_service_vlan', None), + ('apic_nested_domain_node_network_vlan', None), + ('apic_extra_provided_contracts', None), + ('apic_extra_consumed_contracts', None), + ] + set_ext = network_ext.CreateAndSetNetworkExtension(self.app) + parsed_args = self.check_parser_ext( + self.cmd, arglist, verifylist, set_ext) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_network.called) + self.assertIsNone(result) + + def test_set_all_valid_options(self): + arglist = [ + self._network.name, + "--external", + "--apic-nested-domain-name", "dntest11", + "--apic-nested-domain-type", "dntype11", + "--apic-nat-type", "distributed", + "--apic-external-cidrs", "['30.30.30.0/8']", + "--apic-bgp-disable", + "--apic-bgp-asn", '2', + "--apic-bgp-type", "bgptest11", + "--apic-nested-domain-infra-vlan", '2', + "--apic-nested-domain-allowed-vlans", "[2, 3]", + "--apic-nested-domain-service-vlan", '4', + "--apic-nested-domain-node-network-vlan", '5', + "--apic-extra-provided-contracts", "['pcontest1', 'pcontest11']", + "--apic-extra-consumed-contracts", "['contest1', 'contest11']", + ] + verifylist = [ + ('network', self._network.name), + ('external', True), + ('apic_nested_domain_name', "dntest11"), + ('apic_nested_domain_type', "dntype11"), + ('apic_distinguished_names', None), + ('apic_synchronization_state', None), + ('apic_nat_type', "distributed"), + ('apic_external_cidrs', "['30.30.30.0/8']"), + ('apic_svi_enable', None), + ('apic_bgp_disable', True), + ('apic_bgp_asn', '2'), + ('apic_bgp_type', "bgptest11"), + ('apic_nested_domain_infra_vlan', '2'), + ('apic_nested_domain_allowed_vlans', "[2, 3]"), + ('apic_nested_domain_service_vlan', '4'), + ('apic_nested_domain_node_network_vlan', '5'), + ('apic_extra_provided_contracts', "['pcontest1', 'pcontest11']"), + ('apic_extra_consumed_contracts', "['contest1', 'contest11']"), + ] + set_ext = network_ext.CreateAndSetNetworkExtension(self.app) + parsed_args = self.check_parser_ext( + self.cmd, arglist, verifylist, set_ext) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'router:external': True, + 'apic:nested_domain_name': 'dntest11', + 'apic:external_cidrs': ['30.30.30.0/8'], + 'apic:nat_type': 'distributed', + 'apic:nested_domain_name': 'dntest11', + 'apic:nested_domain_type': 'dntype11', + 'apic:bgp_enable': False, + 'apic:bgp_asn': '2', + 'apic:bgp_type': 'bgptest11', + 'apic:extra_consumed_contracts': ['contest1', 'contest11'], + 'apic:extra_provided_contracts': ['pcontest1', 'pcontest11'], + 'apic:nested_domain_allowed_vlans': [2, 3], + 'apic:nested_domain_infra_vlan': '2', + 'apic:nested_domain_node_network_vlan': '5', + 'apic:nested_domain_service_vlan': '4', + } + + self.network.update_network.assert_called_once_with( + self._network, **attrs) + self.assertIsNone(result) diff --git a/gbpclient/tests/unit/test_router.py b/gbpclient/tests/unit/test_router.py new file mode 100644 index 0000000..64bc0cd --- /dev/null +++ b/gbpclient/tests/unit/test_router.py @@ -0,0 +1,137 @@ +# 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 unittest import mock + +from gbpclient.gbp.v2_0 import router as router_ext +from gbpclient.tests.unit import test_cli20 +from openstackclient.network.v2 import router +from openstackclient.tests.unit.network.v2 import test_router + + +# Tests for router create for APIC extensions +# +class TestRouterCreate(test_router.TestRouter, test_cli20.CLITestV20Base): + + def setUp(self): + super(TestRouterCreate, self).setUp() + self.new_router = test_router.TestCreateRouter.new_router + self.network.create_router = mock.Mock(return_value=self.new_router) + self.cmd = router.CreateRouter(self.app, self.namespace) + + def test_create_default_options(self): + arglist = [ + self.new_router.name, + ] + verifylist = [ + ('name', self.new_router.name), + ('apic_distinguished_names', None), + ('apic_synchronization_state', None), + ('apic_external_provided_contracts', None), + ('apic_external_consumed_contracts', None), + ] + create_ext = router_ext.CreateAndSetRouterExtension(self.app) + parsed_args = self.check_parser_ext( + self.cmd, arglist, verifylist, create_ext) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_router.assert_called_once_with(**{ + 'admin_state_up': True, + 'name': self.new_router.name, + }) + + def test_create_all_options(self): + arglist = [ + self.new_router.name, + "--apic-distinguished-names", '{"disttest1": "test1"}', + "--apic-external-provided-contracts", "['ptest1']", + "--apic-external-consumed-contracts", "['ctest1']", + ] + verifylist = [ + ('name', self.new_router.name), + ('apic_distinguished_names', '{"disttest1": "test1"}'), + ('apic_synchronization_state', None), + ('apic_external_provided_contracts', "['ptest1']"), + ('apic_external_consumed_contracts', "['ctest1']"), + ] + create_ext = router_ext.CreateAndSetRouterExtension(self.app) + parsed_args = self.check_parser_ext( + self.cmd, arglist, verifylist, create_ext) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_router.assert_called_once_with(**{ + 'admin_state_up': True, + 'name': self.new_router.name, + 'apic:distinguished_names': {"disttest1": "test1"}, + 'apic:external_provided_contracts': ['ptest1'], + 'apic:external_consumed_contracts': ['ctest1'], + }) + + +# Tests for router set for APIC extensions +# +class TestRouterSet(test_router.TestRouter, test_cli20.CLITestV20Base): + + _network = test_router.TestSetRouter._network + _subnet = test_router.TestSetRouter._subnet + _router = test_router.TestSetRouter._router + + def setUp(self): + super(TestRouterSet, self).setUp() + self.network.router_add_gateway = mock.Mock() + self.network.update_router = mock.Mock(return_value=None) + self.network.set_tags = mock.Mock(return_value=None) + self.network.find_router = mock.Mock(return_value=self._router) + self.network.find_network = mock.Mock(return_value=self._network) + self.network.find_subnet = mock.Mock(return_value=self._subnet) + self.cmd = router.SetRouter(self.app, self.namespace) + + def test_set_no_options(self): + arglist = [ + self._router.name, + ] + verifylist = [ + ('router', self._router.name), + ] + set_ext = router_ext.CreateAndSetRouterExtension(self.app) + parsed_args = self.check_parser_ext( + self.cmd, arglist, verifylist, set_ext) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_router.called) + self.assertFalse(self.network.set_tags.called) + self.assertIsNone(result) + + def test_set_all_valid_options(self): + arglist = [ + self._router.name, + "--apic-external-provided-contracts", "['ptest1', 'ptest11']", + "--apic-external-consumed-contracts", "['ctest1', 'ctest11']", + ] + verifylist = [ + ('router', self._router.name), + ('apic_external_provided_contracts', "['ptest1', 'ptest11']"), + ('apic_external_consumed_contracts', "['ctest1', 'ctest11']"), + ] + set_ext = router_ext.CreateAndSetRouterExtension(self.app) + parsed_args = self.check_parser_ext( + self.cmd, arglist, verifylist, set_ext) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'apic:external_provided_contracts': ['ptest1', 'ptest11'], + 'apic:external_consumed_contracts': ['ctest1', 'ctest11'], + } + self.network.update_router.assert_called_once_with( + self._router, **attrs) + self.assertIsNone(result) diff --git a/gbpclient/tests/unit/test_subnet.py b/gbpclient/tests/unit/test_subnet.py new file mode 100644 index 0000000..75506d2 --- /dev/null +++ b/gbpclient/tests/unit/test_subnet.py @@ -0,0 +1,154 @@ +# 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 unittest import mock + +from gbpclient.gbp.v2_0 import subnet as subnet_ext +from gbpclient.tests.unit import test_cli20 +from openstackclient.network.v2 import subnet +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit.network.v2 import test_subnet + + +# Tests for subnet create for APIC extensions +# +class TestSubnetCreate(test_subnet.TestSubnet, test_cli20.CLITestV20Base): + + def setUp(self): + super(TestSubnetCreate, self).setUp() + self._subnet = network_fakes.FakeSubnet.create_one_subnet( + attrs={ + 'tenant_id': '1', + } + ) + self._network = network_fakes.FakeNetwork.create_one_network( + attrs={ + 'id': self._subnet.network_id, + } + ) + self.network.create_subnet = mock.Mock(return_value=self._subnet) + self.network.find_network = mock.Mock(return_value=self._network) + self.cmd = subnet.CreateSubnet(self.app, self.namespace) + + def test_create_default_options(self): + arglist = [ + "--subnet-range", self._subnet.cidr, + "--network", self._subnet.network_id, + self._subnet.name, + ] + verifylist = [ + ('name', self._subnet.name), + ('network', self._subnet.network_id), + ('apic_distinguished_names', None), + ('apic_synchronization_state', None), + ('apic_snat_host_pool_enable', None), + ('apic_active_active_aap_enable', None), + ] + create_ext = subnet_ext.CreateAndSetSubnetExtension(self.app) + parsed_args = self.check_parser_ext( + self.cmd, arglist, verifylist, create_ext) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_subnet.assert_called_once_with(**{ + 'ip_version': 4, + 'cidr': '10.10.10.0/24', + 'name': self._subnet.name, + 'network_id': self._subnet.network_id, + }) + + def test_create_all_options(self): + arglist = [ + "--subnet-range", self._subnet.cidr, + "--network", self._subnet.network_id, + self._subnet.name, + "--apic-distinguished-names", '{"disttest1": "test1"}', + "--apic-snat-host-pool-enable", + "--apic-active-active-aap-enable", + ] + verifylist = [ + ('name', self._subnet.name), + ('network', self._subnet.network_id), + ('apic_distinguished_names', '{"disttest1": "test1"}'), + ('apic_synchronization_state', None), + ('apic_snat_host_pool_enable', True), + ('apic_active_active_aap_enable', True), + ] + create_ext = subnet_ext.CreateAndSetSubnetExtension(self.app) + parsed_args = self.check_parser_ext( + self.cmd, arglist, verifylist, create_ext) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_subnet.assert_called_once_with(**{ + 'ip_version': 4, + 'cidr': '10.10.10.0/24', + 'name': self._subnet.name, + 'network_id': self._subnet.network_id, + 'apic:active_active_aap': True, + 'apic:distinguished_names': {"disttest1": "test1"}, + 'apic:snat_host_pool': True, + }) + + +# Tests for subnet set for APIC extensions +# +class TestSubnetSet(test_subnet.TestSubnet, test_cli20.CLITestV20Base): + + _subnet = test_subnet.TestSetSubnet._subnet + + def setUp(self): + super(TestSubnetSet, self).setUp() + self.network.update_subnet = mock.Mock(return_value=None) + self.network.find_subnet = mock.Mock(return_value=self._subnet) + self.cmd = subnet.SetSubnet(self.app, self.namespace) + + def test_set_no_options(self): + arglist = [ + self._subnet.name, + ] + verifylist = [ + ('subnet', self._subnet.name), + ('apic_distinguished_names', None), + ('apic_synchronization_state', None), + ('apic_snat_host_pool_enable', None), + ('apic_active_active_aap_enable', None), + ] + set_ext = subnet_ext.CreateAndSetSubnetExtension(self.app) + parsed_args = self.check_parser_ext( + self.cmd, arglist, verifylist, set_ext) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_subnet.called) + self.assertIsNone(result) + + def test_set_all_valid_options(self): + arglist = [ + self._subnet.name, + "--apic-snat-host-pool-disable", + ] + verifylist = [ + ('subnet', self._subnet.name), + ('apic_distinguished_names', None), + ('apic_synchronization_state', None), + ('apic_snat_host_pool_disable', True), + ('apic_active_active_aap_enable', None), + ] + set_ext = subnet_ext.CreateAndSetSubnetExtension(self.app) + parsed_args = self.check_parser_ext( + self.cmd, arglist, verifylist, set_ext) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'apic:snat_host_pool': False, + } + self.network.update_subnet.assert_called_with(self._subnet, **attrs) + self.assertIsNone(result) diff --git a/setup.cfg b/setup.cfg index b5b00d8..6577343 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,6 +30,42 @@ setup-hooks = console_scripts = gbp = gbpclient.gbpshell:main +openstack.cli.network_create = + network_create_extension = gbpclient.gbp.v2_0.network:CreateAndSetNetworkExtension + +openstack.cli.network_show = + network_show_extension = gbpclient.gbp.v2_0.network:ShowNetworkExtension + +openstack.cli.network_set = + network_set_extension = gbpclient.gbp.v2_0.network:CreateAndSetNetworkExtension + +openstack.cli.subnet_create = + subnet_create_extension = gbpclient.gbp.v2_0.subnet:CreateAndSetSubnetExtension + +openstack.cli.subnet_show = + subnet_show_extension = gbpclient.gbp.v2_0.subnet:ShowSubnetExtension + +openstack.cli.subnet_set = + subnet_set_extension = gbpclient.gbp.v2_0.subnet:CreateAndSetSubnetExtension + +openstack.cli.address_scope_create = + address_scope_create_extension = gbpclient.gbp.v2_0.address_scope:CreateAndSetAddressScopeExtension + +openstack.cli.address_scope_show = + address_scope_show_extension = gbpclient.gbp.v2_0.address_scope:ShowAddressScopeExtension + +openstack.cli.address_scope_set = + address_scope_set_extension = gbpclient.gbp.v2_0.address_scope:CreateAndSetAddressScopeExtension + +openstack.cli.router_create = + router_create_extension = gbpclient.gbp.v2_0.router:CreateAndSetRouterExtension + +openstack.cli.router_show = + router_show_extension = gbpclient.gbp.v2_0.router:ShowRouterExtension + +openstack.cli.router_set = + router_set_extension = gbpclient.gbp.v2_0.router:CreateAndSetRouterExtension + [build_sphinx] all_files = 1 build-dir = doc/build