From cd5317edda843e938da5362b98525ea4ff21a1e7 Mon Sep 17 00:00:00 2001 From: vineet-pruthi Date: Wed, 11 Sep 2019 11:03:07 +0000 Subject: [PATCH 01/65] Adding RDS v3 implementation --- otcextensions/osclient/rds/client.py | 16 +- otcextensions/osclient/rds/v3/__init__.py | 0 otcextensions/osclient/rds/v3/flavor.py | 61 ++ otcextensions/sdk/__init__.py | 1 + otcextensions/sdk/rds/rds_service.py | 6 +- otcextensions/sdk/rds/v3/_proxy.py | 521 ++++++++++++++++++ otcextensions/sdk/rds/v3/flavor.py | 40 ++ .../tests/unit/sdk/rds/v3/__init__.py | 0 .../tests/unit/sdk/rds/v3/test_flavor.py | 48 ++ .../tests/unit/sdk/rds/v3/test_proxy.py | 27 + setup.cfg | 3 + 11 files changed, 717 insertions(+), 6 deletions(-) create mode 100644 otcextensions/osclient/rds/v3/__init__.py create mode 100644 otcextensions/osclient/rds/v3/flavor.py create mode 100644 otcextensions/sdk/rds/v3/_proxy.py create mode 100644 otcextensions/sdk/rds/v3/flavor.py create mode 100644 otcextensions/tests/unit/sdk/rds/v3/__init__.py create mode 100644 otcextensions/tests/unit/sdk/rds/v3/test_flavor.py create mode 100644 otcextensions/tests/unit/sdk/rds/v3/test_proxy.py diff --git a/otcextensions/osclient/rds/client.py b/otcextensions/osclient/rds/client.py index db8479d9b..b04b6b9fb 100644 --- a/otcextensions/osclient/rds/client.py +++ b/otcextensions/osclient/rds/client.py @@ -12,20 +12,22 @@ # import logging -from otcextensions import sdk +from osc_lib import utils +from otcextensions import sdk +from otcextensions.i18n import _ LOG = logging.getLogger(__name__) -DEFAULT_API_VERSION = '1.0' +DEFAULT_API_VERSION = '3' API_VERSION_OPTION = 'os_rds_api_version' API_NAME = "rds" API_VERSIONS = { "1.0": "openstack.connection.Connection", "1": "openstack.connection.Connection", + "3": "openstack.connection.Connection" } - def make_client(instance): """Returns a rds proxy""" @@ -39,7 +41,13 @@ def make_client(instance): conn.rds) return conn.rds - def build_option_parser(parser): """Hook to add global options""" + parser.add_argument( + '--os-rds-api-version', + metavar='', + default=utils.env('OS_RDS_API_VERSION'), + help=_("RDS API version, default=%s " + "(Env: OS_RDS_API_VERSION)") % DEFAULT_API_VERSION + ) return parser diff --git a/otcextensions/osclient/rds/v3/__init__.py b/otcextensions/osclient/rds/v3/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/osclient/rds/v3/flavor.py b/otcextensions/osclient/rds/v3/flavor.py new file mode 100644 index 000000000..70280bb4e --- /dev/null +++ b/otcextensions/osclient/rds/v3/flavor.py @@ -0,0 +1,61 @@ +# 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. +# +"""Flavor v3 action implementations""" +import logging + +from osc_lib import utils +from osc_lib.command import command + +from otcextensions.i18n import _ + +LOG = logging.getLogger(__name__) + +DB_TYPE_CHOICES = ['mysql', 'postgresql', 'sqlserver'] + + +class ListDatabaseFlavors(command.Lister): + + _description = _("List database flavors") + columns = ['instance_mode', 'vcpus', 'ram', 'spec_code'] + + def get_parser(self, prog_name): + parser = super(ListDatabaseFlavors, self).get_parser(prog_name) + + parser.add_argument( + 'db_name', + metavar='{' + ','.join(DB_TYPE_CHOICES) + '}', + type=lambda s: s.lower(), + choices=DB_TYPE_CHOICES, + help=_('Name of the datastore Engine.'), + ) + + parser.add_argument( + 'version', + metavar='', + help=_('Specifies the database version.'), + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.rds + + data = client.flavors(datastore_name=parsed_args.db_name, version_name=parsed_args.version) + + return ( + self.columns, + (utils.get_item_properties( + s, + self.columns, + ) for s in data) + ) diff --git a/otcextensions/sdk/__init__.py b/otcextensions/sdk/__init__.py index 12cb54d2b..f9c76b8e2 100644 --- a/otcextensions/sdk/__init__.py +++ b/otcextensions/sdk/__init__.py @@ -91,6 +91,7 @@ 'rds': { 'service_type': 'rds', # 'additional_headers': {'content-type': 'application/json'}, + 'endpoint_service_type': 'rdsv3', 'append_project_id': True, }, 'volume_backup': { diff --git a/otcextensions/sdk/rds/rds_service.py b/otcextensions/sdk/rds/rds_service.py index 555111e71..bb0f0fe9a 100644 --- a/otcextensions/sdk/rds/rds_service.py +++ b/otcextensions/sdk/rds/rds_service.py @@ -12,12 +12,14 @@ from openstack import service_description -from otcextensions.sdk.rds.v1 import _proxy +from otcextensions.sdk.rds.v1 import _proxy as _proxy_v1 +from otcextensions.sdk.rds.v3 import _proxy as _proxy_v3 class RdsService(service_description.ServiceDescription): """The RDS service.""" supported_versions = { - '1': _proxy.Proxy + '1': _proxy_v1.Proxy, + '3': _proxy_v3.Proxy } diff --git a/otcextensions/sdk/rds/v3/_proxy.py b/otcextensions/sdk/rds/v3/_proxy.py new file mode 100644 index 000000000..840b2ef24 --- /dev/null +++ b/otcextensions/sdk/rds/v3/_proxy.py @@ -0,0 +1,521 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from openstack import exceptions + +from openstack import proxy +#from otcextensions.sdk.rds.v3 import backup as _backup +#from otcextensions.sdk.rds.v3 import configuration as _configuration +#from otcextensions.sdk.rds.v3 import datastore as _datastore +from otcextensions.sdk.rds.v3 import flavor as _flavor +#from otcextensions.sdk.rds.v3 import instance as _instance + + +class Proxy(proxy.Proxy): + + skip_discovery = True + +# def get_os_endpoint(self, **kwargs): +# """Return OpenStack compliant endpoint +# +# """ +# return self.get_endpoint(service_type='database') +# # if 'os_endpoint' not in self: +# # endpoint = super(Proxy, self).get_endpoint(**kwargs) +# # # endpoint_override = self.endpoint_override +# # if endpoint.endswith('/rds/v1'): # and not endpoint_override: +# # endpoint = endpoint.rstrip('/rds/v1') +# # endpoint = utils.urljoin(endpoint, 'v1.0') +# # self.os_endpoint = endpoint +# # # else: +# # # _logger.debug('RDS endpoint_override is set. Return it') +# # # return endpoint_override +# # else: +# # return self.os_endpoint +# +# def get_rds_endpoint(self, **kwargs): +# """Return RDS propriatary endpoint +# +# """ +# return self.get_endpoint(service_type='rds') +# # endpoint = super(Proxy, self).get_endpoint(**kwargs) +# # endpoint_override = self.endpoint_override +# # if endpoint.endswith('/rds/v1') and not endpoint_override: +# # return endpoint +# # elif endpoint_override: +# # _logger.debug('RDS endpoint_override is set. Return it') +# # return endpoint_override +# # else: +# # return endpoint +# +# def get_os_headers(self, language=None): +# """Get headers for request +# +# Unfortunatels RDS requires 'Content-Type: application/json' +# header even for GET and LIST operations with empty body +# We need to deel with it +# +# :param language: whether language should be added to headers +# can be either bool (then self.get_language is used) +# or a language code directly (i.e. "en-us") +# :returns dict: dictionary with headers +# """ +# headers = { +# 'Content-Type': 'application/json', +# } +# if language: +# if isinstance(language, bool): +# headers['X-Language'] = self.get_language() +# elif isinstance(language, str): +# headers['X-Language'] = language +# return headers +# +# def get_language(self): +# """Returns language code +# +# """ +# return 'en-us' +# +# # ======= Datastores ======= +# def datastore_types(self): +# """List supported datastore types +# +# :returns: A generator of supported datastore types +# :rtype object: object with name attribte +# """ +# for ds in ['MySQL', 'PostgreSQL', 'SQLServer']: +# obj = type('obj', (object,), {'name': ds}) +# yield obj +# +# return +# +# def datastore_versions(self, datastore): +# """List datastores +# +# :param dbId: database store name +# (MySQL, PostgreSQL, or SQLServer and is case-sensitive.) +# +# :returns: A generator of datastore versions +# :rtype: :class:`~otcextensions.sdk.rds.v1.flavor.Flavor` +# """ +# return self._list( +# _datastore.Datastore, +# paginated=False, +# # endpoint_override=self.get_rds_endpoint(), +# headers=self.get_os_headers(True), +# # project_id=self.session.get_project_id(), +# datastore_name=datastore +# ) +# +# def get_datastore_version(self, datastore, datastore_version): +# """Get the detail of a datastore version +# +# :param datastore: datastore name +# :param datastore_Version: id of the datastore version +# :returns: Detail of datastore version +# :rtype: :class:`~otcextensions.sdk.rds.v1.datastore.Datastore` +# """ +# versions = self._list( +# _datastore.Datastore, +# paginated=False, +# # endpoint_override=self.get_rds_endpoint(), +# headers=self.get_os_headers(True), +# # project_id=self.session.get_project_id(), +# datastore_name=datastore +# ) +# for ver in versions: +# if ver.id == datastore_version: +# return ver +# return exceptions.NotFoundException('Resource not found') + + # ======= Flavors ======= + def flavors(self, datastore_name, version_name): + """List flavors of given datastore_name and datastore_version + + :param datastore_name: datastore_name + :param version_name: version_name + + :returns: A generator of flavor + :rtype: :class:`~otcextensions.sdk.rds.v3.flavor.Flavor` + """ + return self._list(_flavor.Flavor, datastore_name=datastore_name, version_name=version_name) + +# def get_flavor(self, flavor): +# """Get the detail of a flavor +# +# :param id: Flavor id or an object of class +# :class:`~otcextensions.sdk.rds_os.v1.flavor.Flavor` +# :returns: Detail of flavor +# :rtype: :class:`~otcextensions.sdk.rds_os.v1.flavor.Flavor` +# """ +# return self._get( +# _flavor.Flavor, +# flavor, +# # project_id=self.session.get_project_id(), +# # endpoint_override=self.get_os_endpoint(), +# headers=self.get_os_headers(True), +# ) +# +# # def find_flavor(self, name_or_id, ignore_missing=True): +# # """Find a single flavor +# # +# # :param name_or_id: The name or ID of a flavor. +# # :param bool ignore_missing: When set to ``False`` +# # :class:`~openstack.exceptions.ResourceNotFound` will be +# # raised when the resource does not exist. +# # When set to ``True``, None will be returned when +# # attempting to find a nonexistent resource. +# # :returns: One :class:`~otcextensions.sdk.rds.v1.flavor.Flavor` +# # or None +# # """ +# # return self._find(_flavor.Flavor, name_or_id, +# # # project_id=self.session.get_project_id(), +# # # endpoint_override=self.get_os_endpoint(), +# # # headers=self.get_os_headers(), +# # ignore_missing=ignore_missing) +# +# # ======= Instance ======= +# def create_instance(self, **attrs): +# """Create a new instance from attributes +# +# :param dict attrs: Keyword arguments which will be used to create +# a :class:`~otcextensions.sdk.rds.v1.instance.Instance`, +# comprised of the properties on the Instance class. +# +# :returns: The results of server creation +# :rtype: :class:`~otcextensions.sdk.rds.v1.instance.Instance` +# """ +# return self._create(_instance.Instance, +# # project_id=self.session.get_project_id(), +# # endpoint_override=self.get_os_endpoint(), +# headers=self.get_os_headers(True), +# **attrs) +# +# def delete_instance(self, instance, ignore_missing=True): +# """Delete an instance +# +# :param instance: The value can be either the ID of an instance or a +# :class:`~otcextensions.sdk.rds.v1.instance.Instance` instance. +# :param bool ignore_missing: When set to ``False`` +# :class:`~openstack.exceptions.ResourceNotFound` will be +# raised when the instance does not exist. +# When set to ``True``, no exception will be set when +# attempting to delete a nonexistent instance. +# +# :returns: ``None`` +# """ +# self._delete( +# _instance.Instance, instance, +# ignore_missing=ignore_missing, +# # project_id=self.session.get_project_id(), +# # endpoint_override=self.get_os_endpoint(), +# headers=self.get_os_headers(True) +# ) +# +# def find_instance(self, name_or_id, ignore_missing=True): +# """Find a single instance +# +# :param name_or_id: The name or ID of a instance. +# :param bool ignore_missing: When set to ``False`` +# :class:`~openstack.exceptions.ResourceNotFound` will be +# raised when the resource does not exist. +# When set to ``True``, None will be returned when +# attempting to find a nonexistent resource. +# :returns: One :class:`~otcextensions.sdk.rds.v1.instance.Instance` +# or None +# """ +# return self._find(_instance.Instance, name_or_id, +# # project_id=self.session.get_project_id(), +# # endpoint_override=self.get_os_endpoint(), +# headers=self.get_os_headers(True), +# ignore_missing=ignore_missing) +# +# def get_instance(self, instance): +# """Get a single instance +# +# :param instance: The value can be the ID of an instance or a +# :class:`~otcextensions.sdk.rds.v1.instance.Instance` +# instance. +# +# :returns: One :class:`~otcextensions.sdk.rds.v1.instance.Instance` +# :raises: :class:`~openstack.exceptions.ResourceNotFound` +# when no resource can be found. +# """ +# return self._get( +# _instance.Instance, +# instance, +# # project_id=self.session.get_project_id(), +# # endpoint_override=self.get_os_endpoint(), +# headers=self.get_os_headers(True), +# ) +# +# def instances(self): +# """Return a generator of instances +# +# :returns: A generator of instance objects +# :rtype: :class:`~otcextensions.sdk.rds.v1.instance.Instance` +# """ +# return self._list( +# _instance.Instance, paginated=False, +# # project_id=self.session.get_project_id(), +# # endpoint_override=self.get_os_endpoint(), +# headers=self.get_os_headers(True), +# ) +# +# def update_instance(self, instance, **attrs): +# """Update a instance +# +# :param instance: Either the id of a instance or a +# :class:`~otcextensions.sdk.rds.v1.instance.Instance` +# instance. +# :attrs attrs: The attributes to update on the instance represented +# by ``value``. +# +# :returns: The updated instance +# :rtype: :class:`~otcextensions.sdk.rds.v1.instance.Instance` +# """ +# return self._update( +# _instance.Instance, +# instance=instance, +# # project_id=self.session.get_project_id(), +# # endpoint_override=self.get_rds_endpoint(), +# headers=self.get_os_headers(True), +# **attrs +# ) +# +# def restart_instance(self, instance): +# """Restart instance +# +# :param instance: Either the id of a instance or a +# :class:`~otcextensions.sdk.rds.v1.instance.Instance` +# instance. +# :returns: The updated instance +# :rtype: :class:`~otcextensions.sdk.rds.v1.instance.Instance` +# """ +# instance = self._get_resource(_instance.Instance, instance) +# return instance.restart(self) +# +# def restore_instance(self, instance, backup): +# """Restore instance from backup +# +# :param instance: Either the id of a instance or a +# :class:`~otcextensions.sdk.rds.v1.instance.Instance` +# instance. +# :param backup: Either the id of a backup or a +# :class:`~otcextensions.sdk.rds.v1.backup.Backup` +# instance. +# +# :returns: None +# :rtype: +# """ +# instance = self._get_resource(_instance.Instance, instance) +# backup = self._get_resource(_backup.Backup, backup) +# return instance.restore(self, backupRef=backup.id) +# # +# # def create_instance_from_backup(self, backup, **attrs): +# # """Restore instance from backup +# # +# # :param instance: Either the id of a instance or a +# # :class:`~otcextensions.sdk.rds.v1.instance.Instance` +# # instance. +# # :param backup: Either the id of a backup or a +# # :class:`~otcextensions.sdk.rds.v1.backup.Backup` +# # instance. +# # :attrs attrs: The attributes to update on the instance represented +# # by ``value``. +# # +# # :returns: The updated instance +# # :rtype: :class:`~otcextensions.sdk.rds.v1.instance.Instance` +# # """ +# # # instance = self._get_resource(_instance.Instance, instance) +# # # backup = self._get_resource(_backup.Backup, backup) +# # +# # res = _instance.Instance.new(**attrs) +# # +# # return res.create(self) +# +# # ======= Configurations ======= +# def configurations(self, **attrs): +# """Obtaining a ConfigurationGroup List +# +# :returns: A generator of ConfigurationGroup object +# :rtype: +# :class:`~otcextensions.sdk.rds.v1.configuration.ConfigurationGroup` +# """ +# return self._list( +# _configuration.ConfigurationGroup, +# paginated=False, +# # project_id=self.session.get_project_id(), +# # endpoint_override=self.get_os_endpoint(), +# # headers=self.get_os_headers(), +# ) +# +# def get_configuration(self, cg): +# """Obtaining a ConfigurationGroup +# +# :param parameter_group: The value can be the ID of a ConfigurationGroup +# or a object of +# :class:`~otcextensions.sdk.rds.v1.configuration.ConfigurationGroup`. +# +# :returns: A Parameter Group Object +# :rtype: :class:`~otcextensions.rds.v1.configuration.ConfigurationGroup` +# """ +# return self._get( +# _configuration.ConfigurationGroup, +# cg, +# # project_id=self.session.get_project_id(), +# # endpoint_override=self.get_os_endpoint(), +# headers=self.get_os_headers(True) +# ) +# +# def create_configuration(self, **attrs): +# """Creating a ConfigurationGroup +# +# :param dict **attrs: Dict to overwrite ConfigurationGroup object +# :returns: A Parameter Group Object +# :rtype: +# :class:`~otcextensions.sdk.rds.v1.configuration.ConfigurationGroup` +# """ +# return self._create(_configuration.ConfigurationGroup, +# # project_id=self.session.get_project_id(), +# # endpoint_override=self.get_os_endpoint(), +# # headers=self.get_os_headers(), +# **attrs) +# +# def delete_configuration(self, cg, ignore_missing=True): +# """Deleting a ConfigurationGroup +# +# :param cg: The value can be the ID of a ConfigurationGroup or a +# object of +# :class:`~otcextensions.sdk.rds.v1.configuration.ConfigurationGroup`. +# :param bool ignore_missing: When set to ``False`` +# :class:`~openstack.exceptions.ResourceNotFound` will be +# raised when the Parameter Group does not exist. +# When set to ``True``, no exception will be set when +# attempting to delete a nonexistent Parameter Group. +# +# :returns: None +# :rtype: None +# """ +# self._delete( +# _configuration.ConfigurationGroup, cg, +# ignore_missing=ignore_missing, +# # project_id=self.session.get_project_id(), +# # endpoint_override=self.get_os_endpoint(), +# # headers=self.get_os_headers() +# ) +# +# def find_configuration(self, name_or_id, ignore_missing=True): +# """Find a ConfigurationGroup +# +# :param parameter_group: The value can be the ID of a ConfigurationGroup +# or a object of +# :class:`~otcextensions.sdk.rds.v1.configuration.Configurations`. +# :returns: A Parameter Group Object +# :rtype: +# :class:`~otcextensions.rds.v1.configuration.ConfigurationGroup`. +# """ +# return self._find(_configuration.ConfigurationGroup, name_or_id, +# # project_id=self.session.get_project_id(), +# # endpoint_override=self.get_os_endpoint(), +# # headers=self.get_os_headers(), +# ignore_missing=ignore_missing) +# +# # ======= Backups ======= +# def backups(self): +# """List Backups +# +# :returns: A generator of backup +# :rtype: :class:`~otcextensions.sdk.rds.v1.backup.Backup` +# """ +# return self._list( +# _backup.Backup, paginated=False, +# # project_id=self.session.get_project_id(), +# # endpoint_override=self.get_rds_endpoint(), +# headers=self.get_os_headers(True) +# ) +# +# def create_backup(self, instance, **attrs): +# """Create a backups of instance +# +# :returns: A new backup object +# :rtype: :class:`~otcextensions.sdk.rds.v1.backup.Backup` +# """ +# instance = self._get_resource(_instance.Instance, instance) +# return self._create( +# _backup.Backup, +# instance=instance.id, +# # project_id=self.session.get_project_id(), +# # endpoint_override=self.get_rds_endpoint(), +# headers=self.get_os_headers(True), +# **attrs +# ) +# +# def delete_backup(self, backup, ignore_missing=True): +# """Deletes given backup +# +# :param instance: The value can be either the ID of an instance or a +# :class:`~openstack.database.v1.instance.Instance` instance. +# :param bool ignore_missing: When set to ``False`` +# :class:`~openstack.exceptions.ResourceNotFound` will be +# raised when the instance does not exist. +# When set to ``True``, no exception will be set when +# attempting to delete a nonexistent instance. +# +# :returns: ``None`` +# """ +# return self._delete( +# _backup.Backup, +# backup, +# ignore_missing=ignore_missing, +# # project_id=self.session.get_project_id(), +# # endpoint_override=self.get_rds_endpoint(), +# headers=self.get_os_headers(True), +# ) +# +# def get_backup_policy(self, instance): +# """Obtaining a backup policy of the instance +# +# :param instance: This parameter can be either the ID of an instance +# or a :class:`~openstack.sdk.rds.v1.instance.Instance` +# :returns: A Backup policy +# :rtype: :class:`~otcextensions.sdk.rds.v1.configuration.BackupPolicy` +# +# """ +# instance = self._get_resource(_instance.Instance, instance) +# return self._get( +# _backup.BackupPolicy, +# requires_id=False, +# instance_id=instance.id, +# # project_id=self.session.get_project_id(), +# # endpoint_override=self.get_rds_endpoint(), +# headers=self.get_os_headers(True), +# ) +# +# def set_backup_policy(self, backup_policy, instance, **attrs): +# """Sets the backup policy of the instance +# +# :param instance: This parameter can be either the ID of an instance +# or a :class:`~openstack.sdk.rds.v1.instance.Instance` +# :param dict attrs: The attributes to update on the backup_policy +# represented by ``backup_policy``. +# +# :returns: ``None`` +# """ +# instance = self._get_resource(_instance.Instance, instance) +# return self._update( +# _backup.BackupPolicy, +# backup_policy, +# instance_id=instance.id, +# # project_id=self.session.get_project_id(), +# # endpoint_override=self.get_rds_endpoint(), +# headers=self.get_os_headers(True), +# **attrs) diff --git a/otcextensions/sdk/rds/v3/flavor.py b/otcextensions/sdk/rds/v3/flavor.py new file mode 100644 index 000000000..b83b4bdba --- /dev/null +++ b/otcextensions/sdk/rds/v3/flavor.py @@ -0,0 +1,40 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# from openstack import exceptions +from openstack import resource + +class Flavor(resource.Resource): + + base_path = '/flavors/%(datastore_name)s' + # In difference to regular OS services RDS + # does not return single item with a proper element tag + resources_key = 'flavors' + + # capabilities + allow_list = True + + _query_mapping = resource.QueryParameters( + 'version_name' + ) + datastore_name = resource.URI('datastore_name') + #: VCPU's + #: *Type: str* + vcpus = resource.Body('vcpus') + #: Instance Mode (single/ha/replica) + #: *Type: int* + instance_mode = resource.Body('instance_mode') + #: Ram size in MB. + #: *Type:int* + ram = resource.Body('ram', type=int) + #: Specification code + #: *Type: str* + spec_code = resource.Body('spec_code') diff --git a/otcextensions/tests/unit/sdk/rds/v3/__init__.py b/otcextensions/tests/unit/sdk/rds/v3/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/unit/sdk/rds/v3/test_flavor.py b/otcextensions/tests/unit/sdk/rds/v3/test_flavor.py new file mode 100644 index 000000000..b895b4521 --- /dev/null +++ b/otcextensions/tests/unit/sdk/rds/v3/test_flavor.py @@ -0,0 +1,48 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from openstack.tests.unit import base + +from otcextensions.sdk.rds.v3 import flavor + +EXAMPLE = { + "vcpus": "1", + "ram": 2, + "spec_code": "rds.mysql.c2.medium.ha", + "instance_mode": "ha" +} + +class TestFlavor(base.TestCase): + + def setUp(self): + super(TestFlavor, self).setUp() + + def test_basic(self): + sot = flavor.Flavor() + + self.assertEqual('/flavors/%(datastore_name)s', sot.base_path) + self.assertEqual('flavors', sot.resources_key) + self.assertIsNone(sot.resource_key) + + self.assertTrue(sot.allow_list) + self.assertFalse(sot.allow_fetch) + self.assertFalse(sot.allow_create) + self.assertFalse(sot.allow_delete) + self.assertFalse(sot.allow_commit) + self.assertDictEqual({'limit': 'limit', 'marker': 'marker', 'version_name': 'version_name'}, sot._query_mapping._mapping) + + def test_make_it(self): + + sot = flavor.Flavor(**EXAMPLE) + self.assertEqual(EXAMPLE['spec_code'], sot.spec_code) + self.assertEqual(EXAMPLE['vcpus'], sot.vcpus) + self.assertEqual(EXAMPLE['ram'], sot.ram) + self.assertEqual(EXAMPLE['instance_mode'], sot.instance_mode) diff --git a/otcextensions/tests/unit/sdk/rds/v3/test_proxy.py b/otcextensions/tests/unit/sdk/rds/v3/test_proxy.py new file mode 100644 index 000000000..70ca4cdf7 --- /dev/null +++ b/otcextensions/tests/unit/sdk/rds/v3/test_proxy.py @@ -0,0 +1,27 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from otcextensions.sdk.rds.v3 import _proxy +from otcextensions.sdk.rds.v3 import flavor + +from openstack.tests.unit import test_proxy_base + + +class TestRdsProxy(test_proxy_base.TestProxyBase): + def setUp(self): + super(TestRdsProxy, self).setUp() + self.proxy = _proxy.Proxy(self.session) + + +class TestRdsFlavor(TestRdsProxy): + def test_flavors(self): + self.verify_list(self.proxy.flavors, flavor.Flavor, method_kwargs={'datastore_name': 'MySQL', 'version_name': '5.7'}, expected_kwargs={'datastore_name': 'MySQL', 'version_name': '5.7'}) diff --git a/setup.cfg b/setup.cfg index 2e160dcf4..afc20b388 100644 --- a/setup.cfg +++ b/setup.cfg @@ -92,6 +92,9 @@ openstack.rds.v1 = rds_backup_create = otcextensions.osclient.rds.v1.backup:CreateBackup rds_backup_delete = otcextensions.osclient.rds.v1.backup:DeleteBackup +openstack.rds.v3 = + rds_flavor_list = otcextensions.osclient.rds.v3.flavor:ListDatabaseFlavors + openstack.auto_scaling.v1 = as_group_list = otcextensions.osclient.auto_scaling.v1.group:ListAutoScalingGroup as_group_show = otcextensions.osclient.auto_scaling.v1.group:ShowAutoScalingGroup From 257caf5b7d9685c15e5962abf96679b6de11f940 Mon Sep 17 00:00:00 2001 From: vineet-pruthi Date: Sun, 15 Sep 2019 06:29:11 +0000 Subject: [PATCH 02/65] Saving Changes to github --- otcextensions/osclient/rds/client.py | 2 + otcextensions/osclient/rds/v3/backup.py | 186 ++++++ .../osclient/rds/v3/configuration.py | 437 ++++++++++++ otcextensions/osclient/rds/v3/datastore.py | 79 +++ otcextensions/osclient/rds/v3/flavor.py | 4 +- otcextensions/osclient/rds/v3/instance.py | 511 ++++++++++++++ otcextensions/sdk/rds/v3/_proxy.py | 626 ++++++++---------- otcextensions/sdk/rds/v3/backup.py | 129 ++++ otcextensions/sdk/rds/v3/datastore.py | 30 + otcextensions/sdk/rds/v3/flavor.py | 5 +- otcextensions/sdk/rds/v3/instance.py | 112 ++++ .../tests/unit/osclient/rds/v3/__init__.py | 0 .../tests/unit/osclient/rds/v3/fakes.py | 242 +++++++ .../unit/osclient/rds/v3/test_datastore.py | 112 ++++ .../tests/unit/osclient/rds/v3/test_flavor.py | 75 +++ .../tests/unit/sdk/rds/v3/test_datastore.py | 48 ++ .../tests/unit/sdk/rds/v3/test_flavor.py | 8 +- setup.cfg | 9 + 18 files changed, 2262 insertions(+), 353 deletions(-) create mode 100644 otcextensions/osclient/rds/v3/backup.py create mode 100644 otcextensions/osclient/rds/v3/configuration.py create mode 100644 otcextensions/osclient/rds/v3/datastore.py create mode 100644 otcextensions/osclient/rds/v3/instance.py create mode 100644 otcextensions/sdk/rds/v3/backup.py create mode 100644 otcextensions/sdk/rds/v3/datastore.py create mode 100644 otcextensions/sdk/rds/v3/instance.py create mode 100644 otcextensions/tests/unit/osclient/rds/v3/__init__.py create mode 100644 otcextensions/tests/unit/osclient/rds/v3/fakes.py create mode 100644 otcextensions/tests/unit/osclient/rds/v3/test_datastore.py create mode 100644 otcextensions/tests/unit/osclient/rds/v3/test_flavor.py create mode 100644 otcextensions/tests/unit/sdk/rds/v3/test_datastore.py diff --git a/otcextensions/osclient/rds/client.py b/otcextensions/osclient/rds/client.py index b04b6b9fb..eecdfebff 100644 --- a/otcextensions/osclient/rds/client.py +++ b/otcextensions/osclient/rds/client.py @@ -28,6 +28,7 @@ "3": "openstack.connection.Connection" } + def make_client(instance): """Returns a rds proxy""" @@ -41,6 +42,7 @@ def make_client(instance): conn.rds) return conn.rds + def build_option_parser(parser): """Hook to add global options""" parser.add_argument( diff --git a/otcextensions/osclient/rds/v3/backup.py b/otcextensions/osclient/rds/v3/backup.py new file mode 100644 index 000000000..05da4250a --- /dev/null +++ b/otcextensions/osclient/rds/v3/backup.py @@ -0,0 +1,186 @@ +# 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. +# +"""Backup v3 action implementations""" +import logging + +from osc_lib import utils +from osc_lib.command import command + +from otcextensions.i18n import _ + +LOG = logging.getLogger(__name__) + + +def set_attributes_for_print_detail(obj): + info = {} + attr_list = ['id', 'name', 'type', 'size', 'status', 'begin_time', 'end_time', 'instance_id'] + for attr in dir(obj): + if attr == 'datastore' and getattr(obj, attr): + info['datastore_type'] = obj.datastore['type'] + info['datastore_version'] = obj.datastore['version'] + elif attr == 'databases' and getattr(obj, attr): + info['databases'] = [] + for database in obj.databases: + info['databases'].append(database['name']) + elif attr in attr_list and getattr(obj, attr): + info[attr] = getattr(obj, attr) + return info + + +class ListBackup(command.Lister): + + _description = _("List database backups/snapshots") + columns = ('Id', 'name', 'type', 'instance_id', 'datastore_type', 'datastore_version') + + def get_parser(self, prog_name): + parser = super(ListBackup, self).get_parser(prog_name) + parser.add_argument( + 'instance', + metavar='', + help=_('Specify instance ID or Name to get backup list'), + ) + parser.add_argument( + '--backup_id', + metavar='', + help=_('Specify the backup ID.'), + ) + parser.add_argument( + '--backup_type', + metavar='', + choices=['auto', 'manual', 'fragment', 'incremental'], + help=_('Specify the backup type.'), + ) + parser.add_argument( + '--offset', + metavar='', + help=_('Specify the index position.'), + ) + parser.add_argument( + '--limit', + metavar='', + help=_('Specify the limit of resources to be queried.'), + ) + parser.add_argument( + '--begin_time', + metavar='', + help=_('Specify the start time for obtaining the backup list.'), + ) + parser.add_argument( + '--end_time', + metavar='', + help=_('Specify the end time for obtaining the backup list.'), + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.rds + attrs = {} + args_list = ['instance', 'backup_id', 'backup_type', 'offset', 'limit', 'begin_time', 'end_time'] + for arg in args_list: + if arg == 'instance': + attrs['instance_id'] = client.find_instance(parsed_args.instance).id + elif getattr(parsed_args, arg): + attrs[arg] = getattr(parsed_args, arg) + + data = client.backups(**attrs) + + return ( + self.columns, + (utils.get_dict_properties( + set_attributes_for_print_detail(s), + self.columns, + ) for s in data) + ) + +class CreateBackup(command.ShowOne): + _description = _('Create Database backup') + + columns = ('ID', 'Name', 'type', 'instance_id', + 'status', 'begin_time', 'databases') + + def get_parser(self, prog_name): + parser = super(CreateBackup, self).get_parser(prog_name) + parser.add_argument( + 'instance', + metavar='', + help=_('ID or Name of the instance to create backup from') + ) + parser.add_argument( + '--name', + metavar='', + required=True, + help=_('Name for the backup') + ) + parser.add_argument( + '--description', + metavar='', + help=_('Description for the backup') + ) + parser.add_argument( + '--databases', + metavar='', + help=_('Specifies a list of self-built SQL Server' + 'databases that are partially backed up' + '(Only SQL Server support partial backups.)') + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.rds + + attrs = {} + attrs['instance_id'] = client.find_instance(parsed_args.instance).id + + attrs['name'] = parsed_args.name + if parsed_args.description: + attrs['description'] = parsed_args.description + if parsed_args.databases: + attrs['databases'] = [] + databases = parsed_args.databases + databases = databases.split(",") + for db_name in databases: + attrs['databases'].append({'name': db_name}) + + data = client.create_backup(**attrs) + + return ( + self.columns, + utils.get_dict_properties( + set_attributes_for_print_detail(data), + self.columns, + ) + ) + + +class DeleteBackup(command.Command): + _description = _('Delete Backup') + + def get_parser(self, prog_name): + parser = super(DeleteBackup, self).get_parser(prog_name) + parser.add_argument( + 'backup', + metavar='', + nargs='+', + help=_('ID of the backup') + ) + return parser + + def take_action(self, parsed_args): + + if parsed_args.backup: + client = self.app.client_manager.rds + for bck in parsed_args.backup: + client.delete_backup( + backup=bck, + ignore_missing=False) diff --git a/otcextensions/osclient/rds/v3/configuration.py b/otcextensions/osclient/rds/v3/configuration.py new file mode 100644 index 000000000..448bd3624 --- /dev/null +++ b/otcextensions/osclient/rds/v3/configuration.py @@ -0,0 +1,437 @@ +# 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. +# +"""Configuration v1 action implementations""" +import json +import logging + +from osc_lib import exceptions +from osc_lib import utils +from osc_lib.cli import parseractions +from osc_lib.command import command + +from otcextensions.i18n import _ + +import six + +LOG = logging.getLogger(__name__) + +DATASTORE_TYPE_CHOICES = ['MySQL', 'PostgreSQL', 'SQLServer'] + + +def format_dict(data): + """Return a formatted string of key value pairs + + :param data: a dict + :rtype: a string formatted to key='value' + """ + + if data is None: + return None + + output = "" + for s in sorted(data): + output = output + s + "='" + six.text_type(data[s]) + "',\n " + return output[:-2] + + +class ListConfigurations(command.Lister): + _description = _("List Parameter Groups") + columns = ['ID', 'Name', 'Description', 'Datastore Name', + 'Datastore Version Name'] + + def get_parser(self, prog_name): + parser = super(ListConfigurations, self).get_parser(prog_name) + parser.add_argument( + '--limit', + dest='limit', + metavar='', + type=int, + default=None, + help=_('Limit the number of results displayed. (Not supported)') + ) + parser.add_argument( + '--marker', + dest='marker', + metavar='', + help=_('Begin displaying the results for IDs greater than the ' + 'specified marker. When used with --limit, set this to ' + 'the last ID displayed in the previous run. ' + '(Not supported)') + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.rds + + data = client.configurations() + + return ( + self.columns, + (utils.get_item_properties( + s, + self.columns, + ) for s in data) + ) + + +class ShowConfiguration(command.ShowOne): + _description = _("Shows details of a database configuration group.") + columns = ['ID', 'Name', 'Description', 'Datastore Name', + 'Datastore Version Name', 'Values'] + + def get_parser(self, prog_name): + parser = super(ShowConfiguration, self).get_parser(prog_name) + parser.add_argument( + 'configuration_group', + metavar="", + help=_("ID or name of the configuration group") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.rds + + obj = client.find_configuration(parsed_args.configuration_group) + # TODO(agoncharov) find by name does not return parameter values + + # TODO(agoncharov) values and parameters are breaking the layout + # dramatically. Maybe it make sence to create additional + # filter to get/list only specific values/params + data = utils.get_item_properties( + obj, self.columns, + formatters={ + 'values': format_dict, + 'parameters': utils.format_list_of_dicts, + } + ) + + return (self.columns, data) + + +class CreateConfiguration(command.ShowOne): + _description = _("Create new Parameter Group") + + columns = ( + 'id', + 'name', + 'description', + 'datastore_version_id', + 'datastore_version_name', + 'datastore_name', + 'created', + 'updated', + 'allowed_updated', + 'instance_count', + 'values' + ) + + def get_parser(self, prog_name): + parser = super(CreateConfiguration, self).get_parser(prog_name) + parser.add_argument( + '--name', + metavar="", + required=True, + help=_("Parameter group name") + ) + parser.add_argument( + '--description', + metavar="", + help=_("Parameter group description") + ) + parser.add_argument( + '--datastore_type', + metavar="", + choices=DATASTORE_TYPE_CHOICES, + required=True, + help=_("Datastore type") + ) + parser.add_argument( + '--datastore_version', + metavar="", + required=True, + help=_("Datastore version") + ) + parser.add_argument( + '--value', + dest="ind_values", + metavar="", + action=parseractions.KeyValueAction, + help=_("Parameter group value" + "(repeat option to set multiple values)") + ) + parser.add_argument( + 'values', + metavar='', + help=_('Dictionary (JSon) of the values to set.'), + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.rds + + config_attrs = {} + config_attrs['datastore'] = {} + if parsed_args.name: + config_attrs['name'] = parsed_args.name + if parsed_args.description: + config_attrs['description'] = parsed_args.description + if parsed_args.datastore_type and parsed_args.datastore_version: + datastore = {} + datastore['type'] = parsed_args.datastore_type + datastore['version'] = parsed_args.datastore_version + config_attrs['datastore'] = datastore + + # flatten values into the proper config_attrs + values = {} + try: + values = json.loads(parsed_args.values) + except Exception as e: + msg = (_("Failed to parse configuration values: %(e)s") + % {'e': e}) + raise exceptions.CommandError(msg) + + if getattr(parsed_args, 'ind_values', None): + for k, v in six.iteritems(parsed_args.ind_values): + values[k] = str(v) + + config_attrs['values'] = values + + config = client.create_configuration(**config_attrs) + + data = utils.get_item_properties( + config, self.columns, + formatters={ + 'values': format_dict, + } + ) + + return (self.columns, data) + + +class SetConfiguration(command.ShowOne): + _description = _("Set values of the Parameter Group") + + columns = ( + 'id', + 'name', + 'description', + 'datastore_version_id', + 'datastore_version_name', + 'datastore_name', + 'created', + 'updated', + 'allowed_updated', + 'instance_count', + 'values' + ) + + def get_parser(self, prog_name): + parser = super(SetConfiguration, self).get_parser(prog_name) + parser.add_argument( + '--parameter-group', + metavar="", + required=True, + help=_("Parameter group id") + ) + parser.add_argument( + '--name', + metavar="", + help=_("New ParameterGroup name") + ) + parser.add_argument( + '--description', + metavar="", + help=_("New ParameterGroup description") + ) + # parser.add_argument( + # '--datastore_type', + # metavar="", + # choices=DATASTORE_TYPE_CHOICES, + # required=True, + # help=_("Datastore type") + # ) + # parser.add_argument( + # '--datastore_version', + # metavar=" Date: Tue, 17 Sep 2019 09:53:34 +0000 Subject: [PATCH 03/65] Saving further changes in RDSv3 --- .../osclient/rds/v3/configuration.py | 74 +++---- otcextensions/osclient/rds/v3/instance.py | 90 +++++--- otcextensions/sdk/rds/v3/_proxy.py | 208 +++++++++--------- otcextensions/sdk/rds/v3/configuration.py | 194 ++++++++++++++++ otcextensions/sdk/rds/v3/instance.py | 9 + setup.cfg | 3 + 6 files changed, 396 insertions(+), 182 deletions(-) create mode 100644 otcextensions/sdk/rds/v3/configuration.py diff --git a/otcextensions/osclient/rds/v3/configuration.py b/otcextensions/osclient/rds/v3/configuration.py index 448bd3624..7cb5f5af8 100644 --- a/otcextensions/osclient/rds/v3/configuration.py +++ b/otcextensions/osclient/rds/v3/configuration.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. # -"""Configuration v1 action implementations""" +"""Configuration v3 action implementations""" import json import logging @@ -22,7 +22,7 @@ from otcextensions.i18n import _ import six - +import yaml LOG = logging.getLogger(__name__) DATASTORE_TYPE_CHOICES = ['MySQL', 'PostgreSQL', 'SQLServer'] @@ -47,7 +47,7 @@ def format_dict(data): class ListConfigurations(command.Lister): _description = _("List Parameter Groups") columns = ['ID', 'Name', 'Description', 'Datastore Name', - 'Datastore Version Name'] + 'Datastore Version Name', 'User Defined'] def get_parser(self, prog_name): parser = super(ListConfigurations, self).get_parser(prog_name) @@ -162,18 +162,19 @@ def get_parser(self, prog_name): required=True, help=_("Datastore version") ) +# parser.add_argument( +# '--value', +# dest="ind_values", +# metavar="", +# action=parseractions.KeyValueAction, +# help=_("Parameter group value" +# "(repeat option to set multiple values)") +# ) parser.add_argument( - '--value', - dest="ind_values", - metavar="", - action=parseractions.KeyValueAction, - help=_("Parameter group value" - "(repeat option to set multiple values)") - ) - parser.add_argument( - 'values', + '--values', metavar='', - help=_('Dictionary (JSon) of the values to set.'), + help=_('Dictionary (JSON) of the values to set.'), + type=yaml.load ) return parser @@ -194,20 +195,19 @@ def take_action(self, parsed_args): config_attrs['datastore'] = datastore # flatten values into the proper config_attrs - values = {} - try: - values = json.loads(parsed_args.values) - except Exception as e: - msg = (_("Failed to parse configuration values: %(e)s") - % {'e': e}) - raise exceptions.CommandError(msg) - - if getattr(parsed_args, 'ind_values', None): - for k, v in six.iteritems(parsed_args.ind_values): - values[k] = str(v) - - config_attrs['values'] = values - +# values = {} +# try: +# values = json.loads(parsed_args.values) +# except Exception as e: +# msg = (_("Failed to parse configuration values: %(e)s") +# % {'e': e}) +# raise exceptions.CommandError(msg) +# +# if getattr(parsed_args, 'ind_values', None): +# for k, v in six.iteritems(parsed_args.ind_values): +# values[k] = str(v) +# + config_attrs['values'] = parsed_args.values config = client.create_configuration(**config_attrs) data = utils.get_item_properties( @@ -321,23 +321,19 @@ class DeleteConfiguration(command.Command): def get_parser(self, prog_name): parser = super(DeleteConfiguration, self).get_parser(prog_name) parser.add_argument( - "configuration_group", - metavar="", - help=_("ID or name of the configuration group"), + 'configuration', + metavar='', + nargs='+', + help=_('ID or name of the configuration group') ) return parser def take_action(self, parsed_args): client = self.app.client_manager.rds - - try: - configuration = client.find_configuration( - parsed_args.configuration_group) - client.delete_configuration(configuration) - except Exception as e: - msg = (_("Failed to delete configuration %(c_group)s: %(e)s") - % {'c_group': parsed_args.configuration_group, 'e': e}) - raise exceptions.CommandError(msg) + if parsed_args.configuration: + for cnf in parsed_args.configuration: + client.delete_configuration( + cnf, ignore_missing=False) class ListDatabaseConfigurationParameters(command.Lister): diff --git a/otcextensions/osclient/rds/v3/instance.py b/otcextensions/osclient/rds/v3/instance.py index 3cff59599..07f829316 100644 --- a/otcextensions/osclient/rds/v3/instance.py +++ b/otcextensions/osclient/rds/v3/instance.py @@ -47,19 +47,20 @@ def set_attributes_for_print(instances): yield instance -def set_attributes_for_print_detail(instance): +def set_attributes_for_print_detail(obj): info = {} # instance._info.copy() - info['name'] = instance.name - info['flavor_id'] = instance['flavor_ref'] - if getattr(instance, 'volume', None): - info['volume'] = instance.volume['size'] - if getattr(instance, 'private_ips', None): - info['private_ips'] = ', '.join(instance.private_ips) - if getattr(instance, 'datastore', None): - info['datastore'] = instance.datastore['type'] - info['datastore_version'] = instance.datastore['version'] - if getattr(instance, 'configuration_id', None): - info['configuration_id'] = instance['configuration_id'] + attr_list = ['id', 'name', 'datastore', 'flavor_ref', 'disk_encryption_id', 'region', 'availability_zone', 'vpc_id', 'subnet_id', 'security_group_id', 'port', 'backup_strategy', 'configuration_id', 'charge_info', 'backup_strategy'] + for attr in dir(obj): + if attr == 'datastore' and getattr(obj, attr): + info['datastore'] = obj.datastore['type'] + info['datastore_version'] = obj.datastore['version'] + elif attr == 'volume' and getattr(obj, attr): + info['volume_type'] = obj.volume['type'] + info['size'] = obj.volume['size'] + elif attr == 'charge_info' and getattr(obj, attr): + info['charge_mode'] = obj.charge_info['charge_mode'] + elif attr in attr_list and getattr(obj, attr): + info[attr] = getattr(obj, attr) return info @@ -171,8 +172,7 @@ def take_action(self, parsed_args): class ShowDatabaseInstance(command.ShowOne): _description = _("Show instance details") - columns = ['ID', 'Name', 'Datastore', 'Datastore Version', 'Status', - 'Flavor ID', 'Size', 'Region'] + columns = ['id', 'name', 'datastore', 'datastore_version', 'flavor Id', 'Volume Type', 'Size', 'disk_encryption_id', 'region', 'availability_zone', 'vpc_id', 'subnet_id', 'security_group_id', 'port', 'backup_strategy', 'configuration_id', 'charge mode'] def get_parser(self, prog_name): parser = super(ShowDatabaseInstance, self).get_parser(prog_name) @@ -206,6 +206,8 @@ class CreateDatabaseInstance(command.ShowOne): _description = _("Creates a new database instance.") + columns = ['id', 'name', 'datastore', 'datastore_version', 'flavor Id', 'Volume Type', 'Size', 'disk_encryption_id', 'region', 'availability_zone', 'vpc_id', 'subnet_id', 'security_group_id', 'port', 'backup_strategy', 'configuration_id', 'charge mode'] + def get_parser(self, prog_name): parser = super(CreateDatabaseInstance, self).get_parser(prog_name) parser.add_argument( @@ -214,9 +216,9 @@ def get_parser(self, prog_name): help=_("Name of the instance."), ) parser.add_argument( - 'flavor', + 'flavor_ref', metavar='', - help=_("A flavor spec_code."), + help=_("flavor spec_code."), ) parser.add_argument( '--size', @@ -236,27 +238,42 @@ def get_parser(self, prog_name): parser.add_argument( '--availability_zone', metavar='', - default=None, help=_("The Zone hint to give to Nova."), ) parser.add_argument( '--datastore', metavar='', - default=None, - help=_("A datastore name"), + help=_("datastore name"), ) parser.add_argument( '--datastore_version', metavar='', - default=None, help=_("datastore version."), ) parser.add_argument( - '--configuration', + '--configuration_id', metavar='', default=None, help=_("ID of the configuration group to attach to the instance."), ) + parser.add_argument( + '--disk_encryption_id', + metavar='', + default=None, + help=_("key ID for disk encryption."), + ) + parser.add_argument( + '--port', + metavar='', + default=None, + type=int, + help=_("Database Port"), + ) + parser.add_argument( + '--password', + metavar='', + help=_("ID of the configuration group to attach to the instance."), + ) parser.add_argument( '--replica_of', metavar='', @@ -267,7 +284,6 @@ def get_parser(self, prog_name): '--region', metavar='', type=str, - default=None, help=argparse.SUPPRESS, ) parser.add_argument( @@ -297,14 +313,14 @@ def get_parser(self, prog_name): '--ha_replication_mode', metavar='', type=str, - # required=True, help=_('replication mode for the standby DB instance') ) parser.add_argument( - '--replica_of', - metavar='', + '--charge_mode', + metavar='', type=str, - help=_('ID or name of an existing instance to replicate from.') + default='postPaid', + help=_('Specifies the billing mode') ) return parser @@ -316,16 +332,12 @@ def take_action(self, parsed_args): attrs = {} args_list = ['name', 'availability_zone', 'configuration_id', 'region', 'vpc_id', 'subnet_id', 'security_group_id', - 'disk_encryption_id', 'port', 'password'] + 'disk_encryption_id', 'port', 'password', 'flavor_ref'] for arg in args_list: if getattr(parsed_args, arg): attrs[arg] = getattr(parsed_args, arg) - volume = None - if parsed_args['size'] and parsed_args['size'] <= 0: - raise exceptions.ValidationError( - _("Volume size '%s' must be an integer and greater than 0.") - % parsed_args.size) - elif parsed_args.size: + volume = {} + if parsed_args.size: volume = {"size": parsed_args.size} if parsed_args.volume_type: volume['type'] = parsed_args.volume_type @@ -344,9 +356,15 @@ def take_action(self, parsed_args): 'replication_mode': parsed_args.ha_replication_mode } attrs['ha'] = ha - instance = client.create_instance(**attrs) - instance = set_attributes_for_print_detail(instance) - return zip(*sorted(six.iteritems(instance))) + if parsed_args.charge_mode: + attrs['charge_info'] = { + 'charge_mode': parsed_args.charge_mode + } + obj = client.create_instance(**attrs) + + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + return (display_columns, data) class DeleteDatabaseInstance(command.Command): diff --git a/otcextensions/sdk/rds/v3/_proxy.py b/otcextensions/sdk/rds/v3/_proxy.py index a2169b0ed..7d48dea9e 100644 --- a/otcextensions/sdk/rds/v3/_proxy.py +++ b/otcextensions/sdk/rds/v3/_proxy.py @@ -13,7 +13,7 @@ from otcextensions.sdk import sdk_proxy from openstack import proxy from otcextensions.sdk.rds.v3 import backup as _backup -#from otcextensions.sdk.rds.v3 import configuration as _configuration +from otcextensions.sdk.rds.v3 import configuration as _configuration from otcextensions.sdk.rds.v3 import datastore as _datastore from otcextensions.sdk.rds.v3 import flavor as _flavor from otcextensions.sdk.rds.v3 import instance as _instance @@ -152,17 +152,17 @@ def create_instance(self, **attrs): """Create a new instance from attributes :param dict attrs: Keyword arguments which will be used to create - a :class:`~otcextensions.sdk.rds.v1.instance.Instance`, + a :class:`~otcextensions.sdk.rds.v3.instance.Instance`, comprised of the properties on the Instance class. :returns: The results of server creation - :rtype: :class:`~otcextensions.sdk.rds.v1.instance.Instance` + :rtype: :class:`~otcextensions.sdk.rds.v3.instance.Instance` """ return self._create(_instance.Instance, # project_id=self.session.get_project_id(), # endpoint_override=self.get_os_endpoint(), headers=self.get_os_headers(True), - **attrs) + prepend_key=False, **attrs) def delete_instance(self, instance, ignore_missing=True): """Delete an instance @@ -192,7 +192,7 @@ def find_instance(self, name_or_id, ignore_missing=True): raised when the resource does not exist. When set to ``True``, None will be returned when attempting to find a nonexistent resource. - :returns: One :class:`~otcextensions.sdk.rds.v1.instance.Instance` + :returns: One :class:`~otcextensions.sdk.rds.v3.instance.Instance` or None """ return self._find(_instance.Instance, name_or_id, @@ -203,7 +203,7 @@ def instances(self, **params): """Return a generator of instances :returns: A generator of instance objects - :rtype: :class:`~otcextensions.sdk.rds.v1.instance.Instance` + :rtype: :class:`~otcextensions.sdk.rds.v3.instance.Instance` """ return self._list( _instance.Instance, paginated=False, @@ -214,13 +214,13 @@ def update_instance(self, instance, **attrs): """Update a instance :param instance: Either the id of a instance or a - :class:`~otcextensions.sdk.rds.v1.instance.Instance` + :class:`~otcextensions.sdk.rds.v3.instance.Instance` instance. :attrs attrs: The attributes to update on the instance represented by ``value``. :returns: The updated instance - :rtype: :class:`~otcextensions.sdk.rds.v1.instance.Instance` + :rtype: :class:`~otcextensions.sdk.rds.v3.instance.Instance` """ return self._update( _instance.Instance, @@ -235,10 +235,10 @@ def restart_instance(self, instance): """Restart instance :param instance: Either the id of a instance or a - :class:`~otcextensions.sdk.rds.v1.instance.Instance` + :class:`~otcextensions.sdk.rds.v3.instance.Instance` instance. :returns: The updated instance - :rtype: :class:`~otcextensions.sdk.rds.v1.instance.Instance` + :rtype: :class:`~otcextensions.sdk.rds.v3.instance.Instance` """ instance = self._get_resource(_instance.Instance, instance) return instance.restart(self) @@ -247,10 +247,10 @@ def restore_instance(self, instance, backup): """Restore instance from backup :param instance: Either the id of a instance or a - :class:`~otcextensions.sdk.rds.v1.instance.Instance` + :class:`~otcextensions.sdk.rds.v3.instance.Instance` instance. :param backup: Either the id of a backup or a - :class:`~otcextensions.sdk.rds.v1.backup.Backup` + :class:`~otcextensions.sdk.rds.v3.backup.Backup` instance. :returns: None @@ -264,16 +264,16 @@ def restore_instance(self, instance, backup): # """Restore instance from backup # # :param instance: Either the id of a instance or a - # :class:`~otcextensions.sdk.rds.v1.instance.Instance` + # :class:`~otcextensions.sdk.rds.v3.instance.Instance` # instance. # :param backup: Either the id of a backup or a - # :class:`~otcextensions.sdk.rds.v1.backup.Backup` + # :class:`~otcextensions.sdk.rds.v3.backup.Backup` # instance. # :attrs attrs: The attributes to update on the instance represented # by ``value``. # # :returns: The updated instance - # :rtype: :class:`~otcextensions.sdk.rds.v1.instance.Instance` + # :rtype: :class:`~otcextensions.sdk.rds.v3.instance.Instance` # """ # # instance = self._get_resource(_instance.Instance, instance) # # backup = self._get_resource(_backup.Backup, backup) @@ -282,94 +282,88 @@ def restore_instance(self, instance, backup): # # return res.create(self) -# # ======= Configurations ======= -# def configurations(self, **attrs): -# """Obtaining a ConfigurationGroup List -# -# :returns: A generator of ConfigurationGroup object -# :rtype: -# :class:`~otcextensions.sdk.rds.v1.configuration.ConfigurationGroup` -# """ -# return self._list( -# _configuration.ConfigurationGroup, -# paginated=False, -# # project_id=self.session.get_project_id(), -# # endpoint_override=self.get_os_endpoint(), -# # headers=self.get_os_headers(), -# ) -# -# def get_configuration(self, cg): -# """Obtaining a ConfigurationGroup -# -# :param parameter_group: The value can be the ID of a ConfigurationGroup -# or a object of -# :class:`~otcextensions.sdk.rds.v1.configuration.ConfigurationGroup`. -# -# :returns: A Parameter Group Object -# :rtype: :class:`~otcextensions.rds.v1.configuration.ConfigurationGroup` -# """ -# return self._get( -# _configuration.ConfigurationGroup, -# cg, -# # project_id=self.session.get_project_id(), -# # endpoint_override=self.get_os_endpoint(), -# headers=self.get_os_headers(True) -# ) -# -# def create_configuration(self, **attrs): -# """Creating a ConfigurationGroup -# -# :param dict **attrs: Dict to overwrite ConfigurationGroup object -# :returns: A Parameter Group Object -# :rtype: -# :class:`~otcextensions.sdk.rds.v1.configuration.ConfigurationGroup` -# """ -# return self._create(_configuration.ConfigurationGroup, -# # project_id=self.session.get_project_id(), -# # endpoint_override=self.get_os_endpoint(), -# # headers=self.get_os_headers(), -# **attrs) -# -# def delete_configuration(self, cg, ignore_missing=True): -# """Deleting a ConfigurationGroup -# -# :param cg: The value can be the ID of a ConfigurationGroup or a -# object of -# :class:`~otcextensions.sdk.rds.v1.configuration.ConfigurationGroup`. -# :param bool ignore_missing: When set to ``False`` -# :class:`~openstack.exceptions.ResourceNotFound` will be -# raised when the Parameter Group does not exist. -# When set to ``True``, no exception will be set when -# attempting to delete a nonexistent Parameter Group. -# -# :returns: None -# :rtype: None -# """ -# self._delete( -# _configuration.ConfigurationGroup, cg, -# ignore_missing=ignore_missing, -# # project_id=self.session.get_project_id(), -# # endpoint_override=self.get_os_endpoint(), -# # headers=self.get_os_headers() -# ) -# -# def find_configuration(self, name_or_id, ignore_missing=True): -# """Find a ConfigurationGroup -# -# :param parameter_group: The value can be the ID of a ConfigurationGroup -# or a object of -# :class:`~otcextensions.sdk.rds.v1.configuration.Configurations`. -# :returns: A Parameter Group Object -# :rtype: -# :class:`~otcextensions.rds.v1.configuration.ConfigurationGroup`. -# """ -# return self._find(_configuration.ConfigurationGroup, name_or_id, -# # project_id=self.session.get_project_id(), -# # endpoint_override=self.get_os_endpoint(), -# # headers=self.get_os_headers(), -# ignore_missing=ignore_missing) -# - # ======= Backups ======= + # ======= Configurations ======= + def configurations(self, **attrs): + """Obtaining a ConfigurationGroup List + + :returns: A generator of ConfigurationGroup object + :rtype: + :class:`~otcextensions.sdk.rds.v3.configuration.ConfigurationGroup` + """ + return self._list( + _configuration.ConfigurationGroup, + paginated=False, + headers=self.get_os_headers(True), + ) + + def get_configuration(self, cg): + """Obtaining a ConfigurationGroup + + :param parameter_group: The value can be the ID of a ConfigurationGroup + or a object of + :class:`~otcextensions.sdk.rds.v3.configuration.ConfigurationGroup`. + + :returns: A Parameter Group Object + :rtype: :class:`~otcextensions.rds.v1.configuration.ConfigurationGroup` + """ + return self._get( + _configuration.ConfigurationGroup, + cg, + # project_id=self.session.get_project_id(), + # endpoint_override=self.get_os_endpoint(), + headers=self.get_os_headers(True) + ) + + def create_configuration(self, **attrs): + """Creating a ConfigurationGroup + + :param dict **attrs: Dict to overwrite ConfigurationGroup object + :returns: A Parameter Group Object + :rtype: + :class:`~otcextensions.sdk.rds.v3.configuration.ConfigurationGroup` + """ + return self._create(_configuration.ConfigurationGroup, + headers=self.get_os_headers(True), + prepend_key=False, **attrs) + + def delete_configuration(self, cg, ignore_missing=True): + """Deleting a ConfigurationGroup + + :param cg: The value can be the ID of a ConfigurationGroup or a + object of + :class:`~otcextensions.sdk.rds.v3.configuration.ConfigurationGroup`. + :param bool ignore_missing: When set to ``False`` + :class:`~openstack.exceptions.ResourceNotFound` will be + raised when the Parameter Group does not exist. + When set to ``True``, no exception will be set when + attempting to delete a nonexistent Parameter Group. + + :returns: None + :rtype: None + """ + self._delete( + _configuration.ConfigurationGroup, cg, + ignore_missing=ignore_missing, + headers=self.get_os_headers(True) + ) + + def find_configuration(self, name_or_id, ignore_missing=True): + """Find a ConfigurationGroup + + :param parameter_group: The value can be the ID of a ConfigurationGroup + or a object of + :class:`~otcextensions.sdk.rds.v3.configuration.Configurations`. + :returns: A Parameter Group Object + :rtype: + :class:`~otcextensions.rds.v1.configuration.ConfigurationGroup`. + """ + return self._find(_configuration.ConfigurationGroup, name_or_id, + # project_id=self.session.get_project_id(), + # endpoint_override=self.get_os_endpoint(), + # headers=self.get_os_headers(), + ignore_missing=ignore_missing) + + # ======= Backups ======= def backups(self, **params): """List Backups @@ -384,7 +378,7 @@ def create_backup(self, **attrs): """Create a backups of instance :returns: A new backup object - :rtype: :class:`~otcextensions.sdk.rds.v1.backup.Backup` + :rtype: :class:`~otcextensions.sdk.rds.v3.backup.Backup` """ return self._create(_backup.Backup, headers=self.get_os_headers(True), @@ -415,9 +409,9 @@ def delete_backup(self, backup, ignore_missing=True): # """Obtaining a backup policy of the instance # # :param instance: This parameter can be either the ID of an instance -# or a :class:`~openstack.sdk.rds.v1.instance.Instance` +# or a :class:`~openstack.sdk.rds.v3.instance.Instance` # :returns: A Backup policy -# :rtype: :class:`~otcextensions.sdk.rds.v1.configuration.BackupPolicy` +# :rtype: :class:`~otcextensions.sdk.rds.v3.configuration.BackupPolicy` # # """ # instance = self._get_resource(_instance.Instance, instance) @@ -434,7 +428,7 @@ def delete_backup(self, backup, ignore_missing=True): # """Sets the backup policy of the instance # # :param instance: This parameter can be either the ID of an instance -# or a :class:`~openstack.sdk.rds.v1.instance.Instance` +# or a :class:`~openstack.sdk.rds.v3.instance.Instance` # :param dict attrs: The attributes to update on the backup_policy # represented by ``backup_policy``. # diff --git a/otcextensions/sdk/rds/v3/configuration.py b/otcextensions/sdk/rds/v3/configuration.py new file mode 100644 index 000000000..75bb85884 --- /dev/null +++ b/otcextensions/sdk/rds/v3/configuration.py @@ -0,0 +1,194 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from openstack import _log +from openstack import exceptions +from openstack import resource +from openstack import utils + +from otcextensions.sdk import sdk_resource + +_logger = _log.setup_logging('openstack') + + +# class InstanceConfiguration(sdk_resource.Resource): +# # TODO(agoncharov) most likely useless +# +# base_path = '/%(project_id)s/instances/%(instanceId)s/configuration' +# resource_key = 'instance' +# service = rds_service.RdsService() +# +# # capabilities +# allow_get = True +# +# # Properties +# # Instance of the configuration +# instanceId = resource.URI("instanceId") +# #: Configuration list, key value pairs +# #: *Type:dict* +# configuration = resource.Body('configuration', type=dict) +# +# +# class Parameter(sdk_resource.Resource): +# # TODO(agoncharov) most likely useless +# +# base_path = '/%(project_id)s/datastores/versions/' +# '%(datastore_version_id)s/parameters/' +# resources_key = 'configuration-parameters' +# service = rds_service.RdsService() +# +# # capabilities +# allow_list = True +# allow_get = True +# +# # Properties +# #: Parameter name +# name = resource.Body('name', alternate_id=True) +# #: Minimum value of the parameter +# #: *Type: int* +# min = resource.Body('min', type=int) +# #: Maximum value of the parameter +# #: *Type: int* +# max = resource.Body('max', type=int) +# #: Parameter type +# type = resource.Body('type') +# #: Value range +# value_range = resource.Body('value_range') +# #: Description +# description = resource.Body('description') +# #: Require restart or not +# #: *Type: bool* +# restart_required = resource.Body('restart_required', type=bool) +# #: Datastore version id +# datastore_version_id = resource.Body('datastore_version_id') + + +class ConfigurationGroup(sdk_resource.Resource): + + base_path = '/configurations' + resource_key = 'configuration' + resources_key = 'configurations' + + # capabilities + allow_create = True + allow_delete = True + allow_update = True + allow_get = True + allow_list = True + + #: Id of the configuration group + #: *Type:str* + id = resource.Body('id') + #: Name of the configuration group + #: *Type:str* + name = resource.Body('name') + #: Data store information + #: *Type: dict* + datastore = resource.Body('datastore', type=dict) + #: Description of the configuration group + #: *Type:str* + description = resource.Body('description') + #: name of Datastore version + #: *Type:str* + datastore_version_name = resource.Body('datastore_version_name') + #: name of Datastore + #: *Type:str* + datastore_name = resource.Body('datastore_name') + #: Date of created + #: *Type:str* + created = resource.Body('created') + #: Date of updated + #: *Type:str* + updated = resource.Body('updated') + #: Indicates whether the parameter group is created by users. + #: *Type:bool* + user_defined = resource.Body('user_defined') + #: parameter group values defined by users + #: *Type: dict* + values = resource.Body('values', type=dict) + + + def get_associated_instances(self, session, endpoint_override=None): + """Get associated instancs + + :param session: session (adapter) + :param endpoint_override: optional endpoint_override + + :returns: list of instance id/name dicts with instances + with this configuration group + :rtype: list of dicts + """ + + request = self._prepare_request() + session = self._get_session(session) + + if not endpoint_override: + if getattr(self, 'endpoint_override', None): + # If we have internal endpoint_override - use it + endpoint_override = self.endpoint_override + + # Build additional arguments to the DELETE call + args = self._prepare_override_args( + endpoint_override=endpoint_override, + request_headers=request.headers, + additional_headers={'Content-Type': 'application/json'} + ) + + # URL is a subpoin + url = utils.urljoin(request.url, 'instances') + + resp = session.get(url, **args) + resp = resp.json() + + if resp: + return resp['instances'] + + def _translate_response(self, response, has_body=None, error_message=None): + """Given a KSA response, inflate this instance with its data + + 'DELETE' operations don't return a body, so only try to work + with a body when has_body is True. + + This method updates attributes that correspond to headers + and body on this instance and clears the dirty set. + """ + if has_body is None: + has_body = self.has_body + # exceptions.raise_from_response(response, error_message=error_message) + if has_body: + body = response.json() + + errCode = body.get('errCode', None) + if errCode and errCode == 'RDS.0041': + if self.resource_key and self.resource_key in body: + body = body[self.resource_key] + + body = self._consume_body_attrs(body) + self._body.attributes.update(body) + self._body.clean() + elif errCode and errCode == 'RDS.0028': + raise exceptions.NotFoundException('Resource not found') + elif errCode and errCode != 'RDS.0041': + _logger.error('error during service invokation %s' % errCode) + raise exceptions.SDKException( + body.get('externalMessage', body) + ) + elif not errCode: + if self.resource_key and self.resource_key in body: + body = body[self.resource_key] + + body = self._consume_body_attrs(body) + self._body.attributes.update(body) + self._body.clean() + + headers = self._consume_header_attrs(response.headers) + self._header.attributes.update(headers) + self._header.clean() diff --git a/otcextensions/sdk/rds/v3/instance.py b/otcextensions/sdk/rds/v3/instance.py index 79daacee2..314e9857d 100644 --- a/otcextensions/sdk/rds/v3/instance.py +++ b/otcextensions/sdk/rds/v3/instance.py @@ -80,6 +80,9 @@ class Instance(sdk_resource.Resource): #: Flavor ID #: *Type:uuid* flavor_ref = resource.Body('flavor_ref') + #: Availability Zone + #: *Type:str* + availability_zone = resource.Body('availability_zone') #: Volume information #: *Type: dict* volume = resource.Body('volume', type=dict) @@ -103,6 +106,9 @@ class Instance(sdk_resource.Resource): #: Disk Encryption Key Id #: *Type:str* disk_encryption_id = resource.Body('disk_encryption_id') + #: Database Password + #: *Type:str* + password = resource.Body('password') #: Time Zone #: *Type:str* time_zone = resource.Body('time_zone') @@ -110,3 +116,6 @@ class Instance(sdk_resource.Resource): #: DB instance. #: *Type:str* replication_mode = resource.Body('replication_mode') + #: Charge Info + #: *Type: dict* + backup_strategy = resource.Body('charge_info', type=dict) diff --git a/setup.cfg b/setup.cfg index eb519757b..22ff9930a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -96,6 +96,9 @@ openstack.rds.v3 = rds_flavor_list = otcextensions.osclient.rds.v3.flavor:ListDatabaseFlavors rds_datastore_list = otcextensions.osclient.rds.v3.datastore:ListDatastores rds_datastore_version_list = otcextensions.osclient.rds.v3.datastore:ListDatastoreVersions + rds_configuration_list = otcextensions.osclient.rds.v3.configuration:ListConfigurations + rds_configuration_create = otcextensions.osclient.rds.v3.configuration:CreateConfiguration + rds_configuration_delete = otcextensions.osclient.rds.v3.configuration:DeleteConfiguration rds_instance_list = otcextensions.osclient.rds.v3.instance:ListDatabaseInstances rds_instance_show = otcextensions.osclient.rds.v3.instance:ShowDatabaseInstance rds_instance_create = otcextensions.osclient.rds.v3.instance:CreateDatabaseInstance From 6a8605bec7849d65595942a6bfec10e991d8814e Mon Sep 17 00:00:00 2001 From: vineet-pruthi Date: Thu, 26 Sep 2019 08:41:53 +0000 Subject: [PATCH 04/65] Adding RDSv3 Instance and Backup functionalities --- otcextensions/osclient/rds/v3/backup.py | 28 +-- otcextensions/osclient/rds/v3/instance.py | 86 +++++-- otcextensions/sdk/rds/v3/_proxy.py | 8 +- .../tests/unit/osclient/rds/v3/fakes.py | 171 +++++++------- .../tests/unit/osclient/rds/v3/test_backup.py | 184 +++++++++++++++ .../unit/osclient/rds/v3/test_instance.py | 204 +++++++++++++++++ .../tests/unit/sdk/rds/v3/test_backup.py | 210 ++++++++++++++++++ .../tests/unit/sdk/rds/v3/test_instance.py | 197 ++++++++++++++++ setup.cfg | 3 + 9 files changed, 967 insertions(+), 124 deletions(-) create mode 100644 otcextensions/tests/unit/osclient/rds/v3/test_backup.py create mode 100644 otcextensions/tests/unit/osclient/rds/v3/test_instance.py create mode 100644 otcextensions/tests/unit/sdk/rds/v3/test_backup.py create mode 100644 otcextensions/tests/unit/sdk/rds/v3/test_instance.py diff --git a/otcextensions/osclient/rds/v3/backup.py b/otcextensions/osclient/rds/v3/backup.py index 05da4250a..1a2589547 100644 --- a/otcextensions/osclient/rds/v3/backup.py +++ b/otcextensions/osclient/rds/v3/backup.py @@ -40,7 +40,7 @@ def set_attributes_for_print_detail(obj): class ListBackup(command.Lister): _description = _("List database backups/snapshots") - columns = ('Id', 'name', 'type', 'instance_id', 'datastore_type', 'datastore_version') + columns = ('ID', 'Name', 'Type', 'Instance Id', 'Datastore Type', 'Datastore Version') def get_parser(self, prog_name): parser = super(ListBackup, self).get_parser(prog_name) @@ -86,14 +86,12 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.rds attrs = {} - args_list = ['instance', 'backup_id', 'backup_type', 'offset', 'limit', 'begin_time', 'end_time'] + args_list = ['backup_id', 'backup_type', 'offset', 'limit', 'begin_time', 'end_time'] for arg in args_list: - if arg == 'instance': - attrs['instance_id'] = client.find_instance(parsed_args.instance).id - elif getattr(parsed_args, arg): + if getattr(parsed_args, arg): attrs[arg] = getattr(parsed_args, arg) - data = client.backups(**attrs) + data = client.backups(parsed_args.instance, **attrs) return ( self.columns, @@ -111,17 +109,16 @@ class CreateBackup(command.ShowOne): def get_parser(self, prog_name): parser = super(CreateBackup, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help=_('Name for the backup') + ) parser.add_argument( 'instance', metavar='', help=_('ID or Name of the instance to create backup from') ) - parser.add_argument( - '--name', - metavar='', - required=True, - help=_('Name for the backup') - ) parser.add_argument( '--description', metavar='', @@ -139,10 +136,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.rds - attrs = {} - attrs['instance_id'] = client.find_instance(parsed_args.instance).id - - attrs['name'] = parsed_args.name + attrs = { 'name': parsed_args.name } if parsed_args.description: attrs['description'] = parsed_args.description if parsed_args.databases: @@ -152,7 +146,7 @@ def take_action(self, parsed_args): for db_name in databases: attrs['databases'].append({'name': db_name}) - data = client.create_backup(**attrs) + data = client.create_backup(parsed_args.instance, **attrs) return ( self.columns, diff --git a/otcextensions/osclient/rds/v3/instance.py b/otcextensions/osclient/rds/v3/instance.py index 07f829316..0d8a9d4ee 100644 --- a/otcextensions/osclient/rds/v3/instance.py +++ b/otcextensions/osclient/rds/v3/instance.py @@ -49,7 +49,22 @@ def set_attributes_for_print(instances): def set_attributes_for_print_detail(obj): info = {} # instance._info.copy() - attr_list = ['id', 'name', 'datastore', 'flavor_ref', 'disk_encryption_id', 'region', 'availability_zone', 'vpc_id', 'subnet_id', 'security_group_id', 'port', 'backup_strategy', 'configuration_id', 'charge_info', 'backup_strategy'] + attr_list = [ + 'id', + 'name', + 'datastore', + 'flavor_ref', + 'disk_encryption_id', + 'region', + 'availability_zone', + 'vpc_id', + 'subnet_id', + 'security_group_id', + 'port', + 'backup_strategy', + 'configuration_id', + 'charge_info', + 'backup_strategy'] for attr in dir(obj): if attr == 'datastore' and getattr(obj, attr): info['datastore'] = obj.datastore['type'] @@ -150,12 +165,19 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.rds - args_list = ['name', 'id', 'vpc_id', 'subnet_id', 'type', 'datastore_type', 'offset'] + args_list = [ + 'name', + 'id', + 'vpc_id', + 'subnet_id', + 'type', + 'datastore_type', + 'offset'] attrs = {} for arg in args_list: if getattr(parsed_args, arg): attrs[arg] = getattr(parsed_args, arg) - + data = client.instances(**attrs) if data: data = set_attributes_for_print(data) @@ -172,7 +194,24 @@ def take_action(self, parsed_args): class ShowDatabaseInstance(command.ShowOne): _description = _("Show instance details") - columns = ['id', 'name', 'datastore', 'datastore_version', 'flavor Id', 'Volume Type', 'Size', 'disk_encryption_id', 'region', 'availability_zone', 'vpc_id', 'subnet_id', 'security_group_id', 'port', 'backup_strategy', 'configuration_id', 'charge mode'] + columns = [ + 'id', + 'name', + 'datastore', + 'datastore_version', + 'flavor Ref', + 'Volume Type', + 'Size', + 'disk_encryption_id', + 'region', + 'availability_zone', + 'vpc_id', + 'subnet_id', + 'security_group_id', + 'port', + 'backup_strategy', + 'configuration_id', + 'charge mode'] def get_parser(self, prog_name): parser = super(ShowDatabaseInstance, self).get_parser(prog_name) @@ -187,16 +226,6 @@ def take_action(self, parsed_args): client = self.app.client_manager.rds obj = client.find_instance(parsed_args.instance) - # data = set_attributes_for_print_detail(obj) - # - # return ( - # self.columns, - # (utils.get_dict_properties( - # s, - # self.columns, - # ) for s in data) - # ) - display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) return (display_columns, data) @@ -206,7 +235,24 @@ class CreateDatabaseInstance(command.ShowOne): _description = _("Creates a new database instance.") - columns = ['id', 'name', 'datastore', 'datastore_version', 'flavor Id', 'Volume Type', 'Size', 'disk_encryption_id', 'region', 'availability_zone', 'vpc_id', 'subnet_id', 'security_group_id', 'port', 'backup_strategy', 'configuration_id', 'charge mode'] + columns = [ + 'id', + 'name', + 'datastore', + 'datastore_version', + 'flavor Id', + 'Volume Type', + 'Size', + 'disk_encryption_id', + 'region', + 'availability_zone', + 'vpc_id', + 'subnet_id', + 'security_group_id', + 'port', + 'backup_strategy', + 'configuration_id', + 'charge mode'] def get_parser(self, prog_name): parser = super(CreateDatabaseInstance, self).get_parser(prog_name) @@ -216,7 +262,8 @@ def get_parser(self, prog_name): help=_("Name of the instance."), ) parser.add_argument( - 'flavor_ref', + '--flavor', + dest='flavor_ref', metavar='', help=_("flavor spec_code."), ) @@ -287,7 +334,7 @@ def get_parser(self, prog_name): help=argparse.SUPPRESS, ) parser.add_argument( - '--network_id', + '--vpc_id', dest='vpc_id', metavar='', type=str, @@ -331,8 +378,8 @@ def take_action(self, parsed_args): attrs = {} args_list = ['name', 'availability_zone', 'configuration_id', - 'region', 'vpc_id', 'subnet_id', 'security_group_id', - 'disk_encryption_id', 'port', 'password', 'flavor_ref'] + 'region', 'vpc_id', 'subnet_id', 'security_group_id', + 'disk_encryption_id', 'port', 'password', 'flavor_ref'] for arg in args_list: if getattr(parsed_args, arg): attrs[arg] = getattr(parsed_args, arg) @@ -391,7 +438,6 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) - class ForceDeleteDatabaseInstance(command.Command): _description = _("Force delete an instance.") diff --git a/otcextensions/sdk/rds/v3/_proxy.py b/otcextensions/sdk/rds/v3/_proxy.py index 7d48dea9e..27022f3d6 100644 --- a/otcextensions/sdk/rds/v3/_proxy.py +++ b/otcextensions/sdk/rds/v3/_proxy.py @@ -364,22 +364,26 @@ def find_configuration(self, name_or_id, ignore_missing=True): ignore_missing=ignore_missing) # ======= Backups ======= - def backups(self, **params): + def backups(self, instance, **params): """List Backups :returns: A generator of backup :rtype: :class:`~otcextensions.sdk.rds.v3.backup.Backup` """ + instance = self._get_resource(_instance.Instance, instance) + params['instance_id'] = instance.id return self._list( _backup.Backup, paginated=False, **params, headers=self.get_os_headers(True) ) - def create_backup(self, **attrs): + def create_backup(self, instance, **attrs): """Create a backups of instance :returns: A new backup object :rtype: :class:`~otcextensions.sdk.rds.v3.backup.Backup` """ + instance = self._get_resource(_instance.Instance, instance) + attrs['instance_id'] = instance.id return self._create(_backup.Backup, headers=self.get_os_headers(True), prepend_key=False, **attrs diff --git a/otcextensions/tests/unit/osclient/rds/v3/fakes.py b/otcextensions/tests/unit/osclient/rds/v3/fakes.py index e3b2c9c7e..741c3c43b 100644 --- a/otcextensions/tests/unit/osclient/rds/v3/fakes.py +++ b/otcextensions/tests/unit/osclient/rds/v3/fakes.py @@ -24,8 +24,8 @@ #from otcextensions.sdk.rds.v3.configuration import ConfigurationGroup from otcextensions.sdk.rds.v3.datastore import Datastore from otcextensions.sdk.rds.v3 import flavor -#from otcextensions.sdk.rds.v3.instance import Instance -#from otcextensions.sdk.rds.v3 import backup +from otcextensions.sdk.rds.v3.instance import Instance +from otcextensions.sdk.rds.v3 import backup class TestRds(utils.TestCommand): @@ -39,7 +39,7 @@ def setUp(self): self.datastore_mock = FakeDatastore self.flavor_mock = FakeFlavor - #self.instance_mock = FakeInstance + self.instance_mock = FakeInstance #self.configuration_mock = FakeConfiguration @@ -158,85 +158,86 @@ def generate(cls): # return objects # # -# class FakeInstance(object): -# """Fake one or more Instance.""" -# -# @staticmethod -# def create_one(attrs=None, methods=None): -# """Create a fake Configuration. -# -# :param Dictionary attrs: -# A dictionary with all attributes -# :param Dictionary methods: -# A dictionary with all methods -# :return: -# A FakeResource object, with id, name, metadata, and so on -# """ -# attrs = attrs or {} -# methods = methods or {} -# -# # Set default attributes. -# object_info = { -# 'id': 'id-' + uuid.uuid4().hex, -# 'name': 'name-' + uuid.uuid4().hex, -# 'status': 'status-' + uuid.uuid4().hex, -# 'datastore': { -# 'type': 'datastore-' + uuid.uuid4().hex, -# 'version': 'version-' + uuid.uuid4().hex, -# }, -# 'flavor': {'id': uuid.uuid4().hex}, -# 'volume': { -# 'type': 'type' + uuid.uuid4().hex, -# 'size': random.randint(1, 10280), -# }, -# 'region': 'region' + uuid.uuid4().hex, -# } -# -# # Overwrite default attributes. -# # object_info.update(attrs) -# return Instance(**object_info) -# -# @staticmethod -# def create_multiple(attrs=None, methods=None, count=2): -# """Create multiple fake Configuration. -# -# :param Dictionary attrs: -# A dictionary with all attributes -# :param Dictionary methods: -# A dictionary with all methods -# :param int count: -# The number of servers to fake -# :return: -# A list of FakeResource objects faking the servers -# """ -# objects = [] -# for i in range(0, count): -# objects.append( -# FakeInstance.create_one(attrs, methods) -# ) -# -# return objects -# -# -# class FakeBackup(test_base.Fake): -# """Fake one or more VBS Policy""" -# -# @classmethod -# def generate(cls): -# object_info = { -# 'id': 'id-' + uuid.uuid4().hex, -# 'name': 'name-' + uuid.uuid4().hex, -# 'description': uuid.uuid4().hex, -# 'datastore': { -# 'type': 'datastore-' + uuid.uuid4().hex, -# 'version': 'version-' + uuid.uuid4().hex, -# }, -# 'instance_id': 'instance_id-' + uuid.uuid4().hex, -# 'size': random.randint(0, 100), -# 'status': random.choice(['BUILDING', 'COMPLETED', 'FAILED', -# 'DELETING']), -# 'created': uuid.uuid4().hex, -# 'updated': uuid.uuid4().hex, -# } -# obj = backup.Backup.existing(**object_info) -# return obj +class FakeInstance(object): + """Fake one or more Instance.""" + + @staticmethod + def create_one(attrs=None, methods=None): + """Create a fake Configuration. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object, with id, name, metadata, and so on + """ + attrs = attrs or {} + methods = methods or {} + + # Set default attributes. + object_info = { + 'id': 'id-' + uuid.uuid4().hex, + 'name': 'name-' + uuid.uuid4().hex, + 'status': 'status-' + uuid.uuid4().hex, + 'datastore': { + 'type': 'datastore-' + uuid.uuid4().hex, + 'version': 'version-' + uuid.uuid4().hex, + }, + 'flavor_ref': {'id': uuid.uuid4().hex}, + 'volume': { + 'type': 'type' + uuid.uuid4().hex, + 'size': random.randint(1, 10280), + }, + 'region': 'region' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + # object_info.update(attrs) + return Instance(**object_info) + + @staticmethod + def create_multiple(attrs=None, methods=None, count=2): + """Create multiple fake Configuration. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of servers to fake + :return: + A list of FakeResource objects faking the servers + """ + objects = [] + for i in range(0, count): + objects.append( + FakeInstance.create_one(attrs, methods) + ) + + return objects + + +class FakeBackup(test_base.Fake): + """Fake one or more VBS Policy""" + + @classmethod + def generate(cls): + object_info = { + 'id': 'id-' + uuid.uuid4().hex, + 'name': 'name-' + uuid.uuid4().hex, + 'description': uuid.uuid4().hex, + 'datastore': { + 'type': 'datastore-' + uuid.uuid4().hex, + 'version': 'version-' + uuid.uuid4().hex, + }, + 'instance_id': 'instance_id-' + uuid.uuid4().hex, + 'size': random.randint(0, 100), + 'status': random.choice(['BUILDING', 'COMPLETED', 'FAILED', + 'DELETING']), + 'type': random.choice(['auto', 'manual']), + 'begin_time': uuid.uuid4().hex, + 'end_time': uuid.uuid4().hex + } + obj = backup.Backup.existing(**object_info) + return obj diff --git a/otcextensions/tests/unit/osclient/rds/v3/test_backup.py b/otcextensions/tests/unit/osclient/rds/v3/test_backup.py new file mode 100644 index 000000000..731937740 --- /dev/null +++ b/otcextensions/tests/unit/osclient/rds/v3/test_backup.py @@ -0,0 +1,184 @@ +# Copyright 2013 Nebula Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +import mock + +from otcextensions.osclient.rds.v3 import backup +from otcextensions.tests.unit.osclient.rds.v3 import fakes as fakes + + +class TestList(fakes.TestRds): + + _objects = fakes.FakeBackup.create_multiple(3) + + columns = ('ID', 'Name', 'Type', 'Instance Id', 'Datastore Type', + 'Datastore Version') + + data = [] + + for s in _objects: + data.append(( + s.id, + s.name, + s.type, + s.instance_id, + s.datastore['type'], + s.datastore['version'], + )) + + def setUp(self): + super(TestList, self).setUp() + + self.cmd = backup.ListBackup(self.app, None) + + self.client.backups = mock.Mock() + + def test_list_default(self): + arglist = ['test-instance' + ] + + verifylist = [('instance', 'test-instance'), + ] + # Verify cm is triggereg with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Set the response + self.client.backups.side_effect = [ + self._objects + ] + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + + self.client.backups.assert_called_once_with('test-instance') + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestCreate(fakes.TestRds): + + _obj = fakes.FakeBackup.create_one() + + columns = ('ID', 'Name', 'type', 'instance_id', + 'status', 'begin_time', 'databases') + + data = ( + _obj.id, + _obj.name, + _obj.instance_id, + _obj.status, + _obj.begin_time, + _obj.type, + _obj.databases, + ) + + def setUp(self): + super(TestCreate, self).setUp() + + self.cmd = backup.CreateBackup(self.app, None) + + self.client.create_backup = mock.Mock() + self.app.client_manager.rds.find_instance = mock.Mock() + + def test_create(self): + arglist = [ + 'test-backup', + 'test-instance', + '--description', 'test description', + ] + verifylist = [ + ('name', 'test-backup'), + ('instance', 'test-instance'), + ('description', 'test description'), + ] + # Verify cm is triggereg with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Set the response + self.client.create_backup.side_effect = [ + self._obj + ] + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + + self.client.create_backup.assert_called_with( + 'test-instance', + description='test description', + name='test-backup', + ) + self.assertEqual(self.columns, columns) + + +class TestDelete(fakes.TestRds): + + def setUp(self): + super(TestDelete, self).setUp() + + self.cmd = backup.DeleteBackup(self.app, None) + + self.client.delete_backup = mock.Mock() + + def test_delete_default(self): + arglist = [ + 'bck1', + ] + + verifylist = [ + ('backup', ['bck1']), + ] + + # Verify cm is triggereg with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Set the response + self.client.delete_backup.side_effect = [ + {} + ] + + # Trigger the action + self.cmd.take_action(parsed_args) + + self.client.delete_backup.assert_called_once_with( + backup='bck1', + ignore_missing=False + ) + + def test_delete_multiple(self): + arglist = [ + 'bck1', + 'bck2' + ] + + verifylist = [ + ('backup', ['bck1', 'bck2']), + ] + + # Verify cm is triggereg with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Set the response + self.client.delete_backup.side_effect = [{}, {}] + + # Trigger the action + self.cmd.take_action(parsed_args) + + calls = [ + mock.call(backup='bck1', ignore_missing=False), + mock.call(backup='bck2', ignore_missing=False), + ] + + self.client.delete_backup.assert_has_calls(calls) + self.assertEqual(2, self.client.delete_backup.call_count) diff --git a/otcextensions/tests/unit/osclient/rds/v3/test_instance.py b/otcextensions/tests/unit/osclient/rds/v3/test_instance.py new file mode 100644 index 000000000..9330822ab --- /dev/null +++ b/otcextensions/tests/unit/osclient/rds/v3/test_instance.py @@ -0,0 +1,204 @@ +# Copyright 2013 Nebula Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +import mock +# from osc_lib import utils as common_utils + +from otcextensions.osclient.rds.v3 import instance +from otcextensions.tests.unit.osclient.rds.v3 import fakes as rds_fakes + + +class TestListDatabaseInstances(rds_fakes.TestRds): + + column_list_headers = ['ID', 'Name', 'Datastore Type', 'Datastore Version', 'Status', + 'Flavor_ref', 'Type', 'Size', 'Region'] + + def setUp(self): + super(TestListDatabaseInstances, self).setUp() + + self.cmd = instance.ListDatabaseInstances(self.app, None) + + self.app.client_manager.rds.instances = mock.Mock() + + self.objects = self.instance_mock.create_multiple(3) + self.object_data = [] + for s in self.objects: + self.object_data.append(( + s.id, + s.name, + s.datastore['type'], + s.datastore['version'], + s.status, + s.flavor_ref, + s.type, + s.volume['size'], + s.region + )) + + def test_list(self): + arglist = [ + ] + + verifylist = [ + ] + + # Verify cm is triggereg with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Set the response + self.app.client_manager.rds.instances.side_effect = [ + self.objects + ] + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + + self.app.client_manager.rds.instances.assert_called() + + self.assertEqual(self.column_list_headers, columns) + self.assertEqual(tuple(self.object_data), tuple(data)) + + +class TestShowDatabaseInstance(rds_fakes.TestRds): + + column_list_headers = [ + 'datastore', 'flavor_ref', 'id', 'name', 'region' + 'status', 'volume'] + + def setUp(self): + super(TestShowDatabaseInstance, self).setUp() + + self.cmd = instance.ShowDatabaseInstance(self.app, None) + + self.app.client_manager.rds.find_instance = mock.Mock() + + self.object = self.instance_mock.create_one() + + self.object_data = ( + self.object.datastore, + self.object.flavor_ref, + self.object.id, + self.object.name, + self.object.region, + self.object.status, + self.object.volume, + ) + + def test_show(self): + arglist = [ + 'test_instance', + ] + + verifylist = [ + ('instance', 'test_instance'), + ] + + # Verify cm is triggereg with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Set the response + self.app.client_manager.rds.find_instance.side_effect = [ + self.object + ] + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + self.app.client_manager.rds.find_instance.assert_called() + + # self.assertEqual(self.column_list_headers, columns) + self.assertEqual(self.object_data, data) + + +class TestDeleteDatabaseInstance(rds_fakes.TestRds): + + def setUp(self): + super(TestDeleteDatabaseInstance, self).setUp() + + self.cmd = instance.DeleteDatabaseInstance(self.app, None) + + self.app.client_manager.rds.delete_instance = mock.Mock() + + def test_delete(self): + arglist = [ + 'test_obj', + ] + + verifylist = [ + ('instance', 'test_obj'), + ] + + # Verify cm is triggereg with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Set the response + self.app.client_manager.rds.delete_instance.side_effect = [ + True + ] + + # Trigger the action + self.cmd.take_action(parsed_args) + + self.app.client_manager.rds.delete_instance.assert_called() + + +class TestCreateDatabaseInstance(rds_fakes.TestRds): + + def setUp(self): + super(TestCreateDatabaseInstance, self).setUp() + + self.cmd = instance.CreateDatabaseInstance(self.app, None) + + self.app.client_manager.rds.find_flavor = mock.Mock() + self.app.client_manager.rds.create_instance = mock.Mock() + + self.instance = self.instance_mock.create_one() + self.flavor = self.flavor_mock.create_one() + # self.instance.resize = mock.Mock() + + # self.instance._update(project_id='123') + + def test_action(self): + arglist = [ + 'inst_name', '--flavor', 'test-flavor', '--availability_zone', 'test-az-01', '--datastore', 'MySQL', '--datastore_version', '5.7', + '--vpc_id', 'test-vpc-id', '--subnet_id', 'test-subnet-id', '--security_group', 'test-subnet-id', '--volume_type', 'ULTRAHIGH', + '--size', '100', '--password', 'testtest', '--region', 'test-region' + ] + + verifylist = [ + ('name', 'inst_name'), + ('flavor_ref', 'test-flavor'), + ('availability_zone', 'test-az-01'), + ('datastore', 'MySQL'), + ('datastore_version', '5.7'), + ('vpc_id', 'test-vpc-id'), + ('subnet_id', 'test-subnet-id'), + ('security_group_id', 'test-subnet-id'), + ('volume_type', 'ULTRAHIGH'), + ('size', 100), + ('password', 'testtest'), + ('region', 'test-region') + ] + + # Verify cm is triggereg with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Set the response + self.app.client_manager.rds.create_instance.side_effect = [ + self.instance + ] + + # Trigger the action + self.cmd.take_action(parsed_args) + + self.app.client_manager.rds.create_instance.assert_called() diff --git a/otcextensions/tests/unit/sdk/rds/v3/test_backup.py b/otcextensions/tests/unit/sdk/rds/v3/test_backup.py new file mode 100644 index 000000000..04ea61e18 --- /dev/null +++ b/otcextensions/tests/unit/sdk/rds/v3/test_backup.py @@ -0,0 +1,210 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import copy + +from keystoneauth1 import adapter + +import mock + +from openstack.tests.unit import base + +from otcextensions.sdk.rds.v3 import backup + +RDS_HEADERS = { + 'content-type': 'application/json', + 'x-language': 'en-us' +} + +# PROJECT_ID = '23' +IDENTIFIER = 'IDENTIFIER' +EXAMPLE = { + 'id': IDENTIFIER, + 'name': '50deafb3e45d451a9406ca146b71fe9a_rds', + 'description': '', + 'begin_time': '2016-08-23T04:01:40', + 'status': 'COMPLETED', + 'instance_id': '4f87d3c4-9e33-482f-b962-e23b30d1a18c', + 'type': 'manual' +} + +EXAMPLE_POLICY = { + 'keepday': 7, + 'starttime': '00:00:00' +} + + +class TestBackup(base.TestCase): + + def setUp(self): + super(TestBackup, self).setUp() + self.sess = mock.Mock(spec=adapter.Adapter) + self.sess.get = mock.Mock() + self.sess.post = mock.Mock() + self.sess.delete = mock.Mock() + self.sess.put = mock.Mock() + # self.sess.get_project_id = mock.Mock(return_value=PROJECT_ID) + self.sot = backup.Backup(**EXAMPLE) + + def test_basic(self): + sot = backup.Backup() + self.assertEqual('backup', sot.resource_key) + self.assertEqual('backups', sot.resources_key) + self.assertEqual('/backups', sot.base_path) + self.assertTrue(sot.allow_list) + self.assertTrue(sot.allow_create) + self.assertFalse(sot.allow_get) + self.assertFalse(sot.allow_update) + self.assertTrue(sot.allow_delete) + + def test_make_it(self): + sot = backup.Backup(**EXAMPLE) + self.assertEqual(IDENTIFIER, sot.id) + self.assertEqual(EXAMPLE['name'], sot.name) + self.assertEqual(EXAMPLE['instance_id'], sot.instance_id) + self.assertEqual(EXAMPLE['description'], sot.description) + self.assertEqual(EXAMPLE['begin_time'], sot.begin_time) + self.assertEqual(EXAMPLE['type'], sot.type) + self.assertEqual(EXAMPLE['status'], sot.status) + + def test_create(self): + + mock_response = mock.Mock() + mock_response.status_code = 200 + mock_response.json.return_value = {'backup': copy.deepcopy(EXAMPLE)} + mock_response.headers = {} + + self.sess.post.return_value = mock_response + + sot = backup.Backup.new( + name='backup_name', + description='descr', + instance_id='some_instance') + + result = sot.create(self.sess, headers=RDS_HEADERS) + + self.sess.post.assert_called_once_with( + '/backups', + headers=RDS_HEADERS, + json={'backup': { + 'instance_id': 'some_instance', + 'description': 'descr', + 'name': 'backup_name'} + } + ) + + self.assertEqual( + backup.Backup( + **EXAMPLE), + result) + + def test_delete(self): + + mock_response = mock.Mock() + mock_response.status_code = 200 + mock_response.json.return_value = {} + mock_response.headers = {} + + self.sess.delete.return_value = mock_response + + sot = backup.Backup( + **EXAMPLE + ) + + sot.delete(self.sess, headers=RDS_HEADERS) + + url = 'backups/%(id)s' % \ + { + 'id': sot.id + } + + self.sess.delete.assert_called_once_with( + url, + headers=RDS_HEADERS + ) + +# def test_policy_basic(self): +# sot = backup.BackupPolicy() +# self.assertEqual('policy', sot.resource_key) +# self.assertEqual(None, sot.resources_key) +# self.assertEqual('/instances/%(instance_id)s/' +# 'backups/policy', sot.base_path) +# self.assertFalse(sot.allow_list) +# self.assertFalse(sot.allow_create) +# self.assertTrue(sot.allow_get) +# self.assertTrue(sot.allow_update) +# self.assertFalse(sot.allow_delete) +# +# def test_policy_make_it(self): +# sot = backup.BackupPolicy(**EXAMPLE_POLICY) +# self.assertEqual(EXAMPLE_POLICY['keepday'], sot.keepday) +# self.assertEqual(EXAMPLE_POLICY['starttime'], sot.starttime) +# +# def test_policy_update(self): +# mock_response = mock.Mock() +# mock_response.status_code = 200 +# mock_response.json.return_value = { +# 'policy': +# copy.deepcopy(EXAMPLE_POLICY)} +# mock_response.headers = {} +# instance_id = 'instance_id' +# +# self.sess.put.return_value = mock_response +# +# sot = backup.BackupPolicy.new( +# # project_id=PROJECT_ID, +# instance_id=instance_id, +# **EXAMPLE_POLICY) +# +# self.assertIsNone(sot.update(self.sess, headers=RDS_HEADERS)) +# +# url = '/instances/%(instance_id)s/backups/policy' % \ +# { +# 'instance_id': instance_id +# } +# +# self.sess.put.assert_called_once_with( +# url, +# headers=RDS_HEADERS, +# json={'policy': EXAMPLE_POLICY} +# ) +# +# def test_policy_get(self): +# +# mock_response = mock.Mock() +# mock_response.status_code = 200 +# mock_response.json.return_value = { +# 'policy': +# copy.deepcopy(EXAMPLE_POLICY)} +# mock_response.headers = {} +# instance_id = 'instance_id' +# +# self.sess.get.return_value = mock_response +# +# sot = backup.BackupPolicy.new( +# # **EXAMPLE_POLICY, +# # project_id=PROJECT_ID, +# instance_id=instance_id) +# +# res = sot.get(self.sess, requires_id=False, headers=RDS_HEADERS) +# +# url = '/instances/%(instance_id)s/backups/policy' % \ +# { +# 'instance_id': instance_id +# } +# +# self.sess.get.assert_called_once_with( +# url, +# headers=RDS_HEADERS +# ) +# +# self.assertEqual(EXAMPLE_POLICY['keepday'], res.keepday) +# self.assertEqual(EXAMPLE_POLICY['starttime'], res.starttime) diff --git a/otcextensions/tests/unit/sdk/rds/v3/test_instance.py b/otcextensions/tests/unit/sdk/rds/v3/test_instance.py new file mode 100644 index 000000000..77f590273 --- /dev/null +++ b/otcextensions/tests/unit/sdk/rds/v3/test_instance.py @@ -0,0 +1,197 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import copy + +from keystoneauth1 import adapter + +import mock + +from openstack.tests.unit import base + +from otcextensions.sdk.rds.v3 import instance + +# RDS requires those headers to be present in the request, to native API +# otherwise 404 +RDS_HEADERS = { + 'Content-Type': 'application/json', + 'X-Language': 'en-us' +} + +# RDS requires those headers to be present in the request, to OS-compat API +# otherwise 404 +OS_HEADERS = { + 'Content-Type': 'application/json', +} + +# PROJECT_ID = '123' +IDENTIFIER = 'IDENTIFIER' +EXAMPLE = { + "flavor_ref": "rds.mysql.s1.large", + "id": IDENTIFIER, + "status": "ACTIVE", + "name": "mysql-0820-022709-01", + "port": 3306, + "type": "Single", + "region": "eu-de", + "volume": { + "type": "ULTRAHIGH", + "size": 100 + }, + "datastore": { + "type": "mysql", + "version": "5.7" + }, + "created": "2018-08-20T02:33:49+0800", + "updated": "2018-08-20T02:33:50+0800", + "nodes": [{ + "id": "06f1c2ad57604ae89e153e4d27f4e4b8no01", + "name": "mysql-0820-022709-01_node0", + "role": "master", + "status": "ACTIVE", + "availability_zone": "eu-de-01" + }], + "private_ips": ["192.168.0.142"], + "public_ips": ["10.154.219.187", "10.154.219.186"], + "db_user_name": "root", + "vpc_id": "b21630c1-e7d3-450d-907d-39ef5f445ae7", + "subnet_id": "45557a98-9e17-4600-8aec-999150bc4eef", + "security_group_id": "38815c5c-482b-450a-80b6-0a301f2afd97", + "switch_strategy": "", + "backup_strategy": { + "start_time": "19:00-20:00", + "keep_days": 7 + }, + "maintenance_window": "02:00-06:00", + "related_instance": [], + "disk_encryption_id": "", + "time_zone": "" +} + +class TestInstance(base.TestCase): + + def setUp(self): + super(TestInstance, self).setUp() + self.sess = mock.Mock(spec=adapter.Adapter) + self.sess.get = mock.Mock() + # self.sess.get_project_id = mock.Mock(return_value=PROJECT_ID) + self.sot = instance.Instance(**EXAMPLE) + + def test_basic(self): + sot = instance.Instance() + self.assertEqual('instance', sot.resource_key) + self.assertEqual('instances', sot.resources_key) + self.assertEqual('/instances', sot.base_path) + self.assertTrue(sot.allow_list) + self.assertTrue(sot.allow_create) + self.assertFalse(sot.allow_get) + self.assertTrue(sot.allow_update) + self.assertTrue(sot.allow_delete) + + def test_make_it(self): + sot = instance.Instance(**EXAMPLE) + self.assertEqual(IDENTIFIER, sot.id) + self.assertEqual(EXAMPLE['volume'], sot.volume) + self.assertEqual(EXAMPLE['flavor_ref'], sot.flavor_ref) + self.assertEqual(EXAMPLE['datastore'], sot.datastore) + self.assertEqual(EXAMPLE['region'], sot.region) + self.assertEqual(EXAMPLE['port'], sot.port) + self.assertEqual(EXAMPLE['disk_encryption_id'], sot.disk_encryption_id) + self.assertEqual(EXAMPLE['vpc_id'], sot.vpc_id) + self.assertEqual(EXAMPLE['subnet_id'], sot.subnet_id) + self.assertEqual(EXAMPLE['security_group_id'], sot.security_group_id) + self.assertEqual(EXAMPLE['private_ips'], sot.private_ips) + self.assertEqual(EXAMPLE['public_ips'], sot.public_ips) + self.assertEqual(EXAMPLE['nodes'], sot.nodes) + self.assertEqual(EXAMPLE['db_user_name'], sot.db_user_name) + self.assertEqual(EXAMPLE['backup_strategy'], sot.backup_strategy) + self.assertEqual(EXAMPLE['switch_strategy'], sot.switch_strategy) + self.assertEqual(EXAMPLE['maintenance_window'], sot.maintenance_window) + self.assertEqual(EXAMPLE['related_instance'], sot.related_instance) + self.assertEqual(EXAMPLE['disk_encryption_id'], sot.disk_encryption_id) + self.assertEqual(EXAMPLE['time_zone'], sot.time_zone) + + def test_list(self): + + mock_response = mock.Mock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "instances": [{ + "id": IDENTIFIER, + "status": "ACTIVE", + "name": "mysql-0820-022709-01", + "port": 3306, + "type": "Single", + "region": "eu-de", + "datastore": { + "type": "mysql", + "version": "5.7" + }, + "created": "2018-08-20T02:33:49+0800", + "updated": "2018-08-20T02:33:50+0800", + "volume": { + "type": "ULTRAHIGH", + "size": 100 + }, + "nodes": [{ + "id": "06f1c2ad57604ae89e153e4d27f4e4b8no01", + "name": "mysql-0820-022709-01_node0", + "role": "master", + "status": "ACTIVE", + "availability_zone": "eu-de-01" + }], + "private_ips": ["192.168.0.142"], + "public_ips": ["10.154.219.187", "10.154.219.186"], + "db_user_name": "root", + "vpc_id": "b21630c1-e7d3-450d-907d-39ef5f445ae7", + "subnet_id": "45557a98-9e17-4600-8aec-999150bc4eef", + "security_group_id": "38815c5c-482b-450a-80b6-0a301f2afd97", + "flavor_ref": "rds.mysql.s1.large", + "switch_strategy": "", + "backup_strategy": { + "start_time": "19:00-20:00", + "keep_days": 7 + }, + "maintenance_window": "02:00-06:00", + "related_instance": [], + "disk_encryption_id": "", + "time_zone": "" + }], + "total_count": 1 + } + + self.sess.get.return_value = mock_response + + result = list(self.sot.list(self.sess)) + + self.sess.get.assert_called_once_with( + '/instances', + params={}, + ) + + self.assertEqual([instance.Instance(**EXAMPLE)], result) + + +# def test_action_restore(self): +# sot = instance.Instance(**EXAMPLE) +# response = mock.Mock() +# response.json = mock.Mock(return_value='') +# sess = mock.Mock() +# sess.post = mock.Mock(return_value=response) +# backupRef = 'backupRef' +# +# self.assertIsNotNone(sot.restore(sess, backupRef)) +# +# url = ("instances/%(id)s/action" % { +# 'id': IDENTIFIER, +# }) +# body = {'restore': {'backupRef': backupRef}} +# sess.post.assert_called_with(url, json=body, headers={'X-Language': 'en-us'}) diff --git a/setup.cfg b/setup.cfg index 22ff9930a..4be9e08c3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -97,6 +97,9 @@ openstack.rds.v3 = rds_datastore_list = otcextensions.osclient.rds.v3.datastore:ListDatastores rds_datastore_version_list = otcextensions.osclient.rds.v3.datastore:ListDatastoreVersions rds_configuration_list = otcextensions.osclient.rds.v3.configuration:ListConfigurations + rds_configuration_show = otcextensions.osclient.rds.v3.configuration:ShowConfiguration + rds_configuration_update = otcextensions.osclient.rds.v3.configuration:UpdateConfiguration + rds_configuration_apply = otcextensions.osclient.rds.v3.configuration:ApplyConfiguration rds_configuration_create = otcextensions.osclient.rds.v3.configuration:CreateConfiguration rds_configuration_delete = otcextensions.osclient.rds.v3.configuration:DeleteConfiguration rds_instance_list = otcextensions.osclient.rds.v3.instance:ListDatabaseInstances From 901a03161870aeb0287de57ec1f529074f5118b6 Mon Sep 17 00:00:00 2001 From: vineet-pruthi Date: Fri, 27 Sep 2019 09:02:59 +0000 Subject: [PATCH 05/65] Updates in RDS Backup Funcationality --- otcextensions/sdk/rds/v3/_proxy.py | 88 +++++++++++++++++------------- otcextensions/sdk/rds/v3/backup.py | 31 ++++++++++- 2 files changed, 79 insertions(+), 40 deletions(-) diff --git a/otcextensions/sdk/rds/v3/_proxy.py b/otcextensions/sdk/rds/v3/_proxy.py index 27022f3d6..d3522f5e0 100644 --- a/otcextensions/sdk/rds/v3/_proxy.py +++ b/otcextensions/sdk/rds/v3/_proxy.py @@ -409,41 +409,53 @@ def delete_backup(self, backup, ignore_missing=True): headers=self.get_os_headers(True), ) -# def get_backup_policy(self, instance): -# """Obtaining a backup policy of the instance -# -# :param instance: This parameter can be either the ID of an instance -# or a :class:`~openstack.sdk.rds.v3.instance.Instance` -# :returns: A Backup policy -# :rtype: :class:`~otcextensions.sdk.rds.v3.configuration.BackupPolicy` -# -# """ -# instance = self._get_resource(_instance.Instance, instance) -# return self._get( -# _backup.BackupPolicy, -# requires_id=False, -# instance_id=instance.id, -# # project_id=self.session.get_project_id(), -# # endpoint_override=self.get_rds_endpoint(), -# headers=self.get_os_headers(True), -# ) -# -# def set_backup_policy(self, backup_policy, instance, **attrs): -# """Sets the backup policy of the instance -# -# :param instance: This parameter can be either the ID of an instance -# or a :class:`~openstack.sdk.rds.v3.instance.Instance` -# :param dict attrs: The attributes to update on the backup_policy -# represented by ``backup_policy``. -# -# :returns: ``None`` -# """ -# instance = self._get_resource(_instance.Instance, instance) -# return self._update( -# _backup.BackupPolicy, -# backup_policy, -# instance_id=instance.id, -# # project_id=self.session.get_project_id(), -# # endpoint_override=self.get_rds_endpoint(), -# headers=self.get_os_headers(True), -# **attrs) + def get_backup_policy(self, instance): + """Obtaining a backup policy of the instance + + :param instance: This parameter can be either the ID of an instance + or a :class:`~openstack.sdk.rds.v3.instance.Instance` + :returns: A Backup policy + :rtype: :class:`~otcextensions.sdk.rds.v3.backup.BackupPolicy` + + """ + instance = self._get_resource(_instance.Instance, instance) + return self._get( + _backup.BackupPolicy, + requires_id=False, + instance_id=instance.id, + headers=self.get_os_headers(True), + ) + + def set_backup_policy(self, instance, **attrs): + """Sets the backup policy of the instance + + :param instance: This parameter can be either the ID of an instance + or a :class:`~openstack.sdk.rds.v3.instance.Instance` + :param dict attrs: The attributes to update on the backup_policy + represented by ``backup_policy``. + + :returns: ``None`` + """ + instance = self._get_resource(_instance.Instance, instance) + return self._update( + _backup.BackupPolicy, + instance_id=instance.id, + headers=self.get_os_headers(True), + **attrs) + + def get_backup_restore_time(self, instance): + """Obtaining a backup policy of the instance + + :param instance: This parameter can be either the ID of an instance + or a :class:`~openstack.sdk.rds.v3.instance.Instance` + :returns: A Backup RestoreTime + :rtype: :class:`~otcextensions.sdk.rds.v3.backup.BackupRestoreTime` + + """ + instance = self._get_resource(_instance.Instance, instance) + return self._get( + _backup.BackupRestoreTime, + requires_id=False, + instance_id=instance.id, + headers=self.get_os_headers(True), + ) diff --git a/otcextensions/sdk/rds/v3/backup.py b/otcextensions/sdk/rds/v3/backup.py index c591b46cd..ed7835d58 100644 --- a/otcextensions/sdk/rds/v3/backup.py +++ b/otcextensions/sdk/rds/v3/backup.py @@ -42,7 +42,7 @@ class Backup(sdk_resource.Resource): #: Data backup description description = resource.Body('description') #: Create back of specific dbs - #: *Type:dict* + #: *Type:list* databases = resource.Body('databases', type=list) #: Data store information #: *Type: dict* @@ -65,7 +65,7 @@ class Backup(sdk_resource.Resource): class BackupPolicy(sdk_resource.Resource): base_path = '/instances/%(instance_id)s/backups/policy' - resource_key = 'policy' + resource_key = 'backup_policy' # capabilities allow_update = True @@ -127,3 +127,30 @@ def update(self, session, prepend_key=True, headers=headers) return None + + +class BackupRestoreTime(sdk_resource.Resource): + + base_path = '/instances/%(instance_id)s/restore-time' + resource_key = 'restore_time' + + # capabilities + allow_get = True + + #: instaceId + instance_id = resource.URI('instance_id') + # project_id = resource.URI('project_id') + + #: Start time + #: Indicates the start time of the recovery time period in + #: the UNIX timestamp format. The unit is + #: millisecond and the time zone is UTC. + #: *Type: string* + start_time = resource.Body('start_time') + + #: End time + #: Indicates the end time of the recovery time period in + #: the UNIX timestamp format. The unit is + #: millisecond and the time zone is UTC. + #: *Type: string* + end_time = resource.Body('end_time') From e7f2e521f83db1aef8cc922e49281655ea35286e Mon Sep 17 00:00:00 2001 From: vineet-pruthi Date: Thu, 3 Oct 2019 11:03:27 +0000 Subject: [PATCH 06/65] Saving RDSv3 Changes to GitHub --- doc/source/enforcer.py | 1 + .../user/proxies/{rds.rst => rds_v1.rst} | 0 doc/source/user/proxies/rds_v3.rst | 55 +++ doc/source/user/resources/rds/index.rst | 3 + .../user/resources/rds/v3/configuration.rst | 13 + doc/source/user/resources/rds/v3/flavor.rst | 13 + doc/source/user/resources/rds/v3/instance.rst | 13 + otcextensions/osclient/rds/v3/backup.py | 31 +- .../osclient/rds/v3/configuration.py | 245 ++++------ otcextensions/osclient/rds/v3/datastore.py | 23 +- otcextensions/osclient/rds/v3/instance.py | 447 ++++++------------ otcextensions/sdk/rds/v3/_proxy.py | 321 ++++++------- otcextensions/sdk/rds/v3/backup.py | 51 +- otcextensions/sdk/rds/v3/configuration.py | 152 +----- otcextensions/sdk/rds/v3/datastore.py | 1 + otcextensions/sdk/rds/v3/instance.py | 98 +++- .../tests/unit/osclient/rds/v3/fakes.py | 45 +- .../tests/unit/osclient/rds/v3/test_backup.py | 37 +- .../unit/osclient/rds/v3/test_instance.py | 76 ++- .../tests/unit/sdk/rds/v3/test_datastore.py | 15 +- .../tests/unit/sdk/rds/v3/test_instance.py | 164 ++++--- .../tests/unit/sdk/rds/v3/test_proxy.py | 11 +- setup.cfg | 7 +- 23 files changed, 838 insertions(+), 984 deletions(-) rename doc/source/user/proxies/{rds.rst => rds_v1.rst} (100%) create mode 100644 doc/source/user/proxies/rds_v3.rst create mode 100644 doc/source/user/resources/rds/v3/configuration.rst create mode 100644 doc/source/user/resources/rds/v3/flavor.rst create mode 100644 doc/source/user/resources/rds/v3/instance.rst diff --git a/doc/source/enforcer.py b/doc/source/enforcer.py index 80f9db381..ca79b0311 100644 --- a/doc/source/enforcer.py +++ b/doc/source/enforcer.py @@ -43,6 +43,7 @@ def get_proxy_methods(): "otcextensions.sdk.kms.v1._proxy", "otcextensions.sdk.obs.v1._proxy", "otcextensions.sdk.rds.v1._proxy", + "otcextensions.sdk.rds.v3._proxy", "otcextensions.sdk.volume_backup.v2._proxy" ] diff --git a/doc/source/user/proxies/rds.rst b/doc/source/user/proxies/rds_v1.rst similarity index 100% rename from doc/source/user/proxies/rds.rst rename to doc/source/user/proxies/rds_v1.rst diff --git a/doc/source/user/proxies/rds_v3.rst b/doc/source/user/proxies/rds_v3.rst new file mode 100644 index 000000000..2efeddecc --- /dev/null +++ b/doc/source/user/proxies/rds_v3.rst @@ -0,0 +1,55 @@ +Database RDS API +================ + +For details on how to use database, see :doc:`/user/guides/rds` + +.. automodule:: otcextensions.sdk.rds.v3._proxy + +The Database Class +------------------ + +The database high-level interface is available through the ``rds`` member of a +:class:`~openstack.connection.Connection` object. The ``rds`` member will only +be added if the ``otcextensions.sdk.register_otc_extensions(conn)`` method is +called. + +Datastore Operations +^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: otcextensions.sdk.rds.v3._proxy.Proxy + + .. automethod:: otcextensions.sdk.rds.v3._proxy.Proxy.datastore_versions + .. automethod:: otcextensions.sdk.rds.v3._proxy.Proxy.get_datastore_version + .. automethod:: otcextensions.sdk.rds.v3._proxy.Proxy.datastore_types + +Flavor Operations +^^^^^^^^^^^^^^^^^ + +.. autoclass:: otcextensions.sdk.rds.v3._proxy.Proxy + + .. automethod:: otcextensions.sdk.rds.v3._proxy.Proxy.get_flavor + .. automethod:: otcextensions.sdk.rds.v3._proxy.Proxy.flavors + +Instance Operations +^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: otcextensions.sdk.rds.v3._proxy.Proxy + + .. automethod:: otcextensions.sdk.rds.v3._proxy.Proxy.create_instance + .. automethod:: otcextensions.sdk.rds.v3._proxy.Proxy.update_instance + .. automethod:: otcextensions.sdk.rds.v3._proxy.Proxy.delete_instance + .. automethod:: otcextensions.sdk.rds.v3._proxy.Proxy.get_instance + .. automethod:: otcextensions.sdk.rds.v3._proxy.Proxy.find_instance + .. automethod:: otcextensions.sdk.rds.v3._proxy.Proxy.instances + + +Backup Operations +^^^^^^^^^^^^^^^^^ + +.. autoclass:: otcextensions.sdk.rds.v3._proxy.Proxy + + .. automethod:: otcextensions.sdk.rds.v3._proxy.Proxy.backups + .. automethod:: otcextensions.sdk.rds.v3._proxy.Proxy.create_backup + .. automethod:: otcextensions.sdk.rds.v3._proxy.Proxy.delete_backup + .. automethod:: otcextensions.sdk.rds.v3._proxy.Proxy.get_backup_policy + .. automethod:: otcextensions.sdk.rds.v3._proxy.Proxy.set_backup_policy diff --git a/doc/source/user/resources/rds/index.rst b/doc/source/user/resources/rds/index.rst index c27641abc..935c168e6 100644 --- a/doc/source/user/resources/rds/index.rst +++ b/doc/source/user/resources/rds/index.rst @@ -7,3 +7,6 @@ RDS Resources v1/configuration v1/flavor v1/instance + v3/configuration + v3/flavor + v3/instance diff --git a/doc/source/user/resources/rds/v3/configuration.rst b/doc/source/user/resources/rds/v3/configuration.rst new file mode 100644 index 000000000..2952778eb --- /dev/null +++ b/doc/source/user/resources/rds/v3/configuration.rst @@ -0,0 +1,13 @@ +otcextensions.sdk.rds.v3.configuration +====================================== + +.. automodule:: otcextensions.sdk.rds.v3.configuration + +The Configuration Class +----------------------- + +The ``Configuration`` class inherits from +:class:`~otcextensions.sdk.sdk_resource.Resource`. + +.. autoclass:: otcextensions.sdk.rds.v3.configuration.ConfigurationGroup + :members: diff --git a/doc/source/user/resources/rds/v3/flavor.rst b/doc/source/user/resources/rds/v3/flavor.rst new file mode 100644 index 000000000..a8806f213 --- /dev/null +++ b/doc/source/user/resources/rds/v3/flavor.rst @@ -0,0 +1,13 @@ +otcextensions.sdk.rds.v3.flavor +=============================== + +.. automodule:: otcextensions.sdk.rds.v3.flavor + +The Flavor Class +---------------- + +The ``Flavor`` class inherits from +:class:`~otcextensions.sdk.sdk_resource.Resource`. + +.. autoclass:: otcextensions.sdk.rds.v3.flavor.Flavor + :members: diff --git a/doc/source/user/resources/rds/v3/instance.rst b/doc/source/user/resources/rds/v3/instance.rst new file mode 100644 index 000000000..d72e7cd4f --- /dev/null +++ b/doc/source/user/resources/rds/v3/instance.rst @@ -0,0 +1,13 @@ +otcextensions.sdk.rds.v3.instance +================================= + +.. automodule:: otcextensions.sdk.rds.v3.instance + +The Instance Class +------------------ + +The ``Instance`` class inherits from +:class:`~otcextensions.sdk.sdk_resource.Resource`. + +.. autoclass:: otcextensions.sdk.rds.v3.instance.Instance + :members: diff --git a/otcextensions/osclient/rds/v3/backup.py b/otcextensions/osclient/rds/v3/backup.py index 1a2589547..2dc03af93 100644 --- a/otcextensions/osclient/rds/v3/backup.py +++ b/otcextensions/osclient/rds/v3/backup.py @@ -23,7 +23,15 @@ def set_attributes_for_print_detail(obj): info = {} - attr_list = ['id', 'name', 'type', 'size', 'status', 'begin_time', 'end_time', 'instance_id'] + attr_list = [ + 'id', + 'name', + 'type', + 'size', + 'status', + 'begin_time', + 'end_time', + 'instance_id'] for attr in dir(obj): if attr == 'datastore' and getattr(obj, attr): info['datastore_type'] = obj.datastore['type'] @@ -40,7 +48,13 @@ def set_attributes_for_print_detail(obj): class ListBackup(command.Lister): _description = _("List database backups/snapshots") - columns = ('ID', 'Name', 'Type', 'Instance Id', 'Datastore Type', 'Datastore Version') + columns = ( + 'ID', + 'Name', + 'Type', + 'Instance Id', + 'Datastore Type', + 'Datastore Version') def get_parser(self, prog_name): parser = super(ListBackup, self).get_parser(prog_name) @@ -86,11 +100,17 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.rds attrs = {} - args_list = ['backup_id', 'backup_type', 'offset', 'limit', 'begin_time', 'end_time'] + args_list = [ + 'backup_id', + 'backup_type', + 'offset', + 'limit', + 'begin_time', + 'end_time'] for arg in args_list: if getattr(parsed_args, arg): attrs[arg] = getattr(parsed_args, arg) - + data = client.backups(parsed_args.instance, **attrs) return ( @@ -101,6 +121,7 @@ def take_action(self, parsed_args): ) for s in data) ) + class CreateBackup(command.ShowOne): _description = _('Create Database backup') @@ -136,7 +157,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.rds - attrs = { 'name': parsed_args.name } + attrs = {'name': parsed_args.name} if parsed_args.description: attrs['description'] = parsed_args.description if parsed_args.databases: diff --git a/otcextensions/osclient/rds/v3/configuration.py b/otcextensions/osclient/rds/v3/configuration.py index 7cb5f5af8..d67f98d96 100644 --- a/otcextensions/osclient/rds/v3/configuration.py +++ b/otcextensions/osclient/rds/v3/configuration.py @@ -11,7 +11,6 @@ # under the License. # """Configuration v3 action implementations""" -import json import logging from osc_lib import exceptions @@ -46,8 +45,10 @@ def format_dict(data): class ListConfigurations(command.Lister): _description = _("List Parameter Groups") - columns = ['ID', 'Name', 'Description', 'Datastore Name', - 'Datastore Version Name', 'User Defined'] + columns = [ + 'ID', 'Name', 'Description', 'Datastore Name', + 'Datastore Version Name', 'User Defined' + ] def get_parser(self, prog_name): parser = super(ListConfigurations, self).get_parser(prog_name) @@ -57,8 +58,7 @@ def get_parser(self, prog_name): metavar='', type=int, default=None, - help=_('Limit the number of results displayed. (Not supported)') - ) + help=_('Limit the number of results displayed. (Not supported)')) parser.add_argument( '--marker', dest='marker', @@ -66,8 +66,7 @@ def get_parser(self, prog_name): help=_('Begin displaying the results for IDs greater than the ' 'specified marker. When used with --limit, set this to ' 'the last ID displayed in the previous run. ' - '(Not supported)') - ) + '(Not supported)')) return parser @@ -76,27 +75,24 @@ def take_action(self, parsed_args): data = client.configurations() - return ( + return (self.columns, (utils.get_item_properties( + s, self.columns, - (utils.get_item_properties( - s, - self.columns, - ) for s in data) - ) + ) for s in data)) class ShowConfiguration(command.ShowOne): _description = _("Shows details of a database configuration group.") - columns = ['ID', 'Name', 'Description', 'Datastore Name', - 'Datastore Version Name', 'Values'] + columns = [ + 'ID', 'Name', 'Description', 'Datastore Name', + 'Datastore Version Name', 'Values' + ] def get_parser(self, prog_name): parser = super(ShowConfiguration, self).get_parser(prog_name) - parser.add_argument( - 'configuration_group', - metavar="", - help=_("ID or name of the configuration group") - ) + parser.add_argument('configuration_group', + metavar="", + help=_("ID or name of the configuration group")) return parser def take_action(self, parsed_args): @@ -108,13 +104,14 @@ def take_action(self, parsed_args): # TODO(agoncharov) values and parameters are breaking the layout # dramatically. Maybe it make sence to create additional # filter to get/list only specific values/params - data = utils.get_item_properties( - obj, self.columns, - formatters={ - 'values': format_dict, - 'parameters': utils.format_list_of_dicts, - } - ) + data = utils.get_item_properties(obj, + self.columns, + formatters={ + 'values': + format_dict, + 'parameters': + utils.format_list_of_dicts, + }) return (self.columns, data) @@ -122,60 +119,40 @@ def take_action(self, parsed_args): class CreateConfiguration(command.ShowOne): _description = _("Create new Parameter Group") - columns = ( - 'id', - 'name', - 'description', - 'datastore_version_id', - 'datastore_version_name', - 'datastore_name', - 'created', - 'updated', - 'allowed_updated', - 'instance_count', - 'values' - ) + columns = ('id', 'name', 'description', 'datastore_version_id', + 'datastore_version_name', 'datastore_name', 'created', + 'updated', 'allowed_updated', 'instance_count', 'values') def get_parser(self, prog_name): parser = super(CreateConfiguration, self).get_parser(prog_name) - parser.add_argument( - '--name', - metavar="", - required=True, - help=_("Parameter group name") - ) - parser.add_argument( - '--description', - metavar="", - help=_("Parameter group description") - ) - parser.add_argument( - '--datastore_type', - metavar="", - choices=DATASTORE_TYPE_CHOICES, - required=True, - help=_("Datastore type") - ) - parser.add_argument( - '--datastore_version', - metavar="", - required=True, - help=_("Datastore version") - ) -# parser.add_argument( -# '--value', -# dest="ind_values", -# metavar="", -# action=parseractions.KeyValueAction, -# help=_("Parameter group value" -# "(repeat option to set multiple values)") -# ) - parser.add_argument( - '--values', - metavar='', - help=_('Dictionary (JSON) of the values to set.'), - type=yaml.load - ) + parser.add_argument('--name', + metavar="", + required=True, + help=_("Parameter group name")) + parser.add_argument('--description', + metavar="", + help=_("Parameter group description")) + parser.add_argument('--datastore_type', + metavar="", + choices=DATASTORE_TYPE_CHOICES, + required=True, + help=_("Datastore type")) + parser.add_argument('--datastore_version', + metavar="", + required=True, + help=_("Datastore version")) + # parser.add_argument( + # '--value', + # dest="ind_values", + # metavar="", + # action=parseractions.KeyValueAction, + # help=_("Parameter group value" + # "(repeat option to set multiple values)") + # ) + parser.add_argument('--values', + metavar='', + help=_('Dictionary (JSON) of the values to set.'), + type=yaml.load) return parser @@ -195,6 +172,8 @@ def take_action(self, parsed_args): config_attrs['datastore'] = datastore # flatten values into the proper config_attrs + + # values = {} # try: # values = json.loads(parsed_args.values) @@ -210,12 +189,11 @@ def take_action(self, parsed_args): config_attrs['values'] = parsed_args.values config = client.create_configuration(**config_attrs) - data = utils.get_item_properties( - config, self.columns, - formatters={ - 'values': format_dict, - } - ) + data = utils.get_item_properties(config, + self.columns, + formatters={ + 'values': format_dict, + }) return (self.columns, data) @@ -223,38 +201,22 @@ def take_action(self, parsed_args): class SetConfiguration(command.ShowOne): _description = _("Set values of the Parameter Group") - columns = ( - 'id', - 'name', - 'description', - 'datastore_version_id', - 'datastore_version_name', - 'datastore_name', - 'created', - 'updated', - 'allowed_updated', - 'instance_count', - 'values' - ) + columns = ('id', 'name', 'description', 'datastore_version_id', + 'datastore_version_name', 'datastore_name', 'created', + 'updated', 'allowed_updated', 'instance_count', 'values') def get_parser(self, prog_name): parser = super(SetConfiguration, self).get_parser(prog_name) - parser.add_argument( - '--parameter-group', - metavar="", - required=True, - help=_("Parameter group id") - ) - parser.add_argument( - '--name', - metavar="", - help=_("New ParameterGroup name") - ) - parser.add_argument( - '--description', - metavar="", - help=_("New ParameterGroup description") - ) + parser.add_argument('--parameter-group', + metavar="", + required=True, + help=_("Parameter group id")) + parser.add_argument('--name', + metavar="", + help=_("New ParameterGroup name")) + parser.add_argument('--description', + metavar="", + help=_("New ParameterGroup description")) # parser.add_argument( # '--datastore_type', # metavar="", @@ -268,15 +230,13 @@ def get_parser(self, prog_name): # required=True, # help=_("Datastore version") # ) - parser.add_argument( - '--value', - dest="values", - metavar="", - required=True, - action=parseractions.KeyValueAction, - help=_("Parameter group value" - "(repeat option to set multiple values)") - ) + parser.add_argument('--value', + dest="values", + metavar="", + required=True, + action=parseractions.KeyValueAction, + help=_("Parameter group value" + "(repeat option to set multiple values)")) return parser @@ -299,18 +259,17 @@ def take_action(self, parsed_args): config = client.get_parameter_group(parsed_args.parameter_group) if not config: - msg = (_("Failed to find Parameter Group by ID %s") - % parsed_args.parameter_group) + msg = (_("Failed to find Parameter Group by ID %s") % + parsed_args.parameter_group) raise exceptions.CommandError(msg) config = config.change_parameter_info(session=client, **config_attrs) - data = utils.get_item_properties( - config, self.columns, - formatters={ - 'values': format_dict, - } - ) + data = utils.get_item_properties(config, + self.columns, + formatters={ + 'values': format_dict, + }) return (self.columns, data) @@ -320,20 +279,17 @@ class DeleteConfiguration(command.Command): def get_parser(self, prog_name): parser = super(DeleteConfiguration, self).get_parser(prog_name) - parser.add_argument( - 'configuration', - metavar='', - nargs='+', - help=_('ID or name of the configuration group') - ) + parser.add_argument('configuration', + metavar='', + nargs='+', + help=_('ID or name of the configuration group')) return parser def take_action(self, parsed_args): client = self.app.client_manager.rds if parsed_args.configuration: for cnf in parsed_args.configuration: - client.delete_configuration( - cnf, ignore_missing=False) + client.delete_configuration(cnf, ignore_missing=False) class ListDatabaseConfigurationParameters(command.Lister): @@ -344,20 +300,17 @@ class ListDatabaseConfigurationParameters(command.Lister): def get_parser(self, prog_name): parser = super(ListDatabaseConfigurationParameters, self).\ get_parser(prog_name) - parser.add_argument( - 'datastore_version', - metavar='', - help=_('Datastore version name or ID assigned' - 'to the configuration group.') - ) + parser.add_argument('datastore_version', + metavar='', + help=_('Datastore version name or ID assigned' + 'to the configuration group.')) parser.add_argument( '--datastore', metavar='', default=None, help=_('ID or name of the datastore to list configuration' 'parameters for. Optional if the ID of the' - 'datastore_version is provided.') - ) + 'datastore_version is provided.')) return parser def take_action(self, parsed_args): diff --git a/otcextensions/osclient/rds/v3/datastore.py b/otcextensions/osclient/rds/v3/datastore.py index 87145d1c0..ebdc6558b 100644 --- a/otcextensions/osclient/rds/v3/datastore.py +++ b/otcextensions/osclient/rds/v3/datastore.py @@ -17,7 +17,7 @@ from osc_lib.command import command from otcextensions.i18n import _ - +from otcextensions.common import sdk_utils LOG = logging.getLogger(__name__) @@ -25,8 +25,7 @@ def _get_columns(item): - column_map = { - } + column_map = {} return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) @@ -40,13 +39,10 @@ def take_action(self, parsed_args): data = client.datastore_types() - return ( + return (self.columns, (utils.get_item_properties( + s, self.columns, - (utils.get_item_properties( - s, - self.columns, - ) for s in data) - ) + ) for s in data)) class ListDatastoreVersions(command.Lister): @@ -70,10 +66,7 @@ def take_action(self, parsed_args): data = client.datastore_versions(datastore_name=parsed_args.db_name) - return ( + return (self.columns, (utils.get_item_properties( + s, self.columns, - (utils.get_item_properties( - s, - self.columns, - ) for s in data) - ) + ) for s in data)) diff --git a/otcextensions/osclient/rds/v3/instance.py b/otcextensions/osclient/rds/v3/instance.py index 0d8a9d4ee..6d0da421a 100644 --- a/otcextensions/osclient/rds/v3/instance.py +++ b/otcextensions/osclient/rds/v3/instance.py @@ -21,14 +21,11 @@ from otcextensions.common import sdk_utils from otcextensions.i18n import _ -import six - LOG = logging.getLogger(__name__) def _get_columns(item): - column_map = { - } + column_map = {} return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) @@ -40,8 +37,7 @@ def set_attributes_for_print(instances): setattr(instance, 'size', '-') datastore = instance.datastore if datastore.get('version'): - setattr(instance, 'datastore_version', - datastore['version']) + setattr(instance, 'datastore_version', datastore['version']) if datastore.get('type'): setattr(instance, 'datastore_type', datastore['type']) yield instance @@ -50,21 +46,11 @@ def set_attributes_for_print(instances): def set_attributes_for_print_detail(obj): info = {} # instance._info.copy() attr_list = [ - 'id', - 'name', - 'datastore', - 'flavor_ref', - 'disk_encryption_id', - 'region', - 'availability_zone', - 'vpc_id', - 'subnet_id', - 'security_group_id', - 'port', - 'backup_strategy', - 'configuration_id', - 'charge_info', - 'backup_strategy'] + 'id', 'name', 'datastore', 'flavor_ref', 'disk_encryption_id', + 'region', 'availability_zone', 'vpc_id', 'subnet_id', + 'security_group_id', 'port', 'backup_strategy', 'configuration_id', + 'charge_info', 'backup_strategy' + ] for attr in dir(obj): if attr == 'datastore' and getattr(obj, attr): info['datastore'] = obj.datastore['type'] @@ -81,76 +67,66 @@ def set_attributes_for_print_detail(obj): class ListDatabaseInstances(command.Lister): _description = _('List database instances') - columns = ['ID', 'Name', 'Datastore Type', 'Datastore Version', 'Status', - 'Flavor_ref', 'Type', 'Size', 'Region'] + columns = [ + 'ID', 'Name', 'Datastore Type', 'Datastore Version', 'Status', + 'Flavor_ref', 'Type', 'Size', 'Region' + ] def get_parser(self, prog_name): parser = super(ListDatabaseInstances, self).get_parser(prog_name) - parser.add_argument( - '--limit', - dest='limit', - metavar='', - type=int, - default=None, - help=_('Limit the number of results displayed') - ) - parser.add_argument( - '--id', - dest='id', - metavar='', - type=str, - default=None, - help=_('Specifies the DB instance ID.') - ) - parser.add_argument( - '--name', - dest='name', - metavar='', - type=str, - default=None, - help=_('Specifies the DB instance Name.') - ) + parser.add_argument('--limit', + dest='limit', + metavar='', + type=int, + default=None, + help=_('Limit the number of results displayed')) + parser.add_argument('--id', + dest='id', + metavar='', + type=str, + default=None, + help=_('Specifies the DB instance ID.')) + parser.add_argument('--name', + dest='name', + metavar='', + type=str, + default=None, + help=_('Specifies the DB instance Name.')) parser.add_argument( '--type', dest='type', metavar='', type=str, default=None, - help=_('Specifies the instance type. Values cane be single/ha/replica') - ) - parser.add_argument( - '--db_type', - dest='datastore_type', - metavar='', - type=str, - default=None, - help=_('Specifies the database type. ' - 'value is MySQL, PostgreSQL, or SQLServer.') - ) - parser.add_argument( - '--vpc_id', - dest='vpc_id', - metavar='', - type=str, - default=None, - help=_('Indicates the VPC ID. of DB Instance.') - ) - parser.add_argument( - '--subnet_id', - dest='subnet_id', - metavar='', - type=str, - default=None, - help=_('Indicates the Subnet ID of DB Instance.') - ) - parser.add_argument( - '--offset', - dest='offset', - metavar='', - type=str, - default=None, - help=_('Specifies the index position.') - ) + help=_( + 'Specifies the instance type. Values cane be single/ha/replica' + )) + parser.add_argument('--db_type', + dest='datastore_type', + metavar='', + type=str, + default=None, + help=_( + 'Specifies the database type. ' + 'value is MySQL, PostgreSQL, or SQLServer.')) + parser.add_argument('--vpc_id', + dest='vpc_id', + metavar='', + type=str, + default=None, + help=_('Indicates the VPC ID. of DB Instance.')) + parser.add_argument('--subnet_id', + dest='subnet_id', + metavar='', + type=str, + default=None, + help=_('Indicates the Subnet ID of DB Instance.')) + parser.add_argument('--offset', + dest='offset', + metavar='', + type=str, + default=None, + help=_('Specifies the index position.')) parser.add_argument( '--marker', dest='marker', @@ -158,21 +134,16 @@ def get_parser(self, prog_name): help=_('Begin displaying the results for IDs greater than the ' 'specified marker. When used with --limit, set this to ' 'the last ID displayed in the previous run. ' - '(Not supported)') - ) + '(Not supported)')) return parser def take_action(self, parsed_args): client = self.app.client_manager.rds args_list = [ - 'name', - 'id', - 'vpc_id', - 'subnet_id', - 'type', - 'datastore_type', - 'offset'] + 'name', 'id', 'vpc_id', 'subnet_id', 'type', 'datastore_type', + 'offset' + ] attrs = {} for arg in args_list: if getattr(parsed_args, arg): @@ -182,36 +153,21 @@ def take_action(self, parsed_args): if data: data = set_attributes_for_print(data) - return ( + return (self.columns, (utils.get_item_properties( + s, self.columns, - (utils.get_item_properties( - s, - self.columns, - ) for s in data) - ) + ) for s in data)) class ShowDatabaseInstance(command.ShowOne): _description = _("Show instance details") columns = [ - 'id', - 'name', - 'datastore', - 'datastore_version', - 'flavor Ref', - 'Volume Type', - 'Size', - 'disk_encryption_id', - 'region', - 'availability_zone', - 'vpc_id', - 'subnet_id', - 'security_group_id', - 'port', - 'backup_strategy', - 'configuration_id', - 'charge mode'] + 'id', 'name', 'datastore', 'datastore_version', 'flavor Ref', + 'Volume Type', 'Size', 'disk_encryption_id', 'region', + 'availability_zone', 'vpc_id', 'subnet_id', 'security_group_id', + 'port', 'backup_strategy', 'configuration_id', 'charge mode' + ] def get_parser(self, prog_name): parser = super(ShowDatabaseInstance, self).get_parser(prog_name) @@ -235,25 +191,6 @@ class CreateDatabaseInstance(command.ShowOne): _description = _("Creates a new database instance.") - columns = [ - 'id', - 'name', - 'datastore', - 'datastore_version', - 'flavor Id', - 'Volume Type', - 'Size', - 'disk_encryption_id', - 'region', - 'availability_zone', - 'vpc_id', - 'subnet_id', - 'security_group_id', - 'port', - 'backup_strategy', - 'configuration_id', - 'charge mode'] - def get_parser(self, prog_name): parser = super(CreateDatabaseInstance, self).get_parser(prog_name) parser.add_argument( @@ -262,10 +199,9 @@ def get_parser(self, prog_name): help=_("Name of the instance."), ) parser.add_argument( - '--flavor', - dest='flavor_ref', + 'flavor_ref', metavar='', - help=_("flavor spec_code."), + help=_("Flavor spec_code"), ) parser.add_argument( '--size', @@ -285,20 +221,24 @@ def get_parser(self, prog_name): parser.add_argument( '--availability_zone', metavar='', + default=None, help=_("The Zone hint to give to Nova."), ) parser.add_argument( '--datastore', metavar='', + default=None, help=_("datastore name"), ) parser.add_argument( '--datastore_version', + default=None, metavar='', help=_("datastore version."), ) parser.add_argument( - '--configuration_id', + '--configuration', + dest='configuration_id', metavar='', default=None, help=_("ID of the configuration group to attach to the instance."), @@ -331,44 +271,34 @@ def get_parser(self, prog_name): '--region', metavar='', type=str, + default=None, help=argparse.SUPPRESS, ) - parser.add_argument( - '--vpc_id', - dest='vpc_id', - metavar='', - type=str, - # required=True, - help=_('Network (VPC) ID') - ) - parser.add_argument( - '--subnet_id', - metavar='', - type=str, - # required=True, - help=_('Network (VPC) ID') - ) - parser.add_argument( - '--security_group', - dest='security_group_id', - metavar='', - type=str, - # required=True, - help=_('Security group ID') - ) - parser.add_argument( - '--ha_replication_mode', + parser.add_argument('--network_id', + dest='vpc_id', + metavar='', + type=str, + help=_('Network (VPC) ID')) + parser.add_argument('--subnet_id', + metavar='', + type=str, + help=_('Network (VPC) ID')) + parser.add_argument('--security_group', + dest='security_group_id', + metavar='', + type=str, + help=_('Security group ID')) + parser.add_argument( + '--ha_mode', metavar='', type=str, - help=_('replication mode for the standby DB instance') - ) - parser.add_argument( - '--charge_mode', - metavar='', - type=str, - default='postPaid', - help=_('Specifies the billing mode') - ) + default=None, + help=_('replication mode for the standby DB instance')) + parser.add_argument('--charge_mode', + metavar='', + type=str, + default='postPaid', + help=_('Specifies the billing mode')) return parser def take_action(self, parsed_args): @@ -377,9 +307,11 @@ def take_action(self, parsed_args): client = self.app.client_manager.rds attrs = {} - args_list = ['name', 'availability_zone', 'configuration_id', - 'region', 'vpc_id', 'subnet_id', 'security_group_id', - 'disk_encryption_id', 'port', 'password', 'flavor_ref'] + args_list = [ + 'name', 'availability_zone', 'configuration_id', 'region', + 'vpc_id', 'subnet_id', 'security_group_id', 'disk_encryption_id', + 'port', 'password', 'flavor_ref' + ] for arg in args_list: if getattr(parsed_args, arg): attrs[arg] = getattr(parsed_args, arg) @@ -397,16 +329,11 @@ def take_action(self, parsed_args): 'version': parsed_args.datastore_version } attrs['datastore'] = datastore - if parsed_args.ha_replication_mode: - ha = { - 'mode': 'ha', - 'replication_mode': parsed_args.ha_replication_mode - } + if parsed_args.ha_mode: + ha = {'mode': 'ha', 'replication_mode': parsed_args.ha_mode} attrs['ha'] = ha if parsed_args.charge_mode: - attrs['charge_info'] = { - 'charge_mode': parsed_args.charge_mode - } + attrs['charge_info'] = {'charge_mode': parsed_args.charge_mode} obj = client.create_instance(**attrs) display_columns, columns = _get_columns(obj) @@ -433,143 +360,45 @@ def take_action(self, parsed_args): try: client.delete_instance(instance.id) except Exception as e: - msg = (_("Failed to delete instance %(instance)s: %(e)s") - % {'instance': parsed_args.instance, 'e': e}) + msg = (_("Failed to delete instance %(instance)s: %(e)s") % { + 'instance': parsed_args.instance, + 'e': e + }) raise exceptions.CommandError(msg) -class ForceDeleteDatabaseInstance(command.Command): - - _description = _("Force delete an instance.") - - def get_parser(self, prog_name): - parser = (super(ForceDeleteDatabaseInstance, self) - .get_parser(prog_name)) - parser.add_argument( - 'instance', - metavar='', - help=_('ID or name of the instance'), - ) - return parser - - def take_action(self, parsed_args): - raise NotImplementedError - # db_instances = self.app.client_manager.database.instances - # instance = osc_utils.find_resource(db_instances, - # parsed_args.instance) - # db_instances.reset_status(instance) - # try: - # db_instances.delete(instance) - # except Exception as e: - # msg = (_("Failed to delete instance %(instance)s: %(e)s") - # % {'instance': parsed_args.instance, 'e': e}) - # raise exceptions.CommandError(msg) - - -class RestartDatabaseInstance(command.Command): - - _description = _("Restarts an instance.") - - def get_parser(self, prog_name): - parser = super(RestartDatabaseInstance, self).get_parser( - prog_name - ) - parser.add_argument( - 'instance', - metavar='', - type=str, - help=_('ID or name of the instance.') - ) - return parser - - def take_action(self, parsed_args): - client = self.app.client_manager.rds - - client.restart_instance(parsed_args.instance) - - class RestoreDatabaseInstance(command.Command): _description = _("Restores an instance from backup.") def get_parser(self, prog_name): - parser = super(RestoreDatabaseInstance, self).get_parser( - prog_name - ) - parser.add_argument( - 'instance', - metavar='', - type=str, - help=_('ID or name of the instance.') - ) - parser.add_argument( - '--backup', - metavar='', - type=str, - help=_('ID or name of the backup.') - ) + parser = super(RestoreDatabaseInstance, self).get_parser(prog_name) + parser.add_argument('instance', + metavar='', + type=str, + help=_('ID or name of the instance.')) + parser.add_argument('--backup', + metavar='', + default=None, + type=str, + help=_('ID or name of the backup.')) + parser.add_argument('--restore_time', + metavar='', + default=None, + type=str, + help=_('Specifies the time point of data ' + 'restoration in the UNIX timestamp')) + parser.add_argument('--target_instance', + metavar='', + default=None, + type=str, + help=_('ID or name of the target instance.')) return parser def take_action(self, parsed_args): client = self.app.client_manager.rds - client.restore_instance( - instance=parsed_args.instance, - backup=parsed_args.backup) - - -class UpdateDatabaseInstance(command.Command): - - _description = _("Updates an instance: Edits name, " - "configuration, or replica source.") - - def get_parser(self, prog_name): - parser = super(UpdateDatabaseInstance, self).get_parser(prog_name) - parser.add_argument( - 'instance', - metavar='', - type=str, - help=_('ID or name of the instance.'), - ) - parser.add_argument( - '--name', - metavar='', - type=str, - default=None, - help=_('ID or name of the instance.'), - ) - parser.add_argument( - '--configuration', - metavar='', - type=str, - default=None, - help=_('ID of the configuration reference to attach.'), - ) - parser.add_argument( - '--detach_replica_source', - '--detach-replica-source', - dest='detach_replica_source', - action="store_true", - default=False, - help=_('Detach the replica instance from its replication source. ' - '--detach-replica-source may be deprecated in the future ' - 'in favor of just --detach_replica_source'), - ) - parser.add_argument( - '--remove_configuration', - dest='remove_configuration', - action="store_true", - default=False, - help=_('Drops the current configuration reference.'), - ) - return parser - - def take_action(self, parsed_args): - raise NotImplementedError - # db_instances = self.app.client_manager.database.instances - # instance = osc_utils.find_resource(db_instances, - # parsed_args.instance) - # db_instances.edit(instance, parsed_args.configuration, - # parsed_args.name, - # parsed_args.detach_replica_source, - # parsed_args.remove_configuration) + client.restore_instance(instance=parsed_args.instance, + backup=parsed_args.backup, + restore_time=parsed_args.restore_time, + target_instance=parsed_args.target_instance) diff --git a/otcextensions/sdk/rds/v3/_proxy.py b/otcextensions/sdk/rds/v3/_proxy.py index d3522f5e0..f8dd06f43 100644 --- a/otcextensions/sdk/rds/v3/_proxy.py +++ b/otcextensions/sdk/rds/v3/_proxy.py @@ -9,9 +9,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from openstack import exceptions from otcextensions.sdk import sdk_proxy -from openstack import proxy from otcextensions.sdk.rds.v3 import backup as _backup from otcextensions.sdk.rds.v3 import configuration as _configuration from otcextensions.sdk.rds.v3 import datastore as _datastore @@ -23,6 +21,13 @@ class Proxy(sdk_proxy.Proxy): skip_discovery = True + def __init__(self, session, *args, **kwargs): + super(Proxy, self).__init__(session=session, *args, **kwargs) + self.additional_headers = { + 'Content-Type': 'application/json', + 'X-Language': 'en-us' + } + def get_os_endpoint(self, **kwargs): """Return OpenStack compliant endpoint @@ -56,34 +61,6 @@ def get_rds_endpoint(self, **kwargs): # else: # return endpoint - def get_os_headers(self, language=None): - """Get headers for request - - Unfortunatels RDS requires 'Content-Type: application/json' - header even for GET and LIST operations with empty body - We need to deel with it - - :param language: whether language should be added to headers - can be either bool (then self.get_language is used) - or a language code directly (i.e. "en-us") - :returns dict: dictionary with headers - """ - headers = { - 'Content-Type': 'application/json', - } - if language: - if isinstance(language, bool): - headers['X-Language'] = self.get_language() - elif isinstance(language, str): - headers['X-Language'] = language - return headers - - def get_language(self): - """Returns language code - - """ - return 'en-us' - # ======= Datastores ======= def datastore_types(self): """List supported datastore types @@ -92,7 +69,7 @@ def datastore_types(self): :rtype object: object with name attribte """ for ds in ['MySQL', 'PostgreSQL', 'SQLServer']: - obj = type('obj', (object,), {'name': ds}) + obj = type('obj', (object, ), {'name': ds}) yield obj return @@ -109,11 +86,10 @@ def datastore_versions(self, datastore_name): return self._list( _datastore.Datastore, datastore_name=datastore_name, - headers=self.get_os_headers(True) ) - # ======= Flavors ======= + def flavors(self, datastore_name, version_name): """List flavors of given datastore_name and datastore_version @@ -123,31 +99,12 @@ def flavors(self, datastore_name, version_name): :returns: A generator of flavor :rtype: :class:`~otcextensions.sdk.rds.v3.flavor.Flavor` """ - return self._list( - _flavor.Flavor, - headers=self.get_os_headers(True), - datastore_name=datastore_name, - version_name=version_name - ) - -# def get_flavor(self, flavor): -# """Get the detail of a flavor -# -# :param id: Flavor id or an object of class -# :class:`~otcextensions.sdk.rds_os.v1.flavor.Flavor` -# :returns: Detail of flavor -# :rtype: :class:`~otcextensions.sdk.rds_os.v1.flavor.Flavor` -# """ -# return self._get( -# _flavor.Flavor, -# flavor, -# # project_id=self.session.get_project_id(), -# # endpoint_override=self.get_os_endpoint(), -# headers=self.get_os_headers(True), -# ) -# + return self._list(_flavor.Flavor, + datastore_name=datastore_name, + version_name=version_name) # ======= Instance ======= + def create_instance(self, **attrs): """Create a new instance from attributes @@ -158,11 +115,12 @@ def create_instance(self, **attrs): :returns: The results of server creation :rtype: :class:`~otcextensions.sdk.rds.v3.instance.Instance` """ - return self._create(_instance.Instance, - # project_id=self.session.get_project_id(), - # endpoint_override=self.get_os_endpoint(), - headers=self.get_os_headers(True), - prepend_key=False, **attrs) + return self._create( + _instance.Instance, + # project_id=self.session.get_project_id(), + # endpoint_override=self.get_os_endpoint(), + prepend_key=False, + **attrs) def delete_instance(self, instance, ignore_missing=True): """Delete an instance @@ -178,9 +136,9 @@ def delete_instance(self, instance, ignore_missing=True): :returns: ``None`` """ self._delete( - _instance.Instance, instance, + _instance.Instance, + instance, ignore_missing=ignore_missing, - headers=self.get_os_headers(True) ) def find_instance(self, name_or_id, ignore_missing=True): @@ -195,8 +153,8 @@ def find_instance(self, name_or_id, ignore_missing=True): :returns: One :class:`~otcextensions.sdk.rds.v3.instance.Instance` or None """ - return self._find(_instance.Instance, name_or_id, - headers=self.get_os_headers(True), + return self._find(_instance.Instance, + name_or_id, ignore_missing=ignore_missing) def instances(self, **params): @@ -205,82 +163,118 @@ def instances(self, **params): :returns: A generator of instance objects :rtype: :class:`~otcextensions.sdk.rds.v3.instance.Instance` """ - return self._list( - _instance.Instance, paginated=False, - headers=self.get_os_headers(True), **params - ) + return self._list(_instance.Instance, paginated=False, **params) - def update_instance(self, instance, **attrs): - """Update a instance + def restore_instance(self, + instance, + backup=None, + restore_time=None, + target_instance=None): + """Restore instance from backup + or Restore Point in Time Recovery :param instance: Either the id of a instance or a :class:`~otcextensions.sdk.rds.v3.instance.Instance` instance. - :attrs attrs: The attributes to update on the instance represented - by ``value``. + :param backup: Either the id of a backup or a + :class:`~otcextensions.sdk.rds.v3.backup.Backup` + instance. - :returns: The updated instance - :rtype: :class:`~otcextensions.sdk.rds.v3.instance.Instance` + :returns: None + :rtype: """ - return self._update( - _instance.Instance, - instance=instance, - # project_id=self.session.get_project_id(), - # endpoint_override=self.get_rds_endpoint(), - headers=self.get_os_headers(True), - **attrs - ) - - def restart_instance(self, instance): - """Restart instance + attrs = {} + instance = self._get_resource(_instance.Instance, instance) + if target_instance: + target_instance = self._get_resource(_instance.Instance, + target_instance) + else: + target_instance = instance + if backup: + backup = self._get_resource(_backup.Backup, backup) + attrs['source'] = {'type': 'backup', 'backup_id': backup.id} + elif restore_time: + attrs['source'] = { + 'type': 'timestamp', + 'restore_time': restore_time + } + attrs['source']['instance_id'] = instance.id + attrs['target'] = {'instance_id': target_instance.id} + return self._create(_instance.InstanceRecovery, + prepend_key=False, + **attrs) + + def create_instance_from_backup(self, + instance, + backup=None, + restore_time=None, + **attrs): + """Restore instance from backup :param instance: Either the id of a instance or a :class:`~otcextensions.sdk.rds.v3.instance.Instance` instance. + :param backup: Either the id of a backup or a + :class:`~otcextensions.sdk.rds.v3.backup.Backup` + instance. + :attrs attrs: The attributes to update on the instance represented + by ``value``. + :returns: The updated instance :rtype: :class:`~otcextensions.sdk.rds.v3.instance.Instance` """ instance = self._get_resource(_instance.Instance, instance) - return instance.restart(self) + if backup: + backup = self._get_resource(_backup.Backup, backup) + attrs['restore_point'] = {'type': 'backup', 'backup_id': backup.id} + elif restore_time: + attrs['restore_point'] = { + 'type': 'timestamp', + 'restore_time': restore_time + } + attrs['source']['instance_id'] = instance.id + attrs['restore_point']['instance_id'] = instance.id + return self._create(_instance.Instance, prepend_key=False, **attrs) + + def get_instance_restore_time(self, instance): + """Obtaining a restore time of an instance - def restore_instance(self, instance, backup): - """Restore instance from backup + :param instance: This parameter can be either the ID of an instance + or a :class:`~openstack.sdk.rds.v3.instance.Instance` + :returns: Instance restore time + :rtype: :class:`~otcextensions.sdk.rds.v3.instance.InstanceRestoreTime` - :param instance: Either the id of a instance or a - :class:`~otcextensions.sdk.rds.v3.instance.Instance` - instance. - :param backup: Either the id of a backup or a - :class:`~otcextensions.sdk.rds.v3.backup.Backup` - instance. + """ + instance = self._get_resource(_instance.Instance, instance) + return self._get(_instance.InstanceRestoreTime, + instance_id=instance.id) - :returns: None - :rtype: + def get_instance_configuration(self, instance): + """Obtaining a Configuration Group associated to instance + + :param instance: This parameter can be either the ID of an instance + or a :class:`~openstack.sdk.rds.v3.instance.Instance` + :returns: Configuration Group details associated to instance + :rtype: :class: + `~otcextensions.sdk.rds.v3.instance.InstanceConfiguration` + + """ + instance = self._get_resource(_instance.Instance, instance) + return self._get(_instance.InstanceConfiguration, + instance_id=instance.id) + + def update_instance_configuration(self, instance, **attrs): + """Updates the configuration params of the instance + + :param instance: This parameter can be either the ID of an instance + or a :class:`~openstack.sdk.rds.v3.instance.Instance` + + :returns: Parameter restart required as bool value """ instance = self._get_resource(_instance.Instance, instance) - backup = self._get_resource(_backup.Backup, backup) - return instance.restore(self, backupRef=backup.id) - # - # def create_instance_from_backup(self, backup, **attrs): - # """Restore instance from backup - # - # :param instance: Either the id of a instance or a - # :class:`~otcextensions.sdk.rds.v3.instance.Instance` - # instance. - # :param backup: Either the id of a backup or a - # :class:`~otcextensions.sdk.rds.v3.backup.Backup` - # instance. - # :attrs attrs: The attributes to update on the instance represented - # by ``value``. - # - # :returns: The updated instance - # :rtype: :class:`~otcextensions.sdk.rds.v3.instance.Instance` - # """ - # # instance = self._get_resource(_instance.Instance, instance) - # # backup = self._get_resource(_backup.Backup, backup) - # - # res = _instance.Instance.new(**attrs) - # - # return res.create(self) + return self._update(_instance.InstanceConfiguration, + instance_id=instance.id, + **attrs) # ======= Configurations ======= def configurations(self, **attrs): @@ -293,7 +287,6 @@ def configurations(self, **attrs): return self._list( _configuration.ConfigurationGroup, paginated=False, - headers=self.get_os_headers(True), ) def get_configuration(self, cg): @@ -304,15 +297,9 @@ def get_configuration(self, cg): :class:`~otcextensions.sdk.rds.v3.configuration.ConfigurationGroup`. :returns: A Parameter Group Object - :rtype: :class:`~otcextensions.rds.v1.configuration.ConfigurationGroup` + :rtype: :class:`~otcextensions.rds.v3.configuration.ConfigurationGroup` """ - return self._get( - _configuration.ConfigurationGroup, - cg, - # project_id=self.session.get_project_id(), - # endpoint_override=self.get_os_endpoint(), - headers=self.get_os_headers(True) - ) + return self._get(_configuration.ConfigurationGroup, cg) def create_configuration(self, **attrs): """Creating a ConfigurationGroup @@ -323,8 +310,8 @@ def create_configuration(self, **attrs): :class:`~otcextensions.sdk.rds.v3.configuration.ConfigurationGroup` """ return self._create(_configuration.ConfigurationGroup, - headers=self.get_os_headers(True), - prepend_key=False, **attrs) + prepend_key=False, + **attrs) def delete_configuration(self, cg, ignore_missing=True): """Deleting a ConfigurationGroup @@ -342,9 +329,9 @@ def delete_configuration(self, cg, ignore_missing=True): :rtype: None """ self._delete( - _configuration.ConfigurationGroup, cg, + _configuration.ConfigurationGroup, + cg, ignore_missing=ignore_missing, - headers=self.get_os_headers(True) ) def find_configuration(self, name_or_id, ignore_missing=True): @@ -355,15 +342,13 @@ def find_configuration(self, name_or_id, ignore_missing=True): :class:`~otcextensions.sdk.rds.v3.configuration.Configurations`. :returns: A Parameter Group Object :rtype: - :class:`~otcextensions.rds.v1.configuration.ConfigurationGroup`. + :class:`~otcextensions.rds.v3.configuration.ConfigurationGroup`. """ - return self._find(_configuration.ConfigurationGroup, name_or_id, - # project_id=self.session.get_project_id(), - # endpoint_override=self.get_os_endpoint(), - # headers=self.get_os_headers(), + return self._find(_configuration.ConfigurationGroup, + name_or_id, ignore_missing=ignore_missing) - # ======= Backups ======= + # ======= Backups ======= def backups(self, instance, **params): """List Backups @@ -372,9 +357,7 @@ def backups(self, instance, **params): """ instance = self._get_resource(_instance.Instance, instance) params['instance_id'] = instance.id - return self._list( - _backup.Backup, paginated=False, **params, headers=self.get_os_headers(True) - ) + return self._list(_backup.Backup, paginated=False, **params) def create_backup(self, instance, **attrs): """Create a backups of instance @@ -384,16 +367,13 @@ def create_backup(self, instance, **attrs): """ instance = self._get_resource(_instance.Instance, instance) attrs['instance_id'] = instance.id - return self._create(_backup.Backup, - headers=self.get_os_headers(True), - prepend_key=False, **attrs - ) + return self._create(_backup.Backup, prepend_key=False, **attrs) def delete_backup(self, backup, ignore_missing=True): """Deletes given backup :param instance: The value can be either the ID of an instance or a - :class:`~openstack.database.v1.instance.Instance` instance. + :class:`~openstack.database.v3.instance.Instance` instance. :param bool ignore_missing: When set to ``False`` :class:`~openstack.exceptions.ResourceNotFound` will be raised when the instance does not exist. @@ -402,12 +382,9 @@ def delete_backup(self, backup, ignore_missing=True): :returns: ``None`` """ - return self._delete( - _backup.Backup, - backup, - ignore_missing=ignore_missing, - headers=self.get_os_headers(True), - ) + return self._delete(_backup.Backup, + backup, + ignore_missing=ignore_missing) def get_backup_policy(self, instance): """Obtaining a backup policy of the instance @@ -419,12 +396,9 @@ def get_backup_policy(self, instance): """ instance = self._get_resource(_instance.Instance, instance) - return self._get( - _backup.BackupPolicy, - requires_id=False, - instance_id=instance.id, - headers=self.get_os_headers(True), - ) + return self._get(_backup.BackupPolicy, + requires_id=False, + instance_id=instance.id) def set_backup_policy(self, instance, **attrs): """Sets the backup policy of the instance @@ -437,25 +411,16 @@ def set_backup_policy(self, instance, **attrs): :returns: ``None`` """ instance = self._get_resource(_instance.Instance, instance) - return self._update( - _backup.BackupPolicy, - instance_id=instance.id, - headers=self.get_os_headers(True), - **attrs) + return self._update(_backup.BackupPolicy, + instance_id=instance.id, + **attrs) - def get_backup_restore_time(self, instance): - """Obtaining a backup policy of the instance + def backup_file_links(self, backup_id): + """Obtaining a backup file download links - :param instance: This parameter can be either the ID of an instance - or a :class:`~openstack.sdk.rds.v3.instance.Instance` - :returns: A Backup RestoreTime - :rtype: :class:`~otcextensions.sdk.rds.v3.backup.BackupRestoreTime` + :param backup_id + :returns: files link + :rtype: :class:`~otcextensions.sdk.rds.v3.backup.BackupFiles` """ - instance = self._get_resource(_instance.Instance, instance) - return self._get( - _backup.BackupRestoreTime, - requires_id=False, - instance_id=instance.id, - headers=self.get_os_headers(True), - ) + return self._list(_backup.BackupFiles, backup_id=backup_id) diff --git a/otcextensions/sdk/rds/v3/backup.py b/otcextensions/sdk/rds/v3/backup.py index ed7835d58..7d741c206 100644 --- a/otcextensions/sdk/rds/v3/backup.py +++ b/otcextensions/sdk/rds/v3/backup.py @@ -12,17 +12,19 @@ from openstack import resource from otcextensions.sdk import sdk_resource + class Backup(sdk_resource.Resource): base_path = '/backups' resource_key = 'backup' resources_key = 'backups' - service_expectes_json_type = True + # service_expectes_json_type = True # capabilities allow_create = True allow_delete = True allow_list = True + allow_get = False _query_mapping = resource.QueryParameters( 'offset', @@ -98,13 +100,6 @@ class BackupPolicy(sdk_resource.Resource): #: *Type: string* period = resource.Body('period') - # @classmethod - # def new(cls, **attrs): - # return BackupPolicy( - # content_type='application/json', - # x_language='en-us', **attrs) - - # use put to create, but we don't require id def update(self, session, prepend_key=True, endpoint_override=None, headers=None): """Create a remote resource based on this instance. @@ -129,28 +124,28 @@ def update(self, session, prepend_key=True, return None -class BackupRestoreTime(sdk_resource.Resource): +class BackupFiles(resource.Resource): - base_path = '/instances/%(instance_id)s/restore-time' - resource_key = 'restore_time' + base_path = '/backup-files' + resources_key = 'files' # capabilities - allow_get = True - - #: instaceId - instance_id = resource.URI('instance_id') - # project_id = resource.URI('project_id') + allow_list = True - #: Start time - #: Indicates the start time of the recovery time period in - #: the UNIX timestamp format. The unit is - #: millisecond and the time zone is UTC. - #: *Type: string* - start_time = resource.Body('start_time') + _query_mapping = resource.QueryParameters( + 'backup_id' + ) - #: End time - #: Indicates the end time of the recovery time period in - #: the UNIX timestamp format. The unit is - #: millisecond and the time zone is UTC. - #: *Type: string* - end_time = resource.Body('end_time') + #: Indicates the file name + #: *Type: string* + name = resource.Body('name') + #: Indicates the file size in KB. + #: *Type: long* + size = resource.Body('size', type=int) + #: Indicates the link for downloading the backup file. + #: *Type: string* + download_link = resource.Body('download_link') + #: Indicates the link expiration time. + #: The format is "yyyy-mmddThh:mm:ssZ". + #: *Type: string* + link_expired_time = resource.Body('link_expired_time') diff --git a/otcextensions/sdk/rds/v3/configuration.py b/otcextensions/sdk/rds/v3/configuration.py index 75bb85884..912375b5e 100644 --- a/otcextensions/sdk/rds/v3/configuration.py +++ b/otcextensions/sdk/rds/v3/configuration.py @@ -10,67 +10,13 @@ # License for the specific language governing permissions and limitations # under the License. from openstack import _log -from openstack import exceptions from openstack import resource -from openstack import utils from otcextensions.sdk import sdk_resource _logger = _log.setup_logging('openstack') -# class InstanceConfiguration(sdk_resource.Resource): -# # TODO(agoncharov) most likely useless -# -# base_path = '/%(project_id)s/instances/%(instanceId)s/configuration' -# resource_key = 'instance' -# service = rds_service.RdsService() -# -# # capabilities -# allow_get = True -# -# # Properties -# # Instance of the configuration -# instanceId = resource.URI("instanceId") -# #: Configuration list, key value pairs -# #: *Type:dict* -# configuration = resource.Body('configuration', type=dict) -# -# -# class Parameter(sdk_resource.Resource): -# # TODO(agoncharov) most likely useless -# -# base_path = '/%(project_id)s/datastores/versions/' -# '%(datastore_version_id)s/parameters/' -# resources_key = 'configuration-parameters' -# service = rds_service.RdsService() -# -# # capabilities -# allow_list = True -# allow_get = True -# -# # Properties -# #: Parameter name -# name = resource.Body('name', alternate_id=True) -# #: Minimum value of the parameter -# #: *Type: int* -# min = resource.Body('min', type=int) -# #: Maximum value of the parameter -# #: *Type: int* -# max = resource.Body('max', type=int) -# #: Parameter type -# type = resource.Body('type') -# #: Value range -# value_range = resource.Body('value_range') -# #: Description -# description = resource.Body('description') -# #: Require restart or not -# #: *Type: bool* -# restart_required = resource.Body('restart_required', type=bool) -# #: Datastore version id -# datastore_version_id = resource.Body('datastore_version_id') - - class ConfigurationGroup(sdk_resource.Resource): base_path = '/configurations' @@ -116,79 +62,31 @@ class ConfigurationGroup(sdk_resource.Resource): values = resource.Body('values', type=dict) - def get_associated_instances(self, session, endpoint_override=None): - """Get associated instancs - - :param session: session (adapter) - :param endpoint_override: optional endpoint_override - - :returns: list of instance id/name dicts with instances - with this configuration group - :rtype: list of dicts - """ +class ApplyConfigurationGroup(sdk_resource.Resource): - request = self._prepare_request() - session = self._get_session(session) + base_path = '/configurations/%(config_id)s/apply' - if not endpoint_override: - if getattr(self, 'endpoint_override', None): - # If we have internal endpoint_override - use it - endpoint_override = self.endpoint_override - - # Build additional arguments to the DELETE call - args = self._prepare_override_args( - endpoint_override=endpoint_override, - request_headers=request.headers, - additional_headers={'Content-Type': 'application/json'} - ) - - # URL is a subpoin - url = utils.urljoin(request.url, 'instances') - - resp = session.get(url, **args) - resp = resp.json() - - if resp: - return resp['instances'] - - def _translate_response(self, response, has_body=None, error_message=None): - """Given a KSA response, inflate this instance with its data - - 'DELETE' operations don't return a body, so only try to work - with a body when has_body is True. - - This method updates attributes that correspond to headers - and body on this instance and clears the dirty set. - """ - if has_body is None: - has_body = self.has_body - # exceptions.raise_from_response(response, error_message=error_message) - if has_body: - body = response.json() - - errCode = body.get('errCode', None) - if errCode and errCode == 'RDS.0041': - if self.resource_key and self.resource_key in body: - body = body[self.resource_key] - - body = self._consume_body_attrs(body) - self._body.attributes.update(body) - self._body.clean() - elif errCode and errCode == 'RDS.0028': - raise exceptions.NotFoundException('Resource not found') - elif errCode and errCode != 'RDS.0041': - _logger.error('error during service invokation %s' % errCode) - raise exceptions.SDKException( - body.get('externalMessage', body) - ) - elif not errCode: - if self.resource_key and self.resource_key in body: - body = body[self.resource_key] - - body = self._consume_body_attrs(body) - self._body.attributes.update(body) - self._body.clean() + # capabilities + allow_update = True - headers = self._consume_header_attrs(response.headers) - self._header.attributes.update(headers) - self._header.clean() + #: configuration Id + configuration_id = resource.URI('configuration_id') + + #: configuration Id + #: *Type: uuid* + configuration_id = resource.Body('configuration_id') + #: configuration name + #: *Type: string* + configuration_name = resource.Body('configuration_name') + #: Apply Status + #: Indicates the parameter + #: group application result. + #: *Type: list* + apply_status = resource.Body('apply_status', type=list) + #: Success + #: Indicates whether all + #: parameter groups are + #: applied to DB instances + #: successfully. + #: *Type: bool* + success = resource.Body('success', type=bool) diff --git a/otcextensions/sdk/rds/v3/datastore.py b/otcextensions/sdk/rds/v3/datastore.py index ae31fcbd5..cadd34972 100644 --- a/otcextensions/sdk/rds/v3/datastore.py +++ b/otcextensions/sdk/rds/v3/datastore.py @@ -12,6 +12,7 @@ from openstack import resource from otcextensions.sdk import sdk_resource + class Datastore(sdk_resource.Resource): base_path = '/datastores/%(datastore_name)s' diff --git a/otcextensions/sdk/rds/v3/instance.py b/otcextensions/sdk/rds/v3/instance.py index 314e9857d..777ea5242 100644 --- a/otcextensions/sdk/rds/v3/instance.py +++ b/otcextensions/sdk/rds/v3/instance.py @@ -9,12 +9,11 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from openstack import _log from openstack import resource -from openstack import utils - +from openstack import _log from otcextensions.sdk import sdk_resource + _logger = _log.setup_logging('openstack') @@ -65,7 +64,7 @@ class Instance(sdk_resource.Resource): #: Instance created time #: *Type:str* created = resource.Body('created') - #datastore: Instance updated time + # datastore: Instance updated time #: *Type:str* updated = resource.Body('updated') #: DB default username @@ -96,7 +95,7 @@ class Instance(sdk_resource.Resource): #: *Type:str* maintenance_window = resource.Body('maintenance_window') #: Node information - #: Indicates the primary/standby DB + #: Indicates the primary/standby DB #: instance information #: *Type:list* nodes = resource.Body('nodes', type=list) @@ -119,3 +118,92 @@ class Instance(sdk_resource.Resource): #: Charge Info #: *Type: dict* backup_strategy = resource.Body('charge_info', type=dict) + + +class InstanceRecovery(sdk_resource.Resource): + + # capabilities + allow_create = True + + #: Specifies the restoration information + #: *Type: dict* + source = resource.Body('source', type=dict) + #: Specifies the restoration target + #: *Type: dict* + target = resource.Body('target', type=dict) + + +class InstanceConfiguration(sdk_resource.Resource): + + base_path = '/instances/%(instance_id)s/configurations' + + # capabilities + allow_get = True + allow_update = True + + #: instaceId + instance_id = resource.URI('instance_id') + + #: database version name + #: *Type: string* + datastore_version_name = resource.Body('datastore_version_name') + #: database name + #: *Type: string* + datastore_name = resource.Body('datastore_name') + #: Indicates the creation time in the following + #: format: yyyy-MM-ddTHH:mm:ssZ. + #: *Type: string* + created = resource.Body('created') + #: Indicates the update time in the following + #: format: yyyy-MM-ddTHH:mm:ssZ. + #: *Type: string* + updated = resource.Body('updated') + #: Indicates the parameter configuration + #: defined by users based on the default + #: parameter groups. + #: *Type: list* + configuration_parameters = resource.Body('configuration_parameters') + + def update(self, session, prepend_key=False): + """ + Method is overriden, because PUT without ID should be used + + :param session: The session to use for making this request. + :type session: :class:`~keystoneauth1.adapter.Adapter` + :param prepend_key: A boolean indicating whether the resource_key + should be prepended in a resource creation + request. Default to True. + + :return: None. + :raises: :exc:`~openstack.exceptions.MethodNotSupported` if + :data:`Resource.allow_create` is not set to ``True``. + """ + return self.update_no_id(session, prepend_key) + + +class InstanceRestoreTime(resource.Resource): + + base_path = '/instances/%(instance_id)s/restore-time' + + resource_key = 'restore_time' + + # capabilities + allow_get = True + + #: instaceId + instance_id = resource.URI('instance_id') + # project_id = resource.URI('project_id') + + #: Start time + #: Indicates the start time of the recovery time period in + #: the UNIX timestamp format. The unit is + #: millisecond and the time zone is UTC. + #: *Type: string* + start_time = resource.Body('start_time') + + #: End time + #: Indicates the end time of the recovery time period in + #: the UNIX timestamp format. The unit is + #: millisecond and the time zone is UTC. + #: *Type: string* + end_time = resource.Body('end_time') diff --git a/otcextensions/tests/unit/osclient/rds/v3/fakes.py b/otcextensions/tests/unit/osclient/rds/v3/fakes.py index 741c3c43b..8be2668e5 100644 --- a/otcextensions/tests/unit/osclient/rds/v3/fakes.py +++ b/otcextensions/tests/unit/osclient/rds/v3/fakes.py @@ -21,7 +21,7 @@ from otcextensions.tests.unit.osclient import test_base -#from otcextensions.sdk.rds.v3.configuration import ConfigurationGroup +# from otcextensions.sdk.rds.v3.configuration import ConfigurationGroup from otcextensions.sdk.rds.v3.datastore import Datastore from otcextensions.sdk.rds.v3 import flavor from otcextensions.sdk.rds.v3.instance import Instance @@ -29,7 +29,6 @@ class TestRds(utils.TestCommand): - def setUp(self): super(TestRds, self).setUp() @@ -40,12 +39,11 @@ def setUp(self): self.datastore_mock = FakeDatastore self.flavor_mock = FakeFlavor self.instance_mock = FakeInstance - #self.configuration_mock = FakeConfiguration + # self.configuration_mock = FakeConfiguration class FakeDatastore(object): """Fake one or more datastore versions.""" - @staticmethod def create_one(attrs=None, methods=None): """Create a fake datastore. @@ -92,7 +90,6 @@ def create_multiple(attrs=None, methods=None, count=2): class FakeFlavor(test_base.Fake): """Fake one or more VBS Policy""" - @classmethod def generate(cls): object_info = { @@ -160,7 +157,6 @@ def generate(cls): # class FakeInstance(object): """Fake one or more Instance.""" - @staticmethod def create_one(attrs=None, methods=None): """Create a fake Configuration. @@ -184,7 +180,9 @@ def create_one(attrs=None, methods=None): 'type': 'datastore-' + uuid.uuid4().hex, 'version': 'version-' + uuid.uuid4().hex, }, - 'flavor_ref': {'id': uuid.uuid4().hex}, + 'flavor_ref': { + 'id': uuid.uuid4().hex + }, 'volume': { 'type': 'type' + uuid.uuid4().hex, 'size': random.randint(1, 10280), @@ -211,33 +209,38 @@ def create_multiple(attrs=None, methods=None, count=2): """ objects = [] for i in range(0, count): - objects.append( - FakeInstance.create_one(attrs, methods) - ) + objects.append(FakeInstance.create_one(attrs, methods)) return objects class FakeBackup(test_base.Fake): """Fake one or more VBS Policy""" - @classmethod def generate(cls): object_info = { - 'id': 'id-' + uuid.uuid4().hex, - 'name': 'name-' + uuid.uuid4().hex, - 'description': uuid.uuid4().hex, + 'id': + 'id-' + uuid.uuid4().hex, + 'name': + 'name-' + uuid.uuid4().hex, + 'description': + uuid.uuid4().hex, 'datastore': { 'type': 'datastore-' + uuid.uuid4().hex, 'version': 'version-' + uuid.uuid4().hex, }, - 'instance_id': 'instance_id-' + uuid.uuid4().hex, - 'size': random.randint(0, 100), - 'status': random.choice(['BUILDING', 'COMPLETED', 'FAILED', - 'DELETING']), - 'type': random.choice(['auto', 'manual']), - 'begin_time': uuid.uuid4().hex, - 'end_time': uuid.uuid4().hex + 'instance_id': + 'instance_id-' + uuid.uuid4().hex, + 'size': + random.randint(0, 100), + 'status': + random.choice(['BUILDING', 'COMPLETED', 'FAILED', 'DELETING']), + 'type': + random.choice(['auto', 'manual']), + 'begin_time': + uuid.uuid4().hex, + 'end_time': + uuid.uuid4().hex } obj = backup.Backup.existing(**object_info) return obj diff --git a/otcextensions/tests/unit/osclient/rds/v3/test_backup.py b/otcextensions/tests/unit/osclient/rds/v3/test_backup.py index 731937740..6dc60e62c 100644 --- a/otcextensions/tests/unit/osclient/rds/v3/test_backup.py +++ b/otcextensions/tests/unit/osclient/rds/v3/test_backup.py @@ -45,18 +45,16 @@ def setUp(self): self.client.backups = mock.Mock() def test_list_default(self): - arglist = ['test-instance' - ] + arglist = ['test-instance'] - verifylist = [('instance', 'test-instance'), + verifylist = [ + ('instance', 'test-instance'), ] # Verify cm is triggereg with default parameters parsed_args = self.check_parser(self.cmd, arglist, verifylist) # Set the response - self.client.backups.side_effect = [ - self._objects - ] + self.client.backups.side_effect = [self._objects] # Trigger the action columns, data = self.cmd.take_action(parsed_args) @@ -71,8 +69,8 @@ class TestCreate(fakes.TestRds): _obj = fakes.FakeBackup.create_one() - columns = ('ID', 'Name', 'type', 'instance_id', - 'status', 'begin_time', 'databases') + columns = ('ID', 'Name', 'type', 'instance_id', 'status', 'begin_time', + 'databases') data = ( _obj.id, @@ -96,7 +94,8 @@ def test_create(self): arglist = [ 'test-backup', 'test-instance', - '--description', 'test description', + '--description', + 'test description', ] verifylist = [ ('name', 'test-backup'), @@ -107,9 +106,7 @@ def test_create(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) # Set the response - self.client.create_backup.side_effect = [ - self._obj - ] + self.client.create_backup.side_effect = [self._obj] # Trigger the action columns, data = self.cmd.take_action(parsed_args) @@ -123,7 +120,6 @@ def test_create(self): class TestDelete(fakes.TestRds): - def setUp(self): super(TestDelete, self).setUp() @@ -144,23 +140,16 @@ def test_delete_default(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) # Set the response - self.client.delete_backup.side_effect = [ - {} - ] + self.client.delete_backup.side_effect = [{}] # Trigger the action self.cmd.take_action(parsed_args) - self.client.delete_backup.assert_called_once_with( - backup='bck1', - ignore_missing=False - ) + self.client.delete_backup.assert_called_once_with(backup='bck1', + ignore_missing=False) def test_delete_multiple(self): - arglist = [ - 'bck1', - 'bck2' - ] + arglist = ['bck1', 'bck2'] verifylist = [ ('backup', ['bck1', 'bck2']), diff --git a/otcextensions/tests/unit/osclient/rds/v3/test_instance.py b/otcextensions/tests/unit/osclient/rds/v3/test_instance.py index 9330822ab..651641045 100644 --- a/otcextensions/tests/unit/osclient/rds/v3/test_instance.py +++ b/otcextensions/tests/unit/osclient/rds/v3/test_instance.py @@ -21,8 +21,10 @@ class TestListDatabaseInstances(rds_fakes.TestRds): - column_list_headers = ['ID', 'Name', 'Datastore Type', 'Datastore Version', 'Status', - 'Flavor_ref', 'Type', 'Size', 'Region'] + column_list_headers = [ + 'ID', 'Name', 'Datastore Type', 'Datastore Version', 'Status', + 'Flavor_ref', 'Type', 'Size', 'Region' + ] def setUp(self): super(TestListDatabaseInstances, self).setUp() @@ -34,32 +36,20 @@ def setUp(self): self.objects = self.instance_mock.create_multiple(3) self.object_data = [] for s in self.objects: - self.object_data.append(( - s.id, - s.name, - s.datastore['type'], - s.datastore['version'], - s.status, - s.flavor_ref, - s.type, - s.volume['size'], - s.region - )) + self.object_data.append( + (s.id, s.name, s.datastore['type'], s.datastore['version'], + s.status, s.flavor_ref, s.type, s.volume['size'], s.region)) def test_list(self): - arglist = [ - ] + arglist = [] - verifylist = [ - ] + verifylist = [] # Verify cm is triggereg with default parameters parsed_args = self.check_parser(self.cmd, arglist, verifylist) # Set the response - self.app.client_manager.rds.instances.side_effect = [ - self.objects - ] + self.app.client_manager.rds.instances.side_effect = [self.objects] # Trigger the action columns, data = self.cmd.take_action(parsed_args) @@ -74,7 +64,8 @@ class TestShowDatabaseInstance(rds_fakes.TestRds): column_list_headers = [ 'datastore', 'flavor_ref', 'id', 'name', 'region' - 'status', 'volume'] + 'status', 'volume' + ] def setUp(self): super(TestShowDatabaseInstance, self).setUp() @@ -108,10 +99,8 @@ def test_show(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) # Set the response - self.app.client_manager.rds.find_instance.side_effect = [ - self.object - ] - + self.app.client_manager.rds.find_instance.side_effect = [self.object] + # Trigger the action columns, data = self.cmd.take_action(parsed_args) self.app.client_manager.rds.find_instance.assert_called() @@ -121,7 +110,6 @@ def test_show(self): class TestDeleteDatabaseInstance(rds_fakes.TestRds): - def setUp(self): super(TestDeleteDatabaseInstance, self).setUp() @@ -142,9 +130,7 @@ def test_delete(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) # Set the response - self.app.client_manager.rds.delete_instance.side_effect = [ - True - ] + self.app.client_manager.rds.delete_instance.side_effect = [True] # Trigger the action self.cmd.take_action(parsed_args) @@ -153,7 +139,6 @@ def test_delete(self): class TestCreateDatabaseInstance(rds_fakes.TestRds): - def setUp(self): super(TestCreateDatabaseInstance, self).setUp() @@ -170,25 +155,22 @@ def setUp(self): def test_action(self): arglist = [ - 'inst_name', '--flavor', 'test-flavor', '--availability_zone', 'test-az-01', '--datastore', 'MySQL', '--datastore_version', '5.7', - '--vpc_id', 'test-vpc-id', '--subnet_id', 'test-subnet-id', '--security_group', 'test-subnet-id', '--volume_type', 'ULTRAHIGH', - '--size', '100', '--password', 'testtest', '--region', 'test-region' + 'inst_name', 'test-flavor', '--availability_zone', 'test-az-01', + '--datastore', 'MySQL', '--datastore_version', '5.7', + '--network_id', 'test-vpc-id', '--subnet_id', 'test-subnet-id', + '--security_group', 'test-subnet-id', '--volume_type', 'ULTRAHIGH', + '--size', '100', '--password', 'testtest', '--region', + 'test-region' ] - verifylist = [ - ('name', 'inst_name'), - ('flavor_ref', 'test-flavor'), - ('availability_zone', 'test-az-01'), - ('datastore', 'MySQL'), - ('datastore_version', '5.7'), - ('vpc_id', 'test-vpc-id'), - ('subnet_id', 'test-subnet-id'), - ('security_group_id', 'test-subnet-id'), - ('volume_type', 'ULTRAHIGH'), - ('size', 100), - ('password', 'testtest'), - ('region', 'test-region') - ] + verifylist = [('name', 'inst_name'), ('flavor_ref', 'test-flavor'), + ('availability_zone', 'test-az-01'), + ('datastore', 'MySQL'), ('datastore_version', '5.7'), + ('vpc_id', 'test-vpc-id'), + ('subnet_id', 'test-subnet-id'), + ('security_group_id', 'test-subnet-id'), + ('volume_type', 'ULTRAHIGH'), ('size', 100), + ('password', 'testtest'), ('region', 'test-region')] # Verify cm is triggereg with default parameters parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/otcextensions/tests/unit/sdk/rds/v3/test_datastore.py b/otcextensions/tests/unit/sdk/rds/v3/test_datastore.py index 80b4e81c5..f241d9e3a 100644 --- a/otcextensions/tests/unit/sdk/rds/v3/test_datastore.py +++ b/otcextensions/tests/unit/sdk/rds/v3/test_datastore.py @@ -14,16 +14,11 @@ from otcextensions.sdk.rds.v3 import datastore - IDENTIFIER = 'IDENTIFIER' -EXAMPLE = { - "id": IDENTIFIER, - "name": "5.7" -} +EXAMPLE = {"id": IDENTIFIER, "name": "5.7"} class TestDatastore(base.TestCase): - def setUp(self): super(TestDatastore, self).setUp() @@ -37,10 +32,10 @@ def test_basic(self): self.assertFalse(sot.allow_create) self.assertFalse(sot.allow_delete) self.assertFalse(sot.allow_commit) - self.assertDictEqual({'limit': 'limit', - 'marker': 'marker'}, - sot._query_mapping._mapping) - + self.assertDictEqual({ + 'limit': 'limit', + 'marker': 'marker' + }, sot._query_mapping._mapping) def test_make_it(self): sot = datastore.Datastore(**EXAMPLE) diff --git a/otcextensions/tests/unit/sdk/rds/v3/test_instance.py b/otcextensions/tests/unit/sdk/rds/v3/test_instance.py index 77f590273..acb6a35e0 100644 --- a/otcextensions/tests/unit/sdk/rds/v3/test_instance.py +++ b/otcextensions/tests/unit/sdk/rds/v3/test_instance.py @@ -9,8 +9,6 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -import copy - from keystoneauth1 import adapter import mock @@ -21,10 +19,7 @@ # RDS requires those headers to be present in the request, to native API # otherwise 404 -RDS_HEADERS = { - 'Content-Type': 'application/json', - 'X-Language': 'en-us' -} +RDS_HEADERS = {'Content-Type': 'application/json', 'X-Language': 'en-us'} # RDS requires those headers to be present in the request, to OS-compat API # otherwise 404 @@ -35,49 +30,66 @@ # PROJECT_ID = '123' IDENTIFIER = 'IDENTIFIER' EXAMPLE = { - "flavor_ref": "rds.mysql.s1.large", - "id": IDENTIFIER, - "status": "ACTIVE", - "name": "mysql-0820-022709-01", - "port": 3306, - "type": "Single", - "region": "eu-de", - "volume": { - "type": "ULTRAHIGH", - "size": 100 - }, - "datastore": { - "type": "mysql", - "version": "5.7" - }, - "created": "2018-08-20T02:33:49+0800", - "updated": "2018-08-20T02:33:50+0800", - "nodes": [{ - "id": "06f1c2ad57604ae89e153e4d27f4e4b8no01", - "name": "mysql-0820-022709-01_node0", - "role": "master", - "status": "ACTIVE", - "availability_zone": "eu-de-01" - }], - "private_ips": ["192.168.0.142"], - "public_ips": ["10.154.219.187", "10.154.219.186"], - "db_user_name": "root", - "vpc_id": "b21630c1-e7d3-450d-907d-39ef5f445ae7", - "subnet_id": "45557a98-9e17-4600-8aec-999150bc4eef", - "security_group_id": "38815c5c-482b-450a-80b6-0a301f2afd97", - "switch_strategy": "", - "backup_strategy": { - "start_time": "19:00-20:00", - "keep_days": 7 - }, - "maintenance_window": "02:00-06:00", - "related_instance": [], - "disk_encryption_id": "", - "time_zone": "" + "flavor_ref": + "rds.mysql.s1.large", + "id": + IDENTIFIER, + "status": + "ACTIVE", + "name": + "mysql-0820-022709-01", + "port": + 3306, + "type": + "Single", + "region": + "eu-de", + "volume": { + "type": "ULTRAHIGH", + "size": 100 + }, + "datastore": { + "type": "mysql", + "version": "5.7" + }, + "created": + "2018-08-20T02:33:49+0800", + "updated": + "2018-08-20T02:33:50+0800", + "nodes": [{ + "id": "06f1c2ad57604ae89e153e4d27f4e4b8no01", + "name": "mysql-0820-022709-01_node0", + "role": "master", + "status": "ACTIVE", + "availability_zone": "eu-de-01" + }], + "private_ips": ["192.168.0.142"], + "public_ips": ["10.154.219.187", "10.154.219.186"], + "db_user_name": + "root", + "vpc_id": + "b21630c1-e7d3-450d-907d-39ef5f445ae7", + "subnet_id": + "45557a98-9e17-4600-8aec-999150bc4eef", + "security_group_id": + "38815c5c-482b-450a-80b6-0a301f2afd97", + "switch_strategy": + "", + "backup_strategy": { + "start_time": "19:00-20:00", + "keep_days": 7 + }, + "maintenance_window": + "02:00-06:00", + "related_instance": [], + "disk_encryption_id": + "", + "time_zone": + "" } -class TestInstance(base.TestCase): +class TestInstance(base.TestCase): def setUp(self): super(TestInstance, self).setUp() self.sess = mock.Mock(spec=adapter.Adapter) @@ -125,18 +137,26 @@ def test_list(self): mock_response.status_code = 200 mock_response.json.return_value = { "instances": [{ - "id": IDENTIFIER, - "status": "ACTIVE", - "name": "mysql-0820-022709-01", - "port": 3306, - "type": "Single", - "region": "eu-de", + "id": + IDENTIFIER, + "status": + "ACTIVE", + "name": + "mysql-0820-022709-01", + "port": + 3306, + "type": + "Single", + "region": + "eu-de", "datastore": { "type": "mysql", "version": "5.7" }, - "created": "2018-08-20T02:33:49+0800", - "updated": "2018-08-20T02:33:50+0800", + "created": + "2018-08-20T02:33:49+0800", + "updated": + "2018-08-20T02:33:50+0800", "volume": { "type": "ULTRAHIGH", "size": 100 @@ -150,23 +170,33 @@ def test_list(self): }], "private_ips": ["192.168.0.142"], "public_ips": ["10.154.219.187", "10.154.219.186"], - "db_user_name": "root", - "vpc_id": "b21630c1-e7d3-450d-907d-39ef5f445ae7", - "subnet_id": "45557a98-9e17-4600-8aec-999150bc4eef", - "security_group_id": "38815c5c-482b-450a-80b6-0a301f2afd97", - "flavor_ref": "rds.mysql.s1.large", - "switch_strategy": "", + "db_user_name": + "root", + "vpc_id": + "b21630c1-e7d3-450d-907d-39ef5f445ae7", + "subnet_id": + "45557a98-9e17-4600-8aec-999150bc4eef", + "security_group_id": + "38815c5c-482b-450a-80b6-0a301f2afd97", + "flavor_ref": + "rds.mysql.s1.large", + "switch_strategy": + "", "backup_strategy": { "start_time": "19:00-20:00", "keep_days": 7 }, - "maintenance_window": "02:00-06:00", + "maintenance_window": + "02:00-06:00", "related_instance": [], - "disk_encryption_id": "", - "time_zone": "" - }], - "total_count": 1 - } + "disk_encryption_id": + "", + "time_zone": + "" + }], + "total_count": + 1 + } self.sess.get.return_value = mock_response @@ -194,4 +224,4 @@ def test_list(self): # 'id': IDENTIFIER, # }) # body = {'restore': {'backupRef': backupRef}} -# sess.post.assert_called_with(url, json=body, headers={'X-Language': 'en-us'}) +# sess.post.assert_called_with(url, json=body) diff --git a/otcextensions/tests/unit/sdk/rds/v3/test_proxy.py b/otcextensions/tests/unit/sdk/rds/v3/test_proxy.py index 70ca4cdf7..f05d4968c 100644 --- a/otcextensions/tests/unit/sdk/rds/v3/test_proxy.py +++ b/otcextensions/tests/unit/sdk/rds/v3/test_proxy.py @@ -24,4 +24,13 @@ def setUp(self): class TestRdsFlavor(TestRdsProxy): def test_flavors(self): - self.verify_list(self.proxy.flavors, flavor.Flavor, method_kwargs={'datastore_name': 'MySQL', 'version_name': '5.7'}, expected_kwargs={'datastore_name': 'MySQL', 'version_name': '5.7'}) + self.verify_list(self.proxy.flavors, + flavor.Flavor, + method_kwargs={ + 'datastore_name': 'MySQL', + 'version_name': '5.7' + }, + expected_kwargs={ + 'datastore_name': 'MySQL', + 'version_name': '5.7' + }) diff --git a/setup.cfg b/setup.cfg index 4be9e08c3..fafc60ce6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -99,16 +99,21 @@ openstack.rds.v3 = rds_configuration_list = otcextensions.osclient.rds.v3.configuration:ListConfigurations rds_configuration_show = otcextensions.osclient.rds.v3.configuration:ShowConfiguration rds_configuration_update = otcextensions.osclient.rds.v3.configuration:UpdateConfiguration - rds_configuration_apply = otcextensions.osclient.rds.v3.configuration:ApplyConfiguration rds_configuration_create = otcextensions.osclient.rds.v3.configuration:CreateConfiguration rds_configuration_delete = otcextensions.osclient.rds.v3.configuration:DeleteConfiguration rds_instance_list = otcextensions.osclient.rds.v3.instance:ListDatabaseInstances rds_instance_show = otcextensions.osclient.rds.v3.instance:ShowDatabaseInstance rds_instance_create = otcextensions.osclient.rds.v3.instance:CreateDatabaseInstance rds_instance_delete = otcextensions.osclient.rds.v3.instance:DeleteDatabaseInstance + rds_instance_restore = otcextensions.osclient.rds.v3.instance:RestoreDatabaseInstance + rds_instance_restore_new = otcextensions.osclient.rds.v3.instance:RestoreNewDatabaseInstance + rds_instance_configuration_show = otcextensions.osclient.rds.v3.instance:ShowDatabaseConfiguration + rds_instance_backup_policy_show = otcextensions.osclient.rds.v3.instance:ShowDatabaseBackupPolicy + rds_instance_backup_policy_update = otcextensions.osclient.rds.v3.instance:UpdateDatabaseBackupPolicy rds_backup_list = otcextensions.osclient.rds.v3.backup:ListBackup rds_backup_create = otcextensions.osclient.rds.v3.backup:CreateBackup rds_backup_delete = otcextensions.osclient.rds.v3.backup:DeleteBackup + rds_backup_file_links = otcextensions.osclient.rds.v3.backup:ShowBackupFileLinks openstack.auto_scaling.v1 = as_group_list = otcextensions.osclient.auto_scaling.v1.group:ListAutoScalingGroup From 66b46221ec9bea0b752bda51e7e662736beed9a5 Mon Sep 17 00:00:00 2001 From: vineet-pruthi Date: Fri, 4 Oct 2019 07:41:35 +0000 Subject: [PATCH 07/65] Adding Backup Policy for RDSv3 --- otcextensions/osclient/rds/v3/backup.py | 120 +++++++++++++++++++++- otcextensions/osclient/rds/v3/instance.py | 21 ++++ otcextensions/sdk/rds/v3/_proxy.py | 20 ++-- otcextensions/sdk/rds/v3/backup.py | 8 +- setup.cfg | 10 +- 5 files changed, 160 insertions(+), 19 deletions(-) diff --git a/otcextensions/osclient/rds/v3/backup.py b/otcextensions/osclient/rds/v3/backup.py index 2dc03af93..70c7cc8bf 100644 --- a/otcextensions/osclient/rds/v3/backup.py +++ b/otcextensions/osclient/rds/v3/backup.py @@ -11,16 +11,23 @@ # under the License. # """Backup v3 action implementations""" +import argparse import logging +from osc_lib import exceptions from osc_lib import utils from osc_lib.command import command +from otcextensions.common import sdk_utils from otcextensions.i18n import _ LOG = logging.getLogger(__name__) +def _get_columns(item): + column_map = {} + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + def set_attributes_for_print_detail(obj): info = {} attr_list = [ @@ -31,7 +38,10 @@ def set_attributes_for_print_detail(obj): 'status', 'begin_time', 'end_time', - 'instance_id'] + 'instance_id', + 'start_time', + 'period', + 'keep_days'] for attr in dir(obj): if attr == 'datastore' and getattr(obj, attr): info['datastore_type'] = obj.datastore['type'] @@ -199,3 +209,111 @@ def take_action(self, parsed_args): client.delete_backup( backup=bck, ignore_missing=False) + + +class ShowBackupPolicy(command.Command): + _description = _('Show Database Backup Policy') + + columns = ('keep days', 'period', 'start time') + def get_parser(self, prog_name): + parser = super(ShowBackupPolicy, self).get_parser(prog_name) + parser.add_argument( + 'instance', + metavar='', + help=_('Instance ID or Name') + ) + return parser + + def take_action(self, parsed_args): + + client = self.app.client_manager.rds + data = client.get_backup_policy(parsed_args.instance) + + return ( + self.columns, + utils.get_dict_properties( + set_attributes_for_print_detail(data), + self.columns, + ) + ) + + +class UpdateBackupPolicy(command.Command): + _description = _('Show Database Backup Policy') + + columns = ('keep days', 'period', 'start time') + def get_parser(self, prog_name): + parser = super(UpdateBackupPolicy, self).get_parser(prog_name) + parser.add_argument( + 'instance', + metavar='', + help=_('Instance ID or Name') + ) + parser.add_argument( + '--keep_days', + metavar='', + type=int, + help=_('Specifies the number of days to' + 'retain the generated backup files.') + ) + parser.add_argument( + '--start_time', + metavar='', + help=_('Specifies the backup time window.' + 'The value must be a valid value in the' + '"hh:mm-HH:MM" format.') + ) + parser.add_argument( + '--period', + metavar='', + help=_('Specifies the backup cycle' + 'configuration. Data will be' + 'automatically backed up on the' + 'selected days every week.') + ) + return parser + + def take_action(self, parsed_args): + + client = self.app.client_manager.rds + args_list = ['keep_days', 'start_time', 'period'] + attrs = {} + for arg in args_list: + if getattr(parsed_args, arg): + attrs[arg] = getattr(parsed_args, arg) + data = client.set_backup_policy(parsed_args.instance, **attrs) + + return ( + self.columns, + utils.get_dict_properties( + set_attributes_for_print_detail(data), + self.columns, + ) + ) + + +class ListBackupDownloadLinks(command.Command): + _description = _('List Backup Download Links') + + columns = ('Name', 'Size', 'Download Link') + + def get_parser(self, prog_name): + parser = super(ListBackupDownloadLinks, self).get_parser(prog_name) + parser.add_argument( + 'backup_id', + metavar='', + help=_('ID of the backup') + ) + return parser + + def take_action(self, parsed_args): + + client = self.app.client_manager.rds + data = client.backup_download_links(backup_id=parsed_args.backup_id) + return ( + self.columns, + (utils.get_dict_properties( + set_attributes_for_print_detail(s), + self.columns, + ) for s in data) + ) diff --git a/otcextensions/osclient/rds/v3/instance.py b/otcextensions/osclient/rds/v3/instance.py index 6d0da421a..d7a3b143b 100644 --- a/otcextensions/osclient/rds/v3/instance.py +++ b/otcextensions/osclient/rds/v3/instance.py @@ -402,3 +402,24 @@ def take_action(self, parsed_args): backup=parsed_args.backup, restore_time=parsed_args.restore_time, target_instance=parsed_args.target_instance) + +class ShowDatabaseConfiguration(command.Command): + + _description = _("Show Configuration details associated to instance") + + def get_parser(self, prog_name): + parser = super(ShowDatabaseConfiguration, self).get_parser(prog_name) + parser.add_argument('instance', + metavar='', + type=str, + help=_('ID or name of the instance.')) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.rds + + obj = client.get_instance_configuration(instance=parsed_args.instance) + + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + return (display_columns, data) diff --git a/otcextensions/sdk/rds/v3/_proxy.py b/otcextensions/sdk/rds/v3/_proxy.py index f8dd06f43..0af6f9580 100644 --- a/otcextensions/sdk/rds/v3/_proxy.py +++ b/otcextensions/sdk/rds/v3/_proxy.py @@ -245,8 +245,9 @@ def get_instance_restore_time(self, instance): :rtype: :class:`~otcextensions.sdk.rds.v3.instance.InstanceRestoreTime` """ - instance = self._get_resource(_instance.Instance, instance) + instance = self.find_instance(instance) return self._get(_instance.InstanceRestoreTime, + requires_id=False, instance_id=instance.id) def get_instance_configuration(self, instance): @@ -259,8 +260,9 @@ def get_instance_configuration(self, instance): `~otcextensions.sdk.rds.v3.instance.InstanceConfiguration` """ - instance = self._get_resource(_instance.Instance, instance) + instance = self.find_instance(instance) return self._get(_instance.InstanceConfiguration, + requires_id=False, instance_id=instance.id) def update_instance_configuration(self, instance, **attrs): @@ -355,7 +357,7 @@ def backups(self, instance, **params): :returns: A generator of backup :rtype: :class:`~otcextensions.sdk.rds.v3.backup.Backup` """ - instance = self._get_resource(_instance.Instance, instance) + instance = self.find_instance(instance) params['instance_id'] = instance.id return self._list(_backup.Backup, paginated=False, **params) @@ -365,7 +367,7 @@ def create_backup(self, instance, **attrs): :returns: A new backup object :rtype: :class:`~otcextensions.sdk.rds.v3.backup.Backup` """ - instance = self._get_resource(_instance.Instance, instance) + instance = self.find_instance(instance) attrs['instance_id'] = instance.id return self._create(_backup.Backup, prepend_key=False, **attrs) @@ -386,7 +388,7 @@ def delete_backup(self, backup, ignore_missing=True): backup, ignore_missing=ignore_missing) - def get_backup_policy(self, instance): + def get_backup_policy(self, instance, ignore_missing=True): """Obtaining a backup policy of the instance :param instance: This parameter can be either the ID of an instance @@ -395,9 +397,10 @@ def get_backup_policy(self, instance): :rtype: :class:`~otcextensions.sdk.rds.v3.backup.BackupPolicy` """ - instance = self._get_resource(_instance.Instance, instance) + instance = self.find_instance(instance) return self._get(_backup.BackupPolicy, requires_id=False, + ignore_missing=ignore_missing, instance_id=instance.id) def set_backup_policy(self, instance, **attrs): @@ -410,12 +413,13 @@ def set_backup_policy(self, instance, **attrs): :returns: ``None`` """ - instance = self._get_resource(_instance.Instance, instance) + instance = self.find_instance(instance) return self._update(_backup.BackupPolicy, + requires_id=False, instance_id=instance.id, **attrs) - def backup_file_links(self, backup_id): + def backup_download_links(self, backup_id): """Obtaining a backup file download links :param backup_id diff --git a/otcextensions/sdk/rds/v3/backup.py b/otcextensions/sdk/rds/v3/backup.py index 7d741c206..096d40376 100644 --- a/otcextensions/sdk/rds/v3/backup.py +++ b/otcextensions/sdk/rds/v3/backup.py @@ -101,7 +101,7 @@ class BackupPolicy(sdk_resource.Resource): period = resource.Body('period') def update(self, session, prepend_key=True, - endpoint_override=None, headers=None): + endpoint_override=False, headers=False, requests_auth=False): """Create a remote resource based on this instance. Method is overriden, because PUT without ID should be used @@ -117,14 +117,12 @@ def update(self, session, prepend_key=True, :data:`Resource.allow_create` is not set to ``True``. """ self.update_no_id( - session, prepend_key, - # endpoint_override=endpoint_override, - headers=headers) + session, prepend_key) return None -class BackupFiles(resource.Resource): +class BackupFiles(sdk_resource.Resource): base_path = '/backup-files' resources_key = 'files' diff --git a/setup.cfg b/setup.cfg index fafc60ce6..9d301b186 100644 --- a/setup.cfg +++ b/setup.cfg @@ -105,15 +105,15 @@ openstack.rds.v3 = rds_instance_show = otcextensions.osclient.rds.v3.instance:ShowDatabaseInstance rds_instance_create = otcextensions.osclient.rds.v3.instance:CreateDatabaseInstance rds_instance_delete = otcextensions.osclient.rds.v3.instance:DeleteDatabaseInstance + rds_backup_policy_show = otcextensions.osclient.rds.v3.backup:ShowBackupPolicy + rds_backup_policy_update = otcextensions.osclient.rds.v3.backup:UpdateBackupPolicy rds_instance_restore = otcextensions.osclient.rds.v3.instance:RestoreDatabaseInstance - rds_instance_restore_new = otcextensions.osclient.rds.v3.instance:RestoreNewDatabaseInstance - rds_instance_configuration_show = otcextensions.osclient.rds.v3.instance:ShowDatabaseConfiguration - rds_instance_backup_policy_show = otcextensions.osclient.rds.v3.instance:ShowDatabaseBackupPolicy - rds_instance_backup_policy_update = otcextensions.osclient.rds.v3.instance:UpdateDatabaseBackupPolicy + rds_instance_create_from_backup = otcextensions.osclient.rds.v3.instance:RestoreNewDatabaseInstance + # rds_instance_configuration_show = otcextensions.osclient.rds.v3.instance:ShowDatabaseConfiguration rds_backup_list = otcextensions.osclient.rds.v3.backup:ListBackup rds_backup_create = otcextensions.osclient.rds.v3.backup:CreateBackup rds_backup_delete = otcextensions.osclient.rds.v3.backup:DeleteBackup - rds_backup_file_links = otcextensions.osclient.rds.v3.backup:ShowBackupFileLinks + rds_backup_download_links = otcextensions.osclient.rds.v3.backup:ListBackupDownloadLinks openstack.auto_scaling.v1 = as_group_list = otcextensions.osclient.auto_scaling.v1.group:ListAutoScalingGroup From 6f10c6b031bed0ea1e38b6739d5fb09756033f72 Mon Sep 17 00:00:00 2001 From: vineet-pruthi Date: Fri, 4 Oct 2019 11:48:22 +0000 Subject: [PATCH 08/65] Adding RDSv3 restore functionality --- otcextensions/osclient/rds/v3/backup.py | 203 ++++++++-------------- otcextensions/osclient/rds/v3/instance.py | 116 ++++++++++++- otcextensions/sdk/rds/v3/_proxy.py | 5 +- otcextensions/sdk/rds/v3/backup.py | 28 ++- otcextensions/sdk/rds/v3/instance.py | 13 +- setup.cfg | 2 +- 6 files changed, 211 insertions(+), 156 deletions(-) diff --git a/otcextensions/osclient/rds/v3/backup.py b/otcextensions/osclient/rds/v3/backup.py index 70c7cc8bf..b62c58214 100644 --- a/otcextensions/osclient/rds/v3/backup.py +++ b/otcextensions/osclient/rds/v3/backup.py @@ -11,10 +11,8 @@ # under the License. # """Backup v3 action implementations""" -import argparse import logging -from osc_lib import exceptions from osc_lib import utils from osc_lib.command import command @@ -28,20 +26,13 @@ def _get_columns(item): column_map = {} return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + def set_attributes_for_print_detail(obj): info = {} attr_list = [ - 'id', - 'name', - 'type', - 'size', - 'status', - 'begin_time', - 'end_time', - 'instance_id', - 'start_time', - 'period', - 'keep_days'] + 'id', 'name', 'type', 'size', 'status', 'begin_time', 'end_time', + 'instance_id', 'start_time', 'period', 'keep_days' + ] for attr in dir(obj): if attr == 'datastore' and getattr(obj, attr): info['datastore_type'] = obj.datastore['type'] @@ -58,13 +49,8 @@ def set_attributes_for_print_detail(obj): class ListBackup(command.Lister): _description = _("List database backups/snapshots") - columns = ( - 'ID', - 'Name', - 'Type', - 'Instance Id', - 'Datastore Type', - 'Datastore Version') + columns = ('ID', 'Name', 'Type', 'Instance Id', 'Datastore Type', + 'Datastore Version') def get_parser(self, prog_name): parser = super(ListBackup, self).get_parser(prog_name) @@ -111,57 +97,45 @@ def take_action(self, parsed_args): client = self.app.client_manager.rds attrs = {} args_list = [ - 'backup_id', - 'backup_type', - 'offset', - 'limit', - 'begin_time', - 'end_time'] + 'backup_id', 'backup_type', 'offset', 'limit', 'begin_time', + 'end_time' + ] for arg in args_list: if getattr(parsed_args, arg): attrs[arg] = getattr(parsed_args, arg) data = client.backups(parsed_args.instance, **attrs) - return ( + return (self.columns, (utils.get_dict_properties( + set_attributes_for_print_detail(s), self.columns, - (utils.get_dict_properties( - set_attributes_for_print_detail(s), - self.columns, - ) for s in data) - ) + ) for s in data)) class CreateBackup(command.ShowOne): _description = _('Create Database backup') - columns = ('ID', 'Name', 'type', 'instance_id', - 'status', 'begin_time', 'databases') + columns = ('ID', 'Name', 'type', 'instance_id', 'status', 'begin_time', + 'databases') def get_parser(self, prog_name): parser = super(CreateBackup, self).get_parser(prog_name) - parser.add_argument( - 'name', - metavar='', - help=_('Name for the backup') - ) + parser.add_argument('name', + metavar='', + help=_('Name for the backup')) parser.add_argument( 'instance', metavar='', - help=_('ID or Name of the instance to create backup from') - ) - parser.add_argument( - '--description', - metavar='', - help=_('Description for the backup') - ) - parser.add_argument( - '--databases', - metavar='', - help=_('Specifies a list of self-built SQL Server' - 'databases that are partially backed up' - '(Only SQL Server support partial backups.)') - ) + help=_('ID or Name of the instance to create backup from')) + parser.add_argument('--description', + metavar='', + help=_('Description for the backup')) + parser.add_argument('--databases', + metavar='', + help=_( + 'Specifies a list of self-built SQL Server' + 'databases that are partially backed up' + '(Only SQL Server support partial backups.)')) return parser def take_action(self, parsed_args): @@ -179,13 +153,11 @@ def take_action(self, parsed_args): data = client.create_backup(parsed_args.instance, **attrs) - return ( - self.columns, - utils.get_dict_properties( - set_attributes_for_print_detail(data), - self.columns, - ) - ) + return (self.columns, + utils.get_dict_properties( + set_attributes_for_print_detail(data), + self.columns, + )) class DeleteBackup(command.Command): @@ -193,12 +165,10 @@ class DeleteBackup(command.Command): def get_parser(self, prog_name): parser = super(DeleteBackup, self).get_parser(prog_name) - parser.add_argument( - 'backup', - metavar='', - nargs='+', - help=_('ID of the backup') - ) + parser.add_argument('backup', + metavar='', + nargs='+', + help=_('ID of the backup')) return parser def take_action(self, parsed_args): @@ -206,22 +176,19 @@ def take_action(self, parsed_args): if parsed_args.backup: client = self.app.client_manager.rds for bck in parsed_args.backup: - client.delete_backup( - backup=bck, - ignore_missing=False) + client.delete_backup(backup=bck, ignore_missing=False) class ShowBackupPolicy(command.Command): _description = _('Show Database Backup Policy') columns = ('keep days', 'period', 'start time') + def get_parser(self, prog_name): parser = super(ShowBackupPolicy, self).get_parser(prog_name) - parser.add_argument( - 'instance', - metavar='', - help=_('Instance ID or Name') - ) + parser.add_argument('instance', + metavar='', + help=_('Instance ID or Name')) return parser def take_action(self, parsed_args): @@ -229,48 +196,39 @@ def take_action(self, parsed_args): client = self.app.client_manager.rds data = client.get_backup_policy(parsed_args.instance) - return ( - self.columns, - utils.get_dict_properties( - set_attributes_for_print_detail(data), - self.columns, - ) - ) + return (self.columns, + utils.get_dict_properties( + set_attributes_for_print_detail(data), + self.columns, + )) class UpdateBackupPolicy(command.Command): _description = _('Show Database Backup Policy') columns = ('keep days', 'period', 'start time') + def get_parser(self, prog_name): parser = super(UpdateBackupPolicy, self).get_parser(prog_name) - parser.add_argument( - 'instance', - metavar='', - help=_('Instance ID or Name') - ) - parser.add_argument( - '--keep_days', - metavar='', - type=int, - help=_('Specifies the number of days to' - 'retain the generated backup files.') - ) - parser.add_argument( - '--start_time', - metavar='', - help=_('Specifies the backup time window.' - 'The value must be a valid value in the' - '"hh:mm-HH:MM" format.') - ) - parser.add_argument( - '--period', - metavar='', - help=_('Specifies the backup cycle' - 'configuration. Data will be' - 'automatically backed up on the' - 'selected days every week.') - ) + parser.add_argument('instance', + metavar='', + help=_('Instance ID or Name')) + parser.add_argument('--keep_days', + metavar='', + type=int, + help=_('Specifies the number of days to' + 'retain the generated backup files.')) + parser.add_argument('--start_time', + metavar='', + help=_('Specifies the backup time window.' + 'The value must be a valid value in the' + '"hh:mm-HH:MM" format.')) + parser.add_argument('--period', + metavar='', + help=_('Specifies the backup cycle' + 'configuration. Data will be' + 'automatically backed up on the' + 'selected days every week.')) return parser def take_action(self, parsed_args): @@ -283,13 +241,11 @@ def take_action(self, parsed_args): attrs[arg] = getattr(parsed_args, arg) data = client.set_backup_policy(parsed_args.instance, **attrs) - return ( - self.columns, - utils.get_dict_properties( - set_attributes_for_print_detail(data), - self.columns, - ) - ) + return (self.columns, + utils.get_dict_properties( + set_attributes_for_print_detail(data), + self.columns, + )) class ListBackupDownloadLinks(command.Command): @@ -299,21 +255,16 @@ class ListBackupDownloadLinks(command.Command): def get_parser(self, prog_name): parser = super(ListBackupDownloadLinks, self).get_parser(prog_name) - parser.add_argument( - 'backup_id', - metavar='', - help=_('ID of the backup') - ) + parser.add_argument('backup_id', + metavar='', + help=_('ID of the backup')) return parser def take_action(self, parsed_args): client = self.app.client_manager.rds data = client.backup_download_links(backup_id=parsed_args.backup_id) - return ( + return (self.columns, (utils.get_dict_properties( + set_attributes_for_print_detail(s), self.columns, - (utils.get_dict_properties( - set_attributes_for_print_detail(s), - self.columns, - ) for s in data) - ) + ) for s in data)) diff --git a/otcextensions/osclient/rds/v3/instance.py b/otcextensions/osclient/rds/v3/instance.py index d7a3b143b..572ee8593 100644 --- a/otcextensions/osclient/rds/v3/instance.py +++ b/otcextensions/osclient/rds/v3/instance.py @@ -402,23 +402,129 @@ def take_action(self, parsed_args): backup=parsed_args.backup, restore_time=parsed_args.restore_time, target_instance=parsed_args.target_instance) - -class ShowDatabaseConfiguration(command.Command): - _description = _("Show Configuration details associated to instance") + +class CreateDatabaseFromBackup(command.ShowOne): + + _description = _("Creates a new database instance from backup.") def get_parser(self, prog_name): - parser = super(ShowDatabaseConfiguration, self).get_parser(prog_name) + parser = super(CreateDatabaseFromBackup, self).get_parser(prog_name) parser.add_argument('instance', metavar='', type=str, help=_('ID or name of the instance.')) + parser.add_argument('name', + metavar='', + help=_("Name of the instance.")) + parser.add_argument('flavor_ref', + metavar='', + help=_("Flavor spec_code")) + parser.add_argument('--size', + metavar='', + type=int, + required=True, + help=_("Size of the instance disk volume in GB. ")) + parser.add_argument('--volume_type', + metavar='', + type=str, + default=None, + choices=['COMMON', 'ULTRAHIGH'], + help=_("Volume type. (COMMON, ULTRAHIGH).")) + parser.add_argument('--availability_zone', + metavar='', + default=None, + help=_("The Zone hint to give to Nova.")) + parser.add_argument( + '--configuration', + dest='configuration_id', + metavar='', + default=None, + help=_("ID of the configuration group to attach to the instance.")) + parser.add_argument('--disk_encryption_id', + metavar='', + default=None, + help=_("key ID for disk encryption.")) + parser.add_argument('--port', + metavar='', + default=None, + type=int, + help=_("Database Port")) + parser.add_argument( + '--password', + metavar='', + help=_("ID of the configuration group to attach to the instance.")) + parser.add_argument('--region', + metavar='', + type=str, + default=None, + help=argparse.SUPPRESS) + parser.add_argument('--network_id', + dest='vpc_id', + metavar='', + type=str, + help=_('Network (VPC) ID')) + parser.add_argument('--subnet_id', + metavar='', + type=str, + help=_('Network (VPC) ID')) + parser.add_argument('--security_group', + dest='security_group_id', + metavar='', + type=str, + help=_('Security group ID')) + parser.add_argument( + '--ha_mode', + metavar='', + type=str, + default=None, + help=_('replication mode for the standby DB instance')) + parser.add_argument('--backup', + metavar='', + default=None, + type=str, + help=_('ID or name of the backup.')) + parser.add_argument('--restore_time', + metavar='', + default=None, + type=str, + help=_('Specifies the time point of data ' + 'restoration in the UNIX timestamp')) + parser.add_argument('--target_instance', + metavar='', + default=None, + type=str, + help=_('ID or name of the target instance.')) return parser def take_action(self, parsed_args): + # raise NotImplementedError + # Attention: not conform password result in BadRequest with no info client = self.app.client_manager.rds - obj = client.get_instance_configuration(instance=parsed_args.instance) + attrs = {} + args_list = [ + 'name', 'availability_zone', 'configuration_id', 'region', + 'vpc_id', 'subnet_id', 'security_group_id', 'disk_encryption_id', + 'port', 'password', 'flavor_ref' + ] + for arg in args_list: + if getattr(parsed_args, arg): + attrs[arg] = getattr(parsed_args, arg) + volume = {} + if parsed_args.size: + volume = {"size": parsed_args.size} + if parsed_args.volume_type: + volume['type'] = parsed_args.volume_type + attrs['volume'] = volume + if parsed_args.ha_mode: + ha = {'mode': 'ha', 'replication_mode': parsed_args.ha_mode} + attrs['ha'] = ha + obj = client.create_instance_from_backup( + instance=parsed_args.instance, + backup=parsed_args.backup, + restore_time=parsed_args.restore_time, + **attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) diff --git a/otcextensions/sdk/rds/v3/_proxy.py b/otcextensions/sdk/rds/v3/_proxy.py index 0af6f9580..da0935d4b 100644 --- a/otcextensions/sdk/rds/v3/_proxy.py +++ b/otcextensions/sdk/rds/v3/_proxy.py @@ -232,7 +232,6 @@ def create_instance_from_backup(self, 'type': 'timestamp', 'restore_time': restore_time } - attrs['source']['instance_id'] = instance.id attrs['restore_point']['instance_id'] = instance.id return self._create(_instance.Instance, prepend_key=False, **attrs) @@ -247,7 +246,7 @@ def get_instance_restore_time(self, instance): """ instance = self.find_instance(instance) return self._get(_instance.InstanceRestoreTime, - requires_id=False, + requires_id=False, instance_id=instance.id) def get_instance_configuration(self, instance): @@ -262,7 +261,7 @@ def get_instance_configuration(self, instance): """ instance = self.find_instance(instance) return self._get(_instance.InstanceConfiguration, - requires_id=False, + requires_id=False, instance_id=instance.id) def update_instance_configuration(self, instance, **attrs): diff --git a/otcextensions/sdk/rds/v3/backup.py b/otcextensions/sdk/rds/v3/backup.py index 096d40376..653aaf920 100644 --- a/otcextensions/sdk/rds/v3/backup.py +++ b/otcextensions/sdk/rds/v3/backup.py @@ -26,15 +26,10 @@ class Backup(sdk_resource.Resource): allow_list = True allow_get = False - _query_mapping = resource.QueryParameters( - 'offset', - 'begin_time', - 'instance_id', - 'backup_id', - 'backup_type', - 'begin_time', - 'end_time' - ) + _query_mapping = resource.QueryParameters('offset', 'begin_time', + 'instance_id', 'backup_id', + 'backup_type', 'begin_time', + 'end_time') #: Backup id #: Type: uuid* @@ -100,8 +95,12 @@ class BackupPolicy(sdk_resource.Resource): #: *Type: string* period = resource.Body('period') - def update(self, session, prepend_key=True, - endpoint_override=False, headers=False, requests_auth=False): + def update(self, + session, + prepend_key=True, + endpoint_override=False, + headers=False, + requests_auth=False): """Create a remote resource based on this instance. Method is overriden, because PUT without ID should be used @@ -116,8 +115,7 @@ def update(self, session, prepend_key=True, :raises: :exc:`~openstack.exceptions.MethodNotSupported` if :data:`Resource.allow_create` is not set to ``True``. """ - self.update_no_id( - session, prepend_key) + self.update_no_id(session, prepend_key) return None @@ -130,9 +128,7 @@ class BackupFiles(sdk_resource.Resource): # capabilities allow_list = True - _query_mapping = resource.QueryParameters( - 'backup_id' - ) + _query_mapping = resource.QueryParameters('backup_id') #: Indicates the file name #: *Type: string* diff --git a/otcextensions/sdk/rds/v3/instance.py b/otcextensions/sdk/rds/v3/instance.py index 777ea5242..4c68dcc27 100644 --- a/otcextensions/sdk/rds/v3/instance.py +++ b/otcextensions/sdk/rds/v3/instance.py @@ -13,7 +13,6 @@ from openstack import _log from otcextensions.sdk import sdk_resource - _logger = _log.setup_logging('openstack') @@ -30,10 +29,9 @@ class Instance(sdk_resource.Resource): allow_update = True allow_list = True - _query_mapping = resource.QueryParameters( - 'id', 'name', 'type', 'datastore_type', - 'vpc_id', 'subnet_id', 'offset', 'limit' - ) + _query_mapping = resource.QueryParameters('id', 'name', 'type', + 'datastore_type', 'vpc_id', + 'subnet_id', 'offset', 'limit') #: Instance id id = resource.Body('id') #: Instance name @@ -118,10 +116,15 @@ class Instance(sdk_resource.Resource): #: Charge Info #: *Type: dict* backup_strategy = resource.Body('charge_info', type=dict) + #: Specifies the restoration information + #: *Type: dict* + restore_point = resource.Body('restore_point', type=dict) class InstanceRecovery(sdk_resource.Resource): + base_path = '/instances/recovery' + # capabilities allow_create = True diff --git a/setup.cfg b/setup.cfg index 9d301b186..ccc2b744d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -108,7 +108,7 @@ openstack.rds.v3 = rds_backup_policy_show = otcextensions.osclient.rds.v3.backup:ShowBackupPolicy rds_backup_policy_update = otcextensions.osclient.rds.v3.backup:UpdateBackupPolicy rds_instance_restore = otcextensions.osclient.rds.v3.instance:RestoreDatabaseInstance - rds_instance_create_from_backup = otcextensions.osclient.rds.v3.instance:RestoreNewDatabaseInstance + rds_instance_create_from_backup = otcextensions.osclient.rds.v3.instance:CreateDatabaseFromBackup # rds_instance_configuration_show = otcextensions.osclient.rds.v3.instance:ShowDatabaseConfiguration rds_backup_list = otcextensions.osclient.rds.v3.backup:ListBackup rds_backup_create = otcextensions.osclient.rds.v3.backup:CreateBackup From f37095fa147ff49d54c308d845c583bf098c91a0 Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Mon, 7 Oct 2019 17:23:50 +0200 Subject: [PATCH 09/65] interim update wrt coding conventions --- otcextensions/osclient/rds/v3/datastore.py | 11 +- otcextensions/osclient/rds/v3/flavor.py | 8 +- otcextensions/osclient/rds/v3/instance.py | 73 ++-- otcextensions/sdk/rds/v3/_proxy.py | 360 ++++++++---------- otcextensions/sdk/rds/v3/backup.py | 52 +-- otcextensions/sdk/rds/v3/configuration.py | 68 ++-- otcextensions/sdk/rds/v3/datastore.py | 7 +- otcextensions/sdk/rds/v3/flavor.py | 20 +- otcextensions/sdk/rds/v3/instance.py | 308 +++++++-------- .../tests/functional/osclient/rds/__init__.py | 0 .../functional/osclient/rds/v3/__init__.py | 0 .../osclient/rds/v3/test_datastore.py | 35 ++ .../functional/osclient/rds/v3/test_flavor.py | 36 ++ .../osclient/rds/v3/test_instance.py | 39 ++ .../unit/osclient/rds/v3/test_datastore.py | 10 +- .../tests/unit/osclient/rds/v3/test_flavor.py | 10 +- .../unit/osclient/rds/v3/test_instance.py | 126 ++++-- otcextensions/tests/unit/sdk/rds/__init__.py | 0 .../tests/unit/sdk/rds/v3/test_backup.py | 219 +++-------- .../tests/unit/sdk/rds/v3/test_config.py | 112 ++++++ .../tests/unit/sdk/rds/v3/test_datastore.py | 2 +- .../tests/unit/sdk/rds/v3/test_flavor.py | 4 +- .../tests/unit/sdk/rds/v3/test_instance.py | 218 +++-------- .../tests/unit/sdk/rds/v3/test_proxy.py | 134 ++++++- 24 files changed, 984 insertions(+), 868 deletions(-) create mode 100644 otcextensions/tests/functional/osclient/rds/__init__.py create mode 100644 otcextensions/tests/functional/osclient/rds/v3/__init__.py create mode 100644 otcextensions/tests/functional/osclient/rds/v3/test_datastore.py create mode 100644 otcextensions/tests/functional/osclient/rds/v3/test_flavor.py create mode 100644 otcextensions/tests/functional/osclient/rds/v3/test_instance.py create mode 100644 otcextensions/tests/unit/sdk/rds/__init__.py create mode 100644 otcextensions/tests/unit/sdk/rds/v3/test_config.py diff --git a/otcextensions/osclient/rds/v3/datastore.py b/otcextensions/osclient/rds/v3/datastore.py index ebdc6558b..8399b3676 100644 --- a/otcextensions/osclient/rds/v3/datastore.py +++ b/otcextensions/osclient/rds/v3/datastore.py @@ -11,15 +11,12 @@ # under the License. # """Datastore v3 action implementations""" -import logging - from osc_lib import utils from osc_lib.command import command from otcextensions.i18n import _ from otcextensions.common import sdk_utils -LOG = logging.getLogger(__name__) DB_TYPE_CHOICES = ['mysql', 'postgresql', 'sqlserver'] @@ -31,7 +28,7 @@ def _get_columns(item): class ListDatastores(command.Lister): - _description = _("List available datastores") + _description = _("List available datastores.") columns = ['Name'] def take_action(self, parsed_args): @@ -46,14 +43,14 @@ def take_action(self, parsed_args): class ListDatastoreVersions(command.Lister): - _description = _("Lists available versions for a datastore") + _description = _("Lists available versions for a datastore.") columns = ['ID', 'Name'] def get_parser(self, prog_name): parser = super(ListDatastoreVersions, self).get_parser(prog_name) parser.add_argument( - 'db_name', + 'database', metavar='{' + ','.join(DB_TYPE_CHOICES) + '}', type=lambda s: s.lower(), choices=DB_TYPE_CHOICES, @@ -64,7 +61,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.rds - data = client.datastore_versions(datastore_name=parsed_args.db_name) + data = client.datastores(database_name=parsed_args.database) return (self.columns, (utils.get_item_properties( s, diff --git a/otcextensions/osclient/rds/v3/flavor.py b/otcextensions/osclient/rds/v3/flavor.py index 26df68b3f..9ac40ea47 100644 --- a/otcextensions/osclient/rds/v3/flavor.py +++ b/otcextensions/osclient/rds/v3/flavor.py @@ -25,14 +25,14 @@ class ListDatabaseFlavors(command.Lister): - _description = _("List database flavors") - columns = ['instance_mode', 'vcpus', 'ram', 'spec_code'] + _description = _("List database flavors.") + columns = ['name', 'instance_mode', 'vcpus', 'ram'] def get_parser(self, prog_name): parser = super(ListDatabaseFlavors, self).get_parser(prog_name) parser.add_argument( - 'db_name', + 'database', metavar='{' + ','.join(DB_TYPE_CHOICES) + '}', type=lambda s: s.lower(), choices=DB_TYPE_CHOICES, @@ -51,7 +51,7 @@ def take_action(self, parsed_args): client = self.app.client_manager.rds data = client.flavors( - datastore_name=parsed_args.db_name, + datastore_name=parsed_args.database, version_name=parsed_args.version) return ( diff --git a/otcextensions/osclient/rds/v3/instance.py b/otcextensions/osclient/rds/v3/instance.py index 572ee8593..3cc5fc83a 100644 --- a/otcextensions/osclient/rds/v3/instance.py +++ b/otcextensions/osclient/rds/v3/instance.py @@ -101,7 +101,7 @@ def get_parser(self, prog_name): help=_( 'Specifies the instance type. Values cane be single/ha/replica' )) - parser.add_argument('--db_type', + parser.add_argument('--database', dest='datastore_type', metavar='', type=str, @@ -109,12 +109,13 @@ def get_parser(self, prog_name): help=_( 'Specifies the database type. ' 'value is MySQL, PostgreSQL, or SQLServer.')) - parser.add_argument('--vpc_id', - dest='vpc_id', - metavar='', + parser.add_argument('--router_id', + dest='router_id', + metavar='', type=str, default=None, - help=_('Indicates the VPC ID. of DB Instance.')) + help=_('Specifies the ID of Router to which ' + 'instance should be connected to.')) parser.add_argument('--subnet_id', dest='subnet_id', metavar='', @@ -124,25 +125,17 @@ def get_parser(self, prog_name): parser.add_argument('--offset', dest='offset', metavar='', - type=str, + type=int, default=None, help=_('Specifies the index position.')) - parser.add_argument( - '--marker', - dest='marker', - metavar='', - help=_('Begin displaying the results for IDs greater than the ' - 'specified marker. When used with --limit, set this to ' - 'the last ID displayed in the previous run. ' - '(Not supported)')) return parser def take_action(self, parsed_args): client = self.app.client_manager.rds args_list = [ - 'name', 'id', 'vpc_id', 'subnet_id', 'type', 'datastore_type', - 'offset' + 'name', 'id', 'router_id', 'subnet_id', 'type', 'datastore_type', + 'offset', 'limit' ] attrs = {} for arg in args_list: @@ -160,14 +153,7 @@ def take_action(self, parsed_args): class ShowDatabaseInstance(command.ShowOne): - _description = _("Show instance details") - - columns = [ - 'id', 'name', 'datastore', 'datastore_version', 'flavor Ref', - 'Volume Type', 'Size', 'disk_encryption_id', 'region', - 'availability_zone', 'vpc_id', 'subnet_id', 'security_group_id', - 'port', 'backup_strategy', 'configuration_id', 'charge mode' - ] + _description = _("Show instance details.") def get_parser(self, prog_name): parser = super(ShowDatabaseInstance, self).get_parser(prog_name) @@ -183,7 +169,8 @@ def take_action(self, parsed_args): obj = client.find_instance(parsed_args.instance) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters={}) + data = utils.get_item_properties(obj, columns) + return (display_columns, data) @@ -211,7 +198,7 @@ def get_parser(self, prog_name): help=_("Size of the instance disk volume in GB. "), ) parser.add_argument( - '--volume_type', + '--volume-type', metavar='', type=str, default=None, @@ -219,7 +206,7 @@ def get_parser(self, prog_name): help=_("Volume type. (COMMON, ULTRAHIGH)."), ) parser.add_argument( - '--availability_zone', + '--availability-zone', metavar='', default=None, help=_("The Zone hint to give to Nova."), @@ -231,7 +218,7 @@ def get_parser(self, prog_name): help=_("datastore name"), ) parser.add_argument( - '--datastore_version', + '--datastore-version', default=None, metavar='', help=_("datastore version."), @@ -244,7 +231,7 @@ def get_parser(self, prog_name): help=_("ID of the configuration group to attach to the instance."), ) parser.add_argument( - '--disk_encryption_id', + '--disk-encryption-id', metavar='', default=None, help=_("key ID for disk encryption."), @@ -262,7 +249,7 @@ def get_parser(self, prog_name): help=_("ID of the configuration group to attach to the instance."), ) parser.add_argument( - '--replica_of', + '--replica-of', metavar='', default=None, help=_("ID or name of an existing instance to replicate from."), @@ -274,27 +261,28 @@ def get_parser(self, prog_name): default=None, help=argparse.SUPPRESS, ) - parser.add_argument('--network_id', - dest='vpc_id', - metavar='', + parser.add_argument('--router-id', + metavar='', type=str, - help=_('Network (VPC) ID')) - parser.add_argument('--subnet_id', + help=_('ID of a Router the DB should be connected ' + 'to')) + parser.add_argument('--subnet-id', metavar='', type=str, - help=_('Network (VPC) ID')) - parser.add_argument('--security_group', + help=_('ID of a subnet the DB should be connected ' + 'to.')) + parser.add_argument('--security-group-id', dest='security_group_id', metavar='', type=str, help=_('Security group ID')) parser.add_argument( - '--ha_mode', + '--ha-mode', metavar='', type=str, default=None, help=_('replication mode for the standby DB instance')) - parser.add_argument('--charge_mode', + parser.add_argument('--charge-mode', metavar='', type=str, default='postPaid', @@ -309,8 +297,8 @@ def take_action(self, parsed_args): attrs = {} args_list = [ 'name', 'availability_zone', 'configuration_id', 'region', - 'vpc_id', 'subnet_id', 'security_group_id', 'disk_encryption_id', - 'port', 'password', 'flavor_ref' + 'router_id', 'subnet_id', 'security_group_id', + 'disk_encryption_id', 'port', 'password', 'flavor_ref' ] for arg in args_list: if getattr(parsed_args, arg): @@ -322,7 +310,8 @@ def take_action(self, parsed_args): volume['type'] = parsed_args.volume_type attrs['volume'] = volume if parsed_args.replica_of: - attrs['replica_of'] = client.find_instance(parsed_args.replica_of) + attrs['replica_of_id'] = \ + client.find_instance(parsed_args.replica_of).id if parsed_args.datastore: datastore = { 'type': parsed_args.datastore, diff --git a/otcextensions/sdk/rds/v3/_proxy.py b/otcextensions/sdk/rds/v3/_proxy.py index da0935d4b..d79169aba 100644 --- a/otcextensions/sdk/rds/v3/_proxy.py +++ b/otcextensions/sdk/rds/v3/_proxy.py @@ -9,7 +9,9 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from otcextensions.sdk import sdk_proxy + +from openstack import proxy + from otcextensions.sdk.rds.v3 import backup as _backup from otcextensions.sdk.rds.v3 import configuration as _configuration from otcextensions.sdk.rds.v3 import datastore as _datastore @@ -17,7 +19,7 @@ from otcextensions.sdk.rds.v3 import instance as _instance -class Proxy(sdk_proxy.Proxy): +class Proxy(proxy.Proxy): skip_discovery = True @@ -28,39 +30,6 @@ def __init__(self, session, *args, **kwargs): 'X-Language': 'en-us' } - def get_os_endpoint(self, **kwargs): - """Return OpenStack compliant endpoint - - """ - return self.get_endpoint(service_type='database') - # if 'os_endpoint' not in self: - # endpoint = super(Proxy, self).get_endpoint(**kwargs) - # # endpoint_override = self.endpoint_override - # if endpoint.endswith('/rds/v1'): # and not endpoint_override: - # endpoint = endpoint.rstrip('/rds/v1') - # endpoint = utils.urljoin(endpoint, 'v1.0') - # self.os_endpoint = endpoint - # # else: - # # _logger.debug('RDS endpoint_override is set. Return it') - # # return endpoint_override - # else: - # return self.os_endpoint - - def get_rds_endpoint(self, **kwargs): - """Return RDS propriatary endpoint - - """ - return self.get_endpoint(service_type='rds') - # endpoint = super(Proxy, self).get_endpoint(**kwargs) - # endpoint_override = self.endpoint_override - # if endpoint.endswith('/rds/v1') and not endpoint_override: - # return endpoint - # elif endpoint_override: - # _logger.debug('RDS endpoint_override is set. Return it') - # return endpoint_override - # else: - # return endpoint - # ======= Datastores ======= def datastore_types(self): """List supported datastore types @@ -72,20 +41,18 @@ def datastore_types(self): obj = type('obj', (object, ), {'name': ds}) yield obj - return - - def datastore_versions(self, datastore_name): + def datastores(self, database_name): """List datastores - :param datastore_name: database store name + :param database_name: database store name (MySQL, PostgreSQL, or SQLServer and is case-sensitive.) - :returns: A generator of datastore versions + :returns: A generator of supported datastore versions :rtype: :class:`~otcextensions.sdk.rds.v3.datastore.Datastore` """ return self._list( _datastore.Datastore, - datastore_name=datastore_name, + database_name=database_name, ) # ======= Flavors ======= @@ -117,9 +84,6 @@ def create_instance(self, **attrs): """ return self._create( _instance.Instance, - # project_id=self.session.get_project_id(), - # endpoint_override=self.get_os_endpoint(), - prepend_key=False, **attrs) def delete_instance(self, instance, ignore_missing=True): @@ -163,202 +127,201 @@ def instances(self, **params): :returns: A generator of instance objects :rtype: :class:`~otcextensions.sdk.rds.v3.instance.Instance` """ - return self._list(_instance.Instance, paginated=False, **params) - - def restore_instance(self, - instance, - backup=None, - restore_time=None, - target_instance=None): - """Restore instance from backup - or Restore Point in Time Recovery - - :param instance: Either the id of a instance or a - :class:`~otcextensions.sdk.rds.v3.instance.Instance` - instance. - :param backup: Either the id of a backup or a - :class:`~otcextensions.sdk.rds.v3.backup.Backup` - instance. - - :returns: None - :rtype: - """ - attrs = {} - instance = self._get_resource(_instance.Instance, instance) - if target_instance: - target_instance = self._get_resource(_instance.Instance, - target_instance) - else: - target_instance = instance - if backup: - backup = self._get_resource(_backup.Backup, backup) - attrs['source'] = {'type': 'backup', 'backup_id': backup.id} - elif restore_time: - attrs['source'] = { - 'type': 'timestamp', - 'restore_time': restore_time - } - attrs['source']['instance_id'] = instance.id - attrs['target'] = {'instance_id': target_instance.id} - return self._create(_instance.InstanceRecovery, - prepend_key=False, - **attrs) - - def create_instance_from_backup(self, - instance, - backup=None, - restore_time=None, - **attrs): - """Restore instance from backup - - :param instance: Either the id of a instance or a - :class:`~otcextensions.sdk.rds.v3.instance.Instance` - instance. - :param backup: Either the id of a backup or a - :class:`~otcextensions.sdk.rds.v3.backup.Backup` - instance. - :attrs attrs: The attributes to update on the instance represented - by ``value``. - - :returns: The updated instance - :rtype: :class:`~otcextensions.sdk.rds.v3.instance.Instance` - """ - instance = self._get_resource(_instance.Instance, instance) - if backup: - backup = self._get_resource(_backup.Backup, backup) - attrs['restore_point'] = {'type': 'backup', 'backup_id': backup.id} - elif restore_time: - attrs['restore_point'] = { - 'type': 'timestamp', - 'restore_time': restore_time - } - attrs['restore_point']['instance_id'] = instance.id - return self._create(_instance.Instance, prepend_key=False, **attrs) - + # TODO: check whether pagination works properly + return self._list(_instance.Instance, **params) + +# def restore_instance(self, +# instance, +# backup=None, +# restore_time=None, +# target_instance=None): +# """Restore instance from backup +# or Restore Point in Time Recovery +# +# :param instance: Either the id of a instance or a +# :class:`~otcextensions.sdk.rds.v3.instance.Instance` +# instance. +# :param backup: Either the id of a backup or a +# :class:`~otcextensions.sdk.rds.v3.backup.Backup` +# instance. +# +# :returns: None +# :rtype: +# """ +# attrs = {} +# instance = self._get_resource(_instance.Instance, instance) +# if target_instance: +# target_instance = self._get_resource(_instance.Instance, +# target_instance) +# else: +# target_instance = instance +# if backup: +# backup = self._get_resource(_backup.Backup, backup) +# attrs['source'] = {'type': 'backup', 'backup_id': backup.id} +# elif restore_time: +# attrs['source'] = { +# 'type': 'timestamp', +# 'restore_time': restore_time +# } +# attrs['source']['instance_id'] = instance.id +# attrs['target'] = {'instance_id': target_instance.id} +# return self._create(_instance.InstanceRecovery, +# prepend_key=False, +# **attrs) + +# def create_instance_from_backup(self, +# instance, +# backup=None, +# restore_time=None, +# **attrs): +# """Restore instance from backup +# +# :param instance: Either the id of a instance or a +# :class:`~otcextensions.sdk.rds.v3.instance.Instance` +# instance. +# :param backup: Either the id of a backup or a +# :class:`~otcextensions.sdk.rds.v3.backup.Backup` +# instance. +# :attrs attrs: The attributes to update on the instance represented +# by ``value``. +# +# :returns: The updated instance +# :rtype: :class:`~otcextensions.sdk.rds.v3.instance.Instance` +# """ +# instance = self._get_resource(_instance.Instance, instance) +# if backup: +# backup = self._get_resource(_backup.Backup, backup) +# attrs['restore_point'] = {'type': 'backup', 'backup_id': backup.id} +# elif restore_time: +# attrs['restore_point'] = { +# 'type': 'timestamp', +# 'restore_time': restore_time +# } +# attrs['restore_point']['instance_id'] = instance.id +# return self._create(_instance.Instance, prepend_key=False, **attrs) +# def get_instance_restore_time(self, instance): - """Obtaining a restore time of an instance + """Obtaining a restore time of an instance. :param instance: This parameter can be either the ID of an instance or a :class:`~openstack.sdk.rds.v3.instance.Instance` :returns: Instance restore time - :rtype: :class:`~otcextensions.sdk.rds.v3.instance.InstanceRestoreTime` - - """ - instance = self.find_instance(instance) - return self._get(_instance.InstanceRestoreTime, - requires_id=False, - instance_id=instance.id) - - def get_instance_configuration(self, instance): - """Obtaining a Configuration Group associated to instance - - :param instance: This parameter can be either the ID of an instance - or a :class:`~openstack.sdk.rds.v3.instance.Instance` - :returns: Configuration Group details associated to instance - :rtype: :class: - `~otcextensions.sdk.rds.v3.instance.InstanceConfiguration` - - """ - instance = self.find_instance(instance) - return self._get(_instance.InstanceConfiguration, - requires_id=False, - instance_id=instance.id) - - def update_instance_configuration(self, instance, **attrs): - """Updates the configuration params of the instance - - :param instance: This parameter can be either the ID of an instance - or a :class:`~openstack.sdk.rds.v3.instance.Instance` + :rtype: list - :returns: Parameter restart required as bool value """ instance = self._get_resource(_instance.Instance, instance) - return self._update(_instance.InstanceConfiguration, - instance_id=instance.id, - **attrs) + return instance.fetch_restore_times(self) +# def get_instance_configuration(self, instance): +# """Obtaining a Configuration associated to instance. +# +# :param instance: This parameter can be either the ID of an instance +# or a :class:`~openstack.sdk.rds.v3.instance.Instance` +# :returns: Configuration Group details associated to instance +# :rtype: :class: +# `~otcextensions.sdk.rds.v3.instance.InstanceConfiguration` +# +# """ +# instance = self._get_resource(instance) +# return self._get(_instance.InstanceConfiguration, +# requires_id=False, +# instance_id=instance.id) +# +# def update_instance_configuration(self, instance, **attrs): +# """Updates the configuration params of the instance. +# +# :param instance: This parameter can be either the ID of an instance +# or a :class:`~openstack.sdk.rds.v3.instance.Instance` +# +# :returns: Parameter restart required as bool value +# """ +# instance = self._get_resource(_instance.Instance, instance) +# return self._update(_instance.InstanceConfiguration, +# instance_id=instance.id, +# **attrs) +# # ======= Configurations ======= def configurations(self, **attrs): - """Obtaining a ConfigurationGroup List + """Obtaining a list of DB Configuration. - :returns: A generator of ConfigurationGroup object + :returns: A generator of Configuration object :rtype: - :class:`~otcextensions.sdk.rds.v3.configuration.ConfigurationGroup` + :class:`~otcextensions.sdk.rds.v3.configuration.Configuration` """ return self._list( - _configuration.ConfigurationGroup, + _configuration.Configuration, paginated=False, ) def get_configuration(self, cg): - """Obtaining a ConfigurationGroup + """Obtaining a Configuration. - :param parameter_group: The value can be the ID of a ConfigurationGroup + :param parameter_group: The value can be the ID of a Configuration or a object of - :class:`~otcextensions.sdk.rds.v3.configuration.ConfigurationGroup`. + :class:`~otcextensions.sdk.rds.v3.configuration.Configuration`. - :returns: A Parameter Group Object - :rtype: :class:`~otcextensions.rds.v3.configuration.ConfigurationGroup` + :returns: A Configuration Object + :rtype: :class:`~otcextensions.rds.v3.configuration.Configuration` """ - return self._get(_configuration.ConfigurationGroup, cg) + return self._get(_configuration.Configuration, cg) def create_configuration(self, **attrs): - """Creating a ConfigurationGroup + """Create DB Configuration. - :param dict **attrs: Dict to overwrite ConfigurationGroup object - :returns: A Parameter Group Object + :param dict **attrs: Dict to overwrite Configuration object + :returns: A Configuration Object :rtype: - :class:`~otcextensions.sdk.rds.v3.configuration.ConfigurationGroup` + :class:`~otcextensions.sdk.rds.v3.configuration.Configuration` """ - return self._create(_configuration.ConfigurationGroup, + return self._create(_configuration.Configuration, prepend_key=False, **attrs) def delete_configuration(self, cg, ignore_missing=True): - """Deleting a ConfigurationGroup + """Delete DB Configuration. - :param cg: The value can be the ID of a ConfigurationGroup or a + :param cg: The value can be the ID of a Configuration or a object of - :class:`~otcextensions.sdk.rds.v3.configuration.ConfigurationGroup`. + :class:`~otcextensions.sdk.rds.v3.configuration.Configuration`. :param bool ignore_missing: When set to ``False`` :class:`~openstack.exceptions.ResourceNotFound` will be - raised when the Parameter Group does not exist. + raised when the Configration does not exist. When set to ``True``, no exception will be set when - attempting to delete a nonexistent Parameter Group. + attempting to delete a nonexistent Configuration. :returns: None :rtype: None """ self._delete( - _configuration.ConfigurationGroup, + _configuration.Configuration, cg, ignore_missing=ignore_missing, ) - def find_configuration(self, name_or_id, ignore_missing=True): - """Find a ConfigurationGroup + def apply_configuration(self, configuration, instances): + """Apply configuration to instances. - :param parameter_group: The value can be the ID of a ConfigurationGroup + :param configuration: The value can be the ID of a Configuration or a object of - :class:`~otcextensions.sdk.rds.v3.configuration.Configurations`. - :returns: A Parameter Group Object + :class:`~otcextensions.sdk.rds.v3.configuration.Configuration`. + :param instances: List of instance ids the configuration should be + applied to + :returns: Updated Configuration Object :rtype: - :class:`~otcextensions.rds.v3.configuration.ConfigurationGroup`. + :class:`~otcextensions.rds.v3.configuration.Configuration`. """ - return self._find(_configuration.ConfigurationGroup, - name_or_id, - ignore_missing=ignore_missing) + cg = self._get_resource(_configuration.Configuration, + configuration) + return cg.apply(self, instances) # ======= Backups ======= - def backups(self, instance, **params): - """List Backups + def backups(self, **params): + """List Backups. :returns: A generator of backup :rtype: :class:`~otcextensions.sdk.rds.v3.backup.Backup` """ - instance = self.find_instance(instance) - params['instance_id'] = instance.id - return self._list(_backup.Backup, paginated=False, **params) + return self._list(_backup.Backup, **params) def create_backup(self, instance, **attrs): """Create a backups of instance @@ -366,9 +329,9 @@ def create_backup(self, instance, **attrs): :returns: A new backup object :rtype: :class:`~otcextensions.sdk.rds.v3.backup.Backup` """ - instance = self.find_instance(instance) - attrs['instance_id'] = instance.id - return self._create(_backup.Backup, prepend_key=False, **attrs) + instance = self._get_resource(_instance.Instance, instance) + attrs.update({'instance_id': instance.id}) + return self._create(_backup.Backup, **attrs) def delete_backup(self, backup, ignore_missing=True): """Deletes given backup @@ -387,7 +350,7 @@ def delete_backup(self, backup, ignore_missing=True): backup, ignore_missing=ignore_missing) - def get_backup_policy(self, instance, ignore_missing=True): + def get_instance_backup_policy(self, instance): """Obtaining a backup policy of the instance :param instance: This parameter can be either the ID of an instance @@ -396,34 +359,29 @@ def get_backup_policy(self, instance, ignore_missing=True): :rtype: :class:`~otcextensions.sdk.rds.v3.backup.BackupPolicy` """ - instance = self.find_instance(instance) + instance = self._get_resource(_instance.Instance, instance) return self._get(_backup.BackupPolicy, - requires_id=False, - ignore_missing=ignore_missing, instance_id=instance.id) - def set_backup_policy(self, instance, **attrs): + def update_instance_backup_policy(self, policy, **attrs): """Sets the backup policy of the instance - :param instance: This parameter can be either the ID of an instance - or a :class:`~openstack.sdk.rds.v3.instance.Instance` + :param policy: This parameter can be either the ID of a policy + or a :class:`~openstack.sdk.rds.v3.backup.BackupPolicy` :param dict attrs: The attributes to update on the backup_policy represented by ``backup_policy``. :returns: ``None`` """ - instance = self.find_instance(instance) - return self._update(_backup.BackupPolicy, - requires_id=False, - instance_id=instance.id, + return self._update(_backup.BackupPolicy, policy, **attrs) def backup_download_links(self, backup_id): - """Obtaining a backup file download links + """Obtaining a backup file download links. :param backup_id :returns: files link - :rtype: :class:`~otcextensions.sdk.rds.v3.backup.BackupFiles` + :rtype: :class:`~otcextensions.sdk.rds.v3.backup.BackupFile` """ - return self._list(_backup.BackupFiles, backup_id=backup_id) + return self._list(_backup.BackupFile, backup_id=backup_id) diff --git a/otcextensions/sdk/rds/v3/backup.py b/otcextensions/sdk/rds/v3/backup.py index 653aaf920..a6fe3e4a8 100644 --- a/otcextensions/sdk/rds/v3/backup.py +++ b/otcextensions/sdk/rds/v3/backup.py @@ -10,26 +10,23 @@ # License for the specific language governing permissions and limitations # under the License. from openstack import resource -from otcextensions.sdk import sdk_resource -class Backup(sdk_resource.Resource): +class Backup(resource.Resource): base_path = '/backups' - resource_key = 'backup' resources_key = 'backups' - # service_expectes_json_type = True # capabilities allow_create = True allow_delete = True allow_list = True - allow_get = False + allow_fetch = False - _query_mapping = resource.QueryParameters('offset', 'begin_time', - 'instance_id', 'backup_id', - 'backup_type', 'begin_time', - 'end_time') + _query_mapping = resource.QueryParameters( + 'offset', 'begin_time', 'instance_id', 'id', + 'type', 'begin_time', 'end_time', + id='backup_id', type='backup_type') #: Backup id #: Type: uuid* @@ -59,18 +56,19 @@ class Backup(sdk_resource.Resource): type = resource.Body('type') -class BackupPolicy(sdk_resource.Resource): +class BackupPolicy(resource.Resource): base_path = '/instances/%(instance_id)s/backups/policy' resource_key = 'backup_policy' # capabilities - allow_update = True - allow_get = True + allow_commit = True + allow_fetch = True + + requires_id = False #: instaceId instance_id = resource.URI('instance_id') - # project_id = resource.URI('project_id') # Properties #: Policy keep days @@ -95,32 +93,8 @@ class BackupPolicy(sdk_resource.Resource): #: *Type: string* period = resource.Body('period') - def update(self, - session, - prepend_key=True, - endpoint_override=False, - headers=False, - requests_auth=False): - """Create a remote resource based on this instance. - - Method is overriden, because PUT without ID should be used - - :param session: The session to use for making this request. - :type session: :class:`~keystoneauth1.adapter.Adapter` - :param prepend_key: A boolean indicating whether the resource_key - should be prepended in a resource creation - request. Default to True. - - :return: None. - :raises: :exc:`~openstack.exceptions.MethodNotSupported` if - :data:`Resource.allow_create` is not set to ``True``. - """ - self.update_no_id(session, prepend_key) - - return None - -class BackupFiles(sdk_resource.Resource): +class BackupFile(resource.Resource): base_path = '/backup-files' resources_key = 'files' @@ -142,4 +116,4 @@ class BackupFiles(sdk_resource.Resource): #: Indicates the link expiration time. #: The format is "yyyy-mmddThh:mm:ssZ". #: *Type: string* - link_expired_time = resource.Body('link_expired_time') + expires_at = resource.Body('link_expired_time') diff --git a/otcextensions/sdk/rds/v3/configuration.py b/otcextensions/sdk/rds/v3/configuration.py index 912375b5e..8c1378688 100644 --- a/otcextensions/sdk/rds/v3/configuration.py +++ b/otcextensions/sdk/rds/v3/configuration.py @@ -9,33 +9,31 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from openstack import _log +from openstack import exceptions from openstack import resource +from openstack import utils -from otcextensions.sdk import sdk_resource -_logger = _log.setup_logging('openstack') - - -class ConfigurationGroup(sdk_resource.Resource): +class Configuration(resource.Resource): base_path = '/configurations' - resource_key = 'configuration' resources_key = 'configurations' # capabilities allow_create = True allow_delete = True - allow_update = True - allow_get = True + allow_commit = True + allow_fetch = True allow_list = True #: Id of the configuration group + configuration_id = resource.Body('configuration_id') + configuration_name = resource.Body('configuration_name') #: *Type:str* - id = resource.Body('id') + id = resource.Body('id', alias='configuration_id') #: Name of the configuration group #: *Type:str* - name = resource.Body('name') + name = resource.Body('name', alias='configuration_name') #: Data store information #: *Type: dict* datastore = resource.Body('datastore', type=dict) @@ -50,43 +48,29 @@ class ConfigurationGroup(sdk_resource.Resource): datastore_name = resource.Body('datastore_name') #: Date of created #: *Type:str* - created = resource.Body('created') + created_at = resource.Body('created') #: Date of updated #: *Type:str* - updated = resource.Body('updated') + updated_at = resource.Body('updated') #: Indicates whether the parameter group is created by users. #: *Type:bool* - user_defined = resource.Body('user_defined') + is_user_defined = resource.Body('user_defined', type=bool) #: parameter group values defined by users #: *Type: dict* values = resource.Body('values', type=dict) + #: Results of the configuration apply per instance + #: *Type:list* + apply_results = resource.Body('apply_results', type=list) -class ApplyConfigurationGroup(sdk_resource.Resource): - - base_path = '/configurations/%(config_id)s/apply' - - # capabilities - allow_update = True - - #: configuration Id - configuration_id = resource.URI('configuration_id') - - #: configuration Id - #: *Type: uuid* - configuration_id = resource.Body('configuration_id') - #: configuration name - #: *Type: string* - configuration_name = resource.Body('configuration_name') - #: Apply Status - #: Indicates the parameter - #: group application result. - #: *Type: list* - apply_status = resource.Body('apply_status', type=list) - #: Success - #: Indicates whether all - #: parameter groups are - #: applied to DB instances - #: successfully. - #: *Type: bool* - success = resource.Body('success', type=bool) + def apply(self, session, instances): + """Apply configuration to the given instances + """ + url = utils.urljoin(self.base_path, self.id, 'apply') + body = {'instance_ids': instances} + response = session.put( + url, + json=body) + exceptions.raise_from_response(response) + self._translate_response(response) + return self diff --git a/otcextensions/sdk/rds/v3/datastore.py b/otcextensions/sdk/rds/v3/datastore.py index cadd34972..291d4df50 100644 --- a/otcextensions/sdk/rds/v3/datastore.py +++ b/otcextensions/sdk/rds/v3/datastore.py @@ -10,18 +10,17 @@ # License for the specific language governing permissions and limitations # under the License. from openstack import resource -from otcextensions.sdk import sdk_resource -class Datastore(sdk_resource.Resource): +class Datastore(resource.Resource): - base_path = '/datastores/%(datastore_name)s' + base_path = '/datastores/%(database_name)s' resources_key = 'dataStores' # capabilities allow_list = True - datastore_name = resource.URI('datastore_name') + database_name = resource.URI('database_name') # Indicates the database version ID. Its value is unique. # :*Type:string* diff --git a/otcextensions/sdk/rds/v3/flavor.py b/otcextensions/sdk/rds/v3/flavor.py index 3452f1465..586df6417 100644 --- a/otcextensions/sdk/rds/v3/flavor.py +++ b/otcextensions/sdk/rds/v3/flavor.py @@ -9,33 +9,31 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -# from openstack import exceptions from openstack import resource -from otcextensions.sdk import sdk_resource -class Flavor(sdk_resource.Resource): +class Flavor(resource.Resource): base_path = '/flavors/%(datastore_name)s' - # In difference to regular OS services RDS - # does not return single item with a proper element tag resources_key = 'flavors' # capabilities allow_list = True - _query_mapping = resource.QueryParameters( - 'version_name' - ) + _query_mapping = resource.QueryParameters('version_name') + datastore_name = resource.URI('datastore_name') - #: VCPU's - #: *Type: str* - vcpus = resource.Body('vcpus') #: Instance Mode (single/ha/replica) #: *Type: int* instance_mode = resource.Body('instance_mode') #: Ram size in MB. #: *Type:int* ram = resource.Body('ram', type=int) + #: Flavor name. + #: *Type:str* + name = resource.Body('name', alias='spec_code') #: Specification code #: *Type: str* spec_code = resource.Body('spec_code') + #: Amount of VCPU's + #: *Type: str* + vcpus = resource.Body('vcpus') diff --git a/otcextensions/sdk/rds/v3/instance.py b/otcextensions/sdk/rds/v3/instance.py index 4c68dcc27..051dcfc8f 100644 --- a/otcextensions/sdk/rds/v3/instance.py +++ b/otcextensions/sdk/rds/v3/instance.py @@ -9,204 +9,190 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +from openstack import exceptions from openstack import resource -from openstack import _log -from otcextensions.sdk import sdk_resource +from openstack import utils -_logger = _log.setup_logging('openstack') - -class Instance(sdk_resource.Resource): +class Instance(resource.Resource): base_path = '/instances' resources_key = 'instances' - resource_key = 'instance' - # service_expectes_json_type = True # capabilities allow_create = True allow_delete = True - allow_update = True + allow_commit = True allow_list = True _query_mapping = resource.QueryParameters('id', 'name', 'type', - 'datastore_type', 'vpc_id', - 'subnet_id', 'offset', 'limit') - #: Instance id - id = resource.Body('id') - #: Instance name - name = resource.Body('name') - #: Instance status - status = resource.Body('status') - #: Private IP address List - #: *Type:list* - private_ips = resource.Body('private_ips', type=list) - #: Public IP address List - #: *Type:list* - public_ips = resource.Body('public_ips', type=list) - #: Database port number - #: *Type:int* - port = resource.Body('port', type=int) - #: Instance type readreplica/master/slave + 'datastore_type', 'router_id', + 'subnet_id', 'limit', 'offset') + + #: Availability Zone. #: *Type:str* - type = resource.Body('type') - #: HA information + availability_zone = resource.Body('availability_zone') + # TODO: extract backup strategy into separate type + #: Backup Strategy. #: *Type: dict* - ha = resource.Body('ha', type=dict) - #: Region + backup_strategy = resource.Body('backup_strategy', type=dict) + #: Specifies the billing information, which is pay-per-use. + #: *Type: dict* + charge_info = resource.Body('charge_info', type=dict) + #: Parameter configuration ID. + #: *Type:uuid* + configuration_id = resource.Body('configuration_id') + #: Instance created time. #: *Type:str* - region = resource.Body('region') - #: Data store information + created_at = resource.Body('created') + #: Data store information. #: *Type: dict* datastore = resource.Body('datastore', type=dict) - #: Instance created time - #: *Type:str* - created = resource.Body('created') - # datastore: Instance updated time - #: *Type:str* - updated = resource.Body('updated') - #: DB default username + #: Datastore type information (for querying). + datastore_type = resource.Body('datastore_type') + #: Disk Encryption Key Id. #: *Type:str* - db_user_name = resource.Body('db_user_name') - #: Private cloud id - vpc_id = resource.Body('vpc_id') - #: Id of subnet - subnet_id = resource.Body('subnet_id') - #: Security Group Id - security_group_id = resource.Body('security_group_id') + disk_encryption_id = resource.Body('disk_encryption_id') #: Flavor ID #: *Type:uuid* flavor_ref = resource.Body('flavor_ref') - #: Availability Zone - #: *Type:str* - availability_zone = resource.Body('availability_zone') - #: Volume information - #: *Type: dict* - volume = resource.Body('volume', type=dict) - #: Switch Strategy - #: *Type:str* - switch_strategy = resource.Body('switch_strategy') - #: Backup Strategy + #: HighAvailability configuration parameters. #: *Type: dict* - backup_strategy = resource.Body('backup_strategy', type=dict) - #: maintenance time window + ha = resource.Body('ha', type=dict) + #: Maintenance time window. #: *Type:str* maintenance_window = resource.Body('maintenance_window') #: Node information - #: Indicates the primary/standby DB - #: instance information + #: Indicates the primary/standby DB instance information. #: *Type:list* nodes = resource.Body('nodes', type=list) - #: list of associated DB instances + #: Password of the default user. + #: *Type:str* + password = resource.Body('password') + #: Database listen port number. + #: *Type:int* + port = resource.Body('port', type=int) + #: Private IP address List. #: *Type:list* - related_instance = resource.Body('related_instance', type=list) - #: Disk Encryption Key Id + private_ips = resource.Body('private_ips', type=list) + #: Public IP address List, + #: *Type:list* + public_ips = resource.Body('public_ips', type=list) + #: Region where DB is deployed. #: *Type:str* - disk_encryption_id = resource.Body('disk_encryption_id') - #: Database Password + region = resource.Body('region') + #: list of associated DB instances. + #: *Type:list* + related_instances = resource.Body('related_instance', type=list) + #: Specifies the DB instance ID, which is used to create a read replica. + replica_of_id = resource.Body('replica_of_id') + #: Specifies the restoration point for instance recovery. + #: *Type: dict* + restore_point = resource.Body('restore_point', type=dict) + #: Recovery time period for instance. + restore_time = resource.Body('restore_time', type=list) + #: Neutron router ID. + router_id = resource.Body('vpc_id') + #: Security Group Id. + security_group_id = resource.Body('security_group_id') + #: Id of subnet. + subnet_id = resource.Body('subnet_id') + #: Instance status. + status = resource.Body('status') + #: Switch Strategy. The value can be reliability or availability, + #: indicating the reliability first and availability first, respectively. #: *Type:str* - password = resource.Body('password') - #: Time Zone + switch_strategy = resource.Body('switch_strategy') + #: Time Zone. #: *Type:str* time_zone = resource.Body('time_zone') - #: replication mode for the standby - #: DB instance. + #: Instance type Single/Ha/Replica., #: *Type:str* - replication_mode = resource.Body('replication_mode') - #: Charge Info - #: *Type: dict* - backup_strategy = resource.Body('charge_info', type=dict) - #: Specifies the restoration information + type = resource.Body('type') + # datastore: Instance updated time. + #: *Type:str* + updated_at = resource.Body('updated') + #: Username of the default DB user. + #: *Type:str* + user_name = resource.Body('db_user_name') + #: Volume information #: *Type: dict* - restore_point = resource.Body('restore_point', type=dict) - - -class InstanceRecovery(sdk_resource.Resource): - - base_path = '/instances/recovery' - - # capabilities - allow_create = True - - #: Specifies the restoration information - #: *Type: dict* - source = resource.Body('source', type=dict) - #: Specifies the restoration target - #: *Type: dict* - target = resource.Body('target', type=dict) - - -class InstanceConfiguration(sdk_resource.Resource): - - base_path = '/instances/%(instance_id)s/configurations' - - # capabilities - allow_get = True - allow_update = True - - #: instaceId - instance_id = resource.URI('instance_id') - - #: database version name - #: *Type: string* - datastore_version_name = resource.Body('datastore_version_name') - #: database name - #: *Type: string* - datastore_name = resource.Body('datastore_name') - #: Indicates the creation time in the following - #: format: yyyy-MM-ddTHH:mm:ssZ. - #: *Type: string* - created = resource.Body('created') - #: Indicates the update time in the following - #: format: yyyy-MM-ddTHH:mm:ssZ. - #: *Type: string* - updated = resource.Body('updated') - #: Indicates the parameter configuration - #: defined by users based on the default - #: parameter groups. - #: *Type: list* - configuration_parameters = resource.Body('configuration_parameters') - - def update(self, session, prepend_key=False): - """ - Method is overriden, because PUT without ID should be used - - :param session: The session to use for making this request. - :type session: :class:`~keystoneauth1.adapter.Adapter` - :param prepend_key: A boolean indicating whether the resource_key - should be prepended in a resource creation - request. Default to True. + volume = resource.Body('volume', type=dict) - :return: None. - :raises: :exc:`~openstack.exceptions.MethodNotSupported` if - :data:`Resource.allow_create` is not set to ``True``. + def fetch_restore_times(self, session): + """List possible restore times for the instance """ - return self.update_no_id(session, prepend_key) - - -class InstanceRestoreTime(resource.Resource): - - base_path = '/instances/%(instance_id)s/restore-time' - - resource_key = 'restore_time' + url = utils.urljoin(self.base_path, self.id, 'restore-time') + response = session.get(url) + self._translate_response(response) + return self.restore_time - # capabilities - allow_get = True - - #: instaceId - instance_id = resource.URI('instance_id') - # project_id = resource.URI('project_id') + def get_instance_configuration(self, session): + pass - #: Start time - #: Indicates the start time of the recovery time period in - #: the UNIX timestamp format. The unit is - #: millisecond and the time zone is UTC. - #: *Type: string* - start_time = resource.Body('start_time') + def update_instance_configuration(self, session): + pass - #: End time - #: Indicates the end time of the recovery time period in - #: the UNIX timestamp format. The unit is - #: millisecond and the time zone is UTC. - #: *Type: string* - end_time = resource.Body('end_time') +# +# class InstanceRecovery(sdk_resource.Resource): +# +# base_path = '/instances/recovery' +# +# # capabilities +# allow_create = True +# +# #: Specifies the restoration information +# #: *Type: dict* +# source = resource.Body('source', type=dict) +# #: Specifies the restoration target +# #: *Type: dict* +# target = resource.Body('target', type=dict) +# +# +# class InstanceConfiguration(sdk_resource.Resource): +# +# base_path = '/instances/%(instance_id)s/configurations' +# +# # capabilities +# allow_get = True +# allow_update = True +# +# #: instaceId +# instance_id = resource.URI('instance_id') +# +# #: Indicates the parameter configuration +# #: defined by users based on the default parameter groups. +# #: *Type: list* +# configuration_parameters = resource.Body('configuration_parameters') +# #: Indicates the creation time in the following +# #: format: yyyy-MM-ddTHH:mm:ssZ. +# #: *Type: string* +# created_at = resource.Body('created') +# #: database version name. +# #: *Type: string* +# datastore_version_name = resource.Body('datastore_version_name') +# #: database name. +# #: *Type: string* +# datastore_name = resource.Body('datastore_name') +# #: Indicates the update time in the following +# #: format: yyyy-MM-ddTHH:mm:ssZ. +# #: *Type: string* +# updated_at = resource.Body('updated') +# +# +# def update(self, session, prepend_key=False): +# """ +# Method is overriden, because PUT without ID should be used +# +# :param session: The session to use for making this request. +# :type session: :class:`~keystoneauth1.adapter.Adapter` +# :param prepend_key: A boolean indicating whether the resource_key +# should be prepended in a resource creation +# request. Default to True. +# +# :return: None. +# :raises: :exc:`~openstack.exceptions.MethodNotSupported` if +# :data:`Resource.allow_create` is not set to ``True``. +# """ +# return self.update_no_id(session, prepend_key) +# diff --git a/otcextensions/tests/functional/osclient/rds/__init__.py b/otcextensions/tests/functional/osclient/rds/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/functional/osclient/rds/v3/__init__.py b/otcextensions/tests/functional/osclient/rds/v3/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/functional/osclient/rds/v3/test_datastore.py b/otcextensions/tests/functional/osclient/rds/v3/test_datastore.py new file mode 100644 index 000000000..83fbdae41 --- /dev/null +++ b/otcextensions/tests/functional/osclient/rds/v3/test_datastore.py @@ -0,0 +1,35 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json + +from openstackclient.tests.functional import base + + +class TestRdsDatastore(base.TestCase): + """Functional tests for RDS Datastore. """ + + def test_datastore_list(self): + json_output = json.loads(self.openstack( + 'rds datastore list -f json ' + )) + self.assertIn( + 'PostgreSQL', + [ds.Name for ds in json_output] + ) + + def test_datastore_version_list(self): + json_output = json.loads(self.openstack( + 'rds datastore version list PostgreSQL -f json' + )) + + self.assertIsNotNone(json_output) diff --git a/otcextensions/tests/functional/osclient/rds/v3/test_flavor.py b/otcextensions/tests/functional/osclient/rds/v3/test_flavor.py new file mode 100644 index 000000000..864316543 --- /dev/null +++ b/otcextensions/tests/functional/osclient/rds/v3/test_flavor.py @@ -0,0 +1,36 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json +import uuid + +from openstackclient.tests.functional import base + + +class TestRdsFlavor(base.TestCase): + """Functional tests for RDS Flavor. """ + + NAME = uuid.uuid4().hex + OTHER_NAME = uuid.uuid4().hex + + def test_flavor_list(self): + json_output = json.loads(self.openstack( + 'rds datastore version list postgresql -f json ' + )) + ver = json_output[0]['Name'] + + json_output = json.loads(self.openstack( + 'rds flavor list postgresql {ver} -f json '.format( + ver=ver) + )) + + self.assertIsNotNone(json_output) diff --git a/otcextensions/tests/functional/osclient/rds/v3/test_instance.py b/otcextensions/tests/functional/osclient/rds/v3/test_instance.py new file mode 100644 index 000000000..8e34fb33c --- /dev/null +++ b/otcextensions/tests/functional/osclient/rds/v3/test_instance.py @@ -0,0 +1,39 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from openstackclient.tests.functional import base + + +class TestRdsInstance(base.TestCase): + """Functional tests for RDS Instance. """ + + NAME = uuid.uuid4().hex + OTHER_NAME = uuid.uuid4().hex + + def test_instance_list(self): + self.openstack( + 'rds instance list -f json ' + ) + + def test_instance_list_filters(self): + self.openstack( + 'rds instance list ' + '--limit 1 --id 2 ' + '--name 3 --type Single ' + '--database PostgreSQL ' + '--router_id 123asd --subnet_id 123qwe ' + '--offset 5' + ) + + self.assertTrue(True) diff --git a/otcextensions/tests/unit/osclient/rds/v3/test_datastore.py b/otcextensions/tests/unit/osclient/rds/v3/test_datastore.py index 9080a260b..00b1b69e1 100644 --- a/otcextensions/tests/unit/osclient/rds/v3/test_datastore.py +++ b/otcextensions/tests/unit/osclient/rds/v3/test_datastore.py @@ -13,7 +13,6 @@ # under the License. # import mock -# from osc_lib import utils as common_utils from otcextensions.osclient.rds.v3 import datastore from otcextensions.tests.unit.osclient.rds.v3 import fakes as rds_fakes @@ -65,9 +64,6 @@ class TestListDatastoreVersions(rds_fakes.TestRds): column_headers = [ 'ID', 'Name', - # 'Datastore', - # 'Image', - # 'Packages' ] def setUp(self): @@ -92,21 +88,21 @@ def test_list_datastore_versions(self): ] verifylist = [ - ('db_name', 'mysql') + ('database', 'mysql') ] # Verify cm is triggereg with default parameters parsed_args = self.check_parser(self.cmd, arglist, verifylist) # Set the response - self.app.client_manager.rds.datastore_versions.side_effect = [ + self.app.client_manager.rds.datastores.side_effect = [ self.datastores ] # Trigger the action columns, data = self.cmd.take_action(parsed_args) - self.app.client_manager.rds.datastore_versions.assert_called() + self.app.client_manager.rds.datastores.assert_called() self.assertEqual(self.column_headers, columns) self.assertEqual(tuple(self.datastore_data), tuple(data)) diff --git a/otcextensions/tests/unit/osclient/rds/v3/test_flavor.py b/otcextensions/tests/unit/osclient/rds/v3/test_flavor.py index e3f009132..6d180217c 100644 --- a/otcextensions/tests/unit/osclient/rds/v3/test_flavor.py +++ b/otcextensions/tests/unit/osclient/rds/v3/test_flavor.py @@ -22,12 +22,10 @@ class TestListDatabaseFlavors(rds_fakes.TestRds): column_list_headers = [ + 'name', 'instance_mode', 'vcpus', 'ram', - 'spec_code' - # 'Str_ID', - # 'vCPUs' ] def setUp(self): @@ -41,10 +39,10 @@ def setUp(self): self.flavor_data = [] for s in self.flavors: self.flavor_data.append(( + s.name, s.instance_mode, s.vcpus, - s.ram, - s.spec_code + s.ram )) def test_list_flavors(self): @@ -54,7 +52,7 @@ def test_list_flavors(self): ] verifylist = [ - ('db_name', 'mysql'), + ('database', 'mysql'), ('version', '5.7') ] diff --git a/otcextensions/tests/unit/osclient/rds/v3/test_instance.py b/otcextensions/tests/unit/osclient/rds/v3/test_instance.py index 651641045..12604be54 100644 --- a/otcextensions/tests/unit/osclient/rds/v3/test_instance.py +++ b/otcextensions/tests/unit/osclient/rds/v3/test_instance.py @@ -31,7 +31,8 @@ def setUp(self): self.cmd = instance.ListDatabaseInstances(self.app, None) - self.app.client_manager.rds.instances = mock.Mock() + self.client.instances = mock.Mock() + self.client.api_mock = self.client.instances self.objects = self.instance_mock.create_multiple(3) self.object_data = [] @@ -49,16 +50,56 @@ def test_list(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) # Set the response - self.app.client_manager.rds.instances.side_effect = [self.objects] + self.client.api_mock.side_effect = [self.objects] # Trigger the action columns, data = self.cmd.take_action(parsed_args) - self.app.client_manager.rds.instances.assert_called() + self.client.api_mock.assert_called_with() self.assertEqual(self.column_list_headers, columns) self.assertEqual(tuple(self.object_data), tuple(data)) + def test_list_args(self): + arglist = [ + '--limit', '1', + '--id', '2', + '--name', '3', + '--type', '4', + '--database', '5', + '--router_id', '6', + '--subnet_id', '7', + '--offset', '8', + ] + + verifylist = [ + ('limit', 1), + ('id', '2'), + ('name', '3'), + ('type', '4'), + ('datastore_type', '5'), + ('router_id', '6'), + ('subnet_id', '7'), + ('offset', 8), + ] + + # Verify cm is triggereg with default parameters + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Trigger the action + columns, data = self.cmd.take_action(parsed_args) + + self.client.api_mock.assert_called_with( + datastore_type='5', + id='2', + limit=1, + name='3', + offset=8, + router_id='6', + subnet_id='7', + type='4' + ) + class TestShowDatabaseInstance(rds_fakes.TestRds): @@ -144,43 +185,80 @@ def setUp(self): self.cmd = instance.CreateDatabaseInstance(self.app, None) - self.app.client_manager.rds.find_flavor = mock.Mock() - self.app.client_manager.rds.create_instance = mock.Mock() + self.client.find_flavor = mock.Mock() + self.client.create_instance = mock.Mock() self.instance = self.instance_mock.create_one() self.flavor = self.flavor_mock.create_one() - # self.instance.resize = mock.Mock() - - # self.instance._update(project_id='123') def test_action(self): arglist = [ - 'inst_name', 'test-flavor', '--availability_zone', 'test-az-01', - '--datastore', 'MySQL', '--datastore_version', '5.7', - '--network_id', 'test-vpc-id', '--subnet_id', 'test-subnet-id', - '--security_group', 'test-subnet-id', '--volume_type', 'ULTRAHIGH', - '--size', '100', '--password', 'testtest', '--region', - 'test-region' + 'inst_name', + 'test-flavor', + '--availability-zone', 'test-az-01', + '--configuration', '123', + '--datastore', 'MySQL', + '--datastore-version', '5.7', + '--disk-encryption-id', '234', + '--ha-mode', 'semisync', + '--router-id', 'test-vpc-id', + '--subnet-id', 'test-subnet-id', + '--security-group-id', 'test-sec_grp-id', + '--replica-of', 'fake_name', + '--volume-type', 'ULTRAHIGH', + '--size', '100', + '--password', 'testtest', + '--region', 'test-region', + '--port', '12345' ] - verifylist = [('name', 'inst_name'), ('flavor_ref', 'test-flavor'), - ('availability_zone', 'test-az-01'), - ('datastore', 'MySQL'), ('datastore_version', '5.7'), - ('vpc_id', 'test-vpc-id'), - ('subnet_id', 'test-subnet-id'), - ('security_group_id', 'test-subnet-id'), - ('volume_type', 'ULTRAHIGH'), ('size', 100), - ('password', 'testtest'), ('region', 'test-region')] + verifylist = [ + ('name', 'inst_name'), + ('configuration_id', '123'), + ('flavor_ref', 'test-flavor'), + ('availability_zone', 'test-az-01'), + ('datastore', 'MySQL'), + ('datastore_version', '5.7'), + ('disk_encryption_id', '234'), + ('ha_mode', 'semisync'), + ('router_id', 'test-vpc-id'), + ('subnet_id', 'test-subnet-id'), + ('security_group_id', 'test-sec_grp-id'), + ('port', 12345), + ('replica_of', 'fake_name'), + ('volume_type', 'ULTRAHIGH'), + ('size', 100), + ('password', 'testtest'), + ('region', 'test-region')] # Verify cm is triggereg with default parameters parsed_args = self.check_parser(self.cmd, arglist, verifylist) # Set the response - self.app.client_manager.rds.create_instance.side_effect = [ + self.client.create_instance.side_effect = [ self.instance ] + self.client.find_instance.return_value = self.instance + # Trigger the action self.cmd.take_action(parsed_args) - self.app.client_manager.rds.create_instance.assert_called() + self.client.create_instance.assert_called_with( + availability_zone='test-az-01', + charge_info={'charge_mode': 'postPaid'}, + configuration_id='123', + datastore={'type': 'MySQL', 'version': '5.7'}, + disk_encryption_id='234', + flavor_ref='test-flavor', + ha={'mode': 'ha', 'replication_mode': 'semisync'}, + name='inst_name', + password='testtest', + port=12345, + region='test-region', + replica_of_id=self.instance.id, + router_id='test-vpc-id', + security_group_id='test-sec_grp-id', + volume={'size': 100, 'type': 'ULTRAHIGH'}, + subnet_id='test-subnet-id' + ) diff --git a/otcextensions/tests/unit/sdk/rds/__init__.py b/otcextensions/tests/unit/sdk/rds/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/otcextensions/tests/unit/sdk/rds/v3/test_backup.py b/otcextensions/tests/unit/sdk/rds/v3/test_backup.py index 04ea61e18..a7635369a 100644 --- a/otcextensions/tests/unit/sdk/rds/v3/test_backup.py +++ b/otcextensions/tests/unit/sdk/rds/v3/test_backup.py @@ -9,60 +9,49 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -import copy - -from keystoneauth1 import adapter - -import mock from openstack.tests.unit import base from otcextensions.sdk.rds.v3 import backup -RDS_HEADERS = { - 'content-type': 'application/json', - 'x-language': 'en-us' -} - -# PROJECT_ID = '23' IDENTIFIER = 'IDENTIFIER' EXAMPLE = { 'id': IDENTIFIER, 'name': '50deafb3e45d451a9406ca146b71fe9a_rds', 'description': '', 'begin_time': '2016-08-23T04:01:40', + 'end_time': '2016-08-23T04:04:40', 'status': 'COMPLETED', 'instance_id': '4f87d3c4-9e33-482f-b962-e23b30d1a18c', - 'type': 'manual' + 'type': 'manual', + 'datastore': {} } EXAMPLE_POLICY = { - 'keepday': 7, - 'starttime': '00:00:00' + 'keep_days': 7, + 'start_time': '00:00:00', + 'period': '1,2' } +EXAMPLE_FILE = { + 'name': '43e4feaab48f11e89039fa163ebaa7e4br01.xxx', + 'size': 2803, + 'download_link': 'fake_url', + 'link_expired_time': '2018-08-016T10:15:14+0800' +} -class TestBackup(base.TestCase): - def setUp(self): - super(TestBackup, self).setUp() - self.sess = mock.Mock(spec=adapter.Adapter) - self.sess.get = mock.Mock() - self.sess.post = mock.Mock() - self.sess.delete = mock.Mock() - self.sess.put = mock.Mock() - # self.sess.get_project_id = mock.Mock(return_value=PROJECT_ID) - self.sot = backup.Backup(**EXAMPLE) +class TestBackup(base.TestCase): def test_basic(self): sot = backup.Backup() - self.assertEqual('backup', sot.resource_key) + self.assertIsNone(sot.resource_key) self.assertEqual('backups', sot.resources_key) self.assertEqual('/backups', sot.base_path) self.assertTrue(sot.allow_list) self.assertTrue(sot.allow_create) - self.assertFalse(sot.allow_get) - self.assertFalse(sot.allow_update) + self.assertFalse(sot.allow_fetch) + self.assertFalse(sot.allow_commit) self.assertTrue(sot.allow_delete) def test_make_it(self): @@ -72,139 +61,49 @@ def test_make_it(self): self.assertEqual(EXAMPLE['instance_id'], sot.instance_id) self.assertEqual(EXAMPLE['description'], sot.description) self.assertEqual(EXAMPLE['begin_time'], sot.begin_time) + self.assertEqual(EXAMPLE['end_time'], sot.end_time) self.assertEqual(EXAMPLE['type'], sot.type) self.assertEqual(EXAMPLE['status'], sot.status) + self.assertEqual(EXAMPLE['datastore'], sot.datastore) - def test_create(self): - - mock_response = mock.Mock() - mock_response.status_code = 200 - mock_response.json.return_value = {'backup': copy.deepcopy(EXAMPLE)} - mock_response.headers = {} - - self.sess.post.return_value = mock_response - - sot = backup.Backup.new( - name='backup_name', - description='descr', - instance_id='some_instance') - - result = sot.create(self.sess, headers=RDS_HEADERS) - - self.sess.post.assert_called_once_with( - '/backups', - headers=RDS_HEADERS, - json={'backup': { - 'instance_id': 'some_instance', - 'description': 'descr', - 'name': 'backup_name'} - } - ) - - self.assertEqual( - backup.Backup( - **EXAMPLE), - result) - - def test_delete(self): - - mock_response = mock.Mock() - mock_response.status_code = 200 - mock_response.json.return_value = {} - mock_response.headers = {} - - self.sess.delete.return_value = mock_response - - sot = backup.Backup( - **EXAMPLE - ) - - sot.delete(self.sess, headers=RDS_HEADERS) - - url = 'backups/%(id)s' % \ - { - 'id': sot.id - } - - self.sess.delete.assert_called_once_with( - url, - headers=RDS_HEADERS - ) - -# def test_policy_basic(self): -# sot = backup.BackupPolicy() -# self.assertEqual('policy', sot.resource_key) -# self.assertEqual(None, sot.resources_key) -# self.assertEqual('/instances/%(instance_id)s/' -# 'backups/policy', sot.base_path) -# self.assertFalse(sot.allow_list) -# self.assertFalse(sot.allow_create) -# self.assertTrue(sot.allow_get) -# self.assertTrue(sot.allow_update) -# self.assertFalse(sot.allow_delete) -# -# def test_policy_make_it(self): -# sot = backup.BackupPolicy(**EXAMPLE_POLICY) -# self.assertEqual(EXAMPLE_POLICY['keepday'], sot.keepday) -# self.assertEqual(EXAMPLE_POLICY['starttime'], sot.starttime) -# -# def test_policy_update(self): -# mock_response = mock.Mock() -# mock_response.status_code = 200 -# mock_response.json.return_value = { -# 'policy': -# copy.deepcopy(EXAMPLE_POLICY)} -# mock_response.headers = {} -# instance_id = 'instance_id' -# -# self.sess.put.return_value = mock_response -# -# sot = backup.BackupPolicy.new( -# # project_id=PROJECT_ID, -# instance_id=instance_id, -# **EXAMPLE_POLICY) -# -# self.assertIsNone(sot.update(self.sess, headers=RDS_HEADERS)) -# -# url = '/instances/%(instance_id)s/backups/policy' % \ -# { -# 'instance_id': instance_id -# } -# -# self.sess.put.assert_called_once_with( -# url, -# headers=RDS_HEADERS, -# json={'policy': EXAMPLE_POLICY} -# ) -# -# def test_policy_get(self): -# -# mock_response = mock.Mock() -# mock_response.status_code = 200 -# mock_response.json.return_value = { -# 'policy': -# copy.deepcopy(EXAMPLE_POLICY)} -# mock_response.headers = {} -# instance_id = 'instance_id' -# -# self.sess.get.return_value = mock_response -# -# sot = backup.BackupPolicy.new( -# # **EXAMPLE_POLICY, -# # project_id=PROJECT_ID, -# instance_id=instance_id) -# -# res = sot.get(self.sess, requires_id=False, headers=RDS_HEADERS) -# -# url = '/instances/%(instance_id)s/backups/policy' % \ -# { -# 'instance_id': instance_id -# } -# -# self.sess.get.assert_called_once_with( -# url, -# headers=RDS_HEADERS -# ) -# -# self.assertEqual(EXAMPLE_POLICY['keepday'], res.keepday) -# self.assertEqual(EXAMPLE_POLICY['starttime'], res.starttime) + +class TestBackupPolicy(base.TestCase): + + def test_basic(self): + sot = backup.BackupPolicy() + self.assertIsNone(sot.resources_key) + self.assertEqual('backup_policy', sot.resource_key) + + self.assertEqual('/instances/%(instance_id)s/backups/policy', + sot.base_path) + self.assertTrue(sot.allow_commit) + self.assertTrue(sot.allow_fetch) + self.assertFalse(sot.requires_id) + + def test_make_it(self): + sot = backup.BackupPolicy(**EXAMPLE_POLICY) + self.assertEqual(EXAMPLE_POLICY['keep_days'], sot.keep_days) + self.assertEqual(EXAMPLE_POLICY['start_time'], sot.start_time) + self.assertEqual(EXAMPLE_POLICY['period'], sot.period) + + +class TestBackupFile(base.TestCase): + + def test_basic(self): + sot = backup.BackupFile() + self.assertEqual('files', sot.resources_key) + self.assertEqual('/backup-files', sot.base_path) + self.assertTrue(sot.allow_list) + + self.assertDictEqual({ + 'limit': 'limit', + 'marker': 'marker', + 'backup_id': 'backup_id'}, + sot._query_mapping._mapping) + + def test_make_it(self): + sot = backup.BackupFile(**EXAMPLE_FILE) + self.assertEqual(EXAMPLE_FILE['name'], sot.name) + self.assertEqual(EXAMPLE_FILE['size'], sot.size) + self.assertEqual(EXAMPLE_FILE['download_link'], sot.download_link) + self.assertEqual(EXAMPLE_FILE['link_expired_time'], sot.expires_at) diff --git a/otcextensions/tests/unit/sdk/rds/v3/test_config.py b/otcextensions/tests/unit/sdk/rds/v3/test_config.py new file mode 100644 index 000000000..3bf9efb3f --- /dev/null +++ b/otcextensions/tests/unit/sdk/rds/v3/test_config.py @@ -0,0 +1,112 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from keystoneauth1 import adapter + +import copy +import mock + +from openstack.tests.unit import base + +from otcextensions.sdk.rds.v3 import configuration + + +IDENTIFIER = 'ID' +EXAMPLE = { + "id": IDENTIFIER, + "name": "paramsGroup-b6d2", + "description": "", + "datastore": {"type": "PostgreSQL"}, + "datastore_version_name": "2016_SE", + "datastore_name": "sqlserver", + "created": "2018-09-15T03:33:07+0800", + "updated": "2018-09-15T03:33:07+0800", + "user_defined": True, + "vcpus": "1", + "ram": 2, + "spec_code": "rds.mysql.c2.medium.ha", + "instance_mode": "ha", + "values": { + "max_connections": "10", + "autocommit": "OFF" + } +} + + +class TestConfiguration(base.TestCase): + + def setUp(self): + super(TestConfiguration, self).setUp() + self.sess = mock.Mock(spec=adapter.Adapter) + self.sess.put = mock.Mock() + + def test_basic(self): + sot = configuration.Configuration() + + self.assertEqual('/configurations', sot.base_path) + self.assertEqual('configurations', sot.resources_key) + self.assertIsNone(sot.resource_key) + self.assertTrue(sot.allow_list) + self.assertTrue(sot.allow_fetch) + self.assertTrue(sot.allow_create) + self.assertTrue(sot.allow_delete) + self.assertTrue(sot.allow_commit) + + def test_make_it(self): + sot = configuration.Configuration(**EXAMPLE) + self.assertEqual(IDENTIFIER, sot.id) + self.assertEqual(EXAMPLE['name'], sot.name) + self.assertEqual(EXAMPLE['datastore'], sot.datastore) + self.assertEqual(EXAMPLE['description'], sot.description) + self.assertEqual(EXAMPLE['datastore_name'], sot.datastore_name) + self.assertEqual(EXAMPLE['datastore_version_name'], + sot.datastore_version_name) + self.assertEqual(EXAMPLE['created'], sot.created_at) + self.assertEqual(EXAMPLE['updated'], sot.updated_at) + self.assertEqual(EXAMPLE['user_defined'], sot.is_user_defined) + self.assertEqual(EXAMPLE['values'], sot.values) + + def test_apply(self): + sot = configuration.Configuration(id=IDENTIFIER) + instances = ['id1', 'id2'] + + resp = mock.Mock() + resp.body = { + "configuration_id": IDENTIFIER, + "configuration_name": "paramsGroup-bcf9", + "apply_results": [{ + "instance_id": "fe5f5a07539c431181fc78220713aebein01", + "instance_name": "zyy1", + "restart_required": False, + "success": False + }, { + "instance_id": "73ea2bf70c73497f89ee0ad4ee008aa2in01", + "instance_name": "zyy2", + "restart_required": False, + "success": False + }], + "success": False + } + resp.json = mock.Mock(return_value=copy.deepcopy(resp.body)) + resp.headers = {} + resp.status_code = 200 + self.sess.put.return_value = resp + + updated = sot.apply(self.sess, instances) + self.sess.put.assert_called_with( + 'configurations/ID/apply', + json={'instance_ids': ['id1', 'id2']} + ) + + self.assertEqual(resp.body['configuration_id'], updated.id) + self.assertEqual(resp.body['configuration_name'], updated.name) + self.assertEqual(resp.body['apply_results'], + sot.apply_results) diff --git a/otcextensions/tests/unit/sdk/rds/v3/test_datastore.py b/otcextensions/tests/unit/sdk/rds/v3/test_datastore.py index f241d9e3a..0da2bd8d0 100644 --- a/otcextensions/tests/unit/sdk/rds/v3/test_datastore.py +++ b/otcextensions/tests/unit/sdk/rds/v3/test_datastore.py @@ -25,7 +25,7 @@ def setUp(self): def test_basic(self): sot = datastore.Datastore() self.assertEqual('dataStores', sot.resources_key) - self.assertEqual('/datastores/%(datastore_name)s', sot.base_path) + self.assertEqual('/datastores/%(database_name)s', sot.base_path) self.assertIsNone(sot.resource_key) self.assertTrue(sot.allow_list) self.assertFalse(sot.allow_fetch) diff --git a/otcextensions/tests/unit/sdk/rds/v3/test_flavor.py b/otcextensions/tests/unit/sdk/rds/v3/test_flavor.py index 07d9ebd42..3bf256ba6 100644 --- a/otcextensions/tests/unit/sdk/rds/v3/test_flavor.py +++ b/otcextensions/tests/unit/sdk/rds/v3/test_flavor.py @@ -23,9 +23,6 @@ class TestFlavor(base.TestCase): - def setUp(self): - super(TestFlavor, self).setUp() - def test_basic(self): sot = flavor.Flavor() @@ -47,6 +44,7 @@ def test_make_it(self): sot = flavor.Flavor(**EXAMPLE) self.assertEqual(EXAMPLE['spec_code'], sot.spec_code) + self.assertEqual(EXAMPLE['spec_code'], sot.name) self.assertEqual(EXAMPLE['vcpus'], sot.vcpus) self.assertEqual(EXAMPLE['ram'], sot.ram) self.assertEqual(EXAMPLE['instance_mode'], sot.instance_mode) diff --git a/otcextensions/tests/unit/sdk/rds/v3/test_instance.py b/otcextensions/tests/unit/sdk/rds/v3/test_instance.py index acb6a35e0..17631a28e 100644 --- a/otcextensions/tests/unit/sdk/rds/v3/test_instance.py +++ b/otcextensions/tests/unit/sdk/rds/v3/test_instance.py @@ -17,45 +17,30 @@ from otcextensions.sdk.rds.v3 import instance -# RDS requires those headers to be present in the request, to native API -# otherwise 404 -RDS_HEADERS = {'Content-Type': 'application/json', 'X-Language': 'en-us'} - -# RDS requires those headers to be present in the request, to OS-compat API -# otherwise 404 -OS_HEADERS = { - 'Content-Type': 'application/json', -} - # PROJECT_ID = '123' IDENTIFIER = 'IDENTIFIER' EXAMPLE = { - "flavor_ref": - "rds.mysql.s1.large", - "id": - IDENTIFIER, - "status": - "ACTIVE", - "name": - "mysql-0820-022709-01", - "port": - 3306, - "type": - "Single", - "region": - "eu-de", + "id": IDENTIFIER, + "availability_zone": "fake_az", + "status": "ACTIVE", + "name": "mysql-0820-022709-01", + "port": 3306, + "type": "Single", + "region": "eu-de", + "datastore": { + "type": "mysql", + "version": "5.7" + }, + "replica_of_id": "some_fake", + "created": "2018-08-20T02:33:49+0800", + "updated": "2018-08-20T02:33:50+0800", "volume": { "type": "ULTRAHIGH", "size": 100 }, - "datastore": { - "type": "mysql", - "version": "5.7" + "ha": { + "replication_mode": "sync" }, - "created": - "2018-08-20T02:33:49+0800", - "updated": - "2018-08-20T02:33:50+0800", "nodes": [{ "id": "06f1c2ad57604ae89e153e4d27f4e4b8no01", "name": "mysql-0820-022709-01_node0", @@ -65,27 +50,21 @@ }], "private_ips": ["192.168.0.142"], "public_ips": ["10.154.219.187", "10.154.219.186"], - "db_user_name": - "root", - "vpc_id": - "b21630c1-e7d3-450d-907d-39ef5f445ae7", - "subnet_id": - "45557a98-9e17-4600-8aec-999150bc4eef", - "security_group_id": - "38815c5c-482b-450a-80b6-0a301f2afd97", - "switch_strategy": - "", + "db_user_name": "root", + "password": "fake_pass", + "vpc_id": "b21630c1-e7d3-450d-907d-39ef5f445ae7", + "subnet_id": "45557a98-9e17-4600-8aec-999150bc4eef", + "security_group_id": "38815c5c-482b-450a-80b6-0a301f2afd97", + "flavor_ref": "rds.mysql.s1.large", + "switch_strategy": "", "backup_strategy": { "start_time": "19:00-20:00", "keep_days": 7 }, - "maintenance_window": - "02:00-06:00", + "maintenance_window": "02:00-06:00", "related_instance": [], - "disk_encryption_id": - "", - "time_zone": - "" + "disk_encryption_id": "", + "time_zone": "" } @@ -99,129 +78,58 @@ def setUp(self): def test_basic(self): sot = instance.Instance() - self.assertEqual('instance', sot.resource_key) + self.assertIsNone(sot.resource_key) self.assertEqual('instances', sot.resources_key) self.assertEqual('/instances', sot.base_path) self.assertTrue(sot.allow_list) self.assertTrue(sot.allow_create) - self.assertFalse(sot.allow_get) - self.assertTrue(sot.allow_update) + self.assertFalse(sot.allow_fetch) + self.assertTrue(sot.allow_commit) self.assertTrue(sot.allow_delete) def test_make_it(self): sot = instance.Instance(**EXAMPLE) self.assertEqual(IDENTIFIER, sot.id) - self.assertEqual(EXAMPLE['volume'], sot.volume) - self.assertEqual(EXAMPLE['flavor_ref'], sot.flavor_ref) + self.assertEqual(EXAMPLE['availability_zone'], sot.availability_zone) + self.assertEqual(EXAMPLE['backup_strategy'], sot.backup_strategy) + self.assertEqual(EXAMPLE['created'], sot.created_at) self.assertEqual(EXAMPLE['datastore'], sot.datastore) + self.assertEqual(EXAMPLE['disk_encryption_id'], sot.disk_encryption_id) + self.assertEqual(EXAMPLE['flavor_ref'], sot.flavor_ref) + self.assertEqual(EXAMPLE['ha'], sot.ha) + self.assertEqual(EXAMPLE['nodes'], sot.nodes) + self.assertEqual(EXAMPLE['password'], sot.password) + self.assertEqual(EXAMPLE['port'], sot.port) + self.assertEqual(EXAMPLE['volume'], sot.volume) self.assertEqual(EXAMPLE['region'], sot.region) self.assertEqual(EXAMPLE['port'], sot.port) - self.assertEqual(EXAMPLE['disk_encryption_id'], sot.disk_encryption_id) - self.assertEqual(EXAMPLE['vpc_id'], sot.vpc_id) - self.assertEqual(EXAMPLE['subnet_id'], sot.subnet_id) - self.assertEqual(EXAMPLE['security_group_id'], sot.security_group_id) self.assertEqual(EXAMPLE['private_ips'], sot.private_ips) self.assertEqual(EXAMPLE['public_ips'], sot.public_ips) - self.assertEqual(EXAMPLE['nodes'], sot.nodes) - self.assertEqual(EXAMPLE['db_user_name'], sot.db_user_name) - self.assertEqual(EXAMPLE['backup_strategy'], sot.backup_strategy) + self.assertEqual(EXAMPLE['region'], sot.region) + self.assertEqual(EXAMPLE['related_instance'], sot.related_instances) + self.assertEqual(EXAMPLE['replica_of_id'], sot.replica_of_id) + self.assertEqual(EXAMPLE['vpc_id'], sot.router_id) + self.assertEqual(EXAMPLE['security_group_id'], sot.security_group_id) + self.assertEqual(EXAMPLE['subnet_id'], sot.subnet_id) + self.assertEqual(EXAMPLE['db_user_name'], sot.user_name) self.assertEqual(EXAMPLE['switch_strategy'], sot.switch_strategy) self.assertEqual(EXAMPLE['maintenance_window'], sot.maintenance_window) - self.assertEqual(EXAMPLE['related_instance'], sot.related_instance) - self.assertEqual(EXAMPLE['disk_encryption_id'], sot.disk_encryption_id) self.assertEqual(EXAMPLE['time_zone'], sot.time_zone) - def test_list(self): - - mock_response = mock.Mock() - mock_response.status_code = 200 - mock_response.json.return_value = { - "instances": [{ - "id": - IDENTIFIER, - "status": - "ACTIVE", - "name": - "mysql-0820-022709-01", - "port": - 3306, - "type": - "Single", - "region": - "eu-de", - "datastore": { - "type": "mysql", - "version": "5.7" - }, - "created": - "2018-08-20T02:33:49+0800", - "updated": - "2018-08-20T02:33:50+0800", - "volume": { - "type": "ULTRAHIGH", - "size": 100 - }, - "nodes": [{ - "id": "06f1c2ad57604ae89e153e4d27f4e4b8no01", - "name": "mysql-0820-022709-01_node0", - "role": "master", - "status": "ACTIVE", - "availability_zone": "eu-de-01" - }], - "private_ips": ["192.168.0.142"], - "public_ips": ["10.154.219.187", "10.154.219.186"], - "db_user_name": - "root", - "vpc_id": - "b21630c1-e7d3-450d-907d-39ef5f445ae7", - "subnet_id": - "45557a98-9e17-4600-8aec-999150bc4eef", - "security_group_id": - "38815c5c-482b-450a-80b6-0a301f2afd97", - "flavor_ref": - "rds.mysql.s1.large", - "switch_strategy": - "", - "backup_strategy": { - "start_time": "19:00-20:00", - "keep_days": 7 - }, - "maintenance_window": - "02:00-06:00", - "related_instance": [], - "disk_encryption_id": - "", - "time_zone": - "" - }], - "total_count": - 1 - } - - self.sess.get.return_value = mock_response - - result = list(self.sot.list(self.sess)) - - self.sess.get.assert_called_once_with( - '/instances', - params={}, - ) - - self.assertEqual([instance.Instance(**EXAMPLE)], result) - - -# def test_action_restore(self): -# sot = instance.Instance(**EXAMPLE) -# response = mock.Mock() -# response.json = mock.Mock(return_value='') -# sess = mock.Mock() -# sess.post = mock.Mock(return_value=response) -# backupRef = 'backupRef' -# -# self.assertIsNotNone(sot.restore(sess, backupRef)) -# -# url = ("instances/%(id)s/action" % { -# 'id': IDENTIFIER, -# }) -# body = {'restore': {'backupRef': backupRef}} -# sess.post.assert_called_with(url, json=body) + def test_fetch_restore_times(self): + sot = instance.Instance(**EXAMPLE) + restore_times = [{ + 'start_time': 'some_start_time', + 'end_time': 'some_end_time' + }] + response = mock.Mock() + response.status_code = 200 + response.json.return_value = { + 'restore_time': restore_times} + response.headers = {} + self.sess.get.return_value = response + + rt = sot.fetch_restore_times(self.sess) + + self.assertEqual(restore_times, rt) + self.assertEqual(restore_times, sot.restore_time) diff --git a/otcextensions/tests/unit/sdk/rds/v3/test_proxy.py b/otcextensions/tests/unit/sdk/rds/v3/test_proxy.py index f05d4968c..e8da1a671 100644 --- a/otcextensions/tests/unit/sdk/rds/v3/test_proxy.py +++ b/otcextensions/tests/unit/sdk/rds/v3/test_proxy.py @@ -11,7 +11,11 @@ # under the License. from otcextensions.sdk.rds.v3 import _proxy +from otcextensions.sdk.rds.v3 import backup +from otcextensions.sdk.rds.v3 import configuration +from otcextensions.sdk.rds.v3 import datastore from otcextensions.sdk.rds.v3 import flavor +from otcextensions.sdk.rds.v3 import instance from openstack.tests.unit import test_proxy_base @@ -22,7 +26,7 @@ def setUp(self): self.proxy = _proxy.Proxy(self.session) -class TestRdsFlavor(TestRdsProxy): +class TestFlavor(TestRdsProxy): def test_flavors(self): self.verify_list(self.proxy.flavors, flavor.Flavor, @@ -34,3 +38,131 @@ def test_flavors(self): 'datastore_name': 'MySQL', 'version_name': '5.7' }) + + +class TestDatastore(TestRdsProxy): + def test_datastores(self): + self.verify_list(self.proxy.datastores, + datastore.Datastore, + method_args=['ss'], + expected_kwargs={ + 'database_name': 'ss' + }) + + +class TestConfiguration(TestRdsProxy): + def test_configurations(self): + self.verify_list(self.proxy.configurations, + configuration.Configuration, + expected_kwargs={'paginated': False}) + + def test_get_configuration(self): + self.verify_get( + self.proxy.get_configuration, + configuration.Configuration) + + def test_create_configuration(self): + self.verify_create( + self.proxy.create_configuration, + configuration.Configuration, + method_kwargs={'a': 'b'}, + expected_kwargs={'prepend_key': False, 'a': 'b'}) + + def test_delete_configuration(self): + self.verify_delete(self.proxy.delete_configuration, + configuration.Configuration, False) + + def test_delete_configuration_ignore(self): + self.verify_delete(self.proxy.delete_configuration, + configuration.Configuration, True) + + def test_apply_configuration(self): + self._verify( + 'otcextensions.sdk.rds.v3.configuration.Configuration.apply', + self.proxy.apply_configuration, + method_args=["val", ['a', 'b']], + expected_args=[['a', 'b']] + ) + + +class TestBackup(TestRdsProxy): + def test_backups(self): + self.verify_list(self.proxy.backups, + backup.Backup) + + def test_create_backup(self): + self.verify_create( + self.proxy.create_backup, + backup.Backup, + method_args=['inst'], + method_kwargs={'x': 1, 'y': 2, 'z': 3}, + expected_kwargs={ + 'instance_id': 'inst', + 'x': 1, 'y': 2, 'z': 3 + }) + + def test_delete_backup(self): + self.verify_delete(self.proxy.delete_backup, + backup.Backup, False) + + def test_delete_backup_ignore(self): + self.verify_delete(self.proxy.delete_backup, + backup.Backup, True) + + def test_get_instance_backup_policy(self): + self.verify_get( + self.proxy.get_instance_backup_policy, + backup.BackupPolicy, + expected_args=[backup.BackupPolicy], + expected_kwargs={'instance_id': 'value'}) + + def test_update_instance_backup_policy(self): + self.verify_update( + self.proxy.update_instance_backup_policy, + backup.BackupPolicy) + + def test_download_linkss(self): + self.verify_list(self.proxy.backup_download_links, + backup.BackupFile, + method_args=['bck_id'], + expected_kwargs={'backup_id': 'bck_id'}) + + +class TestInstance(TestRdsProxy): + def test_instances(self): + self.verify_list(self.proxy.instances, + instance.Instance) + + def test_create_instance(self): + self.verify_create( + self.proxy.create_instance, + instance.Instance) + + def test_find_instance(self): + self.verify_find( + self.proxy.find_instance, + instance.Instance) + + def test_delete_instance(self): + self.verify_delete(self.proxy.delete_instance, + instance.Instance, False) + + def test_delete_instance_ignore(self): + self.verify_delete(self.proxy.delete_instance, + instance.Instance, True) + + def test_fetch_restore_times(self): + self._verify2( + 'otcextensions.sdk.rds.v3.instance.Instance.fetch_restore_times', + self.proxy.get_instance_restore_time, + method_args=["inst"], + expected_args=[self.proxy] + ) + + def test_get_instance_configuration(self): + pass + + def test_update_instance_configuration(self): + pass + + From a991818d8974fe90dbde13241c04586874e5c509 Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Tue, 8 Oct 2019 15:31:45 +0200 Subject: [PATCH 10/65] next bunch of changes for instance --- otcextensions/osclient/rds/v3/instance.py | 352 +++++++--------- otcextensions/sdk/rds/v3/__init__.py | 0 otcextensions/sdk/rds/v3/_proxy.py | 114 ++---- otcextensions/sdk/rds/v3/instance.py | 50 ++- .../tests/unit/osclient/rds/v3/fakes.py | 25 +- .../unit/osclient/rds/v3/test_instance.py | 378 +++++++++++++++++- .../tests/unit/sdk/rds/v3/test_instance.py | 51 ++- .../tests/unit/sdk/rds/v3/test_proxy.py | 16 + setup.cfg | 13 - 9 files changed, 656 insertions(+), 343 deletions(-) create mode 100644 otcextensions/sdk/rds/v3/__init__.py diff --git a/otcextensions/osclient/rds/v3/instance.py b/otcextensions/osclient/rds/v3/instance.py index 3cc5fc83a..9981ead11 100644 --- a/otcextensions/osclient/rds/v3/instance.py +++ b/otcextensions/osclient/rds/v3/instance.py @@ -176,121 +176,146 @@ def take_action(self, parsed_args): class CreateDatabaseInstance(command.ShowOne): - _description = _("Creates a new database instance.") + _description = _("Create a new database instance.") def get_parser(self, prog_name): parser = super(CreateDatabaseInstance, self).get_parser(prog_name) parser.add_argument( 'name', metavar='', - help=_("Name of the instance."), + help=_("Name of the instance.") ) parser.add_argument( 'flavor_ref', metavar='', - help=_("Flavor spec_code"), + help=_("Flavor spec_code") ) - parser.add_argument( + disk_group = parser.add_argument_group('Disk data') + disk_group.add_argument( '--size', metavar='', type=int, required=True, - help=_("Size of the instance disk volume in GB. "), + help=_("Size of the instance disk volume in GB. ") ) - parser.add_argument( + disk_group.add_argument( '--volume-type', metavar='', type=str, default=None, choices=['COMMON', 'ULTRAHIGH'], - help=_("Volume type. (COMMON, ULTRAHIGH)."), + help=_("Volume type. (COMMON, ULTRAHIGH).") ) parser.add_argument( '--availability-zone', metavar='', - default=None, - help=_("The Zone hint to give to Nova."), + required=True, + help=_("The Zone hint to give to Nova.") ) parser.add_argument( + '--region', + metavar='', + required=True, + help=argparse.SUPPRESS, + ) + ds_group = parser.add_argument_group('Datasoure parameters') + ds_group.add_argument( '--datastore', metavar='', - default=None, - help=_("datastore name"), + required=True, + help=_("Name of the datastore.") ) - parser.add_argument( + ds_group.add_argument( '--datastore-version', - default=None, + required=True, metavar='', - help=_("datastore version."), + help=_("Datastore version.") ) parser.add_argument( '--configuration', dest='configuration_id', metavar='', default=None, - help=_("ID of the configuration group to attach to the instance."), + help=_("ID of the configuration group to attach to the instance.") ) parser.add_argument( '--disk-encryption-id', metavar='', default=None, - help=_("key ID for disk encryption."), + help=_("key ID for disk encryption.") ) - parser.add_argument( + new_instance_group = parser.add_argument_group( + 'New instance parameters', + 'Parameters to be used for the new instance creation (not when ' + 'created as replica or from backup') + new_instance_group.add_argument( '--port', metavar='', - default=None, type=int, - help=_("Database Port"), + help=_("Database Port") ) - parser.add_argument( + new_instance_group.add_argument( '--password', metavar='', - help=_("ID of the configuration group to attach to the instance."), + help=_("ID of the configuration group to attach to the instance.") ) - parser.add_argument( - '--replica-of', - metavar='', - default=None, - help=_("ID or name of an existing instance to replicate from."), + new_instance_group.add_argument( + '--router-id', + metavar='', + help=_('ID of a Router the DB should be connected to') ) - parser.add_argument( - '--region', - metavar='', - type=str, - default=None, - help=argparse.SUPPRESS, + new_instance_group.add_argument( + '--subnet-id', + metavar='', + help=_('ID of a subnet the DB should be connected to.') + ) + new_instance_group.add_argument( + '--security-group-id', + dest='security_group_id', + metavar='', + help=_('Security group ID') ) - parser.add_argument('--router-id', - metavar='', - type=str, - help=_('ID of a Router the DB should be connected ' - 'to')) - parser.add_argument('--subnet-id', - metavar='', - type=str, - help=_('ID of a subnet the DB should be connected ' - 'to.')) - parser.add_argument('--security-group-id', - dest='security_group_id', - metavar='', - type=str, - help=_('Security group ID')) parser.add_argument( '--ha-mode', metavar='', - type=str, + help=_('replication mode for the standby DB instance') + ) + parser.add_argument( + '--charge-mode', + metavar='', + default='postPaid', + help=_('Specifies the billing mode') + ) + create_from_group = parser.add_argument_group( + 'Create FROM group', + 'Parameters to be used when creating new instance as a ' + 'replica or from backup') + create_from_group.add_argument( + '--replica-of', + metavar='', default=None, - help=_('replication mode for the standby DB instance')) - parser.add_argument('--charge-mode', - metavar='', - type=str, - default='postPaid', - help=_('Specifies the billing mode')) + help=_("ID or name of an existing instance to replicate from.") + ) + create_from_group.add_argument( + '--from-backup', + metavar='', + help=_('Backup ID or Name to create new instance from,') + ) + create_from_group.add_argument( + '--from-instance', + metavar='', + help=_('Source instance ID or Name to create from. ' + 'This requires setting restore_time additionally.') + ) + create_from_group.add_argument( + '--restore-time', + metavar='