diff --git a/doc/source/admin/datastore.rst b/doc/source/admin/datastore.rst index d98fdef4fc..59e2b26d30 100644 --- a/doc/source/admin/datastore.rst +++ b/doc/source/admin/datastore.rst @@ -60,8 +60,8 @@ To create a datastore version: .. code-block:: console openstack datastore version create 5.7.29 mysql mysql "" \ - --image-tags trove,mysql \ - --active --default + --image-tags trove,mysql \ + --active --default #. Load validation rules for configuration groups @@ -121,4 +121,19 @@ version for testing purpose, to do that: .. code-block:: console - $ openstack datastore version --disable + $ openstack datastore version set --disable + +Replace image ID with tags +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For datastore versions what are created using image ID, it's easy to switch to +image tags without affecting the existing instances. New instances will be +created by the image ID (the most recently uploaded) that getting from Glance +using image tags. To do that, as the cloud admin user: + + .. code-block:: console + + $ openstack datastore version set --image-tags trove,mysql + +Ignoring ``--image`` means removing the image ID from the datastore version if +it's associated. diff --git a/doc/source/install/verify.rst b/doc/source/install/verify.rst index a607ee5899..3fd0960ae9 100644 --- a/doc/source/install/verify.rst +++ b/doc/source/install/verify.rst @@ -33,7 +33,9 @@ Verify operation of the Database service. .. code-block:: console - $ openstack datastore version create 5.7.29 mysql mysql "" trove,mysql --active --default + $ openstack datastore version create 5.7.29 mysql mysql "" \ + --image-tags trove,mysql \ + --active --default #. Create a database `instance `_. diff --git a/trove/common/glance.py b/trove/common/glance.py index 7461ae90ce..3bf8a6d9e6 100644 --- a/trove/common/glance.py +++ b/trove/common/glance.py @@ -26,6 +26,8 @@ def get_image_id(client, image_id, image_tags): return image_id elif image_tags: + if isinstance(image_tags, str): + image_tags = image_tags.split(',') filters = {'tag': image_tags, 'status': 'active'} images = list(client.images.list( filters=filters, sort='created_at:desc', limit=1)) diff --git a/trove/instance/service.py b/trove/instance/service.py index 883c1cd3d8..66c554707d 100644 --- a/trove/instance/service.py +++ b/trove/instance/service.py @@ -24,6 +24,7 @@ import trove.common.apischema as apischema from trove.common import cfg from trove.common import clients from trove.common import exception +from trove.common import glance as common_glance from trove.common.i18n import _ from trove.common import neutron from trove.common import notification @@ -414,7 +415,13 @@ class InstanceController(wsgi.Controller): datastore, datastore_version = ds_models.get_datastore_version( **datastore_args) - image_id = datastore_version.image_id + # If only image_tags is configured in the datastore version, get + # the image ID using the tags. + glance_client = clients.create_glance_client(context) + image_id = common_glance.get_image_id( + glance_client, datastore_version.image_id, + datastore_version.image_tags) + LOG.info(f'Using image {image_id} for creating instance') databases = populate_validated_databases( body['instance'].get('databases', [])) diff --git a/trove/tests/unittests/extensions/mgmt/datastores/test_service.py b/trove/tests/unittests/extensions/mgmt/datastores/test_service.py index be1d49fc15..185a740ebe 100644 --- a/trove/tests/unittests/extensions/mgmt/datastores/test_service.py +++ b/trove/tests/unittests/extensions/mgmt/datastores/test_service.py @@ -50,11 +50,7 @@ class TestDatastoreVersionController(trove_testtools.TestCase): @classmethod def tearDownClass(cls): - versions = models.DatastoreVersions.load_all(only_active=False) - for ver in versions: - ver.delete() - - cls.ds.delete() + util.cleanup_db() super(TestDatastoreVersionController, cls).tearDownClass() diff --git a/trove/tests/unittests/instance/test_service.py b/trove/tests/unittests/instance/test_service.py new file mode 100644 index 0000000000..e263565498 --- /dev/null +++ b/trove/tests/unittests/instance/test_service.py @@ -0,0 +1,80 @@ +# 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 unittest import mock + +from trove.common import clients +from trove.datastore import models as ds_models +from trove.instance import service +from trove.tests.unittests import trove_testtools +from trove.tests.unittests.util import util + + +class TestInstanceController(trove_testtools.TestCase): + @classmethod + def setUpClass(cls): + util.init_db() + + cls.ds_name = cls.random_name('datastore', + prefix='TestInstanceController') + ds_models.update_datastore(name=cls.ds_name, default_version=None) + cls.ds = ds_models.Datastore.load(cls.ds_name) + + ds_models.update_datastore_version( + cls.ds_name, 'test_image_id', 'mysql', cls.random_uuid(), [], '', + 1) + ds_models.update_datastore_version( + cls.ds_name, 'test_image_tags', 'mysql', '', ['trove', 'mysql'], + '', 1) + cls.ds_version_imageid = ds_models.DatastoreVersion.load( + cls.ds, 'test_image_id') + cls.ds_version_imagetags = ds_models.DatastoreVersion.load( + cls.ds, 'test_image_tags') + + cls.controller = service.InstanceController() + + super(TestInstanceController, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + util.cleanup_db() + + super(TestInstanceController, cls).tearDownClass() + + @mock.patch.object(clients, 'create_glance_client') + @mock.patch('trove.instance.models.Instance.create') + def test_create_by_ds_version_image_tags(self, mock_model_create, + mock_create_client): + mock_glance_client = mock.MagicMock() + mock_glance_client.images.list.return_value = [ + {'id': self.random_uuid()}] + mock_create_client.return_value = mock_glance_client + + body = { + 'instance': { + 'name': self.random_name(name='instance', + prefix='TestInstanceController'), + 'flavorRef': self.random_uuid(), + 'datastore': { + 'type': self.ds_name, + 'version': self.ds_version_imagetags.name + } + } + } + ret = self.controller.create(mock.MagicMock(), body, mock.ANY) + + self.assertEqual(200, ret.status) + mock_glance_client.images.list.assert_called_once_with( + filters={'tag': ['trove', 'mysql'], 'status': 'active'}, + sort='created_at:desc', limit=1 + ) diff --git a/trove/tests/unittests/util/util.py b/trove/tests/unittests/util/util.py index bf1eda6678..ce22ed7bc0 100644 --- a/trove/tests/unittests/util/util.py +++ b/trove/tests/unittests/util/util.py @@ -31,3 +31,11 @@ def init_db(): db_api.db_sync(CONF) session.configure_db(CONF) DB_SETUP = True + + +def cleanup_db(): + with LOCK: + global DB_SETUP + if DB_SETUP: + session.clean_db() + DB_SETUP = False