Add server groups db models and api
This change add ServerGroup, ServerGroupPolicy and ServerGroupMember models, and add several basic DB interfaces. Partially Implements: bp server-group-api-extension Change-Id: I86e17798718fc26e1fd6a1fe1c33ee408738b6da
This commit is contained in:
@@ -447,4 +447,12 @@ class CannotDisassociateAutoAssignedFloatingIP(Forbidden):
|
|||||||
class FloatingIpNotAssociated(Invalid):
|
class FloatingIpNotAssociated(Invalid):
|
||||||
_msg_fmt = _("Floating IP: %(floatingip)s is not associated")
|
_msg_fmt = _("Floating IP: %(floatingip)s is not associated")
|
||||||
|
|
||||||
|
|
||||||
|
class ServerGroupNotFound(NotFound):
|
||||||
|
_msg_fmt = _("Server group %(group_uuid)s could not be found.")
|
||||||
|
|
||||||
|
|
||||||
|
class ServerGroupExists(Conflict):
|
||||||
|
_msg_fmt = _("Sever group %(group_uuid)s already exists.")
|
||||||
|
|
||||||
ObjectActionError = obj_exc.ObjectActionError
|
ObjectActionError = obj_exc.ObjectActionError
|
||||||
|
|||||||
@@ -243,3 +243,24 @@ class Connection(object):
|
|||||||
def aggregate_metadata_delete(self, context, key):
|
def aggregate_metadata_delete(self, context, key):
|
||||||
"""Delete aggregate metadata by key."""
|
"""Delete aggregate metadata by key."""
|
||||||
return IMPL.aggregate_metadata_delete(context, key)
|
return IMPL.aggregate_metadata_delete(context, key)
|
||||||
|
|
||||||
|
def server_group_create(self, context, values, policies=None,
|
||||||
|
members=None):
|
||||||
|
"""Create a new group."""
|
||||||
|
return IMPL.server_group_create(context, values, policies, members)
|
||||||
|
|
||||||
|
def server_group_get(self, context, group_uuid):
|
||||||
|
"""Get a specific group by uuid."""
|
||||||
|
return IMPL.server_group_get(context, group_uuid)
|
||||||
|
|
||||||
|
def server_group_update(self, context, group_uuid, values):
|
||||||
|
"""Update the attributes of a group."""
|
||||||
|
return IMPL.server_group_update(context, group_uuid, values)
|
||||||
|
|
||||||
|
def server_group_delete(self, context, group_uuid):
|
||||||
|
"""Delete a group."""
|
||||||
|
return IMPL.server_group_delete(context, group_uuid)
|
||||||
|
|
||||||
|
def server_group_get_all(self, context, project_id=None):
|
||||||
|
"""Get server groups."""
|
||||||
|
return IMPL.server_group_get_all(context, project_id)
|
||||||
|
|||||||
@@ -218,3 +218,43 @@ def upgrade():
|
|||||||
)
|
)
|
||||||
op.create_index('aggregate_metadata_key_idx', 'aggregate_metadata',
|
op.create_index('aggregate_metadata_key_idx', 'aggregate_metadata',
|
||||||
['key'], unique=False)
|
['key'], unique=False)
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'server_groups',
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('id', sa.Integer(), primary_key=True, nullable=False),
|
||||||
|
sa.Column('uuid', sa.String(length=36), nullable=True),
|
||||||
|
sa.Column('name', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('user_id', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('project_id', sa.String(length=255), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('uuid', name='uniq_server_groups0uuid'),
|
||||||
|
mysql_engine='InnoDB',
|
||||||
|
mysql_charset='utf8'
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
'server_group_policy',
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('id', sa.Integer(), primary_key=True, nullable=False),
|
||||||
|
sa.Column('policy', sa.String(length=36), nullable=True),
|
||||||
|
sa.Column('group_id', sa.String(length=255), nullable=False),
|
||||||
|
sa.Index('server_group_policy_policy_idx', 'policy'),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
mysql_engine='InnoDB',
|
||||||
|
mysql_charset='utf8'
|
||||||
|
)
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'server_group_member',
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime()),
|
||||||
|
sa.Column('id', sa.Integer(), primary_key=True, nullable=False),
|
||||||
|
sa.Column('server_uuid', sa.String(length=36), nullable=True),
|
||||||
|
sa.Column('group_id', sa.Integer, sa.ForeignKey('server_groups.id'),
|
||||||
|
nullable=False),
|
||||||
|
sa.Index('server_group_member_server_idx', 'server_uuid'),
|
||||||
|
mysql_engine='InnoDB',
|
||||||
|
mysql_charset='utf8'
|
||||||
|
)
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from oslo_utils import strutils
|
|||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
from sqlalchemy import or_
|
from sqlalchemy import or_
|
||||||
|
from sqlalchemy import orm
|
||||||
from sqlalchemy.orm import contains_eager
|
from sqlalchemy.orm import contains_eager
|
||||||
from sqlalchemy.orm.exc import NoResultFound
|
from sqlalchemy.orm.exc import NoResultFound
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
@@ -892,6 +893,151 @@ class Connection(api.Connection):
|
|||||||
raise exception.AggregateMetadataNotFound(
|
raise exception.AggregateMetadataNotFound(
|
||||||
key=key, aggregate_id=aggregate_id)
|
key=key, aggregate_id=aggregate_id)
|
||||||
|
|
||||||
|
@oslo_db_api.retry_on_deadlock
|
||||||
|
def _server_group_policies_add(self, context, group_id, policies,
|
||||||
|
delete=False):
|
||||||
|
all_policies = set(policies)
|
||||||
|
query = model_query(context, models.ServerGroupPolicy).filter_by(
|
||||||
|
group_id=group_id)
|
||||||
|
if delete:
|
||||||
|
with _session_for_write():
|
||||||
|
query.filter(~models.ServerGroupPolicy.policy.in_(
|
||||||
|
all_policies)).delete(synchronize_session=False)
|
||||||
|
query = query.filter(models.ServerGroupPolicy.policy.in_(all_policies))
|
||||||
|
already_existing = set()
|
||||||
|
for policy_ref in query.all():
|
||||||
|
already_existing.add(policy_ref.policy)
|
||||||
|
with _session_for_write() as session:
|
||||||
|
for policy in all_policies:
|
||||||
|
if policy in already_existing:
|
||||||
|
continue
|
||||||
|
policy_ref = models.ServerGroupPolicy()
|
||||||
|
policy_ref.update({'policy': policy,
|
||||||
|
'group_id': group_id})
|
||||||
|
session.add(policy_ref)
|
||||||
|
return policies
|
||||||
|
|
||||||
|
@oslo_db_api.retry_on_deadlock
|
||||||
|
def _server_group_members_add(self, context, group_id, members,
|
||||||
|
delete=False):
|
||||||
|
all_members = set(members)
|
||||||
|
query = model_query(context, models.ServerGroupMember).filter_by(
|
||||||
|
group_id=group_id)
|
||||||
|
if delete:
|
||||||
|
with _session_for_write():
|
||||||
|
query.filter(~models.ServerGroupMember.server_uuid.in_(
|
||||||
|
all_members)).delete(synchronize_session=False)
|
||||||
|
query = query.filter(models.ServerGroupMember.server_uuid.in_(
|
||||||
|
all_members))
|
||||||
|
already_existing = set()
|
||||||
|
for member_ref in query.all():
|
||||||
|
already_existing.add(member_ref.server_uuid)
|
||||||
|
with _session_for_write() as session:
|
||||||
|
for server_uuid in members:
|
||||||
|
if server_uuid in already_existing:
|
||||||
|
continue
|
||||||
|
member_ref = models.ServerGroupMember()
|
||||||
|
member_ref.update({'server_uuid': server_uuid,
|
||||||
|
'group_id': group_id})
|
||||||
|
session.add(member_ref)
|
||||||
|
return members
|
||||||
|
|
||||||
|
@oslo_db_api.retry_on_deadlock
|
||||||
|
def server_group_create(self, context, values, policies=None,
|
||||||
|
members=None):
|
||||||
|
"""Create a new group."""
|
||||||
|
uuid = values.get('uuid', None)
|
||||||
|
if uuid is None:
|
||||||
|
uuid = uuidutils.generate_uuid()
|
||||||
|
values['uuid'] = uuid
|
||||||
|
with _session_for_write() as session:
|
||||||
|
try:
|
||||||
|
server_group_ref = models.ServerGroup()
|
||||||
|
server_group_ref.update(values)
|
||||||
|
server_group_ref.save(session)
|
||||||
|
except db_exc.DBDuplicateEntry:
|
||||||
|
raise exception.ServerGroupExists(group_uuid=values['uuid'])
|
||||||
|
if policies:
|
||||||
|
self._server_group_policies_add(context, server_group_ref.id,
|
||||||
|
policies)
|
||||||
|
if members:
|
||||||
|
self._server_group_members_add(context, server_group_ref.id,
|
||||||
|
members)
|
||||||
|
|
||||||
|
return self.server_group_get(context, uuid)
|
||||||
|
|
||||||
|
def server_group_get(self, context, group_uuid):
|
||||||
|
"""Get a specific group by uuid."""
|
||||||
|
columns_to_join = ['_policies', '_members']
|
||||||
|
query = model_query(context, models.ServerGroup)
|
||||||
|
for c in columns_to_join:
|
||||||
|
query = query.options(orm.joinedload(c))
|
||||||
|
query = query.filter(models.ServerGroup.uuid == group_uuid)
|
||||||
|
group = query.first()
|
||||||
|
if not group:
|
||||||
|
raise exception.ServerGroupNotFound(group_uuid=group_uuid)
|
||||||
|
return group
|
||||||
|
|
||||||
|
@oslo_db_api.retry_on_deadlock
|
||||||
|
def server_group_update(self, context, group_uuid, values):
|
||||||
|
"""Update the attributes of a group.
|
||||||
|
|
||||||
|
If values contains a metadata key, it updates the aggregate metadata
|
||||||
|
too. Similarly for the policies and members.
|
||||||
|
"""
|
||||||
|
group_query = model_query(context, models.ServerGroup).filter_by(
|
||||||
|
uuid=group_uuid)
|
||||||
|
group = group_query.first()
|
||||||
|
if not group:
|
||||||
|
raise exception.ServerGroupNotFound(group_uuid=group_uuid)
|
||||||
|
|
||||||
|
policies = values.get('policies')
|
||||||
|
if policies is not None:
|
||||||
|
self._server_group_policies_add(context,
|
||||||
|
group.id,
|
||||||
|
values.pop('policies'),
|
||||||
|
True)
|
||||||
|
members = values.get('members')
|
||||||
|
if members is not None:
|
||||||
|
self._server_group_members_add(context,
|
||||||
|
group.id,
|
||||||
|
values.pop('members'),
|
||||||
|
True)
|
||||||
|
with _session_for_write():
|
||||||
|
group_query.update(values)
|
||||||
|
|
||||||
|
if policies:
|
||||||
|
values['policies'] = policies
|
||||||
|
if members:
|
||||||
|
values['members'] = members
|
||||||
|
|
||||||
|
@oslo_db_api.retry_on_deadlock
|
||||||
|
def server_group_delete(self, context, group_uuid):
|
||||||
|
"""Delete a group."""
|
||||||
|
query = model_query(context, models.ServerGroup).filter_by(
|
||||||
|
uuid=group_uuid)
|
||||||
|
group = query.first()
|
||||||
|
if not group:
|
||||||
|
raise exception.ServerGroupNotFound(group_uuid=group_uuid)
|
||||||
|
group_id = group.id
|
||||||
|
with _session_for_write():
|
||||||
|
query.delete()
|
||||||
|
# Delete policies and members
|
||||||
|
instance_models = [models.ServerGroupPolicy,
|
||||||
|
models.ServerGroupMember]
|
||||||
|
for model in instance_models:
|
||||||
|
model_query(context, model).filter_by(group_id=group_id).delete()
|
||||||
|
|
||||||
|
def server_group_get_all(self, context, project_id=None):
|
||||||
|
"""Get all groups."""
|
||||||
|
columns_to_join = ['_policies', '_members']
|
||||||
|
query = model_query(context, models.ServerGroup)
|
||||||
|
for c in columns_to_join:
|
||||||
|
query = query.options(orm.joinedload(c))
|
||||||
|
if project_id is not None:
|
||||||
|
query = query.filter_by(project_id=project_id)
|
||||||
|
return query.all()
|
||||||
|
|
||||||
|
|
||||||
def _get_id_from_flavor_query(context, type_id):
|
def _get_id_from_flavor_query(context, type_id):
|
||||||
return model_query(context, models.Flavors). \
|
return model_query(context, models.Flavors). \
|
||||||
|
|||||||
@@ -309,3 +309,61 @@ class Aggregate(Base):
|
|||||||
@property
|
@property
|
||||||
def metadetails(self):
|
def metadetails(self):
|
||||||
return {m.key: m.value for m in self._metadata}
|
return {m.key: m.value for m in self._metadata}
|
||||||
|
|
||||||
|
|
||||||
|
class ServerGroupMember(Base):
|
||||||
|
"""Represents the members for a server group."""
|
||||||
|
__tablename__ = 'server_group_member'
|
||||||
|
__table_args__ = (
|
||||||
|
Index('server_group_member_server_idx', 'server_uuid'),
|
||||||
|
)
|
||||||
|
id = Column(Integer, primary_key=True, nullable=False)
|
||||||
|
server_uuid = Column(String(255))
|
||||||
|
group_id = Column(Integer, ForeignKey('server_groups.id'),
|
||||||
|
nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class ServerGroupPolicy(Base):
|
||||||
|
"""Represents the policy type for a server group."""
|
||||||
|
__tablename__ = 'server_group_policy'
|
||||||
|
__table_args__ = (
|
||||||
|
Index('server_group_policy_policy_idx', 'policy'),
|
||||||
|
)
|
||||||
|
id = Column(Integer, primary_key=True, nullable=False)
|
||||||
|
policy = Column(String(255))
|
||||||
|
group_id = Column(Integer, ForeignKey('server_groups.id'),
|
||||||
|
nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class ServerGroup(Base):
|
||||||
|
"""Represents a server group.
|
||||||
|
|
||||||
|
A group will maintain a collection of servers and the relationship
|
||||||
|
between them.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = 'server_groups'
|
||||||
|
__table_args__ = (
|
||||||
|
schema.UniqueConstraint("uuid",
|
||||||
|
name="uniq_server_groups0uuid"),
|
||||||
|
)
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
user_id = Column(String(255))
|
||||||
|
project_id = Column(String(255))
|
||||||
|
uuid = Column(String(36), nullable=False)
|
||||||
|
name = Column(String(255))
|
||||||
|
_policies = orm.relationship(
|
||||||
|
ServerGroupPolicy,
|
||||||
|
primaryjoin='ServerGroup.id == ServerGroupPolicy.group_id')
|
||||||
|
_members = orm.relationship(
|
||||||
|
ServerGroupMember,
|
||||||
|
primaryjoin='ServerGroup.id == ServerGroupMember.group_id')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def policies(self):
|
||||||
|
return [p.policy for p in self._policies]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def members(self):
|
||||||
|
return [m.server_uuid for m in self._members]
|
||||||
|
|||||||
80
mogan/tests/unit/db/test_server_group.py
Normal file
80
mogan/tests/unit/db/test_server_group.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# Copyright 2017 Huawei 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 groups via the DB API"""
|
||||||
|
|
||||||
|
from mogan.common import exception
|
||||||
|
from mogan.tests.unit.db import base
|
||||||
|
from mogan.tests.unit.db import utils
|
||||||
|
|
||||||
|
|
||||||
|
class DbServerGroupTestCase(base.DbTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(DbServerGroupTestCase, self).setUp()
|
||||||
|
self.server_group = utils.create_test_server_group(
|
||||||
|
context={},
|
||||||
|
name='test_server_group',
|
||||||
|
user_id='fake_user_id',
|
||||||
|
project_id='fake_project_id',
|
||||||
|
policies=['policy1', 'policy2'],
|
||||||
|
members=['server1', 'server2'])
|
||||||
|
|
||||||
|
def test_server_group_create(self):
|
||||||
|
sg = utils.create_test_aggregate(name='testing')
|
||||||
|
self.assertEqual('testing', sg.name)
|
||||||
|
|
||||||
|
def test_server_group_get(self):
|
||||||
|
server_group = self.dbapi.server_group_get(
|
||||||
|
context={}, group_uuid=self.server_group.uuid)
|
||||||
|
self.assertEqual('test_server_group', server_group.name)
|
||||||
|
self.assertEqual('fake_user_id', server_group.user_id)
|
||||||
|
self.assertEqual('fake_project_id', server_group.project_id)
|
||||||
|
self.assertItemsEqual(['policy1', 'policy2'], server_group.policies)
|
||||||
|
self.assertItemsEqual(['server1', 'server2'], server_group.members)
|
||||||
|
|
||||||
|
def test_server_group_update(self):
|
||||||
|
update_values = {'name': 'new_test_name',
|
||||||
|
'policies': ['policy2', 'policy3'],
|
||||||
|
'members': ['server2', 'server3']
|
||||||
|
}
|
||||||
|
self.dbapi.server_group_update({}, self.server_group.uuid,
|
||||||
|
update_values)
|
||||||
|
server_group = self.dbapi.server_group_get(
|
||||||
|
context={}, group_uuid=self.server_group.uuid)
|
||||||
|
self.assertEqual('new_test_name', server_group.name)
|
||||||
|
self.assertEqual('fake_user_id', server_group.user_id)
|
||||||
|
self.assertEqual('fake_project_id', server_group.project_id)
|
||||||
|
self.assertItemsEqual(['policy2', 'policy3'], server_group.policies)
|
||||||
|
self.assertItemsEqual(['server2', 'server3'], server_group.members)
|
||||||
|
|
||||||
|
def test_server_group_delete(self):
|
||||||
|
self.dbapi.server_group_delete(context={},
|
||||||
|
group_uuid=self.server_group.uuid)
|
||||||
|
self.assertRaises(exception.ServerGroupNotFound,
|
||||||
|
self.dbapi.server_group_get,
|
||||||
|
self.context,
|
||||||
|
self.server_group.uuid)
|
||||||
|
|
||||||
|
def test_server_group_get_all(self):
|
||||||
|
server_groups = self.dbapi.server_group_get_all(context={})
|
||||||
|
self.assertIsInstance(server_groups, list)
|
||||||
|
self.assertEqual(1, len(server_groups))
|
||||||
|
self.assertEqual('test_server_group', server_groups[0].name)
|
||||||
|
self.assertEqual('fake_user_id', server_groups[0].user_id)
|
||||||
|
self.assertEqual('fake_project_id', server_groups[0].project_id)
|
||||||
|
self.assertItemsEqual(['policy1', 'policy2'],
|
||||||
|
server_groups[0].policies)
|
||||||
|
self.assertItemsEqual(['server1', 'server2'],
|
||||||
|
server_groups[0].members)
|
||||||
@@ -205,3 +205,35 @@ def create_test_aggregate(context={}, **kw):
|
|||||||
dbapi = db_api.get_instance()
|
dbapi = db_api.get_instance()
|
||||||
|
|
||||||
return dbapi.aggregate_create(context, agg)
|
return dbapi.aggregate_create(context, agg)
|
||||||
|
|
||||||
|
|
||||||
|
def get_test_server_group(**kw):
|
||||||
|
return {
|
||||||
|
'id': kw.get('id', 123),
|
||||||
|
'name': kw.get('name', 'test'),
|
||||||
|
'uuid': kw.get('uuid', uuidutils.generate_uuid()),
|
||||||
|
'user_id': kw.get('user_id', '2b846ce623754aa1b2ae3f99ff297cb8'),
|
||||||
|
'project_id': kw.get('project_id', '9851baf53c75452dad7951bca7b3dbac'),
|
||||||
|
'policies': kw.get('policies', ["anti-affinity", "affinity"]),
|
||||||
|
'members': kw.get('members', ['server1', 'server2']),
|
||||||
|
'updated_at': kw.get('updated_at'),
|
||||||
|
'created_at': kw.get('updated_at')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def create_test_server_group(context={}, **kw):
|
||||||
|
"""Create test server fault entry in DB and return the DB object.
|
||||||
|
|
||||||
|
Function to be used to create test Server Fault objects in the database.
|
||||||
|
|
||||||
|
:param context: The request context, for access checks.
|
||||||
|
:param kw: kwargs with overriding values for server fault's attributes.
|
||||||
|
:returns: Test Server Fault DB object.
|
||||||
|
|
||||||
|
"""
|
||||||
|
server_fault = get_test_server_group(**kw)
|
||||||
|
dbapi = db_api.get_instance()
|
||||||
|
members = server_fault.pop('members')
|
||||||
|
policies = server_fault.pop('policies')
|
||||||
|
return dbapi.server_group_create(context, server_fault, policies=policies,
|
||||||
|
members=members)
|
||||||
|
|||||||
Reference in New Issue
Block a user