Added support for Security Groups via a new extension.

- Added migrate changes for new database artifacts.
- Added models for Security Groups and Security Group Rules.
- Definition and addition of Controllers for API.
- Views for Security Groups and Security Group rules.
- Extended taskmanager to use newly created  Security Group on instance
  creation, and to clean it up on deletion if extension is enabled
- Added new flag to conf to enable "reddwarf_security_groups_support"
- Integration tests.

Implements blueprint: security-groups

Change-Id: I44053359c12e53a9e549bd334e628ecd249d8ba0
This commit is contained in:
Nikhil Manchanda 2013-02-25 15:58:30 -08:00
parent 01a7590b58
commit 8d89bae2ad
21 changed files with 1079 additions and 19 deletions

View File

@ -81,6 +81,12 @@ agent_call_high_timeout = 150
# Reboot time out for instances
reboot_time_out = 60
# Reddwarf Security Groups for Instances
reddwarf_security_groups_support = True
reddwarf_security_group_rule_protocol = tcp
reddwarf_security_group_rule_port = 3306
# ============ notifer queue kombu connection options ========================
notifier_queue_hostname = localhost

View File

@ -108,6 +108,9 @@ common_opts = [
cfg.IntOpt('http_put_rate', default=200),
cfg.BoolOpt('hostname_require_ipv4', default=True,
help="Require user hostnames to be IPv4 addresses."),
cfg.BoolOpt('reddwarf_security_groups_support', default=True),
cfg.StrOpt('reddwarf_security_group_rule_protocol', default='tcp'),
cfg.IntOpt('reddwarf_security_group_rule_port', default=3306),
]

View File

@ -23,7 +23,6 @@ from reddwarf.openstack.common.gettextutils import _
from webob import exc
ClientConnectionError = openstack_exception.ClientConnectionError
ProcessExecutionError = processutils.ProcessExecutionError
DatabaseMigrationError = openstack_exception.DatabaseMigrationError
@ -230,3 +229,23 @@ class BackupCreationError(ReddwarfError):
class BackupUpdateError(ReddwarfError):
message = _("Unable to update Backup table in db")
class SecurityGroupCreationError(ReddwarfError):
message = _("Failed to create Security Group.")
class SecurityGroupDeletionError(ReddwarfError):
message = _("Failed to delete Security Group.")
class SecurityGroupRuleCreationError(ReddwarfError):
message = _("Failed to create Security Group Rule.")
class SecurityGroupRuleDeletionError(ReddwarfError):
message = _("Failed to delete Security Group Rule.")

View File

@ -86,6 +86,12 @@ CUSTOM_SERIALIZER_METADATA = {
'database': {'name': ''},
'user': {'name': '', 'password': ''},
'account': {'id': ''},
'security_group': {'id': '', 'name': '', 'description': '', 'user': '',
'tenant_id': ''},
'security_group_rule': {'id': '', 'group_id': '', 'protocol': '',
'from_port': '', 'to_port': '', 'cidr': ''},
'security_group_instance_association': {'id': '', 'security_group_id': '',
'instance_id': ''},
# mgmt/host
'host': {'instanceCount': '', 'name': '', 'usedRAM': '', 'totalRAM': '',
'percentUsed': ''},
@ -104,6 +110,7 @@ CUSTOM_SERIALIZER_METADATA = {
'version': '', 'vmRss': '', 'fdSize': ''},
#mgmt/instance/root
'root_history': {'enabled': '', 'id': '', 'user': ''},
}
@ -373,7 +380,7 @@ class Controller(object):
exception.ModelNotFoundError,
exception.UserNotFound,
exception.DatabaseNotFound,
exception.QuotaResourceUnknown
exception.QuotaResourceUnknown,
],
webob.exc.HTTPConflict: [],
webob.exc.HTTPRequestEntityTooLarge: [

View File

@ -29,7 +29,10 @@ class DatabaseModelBase(models.ModelBase):
@classmethod
def create(cls, **values):
values['id'] = utils.generate_uuid()
if 'id' not in values:
values['id'] = utils.generate_uuid()
if hasattr(cls, 'deleted') and 'deleted' not in values:
values['deleted'] = False
values['created'] = utils.utcnow()
instance = cls(**values).save()
if not instance.is_valid():
@ -40,6 +43,10 @@ class DatabaseModelBase(models.ModelBase):
def db_api(self):
return get_db_api()
@property
def preserve_on_delete(self):
return hasattr(self, 'deleted') and hasattr(self, 'deleted_at')
def save(self):
if not self.is_valid():
raise exception.InvalidModelError(errors=self.errors)
@ -52,7 +59,13 @@ class DatabaseModelBase(models.ModelBase):
self['updated'] = utils.utcnow()
LOG.debug(_("Deleting %s: %s") %
(self.__class__.__name__, self.__dict__))
return self.db_api.delete(self)
if self.preserve_on_delete:
self['deleted_at'] = utils.utcnow()
self['deleted'] = True
return self.db_api.save(self)
else:
return self.db_api.delete(self)
def update(self, **values):
for key in values:

View File

@ -46,6 +46,13 @@ def map(engine, models):
Table('reservations', meta, autoload=True))
orm.mapper(models['backups'],
Table('backups', meta, autoload=True))
orm.mapper(models['security_group'],
Table('security_groups', meta, autoload=True))
orm.mapper(models['security_group_rule'],
Table('security_group_rules', meta, autoload=True))
orm.mapper(models['security_group_instance_association'],
Table('security_group_instance_associations', meta,
autoload=True))
def mapping_exists(model):

