Image tags support in datastore version
Change-Id: I0e51d08515c121e3a7b0e82b6e4c4161bb4fbc4a
This commit is contained in:
		| @@ -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 | ||||
| --------------- | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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. | ||||
|  | ||||
| The Database service provides resource isolation at high performance | ||||
| levels, and automates complex administrative tasks such as deployment, | ||||
| configuration, patching, backups, restores, and monitoring. | ||||
| Admin user needs to create datastore and its versions as required. | ||||
|  | ||||
| Create datastore | ||||
| ~~~~~~~~~~~~~~~~ | ||||
| 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. | ||||
|  | ||||
| An administrative user can create datastores for a variety of databases. | ||||
| Create datastore version | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| 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 creating a datastore version, Trove will create the datastore first if it | ||||
| doesn't exist. | ||||
|  | ||||
| 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 | ||||
|         --tag trove --tag mysql | ||||
|  | ||||
| #. **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: | ||||
| #. Create the datastore version | ||||
|  | ||||
|    .. code-block:: console | ||||
|  | ||||
|       $ trove-manage --config-file=/etc/trove/trove.conf datastore_update mysql "" | ||||
|       openstack datastore version create 5.7.29 mysql mysql "" \ | ||||
| 			  --image-tags trove,mysql \ | ||||
| 				--active --default | ||||
|  | ||||
| #. **Add a version to the new data store** | ||||
|  | ||||
|    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: | ||||
|  | ||||
|    .. code-block:: console | ||||
|  | ||||
|       $ trove-manage --config-file=/etc/trove/trove.conf datastore_version_update mysql mysql-5.5 mysql GLANCE_ID "" 1 | ||||
|  | ||||
|    **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 | ||||
|       $ trove-manage db_load_datastore_config_parameters mysql 5.7.29 /usr/lib/python3/dist-packages/trove/templates/mysql/validation-rules.json | ||||
|  | ||||
| #. **Validate data store** | ||||
| Hide a datastore version | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
|    To validate your new data store and version, start by listing the | ||||
|    data stores on your system: | ||||
| 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 list | ||||
|       +--------------------------------------+--------------+ | ||||
|       |                  id                  |     name     | | ||||
|       +--------------------------------------+--------------+ | ||||
|       | 10000000-0000-0000-0000-000000000001 | Legacy MySQL | | ||||
|       | e5dc1da3-f080-4589-a4c2-eff7928f969a |    mysql     | | ||||
|       +--------------------------------------+--------------+ | ||||
|  | ||||
|    Show the versions of a specific datastore: | ||||
|  | ||||
|    .. code-block:: console | ||||
|  | ||||
|       $ openstack datastore version list mysql | ||||
|       +--------------------------------------+-----------+ | ||||
|       |                  id                  |    name   | | ||||
|       +--------------------------------------+-----------+ | ||||
|       | 36a6306b-efd8-4d83-9b75-8b30dd756381 | mysql-5.5 | | ||||
|       +--------------------------------------+-----------+ | ||||
|       $ openstack datastore version <version-id> --disable | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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>`_. | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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. | ||||
| @@ -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.') | ||||
|   | ||||
| @@ -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]}, | ||||
|         } | ||||
|   | ||||
| @@ -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'.") | ||||
|   | ||||
							
								
								
									
										36
									
								
								trove/common/glance.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								trove/common/glance.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| @@ -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' | ||||
|  | ||||
|  | ||||
| @@ -447,6 +446,10 @@ class DatastoreVersion(object): | ||||
|     def image_id(self): | ||||
|         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 | ||||
|  | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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) | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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' | ||||
|         ) | ||||
|  | ||||
|   | ||||
| @@ -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() | ||||
|         super(TestDatastoreBase, cls).setUpClass() | ||||
|  | ||||
|     @classmethod | ||||
|     def tearDownClass(cls): | ||||
|         capabilities_overridden = DBCapabilityOverrides.find_all( | ||||
|             datastore_version_id=self.datastore_version.id).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 | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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), | ||||
|   | ||||
							
								
								
									
										324
									
								
								trove/tests/unittests/extensions/mgmt/datastores/test_service.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										324
									
								
								trove/tests/unittests/extensions/mgmt/datastores/test_service.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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']) | ||||
| @@ -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) | ||||
| @@ -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) | ||||
| @@ -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']) | ||||
| @@ -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()) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Lingxian Kong
					Lingxian Kong