diff --git a/.travis.yml b/.travis.yml index 0cfad8233..2e6a49ecd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,8 @@ matrix: include: - python: 3.6 env: TOXENV=py36 + - python: 3.7 + env: TOXENV=py37 - python: 3.6 env: TOXENV=pep8 install: pip install tox diff --git a/doc/source/sdk/proxies/index.rst b/doc/source/sdk/proxies/index.rst index e83847d81..c5f6a8d11 100644 --- a/doc/source/sdk/proxies/index.rst +++ b/doc/source/sdk/proxies/index.rst @@ -14,10 +14,11 @@ Service Proxies Distributed Message Service (DMS) Domain Name Server Service (DNS) Key Management Service (KMS) + Network Address Translation (NAT) Object Block Storage (OBS) - Volume Backup Service (VBS) Relational Database Service RDS V1 (RDSv1) Relational Database Service RDS V3 (RDS) + Volume Backup Service (VBS) .. _service-proxies: diff --git a/doc/source/sdk/proxies/nat.rst b/doc/source/sdk/proxies/nat.rst new file mode 100644 index 000000000..824b1d0b5 --- /dev/null +++ b/doc/source/sdk/proxies/nat.rst @@ -0,0 +1,34 @@ +NAT API +======= + +.. automodule:: otcextensions.sdk.nat.v2._proxy + +The Network Address Translation Class +------------------------------------- + +The nat high-level interface is available through the ``nat`` +member of a :class:`~openstack.connection.Connection` object. The +``nat`` member will only be added if the +``otcextensions.sdk.register_otc_extensions(conn)`` method is called. + +Gateway Operations +^^^^^^^^^^^^^^^^^^ + +.. autoclass:: otcextensions.sdk.nat.v2._proxy.Proxy + :noindex: + :members: gateways, find_gateway, + create_gateway, update_gateway, delete_gateway + +SNAT Rule Operations +^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: otcextensions.sdk.nat.v2._proxy.Proxy + :noindex: + :members: snat_rules, get_snat_rule, create_snat_rule, delete_snat_rule + +DNAT Rule Operations +^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: otcextensions.sdk.nat.v2._proxy.Proxy + :noindex: + :members: dnat_rules, get_dnat_rule, create_dnat_rule, delete_dnat_rule diff --git a/doc/source/sdk/resources/index.rst b/doc/source/sdk/resources/index.rst index 8987ad415..a8fb45e4b 100644 --- a/doc/source/sdk/resources/index.rst +++ b/doc/source/sdk/resources/index.rst @@ -16,6 +16,7 @@ Open Telekom Cloud Resources Distributed Message Service (DMS) Domain Name Service (DNS) Key Management Service (KMS) + Network Address Translation (NAT) Object Block Storage (OBS) Relational Database Service (RDS) diff --git a/doc/source/sdk/resources/nat/index.rst b/doc/source/sdk/resources/nat/index.rst new file mode 100644 index 000000000..712e949da --- /dev/null +++ b/doc/source/sdk/resources/nat/index.rst @@ -0,0 +1,9 @@ +NAT Resources +============= + +.. toctree:: + :maxdepth: 1 + + v2/gateway + v2/snat + v2/dnat diff --git a/doc/source/sdk/resources/nat/v2/dnat.rst b/doc/source/sdk/resources/nat/v2/dnat.rst new file mode 100644 index 000000000..8fcb3cf4f --- /dev/null +++ b/doc/source/sdk/resources/nat/v2/dnat.rst @@ -0,0 +1,13 @@ +otcextensions.sdk.nat.v2.dnat +============================= + +.. automodule:: otcextensions.sdk.nat.v2.dnat + +The DNAT Rule Class +-------------------- + +The ``Dnat`` class inherits from +:class:`~otcextensions.sdk.sdk_resource.Resource`. + +.. autoclass:: otcextensions.sdk.nat.v2.dnat.Dnat + :members: diff --git a/doc/source/sdk/resources/nat/v2/gateway.rst b/doc/source/sdk/resources/nat/v2/gateway.rst new file mode 100644 index 000000000..1fea7908d --- /dev/null +++ b/doc/source/sdk/resources/nat/v2/gateway.rst @@ -0,0 +1,13 @@ +otcextensions.sdk.nat.v2.gateway +================================ + +.. automodule:: otcextensions.sdk.nat.v2.gateway + +The NAT Gateway Class +---------------------- + +The ``Gateway`` class inherits from +:class:`~otcextensions.sdk.sdk_resource.Resource`. + +.. autoclass:: otcextensions.sdk.nat.v2.gateway.Gateway + :members: diff --git a/doc/source/sdk/resources/nat/v2/snat.rst b/doc/source/sdk/resources/nat/v2/snat.rst new file mode 100644 index 000000000..6dd62e650 --- /dev/null +++ b/doc/source/sdk/resources/nat/v2/snat.rst @@ -0,0 +1,13 @@ +otcextensions.sdk.nat.v2.snat +============================= + +.. automodule:: otcextensions.sdk.nat.v2.snat + +The SNAT Rule Class +-------------------- + +The ``Snat`` class inherits from +:class:`~otcextensions.sdk.sdk_resource.Resource`. + +.. autoclass:: otcextensions.sdk.nat.v2.snat.Snat + :members: diff --git a/otcextensions/osclient/nat/__init__.py b/otcextensions/osclient/nat/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/osclient/nat/client.py b/otcextensions/osclient/nat/client.py new file mode 100644 index 000000000..110ddff6f --- /dev/null +++ b/otcextensions/osclient/nat/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_nat_api_version' +API_NAME = "nat" +API_VERSIONS = { + "2": "openstack.connection.Connection" +} + + +def make_client(instance): + """Returns a rds proxy""" + + conn = instance.sdk_connection + + if getattr(conn, 'nat', None) is None: + LOG.debug('OTC extensions are not registered. Do that now') + sdk.register_otc_extensions(conn) + + LOG.debug('NAT client initialized using OpenStack OTC SDK: %s', + conn.nat) + return conn.nat + + +def build_option_parser(parser): + """Hook to add global options""" + parser.add_argument( + '--os-nat-api-version', + metavar='', + default=utils.env('OS_NAT_API_VERSION'), + help=_("NAT API version, default=%s " + "(Env: OS_NAT_API_VERSION)") % DEFAULT_API_VERSION + ) + return parser diff --git a/otcextensions/osclient/nat/v2/__init__.py b/otcextensions/osclient/nat/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/osclient/nat/v2/dnat.py b/otcextensions/osclient/nat/v2/dnat.py new file mode 100644 index 000000000..d241a650f --- /dev/null +++ b/otcextensions/osclient/nat/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/nat/v2/gateway.py b/otcextensions/osclient/nat/v2/gateway.py new file mode 100644 index 000000000..37f34be56 --- /dev/null +++ b/otcextensions/osclient/nat/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/nat/v2/snat.py b/otcextensions/osclient/nat/v2/snat.py new file mode 100644 index 000000000..4dc54cabe --- /dev/null +++ b/otcextensions/osclient/nat/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 f9837b5b8..030d22517 100644 --- a/otcextensions/sdk/__init__.py +++ b/otcextensions/sdk/__init__.py @@ -114,7 +114,7 @@ 'service_type': 'mrs' }, 'nat': { - 'service_type': 'nat' + 'service_type': 'nat', }, 'obs': { 'service_type': 'obs', diff --git a/otcextensions/sdk/nat/v2/_proxy.py b/otcextensions/sdk/nat/v2/_proxy.py index 4df1a1a06..5f994c2d8 100644 --- a/otcextensions/sdk/nat/v2/_proxy.py +++ b/otcextensions/sdk/nat/v2/_proxy.py @@ -83,6 +83,21 @@ def update_gateway(self, gateway, **attrs): """ return self._update(_gateway.Gateway, gateway, **attrs) + def find_gateway(self, name_or_id, ignore_missing=False): + """Find a single Nat Gateway + + :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 gateway does not exist. + When set to ``True``, no exception will be set when attempting + to delete a nonexistent gateway. + + :returns: ``None`` + """ + return self._find(_gateway.Gateway, name_or_id, + ignore_missing=ignore_missing) + # ======== SNAT rules ======== def create_snat_rule(self, **attrs): """Create a new SNAT rule from attributes diff --git a/otcextensions/sdk/nat/v2/dnat.py b/otcextensions/sdk/nat/v2/dnat.py index 5c71f7e2c..4f1807189 100644 --- a/otcextensions/sdk/nat/v2/dnat.py +++ b/otcextensions/sdk/nat/v2/dnat.py @@ -1,69 +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. -from openstack import resource - - -class Dnat(resource.Resource): - resources_key = 'dnat_rules' - resource_key = 'dnat_rule' - base_path = '/dnat_rules' - - # capabilities - allow_create = True - allow_fetch = True - allow_delete = True - allow_list = True - - _query_mapping = resource.QueryParameters( - 'admin_state_up', 'cidr', 'created_at', 'external_service_port', - 'floating_ip_address', 'floating_ip_id', 'id', - 'internal_service_port', 'limit', 'nat_gateway_id', 'network_id', - 'port_id', 'private_id', 'protocol', 'source_type', 'status', - 'project_id' - ) - - # Properties - #: Specifies whether DNAT rule is enabled / disabled - #: *true:* DNAT rule is enabled - #: *false:* DNAT rule is disabled - admin_state_up = resource.Body('admin_state_up', type=bool) - #: Specifies when the rule is created. - #: The format is yyyy-mm-dd hh:mm:ss. - created_at = resource.Body('created_at') - #: Specifies the port for providing external services. - external_service_port = resource.Body('external_service_port', type=int) - #: Specifies the EIP - floating_ip_address = resource.Body('floating_ip_address') - #: Specifies the EIP ID - floating_ip_id = resource.Body('floating_ip_id') - #: Specifies the gateway ID. - gateway_id = resource.Body('gateway_id') - #: Specifies the ID of the DNAT rule. - id = resource.Body('id') - #: Specifies port used by ECS/BMS to provide services for external systems - internal_service_port = resource.Body('internal_service_port', type=int) - #: Specifies the ID of the NAT gateway. - nat_gateway_id = resource.Body('nat_gateway_id') - #: Specifies the port ID of an ECS or BMS - #: Parameter is used in the VPC scenario. - #: This parameter is an alternative to private_ip - port_id = resource.Body('port_id') - #: Specifies the IP address of a Direct Connect connection. - #: Parameter is used in the Direct Connect scenario. - #: This parameter is an alternative to port_id. - private_ip = resource.Body('private_ip') - #: Specifies the project ID. - project_id = resource.Body('tenant_id') - #: Specifies the protocol type. Currently TCP(6), UDP(17) and ANY(0) - protocol = resource.Body('protocol') - #: Specifies the status of the DNAT rule - status = resource.Body('status') +# 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 Dnat(resource.Resource): + resources_key = 'dnat_rules' + resource_key = 'dnat_rule' + base_path = '/dnat_rules' + + # capabilities + allow_create = True + allow_fetch = True + allow_delete = True + allow_list = True + + _query_mapping = resource.QueryParameters( + 'admin_state_up', 'created_at', 'external_service_port', 'id', + 'floating_ip_address', 'floating_ip_id', 'internal_service_port', + 'limit', 'nat_gateway_id', 'port_id', 'private_ip', 'protocol', + 'status', 'project_id', project_id='tenant_id' + ) + + # Properties + #: Specifies whether DNAT rule is enabled / disabled + #: *true:* DNAT rule is enabled + #: *false:* DNAT rule is disabled + admin_state_up = resource.Body('admin_state_up', type=bool) + #: Specifies when the rule is created. + #: The format is yyyy-mm-dd hh:mm:ss. + created_at = resource.Body('created_at') + #: Specifies the port for providing external services. + external_service_port = resource.Body('external_service_port', type=int) + #: Specifies the EIP + floating_ip_address = resource.Body('floating_ip_address') + #: Specifies the EIP ID + floating_ip_id = resource.Body('floating_ip_id') + #: Specifies the gateway ID. + gateway_id = resource.Body('gateway_id') + #: Specifies the ID of the DNAT rule. + id = resource.Body('id') + #: Specifies port used by ECS/BMS to provide services for external systems + internal_service_port = resource.Body('internal_service_port', type=int) + #: Specifies the ID of the NAT gateway. + nat_gateway_id = resource.Body('nat_gateway_id') + #: Specifies the port ID of an ECS or BMS + #: Parameter is used in the VPC scenario. + #: This parameter is an alternative to private_ip + port_id = resource.Body('port_id') + #: Specifies the IP address of a Direct Connect connection. + #: Parameter is used in the Direct Connect scenario. + #: This parameter is an alternative to port_id. + private_ip = resource.Body('private_ip') + #: Specifies the project ID. + project_id = resource.Body('tenant_id') + #: Specifies the protocol type. Currently TCP(6), UDP(17) and ANY(0) + protocol = resource.Body('protocol') + #: Specifies the status of the DNAT rule + status = resource.Body('status') diff --git a/otcextensions/sdk/nat/v2/gateway.py b/otcextensions/sdk/nat/v2/gateway.py index bcacd7f32..b93901ebb 100644 --- a/otcextensions/sdk/nat/v2/gateway.py +++ b/otcextensions/sdk/nat/v2/gateway.py @@ -27,7 +27,7 @@ class Gateway(resource.Resource): _query_mapping = resource.QueryParameters( 'admin_state_up', 'created_at', 'description', 'id', 'internal_network_id', 'limit', 'name', 'router_id', - 'spec', 'status', 'project_id' + 'spec', 'status', 'project_id', project_id='tenant_id' ) # Properties diff --git a/otcextensions/sdk/nat/v2/snat.py b/otcextensions/sdk/nat/v2/snat.py index 57c75685f..ad5dc0cb1 100644 --- a/otcextensions/sdk/nat/v2/snat.py +++ b/otcextensions/sdk/nat/v2/snat.py @@ -1,62 +1,62 @@ -# 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 Snat(resource.Resource): - resources_key = 'snat_rules' - resource_key = 'snat_rule' - base_path = '/snat_rules' - - # capabilities - allow_create = True - allow_fetch = True - allow_delete = True - allow_list = True - - _query_mapping = resource.QueryParameters( - 'admin_state_up', 'cidr', 'created_at', 'floating_ip_address', - 'floating_ip_id', 'id', 'limit', 'nat_gateway_id', 'network_id', - 'source_type', 'status', 'project_id' - ) - - # Properties - #: Specifies the status of the SNAT rule - admin_state_up = resource.Body('admin_state_up', type=bool) - #: Specifies a subset of the VPC subnet CIDR block or a - #: CIDR block of Direct Connect connection. - cidr = resource.Body('cidr') - #: Specifies when the rule is created. - #: The format is yyyy-mm-dd hh:mm:ss. - created_at = resource.Body('created_at') - #: Specifies the EIP - #: Multiple EIPs are separated using commas - floating_ip_address = resource.Body('floating_ip_address') - #: Specifies the EIP ID - #: Multiple EIPs are separated using commas - floating_ip_id = resource.Body('floating_ip_id') - #: Specifies the ID of the SNAT rule. - id = resource.Body('id') - #: Specifies the gateway ID. - nat_gateway_id = resource.Body('nat_gateway_id') - #: Specifies the network ID - network_id = resource.Body('network_id') - #: Specifies the project ID. - project_id = resource.Body('tenant_id') - #: *0:* Either network_id or cidr can be specified in VPC - #: *1:* only cidr can be specified over a Direct Connect connection - #: Default: 0 - source_type = resource.Body('source_type', type=int) - #: Specifies whether SNAT rule is enabled / disabled - #: *true:* SNAT rule is enabled - #: *false:* SNAT rule is disabled - status = resource.Body('status') +# 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 Snat(resource.Resource): + resources_key = 'snat_rules' + resource_key = 'snat_rule' + base_path = '/snat_rules' + + # capabilities + allow_create = True + allow_fetch = True + allow_delete = True + allow_list = True + + _query_mapping = resource.QueryParameters( + 'admin_state_up', 'cidr', 'created_at', 'floating_ip_address', + 'floating_ip_id', 'id', 'limit', 'nat_gateway_id', 'network_id', + 'source_type', 'status', 'project_id', project_id='tenant_id' + ) + + # Properties + #: Specifies the status of the SNAT rule + admin_state_up = resource.Body('admin_state_up', type=bool) + #: Specifies a subset of the VPC subnet CIDR block or a + #: CIDR block of Direct Connect connection. + cidr = resource.Body('cidr') + #: Specifies when the rule is created. + #: The format is yyyy-mm-dd hh:mm:ss. + created_at = resource.Body('created_at') + #: Specifies the EIP + #: Multiple EIPs are separated using commas + floating_ip_address = resource.Body('floating_ip_address') + #: Specifies the EIP ID + #: Multiple EIPs are separated using commas + floating_ip_id = resource.Body('floating_ip_id') + #: Specifies the ID of the SNAT rule. + id = resource.Body('id') + #: Specifies the gateway ID. + nat_gateway_id = resource.Body('nat_gateway_id') + #: Specifies the network ID + network_id = resource.Body('network_id') + #: Specifies the project ID. + project_id = resource.Body('tenant_id') + #: *0:* Either network_id or cidr can be specified in VPC + #: *1:* only cidr can be specified over a Direct Connect connection + #: Default: 0 + source_type = resource.Body('source_type', type=int) + #: Specifies whether SNAT rule is enabled / disabled + #: *true:* SNAT rule is enabled + #: *false:* SNAT rule is disabled + status = resource.Body('status') diff --git a/otcextensions/tests/functional/osclient/nat/__init__.py b/otcextensions/tests/functional/osclient/nat/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/functional/osclient/nat/v2/__init__.py b/otcextensions/tests/functional/osclient/nat/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/functional/osclient/nat/v2/common.py b/otcextensions/tests/functional/osclient/nat/v2/common.py new file mode 100644 index 000000000..8a943a617 --- /dev/null +++ b/otcextensions/tests/functional/osclient/nat/v2/common.py @@ -0,0 +1,165 @@ +# 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 NatTestCase(base.TestCase): + """Common functional test bits for NAT commands""" + + CURR_TIME = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") + + def setUp(self): + super(NatTestCase, self).setUp() + UUID = uuid.uuid4().hex[:8] + self.ROUTER_NAME = 'otce-nat-test-router-' + UUID + self.NETWORK_NAME = 'otce-nat-test-net-' + UUID + self.SUBNET_NAME = 'otce-nat-test-subnet-' + UUID + self.NAT_NAME = 'otce-nat-test-' + UUID + + self.ROUTER_ID = None + self.NETWORK_ID = None + self.FLOATING_IP_ID = None + self.NAT_ID = None + self.SNAT_RULE_ID = None + self.DNAT_RULE_ID = None + + def create_nat_gateway(self, name=None): + self._initialize_network() + name = name or self.NAT_NAME + json_output = json.loads(self.openstack( + 'nat gateway create {name}' + ' --router-id {router_id}' + ' --internal-network-id {net_id}' + ' --spec {spec} -f json'.format( + name=name, + router_id=self.ROUTER_ID, + net_id=self.NETWORK_ID, + description='OTCE Lib Test', + spec=1) + )) + self.assertIsNotNone(json_output) + self.NAT_ID = json_output['id'] + return json_output + + def delete_nat_gateway(self): + self.addCleanup(self._denitialize_network) + self.openstack('nat gateway delete ' + self.NAT_ID) + + def create_snat_rule(self): + nat_gateway = self.create_nat_gateway() + self.assertIsNotNone(nat_gateway) + self.assertIsNotNone(self.FLOATING_IP_ID) + json_output = json.loads(self.openstack( + 'nat snat rule create ' + '--nat-gateway-id {nat_gateway_id} ' + '--floating-ip-id {floating_ip_id} ' + '--network-id {net_id} -f json'.format( + nat_gateway_id=nat_gateway['id'], + floating_ip_id=self.FLOATING_IP_ID, + net_id=self.NETWORK_ID) + )) + self.assertIsNotNone(json_output) + self.SNAT_RULE_ID = json_output['id'] + return json_output + + def delete_snat_rule(self): + self.addCleanup(self.delete_nat_gateway) + self.openstack( + 'nat snat rule delete ' + self.SNAT_RULE_ID) + + def create_dnat_rule(self): + nat_gateway = self.create_nat_gateway() + self.assertIsNotNone(nat_gateway) + self.assertIsNotNone(self.FLOATING_IP_ID) + json_output = json.loads(self.openstack( + 'nat dnat rule create ' + '--nat-gateway-id {nat_gateway_id} ' + '--floating-ip-id {floating_ip_id} ' + '--protocol {protocol} ' + '--internal-service-port 80 ' + '--external-service-port 80 ' + '--private-ip {private_ip} ' + '-f json'.format( + nat_gateway_id=nat_gateway['id'], + protocol='TCP', + private_ip='192.168.0.3', + floating_ip_id=self.FLOATING_IP_ID) + )) + self.assertIsNotNone(json_output) + self.DNAT_RULE_ID = json_output['id'] + return json_output + + def delete_dnat_rule(self): + self.addCleanup(self.delete_nat_gateway) + self.openstack( + 'nat dnat rule delete ' + self.DNAT_RULE_ID) + + def _initialize_network(self): + router = json.loads(self.openstack( + 'router create -f json ' + self.ROUTER_NAME + )) + net = json.loads(self.openstack( + 'network create -f json ' + self.NETWORK_NAME + )) + self.openstack( + 'subnet create {subnet} -f json ' + '--network {net} ' + '--subnet-range 192.168.0.0/24 '.format( + subnet=self.SUBNET_NAME, + net=self.NETWORK_NAME + )) + + self.openstack( + 'router add subnet {router} ' + '{subnet} '.format( + router=self.ROUTER_NAME, + subnet=self.SUBNET_NAME + ) + ) + + floating_ip = json.loads(self.openstack( + 'floating ip create -f json ' + '{network}'.format( + network='admin_external_net') + )) + + self.ROUTER_ID = router['id'] + self.NETWORK_ID = net['id'] + self.FLOATING_IP_ID = floating_ip['id'] + + def _denitialize_network(self): + self.openstack( + 'floating ip delete ' + self.FLOATING_IP_ID + ) + self.openstack( + 'router remove subnet {router} ' + '{subnet} '.format( + router=self.ROUTER_NAME, + subnet=self.SUBNET_NAME + ) + ) + self.openstack( + 'subnet delete ' + self.SUBNET_NAME + ) + self.openstack( + 'network delete ' + self.NETWORK_NAME + ) + self.openstack( + 'router delete ' + self.ROUTER_NAME + ) diff --git a/otcextensions/tests/functional/osclient/nat/v2/test_dnat.py b/otcextensions/tests/functional/osclient/nat/v2/test_dnat.py new file mode 100644 index 000000000..0450c9120 --- /dev/null +++ b/otcextensions/tests/functional/osclient/nat/v2/test_dnat.py @@ -0,0 +1,80 @@ +# 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.nat.v2 import common + + +class TestDnat(common.NatTestCase): + """Functional Tests for NAT Gateway""" + + def setUp(self): + super(TestDnat, self).setUp() + + def test_nat_dnat_rule_list(self): + json_output = json.loads(self.openstack( + 'nat dnat rule list -f json ' + )) + self.assertIsNotNone(json_output) + + def test_nat_dnat_rule_list_filters(self): + json_output = json.loads(self.openstack( + 'nat dnat rule list -f json ' + '--limit 1 --id 2 ' + '--project-id 3 ' + '--port-id 4 ' + '--private-ip 5 ' + '--internal-service-port 6 ' + '--floating-ip-id 7 ' + '--floating-ip-address 8 ' + '--external-service-port 9 ' + '--status 10 ' + '--nat-gateway-id 11 ' + '--protocol tcp ' + '--admin-state-up true ' + '--created-at "{}"'.format(self.CURR_TIME) + )) + self.assertIsNotNone(json_output) + + def test_nat_dnat_rule_create(self): + json_output = self.create_dnat_rule() + self.addCleanup(self.delete_dnat_rule) + dnat_rule_id = json_output['id'] + nat_id = json_output['nat_gateway_id'] + + # List Dnat Rules by Id filter + json_output = json.loads(self.openstack( + 'nat dnat rule list -f json ' + '--id ' + dnat_rule_id + )) + self.assertIsNotNone(json_output) + self.assertEqual(next(iter(json_output))['Id'], dnat_rule_id) + self.assertEqual( + next(iter(json_output))['Nat Gateway Id'], nat_id) + + # List Dnat Rules by Nat Id filter + json_output = json.loads(self.openstack( + 'nat dnat rule list -f json ' + '--nat-gateway-id ' + nat_id + )) + self.assertIsNotNone(json_output) + self.assertEqual( + next(iter(json_output))['Nat Gateway Id'], nat_id) + + # Show Dnat Rule details + json_output = json.loads(self.openstack( + 'nat dnat rule show ' + ' -f json ' + dnat_rule_id + )) + self.assertIsNotNone(json_output) + self.assertEqual(json_output['id'], dnat_rule_id) diff --git a/otcextensions/tests/functional/osclient/nat/v2/test_gateway.py b/otcextensions/tests/functional/osclient/nat/v2/test_gateway.py new file mode 100644 index 000000000..aef96de1d --- /dev/null +++ b/otcextensions/tests/functional/osclient/nat/v2/test_gateway.py @@ -0,0 +1,103 @@ +# 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.nat.v2 import common + + +class TestGateway(common.NatTestCase): + """Functional Tests for NAT Gateway""" + + def setUp(self): + super(TestGateway, self).setUp() + + def test_nat_gateway_list(self): + json_output = json.loads(self.openstack( + 'nat gateway list -f json ' + )) + self.assertIsNotNone(json_output) + + def test_nat_gateway_list_filters(self): + json_output = json.loads(self.openstack( + 'nat gateway list -f json ' + '--limit 1 --id 2 ' + '--name 3 --spec 4 ' + '--router-id 5 ' + '--internal-network-id 6 ' + '--project-id 7 ' + '--status active ' + '--admin-state-up True ' + '--created-at "{}"'.format(self.CURR_TIME) + )) + self.assertIsNotNone(json_output) + + def test_nat_gateway(self): + nat_gateway = self.create_nat_gateway() + nat_id = nat_gateway['id'] + nat_name = nat_gateway['name'] + router_id = nat_gateway['router_id'] + + self.addCleanup(self.delete_nat_gateway) + + # List Nat Gateway By Id + json_output = json.loads(self.openstack( + 'nat gateway list -f json' + ' --id {}'.format(nat_id) + )) + self.assertEqual(json_output[0]['Name'], nat_name) + self.assertEqual(json_output[0]['Id'], nat_id) + + # List Nat Gateway By Name + json_output = json.loads(self.openstack( + 'nat gateway list -f json' + ' --name {}'.format(nat_name) + )) + self.assertEqual(json_output[0]['Name'], nat_name) + self.assertEqual(json_output[0]['Id'], nat_id) + + # List Nat Gateway by Router ID + json_output = json.loads(self.openstack( + 'nat gateway list -f json' + ' --router-id {}'.format(router_id) + )) + for nat_gw in json_output: + self.assertEqual(nat_gw['Router Id'], router_id) + + # Show Nat Gateway by Name + json_output = json.loads(self.openstack( + 'nat gateway show -f json ' + nat_name + )) + self.assertEqual(json_output['name'], nat_name) + self.assertEqual(json_output['id'], nat_id) + + # Show Nat Gateway by Id + json_output = json.loads(self.openstack( + 'nat gateway show -f json ' + nat_id + )) + self.assertEqual(json_output['name'], nat_name) + self.assertEqual(json_output['id'], nat_id) + + # Update Nat Gateway + nat_name = nat_name + "-updated" + description = "otce cli test nat updated" + json_output = json.loads(self.openstack( + 'nat gateway update {nat_id} ' + '--name {name} ' + '--description "{desc}" ' + '-f json'.format( + nat_id=nat_id, + name=nat_name, + desc=description) + )) + self.assertEqual(json_output['name'], nat_name) + self.assertEqual(json_output['description'], description) diff --git a/otcextensions/tests/functional/osclient/nat/v2/test_snat.py b/otcextensions/tests/functional/osclient/nat/v2/test_snat.py new file mode 100644 index 000000000..07d8a5f97 --- /dev/null +++ b/otcextensions/tests/functional/osclient/nat/v2/test_snat.py @@ -0,0 +1,79 @@ +# 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.nat.v2 import common + + +class TestSnat(common.NatTestCase): + """Functional Tests for NAT Gateway""" + + def setUp(self): + super(TestSnat, self).setUp() + + def test_snat_rule_list(self): + json_output = json.loads(self.openstack( + 'nat snat rule list -f json ' + )) + self.assertIsNotNone(json_output) + + def test_snat_rule_list_filters(self): + json_output = json.loads(self.openstack( + 'nat snat rule list -f json ' + '--limit 1 --id 2 ' + '--project-id 3 ' + '--nat-gateway-id 4 ' + '--network-id 5 ' + '--cidr 6 ' + '--source-type 7 ' + '--floating-ip-id 8 ' + '--floating-ip-address 9 ' + '--status 10 ' + '--admin-state-up true ' + '--created-at "{}"'.format(self.CURR_TIME) + )) + self.assertIsNotNone(json_output) + + def test_nat_snat_rule(self): + json_output = self.create_snat_rule() + self.addCleanup(self.delete_snat_rule) + snat_rule_id = json_output['id'] + nat_id = json_output['nat_gateway_id'] + + # List Snat Rule by Snat Id filter + json_output = json.loads(self.openstack( + 'nat snat rule list -f json ' + '--id ' + snat_rule_id + )) + self.assertIsNotNone(json_output) + self.assertEqual(next(iter(json_output))['Id'], snat_rule_id) + self.assertEqual( + next(iter(json_output))['Nat Gateway Id'], nat_id) + + # List Snat Rule by nat-gateway-id filter + json_output = json.loads(self.openstack( + 'nat snat rule list -f json ' + '--nat-gateway-id ' + nat_id + )) + self.assertIsNotNone(json_output) + self.assertEqual( + next(iter(json_output))['Nat Gateway Id'], nat_id) + + # Show Snat Rule by Id + self.assertIsNotNone(self.SNAT_RULE_ID) + json_output = json.loads(self.openstack( + 'nat snat rule show ' + ' -f json ' + self.SNAT_RULE_ID + )) + self.assertIsNotNone(json_output) + self.assertEqual(json_output['id'], self.SNAT_RULE_ID) diff --git a/otcextensions/tests/functional/sdk/nat/__init__.py b/otcextensions/tests/functional/sdk/nat/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/functional/sdk/nat/v2/__init__.py b/otcextensions/tests/functional/sdk/nat/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/functional/sdk/nat/v2/test_dnat.py b/otcextensions/tests/functional/sdk/nat/v2/test_dnat.py new file mode 100644 index 000000000..57eab032c --- /dev/null +++ b/otcextensions/tests/functional/sdk/nat/v2/test_dnat.py @@ -0,0 +1,48 @@ +# 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 openstack +from otcextensions.tests.functional import base +from datetime import datetime + +_logger = openstack._log.setup_logging('openstack') + + +class TestDnat(base.BaseFunctionalTest): + CURR_TIME = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") + + def setUp(self): + super(TestDnat, self).setUp() + openstack.enable_logging(debug=True, http_debug=True) + + def test_list(self): + gateways = list(self.conn.nat.dnat_rules()) + self.assertGreaterEqual(len(gateways), 0) + + def test_list_filters(self): + attrs = { + 'limit': 1, + 'id': '2', + 'project_id': '3', + 'port_id': '4', + 'private_ip': '5', + 'internal_service_port': '6', + 'floating_ip_id': '7', + 'floating_ip_address': '8', + 'external_service_port': '9', + 'nat_gateway_id': '11', + 'protocol': 'tcp', + 'status': 'active', + 'admin_state_up': True, + 'created_at': self.CURR_TIME + } + gateways = list(self.conn.nat.dnat_rules(**attrs)) + self.assertGreaterEqual(len(gateways), 0) diff --git a/otcextensions/tests/functional/sdk/nat/v2/test_gateway.py b/otcextensions/tests/functional/sdk/nat/v2/test_gateway.py new file mode 100644 index 000000000..25583aeda --- /dev/null +++ b/otcextensions/tests/functional/sdk/nat/v2/test_gateway.py @@ -0,0 +1,44 @@ +# 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 openstack +from otcextensions.tests.functional import base +from datetime import datetime + +_logger = openstack._log.setup_logging('openstack') + + +class TestGateway(base.BaseFunctionalTest): + CURR_TIME = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") + + def setUp(self): + super(TestGateway, self).setUp() + openstack.enable_logging(debug=True, http_debug=True) + + def test_list(self): + gateways = list(self.conn.nat.gateways()) + self.assertGreaterEqual(len(gateways), 0) + + def test_list_filters(self): + attrs = { + 'limit': 1, + 'id': '2', + 'name': '3', + 'spec': '4', + 'router_id': '5', + 'internal_network_id': '6', + 'project_id': '7', + 'status': 'active', + 'admin_state_up': True, + 'created_at': self.CURR_TIME + } + gateways = list(self.conn.nat.gateways(**attrs)) + self.assertGreaterEqual(len(gateways), 0) diff --git a/otcextensions/tests/functional/sdk/nat/v2/test_service.py b/otcextensions/tests/functional/sdk/nat/v2/test_service.py new file mode 100644 index 000000000..3dd333c81 --- /dev/null +++ b/otcextensions/tests/functional/sdk/nat/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.nat + + self.assertIsNotNone(client) diff --git a/otcextensions/tests/functional/sdk/nat/v2/test_snat.py b/otcextensions/tests/functional/sdk/nat/v2/test_snat.py new file mode 100644 index 000000000..f7629a8bc --- /dev/null +++ b/otcextensions/tests/functional/sdk/nat/v2/test_snat.py @@ -0,0 +1,46 @@ +# 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 openstack +from otcextensions.tests.functional import base +from datetime import datetime + +_logger = openstack._log.setup_logging('openstack') + + +class TestSnat(base.BaseFunctionalTest): + CURR_TIME = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") + + def setUp(self): + super(TestSnat, self).setUp() + openstack.enable_logging(debug=True, http_debug=True) + + def test_list(self): + gateways = list(self.conn.nat.snat_rules()) + self.assertGreaterEqual(len(gateways), 0) + + def test_list_filters(self): + attrs = { + 'limit': 1, + 'id': '2', + 'project_id': '3', + 'nat_gateway_id': '4', + 'network_id': '5', + 'cidr': '6', + 'source_type': '7', + 'floating_ip_id': '8', + 'floating_ip_address': '9', + 'status': 'active', + 'admin_state_up': True, + 'created_at': self.CURR_TIME + } + gateways = list(self.conn.nat.snat_rules(**attrs)) + self.assertGreaterEqual(len(gateways), 0) diff --git a/otcextensions/tests/unit/osclient/nat/__init__.py b/otcextensions/tests/unit/osclient/nat/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/unit/osclient/nat/v2/__init__.py b/otcextensions/tests/unit/osclient/nat/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/unit/osclient/nat/v2/fakes.py b/otcextensions/tests/unit/osclient/nat/v2/fakes.py new file mode 100644 index 000000000..435ebc0e7 --- /dev/null +++ b/otcextensions/tests/unit/osclient/nat/v2/fakes.py @@ -0,0 +1,128 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +import uuid +from datetime import datetime + +import mock + +from openstackclient.tests.unit import utils + +from otcextensions.tests.unit.osclient import test_base + +from otcextensions.sdk.nat.v2 import gateway +from otcextensions.sdk.nat.v2 import snat +from otcextensions.sdk.nat.v2 import dnat + + +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 TestNat(utils.TestCommand): + def setUp(self): + super(TestNat, self).setUp() + + self.app.client_manager.nat = mock.Mock() + + self.client = self.app.client_manager.nat + + +class FakeNatGateway(test_base.Fake): + """Fake one or more Nat Gateways.""" + @classmethod + def generate(cls): + """Create a fake NAT Gateway. + + :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, + "router_id": "router-" + uuid.uuid4().hex, + "status": "PENDING_CREATE", + "description": "my nat gateway", + "admin_state_up": 'true', + "tenant_id": "tenant-id-" + uuid.uuid4().hex, + "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"), + "spec": "1", + "internal_network_id": "net-id-" + uuid.uuid4().hex + } + + return gateway.Gateway(**object_info) + + +class FakeSnatRule(test_base.Fake): + """Fake one or more SNAT Rule.""" + @classmethod + def generate(cls): + """Create a fake SNAT Rule. + + :return: + A FakeResource object, with id, status and so on + """ + # Set default attributes. + object_info = { + "id": "id-" + uuid.uuid4().hex, + "floating_ip_id": "eip-id-" + uuid.uuid4().hex, + "status": "PENDING_CREATE", + "nat_gateway_id": "gw-id-" + uuid.uuid4().hex, + "admin_state_up": True, + "network_id": "net-id-" + uuid.uuid4().hex, + "cidr": uuid.uuid4().hex, + "source_type": 0, + "tenant_id": "tenant-id-" + uuid.uuid4().hex, + "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"), + "floating_ip_address": uuid.uuid4().hex + } + + return snat.Snat.existing(**object_info) + + +class FakeDnatRule(test_base.Fake): + """Fake one or more DNAT Rule""" + @classmethod + def generate(cls): + """Create a fake DNAT Rule. + + :return: + A FakeResource object, with id, status and so on + """ + # Set default attributes. + object_info = { + "id": "id-" + uuid.uuid4().hex, + "floating_ip_id": "eip-id-" + uuid.uuid4().hex, + "status": "ACTIVE", + "nat_gateway_id": "gw-id-" + uuid.uuid4().hex, + "admin_state_up": True, + "private_ip": uuid.uuid4().hex, + "internal_service_port": 0, + "protocol": "any", + "tenant_id": "abc", + "port_id": "", + "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"), + "floating_ip_address": uuid.uuid4().hex, + "external_service_port": 0 + } + + obj = dnat.Dnat.existing(**object_info) + return obj diff --git a/otcextensions/tests/unit/osclient/nat/v2/test_dnat.py b/otcextensions/tests/unit/osclient/nat/v2/test_dnat.py new file mode 100644 index 000000000..393eb69cd --- /dev/null +++ b/otcextensions/tests/unit/osclient/nat/v2/test_dnat.py @@ -0,0 +1,375 @@ +# 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.nat.v2 import dnat +from otcextensions.tests.unit.osclient.nat.v2 import fakes + +from openstackclient.tests.unit import utils as tests_utils + + +class TestListDnatRules(fakes.TestNat): + + objects = fakes.FakeDnatRule.create_multiple(3) + + column_list_headers = ( + 'Id', + 'Nat Gateway Id', + 'Port Id', + 'Private IP', + 'Floating Ip Address', + 'Protocol', + 'Status' + ) + columns = ( + 'id', + 'nat_gateway_id', + 'port_id', + 'private_ip', + 'floating_ip_address', + 'protocol', + 'status' + ) + + data = [] + + for s in objects: + data.append(( + s.id, + s.nat_gateway_id, + s.port_id, + s.private_ip, + s.floating_ip_address, + s.protocol, + s.status + )) + + def setUp(self): + super(TestListDnatRules, self).setUp() + + self.cmd = dnat.ListDnatRules(self.app, None) + + self.client.dnat_rules = mock.Mock() + self.client.api_mock = self.client.dnat_rules + + 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', + '--id', '2', + '--nat-gateway-id', '3', + '--project-id', '4', + '--private-ip', '5', + '--internal-service-port', '6', + '--protocol', '7', + '--floating-ip-id', '8', + '--floating-ip-address', '9', + '--admin-state-up', '10', + '--created-at', '11', + '--status', '12' + ] + + verifylist = [ + ('limit', 1), + ('id', '2'), + ('nat_gateway_id', '3'), + ('project_id', '4'), + ('private_ip', '5'), + ('internal_service_port', '6'), + ('protocol', '7'), + ('floating_ip_id', '8'), + ('floating_ip_address', '9'), + ('admin_state_up', '10'), + ('created_at', '11'), + ('status', '12'), + ] + + # 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, + id='2', + nat_gateway_id='3', + project_id='4', + private_ip='5', + internal_service_port='6', + protocol='7', + floating_ip_id='8', + floating_ip_address='9', + admin_state_up='10', + created_at='11', + status='12', + ) + + +class TestShowDnatRule(fakes.TestNat): + + _data = fakes.FakeDnatRule.create_one() + + columns = ( + 'admin_state_up', + 'created_at', + 'external_service_port', + 'floating_ip_address', + 'floating_ip_id', + 'id', + 'internal_service_port', + 'nat_gateway_id', + 'port_id', + 'private_ip', + 'project_id', + 'protocol', + 'status' + ) + + data = fakes.gen_data(_data, columns) + + def setUp(self): + super(TestShowDnatRule, self).setUp() + + self.cmd = dnat.ShowDnatRule(self.app, None) + + self.client.get_dnat_rule = 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 = [ + ('dnat', 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_dnat_rule.assert_called_with(self._data.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_show_non_existent(self): + arglist = [ + 'unexist_dnat_rule_id', + ] + + verifylist = [ + ('dnat', arglist[0]), + ] + + # 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_dnat_rule = ( + 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_dnat_rule.assert_called_with(arglist[0]) + + +class TestCreateDnatRule(fakes.TestNat): + + _data = fakes.FakeDnatRule.create_one() + + columns = ( + 'admin_state_up', + 'created_at', + 'external_service_port', + 'floating_ip_address', + 'floating_ip_id', + 'id', + 'internal_service_port', + 'nat_gateway_id', + 'port_id', + 'private_ip', + 'project_id', + 'protocol', + 'status' + ) + + data = fakes.gen_data(_data, columns) + + def setUp(self): + super(TestCreateDnatRule, self).setUp() + + self.cmd = dnat.CreateDnatRule(self.app, None) + + self.client.create_dnat_rule = mock.Mock(return_value=self._data) + + def test_create(self): + arglist = [ + '--nat-gateway-id', 'test-nat-uuid', + '--floating-ip-id', 'test-floating-ip-uuid', + '--protocol', 'tcp', + '--internal-service-port', '80', + '--external-service-port', '80', + '--private-ip', '192.168.0.99', + ] + + verifylist = [ + ('nat_gateway_id', 'test-nat-uuid'), + ('floating_ip_id', 'test-floating-ip-uuid'), + ('protocol', 'tcp'), + ('internal_service_port', '80'), + ('external_service_port', '80'), + ('private_ip', '192.168.0.99'), + ] + + # 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.create_dnat_rule.assert_called_with( + nat_gateway_id='test-nat-uuid', + floating_ip_id='test-floating-ip-uuid', + protocol='tcp', + internal_service_port='80', + external_service_port='80', + private_ip='192.168.0.99') + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestDeleteDnatRule(fakes.TestNat): + + _data = fakes.FakeDnatRule.create_multiple(2) + + def setUp(self): + super(TestDeleteDnatRule, self).setUp() + + self.client.delete_dnat_rule = mock.Mock(return_value=None) + + self.cmd = dnat.DeleteDnatRule(self.app, None) + + def test_delete(self): + arglist = [ + self._data[0].id, + ] + + verifylist = [ + ('dnat', arglist), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.client.get_dnat_rule = ( + mock.Mock(return_value=self._data[0]) + ) + + # Trigger the action + result = self.cmd.take_action(parsed_args) + self.client.get_dnat_rule.assert_called_with(self._data[0].id) + self.client.delete_dnat_rule.assert_called_with(self._data[0].id) + self.assertIsNone(result) + + def test_multiple_delete(self): + arglist = [] + + for dnat_rule in self._data: + arglist.append(dnat_rule.id) + + verifylist = [ + ('dnat', arglist), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = self._data + self.client.get_dnat_rule = ( + mock.Mock(side_effect=find_mock_result) + ) + + # Trigger the action + result = self.cmd.take_action(parsed_args) + + calls = [] + for dnat_rule in self._data: + calls.append(call(dnat_rule.id)) + self.client.delete_dnat_rule.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multiple_delete_with_exception(self): + arglist = [ + self._data[0].id, + 'unexist_dnat_rule_id', + ] + verifylist = [ + ('dnat', 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.get_dnat_rule = ( + 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 DNAT Rule(s) failed to delete.', str(e)) + + self.client.get_dnat_rule.assert_any_call(arglist[0]) + self.client.get_dnat_rule.assert_any_call(arglist[1]) + self.client.delete_dnat_rule.assert_called_once_with(arglist[0]) diff --git a/otcextensions/tests/unit/osclient/nat/v2/test_gateway.py b/otcextensions/tests/unit/osclient/nat/v2/test_gateway.py new file mode 100644 index 000000000..ec5435e47 --- /dev/null +++ b/otcextensions/tests/unit/osclient/nat/v2/test_gateway.py @@ -0,0 +1,389 @@ +# 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.nat.v2 import gateway +from otcextensions.tests.unit.osclient.nat.v2 import fakes + +from openstackclient.tests.unit import utils as tests_utils + + +class TestListNatGateways(fakes.TestNat): + + objects = fakes.FakeNatGateway.create_multiple(3) + + column_list_headers = ('Id', 'Name', 'Spec', 'Router Id', 'Status') + + columns = ('id', 'name', 'spec', 'router_id', 'status') + + data = [] + + for s in objects: + data.append( + (s.id, s.name, s.spec, s.router_id, s.status)) + + def setUp(self): + super(TestListNatGateways, self).setUp() + + self.cmd = gateway.ListNatGateways(self.app, None) + + self.client.gateways = mock.Mock() + self.client.api_mock = self.client.gateways + + 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', + '--id', '2', + '--name', '3', + '--project-id', '4', + '--spec', '5', + '--router-id', '6', + '--internal-network-id', '7', + '--admin-state-up', '8', + '--created-at', '9', + '--status', '10' + ] + + verifylist = [ + ('limit', 1), + ('id', '2'), + ('name', '3'), + ('project_id', '4'), + ('spec', '5'), + ('router_id', '6'), + ('internal_network_id', '7'), + ('admin_state_up', '8'), + ('created_at', '9'), + ('status', '10'), + ] + + # 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, + id='2', + name='3', + project_id='4', + spec='5', + router_id='6', + internal_network_id='7', + admin_state_up='8', + created_at='9', + status='10', + ) + + +class TestCreateNatGateway(fakes.TestNat): + + _data = fakes.FakeNatGateway.create_one() + + columns = ( + 'admin_state_up', + 'created_at', + 'description', + 'id', + 'internal_network_id', + 'name', + 'project_id', + 'router_id', + 'spec', + 'status' + ) + + data = fakes.gen_data(_data, columns) + + def setUp(self): + super(TestCreateNatGateway, self).setUp() + + self.cmd = gateway.CreateNatGateway(self.app, None) + + self.client.create_gateway = mock.Mock(return_value=self._data) + + def test_create(self): + arglist = [ + 'test-gateway', + '--router-id', 'test-router-uuid', + '--internal-network-id', 'test-network-uuid', + '--spec', '1', + ] + verifylist = [ + ('name', 'test-gateway'), + ('router_id', 'test-router-uuid'), + ('internal_network_id', 'test-network-uuid'), + ('spec', '1'), + ] + # 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.create_gateway.assert_called_with( + name='test-gateway', + router_id='test-router-uuid', + internal_network_id='test-network-uuid', + spec='1' + ) + self.assertEqual(self.columns, columns) + + +class TestUpdateNatGateway(fakes.TestNat): + + _data = fakes.FakeNatGateway.create_one() + + columns = ( + 'admin_state_up', + 'created_at', + 'description', + 'id', + 'internal_network_id', + 'name', + 'project_id', + 'router_id', + 'spec', + 'status' + ) + + data = fakes.gen_data(_data, columns) + + def setUp(self): + super(TestUpdateNatGateway, self).setUp() + + self.cmd = gateway.UpdateNatGateway(self.app, None) + + self.client.find_gateway = mock.Mock(return_value=self._data) + self.client.update_gateway = mock.Mock(return_value=self._data) + + def test_update(self): + arglist = [ + self._data.name, + '--name', 'test-gateway-updated', + '--description', 'nat gateway updated', + '--spec', '2', + ] + verifylist = [ + ('gateway', self._data.name), + ('name', 'test-gateway-updated'), + ('description', 'nat gateway updated'), + ('spec', '2'), + ] + # 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_gateway.assert_called_with(self._data.name) + self.client.update_gateway.assert_called_with( + self._data.id, + name='test-gateway-updated', + description='nat gateway updated', + spec='2' + ) + self.assertEqual(self.columns, columns) + + +class TestShowNatGateway(fakes.TestNat): + + _data = fakes.FakeNatGateway.create_one() + + columns = ( + 'admin_state_up', + 'created_at', + 'description', + 'id', + 'internal_network_id', + 'name', + 'project_id', + 'router_id', + 'spec', + 'status' + ) + + data = fakes.gen_data(_data, columns) + + def setUp(self): + super(TestShowNatGateway, self).setUp() + + self.cmd = gateway.ShowNatGateway(self.app, None) + + self.client.find_gateway = 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 = [ + ('gateway', 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_gateway.assert_called_with(self._data.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_show_non_existent(self): + arglist = [ + 'unexist_nat_gateway', + ] + + verifylist = [ + ('gateway', 'unexist_nat_gateway'), + ] + + # 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_gateway = ( + 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_gateway.assert_called_with('unexist_nat_gateway') + + +class TestDeleteNatGateway(fakes.TestNat): + + _data = fakes.FakeNatGateway.create_multiple(2) + + def setUp(self): + super(TestDeleteNatGateway, self).setUp() + + self.client.delete_gateway = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = gateway.DeleteNatGateway(self.app, None) + + def test_delete(self): + arglist = [ + self._data[0].name, + ] + + verifylist = [ + ('gateway', [self._data[0].name]), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.client.find_gateway = ( + mock.Mock(return_value=self._data[0]) + ) + + # Trigger the action + result = self.cmd.take_action(parsed_args) + self.client.delete_gateway.assert_called_with(self._data[0].id) + self.assertIsNone(result) + + def test_multiple_delete(self): + arglist = [] + + for nat_gw in self._data: + arglist.append(nat_gw.name) + + verifylist = [ + ('gateway', 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_gateway = ( + mock.Mock(side_effect=find_mock_result) + ) + + # Trigger the action + result = self.cmd.take_action(parsed_args) + + calls = [] + for nat_gw in self._data: + calls.append(call(nat_gw.id)) + self.client.delete_gateway.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multiple_delete_with_exception(self): + arglist = [ + self._data[0].name, + 'unexist_nat_gateway', + ] + verifylist = [ + ('gateway', 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_gateway = ( + 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 NAT Gateway(s) failed to delete.', str(e)) + + self.client.find_gateway.assert_any_call(self._data[0].name) + self.client.find_gateway.assert_any_call('unexist_nat_gateway') + self.client.delete_gateway.assert_called_once_with(self._data[0].id) diff --git a/otcextensions/tests/unit/osclient/nat/v2/test_snat.py b/otcextensions/tests/unit/osclient/nat/v2/test_snat.py new file mode 100644 index 000000000..0a046eaa3 --- /dev/null +++ b/otcextensions/tests/unit/osclient/nat/v2/test_snat.py @@ -0,0 +1,359 @@ +# 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.nat.v2 import snat +from otcextensions.tests.unit.osclient.nat.v2 import fakes + +from openstackclient.tests.unit import utils as tests_utils + + +class TestListSnatRules(fakes.TestNat): + + objects = fakes.FakeSnatRule.create_multiple(3) + + column_list_headers = ( + 'Id', + 'Nat Gateway Id', + 'Network Id', + 'Cidr', + 'Floating Ip Address', + 'Status' + ) + columns = ( + 'id', + 'nat_gateway_id', + 'network_id', + 'cidr', + 'floating_ip_address', + 'status' + ) + + data = [] + + for s in objects: + data.append(( + s.id, + s.nat_gateway_id, + s.network_id, + s.cidr, + s.floating_ip_address, + s.status + )) + + def setUp(self): + super(TestListSnatRules, self).setUp() + + self.cmd = snat.ListSnatRules(self.app, None) + + self.client.snat_rules = mock.Mock() + self.client.api_mock = self.client.snat_rules + + 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', + '--id', '2', + '--nat-gateway-id', '3', + '--network-id', '4', + '--project-id', '5', + '--cidr', '6', + '--source-type', '7', + '--floating-ip-id', '8', + '--floating-ip-address', '9', + '--admin-state-up', '10', + '--created-at', '11', + '--status', '12' + ] + + verifylist = [ + ('limit', 1), + ('id', '2'), + ('nat_gateway_id', '3'), + ('network_id', '4'), + ('project_id', '5'), + ('cidr', '6'), + ('source_type', '7'), + ('floating_ip_id', '8'), + ('floating_ip_address', '9'), + ('admin_state_up', '10'), + ('created_at', '11'), + ('status', '12'), + ] + + # 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, + id='2', + nat_gateway_id='3', + network_id='4', + project_id='5', + cidr='6', + source_type='7', + floating_ip_id='8', + floating_ip_address='9', + admin_state_up='10', + created_at='11', + status='12', + ) + + +class TestCreateSnatRule(fakes.TestNat): + + _data = fakes.FakeSnatRule.create_one() + + columns = ( + 'admin_state_up', + 'cidr', + 'created_at', + 'floating_ip_address', + 'floating_ip_id', + 'id', + 'nat_gateway_id', + 'network_id', + 'project_id', + 'source_type', + 'status' + ) + + data = fakes.gen_data(_data, columns) + + def setUp(self): + super(TestCreateSnatRule, self).setUp() + + self.cmd = snat.CreateSnatRule(self.app, None) + + self.client.create_snat_rule = mock.Mock(return_value=self._data) + + def test_create(self): + arglist = [ + '--nat-gateway-id', 'test-nat-uuid', + '--floating-ip-id', 'test-floating-ip-uuid', + '--network-id', 'test-network-uuid', + ] + + verifylist = [ + ('nat_gateway_id', 'test-nat-uuid'), + ('floating_ip_id', 'test-floating-ip-uuid'), + ('network_id', 'test-network-uuid'), + ] + + # 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.create_snat_rule.assert_called_with( + nat_gateway_id='test-nat-uuid', + floating_ip_id='test-floating-ip-uuid', + network_id='test-network-uuid') + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestShowSnatRule(fakes.TestNat): + + _data = fakes.FakeSnatRule.create_one() + + columns = ( + 'admin_state_up', + 'cidr', + 'created_at', + 'floating_ip_address', + 'floating_ip_id', + 'id', + 'nat_gateway_id', + 'network_id', + 'project_id', + 'source_type', + 'status' + ) + + data = fakes.gen_data(_data, columns) + + def setUp(self): + super(TestShowSnatRule, self).setUp() + + self.cmd = snat.ShowSnatRule(self.app, None) + + self.client.get_snat_rule = 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 = [ + ('snat', 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_snat_rule.assert_called_with(self._data.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_show_non_existent(self): + arglist = [ + 'unexist_snat_rule_id', + ] + + verifylist = [ + ('snat', arglist[0]), + ] + + # 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_snat_rule = ( + 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_snat_rule.assert_called_with(arglist[0]) + + +class TestDeleteSnatRule(fakes.TestNat): + + _data = fakes.FakeSnatRule.create_multiple(2) + + def setUp(self): + super(TestDeleteSnatRule, self).setUp() + + self.client.delete_snat_rule = mock.Mock(return_value=None) + + self.cmd = snat.DeleteSnatRule(self.app, None) + + def test_delete(self): + arglist = [ + self._data[0].id, + ] + + verifylist = [ + ('snat', arglist), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.client.get_snat_rule = ( + mock.Mock(return_value=self._data[0]) + ) + + # Trigger the action + result = self.cmd.take_action(parsed_args) + self.client.get_snat_rule.assert_called_with(self._data[0].id) + self.client.delete_snat_rule.assert_called_with(self._data[0].id) + self.assertIsNone(result) + + def test_multiple_delete(self): + arglist = [] + + for snat_rule in self._data: + arglist.append(snat_rule.id) + + verifylist = [ + ('snat', arglist), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = self._data + self.client.get_snat_rule = ( + mock.Mock(side_effect=find_mock_result) + ) + + # Trigger the action + result = self.cmd.take_action(parsed_args) + + calls = [] + for snat_rule in self._data: + calls.append(call(snat_rule.id)) + self.client.delete_snat_rule.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multiple_delete_with_exception(self): + arglist = [ + self._data[0].id, + 'unexist_snat_rule_id', + ] + verifylist = [ + ('snat', 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.get_snat_rule = ( + 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 SNAT Rule(s) failed to delete.', str(e)) + + self.client.get_snat_rule.assert_any_call(arglist[0]) + self.client.get_snat_rule.assert_any_call(arglist[1]) + self.client.delete_snat_rule.assert_called_once_with(arglist[0]) diff --git a/otcextensions/tests/unit/sdk/nat/v2/__init__.py b/otcextensions/tests/unit/sdk/nat/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/setup.cfg b/setup.cfg index cc13ea867..6f69f6bdd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -40,6 +40,7 @@ openstack.cli.extension = anti_ddos = otcextensions.osclient.anti_ddos.client dns = otcextensions.osclient.dns.client deh = otcextensions.osclient.deh.client + nat = otcextensions.osclient.nat.client #openstack.obs.v1 = # s3_ls = otcextensions.osclient.obs.v1.ls:List @@ -102,6 +103,21 @@ openstack.rds.v3 = rds_backup_delete = otcextensions.osclient.rds.v3.backup:DeleteBackup rds_backup_download_links = otcextensions.osclient.rds.v3.backup:ListBackupDownloadLinks +openstack.nat.v2 = + nat_gateway_list = otcextensions.osclient.nat.v2.gateway:ListNatGateways + nat_gateway_show = otcextensions.osclient.nat.v2.gateway:ShowNatGateway + nat_gateway_create = otcextensions.osclient.nat.v2.gateway:CreateNatGateway + nat_gateway_update = otcextensions.osclient.nat.v2.gateway:UpdateNatGateway + nat_gateway_delete = otcextensions.osclient.nat.v2.gateway:DeleteNatGateway + nat_snat_rule_list = otcextensions.osclient.nat.v2.snat:ListSnatRules + nat_snat_rule_show = otcextensions.osclient.nat.v2.snat:ShowSnatRule + nat_snat_rule_create = otcextensions.osclient.nat.v2.snat:CreateSnatRule + nat_snat_rule_delete = otcextensions.osclient.nat.v2.snat:DeleteSnatRule + nat_dnat_rule_list = otcextensions.osclient.nat.v2.dnat:ListDnatRules + nat_dnat_rule_show = otcextensions.osclient.nat.v2.dnat:ShowDnatRule + nat_dnat_rule_create = otcextensions.osclient.nat.v2.dnat:CreateDnatRule + nat_dnat_rule_delete = otcextensions.osclient.nat.v2.dnat:DeleteDnatRule + openstack.auto_scaling.v1 = as_group_list = otcextensions.osclient.auto_scaling.v1.group:ListAutoScalingGroup as_group_show = otcextensions.osclient.auto_scaling.v1.group:ShowAutoScalingGroup