merge recent revision(version of 2010/12/28)

Change: 
   1. Use greenthread instead of defer at nova.virt.libvirt_conn.live_migration.
   2. Move nova.scheduler.manager.live_migration to nova.scheduler.driver
   3. Move nova.scheduler.manager.has_enough_resource to nova.scheduler.driver
   4. Any check routine in nova-manage.instance.live_migration is moved to
      nova.scheduler.driver.schedule_live_migration.
This commit is contained in:
masumotok
2010-12-31 04:03:37 +09:00
89 changed files with 2605 additions and 2350 deletions

View File

@@ -19,11 +19,11 @@
<mordred@inaugust.com> <mordred@hudson> <mordred@inaugust.com> <mordred@hudson>
<paul@openstack.org> <pvoccio@castor.local> <paul@openstack.org> <pvoccio@castor.local>
<paul@openstack.org> <paul.voccio@rackspace.com> <paul@openstack.org> <paul.voccio@rackspace.com>
<soren.hansen@rackspace.com> <soren@linux2go.dk>
<todd@ansolabs.com> <todd@lapex> <todd@ansolabs.com> <todd@lapex>
<todd@ansolabs.com> <todd@rubidine.com> <todd@ansolabs.com> <todd@rubidine.com>
<vishvananda@gmail.com> <vishvananda@yahoo.com> <vishvananda@gmail.com> <vishvananda@yahoo.com>
<vishvananda@gmail.com> <root@mirror.nasanebula.net> <vishvananda@gmail.com> <root@mirror.nasanebula.net>
# These are from people who failed to set a proper committer <vishvananda@gmail.com> <root@ubuntu>
. <root@tonbuntu> <sleepsonthefloor@gmail.com> <root@tonbuntu>
. <laner@controller> <rlane@wikimedia.org> <laner@controller>
. <root@ubuntu>

10
Authors
View File

@@ -3,8 +3,12 @@ Anne Gentle <anne@openstack.org>
Anthony Young <sleepsonthefloor@gmail.com> Anthony Young <sleepsonthefloor@gmail.com>
Armando Migliaccio <Armando.Migliaccio@eu.citrix.com> Armando Migliaccio <Armando.Migliaccio@eu.citrix.com>
Chris Behrens <cbehrens@codestud.com> Chris Behrens <cbehrens@codestud.com>
Chmouel Boudjnah <chmouel@chmouel.com>
David Pravec <David.Pravec@danix.org>
Dean Troyer <dtroyer@gmail.com> Dean Troyer <dtroyer@gmail.com>
Devin Carlen <devin.carlen@gmail.com> Devin Carlen <devin.carlen@gmail.com>
Ed Leafe <ed@leafe.com>
Eldar Nugaev <enugaev@griddynamics.com>
Eric Day <eday@oddments.org> Eric Day <eday@oddments.org>
Ewan Mellor <ewan.mellor@citrix.com> Ewan Mellor <ewan.mellor@citrix.com>
Hisaki Ohara <hisaki.ohara@intel.com> Hisaki Ohara <hisaki.ohara@intel.com>
@@ -12,6 +16,7 @@ Jay Pipes <jaypipes@gmail.com>
Jesse Andrews <anotherjesse@gmail.com> Jesse Andrews <anotherjesse@gmail.com>
Joe Heck <heckj@mac.com> Joe Heck <heckj@mac.com>
Joel Moore <joelbm24@gmail.com> Joel Moore <joelbm24@gmail.com>
Jonathan Bryce <jbryce@jbryce.com>
Josh Kearney <josh.kearney@rackspace.com> Josh Kearney <josh.kearney@rackspace.com>
Joshua McKenty <jmckenty@gmail.com> Joshua McKenty <jmckenty@gmail.com>
Justin Santa Barbara <justin@fathomdb.com> Justin Santa Barbara <justin@fathomdb.com>
@@ -20,8 +25,13 @@ Michael Gundlach <michael.gundlach@rackspace.com>
Monty Taylor <mordred@inaugust.com> Monty Taylor <mordred@inaugust.com>
Paul Voccio <paul@openstack.org> Paul Voccio <paul@openstack.org>
Rick Clark <rick@openstack.org> Rick Clark <rick@openstack.org>
Ryan Lane <rlane@wikimedia.org>
Ryan Lucio <rlucio@internap.com>
Sandy Walsh <sandy.walsh@rackspace.com>
Soren Hansen <soren.hansen@rackspace.com> Soren Hansen <soren.hansen@rackspace.com>
Thierry Carrez <thierry@openstack.org>
Todd Willey <todd@ansolabs.com> Todd Willey <todd@ansolabs.com>
Trey Morris <trey.morris@rackspace.com>
Vishvananda Ishaya <vishvananda@gmail.com> Vishvananda Ishaya <vishvananda@gmail.com>
Youcef Laribi <Youcef.Laribi@eu.citrix.com> Youcef Laribi <Youcef.Laribi@eu.citrix.com>
Zhixue Wu <Zhixue.Wu@citrix.com> Zhixue Wu <Zhixue.Wu@citrix.com>

View File

@@ -16,16 +16,24 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
# ARG is the id of the user # $1 is the id of the project and $2 is the subject of the cert
export SUBJ="/C=US/ST=California/L=MountainView/O=AnsoLabs/OU=NovaDev/CN=customer-intCA-$1" NAME=$1
mkdir INTER/$1 SUBJ=$2
cd INTER/$1 mkdir -p projects/$NAME
cd projects/$NAME
cp ../../openssl.cnf.tmpl openssl.cnf cp ../../openssl.cnf.tmpl openssl.cnf
sed -i -e s/%USERNAME%/$1/g openssl.cnf sed -i -e s/%USERNAME%/$NAME/g openssl.cnf
mkdir certs crl newcerts private mkdir certs crl newcerts private
openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes
echo "10" > serial echo "10" > serial
touch index.txt touch index.txt
openssl genrsa -out private/cakey.pem 1024 -config ./openssl.cnf -batch -nodes # NOTE(vish): Disabling intermediate ca's because we don't actually need them.
openssl req -new -sha2 -key private/cakey.pem -out ../../reqs/inter$1.csr -batch -subj "$SUBJ" # It makes more sense to have each project have its own root ca.
cd ../../ # openssl genrsa -out private/cakey.pem 1024 -config ./openssl.cnf -batch -nodes
openssl ca -extensions v3_ca -days 365 -out INTER/$1/cacert.pem -in reqs/inter$1.csr -config openssl.cnf -batch # openssl req -new -sha256 -key private/cakey.pem -out ../../reqs/inter$NAME.csr -batch -subj "$SUBJ"
openssl ca -gencrl -config ./openssl.cnf -out crl.pem
if [ "`id -u`" != "`grep nova /etc/passwd | cut -d':' -f3`" ]; then
sudo chown -R nova:nogroup .
fi
# cd ../../
# openssl ca -extensions v3_ca -days 365 -out INTER/$NAME/cacert.pem -in reqs/inter$NAME.csr -config openssl.cnf -batch

View File

@@ -25,4 +25,5 @@ else
openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes
touch index.txt touch index.txt
echo "10" > serial echo "10" > serial
openssl ca -gencrl -config ./openssl.cnf -out crl.pem
fi fi

34
nova/tests/validator_unittest.py → CA/genvpn.sh Normal file → Executable file
View File

@@ -1,3 +1,4 @@
#!/bin/bash
# vim: tabstop=4 shiftwidth=4 softtabstop=4 # vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the # Copyright 2010 United States Government as represented by the
@@ -16,27 +17,20 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import logging # This gets zipped and run on the cloudpipe-managed OpenVPN server
import unittest NAME=$1
SUBJ=$2
from nova import flags mkdir -p projects/$NAME
from nova import test cd projects/$NAME
from nova import validate
# generate a server priv key
openssl genrsa -out server.key 2048
class ValidationTestCase(test.TrialTestCase): # generate a server CSR
def setUp(self): openssl req -new -key server.key -out server.csr -batch -subj "$SUBJ"
super(ValidationTestCase, self).setUp()
def tearDown(self): novauid=`getent passwd nova | awk -F: '{print $3}'`
super(ValidationTestCase, self).tearDown() if [ ! -z "${novauid}" ] && [ "`id -u`" != "${novauid}" ]; then
sudo chown -R nova:nogroup .
def test_type_validation(self): fi
self.assertTrue(type_case("foo", 5, 1))
self.assertRaises(TypeError, type_case, "bar", "5", 1)
self.assertRaises(TypeError, type_case, None, 5, 1)
@validate.typetest(instanceid=str, size=int, number_of_instances=int)
def type_case(instanceid, size, number_of_instances):
return True

View File

@@ -24,7 +24,6 @@ dir = .
[ ca ] [ ca ]
default_ca = CA_default default_ca = CA_default
unique_subject = no
[ CA_default ] [ CA_default ]
serial = $dir/serial serial = $dir/serial
@@ -32,6 +31,8 @@ database = $dir/index.txt
new_certs_dir = $dir/newcerts new_certs_dir = $dir/newcerts
certificate = $dir/cacert.pem certificate = $dir/cacert.pem
private_key = $dir/private/cakey.pem private_key = $dir/private/cakey.pem
unique_subject = no
default_crl_days = 365
default_days = 365 default_days = 365
default_md = md5 default_md = md5
preserve = no preserve = no

View File

@@ -13,7 +13,7 @@ include nova/cloudpipe/client.ovpn.template
include nova/compute/fakevirtinstance.xml include nova/compute/fakevirtinstance.xml
include nova/compute/interfaces.template include nova/compute/interfaces.template
include nova/virt/interfaces.template include nova/virt/interfaces.template
include nova/virt/libvirt.*.xml.template include nova/virt/libvirt*.xml.template
include nova/tests/CA/ include nova/tests/CA/
include nova/tests/CA/cacert.pem include nova/tests/CA/cacert.pem
include nova/tests/CA/private/ include nova/tests/CA/private/

View File

@@ -147,7 +147,7 @@
3. error checking 3. error checking
When live migration fails somehow, error message shows at: When live migration fails somehow, error messages are shown at:
a. scheduler logfile a. scheduler logfile
b. source compute node logfile b. source compute node logfile
c. dest compute node logfile c. dest compute node logfile

View File

@@ -17,10 +17,10 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""
Nova API daemon.
"""
"""Starter script for Nova API."""
import gettext
import os import os
import sys import sys
@@ -32,9 +32,13 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
gettext.install('nova', unicode=1)
from nova import api
from nova import flags from nova import flags
from nova import utils from nova import utils
from nova import server from nova import wsgi
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_integer('osapi_port', 8774, 'OpenStack API port') flags.DEFINE_integer('osapi_port', 8774, 'OpenStack API port')
@@ -43,15 +47,10 @@ flags.DEFINE_integer('ec2api_port', 8773, 'EC2 API port')
flags.DEFINE_string('ec2api_host', '0.0.0.0', 'EC2 API host') flags.DEFINE_string('ec2api_host', '0.0.0.0', 'EC2 API host')
def main(_args): if __name__ == '__main__':
from nova import api utils.default_flagfile()
from nova import wsgi FLAGS(sys.argv)
server = wsgi.Server() server = wsgi.Server()
server.start(api.API('os'), FLAGS.osapi_port, host=FLAGS.osapi_host) server.start(api.API('os'), FLAGS.osapi_port, host=FLAGS.osapi_host)
server.start(api.API('ec2'), FLAGS.ec2api_port, host=FLAGS.ec2api_host) server.start(api.API('ec2'), FLAGS.ec2api_port, host=FLAGS.ec2api_host)
server.wait() server.wait()
if __name__ == '__main__':
utils.default_flagfile()
server.serve('nova-api', main)

68
bin/nova-combined Executable file
View File

@@ -0,0 +1,68 @@
#!/usr/bin/env python
# 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.
"""Combined starter script for Nova services."""
import eventlet
eventlet.monkey_patch()
import gettext
import os
import sys
# If ../nova/__init__.py exists, add ../ to Python search path, so that
# it will override what happens to be installed in /usr/(local/)lib/python...
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
os.pardir,
os.pardir))
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir)
gettext.install('nova', unicode=1)
from nova import api
from nova import flags
from nova import service
from nova import utils
from nova import wsgi
FLAGS = flags.FLAGS
flags.DEFINE_integer('osapi_port', 8774, 'OpenStack API port')
flags.DEFINE_string('osapi_host', '0.0.0.0', 'OpenStack API host')
flags.DEFINE_integer('ec2api_port', 8773, 'EC2 API port')
flags.DEFINE_string('ec2api_host', '0.0.0.0', 'EC2 API host')
if __name__ == '__main__':
utils.default_flagfile()
FLAGS(sys.argv)
compute = service.Service.create(binary='nova-compute')
network = service.Service.create(binary='nova-network')
volume = service.Service.create(binary='nova-volume')
scheduler = service.Service.create(binary='nova-scheduler')
#objectstore = service.Service.create(binary='nova-objectstore')
service.serve(compute, network, volume, scheduler)
server = wsgi.Server()
server.start(api.API('os'), FLAGS.osapi_port, host=FLAGS.osapi_host)
server.start(api.API('ec2'), FLAGS.ec2api_port, host=FLAGS.ec2api_host)
server.wait()

View File

@@ -17,10 +17,12 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """Starter script for Nova Compute."""
Twistd daemon for the nova compute nodes.
"""
import eventlet
eventlet.monkey_patch()
import gettext
import os import os
import sys import sys
@@ -32,14 +34,12 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
from nova import service gettext.install('nova', unicode=1)
from nova import twistd
from nova import utils
from nova import service
from nova import utils
if __name__ == '__main__': if __name__ == '__main__':
utils.default_flagfile() utils.default_flagfile()
twistd.serve(__file__) service.serve()
service.wait()
if __name__ == '__builtin__':
application = service.Service.create() # pylint: disable=C0103

View File

@@ -21,6 +21,7 @@
Handle lease database updates from DHCP servers. Handle lease database updates from DHCP servers.
""" """
import gettext
import logging import logging
import os import os
import sys import sys
@@ -33,6 +34,8 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
gettext.install('nova', unicode=1)
from nova import context from nova import context
from nova import db from nova import db
from nova import flags from nova import flags
@@ -107,7 +110,6 @@ def main():
FLAGS.num_networks = 5 FLAGS.num_networks = 5
path = os.path.abspath(os.path.join(os.path.dirname(__file__), path = os.path.abspath(os.path.join(os.path.dirname(__file__),
'..', '..',
'_trial_temp',
'nova.sqlite')) 'nova.sqlite'))
FLAGS.sql_connection = 'sqlite:///%s' % path FLAGS.sql_connection = 'sqlite:///%s' % path
action = argv[1] action = argv[1]

View File

@@ -21,6 +21,7 @@
Download images from Canonical Image Store Download images from Canonical Image Store
""" """
import gettext
import json import json
import os import os
import tempfile import tempfile
@@ -37,6 +38,8 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
gettext.install('nova', unicode=1)
from nova import flags from nova import flags
from nova import utils from nova import utils
from nova.objectstore import image from nova.objectstore import image

View File

@@ -21,6 +21,7 @@
Daemon for Nova RRD based instance resource monitoring. Daemon for Nova RRD based instance resource monitoring.
""" """
import gettext
import os import os
import logging import logging
import sys import sys
@@ -34,6 +35,8 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
gettext.install('nova', unicode=1)
from nova import utils from nova import utils
from nova import twistd from nova import twistd
from nova.compute import monitor from nova.compute import monitor

View File

@@ -53,6 +53,7 @@
CLI interface for nova management. CLI interface for nova management.
""" """
import gettext
import logging import logging
import os import os
import sys import sys
@@ -68,20 +69,19 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
gettext.install('nova', unicode=1)
from nova import context from nova import context
from nova import crypto
from nova import db from nova import db
from nova import exception from nova import exception
from nova import flags from nova import flags
from nova import quota from nova import quota
from nova import utils from nova import utils
from nova.auth import manager from nova.auth import manager
from nova.cloudpipe import pipelib
#added by masumotok
from nova import rpc from nova import rpc
# added by masumotok from nova.cloudpipe import pipelib
from nova.api.ec2 import cloud from nova.api.ec2 import cloud
# added by masumotok
from nova.compute import power_state
@@ -100,47 +100,43 @@ class VpnCommands(object):
self.manager = manager.AuthManager() self.manager = manager.AuthManager()
self.pipe = pipelib.CloudPipe() self.pipe = pipelib.CloudPipe()
def list(self): def list(self, project=None):
"""Print a listing of the VPNs for all projects.""" """Print a listing of the VPN data for one or all projects.
args: [project=all]"""
print "%-12s\t" % 'project', print "%-12s\t" % 'project',
print "%-20s\t" % 'ip:port', print "%-20s\t" % 'ip:port',
print "%-20s\t" % 'private_ip',
print "%s" % 'state' print "%s" % 'state'
for project in self.manager.get_projects(): if project:
projects = [self.manager.get_project(project)]
else:
projects = self.manager.get_projects()
# NOTE(vish): This hits the database a lot. We could optimize
# by getting all networks in one query and all vpns
# in aother query, then doing lookups by project
for project in projects:
print "%-12s\t" % project.name, print "%-12s\t" % project.name,
ipport = "%s:%s" % (project.vpn_ip, project.vpn_port)
try: print "%-20s\t" % ipport,
s = "%s:%s" % (project.vpn_ip, project.vpn_port) ctxt = context.get_admin_context()
except exception.NotFound: vpn = db.instance_get_project_vpn(ctxt, project.id)
s = "None"
print "%-20s\t" % s,
vpn = self._vpn_for(project.id)
if vpn: if vpn:
command = "ping -c1 -w1 %s > /dev/null; echo $?" address = None
out, _err = utils.execute(command % vpn['private_dns_name'], state = 'down'
check_exit_code=False) if vpn.get('fixed_ip', None):
if out.strip() == '0': address = vpn['fixed_ip']['address']
net = 'up' if project.vpn_ip and utils.vpn_ping(project.vpn_ip,
else: project.vpn_port):
net = 'down' state = 'up'
print vpn['private_dns_name'], print address,
print vpn['node_name'], print vpn['host'],
print vpn['instance_id'], print vpn['ec2_id'],
print vpn['state_description'], print vpn['state_description'],
print net print state
else: else:
print None print None
def _vpn_for(self, project_id):
"""Get the VPN instance for a project ID."""
for instance in db.instance_get_all(context.get_admin_context()):
if (instance['image_id'] == FLAGS.vpn_image_id
and not instance['state_description'] in
['shutting_down', 'shutdown']
and instance['project_id'] == project_id):
return instance
def spawn(self): def spawn(self):
"""Run all VPNs.""" """Run all VPNs."""
for p in reversed(self.manager.get_projects()): for p in reversed(self.manager.get_projects()):
@@ -153,6 +149,21 @@ class VpnCommands(object):
"""Start the VPN for a given project.""" """Start the VPN for a given project."""
self.pipe.launch_vpn_instance(project_id) self.pipe.launch_vpn_instance(project_id)
def change(self, project_id, ip, port):
"""Change the ip and port for a vpn.
args: project, ip, port"""
project = self.manager.get_project(project_id)
if not project:
print 'No project %s' % (project_id)
return
admin = context.get_admin_context()
network_ref = db.project_get_network(admin, project_id)
db.network_update(admin,
network_ref['id'],
{'vpn_public_address': ip,
'vpn_public_port': int(port)})
class ShellCommands(object): class ShellCommands(object):
def bpython(self): def bpython(self):
@@ -299,6 +310,14 @@ class UserCommands(object):
is_admin = False is_admin = False
self.manager.modify_user(name, access_key, secret_key, is_admin) self.manager.modify_user(name, access_key, secret_key, is_admin)
def revoke(self, user_id, project_id=None):
"""revoke certs for a user
arguments: user_id [project_id]"""
if project_id:
crypto.revoke_certs_by_user_and_project(user_id, project_id)
else:
crypto.revoke_certs_by_user(user_id)
class ProjectCommands(object): class ProjectCommands(object):
"""Class for managing projects.""" """Class for managing projects."""
@@ -366,9 +385,14 @@ class ProjectCommands(object):
def zipfile(self, project_id, user_id, filename='nova.zip'): def zipfile(self, project_id, user_id, filename='nova.zip'):
"""Exports credentials for project to a zip file """Exports credentials for project to a zip file
arguments: project_id user_id [filename='nova.zip]""" arguments: project_id user_id [filename='nova.zip]"""
zip_file = self.manager.get_credentials(user_id, project_id) try:
with open(filename, 'w') as f: zip_file = self.manager.get_credentials(user_id, project_id)
f.write(zip_file) with open(filename, 'w') as f:
f.write(zip_file)
except db.api.NoMoreNetworks:
print ('No more networks available. If this is a new '
'installation, you need\nto call something like this:\n\n'
' nova-manage network create 10.0.0.0/8 10 64\n\n')
class FloatingIpCommands(object): class FloatingIpCommands(object):
@@ -431,7 +455,7 @@ class NetworkCommands(object):
int(network_size), int(vlan_start), int(network_size), int(vlan_start),
int(vpn_start)) int(vpn_start))
# this class is added by masumotok
class InstanceCommands(object): class InstanceCommands(object):
"""Class for mangaging VM instances.""" """Class for mangaging VM instances."""
@@ -441,43 +465,29 @@ class InstanceCommands(object):
logging.basicConfig() logging.basicConfig()
ctxt = context.get_admin_context() ctxt = context.get_admin_context()
# 1. whether destination host exists try:
host_ref = db.host_get_by_name(ctxt, dest)
# 2. whether instance exists and running
# try-catch clause is necessary because only internal_id is shown
# when NotFound exception occurs. it isnot understandable to admins.
try :
internal_id = cloud.ec2_id_to_internal_id(ec2_id) internal_id = cloud.ec2_id_to_internal_id(ec2_id)
instance_ref = db.instance_get_by_internal_id(ctxt, internal_id) instance_ref = db.instance_get_by_internal_id(ctxt, internal_id)
except exception.NotFound : instance_id = instance_ref['id']
print 'Not found instance_id(%s (internal_id:%s))' % ( ec2_id, internal_id) except exception.NotFound as e:
raise msg = _('instance(%s) is not found')
e.args += (msg % ec2_id,)
raise e
if power_state.RUNNING != instance_ref['state'] or \
'running' != instance_ref['state_description']:
raise exception.Invalid('Instance(%s) is not running' % ec2_id)
# 3. the host where instance is running and dst host is not same
if dest == instance_ref['host'] :
msg = '%s is where %s is running now. choose other host.' % (dest, ec2_id)
raise exception.Invalid(msg)
# 4. live migration
ret = rpc.call(ctxt, ret = rpc.call(ctxt,
FLAGS.scheduler_topic, FLAGS.scheduler_topic,
{ "method": "live_migration", {"method": "live_migration",
"args": {"ec2_id": ec2_id, "args": {"instance_id": instance_id,
"dest":dest}}) "dest": dest,
"topic": FLAGS.compute_topic}})
if None != ret : if None != ret:
raise ret raise ret
print 'Finished all procedure. check instance are migrated successfully' print 'Finished all procedure. Check migrating finishes successfully'
print 'check status by using euca-describe-instances.' print 'check status by using euca-describe-instances.'
# this class is created by masumotok
class HostCommands(object): class HostCommands(object):
"""Class for mangaging host(physical nodes).""" """Class for mangaging host(physical nodes)."""
@@ -485,17 +495,18 @@ class HostCommands(object):
def list(self): def list(self):
"""describe host list.""" """describe host list."""
# to supress msg: No handlers could be found for logger "amqplib" # To supress msg: No handlers could be found for logger "amqplib"
logging.basicConfig() logging.basicConfig()
host_refs = db.host_get_all(context.get_admin_context()) host_refs = db.host_get_all(context.get_admin_context())
for host_ref in host_refs: for host_ref in host_refs:
print host_ref['name'] print host_ref['name']
def show(self, host): def show(self, host):
"""describe cpu/memory/hdd info for host.""" """describe cpu/memory/hdd info for host."""
# to supress msg: No handlers could be found for logger "amqplib" # To supress msg: No handlers could be found for logger "amqplib"
logging.basicConfig() logging.basicConfig()
result = rpc.call(context.get_admin_context(), result = rpc.call(context.get_admin_context(),
@@ -503,41 +514,27 @@ class HostCommands(object):
{"method": "show_host_resource", {"method": "show_host_resource",
"args": {"host": host}}) "args": {"host": host}})
# checing result msg format is necessary, that will have done # Checking result msg format is necessary, that will have done
# when this feture is included in API. # when this feture is included in API.
if dict != type(result): if dict != type(result):
print 'Unexpected error occurs' print 'Unexpected error occurs'
elif not result['ret'] : elif not result['ret']:
print '%s' % result['msg'] print '%s' % result['msg']
else : else:
cpu = result['phy_resource']['vcpus'] cpu = result['phy_resource']['vcpus']
mem = result['phy_resource']['memory_mb'] mem = result['phy_resource']['memory_mb']
hdd = result['phy_resource']['local_gb'] hdd = result['phy_resource']['local_gb']
print 'HOST\t\tPROJECT\t\tcpu\tmem(mb)\tdisk(gb)' print 'HOST\t\tPROJECT\t\tcpu\tmem(mb)\tdisk(gb)'
print '%s\t\t\t%s\t%s\t%s' % ( host,cpu, mem, hdd) print '%s\t\t\t%s\t%s\t%s' % (host, cpu, mem, hdd)
for p_id, val in result['usage'].items() : for p_id, val in result['usage'].items():
print '%s\t%s\t\t%s\t%s\t%s' % ( host, print '%s\t%s\t\t%s\t%s\t%s' % (host,
p_id, p_id,
val['vcpus'], val['vcpus'],
val['memory_mb'], val['memory_mb'],
val['local_gb']) val['local_gb'])
def has_keys(self, dic, keys):
not_found = [ key for key in keys if not dict.has_key(key) ]
return ( (0 == len(not_found)), not_found )
# modified by masumotok
#CATEGORIES = [
# ('user', UserCommands),
# ('project', ProjectCommands),
# ('role', RoleCommands),
# ('shell', ShellCommands),
# ('vpn', VpnCommands),
# ('floating', FloatingIpCommands),
# ('network', NetworkCommands)]
CATEGORIES = [ CATEGORIES = [
('user', UserCommands), ('user', UserCommands),
('project', ProjectCommands), ('project', ProjectCommands),
@@ -547,7 +544,7 @@ CATEGORIES = [
('floating', FloatingIpCommands), ('floating', FloatingIpCommands),
('network', NetworkCommands), ('network', NetworkCommands),
('instance', InstanceCommands), ('instance', InstanceCommands),
('host',HostCommands)] ('host', HostCommands)]
def lazy_match(name, key_value_tuples): def lazy_match(name, key_value_tuples):
"""Finds all objects that have a key that case insensitively contains """Finds all objects that have a key that case insensitively contains

View File

@@ -17,10 +17,12 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """Starter script for Nova Network."""
Twistd daemon for the nova network nodes.
"""
import eventlet
eventlet.monkey_patch()
import gettext
import os import os
import sys import sys
@@ -32,14 +34,12 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
from nova import service gettext.install('nova', unicode=1)
from nova import twistd
from nova import utils
from nova import service
from nova import utils
if __name__ == '__main__': if __name__ == '__main__':
utils.default_flagfile() utils.default_flagfile()
twistd.serve(__file__) service.serve()
service.wait()
if __name__ == '__builtin__':
application = service.Service.create() # pylint: disable-msg=C0103

View File

@@ -21,6 +21,7 @@
Twisted daemon for nova objectstore. Supports S3 API. Twisted daemon for nova objectstore. Supports S3 API.
""" """
import gettext
import os import os
import sys import sys
@@ -32,6 +33,8 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
gettext.install('nova', unicode=1)
from nova import flags from nova import flags
from nova import utils from nova import utils
from nova import twistd from nova import twistd

View File

@@ -17,10 +17,12 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """Starter script for Nova Scheduler."""
Twistd daemon for the nova scheduler nodes.
"""
import eventlet
eventlet.monkey_patch()
import gettext
import os import os
import sys import sys
@@ -32,14 +34,12 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
from nova import service gettext.install('nova', unicode=1)
from nova import twistd
from nova import utils
from nova import service
from nova import utils
if __name__ == '__main__': if __name__ == '__main__':
utils.default_flagfile() utils.default_flagfile()
twistd.serve(__file__) service.serve()
service.wait()
if __name__ == '__builtin__':
application = service.Service.create()

View File

@@ -17,10 +17,12 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """Starter script for Nova Volume."""
Twistd daemon for the nova volume nodes.
"""
import eventlet
eventlet.monkey_patch()
import gettext
import os import os
import sys import sys
@@ -32,14 +34,12 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
from nova import service gettext.install('nova', unicode=1)
from nova import twistd
from nova import utils
from nova import service
from nova import utils
if __name__ == '__main__': if __name__ == '__main__':
utils.default_flagfile() utils.default_flagfile()
twistd.serve(__file__) service.serve()
service.wait()
if __name__ == '__builtin__':
application = service.Service.create() # pylint: disable-msg=C0103

View File

@@ -1,5 +1,8 @@
import gettext
import os import os
gettext.install('nova')
from nova import utils from nova import utils
def setup(app): def setup(app):

View File

@@ -23,7 +23,7 @@ In Nova, users organize their cloud resources in projects. A Nova project consis
Nova Network Strategies Nova Network Strategies
----------------------- -----------------------
Currently, Nova supports three kinds of networks, implemented in three "Network Manager" types respectively: Flat Network Manager, Flat DHCP Network Manager, and VLAN Network Manager. The three kinds of networks can c-exist in a cloud system. However, the scheduler for selecting the type of network for a given project is not yet implemented. Here is a brief description of each of the different network strategies, with a focus on the VLAN Manager in a separate section. Currently, Nova supports three kinds of networks, implemented in three "Network Manager" types respectively: Flat Network Manager, Flat DHCP Network Manager, and VLAN Network Manager. The three kinds of networks can co-exist in a cloud system. However, the scheduler for selecting the type of network for a given project is not yet implemented. Here is a brief description of each of the different network strategies, with a focus on the VLAN Manager in a separate section.
Read more about Nova network strategies here: Read more about Nova network strategies here:

View File

@@ -19,7 +19,7 @@ Installing Nova on Multiple Servers
=================================== ===================================
When you move beyond evaluating the technology and into building an actual When you move beyond evaluating the technology and into building an actual
production environemnt, you will need to know how to configure your datacenter production environment, you will need to know how to configure your datacenter
and how to deploy components across your clusters. This guide should help you and how to deploy components across your clusters. This guide should help you
through that process. through that process.
@@ -35,7 +35,6 @@ Requirements for a multi-node installation
* For a recommended HA setup, consider a MySQL master/slave replication, with as many slaves as you like, and probably a heartbeat to kick one of the slaves into being a master if it dies. * For a recommended HA setup, consider a MySQL master/slave replication, with as many slaves as you like, and probably a heartbeat to kick one of the slaves into being a master if it dies.
* For performance optimization, split reads and writes to the database. MySQL proxy is the easiest way to make this work if running MySQL. * For performance optimization, split reads and writes to the database. MySQL proxy is the easiest way to make this work if running MySQL.
Assumptions Assumptions
^^^^^^^^^^^ ^^^^^^^^^^^
@@ -69,14 +68,14 @@ Step 1 Use apt-get to get the latest code
It is highly likely that there will be errors when the nova services come up since they are not yet configured. Don't worry, you're only at step 1! It is highly likely that there will be errors when the nova services come up since they are not yet configured. Don't worry, you're only at step 1!
Step 2 Setup configuration files (installed in /etc/nova) Step 2 Setup configuration file (installed in /etc/nova)
--------------------------------------------------------- ---------------------------------------------------------
Note: CC_ADDR=<the external IP address of your cloud controller> Note: CC_ADDR=<the external IP address of your cloud controller>
1. These need to be defined in EACH configuration file Nova development has consolidated all .conf files to nova.conf as of November 2010. References to specific .conf files may be ignored.
:: #. These need to be defined in the nova.conf configuration file::
--sql_connection=mysql://root:nova@$CC_ADDR/nova # location of nova sql db --sql_connection=mysql://root:nova@$CC_ADDR/nova # location of nova sql db
--s3_host=$CC_ADDR # This is where nova is hosting the objectstore service, which --s3_host=$CC_ADDR # This is where nova is hosting the objectstore service, which
@@ -87,31 +86,14 @@ Note: CC_ADDR=<the external IP address of your cloud controller>
--ec2_url=http://$CC_ADDR:8773/services/Cloud --ec2_url=http://$CC_ADDR:8773/services/Cloud
--network_manager=nova.network.manager.FlatManager # simple, no-vlan networking type --network_manager=nova.network.manager.FlatManager # simple, no-vlan networking type
2. nova-manage specific flags
::
--fixed_range=<network/prefix> # ip network to use for VM guests, ex 192.168.2.64/26 --fixed_range=<network/prefix> # ip network to use for VM guests, ex 192.168.2.64/26
--network_size=<# of addrs> # number of ip addrs to use for VM guests, ex 64 --network_size=<# of addrs> # number of ip addrs to use for VM guests, ex 64
#. Create a nova group::
3. nova-network specific flags
::
--fixed_range=<network/prefix> # ip network to use for VM guests, ex 192.168.2.64/26
--network_size=<# of addrs> # number of ip addrs to use for VM guests, ex 64
4. Create a nova group
::
sudo addgroup nova sudo addgroup nova
5. nova-objectstore specific flags < no specific config needed > The Nova config file should have its owner set to root:nova, and mode set to 0640, since they contain your MySQL server's root password.
Config files should be have their owner set to root:nova, and mode set to 0640, since they contain your MySQL server's root password.
:: ::
@@ -121,7 +103,7 @@ Config files should be have their owner set to root:nova, and mode set to 0640,
Step 3 Setup the sql db Step 3 Setup the sql db
----------------------- -----------------------
1. First you 'preseed' (using vishy's :doc:`../quickstart`). Run this as root. 1. First you 'preseed' (using the Quick Start method :doc:`../quickstart`). Run this as root.
:: ::
@@ -161,7 +143,7 @@ Step 3 Setup the sql db
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION; GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
SET PASSWORD FOR 'root'@'%' = PASSWORD('nova'); SET PASSWORD FOR 'root'@'%' = PASSWORD('nova');
7. branch and install Nova 7. Branch and install Nova
:: ::
@@ -186,9 +168,7 @@ Step 4 Setup Nova environment
Note: The nova-manage service assumes that the first IP address is your network (like 192.168.0.0), that the 2nd IP is your gateway (192.168.0.1), and that the broadcast is the very last IP in the range you defined (192.168.0.255). If this is not the case you will need to manually edit the sql db 'networks' table.o. Note: The nova-manage service assumes that the first IP address is your network (like 192.168.0.0), that the 2nd IP is your gateway (192.168.0.1), and that the broadcast is the very last IP in the range you defined (192.168.0.255). If this is not the case you will need to manually edit the sql db 'networks' table.o.
On running this command, entries are made in the 'networks' and 'fixed_ips' table. However, one of the networks listed in the 'networks' table needs to be marked as bridge in order for the code to know that a bridge exists. We ended up doing this manually, (update query fired directly in the DB). Is there a better way to mark a network as bridged? On running this command, entries are made in the 'networks' and 'fixed_ips' table. However, one of the networks listed in the 'networks' table needs to be marked as bridge in order for the code to know that a bridge exists. The Network is marked as bridged automatically based on the type of network manager selected.
Update: This has been resolved w.e.f 27/10. network is marked as bridged automatically based on the type of n/w manager selected.
More networking details to create a network bridge for flat network More networking details to create a network bridge for flat network
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -233,7 +213,6 @@ unzip them in your home directory, and add them to your environment::
echo ". creds/novarc" >> ~/.bashrc echo ". creds/novarc" >> ~/.bashrc
~/.bashrc ~/.bashrc
Step 6 Restart all relevant services Step 6 Restart all relevant services
------------------------------------ ------------------------------------
@@ -249,8 +228,8 @@ Restart relevant nova services::
.. todo:: do we still need the content below? .. todo:: do we still need the content below?
Bare-metal Provisioning Bare-metal Provisioning Notes
----------------------- -----------------------------
To install the base operating system you can use PXE booting. To install the base operating system you can use PXE booting.

View File

@@ -9,7 +9,7 @@ The fastest way to get a test cloud running is through our :doc:`../quickstart`.
Step 1 and 2: Get the latest Nova code system software Step 1 and 2: Get the latest Nova code system software
------------------------------------------------------ ------------------------------------------------------
Depending on your system, the mehod for accomplishing this varies Depending on your system, the method for accomplishing this varies
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
@@ -63,8 +63,20 @@ You see an access key and a secret key export, such as these made-up ones:::
export EC2_ACCESS_KEY=4e6498a2-blah-blah-blah-17d1333t97fd export EC2_ACCESS_KEY=4e6498a2-blah-blah-blah-17d1333t97fd
export EC2_SECRET_KEY=0a520304-blah-blah-blah-340sp34k05bbe9a7 export EC2_SECRET_KEY=0a520304-blah-blah-blah-340sp34k05bbe9a7
Step 5: Create the network
--------------------------
Step 5: Create a project with the user you created Type or copy/paste in the following line to create a network prior to creating a project.
::
sudo nova-manage network create 10.0.0.0/8 1 64
For this command, the IP address is the cidr notation for your netmask, such as 192.168.1.0/24. The value 1 is the total number of networks you want made, and the 64 value is the total number of ips in all networks.
After running this command, entries are made in the 'networks' and 'fixed_ips' table in the database.
Step 6: Create a project with the user you created
-------------------------------------------------- --------------------------------------------------
Type or copy/paste in the following line to create a project named IRT (for Ice Road Truckers, of course) with the newly-created user named anne. Type or copy/paste in the following line to create a project named IRT (for Ice Road Truckers, of course) with the newly-created user named anne.
@@ -94,7 +106,7 @@ Type or copy/paste in the following line to create a project named IRT (for Ice
Data Base Updated Data Base Updated
Step 6: Unzip the nova.zip Step 7: Unzip the nova.zip
-------------------------- --------------------------
You should have a nova.zip file in your current working directory. Unzip it with this command: You should have a nova.zip file in your current working directory. Unzip it with this command:
@@ -116,7 +128,7 @@ You'll see these files extract.
extracting: cacert.pem extracting: cacert.pem
Step 7: Source the rc file Step 8: Source the rc file
-------------------------- --------------------------
Type or copy/paste the following to source the novarc file in your current working directory. Type or copy/paste the following to source the novarc file in your current working directory.
@@ -125,9 +137,9 @@ Type or copy/paste the following to source the novarc file in your current worki
. novarc . novarc
Step 8: Pat yourself on the back :) Step 9: Pat yourself on the back :)
----------------------------------- -----------------------------------
Congratulations, your cloud is up and running, youve created an admin user, retrieved the user's credentials and put them in your environment. Congratulations, your cloud is up and running, youve created an admin user, created a network, retrieved the user's credentials and put them in your environment.
Now you need an image. Now you need an image.

