diff options
| author | Michael Gundlach <michael.gundlach@rackspace.com> | 2010-10-13 16:36:33 -0400 |
|---|---|---|
| committer | Michael Gundlach <michael.gundlach@rackspace.com> | 2010-10-13 16:36:33 -0400 |
| commit | beebed574bba9ef0e7bbeedd554a13ad5ded375a (patch) | |
| tree | 3b35c1e18dc6604023474f871903b0d12eabb02e /nova/db | |
| parent | 79a2c349ca5772a69b6f7f28a768e711d6db1524 (diff) | |
| parent | a4aa6725be683e7e1f35df1e54069b755d19551b (diff) | |
| download | nova-beebed574bba9ef0e7bbeedd554a13ad5ded375a.tar.gz nova-beebed574bba9ef0e7bbeedd554a13ad5ded375a.tar.xz nova-beebed574bba9ef0e7bbeedd554a13ad5ded375a.zip | |
Merge from trunk
Diffstat (limited to 'nova/db')
| -rw-r--r-- | nova/db/api.py | 115 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/api.py | 303 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/models.py | 97 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/session.py | 9 |
4 files changed, 410 insertions, 114 deletions
diff --git a/nova/db/api.py b/nova/db/api.py index 11815991e..7e6994b56 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -304,6 +304,11 @@ def instance_update(context, instance_id, values): return IMPL.instance_update(context, instance_id, values) +def instance_add_security_group(context, instance_id, security_group_id): + """Associate the given security group with the given instance""" + return IMPL.instance_add_security_group(context, instance_id, security_group_id) + + ################### @@ -335,6 +340,11 @@ def key_pair_get_all_by_user(context, user_id): #################### +def network_associate(context, project_id): + """Associate a free network to a project.""" + return IMPL.network_associate(context, project_id) + + def network_count(context): """Return the number of networks.""" return IMPL.network_count(context) @@ -355,9 +365,12 @@ def network_count_reserved_ips(context, network_id): return IMPL.network_count_reserved_ips(context, network_id) -def network_create(context, values): - """Create a network from the values dictionary.""" - return IMPL.network_create(context, values) +def network_create_safe(context, values): + """Create a network from the values dict + + The network is only returned if the create succeeds. If the create violates + constraints because the network already exists, no exception is raised.""" + return IMPL.network_create_safe(context, values) def network_create_fixed_ips(context, network_id, num_vpn_clients): @@ -365,9 +378,14 @@ def network_create_fixed_ips(context, network_id, num_vpn_clients): return IMPL.network_create_fixed_ips(context, network_id, num_vpn_clients) -def network_destroy(context, network_id): - """Destroy the network or raise if it does not exist.""" - return IMPL.network_destroy(context, network_id) +def network_disassociate(context, network_id): + """Disassociate the network from project or raise if it does not exist.""" + return IMPL.network_disassociate(context, network_id) + + +def network_disassociate_all(context): + """Disassociate all networks from projects.""" + return IMPL.network_disassociate_all(context) def network_get(context, network_id): @@ -382,10 +400,15 @@ def network_get_associated_fixed_ips(context, network_id): def network_get_by_bridge(context, bridge): - """Get an network or raise if it does not exist.""" + """Get a network by bridge or raise if it does not exist.""" return IMPL.network_get_by_bridge(context, bridge) +def network_get_by_instance(context, instance_id): + """Get a network by instance id or raise if it does not exist.""" + return IMPL.network_get_by_instance(context, instance_id) + + def network_get_index(context, network_id): """Get non-conflicting index for network""" return IMPL.network_get_index(context, network_id) @@ -396,19 +419,6 @@ def network_get_vpn_ip(context, network_id): return IMPL.network_get_vpn_ip(context, network_id) -def network_index_count(context): - """Return count of network indexes""" - return IMPL.network_index_count(context) - - -def network_index_create_safe(context, values): - """Create a network index from the values dict - - The index is not returned. If the create violates the unique - constraints because the index already exists, no exception is raised.""" - return IMPL.network_index_create_safe(context, values) - - def network_set_cidr(context, network_id, cidr): """Set the Classless Inner Domain Routing for the network""" return IMPL.network_set_cidr(context, network_id, cidr) @@ -571,6 +581,71 @@ def volume_update(context, volume_id, values): return IMPL.volume_update(context, volume_id, values) +#################### + + +def security_group_get_all(context): + """Get all security groups""" + return IMPL.security_group_get_all(context) + + +def security_group_get(context, security_group_id): + """Get security group by its internal id""" + return IMPL.security_group_get(context, security_group_id) + + +def security_group_get_by_name(context, project_id, group_name): + """Returns a security group with the specified name from a project""" + return IMPL.security_group_get_by_name(context, project_id, group_name) + + +def security_group_get_by_project(context, project_id): + """Get all security groups belonging to a project""" + return IMPL.security_group_get_by_project(context, project_id) + + +def security_group_get_by_instance(context, instance_id): + """Get security groups to which the instance is assigned""" + return IMPL.security_group_get_by_instance(context, instance_id) + + +def security_group_exists(context, project_id, group_name): + """Indicates if a group name exists in a project""" + return IMPL.security_group_exists(context, project_id, group_name) + + +def security_group_create(context, values): + """Create a new security group""" + return IMPL.security_group_create(context, values) + + +def security_group_destroy(context, security_group_id): + """Deletes a security group""" + return IMPL.security_group_destroy(context, security_group_id) + + +def security_group_destroy_all(context): + """Deletes a security group""" + return IMPL.security_group_destroy_all(context) + + +#################### + + +def security_group_rule_create(context, values): + """Create a new security group""" + return IMPL.security_group_rule_create(context, values) + + +def security_group_rule_get_by_security_group(context, security_group_id): + """Get all rules for a a given security group""" + return IMPL.security_group_rule_get_by_security_group(context, security_group_id) + +def security_group_rule_destroy(context, security_group_rule_id): + """Deletes a security group rule""" + return IMPL.security_group_rule_destroy(context, security_group_rule_id) + + ################### diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 1043f4bfb..6b979f0ae 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -29,8 +29,11 @@ from nova.db.sqlalchemy import models from nova.db.sqlalchemy.session import get_session from sqlalchemy import or_ from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import joinedload, joinedload_all -from sqlalchemy.sql import exists, func +from sqlalchemy.orm import joinedload +from sqlalchemy.orm import joinedload_all +from sqlalchemy.sql import exists +from sqlalchemy.sql import func +from sqlalchemy.orm.exc import NoResultFound FLAGS = flags.FLAGS @@ -571,11 +574,13 @@ def instance_get(context, instance_id, session=None): if is_admin_context(context): result = session.query(models.Instance + ).options(joinedload('security_groups') ).filter_by(id=instance_id ).filter_by(deleted=can_read_deleted(context) ).first() elif is_user_context(context): result = session.query(models.Instance + ).options(joinedload('security_groups') ).filter_by(project_id=context.project.id ).filter_by(id=instance_id ).filter_by(deleted=False @@ -591,6 +596,7 @@ def instance_get_all(context): session = get_session() return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') + ).options(joinedload('security_groups') ).filter_by(deleted=can_read_deleted(context) ).all() @@ -600,6 +606,7 @@ def instance_get_all_by_user(context, user_id): session = get_session() return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') + ).options(joinedload('security_groups') ).filter_by(deleted=can_read_deleted(context) ).filter_by(user_id=user_id ).all() @@ -612,6 +619,7 @@ def instance_get_all_by_project(context, project_id): session = get_session() return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') + ).options(joinedload('security_groups') ).filter_by(project_id=project_id ).filter_by(deleted=can_read_deleted(context) ).all() @@ -624,12 +632,14 @@ def instance_get_all_by_reservation(context, reservation_id): if is_admin_context(context): return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') + ).options(joinedload('security_groups') ).filter_by(reservation_id=reservation_id ).filter_by(deleted=can_read_deleted(context) ).all() elif is_user_context(context): return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') + ).options(joinedload('security_groups') ).filter_by(project_id=context.project.id ).filter_by(reservation_id=reservation_id ).filter_by(deleted=False @@ -642,11 +652,13 @@ def instance_get_by_internal_id(context, internal_id): if is_admin_context(context): result = session.query(models.Instance + ).options(joinedload('security_groups') ).filter_by(internal_id=internal_id ).filter_by(deleted=can_read_deleted(context) ).first() elif is_user_context(context): result = session.query(models.Instance + ).options(joinedload('security_groups') ).filter_by(project_id=context.project.id ).filter_by(internal_id=internal_id ).filter_by(deleted=False @@ -718,6 +730,18 @@ def instance_update(context, instance_id, values): instance_ref.save(session=session) +def instance_add_security_group(context, instance_id, security_group_id): + """Associate the given security group with the given instance""" + session = get_session() + with session.begin(): + instance_ref = instance_get(context, instance_id, session=session) + security_group_ref = security_group_get(context, + security_group_id, + session=session) + instance_ref.security_groups += [security_group_ref] + instance_ref.save(session=session) + + ################### @@ -781,6 +805,24 @@ def key_pair_get_all_by_user(context, user_id): @require_admin_context +def network_associate(context, project_id): + session = get_session() + with session.begin(): + network_ref = session.query(models.Network + ).filter_by(deleted=False + ).filter_by(project_id=None + ).with_lockmode('update' + ).first() + # NOTE(vish): if with_lockmode isn't supported, as in sqlite, + # then this has concurrency issues + if not network_ref: + raise db.NoMoreNetworks() + network_ref['project_id'] = project_id + session.add(network_ref) + return network_ref + + +@require_admin_context def network_count(context): session = get_session() return session.query(models.Network @@ -820,31 +862,26 @@ def network_count_reserved_ips(context, network_id): @require_admin_context -def network_create(context, values): +def network_create_safe(context, values): network_ref = models.Network() for (key, value) in values.iteritems(): network_ref[key] = value - network_ref.save() - return network_ref + try: + network_ref.save() + return network_ref + except IntegrityError: + return None @require_admin_context -def network_destroy(context, network_id): +def network_disassociate(context, network_id): + network_update(context, network_id, {'project_id': None}) + + +@require_admin_context +def network_disassociate_all(context): session = get_session() - with session.begin(): - # TODO(vish): do we have to use sql here? - session.execute('update networks set deleted=1 where id=:id', - {'id': network_id}) - session.execute('update fixed_ips set deleted=1 where network_id=:id', - {'id': network_id}) - session.execute('update floating_ips set deleted=1 ' - 'where fixed_ip_id in ' - '(select id from fixed_ips ' - 'where network_id=:id)', - {'id': network_id}) - session.execute('update network_indexes set network_id=NULL ' - 'where network_id=:id', - {'id': network_id}) + session.execute('update networks set project_id=NULL') @require_context @@ -894,48 +931,21 @@ def network_get_by_bridge(context, bridge): if not result: raise exception.NotFound('No network for bridge %s' % bridge) - return result @require_admin_context -def network_get_index(context, network_id): +def network_get_by_instance(_context, instance_id): session = get_session() - with session.begin(): - network_index = session.query(models.NetworkIndex - ).filter_by(network_id=None - ).filter_by(deleted=False - ).with_lockmode('update' - ).first() - - if not network_index: - raise db.NoMoreNetworks() - - network_index['network'] = network_get(context, - network_id, - session=session) - session.add(network_index) - - return network_index['index'] - - -@require_admin_context -def network_index_count(context): - session = get_session() - return session.query(models.NetworkIndex - ).filter_by(deleted=can_read_deleted(context) - ).count() - - -@require_admin_context -def network_index_create_safe(context, values): - network_index_ref = models.NetworkIndex() - for (key, value) in values.iteritems(): - network_index_ref[key] = value - try: - network_index_ref.save() - except IntegrityError: - pass + rv = session.query(models.Network + ).filter_by(deleted=False + ).join(models.Network.fixed_ips + ).filter_by(instance_id=instance_id + ).filter_by(deleted=False + ).first() + if not rv: + raise exception.NotFound('No network for instance %s' % instance_id) + return rv @require_admin_context @@ -975,15 +985,22 @@ def network_update(context, network_id, values): @require_context def project_get_network(context, project_id): session = get_session() - result= session.query(models.Network + rv = session.query(models.Network ).filter_by(project_id=project_id ).filter_by(deleted=False ).first() - - if not result: - raise exception.NotFound('No network for project: %s' % project_id) - - return result + if not rv: + try: + return network_associate(context, project_id) + except IntegrityError: + # NOTE(vish): We hit this if there is a race and two + # processes are attempting to allocate the + # network at the same time + rv = session.query(models.Network + ).filter_by(project_id=project_id + ).filter_by(deleted=False + ).first() + return rv ################### @@ -1193,6 +1210,7 @@ def volume_get(context, volume_id, session=None): @require_admin_context def volume_get_all(context): + session = get_session() return session.query(models.Volume ).filter_by(deleted=can_read_deleted(context) ).all() @@ -1283,6 +1301,163 @@ def volume_update(context, volume_id, values): ################### +@require_context +def security_group_get_all(context): + session = get_session() + return session.query(models.SecurityGroup + ).filter_by(deleted=can_read_deleted(context) + ).options(joinedload_all('rules') + ).all() + + +@require_context +def security_group_get(context, security_group_id, session=None): + if not session: + session = get_session() + if is_admin_context(context): + result = session.query(models.SecurityGroup + ).filter_by(deleted=can_read_deleted(context), + ).filter_by(id=security_group_id + ).options(joinedload_all('rules') + ).first() + else: + result = session.query(models.SecurityGroup + ).filter_by(deleted=False + ).filter_by(id=security_group_id + ).filter_by(project_id=context.project_id + ).options(joinedload_all('rules') + ).first() + if not result: + raise exception.NotFound("No secuity group with id %s" % + security_group_id) + return result + + +@require_context +def security_group_get_by_name(context, project_id, group_name): + session = get_session() + result = session.query(models.SecurityGroup + ).filter_by(project_id=project_id + ).filter_by(name=group_name + ).filter_by(deleted=False + ).options(joinedload_all('rules') + ).options(joinedload_all('instances') + ).first() + if not result: + raise exception.NotFound( + 'No security group named %s for project: %s' \ + % (group_name, project_id)) + return result + + +@require_context +def security_group_get_by_project(context, project_id): + session = get_session() + return session.query(models.SecurityGroup + ).filter_by(project_id=project_id + ).filter_by(deleted=False + ).options(joinedload_all('rules') + ).all() + + +@require_context +def security_group_get_by_instance(context, instance_id): + session = get_session() + return session.query(models.SecurityGroup + ).filter_by(deleted=False + ).options(joinedload_all('rules') + ).join(models.SecurityGroup.instances + ).filter_by(id=instance_id + ).filter_by(deleted=False + ).all() + + +@require_context +def security_group_exists(context, project_id, group_name): + try: + group = security_group_get_by_name(context, project_id, group_name) + return group != None + except exception.NotFound: + return False + + +@require_context +def security_group_create(context, values): + security_group_ref = models.SecurityGroup() + # FIXME(devcamcar): Unless I do this, rules fails with lazy load exception + # once save() is called. This will get cleaned up in next orm pass. + security_group_ref.rules + for (key, value) in values.iteritems(): + security_group_ref[key] = value + security_group_ref.save() + return security_group_ref + + +@require_context +def security_group_destroy(context, security_group_id): + session = get_session() + with session.begin(): + # TODO(vish): do we have to use sql here? + session.execute('update security_groups set deleted=1 where id=:id', + {'id': security_group_id}) + session.execute('update security_group_rules set deleted=1 ' + 'where group_id=:id', + {'id': security_group_id}) + +@require_context +def security_group_destroy_all(context, session=None): + if not session: + session = get_session() + with session.begin(): + # TODO(vish): do we have to use sql here? + session.execute('update security_groups set deleted=1') + session.execute('update security_group_rules set deleted=1') + + +################### + + +@require_context +def security_group_rule_get(context, security_group_rule_id, session=None): + if not session: + session = get_session() + if is_admin_context(context): + result = session.query(models.SecurityGroupIngressRule + ).filter_by(deleted=can_read_deleted(context) + ).filter_by(id=security_group_rule_id + ).first() + else: + # TODO(vish): Join to group and check for project_id + result = session.query(models.SecurityGroupIngressRule + ).filter_by(deleted=False + ).filter_by(id=security_group_rule_id + ).first() + if not result: + raise exception.NotFound("No secuity group rule with id %s" % + security_group_rule_id) + return result + + +@require_context +def security_group_rule_create(context, values): + security_group_rule_ref = models.SecurityGroupIngressRule() + for (key, value) in values.iteritems(): + security_group_rule_ref[key] = value + security_group_rule_ref.save() + return security_group_rule_ref + +@require_context +def security_group_rule_destroy(context, security_group_rule_id): + session = get_session() + with session.begin(): + security_group_rule = security_group_rule_get(context, + security_group_rule_id, + session=session) + security_group_rule.delete(session=session) + + +################### + @require_admin_context def user_get(context, id, session=None): if not session: @@ -1492,6 +1667,8 @@ def user_add_project_role(context, user_id, project_id, role): ################### + +@require_admin_context def host_get_networks(context, host): session = get_session() with session.begin(): diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index ebcb73413..eed8f0578 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -25,7 +25,7 @@ import datetime # TODO(vish): clean up these imports from sqlalchemy.orm import relationship, backref, exc, object_mapper -from sqlalchemy import Column, Integer, String +from sqlalchemy import Column, Integer, String, schema from sqlalchemy import ForeignKey, DateTime, Boolean, Text from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.declarative import declarative_base @@ -169,7 +169,7 @@ class Instance(BASE, NovaBase): @property def name(self): - return self.internal_id + return "instance-%d" % self.internal_id image_id = Column(String(255)) kernel_id = Column(String(255)) @@ -187,7 +187,6 @@ class Instance(BASE, NovaBase): launch_index = Column(Integer) key_name = Column(String(255)) key_data = Column(Text) - security_group = Column(String(255)) state = Column(Integer) state_description = Column(String(255)) @@ -289,10 +288,66 @@ class ExportDevice(BASE, NovaBase): 'ExportDevice.deleted==False)') +class SecurityGroupInstanceAssociation(BASE, NovaBase): + __tablename__ = 'security_group_instance_association' + id = Column(Integer, primary_key=True) + security_group_id = Column(Integer, ForeignKey('security_groups.id')) + instance_id = Column(Integer, ForeignKey('instances.id')) + + +class SecurityGroup(BASE, NovaBase): + """Represents a security group""" + __tablename__ = 'security_groups' + id = Column(Integer, primary_key=True) + + name = Column(String(255)) + description = Column(String(255)) + user_id = Column(String(255)) + project_id = Column(String(255)) + + instances = relationship(Instance, + secondary="security_group_instance_association", + primaryjoin="and_(SecurityGroup.id == SecurityGroupInstanceAssociation.security_group_id," + "SecurityGroup.deleted == False)", + secondaryjoin="and_(SecurityGroupInstanceAssociation.instance_id == Instance.id," + "Instance.deleted == False)", + backref='security_groups') + + @property + def user(self): + return auth.manager.AuthManager().get_user(self.user_id) + + @property + def project(self): + return auth.manager.AuthManager().get_project(self.project_id) + + +class SecurityGroupIngressRule(BASE, NovaBase): + """Represents a rule in a security group""" + __tablename__ = 'security_group_rules' + id = Column(Integer, primary_key=True) + + parent_group_id = Column(Integer, ForeignKey('security_groups.id')) + parent_group = relationship("SecurityGroup", backref="rules", + foreign_keys=parent_group_id, + primaryjoin="and_(SecurityGroupIngressRule.parent_group_id == SecurityGroup.id," + "SecurityGroupIngressRule.deleted == False)") + + protocol = Column(String(5)) # "tcp", "udp", or "icmp" + from_port = Column(Integer) + to_port = Column(Integer) + cidr = Column(String(255)) + + # Note: This is not the parent SecurityGroup. It's SecurityGroup we're + # granting access for. + group_id = Column(Integer, ForeignKey('security_groups.id')) + + class KeyPair(BASE, NovaBase): """Represents a public key pair for ssh""" __tablename__ = 'key_pairs' id = Column(Integer, primary_key=True) + name = Column(String(255)) user_id = Column(String(255)) @@ -308,10 +363,13 @@ class KeyPair(BASE, NovaBase): class Network(BASE, NovaBase): """Represents a network""" __tablename__ = 'networks' + __table_args__ = (schema.UniqueConstraint("vpn_public_address", + "vpn_public_port"), + {'mysql_engine': 'InnoDB'}) id = Column(Integer, primary_key=True) injected = Column(Boolean, default=False) - cidr = Column(String(255)) + cidr = Column(String(255), unique=True) netmask = Column(String(255)) bridge = Column(String(255)) gateway = Column(String(255)) @@ -324,28 +382,13 @@ class Network(BASE, NovaBase): vpn_private_address = Column(String(255)) dhcp_start = Column(String(255)) - project_id = Column(String(255)) + # NOTE(vish): The unique constraint below helps avoid a race condition + # when associating a network, but it also means that we + # can't associate two networks with one project. + project_id = Column(String(255), unique=True) host = Column(String(255)) # , ForeignKey('hosts.id')) -class NetworkIndex(BASE, NovaBase): - """Represents a unique offset for a network - - Currently vlan number, vpn port, and fixed ip ranges are keyed off of - this index. These may ultimately need to be converted to separate - pools. - """ - __tablename__ = 'network_indexes' - id = Column(Integer, primary_key=True) - index = Column(Integer, unique=True) - network_id = Column(Integer, ForeignKey('networks.id'), nullable=True) - network = relationship(Network, - backref=backref('network_index', uselist=False), - foreign_keys=network_id, - primaryjoin='and_(NetworkIndex.network_id==Network.id,' - 'NetworkIndex.deleted==False)') - - class AuthToken(BASE, NovaBase): """Represents an authorization token for all API transactions. Fields are a string representing the actual token and a user id for mapping @@ -358,7 +401,6 @@ class AuthToken(BASE, NovaBase): cdn_management_url = Column(String(255)) - # TODO(vish): can these both come from the same baseclass? class FixedIp(BASE, NovaBase): """Represents a fixed ip for an instance""" @@ -461,9 +503,10 @@ class FloatingIp(BASE, NovaBase): def register_models(): """Register Models and create metadata""" from sqlalchemy import create_engine - models = (Service, Instance, Volume, ExportDevice, - FixedIp, FloatingIp, Network, NetworkIndex, - AuthToken, UserProjectAssociation, User, Project) # , Image, Host) + models = (Service, Instance, Volume, ExportDevice, FixedIp, + FloatingIp, Network, SecurityGroup, + SecurityGroupIngressRule, SecurityGroupInstanceAssociation, + AuthToken, User, Project) # , Image, Host engine = create_engine(FLAGS.sql_connection, echo=False) for model in models: model.metadata.create_all(engine) diff --git a/nova/db/sqlalchemy/session.py b/nova/db/sqlalchemy/session.py index 69a205378..826754f6a 100644 --- a/nova/db/sqlalchemy/session.py +++ b/nova/db/sqlalchemy/session.py @@ -36,7 +36,8 @@ def get_session(autocommit=True, expire_on_commit=False): if not _MAKER: if not _ENGINE: _ENGINE = create_engine(FLAGS.sql_connection, echo=False) - _MAKER = sessionmaker(bind=_ENGINE, - autocommit=autocommit, - expire_on_commit=expire_on_commit) - return _MAKER() + _MAKER = (sessionmaker(bind=_ENGINE, + autocommit=autocommit, + expire_on_commit=expire_on_commit)) + session = _MAKER() + return session |