View File

@ -0,0 +1,98 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# 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 sqlalchemy import ForeignKey
from sqlalchemy.schema import Column
from sqlalchemy.schema import MetaData
from reddwarf.db.sqlalchemy.migrate_repo.schema import create_tables
from reddwarf.db.sqlalchemy.migrate_repo.schema import drop_tables
from reddwarf.db.sqlalchemy.migrate_repo.schema import Boolean
from reddwarf.db.sqlalchemy.migrate_repo.schema import Integer
from reddwarf.db.sqlalchemy.migrate_repo.schema import String
from reddwarf.db.sqlalchemy.migrate_repo.schema import DateTime
from reddwarf.db.sqlalchemy.migrate_repo.schema import Table
meta = MetaData()
security_groups = Table(
'security_groups',
meta,
Column('id', String(length=36), primary_key=True, nullable=False),
Column('name', String(length=255)),
Column('description', String(length=255)),
Column('user', String(length=255)),
Column('tenant_id', String(length=255)),
Column('created', DateTime()),
Column('updated', DateTime()),
Column('deleted', Boolean(), default=0),
Column('deleted_at', DateTime()),
)
security_group_instance_associations = Table(
'security_group_instance_associations',
meta,
Column('id', String(length=36), primary_key=True, nullable=False),
Column('security_group_id', String(length=36),
ForeignKey('security_groups.id', ondelete="CASCADE",
onupdate="CASCADE")),
Column('instance_id', String(length=36),
ForeignKey('instances.id', ondelete="CASCADE",
onupdate="CASCADE")),
Column('created', DateTime()),
Column('updated', DateTime()),
Column('deleted', Boolean(), default=0),
Column('deleted_at', DateTime()),
)
security_group_rules = Table(
'security_group_rules',
meta,
Column('id', String(length=36), primary_key=True, nullable=False),
Column('group_id', String(length=36),
ForeignKey('security_groups.id', ondelete="CASCADE",
onupdate="CASCADE")),
Column('parent_group_id', String(length=36),
ForeignKey('security_groups.id', ondelete="CASCADE",
onupdate="CASCADE")),
Column('protocol', String(length=255)),
Column('from_port', Integer()),
Column('to_port', Integer()),
Column('cidr', String(length=255)),
Column('created', DateTime()),
Column('updated', DateTime()),
Column('deleted', Boolean(), default=0),
Column('deleted_at', DateTime()),
)
def upgrade(migrate_engine):
meta.bind = migrate_engine
instances = Table(
'instances',
meta,
autoload=True,
)
create_tables([security_groups, security_group_rules,
security_group_instance_associations])
def downgrade(migrate_engine):
meta.bind = migrate_engine
drop_tables([security_group_instance_associations,
security_group_rules, security_groups])

View File

@ -47,6 +47,7 @@ def configure_db(options, models_mapper=None):
from reddwarf.guestagent import models as agent_models
from reddwarf.quota import models as quota_models
from reddwarf.backup import models as backup_models
from reddwarf.extensions.security_group import models as secgrp_models
model_modules = [
base_models,
@ -55,6 +56,7 @@ def configure_db(options, models_mapper=None):
agent_models,
quota_models,
backup_models,
secgrp_models,
]
models = {}

View File

@ -0,0 +1,73 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# 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 reddwarf.openstack.common import log as logging
from reddwarf.common import extensions
from reddwarf.common import wsgi
from reddwarf.common import cfg
from reddwarf.extensions.security_group import service
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
# The Extensions module from openstack common expects the classname of the
# extension to be loaded to be the exact same as the filename, except with
# a capital first letter. That's the reason this class has such a funky name.
class Security_group(extensions.ExtensionsDescriptor):
def get_name(self):
return "SecurityGroup"
def get_description(self):
return "Security Group related operations such as list \
security groups and manage security group rules."
def get_alias(self):
return "SecurityGroup"
def get_namespace(self):
return "http://TBD"
def get_updated(self):
return "2012-02-26T17:25:27-08:00"
def get_resources(self):
resources = []
serializer = wsgi.ReddwarfResponseSerializer(
body_serializers={'application/xml':
wsgi.ReddwarfXMLDictSerializer()})
if CONF.reddwarf_security_groups_support:
security_groups = extensions.ResourceExtension(
'{tenant_id}/security_groups',
service.SecurityGroupController(),
deserializer=wsgi.ReddwarfRequestDeserializer(),
serializer=serializer)
resources.append(security_groups)
security_group_rules = extensions.ResourceExtension(
'{tenant_id}/security_group_rules',
service.SecurityGroupRuleController(),
deserializer=wsgi.ReddwarfRequestDeserializer(),
serializer=serializer)
resources.append(security_group_rules)
return resources