View File

@@ -54,6 +54,8 @@ Cloud computing offers different service models depending on the capabilities a
The US-based National Institute of Standards and Technology offers definitions for cloud computing The US-based National Institute of Standards and Technology offers definitions for cloud computing
and the service models that are emerging. and the service models that are emerging.
These definitions are summarized from http://csrc.nist.gov/groups/SNS/cloud-computing/.
SaaS - Software as a Service SaaS - Software as a Service
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -72,12 +74,15 @@ IaaS - Infrastructure as a Service
Provides infrastructure such as computer instances, network connections, and storage so that people Provides infrastructure such as computer instances, network connections, and storage so that people
can run any software or operating system. can run any software or operating system.
.. todo:: Use definitions from http://csrc.nist.gov/groups/SNS/cloud-computing/ and attribute NIST
Types of Cloud Deployments Types of Cloud Deployments
-------------------------- --------------------------
.. todo:: describe public/private/hybrid/etc
When you hear terms such as public cloud or private cloud, these refer to the deployment model for the cloud. A private cloud operates for a single organization, but can be managed on-premise or off-premise. A public cloud has an infrastructure that is available to the general public or a large industry group and is likely owned by a cloud services company.
The NIST also defines community cloud as shared by several organizations supporting a specific community with shared concerns.
A hybrid cloud can be a deployment model, as a composition of both public and private clouds, or a hybrid model for cloud computing may involve both virtual and physical servers.
Work in the Clouds Work in the Clouds
------------------ ------------------

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -26,7 +26,7 @@ Nova is written with the following design guidelines in mind:
* **Component based architecture**: Quickly add new behaviors * **Component based architecture**: Quickly add new behaviors
* **Highly available**: Scale to very serious workloads * **Highly available**: Scale to very serious workloads
* **Fault-Tollerant**: Isloated processes avoid cascading failures * **Fault-Tolerant**: Isolated processes avoid cascading failures
* **Recoverable**: Failures should be easy to diagnose, debug, and rectify * **Recoverable**: Failures should be easy to diagnose, debug, and rectify
* **Open Standards**: Be a reference implementation for a community-driven api * **Open Standards**: Be a reference implementation for a community-driven api
* **API Compatibility**: Nova strives to provide API-compatible with popular systems like Amazon EC2 * **API Compatibility**: Nova strives to provide API-compatible with popular systems like Amazon EC2
@@ -62,8 +62,6 @@ Administrator's Documentation
adminguide/single.node.install adminguide/single.node.install
adminguide/multi.node.install adminguide/multi.node.install
.. todo:: add swiftadmin
Developer Docs Developer Docs
============== ==============

View File

@@ -1,2 +1,48 @@
Installing the Live CD Installing the Live CD
====================== ======================
If you'd like to set up a sandbox installation of Nova, you can use one of these Live CD images.
If you don't already have VirtualBox installed, you can download it from http://www.virtualbox.org/wiki/Downloads.
Download the zip or iso file and then follow these steps to try Nova in a virtual environment.
http://c0047913.cdn1.cloudfiles.rackspacecloud.com/OpenStackNova.x86_64-2010.1.2.iso (OpenSUSE image; root password is "linux" for this image)
http://c0028699.cdn1.cloudfiles.rackspacecloud.com/nova-vm.zip (~900 MB) (log in information is nova/nova)
Once a VM is configured and started, here are the basics:
#. Login to Ubuntu using ID nova and Password nova.
#. Switch to running as sudo (enter nova when prompted for the password)::
sudo -s
#. To run Nova for the first time, enter::
cd /var/openstack/
#. Now that you're in the correct directory, enter::
./nova.sh run
.. image:: images/novashvirtually.png
If it's already running, use screen -ls, and when the nova screen is presented,then enter screen -d -r nova.
These are the steps to get an instance running (the image is already provided in this environment). Enter these commands in the "test" screen.
::
euca-add-keypair test > test.pem
chmod 600 test.pem
euca-run-instances -k test -t m1.tiny ami-tiny
euca-describe-instances
ssh -i test.pem root@10.0.0.3
To see output from the various workers, switch screen windows with Ctrl+A " (quotation mark).
.. image:: images/novascreens.png

View File

