diff --git a/doc/source/cli/index.rst b/doc/source/cli/index.rst index e0126a6e6..57414f124 100644 --- a/doc/source/cli/index.rst +++ b/doc/source/cli/index.rst @@ -37,3 +37,4 @@ documentation of these services: obs rds_v3 volume_backup + vpc diff --git a/doc/source/cli/vpc.rst b/doc/source/cli/vpc.rst new file mode 100644 index 000000000..52aa93f5e --- /dev/null +++ b/doc/source/cli/vpc.rst @@ -0,0 +1,27 @@ +Virtual Private Cloud (VPC) +=========================== + +The VPC client is the command-line interface (CLI) for +the Virtual Private Cloud (VPC) API and its extensions. + +For help on a specific `vpc` command, enter: + +.. code-block:: console + + $ openstack vpc help SUBCOMMAND + +.. _peering: + +Vpc Peering Operations +---------------------- + +.. autoprogram-cliff:: openstack.vpc.v2 + :command: vpc peering * + +.. _route: + +Vpc Route Operations +-------------------- + +.. autoprogram-cliff:: openstack.vpc.v2 + :command: vpc route * diff --git a/doc/source/coverage.rst b/doc/source/coverage.rst index 4b33b9d3c..7a92a6940 100644 --- a/doc/source/coverage.rst +++ b/doc/source/coverage.rst @@ -108,7 +108,13 @@ under several service tags. This may change in future. - X - * - vbs - - Volume Backup + - Volume Backup Service + - X + - X + - X + - + * - vpc_v2 + - Virtual Private Cloud - X - X - X diff --git a/doc/source/sdk/guides/index.rst b/doc/source/sdk/guides/index.rst index cb1ca3545..04f39aab3 100644 --- a/doc/source/sdk/guides/index.rst +++ b/doc/source/sdk/guides/index.rst @@ -17,6 +17,7 @@ Open Telekom Cloud related User Guides kms nat rds + vpc logging .. _user_guides: diff --git a/doc/source/sdk/guides/vpc.rst b/doc/source/sdk/guides/vpc.rst new file mode 100644 index 000000000..cb15c6cb6 --- /dev/null +++ b/doc/source/sdk/guides/vpc.rst @@ -0,0 +1,131 @@ +Virtual Private Cloud (VPC) +=========================== + +.. contents:: Table of Contents + :local: + +VPC Peering Connection +---------------------- + +A VPC peering connection is a network connection between two VPCs that +enables you to route traffic between them using private IP addresses. +ECSs in either VPC can communicate with each other just as if they were in +the same VPC. You can create a VPC peering connection between your own VPCs, +or between your VPC and another account's VPC within the same region. A VPC +peering connection between VPCs in different regions will not take effect. + +List VPC Peerings +^^^^^^^^^^^^^^^^^ + +This interface is used to query all VPC peering connections accessible to the +tenant submitting the request. The connections are filtered based on the +filtering condition. +:class:`~otcextensions.sdk.vpc.v2.peering.Peering`. + +.. literalinclude:: ../examples/vpc/list_peerings.py + :lines: 16-23 + +Create VPC Peering +^^^^^^^^^^^^^^^^^^ + +This interface is used to create a VPC peering connection with +parameters. +:class:`~otcextensions.sdk.vpc.v2.peering.Peering`. + +.. literalinclude:: ../examples/vpc/create_peering.py + :lines: 16-33 + +Get VPC Peering +^^^^^^^^^^^^^^^ + +This interface is used to get a VPC peering connection by ID +or an instance of class. +:class:`~otcextensions.sdk.vpc.v2.peering.Peering`. + +.. literalinclude:: ../examples/vpc/get_peering.py + :lines: 16-26 + +Find VPC Peering +^^^^^^^^^^^^^^^^ + +This interface is used to find a VPC peering connection by id or name. +:class:`~otcextensions.sdk.vpc.v2.peering.Peering`. + +.. literalinclude:: ../examples/vpc/find_peering.py + :lines: 16-26 + +Update VPC Peering +^^^^^^^^^^^^^^^^^^ + +This interface is used to update parameters of a VPC peering connection by +id or an instance of class. +:class:`~otcextensions.sdk.vpc.v2.peering.Peering`. + +.. literalinclude:: ../examples/vpc/update_peering.py + :lines: 16-24 + +Delete VPC Peering +^^^^^^^^^^^^^^^^^^ + +This interface is used to delete a VPC peering connection by ID +or an instance of class. +:class:`~otcextensions.sdk.vpc.v2.peering.Peering`. + +.. literalinclude:: ../examples/vpc/delete_peering.py + :lines: 16-23 + +Set VPC Peering +^^^^^^^^^^^^^^^ + +This interface is used to accept of reject a VPC peering connection +request by ID or an instance of class. +:class:`~otcextensions.sdk.vpc.v2.peering.Peering`. + +.. literalinclude:: ../examples/vpc/set_peering.py + :lines: 16-32 + + +VPC Route +--------- + +To enable communication between the two VPCs, you need to add local and +peer routes for the VPC peering connection. + +List VPC Routes +^^^^^^^^^^^^^^^ + +This interface is used to query all routes of the tenant submitting the +request. The routes are filtered based on the filtering condition. +:class:`~otcextensions.sdk.vpc.v2.route.Route`. + +.. literalinclude:: ../examples/vpc/list_routes.py + :lines: 16-23 + +Add VPC Route +^^^^^^^^^^^^^ + +This Interface is used to add a VPC route. +:class:`~otcextensions.sdk.vpc.v2.route.Route`. + +.. literalinclude:: ../examples/vpc/add_route.py + :lines: 16-31 + +Get VPC Route +^^^^^^^^^^^^^ + +This interface is used to get a VPC route by ID +or an instance of class. +:class:`~otcextensions.sdk.vpc.v2.route.Route`. + +.. literalinclude:: ../examples/vpc/get_route.py + :lines: 16-26 + +Delete VPC Route +^^^^^^^^^^^^^^^^ + +This interface is used to delete a VPC route by ID +or an instance of class. +:class:`~otcextensions.sdk.vpc.v2.peering.Peering`. + +.. literalinclude:: ../examples/vpc/delete_route.py + :lines: 16-23 diff --git a/doc/source/sdk/proxies/index.rst b/doc/source/sdk/proxies/index.rst index c5f6a8d11..54c01ca98 100644 --- a/doc/source/sdk/proxies/index.rst +++ b/doc/source/sdk/proxies/index.rst @@ -19,6 +19,7 @@ Service Proxies Relational Database Service RDS V1 (RDSv1) Relational Database Service RDS V3 (RDS) Volume Backup Service (VBS) + Virtual Private Cloud (VPC) .. _service-proxies: diff --git a/doc/source/sdk/proxies/vpc.rst b/doc/source/sdk/proxies/vpc.rst new file mode 100644 index 000000000..18cd53c74 --- /dev/null +++ b/doc/source/sdk/proxies/vpc.rst @@ -0,0 +1,27 @@ +VPC API +======= + +.. automodule:: otcextensions.sdk.vpc.v2._proxy + +The Virtual Private Cloud Class +------------------------------- + +The nat high-level interface is available through the ``vpc`` +member of a :class:`~openstack.connection.Connection` object. The +``vpc`` member will only be added if the +``otcextensions.sdk.register_otc_extensions(conn)`` method is called. + +VPC Peering Operations +^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: otcextensions.sdk.vpc.v2._proxy.Proxy + :noindex: + :members: peerings, find_peering, create_peering, + update_peering, delete_peering, set_peering + +VPC Route Operations +^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: otcextensions.sdk.vpc.v2._proxy.Proxy + :noindex: + :members: routes, get_route, add_route, delete_route diff --git a/doc/source/sdk/resources/index.rst b/doc/source/sdk/resources/index.rst index a8fb45e4b..93f3c977a 100644 --- a/doc/source/sdk/resources/index.rst +++ b/doc/source/sdk/resources/index.rst @@ -19,6 +19,7 @@ Open Telekom Cloud Resources Network Address Translation (NAT) Object Block Storage (OBS) Relational Database Service (RDS) + Virtual Private Cloud (VPC) Every resource which is used within the proxy methods have own attributes. Those attributes define the behavior of the resource which can be a cluster diff --git a/doc/source/sdk/resources/vpc/index.rst b/doc/source/sdk/resources/vpc/index.rst new file mode 100644 index 000000000..9c255873c --- /dev/null +++ b/doc/source/sdk/resources/vpc/index.rst @@ -0,0 +1,8 @@ +VPC Resources +============= + +.. toctree:: + :maxdepth: 1 + + v2/peering + v2/route diff --git a/doc/source/sdk/resources/vpc/v2/peering.rst b/doc/source/sdk/resources/vpc/v2/peering.rst new file mode 100644 index 000000000..12b8dcc8b --- /dev/null +++ b/doc/source/sdk/resources/vpc/v2/peering.rst @@ -0,0 +1,13 @@ +otcextensions.sdk.vpc.v2.peering +================================ + +.. automodule:: otcextensions.sdk.vpc.v2.peering + +The VPC Peering Class +---------------------- + +The ``Peering`` class inherits from +:class:`~otcextensions.sdk.sdk_resource.Resource`. + +.. autoclass:: otcextensions.sdk.vpc.v2.peering.Peering + :members: diff --git a/doc/source/sdk/resources/vpc/v2/route.rst b/doc/source/sdk/resources/vpc/v2/route.rst new file mode 100644 index 000000000..d2f3cb467 --- /dev/null +++ b/doc/source/sdk/resources/vpc/v2/route.rst @@ -0,0 +1,13 @@ +otcextensions.sdk.vpc.v2.route +================================ + +.. automodule:: otcextensions.sdk.vpc.v2.route + +The VPC Route Class +------------------- + +The ``Route`` class inherits from +:class:`~otcextensions.sdk.sdk_resource.Resource`. + +.. autoclass:: otcextensions.sdk.vpc.v2.route.Route + :members: diff --git a/examples/vpc/add_route.py b/examples/vpc/add_route.py new file mode 100644 index 000000000..5daba9604 --- /dev/null +++ b/examples/vpc/add_route.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Add Route to VPC Peering Connection +""" +import openstack +from otcextensions import sdk + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') +sdk.register_otc_extensions(conn) + +attrs = { + "type": "peering", + "nexthop": "peering-uuid", + "destination": "192.168.100.0/24", + "vpc_id": "local-router-uuid" +} + +route = conn.vpc.add_route(**attrs) +print(route) diff --git a/examples/vpc/create_peering.py b/examples/vpc/create_peering.py new file mode 100644 index 000000000..5e3cb22bc --- /dev/null +++ b/examples/vpc/create_peering.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Create VPC Peering Connection +""" +import openstack +from otcextensions import sdk + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') +sdk.register_otc_extensions(conn) + +attrs = { + "name": "test-peering", + "request_vpc_info": { + "vpc_id": "requester-router-uuid" + }, + "accept_vpc_info": { + "vpc_id": "accepter-router-uuid" + } +} + +peering = conn.vpc.create_peering(**attrs) +print(peering) diff --git a/examples/vpc/delete_peering.py b/examples/vpc/delete_peering.py new file mode 100644 index 000000000..c99a7fd2e --- /dev/null +++ b/examples/vpc/delete_peering.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Delete VPC Peering by id or name +""" +import openstack + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') + + +peering = "peering_id" +conn.vpc.delete_peering(peering) diff --git a/examples/vpc/delete_route.py b/examples/vpc/delete_route.py new file mode 100644 index 000000000..608491515 --- /dev/null +++ b/examples/vpc/delete_route.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Delete VPC Route By ID +""" +import openstack + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') + + +route_id = "route-uuid" +conn.vpc.delete_route(route_id) diff --git a/examples/vpc/find_peering.py b/examples/vpc/find_peering.py new file mode 100644 index 000000000..15eefd713 --- /dev/null +++ b/examples/vpc/find_peering.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Find VPC Peering by id or name +""" +import openstack +from otcextensions import sdk + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') +sdk.register_otc_extensions(conn) + + +name_or_id = "name_or_id" +peering = conn.vpc.find_peering(name_or_id, ignore_missing=True) +print(peering) diff --git a/examples/vpc/get_peering.py b/examples/vpc/get_peering.py new file mode 100644 index 000000000..ec56cd726 --- /dev/null +++ b/examples/vpc/get_peering.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Get VPC Peering by id or peering Instance +""" +import openstack +from otcextensions import sdk + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') +sdk.register_otc_extensions(conn) + + +peering_id = "peering_id" +peering = conn.vpc.get_peering(peering_id) +print(peering) diff --git a/examples/vpc/get_route.py b/examples/vpc/get_route.py new file mode 100644 index 000000000..9b62c172f --- /dev/null +++ b/examples/vpc/get_route.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Get VPC Route by ID +""" +import openstack +from otcextensions import sdk + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') +sdk.register_otc_extensions(conn) + + +route_id = "route-uuid" +route = conn.vpc.get_route(route_id) +print(route) diff --git a/examples/vpc/list_peerings.py b/examples/vpc/list_peerings.py new file mode 100644 index 000000000..455246bff --- /dev/null +++ b/examples/vpc/list_peerings.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +List VPC Peerings +""" +import openstack + + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') + +for peering in conn.vpc.peerings(): + print(peering) diff --git a/examples/vpc/list_routes.py b/examples/vpc/list_routes.py new file mode 100644 index 000000000..896a2c51b --- /dev/null +++ b/examples/vpc/list_routes.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +List VPC Routes +""" +import openstack + + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') + +for route in conn.vpc.routes(): + print(route) diff --git a/examples/vpc/set_peering.py b/examples/vpc/set_peering.py new file mode 100644 index 000000000..1a704d67a --- /dev/null +++ b/examples/vpc/set_peering.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Accept or Reject VPC Peering request +""" +import openstack + + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') + +peering = conn.vpc.get_peering("peering_id") + +# accept VPC peering request +set_status = 'accept' +peering = conn.vpc.set_peering(peering=peering, set_status=set_status) +print(peering) + +# Reject VPC peering request +set_status = 'reject' +peering = conn.vpc.set_peering(peering=peering, set_status=set_status) +print(peering) diff --git a/examples/vpc/update_peering.py b/examples/vpc/update_peering.py new file mode 100644 index 000000000..56ca281bb --- /dev/null +++ b/examples/vpc/update_peering.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Update VPC Peering attributes +""" +import openstack + + +openstack.enable_logging(True) +conn = openstack.connect(cloud='otc') + +peering = conn.vpc.get_peering("peering_id") +peering = conn.vpc.update_peering(peering=peering, name='new-name') +print(peering) diff --git a/otcextensions/osclient/vpc/__init__.py b/otcextensions/osclient/vpc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/osclient/vpc/client.py b/otcextensions/osclient/vpc/client.py new file mode 100644 index 000000000..5f031ebd2 --- /dev/null +++ b/otcextensions/osclient/vpc/client.py @@ -0,0 +1,53 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +import logging + +from osc_lib import utils + +from otcextensions import sdk +from otcextensions.i18n import _ + +LOG = logging.getLogger(__name__) + +DEFAULT_API_VERSION = '2' +API_VERSION_OPTION = 'os_vpc_api_version' +API_NAME = "vpc" +API_VERSIONS = { + "2": "openstack.connection.Connection" +} + + +def make_client(instance): + """Returns a vpc proxy""" + + conn = instance.sdk_connection + + if getattr(conn, 'vpc', None) is None: + LOG.debug('OTC extensions are not registered. Do that now') + sdk.register_otc_extensions(conn) + + LOG.debug('VPC client initialized using OpenStack OTC SDK: %s', + conn.vpc) + return conn.vpc + + +def build_option_parser(parser): + """Hook to add global options""" + parser.add_argument( + '--os-vpc-api-version', + metavar='', + default=utils.env('OS_VPC_API_VERSION'), + help=_("VPC API version, default=%s " + "(Env: OS_VPC_API_VERSION)") % DEFAULT_API_VERSION + ) + return parser diff --git a/otcextensions/osclient/vpc/v2/__init__.py b/otcextensions/osclient/vpc/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/osclient/vpc/v2/peering.py b/otcextensions/osclient/vpc/v2/peering.py new file mode 100644 index 000000000..f9d8b621f --- /dev/null +++ b/otcextensions/osclient/vpc/v2/peering.py @@ -0,0 +1,339 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +"""VPC Peering v2 action implementations""" +import logging + +from osc_lib import utils +from osc_lib import exceptions +from osc_lib.command import command + +from otcextensions.i18n import _ + +LOG = logging.getLogger(__name__) + +STATUS_CHOICES = [ + 'PENDING_ACCEPTANCE', + 'REJECTED', + 'EXPIRED', + 'DELETED', + 'ACTIVE' +] + + +def _update_vpc_info(vpcinfo): + if 'vpc_id' in vpcinfo: + vpcinfo['router_id'] = vpcinfo.pop('vpc_id') + if 'tenant_id' in vpcinfo: + vpcinfo['project_id'] = vpcinfo.pop('tenant_id') + return vpcinfo + + +def set_attributes_for_print(peerings): + for peering in peerings: + setattr(peering, 'local_router_id', + peering.local_vpc_info['vpc_id']) + setattr(peering, 'peer_router_id', + peering.peer_vpc_info['vpc_id']) + setattr(peering, 'peer_project_id', + peering.peer_vpc_info['tenant_id']) + yield peering + + +def translate_response(func): + def new(self, *args, **kwargs): + response = func(self, *args, **kwargs) + setattr(response, + 'local_vpc_info', + _update_vpc_info(response.local_vpc_info)) + setattr(response, + 'peer_vpc_info', + _update_vpc_info(response.peer_vpc_info)) + columns = ( + 'id', + 'name', + 'local_vpc_info', + 'peer_vpc_info', + 'description', + 'created_at', + 'updated_at', + 'status' + ) + data = utils.get_item_properties(response, columns) + return (columns, data) + new.__name__ = func.__name__ + new.__doc__ = func.__doc__ + return new + + +class ListVpcPeerings(command.Lister): + + _description = _("List Vpc Peerings.") + columns = ( + 'Id', + 'Name', + 'Status', + 'Local Router Id', + 'Peer Router Id', + 'Peer Project Id') + + def get_parser(self, prog_name): + parser = super(ListVpcPeerings, self).get_parser(prog_name) + + parser.add_argument( + '--id', + metavar='', + help=_("Specifies the ID of the VPC peering connection."), + ) + parser.add_argument( + '--limit', + metavar='', + type=int, + help=_("Limit to fetch number of records."), + ) + parser.add_argument( + '--marker', + metavar='', + help=_("Specifies the start resource ID of pagination query."), + ) + parser.add_argument( + '--project-id', + metavar='', + help=_("Specifies the project ID."), + ) + parser.add_argument( + '--name', + metavar='', + help=_("Specifies the Name of the VPC peering connection."), + ) + parser.add_argument( + '--router-id', + metavar='', + help=_("Specifies the router/vpc ID of requester/accepter " + "of vpc peering."), + ) + parser.add_argument( + '--status', + metavar='{' + ','.join(STATUS_CHOICES) + '}', + type=lambda s: s.upper(), + choices=STATUS_CHOICES, + help=_("Specifies the status of the VPC peering connection."), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + args_list = [ + 'id', + 'name', + 'limit', + 'marker', + 'project_id', + 'router_id', + 'status'] + attrs = {} + for arg in args_list: + val = getattr(parsed_args, arg) + if val: + attrs[arg] = val + + data = client.peerings(**attrs) + if data: + data = set_attributes_for_print(data) + + return (self.columns, (utils.get_item_properties(s, self.columns) + for s in data)) + + +class ShowVpcPeering(command.ShowOne): + _description = _("Show VPC peering connection details.") + + def get_parser(self, prog_name): + parser = super(ShowVpcPeering, self).get_parser(prog_name) + parser.add_argument( + 'peering', + metavar='', + help=_("Specifies the Name or ID of the VPC peering connection."), + ) + return parser + + @translate_response + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + return client.find_peering(parsed_args.peering) + + +class SetVpcPeering(command.ShowOne): + _description = _("Accept VPC peering connection Request.") + + def get_parser(self, prog_name): + parser = super(SetVpcPeering, self).get_parser(prog_name) + parser.add_argument( + 'peering', + metavar='', + help=_("Specifies the Name or ID of the VPC peering connection."), + ) + manage_request_group = parser.add_mutually_exclusive_group() + manage_request_group.add_argument( + '--accept', + action='store_true', + help=('Accept VPC peering connection request.') + ) + manage_request_group.add_argument( + '--reject', + action='store_true', + help=('Reject VPC peering connection request.') + ) + return parser + + @translate_response + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + set_args = ('accept', 'reject') + request_status = [request for request in set_args if + getattr(parsed_args, request)] + peering = client.find_peering(parsed_args.peering) + return client.set_peering(peering.id, request_status[0]) + + +class CreateVpcPeering(command.ShowOne): + _description = _("Create new Vpc Peering.") + + def get_parser(self, prog_name): + parser = super(CreateVpcPeering, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help=_("Specifies the name of the VPC peering connection."), + ) + parser.add_argument( + '--description', + metavar='', + help=_("Specifies the description of the VPC peering connection."), + ) + parser.add_argument( + '--local-router-id', + metavar='', + required=True, + help=_("Specifies information about the local router_id " + "involved in a VPC peering connection."), + ) + parser.add_argument( + '--peer-router-id', + metavar='', + required=True, + help=_("Specifies information about the peer router_id " + "involved in a VPC peering connection."), + ) + parser.add_argument( + '--peer-project-id', + metavar='', + help=_("Specifies the ID of the project to which a " + "peer router belongs. It is required if the peer " + "router is from different project."), + ) + return parser + + @translate_response + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + attrs = { + 'name': parsed_args.name, + 'request_vpc_info': { + 'vpc_id': parsed_args.local_router_id + }, + 'accept_vpc_info': { + 'vpc_id': parsed_args.peer_router_id + } + } + val = getattr(parsed_args, 'peer_project_id') + if val: + attrs['accept_vpc_info']['tenant_id'] = val + attrs['request_vpc_info']['tenant_id'] = client.get_project_id() + + val = getattr(parsed_args, 'description') + if val: + attrs['description'] = val + + return client.create_peering(**attrs) + + +class UpdateVpcPeering(command.ShowOne): + _description = _("Update a VPC peering connection.") + + def get_parser(self, prog_name): + parser = super(UpdateVpcPeering, self).get_parser(prog_name) + parser.add_argument( + 'peering', + metavar='', + help=_("Specifies the Name or ID of the VPC peering connection."), + ) + parser.add_argument( + '--name', + metavar='', + help=_("Specifies the new name of the VPC peering connection."), + ) + parser.add_argument( + '--description', + metavar='', + help=_("Specifies the new descrition of the VPC peering " + "connection."), + ) + return parser + + @translate_response + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + args_list = [ + 'name', 'description' + ] + attrs = {} + for arg in args_list: + val = getattr(parsed_args, arg) + if val: + attrs[arg] = val + peering = client.find_peering(parsed_args.peering) + + return client.update_peering(peering.id, **attrs) + + +class DeleteVpcPeering(command.Command): + + _description = _("Deletes VPC Peering.") + + def get_parser(self, prog_name): + parser = super(DeleteVpcPeering, self).get_parser(prog_name) + parser.add_argument( + 'peering', + metavar='', + nargs='+', + help=_("VPC Peering(s) to delete (Name or ID)"), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + result = 0 + for peering in parsed_args.peering: + try: + obj = client.find_peering(peering) + client.delete_peering(obj.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete VPC peering connection with " + "name or ID '%(peering)s': %(e)s"), + {'peering': peering, 'e': e}) + if result > 0: + total = len(parsed_args.peering) + msg = (_("%(result)s of %(total)s VPC peering(s) failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) diff --git a/otcextensions/osclient/vpc/v2/route.py b/otcextensions/osclient/vpc/v2/route.py new file mode 100644 index 000000000..f5f1574c9 --- /dev/null +++ b/otcextensions/osclient/vpc/v2/route.py @@ -0,0 +1,226 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +"""VPC Route v2 action implementations""" +import logging + +from osc_lib import utils +from osc_lib import exceptions +from osc_lib.command import command + +from otcextensions.i18n import _ + +LOG = logging.getLogger(__name__) + + +def set_attributes_for_print(routes): + for route in routes: + yield route + + +def translate_response(func): + def new(self, *args, **kwargs): + response = func(self, *args, **kwargs) + columns = ( + 'id', + 'type', + 'nexthop', + 'destination', + 'router_id', + 'project_id' + ) + data = utils.get_item_properties(response, columns) + return (columns, data) + new.__name__ = func.__name__ + new.__doc__ = func.__doc__ + return new + + +class ListVpcRoutes(command.Lister): + + _description = _("List Vpc Routes.") + columns = ( + 'Id', + 'Type', + 'Router Id', + 'Project Id', + 'NextHop', + 'Destination' + ) + + def get_parser(self, prog_name): + parser = super(ListVpcRoutes, self).get_parser(prog_name) + + parser.add_argument( + '--id', + metavar='', + help=_("Specifies the ID of the VPC route."), + ) + parser.add_argument( + '--limit', + metavar='', + type=int, + help=_("Limit to fetch number of records."), + ) + parser.add_argument( + '--marker', + metavar='', + help=_("Specifies the start resource ID of pagination query."), + ) + parser.add_argument( + '--project-id', + metavar='', + help=_("Specifies the project ID."), + ) + parser.add_argument( + '--router-id', + metavar='', + help=_("Specifies the router/vpc ID."), + ) + parser.add_argument( + '--destination', + metavar='', + help=_("Specifies that the route destination address (CIDR) " + "is used as the filtering condition."), + ) + parser.add_argument( + '--type', + metavar='', + help=_("Specifies that the type is used as the filtering " + "condition. Currently, the value can only be peering."), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + args_list = [ + 'id', + 'type', + 'limit', + 'marker', + 'project_id', + 'router_id', + 'destination'] + attrs = {} + for arg in args_list: + val = getattr(parsed_args, arg) + if val: + attrs[arg] = val + + data = client.routes(**attrs) + if data: + data = set_attributes_for_print(data) + + return (self.columns, (utils.get_item_properties(s, self.columns) + for s in data)) + + +class ShowVpcRoute(command.ShowOne): + _description = _("Show VPC Route Details.") + + def get_parser(self, prog_name): + parser = super(ShowVpcRoute, self).get_parser(prog_name) + parser.add_argument( + 'route', + metavar='', + help=_("Specifies the ID of the VPC route."), + ) + return parser + + @translate_response + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + return client.get_route(parsed_args.route) + + +class AddVpcRoute(command.ShowOne): + _description = _("Add Vpc Route.") + + def get_parser(self, prog_name): + parser = super(AddVpcRoute, self).get_parser(prog_name) + parser.add_argument( + '--destination', + metavar='', + required=True, + help=_("Specifies the destination address in the CIDR " + "notation format, for example, 192.168.200.0/24."), + ) + parser.add_argument( + '--nexthop', + metavar='', + required=True, + help=_("Specifies the next hop. If the type is " + "peering, enter the VPC peering connection ID."), + ) + parser.add_argument( + '--type', + metavar='', + default='peering', + help=_("Specifies the route type. Currently, the value can " + "only be peering."), + ) + parser.add_argument( + '--router-id', + metavar='', + required=True, + help=_("Specifies the requesting router ID for creating a route."), + ) + return parser + + @translate_response + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + args_list = [ + 'type', + 'router_id', + 'destination', + 'nexthop'] + attrs = {} + for arg in args_list: + val = getattr(parsed_args, arg) + if val: + attrs[arg] = val + + return client.add_route(**attrs) + + +class DeleteVpcRoute(command.Command): + + _description = _("Deletes VPC Route.") + + def get_parser(self, prog_name): + parser = super(DeleteVpcRoute, self).get_parser(prog_name) + parser.add_argument( + 'route', + metavar='', + nargs='+', + help=_("VPC Routes(s) ID to delete"), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.vpc + result = 0 + for route in parsed_args.route: + try: + obj = client.get_route(route) + client.delete_route(obj.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete VPC route with " + "ID '%(route)s': %(e)s"), + {'route': route, 'e': e}) + if result > 0: + total = len(parsed_args.route) + msg = (_("%(result)s of %(total)s VPC route(s) failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) diff --git a/otcextensions/sdk/__init__.py b/otcextensions/sdk/__init__.py index 7da252974..1c1c403b2 100644 --- a/otcextensions/sdk/__init__.py +++ b/otcextensions/sdk/__init__.py @@ -144,6 +144,9 @@ 'append_project_id': True, 'endpoint_service_type': 'vbs', }, + 'vpc': { + 'service_type': 'vpc', + }, 'waf': { 'service_type': 'waf', 'append_project_id': True, diff --git a/otcextensions/sdk/vpc/__init__.py b/otcextensions/sdk/vpc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/sdk/vpc/v2/__init__.py b/otcextensions/sdk/vpc/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/sdk/vpc/v2/_proxy.py b/otcextensions/sdk/vpc/v2/_proxy.py new file mode 100644 index 000000000..6e300d90e --- /dev/null +++ b/otcextensions/sdk/vpc/v2/_proxy.py @@ -0,0 +1,185 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from otcextensions.sdk.vpc.v2 import peering as _peering +from otcextensions.sdk.vpc.v2 import route as _route + +from openstack import proxy + + +class Proxy(proxy.Proxy): + + def _override_endpoint(self): + endpoint = self.get_endpoint(service_type='network') + setattr(self, 'endpoint_override', endpoint) + + # ======== Peering ======== + def create_peering(self, **attrs): + """Create a new vpc peering from attributes + + :param dict attrs: Keyword arguments which will be used to create + a :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + """ + self._override_endpoint() + return self._create(_peering.Peering, **attrs) + + def delete_peering(self, peering, ignore_missing=True): + """Delete a vpc peering + + :param peering: key id or an instance of + :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + :param bool ignore_missing: When set to ``False`` + :class:`~openstack.exceptions.ResourceNotFound` will be raised when + the vpc peering does not exist. + When set to ``True``, no exception will be set when attempting to + delete a nonexistent peering. + + :returns: ``None`` + """ + self._override_endpoint() + return self._delete(_peering.Peering, peering, + ignore_missing=ignore_missing) + + def peerings(self, **query): + """Return a generator of vpc peerings + + :param dict query: Optional query parameters to be sent to limit + the resources being returned. + + :returns: A generator of vpc peering objects + + :rtype: :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + """ + self._override_endpoint() + return self._list(_peering.Peering, **query) + + def get_peering(self, peering): + """Get a single vpc peering + + :param peering: The value can be the ID of a vpc peering or a + :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + instance. + + :returns: One :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + """ + self._override_endpoint() + return self._get(_peering.Peering, peering) + + def find_peering(self, name_or_id, ignore_missing=False): + """Find a single vpc peering + + :param name_or_id: The name or ID of a zone + :param bool ignore_missing: When set to ``False`` + :class:`~openstack.exceptions.ResourceNotFound` will be raised + when the vpc peering does not exist. + When set to ``True``, no exception will be set when attempting + to delete a nonexistent peering. + + :returns: One :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + """ + self._override_endpoint() + return self._find(_peering.Peering, name_or_id, + ignore_missing=ignore_missing) + + def update_peering(self, peering, **attrs): + """Update a vpc peering + + :param peering: Either the ID of a vpc peering or a + :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + instance. + :param dict attrs: The attributes to update on the vpc peering + represented by ``peering``. + + :returns: The updated peering + + :rtype: :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + """ + self._override_endpoint() + return self._update(_peering.Peering, peering, **attrs) + + def set_peering(self, peering, set_status): + """Accept/Reject a vpc peering connection request + + :param peering: Either the ID of a vpc peering or a + :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + instance. + :param set_status: The value can been ``accept`` or ``reject`` + + :returns: The updated peering + + :rtype: :class:`~otcextensions.sdk.vpc.v2.peering.Peering` + """ + valid_status = ['accept', 'reject'] + if set_status.lower() not in valid_status: + raise ValueError( + "results: status must be one of %r." % valid_status) + peering = self._get_resource(_peering.Peering, peering) + self._override_endpoint() + return peering._set_peering(self, set_status.lower()) + + # ======== Route ======== + + def add_route(self, **attrs): + """Add vpc route + + :param dict attrs: Keyword arguments which will be used to create + a :class:`~otcextensions.sdk.vpc.v2.route.Route` + """ + self._override_endpoint() + return self._create(_route.Route, **attrs) + + def delete_route(self, route, ignore_missing=True): + """Delete a vpc route + + :param route: route id or an instance of + :class:`~otcextensions.sdk.vpc.v2.route.Route` + :param bool ignore_missing: When set to ``False`` + :class:`~openstack.exceptions.ResourceNotFound` will be raised when + the vpc route does not exist. + When set to ``True``, no exception will be set when attempting to + delete a nonexistent route. + + :returns: ``None`` + """ + self._override_endpoint() + return self._delete(_route.Route, route, + ignore_missing=ignore_missing) + + def routes(self, **query): + """Return a generator of vpc routes + + :param dict query: Optional query parameters to be sent to limit + the resources being returned. + + :returns: A generator of vpc route objects + + :rtype: :class:`~otcextensions.sdk.vpc.v2.route.Route` + """ + self._override_endpoint() + return self._list(_route.Route, **query) + + def get_route(self, route): + """Get details of a single vpc route + + :param route: The value can be the ID of a vpc route or a + :class:`~otcextensions.sdk.vpc.v2.route.Route` + instance. + + :returns: One :class:`~otcextensions.sdk.vpc.v2.route.Route` + + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + """ + self._override_endpoint() + return self._get(_route.Route, route) diff --git a/otcextensions/sdk/vpc/v2/peering.py b/otcextensions/sdk/vpc/v2/peering.py new file mode 100644 index 000000000..cad7aa574 --- /dev/null +++ b/otcextensions/sdk/vpc/v2/peering.py @@ -0,0 +1,63 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from openstack import exceptions +from openstack import resource +from openstack import utils + + +class Peering(resource.Resource): + resources_key = 'peerings' + resource_key = 'peering' + base_path = '/vpc/peerings' + + # capabilities + allow_create = True + allow_fetch = True + allow_commit = True + allow_delete = True + allow_list = True + + _query_mapping = resource.QueryParameters( + 'id', 'marker', 'limit', 'name', 'router_id', + 'status', 'project_id', project_id='tenant_id', + router_id='vpc_id' + ) + + #: Specifies the VPC peering connection ID. + #: *Type: uuid* + id = resource.Body('id') + #: Specifies information about the local VPC. + #: *Type: dict* + peer_vpc_info = resource.Body('accept_vpc_info', type=dict) + #: Specifies information about the local VPC. + #: *Type: dict* + local_vpc_info = resource.Body('request_vpc_info', type=dict) + #: Specifies the name of the VPC peering connection. + name = resource.Body('name') + #: Specifies the status. + status = resource.Body('status') + #: Provides supplementary information about the VPC peering connection. + description = resource.Body('description') + #: Specifies the time (UTC) when the VPC peering connection is created. + #: Format is *yyyy-mm-dd hh:mm:ss*. + created_at = resource.Body('created_at') + #: Specifies the time (UTC) when the VPC peering connection is updated. + #: Format is *yyyy-mm-dd hh:mm:ss*. + updated_at = resource.Body('updated_at') + + def _set_peering(self, session, set_status): + """Accept/Reject Peering Request""" + url = utils.urljoin(self.base_path, self.id, set_status) + response = session.put(url) + exceptions.raise_from_response(response) + self._translate_response(response) + return self diff --git a/otcextensions/sdk/vpc/v2/route.py b/otcextensions/sdk/vpc/v2/route.py new file mode 100644 index 000000000..1b70fe99c --- /dev/null +++ b/otcextensions/sdk/vpc/v2/route.py @@ -0,0 +1,47 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from openstack import resource + + +class Route(resource.Resource): + resources_key = 'routes' + resource_key = 'route' + base_path = '/vpc/routes' + + # capabilities + allow_create = True + allow_fetch = True + allow_delete = True + allow_list = True + + _query_mapping = resource.QueryParameters( + 'id', 'marker', 'limit', 'type', 'router_id', + 'destination', 'project_id', project_id='tenant_id', + router_id='vpc_id' + ) + + #: Specifies the VPC peering connection ID. + #: *Type: uuid* + id = resource.Body('id') + #: Specifies the next hop. + #: If the route type is peering, enter the VPC peering connection ID. + nexthop = resource.Body('nexthop') + #: Specifies the destination address in the CIDR notation format, + #: for example, 192.168.200.0/24. + destination = resource.Body('destination') + #: Specifies the route type. Currently, the value can only be peering. + type = resource.Body('type') + #: Specifies the Router of the route. + #: Set this parameter to the existing Router ID. + router_id = resource.Body('vpc_id') + #: Specifies the project ID. + project_id = resource.Body('tenant_id') diff --git a/otcextensions/sdk/vpc/vpc_service.py b/otcextensions/sdk/vpc/vpc_service.py new file mode 100644 index 000000000..b1749d49c --- /dev/null +++ b/otcextensions/sdk/vpc/vpc_service.py @@ -0,0 +1,22 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from openstack import service_description + +from otcextensions.sdk.vpc.v2 import _proxy + + +class VpcService(service_description.ServiceDescription): + """The NAT service.""" + + supported_versions = { + '2': _proxy.Proxy + } diff --git a/otcextensions/tests/functional/osclient/vpc/__init__.py b/otcextensions/tests/functional/osclient/vpc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/functional/osclient/vpc/v2/__init__.py b/otcextensions/tests/functional/osclient/vpc/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/functional/osclient/vpc/v2/common.py b/otcextensions/tests/functional/osclient/vpc/v2/common.py new file mode 100644 index 000000000..16ec4bab2 --- /dev/null +++ b/otcextensions/tests/functional/osclient/vpc/v2/common.py @@ -0,0 +1,74 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import json +import uuid + +from datetime import datetime + +from openstackclient.tests.functional import base + + +class VpcTestCase(base.TestCase): + """Common functional test bits for VPC commands""" + + CURR_TIME = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") + + def setUp(self): + super(VpcTestCase, self).setUp() + UUID = uuid.uuid4().hex[:8] + self.LOCAL_ROUTER_NAME = 'test-local-router-otce-cli' + UUID + self.PEER_ROUTER_NAME = 'test-peer-router-otce-cli' + UUID + self.PEERING_NAME = 'test-peering-otce-cli-' + UUID + + self.LOCAL_ROUTER_ID = None + self.PEER_ROUTER_ID = None + self.PEERING_ID = None + + def create_vpc_peering(self, name=None): + self._create_routers() + name = name or self.PEERING_NAME + json_output = json.loads(self.openstack( + 'vpc peering create ' + '{name} ' + '--local-router-id "{local_router_id}" ' + '--peer-router-id "{peer_router_id}" ' + '-f json'.format( + name=name, + local_router_id=self.LOCAL_ROUTER_ID, + peer_router_id=self.PEER_ROUTER_ID) + )) + self.assertIsNotNone(json_output) + self.PEERING_ID = json_output['id'] + return json_output + + def delete_vpc_peering(self): + self.addCleanup(self._delete_routers) + self.openstack('vpc peering delete {}'.format(self.PEERING_ID)) + + def _create_routers(self): + local_router = json.loads(self.openstack( + 'router create -f json ' + self.LOCAL_ROUTER_NAME + )) + self.LOCAL_ROUTER_ID = local_router['id'] + + peer_router = json.loads(self.openstack( + 'router create -f json ' + self.PEER_ROUTER_NAME + )) + self.PEER_ROUTER_ID = peer_router['id'] + + def _delete_routers(self): + self.openstack( + 'router delete {} {}'.format( + self.LOCAL_ROUTER_ID, self.PEER_ROUTER_ID + )) diff --git a/otcextensions/tests/functional/osclient/vpc/v2/test_peering.py b/otcextensions/tests/functional/osclient/vpc/v2/test_peering.py new file mode 100644 index 000000000..26270ad56 --- /dev/null +++ b/otcextensions/tests/functional/osclient/vpc/v2/test_peering.py @@ -0,0 +1,111 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json + +from otcextensions.tests.functional.osclient.vpc.v2 import common + + +class TestPeering(common.VpcTestCase): + """Functional Tests for NAT Gateway""" + + def setUp(self): + super(TestPeering, self).setUp() + + def test_vpc_peering_list(self): + json_output = json.loads(self.openstack( + 'vpc peering list -f json ' + )) + self.assertIsNotNone(json_output) + + def test_vpc_peering_list_filters(self): + json_output = json.loads(self.openstack( + 'vpc peering list -f json ' + '--limit 1 ' + '--id 2 ' + '--name 3 ' + '--project-id 4 ' + '--router-id 5 ' + '--status ACTIVE ' + )) + self.assertIsNotNone(json_output) + + def test_vpc_peering(self): + self.addCleanup(self.delete_vpc_peering) + peering = self.create_vpc_peering() + peering_id = peering['id'] + peering_name = peering['name'] + local_router_id = peering['local_vpc_info']['router_id'] + peer_router_id = peering['peer_vpc_info']['router_id'] + + # List Vpc Peering By Id + json_output = json.loads(self.openstack( + 'vpc peering list -f json ' + '--id {}'.format(peering_id) + )) + self.assertEqual(json_output[0]['Name'], peering_name) + self.assertEqual(json_output[0]['Id'], peering_id) + + # List Vpc Peering By Name + json_output = json.loads(self.openstack( + 'vpc peering list -f json ' + '--name {}'.format(peering_name) + )) + self.assertEqual(json_output[0]['Name'], peering_name) + self.assertEqual(json_output[0]['Id'], peering_id) + + # List Vpc Peering by Requester Router ID + json_output = json.loads(self.openstack( + 'vpc peering list -f json ' + '--router-id {}'.format(local_router_id) + )) + self.assertIsNotNone(json_output) + + # List Vpc Peering by Accepter Router ID + json_output = json.loads(self.openstack( + 'vpc peering list -f json ' + '--router-id {}'.format(peer_router_id) + )) + for peering in json_output: + self.assertEqual( + peering['Peer Router Id'], + peer_router_id + ) + + # Show Vpc Peering by Name + json_output = json.loads(self.openstack( + 'vpc peering show -f json ' + peering_name + )) + self.assertEqual(json_output['name'], peering_name) + self.assertEqual(json_output['id'], peering_id) + + # Show Vpc Peering by Id + json_output = json.loads(self.openstack( + 'vpc peering show -f json ' + peering_id + )) + self.assertEqual(json_output['name'], peering_name) + self.assertEqual(json_output['id'], peering_id) + + # Update Vpc Peering + peering_name = peering_name + "-updated" + description = "test vpc peering updated by otce cli" + json_output = json.loads(self.openstack( + 'vpc peering update {peering_id} ' + '--name {name} ' + '--description "{desc}" ' + '-f json'.format( + peering_id=peering_id, + name=peering_name, + desc=description) + )) + self.assertEqual(json_output['name'], peering_name) + self.assertEqual(json_output['description'], description) diff --git a/otcextensions/tests/functional/sdk/vpc/__init__.py b/otcextensions/tests/functional/sdk/vpc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/functional/sdk/vpc/v2/__init__.py b/otcextensions/tests/functional/sdk/vpc/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/functional/sdk/vpc/v2/test_service.py b/otcextensions/tests/functional/sdk/vpc/v2/test_service.py new file mode 100644 index 000000000..88d1a1744 --- /dev/null +++ b/otcextensions/tests/functional/sdk/vpc/v2/test_service.py @@ -0,0 +1,24 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from openstack import _log + +from otcextensions.tests.functional import base + +_logger = _log.setup_logging('openstack') + + +class TestService(base.BaseFunctionalTest): + + def test_initialize(self): + client = self.conn.vpc + + self.assertIsNotNone(client) diff --git a/otcextensions/tests/unit/osclient/vpc/__init__.py b/otcextensions/tests/unit/osclient/vpc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/unit/osclient/vpc/v2/__init__.py b/otcextensions/tests/unit/osclient/vpc/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/unit/osclient/vpc/v2/fakes.py b/otcextensions/tests/unit/osclient/vpc/v2/fakes.py new file mode 100644 index 000000000..81c3eb11e --- /dev/null +++ b/otcextensions/tests/unit/osclient/vpc/v2/fakes.py @@ -0,0 +1,94 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +import uuid +import mock +from datetime import datetime + +from openstackclient.tests.unit import utils +from otcextensions.tests.unit.osclient import test_base + +from otcextensions.sdk.vpc.v2 import peering +from otcextensions.sdk.vpc.v2 import route + + +def gen_data(data, columns): + """Fill expected data tuple based on columns list + """ + return tuple(getattr(data, attr, '') for attr in columns) + + +def gen_data_dict(data, columns): + """Fill expected data tuple based on columns list + """ + return tuple(data.get(attr, '') for attr in columns) + + +class TestVpc(utils.TestCommand): + def setUp(self): + super(TestVpc, self).setUp() + + self.app.client_manager.vpc = mock.Mock() + + self.client = self.app.client_manager.vpc + + +class FakeVpcPeering(test_base.Fake): + """Fake one or more VPC peering connections.""" + @classmethod + def generate(cls): + """Create a fake VPC peering connection. + + :return: + A FakeResource object, with id, name and so on + """ + # Set default attributes. + object_info = { + "id": "id-" + uuid.uuid4().hex, + "name": "name-" + uuid.uuid4().hex, + "request_vpc_info": { + "vpc_id": uuid.uuid4().hex, + "tenant_id": uuid.uuid4().hex + }, + "accept_vpc_info": { + "vpc_id": uuid.uuid4().hex, + "tenant_id": uuid.uuid4().hex + }, + "status": "ACTIVE", + "description": "my vpc peering", + "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"), + "updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") + } + + return peering.Peering(**object_info) + + +class FakeVpcRoute(test_base.Fake): + """Fake one or more VPC routes.""" + @classmethod + def generate(cls): + """Create a fake VPC route. + + :return: + A FakeResource object, with id, name and so on + """ + # Set default attributes. + object_info = { + "id": "id-" + uuid.uuid4().hex, + "type": "peering", + "nexthop": uuid.uuid4().hex, + "router_id": uuid.uuid4().hex, + "project_id": uuid.uuid4().hex, + "destination": "192.168.200.0/24", + } + + return route.Route(**object_info) diff --git a/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py b/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py new file mode 100644 index 000000000..f34d3598f --- /dev/null +++ b/otcextensions/tests/unit/osclient/vpc/v2/test_peering.py @@ -0,0 +1,478 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +import mock +from unittest.mock import call + +from osc_lib import exceptions + +from otcextensions.osclient.vpc.v2 import peering +from otcextensions.tests.unit.osclient.vpc.v2 import fakes + +from openstackclient.tests.unit import utils as tests_utils + + +class TestListVpcPeerings(fakes.TestVpc): + + objects = fakes.FakeVpcPeering.create_multiple(3) + + column_list_headers = ( + 'Id', + 'Name', + 'Status', + 'Local Router Id', + 'Peer Router Id', + 'Peer Project Id', + ) + + columns = ( + 'id', + 'name', + 'status', + 'local_router_id', + 'peer_router_id', + 'peer_project_id' + ) + + data = [] + + for s in objects: + data.append( + (s.id, s.name, s.status, s.local_vpc_info['vpc_id'], + s.peer_vpc_info['vpc_id'], s.peer_vpc_info['tenant_id'])) + + def setUp(self): + super(TestListVpcPeerings, self).setUp() + + self.cmd = peering.ListVpcPeerings(self.app, None) + + self.client.peerings = mock.Mock() + self.client.api_mock = self.client.peerings + + def test_list(self): + arglist = [] + + verifylist = [] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Set the response + self.client.api_mock.side_effect = [self.objects] + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + + self.client.api_mock.assert_called_with() + + self.assertEqual(self.column_list_headers, columns) + self.assertEqual(self.data, list(data)) + + def test_list_args(self): + arglist = [ + '--limit', '1', + '--marker', '2', + '--id', '3', + '--name', '4', + '--project-id', '5', + '--router-id', '6', + '--status', 'ACTIVE' + ] + + verifylist = [ + ('limit', 1), + ('marker', '2'), + ('id', '3'), + ('name', '4'), + ('project_id', '5'), + ('router_id', '6'), + ('status', 'ACTIVE'), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Set the response + self.client.api_mock.side_effect = [self.objects] + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + + self.client.api_mock.assert_called_with( + limit=1, + marker='2', + id='3', + name='4', + project_id='5', + router_id='6', + status='ACTIVE', + ) + + +class TestCreateVpcPeering(fakes.TestVpc): + + _data = fakes.FakeVpcPeering.create_one() + + columns = ( + 'id', + 'name', + 'local_vpc_info', + 'peer_vpc_info', + 'description', + 'created_at', + 'updated_at', + 'status' + ) + + data = fakes.gen_data(_data, columns) + + def setUp(self): + super(TestCreateVpcPeering, self).setUp() + + self.cmd = peering.CreateVpcPeering(self.app, None) + self.client.create_peering = mock.Mock( + return_value=fakes.FakeVpcPeering.create_one()) + self.client.get_project_id = mock.Mock( + return_value='test-local-project-uuid') + + def test_create_different_project(self): + arglist = [ + 'test-peering', + '--local-router-id', 'test-local-router-uuid', + '--peer-router-id', 'test-peer-router-uuid', + '--peer-project-id', 'test-peer-project-uuid', + '--description', 'test-peering', + ] + verifylist = [ + ('name', 'test-peering'), + ('local_router_id', 'test-local-router-uuid'), + ('peer_router_id', 'test-peer-router-uuid'), + ('peer_project_id', 'test-peer-project-uuid'), + ('description', 'test-peering'), + ] + # Verify cm is triggereg with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + attrs = { + 'name': 'test-peering', + 'request_vpc_info': { + 'vpc_id': 'test-local-router-uuid', + 'tenant_id': 'test-local-project-uuid' + }, + 'accept_vpc_info': { + 'vpc_id': 'test-peer-router-uuid', + 'tenant_id': 'test-peer-project-uuid' + }, + 'description': 'test-peering' + } + + self.client.create_peering.assert_called_with(**attrs) + self.assertEqual(self.columns, columns) + + def test_create_same_project(self): + arglist = [ + 'test-peering', + '--local-router-id', 'test-local-router-uuid', + '--peer-router-id', 'test-peer-router-uuid', + '--description', 'test-peering', + ] + verifylist = [ + ('name', 'test-peering'), + ('local_router_id', 'test-local-router-uuid'), + ('peer_router_id', 'test-peer-router-uuid'), + ('description', 'test-peering'), + ] + # Verify cm is triggereg with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + attrs = { + 'name': 'test-peering', + 'request_vpc_info': { + 'vpc_id': 'test-local-router-uuid' + }, + 'accept_vpc_info': { + 'vpc_id': 'test-peer-router-uuid' + }, + 'description': 'test-peering' + } + + self.client.create_peering.assert_called_with(**attrs) + self.assertEqual(self.columns, columns) + + +class TestUpdateVpcPeering(fakes.TestVpc): + + _data = fakes.FakeVpcPeering.create_one() + + columns = ( + 'id', + 'name', + 'local_vpc_info', + 'peer_vpc_info', + 'description', + 'created_at', + 'updated_at', + 'status' + ) + + data = fakes.gen_data(_data, columns) + + def setUp(self): + super(TestUpdateVpcPeering, self).setUp() + + self.cmd = peering.UpdateVpcPeering(self.app, None) + + self.client.find_peering = mock.Mock(return_value=self._data) + self.client.update_peering = mock.Mock(return_value=self._data) + + def test_update(self): + arglist = [ + self._data.name, + '--name', 'test-peering-updated', + '--description', 'vpc peering updated', + ] + verifylist = [ + ('peering', self._data.name), + ('name', 'test-peering-updated'), + ('description', 'vpc peering updated'), + ] + # Verify cm is triggereg with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + + self.client.find_peering.assert_called_with(self._data.name) + self.client.update_peering.assert_called_with( + self._data.id, + name='test-peering-updated', + description='vpc peering updated' + ) + self.assertEqual(self.columns, columns) + + +class TestShowVpcPeering(fakes.TestVpc): + + _data = fakes.FakeVpcPeering.create_one() + + columns = ( + 'id', + 'name', + 'local_vpc_info', + 'peer_vpc_info', + 'description', + 'created_at', + 'updated_at', + 'status' + ) + + data = fakes.gen_data(_data, columns) + + def setUp(self): + super(TestShowVpcPeering, self).setUp() + + self.cmd = peering.ShowVpcPeering(self.app, None) + + self.client.find_peering = mock.Mock(return_value=self._data) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Testing that a call without the required argument will fail and + # throw a "ParserExecption" + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_show(self): + arglist = [ + self._data.id, + ] + + verifylist = [ + ('peering', self._data.id), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + self.client.find_peering.assert_called_with(self._data.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_show_non_existent(self): + arglist = [ + 'unexist_vpc_peering', + ] + + verifylist = [ + ('peering', 'unexist_vpc_peering'), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = exceptions.CommandError('Resource Not Found') + self.client.find_peering = ( + mock.Mock(side_effect=find_mock_result) + ) + + # Trigger the action + try: + self.cmd.take_action(parsed_args) + except Exception as e: + self.assertEqual('Resource Not Found', str(e)) + self.client.find_peering.assert_called_with('unexist_vpc_peering') + + +class TestSetVpcPeering(fakes.TestVpc): + + _data = fakes.FakeVpcPeering.create_one() + + columns = ( + 'id', + 'name', + 'local_vpc_info', + 'peer_vpc_info', + 'description', + 'created_at', + 'updated_at', + 'status' + ) + + data = fakes.gen_data(_data, columns) + + def setUp(self): + super(TestSetVpcPeering, self).setUp() + + self.cmd = peering.SetVpcPeering(self.app, None) + + self.client.find_peering = mock.Mock(return_value=self._data) + self.client.set_peering = mock.Mock(return_value=self._data) + + def test_set(self): + arglist = [ + self._data.name, + '--accept' + ] + + verifylist = [ + ('peering', self._data.name), + ('accept', True), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + self.client.find_peering.assert_called_with(self._data.name) + self.client.set_peering.assert_called_with(self._data.id, 'accept') + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestDeleteVpcPeering(fakes.TestVpc): + + _data = fakes.FakeVpcPeering.create_multiple(2) + + def setUp(self): + super(TestDeleteVpcPeering, self).setUp() + + self.client.delete_peering = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = peering.DeleteVpcPeering(self.app, None) + + def test_delete(self): + arglist = [ + self._data[0].name, + ] + + verifylist = [ + ('peering', [self._data[0].name]), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.client.find_peering = ( + mock.Mock(return_value=self._data[0]) + ) + + # Trigger the action + result = self.cmd.take_action(parsed_args) + self.client.delete_peering.assert_called_with(self._data[0].id) + self.assertIsNone(result) + + def test_multiple_delete(self): + arglist = [] + + for data in self._data: + arglist.append(data.name) + + verifylist = [ + ('peering', arglist), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = self._data + self.client.find_peering = ( + mock.Mock(side_effect=find_mock_result) + ) + + # Trigger the action + result = self.cmd.take_action(parsed_args) + + calls = [] + for data in self._data: + calls.append(call(data.id)) + self.client.delete_peering.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multiple_delete_with_exception(self): + arglist = [ + self._data[0].name, + 'unexist_vpc_peering', + ] + verifylist = [ + ('peering', arglist), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._data[0], exceptions.CommandError] + self.client.find_peering = ( + mock.Mock(side_effect=find_mock_result) + ) + + # Trigger the action + try: + self.cmd.take_action(parsed_args) + except Exception as e: + self.assertEqual( + '1 of 2 VPC peering(s) failed to delete.', str(e)) + + self.client.find_peering.assert_any_call(self._data[0].name) + self.client.find_peering.assert_any_call('unexist_vpc_peering') + self.client.delete_peering.assert_called_once_with(self._data[0].id) diff --git a/otcextensions/tests/unit/osclient/vpc/v2/test_route.py b/otcextensions/tests/unit/osclient/vpc/v2/test_route.py new file mode 100644 index 000000000..b52e5a235 --- /dev/null +++ b/otcextensions/tests/unit/osclient/vpc/v2/test_route.py @@ -0,0 +1,329 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +import mock +from unittest.mock import call + +from osc_lib import exceptions + +from otcextensions.osclient.vpc.v2 import route +from otcextensions.tests.unit.osclient.vpc.v2 import fakes + +from openstackclient.tests.unit import utils as tests_utils + + +class TestListVpcRoutes(fakes.TestVpc): + + objects = fakes.FakeVpcRoute.create_multiple(3) + + column_list_headers = ( + 'Id', + 'Type', + 'Router Id', + 'Project Id', + 'NextHop', + 'Destination' + ) + + columns = ( + 'id', + 'type', + 'router_id', + 'project_id', + 'nexthop', + 'destination' + ) + + data = [] + + for s in objects: + data.append( + (s.id, s.type, s.router_id, s.project_id, + s.nexthop, s.destination)) + + def setUp(self): + super(TestListVpcRoutes, self).setUp() + + self.cmd = route.ListVpcRoutes(self.app, None) + + self.client.routes = mock.Mock() + self.client.api_mock = self.client.routes + + def test_list(self): + arglist = [] + + verifylist = [] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Set the response + self.client.api_mock.side_effect = [self.objects] + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + + self.client.api_mock.assert_called_with() + + self.assertEqual(self.column_list_headers, columns) + self.assertEqual(self.data, list(data)) + + def test_list_args(self): + arglist = [ + '--limit', '1', + '--marker', '2', + '--id', '3', + '--destination', '4', + '--project-id', '5', + '--router-id', '6' + ] + + verifylist = [ + ('limit', 1), + ('marker', '2'), + ('id', '3'), + ('destination', '4'), + ('project_id', '5'), + ('router_id', '6') + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Set the response + self.client.api_mock.side_effect = [self.objects] + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + + self.client.api_mock.assert_called_with( + limit=1, + marker='2', + id='3', + destination='4', + project_id='5', + router_id='6', + ) + + +class TestAddVpcRoute(fakes.TestVpc): + + _data = fakes.FakeVpcRoute.create_one() + + columns = ( + 'id', + 'type', + 'nexthop', + 'destination', + 'router_id', + 'project_id' + ) + + data = fakes.gen_data(_data, columns) + + def setUp(self): + super(TestAddVpcRoute, self).setUp() + + self.cmd = route.AddVpcRoute(self.app, None) + self.client.add_route = mock.Mock( + return_value=fakes.FakeVpcRoute.create_one()) + + def test_add_route(self): + arglist = [ + '--nexthop', 'test-peering-uuid', + '--destination', '192.168.1.0/24', + '--router-id', 'test-router-uuid', + ] + verifylist = [ + ('nexthop', 'test-peering-uuid'), + ('type', 'peering'), + ('destination', '192.168.1.0/24'), + ('router_id', 'test-router-uuid'), + ] + # Verify cm is triggereg with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + attrs = { + 'nexthop': 'test-peering-uuid', + 'type': 'peering', + 'destination': '192.168.1.0/24', + 'router_id': 'test-router-uuid' + } + + self.client.add_route.assert_called_with(**attrs) + self.assertEqual(self.columns, columns) + + +class TestShowVpcRoute(fakes.TestVpc): + + _data = fakes.FakeVpcRoute.create_one() + + columns = ( + 'id', + 'type', + 'nexthop', + 'destination', + 'router_id', + 'project_id' + ) + + data = fakes.gen_data(_data, columns) + + def setUp(self): + super(TestShowVpcRoute, self).setUp() + + self.cmd = route.ShowVpcRoute(self.app, None) + + self.client.get_route = mock.Mock(return_value=self._data) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Testing that a call without the required argument will fail and + # throw a "ParserExecption" + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_show(self): + arglist = [ + self._data.id, + ] + + verifylist = [ + ('route', self._data.id), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + self.client.get_route.assert_called_with(self._data.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_show_non_existent(self): + arglist = [ + 'unexist_vpc_route', + ] + + verifylist = [ + ('route', 'unexist_vpc_route'), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = exceptions.CommandError('Resource Not Found') + self.client.get_route = ( + mock.Mock(side_effect=find_mock_result) + ) + + # Trigger the action + try: + self.cmd.take_action(parsed_args) + except Exception as e: + self.assertEqual('Resource Not Found', str(e)) + self.client.get_route.assert_called_with('unexist_vpc_route') + + +class TestDeleteVpcRoute(fakes.TestVpc): + + _data = fakes.FakeVpcRoute.create_multiple(2) + + def setUp(self): + super(TestDeleteVpcRoute, self).setUp() + + self.client.delete_route = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = route.DeleteVpcRoute(self.app, None) + + def test_delete(self): + arglist = [ + self._data[0].id, + ] + + verifylist = [ + ('route', [self._data[0].id]), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.client.get_route = ( + mock.Mock(return_value=self._data[0]) + ) + + # Trigger the action + result = self.cmd.take_action(parsed_args) + self.client.delete_route.assert_called_with(self._data[0].id) + self.assertIsNone(result) + + def test_multiple_delete(self): + arglist = [] + + for data in self._data: + arglist.append(data.id) + + verifylist = [ + ('route', arglist), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + get_mock_result = self._data + self.client.get_route = ( + mock.Mock(side_effect=get_mock_result) + ) + + # Trigger the action + result = self.cmd.take_action(parsed_args) + + calls = [] + for data in self._data: + calls.append(call(data.id)) + self.client.delete_route.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multiple_delete_with_exception(self): + arglist = [ + self._data[0].id, + 'unexist_vpc_route', + ] + verifylist = [ + ('route', arglist), + ] + + # Verify cm is triggered with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + get_mock_result = [self._data[0], exceptions.CommandError] + self.client.get_route = ( + mock.Mock(side_effect=get_mock_result) + ) + + # Trigger the action + try: + self.cmd.take_action(parsed_args) + except Exception as e: + self.assertEqual( + '1 of 2 VPC route(s) failed to delete.', str(e)) + + self.client.get_route.assert_any_call(self._data[0].id) + self.client.get_route.assert_any_call('unexist_vpc_route') + self.client.delete_route.assert_called_once_with(self._data[0].id) diff --git a/otcextensions/tests/unit/sdk/vpc/__init__.py b/otcextensions/tests/unit/sdk/vpc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/unit/sdk/vpc/v2/__init__.py b/otcextensions/tests/unit/sdk/vpc/v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/unit/sdk/vpc/v2/test_peering.py b/otcextensions/tests/unit/sdk/vpc/v2/test_peering.py new file mode 100644 index 000000000..24f40642c --- /dev/null +++ b/otcextensions/tests/unit/sdk/vpc/v2/test_peering.py @@ -0,0 +1,91 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from keystoneauth1 import adapter + +import copy +import mock + +from openstack.tests.unit import base +from otcextensions.sdk.vpc.v2 import peering + + +IDENTIFIER = 'ID' +EXAMPLE = { + "name": "test", + "id": "22b76469-08e3-4937-8c1d-7aad34892be1", + "request_vpc_info": { + "vpc_id": "9daeac7c-a98f-430f-8e38-67f9c044e299", + "tenant_id": "f65e9ebc-ed5d-418b-a931-9a723718ba4e" + }, + "accept_vpc_info": { + "vpc_id": "f583c072-0bb8-4e19-afb2-afb7c1693be5", + "tenant_id": "f65e9ebc-ed5d-418b-a931-9a723718ba4e" + }, + "status": "ACTIVE" +} + + +class TestPeering(base.TestCase): + + def setUp(self): + super(TestPeering, self).setUp() + self.sess = mock.Mock(spec=adapter.Adapter) + self.sess.put = mock.Mock() + + def test_basic(self): + sot = peering.Peering() + self.assertEqual('peering', sot.resource_key) + self.assertEqual('peerings', sot.resources_key) + path = '/vpc/peerings' + self.assertEqual(path, sot.base_path) + self.assertTrue(sot.allow_list) + self.assertTrue(sot.allow_create) + self.assertTrue(sot.allow_fetch) + self.assertTrue(sot.allow_commit) + self.assertTrue(sot.allow_delete) + + def test_make_it(self): + sot = peering.Peering(**EXAMPLE) + self.assertEqual(EXAMPLE['status'], sot.status) + self.assertEqual(EXAMPLE['request_vpc_info'], sot.local_vpc_info) + self.assertEqual(EXAMPLE['accept_vpc_info'], sot.peer_vpc_info) + self.assertEqual(EXAMPLE['id'], sot.id) + self.assertEqual(EXAMPLE['name'], sot.name) + + def test_set_peering(self): + sot = peering.Peering(id=IDENTIFIER) + + resp = mock.Mock() + resp.body = { + "name": "test", + "id": "22b76469-08e3-4937-8c1d-7aad34892be1", + "request_vpc_info": { + "vpc_id": "9daeac7c-a98f-430f-8e38-67f9c044e299", + "tenant_id": "f65e9ebc-ed5d-418b-a931-9a723718ba4e" + }, + "accept_vpc_info": { + "vpc_id": "f583c072-0bb8-4e19-afb2-afb7c1693be5", + "tenant_id": "f65e9ebc-ed5d-418b-a931-9a723718ba4e" + }, + "status": "REJECTED" + } + resp.json = mock.Mock(return_value=copy.deepcopy(resp.body)) + resp.headers = {} + resp.status_code = 200 + self.sess.put.return_value = resp + + response = sot._set_peering(self.sess, 'reject') + self.sess.put.assert_called_with( + 'vpc/peerings/ID/reject' + ) + self.assertEqual(resp.body['name'], response.name) + self.assertEqual(resp.body['id'], response.id) diff --git a/otcextensions/tests/unit/sdk/vpc/v2/test_proxy.py b/otcextensions/tests/unit/sdk/vpc/v2/test_proxy.py new file mode 100644 index 000000000..8d13f96e3 --- /dev/null +++ b/otcextensions/tests/unit/sdk/vpc/v2/test_proxy.py @@ -0,0 +1,81 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from otcextensions.sdk.vpc.v2 import _proxy +from otcextensions.sdk.vpc.v2 import peering +from otcextensions.sdk.vpc.v2 import route + +from openstack.tests.unit import test_proxy_base + + +class TestVpcProxy(test_proxy_base.TestProxyBase): + def setUp(self): + super(TestVpcProxy, self).setUp() + self.proxy = _proxy.Proxy(self.session) + + +class TestVpcPeering(TestVpcProxy): + def test_peering_create(self): + self.verify_create(self.proxy.create_peering, peering.Peering, + method_kwargs={'name': 'id'}, + expected_kwargs={'name': 'id'}) + + def test_peering_delete(self): + self.verify_delete(self.proxy.delete_peering, + peering.Peering, True) + + def test_peering_get(self): + self.verify_get(self.proxy.get_peering, peering.Peering) + + def test_peerings(self): + self.verify_list(self.proxy.peerings, peering.Peering) + + def test_peering_update(self): + self.verify_update(self.proxy.update_peering, peering.Peering) + + def test_set_peering(self): + self._verify( + 'otcextensions.sdk.vpc.v2.peering.Peering._set_peering', + self.proxy.set_peering, + method_args=[peering.Peering], + method_kwargs={'set_status': 'accept'}, + expected_args=['accept'] + ) + + +class TestVpcRoute(TestVpcProxy): + def test_route_add(self): + self.verify_create(self.proxy.add_route, route.Route, + method_kwargs={'name': 'id'}, + expected_kwargs={'name': 'id'}) + + def test_route_delete(self): + self.verify_delete(self.proxy.delete_route, + route.Route, True) + + def test_route_get(self): + self.verify_get(self.proxy.get_route, route.Route) + + def test_routes(self): + self.verify_list(self.proxy.routes, route.Route) diff --git a/otcextensions/tests/unit/sdk/vpc/v2/test_route.py b/otcextensions/tests/unit/sdk/vpc/v2/test_route.py new file mode 100644 index 000000000..f48c0ddc7 --- /dev/null +++ b/otcextensions/tests/unit/sdk/vpc/v2/test_route.py @@ -0,0 +1,57 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from keystoneauth1 import adapter + +import mock + +from openstack.tests.unit import base +from otcextensions.sdk.vpc.v2 import route + + +IDENTIFIER = 'ID' +EXAMPLE = { + "type": "peering", + "nexthop": "60c809cb-6731-45d0-ace8-3bf5626421a9", + "destination": "192.168.200.0/24", + "vpc_id": "ab78be2d-782f-42a5-aa72-35879f6890ff", + "tenant_id": "6fbe9263116a4b68818cf1edce16bc4f", + "id": "3d42a0d4-a980-4613-ae76-a2cddecff054" +} + + +class TestRoute(base.TestCase): + + def setUp(self): + super(TestRoute, self).setUp() + self.sess = mock.Mock(spec=adapter.Adapter) + self.sess.put = mock.Mock() + + def test_basic(self): + sot = route.Route() + self.assertEqual('route', sot.resource_key) + self.assertEqual('routes', sot.resources_key) + path = '/vpc/routes' + self.assertEqual(path, sot.base_path) + self.assertTrue(sot.allow_list) + self.assertTrue(sot.allow_create) + self.assertTrue(sot.allow_fetch) + self.assertTrue(sot.allow_delete) + self.assertFalse(sot.allow_commit) + + def test_make_it(self): + sot = route.Route(**EXAMPLE) + self.assertEqual(EXAMPLE['id'], sot.id) + self.assertEqual(EXAMPLE['destination'], sot.destination) + self.assertEqual(EXAMPLE['nexthop'], sot.nexthop) + self.assertEqual(EXAMPLE['type'], sot.type) + self.assertEqual(EXAMPLE['vpc_id'], sot.router_id) + self.assertEqual(EXAMPLE['tenant_id'], sot.project_id) diff --git a/setup.cfg b/setup.cfg index 82cff272f..49fc72e13 100644 --- a/setup.cfg +++ b/setup.cfg @@ -41,6 +41,7 @@ openstack.cli.extension = dns = otcextensions.osclient.dns.client deh = otcextensions.osclient.deh.client nat = otcextensions.osclient.nat.client + vpc = otcextensions.osclient.vpc.client #openstack.obs.v1 = # s3_ls = otcextensions.osclient.obs.v1.ls:List @@ -299,6 +300,18 @@ openstack.compute.v2 = server_set = otcextensions.osclient.compute.v2.server:SetServer server_unset = otcextensions.osclient.compute.v2.server:UnsetServer +openstack.vpc.v2 = + vpc_peering_list = otcextensions.osclient.vpc.v2.peering:ListVpcPeerings + vpc_peering_show = otcextensions.osclient.vpc.v2.peering:ShowVpcPeering + vpc_peering_create = otcextensions.osclient.vpc.v2.peering:CreateVpcPeering + vpc_peering_update = otcextensions.osclient.vpc.v2.peering:UpdateVpcPeering + vpc_peering_delete = otcextensions.osclient.vpc.v2.peering:DeleteVpcPeering + vpc_peering_set = otcextensions.osclient.vpc.v2.peering:SetVpcPeering + vpc_route_list = otcextensions.osclient.vpc.v2.route:ListVpcRoutes + vpc_route_show = otcextensions.osclient.vpc.v2.route:ShowVpcRoute + vpc_route_add = otcextensions.osclient.vpc.v2.route:AddVpcRoute + vpc_route_delete = otcextensions.osclient.vpc.v2.route:DeleteVpcRoute + [build_sphinx] builders = html,man all-files = 1