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:
parent
2d1379a72d
commit
3951ab3064
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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'
|
||||
)
|
||||
|
@ -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). \
|
||||
|
@ -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]
|
||||
|
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()
|
||||
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user