diff --git a/mogan/common/exception.py b/mogan/common/exception.py index c935612c..71178d5e 100644 --- a/mogan/common/exception.py +++ b/mogan/common/exception.py @@ -504,4 +504,8 @@ class NodeNotAllowedManaged(Forbidden): _msg_fmt = _("The bare metal node %(node_uuid)s is not allowed to " "be managed") + +class ServerTagNotFound(NotFound): + _msg_fmt = _("Server %(server_id)s doesn't have a tag '%(tag)s'") + ObjectActionError = obj_exc.ObjectActionError diff --git a/mogan/db/api.py b/mogan/db/api.py index 33af7204..329f14fb 100644 --- a/mogan/db/api.py +++ b/mogan/db/api.py @@ -289,3 +289,70 @@ class Connection(object): def server_group_members_add(self, context, group_uuid, members): """Add a list of members to a server group""" return IMPL.server_group_members_add(context, group_uuid, members) + + @abc.abstractmethod + def set_server_tags(self, context, server_id, tags): + """Replace all of the server tags with specified list of tags. + + This ignores duplicate tags in the specified list. + + :param context: Request context + :param server_id: The id of a server. + :param tags: List of tags. + :returns: A list of ServerTag objects. + :raises: ServerNotFound if the server is not found. + """ + + @abc.abstractmethod + def unset_server_tags(self, context, server_id): + """Remove all tags of the server. + + :param context: Request context + :param server_id: The id of a server. + :raises: ServerNotFound if the server is not found. + """ + + @abc.abstractmethod + def get_server_tags_by_server_id(self, context, server_id): + """Get server tags based on its id. + + :param context: Request context + :param server_id: The id of a server. + :returns: A list of ServerTag objects. + :raises: ServerNotFound if the server is not found. + """ + + @abc.abstractmethod + def add_server_tag(self, context, server_id, tag): + """Add tag to the server. + + If the server_id and tag pair already exists, this should still + succeed. + + :param context: Request context + :param server_id: The id of a server. + :param tag: A tag string. + :returns: the ServerTag object. + :raises: ServerNotFound if the server is not found. + """ + + @abc.abstractmethod + def delete_server_tag(self, context, server_id, tag): + """Delete specified tag from the server. + + :param context: Request context + :param server_id: The id of a server. + :param tag: A tag string. + :raises: ServerNotFound if the server is not found. + """ + + @abc.abstractmethod + def server_tag_exists(self, context, server_id, tag): + """Check if the specified tag exist on the server. + + :param context: Request context + :param server_id: The id of a server. + :param tag: A tag string. + :returns: True if the tag exists otherwise False. + :raises: ServerTagNotFound if the tag is not found. + """ diff --git a/mogan/db/sqlalchemy/api.py b/mogan/db/sqlalchemy/api.py index 1399ffcd..23bf1c22 100644 --- a/mogan/db/sqlalchemy/api.py +++ b/mogan/db/sqlalchemy/api.py @@ -234,6 +234,7 @@ class Connection(api.Connection): nic_ref = models.ServerNic() nic_ref.update(nic) nic_refs.append(nic_ref) + with _session_for_write() as session: try: session.add(server) @@ -1075,6 +1076,70 @@ class Connection(api.Connection): raise exception.ServerGroupNotFound(group_uuid=group_uuid) self._server_group_members_add(context, group.id, members) + def _check_server_exists(self, context, server_id): + if not model_query(context, models.Server)\ + .filter_by(id=server_id).scalar(): + raise exception.ServerNotFound(server=server_id) + + @oslo_db_api.retry_on_deadlock + def set_server_tags(self, context, server_id, tags): + # remove duplicate tags + tags = set(tags) + with _session_for_write() as session: + self.unset_server_tags(context, server_id) + server_tags = [] + for tag in tags: + server_tag = models.ServerTag(tag=tag, server_id=server_id) + session.add(server_tag) + server_tags.append(server_tag) + + return server_tags + + @oslo_db_api.retry_on_deadlock + def unset_server_tags(self, context, server_id): + self._check_server_exists(context, server_id) + with _session_for_write(): + model_query(context, models.ServerTag)\ + .filter_by(server_id=server_id).delete() + + def get_server_tags_by_server_id(self, context, server_id): + self._check_server_exists(context, server_id) + result = (model_query(context, models.ServerTag) + .filter_by(server_id=server_id) + .all()) + return result + + @oslo_db_api.retry_on_deadlock + def add_server_tag(self, context, server_id, tag): + self._check_server_exists(context, server_id) + server_tag = models.ServerTag(tag=tag, server_id=server_id) + try: + with _session_for_write() as session: + session.add(server_tag) + session.flush() + except db_exc.DBDuplicateEntry: + # NOTE(litao): ignore tags duplicates + pass + + return server_tag + + @oslo_db_api.retry_on_deadlock + def delete_server_tag(self, context, server_id, tag): + self._check_server_exists(context, server_id) + with _session_for_write(): + result = model_query(context, models.ServerTag).filter_by( + server_id=server_id, tag=tag).delete() + + if not result: + raise exception.ServerTagNotFound(server_id=server_id, tag=tag) + + def server_tag_exists(self, context, server_id, tag): + self._check_server_exists(context, server_id) + q = model_query(context, models.ServerTag)\ + .filter_by(server_id=server_id, tag=tag) + + return q.scalar() + def _get_id_from_flavor_query(context, type_id): return model_query(context, models.Flavors). \ diff --git a/mogan/db/sqlalchemy/models.py b/mogan/db/sqlalchemy/models.py index 3148e611..ac2f524d 100644 --- a/mogan/db/sqlalchemy/models.py +++ b/mogan/db/sqlalchemy/models.py @@ -385,3 +385,10 @@ class ServerTag(Base): server_id = Column(Integer, ForeignKey('servers.id'), primary_key=True, nullable=False) tag = Column(String(255), primary_key=True, nullable=False) + + server = orm.relationship( + "Server", + backref='tags', + primaryjoin='and_(ServerTag.server_id == Server.id)', + foreign_keys=server_id + ) diff --git a/mogan/tests/unit/db/test_server_tags.py b/mogan/tests/unit/db/test_server_tags.py new file mode 100644 index 00000000..a76e6de5 --- /dev/null +++ b/mogan/tests/unit/db/test_server_tags.py @@ -0,0 +1,130 @@ +# Copyright 2017 Fiberhome Integration Technologies Co.,LTD. +# 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. + +"""Tests for manipulating Server tags via the DB API""" + +from mogan.common import exception +from mogan.tests.unit.db import base +from mogan.tests.unit.db import utils + + +class DbServerTagTestCase(base.DbTestCase): + + def setUp(self): + super(DbServerTagTestCase, self).setUp() + self.server = utils.create_test_server() + + def test_set_server_tags(self): + tags = self.dbapi.set_server_tags(self.context, self.server.id, + ['tag1', 'tag2']) + self.assertEqual(self.server.id, tags[0].server_id) + self.assertItemsEqual(['tag1', 'tag2'], [tag.tag for tag in tags]) + + tags = self.dbapi.set_server_tags(self.context, self.server.id, []) + self.assertEqual([], tags) + + def test_set_server_tags_duplicate(self): + tags = self.dbapi.set_server_tags(self.context, self.server.id, + ['tag1', 'tag2', 'tag2']) + self.assertEqual(self.server.id, tags[0].server_id) + self.assertItemsEqual(['tag1', 'tag2'], [tag.tag for tag in tags]) + + def test_set_server_tags_server_not_exist(self): + self.assertRaises(exception.ServerNotFound, + self.dbapi.set_server_tags, + self.context, '1234', ['tag1', 'tag2']) + + def test_get_server_tags_by_server_id(self): + self.dbapi.set_server_tags(self.context, self.server.id, + ['tag1', 'tag2']) + tags = self.dbapi.get_server_tags_by_server_id(self.context, + self.server.id) + self.assertEqual(self.server.id, tags[0].server_id) + self.assertItemsEqual(['tag1', 'tag2'], [tag.tag for tag in tags]) + + def test_get_server_tags_empty(self): + tags = self.dbapi.get_server_tags_by_server_id(self.context, + self.server.id) + self.assertEqual([], tags) + + def test_get_server_tags_server_not_exist(self): + self.assertRaises(exception.ServerNotFound, + self.dbapi.get_server_tags_by_server_id, + self.context, '123') + + def test_unset_server_tags(self): + self.dbapi.set_server_tags(self.context, self.server.id, + ['tag1', 'tag2']) + self.dbapi.unset_server_tags(self.context, self.server.id) + tags = self.dbapi.get_server_tags_by_server_id(self.context, + self.server.id) + self.assertEqual([], tags) + + def test_unset_empty_server_tags(self): + self.dbapi.unset_server_tags(self.context, self.server.id) + tags = self.dbapi.get_server_tags_by_server_id(self.context, + self.server.id) + self.assertEqual([], tags) + + def test_unset_server_tags_server_not_exist(self): + self.assertRaises(exception.ServerNotFound, + self.dbapi.unset_server_tags, self.context, '123') + + def test_add_server_tag(self): + tag = self.dbapi.add_server_tag(self.context, self.server.id, 'tag1') + self.assertEqual(self.server.id, tag.server_id) + self.assertEqual('tag1', tag.tag) + + def test_add_server_tag_duplicate(self): + tag = self.dbapi.add_server_tag(self.context, self.server.id, 'tag1') + tag = self.dbapi.add_server_tag(self.context, self.server.id, 'tag1') + self.assertEqual(self.server.id, tag.server_id) + self.assertEqual('tag1', tag.tag) + + def test_add_server_tag_server_not_exist(self): + self.assertRaises(exception.ServerNotFound, + self.dbapi.add_server_tag, self.context, + '123', 'tag1') + + def test_delete_server_tag(self): + self.dbapi.set_server_tags(self.context, self.server.id, + ['tag1', 'tag2']) + self.dbapi.delete_server_tag(self.context, self.server.id, 'tag1') + tags = self.dbapi.get_server_tags_by_server_id(self.context, + self.server.id) + self.assertEqual(1, len(tags)) + self.assertEqual('tag2', tags[0].tag) + + def test_delete_server_tag_not_found(self): + self.assertRaises(exception.ServerTagNotFound, + self.dbapi.delete_server_tag, self.context, + self.server.id, 'tag1') + + def test_delete_server_tag_server_not_found(self): + self.assertRaises(exception.ServerNotFound, + self.dbapi.delete_server_tag, self.context, + '123', 'tag1') + + def test_server_tag_exists(self): + self.dbapi.set_server_tags(self.context, + self.server.id, ['tag1', 'tag2']) + ret = self.dbapi.server_tag_exists(self.context, + self.server.id, 'tag1') + self.assertTrue(ret) + + def test_server_tag_not_exists(self): + ret = self.dbapi.server_tag_exists(self.context, + self.server.id, 'tag1') + self.assertFalse(ret) diff --git a/mogan/tests/unit/db/test_servers.py b/mogan/tests/unit/db/test_servers.py index 7ffed780..b7806442 100644 --- a/mogan/tests/unit/db/test_servers.py +++ b/mogan/tests/unit/db/test_servers.py @@ -149,3 +149,25 @@ class DbServerTestCase(base.DbTestCase): self.context, server.uuid, {'uuid': '12345678-9999-0000-aaaa-123456789012'}) + + def test_tags_get_destroyed_after_destroying_a_server(self): + server = utils.create_test_server(id=123) + tag = utils.create_test_server_tag(self.context, server_id=server.id) + + self.assertTrue(self.dbapi.server_tag_exists(self.context, + server.id, tag.tag)) + self.dbapi.server_destroy(self.context, server.id) + self.assertRaises(exception.ServerNotFound, + self.dbapi.server_tag_exists, + self.context, server.id, tag.tag) + + def test_tags_get_destroyed_after_destroying_a_server_by_uuid(self): + server = utils.create_test_server(id=124) + tag = utils.create_test_server_tag(self.context, server_id=server.id) + + self.assertTrue(self.dbapi.server_tag_exists(self.context, + server.id, tag.tag)) + self.dbapi.server_destroy(self.context, server.uuid) + self.assertRaises(exception.ServerNotFound, + self.dbapi.server_tag_exists, + self.context, server.id, tag.tag) diff --git a/mogan/tests/unit/db/utils.py b/mogan/tests/unit/db/utils.py index 133188df..b03af43c 100644 --- a/mogan/tests/unit/db/utils.py +++ b/mogan/tests/unit/db/utils.py @@ -243,3 +243,27 @@ def create_test_server_group(context={}, **kw): policies = server_fault.pop('policies') return dbapi.server_group_create(context, server_fault, policies=policies, members=members) + + +def get_test_server_tag(**kw): + return { + "tag": kw.get("tag", "tag1"), + "server_id": kw.get("server_id", "123"), + 'created_at': kw.get('created_at'), + 'updated_at': kw.get('updated_at'), + } + + +def create_test_server_tag(context, **kw): + """Create test node tag entry in DB and return NodeTag DB object. + + Function to be used to create test NodeTag objects in the database. + + :param context: Request context + :param kw: kwargs with overriding values for tag's attributes. + :returns: Test NodeTag DB object. + + """ + tag = get_test_server_tag(**kw) + dbapi = db_api.get_instance() + return dbapi.add_server_tag(context, tag['server_id'], tag['tag'])