Add db api layer for CRUD operations on node tags
- set/unset node tags - get node tags - add/delete single tag - check whether node tag exists - Delete all tags attached to the node when it's destroyed This will not support creating node with tags, just ignore the specified tags. Change-Id: Ibe83a726d904fd33b8550c8bec134cf05644560e Partial-bug: #1526266
This commit is contained in:
parent
33b4bf8542
commit
c1a8606095
@ -601,3 +601,7 @@ class CIMCException(IronicException):
|
|||||||
|
|
||||||
class OneViewError(IronicException):
|
class OneViewError(IronicException):
|
||||||
_msg_fmt = _("OneView exception occurred. Error: %(error)s")
|
_msg_fmt = _("OneView exception occurred. Error: %(error)s")
|
||||||
|
|
||||||
|
|
||||||
|
class NodeTagNotFound(IronicException):
|
||||||
|
_msg_fmt = _("Node %(node_id)s doesn't have a tag '%(tag)s'")
|
||||||
|
@ -543,3 +543,64 @@ class Connection(object):
|
|||||||
:param node_id: The id of a node.
|
:param node_id: The id of a node.
|
||||||
:raises: NodeNotFound
|
:raises: NodeNotFound
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def set_node_tags(self, node_id, tags):
|
||||||
|
"""Replace all of the node tags with specified list of tags.
|
||||||
|
|
||||||
|
This ignores duplicate tags in the specified list.
|
||||||
|
|
||||||
|
:param node_id: The id of a node.
|
||||||
|
:param tags: List of tags.
|
||||||
|
:returns: A list of NodeTag objects.
|
||||||
|
:raises: NodeNotFound if the node is not found.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def unset_node_tags(self, node_id):
|
||||||
|
"""Remove all tags of the node.
|
||||||
|
|
||||||
|
:param node_id: The id of a node.
|
||||||
|
:raises: NodeNotFound if the node is not found.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_node_tags_by_node_id(self, node_id):
|
||||||
|
"""Get node tags based on its id.
|
||||||
|
|
||||||
|
:param node_id: The id of a node.
|
||||||
|
:returns: A list of NodeTag objects.
|
||||||
|
:raises: NodeNotFound if the node is not found.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def add_node_tag(self, node_id, tag):
|
||||||
|
"""Add tag to the node.
|
||||||
|
|
||||||
|
If the node_id and tag pair already exists, this should still
|
||||||
|
succeed.
|
||||||
|
|
||||||
|
:param node_id: The id of a node.
|
||||||
|
:param tag: A tag string.
|
||||||
|
:returns: the NodeTag object.
|
||||||
|
:raises: NodeNotFound if the node is not found.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def delete_node_tag(self, node_id, tag):
|
||||||
|
"""Delete specified tag from the node.
|
||||||
|
|
||||||
|
:param node_id: The id of a node.
|
||||||
|
:param tag: A tag string.
|
||||||
|
:raises: NodeNotFound if the node is not found.
|
||||||
|
:raises: NodeTagNotFound if the tag is not found.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def node_tag_exists(self, node_id, tag):
|
||||||
|
"""Check if the specified tag exist on the node.
|
||||||
|
|
||||||
|
:param node_id: The id of a node.
|
||||||
|
:param tag: A tag string.
|
||||||
|
:returns: True if the tag exists otherwise False.
|
||||||
|
"""
|
||||||
|
@ -289,6 +289,14 @@ class Connection(api.Connection):
|
|||||||
if 'provision_state' not in values:
|
if 'provision_state' not in values:
|
||||||
values['provision_state'] = states.ENROLL
|
values['provision_state'] = states.ENROLL
|
||||||
|
|
||||||
|
# TODO(zhenguo): Support creating node with tags
|
||||||
|
if 'tags' in values:
|
||||||
|
LOG.warning(
|
||||||
|
_LW('Ignore the specified tags %(tags)s when creating node: '
|
||||||
|
'%(node)s.'), {'tags': values['tags'],
|
||||||
|
'node': values['uuid']})
|
||||||
|
del values['tags']
|
||||||
|
|
||||||
node = models.Node()
|
node = models.Node()
|
||||||
node.update(values)
|
node.update(values)
|
||||||
with _session_for_write() as session:
|
with _session_for_write() as session:
|
||||||
@ -364,6 +372,10 @@ class Connection(api.Connection):
|
|||||||
node_id)
|
node_id)
|
||||||
portgroup_query.delete()
|
portgroup_query.delete()
|
||||||
|
|
||||||
|
# Delete all tags attached to the node
|
||||||
|
tag_query = model_query(models.NodeTag).filter_by(node_id=node_id)
|
||||||
|
tag_query.delete()
|
||||||
|
|
||||||
query.delete()
|
query.delete()
|
||||||
|
|
||||||
def update_node(self, node_id, values):
|
def update_node(self, node_id, values):
|
||||||
@ -763,3 +775,59 @@ class Connection(api.Connection):
|
|||||||
count = query.update({'provision_updated_at': timeutils.utcnow()})
|
count = query.update({'provision_updated_at': timeutils.utcnow()})
|
||||||
if count == 0:
|
if count == 0:
|
||||||
raise exception.NodeNotFound(node_id)
|
raise exception.NodeNotFound(node_id)
|
||||||
|
|
||||||
|
def _check_node_exists(self, node_id):
|
||||||
|
if not model_query(models.Node).filter_by(id=node_id).scalar():
|
||||||
|
raise exception.NodeNotFound(node=node_id)
|
||||||
|
|
||||||
|
def set_node_tags(self, node_id, tags):
|
||||||
|
# remove duplicate tags
|
||||||
|
tags = set(tags)
|
||||||
|
with _session_for_write() as session:
|
||||||
|
self.unset_node_tags(node_id)
|
||||||
|
node_tags = []
|
||||||
|
for tag in tags:
|
||||||
|
node_tag = models.NodeTag(tag=tag, node_id=node_id)
|
||||||
|
session.add(node_tag)
|
||||||
|
node_tags.append(node_tag)
|
||||||
|
|
||||||
|
return node_tags
|
||||||
|
|
||||||
|
def unset_node_tags(self, node_id):
|
||||||
|
self._check_node_exists(node_id)
|
||||||
|
with _session_for_write():
|
||||||
|
model_query(models.NodeTag).filter_by(node_id=node_id).delete()
|
||||||
|
|
||||||
|
def get_node_tags_by_node_id(self, node_id):
|
||||||
|
self._check_node_exists(node_id)
|
||||||
|
result = (model_query(models.NodeTag)
|
||||||
|
.filter_by(node_id=node_id)
|
||||||
|
.all())
|
||||||
|
return result
|
||||||
|
|
||||||
|
def add_node_tag(self, node_id, tag):
|
||||||
|
node_tag = models.NodeTag(tag=tag, node_id=node_id)
|
||||||
|
|
||||||
|
self._check_node_exists(node_id)
|
||||||
|
try:
|
||||||
|
with _session_for_write() as session:
|
||||||
|
session.add(node_tag)
|
||||||
|
session.flush()
|
||||||
|
except db_exc.DBDuplicateEntry:
|
||||||
|
# NOTE(zhenguo): ignore tags duplicates
|
||||||
|
pass
|
||||||
|
|
||||||
|
return node_tag
|
||||||
|
|
||||||
|
def delete_node_tag(self, node_id, tag):
|
||||||
|
self._check_node_exists(node_id)
|
||||||
|
with _session_for_write():
|
||||||
|
result = model_query(models.NodeTag).filter_by(
|
||||||
|
node_id=node_id, tag=tag).delete()
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
raise exception.NodeTagNotFound(node_id=node_id, tag=tag)
|
||||||
|
|
||||||
|
def node_tag_exists(self, node_id, tag):
|
||||||
|
q = model_query(models.NodeTag).filter_by(node_id=node_id, tag=tag)
|
||||||
|
return model_query(q.exists()).scalar()
|
||||||
|
@ -95,6 +95,7 @@ def node_post_data(**kw):
|
|||||||
node.pop('chassis_id')
|
node.pop('chassis_id')
|
||||||
node.pop('target_raid_config')
|
node.pop('target_raid_config')
|
||||||
node.pop('raid_config')
|
node.pop('raid_config')
|
||||||
|
node.pop('tags')
|
||||||
internal = node_controller.NodePatchType.internal_attrs()
|
internal = node_controller.NodePatchType.internal_attrs()
|
||||||
return remove_internal(node, internal)
|
return remove_internal(node, internal)
|
||||||
|
|
||||||
|
111
ironic/tests/unit/db/test_node_tags.py
Normal file
111
ironic/tests/unit/db/test_node_tags.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
# 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 NodeTags via the DB API"""
|
||||||
|
|
||||||
|
from ironic.common import exception
|
||||||
|
|
||||||
|
from ironic.tests.unit.db import base
|
||||||
|
from ironic.tests.unit.db import utils as db_utils
|
||||||
|
|
||||||
|
|
||||||
|
class DbNodeTagTestCase(base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(DbNodeTagTestCase, self).setUp()
|
||||||
|
self.node = db_utils.create_test_node()
|
||||||
|
|
||||||
|
def test_set_node_tags(self):
|
||||||
|
tags = self.dbapi.set_node_tags(self.node.id, ['tag1', 'tag2'])
|
||||||
|
self.assertEqual(self.node.id, tags[0].node_id)
|
||||||
|
self.assertItemsEqual(['tag1', 'tag2'], [tag.tag for tag in tags])
|
||||||
|
|
||||||
|
tags = self.dbapi.set_node_tags(self.node.id, [])
|
||||||
|
self.assertEqual([], tags)
|
||||||
|
|
||||||
|
def test_set_node_tags_duplicate(self):
|
||||||
|
tags = self.dbapi.set_node_tags(self.node.id,
|
||||||
|
['tag1', 'tag2', 'tag2'])
|
||||||
|
self.assertEqual(self.node.id, tags[0].node_id)
|
||||||
|
self.assertItemsEqual(['tag1', 'tag2'], [tag.tag for tag in tags])
|
||||||
|
|
||||||
|
def test_set_node_tags_node_not_exist(self):
|
||||||
|
self.assertRaises(exception.NodeNotFound,
|
||||||
|
self.dbapi.set_node_tags, '1234', ['tag1', 'tag2'])
|
||||||
|
|
||||||
|
def test_get_node_tags_by_node_id(self):
|
||||||
|
self.dbapi.set_node_tags(self.node.id, ['tag1', 'tag2'])
|
||||||
|
tags = self.dbapi.get_node_tags_by_node_id(self.node.id)
|
||||||
|
self.assertEqual(self.node.id, tags[0].node_id)
|
||||||
|
self.assertItemsEqual(['tag1', 'tag2'], [tag.tag for tag in tags])
|
||||||
|
|
||||||
|
def test_get_node_tags_empty(self):
|
||||||
|
tags = self.dbapi.get_node_tags_by_node_id(self.node.id)
|
||||||
|
self.assertEqual([], tags)
|
||||||
|
|
||||||
|
def test_get_node_tags_node_not_exist(self):
|
||||||
|
self.assertRaises(exception.NodeNotFound,
|
||||||
|
self.dbapi.get_node_tags_by_node_id, '123')
|
||||||
|
|
||||||
|
def test_unset_node_tags(self):
|
||||||
|
self.dbapi.set_node_tags(self.node.id, ['tag1', 'tag2'])
|
||||||
|
self.dbapi.unset_node_tags(self.node.id)
|
||||||
|
tags = self.dbapi.get_node_tags_by_node_id(self.node.id)
|
||||||
|
self.assertEqual([], tags)
|
||||||
|
|
||||||
|
def test_unset_empty_node_tags(self):
|
||||||
|
self.dbapi.unset_node_tags(self.node.id)
|
||||||
|
tags = self.dbapi.get_node_tags_by_node_id(self.node.id)
|
||||||
|
self.assertEqual([], tags)
|
||||||
|
|
||||||
|
def test_unset_node_tags_node_not_exist(self):
|
||||||
|
self.assertRaises(exception.NodeNotFound,
|
||||||
|
self.dbapi.unset_node_tags, '123')
|
||||||
|
|
||||||
|
def test_add_node_tag(self):
|
||||||
|
tag = self.dbapi.add_node_tag(self.node.id, 'tag1')
|
||||||
|
self.assertEqual(self.node.id, tag.node_id)
|
||||||
|
self.assertEqual('tag1', tag.tag)
|
||||||
|
|
||||||
|
def test_add_node_tag_duplicate(self):
|
||||||
|
tag = self.dbapi.add_node_tag(self.node.id, 'tag1')
|
||||||
|
tag = self.dbapi.add_node_tag(self.node.id, 'tag1')
|
||||||
|
self.assertEqual(self.node.id, tag.node_id)
|
||||||
|
self.assertEqual('tag1', tag.tag)
|
||||||
|
|
||||||
|
def test_add_node_tag_node_not_exist(self):
|
||||||
|
self.assertRaises(exception.NodeNotFound,
|
||||||
|
self.dbapi.add_node_tag, '123', 'tag1')
|
||||||
|
|
||||||
|
def test_delete_node_tag(self):
|
||||||
|
self.dbapi.set_node_tags(self.node.id, ['tag1', 'tag2'])
|
||||||
|
self.dbapi.delete_node_tag(self.node.id, 'tag1')
|
||||||
|
tags = self.dbapi.get_node_tags_by_node_id(self.node.id)
|
||||||
|
self.assertEqual(1, len(tags))
|
||||||
|
self.assertEqual('tag2', tags[0].tag)
|
||||||
|
|
||||||
|
def test_delete_node_tag_not_found(self):
|
||||||
|
self.assertRaises(exception.NodeTagNotFound,
|
||||||
|
self.dbapi.delete_node_tag, self.node.id, 'tag1')
|
||||||
|
|
||||||
|
def test_delete_node_tag_node_not_found(self):
|
||||||
|
self.assertRaises(exception.NodeNotFound,
|
||||||
|
self.dbapi.delete_node_tag, '123', 'tag1')
|
||||||
|
|
||||||
|
def test_node_tag_exists(self):
|
||||||
|
self.dbapi.set_node_tags(self.node.id, ['tag1', 'tag2'])
|
||||||
|
ret = self.dbapi.node_tag_exists(self.node.id, 'tag1')
|
||||||
|
self.assertTrue(ret)
|
||||||
|
|
||||||
|
def test_node_tag_not_exists(self):
|
||||||
|
ret = self.dbapi.node_tag_exists(self.node.id, 'tag1')
|
||||||
|
self.assertFalse(ret)
|
@ -24,6 +24,7 @@ import six
|
|||||||
|
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
from ironic.common import states
|
from ironic.common import states
|
||||||
|
from ironic.db.sqlalchemy import api
|
||||||
from ironic.tests.unit.db import base
|
from ironic.tests.unit.db import base
|
||||||
from ironic.tests.unit.db import utils
|
from ironic.tests.unit.db import utils
|
||||||
|
|
||||||
@ -33,6 +34,11 @@ class DbNodeTestCase(base.DbTestCase):
|
|||||||
def test_create_node(self):
|
def test_create_node(self):
|
||||||
utils.create_test_node()
|
utils.create_test_node()
|
||||||
|
|
||||||
|
@mock.patch.object(api.LOG, 'warning', autospec=True)
|
||||||
|
def test_create_node_with_tags(self, mock_log):
|
||||||
|
utils.create_test_node(tags=['tag1', 'tag2'])
|
||||||
|
self.assertTrue(mock_log.called)
|
||||||
|
|
||||||
def test_create_node_already_exists(self):
|
def test_create_node_already_exists(self):
|
||||||
utils.create_test_node()
|
utils.create_test_node()
|
||||||
self.assertRaises(exception.NodeAlreadyExists,
|
self.assertRaises(exception.NodeAlreadyExists,
|
||||||
@ -322,6 +328,24 @@ class DbNodeTestCase(base.DbTestCase):
|
|||||||
self.assertRaises(exception.PortNotFound,
|
self.assertRaises(exception.PortNotFound,
|
||||||
self.dbapi.get_port_by_id, port.id)
|
self.dbapi.get_port_by_id, port.id)
|
||||||
|
|
||||||
|
def test_tags_get_destroyed_after_destroying_a_node(self):
|
||||||
|
node = utils.create_test_node()
|
||||||
|
|
||||||
|
tag = utils.create_test_node_tag(node_id=node.id)
|
||||||
|
|
||||||
|
self.assertTrue(self.dbapi.node_tag_exists(node.id, tag.tag))
|
||||||
|
self.dbapi.destroy_node(node.id)
|
||||||
|
self.assertFalse(self.dbapi.node_tag_exists(node.id, tag.tag))
|
||||||
|
|
||||||
|
def test_tags_get_destroyed_after_destroying_a_node_by_uuid(self):
|
||||||
|
node = utils.create_test_node()
|
||||||
|
|
||||||
|
tag = utils.create_test_node_tag(node_id=node.id)
|
||||||
|
|
||||||
|
self.assertTrue(self.dbapi.node_tag_exists(node.id, tag.tag))
|
||||||
|
self.dbapi.destroy_node(node.uuid)
|
||||||
|
self.assertFalse(self.dbapi.node_tag_exists(node.id, tag.tag))
|
||||||
|
|
||||||
def test_update_node(self):
|
def test_update_node(self):
|
||||||
node = utils.create_test_node()
|
node = utils.create_test_node()
|
||||||
|
|
||||||
|
@ -224,6 +224,7 @@ def get_test_node(**kw):
|
|||||||
'inspection_started_at': kw.get('inspection_started_at'),
|
'inspection_started_at': kw.get('inspection_started_at'),
|
||||||
'raid_config': kw.get('raid_config'),
|
'raid_config': kw.get('raid_config'),
|
||||||
'target_raid_config': kw.get('target_raid_config'),
|
'target_raid_config': kw.get('target_raid_config'),
|
||||||
|
'tags': kw.get('tags', []),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -380,3 +381,26 @@ def create_test_portgroup(**kw):
|
|||||||
del portgroup['id']
|
del portgroup['id']
|
||||||
dbapi = db_api.get_instance()
|
dbapi = db_api.get_instance()
|
||||||
return dbapi.create_portgroup(portgroup)
|
return dbapi.create_portgroup(portgroup)
|
||||||
|
|
||||||
|
|
||||||
|
def get_test_node_tag(**kw):
|
||||||
|
return {
|
||||||
|
"tag": kw.get("tag", "tag1"),
|
||||||
|
"node_id": kw.get("node_id", "123"),
|
||||||
|
'created_at': kw.get('created_at'),
|
||||||
|
'updated_at': kw.get('updated_at'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def create_test_node_tag(**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 kw: kwargs with overriding values for tag's attributes.
|
||||||
|
:returns: Test NodeTag DB object.
|
||||||
|
|
||||||
|
"""
|
||||||
|
tag = get_test_node_tag(**kw)
|
||||||
|
dbapi = db_api.get_instance()
|
||||||
|
return dbapi.add_node_tag(tag['node_id'], tag['tag'])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user