Merge "ng-1: Add nodegroup representation"

This commit is contained in:
Zuul 2019-03-24 20:48:47 +00:00 committed by Gerrit Code Review
commit e63611ad1a
11 changed files with 984 additions and 2 deletions

View File

@ -397,3 +397,12 @@ class MemberAlreadyExists(Conflict):
class PreDeletionFailed(Conflict):
message = _("Failed to pre-delete resources for cluster %(cluster_uuid)s, "
"error: %(msg)s.")
class NodeGroupAlreadyExists(Conflict):
message = _("A node group with name %(name)s already exists in the "
"cluster %(cluster_id)s.")
class NodeGroupNotFound(ResourceNotFound):
message = _("Nodegroup %(nodegroup)s could not be found.")

View File

@ -540,3 +540,118 @@ class Connection(object):
:returns: A federation.
:raises: FederationNotFound
"""
@abc.abstractmethod
def create_nodegroup(self, values):
"""Create a new nodegroup in cluster.
:param values: A dict containing several items used to identify
and track the nodegroup.
For example:
::
{
'uuid': uuidutils.generate_uuid(),
'name': 'example',
...
}
:returns: A nodegroup record.
"""
@abc.abstractmethod
def destroy_nodegroup(self, cluster_id, nodegroup_id):
"""Destroy a nodegroup.
:param cluster_id: The uuid of the cluster where the nodegroup
belongs to.
:param nodegroup_id: The id or uuid of the nodegroup
"""
@abc.abstractmethod
def update_nodegroup(self, cluster_id, nodegroup_id, values):
"""Update properties of a nodegroup.
:param cluster_id: The uuid of the cluster where the nodegroup
belongs to.
:param nodegroup_id: The id or uuid of a nodegroup.
:param values: A dict containing several items used to identify
and track the nodegroup.
For example:
::
{
'uuid': uuidutils.generate_uuid(),
'name': 'example',
...
}
:returns: A nodegroup record.
:raises: NodeGroupNotFound
"""
@abc.abstractmethod
def get_nodegroup_by_id(self, context, cluster_id, nodegroup_id):
"""Return a nodegroup for a given cluster uuid and nodegroup id.
:param cluster_id: The uuid of the cluster where the nodegroup
belongs to.
:param nodegroup_id: The id of a nodegroup.
:returns: A nodegroup record.
:raises: NodeGroupNotFound
"""
@abc.abstractmethod
def get_nodegroup_by_uuid(self, context, cluster_id, nodegroup_uuid):
"""Return a nodegroup for a given cluster uuid and nodegroup uuid.
:param cluster_id: The uuid of the cluster where the nodegroup
belongs to.
:param nodegroup_uuid: The uuid of a nodegroup.
:returns: A nodegroup record.
:raises: NodeGroupNotFound
"""
@abc.abstractmethod
def get_nodegroup_by_name(self, context, cluster_id, nodegroup_name):
"""Return a nodegroup for a given cluster uuid and nodegroup name.
:param cluster_id: The uuid of the cluster where the nodegroup
belongs to.
:param nodegroup_name: The name of a nodegroup.
:returns: A nodegroup record.
:raises: NodeGroupNotFound
"""
@abc.abstractmethod
def list_cluster_nodegroups(self, context, cluster_id, filters=None,
limit=None, marker=None, sort_key=None,
sort_dir=None):
"""Get matching nodegroups in a given cluster.
:param context: The security context
:param cluster_id: The uuid of the cluster where the nodegroup
belongs to.
:param filters: Filters to apply. Defaults to None.
:param limit: Maximum number of nodegroups to return.
:param marker: the last item of the previous page; we return the next
result set.
:param sort_key: Attribute by which results should be sorted.
:param sort_dir: direction in which results should be sorted.
(asc, desc)
:returns: A list of nodegroup records.
"""
@abc.abstractmethod
def get_cluster_nodegroup_count(self, context, cluster_id):
"""Get count of nodegroups in a given cluster.
:param cluster_id: The uuid of the cluster where the nodegroup
belongs to.
:returns: Count of matching clusters.
"""

View File

@ -0,0 +1,61 @@
# Copyright (c) 2018 European Organization for Nuclear Research.
# 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.
"""add nodegoup table
Revision ID: ac92cbae311c
Revises: cbbc65a86986
Create Date: 2018-09-20 15:26:00.869885
"""
# revision identifiers, used by Alembic.
revision = 'ac92cbae311c'
down_revision = '87e62e3c7abc'
from alembic import op
import sqlalchemy as sa
from oslo_db.sqlalchemy.types import String
from magnum.db.sqlalchemy import models
def upgrade():
op.create_table(
'nodegroup',
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('uuid', String(length=36), nullable=False),
sa.Column('name', String(length=255), nullable=False),
sa.Column('cluster_id', String(length=255), nullable=False),
sa.Column('project_id', String(length=255), nullable=False),
sa.Column('docker_volume_size', sa.Integer(), nullable=True),
sa.Column('labels', models.JSONEncodedDict, nullable=True),
sa.Column('flavor_id', String(length=255), nullable=True),
sa.Column('image_id', String(length=255), nullable=True),
sa.Column('node_addresses', models.JSONEncodedList(), nullable=True),
sa.Column('node_count', sa.Integer(), nullable=True),
sa.Column('max_node_count', sa.Integer(), nullable=True),
sa.Column('min_node_count', sa.Integer(), nullable=True),
sa.Column('role', String(length=255), nullable=True),
sa.Column('is_default', sa.Boolean(), default=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('uuid', name='uniq_nodegroup0uuid'),
sa.UniqueConstraint('cluster_id', 'name',
name='uniq_nodegroup0cluster_id0name'),
)

View File

@ -762,3 +762,115 @@ class Connection(api.Connection):
ref.update(values)
return ref
def _add_nodegoup_filters(self, query, filters):
if filters is None:
filters = {}
possible_filters = ["name", "node_count", "node_addresses",
"role", "is_default"]
filter_names = set(filters).intersection(possible_filters)
filter_dict = {filter_name: filters[filter_name]
for filter_name in filter_names}
query = query.filter_by(**filter_dict)
return query
def create_nodegroup(self, values):
if not values.get('uuid'):
values['uuid'] = uuidutils.generate_uuid()
nodegroup = models.NodeGroup()
nodegroup.update(values)
try:
nodegroup.save()
except db_exc.DBDuplicateEntry:
raise exception.NodeGroupAlreadyExists(
cluster_id=values['cluster_id'], name=values['name'])
return nodegroup
def destroy_nodegroup(self, cluster_id, nodegroup_id):
session = get_session()
with session.begin():
query = model_query(models.NodeGroup, session=session)
query = add_identity_filter(query, nodegroup_id)
query = query.filter_by(cluster_id=cluster_id)
try:
query.one()
except NoResultFound:
raise exception.NodeGroupNotFound(nodegroup=nodegroup_id)
query.delete()
def update_nodegroup(self, cluster_id, nodegroup_id, values):
return self._do_update_nodegroup(cluster_id, nodegroup_id, values)
def _do_update_nodegroup(self, cluster_id, nodegroup_id, values):
session = get_session()
with session.begin():
query = model_query(models.NodeGroup, session=session)
query = add_identity_filter(query, nodegroup_id)
query = query.filter_by(cluster_id=cluster_id)
try:
ref = query.with_lockmode('update').one()
except NoResultFound:
raise exception.NodeGroupNotFound(nodegroup=nodegroup_id)
ref.update(values)
return ref
def get_nodegroup_by_id(self, context, cluster_id, nodegroup_id):
query = model_query(models.NodeGroup)
if not context.is_admin:
query = query.filter_by(project_id=context.project_id)
query = query.filter_by(cluster_id=cluster_id)
query = query.filter_by(id=nodegroup_id)
try:
return query.one()
except NoResultFound:
raise exception.NodeGroupNotFound(nodegroup=nodegroup_id)
def get_nodegroup_by_uuid(self, context, cluster_id, nodegroup_uuid):
query = model_query(models.NodeGroup)
if not context.is_admin:
query = query.filter_by(project_id=context.project_id)
query = query.filter_by(cluster_id=cluster_id)
query = query.filter_by(uuid=nodegroup_uuid)
try:
return query.one()
except NoResultFound:
raise exception.NodeGroupNotFound(nodegroup=nodegroup_uuid)
def get_nodegroup_by_name(self, context, cluster_id, nodegroup_name):
query = model_query(models.NodeGroup)
if not context.is_admin:
query = query.filter_by(project_id=context.project_id)
query = query.filter_by(cluster_id=cluster_id)
query = query.filter_by(name=nodegroup_name)
try:
return query.one()
except MultipleResultsFound:
raise exception.Conflict('Multiple nodegroups exist with same '
'name. Please use the nodegroup uuid '
'instead.')
except NoResultFound:
raise exception.NodeGroupNotFound(nodegroup=nodegroup_name)
def list_cluster_nodegroups(self, context, cluster_id, filters=None,
limit=None, marker=None, sort_key=None,
sort_dir=None):
query = model_query(models.NodeGroup)
if not context.is_admin:
query = query.filter_by(project_id=context.project_id)
query = self._add_nodegoup_filters(query, filters)
query = query.filter_by(cluster_id=cluster_id)
return _paginate_query(models.NodeGroup, limit, marker,
sort_key, sort_dir, query)
def get_cluster_nodegroup_count(self, context, cluster_id):
query = model_query(models.NodeGroup)
if not context.is_admin:
query = query.filter_by(project_id=context.project_id)
query = query.filter_by(cluster_id=cluster_id)
return query.count()

View File

@ -260,3 +260,33 @@ class Federation(Base):
status = Column(String(20))
status_reason = Column(Text)
properties = Column(JSONEncodedDict)
class NodeGroup(Base):
"""Represents a NodeGroup."""
__tablename__ = 'nodegroup'
__table_args__ = (
schema.UniqueConstraint('uuid', name='uniq_nodegroup0uuid'),
schema.UniqueConstraint(
'cluster_id', 'name',
name='uniq_nodegroup0cluster_id0name'),
table_args()
)
id = Column(Integer, primary_key=True)
uuid = Column(String(36))
name = Column(String(255))
cluster_id = Column(String(255))
project_id = Column(String(255))
docker_volume_size = Column(Integer(), nullable=True)
labels = Column(JSONEncodedDict, nullable=True)
flavor_id = Column(String(255), nullable=True)
image_id = Column(String(255), nullable=True)
node_addresses = Column(JSONEncodedList, nullable=True)
node_count = Column(Integer())
role = Column(String(255))
# NOTE(ttsiouts) We have to define the min and
# max number of nodes for each nodegroup
max_node_count = Column(Integer())
min_node_count = Column(Integer())
is_default = Column(Boolean, default=False)

View File

@ -17,6 +17,7 @@ from magnum.objects import cluster
from magnum.objects import cluster_template
from magnum.objects import federation
from magnum.objects import magnum_service
from magnum.objects import nodegroup
from magnum.objects import quota
from magnum.objects import stats
from magnum.objects import x509keypair
@ -30,6 +31,7 @@ X509KeyPair = x509keypair.X509KeyPair
Certificate = certificate.Certificate
Stats = stats.Stats
Federation = federation.Federation
NodeGroup = nodegroup.NodeGroup
__all__ = (Cluster,
ClusterTemplate,
MagnumService,
@ -37,5 +39,6 @@ __all__ = (Cluster,
Certificate,
Stats,
Quota,
Federation
Federation,
NodeGroup
)

218
magnum/objects/nodegroup.py Normal file
View File

@ -0,0 +1,218 @@
# Copyright (c) 2018 European Organization for Nuclear Research.
# 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 oslo_utils import strutils
from oslo_utils import uuidutils
from oslo_versionedobjects import fields
from magnum.db import api as dbapi
from magnum.objects import base
@base.MagnumObjectRegistry.register
class NodeGroup(base.MagnumPersistentObject, base.MagnumObject,
base.MagnumObjectDictCompat):
# Version 1.0: Initial version
VERSION = '1.0'
dbapi = dbapi.get_instance()
fields = {
'id': fields.IntegerField(),
'uuid': fields.UUIDField(),
'name': fields.StringField(),
'cluster_id': fields.StringField(),
'project_id': fields.StringField(),
'docker_volume_size': fields.IntegerField(nullable=True),
'labels': fields.DictOfStringsField(nullable=True),
'flavor_id': fields.StringField(nullable=True),
'image_id': fields.StringField(nullable=True),
'node_addresses': fields.ListOfStringsField(nullable=True),
'node_count': fields.IntegerField(nullable=False, default=1),
'role': fields.StringField(),
'max_node_count': fields.IntegerField(nullable=True),
'min_node_count': fields.IntegerField(nullable=False, default=1),
'is_default': fields.BooleanField(default=False)
}
@staticmethod
def _from_db_object(nodegroup, db_nodegroup):
"""Converts a database entity to a formal object."""
for field in nodegroup.fields:
nodegroup[field] = db_nodegroup[field]
nodegroup.obj_reset_changes()
return nodegroup
@staticmethod
def _from_db_object_list(db_objects, cls, context):
"""Converts a list of database entities to a list of formal objects."""
return [NodeGroup._from_db_object(cls(context), obj)
for obj in db_objects]
@base.remotable_classmethod
def get(cls, context, cluster_id, nodegroup_id):
"""Find a nodegroup based on its id or uuid and return a NodeGroup.
:param cluster_id: the of id a cluster.
:param nodegroup_id: the id of a nodegroup.
:param context: Security context
:returns: a :class:`NodeGroup` object.
"""
if strutils.is_int_like(nodegroup_id):
return cls.get_by_id(context, cluster_id, nodegroup_id)
elif uuidutils.is_uuid_like(nodegroup_id):
return cls.get_by_uuid(context, cluster_id, nodegroup_id)
else:
return cls.get_by_name(context, cluster_id, nodegroup_id)
@base.remotable_classmethod
def get_by_id(cls, context, cluster, id_):
"""Find a nodegroup based on its integer id and return a NodeGroup.
:param cluster: the id of a cluster.
:param id_: the id of a nodegroup.
:param context: Security context
:returns: a :class:`NodeGroup` object.
"""
db_nodegroup = cls.dbapi.get_nodegroup_by_id(context, cluster, id_)
nodegroup = NodeGroup._from_db_object(cls(context), db_nodegroup)
return nodegroup
@base.remotable_classmethod
def get_by_uuid(cls, context, cluster, uuid):
"""Find a nodegroup based on uuid and return a :class:`NodeGroup`.
:param cluster: the id of a cluster.
:param uuid: the uuid of a nodegroup.
:param context: Security context
:returns: a :class:`NodeGroup` object.
"""
db_nodegroup = cls.dbapi.get_nodegroup_by_uuid(context, cluster, uuid)
nodegroup = NodeGroup._from_db_object(cls(context), db_nodegroup)
return nodegroup
@base.remotable_classmethod
def get_by_name(cls, context, cluster, name):
"""Find a nodegroup based on name and return a NodeGroup object.
:param cluster: the id of a cluster.
:param name: the logical name of a nodegroup.
:param context: Security context
:returns: a :class:`NodeGroup` object.
"""
db_nodegroup = cls.dbapi.get_nodegroup_by_name(context, cluster, name)
nodegroup = NodeGroup._from_db_object(cls(context), db_nodegroup)
return nodegroup
@base.remotable_classmethod
def get_count_all(cls, context, cluster_id):
"""Get count of nodegroups in cluster.
:param context: The security context
:param cluster_id: The uuid of the cluster
:returns: Count of nodegroups in the cluster.
"""
return cls.dbapi.get_cluster_nodegroup_count(context, cluster_id)
@base.remotable_classmethod
def list(cls, context, cluster, limit=None, marker=None,
sort_key=None, sort_dir=None, filters=None):
"""Return a list of NodeGroup objects.
:param context: Security context.
:param cluster: The cluster uuid or name
:param limit: maximum number of resources to return in a single result.
:param marker: pagination marker for large data sets.
:param sort_key: column to sort results by.
:param sort_dir: direction to sort. "asc" or "desc".
:param filters: filter dict, can includes 'name', 'node_count',
'stack_id', 'node_addresses',
'status'(should be a status list).
:returns: a list of :class:`NodeGroup` objects.
"""
db_nodegroups = cls.dbapi.list_cluster_nodegroups(context, cluster,
limit=limit,
marker=marker,
sort_key=sort_key,
sort_dir=sort_dir,
filters=filters)
return NodeGroup._from_db_object_list(db_nodegroups, cls, context)
@base.remotable
def create(self, context=None):
"""Create a nodegroup record in the DB.
:param context: Security context
"""
values = self.obj_get_changes()
db_nodegroup = self.dbapi.create_nodegroup(values)
self._from_db_object(self, db_nodegroup)
@base.remotable
def destroy(self, context=None):
"""Delete the NodeGroup from the DB.
:param context: Security context.
"""
self.dbapi.destroy_nodegroup(self.cluster_id, self.uuid)
self.obj_reset_changes()
@base.remotable
def save(self, context=None):
"""Save updates to this NodeGroup.
Updates will be made column by column based on the result
of self.what_changed().
:param context: Security context.
"""
updates = self.obj_get_changes()
self.dbapi.update_nodegroup(self.cluster_id, self.uuid, updates)
self.obj_reset_changes()
@base.remotable
def refresh(self, context=None):
"""Loads updates for this NodeGroup.
Loads a NodeGroup with the same uuid from the database and
checks for updated attributes. Updates are applied from
the loaded NogeGroup column by column, if there are any updates.
:param context: Security context.
"""
current = self.__class__.get_by_uuid(self._context,
cluster=self.cluster_id,
uuid=self.uuid)
for field in self.fields:
if self.obj_attr_is_set(field) and self[field] != current[field]:
self[field] = current[field]
@base.remotable_classmethod
def update_nodegroup(cls, context, cluster_id, nodegroup_id, values):
"""Updates a NodeGroup.
:param context: Security context.
:param cluster_id:
:param nodegroup_id:
:param values: a dictionary with the changed values
"""
current = cls.get(context, cluster_id, nodegroup_id)
db_nodegroup = cls.dbapi.update_nodegroup(cluster_id, current.uuid,
values)
return NodeGroup._from_db_object(cls(context), db_nodegroup)

View File

@ -0,0 +1,228 @@
# Copyright (c) 2018 European Organization for Nuclear Research.
# 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 NodeGroups via the DB API"""
from oslo_utils import uuidutils
from magnum.common import exception
from magnum.tests.unit.db import base
from magnum.tests.unit.db import utils
class DbNodeGroupTestCase(base.DbTestCase):
def test_create_nodegroup(self):
utils.create_test_nodegroup()
def test_create_nodegroup_already_exists(self):
utils.create_test_nodegroup()
self.assertRaises(exception.NodeGroupAlreadyExists,
utils.create_test_nodegroup)
def test_create_nodegroup_same_name_same_cluster(self):
# NOTE(ttsiouts): Don't allow the same name for nodegroups
# in the same cluster.
nodegroup = utils.create_test_nodegroup()
new = {
'name': nodegroup.name,
'id': nodegroup.id + 8,
'cluster_id': nodegroup.cluster_id
}
self.assertRaises(exception.NodeGroupAlreadyExists,
utils.create_test_nodegroup, **new)
def test_create_nodegroup_same_name_different_cluster(self):
# NOTE(ttsiouts): Verify nodegroups with the same name
# but in different clusters are allowed.
nodegroup = utils.create_test_nodegroup()
new = {
'name': nodegroup.name,
'id': nodegroup.id + 8,
'cluster_id': 'fake-cluster-uuid',
'uuid': 'fake-nodegroup-uuid',
'project_id': nodegroup.project_id,
}
try:
utils.create_test_nodegroup(**new)
except Exception:
# Something went wrong, just fail the testcase
self.assertTrue(False)
def test_get_nodegroup_by_id(self):
nodegroup = utils.create_test_nodegroup()
res = self.dbapi.get_nodegroup_by_id(self.context,
nodegroup.cluster_id,
nodegroup.id)
self.assertEqual(nodegroup.id, res.id)
self.assertEqual(nodegroup.uuid, res.uuid)
def test_get_nodegroup_by_name(self):
nodegroup = utils.create_test_nodegroup()
res = self.dbapi.get_nodegroup_by_name(self.context,
nodegroup.cluster_id,
nodegroup.name)
self.assertEqual(nodegroup.name, res.name)
self.assertEqual(nodegroup.uuid, res.uuid)
def test_get_cluster_by_uuid(self):
nodegroup = utils.create_test_nodegroup()
res = self.dbapi.get_nodegroup_by_uuid(self.context,
nodegroup.cluster_id,
nodegroup.uuid)
self.assertEqual(nodegroup.id, res.id)
self.assertEqual(nodegroup.uuid, res.uuid)
def test_get_nodegroup_that_does_not_exist(self):
# Create a cluster with no nodegroups
cluster = utils.create_test_cluster()
self.assertRaises(exception.NodeGroupNotFound,
self.dbapi.get_nodegroup_by_id,
self.context, cluster.uuid, 100)
self.assertRaises(exception.NodeGroupNotFound,
self.dbapi.get_nodegroup_by_uuid,
self.context, cluster.uuid,
'12345678-9999-0000-aaaa-123456789012')
self.assertRaises(exception.NodeGroupNotFound,
self.dbapi.get_nodegroup_by_name,
self.context, cluster.uuid, 'not_found')
def test_get_nodegroups_in_cluster(self):
uuids_in_cluster = []
uuids_not_in_cluster = []
cluster = utils.create_test_cluster(uuid=uuidutils.generate_uuid())
for i in range(2):
ng = utils.create_test_nodegroup(uuid=uuidutils.generate_uuid(),
name='test%(id)s' % {'id': i},
cluster_id=cluster.uuid)
uuids_in_cluster.append(ng.uuid)
for i in range(2):
ng = utils.create_test_nodegroup(uuid=uuidutils.generate_uuid(),
name='test%(id)s' % {'id': i},
cluster_id='fake_cluster')
uuids_not_in_cluster.append(ng.uuid)
res = self.dbapi.list_cluster_nodegroups(self.context, cluster.uuid)
res_uuids = [r.uuid for r in res]
self.assertEqual(sorted(uuids_in_cluster), sorted(res_uuids))
for uuid in uuids_not_in_cluster:
self.assertNotIn(uuid, res_uuids)
def test_get_cluster_list_sorted(self):
uuids = []
cluster = utils.create_test_cluster(uuid=uuidutils.generate_uuid())
for i in range(5):
ng = utils.create_test_nodegroup(uuid=uuidutils.generate_uuid(),
name='test%(id)s' % {'id': i},
cluster_id=cluster.uuid)
uuids.append(ng.uuid)
res = self.dbapi.list_cluster_nodegroups(self.context, cluster.uuid,
sort_key='uuid')
res_uuids = [r.uuid for r in res]
self.assertEqual(sorted(uuids), res_uuids)
self.assertRaises(exception.InvalidParameterValue,
self.dbapi.list_cluster_nodegroups,
self.context,
cluster.uuid,
sort_key='not-there')
def test_get_nodegroup_list_with_filters(self):
cluster_dict = utils.get_test_cluster(
id=1, uuid=uuidutils.generate_uuid())
cluster = self.dbapi.create_cluster(cluster_dict)
group1 = utils.create_test_nodegroup(
name='group-one',
cluster_id=cluster.uuid,
flavor_id=1,
uuid=uuidutils.generate_uuid(),
node_count=1)
group2 = utils.create_test_nodegroup(
name='group-two',
cluster_id=cluster.uuid,
flavor_id=1,
uuid=uuidutils.generate_uuid(),
node_count=1)
group3 = utils.create_test_nodegroup(
name='group-four',
cluster_id=cluster.uuid,
flavor_id=2,
uuid=uuidutils.generate_uuid(),
node_count=3)
filters = {'name': 'group-one'}
res = self.dbapi.list_cluster_nodegroups(
self.context, cluster.uuid, filters=filters)
self.assertEqual([group1.id], [r.id for r in res])
filters = {'node_count': 1}
res = self.dbapi.list_cluster_nodegroups(
self.context, cluster.uuid, filters=filters)
self.assertEqual([group1.id, group2.id], [r.id for r in res])
filters = {'flavor_id': 2, 'node_count': 3}
res = self.dbapi.list_cluster_nodegroups(
self.context, cluster.uuid, filters=filters)
self.assertEqual([group3.id], [r.id for r in res])
filters = {'name': 'group-five'}
res = self.dbapi.list_cluster_nodegroups(
self.context, cluster.uuid, filters=filters)
self.assertEqual([], [r.id for r in res])
def test_destroy_nodegroup(self):
cluster = utils.create_test_cluster()
nodegroup = utils.create_test_nodegroup()
self.assertEqual(nodegroup.uuid, self.dbapi.get_nodegroup_by_uuid(
self.context, cluster.uuid, nodegroup.uuid).uuid)
self.dbapi.destroy_nodegroup(cluster.uuid, nodegroup.uuid)
self.assertRaises(exception.NodeGroupNotFound,
self.dbapi.get_nodegroup_by_uuid,
self.context, cluster.uuid, nodegroup.uuid)
self.assertRaises(exception.NodeGroupNotFound,
self.dbapi.destroy_nodegroup, cluster.uuid,
nodegroup.uuid)
def test_destroy_nodegroup_by_uuid(self):
cluster = utils.create_test_cluster()
nodegroup = utils.create_test_nodegroup()
self.assertIsNotNone(self.dbapi.get_nodegroup_by_uuid(self.context,
cluster.uuid,
nodegroup.uuid))
self.dbapi.destroy_nodegroup(cluster.uuid, nodegroup.uuid)
self.assertRaises(exception.NodeGroupNotFound,
self.dbapi.get_nodegroup_by_uuid, self.context,
cluster.uuid, nodegroup.uuid)
def test_destroy_cluster_by_uuid_that_does_not_exist(self):
self.assertRaises(exception.NodeGroupNotFound,
self.dbapi.destroy_nodegroup, 'c_uuid',
'12345678-9999-0000-aaaa-123456789012')
def test_update_cluster(self):
nodegroup = utils.create_test_nodegroup()
old_flavor = nodegroup.flavor_id
new_flavor = 5
self.assertNotEqual(old_flavor, new_flavor)
res = self.dbapi.update_nodegroup(nodegroup.cluster_id, nodegroup.id,
{'flavor_id': new_flavor})
self.assertEqual(new_flavor, res.flavor_id)
def test_update_nodegroup_not_found(self):
uuid = uuidutils.generate_uuid()
self.assertRaises(exception.NodeGroupNotFound,
self.dbapi.update_nodegroup, "c_uuid", uuid,
{'node_count': 5})

