diff options
| author | Corey Bryant <corey.bryant@canonical.com> | 2022-09-08 18:25:53 -0400 |
|---|---|---|
| committer | git-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com> | 2022-09-08 22:34:10 +0000 |
| commit | fad5650933d0a7320db7ded8df77cdbc9267c7ac (patch) | |
| tree | 493ab1059740f6933d8adcf63d1c16c5ff93d349 | |
| parent | 4c7c05ece5d38fb781c721d1f21af502060b897a (diff) | |
6.0.0-0ubuntu1 (patches unapplied)import/6.0.0-0ubuntu1ubuntu/kinetic-proposedubuntu/kinetic-develubuntu/kinetic
Imported using git-ubuntu import.
Notes
Notes:
* New upstream release for OpenStack Zed.
* d/control: Bump debhelper compat to 13.
* d/control: Update standards version to 4.6.1.
120 files changed, 5731 insertions, 3209 deletions
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f91d10b..28ed93d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,6 +16,7 @@ repos: - id: debug-statements - id: check-yaml files: .*\.(yaml|yml)$ + args: ['--unsafe'] - repo: local hooks: - id: flake8 @@ -18,8 +18,8 @@ zuul_work_dir: src/opendev.org/openstack/python-openstackclient - job: - name: osc-tox-py36-tips - parent: openstack-tox-py36 + name: osc-tox-py38-tips + parent: openstack-tox-py38 description: | Run unit tests for OpenStackClient with master branch of important libs. @@ -189,8 +189,8 @@ description: Build Docker images. allowed-projects: openstack/python-openstackclient requires: - - python-builder-3.7-container-image - - python-base-3.7-container-image + - python-builder-3.9-container-image + - python-base-3.9-container-image provides: osc-container-image vars: &osc_image_vars docker_images: @@ -203,8 +203,8 @@ description: Build Docker images and upload to Docker Hub. allowed-projects: openstack/python-openstackclient requires: - - python-builder-3.7-container-image - - python-base-3.7-container-image + - python-builder-3.9-container-image + - python-base-3.9-container-image provides: osc-container-image secrets: - name: docker_credentials @@ -229,18 +229,17 @@ name: osc-tox-unit-tips check: jobs: - - osc-tox-py36-tips + - osc-tox-py38-tips gate: jobs: - - osc-tox-py36-tips + - osc-tox-py38-tips - project: templates: - openstackclient-plugin-jobs - osc-tox-unit-tips - openstack-cover-jobs - - openstack-lower-constraints-jobs - - openstack-python3-yoga-jobs + - openstack-python3-zed-jobs - publish-openstack-docs-pti - check-requirements - release-notes-jobs-python3 @@ -171,6 +171,7 @@ James E. Blair <jeblair@hp.com> James E. Blair <jeblair@redhat.com> Jamie Lennox <jamielennox@redhat.com> Jan Gutter <jan.gutter@netronome.com> +Jan Hartkopf <jhartkopf@inovex.de> Jas <singhj@us.ibm.com> Jaspreet Singh Rawel <jaspreetsinghrawel@gmail.com> Javier Pena <jpena@redhat.com> @@ -287,6 +288,7 @@ Petr Blaho <pblaho@redhat.com> Pierre Hanselmann <pierre.hanselmann@gmail.com> Pierre Prinetti <pierreprinetti@redhat.com> Pierre Riteau <pierre@stackhpc.com> +Przemyslaw Szczerbik <przemyslaw.szczerbik@est.tech> Qiu Yu <qiuyu@ebaysf.com> Radoslaw Smigielski <radoslaw.smigielski@nokia.com> Radosław Piliszek <radoslaw.piliszek@gmail.com> @@ -342,6 +344,7 @@ SongmingYan <yan.songming@zte.com.cn> Stephen Finucane <sfinucan@redhat.com> Stephen Finucane <stephen.finucane@intel.com> Stephen Finucane <stephen@that.guru> +Stephen Finucane <stephenfin@redhat.com> Steve Martinelli <s.martinelli@gmail.com> Steve Martinelli <stevemar@ca.ibm.com> Steven Hardy <shardy@redhat.com> @@ -357,6 +360,7 @@ Telles Nobrega <tenobreg@redhat.com> Terry Howe <terrylhowe@gmail.com> Terry Howe <thowe@hp.com> TerryHowe <terrylhowe@gmail.com> +Thobias Salazar Trevisan <thobiast@gmail.com> Thomas Goirand <zigo@debian.org> Thrivikram Mudunuri <mthrivikram+opendev@gmail.com> Tim Burke <tim.burke@gmail.com> @@ -374,6 +378,7 @@ Vasyl Saienko <vsaienko@mirantis.com> Victor Silva <victsou@gmail.com> Vijendra Soni <vijendra.soni@cognizant.com> Vincent Legoll <vincent.legoll@idgrilles.fr> +Violet Kurtz <vikurtz@osuosl.org> Vishakha Agarwal <agarwalvishakha18@gmail.com> Vladimir Eremin <yottatsa@yandex-team.ru> Vu Cong Tuan <tuanvc@vn.fujitsu.com> @@ -389,6 +394,7 @@ Yang Youseok <ileixe@gmail.com> Yejia Xu <yejia@unitedstack.com> Yi Zhao <zhaoyi@cmss.chinamobile.com> Yongli He <yongli.he@intel.com> +Yoonsoo Lim <msdbtjd123@naver.com> YuehuiLei <leiyuehui@inspur.com> Yunpeng Li <yunpeng.li@live.cn> Zane Bitter <zbitter@redhat.com> @@ -1,10 +1,50 @@ CHANGES ======= +6.0.0 +----- + +* Migrate server\_groups to the new API +* Update the Nova CLI Mapping Guide +* Drop support for Python 3.6, 3.7 +* pre-commit: Allow unsafe YAML +* Add support for CRUD operations for QoS minimum packet rate rule +* image: Split image creation depending on service +* image: Make better use of argparse +* Fix: create image from volume command +* image: Trivial style changes +* Skip test\_quota\_network\_set\_with\_force func test +* Don't show tenant\_id in network objects +* Allow users to list all images +* Migrate osc-tox-py3N-tips to Python 3.8 +* Add 'Host Status' to 'server list --long' with >= v2.16 +* volume: Correct output of 'volume attachment create' +* Allow to filter multiple tags for image list +* Stop testing lower-constraints +* volume: Add 'block storage resource filter list' command +* volume: Add 'block storage cluster \*' commands +* Refactor network fakes to sdk properties PART 4 +* Stop using private \_is\_uuid\_like method +* Add more filter option of columns for server list -c COLUMN +* Allow "--force" flag in quota network commands +* Refactor network fakes to sdk properties PART 3 +* compute: Add 'Security Groups' for 'server list' +* Refactor network fakes to sdk properties PART 2 +* Refactor network fakes to sdk properties PART 1 +* Refactor "volume backup restore" command +* compute: Move server migrations commands to their own file +* compute: Allow retrieval of migration by UUID +* volume: fix backup list of deleted volume +* Add Python3 zed unit tests +* Update master for stable/yoga +* Fix metavars and typos in local\_ip + 5.8.0 ----- * Add support for setting extra DHCP options on existing ports +* volume list: don't fail when there's no compute service +* Fix 'server event list|show' for deleted servers * docs: Document additional 'server migration' commands * Add support for 'remote-managed' vnic type * Don't fail on extlinks warnings for reno build @@ -19,6 +59,7 @@ CHANGES * compute: Fix weird option definition for 'server ssh' * compute: Return information about fixed IP * Switch add fixed IP to SDK +* tests: Improve logging for executed commands * Allow setting gateway when creating a router * Fix RemoveServerVolume * Add network update quota "limit\_check" parameter @@ -45,8 +86,10 @@ CHANGES ----- * compute: Return details of attached volumes +* compute: Use correct command class for 'show migration' * compute: Don't warn if disk overcommit params unset * Switch server backup to sdk +* Fix typos * Remove remnants of 'six' * Remove 'get\_osc\_show\_columns\_for\_sdk\_resource' duplicates * tox: Ignore virtualenvs for pep8 environment @@ -94,6 +137,7 @@ CHANGES * docs: Update novaclient comparison doc * docs: Update cinderclient comparison doc * Include hosts in aggregate list --long +* Changed minversion in tox to 3.18.0 * compute: Fix typo * setup.cfg: Replace dashes with underscores * Make functional Neutron tests running fine on ML2/OVN environments @@ -13,12 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM docker.io/opendevorg/python-builder:3.7 as builder +FROM docker.io/opendevorg/python-builder:3.9 as builder COPY . /tmp/src RUN assemble -FROM docker.io/opendevorg/python-base:3.7 +FROM docker.io/opendevorg/python-base:3.9 COPY --from=builder /output/ /output RUN /output/install-from-bindep @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: python-openstackclient -Version: 5.8.0 +Version: 6.0.0 Summary: OpenStack Command-line Client Home-page: https://docs.openstack.org/python-openstackclient/latest/ Author: OpenStack @@ -154,7 +154,6 @@ Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 -Requires-Python: >=3.6 +Classifier: Programming Language :: Python :: 3.9 +Requires-Python: >=3.8 diff --git a/debian/changelog b/debian/changelog index 21a3e15..166a5ea 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +python-openstackclient (6.0.0-0ubuntu1) kinetic; urgency=medium + + * New upstream release for OpenStack Zed. + * d/control: Bump debhelper compat to 13. + * d/control: Update standards version to 4.6.1. + + -- Corey Bryant <corey.bryant@canonical.com> Thu, 08 Sep 2022 18:25:53 -0400 + python-openstackclient (5.8.0-0ubuntu1) jammy; urgency=medium * New upstream release for OpenStack Yoga. diff --git a/debian/control b/debian/control index 81b14b9..3d33561 100644 --- a/debian/control +++ b/debian/control @@ -7,7 +7,7 @@ Uploaders: gustavo panizzo <gfa@zumbi.com.ar>, Thomas Goirand <zigo@debian.org>, Build-Depends: - debhelper-compat (= 12), + debhelper-compat (= 13), dh-python, openstack-pkg-tools, python3-all, @@ -65,8 +65,7 @@ Build-Depends-Indep: python3-zaqarclient (>= 1.0.0), python3-zunclient (>= 3.6.0), subunit, -Standards-Version: 4.5.0 -Vcs-Browser: https://git.launchpad.net/~ubuntu-openstack-dev/ubuntu/+source/python-openstackclient +Standards-Version: 4.6.1 Vcs-Git: https://git.launchpad.net/~ubuntu-openstack-dev/ubuntu/+source/python-openstackclient Homepage: https://opendev.org/openstack/python-openstackclient Testsuite: autopkgtest-pkg-python diff --git a/doc/source/cli/command-objects/block-storage-cluster.rst b/doc/source/cli/command-objects/block-storage-cluster.rst new file mode 100644 index 0000000..318419e --- /dev/null +++ b/doc/source/cli/command-objects/block-storage-cluster.rst @@ -0,0 +1,8 @@ +===================== +block storage cluster +===================== + +Block Storage v3 + +.. autoprogram-cliff:: openstack.volume.v3 + :command: block storage cluster * diff --git a/doc/source/cli/command-objects/block-storage-resource-filter.rst b/doc/source/cli/command-objects/block-storage-resource-filter.rst new file mode 100644 index 0000000..3e4c33b --- /dev/null +++ b/doc/source/cli/command-objects/block-storage-resource-filter.rst @@ -0,0 +1,8 @@ +============================= +block storage resource filter +============================= + +Block Storage v3 + +.. autoprogram-cliff:: openstack.volume.v3 + :command: block storage resource filter * diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index 7e59215..d789ece 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -76,6 +76,8 @@ referring to both Compute and Volume quotas. * ``address scope``: (**Network**) a scope of IPv4 or IPv6 addresses * ``aggregate``: (**Compute**) a grouping of compute hosts * ``availability zone``: (**Compute**, **Network**, **Volume**) a logical partition of hosts or block storage or network services +* ``block storage cluster``: (**Volume**) clusters of volume services +* ``block storage resource filter``: (**Volume**) filters for volume service resources * ``catalog``: (**Identity**) service catalog * ``command``: (**Internal**) installed commands in the OSC process * ``compute agent``: (**Compute**) a cloud Compute agent available to a hypervisor diff --git a/doc/source/cli/data/cinder.csv b/doc/source/cli/data/cinder.csv index 9d79d1b..eeaaeac 100644 --- a/doc/source/cli/data/cinder.csv +++ b/doc/source/cli/data/cinder.csv @@ -20,10 +20,10 @@ cgsnapshot-create,consistency group snapshot create,Creates a cgsnapshot. cgsnapshot-delete,consistency group snapshot delete,Removes one or more cgsnapshots. cgsnapshot-list,consistency group snapshot list,Lists all cgsnapshots. cgsnapshot-show,consistency group snapshot show,Shows cgsnapshot details. -cluster-disable,,Disables clustered services. (Supported by API versions 3.7 - 3.latest) -cluster-enable,,Enables clustered services. (Supported by API versions 3.7 - 3.latest) -cluster-list,,Lists clustered services with optional filtering. (Supported by API versions 3.7 - 3.latest) -cluster-show,,Show detailed information on a clustered service. (Supported by API versions 3.7 - 3.latest) +cluster-disable,block storage cluster set --disable,Disables clustered services. (Supported by API versions 3.7 - 3.latest) +cluster-enable,block storage cluster set --enable,Enables clustered services. (Supported by API versions 3.7 - 3.latest) +cluster-list,block storage cluster list,Lists clustered services with optional filtering. (Supported by API versions 3.7 - 3.latest) +cluster-show,block storage cluster show,Show detailed information on a clustered service. (Supported by API versions 3.7 - 3.latest) consisgroup-create,consistency group create,Creates a consistency group. consisgroup-create-from-src,consistency group create --consistency-group-snapshot,Creates a consistency group from a cgsnapshot or a source CG consisgroup-delete,consistency group delete,Removes one or more consistency groups. @@ -69,7 +69,7 @@ group-update,volume group set,Updates a group. (Supported by API versions 3.13 - image-metadata,volume set --image-property,Sets or deletes volume image metadata. image-metadata-show,volume show,Shows volume image metadata. list,volume list,Lists all volumes. -list-filters,,List enabled filters. (Supported by API versions 3.33 - 3.latest) +list-filters,block storage resource filter list,List enabled filters. (Supported by API versions 3.33 - 3.latest) manage,volume create --remote-source k=v,Manage an existing volume. manageable-list,,Lists all manageable volumes. (Supported by API versions 3.8 - 3.latest) message-delete,volume message delete,Removes one or more messages. (Supported by API versions 3.3 - 3.latest) diff --git a/doc/source/cli/data/nova.csv b/doc/source/cli/data/nova.csv index 0ab2b2f..4645916 100644 --- a/doc/source/cli/data/nova.csv +++ b/doc/source/cli/data/nova.csv @@ -47,7 +47,7 @@ hypervisor-stats,hypervisor stats show,Get hypervisor statistics over all comput hypervisor-uptime,,Display the uptime of the specified hypervisor. image-create,server image create,Create a new image by taking a snapshot of a running server. instance-action,server event show,Show an action. -instance-action-list,,List actions on a server. +instance-action-list,server event list,List actions on a server. instance-usage-audit-log,,List/Get server usage audits. interface-attach,server add port / server add floating ip / server add fixed ip,Attach a network interface to a server. interface-detach,server remove port,Detach a network interface from a server. diff --git a/lower-constraints.txt b/lower-constraints.txt deleted file mode 100644 index 495b102..0000000 --- a/lower-constraints.txt +++ /dev/null @@ -1,97 +0,0 @@ -amqp==2.1.1 -appdirs==1.3.0 -asn1crypto==0.23.0 -Babel==2.6.0 -bcrypt==3.2.0 -cachetools==2.0.0 -cffi==1.14.0 -cliff==3.5.0 -cmd2==0.8.0 -coverage==4.0 -cryptography==2.7 -ddt==1.0.1 -debtcollector==1.2.0 -decorator==4.4.1 -dogpile.cache==0.6.5 -eventlet==0.18.2 -extras==1.0.0 -fasteners==0.7.0 -fixtures==3.0.0 -future==0.16.0 -futurist==2.1.0 -greenlet==0.4.15 -importlib-metadata==3.1.1 -iso8601==0.1.11 -Jinja2==2.10 -jmespath==0.9.0 -jsonpatch==1.16 -jsonpointer==1.13 -jsonschema==2.6.0 -keystoneauth1==3.18.0 -kombu==4.0.0 -linecache2==1.0.0 -MarkupSafe==1.1.1 -mock==4.0.2 -monotonic==0.6 -mox3==0.20.0 -msgpack-python==0.4.0 -munch==2.1.0 -netaddr==0.7.18 -netifaces==0.10.4 -openstacksdk==0.61.0 -os-client-config==2.1.0 -os-service-types==1.7.0 -osc-lib==2.3.0 -oslo.concurrency==3.26.0 -oslo.config==5.2.0 -oslo.context==2.19.2 -oslo.i18n==3.15.3 -oslo.log==3.36.0 -oslo.messaging==5.29.0 -oslo.middleware==3.31.0 -oslo.serialization==2.18.0 -oslo.service==1.24.0 -oslo.utils==3.33.0 -oslotest==3.2.0 -osprofiler==1.4.0 -paramiko==2.7.1 -Paste==2.0.2 -PasteDeploy==1.5.0 -pbr==2.0.0 -pika==0.10.0 -pika-pool==0.1.3 -prettytable==0.7.2 -pycparser==2.18 -pyinotify==0.9.6 -PyNaCl==1.4.0 -pyparsing==2.1.0 -pyperclip==1.5.27 -python-cinderclient==3.3.0 -python-dateutil==2.5.3 -python-keystoneclient==3.22.0 -python-mimeparse==1.6.0 -python-novaclient==17.0.0 -python-subunit==1.0.0 -pytz==2013.6 -PyYAML==3.13 -repoze.lru==0.7 -requests==2.14.2 -requests-mock==1.2.0 -requestsexceptions==1.2.0 -rfc3986==0.3.1 -Routes==2.3.1 -simplejson==3.5.1 -statsd==3.2.1 -stestr==1.0.0 -stevedore==2.0.1 -tempest==17.1.0 -tenacity==3.2.1 -testrepository==0.0.18 -testtools==2.2.0 -traceback2==1.4.0 -unittest2==1.1.0 -urllib3==1.21.1 -vine==1.1.4 -WebOb==1.7.1 -wrapt==1.7.0 -zipp==3.4.0 diff --git a/openstackclient/common/progressbar.py b/openstackclient/common/progressbar.py index ef767a9..7678ace 100644 --- a/openstackclient/common/progressbar.py +++ b/openstackclient/common/progressbar.py @@ -17,7 +17,7 @@ import sys class _ProgressBarBase(object): - """A progress bar provider for a wrapped obect. + """A progress bar provider for a wrapped object. Base abstract class used by specific class wrapper to show a progress bar when the wrapped object are consumed. diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 677cba0..4448236 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -205,7 +205,7 @@ class BaseQuota(object): class ListQuota(command.Lister, BaseQuota): _description = _( "List quotas for all projects with non-default quota values or " - "list detailed quota informations for requested project") + "list detailed quota information for requested project") def _get_detailed_quotas(self, parsed_args): columns = ( @@ -230,7 +230,7 @@ class ListQuota(command.Lister, BaseQuota): result = [] for resource, values in quotas.items(): # NOTE(slaweq): there is no detailed quotas info for some resources - # and it should't be displayed here + # and it shouldn't be displayed here if type(values) is dict: result.append({ 'resource': resource, @@ -533,7 +533,8 @@ class SetQuota(common.NetDetectionMixin, command.Command): parser.add_argument( '--force', action='store_true', - help=_('Force quota update (only supported by compute)') + help=_('Force quota update (only supported by compute and ' + 'network)') ) parser.add_argument( '--check-limit', @@ -569,6 +570,8 @@ class SetQuota(common.NetDetectionMixin, command.Command): network_kwargs = {} if parsed_args.check_limit: network_kwargs['check_limit'] = True + if parsed_args.force: + network_kwargs['force'] = True if self.app.client_manager.is_network_endpoint_enabled(): for k, v in NETWORK_QUOTAS.items(): diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index a46fb90..b02ee6f 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2455,6 +2455,30 @@ class ListServer(command.Lister): if c in ('Created At', 'created_at'): columns += ('created',) column_headers += ('Created At',) + if c in ('Security Groups', 'security_groups'): + columns += ('security_groups_name',) + column_headers += ('Security Groups',) + if c in ("Task State", "task_state"): + columns += ('OS-EXT-STS:task_state',) + column_headers += ('Task State',) + if c in ("Power State", "power_state"): + columns += ('OS-EXT-STS:power_state',) + column_headers += ('Power State',) + if c in ("Image ID", "image_id"): + columns += ('Image ID',) + column_headers += ('Image ID',) + if c in ("Flavor ID", "flavor_id"): + columns += ('Flavor ID',) + column_headers += ('Flavor ID',) + if c in ('Availability Zone', "availability_zone"): + columns += ('OS-EXT-AZ:availability_zone',) + column_headers += ('Availability Zone',) + if c in ('Host', "host"): + columns += ('OS-EXT-SRV-ATTR:host',) + column_headers += ('Host',) + if c in ('Properties', "properties"): + columns += ('Metadata',) + column_headers += ('Properties',) # convert back to tuple column_headers = tuple(column_headers) @@ -2534,7 +2558,7 @@ class ListServer(command.Lister): # and 'image' missing in the server response during # infrastructure failure situations. # For those servers with partial constructs we just skip the - # processing of the image and flavor informations. + # processing of the image and flavor information. if not hasattr(s, 'image') or not hasattr(s, 'flavor'): continue @@ -2559,6 +2583,28 @@ class ListServer(command.Lister): else: s.flavor_name = s.flavor['original_name'] + # Add a list with security group name as attribute + for s in data: + if hasattr(s, 'security_groups'): + s.security_groups_name = [x["name"] for x in s.security_groups] + else: + s.security_groups_name = [] + + # The host_status field contains the status of the compute host the + # server is on. It is only returned by the API when the nova-api + # policy allows. Users can look at the host_status field when, for + # example, their server has status ACTIVE but is unresponsive. The + # host_status field can indicate a possible problem on the host + # it's on, providing useful information to a user in this + # situation. + if ( + compute_client.api_version >= api_versions.APIVersion('2.16') and + parsed_args.long + ): + if any([hasattr(s, 'host_status') for s in data]): + columns += ('Host Status',) + column_headers += ('Host Status',) + table = ( column_headers, ( @@ -2574,6 +2620,7 @@ class ListServer(command.Lister): 'OS-EXT-STS:power_state': PowerStateColumn, 'networks': format_columns.DictListColumn, 'metadata': format_columns.DictColumn, + 'security_groups_name': format_columns.ListColumn, }, ) for s in data ), @@ -2811,372 +2858,6 @@ revert to release the new server and restart the old one.""") raise SystemExit -class ListMigration(command.Lister): - _description = _("""List server migrations""") - - def get_parser(self, prog_name): - parser = super(ListMigration, self).get_parser(prog_name) - parser.add_argument( - '--server', - metavar='<server>', - help=_( - 'Filter migrations by server (name or ID)' - ) - ) - parser.add_argument( - '--host', - metavar='<host>', - help=_( - 'Filter migrations by source or destination host' - ), - ) - parser.add_argument( - '--status', - metavar='<status>', - help=_('Filter migrations by status') - ) - parser.add_argument( - '--type', - metavar='<type>', - choices=[ - 'evacuation', 'live-migration', 'cold-migration', 'resize', - ], - help=_('Filter migrations by type'), - ) - parser.add_argument( - '--marker', - metavar='<marker>', - help=_( - "The last migration of the previous page; displays list " - "of migrations after 'marker'. Note that the marker is " - "the migration UUID. " - "(supported with --os-compute-api-version 2.59 or above)" - ), - ) - parser.add_argument( - '--limit', - metavar='<limit>', - type=int, - help=_( - "Maximum number of migrations to display. Note that there " - "is a configurable max limit on the server, and the limit " - "that is used will be the minimum of what is requested " - "here and what is configured in the server. " - "(supported with --os-compute-api-version 2.59 or above)" - ), - ) - parser.add_argument( - '--changes-since', - dest='changes_since', - metavar='<changes-since>', - help=_( - "List only migrations changed later or equal to a certain " - "point of time. The provided time should be an ISO 8061 " - "formatted time, e.g. ``2016-03-04T06:27:59Z``. " - "(supported with --os-compute-api-version 2.59 or above)" - ), - ) - parser.add_argument( - '--changes-before', - dest='changes_before', - metavar='<changes-before>', - help=_( - "List only migrations changed earlier or equal to a " - "certain point of time. The provided time should be an ISO " - "8061 formatted time, e.g. ``2016-03-04T06:27:59Z``. " - "(supported with --os-compute-api-version 2.66 or above)" - ), - ) - parser.add_argument( - '--project', - metavar='<project>', - help=_( - "Filter migrations by project (name or ID) " - "(supported with --os-compute-api-version 2.80 or above)" - ), - ) - identity_common.add_project_domain_option_to_parser(parser) - parser.add_argument( - '--user', - metavar='<user>', - help=_( - "Filter migrations by user (name or ID) " - "(supported with --os-compute-api-version 2.80 or above)" - ), - ) - identity_common.add_user_domain_option_to_parser(parser) - return parser - - def print_migrations(self, parsed_args, compute_client, migrations): - column_headers = [ - 'Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', - 'Dest Host', 'Status', 'Server UUID', 'Old Flavor', 'New Flavor', - 'Created At', 'Updated At', - ] - - # Response fields coming back from the REST API are not always exactly - # the same as the column header names. - columns = [ - 'source_node', 'dest_node', 'source_compute', 'dest_compute', - 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id', - 'new_instance_type_id', 'created_at', 'updated_at', - ] - - # Insert migrations UUID after ID - if compute_client.api_version >= api_versions.APIVersion("2.59"): - column_headers.insert(0, "UUID") - columns.insert(0, "uuid") - - if compute_client.api_version >= api_versions.APIVersion("2.23"): - column_headers.insert(0, "Id") - columns.insert(0, "id") - column_headers.insert(len(column_headers) - 2, "Type") - columns.insert(len(columns) - 2, "migration_type") - - if compute_client.api_version >= api_versions.APIVersion("2.80"): - if parsed_args.project: - column_headers.insert(len(column_headers) - 2, "Project") - columns.insert(len(columns) - 2, "project_id") - if parsed_args.user: - column_headers.insert(len(column_headers) - 2, "User") - columns.insert(len(columns) - 2, "user_id") - - return ( - column_headers, - (utils.get_item_properties(mig, columns) for mig in migrations), - ) - - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - identity_client = self.app.client_manager.identity - - search_opts = { - 'host': parsed_args.host, - 'status': parsed_args.status, - } - - if parsed_args.server: - search_opts['instance_uuid'] = utils.find_resource( - compute_client.servers, - parsed_args.server, - ).id - - if parsed_args.type: - migration_type = parsed_args.type - # we're using an alias because the default value is confusing - if migration_type == 'cold-migration': - migration_type = 'migration' - search_opts['migration_type'] = migration_type - - if parsed_args.marker: - if compute_client.api_version < api_versions.APIVersion('2.59'): - msg = _( - '--os-compute-api-version 2.59 or greater is required to ' - 'support the --marker option' - ) - raise exceptions.CommandError(msg) - search_opts['marker'] = parsed_args.marker - - if parsed_args.limit: - if compute_client.api_version < api_versions.APIVersion('2.59'): - msg = _( - '--os-compute-api-version 2.59 or greater is required to ' - 'support the --limit option' - ) - raise exceptions.CommandError(msg) - search_opts['limit'] = parsed_args.limit - - if parsed_args.changes_since: - if compute_client.api_version < api_versions.APIVersion('2.59'): - msg = _( - '--os-compute-api-version 2.59 or greater is required to ' - 'support the --changes-since option' - ) - raise exceptions.CommandError(msg) - search_opts['changes_since'] = parsed_args.changes_since - - if parsed_args.changes_before: - if compute_client.api_version < api_versions.APIVersion('2.66'): - msg = _( - '--os-compute-api-version 2.66 or greater is required to ' - 'support the --changes-before option' - ) - raise exceptions.CommandError(msg) - search_opts['changes_before'] = parsed_args.changes_before - - if parsed_args.project: - if compute_client.api_version < api_versions.APIVersion('2.80'): - msg = _( - '--os-compute-api-version 2.80 or greater is required to ' - 'support the --project option' - ) - raise exceptions.CommandError(msg) - - search_opts['project_id'] = identity_common.find_project( - identity_client, - parsed_args.project, - parsed_args.project_domain, - ).id - - if parsed_args.user: - if compute_client.api_version < api_versions.APIVersion('2.80'): - msg = _( - '--os-compute-api-version 2.80 or greater is required to ' - 'support the --user option' - ) - raise exceptions.CommandError(msg) - - search_opts['user_id'] = identity_common.find_user( - identity_client, - parsed_args.user, - parsed_args.user_domain, - ).id - - migrations = compute_client.migrations.list(**search_opts) - - return self.print_migrations(parsed_args, compute_client, migrations) - - -class ShowMigration(command.Command): - """Show a migration for a given server.""" - - def get_parser(self, prog_name): - parser = super().get_parser(prog_name) - parser.add_argument( - 'server', - metavar='<server>', - help=_('Server (name or ID)'), - ) - parser.add_argument( - 'migration', - metavar='<migration>', - help=_("Migration (ID)"), - ) - return parser - - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - - if compute_client.api_version < api_versions.APIVersion('2.24'): - msg = _( - '--os-compute-api-version 2.24 or greater is required to ' - 'support the server migration show command' - ) - raise exceptions.CommandError(msg) - - server = utils.find_resource( - compute_client.servers, - parsed_args.server, - ) - server_migration = compute_client.server_migrations.get( - server.id, parsed_args.migration, - ) - - columns = ( - 'ID', - 'Server UUID', - 'Status', - 'Source Compute', - 'Source Node', - 'Dest Compute', - 'Dest Host', - 'Dest Node', - 'Memory Total Bytes', - 'Memory Processed Bytes', - 'Memory Remaining Bytes', - 'Disk Total Bytes', - 'Disk Processed Bytes', - 'Disk Remaining Bytes', - 'Created At', - 'Updated At', - ) - - if compute_client.api_version >= api_versions.APIVersion('2.59'): - columns += ('UUID',) - - if compute_client.api_version >= api_versions.APIVersion('2.80'): - columns += ('User ID', 'Project ID') - - data = utils.get_item_properties(server_migration, columns) - return columns, data - - -class AbortMigration(command.Command): - """Cancel an ongoing live migration. - - This command requires ``--os-compute-api-version`` 2.24 or greater. - """ - - def get_parser(self, prog_name): - parser = super(AbortMigration, self).get_parser(prog_name) - parser.add_argument( - 'server', - metavar='<server>', - help=_('Server (name or ID)'), - ) - parser.add_argument( - 'migration', - metavar='<migration>', - help=_("Migration (ID)"), - ) - return parser - - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - - if compute_client.api_version < api_versions.APIVersion('2.24'): - msg = _( - '--os-compute-api-version 2.24 or greater is required to ' - 'support the server migration abort command' - ) - raise exceptions.CommandError(msg) - - server = utils.find_resource( - compute_client.servers, - parsed_args.server, - ) - compute_client.server_migrations.live_migration_abort( - server.id, parsed_args.migration) - - -class ForceCompleteMigration(command.Command): - """Force an ongoing live migration to complete. - - This command requires ``--os-compute-api-version`` 2.22 or greater. - """ - - def get_parser(self, prog_name): - parser = super(ForceCompleteMigration, self).get_parser(prog_name) - parser.add_argument( - 'server', - metavar='<server>', - help=_('Server (name or ID)'), - ) - parser.add_argument( - 'migration', - metavar='<migration>', - help=_('Migration (ID)') - ) - return parser - - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - - if compute_client.api_version < api_versions.APIVersion('2.22'): - msg = _( - '--os-compute-api-version 2.22 or greater is required to ' - 'support the server migration force complete command' - ) - raise exceptions.CommandError(msg) - - server = utils.find_resource( - compute_client.servers, - parsed_args.server, - ) - compute_client.server_migrations.live_migrate_force_complete( - server.id, parsed_args.migration) - - class PauseServer(command.Command): _description = _("Pause server(s)") @@ -4375,7 +4056,7 @@ class ShelveServer(command.Command): server_obj.shelve() - # if we don't hav to wait, either because it was requested explicitly + # if we don't have to wait, either because it was requested explicitly # or is required implicitly, then our job is done if not parsed_args.wait and not parsed_args.offload: return diff --git a/openstackclient/compute/v2/server_event.py b/openstackclient/compute/v2/server_event.py index 1b971e5..ebf0d52 100644 --- a/openstackclient/compute/v2/server_event.py +++ b/openstackclient/compute/v2/server_event.py @@ -22,6 +22,7 @@ from novaclient import api_versions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from oslo_utils import uuidutils from openstackclient.i18n import _ @@ -33,7 +34,7 @@ class ListServerEvent(command.Lister): """List recent events of a server. Specify ``--os-compute-api-version 2.21`` or higher to show events for a - deleted server. + deleted server, specified by ID only. """ def get_parser(self, prog_name): @@ -143,9 +144,18 @@ class ListServerEvent(command.Lister): kwargs['changes_before'] = parsed_args.changes_before - server_id = utils.find_resource( - compute_client.servers, parsed_args.server, - ).id + try: + server_id = utils.find_resource( + compute_client.servers, parsed_args.server, + ).id + except exceptions.CommandError: + # If we fail to find the resource, it is possible the server is + # deleted. Try once more using the <server> arg directly if it is a + # UUID. + if uuidutils.is_uuid_like(parsed_args.server): + server_id = parsed_args.server + else: + raise data = compute_client.instance_action.list(server_id, **kwargs) @@ -184,8 +194,9 @@ class ShowServerEvent(command.ShowOne): """Show server event details. Specify ``--os-compute-api-version 2.21`` or higher to show event details - for a deleted server. Specify ``--os-compute-api-version 2.51`` or higher - to show event details for non-admin users. + for a deleted server, specified by ID only. Specify + ``--os-compute-api-version 2.51`` or higher to show event details for + non-admin users. """ def get_parser(self, prog_name): @@ -205,9 +216,18 @@ class ShowServerEvent(command.ShowOne): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - server_id = utils.find_resource( - compute_client.servers, parsed_args.server, - ).id + try: + server_id = utils.find_resource( + compute_client.servers, parsed_args.server, + ).id + except exceptions.CommandError: + # If we fail to find the resource, it is possible the server is + # deleted. Try once more using the <server> arg directly if it is a + # UUID. + if uuidutils.is_uuid_like(parsed_args.server): + server_id = parsed_args.server + else: + raise action_detail = compute_client.instance_action.get( server_id, parsed_args.request_id diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index 32dd193..eadc3ff 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -17,7 +17,7 @@ import logging -from novaclient import api_versions +from openstack import utils as sdk_utils from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command @@ -31,19 +31,24 @@ LOG = logging.getLogger(__name__) _formatters = { - 'members': format_columns.ListColumn, + 'member_ids': format_columns.ListColumn, 'policies': format_columns.ListColumn, 'rules': format_columns.DictColumn, } -def _get_columns(info): - columns = list(info.keys()) - if 'metadata' in columns: - # NOTE(RuiChen): The metadata of server group is always empty since API - # compatible, so hide it in order to avoid confusion. - columns.remove('metadata') - return tuple(sorted(columns)) +def _get_server_group_columns(item, client): + column_map = {'member_ids': 'members'} + hidden_columns = ['metadata', 'location'] + + if sdk_utils.supports_microversion(client, '2.64'): + hidden_columns.append('policies') + else: + hidden_columns.append('policy') + hidden_columns.append('rules') + + return utils.get_osc_show_columns_for_sdk_resource( + item, column_map, hidden_columns) class CreateServerGroup(command.ShowOne): @@ -54,7 +59,7 @@ class CreateServerGroup(command.ShowOne): parser.add_argument( 'name', metavar='<name>', - help=_("New server group name") + help=_("New server group name"), ) parser.add_argument( '--policy', @@ -87,11 +92,10 @@ class CreateServerGroup(command.ShowOne): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - info = {} + compute_client = self.app.client_manager.sdk_connection.compute if parsed_args.policy in ('soft-affinity', 'soft-anti-affinity'): - if compute_client.api_version < api_versions.APIVersion('2.15'): + if not sdk_utils.supports_microversion(compute_client, '2.15'): msg = _( '--os-compute-api-version 2.15 or greater is required to ' 'support the %s policy' @@ -99,30 +103,39 @@ class CreateServerGroup(command.ShowOne): raise exceptions.CommandError(msg % parsed_args.policy) if parsed_args.rules: - if compute_client.api_version < api_versions.APIVersion('2.64'): + if not sdk_utils.supports_microversion(compute_client, '2.64'): msg = _( '--os-compute-api-version 2.64 or greater is required to ' 'support the --rule option' ) raise exceptions.CommandError(msg) - if compute_client.api_version < api_versions.APIVersion('2.64'): - kwargs = {'policies': [parsed_args.policy]} + if not sdk_utils.supports_microversion(compute_client, '2.64'): + kwargs = { + 'name': parsed_args.name, + 'policies': [parsed_args.policy], + } else: kwargs = { + 'name': parsed_args.name, 'policy': parsed_args.policy, - 'rules': parsed_args.rules or None, } - server_group = compute_client.server_groups.create( - name=parsed_args.name, **kwargs) + if parsed_args.rules: + kwargs['rules'] = parsed_args.rules - info.update(server_group._info) + server_group = compute_client.create_server_group(**kwargs) - columns = _get_columns(info) - data = utils.get_dict_properties( - info, columns, formatters=_formatters) - return columns, data + display_columns, columns = _get_server_group_columns( + server_group, + compute_client, + ) + data = utils.get_item_properties( + server_group, + columns, + formatters=_formatters, + ) + return display_columns, data class DeleteServerGroup(command.Command): @@ -134,18 +147,17 @@ class DeleteServerGroup(command.Command): 'server_group', metavar='<server-group>', nargs='+', - help=_("server group(s) to delete (name or ID)") + help=_("server group(s) to delete (name or ID)"), ) return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute result = 0 for group in parsed_args.server_group: try: - group_obj = utils.find_resource(compute_client.server_groups, - group) - compute_client.server_groups.delete(group_obj.id) + group_obj = compute_client.find_server_group(group) + compute_client.delete_server_group(group_obj.id) # Catch all exceptions in order to avoid to block the next deleting except Exception as e: result += 1 @@ -169,13 +181,13 @@ class ListServerGroup(command.Lister): '--all-projects', action='store_true', default=False, - help=_("Display information from all projects (admin only)") + help=_("Display information from all projects (admin only)"), ) parser.add_argument( '--long', action='store_true', default=False, - help=_("List additional fields in output") + help=_("List additional fields in output"), ) # TODO(stephenfin): This should really be a --marker option, but alas # the API doesn't support that for some reason @@ -204,7 +216,7 @@ class ListServerGroup(command.Lister): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute kwargs = {} @@ -217,10 +229,10 @@ class ListServerGroup(command.Lister): if parsed_args.limit: kwargs['limit'] = parsed_args.limit - data = compute_client.server_groups.list(**kwargs) + data = compute_client.server_groups(**kwargs) policy_key = 'Policies' - if compute_client.api_version >= api_versions.APIVersion("2.64"): + if sdk_utils.supports_microversion(compute_client, '2.64'): policy_key = 'Policy' columns = ( @@ -235,7 +247,7 @@ class ListServerGroup(command.Lister): ) if parsed_args.long: columns += ( - 'members', + 'member_ids', 'project_id', 'user_id', ) @@ -263,17 +275,18 @@ class ShowServerGroup(command.ShowOne): parser.add_argument( 'server_group', metavar='<server-group>', - help=_("server group to display (name or ID)") + help=_("server group to display (name or ID)"), ) return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - group = utils.find_resource(compute_client.server_groups, - parsed_args.server_group) - info = {} - info.update(group._info) - columns = _get_columns(info) - data = utils.get_dict_properties( - info, columns, formatters=_formatters) - return columns, data + compute_client = self.app.client_manager.sdk_connection.compute + group = compute_client.find_server_group(parsed_args.server_group) + display_columns, columns = _get_server_group_columns( + group, + compute_client, + ) + data = utils.get_item_properties( + group, columns, formatters=_formatters + ) + return display_columns, data diff --git a/openstackclient/compute/v2/server_migration.py b/openstackclient/compute/v2/server_migration.py new file mode 100644 index 0000000..919b67b --- /dev/null +++ b/openstackclient/compute/v2/server_migration.py @@ -0,0 +1,485 @@ +# 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. + +"""Compute v2 Server Migration action implementations""" + +import uuid + +from novaclient import api_versions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common + + +class ListMigration(command.Lister): + _description = _("""List server migrations""") + + def get_parser(self, prog_name): + parser = super(ListMigration, self).get_parser(prog_name) + parser.add_argument( + '--server', + metavar='<server>', + help=_( + 'Filter migrations by server (name or ID)' + ) + ) + parser.add_argument( + '--host', + metavar='<host>', + help=_( + 'Filter migrations by source or destination host' + ), + ) + parser.add_argument( + '--status', + metavar='<status>', + help=_('Filter migrations by status') + ) + parser.add_argument( + '--type', + metavar='<type>', + choices=[ + 'evacuation', 'live-migration', 'cold-migration', 'resize', + ], + help=_('Filter migrations by type'), + ) + parser.add_argument( + '--marker', + metavar='<marker>', + help=_( + "The last migration of the previous page; displays list " + "of migrations after 'marker'. Note that the marker is " + "the migration UUID. " + "(supported with --os-compute-api-version 2.59 or above)" + ), + ) + parser.add_argument( + '--limit', + metavar='<limit>', + type=int, + help=_( + "Maximum number of migrations to display. Note that there " + "is a configurable max limit on the server, and the limit " + "that is used will be the minimum of what is requested " + "here and what is configured in the server. " + "(supported with --os-compute-api-version 2.59 or above)" + ), + ) + parser.add_argument( + '--changes-since', + dest='changes_since', + metavar='<changes-since>', + help=_( + "List only migrations changed later or equal to a certain " + "point of time. The provided time should be an ISO 8061 " + "formatted time, e.g. ``2016-03-04T06:27:59Z``. " + "(supported with --os-compute-api-version 2.59 or above)" + ), + ) + parser.add_argument( + '--changes-before', + dest='changes_before', + metavar='<changes-before>', + help=_( + "List only migrations changed earlier or equal to a " + "certain point of time. The provided time should be an ISO " + "8061 formatted time, e.g. ``2016-03-04T06:27:59Z``. " + "(supported with --os-compute-api-version 2.66 or above)" + ), + ) + parser.add_argument( + '--project', + metavar='<project>', + help=_( + "Filter migrations by project (name or ID) " + "(supported with --os-compute-api-version 2.80 or above)" + ), + ) + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--user', + metavar='<user>', + help=_( + "Filter migrations by user (name or ID) " + "(supported with --os-compute-api-version 2.80 or above)" + ), + ) + identity_common.add_user_domain_option_to_parser(parser) + return parser + + def print_migrations(self, parsed_args, compute_client, migrations): + column_headers = [ + 'Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', + 'Dest Host', 'Status', 'Server UUID', 'Old Flavor', 'New Flavor', + 'Created At', 'Updated At', + ] + + # Response fields coming back from the REST API are not always exactly + # the same as the column header names. + columns = [ + 'source_node', 'dest_node', 'source_compute', 'dest_compute', + 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id', + 'new_instance_type_id', 'created_at', 'updated_at', + ] + + # Insert migrations UUID after ID + if compute_client.api_version >= api_versions.APIVersion("2.59"): + column_headers.insert(0, "UUID") + columns.insert(0, "uuid") + + if compute_client.api_version >= api_versions.APIVersion("2.23"): + column_headers.insert(0, "Id") + columns.insert(0, "id") + column_headers.insert(len(column_headers) - 2, "Type") + columns.insert(len(columns) - 2, "migration_type") + + if compute_client.api_version >= api_versions.APIVersion("2.80"): + if parsed_args.project: + column_headers.insert(len(column_headers) - 2, "Project") + columns.insert(len(columns) - 2, "project_id") + if parsed_args.user: + column_headers.insert(len(column_headers) - 2, "User") + columns.insert(len(columns) - 2, "user_id") + + return ( + column_headers, + (utils.get_item_properties(mig, columns) for mig in migrations), + ) + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + identity_client = self.app.client_manager.identity + + search_opts = { + 'host': parsed_args.host, + 'status': parsed_args.status, + } + + if parsed_args.server: + search_opts['instance_uuid'] = utils.find_resource( + compute_client.servers, + parsed_args.server, + ).id + + if parsed_args.type: + migration_type = parsed_args.type + # we're using an alias because the default value is confusing + if migration_type == 'cold-migration': + migration_type = 'migration' + search_opts['migration_type'] = migration_type + + if parsed_args.marker: + if compute_client.api_version < api_versions.APIVersion('2.59'): + msg = _( + '--os-compute-api-version 2.59 or greater is required to ' + 'support the --marker option' + ) + raise exceptions.CommandError(msg) + search_opts['marker'] = parsed_args.marker + + if parsed_args.limit: + if compute_client.api_version < api_versions.APIVersion('2.59'): + msg = _( + '--os-compute-api-version 2.59 or greater is required to ' + 'support the --limit option' + ) + raise exceptions.CommandError(msg) + search_opts['limit'] = parsed_args.limit + + if parsed_args.changes_since: + if compute_client.api_version < api_versions.APIVersion('2.59'): + msg = _( + '--os-compute-api-version 2.59 or greater is required to ' + 'support the --changes-since option' + ) + raise exceptions.CommandError(msg) + search_opts['changes_since'] = parsed_args.changes_since + + if parsed_args.changes_before: + if compute_client.api_version < api_versions.APIVersion('2.66'): + msg = _( + '--os-compute-api-version 2.66 or greater is required to ' + 'support the --changes-before option' + ) + raise exceptions.CommandError(msg) + search_opts['changes_before'] = parsed_args.changes_before + + if parsed_args.project: + if compute_client.api_version < api_versions.APIVersion('2.80'): + msg = _( + '--os-compute-api-version 2.80 or greater is required to ' + 'support the --project option' + ) + raise exceptions.CommandError(msg) + + search_opts['project_id'] = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + + if parsed_args.user: + if compute_client.api_version < api_versions.APIVersion('2.80'): + msg = _( + '--os-compute-api-version 2.80 or greater is required to ' + 'support the --user option' + ) + raise exceptions.CommandError(msg) + + search_opts['user_id'] = identity_common.find_user( + identity_client, + parsed_args.user, + parsed_args.user_domain, + ).id + + migrations = compute_client.migrations.list(**search_opts) + + return self.print_migrations(parsed_args, compute_client, migrations) + + +def _get_migration_by_uuid(compute_client, server_id, migration_uuid): + for migration in compute_client.server_migrations.list(server_id): + if migration.uuid == migration_uuid: + return migration + break + else: + msg = _( + 'In-progress live migration %s is not found for server %s.' + ) + raise exceptions.CommandError(msg % (migration_uuid, server_id)) + + +class ShowMigration(command.ShowOne): + """Show an in-progress live migration for a given server. + + Note that it is not possible to show cold migrations or completed + live-migrations. Use 'openstack server migration list' to get details for + these. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'server', + metavar='<server>', + help=_('Server (name or ID)'), + ) + parser.add_argument( + 'migration', + metavar='<migration>', + help=_("Migration (ID)"), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + if compute_client.api_version < api_versions.APIVersion('2.24'): + msg = _( + '--os-compute-api-version 2.24 or greater is required to ' + 'support the server migration show command' + ) + raise exceptions.CommandError(msg) + + if not parsed_args.migration.isdigit(): + try: + uuid.UUID(parsed_args.migration) + except ValueError: + msg = _( + 'The <migration> argument must be an ID or UUID' + ) + raise exceptions.CommandError(msg) + + if compute_client.api_version < api_versions.APIVersion('2.59'): + msg = _( + '--os-compute-api-version 2.59 or greater is required to ' + 'retrieve server migrations by UUID' + ) + raise exceptions.CommandError(msg) + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + + # the nova API doesn't currently allow retrieval by UUID but it's a + # reasonably common operation so emulate this behavior by listing + # migrations - the responses are identical + if not parsed_args.migration.isdigit(): + server_migration = _get_migration_by_uuid( + compute_client, server.id, parsed_args.migration, + ) + else: + server_migration = compute_client.server_migrations.get( + server.id, parsed_args.migration, + ) + + columns = ( + 'ID', + 'Server UUID', + 'Status', + 'Source Compute', + 'Source Node', + 'Dest Compute', + 'Dest Host', + 'Dest Node', + 'Memory Total Bytes', + 'Memory Processed Bytes', + 'Memory Remaining Bytes', + 'Disk Total Bytes', + 'Disk Processed Bytes', + 'Disk Remaining Bytes', + 'Created At', + 'Updated At', + ) + + if compute_client.api_version >= api_versions.APIVersion('2.59'): + columns += ('UUID',) + + if compute_client.api_version >= api_versions.APIVersion('2.80'): + columns += ('User ID', 'Project ID') + + data = utils.get_item_properties(server_migration, columns) + return columns, data + + +class AbortMigration(command.Command): + """Cancel an ongoing live migration. + + This command requires ``--os-compute-api-version`` 2.24 or greater. + """ + + def get_parser(self, prog_name): + parser = super(AbortMigration, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='<server>', + help=_('Server (name or ID)'), + ) + parser.add_argument( + 'migration', + metavar='<migration>', + help=_("Migration (ID)"), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + if compute_client.api_version < api_versions.APIVersion('2.24'): + msg = _( + '--os-compute-api-version 2.24 or greater is required to ' + 'support the server migration abort command' + ) + raise exceptions.CommandError(msg) + + if not parsed_args.migration.isdigit(): + try: + uuid.UUID(parsed_args.migration) + except ValueError: + msg = _( + 'The <migration> argument must be an ID or UUID' + ) + raise exceptions.CommandError(msg) + + if compute_client.api_version < api_versions.APIVersion('2.59'): + msg = _( + '--os-compute-api-version 2.59 or greater is required to ' + 'abort server migrations by UUID' + ) + raise exceptions.CommandError(msg) + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + + # the nova API doesn't currently allow retrieval by UUID but it's a + # reasonably common operation so emulate this behavior by listing + # migrations - the responses are identical + migration_id = parsed_args.migration + if not parsed_args.migration.isdigit(): + migration_id = _get_migration_by_uuid( + compute_client, server.id, parsed_args.migration, + ).id + + compute_client.server_migrations.live_migration_abort( + server.id, migration_id, + ) + + +class ForceCompleteMigration(command.Command): + """Force an ongoing live migration to complete. + + This command requires ``--os-compute-api-version`` 2.22 or greater. + """ + + def get_parser(self, prog_name): + parser = super(ForceCompleteMigration, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='<server>', + help=_('Server (name or ID)'), + ) + parser.add_argument( + 'migration', + metavar='<migration>', + help=_('Migration (ID)') + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + if compute_client.api_version < api_versions.APIVersion('2.22'): + msg = _( + '--os-compute-api-version 2.22 or greater is required to ' + 'support the server migration force complete command' + ) + raise exceptions.CommandError(msg) + + if not parsed_args.migration.isdigit(): + try: + uuid.UUID(parsed_args.migration) + except ValueError: + msg = _( + 'The <migration> argument must be an ID or UUID' + ) + raise exceptions.CommandError(msg) + + if compute_client.api_version < api_versions.APIVersion('2.59'): + msg = _( + '--os-compute-api-version 2.59 or greater is required to ' + 'abort server migrations by UUID' + ) + raise exceptions.CommandError(msg) + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + + # the nova API doesn't currently allow retrieval by UUID but it's a + # reasonably common operation so emulate this behavior by listing + # migrations - the responses are identical + migration_id = parsed_args.migration + if not parsed_args.migration.isdigit(): + migration_id = _get_migration_by_uuid( + compute_client, server.id, parsed_args.migration, + ).id + + compute_client.server_migrations.live_migrate_force_complete( + server.id, migration_id, + ) diff --git a/openstackclient/identity/v3/endpoint_group.py b/openstackclient/identity/v3/endpoint_group.py index cbe27ed..9bb026a 100644 --- a/openstackclient/identity/v3/endpoint_group.py +++ b/openstackclient/identity/v3/endpoint_group.py @@ -268,7 +268,7 @@ class SetEndpointGroup(command.Command, _FiltersReader): parser.add_argument( '--name', metavar='<name>', - help=_('New enpoint group name'), + help=_('New endpoint group name'), ) parser.add_argument( '--filters', diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 407c129..2342fd3 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -21,7 +21,7 @@ import logging import os import sys -import openstack.cloud._utils +from cinderclient import api_versions from openstack.image import image_signer from osc_lib.api import utils as api_utils from osc_lib.cli import format_columns @@ -29,6 +29,7 @@ from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from oslo_utils import uuidutils from openstackclient.common import progressbar from openstackclient.i18n import _ @@ -43,8 +44,19 @@ else: CONTAINER_CHOICES = ["ami", "ari", "aki", "bare", "docker", "ova", "ovf"] DEFAULT_CONTAINER_FORMAT = 'bare' DEFAULT_DISK_FORMAT = 'raw' -DISK_CHOICES = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", "vhdx", - "vdi", "iso", "ploop"] +DISK_CHOICES = [ + "ami", + "ari", + "aki", + "vhd", + "vmdk", + "raw", + "qcow2", + "vhdx", + "vdi", + "iso", + "ploop", +] MEMBER_STATUS_CHOICES = ["accepted", "pending", "rejected", "all"] @@ -58,10 +70,25 @@ def _format_image(image, human_readable=False): properties = {} # the only fields we're not including is "links", "tags" and the properties - fields_to_show = ['status', 'name', 'container_format', 'created_at', - 'size', 'disk_format', 'updated_at', 'visibility', - 'min_disk', 'protected', 'id', 'file', 'checksum', - 'owner', 'virtual_size', 'min_ram', 'schema'] + fields_to_show = [ + 'status', + 'name', + 'container_format', + 'created_at', + 'size', + 'disk_format', + 'updated_at', + 'visibility', + 'min_disk', + 'protected', + 'id', + 'file', + 'checksum', + 'owner', + 'virtual_size', + 'min_ram', + 'schema', + ] # TODO(gtema/anybody): actually it should be possible to drop this method, # since SDK already delivers a proper object @@ -98,12 +125,12 @@ _formatters = { def _get_member_columns(item): - column_map = { - 'image_id': 'image_id' - } + column_map = {'image_id': 'image_id'} hidden_columns = ['id', 'location', 'name'] return utils.get_osc_show_columns_for_sdk_resource( - item.to_dict(), column_map, hidden_columns, + item.to_dict(), + column_map, + hidden_columns, ) @@ -141,7 +168,7 @@ class AddProjectToImage(command.ShowOne): _description = _("Associate project with image") def get_parser(self, prog_name): - parser = super(AddProjectToImage, self).get_parser(prog_name) + parser = super().get_parser(prog_name) parser.add_argument( "image", metavar="<image>", @@ -159,16 +186,18 @@ class AddProjectToImage(command.ShowOne): image_client = self.app.client_manager.image identity_client = self.app.client_manager.identity - if openstack.cloud._utils._is_uuid_like(parsed_args.project): + if uuidutils.is_uuid_like(parsed_args.project): project_id = parsed_args.project else: project_id = common.find_project( identity_client, parsed_args.project, - parsed_args.project_domain).id + parsed_args.project_domain, + ).id - image = image_client.find_image(parsed_args.image, - ignore_missing=False) + image = image_client.find_image( + parsed_args.image, ignore_missing=False + ) obj = image_client.add_member( image=image.id, @@ -187,7 +216,7 @@ class CreateImage(command.ShowOne): deadopts = ('size', 'location', 'copy-from', 'checksum', 'store') def get_parser(self, prog_name): - parser = super(CreateImage, self).get_parser(prog_name) + parser = super().get_parser(prog_name) # TODO(bunting): There are additional arguments that v1 supported # that v2 either doesn't support or supports weirdly. # --checksum - could be faked clientside perhaps? @@ -210,19 +239,28 @@ class CreateImage(command.ShowOne): default=DEFAULT_CONTAINER_FORMAT, choices=CONTAINER_CHOICES, metavar="<container-format>", - help=(_("Image container format. " + help=( + _( + "Image container format. " "The supported options are: %(option_list)s. " - "The default format is: %(default_opt)s") % - {'option_list': ', '.join(CONTAINER_CHOICES), - 'default_opt': DEFAULT_CONTAINER_FORMAT}) + "The default format is: %(default_opt)s" + ) + % { + 'option_list': ', '.join(CONTAINER_CHOICES), + 'default_opt': DEFAULT_CONTAINER_FORMAT, + } + ), ) parser.add_argument( "--disk-format", default=DEFAULT_DISK_FORMAT, choices=DISK_CHOICES, metavar="<disk-format>", - help=_("Image disk format. The supported options are: %s. " - "The default format is: raw") % ', '.join(DISK_CHOICES) + help=_( + "Image disk format. The supported options are: %s. " + "The default format is: raw" + ) + % ', '.join(DISK_CHOICES), ) parser.add_argument( "--min-disk", @@ -252,8 +290,10 @@ class CreateImage(command.ShowOne): dest='force', action='store_true', default=False, - help=_("Force image creation if volume is in use " - "(only meaningful with --volume)"), + help=_( + "Force image creation if volume is in use " + "(only meaningful with --volume)" + ), ) parser.add_argument( "--progress", @@ -265,48 +305,64 @@ class CreateImage(command.ShowOne): '--sign-key-path', metavar="<sign-key-path>", default=[], - help=_("Sign the image using the specified private key. " - "Only use in combination with --sign-cert-id") + help=_( + "Sign the image using the specified private key. " + "Only use in combination with --sign-cert-id" + ), ) parser.add_argument( '--sign-cert-id', metavar="<sign-cert-id>", default=[], - help=_("The specified certificate UUID is a reference to " - "the certificate in the key manager that corresponds " - "to the public key and is used for signature validation. " - "Only use in combination with --sign-key-path") + help=_( + "The specified certificate UUID is a reference to " + "the certificate in the key manager that corresponds " + "to the public key and is used for signature validation. " + "Only use in combination with --sign-key-path" + ), ) protected_group = parser.add_mutually_exclusive_group() protected_group.add_argument( "--protected", action="store_true", + dest="is_protected", + default=None, help=_("Prevent image from being deleted"), ) protected_group.add_argument( "--unprotected", - action="store_true", + action="store_false", + dest="is_protected", + default=None, help=_("Allow image to be deleted (default)"), ) public_group = parser.add_mutually_exclusive_group() public_group.add_argument( "--public", - action="store_true", + action="store_const", + const="public", + dest="visibility", help=_("Image is accessible to the public"), ) public_group.add_argument( "--private", - action="store_true", + action="store_const", + const="private", + dest="visibility", help=_("Image is inaccessible to the public (default)"), ) public_group.add_argument( "--community", - action="store_true", + action="store_const", + const="community", + dest="visibility", help=_("Image is accessible to the community"), ) public_group.add_argument( "--shared", - action="store_true", + action="store_const", + const="shared", + dest="visibility", help=_("Image can be shared"), ) parser.add_argument( @@ -314,16 +370,20 @@ class CreateImage(command.ShowOne): dest="properties", metavar="<key=value>", action=parseractions.KeyValueAction, - help=_("Set a property on this image " - "(repeat option to set multiple properties)"), + help=_( + "Set a property on this image " + "(repeat option to set multiple properties)" + ), ) parser.add_argument( "--tag", dest="tags", metavar="<tag>", action='append', - help=_("Set a tag on this image " - "(repeat option to set multiple tags)"), + help=_( + "Set a tag on this image " + "(repeat option to set multiple tags)" + ), ) parser.add_argument( "--project", @@ -335,8 +395,8 @@ class CreateImage(command.ShowOne): dest="use_import", action="store_true", help=_( - "Force the use of glance image import instead of" - " direct upload") + "Force the use of glance image import instead of direct upload" + ), ) common.add_project_domain_option_to_parser(parser) for deadopt in self.deadopts: @@ -348,22 +408,23 @@ class CreateImage(command.ShowOne): ) return parser - def take_action(self, parsed_args): + def _take_action_image(self, parsed_args): identity_client = self.app.client_manager.identity image_client = self.app.client_manager.image - for deadopt in self.deadopts: - if getattr(parsed_args, deadopt.replace('-', '_'), None): - raise exceptions.CommandError( - _("ERROR: --%s was given, which is an Image v1 option" - " that is no longer supported in Image v2") % deadopt) - # Build an attribute dict from the parsed args, only include # attributes that were actually set on the command line kwargs = {'allow_duplicates': True} - copy_attrs = ('name', 'id', - 'container_format', 'disk_format', - 'min_disk', 'min_ram', 'tags', 'visibility') + copy_attrs = ( + 'name', + 'id', + 'container_format', + 'disk_format', + 'min_disk', + 'min_ram', + 'tags', + 'visibility', + ) for attr in copy_attrs: if attr in parsed_args: val = getattr(parsed_args, attr, None) @@ -383,18 +444,12 @@ class CreateImage(command.ShowOne): # a single value for the pair of options because the default must be # to do nothing when no options are present as opposed to always # setting a default. - if parsed_args.protected: - kwargs['is_protected'] = True - if parsed_args.unprotected: - kwargs['is_protected'] = False - if parsed_args.public: - kwargs['visibility'] = 'public' - if parsed_args.private: - kwargs['visibility'] = 'private' - if parsed_args.community: - kwargs['visibility'] = 'community' - if parsed_args.shared: - kwargs['visibility'] = 'shared' + if parsed_args.is_protected is not None: + kwargs['is_protected'] = parsed_args.is_protected + + if parsed_args.visibility is not None: + kwargs['visibility'] = parsed_args.visibility + if parsed_args.project: kwargs['owner_id'] = common.find_project( identity_client, @@ -408,16 +463,19 @@ class CreateImage(command.ShowOne): # open the file first to ensure any failures are handled before the # image is created. Get the file name (if it is file, and not stdin) # for easier further handling. - (fp, fname) = get_data_file(parsed_args) - info = {} + fp, fname = get_data_file(parsed_args) if fp is not None and parsed_args.volume: - raise exceptions.CommandError(_("Uploading data and using " - "container are not allowed at " - "the same time")) + msg = _( + "Uploading data and using container are not allowed at " + "the same time" + ) + raise exceptions.CommandError(msg) + if fp is None and parsed_args.file: LOG.warning(_("Failed to get an image file.")) return {}, {} + if fp is not None and parsed_args.progress: filesize = os.path.getsize(fname) if filesize is not None: @@ -432,76 +490,145 @@ class CreateImage(command.ShowOne): # sign an image using a given local private key file if parsed_args.sign_key_path or parsed_args.sign_cert_id: if not parsed_args.file: - msg = (_("signing an image requires the --file option, " - "passing files via stdin when signing is not " - "supported.")) + msg = _( + "signing an image requires the --file option, " + "passing files via stdin when signing is not " + "supported." + ) raise exceptions.CommandError(msg) - if (len(parsed_args.sign_key_path) < 1 or - len(parsed_args.sign_cert_id) < 1): - msg = (_("'sign-key-path' and 'sign-cert-id' must both be " - "specified when attempting to sign an image.")) + + if ( + len(parsed_args.sign_key_path) < 1 or + len(parsed_args.sign_cert_id) < 1 + ): + msg = _( + "'sign-key-path' and 'sign-cert-id' must both be " + "specified when attempting to sign an image." + ) raise exceptions.CommandError(msg) - else: - sign_key_path = parsed_args.sign_key_path - sign_cert_id = parsed_args.sign_cert_id - signer = image_signer.ImageSigner() - try: - pw = utils.get_password( - self.app.stdin, - prompt=("Please enter private key password, leave " - "empty if none: "), - confirm=False) - - if not pw or len(pw) < 1: - pw = None - else: - # load_private_key() requires the password to be - # passed as bytes - pw = pw.encode() - - signer.load_private_key( - sign_key_path, - password=pw) - except Exception: - msg = (_("Error during sign operation: private key " - "could not be loaded.")) - raise exceptions.CommandError(msg) - - signature = signer.generate_signature(fp) - signature_b64 = b64encode(signature) - kwargs['img_signature'] = signature_b64 - kwargs['img_signature_certificate_uuid'] = sign_cert_id - kwargs['img_signature_hash_method'] = signer.hash_method - if signer.padding_method: - kwargs['img_signature_key_type'] = \ - signer.padding_method - - # If a volume is specified. - if parsed_args.volume: - volume_client = self.app.client_manager.volume - source_volume = utils.find_resource( - volume_client.volumes, - parsed_args.volume, - ) - response, body = volume_client.volumes.upload_to_image( - source_volume.id, - parsed_args.force, - parsed_args.name, - parsed_args.container_format, - parsed_args.disk_format, - visibility=kwargs.get('visibility', 'private'), - protected=True if parsed_args.protected else False - ) - info = body['os-volume_upload_image'] + + sign_key_path = parsed_args.sign_key_path + sign_cert_id = parsed_args.sign_cert_id + signer = image_signer.ImageSigner() try: - info['volume_type'] = info['volume_type']['name'] - except TypeError: - info['volume_type'] = None + pw = utils.get_password( + self.app.stdin, + prompt=( + "Please enter private key password, leave " + "empty if none: " + ), + confirm=False, + ) + + if not pw or len(pw) < 1: + pw = None + else: + # load_private_key() requires the password to be + # passed as bytes + pw = pw.encode() + + signer.load_private_key(sign_key_path, password=pw) + except Exception: + msg = _( + "Error during sign operation: private key " + "could not be loaded." + ) + raise exceptions.CommandError(msg) + + signature = signer.generate_signature(fp) + signature_b64 = b64encode(signature) + kwargs['img_signature'] = signature_b64 + kwargs['img_signature_certificate_uuid'] = sign_cert_id + kwargs['img_signature_hash_method'] = signer.hash_method + if signer.padding_method: + kwargs['img_signature_key_type'] = signer.padding_method + + image = image_client.create_image(**kwargs) + return _format_image(image) + + def _take_action_volume(self, parsed_args): + volume_client = self.app.client_manager.volume + + unsupported_opts = { + # 'name', # 'name' is a positional argument and will always exist + 'id', + 'min_disk', + 'min_ram', + 'file', + 'force', + 'progress', + 'sign_key_path', + 'sign_cert_id', + 'properties', + 'tags', + 'project', + 'use_import', + } + for unsupported_opt in unsupported_opts: + if getattr(parsed_args, unsupported_opt, None): + opt_name = unsupported_opt.replace('-', '_') + if unsupported_opt == 'use_import': + opt_name = 'import' + msg = _( + "'--%s' was given, which is not supported when " + "creating an image from a volume. " + "This will be an error in a future version." + ) + # TODO(stephenfin): These should be an error in a future + # version + LOG.warning(msg % opt_name) + + source_volume = utils.find_resource( + volume_client.volumes, + parsed_args.volume, + ) + kwargs = {} + if volume_client.api_version < api_versions.APIVersion('3.1'): + if ( + parsed_args.visibility or + parsed_args.is_protected is not None + ): + msg = _( + '--os-volume-api-version 3.1 or greater is required ' + 'to support the --public, --private, --community, ' + '--shared or --protected option.' + ) + raise exceptions.CommandError(msg) else: - image = image_client.create_image(**kwargs) + kwargs.update( + visibility=parsed_args.visibility or 'private', + protected=parsed_args.is_protected or False, + ) + + response, body = volume_client.volumes.upload_to_image( + source_volume.id, + parsed_args.force, + parsed_args.name, + parsed_args.container_format, + parsed_args.disk_format, + **kwargs + ) + info = body['os-volume_upload_image'] + try: + info['volume_type'] = info['volume_type']['name'] + except TypeError: + info['volume_type'] = None - if not info: - info = _format_image(image) + return info + + def take_action(self, parsed_args): + for deadopt in self.deadopts: + if getattr(parsed_args, deadopt.replace('-', '_'), None): + msg = _( + "ERROR: --%s was given, which is an Image v1 option " + "that is no longer supported in Image v2" + ) + raise exceptions.CommandError(msg % deadopt) + + if parsed_args.volume: + info = self._take_action_volume(parsed_args) + else: + info = self._take_action_image(parsed_args) return zip(*sorted(info.items())) @@ -510,7 +637,7 @@ class DeleteImage(command.Command): _description = _("Delete image(s)") def get_parser(self, prog_name): - parser = super(DeleteImage, self).get_parser(prog_name) + parser = super().get_parser(prog_name) parser.add_argument( "images", metavar="<image>", @@ -525,19 +652,24 @@ class DeleteImage(command.Command): image_client = self.app.client_manager.image for image in parsed_args.images: try: - image_obj = image_client.find_image(image, - ignore_missing=False) + image_obj = image_client.find_image( + image, ignore_missing=False + ) image_client.delete_image(image_obj.id) except Exception as e: del_result += 1 - LOG.error(_("Failed to delete image with name or " - "ID '%(image)s': %(e)s"), - {'image': image, 'e': e}) + msg = _( + "Failed to delete image with name or " + "ID '%(image)s': %(e)s" + ) + LOG.error(msg, {'image': image, 'e': e}) total = len(parsed_args.images) - if (del_result > 0): - msg = (_("Failed to delete %(dresult)s of %(total)s images.") - % {'dresult': del_result, 'total': total}) + if del_result > 0: + msg = _("Failed to delete %(dresult)s of %(total)s images.") % { + 'dresult': del_result, + 'total': total, + } raise exceptions.CommandError(msg) @@ -545,54 +677,63 @@ class ListImage(command.Lister): _description = _("List available images") def get_parser(self, prog_name): - parser = super(ListImage, self).get_parser(prog_name) + parser = super().get_parser(prog_name) public_group = parser.add_mutually_exclusive_group() public_group.add_argument( "--public", - dest="public", - action="store_true", - default=False, + action="store_const", + const="public", + dest="visibility", help=_("List only public images"), ) public_group.add_argument( "--private", - dest="private", - action="store_true", - default=False, + action="store_const", + const="private", + dest="visibility", help=_("List only private images"), ) public_group.add_argument( "--community", - dest="community", - action="store_true", - default=False, + action="store_const", + const="community", + dest="visibility", help=_("List only community images"), ) public_group.add_argument( "--shared", - dest="shared", - action="store_true", - default=False, + action="store_const", + const="shared", + dest="visibility", help=_("List only shared images"), ) + public_group.add_argument( + "--all", + action="store_const", + const="all", + dest="visibility", + help=_("List all images"), + ) parser.add_argument( '--property', metavar='<key=value>', action=parseractions.KeyValueAction, - help=_('Filter output based on property ' - '(repeat option to filter on multiple properties)'), + help=_( + 'Filter output based on property ' + '(repeat option to filter on multiple properties)' + ), ) parser.add_argument( '--name', metavar='<name>', default=None, - help=_("Filter images based on name.") + help=_("Filter images based on name."), ) parser.add_argument( '--status', metavar='<status>', default=None, - help=_("Filter images based on status.") + help=_("Filter images based on status."), ) parser.add_argument( '--member-status', @@ -600,25 +741,34 @@ class ListImage(command.Lister): default=None, type=lambda s: s.lower(), choices=MEMBER_STATUS_CHOICES, - help=(_("Filter images based on member status. " - "The supported options are: %s. ") % - ', '.join(MEMBER_STATUS_CHOICES)) + help=( + _( + "Filter images based on member status. " + "The supported options are: %s. " + ) + % ', '.join(MEMBER_STATUS_CHOICES) + ), ) parser.add_argument( '--project', metavar='<project>', - help=_("Search by project (admin only) (name or ID)") + help=_("Search by project (admin only) (name or ID)"), ) common.add_project_domain_option_to_parser(parser) parser.add_argument( '--tag', metavar='<tag>', - default=None, - help=_('Filter images based on tag.'), + action='append', + default=[], + help=_( + 'Filter images based on tag. ' + '(repeat option to filter on multiple tags)' + ), ) parser.add_argument( '--hidden', action='store_true', + dest='is_hidden', default=False, help=_('List hidden images'), ) @@ -640,9 +790,11 @@ class ListImage(command.Lister): '--sort', metavar="<key>[:<direction>]", default='name:asc', - help=_("Sort output by selected keys and directions(asc or desc) " - "(default: name:asc), multiple keys and directions can be " - "specified separated by comma"), + help=_( + "Sort output by selected keys and directions (asc or desc) " + "(default: name:asc), multiple keys and directions can be " + "specified separated by comma" + ), ) parser.add_argument( "--limit", @@ -654,9 +806,11 @@ class ListImage(command.Lister): '--marker', metavar='<image>', default=None, - help=_("The last image of the previous page. Display " - "list of images after marker. Display all images if not " - "specified. (name or ID)"), + help=_( + "The last image of the previous page. Display " + "list of images after marker. Display all images if not " + "specified. (name or ID)" + ), ) return parser @@ -665,14 +819,8 @@ class ListImage(command.Lister): image_client = self.app.client_manager.image kwargs = {} - if parsed_args.public: - kwargs['visibility'] = 'public' - if parsed_args.private: - kwargs['visibility'] = 'private' - if parsed_args.community: - kwargs['visibility'] = 'community' - if parsed_args.shared: - kwargs['visibility'] = 'shared' + if parsed_args.visibility is not None: + kwargs['visibility'] = parsed_args.visibility if parsed_args.limit: kwargs['limit'] = parsed_args.limit if parsed_args.marker: @@ -693,8 +841,8 @@ class ListImage(command.Lister): parsed_args.project_domain, ).id kwargs['owner'] = project_id - if parsed_args.hidden: - kwargs['is_hidden'] = True + if parsed_args.is_hidden: + kwargs['is_hidden'] = parsed_args.is_hidden if parsed_args.long: columns = ( 'ID', @@ -745,11 +893,14 @@ class ListImage(command.Lister): return ( column_headers, - (utils.get_item_properties( - s, - columns, - formatters=_formatters, - ) for s in data) + ( + utils.get_item_properties( + s, + columns, + formatters=_formatters, + ) + for s in data + ), ) @@ -757,7 +908,7 @@ class ListImageProjects(command.Lister): _description = _("List projects associated with image") def get_parser(self, prog_name): - parser = super(ListImageProjects, self).get_parser(prog_name) + parser = super().get_parser(prog_name) parser.add_argument( "image", metavar="<image>", @@ -768,27 +919,29 @@ class ListImageProjects(command.Lister): def take_action(self, parsed_args): image_client = self.app.client_manager.image - columns = ( - "Image ID", - "Member ID", - "Status" - ) + columns = ("Image ID", "Member ID", "Status") image_id = image_client.find_image(parsed_args.image).id data = image_client.members(image=image_id) - return (columns, - (utils.get_item_properties( - s, columns, - ) for s in data)) + return ( + columns, + ( + utils.get_item_properties( + s, + columns, + ) + for s in data + ), + ) class RemoveProjectImage(command.Command): _description = _("Disassociate project with image") def get_parser(self, prog_name): - parser = super(RemoveProjectImage, self).get_parser(prog_name) + parser = super().get_parser(prog_name) parser.add_argument( "image", metavar="<image>", @@ -806,23 +959,22 @@ class RemoveProjectImage(command.Command): image_client = self.app.client_manager.image identity_client = self.app.client_manager.identity - project_id = common.find_project(identity_client, - parsed_args.project, - parsed_args.project_domain).id + project_id = common.find_project( + identity_client, parsed_args.project, parsed_args.project_domain + ).id - image = image_client.find_image(parsed_args.image, - ignore_missing=False) + image = image_client.find_image( + parsed_args.image, ignore_missing=False + ) - image_client.remove_member( - member=project_id, - image=image.id) + image_client.remove_member(member=project_id, image=image.id) class SaveImage(command.Command): _description = _("Save an image locally") def get_parser(self, prog_name): - parser = super(SaveImage, self).get_parser(prog_name) + parser = super().get_parser(prog_name) parser.add_argument( "--file", metavar="<filename>", @@ -852,7 +1004,7 @@ class SetImage(command.Command): deadopts = ('visibility',) def get_parser(self, prog_name): - parser = super(SetImage, self).get_parser(prog_name) + parser = super().get_parser(prog_name) # TODO(bunting): There are additional arguments that v1 supported # --size - does not exist in v2 # --store - does not exist in v2 @@ -864,20 +1016,16 @@ class SetImage(command.Command): # --checksum - maybe could be done client side # --stdin - could be implemented parser.add_argument( - "image", - metavar="<image>", - help=_("Image to modify (name or ID)") + "image", metavar="<image>", help=_("Image to modify (name or ID)") ) parser.add_argument( - "--name", - metavar="<name>", - help=_("New image name") + "--name", metavar="<name>", help=_("New image name") ) parser.add_argument( "--min-disk", type=int, metavar="<disk-gb>", - help=_("Minimum disk size needed to boot image, in gigabytes") + help=_("Minimum disk size needed to boot image, in gigabytes"), ) parser.add_argument( "--min-ram", @@ -889,46 +1037,58 @@ class SetImage(command.Command): "--container-format", metavar="<container-format>", choices=CONTAINER_CHOICES, - help=_("Image container format. The supported options are: %s") % - ', '.join(CONTAINER_CHOICES) + help=_("Image container format. The supported options are: %s") + % ', '.join(CONTAINER_CHOICES), ) parser.add_argument( "--disk-format", metavar="<disk-format>", choices=DISK_CHOICES, - help=_("Image disk format. The supported options are: %s") % - ', '.join(DISK_CHOICES) + help=_("Image disk format. The supported options are: %s") + % ', '.join(DISK_CHOICES), ) protected_group = parser.add_mutually_exclusive_group() protected_group.add_argument( "--protected", action="store_true", + dest="is_protected", + default=None, help=_("Prevent image from being deleted"), ) protected_group.add_argument( "--unprotected", - action="store_true", + action="store_false", + dest="is_protected", + default=None, help=_("Allow image to be deleted (default)"), ) public_group = parser.add_mutually_exclusive_group() public_group.add_argument( "--public", - action="store_true", + action="store_const", + const="public", + dest="visibility", help=_("Image is accessible to the public"), ) public_group.add_argument( "--private", - action="store_true", + action="store_const", + const="private", + dest="visibility", help=_("Image is inaccessible to the public (default)"), ) public_group.add_argument( "--community", - action="store_true", + action="store_const", + const="community", + dest="visibility", help=_("Image is accessible to the community"), ) public_group.add_argument( "--shared", - action="store_true", + action="store_const", + const="shared", + dest="visibility", help=_("Image can be shared"), ) parser.add_argument( @@ -936,8 +1096,10 @@ class SetImage(command.Command): dest="properties", metavar="<key=value>", action=parseractions.KeyValueAction, - help=_("Set a property on this image " - "(repeat option to set multiple properties)"), + help=_( + "Set a property on this image " + "(repeat option to set multiple properties)" + ), ) parser.add_argument( "--tag", @@ -945,8 +1107,10 @@ class SetImage(command.Command): metavar="<tag>", default=None, action='append', - help=_("Set a tag on this image " - "(repeat option to set multiple tags)"), + help=_( + "Set a tag on this image " + "(repeat option to set multiple tags)" + ), ) parser.add_argument( "--architecture", @@ -1005,7 +1169,7 @@ class SetImage(command.Command): parser.add_argument( "--%s" % deadopt, metavar="<%s>" % deadopt, - dest=deadopt.replace('-', '_'), + dest=f"dead_{deadopt.replace('-', '_')}", help=argparse.SUPPRESS, ) @@ -1038,14 +1202,14 @@ class SetImage(command.Command): hidden_group = parser.add_mutually_exclusive_group() hidden_group.add_argument( "--hidden", - dest='hidden', + dest="is_hidden", default=None, action="store_true", help=_("Hide the image"), ) hidden_group.add_argument( "--unhidden", - dest='hidden', + dest="is_hidden", default=None, action="store_false", help=_("Unhide the image"), @@ -1057,13 +1221,18 @@ class SetImage(command.Command): image_client = self.app.client_manager.image for deadopt in self.deadopts: - if getattr(parsed_args, deadopt.replace('-', '_'), None): + if getattr(parsed_args, f"dead_{deadopt.replace('-', '_')}", None): raise exceptions.CommandError( - _("ERROR: --%s was given, which is an Image v1 option" - " that is no longer supported in Image v2") % deadopt) + _( + "ERROR: --%s was given, which is an Image v1 option" + " that is no longer supported in Image v2" + ) + % deadopt + ) image = image_client.find_image( - parsed_args.image, ignore_missing=False, + parsed_args.image, + ignore_missing=False, ) project_id = None if parsed_args.project: @@ -1100,10 +1269,25 @@ class SetImage(command.Command): # handle everything else kwargs = {} - copy_attrs = ('architecture', 'container_format', 'disk_format', - 'file', 'instance_id', 'kernel_id', 'locations', - 'min_disk', 'min_ram', 'name', 'os_distro', 'os_version', - 'prefix', 'progress', 'ramdisk_id', 'tags', 'visibility') + copy_attrs = ( + 'architecture', + 'container_format', + 'disk_format', + 'file', + 'instance_id', + 'kernel_id', + 'locations', + 'min_disk', + 'min_ram', + 'name', + 'os_distro', + 'os_version', + 'prefix', + 'progress', + 'ramdisk_id', + 'tags', + 'visibility', + ) for attr in copy_attrs: if attr in parsed_args: val = getattr(parsed_args, attr, None) @@ -1123,33 +1307,31 @@ class SetImage(command.Command): # a single value for the pair of options because the default must be # to do nothing when no options are present as opposed to always # setting a default. - if parsed_args.protected: - kwargs['is_protected'] = True - if parsed_args.unprotected: - kwargs['is_protected'] = False - if parsed_args.public: - kwargs['visibility'] = 'public' - if parsed_args.private: - kwargs['visibility'] = 'private' - if parsed_args.community: - kwargs['visibility'] = 'community' - if parsed_args.shared: - kwargs['visibility'] = 'shared' + if parsed_args.is_protected is not None: + kwargs['is_protected'] = parsed_args.is_protected + + if parsed_args.visibility is not None: + kwargs['visibility'] = parsed_args.visibility + if parsed_args.project: # We already did the project lookup above kwargs['owner_id'] = project_id + if parsed_args.tags: # Tags should be extended, but duplicates removed kwargs['tags'] = list(set(image.tags).union(set(parsed_args.tags))) - if parsed_args.hidden is not None: - kwargs['is_hidden'] = parsed_args.hidden + + if parsed_args.is_hidden is not None: + kwargs['is_hidden'] = parsed_args.is_hidden try: image = image_client.update_image(image.id, **kwargs) except Exception: if activation_status is not None: - LOG.info(_("Image %(id)s was %(status)s."), - {'id': image.id, 'status': activation_status}) + LOG.info( + _("Image %(id)s was %(status)s."), + {'id': image.id, 'status': activation_status}, + ) raise @@ -1157,7 +1339,7 @@ class ShowImage(command.ShowOne): _description = _("Display image details") def get_parser(self, prog_name): - parser = super(ShowImage, self).get_parser(prog_name) + parser = super().get_parser(prog_name) parser.add_argument( "--human-readable", default=False, @@ -1174,8 +1356,9 @@ class ShowImage(command.ShowOne): def take_action(self, parsed_args): image_client = self.app.client_manager.image - image = image_client.find_image(parsed_args.image, - ignore_missing=False) + image = image_client.find_image( + parsed_args.image, ignore_missing=False + ) info = _format_image(image, parsed_args.human_readable) return zip(*sorted(info.items())) @@ -1185,7 +1368,7 @@ class UnsetImage(command.Command): _description = _("Unset image tags and properties") def get_parser(self, prog_name): - parser = super(UnsetImage, self).get_parser(prog_name) + parser = super().get_parser(prog_name) parser.add_argument( "image", metavar="<image>", @@ -1197,8 +1380,10 @@ class UnsetImage(command.Command): metavar="<tag>", default=[], action='append', - help=_("Unset a tag on this image " - "(repeat option to unset multiple tags)"), + help=_( + "Unset a tag on this image " + "(repeat option to unset multiple tags)" + ), ) parser.add_argument( "--property", @@ -1206,15 +1391,18 @@ class UnsetImage(command.Command): metavar="<property-key>", default=[], action='append', - help=_("Unset a property on this image " - "(repeat option to unset multiple properties)"), + help=_( + "Unset a property on this image " + "(repeat option to unset multiple properties)" + ), ) return parser def take_action(self, parsed_args): image_client = self.app.client_manager.image - image = image_client.find_image(parsed_args.image, - ignore_missing=False) + image = image_client.find_image( + parsed_args.image, ignore_missing=False + ) kwargs = {} tagret = 0 @@ -1224,8 +1412,9 @@ class UnsetImage(command.Command): try: image_client.remove_tag(image.id, k) except Exception: - LOG.error(_("tag unset failed, '%s' is a " - "nonexistent tag "), k) + LOG.error( + _("tag unset failed, '%s' is a " "nonexistent tag "), k + ) tagret += 1 if parsed_args.properties: @@ -1237,35 +1426,46 @@ class UnsetImage(command.Command): # pass modified properties object, so that SDK can figure # out, what was changed inside # NOTE: ping gtema to improve that in SDK - new_props = kwargs.get('properties', - image.get('properties').copy()) + new_props = kwargs.get( + 'properties', image.get('properties').copy() + ) new_props.pop(k, None) kwargs['properties'] = new_props else: - LOG.error(_("property unset failed, '%s' is a " - "nonexistent property "), k) + LOG.error( + _( + "property unset failed, '%s' is a " + "nonexistent property " + ), + k, + ) propret += 1 # We must give to update a current image for the reference on what # has changed - image_client.update_image( - image, - **kwargs) + image_client.update_image(image, **kwargs) tagtotal = len(parsed_args.tags) proptotal = len(parsed_args.properties) - if (tagret > 0 and propret > 0): - msg = (_("Failed to unset %(tagret)s of %(tagtotal)s tags," - "Failed to unset %(propret)s of %(proptotal)s properties.") - % {'tagret': tagret, 'tagtotal': tagtotal, - 'propret': propret, 'proptotal': proptotal}) + if tagret > 0 and propret > 0: + msg = _( + "Failed to unset %(tagret)s of %(tagtotal)s tags," + "Failed to unset %(propret)s of %(proptotal)s properties." + ) % { + 'tagret': tagret, + 'tagtotal': tagtotal, + 'propret': propret, + 'proptotal': proptotal, + } raise exceptions.CommandError(msg) elif tagret > 0: - msg = (_("Failed to unset %(tagret)s of %(tagtotal)s tags.") - % {'tagret': tagret, 'tagtotal': tagtotal}) + msg = _("Failed to unset %(tagret)s of %(tagtotal)s tags.") % { + 'tagret': tagret, + 'tagtotal': tagtotal, + } raise exceptions.CommandError(msg) elif propret > 0: - msg = (_("Failed to unset %(propret)s of %(proptotal)s" - " properties.") - % {'propret': propret, 'proptotal': proptotal}) + msg = _( + "Failed to unset %(propret)s of %(proptotal)s" " properties." + ) % {'propret': propret, 'proptotal': proptotal} raise exceptions.CommandError(msg) diff --git a/openstackclient/network/v2/address_group.py b/openstackclient/network/v2/address_group.py index b08fa87..b22fd8a 100644 --- a/openstackclient/network/v2/address_group.py +++ b/openstackclient/network/v2/address_group.py @@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__) def _get_columns(item): column_map = {} - hidden_columns = ['location'] + hidden_columns = ['location', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index 488e160..91f581b 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -30,7 +30,7 @@ def _get_columns(item): column_map = { 'is_shared': 'shared', } - hidden_columns = ['location'] + hidden_columns = ['location', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 4c03074..22096bc 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -28,7 +28,7 @@ _formatters = { def _get_network_columns(item): column_map = {} - hidden_columns = ['location'] + hidden_columns = ['location', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, diff --git a/openstackclient/network/v2/floating_ip_port_forwarding.py b/openstackclient/network/v2/floating_ip_port_forwarding.py index b33633d..bcd5add 100644 --- a/openstackclient/network/v2/floating_ip_port_forwarding.py +++ b/openstackclient/network/v2/floating_ip_port_forwarding.py @@ -27,7 +27,7 @@ LOG = logging.getLogger(__name__) def _get_columns(item): column_map = {} - hidden_columns = ['location'] + hidden_columns = ['location', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py index 9e56c5b..b065ab1 100644 --- a/openstackclient/network/v2/ip_availability.py +++ b/openstackclient/network/v2/ip_availability.py @@ -27,7 +27,7 @@ _formatters = { def _get_columns(item): column_map = {} - hidden_columns = ['location'] + hidden_columns = ['id', 'name', 'location', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, diff --git a/openstackclient/network/v2/l3_conntrack_helper.py b/openstackclient/network/v2/l3_conntrack_helper.py index 1de5b78..df153dd 100644 --- a/openstackclient/network/v2/l3_conntrack_helper.py +++ b/openstackclient/network/v2/l3_conntrack_helper.py @@ -26,7 +26,7 @@ LOG = logging.getLogger(__name__) def _get_columns(item): column_map = {} - hidden_columns = ['location'] + hidden_columns = ['location', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, diff --git a/openstackclient/network/v2/local_ip.py b/openstackclient/network/v2/local_ip.py index 109f64c..e8fb5f8 100644 --- a/openstackclient/network/v2/local_ip.py +++ b/openstackclient/network/v2/local_ip.py @@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__) def _get_columns(item): column_map = {} - hidden_columns = ['location'] + hidden_columns = ['location', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, hidden_columns) @@ -87,17 +87,17 @@ class CreateLocalIP(command.ShowOne): ) parser.add_argument( '--local-port', - metavar='<local_port>', + metavar='<local-port>', help=_("Port to allocate Local IP (name or ID)") ) parser.add_argument( "--local-ip-address", - metavar="<local_ip_address>", + metavar="<local-ip-address>", help=_("IP address or CIDR "), ) parser.add_argument( '--ip-mode', - metavar='<ip_mode>', + metavar='<ip-mode>', help=_("local IP ip mode") ) @@ -212,13 +212,13 @@ class ListLocalIP(command.Lister): ) parser.add_argument( '--local-port', - metavar='<local_port>', + metavar='<local-port>', help=_("List Local IP(s) according to " "given port (name or ID)") ) parser.add_argument( '--local-ip-address', - metavar='<local_ip_address>', + metavar='<local-ip-address>', help=_("List Local IP(s) according to " "given Local IP Address") ) diff --git a/openstackclient/network/v2/local_ip_association.py b/openstackclient/network/v2/local_ip_association.py index aa0747c..4cd7707 100644 --- a/openstackclient/network/v2/local_ip_association.py +++ b/openstackclient/network/v2/local_ip_association.py @@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__) def _get_columns(item): column_map = {} - hidden_columns = ['location', 'name', 'id'] + hidden_columns = ['location', 'name', 'id', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, hidden_columns) @@ -43,16 +43,16 @@ class CreateLocalIPAssociation(command.ShowOne): 'local_ip', metavar='<local-ip>', help=_("Local IP that the port association belongs to " - "(IP address or ID)") + "(Name or ID)") ) parser.add_argument( 'fixed_port', - metavar='<fixed_port>', + metavar='<fixed-port>', help=_("The ID or Name of Port to allocate Local IP Association") ) parser.add_argument( '--fixed-ip', - metavar='<fixed_ip>', + metavar='<fixed-ip>', help=_("Fixed IP for Local IP Association") ) @@ -87,14 +87,14 @@ class DeleteLocalIPAssociation(command.Command): parser = super().get_parser(prog_name) parser.add_argument( 'local_ip', - metavar="<local_ip>", + metavar="<local-ip>", help=_("Local IP that the port association belongs to " - "(IP address or ID)") + "(Name or ID)") ) parser.add_argument( 'fixed_port_id', nargs="+", - metavar="<fixed_port_id>", + metavar="<fixed-port-id>", help=_("The fixed port ID of Local IP Association") ) return parser @@ -136,18 +136,18 @@ class ListLocalIPAssociation(command.Lister): parser.add_argument( 'local_ip', - metavar='<local_ip>', + metavar='<local-ip>', help=_("Local IP that port associations belongs to") ) parser.add_argument( '--fixed-port', - metavar='<fixed_port>', + metavar='<fixed-port>', help=_("Filter the list result by the ID or name of " "the fixed port") ) parser.add_argument( '--fixed-ip', - metavar='<fixed_ip>', + metavar='<fixed-ip>', help=_("Filter the list result by fixed ip") ) parser.add_argument( diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index a239e0f..d7b37eb 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -60,6 +60,7 @@ def _get_columns_network(item): 'ipv6_address_scope_id': 'ipv6_address_scope', 'tags': 'tags', } + hidden_columns = ['location', 'tenant_id'] hidden_columns = ['location'] return utils.get_osc_show_columns_for_sdk_resource( item, diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index 3024d02..d963b3b 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -50,7 +50,7 @@ def _get_network_columns(item): 'is_admin_state_up': 'admin_state_up', 'is_alive': 'alive', } - hidden_columns = ['location'] + hidden_columns = ['location', 'name', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, diff --git a/openstackclient/network/v2/network_auto_allocated_topology.py b/openstackclient/network/v2/network_auto_allocated_topology.py index 496606b..c612f05 100644 --- a/openstackclient/network/v2/network_auto_allocated_topology.py +++ b/openstackclient/network/v2/network_auto_allocated_topology.py @@ -26,7 +26,7 @@ LOG = logging.getLogger(__name__) def _get_columns(item): column_map = {} - hidden_columns = ['location'] + hidden_columns = ['name', 'location', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, diff --git a/openstackclient/network/v2/network_flavor.py b/openstackclient/network/v2/network_flavor.py index 862155c..864184c 100644 --- a/openstackclient/network/v2/network_flavor.py +++ b/openstackclient/network/v2/network_flavor.py @@ -31,7 +31,7 @@ def _get_columns(item): 'is_enabled': 'enabled', } - hidden_columns = ['location'] + hidden_columns = ['location', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, diff --git a/openstackclient/network/v2/network_flavor_profile.py b/openstackclient/network/v2/network_flavor_profile.py index 719f955..66c6dcf 100644 --- a/openstackclient/network/v2/network_flavor_profile.py +++ b/openstackclient/network/v2/network_flavor_profile.py @@ -29,7 +29,7 @@ def _get_columns(item): 'is_enabled': 'enabled', } - hidden_columns = ['location'] + hidden_columns = ['location', 'name', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, @@ -166,7 +166,7 @@ class ListNetworkFlavorProfile(command.Lister): 'id', 'driver', 'is_enabled', - 'metainfo', + 'meta_info', 'description', ) column_headers = ( diff --git a/openstackclient/network/v2/network_meter.py b/openstackclient/network/v2/network_meter.py index b7b77fb..99b0bdd 100644 --- a/openstackclient/network/v2/network_meter.py +++ b/openstackclient/network/v2/network_meter.py @@ -30,7 +30,7 @@ def _get_columns(item): column_map = { 'is_shared': 'shared', } - hidden_columns = ['location'] + hidden_columns = ['location', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, diff --git a/openstackclient/network/v2/network_meter_rule.py b/openstackclient/network/v2/network_meter_rule.py index 0f42727..2c50e5a 100644 --- a/openstackclient/network/v2/network_meter_rule.py +++ b/openstackclient/network/v2/network_meter_rule.py @@ -28,7 +28,7 @@ LOG = logging.getLogger(__name__) def _get_columns(item): column_map = {} - hidden_columns = ['location'] + hidden_columns = ['location', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, diff --git a/openstackclient/network/v2/network_qos_policy.py b/openstackclient/network/v2/network_qos_policy.py index bc25790..d77e5db 100644 --- a/openstackclient/network/v2/network_qos_policy.py +++ b/openstackclient/network/v2/network_qos_policy.py @@ -30,7 +30,7 @@ def _get_columns(item): column_map = { 'is_shared': 'shared', } - hidden_columns = ['location'] + hidden_columns = ['location', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py index a4129b8..cb2d233 100644 --- a/openstackclient/network/v2/network_qos_rule.py +++ b/openstackclient/network/v2/network_qos_rule.py @@ -25,16 +25,20 @@ from openstackclient.network import common RULE_TYPE_BANDWIDTH_LIMIT = 'bandwidth-limit' RULE_TYPE_DSCP_MARKING = 'dscp-marking' RULE_TYPE_MINIMUM_BANDWIDTH = 'minimum-bandwidth' +RULE_TYPE_MINIMUM_PACKET_RATE = 'minimum-packet-rate' MANDATORY_PARAMETERS = { RULE_TYPE_MINIMUM_BANDWIDTH: {'min_kbps', 'direction'}, + RULE_TYPE_MINIMUM_PACKET_RATE: {'min_kpps', 'direction'}, RULE_TYPE_DSCP_MARKING: {'dscp_mark'}, RULE_TYPE_BANDWIDTH_LIMIT: {'max_kbps'}} OPTIONAL_PARAMETERS = { RULE_TYPE_MINIMUM_BANDWIDTH: set(), + RULE_TYPE_MINIMUM_PACKET_RATE: set(), RULE_TYPE_DSCP_MARKING: set(), RULE_TYPE_BANDWIDTH_LIMIT: {'direction', 'max_burst_kbps'}} DIRECTION_EGRESS = 'egress' DIRECTION_INGRESS = 'ingress' +DIRECTION_ANY = 'any' DSCP_VALID_MARKS = [0, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 46, 48, 56] @@ -47,7 +51,7 @@ ACTION_SHOW = 'get' def _get_columns(item): column_map = {} - hidden_columns = ['location'] + hidden_columns = ['location', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, @@ -98,10 +102,20 @@ def _get_attrs(network_client, parsed_args, is_create=False): attrs['dscp_mark'] = parsed_args.dscp_mark if parsed_args.min_kbps is not None: attrs['min_kbps'] = parsed_args.min_kbps + if parsed_args.min_kpps is not None: + attrs['min_kpps'] = parsed_args.min_kpps if parsed_args.ingress: - attrs['direction'] = 'ingress' + attrs['direction'] = DIRECTION_INGRESS if parsed_args.egress: - attrs['direction'] = 'egress' + attrs['direction'] = DIRECTION_EGRESS + if parsed_args.any: + if rule_type == RULE_TYPE_MINIMUM_PACKET_RATE: + attrs['direction'] = DIRECTION_ANY + else: + msg = (_('Direction "any" can only be used with ' + '%(rule_type_min_pps)s rule type') % + {'rule_type_min_pps': RULE_TYPE_MINIMUM_PACKET_RATE}) + raise exceptions.CommandError(msg) _check_type_parameters(attrs, rule_type, is_create) return attrs @@ -160,6 +174,13 @@ def _add_rule_arguments(parser): type=int, help=_('Minimum guaranteed bandwidth in kbps') ) + parser.add_argument( + '--min-kpps', + dest='min_kpps', + metavar='<min-kpps>', + type=int, + help=_('Minimum guaranteed packet rate in kpps') + ) direction_group = parser.add_mutually_exclusive_group() direction_group.add_argument( '--ingress', @@ -171,6 +192,12 @@ def _add_rule_arguments(parser): action='store_true', help=_("Egress traffic direction from the project point of view") ) + direction_group.add_argument( + '--any', + action='store_true', + help=_("Any traffic direction from the project point of view. Can be " + "used only with minimum packet rate rule.") + ) class CreateNetworkQosRule(command.ShowOne, @@ -190,6 +217,7 @@ class CreateNetworkQosRule(command.ShowOne, metavar='<type>', required=True, choices=[RULE_TYPE_MINIMUM_BANDWIDTH, + RULE_TYPE_MINIMUM_PACKET_RATE, RULE_TYPE_DSCP_MARKING, RULE_TYPE_BANDWIDTH_LIMIT], help=(_('QoS rule type (%s)') % @@ -200,10 +228,10 @@ class CreateNetworkQosRule(command.ShowOne, def take_action(self, parsed_args): network_client = self.app.client_manager.network - attrs = _get_attrs(network_client, parsed_args, is_create=True) - attrs.update( - self._parse_extra_properties(parsed_args.extra_properties)) try: + attrs = _get_attrs(network_client, parsed_args, is_create=True) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = _rule_action_call( network_client, ACTION_CREATE, parsed_args.type)( attrs.pop('qos_policy_id'), **attrs) @@ -270,6 +298,7 @@ class ListNetworkQosRule(command.Lister): 'max_kbps', 'max_burst_kbps', 'min_kbps', + 'min_kpps', 'dscp_mark', 'direction', ) @@ -280,6 +309,7 @@ class ListNetworkQosRule(command.Lister): 'Max Kbps', 'Max Burst Kbits', 'Min Kbps', + 'Min Kpps', 'DSCP mark', 'Direction', ) diff --git a/openstackclient/network/v2/network_qos_rule_type.py b/openstackclient/network/v2/network_qos_rule_type.py index 1bcfda8..9af2287 100644 --- a/openstackclient/network/v2/network_qos_rule_type.py +++ b/openstackclient/network/v2/network_qos_rule_type.py @@ -24,9 +24,9 @@ def _get_columns(item): "type": "rule_type_name", "drivers": "drivers", } - invisible_columns = ["id", "location", "name"] + hidden_columns = ["id", "location", "name", 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( - item, column_map, invisible_columns) + item, column_map, hidden_columns) class ListNetworkQosRuleType(command.Lister): diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index 0066739..91cd9dc 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -30,7 +30,7 @@ def _get_columns(item): column_map = { 'target_tenant': 'target_project_id', } - hidden_columns = ['location'] + hidden_columns = ['location', 'name', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index 0f64bd8..c6c88e3 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -27,7 +27,7 @@ LOG = logging.getLogger(__name__) def _get_columns(item): column_map = {} - hidden_columns = ['location'] + hidden_columns = ['location', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, diff --git a/openstackclient/network/v2/network_segment_range.py b/openstackclient/network/v2/network_segment_range.py index a95adb0..1291d9d 100644 --- a/openstackclient/network/v2/network_segment_range.py +++ b/openstackclient/network/v2/network_segment_range.py @@ -33,7 +33,7 @@ LOG = logging.getLogger(__name__) def _get_columns(item): column_map = {} - hidden_columns = ['location'] + hidden_columns = ['location', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 16072bc..8bf14d6 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -64,7 +64,7 @@ def _get_columns(item): 'is_admin_state_up': 'admin_state_up', 'is_port_security_enabled': 'port_security_enabled', } - hidden_columns = ['location'] + hidden_columns = ['location', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index d8c38f4..ffad998 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -35,6 +35,7 @@ def _format_network_security_group_rules(sg_rules): for key in empty_keys: sg_rule.pop(key) sg_rule.pop('security_group_id', None) + sg_rule.pop('tenant_id', None) sg_rule.pop('project_id', None) return utils.format_list_of_dicts(sg_rules) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index a112261..2179f33 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -72,7 +72,7 @@ def _format_remote_ip_prefix(rule): def _get_columns(item): column_map = {} - hidden_columns = ['location'] + hidden_columns = ['location', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index bf6a46d..8e3a877 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -138,16 +138,17 @@ def _get_columns(item): 'is_dhcp_enabled': 'enable_dhcp', 'subnet_pool_id': 'subnetpool_id', } - # Do not show this column when displaying a subnet - invisible_columns = [ + # Do not show these columns when displaying a subnet + hidden_columns = [ 'location', 'use_default_subnet_pool', - 'prefix_length' + 'prefix_length', + 'tenant_id', ] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, - invisible_columns + hidden_columns ) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index b4142f3..2369960 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -37,7 +37,7 @@ def _get_columns(item): 'maximum_prefix_length': 'max_prefixlen', 'minimum_prefix_length': 'min_prefixlen', } - hidden_columns = ['location'] + hidden_columns = ['location', 'tenant_id'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index 0ed7dff..b6867f8 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import logging import os import shlex import subprocess @@ -18,22 +19,30 @@ from tempest.lib.cli import output_parser from tempest.lib import exceptions import testtools - ADMIN_CLOUD = os.environ.get('OS_ADMIN_CLOUD', 'devstack-admin') +LOG = logging.getLogger(__name__) def execute(cmd, fail_ok=False, merge_stderr=False): """Executes specified command for the given action.""" + LOG.debug('Executing: %s', cmd) cmdlist = shlex.split(cmd) stdout = subprocess.PIPE stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE + proc = subprocess.Popen(cmdlist, stdout=stdout, stderr=stderr) - result, result_err = proc.communicate() - result = result.decode('utf-8') + + result_out, result_err = proc.communicate() + result_out = result_out.decode('utf-8') + LOG.debug('stdout: %s', result_out) + LOG.debug('stderr: %s', result_err) + if not fail_ok and proc.returncode != 0: - raise exceptions.CommandFailed(proc.returncode, cmd, result, - result_err) - return result + raise exceptions.CommandFailed( + proc.returncode, cmd, result_out, result_err, + ) + + return result_out class TestCase(testtools.TestCase): @@ -42,7 +51,7 @@ class TestCase(testtools.TestCase): def openstack(cls, cmd, cloud=ADMIN_CLOUD, fail_ok=False): """Executes openstackclient command for the given action - NOTE(dtroyer): There is a subtle distinction between pasing + NOTE(dtroyer): There is a subtle distinction between passing cloud=None and cloud='': for compatibility reasons passing cloud=None continues to include the option '--os-auth-type none' in the command while passing cloud='' omits the '--os-auth-type' @@ -61,7 +70,7 @@ class TestCase(testtools.TestCase): fail_ok=fail_ok ) else: - # Execure command with an explicit cloud specified + # Execute command with an explicit cloud specified return execute( 'openstack --os-cloud=' + cloud + ' ' + cmd, fail_ok=fail_ok diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index bf67101..783294d 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -169,12 +169,21 @@ class QuotaTests(base.TestCase): self.assertTrue(cmd_output["key-pairs"] >= 0) self.assertTrue(cmd_output["snapshots"] >= 0) + def _restore_quota_limit(self, resource, limit, project): + self.openstack('quota set --%s %s %s' % (resource, limit, project)) + def test_quota_network_set_with_check_limit(self): if not self.haz_network: self.skipTest('No Network service present') if not self.is_extension_enabled('quota-check-limit'): self.skipTest('No "quota-check-limit" extension present') + cmd_output = json.loads(self.openstack( + 'quota list -f json --network' + )) + self.addCleanup(self._restore_quota_limit, 'network', + cmd_output[0]['Networks'], self.PROJECT_NAME) + self.openstack('quota set --networks 40 ' + self.PROJECT_NAME) cmd_output = json.loads(self.openstack( 'quota list -f json --network' @@ -190,3 +199,42 @@ class QuotaTests(base.TestCase): self.assertRaises(exceptions.CommandFailed, self.openstack, 'quota set --networks 1 --check-limit ' + self.PROJECT_NAME) + + def test_quota_network_set_with_force(self): + self.skipTest('story 2010110') + if not self.haz_network: + self.skipTest('No Network service present') + # NOTE(ralonsoh): the Neutron support for the flag "check-limit" was + # added with the extension "quota-check-limit". The flag "force" was + # added too in order to change the behaviour of Neutron quota engine + # and mimic the Nova one: by default the engine will check the resource + # usage before setting the new limit; with "force", this check will be + # skipped (in Yoga, this behaviour is still NOT the default in + # Neutron). + if not self.is_extension_enabled('quota-check-limit'): + self.skipTest('No "quota-check-limit" extension present') + + cmd_output = json.loads(self.openstack( + 'quota list -f json --network' + )) + self.addCleanup(self._restore_quota_limit, 'network', + cmd_output[0]['Networks'], self.PROJECT_NAME) + + self.openstack('quota set --networks 40 ' + self.PROJECT_NAME) + cmd_output = json.loads(self.openstack( + 'quota list -f json --network' + )) + self.assertIsNotNone(cmd_output) + self.assertEqual(40, cmd_output[0]['Networks']) + + # That will ensure we have at least two networks in the system. + for _ in range(2): + self.openstack('network create --project %s %s' % + (self.PROJECT_NAME, uuid.uuid4().hex)) + + self.openstack('quota set --networks 1 --force ' + self.PROJECT_NAME) + cmd_output = json.loads(self.openstack( + 'quota list -f json --network' + )) + self.assertIsNotNone(cmd_output) + self.assertEqual(1, cmd_output[0]['Networks']) diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index cf4bcbc..0558ef6 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -1195,19 +1195,19 @@ class ServerTests(common.ComputeTestCase): def test_server_add_remove_volume(self): volume_wait_for = volume_common.BaseVolumeTests.wait_for_status - name = uuid.uuid4().hex + server_name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'server create -f json ' + '--network private ' + '--flavor ' + self.flavor_name + ' ' + '--image ' + self.image_name + ' ' + '--wait ' + - name + server_name )) self.assertIsNotNone(cmd_output['id']) - self.assertEqual(name, cmd_output['name']) - self.addCleanup(self.openstack, 'server delete --wait ' + name) + self.assertEqual(server_name, cmd_output['name']) + self.addCleanup(self.openstack, 'server delete --wait ' + server_name) server_id = cmd_output['id'] volume_name = uuid.uuid4().hex @@ -1225,7 +1225,7 @@ class ServerTests(common.ComputeTestCase): cmd_output = json.loads(self.openstack( 'server add volume -f json ' + - name + ' ' + + server_name + ' ' + volume_name + ' ' + '--tag bar' )) @@ -1237,7 +1237,7 @@ class ServerTests(common.ComputeTestCase): cmd_output = json.loads(self.openstack( 'server volume list -f json ' + - name + server_name )) self.assertEqual(volume_attachment_id, cmd_output[0]['ID']) @@ -1245,8 +1245,25 @@ class ServerTests(common.ComputeTestCase): self.assertEqual(volume_id, cmd_output[0]['Volume ID']) volume_wait_for('volume', volume_name, 'in-use') - self.openstack('server remove volume ' + name + ' ' + volume_name) + + cmd_output = json.loads(self.openstack( + 'server event list -f json ' + + server_name + )) + self.assertEqual(2, len(cmd_output)) + self.assertIn('attach_volume', {x['Action'] for x in cmd_output}) + + self.openstack( + 'server remove volume ' + server_name + ' ' + volume_name + ) volume_wait_for('volume', volume_name, 'available') - raw_output = self.openstack('server volume list ' + name) + cmd_output = json.loads(self.openstack( + 'server event list -f json ' + + server_name + )) + self.assertEqual(3, len(cmd_output)) + self.assertIn('detach_volume', {x['Action'] for x in cmd_output}) + + raw_output = self.openstack('server volume list ' + server_name) self.assertEqual('\n', raw_output) diff --git a/openstackclient/tests/functional/compute/v2/test_server_event.py b/openstackclient/tests/functional/compute/v2/test_server_event.py index 953ade4..b4725fe 100644 --- a/openstackclient/tests/functional/compute/v2/test_server_event.py +++ b/openstackclient/tests/functional/compute/v2/test_server_event.py @@ -82,3 +82,38 @@ class ServerEventTests(common.ComputeTestCase): self.assertEqual('reboot', cmd_output.get('action')) self.assertIsNotNone(cmd_output.get('events')) self.assertIsInstance(cmd_output.get('events'), list) + + def test_server_event_list_and_show_deleted_server(self): + # Need to create a new server that will not attempt cleanup because we + # are going to delete it during the test and cleanup would fail with + # 404. + cmd_output = self.server_create(cleanup=False) + server_id = cmd_output['id'] + # Delete the server + self.openstack('server delete --wait ' + server_id) + # And verify we can get the event list after it's deleted + # Test 'server event list' for deleting + cmd_output = json.loads(self.openstack( + '--os-compute-api-version 2.21 ' + 'server event list -f json ' + server_id + )) + request_id = None + for each_event in cmd_output: + self.assertNotIn('Message', each_event) + self.assertNotIn('Project ID', each_event) + self.assertNotIn('User ID', each_event) + if each_event.get('Action') == 'delete': + self.assertEqual(server_id, each_event.get('Server ID')) + request_id = each_event.get('Request ID') + break + self.assertIsNotNone(request_id) + # Test 'server event show' for deleting + cmd_output = json.loads(self.openstack( + '--os-compute-api-version 2.21 ' + 'server event show -f json ' + server_id + ' ' + request_id + )) + self.assertEqual(server_id, cmd_output.get('instance_uuid')) + self.assertEqual(request_id, cmd_output.get('request_id')) + self.assertEqual('delete', cmd_output.get('action')) + self.assertIsNotNone(cmd_output.get('events')) + self.assertIsInstance(cmd_output.get('events'), list) diff --git a/openstackclient/tests/functional/compute/v2/test_server_group.py b/openstackclient/tests/functional/compute/v2/test_server_group.py index 3dff3dc..daeecd2 100644 --- a/openstackclient/tests/functional/compute/v2/test_server_group.py +++ b/openstackclient/tests/functional/compute/v2/test_server_group.py @@ -33,8 +33,8 @@ class ServerGroupTests(base.TestCase): cmd_output['name'] ) self.assertEqual( - ['affinity'], - cmd_output['policies'] + 'affinity', + cmd_output['policy'] ) cmd_output = json.loads(self.openstack( @@ -47,8 +47,8 @@ class ServerGroupTests(base.TestCase): cmd_output['name'] ) self.assertEqual( - ['anti-affinity'], - cmd_output['policies'] + 'anti-affinity', + cmd_output['policy'] ) del_output = self.openstack( @@ -60,7 +60,7 @@ class ServerGroupTests(base.TestCase): name1 = uuid.uuid4().hex name2 = uuid.uuid4().hex - # test server gorup show + # test server group show cmd_output = json.loads(self.openstack( 'server group create -f json ' + '--policy affinity ' + @@ -74,8 +74,8 @@ class ServerGroupTests(base.TestCase): cmd_output['name'] ) self.assertEqual( - ['affinity'], - cmd_output['policies'] + 'affinity', + cmd_output['policy'] ) cmd_output = json.loads(self.openstack( @@ -91,8 +91,8 @@ class ServerGroupTests(base.TestCase): cmd_output['name'] ) self.assertEqual( - ['anti-affinity'], - cmd_output['policies'] + 'anti-affinity', + cmd_output['policy'] ) # test server group list @@ -101,6 +101,6 @@ class ServerGroupTests(base.TestCase): names = [x["Name"] for x in cmd_output] self.assertIn(name1, names) self.assertIn(name2, names) - policies = [x["Policies"] for x in cmd_output] - self.assertIn(['affinity'], policies) - self.assertIn(['anti-affinity'], policies) + policies = [x["Policy"] for x in cmd_output] + self.assertIn('affinity', policies) + self.assertIn('anti-affinity', policies) diff --git a/openstackclient/tests/functional/image/v2/test_image.py b/openstackclient/tests/functional/image/v2/test_image.py index 0a3a736..5b07470 100644 --- a/openstackclient/tests/functional/image/v2/test_image.py +++ b/openstackclient/tests/functional/image/v2/test_image.py @@ -26,6 +26,7 @@ class ImageTests(base.BaseImageTests): self.name = uuid.uuid4().hex self.image_tag = 'my_tag' + self.image_tag1 = 'random' json_output = json.loads(self.openstack( '--os-image-api-version 2 ' 'image create -f json --tag {tag} {name}'.format( @@ -78,13 +79,18 @@ class ImageTests(base.BaseImageTests): def test_image_list_with_tag_filter(self): json_output = json.loads(self.openstack( - 'image list --tag ' + self.image_tag + ' --long -f json' + 'image list --tag ' + self.image_tag + ' --tag ' + + self.image_tag1 + ' --long -f json' )) for taglist in [img['Tags'] for img in json_output]: self.assertIn( self.image_tag, taglist ) + self.assertIn( + self.image_tag1, + taglist + ) def test_image_attributes(self): """Test set, unset, show on attributes, tags and properties""" diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py index 98e588e..fd411b3 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py @@ -85,6 +85,73 @@ class NetworkQosRuleTestsMinimumBandwidth(common.NetworkTests): self.assertEqual(7500, cmd_output['min_kbps']) +class NetworkQosRuleTestsMinimumPacketRate(common.NetworkTests): + """Functional tests for QoS minimum packet rate rule""" + + def setUp(self): + super(NetworkQosRuleTestsMinimumPacketRate, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") + + self.QOS_POLICY_NAME = 'qos_policy_%s' % uuid.uuid4().hex + + self.openstack( + 'network qos policy create %s' % self.QOS_POLICY_NAME + ) + self.addCleanup(self.openstack, + 'network qos policy delete %s' % self.QOS_POLICY_NAME) + cmd_output = json.loads(self.openstack( + 'network qos rule create -f json ' + '--type minimum-packet-rate ' + '--min-kpps 2800 ' + '--egress %s' % + self.QOS_POLICY_NAME + )) + self.RULE_ID = cmd_output['id'] + self.addCleanup(self.openstack, + 'network qos rule delete %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID)) + self.assertTrue(self.RULE_ID) + + def test_qos_rule_create_delete(self): + # This is to check the output of qos rule delete + policy_name = uuid.uuid4().hex + self.openstack('network qos policy create -f json %s' % policy_name) + self.addCleanup(self.openstack, + 'network qos policy delete %s' % policy_name) + rule = json.loads(self.openstack( + 'network qos rule create -f json ' + '--type minimum-packet-rate ' + '--min-kpps 2800 ' + '--egress %s' % policy_name + )) + raw_output = self.openstack( + 'network qos rule delete %s %s' % + (policy_name, rule['id'])) + self.assertEqual('', raw_output) + + def test_qos_rule_list(self): + cmd_output = json.loads(self.openstack( + 'network qos rule list -f json %s' % self.QOS_POLICY_NAME)) + self.assertIn(self.RULE_ID, [rule['ID'] for rule in cmd_output]) + + def test_qos_rule_show(self): + cmd_output = json.loads(self.openstack( + 'network qos rule show -f json %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID))) + self.assertEqual(self.RULE_ID, cmd_output['id']) + + def test_qos_rule_set(self): + self.openstack('network qos rule set --min-kpps 7500 --any %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID)) + cmd_output = json.loads(self.openstack( + 'network qos rule show -f json %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID))) + self.assertEqual(7500, cmd_output['min_kpps']) + self.assertEqual('any', cmd_output['direction']) + + class NetworkQosRuleTestsDSCPMarking(common.NetworkTests): """Functional tests for QoS DSCP marking rule""" diff --git a/openstackclient/tests/functional/volume/v1/test_volume_type.py b/openstackclient/tests/functional/volume/v1/test_volume_type.py index fb8dabd..7434b5b 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v1/test_volume_type.py @@ -118,10 +118,10 @@ class VolumeTypeTests(common.BaseVolumeTests): raw_output = self.openstack(cmd) self.assertOutput('', raw_output) - # NOTE: Add some basic funtional tests with the old format to + # NOTE: Add some basic functional tests with the old format to # make sure the command works properly, need to change # these to new test format when beef up all tests for - # volume tye commands. + # volume type commands. def test_encryption_type(self): encryption_type = uuid.uuid4().hex # test create new encryption type diff --git a/openstackclient/tests/functional/volume/v2/test_volume_type.py b/openstackclient/tests/functional/volume/v2/test_volume_type.py index 3f1a6ea..861c393 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_type.py @@ -139,10 +139,10 @@ class VolumeTypeTests(common.BaseVolumeTests): raw_output = self.openstack(cmd) self.assertOutput('', raw_output) - # NOTE: Add some basic funtional tests with the old format to + # NOTE: Add some basic functional tests with the old format to # make sure the command works properly, need to change # these to new test format when beef up all tests for - # volume tye commands. + # volume type commands. def test_encryption_type(self): name = uuid.uuid4().hex encryption_type = uuid.uuid4().hex diff --git a/openstackclient/tests/functional/volume/v3/test_volume_type.py b/openstackclient/tests/functional/volume/v3/test_volume_type.py index 79d4096..165c625 100644 --- a/openstackclient/tests/functional/volume/v3/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v3/test_volume_type.py @@ -139,10 +139,10 @@ class VolumeTypeTests(common.BaseVolumeTests): raw_output = self.openstack(cmd) self.assertOutput('', raw_output) - # NOTE: Add some basic funtional tests with the old format to + # NOTE: Add some basic functional tests with the old format to # make sure the command works properly, need to change # these to new test format when beef up all tests for - # volume tye commands. + # volume type commands. def test_encryption_type(self): name = uuid.uuid4().hex encryption_type = uuid.uuid4().hex diff --git a/openstackclient/tests/unit/common/test_availability_zone.py b/openstackclient/tests/unit/common/test_availability_zone.py index 8733b51..e5348ec 100644 --- a/openstackclient/tests/unit/common/test_availability_zone.py +++ b/openstackclient/tests/unit/common/test_availability_zone.py @@ -115,8 +115,7 @@ class TestAvailabilityZoneList(TestAvailabilityZone): compute_fakes.FakeAvailabilityZone.create_availability_zones() volume_azs = \ volume_fakes.FakeAvailabilityZone.create_availability_zones(count=1) - network_azs = \ - network_fakes.FakeAvailabilityZone.create_availability_zones() + network_azs = network_fakes.create_availability_zones() short_columnslist = ('Zone Name', 'Zone Status') long_columnslist = ( diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 896a63a..70fd143 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -935,6 +935,7 @@ class TestQuotaSet(TestQuota): } kwargs_network = { 'subnet': network_fakes.QUOTA['subnet'], + 'force': True, } self.compute_quotas_mock.update.assert_called_once_with( self.projects[0].id, diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 55572cd..d77797a 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -21,6 +21,7 @@ import uuid from novaclient import api_versions from openstack.compute.v2 import flavor as _flavor from openstack.compute.v2 import server +from openstack.compute.v2 import server_group as _server_group from openstack.compute.v2 import server_interface as _server_interface from openstack.compute.v2 import service from openstack.compute.v2 import volume_attachment @@ -1290,72 +1291,6 @@ class FakeHost(object): return host_info -class FakeServerGroup(object): - """Fake one server group""" - - @staticmethod - def _create_one_server_group(attrs=None): - """Create a fake server group - - :param dict attrs: - A dictionary with all attributes - :return: - A FakeResource object, with id and other attributes - """ - if attrs is None: - attrs = {} - - # Set default attributes. - server_group_info = { - 'id': 'server-group-id-' + uuid.uuid4().hex, - 'members': [], - 'metadata': {}, - 'name': 'server-group-name-' + uuid.uuid4().hex, - 'project_id': 'server-group-project-id-' + uuid.uuid4().hex, - 'user_id': 'server-group-user-id-' + uuid.uuid4().hex, - } - - # Overwrite default attributes. - server_group_info.update(attrs) - - server_group = fakes.FakeResource( - info=copy.deepcopy(server_group_info), - loaded=True) - return server_group - - @staticmethod - def create_one_server_group(attrs=None): - """Create a fake server group - - :param dict attrs: - A dictionary with all attributes - :return: - A FakeResource object, with id and other attributes - """ - if attrs is None: - attrs = {} - attrs.setdefault('policies', ['policy1', 'policy2']) - return FakeServerGroup._create_one_server_group(attrs) - - -class FakeServerGroupV264(object): - """Fake one server group fo API >= 2.64""" - - @staticmethod - def create_one_server_group(attrs=None): - """Create a fake server group - - :param dict attrs: - A dictionary with all attributes - :return: - A FakeResource object, with id and other attributes - """ - if attrs is None: - attrs = {} - attrs.setdefault('policy', 'policy1') - return FakeServerGroup._create_one_server_group(attrs) - - class FakeUsage(object): """Fake one or more usage.""" @@ -1454,7 +1389,7 @@ class FakeQuota(object): @staticmethod def create_one_default_comp_quota(attrs=None): - """Crate one quota""" + """Create one quota""" attrs = attrs or {} @@ -1860,6 +1795,34 @@ class FakeVolumeAttachment(object): return volume_attachments +def create_one_server_group(attrs=None): + """Create a fake server group + + :param dict attrs: + A dictionary with all attributes + :return: + A fake ServerGroup object, with id and other attributes + """ + if attrs is None: + attrs = {} + + # Set default attributes. + server_group_info = { + 'id': 'server-group-id-' + uuid.uuid4().hex, + 'member_ids': '', + 'metadata': {}, + 'name': 'server-group-name-' + uuid.uuid4().hex, + 'project_id': 'server-group-project-id-' + uuid.uuid4().hex, + 'user_id': 'server-group-user-id-' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + server_group_info.update(attrs) + + server_group = _server_group.ServerGroup(**server_group_info) + return server_group + + def create_one_server_interface(attrs=None): """Create a fake SDK ServerInterface. diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 480dcfe..004f3a0 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -31,7 +31,6 @@ from osc_lib import utils as common_utils from openstackclient.compute.v2 import server from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes -from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes from openstackclient.tests.unit.image.v2 import fakes as image_fakes from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils @@ -593,7 +592,7 @@ class TestServerAddFloatingIPNetwork( def test_server_add_floating_ip(self): _server = compute_fakes.FakeServer.create_one_server() self.servers_mock.get.return_value = _server - _port = network_fakes.FakePort.create_one_port() + _port = network_fakes.create_one_port() _floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() self.network.find_ip = mock.Mock(return_value=_floating_ip) self.network.ports = mock.Mock(return_value=[_port]) @@ -662,7 +661,7 @@ class TestServerAddFloatingIPNetwork( def test_server_add_floating_ip_no_external_gateway(self, success=False): _server = compute_fakes.FakeServer.create_one_server() self.servers_mock.get.return_value = _server - _port = network_fakes.FakePort.create_one_port() + _port = network_fakes.create_one_port() _floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() self.network.find_ip = mock.Mock(return_value=_floating_ip) return_value = [_port] @@ -718,7 +717,7 @@ class TestServerAddFloatingIPNetwork( def test_server_add_floating_ip_with_fixed_ip(self): _server = compute_fakes.FakeServer.create_one_server() self.servers_mock.get.return_value = _server - _port = network_fakes.FakePort.create_one_port() + _port = network_fakes.create_one_port() _floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() self.network.find_ip = mock.Mock(return_value=_floating_ip) self.network.ports = mock.Mock(return_value=[_port]) @@ -760,7 +759,7 @@ class TestServerAddFloatingIPNetwork( def test_server_add_floating_ip_with_fixed_ip_no_port_found(self): _server = compute_fakes.FakeServer.create_one_server() self.servers_mock.get.return_value = _server - _port = network_fakes.FakePort.create_one_port() + _port = network_fakes.create_one_port() _floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() self.network.find_ip = mock.Mock(return_value=_floating_ip) self.network.ports = mock.Mock(return_value=[_port]) @@ -4486,6 +4485,14 @@ class TestServerList(_TestServerList): '-c', 'Project ID', '-c', 'User ID', '-c', 'Created At', + '-c', 'Security Groups', + '-c', 'Task State', + '-c', 'Power State', + '-c', 'Image ID', + '-c', 'Flavor ID', + '-c', 'Availability Zone', + '-c', 'Host', + '-c', 'Properties', '--long' ] verifylist = [ @@ -4499,6 +4506,14 @@ class TestServerList(_TestServerList): self.assertIn('Project ID', columns) self.assertIn('User ID', columns) self.assertIn('Created At', columns) + self.assertIn('Security Groups', columns) + self.assertIn('Task State', columns) + self.assertIn('Power State', columns) + self.assertIn('Image ID', columns) + self.assertIn('Flavor ID', columns) + self.assertIn('Availability Zone', columns) + self.assertIn('Host', columns) + self.assertIn('Properties', columns) def test_server_list_no_name_lookup_option(self): self.data = tuple( @@ -4884,6 +4899,99 @@ class TestServerList(_TestServerList): self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) + def test_server_list_long_with_host_status_v216(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.16') + + self.data1 = tuple( + ( + s.id, + s.name, + s.status, + getattr(s, 'OS-EXT-STS:task_state'), + server.PowerStateColumn( + getattr(s, 'OS-EXT-STS:power_state') + ), + format_columns.DictListColumn(s.networks), + # Image will be an empty string if boot-from-volume + self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, + s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, + self.flavor.name, + s.flavor['id'], + getattr(s, 'OS-EXT-AZ:availability_zone'), + getattr(s, 'OS-EXT-SRV-ATTR:host'), + s.Metadata, + ) for s in self.servers) + + arglist = [ + '--long' + ] + verifylist = [ + ('long', True), + ] + + # First test without host_status in the data -- the column should not + # be present in this case. + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertEqual(self.columns_long, columns) + self.assertEqual(tuple(self.data1), tuple(data)) + + # Next test with host_status in the data -- the column should be + # present in this case. + self.servers_mock.reset_mock() + + self.attrs['host_status'] = 'UP' + servers = self.setup_servers_mock(3) + self.servers_mock.list.return_value = servers + + # Make sure the returned image and flavor IDs match the servers. + Image = collections.namedtuple('Image', 'id name') + self.images_mock.return_value = [ + Image(id=s.image['id'], name=self.image.name) + # Image will be an empty string if boot-from-volume + for s in servers if s.image + ] + + Flavor = collections.namedtuple('Flavor', 'id name') + self.flavors_mock.list.return_value = [ + Flavor(id=s.flavor['id'], name=self.flavor.name) + for s in servers + ] + + # Add the expected host_status column and data. + columns_long = self.columns_long + ('Host Status',) + self.data2 = tuple( + ( + s.id, + s.name, + s.status, + getattr(s, 'OS-EXT-STS:task_state'), + server.PowerStateColumn( + getattr(s, 'OS-EXT-STS:power_state') + ), + format_columns.DictListColumn(s.networks), + # Image will be an empty string if boot-from-volume + self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, + s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, + self.flavor.name, + s.flavor['id'], + getattr(s, 'OS-EXT-AZ:availability_zone'), + getattr(s, 'OS-EXT-SRV-ATTR:host'), + s.Metadata, + s.host_status, + ) for s in servers) + + columns, data = self.cmd.take_action(parsed_args) + + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertEqual(columns_long, columns) + self.assertEqual(tuple(self.data2), tuple(data)) + class TestServerListV273(_TestServerList): @@ -5559,782 +5667,6 @@ class TestServerMigrate(TestServer): self.assertNotCalled(self.servers_mock.live_migrate) -class TestListMigration(TestServer): - """Test fetch all migrations.""" - - MIGRATION_COLUMNS = [ - 'Source Node', 'Dest Node', 'Source Compute', - 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', - 'Old Flavor', 'New Flavor', 'Created At', 'Updated At' - ] - - # These are the fields that come back in the response from the REST API. - MIGRATION_FIELDS = [ - 'source_node', 'dest_node', 'source_compute', 'dest_compute', - 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id', - 'new_instance_type_id', 'created_at', 'updated_at' - ] - - def setUp(self): - super(TestListMigration, self).setUp() - - self.server = compute_fakes.FakeServer.create_one_server() - self.servers_mock.get.return_value = self.server - - self.migrations = compute_fakes.FakeMigration.create_migrations( - count=3) - self.migrations_mock.list.return_value = self.migrations - - self.data = (common_utils.get_item_properties( - s, self.MIGRATION_FIELDS) for s in self.migrations) - - # Get the command object to test - self.cmd = server.ListMigration(self.app, None) - - def test_server_migration_list_no_options(self): - arglist = [] - verifylist = [] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'status': None, - 'host': None, - } - - self.migrations_mock.list.assert_called_with(**kwargs) - - self.assertEqual(self.MIGRATION_COLUMNS, columns) - self.assertEqual(tuple(self.data), tuple(data)) - - def test_server_migration_list(self): - arglist = [ - '--server', 'server1', - '--host', 'host1', - '--status', 'migrating', - '--type', 'cold-migration', - ] - verifylist = [ - ('server', 'server1'), - ('host', 'host1'), - ('status', 'migrating'), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'status': 'migrating', - 'host': 'host1', - 'instance_uuid': self.server.id, - 'migration_type': 'migration', - } - - self.servers_mock.get.assert_called_with('server1') - self.migrations_mock.list.assert_called_with(**kwargs) - - self.assertEqual(self.MIGRATION_COLUMNS, columns) - self.assertEqual(tuple(self.data), tuple(data)) - - -class TestListMigrationV223(TestListMigration): - """Test fetch all migrations. """ - - MIGRATION_COLUMNS = [ - 'Id', 'Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', - 'Dest Host', 'Status', 'Server UUID', 'Old Flavor', 'New Flavor', - 'Type', 'Created At', 'Updated At' - ] - - # These are the fields that come back in the response from the REST API. - MIGRATION_FIELDS = [ - 'id', 'source_node', 'dest_node', 'source_compute', 'dest_compute', - 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id', - 'new_instance_type_id', 'migration_type', 'created_at', 'updated_at' - ] - - def setUp(self): - super(TestListMigrationV223, self).setUp() - - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.23') - - def test_server_migration_list(self): - arglist = [ - '--status', 'migrating' - ] - verifylist = [ - ('status', 'migrating') - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'status': 'migrating', - 'host': None, - } - - self.migrations_mock.list.assert_called_with(**kwargs) - - self.assertEqual(self.MIGRATION_COLUMNS, columns) - self.assertEqual(tuple(self.data), tuple(data)) - - -class TestListMigrationV259(TestListMigration): - """Test fetch all migrations. """ - - MIGRATION_COLUMNS = [ - 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute', - 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', - 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' - ] - - # These are the fields that come back in the response from the REST API. - MIGRATION_FIELDS = [ - 'id', 'uuid', 'source_node', 'dest_node', 'source_compute', - 'dest_compute', 'dest_host', 'status', 'instance_uuid', - 'old_instance_type_id', 'new_instance_type_id', 'migration_type', - 'created_at', 'updated_at' - ] - - def setUp(self): - super(TestListMigrationV259, self).setUp() - - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.59') - - def test_server_migration_list(self): - arglist = [ - '--status', 'migrating', - '--limit', '1', - '--marker', 'test_kp', - '--changes-since', '2019-08-09T08:03:25Z' - ] - verifylist = [ - ('status', 'migrating'), - ('limit', 1), - ('marker', 'test_kp'), - ('changes_since', '2019-08-09T08:03:25Z') - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'status': 'migrating', - 'limit': 1, - 'marker': 'test_kp', - 'host': None, - 'changes_since': '2019-08-09T08:03:25Z', - } - - self.migrations_mock.list.assert_called_with(**kwargs) - - self.assertEqual(self.MIGRATION_COLUMNS, columns) - self.assertEqual(tuple(self.data), tuple(data)) - - def test_server_migration_list_with_limit_pre_v259(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.58') - arglist = [ - '--status', 'migrating', - '--limit', '1' - ] - verifylist = [ - ('status', 'migrating'), - ('limit', 1) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.59 or greater is required', - str(ex)) - - def test_server_migration_list_with_marker_pre_v259(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.58') - arglist = [ - '--status', 'migrating', - '--marker', 'test_kp' - ] - verifylist = [ - ('status', 'migrating'), - ('marker', 'test_kp') - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.59 or greater is required', - str(ex)) - - def test_server_migration_list_with_changes_since_pre_v259(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.58') - arglist = [ - '--status', 'migrating', - '--changes-since', '2019-08-09T08:03:25Z' - ] - verifylist = [ - ('status', 'migrating'), - ('changes_since', '2019-08-09T08:03:25Z') - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.59 or greater is required', - str(ex)) - - -class TestListMigrationV266(TestListMigration): - """Test fetch all migrations by changes-before. """ - - MIGRATION_COLUMNS = [ - 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute', - 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', - 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' - ] - - # These are the fields that come back in the response from the REST API. - MIGRATION_FIELDS = [ - 'id', 'uuid', 'source_node', 'dest_node', 'source_compute', - 'dest_compute', 'dest_host', 'status', 'instance_uuid', - 'old_instance_type_id', 'new_instance_type_id', 'migration_type', - 'created_at', 'updated_at' - ] - - def setUp(self): - super(TestListMigrationV266, self).setUp() - - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.66') - - def test_server_migration_list_with_changes_before(self): - arglist = [ - '--status', 'migrating', - '--limit', '1', - '--marker', 'test_kp', - '--changes-since', '2019-08-07T08:03:25Z', - '--changes-before', '2019-08-09T08:03:25Z' - ] - verifylist = [ - ('status', 'migrating'), - ('limit', 1), - ('marker', 'test_kp'), - ('changes_since', '2019-08-07T08:03:25Z'), - ('changes_before', '2019-08-09T08:03:25Z') - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'status': 'migrating', - 'limit': 1, - 'marker': 'test_kp', - 'host': None, - 'changes_since': '2019-08-07T08:03:25Z', - 'changes_before': '2019-08-09T08:03:25Z', - } - - self.migrations_mock.list.assert_called_with(**kwargs) - - self.assertEqual(self.MIGRATION_COLUMNS, columns) - self.assertEqual(tuple(self.data), tuple(data)) - - def test_server_migration_list_with_changes_before_pre_v266(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.65') - arglist = [ - '--status', 'migrating', - '--changes-before', '2019-08-09T08:03:25Z' - ] - verifylist = [ - ('status', 'migrating'), - ('changes_before', '2019-08-09T08:03:25Z') - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.66 or greater is required', - str(ex)) - - -class TestListMigrationV280(TestListMigration): - """Test fetch all migrations by user-id and/or project-id. """ - - MIGRATION_COLUMNS = [ - 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute', - 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', - 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' - ] - - # These are the fields that come back in the response from the REST API. - MIGRATION_FIELDS = [ - 'id', 'uuid', 'source_node', 'dest_node', 'source_compute', - 'dest_compute', 'dest_host', 'status', 'instance_uuid', - 'old_instance_type_id', 'new_instance_type_id', 'migration_type', - 'created_at', 'updated_at' - ] - - project = identity_fakes.FakeProject.create_one_project() - user = identity_fakes.FakeUser.create_one_user() - - def setUp(self): - super(TestListMigrationV280, self).setUp() - - self.projects_mock = self.app.client_manager.identity.projects - self.projects_mock.reset_mock() - - self.users_mock = self.app.client_manager.identity.users - self.users_mock.reset_mock() - - self.projects_mock.get.return_value = self.project - self.users_mock.get.return_value = self.user - - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.80') - - def test_server_migration_list_with_project(self): - arglist = [ - '--status', 'migrating', - '--limit', '1', - '--marker', 'test_kp', - '--changes-since', '2019-08-07T08:03:25Z', - '--changes-before', '2019-08-09T08:03:25Z', - '--project', self.project.id - ] - verifylist = [ - ('status', 'migrating'), - ('limit', 1), - ('marker', 'test_kp'), - ('changes_since', '2019-08-07T08:03:25Z'), - ('changes_before', '2019-08-09T08:03:25Z'), - ('project', self.project.id) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'status': 'migrating', - 'limit': 1, - 'marker': 'test_kp', - 'host': None, - 'project_id': self.project.id, - 'changes_since': '2019-08-07T08:03:25Z', - 'changes_before': "2019-08-09T08:03:25Z", - } - - self.migrations_mock.list.assert_called_with(**kwargs) - - self.MIGRATION_COLUMNS.insert( - len(self.MIGRATION_COLUMNS) - 2, "Project") - self.MIGRATION_FIELDS.insert( - len(self.MIGRATION_FIELDS) - 2, "project_id") - self.assertEqual(self.MIGRATION_COLUMNS, columns) - self.assertEqual(tuple(self.data), tuple(data)) - # Clean up global variables MIGRATION_COLUMNS - self.MIGRATION_COLUMNS.remove('Project') - # Clean up global variables MIGRATION_FIELDS - self.MIGRATION_FIELDS.remove('project_id') - - def test_get_migrations_with_project_pre_v280(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.79') - arglist = [ - '--status', 'migrating', - '--changes-before', '2019-08-09T08:03:25Z', - '--project', '0c2accde-644a-45fa-8c10-e76debc7fbc3' - ] - verifylist = [ - ('status', 'migrating'), - ('changes_before', '2019-08-09T08:03:25Z'), - ('project', '0c2accde-644a-45fa-8c10-e76debc7fbc3') - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.80 or greater is required', - str(ex)) - - def test_server_migration_list_with_user(self): - arglist = [ - '--status', 'migrating', - '--limit', '1', - '--marker', 'test_kp', - '--changes-since', '2019-08-07T08:03:25Z', - '--changes-before', '2019-08-09T08:03:25Z', - '--user', self.user.id, - ] - verifylist = [ - ('status', 'migrating'), - ('limit', 1), - ('marker', 'test_kp'), - ('changes_since', '2019-08-07T08:03:25Z'), - ('changes_before', '2019-08-09T08:03:25Z'), - ('user', self.user.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'status': 'migrating', - 'limit': 1, - 'marker': 'test_kp', - 'host': None, - 'user_id': self.user.id, - 'changes_since': '2019-08-07T08:03:25Z', - 'changes_before': "2019-08-09T08:03:25Z", - } - - self.migrations_mock.list.assert_called_with(**kwargs) - - self.MIGRATION_COLUMNS.insert( - len(self.MIGRATION_COLUMNS) - 2, "User") - self.MIGRATION_FIELDS.insert( - len(self.MIGRATION_FIELDS) - 2, "user_id") - self.assertEqual(self.MIGRATION_COLUMNS, columns) - self.assertEqual(tuple(self.data), tuple(data)) - # Clean up global variables MIGRATION_COLUMNS - self.MIGRATION_COLUMNS.remove('User') - # Clean up global variables MIGRATION_FIELDS - self.MIGRATION_FIELDS.remove('user_id') - - def test_get_migrations_with_user_pre_v280(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.79') - arglist = [ - '--status', 'migrating', - '--changes-before', '2019-08-09T08:03:25Z', - '--user', self.user.id, - ] - verifylist = [ - ('status', 'migrating'), - ('changes_before', '2019-08-09T08:03:25Z'), - ('user', self.user.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.80 or greater is required', - str(ex)) - - def test_server_migration_list_with_project_and_user(self): - arglist = [ - '--status', 'migrating', - '--limit', '1', - '--changes-since', '2019-08-07T08:03:25Z', - '--changes-before', '2019-08-09T08:03:25Z', - '--project', self.project.id, - '--user', self.user.id, - ] - verifylist = [ - ('status', 'migrating'), - ('limit', 1), - ('changes_since', '2019-08-07T08:03:25Z'), - ('changes_before', '2019-08-09T08:03:25Z'), - ('project', self.project.id), - ('user', self.user.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'status': 'migrating', - 'limit': 1, - 'host': None, - 'project_id': self.project.id, - 'user_id': self.user.id, - 'changes_since': '2019-08-07T08:03:25Z', - 'changes_before': "2019-08-09T08:03:25Z", - } - - self.migrations_mock.list.assert_called_with(**kwargs) - - self.MIGRATION_COLUMNS.insert( - len(self.MIGRATION_COLUMNS) - 2, "Project") - self.MIGRATION_FIELDS.insert( - len(self.MIGRATION_FIELDS) - 2, "project_id") - self.MIGRATION_COLUMNS.insert( - len(self.MIGRATION_COLUMNS) - 2, "User") - self.MIGRATION_FIELDS.insert( - len(self.MIGRATION_FIELDS) - 2, "user_id") - self.assertEqual(self.MIGRATION_COLUMNS, columns) - self.assertEqual(tuple(self.data), tuple(data)) - # Clean up global variables MIGRATION_COLUMNS - self.MIGRATION_COLUMNS.remove('Project') - self.MIGRATION_FIELDS.remove('project_id') - self.MIGRATION_COLUMNS.remove('User') - self.MIGRATION_FIELDS.remove('user_id') - - def test_get_migrations_with_project_and_user_pre_v280(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.79') - arglist = [ - '--status', 'migrating', - '--changes-before', '2019-08-09T08:03:25Z', - '--project', self.project.id, - '--user', self.user.id, - ] - verifylist = [ - ('status', 'migrating'), - ('changes_before', '2019-08-09T08:03:25Z'), - ('project', self.project.id), - ('user', self.user.id) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.80 or greater is required', - str(ex)) - - -class TestServerMigrationShow(TestServer): - - def setUp(self): - super().setUp() - - self.server = compute_fakes.FakeServer.create_one_server() - self.servers_mock.get.return_value = self.server - - self.server_migration = compute_fakes.FakeServerMigration\ - .create_one_server_migration() - self.server_migrations_mock.get.return_value = self.server_migration - - self.columns = ( - 'ID', - 'Server UUID', - 'Status', - 'Source Compute', - 'Source Node', - 'Dest Compute', - 'Dest Host', - 'Dest Node', - 'Memory Total Bytes', - 'Memory Processed Bytes', - 'Memory Remaining Bytes', - 'Disk Total Bytes', - 'Disk Processed Bytes', - 'Disk Remaining Bytes', - 'Created At', - 'Updated At', - ) - - self.data = ( - self.server_migration.id, - self.server_migration.server_uuid, - self.server_migration.status, - self.server_migration.source_compute, - self.server_migration.source_node, - self.server_migration.dest_compute, - self.server_migration.dest_host, - self.server_migration.dest_node, - self.server_migration.memory_total_bytes, - self.server_migration.memory_processed_bytes, - self.server_migration.memory_remaining_bytes, - self.server_migration.disk_total_bytes, - self.server_migration.disk_processed_bytes, - self.server_migration.disk_remaining_bytes, - self.server_migration.created_at, - self.server_migration.updated_at, - ) - - # Get the command object to test - self.cmd = server.ShowMigration(self.app, None) - - def _test_server_migration_show(self): - arglist = [ - self.server.id, - '2', # arbitrary migration ID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - self.servers_mock.get.assert_called_with(self.server.id) - self.server_migrations_mock.get.assert_called_with( - self.server.id, '2',) - - def test_server_migration_show(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.24') - - self._test_server_migration_show() - - def test_server_migration_show_v259(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.59') - - self.columns += ('UUID',) - self.data += (self.server_migration.uuid,) - - self._test_server_migration_show() - - def test_server_migration_show_v280(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.80') - - self.columns += ('UUID', 'User ID', 'Project ID') - self.data += ( - self.server_migration.uuid, - self.server_migration.user_id, - self.server_migration.project_id, - ) - - self._test_server_migration_show() - - def test_server_migration_show_pre_v224(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.23') - - arglist = [ - self.server.id, - '2', # arbitrary migration ID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.24 or greater is required', - str(ex)) - - -class TestServerMigrationAbort(TestServer): - - def setUp(self): - super(TestServerMigrationAbort, self).setUp() - - self.server = compute_fakes.FakeServer.create_one_server() - - # Return value for utils.find_resource for server. - self.servers_mock.get.return_value = self.server - - # Get the command object to test - self.cmd = server.AbortMigration(self.app, None) - - def test_migration_abort(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.24') - - arglist = [ - self.server.id, - '2', # arbitrary migration ID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.servers_mock.get.assert_called_with(self.server.id) - self.server_migrations_mock.live_migration_abort.assert_called_with( - self.server.id, '2',) - self.assertIsNone(result) - - def test_migration_abort_pre_v224(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.23') - - arglist = [ - self.server.id, - '2', # arbitrary migration ID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.24 or greater is required', - str(ex)) - - -class TestServerMigrationForceComplete(TestServer): - - def setUp(self): - super(TestServerMigrationForceComplete, self).setUp() - - self.server = compute_fakes.FakeServer.create_one_server() - - # Return value for utils.find_resource for server. - self.servers_mock.get.return_value = self.server - - # Get the command object to test - self.cmd = server.ForceCompleteMigration(self.app, None) - - def test_migration_force_complete(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.22') - - arglist = [ - self.server.id, - '2', # arbitrary migration ID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.servers_mock.get.assert_called_with(self.server.id) - self.server_migrations_mock.live_migrate_force_complete\ - .assert_called_with(self.server.id, '2',) - self.assertIsNone(result) - - def test_migration_force_complete_pre_v222(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.21') - - arglist = [ - self.server.id, - '2', # arbitrary migration ID - ] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - ex = self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.22 or greater is required', - str(ex)) - - class TestServerPause(TestServer): def setUp(self): diff --git a/openstackclient/tests/unit/compute/v2/test_server_group.py b/openstackclient/tests/unit/compute/v2/test_server_group.py index 3ed19e2..655366a 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_group.py +++ b/openstackclient/tests/unit/compute/v2/test_server_group.py @@ -15,10 +15,9 @@ from unittest import mock -from novaclient import api_versions +from openstack import utils as sdk_utils from osc_lib.cli import format_columns from osc_lib import exceptions -from osc_lib import utils from openstackclient.compute.v2 import server_group from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes @@ -27,38 +26,7 @@ from openstackclient.tests.unit import utils as tests_utils class TestServerGroup(compute_fakes.TestComputev2): - fake_server_group = compute_fakes.FakeServerGroup.create_one_server_group() - - columns = ( - 'id', - 'members', - 'name', - 'policies', - 'project_id', - 'user_id', - ) - - data = ( - fake_server_group.id, - format_columns.ListColumn(fake_server_group.members), - fake_server_group.name, - format_columns.ListColumn(fake_server_group.policies), - fake_server_group.project_id, - fake_server_group.user_id, - ) - - def setUp(self): - super(TestServerGroup, self).setUp() - - # Get a shortcut to the ServerGroupsManager Mock - self.server_groups_mock = self.app.client_manager.compute.server_groups - self.server_groups_mock.reset_mock() - - -class TestServerGroupV264(TestServerGroup): - - fake_server_group = \ - compute_fakes.FakeServerGroupV264.create_one_server_group() + fake_server_group = compute_fakes.create_one_server_group() columns = ( 'id', @@ -66,31 +34,40 @@ class TestServerGroupV264(TestServerGroup): 'name', 'policy', 'project_id', + 'rules', 'user_id', ) data = ( fake_server_group.id, - format_columns.ListColumn(fake_server_group.members), + format_columns.ListColumn(fake_server_group.member_ids), fake_server_group.name, fake_server_group.policy, fake_server_group.project_id, + format_columns.DictColumn(fake_server_group.rules), fake_server_group.user_id, ) def setUp(self): - super(TestServerGroupV264, self).setUp() + super().setUp() + + # Create and get a shortcut to the compute client mock + self.app.client_manager.sdk_connection = mock.Mock() + self.sdk_client = self.app.client_manager.sdk_connection.compute + self.sdk_client.reset_mock() class TestServerGroupCreate(TestServerGroup): def setUp(self): - super(TestServerGroupCreate, self).setUp() + super().setUp() - self.server_groups_mock.create.return_value = self.fake_server_group + self.sdk_client.create_server_group.return_value = \ + self.fake_server_group self.cmd = server_group.CreateServerGroup(self.app, None) - def test_server_group_create(self): + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_server_group_create(self, sm_mock): arglist = [ '--policy', 'anti-affinity', 'affinity_group', @@ -101,18 +78,16 @@ class TestServerGroupCreate(TestServerGroup): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.server_groups_mock.create.assert_called_once_with( + self.sdk_client.create_server_group.assert_called_once_with( name=parsed_args.name, - policies=[parsed_args.policy], + policy=parsed_args.policy, ) self.assertCountEqual(self.columns, columns) self.assertCountEqual(self.data, data) - def test_server_group_create_with_soft_policies(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.15') - + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_server_group_create_with_soft_policies(self, sm_mock): arglist = [ '--policy', 'soft-anti-affinity', 'affinity_group', @@ -123,18 +98,16 @@ class TestServerGroupCreate(TestServerGroup): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.server_groups_mock.create.assert_called_once_with( + self.sdk_client.create_server_group.assert_called_once_with( name=parsed_args.name, - policies=[parsed_args.policy], + policy=parsed_args.policy, ) self.assertCountEqual(self.columns, columns) self.assertCountEqual(self.data, data) - def test_server_group_create_with_soft_policies_pre_v215(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.14') - + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_server_group_create_with_soft_policies_pre_v215(self, sm_mock): arglist = [ '--policy', 'soft-anti-affinity', 'affinity_group', @@ -152,10 +125,8 @@ class TestServerGroupCreate(TestServerGroup): '--os-compute-api-version 2.15 or greater is required', str(ex)) - def test_server_group_create_with_rules(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.64') - + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_server_group_create_with_rules(self, sm_mock): arglist = [ '--policy', 'soft-anti-affinity', '--rule', 'max_server_per_host=2', @@ -168,19 +139,18 @@ class TestServerGroupCreate(TestServerGroup): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.server_groups_mock.create.assert_called_once_with( + self.sdk_client.create_server_group.assert_called_once_with( name=parsed_args.name, - policy=parsed_args.policy, # should be 'policy', not 'policies' + policy=parsed_args.policy, rules=parsed_args.rules, ) self.assertCountEqual(self.columns, columns) self.assertCountEqual(self.data, data) - def test_server_group_create_with_rules_pre_v264(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.63') - + @mock.patch.object( + sdk_utils, 'supports_microversion', side_effect=[True, False]) + def test_server_group_create_with_rules_pre_v264(self, sm_mock): arglist = [ '--policy', 'soft-anti-affinity', '--rule', 'max_server_per_host=2', @@ -205,9 +175,9 @@ class TestServerGroupCreate(TestServerGroup): class TestServerGroupDelete(TestServerGroup): def setUp(self): - super(TestServerGroupDelete, self).setUp() + super().setUp() - self.server_groups_mock.get.return_value = self.fake_server_group + self.sdk_client.find_server_group.return_value = self.fake_server_group self.cmd = server_group.DeleteServerGroup(self.app, None) def test_server_group_delete(self): @@ -219,8 +189,10 @@ class TestServerGroupDelete(TestServerGroup): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.server_groups_mock.get.assert_called_once_with('affinity_group') - self.server_groups_mock.delete.assert_called_once_with( + self.sdk_client.find_server_group.assert_called_once_with( + 'affinity_group' + ) + self.sdk_client.delete_server_group.assert_called_once_with( self.fake_server_group.id ) self.assertIsNone(result) @@ -235,13 +207,15 @@ class TestServerGroupDelete(TestServerGroup): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.server_groups_mock.get.assert_any_call('affinity_group') - self.server_groups_mock.get.assert_any_call('anti_affinity_group') - self.server_groups_mock.delete.assert_called_with( + self.sdk_client.find_server_group.assert_any_call('affinity_group') + self.sdk_client.find_server_group.assert_any_call( + 'anti_affinity_group' + ) + self.sdk_client.delete_server_group.assert_called_with( self.fake_server_group.id ) - self.assertEqual(2, self.server_groups_mock.get.call_count) - self.assertEqual(2, self.server_groups_mock.delete.call_count) + self.assertEqual(2, self.sdk_client.find_server_group.call_count) + self.assertEqual(2, self.sdk_client.delete_server_group.call_count) self.assertIsNone(result) def test_server_group_delete_no_input(self): @@ -262,25 +236,23 @@ class TestServerGroupDelete(TestServerGroup): ('server_group', ['affinity_group', 'anti_affinity_group']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - find_mock_result = [self.fake_server_group, exceptions.CommandError] - with mock.patch.object(utils, 'find_resource', - side_effect=find_mock_result) as find_mock: - try: - self.cmd.take_action(parsed_args) - self.fail('CommandError should be raised.') - except exceptions.CommandError as e: - self.assertEqual('1 of 2 server groups failed to delete.', - str(e)) - - find_mock.assert_any_call(self.server_groups_mock, - 'affinity_group') - find_mock.assert_any_call(self.server_groups_mock, - 'anti_affinity_group') - - self.assertEqual(2, find_mock.call_count) - self.server_groups_mock.delete.assert_called_once_with( - self.fake_server_group.id - ) + + self.sdk_client.find_server_group.side_effect = [ + self.fake_server_group, exceptions.CommandError] + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 server groups failed to delete.', str(e)) + + self.sdk_client.find_server_group.assert_any_call('affinity_group') + self.sdk_client.find_server_group.assert_any_call( + 'anti_affinity_group' + ) + self.assertEqual(2, self.sdk_client.find_server_group.call_count) + self.sdk_client.delete_server_group.assert_called_once_with( + self.fake_server_group.id + ) class TestServerGroupList(TestServerGroup): @@ -300,28 +272,67 @@ class TestServerGroupList(TestServerGroup): 'User Id', ) + list_columns_v264 = ( + 'ID', + 'Name', + 'Policy', + ) + + list_columns_v264_long = ( + 'ID', + 'Name', + 'Policy', + 'Members', + 'Project Id', + 'User Id', + ) + list_data = (( TestServerGroup.fake_server_group.id, TestServerGroup.fake_server_group.name, - format_columns.ListColumn(TestServerGroup.fake_server_group.policies), + format_columns.ListColumn( + TestServerGroup.fake_server_group.policies + ), ),) list_data_long = (( TestServerGroup.fake_server_group.id, TestServerGroup.fake_server_group.name, - format_columns.ListColumn(TestServerGroup.fake_server_group.policies), - format_columns.ListColumn(TestServerGroup.fake_server_group.members), + format_columns.ListColumn( + TestServerGroup.fake_server_group.policies + ), + format_columns.ListColumn( + TestServerGroup.fake_server_group.member_ids + ), + TestServerGroup.fake_server_group.project_id, + TestServerGroup.fake_server_group.user_id, + ),) + + list_data_v264 = (( + TestServerGroup.fake_server_group.id, + TestServerGroup.fake_server_group.name, + TestServerGroup.fake_server_group.policy, + ),) + + list_data_v264_long = (( + TestServerGroup.fake_server_group.id, + TestServerGroup.fake_server_group.name, + TestServerGroup.fake_server_group.policy, + format_columns.ListColumn( + TestServerGroup.fake_server_group.member_ids + ), TestServerGroup.fake_server_group.project_id, TestServerGroup.fake_server_group.user_id, ),) def setUp(self): - super(TestServerGroupList, self).setUp() + super().setUp() - self.server_groups_mock.list.return_value = [self.fake_server_group] + self.sdk_client.server_groups.return_value = [self.fake_server_group] self.cmd = server_group.ListServerGroup(self.app, None) - def test_server_group_list(self): + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_server_group_list(self, sm_mock): arglist = [] verifylist = [ ('all_projects', False), @@ -332,12 +343,13 @@ class TestServerGroupList(TestServerGroup): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.server_groups_mock.list.assert_called_once_with() + self.sdk_client.server_groups.assert_called_once_with() self.assertCountEqual(self.list_columns, columns) self.assertCountEqual(self.list_data, tuple(data)) - def test_server_group_list_with_all_projects_and_long(self): + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_server_group_list_with_all_projects_and_long(self, sm_mock): arglist = [ '--all-projects', '--long', @@ -350,13 +362,14 @@ class TestServerGroupList(TestServerGroup): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.server_groups_mock.list.assert_called_once_with( + self.sdk_client.server_groups.assert_called_once_with( all_projects=True) self.assertCountEqual(self.list_columns_long, columns) self.assertCountEqual(self.list_data_long, tuple(data)) - def test_server_group_list_with_limit(self): + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_server_group_list_with_limit(self, sm_mock): arglist = [ '--limit', '1', ] @@ -370,9 +383,10 @@ class TestServerGroupList(TestServerGroup): parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - self.server_groups_mock.list.assert_called_once_with(limit=1) + self.sdk_client.server_groups.assert_called_once_with(limit=1) - def test_server_group_list_with_offset(self): + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_server_group_list_with_offset(self, sm_mock): arglist = [ '--offset', '5', ] @@ -386,51 +400,10 @@ class TestServerGroupList(TestServerGroup): parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - self.server_groups_mock.list.assert_called_once_with(offset=5) - - -class TestServerGroupListV264(TestServerGroupV264): - - list_columns = ( - 'ID', - 'Name', - 'Policy', - ) + self.sdk_client.server_groups.assert_called_once_with(offset=5) - list_columns_long = ( - 'ID', - 'Name', - 'Policy', - 'Members', - 'Project Id', - 'User Id', - ) - - list_data = (( - TestServerGroupV264.fake_server_group.id, - TestServerGroupV264.fake_server_group.name, - TestServerGroupV264.fake_server_group.policy, - ),) - - list_data_long = (( - TestServerGroupV264.fake_server_group.id, - TestServerGroupV264.fake_server_group.name, - TestServerGroupV264.fake_server_group.policy, - format_columns.ListColumn( - TestServerGroupV264.fake_server_group.members), - TestServerGroupV264.fake_server_group.project_id, - TestServerGroupV264.fake_server_group.user_id, - ),) - - def setUp(self): - super(TestServerGroupListV264, self).setUp() - - self.server_groups_mock.list.return_value = [self.fake_server_group] - self.cmd = server_group.ListServerGroup(self.app, None) - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.64') - - def test_server_group_list(self): + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_server_group_list_v264(self, sm_mock): arglist = [] verifylist = [ ('all_projects', False), @@ -438,12 +411,13 @@ class TestServerGroupListV264(TestServerGroupV264): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.server_groups_mock.list.assert_called_once_with() + self.sdk_client.server_groups.assert_called_once_with() - self.assertCountEqual(self.list_columns, columns) - self.assertCountEqual(self.list_data, tuple(data)) + self.assertCountEqual(self.list_columns_v264, columns) + self.assertCountEqual(self.list_data_v264, tuple(data)) - def test_server_group_list_with_all_projects_and_long(self): + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_server_group_list_with_all_projects_and_long_v264(self, sm_mock): arglist = [ '--all-projects', '--long', @@ -454,22 +428,23 @@ class TestServerGroupListV264(TestServerGroupV264): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.server_groups_mock.list.assert_called_once_with( + self.sdk_client.server_groups.assert_called_once_with( all_projects=True) - self.assertCountEqual(self.list_columns_long, columns) - self.assertCountEqual(self.list_data_long, tuple(data)) + self.assertCountEqual(self.list_columns_v264_long, columns) + self.assertCountEqual(self.list_data_v264_long, tuple(data)) class TestServerGroupShow(TestServerGroup): def setUp(self): - super(TestServerGroupShow, self).setUp() + super().setUp() - self.server_groups_mock.get.return_value = self.fake_server_group + self.sdk_client.find_server_group.return_value = self.fake_server_group self.cmd = server_group.ShowServerGroup(self.app, None) - def test_server_group_show(self): + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_server_group_show(self, sm_mock): arglist = [ 'affinity_group', ] diff --git a/openstackclient/tests/unit/compute/v2/test_server_migration.py b/openstackclient/tests/unit/compute/v2/test_server_migration.py new file mode 100644 index 0000000..c4cbac4 --- /dev/null +++ b/openstackclient/tests/unit/compute/v2/test_server_migration.py @@ -0,0 +1,1028 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from unittest import mock + +from novaclient import api_versions +from osc_lib import exceptions +from osc_lib import utils as common_utils + +from openstackclient.compute.v2 import server_migration +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes + + +class TestServerMigration(compute_fakes.TestComputev2): + + def setUp(self): + super().setUp() + + # Get a shortcut to the compute client ServerManager Mock + self.servers_mock = self.app.client_manager.compute.servers + self.servers_mock.reset_mock() + + # Get a shortcut to the compute client ServerMigrationsManager Mock + self.server_migrations_mock = \ + self.app.client_manager.compute.server_migrations + self.server_migrations_mock.reset_mock() + + # Get a shortcut to the compute client MigrationManager mock + self.migrations_mock = self.app.client_manager.compute.migrations + self.migrations_mock.reset_mock() + + self.app.client_manager.sdk_connection = mock.Mock() + self.app.client_manager.sdk_connection.compute = mock.Mock() + self.sdk_client = self.app.client_manager.sdk_connection.compute + + +class TestListMigration(TestServerMigration): + """Test fetch all migrations.""" + + MIGRATION_COLUMNS = [ + 'Source Node', 'Dest Node', 'Source Compute', + 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', + 'Old Flavor', 'New Flavor', 'Created At', 'Updated At' + ] + + # These are the fields that come back in the response from the REST API. + MIGRATION_FIELDS = [ + 'source_node', 'dest_node', 'source_compute', 'dest_compute', + 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id', + 'new_instance_type_id', 'created_at', 'updated_at' + ] + + def setUp(self): + super().setUp() + + self.server = compute_fakes.FakeServer.create_one_server() + self.servers_mock.get.return_value = self.server + + self.migrations = compute_fakes.FakeMigration.create_migrations( + count=3) + self.migrations_mock.list.return_value = self.migrations + + self.data = (common_utils.get_item_properties( + s, self.MIGRATION_FIELDS) for s in self.migrations) + + # Get the command object to test + self.cmd = server_migration.ListMigration(self.app, None) + + def test_server_migration_list_no_options(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': None, + 'host': None, + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_migration_list(self): + arglist = [ + '--server', 'server1', + '--host', 'host1', + '--status', 'migrating', + '--type', 'cold-migration', + ] + verifylist = [ + ('server', 'server1'), + ('host', 'host1'), + ('status', 'migrating'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'host': 'host1', + 'instance_uuid': self.server.id, + 'migration_type': 'migration', + } + + self.servers_mock.get.assert_called_with('server1') + self.migrations_mock.list.assert_called_with(**kwargs) + + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + +class TestListMigrationV223(TestListMigration): + """Test fetch all migrations. """ + + MIGRATION_COLUMNS = [ + 'Id', 'Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', + 'Dest Host', 'Status', 'Server UUID', 'Old Flavor', 'New Flavor', + 'Type', 'Created At', 'Updated At' + ] + + # These are the fields that come back in the response from the REST API. + MIGRATION_FIELDS = [ + 'id', 'source_node', 'dest_node', 'source_compute', 'dest_compute', + 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id', + 'new_instance_type_id', 'migration_type', 'created_at', 'updated_at' + ] + + def setUp(self): + super().setUp() + + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.23') + + def test_server_migration_list(self): + arglist = [ + '--status', 'migrating' + ] + verifylist = [ + ('status', 'migrating') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'host': None, + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + +class TestListMigrationV259(TestListMigration): + """Test fetch all migrations. """ + + MIGRATION_COLUMNS = [ + 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute', + 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', + 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' + ] + + # These are the fields that come back in the response from the REST API. + MIGRATION_FIELDS = [ + 'id', 'uuid', 'source_node', 'dest_node', 'source_compute', + 'dest_compute', 'dest_host', 'status', 'instance_uuid', + 'old_instance_type_id', 'new_instance_type_id', 'migration_type', + 'created_at', 'updated_at' + ] + + def setUp(self): + super().setUp() + + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.59') + + def test_server_migration_list(self): + arglist = [ + '--status', 'migrating', + '--limit', '1', + '--marker', 'test_kp', + '--changes-since', '2019-08-09T08:03:25Z' + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1), + ('marker', 'test_kp'), + ('changes_since', '2019-08-09T08:03:25Z') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'limit': 1, + 'marker': 'test_kp', + 'host': None, + 'changes_since': '2019-08-09T08:03:25Z', + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_migration_list_with_limit_pre_v259(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.58') + arglist = [ + '--status', 'migrating', + '--limit', '1' + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.59 or greater is required', + str(ex)) + + def test_server_migration_list_with_marker_pre_v259(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.58') + arglist = [ + '--status', 'migrating', + '--marker', 'test_kp' + ] + verifylist = [ + ('status', 'migrating'), + ('marker', 'test_kp') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.59 or greater is required', + str(ex)) + + def test_server_migration_list_with_changes_since_pre_v259(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.58') + arglist = [ + '--status', 'migrating', + '--changes-since', '2019-08-09T08:03:25Z' + ] + verifylist = [ + ('status', 'migrating'), + ('changes_since', '2019-08-09T08:03:25Z') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.59 or greater is required', + str(ex)) + + +class TestListMigrationV266(TestListMigration): + """Test fetch all migrations by changes-before. """ + + MIGRATION_COLUMNS = [ + 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute', + 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', + 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' + ] + + # These are the fields that come back in the response from the REST API. + MIGRATION_FIELDS = [ + 'id', 'uuid', 'source_node', 'dest_node', 'source_compute', + 'dest_compute', 'dest_host', 'status', 'instance_uuid', + 'old_instance_type_id', 'new_instance_type_id', 'migration_type', + 'created_at', 'updated_at' + ] + + def setUp(self): + super().setUp() + + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.66') + + def test_server_migration_list_with_changes_before(self): + arglist = [ + '--status', 'migrating', + '--limit', '1', + '--marker', 'test_kp', + '--changes-since', '2019-08-07T08:03:25Z', + '--changes-before', '2019-08-09T08:03:25Z' + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1), + ('marker', 'test_kp'), + ('changes_since', '2019-08-07T08:03:25Z'), + ('changes_before', '2019-08-09T08:03:25Z') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'limit': 1, + 'marker': 'test_kp', + 'host': None, + 'changes_since': '2019-08-07T08:03:25Z', + 'changes_before': '2019-08-09T08:03:25Z', + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_migration_list_with_changes_before_pre_v266(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.65') + arglist = [ + '--status', 'migrating', + '--changes-before', '2019-08-09T08:03:25Z' + ] + verifylist = [ + ('status', 'migrating'), + ('changes_before', '2019-08-09T08:03:25Z') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.66 or greater is required', + str(ex)) + + +class TestListMigrationV280(TestListMigration): + """Test fetch all migrations by user-id and/or project-id. """ + + MIGRATION_COLUMNS = [ + 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute', + 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', + 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' + ] + + # These are the fields that come back in the response from the REST API. + MIGRATION_FIELDS = [ + 'id', 'uuid', 'source_node', 'dest_node', 'source_compute', + 'dest_compute', 'dest_host', 'status', 'instance_uuid', + 'old_instance_type_id', 'new_instance_type_id', 'migration_type', + 'created_at', 'updated_at' + ] + + project = identity_fakes.FakeProject.create_one_project() + user = identity_fakes.FakeUser.create_one_user() + + def setUp(self): + super().setUp() + + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + + self.users_mock = self.app.client_manager.identity.users + self.users_mock.reset_mock() + + self.projects_mock.get.return_value = self.project + self.users_mock.get.return_value = self.user + + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.80') + + def test_server_migration_list_with_project(self): + arglist = [ + '--status', 'migrating', + '--limit', '1', + '--marker', 'test_kp', + '--changes-since', '2019-08-07T08:03:25Z', + '--changes-before', '2019-08-09T08:03:25Z', + '--project', self.project.id + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1), + ('marker', 'test_kp'), + ('changes_since', '2019-08-07T08:03:25Z'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('project', self.project.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'limit': 1, + 'marker': 'test_kp', + 'host': None, + 'project_id': self.project.id, + 'changes_since': '2019-08-07T08:03:25Z', + 'changes_before': "2019-08-09T08:03:25Z", + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.MIGRATION_COLUMNS.insert( + len(self.MIGRATION_COLUMNS) - 2, "Project") + self.MIGRATION_FIELDS.insert( + len(self.MIGRATION_FIELDS) - 2, "project_id") + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + # Clean up global variables MIGRATION_COLUMNS + self.MIGRATION_COLUMNS.remove('Project') + # Clean up global variables MIGRATION_FIELDS + self.MIGRATION_FIELDS.remove('project_id') + + def test_get_migrations_with_project_pre_v280(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.79') + arglist = [ + '--status', 'migrating', + '--changes-before', '2019-08-09T08:03:25Z', + '--project', '0c2accde-644a-45fa-8c10-e76debc7fbc3' + ] + verifylist = [ + ('status', 'migrating'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('project', '0c2accde-644a-45fa-8c10-e76debc7fbc3') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.80 or greater is required', + str(ex)) + + def test_server_migration_list_with_user(self): + arglist = [ + '--status', 'migrating', + '--limit', '1', + '--marker', 'test_kp', + '--changes-since', '2019-08-07T08:03:25Z', + '--changes-before', '2019-08-09T08:03:25Z', + '--user', self.user.id, + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1), + ('marker', 'test_kp'), + ('changes_since', '2019-08-07T08:03:25Z'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('user', self.user.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'limit': 1, + 'marker': 'test_kp', + 'host': None, + 'user_id': self.user.id, + 'changes_since': '2019-08-07T08:03:25Z', + 'changes_before': "2019-08-09T08:03:25Z", + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.MIGRATION_COLUMNS.insert( + len(self.MIGRATION_COLUMNS) - 2, "User") + self.MIGRATION_FIELDS.insert( + len(self.MIGRATION_FIELDS) - 2, "user_id") + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + # Clean up global variables MIGRATION_COLUMNS + self.MIGRATION_COLUMNS.remove('User') + # Clean up global variables MIGRATION_FIELDS + self.MIGRATION_FIELDS.remove('user_id') + + def test_get_migrations_with_user_pre_v280(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.79') + arglist = [ + '--status', 'migrating', + '--changes-before', '2019-08-09T08:03:25Z', + '--user', self.user.id, + ] + verifylist = [ + ('status', 'migrating'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('user', self.user.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.80 or greater is required', + str(ex)) + + def test_server_migration_list_with_project_and_user(self): + arglist = [ + '--status', 'migrating', + '--limit', '1', + '--changes-since', '2019-08-07T08:03:25Z', + '--changes-before', '2019-08-09T08:03:25Z', + '--project', self.project.id, + '--user', self.user.id, + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1), + ('changes_since', '2019-08-07T08:03:25Z'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('project', self.project.id), + ('user', self.user.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'limit': 1, + 'host': None, + 'project_id': self.project.id, + 'user_id': self.user.id, + 'changes_since': '2019-08-07T08:03:25Z', + 'changes_before': "2019-08-09T08:03:25Z", + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.MIGRATION_COLUMNS.insert( + len(self.MIGRATION_COLUMNS) - 2, "Project") + self.MIGRATION_FIELDS.insert( + len(self.MIGRATION_FIELDS) - 2, "project_id") + self.MIGRATION_COLUMNS.insert( + len(self.MIGRATION_COLUMNS) - 2, "User") + self.MIGRATION_FIELDS.insert( + len(self.MIGRATION_FIELDS) - 2, "user_id") + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + # Clean up global variables MIGRATION_COLUMNS + self.MIGRATION_COLUMNS.remove('Project') + self.MIGRATION_FIELDS.remove('project_id') + self.MIGRATION_COLUMNS.remove('User') + self.MIGRATION_FIELDS.remove('user_id') + + def test_get_migrations_with_project_and_user_pre_v280(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.79') + arglist = [ + '--status', 'migrating', + '--changes-before', '2019-08-09T08:03:25Z', + '--project', self.project.id, + '--user', self.user.id, + ] + verifylist = [ + ('status', 'migrating'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('project', self.project.id), + ('user', self.user.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.80 or greater is required', + str(ex)) + + +class TestServerMigrationShow(TestServerMigration): + + def setUp(self): + super().setUp() + + self.server = compute_fakes.FakeServer.create_one_server() + self.servers_mock.get.return_value = self.server + + self.server_migration = compute_fakes.FakeServerMigration\ + .create_one_server_migration() + self.server_migrations_mock.get.return_value = self.server_migration + + self.columns = ( + 'ID', + 'Server UUID', + 'Status', + 'Source Compute', + 'Source Node', + 'Dest Compute', + 'Dest Host', + 'Dest Node', + 'Memory Total Bytes', + 'Memory Processed Bytes', + 'Memory Remaining Bytes', + 'Disk Total Bytes', + 'Disk Processed Bytes', + 'Disk Remaining Bytes', + 'Created At', + 'Updated At', + ) + + self.data = ( + self.server_migration.id, + self.server_migration.server_uuid, + self.server_migration.status, + self.server_migration.source_compute, + self.server_migration.source_node, + self.server_migration.dest_compute, + self.server_migration.dest_host, + self.server_migration.dest_node, + self.server_migration.memory_total_bytes, + self.server_migration.memory_processed_bytes, + self.server_migration.memory_remaining_bytes, + self.server_migration.disk_total_bytes, + self.server_migration.disk_processed_bytes, + self.server_migration.disk_remaining_bytes, + self.server_migration.created_at, + self.server_migration.updated_at, + ) + + # Get the command object to test + self.cmd = server_migration.ShowMigration(self.app, None) + + def _test_server_migration_show(self): + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server_migrations_mock.get.assert_called_with( + self.server.id, '2',) + + def test_server_migration_show(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.24') + + self._test_server_migration_show() + + def test_server_migration_show_v259(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.59') + + self.columns += ('UUID',) + self.data += (self.server_migration.uuid,) + + self._test_server_migration_show() + + def test_server_migration_show_v280(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.80') + + self.columns += ('UUID', 'User ID', 'Project ID') + self.data += ( + self.server_migration.uuid, + self.server_migration.user_id, + self.server_migration.project_id, + ) + + self._test_server_migration_show() + + def test_server_migration_show_pre_v224(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.23') + + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.24 or greater is required', + str(ex)) + + def test_server_migration_show_by_uuid(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.59') + self.server_migrations_mock.list.return_value = [self.server_migration] + + self.columns += ('UUID',) + self.data += (self.server_migration.uuid,) + + arglist = [ + self.server.id, + self.server_migration.uuid, # arbitrary migration UUID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server_migrations_mock.list.assert_called_with(self.server.id) + self.server_migrations_mock.get.assert_not_called() + + def test_server_migration_show_by_uuid_no_matches(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.59') + self.server_migrations_mock.list.return_value = [] + + arglist = [ + self.server.id, + '69f95745-bfe3-4302-90f7-5b0022cba1ce', # arbitrary migration UUID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + 'In-progress live migration 69f95745-bfe3-4302-90f7-5b0022cba1ce', + str(ex)) + + def test_server_migration_show_by_uuid_pre_v259(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.58') + + arglist = [ + self.server.id, + '69f95745-bfe3-4302-90f7-5b0022cba1ce', # arbitrary migration UUID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.59 or greater is required', + str(ex)) + + def test_server_migration_show_invalid_id(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.24') + + arglist = [ + self.server.id, + 'foo', # invalid migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + 'The <migration> argument must be an ID or UUID', + str(ex)) + + +class TestServerMigrationAbort(TestServerMigration): + + def setUp(self): + super().setUp() + + self.server = compute_fakes.FakeServer.create_one_server() + + # Return value for utils.find_resource for server. + self.servers_mock.get.return_value = self.server + + # Get the command object to test + self.cmd = server_migration.AbortMigration(self.app, None) + + def test_migration_abort(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.24') + + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server_migrations_mock.live_migration_abort.assert_called_with( + self.server.id, '2',) + self.assertIsNone(result) + + def test_migration_abort_pre_v224(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.23') + + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.24 or greater is required', + str(ex)) + + def test_server_migration_abort_by_uuid(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.59') + + self.server_migration = compute_fakes.FakeServerMigration\ + .create_one_server_migration() + self.server_migrations_mock.list.return_value = [self.server_migration] + + arglist = [ + self.server.id, + self.server_migration.uuid, # arbitrary migration UUID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server_migrations_mock.list.assert_called_with(self.server.id) + self.server_migrations_mock.live_migration_abort.assert_called_with( + self.server.id, self.server_migration.id) + self.assertIsNone(result) + + def test_server_migration_abort_by_uuid_no_matches(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.59') + + self.server_migrations_mock.list.return_value = [] + + arglist = [ + self.server.id, + '69f95745-bfe3-4302-90f7-5b0022cba1ce', # arbitrary migration UUID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + 'In-progress live migration 69f95745-bfe3-4302-90f7-5b0022cba1ce', + str(ex)) + + def test_server_migration_abort_by_uuid_pre_v259(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.58') + + arglist = [ + self.server.id, + '69f95745-bfe3-4302-90f7-5b0022cba1ce', # arbitrary migration UUID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.59 or greater is required', + str(ex)) + + +class TestServerMigrationForceComplete(TestServerMigration): + + def setUp(self): + super().setUp() + + self.server = compute_fakes.FakeServer.create_one_server() + + # Return value for utils.find_resource for server. + self.servers_mock.get.return_value = self.server + + # Get the command object to test + self.cmd = server_migration.ForceCompleteMigration(self.app, None) + + def test_migration_force_complete(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.22') + + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server_migrations_mock.live_migrate_force_complete\ + .assert_called_with(self.server.id, '2',) + self.assertIsNone(result) + + def test_migration_force_complete_pre_v222(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.21') + + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.22 or greater is required', + str(ex)) + + def test_server_migration_force_complete_by_uuid(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.59') + + self.server_migration = compute_fakes.FakeServerMigration\ + .create_one_server_migration() + self.server_migrations_mock.list.return_value = [self.server_migration] + + arglist = [ + self.server.id, + self.server_migration.uuid, # arbitrary migration UUID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server_migrations_mock.list.assert_called_with(self.server.id) + self.server_migrations_mock.live_migrate_force_complete\ + .assert_called_with(self.server.id, self.server_migration.id) + self.assertIsNone(result) + + def test_server_migration_force_complete_by_uuid_no_matches(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.59') + + self.server_migrations_mock.list.return_value = [] + + arglist = [ + self.server.id, + '69f95745-bfe3-4302-90f7-5b0022cba1ce', # arbitrary migration UUID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + 'In-progress live migration 69f95745-bfe3-4302-90f7-5b0022cba1ce', + str(ex)) + + def test_server_migration_force_complete_by_uuid_pre_v259(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.58') + + arglist = [ + self.server.id, + '69f95745-bfe3-4302-90f7-5b0022cba1ce', # arbitrary migration UUID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.59 or greater is required', + str(ex)) diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 7ccc9f0..02f7b41 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -18,6 +18,7 @@ import os import tempfile from unittest import mock +from cinderclient import api_versions from openstack import exceptions as sdk_exceptions from osc_lib.cli import format_columns from osc_lib import exceptions @@ -25,12 +26,13 @@ from osc_lib import exceptions from openstackclient.image.v2 import image from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes from openstackclient.tests.unit.image.v2 import fakes as image_fakes +from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes -class TestImage(image_fakes.TestImagev2): +class TestImage(image_fakes.TestImagev2, volume_fakes.TestVolume): def setUp(self): - super(TestImage, self).setUp() + super().setUp() # Get shortcuts to mocked image client self.client = self.app.client_manager.image @@ -40,6 +42,13 @@ class TestImage(image_fakes.TestImagev2): self.project_mock.reset_mock() self.domain_mock = self.app.client_manager.identity.domains self.domain_mock.reset_mock() + self.volumes_mock = self.app.client_manager.volume.volumes + fake_body = { + 'os-volume_upload_image': + {'volume_type': {'name': 'fake_type'}}} + self.volumes_mock.upload_to_image.return_value = ( + 200, fake_body) + self.volumes_mock.reset_mock() def setup_images_mock(self, count): images = image_fakes.create_images(count=count) @@ -53,7 +62,7 @@ class TestImageCreate(TestImage): domain = identity_fakes.FakeDomain.create_one_domain() def setUp(self): - super(TestImageCreate, self).setUp() + super().setUp() self.new_image = image_fakes.create_one_image() self.client.create_image.return_value = self.new_image @@ -125,10 +134,8 @@ class TestImageCreate(TestImage): ('disk_format', 'ami'), ('min_disk', 10), ('min_ram', 4), - ('protected', self.new_image.is_protected), - ('unprotected', not self.new_image.is_protected), - ('public', self.new_image.visibility == 'public'), - ('private', self.new_image.visibility == 'private'), + ('is_protected', self.new_image.is_protected), + ('visibility', self.new_image.visibility), ('project', self.new_image.owner_id), ('project_domain', self.domain.id), ('name', self.new_image.name), @@ -179,10 +186,8 @@ class TestImageCreate(TestImage): ('disk_format', 'ami'), ('min_disk', 10), ('min_ram', 4), - ('protected', True), - ('unprotected', False), - ('public', False), - ('private', True), + ('is_protected', True), + ('visibility', 'private'), ('project', 'unexist_owner'), ('name', 'graven'), ] @@ -213,10 +218,8 @@ class TestImageCreate(TestImage): ] verifylist = [ ('file', imagefile.name), - ('protected', self.new_image.is_protected), - ('unprotected', not self.new_image.is_protected), - ('public', self.new_image.visibility == 'public'), - ('private', self.new_image.visibility == 'private'), + ('is_protected', self.new_image.is_protected), + ('visibility', self.new_image.visibility), ('properties', {'Alpha': '1', 'Beta': '2'}), ('tags', self.new_image.tags), ('name', self.new_image.name), @@ -287,6 +290,101 @@ class TestImageCreate(TestImage): use_import=True ) + @mock.patch('osc_lib.utils.find_resource') + @mock.patch('openstackclient.image.v2.image.get_data_file') + def test_image_create_from_volume(self, mock_get_data_f, mock_get_vol): + + fake_vol_id = 'fake-volume-id' + mock_get_data_f.return_value = (None, None) + + class FakeVolume: + id = fake_vol_id + + mock_get_vol.return_value = FakeVolume() + + arglist = [ + '--volume', fake_vol_id, + self.new_image.name, + ] + verifylist = [ + ('name', self.new_image.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.upload_to_image.assert_called_with( + fake_vol_id, + False, + self.new_image.name, + 'bare', + 'raw' + ) + + @mock.patch('osc_lib.utils.find_resource') + @mock.patch('openstackclient.image.v2.image.get_data_file') + def test_image_create_from_volume_fail(self, mock_get_data_f, + mock_get_vol): + + fake_vol_id = 'fake-volume-id' + mock_get_data_f.return_value = (None, None) + + class FakeVolume: + id = fake_vol_id + + mock_get_vol.return_value = FakeVolume() + + arglist = [ + '--volume', fake_vol_id, + self.new_image.name, + '--public' + ] + verifylist = [ + ('name', self.new_image.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + @mock.patch('osc_lib.utils.find_resource') + @mock.patch('openstackclient.image.v2.image.get_data_file') + def test_image_create_from_volume_v31(self, mock_get_data_f, + mock_get_vol): + + self.app.client_manager.volume.api_version = ( + api_versions.APIVersion('3.1')) + + fake_vol_id = 'fake-volume-id' + mock_get_data_f.return_value = (None, None) + + class FakeVolume: + id = fake_vol_id + + mock_get_vol.return_value = FakeVolume() + + arglist = [ + '--volume', fake_vol_id, + self.new_image.name, + '--public' + ] + verifylist = [ + ('name', self.new_image.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.upload_to_image.assert_called_with( + fake_vol_id, + False, + self.new_image.name, + 'bare', + 'raw', + visibility='public', + protected=False + ) + class TestAddProjectToImage(TestImage): @@ -317,7 +415,7 @@ class TestAddProjectToImage(TestImage): ) def setUp(self): - super(TestAddProjectToImage, self).setUp() + super().setUp() # This is the return value for utils.find_resource() self.client.find_image.return_value = self._image @@ -381,7 +479,7 @@ class TestAddProjectToImage(TestImage): class TestImageDelete(TestImage): def setUp(self): - super(TestImageDelete, self).setUp() + super().setUp() self.client.delete_image.return_value = None @@ -472,7 +570,7 @@ class TestImageList(TestImage): ), def setUp(self): - super(TestImageList, self).setUp() + super().setUp() self.client.images.side_effect = [[self._image], []] @@ -482,10 +580,7 @@ class TestImageList(TestImage): def test_image_list_no_options(self): arglist = [] verifylist = [ - ('public', False), - ('private', False), - ('community', False), - ('shared', False), + ('visibility', None), ('long', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -506,10 +601,7 @@ class TestImageList(TestImage): '--public', ] verifylist = [ - ('public', True), - ('private', False), - ('community', False), - ('shared', False), + ('visibility', 'public'), ('long', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -530,10 +622,7 @@ class TestImageList(TestImage): '--private', ] verifylist = [ - ('public', False), - ('private', True), - ('community', False), - ('shared', False), + ('visibility', 'private'), ('long', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -554,10 +643,7 @@ class TestImageList(TestImage): '--community', ] verifylist = [ - ('public', False), - ('private', False), - ('community', True), - ('shared', False), + ('visibility', 'community'), ('long', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -578,10 +664,7 @@ class TestImageList(TestImage): '--shared', ] verifylist = [ - ('public', False), - ('private', False), - ('community', False), - ('shared', True), + ('visibility', 'shared'), ('long', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -597,16 +680,34 @@ class TestImageList(TestImage): self.assertEqual(self.columns, columns) self.assertCountEqual(self.datalist, tuple(data)) + def test_image_list_all_option(self): + arglist = [ + '--all', + ] + verifylist = [ + ('visibility', 'all'), + ('long', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + self.client.images.assert_called_with( + visibility='all', + ) + + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.datalist, tuple(data)) + def test_image_list_shared_member_status_option(self): arglist = [ '--shared', '--member-status', 'all' ] verifylist = [ - ('public', False), - ('private', False), - ('community', False), - ('shared', True), + ('visibility', 'shared'), ('long', False), ('member_status', 'all') ] @@ -630,10 +731,7 @@ class TestImageList(TestImage): '--member-status', 'ALl' ] verifylist = [ - ('public', False), - ('private', False), - ('community', False), - ('shared', True), + ('visibility', 'shared'), ('long', False), ('member_status', 'all') ] @@ -823,7 +921,7 @@ class TestImageList(TestImage): '--hidden', ] verifylist = [ - ('hidden', True), + ('is_hidden', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -835,15 +933,16 @@ class TestImageList(TestImage): def test_image_list_tag_option(self): arglist = [ '--tag', 'abc', + '--tag', 'cba' ] verifylist = [ - ('tag', 'abc'), + ('tag', ['abc', 'cba']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.client.images.assert_called_with( - tag='abc' + tag=['abc', 'cba'] ) @@ -869,7 +968,7 @@ class TestListImageProjects(TestImage): )] def setUp(self): - super(TestListImageProjects, self).setUp() + super().setUp() self.client.find_image.return_value = self._image self.client.members.return_value = [self.member] @@ -899,7 +998,7 @@ class TestRemoveProjectImage(TestImage): domain = identity_fakes.FakeDomain.create_one_domain() def setUp(self): - super(TestRemoveProjectImage, self).setUp() + super().setUp() self._image = image_fakes.create_one_image() # This is the return value for utils.find_resource() @@ -963,7 +1062,7 @@ class TestImageSet(TestImage): _image = image_fakes.create_one_image({'tags': []}) def setUp(self): - super(TestImageSet, self).setUp() + super().setUp() self.project_mock.get.return_value = self.project @@ -1145,10 +1244,8 @@ class TestImageSet(TestImage): 'graven', ] verifylist = [ - ('protected', True), - ('unprotected', False), - ('public', False), - ('private', True), + ('is_protected', True), + ('visibility', 'private'), ('image', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1173,10 +1270,8 @@ class TestImageSet(TestImage): 'graven', ] verifylist = [ - ('protected', False), - ('unprotected', True), - ('public', True), - ('private', False), + ('is_protected', False), + ('visibility', 'public'), ('image', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1395,7 +1490,7 @@ class TestImageSet(TestImage): 'graven', ] verifylist = [ - ('visibility', '1-mile'), + ('dead_visibility', '1-mile'), ('image', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1437,9 +1532,8 @@ class TestImageSet(TestImage): 'graven', ] verifylist = [ - ('hidden', True), - ('public', True), - ('private', False), + ('is_hidden', True), + ('visibility', 'public'), ('image', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1464,9 +1558,8 @@ class TestImageSet(TestImage): 'graven', ] verifylist = [ - ('hidden', False), - ('public', True), - ('private', False), + ('is_hidden', False), + ('visibility', 'public'), ('image', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1506,7 +1599,7 @@ class TestImageShow(TestImage): ) def setUp(self): - super(TestImageShow, self).setUp() + super().setUp() self.client.find_image = mock.Mock(return_value=self._data) @@ -1562,7 +1655,7 @@ class TestImageShow(TestImage): class TestImageUnset(TestImage): def setUp(self): - super(TestImageUnset, self).setUp() + super().setUp() attrs = {} attrs['tags'] = ['test'] @@ -1661,7 +1754,7 @@ class TestImageSave(TestImage): image = image_fakes.create_one_image({}) def setUp(self): - super(TestImageSave, self).setUp() + super().setUp() self.client.find_image.return_value = self.image self.client.download_image.return_value = self.image @@ -1690,7 +1783,7 @@ class TestImageSave(TestImage): class TestImageGetData(TestImage): def setUp(self): - super(TestImageGetData, self).setUp() + super().setUp() self.args = mock.Mock() def test_get_data_file_file(self): diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 2f839d1..06c5822 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -18,8 +18,21 @@ from random import randint from unittest import mock import uuid +from openstack.network.v2 import address_group as _address_group +from openstack.network.v2 import address_scope as _address_scope +from openstack.network.v2 import agent as network_agent +from openstack.network.v2 import auto_allocated_topology as allocated_topology +from openstack.network.v2 import availability_zone as _availability_zone +from openstack.network.v2 import flavor as _flavor from openstack.network.v2 import local_ip as _local_ip from openstack.network.v2 import local_ip_association as _local_ip_association +from openstack.network.v2 import network as _network +from openstack.network.v2 import network_ip_availability as _ip_availability +from openstack.network.v2 import network_segment_range as _segment_range +from openstack.network.v2 import port as _port +from openstack.network.v2 import rbac_policy as network_rbac +from openstack.network.v2 import segment as _segment +from openstack.network.v2 import service_profile as _flavor_profile from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 @@ -45,9 +58,11 @@ QUOTA = { RULE_TYPE_BANDWIDTH_LIMIT = 'bandwidth-limit' RULE_TYPE_DSCP_MARKING = 'dscp-marking' RULE_TYPE_MINIMUM_BANDWIDTH = 'minimum-bandwidth' +RULE_TYPE_MINIMUM_PACKET_RATE = 'minimum-packet-rate' VALID_QOS_RULES = [RULE_TYPE_BANDWIDTH_LIMIT, RULE_TYPE_DSCP_MARKING, - RULE_TYPE_MINIMUM_BANDWIDTH] + RULE_TYPE_MINIMUM_BANDWIDTH, + RULE_TYPE_MINIMUM_PACKET_RATE] VALID_DSCP_MARKS = [0, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 46, 48, 56] @@ -86,271 +101,6 @@ class TestNetworkV2(utils.TestCommand): ) -class FakeAddressGroup(object): - """Fake one or more address groups.""" - - @staticmethod - def create_one_address_group(attrs=None): - """Create a fake address group. - - :param Dictionary attrs: - A dictionary with all attributes - :return: - A FakeResource object with name, id, etc. - """ - attrs = attrs or {} - - # Set default attributes. - address_group_attrs = { - 'name': 'address-group-name-' + uuid.uuid4().hex, - 'description': 'address-group-description-' + uuid.uuid4().hex, - 'id': 'address-group-id-' + uuid.uuid4().hex, - 'project_id': 'project-id-' + uuid.uuid4().hex, - 'addresses': ['10.0.0.1/32'], - 'location': 'MUNCHMUNCHMUNCH', - } - - # Overwrite default attributes. - address_group_attrs.update(attrs) - - address_group = fakes.FakeResource( - info=copy.deepcopy(address_group_attrs), - loaded=True) - - return address_group - - @staticmethod - def create_address_groups(attrs=None, count=2): - """Create multiple fake address groups. - - :param Dictionary attrs: - A dictionary with all attributes - :param int count: - The number of address groups to fake - :return: - A list of FakeResource objects faking the address groups - """ - address_groups = [] - for i in range(0, count): - address_groups.append( - FakeAddressGroup.create_one_address_group(attrs)) - - return address_groups - - @staticmethod - def get_address_groups(address_groups=None, count=2): - """Get an iterable Mock object with a list of faked address groups. - - If address groups list is provided, then initialize the Mock object - with the list. Otherwise create one. - - :param List address_groups: - A list of FakeResource objects faking address groups - :param int count: - The number of address groups to fake - :return: - An iterable Mock object with side_effect set to a list of faked - address groups - """ - if address_groups is None: - address_groups = FakeAddressGroup.create_address_groups(count) - return mock.Mock(side_effect=address_groups) - - -class FakeAddressScope(object): - """Fake one or more address scopes.""" - - @staticmethod - def create_one_address_scope(attrs=None): - """Create a fake address scope. - - :param Dictionary attrs: - A dictionary with all attributes - :return: - A FakeResource object with name, id, etc. - """ - attrs = attrs or {} - - # Set default attributes. - address_scope_attrs = { - 'name': 'address-scope-name-' + uuid.uuid4().hex, - 'id': 'address-scope-id-' + uuid.uuid4().hex, - 'project_id': 'project-id-' + uuid.uuid4().hex, - 'shared': False, - 'ip_version': 4, - 'location': 'MUNCHMUNCHMUNCH', - } - - # Overwrite default attributes. - address_scope_attrs.update(attrs) - - address_scope = fakes.FakeResource( - info=copy.deepcopy(address_scope_attrs), - loaded=True) - - # Set attributes with special mapping in OpenStack SDK. - address_scope.is_shared = address_scope_attrs['shared'] - - return address_scope - - @staticmethod - def create_address_scopes(attrs=None, count=2): - """Create multiple fake address scopes. - - :param Dictionary attrs: - A dictionary with all attributes - :param int count: - The number of address scopes to fake - :return: - A list of FakeResource objects faking the address scopes - """ - address_scopes = [] - for i in range(0, count): - address_scopes.append( - FakeAddressScope.create_one_address_scope(attrs)) - - return address_scopes - - @staticmethod - def get_address_scopes(address_scopes=None, count=2): - """Get an iterable Mock object with a list of faked address scopes. - - If address scopes list is provided, then initialize the Mock object - with the list. Otherwise create one. - - :param List address_scopes: - A list of FakeResource objects faking address scopes - :param int count: - The number of address scopes to fake - :return: - An iterable Mock object with side_effect set to a list of faked - address scopes - """ - if address_scopes is None: - address_scopes = FakeAddressScope.create_address_scopes(count) - return mock.Mock(side_effect=address_scopes) - - -class FakeAutoAllocatedTopology(object): - """Fake Auto Allocated Topology""" - - @staticmethod - def create_one_topology(attrs=None): - attrs = attrs or {} - - auto_allocated_topology_attrs = { - 'id': 'network-id-' + uuid.uuid4().hex, - 'project_id': 'project-id-' + uuid.uuid4().hex, - } - - auto_allocated_topology_attrs.update(attrs) - - auto_allocated_topology = fakes.FakeResource( - info=copy.deepcopy(auto_allocated_topology_attrs), - loaded=True) - - return auto_allocated_topology - - -class FakeAvailabilityZone(object): - """Fake one or more network availability zones (AZs).""" - - @staticmethod - def create_one_availability_zone(attrs=None): - """Create a fake AZ. - - :param Dictionary attrs: - A dictionary with all attributes - :return: - A FakeResource object with name, state, etc. - """ - attrs = attrs or {} - - # Set default attributes. - availability_zone = { - 'name': uuid.uuid4().hex, - 'state': 'available', - 'resource': 'network', - } - - # Overwrite default attributes. - availability_zone.update(attrs) - - availability_zone = fakes.FakeResource( - info=copy.deepcopy(availability_zone), - loaded=True) - return availability_zone - - @staticmethod - def create_availability_zones(attrs=None, count=2): - """Create multiple fake AZs. - - :param Dictionary attrs: - A dictionary with all attributes - :param int count: - The number of AZs to fake - :return: - A list of FakeResource objects faking the AZs - """ - availability_zones = [] - for i in range(0, count): - availability_zone = \ - FakeAvailabilityZone.create_one_availability_zone(attrs) - availability_zones.append(availability_zone) - - return availability_zones - - -class FakeIPAvailability(object): - """Fake one or more network ip availabilities.""" - - @staticmethod - def create_one_ip_availability(attrs=None): - """Create a fake list with ip availability stats of a network. - - :param Dictionary attrs: - A dictionary with all attributes - :return: - A FakeResource object with network_name, network_id, etc. - """ - attrs = attrs or {} - - # Set default attributes. - network_ip_attrs = { - 'network_id': 'network-id-' + uuid.uuid4().hex, - 'network_name': 'network-name-' + uuid.uuid4().hex, - 'project_id': '', - 'subnet_ip_availability': [], - 'total_ips': 254, - 'used_ips': 6, - 'location': 'MUNCHMUNCHMUNCH', - } - network_ip_attrs.update(attrs) - - network_ip_availability = fakes.FakeResource( - info=copy.deepcopy(network_ip_attrs), - loaded=True) - - return network_ip_availability - - @staticmethod - def create_ip_availability(count=2): - """Create fake list of ip availability stats of multiple networks. - - :param int count: - The number of networks to fake - :return: - A list of FakeResource objects faking network ip availability stats - """ - network_ip_availabilities = [] - for i in range(0, count): - network_ip_availability = \ - FakeIPAvailability.create_one_ip_availability() - network_ip_availabilities.append(network_ip_availability) - - return network_ip_availabilities - - class FakeExtension(object): """Fake one or more extension.""" @@ -384,582 +134,6 @@ class FakeExtension(object): return extension -class FakeNetwork(object): - """Fake one or more networks.""" - - @staticmethod - def create_one_network(attrs=None): - """Create a fake network. - - :param Dictionary attrs: - A dictionary with all attributes - :return: - A FakeResource object, with id, name, etc. - """ - attrs = attrs or {} - - # Set default attributes. - network_attrs = { - 'id': 'network-id-' + uuid.uuid4().hex, - 'name': 'network-name-' + uuid.uuid4().hex, - 'status': 'ACTIVE', - 'description': 'network-description-' + uuid.uuid4().hex, - 'dns_domain': 'example.org.', - 'mtu': '1350', - 'project_id': 'project-id-' + uuid.uuid4().hex, - 'admin_state_up': True, - 'shared': False, - 'subnets': ['a', 'b'], - 'provider:network_type': 'vlan', - 'provider:physical_network': 'physnet1', - 'provider:segmentation_id': "400", - 'router:external': True, - 'availability_zones': [], - 'availability_zone_hints': [], - 'is_default': False, - 'port_security_enabled': True, - 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, - 'ipv4_address_scope': 'ipv4' + uuid.uuid4().hex, - 'ipv6_address_scope': 'ipv6' + uuid.uuid4().hex, - 'tags': [], - 'location': 'MUNCHMUNCHMUNCH', - } - - # Overwrite default attributes. - network_attrs.update(attrs) - - network = fakes.FakeResource(info=copy.deepcopy(network_attrs), - loaded=True) - - # Set attributes with special mapping in OpenStack SDK. - network.is_router_external = network_attrs['router:external'] - network.is_admin_state_up = network_attrs['admin_state_up'] - network.is_port_security_enabled = \ - network_attrs['port_security_enabled'] - network.subnet_ids = network_attrs['subnets'] - network.is_shared = network_attrs['shared'] - network.is_tags = network_attrs['tags'] - network.provider_network_type = \ - network_attrs['provider:network_type'] - network.provider_physical_network = \ - network_attrs['provider:physical_network'] - network.provider_segmentation_id = \ - network_attrs['provider:segmentation_id'] - network.ipv4_address_scope_id = \ - network_attrs['ipv4_address_scope'] - network.ipv6_address_scope_id = \ - network_attrs['ipv6_address_scope'] - - return network - - @staticmethod - def create_networks(attrs=None, count=2): - """Create multiple fake networks. - - :param Dictionary attrs: - A dictionary with all attributes - :param int count: - The number of networks to fake - :return: - A list of FakeResource objects faking the networks - """ - networks = [] - for i in range(0, count): - networks.append(FakeNetwork.create_one_network(attrs)) - - return networks - - @staticmethod - def get_networks(networks=None, count=2): - """Get an iterable Mock object with a list of faked networks. - - If networks list is provided, then initialize the Mock object with the - list. Otherwise create one. - - :param List networks: - A list of FakeResource objects faking networks - :param int count: - The number of networks to fake - :return: - An iterable Mock object with side_effect set to a list of faked - networks - """ - if networks is None: - networks = FakeNetwork.create_networks(count) - return mock.Mock(side_effect=networks) - - -class FakeNetworkFlavor(object): - """Fake Network Flavor.""" - - @staticmethod - def create_one_network_flavor(attrs=None): - """Create a fake network flavor. - - :param Dictionary attrs: - A dictionary with all attributes - :return: - A FakeResource object faking the network flavor - """ - attrs = attrs or {} - - fake_uuid = uuid.uuid4().hex - network_flavor_attrs = { - 'description': 'network-flavor-description-' + fake_uuid, - 'enabled': True, - 'id': 'network-flavor-id-' + fake_uuid, - 'name': 'network-flavor-name-' + fake_uuid, - 'service_type': 'vpn', - 'project_id': 'project-id-' + uuid.uuid4().hex, - 'location': 'MUNCHMUNCHMUNCH', - } - - # Overwrite default attributes. - network_flavor_attrs.update(attrs) - - network_flavor = fakes.FakeResource( - info=copy.deepcopy(network_flavor_attrs), - loaded=True - ) - - network_flavor.is_enabled = network_flavor_attrs['enabled'] - - return network_flavor - - @staticmethod - def create_flavor(attrs=None, count=2): - """Create multiple fake network flavors. - - :param Dictionary attrs: - A dictionary with all attributes - :param int count: - The number of network flavors to fake - :return: - A list of FakeResource objects faking the network falvors - """ - network_flavors = [] - for i in range(0, count): - network_flavors.append( - FakeNetworkFlavor.create_one_network_flavor(attrs) - ) - return network_flavors - - @staticmethod - def get_flavor(network_flavors=None, count=2): - """Get a list of flavors.""" - if network_flavors is None: - network_flavors = (FakeNetworkFlavor.create_flavor(count)) - return mock.Mock(side_effect=network_flavors) - - -class FakeNetworkSegment(object): - """Fake one or more network segments.""" - - @staticmethod - def create_one_network_segment(attrs=None): - """Create a fake network segment. - - :param Dictionary attrs: - A dictionary with all attributes - :return: - A FakeResource object faking the network segment - """ - attrs = attrs or {} - - # Set default attributes. - fake_uuid = uuid.uuid4().hex - network_segment_attrs = { - 'description': 'network-segment-description-' + fake_uuid, - 'id': 'network-segment-id-' + fake_uuid, - 'name': 'network-segment-name-' + fake_uuid, - 'network_id': 'network-id-' + fake_uuid, - 'network_type': 'vlan', - 'physical_network': 'physical-network-name-' + fake_uuid, - 'segmentation_id': 1024, - 'location': 'MUNCHMUNCHMUNCH', - } - - # Overwrite default attributes. - network_segment_attrs.update(attrs) - - network_segment = fakes.FakeResource( - info=copy.deepcopy(network_segment_attrs), - loaded=True - ) - - return network_segment - - @staticmethod - def create_network_segments(attrs=None, count=2): - """Create multiple fake network segments. - - :param Dictionary attrs: - A dictionary with all attributes - :param int count: - The number of network segments to fake - :return: - A list of FakeResource objects faking the network segments - """ - network_segments = [] - for i in range(0, count): - network_segments.append( - FakeNetworkSegment.create_one_network_segment(attrs) - ) - return network_segments - - -class FakeNetworkSegmentRange(object): - """Fake one or more network segment ranges.""" - - @staticmethod - def create_one_network_segment_range(attrs=None): - """Create a fake network segment range. - - :param Dictionary attrs: - A dictionary with all attributes - :return: - A FakeResource object faking the network segment range - """ - attrs = attrs or {} - - # Set default attributes. - fake_uuid = uuid.uuid4().hex - network_segment_range_attrs = { - 'id': 'network-segment-range-id-' + fake_uuid, - 'name': 'network-segment-name-' + fake_uuid, - 'default': False, - 'shared': False, - 'project_id': 'project-id-' + fake_uuid, - 'network_type': 'vlan', - 'physical_network': 'physical-network-name-' + fake_uuid, - 'minimum': 100, - 'maximum': 106, - 'used': {104: '3312e4ba67864b2eb53f3f41432f8efc', - 106: '3312e4ba67864b2eb53f3f41432f8efc'}, - 'available': [100, 101, 102, 103, 105], - 'location': 'MUNCHMUNCHMUNCH', - } - - # Overwrite default attributes. - network_segment_range_attrs.update(attrs) - - network_segment_range = fakes.FakeResource( - info=copy.deepcopy(network_segment_range_attrs), - loaded=True - ) - - return network_segment_range - - @staticmethod - def create_network_segment_ranges(attrs=None, count=2): - """Create multiple fake network segment ranges. - - :param Dictionary attrs: - A dictionary with all attributes - :param int count: - The number of network segment ranges to fake - :return: - A list of FakeResource objects faking the network segment ranges - """ - network_segment_ranges = [] - for i in range(0, count): - network_segment_ranges.append( - FakeNetworkSegmentRange.create_one_network_segment_range(attrs) - ) - return network_segment_ranges - - -class FakePort(object): - """Fake one or more ports.""" - - @staticmethod - def create_one_port(attrs=None): - """Create a fake port. - - :param Dictionary attrs: - A dictionary with all attributes - :return: - A FakeResource object, with id, name, etc. - """ - attrs = attrs or {} - - # Set default attributes. - port_attrs = { - 'admin_state_up': True, - 'allowed_address_pairs': [{}], - 'binding:host_id': 'binding-host-id-' + uuid.uuid4().hex, - 'binding:profile': {}, - 'binding:vif_details': {}, - 'binding:vif_type': 'ovs', - 'binding:vnic_type': 'normal', - 'data_plane_status': None, - 'description': 'description-' + uuid.uuid4().hex, - 'device_id': 'device-id-' + uuid.uuid4().hex, - 'device_owner': 'compute:nova', - 'device_profile': 'cyborg_device_profile_1', - 'dns_assignment': [{}], - 'dns_domain': 'dns-domain-' + uuid.uuid4().hex, - 'dns_name': 'dns-name-' + uuid.uuid4().hex, - 'extra_dhcp_opts': [{}], - 'fixed_ips': [{'ip_address': '10.0.0.3', - 'subnet_id': 'subnet-id-' + uuid.uuid4().hex}], - 'id': 'port-id-' + uuid.uuid4().hex, - 'mac_address': 'fa:16:3e:a9:4e:72', - 'name': 'port-name-' + uuid.uuid4().hex, - 'network_id': 'network-id-' + uuid.uuid4().hex, - 'numa_affinity_policy': 'required', - 'port_security_enabled': True, - 'security_group_ids': [], - 'status': 'ACTIVE', - 'project_id': 'project-id-' + uuid.uuid4().hex, - 'qos_network_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, - 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, - 'tags': [], - 'propagate_uplink_status': False, - 'location': 'MUNCHMUNCHMUNCH', - } - - # Overwrite default attributes. - port_attrs.update(attrs) - - port = fakes.FakeResource(info=copy.deepcopy(port_attrs), - loaded=True) - - # Set attributes with special mappings in OpenStack SDK. - port.binding_host_id = port_attrs['binding:host_id'] - port.binding_profile = port_attrs['binding:profile'] - port.binding_vif_details = port_attrs['binding:vif_details'] - port.binding_vif_type = port_attrs['binding:vif_type'] - port.binding_vnic_type = port_attrs['binding:vnic_type'] - port.is_admin_state_up = port_attrs['admin_state_up'] - port.is_port_security_enabled = port_attrs['port_security_enabled'] - - return port - - @staticmethod - def create_ports(attrs=None, count=2): - """Create multiple fake ports. - - :param Dictionary attrs: - A dictionary with all attributes - :param int count: - The number of ports to fake - :return: - A list of FakeResource objects faking the ports - """ - ports = [] - for i in range(0, count): - ports.append(FakePort.create_one_port(attrs)) - - return ports - - @staticmethod - def get_ports(ports=None, count=2): - """Get an iterable Mock object with a list of faked ports. - - If ports list is provided, then initialize the Mock object with the - list. Otherwise create one. - - :param List ports: - A list of FakeResource objects faking ports - :param int count: - The number of ports to fake - :return: - An iterable Mock object with side_effect set to a list of faked - ports - """ - if ports is None: - ports = FakePort.create_ports(count) - return mock.Mock(side_effect=ports) - - -class FakeNetworkAgent(object): - """Fake one or more network agents.""" - - @staticmethod - def create_one_network_agent(attrs=None): - """Create a fake network agent - - :param Dictionary attrs: - A dictionary with all attributes - :return: - A FakeResource object, with id, agent_type, and so on. - """ - attrs = attrs or {} - - # Set default attributes - agent_attrs = { - 'id': 'agent-id-' + uuid.uuid4().hex, - 'agent_type': 'agent-type-' + uuid.uuid4().hex, - 'host': 'host-' + uuid.uuid4().hex, - 'availability_zone': 'zone-' + uuid.uuid4().hex, - 'alive': True, - 'admin_state_up': True, - 'binary': 'binary-' + uuid.uuid4().hex, - 'configurations': {'subnet': 2, 'networks': 1}, - 'location': 'MUNCHMUNCHMUNCH', - } - agent_attrs.update(attrs) - agent = fakes.FakeResource(info=copy.deepcopy(agent_attrs), - loaded=True) - agent.is_admin_state_up = agent_attrs['admin_state_up'] - agent.is_alive = agent_attrs['alive'] - return agent - - @staticmethod - def create_network_agents(attrs=None, count=2): - """Create multiple fake network agents. - - :param Dictionary attrs: - A dictionary with all attributes - :param int count: - The number of network agents to fake - :return: - A list of FakeResource objects faking the network agents - """ - agents = [] - for i in range(0, count): - agents.append(FakeNetworkAgent.create_one_network_agent(attrs)) - - return agents - - @staticmethod - def get_network_agents(agents=None, count=2): - """Get an iterable Mock object with a list of faked network agents. - - If network agents list is provided, then initialize the Mock object - with the list. Otherwise create one. - - :param List agents: - A list of FakeResource objects faking network agents - :param int count: - The number of network agents to fake - :return: - An iterable Mock object with side_effect set to a list of faked - network agents - """ - if agents is None: - agents = FakeNetworkAgent.create_network_agents(count) - return mock.Mock(side_effect=agents) - - -class FakeNetworkRBAC(object): - """Fake one or more network rbac policies.""" - - @staticmethod - def create_one_network_rbac(attrs=None): - """Create a fake network rbac - - :param Dictionary attrs: - A dictionary with all attributes - :return: - A FakeResource object, with id, action, target_tenant, - project_id, type - """ - attrs = attrs or {} - - # Set default attributes - rbac_attrs = { - 'id': 'rbac-id-' + uuid.uuid4().hex, - 'object_type': 'network', - 'object_id': 'object-id-' + uuid.uuid4().hex, - 'action': 'access_as_shared', - 'target_tenant': 'target-tenant-' + uuid.uuid4().hex, - 'project_id': 'project-id-' + uuid.uuid4().hex, - 'location': 'MUNCHMUNCHMUNCH', - } - rbac_attrs.update(attrs) - rbac = fakes.FakeResource(info=copy.deepcopy(rbac_attrs), - loaded=True) - # Set attributes with special mapping in OpenStack SDK. - rbac.target_project_id = rbac_attrs['target_tenant'] - return rbac - - @staticmethod - def create_network_rbacs(attrs=None, count=2): - """Create multiple fake network rbac policies. - - :param Dictionary attrs: - A dictionary with all attributes - :param int count: - The number of rbac policies to fake - :return: - A list of FakeResource objects faking the rbac policies - """ - rbac_policies = [] - for i in range(0, count): - rbac_policies.append(FakeNetworkRBAC. - create_one_network_rbac(attrs)) - - return rbac_policies - - @staticmethod - def get_network_rbacs(rbac_policies=None, count=2): - """Get an iterable Mock object with a list of faked rbac policies. - - If rbac policies list is provided, then initialize the Mock object - with the list. Otherwise create one. - - :param List rbac_policies: - A list of FakeResource objects faking rbac policies - :param int count: - The number of rbac policies to fake - :return: - An iterable Mock object with side_effect set to a list of faked - rbac policies - """ - if rbac_policies is None: - rbac_policies = FakeNetworkRBAC.create_network_rbacs(count) - return mock.Mock(side_effect=rbac_policies) - - -class FakeNetworkFlavorProfile(object): - """Fake network flavor profile.""" - - @staticmethod - def create_one_service_profile(attrs=None): - """Create flavor profile.""" - attrs = attrs or {} - - flavor_profile_attrs = { - 'id': 'flavor-profile-id' + uuid.uuid4().hex, - 'description': 'flavor-profile-description-' + uuid.uuid4().hex, - 'project_id': 'project-id-' + uuid.uuid4().hex, - 'driver': 'driver-' + uuid.uuid4().hex, - 'metainfo': 'metainfo-' + uuid.uuid4().hex, - 'enabled': True, - 'location': 'MUNCHMUNCHMUNCH', - } - - flavor_profile_attrs.update(attrs) - - flavor_profile = fakes.FakeResource( - info=copy.deepcopy(flavor_profile_attrs), - loaded=True) - - flavor_profile.is_enabled = flavor_profile_attrs['enabled'] - - return flavor_profile - - @staticmethod - def create_service_profile(attrs=None, count=2): - """Create multiple flavor profiles.""" - - flavor_profiles = [] - for i in range(0, count): - flavor_profiles.append(FakeNetworkFlavorProfile. - create_one_service_profile(attrs)) - return flavor_profiles - - @staticmethod - def get_service_profile(flavor_profile=None, count=2): - """Get a list of flavor profiles.""" - if flavor_profile is None: - flavor_profile = (FakeNetworkFlavorProfile. - create_service_profile(count)) - return mock.Mock(side_effect=flavor_profile) - - class FakeNetworkQosPolicy(object): """Fake one or more QoS policies.""" @@ -1102,6 +276,9 @@ class FakeNetworkQosRule(object): elif type == RULE_TYPE_MINIMUM_BANDWIDTH: qos_rule_attrs['min_kbps'] = randint(1, 10000) qos_rule_attrs['direction'] = 'egress' + elif type == RULE_TYPE_MINIMUM_PACKET_RATE: + qos_rule_attrs['min_kpps'] = randint(1, 10000) + qos_rule_attrs['direction'] = 'egress' # Overwrite default attributes. qos_rule_attrs.update(attrs) @@ -2038,6 +1215,737 @@ class FakeL3ConntrackHelper(object): return mock.Mock(side_effect=ct_helpers) +def create_one_address_group(attrs=None): + """Create a fake address group. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + An AddressGroup object with name, id, etc. + """ + attrs = attrs or {} + + # Set default attributes. + address_group_attrs = { + 'name': 'address-group-name-' + uuid.uuid4().hex, + 'description': 'address-group-description-' + uuid.uuid4().hex, + 'id': 'address-group-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, + 'addresses': ['10.0.0.1/32'], + 'location': 'MUNCHMUNCHMUNCH', + } + + # Overwrite default attributes. + address_group_attrs.update(attrs) + + address_group = _address_group.AddressGroup(**address_group_attrs) + + return address_group + + +def create_address_groups(attrs=None, count=2): + """Create multiple fake address groups. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of address groups to fake + :return: + A list of AddressGroup objects faking the address groups + """ + address_groups = [] + for i in range(0, count): + address_groups.append(create_one_address_group(attrs)) + + return address_groups + + +def get_address_groups(address_groups=None, count=2): + """Get an iterable Mock object with a list of faked address groups. + + If address groups list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List address_groups: + A list of FakeResource objects faking address groups + :param int count: + The number of address groups to fake + :return: + An iterable Mock object with side_effect set to a list of faked + AddressGroup objects + """ + if address_groups is None: + address_groups = create_address_groups(count) + return mock.Mock(side_effect=address_groups) + + +def create_one_address_scope(attrs=None): + """Create a fake address scope. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + An AddressScope object with name, id, etc. + """ + attrs = attrs or {} + + # Set default attributes. + address_scope_attrs = { + 'name': 'address-scope-name-' + uuid.uuid4().hex, + 'id': 'address-scope-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, + 'shared': False, + 'ip_version': 4, + 'location': 'MUNCHMUNCHMUNCH', + } + + # Overwrite default attributes. + address_scope_attrs.update(attrs) + + address_scope = _address_scope.AddressScope(**address_scope_attrs) + + # Set attributes with special mapping in OpenStack SDK. + address_scope.is_shared = address_scope_attrs['shared'] + + return address_scope + + +def create_address_scopes(attrs=None, count=2): + """Create multiple fake address scopes. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of address scopes to fake + :return: + A list of AddressScope objects faking the address scopes + """ + address_scopes = [] + for i in range(0, count): + address_scopes.append(create_one_address_scope(attrs)) + + return address_scopes + + +def get_address_scopes(address_scopes=None, count=2): + """Get an iterable Mock object with a list of faked address scopes. + + If address scopes list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List address_scopes: + A list of FakeResource objects faking address scopes + :param int count: + The number of address scopes to fake + :return: + An iterable Mock object with side_effect set to a list of faked + AddressScope objects + """ + if address_scopes is None: + address_scopes = create_address_scopes(count) + return mock.Mock(side_effect=address_scopes) + + +def create_one_topology(attrs=None): + attrs = attrs or {} + + auto_allocated_topology_attrs = { + 'id': 'network-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, + } + + auto_allocated_topology_attrs.update(attrs) + + auto_allocated_topology = allocated_topology.AutoAllocatedTopology( + **auto_allocated_topology_attrs + ) + + return auto_allocated_topology + + +def create_one_availability_zone(attrs=None): + """Create a fake AZ. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + An AvailabilityZone object with name, state, etc. + """ + attrs = attrs or {} + + # Set default attributes. + availability_zone = { + 'name': uuid.uuid4().hex, + 'state': 'available', + 'resource': 'network', + } + + # Overwrite default attributes. + availability_zone.update(attrs) + + availability_zone = _availability_zone.AvailabilityZone( + **availability_zone + ) + return availability_zone + + +def create_availability_zones(attrs=None, count=2): + """Create multiple fake AZs. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of AZs to fake + :return: + A list of AvailabilityZone objects faking the AZs + """ + availability_zones = [] + for i in range(0, count): + availability_zone = create_one_availability_zone(attrs) + availability_zones.append(availability_zone) + + return availability_zones + + +def create_one_ip_availability(attrs=None): + """Create a fake list with ip availability stats of a network. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A NetworkIPAvailability object with network_name, network_id, etc. + """ + attrs = attrs or {} + + # Set default attributes. + network_ip_attrs = { + 'network_id': 'network-id-' + uuid.uuid4().hex, + 'network_name': 'network-name-' + uuid.uuid4().hex, + 'project_id': '', + 'subnet_ip_availability': [], + 'total_ips': 254, + 'used_ips': 6, + 'location': 'MUNCHMUNCHMUNCH', + } + network_ip_attrs.update(attrs) + + network_ip_availability = _ip_availability.NetworkIPAvailability( + **network_ip_attrs) + + return network_ip_availability + + +def create_ip_availability(count=2): + """Create fake list of ip availability stats of multiple networks. + + :param int count: + The number of networks to fake + :return: + A list of NetworkIPAvailability objects faking + network ip availability stats + """ + network_ip_availabilities = [] + for i in range(0, count): + network_ip_availability = create_one_ip_availability() + network_ip_availabilities.append(network_ip_availability) + + return network_ip_availabilities + + +def create_one_network(attrs=None): + """Create a fake network. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + An Network object, with id, name, etc. + """ + attrs = attrs or {} + + # Set default attributes. + network_attrs = { + 'created_at': '2021-11-29T10:10:23.000000', + 'id': 'network-id-' + uuid.uuid4().hex, + 'name': 'network-name-' + uuid.uuid4().hex, + 'status': 'ACTIVE', + 'description': 'network-description-' + uuid.uuid4().hex, + 'dns_domain': 'example.org.', + 'mtu': '1350', + 'project_id': 'project-id-' + uuid.uuid4().hex, + 'admin_state_up': True, + 'shared': False, + 'subnets': ['a', 'b'], + 'segments': 'network-segment-' + uuid.uuid4().hex, + 'provider:network_type': 'vlan', + 'provider:physical_network': 'physnet1', + 'provider:segmentation_id': "400", + 'router:external': True, + 'availability_zones': [], + 'availability_zone_hints': [], + 'is_default': False, + 'is_vlan_transparent': True, + 'port_security_enabled': True, + 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, + 'ipv4_address_scope': 'ipv4' + uuid.uuid4().hex, + 'ipv6_address_scope': 'ipv6' + uuid.uuid4().hex, + 'tags': [], + 'location': 'MUNCHMUNCHMUNCH', + 'updated_at': '2021-11-29T10:10:25.000000', + } + + # Overwrite default attributes. + network_attrs.update(attrs) + + network = _network.Network(**network_attrs) + + return network + + +def create_networks(attrs=None, count=2): + """Create multiple fake networks. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of networks to fake + :return: + A list of Network objects faking the networks + """ + networks = [] + for i in range(0, count): + networks.append(create_one_network(attrs)) + + return networks + + +def get_networks(networks=None, count=2): + """Get an iterable Mock object with a list of faked networks. + + If networks list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List networks: + A list of Network objects faking networks + :param int count: + The number of networks to fake + :return: + An iterable Mock object with side_effect set to a list of faked + networks + """ + if networks is None: + networks = create_networks(count) + return mock.Mock(side_effect=networks) + + +def create_one_network_flavor(attrs=None): + """Create a fake network flavor. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A Flavor object faking the network flavor + """ + attrs = attrs or {} + + fake_uuid = uuid.uuid4().hex + network_flavor_attrs = { + 'description': 'network-flavor-description-' + fake_uuid, + 'is_enabled': True, + 'id': 'network-flavor-id-' + fake_uuid, + 'name': 'network-flavor-name-' + fake_uuid, + 'service_type': 'vpn', + 'location': 'MUNCHMUNCHMUNCH', + } + + # Overwrite default attributes. + network_flavor_attrs.update(attrs) + + network_flavor = _flavor.Flavor(**network_flavor_attrs) + + return network_flavor + + +def create_flavor(attrs=None, count=2): + """Create multiple fake network flavors. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of network flavors to fake + :return: + A list of Flavor objects faking the network falvors + """ + network_flavors = [] + for i in range(0, count): + network_flavors.append(create_one_network_flavor(attrs)) + + return network_flavors + + +def get_flavor(network_flavors=None, count=2): + """Get a list of flavors.""" + if network_flavors is None: + network_flavors = create_flavor(count) + return mock.Mock(side_effect=network_flavors) + + +def create_one_network_segment(attrs=None): + """Create a fake network segment. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + An Segment object faking the network segment + """ + attrs = attrs or {} + + # Set default attributes. + fake_uuid = uuid.uuid4().hex + network_segment_attrs = { + 'description': 'network-segment-description-' + fake_uuid, + 'id': 'network-segment-id-' + fake_uuid, + 'name': 'network-segment-name-' + fake_uuid, + 'network_id': 'network-id-' + fake_uuid, + 'network_type': 'vlan', + 'physical_network': 'physical-network-name-' + fake_uuid, + 'segmentation_id': 1024, + 'location': 'MUNCHMUNCHMUNCH', + } + + # Overwrite default attributes. + network_segment_attrs.update(attrs) + + network_segment = _segment.Segment(**network_segment_attrs) + + return network_segment + + +def create_network_segments(attrs=None, count=2): + """Create multiple fake network segments. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of network segments to fake + :return: + A list of Segment objects faking the network segments + """ + network_segments = [] + for i in range(0, count): + network_segments.append(create_one_network_segment(attrs)) + return network_segments + + +def create_one_network_segment_range(attrs=None): + """Create a fake network segment range. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A NetworkSegmentRange object faking the network segment range + """ + attrs = attrs or {} + + # Set default attributes. + fake_uuid = uuid.uuid4().hex + network_segment_range_attrs = { + 'id': 'network-segment-range-id-' + fake_uuid, + 'name': 'network-segment-name-' + fake_uuid, + 'default': False, + 'shared': False, + 'project_id': 'project-id-' + fake_uuid, + 'network_type': 'vlan', + 'physical_network': 'physical-network-name-' + fake_uuid, + 'minimum': 100, + 'maximum': 106, + 'used': {104: '3312e4ba67864b2eb53f3f41432f8efc', + 106: '3312e4ba67864b2eb53f3f41432f8efc'}, + 'available': [100, 101, 102, 103, 105], + 'location': 'MUNCHMUNCHMUNCH', + } + + # Overwrite default attributes. + network_segment_range_attrs.update(attrs) + + network_segment_range = ( + _segment_range.NetworkSegmentRange(**network_segment_range_attrs)) + + return network_segment_range + + +def create_network_segment_ranges(attrs=None, count=2): + """Create multiple fake network segment ranges. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of network segment ranges to fake + :return: + A list of NetworkSegmentRange objects faking + the network segment ranges + """ + network_segment_ranges = [] + for i in range(0, count): + network_segment_ranges.append(create_one_network_segment_range(attrs)) + return network_segment_ranges + + +def create_one_port(attrs=None): + """Create a fake port. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A Port object, with id, name, etc. + """ + attrs = attrs or {} + + # Set default attributes. + port_attrs = { + 'is_admin_state_up': True, + 'allowed_address_pairs': [{}], + 'binding:host_id': 'binding-host-id-' + uuid.uuid4().hex, + 'binding:profile': {}, + 'binding:vif_details': {}, + 'binding:vif_type': 'ovs', + 'binding:vnic_type': 'normal', + 'data_plane_status': None, + 'description': 'description-' + uuid.uuid4().hex, + 'device_id': 'device-id-' + uuid.uuid4().hex, + 'device_owner': 'compute:nova', + 'device_profile': 'cyborg_device_profile_1', + 'dns_assignment': [{}], + 'dns_domain': 'dns-domain-' + uuid.uuid4().hex, + 'dns_name': 'dns-name-' + uuid.uuid4().hex, + 'extra_dhcp_opts': [{}], + 'fixed_ips': [{'ip_address': '10.0.0.3', + 'subnet_id': 'subnet-id-' + uuid.uuid4().hex}], + 'id': 'port-id-' + uuid.uuid4().hex, + 'mac_address': 'fa:16:3e:a9:4e:72', + 'name': 'port-name-' + uuid.uuid4().hex, + 'network_id': 'network-id-' + uuid.uuid4().hex, + 'numa_affinity_policy': 'required', + 'is_port_security_enabled': True, + 'security_group_ids': [], + 'status': 'ACTIVE', + 'project_id': 'project-id-' + uuid.uuid4().hex, + 'qos_network_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, + 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, + 'tags': [], + 'propagate_uplink_status': False, + 'location': 'MUNCHMUNCHMUNCH', + } + + # Overwrite default attributes. + port_attrs.update(attrs) + + port = _port.Port(**port_attrs) + + return port + + +def create_ports(attrs=None, count=2): + """Create multiple fake ports. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of ports to fake + :return: + A list of Port objects faking the ports + """ + ports = [] + for i in range(0, count): + ports.append(create_one_port(attrs)) + + return ports + + +def get_ports(ports=None, count=2): + """Get an iterable Mock object with a list of faked ports. + + If ports list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List ports: + A list of Port objects faking ports + :param int count: + The number of ports to fake + :return: + An iterable Mock object with side_effect set to a list of faked + ports + """ + if ports is None: + ports = create_ports(count) + return mock.Mock(side_effect=ports) + + +def create_one_network_agent(attrs=None): + """Create a fake network agent + + :param Dictionary attrs: + A dictionary with all attributes + :return: + An Agent object, with id, agent_type, and so on. + """ + attrs = attrs or {} + + # Set default attributes + agent_attrs = { + 'id': 'agent-id-' + uuid.uuid4().hex, + 'agent_type': 'agent-type-' + uuid.uuid4().hex, + 'host': 'host-' + uuid.uuid4().hex, + 'availability_zone': 'zone-' + uuid.uuid4().hex, + 'alive': True, + 'admin_state_up': True, + 'binary': 'binary-' + uuid.uuid4().hex, + 'configurations': {'subnet': 2, 'networks': 1}, + 'location': 'MUNCHMUNCHMUNCH', + } + agent_attrs.update(attrs) + agent = network_agent.Agent(**agent_attrs) + + return agent + + +def create_network_agents(attrs=None, count=2): + """Create multiple fake network agents. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of network agents to fake + :return: + A list of Agent objects faking the network agents + """ + agents = [] + for i in range(0, count): + agents.append(create_one_network_agent(attrs)) + + return agents + + +def get_network_agents(agents=None, count=2): + """Get an iterable Mock object with a list of faked network agents. + + If network agents list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List agents: + A list of Agent objects faking network agents + :param int count: + The number of network agents to fake + :return: + An iterable Mock object with side_effect set to a list of faked + network agents + """ + if agents is None: + agents = create_network_agents(count) + return mock.Mock(side_effect=agents) + + +def create_one_network_rbac(attrs=None): + """Create a fake network rbac + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A RBACPolicy object, with id, action, target_tenant, + project_id, type + """ + attrs = attrs or {} + + # Set default attributes + rbac_attrs = { + 'id': 'rbac-id-' + uuid.uuid4().hex, + 'object_type': 'network', + 'object_id': 'object-id-' + uuid.uuid4().hex, + 'action': 'access_as_shared', + 'target_tenant': 'target-tenant-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, + 'location': 'MUNCHMUNCHMUNCH', + } + + rbac_attrs.update(attrs) + rbac = network_rbac.RBACPolicy(**rbac_attrs) + + return rbac + + +def create_network_rbacs(attrs=None, count=2): + """Create multiple fake network rbac policies. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of rbac policies to fake + :return: + A list of RBACPolicy objects faking the rbac policies + """ + rbac_policies = [] + for i in range(0, count): + rbac_policies.append(create_one_network_rbac(attrs)) + + return rbac_policies + + +def get_network_rbacs(rbac_policies=None, count=2): + """Get an iterable Mock object with a list of faked rbac policies. + + If rbac policies list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List rbac_policies: + A list of RBACPolicy objects faking rbac policies + :param int count: + The number of rbac policies to fake + :return: + An iterable Mock object with side_effect set to a list of faked + rbac policies + """ + if rbac_policies is None: + rbac_policies = create_network_rbacs(count) + return mock.Mock(side_effect=rbac_policies) + + +def create_one_service_profile(attrs=None): + """Create flavor profile.""" + attrs = attrs or {} + + flavor_profile_attrs = { + 'id': 'flavor-profile-id' + uuid.uuid4().hex, + 'description': 'flavor-profile-description-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, + 'driver': 'driver-' + uuid.uuid4().hex, + 'metainfo': 'metainfo-' + uuid.uuid4().hex, + 'enabled': True, + 'location': 'MUNCHMUNCHMUNCH', + } + + flavor_profile_attrs.update(attrs) + + flavor_profile = _flavor_profile.ServiceProfile(**flavor_profile_attrs) + + return flavor_profile + + +def create_service_profile(attrs=None, count=2): + """Create multiple flavor profiles.""" + + flavor_profiles = [] + for i in range(0, count): + flavor_profiles.append(create_one_service_profile(attrs)) + return flavor_profiles + + +def get_service_profile(flavor_profile=None, count=2): + """Get a list of flavor profiles.""" + if flavor_profile is None: + flavor_profile = create_service_profile(count) + + return mock.Mock(side_effect=flavor_profile) + + def create_one_local_ip(attrs=None): """Create a fake local ip. @@ -2142,7 +2050,7 @@ def create_local_ip_associations(attrs=None, count=2): :param Dictionary attrs: A dictionary with all attributes :param int count: - The number of address groups to fake + The number of local ip associations to fake :return: A list of FakeResource objects faking the local ip associations """ diff --git a/openstackclient/tests/unit/network/v2/test_address_group.py b/openstackclient/tests/unit/network/v2/test_address_group.py index 703ab74..f550db6 100644 --- a/openstackclient/tests/unit/network/v2/test_address_group.py +++ b/openstackclient/tests/unit/network/v2/test_address_group.py @@ -41,7 +41,7 @@ class TestCreateAddressGroup(TestAddressGroup): domain = identity_fakes_v3.FakeDomain.create_one_domain() # The new address group created. new_address_group = ( - network_fakes.FakeAddressGroup.create_one_address_group( + network_fakes.create_one_address_group( attrs={ 'project_id': project.id, } @@ -133,16 +133,13 @@ class TestCreateAddressGroup(TestAddressGroup): class TestDeleteAddressGroup(TestAddressGroup): # The address group to delete. - _address_groups = ( - network_fakes.FakeAddressGroup.create_address_groups(count=2)) + _address_groups = network_fakes.create_address_groups(count=2) def setUp(self): super(TestDeleteAddressGroup, self).setUp() self.network.delete_address_group = mock.Mock(return_value=None) - self.network.find_address_group = ( - network_fakes.FakeAddressGroup.get_address_groups( - address_groups=self._address_groups) - ) + self.network.find_address_group = network_fakes.get_address_groups( + address_groups=self._address_groups) # Get the command object to test self.cmd = address_group.DeleteAddressGroup(self.app, self.namespace) @@ -216,8 +213,7 @@ class TestDeleteAddressGroup(TestAddressGroup): class TestListAddressGroup(TestAddressGroup): # The address groups to list up. - address_groups = ( - network_fakes.FakeAddressGroup.create_address_groups(count=3)) + address_groups = network_fakes.create_address_groups(count=3) columns = ( 'ID', 'Name', @@ -308,7 +304,7 @@ class TestListAddressGroup(TestAddressGroup): class TestSetAddressGroup(TestAddressGroup): # The address group to set. - _address_group = network_fakes.FakeAddressGroup.create_one_address_group() + _address_group = network_fakes.create_one_address_group() def setUp(self): super(TestSetAddressGroup, self).setUp() @@ -392,7 +388,7 @@ class TestSetAddressGroup(TestAddressGroup): class TestShowAddressGroup(TestAddressGroup): # The address group to show. - _address_group = network_fakes.FakeAddressGroup.create_one_address_group() + _address_group = network_fakes.create_one_address_group() columns = ( 'addresses', 'description', @@ -444,7 +440,7 @@ class TestShowAddressGroup(TestAddressGroup): class TestUnsetAddressGroup(TestAddressGroup): # The address group to unset. - _address_group = network_fakes.FakeAddressGroup.create_one_address_group() + _address_group = network_fakes.create_one_address_group() def setUp(self): super(TestUnsetAddressGroup, self).setUp() diff --git a/openstackclient/tests/unit/network/v2/test_address_scope.py b/openstackclient/tests/unit/network/v2/test_address_scope.py index 7e7c421..d4c8352 100644 --- a/openstackclient/tests/unit/network/v2/test_address_scope.py +++ b/openstackclient/tests/unit/network/v2/test_address_scope.py @@ -40,12 +40,10 @@ class TestCreateAddressScope(TestAddressScope): project = identity_fakes_v3.FakeProject.create_one_project() domain = identity_fakes_v3.FakeDomain.create_one_domain() # The new address scope created. - new_address_scope = ( - network_fakes.FakeAddressScope.create_one_address_scope( - attrs={ - 'project_id': project.id, - } - )) + new_address_scope = network_fakes.create_one_address_scope( + attrs={ + 'project_id': project.id, + }) columns = ( 'id', 'ip_version', @@ -58,7 +56,7 @@ class TestCreateAddressScope(TestAddressScope): new_address_scope.ip_version, new_address_scope.name, new_address_scope.project_id, - new_address_scope.shared, + new_address_scope.is_shared, ) def setUp(self): @@ -153,16 +151,13 @@ class TestCreateAddressScope(TestAddressScope): class TestDeleteAddressScope(TestAddressScope): # The address scope to delete. - _address_scopes = ( - network_fakes.FakeAddressScope.create_address_scopes(count=2)) + _address_scopes = network_fakes.create_address_scopes(count=2) def setUp(self): super(TestDeleteAddressScope, self).setUp() self.network.delete_address_scope = mock.Mock(return_value=None) - self.network.find_address_scope = ( - network_fakes.FakeAddressScope.get_address_scopes( - address_scopes=self._address_scopes) - ) + self.network.find_address_scope = network_fakes.get_address_scopes( + address_scopes=self._address_scopes) # Get the command object to test self.cmd = address_scope.DeleteAddressScope(self.app, self.namespace) @@ -237,8 +232,7 @@ class TestDeleteAddressScope(TestAddressScope): class TestListAddressScope(TestAddressScope): # The address scopes to list up. - address_scopes = ( - network_fakes.FakeAddressScope.create_address_scopes(count=3)) + address_scopes = network_fakes.create_address_scopes(count=3) columns = ( 'ID', 'Name', @@ -252,7 +246,7 @@ class TestListAddressScope(TestAddressScope): scope.id, scope.name, scope.ip_version, - scope.shared, + scope.is_shared, scope.project_id, )) @@ -377,7 +371,7 @@ class TestListAddressScope(TestAddressScope): class TestSetAddressScope(TestAddressScope): # The address scope to set. - _address_scope = network_fakes.FakeAddressScope.create_one_address_scope() + _address_scope = network_fakes.create_one_address_scope() def setUp(self): super(TestSetAddressScope, self).setUp() @@ -448,7 +442,7 @@ class TestShowAddressScope(TestAddressScope): # The address scope to show. _address_scope = ( - network_fakes.FakeAddressScope.create_one_address_scope()) + network_fakes.create_one_address_scope()) columns = ( 'id', 'ip_version', @@ -461,7 +455,7 @@ class TestShowAddressScope(TestAddressScope): _address_scope.ip_version, _address_scope.name, _address_scope.project_id, - _address_scope.shared, + _address_scope.is_shared, ) def setUp(self): diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py index 5b5c83a..f76dcc7 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py @@ -40,9 +40,9 @@ class TestFloatingIPNetwork(network_fakes.TestNetworkV2): class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): # Fake data for option tests. - floating_network = network_fakes.FakeNetwork.create_one_network() + floating_network = network_fakes.create_one_network() subnet = network_fakes.FakeSubnet.create_one_subnet() - port = network_fakes.FakePort.create_one_port() + port = network_fakes.create_one_port() # The floating ip created. floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip( @@ -378,10 +378,10 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): # The floating ips to list up floating_ips = network_fakes.FakeFloatingIP.create_floating_ips(count=3) - fake_network = network_fakes.FakeNetwork.create_one_network({ + fake_network = network_fakes.create_one_network({ 'id': 'fake_network_id', }) - fake_port = network_fakes.FakePort.create_one_port({ + fake_port = network_fakes.create_one_port({ 'id': 'fake_port_id', }) fake_router = network_fakes.FakeRouter.create_one_router({ @@ -700,9 +700,9 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork): class TestSetFloatingIP(TestFloatingIPNetwork): # Fake data for option tests. - floating_network = network_fakes.FakeNetwork.create_one_network() + floating_network = network_fakes.create_one_network() subnet = network_fakes.FakeSubnet.create_one_subnet() - port = network_fakes.FakePort.create_one_port() + port = network_fakes.create_one_port() # The floating ip to be set. floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip( @@ -932,9 +932,9 @@ class TestSetFloatingIP(TestFloatingIPNetwork): class TestUnsetFloatingIP(TestFloatingIPNetwork): - floating_network = network_fakes.FakeNetwork.create_one_network() + floating_network = network_fakes.create_one_network() subnet = network_fakes.FakeSubnet.create_one_subnet() - port = network_fakes.FakePort.create_one_port() + port = network_fakes.create_one_port() # The floating ip to be unset. floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip( diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py b/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py index 7b9e3aa..97399f4 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py @@ -31,7 +31,7 @@ class TestFloatingIPPortForwarding(network_fakes.TestNetworkV2): self.network = self.app.client_manager.network self.floating_ip = (network_fakes.FakeFloatingIP. create_one_floating_ip()) - self.port = network_fakes.FakePort.create_one_port() + self.port = network_fakes.create_one_port() self.project = identity_fakes_v2.FakeProject.create_one_project() self.network.find_port = mock.Mock(return_value=self.port) diff --git a/openstackclient/tests/unit/network/v2/test_ip_availability.py b/openstackclient/tests/unit/network/v2/test_ip_availability.py index 880cf58..fbe3b1a 100644 --- a/openstackclient/tests/unit/network/v2/test_ip_availability.py +++ b/openstackclient/tests/unit/network/v2/test_ip_availability.py @@ -38,8 +38,7 @@ class TestIPAvailability(network_fakes.TestNetworkV2): class TestListIPAvailability(TestIPAvailability): - _ip_availability = \ - network_fakes.FakeIPAvailability.create_ip_availability(count=3) + _ip_availability = network_fakes.create_ip_availability(count=3) columns = ( 'Network ID', 'Network Name', @@ -117,10 +116,9 @@ class TestListIPAvailability(TestIPAvailability): class TestShowIPAvailability(TestIPAvailability): - _network = network_fakes.FakeNetwork.create_one_network() - _ip_availability = \ - network_fakes.FakeIPAvailability.create_one_ip_availability( - attrs={'network_id': _network.id}) + _network = network_fakes.create_one_network() + _ip_availability = network_fakes.create_one_ip_availability( + attrs={'network_id': _network.id}) columns = ( 'network_id', diff --git a/openstackclient/tests/unit/network/v2/test_local_ip.py b/openstackclient/tests/unit/network/v2/test_local_ip.py index 17e8dcd..97e246d 100644 --- a/openstackclient/tests/unit/network/v2/test_local_ip.py +++ b/openstackclient/tests/unit/network/v2/test_local_ip.py @@ -40,8 +40,8 @@ class TestLocalIP(network_fakes.TestNetworkV2): class TestCreateLocalIP(TestLocalIP): project = identity_fakes_v3.FakeProject.create_one_project() domain = identity_fakes_v3.FakeDomain.create_one_domain() - local_ip_network = network_fakes.FakeNetwork.create_one_network() - port = network_fakes.FakePort.create_one_port() + local_ip_network = network_fakes.create_one_network() + port = network_fakes.create_one_port() # The new local ip created. new_local_ip = network_fakes.create_one_local_ip( attrs={'project_id': project.id, @@ -216,7 +216,7 @@ class TestListLocalIP(TestLocalIP): # The local ip to list up. local_ips = ( network_fakes.create_local_ips(count=3)) - fake_network = network_fakes.FakeNetwork.create_one_network( + fake_network = network_fakes.create_one_network( {'id': 'fake_network_id'} ) diff --git a/openstackclient/tests/unit/network/v2/test_local_ip_association.py b/openstackclient/tests/unit/network/v2/test_local_ip_association.py index 9775930..0e45374 100644 --- a/openstackclient/tests/unit/network/v2/test_local_ip_association.py +++ b/openstackclient/tests/unit/network/v2/test_local_ip_association.py @@ -29,7 +29,7 @@ class TestLocalIPAssociation(network_fakes.TestNetworkV2): super().setUp() self.network = self.app.client_manager.network self.local_ip = network_fakes.create_one_local_ip() - self.fixed_port = network_fakes.FakePort.create_one_port() + self.fixed_port = network_fakes.create_one_port() self.project = identity_fakes_v2.FakeProject.create_one_project() self.network.find_port = mock.Mock(return_value=self.fixed_port) diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index 2b04edf..6adb9e1 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -46,7 +46,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): project = identity_fakes_v3.FakeProject.create_one_project() domain = identity_fakes_v3.FakeDomain.create_one_domain() # The new network created. - _network = network_fakes.FakeNetwork.create_one_network( + _network = network_fakes.create_one_network( attrs={ 'project_id': project.id, 'availability_zone_hints': ["nova"], @@ -59,12 +59,14 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'admin_state_up', 'availability_zone_hints', 'availability_zones', + 'created_at', 'description', 'dns_domain', 'id', 'ipv4_address_scope', 'ipv6_address_scope', 'is_default', + 'is_vlan_transparent', 'mtu', 'name', 'port_security_enabled', @@ -76,14 +78,18 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'router:external', 'shared', 'status', + 'segments', 'subnets', 'tags', + 'revision_number', + 'updated_at', ) data = ( - network.AdminStateColumn(_network.admin_state_up), + network.AdminStateColumn(_network.is_admin_state_up), format_columns.ListColumn(_network.availability_zone_hints), format_columns.ListColumn(_network.availability_zones), + _network.created_at, _network.description, _network.dns_domain, _network.id, @@ -99,10 +105,14 @@ class TestCreateNetworkIdentityV3(TestNetwork): _network.provider_segmentation_id, _network.qos_policy_id, network.RouterExternalColumn(_network.is_router_external), - _network.shared, + _network.is_shared, + _network.is_vlan_transparent, _network.status, - format_columns.ListColumn(_network.subnets), + _network.segments, + format_columns.ListColumn(_network.subnet_ids), format_columns.ListColumn(_network.tags), + _network.revision_number, + _network.updated_at, ) def setUp(self): @@ -145,7 +155,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'name': self._network.name, }) self.assertFalse(self.network.set_tags.called) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) def test_create_all_options(self): @@ -153,7 +163,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): "--disable", "--share", "--description", self._network.description, - "--mtu", self._network.mtu, + "--mtu", str(self._network.mtu), "--project", self.project.name, "--project-domain", self.domain.name, "--availability-zone-hint", "nova", @@ -171,7 +181,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): ('disable', True), ('share', True), ('description', self._network.description), - ('mtu', self._network.mtu), + ('mtu', str(self._network.mtu)), ('project', self.project.name), ('project_domain', self.domain.name), ('availability_zone_hints', ["nova"]), @@ -196,7 +206,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'name': self._network.name, 'shared': True, 'description': self._network.description, - 'mtu': self._network.mtu, + 'mtu': str(self._network.mtu), 'project_id': self.project.id, 'is_default': True, 'router:external': True, @@ -208,7 +218,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'port_security_enabled': True, 'dns_domain': 'example.org.', }) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) def test_create_other_options(self): @@ -235,7 +245,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'shared': False, 'port_security_enabled': False, }) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): @@ -267,7 +277,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): tests_utils.CompareBySet(['red', 'blue'])) else: self.assertFalse(self.network.set_tags.called) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) def test_create_with_tags(self): @@ -281,20 +291,21 @@ class TestCreateNetworkIdentityV2(TestNetwork): project = identity_fakes_v2.FakeProject.create_one_project() # The new network created. - _network = network_fakes.FakeNetwork.create_one_network( + _network = network_fakes.create_one_network( attrs={'project_id': project.id} ) - columns = ( 'admin_state_up', 'availability_zone_hints', 'availability_zones', + 'created_at', 'description', 'dns_domain', 'id', 'ipv4_address_scope', 'ipv6_address_scope', 'is_default', + 'is_vlan_transparent', 'mtu', 'name', 'port_security_enabled', @@ -306,14 +317,18 @@ class TestCreateNetworkIdentityV2(TestNetwork): 'router:external', 'shared', 'status', + 'segments', 'subnets', 'tags', + 'revision_number', + 'updated_at', ) data = ( - network.AdminStateColumn(_network.admin_state_up), + network.AdminStateColumn(_network.is_admin_state_up), format_columns.ListColumn(_network.availability_zone_hints), format_columns.ListColumn(_network.availability_zones), + _network.created_at, _network.description, _network.dns_domain, _network.id, @@ -329,10 +344,14 @@ class TestCreateNetworkIdentityV2(TestNetwork): _network.provider_segmentation_id, _network.qos_policy_id, network.RouterExternalColumn(_network.is_router_external), - _network.shared, + _network.is_shared, + _network.is_vlan_transparent, _network.status, - format_columns.ListColumn(_network.subnets), + _network.segments, + format_columns.ListColumn(_network.subnet_ids), format_columns.ListColumn(_network.tags), + _network.revision_number, + _network.updated_at, ) def setUp(self): @@ -380,7 +399,7 @@ class TestCreateNetworkIdentityV2(TestNetwork): 'project_id': self.project.id, }) self.assertFalse(self.network.set_tags.called) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) def test_create_with_domain_identityv2(self): @@ -413,11 +432,11 @@ class TestDeleteNetwork(TestNetwork): super(TestDeleteNetwork, self).setUp() # The networks to delete - self._networks = network_fakes.FakeNetwork.create_networks(count=3) + self._networks = network_fakes.create_networks(count=3) self.network.delete_network = mock.Mock(return_value=None) - self.network.find_network = network_fakes.FakeNetwork.get_networks( + self.network.find_network = network_fakes.get_networks( networks=self._networks) # Get the command object to test @@ -495,7 +514,7 @@ class TestDeleteNetwork(TestNetwork): class TestListNetwork(TestNetwork): # The networks going to be listed up. - _network = network_fakes.FakeNetwork.create_networks(count=3) + _network = network_fakes.create_networks(count=3) columns = ( 'ID', @@ -521,7 +540,7 @@ class TestListNetwork(TestNetwork): data.append(( net.id, net.name, - format_columns.ListColumn(net.subnets), + format_columns.ListColumn(net.subnet_ids), )) data_long = [] @@ -531,9 +550,9 @@ class TestListNetwork(TestNetwork): net.name, net.status, net.project_id, - network.AdminStateColumn(net.admin_state_up), - net.shared, - format_columns.ListColumn(net.subnets), + network.AdminStateColumn(net.is_admin_state_up), + net.is_shared, + format_columns.ListColumn(net.subnet_ids), net.provider_network_type, network.RouterExternalColumn(net.is_router_external), format_columns.ListColumn(net.availability_zones), @@ -549,7 +568,7 @@ class TestListNetwork(TestNetwork): self.network.networks = mock.Mock(return_value=self._network) self._agent = \ - network_fakes.FakeNetworkAgent.create_one_network_agent() + network_fakes.create_one_network_agent() self.network.get_agent = mock.Mock(return_value=self._agent) self.network.dhcp_agent_hosting_networks = mock.Mock( @@ -840,13 +859,12 @@ class TestListNetwork(TestNetwork): ('agent_id', self._agent.id), ] - attrs = {self._agent, } parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.network.dhcp_agent_hosting_networks.assert_called_once_with( - *attrs) + self._agent) self.assertEqual(self.columns, columns) self.assertCountEqual(list(data), list(self.data)) @@ -880,7 +898,7 @@ class TestListNetwork(TestNetwork): class TestSetNetwork(TestNetwork): # The network to set. - _network = network_fakes.FakeNetwork.create_one_network( + _network = network_fakes.create_one_network( {'tags': ['green', 'red']}) qos_policy = (network_fakes.FakeNetworkQosPolicy. create_one_qos_policy(attrs={'id': _network.qos_policy_id})) @@ -1025,18 +1043,19 @@ class TestSetNetwork(TestNetwork): class TestShowNetwork(TestNetwork): # The network to show. - _network = network_fakes.FakeNetwork.create_one_network() - + _network = network_fakes.create_one_network() columns = ( 'admin_state_up', 'availability_zone_hints', 'availability_zones', + 'created_at', 'description', 'dns_domain', 'id', 'ipv4_address_scope', 'ipv6_address_scope', 'is_default', + 'is_vlan_transparent', 'mtu', 'name', 'port_security_enabled', @@ -1048,14 +1067,18 @@ class TestShowNetwork(TestNetwork): 'router:external', 'shared', 'status', + 'segments', 'subnets', 'tags', + 'revision_number', + 'updated_at', ) data = ( - network.AdminStateColumn(_network.admin_state_up), + network.AdminStateColumn(_network.is_admin_state_up), format_columns.ListColumn(_network.availability_zone_hints), format_columns.ListColumn(_network.availability_zones), + _network.created_at, _network.description, _network.dns_domain, _network.id, @@ -1071,10 +1094,14 @@ class TestShowNetwork(TestNetwork): _network.provider_segmentation_id, _network.qos_policy_id, network.RouterExternalColumn(_network.is_router_external), - _network.shared, + _network.is_shared, + _network.is_vlan_transparent, _network.status, - format_columns.ListColumn(_network.subnets), + _network.segments, + format_columns.ListColumn(_network.subnet_ids), format_columns.ListColumn(_network.tags), + _network.revision_number, + _network.updated_at, ) def setUp(self): @@ -1106,14 +1133,14 @@ class TestShowNetwork(TestNetwork): self.network.find_network.assert_called_once_with( self._network.name, ignore_missing=False) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) class TestUnsetNetwork(TestNetwork): # The network to set. - _network = network_fakes.FakeNetwork.create_one_network( + _network = network_fakes.create_one_network( {'tags': ['green', 'red']}) qos_policy = (network_fakes.FakeNetworkQosPolicy. create_one_qos_policy(attrs={'id': _network.qos_policy_id})) diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py index 734a36e..15c4c5d 100644 --- a/openstackclient/tests/unit/network/v2/test_network_agent.py +++ b/openstackclient/tests/unit/network/v2/test_network_agent.py @@ -33,8 +33,8 @@ class TestNetworkAgent(network_fakes.TestNetworkV2): class TestAddNetworkToAgent(TestNetworkAgent): - net = network_fakes.FakeNetwork.create_one_network() - agent = network_fakes.FakeNetworkAgent.create_one_network_agent() + net = network_fakes.create_one_network() + agent = network_fakes.create_one_network_agent() def setUp(self): super(TestAddNetworkToAgent, self).setUp() @@ -76,7 +76,7 @@ class TestAddNetworkToAgent(TestNetworkAgent): class TestAddRouterAgent(TestNetworkAgent): _router = network_fakes.FakeRouter.create_one_router() - _agent = network_fakes.FakeNetworkAgent.create_one_network_agent() + _agent = network_fakes.create_one_network_agent() def setUp(self): super(TestAddRouterAgent, self).setUp() @@ -115,8 +115,7 @@ class TestAddRouterAgent(TestNetworkAgent): class TestDeleteNetworkAgent(TestNetworkAgent): - network_agents = ( - network_fakes.FakeNetworkAgent.create_network_agents(count=2)) + network_agents = network_fakes.create_network_agents(count=2) def setUp(self): super(TestDeleteNetworkAgent, self).setUp() @@ -188,8 +187,7 @@ class TestDeleteNetworkAgent(TestNetworkAgent): class TestListNetworkAgent(TestNetworkAgent): - network_agents = ( - network_fakes.FakeNetworkAgent.create_network_agents(count=3)) + network_agents = network_fakes.create_network_agents(count=3) columns = ( 'ID', @@ -207,8 +205,8 @@ class TestListNetworkAgent(TestNetworkAgent): agent.agent_type, agent.host, agent.availability_zone, - network_agent.AliveColumn(agent.alive), - network_agent.AdminStateColumn(agent.admin_state_up), + network_agent.AliveColumn(agent.is_alive), + network_agent.AdminStateColumn(agent.is_admin_state_up), agent.binary, )) @@ -217,11 +215,10 @@ class TestListNetworkAgent(TestNetworkAgent): self.network.agents = mock.Mock( return_value=self.network_agents) - _testagent = \ - network_fakes.FakeNetworkAgent.create_one_network_agent() + _testagent = network_fakes.create_one_network_agent() self.network.get_agent = mock.Mock(return_value=_testagent) - self._testnetwork = network_fakes.FakeNetwork.create_one_network() + self._testnetwork = network_fakes.create_one_network() self.network.find_network = mock.Mock(return_value=self._testnetwork) self.network.network_hosting_dhcp_agents = mock.Mock( return_value=self.network_agents) @@ -290,13 +287,11 @@ class TestListNetworkAgent(TestNetworkAgent): ('network', self._testnetwork.id), ] - attrs = {self._testnetwork, } - parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.network.network_hosting_dhcp_agents.assert_called_once_with( - *attrs) + self._testnetwork) self.assertEqual(self.columns, columns) self.assertCountEqual(self.data, list(data)) @@ -343,13 +338,13 @@ class TestListNetworkAgent(TestNetworkAgent): router_agent_data = [d + ('',) for d in self.data] self.assertEqual(router_agent_columns, columns) - self.assertCountEqual(router_agent_data, list(data)) + self.assertEqual(len(router_agent_data), len(list(data))) class TestRemoveNetworkFromAgent(TestNetworkAgent): - net = network_fakes.FakeNetwork.create_one_network() - agent = network_fakes.FakeNetworkAgent.create_one_network_agent() + net = network_fakes.create_one_network() + agent = network_fakes.create_one_network_agent() def setUp(self): super(TestRemoveNetworkFromAgent, self).setUp() @@ -400,7 +395,7 @@ class TestRemoveNetworkFromAgent(TestNetworkAgent): class TestRemoveRouterAgent(TestNetworkAgent): _router = network_fakes.FakeRouter.create_one_router() - _agent = network_fakes.FakeNetworkAgent.create_one_network_agent() + _agent = network_fakes.create_one_network_agent() def setUp(self): super(TestRemoveRouterAgent, self).setUp() @@ -440,8 +435,7 @@ class TestRemoveRouterAgent(TestNetworkAgent): class TestSetNetworkAgent(TestNetworkAgent): - _network_agent = ( - network_fakes.FakeNetworkAgent.create_one_network_agent()) + _network_agent = network_fakes.create_one_network_agent() def setUp(self): super(TestSetNetworkAgent, self).setUp() @@ -517,8 +511,7 @@ class TestSetNetworkAgent(TestNetworkAgent): class TestShowNetworkAgent(TestNetworkAgent): - _network_agent = ( - network_fakes.FakeNetworkAgent.create_one_network_agent()) + _network_agent = network_fakes.create_one_network_agent() columns = ( 'admin_state_up', @@ -526,19 +519,33 @@ class TestShowNetworkAgent(TestNetworkAgent): 'alive', 'availability_zone', 'binary', - 'configurations', + 'configuration', + 'created_at', + 'description', 'host', + 'ha_state', 'id', + 'last_heartbeat_at', + 'resources_synced', + 'started_at', + 'topic', ) data = ( - network_agent.AdminStateColumn(_network_agent.admin_state_up), + network_agent.AdminStateColumn(_network_agent.is_admin_state_up), _network_agent.agent_type, network_agent.AliveColumn(_network_agent.is_alive), _network_agent.availability_zone, _network_agent.binary, - format_columns.DictColumn(_network_agent.configurations), + format_columns.DictColumn(_network_agent.configuration), + _network_agent.created_at, + _network_agent.description, + _network_agent.ha_state, _network_agent.host, _network_agent.id, + _network_agent.last_heartbeat_at, + _network_agent.resources_synced, + _network_agent.started_at, + _network_agent.topic, ) def setUp(self): @@ -570,5 +577,5 @@ class TestShowNetworkAgent(TestNetworkAgent): self.network.get_agent.assert_called_once_with( self._network_agent.id) - self.assertEqual(self.columns, columns) - self.assertCountEqual(list(self.data), list(data)) + self.assertEqual(set(self.columns), set(columns)) + self.assertEqual(len(list(self.data)), len(list(data))) diff --git a/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py b/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py index d77d689..a5dbcde 100644 --- a/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py +++ b/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py @@ -29,9 +29,9 @@ class TestAutoAllocatedTopology(network_fakes.TestNetworkV2): class TestCreateAutoAllocatedTopology(TestAutoAllocatedTopology): project = identity_fakes.FakeProject.create_one_project() - network_object = network_fakes.FakeNetwork.create_one_network() + network_object = network_fakes.create_one_network() - topology = network_fakes.FakeAutoAllocatedTopology.create_one_topology( + topology = network_fakes.create_one_topology( attrs={'id': network_object.id, 'project_id': project.id} ) @@ -127,9 +127,9 @@ class TestCreateAutoAllocatedTopology(TestAutoAllocatedTopology): class TestValidateAutoAllocatedTopology(TestAutoAllocatedTopology): project = identity_fakes.FakeProject.create_one_project() - network_object = network_fakes.FakeNetwork.create_one_network() + network_object = network_fakes.create_one_network() - topology = network_fakes.FakeAutoAllocatedTopology.create_one_topology( + topology = network_fakes.create_one_topology( attrs={'id': network_object.id, 'project_id': project.id} ) @@ -204,9 +204,9 @@ class TestValidateAutoAllocatedTopology(TestAutoAllocatedTopology): class TestDeleteAutoAllocatedTopology(TestAutoAllocatedTopology): project = identity_fakes.FakeProject.create_one_project() - network_object = network_fakes.FakeNetwork.create_one_network() + network_object = network_fakes.create_one_network() - topology = network_fakes.FakeAutoAllocatedTopology.create_one_topology( + topology = network_fakes.create_one_topology( attrs={'id': network_object.id, 'project_id': project.id} ) diff --git a/openstackclient/tests/unit/network/v2/test_network_flavor.py b/openstackclient/tests/unit/network/v2/test_network_flavor.py index 20c5b9d..3149def 100644 --- a/openstackclient/tests/unit/network/v2/test_network_flavor.py +++ b/openstackclient/tests/unit/network/v2/test_network_flavor.py @@ -39,10 +39,8 @@ class TestNetworkFlavor(network_fakes.TestNetworkV2): class TestAddNetworkFlavorToProfile(TestNetworkFlavor): - network_flavor = \ - network_fakes.FakeNetworkFlavor.create_one_network_flavor() - service_profile = \ - network_fakes.FakeNetworkFlavorProfile.create_one_service_profile() + network_flavor = network_fakes.create_one_network_flavor() + service_profile = network_fakes.create_one_service_profile() def setUp(self): super(TestAddNetworkFlavorToProfile, self).setUp() @@ -84,23 +82,22 @@ class TestCreateNetworkFlavor(TestNetworkFlavor): project = identity_fakes_v3.FakeProject.create_one_project() domain = identity_fakes_v3.FakeDomain.create_one_domain() # The new network flavor created. - new_network_flavor = ( - network_fakes.FakeNetworkFlavor.create_one_network_flavor()) + new_network_flavor = network_fakes.create_one_network_flavor() columns = ( 'description', 'enabled', 'id', 'name', - 'project_id', - 'service_type' + 'service_type', + 'service_profile_ids', ) data = ( new_network_flavor.description, - new_network_flavor.enabled, + new_network_flavor.is_enabled, new_network_flavor.id, new_network_flavor.name, - new_network_flavor.project_id, new_network_flavor.service_type, + new_network_flavor.service_profile_ids, ) def setUp(self): @@ -139,14 +136,14 @@ class TestCreateNetworkFlavor(TestNetworkFlavor): 'service_type': self.new_network_flavor.service_type, 'name': self.new_network_flavor.name, }) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertEqual(set(self.columns), set(columns)) + self.assertEqual(set(self.data), set(data)) def test_create_all_options(self): arglist = [ '--description', self.new_network_flavor.description, '--enable', - '--project', self.new_network_flavor.project_id, + '--project', self.project.id, '--project-domain', self.domain.name, '--service-type', self.new_network_flavor.service_type, self.new_network_flavor.name, @@ -154,7 +151,7 @@ class TestCreateNetworkFlavor(TestNetworkFlavor): verifylist = [ ('description', self.new_network_flavor.description), ('enable', True), - ('project', self.new_network_flavor.project_id), + ('project', self.project.id), ('project_domain', self.domain.name), ('service_type', self.new_network_flavor.service_type), ('name', self.new_network_flavor.name), @@ -170,8 +167,8 @@ class TestCreateNetworkFlavor(TestNetworkFlavor): 'service_type': self.new_network_flavor.service_type, 'name': self.new_network_flavor.name, }) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertEqual(set(self.columns), set(columns)) + self.assertEqual(set(self.data), set(data)) def test_create_disable(self): arglist = [ @@ -193,23 +190,20 @@ class TestCreateNetworkFlavor(TestNetworkFlavor): 'service_type': self.new_network_flavor.service_type, 'name': self.new_network_flavor.name, }) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertEqual(set(self.columns), set(columns)) + self.assertEqual(set(self.data), set(data)) class TestDeleteNetworkFlavor(TestNetworkFlavor): # The network flavor to delete. - _network_flavors = ( - network_fakes.FakeNetworkFlavor.create_flavor(count=2)) + _network_flavors = network_fakes.create_flavor(count=2) def setUp(self): super(TestDeleteNetworkFlavor, self).setUp() self.network.delete_flavor = mock.Mock(return_value=None) - self.network.find_flavor = ( - network_fakes.FakeNetworkFlavor.get_flavor( - network_flavors=self._network_flavors) - ) + self.network.find_flavor = network_fakes.get_flavor( + network_flavors=self._network_flavors) # Get the command object to test self.cmd = network_flavor.DeleteNetworkFlavor(self.app, self.namespace) @@ -284,8 +278,7 @@ class TestDeleteNetworkFlavor(TestNetworkFlavor): class TestListNetworkFlavor(TestNetworkFlavor): # The network flavors to list up. - _network_flavors = ( - network_fakes.FakeNetworkFlavor.create_flavor(count=2)) + _network_flavors = network_fakes.create_flavor(count=2) columns = ( 'ID', 'Name', @@ -298,7 +291,7 @@ class TestListNetworkFlavor(TestNetworkFlavor): data.append(( flavor.id, flavor.name, - flavor.enabled, + flavor.is_enabled, flavor.service_type, flavor.description, )) @@ -319,16 +312,14 @@ class TestListNetworkFlavor(TestNetworkFlavor): columns, data = self.cmd.take_action(parsed_args) self.network.flavors.assert_called_once_with(**{}) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertEqual(self.data, list(data)) class TestRemoveNetworkFlavorFromProfile(TestNetworkFlavor): - network_flavor = \ - network_fakes.FakeNetworkFlavor.create_one_network_flavor() - service_profile = \ - network_fakes.FakeNetworkFlavorProfile.create_one_service_profile() + network_flavor = network_fakes.create_one_network_flavor() + service_profile = network_fakes.create_one_service_profile() def setUp(self): super(TestRemoveNetworkFlavorFromProfile, self).setUp() @@ -368,23 +359,22 @@ class TestRemoveNetworkFlavorFromProfile(TestNetworkFlavor): class TestShowNetworkFlavor(TestNetworkFlavor): # The network flavor to show. - new_network_flavor = ( - network_fakes.FakeNetworkFlavor.create_one_network_flavor()) + new_network_flavor = network_fakes.create_one_network_flavor() columns = ( 'description', 'enabled', 'id', 'name', - 'project_id', - 'service_type' + 'service_type', + 'service_profile_ids', ) data = ( new_network_flavor.description, - new_network_flavor.enabled, + new_network_flavor.is_enabled, new_network_flavor.id, new_network_flavor.name, - new_network_flavor.project_id, new_network_flavor.service_type, + new_network_flavor.service_profile_ids, ) def setUp(self): @@ -416,15 +406,15 @@ class TestShowNetworkFlavor(TestNetworkFlavor): self.network.find_flavor.assert_called_once_with( self.new_network_flavor.name, ignore_missing=False) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertEqual(set(self.columns), set(columns)) + self.assertEqual(set(self.data), set(data)) class TestSetNetworkFlavor(TestNetworkFlavor): # The network flavor to set. new_network_flavor = ( - network_fakes.FakeNetworkFlavor.create_one_network_flavor()) + network_fakes.create_one_network_flavor()) def setUp(self): super(TestSetNetworkFlavor, self).setUp() diff --git a/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py b/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py index 1cbe30b..5c2b9e2 100644 --- a/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py +++ b/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py @@ -34,25 +34,23 @@ class TestFlavorProfile(network_fakes.TestNetworkV2): class TestCreateFlavorProfile(TestFlavorProfile): project = identity_fakes_v3.FakeProject.create_one_project() domain = identity_fakes_v3.FakeDomain.create_one_domain() - new_flavor_profile = ( - network_fakes.FakeNetworkFlavorProfile. - create_one_service_profile() - ) + new_flavor_profile = network_fakes.create_one_service_profile() + columns = ( 'description', 'driver', 'enabled', 'id', - 'metainfo', + 'meta_info', 'project_id', ) data = ( new_flavor_profile.description, new_flavor_profile.driver, - new_flavor_profile.enabled, + new_flavor_profile.is_enabled, new_flavor_profile.id, - new_flavor_profile.metainfo, + new_flavor_profile.meta_info, new_flavor_profile.project_id, ) @@ -72,7 +70,7 @@ class TestCreateFlavorProfile(TestFlavorProfile): '--project-domain', self.domain.name, "--enable", "--driver", self.new_flavor_profile.driver, - "--metainfo", self.new_flavor_profile.metainfo, + "--metainfo", self.new_flavor_profile.meta_info, ] verifylist = [ @@ -81,7 +79,7 @@ class TestCreateFlavorProfile(TestFlavorProfile): ('project_domain', self.domain.name), ('enable', True), ('driver', self.new_flavor_profile.driver), - ('metainfo', self.new_flavor_profile.metainfo) + ('metainfo', self.new_flavor_profile.meta_info) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -90,9 +88,9 @@ class TestCreateFlavorProfile(TestFlavorProfile): self.network.create_service_profile.assert_called_once_with( **{'description': self.new_flavor_profile.description, 'project_id': self.project.id, - 'enabled': self.new_flavor_profile.enabled, + 'enabled': self.new_flavor_profile.is_enabled, 'driver': self.new_flavor_profile.driver, - 'metainfo': self.new_flavor_profile.metainfo} + 'metainfo': self.new_flavor_profile.meta_info} ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -103,7 +101,7 @@ class TestCreateFlavorProfile(TestFlavorProfile): "--project", self.new_flavor_profile.project_id, '--project-domain', self.domain.name, "--enable", - "--metainfo", self.new_flavor_profile.metainfo, + "--metainfo", self.new_flavor_profile.meta_info, ] verifylist = [ @@ -111,7 +109,7 @@ class TestCreateFlavorProfile(TestFlavorProfile): ('project', self.new_flavor_profile.project_id), ('project_domain', self.domain.name), ('enable', True), - ('metainfo', self.new_flavor_profile.metainfo) + ('metainfo', self.new_flavor_profile.meta_info) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -120,8 +118,8 @@ class TestCreateFlavorProfile(TestFlavorProfile): self.network.create_service_profile.assert_called_once_with( **{'description': self.new_flavor_profile.description, 'project_id': self.project.id, - 'enabled': self.new_flavor_profile.enabled, - 'metainfo': self.new_flavor_profile.metainfo} + 'enabled': self.new_flavor_profile.is_enabled, + 'metainfo': self.new_flavor_profile.meta_info} ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -149,7 +147,7 @@ class TestCreateFlavorProfile(TestFlavorProfile): self.network.create_service_profile.assert_called_once_with( **{'description': self.new_flavor_profile.description, 'project_id': self.project.id, - 'enabled': self.new_flavor_profile.enabled, + 'enabled': self.new_flavor_profile.is_enabled, 'driver': self.new_flavor_profile.driver, } ) @@ -203,14 +201,13 @@ class TestCreateFlavorProfile(TestFlavorProfile): class TestDeleteFlavorProfile(TestFlavorProfile): # The network flavor_profiles to delete. - _network_flavor_profiles = ( - network_fakes.FakeNetworkFlavorProfile.create_service_profile(count=2)) + _network_flavor_profiles = network_fakes.create_service_profile(count=2) def setUp(self): super(TestDeleteFlavorProfile, self).setUp() self.network.delete_service_profile = mock.Mock(return_value=None) self.network.find_service_profile = ( - network_fakes.FakeNetworkFlavorProfile.get_service_profile( + network_fakes.get_service_profile( flavor_profile=self._network_flavor_profiles) ) @@ -290,8 +287,7 @@ class TestDeleteFlavorProfile(TestFlavorProfile): class TestListFlavorProfile(TestFlavorProfile): # The network flavor profiles list - _network_flavor_profiles = ( - network_fakes.FakeNetworkFlavorProfile.create_service_profile(count=2)) + _network_flavor_profiles = network_fakes.create_service_profile(count=2) columns = ( 'ID', @@ -305,8 +301,8 @@ class TestListFlavorProfile(TestFlavorProfile): data.append(( flavor_profile.id, flavor_profile.driver, - flavor_profile.enabled, - flavor_profile.metainfo, + flavor_profile.is_enabled, + flavor_profile.meta_info, flavor_profile.description, )) @@ -334,22 +330,21 @@ class TestListFlavorProfile(TestFlavorProfile): class TestShowFlavorProfile(TestFlavorProfile): # The network flavor profile to show. - network_flavor_profile = ( - network_fakes.FakeNetworkFlavorProfile.create_one_service_profile()) + network_flavor_profile = network_fakes.create_one_service_profile() columns = ( 'description', 'driver', 'enabled', 'id', - 'metainfo', + 'meta_info', 'project_id', ) data = ( network_flavor_profile.description, network_flavor_profile.driver, - network_flavor_profile.enabled, + network_flavor_profile.is_enabled, network_flavor_profile.id, - network_flavor_profile.metainfo, + network_flavor_profile.meta_info, network_flavor_profile.project_id, ) @@ -382,8 +377,7 @@ class TestShowFlavorProfile(TestFlavorProfile): class TestSetFlavorProfile(TestFlavorProfile): # The network flavor profile to set. - network_flavor_profile = ( - network_fakes.FakeNetworkFlavorProfile.create_one_service_profile()) + network_flavor_profile = network_fakes.create_one_service_profile() def setUp(self): super(TestSetFlavorProfile, self).setUp() diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_rule.py b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py index 217e481..c7de816 100644 --- a/openstackclient/tests/unit/network/v2/test_network_qos_rule.py +++ b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py @@ -25,6 +25,7 @@ from openstackclient.tests.unit import utils as tests_utils RULE_TYPE_BANDWIDTH_LIMIT = 'bandwidth-limit' RULE_TYPE_DSCP_MARKING = 'dscp-marking' RULE_TYPE_MINIMUM_BANDWIDTH = 'minimum-bandwidth' +RULE_TYPE_MINIMUM_PACKET_RATE = 'minimum-packet-rate' DSCP_VALID_MARKS = [0, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 46, 48, 56] @@ -126,8 +127,101 @@ class TestCreateNetworkQosRuleMinimumBandwidth(TestNetworkQosRule): try: self.cmd.take_action(parsed_args) except exceptions.CommandError as e: - msg = ('"Create" rule command for type "minimum-bandwidth" ' - 'requires arguments: direction, min_kbps') + msg = ('Failed to create Network QoS rule: "Create" rule command ' + 'for type "minimum-bandwidth" requires arguments: ' + 'direction, min_kbps') + self.assertEqual(msg, str(e)) + + +class TestCreateNetworkQosRuleMinimumPacketRate(TestNetworkQosRule): + + def test_check_type_parameters(self): + pass + + def setUp(self): + super(TestCreateNetworkQosRuleMinimumPacketRate, self).setUp() + attrs = {'qos_policy_id': self.qos_policy.id, + 'type': RULE_TYPE_MINIMUM_PACKET_RATE} + self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule( + attrs) + self.columns = ( + 'direction', + 'id', + 'min_kpps', + 'project_id', + 'qos_policy_id', + 'type' + ) + + self.data = ( + self.new_rule.direction, + self.new_rule.id, + self.new_rule.min_kpps, + self.new_rule.project_id, + self.new_rule.qos_policy_id, + self.new_rule.type, + ) + self.network.create_qos_minimum_packet_rate_rule = mock.Mock( + return_value=self.new_rule) + + # Get the command object to test + self.cmd = network_qos_rule.CreateNetworkQosRule(self.app, + self.namespace) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_default_options(self): + arglist = [ + '--type', RULE_TYPE_MINIMUM_PACKET_RATE, + '--min-kpps', str(self.new_rule.min_kpps), + '--egress', + self.new_rule.qos_policy_id, + ] + + verifylist = [ + ('type', RULE_TYPE_MINIMUM_PACKET_RATE), + ('min_kpps', self.new_rule.min_kpps), + ('egress', True), + ('qos_policy', self.new_rule.qos_policy_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_qos_minimum_packet_rate_rule.\ + assert_called_once_with( + self.qos_policy.id, + **{'min_kpps': self.new_rule.min_kpps, + 'direction': self.new_rule.direction}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_wrong_options(self): + arglist = [ + '--type', RULE_TYPE_MINIMUM_PACKET_RATE, + '--min-kbps', '10000', + self.new_rule.qos_policy_id, + ] + + verifylist = [ + ('type', RULE_TYPE_MINIMUM_PACKET_RATE), + ('min_kbps', 10000), + ('qos_policy', self.new_rule.qos_policy_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + except exceptions.CommandError as e: + msg = ('Failed to create Network QoS rule: "Create" rule command ' + 'for type "minimum-packet-rate" requires arguments: ' + 'direction, min_kpps') self.assertEqual(msg, str(e)) @@ -212,8 +306,8 @@ class TestCreateNetworkQosRuleDSCPMarking(TestNetworkQosRule): try: self.cmd.take_action(parsed_args) except exceptions.CommandError as e: - msg = ('"Create" rule command for type "dscp-marking" ' - 'requires arguments: dscp_mark') + msg = ('Failed to create Network QoS rule: "Create" rule command ' + 'for type "dscp-marking" requires arguments: dscp_mark') self.assertEqual(msg, str(e)) @@ -351,8 +445,8 @@ class TestCreateNetworkQosRuleBandwidtLimit(TestNetworkQosRule): try: self.cmd.take_action(parsed_args) except exceptions.CommandError as e: - msg = ('"Create" rule command for type "bandwidth-limit" ' - 'requires arguments: max_kbps') + msg = ('Failed to create Network QoS rule: "Create" rule command ' + 'for type "bandwidth-limit" requires arguments: max_kbps') self.assertEqual(msg, str(e)) @@ -415,6 +509,65 @@ class TestDeleteNetworkQosRuleMinimumBandwidth(TestNetworkQosRule): self.assertEqual(msg, str(e)) +class TestDeleteNetworkQosRuleMinimumPacketRate(TestNetworkQosRule): + + def setUp(self): + super(TestDeleteNetworkQosRuleMinimumPacketRate, self).setUp() + attrs = {'qos_policy_id': self.qos_policy.id, + 'type': RULE_TYPE_MINIMUM_PACKET_RATE} + self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule( + attrs) + self.qos_policy.rules = [self.new_rule] + self.network.delete_qos_minimum_packet_rate_rule = mock.Mock( + return_value=None) + self.network.find_qos_minimum_packet_rate_rule = ( + network_fakes.FakeNetworkQosRule.get_qos_rules( + qos_rules=self.new_rule) + ) + + # Get the command object to test + self.cmd = network_qos_rule.DeleteNetworkQosRule(self.app, + self.namespace) + + def test_qos_policy_delete(self): + arglist = [ + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.network.find_qos_policy.assert_called_once_with( + self.qos_policy.id, ignore_missing=False) + self.network.delete_qos_minimum_packet_rate_rule.\ + assert_called_once_with(self.new_rule.id, self.qos_policy.id) + self.assertIsNone(result) + + def test_qos_policy_delete_error(self): + arglist = [ + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + self.network.delete_qos_minimum_packet_rate_rule.side_effect = \ + Exception('Error message') + try: + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + except exceptions.CommandError as e: + msg = ('Failed to delete Network QoS rule ID "%(rule)s": %(e)s' % + {'rule': self.new_rule.id, 'e': 'Error message'}) + self.assertEqual(msg, str(e)) + + class TestDeleteNetworkQosRuleDSCPMarking(TestNetworkQosRule): def setUp(self): @@ -627,6 +780,100 @@ class TestSetNetworkQosRuleMinimumBandwidth(TestNetworkQosRule): self.assertEqual(msg, str(e)) +class TestSetNetworkQosRuleMinimumPacketRate(TestNetworkQosRule): + + def setUp(self): + super(TestSetNetworkQosRuleMinimumPacketRate, self).setUp() + attrs = {'qos_policy_id': self.qos_policy.id, + 'type': RULE_TYPE_MINIMUM_PACKET_RATE} + self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule( + attrs=attrs) + self.qos_policy.rules = [self.new_rule] + self.network.update_qos_minimum_packet_rate_rule = mock.Mock( + return_value=None) + self.network.find_qos_minimum_packet_rate_rule = mock.Mock( + return_value=self.new_rule) + self.network.find_qos_policy = mock.Mock( + return_value=self.qos_policy) + + # Get the command object to test + self.cmd = (network_qos_rule.SetNetworkQosRule(self.app, + self.namespace)) + + def test_set_nothing(self): + arglist = [ + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network.update_qos_minimum_packet_rate_rule.assert_called_with( + self.new_rule, self.qos_policy.id) + self.assertIsNone(result) + + def test_set_min_kpps(self): + self._set_min_kpps() + + def test_set_min_kpps_to_zero(self): + self._set_min_kpps(min_kpps=0) + + def _set_min_kpps(self, min_kpps=None): + if min_kpps: + previous_min_kpps = self.new_rule.min_kpps + self.new_rule.min_kpps = min_kpps + + arglist = [ + '--min-kpps', str(self.new_rule.min_kpps), + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('min_kpps', self.new_rule.min_kpps), + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'min_kpps': self.new_rule.min_kpps, + } + self.network.update_qos_minimum_packet_rate_rule.assert_called_with( + self.new_rule, self.qos_policy.id, **attrs) + self.assertIsNone(result) + + if min_kpps: + self.new_rule.min_kpps = previous_min_kpps + + def test_set_wrong_options(self): + arglist = [ + '--min-kbps', str(10000), + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('min_kbps', 10000), + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + except exceptions.CommandError as e: + msg = ('Failed to set Network QoS rule ID "%(rule)s": Rule type ' + '"minimum-packet-rate" only requires arguments: direction, ' + 'min_kpps' % {'rule': self.new_rule.id}) + self.assertEqual(msg, str(e)) + + class TestSetNetworkQosRuleDSCPMarking(TestNetworkQosRule): def setUp(self): @@ -893,6 +1140,9 @@ class TestListNetworkQosRule(TestNetworkQosRule): 'type': RULE_TYPE_MINIMUM_BANDWIDTH} self.new_rule_min_bw = (network_fakes.FakeNetworkQosRule. create_one_qos_rule(attrs=attrs)) + attrs['type'] = RULE_TYPE_MINIMUM_PACKET_RATE + self.new_rule_min_pps = (network_fakes.FakeNetworkQosRule. + create_one_qos_rule(attrs=attrs)) attrs['type'] = RULE_TYPE_DSCP_MARKING self.new_rule_dscp_mark = (network_fakes.FakeNetworkQosRule. create_one_qos_rule(attrs=attrs)) @@ -900,10 +1150,13 @@ class TestListNetworkQosRule(TestNetworkQosRule): self.new_rule_max_bw = (network_fakes.FakeNetworkQosRule. create_one_qos_rule(attrs=attrs)) self.qos_policy.rules = [self.new_rule_min_bw, + self.new_rule_min_pps, self.new_rule_dscp_mark, self.new_rule_max_bw] self.network.find_qos_minimum_bandwidth_rule = mock.Mock( return_value=self.new_rule_min_bw) + self.network.find_qos_minimum_packet_rate_rule = mock.Mock( + return_value=self.new_rule_min_pps) self.network.find_qos_dscp_marking_rule = mock.Mock( return_value=self.new_rule_dscp_mark) self.network.find_qos_bandwidth_limit_rule = mock.Mock( @@ -915,6 +1168,7 @@ class TestListNetworkQosRule(TestNetworkQosRule): 'Max Kbps', 'Max Burst Kbits', 'Min Kbps', + 'Min Kpps', 'DSCP mark', 'Direction', ) @@ -927,6 +1181,7 @@ class TestListNetworkQosRule(TestNetworkQosRule): getattr(self.qos_policy.rules[index], 'max_kbps', ''), getattr(self.qos_policy.rules[index], 'max_burst_kbps', ''), getattr(self.qos_policy.rules[index], 'min_kbps', ''), + getattr(self.qos_policy.rules[index], 'min_kpps', ''), getattr(self.qos_policy.rules[index], 'dscp_mark', ''), getattr(self.qos_policy.rules[index], 'direction', ''), )) @@ -1014,6 +1269,66 @@ class TestShowNetworkQosRuleMinimumBandwidth(TestNetworkQosRule): self.assertEqual(list(self.data), list(data)) +class TestShowNetworkQosRuleMinimumPacketRate(TestNetworkQosRule): + + def setUp(self): + super(TestShowNetworkQosRuleMinimumPacketRate, self).setUp() + attrs = {'qos_policy_id': self.qos_policy.id, + 'type': RULE_TYPE_MINIMUM_PACKET_RATE} + self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule( + attrs) + self.qos_policy.rules = [self.new_rule] + self.columns = ( + 'direction', + 'id', + 'min_kpps', + 'project_id', + 'qos_policy_id', + 'type' + ) + self.data = ( + self.new_rule.direction, + self.new_rule.id, + self.new_rule.min_kpps, + self.new_rule.project_id, + self.new_rule.qos_policy_id, + self.new_rule.type, + ) + + self.network.get_qos_minimum_packet_rate_rule = mock.Mock( + return_value=self.new_rule) + + # Get the command object to test + self.cmd = network_qos_rule.ShowNetworkQosRule(self.app, + self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.get_qos_minimum_packet_rate_rule.assert_called_once_with( + self.new_rule.id, self.qos_policy.id) + self.assertEqual(self.columns, columns) + self.assertEqual(list(self.data), list(data)) + + class TestShowNetworkQosDSCPMarking(TestNetworkQosRule): def setUp(self): diff --git a/openstackclient/tests/unit/network/v2/test_network_rbac.py b/openstackclient/tests/unit/network/v2/test_network_rbac.py index e20efc8..b0bc7e8 100644 --- a/openstackclient/tests/unit/network/v2/test_network_rbac.py +++ b/openstackclient/tests/unit/network/v2/test_network_rbac.py @@ -37,14 +37,14 @@ class TestNetworkRBAC(network_fakes.TestNetworkV2): @ddt.ddt class TestCreateNetworkRBAC(TestNetworkRBAC): - network_object = network_fakes.FakeNetwork.create_one_network() + network_object = network_fakes.create_one_network() qos_object = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() sg_object = network_fakes.FakeNetworkSecGroup.create_one_security_group() - as_object = network_fakes.FakeAddressScope.create_one_address_scope() + as_object = network_fakes.create_one_address_scope() snp_object = network_fakes.FakeSubnetPool.create_one_subnet_pool() - ag_object = network_fakes.FakeAddressGroup.create_one_address_group() + ag_object = network_fakes.create_one_address_group() project = identity_fakes_v3.FakeProject.create_one_project() - rbac_policy = network_fakes.FakeNetworkRBAC.create_one_network_rbac( + rbac_policy = network_fakes.create_one_network_rbac( attrs={'project_id': project.id, 'target_tenant': project.id, 'object_id': network_object.id} @@ -65,7 +65,7 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): rbac_policy.object_id, rbac_policy.object_type, rbac_policy.project_id, - rbac_policy.target_tenant, + rbac_policy.target_project_id, ] def setUp(self): @@ -120,13 +120,13 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): arglist = [ '--action', self.rbac_policy.action, '--type', 'invalid_type', - '--target-project', self.rbac_policy.target_tenant, + '--target-project', self.rbac_policy.target_project_id, self.rbac_policy.object_id, ] verifylist = [ ('action', self.rbac_policy.action), ('type', 'invalid_type'), - ('target-project', self.rbac_policy.target_tenant), + ('target-project', self.rbac_policy.target_project_id), ('rbac_policy', self.rbac_policy.id), ] @@ -137,13 +137,13 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): arglist = [ '--type', self.rbac_policy.object_type, '--action', 'invalid_action', - '--target-project', self.rbac_policy.target_tenant, + '--target-project', self.rbac_policy.target_project_id, self.rbac_policy.object_id, ] verifylist = [ ('type', self.rbac_policy.object_type), ('action', 'invalid_action'), - ('target-project', self.rbac_policy.target_tenant), + ('target-project', self.rbac_policy.target_project_id), ('rbac_policy', self.rbac_policy.id), ] @@ -154,13 +154,13 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): arglist = [ '--type', self.rbac_policy.object_type, '--action', self.rbac_policy.action, - '--target-project', self.rbac_policy.target_tenant, + '--target-project', self.rbac_policy.target_project_id, self.rbac_policy.object_id, ] verifylist = [ ('type', self.rbac_policy.object_type), ('action', self.rbac_policy.action), - ('target_project', self.rbac_policy.target_tenant), + ('target_project', self.rbac_policy.target_project_id), ('rbac_object', self.rbac_policy.object_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -172,7 +172,7 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): 'object_id': self.rbac_policy.object_id, 'object_type': self.rbac_policy.object_type, 'action': self.rbac_policy.action, - 'target_tenant': self.rbac_policy.target_tenant, + 'target_tenant': self.rbac_policy.target_project_id, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -205,7 +205,7 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): arglist = [ '--type', self.rbac_policy.object_type, '--action', self.rbac_policy.action, - '--target-project', self.rbac_policy.target_tenant, + '--target-project', self.rbac_policy.target_project_id, '--project', self.rbac_policy.project_id, '--project-domain', self.project.domain_id, '--target-project-domain', self.project.domain_id, @@ -214,7 +214,7 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): verifylist = [ ('type', self.rbac_policy.object_type), ('action', self.rbac_policy.action), - ('target_project', self.rbac_policy.target_tenant), + ('target_project', self.rbac_policy.target_project_id), ('project', self.rbac_policy.project_id), ('project_domain', self.project.domain_id), ('target_project_domain', self.project.domain_id), @@ -229,7 +229,7 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): 'object_id': self.rbac_policy.object_id, 'object_type': self.rbac_policy.object_type, 'action': self.rbac_policy.action, - 'target_tenant': self.rbac_policy.target_tenant, + 'target_tenant': self.rbac_policy.target_project_id, 'project_id': self.rbac_policy.project_id, }) self.assertEqual(self.columns, columns) @@ -251,13 +251,13 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): arglist = [ '--type', obj_type, '--action', self.rbac_policy.action, - '--target-project', self.rbac_policy.target_tenant, + '--target-project', self.rbac_policy.target_project_id, obj_fake.name, ] verifylist = [ ('type', obj_type), ('action', self.rbac_policy.action), - ('target_project', self.rbac_policy.target_tenant), + ('target_project', self.rbac_policy.target_project_id), ('rbac_object', obj_fake.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -269,7 +269,7 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): 'object_id': obj_fake.id, 'object_type': obj_type, 'action': self.rbac_policy.action, - 'target_tenant': self.rbac_policy.target_tenant, + 'target_tenant': self.rbac_policy.target_project_id, }) self.data = [ self.rbac_policy.action, @@ -277,7 +277,7 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): obj_fake.id, obj_type, self.rbac_policy.project_id, - self.rbac_policy.target_tenant, + self.rbac_policy.target_project_id, ] self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -285,13 +285,13 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): class TestDeleteNetworkRBAC(TestNetworkRBAC): - rbac_policies = network_fakes.FakeNetworkRBAC.create_network_rbacs(count=2) + rbac_policies = network_fakes.create_network_rbacs(count=2) def setUp(self): super(TestDeleteNetworkRBAC, self).setUp() self.network.delete_rbac_policy = mock.Mock(return_value=None) self.network.find_rbac_policy = ( - network_fakes.FakeNetworkRBAC.get_network_rbacs( + network_fakes.get_network_rbacs( rbac_policies=self.rbac_policies) ) @@ -368,7 +368,7 @@ class TestDeleteNetworkRBAC(TestNetworkRBAC): class TestListNetworkRABC(TestNetworkRBAC): # The network rbac policies going to be listed up. - rbac_policies = network_fakes.FakeNetworkRBAC.create_network_rbacs(count=3) + rbac_policies = network_fakes.create_network_rbacs(count=3) columns = ( 'ID', @@ -470,7 +470,7 @@ class TestListNetworkRABC(TestNetworkRBAC): class TestSetNetworkRBAC(TestNetworkRBAC): project = identity_fakes_v3.FakeProject.create_one_project() - rbac_policy = network_fakes.FakeNetworkRBAC.create_one_network_rbac( + rbac_policy = network_fakes.create_one_network_rbac( attrs={'target_tenant': project.id}) def setUp(self): @@ -525,7 +525,7 @@ class TestSetNetworkRBAC(TestNetworkRBAC): class TestShowNetworkRBAC(TestNetworkRBAC): - rbac_policy = network_fakes.FakeNetworkRBAC.create_one_network_rbac() + rbac_policy = network_fakes.create_one_network_rbac() columns = ( 'action', @@ -542,7 +542,7 @@ class TestShowNetworkRBAC(TestNetworkRBAC): rbac_policy.object_id, rbac_policy.object_type, rbac_policy.project_id, - rbac_policy.target_tenant, + rbac_policy.target_project_id, ] def setUp(self): diff --git a/openstackclient/tests/unit/network/v2/test_network_segment.py b/openstackclient/tests/unit/network/v2/test_network_segment.py index 6cd948e..b41b619 100644 --- a/openstackclient/tests/unit/network/v2/test_network_segment.py +++ b/openstackclient/tests/unit/network/v2/test_network_segment.py @@ -33,9 +33,8 @@ class TestNetworkSegment(network_fakes.TestNetworkV2): class TestCreateNetworkSegment(TestNetworkSegment): # The network segment to create along with associated network. - _network_segment = \ - network_fakes.FakeNetworkSegment.create_one_network_segment() - _network = network_fakes.FakeNetwork.create_one_network({ + _network_segment = network_fakes.create_one_network_segment() + _network = network_fakes.create_one_network({ 'id': _network_segment.network_id, }) @@ -156,8 +155,7 @@ class TestCreateNetworkSegment(TestNetworkSegment): class TestDeleteNetworkSegment(TestNetworkSegment): # The network segments to delete. - _network_segments = \ - network_fakes.FakeNetworkSegment.create_network_segments() + _network_segments = network_fakes.create_network_segments() def setUp(self): super(TestDeleteNetworkSegment, self).setUp() @@ -240,9 +238,8 @@ class TestDeleteNetworkSegment(TestNetworkSegment): class TestListNetworkSegment(TestNetworkSegment): - _network = network_fakes.FakeNetwork.create_one_network() - _network_segments = \ - network_fakes.FakeNetworkSegment.create_network_segments(count=3) + _network = network_fakes.create_one_network() + _network_segments = network_fakes.create_network_segments(count=3) columns = ( 'ID', @@ -338,8 +335,7 @@ class TestListNetworkSegment(TestNetworkSegment): class TestSetNetworkSegment(TestNetworkSegment): # The network segment to show. - _network_segment = \ - network_fakes.FakeNetworkSegment.create_one_network_segment() + _network_segment = network_fakes.create_one_network_segment() def setUp(self): super(TestSetNetworkSegment, self).setUp() @@ -398,8 +394,7 @@ class TestSetNetworkSegment(TestNetworkSegment): class TestShowNetworkSegment(TestNetworkSegment): # The network segment to show. - _network_segment = \ - network_fakes.FakeNetworkSegment.create_one_network_segment() + _network_segment = network_fakes.create_one_network_segment() columns = ( 'description', diff --git a/openstackclient/tests/unit/network/v2/test_network_segment_range.py b/openstackclient/tests/unit/network/v2/test_network_segment_range.py index b60f171..20d34bf 100644 --- a/openstackclient/tests/unit/network/v2/test_network_segment_range.py +++ b/openstackclient/tests/unit/network/v2/test_network_segment_range.py @@ -50,8 +50,7 @@ class TestNetworkSegmentRange(network_fakes.TestNetworkV2): class TestCreateNetworkSegmentRange(TestNetworkSegmentRange): # The network segment range to create. - _network_segment_range = network_fakes.FakeNetworkSegmentRange.\ - create_one_network_segment_range() + _network_segment_range = network_fakes.create_one_network_segment_range() columns = ( 'available', @@ -310,8 +309,7 @@ class TestCreateNetworkSegmentRange(TestNetworkSegmentRange): class TestDeleteNetworkSegmentRange(TestNetworkSegmentRange): # The network segment ranges to delete. - _network_segment_ranges = \ - network_fakes.FakeNetworkSegmentRange.create_network_segment_ranges() + _network_segment_ranges = network_fakes.create_network_segment_ranges() def setUp(self): super(TestDeleteNetworkSegmentRange, self).setUp() @@ -396,8 +394,8 @@ class TestDeleteNetworkSegmentRange(TestNetworkSegmentRange): class TestListNetworkSegmentRange(TestNetworkSegmentRange): - _network_segment_ranges = network_fakes.FakeNetworkSegmentRange.\ - create_network_segment_ranges(count=3) + _network_segment_ranges = ( + network_fakes.create_network_segment_ranges(count=3)) columns = ( 'ID', @@ -496,14 +494,13 @@ class TestListNetworkSegmentRange(TestNetworkSegmentRange): class TestSetNetworkSegmentRange(TestNetworkSegmentRange): # The network segment range to set. - _network_segment_range = network_fakes.FakeNetworkSegmentRange.\ - create_one_network_segment_range() + _network_segment_range = network_fakes.create_one_network_segment_range() # The network segment range updated. minimum_updated = _network_segment_range.minimum - 5 maximum_updated = _network_segment_range.maximum + 5 available_updated = (list(range(minimum_updated, 104)) + [105] + list(range(107, maximum_updated + 1))) - _network_segment_range_updated = network_fakes.FakeNetworkSegmentRange.\ + _network_segment_range_updated = network_fakes.\ create_one_network_segment_range( attrs={'minimum': minimum_updated, 'maximum': maximum_updated, @@ -577,8 +574,7 @@ class TestSetNetworkSegmentRange(TestNetworkSegmentRange): class TestShowNetworkSegmentRange(TestNetworkSegmentRange): # The network segment range to show. - _network_segment_range = network_fakes.FakeNetworkSegmentRange.\ - create_one_network_segment_range() + _network_segment_range = network_fakes.create_one_network_segment_range() columns = ( 'available', diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 6f830aa..04412c5 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -50,6 +50,7 @@ class TestPort(network_fakes.TestNetworkV2): 'binding_vif_details', 'binding_vif_type', 'binding_vnic_type', + 'created_at', 'data_plane_status', 'description', 'device_id', @@ -61,6 +62,7 @@ class TestPort(network_fakes.TestNetworkV2): 'extra_dhcp_opts', 'fixed_ips', 'id', + 'ip_allocation', 'mac_address', 'name', 'network_id', @@ -68,21 +70,26 @@ class TestPort(network_fakes.TestNetworkV2): 'port_security_enabled', 'project_id', 'propagate_uplink_status', + 'resource_request', + 'revision_number', 'qos_network_policy_id', 'qos_policy_id', 'security_group_ids', 'status', 'tags', + 'trunk_details', + 'updated_at', ) data = ( - port.AdminStateColumn(fake_port.admin_state_up), + port.AdminStateColumn(fake_port.is_admin_state_up), format_columns.ListDictColumn(fake_port.allowed_address_pairs), fake_port.binding_host_id, format_columns.DictColumn(fake_port.binding_profile), format_columns.DictColumn(fake_port.binding_vif_details), fake_port.binding_vif_type, fake_port.binding_vnic_type, + fake_port.created_at, fake_port.data_plane_status, fake_port.description, fake_port.device_id, @@ -94,18 +101,23 @@ class TestPort(network_fakes.TestNetworkV2): format_columns.ListDictColumn(fake_port.extra_dhcp_opts), format_columns.ListDictColumn(fake_port.fixed_ips), fake_port.id, + fake_port.ip_allocation, fake_port.mac_address, fake_port.name, fake_port.network_id, fake_port.numa_affinity_policy, - fake_port.port_security_enabled, + fake_port.is_port_security_enabled, fake_port.project_id, fake_port.propagate_uplink_status, + fake_port.resource_request, + fake_port.revision_number, fake_port.qos_network_policy_id, fake_port.qos_policy_id, format_columns.ListColumn(fake_port.security_group_ids), fake_port.status, format_columns.ListColumn(fake_port.tags), + fake_port.trunk_details, + fake_port.updated_at, ) return columns, data @@ -113,7 +125,7 @@ class TestPort(network_fakes.TestNetworkV2): class TestCreatePort(TestPort): - _port = network_fakes.FakePort.create_one_port() + _port = network_fakes.create_one_port() columns, data = TestPort._get_common_cols_data(_port) def setUp(self): @@ -121,7 +133,7 @@ class TestCreatePort(TestPort): self.network.create_port = mock.Mock(return_value=self._port) self.network.set_tags = mock.Mock(return_value=None) - fake_net = network_fakes.FakeNetwork.create_one_network({ + fake_net = network_fakes.create_one_network({ 'id': self._port.network_id, }) self.network.find_network = mock.Mock(return_value=fake_net) @@ -152,7 +164,7 @@ class TestCreatePort(TestPort): }) self.assertFalse(self.network.set_tags.called) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) def test_create_full_options(self): @@ -210,7 +222,7 @@ class TestCreatePort(TestPort): 'name': 'test-port', }) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) def test_create_invalid_json_binding_profile(self): @@ -261,7 +273,7 @@ class TestCreatePort(TestPort): 'name': 'test-port', }) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) def test_create_with_security_group(self): @@ -290,7 +302,7 @@ class TestCreatePort(TestPort): 'name': 'test-port', }) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) def test_create_port_with_dns_name(self): @@ -316,7 +328,7 @@ class TestCreatePort(TestPort): 'name': 'test-port', }) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) def test_create_with_security_groups(self): @@ -346,7 +358,7 @@ class TestCreatePort(TestPort): 'name': 'test-port', }) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) def test_create_with_no_security_groups(self): @@ -372,7 +384,7 @@ class TestCreatePort(TestPort): 'name': 'test-port', }) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) def test_create_with_no_fixed_ips(self): @@ -398,7 +410,7 @@ class TestCreatePort(TestPort): 'name': 'test-port', }) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) def test_create_port_with_allowed_address_pair_ipaddr(self): @@ -428,7 +440,7 @@ class TestCreatePort(TestPort): 'name': 'test-port', }) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) def test_create_port_with_allowed_address_pair(self): @@ -464,7 +476,7 @@ class TestCreatePort(TestPort): 'name': 'test-port', }) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) def test_create_port_with_qos(self): @@ -492,7 +504,7 @@ class TestCreatePort(TestPort): 'name': 'test-port', }) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) def test_create_port_security_enabled(self): @@ -601,7 +613,7 @@ class TestCreatePort(TestPort): else: self.assertFalse(self.network.set_tags.called) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) def test_create_with_tags(self): @@ -644,7 +656,7 @@ class TestCreatePort(TestPort): 'name': 'test-port', }) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) def test_create_with_uplink_status_propagation_enabled(self): @@ -724,7 +736,7 @@ class TestCreatePort(TestPort): create_args['numa_affinity_policy'] = numa_affinity_policy self.network.create_port.assert_called_once_with(**create_args) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) def test_create_with_numa_affinity_policy_required(self): @@ -763,20 +775,20 @@ class TestCreatePort(TestPort): 'device_profile': 'cyborg_device_profile_1', } self.network.create_port.assert_called_once_with(**create_args) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) class TestDeletePort(TestPort): # Ports to delete. - _ports = network_fakes.FakePort.create_ports(count=2) + _ports = network_fakes.create_ports(count=2) def setUp(self): super(TestDeletePort, self).setUp() self.network.delete_port = mock.Mock(return_value=None) - self.network.find_port = network_fakes.FakePort.get_ports( + self.network.find_port = network_fakes.get_ports( ports=self._ports) # Get the command object to test self.cmd = port.DeletePort(self.app, self.namespace) @@ -848,7 +860,7 @@ class TestDeletePort(TestPort): class TestListPort(TestPort): - _ports = network_fakes.FakePort.create_ports(count=3) + _ports = network_fakes.create_ports(count=3) columns = ( 'ID', @@ -901,7 +913,7 @@ class TestListPort(TestPort): fake_router = network_fakes.FakeRouter.create_one_router({ 'id': 'fake-router-id', }) - fake_network = network_fakes.FakeNetwork.create_one_network({ + fake_network = network_fakes.create_one_network({ 'id': 'fake-network-id', }) self.network.find_router = mock.Mock(return_value=fake_router) @@ -1317,7 +1329,7 @@ class TestListPort(TestPort): class TestSetPort(TestPort): - _port = network_fakes.FakePort.create_one_port({'tags': ['green', 'red']}) + _port = network_fakes.create_one_port({'tags': ['green', 'red']}) def setUp(self): super(TestSetPort, self).setUp() @@ -1345,7 +1357,7 @@ class TestSetPort(TestPort): self.assertIsNone(result) def test_set_port_fixed_ip(self): - _testport = network_fakes.FakePort.create_one_port( + _testport = network_fakes.create_one_port( {'fixed_ips': [{'ip_address': '0.0.0.1'}]}) self.network.find_port = mock.Mock(return_value=_testport) arglist = [ @@ -1369,7 +1381,7 @@ class TestSetPort(TestPort): self.assertIsNone(result) def test_set_port_fixed_ip_clear(self): - _testport = network_fakes.FakePort.create_one_port( + _testport = network_fakes.create_one_port( {'fixed_ips': [{'ip_address': '0.0.0.1'}]}) self.network.find_port = mock.Mock(return_value=_testport) arglist = [ @@ -1412,7 +1424,7 @@ class TestSetPort(TestPort): self.assertIsNone(result) def test_set_port_overwrite_binding_profile(self): - _testport = network_fakes.FakePort.create_one_port( + _testport = network_fakes.create_one_port( {'binding_profile': {'lok_i': 'visi_on'}}) self.network.find_port = mock.Mock(return_value=_testport) arglist = [ @@ -1434,7 +1446,7 @@ class TestSetPort(TestPort): self.assertIsNone(result) def test_overwrite_mac_address(self): - _testport = network_fakes.FakePort.create_one_port( + _testport = network_fakes.create_one_port( {'mac_address': '11:22:33:44:55:66'}) self.network.find_port = mock.Mock(return_value=_testport) arglist = [ @@ -1578,7 +1590,7 @@ class TestSetPort(TestPort): sg_2 = network_fakes.FakeSecurityGroup.create_one_security_group() sg_3 = network_fakes.FakeSecurityGroup.create_one_security_group() self.network.find_security_group = mock.Mock(side_effect=[sg_2, sg_3]) - _testport = network_fakes.FakePort.create_one_port( + _testport = network_fakes.create_one_port( {'security_group_ids': [sg_1.id]}) self.network.find_port = mock.Mock(return_value=_testport) arglist = [ @@ -1620,7 +1632,7 @@ class TestSetPort(TestPort): def test_set_port_security_group_replace(self): sg1 = network_fakes.FakeSecurityGroup.create_one_security_group() sg2 = network_fakes.FakeSecurityGroup.create_one_security_group() - _testport = network_fakes.FakePort.create_one_port( + _testport = network_fakes.create_one_port( {'security_group_ids': [sg1.id]}) self.network.find_port = mock.Mock(return_value=_testport) self.network.find_security_group = mock.Mock(return_value=sg2) @@ -1662,7 +1674,7 @@ class TestSetPort(TestPort): self.assertIsNone(result) def test_set_port_append_allowed_address_pair(self): - _testport = network_fakes.FakePort.create_one_port( + _testport = network_fakes.create_one_port( {'allowed_address_pairs': [{'ip_address': '192.168.1.123'}]}) self.network.find_port = mock.Mock(return_value=_testport) arglist = [ @@ -1685,7 +1697,7 @@ class TestSetPort(TestPort): self.assertIsNone(result) def test_set_port_overwrite_allowed_address_pair(self): - _testport = network_fakes.FakePort.create_one_port( + _testport = network_fakes.create_one_port( {'allowed_address_pairs': [{'ip_address': '192.168.1.123'}]}) self.network.find_port = mock.Mock(return_value=_testport) arglist = [ @@ -1787,7 +1799,7 @@ class TestSetPort(TestPort): def test_set_port_with_qos(self): qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() self.network.find_qos_policy = mock.Mock(return_value=qos_policy) - _testport = network_fakes.FakePort.create_one_port( + _testport = network_fakes.create_one_port( {'qos_policy_id': None}) self.network.find_port = mock.Mock(return_value=_testport) arglist = [ @@ -1809,7 +1821,7 @@ class TestSetPort(TestPort): self.assertIsNone(result) def test_set_port_data_plane_status(self): - _testport = network_fakes.FakePort.create_one_port( + _testport = network_fakes.create_one_port( {'data_plane_status': None}) self.network.find_port = mock.Mock(return_value=_testport) arglist = [ @@ -1900,7 +1912,7 @@ class TestSetPort(TestPort): class TestShowPort(TestPort): # The port to show. - _port = network_fakes.FakePort.create_one_port() + _port = network_fakes.create_one_port() columns, data = TestPort._get_common_cols_data(_port) def setUp(self): @@ -1932,7 +1944,7 @@ class TestShowPort(TestPort): self.network.find_port.assert_called_once_with( self._port.name, ignore_missing=False) - self.assertEqual(self.columns, columns) + self.assertEqual(set(self.columns), set(columns)) self.assertCountEqual(self.data, data) @@ -1940,7 +1952,7 @@ class TestUnsetPort(TestPort): def setUp(self): super(TestUnsetPort, self).setUp() - self._testport = network_fakes.FakePort.create_one_port( + self._testport = network_fakes.create_one_port( {'fixed_ips': [{'subnet_id': '042eb10a-3a18-4658-ab-cf47c8d03152', 'ip_address': '0.0.0.1'}, {'subnet_id': '042eb10a-3a18-4658-ab-cf47c8d03152', @@ -2024,7 +2036,7 @@ class TestUnsetPort(TestPort): def test_unset_security_group(self): _fake_sg1 = network_fakes.FakeSecurityGroup.create_one_security_group() _fake_sg2 = network_fakes.FakeSecurityGroup.create_one_security_group() - _fake_port = network_fakes.FakePort.create_one_port( + _fake_port = network_fakes.create_one_port( {'security_group_ids': [_fake_sg1.id, _fake_sg2.id]}) self.network.find_port = mock.Mock(return_value=_fake_port) self.network.find_security_group = mock.Mock(return_value=_fake_sg2) @@ -2049,7 +2061,7 @@ class TestUnsetPort(TestPort): def test_unset_port_security_group_not_existent(self): _fake_sg1 = network_fakes.FakeSecurityGroup.create_one_security_group() _fake_sg2 = network_fakes.FakeSecurityGroup.create_one_security_group() - _fake_port = network_fakes.FakePort.create_one_port( + _fake_port = network_fakes.create_one_port( {'security_group_ids': [_fake_sg1.id]}) self.network.find_security_group = mock.Mock(return_value=_fake_sg2) arglist = [ @@ -2066,7 +2078,7 @@ class TestUnsetPort(TestPort): parsed_args) def test_unset_port_allowed_address_pair(self): - _fake_port = network_fakes.FakePort.create_one_port( + _fake_port = network_fakes.create_one_port( {'allowed_address_pairs': [{'ip_address': '192.168.1.123'}]}) self.network.find_port = mock.Mock(return_value=_fake_port) arglist = [ @@ -2088,7 +2100,7 @@ class TestUnsetPort(TestPort): self.assertIsNone(result) def test_unset_port_allowed_address_pair_not_existent(self): - _fake_port = network_fakes.FakePort.create_one_port( + _fake_port = network_fakes.create_one_port( {'allowed_address_pairs': [{'ip_address': '192.168.1.123'}]}) self.network.find_port = mock.Mock(return_value=_fake_port) arglist = [ @@ -2105,7 +2117,7 @@ class TestUnsetPort(TestPort): parsed_args) def test_unset_port_data_plane_status(self): - _fake_port = network_fakes.FakePort.create_one_port( + _fake_port = network_fakes.create_one_port( {'data_plane_status': 'ACTIVE'}) self.network.find_port = mock.Mock(return_value=_fake_port) arglist = [ @@ -2156,7 +2168,7 @@ class TestUnsetPort(TestPort): self._test_unset_tags(with_tags=False) def test_unset_numa_affinity_policy(self): - _fake_port = network_fakes.FakePort.create_one_port( + _fake_port = network_fakes.create_one_port( {'numa_affinity_policy': 'required'}) self.network.find_port = mock.Mock(return_value=_fake_port) arglist = [ diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 4d03507..fb9673c 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -36,7 +36,7 @@ class TestRouter(network_fakes.TestNetworkV2): class TestAddPortToRouter(TestRouter): '''Add port to Router ''' - _port = network_fakes.FakePort.create_one_port() + _port = network_fakes.create_one_port() _router = network_fakes.FakeRouter.create_one_router( attrs={'port': _port.id}) @@ -187,7 +187,7 @@ class TestCreateRouter(TestRouter): self.assertCountEqual(self.data, data) def test_create_with_gateway(self): - _network = network_fakes.FakeNetwork.create_one_network() + _network = network_fakes.create_one_network() _subnet = network_fakes.FakeSubnet.create_one_subnet() self.network.find_network = mock.Mock(return_value=_network) self.network.find_subnet = mock.Mock(return_value=_subnet) @@ -512,8 +512,7 @@ class TestListRouter(TestRouter): self.network.routers = mock.Mock(return_value=self.routers) self.network.find_extension = mock.Mock(return_value=self._extensions) self.network.find_router = mock.Mock(return_value=self.routers[0]) - self._testagent = \ - network_fakes.FakeNetworkAgent.create_one_network_agent() + self._testagent = network_fakes.create_one_network_agent() self.network.get_agent = mock.Mock(return_value=self._testagent) self.network.get_router = mock.Mock(return_value=self.routers[0]) @@ -739,7 +738,7 @@ class TestListRouter(TestRouter): class TestRemovePortFromRouter(TestRouter): '''Remove port from a Router ''' - _port = network_fakes.FakePort.create_one_port() + _port = network_fakes.create_one_port() _router = network_fakes.FakeRouter.create_one_router( attrs={'port': _port.id}) @@ -957,7 +956,7 @@ class TestSetRouter(TestRouter): # The router to set. _default_route = {'destination': '10.20.20.0/24', 'nexthop': '10.20.30.1'} - _network = network_fakes.FakeNetwork.create_one_network() + _network = network_fakes.create_one_network() _subnet = network_fakes.FakeSubnet.create_one_subnet() _router = network_fakes.FakeRouter.create_one_router( attrs={'routes': [_default_route], @@ -1364,7 +1363,7 @@ class TestShowRouter(TestRouter): # The router to set. _router = network_fakes.FakeRouter.create_one_router() - _port = network_fakes.FakePort.create_one_port({ + _port = network_fakes.create_one_port({ 'device_owner': 'network:router_interface', 'device_id': _router.id }) @@ -1485,7 +1484,7 @@ class TestUnsetRouter(TestRouter): def setUp(self): super(TestUnsetRouter, self).setUp() - self.fake_network = network_fakes.FakeNetwork.create_one_network() + self.fake_network = network_fakes.create_one_network() self.fake_qos_policy = ( network_fakes.FakeNetworkQosPolicy.create_one_qos_policy()) self._testrouter = network_fakes.FakeRouter.create_one_router( diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py index 4e765f3..3c90f28 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py @@ -47,7 +47,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): network_fakes.FakeSecurityGroup.create_one_security_group() # The address group to be used in security group rules - _address_group = network_fakes.FakeAddressGroup.create_one_address_group() + _address_group = network_fakes.create_one_address_group() expected_columns = ( 'description', diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index dcac2ef..6b3ab2c 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -95,7 +95,7 @@ class TestCreateSubnet(TestSubnet): ) # The network to be returned from find_network - self._network = network_fakes.FakeNetwork.create_one_network( + self._network = network_fakes.create_one_network( attrs={ 'id': self._subnet.network_id, } @@ -103,7 +103,7 @@ class TestCreateSubnet(TestSubnet): # The network segment to be returned from find_segment self._network_segment = \ - network_fakes.FakeNetworkSegment.create_one_network_segment( + network_fakes.create_one_network_segment( attrs={ 'network_id': self._subnet.network_id, } @@ -828,7 +828,7 @@ class TestListSubnet(TestSubnet): self.assertCountEqual(self.data, list(data)) def test_subnet_list_network(self): - network = network_fakes.FakeNetwork.create_one_network() + network = network_fakes.create_one_network() self.network.find_network = mock.Mock(return_value=network) arglist = [ '--network', network.id, @@ -1185,8 +1185,8 @@ class TestSetSubnet(TestSubnet): self._test_set_tags(with_tags=False) def test_set_segment(self): - _net = network_fakes.FakeNetwork.create_one_network() - _segment = network_fakes.FakeNetworkSegment.create_one_network_segment( + _net = network_fakes.create_one_network() + _segment = network_fakes.create_one_network_segment( attrs={'network_id': _net.id}) _subnet = network_fakes.FakeSubnet.create_one_subnet( {'host_routes': [{'destination': '10.20.20.0/24', diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index 55d2cc2..b24906b 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -44,7 +44,7 @@ class TestCreateSubnetPool(TestSubnetPool): # The new subnet pool to create. _subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() - _address_scope = network_fakes.FakeAddressScope.create_one_address_scope() + _address_scope = network_fakes.create_one_address_scope() columns = ( 'address_scope_id', @@ -614,7 +614,7 @@ class TestListSubnetPool(TestSubnetPool): self.assertCountEqual(self.data, list(data)) def test_subnet_pool_list_address_scope(self): - addr_scope = network_fakes.FakeAddressScope.create_one_address_scope() + addr_scope = network_fakes.create_one_address_scope() self.network.find_address_scope = mock.Mock(return_value=addr_scope) arglist = [ '--address-scope', addr_scope.id, @@ -665,7 +665,7 @@ class TestSetSubnetPool(TestSubnetPool): 'tags': ['green', 'red']} ) - _address_scope = network_fakes.FakeAddressScope.create_one_address_scope() + _address_scope = network_fakes.create_one_address_scope() def setUp(self): super(TestSetSubnetPool, self).setUp() diff --git a/openstackclient/tests/unit/volume/v1/test_volume_backup.py b/openstackclient/tests/unit/volume/v1/test_volume_backup.py index f25a5ff..2f56892 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v1/test_volume_backup.py @@ -319,29 +319,69 @@ class TestBackupRestore(TestBackup): attrs={'volume_id': volume.id}) def setUp(self): - super(TestBackupRestore, self).setUp() + super().setUp() self.backups_mock.get.return_value = self.backup self.volumes_mock.get.return_value = self.volume - self.restores_mock.restore.return_value = None + self.restores_mock.restore.return_value = ( + volume_fakes.FakeVolume.create_one_volume( + {'id': self.volume['id']}, + ) + ) # Get the command object to mock self.cmd = volume_backup.RestoreVolumeBackup(self.app, None) def test_backup_restore(self): arglist = [ self.backup.id, - self.backup.volume_id ] verifylist = [ ("backup", self.backup.id), - ("volume", self.backup.volume_id) + ("volume", None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.restores_mock.restore.assert_called_with(self.backup.id, - self.backup.volume_id) - self.assertIsNone(result) + None) + self.assertIsNotNone(result) + + def test_backup_restore_with_existing_volume(self): + arglist = [ + self.backup.id, + self.backup.volume_id, + ] + verifylist = [ + ("backup", self.backup.id), + ("volume", self.backup.volume_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.restores_mock.restore.assert_called_with( + self.backup.id, self.backup.volume_id, + ) + self.assertIsNotNone(result) + + def test_backup_restore_with_invalid_volume(self): + arglist = [ + self.backup.id, + "unexist_volume", + ] + verifylist = [ + ("backup", self.backup.id), + ("volume", "unexist_volume"), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + with mock.patch.object( + utils, 'find_resource', + side_effect=exceptions.CommandError(), + ): + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) class TestBackupShow(TestBackup): diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backup.py b/openstackclient/tests/unit/volume/v2/test_volume_backup.py index 4b9212d..ffd8490 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_backup.py @@ -458,35 +458,95 @@ class TestBackupRestore(TestBackup): volume = volume_fakes.FakeVolume.create_one_volume() backup = volume_fakes.FakeBackup.create_one_backup( - attrs={'volume_id': volume.id}) + attrs={'volume_id': volume.id}, + ) def setUp(self): - super(TestBackupRestore, self).setUp() + super().setUp() self.backups_mock.get.return_value = self.backup self.volumes_mock.get.return_value = self.volume self.restores_mock.restore.return_value = ( volume_fakes.FakeVolume.create_one_volume( - {'id': self.volume['id']})) + {'id': self.volume['id']}, + ) + ) # Get the command object to mock self.cmd = volume_backup.RestoreVolumeBackup(self.app, None) def test_backup_restore(self): + self.volumes_mock.get.side_effect = exceptions.CommandError() + self.volumes_mock.find.side_effect = exceptions.CommandError() + arglist = [ + self.backup.id + ] + verifylist = [ + ("backup", self.backup.id), + ("volume", None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.restores_mock.restore.assert_called_with( + self.backup.id, None, None, + ) + self.assertIsNotNone(result) + + def test_backup_restore_with_volume(self): + self.volumes_mock.get.side_effect = exceptions.CommandError() + self.volumes_mock.find.side_effect = exceptions.CommandError() + arglist = [ + self.backup.id, + self.backup.volume_id, + ] + verifylist = [ + ("backup", self.backup.id), + ("volume", self.backup.volume_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.restores_mock.restore.assert_called_with( + self.backup.id, None, self.backup.volume_id, + ) + self.assertIsNotNone(result) + + def test_backup_restore_with_volume_force(self): arglist = [ + "--force", self.backup.id, - self.backup.volume_id + self.volume.name, ] verifylist = [ + ("force", True), ("backup", self.backup.id), - ("volume", self.backup.volume_id) + ("volume", self.volume.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.restores_mock.restore.assert_called_with(self.backup.id, - self.backup.volume_id) + self.restores_mock.restore.assert_called_with( + self.backup.id, self.volume.id, None, + ) self.assertIsNotNone(result) + def test_backup_restore_with_volume_existing(self): + arglist = [ + self.backup.id, + self.volume.name, + ] + verifylist = [ + ("backup", self.backup.id), + ("volume", self.volume.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + class TestBackupSet(TestBackup): diff --git a/openstackclient/tests/unit/volume/v3/fakes.py b/openstackclient/tests/unit/volume/v3/fakes.py index 9040b2b..e27d7fc 100644 --- a/openstackclient/tests/unit/volume/v3/fakes.py +++ b/openstackclient/tests/unit/volume/v3/fakes.py @@ -32,6 +32,8 @@ class FakeVolumeClient(object): self.attachments = mock.Mock() self.attachments.resource_class = fakes.FakeResource(None, {}) + self.clusters = mock.Mock() + self.clusters.resource_class = fakes.FakeResource(None, {}) self.groups = mock.Mock() self.groups.resource_class = fakes.FakeResource(None, {}) self.group_snapshots = mock.Mock() @@ -40,6 +42,8 @@ class FakeVolumeClient(object): self.group_types.resource_class = fakes.FakeResource(None, {}) self.messages = mock.Mock() self.messages.resource_class = fakes.FakeResource(None, {}) + self.resource_filters = mock.Mock() + self.resource_filters.resource_class = fakes.FakeResource(None, {}) self.volumes = mock.Mock() self.volumes.resource_class = fakes.FakeResource(None, {}) self.volume_types = mock.Mock() @@ -70,6 +74,105 @@ FakeVolume = volume_v2_fakes.FakeVolume FakeVolumeType = volume_v2_fakes.FakeVolumeType +class FakeCluster: + """Fake one or more clusters.""" + + @staticmethod + def create_one_cluster(attrs=None): + """Create a fake service cluster. + + :param attrs: A dictionary with all attributes of service cluster + :return: A FakeResource object with id, name, status, etc. + """ + attrs = attrs or {} + + # Set default attribute + cluster_info = { + 'name': f'cluster-{uuid.uuid4().hex}', + 'binary': f'binary-{uuid.uuid4().hex}', + 'state': random.choice(['up', 'down']), + 'status': random.choice(['enabled', 'disabled']), + 'disabled_reason': None, + 'num_hosts': random.randint(1, 64), + 'num_down_hosts': random.randint(1, 64), + 'last_heartbeat': '2015-09-16T09:28:52.000000', + 'created_at': '2015-09-16T09:28:52.000000', + 'updated_at': '2015-09-16T09:28:52.000000', + 'replication_status': None, + 'frozen': False, + 'active_backend_id': None, + } + + # Overwrite default attributes if there are some attributes set + cluster_info.update(attrs) + + return fakes.FakeResource( + None, + cluster_info, + loaded=True) + + @staticmethod + def create_clusters(attrs=None, count=2): + """Create multiple fake service clusters. + + :param attrs: A dictionary with all attributes of service cluster + :param count: The number of service clusters to be faked + :return: A list of FakeResource objects + """ + clusters = [] + for n in range(0, count): + clusters.append(FakeCluster.create_one_cluster(attrs)) + + return clusters + + +class FakeResourceFilter: + """Fake one or more resource filters.""" + + @staticmethod + def create_one_resource_filter(attrs=None): + """Create a fake resource filter. + + :param attrs: A dictionary with all attributes of resource filter + :return: A FakeResource object with id, name, status, etc. + """ + attrs = attrs or {} + + # Set default attribute + + resource_filter_info = { + 'filters': [ + 'name', + 'status', + 'image_metadata', + 'bootable', + 'migration_status', + ], + 'resource': 'volume', + } + + # Overwrite default attributes if there are some attributes set + resource_filter_info.update(attrs) + + return fakes.FakeResource(None, resource_filter_info, loaded=True) + + @staticmethod + def create_resource_filters(attrs=None, count=2): + """Create multiple fake resource filters. + + :param attrs: A dictionary with all attributes of resource filter + :param count: The number of resource filters to be faked + :return: A list of FakeResource objects + """ + resource_filters = [] + for n in range(0, count): + resource_filters.append( + FakeResourceFilter.create_one_resource_filter(attrs) + ) + + return resource_filters + + class FakeVolumeGroup: """Fake one or more volume groups.""" @@ -255,11 +358,10 @@ class FakeVolumeMessage: # Overwrite default attributes if there are some attributes set message_info.update(attrs) - message = fakes.FakeResource( + return fakes.FakeResource( None, message_info, loaded=True) - return message @staticmethod def create_volume_messages(attrs=None, count=2): @@ -348,11 +450,10 @@ class FakeVolumeAttachment: # Overwrite default attributes if there are some attributes set attachment_info.update(attrs) - attachment = fakes.FakeResource( + return fakes.FakeResource( None, attachment_info, loaded=True) - return attachment @staticmethod def create_volume_attachments(attrs=None, count=2): diff --git a/openstackclient/tests/unit/volume/v3/test_block_storage_cluster.py b/openstackclient/tests/unit/volume/v3/test_block_storage_cluster.py new file mode 100644 index 0000000..d87a946 --- /dev/null +++ b/openstackclient/tests/unit/volume/v3/test_block_storage_cluster.py @@ -0,0 +1,434 @@ +# 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 cinderclient import api_versions +from osc_lib import exceptions + +from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes +from openstackclient.volume.v3 import block_storage_cluster + + +class TestBlockStorageCluster(volume_fakes.TestVolume): + + def setUp(self): + super().setUp() + + # Get a shortcut to the BlockStorageClusterManager Mock + self.cluster_mock = self.app.client_manager.volume.clusters + self.cluster_mock.reset_mock() + + +class TestBlockStorageClusterList(TestBlockStorageCluster): + + # The cluster to be listed + fake_clusters = volume_fakes.FakeCluster.create_clusters() + + def setUp(self): + super().setUp() + + self.cluster_mock.list.return_value = self.fake_clusters + + # Get the command object to test + self.cmd = \ + block_storage_cluster.ListBlockStorageCluster(self.app, None) + + def test_cluster_list(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.7') + + arglist = [ + ] + verifylist = [ + ('cluster', None), + ('binary', None), + ('is_up', None), + ('is_disabled', None), + ('num_hosts', None), + ('num_down_hosts', None), + ('long', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + expected_columns = ('Name', 'Binary', 'State', 'Status') + expected_data = tuple( + ( + cluster.name, + cluster.binary, + cluster.state, + cluster.status, + ) for cluster in self.fake_clusters + ) + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, tuple(data)) + + # checking if proper call was made to list clusters + self.cluster_mock.list.assert_called_with( + name=None, + binary=None, + is_up=None, + disabled=None, + num_hosts=None, + num_down_hosts=None, + detailed=False, + ) + + def test_cluster_list_with_full_options(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.7') + + arglist = [ + '--cluster', 'foo', + '--binary', 'bar', + '--up', + '--disabled', + '--num-hosts', '5', + '--num-down-hosts', '0', + '--long', + ] + verifylist = [ + ('cluster', 'foo'), + ('binary', 'bar'), + ('is_up', True), + ('is_disabled', True), + ('num_hosts', 5), + ('num_down_hosts', 0), + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + expected_columns = ( + 'Name', + 'Binary', + 'State', + 'Status', + 'Num Hosts', + 'Num Down Hosts', + 'Last Heartbeat', + 'Disabled Reason', + 'Created At', + 'Updated At', + ) + expected_data = tuple( + ( + cluster.name, + cluster.binary, + cluster.state, + cluster.status, + cluster.num_hosts, + cluster.num_down_hosts, + cluster.last_heartbeat, + cluster.disabled_reason, + cluster.created_at, + cluster.updated_at, + ) for cluster in self.fake_clusters + ) + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, tuple(data)) + + # checking if proper call was made to list clusters + self.cluster_mock.list.assert_called_with( + name='foo', + binary='bar', + is_up=True, + disabled=True, + num_hosts=5, + num_down_hosts=0, + detailed=True, + ) + + def test_cluster_list_pre_v37(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.6') + + arglist = [ + ] + verifylist = [ + ('cluster', None), + ('binary', None), + ('is_up', None), + ('is_disabled', None), + ('num_hosts', None), + ('num_down_hosts', None), + ('long', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.7 or greater is required', str(exc)) + + +class TestBlockStorageClusterSet(TestBlockStorageCluster): + + cluster = volume_fakes.FakeCluster.create_one_cluster() + columns = ( + 'Name', + 'Binary', + 'State', + 'Status', + 'Disabled Reason', + 'Hosts', + 'Down Hosts', + 'Last Heartbeat', + 'Created At', + 'Updated At', + 'Replication Status', + 'Frozen', + 'Active Backend ID', + ) + data = ( + cluster.name, + cluster.binary, + cluster.state, + cluster.status, + cluster.disabled_reason, + cluster.num_hosts, + cluster.num_down_hosts, + cluster.last_heartbeat, + cluster.created_at, + cluster.updated_at, + cluster.replication_status, + cluster.frozen, + cluster.active_backend_id, + ) + + def setUp(self): + super().setUp() + + self.cluster_mock.update.return_value = self.cluster + + self.cmd = \ + block_storage_cluster.SetBlockStorageCluster(self.app, None) + + def test_cluster_set(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.7') + + arglist = [ + '--enable', + self.cluster.name, + ] + verifylist = [ + ('cluster', self.cluster.name), + ('binary', 'cinder-volume'), + ('disabled', False), + ('disabled_reason', None), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, tuple(data)) + + self.cluster_mock.update.assert_called_once_with( + self.cluster.name, + 'cinder-volume', + disabled=False, + disabled_reason=None, + ) + + def test_cluster_set_disable_with_reason(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.7') + + arglist = [ + '--binary', self.cluster.binary, + '--disable', + '--disable-reason', 'foo', + self.cluster.name, + ] + verifylist = [ + ('cluster', self.cluster.name), + ('binary', self.cluster.binary), + ('disabled', True), + ('disabled_reason', 'foo'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, tuple(data)) + + self.cluster_mock.update.assert_called_once_with( + self.cluster.name, + self.cluster.binary, + disabled=True, + disabled_reason='foo', + ) + + def test_cluster_set_only_with_disable_reason(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.7') + + arglist = [ + '--disable-reason', 'foo', + self.cluster.name, + ] + verifylist = [ + ('cluster', self.cluster.name), + ('binary', 'cinder-volume'), + ('disabled', None), + ('disabled_reason', 'foo'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + "Cannot specify --disable-reason without --disable", str(exc)) + + def test_cluster_set_enable_with_disable_reason(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.7') + + arglist = [ + '--enable', + '--disable-reason', 'foo', + self.cluster.name, + ] + verifylist = [ + ('cluster', self.cluster.name), + ('binary', 'cinder-volume'), + ('disabled', False), + ('disabled_reason', 'foo'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + "Cannot specify --disable-reason without --disable", str(exc)) + + def test_cluster_set_pre_v37(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.6') + + arglist = [ + '--enable', + self.cluster.name, + ] + verifylist = [ + ('cluster', self.cluster.name), + ('binary', 'cinder-volume'), + ('disabled', False), + ('disabled_reason', None), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.7 or greater is required', str(exc)) + + +class TestBlockStorageClusterShow(TestBlockStorageCluster): + + cluster = volume_fakes.FakeCluster.create_one_cluster() + columns = ( + 'Name', + 'Binary', + 'State', + 'Status', + 'Disabled Reason', + 'Hosts', + 'Down Hosts', + 'Last Heartbeat', + 'Created At', + 'Updated At', + 'Replication Status', + 'Frozen', + 'Active Backend ID', + ) + data = ( + cluster.name, + cluster.binary, + cluster.state, + cluster.status, + cluster.disabled_reason, + cluster.num_hosts, + cluster.num_down_hosts, + cluster.last_heartbeat, + cluster.created_at, + cluster.updated_at, + cluster.replication_status, + cluster.frozen, + cluster.active_backend_id, + ) + + def setUp(self): + super().setUp() + + self.cluster_mock.show.return_value = self.cluster + + self.cmd = \ + block_storage_cluster.ShowBlockStorageCluster(self.app, None) + + def test_cluster_show(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.7') + + arglist = [ + '--binary', self.cluster.binary, + self.cluster.name, + ] + verifylist = [ + ('cluster', self.cluster.name), + ('binary', self.cluster.binary), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, tuple(data)) + + self.cluster_mock.show.assert_called_once_with( + self.cluster.name, + binary=self.cluster.binary, + ) + + def test_cluster_show_pre_v37(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.6') + + arglist = [ + '--binary', self.cluster.binary, + self.cluster.name, + ] + verifylist = [ + ('cluster', self.cluster.name), + ('binary', self.cluster.binary), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.7 or greater is required', str(exc)) diff --git a/openstackclient/tests/unit/volume/v3/test_block_storage_resource_filter.py b/openstackclient/tests/unit/volume/v3/test_block_storage_resource_filter.py new file mode 100644 index 0000000..b886726 --- /dev/null +++ b/openstackclient/tests/unit/volume/v3/test_block_storage_resource_filter.py @@ -0,0 +1,144 @@ +# 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 cinderclient import api_versions +from osc_lib import exceptions + +from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes +from openstackclient.volume.v3 import block_storage_resource_filter + + +class TestBlockStorageResourceFilter(volume_fakes.TestVolume): + + def setUp(self): + super().setUp() + + # Get a shortcut to the ResourceFilterManager Mock + self.resource_filter_mock = \ + self.app.client_manager.volume.resource_filters + self.resource_filter_mock.reset_mock() + + +class TestBlockStorageResourceFilterList(TestBlockStorageResourceFilter): + + # The resource filters to be listed + fake_resource_filters = \ + volume_fakes.FakeResourceFilter.create_resource_filters() + + def setUp(self): + super().setUp() + + self.resource_filter_mock.list.return_value = \ + self.fake_resource_filters + + # Get the command object to test + self.cmd = block_storage_resource_filter\ + .ListBlockStorageResourceFilter(self.app, None) + + def test_resource_filter_list(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.33') + + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + expected_columns = ('Resource', 'Filters') + expected_data = tuple( + ( + resource_filter.resource, + resource_filter.filters, + ) for resource_filter in self.fake_resource_filters + ) + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, tuple(data)) + + # checking if proper call was made to list clusters + self.resource_filter_mock.list.assert_called_with() + + def test_resource_filter_list_pre_v333(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.32') + + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.33 or greater is required', str(exc)) + + +class TestBlockStorageResourceFilterShow(TestBlockStorageResourceFilter): + + # The resource filters to be listed + fake_resource_filter = \ + volume_fakes.FakeResourceFilter.create_one_resource_filter() + + def setUp(self): + super().setUp() + + self.resource_filter_mock.list.return_value = \ + iter([self.fake_resource_filter]) + + # Get the command object to test + self.cmd = block_storage_resource_filter\ + .ShowBlockStorageResourceFilter(self.app, None) + + def test_resource_filter_show(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.33') + + arglist = [ + self.fake_resource_filter.resource, + ] + verifylist = [ + ('resource', self.fake_resource_filter.resource), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + expected_columns = ('filters', 'resource') + expected_data = ( + self.fake_resource_filter.filters, + self.fake_resource_filter.resource, + ) + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) + + # checking if proper call was made to list clusters + self.resource_filter_mock.list.assert_called_with(resource='volume') + + def test_resource_filter_show_pre_v333(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.32') + + arglist = [ + self.fake_resource_filter.resource, + ] + verifylist = [ + ('resource', self.fake_resource_filter.resource), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.33 or greater is required', str(exc)) diff --git a/openstackclient/tests/unit/volume/v3/test_volume_attachment.py b/openstackclient/tests/unit/volume/v3/test_volume_attachment.py index 09f698e..44fac6c 100644 --- a/openstackclient/tests/unit/volume/v3/test_volume_attachment.py +++ b/openstackclient/tests/unit/volume/v3/test_volume_attachment.py @@ -73,8 +73,9 @@ class TestVolumeAttachmentCreate(TestVolumeAttachment): self.volumes_mock.get.return_value = self.volume self.servers_mock.get.return_value = self.server + # VolumeAttachmentManager.create returns a dict self.volume_attachments_mock.create.return_value = \ - self.volume_attachment + self.volume_attachment.to_dict() self.cmd = volume_attachment.CreateVolumeAttachment(self.app, None) diff --git a/openstackclient/volume/v1/volume_backup.py b/openstackclient/volume/v1/volume_backup.py index 1a83a3c..790cb46 100644 --- a/openstackclient/volume/v1/volume_backup.py +++ b/openstackclient/volume/v1/volume_backup.py @@ -231,18 +231,23 @@ class RestoreVolumeBackup(command.Command): parser.add_argument( 'volume', metavar='<volume>', - help=_('Volume to restore to (name or ID)') + nargs='?', + help=_('Volume to restore to (name or ID) (default to None)') ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - backup = utils.find_resource(volume_client.backups, - parsed_args.backup) - destination_volume = utils.find_resource(volume_client.volumes, - parsed_args.volume) - return volume_client.restores.restore(backup.id, - destination_volume.id) + backup = utils.find_resource( + volume_client.backups, parsed_args.backup, + ) + volume_id = None + if parsed_args.volume is not None: + volume_id = utils.find_resource( + volume_client.volumes, + parsed_args.volume, + ).id + return volume_client.restores.restore(backup.id, volume_id) class ShowVolumeBackup(command.ShowOne): diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index 4f015d1..c584943 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -411,7 +411,7 @@ class UnsetVolumeType(command.Command): "--encryption-type", action="store_true", help=_("Remove the encryption type for this volume type " - "(admin oly)"), + "(admin only)"), ) return parser diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index cab0b2f..6d14b36 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -361,7 +361,6 @@ class ListVolume(command.Lister): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - compute_client = self.app.client_manager.compute identity_client = self.app.client_manager.identity if parsed_args.long: @@ -393,6 +392,7 @@ class ListVolume(command.Lister): # Cache the server list server_cache = {} try: + compute_client = self.app.client_manager.compute for s in compute_client.servers.list(): server_cache[s.id] = s except Exception: diff --git a/openstackclient/volume/v2/volume_backup.py b/openstackclient/volume/v2/volume_backup.py index 96b22a6..d96b28e 100644 --- a/openstackclient/volume/v2/volume_backup.py +++ b/openstackclient/volume/v2/volume_backup.py @@ -310,9 +310,17 @@ class ListVolumeBackup(command.Lister): filter_volume_id = None if parsed_args.volume: - filter_volume_id = utils.find_resource( - volume_client.volumes, parsed_args.volume, - ).id + try: + filter_volume_id = utils.find_resource( + volume_client.volumes, parsed_args.volume, + ).id + except exceptions.CommandError: + # Volume with that ID does not exist, but search for backups + # for that volume nevertheless + LOG.debug("No volume with ID %s existing, continuing to " + "search for backups for that volume ID", + parsed_args.volume) + filter_volume_id = parsed_args.volume marker_backup_id = None if parsed_args.marker: @@ -355,18 +363,50 @@ class RestoreVolumeBackup(command.ShowOne): parser.add_argument( "volume", metavar="<volume>", - help=_("Volume to restore to (name or ID)") + nargs="?", + help=_( + "Volume to restore to " + "(name or ID for existing volume, name only for new volume) " + "(default to None)" + ) + ) + parser.add_argument( + "--force", + action="store_true", + help=_( + "Restore the backup to an existing volume " + "(default to False)" + ) ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume + backup = utils.find_resource(volume_client.backups, parsed_args.backup) - destination_volume = utils.find_resource(volume_client.volumes, - parsed_args.volume) - backup = volume_client.restores.restore(backup.id, - destination_volume.id) - return zip(*sorted(backup._info.items())) + + volume_name = None + volume_id = None + try: + volume_id = utils.find_resource( + volume_client.volumes, + parsed_args.volume, + ).id + except Exception: + volume_name = parsed_args.volume + else: + # If we didn't fail, the volume must already exist. We only allow + # this to work if the user forced things + if not parsed_args.force: + msg = _( + "Volume '%s' already exists; if you want to restore the " + "backup to it you need to specify the '--force' option" + ) % parsed_args.volume + raise exceptions.CommandError(msg) + + return volume_client.restores.restore( + backup.id, volume_id, volume_name, + ) class SetVolumeBackup(command.Command): diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py index 656f59d..53d8d27 100644 --- a/openstackclient/volume/v2/volume_snapshot.py +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -98,7 +98,7 @@ class CreateVolumeSnapshot(command.ShowOne): "--remote-source", metavar="<key=value>", action=parseractions.KeyValueAction, - help=_("The attribute(s) of the exsiting remote volume snapshot " + help=_("The attribute(s) of the existing remote volume snapshot " "(admin required) (repeat option to specify multiple " "attributes) e.g.: '--remote-source source-name=test_name " "--remote-source source-id=test_id'"), diff --git a/openstackclient/volume/v3/block_storage_cluster.py b/openstackclient/volume/v3/block_storage_cluster.py new file mode 100644 index 0000000..34b25ef --- /dev/null +++ b/openstackclient/volume/v3/block_storage_cluster.py @@ -0,0 +1,281 @@ +# 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 cinderclient import api_versions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ + + +def _format_cluster(cluster, detailed=False): + columns = ( + 'name', + 'binary', + 'state', + 'status', + ) + column_headers = ( + 'Name', + 'Binary', + 'State', + 'Status', + ) + + if detailed: + columns += ( + 'disabled_reason', + 'num_hosts', + 'num_down_hosts', + 'last_heartbeat', + 'created_at', + 'updated_at', + # optional columns, depending on whether replication is enabled + 'replication_status', + 'frozen', + 'active_backend_id', + ) + column_headers += ( + 'Disabled Reason', + 'Hosts', + 'Down Hosts', + 'Last Heartbeat', + 'Created At', + 'Updated At', + # optional columns, depending on whether replication is enabled + 'Replication Status', + 'Frozen', + 'Active Backend ID', + ) + + return ( + column_headers, + utils.get_item_properties( + cluster, + columns, + ), + ) + + +class ListBlockStorageCluster(command.Lister): + """List block storage clusters. + + This command requires ``--os-volume-api-version`` 3.7 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + '--cluster', metavar='<name>', default=None, + help=_( + 'Filter by cluster name, without backend will list ' + 'all clustered services from the same cluster.' + ), + ) + parser.add_argument( + '--binary', + metavar='<binary>', + help=_('Cluster binary.'), + ) + parser.add_argument( + '--up', + action='store_true', + dest='is_up', + default=None, + help=_('Filter by up status.'), + ) + parser.add_argument( + '--down', + action='store_false', + dest='is_up', + help=_('Filter by down status.'), + ) + parser.add_argument( + '--disabled', + action='store_true', + dest='is_disabled', + default=None, + help=_('Filter by disabled status.'), + ) + parser.add_argument( + '--enabled', + action='store_false', + dest='is_disabled', + help=_('Filter by enabled status.'), + ) + parser.add_argument( + '--num-hosts', + metavar='<hosts>', + type=int, + default=None, + help=_('Filter by number of hosts in the cluster.'), + ) + parser.add_argument( + '--num-down-hosts', + metavar='<hosts>', + type=int, + default=None, + help=_('Filter by number of hosts that are down.'), + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help=_("List additional fields in output") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.7'): + msg = _( + "--os-volume-api-version 3.7 or greater is required to " + "support the 'block storage cluster list' command" + ) + raise exceptions.CommandError(msg) + + columns = ('Name', 'Binary', 'State', 'Status') + if parsed_args.long: + columns += ( + 'Num Hosts', + 'Num Down Hosts', + 'Last Heartbeat', + 'Disabled Reason', + 'Created At', + 'Updated At', + ) + + data = volume_client.clusters.list( + name=parsed_args.cluster, + binary=parsed_args.binary, + is_up=parsed_args.is_up, + disabled=parsed_args.is_disabled, + num_hosts=parsed_args.num_hosts, + num_down_hosts=parsed_args.num_down_hosts, + detailed=parsed_args.long, + ) + + return ( + columns, + (utils.get_item_properties(s, columns) for s in data), + ) + + +class SetBlockStorageCluster(command.Command): + """Set block storage cluster properties. + + This command requires ``--os-volume-api-version`` 3.7 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'cluster', + metavar='<cluster>', + help=_('Name of block storage cluster to update (name only)') + ) + parser.add_argument( + '--binary', + metavar='<binary>', + default='cinder-volume', + help=_( + "Name of binary to filter by; defaults to 'cinder-volume' " + "(optional)" + ) + ) + enabled_group = parser.add_mutually_exclusive_group() + enabled_group.add_argument( + '--enable', + action='store_false', + dest='disabled', + default=None, + help=_('Enable cluster') + ) + enabled_group.add_argument( + '--disable', + action='store_true', + dest='disabled', + help=_('Disable cluster') + ) + parser.add_argument( + '--disable-reason', + metavar='<reason>', + dest='disabled_reason', + help=_( + 'Reason for disabling the cluster ' + '(should be used with --disable option)' + ) + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.7'): + msg = _( + "--os-volume-api-version 3.7 or greater is required to " + "support the 'block storage cluster set' command" + ) + raise exceptions.CommandError(msg) + + if parsed_args.disabled_reason and not parsed_args.disabled: + msg = _("Cannot specify --disable-reason without --disable") + raise exceptions.CommandError(msg) + + cluster = volume_client.clusters.update( + parsed_args.cluster, + parsed_args.binary, + disabled=parsed_args.disabled, + disabled_reason=parsed_args.disabled_reason, + ) + + return _format_cluster(cluster, detailed=True) + + +class ShowBlockStorageCluster(command.ShowOne): + """Show detailed information for a block storage cluster. + + This command requires ``--os-volume-api-version`` 3.7 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'cluster', + metavar='<cluster>', + help=_('Name of block storage cluster.'), + ) + parser.add_argument( + '--binary', + metavar='<binary>', + help=_('Service binary.'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.7'): + msg = _( + "--os-volume-api-version 3.7 or greater is required to " + "support the 'block storage cluster show' command" + ) + raise exceptions.CommandError(msg) + + cluster = volume_client.clusters.show( + parsed_args.cluster, + binary=parsed_args.binary, + ) + + return _format_cluster(cluster, detailed=True) diff --git a/openstackclient/volume/v3/block_storage_resource_filter.py b/openstackclient/volume/v3/block_storage_resource_filter.py new file mode 100644 index 0000000..4bcacf9 --- /dev/null +++ b/openstackclient/volume/v3/block_storage_resource_filter.py @@ -0,0 +1,83 @@ +# 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. + +"""Volume V3 Resource Filters implementations""" + +from cinderclient import api_versions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ + + +class ListBlockStorageResourceFilter(command.Lister): + _description = _('List block storage resource filters') + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.33'): + msg = _( + "--os-volume-api-version 3.33 or greater is required to " + "support the 'block storage resource filter list' command" + ) + raise exceptions.CommandError(msg) + + column_headers = ( + 'Resource', + 'Filters', + ) + + data = volume_client.resource_filters.list() + + return ( + column_headers, + (utils.get_item_properties(s, column_headers) for s in data) + ) + + +class ShowBlockStorageResourceFilter(command.ShowOne): + _description = _('Show filters for a block storage resource type') + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'resource', + metavar='<resource>', + help=_('Resource to show filters for (name).') + ) + + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.33'): + msg = _( + "--os-volume-api-version 3.33 or greater is required to " + "support the 'block storage resource filter show' command" + ) + raise exceptions.CommandError(msg) + + data = volume_client.resource_filters.list( + resource=parsed_args.resource + ) + if not data: + msg = _( + "No resource filter with a name of {parsed_args.resource}' " + "exists." + ) + raise exceptions.CommandError(msg) + resource_filter = next(data) + + return zip(*sorted(resource_filter._info.items())) diff --git a/openstackclient/volume/v3/volume_attachment.py b/openstackclient/volume/v3/volume_attachment.py index 39a9c37..c740183 100644 --- a/openstackclient/volume/v3/volume_attachment.py +++ b/openstackclient/volume/v3/volume_attachment.py @@ -51,18 +51,27 @@ def _format_attachment(attachment): 'Properties', ) - # TODO(stephenfin): Improve output with the nested connection_info - # field - cinderclient printed two things but that's equally ugly - return ( - column_headers, - utils.get_item_properties( + # VolumeAttachmentManager.create returns a dict while everything else + # returns a VolumeAttachment object + if isinstance(attachment, dict): + data = [] + for column in columns: + if column == 'connection_info': + data.append(format_columns.DictColumn(attachment[column])) + continue + data.append(attachment[column]) + else: + data = utils.get_item_properties( attachment, columns, formatters={ 'connection_info': format_columns.DictColumn, }, - ), - ) + ) + + # TODO(stephenfin): Improve output with the nested connection_info + # field - cinderclient printed two things but that's equally ugly + return (column_headers, data) class CreateVolumeAttachment(command.ShowOne): diff --git a/python_openstackclient.egg-info/PKG-INFO b/python_openstackclient.egg-info/PKG-INFO index 7868b65..0a41b07 100644 --- a/python_openstackclient.egg-info/PKG-INFO +++ b/python_openstackclient.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: python-openstackclient -Version: 5.8.0 +Version: 6.0.0 Summary: OpenStack Command-line Client Home-page: https://docs.openstack.org/python-openstackclient/latest/ Author: OpenStack @@ -154,7 +154,6 @@ Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 -Requires-Python: >=3.6 +Classifier: Programming Language :: Python :: 3.9 +Requires-Python: >=3.8 diff --git a/python_openstackclient.egg-info/SOURCES.txt b/python_openstackclient.egg-info/SOURCES.txt index 43ad681..b4f120a 100644 --- a/python_openstackclient.egg-info/SOURCES.txt +++ b/python_openstackclient.egg-info/SOURCES.txt @@ -11,7 +11,6 @@ HACKING.rst LICENSE README.rst bindep.txt -lower-constraints.txt requirements.txt setup.cfg setup.py @@ -44,6 +43,8 @@ doc/source/cli/command-objects/address-scope.rst doc/source/cli/command-objects/aggregate.rst doc/source/cli/command-objects/application-credentials.rst doc/source/cli/command-objects/availability-zone.rst +doc/source/cli/command-objects/block-storage-cluster.rst +doc/source/cli/command-objects/block-storage-resource-filter.rst doc/source/cli/command-objects/catalog.rst doc/source/cli/command-objects/command.rst doc/source/cli/command-objects/complete.rst @@ -232,6 +233,7 @@ openstackclient/compute/v2/server_backup.py openstackclient/compute/v2/server_event.py openstackclient/compute/v2/server_group.py openstackclient/compute/v2/server_image.py +openstackclient/compute/v2/server_migration.py openstackclient/compute/v2/server_volume.py openstackclient/compute/v2/service.py openstackclient/compute/v2/usage.py @@ -479,6 +481,7 @@ openstackclient/tests/unit/compute/v2/test_server_backup.py openstackclient/tests/unit/compute/v2/test_server_event.py openstackclient/tests/unit/compute/v2/test_server_group.py openstackclient/tests/unit/compute/v2/test_server_image.py +openstackclient/tests/unit/compute/v2/test_server_migration.py openstackclient/tests/unit/compute/v2/test_server_volume.py openstackclient/tests/unit/compute/v2/test_service.py openstackclient/tests/unit/compute/v2/test_usage.py @@ -605,6 +608,8 @@ openstackclient/tests/unit/volume/v2/test_volume_snapshot.py openstackclient/tests/unit/volume/v2/test_volume_transfer_request.py openstackclient/tests/unit/volume/v3/__init__.py openstackclient/tests/unit/volume/v3/fakes.py +openstackclient/tests/unit/volume/v3/test_block_storage_cluster.py +openstackclient/tests/unit/volume/v3/test_block_storage_resource_filter.py openstackclient/tests/unit/volume/v3/test_volume_attachment.py openstackclient/tests/unit/volume/v3/test_volume_group.py openstackclient/tests/unit/volume/v3/test_volume_group_snapshot.py @@ -634,6 +639,8 @@ openstackclient/volume/v2/volume_snapshot.py openstackclient/volume/v2/volume_transfer_request.py openstackclient/volume/v2/volume_type.py openstackclient/volume/v3/__init__.py +openstackclient/volume/v3/block_storage_cluster.py +openstackclient/volume/v3/block_storage_resource_filter.py openstackclient/volume/v3/volume_attachment.py openstackclient/volume/v3/volume_group.py openstackclient/volume/v3/volume_group_snapshot.py @@ -650,6 +657,7 @@ python_openstackclient.egg-info/top_level.txt releasenotes/notes/.placeholder releasenotes/notes/L3-conntrack-helper-bd0d9da041747e84.yaml releasenotes/notes/add-auto-and-none-as-nic-parameter-ed23a6e7f99f250d.yaml +releasenotes/notes/add-block-storage-cluster-commands-fae8f686582bbbcf.yaml releasenotes/notes/add-community-option-to-image-list-ac0651eb2e5d632f.yaml releasenotes/notes/add-description-field-in-port-forwarding-c536e077b243d517.yaml releasenotes/notes/add-description-to-role-afe7b6ff668df261.yaml @@ -681,6 +689,7 @@ releasenotes/notes/add-missing-server-list-opts-c41e97e86ff1e1ca.yaml releasenotes/notes/add-missing-server-rebuild-opts-5c75e838d8f0487d.yaml releasenotes/notes/add-missing-server-set-opts-e1b4300f5f42e863.yaml releasenotes/notes/add-missing-volume-backup-opts-b9246aded87427ce.yaml +releasenotes/notes/add-more-server-list-columns-4e3b87929dd330f7.yaml releasenotes/notes/add-network-auto-allocated-topology-481580f48840bfc4.yaml releasenotes/notes/add-network-flavor-profile-e7cc5b353c3ed9d9.yaml releasenotes/notes/add-network-list-option-to-ports-9d101344ddeb3e64.yaml @@ -901,6 +910,7 @@ releasenotes/notes/bug-1596818-d4cd93dd4d38d3d6.yaml releasenotes/notes/bug-1596821-a07599eb4beb6342.yaml releasenotes/notes/bug-1597184-f4fb687b3d4d99d9.yaml releasenotes/notes/bug-1597188-a2ff62b809865365.yaml +releasenotes/notes/bug-1597189-02a8d8a402725860.yaml releasenotes/notes/bug-1597192-52801f7520287309.yaml releasenotes/notes/bug-1597195-54ff1ecf381899f6.yaml releasenotes/notes/bug-1597198-e36b55f3fd185a3a.yaml @@ -1064,6 +1074,7 @@ releasenotes/notes/drop-py2-421c90fbdf18dbc2.yaml releasenotes/notes/entrypoint-3.8-0597d159889042f7.yaml releasenotes/notes/fix-flavor-in-server-list-microversion-2.47-af200e9bb4747e2d.yaml releasenotes/notes/fix-flavor-props-formatting-d21e97745543caa7.yaml +releasenotes/notes/fix-image-create-from-volume-c573e553161605c4.yaml releasenotes/notes/fix-network-rbac-create-d1f4de77ad2dd421.yaml releasenotes/notes/fix-openstak-image-save-sdk-port-eb160e8ffc92e514.yaml releasenotes/notes/fix-story-2007890-0974f3e69f26801e.yaml @@ -1075,6 +1086,7 @@ releasenotes/notes/floating-ip-set-unset-port-28e33875937b69cf.yaml releasenotes/notes/floatingip_dns_integration-f26c7575694d098d.yaml releasenotes/notes/force-flag-openstackclient-c172de2717e5cfac.yaml releasenotes/notes/image-list-filter-multiple-properties-03c51d43131ee3b6.yaml +releasenotes/notes/image-list-multiple-tags-a394799c7807f031.yaml releasenotes/notes/image-set-to-update-image-membership-68221f226ca3b6e0.yaml releasenotes/notes/image_set_visibility-babf4ff2f687d465.yaml releasenotes/notes/implement-system-scope-4c3c47996f98deac.yaml @@ -1120,6 +1132,8 @@ releasenotes/notes/pass_ssh_args-cf26a2ce26ccddaf.yaml releasenotes/notes/port-device-profile-4a3bf800da21c778.yaml releasenotes/notes/port-list-security-group-4af5d2e789174ff9.yaml releasenotes/notes/properties-with-image-property-field.yaml-c51bf37c3106d6ff.yaml +releasenotes/notes/qos-min-pps-rule-bfe22cea1966c4a0.yaml +releasenotes/notes/quota-network-force-920913981b45ba1a.yaml releasenotes/notes/rbac-add-address-group-f9bb83238b5a7c1f.yaml releasenotes/notes/rbac-add-address-scope-7f6409ab70d36306.yaml releasenotes/notes/rbac-add-security-group-35370701a06ac906.yaml @@ -1144,7 +1158,9 @@ releasenotes/notes/server-add-tag-63f9cd01dbd82d1b.yaml releasenotes/notes/server-changes-3962541b6ebdbbd8.yaml releasenotes/notes/server-description-ae9618fc09544cac.yaml releasenotes/notes/server-group-create_rule_option-9f84e52f35e7c3ba.yaml +releasenotes/notes/server-list-host-status-1f542a5bc4292a62.yaml releasenotes/notes/server-list-selectable-fields.yaml-1d5fb4784fa6f232.yaml +releasenotes/notes/server-migration-by-uuid-59f8272f63abee5d.yaml releasenotes/notes/server-ops-all-projects-2ce2202cdf617184.yaml releasenotes/notes/server-rebuild-property-e8c6439b04e27c80.yaml releasenotes/notes/server-rebuild-with-keypair-name-83c1aa20db136d91.yaml @@ -1183,6 +1199,7 @@ releasenotes/notes/unset-subnet-pool-333052dd85b95653.yaml releasenotes/notes/usage-list-all-49ca7a62c50e71d3.yaml releasenotes/notes/use_sdk_for_image-f49d2df38e7d9f81.yaml releasenotes/notes/versions-show-12a2443624c83048.yaml +releasenotes/notes/volume-attachment-create-output-fix-56515b8fcdd260b9.yaml releasenotes/notes/volume-backend-c5faae0b31556a24.yaml releasenotes/notes/volume-backup-record-9f5987c45e294dc6.yaml releasenotes/notes/volume-migrate-command-52cf6edd62fe17a7.yaml @@ -1206,6 +1223,7 @@ releasenotes/source/ussuri.rst releasenotes/source/victoria.rst releasenotes/source/wallaby.rst releasenotes/source/xena.rst +releasenotes/source/yoga.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder tools/fast8.sh
\ No newline at end of file diff --git a/python_openstackclient.egg-info/entry_points.txt b/python_openstackclient.egg-info/entry_points.txt index 28d1638..7d9ca12 100644 --- a/python_openstackclient.egg-info/entry_points.txt +++ b/python_openstackclient.egg-info/entry_points.txt @@ -84,12 +84,12 @@ server_lock = openstackclient.compute.v2.server:LockServer server_migrate = openstackclient.compute.v2.server:MigrateServer server_migrate_confirm = openstackclient.compute.v2.server:MigrateConfirm server_migrate_revert = openstackclient.compute.v2.server:MigrateRevert -server_migration_abort = openstackclient.compute.v2.server:AbortMigration +server_migration_abort = openstackclient.compute.v2.server_migration:AbortMigration server_migration_confirm = openstackclient.compute.v2.server:ConfirmMigration -server_migration_force_complete = openstackclient.compute.v2.server:ForceCompleteMigration -server_migration_list = openstackclient.compute.v2.server:ListMigration +server_migration_force_complete = openstackclient.compute.v2.server_migration:ForceCompleteMigration +server_migration_list = openstackclient.compute.v2.server_migration:ListMigration server_migration_revert = openstackclient.compute.v2.server:RevertMigration -server_migration_show = openstackclient.compute.v2.server:ShowMigration +server_migration_show = openstackclient.compute.v2.server_migration:ShowMigration server_pause = openstackclient.compute.v2.server:PauseServer server_reboot = openstackclient.compute.v2.server:RebootServer server_rebuild = openstackclient.compute.v2.server:RebuildServer @@ -572,6 +572,11 @@ volume_type_unset = openstackclient.volume.v2.volume_type:UnsetVolumeType volume_unset = openstackclient.volume.v2.volume:UnsetVolume [openstack.volume.v3] +block_storage_cluster_list = openstackclient.volume.v3.block_storage_cluster:ListBlockStorageCluster +block_storage_cluster_set = openstackclient.volume.v3.block_storage_cluster:SetBlockStorageCluster +block_storage_cluster_show = openstackclient.volume.v3.block_storage_cluster:ShowBlockStorageCluster +block_storage_resource_filter_list = openstackclient.volume.v3.block_storage_resource_filter:ListBlockStorageResourceFilter +block_storage_resource_filter_show = openstackclient.volume.v3.block_storage_resource_filter:ShowBlockStorageResourceFilter consistency_group_add_volume = openstackclient.volume.v2.consistency_group:AddVolumeToConsistencyGroup consistency_group_create = openstackclient.volume.v2.consistency_group:CreateConsistencyGroup consistency_group_delete = openstackclient.volume.v2.consistency_group:DeleteConsistencyGroup diff --git a/python_openstackclient.egg-info/pbr.json b/python_openstackclient.egg-info/pbr.json index b97b200..22bd4c1 100644 --- a/python_openstackclient.egg-info/pbr.json +++ b/python_openstackclient.egg-info/pbr.json @@ -1 +1 @@ -{"git_version": "63452f36", "is_release": true}
\ No newline at end of file +{"git_version": "27b2496e", "is_release": true}
\ No newline at end of file diff --git a/releasenotes/notes/add-block-storage-cluster-commands-fae8f686582bbbcf.yaml b/releasenotes/notes/add-block-storage-cluster-commands-fae8f686582bbbcf.yaml new file mode 100644 index 0000000..be50aeb --- /dev/null +++ b/releasenotes/notes/add-block-storage-cluster-commands-fae8f686582bbbcf.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add ``block storage cluster create``, ``block storage cluster delete``, + ``block storage cluster list`` and ``block storage cluster show`` commands + to create, delete, list, and show block storage service clusters, + respectively. diff --git a/releasenotes/notes/add-more-server-list-columns-4e3b87929dd330f7.yaml b/releasenotes/notes/add-more-server-list-columns-4e3b87929dd330f7.yaml new file mode 100644 index 0000000..a4bf004 --- /dev/null +++ b/releasenotes/notes/add-more-server-list-columns-4e3b87929dd330f7.yaml @@ -0,0 +1,16 @@ +--- +feature: + - | + The ``server list`` command now allows users to select the following + additional columns using the ``-c COLUMN`` option: + + - Task State + - Power State + - Image ID + - Flavor ID + - Availability Zone + - Host + - Properties + + These correspond to columns displayed by default when using the ``--long`` + option.
\ No newline at end of file diff --git a/releasenotes/notes/bug-1597189-02a8d8a402725860.yaml b/releasenotes/notes/bug-1597189-02a8d8a402725860.yaml new file mode 100644 index 0000000..69d1eb7 --- /dev/null +++ b/releasenotes/notes/bug-1597189-02a8d8a402725860.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + The ``volume`` argument of the ``volume backup restore`` command is now + optional and can refer to the name of a new volume that should be created + rather than a name or ID of an existing volume (which would be + overwritten). If not provided, cinder will generate a new volume with a + unique name. To restore a backup to an existing volume, you must now + specify the ``--force`` option (volume v2, v3 only). + [Bug `1597189 <https://bugs.launchpad.net/bugs/1597189>`_] diff --git a/releasenotes/notes/fix-image-create-from-volume-c573e553161605c4.yaml b/releasenotes/notes/fix-image-create-from-volume-c573e553161605c4.yaml new file mode 100644 index 0000000..92fc741 --- /dev/null +++ b/releasenotes/notes/fix-image-create-from-volume-c573e553161605c4.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixed create image from volume command. If user wants to + pass ``visibility`` and ``protected`` fields, they need to + specify volume microversion 3.1 or greater by passing + ``os-volume-api-version 3.1`` with the command. diff --git a/releasenotes/notes/image-list-multiple-tags-a394799c7807f031.yaml b/releasenotes/notes/image-list-multiple-tags-a394799c7807f031.yaml new file mode 100644 index 0000000..82c966b --- /dev/null +++ b/releasenotes/notes/image-list-multiple-tags-a394799c7807f031.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + The ``image list`` now accepts multiple ``--tag`` options, allowing you to + filter images on more than one tag. diff --git a/releasenotes/notes/qos-min-pps-rule-bfe22cea1966c4a0.yaml b/releasenotes/notes/qos-min-pps-rule-bfe22cea1966c4a0.yaml new file mode 100644 index 0000000..71e9910 --- /dev/null +++ b/releasenotes/notes/qos-min-pps-rule-bfe22cea1966c4a0.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add support for QoS minimum packet rate rule to following commands: + ``network qos rule create``, ``network qos rule delete``, + ``network qos rule list``, ``network qos rule show`` and + ``network qos rule set`` diff --git a/releasenotes/notes/quota-network-force-920913981b45ba1a.yaml b/releasenotes/notes/quota-network-force-920913981b45ba1a.yaml new file mode 100644 index 0000000..06507df --- /dev/null +++ b/releasenotes/notes/quota-network-force-920913981b45ba1a.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``--force`` options to the ``openstack quota set`` command for network + service commands. Neutron quota engine now accepts "force" flag to set + a new resource quota limit, regardless of the current resource usage. diff --git a/releasenotes/notes/server-list-host-status-1f542a5bc4292a62.yaml b/releasenotes/notes/server-list-host-status-1f542a5bc4292a62.yaml new file mode 100644 index 0000000..5ae92bc --- /dev/null +++ b/releasenotes/notes/server-list-host-status-1f542a5bc4292a62.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + The ``server list`` command will now display ``Host Status`` when the + ``--long`` option is specified and the compute API microversion is 2.16 or + greater. diff --git a/releasenotes/notes/server-migration-by-uuid-59f8272f63abee5d.yaml b/releasenotes/notes/server-migration-by-uuid-59f8272f63abee5d.yaml new file mode 100644 index 0000000..5421a5f --- /dev/null +++ b/releasenotes/notes/server-migration-by-uuid-59f8272f63abee5d.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + The ``server migration abort``, ``server migration force complete`` and + ``server migration show`` commands will now accept a server migration UUID + in addition to an ID. diff --git a/releasenotes/notes/volume-attachment-create-output-fix-56515b8fcdd260b9.yaml b/releasenotes/notes/volume-attachment-create-output-fix-56515b8fcdd260b9.yaml new file mode 100644 index 0000000..b8bd06e --- /dev/null +++ b/releasenotes/notes/volume-attachment-create-output-fix-56515b8fcdd260b9.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + The ``volume attachment create`` command will now display information + for successfully created volume attachments. Previously an empty table was + returned. diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 0ad18a2..829b09d 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ OpenStackClient Release Notes :maxdepth: 1 unreleased + yoga xena wallaby victoria diff --git a/releasenotes/source/yoga.rst b/releasenotes/source/yoga.rst new file mode 100644 index 0000000..7cd5e90 --- /dev/null +++ b/releasenotes/source/yoga.rst @@ -0,0 +1,6 @@ +========================= +Yoga Series Release Notes +========================= + +.. release-notes:: + :branch: stable/yoga diff --git a/requirements.txt b/requirements.txt index cb414eb..118b7b9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,7 @@ +# Requirements lower bounds listed here are our best effort to keep them up to +# date but we do not test them so no guarantee of having them all correct. If +# you find any incorrect lower bounds, let us know or propose a fix. + # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. @@ -6,7 +6,7 @@ description_file = author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/python-openstackclient/latest/ -python_requires = >=3.6 +python_requires = >=3.8 classifier = Environment :: OpenStack Intended Audience :: Information Technology @@ -15,9 +15,8 @@ classifier = Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 3 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 [files] packages = @@ -107,12 +106,8 @@ openstack.compute.v2 = server_migrate = openstackclient.compute.v2.server:MigrateServer server_migrate_confirm = openstackclient.compute.v2.server:MigrateConfirm server_migrate_revert = openstackclient.compute.v2.server:MigrateRevert - server_migration_abort = openstackclient.compute.v2.server:AbortMigration server_migration_confirm = openstackclient.compute.v2.server:ConfirmMigration - server_migration_force_complete = openstackclient.compute.v2.server:ForceCompleteMigration - server_migration_list = openstackclient.compute.v2.server:ListMigration server_migration_revert = openstackclient.compute.v2.server:RevertMigration - server_migration_show = openstackclient.compute.v2.server:ShowMigration server_pause = openstackclient.compute.v2.server:PauseServer server_reboot = openstackclient.compute.v2.server:RebootServer server_rebuild = openstackclient.compute.v2.server:RebuildServer @@ -153,6 +148,11 @@ openstack.compute.v2 = server_image_create = openstackclient.compute.v2.server_image:CreateServerImage + server_migration_abort = openstackclient.compute.v2.server_migration:AbortMigration + server_migration_force_complete = openstackclient.compute.v2.server_migration:ForceCompleteMigration + server_migration_list = openstackclient.compute.v2.server_migration:ListMigration + server_migration_show = openstackclient.compute.v2.server_migration:ShowMigration + server_volume_list = openstackclient.compute.v2.server_volume:ListServerVolume server_volume_update = openstackclient.compute.v2.server_volume:UpdateServerVolume @@ -742,6 +742,12 @@ openstack.volume.v3 = volume_message_list = openstackclient.volume.v3.volume_message:ListMessages volume_message_show = openstackclient.volume.v3.volume_message:ShowMessage + block_storage_cluster_list = openstackclient.volume.v3.block_storage_cluster:ListBlockStorageCluster + block_storage_cluster_set = openstackclient.volume.v3.block_storage_cluster:SetBlockStorageCluster + block_storage_cluster_show = openstackclient.volume.v3.block_storage_cluster:ShowBlockStorageCluster + block_storage_resource_filter_list = openstackclient.volume.v3.block_storage_resource_filter:ListBlockStorageResourceFilter + block_storage_resource_filter_show = openstackclient.volume.v3.block_storage_resource_filter:ShowBlockStorageResourceFilter + volume_snapshot_create = openstackclient.volume.v2.volume_snapshot:CreateVolumeSnapshot volume_snapshot_delete = openstackclient.volume.v2.volume_snapshot:DeleteVolumeSnapshot volume_snapshot_list = openstackclient.volume.v2.volume_snapshot:ListVolumeSnapshot @@ -1,5 +1,5 @@ [tox] -minversion = 3.2.0 +minversion = 3.18.0 envlist = py38,pep8 skipsdist = True # Automatic envs (pyXX) will only use the python version appropriate to that @@ -18,7 +18,7 @@ deps = -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt commands = stestr run {posargs} -whitelist_externals = stestr +allowlist_externals = stestr [testenv:fast8] # Use same environment directory as pep8 env to save space and install time @@ -71,7 +71,7 @@ commands = pythom -m pip install -q -e "git+file://{toxinidir}/../openstacksdk#egg=openstacksdk" python -m pip freeze stestr run {posargs} -whitelist_externals = stestr +allowlist_externals = stestr [testenv:functional] setenv = OS_TEST_PATH=./openstackclient/tests/functional @@ -138,9 +138,3 @@ exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,tools,releasenotes ignore = W504 import-order-style = pep8 application_import_names = openstackclient - -[testenv:lower-constraints] -deps = - -c{toxinidir}/lower-constraints.txt - -r{toxinidir}/test-requirements.txt - -r{toxinidir}/requirements.txt |