@@ -194,6 +194,7 @@ class HostInfo(object):
class NovaAdminClient(object): class NovaAdminClient(object):
def __init__( def __init__(
self, self,
clc_url=DEFAULT_CLC_URL, clc_url=DEFAULT_CLC_URL,

View File

@@ -37,7 +37,6 @@ class DbDriver(object):
def __init__(self): def __init__(self):
"""Imports the LDAP module""" """Imports the LDAP module"""
pass pass
db
def __enter__(self): def __enter__(self):
return self return self
@@ -83,7 +82,7 @@ class DbDriver(object):
user_ref = db.user_create(context.get_admin_context(), values) user_ref = db.user_create(context.get_admin_context(), values)
return self._db_user_to_auth_user(user_ref) return self._db_user_to_auth_user(user_ref)
except exception.Duplicate, e: except exception.Duplicate, e:
raise exception.Duplicate('User %s already exists' % name) raise exception.Duplicate(_('User %s already exists') % name)
def _db_user_to_auth_user(self, user_ref): def _db_user_to_auth_user(self, user_ref):
return {'id': user_ref['id'], return {'id': user_ref['id'],
@@ -105,8 +104,9 @@ class DbDriver(object):
"""Create a project""" """Create a project"""
manager = db.user_get(context.get_admin_context(), manager_uid) manager = db.user_get(context.get_admin_context(), manager_uid)
if not manager: if not manager:
raise exception.NotFound("Project can't be created because " raise exception.NotFound(_("Project can't be created because "
"manager %s doesn't exist" % manager_uid) "manager %s doesn't exist")
% manager_uid)
# description is a required attribute # description is a required attribute
if description is None: if description is None:
@@ -133,8 +133,8 @@ class DbDriver(object):
try: try:
project = db.project_create(context.get_admin_context(), values) project = db.project_create(context.get_admin_context(), values)
except exception.Duplicate: except exception.Duplicate:
raise exception.Duplicate("Project can't be created because " raise exception.Duplicate(_("Project can't be created because "
"project %s already exists" % name) "project %s already exists") % name)
for member in members: for member in members:
db.project_add_member(context.get_admin_context(), db.project_add_member(context.get_admin_context(),
@@ -155,8 +155,8 @@ class DbDriver(object):
if manager_uid: if manager_uid:
manager = db.user_get(context.get_admin_context(), manager_uid) manager = db.user_get(context.get_admin_context(), manager_uid)
if not manager: if not manager:
raise exception.NotFound("Project can't be modified because " raise exception.NotFound(_("Project can't be modified because "
"manager %s doesn't exist" % "manager %s doesn't exist") %
manager_uid) manager_uid)
values['project_manager'] = manager['id'] values['project_manager'] = manager['id']
if description: if description:
@@ -243,8 +243,8 @@ class DbDriver(object):
def _validate_user_and_project(self, user_id, project_id): def _validate_user_and_project(self, user_id, project_id):
user = db.user_get(context.get_admin_context(), user_id) user = db.user_get(context.get_admin_context(), user_id)
if not user: if not user:
raise exception.NotFound('User "%s" not found' % user_id) raise exception.NotFound(_('User "%s" not found') % user_id)
project = db.project_get(context.get_admin_context(), project_id) project = db.project_get(context.get_admin_context(), project_id)
if not project: if not project:
raise exception.NotFound('Project "%s" not found' % project_id) raise exception.NotFound(_('Project "%s" not found') % project_id)
return user, project return user, project

View File

@@ -15,7 +15,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
"""Fake LDAP server for test harness, backs to ReDIS. """Fake LDAP server for test harness.
This class does very little error checking, and knows nothing about ldap This class does very little error checking, and knows nothing about ldap
class definitions. It implements the minimum emulation of the python ldap class definitions. It implements the minimum emulation of the python ldap
@@ -23,34 +23,65 @@ library to work with nova.
""" """
import fnmatch
import json import json
import redis
from nova import flags
FLAGS = flags.FLAGS
flags.DEFINE_string('redis_host', '127.0.0.1',
'Host that redis is running on.')
flags.DEFINE_integer('redis_port', 6379,
'Port that redis is running on.')
flags.DEFINE_integer('redis_db', 0, 'Multiple DB keeps tests away')
class Redis(object): class Store(object):
def __init__(self): def __init__(self):
if hasattr(self.__class__, '_instance'): if hasattr(self.__class__, '_instance'):
raise Exception('Attempted to instantiate singleton') raise Exception(_('Attempted to instantiate singleton'))
@classmethod @classmethod
def instance(cls): def instance(cls):
if not hasattr(cls, '_instance'): if not hasattr(cls, '_instance'):
inst = redis.Redis(host=FLAGS.redis_host, cls._instance = _StorageDict()
port=FLAGS.redis_port,
db=FLAGS.redis_db)
cls._instance = inst
return cls._instance return cls._instance
class _StorageDict(dict):
def keys(self, pat=None):
ret = super(_StorageDict, self).keys()
if pat is not None:
ret = fnmatch.filter(ret, pat)
return ret
def delete(self, key):
try:
del self[key]
except KeyError:
pass
def flushdb(self):
self.clear()
def hgetall(self, key):
"""Returns the hash for the given key; creates
the hash if the key doesn't exist."""
try:
return self[key]
except KeyError:
self[key] = {}
return self[key]
def hget(self, key, field):
hashdict = self.hgetall(key)
try:
return hashdict[field]
except KeyError:
hashdict[field] = {}
return hashdict[field]
def hset(self, key, field, val):
hashdict = self.hgetall(key)
hashdict[field] = val
def hmset(self, key, value_dict):
hashdict = self.hgetall(key)
for field, val in value_dict.items():
hashdict[field] = val
SCOPE_BASE = 0 SCOPE_BASE = 0
SCOPE_ONELEVEL = 1 # Not implemented SCOPE_ONELEVEL = 1 # Not implemented
SCOPE_SUBTREE = 2 SCOPE_SUBTREE = 2
@@ -119,6 +150,9 @@ def _match(key, value, attrs):
"""Match a given key and value against an attribute list.""" """Match a given key and value against an attribute list."""
if key not in attrs: if key not in attrs:
return False return False
# This is a wild card search. Implemented as all or nothing for now.
if value == "*":
return True
if key != "objectclass": if key != "objectclass":
return value in attrs[key] return value in attrs[key]
# it is an objectclass check, so check subclasses # it is an objectclass check, so check subclasses
@@ -169,8 +203,6 @@ def _to_json(unencoded):
class FakeLDAP(object): class FakeLDAP(object):
#TODO(vish): refactor this class to use a wrapper instead of accessing
# redis directly
"""Fake LDAP connection.""" """Fake LDAP connection."""
def simple_bind_s(self, dn, password): def simple_bind_s(self, dn, password):
@@ -183,14 +215,13 @@ class FakeLDAP(object):
def add_s(self, dn, attr): def add_s(self, dn, attr):
"""Add an object with the specified attributes at dn.""" """Add an object with the specified attributes at dn."""
key = "%s%s" % (self.__redis_prefix, dn) key = "%s%s" % (self.__prefix, dn)
value_dict = dict([(k, _to_json(v)) for k, v in attr]) value_dict = dict([(k, _to_json(v)) for k, v in attr])
Redis.instance().hmset(key, value_dict) Store.instance().hmset(key, value_dict)
def delete_s(self, dn): def delete_s(self, dn):
"""Remove the ldap object at specified dn.""" """Remove the ldap object at specified dn."""
Redis.instance().delete("%s%s" % (self.__redis_prefix, dn)) Store.instance().delete("%s%s" % (self.__prefix, dn))
def modify_s(self, dn, attrs): def modify_s(self, dn, attrs):
"""Modify the object at dn using the attribute list. """Modify the object at dn using the attribute list.
@@ -201,18 +232,18 @@ class FakeLDAP(object):
([MOD_ADD | MOD_DELETE | MOD_REPACE], attribute, value) ([MOD_ADD | MOD_DELETE | MOD_REPACE], attribute, value)
""" """
redis = Redis.instance() store = Store.instance()
key = "%s%s" % (self.__redis_prefix, dn) key = "%s%s" % (self.__prefix, dn)
for cmd, k, v in attrs: for cmd, k, v in attrs:
values = _from_json(redis.hget(key, k)) values = _from_json(store.hget(key, k))
if cmd == MOD_ADD: if cmd == MOD_ADD:
values.append(v) values.append(v)
elif cmd == MOD_REPLACE: elif cmd == MOD_REPLACE:
values = [v] values = [v]
else: else:
values.remove(v) values.remove(v)
values = redis.hset(key, k, _to_json(values)) values = store.hset(key, k, _to_json(values))
def search_s(self, dn, scope, query=None, fields=None): def search_s(self, dn, scope, query=None, fields=None):
"""Search for all matching objects under dn using the query. """Search for all matching objects under dn using the query.
@@ -226,16 +257,17 @@ class FakeLDAP(object):
""" """
if scope != SCOPE_BASE and scope != SCOPE_SUBTREE: if scope != SCOPE_BASE and scope != SCOPE_SUBTREE:
raise NotImplementedError(str(scope)) raise NotImplementedError(str(scope))
redis = Redis.instance() store = Store.instance()
if scope == SCOPE_BASE: if scope == SCOPE_BASE:
keys = ["%s%s" % (self.__redis_prefix, dn)] keys = ["%s%s" % (self.__prefix, dn)]
else: else:
keys = redis.keys("%s*%s" % (self.__redis_prefix, dn)) keys = store.keys("%s*%s" % (self.__prefix, dn))
objects = [] objects = []
for key in keys: for key in keys:
# get the attributes from redis # get the attributes from the store
attrs = redis.hgetall(key) attrs = store.hgetall(key)
# turn the values from redis into lists # turn the values from the store into lists
# pylint: disable-msg=E1103 # pylint: disable-msg=E1103
attrs = dict([(k, _from_json(v)) attrs = dict([(k, _from_json(v))
for k, v in attrs.iteritems()]) for k, v in attrs.iteritems()])
@@ -244,13 +276,13 @@ class FakeLDAP(object):
# filter the attributes by fields # filter the attributes by fields
attrs = dict([(k, v) for k, v in attrs.iteritems() attrs = dict([(k, v) for k, v in attrs.iteritems()
if not fields or k in fields]) if not fields or k in fields])
objects.append((key[len(self.__redis_prefix):], attrs)) objects.append((key[len(self.__prefix):], attrs))
# pylint: enable-msg=E1103 # pylint: enable-msg=E1103
if objects == []: if objects == []:
raise NO_SUCH_OBJECT() raise NO_SUCH_OBJECT()
return objects return objects
@property @property
def __redis_prefix(self): # pylint: disable-msg=R0201 def __prefix(self): # pylint: disable-msg=R0201
"""Get the prefix to use for all redis keys.""" """Get the prefix to use for all keys."""
return 'ldap:' return 'ldap:'

View File

@@ -32,14 +32,21 @@ from nova import flags
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_integer('ldap_schema_version', 2,
'Current version of the LDAP schema')
flags.DEFINE_string('ldap_url', 'ldap://localhost', flags.DEFINE_string('ldap_url', 'ldap://localhost',
'Point this at your ldap server') 'Point this at your ldap server')
flags.DEFINE_string('ldap_password', 'changeme', 'LDAP password') flags.DEFINE_string('ldap_password', 'changeme', 'LDAP password')
flags.DEFINE_string('ldap_user_dn', 'cn=Manager,dc=example,dc=com', flags.DEFINE_string('ldap_user_dn', 'cn=Manager,dc=example,dc=com',
'DN of admin user') 'DN of admin user')
flags.DEFINE_string('ldap_user_id_attribute', 'uid', 'Attribute to use as id')
flags.DEFINE_string('ldap_user_name_attribute', 'cn',
'Attribute to use as name')
flags.DEFINE_string('ldap_user_unit', 'Users', 'OID for Users') flags.DEFINE_string('ldap_user_unit', 'Users', 'OID for Users')
flags.DEFINE_string('ldap_user_subtree', 'ou=Users,dc=example,dc=com', flags.DEFINE_string('ldap_user_subtree', 'ou=Users,dc=example,dc=com',
'OU for Users') 'OU for Users')
flags.DEFINE_boolean('ldap_user_modify_only', False,
'Modify attributes for users instead of creating/deleting')
flags.DEFINE_string('ldap_project_subtree', 'ou=Groups,dc=example,dc=com', flags.DEFINE_string('ldap_project_subtree', 'ou=Groups,dc=example,dc=com',
'OU for Projects') 'OU for Projects')
flags.DEFINE_string('role_project_subtree', 'ou=Groups,dc=example,dc=com', flags.DEFINE_string('role_project_subtree', 'ou=Groups,dc=example,dc=com',
@@ -71,10 +78,20 @@ class LdapDriver(object):
Defines enter and exit and therefore supports the with/as syntax. Defines enter and exit and therefore supports the with/as syntax.
""" """
project_pattern = '(owner=*)'
isadmin_attribute = 'isNovaAdmin'
project_attribute = 'owner'
project_objectclass = 'groupOfNames'
def __init__(self): def __init__(self):
"""Imports the LDAP module""" """Imports the LDAP module"""
self.ldap = __import__('ldap') self.ldap = __import__('ldap')
self.conn = None self.conn = None
if FLAGS.ldap_schema_version == 1:
LdapDriver.project_pattern = '(objectclass=novaProject)'
LdapDriver.isadmin_attribute = 'isAdmin'
LdapDriver.project_attribute = 'projectManager'
LdapDriver.project_objectclass = 'novaProject'
def __enter__(self): def __enter__(self):
"""Creates the connection to LDAP""" """Creates the connection to LDAP"""
@@ -89,8 +106,7 @@ class LdapDriver(object):
def get_user(self, uid): def get_user(self, uid):
"""Retrieve user by id""" """Retrieve user by id"""
attr = self.__find_object(self.__uid_to_dn(uid), attr = self.__get_ldap_user(uid)
'(objectclass=novaUser)')
return self.__to_user(attr) return self.__to_user(attr)
def get_user_from_access_key(self, access): def get_user_from_access_key(self, access):
@@ -103,18 +119,23 @@ class LdapDriver(object):
"""Retrieve project by id""" """Retrieve project by id"""
dn = 'cn=%s,%s' % (pid, dn = 'cn=%s,%s' % (pid,
FLAGS.ldap_project_subtree) FLAGS.ldap_project_subtree)
attr = self.__find_object(dn, '(objectclass=novaProject)') attr = self.__find_object(dn, LdapDriver.project_pattern)
return self.__to_project(attr) return self.__to_project(attr)
def get_users(self): def get_users(self):
"""Retrieve list of users""" """Retrieve list of users"""
attrs = self.__find_objects(FLAGS.ldap_user_subtree, attrs = self.__find_objects(FLAGS.ldap_user_subtree,
'(objectclass=novaUser)') '(objectclass=novaUser)')
return [self.__to_user(attr) for attr in attrs] users = []
for attr in attrs:
user = self.__to_user(attr)
if user is not None:
users.append(user)
return users
def get_projects(self, uid=None): def get_projects(self, uid=None):
"""Retrieve list of projects""" """Retrieve list of projects"""
pattern = '(objectclass=novaProject)' pattern = LdapDriver.project_pattern
if uid: if uid:
pattern = "(&%s(member=%s))" % (pattern, self.__uid_to_dn(uid)) pattern = "(&%s(member=%s))" % (pattern, self.__uid_to_dn(uid))
attrs = self.__find_objects(FLAGS.ldap_project_subtree, attrs = self.__find_objects(FLAGS.ldap_project_subtree,
@@ -125,51 +146,85 @@ class LdapDriver(object):
"""Create a user""" """Create a user"""
if self.__user_exists(name): if self.__user_exists(name):
raise exception.Duplicate("LDAP user %s already exists" % name) raise exception.Duplicate("LDAP user %s already exists" % name)
attr = [ if FLAGS.ldap_user_modify_only:
('objectclass', ['person', if self.__ldap_user_exists(name):
'organizationalPerson', # Retrieve user by name
'inetOrgPerson', user = self.__get_ldap_user(name)
'novaUser']), # Entry could be malformed, test for missing attrs.
('ou', [FLAGS.ldap_user_unit]), # Malformed entries are useless, replace attributes found.
('uid', [name]), attr = []
('sn', [name]), if 'secretKey' in user.keys():
('cn', [name]), attr.append((self.ldap.MOD_REPLACE, 'secretKey',
('secretKey', [secret_key]), [secret_key]))
('accessKey', [access_key]), else:
('isAdmin', [str(is_admin).upper()]), attr.append((self.ldap.MOD_ADD, 'secretKey',
] [secret_key]))
self.conn.add_s(self.__uid_to_dn(name), attr) if 'accessKey' in user.keys():
return self.__to_user(dict(attr)) attr.append((self.ldap.MOD_REPLACE, 'accessKey',
[access_key]))
else:
attr.append((self.ldap.MOD_ADD, 'accessKey',
[access_key]))
if LdapDriver.isadmin_attribute in user.keys():
attr.append((self.ldap.MOD_REPLACE,
LdapDriver.isadmin_attribute,
[str(is_admin).upper()]))
else:
attr.append((self.ldap.MOD_ADD,
LdapDriver.isadmin_attribute,
[str(is_admin).upper()]))
self.conn.modify_s(self.__uid_to_dn(name), attr)
return self.get_user(name)
else:
raise exception.NotFound(_("LDAP object for %s doesn't exist")
% name)
else:
attr = [
('objectclass', ['person',
'organizationalPerson',
'inetOrgPerson',
'novaUser']),
('ou', [FLAGS.ldap_user_unit]),
(FLAGS.ldap_user_id_attribute, [name]),
('sn', [name]),
(FLAGS.ldap_user_name_attribute, [name]),
('secretKey', [secret_key]),
('accessKey', [access_key]),
(LdapDriver.isadmin_attribute, [str(is_admin).upper()]),
]
self.conn.add_s(self.__uid_to_dn(name), attr)
return self.__to_user(dict(attr))
def create_project(self, name, manager_uid, def create_project(self, name, manager_uid,
description=None, member_uids=None): description=None, member_uids=None):
"""Create a project""" """Create a project"""
if self.__project_exists(name): if self.__project_exists(name):
raise exception.Duplicate("Project can't be created because " raise exception.Duplicate(_("Project can't be created because "
"project %s already exists" % name) "project %s already exists") % name)
if not self.__user_exists(manager_uid): if not self.__user_exists(manager_uid):
raise exception.NotFound("Project can't be created because " raise exception.NotFound(_("Project can't be created because "
"manager %s doesn't exist" % manager_uid) "manager %s doesn't exist")
% manager_uid)
manager_dn = self.__uid_to_dn(manager_uid) manager_dn = self.__uid_to_dn(manager_uid)
# description is a required attribute # description is a required attribute
if description is None: if description is None:
description = name description = name
members = [] members = []
if member_uids != None: if member_uids is not None:
for member_uid in member_uids: for member_uid in member_uids:
if not self.__user_exists(member_uid): if not self.__user_exists(member_uid):
raise exception.NotFound("Project can't be created " raise exception.NotFound(_("Project can't be created "
"because user %s doesn't exist" "because user %s doesn't exist")
% member_uid) % member_uid)
members.append(self.__uid_to_dn(member_uid)) members.append(self.__uid_to_dn(member_uid))
# always add the manager as a member because members is required # always add the manager as a member because members is required
if not manager_dn in members: if not manager_dn in members:
members.append(manager_dn) members.append(manager_dn)
attr = [ attr = [
('objectclass', ['novaProject']), ('objectclass', [LdapDriver.project_objectclass]),
('cn', [name]), ('cn', [name]),
('description', [description]), ('description', [description]),
('projectManager', [manager_dn]), (LdapDriver.project_attribute, [manager_dn]),
('member', members)] ('member', members)]
self.conn.add_s('cn=%s,%s' % (name, FLAGS.ldap_project_subtree), attr) self.conn.add_s('cn=%s,%s' % (name, FLAGS.ldap_project_subtree), attr)
return self.__to_project(dict(attr)) return self.__to_project(dict(attr))
@@ -181,11 +236,12 @@ class LdapDriver(object):
attr = [] attr = []
if manager_uid: if manager_uid:
if not self.__user_exists(manager_uid): if not self.__user_exists(manager_uid):
raise exception.NotFound("Project can't be modified because " raise exception.NotFound(_("Project can't be modified because "
"manager %s doesn't exist" % "manager %s doesn't exist")
manager_uid) % manager_uid)
manager_dn = self.__uid_to_dn(manager_uid) manager_dn = self.__uid_to_dn(manager_uid)
attr.append((self.ldap.MOD_REPLACE, 'projectManager', manager_dn)) attr.append((self.ldap.MOD_REPLACE, LdapDriver.project_attribute,
manager_dn))
if description: if description:
attr.append((self.ldap.MOD_REPLACE, 'description', description)) attr.append((self.ldap.MOD_REPLACE, 'description', description))
self.conn.modify_s('cn=%s,%s' % (project_id, self.conn.modify_s('cn=%s,%s' % (project_id,
@@ -245,10 +301,9 @@ class LdapDriver(object):
return roles return roles
else: else:
project_dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) project_dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree)
roles = self.__find_objects(project_dn, query = ('(&(&(objectclass=groupOfNames)(!%s))(member=%s))' %
'(&(&(objectclass=groupOfNames)' (LdapDriver.project_pattern, self.__uid_to_dn(uid)))
'(!(objectclass=novaProject)))' roles = self.__find_objects(project_dn, query)
'(member=%s))' % self.__uid_to_dn(uid))
return [role['cn'][0] for role in roles] return [role['cn'][0] for role in roles]
def delete_user(self, uid): def delete_user(self, uid):
@@ -256,7 +311,25 @@ class LdapDriver(object):
if not self.__user_exists(uid): if not self.__user_exists(uid):
raise exception.NotFound("User %s doesn't exist" % uid) raise exception.NotFound("User %s doesn't exist" % uid)
self.__remove_from_all(uid) self.__remove_from_all(uid)
self.conn.delete_s(self.__uid_to_dn(uid)) if FLAGS.ldap_user_modify_only:
# Delete attributes
attr = []
# Retrieve user by name
user = self.__get_ldap_user(uid)
if 'secretKey' in user.keys():
attr.append((self.ldap.MOD_DELETE, 'secretKey',
user['secretKey']))
if 'accessKey' in user.keys():
attr.append((self.ldap.MOD_DELETE, 'accessKey',
user['accessKey']))
if LdapDriver.isadmin_attribute in user.keys():
attr.append((self.ldap.MOD_DELETE,
LdapDriver.isadmin_attribute,
user[LdapDriver.isadmin_attribute]))
self.conn.modify_s(self.__uid_to_dn(uid), attr)
else:
# Delete entry
self.conn.delete_s(self.__uid_to_dn(uid))
def delete_project(self, project_id): def delete_project(self, project_id):
"""Delete a project""" """Delete a project"""
@@ -265,7 +338,7 @@ class LdapDriver(object):
self.__delete_group(project_dn) self.__delete_group(project_dn)
def modify_user(self, uid, access_key=None, secret_key=None, admin=None): def modify_user(self, uid, access_key=None, secret_key=None, admin=None):
"""Modify an existing project""" """Modify an existing user"""
if not access_key and not secret_key and admin is None: if not access_key and not secret_key and admin is None:
return return
attr = [] attr = []
@@ -274,16 +347,27 @@ class LdapDriver(object):
if secret_key: if secret_key:
attr.append((self.ldap.MOD_REPLACE, 'secretKey', secret_key)) attr.append((self.ldap.MOD_REPLACE, 'secretKey', secret_key))
if admin is not None: if admin is not None:
attr.append((self.ldap.MOD_REPLACE, 'isAdmin', str(admin).upper())) attr.append((self.ldap.MOD_REPLACE, LdapDriver.isadmin_attribute,
str(admin).upper()))
self.conn.modify_s(self.__uid_to_dn(uid), attr) self.conn.modify_s(self.__uid_to_dn(uid), attr)
def __user_exists(self, uid): def __user_exists(self, uid):
"""Check if user exists""" """Check if user exists"""
return self.get_user(uid) != None return self.get_user(uid) is not None
def __ldap_user_exists(self, uid):
"""Check if the user exists in ldap"""
return self.__get_ldap_user(uid) is not None
def __project_exists(self, project_id): def __project_exists(self, project_id):
"""Check if project exists""" """Check if project exists"""
return self.get_project(project_id) != None return self.get_project(project_id) is not None
def __get_ldap_user(self, uid):
"""Retrieve LDAP user entry by id"""
attr = self.__find_object(self.__uid_to_dn(uid),
'(objectclass=novaUser)')
return attr
def __find_object(self, dn, query=None, scope=None): def __find_object(self, dn, query=None, scope=None):
"""Find an object by dn and query""" """Find an object by dn and query"""
@@ -318,24 +402,26 @@ class LdapDriver(object):
def __find_role_dns(self, tree): def __find_role_dns(self, tree):
"""Find dns of role objects in given tree""" """Find dns of role objects in given tree"""
return self.__find_dns(tree, query = ('(&(objectclass=groupOfNames)(!%s))' %
'(&(objectclass=groupOfNames)(!(objectclass=novaProject)))') LdapDriver.project_pattern)
return self.__find_dns(tree, query)
def __find_group_dns_with_member(self, tree, uid): def __find_group_dns_with_member(self, tree, uid):
"""Find dns of group objects in a given tree that contain member""" """Find dns of group objects in a given tree that contain member"""
dns = self.__find_dns(tree, query = ('(&(objectclass=groupOfNames)(member=%s))' %
'(&(objectclass=groupOfNames)(member=%s))' % self.__uid_to_dn(uid))
self.__uid_to_dn(uid)) dns = self.__find_dns(tree, query)
return dns return dns
def __group_exists(self, dn): def __group_exists(self, dn):
"""Check if group exists""" """Check if group exists"""
return self.__find_object(dn, '(objectclass=groupOfNames)') != None query = '(objectclass=groupOfNames)'
return self.__find_object(dn, query) is not None
@staticmethod @staticmethod
def __role_to_dn(role, project_id=None): def __role_to_dn(role, project_id=None):
"""Convert role to corresponding dn""" """Convert role to corresponding dn"""
if project_id == None: if project_id is None:
return FLAGS.__getitem__("ldap_%s" % role).value return FLAGS.__getitem__("ldap_%s" % role).value
else: else:
return 'cn=%s,cn=%s,%s' % (role, return 'cn=%s,cn=%s,%s' % (role,
@@ -349,11 +435,12 @@ class LdapDriver(object):
raise exception.Duplicate("Group can't be created because " raise exception.Duplicate("Group can't be created because "
"group %s already exists" % name) "group %s already exists" % name)
members = [] members = []
if member_uids != None: if member_uids is not None:
for member_uid in member_uids: for member_uid in member_uids:
if not self.__user_exists(member_uid): if not self.__user_exists(member_uid):
raise exception.NotFound("Group can't be created " raise exception.NotFound("Group can't be created "
"because user %s doesn't exist" % member_uid) "because user %s doesn't exist" %
member_uid)
members.append(self.__uid_to_dn(member_uid)) members.append(self.__uid_to_dn(member_uid))
dn = self.__uid_to_dn(uid) dn = self.__uid_to_dn(uid)
if not dn in members: if not dn in members:
@@ -369,25 +456,25 @@ class LdapDriver(object):
"""Check if user is in group""" """Check if user is in group"""
if not self.__user_exists(uid): if not self.__user_exists(uid):
raise exception.NotFound("User %s can't be searched in group " raise exception.NotFound("User %s can't be searched in group "
"becuase the user doesn't exist" % (uid,)) "because the user doesn't exist" % uid)
if not self.__group_exists(group_dn): if not self.__group_exists(group_dn):
return False return False
res = self.__find_object(group_dn, res = self.__find_object(group_dn,
'(member=%s)' % self.__uid_to_dn(uid), '(member=%s)' % self.__uid_to_dn(uid),
self.ldap.SCOPE_BASE) self.ldap.SCOPE_BASE)
return res != None return res is not None
def __add_to_group(self, uid, group_dn): def __add_to_group(self, uid, group_dn):
"""Add user to group""" """Add user to group"""
if not self.__user_exists(uid): if not self.__user_exists(uid):
raise exception.NotFound("User %s can't be added to the group " raise exception.NotFound("User %s can't be added to the group "
"becuase the user doesn't exist" % (uid,)) "because the user doesn't exist" % uid)
if not self.__group_exists(group_dn): if not self.__group_exists(group_dn):
raise exception.NotFound("The group at dn %s doesn't exist" % raise exception.NotFound("The group at dn %s doesn't exist" %
(group_dn,)) group_dn)
if self.__is_in_group(uid, group_dn): if self.__is_in_group(uid, group_dn):
raise exception.Duplicate("User %s is already a member of " raise exception.Duplicate(_("User %s is already a member of "
"the group %s" % (uid, group_dn)) "the group %s") % (uid, group_dn))
attr = [(self.ldap.MOD_ADD, 'member', self.__uid_to_dn(uid))] attr = [(self.ldap.MOD_ADD, 'member', self.__uid_to_dn(uid))]
self.conn.modify_s(group_dn, attr) self.conn.modify_s(group_dn, attr)
@@ -395,16 +482,16 @@ class LdapDriver(object):
"""Remove user from group""" """Remove user from group"""
if not self.__group_exists(group_dn): if not self.__group_exists(group_dn):
raise exception.NotFound("The group at dn %s doesn't exist" % raise exception.NotFound("The group at dn %s doesn't exist" %
(group_dn,)) group_dn)
if not self.__user_exists(uid): if not self.__user_exists(uid):
raise exception.NotFound("User %s can't be removed from the " raise exception.NotFound("User %s can't be removed from the "
"group because the user doesn't exist" % (uid,)) "group because the user doesn't exist" %
uid)
if not self.__is_in_group(uid, group_dn): if not self.__is_in_group(uid, group_dn):
raise exception.NotFound("User %s is not a member of the group" % raise exception.NotFound("User %s is not a member of the group" %
(uid,)) uid)
# NOTE(vish): remove user from group and any sub_groups # NOTE(vish): remove user from group and any sub_groups
sub_dns = self.__find_group_dns_with_member( sub_dns = self.__find_group_dns_with_member(group_dn, uid)
group_dn, uid)
for sub_dn in sub_dns: for sub_dn in sub_dns:
self.__safe_remove_from_group(uid, sub_dn) self.__safe_remove_from_group(uid, sub_dn)
@@ -415,15 +502,15 @@ class LdapDriver(object):
try: try:
self.conn.modify_s(group_dn, attr) self.conn.modify_s(group_dn, attr)
except self.ldap.OBJECT_CLASS_VIOLATION: except self.ldap.OBJECT_CLASS_VIOLATION:
logging.debug("Attempted to remove the last member of a group. " logging.debug(_("Attempted to remove the last member of a group. "
"Deleting the group at %s instead.", group_dn) "Deleting the group at %s instead."), group_dn)
self.__delete_group(group_dn) self.__delete_group(group_dn)
def __remove_from_all(self, uid): def __remove_from_all(self, uid):
"""Remove user from all roles and projects""" """Remove user from all roles and projects"""
if not self.__user_exists(uid): if not self.__user_exists(uid):
raise exception.NotFound("User %s can't be removed from all " raise exception.NotFound("User %s can't be removed from all "
"because the user doesn't exist" % (uid,)) "because the user doesn't exist" % uid)
role_dns = self.__find_group_dns_with_member( role_dns = self.__find_group_dns_with_member(
FLAGS.role_project_subtree, uid) FLAGS.role_project_subtree, uid)
for role_dn in role_dns: for role_dn in role_dns:
@@ -436,7 +523,8 @@ class LdapDriver(object):
def __delete_group(self, group_dn): def __delete_group(self, group_dn):
"""Delete Group""" """Delete Group"""
if not self.__group_exists(group_dn): if not self.__group_exists(group_dn):
raise exception.NotFound("Group at dn %s doesn't exist" % group_dn) raise exception.NotFound(_("Group at dn %s doesn't exist")
% group_dn)
self.conn.delete_s(group_dn) self.conn.delete_s(group_dn)
def __delete_roles(self, project_dn): def __delete_roles(self, project_dn):
@@ -447,24 +535,29 @@ class LdapDriver(object):
@staticmethod @staticmethod
def __to_user(attr): def __to_user(attr):
"""Convert ldap attributes to User object""" """Convert ldap attributes to User object"""
if attr == None: if attr is None:
return None
if ('accessKey' in attr.keys() and 'secretKey' in attr.keys() \
and LdapDriver.isadmin_attribute in attr.keys()):
return {
'id': attr[FLAGS.ldap_user_id_attribute][0],
'name': attr[FLAGS.ldap_user_name_attribute][0],
'access': attr['accessKey'][0],
'secret': attr['secretKey'][0],
'admin': (attr[LdapDriver.isadmin_attribute][0] == 'TRUE')}
else:
return None return None
return {
'id': attr['uid'][0],
'name': attr['cn'][0],
'access': attr['accessKey'][0],
'secret': attr['secretKey'][0],
'admin': (attr['isAdmin'][0] == 'TRUE')}
def __to_project(self, attr): def __to_project(self, attr):
"""Convert ldap attributes to Project object""" """Convert ldap attributes to Project object"""
if attr == None: if attr is None:
return None return None
member_dns = attr.get('member', []) member_dns = attr.get('member', [])
return { return {
'id': attr['cn'][0], 'id': attr['cn'][0],
'name': attr['cn'][0], 'name': attr['cn'][0],
'project_manager_id': self.__dn_to_uid(attr['projectManager'][0]), 'project_manager_id':
self.__dn_to_uid(attr[LdapDriver.project_attribute][0]),
'description': attr.get('description', [None])[0], 'description': attr.get('description', [None])[0],
'member_ids': [self.__dn_to_uid(x) for x in member_dns]} 'member_ids': [self.__dn_to_uid(x) for x in member_dns]}
@@ -474,9 +567,10 @@ class LdapDriver(object):
return dn.split(',')[0].split('=')[1] return dn.split(',')[0].split('=')[1]
@staticmethod @staticmethod
def __uid_to_dn(dn): def __uid_to_dn(uid):
"""Convert uid to dn""" """Convert uid to dn"""
return 'uid=%s,%s' % (dn, FLAGS.ldap_user_subtree) return (FLAGS.ldap_user_id_attribute + '=%s,%s'
% (uid, FLAGS.ldap_user_subtree))
class FakeLdapDriver(LdapDriver): class FakeLdapDriver(LdapDriver):

View File

@@ -64,12 +64,9 @@ flags.DEFINE_string('credential_key_file', 'pk.pem',
'Filename of private key in credentials zip') 'Filename of private key in credentials zip')
flags.DEFINE_string('credential_cert_file', 'cert.pem', flags.DEFINE_string('credential_cert_file', 'cert.pem',
'Filename of certificate in credentials zip') 'Filename of certificate in credentials zip')
flags.DEFINE_string('credential_rc_file', 'novarc', flags.DEFINE_string('credential_rc_file', '%src',
'Filename of rc in credentials zip') 'Filename of rc in credentials zip, %s will be '
flags.DEFINE_string('credential_cert_subject', 'replaced by name of the region (nova by default)')
'/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.dbdriver.DbDriver', flags.DEFINE_string('auth_driver', 'nova.auth.dbdriver.DbDriver',
'Driver that auth manager uses') 'Driver that auth manager uses')
@@ -257,12 +254,12 @@ class AuthManager(object):
# TODO(vish): check for valid timestamp # TODO(vish): check for valid timestamp
(access_key, _sep, project_id) = access.partition(':') (access_key, _sep, project_id) = access.partition(':')
logging.info('Looking up user: %r', access_key) logging.info(_('Looking up user: %r'), access_key)
user = self.get_user_from_access_key(access_key) user = self.get_user_from_access_key(access_key)
logging.info('user: %r', user) logging.info('user: %r', user)
if user == None: if user == None:
raise exception.NotFound('No user found for access key %s' % raise exception.NotFound(_('No user found for access key %s')
access_key) % access_key)
# NOTE(vish): if we stop using project name as id we need better # NOTE(vish): if we stop using project name as id we need better
# logic to find a default project for user # logic to find a default project for user
@@ -271,12 +268,12 @@ class AuthManager(object):
project = self.get_project(project_id) project = self.get_project(project_id)
if project == None: if project == None:
raise exception.NotFound('No project called %s could be found' % raise exception.NotFound(_('No project called %s could be found')
project_id) % project_id)
if not self.is_admin(user) and not self.is_project_member(user, if not self.is_admin(user) and not self.is_project_member(user,
project): project):
raise exception.NotFound('User %s is not a member of project %s' % raise exception.NotFound(_('User %s is not a member of project %s')
(user.id, project.id)) % (user.id, project.id))
if check_type == 's3': if check_type == 's3':
sign = signer.Signer(user.secret.encode()) sign = signer.Signer(user.secret.encode())
expected_signature = sign.s3_authorization(headers, verb, path) expected_signature = sign.s3_authorization(headers, verb, path)
@@ -284,7 +281,7 @@ class AuthManager(object):
logging.debug('expected_signature: %s', expected_signature) logging.debug('expected_signature: %s', expected_signature)
logging.debug('signature: %s', signature) logging.debug('signature: %s', signature)
if signature != expected_signature: if signature != expected_signature:
raise exception.NotAuthorized('Signature does not match') raise exception.NotAuthorized(_('Signature does not match'))
elif check_type == 'ec2': elif check_type == 'ec2':
# NOTE(vish): hmac can't handle unicode, so encode ensures that # NOTE(vish): hmac can't handle unicode, so encode ensures that
# secret isn't unicode # secret isn't unicode
@@ -294,7 +291,7 @@ class AuthManager(object):
logging.debug('expected_signature: %s', expected_signature) logging.debug('expected_signature: %s', expected_signature)
logging.debug('signature: %s', signature) logging.debug('signature: %s', signature)
if signature != expected_signature: if signature != expected_signature:
raise exception.NotAuthorized('Signature does not match') raise exception.NotAuthorized(_('Signature does not match'))
return (user, project) return (user, project)
def get_access_key(self, user, project): def get_access_key(self, user, project):
@@ -364,7 +361,7 @@ class AuthManager(object):
with self.driver() as drv: with self.driver() as drv:
if role == 'projectmanager': if role == 'projectmanager':
if not project: if not project:
raise exception.Error("Must specify project") raise exception.Error(_("Must specify project"))
return self.is_project_manager(user, project) return self.is_project_manager(user, project)
global_role = drv.has_role(User.safe_id(user), global_role = drv.has_role(User.safe_id(user),
@@ -398,9 +395,9 @@ class AuthManager(object):
@param project: Project in which to add local role. @param project: Project in which to add local role.
""" """
if role not in FLAGS.allowed_roles: if role not in FLAGS.allowed_roles:
raise exception.NotFound("The %s role can not be found" % role) raise exception.NotFound(_("The %s role can not be found") % role)
if project is not None and role in FLAGS.global_roles: if project is not None and role in FLAGS.global_roles:
raise exception.NotFound("The %s role is global only" % role) raise exception.NotFound(_("The %s role is global only") % role)
with self.driver() as drv: with self.driver() as drv:
drv.add_role(User.safe_id(user), role, Project.safe_id(project)) drv.add_role(User.safe_id(user), role, Project.safe_id(project))
@@ -543,10 +540,10 @@ class AuthManager(object):
""" """
network_ref = db.project_get_network(context.get_admin_context(), network_ref = db.project_get_network(context.get_admin_context(),
Project.safe_id(project)) Project.safe_id(project), False)
if not network_ref['vpn_public_port']: if not network_ref:
raise exception.NotFound('project network data has not been set') return (None, None)
return (network_ref['vpn_public_address'], return (network_ref['vpn_public_address'],
network_ref['vpn_public_port']) network_ref['vpn_public_port'])
@@ -624,27 +621,41 @@ class AuthManager(object):
with self.driver() as drv: with self.driver() as drv:
drv.modify_user(uid, access_key, secret_key, admin) drv.modify_user(uid, access_key, secret_key, admin)
def get_credentials(self, user, project=None): @staticmethod
def get_key_pairs(context):
return db.key_pair_get_all_by_user(context.elevated(), context.user_id)
def get_credentials(self, user, project=None, use_dmz=True):
"""Get credential zip for user in project""" """Get credential zip for user in project"""
if not isinstance(user, User): if not isinstance(user, User):
user = self.get_user(user) user = self.get_user(user)
if project is None: if project is None:
project = user.id project = user.id
pid = Project.safe_id(project) pid = Project.safe_id(project)
rc = self.__generate_rc(user.access, user.secret, pid) private_key, signed_cert = crypto.generate_x509_cert(user.id, pid)
private_key, signed_cert = self._generate_x509_cert(user.id, pid)
tmpdir = tempfile.mkdtemp() tmpdir = tempfile.mkdtemp()
zf = os.path.join(tmpdir, "temp.zip") zf = os.path.join(tmpdir, "temp.zip")
zippy = zipfile.ZipFile(zf, 'w') zippy = zipfile.ZipFile(zf, 'w')
zippy.writestr(FLAGS.credential_rc_file, rc) if use_dmz and FLAGS.region_list:
regions = {}
for item in FLAGS.region_list:
region, _sep, region_host = item.partition("=")
regions[region] = region_host
else:
regions = {'nova': FLAGS.cc_host}
for region, host in regions.iteritems():
rc = self.__generate_rc(user.access,
user.secret,
pid,
use_dmz,
host)
zippy.writestr(FLAGS.credential_rc_file % region, rc)
zippy.writestr(FLAGS.credential_key_file, private_key) zippy.writestr(FLAGS.credential_key_file, private_key)
zippy.writestr(FLAGS.credential_cert_file, signed_cert) zippy.writestr(FLAGS.credential_cert_file, signed_cert)
try: (vpn_ip, vpn_port) = self.get_project_vpn_data(project)
(vpn_ip, vpn_port) = self.get_project_vpn_data(project)
except exception.NotFound:
vpn_ip = None
if vpn_ip: if vpn_ip:
configfile = open(FLAGS.vpn_client_template, "r") configfile = open(FLAGS.vpn_client_template, "r")
s = string.Template(configfile.read()) s = string.Template(configfile.read())
@@ -655,10 +666,9 @@ class AuthManager(object):
port=vpn_port) port=vpn_port)
zippy.writestr(FLAGS.credential_vpn_file, config) zippy.writestr(FLAGS.credential_vpn_file, config)
else: else:
logging.warn("No vpn data for project %s" % logging.warn(_("No vpn data for project %s"), pid)
pid)
zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(user.id)) zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(pid))
zippy.close() zippy.close()
with open(zf, 'rb') as f: with open(zf, 'rb') as f:
read_buffer = f.read() read_buffer = f.read()
@@ -666,38 +676,38 @@ class AuthManager(object):
shutil.rmtree(tmpdir) shutil.rmtree(tmpdir)
return read_buffer return read_buffer
def get_environment_rc(self, user, project=None): def get_environment_rc(self, user, project=None, use_dmz=True):
"""Get credential zip for user in project""" """Get credential zip for user in project"""
if not isinstance(user, User): if not isinstance(user, User):
user = self.get_user(user) user = self.get_user(user)
if project is None: if project is None:
project = user.id project = user.id
pid = Project.safe_id(project) pid = Project.safe_id(project)
return self.__generate_rc(user.access, user.secret, pid) return self.__generate_rc(user.access, user.secret, pid, use_dmz)
@staticmethod @staticmethod
def __generate_rc(access, secret, pid): def __generate_rc(access, secret, pid, use_dmz=True, host=None):
"""Generate rc file for user""" """Generate rc file for user"""
if use_dmz:
cc_host = FLAGS.cc_dmz
else:
cc_host = FLAGS.cc_host
# NOTE(vish): Always use the dmz since it is used from inside the
# instance
s3_host = FLAGS.s3_dmz
if host:
s3_host = host
cc_host = host
rc = open(FLAGS.credentials_template).read() rc = open(FLAGS.credentials_template).read()
rc = rc % {'access': access, rc = rc % {'access': access,
'project': pid, 'project': pid,
'secret': secret, 'secret': secret,
'ec2': FLAGS.ec2_url, 'ec2': '%s://%s:%s%s' % (FLAGS.ec2_prefix,
's3': 'http://%s:%s' % (FLAGS.s3_host, FLAGS.s3_port), cc_host,
FLAGS.cc_port,
FLAGS.ec2_suffix),
's3': 'http://%s:%s' % (s3_host, FLAGS.s3_port),
'nova': FLAGS.ca_file, 'nova': FLAGS.ca_file,
'cert': FLAGS.credential_cert_file, 'cert': FLAGS.credential_cert_file,
'key': FLAGS.credential_key_file} 'key': FLAGS.credential_key_file}
return rc 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)
@staticmethod
def __cert_subject(uid):
"""Helper to generate cert subject"""
return FLAGS.credential_cert_subject % (uid, utils.isotime())

View File

@@ -1,7 +1,9 @@
# #
# Person object for Nova # Person object for Nova
# inetorgperson with extra attributes # inetorgperson with extra attributes
# Author: Vishvananda Ishaya <vishvananda@yahoo.com> # Schema version: 2
# Authors: Vishvananda Ishaya <vishvananda@gmail.com>
# Ryan Lane <rlane@wikimedia.org>
# #
# #
@@ -30,55 +32,19 @@ attributetype (
SINGLE-VALUE SINGLE-VALUE
) )
attributetype (
novaAttrs:3
NAME 'keyFingerprint'
DESC 'Fingerprint of private key'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
)
attributetype ( attributetype (
novaAttrs:4 novaAttrs:4
NAME 'isAdmin' NAME 'isNovaAdmin'
DESC 'Is user an administrator?' DESC 'Is user an nova administrator?'
EQUALITY booleanMatch EQUALITY booleanMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
SINGLE-VALUE SINGLE-VALUE
) )
attributetype (
novaAttrs:5
NAME 'projectManager'
DESC 'Project Managers of a project'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
)
objectClass ( objectClass (
novaOCs:1 novaOCs:1
NAME 'novaUser' NAME 'novaUser'
DESC 'access and secret keys' DESC 'access and secret keys'
AUXILIARY AUXILIARY
MUST ( uid ) MAY ( accessKey $ secretKey $ isNovaAdmin )
MAY ( accessKey $ secretKey $ isAdmin )
)
objectClass (
novaOCs:2
NAME 'novaKeyPair'
DESC 'Key pair for User'
SUP top
STRUCTURAL
MUST ( cn $ sshPublicKey $ keyFingerprint )
)
objectClass (
novaOCs:3
NAME 'novaProject'
DESC 'Container for project'
SUP groupOfNames
STRUCTURAL
MUST ( cn $ projectManager )
) )

View File

@@ -1,16 +1,13 @@
# #
# Person object for Nova # Person object for Nova
# inetorgperson with extra attributes # inetorgperson with extra attributes
# Author: Vishvananda Ishaya <vishvananda@yahoo.com> # Schema version: 2
# Modified for strict RFC 4512 compatibility by: Ryan Lane <ryan@ryandlane.com> # Authors: Vishvananda Ishaya <vishvananda@gmail.com>
# Ryan Lane <rlane@wikimedia.org>
# #
# using internet experimental oid arc as per BP64 3.1 # using internet experimental oid arc as per BP64 3.1
dn: cn=schema dn: cn=schema
attributeTypes: ( 1.3.6.1.3.1.666.666.3.1 NAME 'accessKey' DESC 'Key for accessing data' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.3.1.666.666.3.1 NAME 'accessKey' DESC 'Key for accessing data' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
attributeTypes: ( 1.3.6.1.3.1.666.666.3.2 NAME 'secretKey' DESC 'Secret key' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.3.1.666.666.3.2 NAME 'secretKey' DESC 'Secret key' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
attributeTypes: ( 1.3.6.1.3.1.666.666.3.3 NAME 'keyFingerprint' DESC 'Fingerprint of private key' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE) attributeTypes: ( 1.3.6.1.3.1.666.666.3.4 NAME 'isNovaAdmin' DESC 'Is user a nova administrator?' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
attributeTypes: ( 1.3.6.1.3.1.666.666.3.4 NAME 'isAdmin' DESC 'Is user an administrator?' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) objectClasses: ( 1.3.6.1.3.1.666.666.4.1 NAME 'novaUser' DESC 'access and secret keys' SUP top AUXILIARY MAY ( accessKey $ secretKey $ isNovaAdmin ) )
attributeTypes: ( 1.3.6.1.3.1.666.666.3.5 NAME 'projectManager' DESC 'Project Managers of a project' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
objectClasses: ( 1.3.6.1.3.1.666.666.4.1 NAME 'novaUser' DESC 'access and secret keys' SUP top AUXILIARY MUST ( uid ) MAY ( accessKey $ secretKey $ isAdmin ) )
objectClasses: ( 1.3.6.1.3.1.666.666.4.2 NAME 'novaKeyPair' DESC 'Key pair for User' SUP top STRUCTURAL MUST ( cn $ sshPublicKey $ keyFingerprint ) )
objectClasses: ( 1.3.6.1.3.1.666.666.4.3 NAME 'novaProject' DESC 'Container for project' SUP groupOfNames STRUCTURAL MUST ( cn $ projectManager ) )

118
nova/auth/opendj.sh Executable file
View File

@@ -0,0 +1,118 @@
#!/usr/bin/env bash
# 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.
# LDAP INSTALL SCRIPT - IS IDEMPOTENT, does not scrub users
apt-get install -y ldap-utils python-ldap openjdk-6-jre
if [ ! -d "/usr/opendj" ]
then
# TODO(rlane): Wikimedia Foundation is the current package maintainer.
# After the package is included in Ubuntu's channel, change this.
wget http://apt.wikimedia.org/wikimedia/pool/main/o/opendj/opendj_2.4.0-7_amd64.deb
dpkg -i opendj_2.4.0-7_amd64.deb
fi
abspath=`dirname "$(cd "${0%/*}" 2>/dev/null; echo "$PWD"/"${0##*/}")"`
schemapath='/var/opendj/instance/config/schema'
cp $abspath/openssh-lpk_sun.schema $schemapath/97-openssh-lpk_sun.ldif
cp $abspath/nova_sun.schema $schemapath/98-nova_sun.ldif
chown opendj:opendj $schemapath/98-nova_sun.ldif
cat >/etc/ldap/ldap.conf <<LDAP_CONF_EOF
# LDAP Client Settings
URI ldap://localhost
BASE dc=example,dc=com
BINDDN cn=Directory Manager
SIZELIMIT 0
TIMELIMIT 0
LDAP_CONF_EOF
cat >/etc/ldap/base.ldif <<BASE_LDIF_EOF
# This is the root of the directory tree
dn: dc=example,dc=com
description: Example.Com, your trusted non-existent corporation.
dc: example
o: Example.Com
objectClass: top
objectClass: dcObject
objectClass: organization
# Subtree for users
dn: ou=Users,dc=example,dc=com
ou: Users
description: Users
objectClass: organizationalUnit
# Subtree for groups
dn: ou=Groups,dc=example,dc=com
ou: Groups
description: Groups
objectClass: organizationalUnit
# Subtree for system accounts
dn: ou=System,dc=example,dc=com
ou: System
description: Special accounts used by software applications.
objectClass: organizationalUnit
# Special Account for Authentication:
dn: uid=authenticate,ou=System,dc=example,dc=com
uid: authenticate
ou: System
description: Special account for authenticating users
userPassword: {MD5}TLnIqASP0CKUR3/LGkEZGg==
objectClass: account
objectClass: simpleSecurityObject
# create the sysadmin entry
dn: cn=developers,ou=Groups,dc=example,dc=com
objectclass: groupOfNames
cn: developers
description: IT admin group
member: uid=admin,ou=Users,dc=example,dc=com
dn: cn=sysadmins,ou=Groups,dc=example,dc=com
objectclass: groupOfNames
cn: sysadmins
description: IT admin group
member: uid=admin,ou=Users,dc=example,dc=com
dn: cn=netadmins,ou=Groups,dc=example,dc=com
objectclass: groupOfNames
cn: netadmins
description: Network admin group
member: uid=admin,ou=Users,dc=example,dc=com
dn: cn=cloudadmins,ou=Groups,dc=example,dc=com
objectclass: groupOfNames
cn: cloudadmins
description: Cloud admin group
member: uid=admin,ou=Users,dc=example,dc=com
dn: cn=itsec,ou=Groups,dc=example,dc=com
objectclass: groupOfNames
cn: itsec
description: IT security users group
member: uid=admin,ou=Users,dc=example,dc=com
BASE_LDIF_EOF
/etc/init.d/opendj stop
su - opendj -c '/usr/opendj/setup -i -b "dc=example,dc=com" -l /etc/ldap/base.ldif -S -w changeme -O -n --noPropertiesFile'
/etc/init.d/opendj start

View File

@@ -22,7 +22,7 @@ apt-get install -y slapd ldap-utils python-ldap
abspath=`dirname "$(cd "${0%/*}" 2>/dev/null; echo "$PWD"/"${0##*/}")"` abspath=`dirname "$(cd "${0%/*}" 2>/dev/null; echo "$PWD"/"${0##*/}")"`
cp $abspath/openssh-lpk_openldap.schema /etc/ldap/schema/openssh-lpk_openldap.schema cp $abspath/openssh-lpk_openldap.schema /etc/ldap/schema/openssh-lpk_openldap.schema
cp $abspath/nova_openldap.schema /etc/ldap/schema/nova_openldap.schema cp $abspath/nova_openldap.schema /etc/ldap/schema/nova.schema
mv /etc/ldap/slapd.conf /etc/ldap/slapd.conf.orig mv /etc/ldap/slapd.conf /etc/ldap/slapd.conf.orig
cat >/etc/ldap/slapd.conf <<SLAPD_CONF_EOF cat >/etc/ldap/slapd.conf <<SLAPD_CONF_EOF
@@ -33,7 +33,6 @@ cat >/etc/ldap/slapd.conf <<SLAPD_CONF_EOF
include /etc/ldap/schema/core.schema include /etc/ldap/schema/core.schema
include /etc/ldap/schema/cosine.schema include /etc/ldap/schema/cosine.schema
include /etc/ldap/schema/inetorgperson.schema include /etc/ldap/schema/inetorgperson.schema
include /etc/ldap/schema/openssh-lpk_openldap.schema
include /etc/ldap/schema/nova.schema include /etc/ldap/schema/nova.schema
pidfile /var/run/slapd/slapd.pid pidfile /var/run/slapd/slapd.pid
argsfile /var/run/slapd/slapd.args argsfile /var/run/slapd/slapd.args

View File

@@ -27,23 +27,26 @@ import traceback
class ProcessExecutionError(IOError): class ProcessExecutionError(IOError):
def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None, def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None,
description=None): description=None):
if description is None: if description is None:
description = "Unexpected error while running command." description = _("Unexpected error while running command.")
if exit_code is None: if exit_code is None:
exit_code = '-' exit_code = '-'
message = "%s\nCommand: %s\nExit code: %s\nStdout: %r\nStderr: %r" % ( message = _("%s\nCommand: %s\nExit code: %s\nStdout: %r\nStderr: %r")\
description, cmd, exit_code, stdout, stderr) % (description, cmd, exit_code, stdout, stderr)
IOError.__init__(self, message) IOError.__init__(self, message)
class Error(Exception): class Error(Exception):
def __init__(self, message=None): def __init__(self, message=None):
super(Error, self).__init__(message) super(Error, self).__init__(message)
class ApiError(Error): class ApiError(Error):
def __init__(self, message='Unknown', code='Unknown'): def __init__(self, message='Unknown', code='Unknown'):
self.message = message self.message = message
self.code = code self.code = code
@@ -81,7 +84,7 @@ def wrap_exception(f):
except Exception, e: except Exception, e:
if not isinstance(e, Error): if not isinstance(e, Error):
#exc_type, exc_value, exc_traceback = sys.exc_info() #exc_type, exc_value, exc_traceback = sys.exc_info()
logging.exception('Uncaught exception') logging.exception(_('Uncaught exception'))
#logging.error(traceback.extract_stack(exc_traceback)) #logging.error(traceback.extract_stack(exc_traceback))
raise Error(str(e)) raise Error(str(e))
raise raise

59
nova/fakememcache.py Normal file
View File

@@ -0,0 +1,59 @@
# 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.
"""Super simple fake memcache client."""
import utils
class Client(object):
"""Replicates a tiny subset of memcached client interface."""
def __init__(self, *args, **kwargs):
"""Ignores the passed in args"""
self.cache = {}
def get(self, key):
"""Retrieves the value for a key or None."""
(timeout, value) = self.cache.get(key, (0, None))
if timeout == 0 or utils.utcnow_ts() < timeout:
return value
return None
def set(self, key, value, time=0, min_compress_len=0):
"""Sets the value for a key."""
timeout = 0
if time != 0:
timeout = utils.utcnow_ts() + time
self.cache[key] = (timeout, value)
return True
def add(self, key, value, time=0, min_compress_len=0):
"""Sets the value for a key if it doesn't exist."""
if not self.get(key) is None:
return False
return self.set(key, value, time, min_compress_len)
def incr(self, key, delta=1):
"""Increments the value for a key."""
value = self.get(key)
if value is None:
return None
new_value = int(value) + delta
self.cache[key] = (self.cache[key][0], str(new_value))
return new_value

View File

@@ -25,6 +25,10 @@ from carrot.backends import base
from eventlet import greenthread from eventlet import greenthread
EXCHANGES = {}
QUEUES = {}
class Message(base.BaseMessage): class Message(base.BaseMessage):
pass pass
@@ -37,12 +41,12 @@ class Exchange(object):
self._routes = {} self._routes = {}
def publish(self, message, routing_key=None): def publish(self, message, routing_key=None):
logging.debug('(%s) publish (key: %s) %s', logging.debug(_('(%s) publish (key: %s) %s'),
self.name, routing_key, message) self.name, routing_key, message)
routing_key = routing_key.split('.')[0] routing_key = routing_key.split('.')[0]
if routing_key in self._routes: if routing_key in self._routes:
for f in self._routes[routing_key]: for f in self._routes[routing_key]:
logging.debug('Publishing to route %s', f) logging.debug(_('Publishing to route %s'), f)
f(message, routing_key=routing_key) f(message, routing_key=routing_key)
def bind(self, callback, routing_key): def bind(self, callback, routing_key):
@@ -68,81 +72,63 @@ class Queue(object):
return self._queue.get() return self._queue.get()
class Backend(object): class Backend(base.BaseBackend):
""" Singleton backend for testing """ def queue_declare(self, queue, **kwargs):
class __impl(base.BaseBackend): global QUEUES
def __init__(self, *args, **kwargs): if queue not in QUEUES:
#super(__impl, self).__init__(*args, **kwargs) logging.debug(_('Declaring queue %s'), queue)
self._exchanges = {} QUEUES[queue] = Queue(queue)
self._queues = {}
def _reset_all(self): def exchange_declare(self, exchange, type, *args, **kwargs):
self._exchanges = {} global EXCHANGES
self._queues = {} if exchange not in EXCHANGES:
logging.debug(_('Declaring exchange %s'), exchange)
EXCHANGES[exchange] = Exchange(exchange, type)
def queue_declare(self, queue, **kwargs): def queue_bind(self, queue, exchange, routing_key, **kwargs):
if queue not in self._queues: global EXCHANGES
logging.debug('Declaring queue %s', queue) global QUEUES
self._queues[queue] = Queue(queue) logging.debug(_('Binding %s to %s with key %s'),
queue, exchange, routing_key)
EXCHANGES[exchange].bind(QUEUES[queue].push, routing_key)
def exchange_declare(self, exchange, type, *args, **kwargs): def declare_consumer(self, queue, callback, *args, **kwargs):
if exchange not in self._exchanges: self.current_queue = queue
logging.debug('Declaring exchange %s', exchange) self.current_callback = callback
self._exchanges[exchange] = Exchange(exchange, type)
def queue_bind(self, queue, exchange, routing_key, **kwargs): def consume(self, limit=None):
logging.debug('Binding %s to %s with key %s', while True:
queue, exchange, routing_key) item = self.get(self.current_queue)
self._exchanges[exchange].bind(self._queues[queue].push, if item:
routing_key) self.current_callback(item)
raise StopIteration()
greenthread.sleep(0)
def declare_consumer(self, queue, callback, *args, **kwargs): def get(self, queue, no_ack=False):
self.current_queue = queue global QUEUES
self.current_callback = callback if not queue in QUEUES or not QUEUES[queue].size():
return None
(message_data, content_type, content_encoding) = QUEUES[queue].pop()
message = Message(backend=self, body=message_data,
content_type=content_type,
content_encoding=content_encoding)
message.result = True
logging.debug(_('Getting from %s: %s'), queue, message)
return message
def consume(self, *args, **kwargs): def prepare_message(self, message_data, delivery_mode,
while True: content_type, content_encoding, **kwargs):
item = self.get(self.current_queue) """Prepare message for sending."""
if item: return (message_data, content_type, content_encoding)
self.current_callback(item)
raise StopIteration()
greenthread.sleep(0)
def get(self, queue, no_ack=False): def publish(self, message, exchange, routing_key, **kwargs):
if not queue in self._queues or not self._queues[queue].size(): global EXCHANGES
return None if exchange in EXCHANGES:
(message_data, content_type, content_encoding) = \ EXCHANGES[exchange].publish(message, routing_key=routing_key)
self._queues[queue].pop()
message = Message(backend=self, body=message_data,
content_type=content_type,
content_encoding=content_encoding)
message.result = True
logging.debug('Getting from %s: %s', queue, message)
return message
def prepare_message(self, message_data, delivery_mode,
content_type, content_encoding, **kwargs):
"""Prepare message for sending."""
return (message_data, content_type, content_encoding)
def publish(self, message, exchange, routing_key, **kwargs):
if exchange in self._exchanges:
self._exchanges[exchange].publish(
message, routing_key=routing_key)
__instance = None
def __init__(self, *args, **kwargs):
if Backend.__instance is None:
Backend.__instance = Backend.__impl(*args, **kwargs)
self.__dict__['_Backend__instance'] = Backend.__instance
def __getattr__(self, attr):
return getattr(self.__instance, attr)
def __setattr__(self, attr, value):
return setattr(self.__instance, attr, value)
def reset_all(): def reset_all():
Backend()._reset_all() global EXCHANGES
global QUEUES
EXCHANGES = {}
QUEUES = {}

View File

@@ -29,6 +29,8 @@ import sys
import gflags import gflags
from nova import utils
class FlagValues(gflags.FlagValues): class FlagValues(gflags.FlagValues):
"""Extension of gflags.FlagValues that allows undefined and runtime flags. """Extension of gflags.FlagValues that allows undefined and runtime flags.
@@ -159,6 +161,7 @@ class StrWrapper(object):
return str(val) return str(val)
raise KeyError(name) raise KeyError(name)
FLAGS = FlagValues() FLAGS = FlagValues()
gflags.FLAGS = FLAGS gflags.FLAGS = FLAGS
gflags.DEFINE_flag(gflags.HelpFlag(), FLAGS) gflags.DEFINE_flag(gflags.HelpFlag(), FLAGS)
@@ -183,6 +186,12 @@ DEFINE_list = _wrapper(gflags.DEFINE_list)
DEFINE_spaceseplist = _wrapper(gflags.DEFINE_spaceseplist) DEFINE_spaceseplist = _wrapper(gflags.DEFINE_spaceseplist)
DEFINE_multistring = _wrapper(gflags.DEFINE_multistring) DEFINE_multistring = _wrapper(gflags.DEFINE_multistring)
DEFINE_multi_int = _wrapper(gflags.DEFINE_multi_int) DEFINE_multi_int = _wrapper(gflags.DEFINE_multi_int)
DEFINE_flag = _wrapper(gflags.DEFINE_flag)
HelpFlag = gflags.HelpFlag
HelpshortFlag = gflags.HelpshortFlag
HelpXMLFlag = gflags.HelpXMLFlag
def DECLARE(name, module_string, flag_values=FLAGS): def DECLARE(name, module_string, flag_values=FLAGS):
@@ -204,7 +213,8 @@ DEFINE_string('connection_type', 'libvirt', 'libvirt, xenapi or fake')
DEFINE_string('aws_access_key_id', 'admin', 'AWS Access ID') DEFINE_string('aws_access_key_id', 'admin', 'AWS Access ID')
DEFINE_string('aws_secret_access_key', 'admin', 'AWS Access Key') DEFINE_string('aws_secret_access_key', 'admin', 'AWS Access Key')
DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_integer('s3_port', 3333, 's3 port')
DEFINE_string('s3_host', '127.0.0.1', 's3 host') DEFINE_string('s3_host', utils.get_my_ip(), 's3 host (for infrastructure)')
DEFINE_string('s3_dmz', utils.get_my_ip(), 's3 dmz ip (for instances)')
DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on')
DEFINE_string('scheduler_topic', 'scheduler', DEFINE_string('scheduler_topic', 'scheduler',
'the topic scheduler nodes listen on') 'the topic scheduler nodes listen on')
@@ -223,22 +233,24 @@ DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host')
DEFINE_integer('rabbit_retry_interval', 10, 'rabbit connection retry interval') DEFINE_integer('rabbit_retry_interval', 10, 'rabbit connection retry interval')
DEFINE_integer('rabbit_max_retries', 12, 'rabbit connection attempts') DEFINE_integer('rabbit_max_retries', 12, 'rabbit connection attempts')
DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to')
DEFINE_string('ec2_url', 'http://127.0.0.1:8773/services/Cloud', DEFINE_string('ec2_prefix', 'http', 'prefix for ec2')
'Url to ec2 api server') DEFINE_string('cc_host', utils.get_my_ip(), 'ip of api server')
DEFINE_string('cc_dmz', utils.get_my_ip(), 'internal ip of api server')
DEFINE_integer('cc_port', 8773, 'cloud controller port')
DEFINE_string('ec2_suffix', '/services/Cloud', 'suffix for ec2')
DEFINE_string('default_image', 'ami-11111', DEFINE_string('default_image', 'ami-11111',
'default image to use, testing only') 'default image to use, testing only')
DEFINE_string('default_kernel', 'aki-11111',
'default kernel to use, testing only')
DEFINE_string('default_ramdisk', 'ari-11111',
'default ramdisk to use, testing only')
DEFINE_string('default_instance_type', 'm1.small', DEFINE_string('default_instance_type', 'm1.small',
'default instance type to use, testing only') 'default instance type to use, testing only')
DEFINE_string('null_kernel', 'nokernel',
'kernel image that indicates not to use a kernel,'
' but to use a raw disk image instead')
DEFINE_string('vpn_image_id', 'ami-CLOUDPIPE', 'AMI for cloudpipe vpn server') DEFINE_string('vpn_image_id', 'ami-cloudpipe', 'AMI for cloudpipe vpn server')
DEFINE_string('vpn_key_suffix', DEFINE_string('vpn_key_suffix',
'-key', '-vpn',
'Suffix to add to project name for vpn key') 'Suffix to add to project name for vpn key and secgroups')
DEFINE_integer('auth_token_ttl', 3600, 'Seconds for auth tokens to linger') DEFINE_integer('auth_token_ttl', 3600, 'Seconds for auth tokens to linger')
@@ -259,7 +271,7 @@ DEFINE_string('scheduler_manager', 'nova.scheduler.manager.SchedulerManager',
'Manager for scheduler') 'Manager for scheduler')
# The service to use for image search and retrieval # The service to use for image search and retrieval
DEFINE_string('image_service', 'nova.image.local.LocalImageService', DEFINE_string('image_service', 'nova.image.s3.S3ImageService',
'The service to use for retrieving and searching for images.') 'The service to use for retrieving and searching for images.')
DEFINE_string('host', socket.gethostname(), DEFINE_string('host', socket.gethostname(),

View File

@@ -35,7 +35,6 @@ try :
from nova import flags from nova import flags
from nova import quota from nova import quota
from nova import utils from nova import utils
from nova import process
from nova.auth import manager from nova.auth import manager
from nova.cloudpipe import pipelib from nova.cloudpipe import pipelib
from nova import rpc from nova import rpc
@@ -177,10 +176,11 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
def test03(self): def test03(self):
"""03: Unexpected exception occurs on finding volume on DB. """ """03: Unexpected exception occurs on finding volume on DB. """
utils.execute = Mock( side_effect=process.ProcessExecutionError('ERR') ) utils.execute = Mock( side_effect=exception.ProcessExecutionError('ERR') )
self.assertRaises(process.ProcessExecutionError, self.assertRaises(exception.ProcessExecutionError,
self.manager.live_migration, self.manager._live_migration,
self.ctxt,
self.instance1, self.instance1,
'host2') 'host2')
@@ -195,6 +195,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
self.assertRaises(TypeError, self.assertRaises(TypeError,
self.manager._post_live_migration, self.manager._post_live_migration,
self.ctxt,
"dummy string", "dummy string",
'host2') 'host2')
@@ -202,7 +203,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
"""05: db.instance_get_fixed_address return None""" """05: db.instance_get_fixed_address return None"""
db.instance_get_fixed_address = Mock( return_value=None ) db.instance_get_fixed_address = Mock( return_value=None )
ret = self.manager._post_live_migration(self.instance1, 'host1') ret = self.manager._post_live_migration(self.ctxt, self.instance1, 'host1')
c1 = (ret == None) c1 = (ret == None)
c2 = (0 <= sys.stderr.buffer.find('fixed_ip is not found')) c2 = (0 <= sys.stderr.buffer.find('fixed_ip is not found'))
self.assertEqual(c1 and c2, True) self.assertEqual(c1 and c2, True)
@@ -213,6 +214,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
db.instance_get_fixed_address = Mock( side_effect=exception.NotFound('ERR') ) db.instance_get_fixed_address = Mock( side_effect=exception.NotFound('ERR') )
self.assertRaises(exception.NotFound, self.assertRaises(exception.NotFound,
self.manager._post_live_migration, self.manager._post_live_migration,
self.ctxt,
self.instance1, self.instance1,
'host2') 'host2')
@@ -222,6 +224,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
db.instance_get_fixed_address = Mock( side_effect=TypeError('ERR') ) db.instance_get_fixed_address = Mock( side_effect=TypeError('ERR') )
self.assertRaises(TypeError, self.assertRaises(TypeError,
self.manager._post_live_migration, self.manager._post_live_migration,
self.ctxt,
self.instance1, self.instance1,
'host1') 'host1')
@@ -231,6 +234,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
db.fixed_ip_update = Mock( side_effect=exception.NotFound('ERR') ) db.fixed_ip_update = Mock( side_effect=exception.NotFound('ERR') )
self.assertRaises(exception.NotFound, self.assertRaises(exception.NotFound,
self.manager._post_live_migration, self.manager._post_live_migration,
self.ctxt,
self.instance1, self.instance1,
'host1') 'host1')
@@ -239,6 +243,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
db.fixed_ip_update = Mock( side_effect=exception.NotAuthorized('ERR') ) db.fixed_ip_update = Mock( side_effect=exception.NotAuthorized('ERR') )
self.assertRaises(exception.NotAuthorized, self.assertRaises(exception.NotAuthorized,
self.manager._post_live_migration, self.manager._post_live_migration,
self.ctxt,
self.instance1, self.instance1,
'host1') 'host1')
@@ -247,6 +252,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
db.fixed_ip_update = Mock( side_effect=TypeError('ERR') ) db.fixed_ip_update = Mock( side_effect=TypeError('ERR') )
self.assertRaises(TypeError, self.assertRaises(TypeError,
self.manager._post_live_migration, self.manager._post_live_migration,
self.ctxt,
self.instance1, self.instance1,
'host1') 'host1')
@@ -256,6 +262,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
db.fixed_ip_get_network = Mock( side_effect=exception.NotFound('ERR') ) db.fixed_ip_get_network = Mock( side_effect=exception.NotFound('ERR') )
self.assertRaises(exception.NotFound, self.assertRaises(exception.NotFound,
self.manager._post_live_migration, self.manager._post_live_migration,
self.ctxt,
self.instance1, self.instance1,
'host1') 'host1')
@@ -268,6 +275,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
db.fixed_ip_get_network = Mock( side_effect=TypeError('ERR') ) db.fixed_ip_get_network = Mock( side_effect=TypeError('ERR') )
self.assertRaises(TypeError, self.assertRaises(TypeError,
self.manager._post_live_migration, self.manager._post_live_migration,
self.ctxt,
self.instance1, self.instance1,
'host1') 'host1')
@@ -276,13 +284,14 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
db.network_update = Mock( side_effect=TypeError('ERR') ) db.network_update = Mock( side_effect=TypeError('ERR') )
self.assertRaises(TypeError, self.assertRaises(TypeError,
self.manager._post_live_migration, self.manager._post_live_migration,
self.ctxt,
self.instance1, self.instance1,
'host1') 'host1')
def test14(self): def test14(self):
"""14: db.instance_get_floating_address raises NotFound. """ """14: db.instance_get_floating_address raises NotFound. """
db.instance_get_floating_address = Mock(side_effect=exception.NotFound("ERR")) db.instance_get_floating_address = Mock(side_effect=exception.NotFound("ERR"))
ret = self.manager._post_live_migration(self.instance1, 'host1') ret = self.manager._post_live_migration(self.ctxt, self.instance1, 'host1')
c1 = (ret == None) c1 = (ret == None)
c2 = (0 <= sys.stderr.buffer.find('doesnt have floating_ip')) c2 = (0 <= sys.stderr.buffer.find('doesnt have floating_ip'))
self.assertEqual(c1 and c2, True) self.assertEqual(c1 and c2, True)
@@ -292,7 +301,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
"""15: db.instance_get_floating_address returns None. """ """15: db.instance_get_floating_address returns None. """
db.instance_get_floating_address = Mock( return_value=None ) db.instance_get_floating_address = Mock( return_value=None )
ret = self.manager._post_live_migration(self.instance1, 'host1') ret = self.manager._post_live_migration(self.ctxt, self.instance1, 'host1')
c1 = (ret == None) c1 = (ret == None)
c2 = (0 <= sys.stderr.buffer.find('floating_ip is not found')) c2 = (0 <= sys.stderr.buffer.find('floating_ip is not found'))
self.assertEqual(c1 and c2, True) self.assertEqual(c1 and c2, True)
@@ -301,7 +310,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
"""16: db.instance_get_floating_address raises NotFound. """ """16: db.instance_get_floating_address raises NotFound. """
db.instance_get_floating_address = Mock(side_effect=exception.NotFound("ERR")) db.instance_get_floating_address = Mock(side_effect=exception.NotFound("ERR"))
ret = self.manager._post_live_migration(self.instance1, 'host1') ret = self.manager._post_live_migration(self.ctxt, self.instance1, 'host1')
c1 = (ret == None) c1 = (ret == None)
c2 = (0 <= sys.stderr.buffer.find('doesnt have floating_ip')) c2 = (0 <= sys.stderr.buffer.find('doesnt have floating_ip'))
self.assertEqual(c1 and c2, True) self.assertEqual(c1 and c2, True)
@@ -309,7 +318,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
def test17(self): def test17(self):
"""17: db.instance_get_floating_address raises Unknown exception. """ """17: db.instance_get_floating_address raises Unknown exception. """
db.instance_get_floating_address = Mock(side_effect=TypeError("ERR")) db.instance_get_floating_address = Mock(side_effect=TypeError("ERR"))
ret = self.manager._post_live_migration(self.instance1, 'host1') ret = self.manager._post_live_migration(self.ctxt, self.instance1, 'host1')
c1 = (ret == None) c1 = (ret == None)
c2 = (0 <= sys.stderr.buffer.find('Live migration: Unexpected error')) c2 = (0 <= sys.stderr.buffer.find('Live migration: Unexpected error'))
self.assertEqual(c1 and c2, True) self.assertEqual(c1 and c2, True)
@@ -319,7 +328,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
"""18: db.floating_ip_get_by_address raises NotFound """ """18: db.floating_ip_get_by_address raises NotFound """
db.floating_ip_get_by_address = Mock(side_effect=exception.NotFound("ERR")) db.floating_ip_get_by_address = Mock(side_effect=exception.NotFound("ERR"))
ret = self.manager._post_live_migration(self.instance1, 'host1') ret = self.manager._post_live_migration(self.ctxt, self.instance1, 'host1')
c1 = (ret == None) c1 = (ret == None)
c2 = (0 <= sys.stderr.buffer.find('doesnt have floating_ip')) c2 = (0 <= sys.stderr.buffer.find('doesnt have floating_ip'))
self.assertEqual(c1 and c2, True) self.assertEqual(c1 and c2, True)
@@ -327,7 +336,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
def test19(self): def test19(self):
"""19: db.floating_ip_get_by_address raises Unknown exception. """ """19: db.floating_ip_get_by_address raises Unknown exception. """
db.floating_ip_get_by_address = Mock(side_effect=TypeError("ERR")) db.floating_ip_get_by_address = Mock(side_effect=TypeError("ERR"))
ret = self.manager._post_live_migration(self.instance1, 'host1') ret = self.manager._post_live_migration(self.ctxt, self.instance1, 'host1')
c1 = (ret == None) c1 = (ret == None)
c2 = (0 <= sys.stderr.buffer.find('Live migration: Unexpected error')) c2 = (0 <= sys.stderr.buffer.find('Live migration: Unexpected error'))
self.assertEqual(c1 and c2, True) self.assertEqual(c1 and c2, True)
@@ -337,7 +346,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
"""20: db.floating_ip_update raises Unknown exception. """20: db.floating_ip_update raises Unknown exception.
""" """
db.floating_ip_update = Mock(side_effect=TypeError("ERR")) db.floating_ip_update = Mock(side_effect=TypeError("ERR"))
ret = self.manager._post_live_migration(self.instance1, 'host1') ret = self.manager._post_live_migration(self.ctxt, self.instance1, 'host1')
c1 = (ret == None) c1 = (ret == None)
c2 = (0 <= sys.stderr.buffer.find('Live migration: Unexpected error')) c2 = (0 <= sys.stderr.buffer.find('Live migration: Unexpected error'))
self.assertEqual(c1 and c2, True) self.assertEqual(c1 and c2, True)
@@ -348,6 +357,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
db.instance_update = Mock(side_effect=TypeError("ERR")) db.instance_update = Mock(side_effect=TypeError("ERR"))
self.assertRaises(TypeError, self.assertRaises(TypeError,
self.manager._post_live_migration, self.manager._post_live_migration,
self.ctxt,
self.instance1, self.instance1,
'host1') 'host1')

View File

@@ -256,73 +256,35 @@ class NovaManageTestFunctions(unittest.TestCase):
self.assertRaises(TypeError, self.instanceCmds.live_migration, 'i-xxx' ) self.assertRaises(TypeError, self.instanceCmds.live_migration, 'i-xxx' )
def test11(self): def test11(self):
"""11: nova-manage instances live_migration ec2_id host, """11: nova-manage instances live_migration ec2_id(invalid id) host"""
where hostname is invalid
""" db.instance_get_by_internal_id = Mock( side_effect=exception.NotFound('ERR') )
db.host_get_by_name = Mock( side_effect=exception.NotFound('ERR') ) try :
self.assertRaises(exception.NotFound, self.instanceCmds.live_migration, 'i-xxx', 'host1' ) self.instanceCmds.live_migration('i-xxx', 'host1')
except exception.NotFound, e:
c1 = (0 < str(e.args).find('is not found') )
self.assertTrue(c1, True)
return False
def test12(self): def test12(self):
"""12: nova-manage instances live_migration ec2_id(invalid id) host""" """12: nova-manage instances live_migration ec2_id host
and db.instance_get_by_internal_id raises unexpected exceptioin.
db.host_get_by_name = Mock(return_value = self.host1) """
db.instance_get_by_internal_id = Mock( side_effect=exception.NotFound('ERR') ) db.instance_get_by_internal_id = Mock( side_effect=TypeError('ERR') )
self.assertRaises(TypeError, self.instanceCmds.live_migration, 'i-xxx' )
self.assertRaises(exception.NotFound, self.instanceCmds.live_migration, 'i-xxx', 'host1' )
def test13(self): def test13(self):
"""13: nova-manage instances live_migration ec2_id host, """13: nova-manage instances live_migration ec2_id host,
but instance specifed by ec2 id is not running (state is not power_state.RUNNING)
"""
db.host_get_by_name = Mock(return_value = self.host1)
db.instance_get_by_internal_id = Mock( return_value = self.instance1 )
try :
self.instanceCmds.live_migration('i-12345', 'host1')
except exception.Invalid, e:
c1 = (0 < e.message.find('is not running') )
self.assertTrue(c1, True)
return False
def test14(self):
"""14: nova-manage instances live_migration ec2_id host,
but instance specifed by ec2 id is not running (state_description is not running)
"""
db.host_get_by_name = Mock(return_value = self.host2)
db.instance_get_by_internal_id = Mock( return_value = self.instance1 )
try :
self.instanceCmds.live_migration('i-12345', 'host2')
except exception.Invalid, e:
c1 = (0 < e.message.find('is not running') )
self.assertTrue(c1, True)
return False
def test15(self):
"""15: nova-manage instances live_migration ec2_id host,
but instance is running at the same host specifed above, so err should be occured.
"""
db.host_get_by_name = Mock(return_value = self.host1)
db.instance_get_by_internal_id = Mock( return_value = self.instance3 )
try :
self.instanceCmds.live_migration('i-12345', 'host1')
except exception.Invalid, e:
c1 = ( 0 <= e.message.find('is running now') )
self.assertTrue(c1, True)
return False
def test16(self):
"""16: nova-manage instances live_migration ec2_id host,
rpc.call raises RemoteError because destination doesnt have enough resource. rpc.call raises RemoteError because destination doesnt have enough resource.
""" """
db.host_get_by_name = Mock(return_value = self.host1) db.host_get_by_name = Mock(return_value = self.host1)
db.instance_get_by_internal_id = Mock( return_value = self.instance3 ) db.instance_get_by_internal_id = Mock( return_value = self.instance3 )
rpc.call = Mock(return_value = rpc.RemoteError(TypeError, 'val', 'traceback')) rpc.call = Mock(return_value = rpc.RemoteError(TypeError, 'val', 'traceback'))
self.assertRaises(rpc.RemoteError, self.instanceCmds.live_migration, 'i-xxx', 'host2' ) self.assertRaises(rpc.RemoteError, self.instanceCmds.live_migration, 'i-xxx', 'host2' )
def test17(self):
"""17: nova-manage instances live_migration ec2_id host, def test14(self):
"""14: nova-manage instances live_migration ec2_id host,
everything goes well, ang gets success messages. everything goes well, ang gets success messages.
""" """
db.host_get_by_name = Mock(return_value = self.host1) db.host_get_by_name = Mock(return_value = self.host1)
@@ -332,7 +294,7 @@ class NovaManageTestFunctions(unittest.TestCase):
self.instanceCmds.live_migration('i-12345', 'host2') self.instanceCmds.live_migration('i-12345', 'host2')
c1 = (0 <= self.stdout.buffer.find('Finished all procedure') ) c1 = (0 <= self.stdout.buffer.find('Finished all procedure') )
self.assertEqual( c1, True ) self.assertEqual( c1, True )
def tearDown(self): def tearDown(self):
"""common terminating method. """ """common terminating method. """

View File

@@ -55,12 +55,15 @@ class tmpStdout:
class SchedulerTestFunctions(unittest.TestCase): class SchedulerTestFunctions(unittest.TestCase):
manager = None
# 共通の初期化処理 # 共通の初期化処理
def setUp(self): def setUp(self):
"""common init method. """ """common init method. """
self.host = 'openstack2-api' self.host = 'openstack2-api'
self.manager = SchedulerManager(host=self.host) if self.manager is None:
self.manager = SchedulerManager(host=self.host)
self.setTestData() self.setTestData()
self.setMocks() self.setMocks()
@@ -72,6 +75,7 @@ class SchedulerTestFunctions(unittest.TestCase):
self.host1.__setitem__('vcpus', 5) self.host1.__setitem__('vcpus', 5)
self.host1.__setitem__('memory_mb', 20480) self.host1.__setitem__('memory_mb', 20480)
self.host1.__setitem__('local_gb', 876) self.host1.__setitem__('local_gb', 876)
self.host1.__setitem__('cpu_info', 1)
self.host2 = Host() self.host2 = Host()
self.host2.__setitem__('name', 'host2') self.host2.__setitem__('name', 'host2')
@@ -86,37 +90,43 @@ class SchedulerTestFunctions(unittest.TestCase):
self.instance1 = Instance() self.instance1 = Instance()
for key, val in [ ('id', 1), ('host', 'host1'), ('hostname', 'i-12345'), for key, val in [ ('id', 1), ('host', 'host1'), ('hostname', 'i-12345'),
('state', power_state.RUNNING), ('project_id', 'testPJ'), ('state', power_state.RUNNING), ('project_id', 'testPJ'),
('vcpus', 3), ('memory_mb', 1024), ('hdd_gb', 5) ]: ('vcpus', 3), ('memory_mb', 1024), ('local_gb', 5) ]:
self.instance1.__setitem__(key, val) self.instance1.__setitem__(key, val)
self.instance2 = Instance() self.instance2 = Instance()
for key, val in [ ('id', 2), ('host', 'host1'), ('hostname', 'i-12345'), for key, val in [ ('id', 2), ('host', 'host1'), ('hostname', 'i-12345'),
('state', power_state.RUNNING), ('project_id', 'testPJ'), ('state', power_state.RUNNING), ('project_id', 'testPJ'),
('vcpus', 3), ('memory_mb', 1024), ('hdd_gb', 5) ]: ('vcpus', 3), ('memory_mb', 1024), ('local_gb', 5) ]:
self.instance2.__setitem__(key, val) self.instance2.__setitem__(key, val)
self.instance3 = Instance() self.instance3 = Instance()
for key, val in [ ('id', 3), ('host', 'host1'), ('hostname', 'i-12345'), for key, val in [ ('id', 3), ('host', 'host1'), ('hostname', 'i-12345'),
('state', power_state.RUNNING), ('project_id', 'testPJ2'), ('state', power_state.RUNNING), ('project_id', 'testPJ2'),
('vcpus', 1), ('memory_mb', 1024), ('hdd_gb', 5) ]: ('vcpus', 1), ('memory_mb', 1024), ('local_gb', 5),
('internal_id', 123456), ('state', 1),
('state_description', 'running') ]:
self.instance3.__setitem__(key, val) self.instance3.__setitem__(key, val)
self.instance4 = Instance() self.instance4 = Instance()
for key, val in [ ('id', 4), ('host', 'host2'), ('hostname', 'i-12345'), for key, val in [ ('id', 4), ('host', 'host2'), ('hostname', 'i-12345'),
('state', power_state.RUNNING), ('project_id', 'testPJ2'), ('state', power_state.RUNNING), ('project_id', 'testPJ2'),
('vcpus', 1), ('memory_mb', 1024), ('local_gb', 5) ]: ('vcpus', 1), ('memory_mb', 1024), ('local_gb', 5),
('internal_id', 123456), ('state', 0),
('state_description', 'running') ]:
self.instance4.__setitem__(key, val) self.instance4.__setitem__(key, val)
self.instance5 = Instance() self.instance5 = Instance()
for key, val in [ ('id', 5), ('host', 'host2'), ('hostname', 'i-12345'), for key, val in [ ('id', 5), ('host', 'host2'), ('hostname', 'i-12345'),
('state', power_state.RUNNING), ('project_id', 'testPJ2'), ('state', power_state.RUNNING), ('project_id', 'testPJ2'),
('vcpus', 1), ('memory_mb', 1024), ('local_gb', 5) ]: ('vcpus', 1), ('memory_mb', 1024), ('local_gb', 5),
('internal_id', 123456), ('state', 1),
('state_description', 'migrating') ]:
self.instance5.__setitem__(key, val) self.instance5.__setitem__(key, val)
self.instance6 = Instance() self.instance6 = Instance()
for key, val in [ ('id', 6), ('host', 'host1'), ('hostname', 'i-12345'), for key, val in [ ('id', 6), ('host', 'host2'), ('hostname', 'i-12345'),
('state', power_state.RUNNING), ('project_id', 'testPJ2'), ('state', power_state.RUNNING), ('project_id', 'testPJ2'),
('vcpus', 3), ('memory_mb', 1024), ('local_gb', 5) ]: ('vcpus', 3), ('memory_mb', 1024), ('local_gb', 5) ]:
self.instance6.__setitem__(key, val) self.instance6.__setitem__(key, val)
@@ -129,7 +139,8 @@ class SchedulerTestFunctions(unittest.TestCase):
self.instance8 = Instance() self.instance8 = Instance()
for key, val in [ ('id', 8), ('host', 'host1'), ('hostname', 'i-12345'), for key, val in [ ('id', 8), ('host', 'host1'), ('hostname', 'i-12345'),
('state', power_state.RUNNING), ('project_id', 'testPJ2'), ('state', power_state.RUNNING),
('state_description', 'running'),('project_id', 'testPJ2'),
('vcpus', 1), ('memory_mb', 1024), ('local_gb', 866) ]: ('vcpus', 1), ('memory_mb', 1024), ('local_gb', 866) ]:
self.instance8.__setitem__(key, val) self.instance8.__setitem__(key, val)
@@ -138,6 +149,10 @@ class SchedulerTestFunctions(unittest.TestCase):
('topic', 'compute')]: ('topic', 'compute')]:
self.service1.__setitem__(key, val) self.service1.__setitem__(key, val)
self.service2 = Service()
for key, val in [ ('id', 2), ('host', 'host2'), ('binary', 'nova-compute'),
('topic', 'compute')]:
self.service1.__setitem__(key, val)
def setMocks(self): def setMocks(self):
self.ctxt = context.get_admin_context() self.ctxt = context.get_admin_context()
@@ -147,10 +162,11 @@ class SchedulerTestFunctions(unittest.TestCase):
db.instance_get_all_by_host = Mock(return_value = [self.instance4, self.instance5] ) db.instance_get_all_by_host = Mock(return_value = [self.instance4, self.instance5] )
# Mocks for live_migration # Mocks for live_migration
db.instance_get_by_internal_id = Mock(return_value = self.instance1)
# db.host_get_by_name <- defined above.
db.service_get_all_by_topic = Mock(return_value = [self.service1] ) db.service_get_all_by_topic = Mock(return_value = [self.service1] )
self.manager.service_ip_up = Mock(return_value = True)
rpc.call = Mock(return_value=1) rpc.call = Mock(return_value=1)
db.instance_set_state = Mock(return_value = True)
self.manager.driver.service_is_up = Mock(return_value = True)
def check_format(self, val): def check_format(self, val):
"""check result format of show_host_resource """ """check result format of show_host_resource """
@@ -259,9 +275,12 @@ class SchedulerTestFunctions(unittest.TestCase):
db.instance_get = Mock(return_value = self.instance6) db.instance_get = Mock(return_value = self.instance6)
try : try :
self.manager.has_enough_resource(self.ctxt, 'i-12345', 'host1') self.manager.driver.has_enough_resource(self.ctxt, 'i-12345', 'host1')
except exception.NotEmpty, e: except exception.NotEmpty, e:
c1 = ( 0 < e.message.find('doesnt have enough resource') ) # dont do e.message.find(), because the below message is occured.
# DeprecationWarning: BaseException.message has been deprecated
# as of Python 2.6
c1 = ( 0 < str(e.args).find('doesnt have enough resource') )
self.assertTrue(c1, True) self.assertTrue(c1, True)
return False return False
@@ -271,9 +290,9 @@ class SchedulerTestFunctions(unittest.TestCase):
db.instance_get = Mock(return_value = self.instance7) db.instance_get = Mock(return_value = self.instance7)
try : try :
self.manager.has_enough_resource(self.ctxt, 'i-12345', 'host1') self.manager.driver.has_enough_resource(self.ctxt, 'i-12345', 'host1')
except exception.NotEmpty, e: except exception.NotEmpty, e:
c1 = ( 0 <= e.message.find('doesnt have enough resource') ) c1 = ( 0 <= str(e.args).find('doesnt have enough resource') )
self.assertTrue(c1, True) self.assertTrue(c1, True)
return False return False
@@ -282,9 +301,9 @@ class SchedulerTestFunctions(unittest.TestCase):
db.instance_get = Mock(return_value = self.instance8) db.instance_get = Mock(return_value = self.instance8)
try : try :
self.manager.has_enough_resource(self.ctxt, 'i-12345', 'host1') self.manager.driver.has_enough_resource(self.ctxt, 'i-12345', 'host1')
except exception.NotEmpty, e: except exception.NotEmpty, e:
c1 = ( 0 <= e.message.find('doesnt have enough resource') ) c1 = ( 0 <= str(e.args).find('doesnt have enough resource') )
self.assertTrue(c1, True) self.assertTrue(c1, True)
return False return False
@@ -292,7 +311,7 @@ class SchedulerTestFunctions(unittest.TestCase):
def test08(self): def test08(self):
"""08: everything goes well. (instance_get_all_by_host returns list)""" """08: everything goes well. (instance_get_all_by_host returns list)"""
ret= self.manager.has_enough_resource(self.ctxt, 'i-12345', 'host1') ret= self.manager.driver.has_enough_resource(self.ctxt, 'i-12345', 'host1')
self.assertEqual(ret, None) self.assertEqual(ret, None)
@@ -300,7 +319,7 @@ class SchedulerTestFunctions(unittest.TestCase):
"""09: everything goes well(instance_get_all_by_host returns[]). """ """09: everything goes well(instance_get_all_by_host returns[]). """
db.instance_get_all_by_host = Mock(return_value = [] ) db.instance_get_all_by_host = Mock(return_value = [] )
ret= self.manager.has_enough_resource(self.ctxt, 'i-12345', 'host1') ret= self.manager.driver.has_enough_resource(self.ctxt, 'i-12345', 'host1')
self.assertEqual(ret, None) self.assertEqual(ret, None)
@@ -308,91 +327,120 @@ class SchedulerTestFunctions(unittest.TestCase):
def test10(self): def test10(self):
"""10: instance_get_by_internal_id issue NotFound. """ """10: instance_get issues NotFound. """
# Mocks for has_enough_resource() db.instance_get = Mock(side_effect=exception.NotFound("ERR"))
db.instance_get = Mock(return_value = self.instance8)
# Mocks for live_migration()db.instance_get_by_internal_id
# (any Mock is ok here. important mock is all above)
db.instance_get_by_internal_id = Mock(side_effect=exception.NotFound("ERR"))
self.assertRaises(exception.NotFound, self.assertRaises(exception.NotFound,
self.manager.live_migration, self.manager.driver.schedule_live_migration,
self.ctxt, self.ctxt,
'i-12345', 'i-12345',
'host1') 'host1')
def test11(self): def test11(self):
"""11: get NotFound exception when dest host not found on DB """ """11: instance_get issues Unexpected error. """
db.host_get_by_name = Mock( side_effect=exception.NotFound('ERR') ) db.instance_get = Mock(side_effect=TypeError("ERR"))
self.assertRaises(exception.NotFound, self.assertRaises(TypeError,
self.manager.live_migration, self.manager.driver.schedule_live_migration,
self.ctxt, self.ctxt,
'i-12345', 'i-12345',
'host1') 'host1')
def test12(self): def test12(self):
"""12: Destination host is not compute node """ """12: instance state is not power_state.RUNNING. """
self.assertRaises(exception.Invalid,
self.manager.live_migration,
self.ctxt,
'i-12345',
'host2')
db.instance_get = Mock(return_value=self.instance4)
try :
self.manager.driver.schedule_live_migration(self.ctxt, 'i-12345', 'host1')
except exception.Invalid, e:
c1 = (0 <= str(e.args).find('is not running'))
self.assertTrue(c1, True)
return False
# Cannot test the case of hypervisor type difference and hypervisor
# version difference, since we cannot set different mocks to same method..
def test13(self): def test13(self):
"""13: rpc.call raises RemoteError(Unexpected error occurs when executing compareCPU) """ """13: instance state_description is not running. """
rpc.call = Mock(return_value = rpc.RemoteError(libvirt.libvirtError, 'val', 'traceback'))
self.assertRaises(rpc.RemoteError, db.instance_get = Mock(return_value=self.instance5)
self.manager.live_migration, try :
self.ctxt, self.manager.driver.schedule_live_migration(self.ctxt, 'i-12345', 'host1')
'i-12345', except exception.Invalid, e:
'host1') c1 = (0 <= str(e.args).find('is not running'))
self.assertTrue(c1, True)
return False
def test14(self): def test14(self):
"""14: rpc.call returns 0 (cpu is not compatible between src and dest) """ """14: dest is not compute node.
rpc.call = Mock(return_value = 0) (dest is not included in the result of db.service_get_all_by_topic)
"""
try : try :
self.manager.live_migration(self.ctxt, 'i-12345', 'host1') self.manager.driver.schedule_live_migration(self.ctxt, 'i-12345', 'host2')
except exception.Invalid, e: except exception.Invalid, e:
c1 = ( 0 <= e.message.find('doesnt have compatibility to')) c1 = (0 <= str(e.args).find('must be compute node'))
self.assertTrue(c1, True) self.assertTrue(c1, True)
return False return False
def test15(self): def test15(self):
"""15: raise NotEmpty if host doesnt have enough resource. """ """ 15: dest is not alive.(service_is up returns False) """
# Mocks for has_enough_resource() self.manager.driver.service_is_up = Mock(return_value=False)
db.instance_get = Mock(return_value = self.instance8) try :
self.manager.driver.schedule_live_migration(self.ctxt, 'i-12345', 'host2')
except exception.Invalid, e:
c1 = (0 <= str(e.args).find('is not alive'))
self.assertTrue(c1, True)
return False
# Mocks for live_migration() # Cannot test the case of hypervisor type difference and hypervisor
db.instance_get_by_internal_id = Mock(return_value = self.instance8) # version difference, since we cannot set different mocks to same method..
db.instance_set_state = Mock(return_value = True)
rpc_cast = Mock(return_value = True)
try : def test16(self):
self.manager.live_migration(self.ctxt, 'i-12345', 'host1') """ 16: stored "cpuinfo" is not string """
except exception.NotEmpty, e:
c1 = ( 0 <= e.message.find('doesnt have enough resource') ) try :
self.manager.driver.schedule_live_migration(self.ctxt, 'i-12345', 'host2')
except exception.Invalid, e:
c1 = (0 <= str(e.args).find('Unexpected err') )
self.assertTrue(c1, True) self.assertTrue(c1, True)
return False return False
def test16(self): def test17(self):
"""16: everything goes well. """ """17: rpc.call raises RemoteError(Unexpected error occurs when executing compareCPU) """
rpc.call = Mock(return_value = rpc.RemoteError(libvirt.libvirtError, 'val', 'traceback'))
self.assertRaises(rpc.RemoteError,
self.manager.driver.schedule_live_migration,
self.ctxt,
'i-12345',
'host2')
db.instance_get_by_internal_id = Mock(return_value = self.instance8) def test18(self):
db.instance_set_state = Mock(return_value = True) """18: rpc.call returns 0 (cpu is not compatible between src and dest) """
rpc.cast = Mock(return_value = True) rpc.call = Mock(return_value = 0)
try :
self.manager.driver.schedule_live_migration(self.ctxt, 'i-12345', 'host2')
except exception.Invalid, e:
c1 = ( 0 <= str(e.args).find('doesnt have compatibility to'))
self.assertTrue(c1, True)
return False
ret= self.manager.live_migration(self.ctxt, 'i-12345', 'host1') def test19(self):
self.assertEqual(ret, None) """19: raise NotEmpty if host doesnt have enough resource. """
db.instance_get = Mock(return_value = self.instance8)
try :
self.manager.driver.schedule_live_migration(self.ctxt, 'i-12345', 'host2')
except exception.NotEmpty, e:
c1 = ( 0 <= str(e.args).find('doesnt have enough resource') )
self.assertTrue(c1, True)
return False
def test20(self):
"""20: everything goes well. """
#db.instance_get = Mock(return_value = self.instance8)
ret= self.manager.driver.schedule_live_migration(self.ctxt, 'i-12345', 'host2')
self.assertEqual(ret, self.instance8['host'])
def tearDown(self): def tearDown(self):

View File

@@ -55,7 +55,6 @@ from nova import utils
from nova import flags from nova import flags
from nova.db import base from nova.db import base
from twisted.internet import defer
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
@@ -67,10 +66,9 @@ class Manager(base.Base):
self.host = host self.host = host
super(Manager, self).__init__(db_driver) super(Manager, self).__init__(db_driver)
@defer.inlineCallbacks
def periodic_tasks(self, context=None): def periodic_tasks(self, context=None):
"""Tasks to be run at a periodic interval""" """Tasks to be run at a periodic interval"""
yield pass
def init_host(self): def init_host(self):
"""Do any initialization that needs to be run if this is a standalone """Do any initialization that needs to be run if this is a standalone

View File

@@ -1,209 +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.
# Copyright 2010 FathomDB Inc.
# 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.
"""
Process pool using twisted threading
"""
import logging
import StringIO
from twisted.internet import defer
from twisted.internet import error
from twisted.internet import protocol
from twisted.internet import reactor
from nova import flags
from nova.exception import ProcessExecutionError
FLAGS = flags.FLAGS
flags.DEFINE_integer('process_pool_size', 4,
'Number of processes to use in the process pool')
# This is based on _BackRelay from twister.internal.utils, but modified to
# capture both stdout and stderr, without odd stderr handling, and also to
# handle stdin
class BackRelayWithInput(protocol.ProcessProtocol):
"""
Trivial protocol for communicating with a process and turning its output
into the result of a L{Deferred}.
@ivar deferred: A L{Deferred} which will be called back with all of stdout
and all of stderr as well (as a tuple). C{terminate_on_stderr} is true
and any bytes are received over stderr, this will fire with an
L{_ProcessExecutionError} instance and the attribute will be set to
C{None}.
@ivar onProcessEnded: If C{terminate_on_stderr} is false and bytes are
received over stderr, this attribute will refer to a L{Deferred} which
will be called back when the process ends. This C{Deferred} is also
associated with the L{_ProcessExecutionError} which C{deferred} fires
with earlier in this case so that users can determine when the process
has actually ended, in addition to knowing when bytes have been
received via stderr.
"""
def __init__(self, deferred, cmd, started_deferred=None,
terminate_on_stderr=False, check_exit_code=True,
process_input=None):
self.deferred = deferred
self.cmd = cmd
self.stdout = StringIO.StringIO()
self.stderr = StringIO.StringIO()
self.started_deferred = started_deferred
self.terminate_on_stderr = terminate_on_stderr
self.check_exit_code = check_exit_code
self.process_input = process_input
self.on_process_ended = None
def _build_execution_error(self, exit_code=None):
return ProcessExecutionError(cmd=self.cmd,
exit_code=exit_code,
stdout=self.stdout.getvalue(),
stderr=self.stderr.getvalue())
def errReceived(self, text):
self.stderr.write(text)
if self.terminate_on_stderr and (self.deferred is not None):
self.on_process_ended = defer.Deferred()
self.deferred.errback(self._build_execution_error())
self.deferred = None
self.transport.loseConnection()
def outReceived(self, text):
self.stdout.write(text)
def processEnded(self, reason):
if self.deferred is not None:
stdout, stderr = self.stdout.getvalue(), self.stderr.getvalue()
exit_code = reason.value.exitCode
if self.check_exit_code and exit_code != 0:
self.deferred.errback(self._build_execution_error(exit_code))
else:
try:
if self.check_exit_code:
reason.trap(error.ProcessDone)
self.deferred.callback((stdout, stderr))
except:
# NOTE(justinsb): This logic is a little suspicious to me.
# If the callback throws an exception, then errback will
# be called also. However, this is what the unit tests
# test for.
exec_error = self._build_execution_error(exit_code)
self.deferred.errback(exec_error)
elif self.on_process_ended is not None:
self.on_process_ended.errback(reason)
def connectionMade(self):
if self.started_deferred:
self.started_deferred.callback(self)
if self.process_input:
self.transport.write(str(self.process_input))
self.transport.closeStdin()
def get_process_output(executable, args=None, env=None, path=None,
process_reactor=None, check_exit_code=True,
process_input=None, started_deferred=None,
terminate_on_stderr=False):
if process_reactor is None:
process_reactor = reactor
args = args and args or ()
env = env and env and {}
deferred = defer.Deferred()
cmd = executable
if args:
cmd = " ".join([cmd] + args)
logging.debug("Running cmd: %s", cmd)
process_handler = BackRelayWithInput(
deferred,
cmd,
started_deferred=started_deferred,
check_exit_code=check_exit_code,
process_input=process_input,
terminate_on_stderr=terminate_on_stderr)
# NOTE(vish): commands come in as unicode, but self.executes needs
# strings or process.spawn raises a deprecation warning
executable = str(executable)
if not args is None:
args = [str(x) for x in args]
process_reactor.spawnProcess(process_handler, executable,
(executable,) + tuple(args), env, path)
return deferred
class ProcessPool(object):
""" A simple process pool implementation using Twisted's Process bits.
This is pretty basic right now, but hopefully the API will be the correct
one so that it can be optimized later.
"""
def __init__(self, size=None):
self.size = size and size or FLAGS.process_pool_size
self._pool = defer.DeferredSemaphore(self.size)
def simple_execute(self, cmd, **kw):
""" Weak emulation of the old utils.execute() function.
This only exists as a way to quickly move old execute methods to
this new style of code.
NOTE(termie): This will break on args with spaces in them.
"""
parsed = cmd.split(' ')
executable, args = parsed[0], parsed[1:]
return self.execute(executable, args, **kw)
def execute(self, *args, **kw):
deferred = self._pool.acquire()
def _associate_process(proto):
deferred.process = proto.transport
return proto.transport
started = defer.Deferred()
started.addCallback(_associate_process)
kw.setdefault('started_deferred', started)
deferred.process = None
deferred.started = started
deferred.addCallback(lambda _: get_process_output(*args, **kw))
deferred.addBoth(self._release)
return deferred
def _release(self, retval=None):
self._pool.release()
return retval
class SharedPool(object):
_instance = None
def __init__(self):
if SharedPool._instance is None:
self.__class__._instance = ProcessPool()
def __getattr__(self, key):
return getattr(self._instance, key)
def simple_execute(cmd, **kwargs):
return SharedPool().simple_execute(cmd, **kwargs)

View File

@@ -25,18 +25,18 @@ import json
import logging import logging
import sys import sys
import time import time
import traceback
import uuid import uuid
from carrot import connection as carrot_connection from carrot import connection as carrot_connection
from carrot import messaging from carrot import messaging
from eventlet import greenthread from eventlet import greenthread
from twisted.internet import defer
from twisted.internet import task
from nova import context
from nova import exception from nova import exception
from nova import fakerabbit from nova import fakerabbit
from nova import flags from nova import flags
from nova import context from nova import utils
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
@@ -91,15 +91,15 @@ class Consumer(messaging.Consumer):
self.failed_connection = False self.failed_connection = False
break break
except: # Catching all because carrot sucks except: # Catching all because carrot sucks
logging.exception("AMQP server on %s:%d is unreachable." \ logging.exception(_("AMQP server on %s:%d is unreachable."
" Trying again in %d seconds." % ( " Trying again in %d seconds.") % (
FLAGS.rabbit_host, FLAGS.rabbit_host,
FLAGS.rabbit_port, FLAGS.rabbit_port,
FLAGS.rabbit_retry_interval)) FLAGS.rabbit_retry_interval))
self.failed_connection = True self.failed_connection = True
if self.failed_connection: if self.failed_connection:
logging.exception("Unable to connect to AMQP server" \ logging.exception(_("Unable to connect to AMQP server"
" after %d tries. Shutting down." % FLAGS.rabbit_max_retries) " after %d tries. Shutting down.") % FLAGS.rabbit_max_retries)
sys.exit(1) sys.exit(1)
def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False):
@@ -116,29 +116,21 @@ class Consumer(messaging.Consumer):
self.declare() self.declare()
super(Consumer, self).fetch(no_ack, auto_ack, enable_callbacks) super(Consumer, self).fetch(no_ack, auto_ack, enable_callbacks)
if self.failed_connection: if self.failed_connection:
logging.error("Reconnected to queue") logging.error(_("Reconnected to queue"))
self.failed_connection = False self.failed_connection = False
# NOTE(vish): This is catching all errors because we really don't # NOTE(vish): This is catching all errors because we really don't
# exceptions to be logged 10 times a second if some # exceptions to be logged 10 times a second if some
# persistent failure occurs. # persistent failure occurs.
except Exception: # pylint: disable-msg=W0703 except Exception: # pylint: disable-msg=W0703
if not self.failed_connection: if not self.failed_connection:
logging.exception("Failed to fetch message from queue") logging.exception(_("Failed to fetch message from queue"))
self.failed_connection = True self.failed_connection = True
def attach_to_eventlet(self): def attach_to_eventlet(self):
"""Only needed for unit tests!""" """Only needed for unit tests!"""
def fetch_repeatedly(): timer = utils.LoopingCall(self.fetch, enable_callbacks=True)
while True: timer.start(0.1)
self.fetch(enable_callbacks=True) return timer
greenthread.sleep(0.1)
greenthread.spawn(fetch_repeatedly)
def attach_to_twisted(self):
"""Attach a callback to twisted that fires 10 times a second"""
loop = task.LoopingCall(self.fetch, enable_callbacks=True)
loop.start(interval=0.1)
return loop
class Publisher(messaging.Publisher): class Publisher(messaging.Publisher):
@@ -161,7 +153,7 @@ class TopicConsumer(Consumer):
class AdapterConsumer(TopicConsumer): class AdapterConsumer(TopicConsumer):
"""Calls methods on a proxy object based on method and args""" """Calls methods on a proxy object based on method and args"""
def __init__(self, connection=None, topic="broadcast", proxy=None): def __init__(self, connection=None, topic="broadcast", proxy=None):
LOG.debug('Initing the Adapter Consumer for %s' % (topic)) LOG.debug(_('Initing the Adapter Consumer for %s') % (topic))
self.proxy = proxy self.proxy = proxy
super(AdapterConsumer, self).__init__(connection=connection, super(AdapterConsumer, self).__init__(connection=connection,
topic=topic) topic=topic)
@@ -176,7 +168,7 @@ class AdapterConsumer(TopicConsumer):
Example: {'method': 'echo', 'args': {'value': 42}} Example: {'method': 'echo', 'args': {'value': 42}}
""" """
LOG.debug('received %s' % (message_data)) LOG.debug(_('received %s') % (message_data))
msg_id = message_data.pop('_msg_id', None) msg_id = message_data.pop('_msg_id', None)
ctxt = _unpack_context(message_data) ctxt = _unpack_context(message_data)
@@ -189,18 +181,20 @@ class AdapterConsumer(TopicConsumer):
# messages stay in the queue indefinitely, so for now # messages stay in the queue indefinitely, so for now
# we just log the message and send an error string # we just log the message and send an error string
# back to the caller # back to the caller
LOG.warn('no method for message: %s' % (message_data)) LOG.warn(_('no method for message: %s') % (message_data))
msg_reply(msg_id, 'No method for message: %s' % message_data) msg_reply(msg_id, _('No method for message: %s') % message_data)
return return
node_func = getattr(self.proxy, str(method)) node_func = getattr(self.proxy, str(method))
node_args = dict((str(k), v) for k, v in args.iteritems()) node_args = dict((str(k), v) for k, v in args.iteritems())
# NOTE(vish): magic is fun! # NOTE(vish): magic is fun!
# pylint: disable-msg=W0142 try:
d = defer.maybeDeferred(node_func, context=ctxt, **node_args) rval = node_func(context=ctxt, **node_args)
if msg_id: if msg_id:
d.addCallback(lambda rval: msg_reply(msg_id, rval, None)) msg_reply(msg_id, rval, None)
d.addErrback(lambda e: msg_reply(msg_id, None, e)) except Exception as e:
if msg_id:
msg_reply(msg_id, None, sys.exc_info())
return return
@@ -242,14 +236,16 @@ class DirectPublisher(Publisher):
def msg_reply(msg_id, reply=None, failure=None): def msg_reply(msg_id, reply=None, failure=None):
"""Sends a reply or an error on the channel signified by msg_id """Sends a reply or an error on the channel signified by msg_id
failure should be a twisted failure object""" failure should be a sys.exc_info() tuple.
"""
if failure: if failure:
message = failure.getErrorMessage() message = str(failure[1])
traceback = failure.getTraceback() tb = traceback.format_exception(*failure)
logging.error("Returning exception %s to caller", message) logging.error(_("Returning exception %s to caller"), message)
logging.error(traceback) logging.error(tb)
failure = (failure.type.__name__, str(failure.value), traceback) failure = (failure[0].__name__, str(failure[1]), tb)
conn = Connection.instance() conn = Connection.instance(True)
publisher = DirectPublisher(connection=conn, msg_id=msg_id) publisher = DirectPublisher(connection=conn, msg_id=msg_id)
try: try:
publisher.send({'result': reply, 'failure': failure}) publisher.send({'result': reply, 'failure': failure})
@@ -287,7 +283,7 @@ def _unpack_context(msg):
if key.startswith('_context_'): if key.startswith('_context_'):
value = msg.pop(key) value = msg.pop(key)
context_dict[key[9:]] = value context_dict[key[9:]] = value
LOG.debug('unpacked context: %s', context_dict) LOG.debug(_('unpacked context: %s'), context_dict)
return context.RequestContext.from_dict(context_dict) return context.RequestContext.from_dict(context_dict)
@@ -306,14 +302,13 @@ def _pack_context(msg, context):
def call(context, topic, msg): def call(context, topic, msg):
"""Sends a message on a topic and wait for a response""" """Sends a message on a topic and wait for a response"""
LOG.debug("Making asynchronous call...") LOG.debug(_("Making asynchronous call..."))
msg_id = uuid.uuid4().hex msg_id = uuid.uuid4().hex
msg.update({'_msg_id': msg_id}) msg.update({'_msg_id': msg_id})
LOG.debug("MSG_ID is %s" % (msg_id)) LOG.debug(_("MSG_ID is %s") % (msg_id))
_pack_context(msg, context) _pack_context(msg, context)
class WaitMessage(object): class WaitMessage(object):
def __call__(self, data, message): def __call__(self, data, message):
"""Acks message and sets result.""" """Acks message and sets result."""
message.ack() message.ack()
@@ -337,41 +332,15 @@ def call(context, topic, msg):
except StopIteration: except StopIteration:
pass pass
consumer.close() consumer.close()
# NOTE(termie): this is a little bit of a change from the original
# non-eventlet code where returning a Failure
# instance from a deferred call is very similar to
# raising an exception
if isinstance(wait_msg.result, Exception):
raise wait_msg.result
return wait_msg.result return wait_msg.result
def call_twisted(context, topic, msg):
"""Sends a message on a topic and wait for a response"""
LOG.debug("Making asynchronous call...")
msg_id = uuid.uuid4().hex
msg.update({'_msg_id': msg_id})
LOG.debug("MSG_ID is %s" % (msg_id))
_pack_context(msg, context)
conn = Connection.instance()
d = defer.Deferred()
consumer = DirectConsumer(connection=conn, msg_id=msg_id)
def deferred_receive(data, message):
"""Acks message and callbacks or errbacks"""
message.ack()
if data['failure']:
return d.errback(RemoteError(*data['failure']))
else:
return d.callback(data['result'])
consumer.register_callback(deferred_receive)
injected = consumer.attach_to_twisted()
# clean up after the injected listened and return x
d.addCallback(lambda x: injected.stop() and x or x)
publisher = TopicPublisher(connection=conn, topic=topic)
publisher.send(msg)
publisher.close()
return d
def cast(context, topic, msg): def cast(context, topic, msg):
"""Sends a message on a topic without waiting for a response""" """Sends a message on a topic without waiting for a response"""
LOG.debug("Making asynchronous cast...") LOG.debug("Making asynchronous cast...")
@@ -384,7 +353,7 @@ def cast(context, topic, msg):
def generic_response(message_data, message): def generic_response(message_data, message):
"""Logs a result and exits""" """Logs a result and exits"""
LOG.debug('response %s', message_data) LOG.debug(_('response %s'), message_data)
message.ack() message.ack()
sys.exit(0) sys.exit(0)
@@ -393,8 +362,8 @@ def send_message(topic, message, wait=True):
"""Sends a message for testing""" """Sends a message for testing"""
msg_id = uuid.uuid4().hex msg_id = uuid.uuid4().hex
message.update({'_msg_id': msg_id}) message.update({'_msg_id': msg_id})
LOG.debug('topic is %s', topic) LOG.debug(_('topic is %s'), topic)
LOG.debug('message %s', message) LOG.debug(_('message %s'), message)
if wait: if wait:
consumer = messaging.Consumer(connection=Connection.instance(), consumer = messaging.Consumer(connection=Connection.instance(),

View File

@@ -34,5 +34,5 @@ class ChanceScheduler(driver.Scheduler):
hosts = self.hosts_up(context, topic) hosts = self.hosts_up(context, topic)
if not hosts: if not hosts:
raise driver.NoValidHost("No hosts found") raise driver.NoValidHost(_("No hosts found"))
return hosts[int(random.random() * len(hosts))] return hosts[int(random.random() * len(hosts))]

View File

@@ -22,10 +22,14 @@ Scheduler base class that all Schedulers should inherit from
""" """
import datetime import datetime
import logging
from nova import db from nova import db
from nova import exception from nova import exception
from nova import flags from nova import flags
from nova import rpc
from nova.api.ec2 import cloud
from nova.compute import power_state
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_integer('service_down_time', 60, flags.DEFINE_integer('service_down_time', 60,
@@ -58,4 +62,137 @@ class Scheduler(object):
def schedule(self, context, topic, *_args, **_kwargs): def schedule(self, context, topic, *_args, **_kwargs):
"""Must override at least this method for scheduler to work.""" """Must override at least this method for scheduler to work."""
raise NotImplementedError("Must implement a fallback schedule") raise NotImplementedError(_("Must implement a fallback schedule"))
def schedule_live_migration(self, context, instance_id, dest):
""" live migration method """
# Whether instance exists and running
# try-catch clause is necessary because only internal_id is shown
# when NotFound exception occurs. it isnot understandable to admins.
try:
instance_ref = db.instance_get(context, instance_id)
ec2_id = instance_ref['hostname']
internal_id = instance_ref['internal_id']
except exception.NotFound, e:
msg = _('Unexpected error: instance is not found')
e.args += ('\n' + msg, )
raise e
# Checking instance state.
if power_state.RUNNING != instance_ref['state'] or \
'running' != instance_ref['state_description']:
msg = _('Instance(%s) is not running')
raise exception.Invalid(msg % ec2_id)
# Checking destination host exists
dhost_ref = db.host_get_by_name(context, dest)
# Checking whether The host where instance is running
# and dest is not same.
src = instance_ref['host']
if dest == src:
msg = _('%s is where %s is running now. choose other host.')
raise exception.Invalid(msg % (dest, ec2_id))
# Checking dest is compute node.
services = db.service_get_all_by_topic(context, 'compute')
if dest not in [service.host for service in services]:
msg = _('%s must be compute node')
raise exception.Invalid(msg % dest)
# Checking dest host is alive.
service = [service for service in services if service.host == dest]
service = service[0]
if not self.service_is_up(service):
msg = _('%s is not alive(time synchronize problem?)')
raise exception.Invalid(msg % dest)
# NOTE(masumotok): Below pre-checkings are followed by
# http://wiki.libvirt.org/page/TodoPreMigrationChecks
# Checking hypervisor is same.
orighost = instance_ref['launched_on']
ohost_ref = db.host_get_by_name(context, orighost)
otype = ohost_ref['hypervisor_type']
dtype = dhost_ref['hypervisor_type']
if otype != dtype:
msg = _('Different hypervisor type(%s->%s)')
raise exception.Invalid(msg % (otype, dtype))
# Checkng hypervisor version.
oversion = ohost_ref['hypervisor_version']
dversion = dhost_ref['hypervisor_version']
if oversion > dversion:
msg = _('Older hypervisor version(%s->%s)')
raise exception.Invalid(msg % (oversion, dversion))
# Checking cpuinfo.
cpuinfo = ohost_ref['cpu_info']
if str != type(cpuinfo):
msg = _('Unexpected err: not found cpu_info for %s on DB.hosts')
raise exception.Invalid(msg % orighost)
ret = rpc.call(context,
db.queue_get_for(context, FLAGS.compute_topic, dest),
{"method": 'compareCPU',
"args": {'xml': cpuinfo}})
if int != type(ret):
raise ret
if 0 >= ret:
u = 'http://libvirt.org/html/libvirt-libvirt.html'
u += '#virCPUCompareResult'
msg = '%s doesnt have compatibility to %s(where %s launching at)\n'
msg += 'result:%d \n'
msg += 'Refer to %s'
msg = _(msg)
raise exception.Invalid(msg % (dest, src, ec2_id, ret, u))
# Checking dst host still has enough capacities.
self.has_enough_resource(context, instance_id, dest)
# Changing instance_state.
db.instance_set_state(context,
instance_id,
power_state.PAUSED,
'migrating')
# Requesting live migration.
return src
def has_enough_resource(self, context, instance_id, dest):
""" Check if destination host has enough resource for live migration"""
# Getting instance information
instance_ref = db.instance_get(context, instance_id)
ec2_id = instance_ref['hostname']
vcpus = instance_ref['vcpus']
mem = instance_ref['memory_mb']
hdd = instance_ref['local_gb']
# Gettin host information
host_ref = db.host_get_by_name(context, dest)
total_cpu = int(host_ref['vcpus'])
total_mem = int(host_ref['memory_mb'])
total_hdd = int(host_ref['local_gb'])
instances_ref = db.instance_get_all_by_host(context, dest)
for i_ref in instances_ref:
total_cpu -= int(i_ref['vcpus'])
total_mem -= int(i_ref['memory_mb'])
total_hdd -= int(i_ref['local_gb'])
# Checking host has enough information
logging.debug('host(%s) remains vcpu:%s mem:%s hdd:%s,' %
(dest, total_cpu, total_mem, total_hdd))
logging.debug('instance(%s) has vcpu:%s mem:%s hdd:%s,' %
(ec2_id, vcpus, mem, hdd))
if total_cpu <= vcpus or total_mem <= mem or total_hdd <= hdd:
msg = '%s doesnt have enough resource for %s' % (dest, ec2_id)
raise exception.NotEmpty(msg)
logging.debug(_('%s has enough resource for %s') % (dest, ec2_id))

View File

@@ -30,8 +30,6 @@ from nova import manager
from nova import rpc from nova import rpc
from nova import utils from nova import utils
from nova import exception from nova import exception
from nova.api.ec2 import cloud
from nova.compute import power_state
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_string('scheduler_driver', flags.DEFINE_string('scheduler_driver',
@@ -68,119 +66,11 @@ class SchedulerManager(manager.Manager):
db.queue_get_for(context, topic, host), db.queue_get_for(context, topic, host),
{"method": method, {"method": method,
"args": kwargs}) "args": kwargs})
logging.debug("Casting to %s %s for %s", topic, host, method) logging.debug(_("Casting to %s %s for %s"), topic, host, method)
def live_migration(self, context, ec2_id, dest):
""" live migration method"""
# (masumotok) below pre-checking is followed by
# http://wiki.libvirt.org/page/TodoPreMigrationChecks
# 1. get instance id
internal_id = cloud.ec2_id_to_internal_id(ec2_id)
instance_ref = db.instance_get_by_internal_id(context, internal_id)
instance_id = instance_ref['id']
# 2. get src host and dst host
src = instance_ref['launched_on']
shost_ref = db.host_get_by_name(context, src )
dhost_ref = db.host_get_by_name(context, dest)
# 3. dest should be compute
services = db.service_get_all_by_topic(context, 'compute')
logging.warn('%s' % [service.host for service in services])
if dest not in [service.host for service in services] :
raise exception.Invalid('%s must be compute node' % dest)
# 4. check hypervisor is same
shypervisor = shost_ref['hypervisor_type']
dhypervisor = dhost_ref['hypervisor_type']
if shypervisor != dhypervisor:
msg = 'Different hypervisor type(%s->%s)' % (shypervisor, dhypervisor)
raise exception.Invalid(msg)
# 5. check hypervisor version
shypervisor = shost_ref['hypervisor_version']
dhypervisor = dhost_ref['hypervisor_version']
if shypervisor > dhypervisor:
msg = 'Older hypervisor version(%s->%s)' % (shypervisor, dhypervisor)
raise exception.Invalid(msg)
# 6. check cpuinfo
cpuinfo = shost_ref['cpu_info']
if str != type(cpuinfo):
msg = 'Unexpected err: no cpu_info for %s found on DB.hosts' % src
raise exception.Invalid(msg)
logging.warn('cpuinfo %s %d' % (cpuinfo, len(cpuinfo)))
ret = rpc.call(context,
db.queue_get_for(context, FLAGS.compute_topic, dest),
{"method": 'compareCPU',
"args": {'xml': cpuinfo}})
if int != type(ret):
raise ret
if 0 >= ret :
msg = '%s doesnt have compatibility to %s(where %s launching at)\n' \
% (dest, src, ec2_id)
msg += 'result:%d \n' % ret
msg += 'Refer to %s' % \
'http://libvirt.org/html/libvirt-libvirt.html#virCPUCompareResult'
raise exception.Invalid(msg)
# 7. check dst host still has enough capacities
self.has_enough_resource(context, instance_id, dest)
# 8. change instance_state
db.instance_set_state(context,
instance_id,
power_state.PAUSED,
'migrating')
# 9. request live migration
host = instance_ref['host']
rpc.cast(context,
db.queue_get_for(context, FLAGS.compute_topic, host),
{"method": 'live_migration',
"args": {'instance_id': instance_id,
'dest': dest}})
def has_enough_resource(self, context, instance_id, dest):
""" check if destination host has enough resource for live migration"""
# get instance information
instance_ref = db.instance_get(context, instance_id)
ec2_id = instance_ref['hostname']
vcpus = instance_ref['vcpus']
mem = instance_ref['memory_mb']
hdd = instance_ref['local_gb']
# get host information
host_ref = db.host_get_by_name(context, dest)
total_cpu = int(host_ref['vcpus'])
total_mem = int(host_ref['memory_mb'])
total_hdd = int(host_ref['local_gb'])
instances_ref = db.instance_get_all_by_host(context, dest)
for i_ref in instances_ref:
total_cpu -= int(i_ref['vcpus'])
total_mem -= int(i_ref['memory_mb'])
total_hdd -= int(i_ref['local_gb'])
# check host has enough information
logging.debug('host(%s) remains vcpu:%s mem:%s hdd:%s,' %
(dest, total_cpu, total_mem, total_hdd))
logging.debug('instance(%s) has vcpu:%s mem:%s hdd:%s,' %
(ec2_id, total_cpu, total_mem, total_hdd))
if total_cpu <= vcpus or total_mem <= mem or total_hdd <= hdd:
msg = '%s doesnt have enough resource for %s' % (dest, ec2_id)
raise exception.NotEmpty(msg)
logging.debug('%s has enough resource for %s' % (dest, ec2_id))
# NOTE (masumotok) : This method should be moved to nova.api.ec2.admin.
# Based on bear design summit discussion,
# just put this here for bexar release.
def show_host_resource(self, context, host, *args): def show_host_resource(self, context, host, *args):
""" show the physical/usage resource given by hosts.""" """ show the physical/usage resource given by hosts."""
@@ -191,12 +81,12 @@ class SchedulerManager(manager.Manager):
except: except:
raise raise
# get physical resource information # Getting physical resource information
h_resource = {'vcpus': host_ref['vcpus'], h_resource = {'vcpus': host_ref['vcpus'],
'memory_mb': host_ref['memory_mb'], 'memory_mb': host_ref['memory_mb'],
'local_gb': host_ref['local_gb']} 'local_gb': host_ref['local_gb']}
# get usage resource information # Getting usage resource information
u_resource = {} u_resource = {}
instances_ref = db.instance_get_all_by_host(context, host_ref['name']) instances_ref = db.instance_get_all_by_host(context, host_ref['name'])
@@ -215,8 +105,8 @@ class SchedulerManager(manager.Manager):
hdd = db.instance_get_disk_sum_by_host_and_project(context, hdd = db.instance_get_disk_sum_by_host_and_project(context,
host, host,
p_id) p_id)
u_resource[p_id] = {'vcpus': vcpus, u_resource[p_id] = {'vcpus': vcpus,
'memory_mb': mem, 'memory_mb': mem,
'local_gb': hdd} 'local_gb': hdd}
return {'ret': True, 'phy_resource': h_resource, 'usage': u_resource} return {'ret': True, 'phy_resource': h_resource, 'usage': u_resource}

View File

@@ -47,7 +47,7 @@ class SimpleScheduler(chance.ChanceScheduler):
for result in results: for result in results:
(service, instance_cores) = result (service, instance_cores) = result
if instance_cores + instance_ref['vcpus'] > FLAGS.max_cores: if instance_cores + instance_ref['vcpus'] > FLAGS.max_cores:
raise driver.NoValidHost("All hosts have too many cores") raise driver.NoValidHost(_("All hosts have too many cores"))
if self.service_is_up(service): if self.service_is_up(service):
# NOTE(vish): this probably belongs in the manager, if we # NOTE(vish): this probably belongs in the manager, if we
# can generalize this somehow # can generalize this somehow
@@ -57,7 +57,7 @@ class SimpleScheduler(chance.ChanceScheduler):
{'host': service['host'], {'host': service['host'],
'scheduled_at': now}) 'scheduled_at': now})
return service['host'] return service['host']
raise driver.NoValidHost("No hosts found") raise driver.NoValidHost(_("No hosts found"))
def schedule_create_volume(self, context, volume_id, *_args, **_kwargs): def schedule_create_volume(self, context, volume_id, *_args, **_kwargs):
"""Picks a host that is up and has the fewest volumes.""" """Picks a host that is up and has the fewest volumes."""
@@ -66,7 +66,8 @@ class SimpleScheduler(chance.ChanceScheduler):
for result in results: for result in results:
(service, volume_gigabytes) = result (service, volume_gigabytes) = result
if volume_gigabytes + volume_ref['size'] > FLAGS.max_gigabytes: if volume_gigabytes + volume_ref['size'] > FLAGS.max_gigabytes:
raise driver.NoValidHost("All hosts have too many gigabytes") raise driver.NoValidHost(_("All hosts have too many "
"gigabytes"))
if self.service_is_up(service): if self.service_is_up(service):
# NOTE(vish): this probably belongs in the manager, if we # NOTE(vish): this probably belongs in the manager, if we
# can generalize this somehow # can generalize this somehow
@@ -76,7 +77,7 @@ class SimpleScheduler(chance.ChanceScheduler):
{'host': service['host'], {'host': service['host'],
'scheduled_at': now}) 'scheduled_at': now})
return service['host'] return service['host']
raise driver.NoValidHost("No hosts found") raise driver.NoValidHost(_("No hosts found"))
def schedule_set_network_host(self, context, *_args, **_kwargs): def schedule_set_network_host(self, context, *_args, **_kwargs):
"""Picks a host that is up and has the fewest networks.""" """Picks a host that is up and has the fewest networks."""
@@ -85,7 +86,7 @@ class SimpleScheduler(chance.ChanceScheduler):
for result in results: for result in results:
(service, instance_count) = result (service, instance_count) = result
if instance_count >= FLAGS.max_networks: if instance_count >= FLAGS.max_networks:
raise driver.NoValidHost("All hosts have too many networks") raise driver.NoValidHost(_("All hosts have too many networks"))
if self.service_is_up(service): if self.service_is_up(service):
return service['host'] return service['host']
raise driver.NoValidHost("No hosts found") raise driver.NoValidHost(_("No hosts found"))

View File

@@ -1,151 +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.
"""
Base functionality for nova daemons - gradually being replaced with twistd.py.
"""
import daemon
from daemon import pidlockfile
import logging
import logging.handlers
import os
import signal
import sys
import time
from nova import flags
FLAGS = flags.FLAGS
flags.DEFINE_bool('daemonize', False, 'daemonize this process')
# NOTE(termie): right now I am defaulting to using syslog when we daemonize
# it may be better to do something else -shrug-
# NOTE(Devin): I think we should let each process have its own log file
# and put it in /var/logs/nova/(appname).log
# This makes debugging much easier and cuts down on sys log
# clutter.
flags.DEFINE_bool('use_syslog', True, 'output to syslog when daemonizing')
flags.DEFINE_string('logfile', None, 'log file to output to')
flags.DEFINE_string('logdir', None, 'directory to keep log files in '
'(will be prepended to $logfile)')
flags.DEFINE_string('pidfile', None, 'pid file to output to')
flags.DEFINE_string('working_directory', './', 'working directory...')
flags.DEFINE_integer('uid', os.getuid(), 'uid under which to run')
flags.DEFINE_integer('gid', os.getgid(), 'gid under which to run')
def stop(pidfile):
"""
Stop the daemon
"""
# Get the pid from the pidfile
try:
pid = int(open(pidfile, 'r').read().strip())
except IOError:
message = "pidfile %s does not exist. Daemon not running?\n"
sys.stderr.write(message % pidfile)
return
# Try killing the daemon process
try:
while 1:
os.kill(pid, signal.SIGTERM)
time.sleep(0.1)
except OSError, err:
err = str(err)
if err.find("No such process") > 0:
if os.path.exists(pidfile):
os.remove(pidfile)
else:
print str(err)
sys.exit(1)
def serve(name, main):
"""Controller for server"""
argv = FLAGS(sys.argv)
if not FLAGS.pidfile:
FLAGS.pidfile = '%s.pid' % name
logging.debug("Full set of FLAGS: \n\n\n")
for flag in FLAGS:
logging.debug("%s : %s", flag, FLAGS.get(flag, None))
action = 'start'
if len(argv) > 1:
action = argv.pop()
if action == 'stop':
stop(FLAGS.pidfile)
sys.exit()
elif action == 'restart':
stop(FLAGS.pidfile)
elif action == 'start':
pass
else:
print 'usage: %s [options] [start|stop|restart]' % argv[0]
sys.exit(1)
daemonize(argv, name, main)
def daemonize(args, name, main):
"""Does the work of daemonizing the process"""
logging.getLogger('amqplib').setLevel(logging.WARN)
files_to_keep = []
if FLAGS.daemonize:
logger = logging.getLogger()
formatter = logging.Formatter(
name + '(%(name)s): %(levelname)s %(message)s')
if FLAGS.use_syslog and not FLAGS.logfile:
syslog = logging.handlers.SysLogHandler(address='/dev/log')
syslog.setFormatter(formatter)
logger.addHandler(syslog)
files_to_keep.append(syslog.socket)
else:
if not FLAGS.logfile:
FLAGS.logfile = '%s.log' % name
if FLAGS.logdir:
FLAGS.logfile = os.path.join(FLAGS.logdir, FLAGS.logfile)
logfile = logging.FileHandler(FLAGS.logfile)
logfile.setFormatter(formatter)
logger.addHandler(logfile)
files_to_keep.append(logfile.stream)
stdin, stdout, stderr = None, None, None
else:
stdin, stdout, stderr = sys.stdin, sys.stdout, sys.stderr
if FLAGS.verbose:
logging.getLogger().setLevel(logging.DEBUG)
else:
logging.getLogger().setLevel(logging.WARNING)
with daemon.DaemonContext(
detach_process=FLAGS.daemonize,
working_directory=FLAGS.working_directory,
pidfile=pidlockfile.TimeoutPIDLockFile(FLAGS.pidfile,
acquire_timeout=1,
threaded=False),
stdin=stdin,
stdout=stdout,
stderr=stderr,
uid=FLAGS.uid,
gid=FLAGS.gid,
files_preserve=files_to_keep):
main(args)

View File

@@ -17,21 +17,17 @@
# under the License. # under the License.
""" """
A service is a very thin wrapper around a Manager object. It exposes the Generic Node baseclass for all workers that run on hosts
manager's public methods to other components of the system via rpc. It will
report state periodically to the database and is responsible for initiating
any periodic tasts that need to be executed on a given host.
This module contains Service, a generic baseclass for all workers.
""" """
import inspect import inspect
import logging import logging
import os import os
import sys
from twisted.internet import defer from eventlet import event
from twisted.internet import task from eventlet import greenthread
from twisted.application import service from eventlet import greenpool
from nova import context from nova import context
from nova import db from nova import db
@@ -50,8 +46,16 @@ flags.DEFINE_integer('periodic_interval', 60,
'seconds between running periodic tasks', 'seconds between running periodic tasks',
lower_bound=1) lower_bound=1)
flags.DEFINE_string('pidfile', None,
'pidfile to use for this service')
class Service(object, service.Service):
flags.DEFINE_flag(flags.HelpFlag())
flags.DEFINE_flag(flags.HelpshortFlag())
flags.DEFINE_flag(flags.HelpXMLFlag())
class Service(object):
"""Base class for workers that run on hosts.""" """Base class for workers that run on hosts."""
def __init__(self, host, binary, topic, manager, report_interval=None, def __init__(self, host, binary, topic, manager, report_interval=None,
@@ -64,8 +68,9 @@ class Service(object, service.Service):
self.periodic_interval = periodic_interval self.periodic_interval = periodic_interval
super(Service, self).__init__(*args, **kwargs) super(Service, self).__init__(*args, **kwargs)
self.saved_args, self.saved_kwargs = args, kwargs self.saved_args, self.saved_kwargs = args, kwargs
self.timers = []
def startService(self): # pylint: disable-msg C0103 def start(self):
manager_class = utils.import_class(self.manager_class_name) manager_class = utils.import_class(self.manager_class_name)
self.manager = manager_class(host=self.host, *self.saved_args, self.manager = manager_class(host=self.host, *self.saved_args,
**self.saved_kwargs) **self.saved_kwargs)
@@ -87,26 +92,29 @@ class Service(object, service.Service):
except exception.NotFound: except exception.NotFound:
self._create_service_ref(ctxt) self._create_service_ref(ctxt)
conn = rpc.Connection.instance() conn1 = rpc.Connection.instance(new=True)
conn2 = rpc.Connection.instance(new=True)
if self.report_interval: if self.report_interval:
consumer_all = rpc.AdapterConsumer( consumer_all = rpc.AdapterConsumer(
connection=conn, connection=conn1,
topic=self.topic, topic=self.topic,
proxy=self) proxy=self)
consumer_node = rpc.AdapterConsumer( consumer_node = rpc.AdapterConsumer(
connection=conn, connection=conn2,
topic='%s.%s' % (self.topic, self.host), topic='%s.%s' % (self.topic, self.host),
proxy=self) proxy=self)
consumer_all.attach_to_twisted() self.timers.append(consumer_all.attach_to_eventlet())
consumer_node.attach_to_twisted() self.timers.append(consumer_node.attach_to_eventlet())
pulse = task.LoopingCall(self.report_state) pulse = utils.LoopingCall(self.report_state)
pulse.start(interval=self.report_interval, now=False) pulse.start(interval=self.report_interval, now=False)
self.timers.append(pulse)
if self.periodic_interval: if self.periodic_interval:
pulse = task.LoopingCall(self.periodic_tasks) periodic = utils.LoopingCall(self.periodic_tasks)
pulse.start(interval=self.periodic_interval, now=False) periodic.start(interval=self.periodic_interval, now=False)
self.timers.append(periodic)
def _create_service_ref(self, context): def _create_service_ref(self, context):
service_ref = db.service_create(context, service_ref = db.service_create(context,
@@ -170,29 +178,32 @@ class Service(object, service.Service):
report_interval = FLAGS.report_interval report_interval = FLAGS.report_interval
if not periodic_interval: if not periodic_interval:
periodic_interval = FLAGS.periodic_interval periodic_interval = FLAGS.periodic_interval
logging.warn("Starting %s node", topic) logging.warn(_("Starting %s node"), topic)
service_obj = cls(host, binary, topic, manager, service_obj = cls(host, binary, topic, manager,
report_interval, periodic_interval) report_interval, periodic_interval)
# This is the parent service that twistd will be looking for when it return service_obj
# parses this file, return it so that we can get it into globals.
application = service.Application(binary)
service_obj.setServiceParent(application)
return application
def kill(self): def kill(self):
"""Destroy the service object in the datastore""" """Destroy the service object in the datastore"""
self.stop()
try: try:
db.service_destroy(context.get_admin_context(), self.service_id) db.service_destroy(context.get_admin_context(), self.service_id)
except exception.NotFound: except exception.NotFound:
logging.warn("Service killed that has no database entry") logging.warn(_("Service killed that has no database entry"))
def stop(self):
for x in self.timers:
try:
x.stop()
except Exception:
pass
self.timers = []
@defer.inlineCallbacks
def periodic_tasks(self): def periodic_tasks(self):
"""Tasks to be run at a periodic interval""" """Tasks to be run at a periodic interval"""
yield self.manager.periodic_tasks(context.get_admin_context()) self.manager.periodic_tasks(context.get_admin_context())
@defer.inlineCallbacks
def report_state(self): def report_state(self):
"""Update the state of this service in the datastore.""" """Update the state of this service in the datastore."""
ctxt = context.get_admin_context() ctxt = context.get_admin_context()
@@ -200,8 +211,8 @@ class Service(object, service.Service):
try: try:
service_ref = db.service_get(ctxt, self.service_id) service_ref = db.service_get(ctxt, self.service_id)
except exception.NotFound: except exception.NotFound:
logging.debug("The service database object disappeared, " logging.debug(_("The service database object disappeared, "
"Recreating it.") "Recreating it."))
self._create_service_ref(ctxt) self._create_service_ref(ctxt)
service_ref = db.service_get(ctxt, self.service_id) service_ref = db.service_get(ctxt, self.service_id)
@@ -212,11 +223,39 @@ class Service(object, service.Service):
# TODO(termie): make this pattern be more elegant. # TODO(termie): make this pattern be more elegant.
if getattr(self, "model_disconnected", False): if getattr(self, "model_disconnected", False):
self.model_disconnected = False self.model_disconnected = False
logging.error("Recovered model server connection!") logging.error(_("Recovered model server connection!"))
# TODO(vish): this should probably only catch connection errors # TODO(vish): this should probably only catch connection errors
except Exception: # pylint: disable-msg=W0702 except Exception: # pylint: disable-msg=W0702
if not getattr(self, "model_disconnected", False): if not getattr(self, "model_disconnected", False):
self.model_disconnected = True self.model_disconnected = True
logging.exception("model server went away") logging.exception(_("model server went away"))
yield
def serve(*services):
argv = FLAGS(sys.argv)
if not services:
services = [Service.create()]
name = '_'.join(x.binary for x in services)
logging.debug("Serving %s" % name)
logging.getLogger('amqplib').setLevel(logging.WARN)
if FLAGS.verbose:
logging.getLogger().setLevel(logging.DEBUG)
else:
logging.getLogger().setLevel(logging.WARNING)
logging.debug(_("Full set of FLAGS:"))
for flag in FLAGS:
logging.debug("%s : %s" % (flag, FLAGS.get(flag, None)))
for x in services:
x.start()
def wait():
while True:
greenthread.sleep(5)

222
nova/service.py.THIS Normal file
View File

@@ -0,0 +1,222 @@
# 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.
"""
A service is a very thin wrapper around a Manager object. It exposes the
manager's public methods to other components of the system via rpc. It will
report state periodically to the database and is responsible for initiating
any periodic tasts that need to be executed on a given host.
This module contains Service, a generic baseclass for all workers.
"""
import inspect
import logging
import os
from twisted.internet import defer
from twisted.internet import task
from twisted.application import service
from nova import context
from nova import db
from nova import exception
from nova import flags
from nova import rpc
from nova import utils
FLAGS = flags.FLAGS
flags.DEFINE_integer('report_interval', 10,
'seconds between nodes reporting state to datastore',
lower_bound=1)
flags.DEFINE_integer('periodic_interval', 60,
'seconds between running periodic tasks',
lower_bound=1)
class Service(object, service.Service):
"""Base class for workers that run on hosts."""
def __init__(self, host, binary, topic, manager, report_interval=None,
periodic_interval=None, *args, **kwargs):
self.host = host
self.binary = binary
self.topic = topic
self.manager_class_name = manager
self.report_interval = report_interval
self.periodic_interval = periodic_interval
super(Service, self).__init__(*args, **kwargs)
self.saved_args, self.saved_kwargs = args, kwargs
def startService(self): # pylint: disable-msg C0103
manager_class = utils.import_class(self.manager_class_name)
self.manager = manager_class(host=self.host, *self.saved_args,
**self.saved_kwargs)
self.manager.init_host()
self.model_disconnected = False
ctxt = context.get_admin_context()
try:
host_ref = db.host_get_by_name(ctxt, self.host)
except exception.NotFound:
host_ref = db.host_create(ctxt, {'name': self.host})
host_ref = self._update_host_ref(ctxt, host_ref)
try:
service_ref = db.service_get_by_args(ctxt,
self.host,
self.binary)
self.service_id = service_ref['id']
except exception.NotFound:
self._create_service_ref(ctxt)
conn = rpc.Connection.instance()
if self.report_interval:
consumer_all = rpc.AdapterConsumer(
connection=conn,
topic=self.topic,
proxy=self)
consumer_node = rpc.AdapterConsumer(
connection=conn,
topic='%s.%s' % (self.topic, self.host),
proxy=self)
consumer_all.attach_to_twisted()
consumer_node.attach_to_twisted()
pulse = task.LoopingCall(self.report_state)
pulse.start(interval=self.report_interval, now=False)
if self.periodic_interval:
pulse = task.LoopingCall(self.periodic_tasks)
pulse.start(interval=self.periodic_interval, now=False)
def _create_service_ref(self, context):
service_ref = db.service_create(context,
{'host': self.host,
'binary': self.binary,
'topic': self.topic,
'report_count': 0})
self.service_id = service_ref['id']
def _update_host_ref(self, context, host_ref):
if 0 <= self.manager_class_name.find('ComputeManager'):
vcpu = self.manager.driver.get_vcpu_number()
memory_mb = self.manager.get_memory_mb()
local_gb = self.manager.get_local_gb()
hypervisor = self.manager.driver.get_hypervisor_type()
version = self.manager.driver.get_hypervisor_version()
cpu_xml = self.manager.driver.get_cpu_xml()
db.host_update(context,
host_ref['id'],
{'vcpus': vcpu,
'memory_mb': memory_mb,
'local_gb': local_gb,
'hypervisor_type': hypervisor,
'hypervisor_version': version,
'cpu_info':cpu_xml })
return host_ref
def __getattr__(self, key):
manager = self.__dict__.get('manager', None)
return getattr(manager, key)
@classmethod
def create(cls,
host=None,
binary=None,
topic=None,
manager=None,
report_interval=None,
periodic_interval=None):
"""Instantiates class and passes back application object.
Args:
host, defaults to FLAGS.host
binary, defaults to basename of executable
topic, defaults to bin_name - "nova-" part
manager, defaults to FLAGS.<topic>_manager
report_interval, defaults to FLAGS.report_interval
periodic_interval, defaults to FLAGS.periodic_interval
"""
if not host:
host = FLAGS.host
if not binary:
binary = os.path.basename(inspect.stack()[-1][1])
if not topic:
topic = binary.rpartition("nova-")[2]
if not manager:
manager = FLAGS.get('%s_manager' % topic, None)
if not report_interval:
report_interval = FLAGS.report_interval
if not periodic_interval:
periodic_interval = FLAGS.periodic_interval
logging.warn("Starting %s node", topic)
service_obj = cls(host, binary, topic, manager,
report_interval, periodic_interval)
# This is the parent service that twistd will be looking for when it
# parses this file, return it so that we can get it into globals.
application = service.Application(binary)
service_obj.setServiceParent(application)
return application
def kill(self):
"""Destroy the service object in the datastore"""
try:
db.service_destroy(context.get_admin_context(), self.service_id)
except exception.NotFound:
logging.warn("Service killed that has no database entry")
@defer.inlineCallbacks
def periodic_tasks(self):
"""Tasks to be run at a periodic interval"""
yield self.manager.periodic_tasks(context.get_admin_context())
@defer.inlineCallbacks
def report_state(self):
"""Update the state of this service in the datastore."""
ctxt = context.get_admin_context()
try:
try:
service_ref = db.service_get(ctxt, self.service_id)
except exception.NotFound:
logging.debug("The service database object disappeared, "
"Recreating it.")
self._create_service_ref(ctxt)
service_ref = db.service_get(ctxt, self.service_id)
db.service_update(ctxt,
self.service_id,
{'report_count': service_ref['report_count'] + 1})
# TODO(termie): make this pattern be more elegant.
if getattr(self, "model_disconnected", False):
self.model_disconnected = False
logging.error("Recovered model server connection!")
# TODO(vish): this should probably only catch connection errors
except Exception: # pylint: disable-msg=W0702
if not getattr(self, "model_disconnected", False):
self.model_disconnected = True
logging.exception("model server went away")
yield

View File

@@ -25,11 +25,12 @@ and some black magic for inline callbacks.
import datetime import datetime
import sys import sys
import time import time
import unittest
import mox import mox
import stubout import stubout
from twisted.internet import defer from twisted.internet import defer
from twisted.trial import unittest from twisted.trial import unittest as trial_unittest
from nova import context from nova import context
from nova import db from nova import db
@@ -37,9 +38,12 @@ from nova import fakerabbit
from nova import flags from nova import flags
from nova import rpc from nova import rpc
from nova.network import manager as network_manager from nova.network import manager as network_manager
from nova.tests import fake_flags
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_bool('flush_db', True,
'Flush the database before running fake tests')
flags.DEFINE_bool('fake_tests', True, flags.DEFINE_bool('fake_tests', True,
'should we use everything for testing') 'should we use everything for testing')
@@ -55,11 +59,11 @@ def skip_if_fake(func):
return _skipper return _skipper
class TrialTestCase(unittest.TestCase): class TestCase(unittest.TestCase):
"""Test case base class for all unit tests""" """Test case base class for all unit tests"""
def setUp(self): def setUp(self):
"""Run before each test method to initialize test environment""" """Run before each test method to initialize test environment"""
super(TrialTestCase, self).setUp() super(TestCase, self).setUp()
# NOTE(vish): We need a better method for creating fixtures for tests # NOTE(vish): We need a better method for creating fixtures for tests
# now that we have some required db setup for the system # now that we have some required db setup for the system
# to work properly. # to work properly.
@@ -94,7 +98,87 @@ class TrialTestCase(unittest.TestCase):
db.fixed_ip_disassociate_all_by_timeout(ctxt, FLAGS.host, db.fixed_ip_disassociate_all_by_timeout(ctxt, FLAGS.host,
self.start) self.start)
db.network_disassociate_all(ctxt) db.network_disassociate_all(ctxt)
rpc.Consumer.attach_to_twisted = self.originalAttach rpc.Consumer.attach_to_eventlet = self.originalAttach
for x in self.injected:
try:
x.stop()
except AssertionError:
pass
if FLAGS.fake_rabbit:
fakerabbit.reset_all()
db.security_group_destroy_all(ctxt)
super(TestCase, self).tearDown()
finally:
self.reset_flags()
def flags(self, **kw):
"""Override flag variables for a test"""
for k, v in kw.iteritems():
if k in self.flag_overrides:
self.reset_flags()
raise Exception(
'trying to override already overriden flag: %s' % k)
self.flag_overrides[k] = getattr(FLAGS, k)
setattr(FLAGS, k, v)
def reset_flags(self):
"""Resets all flag variables for the test. Runs after each test"""
FLAGS.Reset()
for k, v in self._original_flags.iteritems():
setattr(FLAGS, k, v)
def _monkey_patch_attach(self):
self.originalAttach = rpc.Consumer.attach_to_eventlet
def _wrapped(innerSelf):
rv = self.originalAttach(innerSelf)
self.injected.append(rv)
return rv
_wrapped.func_name = self.originalAttach.func_name
rpc.Consumer.attach_to_eventlet = _wrapped
class TrialTestCase(trial_unittest.TestCase):
"""Test case base class for all unit tests"""
def setUp(self):
"""Run before each test method to initialize test environment"""
super(TrialTestCase, self).setUp()
# NOTE(vish): We need a better method for creating fixtures for tests
# now that we have some required db setup for the system
# to work properly.
self.start = datetime.datetime.utcnow()
ctxt = context.get_admin_context()
if db.network_count(ctxt) != 5:
network_manager.VlanManager().create_networks(ctxt,
FLAGS.fixed_range,
5, 16,
FLAGS.vlan_start,
FLAGS.vpn_start)
# emulate some of the mox stuff, we can't use the metaclass
# because it screws with our generators
self.mox = mox.Mox()
self.stubs = stubout.StubOutForTesting()
self.flag_overrides = {}
self.injected = []
self._original_flags = FLAGS.FlagValuesDict()
def tearDown(self):
"""Runs after each test method to finalize/tear down test
environment."""
try:
self.mox.UnsetStubs()
self.stubs.UnsetAll()
self.stubs.SmartUnsetAll()
self.mox.VerifyAll()
# NOTE(vish): Clean up any ips associated during the test.
ctxt = context.get_admin_context()
db.fixed_ip_disassociate_all_by_timeout(ctxt, FLAGS.host,
self.start)
db.network_disassociate_all(ctxt)
for x in self.injected: for x in self.injected:
try: try:
x.stop() x.stop()
@@ -147,14 +231,3 @@ class TrialTestCase(unittest.TestCase):
return d return d
_wrapped.func_name = func.func_name _wrapped.func_name = func.func_name
return _wrapped return _wrapped
def _monkey_patch_attach(self):
self.originalAttach = rpc.Consumer.attach_to_twisted
def _wrapped(innerSelf):
rv = self.originalAttach(innerSelf)
self.injected.append(rv)
return rv
_wrapped.func_name = self.originalAttach.func_name
rpc.Consumer.attach_to_twisted = _wrapped

View File

@@ -29,3 +29,8 @@
.. moduleauthor:: Manish Singh <yosh@gimp.org> .. moduleauthor:: Manish Singh <yosh@gimp.org>
.. moduleauthor:: Andy Smith <andy@anarkystic.com> .. moduleauthor:: Andy Smith <andy@anarkystic.com>
""" """
# See http://code.google.com/p/python-nose/issues/detail?id=373
# The code below enables nosetests to work with i18n _() blocks
import __builtin__
setattr(__builtin__, '_', lambda x: x)

View File

@@ -1,54 +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.
import boto
from boto.ec2.regioninfo import RegionInfo
import unittest
ACCESS_KEY = 'fake'
SECRET_KEY = 'fake'
CLC_IP = '127.0.0.1'
CLC_PORT = 8773
REGION = 'test'
def get_connection():
return boto.connect_ec2(
aws_access_key_id=ACCESS_KEY,
aws_secret_access_key=SECRET_KEY,
is_secure=False,
region=RegionInfo(None, REGION, CLC_IP),
port=CLC_PORT,
path='/services/Cloud',
debug=99)
class APIIntegrationTests(unittest.TestCase):
def test_001_get_all_images(self):
conn = get_connection()
res = conn.get_all_images()
if __name__ == '__main__':
unittest.main()
#print conn.get_all_key_pairs()
#print conn.create_key_pair
#print conn.create_security_group('name', 'description')

View File

@@ -1,48 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 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 os
import subprocess
from nova import test
from nova.utils import parse_mailmap, str_dict_replace
class ProjectTestCase(test.TrialTestCase):
def test_authors_up_to_date(self):
if os.path.exists('../.bzr'):
log_cmd = subprocess.Popen(["bzr", "log", "-n0"],
stdout=subprocess.PIPE)
changelog = log_cmd.communicate()[0]
mailmap = parse_mailmap('../.mailmap')
contributors = set()
for l in changelog.split('\n'):
l = l.strip()
if (l.startswith('author:') or l.startswith('committer:')
and not l == 'committer: Tarmac'):
email = l.split(' ')[-1]
contributors.add(str_dict_replace(email, mailmap))
authors_file = open('../Authors', 'r').read()
missing = set()
for contributor in contributors:
if not contributor in authors_file:
missing.add(contributor)
self.assertTrue(len(missing) == 0,
'%r not listed in Authors' % missing)

View File

@@ -54,7 +54,7 @@ os.makedirs(os.path.join(OSS_TEMPDIR, 'images'))
os.makedirs(os.path.join(OSS_TEMPDIR, 'buckets')) os.makedirs(os.path.join(OSS_TEMPDIR, 'buckets'))
class ObjectStoreTestCase(test.TrialTestCase): class ObjectStoreTestCase(test.TestCase):
"""Test objectstore API directly.""" """Test objectstore API directly."""
def setUp(self): def setUp(self):
@@ -191,7 +191,7 @@ class TestSite(server.Site):
protocol = TestHTTPChannel protocol = TestHTTPChannel
class S3APITestCase(test.TrialTestCase): class S3APITestCase(test.TestCase):
"""Test objectstore through S3 API.""" """Test objectstore through S3 API."""
def setUp(self): def setUp(self):

View File

@@ -1,132 +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.
import logging
from twisted.internet import defer
from twisted.internet import reactor
from xml.etree import ElementTree
from nova import exception
from nova import flags
from nova import process
from nova import test
from nova import utils
FLAGS = flags.FLAGS
class ProcessTestCase(test.TrialTestCase):
def setUp(self):
logging.getLogger().setLevel(logging.DEBUG)
super(ProcessTestCase, self).setUp()
def test_execute_stdout(self):
pool = process.ProcessPool(2)
d = pool.simple_execute('echo test')
def _check(rv):
self.assertEqual(rv[0], 'test\n')
self.assertEqual(rv[1], '')
d.addCallback(_check)
d.addErrback(self.fail)
return d
def test_execute_stderr(self):
pool = process.ProcessPool(2)
d = pool.simple_execute('cat BAD_FILE', check_exit_code=False)
def _check(rv):
self.assertEqual(rv[0], '')
self.assert_('No such file' in rv[1])
d.addCallback(_check)
d.addErrback(self.fail)
return d
def test_execute_unexpected_stderr(self):
pool = process.ProcessPool(2)
d = pool.simple_execute('cat BAD_FILE')
d.addCallback(lambda x: self.fail('should have raised an error'))
d.addErrback(lambda failure: failure.trap(IOError))
return d
def test_max_processes(self):
pool = process.ProcessPool(2)
d1 = pool.simple_execute('sleep 0.01')
d2 = pool.simple_execute('sleep 0.01')
d3 = pool.simple_execute('sleep 0.005')
d4 = pool.simple_execute('sleep 0.005')
called = []
def _called(rv, name):
called.append(name)
d1.addCallback(_called, 'd1')
d2.addCallback(_called, 'd2')
d3.addCallback(_called, 'd3')
d4.addCallback(_called, 'd4')
# Make sure that d3 and d4 had to wait on the other two and were called
# in order
# NOTE(termie): there may be a race condition in this test if for some
# reason one of the sleeps takes longer to complete
# than it should
d4.addCallback(lambda x: self.assertEqual(called[2], 'd3'))
d4.addCallback(lambda x: self.assertEqual(called[3], 'd4'))
d4.addErrback(self.fail)
return d4
def test_kill_long_process(self):
pool = process.ProcessPool(2)
d1 = pool.simple_execute('sleep 1')
d2 = pool.simple_execute('sleep 0.005')
timeout = reactor.callLater(0.1, self.fail, 'should have been killed')
# kill d1 and wait on it to end then cancel the timeout
d2.addCallback(lambda _: d1.process.signalProcess('KILL'))
d2.addCallback(lambda _: d1)
d2.addBoth(lambda _: timeout.active() and timeout.cancel())
d2.addErrback(self.fail)
return d2
def test_process_exit_is_contained(self):
pool = process.ProcessPool(2)
d1 = pool.simple_execute('sleep 1')
d1.addCallback(lambda x: self.fail('should have errbacked'))
d1.addErrback(lambda fail: fail.trap(IOError))
reactor.callLater(0.05, d1.process.signalProcess, 'KILL')
return d1
def test_shared_pool_is_singleton(self):
pool1 = process.SharedPool()
pool2 = process.SharedPool()
self.assertEqual(id(pool1._instance), id(pool2._instance))
def test_shared_pool_works_as_singleton(self):
d1 = process.simple_execute('sleep 1')
d2 = process.simple_execute('sleep 0.005')
# lp609749: would have failed with
# exceptions.AssertionError: Someone released me too many times:
# too many tokens!
return d1

View File

@@ -1,153 +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.
import logging
from nova import context
from nova import db
from nova import exception
from nova import flags
from nova import quota
from nova import test
from nova import utils
from nova.auth import manager
from nova.api.ec2 import cloud
FLAGS = flags.FLAGS
class QuotaTestCase(test.TrialTestCase):
def setUp(self):
logging.getLogger().setLevel(logging.DEBUG)
super(QuotaTestCase, self).setUp()
self.flags(connection_type='fake',
quota_instances=2,
quota_cores=4,
quota_volumes=2,
quota_gigabytes=20,
quota_floating_ips=1)
self.cloud = cloud.CloudController()
self.manager = manager.AuthManager()
self.user = self.manager.create_user('admin', 'admin', 'admin', True)
self.project = self.manager.create_project('admin', 'admin', 'admin')
self.network = utils.import_object(FLAGS.network_manager)
self.context = context.RequestContext(project=self.project,
user=self.user)
def tearDown(self):
manager.AuthManager().delete_project(self.project)
manager.AuthManager().delete_user(self.user)
super(QuotaTestCase, self).tearDown()
def _create_instance(self, cores=2):
"""Create a test instance"""
inst = {}
inst['image_id'] = 'ami-test'
inst['reservation_id'] = 'r-fakeres'
inst['user_id'] = self.user.id
inst['project_id'] = self.project.id
inst['instance_type'] = 'm1.large'
inst['vcpus'] = cores
inst['mac_address'] = utils.generate_mac()
return db.instance_create(self.context, inst)['id']
def _create_volume(self, size=10):
"""Create a test volume"""
vol = {}
vol['user_id'] = self.user.id
vol['project_id'] = self.project.id
vol['size'] = size
return db.volume_create(self.context, vol)['id']
def test_quota_overrides(self):
"""Make sure overriding a projects quotas works"""
num_instances = quota.allowed_instances(self.context, 100, 'm1.small')
self.assertEqual(num_instances, 2)
db.quota_create(self.context, {'project_id': self.project.id,
'instances': 10})
num_instances = quota.allowed_instances(self.context, 100, 'm1.small')
self.assertEqual(num_instances, 4)
db.quota_update(self.context, self.project.id, {'cores': 100})
num_instances = quota.allowed_instances(self.context, 100, 'm1.small')
self.assertEqual(num_instances, 10)
db.quota_destroy(self.context, self.project.id)
def test_too_many_instances(self):
instance_ids = []
for i in range(FLAGS.quota_instances):
instance_id = self._create_instance()
instance_ids.append(instance_id)
self.assertRaises(quota.QuotaError, self.cloud.run_instances,
self.context,
min_count=1,
max_count=1,
instance_type='m1.small',
image_id='fake')
for instance_id in instance_ids:
db.instance_destroy(self.context, instance_id)
def test_too_many_cores(self):
instance_ids = []
instance_id = self._create_instance(cores=4)
instance_ids.append(instance_id)
self.assertRaises(quota.QuotaError, self.cloud.run_instances,
self.context,
min_count=1,
max_count=1,
instance_type='m1.small',
image_id='fake')
for instance_id in instance_ids:
db.instance_destroy(self.context, instance_id)
def test_too_many_volumes(self):
volume_ids = []
for i in range(FLAGS.quota_volumes):
volume_id = self._create_volume()
volume_ids.append(volume_id)
self.assertRaises(quota.QuotaError, self.cloud.create_volume,
self.context,
size=10)
for volume_id in volume_ids:
db.volume_destroy(self.context, volume_id)
def test_too_many_gigabytes(self):
volume_ids = []
volume_id = self._create_volume(size=20)
volume_ids.append(volume_id)
self.assertRaises(quota.QuotaError,
self.cloud.create_volume,
self.context,
size=10)
for volume_id in volume_ids:
db.volume_destroy(self.context, volume_id)
def test_too_many_addresses(self):
address = '192.168.0.100'
db.floating_ip_create(context.get_admin_context(),
{'address': address, 'host': FLAGS.host})
float_addr = self.network.allocate_floating_ip(self.context,
self.project.id)
# NOTE(vish): This assert never fails. When cloud attempts to
# make an rpc.call, the test just finishes with OK. It
# appears to be something in the magic inline callbacks
# that is breaking.
self.assertRaises(quota.QuotaError, self.cloud.allocate_address,
self.context)
db.floating_ip_destroy(context.get_admin_context(), address)

View File

@@ -1,245 +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.
"""
Unit Tests for remote procedure calls using queue
"""
import mox
from twisted.application.app import startApplication
from twisted.internet import defer
from nova import exception
from nova import flags
from nova import rpc
from nova import test
from nova import service
from nova import manager
FLAGS = flags.FLAGS
flags.DEFINE_string("fake_manager", "nova.tests.service_unittest.FakeManager",
"Manager for testing")
class FakeManager(manager.Manager):
"""Fake manager for tests"""
def test_method(self):
return 'manager'
class ExtendedService(service.Service):
def test_method(self):
return 'service'
class ServiceManagerTestCase(test.TrialTestCase):
"""Test cases for Services"""
def test_attribute_error_for_no_manager(self):
serv = service.Service('test',
'test',
'test',
'nova.tests.service_unittest.FakeManager')
self.assertRaises(AttributeError, getattr, serv, 'test_method')
def test_message_gets_to_manager(self):
serv = service.Service('test',
'test',
'test',
'nova.tests.service_unittest.FakeManager')
serv.startService()
self.assertEqual(serv.test_method(), 'manager')
def test_override_manager_method(self):
serv = ExtendedService('test',
'test',
'test',
'nova.tests.service_unittest.FakeManager')
serv.startService()
self.assertEqual(serv.test_method(), 'service')
class ServiceTestCase(test.TrialTestCase):
"""Test cases for Services"""
def setUp(self):
super(ServiceTestCase, self).setUp()
self.mox.StubOutWithMock(service, 'db')
def test_create(self):
host = 'foo'
binary = 'nova-fake'
topic = 'fake'
# NOTE(vish): Create was moved out of mox replay to make sure that
# the looping calls are created in StartService.
app = service.Service.create(host=host, binary=binary)
self.mox.StubOutWithMock(rpc,
'AdapterConsumer',
use_mock_anything=True)
self.mox.StubOutWithMock(
service.task, 'LoopingCall', use_mock_anything=True)
rpc.AdapterConsumer(connection=mox.IgnoreArg(),
topic=topic,
proxy=mox.IsA(service.Service)).AndReturn(
rpc.AdapterConsumer)
rpc.AdapterConsumer(connection=mox.IgnoreArg(),
topic='%s.%s' % (topic, host),
proxy=mox.IsA(service.Service)).AndReturn(
rpc.AdapterConsumer)
rpc.AdapterConsumer.attach_to_twisted()
rpc.AdapterConsumer.attach_to_twisted()
# Stub out looping call a bit needlessly since we don't have an easy
# way to cancel it (yet) when the tests finishes
service.task.LoopingCall(mox.IgnoreArg()).AndReturn(
service.task.LoopingCall)
service.task.LoopingCall.start(interval=mox.IgnoreArg(),
now=mox.IgnoreArg())
service.task.LoopingCall(mox.IgnoreArg()).AndReturn(
service.task.LoopingCall)
service.task.LoopingCall.start(interval=mox.IgnoreArg(),
now=mox.IgnoreArg())
service_create = {'host': host,
'binary': binary,
'topic': topic,
'report_count': 0}
service_ref = {'host': host,
'binary': binary,
'report_count': 0,
'id': 1}
service.db.service_get_by_args(mox.IgnoreArg(),
host,
binary).AndRaise(exception.NotFound())
service.db.service_create(mox.IgnoreArg(),
service_create).AndReturn(service_ref)
self.mox.ReplayAll()
startApplication(app, False)
self.assert_(app)
# We're testing sort of weird behavior in how report_state decides
# whether it is disconnected, it looks for a variable on itself called
# 'model_disconnected' and report_state doesn't really do much so this
# these are mostly just for coverage
@defer.inlineCallbacks
def test_report_state_no_service(self):
host = 'foo'
binary = 'bar'
topic = 'test'
service_create = {'host': host,
'binary': binary,
'topic': topic,
'report_count': 0}
service_ref = {'host': host,
'binary': binary,
'topic': topic,
'report_count': 0,
'id': 1}
service.db.service_get_by_args(mox.IgnoreArg(),
host,
binary).AndRaise(exception.NotFound())
service.db.service_create(mox.IgnoreArg(),
service_create).AndReturn(service_ref)
service.db.service_get(mox.IgnoreArg(),
service_ref['id']).AndReturn(service_ref)
service.db.service_update(mox.IgnoreArg(), service_ref['id'],
mox.ContainsKeyValue('report_count', 1))
self.mox.ReplayAll()
serv = service.Service(host,
binary,
topic,
'nova.tests.service_unittest.FakeManager')
serv.startService()
yield serv.report_state()
@defer.inlineCallbacks
def test_report_state_newly_disconnected(self):
host = 'foo'
binary = 'bar'
topic = 'test'
service_create = {'host': host,
'binary': binary,
'topic': topic,
'report_count': 0}
service_ref = {'host': host,
'binary': binary,
'topic': topic,
'report_count': 0,
'id': 1}
service.db.service_get_by_args(mox.IgnoreArg(),
host,
binary).AndRaise(exception.NotFound())
service.db.service_create(mox.IgnoreArg(),
service_create).AndReturn(service_ref)
service.db.service_get(mox.IgnoreArg(),
mox.IgnoreArg()).AndRaise(Exception())
self.mox.ReplayAll()
serv = service.Service(host,
binary,
topic,
'nova.tests.service_unittest.FakeManager')
serv.startService()
yield serv.report_state()
self.assert_(serv.model_disconnected)
@defer.inlineCallbacks
def test_report_state_newly_connected(self):
host = 'foo'
binary = 'bar'
topic = 'test'
service_create = {'host': host,
'binary': binary,
'topic': topic,
'report_count': 0}
service_ref = {'host': host,
'binary': binary,
'topic': topic,
'report_count': 0,
'id': 1}
service.db.service_get_by_args(mox.IgnoreArg(),
host,
binary).AndRaise(exception.NotFound())
service.db.service_create(mox.IgnoreArg(),
service_create).AndReturn(service_ref)
service.db.service_get(mox.IgnoreArg(),
service_ref['id']).AndReturn(service_ref)
service.db.service_update(mox.IgnoreArg(), service_ref['id'],
mox.ContainsKeyValue('report_count', 1))
self.mox.ReplayAll()
serv = service.Service(host,
binary,
topic,
'nova.tests.service_unittest.FakeManager')
serv.startService()
serv.model_disconnected = True
yield serv.report_state()
self.assert_(not serv.model_disconnected)

View File

@@ -35,7 +35,7 @@ class Context(object):
pass pass
class AccessTestCase(test.TrialTestCase): class AccessTestCase(test.TestCase):
def setUp(self): def setUp(self):
super(AccessTestCase, self).setUp() super(AccessTestCase, self).setUp()
um = manager.AuthManager() um = manager.AuthManager()

View File

@@ -208,17 +208,13 @@ class AuthManagerTestCase(object):
# so it probably belongs in crypto_unittest # so it probably belongs in crypto_unittest
# but I'm leaving it where I found it. # but I'm leaving it where I found it.
with user_and_project_generator(self.manager) as (user, project): with user_and_project_generator(self.manager) as (user, project):
# NOTE(todd): Should mention why we must setup controller first # NOTE(vish): Setup runs genroot.sh if it hasn't been run
# (somebody please clue me in) cloud.CloudController().setup()
cloud_controller = cloud.CloudController() _key, cert_str = crypto.generate_x509_cert(user.id, project.id)
cloud_controller.setup()
_key, cert_str = self.manager._generate_x509_cert('test1',
'testproj')
logging.debug(cert_str) logging.debug(cert_str)
# Need to verify that it's signed by the right intermediate CA full_chain = crypto.fetch_ca(project_id=project.id, chain=True)
full_chain = crypto.fetch_ca(project_id='testproj', chain=True) int_cert = crypto.fetch_ca(project_id=project.id, chain=False)
int_cert = crypto.fetch_ca(project_id='testproj', chain=False)
cloud_cert = crypto.fetch_ca() cloud_cert = crypto.fetch_ca()
logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain) logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain)
signed_cert = X509.load_cert_string(cert_str) signed_cert = X509.load_cert_string(cert_str)
@@ -227,7 +223,8 @@ class AuthManagerTestCase(object):
cloud_cert = X509.load_cert_string(cloud_cert) cloud_cert = X509.load_cert_string(cloud_cert)
self.assertTrue(signed_cert.verify(chain_cert.get_pubkey())) self.assertTrue(signed_cert.verify(chain_cert.get_pubkey()))
self.assertTrue(signed_cert.verify(int_cert.get_pubkey())) self.assertTrue(signed_cert.verify(int_cert.get_pubkey()))
if not FLAGS.use_intermediate_ca:
if not FLAGS.use_project_ca:
self.assertTrue(signed_cert.verify(cloud_cert.get_pubkey())) self.assertTrue(signed_cert.verify(cloud_cert.get_pubkey()))
else: else:
self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey())) self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey()))
@@ -326,24 +323,20 @@ class AuthManagerTestCase(object):
self.assertTrue(user.is_admin()) self.assertTrue(user.is_admin())
class AuthManagerLdapTestCase(AuthManagerTestCase, test.TrialTestCase): class AuthManagerLdapTestCase(AuthManagerTestCase, test.TestCase):
auth_driver = 'nova.auth.ldapdriver.FakeLdapDriver' auth_driver = 'nova.auth.ldapdriver.FakeLdapDriver'
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
AuthManagerTestCase.__init__(self) AuthManagerTestCase.__init__(self)
test.TrialTestCase.__init__(self, *args, **kwargs) test.TestCase.__init__(self, *args, **kwargs)
import nova.auth.fakeldap as fakeldap import nova.auth.fakeldap as fakeldap
FLAGS.redis_db = 8
if FLAGS.flush_db: if FLAGS.flush_db:
logging.info("Flushing redis datastore") logging.info("Flushing datastore")
try: r = fakeldap.Store.instance()
r = fakeldap.Redis.instance() r.flushdb()
r.flushdb()
except:
self.skip = True
class AuthManagerDbTestCase(AuthManagerTestCase, test.TrialTestCase): class AuthManagerDbTestCase(AuthManagerTestCase, test.TestCase):
auth_driver = 'nova.auth.dbdriver.DbDriver' auth_driver = 'nova.auth.dbdriver.DbDriver'

View File

@@ -22,22 +22,18 @@ import logging
from M2Crypto import BIO from M2Crypto import BIO
from M2Crypto import RSA from M2Crypto import RSA
import os import os
import StringIO
import tempfile import tempfile
import time import time
from eventlet import greenthread from eventlet import greenthread
from twisted.internet import defer
import unittest
from xml.etree import ElementTree
from nova import context from nova import context
from nova import crypto from nova import crypto
from nova import db from nova import db
from nova import flags from nova import flags
from nova import rpc from nova import rpc
from nova import service
from nova import test from nova import test
from nova import utils
from nova.auth import manager from nova.auth import manager
from nova.compute import power_state from nova.compute import power_state
from nova.api.ec2 import cloud from nova.api.ec2 import cloud
@@ -53,10 +49,11 @@ IMAGES_PATH = os.path.join(OSS_TEMPDIR, 'images')
os.makedirs(IMAGES_PATH) os.makedirs(IMAGES_PATH)
class CloudTestCase(test.TrialTestCase): class CloudTestCase(test.TestCase):
def setUp(self): def setUp(self):
super(CloudTestCase, self).setUp() super(CloudTestCase, self).setUp()
self.flags(connection_type='fake', images_path=IMAGES_PATH) self.flags(connection_type='fake',
images_path=IMAGES_PATH)
self.conn = rpc.Connection.instance() self.conn = rpc.Connection.instance()
logging.getLogger().setLevel(logging.DEBUG) logging.getLogger().setLevel(logging.DEBUG)
@@ -64,27 +61,23 @@ class CloudTestCase(test.TrialTestCase):
# set up our cloud # set up our cloud
self.cloud = cloud.CloudController() self.cloud = cloud.CloudController()
# set up a service # set up services
self.compute = utils.import_object(FLAGS.compute_manager) self.compute = service.Service.create(binary='nova-compute')
self.compute_consumer = rpc.AdapterConsumer(connection=self.conn, self.compute.start()
topic=FLAGS.compute_topic, self.network = service.Service.create(binary='nova-network')
proxy=self.compute) self.network.start()
self.compute_consumer.attach_to_eventlet()
self.network = utils.import_object(FLAGS.network_manager)
self.network_consumer = rpc.AdapterConsumer(connection=self.conn,
topic=FLAGS.network_topic,
proxy=self.network)
self.network_consumer.attach_to_eventlet()
self.manager = manager.AuthManager() self.manager = manager.AuthManager()
self.user = self.manager.create_user('admin', 'admin', 'admin', True) self.user = self.manager.create_user('admin', 'admin', 'admin', True)
self.project = self.manager.create_project('proj', 'admin', 'proj') self.project = self.manager.create_project('proj', 'admin', 'proj')
self.context = context.RequestContext(user=self.user, self.context = context.RequestContext(user=self.user,
project=self.project) project=self.project)
def tearDown(self): def tearDown(self):
self.manager.delete_project(self.project) self.manager.delete_project(self.project)
self.manager.delete_user(self.user) self.manager.delete_user(self.user)
self.compute.kill()
self.network.kill()
super(CloudTestCase, self).tearDown() super(CloudTestCase, self).tearDown()
def _create_key(self, name): def _create_key(self, name):
@@ -111,12 +104,13 @@ class CloudTestCase(test.TrialTestCase):
{'address': address, {'address': address,
'host': FLAGS.host}) 'host': FLAGS.host})
self.cloud.allocate_address(self.context) self.cloud.allocate_address(self.context)
inst = db.instance_create(self.context, {}) inst = db.instance_create(self.context, {'host': FLAGS.host})
fixed = self.network.allocate_fixed_ip(self.context, inst['id']) fixed = self.network.allocate_fixed_ip(self.context, inst['id'])
ec2_id = cloud.internal_id_to_ec2_id(inst['internal_id']) ec2_id = cloud.internal_id_to_ec2_id(inst['internal_id'])
self.cloud.associate_address(self.context, self.cloud.associate_address(self.context,
instance_id=ec2_id, instance_id=ec2_id,
public_ip=address) public_ip=address)
greenthread.sleep(0.3)
self.cloud.disassociate_address(self.context, self.cloud.disassociate_address(self.context,
public_ip=address) public_ip=address)
self.cloud.release_address(self.context, self.cloud.release_address(self.context,
@@ -126,6 +120,19 @@ class CloudTestCase(test.TrialTestCase):
db.instance_destroy(self.context, inst['id']) db.instance_destroy(self.context, inst['id'])
db.floating_ip_destroy(self.context, address) db.floating_ip_destroy(self.context, address)
def test_describe_volumes(self):
"""Makes sure describe_volumes works and filters results."""
vol1 = db.volume_create(self.context, {})
vol2 = db.volume_create(self.context, {})
result = self.cloud.describe_volumes(self.context)
self.assertEqual(len(result['volumeSet']), 2)
result = self.cloud.describe_volumes(self.context,
volume_id=[vol2['ec2_id']])
self.assertEqual(len(result['volumeSet']), 1)
self.assertEqual(result['volumeSet'][0]['volumeId'], vol2['ec2_id'])
db.volume_destroy(self.context, vol1['id'])
db.volume_destroy(self.context, vol2['id'])
def test_console_output(self): def test_console_output(self):
image_id = FLAGS.default_image image_id = FLAGS.default_image
instance_type = FLAGS.default_instance_type instance_type = FLAGS.default_instance_type
@@ -186,7 +193,7 @@ class CloudTestCase(test.TrialTestCase):
logging.debug("Need to watch instance %s until it's running..." % logging.debug("Need to watch instance %s until it's running..." %
instance['instance_id']) instance['instance_id'])
while True: while True:
rv = yield defer.succeed(time.sleep(1)) greenthread.sleep(1)
info = self.cloud._get_instance(instance['instance_id']) info = self.cloud._get_instance(instance['instance_id'])
logging.debug(info['state']) logging.debug(info['state'])
if info['state'] == power_state.RUNNING: if info['state'] == power_state.RUNNING:

View File

@@ -22,8 +22,6 @@ Tests For Compute
import datetime import datetime
import logging import logging
from twisted.internet import defer
from nova import context from nova import context
from nova import db from nova import db
from nova import exception from nova import exception
@@ -33,15 +31,17 @@ from nova import utils
from nova.auth import manager from nova.auth import manager
from nova.compute import api as compute_api from nova.compute import api as compute_api
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
class ComputeTestCase(test.TrialTestCase): class ComputeTestCase(test.TestCase):
"""Test case for compute""" """Test case for compute"""
def setUp(self): def setUp(self):
logging.getLogger().setLevel(logging.DEBUG) logging.getLogger().setLevel(logging.DEBUG)
super(ComputeTestCase, self).setUp() super(ComputeTestCase, self).setUp()
self.flags(connection_type='fake', self.flags(connection_type='fake',
stub_network=True,
network_manager='nova.network.manager.FlatManager') network_manager='nova.network.manager.FlatManager')
self.compute = utils.import_object(FLAGS.compute_manager) self.compute = utils.import_object(FLAGS.compute_manager)
self.compute_api = compute_api.ComputeAPI() self.compute_api = compute_api.ComputeAPI()
@@ -72,52 +72,44 @@ class ComputeTestCase(test.TrialTestCase):
"""Verify that an instance cannot be created without a display_name.""" """Verify that an instance cannot be created without a display_name."""
cases = [dict(), dict(display_name=None)] cases = [dict(), dict(display_name=None)]
for instance in cases: for instance in cases:
ref = self.compute_api.create_instance(self.context, None, ref = self.compute_api.create_instances(self.context,
**instance) FLAGS.default_instance_type, None, **instance)
try: try:
self.assertNotEqual(ref.display_name, None) self.assertNotEqual(ref[0].display_name, None)
finally: finally:
db.instance_destroy(self.context, ref['id']) db.instance_destroy(self.context, ref[0]['id'])
def test_create_instance_associates_security_groups(self): def test_create_instance_associates_security_groups(self):
"""Make sure create_instance associates security groups""" """Make sure create_instances associates security groups"""
inst = {}
inst['user_id'] = self.user.id
inst['project_id'] = self.project.id
values = {'name': 'default', values = {'name': 'default',
'description': 'default', 'description': 'default',
'user_id': self.user.id, 'user_id': self.user.id,
'project_id': self.project.id} 'project_id': self.project.id}
group = db.security_group_create(self.context, values) group = db.security_group_create(self.context, values)
ref = self.compute_api.create_instance(self.context, ref = self.compute_api.create_instances(self.context,
security_groups=[group['id']], FLAGS.default_instance_type, None, security_group=['default'])
**inst)
# reload to get groups
instance_ref = db.instance_get(self.context, ref['id'])
try: try:
self.assertEqual(len(instance_ref['security_groups']), 1) self.assertEqual(len(ref[0]['security_groups']), 1)
finally: finally:
db.security_group_destroy(self.context, group['id']) db.security_group_destroy(self.context, group['id'])
db.instance_destroy(self.context, instance_ref['id']) db.instance_destroy(self.context, ref[0]['id'])
@defer.inlineCallbacks
def test_run_terminate(self): def test_run_terminate(self):
"""Make sure it is possible to run and terminate instance""" """Make sure it is possible to run and terminate instance"""
instance_id = self._create_instance() instance_id = self._create_instance()
yield self.compute.run_instance(self.context, instance_id) self.compute.run_instance(self.context, instance_id)
instances = db.instance_get_all(context.get_admin_context()) instances = db.instance_get_all(context.get_admin_context())
logging.info("Running instances: %s", instances) logging.info("Running instances: %s", instances)
self.assertEqual(len(instances), 1) self.assertEqual(len(instances), 1)
yield self.compute.terminate_instance(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id)
instances = db.instance_get_all(context.get_admin_context()) instances = db.instance_get_all(context.get_admin_context())
logging.info("After terminating instances: %s", instances) logging.info("After terminating instances: %s", instances)
self.assertEqual(len(instances), 0) self.assertEqual(len(instances), 0)
@defer.inlineCallbacks
def test_run_terminate_timestamps(self): def test_run_terminate_timestamps(self):
"""Make sure timestamps are set for launched and destroyed""" """Make sure timestamps are set for launched and destroyed"""
instance_id = self._create_instance() instance_id = self._create_instance()
@@ -125,42 +117,48 @@ class ComputeTestCase(test.TrialTestCase):
self.assertEqual(instance_ref['launched_at'], None) self.assertEqual(instance_ref['launched_at'], None)
self.assertEqual(instance_ref['deleted_at'], None) self.assertEqual(instance_ref['deleted_at'], None)
launch = datetime.datetime.utcnow() launch = datetime.datetime.utcnow()
yield self.compute.run_instance(self.context, instance_id) self.compute.run_instance(self.context, instance_id)
instance_ref = db.instance_get(self.context, instance_id) instance_ref = db.instance_get(self.context, instance_id)
self.assert_(instance_ref['launched_at'] > launch) self.assert_(instance_ref['launched_at'] > launch)
self.assertEqual(instance_ref['deleted_at'], None) self.assertEqual(instance_ref['deleted_at'], None)
terminate = datetime.datetime.utcnow() terminate = datetime.datetime.utcnow()
yield self.compute.terminate_instance(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id)
self.context = self.context.elevated(True) self.context = self.context.elevated(True)
instance_ref = db.instance_get(self.context, instance_id) instance_ref = db.instance_get(self.context, instance_id)
self.assert_(instance_ref['launched_at'] < terminate) self.assert_(instance_ref['launched_at'] < terminate)
self.assert_(instance_ref['deleted_at'] > terminate) self.assert_(instance_ref['deleted_at'] > terminate)
@defer.inlineCallbacks def test_pause(self):
"""Ensure instance can be paused"""
instance_id = self._create_instance()
self.compute.run_instance(self.context, instance_id)
self.compute.pause_instance(self.context, instance_id)
self.compute.unpause_instance(self.context, instance_id)
self.compute.terminate_instance(self.context, instance_id)
def test_reboot(self): def test_reboot(self):
"""Ensure instance can be rebooted""" """Ensure instance can be rebooted"""
instance_id = self._create_instance() instance_id = self._create_instance()
yield self.compute.run_instance(self.context, instance_id) self.compute.run_instance(self.context, instance_id)
yield self.compute.reboot_instance(self.context, instance_id) self.compute.reboot_instance(self.context, instance_id)
yield self.compute.terminate_instance(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id)
@defer.inlineCallbacks
def test_console_output(self): def test_console_output(self):
"""Make sure we can get console output from instance""" """Make sure we can get console output from instance"""
instance_id = self._create_instance() instance_id = self._create_instance()
yield self.compute.run_instance(self.context, instance_id) self.compute.run_instance(self.context, instance_id)
console = yield self.compute.get_console_output(self.context, console = self.compute.get_console_output(self.context,
instance_id) instance_id)
self.assert_(console) self.assert_(console)
yield self.compute.terminate_instance(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id)
@defer.inlineCallbacks
def test_run_instance_existing(self): def test_run_instance_existing(self):
"""Ensure failure when running an instance that already exists""" """Ensure failure when running an instance that already exists"""
instance_id = self._create_instance() instance_id = self._create_instance()
yield self.compute.run_instance(self.context, instance_id) self.compute.run_instance(self.context, instance_id)
self.assertFailure(self.compute.run_instance(self.context, self.assertRaises(exception.Error,
instance_id), self.compute.run_instance,
exception.Error) self.context,
yield self.compute.terminate_instance(self.context, instance_id) instance_id)
self.compute.terminate_instance(self.context, instance_id)

View File

@@ -24,7 +24,7 @@ FLAGS = flags.FLAGS
flags.DEFINE_string('flags_unittest', 'foo', 'for testing purposes only') flags.DEFINE_string('flags_unittest', 'foo', 'for testing purposes only')
class FlagsTestCase(test.TrialTestCase): class FlagsTestCase(test.TestCase):
def setUp(self): def setUp(self):
super(FlagsTestCase, self).setUp() super(FlagsTestCase, self).setUp()

View File

@@ -0,0 +1,86 @@
# 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.
import datetime
import webob
import webob.dec
import webob.exc
from nova.api import ec2
from nova import flags
from nova import test
from nova import utils
FLAGS = flags.FLAGS
@webob.dec.wsgify
def conditional_forbid(req):
"""Helper wsgi app returns 403 if param 'die' is 1."""
if 'die' in req.params and req.params['die'] == '1':
raise webob.exc.HTTPForbidden()
return 'OK'
class LockoutTestCase(test.TrialTestCase):
"""Test case for the Lockout middleware."""
def setUp(self): # pylint: disable-msg=C0103
super(LockoutTestCase, self).setUp()
utils.set_time_override()
self.lockout = ec2.Lockout(conditional_forbid)
def tearDown(self): # pylint: disable-msg=C0103
utils.clear_time_override()
super(LockoutTestCase, self).tearDown()
def _send_bad_attempts(self, access_key, num_attempts=1):
"""Fail x."""
for i in xrange(num_attempts):
req = webob.Request.blank('/?AWSAccessKeyId=%s&die=1' % access_key)
self.assertEqual(req.get_response(self.lockout).status_int, 403)
def _is_locked_out(self, access_key):
"""Sends a test request to see if key is locked out."""
req = webob.Request.blank('/?AWSAccessKeyId=%s' % access_key)
return (req.get_response(self.lockout).status_int == 403)
def test_lockout(self):
self._send_bad_attempts('test', FLAGS.lockout_attempts)
self.assertTrue(self._is_locked_out('test'))
def test_timeout(self):
self._send_bad_attempts('test', FLAGS.lockout_attempts)
self.assertTrue(self._is_locked_out('test'))
utils.advance_time_seconds(FLAGS.lockout_minutes * 60)
self.assertFalse(self._is_locked_out('test'))
def test_multiple_keys(self):
self._send_bad_attempts('test1', FLAGS.lockout_attempts)
self.assertTrue(self._is_locked_out('test1'))
self.assertFalse(self._is_locked_out('test2'))
utils.advance_time_seconds(FLAGS.lockout_minutes * 60)
self.assertFalse(self._is_locked_out('test1'))
self.assertFalse(self._is_locked_out('test2'))
def test_window_timeout(self):
self._send_bad_attempts('test', FLAGS.lockout_attempts - 1)
self.assertFalse(self._is_locked_out('test'))
utils.advance_time_seconds(FLAGS.lockout_window * 60)
self._send_bad_attempts('test', FLAGS.lockout_attempts - 1)
self.assertFalse(self._is_locked_out('test'))

55
nova/tests/test_misc.py Normal file
View File

@@ -0,0 +1,55 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 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 os
from nova import test
from nova.utils import parse_mailmap, str_dict_replace
class ProjectTestCase(test.TestCase):
def test_authors_up_to_date(self):
if os.path.exists('.bzr'):
contributors = set()
mailmap = parse_mailmap('.mailmap')
import bzrlib.workingtree
tree = bzrlib.workingtree.WorkingTree.open('.')
tree.lock_read()
try:
parents = tree.get_parent_ids()
g = tree.branch.repository.get_graph()
for p in parents[1:]:
rev_ids = [r for r, _ in g.iter_ancestry(parents)
if r != "null:"]
revs = tree.branch.repository.get_revisions(rev_ids)
for r in revs:
for author in r.get_apparent_authors():
email = author.split(' ')[-1]
contributors.add(str_dict_replace(email, mailmap))
authors_file = open('Authors', 'r').read()
missing = set()
for contributor in contributors:
if not contributor in authors_file:
missing.add(contributor)
self.assertTrue(len(missing) == 0,
'%r not listed in Authors' % missing)
finally:
tree.unlock()

View File

@@ -26,6 +26,7 @@ from nova import context
from nova import db from nova import db
from nova import exception from nova import exception
from nova import flags from nova import flags
from nova import service
from nova import test from nova import test
from nova import utils from nova import utils
from nova.auth import manager from nova.auth import manager
@@ -33,13 +34,14 @@ from nova.auth import manager
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
class NetworkTestCase(test.TrialTestCase): class NetworkTestCase(test.TestCase):
"""Test cases for network code""" """Test cases for network code"""
def setUp(self): def setUp(self):
super(NetworkTestCase, self).setUp() super(NetworkTestCase, self).setUp()
# NOTE(vish): if you change these flags, make sure to change the # NOTE(vish): if you change these flags, make sure to change the
# flags in the corresponding section in nova-dhcpbridge # flags in the corresponding section in nova-dhcpbridge
self.flags(connection_type='fake', self.flags(connection_type='fake',
fake_call=True,
fake_network=True, fake_network=True,
network_size=16, network_size=16,
num_networks=5) num_networks=5)
@@ -56,16 +58,13 @@ class NetworkTestCase(test.TrialTestCase):
# create the necessary network data for the project # create the necessary network data for the project
user_context = context.RequestContext(project=self.projects[i], user_context = context.RequestContext(project=self.projects[i],
user=self.user) user=self.user)
network_ref = self.network.get_network(user_context) host = self.network.get_network_host(user_context.elevated())
self.network.set_network_host(context.get_admin_context(),
network_ref['id'])
instance_ref = self._create_instance(0) instance_ref = self._create_instance(0)
self.instance_id = instance_ref['id'] self.instance_id = instance_ref['id']
instance_ref = self._create_instance(1) instance_ref = self._create_instance(1)
self.instance2_id = instance_ref['id'] self.instance2_id = instance_ref['id']
def tearDown(self): def tearDown(self):
super(NetworkTestCase, self).tearDown()
# TODO(termie): this should really be instantiating clean datastores # TODO(termie): this should really be instantiating clean datastores
# in between runs, one failure kills all the tests # in between runs, one failure kills all the tests
db.instance_destroy(context.get_admin_context(), self.instance_id) db.instance_destroy(context.get_admin_context(), self.instance_id)
@@ -73,6 +72,7 @@ class NetworkTestCase(test.TrialTestCase):
for project in self.projects: for project in self.projects:
self.manager.delete_project(project) self.manager.delete_project(project)
self.manager.delete_user(self.user) self.manager.delete_user(self.user)
super(NetworkTestCase, self).tearDown()
def _create_instance(self, project_num, mac=None): def _create_instance(self, project_num, mac=None):
if not mac: if not mac:

View File

@@ -20,8 +20,6 @@ Unit Tests for remote procedure calls using queue
""" """
import logging import logging
from twisted.internet import defer
from nova import context from nova import context
from nova import flags from nova import flags
from nova import rpc from nova import rpc
@@ -31,32 +29,31 @@ from nova import test
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
class RpcTestCase(test.TrialTestCase): class RpcTestCase(test.TestCase):
"""Test cases for rpc""" """Test cases for rpc"""
def setUp(self): def setUp(self):
super(RpcTestCase, self).setUp() super(RpcTestCase, self).setUp()
self.conn = rpc.Connection.instance() self.conn = rpc.Connection.instance(True)
self.receiver = TestReceiver() self.receiver = TestReceiver()
self.consumer = rpc.AdapterConsumer(connection=self.conn, self.consumer = rpc.AdapterConsumer(connection=self.conn,
topic='test', topic='test',
proxy=self.receiver) proxy=self.receiver)
self.consumer.attach_to_twisted() self.consumer.attach_to_eventlet()
self.context = context.get_admin_context() self.context = context.get_admin_context()
def test_call_succeed(self): def test_call_succeed(self):
"""Get a value through rpc call""" """Get a value through rpc call"""
value = 42 value = 42
result = yield rpc.call_twisted(self.context, result = rpc.call(self.context, 'test', {"method": "echo",
'test', {"method": "echo",
"args": {"value": value}}) "args": {"value": value}})
self.assertEqual(value, result) self.assertEqual(value, result)
def test_context_passed(self): def test_context_passed(self):
"""Makes sure a context is passed through rpc call""" """Makes sure a context is passed through rpc call"""
value = 42 value = 42
result = yield rpc.call_twisted(self.context, result = rpc.call(self.context,
'test', {"method": "context", 'test', {"method": "context",
"args": {"value": value}}) "args": {"value": value}})
self.assertEqual(self.context.to_dict(), result) self.assertEqual(self.context.to_dict(), result)
def test_call_exception(self): def test_call_exception(self):
@@ -67,18 +64,48 @@ class RpcTestCase(test.TrialTestCase):
to an int in the test. to an int in the test.
""" """
value = 42 value = 42
self.assertFailure(rpc.call_twisted(self.context, 'test', self.assertRaises(rpc.RemoteError,
{"method": "fail", rpc.call,
"args": {"value": value}}), self.context,
rpc.RemoteError) 'test',
{"method": "fail",
"args": {"value": value}})
try: try:
yield rpc.call_twisted(self.context, rpc.call(self.context,
'test', {"method": "fail", 'test',
"args": {"value": value}}) {"method": "fail",
"args": {"value": value}})
self.fail("should have thrown rpc.RemoteError") self.fail("should have thrown rpc.RemoteError")
except rpc.RemoteError as exc: except rpc.RemoteError as exc:
self.assertEqual(int(exc.value), value) self.assertEqual(int(exc.value), value)
def test_nested_calls(self):
"""Test that we can do an rpc.call inside another call"""
class Nested(object):
@staticmethod
def echo(context, queue, value):
"""Calls echo in the passed queue"""
logging.debug("Nested received %s, %s", queue, value)
ret = rpc.call(context,
queue,
{"method": "echo",
"args": {"value": value}})
logging.debug("Nested return %s", ret)
return value
nested = Nested()
conn = rpc.Connection.instance(True)
consumer = rpc.AdapterConsumer(connection=conn,
topic='nested',
proxy=nested)
consumer.attach_to_eventlet()
value = 42
result = rpc.call(self.context,
'nested', {"method": "echo",
"args": {"queue": "test",
"value": value}})
self.assertEqual(value, result)
class TestReceiver(object): class TestReceiver(object):
"""Simple Proxy class so the consumer has methods to call """Simple Proxy class so the consumer has methods to call
@@ -89,13 +116,13 @@ class TestReceiver(object):
def echo(context, value): def echo(context, value):
"""Simply returns whatever value is sent in""" """Simply returns whatever value is sent in"""
logging.debug("Received %s", value) logging.debug("Received %s", value)
return defer.succeed(value) return value
@staticmethod @staticmethod
def context(context, value): def context(context, value):
"""Returns dictionary version of context""" """Returns dictionary version of context"""
logging.debug("Received %s", context) logging.debug("Received %s", context)
return defer.succeed(context.to_dict()) return context.to_dict()
@staticmethod @staticmethod
def fail(context, value): def fail(context, value):

View File

@@ -44,11 +44,11 @@ class TestDriver(driver.Scheduler):
return 'named_host' return 'named_host'
class SchedulerTestCase(test.TrialTestCase): class SchedulerTestCase(test.TestCase):
"""Test case for scheduler""" """Test case for scheduler"""
def setUp(self): def setUp(self):
super(SchedulerTestCase, self).setUp() super(SchedulerTestCase, self).setUp()
self.flags(scheduler_driver='nova.tests.scheduler_unittest.TestDriver') self.flags(scheduler_driver='nova.tests.test_scheduler.TestDriver')
def test_fallback(self): def test_fallback(self):
scheduler = manager.SchedulerManager() scheduler = manager.SchedulerManager()
@@ -73,11 +73,12 @@ class SchedulerTestCase(test.TrialTestCase):
scheduler.named_method(ctxt, 'topic', num=7) scheduler.named_method(ctxt, 'topic', num=7)
class SimpleDriverTestCase(test.TrialTestCase): class SimpleDriverTestCase(test.TestCase):
"""Test case for simple driver""" """Test case for simple driver"""
def setUp(self): def setUp(self):
super(SimpleDriverTestCase, self).setUp() super(SimpleDriverTestCase, self).setUp()
self.flags(connection_type='fake', self.flags(connection_type='fake',
stub_network=True,
max_cores=4, max_cores=4,
max_gigabytes=4, max_gigabytes=4,
network_manager='nova.network.manager.FlatManager', network_manager='nova.network.manager.FlatManager',
@@ -122,12 +123,12 @@ class SimpleDriverTestCase(test.TrialTestCase):
'nova-compute', 'nova-compute',
'compute', 'compute',
FLAGS.compute_manager) FLAGS.compute_manager)
compute1.startService() compute1.start()
compute2 = service.Service('host2', compute2 = service.Service('host2',
'nova-compute', 'nova-compute',
'compute', 'compute',
FLAGS.compute_manager) FLAGS.compute_manager)
compute2.startService() compute2.start()
hosts = self.scheduler.driver.hosts_up(self.context, 'compute') hosts = self.scheduler.driver.hosts_up(self.context, 'compute')
self.assertEqual(len(hosts), 2) self.assertEqual(len(hosts), 2)
compute1.kill() compute1.kill()
@@ -139,12 +140,12 @@ class SimpleDriverTestCase(test.TrialTestCase):
'nova-compute', 'nova-compute',
'compute', 'compute',
FLAGS.compute_manager) FLAGS.compute_manager)
compute1.startService() compute1.start()
compute2 = service.Service('host2', compute2 = service.Service('host2',
'nova-compute', 'nova-compute',
'compute', 'compute',
FLAGS.compute_manager) FLAGS.compute_manager)
compute2.startService() compute2.start()
instance_id1 = self._create_instance() instance_id1 = self._create_instance()
compute1.run_instance(self.context, instance_id1) compute1.run_instance(self.context, instance_id1)
instance_id2 = self._create_instance() instance_id2 = self._create_instance()
@@ -162,12 +163,12 @@ class SimpleDriverTestCase(test.TrialTestCase):
'nova-compute', 'nova-compute',
'compute', 'compute',
FLAGS.compute_manager) FLAGS.compute_manager)
compute1.startService() compute1.start()
compute2 = service.Service('host2', compute2 = service.Service('host2',
'nova-compute', 'nova-compute',
'compute', 'compute',
FLAGS.compute_manager) FLAGS.compute_manager)
compute2.startService() compute2.start()
instance_ids1 = [] instance_ids1 = []
instance_ids2 = [] instance_ids2 = []
for index in xrange(FLAGS.max_cores): for index in xrange(FLAGS.max_cores):
@@ -195,12 +196,12 @@ class SimpleDriverTestCase(test.TrialTestCase):
'nova-volume', 'nova-volume',
'volume', 'volume',
FLAGS.volume_manager) FLAGS.volume_manager)
volume1.startService() volume1.start()
volume2 = service.Service('host2', volume2 = service.Service('host2',
'nova-volume', 'nova-volume',
'volume', 'volume',
FLAGS.volume_manager) FLAGS.volume_manager)
volume2.startService() volume2.start()
volume_id1 = self._create_volume() volume_id1 = self._create_volume()
volume1.create_volume(self.context, volume_id1) volume1.create_volume(self.context, volume_id1)
volume_id2 = self._create_volume() volume_id2 = self._create_volume()
@@ -218,12 +219,12 @@ class SimpleDriverTestCase(test.TrialTestCase):
'nova-volume', 'nova-volume',
'volume', 'volume',
FLAGS.volume_manager) FLAGS.volume_manager)
volume1.startService() volume1.start()
volume2 = service.Service('host2', volume2 = service.Service('host2',
'nova-volume', 'nova-volume',
'volume', 'volume',
FLAGS.volume_manager) FLAGS.volume_manager)
volume2.startService() volume2.start()
volume_ids1 = [] volume_ids1 = []
volume_ids2 = [] volume_ids2 = []
for index in xrange(FLAGS.max_gigabytes): for index in xrange(FLAGS.max_gigabytes):