View File

@ -0,0 +1,17 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# 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.
#

View File

@ -0,0 +1,264 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# 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.
#
"""
Model classes for Security Groups and Security Group Rules on instances.
"""
import reddwarf.common.remote
from reddwarf.common import cfg
from reddwarf.common import exception
from reddwarf.db.models import DatabaseModelBase
from reddwarf.common.models import NovaRemoteModelBase
from reddwarf.openstack.common import log as logging
from reddwarf.openstack.common.gettextutils import _
from novaclient import exceptions as nova_exceptions
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
def persisted_models():
return {
'security_group': SecurityGroup,
'security_group_rule': SecurityGroupRule,
'security_group_instance_association':
SecurityGroupInstanceAssociation,
}
class SecurityGroup(DatabaseModelBase):
_data_fields = ['id', 'name', 'description', 'user', 'tenant_id',
'created', 'updated', 'deleted', 'deleted_at']
@classmethod
def create_sec_group(cls, name, description, context):
try:
remote_sec_group = RemoteSecurityGroup.create(name,
description,
context)
if not remote_sec_group:
raise exception.SecurityGroupCreationError(
"Failed to create Security Group")
else:
return cls.create(
id=remote_sec_group.data()['id'],
name=name,
description=description,
user=context.user,
tenant_id=context.tenant)
except exception.SecurityGroupCreationError, e:
LOG.exception("Failed to create remote security group")
raise e
@classmethod
def create_for_instance(cls, instance_id, context):
# Create a new security group
name = _("SecGroup_%s") % instance_id
description = \
_("Default Security Group For DBaaS Instance <%s>") % instance_id
sec_group = cls.create_sec_group(name, description, context)
# Currently this locked down by default, since we don't create any
# default security group rules for the security group.
# Create security group instance association
SecurityGroupInstanceAssociation.create(
security_group_id=sec_group["id"],
instance_id=instance_id)
return sec_group
@classmethod
def get_security_group_by_id_or_instance_id(self, id, tenant_id):
try:
return SecurityGroup.find_by(id=id,
tenant_id=tenant_id,
deleted=False)
except exception.ModelNotFoundError:
return SecurityGroupInstanceAssociation.\
get_security_group_by_instance_id(id)
def get_rules(self):
return SecurityGroupRule.find_all(group_id=self.id,
deleted=False)
def delete(self, context):
try:
sec_group_rules = self.get_rules()
if sec_group_rules:
for rule in sec_group_rules:
rule.delete(context)
RemoteSecurityGroup.delete(self.id, context)
super(SecurityGroup, self).delete()
except exception.ReddwarfError:
LOG.exception('Failed to delete security group')
raise exception.ReddwarfError("Failed to delete Security Group")
@classmethod
def delete_for_instance(cls, instance_id, context):
association = SecurityGroupInstanceAssociation.find_by(
instance_id=instance_id,
deleted=False)
if association:
sec_group = association.get_security_group()
sec_group.delete(context)
association.delete()
class SecurityGroupRule(DatabaseModelBase):
_data_fields = ['id', 'parent_group_id', 'protocol', 'from_port',
'to_port', 'cidr', 'group_id', 'created', 'updated',
'deleted', 'deleted_at']
@classmethod
def create_sec_group_rule(cls, sec_group, protocol, from_port,
to_port, cidr, context):
try:
remote_rule_id = RemoteSecurityGroup.add_rule(
sec_group_id=sec_group['id'],
protocol=protocol,
from_port=from_port,
to_port=to_port,
cidr=cidr,
context=context)
if not remote_rule_id:
raise exception.SecurityGroupRuleCreationError(
"Failed to create Security Group Rule")
else:
# Create db record
return cls.create(
id=remote_rule_id,
protocol=protocol,
from_port=from_port,
to_port=to_port,
cidr=cidr,
group_id=sec_group['id'])
except exception.SecurityGroupRuleCreationError, e:
LOG.exception("Failed to create remote security group")
raise e
def get_security_group(self, tenant_id):
return SecurityGroup.find_by(id=self.group_id,
tenant_id=tenant_id,
deleted=False)
def delete(self, context):
try:
# Delete Remote Security Group Rule
RemoteSecurityGroup.delete_rule(self.id, context)
super(SecurityGroupRule, self).delete()
except exception.ReddwarfError:
LOG.exception('Failed to delete security group')
raise exception.SecurityGroupRuleDeletionError(
"Failed to delete Security Group")
class SecurityGroupInstanceAssociation(DatabaseModelBase):
_data_fields = ['id', 'security_group_id', 'instance_id',
'created', 'updated', 'deleted', 'deleted_at']
def get_security_group(self):
return SecurityGroup.find_by(id=self.security_group_id,
deleted=False)
@classmethod
def get_security_group_by_instance_id(cls, id):
association = SecurityGroupInstanceAssociation.find_by(
instance_id=id,
deleted=False)
return association.get_security_group()
class RemoteSecurityGroup(NovaRemoteModelBase):
_data_fields = ['id', 'name', 'description', 'rules']
def __init__(self, security_group=None, id=None, context=None):
if id is None and security_group is None:
msg = "Security Group does not have id defined!"
raise exception.InvalidModelError(msg)
elif security_group is None:
try:
client = reddwarf.common.remote.create_nova_client(context)
self._data_object = client.security_groups.get(id)
except nova_exceptions.NotFound, e:
raise exception.NotFound(id=id)
except nova_exceptions.ClientException, e:
raise exception.ReddwarfError(str(e))
else:
self._data_object = security_group
@classmethod
def create(cls, name, description, context):
"""Creates a new Security Group"""
client = reddwarf.common.remote.create_nova_client(context)
try:
sec_group = client.security_groups.create(name=name,
description=description)
except nova_exceptions.ClientException, e:
LOG.exception('Failed to create remote security group')
raise exception.SecurityGroupCreationError(str(e))
return RemoteSecurityGroup(security_group=sec_group)
@classmethod
def delete(cls, sec_group_id, context):
client = reddwarf.common.remote.create_nova_client(context)
try:
client.security_groups.delete(sec_group_id)
except nova_exceptions.ClientException, e:
LOG.exception('Failed to delete remote security group')
raise exception.SecurityGroupDeletionError(str(e))
@classmethod
def add_rule(cls, sec_group_id, protocol, from_port,
to_port, cidr, context):
client = reddwarf.common.remote.create_nova_client(context)
try:
sec_group_rule = client.security_group_rules.create(
parent_group_id=sec_group_id,
ip_protocol=protocol,
from_port=from_port,
to_port=to_port,
cidr=cidr)
return sec_group_rule.id
except nova_exceptions.ClientException, e:
LOG.exception('Failed to add rule to remote security group')
raise exception.SecurityGroupRuleCreationError(str(e))
@classmethod
def delete_rule(cls, sec_group_rule_id, context):
client = reddwarf.common.remote.create_nova_client(context)
try:
client.security_group_rules.delete(sec_group_rule_id)
except nova_exceptions.ClientException, e:
LOG.exception('Failed to delete rule to remote security group')
raise exception.SecurityGroupRuleDeletionError(str(e))