View File

@ -267,3 +267,40 @@ def create_test_federation(**kw):
del federation['id']
dbapi = db_api.get_instance()
return dbapi.create_federation(federation)
def get_test_nodegroup(**kw):
return {
'id': kw.get('id', 12),
'uuid': kw.get('uuid', '483203a3-dbee-4a9c-9d65-9820512f4df8'),
'name': kw.get('name', 'nodegroup1'),
'cluster_id': kw.get('cluster_id',
'5d12f6fd-a196-4bf0-ae4c-1f639a523a52'),
'project_id': kw.get('project_id', 'fake_project'),
'docker_volume_size': kw.get('docker_volume_size'),
'labels': kw.get('labels'),
'flavor_id': kw.get('flavor_id', None),
'image_id': kw.get('image_id', None),
'node_addresses': kw.get('node_addresses', ['172.17.2.4']),
'node_count': kw.get('node_count', 3),
'role': kw.get('role', 'worker'),
'max_node_count': kw.get('max_node_count', None),
'min_node_count': kw.get('min_node_count', 1),
'is_default': kw.get('is_default', True),
'created_at': kw.get('created_at'),
'updated_at': kw.get('updated_at')
}
def create_test_nodegroup(**kw):
"""Create test nodegroup entry in DB and return federation DB object.
:param kw: kwargs with overriding values for nodegroup attributes.
:return: Test nodegroup DB object.
"""
nodegroup = get_test_nodegroup(**kw)
# Let DB generate ID if it isn't specified explicitly
if 'id' not in kw:
del nodegroup['id']
dbapi = db_api.get_instance()
return dbapi.create_nodegroup(nodegroup)