View File

@@ -30,9 +30,10 @@ FLAGS = flags.FLAGS
flags.DECLARE('instances_path', 'nova.compute.manager') flags.DECLARE('instances_path', 'nova.compute.manager')
class LibvirtConnTestCase(test.TrialTestCase): class LibvirtConnTestCase(test.TestCase):
def setUp(self): def setUp(self):
super(LibvirtConnTestCase, self).setUp() super(LibvirtConnTestCase, self).setUp()
self.flags(fake_call=True)
self.manager = manager.AuthManager() self.manager = manager.AuthManager()
self.user = self.manager.create_user('fake', 'fake', 'fake', self.user = self.manager.create_user('fake', 'fake', 'fake',
admin=True) admin=True)
@@ -40,33 +41,64 @@ class LibvirtConnTestCase(test.TrialTestCase):
self.network = utils.import_object(FLAGS.network_manager) self.network = utils.import_object(FLAGS.network_manager)
FLAGS.instances_path = '' FLAGS.instances_path = ''
def test_get_uri_and_template(self): test_ip = '10.11.12.13'
ip = '10.11.12.13' test_instance = {'memory_kb': '1024000',
'basepath': '/some/path',
'bridge_name': 'br100',
'mac_address': '02:12:34:46:56:67',
'vcpus': 2,
'project_id': 'fake',
'bridge': 'br101',
'instance_type': 'm1.small'}
instance = {'internal_id': 1, def test_xml_and_uri_no_ramdisk_no_kernel(self):
'memory_kb': '1024000', instance_data = dict(self.test_instance)
'basepath': '/some/path', self._check_xml_and_uri(instance_data,
'bridge_name': 'br100', expect_kernel=False, expect_ramdisk=False)
'mac_address': '02:12:34:46:56:67',
'vcpus': 2,
'project_id': 'fake',
'bridge': 'br101',
'instance_type': 'm1.small'}
def test_xml_and_uri_no_ramdisk(self):
instance_data = dict(self.test_instance)
instance_data['kernel_id'] = 'aki-deadbeef'
self._check_xml_and_uri(instance_data,
expect_kernel=True, expect_ramdisk=False)
def test_xml_and_uri_no_kernel(self):
instance_data = dict(self.test_instance)
instance_data['ramdisk_id'] = 'ari-deadbeef'
self._check_xml_and_uri(instance_data,
expect_kernel=False, expect_ramdisk=False)
def test_xml_and_uri(self):
instance_data = dict(self.test_instance)
instance_data['ramdisk_id'] = 'ari-deadbeef'
instance_data['kernel_id'] = 'aki-deadbeef'
self._check_xml_and_uri(instance_data,
expect_kernel=True, expect_ramdisk=True)
def test_xml_and_uri_rescue(self):
instance_data = dict(self.test_instance)
instance_data['ramdisk_id'] = 'ari-deadbeef'
instance_data['kernel_id'] = 'aki-deadbeef'
self._check_xml_and_uri(instance_data, expect_kernel=True,
expect_ramdisk=True, rescue=True)
def _check_xml_and_uri(self, instance, expect_ramdisk, expect_kernel,
rescue=False):
user_context = context.RequestContext(project=self.project, user_context = context.RequestContext(project=self.project,
user=self.user) user=self.user)
instance_ref = db.instance_create(user_context, instance) instance_ref = db.instance_create(user_context, instance)
network_ref = self.network.get_network(user_context) host = self.network.get_network_host(user_context.elevated())
self.network.set_network_host(context.get_admin_context(), network_ref = db.project_get_network(context.get_admin_context(),
network_ref['id']) self.project.id)
fixed_ip = {'address': ip, fixed_ip = {'address': self.test_ip,
'network_id': network_ref['id']} 'network_id': network_ref['id']}
ctxt = context.get_admin_context() ctxt = context.get_admin_context()
fixed_ip_ref = db.fixed_ip_create(ctxt, fixed_ip) fixed_ip_ref = db.fixed_ip_create(ctxt, fixed_ip)
db.fixed_ip_update(ctxt, ip, {'allocated': True, db.fixed_ip_update(ctxt, self.test_ip,
'instance_id': instance_ref['id']}) {'allocated': True,
'instance_id': instance_ref['id']})
type_uri_map = {'qemu': ('qemu:///system', type_uri_map = {'qemu': ('qemu:///system',
[(lambda t: t.find('.').get('type'), 'qemu'), [(lambda t: t.find('.').get('type'), 'qemu'),
@@ -78,23 +110,73 @@ class LibvirtConnTestCase(test.TrialTestCase):
(lambda t: t.find('./devices/emulator'), None)]), (lambda t: t.find('./devices/emulator'), None)]),
'uml': ('uml:///system', 'uml': ('uml:///system',
[(lambda t: t.find('.').get('type'), 'uml'), [(lambda t: t.find('.').get('type'), 'uml'),
(lambda t: t.find('./os/type').text, 'uml')])} (lambda t: t.find('./os/type').text, 'uml')]),
'xen': ('xen:///',
[(lambda t: t.find('.').get('type'), 'xen'),
(lambda t: t.find('./os/type').text, 'linux')]),
}
for hypervisor_type in ['qemu', 'kvm', 'xen']:
check_list = type_uri_map[hypervisor_type][1]
if rescue:
check = (lambda t: t.find('./os/kernel').text.split('/')[1],
'rescue-kernel')
check_list.append(check)
check = (lambda t: t.find('./os/initrd').text.split('/')[1],
'rescue-ramdisk')
check_list.append(check)
else:
if expect_kernel:
check = (lambda t: t.find('./os/kernel').text.split(
'/')[1], 'kernel')
else:
check = (lambda t: t.find('./os/kernel'), None)
check_list.append(check)
if expect_ramdisk:
check = (lambda t: t.find('./os/initrd').text.split(
'/')[1], 'ramdisk')
else:
check = (lambda t: t.find('./os/initrd'), None)
check_list.append(check)
common_checks = [ common_checks = [
(lambda t: t.find('.').tag, 'domain'), (lambda t: t.find('.').tag, 'domain'),
(lambda t: t.find('./devices/interface/filterref/parameter').\ (lambda t: t.find(
get('name'), 'IP'), './devices/interface/filterref/parameter').get('name'), 'IP'),
(lambda t: t.find('./devices/interface/filterref/parameter').\ (lambda t: t.find(
get('value'), '10.11.12.13')] './devices/interface/filterref/parameter').get(
'value'), '10.11.12.13'),
(lambda t: t.findall(
'./devices/interface/filterref/parameter')[1].get(
'name'), 'DHCPSERVER'),
(lambda t: t.findall(
'./devices/interface/filterref/parameter')[1].get(
'value'), '10.0.0.1'),
(lambda t: t.find('./devices/serial/source').get(
'path').split('/')[1], 'console.log'),
(lambda t: t.find('./memory').text, '2097152')]
if rescue:
common_checks += [
(lambda t: t.findall('./devices/disk/source')[0].get(
'file').split('/')[1], 'rescue-disk'),
(lambda t: t.findall('./devices/disk/source')[1].get(
'file').split('/')[1], 'disk')]
else:
common_checks += [(lambda t: t.findall(
'./devices/disk/source')[0].get('file').split('/')[1],
'disk')]
for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems(): for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems():
FLAGS.libvirt_type = libvirt_type FLAGS.libvirt_type = libvirt_type
conn = libvirt_conn.LibvirtConnection(True) conn = libvirt_conn.LibvirtConnection(True)
uri, _template, _rescue = conn.get_uri_and_templates() uri = conn.get_uri()
self.assertEquals(uri, expected_uri) self.assertEquals(uri, expected_uri)
xml = conn.to_xml(instance_ref) xml = conn.to_xml(instance_ref, rescue)
tree = xml_to_tree(xml) tree = xml_to_tree(xml)
for i, (check, expected_result) in enumerate(checks): for i, (check, expected_result) in enumerate(checks):
self.assertEqual(check(tree), self.assertEqual(check(tree),
@@ -106,6 +188,9 @@ class LibvirtConnTestCase(test.TrialTestCase):
expected_result, expected_result,
'%s failed common check %d' % (xml, i)) '%s failed common check %d' % (xml, i))
# This test is supposed to make sure we don't override a specifically
# set uri
#
# Deliberately not just assigning this string to FLAGS.libvirt_uri and # Deliberately not just assigning this string to FLAGS.libvirt_uri and
# checking against that later on. This way we make sure the # checking against that later on. This way we make sure the
# implementation doesn't fiddle around with the FLAGS. # implementation doesn't fiddle around with the FLAGS.
@@ -114,7 +199,7 @@ class LibvirtConnTestCase(test.TrialTestCase):
for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems(): for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems():
FLAGS.libvirt_type = libvirt_type FLAGS.libvirt_type = libvirt_type
conn = libvirt_conn.LibvirtConnection(True) conn = libvirt_conn.LibvirtConnection(True)
uri, _template, _rescue = conn.get_uri_and_templates() uri = conn.get_uri()
self.assertEquals(uri, testuri) self.assertEquals(uri, testuri)
def tearDown(self): def tearDown(self):
@@ -123,7 +208,7 @@ class LibvirtConnTestCase(test.TrialTestCase):
self.manager.delete_user(self.user) self.manager.delete_user(self.user)
class NWFilterTestCase(test.TrialTestCase): class NWFilterTestCase(test.TestCase):
def setUp(self): def setUp(self):
super(NWFilterTestCase, self).setUp() super(NWFilterTestCase, self).setUp()
@@ -235,7 +320,7 @@ class NWFilterTestCase(test.TrialTestCase):
'project_id': 'fake'}) 'project_id': 'fake'})
inst_id = instance_ref['id'] inst_id = instance_ref['id']
def _ensure_all_called(_): def _ensure_all_called():
instance_filter = 'nova-instance-%s' % instance_ref['name'] instance_filter = 'nova-instance-%s' % instance_ref['name']
secgroup_filter = 'nova-secgroup-%s' % self.security_group['id'] secgroup_filter = 'nova-secgroup-%s' % self.security_group['id']
for required in [secgroup_filter, 'allow-dhcp-server', for required in [secgroup_filter, 'allow-dhcp-server',
@@ -252,8 +337,7 @@ class NWFilterTestCase(test.TrialTestCase):
self.security_group.id) self.security_group.id)
instance = db.instance_get(self.context, inst_id) instance = db.instance_get(self.context, inst_id)
d = self.fw.setup_nwfilters_for_instance(instance) self.fw.setup_base_nwfilters()
d.addCallback(_ensure_all_called) self.fw.setup_nwfilters_for_instance(instance)
d.addCallback(lambda _: self.teardown_security_group()) _ensure_all_called()
self.teardown_security_group()
return d

