summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVishvananda Ishaya <vishvananda@yahoo.com>2010-08-19 11:13:53 -0700
committerVishvananda Ishaya <vishvananda@yahoo.com>2010-08-19 11:13:53 -0700
commitcde815009cc1637b2abb58d5919b356453d122c2 (patch)
treee75cb0fe8a28b30b8292bc3c2207fbcc46a10ef4
parenta74f2a3ca4e26c451a002f9a89f3ba4ac4a083c4 (diff)
parenta92465922fb74ca2c9b392e1c1b7ed5b5e306a76 (diff)
merged termies abstractions
-rw-r--r--nova/auth.py741
-rw-r--r--nova/compute/service.py144
-rw-r--r--nova/db/__init__.py3
-rw-r--r--nova/db/api.py53
-rw-r--r--nova/db/sqlalchemy/__init__.py0
-rw-r--r--nova/db/sqlalchemy/api.py43
-rw-r--r--nova/models.py6
-rw-r--r--nova/utils.py33
8 files changed, 211 insertions, 812 deletions
diff --git a/nova/auth.py b/nova/auth.py
deleted file mode 100644
index 199a887e1..000000000
--- a/nova/auth.py
+++ /dev/null
@@ -1,741 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2010 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# 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.
-
-"""
-Nova authentication management
-"""
-
-import logging
-import os
-import shutil
-import string
-import tempfile
-import uuid
-import zipfile
-
-from nova import crypto
-from nova import exception
-from nova import flags
-from nova import utils
-from nova.auth import signer
-from nova.network import vpn
-from nova.models import User
-
-#unused imports
-#from nova import datastore
-#from nova.auth import ldapdriver # for flags
-#from nova import objectstore # for flags
-
-FLAGS = flags.FLAGS
-
-# NOTE(vish): a user with one of these roles will be a superuser and
-# have access to all api commands
-flags.DEFINE_list('superuser_roles', ['cloudadmin'],
- 'Roles that ignore rbac checking completely')
-
-# NOTE(vish): a user with one of these roles will have it for every
-# project, even if he or she is not a member of the project
-flags.DEFINE_list('global_roles', ['cloudadmin', 'itsec'],
- 'Roles that apply to all projects')
-
-
-flags.DEFINE_string('credentials_template',
- utils.abspath('auth/novarc.template'),
- 'Template for creating users rc file')
-flags.DEFINE_string('vpn_client_template',
- utils.abspath('cloudpipe/client.ovpn.template'),
- 'Template for creating users vpn file')
-flags.DEFINE_string('credential_vpn_file', 'nova-vpn.conf',
- 'Filename of certificate in credentials zip')
-flags.DEFINE_string('credential_key_file', 'pk.pem',
- 'Filename of private key in credentials zip')
-flags.DEFINE_string('credential_cert_file', 'cert.pem',
- 'Filename of certificate in credentials zip')
-flags.DEFINE_string('credential_rc_file', 'novarc',
- 'Filename of rc in credentials zip')
-
-flags.DEFINE_string('credential_cert_subject',
- '/C=US/ST=California/L=MountainView/O=AnsoLabs/'
- 'OU=NovaDev/CN=%s-%s',
- 'Subject for certificate for users')
-
-flags.DEFINE_string('auth_driver', 'nova.auth.ldapdriver.FakeLdapDriver',
- 'Driver that auth manager uses')
-
-class AuthBase(object):
- """Base class for objects relating to auth
-
- Objects derived from this class should be stupid data objects with
- an id member. They may optionally contain methods that delegate to
- AuthManager, but should not implement logic themselves.
- """
- @classmethod
- def safe_id(cls, obj):
- """Safe get object id
-
- This method will return the id of the object if the object
- is of this class, otherwise it will return the original object.
- This allows methods to accept objects or ids as paramaters.
-
- """
- if isinstance(obj, cls):
- return obj.id
- else:
- return obj
-
-
-# anthony - the User class has moved to nova.models
-#class User(AuthBase):
-# """Object representing a user"""
-# def __init__(self, id, name, access, secret, admin):
-# AuthBase.__init__(self)
-# self.id = id
-# self.name = name
-# self.access = access
-# self.secret = secret
-# self.admin = admin
-#
-# def is_superuser(self):
-# return AuthManager().is_superuser(self)
-#
-# def is_admin(self):
-# return AuthManager().is_admin(self)
-#
-# def has_role(self, role):
-# return AuthManager().has_role(self, role)
-#
-# def add_role(self, role):
-# return AuthManager().add_role(self, role)
-#
-# def remove_role(self, role):
-# return AuthManager().remove_role(self, role)
-#
-# def is_project_member(self, project):
-# return AuthManager().is_project_member(self, project)
-#
-# def is_project_manager(self, project):
-# return AuthManager().is_project_manager(self, project)
-#
-# def generate_key_pair(self, name):
-# return AuthManager().generate_key_pair(self.id, name)
-#
-# def create_key_pair(self, name, public_key, fingerprint):
-# return AuthManager().create_key_pair(self.id,
-# name,
-# public_key,
-# fingerprint)
-#
-# def get_key_pair(self, name):
-# return AuthManager().get_key_pair(self.id, name)
-#
-# def delete_key_pair(self, name):
-# return AuthManager().delete_key_pair(self.id, name)
-#
-# def get_key_pairs(self):
-# return AuthManager().get_key_pairs(self.id)
-#
-# def __repr__(self):
-# return "User('%s', '%s', '%s', '%s', %s)" % (self.id,
-# self.name,
-# self.access,
-# self.secret,
-# self.admin)
-
-
-class KeyPair(AuthBase):
- """Represents an ssh key returned from the datastore
-
- Even though this object is named KeyPair, only the public key and
- fingerprint is stored. The user's private key is not saved.
- """
- def __init__(self, id, name, owner_id, public_key, fingerprint):
- AuthBase.__init__(self)
- self.id = id
- self.name = name
- self.owner_id = owner_id
- self.public_key = public_key
- self.fingerprint = fingerprint
-
- def __repr__(self):
- return "KeyPair('%s', '%s', '%s', '%s', '%s')" % (self.id,
- self.name,
- self.owner_id,
- self.public_key,
- self.fingerprint)
-
-
-class Project(AuthBase):
- """Represents a Project returned from the datastore"""
- def __init__(self, id, name, project_manager_id, description, member_ids):
- AuthBase.__init__(self)
- self.id = id
- self.name = name
- self.project_manager_id = project_manager_id
- self.description = description
- self.member_ids = member_ids
-
- @property
- def project_manager(self):
- return AuthManager().get_user(self.project_manager_id)
-
- @property
- def vpn_ip(self):
- ip, port = AuthManager().get_project_vpn_data(self)
- return ip
-
- @property
- def vpn_port(self):
- ip, port = AuthManager().get_project_vpn_data(self)
- return port
-
- def has_manager(self, user):
- return AuthManager().is_project_manager(user, self)
-
- def has_member(self, user):
- return AuthManager().is_project_member(user, self)
-
- def add_role(self, user, role):
- return AuthManager().add_role(user, role, self)
-
- def remove_role(self, user, role):
- return AuthManager().remove_role(user, role, self)
-
- def has_role(self, user, role):
- return AuthManager().has_role(user, role, self)
-
- def get_credentials(self, user):
- return AuthManager().get_credentials(user, self)
-
- def __repr__(self):
- return "Project('%s', '%s', '%s', '%s', %s)" % (self.id,
- self.name,
- self.project_manager_id,
- self.description,
- self.member_ids)
-
-
-
-class AuthManager(object):
- """Manager Singleton for dealing with Users, Projects, and Keypairs
-
- Methods accept objects or ids.
-
- AuthManager uses a driver object to make requests to the data backend.
- See ldapdriver for reference.
-
- AuthManager also manages associated data related to Auth objects that
- need to be more accessible, such as vpn ips and ports.
- """
- _instance = None
- def __new__(cls, *args, **kwargs):
- """Returns the AuthManager singleton"""
- if not cls._instance:
- cls._instance = super(AuthManager, cls).__new__(cls)
- return cls._instance
-
- def __init__(self, driver=None, *args, **kwargs):
- """Inits the driver from parameter or flag
-
- __init__ is run every time AuthManager() is called, so we only
- reset the driver if it is not set or a new driver is specified.
- """
- if driver or not getattr(self, 'driver', None):
- self.driver = utils.import_class(driver or FLAGS.auth_driver)
-
- def authenticate(self, access, signature, params, verb='GET',
- server_string='127.0.0.1:8773', path='/',
- check_type='ec2', headers=None):
- """Authenticates AWS request using access key and signature
-
- If the project is not specified, attempts to authenticate to
- a project with the same name as the user. This way, older tools
- that have no project knowledge will still work.
-
- @type access: str
- @param access: Access key for user in the form "access:project".
-
- @type signature: str
- @param signature: Signature of the request.
-
- @type params: list of str
- @param params: Web paramaters used for the signature.
-
- @type verb: str
- @param verb: Web request verb ('GET' or 'POST').
-
- @type server_string: str
- @param server_string: Web request server string.
-
- @type path: str
- @param path: Web request path.
-
- @type check_type: str
- @param check_type: Type of signature to check. 'ec2' for EC2, 's3' for
- S3. Any other value will cause signature not to be
- checked.
-
- @type headers: list
- @param headers: HTTP headers passed with the request (only needed for
- s3 signature checks)
-
- @rtype: tuple (User, Project)
- @return: User and project that the request represents.
- """
- # TODO(vish): check for valid timestamp
- (access_key, sep, project_id) = access.partition(':')
-
- logging.info('Looking up user: %r', access_key)
- user = self.get_user_from_access_key(access_key)
- logging.info('user: %r', user)
- if user == None:
- raise exception.NotFound('No user found for access key %s' %
- access_key)
-
- # NOTE(vish): if we stop using project name as id we need better
- # logic to find a default project for user
- if project_id is '':
- project_id = user.name
-
- project = self.get_project(project_id)
- if project == None:
- raise exception.NotFound('No project called %s could be found' %
- project_id)
- if not self.is_admin(user) and not self.is_project_member(user,
- project):
- raise exception.NotFound('User %s is not a member of project %s' %
- (user.id, project.id))
- if check_type == 's3':
- expected_signature = signer.Signer(user.secret.encode()).s3_authorization(headers, verb, path)
- logging.debug('user.secret: %s', user.secret)
- logging.debug('expected_signature: %s', expected_signature)
- logging.debug('signature: %s', signature)
- if signature != expected_signature:
- raise exception.NotAuthorized('Signature does not match')
- elif check_type == 'ec2':
- # NOTE(vish): hmac can't handle unicode, so encode ensures that
- # secret isn't unicode
- expected_signature = signer.Signer(user.secret.encode()).generate(
- params, verb, server_string, path)
- logging.debug('user.secret: %s', user.secret)
- logging.debug('expected_signature: %s', expected_signature)
- logging.debug('signature: %s', signature)
- if signature != expected_signature:
- raise exception.NotAuthorized('Signature does not match')
- return (user, project)
-
- def get_access_key(self, user, project):
- """Get an access key that includes user and project"""
- if not isinstance(user, User):
- user = self.get_user(user)
- return "%s:%s" % (user.access, Project.safe_id(project))
-
- def is_superuser(self, user):
- """Checks for superuser status, allowing user to bypass rbac
-
- @type user: User or uid
- @param user: User to check.
-
- @rtype: bool
- @return: True for superuser.
- """
- if not isinstance(user, User):
- user = self.get_user(user)
- # NOTE(vish): admin flag on user represents superuser
- if user.admin:
- return True
- for role in FLAGS.superuser_roles:
- if self.has_role(user, role):
- return True
-
- def is_admin(self, user):
- """Checks for admin status, allowing user to access all projects
-
- @type user: User or uid
- @param user: User to check.
-
- @rtype: bool
- @return: True for admin.
- """
- if not isinstance(user, User):
- user = self.get_user(user)
- if self.is_superuser(user):
- return True
- for role in FLAGS.global_roles:
- if self.has_role(user, role):
- return True
-
- def has_role(self, user, role, project=None):
- """Checks existence of role for user
-
- If project is not specified, checks for a global role. If project
- is specified, checks for the union of the global role and the
- project role.
-
- Role 'projectmanager' only works for projects and simply checks to
- see if the user is the project_manager of the specified project. It
- is the same as calling is_project_manager(user, project).
-
- @type user: User or uid
- @param user: User to check.
-
- @type role: str
- @param role: Role to check.
-
- @type project: Project or project_id
- @param project: Project in which to look for local role.
-
- @rtype: bool
- @return: True if the user has the role.
- """
- with self.driver() as drv:
- if role == 'projectmanager':
- if not project:
- raise exception.Error("Must specify project")
- return self.is_project_manager(user, project)
-
- global_role = drv.has_role(User.safe_id(user),
- role,
- None)
- if not global_role:
- return global_role
-
- if not project or role in FLAGS.global_roles:
- return global_role
-
- return drv.has_role(User.safe_id(user),
- role,
- Project.safe_id(project))
-
- def add_role(self, user, role, project=None):
- """Adds role for user
-
- If project is not specified, adds a global role. If project
- is specified, adds a local role.
-
- The 'projectmanager' role is special and can't be added or removed.
-
- @type user: User or uid
- @param user: User to which to add role.
-
- @type role: str
- @param role: Role to add.
-
- @type project: Project or project_id
- @param project: Project in which to add local role.
- """
- with self.driver() as drv:
- drv.add_role(User.safe_id(user), role, Project.safe_id(project))
-
- def remove_role(self, user, role, project=None):
- """Removes role for user
-
- If project is not specified, removes a global role. If project
- is specified, removes a local role.
-
- The 'projectmanager' role is special and can't be added or removed.
-
- @type user: User or uid
- @param user: User from which to remove role.
-
- @type role: str
- @param role: Role to remove.
-
- @type project: Project or project_id
- @param project: Project in which to remove local role.
- """
- with self.driver() as drv:
- drv.remove_role(User.safe_id(user), role, Project.safe_id(project))
-
- def get_project(self, pid):
- """Get project object by id"""
- with self.driver() as drv:
- project_dict = drv.get_project(pid)
- if project_dict:
- return Project(**project_dict)
-
- def get_projects(self, user=None):
- """Retrieves list of projects, optionally filtered by user"""
- with self.driver() as drv:
- project_list = drv.get_projects(User.safe_id(user))
- if not project_list:
- return []
- return [Project(**project_dict) for project_dict in project_list]
-
- def create_project(self, name, manager_user,
- description=None, member_users=None):
- """Create a project
-
- @type name: str
- @param name: Name of the project to create. The name will also be
- used as the project id.
-
- @type manager_user: User or uid
- @param manager_user: This user will be the project manager.
-
- @type description: str
- @param project: Description of the project. If no description is
- specified, the name of the project will be used.
-
- @type member_users: list of User or uid
- @param: Initial project members. The project manager will always be
- added as a member, even if he isn't specified in this list.
-
- @rtype: Project
- @return: The new project.
- """
- if member_users:
- member_users = [User.safe_id(u) for u in member_users]
- with self.driver() as drv:
- project_dict = drv.create_project(name,
- User.safe_id(manager_user),
- description,
- member_users)
- if project_dict:
- return Project(**project_dict)
-
- def add_to_project(self, user, project):
- """Add user to project"""
- with self.driver() as drv:
- return drv.add_to_project(User.safe_id(user),
- Project.safe_id(project))
-
- def is_project_manager(self, user, project):
- """Checks if user is project manager"""
- if not isinstance(project, Project):
- project = self.get_project(project)
- return User.safe_id(user) == project.project_manager_id
-
- def is_project_member(self, user, project):
- """Checks to see if user is a member of project"""
- if not isinstance(project, Project):
- project = self.get_project(project)
- return User.safe_id(user) in project.member_ids
-
- def remove_from_project(self, user, project):
- """Removes a user from a project"""
- with self.driver() as drv:
- return drv.remove_from_project(User.safe_id(user),
- Project.safe_id(project))
-
- def get_project_vpn_data(self, project):
- """Gets vpn ip and port for project
-
- @type project: Project or project_id
- @param project: Project from which to get associated vpn data
-
- @rvalue: tuple of (str, str)
- @return: A tuple containing (ip, port) or None, None if vpn has
- not been allocated for user.
- """
- network_data = vpn.NetworkData.lookup(Project.safe_id(project))
- if not network_data:
- raise exception.NotFound('project network data has not been set')
- return (network_data.ip, network_data.port)
-
- def delete_project(self, project):
- """Deletes a project"""
- with self.driver() as drv:
- return drv.delete_project(Project.safe_id(project))
-
- def get_user(self, uid):
- """Retrieves a user by id"""
- with self.driver() as drv:
- user_dict = drv.get_user(uid)
- if user_dict:
- return User(**user_dict)
-
- def get_user_from_access_key(self, access_key):
- """Retrieves a user by access key"""
- with self.driver() as drv:
- user_dict = drv.get_user_from_access_key(access_key)
- if user_dict:
- return User(**user_dict)
-
- def get_users(self):
- """Retrieves a list of all users"""
- with self.driver() as drv:
- user_list = drv.get_users()
- if not user_list:
- return []
- return [User(**user_dict) for user_dict in user_list]
-
- def create_user(self, name, access=None, secret=None, admin=False):
- """Creates a user
-
- @type name: str
- @param name: Name of the user to create.
-
- @type access: str
- @param access: Access Key (defaults to a random uuid)
-
- @type secret: str
- @param secret: Secret Key (defaults to a random uuid)
-
- @type admin: bool
- @param admin: Whether to set the admin flag. The admin flag gives
- superuser status regardless of roles specifed for the user.
-
- @type create_project: bool
- @param: Whether to create a project for the user with the same name.
-
- @rtype: User
- @return: The new user.
- """
- if access == None: access = str(uuid.uuid4())
- if secret == None: secret = str(uuid.uuid4())
- with self.driver() as drv:
- user_dict = drv.create_user(name, access, secret, admin)
- if user_dict:
- return User(**user_dict)
-
- def delete_user(self, user):
- """Deletes a user"""
- with self.driver() as drv:
- drv.delete_user(User.safe_id(user))
-
- def generate_key_pair(self, user, key_name):
- """Generates a key pair for a user
-
- Generates a public and private key, stores the public key using the
- key_name, and returns the private key and fingerprint.
-
- @type user: User or uid
- @param user: User for which to create key pair.
-
- @type key_name: str
- @param key_name: Name to use for the generated KeyPair.
-
- @rtype: tuple (private_key, fingerprint)
- @return: A tuple containing the private_key and fingerprint.
- """
- # NOTE(vish): generating key pair is slow so check for legal
- # creation before creating keypair
- uid = User.safe_id(user)
- with self.driver() as drv:
- if not drv.get_user(uid):
- raise exception.NotFound("User %s doesn't exist" % user)
- if drv.get_key_pair(uid, key_name):
- raise exception.Duplicate("The keypair %s already exists"
- % key_name)
- private_key, public_key, fingerprint = crypto.generate_key_pair()
- self.create_key_pair(uid, key_name, public_key, fingerprint)
- return private_key, fingerprint
-
- def create_key_pair(self, user, key_name, public_key, fingerprint):
- """Creates a key pair for user"""
- with self.driver() as drv:
- kp_dict = drv.create_key_pair(User.safe_id(user),
- key_name,
- public_key,
- fingerprint)
- if kp_dict:
- return KeyPair(**kp_dict)
-
- def get_key_pair(self, user, key_name):
- """Retrieves a key pair for user"""
- with self.driver() as drv:
- kp_dict = drv.get_key_pair(User.safe_id(user), key_name)
- if kp_dict:
- return KeyPair(**kp_dict)
-
- def get_key_pairs(self, user):
- """Retrieves all key pairs for user"""
- with self.driver() as drv:
- kp_list = drv.get_key_pairs(User.safe_id(user))
- if not kp_list:
- return []
- return [KeyPair(**kp_dict) for kp_dict in kp_list]
-
- def delete_key_pair(self, user, key_name):
- """Deletes a key pair for user"""
- with self.driver() as drv:
- drv.delete_key_pair(User.safe_id(user), key_name)
-
- def get_credentials(self, user, project=None):
- """Get credential zip for user in project"""
- if not isinstance(user, User):
- user = self.get_user(user)
- if project is None:
- project = user.id
- pid = Project.safe_id(project)
- rc = self.__generate_rc(user.access, user.secret, pid)
- private_key, signed_cert = self._generate_x509_cert(user.id, pid)
-
- tmpdir = tempfile.mkdtemp()
- zf = os.path.join(tmpdir, "temp.zip")
- zippy = zipfile.ZipFile(zf, 'w')
- zippy.writestr(FLAGS.credential_rc_file, rc)
- zippy.writestr(FLAGS.credential_key_file, private_key)
- zippy.writestr(FLAGS.credential_cert_file, signed_cert)
-
- network_data = vpn.NetworkData.lookup(pid)
- if network_data:
- configfile = open(FLAGS.vpn_client_template,"r")
- s = string.Template(configfile.read())
- configfile.close()
- config = s.substitute(keyfile=FLAGS.credential_key_file,
- certfile=FLAGS.credential_cert_file,
- ip=network_data.ip,
- port=network_data.port)
- zippy.writestr(FLAGS.credential_vpn_file, config)
- else:
- logging.warn("No vpn data for project %s" %
- pid)
-
- zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(user.id))
- zippy.close()
- with open(zf, 'rb') as f:
- buffer = f.read()
-
- shutil.rmtree(tmpdir)
- return buffer
-
- def get_environment_rc(self, user, project=None):
- """Get credential zip for user in project"""
- if not isinstance(user, User):
- user = self.get_user(user)
- if project is None:
- project = user.id
- pid = Project.safe_id(project)
- return self.__generate_rc(user.access, user.secret, pid)
-
- def __generate_rc(self, access, secret, pid):
- """Generate rc file for user"""
- rc = open(FLAGS.credentials_template).read()
- rc = rc % { 'access': access,
- 'project': pid,
- 'secret': secret,
- 'ec2': FLAGS.ec2_url,
- 's3': 'http://%s:%s' % (FLAGS.s3_host, FLAGS.s3_port),
- 'nova': FLAGS.ca_file,
- 'cert': FLAGS.credential_cert_file,
- 'key': FLAGS.credential_key_file,
- }
- return rc
-
- def _generate_x509_cert(self, uid, pid):
- """Generate x509 cert for user"""
- (private_key, csr) = crypto.generate_x509_cert(
- self.__cert_subject(uid))
- # TODO(joshua): This should be async call back to the cloud controller
- signed_cert = crypto.sign_csr(csr, pid)
- return (private_key, signed_cert)
-
- def __cert_subject(self, uid):
- """Helper to generate cert subject"""
- return FLAGS.credential_cert_subject % (uid, utils.isotime())
diff --git a/nova/compute/service.py b/nova/compute/service.py
index 3909c8245..7a2cb277d 100644
--- a/nova/compute/service.py
+++ b/nova/compute/service.py
@@ -30,6 +30,7 @@ import os
from twisted.internet import defer
+from nova import db
from nova import exception
from nova import flags
from nova import process
@@ -44,7 +45,7 @@ from nova.volume import service as volume_service
FLAGS = flags.FLAGS
flags.DEFINE_string('instances_path', utils.abspath('../instances'),
- 'where instances are stored on disk')
+ 'where instances are stored on disk')
class ComputeService(service.Service):
@@ -52,109 +53,107 @@ class ComputeService(service.Service):
Manages the running instances.
"""
def __init__(self):
- """ load configuration options for this node and connect to the hypervisor"""
+ """Load configuration options and connect to the hypervisor."""
super(ComputeService, self).__init__()
self._instances = {}
self._conn = virt_connection.get_connection()
- # TODO(joshua): This needs to ensure system state, specifically: modprobe aoe
+ # TODO(joshua): This needs to ensure system state, specifically
+ # modprobe aoe
def noop(self):
- """ simple test of an AMQP message call """
+ """Simple test of an AMQP message call."""
return defer.succeed('PONG')
- def update_state(self, instance_id):
- inst = models.Instance.find(instance_id)
+ def update_state(self, instance_id, context):
# FIXME(ja): include other fields from state?
- inst.state = self._conn.get_info(inst.name)['state']
- inst.save()
-
- @exception.wrap_exception
- def adopt_instances(self):
- """ if there are instances already running, adopt them """
- return defer.succeed(0)
- instance_names = self._conn.list_instances()
- for name in instance_names:
- try:
- new_inst = Instance.fromName(self._conn, name)
- new_inst.update_state()
- except:
- pass
- return defer.succeed(len(self._instances))
+ instance_ref = db.instance_get(context, instance_id)
+ state = self._conn.get_info(instance_ref.name)['state']
+ db.instance_state(context, instance_id, state)
@defer.inlineCallbacks
@exception.wrap_exception
- def run_instance(self, instance_id, **_kwargs):
- """ launch a new instance with specified options """
- inst = models.Instance.find(instance_id)
- if inst.name in self._conn.list_instances():
+ def run_instance(self, instance_id, context=None, **_kwargs):
+ """Launch a new instance with specified options."""
+ instance_ref = db.instance_get(context, instance_id)
+ if instance_ref['name'] in self._conn.list_instances():
raise exception.Error("Instance has already been created")
logging.debug("Starting instance %s..." % (instance_id))
- inst = models.Instance.find(instance_id)
+
# NOTE(vish): passing network type allows us to express the
# network without making a call to network to find
# out which type of network to setup
- network_service.setup_compute_network(inst.project_id)
- inst.node_name = FLAGS.node_name
- inst.save()
+ network_service.setup_compute_network(instance_ref['project_id'])
+ db.instance_update(context, instance_id, {'node_name': FLAGS.node_name})
# TODO(vish) check to make sure the availability zone matches
- inst.set_state(power_state.NOSTATE, 'spawning')
+ db.instance_state(context, instance_id, power_state.NOSTATE, 'spawning')
try:
- yield self._conn.spawn(inst)
+ yield self._conn.spawn(instance_ref)
except:
- logging.exception("Failed to spawn instance %s" % inst.name)
- inst.set_state(power_state.SHUTDOWN)
+ logging.exception("Failed to spawn instance %s" %
+ instance_ref['name'])
+ db.instance_state(context, instance_id, power_state.SHUTDOWN)
- self.update_state(instance_id)
+ self.update_state(instance_id, context)
@defer.inlineCallbacks
@exception.wrap_exception
- def terminate_instance(self, instance_id):
- """ terminate an instance on this machine """
+ def terminate_instance(self, instance_id, context=None):
+ """Terminate an instance on this machine."""
logging.debug("Got told to terminate instance %s" % instance_id)
- inst = models.Instance.find(instance_id)
+ instance_ref = db.instance_get(context, instance_id)
- if inst.state == power_state.SHUTOFF:
- # self.datamodel.destroy() FIXME: RE-ADD ?????
+ if instance_ref['state'] == power_state.SHUTOFF:
+ # self.datamodel.destroy() FIXME: RE-ADD?
raise exception.Error('trying to destroy already destroyed'
' instance: %s' % instance_id)
- inst.set_state(power_state.NOSTATE, 'shutting_down')
- yield self._conn.destroy(inst)
+ db.instance_state(
+ context, instance_id, power_state.NOSTATE, 'shutting_down')
+ yield self._conn.destroy(instance_ref)
+
# FIXME(ja): should we keep it in a terminated state for a bit?
- inst.delete()
+ db.instance_destroy(context, instance_id)
@defer.inlineCallbacks
@exception.wrap_exception
- def reboot_instance(self, instance_id):
- """ reboot an instance on this server
- KVM doesn't support reboot, so we terminate and restart """
- self.update_state(instance_id)
- instance = models.Instance.find(instance_id)
+ def reboot_instance(self, instance_id, context=None):
+ """Reboot an instance on this server.
+
+ KVM doesn't support reboot, so we terminate and restart.
+
+ """
+ self.update_state(instance_id, context)
+ instance_ref = db.instance_get(context, instance_id)
# FIXME(ja): this is only checking the model state - not state on disk?
- if instance.state != power_state.RUNNING:
+ if instance_ref['state'] != power_state.RUNNING:
raise exception.Error(
'trying to reboot a non-running'
- 'instance: %s (state: %s excepted: %s)' % (instance.name, instance.state, power_state.RUNNING))
+ 'instance: %s (state: %s excepted: %s)' %
+ (instance_ref['name'],
+ instance_ref['state'],
+ power_state.RUNNING))
- logging.debug('rebooting instance %s' % instance.name)
- instance.set_state(power_state.NOSTATE, 'rebooting')
- yield self._conn.reboot(instance)
- self.update_state(instance_id)
+ logging.debug('rebooting instance %s' % instance_ref['name'])
+ db.instance_state(
+ context, instance_id, power_state.NOSTATE, 'rebooting')
+ yield self._conn.reboot(instance_ref)
+ self.update_state(instance_id, context)
@exception.wrap_exception
- def get_console_output(self, instance_id):
- """ send the console output for an instance """
+ def get_console_output(self, instance_id, context=None):
+ """Send the console output for an instance."""
# FIXME: Abstract this for Xen
logging.debug("Getting console output for %s" % (instance_id))
- inst = models.Instance.find(instance_id)
+ instance_ref = db.instance_get(context, instance_id)
if FLAGS.connection_type == 'libvirt':
- fname = os.path.abspath(
- os.path.join(FLAGS.instances_path, inst.name, 'console.log'))
+ fname = os.path.abspath(os.path.join(FLAGS.instances_path,
+ instance_ref['name'],
+ 'console.log'))
with open(fname, 'r') as f:
output = f.read()
else:
@@ -169,32 +168,35 @@ class ComputeService(service.Service):
@defer.inlineCallbacks
@exception.wrap_exception
- def attach_volume(self, instance_id = None,
- volume_id = None, mountpoint = None):
- volume = volume_service.get_volume(volume_id)
+ def attach_volume(self, instance_id=None, volume_id=None, mountpoint=None,
+ context=None):
+ """Attach a volume to an instance."""
+ # TODO(termie): check that instance_id exists
+ volume_ref = volume_get(context, volume_id)
yield self._init_aoe()
yield process.simple_execute(
"sudo virsh attach-disk %s /dev/etherd/%s %s" %
(instance_id,
volume['aoe_device'],
mountpoint.rpartition('/dev/')[2]))
- volume.finish_attach()
+ volume_attached(context, volume_id)
defer.returnValue(True)
@defer.inlineCallbacks
- def _init_aoe(self):
- yield process.simple_execute("sudo aoe-discover")
- yield process.simple_execute("sudo aoe-stat")
-
- @defer.inlineCallbacks
@exception.wrap_exception
- def detach_volume(self, instance_id, volume_id):
- """ detach a volume from an instance """
+ def detach_volume(self, instance_id, volume_id, context=None):
+ """Detach a volume from an instance."""
# despite the documentation, virsh detach-disk just wants the device
# name without the leading /dev/
- volume = volume_service.get_volume(volume_id)
+ # TODO(termie): check that instance_id exists
+ volume_ref = volume_get(context, volume_id)
target = volume['mountpoint'].rpartition('/dev/')[2]
yield process.simple_execute(
"sudo virsh detach-disk %s %s " % (instance_id, target))
- volume.finish_detach()
+ volume_detached(context, volume_id)
defer.returnValue(True)
+
+ @defer.inlineCallbacks
+ def _init_aoe(self):
+ yield process.simple_execute("sudo aoe-discover")
+ yield process.simple_execute("sudo aoe-stat")
diff --git a/nova/db/__init__.py b/nova/db/__init__.py
new file mode 100644
index 000000000..2d893cb36
--- /dev/null
+++ b/nova/db/__init__.py
@@ -0,0 +1,3 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+from nova.db.api import *
diff --git a/nova/db/api.py b/nova/db/api.py
new file mode 100644
index 000000000..c1b2dee0d
--- /dev/null
+++ b/nova/db/api.py
@@ -0,0 +1,53 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+from nova import flags
+from nova import utils
+
+
+FLAGS = flags.FLAGS
+flags.DEFINE_string('db_backend', 'sqlalchemy',
+ 'The backend to use for db')
+
+
+_impl = utils.LazyPluggable(FLAGS['db_backend'],
+ sqlalchemy='nova.db.sqlalchemy.api')
+
+
+def instance_destroy(context, instance_id):
+ """Destroy the instance or raise if it does not exist."""
+ return _impl.instance_destroy(context, instance_id)
+
+
+def instance_get(context, instance_id):
+ """Get an instance or raise if it does not exist."""
+ return _impl.instance_get(context, instance_id)
+
+
+def instance_state(context, instance_id, state, description=None):
+ """Set the state of an instance."""
+ return _impl.instance_state(context, instance_id, state, description)
+
+
+def instance_update(context, instance_id, new_values):
+ """Set the given properties on an instance and update it.
+
+ Raises if instance does not exist.
+
+ """
+ return _impl.instance_update(context, instance_id, new_values)
+
+
+def volume_get(context, volume_id):
+ """Get a volume or raise if it does not exist."""
+ return _impl.volume_get(context, volume_id)
+
+
+def volume_attached(context, volume_id):
+ """Ensure that a volume is set as attached."""
+ return _impl.volume_attached(context, volume_id)
+
+
+def volume_detached(context, volume_id):
+ """Ensure that a volume is set as detached."""
+ return _impl.volume_detached(context, volume_id)
+
diff --git a/nova/db/sqlalchemy/__init__.py b/nova/db/sqlalchemy/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/nova/db/sqlalchemy/__init__.py
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
new file mode 100644
index 000000000..6d9f5fe5f
--- /dev/null
+++ b/nova/db/sqlalchemy/api.py
@@ -0,0 +1,43 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+from nova import models
+
+
+def instance_destroy(context, instance_id):
+ instance_ref = instance_get(context, instance_id)
+ instance_ref.delete()
+
+
+def instance_get(context, instance_id):
+ return models.Instance.find(instance_id)
+
+
+def instance_state(context, instance_id, state, description=None):
+ instance_ref = instance_get(context, instance_id)
+ instance_ref.set_state(state, description)
+
+
+def instance_update(context, instance_id, properties):
+ instance_ref = instance_get(context, instance_id)
+ for k, v in properties.iteritems():
+ instance_ref[k] = v
+ instance_ref.save()
+
+
+def volume_get(context, volume_id):
+ return models.Volume.find(volume_id)
+
+
+def volume_attached(context, volume_id):
+ volume_ref = volume_get(context, volume_id)
+ volume_ref['attach_status'] = 'attached'
+ volume_ref.save()
+
+
+def volume_detached(context, volume_id):
+ volume_ref = volume_get(context, volume_id)
+ volume_ref['instance_id'] = None
+ volume_ref['mountpoint'] = None
+ volume_ref['status'] = 'available'
+ volume_ref['attach_status'] = 'detached'
+ volume_ref.save()
diff --git a/nova/models.py b/nova/models.py
index d0b66d9b7..ea529713c 100644
--- a/nova/models.py
+++ b/nova/models.py
@@ -100,6 +100,12 @@ class NovaBase(object):
session = NovaBase.get_session()
session.refresh(self)
+ def __setitem__(self, key, value):
+ setattr(self, key, value)
+
+ def __getitem__(self, key):
+ return getattr(self, key)
+
class Image(Base, NovaBase):
__tablename__ = 'images'
diff --git a/nova/utils.py b/nova/utils.py
index e826f9b71..9e12a5301 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -142,3 +142,36 @@ def isotime(at=None):
def parse_isotime(timestr):
return datetime.datetime.strptime(timestr, TIME_FORMAT)
+
+
+
+class LazyPluggable(object):
+ """A pluggable backend loaded lazily based on some value."""
+
+ def __init__(self, pivot, **backends):
+ self.__backends = backends
+ self.__pivot = pivot
+ self.__backend = None
+
+ def __get_backend(self):
+ if not self.__backend:
+ backend_name = self.__pivot.value
+ if backend_name not in self.__backends:
+ raise exception.Error('Invalid backend: %s' % backend_name)
+
+ backend = self.__backends[backend_name]
+ if type(backend) == type(tuple()):
+ name = backend[0]
+ fromlist = backend[1]
+ else:
+ name = backend
+ fromlist = backend
+
+ self.__backend = __import__(name, None, None, fromlist)
+ logging.error('backend %s', self.__backend)
+ return self.__backend
+
+ def __getattr__(self, key):
+ backend = self.__get_backend()
+ return getattr(backend, key)
+