View File

@ -0,0 +1,168 @@
# Copyright (c) 2018 European Organization for Nuclear Research.
# 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.
import mock
from oslo_utils import uuidutils
from testtools.matchers import HasLength
from magnum import objects
from magnum.tests.unit.db import base
from magnum.tests.unit.db import utils
class TestNodeGroupObject(base.DbTestCase):
def setUp(self):
super(TestNodeGroupObject, self).setUp()
self.fake_nodegroup = utils.get_test_nodegroup()
self.fake_nodegroup['docker_volume_size'] = 3
self.fake_nodegroup['labels'] = {}
def test_get_by_id(self):
nodegroup_id = self.fake_nodegroup['id']
cluster_id = self.fake_nodegroup['cluster_id']
with mock.patch.object(self.dbapi, 'get_nodegroup_by_id',
autospec=True) as mock_get_nodegroup:
mock_get_nodegroup.return_value = self.fake_nodegroup
nodegroup = objects.NodeGroup.get(self.context, cluster_id,
nodegroup_id)
mock_get_nodegroup.assert_called_once_with(self.context,
cluster_id,
nodegroup_id)
self.assertEqual(self.context, nodegroup._context)
def test_get_by_uuid(self):
uuid = self.fake_nodegroup['uuid']
cluster_id = self.fake_nodegroup['cluster_id']
with mock.patch.object(self.dbapi, 'get_nodegroup_by_uuid',
autospec=True) as mock_get_nodegroup:
mock_get_nodegroup.return_value = self.fake_nodegroup
nodegroup = objects.NodeGroup.get(self.context, cluster_id, uuid)
mock_get_nodegroup.assert_called_once_with(self.context,
cluster_id, uuid)
self.assertEqual(self.context, nodegroup._context)
def test_get_by_name(self):
name = self.fake_nodegroup['name']
cluster_id = self.fake_nodegroup['cluster_id']
with mock.patch.object(self.dbapi, 'get_nodegroup_by_name',
autospec=True) as mock_get_nodegroup:
mock_get_nodegroup.return_value = self.fake_nodegroup
nodegroup = objects.NodeGroup.get(self.context, cluster_id, name)
mock_get_nodegroup.assert_called_once_with(self.context,
cluster_id, name)
self.assertEqual(self.context, nodegroup._context)
def test_list(self):
cluster_id = self.fake_nodegroup['cluster_id']
with mock.patch.object(self.dbapi, 'list_cluster_nodegroups',
autospec=True) as mock_get_list:
mock_get_list.return_value = [self.fake_nodegroup]
nodegroups = objects.NodeGroup.list(self.context, cluster_id)
self.assertEqual(1, mock_get_list.call_count)
mock_get_list.assert_called_once_with(
self.context, cluster_id, limit=None, marker=None,
filters=None, sort_dir=None, sort_key=None)
self.assertThat(nodegroups, HasLength(1))
self.assertIsInstance(nodegroups[0], objects.NodeGroup)
self.assertEqual(self.context, nodegroups[0]._context)
def test_list_with_filters(self):
cluster_id = self.fake_nodegroup['cluster_id']
with mock.patch.object(self.dbapi, 'list_cluster_nodegroups',
autospec=True) as mock_get_list:
mock_get_list.return_value = [self.fake_nodegroup]
filters = {'name': self.fake_nodegroup['name']}
nodegroups = objects.NodeGroup.list(self.context, cluster_id,
filters=filters)
self.assertEqual(1, mock_get_list.call_count)
mock_get_list.assert_called_once_with(
self.context, cluster_id, limit=None, marker=None,
filters=filters, sort_dir=None, sort_key=None)
self.assertThat(nodegroups, HasLength(1))
self.assertIsInstance(nodegroups[0], objects.NodeGroup)
self.assertEqual(self.context, nodegroups[0]._context)
def test_create(self):
with mock.patch.object(self.dbapi, 'create_nodegroup',
autospec=True) as mock_create_nodegroup:
mock_create_nodegroup.return_value = self.fake_nodegroup
nodegroup = objects.NodeGroup(self.context, **self.fake_nodegroup)
nodegroup.create()
mock_create_nodegroup.assert_called_once_with(self.fake_nodegroup)
self.assertEqual(self.context, nodegroup._context)
def test_destroy(self):
uuid = self.fake_nodegroup['uuid']
cluster_id = self.fake_nodegroup['cluster_id']
with mock.patch.object(self.dbapi, 'get_nodegroup_by_uuid',
autospec=True) as mock_get_nodegroup:
mock_get_nodegroup.return_value = self.fake_nodegroup
with mock.patch.object(self.dbapi, 'destroy_nodegroup',
autospec=True) as mock_destroy_nodegroup:
nodegroup = objects.NodeGroup.get_by_uuid(self.context,
cluster_id,
uuid)
nodegroup.destroy()
mock_get_nodegroup.assert_called_once_with(self.context,
cluster_id,
uuid)
mock_destroy_nodegroup.assert_called_once_with(cluster_id,
uuid)
self.assertEqual(self.context, nodegroup._context)
def test_save(self):
uuid = self.fake_nodegroup['uuid']
cluster_id = self.fake_nodegroup['cluster_id']
with mock.patch.object(self.dbapi, 'get_nodegroup_by_uuid',
autospec=True) as mock_get_nodegroup:
mock_get_nodegroup.return_value = self.fake_nodegroup
with mock.patch.object(self.dbapi, 'update_nodegroup',
autospec=True) as mock_update_nodegroup:
nodegroup = objects.NodeGroup.get_by_uuid(self.context,
cluster_id,
uuid)
nodegroup.node_count = 10
nodegroup.save()
mock_get_nodegroup.assert_called_once_with(self.context,
cluster_id,
uuid)
expected_changes = {
'node_count': 10,
}
mock_update_nodegroup.assert_called_once_with(
cluster_id, uuid, expected_changes)
self.assertEqual(self.context, nodegroup._context)
def test_refresh(self):
uuid = self.fake_nodegroup['uuid']
cluster_id = self.fake_nodegroup['cluster_id']
new_uuid = uuidutils.generate_uuid()
returns = [dict(self.fake_nodegroup, uuid=uuid),
dict(self.fake_nodegroup, uuid=new_uuid)]
expected = [mock.call(self.context, cluster_id, uuid),
mock.call(self.context, cluster_id, uuid)]
with mock.patch.object(self.dbapi, 'get_nodegroup_by_uuid',
side_effect=returns,
autospec=True) as mock_get_nodegroup:
nodegroup = objects.NodeGroup.get_by_uuid(self.context,
cluster_id,
uuid)
self.assertEqual(uuid, nodegroup.uuid)
nodegroup.refresh()
self.assertEqual(new_uuid, nodegroup.uuid)
self.assertEqual(expected, mock_get_nodegroup.call_args_list)
self.assertEqual(self.context, nodegroup._context)

View File

@ -363,7 +363,8 @@ object_data = {
'MagnumService': '1.0-2d397ec59b0046bd5ec35cd3e06efeca',
'Stats': '1.0-73a1cd6e3c0294c932a66547faba216c',
'Quota': '1.0-94e100aebfa88f7d8428e007f2049c18',
'Federation': '1.0-166da281432b083f0e4b851336e12e20'
'Federation': '1.0-166da281432b083f0e4b851336e12e20',
'NodeGroup': '1.0-75e1378a800040312c59f89546a51d74'
}