View File

@@ -21,8 +21,6 @@ Tests for Volume Code.
""" """
import logging import logging
from twisted.internet import defer
from nova import context from nova import context
from nova import exception from nova import exception
from nova import db from nova import db
@@ -33,7 +31,7 @@ from nova import utils
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
class VolumeTestCase(test.TrialTestCase): class VolumeTestCase(test.TestCase):
"""Test Case for volumes.""" """Test Case for volumes."""
def setUp(self): def setUp(self):
@@ -56,51 +54,48 @@ class VolumeTestCase(test.TrialTestCase):
vol['attach_status'] = "detached" vol['attach_status'] = "detached"
return db.volume_create(context.get_admin_context(), vol)['id'] return db.volume_create(context.get_admin_context(), vol)['id']
@defer.inlineCallbacks
def test_create_delete_volume(self): def test_create_delete_volume(self):
"""Test volume can be created and deleted.""" """Test volume can be created and deleted."""
volume_id = self._create_volume() volume_id = self._create_volume()
yield self.volume.create_volume(self.context, volume_id) self.volume.create_volume(self.context, volume_id)
self.assertEqual(volume_id, db.volume_get(context.get_admin_context(), self.assertEqual(volume_id, db.volume_get(context.get_admin_context(),
volume_id).id) volume_id).id)
yield self.volume.delete_volume(self.context, volume_id) self.volume.delete_volume(self.context, volume_id)
self.assertRaises(exception.NotFound, self.assertRaises(exception.NotFound,
db.volume_get, db.volume_get,
self.context, self.context,
volume_id) volume_id)
@defer.inlineCallbacks
def test_too_big_volume(self): def test_too_big_volume(self):
"""Ensure failure if a too large of a volume is requested.""" """Ensure failure if a too large of a volume is requested."""
# FIXME(vish): validation needs to move into the data layer in # FIXME(vish): validation needs to move into the data layer in
# volume_create # volume_create
defer.returnValue(True) return True
try: try:
volume_id = self._create_volume('1001') volume_id = self._create_volume('1001')
yield self.volume.create_volume(self.context, volume_id) self.volume.create_volume(self.context, volume_id)
self.fail("Should have thrown TypeError") self.fail("Should have thrown TypeError")
except TypeError: except TypeError:
pass pass
@defer.inlineCallbacks
def test_too_many_volumes(self): def test_too_many_volumes(self):
"""Ensure that NoMoreTargets is raised when we run out of volumes.""" """Ensure that NoMoreTargets is raised when we run out of volumes."""
vols = [] vols = []
total_slots = FLAGS.iscsi_num_targets total_slots = FLAGS.iscsi_num_targets
for _index in xrange(total_slots): for _index in xrange(total_slots):
volume_id = self._create_volume() volume_id = self._create_volume()
yield self.volume.create_volume(self.context, volume_id) self.volume.create_volume(self.context, volume_id)
vols.append(volume_id) vols.append(volume_id)
volume_id = self._create_volume() volume_id = self._create_volume()
self.assertFailure(self.volume.create_volume(self.context, self.assertRaises(db.NoMoreTargets,
volume_id), self.volume.create_volume,
db.NoMoreTargets) self.context,
volume_id)
db.volume_destroy(context.get_admin_context(), volume_id) db.volume_destroy(context.get_admin_context(), volume_id)
for volume_id in vols: for volume_id in vols:
yield self.volume.delete_volume(self.context, volume_id) self.volume.delete_volume(self.context, volume_id)
@defer.inlineCallbacks
def test_run_attach_detach_volume(self): def test_run_attach_detach_volume(self):
"""Make sure volume can be attached and detached from instance.""" """Make sure volume can be attached and detached from instance."""
inst = {} inst = {}
@@ -115,15 +110,15 @@ class VolumeTestCase(test.TrialTestCase):
instance_id = db.instance_create(self.context, inst)['id'] instance_id = db.instance_create(self.context, inst)['id']
mountpoint = "/dev/sdf" mountpoint = "/dev/sdf"
volume_id = self._create_volume() volume_id = self._create_volume()
yield self.volume.create_volume(self.context, volume_id) self.volume.create_volume(self.context, volume_id)
if FLAGS.fake_tests: if FLAGS.fake_tests:
db.volume_attached(self.context, volume_id, instance_id, db.volume_attached(self.context, volume_id, instance_id,
mountpoint) mountpoint)
else: else:
yield self.compute.attach_volume(self.context, self.compute.attach_volume(self.context,
instance_id, instance_id,
volume_id, volume_id,
mountpoint) mountpoint)
vol = db.volume_get(context.get_admin_context(), volume_id) vol = db.volume_get(context.get_admin_context(), volume_id)
self.assertEqual(vol['status'], "in-use") self.assertEqual(vol['status'], "in-use")
self.assertEqual(vol['attach_status'], "attached") self.assertEqual(vol['attach_status'], "attached")
@@ -131,25 +126,26 @@ class VolumeTestCase(test.TrialTestCase):
instance_ref = db.volume_get_instance(self.context, volume_id) instance_ref = db.volume_get_instance(self.context, volume_id)
self.assertEqual(instance_ref['id'], instance_id) self.assertEqual(instance_ref['id'], instance_id)
self.assertFailure(self.volume.delete_volume(self.context, volume_id), self.assertRaises(exception.Error,
exception.Error) self.volume.delete_volume,
self.context,
volume_id)
if FLAGS.fake_tests: if FLAGS.fake_tests:
db.volume_detached(self.context, volume_id) db.volume_detached(self.context, volume_id)
else: else:
yield self.compute.detach_volume(self.context, self.compute.detach_volume(self.context,
instance_id, instance_id,
volume_id) volume_id)
vol = db.volume_get(self.context, volume_id) vol = db.volume_get(self.context, volume_id)
self.assertEqual(vol['status'], "available") self.assertEqual(vol['status'], "available")
yield self.volume.delete_volume(self.context, volume_id) self.volume.delete_volume(self.context, volume_id)
self.assertRaises(exception.Error, self.assertRaises(exception.Error,
db.volume_get, db.volume_get,
self.context, self.context,
volume_id) volume_id)
db.instance_destroy(self.context, instance_id) db.instance_destroy(self.context, instance_id)
@defer.inlineCallbacks
def test_concurrent_volumes_get_different_targets(self): def test_concurrent_volumes_get_different_targets(self):
"""Ensure multiple concurrent volumes get different targets.""" """Ensure multiple concurrent volumes get different targets."""
volume_ids = [] volume_ids = []
@@ -164,15 +160,11 @@ class VolumeTestCase(test.TrialTestCase):
self.assert_(iscsi_target not in targets) self.assert_(iscsi_target not in targets)
targets.append(iscsi_target) targets.append(iscsi_target)
logging.debug("Target %s allocated", iscsi_target) logging.debug("Target %s allocated", iscsi_target)
deferreds = []
total_slots = FLAGS.iscsi_num_targets total_slots = FLAGS.iscsi_num_targets
for _index in xrange(total_slots): for _index in xrange(total_slots):
volume_id = self._create_volume() volume_id = self._create_volume()
d = self.volume.create_volume(self.context, volume_id) d = self.volume.create_volume(self.context, volume_id)
d.addCallback(_check) _check(d)
d.addErrback(self.fail)
deferreds.append(d)
yield defer.DeferredList(deferreds)
for volume_id in volume_ids: for volume_id in volume_ids:
self.volume.delete_volume(self.context, volume_id) self.volume.delete_volume(self.context, volume_id)