View File

@ -0,0 +1,117 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# 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 reddwarf.common import exception
from reddwarf.common import wsgi
from reddwarf.common import cfg
from reddwarf.extensions.security_group import models
from reddwarf.extensions.security_group import views
from reddwarf.openstack.common import log as logging
from reddwarf.openstack.common.gettextutils import _
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
class SecurityGroupController(wsgi.Controller):
"""Controller for security groups functionality"""
def index(self, req, tenant_id):
"""Return all security groups tied to a particular tenant_id."""
LOG.debug("Index() called with %s" % (tenant_id))
sec_groups = models.SecurityGroup().find_all(tenant_id=tenant_id,
deleted=False)
# Construct the mapping from Security Groups to Security Group Rules
rules_map = dict([(g.id, g.get_rules()) for g in sec_groups])
return wsgi.Result(
views.SecurityGroupsView(sec_groups,
rules_map,
req, tenant_id).list(), 200)
def show(self, req, tenant_id, id):
"""Return a single security group."""
LOG.debug("Show() called with %s, %s" % (tenant_id, id))
sec_group = \
models.SecurityGroup.get_security_group_by_id_or_instance_id(
id, tenant_id)
return wsgi.Result(
views.SecurityGroupView(sec_group,
sec_group.get_rules(),
req, tenant_id).show(), 200)
class SecurityGroupRuleController(wsgi.Controller):
"""Controller for security group rule functionality"""
def delete(self, req, tenant_id, id):
LOG.debug("Delete Security Group Rule called %s, %s" % (tenant_id, id))
context = req.environ[wsgi.CONTEXT_KEY]
sec_group_rule = models.SecurityGroupRule.find_by(id=id, deleted=False)
sec_group = sec_group_rule.get_security_group(tenant_id)
if sec_group is None:
LOG.error("Attempting to delete Group Rule that does not exist or "
"does not belong to tenant %s" % tenant_id)
raise exception.Forbidden("Unauthorized")
sec_group_rule.delete(context)
return wsgi.Result(None, 204)
def create(self, req, body, tenant_id):
LOG.debug("Creating a Security Group Rule for tenant '%s'" % tenant_id)
context = req.environ[wsgi.CONTEXT_KEY]
self._validate_create_body(body)
sec_group_id = body['security_group_rule']['group_id']
sec_group = models.SecurityGroup.find_by(id=sec_group_id,
tenant_id=tenant_id,
deleted=False)
sec_group_rule = models.SecurityGroupRule.create_sec_group_rule(
sec_group,
CONF.reddwarf_security_group_rule_protocol,
CONF.reddwarf_security_group_rule_port,
CONF.reddwarf_security_group_rule_port,
body['security_group_rule']['cidr'],
context)
resultView = views.SecurityGroupRulesView(sec_group_rule,
req,
tenant_id).create()
return wsgi.Result(resultView, 201)
def _validate_create_body(self, body):
try:
# TODO: Add some better validation here around ports,
# protocol, and cidr values.
body['security_group_rule']
body['security_group_rule']['group_id']
body['security_group_rule']['cidr']
except KeyError as e:
LOG.error(_("Create Security Group Rules Required field(s) "
"- %s") % e)
raise exception.SecurityGroupRuleCreationError(
"Required element/key - %s was not specified" % e)

