summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLingxian Kong <anlin.kong@gmail.com>2020-10-07 18:50:53 +1300
committerLingxian Kong <anlin.kong@gmail.com>2020-10-09 11:43:48 +1300
commit1d24b65052bbfb8f7dff5744c7bb44a58a2336c6 (patch)
treeaaa0fe3f8feb9b6640ca11aa69bf122ce0505ad9
parent680c7b8361521c494ae93d13b5d33c43a27d35ad (diff)
downloadtrove-1d24b65052bbfb8f7dff5744c7bb44a58a2336c6.tar.gz
Image tags support in datastore version
Change-Id: I0e51d08515c121e3a7b0e82b6e4c4161bb4fbc4a
-rw-r--r--api-ref/source/datastore-versions.inc12
-rwxr-xr-xapi-ref/source/parameters.yaml53
-rw-r--r--devstack/plugin.sh2
-rw-r--r--doc/source/admin/building_guest_images.rst18
-rw-r--r--doc/source/admin/datastore.rst212
-rw-r--r--doc/source/admin/run_trove_in_production.rst19
-rw-r--r--doc/source/admin/upgrade.rst2
-rw-r--r--doc/source/cli/trove-manage.rst9
-rw-r--r--doc/source/install/verify.rst10
-rwxr-xr-xintegration/scripts/trovestack4
-rw-r--r--releasenotes/notes/wallaby-datastore-version-image-tags.yaml5
-rw-r--r--trove/cmd/manage.py12
-rw-r--r--trove/common/apischema.py22
-rw-r--r--trove/common/exception.py5
-rw-r--r--trove/common/glance.py36
-rw-r--r--trove/datastore/models.py15
-rw-r--r--trove/datastore/views.py5
-rw-r--r--trove/db/sqlalchemy/migrate_repo/versions/047_image_tag_in_datastore_version.py29
-rw-r--r--trove/extensions/mgmt/datastores/service.py53
-rw-r--r--trove/extensions/mgmt/datastores/views.py2
-rw-r--r--trove/tests/api/datastores.py8
-rw-r--r--trove/tests/unittests/datastore/base.py84
-rw-r--r--trove/tests/unittests/datastore/test_capability.py9
-rw-r--r--trove/tests/unittests/datastore/test_datastore_version_metadata.py62
-rw-r--r--trove/tests/unittests/datastore/test_datastore_versions.py6
-rw-r--r--trove/tests/unittests/extensions/mgmt/__init__.py (renamed from trove/tests/unittests/mgmt/__init__.py)0
-rw-r--r--trove/tests/unittests/extensions/mgmt/datastores/__init__.py0
-rw-r--r--trove/tests/unittests/extensions/mgmt/datastores/test_service.py324
-rw-r--r--trove/tests/unittests/extensions/mgmt/instances/__init__.py0
-rw-r--r--trove/tests/unittests/extensions/mgmt/instances/test_models.py (renamed from trove/tests/unittests/mgmt/test_models.py)0
-rw-r--r--trove/tests/unittests/mgmt/test_clusters.py99
-rw-r--r--trove/tests/unittests/mgmt/test_datastore_controller.py198
-rw-r--r--trove/tests/unittests/mgmt/test_datastores.py165
-rw-r--r--trove/tests/unittests/trove_testtools.py26
34 files changed, 721 insertions, 785 deletions
diff --git a/api-ref/source/datastore-versions.inc b/api-ref/source/datastore-versions.inc
index 7318006e..c431b1b2 100644
--- a/api-ref/source/datastore-versions.inc
+++ b/api-ref/source/datastore-versions.inc
@@ -286,6 +286,13 @@ Request
.. rest_parameters:: parameters.yaml
- project_id: project_id
+ - name: datastore_version_name
+ - datastore_name: datastore_name_required
+ - datastore_manager: datastore_type1
+ - image: image_id
+ - image_tags: image_tags
+ - active: active
+ - default: default
Request Example
---------------
@@ -363,6 +370,11 @@ Request
- project_id: project_id
- datastore_version_id: datastore_version_id
+ - datastore_manager: datastore_type
+ - image: image_id
+ - image_tags: image_tags
+ - active: active_optional
+ - default: default
Request Example
---------------
diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml
index 40fd98d9..101f15d2 100755
--- a/api-ref/source/parameters.yaml
+++ b/api-ref/source/parameters.yaml
@@ -99,6 +99,18 @@ access_is_public:
in: body
required: false
type: boolean
+active:
+ description: |
+ Whether the database version is enabled.
+ in: body
+ required: true
+ type: boolean
+active_optional:
+ description: |
+ Whether the database version is enabled.
+ in: body
+ required: false
+ type: boolean
availability_zone:
description: |
The availability zone of the instance.
@@ -302,6 +314,12 @@ datastore2:
in: body
required: true
type: object
+datastore_name_required:
+ description: |
+ The name of a datastore.
+ in: body
+ required: true
+ type: string
datastore_type:
description: |
The type of a datastore.
@@ -339,6 +357,14 @@ datastore_version_name:
in: body
required: true
type: string
+default:
+ description: |
+ When true this datastore version is created as the default in the
+ datastore. If not specified, for creating, default is false, for updating,
+ it's ignored.
+ in: body
+ required: false
+ type: boolean
description:
description: |
New description of the configuration group.
@@ -392,6 +418,33 @@ flavorRef:
in: body
required: true
type: string
+image_id:
+ description: |
+ The ID of an image.
+
+ Either ``image`` or ``image_tags`` needs to be specified when creating
+ datastore version.
+ in: body
+ required: false
+ type: string
+image_tags:
+ description: |
+ List of image tags.
+
+ Either ``image`` or ``image_tags`` needs to be specified when creating
+ datastore version.
+
+ If the image ID is not provided, the image can be retrieved by the image
+ tags. The tags are used for filtering as a whole rather than separately.
+ Using image tags is more flexible than ID especially when a new guest image
+ is uploaded to Glance, Trove can pick up the latest image automatically for
+ creating instances.
+
+ When updating, only specifying ``image_tags`` could remove ``image``
+ from the datastore version.
+ in: body
+ required: false
+ type: array
instance:
description: |
An ``instance`` object.
diff --git a/devstack/plugin.sh b/devstack/plugin.sh
index 0e133e18..2a753f4b 100644
--- a/devstack/plugin.sh
+++ b/devstack/plugin.sh
@@ -481,7 +481,7 @@ function create_guest_image {
echo "Register the image in datastore"
$TROVE_MANAGE datastore_update $TROVE_DATASTORE_TYPE ""
- $TROVE_MANAGE datastore_version_update $TROVE_DATASTORE_TYPE $TROVE_DATASTORE_VERSION $TROVE_DATASTORE_TYPE $glance_image_id "" 1
+ $TROVE_MANAGE datastore_version_update $TROVE_DATASTORE_TYPE $TROVE_DATASTORE_VERSION $TROVE_DATASTORE_TYPE $glance_image_id "trove" "" 1
$TROVE_MANAGE datastore_update $TROVE_DATASTORE_TYPE $TROVE_DATASTORE_VERSION
echo "Add parameter validation rules if available"
diff --git a/doc/source/admin/building_guest_images.rst b/doc/source/admin/building_guest_images.rst
index 6f6dd2fa..df5f49ee 100644
--- a/doc/source/admin/building_guest_images.rst
+++ b/doc/source/admin/building_guest_images.rst
@@ -3,15 +3,9 @@
.. role:: bash(code)
:language: bash
-=========================================
-Building Guest Images for OpenStack Trove
-=========================================
-
-.. If section numbers are desired, unindent this
- .. sectnum::
-
-.. If a TOC is desired, unindent this
- .. contents::
+====================
+Building guest image
+====================
Overview
========
@@ -199,14 +193,16 @@ image in Glance and register a new datastore or version in Trove using
--private \
--disk-format qcow2 \
--container-format bare \
+ --tag trove --tag mysql \
--file ~/images/trove-guest-ubuntu-bionic-dev.qcow2
- $ trove-manage datastore_version_update mysql 5.7.29 mysql $image_id "" 1
+ $ openstack datastore version create 5.7.29 mysql mysql "" \
+ --image-tags trove,mysql \
+ --active --default
$ trove-manage db_load_datastore_config_parameters mysql 5.7.29 ${trove_repo_dir}/trove/templates/mysql/validation-rules.json
.. note::
The command ``trove-manage`` needs to run on Trove controller node.
- Otherwise, you can use ``openstack datastore version create`` CLI.
If you see anything error or need help for the image creation, please ask help
either in ``#openstack-trove`` IRC channel or sending emails to
diff --git a/doc/source/admin/datastore.rst b/doc/source/admin/datastore.rst
index 9ca9e40a..d98fdef4 100644
--- a/doc/source/admin/datastore.rst
+++ b/doc/source/admin/datastore.rst
@@ -4,44 +4,44 @@
Datastore
=========
-The Database service provides database management features.
-
Introduction
~~~~~~~~~~~~
-The Database service provides scalable and reliable cloud
-provisioning functionality for both relational and non-relational
-database engines. Users can quickly and easily use database features
-without the burden of handling complex administrative tasks. Cloud
-users and database administrators can provision and manage multiple
-database instances as needed.
+A datastore is typically created as a type of database. For each datastore,
+there could be multiple datastore versions. For example, for MySQL database,
+Trove could support 5.7.29, 5.7.30 or 5.8.
+
+Admin user needs to create datastore and its versions as required.
-The Database service provides resource isolation at high performance
-levels, and automates complex administrative tasks such as deployment,
-configuration, patching, backups, restores, and monitoring.
+A datastore version is always associated with a Glance image, either by image
+ID or image tags. If the image ID is not provided, the image can be retrieved
+by the image tags. The tags are used for filtering as a whole rather than
+separately. Using image tags is more flexible than ID especially when a new
+guest image is uploaded to Glance, Trove can pick up the latest image
+automatically for creating instances.
-Create datastore
-~~~~~~~~~~~~~~~~
+Create datastore version
+~~~~~~~~~~~~~~~~~~~~~~~~
-An administrative user can create datastores for a variety of databases.
+When creating a datastore version, Trove will create the datastore first if it
+doesn't exist.
-This section assumes you do not yet have a MySQL data store, and shows
-you how to create a MySQL data store and populate it with a MySQL 5.5
-data store version.
+When using image tags, make sure the image with the tags exists before creating
+the datastore version.
.. note::
From Victoria release, all the datastores can be configured with a same
- Glance image but with different datastore name and version number.
+ Glance image but with different datastore name and version name.
-**To create a data store**
+To create a datastore version:
-#. **Create a trove image**
+#. Create a trove guest image
Refer to `Build images using trovestack
<https://docs.openstack.org/trove/latest/admin/building_guest_images.html#build-images-using-trovestack>`_
-#. **Register image with Image service**
+#. Register image with Image service
You need to register your guest image with the Image service as cloud admin.
@@ -53,133 +53,17 @@ data store version.
--disk-format qcow2 --container-format bare \
--file $image_file \
--property hw_rng_model='virtio' \
- --tag trove
-
-#. **Create the datastore**
-
- Create the data store that configured with the new image. To do this, use
- the :command:`trove-manage` :command:`datastore_update` command.
-
- This example uses the following arguments:
-
- .. list-table::
- :header-rows: 1
- :widths: 20 20 20
-
- * - Argument
- - Description
- - In this example:
- * - config file
- - The configuration file to use.
- - ``--config-file=/etc/trove/trove.conf``
- * - name
- - Name you want to use for this data store.
- - ``mysql``
- * - default version
- - You can attach multiple versions/images to a data store. For
- example, you might have a MySQL 5.5 version and a MySQL 5.6
- version. You can designate one version as the default, which
- the system uses if a user does not explicitly request a
- specific version.
- - ``""``
-
- At this point, you do not yet have a default version, so pass
- in an empty string.
-
- |
-
- Example:
-
- .. code-block:: console
-
- $ trove-manage --config-file=/etc/trove/trove.conf datastore_update mysql ""
-
-#. **Add a version to the new data store**
+ --tag trove --tag mysql
- Now that you have a MySQL data store, you can add a version to it,
- using the :command:`trove-manage` :command:`datastore_version_update`
- command. The version indicates which guest image to use.
-
- This example uses the following arguments:
-
- .. list-table::
- :header-rows: 1
- :widths: 20 20 20
-
- * - Argument
- - Description
- - In this example:
-
- * - config file
- - The configuration file to use.
- - ``--config-file=/etc/trove/trove.conf``
-
- * - data store
- - The name of the data store you just created via
- ``trove-manage`` :command:`datastore_update`.
- - ``mysql``
-
- * - version name
- - The name of the version you are adding to the data store.
- - ``mysql-5.5``
-
- * - data store manager
- - Which data store manager to use for this version. Typically,
- the data store manager is identified by one of the following
- strings, depending on the database:
-
- * cassandra
- * couchbase
- * couchdb
- * db2
- * mariadb
- * mongodb
- * mysql
- * percona
- * postgresql
- * pxc
- * redis
- * vertica
- - ``mysql``
-
- * - glance ID
- - The ID of the guest image you just added to the Image
- service. You can get this ID by using the glance
- :command:`image-show` IMAGE_NAME command.
- - bb75f870-0c33-4907-8467-1367f8cb15b6
-
- * - packages
- - If you want to put additional packages on each guest that
- you create with this data store version, you can list the
- package names here.
- - ``""``
-
- In this example, the guest image already contains all the
- required packages, so leave this argument empty.
-
- * - active
- - Set this to either 1 or 0:
- * ``1`` = active
- * ``0`` = disabled
- - 1
-
- |
-
- Example:
+#. Create the datastore version
.. code-block:: console
- $ trove-manage --config-file=/etc/trove/trove.conf datastore_version_update mysql mysql-5.5 mysql GLANCE_ID "" 1
+ openstack datastore version create 5.7.29 mysql mysql "" \
+ --image-tags trove,mysql \
+ --active --default
- **Optional.** Set your new version as the default version. To do
- this, use the :command:`trove-manage` :command:`datastore_update`
- command again, this time specifying the version you just created.
-
- .. code-block:: console
-
- $ trove-manage --config-file=/etc/trove/trove.conf datastore_update mysql mysql-5.5
-
-#. **Load validation rules for configuration groups**
+#. Load validation rules for configuration groups
**Background.** You can manage database configuration tasks by using
configuration groups. Configuration groups let you set configuration
@@ -200,21 +84,20 @@ data store version.
* - Ubuntu 18.04
- :file:`/usr/lib/python3/dist-packages/trove/templates/DATASTORE_NAME`
- - DATASTORE_NAME is the name of either the MySQL data store or
- the Percona data store. This is typically either ``mysql``
- or ``percona``.
+ - DATASTORE_NAME is the name of the datastore, e.g. ``mysql``
+ or ``postgresql``.
* - RHEL 7, CentOS 7, Fedora 20, and Fedora 21
- :file:`/usr/lib/python3/site-packages/trove/templates/DATASTORE_NAME`
- - DATASTORE_NAME is the name of either the MySQL data store or
- the Percona data store. This is typically either ``mysql`` or ``percona``.
+ - DATASTORE_NAME is the name of the datastore, e.g. ``mysql``
+ or ``postgresql``.
|
Therefore, as part of creating a data store, you need to load the
``validation-rules.json`` file, using the :command:`trove-manage`
- :command:`db_load_datastore_config_parameters` command. This command
- takes the following arguments:
+ :command:`db_load_datastore_config_parameters` command on trove controller
+ node. This command takes the following arguments:
* Data store name
* Data store version
@@ -227,30 +110,15 @@ data store version.
.. code-block:: console
- $ trove-manage db_load_datastore_config_parameters mysql mysql-5.5 /usr/lib/python3/dist-packages/trove/templates/mysql/validation-rules.json
-
-#. **Validate data store**
-
- To validate your new data store and version, start by listing the
- data stores on your system:
-
- .. code-block:: console
+ $ trove-manage db_load_datastore_config_parameters mysql 5.7.29 /usr/lib/python3/dist-packages/trove/templates/mysql/validation-rules.json
- $ openstack datastore list
- +--------------------------------------+--------------+
- | id | name |
- +--------------------------------------+--------------+
- | 10000000-0000-0000-0000-000000000001 | Legacy MySQL |
- | e5dc1da3-f080-4589-a4c2-eff7928f969a | mysql |
- +--------------------------------------+--------------+
+Hide a datastore version
+~~~~~~~~~~~~~~~~~~~~~~~~
- Show the versions of a specific datastore:
+Sometimes, it's needed to make a datastore version invisible to the cloud
+users, e.g when a datastore version is deprecated or creating a datastore
+version for testing purpose, to do that:
.. code-block:: console
- $ openstack datastore version list mysql
- +--------------------------------------+-----------+
- | id | name |
- +--------------------------------------+-----------+
- | 36a6306b-efd8-4d83-9b75-8b30dd756381 | mysql-5.5 |
- +--------------------------------------+-----------+
+ $ openstack datastore version <version-id> --disable
diff --git a/doc/source/admin/run_trove_in_production.rst b/doc/source/admin/run_trove_in_production.rst
index 53d5cfab..7f0120a7 100644
--- a/doc/source/admin/run_trove_in_production.rst
+++ b/doc/source/admin/run_trove_in_production.rst
@@ -313,7 +313,8 @@ for more information. Make sure to use ``dev_mode=false`` for production
environment.
After image is created successfully, the cloud administrator needs to upload
-the image to Glance and make it only accessible to service users.
+the image to Glance and make it only accessible to service users. It's
+recommended to use tags when creating Glance image.
Preparing the Datastore
@@ -323,18 +324,18 @@ datastore versions and the configuration parameters for the particular version.
It's recommended to config a default version for each datastore.
+``trove-manage`` can be only used on trove controller node.
+
Command examples:
.. code-block:: console
- # Create a new datastore 'mysql'
- trove-manage datastore_update mysql ""
- # Create a new datastore version 5.7.29 for 'mysql'
- trove-manage datastore_version_update mysql 5.7.29 mysql $imageid "" 1
- # Use 5.7.29 as the default datastore version for 'mysql'
- trove-manage datastore_update mysql 5.7.29
- # Register configuration parameters for 5.7.29 version of datastore 'mysql'
- trove-manage db_load_datastore_config_parameters mysql 5.7.29 ${trove_repo_dir}}/trove/templates/mysql/validation-rules.json
+ $ # Creating datastore 'mysql' and datastore version 5.7.29.
+ $ openstack datastore version create 5.7.29 mysql mysql "" \
+ --image-tags trove,mysql \
+ --active --default
+ $ # Register configuration parameters for the datastore version
+ $ trove-manage db_load_datastore_config_parameters mysql 5.7.29 ${trove_repo_dir}}/trove/templates/mysql/validation-rules.json
Quota Management
diff --git a/doc/source/admin/upgrade.rst b/doc/source/admin/upgrade.rst
index 24d553f0..a9883c28 100644
--- a/doc/source/admin/upgrade.rst
+++ b/doc/source/admin/upgrade.rst
@@ -157,7 +157,7 @@ Upgrade Trove services
--property hw_rng_model='virtio' \
--tag trove \
-c id -f value)
- $ trove-manage datastore_version_update mysql 5.7.29 mysql $imageid "" 1
+ $ trove-manage datastore_version_update mysql 5.7.29 mysql $imageid "" "" 1
$ trove-manage db_load_datastore_config_parameters mysql 5.7.29 $stackdir/trove/trove/templates/mysql/validation-rules.json
Upgrade Trove guest agent
diff --git a/doc/source/cli/trove-manage.rst b/doc/source/cli/trove-manage.rst
index c9f50284..568c56fb 100644
--- a/doc/source/cli/trove-manage.rst
+++ b/doc/source/cli/trove-manage.rst
@@ -200,7 +200,7 @@ trove-manage datastore_version_update
usage: trove-manage datastore_version_update [-h]
datastore version_name manager
- image_id packages active
+ image_id image_tags packages active
Add or update a datastore version. If the datastore version already exists,
all values except the datastore name and version will be updated.
@@ -219,6 +219,13 @@ all values except the datastore name and version will be updated.
``image_id``
ID of the image used to create an instance of the datastore version.
+``image_tags``
+ List of image tags separated by comma. If the image ID is not provided
+ explicitly, the image can be retrieved by the image tags. Multiple image tags
+ are separated by comma, e.g. trove,mysql. Using image tags is more flexible
+ than ID especially when new guest image is uploaded to Glance, Trove can pick
+ up the latest image automatically for creating instances.
+
``packages``
Packages required by the datastore version that are installed on
the guest image.
diff --git a/doc/source/install/verify.rst b/doc/source/install/verify.rst
index ffeedff6..a607ee58 100644
--- a/doc/source/install/verify.rst
+++ b/doc/source/install/verify.rst
@@ -27,15 +27,13 @@ Verify operation of the Database service.
Create an image for the type of database you want to use, for example,
MySQL, MariaDB, etc.
- * Create a datastore. You need to create a separate datastore for
- each type of database you want to use, for example, MySQL, MongoDB,
- Cassandra. This example shows you how to create a datastore for a
- MySQL database:
+ * Create a datastore. You need to create at least one datastore version for
+ each type of database supported. This example creates a datastore version
+ for MySQL 5.7.29:
.. code-block:: console
- $ trove-manage datastore_update mysql ""
- $ trove-manage datastore_version_update mysql 5.7 mysql $imageid "" 1
+ $ openstack datastore version create 5.7.29 mysql mysql "" trove,mysql --active --default
#. Create a database `instance
<http://docs.openstack.org/user-guide/create_db.html>`_.
diff --git a/integration/scripts/trovestack b/integration/scripts/trovestack
index 3f65f5d2..53334244 100755
--- a/integration/scripts/trovestack
+++ b/integration/scripts/trovestack
@@ -524,8 +524,8 @@ function cmd_set_datastore() {
local IMAGEID=$1
rd_manage datastore_update "$datastore" ""
- # trove-manage datastore_version_update <datastore_name> <version_name> <datastore_manager> <image_id> <packages> <active>
- rd_manage datastore_version_update "${DATASTORE_TYPE}" "${DATASTORE_VERSION}" "${DATASTORE_TYPE}" $IMAGEID "" 1
+ # trove-manage datastore_version_update <datastore_name> <version_name> <datastore_manager> <image_id> <image_tags> <packages> <active>
+ rd_manage datastore_version_update "${DATASTORE_TYPE}" "${DATASTORE_VERSION}" "${DATASTORE_TYPE}" $IMAGEID "" "" 1
rd_manage datastore_update "${DATASTORE_TYPE}" "${DATASTORE_VERSION}"
if [[ -f "$PATH_TROVE"/trove/templates/${DATASTORE_TYPE}/validation-rules.json ]]; then
diff --git a/releasenotes/notes/wallaby-datastore-version-image-tags.yaml b/releasenotes/notes/wallaby-datastore-version-image-tags.yaml
new file mode 100644
index 00000000..6c59c0da
--- /dev/null
+++ b/releasenotes/notes/wallaby-datastore-version-image-tags.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - Support image tags for the datastore version. When using image tags, Trove
+ is able to get the image dynamically from Glance for creating instances. If
+ both are specified, image ID takes precedence over image tags.
diff --git a/trove/cmd/manage.py b/trove/cmd/manage.py
index eaf5d330..52a342ec 100644
--- a/trove/cmd/manage.py
+++ b/trove/cmd/manage.py
@@ -62,12 +62,13 @@ class Commands(object):
print(e)
def datastore_version_update(self, datastore, version_name, manager,
- image_id, packages, active):
+ image_id, image_tags, packages, active):
try:
datastore_models.update_datastore_version(datastore,
version_name,
manager,
image_id,
+ image_tags,
packages, active)
print("Datastore version '%s' updated." % version_name)
except exception.DatastoreNotFound as e:
@@ -208,8 +209,13 @@ def main():
'manager', help='Name of the manager that will administer the '
'datastore version.')
parser.add_argument(
- 'image_id', help='ID of the image used to create an instance of '
- 'the datastore version.')
+ 'image_id',
+ help='ID of the image used to create an instance of '
+ 'the datastore version.')
+ parser.add_argument(
+ 'image_tags',
+ help='List of image tags separated by comma used for getting '
+ 'guest image.')
parser.add_argument(
'packages', help='Packages required by the datastore version that '
'are installed on the guest image.')
diff --git a/trove/common/apischema.py b/trove/common/apischema.py
index 2956120a..9af0e19c 100644
--- a/trove/common/apischema.py
+++ b/trove/common/apischema.py
@@ -947,6 +947,19 @@ package_list = {
}
}
+image_tags = {
+ "type": "array",
+ "minItems": 0,
+ "maxItems": 5,
+ "uniqueItems": True,
+ "items": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 20,
+ "pattern": "^.*[0-9a-zA-Z]+.*$"
+ }
+}
+
mgmt_datastore_version = {
"create": {
"name": "mgmt_datastore_version:create",
@@ -955,14 +968,16 @@ mgmt_datastore_version = {
"properties": {
"version": {
"type": "object",
- "required": ["name", "datastore_name", "image", "active"],
- "additionalProperties": True,
+ "required": ["name", "datastore_name", "datastore_manager",
+ "active"],
+ "additionalProperties": False,
"properties": {
"name": non_empty_string,
"datastore_name": non_empty_string,
"datastore_manager": non_empty_string,
"packages": package_list,
"image": uuid,
+ "image_tags": image_tags,
"active": {"enum": [True, False]},
"default": {"enum": [True, False]}
}
@@ -973,11 +988,12 @@ mgmt_datastore_version = {
"name": "mgmt_datastore_version:edit",
"type": "object",
"required": [],
- "additionalProperties": True,
+ "additionalProperties": False,
"properties": {
"datastore_manager": non_empty_string,
"packages": package_list,
"image": uuid,
+ "image_tags": image_tags,
"active": {"enum": [True, False]},
"default": {"enum": [True, False]},
}
diff --git a/trove/common/exception.py b/trove/common/exception.py
index 6787a204..40d22463 100644
--- a/trove/common/exception.py
+++ b/trove/common/exception.py
@@ -699,6 +699,11 @@ class ImageNotFound(NotFound):
message = _("Image %(uuid)s cannot be found.")
+class ImageNotFoundByTags(NotFound):
+
+ message = _("Failed to retrieve image with tags: %(tags)s.")
+
+
class LogAccessForbidden(Forbidden):
message = _("You must be admin to %(action)s log '%(log)s'.")
diff --git a/trove/common/glance.py b/trove/common/glance.py
new file mode 100644
index 00000000..7461ae90
--- /dev/null
+++ b/trove/common/glance.py
@@ -0,0 +1,36 @@
+# Copyright 2020 Catalyst Cloud
+#
+# 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 glanceclient import exc as glance_exceptions
+
+from trove.common import exception
+
+
+def get_image_id(client, image_id, image_tags):
+ """Get and check image ID."""
+ if image_id:
+ try:
+ client.images.get(image_id)
+ except glance_exceptions.HTTPNotFound:
+ raise exception.ImageNotFound(uuid=image_id)
+ return image_id
+
+ elif image_tags:
+ filters = {'tag': image_tags, 'status': 'active'}
+ images = list(client.images.list(
+ filters=filters, sort='created_at:desc', limit=1))
+ if not images:
+ raise exception.ImageNotFoundByTags(tags=image_tags)
+ image_id = images[0]['id']
+
+ return image_id
diff --git a/trove/datastore/models.py b/trove/datastore/models.py
index 5a53a9f1..bf989133 100644
--- a/trove/datastore/models.py
+++ b/trove/datastore/models.py
@@ -62,9 +62,8 @@ class DBCapabilityOverrides(dbmodels.DatabaseModelBase):
class DBDatastoreVersion(dbmodels.DatabaseModelBase):
-
- _data_fields = ['datastore_id', 'name', 'image_id', 'packages',
- 'active', 'manager']
+ _data_fields = ['datastore_id', 'name', 'image_id', 'image_tags',
+ 'packages', 'active', 'manager']
_table_name = 'datastore_versions'
@@ -448,6 +447,10 @@ class DatastoreVersion(object):
return self.db_info.image_id
@property
+ def image_tags(self):
+ return self.db_info.image_tags
+
+ @property
def packages(self):
return self.db_info.packages
@@ -577,8 +580,8 @@ def update_datastore(name, default_version):
db_api.save(datastore)
-def update_datastore_version(datastore, name, manager, image_id, packages,
- active):
+def update_datastore_version(datastore, name, manager, image_id, image_tags,
+ packages, active):
db_api.configure_db(CONF)
datastore = Datastore.load(datastore)
try:
@@ -592,6 +595,8 @@ def update_datastore_version(datastore, name, manager, image_id, packages,
version.datastore_id = datastore.id
version.manager = manager
version.image_id = image_id
+ version.image_tags = (",".join(image_tags)
+ if type(image_tags) is list else image_tags)
version.packages = packages
version.active = active
diff --git a/trove/datastore/views.py b/trove/datastore/views.py
index 6b4ebb6c..3dfb321a 100644
--- a/trove/datastore/views.py
+++ b/trove/datastore/views.py
@@ -90,6 +90,11 @@ class DatastoreVersionView(object):
datastore_version_dict['packages'] = (self.datastore_version.
packages)
datastore_version_dict['image'] = self.datastore_version.image_id
+
+ image_tags = []
+ if self.datastore_version.image_tags:
+ image_tags = self.datastore_version.image_tags.split(',')
+ datastore_version_dict['image_tags'] = image_tags
return {"version": datastore_version_dict}
def _build_links(self):
diff --git a/trove/db/sqlalchemy/migrate_repo/versions/047_image_tag_in_datastore_version.py b/trove/db/sqlalchemy/migrate_repo/versions/047_image_tag_in_datastore_version.py
new file mode 100644
index 00000000..91873299
--- /dev/null
+++ b/trove/db/sqlalchemy/migrate_repo/versions/047_image_tag_in_datastore_version.py
@@ -0,0 +1,29 @@
+# Copyright 2020 Catalyst Cloud
+# All Rights Reserved.
+#
+# 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 sqlalchemy.schema import Column
+from sqlalchemy.schema import MetaData
+
+from trove.db.sqlalchemy.migrate_repo.schema import Table
+from trove.db.sqlalchemy.migrate_repo.schema import String
+
+
+def upgrade(migrate_engine):
+ meta = MetaData()
+ meta.bind = migrate_engine
+
+ ds_version = Table('datastore_versions', meta, autoload=True)
+ ds_version.create_column(Column('image_tags', String(255), nullable=True))
+ ds_version.c.image_id.alter(nullable=True)
diff --git a/trove/extensions/mgmt/datastores/service.py b/trove/extensions/mgmt/datastores/service.py
index c8d350f1..fb730c9e 100644
--- a/trove/extensions/mgmt/datastores/service.py
+++ b/trove/extensions/mgmt/datastores/service.py
@@ -12,17 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-
-from glanceclient import exc as glance_exceptions
from oslo_log import log as logging
from trove.backup import models as backup_model
from trove.common import apischema
-from trove.common.auth import admin_context
from trove.common import clients
from trove.common import exception
+from trove.common import glance as common_glance
from trove.common import utils
from trove.common import wsgi
+from trove.common.auth import admin_context
from trove.configuration import models as config_model
from trove.datastore import models
from trove.extensions.mgmt.datastores import views
@@ -43,23 +42,24 @@ class DatastoreVersionController(wsgi.Controller):
datastore_name = body['version']['datastore_name']
version_name = body['version']['name']
manager = body['version']['datastore_manager']
- image_id = body['version']['image']
- packages = body['version']['packages']
+ image_id = body['version'].get('image')
+ image_tags = body['version'].get('image_tags')
+ packages = body['version'].get('packages')
if type(packages) is list:
packages = ','.join(packages)
active = body['version']['active']
- default = body['version']['default']
+ default = body['version'].get('default', False)
LOG.info("Tenant: '%(tenant)s' is adding the datastore "
"version: '%(version)s' to datastore: '%(datastore)s'",
{'tenant': tenant_id, 'version': version_name,
'datastore': datastore_name})
+ if not image_id and not image_tags:
+ raise exception.BadRequest("Image must be specified.")
+
client = clients.create_glance_client(context)
- try:
- client.images.get(image_id)
- except glance_exceptions.HTTPNotFound:
- raise exception.ImageNotFound(uuid=image_id)
+ common_glance.get_image_id(client, image_id, image_tags)
try:
datastore = models.Datastore.load(datastore_name)
@@ -76,8 +76,8 @@ class DatastoreVersionController(wsgi.Controller):
raise exception.DatastoreVersionAlreadyExists(name=version_name)
except exception.DatastoreVersionNotFound:
models.update_datastore_version(datastore.name, version_name,
- manager, image_id, packages,
- active)
+ manager, image_id, image_tags,
+ packages, active)
if default:
models.update_datastore(datastore.name, version_name)
@@ -114,23 +114,36 @@ class DatastoreVersionController(wsgi.Controller):
'datastore': datastore_version.datastore_name})
manager = body.get('datastore_manager', datastore_version.manager)
- image_id = body.get('image', datastore_version.image_id)
+ image_id = body.get('image')
+ image_tags = body.get('image_tags')
active = body.get('active', datastore_version.active)
default = body.get('default', None)
packages = body.get('packages', datastore_version.packages)
if type(packages) is list:
packages = ','.join(packages)
- client = clients.create_glance_client(context)
- try:
- client.images.get(image_id)
- except glance_exceptions.HTTPNotFound:
- raise exception.ImageNotFound(uuid=image_id)
+ if image_id or image_tags:
+ client = clients.create_glance_client(context)
+ common_glance.get_image_id(client, image_id, image_tags)
+
+ if not image_id and image_tags:
+ # Remove the image ID from the datastore version.
+ image_id = ""
+
+ if image_id is None:
+ image_id = datastore_version.image_id
+ if image_tags is None:
+ image_tags = datastore_version.image_tags
+ if type(image_tags) is str:
+ image_tags = image_tags.split(',')
+
+ if not image_id and not image_tags:
+ raise exception.BadRequest("Image must be specified.")
models.update_datastore_version(datastore_version.datastore_name,
datastore_version.name,
- manager, image_id, packages,
- active)
+ manager, image_id, image_tags,
+ packages, active)
if default:
models.update_datastore(datastore_version.datastore_name,
diff --git a/trove/extensions/mgmt/datastores/views.py b/trove/extensions/mgmt/datastores/views.py
index e3627660..971bbc0d 100644
--- a/trove/extensions/mgmt/datastores/views.py
+++ b/trove/extensions/mgmt/datastores/views.py
@@ -26,6 +26,8 @@ class DatastoreVersionView(object):
"datastore_name": self.datastore_version.datastore_name,
"datastore_manager": self.datastore_version.manager,
"image": self.datastore_version.image_id,
+ "image_tags": (self.datastore_version.image_tags.split(',')
+ if self.datastore_version.image_tags else ['']),
"packages": (self.datastore_version.packages.split(
',') if self.datastore_version.packages else ['']),
"active": self.datastore_version.active,
diff --git a/trove/tests/api/datastores.py b/trove/tests/api/datastores.py
index ef8b00b7..fa54620b 100644
--- a/trove/tests/api/datastores.py
+++ b/trove/tests/api/datastores.py
@@ -92,18 +92,12 @@ class Datastores(object):
if version['name'] == 'inactive_version':
return
- # Get a valid image ID from a datastore version
- datastore = self.rd_client.datastores.get(test_config.dbaas_datastore)
- ds_version = self.rd_client.datastore_versions.list(datastore.id)[0]
- ds_version_info = self.rd_admin.datastore_versions.get_by_uuid(
- ds_version.id)
-
# Create datastore version for testing
# 'Test_Datastore_1' is also used in other test cases.
# Will be deleted in test_delete_datastore_version
self.rd_admin.mgmt_datastore_versions.create(
"inactive_version", test_config.dbaas_datastore_name_no_versions,
- "test_manager", ds_version_info.image,
+ "test_manager", None, image_tags=['trove'],
active='false', default='false'
)
diff --git a/trove/tests/unittests/datastore/base.py b/trove/tests/unittests/datastore/base.py
index 4230f122..bf38c09a 100644
--- a/trove/tests/unittests/datastore/base.py
+++ b/trove/tests/unittests/datastore/base.py
@@ -11,7 +11,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 uuid
from trove.datastore import models as datastore_models
from trove.datastore.models import Capability
@@ -24,63 +23,66 @@ from trove.tests.unittests.util import util
class TestDatastoreBase(trove_testtools.TestCase):
-
- def setUp(self):
- # Basic setup and mock/fake structures for testing only
- super(TestDatastoreBase, self).setUp()
+ @classmethod
+ def setUpClass(cls):
util.init_db()
- self.rand_id = str(uuid.uuid4())
- self.ds_name = "my-test-datastore" + self.rand_id
- self.ds_version = "my-test-version" + self.rand_id
- self.capability_name = "root_on_create" + self.rand_id
- self.capability_desc = "Enables root on create"
- self.capability_enabled = True
- self.datastore_version_id = str(uuid.uuid4())
- self.flavor_id = 1
- self.volume_type = 'some-valid-volume-type'
- datastore_models.update_datastore(self.ds_name, False)
- self.datastore = Datastore.load(self.ds_name)
+ cls.ds_name = cls.random_name(name='test-datastore')
+ cls.ds_version_name = cls.random_name(name='test-version')
+ cls.capability_name = cls.random_name(name='root_on_create',
+ prefix='TestDatastoreBase')
+ cls.capability_desc = "Enables root on create"
+ cls.capability_enabled = True
+ cls.flavor_id = 1
+ cls.volume_type = 'some-valid-volume-type'
+
+ datastore_models.update_datastore(cls.ds_name, False)
+ cls.datastore = Datastore.load(cls.ds_name)
datastore_models.update_datastore_version(
- self.ds_name, self.ds_version, "mysql", "", "", True)
+ cls.ds_name, cls.ds_version_name, "mysql", "", "", "", True)
DatastoreVersionMetadata.add_datastore_version_flavor_association(
- self.ds_name, self.ds_version, [self.flavor_id])
+ cls.ds_name, cls.ds_version_name, [cls.flavor_id])
DatastoreVersionMetadata.add_datastore_version_volume_type_association(
- self.ds_name, self.ds_version, [self.volume_type])
+ cls.ds_name, cls.ds_version_name, [cls.volume_type])
- self.datastore_version = DatastoreVersion.load(self.datastore,
- self.ds_version)
- self.test_id = self.datastore_version.id
+ cls.datastore_version = DatastoreVersion.load(cls.datastore,
+ cls.ds_version_name)
+ cls.test_id = cls.datastore_version.id
- self.cap1 = Capability.create(self.capability_name,
- self.capability_desc, True)
- self.cap2 = Capability.create("require_volume" + self.rand_id,
- "Require external volume", True)
- self.cap3 = Capability.create("test_capability" + self.rand_id,
- "Test capability", False)
+ cls.cap1 = Capability.create(cls.capability_name,
+ cls.capability_desc, True)
+ cls.cap2 = Capability.create(
+ cls.random_name(name='require_volume', prefix='TestDatastoreBase'),
+ "Require external volume", True)
+ cls.cap3 = Capability.create(
+ cls.random_name(name='test_capability',
+ prefix='TestDatastoreBase'),
+ "Test capability", False)
- def tearDown(self):
- super(TestDatastoreBase, self).tearDown()
- capabilities_overridden = DBCapabilityOverrides.find_all(
- datastore_version_id=self.datastore_version.id).all()
+ super(TestDatastoreBase, cls).setUpClass()
+ @classmethod
+ def tearDownClass(cls):
+ capabilities_overridden = DBCapabilityOverrides.find_all(
+ datastore_version_id=cls.test_id).all()
for ce in capabilities_overridden:
ce.delete()
- self.cap1.delete()
- self.cap2.delete()
- self.cap3.delete()
- datastore = datastore_models.Datastore.load(self.ds_name)
- ds_version = datastore_models.DatastoreVersion.load(datastore,
- self.ds_version)
+ cls.cap1.delete()
+ cls.cap2.delete()
+ cls.cap3.delete()
+
datastore_models.DBDatastoreVersionMetadata.find_by(
- datastore_version_id=ds_version.id).delete()
- Datastore.load(self.ds_name).delete()
+ datastore_version_id=cls.test_id).delete()
+ cls.datastore_version.delete()
+ cls.datastore.delete()
+
+ super(TestDatastoreBase, cls).tearDownClass()
def capability_name_filter(self, capabilities):
new_capabilities = []
for capability in capabilities:
- if self.rand_id in capability.name:
+ if 'TestDatastoreBase' in capability.name:
new_capabilities.append(capability)
return new_capabilities
diff --git a/trove/tests/unittests/datastore/test_capability.py b/trove/tests/unittests/datastore/test_capability.py
index 194d683a..42a51e4b 100644
--- a/trove/tests/unittests/datastore/test_capability.py
+++ b/trove/tests/unittests/datastore/test_capability.py
@@ -19,12 +19,6 @@ from trove.tests.unittests.datastore.base import TestDatastoreBase
class TestCapabilities(TestDatastoreBase):
- def setUp(self):
- super(TestCapabilities, self).setUp()
-
- def tearDown(self):
- super(TestCapabilities, self).tearDown()
-
def test_capability(self):
cap = Capability.load(self.capability_name)
self.assertEqual(self.capability_name, cap.name)
@@ -38,9 +32,6 @@ class TestCapabilities(TestDatastoreBase):
self.ds_cap.delete()
- def test_capability_enabled(self):
- self.assertTrue(Capability.load(self.capability_name).enabled)
-
def test_capability_disabled(self):
capability = Capability.load(self.capability_name)
capability.disable()
diff --git a/trove/tests/unittests/datastore/test_datastore_version_metadata.py b/trove/tests/unittests/datastore/test_datastore_version_metadata.py
index ad40d63e..2574ef10 100644
--- a/trove/tests/unittests/datastore/test_datastore_version_metadata.py
+++ b/trove/tests/unittests/datastore/test_datastore_version_metadata.py
@@ -35,8 +35,8 @@ class TestDatastoreVersionMetadata(TestDatastoreBase):
def test_map_flavors_to_datastore(self):
datastore = datastore_models.Datastore.load(self.ds_name)
- ds_version = datastore_models.DatastoreVersion.load(datastore,
- self.ds_version)
+ ds_version = datastore_models.DatastoreVersion.load(
+ datastore, self.ds_version_name)
mapping = datastore_models.DBDatastoreVersionMetadata.find_by(
datastore_version_id=ds_version.id,
value=self.flavor_id, deleted=False, key='flavor')
@@ -46,8 +46,8 @@ class TestDatastoreVersionMetadata(TestDatastoreBase):
def test_map_volume_types_to_datastores(self):
datastore = datastore_models.Datastore.load(self.ds_name)
- ds_version = datastore_models.DatastoreVersion.load(datastore,
- self.ds_version)
+ ds_version = datastore_models.DatastoreVersion.load(
+ datastore, self.ds_version_name)
mapping = datastore_models.DBDatastoreVersionMetadata.find_by(
datastore_version_id=ds_version.id,
value=self.volume_type, deleted=False, key='volume_type')
@@ -60,82 +60,86 @@ class TestDatastoreVersionMetadata(TestDatastoreBase):
self.assertRaisesRegex(
exception.DatastoreFlavorAssociationAlreadyExists,
"Flavor %s is already associated with datastore %s version %s"
- % (self.flavor_id, self.ds_name, self.ds_version),
+ % (self.flavor_id, self.ds_name, self.ds_version_name),
dsmetadata.add_datastore_version_flavor_association,
- self.ds_name, self.ds_version, [self.flavor_id])
+ self.ds_name, self.ds_version_name, [self.flavor_id])
def test_add_existing_volume_type_associations(self):
dsmetadata = datastore_models.DatastoreVersionMetadata
self.assertRaises(
exception.DatastoreVolumeTypeAssociationAlreadyExists,
dsmetadata.add_datastore_version_volume_type_association,
- self.ds_name, self.ds_version, [self.volume_type])
+ self.ds_name, self.ds_version_name, [self.volume_type])
def test_delete_nonexistent_flavor_mapping(self):
dsmeta = datastore_models.DatastoreVersionMetadata
self.assertRaisesRegex(
exception.DatastoreFlavorAssociationNotFound,
"Flavor 2 is not supported for datastore %s version %s"
- % (self.ds_name, self.ds_version),
+ % (self.ds_name, self.ds_version_name),
dsmeta.delete_datastore_version_flavor_association,
- self.ds_name, self.ds_version, flavor_id=2)
+ self.ds_name, self.ds_version_name, flavor_id=2)
def test_delete_nonexistent_volume_type_mapping(self):
dsmeta = datastore_models.DatastoreVersionMetadata
self.assertRaises(
exception.DatastoreVolumeTypeAssociationNotFound,
dsmeta.delete_datastore_version_volume_type_association,
- self.ds_name, self.ds_version,
+ self.ds_name, self.ds_version_name,
volume_type_name='some random thing')
def test_delete_flavor_mapping(self):
flavor_id = 2
- dsmetadata = datastore_models. DatastoreVersionMetadata
- dsmetadata.add_datastore_version_flavor_association(self.ds_name,
- self.ds_version,
- [flavor_id])
- dsmetadata.delete_datastore_version_flavor_association(self.ds_name,
- self.ds_version,
- flavor_id)
+ dsmetadata = datastore_models.DatastoreVersionMetadata
+ dsmetadata.add_datastore_version_flavor_association(
+ self.ds_name,
+ self.ds_version_name,
+ [flavor_id])
+ dsmetadata.delete_datastore_version_flavor_association(
+ self.ds_name,
+ self.ds_version_name,
+ flavor_id)
datastore = datastore_models.Datastore.load(self.ds_name)
- ds_version = datastore_models.DatastoreVersion.load(datastore,
- self.ds_version)
+ ds_version = datastore_models.DatastoreVersion.load(
+ datastore,
+ self.ds_version_name)
mapping = datastore_models.DBDatastoreVersionMetadata.find_by(
datastore_version_id=ds_version.id, value=flavor_id, key='flavor')
self.assertTrue(mapping.deleted)
# check update
dsmetadata.add_datastore_version_flavor_association(
- self.ds_name, self.ds_version, [flavor_id])
+ self.ds_name, self.ds_version_name, [flavor_id])
mapping = datastore_models.DBDatastoreVersionMetadata.find_by(
datastore_version_id=ds_version.id, value=flavor_id, key='flavor')
self.assertFalse(mapping.deleted)
# clear the mapping
- datastore_models.DatastoreVersionMetadata.\
+ datastore_models.DatastoreVersionMetadata. \
delete_datastore_version_flavor_association(self.ds_name,
- self.ds_version,
+ self.ds_version_name,
flavor_id)
def test_delete_volume_type_mapping(self):
volume_type = 'this is bogus'
- dsmetadata = datastore_models. DatastoreVersionMetadata
+ dsmetadata = datastore_models.DatastoreVersionMetadata
dsmetadata.add_datastore_version_volume_type_association(
self.ds_name,
- self.ds_version,
+ self.ds_version_name,
[volume_type])
dsmetadata.delete_datastore_version_volume_type_association(
self.ds_name,
- self.ds_version,
+ self.ds_version_name,
volume_type)
datastore = datastore_models.Datastore.load(self.ds_name)
- ds_version = datastore_models.DatastoreVersion.load(datastore,
- self.ds_version)
+ ds_version = datastore_models.DatastoreVersion.load(
+ datastore,
+ self.ds_version_name)
mapping = datastore_models.DBDatastoreVersionMetadata.find_by(
datastore_version_id=ds_version.id, value=volume_type,
key='volume_type')
self.assertTrue(mapping.deleted)
# check update
dsmetadata.add_datastore_version_volume_type_association(
- self.ds_name, self.ds_version, [volume_type])
+ self.ds_name, self.ds_version_name, [volume_type])
mapping = datastore_models.DBDatastoreVersionMetadata.find_by(
datastore_version_id=ds_version.id, value=volume_type,
key='volume_type')
@@ -143,7 +147,7 @@ class TestDatastoreVersionMetadata(TestDatastoreBase):
# clear the mapping
dsmetadata.delete_datastore_version_volume_type_association(
self.ds_name,
- self.ds_version,
+ self.ds_version_name,
volume_type)
@mock.patch.object(datastore_models.DatastoreVersionMetadata,
diff --git a/trove/tests/unittests/datastore/test_datastore_versions.py b/trove/tests/unittests/datastore/test_datastore_versions.py
index b24222e8..06729fb9 100644
--- a/trove/tests/unittests/datastore/test_datastore_versions.py
+++ b/trove/tests/unittests/datastore/test_datastore_versions.py
@@ -20,8 +20,8 @@ class TestDatastoreVersions(TestDatastoreBase):
def test_load_datastore_version(self):
datastore_version = DatastoreVersion.load(self.datastore,
- self.ds_version)
- self.assertEqual(self.ds_version, datastore_version.name)
+ self.ds_version_name)
+ self.assertEqual(self.ds_version_name, datastore_version.name)
def test_datastore_version_capabilities(self):
self.datastore_version.capabilities.add(self.cap1, enabled=False)
@@ -35,7 +35,7 @@ class TestDatastoreVersions(TestDatastoreBase):
# Test a fresh reloading of the datastore
self.datastore_version = DatastoreVersion.load(self.datastore,
- self.ds_version)
+ self.ds_version_name)
test_filtered_capabilities = self.capability_name_filter(
self.datastore_version.capabilities)
self.assertEqual(3, len(test_filtered_capabilities),
diff --git a/trove/tests/unittests/mgmt/__init__.py b/trove/tests/unittests/extensions/mgmt/__init__.py
index e69de29b..e69de29b 100644
--- a/trove/tests/unittests/mgmt/__init__.py
+++ b/trove/tests/unittests/extensions/mgmt/__init__.py
diff --git a/trove/tests/unittests/extensions/mgmt/datastores/__init__.py b/trove/tests/unittests/extensions/mgmt/datastores/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/trove/tests/unittests/extensions/mgmt/datastores/__init__.py
diff --git a/trove/tests/unittests/extensions/mgmt/datastores/test_service.py b/trove/tests/unittests/extensions/mgmt/datastores/test_service.py
new file mode 100644
index 00000000..be1d49fc
--- /dev/null
+++ b/trove/tests/unittests/extensions/mgmt/datastores/test_service.py
@@ -0,0 +1,324 @@
+# Copyright [2015] Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from unittest import mock
+from unittest.mock import MagicMock
+from unittest.mock import Mock
+from unittest.mock import patch
+
+from glanceclient import exc as glance_exceptions
+import jsonschema
+
+from trove.common import clients
+from trove.common import exception
+from trove.datastore import models
+from trove.extensions.mgmt.datastores.service import DatastoreVersionController
+from trove.tests.unittests import trove_testtools
+from trove.tests.unittests.util import util
+
+
+class TestDatastoreVersionController(trove_testtools.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ util.init_db()
+ cls.ds_name = cls.random_name('datastore')
+ models.update_datastore(name=cls.ds_name, default_version=None)
+
+ models.update_datastore_version(
+ cls.ds_name, 'test_vr1', 'mysql', cls.random_uuid(), '', 'pkg-1',
+ 1)
+ models.update_datastore_version(
+ cls.ds_name, 'test_vr2', 'mysql', cls.random_uuid(), '', 'pkg-1',
+ 1)
+
+ cls.ds = models.Datastore.load(cls.ds_name)
+ cls.ds_version1 = models.DatastoreVersion.load(cls.ds, 'test_vr1')
+ cls.ds_version2 = models.DatastoreVersion.load(cls.ds, 'test_vr2')
+ cls.version_controller = DatastoreVersionController()
+
+ super(TestDatastoreVersionController, cls).setUpClass()
+
+ @classmethod
+ def tearDownClass(cls):
+ versions = models.DatastoreVersions.load_all(only_active=False)
+ for ver in versions:
+ ver.delete()
+
+ cls.ds.delete()
+
+ super(TestDatastoreVersionController, cls).tearDownClass()
+
+ def test_create_schema(self):
+ image_id = self.random_uuid()
+ ver_name = self.random_name('dsversion')
+ body = {
+ "version": {
+ "datastore_name": self.ds_name,
+ "name": ver_name,
+ "datastore_manager": "mysql",
+ "image": image_id,
+ "image_tags": [],
+ "active": True,
+ "default": False
+ }
+ }
+
+ schema = self.version_controller.get_schema('create', body)
+ validator = jsonschema.Draft4Validator(schema)
+
+ self.assertTrue(validator.is_valid(body))
+
+ def test_create_schema_too_many_image_tags(self):
+ ver_name = self.random_name('dsversion')
+ body = {
+ "version": {
+ "datastore_name": self.ds_name,
+ "name": ver_name,
+ "datastore_manager": "mysql",
+ "image_tags": ['a', 'b', 'c', 'd', 'e', 'f'],
+ "active": True,
+ "default": False
+ }
+ }
+
+ schema = self.version_controller.get_schema('create', body)
+ validator = jsonschema.Draft4Validator(schema)
+
+ self.assertFalse(validator.is_valid(body))
+
+ def test_create_schema_emptyname(self):
+ image_id = self.random_uuid()
+ body = {
+ "version": {
+ "datastore_name": self.ds_name,
+ "name": " ",
+ "datastore_manager": "mysql",
+ "image": image_id,
+ "image_tags": [],
+ "active": True,
+ "default": False
+ }
+ }
+ schema = self.version_controller.get_schema('create', body)
+ validator = jsonschema.Draft4Validator(schema)
+
+ self.assertFalse(validator.is_valid(body))
+
+ errors = sorted(validator.iter_errors(body), key=lambda e: e.path)
+ self.assertEqual(1, len(errors))
+ self.assertEqual("' ' does not match '^.*[0-9a-zA-Z]+.*$'",
+ errors[0].message)
+
+ @patch.object(clients, 'create_glance_client')
+ def test_create(self, mock_glance_client):
+ image_id = self.random_uuid()
+ ver_name = self.random_name('dsversion')
+ body = {
+ "version": {
+ "datastore_name": self.ds_name,
+ "name": ver_name,
+ "datastore_manager": "mysql",
+ "image": image_id,
+ "image_tags": [],
+ "packages": "test-pkg",
+ "active": True,
+ "default": True
+ }
+ }
+ output = self.version_controller.create(MagicMock(), body, mock.ANY)
+ self.assertEqual(202, output.status)
+
+ new_ver = models.DatastoreVersion.load(self.ds, ver_name)
+ self.assertEqual(image_id, new_ver.image_id)
+
+ @patch.object(clients, 'create_glance_client')
+ def test_create_by_image_tags(self, mock_create_client):
+ ver_name = self.random_name('dsversion')
+ body = {
+ "version": {
+ "datastore_name": self.ds_name,
+ "name": ver_name,
+ "datastore_manager": "mysql",
+ "image_tags": ["trove", "mysql"],
+ "active": True,
+ "default": True
+ }
+ }
+ mock_client = MagicMock()
+ mock_client.images.list.return_value = [{"id": self.random_uuid()}]
+ mock_create_client.return_value = mock_client
+
+ output = self.version_controller.create(MagicMock(), body, mock.ANY)
+ self.assertEqual(202, output.status)
+
+ mock_client.images.list.assert_called_once_with(
+ filters={'tag': ["trove", "mysql"], 'status': 'active'},
+ sort='created_at:desc',
+ limit=1
+ )
+
+ new_ver = models.DatastoreVersion.load(self.ds, ver_name)
+ self.assertIsNone(new_ver.image_id)
+ self.assertEqual('trove,mysql', new_ver.image_tags)
+
+ @patch.object(clients, 'create_glance_client')
+ def test_create_exist(self, mock_glance_client):
+ image_id = self.random_uuid()
+ ver_name = 'test_vr1'
+ body = {
+ "version": {
+ "datastore_name": self.ds_name,
+ "name": ver_name,
+ "datastore_manager": "mysql",
+ "image": image_id,
+ "image_tags": [],
+ "packages": "test-pkg",
+ "active": True,
+ "default": True
+ }
+ }
+ self.assertRaises(
+ exception.DatastoreVersionAlreadyExists,
+ self.version_controller.create, MagicMock(), body, mock.ANY)
+
+ def test_create_no_image(self):
+ ver_name = self.random_name('dsversion')
+ body = {
+ "version": {
+ "datastore_name": self.ds_name,
+ "name": ver_name,
+ "datastore_manager": "mysql",
+ "active": True,
+ "default": False
+ }
+ }
+ self.assertRaises(
+ exception.BadRequest,
+ self.version_controller.create, MagicMock(), body, mock.ANY)
+
+ @patch.object(clients, 'create_glance_client')
+ def test_create_image_notfound(self, mock_create_client):
+ image_id = self.random_uuid()
+ ver_name = self.random_name('dsversion')
+ body = {
+ "version": {
+ "datastore_name": self.ds_name,
+ "name": ver_name,
+ "datastore_manager": "mysql",
+ "image": image_id,
+ "active": True,
+ "default": False
+ }
+ }
+ mock_client = Mock()
+ mock_client.images.get.side_effect = [glance_exceptions.HTTPNotFound()]
+ mock_create_client.return_value = mock_client
+
+ self.assertRaises(
+ exception.ImageNotFound,
+ self.version_controller.create, MagicMock(), body, mock.ANY)
+
+ @patch.object(clients, 'create_glance_client')
+ def test_update_image(self, mock_create_client):
+ new_image = self.random_uuid()
+ body = {
+ "image": new_image
+ }
+
+ output = self.version_controller.edit(MagicMock(), body, mock.ANY,
+ self.ds_version1.id)
+ self.assertEqual(202, output.status)
+
+ updated_ver = models.DatastoreVersion.load(self.ds,
+ self.ds_version1.id)
+ self.assertEqual(new_image, updated_ver.image_id)
+
+ @patch.object(clients, 'create_glance_client')
+ def test_update_image_tags(self, mock_create_client):
+ name = self.random_name('dsversion')
+ models.update_datastore_version(
+ self.ds_name, name, 'mysql', self.random_uuid(), '', '', 1)
+ ver = models.DatastoreVersion.load(self.ds, name)
+
+ mock_client = MagicMock()
+ mock_client.images.list.return_value = [{"id": self.random_uuid()}]
+ mock_create_client.return_value = mock_client
+
+ body = {
+ "image_tags": ['trove', 'mysql']
+ }
+
+ output = self.version_controller.edit(MagicMock(), body, mock.ANY,
+ ver.id)
+ self.assertEqual(202, output.status)
+
+ updated_ver = models.DatastoreVersion.load(self.ds, ver.id)
+ self.assertEqual("", updated_ver.image_id)
+ self.assertEqual("trove,mysql", updated_ver.image_tags)
+
+ def test_delete(self):
+ name = self.random_name('dsversion')
+ models.update_datastore_version(
+ self.ds_name, name, 'mysql', self.random_uuid(), '', '', 1)
+ ver = models.DatastoreVersion.load(self.ds, name)
+
+ output = self.version_controller.delete(MagicMock(),
+ mock.ANY,
+ ver.id)
+ self.assertEqual(202, output.status)
+
+ self.assertRaises(
+ exception.DatastoreVersionNotFound,
+ models.DatastoreVersion.load_by_uuid, ver.id)
+
+ def test_index(self):
+ output = self.version_controller.index(MagicMock(), mock.ANY)
+ self.assertEqual(200, output.status)
+
+ data = output.data(None)
+ self.assertGreater(len(data['versions']), 0)
+
+ def test_show(self):
+ output = self.version_controller.show(
+ MagicMock(), mock.ANY, self.ds_version2.id)
+ self.assertEqual(200, output.status)
+ self.assertEqual(self.ds_version2.id,
+ output._data['version']['id'])
+ self.assertEqual(self.ds_version2.name,
+ output._data['version']['name'])
+ self.assertEqual(self.ds_version2.datastore_id,
+ output._data['version']['datastore_id'])
+ self.assertEqual(self.ds_version2.datastore_name,
+ output._data['version']['datastore_name'])
+ self.assertEqual(self.ds_version2.manager,
+ output._data['version']['datastore_manager'])
+ self.assertEqual(self.ds_version2.image_id,
+ output._data['version']['image'])
+ self.assertEqual(self.ds_version2.packages.split(','),
+ output._data['version']['packages'])
+ self.assertEqual(self.ds_version2.active,
+ output._data['version']['active'])
+
+ def test_show_image_tags(self):
+ ver_name = self.random_name('dsversion')
+ tags = ['trove', 'mysql']
+ models.update_datastore_version(self.ds_name, ver_name, 'mysql', '',
+ tags, '', 1)
+ ver = models.DatastoreVersion.load(self.ds, ver_name)
+
+ output = self.version_controller.show(
+ MagicMock(), mock.ANY, ver.id)
+ self.assertEqual(200, output.status)
+
+ data = output.data(None)
+ self.assertEqual(tags, data['version']['image_tags'])
diff --git a/trove/tests/unittests/extensions/mgmt/instances/__init__.py b/trove/tests/unittests/extensions/mgmt/instances/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/trove/tests/unittests/extensions/mgmt/instances/__init__.py
diff --git a/trove/tests/unittests/mgmt/test_models.py b/trove/tests/unittests/extensions/mgmt/instances/test_models.py
index 6c0883c2..6c0883c2 100644
--- a/trove/tests/unittests/mgmt/test_models.py
+++ b/trove/tests/unittests/extensions/mgmt/instances/test_models.py
diff --git a/trove/tests/unittests/mgmt/test_clusters.py b/trove/tests/unittests/mgmt/test_clusters.py
deleted file mode 100644
index fba3dc7b..00000000
--- a/trove/tests/unittests/mgmt/test_clusters.py
+++ /dev/null
@@ -1,99 +0,0 @@
-# Copyright [2015] Hewlett-Packard Development Company, L.P.
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from unittest.mock import Mock, patch
-
-from trove.common import exception
-from trove.extensions.mgmt.clusters.models import MgmtCluster
-from trove.extensions.mgmt.clusters.service import MgmtClusterController
-from trove.tests.unittests import trove_testtools
-
-
-class TestClusterController(trove_testtools.TestCase):
- def setUp(self):
- super(TestClusterController, self).setUp()
-
- self.context = trove_testtools.TroveTestContext(self)
- self.req = Mock()
- self.req.environ = Mock()
- self.req.environ.__getitem__ = Mock(return_value=self.context)
-
- mock_cluster1 = Mock()
- mock_cluster1.datastore_version.manager = 'vertica'
- mock_cluster1.instances = []
- mock_cluster1.instances_without_server = []
- mock_cluster2 = Mock()
- mock_cluster2.datastore_version.manager = 'vertica'
- mock_cluster2.instances = []
- mock_cluster2.instances_without_server = []
- self.mock_clusters = [mock_cluster1, mock_cluster2]
-
- self.controller = MgmtClusterController()
-
- def tearDown(self):
- super(TestClusterController, self).tearDown()
-
- def test_get_action_schema(self):
- body = {'do_stuff': {}}
- action_schema = Mock()
- action_schema.get = Mock()
-
- self.controller.get_action_schema(body, action_schema)
- action_schema.get.assert_called_with('do_stuff', {})
-
- @patch.object(MgmtCluster, 'load')
- def test_show_cluster(self, mock_cluster_load):
- tenant_id = Mock()
- id = Mock()
- mock_cluster_load.return_value = self.mock_clusters[0]
-
- self.controller.show(self.req, tenant_id, id)
- mock_cluster_load.assert_called_with(self.context, id)
-
- @patch.object(MgmtCluster, 'load_all')
- def test_index_cluster(self, mock_cluster_load_all):
- tenant_id = Mock()
- mock_cluster_load_all.return_value = self.mock_clusters
-
- self.controller.index(self.req, tenant_id)
- mock_cluster_load_all.assert_called_with(self.context, deleted=None)
-
- @patch.object(MgmtCluster, 'load')
- def test_controller_action_found(self, mock_cluster_load):
- body = {'reset-task': {}}
- tenant_id = Mock()
- id = Mock()
- mock_cluster_load.return_value = self.mock_clusters[0]
-
- result = self.controller.action(self.req, body, tenant_id, id)
- self.assertEqual(202, result.status)
- self.assertIsNotNone(result.data)
-
- def test_controller_no_body_action_found(self):
- tenant_id = Mock()
- id = Mock()
-
- self.assertRaisesRegex(
- exception.BadRequest, 'Invalid request body.',
- self.controller.action, self.req, None, tenant_id, id)
-
- @patch.object(MgmtCluster, 'load')
- def test_controller_invalid_action_found(self, mock_cluster_load):
- body = {'do_stuff': {}}
- tenant_id = Mock()
- id = Mock()
- mock_cluster_load.return_value = self.mock_clusters[0]
-
- self.assertRaisesRegex(
- exception.BadRequest, 'Invalid cluster action requested.',
- self.controller.action, self.req, body, tenant_id, id)
diff --git a/trove/tests/unittests/mgmt/test_datastore_controller.py b/trove/tests/unittests/mgmt/test_datastore_controller.py
deleted file mode 100644
index 7e5b01c4..00000000
--- a/trove/tests/unittests/mgmt/test_datastore_controller.py
+++ /dev/null
@@ -1,198 +0,0 @@
-# Copyright [2015] Hewlett-Packard Development Company, L.P.
-#
-# 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 jsonschema
-
-from unittest.mock import Mock, patch, MagicMock, PropertyMock
-from testtools.matchers import Is, Equals
-
-from trove.common import clients
-from trove.common import exception
-from trove.datastore import models as datastore_models
-from trove.extensions.mgmt.datastores.service import DatastoreVersionController
-from trove.tests.unittests import trove_testtools
-
-
-class TestDatastoreVersionController(trove_testtools.TestCase):
- def setUp(self):
- super(TestDatastoreVersionController, self).setUp()
- self.controller = DatastoreVersionController()
-
- self.version = {
- "version": {
- "datastore_name": "test_dsx",
- "name": "test_vr1",
- "datastore_manager": "mysql",
- "image": "154b350d-4d86-4214-9067-9c54b230c0da",
- "packages": ["mysql-server-5.7"],
- "active": True,
- "default": False
- }
- }
-
- self.tenant_id = Mock()
- context = trove_testtools.TroveTestContext(self)
- self.req = Mock()
- self.req.environ = Mock()
- self.req.environ.__getitem__ = Mock(return_value=context)
-
- def test_get_schema_create(self):
- schema = self.controller.get_schema('create', self.version)
- self.assertIsNotNone(schema)
- self.assertIn('version', schema['properties'])
-
- def test_validate_create(self):
- body = self.version
- schema = self.controller.get_schema('create', body)
- validator = jsonschema.Draft4Validator(schema)
- self.assertTrue(validator.is_valid(body))
-
- def test_validate_create_blankname(self):
- body = self.version
- body['version']['name'] = " "
- schema = self.controller.get_schema('create', body)
- validator = jsonschema.Draft4Validator(schema)
- self.assertFalse(validator.is_valid(body))
- errors = sorted(validator.iter_errors(body), key=lambda e: e.path)
- self.assertThat(len(errors), Is(1))
- self.assertThat(errors[0].message,
- Equals("' ' does not match '^.*[0-9a-zA-Z]+.*$'"))
-
- def test_validate_create_blank_datastore(self):
- body = self.version
- body['version']['datastore_name'] = ""
- schema = self.controller.get_schema('create', body)
- validator = jsonschema.Draft4Validator(schema)
- self.assertFalse(validator.is_valid(body))
- errors = sorted(validator.iter_errors(body), key=lambda e: e.path)
- error_messages = [error.message for error in errors]
- self.assertThat(len(errors), Is(2))
- self.assertIn("'' is too short", error_messages)
- self.assertIn("'' does not match '^.*[0-9a-zA-Z]+.*$'", error_messages)
-
- @patch.object(clients, 'create_glance_client')
- @patch.object(datastore_models.Datastore, 'load')
- @patch.object(datastore_models.DatastoreVersion, 'load',
- side_effect=exception.DatastoreVersionNotFound)
- @patch.object(datastore_models, 'update_datastore_version')
- def test_create_datastore_versions(self, mock_ds_version_create,
- mock_ds_version_load,
- mock_ds_load, mock_glance_client):
- body = self.version
- mock_ds_load.return_value.name = 'test_dsx'
-
- self.controller.create(self.req, body, self.tenant_id)
- mock_ds_version_create.assert_called_with(
- 'test_dsx', 'test_vr1', 'mysql',
- '154b350d-4d86-4214-9067-9c54b230c0da',
- 'mysql-server-5.7', True)
-
- @patch.object(datastore_models.DatastoreVersion, 'load_by_uuid')
- def test_show_ds_version(self, mock_ds_version_load):
- id = Mock()
-
- self.controller.show(self.req, self.tenant_id, id)
- mock_ds_version_load.assert_called_with(id)
-
- @patch('trove.configuration.models.DBConfiguration.find_all')
- @patch('trove.backup.models.DBBackup.find_all')
- @patch('trove.instance.models.DBInstance.find_all')
- @patch.object(datastore_models.Datastore, 'load')
- @patch.object(datastore_models.DatastoreVersion, 'load_by_uuid')
- def test_delete_ds_version(self, mock_ds_version_load, mock_ds_load,
- mock_instance_find, mock_backup_find,
- mock_config_find):
- ds_version_id = Mock()
- ds_version = Mock()
- mock_ds_version_load.return_value = ds_version
- self.controller.delete(self.req, self.tenant_id, ds_version_id)
- ds_version.delete.assert_called_with()
-
- @patch('trove.instance.models.DBInstance.find_all')
- def test_delete_ds_version_instance_in_use(self, mock_instance_find):
- mock_instance_find.return_value.all.return_value = [Mock()]
-
- self.assertRaises(
- exception.DatastoreVersionsInUse,
- self.controller.delete,
- self.req, self.tenant_id, 'fake_version_id'
- )
-
- @patch('trove.backup.models.DBBackup.find_all')
- @patch('trove.instance.models.DBInstance.find_all')
- def test_delete_ds_version_backup_in_use(self, mock_instance_find,
- mock_backup_find):
- mock_backup_find.return_value.all.return_value = [Mock()]
-
- self.assertRaises(
- exception.DatastoreVersionsInUse,
- self.controller.delete,
- self.req, self.tenant_id, 'fake_version_id'
- )
-
- @patch('trove.configuration.models.DBConfiguration.find_all')
- @patch('trove.backup.models.DBBackup.find_all')
- @patch('trove.instance.models.DBInstance.find_all')
- def test_delete_ds_version_config_in_use(self, mock_instance_find,
- mock_backup_find,
- mock_config_find):
- mock_config_find.return_value.all.return_value = [Mock()]
-
- self.assertRaises(
- exception.DatastoreVersionsInUse,
- self.controller.delete,
- self.req, self.tenant_id, 'fake_version_id'
- )
-
- @patch.object(datastore_models.DatastoreVersion, 'load_by_uuid')
- @patch.object(datastore_models.DatastoreVersions, 'load_all')
- def test_index_ds_version(self, mock_ds_version_load_all,
- mock_ds_version_load_by_uuid):
- mock_id = Mock()
- mock_ds_version = Mock()
- mock_ds_version.id = mock_id
- mock_ds_version_load_all.return_value = [mock_ds_version]
-
- self.controller.index(self.req, self.tenant_id)
- mock_ds_version_load_all.assert_called_with(only_active=False)
- mock_ds_version_load_by_uuid.assert_called_with(mock_id)
-
- @patch.object(clients, 'create_glance_client')
- @patch.object(datastore_models.DatastoreVersion, 'load_by_uuid')
- @patch.object(datastore_models, 'update_datastore_version')
- def test_edit_datastore_versions(self, mock_ds_version_update,
- mock_ds_version_load,
- mock_glance_client):
- body = {'image': '21c8805a-a800-4bca-a192-3a5a2519044d'}
-
- mock_ds_version = MagicMock()
- type(mock_ds_version).datastore_name = PropertyMock(
- return_value=self.version['version']['datastore_name'])
- type(mock_ds_version).name = PropertyMock(
- return_value=self.version['version']['name'])
- type(mock_ds_version).image_id = PropertyMock(
- return_value=self.version['version']['image'])
- type(mock_ds_version).packages = PropertyMock(
- return_value=self.version['version']['packages'])
- type(mock_ds_version).active = PropertyMock(
- return_value=self.version['version']['active'])
- type(mock_ds_version).manager = PropertyMock(
- return_value=self.version['version']['datastore_manager'])
- mock_ds_version_load.return_value = mock_ds_version
-
- self.controller.edit(self.req, body, self.tenant_id, Mock())
- mock_ds_version_update.assert_called_with(
- 'test_dsx', 'test_vr1', 'mysql',
- '21c8805a-a800-4bca-a192-3a5a2519044d',
- 'mysql-server-5.7', True)
diff --git a/trove/tests/unittests/mgmt/test_datastores.py b/trove/tests/unittests/mgmt/test_datastores.py
deleted file mode 100644
index c3b5ad72..00000000
--- a/trove/tests/unittests/mgmt/test_datastores.py
+++ /dev/null
@@ -1,165 +0,0 @@
-# Copyright [2015] Hewlett-Packard Development Company, L.P.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from unittest.mock import Mock, patch
-from glanceclient import exc as glance_exceptions
-
-from trove.common import clients
-from trove.common import exception
-from trove.datastore import models
-from trove.extensions.mgmt.datastores.service import DatastoreVersionController
-from trove.tests.unittests import trove_testtools
-from trove.tests.unittests.util import util
-
-
-class TestDatastoreVersion(trove_testtools.TestCase):
-
- def setUp(self):
- super(TestDatastoreVersion, self).setUp()
- util.init_db()
- models.update_datastore(name='test_ds', default_version=None)
- models.update_datastore_version(
- 'test_ds', 'test_vr1', 'mysql',
- '154b350d-4d86-4214-9067-9c54b230c0da', 'pkg-1', 1)
- models.update_datastore_version(
- 'test_ds', 'test_vr2', 'mysql',
- '154b350d-4d86-4214-9067-9c54b230c0da', 'pkg-1', 1)
- self.ds = models.Datastore.load('test_ds')
- self.ds_version2 = models.DatastoreVersion.load(self.ds, 'test_vr2')
-
- self.context = trove_testtools.TroveTestContext(self)
- self.req = Mock()
- self.req.environ = Mock()
- self.req.environ.__getitem__ = Mock(return_value=self.context)
- self.tenant_id = Mock()
- self.version_controller = DatastoreVersionController()
-
- def tearDown(self):
- super(TestDatastoreVersion, self).tearDown()
-
- @patch.object(clients, 'create_glance_client')
- def test_version_create(self, mock_glance_client):
- body = {"version": {
- "datastore_name": "test_ds",
- "name": "test_version",
- "datastore_manager": "mysql",
- "image": "image-id",
- "packages": "test-pkg",
- "active": True,
- "default": True}}
- output = self.version_controller.create(
- self.req, body, self.tenant_id)
- self.assertEqual(202, output.status)
-
- @patch.object(clients, 'create_glance_client')
- @patch.object(models.DatastoreVersion, 'load')
- def test_fail_already_exists_version_create(self, mock_load,
- mock_glance_client):
- body = {"version": {
- "datastore_name": "test_ds",
- "name": "test_new_vr",
- "datastore_manager": "mysql",
- "image": "image-id",
- "packages": "test-pkg",
- "active": True,
- "default": True}}
- self.assertRaisesRegex(
- exception.DatastoreVersionAlreadyExists,
- "A datastore version with the name 'test_new_vr' already exists",
- self.version_controller.create, self.req, body, self.tenant_id)
-
- @patch.object(clients, 'create_glance_client')
- def test_fail_image_not_found_version_create(self, mock_glance_client):
- mock_glance_client.return_value.images.get = Mock(
- side_effect=glance_exceptions.HTTPNotFound())
- body = {"version": {
- "datastore_name": "test_ds",
- "name": "test_vr",
- "datastore_manager": "mysql",
- "image": "image-id",
- "packages": "test-pkg",
- "active": True,
- "default": True}}
- self.assertRaisesRegex(
- exception.ImageNotFound,
- "Image image-id cannot be found.",
- self.version_controller.create, self.req, body, self.tenant_id)
-
- def test_version_delete(self):
- ds_version1 = models.DatastoreVersion.load(self.ds, 'test_vr1')
-
- output = self.version_controller.delete(self.req,
- self.tenant_id,
- ds_version1.id)
- err_msg = ("Datastore version '%s' cannot be found." %
- ds_version1.id)
-
- self.assertEqual(202, output.status)
-
- # Try to find deleted version, this should raise exception.
- self.assertRaisesRegex(
- exception.DatastoreVersionNotFound,
- err_msg, models.DatastoreVersion.load_by_uuid, ds_version1.id)
-
- @patch.object(clients, 'create_glance_client')
- def test_version_update(self, mock_client):
- body = {"image": "c022f4dc-76ed-4e3f-a25e-33e031f43f8b"}
- output = self.version_controller.edit(self.req, body,
- self.tenant_id,
- self.ds_version2.id)
- self.assertEqual(202, output.status)
-
- # Find the details of version updated and match the updated attribute.
- test_ds_version = models.DatastoreVersion.load_by_uuid(
- self.ds_version2.id)
- self.assertEqual(body['image'], test_ds_version.image_id)
-
- @patch.object(clients, 'create_glance_client')
- def test_version_update_fail_image_not_found(self, mock_glance_client):
- mock_glance_client.return_value.images.get = Mock(
- side_effect=glance_exceptions.HTTPNotFound())
- body = {"image": "non-existent-image-id"}
-
- self.assertRaisesRegex(
- exception.ImageNotFound,
- "Image non-existent-image-id cannot be found.",
- self.version_controller.edit, self.req, body,
- self.tenant_id, self.ds_version2.id)
-
- @patch.object(models.DatastoreVersion, 'load_by_uuid')
- def test_version_index(self, mock_load):
- output = self.version_controller.index(
- self.req, self.tenant_id)
- self.assertEqual(200, output.status)
-
- def test_version_show(self):
- output = self.version_controller.show(
- self.req, self.tenant_id, self.ds_version2.id)
- self.assertEqual(200, output.status)
- self.assertEqual(self.ds_version2.id,
- output._data['version']['id'])
- self.assertEqual(self.ds_version2.name,
- output._data['version']['name'])
- self.assertEqual(self.ds_version2.datastore_id,
- output._data['version']['datastore_id'])
- self.assertEqual(self.ds_version2.datastore_name,
- output._data['version']['datastore_name'])
- self.assertEqual(self.ds_version2.manager,
- output._data['version']['datastore_manager'])
- self.assertEqual(self.ds_version2.image_id,
- output._data['version']['image'])
- self.assertEqual(self.ds_version2.packages.split(','),
- output._data['version']['packages'])
- self.assertEqual(self.ds_version2.active,
- output._data['version']['active'])
diff --git a/trove/tests/unittests/trove_testtools.py b/trove/tests/unittests/trove_testtools.py
index 0366d821..57ada51a 100644
--- a/trove/tests/unittests/trove_testtools.py
+++ b/trove/tests/unittests/trove_testtools.py
@@ -14,8 +14,10 @@
# under the License.
import abc
+import random
import testtools
from unittest import mock
+import uuid
from trove.common import cfg
from trove.common.context import TroveContext
@@ -96,3 +98,27 @@ class TestCase(testtools.TestCase):
new_callable=mock.PropertyMock(return_value=value))
self.addCleanup(conf_patcher.stop)
return conf_patcher.start()
+
+ @classmethod
+ def random_name(cls, name='', prefix=None):
+ """Generate a random name that inclues a random number.
+
+ :param str name: The name that you want to include
+ :param str prefix: The prefix that you want to include
+
+ :return: a random name. The format is
+ '<prefix>-<name>-<random number>'.
+ (e.g. 'prefixfoo-namebar-154876201')
+ :rtype: string
+ """
+ randbits = str(random.randint(1, 0x7fffffff))
+ rand_name = randbits
+ if name:
+ rand_name = name + '-' + rand_name
+ if prefix:
+ rand_name = prefix + '-' + rand_name
+ return rand_name
+
+ @classmethod
+ def random_uuid(cls):
+ return str(uuid.uuid4())