220
nova/tests/test_xenapi.py Normal file
View File

@@ -0,0 +1,220 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2010 Citrix Systems, Inc.
#
# 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.
"""
Test suite for XenAPI
"""
import stubout
from nova import db
from nova import context
from nova import flags
from nova import test
from nova import utils
from nova.auth import manager
from nova.compute import instance_types
from nova.compute import power_state
from nova.virt import xenapi_conn
from nova.virt.xenapi import fake
from nova.virt.xenapi import volume_utils
from nova.tests.db import fakes
from nova.tests.xenapi import stubs
FLAGS = flags.FLAGS
class XenAPIVolumeTestCase(test.TestCase):
"""
Unit tests for Volume operations
"""
def setUp(self):
super(XenAPIVolumeTestCase, self).setUp()
self.stubs = stubout.StubOutForTesting()
FLAGS.target_host = '127.0.0.1'
FLAGS.xenapi_connection_url = 'test_url'
FLAGS.xenapi_connection_password = 'test_pass'
fakes.stub_out_db_instance_api(self.stubs)
stubs.stub_out_get_target(self.stubs)
fake.reset()
self.values = {'name': 1, 'id': 1,
'project_id': 'fake',
'user_id': 'fake',
'image_id': 1,
'kernel_id': 2,
'ramdisk_id': 3,
'instance_type': 'm1.large',
'mac_address': 'aa:bb:cc:dd:ee:ff',
}
def _create_volume(self, size='0'):
"""Create a volume object."""
vol = {}
vol['size'] = size
vol['user_id'] = 'fake'
vol['project_id'] = 'fake'
vol['host'] = 'localhost'
vol['availability_zone'] = FLAGS.storage_availability_zone
vol['status'] = "creating"
vol['attach_status'] = "detached"
return db.volume_create(context.get_admin_context(), vol)
def test_create_iscsi_storage(self):
""" This shows how to test helper classes' methods """
stubs.stubout_session(self.stubs, stubs.FakeSessionForVolumeTests)
session = xenapi_conn.XenAPISession('test_url', 'root', 'test_pass')
helper = volume_utils.VolumeHelper
helper.XenAPI = session.get_imported_xenapi()
vol = self._create_volume()
info = helper.parse_volume_info(vol['ec2_id'], '/dev/sdc')
label = 'SR-%s' % vol['ec2_id']
description = 'Test-SR'
sr_ref = helper.create_iscsi_storage(session, info, label, description)
srs = fake.get_all('SR')
self.assertEqual(sr_ref, srs[0])
db.volume_destroy(context.get_admin_context(), vol['id'])
def test_parse_volume_info_raise_exception(self):
""" This shows how to test helper classes' methods """
stubs.stubout_session(self.stubs, stubs.FakeSessionForVolumeTests)
session = xenapi_conn.XenAPISession('test_url', 'root', 'test_pass')
helper = volume_utils.VolumeHelper
helper.XenAPI = session.get_imported_xenapi()
vol = self._create_volume()
# oops, wrong mount point!
self.assertRaises(volume_utils.StorageError,
helper.parse_volume_info,
vol['ec2_id'],
'/dev/sd')
db.volume_destroy(context.get_admin_context(), vol['id'])
def test_attach_volume(self):
""" This shows how to test Ops classes' methods """
stubs.stubout_session(self.stubs, stubs.FakeSessionForVolumeTests)
conn = xenapi_conn.get_connection(False)
volume = self._create_volume()
instance = db.instance_create(self.values)
fake.create_vm(instance.name, 'Running')
result = conn.attach_volume(instance.name, volume['ec2_id'],
'/dev/sdc')
def check():
# check that the VM has a VBD attached to it
# Get XenAPI reference for the VM
vms = fake.get_all('VM')
# Get XenAPI record for VBD
vbds = fake.get_all('VBD')
vbd = fake.get_record('VBD', vbds[0])
vm_ref = vbd['VM']
self.assertEqual(vm_ref, vms[0])
check()
def test_attach_volume_raise_exception(self):
""" This shows how to test when exceptions are raised """
stubs.stubout_session(self.stubs,
stubs.FakeSessionForVolumeFailedTests)
conn = xenapi_conn.get_connection(False)
volume = self._create_volume()
instance = db.instance_create(self.values)
fake.create_vm(instance.name, 'Running')
self.assertRaises(Exception,
conn.attach_volume,
instance.name,
volume['ec2_id'],
'/dev/sdc')
def tearDown(self):
super(XenAPIVolumeTestCase, self).tearDown()
self.stubs.UnsetAll()
class XenAPIVMTestCase(test.TestCase):
"""
Unit tests for VM operations
"""
def setUp(self):
super(XenAPIVMTestCase, self).setUp()
self.manager = manager.AuthManager()
self.user = self.manager.create_user('fake', 'fake', 'fake',
admin=True)
self.project = self.manager.create_project('fake', 'fake', 'fake')
self.network = utils.import_object(FLAGS.network_manager)
self.stubs = stubout.StubOutForTesting()
FLAGS.xenapi_connection_url = 'test_url'
FLAGS.xenapi_connection_password = 'test_pass'
fake.reset()
fakes.stub_out_db_instance_api(self.stubs)
fake.create_network('fake', FLAGS.flat_network_bridge)
def test_list_instances_0(self):
stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests)
conn = xenapi_conn.get_connection(False)
instances = conn.list_instances()
self.assertEquals(instances, [])
def test_spawn(self):
stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests)
values = {'name': 1, 'id': 1,
'project_id': self.project.id,
'user_id': self.user.id,
'image_id': 1,
'kernel_id': 2,
'ramdisk_id': 3,
'instance_type': 'm1.large',
'mac_address': 'aa:bb:cc:dd:ee:ff',
}
conn = xenapi_conn.get_connection(False)
instance = db.instance_create(values)
conn.spawn(instance)
def check():
instances = conn.list_instances()
self.assertEquals(instances, [1])
# Get Nova record for VM
vm_info = conn.get_info(1)
# Get XenAPI record for VM
vms = fake.get_all('VM')
vm = fake.get_record('VM', vms[0])
# Check that m1.large above turned into the right thing.
instance_type = instance_types.INSTANCE_TYPES['m1.large']
mem_kib = long(instance_type['memory_mb']) << 10
mem_bytes = str(mem_kib << 10)
vcpus = instance_type['vcpus']
self.assertEquals(vm_info['max_mem'], mem_kib)
self.assertEquals(vm_info['mem'], mem_kib)
self.assertEquals(vm['memory_static_max'], mem_bytes)
self.assertEquals(vm['memory_dynamic_max'], mem_bytes)
self.assertEquals(vm['memory_dynamic_min'], mem_bytes)
self.assertEquals(vm['VCPUs_max'], str(vcpus))
self.assertEquals(vm['VCPUs_at_startup'], str(vcpus))
# Check that the VM is running according to Nova
self.assertEquals(vm_info['state'], power_state.RUNNING)
# Check that the VM is running according to XenAPI.
self.assertEquals(vm['power_state'], 'Running')
check()
def tearDown(self):
super(XenAPIVMTestCase, self).tearDown()
self.manager.delete_project(self.project)
self.manager.delete_user(self.user)
self.stubs.UnsetAll()

