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:
liusheng 2017-07-26 14:34:40 +08:00
parent 2d1379a72d
commit 3951ab3064
7 changed files with 385 additions and 0 deletions

View File

@ -447,4 +447,12 @@ class CannotDisassociateAutoAssignedFloatingIP(Forbidden):
class FloatingIpNotAssociated(Invalid):
_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

View File

@ -243,3 +243,24 @@ class Connection(object):
def aggregate_metadata_delete(self, context, key):
"""Delete aggregate metadata by 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)

View File

@ -218,3 +218,43 @@ def upgrade():
)
op.create_index('aggregate_metadata_key_idx', 'aggregate_metadata',
['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'
)

View File

@ -26,6 +26,7 @@ from oslo_utils import strutils
from oslo_utils import timeutils
from oslo_utils import uuidutils
from sqlalchemy import or_
from sqlalchemy import orm
from sqlalchemy.orm import contains_eager
from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy.orm import joinedload
@ -892,6 +893,151 @@ class Connection(api.Connection):
raise exception.AggregateMetadataNotFound(
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):
return model_query(context, models.Flavors). \

View File

@ -309,3 +309,61 @@ class Aggregate(Base):
@property
def metadetails(self):
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]

View 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)

View File

@ -205,3 +205,35 @@ def create_test_aggregate(context={}, **kw):
dbapi = db_api.get_instance()
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)