View File

@ -0,0 +1,124 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# 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 reddwarf.openstack.common import log as logging
import os
LOG = logging.getLogger(__name__)
def _base_url(req):
return req.application_url
class SecurityGroupView(object):
def __init__(self, secgroup, rules, req, tenant_id):
self.secgroup = secgroup
self.rules = rules
self.request = req
self.tenant_id = tenant_id
def _build_links(self):
"""Build the links for the secgroup"""
base_url = _base_url(self.request)
href = os.path.join(base_url, self.tenant_id,
"security-groups", str(self.secgroup['id']))
links = [
{
'rel': 'self',
'href': href
}
]
return links
def _build_rules(self):
rules = []
if self.rules is None:
return rules
for rule in self.rules:
rules.append({'id': str(rule['id']),
'protocol': rule['protocol'],
'from_port': rule['from_port'],
'to_port': rule['to_port'],
'cidr': rule['cidr'],
})
return rules
def data(self):
return {"id": self.secgroup['id'],
"name": self.secgroup['name'],
"description": self.secgroup['description'],
"rules": self._build_rules(),
"links": self._build_links(),
"created": self.secgroup['created'],
"updated": self.secgroup['updated']
}
def show(self):
return {"security_group": self.data()}
def create(self):
return self.show()
class SecurityGroupsView(object):
def __init__(self, secgroups, rules_dict, req, tenant_id):
self.secgroups = secgroups
self.rules = rules_dict
self.request = req
self.tenant_id = tenant_id
def list(self):
groups_data = []
for secgroup in self.secgroups:
rules = \
self.rules[secgroup['id']] if self.rules is not None else None
groups_data.append(SecurityGroupView(secgroup,
rules,
self.request,
self.tenant_id).data())
return {"security_groups": groups_data}
class SecurityGroupRulesView(object):
def __init__(self, rule, req, tenant_id):
self.rule = rule
self.request = req
self.tenant_id = tenant_id
def _build_create(self):
return {"security_group_rule":
{"id": str(self.rule['id']),
"security_group_id": self.rule['group_id'],
"protocol": self.rule['protocol'],
"from_port": self.rule['from_port'],
"to_port": self.rule['to_port'],
"cidr": self.rule['cidr'],
"created": self.rule['created']
}
}
def create(self):
return self._build_create()

View File

@ -29,6 +29,7 @@ from reddwarf.common.remote import create_dns_client
from reddwarf.common.remote import create_guest_client
from reddwarf.common.remote import create_nova_client
from reddwarf.common.remote import create_nova_volume_client
from reddwarf.extensions.security_group.models import SecurityGroup
from reddwarf.db import models as dbmodels
from reddwarf.instance.tasks import InstanceTask
from reddwarf.instance.tasks import InstanceTasks
@ -372,6 +373,10 @@ class BaseInstance(SimpleInstance):
time_now = datetime.now()
self.update_db(deleted=True, deleted_at=time_now,
task_status=InstanceTasks.NONE)
# Delete associated security group
if CONF.reddwarf_security_groups_support:
SecurityGroup.delete_for_instance(self.db_info.id,
self.context)
@property
def guest(self):
@ -428,6 +433,7 @@ class Instance(BuiltInstance):
databases, users, service_type, volume_size):
def _create_resources():
client = create_nova_client(context)
security_groups = None
try:
flavor = client.flavors.get(flavor_id)
except nova_exceptions.NotFound:
@ -450,10 +456,17 @@ class Instance(BuiltInstance):
db_info.hostname = hostname
db_info.save()
if CONF.reddwarf_security_groups_support:
security_group = SecurityGroup.create_for_instance(
db_info.id,
context)
security_groups = [security_group["name"]]
task_api.API(context).create_instance(db_info.id, name, flavor_id,
flavor.ram, image_id,
databases, users,
service_type, volume_size)
service_type, volume_size,
security_groups)
return SimpleInstance(context, db_info, service_status)