View File

@@ -0,0 +1,20 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2010 Citrix Systems, Inc.
#
# 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.
"""
:mod:`xenapi` -- Stubs for XenAPI
=================================
"""

103
nova/tests/xenapi/stubs.py Normal file
View File

@@ -0,0 +1,103 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2010 Citrix Systems, Inc.
#
# 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.
"""Stubouts, mocks and fixtures for the test suite"""
from nova.virt import xenapi_conn
from nova.virt.xenapi import fake
from nova.virt.xenapi import volume_utils
def stubout_session(stubs, cls):
"""Stubs out two methods from XenAPISession"""
def fake_import(self):
"""Stubs out get_imported_xenapi of XenAPISession"""
fake_module = 'nova.virt.xenapi.fake'
from_list = ['fake']
return __import__(fake_module, globals(), locals(), from_list, -1)
stubs.Set(xenapi_conn.XenAPISession, '_create_session',
lambda s, url: cls(url))
stubs.Set(xenapi_conn.XenAPISession, 'get_imported_xenapi',
fake_import)
def stub_out_get_target(stubs):
"""Stubs out _get_target in volume_utils"""
def fake_get_target(volume_id):
return (None, None)
stubs.Set(volume_utils, '_get_target', fake_get_target)
class FakeSessionForVMTests(fake.SessionBase):
""" Stubs out a XenAPISession for VM tests """
def __init__(self, uri):
super(FakeSessionForVMTests, self).__init__(uri)
def network_get_all_records_where(self, _1, _2):
return self.xenapi.network.get_all_records()
def host_call_plugin(self, _1, _2, _3, _4, _5):
return ''
def VM_start(self, _1, ref, _2, _3):
vm = fake.get_record('VM', ref)
if vm['power_state'] != 'Halted':
raise fake.Failure(['VM_BAD_POWER_STATE', ref, 'Halted',
vm['power_state']])
vm['power_state'] = 'Running'
vm['is_a_template'] = False
vm['is_control_domain'] = False
class FakeSessionForVolumeTests(fake.SessionBase):
""" Stubs out a XenAPISession for Volume tests """
def __init__(self, uri):
super(FakeSessionForVolumeTests, self).__init__(uri)
def VBD_plug(self, _1, ref):
rec = fake.get_record('VBD', ref)
rec['currently-attached'] = True
def VDI_introduce(self, _1, uuid, _2, _3, _4, _5,
_6, _7, _8, _9, _10, _11):
valid_vdi = False
refs = fake.get_all('VDI')
for ref in refs:
rec = fake.get_record('VDI', ref)
if rec['uuid'] == uuid:
valid_vdi = True
if not valid_vdi:
raise fake.Failure([['INVALID_VDI', 'session', self._session]])
class FakeSessionForVolumeFailedTests(FakeSessionForVolumeTests):
""" Stubs out a XenAPISession for Volume tests: it injects failures """
def __init__(self, uri):
super(FakeSessionForVolumeFailedTests, self).__init__(uri)
def VDI_introduce(self, _1, uuid, _2, _3, _4, _5,
_6, _7, _8, _9, _10, _11):
# This is for testing failure
raise fake.Failure([['INVALID_VDI', 'session', self._session]])
def PBD_unplug(self, _1, ref):
rec = fake.get_record('PBD', ref)
rec['currently-attached'] = False
def SR_forget(self, _1, ref):
pass

