From 869240d34574cb4139a52116b10c1b6af95fc816 Mon Sep 17 00:00:00 2001 From: Vineet Pruthi Date: Wed, 10 Jun 2020 14:08:54 +0000 Subject: [PATCH 01/12] VPC Peering sdk part --- examples/vpc/find_peering.py | 26 ++ examples/vpc/get_peering.py | 26 ++ otcextensions/osclient/vpc/__init__.py | 0 otcextensions/osclient/vpc/client.py | 53 +++ otcextensions/osclient/vpc/v2/__init__.py | 0 otcextensions/osclient/vpc/v2/dnat.py | 297 +++++++++++++++++ otcextensions/osclient/vpc/v2/gateway.py | 308 ++++++++++++++++++ otcextensions/osclient/vpc/v2/snat.py | 276 ++++++++++++++++ otcextensions/sdk/__init__.py | 3 + otcextensions/sdk/vpc/__init__.py | 0 otcextensions/sdk/vpc/v2/__init__.py | 0 otcextensions/sdk/vpc/v2/_base.py | 18 + otcextensions/sdk/vpc/v2/_proxy.py | 129 ++++++++ otcextensions/sdk/vpc/v2/peering.py | 73 +++++ otcextensions/sdk/vpc/vpc_service.py | 22 ++ .../tests/functional/sdk/vpc/__init__.py | 0 .../tests/functional/sdk/vpc/v2/__init__.py | 0 .../functional/sdk/vpc/v2/test_service.py | 24 ++ otcextensions/tests/unit/sdk/vpc/__init__.py | 0 .../tests/unit/sdk/vpc/v2/__init__.py | 0 .../tests/unit/sdk/vpc/v2/test_peering.py | 91 ++++++ .../tests/unit/sdk/vpc/v2/test_proxy.py | 70 ++++ 22 files changed, 1416 insertions(+) create mode 100644 examples/vpc/find_peering.py create mode 100644 examples/vpc/get_peering.py create mode 100644 otcextensions/osclient/vpc/__init__.py create mode 100644 otcextensions/osclient/vpc/client.py create mode 100644 otcextensions/osclient/vpc/v2/__init__.py create mode 100644 otcextensions/osclient/vpc/v2/dnat.py create mode 100644 otcextensions/osclient/vpc/v2/gateway.py create mode 100644 otcextensions/osclient/vpc/v2/snat.py create mode 100644 otcextensions/sdk/vpc/__init__.py create mode 100644 otcextensions/sdk/vpc/v2/__init__.py create mode 100644 otcextensions/sdk/vpc/v2/_base.py create mode 100644 otcextensions/sdk/vpc/v2/_proxy.py create mode 100644 otcextensions/sdk/vpc/v2/peering.py create mode 100644 otcextensions/sdk/vpc/vpc_service.py create mode 100644 otcextensions/tests/functional/sdk/vpc/__init__.py create mode 100644 otcextensions/tests/functional/sdk/vpc/v2/__init__.py create mode 100644 otcextensions/tests/functional/sdk/vpc/v2/test_service.py create mode 100644 otcextensions/tests/unit/sdk/vpc/__init__.py create mode 100644 otcextensions/tests/unit/sdk/vpc/v2/__init__.py create mode 100644 otcextensions/tests/unit/sdk/vpc/v2/test_peering.py create mode 100644 otcextensions/tests/unit/sdk/vpc/v2/test_proxy.py diff --git a/examples/vpc/find_peering.py b/examples/vpc/find_peering.py new file mode 100644 index 000000000..18c6f81c8 --- /dev/null +++ b/examples/vpc/find_peering.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# 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. +""" +Get VPC Peering by id or name +""" +import openstack +from otcextensions import sdk + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') +sdk.register_otc_extensions(conn) + + +name_or_id = "name_or_id" +peering = conn.vpc.find_peering(name_or_id, ignore_missing=True) +print(peering) diff --git a/examples/vpc/get_peering.py b/examples/vpc/get_peering.py new file mode 100644 index 000000000..ec56cd726 --- /dev/null +++ b/examples/vpc/get_peering.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# 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. +""" +Get VPC Peering by id or peering Instance +""" +import openstack +from otcextensions import sdk + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') +sdk.register_otc_extensions(conn) + + +peering_id = "peering_id" +peering = conn.vpc.get_peering(peering_id) +print(peering) diff --git a/otcextensions/osclient/vpc/__init__.py b/otcextensions/osclient/vpc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/osclient/vpc/client.py b/otcextensions/osclient/vpc/client.py new file mode 100644 index 000000000..1292b0ea5 --- /dev/null +++ b/otcextensions/osclient/vpc/client.py @@ -0,0 +1,53 @@ +# 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 logging + +from osc_lib import utils + +from otcextensions import sdk +from otcextensions.i18n import _ + +LOG = logging.getLogger(__name__) + +DEFAULT_API_VERSION = '2' +API_VERSION_OPTION = 'os_vpc_api_version' +API_NAME = "vpc" +API_VERSIONS = { + "2": "openstack.connection.Connection" +} + + +def make_client(instance): + """Returns a rds proxy""" + + conn = instance.sdk_connection + + if getattr(conn, 'vpc', None) is None: + LOG.debug('OTC extensions are not registered. Do that now') + sdk.register_otc_extensions(conn) + + LOG.debug('VPC client initialized using OpenStack OTC SDK: %s', + conn.vpc) + return conn.vpc + + +def build_option_parser(parser): + """Hook to add global options""" + parser.add_argument( + '--os-vpc-api-version', + metavar='', + default=utils.env('OS_NAT_API_VERSION'), + help=_("VPC API version, default=%s " + "(Env: OS_VPC_API_VERSION)") % DEFAULT_API_VERSION + ) + return parser diff --git a/otcextensions/osclient/vpc/v2/__init__.py b/otcextensions/osclient/vpc/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/osclient/vpc/v2/dnat.py b/otcextensions/osclient/vpc/v2/dnat.py new file mode 100644 index 000000000..d241a650f --- /dev/null +++ b/otcextensions/osclient/vpc/v2/dnat.py @@ -0,0 +1,297 @@ +# 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. +# +"""DNAT v2 action implementations""" +import logging + +from osc_lib import utils +from osc_lib import exceptions +from osc_lib.command import command + +from otcextensions.i18n import _ +from otcextensions.common import sdk_utils + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = { + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + +class ListDnatRules(command.Lister): + + _description = _("List DNAT Rules.") + columns = ( + 'Id', + 'Nat Gateway Id', + 'Port Id', + 'Private IP', + 'Floating Ip Address', + 'Protocol', + 'Status' + ) + + def get_parser(self, prog_name): + parser = super(ListDnatRules, self).get_parser(prog_name) + + parser.add_argument( + '--id', + metavar='', + help=_("Specifies the ID of the SNAT rule."), + ) + parser.add_argument( + '--limit', + metavar='', + type=int, + help=_("Limit to fetch number of records."), + ) + parser.add_argument( + '--project-id', + metavar='', + help=_("Specifies the project ID."), + ) + parser.add_argument( + '--nat-gateway-id', + metavar='', + help=_("Specifies the NAT gateway ID."), + ) + parser.add_argument( + '--port-id', + metavar='', + help=_("Specifies the port ID of an ECS or a BMS."), + ) + parser.add_argument( + '--private-ip', + metavar='', + help=_("Specifies the private IP address, for example, " + "the IP address of a Direct Connect connection."), + ) + parser.add_argument( + '--internal-service-port', + metavar='', + help=_("Specifies port used by ECSs or BMSs to provide " + "services for external systems."), + ) + parser.add_argument( + '--floating-ip-id', + metavar='', + help=_("Specifies the Floating IP ID."), + ) + parser.add_argument( + '--floating-ip-address', + metavar='', + help=_("Specifies the Floating IP."), + ) + parser.add_argument( + '--external-service-port', + metavar='', + help=_("Specifies the port for providing external services."), + ) + parser.add_argument( + '--protocol', + metavar='', + help=_("Specifies the protocol type." + "Currently, TCP, UDP, and ANY are supported."), + ) + parser.add_argument( + '--status', + metavar='', + help=_("Specifies the status of the DNAT rule.\n" + "ACTIVE: The resource status is normal.\n" + "PENDING_CREATE: The resource is being created.\n" + "PENDING_UPDATE: The resource is being updated.\n" + "PENDING_DELETE: The resource is being deleted.\n" + "EIP_FREEZED: The EIP of the resource is frozen.\n" + "INACTIVE: The resource status is abnormal."), + ) + parser.add_argument( + '--admin-state-up', + metavar='', + help=_("Specifies whether the DNAT rule is enabled or " + "disabled. The value can be:\n" + "true: The DNAT rule is enabled.\n" + "false: The DNAT rule is disabled."), + ) + parser.add_argument( + '--created-at', + metavar='', + help=_("Specifies when the DNAT rule is created (UTC time). " + "Its value rounds to 6 decimal places forseconds. " + "The format is yyyy-mm-ddhh:mm:ss."), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.nat + args_list = [ + 'id', + 'limit', + 'project_id', + 'nat_gateway_id', + 'port_id', + 'private_ip', + 'internal_service_port', + 'floating_ip_id', + 'floating_ip_address', + 'external_service_port', + 'protocol', + 'status', + 'admin_state_up', + 'created_at' + ] + attrs = {} + for arg in args_list: + val = getattr(parsed_args, arg) + if val: + attrs[arg] = val + data = client.dnat_rules(**attrs) + + return ( + self.columns, + (utils.get_item_properties( + s, + self.columns, + ) for s in data) + ) + + +class ShowDnatRule(command.ShowOne): + _description = _("Show Dnat Rule details") + + def get_parser(self, prog_name): + parser = super(ShowDnatRule, self).get_parser(prog_name) + parser.add_argument( + 'dnat', + metavar='', + help=_("Specifies the ID of the SNAT Rule"), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.nat + obj = client.get_dnat_rule(parsed_args.dnat) + + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + + return (display_columns, data) + + +class CreateDnatRule(command.ShowOne): + _description = _("Create new DNAT Rule") + + def get_parser(self, prog_name): + parser = super(CreateDnatRule, self).get_parser(prog_name) + parser.add_argument( + '--nat-gateway-id', + metavar='', + required=True, + help=_("Specifies the ID of the NAT Gateway."), + ) + parser.add_argument( + '--port-id', + metavar='', + help=_("Specifies the port ID of an ECS or a BMS."), + ) + parser.add_argument( + '--private-ip', + metavar='', + help=_("Specifies the private IP address, for example, " + "the IP address of a Direct Connect connection."), + ) + parser.add_argument( + '--internal-service-port', + metavar='', + required=True, + help=_("Specifies port used by ECSs or BMSs to provide " + "services for external systems."), + ) + parser.add_argument( + '--floating-ip-id', + metavar="", + required=True, + help=_("Specifies the Floating IP ID. Multiple " + "Floating IPs are separated using commas."), + ) + parser.add_argument( + '--external-service-port', + metavar='', + required=True, + help=_("Specifies the port for providing external services."), + ) + parser.add_argument( + '--protocol', + metavar='', + required=True, + help=_("Specifies the protocol type."), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.nat + + args_list = [ + 'nat_gateway_id', + 'port_id', + 'private_ip', + 'internal_service_port', + 'floating_ip_id', + 'external_service_port', + 'protocol' + ] + attrs = {} + for arg in args_list: + val = getattr(parsed_args, arg) + if val: + attrs[arg] = val + + obj = client.create_dnat_rule(**attrs) + + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + + return (display_columns, data) + + +class DeleteDnatRule(command.Command): + + _description = _("Deletes Dnat Rule(s).") + + def get_parser(self, prog_name): + parser = super(DeleteDnatRule, self).get_parser(prog_name) + parser.add_argument( + 'dnat', + metavar='', + nargs='+', + help=_("Specifies the DNAT Rule(s) ID(s) to delete."), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.nat + result = 0 + for dnat in parsed_args.dnat: + try: + obj = client.get_dnat_rule(dnat) + client.delete_dnat_rule(obj.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete DNAT rule with " + "ID '%(dnat)s': %(e)s"), + {'dnat': dnat, 'e': e}) + if result > 0: + total = len(parsed_args.dnat) + msg = (_("%(result)s of %(total)s DNAT Rule(s) failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) diff --git a/otcextensions/osclient/vpc/v2/gateway.py b/otcextensions/osclient/vpc/v2/gateway.py new file mode 100644 index 000000000..37f34be56 --- /dev/null +++ b/otcextensions/osclient/vpc/v2/gateway.py @@ -0,0 +1,308 @@ +# 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. +# +"""NAT Gateway v2 action implementations""" +import logging + +from osc_lib import utils +from osc_lib import exceptions +from osc_lib.command import command + +from otcextensions.i18n import _ +from otcextensions.common import sdk_utils + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = { + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + +class ListNatGateways(command.Lister): + + _description = _("List Nat Gateway.") + columns = ('Id', 'Name', 'Spec', 'Router Id', 'Status') + + def get_parser(self, prog_name): + parser = super(ListNatGateways, self).get_parser(prog_name) + + parser.add_argument( + '--id', + metavar='', + help=_("Specifies the ID of the NAT Gateway."), + ) + parser.add_argument( + '--limit', + metavar='', + type=int, + help=_("Limit to fetch number of records."), + ) + parser.add_argument( + '--project-id', + metavar='', + help=_("Specifies the project ID."), + ) + parser.add_argument( + '--name', + metavar='', + help=_("Specifies the Name of the NAT Gateway."), + ) + parser.add_argument( + '--spec', + metavar='', + help=_("Specifies the type of the NAT Gateway. " + "The value of spec can be:\n" + "1: small type, which supports up to 10,000 " + "SNAT connections.\n" + "2: medium type, which supports up to 50,000 " + "SNAT connections.\n" + "3: large type, which supports up to 200,000 " + "SNAT connections.\n" + "4: extra-large type, which supports up to " + "1,000,000 SNAT connections."), + ) + parser.add_argument( + '--router-id', + metavar='', + help=_("Specifies the router ID."), + ) + parser.add_argument( + '--internal-network-id', + metavar='', + help=_("Specifies the network ID of the downstream " + "interface (the next hop of the DVR) of the " + "NAT Gateway."), + ) + parser.add_argument( + '--status', + metavar='', + help=_("Specifies the status of the NAT Gateway.\n" + "ACTIVE: The resource status is normal.\n" + "PENDING_CREATE: The resource is being created.\n" + "PENDING_UPDATE: The resource is being updated.\n" + "PENDING_DELETE: The resource is being deleted.\n" + "EIP_FREEZED: The EIP of the resource is frozen.\n" + "INACTIVE: The resource status is abnormal."), + ) + parser.add_argument( + '--admin-state-up', + metavar='', + help=_("Specifies whether the NAT Gateway is enabled " + "or disabled. The value can be:\n" + "true: The NAT gateway is up.\n" + "false: The NAT gateway is down."), + ) + parser.add_argument( + '--created-at', + metavar='', + help=_("Specifies when the NAT Gateway is created (UTC time). " + "Its valuerounds to 6 decimal places forseconds. " + "The format is yyyy-mm-ddhh:mm:ss."), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.nat + args_list = [ + 'id', + 'limit', + 'project_id', + 'name', + 'spec', + 'router_id', + 'internal_network_id', + 'status', + 'admin_state_up', + 'created_at'] + attrs = {} + for arg in args_list: + val = getattr(parsed_args, arg) + if val: + attrs[arg] = val + + data = client.gateways(**attrs) + + return (self.columns, (utils.get_item_properties(s, self.columns) + for s in data)) + + +class ShowNatGateway(command.ShowOne): + _description = _("Show NAT Gateway details") + + def get_parser(self, prog_name): + parser = super(ShowNatGateway, self).get_parser(prog_name) + parser.add_argument( + 'gateway', + metavar='', + help=_("Specifies the Name or ID of the NAT Gateway."), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.nat + obj = client.find_gateway(parsed_args.gateway) + + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + + return (display_columns, data) + + +class CreateNatGateway(command.ShowOne): + _description = _("Create new NAT Gateway") + + def get_parser(self, prog_name): + parser = super(CreateNatGateway, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help=_("Specifies the name of the NAT Gateway."), + ) + parser.add_argument( + '--description', + metavar='', + help=_("Provides supplementary information about " + "the NAT Gateway."), + ) + parser.add_argument( + '--spec', + metavar='', + required=True, + help=_( + "Specifies the type of the NAT Gateway. " + "The value can be:\n" + "1: small type, which supports up to 10,000 " + "SNAT connections.\n" + "2: medium type, which supports up to 50,000 " + "SNAT connections.\n" + "3: large type, which supports up to 200,000 " + "SNAT connections.\n" + "4: extra-large type, which supports up to " + "1,000,000 SNAT connections."), + ) + parser.add_argument( + '--router-id', + metavar='', + required=True, + help=_("Specifies the VPC ID."), + ) + parser.add_argument( + '--internal-network-id', + metavar='', + required=True, + help=_("Specifies the network ID of the downstream interface " + "(the next hop of the DVR) of the NAT Gateway."), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.nat + + args_list = [ + 'name', + 'description', + 'spec', + 'router_id', + 'internal_network_id'] + attrs = {} + for arg in args_list: + val = getattr(parsed_args, arg) + if val: + attrs[arg] = val + + obj = client.create_gateway(**attrs) + + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + + return (display_columns, data) + + +class UpdateNatGateway(command.ShowOne): + _description = _("Update a NAT Gateway.") + + def get_parser(self, prog_name): + parser = super(UpdateNatGateway, self).get_parser(prog_name) + parser.add_argument( + 'gateway', + metavar='', + help=_("Specifies the Name or ID of the NAT Gateway."), + ) + parser.add_argument( + '--name', + metavar='', + help=_("Specifies the name of the NAT Gateway."), + ) + parser.add_argument( + '--description', + metavar='', + help=_("Provides supplementary informationabout the NAT gateway."), + ) + parser.add_argument( + '--spec', + metavar='', + help=_("Specifies the type of the NAT Gateway."), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.nat + args_list = [ + 'name', 'description', 'spec' + ] + attrs = {} + for arg in args_list: + if getattr(parsed_args, arg): + attrs[arg] = getattr(parsed_args, arg) + nat_gateway = client.find_gateway(parsed_args.gateway) + + obj = client.update_gateway(nat_gateway.id, **attrs) + + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + + return (display_columns, data) + + +class DeleteNatGateway(command.Command): + + _description = _("Deletes NAT Gateway.") + + def get_parser(self, prog_name): + parser = super(DeleteNatGateway, self).get_parser(prog_name) + parser.add_argument( + 'gateway', + metavar='', + nargs='+', + help=_("Nat Gateway(s) to delete (Name or ID)"), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.nat + result = 0 + for gateway in parsed_args.gateway: + try: + obj = client.find_gateway(gateway) + client.delete_gateway(obj.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete Nat Gateway with " + "name or ID '%(gateway)s': %(e)s"), + {'gateway': gateway, 'e': e}) + if result > 0: + total = len(parsed_args.gateway) + msg = (_("%(result)s of %(total)s NAT Gateway(s) failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) diff --git a/otcextensions/osclient/vpc/v2/snat.py b/otcextensions/osclient/vpc/v2/snat.py new file mode 100644 index 000000000..4dc54cabe --- /dev/null +++ b/otcextensions/osclient/vpc/v2/snat.py @@ -0,0 +1,276 @@ +# 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. +# +"""SNAT v2 action implementations""" +import logging + +from osc_lib import utils +from osc_lib import exceptions +from osc_lib.command import command + +from otcextensions.i18n import _ +from otcextensions.common import sdk_utils + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = { + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + +class ListSnatRules(command.Lister): + + _description = _("List SNAT Rules.") + columns = ( + 'Id', + 'Nat Gateway Id', + 'Network Id', + 'Cidr', + 'Floating Ip Address', + 'Status' + ) + + def get_parser(self, prog_name): + parser = super(ListSnatRules, self).get_parser(prog_name) + + parser.add_argument( + '--id', + metavar='', + help=_("Specifies the ID of the SNAT rule."), + ) + parser.add_argument( + '--limit', + metavar='', + type=int, + help=_("Limit to fetch number of records."), + ) + parser.add_argument( + '--project-id', + metavar='', + help=_("Specifies the project ID."), + ) + parser.add_argument( + '--nat-gateway-id', + metavar='', + help=_("Specifies the NAT gateway ID."), + ) + parser.add_argument( + '--network-id', + metavar='', + help=_("Specifies the network ID used by the SNAT rule."), + ) + parser.add_argument( + '--cidr', + metavar='', + help=_("Specifies a subset of the VPC subnet CIDR block or " + "a CIDR block of Direct Connect connection."), + ) + parser.add_argument( + '--source-type', + metavar='', + help=_("Specifies Source Type."), + ) + parser.add_argument( + '--floating-ip-id', + metavar='', + help=_("Specifies the Floating IP ID."), + ) + parser.add_argument( + '--floating-ip-address', + metavar='', + help=_("Specifies the Floating IP."), + ) + parser.add_argument( + '--status', + metavar='', + help=_("Specifies the status of the SNAT rule.\n" + "ACTIVE: The resource status is normal.\n" + "PENDING_CREATE: The resource is being created.\n" + "PENDING_UPDATE: The resource is being updated.\n" + "PENDING_DELETE: The resource is being deleted.\n" + "EIP_FREEZED: The EIP of the resource is frozen.\n" + "INACTIVE: The resource status is abnormal."), + ) + parser.add_argument( + '--admin-state-up', + metavar='', + help=_("Specifies whether the SNAT rule is enabled or " + "disabled. The value can be:\n" + "true: The SNAT rule is enabled.\n" + "false: The SNAT rule is disabled."), + ) + parser.add_argument( + '--created-at', + metavar='', + help=_("Specifies when the SNAT rule is created (UTC time). " + "Its value rounds to 6 decimal places for seconds. " + "The format is yyyy-mm-ddhh:mm:ss."), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.nat + args_list = [ + 'id', + 'limit', + 'network_id', + 'project_id', + 'nat_gateway_id', + 'network_id', + 'cidr', + 'source_type', + 'floating_ip_id', + 'floating_ip_address', + 'status', + 'admin_state_up', + 'created_at'] + attrs = {} + for arg in args_list: + val = getattr(parsed_args, arg) + if val: + attrs[arg] = val + + data = client.snat_rules(**attrs) + + return ( + self.columns, + (utils.get_item_properties( + s, + self.columns, + ) for s in data) + ) + + +class ShowSnatRule(command.ShowOne): + _description = _("Show Snat Rule details") + + def get_parser(self, prog_name): + parser = super(ShowSnatRule, self).get_parser(prog_name) + parser.add_argument( + 'snat', + metavar='', + help=_("Specifies the ID of the SNAT Rule."), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.nat + obj = client.get_snat_rule(parsed_args.snat) + + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + + return (display_columns, data) + + +class CreateSnatRule(command.ShowOne): + _description = _("Create new SNAT Rule") + + def get_parser(self, prog_name): + parser = super(CreateSnatRule, self).get_parser(prog_name) + parser.add_argument( + '--nat-gateway-id', + required=True, + metavar='', + help=_("Specifies the ID of the NAT gateway."), + ) + parser.add_argument( + '--floating-ip-id', + metavar='', + required=True, + help=_("Specifies the Floating IP ID. Multiple " + "Floating IPs are separated using commas."), + ) + parser.add_argument( + '--network-id', + metavar='', + help=_("Specifies the network ID used by the SNAT rule. " + "This parameter and cidr are alternative."), + ) + parser.add_argument( + '--cidr', + metavar='', + help=_("Specifies CIDR, which can be in the format of a " + "network segment or a host IP address."), + ) + parser.add_argument( + '--source-type', + metavar='', + help=_( + "Specifies the source type.\n" + "0: Either network_id or cidr can be " + "specified in a VPC.\n" + "1: Only cidr can be specified over a " + "Direct Connect connection.\n" + "If no value is entered, the default " + "value 0 (VPC) is used."), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.nat + + args_list = [ + 'nat_gateway_id', + 'floating_ip_id', + 'network_id', + 'cidr', + 'source_type' + ] + attrs = {} + for arg in args_list: + val = getattr(parsed_args, arg) + if val: + attrs[arg] = val + + obj = client.create_snat_rule(**attrs) + + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + + return (display_columns, data) + + +class DeleteSnatRule(command.Command): + + _description = _("Deletes Snat Rule(s).") + + def get_parser(self, prog_name): + parser = super(DeleteSnatRule, self).get_parser(prog_name) + parser.add_argument( + 'snat', + metavar='', + nargs='+', + help=_("Specifies the SNAT rule(s) ID(s) to delete."), + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.nat + result = 0 + for snat in parsed_args.snat: + try: + obj = client.get_snat_rule(snat) + client.delete_snat_rule(obj.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete SNAT rule with " + "ID '%(snat)s': %(e)s"), + {'snat': snat, 'e': e}) + if result > 0: + total = len(parsed_args.snat) + msg = (_("%(result)s of %(total)s SNAT Rule(s) failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) diff --git a/otcextensions/sdk/__init__.py b/otcextensions/sdk/__init__.py index 7da252974..1c1c403b2 100644 --- a/otcextensions/sdk/__init__.py +++ b/otcextensions/sdk/__init__.py @@ -144,6 +144,9 @@ 'append_project_id': True, 'endpoint_service_type': 'vbs', }, + 'vpc': { + 'service_type': 'vpc', + }, 'waf': { 'service_type': 'waf', 'append_project_id': True, diff --git a/otcextensions/sdk/vpc/__init__.py b/otcextensions/sdk/vpc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/sdk/vpc/v2/__init__.py b/otcextensions/sdk/vpc/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/sdk/vpc/v2/_base.py b/otcextensions/sdk/vpc/v2/_base.py new file mode 100644 index 000000000..5d8322d64 --- /dev/null +++ b/otcextensions/sdk/vpc/v2/_base.py @@ -0,0 +1,18 @@ +# 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 resource + + +class Resource(resource.Resource): + + base_path = '' + service_expectes_json_type = True diff --git a/otcextensions/sdk/vpc/v2/_proxy.py b/otcextensions/sdk/vpc/v2/_proxy.py new file mode 100644 index 000000000..075364e60 --- /dev/null +++ b/otcextensions/sdk/vpc/v2/_proxy.py @@ -0,0 +1,129 @@ +# 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 otcextensions.sdk.vpc.v2 import peering as _peering + +from openstack import proxy + + +class Proxy(proxy.Proxy): + + skip_discovery = True + + # ======== Peering ======== + def create_peering(self, **attrs): + """Create a new vpc peering from attributes + + :param dict attrs: Keyword arguments which will be used to create + a :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + """ + return self._create(_peering.Peering, **attrs) + + def delete_peering(self, peering, ignore_missing=True): + """Delete a vpc peering + + :param peering: key id or an instance of + :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + :param bool ignore_missing: When set to ``False`` + :class:`~openstack.exceptions.ResourceNotFound` will be raised when + the vpc peering does not exist. + When set to ``True``, no exception will be set when attempting to + delete a nonexistent peering. + + :returns: Gateway been deleted + :rtype: :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + """ + return self._delete(_peering.Peering, peering, + ignore_missing=ignore_missing) + + def peerings(self, **query): + """Return a generator of vpc peerings + + :param dict query: Optional query parameters to be sent to limit + the resources being returned. + + :returns: A generator of vpc peering objects + + :rtype: :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + """ + return self._list(_peering.Peering, **query) + + def get_peering(self, peering): + """Get a single vpc peering + + :param peering: The value can be the ID of a vpc peering or a + :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + instance. + + :returns: One :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + """ + return self._get(_peering.Peering, peering) + + def find_peering(self, name_or_id, ignore_missing=False): + """Find a single vpc peering + + :param name_or_id: The name or ID of a zone + :param bool ignore_missing: When set to ``False`` + :class:`~openstack.exceptions.ResourceNotFound` will be raised + when the vpc peering does not exist. + When set to ``True``, no exception will be set when attempting + to delete a nonexistent peering. + + :returns: One :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + """ + return self._find(_peering.Peering, name_or_id, + ignore_missing=ignore_missing) + + def update_peering(self, peering, **attrs): + """Update a vpc peering + + :param peering: Either the ID of a vpc peering or a + :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + instance. + :param dict attrs: The attributes to update on the vpc peering + represented by ``peering``. + + :returns: The updated peering + + :rtype: :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + """ + return self._update(_peering.Peering, peering, **attrs) + + def accept_peering(self, peering): + """Accept a vpc peering + + :param peering: Either the ID of a vpc peering or a + :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + instance. + + :returns: The updated peering + + :rtype: :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + """ + peering = self._get_resource(_peering.Peering, peering) + return peering.approval(self, 'accept') + + def reject_peering(self, peering): + """Accept a vpc peering + + :param peering: Either the ID of a vpc peering or a + :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + instance. + + :returns: The updated peering + + :rtype: :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + """ + peering = self._get_resource(_peering.Peering, peering) + return peering.approval(self, 'reject') diff --git a/otcextensions/sdk/vpc/v2/peering.py b/otcextensions/sdk/vpc/v2/peering.py new file mode 100644 index 000000000..9ec0b16d6 --- /dev/null +++ b/otcextensions/sdk/vpc/v2/peering.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. +from openstack import exceptions +from openstack import resource +from openstack import utils + +from otcextensions.sdk.vpc.v2 import _base + + +class Peering(_base.Resource): + resources_key = 'peerings' + resource_key = 'peering' + base_path = '/vpc/peerings' + + # capabilities + allow_create = True + allow_fetch = True + allow_commit = True + allow_delete = True + allow_list = True + + _query_mapping = resource.QueryParameters( + 'id', 'marker', 'limit', 'name', 'router_id', + 'status', 'project_id', project_id='tenant_id', + router_id='vpc_id' + ) + + #: Specifies the VPC peering connection ID. + #: *Type: uuid* + id = resource.Body('id') + #: Specifies information about the local VPC. + #: *Type: dict* + accept_vpc_info = resource.Body('accept_vpc_info', type=dict) + #: Specifies information about the local VPC. + #: *Type: dict* + request_vpc_info = resource.Body('request_vpc_info', type=dict) + #: Specifies the name of the VPC peering connection. + name = resource.Body('name') + #: Specifies the status. + status = resource.Body('status') + #: Provides supplementary information about the VPC peering connection. + description = resource.Body('description') + #: Specifies the time (UTC) when the VPC peering connection is created. + #: Format is *yyyy-mm-dd hh:mm:ss*. + created_at = resource.Body('created_at') + #: Specifies the time (UTC) when the VPC peering connection is updated. + #: Format is *yyyy-mm-dd hh:mm:ss*. + created_at = resource.Body('updated_at') + #: Specifies the project ID + project_id = resource.Body('tenant_id') + #: Specifies that the router ID is used as the filtering condition. + router_id = resource.Body('vpc_id') + #: Specifies the start resource ID of pagination query. + #: If the parameter is left blank, only resources on the + #: first page are queried. + marker = resource.Body('marker') + + def approval(self, session, request_type): + """Accept/Reject Peering Request""" + url = utils.urljoin(self.base_path, self.id, request_type) + response = session.put(url) + exceptions.raise_from_response(response) + self._translate_response(response) + return self diff --git a/otcextensions/sdk/vpc/vpc_service.py b/otcextensions/sdk/vpc/vpc_service.py new file mode 100644 index 000000000..b1749d49c --- /dev/null +++ b/otcextensions/sdk/vpc/vpc_service.py @@ -0,0 +1,22 @@ +# 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 service_description + +from otcextensions.sdk.vpc.v2 import _proxy + + +class VpcService(service_description.ServiceDescription): + """The NAT service.""" + + supported_versions = { + '2': _proxy.Proxy + } diff --git a/otcextensions/tests/functional/sdk/vpc/__init__.py b/otcextensions/tests/functional/sdk/vpc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/functional/sdk/vpc/v2/__init__.py b/otcextensions/tests/functional/sdk/vpc/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/functional/sdk/vpc/v2/test_service.py b/otcextensions/tests/functional/sdk/vpc/v2/test_service.py new file mode 100644 index 000000000..88d1a1744 --- /dev/null +++ b/otcextensions/tests/functional/sdk/vpc/v2/test_service.py @@ -0,0 +1,24 @@ +# 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 _log + +from otcextensions.tests.functional import base + +_logger = _log.setup_logging('openstack') + + +class TestService(base.BaseFunctionalTest): + + def test_initialize(self): + client = self.conn.vpc + + self.assertIsNotNone(client) diff --git a/otcextensions/tests/unit/sdk/vpc/__init__.py b/otcextensions/tests/unit/sdk/vpc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/unit/sdk/vpc/v2/__init__.py b/otcextensions/tests/unit/sdk/vpc/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/unit/sdk/vpc/v2/test_peering.py b/otcextensions/tests/unit/sdk/vpc/v2/test_peering.py new file mode 100644 index 000000000..b8d38040a --- /dev/null +++ b/otcextensions/tests/unit/sdk/vpc/v2/test_peering.py @@ -0,0 +1,91 @@ +# 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 keystoneauth1 import adapter + +import copy +import mock + +from openstack.tests.unit import base +from otcextensions.sdk.vpc.v2 import peering + + +IDENTIFIER = 'ID' +EXAMPLE = { + "name": "test", + "id": "22b76469-08e3-4937-8c1d-7aad34892be1", + "request_vpc_info": { + "vpc_id": "9daeac7c-a98f-430f-8e38-67f9c044e299", + "tenant_id": "f65e9ebc-ed5d-418b-a931-9a723718ba4e" + }, + "accept_vpc_info": { + "vpc_id": "f583c072-0bb8-4e19-afb2-afb7c1693be5", + "tenant_id": "f65e9ebc-ed5d-418b-a931-9a723718ba4e" + }, + "status": "ACTIVE" +} + + +class TestPeering(base.TestCase): + + def setUp(self): + super(TestPeering, self).setUp() + self.sess = mock.Mock(spec=adapter.Adapter) + self.sess.put = mock.Mock() + + def test_basic(self): + sot = peering.Peering() + self.assertEqual('peering', sot.resource_key) + self.assertEqual('peerings', sot.resources_key) + path = '/vpc/peerings' + self.assertEqual(path, sot.base_path) + self.assertTrue(sot.allow_list) + self.assertTrue(sot.allow_create) + self.assertTrue(sot.allow_fetch) + self.assertTrue(sot.allow_commit) + self.assertTrue(sot.allow_delete) + + def test_make_it(self): + sot = peering.Peering(**EXAMPLE) + self.assertEqual(EXAMPLE['status'], sot.status) + self.assertEqual(EXAMPLE['request_vpc_info'], sot.request_vpc_info) + self.assertEqual(EXAMPLE['accept_vpc_info'], sot.accept_vpc_info) + self.assertEqual(EXAMPLE['id'], sot.id) + self.assertEqual(EXAMPLE['name'], sot.name) + + def test_approval(self): + sot = peering.Peering(id=IDENTIFIER) + + resp = mock.Mock() + resp.body = { + "name": "test", + "id": "22b76469-08e3-4937-8c1d-7aad34892be1", + "request_vpc_info": { + "vpc_id": "9daeac7c-a98f-430f-8e38-67f9c044e299", + "tenant_id": "f65e9ebc-ed5d-418b-a931-9a723718ba4e" + }, + "accept_vpc_info": { + "vpc_id": "f583c072-0bb8-4e19-afb2-afb7c1693be5", + "tenant_id": "f65e9ebc-ed5d-418b-a931-9a723718ba4e" + }, + "status": "REJECTED" + } + resp.json = mock.Mock(return_value=copy.deepcopy(resp.body)) + resp.headers = {} + resp.status_code = 200 + self.sess.put.return_value = resp + + approval_resp = sot.approval(self.sess, 'reject') + self.sess.put.assert_called_with( + 'vpc/peerings/ID/reject' + ) + self.assertEqual(resp.body['name'], approval_resp.name) + self.assertEqual(resp.body['id'], approval_resp.id) diff --git a/otcextensions/tests/unit/sdk/vpc/v2/test_proxy.py b/otcextensions/tests/unit/sdk/vpc/v2/test_proxy.py new file mode 100644 index 000000000..5f419feed --- /dev/null +++ b/otcextensions/tests/unit/sdk/vpc/v2/test_proxy.py @@ -0,0 +1,70 @@ +# 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. + +# 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 otcextensions.sdk.vpc.v2 import _proxy +from otcextensions.sdk.vpc.v2 import peering + +from openstack.tests.unit import test_proxy_base + + +class TestVpcProxy(test_proxy_base.TestProxyBase): + def setUp(self): + super(TestVpcProxy, self).setUp() + self.proxy = _proxy.Proxy(self.session) + + +class TestVpcPeering(TestVpcProxy): + def test_peering_create(self): + self.verify_create(self.proxy.create_peering, peering.Peering, + method_kwargs={'name': 'id'}, + expected_kwargs={'name': 'id'}) + + def test_peering_delete(self): + self.verify_delete(self.proxy.delete_peering, + peering.Peering, True) + + def test_peering_get(self): + self.verify_get(self.proxy.get_peering, peering.Peering) + + def test_peerings(self): + self.verify_list(self.proxy.peerings, peering.Peering) + + def test_peering_update(self): + self.verify_update(self.proxy.update_peering, peering.Peering) + + def test_peering_accept(self): + self._verify( + 'otcextensions.sdk.vpc.v2.peering.Peering.approval', + self.proxy.accept_peering, + method_args=["accept"], + expected_args=["accept"] + ) + + def test_peering_reject(self): + self._verify( + 'otcextensions.sdk.vpc.v2.peering.Peering.approval', + self.proxy.reject_peering, + method_args=["reject"], + expected_args=["reject"] + ) From 0fd0a83f2a5023c11e0b684c9169021cd4948e81 Mon Sep 17 00:00:00 2001 From: Vineet Pruthi Date: Fri, 12 Jun 2020 09:56:14 +0000 Subject: [PATCH 02/12] Adding vpc peering cli functions --- otcextensions/osclient/vpc/client.py | 6 +- otcextensions/osclient/vpc/v2/dnat.py | 297 ----------- otcextensions/osclient/vpc/v2/gateway.py | 308 ----------- otcextensions/osclient/vpc/v2/peering.py | 331 ++++++++++++ otcextensions/osclient/vpc/v2/snat.py | 276 ---------- .../tests/unit/osclient/vpc/__init__.py | 0 .../tests/unit/osclient/vpc/v2/__init__.py | 0 .../tests/unit/osclient/vpc/v2/fakes.py | 68 +++ .../unit/osclient/vpc/v2/test_peering.py | 500 ++++++++++++++++++ setup.cfg | 10 + 10 files changed, 912 insertions(+), 884 deletions(-) delete mode 100644 otcextensions/osclient/vpc/v2/dnat.py delete mode 100644 otcextensions/osclient/vpc/v2/gateway.py create mode 100644 otcextensions/osclient/vpc/v2/peering.py delete mode 100644 otcextensions/osclient/vpc/v2/snat.py create mode 100644 otcextensions/tests/unit/osclient/vpc/__init__.py create mode 100644 otcextensions/tests/unit/osclient/vpc/v2/__init__.py create mode 100644 otcextensions/tests/unit/osclient/vpc/v2/fakes.py create mode 100644 otcextensions/tests/unit/osclient/vpc/v2/test_peering.py diff --git a/otcextensions/osclient/vpc/client.py b/otcextensions/osclient/vpc/client.py index 1292b0ea5..5f031ebd2 100644 --- a/otcextensions/osclient/vpc/client.py +++ b/otcextensions/osclient/vpc/client.py @@ -28,7 +28,7 @@ def make_client(instance): - """Returns a rds proxy""" + """Returns a vpc proxy""" conn = instance.sdk_connection @@ -45,8 +45,8 @@ def build_option_parser(parser): """Hook to add global options""" parser.add_argument( '--os-vpc-api-version', - metavar='', - default=utils.env('OS_NAT_API_VERSION'), + metavar='', + default=utils.env('OS_VPC_API_VERSION'), help=_("VPC API version, default=%s " "(Env: OS_VPC_API_VERSION)") % DEFAULT_API_VERSION ) diff --git a/otcextensions/osclient/vpc/v2/dnat.py b/otcextensions/osclient/vpc/v2/dnat.py deleted file mode 100644 index d241a650f..000000000 --- a/otcextensions/osclient/vpc/v2/dnat.py +++ /dev/null @@ -1,297 +0,0 @@ -# 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. -# -"""DNAT v2 action implementations""" -import logging - -from osc_lib import utils -from osc_lib import exceptions -from osc_lib.command import command - -from otcextensions.i18n import _ -from otcextensions.common import sdk_utils - -LOG = logging.getLogger(__name__) - - -def _get_columns(item): - column_map = { - } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) - - -class ListDnatRules(command.Lister): - - _description = _("List DNAT Rules.") - columns = ( - 'Id', - 'Nat Gateway Id', - 'Port Id', - 'Private IP', - 'Floating Ip Address', - 'Protocol', - 'Status' - ) - - def get_parser(self, prog_name): - parser = super(ListDnatRules, self).get_parser(prog_name) - - parser.add_argument( - '--id', - metavar='', - help=_("Specifies the ID of the SNAT rule."), - ) - parser.add_argument( - '--limit', - metavar='', - type=int, - help=_("Limit to fetch number of records."), - ) - parser.add_argument( - '--project-id', - metavar='', - help=_("Specifies the project ID."), - ) - parser.add_argument( - '--nat-gateway-id', - metavar='', - help=_("Specifies the NAT gateway ID."), - ) - parser.add_argument( - '--port-id', - metavar='', - help=_("Specifies the port ID of an ECS or a BMS."), - ) - parser.add_argument( - '--private-ip', - metavar='', - help=_("Specifies the private IP address, for example, " - "the IP address of a Direct Connect connection."), - ) - parser.add_argument( - '--internal-service-port', - metavar='', - help=_("Specifies port used by ECSs or BMSs to provide " - "services for external systems."), - ) - parser.add_argument( - '--floating-ip-id', - metavar='', - help=_("Specifies the Floating IP ID."), - ) - parser.add_argument( - '--floating-ip-address', - metavar='', - help=_("Specifies the Floating IP."), - ) - parser.add_argument( - '--external-service-port', - metavar='', - help=_("Specifies the port for providing external services."), - ) - parser.add_argument( - '--protocol', - metavar='', - help=_("Specifies the protocol type." - "Currently, TCP, UDP, and ANY are supported."), - ) - parser.add_argument( - '--status', - metavar='', - help=_("Specifies the status of the DNAT rule.\n" - "ACTIVE: The resource status is normal.\n" - "PENDING_CREATE: The resource is being created.\n" - "PENDING_UPDATE: The resource is being updated.\n" - "PENDING_DELETE: The resource is being deleted.\n" - "EIP_FREEZED: The EIP of the resource is frozen.\n" - "INACTIVE: The resource status is abnormal."), - ) - parser.add_argument( - '--admin-state-up', - metavar='', - help=_("Specifies whether the DNAT rule is enabled or " - "disabled. The value can be:\n" - "true: The DNAT rule is enabled.\n" - "false: The DNAT rule is disabled."), - ) - parser.add_argument( - '--created-at', - metavar='', - help=_("Specifies when the DNAT rule is created (UTC time). " - "Its value rounds to 6 decimal places forseconds. " - "The format is yyyy-mm-ddhh:mm:ss."), - ) - return parser - - def take_action(self, parsed_args): - client = self.app.client_manager.nat - args_list = [ - 'id', - 'limit', - 'project_id', - 'nat_gateway_id', - 'port_id', - 'private_ip', - 'internal_service_port', - 'floating_ip_id', - 'floating_ip_address', - 'external_service_port', - 'protocol', - 'status', - 'admin_state_up', - 'created_at' - ] - attrs = {} - for arg in args_list: - val = getattr(parsed_args, arg) - if val: - attrs[arg] = val - data = client.dnat_rules(**attrs) - - return ( - self.columns, - (utils.get_item_properties( - s, - self.columns, - ) for s in data) - ) - - -class ShowDnatRule(command.ShowOne): - _description = _("Show Dnat Rule details") - - def get_parser(self, prog_name): - parser = super(ShowDnatRule, self).get_parser(prog_name) - parser.add_argument( - 'dnat', - metavar='', - help=_("Specifies the ID of the SNAT Rule"), - ) - return parser - - def take_action(self, parsed_args): - client = self.app.client_manager.nat - obj = client.get_dnat_rule(parsed_args.dnat) - - display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) - - return (display_columns, data) - - -class CreateDnatRule(command.ShowOne): - _description = _("Create new DNAT Rule") - - def get_parser(self, prog_name): - parser = super(CreateDnatRule, self).get_parser(prog_name) - parser.add_argument( - '--nat-gateway-id', - metavar='', - required=True, - help=_("Specifies the ID of the NAT Gateway."), - ) - parser.add_argument( - '--port-id', - metavar='', - help=_("Specifies the port ID of an ECS or a BMS."), - ) - parser.add_argument( - '--private-ip', - metavar='', - help=_("Specifies the private IP address, for example, " - "the IP address of a Direct Connect connection."), - ) - parser.add_argument( - '--internal-service-port', - metavar='', - required=True, - help=_("Specifies port used by ECSs or BMSs to provide " - "services for external systems."), - ) - parser.add_argument( - '--floating-ip-id', - metavar="", - required=True, - help=_("Specifies the Floating IP ID. Multiple " - "Floating IPs are separated using commas."), - ) - parser.add_argument( - '--external-service-port', - metavar='', - required=True, - help=_("Specifies the port for providing external services."), - ) - parser.add_argument( - '--protocol', - metavar='', - required=True, - help=_("Specifies the protocol type."), - ) - return parser - - def take_action(self, parsed_args): - client = self.app.client_manager.nat - - args_list = [ - 'nat_gateway_id', - 'port_id', - 'private_ip', - 'internal_service_port', - 'floating_ip_id', - 'external_service_port', - 'protocol' - ] - attrs = {} - for arg in args_list: - val = getattr(parsed_args, arg) - if val: - attrs[arg] = val - - obj = client.create_dnat_rule(**attrs) - - display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) - - return (display_columns, data) - - -class DeleteDnatRule(command.Command): - - _description = _("Deletes Dnat Rule(s).") - - def get_parser(self, prog_name): - parser = super(DeleteDnatRule, self).get_parser(prog_name) - parser.add_argument( - 'dnat', - metavar='', - nargs='+', - help=_("Specifies the DNAT Rule(s) ID(s) to delete."), - ) - return parser - - def take_action(self, parsed_args): - client = self.app.client_manager.nat - result = 0 - for dnat in parsed_args.dnat: - try: - obj = client.get_dnat_rule(dnat) - client.delete_dnat_rule(obj.id) - except Exception as e: - result += 1 - LOG.error(_("Failed to delete DNAT rule with " - "ID '%(dnat)s': %(e)s"), - {'dnat': dnat, 'e': e}) - if result > 0: - total = len(parsed_args.dnat) - msg = (_("%(result)s of %(total)s DNAT Rule(s) failed " - "to delete.") % {'result': result, 'total': total}) - raise exceptions.CommandError(msg) diff --git a/otcextensions/osclient/vpc/v2/gateway.py b/otcextensions/osclient/vpc/v2/gateway.py deleted file mode 100644 index 37f34be56..000000000 --- a/otcextensions/osclient/vpc/v2/gateway.py +++ /dev/null @@ -1,308 +0,0 @@ -# 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. -# -"""NAT Gateway v2 action implementations""" -import logging - -from osc_lib import utils -from osc_lib import exceptions -from osc_lib.command import command - -from otcextensions.i18n import _ -from otcextensions.common import sdk_utils - -LOG = logging.getLogger(__name__) - - -def _get_columns(item): - column_map = { - } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) - - -class ListNatGateways(command.Lister): - - _description = _("List Nat Gateway.") - columns = ('Id', 'Name', 'Spec', 'Router Id', 'Status') - - def get_parser(self, prog_name): - parser = super(ListNatGateways, self).get_parser(prog_name) - - parser.add_argument( - '--id', - metavar='', - help=_("Specifies the ID of the NAT Gateway."), - ) - parser.add_argument( - '--limit', - metavar='', - type=int, - help=_("Limit to fetch number of records."), - ) - parser.add_argument( - '--project-id', - metavar='', - help=_("Specifies the project ID."), - ) - parser.add_argument( - '--name', - metavar='', - help=_("Specifies the Name of the NAT Gateway."), - ) - parser.add_argument( - '--spec', - metavar='', - help=_("Specifies the type of the NAT Gateway. " - "The value of spec can be:\n" - "1: small type, which supports up to 10,000 " - "SNAT connections.\n" - "2: medium type, which supports up to 50,000 " - "SNAT connections.\n" - "3: large type, which supports up to 200,000 " - "SNAT connections.\n" - "4: extra-large type, which supports up to " - "1,000,000 SNAT connections."), - ) - parser.add_argument( - '--router-id', - metavar='', - help=_("Specifies the router ID."), - ) - parser.add_argument( - '--internal-network-id', - metavar='', - help=_("Specifies the network ID of the downstream " - "interface (the next hop of the DVR) of the " - "NAT Gateway."), - ) - parser.add_argument( - '--status', - metavar='', - help=_("Specifies the status of the NAT Gateway.\n" - "ACTIVE: The resource status is normal.\n" - "PENDING_CREATE: The resource is being created.\n" - "PENDING_UPDATE: The resource is being updated.\n" - "PENDING_DELETE: The resource is being deleted.\n" - "EIP_FREEZED: The EIP of the resource is frozen.\n" - "INACTIVE: The resource status is abnormal."), - ) - parser.add_argument( - '--admin-state-up', - metavar='', - help=_("Specifies whether the NAT Gateway is enabled " - "or disabled. The value can be:\n" - "true: The NAT gateway is up.\n" - "false: The NAT gateway is down."), - ) - parser.add_argument( - '--created-at', - metavar='', - help=_("Specifies when the NAT Gateway is created (UTC time). " - "Its valuerounds to 6 decimal places forseconds. " - "The format is yyyy-mm-ddhh:mm:ss."), - ) - return parser - - def take_action(self, parsed_args): - client = self.app.client_manager.nat - args_list = [ - 'id', - 'limit', - 'project_id', - 'name', - 'spec', - 'router_id', - 'internal_network_id', - 'status', - 'admin_state_up', - 'created_at'] - attrs = {} - for arg in args_list: - val = getattr(parsed_args, arg) - if val: - attrs[arg] = val - - data = client.gateways(**attrs) - - return (self.columns, (utils.get_item_properties(s, self.columns) - for s in data)) - - -class ShowNatGateway(command.ShowOne): - _description = _("Show NAT Gateway details") - - def get_parser(self, prog_name): - parser = super(ShowNatGateway, self).get_parser(prog_name) - parser.add_argument( - 'gateway', - metavar='', - help=_("Specifies the Name or ID of the NAT Gateway."), - ) - return parser - - def take_action(self, parsed_args): - client = self.app.client_manager.nat - obj = client.find_gateway(parsed_args.gateway) - - display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) - - return (display_columns, data) - - -class CreateNatGateway(command.ShowOne): - _description = _("Create new NAT Gateway") - - def get_parser(self, prog_name): - parser = super(CreateNatGateway, self).get_parser(prog_name) - parser.add_argument( - 'name', - metavar='', - help=_("Specifies the name of the NAT Gateway."), - ) - parser.add_argument( - '--description', - metavar='', - help=_("Provides supplementary information about " - "the NAT Gateway."), - ) - parser.add_argument( - '--spec', - metavar='', - required=True, - help=_( - "Specifies the type of the NAT Gateway. " - "The value can be:\n" - "1: small type, which supports up to 10,000 " - "SNAT connections.\n" - "2: medium type, which supports up to 50,000 " - "SNAT connections.\n" - "3: large type, which supports up to 200,000 " - "SNAT connections.\n" - "4: extra-large type, which supports up to " - "1,000,000 SNAT connections."), - ) - parser.add_argument( - '--router-id', - metavar='', - required=True, - help=_("Specifies the VPC ID."), - ) - parser.add_argument( - '--internal-network-id', - metavar='', - required=True, - help=_("Specifies the network ID of the downstream interface " - "(the next hop of the DVR) of the NAT Gateway."), - ) - return parser - - def take_action(self, parsed_args): - client = self.app.client_manager.nat - - args_list = [ - 'name', - 'description', - 'spec', - 'router_id', - 'internal_network_id'] - attrs = {} - for arg in args_list: - val = getattr(parsed_args, arg) - if val: - attrs[arg] = val - - obj = client.create_gateway(**attrs) - - display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) - - return (display_columns, data) - - -class UpdateNatGateway(command.ShowOne): - _description = _("Update a NAT Gateway.") - - def get_parser(self, prog_name): - parser = super(UpdateNatGateway, self).get_parser(prog_name) - parser.add_argument( - 'gateway', - metavar='', - help=_("Specifies the Name or ID of the NAT Gateway."), - ) - parser.add_argument( - '--name', - metavar='', - help=_("Specifies the name of the NAT Gateway."), - ) - parser.add_argument( - '--description', - metavar='', - help=_("Provides supplementary informationabout the NAT gateway."), - ) - parser.add_argument( - '--spec', - metavar='', - help=_("Specifies the type of the NAT Gateway."), - ) - return parser - - def take_action(self, parsed_args): - client = self.app.client_manager.nat - args_list = [ - 'name', 'description', 'spec' - ] - attrs = {} - for arg in args_list: - if getattr(parsed_args, arg): - attrs[arg] = getattr(parsed_args, arg) - nat_gateway = client.find_gateway(parsed_args.gateway) - - obj = client.update_gateway(nat_gateway.id, **attrs) - - display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) - - return (display_columns, data) - - -class DeleteNatGateway(command.Command): - - _description = _("Deletes NAT Gateway.") - - def get_parser(self, prog_name): - parser = super(DeleteNatGateway, self).get_parser(prog_name) - parser.add_argument( - 'gateway', - metavar='', - nargs='+', - help=_("Nat Gateway(s) to delete (Name or ID)"), - ) - return parser - - def take_action(self, parsed_args): - client = self.app.client_manager.nat - result = 0 - for gateway in parsed_args.gateway: - try: - obj = client.find_gateway(gateway) - client.delete_gateway(obj.id) - except Exception as e: - result += 1 - LOG.error(_("Failed to delete Nat Gateway with " - "name or ID '%(gateway)s': %(e)s"), - {'gateway': gateway, 'e': e}) - if result > 0: - total = len(parsed_args.gateway) - msg = (_("%(result)s of %(total)s NAT Gateway(s) failed " - "to delete.") % {'result': result, 'total': total}) - raise exceptions.CommandError(msg) diff --git a/otcextensions/osclient/vpc/v2/peering.py b/otcextensions/osclient/vpc/v2/peering.py new file mode 100644 index 000000000..47793faae --- /dev/null +++ b/otcextensions/osclient/vpc/v2/peering.py @@ -0,0 +1,331 @@ +# 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. +# +"""VPC Peering v2 action implementations""" +import logging + +from osc_lib import utils +from osc_lib.cli import parseractions +from osc_lib import exceptions +from osc_lib.command import command + +from otcextensions.i18n import _ +from otcextensions.common import sdk_utils + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = { + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + +def _format_vpc_info(vpc_info_list): + # Map the route keys to match --route option. + if len(vpc_info_list) > 1: + raise RuntimeError('Repeating argument is not supported') + vpc_info = vpc_info_list[0] + if 'router_id' in vpc_info: + vpc_info['vpc_id'] = vpc_info.pop('router_id') + if 'project_id' in vpc_info: + vpc_info['tenant_id'] = vpc_info.pop('project_id') + return vpc_info + + +def translate_response(func): + def new(self, *args, **kwargs): + response = func(self, *args, **kwargs) + response.request_vpc_info[ + 'router_id'] = response.request_vpc_info.pop('vpc_id') + response.request_vpc_info[ + 'project_id'] = response.request_vpc_info.pop('tenant_id') + response.accept_vpc_info[ + 'router_id'] = response.accept_vpc_info.pop('vpc_id') + response.accept_vpc_info[ + 'project_id'] = response.accept_vpc_info.pop('tenant_id') + display_columns, columns = _get_columns(response) + data = utils.get_item_properties(response, columns) + return (display_columns, data) + new.__name__ = func.__name__ + new.__doc__ = func.__doc__ + return new + + +class ListVpcPeerings(command.Lister): + + _description = _("List Vpc Peerings.") + columns = ('Id', 'Name', 'Request Vpc Info', 'Accept Vpc Info', 'Status') + + def get_parser(self, prog_name): + parser = super(ListVpcPeerings, self).get_parser(prog_name) + + parser.add_argument( + '--id', + metavar='', + help=_("Specifies the ID of the VPC peering connection."), + ) + parser.add_argument( + '--limit', + metavar='', + type=int, + help=_("Limit to fetch number of records."), + ) + parser.add_argument( + '--marker', + metavar='', + help=_("Specifies the start resource ID of pagination query."), + ) + parser.add_argument( + '--project-id', + metavar='', + help=_("Specifies the project ID."), + ) + parser.add_argument( + '--name', + metavar='', + help=_("Specifies the Name of the VPC peering connection."), + ) + parser.add_argument( + '--router-id', + metavar='', + help=_("Specifies the router/vpc ID of requester/accepter " + "of vpc peering."), + ) + parser.add_argument( + '--status', + metavar='', + help=_("Specifies the status of the VPC peering connection.\n" + "Possible values are as follows:\n" + "PENDING_ACCEPTANCE\n" + "REJECTED\n" + "EXPIRED\n" + "DELETED\n" + "ACTIVE"), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + args_list = [ + 'id', + 'name', + 'limit', + 'marker', + 'project_id', + 'router_id', + 'status'] + attrs = {} + for arg in args_list: + val = getattr(parsed_args, arg) + if val: + attrs[arg] = val + + data = client.peerings(**attrs) + + return (self.columns, (utils.get_item_properties(s, self.columns) + for s in data)) + + +class ShowVpcPeering(command.ShowOne): + _description = _("Show VPC peering connection details.") + + def get_parser(self, prog_name): + parser = super(ShowVpcPeering, self).get_parser(prog_name) + parser.add_argument( + 'peering', + metavar='', + help=_("Specifies the Name or ID of the VPC peering connection."), + ) + return parser + + @translate_response + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + return client.find_peering(parsed_args.peering) + + +class AcceptVpcPeering(command.ShowOne): + _description = _("Accept VPC peering connection Request.") + + def get_parser(self, prog_name): + parser = super(AcceptVpcPeering, self).get_parser(prog_name) + parser.add_argument( + 'peering', + metavar='', + help=_("Specifies the Name or ID of the VPC peering connection."), + ) + return parser + + @translate_response + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + peering = client.find_peering(parsed_args.peering) + return client.accept_peering(peering.id) + + +class RejectVpcPeering(command.ShowOne): + _description = _("Reject VPC peering connection Request.") + + def get_parser(self, prog_name): + parser = super(RejectVpcPeering, self).get_parser(prog_name) + parser.add_argument( + 'peering', + metavar='', + help=_("Specifies the Name or ID of the VPC peering connection."), + ) + return parser + + @translate_response + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + peering = client.find_peering(parsed_args.peering) + return client.reject_peering(peering.id) + + +class CreateVpcPeering(command.ShowOne): + _description = _("Create new Vpc Peering.") + + def get_parser(self, prog_name): + parser = super(CreateVpcPeering, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help=_("Specifies the name of the VPC peering connection."), + ) + parser.add_argument( + '--description', + metavar='', + help=_("Specifies the description of the VPC peering connection."), + ) + parser.add_argument( + '--request-vpc-info', + metavar='router_id=,project_id=', + dest='request_vpc_info', + action=parseractions.MultiKeyValueAction, + required=True, + required_keys=['router_id'], + optional_keys=['project_id'], + help=_("Specifies information about the local VPC.\n" + "router_id: Specifies the ID of a VPC involved " + "in a VPC peering connection.\n" + "project_id: Specifies the ID of Requester's Project." + "project_id is optional. It is required only when creating " + "vpc peering connection with a vpc from different project"), + ) + parser.add_argument( + '--accept-vpc-info', + metavar='router_id=,project_id=', + dest='accept_vpc_info', + required=True, + action=parseractions.MultiKeyValueAction, + required_keys=['router_id'], + optional_keys=['project_id'], + help=_("Specifies information about the peer VPC.\n" + "router_id: Specifies the ID of a router involved " + "in a VPC peering connection.\n" + "project_id: Specifies the ID of Requester's Project." + "project_id is optional. It is required only when creating " + "vpc peering connection with a vpc from different project"), + ) + return parser + + @translate_response + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + attrs = {} + args_list = [ + 'name', + 'request_vpc_info', + 'accept_vpc_info', + 'description' + ] + for arg in args_list: + val = getattr(parsed_args, arg) + if arg in ['request_vpc_info', 'accept_vpc_info']: + val = _format_vpc_info(val) + if val: + attrs[arg] = val + + return client.create_peering(**attrs) + + +class UpdateVpcPeering(command.ShowOne): + _description = _("Update a VPC peering connection.") + + def get_parser(self, prog_name): + parser = super(UpdateVpcPeering, self).get_parser(prog_name) + parser.add_argument( + 'peering', + metavar='', + help=_("Specifies the Name or ID of the VPC peering connection."), + ) + parser.add_argument( + '--name', + metavar='', + help=_("Specifies the new name of the VPC peering connection."), + ) + parser.add_argument( + '--description', + metavar='', + help=_("Specifies the new descrition of the VPC peering " + "connection."), + ) + return parser + + @translate_response + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + args_list = [ + 'name', 'description' + ] + attrs = {} + for arg in args_list: + val = getattr(parsed_args, arg) + if val: + attrs[arg] = val + peering = client.find_peering(parsed_args.peering) + + return client.update_peering(peering.id, **attrs) + + +class DeleteVpcPeering(command.Command): + + _description = _("Deletes VPC Peering.") + + def get_parser(self, prog_name): + parser = super(DeleteVpcPeering, self).get_parser(prog_name) + parser.add_argument( + 'peering', + metavar='', + nargs='+', + help=_("VPC Peering(s) to delete (Name or ID)"), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + result = 0 + for peering in parsed_args.peering: + try: + obj = client.find_peering(peering) + client.delete_peering(obj.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete VPC peering connection with " + "name or ID '%(peering)s': %(e)s"), + {'peering': peering, 'e': e}) + if result > 0: + total = len(parsed_args.peering) + msg = (_("%(result)s of %(total)s VPC peering(s) failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) diff --git a/otcextensions/osclient/vpc/v2/snat.py b/otcextensions/osclient/vpc/v2/snat.py deleted file mode 100644 index 4dc54cabe..000000000 --- a/otcextensions/osclient/vpc/v2/snat.py +++ /dev/null @@ -1,276 +0,0 @@ -# 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. -# -"""SNAT v2 action implementations""" -import logging - -from osc_lib import utils -from osc_lib import exceptions -from osc_lib.command import command - -from otcextensions.i18n import _ -from otcextensions.common import sdk_utils - -LOG = logging.getLogger(__name__) - - -def _get_columns(item): - column_map = { - } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) - - -class ListSnatRules(command.Lister): - - _description = _("List SNAT Rules.") - columns = ( - 'Id', - 'Nat Gateway Id', - 'Network Id', - 'Cidr', - 'Floating Ip Address', - 'Status' - ) - - def get_parser(self, prog_name): - parser = super(ListSnatRules, self).get_parser(prog_name) - - parser.add_argument( - '--id', - metavar='', - help=_("Specifies the ID of the SNAT rule."), - ) - parser.add_argument( - '--limit', - metavar='', - type=int, - help=_("Limit to fetch number of records."), - ) - parser.add_argument( - '--project-id', - metavar='', - help=_("Specifies the project ID."), - ) - parser.add_argument( - '--nat-gateway-id', - metavar='', - help=_("Specifies the NAT gateway ID."), - ) - parser.add_argument( - '--network-id', - metavar='', - help=_("Specifies the network ID used by the SNAT rule."), - ) - parser.add_argument( - '--cidr', - metavar='', - help=_("Specifies a subset of the VPC subnet CIDR block or " - "a CIDR block of Direct Connect connection."), - ) - parser.add_argument( - '--source-type', - metavar='', - help=_("Specifies Source Type."), - ) - parser.add_argument( - '--floating-ip-id', - metavar='', - help=_("Specifies the Floating IP ID."), - ) - parser.add_argument( - '--floating-ip-address', - metavar='', - help=_("Specifies the Floating IP."), - ) - parser.add_argument( - '--status', - metavar='', - help=_("Specifies the status of the SNAT rule.\n" - "ACTIVE: The resource status is normal.\n" - "PENDING_CREATE: The resource is being created.\n" - "PENDING_UPDATE: The resource is being updated.\n" - "PENDING_DELETE: The resource is being deleted.\n" - "EIP_FREEZED: The EIP of the resource is frozen.\n" - "INACTIVE: The resource status is abnormal."), - ) - parser.add_argument( - '--admin-state-up', - metavar='', - help=_("Specifies whether the SNAT rule is enabled or " - "disabled. The value can be:\n" - "true: The SNAT rule is enabled.\n" - "false: The SNAT rule is disabled."), - ) - parser.add_argument( - '--created-at', - metavar='', - help=_("Specifies when the SNAT rule is created (UTC time). " - "Its value rounds to 6 decimal places for seconds. " - "The format is yyyy-mm-ddhh:mm:ss."), - ) - return parser - - def take_action(self, parsed_args): - client = self.app.client_manager.nat - args_list = [ - 'id', - 'limit', - 'network_id', - 'project_id', - 'nat_gateway_id', - 'network_id', - 'cidr', - 'source_type', - 'floating_ip_id', - 'floating_ip_address', - 'status', - 'admin_state_up', - 'created_at'] - attrs = {} - for arg in args_list: - val = getattr(parsed_args, arg) - if val: - attrs[arg] = val - - data = client.snat_rules(**attrs) - - return ( - self.columns, - (utils.get_item_properties( - s, - self.columns, - ) for s in data) - ) - - -class ShowSnatRule(command.ShowOne): - _description = _("Show Snat Rule details") - - def get_parser(self, prog_name): - parser = super(ShowSnatRule, self).get_parser(prog_name) - parser.add_argument( - 'snat', - metavar='', - help=_("Specifies the ID of the SNAT Rule."), - ) - return parser - - def take_action(self, parsed_args): - client = self.app.client_manager.nat - obj = client.get_snat_rule(parsed_args.snat) - - display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) - - return (display_columns, data) - - -class CreateSnatRule(command.ShowOne): - _description = _("Create new SNAT Rule") - - def get_parser(self, prog_name): - parser = super(CreateSnatRule, self).get_parser(prog_name) - parser.add_argument( - '--nat-gateway-id', - required=True, - metavar='', - help=_("Specifies the ID of the NAT gateway."), - ) - parser.add_argument( - '--floating-ip-id', - metavar='', - required=True, - help=_("Specifies the Floating IP ID. Multiple " - "Floating IPs are separated using commas."), - ) - parser.add_argument( - '--network-id', - metavar='', - help=_("Specifies the network ID used by the SNAT rule. " - "This parameter and cidr are alternative."), - ) - parser.add_argument( - '--cidr', - metavar='', - help=_("Specifies CIDR, which can be in the format of a " - "network segment or a host IP address."), - ) - parser.add_argument( - '--source-type', - metavar='', - help=_( - "Specifies the source type.\n" - "0: Either network_id or cidr can be " - "specified in a VPC.\n" - "1: Only cidr can be specified over a " - "Direct Connect connection.\n" - "If no value is entered, the default " - "value 0 (VPC) is used."), - ) - return parser - - def take_action(self, parsed_args): - client = self.app.client_manager.nat - - args_list = [ - 'nat_gateway_id', - 'floating_ip_id', - 'network_id', - 'cidr', - 'source_type' - ] - attrs = {} - for arg in args_list: - val = getattr(parsed_args, arg) - if val: - attrs[arg] = val - - obj = client.create_snat_rule(**attrs) - - display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) - - return (display_columns, data) - - -class DeleteSnatRule(command.Command): - - _description = _("Deletes Snat Rule(s).") - - def get_parser(self, prog_name): - parser = super(DeleteSnatRule, self).get_parser(prog_name) - parser.add_argument( - 'snat', - metavar='', - nargs='+', - help=_("Specifies the SNAT rule(s) ID(s) to delete."), - ) - - return parser - - def take_action(self, parsed_args): - client = self.app.client_manager.nat - result = 0 - for snat in parsed_args.snat: - try: - obj = client.get_snat_rule(snat) - client.delete_snat_rule(obj.id) - except Exception as e: - result += 1 - LOG.error(_("Failed to delete SNAT rule with " - "ID '%(snat)s': %(e)s"), - {'snat': snat, 'e': e}) - if result > 0: - total = len(parsed_args.snat) - msg = (_("%(result)s of %(total)s SNAT Rule(s) failed " - "to delete.") % {'result': result, 'total': total}) - raise exceptions.CommandError(msg) diff --git a/otcextensions/tests/unit/osclient/vpc/__init__.py b/otcextensions/tests/unit/osclient/vpc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/unit/osclient/vpc/v2/__init__.py b/otcextensions/tests/unit/osclient/vpc/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/unit/osclient/vpc/v2/fakes.py b/otcextensions/tests/unit/osclient/vpc/v2/fakes.py new file mode 100644 index 000000000..b9ca838e1 --- /dev/null +++ b/otcextensions/tests/unit/osclient/vpc/v2/fakes.py @@ -0,0 +1,68 @@ +# 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 +import mock + +from openstackclient.tests.unit import utils +from otcextensions.tests.unit.osclient import test_base + +from otcextensions.sdk.vpc.v2 import peering + + +def gen_data(data, columns): + """Fill expected data tuple based on columns list + """ + return tuple(getattr(data, attr, '') for attr in columns) + + +def gen_data_dict(data, columns): + """Fill expected data tuple based on columns list + """ + return tuple(data.get(attr, '') for attr in columns) + + +class TestVpc(utils.TestCommand): + def setUp(self): + super(TestVpc, self).setUp() + + self.app.client_manager.vpc = mock.Mock() + + self.client = self.app.client_manager.vpc + + +class FakeVpcPeering(test_base.Fake): + """Fake one or more VPC peering connections.""" + @classmethod + def generate(cls): + """Create a fake VPC peering connection. + + :return: + A FakeResource object, with id, name and so on + """ + # Set default attributes. + object_info = { + "id": "id-" + uuid.uuid4().hex, + "name": "name-" + uuid.uuid4().hex, + "request_vpc_info": { + "vpc_id": uuid.uuid4().hex, + "tenant_id": uuid.uuid4().hex + }, + "accept_vpc_info": { + "vpc_id": uuid.uuid4().hex, + "tenant_id": uuid.uuid4().hex + }, + "status": "ACTIVE", + "description": "my vpc peering", + } + + return peering.Peering(**object_info) diff --git a/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py b/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py new file mode 100644 index 000000000..2fb1a03dc --- /dev/null +++ b/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py @@ -0,0 +1,500 @@ +# 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 mock +from unittest.mock import call + +from osc_lib import exceptions + +from otcextensions.osclient.vpc.v2 import peering +from otcextensions.tests.unit.osclient.vpc.v2 import fakes + +from openstackclient.tests.unit import utils as tests_utils + + +class TestListVpcPeerings(fakes.TestVpc): + + objects = fakes.FakeVpcPeering.create_multiple(3) + + column_list_headers = ( + 'Id', + 'Name', + 'Request Vpc Info', + 'Accept Vpc Info', + 'Status' + ) + + columns = ('id', 'name', 'request_vpc_info', 'accept_vpc_info', 'status') + + data = [] + + for s in objects: + data.append( + (s.id, s.name, s.request_vpc_info, s.accept_vpc_info, s.status)) + + def setUp(self): + super(TestListVpcPeerings, self).setUp() + + self.cmd = peering.ListVpcPeerings(self.app, None) + + self.client.peerings = mock.Mock() + self.client.api_mock = self.client.peerings + + def test_list(self): + arglist = [] + + verifylist = [] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Set the response + self.client.api_mock.side_effect = [self.objects] + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + + self.client.api_mock.assert_called_with() + + self.assertEqual(self.column_list_headers, columns) + self.assertEqual(self.data, list(data)) + + def test_list_args(self): + arglist = [ + '--limit', '1', + '--marker', '2', + '--id', '3', + '--name', '4', + '--project-id', '5', + '--router-id', '6', + '--status', '7' + ] + + verifylist = [ + ('limit', 1), + ('marker', '2'), + ('id', '3'), + ('name', '4'), + ('project_id', '5'), + ('router_id', '6'), + ('status', '7'), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Set the response + self.client.api_mock.side_effect = [self.objects] + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + + self.client.api_mock.assert_called_with( + limit=1, + marker='2', + id='3', + name='4', + project_id='5', + router_id='6', + status='7', + ) + + +class TestCreateVpcPeering(fakes.TestVpc): + + _data = fakes.FakeVpcPeering.create_one() + + columns = ( + 'accept_vpc_info', + 'description', + 'id', + 'name', + 'request_vpc_info', + 'status' + ) + + data = fakes.gen_data(_data, columns) + + def setUp(self): + super(TestCreateVpcPeering, self).setUp() + + self.cmd = peering.CreateVpcPeering(self.app, None) + + self.client.create_peering = mock.Mock( + return_value=fakes.FakeVpcPeering.create_one()) + + def test_create_different_project(self): + arglist = [ + 'test-peering', + '--request-vpc-info', + 'router_id=test-router1-uuid,project_id=test-project1-uuid', + '--accept-vpc-info', + 'router_id=test-router2-uuid,project_id=test-project2-uuid', + '--description', 'test-peering', + ] + verifylist = [ + ('name', 'test-peering'), + ('request_vpc_info', + [{'router_id': 'test-router1-uuid', + 'project_id': 'test-project1-uuid'}]), + ('accept_vpc_info', + [{'router_id': 'test-router2-uuid', + 'project_id': 'test-project2-uuid'}]), + ('description', 'test-peering'), + ] + # Verify cm is triggereg with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + attrs = { + 'name': 'test-peering', + 'request_vpc_info': { + 'vpc_id': 'test-router1-uuid', + 'tenant_id': 'test-project1-uuid' + }, + 'accept_vpc_info': { + 'vpc_id': 'test-router2-uuid', + 'tenant_id': 'test-project2-uuid' + }, + 'description': 'test-peering' + } + + self.client.create_peering.assert_called_with(**attrs) + self.assertEqual(self.columns, columns) + + def test_create_same_project(self): + arglist = [ + 'test-peering', + '--request-vpc-info', 'router_id=test-router1-uuid', + '--accept-vpc-info', 'router_id=test-router2-uuid', + '--description', 'test-peering', + ] + verifylist = [ + ('name', 'test-peering'), + ('request_vpc_info', [{'router_id': 'test-router1-uuid'}]), + ('accept_vpc_info', [{'router_id': 'test-router2-uuid'}]), + ('description', 'test-peering'), + ] + # Verify cm is triggereg with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + attrs = { + 'name': 'test-peering', + 'request_vpc_info': { + 'vpc_id': 'test-router1-uuid' + }, + 'accept_vpc_info': { + 'vpc_id': 'test-router2-uuid' + }, + 'description': 'test-peering' + } + + self.client.create_peering.assert_called_with(**attrs) + self.assertEqual(self.columns, columns) + + +class TestUpdateVpcPeering(fakes.TestVpc): + + _data = fakes.FakeVpcPeering.create_one() + + columns = ( + 'accept_vpc_info', + 'description', + 'id', + 'name', + 'request_vpc_info', + 'status' + ) + + data = fakes.gen_data(_data, columns) + + def setUp(self): + super(TestUpdateVpcPeering, self).setUp() + + self.cmd = peering.UpdateVpcPeering(self.app, None) + + self.client.find_peering = mock.Mock(return_value=self._data) + self.client.update_peering = mock.Mock(return_value=self._data) + + def test_update(self): + arglist = [ + self._data.name, + '--name', 'test-peering-updated', + '--description', 'vpc peering updated', + ] + verifylist = [ + ('peering', self._data.name), + ('name', 'test-peering-updated'), + ('description', 'vpc peering updated'), + ] + # Verify cm is triggereg with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + + self.client.find_peering.assert_called_with(self._data.name) + self.client.update_peering.assert_called_with( + self._data.id, + name='test-peering-updated', + description='vpc peering updated' + ) + self.assertEqual(self.columns, columns) + + +class TestShowVpcPeering(fakes.TestVpc): + + _data = fakes.FakeVpcPeering.create_one() + + columns = ( + 'accept_vpc_info', + 'description', + 'id', + 'name', + 'request_vpc_info', + 'status' + ) + + data = fakes.gen_data(_data, columns) + + def setUp(self): + super(TestShowVpcPeering, self).setUp() + + self.cmd = peering.ShowVpcPeering(self.app, None) + + self.client.find_peering = mock.Mock(return_value=self._data) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Testing that a call without the required argument will fail and + # throw a "ParserExecption" + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_show(self): + arglist = [ + self._data.id, + ] + + verifylist = [ + ('peering', self._data.id), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + self.client.find_peering.assert_called_with(self._data.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_show_non_existent(self): + arglist = [ + 'unexist_vpc_peering', + ] + + verifylist = [ + ('peering', 'unexist_vpc_peering'), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = exceptions.CommandError('Resource Not Found') + self.client.find_peering = ( + mock.Mock(side_effect=find_mock_result) + ) + + # Trigger the action + try: + self.cmd.take_action(parsed_args) + except Exception as e: + self.assertEqual('Resource Not Found', str(e)) + self.client.find_peering.assert_called_with('unexist_vpc_peering') + + +class TestAcceptVpcPeering(fakes.TestVpc): + + _data = fakes.FakeVpcPeering.create_one() + + columns = ( + 'accept_vpc_info', + 'description', + 'id', + 'name', + 'request_vpc_info', + 'status' + ) + + def setUp(self): + super(TestAcceptVpcPeering, self).setUp() + + self.cmd = peering.AcceptVpcPeering(self.app, None) + + self.client.find_peering = mock.Mock(return_value=self._data) + self.client.accept_peering = mock.Mock(return_value=self._data) + + def test_accept(self): + arglist = [ + self._data.name, + ] + + verifylist = [ + ('peering', self._data.name), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + self.client.find_peering.assert_called_with(self._data.name) + self.client.accept_peering.assert_called_with(self._data.id) + + self.assertEqual(self.columns, columns) + + +class TestRejectVpcPeering(fakes.TestVpc): + + _data = fakes.FakeVpcPeering.create_one() + + columns = ( + 'accept_vpc_info', + 'description', + 'id', + 'name', + 'request_vpc_info', + 'status' + ) + + def setUp(self): + super(TestRejectVpcPeering, self).setUp() + + self.cmd = peering.RejectVpcPeering(self.app, None) + + self.client.find_peering = mock.Mock(return_value=self._data) + self.client.reject_peering = mock.Mock(return_value=self._data) + + def test_reject(self): + arglist = [ + self._data.name, + ] + + verifylist = [ + ('peering', self._data.name), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + self.client.find_peering.assert_called_with(self._data.name) + self.client.reject_peering.assert_called_with(self._data.id) + + self.assertEqual(self.columns, columns) + + +class TestDeleteVpcPeering(fakes.TestVpc): + + _data = fakes.FakeVpcPeering.create_multiple(2) + + def setUp(self): + super(TestDeleteVpcPeering, self).setUp() + + self.client.delete_peering = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = peering.DeleteVpcPeering(self.app, None) + + def test_delete(self): + arglist = [ + self._data[0].name, + ] + + verifylist = [ + ('peering', [self._data[0].name]), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.client.find_peering = ( + mock.Mock(return_value=self._data[0]) + ) + + # Trigger the action + result = self.cmd.take_action(parsed_args) + self.client.delete_peering.assert_called_with(self._data[0].id) + self.assertIsNone(result) + + def test_multiple_delete(self): + arglist = [] + + for data in self._data: + arglist.append(data.name) + + verifylist = [ + ('peering', arglist), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = self._data + self.client.find_peering = ( + mock.Mock(side_effect=find_mock_result) + ) + + # Trigger the action + result = self.cmd.take_action(parsed_args) + + calls = [] + for data in self._data: + calls.append(call(data.id)) + self.client.delete_peering.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multiple_delete_with_exception(self): + arglist = [ + self._data[0].name, + 'unexist_vpc_peering', + ] + verifylist = [ + ('peering', arglist), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._data[0], exceptions.CommandError] + self.client.find_peering = ( + mock.Mock(side_effect=find_mock_result) + ) + + # Trigger the action + try: + self.cmd.take_action(parsed_args) + except Exception as e: + self.assertEqual( + '1 of 2 VPC peering(s) failed to delete.', str(e)) + + self.client.find_peering.assert_any_call(self._data[0].name) + self.client.find_peering.assert_any_call('unexist_vpc_peering') + self.client.delete_peering.assert_called_once_with(self._data[0].id) diff --git a/setup.cfg b/setup.cfg index 82cff272f..36f3d14c6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -41,6 +41,7 @@ openstack.cli.extension = dns = otcextensions.osclient.dns.client deh = otcextensions.osclient.deh.client nat = otcextensions.osclient.nat.client + vpc = otcextensions.osclient.vpc.client #openstack.obs.v1 = # s3_ls = otcextensions.osclient.obs.v1.ls:List @@ -299,6 +300,15 @@ openstack.compute.v2 = server_set = otcextensions.osclient.compute.v2.server:SetServer server_unset = otcextensions.osclient.compute.v2.server:UnsetServer +openstack.vpc.v2 = + vpc_peering_list = otcextensions.osclient.vpc.v2.peering:ListVpcPeering + vpc_peering_show = otcextensions.osclient.vpc.v2.peering:ShowVpcPeering + vpc_peering_create = otcextensions.osclient.vpc.v2.peering:CreateVpcPeering + vpc_peering_update = otcextensions.osclient.vpc.v2.peering:UpdateVpcPeering + vpc_peering_delete = otcextensions.osclient.vpc.v2.peering:DeleteVpcPeering + vpc_peering_accept = otcextensions.osclient.vpc.v2.peering:AcceptVpcPeering + vpc_peering_reject = otcextensions.osclient.vpc.v2.peering:RejectVpcPeering + [build_sphinx] builders = html,man all-files = 1 From 1f61f54fd885d2ed6b714f4a32ceeb52a77f1a61 Mon Sep 17 00:00:00 2001 From: Vineet Pruthi Date: Sat, 13 Jun 2020 09:13:25 +0000 Subject: [PATCH 03/12] Saving Changes --- otcextensions/osclient/vpc/v2/peering.py | 129 +++++++----------- otcextensions/sdk/vpc/v2/_proxy.py | 25 ++-- otcextensions/sdk/vpc/v2/peering.py | 4 +- .../tests/unit/osclient/vpc/v2/fakes.py | 8 +- .../unit/osclient/vpc/v2/test_peering.py | 95 ++++--------- .../tests/unit/sdk/vpc/v2/test_peering.py | 8 +- .../tests/unit/sdk/vpc/v2/test_proxy.py | 19 +-- setup.cfg | 5 +- 8 files changed, 104 insertions(+), 189 deletions(-) diff --git a/otcextensions/osclient/vpc/v2/peering.py b/otcextensions/osclient/vpc/v2/peering.py index 47793faae..b1118d7f5 100644 --- a/otcextensions/osclient/vpc/v2/peering.py +++ b/otcextensions/osclient/vpc/v2/peering.py @@ -14,7 +14,6 @@ import logging from osc_lib import utils -from osc_lib.cli import parseractions from osc_lib import exceptions from osc_lib.command import command @@ -30,29 +29,9 @@ def _get_columns(item): return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) -def _format_vpc_info(vpc_info_list): - # Map the route keys to match --route option. - if len(vpc_info_list) > 1: - raise RuntimeError('Repeating argument is not supported') - vpc_info = vpc_info_list[0] - if 'router_id' in vpc_info: - vpc_info['vpc_id'] = vpc_info.pop('router_id') - if 'project_id' in vpc_info: - vpc_info['tenant_id'] = vpc_info.pop('project_id') - return vpc_info - - def translate_response(func): def new(self, *args, **kwargs): response = func(self, *args, **kwargs) - response.request_vpc_info[ - 'router_id'] = response.request_vpc_info.pop('vpc_id') - response.request_vpc_info[ - 'project_id'] = response.request_vpc_info.pop('tenant_id') - response.accept_vpc_info[ - 'router_id'] = response.accept_vpc_info.pop('vpc_id') - response.accept_vpc_info[ - 'project_id'] = response.accept_vpc_info.pop('tenant_id') display_columns, columns = _get_columns(response) data = utils.get_item_properties(response, columns) return (display_columns, data) @@ -154,42 +133,37 @@ def take_action(self, parsed_args): return client.find_peering(parsed_args.peering) -class AcceptVpcPeering(command.ShowOne): +class SetVpcPeering(command.ShowOne): _description = _("Accept VPC peering connection Request.") def get_parser(self, prog_name): - parser = super(AcceptVpcPeering, self).get_parser(prog_name) + parser = super(SetVpcPeering, self).get_parser(prog_name) parser.add_argument( 'peering', metavar='', help=_("Specifies the Name or ID of the VPC peering connection."), ) - return parser - - @translate_response - def take_action(self, parsed_args): - client = self.app.client_manager.vpc - peering = client.find_peering(parsed_args.peering) - return client.accept_peering(peering.id) - - -class RejectVpcPeering(command.ShowOne): - _description = _("Reject VPC peering connection Request.") - - def get_parser(self, prog_name): - parser = super(RejectVpcPeering, self).get_parser(prog_name) - parser.add_argument( - 'peering', - metavar='', - help=_("Specifies the Name or ID of the VPC peering connection."), + manage_request_group = parser.add_mutually_exclusive_group() + manage_request_group.add_argument( + '--accept', + action='store_true', + help=('Accept VPC peering connection request.') + ) + manage_request_group.add_argument( + '--reject', + action='store_true', + help=('Reject VPC peering connection request.') ) return parser @translate_response def take_action(self, parsed_args): client = self.app.client_manager.vpc + set_args = ('accept', 'reject') + request_status = [request for request in set_args if + getattr(parsed_args, request)] peering = client.find_peering(parsed_args.peering) - return client.reject_peering(peering.id) + return client.set_peering(peering.id, request_status[0]) class CreateVpcPeering(command.ShowOne): @@ -208,53 +182,50 @@ def get_parser(self, prog_name): help=_("Specifies the description of the VPC peering connection."), ) parser.add_argument( - '--request-vpc-info', - metavar='router_id=,project_id=', - dest='request_vpc_info', - action=parseractions.MultiKeyValueAction, + '--local-router-id', + metavar='', required=True, - required_keys=['router_id'], - optional_keys=['project_id'], - help=_("Specifies information about the local VPC.\n" - "router_id: Specifies the ID of a VPC involved " - "in a VPC peering connection.\n" - "project_id: Specifies the ID of Requester's Project." - "project_id is optional. It is required only when creating " - "vpc peering connection with a vpc from different project"), + help=_("Specifies information about the local router_id " + "involved in a VPC peering connection."), ) parser.add_argument( - '--accept-vpc-info', - metavar='router_id=,project_id=', - dest='accept_vpc_info', + '--peer-router-id', + metavar='', required=True, - action=parseractions.MultiKeyValueAction, - required_keys=['router_id'], - optional_keys=['project_id'], - help=_("Specifies information about the peer VPC.\n" - "router_id: Specifies the ID of a router involved " - "in a VPC peering connection.\n" - "project_id: Specifies the ID of Requester's Project." - "project_id is optional. It is required only when creating " - "vpc peering connection with a vpc from different project"), + help=_("Specifies information about the peer router_id " + "involved in a VPC peering connection."), + ) + parser.add_argument( + '--peer-project-id', + metavar='', + help=_("Specifies the ID of the project to which a " + "peer router belongs. It is required if the peer " + "router is from different project."), ) return parser @translate_response def take_action(self, parsed_args): client = self.app.client_manager.vpc - attrs = {} - args_list = [ - 'name', - 'request_vpc_info', - 'accept_vpc_info', - 'description' - ] - for arg in args_list: - val = getattr(parsed_args, arg) - if arg in ['request_vpc_info', 'accept_vpc_info']: - val = _format_vpc_info(val) - if val: - attrs[arg] = val + attrs = { + 'name': parsed_args.name, + 'request_vpc_info': { + 'vpc_id': parsed_args.local_router_id + }, + 'accept_vpc_info': { + 'vpc_id': parsed_args.peer_router_id + } + } + val = getattr(parsed_args, 'peer_project_id') + if val: + attrs['accept_vpc_info']['tenant_id'] = val + attrs['request_vpc_info']['tenant_id'] = client.get_project_id() + + val = getattr(parsed_args, 'description') + if val: + attrs['description'] = val + + print(attrs) return client.create_peering(**attrs) diff --git a/otcextensions/sdk/vpc/v2/_proxy.py b/otcextensions/sdk/vpc/v2/_proxy.py index 075364e60..70fdd20a3 100644 --- a/otcextensions/sdk/vpc/v2/_proxy.py +++ b/otcextensions/sdk/vpc/v2/_proxy.py @@ -100,30 +100,21 @@ def update_peering(self, peering, **attrs): """ return self._update(_peering.Peering, peering, **attrs) - def accept_peering(self, peering): - """Accept a vpc peering + def set_peering(self, peering, set_status): + """Accept/Reject a vpc peering connection request :param peering: Either the ID of a vpc peering or a :class:`~otcextensions.sdk.vpc.v2.peering.Peering` instance. + :param set_status: The value can been ``accept`` or ``reject`` :returns: The updated peering :rtype: :class:`~otcextensions.sdk.vpc.v2.peering.Peering` """ + valid_status = ['accept', 'reject'] + if set_status.lower() not in valid_status: + raise ValueError( + "results: status must be one of %r." % valid_status) peering = self._get_resource(_peering.Peering, peering) - return peering.approval(self, 'accept') - - def reject_peering(self, peering): - """Accept a vpc peering - - :param peering: Either the ID of a vpc peering or a - :class:`~otcextensions.sdk.vpc.v2.peering.Peering` - instance. - - :returns: The updated peering - - :rtype: :class:`~otcextensions.sdk.vpc.v2.peering.Peering` - """ - peering = self._get_resource(_peering.Peering, peering) - return peering.approval(self, 'reject') + return peering._set_peering(self, set_status.lower()) diff --git a/otcextensions/sdk/vpc/v2/peering.py b/otcextensions/sdk/vpc/v2/peering.py index 9ec0b16d6..9a9a375ca 100644 --- a/otcextensions/sdk/vpc/v2/peering.py +++ b/otcextensions/sdk/vpc/v2/peering.py @@ -64,9 +64,9 @@ class Peering(_base.Resource): #: first page are queried. marker = resource.Body('marker') - def approval(self, session, request_type): + def _set_peering(self, session, set_status): """Accept/Reject Peering Request""" - url = utils.urljoin(self.base_path, self.id, request_type) + url = utils.urljoin(self.base_path, self.id, set_status) response = session.put(url) exceptions.raise_from_response(response) self._translate_response(response) diff --git a/otcextensions/tests/unit/osclient/vpc/v2/fakes.py b/otcextensions/tests/unit/osclient/vpc/v2/fakes.py index b9ca838e1..8746141e5 100644 --- a/otcextensions/tests/unit/osclient/vpc/v2/fakes.py +++ b/otcextensions/tests/unit/osclient/vpc/v2/fakes.py @@ -54,12 +54,12 @@ def generate(cls): "id": "id-" + uuid.uuid4().hex, "name": "name-" + uuid.uuid4().hex, "request_vpc_info": { - "vpc_id": uuid.uuid4().hex, - "tenant_id": uuid.uuid4().hex + "router_id": uuid.uuid4().hex, + "project_id": uuid.uuid4().hex }, "accept_vpc_info": { - "vpc_id": uuid.uuid4().hex, - "tenant_id": uuid.uuid4().hex + "router_id": uuid.uuid4().hex, + "project_id": uuid.uuid4().hex }, "status": "ACTIVE", "description": "my vpc peering", diff --git a/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py b/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py index 2fb1a03dc..321a308e9 100644 --- a/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py +++ b/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py @@ -128,27 +128,24 @@ def setUp(self): super(TestCreateVpcPeering, self).setUp() self.cmd = peering.CreateVpcPeering(self.app, None) - self.client.create_peering = mock.Mock( return_value=fakes.FakeVpcPeering.create_one()) + self.client.get_project_id = mock.Mock( + return_value='test-local-project-uuid') def test_create_different_project(self): arglist = [ 'test-peering', - '--request-vpc-info', - 'router_id=test-router1-uuid,project_id=test-project1-uuid', - '--accept-vpc-info', - 'router_id=test-router2-uuid,project_id=test-project2-uuid', + '--local-router-id', 'test-local-router-uuid', + '--peer-router-id', 'test-peer-router-uuid', + '--peer-project-id', 'test-peer-project-uuid', '--description', 'test-peering', ] verifylist = [ ('name', 'test-peering'), - ('request_vpc_info', - [{'router_id': 'test-router1-uuid', - 'project_id': 'test-project1-uuid'}]), - ('accept_vpc_info', - [{'router_id': 'test-router2-uuid', - 'project_id': 'test-project2-uuid'}]), + ('local_router_id', 'test-local-router-uuid'), + ('peer_router_id', 'test-peer-router-uuid'), + ('peer_project_id', 'test-peer-project-uuid'), ('description', 'test-peering'), ] # Verify cm is triggereg with default parameters @@ -159,12 +156,12 @@ def test_create_different_project(self): attrs = { 'name': 'test-peering', 'request_vpc_info': { - 'vpc_id': 'test-router1-uuid', - 'tenant_id': 'test-project1-uuid' + 'vpc_id': 'test-local-router-uuid', + 'tenant_id': 'test-local-project-uuid' }, 'accept_vpc_info': { - 'vpc_id': 'test-router2-uuid', - 'tenant_id': 'test-project2-uuid' + 'vpc_id': 'test-peer-router-uuid', + 'tenant_id': 'test-peer-project-uuid' }, 'description': 'test-peering' } @@ -175,14 +172,14 @@ def test_create_different_project(self): def test_create_same_project(self): arglist = [ 'test-peering', - '--request-vpc-info', 'router_id=test-router1-uuid', - '--accept-vpc-info', 'router_id=test-router2-uuid', + '--local-router-id', 'test-local-router-uuid', + '--peer-router-id', 'test-peer-router-uuid', '--description', 'test-peering', ] verifylist = [ ('name', 'test-peering'), - ('request_vpc_info', [{'router_id': 'test-router1-uuid'}]), - ('accept_vpc_info', [{'router_id': 'test-router2-uuid'}]), + ('local_router_id', 'test-local-router-uuid'), + ('peer_router_id', 'test-peer-router-uuid'), ('description', 'test-peering'), ] # Verify cm is triggereg with default parameters @@ -193,10 +190,10 @@ def test_create_same_project(self): attrs = { 'name': 'test-peering', 'request_vpc_info': { - 'vpc_id': 'test-router1-uuid' + 'vpc_id': 'test-local-router-uuid' }, 'accept_vpc_info': { - 'vpc_id': 'test-router2-uuid' + 'vpc_id': 'test-peer-router-uuid' }, 'description': 'test-peering' } @@ -329,7 +326,7 @@ def test_show_non_existent(self): self.client.find_peering.assert_called_with('unexist_vpc_peering') -class TestAcceptVpcPeering(fakes.TestVpc): +class TestSetVpcPeering(fakes.TestVpc): _data = fakes.FakeVpcPeering.create_one() @@ -342,62 +339,25 @@ class TestAcceptVpcPeering(fakes.TestVpc): 'status' ) - def setUp(self): - super(TestAcceptVpcPeering, self).setUp() - - self.cmd = peering.AcceptVpcPeering(self.app, None) - - self.client.find_peering = mock.Mock(return_value=self._data) - self.client.accept_peering = mock.Mock(return_value=self._data) - - def test_accept(self): - arglist = [ - self._data.name, - ] - - verifylist = [ - ('peering', self._data.name), - ] - - # Verify cm is triggered with default parameters - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # Trigger the action - columns, data = self.cmd.take_action(parsed_args) - self.client.find_peering.assert_called_with(self._data.name) - self.client.accept_peering.assert_called_with(self._data.id) - - self.assertEqual(self.columns, columns) - - -class TestRejectVpcPeering(fakes.TestVpc): - - _data = fakes.FakeVpcPeering.create_one() - - columns = ( - 'accept_vpc_info', - 'description', - 'id', - 'name', - 'request_vpc_info', - 'status' - ) + data = fakes.gen_data(_data, columns) def setUp(self): - super(TestRejectVpcPeering, self).setUp() + super(TestSetVpcPeering, self).setUp() - self.cmd = peering.RejectVpcPeering(self.app, None) + self.cmd = peering.SetVpcPeering(self.app, None) self.client.find_peering = mock.Mock(return_value=self._data) - self.client.reject_peering = mock.Mock(return_value=self._data) + self.client.set_peering = mock.Mock(return_value=self._data) - def test_reject(self): + def test_set(self): arglist = [ self._data.name, + '--accept' ] verifylist = [ ('peering', self._data.name), + ('accept', True), ] # Verify cm is triggered with default parameters @@ -406,9 +366,10 @@ def test_reject(self): # Trigger the action columns, data = self.cmd.take_action(parsed_args) self.client.find_peering.assert_called_with(self._data.name) - self.client.reject_peering.assert_called_with(self._data.id) + self.client.set_peering.assert_called_with(self._data.id, 'accept') self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) class TestDeleteVpcPeering(fakes.TestVpc): diff --git a/otcextensions/tests/unit/sdk/vpc/v2/test_peering.py b/otcextensions/tests/unit/sdk/vpc/v2/test_peering.py index b8d38040a..4ac9cdf41 100644 --- a/otcextensions/tests/unit/sdk/vpc/v2/test_peering.py +++ b/otcextensions/tests/unit/sdk/vpc/v2/test_peering.py @@ -61,7 +61,7 @@ def test_make_it(self): self.assertEqual(EXAMPLE['id'], sot.id) self.assertEqual(EXAMPLE['name'], sot.name) - def test_approval(self): + def test_set_peering(self): sot = peering.Peering(id=IDENTIFIER) resp = mock.Mock() @@ -83,9 +83,9 @@ def test_approval(self): resp.status_code = 200 self.sess.put.return_value = resp - approval_resp = sot.approval(self.sess, 'reject') + response = sot._set_peering(self.sess, 'reject') self.sess.put.assert_called_with( 'vpc/peerings/ID/reject' ) - self.assertEqual(resp.body['name'], approval_resp.name) - self.assertEqual(resp.body['id'], approval_resp.id) + self.assertEqual(resp.body['name'], response.name) + self.assertEqual(resp.body['id'], response.id) diff --git a/otcextensions/tests/unit/sdk/vpc/v2/test_proxy.py b/otcextensions/tests/unit/sdk/vpc/v2/test_proxy.py index 5f419feed..60b1ebfa3 100644 --- a/otcextensions/tests/unit/sdk/vpc/v2/test_proxy.py +++ b/otcextensions/tests/unit/sdk/vpc/v2/test_proxy.py @@ -53,18 +53,11 @@ def test_peerings(self): def test_peering_update(self): self.verify_update(self.proxy.update_peering, peering.Peering) - def test_peering_accept(self): + def test_set_peering(self): self._verify( - 'otcextensions.sdk.vpc.v2.peering.Peering.approval', - self.proxy.accept_peering, - method_args=["accept"], - expected_args=["accept"] - ) - - def test_peering_reject(self): - self._verify( - 'otcextensions.sdk.vpc.v2.peering.Peering.approval', - self.proxy.reject_peering, - method_args=["reject"], - expected_args=["reject"] + 'otcextensions.sdk.vpc.v2.peering.Peering._set_peering', + self.proxy.set_peering, + method_args=[peering.Peering], + method_kwargs={'set_status': 'accept'}, + expected_args=['accept'] ) diff --git a/setup.cfg b/setup.cfg index 36f3d14c6..7fb799f9e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -301,13 +301,12 @@ openstack.compute.v2 = server_unset = otcextensions.osclient.compute.v2.server:UnsetServer openstack.vpc.v2 = - vpc_peering_list = otcextensions.osclient.vpc.v2.peering:ListVpcPeering + vpc_peering_list = otcextensions.osclient.vpc.v2.peering:ListVpcPeerings vpc_peering_show = otcextensions.osclient.vpc.v2.peering:ShowVpcPeering vpc_peering_create = otcextensions.osclient.vpc.v2.peering:CreateVpcPeering vpc_peering_update = otcextensions.osclient.vpc.v2.peering:UpdateVpcPeering vpc_peering_delete = otcextensions.osclient.vpc.v2.peering:DeleteVpcPeering - vpc_peering_accept = otcextensions.osclient.vpc.v2.peering:AcceptVpcPeering - vpc_peering_reject = otcextensions.osclient.vpc.v2.peering:RejectVpcPeering + vpc_peering_set = otcextensions.osclient.vpc.v2.peering:SetVpcPeering [build_sphinx] builders = html,man From 2811bc0832806555d13154f7de58461e5a738ddd Mon Sep 17 00:00:00 2001 From: Vineet Pruthi Date: Thu, 18 Jun 2020 09:16:04 +0000 Subject: [PATCH 04/12] Added examples and functional tests to vpc peering --- examples/vpc/create_peering.py | 34 ++++++ otcextensions/osclient/vpc/v2/peering.py | 83 +++++++++---- otcextensions/sdk/vpc/v2/peering.py | 4 - .../tests/functional/osclient/vpc/__init__.py | 0 .../functional/osclient/vpc/v2/__init__.py | 0 .../functional/osclient/vpc/v2/common.py | 74 +++++++++++ .../osclient/vpc/v2/test_peering.py | 115 ++++++++++++++++++ .../tests/unit/osclient/vpc/v2/fakes.py | 3 + .../unit/osclient/vpc/v2/test_peering.py | 50 ++++---- 9 files changed, 312 insertions(+), 51 deletions(-) create mode 100644 examples/vpc/create_peering.py create mode 100644 otcextensions/tests/functional/osclient/vpc/__init__.py create mode 100644 otcextensions/tests/functional/osclient/vpc/v2/__init__.py create mode 100644 otcextensions/tests/functional/osclient/vpc/v2/common.py create mode 100644 otcextensions/tests/functional/osclient/vpc/v2/test_peering.py diff --git a/examples/vpc/create_peering.py b/examples/vpc/create_peering.py new file mode 100644 index 000000000..dfde34b58 --- /dev/null +++ b/examples/vpc/create_peering.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# 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. +""" +Get VPC Peering by id or peering Instance +""" +import openstack +from otcextensions import sdk + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') +sdk.register_otc_extensions(conn) + +attrs = { + "name": "test-peering", + "request_vpc_info": { + "vpc_id": "requester-router-uuid" + }, + "accept_vpc_info": { + "vpc_id": "accepter-router-uuid" + } +} + +peering = conn.vpc.create_peering(**attrs) +print(peering) diff --git a/otcextensions/osclient/vpc/v2/peering.py b/otcextensions/osclient/vpc/v2/peering.py index b1118d7f5..de587b1f2 100644 --- a/otcextensions/osclient/vpc/v2/peering.py +++ b/otcextensions/osclient/vpc/v2/peering.py @@ -18,23 +18,58 @@ from osc_lib.command import command from otcextensions.i18n import _ -from otcextensions.common import sdk_utils LOG = logging.getLogger(__name__) +STATUS_CHOICES = [ + 'PENDING_ACCEPTANCE', + 'REJECTED', + 'EXPIRED', + 'DELETED', + 'ACTIVE' +] -def _get_columns(item): - column_map = { - } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + +def _update_vpc_info(vpcinfo): + if 'vpc_id' in vpcinfo: + vpcinfo['router_id'] = vpcinfo.pop('vpc_id') + if 'tenant_id' in vpcinfo: + vpcinfo['project_id'] = vpcinfo.pop('tenant_id') + return vpcinfo + + +def set_attributes_for_print(peerings): + for peering in peerings: + setattr(peering, + 'request_vpc_info', + _update_vpc_info(peering.request_vpc_info)) + setattr(peering, + 'accept_vpc_info', + _update_vpc_info(peering.accept_vpc_info)) + yield peering def translate_response(func): def new(self, *args, **kwargs): response = func(self, *args, **kwargs) - display_columns, columns = _get_columns(response) + setattr(response, + 'request_vpc_info', + _update_vpc_info(response.request_vpc_info)) + setattr(response, + 'accept_vpc_info', + _update_vpc_info(response.accept_vpc_info)) + columns = ( + 'id', + 'name', + 'request_vpc_info', + 'accept_vpc_info', + 'description', + 'created_at', + 'updated_at', + 'status' + ) data = utils.get_item_properties(response, columns) - return (display_columns, data) + return (columns, data) new.__name__ = func.__name__ new.__doc__ = func.__doc__ return new @@ -82,14 +117,10 @@ def get_parser(self, prog_name): ) parser.add_argument( '--status', - metavar='', - help=_("Specifies the status of the VPC peering connection.\n" - "Possible values are as follows:\n" - "PENDING_ACCEPTANCE\n" - "REJECTED\n" - "EXPIRED\n" - "DELETED\n" - "ACTIVE"), + metavar='{' + ','.join(STATUS_CHOICES) + '}', + type=lambda s: s.upper(), + choices=STATUS_CHOICES, + help=_("Specifies the status of the VPC peering connection."), ) return parser @@ -110,6 +141,8 @@ def take_action(self, parsed_args): attrs[arg] = val data = client.peerings(**attrs) + if data: + data = set_attributes_for_print(data) return (self.columns, (utils.get_item_properties(s, self.columns) for s in data)) @@ -182,22 +215,22 @@ def get_parser(self, prog_name): help=_("Specifies the description of the VPC peering connection."), ) parser.add_argument( - '--local-router-id', - metavar='', + '--requester-router-id', + metavar='', required=True, help=_("Specifies information about the local router_id " "involved in a VPC peering connection."), ) parser.add_argument( - '--peer-router-id', - metavar='', + '--accepter-router-id', + metavar='', required=True, help=_("Specifies information about the peer router_id " "involved in a VPC peering connection."), ) parser.add_argument( - '--peer-project-id', - metavar='', + '--accepter-project-id', + metavar='', help=_("Specifies the ID of the project to which a " "peer router belongs. It is required if the peer " "router is from different project."), @@ -210,13 +243,13 @@ def take_action(self, parsed_args): attrs = { 'name': parsed_args.name, 'request_vpc_info': { - 'vpc_id': parsed_args.local_router_id + 'vpc_id': parsed_args.requester_router_id }, 'accept_vpc_info': { - 'vpc_id': parsed_args.peer_router_id + 'vpc_id': parsed_args.accepter_router_id } } - val = getattr(parsed_args, 'peer_project_id') + val = getattr(parsed_args, 'accepter_project_id') if val: attrs['accept_vpc_info']['tenant_id'] = val attrs['request_vpc_info']['tenant_id'] = client.get_project_id() @@ -225,8 +258,6 @@ def take_action(self, parsed_args): if val: attrs['description'] = val - print(attrs) - return client.create_peering(**attrs) diff --git a/otcextensions/sdk/vpc/v2/peering.py b/otcextensions/sdk/vpc/v2/peering.py index 9a9a375ca..901e03347 100644 --- a/otcextensions/sdk/vpc/v2/peering.py +++ b/otcextensions/sdk/vpc/v2/peering.py @@ -55,10 +55,6 @@ class Peering(_base.Resource): #: Specifies the time (UTC) when the VPC peering connection is updated. #: Format is *yyyy-mm-dd hh:mm:ss*. created_at = resource.Body('updated_at') - #: Specifies the project ID - project_id = resource.Body('tenant_id') - #: Specifies that the router ID is used as the filtering condition. - router_id = resource.Body('vpc_id') #: Specifies the start resource ID of pagination query. #: If the parameter is left blank, only resources on the #: first page are queried. diff --git a/otcextensions/tests/functional/osclient/vpc/__init__.py b/otcextensions/tests/functional/osclient/vpc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/functional/osclient/vpc/v2/__init__.py b/otcextensions/tests/functional/osclient/vpc/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/functional/osclient/vpc/v2/common.py b/otcextensions/tests/functional/osclient/vpc/v2/common.py new file mode 100644 index 000000000..91588b5f5 --- /dev/null +++ b/otcextensions/tests/functional/osclient/vpc/v2/common.py @@ -0,0 +1,74 @@ +# 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 json +import uuid + +from datetime import datetime + +from openstackclient.tests.functional import base + + +class VpcTestCase(base.TestCase): + """Common functional test bits for VPC commands""" + + CURR_TIME = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") + + def setUp(self): + super(VpcTestCase, self).setUp() + UUID = uuid.uuid4().hex[:8] + self.REQUESTER_ROUTER_NAME = 'test-local-router-otce-cli' + UUID + self.ACCEPTER_ROUTER_NAME = 'test-peer-router-otce-cli' + UUID + self.PEERING_NAME = 'test-peering-otce-cli-' + UUID + + self.REQUESTER_ROUTER_ID = None + self.ACCEPTER_ROUTER_ID = None + self.PEERING_ID = None + + def create_vpc_peering(self, name=None): + self._create_routers() + name = name or self.PEERING_NAME + json_output = json.loads(self.openstack( + 'vpc peering create ' + '{name} ' + '--requester-router-id "{requester_router_id}" ' + '--accepter-router-id "{accepter_router_id}" ' + '-f json'.format( + name=name, + requester_router_id=self.REQUESTER_ROUTER_ID, + accepter_router_id=self.ACCEPTER_ROUTER_ID) + )) + self.assertIsNotNone(json_output) + self.PEERING_ID = json_output['id'] + return json_output + + def delete_vpc_peering(self): + self.addCleanup(self._delete_routers) + self.openstack('vpc peering delete {}'.format(self.PEERING_ID)) + + def _create_routers(self): + requester_router = json.loads(self.openstack( + 'router create -f json ' + self.REQUESTER_ROUTER_NAME + )) + self.REQUESTER_ROUTER_ID = requester_router['id'] + + accepter_router = json.loads(self.openstack( + 'router create -f json ' + self.ACCEPTER_ROUTER_NAME + )) + self.ACCEPTER_ROUTER_ID = accepter_router['id'] + + def _delete_routers(self): + self.openstack( + 'router delete {} {}'.format( + self.REQUESTER_ROUTER_ID, self.ACCEPTER_ROUTER_ID + )) diff --git a/otcextensions/tests/functional/osclient/vpc/v2/test_peering.py b/otcextensions/tests/functional/osclient/vpc/v2/test_peering.py new file mode 100644 index 000000000..b7d87a898 --- /dev/null +++ b/otcextensions/tests/functional/osclient/vpc/v2/test_peering.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. + +import json + +from otcextensions.tests.functional.osclient.vpc.v2 import common + + +class TestPeering(common.VpcTestCase): + """Functional Tests for NAT Gateway""" + + def setUp(self): + super(TestPeering, self).setUp() + + def test_vpc_peering_list(self): + json_output = json.loads(self.openstack( + 'vpc peering list -f json ' + )) + self.assertIsNotNone(json_output) + + def test_vpc_peering_list_filters(self): + json_output = json.loads(self.openstack( + 'vpc peering list -f json ' + '--limit 1 ' + '--id 2 ' + '--name 3 ' + '--project-id 4 ' + '--router-id 5 ' + '--status ACTIVE ' + )) + self.assertIsNotNone(json_output) + + def test_vpc_peering(self): + self.addCleanup(self.delete_vpc_peering) + peering = self.create_vpc_peering() + peering_id = peering['id'] + peering_name = peering['name'] + requester_router_id = peering['request_vpc_info']['router_id'] + accepter_router_id = peering['accept_vpc_info']['router_id'] + + # List Vpc Peering By Id + json_output = json.loads(self.openstack( + 'vpc peering list -f json ' + '--id {}'.format(peering_id) + )) + self.assertEqual(json_output[0]['Name'], peering_name) + self.assertEqual(json_output[0]['Id'], peering_id) + + # List Vpc Peering By Name + json_output = json.loads(self.openstack( + 'vpc peering list -f json ' + '--name {}'.format(peering_name) + )) + self.assertEqual(json_output[0]['Name'], peering_name) + self.assertEqual(json_output[0]['Id'], peering_id) + + # List Vpc Peering by Requester Router ID + json_output = json.loads(self.openstack( + 'vpc peering list -f json ' + '--router-id {}'.format(requester_router_id) + )) + for peering in json_output: + self.assertEqual( + peering['Request Vpc Info']['router_id'], + requester_router_id + ) + + # List Vpc Peering by Accepter Router ID + json_output = json.loads(self.openstack( + 'vpc peering list -f json ' + '--router-id {}'.format(accepter_router_id) + )) + for peering in json_output: + self.assertEqual( + peering['Accept Vpc Info']['router_id'], + accepter_router_id + ) + + # Show Vpc Peering by Name + json_output = json.loads(self.openstack( + 'vpc peering show -f json ' + peering_name + )) + self.assertEqual(json_output['name'], peering_name) + self.assertEqual(json_output['id'], peering_id) + + # Show Vpc Peering by Id + json_output = json.loads(self.openstack( + 'vpc peering show -f json ' + peering_id + )) + self.assertEqual(json_output['name'], peering_name) + self.assertEqual(json_output['id'], peering_id) + + # Update Vpc Peering + peering_name = peering_name + "-updated" + description = "test vpc peering updated by otce cli" + json_output = json.loads(self.openstack( + 'vpc peering update {peering_id} ' + '--name {name} ' + '--description "{desc}" ' + '-f json'.format( + peering_id=peering_id, + name=peering_name, + desc=description) + )) + self.assertEqual(json_output['name'], peering_name) + self.assertEqual(json_output['description'], description) diff --git a/otcextensions/tests/unit/osclient/vpc/v2/fakes.py b/otcextensions/tests/unit/osclient/vpc/v2/fakes.py index 8746141e5..6c57c7e6e 100644 --- a/otcextensions/tests/unit/osclient/vpc/v2/fakes.py +++ b/otcextensions/tests/unit/osclient/vpc/v2/fakes.py @@ -12,6 +12,7 @@ # import uuid import mock +from datetime import datetime from openstackclient.tests.unit import utils from otcextensions.tests.unit.osclient import test_base @@ -63,6 +64,8 @@ def generate(cls): }, "status": "ACTIVE", "description": "my vpc peering", + "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"), + "updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") } return peering.Peering(**object_info) diff --git a/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py b/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py index 321a308e9..f1d861ba8 100644 --- a/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py +++ b/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py @@ -76,7 +76,7 @@ def test_list_args(self): '--name', '4', '--project-id', '5', '--router-id', '6', - '--status', '7' + '--status', 'ACTIVE' ] verifylist = [ @@ -86,7 +86,7 @@ def test_list_args(self): ('name', '4'), ('project_id', '5'), ('router_id', '6'), - ('status', '7'), + ('status', 'ACTIVE'), ] # Verify cm is triggered with default parameters @@ -105,7 +105,7 @@ def test_list_args(self): name='4', project_id='5', router_id='6', - status='7', + status='ACTIVE', ) @@ -114,11 +114,13 @@ class TestCreateVpcPeering(fakes.TestVpc): _data = fakes.FakeVpcPeering.create_one() columns = ( - 'accept_vpc_info', - 'description', 'id', 'name', 'request_vpc_info', + 'accept_vpc_info', + 'description', + 'created_at', + 'updated_at', 'status' ) @@ -136,16 +138,16 @@ def setUp(self): def test_create_different_project(self): arglist = [ 'test-peering', - '--local-router-id', 'test-local-router-uuid', - '--peer-router-id', 'test-peer-router-uuid', - '--peer-project-id', 'test-peer-project-uuid', + '--requester-router-id', 'test-local-router-uuid', + '--accepter-router-id', 'test-peer-router-uuid', + '--accepter-project-id', 'test-peer-project-uuid', '--description', 'test-peering', ] verifylist = [ ('name', 'test-peering'), - ('local_router_id', 'test-local-router-uuid'), - ('peer_router_id', 'test-peer-router-uuid'), - ('peer_project_id', 'test-peer-project-uuid'), + ('requester_router_id', 'test-local-router-uuid'), + ('accepter_router_id', 'test-peer-router-uuid'), + ('accepter_project_id', 'test-peer-project-uuid'), ('description', 'test-peering'), ] # Verify cm is triggereg with default parameters @@ -172,14 +174,14 @@ def test_create_different_project(self): def test_create_same_project(self): arglist = [ 'test-peering', - '--local-router-id', 'test-local-router-uuid', - '--peer-router-id', 'test-peer-router-uuid', + '--requester-router-id', 'test-local-router-uuid', + '--accepter-router-id', 'test-peer-router-uuid', '--description', 'test-peering', ] verifylist = [ ('name', 'test-peering'), - ('local_router_id', 'test-local-router-uuid'), - ('peer_router_id', 'test-peer-router-uuid'), + ('requester_router_id', 'test-local-router-uuid'), + ('accepter_router_id', 'test-peer-router-uuid'), ('description', 'test-peering'), ] # Verify cm is triggereg with default parameters @@ -207,11 +209,13 @@ class TestUpdateVpcPeering(fakes.TestVpc): _data = fakes.FakeVpcPeering.create_one() columns = ( - 'accept_vpc_info', - 'description', 'id', 'name', 'request_vpc_info', + 'accept_vpc_info', + 'description', + 'created_at', + 'updated_at', 'status' ) @@ -256,11 +260,13 @@ class TestShowVpcPeering(fakes.TestVpc): _data = fakes.FakeVpcPeering.create_one() columns = ( - 'accept_vpc_info', - 'description', 'id', 'name', 'request_vpc_info', + 'accept_vpc_info', + 'description', + 'created_at', + 'updated_at', 'status' ) @@ -331,11 +337,13 @@ class TestSetVpcPeering(fakes.TestVpc): _data = fakes.FakeVpcPeering.create_one() columns = ( - 'accept_vpc_info', - 'description', 'id', 'name', 'request_vpc_info', + 'accept_vpc_info', + 'description', + 'created_at', + 'updated_at', 'status' ) From 5cb4c032184b09e0fd3080d4028bfc988922e8f8 Mon Sep 17 00:00:00 2001 From: Vineet Pruthi Date: Thu, 18 Jun 2020 09:28:14 +0000 Subject: [PATCH 05/12] Adding some Fixes --- otcextensions/sdk/vpc/v2/_proxy.py | 3 +-- otcextensions/sdk/vpc/v2/peering.py | 6 +----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/otcextensions/sdk/vpc/v2/_proxy.py b/otcextensions/sdk/vpc/v2/_proxy.py index 70fdd20a3..d7e94b10b 100644 --- a/otcextensions/sdk/vpc/v2/_proxy.py +++ b/otcextensions/sdk/vpc/v2/_proxy.py @@ -38,8 +38,7 @@ def delete_peering(self, peering, ignore_missing=True): When set to ``True``, no exception will be set when attempting to delete a nonexistent peering. - :returns: Gateway been deleted - :rtype: :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + :returns: ``None`` """ return self._delete(_peering.Peering, peering, ignore_missing=ignore_missing) diff --git a/otcextensions/sdk/vpc/v2/peering.py b/otcextensions/sdk/vpc/v2/peering.py index 901e03347..53d0ece9d 100644 --- a/otcextensions/sdk/vpc/v2/peering.py +++ b/otcextensions/sdk/vpc/v2/peering.py @@ -54,11 +54,7 @@ class Peering(_base.Resource): created_at = resource.Body('created_at') #: Specifies the time (UTC) when the VPC peering connection is updated. #: Format is *yyyy-mm-dd hh:mm:ss*. - created_at = resource.Body('updated_at') - #: Specifies the start resource ID of pagination query. - #: If the parameter is left blank, only resources on the - #: first page are queried. - marker = resource.Body('marker') + updated_at = resource.Body('updated_at') def _set_peering(self, session, set_status): """Accept/Reject Peering Request""" From 66dbb3ca2cdfc917124b575fe0e1441740d1b99d Mon Sep 17 00:00:00 2001 From: Vineet Pruthi Date: Mon, 22 Jun 2020 13:27:40 +0000 Subject: [PATCH 06/12] removed _base.py --- otcextensions/sdk/vpc/v2/_base.py | 18 ------------------ otcextensions/sdk/vpc/v2/peering.py | 4 +--- 2 files changed, 1 insertion(+), 21 deletions(-) delete mode 100644 otcextensions/sdk/vpc/v2/_base.py diff --git a/otcextensions/sdk/vpc/v2/_base.py b/otcextensions/sdk/vpc/v2/_base.py deleted file mode 100644 index 5d8322d64..000000000 --- a/otcextensions/sdk/vpc/v2/_base.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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 resource - - -class Resource(resource.Resource): - - base_path = '' - service_expectes_json_type = True diff --git a/otcextensions/sdk/vpc/v2/peering.py b/otcextensions/sdk/vpc/v2/peering.py index 53d0ece9d..d3bdc0044 100644 --- a/otcextensions/sdk/vpc/v2/peering.py +++ b/otcextensions/sdk/vpc/v2/peering.py @@ -13,10 +13,8 @@ from openstack import resource from openstack import utils -from otcextensions.sdk.vpc.v2 import _base - -class Peering(_base.Resource): +class Peering(resource.Resource): resources_key = 'peerings' resource_key = 'peering' base_path = '/vpc/peerings' From 79b3f9bb9d8134b7579e57dba7f5d35be9affc36 Mon Sep 17 00:00:00 2001 From: Vineet Pruthi Date: Mon, 13 Jul 2020 02:33:05 +0000 Subject: [PATCH 07/12] Override vpc endpoint --- otcextensions/sdk/vpc/v2/_proxy.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/otcextensions/sdk/vpc/v2/_proxy.py b/otcextensions/sdk/vpc/v2/_proxy.py index d7e94b10b..d31f65c67 100644 --- a/otcextensions/sdk/vpc/v2/_proxy.py +++ b/otcextensions/sdk/vpc/v2/_proxy.py @@ -16,7 +16,9 @@ class Proxy(proxy.Proxy): - skip_discovery = True + def _override_endpoint(self): + endpoint = self.get_endpoint(service_type='network') + setattr(self, 'endpoint_override', endpoint) # ======== Peering ======== def create_peering(self, **attrs): @@ -39,7 +41,8 @@ def delete_peering(self, peering, ignore_missing=True): delete a nonexistent peering. :returns: ``None`` - """ + """ + self._override_endpoint() return self._delete(_peering.Peering, peering, ignore_missing=ignore_missing) @@ -53,6 +56,7 @@ def peerings(self, **query): :rtype: :class:`~otcextensions.sdk.vpc.v2.peering.Peering` """ + self._override_endpoint() return self._list(_peering.Peering, **query) def get_peering(self, peering): @@ -67,6 +71,7 @@ def get_peering(self, peering): :raises: :class:`~openstack.exceptions.ResourceNotFound` when no resource can be found. """ + self._override_endpoint() return self._get(_peering.Peering, peering) def find_peering(self, name_or_id, ignore_missing=False): @@ -81,6 +86,7 @@ def find_peering(self, name_or_id, ignore_missing=False): :returns: One :class:`~otcextensions.sdk.vpc.v2.peering.Peering` """ + self._override_endpoint() return self._find(_peering.Peering, name_or_id, ignore_missing=ignore_missing) @@ -97,6 +103,7 @@ def update_peering(self, peering, **attrs): :rtype: :class:`~otcextensions.sdk.vpc.v2.peering.Peering` """ + self._override_endpoint() return self._update(_peering.Peering, peering, **attrs) def set_peering(self, peering, set_status): @@ -116,4 +123,5 @@ def set_peering(self, peering, set_status): raise ValueError( "results: status must be one of %r." % valid_status) peering = self._get_resource(_peering.Peering, peering) + self._override_endpoint() return peering._set_peering(self, set_status.lower()) From 71892244ddf1b7b57af22e94996188bea2f5f017 Mon Sep 17 00:00:00 2001 From: Vineet Pruthi Date: Mon, 13 Jul 2020 02:59:30 +0000 Subject: [PATCH 08/12] added endpoint override for create_peering --- otcextensions/sdk/vpc/v2/_proxy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/otcextensions/sdk/vpc/v2/_proxy.py b/otcextensions/sdk/vpc/v2/_proxy.py index d31f65c67..713f34055 100644 --- a/otcextensions/sdk/vpc/v2/_proxy.py +++ b/otcextensions/sdk/vpc/v2/_proxy.py @@ -27,6 +27,7 @@ def create_peering(self, **attrs): :param dict attrs: Keyword arguments which will be used to create a :class:`~otcextensions.sdk.vpc.v2.peering.Peering` """ + self._override_endpoint() return self._create(_peering.Peering, **attrs) def delete_peering(self, peering, ignore_missing=True): From 8fb5c5dc1ce168d6be809e82b84e3aa5a16f4078 Mon Sep 17 00:00:00 2001 From: "T. Schreiber" Date: Tue, 21 Jul 2020 15:20:31 +0000 Subject: [PATCH 09/12] added several vpc peering SDK examples --- examples/vpc/delete_peering.py | 23 +++++++++++++++++++++++ examples/vpc/list_peerings.py | 23 +++++++++++++++++++++++ examples/vpc/set_peering.py | 27 +++++++++++++++++++++++++++ examples/vpc/update_peering.py | 24 ++++++++++++++++++++++++ 4 files changed, 97 insertions(+) create mode 100644 examples/vpc/delete_peering.py create mode 100644 examples/vpc/list_peerings.py create mode 100644 examples/vpc/set_peering.py create mode 100644 examples/vpc/update_peering.py diff --git a/examples/vpc/delete_peering.py b/examples/vpc/delete_peering.py new file mode 100644 index 000000000..c99a7fd2e --- /dev/null +++ b/examples/vpc/delete_peering.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# 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. +""" +Delete VPC Peering by id or name +""" +import openstack + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') + + +peering = "peering_id" +conn.vpc.delete_peering(peering) diff --git a/examples/vpc/list_peerings.py b/examples/vpc/list_peerings.py new file mode 100644 index 000000000..455246bff --- /dev/null +++ b/examples/vpc/list_peerings.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# 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. +""" +List VPC Peerings +""" +import openstack + + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') + +for peering in conn.vpc.peerings(): + print(peering) diff --git a/examples/vpc/set_peering.py b/examples/vpc/set_peering.py new file mode 100644 index 000000000..6eb31ce5a --- /dev/null +++ b/examples/vpc/set_peering.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# 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. +""" +Accept or reject VPC Peering request +""" +import openstack + + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') + +peering = conn.vpc.get_peering("peering_id") + +# accept or reject +set_status = 'accept_or_reject' +peering = conn.vpc.set_peering(peering=peering, set_status=set_status) +print(peering) diff --git a/examples/vpc/update_peering.py b/examples/vpc/update_peering.py new file mode 100644 index 000000000..56ca281bb --- /dev/null +++ b/examples/vpc/update_peering.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# 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. +""" +Update VPC Peering attributes +""" +import openstack + + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') + +peering = conn.vpc.get_peering("peering_id") +peering = conn.vpc.update_peering(peering=peering, name='new-name') +print(peering) From 7e95e5cb6bf586649b70a1e7b4e57e03932034ed Mon Sep 17 00:00:00 2001 From: Vineet Pruthi Date: Wed, 22 Jul 2020 06:45:04 +0000 Subject: [PATCH 10/12] Fixes in VPC Peering Module --- otcextensions/osclient/vpc/v2/peering.py | 40 ++++++++--------- otcextensions/sdk/vpc/v2/peering.py | 4 +- .../functional/osclient/vpc/v2/common.py | 30 ++++++------- .../osclient/vpc/v2/test_peering.py | 16 +++---- .../tests/unit/osclient/vpc/v2/fakes.py | 8 ++-- .../unit/osclient/vpc/v2/test_peering.py | 44 +++++++++---------- .../tests/unit/sdk/vpc/v2/test_peering.py | 4 +- 7 files changed, 73 insertions(+), 73 deletions(-) diff --git a/otcextensions/osclient/vpc/v2/peering.py b/otcextensions/osclient/vpc/v2/peering.py index de587b1f2..60b7a446c 100644 --- a/otcextensions/osclient/vpc/v2/peering.py +++ b/otcextensions/osclient/vpc/v2/peering.py @@ -41,11 +41,11 @@ def _update_vpc_info(vpcinfo): def set_attributes_for_print(peerings): for peering in peerings: setattr(peering, - 'request_vpc_info', - _update_vpc_info(peering.request_vpc_info)) + 'local_vpc_info', + _update_vpc_info(peering.local_vpc_info)) setattr(peering, - 'accept_vpc_info', - _update_vpc_info(peering.accept_vpc_info)) + 'peer_vpc_info', + _update_vpc_info(peering.peer_vpc_info)) yield peering @@ -53,16 +53,16 @@ def translate_response(func): def new(self, *args, **kwargs): response = func(self, *args, **kwargs) setattr(response, - 'request_vpc_info', - _update_vpc_info(response.request_vpc_info)) + 'local_vpc_info', + _update_vpc_info(response.local_vpc_info)) setattr(response, - 'accept_vpc_info', - _update_vpc_info(response.accept_vpc_info)) + 'peer_vpc_info', + _update_vpc_info(response.peer_vpc_info)) columns = ( 'id', 'name', - 'request_vpc_info', - 'accept_vpc_info', + 'local_vpc_info', + 'peer_vpc_info', 'description', 'created_at', 'updated_at', @@ -78,7 +78,7 @@ def new(self, *args, **kwargs): class ListVpcPeerings(command.Lister): _description = _("List Vpc Peerings.") - columns = ('Id', 'Name', 'Request Vpc Info', 'Accept Vpc Info', 'Status') + columns = ('Id', 'Name', 'Local Vpc Info', 'Peer Vpc Info', 'Status') def get_parser(self, prog_name): parser = super(ListVpcPeerings, self).get_parser(prog_name) @@ -215,22 +215,22 @@ def get_parser(self, prog_name): help=_("Specifies the description of the VPC peering connection."), ) parser.add_argument( - '--requester-router-id', - metavar='', + '--local-router-id', + metavar='', required=True, help=_("Specifies information about the local router_id " "involved in a VPC peering connection."), ) parser.add_argument( - '--accepter-router-id', - metavar='', + '--peer-router-id', + metavar='', required=True, help=_("Specifies information about the peer router_id " "involved in a VPC peering connection."), ) parser.add_argument( - '--accepter-project-id', - metavar='', + '--peer-project-id', + metavar='', help=_("Specifies the ID of the project to which a " "peer router belongs. It is required if the peer " "router is from different project."), @@ -243,13 +243,13 @@ def take_action(self, parsed_args): attrs = { 'name': parsed_args.name, 'request_vpc_info': { - 'vpc_id': parsed_args.requester_router_id + 'vpc_id': parsed_args.local_router_id }, 'accept_vpc_info': { - 'vpc_id': parsed_args.accepter_router_id + 'vpc_id': parsed_args.peer_router_id } } - val = getattr(parsed_args, 'accepter_project_id') + val = getattr(parsed_args, 'peer_project_id') if val: attrs['accept_vpc_info']['tenant_id'] = val attrs['request_vpc_info']['tenant_id'] = client.get_project_id() diff --git a/otcextensions/sdk/vpc/v2/peering.py b/otcextensions/sdk/vpc/v2/peering.py index d3bdc0044..cad7aa574 100644 --- a/otcextensions/sdk/vpc/v2/peering.py +++ b/otcextensions/sdk/vpc/v2/peering.py @@ -37,10 +37,10 @@ class Peering(resource.Resource): id = resource.Body('id') #: Specifies information about the local VPC. #: *Type: dict* - accept_vpc_info = resource.Body('accept_vpc_info', type=dict) + peer_vpc_info = resource.Body('accept_vpc_info', type=dict) #: Specifies information about the local VPC. #: *Type: dict* - request_vpc_info = resource.Body('request_vpc_info', type=dict) + local_vpc_info = resource.Body('request_vpc_info', type=dict) #: Specifies the name of the VPC peering connection. name = resource.Body('name') #: Specifies the status. diff --git a/otcextensions/tests/functional/osclient/vpc/v2/common.py b/otcextensions/tests/functional/osclient/vpc/v2/common.py index 91588b5f5..16ec4bab2 100644 --- a/otcextensions/tests/functional/osclient/vpc/v2/common.py +++ b/otcextensions/tests/functional/osclient/vpc/v2/common.py @@ -27,12 +27,12 @@ class VpcTestCase(base.TestCase): def setUp(self): super(VpcTestCase, self).setUp() UUID = uuid.uuid4().hex[:8] - self.REQUESTER_ROUTER_NAME = 'test-local-router-otce-cli' + UUID - self.ACCEPTER_ROUTER_NAME = 'test-peer-router-otce-cli' + UUID + self.LOCAL_ROUTER_NAME = 'test-local-router-otce-cli' + UUID + self.PEER_ROUTER_NAME = 'test-peer-router-otce-cli' + UUID self.PEERING_NAME = 'test-peering-otce-cli-' + UUID - self.REQUESTER_ROUTER_ID = None - self.ACCEPTER_ROUTER_ID = None + self.LOCAL_ROUTER_ID = None + self.PEER_ROUTER_ID = None self.PEERING_ID = None def create_vpc_peering(self, name=None): @@ -41,12 +41,12 @@ def create_vpc_peering(self, name=None): json_output = json.loads(self.openstack( 'vpc peering create ' '{name} ' - '--requester-router-id "{requester_router_id}" ' - '--accepter-router-id "{accepter_router_id}" ' + '--local-router-id "{local_router_id}" ' + '--peer-router-id "{peer_router_id}" ' '-f json'.format( name=name, - requester_router_id=self.REQUESTER_ROUTER_ID, - accepter_router_id=self.ACCEPTER_ROUTER_ID) + local_router_id=self.LOCAL_ROUTER_ID, + peer_router_id=self.PEER_ROUTER_ID) )) self.assertIsNotNone(json_output) self.PEERING_ID = json_output['id'] @@ -57,18 +57,18 @@ def delete_vpc_peering(self): self.openstack('vpc peering delete {}'.format(self.PEERING_ID)) def _create_routers(self): - requester_router = json.loads(self.openstack( - 'router create -f json ' + self.REQUESTER_ROUTER_NAME + local_router = json.loads(self.openstack( + 'router create -f json ' + self.LOCAL_ROUTER_NAME )) - self.REQUESTER_ROUTER_ID = requester_router['id'] + self.LOCAL_ROUTER_ID = local_router['id'] - accepter_router = json.loads(self.openstack( - 'router create -f json ' + self.ACCEPTER_ROUTER_NAME + peer_router = json.loads(self.openstack( + 'router create -f json ' + self.PEER_ROUTER_NAME )) - self.ACCEPTER_ROUTER_ID = accepter_router['id'] + self.PEER_ROUTER_ID = peer_router['id'] def _delete_routers(self): self.openstack( 'router delete {} {}'.format( - self.REQUESTER_ROUTER_ID, self.ACCEPTER_ROUTER_ID + self.LOCAL_ROUTER_ID, self.PEER_ROUTER_ID )) diff --git a/otcextensions/tests/functional/osclient/vpc/v2/test_peering.py b/otcextensions/tests/functional/osclient/vpc/v2/test_peering.py index b7d87a898..e34d295c2 100644 --- a/otcextensions/tests/functional/osclient/vpc/v2/test_peering.py +++ b/otcextensions/tests/functional/osclient/vpc/v2/test_peering.py @@ -44,8 +44,8 @@ def test_vpc_peering(self): peering = self.create_vpc_peering() peering_id = peering['id'] peering_name = peering['name'] - requester_router_id = peering['request_vpc_info']['router_id'] - accepter_router_id = peering['accept_vpc_info']['router_id'] + local_router_id = peering['local_vpc_info']['router_id'] + peer_router_id = peering['peer_vpc_info']['router_id'] # List Vpc Peering By Id json_output = json.loads(self.openstack( @@ -66,23 +66,23 @@ def test_vpc_peering(self): # List Vpc Peering by Requester Router ID json_output = json.loads(self.openstack( 'vpc peering list -f json ' - '--router-id {}'.format(requester_router_id) + '--router-id {}'.format(local_router_id) )) for peering in json_output: self.assertEqual( - peering['Request Vpc Info']['router_id'], - requester_router_id + peering['Local Vpc Info']['router_id'], + local_router_id ) # List Vpc Peering by Accepter Router ID json_output = json.loads(self.openstack( 'vpc peering list -f json ' - '--router-id {}'.format(accepter_router_id) + '--router-id {}'.format(peer_router_id) )) for peering in json_output: self.assertEqual( - peering['Accept Vpc Info']['router_id'], - accepter_router_id + peering['Peer Vpc Info']['router_id'], + peer_router_id ) # Show Vpc Peering by Name diff --git a/otcextensions/tests/unit/osclient/vpc/v2/fakes.py b/otcextensions/tests/unit/osclient/vpc/v2/fakes.py index 6c57c7e6e..56d9c47d3 100644 --- a/otcextensions/tests/unit/osclient/vpc/v2/fakes.py +++ b/otcextensions/tests/unit/osclient/vpc/v2/fakes.py @@ -55,12 +55,12 @@ def generate(cls): "id": "id-" + uuid.uuid4().hex, "name": "name-" + uuid.uuid4().hex, "request_vpc_info": { - "router_id": uuid.uuid4().hex, - "project_id": uuid.uuid4().hex + "vpc_id": uuid.uuid4().hex, + "tenant_id": uuid.uuid4().hex }, "accept_vpc_info": { - "router_id": uuid.uuid4().hex, - "project_id": uuid.uuid4().hex + "vpc_id": uuid.uuid4().hex, + "tenant_id": uuid.uuid4().hex }, "status": "ACTIVE", "description": "my vpc peering", diff --git a/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py b/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py index f1d861ba8..b4fece168 100644 --- a/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py +++ b/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py @@ -28,18 +28,18 @@ class TestListVpcPeerings(fakes.TestVpc): column_list_headers = ( 'Id', 'Name', - 'Request Vpc Info', - 'Accept Vpc Info', + 'Local Vpc Info', + 'Peer Vpc Info', 'Status' ) - columns = ('id', 'name', 'request_vpc_info', 'accept_vpc_info', 'status') + columns = ('id', 'name', 'local_vpc_info', 'peer_vpc_info', 'status') data = [] for s in objects: data.append( - (s.id, s.name, s.request_vpc_info, s.accept_vpc_info, s.status)) + (s.id, s.name, s.local_vpc_info, s.peer_vpc_info, s.status)) def setUp(self): super(TestListVpcPeerings, self).setUp() @@ -116,8 +116,8 @@ class TestCreateVpcPeering(fakes.TestVpc): columns = ( 'id', 'name', - 'request_vpc_info', - 'accept_vpc_info', + 'local_vpc_info', + 'peer_vpc_info', 'description', 'created_at', 'updated_at', @@ -138,16 +138,16 @@ def setUp(self): def test_create_different_project(self): arglist = [ 'test-peering', - '--requester-router-id', 'test-local-router-uuid', - '--accepter-router-id', 'test-peer-router-uuid', - '--accepter-project-id', 'test-peer-project-uuid', + '--local-router-id', 'test-local-router-uuid', + '--peer-router-id', 'test-peer-router-uuid', + '--peer-project-id', 'test-peer-project-uuid', '--description', 'test-peering', ] verifylist = [ ('name', 'test-peering'), - ('requester_router_id', 'test-local-router-uuid'), - ('accepter_router_id', 'test-peer-router-uuid'), - ('accepter_project_id', 'test-peer-project-uuid'), + ('local_router_id', 'test-local-router-uuid'), + ('peer_router_id', 'test-peer-router-uuid'), + ('peer_project_id', 'test-peer-project-uuid'), ('description', 'test-peering'), ] # Verify cm is triggereg with default parameters @@ -174,14 +174,14 @@ def test_create_different_project(self): def test_create_same_project(self): arglist = [ 'test-peering', - '--requester-router-id', 'test-local-router-uuid', - '--accepter-router-id', 'test-peer-router-uuid', + '--local-router-id', 'test-local-router-uuid', + '--peer-router-id', 'test-peer-router-uuid', '--description', 'test-peering', ] verifylist = [ ('name', 'test-peering'), - ('requester_router_id', 'test-local-router-uuid'), - ('accepter_router_id', 'test-peer-router-uuid'), + ('local_router_id', 'test-local-router-uuid'), + ('peer_router_id', 'test-peer-router-uuid'), ('description', 'test-peering'), ] # Verify cm is triggereg with default parameters @@ -211,8 +211,8 @@ class TestUpdateVpcPeering(fakes.TestVpc): columns = ( 'id', 'name', - 'request_vpc_info', - 'accept_vpc_info', + 'local_vpc_info', + 'peer_vpc_info', 'description', 'created_at', 'updated_at', @@ -262,8 +262,8 @@ class TestShowVpcPeering(fakes.TestVpc): columns = ( 'id', 'name', - 'request_vpc_info', - 'accept_vpc_info', + 'local_vpc_info', + 'peer_vpc_info', 'description', 'created_at', 'updated_at', @@ -339,8 +339,8 @@ class TestSetVpcPeering(fakes.TestVpc): columns = ( 'id', 'name', - 'request_vpc_info', - 'accept_vpc_info', + 'local_vpc_info', + 'peer_vpc_info', 'description', 'created_at', 'updated_at', diff --git a/otcextensions/tests/unit/sdk/vpc/v2/test_peering.py b/otcextensions/tests/unit/sdk/vpc/v2/test_peering.py index 4ac9cdf41..24f40642c 100644 --- a/otcextensions/tests/unit/sdk/vpc/v2/test_peering.py +++ b/otcextensions/tests/unit/sdk/vpc/v2/test_peering.py @@ -56,8 +56,8 @@ def test_basic(self): def test_make_it(self): sot = peering.Peering(**EXAMPLE) self.assertEqual(EXAMPLE['status'], sot.status) - self.assertEqual(EXAMPLE['request_vpc_info'], sot.request_vpc_info) - self.assertEqual(EXAMPLE['accept_vpc_info'], sot.accept_vpc_info) + self.assertEqual(EXAMPLE['request_vpc_info'], sot.local_vpc_info) + self.assertEqual(EXAMPLE['accept_vpc_info'], sot.peer_vpc_info) self.assertEqual(EXAMPLE['id'], sot.id) self.assertEqual(EXAMPLE['name'], sot.name) From 73a74cd5fcf6eef8cdd3e84e26c15cd801db8300 Mon Sep 17 00:00:00 2001 From: Vineet Pruthi Date: Mon, 27 Jul 2020 17:45:28 +0000 Subject: [PATCH 11/12] Added VPC Route functionality --- doc/source/cli/index.rst | 1 + doc/source/cli/vpc.rst | 27 ++ doc/source/coverage.rst | 8 +- doc/source/sdk/guides/index.rst | 1 + doc/source/sdk/guides/vpc.rst | 131 +++++++ doc/source/sdk/proxies/index.rst | 1 + doc/source/sdk/proxies/vpc.rst | 27 ++ doc/source/sdk/resources/index.rst | 1 + doc/source/sdk/resources/vpc/index.rst | 8 + doc/source/sdk/resources/vpc/v2/peering.rst | 13 + doc/source/sdk/resources/vpc/v2/route.rst | 13 + examples/vpc/add_route.py | 31 ++ examples/vpc/create_peering.py | 2 +- examples/vpc/delete_route.py | 23 ++ examples/vpc/get_route.py | 26 ++ examples/vpc/list_routes.py | 23 ++ examples/vpc/set_peering.py | 11 +- otcextensions/osclient/vpc/v2/peering.py | 20 +- otcextensions/osclient/vpc/v2/route.py | 226 ++++++++++++ otcextensions/sdk/vpc/v2/_proxy.py | 57 +++ otcextensions/sdk/vpc/v2/route.py | 47 +++ .../osclient/vpc/v2/test_peering.py | 8 +- .../tests/unit/osclient/vpc/v2/fakes.py | 23 ++ .../unit/osclient/vpc/v2/test_peering.py | 19 +- .../tests/unit/osclient/vpc/v2/test_route.py | 329 ++++++++++++++++++ .../tests/unit/sdk/vpc/v2/test_proxy.py | 18 + .../tests/unit/sdk/vpc/v2/test_route.py | 57 +++ setup.cfg | 4 + 28 files changed, 1132 insertions(+), 23 deletions(-) create mode 100644 doc/source/cli/vpc.rst create mode 100644 doc/source/sdk/guides/vpc.rst create mode 100644 doc/source/sdk/proxies/vpc.rst create mode 100644 doc/source/sdk/resources/vpc/index.rst create mode 100644 doc/source/sdk/resources/vpc/v2/peering.rst create mode 100644 doc/source/sdk/resources/vpc/v2/route.rst create mode 100644 examples/vpc/add_route.py create mode 100644 examples/vpc/delete_route.py create mode 100644 examples/vpc/get_route.py create mode 100644 examples/vpc/list_routes.py create mode 100644 otcextensions/osclient/vpc/v2/route.py create mode 100644 otcextensions/sdk/vpc/v2/route.py create mode 100644 otcextensions/tests/unit/osclient/vpc/v2/test_route.py create mode 100644 otcextensions/tests/unit/sdk/vpc/v2/test_route.py diff --git a/doc/source/cli/index.rst b/doc/source/cli/index.rst index e0126a6e6..57414f124 100644 --- a/doc/source/cli/index.rst +++ b/doc/source/cli/index.rst @@ -37,3 +37,4 @@ documentation of these services: obs rds_v3 volume_backup + vpc diff --git a/doc/source/cli/vpc.rst b/doc/source/cli/vpc.rst new file mode 100644 index 000000000..0d37cf0eb --- /dev/null +++ b/doc/source/cli/vpc.rst @@ -0,0 +1,27 @@ +Network Address Translation (NAT) +================================= + +The VPC client is the command-line interface (CLI) for +the Virtual Private Cloud (VPC) API and its extensions. + +For help on a specific `vpc` command, enter: + +.. code-block:: console + + $ openstack vpc help SUBCOMMAND + +.. _peering: + +Vpc Peering Operations +---------------------- + +.. autoprogram-cliff:: openstack.vpc.v2 + :command: vpc peering * + +.. _route: + +Vpc Route Operations +-------------------- + +.. autoprogram-cliff:: openstack.vpc.v2 + :command: vpc route * diff --git a/doc/source/coverage.rst b/doc/source/coverage.rst index 4b33b9d3c..7a92a6940 100644 --- a/doc/source/coverage.rst +++ b/doc/source/coverage.rst @@ -108,7 +108,13 @@ under several service tags. This may change in future. - X - * - vbs - - Volume Backup + - Volume Backup Service + - X + - X + - X + - + * - vpc_v2 + - Virtual Private Cloud - X - X - X diff --git a/doc/source/sdk/guides/index.rst b/doc/source/sdk/guides/index.rst index cb1ca3545..04f39aab3 100644 --- a/doc/source/sdk/guides/index.rst +++ b/doc/source/sdk/guides/index.rst @@ -17,6 +17,7 @@ Open Telekom Cloud related User Guides kms nat rds + vpc logging .. _user_guides: diff --git a/doc/source/sdk/guides/vpc.rst b/doc/source/sdk/guides/vpc.rst new file mode 100644 index 000000000..cb15c6cb6 --- /dev/null +++ b/doc/source/sdk/guides/vpc.rst @@ -0,0 +1,131 @@ +Virtual Private Cloud (VPC) +=========================== + +.. contents:: Table of Contents + :local: + +VPC Peering Connection +---------------------- + +A VPC peering connection is a network connection between two VPCs that +enables you to route traffic between them using private IP addresses. +ECSs in either VPC can communicate with each other just as if they were in +the same VPC. You can create a VPC peering connection between your own VPCs, +or between your VPC and another account's VPC within the same region. A VPC +peering connection between VPCs in different regions will not take effect. + +List VPC Peerings +^^^^^^^^^^^^^^^^^ + +This interface is used to query all VPC peering connections accessible to the +tenant submitting the request. The connections are filtered based on the +filtering condition. +:class:`~otcextensions.sdk.vpc.v2.peering.Peering`. + +.. literalinclude:: ../examples/vpc/list_peerings.py + :lines: 16-23 + +Create VPC Peering +^^^^^^^^^^^^^^^^^^ + +This interface is used to create a VPC peering connection with +parameters. +:class:`~otcextensions.sdk.vpc.v2.peering.Peering`. + +.. literalinclude:: ../examples/vpc/create_peering.py + :lines: 16-33 + +Get VPC Peering +^^^^^^^^^^^^^^^ + +This interface is used to get a VPC peering connection by ID +or an instance of class. +:class:`~otcextensions.sdk.vpc.v2.peering.Peering`. + +.. literalinclude:: ../examples/vpc/get_peering.py + :lines: 16-26 + +Find VPC Peering +^^^^^^^^^^^^^^^^ + +This interface is used to find a VPC peering connection by id or name. +:class:`~otcextensions.sdk.vpc.v2.peering.Peering`. + +.. literalinclude:: ../examples/vpc/find_peering.py + :lines: 16-26 + +Update VPC Peering +^^^^^^^^^^^^^^^^^^ + +This interface is used to update parameters of a VPC peering connection by +id or an instance of class. +:class:`~otcextensions.sdk.vpc.v2.peering.Peering`. + +.. literalinclude:: ../examples/vpc/update_peering.py + :lines: 16-24 + +Delete VPC Peering +^^^^^^^^^^^^^^^^^^ + +This interface is used to delete a VPC peering connection by ID +or an instance of class. +:class:`~otcextensions.sdk.vpc.v2.peering.Peering`. + +.. literalinclude:: ../examples/vpc/delete_peering.py + :lines: 16-23 + +Set VPC Peering +^^^^^^^^^^^^^^^ + +This interface is used to accept of reject a VPC peering connection +request by ID or an instance of class. +:class:`~otcextensions.sdk.vpc.v2.peering.Peering`. + +.. literalinclude:: ../examples/vpc/set_peering.py + :lines: 16-32 + + +VPC Route +--------- + +To enable communication between the two VPCs, you need to add local and +peer routes for the VPC peering connection. + +List VPC Routes +^^^^^^^^^^^^^^^ + +This interface is used to query all routes of the tenant submitting the +request. The routes are filtered based on the filtering condition. +:class:`~otcextensions.sdk.vpc.v2.route.Route`. + +.. literalinclude:: ../examples/vpc/list_routes.py + :lines: 16-23 + +Add VPC Route +^^^^^^^^^^^^^ + +This Interface is used to add a VPC route. +:class:`~otcextensions.sdk.vpc.v2.route.Route`. + +.. literalinclude:: ../examples/vpc/add_route.py + :lines: 16-31 + +Get VPC Route +^^^^^^^^^^^^^ + +This interface is used to get a VPC route by ID +or an instance of class. +:class:`~otcextensions.sdk.vpc.v2.route.Route`. + +.. literalinclude:: ../examples/vpc/get_route.py + :lines: 16-26 + +Delete VPC Route +^^^^^^^^^^^^^^^^ + +This interface is used to delete a VPC route by ID +or an instance of class. +:class:`~otcextensions.sdk.vpc.v2.peering.Peering`. + +.. literalinclude:: ../examples/vpc/delete_route.py + :lines: 16-23 diff --git a/doc/source/sdk/proxies/index.rst b/doc/source/sdk/proxies/index.rst index c5f6a8d11..54c01ca98 100644 --- a/doc/source/sdk/proxies/index.rst +++ b/doc/source/sdk/proxies/index.rst @@ -19,6 +19,7 @@ Service Proxies Relational Database Service RDS V1 (RDSv1) Relational Database Service RDS V3 (RDS) Volume Backup Service (VBS) + Virtual Private Cloud (VPC) .. _service-proxies: diff --git a/doc/source/sdk/proxies/vpc.rst b/doc/source/sdk/proxies/vpc.rst new file mode 100644 index 000000000..dec522367 --- /dev/null +++ b/doc/source/sdk/proxies/vpc.rst @@ -0,0 +1,27 @@ +NAT API +======= + +.. automodule:: otcextensions.sdk.vpc.v2._proxy + +The Virtual Private Cloud Class +------------------------------- + +The nat high-level interface is available through the ``vpc`` +member of a :class:`~openstack.connection.Connection` object. The +``vpc`` member will only be added if the +``otcextensions.sdk.register_otc_extensions(conn)`` method is called. + +VPC Peering Operations +^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: otcextensions.sdk.vpc.v2._proxy.Proxy + :noindex: + :members: peerings, find_peering, create_peering, + update_peering, delete_peering, set_peering + +VPC Route Operations +^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: otcextensions.sdk.vpc.v2._proxy.Proxy + :noindex: + :members: routes, get_route, add_route, delete_route diff --git a/doc/source/sdk/resources/index.rst b/doc/source/sdk/resources/index.rst index a8fb45e4b..93f3c977a 100644 --- a/doc/source/sdk/resources/index.rst +++ b/doc/source/sdk/resources/index.rst @@ -19,6 +19,7 @@ Open Telekom Cloud Resources Network Address Translation (NAT) Object Block Storage (OBS) Relational Database Service (RDS) + Virtual Private Cloud (VPC) Every resource which is used within the proxy methods have own attributes. Those attributes define the behavior of the resource which can be a cluster diff --git a/doc/source/sdk/resources/vpc/index.rst b/doc/source/sdk/resources/vpc/index.rst new file mode 100644 index 000000000..36d0777b1 --- /dev/null +++ b/doc/source/sdk/resources/vpc/index.rst @@ -0,0 +1,8 @@ +NAT Resources +============= + +.. toctree:: + :maxdepth: 1 + + v2/peering + v2/route diff --git a/doc/source/sdk/resources/vpc/v2/peering.rst b/doc/source/sdk/resources/vpc/v2/peering.rst new file mode 100644 index 000000000..12b8dcc8b --- /dev/null +++ b/doc/source/sdk/resources/vpc/v2/peering.rst @@ -0,0 +1,13 @@ +otcextensions.sdk.vpc.v2.peering +================================ + +.. automodule:: otcextensions.sdk.vpc.v2.peering + +The VPC Peering Class +---------------------- + +The ``Peering`` class inherits from +:class:`~otcextensions.sdk.sdk_resource.Resource`. + +.. autoclass:: otcextensions.sdk.vpc.v2.peering.Peering + :members: diff --git a/doc/source/sdk/resources/vpc/v2/route.rst b/doc/source/sdk/resources/vpc/v2/route.rst new file mode 100644 index 000000000..d2f3cb467 --- /dev/null +++ b/doc/source/sdk/resources/vpc/v2/route.rst @@ -0,0 +1,13 @@ +otcextensions.sdk.vpc.v2.route +================================ + +.. automodule:: otcextensions.sdk.vpc.v2.route + +The VPC Route Class +------------------- + +The ``Route`` class inherits from +:class:`~otcextensions.sdk.sdk_resource.Resource`. + +.. autoclass:: otcextensions.sdk.vpc.v2.route.Route + :members: diff --git a/examples/vpc/add_route.py b/examples/vpc/add_route.py new file mode 100644 index 000000000..5daba9604 --- /dev/null +++ b/examples/vpc/add_route.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# 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. +""" +Add Route to VPC Peering Connection +""" +import openstack +from otcextensions import sdk + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') +sdk.register_otc_extensions(conn) + +attrs = { + "type": "peering", + "nexthop": "peering-uuid", + "destination": "192.168.100.0/24", + "vpc_id": "local-router-uuid" +} + +route = conn.vpc.add_route(**attrs) +print(route) diff --git a/examples/vpc/create_peering.py b/examples/vpc/create_peering.py index dfde34b58..5e3cb22bc 100644 --- a/examples/vpc/create_peering.py +++ b/examples/vpc/create_peering.py @@ -11,7 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. """ -Get VPC Peering by id or peering Instance +Create VPC Peering Connection """ import openstack from otcextensions import sdk diff --git a/examples/vpc/delete_route.py b/examples/vpc/delete_route.py new file mode 100644 index 000000000..608491515 --- /dev/null +++ b/examples/vpc/delete_route.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# 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. +""" +Delete VPC Route By ID +""" +import openstack + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') + + +route_id = "route-uuid" +conn.vpc.delete_route(route_id) diff --git a/examples/vpc/get_route.py b/examples/vpc/get_route.py new file mode 100644 index 000000000..9b62c172f --- /dev/null +++ b/examples/vpc/get_route.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# 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. +""" +Get VPC Route by ID +""" +import openstack +from otcextensions import sdk + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') +sdk.register_otc_extensions(conn) + + +route_id = "route-uuid" +route = conn.vpc.get_route(route_id) +print(route) diff --git a/examples/vpc/list_routes.py b/examples/vpc/list_routes.py new file mode 100644 index 000000000..896a2c51b --- /dev/null +++ b/examples/vpc/list_routes.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# 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. +""" +List VPC Routes +""" +import openstack + + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') + +for route in conn.vpc.routes(): + print(route) diff --git a/examples/vpc/set_peering.py b/examples/vpc/set_peering.py index 6eb31ce5a..1a704d67a 100644 --- a/examples/vpc/set_peering.py +++ b/examples/vpc/set_peering.py @@ -11,7 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. """ -Accept or reject VPC Peering request +Accept or Reject VPC Peering request """ import openstack @@ -21,7 +21,12 @@ peering = conn.vpc.get_peering("peering_id") -# accept or reject -set_status = 'accept_or_reject' +# accept VPC peering request +set_status = 'accept' +peering = conn.vpc.set_peering(peering=peering, set_status=set_status) +print(peering) + +# Reject VPC peering request +set_status = 'reject' peering = conn.vpc.set_peering(peering=peering, set_status=set_status) print(peering) diff --git a/otcextensions/osclient/vpc/v2/peering.py b/otcextensions/osclient/vpc/v2/peering.py index 60b7a446c..f9d8b621f 100644 --- a/otcextensions/osclient/vpc/v2/peering.py +++ b/otcextensions/osclient/vpc/v2/peering.py @@ -40,12 +40,12 @@ def _update_vpc_info(vpcinfo): def set_attributes_for_print(peerings): for peering in peerings: - setattr(peering, - 'local_vpc_info', - _update_vpc_info(peering.local_vpc_info)) - setattr(peering, - 'peer_vpc_info', - _update_vpc_info(peering.peer_vpc_info)) + setattr(peering, 'local_router_id', + peering.local_vpc_info['vpc_id']) + setattr(peering, 'peer_router_id', + peering.peer_vpc_info['vpc_id']) + setattr(peering, 'peer_project_id', + peering.peer_vpc_info['tenant_id']) yield peering @@ -78,7 +78,13 @@ def new(self, *args, **kwargs): class ListVpcPeerings(command.Lister): _description = _("List Vpc Peerings.") - columns = ('Id', 'Name', 'Local Vpc Info', 'Peer Vpc Info', 'Status') + columns = ( + 'Id', + 'Name', + 'Status', + 'Local Router Id', + 'Peer Router Id', + 'Peer Project Id') def get_parser(self, prog_name): parser = super(ListVpcPeerings, self).get_parser(prog_name) diff --git a/otcextensions/osclient/vpc/v2/route.py b/otcextensions/osclient/vpc/v2/route.py new file mode 100644 index 000000000..f5f1574c9 --- /dev/null +++ b/otcextensions/osclient/vpc/v2/route.py @@ -0,0 +1,226 @@ +# 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. +# +"""VPC Route v2 action implementations""" +import logging + +from osc_lib import utils +from osc_lib import exceptions +from osc_lib.command import command + +from otcextensions.i18n import _ + +LOG = logging.getLogger(__name__) + + +def set_attributes_for_print(routes): + for route in routes: + yield route + + +def translate_response(func): + def new(self, *args, **kwargs): + response = func(self, *args, **kwargs) + columns = ( + 'id', + 'type', + 'nexthop', + 'destination', + 'router_id', + 'project_id' + ) + data = utils.get_item_properties(response, columns) + return (columns, data) + new.__name__ = func.__name__ + new.__doc__ = func.__doc__ + return new + + +class ListVpcRoutes(command.Lister): + + _description = _("List Vpc Routes.") + columns = ( + 'Id', + 'Type', + 'Router Id', + 'Project Id', + 'NextHop', + 'Destination' + ) + + def get_parser(self, prog_name): + parser = super(ListVpcRoutes, self).get_parser(prog_name) + + parser.add_argument( + '--id', + metavar='', + help=_("Specifies the ID of the VPC route."), + ) + parser.add_argument( + '--limit', + metavar='', + type=int, + help=_("Limit to fetch number of records."), + ) + parser.add_argument( + '--marker', + metavar='', + help=_("Specifies the start resource ID of pagination query."), + ) + parser.add_argument( + '--project-id', + metavar='', + help=_("Specifies the project ID."), + ) + parser.add_argument( + '--router-id', + metavar='', + help=_("Specifies the router/vpc ID."), + ) + parser.add_argument( + '--destination', + metavar='', + help=_("Specifies that the route destination address (CIDR) " + "is used as the filtering condition."), + ) + parser.add_argument( + '--type', + metavar='', + help=_("Specifies that the type is used as the filtering " + "condition. Currently, the value can only be peering."), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + args_list = [ + 'id', + 'type', + 'limit', + 'marker', + 'project_id', + 'router_id', + 'destination'] + attrs = {} + for arg in args_list: + val = getattr(parsed_args, arg) + if val: + attrs[arg] = val + + data = client.routes(**attrs) + if data: + data = set_attributes_for_print(data) + + return (self.columns, (utils.get_item_properties(s, self.columns) + for s in data)) + + +class ShowVpcRoute(command.ShowOne): + _description = _("Show VPC Route Details.") + + def get_parser(self, prog_name): + parser = super(ShowVpcRoute, self).get_parser(prog_name) + parser.add_argument( + 'route', + metavar='', + help=_("Specifies the ID of the VPC route."), + ) + return parser + + @translate_response + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + return client.get_route(parsed_args.route) + + +class AddVpcRoute(command.ShowOne): + _description = _("Add Vpc Route.") + + def get_parser(self, prog_name): + parser = super(AddVpcRoute, self).get_parser(prog_name) + parser.add_argument( + '--destination', + metavar='', + required=True, + help=_("Specifies the destination address in the CIDR " + "notation format, for example, 192.168.200.0/24."), + ) + parser.add_argument( + '--nexthop', + metavar='', + required=True, + help=_("Specifies the next hop. If the type is " + "peering, enter the VPC peering connection ID."), + ) + parser.add_argument( + '--type', + metavar='', + default='peering', + help=_("Specifies the route type. Currently, the value can " + "only be peering."), + ) + parser.add_argument( + '--router-id', + metavar='', + required=True, + help=_("Specifies the requesting router ID for creating a route."), + ) + return parser + + @translate_response + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + args_list = [ + 'type', + 'router_id', + 'destination', + 'nexthop'] + attrs = {} + for arg in args_list: + val = getattr(parsed_args, arg) + if val: + attrs[arg] = val + + return client.add_route(**attrs) + + +class DeleteVpcRoute(command.Command): + + _description = _("Deletes VPC Route.") + + def get_parser(self, prog_name): + parser = super(DeleteVpcRoute, self).get_parser(prog_name) + parser.add_argument( + 'route', + metavar='', + nargs='+', + help=_("VPC Routes(s) ID to delete"), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + result = 0 + for route in parsed_args.route: + try: + obj = client.get_route(route) + client.delete_route(obj.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete VPC route with " + "ID '%(route)s': %(e)s"), + {'route': route, 'e': e}) + if result > 0: + total = len(parsed_args.route) + msg = (_("%(result)s of %(total)s VPC route(s) failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) diff --git a/otcextensions/sdk/vpc/v2/_proxy.py b/otcextensions/sdk/vpc/v2/_proxy.py index 713f34055..6e300d90e 100644 --- a/otcextensions/sdk/vpc/v2/_proxy.py +++ b/otcextensions/sdk/vpc/v2/_proxy.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. from otcextensions.sdk.vpc.v2 import peering as _peering +from otcextensions.sdk.vpc.v2 import route as _route from openstack import proxy @@ -126,3 +127,59 @@ def set_peering(self, peering, set_status): peering = self._get_resource(_peering.Peering, peering) self._override_endpoint() return peering._set_peering(self, set_status.lower()) + + # ======== Route ======== + + def add_route(self, **attrs): + """Add vpc route + + :param dict attrs: Keyword arguments which will be used to create + a :class:`~otcextensions.sdk.vpc.v2.route.Route` + """ + self._override_endpoint() + return self._create(_route.Route, **attrs) + + def delete_route(self, route, ignore_missing=True): + """Delete a vpc route + + :param route: route id or an instance of + :class:`~otcextensions.sdk.vpc.v2.route.Route` + :param bool ignore_missing: When set to ``False`` + :class:`~openstack.exceptions.ResourceNotFound` will be raised when + the vpc route does not exist. + When set to ``True``, no exception will be set when attempting to + delete a nonexistent route. + + :returns: ``None`` + """ + self._override_endpoint() + return self._delete(_route.Route, route, + ignore_missing=ignore_missing) + + def routes(self, **query): + """Return a generator of vpc routes + + :param dict query: Optional query parameters to be sent to limit + the resources being returned. + + :returns: A generator of vpc route objects + + :rtype: :class:`~otcextensions.sdk.vpc.v2.route.Route` + """ + self._override_endpoint() + return self._list(_route.Route, **query) + + def get_route(self, route): + """Get details of a single vpc route + + :param route: The value can be the ID of a vpc route or a + :class:`~otcextensions.sdk.vpc.v2.route.Route` + instance. + + :returns: One :class:`~otcextensions.sdk.vpc.v2.route.Route` + + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + """ + self._override_endpoint() + return self._get(_route.Route, route) diff --git a/otcextensions/sdk/vpc/v2/route.py b/otcextensions/sdk/vpc/v2/route.py new file mode 100644 index 000000000..1b70fe99c --- /dev/null +++ b/otcextensions/sdk/vpc/v2/route.py @@ -0,0 +1,47 @@ +# 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 resource + + +class Route(resource.Resource): + resources_key = 'routes' + resource_key = 'route' + base_path = '/vpc/routes' + + # capabilities + allow_create = True + allow_fetch = True + allow_delete = True + allow_list = True + + _query_mapping = resource.QueryParameters( + 'id', 'marker', 'limit', 'type', 'router_id', + 'destination', 'project_id', project_id='tenant_id', + router_id='vpc_id' + ) + + #: Specifies the VPC peering connection ID. + #: *Type: uuid* + id = resource.Body('id') + #: Specifies the next hop. + #: If the route type is peering, enter the VPC peering connection ID. + nexthop = resource.Body('nexthop') + #: Specifies the destination address in the CIDR notation format, + #: for example, 192.168.200.0/24. + destination = resource.Body('destination') + #: Specifies the route type. Currently, the value can only be peering. + type = resource.Body('type') + #: Specifies the Router of the route. + #: Set this parameter to the existing Router ID. + router_id = resource.Body('vpc_id') + #: Specifies the project ID. + project_id = resource.Body('tenant_id') diff --git a/otcextensions/tests/functional/osclient/vpc/v2/test_peering.py b/otcextensions/tests/functional/osclient/vpc/v2/test_peering.py index e34d295c2..26270ad56 100644 --- a/otcextensions/tests/functional/osclient/vpc/v2/test_peering.py +++ b/otcextensions/tests/functional/osclient/vpc/v2/test_peering.py @@ -68,11 +68,7 @@ def test_vpc_peering(self): 'vpc peering list -f json ' '--router-id {}'.format(local_router_id) )) - for peering in json_output: - self.assertEqual( - peering['Local Vpc Info']['router_id'], - local_router_id - ) + self.assertIsNotNone(json_output) # List Vpc Peering by Accepter Router ID json_output = json.loads(self.openstack( @@ -81,7 +77,7 @@ def test_vpc_peering(self): )) for peering in json_output: self.assertEqual( - peering['Peer Vpc Info']['router_id'], + peering['Peer Router Id'], peer_router_id ) diff --git a/otcextensions/tests/unit/osclient/vpc/v2/fakes.py b/otcextensions/tests/unit/osclient/vpc/v2/fakes.py index 56d9c47d3..81c3eb11e 100644 --- a/otcextensions/tests/unit/osclient/vpc/v2/fakes.py +++ b/otcextensions/tests/unit/osclient/vpc/v2/fakes.py @@ -18,6 +18,7 @@ from otcextensions.tests.unit.osclient import test_base from otcextensions.sdk.vpc.v2 import peering +from otcextensions.sdk.vpc.v2 import route def gen_data(data, columns): @@ -69,3 +70,25 @@ def generate(cls): } return peering.Peering(**object_info) + + +class FakeVpcRoute(test_base.Fake): + """Fake one or more VPC routes.""" + @classmethod + def generate(cls): + """Create a fake VPC route. + + :return: + A FakeResource object, with id, name and so on + """ + # Set default attributes. + object_info = { + "id": "id-" + uuid.uuid4().hex, + "type": "peering", + "nexthop": uuid.uuid4().hex, + "router_id": uuid.uuid4().hex, + "project_id": uuid.uuid4().hex, + "destination": "192.168.200.0/24", + } + + return route.Route(**object_info) diff --git a/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py b/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py index b4fece168..f34d3598f 100644 --- a/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py +++ b/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py @@ -28,18 +28,27 @@ class TestListVpcPeerings(fakes.TestVpc): column_list_headers = ( 'Id', 'Name', - 'Local Vpc Info', - 'Peer Vpc Info', - 'Status' + 'Status', + 'Local Router Id', + 'Peer Router Id', + 'Peer Project Id', ) - columns = ('id', 'name', 'local_vpc_info', 'peer_vpc_info', 'status') + columns = ( + 'id', + 'name', + 'status', + 'local_router_id', + 'peer_router_id', + 'peer_project_id' + ) data = [] for s in objects: data.append( - (s.id, s.name, s.local_vpc_info, s.peer_vpc_info, s.status)) + (s.id, s.name, s.status, s.local_vpc_info['vpc_id'], + s.peer_vpc_info['vpc_id'], s.peer_vpc_info['tenant_id'])) def setUp(self): super(TestListVpcPeerings, self).setUp() diff --git a/otcextensions/tests/unit/osclient/vpc/v2/test_route.py b/otcextensions/tests/unit/osclient/vpc/v2/test_route.py new file mode 100644 index 000000000..b52e5a235 --- /dev/null +++ b/otcextensions/tests/unit/osclient/vpc/v2/test_route.py @@ -0,0 +1,329 @@ +# 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 mock +from unittest.mock import call + +from osc_lib import exceptions + +from otcextensions.osclient.vpc.v2 import route +from otcextensions.tests.unit.osclient.vpc.v2 import fakes + +from openstackclient.tests.unit import utils as tests_utils + + +class TestListVpcRoutes(fakes.TestVpc): + + objects = fakes.FakeVpcRoute.create_multiple(3) + + column_list_headers = ( + 'Id', + 'Type', + 'Router Id', + 'Project Id', + 'NextHop', + 'Destination' + ) + + columns = ( + 'id', + 'type', + 'router_id', + 'project_id', + 'nexthop', + 'destination' + ) + + data = [] + + for s in objects: + data.append( + (s.id, s.type, s.router_id, s.project_id, + s.nexthop, s.destination)) + + def setUp(self): + super(TestListVpcRoutes, self).setUp() + + self.cmd = route.ListVpcRoutes(self.app, None) + + self.client.routes = mock.Mock() + self.client.api_mock = self.client.routes + + def test_list(self): + arglist = [] + + verifylist = [] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Set the response + self.client.api_mock.side_effect = [self.objects] + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + + self.client.api_mock.assert_called_with() + + self.assertEqual(self.column_list_headers, columns) + self.assertEqual(self.data, list(data)) + + def test_list_args(self): + arglist = [ + '--limit', '1', + '--marker', '2', + '--id', '3', + '--destination', '4', + '--project-id', '5', + '--router-id', '6' + ] + + verifylist = [ + ('limit', 1), + ('marker', '2'), + ('id', '3'), + ('destination', '4'), + ('project_id', '5'), + ('router_id', '6') + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Set the response + self.client.api_mock.side_effect = [self.objects] + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + + self.client.api_mock.assert_called_with( + limit=1, + marker='2', + id='3', + destination='4', + project_id='5', + router_id='6', + ) + + +class TestAddVpcRoute(fakes.TestVpc): + + _data = fakes.FakeVpcRoute.create_one() + + columns = ( + 'id', + 'type', + 'nexthop', + 'destination', + 'router_id', + 'project_id' + ) + + data = fakes.gen_data(_data, columns) + + def setUp(self): + super(TestAddVpcRoute, self).setUp() + + self.cmd = route.AddVpcRoute(self.app, None) + self.client.add_route = mock.Mock( + return_value=fakes.FakeVpcRoute.create_one()) + + def test_add_route(self): + arglist = [ + '--nexthop', 'test-peering-uuid', + '--destination', '192.168.1.0/24', + '--router-id', 'test-router-uuid', + ] + verifylist = [ + ('nexthop', 'test-peering-uuid'), + ('type', 'peering'), + ('destination', '192.168.1.0/24'), + ('router_id', 'test-router-uuid'), + ] + # Verify cm is triggereg with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + attrs = { + 'nexthop': 'test-peering-uuid', + 'type': 'peering', + 'destination': '192.168.1.0/24', + 'router_id': 'test-router-uuid' + } + + self.client.add_route.assert_called_with(**attrs) + self.assertEqual(self.columns, columns) + + +class TestShowVpcRoute(fakes.TestVpc): + + _data = fakes.FakeVpcRoute.create_one() + + columns = ( + 'id', + 'type', + 'nexthop', + 'destination', + 'router_id', + 'project_id' + ) + + data = fakes.gen_data(_data, columns) + + def setUp(self): + super(TestShowVpcRoute, self).setUp() + + self.cmd = route.ShowVpcRoute(self.app, None) + + self.client.get_route = mock.Mock(return_value=self._data) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Testing that a call without the required argument will fail and + # throw a "ParserExecption" + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_show(self): + arglist = [ + self._data.id, + ] + + verifylist = [ + ('route', self._data.id), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + self.client.get_route.assert_called_with(self._data.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_show_non_existent(self): + arglist = [ + 'unexist_vpc_route', + ] + + verifylist = [ + ('route', 'unexist_vpc_route'), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = exceptions.CommandError('Resource Not Found') + self.client.get_route = ( + mock.Mock(side_effect=find_mock_result) + ) + + # Trigger the action + try: + self.cmd.take_action(parsed_args) + except Exception as e: + self.assertEqual('Resource Not Found', str(e)) + self.client.get_route.assert_called_with('unexist_vpc_route') + + +class TestDeleteVpcRoute(fakes.TestVpc): + + _data = fakes.FakeVpcRoute.create_multiple(2) + + def setUp(self): + super(TestDeleteVpcRoute, self).setUp() + + self.client.delete_route = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = route.DeleteVpcRoute(self.app, None) + + def test_delete(self): + arglist = [ + self._data[0].id, + ] + + verifylist = [ + ('route', [self._data[0].id]), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.client.get_route = ( + mock.Mock(return_value=self._data[0]) + ) + + # Trigger the action + result = self.cmd.take_action(parsed_args) + self.client.delete_route.assert_called_with(self._data[0].id) + self.assertIsNone(result) + + def test_multiple_delete(self): + arglist = [] + + for data in self._data: + arglist.append(data.id) + + verifylist = [ + ('route', arglist), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + get_mock_result = self._data + self.client.get_route = ( + mock.Mock(side_effect=get_mock_result) + ) + + # Trigger the action + result = self.cmd.take_action(parsed_args) + + calls = [] + for data in self._data: + calls.append(call(data.id)) + self.client.delete_route.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multiple_delete_with_exception(self): + arglist = [ + self._data[0].id, + 'unexist_vpc_route', + ] + verifylist = [ + ('route', arglist), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + get_mock_result = [self._data[0], exceptions.CommandError] + self.client.get_route = ( + mock.Mock(side_effect=get_mock_result) + ) + + # Trigger the action + try: + self.cmd.take_action(parsed_args) + except Exception as e: + self.assertEqual( + '1 of 2 VPC route(s) failed to delete.', str(e)) + + self.client.get_route.assert_any_call(self._data[0].id) + self.client.get_route.assert_any_call('unexist_vpc_route') + self.client.delete_route.assert_called_once_with(self._data[0].id) diff --git a/otcextensions/tests/unit/sdk/vpc/v2/test_proxy.py b/otcextensions/tests/unit/sdk/vpc/v2/test_proxy.py index 60b1ebfa3..8d13f96e3 100644 --- a/otcextensions/tests/unit/sdk/vpc/v2/test_proxy.py +++ b/otcextensions/tests/unit/sdk/vpc/v2/test_proxy.py @@ -24,6 +24,7 @@ from otcextensions.sdk.vpc.v2 import _proxy from otcextensions.sdk.vpc.v2 import peering +from otcextensions.sdk.vpc.v2 import route from openstack.tests.unit import test_proxy_base @@ -61,3 +62,20 @@ def test_set_peering(self): method_kwargs={'set_status': 'accept'}, expected_args=['accept'] ) + + +class TestVpcRoute(TestVpcProxy): + def test_route_add(self): + self.verify_create(self.proxy.add_route, route.Route, + method_kwargs={'name': 'id'}, + expected_kwargs={'name': 'id'}) + + def test_route_delete(self): + self.verify_delete(self.proxy.delete_route, + route.Route, True) + + def test_route_get(self): + self.verify_get(self.proxy.get_route, route.Route) + + def test_routes(self): + self.verify_list(self.proxy.routes, route.Route) diff --git a/otcextensions/tests/unit/sdk/vpc/v2/test_route.py b/otcextensions/tests/unit/sdk/vpc/v2/test_route.py new file mode 100644 index 000000000..f48c0ddc7 --- /dev/null +++ b/otcextensions/tests/unit/sdk/vpc/v2/test_route.py @@ -0,0 +1,57 @@ +# 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 keystoneauth1 import adapter + +import mock + +from openstack.tests.unit import base +from otcextensions.sdk.vpc.v2 import route + + +IDENTIFIER = 'ID' +EXAMPLE = { + "type": "peering", + "nexthop": "60c809cb-6731-45d0-ace8-3bf5626421a9", + "destination": "192.168.200.0/24", + "vpc_id": "ab78be2d-782f-42a5-aa72-35879f6890ff", + "tenant_id": "6fbe9263116a4b68818cf1edce16bc4f", + "id": "3d42a0d4-a980-4613-ae76-a2cddecff054" +} + + +class TestRoute(base.TestCase): + + def setUp(self): + super(TestRoute, self).setUp() + self.sess = mock.Mock(spec=adapter.Adapter) + self.sess.put = mock.Mock() + + def test_basic(self): + sot = route.Route() + self.assertEqual('route', sot.resource_key) + self.assertEqual('routes', sot.resources_key) + path = '/vpc/routes' + self.assertEqual(path, sot.base_path) + self.assertTrue(sot.allow_list) + self.assertTrue(sot.allow_create) + self.assertTrue(sot.allow_fetch) + self.assertTrue(sot.allow_delete) + self.assertFalse(sot.allow_commit) + + def test_make_it(self): + sot = route.Route(**EXAMPLE) + self.assertEqual(EXAMPLE['id'], sot.id) + self.assertEqual(EXAMPLE['destination'], sot.destination) + self.assertEqual(EXAMPLE['nexthop'], sot.nexthop) + self.assertEqual(EXAMPLE['type'], sot.type) + self.assertEqual(EXAMPLE['vpc_id'], sot.router_id) + self.assertEqual(EXAMPLE['tenant_id'], sot.project_id) diff --git a/setup.cfg b/setup.cfg index 7fb799f9e..49fc72e13 100644 --- a/setup.cfg +++ b/setup.cfg @@ -307,6 +307,10 @@ openstack.vpc.v2 = vpc_peering_update = otcextensions.osclient.vpc.v2.peering:UpdateVpcPeering vpc_peering_delete = otcextensions.osclient.vpc.v2.peering:DeleteVpcPeering vpc_peering_set = otcextensions.osclient.vpc.v2.peering:SetVpcPeering + vpc_route_list = otcextensions.osclient.vpc.v2.route:ListVpcRoutes + vpc_route_show = otcextensions.osclient.vpc.v2.route:ShowVpcRoute + vpc_route_add = otcextensions.osclient.vpc.v2.route:AddVpcRoute + vpc_route_delete = otcextensions.osclient.vpc.v2.route:DeleteVpcRoute [build_sphinx] builders = html,man From d713049640b287117c9277c2ed29f817afc36e2b Mon Sep 17 00:00:00 2001 From: Vineet Pruthi Date: Tue, 28 Jul 2020 02:38:00 +0000 Subject: [PATCH 12/12] Minor Fixes --- doc/source/cli/vpc.rst | 4 ++-- doc/source/sdk/proxies/vpc.rst | 2 +- doc/source/sdk/resources/vpc/index.rst | 2 +- examples/vpc/find_peering.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/source/cli/vpc.rst b/doc/source/cli/vpc.rst index 0d37cf0eb..52aa93f5e 100644 --- a/doc/source/cli/vpc.rst +++ b/doc/source/cli/vpc.rst @@ -1,5 +1,5 @@ -Network Address Translation (NAT) -================================= +Virtual Private Cloud (VPC) +=========================== The VPC client is the command-line interface (CLI) for the Virtual Private Cloud (VPC) API and its extensions. diff --git a/doc/source/sdk/proxies/vpc.rst b/doc/source/sdk/proxies/vpc.rst index dec522367..18cd53c74 100644 --- a/doc/source/sdk/proxies/vpc.rst +++ b/doc/source/sdk/proxies/vpc.rst @@ -1,4 +1,4 @@ -NAT API +VPC API ======= .. automodule:: otcextensions.sdk.vpc.v2._proxy diff --git a/doc/source/sdk/resources/vpc/index.rst b/doc/source/sdk/resources/vpc/index.rst index 36d0777b1..9c255873c 100644 --- a/doc/source/sdk/resources/vpc/index.rst +++ b/doc/source/sdk/resources/vpc/index.rst @@ -1,4 +1,4 @@ -NAT Resources +VPC Resources ============= .. toctree:: diff --git a/examples/vpc/find_peering.py b/examples/vpc/find_peering.py index 18c6f81c8..15eefd713 100644 --- a/examples/vpc/find_peering.py +++ b/examples/vpc/find_peering.py @@ -11,7 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. """ -Get VPC Peering by id or name +Find VPC Peering by id or name """ import openstack from otcextensions import sdk