View File

@ -89,9 +89,11 @@ class API(ManagerAPI):
self._cast("delete_instance", instance_id=instance_id)
def create_instance(self, instance_id, name, flavor_id, flavor_ram,
image_id, databases, users, service_type, volume_size):
image_id, databases, users, service_type, volume_size,
security_groups):
LOG.debug("Making async call to create instance %s " % instance_id)
self._cast("create_instance", instance_id=instance_id, name=name,
flavor_id=flavor_id, flavor_ram=flavor_ram,
image_id=image_id, databases=databases, users=users,
service_type=service_type, volume_size=volume_size)
service_type=service_type, volume_size=volume_size,
security_groups=security_groups)

View File

@ -70,8 +70,8 @@ class Manager(periodic_task.PeriodicTasks):
def create_instance(self, context, instance_id, name, flavor_id,
flavor_ram, image_id, databases, users, service_type,
volume_size):
volume_size, security_groups):
instance_tasks = FreshInstanceTasks.load(context, instance_id)
instance_tasks.create_instance(flavor_id, flavor_ram, image_id,
databases, users, service_type,
volume_size)
volume_size, security_groups)

View File

@ -58,17 +58,20 @@ use_nova_server_volume = CONF.use_nova_server_volume
class FreshInstanceTasks(FreshInstance):
def create_instance(self, flavor_id, flavor_ram, image_id,
databases, users, service_type, volume_size):
databases, users, service_type, volume_size,
security_groups):
if use_nova_server_volume:
server, volume_info = self._create_server_volume(
flavor_id,
image_id,
security_groups,
service_type,
volume_size)
else:
server, volume_info = self._create_server_volume_individually(
flavor_id,
image_id,
security_groups,
service_type,
volume_size)
try:
@ -85,8 +88,8 @@ class FreshInstanceTasks(FreshInstance):
if not self.db_info.task_status.is_error:
self.update_db(task_status=inst_models.InstanceTasks.NONE)
def _create_server_volume(self, flavor_id, image_id, service_type,
volume_size):
def _create_server_volume(self, flavor_id, image_id, security_groups,
service_type, volume_size):
server = None
try:
nova_client = create_nova_client(self.context)
@ -99,8 +102,10 @@ class FreshInstanceTasks(FreshInstance):
volume_ref = {'size': volume_size, 'name': volume_name,
'description': volume_desc}
server = nova_client.servers.create(name, image_id, flavor_id,
files=files, volume=volume_ref)
server = nova_client.servers.create(
name, image_id, flavor_id,
files=files, volume=volume_ref,
security_groups=security_groups)
LOG.debug(_("Created new compute instance %s.") % server.id)
server_dict = server._info
@ -123,7 +128,8 @@ class FreshInstanceTasks(FreshInstance):
return server, volume_info
def _create_server_volume_individually(self, flavor_id, image_id,
service_type, volume_size):
security_groups, service_type,
volume_size):
volume_info = None
block_device_mapping = None
server = None
@ -136,8 +142,8 @@ class FreshInstanceTasks(FreshInstance):
self._log_and_raise(e, msg, err)
try:
server = self._create_server(flavor_id, image_id, service_type,
block_device_mapping)
server = self._create_server(flavor_id, image_id, security_groups,
service_type, block_device_mapping)
server_id = server.id
# Save server ID.
self.update_db(compute_instance_id=server_id)
@ -212,7 +218,7 @@ class FreshInstanceTasks(FreshInstance):
'volumes': volumes}
return volume_info
def _create_server(self, flavor_id, image_id,
def _create_server(self, flavor_id, image_id, security_groups,
service_type, block_device_mapping):
nova_client = create_nova_client(self.context)
files = {"/etc/guest_info": ("[DEFAULT]\nguest_id=%s\n"
@ -222,6 +228,7 @@ class FreshInstanceTasks(FreshInstance):
bdmap = block_device_mapping
server = nova_client.servers.create(name, image_id, flavor_id,
files=files,
security_groups=security_groups,
block_device_mapping=bdmap)
LOG.debug(_("Created new compute instance %s.") % server.id)
return server

View File

@ -31,6 +31,7 @@ GROUP_STOP = "dbaas.guest.shutdown"
GROUP_USERS = "dbaas.api.users"
GROUP_ROOT = "dbaas.api.root"
GROUP_DATABASES = "dbaas.api.databases"
GROUP_SECURITY_GROUPS = "dbaas.api.security_groups"
from datetime import datetime
from nose.plugins.skip import SkipTest
@ -51,6 +52,7 @@ from proboscis.asserts import assert_not_equal
from proboscis.asserts import assert_raises
from proboscis.asserts import assert_is
from proboscis.asserts import assert_is_none
from proboscis.asserts import assert_is_not_none
from proboscis.asserts import assert_is_not
from proboscis.asserts import assert_true
from proboscis.asserts import Check
@ -67,6 +69,7 @@ from reddwarf.tests.util import skip_if_xml
from reddwarf.tests.util import string_in_list
from reddwarf.tests.util import poll_until
from reddwarf.tests.util.check import AttrCheck
from reddwarf.tests.util.check import TypeCheck
class InstanceTestInfo(object):
@ -451,6 +454,106 @@ def assert_unprocessable(func, *args):
pass # Good
@test(depends_on_classes=[CreateInstance],
groups=[GROUP, GROUP_SECURITY_GROUPS],
runs_after_groups=[tests.PRE_INSTANCES])
class SecurityGroupsTest(object):
@before_class
def setUp(self):
self.testSecurityGroup = dbaas.security_groups.get(
instance_info.id)
self.secGroupName = "SecGroup_%s" % instance_info.id
self.secGroupDescription = \
"Default Security Group For DBaaS Instance <%s>" % instance_info.id
@test
def test_created_security_group(self):
assert_is_not_none(self.testSecurityGroup)
with TypeCheck('SecurityGroup', self.testSecurityGroup) as secGrp:
secGrp.has_field('id', basestring)
secGrp.has_field('name', basestring)
secGrp.has_field('description', basestring)
secGrp.has_field('created', basestring)
secGrp.has_field('updated', basestring)
assert_equal(self.testSecurityGroup.name, self.secGroupName)
assert_equal(self.testSecurityGroup.description,
self.secGroupDescription)
@test
def test_list_security_group(self):
securityGroupList = dbaas.security_groups.list()
assert_is_not_none(securityGroupList)
securityGroup = [x for x in securityGroupList
if x.name in self.secGroupName]
assert_is_not_none(securityGroup)
@test
def test_get_security_group(self):
securityGroup = dbaas.security_groups.get(self.testSecurityGroup.id)
assert_is_not_none(securityGroup)
assert_equal(securityGroup.name, self.secGroupName)
assert_equal(securityGroup.description, self.secGroupDescription)
@test(depends_on_classes=[SecurityGroupsTest],
groups=[GROUP, GROUP_SECURITY_GROUPS],
runs_after_groups=[tests.PRE_INSTANCES])
class SecurityGroupsRulesTest(object):
@before_class
def setUp(self):
self.testSecurityGroup = dbaas.security_groups.get(
instance_info.id)
self.secGroupName = "SecGroup_%s" % instance_info.id
self.secGroupDescription = \
"Default Security Group For DBaaS Instance <%s>" % instance_info.id
@test
def test_create_security_group_rule(self):
self.testSecurityGroupRule = dbaas.security_group_rules.create(
group_id=self.testSecurityGroup.id,
protocol="tcp",
from_port=3306,
to_port=3306,
cidr="0.0.0.0/0")
assert_is_not_none(self.testSecurityGroupRule)
with TypeCheck('SecurityGroupRule',
self.testSecurityGroupRule) as secGrpRule:
secGrpRule.has_field('id', basestring)
secGrpRule.has_field('security_group_id', basestring)
secGrpRule.has_field('protocol', basestring)
secGrpRule.has_field('cidr', basestring)
secGrpRule.has_field('from_port', int)
secGrpRule.has_field('to_port', int)
secGrpRule.has_field('created', basestring)
assert_equal(self.testSecurityGroupRule.security_group_id,
self.testSecurityGroup.id)
assert_equal(self.testSecurityGroupRule.protocol, "tcp")
assert_equal(int(self.testSecurityGroupRule.from_port), 3306)
assert_equal(int(self.testSecurityGroupRule.to_port), 3306)
assert_equal(self.testSecurityGroupRule.cidr, "0.0.0.0/0")
@test
def test_deep_list_security_group_with_rules(self):
securityGroupList = dbaas.security_groups.list()
assert_is_not_none(securityGroupList)
securityGroup = [x for x in securityGroupList
if x.name in self.secGroupName]
assert_is_not_none(securityGroup[0])
assert_equal(len(securityGroup[0].rules), 1)
@test
def test_delete_security_group_rule(self):
dbaas.security_group_rules.delete(self.testSecurityGroupRule.id)
securityGroupList = dbaas.security_groups.list()
assert_is_not_none(securityGroupList)
securityGroup = [x for x in securityGroupList
if x.name in self.secGroupName]
assert_is_not_none(securityGroup[0])
assert_equal(len(securityGroup[0].rules), 0)
@test(depends_on_classes=[CreateInstance],
groups=[GROUP,
GROUP_START,

View File

@ -256,7 +256,7 @@ class FakeServers(object):
server.owner.tenant == self.context.tenant)
def create(self, name, image_id, flavor_ref, files=None,
block_device_mapping=None, volume=None):
block_device_mapping=None, volume=None, security_groups=None):
id = "FAKE_%s" % uuid.uuid4()
if volume:
volume = self.volumes.create(volume['size'], volume['name'],
@ -644,6 +644,93 @@ class FakeRdStorages(object):
return [self.storages[name] for name in self.storages]
class FakeSecurityGroup(object):
def __init__(self, name=None, description=None, context=None):
self.name = name
self.description = description
self.id = "FAKE_SECGRP_%s" % uuid.uuid4()
self.rules = {}
def get_id(self):
return self.id
def add_rule(self, fakeSecGroupRule):
self.rules.append(fakeSecGroupRule)
return self.rules
def get_rules(self):
result = ""
for rule in self.rules:
result = result + rule.data()
return result
def data(self):
return {
'id': self.id,
'name': self.name,
'description': self.description
}
class FakeSecurityGroups(object):
def __init__(self, context=None):
self.context = context
self.securityGroups = {}
def create(self, name=None, description=None):
secGrp = FakeSecurityGroup(name, description)
self.securityGroups[secGrp.get_id()] = secGrp
return secGrp
def list(self):
pass
class FakeSecurityGroupRule(object):
def __init__(self, ip_protocol=None, from_port=None, to_port=None,
cidr=None, parent_group_id=None, context=None):
self.group_id = parent_group_id
self.protocol = ip_protocol
self.from_port = from_port
self.to_port = to_port
self.cidr = cidr
self.context = context
self.id = "FAKE_SECGRP_RULE_%s" % uuid.uuid4()
def get_id(self):
return self.id
def data(self):
return {
'id': self.id,
'group_id': self.group_id,
'protocol': self.protocol,
'from_port': self.from_port,
'to_port': self.to_port,
'cidr': self.cidr
}
class FakeSecurityGroupRules(object):
def __init__(self, context=None):
self.context = context
self.securityGroupRules = {}
def create(self, parent_group_id, ip_protocol, from_port, to_port, cidr):
secGrpRule = FakeSecurityGroupRule(ip_protocol, from_port, to_port,
cidr, parent_group_id)
self.securityGroupRules[secGrpRule.get_id()] = secGrpRule
return secGrpRule
def delete(self, id):
if id in self.securityGroupRules:
del self.securityGroupRules[id]
class FakeClient(object):
def __init__(self, context):
@ -656,6 +743,8 @@ class FakeClient(object):
self.rdhosts = FakeHosts(self.servers)
self.rdstorage = FakeRdStorages()
self.rdservers = FakeRdServers(self.servers)
self.security_groups = FakeSecurityGroups(context)
self.security_group_rules = FakeSecurityGroupRules(context)
def get_server_volumes(self, server_id):
return self.servers.get_server_volumes(server_id)

View File

@ -0,0 +1,16 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# 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.

View File

@ -0,0 +1,80 @@
# Copyright 2012 OpenStack LLC
#
# 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 testtools
import reddwarf.common.remote
from mock import Mock
from reddwarf.extensions.security_group import models
from reddwarf.common import exception
from reddwarf.tests.fakes import nova
from novaclient import exceptions as nova_exceptions
"""
Unit tests for testing the exceptions raised by Security Groups
"""
class Security_Group_Exceptions_Test(testtools.TestCase):
def setUp(self):
super(Security_Group_Exceptions_Test, self).setUp()
self.createNovaClient = reddwarf.common.remote.create_nova_client
self.context = Mock()
self.FakeClient = nova.fake_create_nova_client(self.context)
fException = Mock(side_effect=
lambda *args, **kwargs:
self._raise(nova_exceptions.ClientException("Test")))
self.FakeClient.security_groups.create = fException
self.FakeClient.security_groups.delete = fException
self.FakeClient.security_group_rules.create = fException
self.FakeClient.security_group_rules.delete = fException
reddwarf.common.remote.create_nova_client = \
lambda c: self._return_mocked_nova_client(c)
def tearDown(self):
super(Security_Group_Exceptions_Test, self).tearDown()
reddwarf.common.remote.create_nova_client = self.createNovaClient
def _return_mocked_nova_client(self, context):
return self.FakeClient
def _raise(self, ex):
raise ex
def test_failed_to_create_security_group(self):
self.assertRaises(exception.SecurityGroupCreationError,
models.RemoteSecurityGroup.create,
"TestName",
"TestDescription",
self.context)
def test_failed_to_delete_security_group(self):
self.assertRaises(exception.SecurityGroupDeletionError,
models.RemoteSecurityGroup.delete,
1, self.context)
def test_failed_to_create_security_group_rule(self):
self.assertRaises(exception.SecurityGroupRuleCreationError,
models.RemoteSecurityGroup.add_rule,
1, "tcp", 3306, 3306, "0.0.0.0/0", self.context)
def test_failed_to_delete_security_group_rule(self):
self.assertRaises(exception.SecurityGroupRuleDeletionError,
models.RemoteSecurityGroup.delete_rule,
1, self.context)