View File

@@ -43,7 +43,7 @@ else:
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_string('logdir', None, 'directory to keep log files in ' flags.DEFINE_string('logdir', None, 'directory to keep log files in '
'(will be prepended to $logfile)') '(will be prepended to $logfile)')
@@ -208,7 +208,7 @@ def stop(pidfile):
pid = None pid = None
if not pid: if not pid:
message = "pidfile %s does not exist. Daemon not running?\n" message = _("pidfile %s does not exist. Daemon not running?\n")
sys.stderr.write(message % pidfile) sys.stderr.write(message % pidfile)
# Not an error in a restart # Not an error in a restart
return return
@@ -229,7 +229,7 @@ def stop(pidfile):
def serve(filename): def serve(filename):
logging.debug("Serving %s" % filename) logging.debug(_("Serving %s") % filename)
name = os.path.basename(filename) name = os.path.basename(filename)
OptionsClass = WrapTwistedOptions(TwistdServerOptions) OptionsClass = WrapTwistedOptions(TwistdServerOptions)
options = OptionsClass() options = OptionsClass()
@@ -281,7 +281,7 @@ def serve(filename):
else: else:
logging.getLogger().setLevel(logging.WARNING) logging.getLogger().setLevel(logging.WARNING)
logging.debug("Full set of FLAGS:") logging.debug(_("Full set of FLAGS:"))
for flag in FLAGS: for flag in FLAGS:
logging.debug("%s : %s" % (flag, FLAGS.get(flag, None))) logging.debug("%s : %s" % (flag, FLAGS.get(flag, None)))

View File

@@ -21,24 +21,24 @@ System-level utilities and helper functions.
""" """
import datetime import datetime
import functools
import inspect import inspect
import logging import logging
import os import os
import random import random
import subprocess import subprocess
import socket import socket
import struct
import sys import sys
import time
from xml.sax import saxutils from xml.sax import saxutils
from twisted.internet.threads import deferToThread from eventlet import event
from eventlet import greenthread
from nova import exception from nova import exception
from nova import flags
from nova.exception import ProcessExecutionError from nova.exception import ProcessExecutionError
FLAGS = flags.FLAGS
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
@@ -49,7 +49,7 @@ def import_class(import_str):
__import__(mod_str) __import__(mod_str)
return getattr(sys.modules[mod_str], class_str) return getattr(sys.modules[mod_str], class_str)
except (ImportError, ValueError, AttributeError): except (ImportError, ValueError, AttributeError):
raise exception.NotFound('Class %s cannot be found' % class_str) raise exception.NotFound(_('Class %s cannot be found') % class_str)
def import_object(import_str): def import_object(import_str):
@@ -62,8 +62,53 @@ def import_object(import_str):
return cls() return cls()
def vpn_ping(address, port, timeout=0.05, session_id=None):
"""Sends a vpn negotiation packet and returns the server session.
Returns False on a failure. Basic packet structure is below.
Client packet (14 bytes)::
0 1 8 9 13
+-+--------+-----+
|x| cli_id |?????|
+-+--------+-----+
x = packet identifier 0x38
cli_id = 64 bit identifier
? = unknown, probably flags/padding
Server packet (26 bytes)::
0 1 8 9 13 14 21 2225
+-+--------+-----+--------+----+
|x| srv_id |?????| cli_id |????|
+-+--------+-----+--------+----+
x = packet identifier 0x40
cli_id = 64 bit identifier
? = unknown, probably flags/padding
bit 9 was 1 and the rest were 0 in testing
"""
if session_id is None:
session_id = random.randint(0, 0xffffffffffffffff)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
data = struct.pack("!BQxxxxxx", 0x38, session_id)
sock.sendto(data, (address, port))
sock.settimeout(timeout)
try:
received = sock.recv(2048)
except socket.timeout:
return False
finally:
sock.close()
fmt = "!BQxxxxxQxxxx"
if len(received) != struct.calcsize(fmt):
print struct.calcsize(fmt)
return False
(identifier, server_sess, client_sess) = struct.unpack(fmt, received)
if identifier == 0x40 and client_sess == session_id:
return server_sess
def fetchfile(url, target): def fetchfile(url, target):
logging.debug("Fetching %s" % url) logging.debug(_("Fetching %s") % url)
# c = pycurl.Curl() # c = pycurl.Curl()
# fp = open(target, "wb") # fp = open(target, "wb")
# c.setopt(c.URL, url) # c.setopt(c.URL, url)
@@ -75,7 +120,7 @@ def fetchfile(url, target):
def execute(cmd, process_input=None, addl_env=None, check_exit_code=True): def execute(cmd, process_input=None, addl_env=None, check_exit_code=True):
logging.debug("Running cmd: %s", cmd) logging.debug(_("Running cmd (subprocess): %s"), cmd)
env = os.environ.copy() env = os.environ.copy()
if addl_env: if addl_env:
env.update(addl_env) env.update(addl_env)
@@ -88,13 +133,16 @@ def execute(cmd, process_input=None, addl_env=None, check_exit_code=True):
result = obj.communicate() result = obj.communicate()
obj.stdin.close() obj.stdin.close()
if obj.returncode: if obj.returncode:
logging.debug("Result was %s" % (obj.returncode))
if check_exit_code and obj.returncode != 0: if check_exit_code and obj.returncode != 0:
(stdout, stderr) = result (stdout, stderr) = result
raise ProcessExecutionError(exit_code=obj.returncode, raise ProcessExecutionError(exit_code=obj.returncode,
stdout=stdout, stdout=stdout,
stderr=stderr, stderr=stderr,
cmd=cmd) cmd=cmd)
# NOTE(termie): this appears to be necessary to let the subprocess call
# clean something up in between calls, without it two
# execute calls in a row hangs the second one
greenthread.sleep(0)
return result return result
@@ -122,14 +170,8 @@ def debug(arg):
def runthis(prompt, cmd, check_exit_code=True): def runthis(prompt, cmd, check_exit_code=True):
logging.debug("Running %s" % (cmd)) logging.debug(_("Running %s") % (cmd))
exit_code = subprocess.call(cmd.split(" ")) rv, err = execute(cmd, check_exit_code=check_exit_code)
logging.debug(prompt % (exit_code))
if check_exit_code and exit_code != 0:
raise ProcessExecutionError(exit_code=exit_code,
stdout=None,
stderr=None,
cmd=cmd)
def generate_uid(topic, size=8): def generate_uid(topic, size=8):
@@ -159,8 +201,6 @@ def last_octet(address):
def get_my_ip(): def get_my_ip():
"""Returns the actual ip of the local machine.""" """Returns the actual ip of the local machine."""
if getattr(FLAGS, 'fake_tests', None):
return '127.0.0.1'
try: try:
csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
csock.connect(('8.8.8.8', 80)) csock.connect(('8.8.8.8', 80))
@@ -168,17 +208,55 @@ def get_my_ip():
csock.close() csock.close()
return addr return addr
except socket.gaierror as ex: except socket.gaierror as ex:
logging.warn("Couldn't get IP, using 127.0.0.1 %s", ex) logging.warn(_("Couldn't get IP, using 127.0.0.1 %s"), ex)
return "127.0.0.1" return "127.0.0.1"
def utcnow():
"""Overridable version of datetime.datetime.utcnow."""
if utcnow.override_time:
return utcnow.override_time
return datetime.datetime.utcnow()
utcnow.override_time = None
def utcnow_ts():
"""Timestamp version of our utcnow function."""
return time.mktime(utcnow().timetuple())
def set_time_override(override_time=datetime.datetime.utcnow()):
"""Override utils.utcnow to return a constant time."""
utcnow.override_time = override_time
def advance_time_delta(timedelta):
"""Advance overriden time using a datetime.timedelta."""
assert(not utcnow.override_time is None)
utcnow.override_time += timedelta
def advance_time_seconds(seconds):
"""Advance overriden time by seconds."""
advance_time_delta(datetime.timedelta(0, seconds))
def clear_time_override():
"""Remove the overridden time."""
utcnow.override_time = None
def isotime(at=None): def isotime(at=None):
"""Returns iso formatted utcnow."""
if not at: if not at:
at = datetime.datetime.utcnow() at = utcnow()
return at.strftime(TIME_FORMAT) return at.strftime(TIME_FORMAT)
def parse_isotime(timestr): def parse_isotime(timestr):
"""Turn an iso formatted time back into a datetime"""
return datetime.datetime.strptime(timestr, TIME_FORMAT) return datetime.datetime.strptime(timestr, TIME_FORMAT)
@@ -212,7 +290,7 @@ class LazyPluggable(object):
if not self.__backend: if not self.__backend:
backend_name = self.__pivot.value backend_name = self.__pivot.value
if backend_name not in self.__backends: if backend_name not in self.__backends:
raise exception.Error('Invalid backend: %s' % backend_name) raise exception.Error(_('Invalid backend: %s') % backend_name)
backend = self.__backends[backend_name] backend = self.__backends[backend_name]
if type(backend) == type(tuple()): if type(backend) == type(tuple()):
@@ -231,10 +309,41 @@ class LazyPluggable(object):
return getattr(backend, key) return getattr(backend, key)
def deferredToThread(f): class LoopingCall(object):
def g(*args, **kwargs): def __init__(self, f=None, *args, **kw):
return deferToThread(f, *args, **kwargs) self.args = args
return g self.kw = kw
self.f = f
self._running = False
def start(self, interval, now=True):
self._running = True
done = event.Event()
def _inner():
if not now:
greenthread.sleep(interval)
try:
while self._running:
self.f(*self.args, **self.kw)
greenthread.sleep(interval)
except Exception:
logging.exception('in looping call')
done.send_exception(*sys.exc_info())
return
done.send(True)
self.done = done
greenthread.spawn(_inner)
return self.done
def stop(self):
self._running = False
def wait(self):
return self.done.wait()
def xhtml_escape(value): def xhtml_escape(value):

View File

@@ -1,94 +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.
"""Decorators for argument validation, courtesy of
http://rmi.net/~lutz/rangetest.html"""
def rangetest(**argchecks):
"""Validate ranges for both + defaults"""
def onDecorator(func):
"""onCall remembers func and argchecks"""
import sys
code = func.__code__ if sys.version_info[0] == 3 else func.func_code
allargs = code.co_varnames[:code.co_argcount]
funcname = func.__name__
def onCall(*pargs, **kargs):
# all pargs match first N args by position
# the rest must be in kargs or omitted defaults
positionals = list(allargs)
positionals = positionals[:len(pargs)]
for (argname, (low, high)) in argchecks.items():
# for all args to be checked
if argname in kargs:
# was passed by name
if float(kargs[argname]) < low or \
float(kargs[argname]) > high:
errmsg = '{0} argument "{1}" not in {2}..{3}'
errmsg = errmsg.format(funcname, argname, low, high)
raise TypeError(errmsg)
elif argname in positionals:
# was passed by position
position = positionals.index(argname)
if float(pargs[position]) < low or \
float(pargs[position]) > high:
errmsg = '{0} argument "{1}" with value of {4} ' \
'not in {2}..{3}'
errmsg = errmsg.format(funcname, argname, low, high,
pargs[position])
raise TypeError(errmsg)
else:
pass
return func(*pargs, **kargs) # okay: run original call
return onCall
return onDecorator
def typetest(**argchecks):
def onDecorator(func):
import sys
code = func.__code__ if sys.version_info[0] == 3 else func.func_code
allargs = code.co_varnames[:code.co_argcount]
funcname = func.__name__
def onCall(*pargs, **kargs):
positionals = list(allargs)[:len(pargs)]
for (argname, typeof) in argchecks.items():
if argname in kargs:
if not isinstance(kargs[argname], typeof):
errmsg = '{0} argument "{1}" not of type {2}'
errmsg = errmsg.format(funcname, argname, typeof)
raise TypeError(errmsg)
elif argname in positionals:
position = positionals.index(argname)
if not isinstance(pargs[position], typeof):
errmsg = '{0} argument "{1}" with value of {2} ' \
'not of type {3}'
errmsg = errmsg.format(funcname, argname,
pargs[position], typeof)
raise TypeError(errmsg)
else:
pass
return func(*pargs, **kargs)
return onCall
return onDecorator

View File

@@ -1,122 +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.
"""
This is our basic test running framework based on Twisted's Trial.
Usage Examples:
# to run all the tests
python run_tests.py
# to run a specific test suite imported here
python run_tests.py NodeConnectionTestCase
# to run a specific test imported here
python run_tests.py NodeConnectionTestCase.test_reboot
# to run some test suites elsewhere
python run_tests.py nova.tests.node_unittest
python run_tests.py nova.tests.node_unittest.NodeConnectionTestCase
Due to our use of multiprocessing it we frequently get some ignorable
'Interrupted system call' exceptions after test completion.
"""
import __main__
import os
import sys
from twisted.scripts import trial as trial_script
from nova import flags
from nova import twistd
from nova.tests.access_unittest import *
from nova.tests.api_unittest import *
from nova.tests.auth_unittest import *
from nova.tests.cloud_unittest import *
from nova.tests.compute_unittest import *
from nova.tests.flags_unittest import *
from nova.tests.misc_unittest import *
from nova.tests.network_unittest import *
from nova.tests.objectstore_unittest import *
from nova.tests.process_unittest import *
from nova.tests.quota_unittest import *
from nova.tests.rpc_unittest import *
from nova.tests.scheduler_unittest import *
from nova.tests.service_unittest import *
from nova.tests.twistd_unittest import *
from nova.tests.validator_unittest import *
from nova.tests.virt_unittest import *
from nova.tests.virt_unittest import *
from nova.tests.volume_unittest import *
FLAGS = flags.FLAGS
flags.DEFINE_bool('flush_db', True,
'Flush the database before running fake tests')
flags.DEFINE_string('tests_stderr', 'run_tests.err.log',
'Path to where to pipe STDERR during test runs.'
' Default = "run_tests.err.log"')
if __name__ == '__main__':
OptionsClass = twistd.WrapTwistedOptions(trial_script.Options)
config = OptionsClass()
argv = config.parseOptions()
FLAGS.verbose = True
# TODO(termie): these should make a call instead of doing work on import
if FLAGS.fake_tests:
from nova.tests.fake_flags import *
else:
from nova.tests.real_flags import *
# Establish redirect for STDERR
sys.stderr.flush()
err = open(FLAGS.tests_stderr, 'w+', 0)
os.dup2(err.fileno(), sys.stderr.fileno())
if len(argv) == 1 and len(config['tests']) == 0:
# If no tests were specified run the ones imported in this file
# NOTE(termie): "tests" is not a flag, just some Trial related stuff
config['tests'].update(['__main__'])
elif len(config['tests']):
# If we specified tests check first whether they are in __main__
for arg in config['tests']:
key = arg.split('.')[0]
if hasattr(__main__, key):
config['tests'].remove(arg)
config['tests'].add('__main__.%s' % arg)
trial_script._initialDebugSetup(config)
trialRunner = trial_script._makeRunner(config)
suite = trial_script._getSuite(config)
if config['until-failure']:
test_result = trialRunner.runUntilFailure(suite)
else:
test_result = trialRunner.run(suite)
if config.tracer:
sys.settrace(None)
results = config.tracer.results()
results.write_results(show_missing=1, summary=False,
coverdir=config.coverdir)
sys.exit(not test_result.wasSuccessful())

View File

@@ -36,7 +36,8 @@ done
if [ $never_venv -eq 1 ]; then if [ $never_venv -eq 1 ]; then
# Just run the test suites in current environment # Just run the test suites in current environment
python run_tests.py rm -f nova.sqlite
nosetests -v
exit exit
fi fi
@@ -47,7 +48,8 @@ if [ $force -eq 1 ]; then
fi fi
if [ -e ${venv} ]; then if [ -e ${venv} ]; then
${with_venv} python run_tests.py $@ ${with_venv} rm -f nova.sqlite
${with_venv} nosetests -v $@
else else
if [ $always_venv -eq 1 ]; then if [ $always_venv -eq 1 ]; then
# Automatically install the virtualenv # Automatically install the virtualenv
@@ -59,9 +61,11 @@ else
# Install the virtualenv and run the test suite in it # Install the virtualenv and run the test suite in it
python tools/install_venv.py python tools/install_venv.py
else else
python run_tests.py rm -f nova.sqlite
nosetests -v
exit exit
fi fi
fi fi
${with_venv} python run_tests.py $@ ${with_venv} rm -f nova.sqlite
${with_venv} nosetests -v $@
fi fi

View File

@@ -59,6 +59,7 @@ setup(name='nova',
'build_sphinx': local_BuildDoc}, 'build_sphinx': local_BuildDoc},
packages=find_packages(exclude=['bin', 'smoketests']), packages=find_packages(exclude=['bin', 'smoketests']),
include_package_data=True, include_package_data=True,
test_suite='nose.collector',
scripts=['bin/nova-api', scripts=['bin/nova-api',
'bin/nova-compute', 'bin/nova-compute',
'bin/nova-dhcpbridge', 'bin/nova-dhcpbridge',