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:
8
.mailmap
8
.mailmap
@@ -19,11 +19,11 @@
|
||||
<mordred@inaugust.com> <mordred@hudson>
|
||||
<paul@openstack.org> <pvoccio@castor.local>
|
||||
<paul@openstack.org> <paul.voccio@rackspace.com>
|
||||
<soren.hansen@rackspace.com> <soren@linux2go.dk>
|
||||
<todd@ansolabs.com> <todd@lapex>
|
||||
<todd@ansolabs.com> <todd@rubidine.com>
|
||||
<vishvananda@gmail.com> <vishvananda@yahoo.com>
|
||||
<vishvananda@gmail.com> <root@mirror.nasanebula.net>
|
||||
# These are from people who failed to set a proper committer
|
||||
. <root@tonbuntu>
|
||||
. <laner@controller>
|
||||
. <root@ubuntu>
|
||||
<vishvananda@gmail.com> <root@ubuntu>
|
||||
<sleepsonthefloor@gmail.com> <root@tonbuntu>
|
||||
<rlane@wikimedia.org> <laner@controller>
|
||||
|
||||
10
Authors
10
Authors
@@ -3,8 +3,12 @@ Anne Gentle <anne@openstack.org>
|
||||
Anthony Young <sleepsonthefloor@gmail.com>
|
||||
Armando Migliaccio <Armando.Migliaccio@eu.citrix.com>
|
||||
Chris Behrens <cbehrens@codestud.com>
|
||||
Chmouel Boudjnah <chmouel@chmouel.com>
|
||||
David Pravec <David.Pravec@danix.org>
|
||||
Dean Troyer <dtroyer@gmail.com>
|
||||
Devin Carlen <devin.carlen@gmail.com>
|
||||
Ed Leafe <ed@leafe.com>
|
||||
Eldar Nugaev <enugaev@griddynamics.com>
|
||||
Eric Day <eday@oddments.org>
|
||||
Ewan Mellor <ewan.mellor@citrix.com>
|
||||
Hisaki Ohara <hisaki.ohara@intel.com>
|
||||
@@ -12,6 +16,7 @@ Jay Pipes <jaypipes@gmail.com>
|
||||
Jesse Andrews <anotherjesse@gmail.com>
|
||||
Joe Heck <heckj@mac.com>
|
||||
Joel Moore <joelbm24@gmail.com>
|
||||
Jonathan Bryce <jbryce@jbryce.com>
|
||||
Josh Kearney <josh.kearney@rackspace.com>
|
||||
Joshua McKenty <jmckenty@gmail.com>
|
||||
Justin Santa Barbara <justin@fathomdb.com>
|
||||
@@ -20,8 +25,13 @@ Michael Gundlach <michael.gundlach@rackspace.com>
|
||||
Monty Taylor <mordred@inaugust.com>
|
||||
Paul Voccio <paul@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>
|
||||
Thierry Carrez <thierry@openstack.org>
|
||||
Todd Willey <todd@ansolabs.com>
|
||||
Trey Morris <trey.morris@rackspace.com>
|
||||
Vishvananda Ishaya <vishvananda@gmail.com>
|
||||
Youcef Laribi <Youcef.Laribi@eu.citrix.com>
|
||||
Zhixue Wu <Zhixue.Wu@citrix.com>
|
||||
|
||||
@@ -16,16 +16,24 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
# ARG is the id of the user
|
||||
export SUBJ="/C=US/ST=California/L=MountainView/O=AnsoLabs/OU=NovaDev/CN=customer-intCA-$1"
|
||||
mkdir INTER/$1
|
||||
cd INTER/$1
|
||||
# $1 is the id of the project and $2 is the subject of the cert
|
||||
NAME=$1
|
||||
SUBJ=$2
|
||||
mkdir -p projects/$NAME
|
||||
cd projects/$NAME
|
||||
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
|
||||
openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes
|
||||
echo "10" > serial
|
||||
touch index.txt
|
||||
openssl genrsa -out private/cakey.pem 1024 -config ./openssl.cnf -batch -nodes
|
||||
openssl req -new -sha2 -key private/cakey.pem -out ../../reqs/inter$1.csr -batch -subj "$SUBJ"
|
||||
cd ../../
|
||||
openssl ca -extensions v3_ca -days 365 -out INTER/$1/cacert.pem -in reqs/inter$1.csr -config openssl.cnf -batch
|
||||
# NOTE(vish): Disabling intermediate ca's because we don't actually need them.
|
||||
# It makes more sense to have each project have its own root ca.
|
||||
# openssl genrsa -out private/cakey.pem 1024 -config ./openssl.cnf -batch -nodes
|
||||
# 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
|
||||
|
||||
@@ -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
|
||||
touch index.txt
|
||||
echo "10" > serial
|
||||
openssl ca -gencrl -config ./openssl.cnf -out crl.pem
|
||||
fi
|
||||
|
||||
34
nova/tests/validator_unittest.py → CA/genvpn.sh
Normal file → Executable file
34
nova/tests/validator_unittest.py → CA/genvpn.sh
Normal file → Executable file
@@ -1,3 +1,4 @@
|
||||
#!/bin/bash
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
@@ -16,27 +17,20 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
import unittest
|
||||
# This gets zipped and run on the cloudpipe-managed OpenVPN server
|
||||
NAME=$1
|
||||
SUBJ=$2
|
||||
|
||||
from nova import flags
|
||||
from nova import test
|
||||
from nova import validate
|
||||
mkdir -p projects/$NAME
|
||||
cd projects/$NAME
|
||||
|
||||
# generate a server priv key
|
||||
openssl genrsa -out server.key 2048
|
||||
|
||||
class ValidationTestCase(test.TrialTestCase):
|
||||
def setUp(self):
|
||||
super(ValidationTestCase, self).setUp()
|
||||
# generate a server CSR
|
||||
openssl req -new -key server.key -out server.csr -batch -subj "$SUBJ"
|
||||
|
||||
def tearDown(self):
|
||||
super(ValidationTestCase, self).tearDown()
|
||||
|
||||
def test_type_validation(self):
|
||||
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
|
||||
novauid=`getent passwd nova | awk -F: '{print $3}'`
|
||||
if [ ! -z "${novauid}" ] && [ "`id -u`" != "${novauid}" ]; then
|
||||
sudo chown -R nova:nogroup .
|
||||
fi
|
||||
@@ -24,7 +24,6 @@ dir = .
|
||||
|
||||
[ ca ]
|
||||
default_ca = CA_default
|
||||
unique_subject = no
|
||||
|
||||
[ CA_default ]
|
||||
serial = $dir/serial
|
||||
@@ -32,6 +31,8 @@ database = $dir/index.txt
|
||||
new_certs_dir = $dir/newcerts
|
||||
certificate = $dir/cacert.pem
|
||||
private_key = $dir/private/cakey.pem
|
||||
unique_subject = no
|
||||
default_crl_days = 365
|
||||
default_days = 365
|
||||
default_md = md5
|
||||
preserve = no
|
||||
|
||||
@@ -13,7 +13,7 @@ include nova/cloudpipe/client.ovpn.template
|
||||
include nova/compute/fakevirtinstance.xml
|
||||
include nova/compute/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/cacert.pem
|
||||
include nova/tests/CA/private/
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
|
||||
|
||||
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
|
||||
b. source compute node logfile
|
||||
c. dest compute node logfile
|
||||
|
||||
23
bin/nova-api
23
bin/nova-api
@@ -17,10 +17,10 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""
|
||||
Nova API daemon.
|
||||
"""
|
||||
|
||||
"""Starter script for Nova API."""
|
||||
|
||||
import gettext
|
||||
import os
|
||||
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')):
|
||||
sys.path.insert(0, possible_topdir)
|
||||
|
||||
gettext.install('nova', unicode=1)
|
||||
|
||||
from nova import api
|
||||
from nova import flags
|
||||
from nova import utils
|
||||
from nova import server
|
||||
from nova import wsgi
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
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')
|
||||
|
||||
|
||||
def main(_args):
|
||||
from nova import api
|
||||
from nova import wsgi
|
||||
if __name__ == '__main__':
|
||||
utils.default_flagfile()
|
||||
FLAGS(sys.argv)
|
||||
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()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
utils.default_flagfile()
|
||||
server.serve('nova-api', main)
|
||||
|
||||
68
bin/nova-combined
Executable file
68
bin/nova-combined
Executable 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()
|
||||
@@ -17,10 +17,12 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Twistd daemon for the nova compute nodes.
|
||||
"""
|
||||
"""Starter script for Nova Compute."""
|
||||
|
||||
import eventlet
|
||||
eventlet.monkey_patch()
|
||||
|
||||
import gettext
|
||||
import os
|
||||
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')):
|
||||
sys.path.insert(0, possible_topdir)
|
||||
|
||||
from nova import service
|
||||
from nova import twistd
|
||||
from nova import utils
|
||||
gettext.install('nova', unicode=1)
|
||||
|
||||
from nova import service
|
||||
from nova import utils
|
||||
|
||||
if __name__ == '__main__':
|
||||
utils.default_flagfile()
|
||||
twistd.serve(__file__)
|
||||
|
||||
if __name__ == '__builtin__':
|
||||
application = service.Service.create() # pylint: disable=C0103
|
||||
service.serve()
|
||||
service.wait()
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
Handle lease database updates from DHCP servers.
|
||||
"""
|
||||
|
||||
import gettext
|
||||
import logging
|
||||
import os
|
||||
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')):
|
||||
sys.path.insert(0, possible_topdir)
|
||||
|
||||
gettext.install('nova', unicode=1)
|
||||
|
||||
from nova import context
|
||||
from nova import db
|
||||
from nova import flags
|
||||
@@ -107,7 +110,6 @@ def main():
|
||||
FLAGS.num_networks = 5
|
||||
path = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||
'..',
|
||||
'_trial_temp',
|
||||
'nova.sqlite'))
|
||||
FLAGS.sql_connection = 'sqlite:///%s' % path
|
||||
action = argv[1]
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
Download images from Canonical Image Store
|
||||
"""
|
||||
|
||||
import gettext
|
||||
import json
|
||||
import os
|
||||
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')):
|
||||
sys.path.insert(0, possible_topdir)
|
||||
|
||||
gettext.install('nova', unicode=1)
|
||||
|
||||
from nova import flags
|
||||
from nova import utils
|
||||
from nova.objectstore import image
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
Daemon for Nova RRD based instance resource monitoring.
|
||||
"""
|
||||
|
||||
import gettext
|
||||
import os
|
||||
import logging
|
||||
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')):
|
||||
sys.path.insert(0, possible_topdir)
|
||||
|
||||
gettext.install('nova', unicode=1)
|
||||
|
||||
from nova import utils
|
||||
from nova import twistd
|
||||
from nova.compute import monitor
|
||||
|
||||
177
bin/nova-manage
177
bin/nova-manage
@@ -53,6 +53,7 @@
|
||||
CLI interface for nova management.
|
||||
"""
|
||||
|
||||
import gettext
|
||||
import logging
|
||||
import os
|
||||
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')):
|
||||
sys.path.insert(0, possible_topdir)
|
||||
|
||||
gettext.install('nova', unicode=1)
|
||||
|
||||
from nova import context
|
||||
from nova import crypto
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
from nova import quota
|
||||
from nova import utils
|
||||
from nova.auth import manager
|
||||
from nova.cloudpipe import pipelib
|
||||
#added by masumotok
|
||||
from nova import rpc
|
||||
# added by masumotok
|
||||
from nova.cloudpipe import pipelib
|
||||
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.pipe = pipelib.CloudPipe()
|
||||
|
||||
def list(self):
|
||||
"""Print a listing of the VPNs for all projects."""
|
||||
def list(self, project=None):
|
||||
"""Print a listing of the VPN data for one or all projects.
|
||||
|
||||
args: [project=all]"""
|
||||
print "%-12s\t" % 'project',
|
||||
print "%-20s\t" % 'ip:port',
|
||||
print "%-20s\t" % 'private_ip',
|
||||
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,
|
||||
|
||||
try:
|
||||
s = "%s:%s" % (project.vpn_ip, project.vpn_port)
|
||||
except exception.NotFound:
|
||||
s = "None"
|
||||
print "%-20s\t" % s,
|
||||
|
||||
vpn = self._vpn_for(project.id)
|
||||
ipport = "%s:%s" % (project.vpn_ip, project.vpn_port)
|
||||
print "%-20s\t" % ipport,
|
||||
ctxt = context.get_admin_context()
|
||||
vpn = db.instance_get_project_vpn(ctxt, project.id)
|
||||
if vpn:
|
||||
command = "ping -c1 -w1 %s > /dev/null; echo $?"
|
||||
out, _err = utils.execute(command % vpn['private_dns_name'],
|
||||
check_exit_code=False)
|
||||
if out.strip() == '0':
|
||||
net = 'up'
|
||||
else:
|
||||
net = 'down'
|
||||
print vpn['private_dns_name'],
|
||||
print vpn['node_name'],
|
||||
print vpn['instance_id'],
|
||||
address = None
|
||||
state = 'down'
|
||||
if vpn.get('fixed_ip', None):
|
||||
address = vpn['fixed_ip']['address']
|
||||
if project.vpn_ip and utils.vpn_ping(project.vpn_ip,
|
||||
project.vpn_port):
|
||||
state = 'up'
|
||||
print address,
|
||||
print vpn['host'],
|
||||
print vpn['ec2_id'],
|
||||
print vpn['state_description'],
|
||||
print net
|
||||
|
||||
print state
|
||||
else:
|
||||
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):
|
||||
"""Run all VPNs."""
|
||||
for p in reversed(self.manager.get_projects()):
|
||||
@@ -153,6 +149,21 @@ class VpnCommands(object):
|
||||
"""Start the VPN for a given project."""
|
||||
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):
|
||||
def bpython(self):
|
||||
@@ -299,6 +310,14 @@ class UserCommands(object):
|
||||
is_admin = False
|
||||
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 for managing projects."""
|
||||
@@ -366,9 +385,14 @@ class ProjectCommands(object):
|
||||
def zipfile(self, project_id, user_id, filename='nova.zip'):
|
||||
"""Exports credentials for project to a zip file
|
||||
arguments: project_id user_id [filename='nova.zip]"""
|
||||
zip_file = self.manager.get_credentials(user_id, project_id)
|
||||
with open(filename, 'w') as f:
|
||||
f.write(zip_file)
|
||||
try:
|
||||
zip_file = self.manager.get_credentials(user_id, project_id)
|
||||
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):
|
||||
@@ -431,7 +455,7 @@ class NetworkCommands(object):
|
||||
int(network_size), int(vlan_start),
|
||||
int(vpn_start))
|
||||
|
||||
# this class is added by masumotok
|
||||
|
||||
class InstanceCommands(object):
|
||||
"""Class for mangaging VM instances."""
|
||||
|
||||
@@ -441,43 +465,29 @@ class InstanceCommands(object):
|
||||
logging.basicConfig()
|
||||
ctxt = context.get_admin_context()
|
||||
|
||||
# 1. whether destination host exists
|
||||
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 :
|
||||
try:
|
||||
internal_id = cloud.ec2_id_to_internal_id(ec2_id)
|
||||
instance_ref = db.instance_get_by_internal_id(ctxt, internal_id)
|
||||
except exception.NotFound :
|
||||
print 'Not found instance_id(%s (internal_id:%s))' % ( ec2_id, internal_id)
|
||||
raise
|
||||
instance_id = instance_ref['id']
|
||||
except exception.NotFound as e:
|
||||
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,
|
||||
FLAGS.scheduler_topic,
|
||||
{ "method": "live_migration",
|
||||
"args": {"ec2_id": ec2_id,
|
||||
"dest":dest}})
|
||||
{"method": "live_migration",
|
||||
"args": {"instance_id": instance_id,
|
||||
"dest": dest,
|
||||
"topic": FLAGS.compute_topic}})
|
||||
|
||||
if None != ret :
|
||||
if None != 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.'
|
||||
|
||||
|
||||
# this class is created by masumotok
|
||||
class HostCommands(object):
|
||||
"""Class for mangaging host(physical nodes)."""
|
||||
|
||||
@@ -485,17 +495,18 @@ class HostCommands(object):
|
||||
def list(self):
|
||||
"""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()
|
||||
|
||||
host_refs = db.host_get_all(context.get_admin_context())
|
||||
for host_ref in host_refs:
|
||||
print host_ref['name']
|
||||
|
||||
|
||||
def show(self, 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()
|
||||
|
||||
result = rpc.call(context.get_admin_context(),
|
||||
@@ -503,41 +514,27 @@ class HostCommands(object):
|
||||
{"method": "show_host_resource",
|
||||
"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.
|
||||
if dict != type(result):
|
||||
print 'Unexpected error occurs'
|
||||
elif not result['ret'] :
|
||||
elif not result['ret']:
|
||||
print '%s' % result['msg']
|
||||
else :
|
||||
else:
|
||||
cpu = result['phy_resource']['vcpus']
|
||||
mem = result['phy_resource']['memory_mb']
|
||||
hdd = result['phy_resource']['local_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)
|
||||
for p_id, val in result['usage'].items() :
|
||||
print '%s\t%s\t\t%s\t%s\t%s' % ( host,
|
||||
print '%s\t\t\t%s\t%s\t%s' % (host, cpu, mem, hdd)
|
||||
for p_id, val in result['usage'].items():
|
||||
print '%s\t%s\t\t%s\t%s\t%s' % (host,
|
||||
p_id,
|
||||
val['vcpus'],
|
||||
val['memory_mb'],
|
||||
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 = [
|
||||
('user', UserCommands),
|
||||
('project', ProjectCommands),
|
||||
@@ -547,7 +544,7 @@ CATEGORIES = [
|
||||
('floating', FloatingIpCommands),
|
||||
('network', NetworkCommands),
|
||||
('instance', InstanceCommands),
|
||||
('host',HostCommands)]
|
||||
('host', HostCommands)]
|
||||
|
||||
def lazy_match(name, key_value_tuples):
|
||||
"""Finds all objects that have a key that case insensitively contains
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Twistd daemon for the nova network nodes.
|
||||
"""
|
||||
"""Starter script for Nova Network."""
|
||||
|
||||
import eventlet
|
||||
eventlet.monkey_patch()
|
||||
|
||||
import gettext
|
||||
import os
|
||||
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')):
|
||||
sys.path.insert(0, possible_topdir)
|
||||
|
||||
from nova import service
|
||||
from nova import twistd
|
||||
from nova import utils
|
||||
gettext.install('nova', unicode=1)
|
||||
|
||||
from nova import service
|
||||
from nova import utils
|
||||
|
||||
if __name__ == '__main__':
|
||||
utils.default_flagfile()
|
||||
twistd.serve(__file__)
|
||||
|
||||
if __name__ == '__builtin__':
|
||||
application = service.Service.create() # pylint: disable-msg=C0103
|
||||
service.serve()
|
||||
service.wait()
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
Twisted daemon for nova objectstore. Supports S3 API.
|
||||
"""
|
||||
|
||||
import gettext
|
||||
import os
|
||||
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')):
|
||||
sys.path.insert(0, possible_topdir)
|
||||
|
||||
gettext.install('nova', unicode=1)
|
||||
|
||||
from nova import flags
|
||||
from nova import utils
|
||||
from nova import twistd
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Twistd daemon for the nova scheduler nodes.
|
||||
"""
|
||||
"""Starter script for Nova Scheduler."""
|
||||
|
||||
import eventlet
|
||||
eventlet.monkey_patch()
|
||||
|
||||
import gettext
|
||||
import os
|
||||
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')):
|
||||
sys.path.insert(0, possible_topdir)
|
||||
|
||||
from nova import service
|
||||
from nova import twistd
|
||||
from nova import utils
|
||||
gettext.install('nova', unicode=1)
|
||||
|
||||
from nova import service
|
||||
from nova import utils
|
||||
|
||||
if __name__ == '__main__':
|
||||
utils.default_flagfile()
|
||||
twistd.serve(__file__)
|
||||
|
||||
if __name__ == '__builtin__':
|
||||
application = service.Service.create()
|
||||
service.serve()
|
||||
service.wait()
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Twistd daemon for the nova volume nodes.
|
||||
"""
|
||||
"""Starter script for Nova Volume."""
|
||||
|
||||
import eventlet
|
||||
eventlet.monkey_patch()
|
||||
|
||||
import gettext
|
||||
import os
|
||||
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')):
|
||||
sys.path.insert(0, possible_topdir)
|
||||
|
||||
from nova import service
|
||||
from nova import twistd
|
||||
from nova import utils
|
||||
gettext.install('nova', unicode=1)
|
||||
|
||||
from nova import service
|
||||
from nova import utils
|
||||
|
||||
if __name__ == '__main__':
|
||||
utils.default_flagfile()
|
||||
twistd.serve(__file__)
|
||||
|
||||
if __name__ == '__builtin__':
|
||||
application = service.Service.create() # pylint: disable-msg=C0103
|
||||
service.serve()
|
||||
service.wait()
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import gettext
|
||||
import os
|
||||
|
||||
gettext.install('nova')
|
||||
|
||||
from nova import utils
|
||||
|
||||
def setup(app):
|
||||
|
||||
@@ -23,7 +23,7 @@ In Nova, users organize their cloud resources in projects. A Nova project consis
|
||||
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:
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ Installing Nova on Multiple Servers
|
||||
===================================
|
||||
|
||||
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
|
||||
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 performance optimization, split reads and writes to the database. MySQL proxy is the easiest way to make this work if running MySQL.
|
||||
|
||||
|
||||
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!
|
||||
|
||||
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>
|
||||
|
||||
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
|
||||
--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
|
||||
--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
|
||||
--network_size=<# of addrs> # number of ip addrs to use for VM guests, ex 64
|
||||
|
||||
|
||||
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
|
||||
|
||||
::
|
||||
#. Create a nova group::
|
||||
|
||||
sudo addgroup nova
|
||||
|
||||
5. nova-objectstore specific flags < no specific config needed >
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
::
|
||||
|
||||
@@ -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
|
||||
-----------------------
|
||||
|
||||
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;
|
||||
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.
|
||||
|
||||
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?
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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
|
||||
~/.bashrc
|
||||
|
||||
|
||||
Step 6 Restart all relevant services
|
||||
------------------------------------
|
||||
|
||||
@@ -249,8 +228,8 @@ Restart relevant nova services::
|
||||
|
||||
.. 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.
|
||||
|
||||
|
||||
@@ -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
|
||||
------------------------------------------------------
|
||||
|
||||
Depending on your system, the mehod for accomplishing this varies
|
||||
Depending on your system, the method for accomplishing this varies
|
||||
|
||||
.. toctree::
|
||||
: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_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.
|
||||
|
||||
@@ -94,7 +106,7 @@ Type or copy/paste in the following line to create a project named IRT (for Ice
|
||||
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:
|
||||
@@ -116,7 +128,7 @@ You'll see these files extract.
|
||||
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.
|
||||
|
||||
@@ -125,9 +137,9 @@ Type or copy/paste the following to source the novarc file in your current worki
|
||||
. novarc
|
||||
|
||||
|
||||
Step 8: Pat yourself on the back :)
|
||||
Step 9: Pat yourself on the back :)
|
||||
-----------------------------------
|
||||
Congratulations, your cloud is up and running, you’ve created an admin user, retrieved the user's credentials and put them in your environment.
|
||||
Congratulations, your cloud is up and running, you’ve created an admin user, created a network, retrieved the user's credentials and put them in your environment.
|
||||
|
||||
Now you need an image.
|
||||
|
||||
|
||||
@@ -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
|
||||
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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -72,12 +74,15 @@ IaaS - Infrastructure as a Service
|
||||
Provides infrastructure such as computer instances, network connections, and storage so that people
|
||||
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
|
||||
--------------------------
|
||||
.. 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
|
||||
------------------
|
||||
|
||||
BIN
doc/source/images/novascreens.png
Normal file
BIN
doc/source/images/novascreens.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
BIN
doc/source/images/novashvirtually.png
Normal file
BIN
doc/source/images/novashvirtually.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
@@ -26,7 +26,7 @@ Nova is written with the following design guidelines in mind:
|
||||
|
||||
* **Component based architecture**: Quickly add new behaviors
|
||||
* **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
|
||||
* **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
|
||||
@@ -62,8 +62,6 @@ Administrator's Documentation
|
||||
adminguide/single.node.install
|
||||
adminguide/multi.node.install
|
||||
|
||||
.. todo:: add swiftadmin
|
||||
|
||||
Developer Docs
|
||||
==============
|
||||
|
||||
|
||||
@@ -1,2 +1,48 @@
|
||||
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
|
||||
|
||||
|
||||
@@ -194,6 +194,7 @@ class HostInfo(object):
|
||||
|
||||
|
||||
class NovaAdminClient(object):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
clc_url=DEFAULT_CLC_URL,
|
||||
|
||||
@@ -37,7 +37,6 @@ class DbDriver(object):
|
||||
def __init__(self):
|
||||
"""Imports the LDAP module"""
|
||||
pass
|
||||
db
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
@@ -83,7 +82,7 @@ class DbDriver(object):
|
||||
user_ref = db.user_create(context.get_admin_context(), values)
|
||||
return self._db_user_to_auth_user(user_ref)
|
||||
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):
|
||||
return {'id': user_ref['id'],
|
||||
@@ -105,8 +104,9 @@ class DbDriver(object):
|
||||
"""Create a project"""
|
||||
manager = db.user_get(context.get_admin_context(), manager_uid)
|
||||
if not manager:
|
||||
raise exception.NotFound("Project can't be created because "
|
||||
"manager %s doesn't exist" % manager_uid)
|
||||
raise exception.NotFound(_("Project can't be created because "
|
||||
"manager %s doesn't exist")
|
||||
% manager_uid)
|
||||
|
||||
# description is a required attribute
|
||||
if description is None:
|
||||
@@ -133,8 +133,8 @@ class DbDriver(object):
|
||||
try:
|
||||
project = db.project_create(context.get_admin_context(), values)
|
||||
except exception.Duplicate:
|
||||
raise exception.Duplicate("Project can't be created because "
|
||||
"project %s already exists" % name)
|
||||
raise exception.Duplicate(_("Project can't be created because "
|
||||
"project %s already exists") % name)
|
||||
|
||||
for member in members:
|
||||
db.project_add_member(context.get_admin_context(),
|
||||
@@ -155,8 +155,8 @@ class DbDriver(object):
|
||||
if manager_uid:
|
||||
manager = db.user_get(context.get_admin_context(), manager_uid)
|
||||
if not manager:
|
||||
raise exception.NotFound("Project can't be modified because "
|
||||
"manager %s doesn't exist" %
|
||||
raise exception.NotFound(_("Project can't be modified because "
|
||||
"manager %s doesn't exist") %
|
||||
manager_uid)
|
||||
values['project_manager'] = manager['id']
|
||||
if description:
|
||||
@@ -243,8 +243,8 @@ class DbDriver(object):
|
||||
def _validate_user_and_project(self, user_id, project_id):
|
||||
user = db.user_get(context.get_admin_context(), user_id)
|
||||
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)
|
||||
if not project:
|
||||
raise exception.NotFound('Project "%s" not found' % project_id)
|
||||
raise exception.NotFound(_('Project "%s" not found') % project_id)
|
||||
return user, project
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# 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
|
||||
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 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):
|
||||
if hasattr(self.__class__, '_instance'):
|
||||
raise Exception('Attempted to instantiate singleton')
|
||||
raise Exception(_('Attempted to instantiate singleton'))
|
||||
|
||||
@classmethod
|
||||
def instance(cls):
|
||||
if not hasattr(cls, '_instance'):
|
||||
inst = redis.Redis(host=FLAGS.redis_host,
|
||||
port=FLAGS.redis_port,
|
||||
db=FLAGS.redis_db)
|
||||
cls._instance = inst
|
||||
cls._instance = _StorageDict()
|
||||
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_ONELEVEL = 1 # Not implemented
|
||||
SCOPE_SUBTREE = 2
|
||||
@@ -119,6 +150,9 @@ def _match(key, value, attrs):
|
||||
"""Match a given key and value against an attribute list."""
|
||||
if key not in attrs:
|
||||
return False
|
||||
# This is a wild card search. Implemented as all or nothing for now.
|
||||
if value == "*":
|
||||
return True
|
||||
if key != "objectclass":
|
||||
return value in attrs[key]
|
||||
# it is an objectclass check, so check subclasses
|
||||
@@ -169,8 +203,6 @@ def _to_json(unencoded):
|
||||
|
||||
|
||||
class FakeLDAP(object):
|
||||
#TODO(vish): refactor this class to use a wrapper instead of accessing
|
||||
# redis directly
|
||||
"""Fake LDAP connection."""
|
||||
|
||||
def simple_bind_s(self, dn, password):
|
||||
@@ -183,14 +215,13 @@ class FakeLDAP(object):
|
||||
|
||||
def add_s(self, dn, attr):
|
||||
"""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])
|
||||
Redis.instance().hmset(key, value_dict)
|
||||
Store.instance().hmset(key, value_dict)
|
||||
|
||||
def delete_s(self, 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):
|
||||
"""Modify the object at dn using the attribute list.
|
||||
@@ -201,18 +232,18 @@ class FakeLDAP(object):
|
||||
([MOD_ADD | MOD_DELETE | MOD_REPACE], attribute, value)
|
||||
|
||||
"""
|
||||
redis = Redis.instance()
|
||||
key = "%s%s" % (self.__redis_prefix, dn)
|
||||
store = Store.instance()
|
||||
key = "%s%s" % (self.__prefix, dn)
|
||||
|
||||
for cmd, k, v in attrs:
|
||||
values = _from_json(redis.hget(key, k))
|
||||
values = _from_json(store.hget(key, k))
|
||||
if cmd == MOD_ADD:
|
||||
values.append(v)
|
||||
elif cmd == MOD_REPLACE:
|
||||
values = [v]
|
||||
else:
|
||||
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):
|
||||
"""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:
|
||||
raise NotImplementedError(str(scope))
|
||||
redis = Redis.instance()
|
||||
store = Store.instance()
|
||||
if scope == SCOPE_BASE:
|
||||
keys = ["%s%s" % (self.__redis_prefix, dn)]
|
||||
keys = ["%s%s" % (self.__prefix, dn)]
|
||||
else:
|
||||
keys = redis.keys("%s*%s" % (self.__redis_prefix, dn))
|
||||
keys = store.keys("%s*%s" % (self.__prefix, dn))
|
||||
|
||||
objects = []
|
||||
for key in keys:
|
||||
# get the attributes from redis
|
||||
attrs = redis.hgetall(key)
|
||||
# turn the values from redis into lists
|
||||
# get the attributes from the store
|
||||
attrs = store.hgetall(key)
|
||||
# turn the values from the store into lists
|
||||
# pylint: disable-msg=E1103
|
||||
attrs = dict([(k, _from_json(v))
|
||||
for k, v in attrs.iteritems()])
|
||||
@@ -244,13 +276,13 @@ class FakeLDAP(object):
|
||||
# filter the attributes by fields
|
||||
attrs = dict([(k, v) for k, v in attrs.iteritems()
|
||||
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
|
||||
if objects == []:
|
||||
raise NO_SUCH_OBJECT()
|
||||
return objects
|
||||
|
||||
@property
|
||||
def __redis_prefix(self): # pylint: disable-msg=R0201
|
||||
"""Get the prefix to use for all redis keys."""
|
||||
def __prefix(self): # pylint: disable-msg=R0201
|
||||
"""Get the prefix to use for all keys."""
|
||||
return 'ldap:'
|
||||
|
||||
@@ -32,14 +32,21 @@ from nova import flags
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
flags.DEFINE_integer('ldap_schema_version', 2,
|
||||
'Current version of the LDAP schema')
|
||||
flags.DEFINE_string('ldap_url', 'ldap://localhost',
|
||||
'Point this at your ldap server')
|
||||
flags.DEFINE_string('ldap_password', 'changeme', 'LDAP password')
|
||||
flags.DEFINE_string('ldap_user_dn', 'cn=Manager,dc=example,dc=com',
|
||||
'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_subtree', 'ou=Users,dc=example,dc=com',
|
||||
'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',
|
||||
'OU for Projects')
|
||||
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.
|
||||
"""
|
||||
|
||||
project_pattern = '(owner=*)'
|
||||
isadmin_attribute = 'isNovaAdmin'
|
||||
project_attribute = 'owner'
|
||||
project_objectclass = 'groupOfNames'
|
||||
|
||||
def __init__(self):
|
||||
"""Imports the LDAP module"""
|
||||
self.ldap = __import__('ldap')
|
||||
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):
|
||||
"""Creates the connection to LDAP"""
|
||||
@@ -89,8 +106,7 @@ class LdapDriver(object):
|
||||
|
||||
def get_user(self, uid):
|
||||
"""Retrieve user by id"""
|
||||
attr = self.__find_object(self.__uid_to_dn(uid),
|
||||
'(objectclass=novaUser)')
|
||||
attr = self.__get_ldap_user(uid)
|
||||
return self.__to_user(attr)
|
||||
|
||||
def get_user_from_access_key(self, access):
|
||||
@@ -103,18 +119,23 @@ class LdapDriver(object):
|
||||
"""Retrieve project by id"""
|
||||
dn = 'cn=%s,%s' % (pid,
|
||||
FLAGS.ldap_project_subtree)
|
||||
attr = self.__find_object(dn, '(objectclass=novaProject)')
|
||||
attr = self.__find_object(dn, LdapDriver.project_pattern)
|
||||
return self.__to_project(attr)
|
||||
|
||||
def get_users(self):
|
||||
"""Retrieve list of users"""
|
||||
attrs = self.__find_objects(FLAGS.ldap_user_subtree,
|
||||
'(objectclass=novaUser)')
|
||||
return [self.__to_user(attr) for attr in attrs]
|
||||
'(objectclass=novaUser)')
|
||||
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):
|
||||
"""Retrieve list of projects"""
|
||||
pattern = '(objectclass=novaProject)'
|
||||
pattern = LdapDriver.project_pattern
|
||||
if uid:
|
||||
pattern = "(&%s(member=%s))" % (pattern, self.__uid_to_dn(uid))
|
||||
attrs = self.__find_objects(FLAGS.ldap_project_subtree,
|
||||
@@ -125,51 +146,85 @@ class LdapDriver(object):
|
||||
"""Create a user"""
|
||||
if self.__user_exists(name):
|
||||
raise exception.Duplicate("LDAP user %s already exists" % name)
|
||||
attr = [
|
||||
('objectclass', ['person',
|
||||
'organizationalPerson',
|
||||
'inetOrgPerson',
|
||||
'novaUser']),
|
||||
('ou', [FLAGS.ldap_user_unit]),
|
||||
('uid', [name]),
|
||||
('sn', [name]),
|
||||
('cn', [name]),
|
||||
('secretKey', [secret_key]),
|
||||
('accessKey', [access_key]),
|
||||
('isAdmin', [str(is_admin).upper()]),
|
||||
]
|
||||
self.conn.add_s(self.__uid_to_dn(name), attr)
|
||||
return self.__to_user(dict(attr))
|
||||
if FLAGS.ldap_user_modify_only:
|
||||
if self.__ldap_user_exists(name):
|
||||
# Retrieve user by name
|
||||
user = self.__get_ldap_user(name)
|
||||
# Entry could be malformed, test for missing attrs.
|
||||
# Malformed entries are useless, replace attributes found.
|
||||
attr = []
|
||||
if 'secretKey' in user.keys():
|
||||
attr.append((self.ldap.MOD_REPLACE, 'secretKey',
|
||||
[secret_key]))
|
||||
else:
|
||||
attr.append((self.ldap.MOD_ADD, 'secretKey',
|
||||
[secret_key]))
|
||||
if 'accessKey' in user.keys():
|
||||
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,
|
||||
description=None, member_uids=None):
|
||||
"""Create a project"""
|
||||
if self.__project_exists(name):
|
||||
raise exception.Duplicate("Project can't be created because "
|
||||
"project %s already exists" % name)
|
||||
raise exception.Duplicate(_("Project can't be created because "
|
||||
"project %s already exists") % name)
|
||||
if not self.__user_exists(manager_uid):
|
||||
raise exception.NotFound("Project can't be created because "
|
||||
"manager %s doesn't exist" % manager_uid)
|
||||
raise exception.NotFound(_("Project can't be created because "
|
||||
"manager %s doesn't exist")
|
||||
% manager_uid)
|
||||
manager_dn = self.__uid_to_dn(manager_uid)
|
||||
# description is a required attribute
|
||||
if description is None:
|
||||
description = name
|
||||
members = []
|
||||
if member_uids != None:
|
||||
if member_uids is not None:
|
||||
for member_uid in member_uids:
|
||||
if not self.__user_exists(member_uid):
|
||||
raise exception.NotFound("Project can't be created "
|
||||
"because user %s doesn't exist"
|
||||
raise exception.NotFound(_("Project can't be created "
|
||||
"because user %s doesn't exist")
|
||||
% member_uid)
|
||||
members.append(self.__uid_to_dn(member_uid))
|
||||
# always add the manager as a member because members is required
|
||||
if not manager_dn in members:
|
||||
members.append(manager_dn)
|
||||
attr = [
|
||||
('objectclass', ['novaProject']),
|
||||
('objectclass', [LdapDriver.project_objectclass]),
|
||||
('cn', [name]),
|
||||
('description', [description]),
|
||||
('projectManager', [manager_dn]),
|
||||
(LdapDriver.project_attribute, [manager_dn]),
|
||||
('member', members)]
|
||||
self.conn.add_s('cn=%s,%s' % (name, FLAGS.ldap_project_subtree), attr)
|
||||
return self.__to_project(dict(attr))
|
||||
@@ -181,11 +236,12 @@ class LdapDriver(object):
|
||||
attr = []
|
||||
if manager_uid:
|
||||
if not self.__user_exists(manager_uid):
|
||||
raise exception.NotFound("Project can't be modified because "
|
||||
"manager %s doesn't exist" %
|
||||
manager_uid)
|
||||
raise exception.NotFound(_("Project can't be modified because "
|
||||
"manager %s doesn't exist")
|
||||
% 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:
|
||||
attr.append((self.ldap.MOD_REPLACE, 'description', description))
|
||||
self.conn.modify_s('cn=%s,%s' % (project_id,
|
||||
@@ -245,10 +301,9 @@ class LdapDriver(object):
|
||||
return roles
|
||||
else:
|
||||
project_dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree)
|
||||
roles = self.__find_objects(project_dn,
|
||||
'(&(&(objectclass=groupOfNames)'
|
||||
'(!(objectclass=novaProject)))'
|
||||
'(member=%s))' % self.__uid_to_dn(uid))
|
||||
query = ('(&(&(objectclass=groupOfNames)(!%s))(member=%s))' %
|
||||
(LdapDriver.project_pattern, self.__uid_to_dn(uid)))
|
||||
roles = self.__find_objects(project_dn, query)
|
||||
return [role['cn'][0] for role in roles]
|
||||
|
||||
def delete_user(self, uid):
|
||||
@@ -256,7 +311,25 @@ class LdapDriver(object):
|
||||
if not self.__user_exists(uid):
|
||||
raise exception.NotFound("User %s doesn't exist" % 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):
|
||||
"""Delete a project"""
|
||||
@@ -265,7 +338,7 @@ class LdapDriver(object):
|
||||
self.__delete_group(project_dn)
|
||||
|
||||
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:
|
||||
return
|
||||
attr = []
|
||||
@@ -274,16 +347,27 @@ class LdapDriver(object):
|
||||
if secret_key:
|
||||
attr.append((self.ldap.MOD_REPLACE, 'secretKey', secret_key))
|
||||
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)
|
||||
|
||||
def __user_exists(self, uid):
|
||||
"""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):
|
||||
"""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):
|
||||
"""Find an object by dn and query"""
|
||||
@@ -318,24 +402,26 @@ class LdapDriver(object):
|
||||
|
||||
def __find_role_dns(self, tree):
|
||||
"""Find dns of role objects in given tree"""
|
||||
return self.__find_dns(tree,
|
||||
'(&(objectclass=groupOfNames)(!(objectclass=novaProject)))')
|
||||
query = ('(&(objectclass=groupOfNames)(!%s))' %
|
||||
LdapDriver.project_pattern)
|
||||
return self.__find_dns(tree, query)
|
||||
|
||||
def __find_group_dns_with_member(self, tree, uid):
|
||||
"""Find dns of group objects in a given tree that contain member"""
|
||||
dns = self.__find_dns(tree,
|
||||
'(&(objectclass=groupOfNames)(member=%s))' %
|
||||
self.__uid_to_dn(uid))
|
||||
query = ('(&(objectclass=groupOfNames)(member=%s))' %
|
||||
self.__uid_to_dn(uid))
|
||||
dns = self.__find_dns(tree, query)
|
||||
return dns
|
||||
|
||||
def __group_exists(self, dn):
|
||||
"""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
|
||||
def __role_to_dn(role, project_id=None):
|
||||
"""Convert role to corresponding dn"""
|
||||
if project_id == None:
|
||||
if project_id is None:
|
||||
return FLAGS.__getitem__("ldap_%s" % role).value
|
||||
else:
|
||||
return 'cn=%s,cn=%s,%s' % (role,
|
||||
@@ -349,11 +435,12 @@ class LdapDriver(object):
|
||||
raise exception.Duplicate("Group can't be created because "
|
||||
"group %s already exists" % name)
|
||||
members = []
|
||||
if member_uids != None:
|
||||
if member_uids is not None:
|
||||
for member_uid in member_uids:
|
||||
if not self.__user_exists(member_uid):
|
||||
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))
|
||||
dn = self.__uid_to_dn(uid)
|
||||
if not dn in members:
|
||||
@@ -369,25 +456,25 @@ class LdapDriver(object):
|
||||
"""Check if user is in group"""
|
||||
if not self.__user_exists(uid):
|
||||
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):
|
||||
return False
|
||||
res = self.__find_object(group_dn,
|
||||
'(member=%s)' % self.__uid_to_dn(uid),
|
||||
self.ldap.SCOPE_BASE)
|
||||
return res != None
|
||||
return res is not None
|
||||
|
||||
def __add_to_group(self, uid, group_dn):
|
||||
"""Add user to group"""
|
||||
if not self.__user_exists(uid):
|
||||
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):
|
||||
raise exception.NotFound("The group at dn %s doesn't exist" %
|
||||
(group_dn,))
|
||||
group_dn)
|
||||
if self.__is_in_group(uid, group_dn):
|
||||
raise exception.Duplicate("User %s is already a member of "
|
||||
"the group %s" % (uid, group_dn))
|
||||
raise exception.Duplicate(_("User %s is already a member of "
|
||||
"the group %s") % (uid, group_dn))
|
||||
attr = [(self.ldap.MOD_ADD, 'member', self.__uid_to_dn(uid))]
|
||||
self.conn.modify_s(group_dn, attr)
|
||||
|
||||
@@ -395,16 +482,16 @@ class LdapDriver(object):
|
||||
"""Remove user from group"""
|
||||
if not self.__group_exists(group_dn):
|
||||
raise exception.NotFound("The group at dn %s doesn't exist" %
|
||||
(group_dn,))
|
||||
group_dn)
|
||||
if not self.__user_exists(uid):
|
||||
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):
|
||||
raise exception.NotFound("User %s is not a member of the group" %
|
||||
(uid,))
|
||||
uid)
|
||||
# NOTE(vish): remove user from group and any sub_groups
|
||||
sub_dns = self.__find_group_dns_with_member(
|
||||
group_dn, uid)
|
||||
sub_dns = self.__find_group_dns_with_member(group_dn, uid)
|
||||
for sub_dn in sub_dns:
|
||||
self.__safe_remove_from_group(uid, sub_dn)
|
||||
|
||||
@@ -415,15 +502,15 @@ class LdapDriver(object):
|
||||
try:
|
||||
self.conn.modify_s(group_dn, attr)
|
||||
except self.ldap.OBJECT_CLASS_VIOLATION:
|
||||
logging.debug("Attempted to remove the last member of a group. "
|
||||
"Deleting the group at %s instead.", group_dn)
|
||||
logging.debug(_("Attempted to remove the last member of a group. "
|
||||
"Deleting the group at %s instead."), group_dn)
|
||||
self.__delete_group(group_dn)
|
||||
|
||||
def __remove_from_all(self, uid):
|
||||
"""Remove user from all roles and projects"""
|
||||
if not self.__user_exists(uid):
|
||||
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(
|
||||
FLAGS.role_project_subtree, uid)
|
||||
for role_dn in role_dns:
|
||||
@@ -436,7 +523,8 @@ class LdapDriver(object):
|
||||
def __delete_group(self, group_dn):
|
||||
"""Delete Group"""
|
||||
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)
|
||||
|
||||
def __delete_roles(self, project_dn):
|
||||
@@ -447,24 +535,29 @@ class LdapDriver(object):
|
||||
@staticmethod
|
||||
def __to_user(attr):
|
||||
"""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 {
|
||||
'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):
|
||||
"""Convert ldap attributes to Project object"""
|
||||
if attr == None:
|
||||
if attr is None:
|
||||
return None
|
||||
member_dns = attr.get('member', [])
|
||||
return {
|
||||
'id': 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],
|
||||
'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]
|
||||
|
||||
@staticmethod
|
||||
def __uid_to_dn(dn):
|
||||
def __uid_to_dn(uid):
|
||||
"""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):
|
||||
|
||||
@@ -64,12 +64,9 @@ flags.DEFINE_string('credential_key_file', 'pk.pem',
|
||||
'Filename of private key in credentials zip')
|
||||
flags.DEFINE_string('credential_cert_file', 'cert.pem',
|
||||
'Filename of certificate in credentials zip')
|
||||
flags.DEFINE_string('credential_rc_file', 'novarc',
|
||||
'Filename of rc in credentials zip')
|
||||
flags.DEFINE_string('credential_cert_subject',
|
||||
'/C=US/ST=California/L=MountainView/O=AnsoLabs/'
|
||||
'OU=NovaDev/CN=%s-%s',
|
||||
'Subject for certificate for users')
|
||||
flags.DEFINE_string('credential_rc_file', '%src',
|
||||
'Filename of rc in credentials zip, %s will be '
|
||||
'replaced by name of the region (nova by default)')
|
||||
flags.DEFINE_string('auth_driver', 'nova.auth.dbdriver.DbDriver',
|
||||
'Driver that auth manager uses')
|
||||
|
||||
@@ -257,12 +254,12 @@ class AuthManager(object):
|
||||
# TODO(vish): check for valid timestamp
|
||||
(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)
|
||||
logging.info('user: %r', user)
|
||||
if user == None:
|
||||
raise exception.NotFound('No user found for access key %s' %
|
||||
access_key)
|
||||
raise exception.NotFound(_('No user found for access key %s')
|
||||
% access_key)
|
||||
|
||||
# NOTE(vish): if we stop using project name as id we need better
|
||||
# logic to find a default project for user
|
||||
@@ -271,12 +268,12 @@ class AuthManager(object):
|
||||
|
||||
project = self.get_project(project_id)
|
||||
if project == None:
|
||||
raise exception.NotFound('No project called %s could be found' %
|
||||
project_id)
|
||||
raise exception.NotFound(_('No project called %s could be found')
|
||||
% project_id)
|
||||
if not self.is_admin(user) and not self.is_project_member(user,
|
||||
project):
|
||||
raise exception.NotFound('User %s is not a member of project %s' %
|
||||
(user.id, project.id))
|
||||
raise exception.NotFound(_('User %s is not a member of project %s')
|
||||
% (user.id, project.id))
|
||||
if check_type == 's3':
|
||||
sign = signer.Signer(user.secret.encode())
|
||||
expected_signature = sign.s3_authorization(headers, verb, path)
|
||||
@@ -284,7 +281,7 @@ class AuthManager(object):
|
||||
logging.debug('expected_signature: %s', expected_signature)
|
||||
logging.debug('signature: %s', signature)
|
||||
if signature != expected_signature:
|
||||
raise exception.NotAuthorized('Signature does not match')
|
||||
raise exception.NotAuthorized(_('Signature does not match'))
|
||||
elif check_type == 'ec2':
|
||||
# NOTE(vish): hmac can't handle unicode, so encode ensures that
|
||||
# secret isn't unicode
|
||||
@@ -294,7 +291,7 @@ class AuthManager(object):
|
||||
logging.debug('expected_signature: %s', expected_signature)
|
||||
logging.debug('signature: %s', signature)
|
||||
if signature != expected_signature:
|
||||
raise exception.NotAuthorized('Signature does not match')
|
||||
raise exception.NotAuthorized(_('Signature does not match'))
|
||||
return (user, project)
|
||||
|
||||
def get_access_key(self, user, project):
|
||||
@@ -364,7 +361,7 @@ class AuthManager(object):
|
||||
with self.driver() as drv:
|
||||
if role == 'projectmanager':
|
||||
if not project:
|
||||
raise exception.Error("Must specify project")
|
||||
raise exception.Error(_("Must specify project"))
|
||||
return self.is_project_manager(user, project)
|
||||
|
||||
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.
|
||||
"""
|
||||
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:
|
||||
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:
|
||||
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(),
|
||||
Project.safe_id(project))
|
||||
Project.safe_id(project), False)
|
||||
|
||||
if not network_ref['vpn_public_port']:
|
||||
raise exception.NotFound('project network data has not been set')
|
||||
if not network_ref:
|
||||
return (None, None)
|
||||
return (network_ref['vpn_public_address'],
|
||||
network_ref['vpn_public_port'])
|
||||
|
||||
@@ -624,27 +621,41 @@ class AuthManager(object):
|
||||
with self.driver() as drv:
|
||||
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"""
|
||||
if not isinstance(user, User):
|
||||
user = self.get_user(user)
|
||||
if project is None:
|
||||
project = user.id
|
||||
pid = Project.safe_id(project)
|
||||
rc = self.__generate_rc(user.access, user.secret, pid)
|
||||
private_key, signed_cert = self._generate_x509_cert(user.id, pid)
|
||||
private_key, signed_cert = crypto.generate_x509_cert(user.id, pid)
|
||||
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
zf = os.path.join(tmpdir, "temp.zip")
|
||||
zippy = zipfile.ZipFile(zf, 'w')
|
||||
zippy.writestr(FLAGS.credential_rc_file, rc)
|
||||
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_cert_file, signed_cert)
|
||||
|
||||
try:
|
||||
(vpn_ip, vpn_port) = self.get_project_vpn_data(project)
|
||||
except exception.NotFound:
|
||||
vpn_ip = None
|
||||
(vpn_ip, vpn_port) = self.get_project_vpn_data(project)
|
||||
if vpn_ip:
|
||||
configfile = open(FLAGS.vpn_client_template, "r")
|
||||
s = string.Template(configfile.read())
|
||||
@@ -655,10 +666,9 @@ class AuthManager(object):
|
||||
port=vpn_port)
|
||||
zippy.writestr(FLAGS.credential_vpn_file, config)
|
||||
else:
|
||||
logging.warn("No vpn data for project %s" %
|
||||
pid)
|
||||
logging.warn(_("No vpn data for project %s"), pid)
|
||||
|
||||
zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(user.id))
|
||||
zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(pid))
|
||||
zippy.close()
|
||||
with open(zf, 'rb') as f:
|
||||
read_buffer = f.read()
|
||||
@@ -666,38 +676,38 @@ class AuthManager(object):
|
||||
shutil.rmtree(tmpdir)
|
||||
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"""
|
||||
if not isinstance(user, User):
|
||||
user = self.get_user(user)
|
||||
if project is None:
|
||||
project = user.id
|
||||
pid = Project.safe_id(project)
|
||||
return self.__generate_rc(user.access, user.secret, pid)
|
||||
return self.__generate_rc(user.access, user.secret, pid, use_dmz)
|
||||
|
||||
@staticmethod
|
||||
def __generate_rc(access, secret, pid):
|
||||
def __generate_rc(access, secret, pid, use_dmz=True, host=None):
|
||||
"""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 = rc % {'access': access,
|
||||
'project': pid,
|
||||
'secret': secret,
|
||||
'ec2': FLAGS.ec2_url,
|
||||
's3': 'http://%s:%s' % (FLAGS.s3_host, FLAGS.s3_port),
|
||||
'ec2': '%s://%s:%s%s' % (FLAGS.ec2_prefix,
|
||||
cc_host,
|
||||
FLAGS.cc_port,
|
||||
FLAGS.ec2_suffix),
|
||||
's3': 'http://%s:%s' % (s3_host, FLAGS.s3_port),
|
||||
'nova': FLAGS.ca_file,
|
||||
'cert': FLAGS.credential_cert_file,
|
||||
'key': FLAGS.credential_key_file}
|
||||
return rc
|
||||
|
||||
def _generate_x509_cert(self, uid, pid):
|
||||
"""Generate x509 cert for user"""
|
||||
(private_key, csr) = crypto.generate_x509_cert(
|
||||
self.__cert_subject(uid))
|
||||
# TODO(joshua): This should be async call back to the cloud controller
|
||||
signed_cert = crypto.sign_csr(csr, pid)
|
||||
return (private_key, signed_cert)
|
||||
|
||||
@staticmethod
|
||||
def __cert_subject(uid):
|
||||
"""Helper to generate cert subject"""
|
||||
return FLAGS.credential_cert_subject % (uid, utils.isotime())
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#
|
||||
# Person object for Nova
|
||||
# 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
|
||||
)
|
||||
|
||||
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 (
|
||||
novaAttrs:4
|
||||
NAME 'isAdmin'
|
||||
DESC 'Is user an administrator?'
|
||||
NAME 'isNovaAdmin'
|
||||
DESC 'Is user an nova administrator?'
|
||||
EQUALITY booleanMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
|
||||
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 (
|
||||
novaOCs:1
|
||||
NAME 'novaUser'
|
||||
DESC 'access and secret keys'
|
||||
AUXILIARY
|
||||
MUST ( uid )
|
||||
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 )
|
||||
MAY ( accessKey $ secretKey $ isNovaAdmin )
|
||||
)
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
#
|
||||
# Person object for Nova
|
||||
# inetorgperson with extra attributes
|
||||
# Author: Vishvananda Ishaya <vishvananda@yahoo.com>
|
||||
# Modified for strict RFC 4512 compatibility by: Ryan Lane <ryan@ryandlane.com>
|
||||
# Schema version: 2
|
||||
# Authors: Vishvananda Ishaya <vishvananda@gmail.com>
|
||||
# Ryan Lane <rlane@wikimedia.org>
|
||||
#
|
||||
# using internet experimental oid arc as per BP64 3.1
|
||||
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.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 'isAdmin' DESC 'Is user an 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.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 ) )
|
||||
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 )
|
||||
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 ) )
|
||||
|
||||
118
nova/auth/opendj.sh
Executable file
118
nova/auth/opendj.sh
Executable 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
|
||||
@@ -22,7 +22,7 @@ apt-get install -y slapd ldap-utils python-ldap
|
||||
|
||||
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/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
|
||||
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/cosine.schema
|
||||
include /etc/ldap/schema/inetorgperson.schema
|
||||
include /etc/ldap/schema/openssh-lpk_openldap.schema
|
||||
include /etc/ldap/schema/nova.schema
|
||||
pidfile /var/run/slapd/slapd.pid
|
||||
argsfile /var/run/slapd/slapd.args
|
||||
|
||||
@@ -27,23 +27,26 @@ import traceback
|
||||
|
||||
|
||||
class ProcessExecutionError(IOError):
|
||||
|
||||
def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None,
|
||||
description=None):
|
||||
if description is None:
|
||||
description = "Unexpected error while running command."
|
||||
description = _("Unexpected error while running command.")
|
||||
if exit_code is None:
|
||||
exit_code = '-'
|
||||
message = "%s\nCommand: %s\nExit code: %s\nStdout: %r\nStderr: %r" % (
|
||||
description, cmd, exit_code, stdout, stderr)
|
||||
message = _("%s\nCommand: %s\nExit code: %s\nStdout: %r\nStderr: %r")\
|
||||
% (description, cmd, exit_code, stdout, stderr)
|
||||
IOError.__init__(self, message)
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
|
||||
def __init__(self, message=None):
|
||||
super(Error, self).__init__(message)
|
||||
|
||||
|
||||
class ApiError(Error):
|
||||
|
||||
def __init__(self, message='Unknown', code='Unknown'):
|
||||
self.message = message
|
||||
self.code = code
|
||||
@@ -81,7 +84,7 @@ def wrap_exception(f):
|
||||
except Exception, e:
|
||||
if not isinstance(e, Error):
|
||||
#exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
logging.exception('Uncaught exception')
|
||||
logging.exception(_('Uncaught exception'))
|
||||
#logging.error(traceback.extract_stack(exc_traceback))
|
||||
raise Error(str(e))
|
||||
raise
|
||||
|
||||
59
nova/fakememcache.py
Normal file
59
nova/fakememcache.py
Normal 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
|
||||
@@ -25,6 +25,10 @@ from carrot.backends import base
|
||||
from eventlet import greenthread
|
||||
|
||||
|
||||
EXCHANGES = {}
|
||||
QUEUES = {}
|
||||
|
||||
|
||||
class Message(base.BaseMessage):
|
||||
pass
|
||||
|
||||
@@ -37,12 +41,12 @@ class Exchange(object):
|
||||
self._routes = {}
|
||||
|
||||
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)
|
||||
routing_key = routing_key.split('.')[0]
|
||||
if routing_key in self._routes:
|
||||
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)
|
||||
|
||||
def bind(self, callback, routing_key):
|
||||
@@ -68,81 +72,63 @@ class Queue(object):
|
||||
return self._queue.get()
|
||||
|
||||
|
||||
class Backend(object):
|
||||
""" Singleton backend for testing """
|
||||
class __impl(base.BaseBackend):
|
||||
def __init__(self, *args, **kwargs):
|
||||
#super(__impl, self).__init__(*args, **kwargs)
|
||||
self._exchanges = {}
|
||||
self._queues = {}
|
||||
class Backend(base.BaseBackend):
|
||||
def queue_declare(self, queue, **kwargs):
|
||||
global QUEUES
|
||||
if queue not in QUEUES:
|
||||
logging.debug(_('Declaring queue %s'), queue)
|
||||
QUEUES[queue] = Queue(queue)
|
||||
|
||||
def _reset_all(self):
|
||||
self._exchanges = {}
|
||||
self._queues = {}
|
||||
def exchange_declare(self, exchange, type, *args, **kwargs):
|
||||
global EXCHANGES
|
||||
if exchange not in EXCHANGES:
|
||||
logging.debug(_('Declaring exchange %s'), exchange)
|
||||
EXCHANGES[exchange] = Exchange(exchange, type)
|
||||
|
||||
def queue_declare(self, queue, **kwargs):
|
||||
if queue not in self._queues:
|
||||
logging.debug('Declaring queue %s', queue)
|
||||
self._queues[queue] = Queue(queue)
|
||||
def queue_bind(self, queue, exchange, routing_key, **kwargs):
|
||||
global EXCHANGES
|
||||
global QUEUES
|
||||
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):
|
||||
if exchange not in self._exchanges:
|
||||
logging.debug('Declaring exchange %s', exchange)
|
||||
self._exchanges[exchange] = Exchange(exchange, type)
|
||||
def declare_consumer(self, queue, callback, *args, **kwargs):
|
||||
self.current_queue = queue
|
||||
self.current_callback = callback
|
||||
|
||||
def queue_bind(self, queue, exchange, routing_key, **kwargs):
|
||||
logging.debug('Binding %s to %s with key %s',
|
||||
queue, exchange, routing_key)
|
||||
self._exchanges[exchange].bind(self._queues[queue].push,
|
||||
routing_key)
|
||||
def consume(self, limit=None):
|
||||
while True:
|
||||
item = self.get(self.current_queue)
|
||||
if item:
|
||||
self.current_callback(item)
|
||||
raise StopIteration()
|
||||
greenthread.sleep(0)
|
||||
|
||||
def declare_consumer(self, queue, callback, *args, **kwargs):
|
||||
self.current_queue = queue
|
||||
self.current_callback = callback
|
||||
def get(self, queue, no_ack=False):
|
||||
global QUEUES
|
||||
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):
|
||||
while True:
|
||||
item = self.get(self.current_queue)
|
||||
if item:
|
||||
self.current_callback(item)
|
||||
raise StopIteration()
|
||||
greenthread.sleep(0)
|
||||
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 get(self, queue, no_ack=False):
|
||||
if not queue in self._queues or not self._queues[queue].size():
|
||||
return None
|
||||
(message_data, content_type, content_encoding) = \
|
||||
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 publish(self, message, exchange, routing_key, **kwargs):
|
||||
global EXCHANGES
|
||||
if exchange in EXCHANGES:
|
||||
EXCHANGES[exchange].publish(message, routing_key=routing_key)
|
||||
|
||||
|
||||
def reset_all():
|
||||
Backend()._reset_all()
|
||||
global EXCHANGES
|
||||
global QUEUES
|
||||
EXCHANGES = {}
|
||||
QUEUES = {}
|
||||
|
||||
@@ -29,6 +29,8 @@ import sys
|
||||
|
||||
import gflags
|
||||
|
||||
from nova import utils
|
||||
|
||||
|
||||
class FlagValues(gflags.FlagValues):
|
||||
"""Extension of gflags.FlagValues that allows undefined and runtime flags.
|
||||
@@ -159,6 +161,7 @@ class StrWrapper(object):
|
||||
return str(val)
|
||||
raise KeyError(name)
|
||||
|
||||
|
||||
FLAGS = FlagValues()
|
||||
gflags.FLAGS = FLAGS
|
||||
gflags.DEFINE_flag(gflags.HelpFlag(), FLAGS)
|
||||
@@ -183,6 +186,12 @@ DEFINE_list = _wrapper(gflags.DEFINE_list)
|
||||
DEFINE_spaceseplist = _wrapper(gflags.DEFINE_spaceseplist)
|
||||
DEFINE_multistring = _wrapper(gflags.DEFINE_multistring)
|
||||
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):
|
||||
@@ -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_secret_access_key', 'admin', 'AWS Access Key')
|
||||
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('scheduler_topic', 'scheduler',
|
||||
'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_max_retries', 12, 'rabbit connection attempts')
|
||||
DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to')
|
||||
DEFINE_string('ec2_url', 'http://127.0.0.1:8773/services/Cloud',
|
||||
'Url to ec2 api server')
|
||||
DEFINE_string('ec2_prefix', 'http', 'prefix for ec2')
|
||||
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',
|
||||
'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',
|
||||
'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',
|
||||
'-key',
|
||||
'Suffix to add to project name for vpn key')
|
||||
'-vpn',
|
||||
'Suffix to add to project name for vpn key and secgroups')
|
||||
|
||||
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')
|
||||
|
||||
# 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.')
|
||||
|
||||
DEFINE_string('host', socket.gethostname(),
|
||||
|
||||
Binary file not shown.
@@ -35,7 +35,6 @@ try :
|
||||
from nova import flags
|
||||
from nova import quota
|
||||
from nova import utils
|
||||
from nova import process
|
||||
from nova.auth import manager
|
||||
from nova.cloudpipe import pipelib
|
||||
from nova import rpc
|
||||
@@ -177,10 +176,11 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
|
||||
def test03(self):
|
||||
"""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.manager.live_migration,
|
||||
self.assertRaises(exception.ProcessExecutionError,
|
||||
self.manager._live_migration,
|
||||
self.ctxt,
|
||||
self.instance1,
|
||||
'host2')
|
||||
|
||||
@@ -195,6 +195,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
|
||||
|
||||
self.assertRaises(TypeError,
|
||||
self.manager._post_live_migration,
|
||||
self.ctxt,
|
||||
"dummy string",
|
||||
'host2')
|
||||
|
||||
@@ -202,7 +203,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
|
||||
"""05: db.instance_get_fixed_address return 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)
|
||||
c2 = (0 <= sys.stderr.buffer.find('fixed_ip is not found'))
|
||||
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') )
|
||||
self.assertRaises(exception.NotFound,
|
||||
self.manager._post_live_migration,
|
||||
self.ctxt,
|
||||
self.instance1,
|
||||
'host2')
|
||||
|
||||
@@ -222,6 +224,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
|
||||
db.instance_get_fixed_address = Mock( side_effect=TypeError('ERR') )
|
||||
self.assertRaises(TypeError,
|
||||
self.manager._post_live_migration,
|
||||
self.ctxt,
|
||||
self.instance1,
|
||||
'host1')
|
||||
|
||||
@@ -231,6 +234,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
|
||||
db.fixed_ip_update = Mock( side_effect=exception.NotFound('ERR') )
|
||||
self.assertRaises(exception.NotFound,
|
||||
self.manager._post_live_migration,
|
||||
self.ctxt,
|
||||
self.instance1,
|
||||
'host1')
|
||||
|
||||
@@ -239,6 +243,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
|
||||
db.fixed_ip_update = Mock( side_effect=exception.NotAuthorized('ERR') )
|
||||
self.assertRaises(exception.NotAuthorized,
|
||||
self.manager._post_live_migration,
|
||||
self.ctxt,
|
||||
self.instance1,
|
||||
'host1')
|
||||
|
||||
@@ -247,6 +252,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
|
||||
db.fixed_ip_update = Mock( side_effect=TypeError('ERR') )
|
||||
self.assertRaises(TypeError,
|
||||
self.manager._post_live_migration,
|
||||
self.ctxt,
|
||||
self.instance1,
|
||||
'host1')
|
||||
|
||||
@@ -256,6 +262,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
|
||||
db.fixed_ip_get_network = Mock( side_effect=exception.NotFound('ERR') )
|
||||
self.assertRaises(exception.NotFound,
|
||||
self.manager._post_live_migration,
|
||||
self.ctxt,
|
||||
self.instance1,
|
||||
'host1')
|
||||
|
||||
@@ -268,6 +275,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
|
||||
db.fixed_ip_get_network = Mock( side_effect=TypeError('ERR') )
|
||||
self.assertRaises(TypeError,
|
||||
self.manager._post_live_migration,
|
||||
self.ctxt,
|
||||
self.instance1,
|
||||
'host1')
|
||||
|
||||
@@ -276,13 +284,14 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
|
||||
db.network_update = Mock( side_effect=TypeError('ERR') )
|
||||
self.assertRaises(TypeError,
|
||||
self.manager._post_live_migration,
|
||||
self.ctxt,
|
||||
self.instance1,
|
||||
'host1')
|
||||
|
||||
def test14(self):
|
||||
"""14: db.instance_get_floating_address raises NotFound. """
|
||||
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)
|
||||
c2 = (0 <= sys.stderr.buffer.find('doesnt have floating_ip'))
|
||||
self.assertEqual(c1 and c2, True)
|
||||
@@ -292,7 +301,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
|
||||
"""15: db.instance_get_floating_address returns 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)
|
||||
c2 = (0 <= sys.stderr.buffer.find('floating_ip is not found'))
|
||||
self.assertEqual(c1 and c2, True)
|
||||
@@ -301,7 +310,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
|
||||
"""16: db.instance_get_floating_address raises NotFound. """
|
||||
|
||||
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)
|
||||
c2 = (0 <= sys.stderr.buffer.find('doesnt have floating_ip'))
|
||||
self.assertEqual(c1 and c2, True)
|
||||
@@ -309,7 +318,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
|
||||
def test17(self):
|
||||
"""17: db.instance_get_floating_address raises Unknown exception. """
|
||||
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)
|
||||
c2 = (0 <= sys.stderr.buffer.find('Live migration: Unexpected error'))
|
||||
self.assertEqual(c1 and c2, True)
|
||||
@@ -319,7 +328,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
|
||||
"""18: db.floating_ip_get_by_address raises NotFound """
|
||||
|
||||
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)
|
||||
c2 = (0 <= sys.stderr.buffer.find('doesnt have floating_ip'))
|
||||
self.assertEqual(c1 and c2, True)
|
||||
@@ -327,7 +336,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
|
||||
def test19(self):
|
||||
"""19: db.floating_ip_get_by_address raises Unknown exception. """
|
||||
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)
|
||||
c2 = (0 <= sys.stderr.buffer.find('Live migration: Unexpected error'))
|
||||
self.assertEqual(c1 and c2, True)
|
||||
@@ -337,7 +346,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
|
||||
"""20: db.floating_ip_update raises Unknown exception.
|
||||
"""
|
||||
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)
|
||||
c2 = (0 <= sys.stderr.buffer.find('Live migration: Unexpected error'))
|
||||
self.assertEqual(c1 and c2, True)
|
||||
@@ -348,6 +357,7 @@ class LibvirtConnectionTestFunctions(unittest.TestCase):
|
||||
db.instance_update = Mock(side_effect=TypeError("ERR"))
|
||||
self.assertRaises(TypeError,
|
||||
self.manager._post_live_migration,
|
||||
self.ctxt,
|
||||
self.instance1,
|
||||
'host1')
|
||||
|
||||
|
||||
@@ -256,73 +256,35 @@ class NovaManageTestFunctions(unittest.TestCase):
|
||||
self.assertRaises(TypeError, self.instanceCmds.live_migration, 'i-xxx' )
|
||||
|
||||
def test11(self):
|
||||
"""11: nova-manage instances live_migration ec2_id host,
|
||||
where hostname is invalid
|
||||
"""
|
||||
db.host_get_by_name = Mock( side_effect=exception.NotFound('ERR') )
|
||||
self.assertRaises(exception.NotFound, self.instanceCmds.live_migration, 'i-xxx', 'host1' )
|
||||
"""11: nova-manage instances live_migration ec2_id(invalid id) host"""
|
||||
|
||||
db.instance_get_by_internal_id = Mock( side_effect=exception.NotFound('ERR') )
|
||||
try :
|
||||
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):
|
||||
"""12: nova-manage instances live_migration ec2_id(invalid id) host"""
|
||||
|
||||
db.host_get_by_name = Mock(return_value = self.host1)
|
||||
db.instance_get_by_internal_id = Mock( side_effect=exception.NotFound('ERR') )
|
||||
|
||||
self.assertRaises(exception.NotFound, self.instanceCmds.live_migration, 'i-xxx', 'host1' )
|
||||
"""12: nova-manage instances live_migration ec2_id host
|
||||
and db.instance_get_by_internal_id raises unexpected exceptioin.
|
||||
"""
|
||||
db.instance_get_by_internal_id = Mock( side_effect=TypeError('ERR') )
|
||||
self.assertRaises(TypeError, self.instanceCmds.live_migration, 'i-xxx' )
|
||||
|
||||
def test13(self):
|
||||
"""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.
|
||||
"""
|
||||
db.host_get_by_name = Mock(return_value = self.host1)
|
||||
db.instance_get_by_internal_id = Mock( return_value = self.instance3 )
|
||||
rpc.call = Mock(return_value = rpc.RemoteError(TypeError, 'val', 'traceback'))
|
||||
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.
|
||||
"""
|
||||
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')
|
||||
c1 = (0 <= self.stdout.buffer.find('Finished all procedure') )
|
||||
self.assertEqual( c1, True )
|
||||
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
"""common terminating method. """
|
||||
|
||||
@@ -55,12 +55,15 @@ class tmpStdout:
|
||||
|
||||
class SchedulerTestFunctions(unittest.TestCase):
|
||||
|
||||
manager = None
|
||||
|
||||
# 共通の初期化処理
|
||||
def setUp(self):
|
||||
"""common init method. """
|
||||
|
||||
self.host = 'openstack2-api'
|
||||
self.manager = SchedulerManager(host=self.host)
|
||||
if self.manager is None:
|
||||
self.manager = SchedulerManager(host=self.host)
|
||||
|
||||
self.setTestData()
|
||||
self.setMocks()
|
||||
@@ -72,6 +75,7 @@ class SchedulerTestFunctions(unittest.TestCase):
|
||||
self.host1.__setitem__('vcpus', 5)
|
||||
self.host1.__setitem__('memory_mb', 20480)
|
||||
self.host1.__setitem__('local_gb', 876)
|
||||
self.host1.__setitem__('cpu_info', 1)
|
||||
|
||||
self.host2 = Host()
|
||||
self.host2.__setitem__('name', 'host2')
|
||||
@@ -86,37 +90,43 @@ class SchedulerTestFunctions(unittest.TestCase):
|
||||
self.instance1 = Instance()
|
||||
for key, val in [ ('id', 1), ('host', 'host1'), ('hostname', 'i-12345'),
|
||||
('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.instance2 = Instance()
|
||||
for key, val in [ ('id', 2), ('host', 'host1'), ('hostname', 'i-12345'),
|
||||
('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.instance3 = Instance()
|
||||
for key, val in [ ('id', 3), ('host', 'host1'), ('hostname', 'i-12345'),
|
||||
('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.instance4 = Instance()
|
||||
for key, val in [ ('id', 4), ('host', 'host2'), ('hostname', 'i-12345'),
|
||||
('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.instance5 = Instance()
|
||||
for key, val in [ ('id', 5), ('host', 'host2'), ('hostname', 'i-12345'),
|
||||
('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.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'),
|
||||
('vcpus', 3), ('memory_mb', 1024), ('local_gb', 5) ]:
|
||||
self.instance6.__setitem__(key, val)
|
||||
@@ -129,7 +139,8 @@ class SchedulerTestFunctions(unittest.TestCase):
|
||||
|
||||
self.instance8 = Instance()
|
||||
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) ]:
|
||||
self.instance8.__setitem__(key, val)
|
||||
|
||||
@@ -138,6 +149,10 @@ class SchedulerTestFunctions(unittest.TestCase):
|
||||
('topic', 'compute')]:
|
||||
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):
|
||||
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] )
|
||||
|
||||
# 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] )
|
||||
self.manager.service_ip_up = Mock(return_value = True)
|
||||
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):
|
||||
"""check result format of show_host_resource """
|
||||
@@ -259,9 +275,12 @@ class SchedulerTestFunctions(unittest.TestCase):
|
||||
|
||||
db.instance_get = Mock(return_value = self.instance6)
|
||||
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:
|
||||
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)
|
||||
return False
|
||||
|
||||
@@ -271,9 +290,9 @@ class SchedulerTestFunctions(unittest.TestCase):
|
||||
|
||||
db.instance_get = Mock(return_value = self.instance7)
|
||||
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:
|
||||
c1 = ( 0 <= e.message.find('doesnt have enough resource') )
|
||||
c1 = ( 0 <= str(e.args).find('doesnt have enough resource') )
|
||||
self.assertTrue(c1, True)
|
||||
return False
|
||||
|
||||
@@ -282,9 +301,9 @@ class SchedulerTestFunctions(unittest.TestCase):
|
||||
|
||||
db.instance_get = Mock(return_value = self.instance8)
|
||||
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:
|
||||
c1 = ( 0 <= e.message.find('doesnt have enough resource') )
|
||||
c1 = ( 0 <= str(e.args).find('doesnt have enough resource') )
|
||||
self.assertTrue(c1, True)
|
||||
return False
|
||||
|
||||
@@ -292,7 +311,7 @@ class SchedulerTestFunctions(unittest.TestCase):
|
||||
def test08(self):
|
||||
"""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)
|
||||
|
||||
|
||||
@@ -300,7 +319,7 @@ class SchedulerTestFunctions(unittest.TestCase):
|
||||
"""09: everything goes well(instance_get_all_by_host returns[]). """
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@@ -308,91 +327,120 @@ class SchedulerTestFunctions(unittest.TestCase):
|
||||
|
||||
|
||||
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(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"))
|
||||
|
||||
db.instance_get = Mock(side_effect=exception.NotFound("ERR"))
|
||||
self.assertRaises(exception.NotFound,
|
||||
self.manager.live_migration,
|
||||
self.manager.driver.schedule_live_migration,
|
||||
self.ctxt,
|
||||
'i-12345',
|
||||
'host1')
|
||||
|
||||
|
||||
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') )
|
||||
self.assertRaises(exception.NotFound,
|
||||
self.manager.live_migration,
|
||||
db.instance_get = Mock(side_effect=TypeError("ERR"))
|
||||
self.assertRaises(TypeError,
|
||||
self.manager.driver.schedule_live_migration,
|
||||
self.ctxt,
|
||||
'i-12345',
|
||||
'host1')
|
||||
|
||||
|
||||
def test12(self):
|
||||
"""12: Destination host is not compute node """
|
||||
self.assertRaises(exception.Invalid,
|
||||
self.manager.live_migration,
|
||||
self.ctxt,
|
||||
'i-12345',
|
||||
'host2')
|
||||
"""12: instance state is not power_state.RUNNING. """
|
||||
|
||||
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):
|
||||
"""13: 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.live_migration,
|
||||
self.ctxt,
|
||||
'i-12345',
|
||||
'host1')
|
||||
"""13: instance state_description is not running. """
|
||||
|
||||
db.instance_get = Mock(return_value=self.instance5)
|
||||
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
|
||||
|
||||
def test14(self):
|
||||
"""14: rpc.call returns 0 (cpu is not compatible between src and dest) """
|
||||
rpc.call = Mock(return_value = 0)
|
||||
"""14: dest is not compute node.
|
||||
(dest is not included in the result of db.service_get_all_by_topic)
|
||||
"""
|
||||
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:
|
||||
c1 = ( 0 <= e.message.find('doesnt have compatibility to'))
|
||||
c1 = (0 <= str(e.args).find('must be compute node'))
|
||||
self.assertTrue(c1, True)
|
||||
return False
|
||||
|
||||
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()
|
||||
db.instance_get = Mock(return_value = self.instance8)
|
||||
self.manager.driver.service_is_up = Mock(return_value=False)
|
||||
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()
|
||||
db.instance_get_by_internal_id = Mock(return_value = self.instance8)
|
||||
db.instance_set_state = Mock(return_value = True)
|
||||
rpc_cast = Mock(return_value = True)
|
||||
# Cannot test the case of hypervisor type difference and hypervisor
|
||||
# version difference, since we cannot set different mocks to same method..
|
||||
|
||||
try :
|
||||
self.manager.live_migration(self.ctxt, 'i-12345', 'host1')
|
||||
except exception.NotEmpty, e:
|
||||
c1 = ( 0 <= e.message.find('doesnt have enough resource') )
|
||||
def test16(self):
|
||||
""" 16: stored "cpuinfo" is not string """
|
||||
|
||||
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)
|
||||
return False
|
||||
|
||||
|
||||
def test16(self):
|
||||
"""16: everything goes well. """
|
||||
def test17(self):
|
||||
"""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)
|
||||
db.instance_set_state = Mock(return_value = True)
|
||||
rpc.cast = Mock(return_value = True)
|
||||
def test18(self):
|
||||
"""18: rpc.call returns 0 (cpu is not compatible between src and dest) """
|
||||
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')
|
||||
self.assertEqual(ret, None)
|
||||
def test19(self):
|
||||
"""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):
|
||||
|
||||
Binary file not shown.
@@ -55,7 +55,6 @@ from nova import utils
|
||||
from nova import flags
|
||||
from nova.db import base
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
@@ -67,10 +66,9 @@ class Manager(base.Base):
|
||||
self.host = host
|
||||
super(Manager, self).__init__(db_driver)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def periodic_tasks(self, context=None):
|
||||
"""Tasks to be run at a periodic interval"""
|
||||
yield
|
||||
pass
|
||||
|
||||
def init_host(self):
|
||||
"""Do any initialization that needs to be run if this is a standalone
|
||||
|
||||
209
nova/process.py
209
nova/process.py
@@ -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)
|
||||
119
nova/rpc.py
119
nova/rpc.py
@@ -25,18 +25,18 @@ import json
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import uuid
|
||||
|
||||
from carrot import connection as carrot_connection
|
||||
from carrot import messaging
|
||||
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 fakerabbit
|
||||
from nova import flags
|
||||
from nova import context
|
||||
from nova import utils
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
@@ -91,15 +91,15 @@ class Consumer(messaging.Consumer):
|
||||
self.failed_connection = False
|
||||
break
|
||||
except: # Catching all because carrot sucks
|
||||
logging.exception("AMQP server on %s:%d is unreachable." \
|
||||
" Trying again in %d seconds." % (
|
||||
logging.exception(_("AMQP server on %s:%d is unreachable."
|
||||
" Trying again in %d seconds.") % (
|
||||
FLAGS.rabbit_host,
|
||||
FLAGS.rabbit_port,
|
||||
FLAGS.rabbit_retry_interval))
|
||||
self.failed_connection = True
|
||||
if self.failed_connection:
|
||||
logging.exception("Unable to connect to AMQP server" \
|
||||
" after %d tries. Shutting down." % FLAGS.rabbit_max_retries)
|
||||
logging.exception(_("Unable to connect to AMQP server"
|
||||
" after %d tries. Shutting down.") % FLAGS.rabbit_max_retries)
|
||||
sys.exit(1)
|
||||
|
||||
def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False):
|
||||
@@ -116,29 +116,21 @@ class Consumer(messaging.Consumer):
|
||||
self.declare()
|
||||
super(Consumer, self).fetch(no_ack, auto_ack, enable_callbacks)
|
||||
if self.failed_connection:
|
||||
logging.error("Reconnected to queue")
|
||||
logging.error(_("Reconnected to queue"))
|
||||
self.failed_connection = False
|
||||
# NOTE(vish): This is catching all errors because we really don't
|
||||
# exceptions to be logged 10 times a second if some
|
||||
# persistent failure occurs.
|
||||
except Exception: # pylint: disable-msg=W0703
|
||||
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
|
||||
|
||||
def attach_to_eventlet(self):
|
||||
"""Only needed for unit tests!"""
|
||||
def fetch_repeatedly():
|
||||
while True:
|
||||
self.fetch(enable_callbacks=True)
|
||||
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
|
||||
timer = utils.LoopingCall(self.fetch, enable_callbacks=True)
|
||||
timer.start(0.1)
|
||||
return timer
|
||||
|
||||
|
||||
class Publisher(messaging.Publisher):
|
||||
@@ -161,7 +153,7 @@ class TopicConsumer(Consumer):
|
||||
class AdapterConsumer(TopicConsumer):
|
||||
"""Calls methods on a proxy object based on method and args"""
|
||||
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
|
||||
super(AdapterConsumer, self).__init__(connection=connection,
|
||||
topic=topic)
|
||||
@@ -176,7 +168,7 @@ class AdapterConsumer(TopicConsumer):
|
||||
|
||||
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)
|
||||
|
||||
ctxt = _unpack_context(message_data)
|
||||
@@ -189,18 +181,20 @@ class AdapterConsumer(TopicConsumer):
|
||||
# messages stay in the queue indefinitely, so for now
|
||||
# we just log the message and send an error string
|
||||
# back to the caller
|
||||
LOG.warn('no method for message: %s' % (message_data))
|
||||
msg_reply(msg_id, '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)
|
||||
return
|
||||
|
||||
node_func = getattr(self.proxy, str(method))
|
||||
node_args = dict((str(k), v) for k, v in args.iteritems())
|
||||
# NOTE(vish): magic is fun!
|
||||
# pylint: disable-msg=W0142
|
||||
d = defer.maybeDeferred(node_func, context=ctxt, **node_args)
|
||||
if msg_id:
|
||||
d.addCallback(lambda rval: msg_reply(msg_id, rval, None))
|
||||
d.addErrback(lambda e: msg_reply(msg_id, None, e))
|
||||
try:
|
||||
rval = node_func(context=ctxt, **node_args)
|
||||
if msg_id:
|
||||
msg_reply(msg_id, rval, None)
|
||||
except Exception as e:
|
||||
if msg_id:
|
||||
msg_reply(msg_id, None, sys.exc_info())
|
||||
return
|
||||
|
||||
|
||||
@@ -242,14 +236,16 @@ class DirectPublisher(Publisher):
|
||||
def msg_reply(msg_id, reply=None, failure=None):
|
||||
"""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:
|
||||
message = failure.getErrorMessage()
|
||||
traceback = failure.getTraceback()
|
||||
logging.error("Returning exception %s to caller", message)
|
||||
logging.error(traceback)
|
||||
failure = (failure.type.__name__, str(failure.value), traceback)
|
||||
conn = Connection.instance()
|
||||
message = str(failure[1])
|
||||
tb = traceback.format_exception(*failure)
|
||||
logging.error(_("Returning exception %s to caller"), message)
|
||||
logging.error(tb)
|
||||
failure = (failure[0].__name__, str(failure[1]), tb)
|
||||
conn = Connection.instance(True)
|
||||
publisher = DirectPublisher(connection=conn, msg_id=msg_id)
|
||||
try:
|
||||
publisher.send({'result': reply, 'failure': failure})
|
||||
@@ -287,7 +283,7 @@ def _unpack_context(msg):
|
||||
if key.startswith('_context_'):
|
||||
value = msg.pop(key)
|
||||
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)
|
||||
|
||||
|
||||
@@ -306,14 +302,13 @@ def _pack_context(msg, context):
|
||||
|
||||
def call(context, topic, msg):
|
||||
"""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.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)
|
||||
|
||||
class WaitMessage(object):
|
||||
|
||||
def __call__(self, data, message):
|
||||
"""Acks message and sets result."""
|
||||
message.ack()
|
||||
@@ -337,41 +332,15 @@ def call(context, topic, msg):
|
||||
except StopIteration:
|
||||
pass
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
"""Sends a message on a topic without waiting for a response"""
|
||||
LOG.debug("Making asynchronous cast...")
|
||||
@@ -384,7 +353,7 @@ def cast(context, topic, msg):
|
||||
|
||||
def generic_response(message_data, message):
|
||||
"""Logs a result and exits"""
|
||||
LOG.debug('response %s', message_data)
|
||||
LOG.debug(_('response %s'), message_data)
|
||||
message.ack()
|
||||
sys.exit(0)
|
||||
|
||||
@@ -393,8 +362,8 @@ def send_message(topic, message, wait=True):
|
||||
"""Sends a message for testing"""
|
||||
msg_id = uuid.uuid4().hex
|
||||
message.update({'_msg_id': msg_id})
|
||||
LOG.debug('topic is %s', topic)
|
||||
LOG.debug('message %s', message)
|
||||
LOG.debug(_('topic is %s'), topic)
|
||||
LOG.debug(_('message %s'), message)
|
||||
|
||||
if wait:
|
||||
consumer = messaging.Consumer(connection=Connection.instance(),
|
||||
|
||||
@@ -34,5 +34,5 @@ class ChanceScheduler(driver.Scheduler):
|
||||
|
||||
hosts = self.hosts_up(context, topic)
|
||||
if not hosts:
|
||||
raise driver.NoValidHost("No hosts found")
|
||||
raise driver.NoValidHost(_("No hosts found"))
|
||||
return hosts[int(random.random() * len(hosts))]
|
||||
|
||||
@@ -22,10 +22,14 @@ Scheduler base class that all Schedulers should inherit from
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
from nova import rpc
|
||||
from nova.api.ec2 import cloud
|
||||
from nova.compute import power_state
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
flags.DEFINE_integer('service_down_time', 60,
|
||||
@@ -58,4 +62,137 @@ class Scheduler(object):
|
||||
|
||||
def schedule(self, context, topic, *_args, **_kwargs):
|
||||
"""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))
|
||||
|
||||
@@ -30,8 +30,6 @@ from nova import manager
|
||||
from nova import rpc
|
||||
from nova import utils
|
||||
from nova import exception
|
||||
from nova.api.ec2 import cloud
|
||||
from nova.compute import power_state
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
flags.DEFINE_string('scheduler_driver',
|
||||
@@ -68,119 +66,11 @@ class SchedulerManager(manager.Manager):
|
||||
db.queue_get_for(context, topic, host),
|
||||
{"method": method,
|
||||
"args": kwargs})
|
||||
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))
|
||||
logging.debug(_("Casting to %s %s for %s"), topic, host, method)
|
||||
|
||||
# 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):
|
||||
""" show the physical/usage resource given by hosts."""
|
||||
|
||||
@@ -191,12 +81,12 @@ class SchedulerManager(manager.Manager):
|
||||
except:
|
||||
raise
|
||||
|
||||
# get physical resource information
|
||||
# Getting physical resource information
|
||||
h_resource = {'vcpus': host_ref['vcpus'],
|
||||
'memory_mb': host_ref['memory_mb'],
|
||||
'local_gb': host_ref['local_gb']}
|
||||
|
||||
# get usage resource information
|
||||
# Getting usage resource information
|
||||
u_resource = {}
|
||||
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,
|
||||
host,
|
||||
p_id)
|
||||
u_resource[p_id] = {'vcpus': vcpus,
|
||||
'memory_mb': mem,
|
||||
u_resource[p_id] = {'vcpus': vcpus,
|
||||
'memory_mb': mem,
|
||||
'local_gb': hdd}
|
||||
|
||||
return {'ret': True, 'phy_resource': h_resource, 'usage': u_resource}
|
||||
|
||||
@@ -47,7 +47,7 @@ class SimpleScheduler(chance.ChanceScheduler):
|
||||
for result in results:
|
||||
(service, instance_cores) = result
|
||||
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):
|
||||
# NOTE(vish): this probably belongs in the manager, if we
|
||||
# can generalize this somehow
|
||||
@@ -57,7 +57,7 @@ class SimpleScheduler(chance.ChanceScheduler):
|
||||
{'host': service['host'],
|
||||
'scheduled_at': now})
|
||||
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):
|
||||
"""Picks a host that is up and has the fewest volumes."""
|
||||
@@ -66,7 +66,8 @@ class SimpleScheduler(chance.ChanceScheduler):
|
||||
for result in results:
|
||||
(service, volume_gigabytes) = result
|
||||
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):
|
||||
# NOTE(vish): this probably belongs in the manager, if we
|
||||
# can generalize this somehow
|
||||
@@ -76,7 +77,7 @@ class SimpleScheduler(chance.ChanceScheduler):
|
||||
{'host': service['host'],
|
||||
'scheduled_at': now})
|
||||
return service['host']
|
||||
raise driver.NoValidHost("No hosts found")
|
||||
raise driver.NoValidHost(_("No hosts found"))
|
||||
|
||||
def schedule_set_network_host(self, context, *_args, **_kwargs):
|
||||
"""Picks a host that is up and has the fewest networks."""
|
||||
@@ -85,7 +86,7 @@ class SimpleScheduler(chance.ChanceScheduler):
|
||||
for result in results:
|
||||
(service, instance_count) = result
|
||||
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):
|
||||
return service['host']
|
||||
raise driver.NoValidHost("No hosts found")
|
||||
raise driver.NoValidHost(_("No hosts found"))
|
||||
|
||||
151
nova/server.py
151
nova/server.py
@@ -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)
|
||||
107
nova/service.py
107
nova/service.py
@@ -17,21 +17,17 @@
|
||||
# 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.
|
||||
Generic Node baseclass for all workers that run on hosts
|
||||
"""
|
||||
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
from twisted.internet import defer
|
||||
from twisted.internet import task
|
||||
from twisted.application import service
|
||||
from eventlet import event
|
||||
from eventlet import greenthread
|
||||
from eventlet import greenpool
|
||||
|
||||
from nova import context
|
||||
from nova import db
|
||||
@@ -50,8 +46,16 @@ flags.DEFINE_integer('periodic_interval', 60,
|
||||
'seconds between running periodic tasks',
|
||||
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."""
|
||||
|
||||
def __init__(self, host, binary, topic, manager, report_interval=None,
|
||||
@@ -64,8 +68,9 @@ class Service(object, service.Service):
|
||||
self.periodic_interval = periodic_interval
|
||||
super(Service, self).__init__(*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)
|
||||
self.manager = manager_class(host=self.host, *self.saved_args,
|
||||
**self.saved_kwargs)
|
||||
@@ -87,26 +92,29 @@ class Service(object, service.Service):
|
||||
except exception.NotFound:
|
||||
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:
|
||||
consumer_all = rpc.AdapterConsumer(
|
||||
connection=conn,
|
||||
connection=conn1,
|
||||
topic=self.topic,
|
||||
proxy=self)
|
||||
consumer_node = rpc.AdapterConsumer(
|
||||
connection=conn,
|
||||
connection=conn2,
|
||||
topic='%s.%s' % (self.topic, self.host),
|
||||
proxy=self)
|
||||
|
||||
consumer_all.attach_to_twisted()
|
||||
consumer_node.attach_to_twisted()
|
||||
self.timers.append(consumer_all.attach_to_eventlet())
|
||||
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)
|
||||
self.timers.append(pulse)
|
||||
|
||||
if self.periodic_interval:
|
||||
pulse = task.LoopingCall(self.periodic_tasks)
|
||||
pulse.start(interval=self.periodic_interval, now=False)
|
||||
periodic = utils.LoopingCall(self.periodic_tasks)
|
||||
periodic.start(interval=self.periodic_interval, now=False)
|
||||
self.timers.append(periodic)
|
||||
|
||||
def _create_service_ref(self, context):
|
||||
service_ref = db.service_create(context,
|
||||
@@ -170,29 +178,32 @@ class Service(object, service.Service):
|
||||
report_interval = FLAGS.report_interval
|
||||
if not 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,
|
||||
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
|
||||
return service_obj
|
||||
|
||||
def kill(self):
|
||||
"""Destroy the service object in the datastore"""
|
||||
self.stop()
|
||||
try:
|
||||
db.service_destroy(context.get_admin_context(), self.service_id)
|
||||
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):
|
||||
"""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):
|
||||
"""Update the state of this service in the datastore."""
|
||||
ctxt = context.get_admin_context()
|
||||
@@ -200,8 +211,8 @@ class Service(object, service.Service):
|
||||
try:
|
||||
service_ref = db.service_get(ctxt, self.service_id)
|
||||
except exception.NotFound:
|
||||
logging.debug("The service database object disappeared, "
|
||||
"Recreating it.")
|
||||
logging.debug(_("The service database object disappeared, "
|
||||
"Recreating it."))
|
||||
self._create_service_ref(ctxt)
|
||||
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.
|
||||
if getattr(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
|
||||
except Exception: # pylint: disable-msg=W0702
|
||||
if not getattr(self, "model_disconnected", False):
|
||||
self.model_disconnected = True
|
||||
logging.exception("model server went away")
|
||||
yield
|
||||
logging.exception(_("model server went away"))
|
||||
|
||||
|
||||
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
222
nova/service.py.THIS
Normal 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
|
||||
103
nova/test.py
103
nova/test.py
@@ -25,11 +25,12 @@ and some black magic for inline callbacks.
|
||||
import datetime
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
|
||||
import mox
|
||||
import stubout
|
||||
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 db
|
||||
@@ -37,9 +38,12 @@ from nova import fakerabbit
|
||||
from nova import flags
|
||||
from nova import rpc
|
||||
from nova.network import manager as network_manager
|
||||
from nova.tests import fake_flags
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
flags.DEFINE_bool('flush_db', True,
|
||||
'Flush the database before running fake tests')
|
||||
flags.DEFINE_bool('fake_tests', True,
|
||||
'should we use everything for testing')
|
||||
|
||||
@@ -55,11 +59,11 @@ def skip_if_fake(func):
|
||||
return _skipper
|
||||
|
||||
|
||||
class TrialTestCase(unittest.TestCase):
|
||||
class TestCase(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()
|
||||
super(TestCase, 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.
|
||||
@@ -94,7 +98,87 @@ class TrialTestCase(unittest.TestCase):
|
||||
db.fixed_ip_disassociate_all_by_timeout(ctxt, FLAGS.host,
|
||||
self.start)
|
||||
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:
|
||||
try:
|
||||
x.stop()
|
||||
@@ -147,14 +231,3 @@ class TrialTestCase(unittest.TestCase):
|
||||
return d
|
||||
_wrapped.func_name = func.func_name
|
||||
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
|
||||
|
||||
@@ -29,3 +29,8 @@
|
||||
.. moduleauthor:: Manish Singh <yosh@gimp.org>
|
||||
.. 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)
|
||||
|
||||
@@ -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')
|
||||
@@ -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)
|
||||
@@ -54,7 +54,7 @@ os.makedirs(os.path.join(OSS_TEMPDIR, 'images'))
|
||||
os.makedirs(os.path.join(OSS_TEMPDIR, 'buckets'))
|
||||
|
||||
|
||||
class ObjectStoreTestCase(test.TrialTestCase):
|
||||
class ObjectStoreTestCase(test.TestCase):
|
||||
"""Test objectstore API directly."""
|
||||
|
||||
def setUp(self):
|
||||
@@ -191,7 +191,7 @@ class TestSite(server.Site):
|
||||
protocol = TestHTTPChannel
|
||||
|
||||
|
||||
class S3APITestCase(test.TrialTestCase):
|
||||
class S3APITestCase(test.TestCase):
|
||||
"""Test objectstore through S3 API."""
|
||||
|
||||
def setUp(self):
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -35,7 +35,7 @@ class Context(object):
|
||||
pass
|
||||
|
||||
|
||||
class AccessTestCase(test.TrialTestCase):
|
||||
class AccessTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(AccessTestCase, self).setUp()
|
||||
um = manager.AuthManager()
|
||||
@@ -208,17 +208,13 @@ class AuthManagerTestCase(object):
|
||||
# so it probably belongs in crypto_unittest
|
||||
# but I'm leaving it where I found it.
|
||||
with user_and_project_generator(self.manager) as (user, project):
|
||||
# NOTE(todd): Should mention why we must setup controller first
|
||||
# (somebody please clue me in)
|
||||
cloud_controller = cloud.CloudController()
|
||||
cloud_controller.setup()
|
||||
_key, cert_str = self.manager._generate_x509_cert('test1',
|
||||
'testproj')
|
||||
# NOTE(vish): Setup runs genroot.sh if it hasn't been run
|
||||
cloud.CloudController().setup()
|
||||
_key, cert_str = crypto.generate_x509_cert(user.id, project.id)
|
||||
logging.debug(cert_str)
|
||||
|
||||
# Need to verify that it's signed by the right intermediate CA
|
||||
full_chain = crypto.fetch_ca(project_id='testproj', chain=True)
|
||||
int_cert = crypto.fetch_ca(project_id='testproj', chain=False)
|
||||
full_chain = crypto.fetch_ca(project_id=project.id, chain=True)
|
||||
int_cert = crypto.fetch_ca(project_id=project.id, chain=False)
|
||||
cloud_cert = crypto.fetch_ca()
|
||||
logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain)
|
||||
signed_cert = X509.load_cert_string(cert_str)
|
||||
@@ -227,7 +223,8 @@ class AuthManagerTestCase(object):
|
||||
cloud_cert = X509.load_cert_string(cloud_cert)
|
||||
self.assertTrue(signed_cert.verify(chain_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()))
|
||||
else:
|
||||
self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey()))
|
||||
@@ -326,24 +323,20 @@ class AuthManagerTestCase(object):
|
||||
self.assertTrue(user.is_admin())
|
||||
|
||||
|
||||
class AuthManagerLdapTestCase(AuthManagerTestCase, test.TrialTestCase):
|
||||
class AuthManagerLdapTestCase(AuthManagerTestCase, test.TestCase):
|
||||
auth_driver = 'nova.auth.ldapdriver.FakeLdapDriver'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
AuthManagerTestCase.__init__(self)
|
||||
test.TrialTestCase.__init__(self, *args, **kwargs)
|
||||
test.TestCase.__init__(self, *args, **kwargs)
|
||||
import nova.auth.fakeldap as fakeldap
|
||||
FLAGS.redis_db = 8
|
||||
if FLAGS.flush_db:
|
||||
logging.info("Flushing redis datastore")
|
||||
try:
|
||||
r = fakeldap.Redis.instance()
|
||||
r.flushdb()
|
||||
except:
|
||||
self.skip = True
|
||||
logging.info("Flushing datastore")
|
||||
r = fakeldap.Store.instance()
|
||||
r.flushdb()
|
||||
|
||||
|
||||
class AuthManagerDbTestCase(AuthManagerTestCase, test.TrialTestCase):
|
||||
class AuthManagerDbTestCase(AuthManagerTestCase, test.TestCase):
|
||||
auth_driver = 'nova.auth.dbdriver.DbDriver'
|
||||
|
||||
|
||||
@@ -22,22 +22,18 @@ import logging
|
||||
from M2Crypto import BIO
|
||||
from M2Crypto import RSA
|
||||
import os
|
||||
import StringIO
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from eventlet import greenthread
|
||||
from twisted.internet import defer
|
||||
import unittest
|
||||
from xml.etree import ElementTree
|
||||
|
||||
from nova import context
|
||||
from nova import crypto
|
||||
from nova import db
|
||||
from nova import flags
|
||||
from nova import rpc
|
||||
from nova import service
|
||||
from nova import test
|
||||
from nova import utils
|
||||
from nova.auth import manager
|
||||
from nova.compute import power_state
|
||||
from nova.api.ec2 import cloud
|
||||
@@ -53,10 +49,11 @@ IMAGES_PATH = os.path.join(OSS_TEMPDIR, 'images')
|
||||
os.makedirs(IMAGES_PATH)
|
||||
|
||||
|
||||
class CloudTestCase(test.TrialTestCase):
|
||||
class CloudTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
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()
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
@@ -64,27 +61,23 @@ class CloudTestCase(test.TrialTestCase):
|
||||
# set up our cloud
|
||||
self.cloud = cloud.CloudController()
|
||||
|
||||
# set up a service
|
||||
self.compute = utils.import_object(FLAGS.compute_manager)
|
||||
self.compute_consumer = rpc.AdapterConsumer(connection=self.conn,
|
||||
topic=FLAGS.compute_topic,
|
||||
proxy=self.compute)
|
||||
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()
|
||||
# set up services
|
||||
self.compute = service.Service.create(binary='nova-compute')
|
||||
self.compute.start()
|
||||
self.network = service.Service.create(binary='nova-network')
|
||||
self.network.start()
|
||||
|
||||
self.manager = manager.AuthManager()
|
||||
self.user = self.manager.create_user('admin', 'admin', 'admin', True)
|
||||
self.project = self.manager.create_project('proj', 'admin', 'proj')
|
||||
self.context = context.RequestContext(user=self.user,
|
||||
project=self.project)
|
||||
project=self.project)
|
||||
|
||||
def tearDown(self):
|
||||
self.manager.delete_project(self.project)
|
||||
self.manager.delete_user(self.user)
|
||||
self.compute.kill()
|
||||
self.network.kill()
|
||||
super(CloudTestCase, self).tearDown()
|
||||
|
||||
def _create_key(self, name):
|
||||
@@ -111,12 +104,13 @@ class CloudTestCase(test.TrialTestCase):
|
||||
{'address': address,
|
||||
'host': FLAGS.host})
|
||||
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'])
|
||||
ec2_id = cloud.internal_id_to_ec2_id(inst['internal_id'])
|
||||
self.cloud.associate_address(self.context,
|
||||
instance_id=ec2_id,
|
||||
public_ip=address)
|
||||
greenthread.sleep(0.3)
|
||||
self.cloud.disassociate_address(self.context,
|
||||
public_ip=address)
|
||||
self.cloud.release_address(self.context,
|
||||
@@ -126,6 +120,19 @@ class CloudTestCase(test.TrialTestCase):
|
||||
db.instance_destroy(self.context, inst['id'])
|
||||
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):
|
||||
image_id = FLAGS.default_image
|
||||
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..." %
|
||||
instance['instance_id'])
|
||||
while True:
|
||||
rv = yield defer.succeed(time.sleep(1))
|
||||
greenthread.sleep(1)
|
||||
info = self.cloud._get_instance(instance['instance_id'])
|
||||
logging.debug(info['state'])
|
||||
if info['state'] == power_state.RUNNING:
|
||||
@@ -22,8 +22,6 @@ Tests For Compute
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
from nova import context
|
||||
from nova import db
|
||||
from nova import exception
|
||||
@@ -33,15 +31,17 @@ from nova import utils
|
||||
from nova.auth import manager
|
||||
from nova.compute import api as compute_api
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
class ComputeTestCase(test.TrialTestCase):
|
||||
class ComputeTestCase(test.TestCase):
|
||||
"""Test case for compute"""
|
||||
def setUp(self):
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
super(ComputeTestCase, self).setUp()
|
||||
self.flags(connection_type='fake',
|
||||
stub_network=True,
|
||||
network_manager='nova.network.manager.FlatManager')
|
||||
self.compute = utils.import_object(FLAGS.compute_manager)
|
||||
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."""
|
||||
cases = [dict(), dict(display_name=None)]
|
||||
for instance in cases:
|
||||
ref = self.compute_api.create_instance(self.context, None,
|
||||
**instance)
|
||||
ref = self.compute_api.create_instances(self.context,
|
||||
FLAGS.default_instance_type, None, **instance)
|
||||
try:
|
||||
self.assertNotEqual(ref.display_name, None)
|
||||
self.assertNotEqual(ref[0].display_name, None)
|
||||
finally:
|
||||
db.instance_destroy(self.context, ref['id'])
|
||||
db.instance_destroy(self.context, ref[0]['id'])
|
||||
|
||||
def test_create_instance_associates_security_groups(self):
|
||||
"""Make sure create_instance associates security groups"""
|
||||
inst = {}
|
||||
inst['user_id'] = self.user.id
|
||||
inst['project_id'] = self.project.id
|
||||
"""Make sure create_instances associates security groups"""
|
||||
values = {'name': 'default',
|
||||
'description': 'default',
|
||||
'user_id': self.user.id,
|
||||
'project_id': self.project.id}
|
||||
group = db.security_group_create(self.context, values)
|
||||
ref = self.compute_api.create_instance(self.context,
|
||||
security_groups=[group['id']],
|
||||
**inst)
|
||||
# reload to get groups
|
||||
instance_ref = db.instance_get(self.context, ref['id'])
|
||||
ref = self.compute_api.create_instances(self.context,
|
||||
FLAGS.default_instance_type, None, security_group=['default'])
|
||||
try:
|
||||
self.assertEqual(len(instance_ref['security_groups']), 1)
|
||||
self.assertEqual(len(ref[0]['security_groups']), 1)
|
||||
finally:
|
||||
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):
|
||||
"""Make sure it is possible to run and terminate 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())
|
||||
logging.info("Running instances: %s", instances)
|
||||
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())
|
||||
logging.info("After terminating instances: %s", instances)
|
||||
self.assertEqual(len(instances), 0)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_run_terminate_timestamps(self):
|
||||
"""Make sure timestamps are set for launched and destroyed"""
|
||||
instance_id = self._create_instance()
|
||||
@@ -125,42 +117,48 @@ class ComputeTestCase(test.TrialTestCase):
|
||||
self.assertEqual(instance_ref['launched_at'], None)
|
||||
self.assertEqual(instance_ref['deleted_at'], None)
|
||||
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)
|
||||
self.assert_(instance_ref['launched_at'] > launch)
|
||||
self.assertEqual(instance_ref['deleted_at'], None)
|
||||
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)
|
||||
instance_ref = db.instance_get(self.context, instance_id)
|
||||
self.assert_(instance_ref['launched_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):
|
||||
"""Ensure instance can be rebooted"""
|
||||
instance_id = self._create_instance()
|
||||
yield self.compute.run_instance(self.context, instance_id)
|
||||
yield self.compute.reboot_instance(self.context, instance_id)
|
||||
yield self.compute.terminate_instance(self.context, instance_id)
|
||||
self.compute.run_instance(self.context, instance_id)
|
||||
self.compute.reboot_instance(self.context, instance_id)
|
||||
self.compute.terminate_instance(self.context, instance_id)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_console_output(self):
|
||||
"""Make sure we can get console output from 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)
|
||||
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):
|
||||
"""Ensure failure when running an instance that already exists"""
|
||||
instance_id = self._create_instance()
|
||||
yield self.compute.run_instance(self.context, instance_id)
|
||||
self.assertFailure(self.compute.run_instance(self.context,
|
||||
instance_id),
|
||||
exception.Error)
|
||||
yield self.compute.terminate_instance(self.context, instance_id)
|
||||
self.compute.run_instance(self.context, instance_id)
|
||||
self.assertRaises(exception.Error,
|
||||
self.compute.run_instance,
|
||||
self.context,
|
||||
instance_id)
|
||||
self.compute.terminate_instance(self.context, instance_id)
|
||||
@@ -24,7 +24,7 @@ FLAGS = flags.FLAGS
|
||||
flags.DEFINE_string('flags_unittest', 'foo', 'for testing purposes only')
|
||||
|
||||
|
||||
class FlagsTestCase(test.TrialTestCase):
|
||||
class FlagsTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(FlagsTestCase, self).setUp()
|
||||
86
nova/tests/test_middleware.py
Normal file
86
nova/tests/test_middleware.py
Normal 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
55
nova/tests/test_misc.py
Normal 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()
|
||||
@@ -26,6 +26,7 @@ from nova import context
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
from nova import service
|
||||
from nova import test
|
||||
from nova import utils
|
||||
from nova.auth import manager
|
||||
@@ -33,13 +34,14 @@ from nova.auth import manager
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
class NetworkTestCase(test.TrialTestCase):
|
||||
class NetworkTestCase(test.TestCase):
|
||||
"""Test cases for network code"""
|
||||
def setUp(self):
|
||||
super(NetworkTestCase, self).setUp()
|
||||
# NOTE(vish): if you change these flags, make sure to change the
|
||||
# flags in the corresponding section in nova-dhcpbridge
|
||||
self.flags(connection_type='fake',
|
||||
fake_call=True,
|
||||
fake_network=True,
|
||||
network_size=16,
|
||||
num_networks=5)
|
||||
@@ -56,16 +58,13 @@ class NetworkTestCase(test.TrialTestCase):
|
||||
# create the necessary network data for the project
|
||||
user_context = context.RequestContext(project=self.projects[i],
|
||||
user=self.user)
|
||||
network_ref = self.network.get_network(user_context)
|
||||
self.network.set_network_host(context.get_admin_context(),
|
||||
network_ref['id'])
|
||||
host = self.network.get_network_host(user_context.elevated())
|
||||
instance_ref = self._create_instance(0)
|
||||
self.instance_id = instance_ref['id']
|
||||
instance_ref = self._create_instance(1)
|
||||
self.instance2_id = instance_ref['id']
|
||||
|
||||
def tearDown(self):
|
||||
super(NetworkTestCase, self).tearDown()
|
||||
# TODO(termie): this should really be instantiating clean datastores
|
||||
# in between runs, one failure kills all the tests
|
||||
db.instance_destroy(context.get_admin_context(), self.instance_id)
|
||||
@@ -73,6 +72,7 @@ class NetworkTestCase(test.TrialTestCase):
|
||||
for project in self.projects:
|
||||
self.manager.delete_project(project)
|
||||
self.manager.delete_user(self.user)
|
||||
super(NetworkTestCase, self).tearDown()
|
||||
|
||||
def _create_instance(self, project_num, mac=None):
|
||||
if not mac:
|
||||
@@ -20,8 +20,6 @@ Unit Tests for remote procedure calls using queue
|
||||
"""
|
||||
import logging
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
from nova import context
|
||||
from nova import flags
|
||||
from nova import rpc
|
||||
@@ -31,32 +29,31 @@ from nova import test
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
class RpcTestCase(test.TrialTestCase):
|
||||
class RpcTestCase(test.TestCase):
|
||||
"""Test cases for rpc"""
|
||||
def setUp(self):
|
||||
super(RpcTestCase, self).setUp()
|
||||
self.conn = rpc.Connection.instance()
|
||||
self.conn = rpc.Connection.instance(True)
|
||||
self.receiver = TestReceiver()
|
||||
self.consumer = rpc.AdapterConsumer(connection=self.conn,
|
||||
topic='test',
|
||||
proxy=self.receiver)
|
||||
self.consumer.attach_to_twisted()
|
||||
self.consumer.attach_to_eventlet()
|
||||
self.context = context.get_admin_context()
|
||||
|
||||
def test_call_succeed(self):
|
||||
"""Get a value through rpc call"""
|
||||
value = 42
|
||||
result = yield rpc.call_twisted(self.context,
|
||||
'test', {"method": "echo",
|
||||
result = rpc.call(self.context, 'test', {"method": "echo",
|
||||
"args": {"value": value}})
|
||||
self.assertEqual(value, result)
|
||||
|
||||
def test_context_passed(self):
|
||||
"""Makes sure a context is passed through rpc call"""
|
||||
value = 42
|
||||
result = yield rpc.call_twisted(self.context,
|
||||
'test', {"method": "context",
|
||||
"args": {"value": value}})
|
||||
result = rpc.call(self.context,
|
||||
'test', {"method": "context",
|
||||
"args": {"value": value}})
|
||||
self.assertEqual(self.context.to_dict(), result)
|
||||
|
||||
def test_call_exception(self):
|
||||
@@ -67,18 +64,48 @@ class RpcTestCase(test.TrialTestCase):
|
||||
to an int in the test.
|
||||
"""
|
||||
value = 42
|
||||
self.assertFailure(rpc.call_twisted(self.context, 'test',
|
||||
{"method": "fail",
|
||||
"args": {"value": value}}),
|
||||
rpc.RemoteError)
|
||||
self.assertRaises(rpc.RemoteError,
|
||||
rpc.call,
|
||||
self.context,
|
||||
'test',
|
||||
{"method": "fail",
|
||||
"args": {"value": value}})
|
||||
try:
|
||||
yield rpc.call_twisted(self.context,
|
||||
'test', {"method": "fail",
|
||||
"args": {"value": value}})
|
||||
rpc.call(self.context,
|
||||
'test',
|
||||
{"method": "fail",
|
||||
"args": {"value": value}})
|
||||
self.fail("should have thrown rpc.RemoteError")
|
||||
except rpc.RemoteError as exc:
|
||||
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):
|
||||
"""Simple Proxy class so the consumer has methods to call
|
||||
@@ -89,13 +116,13 @@ class TestReceiver(object):
|
||||
def echo(context, value):
|
||||
"""Simply returns whatever value is sent in"""
|
||||
logging.debug("Received %s", value)
|
||||
return defer.succeed(value)
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
def context(context, value):
|
||||
"""Returns dictionary version of context"""
|
||||
logging.debug("Received %s", context)
|
||||
return defer.succeed(context.to_dict())
|
||||
return context.to_dict()
|
||||
|
||||
@staticmethod
|
||||
def fail(context, value):
|
||||
@@ -44,11 +44,11 @@ class TestDriver(driver.Scheduler):
|
||||
return 'named_host'
|
||||
|
||||
|
||||
class SchedulerTestCase(test.TrialTestCase):
|
||||
class SchedulerTestCase(test.TestCase):
|
||||
"""Test case for scheduler"""
|
||||
def setUp(self):
|
||||
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):
|
||||
scheduler = manager.SchedulerManager()
|
||||
@@ -73,11 +73,12 @@ class SchedulerTestCase(test.TrialTestCase):
|
||||
scheduler.named_method(ctxt, 'topic', num=7)
|
||||
|
||||
|
||||
class SimpleDriverTestCase(test.TrialTestCase):
|
||||
class SimpleDriverTestCase(test.TestCase):
|
||||
"""Test case for simple driver"""
|
||||
def setUp(self):
|
||||
super(SimpleDriverTestCase, self).setUp()
|
||||
self.flags(connection_type='fake',
|
||||
stub_network=True,
|
||||
max_cores=4,
|
||||
max_gigabytes=4,
|
||||
network_manager='nova.network.manager.FlatManager',
|
||||
@@ -122,12 +123,12 @@ class SimpleDriverTestCase(test.TrialTestCase):
|
||||
'nova-compute',
|
||||
'compute',
|
||||
FLAGS.compute_manager)
|
||||
compute1.startService()
|
||||
compute1.start()
|
||||
compute2 = service.Service('host2',
|
||||
'nova-compute',
|
||||
'compute',
|
||||
FLAGS.compute_manager)
|
||||
compute2.startService()
|
||||
compute2.start()
|
||||
hosts = self.scheduler.driver.hosts_up(self.context, 'compute')
|
||||
self.assertEqual(len(hosts), 2)
|
||||
compute1.kill()
|
||||
@@ -139,12 +140,12 @@ class SimpleDriverTestCase(test.TrialTestCase):
|
||||
'nova-compute',
|
||||
'compute',
|
||||
FLAGS.compute_manager)
|
||||
compute1.startService()
|
||||
compute1.start()
|
||||
compute2 = service.Service('host2',
|
||||
'nova-compute',
|
||||
'compute',
|
||||
FLAGS.compute_manager)
|
||||
compute2.startService()
|
||||
compute2.start()
|
||||
instance_id1 = self._create_instance()
|
||||
compute1.run_instance(self.context, instance_id1)
|
||||
instance_id2 = self._create_instance()
|
||||
@@ -162,12 +163,12 @@ class SimpleDriverTestCase(test.TrialTestCase):
|
||||
'nova-compute',
|
||||
'compute',
|
||||
FLAGS.compute_manager)
|
||||
compute1.startService()
|
||||
compute1.start()
|
||||
compute2 = service.Service('host2',
|
||||
'nova-compute',
|
||||
'compute',
|
||||
FLAGS.compute_manager)
|
||||
compute2.startService()
|
||||
compute2.start()
|
||||
instance_ids1 = []
|
||||
instance_ids2 = []
|
||||
for index in xrange(FLAGS.max_cores):
|
||||
@@ -195,12 +196,12 @@ class SimpleDriverTestCase(test.TrialTestCase):
|
||||
'nova-volume',
|
||||
'volume',
|
||||
FLAGS.volume_manager)
|
||||
volume1.startService()
|
||||
volume1.start()
|
||||
volume2 = service.Service('host2',
|
||||
'nova-volume',
|
||||
'volume',
|
||||
FLAGS.volume_manager)
|
||||
volume2.startService()
|
||||
volume2.start()
|
||||
volume_id1 = self._create_volume()
|
||||
volume1.create_volume(self.context, volume_id1)
|
||||
volume_id2 = self._create_volume()
|
||||
@@ -218,12 +219,12 @@ class SimpleDriverTestCase(test.TrialTestCase):
|
||||
'nova-volume',
|
||||
'volume',
|
||||
FLAGS.volume_manager)
|
||||
volume1.startService()
|
||||
volume1.start()
|
||||
volume2 = service.Service('host2',
|
||||
'nova-volume',
|
||||
'volume',
|
||||
FLAGS.volume_manager)
|
||||
volume2.startService()
|
||||
volume2.start()
|
||||
volume_ids1 = []
|
||||
volume_ids2 = []
|
||||
for index in xrange(FLAGS.max_gigabytes):
|
||||
@@ -30,9 +30,10 @@ FLAGS = flags.FLAGS
|
||||
flags.DECLARE('instances_path', 'nova.compute.manager')
|
||||
|
||||
|
||||
class LibvirtConnTestCase(test.TrialTestCase):
|
||||
class LibvirtConnTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(LibvirtConnTestCase, self).setUp()
|
||||
self.flags(fake_call=True)
|
||||
self.manager = manager.AuthManager()
|
||||
self.user = self.manager.create_user('fake', 'fake', 'fake',
|
||||
admin=True)
|
||||
@@ -40,33 +41,64 @@ class LibvirtConnTestCase(test.TrialTestCase):
|
||||
self.network = utils.import_object(FLAGS.network_manager)
|
||||
FLAGS.instances_path = ''
|
||||
|
||||
def test_get_uri_and_template(self):
|
||||
ip = '10.11.12.13'
|
||||
test_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,
|
||||
'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'}
|
||||
def test_xml_and_uri_no_ramdisk_no_kernel(self):
|
||||
instance_data = dict(self.test_instance)
|
||||
self._check_xml_and_uri(instance_data,
|
||||
expect_kernel=False, expect_ramdisk=False)
|
||||
|
||||
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=self.user)
|
||||
instance_ref = db.instance_create(user_context, instance)
|
||||
network_ref = self.network.get_network(user_context)
|
||||
self.network.set_network_host(context.get_admin_context(),
|
||||
network_ref['id'])
|
||||
host = self.network.get_network_host(user_context.elevated())
|
||||
network_ref = db.project_get_network(context.get_admin_context(),
|
||||
self.project.id)
|
||||
|
||||
fixed_ip = {'address': ip,
|
||||
fixed_ip = {'address': self.test_ip,
|
||||
'network_id': network_ref['id']}
|
||||
|
||||
ctxt = context.get_admin_context()
|
||||
fixed_ip_ref = db.fixed_ip_create(ctxt, fixed_ip)
|
||||
db.fixed_ip_update(ctxt, ip, {'allocated': True,
|
||||
'instance_id': instance_ref['id']})
|
||||
db.fixed_ip_update(ctxt, self.test_ip,
|
||||
{'allocated': True,
|
||||
'instance_id': instance_ref['id']})
|
||||
|
||||
type_uri_map = {'qemu': ('qemu:///system',
|
||||
[(lambda t: t.find('.').get('type'), 'qemu'),
|
||||
@@ -78,23 +110,73 @@ class LibvirtConnTestCase(test.TrialTestCase):
|
||||
(lambda t: t.find('./devices/emulator'), None)]),
|
||||
'uml': ('uml:///system',
|
||||
[(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 = [
|
||||
(lambda t: t.find('.').tag, 'domain'),
|
||||
(lambda t: t.find('./devices/interface/filterref/parameter').\
|
||||
get('name'), 'IP'),
|
||||
(lambda t: t.find('./devices/interface/filterref/parameter').\
|
||||
get('value'), '10.11.12.13')]
|
||||
(lambda t: t.find(
|
||||
'./devices/interface/filterref/parameter').get('name'), 'IP'),
|
||||
(lambda t: t.find(
|
||||
'./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():
|
||||
FLAGS.libvirt_type = libvirt_type
|
||||
conn = libvirt_conn.LibvirtConnection(True)
|
||||
|
||||
uri, _template, _rescue = conn.get_uri_and_templates()
|
||||
uri = conn.get_uri()
|
||||
self.assertEquals(uri, expected_uri)
|
||||
|
||||
xml = conn.to_xml(instance_ref)
|
||||
xml = conn.to_xml(instance_ref, rescue)
|
||||
tree = xml_to_tree(xml)
|
||||
for i, (check, expected_result) in enumerate(checks):
|
||||
self.assertEqual(check(tree),
|
||||
@@ -106,6 +188,9 @@ class LibvirtConnTestCase(test.TrialTestCase):
|
||||
expected_result,
|
||||
'%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
|
||||
# checking against that later on. This way we make sure the
|
||||
# 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():
|
||||
FLAGS.libvirt_type = libvirt_type
|
||||
conn = libvirt_conn.LibvirtConnection(True)
|
||||
uri, _template, _rescue = conn.get_uri_and_templates()
|
||||
uri = conn.get_uri()
|
||||
self.assertEquals(uri, testuri)
|
||||
|
||||
def tearDown(self):
|
||||
@@ -123,7 +208,7 @@ class LibvirtConnTestCase(test.TrialTestCase):
|
||||
self.manager.delete_user(self.user)
|
||||
|
||||
|
||||
class NWFilterTestCase(test.TrialTestCase):
|
||||
class NWFilterTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(NWFilterTestCase, self).setUp()
|
||||
@@ -235,7 +320,7 @@ class NWFilterTestCase(test.TrialTestCase):
|
||||
'project_id': 'fake'})
|
||||
inst_id = instance_ref['id']
|
||||
|
||||
def _ensure_all_called(_):
|
||||
def _ensure_all_called():
|
||||
instance_filter = 'nova-instance-%s' % instance_ref['name']
|
||||
secgroup_filter = 'nova-secgroup-%s' % self.security_group['id']
|
||||
for required in [secgroup_filter, 'allow-dhcp-server',
|
||||
@@ -252,8 +337,7 @@ class NWFilterTestCase(test.TrialTestCase):
|
||||
self.security_group.id)
|
||||
instance = db.instance_get(self.context, inst_id)
|
||||
|
||||
d = self.fw.setup_nwfilters_for_instance(instance)
|
||||
d.addCallback(_ensure_all_called)
|
||||
d.addCallback(lambda _: self.teardown_security_group())
|
||||
|
||||
return d
|
||||
self.fw.setup_base_nwfilters()
|
||||
self.fw.setup_nwfilters_for_instance(instance)
|
||||
_ensure_all_called()
|
||||
self.teardown_security_group()
|
||||
@@ -21,8 +21,6 @@ Tests for Volume Code.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
from nova import context
|
||||
from nova import exception
|
||||
from nova import db
|
||||
@@ -33,7 +31,7 @@ from nova import utils
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
class VolumeTestCase(test.TrialTestCase):
|
||||
class VolumeTestCase(test.TestCase):
|
||||
"""Test Case for volumes."""
|
||||
|
||||
def setUp(self):
|
||||
@@ -56,51 +54,48 @@ class VolumeTestCase(test.TrialTestCase):
|
||||
vol['attach_status'] = "detached"
|
||||
return db.volume_create(context.get_admin_context(), vol)['id']
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_create_delete_volume(self):
|
||||
"""Test volume can be created and deleted."""
|
||||
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(),
|
||||
volume_id).id)
|
||||
|
||||
yield self.volume.delete_volume(self.context, volume_id)
|
||||
self.volume.delete_volume(self.context, volume_id)
|
||||
self.assertRaises(exception.NotFound,
|
||||
db.volume_get,
|
||||
self.context,
|
||||
volume_id)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_too_big_volume(self):
|
||||
"""Ensure failure if a too large of a volume is requested."""
|
||||
# FIXME(vish): validation needs to move into the data layer in
|
||||
# volume_create
|
||||
defer.returnValue(True)
|
||||
return True
|
||||
try:
|
||||
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")
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_too_many_volumes(self):
|
||||
"""Ensure that NoMoreTargets is raised when we run out of volumes."""
|
||||
vols = []
|
||||
total_slots = FLAGS.iscsi_num_targets
|
||||
for _index in xrange(total_slots):
|
||||
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)
|
||||
volume_id = self._create_volume()
|
||||
self.assertFailure(self.volume.create_volume(self.context,
|
||||
volume_id),
|
||||
db.NoMoreTargets)
|
||||
self.assertRaises(db.NoMoreTargets,
|
||||
self.volume.create_volume,
|
||||
self.context,
|
||||
volume_id)
|
||||
db.volume_destroy(context.get_admin_context(), volume_id)
|
||||
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):
|
||||
"""Make sure volume can be attached and detached from instance."""
|
||||
inst = {}
|
||||
@@ -115,15 +110,15 @@ class VolumeTestCase(test.TrialTestCase):
|
||||
instance_id = db.instance_create(self.context, inst)['id']
|
||||
mountpoint = "/dev/sdf"
|
||||
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:
|
||||
db.volume_attached(self.context, volume_id, instance_id,
|
||||
mountpoint)
|
||||
else:
|
||||
yield self.compute.attach_volume(self.context,
|
||||
instance_id,
|
||||
volume_id,
|
||||
mountpoint)
|
||||
self.compute.attach_volume(self.context,
|
||||
instance_id,
|
||||
volume_id,
|
||||
mountpoint)
|
||||
vol = db.volume_get(context.get_admin_context(), volume_id)
|
||||
self.assertEqual(vol['status'], "in-use")
|
||||
self.assertEqual(vol['attach_status'], "attached")
|
||||
@@ -131,25 +126,26 @@ class VolumeTestCase(test.TrialTestCase):
|
||||
instance_ref = db.volume_get_instance(self.context, volume_id)
|
||||
self.assertEqual(instance_ref['id'], instance_id)
|
||||
|
||||
self.assertFailure(self.volume.delete_volume(self.context, volume_id),
|
||||
exception.Error)
|
||||
self.assertRaises(exception.Error,
|
||||
self.volume.delete_volume,
|
||||
self.context,
|
||||
volume_id)
|
||||
if FLAGS.fake_tests:
|
||||
db.volume_detached(self.context, volume_id)
|
||||
else:
|
||||
yield self.compute.detach_volume(self.context,
|
||||
instance_id,
|
||||
volume_id)
|
||||
self.compute.detach_volume(self.context,
|
||||
instance_id,
|
||||
volume_id)
|
||||
vol = db.volume_get(self.context, volume_id)
|
||||
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,
|
||||
db.volume_get,
|
||||
self.context,
|
||||
volume_id)
|
||||
db.instance_destroy(self.context, instance_id)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_concurrent_volumes_get_different_targets(self):
|
||||
"""Ensure multiple concurrent volumes get different targets."""
|
||||
volume_ids = []
|
||||
@@ -164,15 +160,11 @@ class VolumeTestCase(test.TrialTestCase):
|
||||
self.assert_(iscsi_target not in targets)
|
||||
targets.append(iscsi_target)
|
||||
logging.debug("Target %s allocated", iscsi_target)
|
||||
deferreds = []
|
||||
total_slots = FLAGS.iscsi_num_targets
|
||||
for _index in xrange(total_slots):
|
||||
volume_id = self._create_volume()
|
||||
d = self.volume.create_volume(self.context, volume_id)
|
||||
d.addCallback(_check)
|
||||
d.addErrback(self.fail)
|
||||
deferreds.append(d)
|
||||
yield defer.DeferredList(deferreds)
|
||||
_check(d)
|
||||
for volume_id in volume_ids:
|
||||
self.volume.delete_volume(self.context, volume_id)
|
||||
|
||||
220
nova/tests/test_xenapi.py
Normal file
220
nova/tests/test_xenapi.py
Normal 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()
|
||||
20
nova/tests/xenapi/__init__.py
Normal file
20
nova/tests/xenapi/__init__.py
Normal 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
103
nova/tests/xenapi/stubs.py
Normal 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
|
||||
@@ -43,7 +43,7 @@ else:
|
||||
|
||||
|
||||
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)')
|
||||
|
||||
|
||||
@@ -208,7 +208,7 @@ def stop(pidfile):
|
||||
pid = None
|
||||
|
||||
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)
|
||||
# Not an error in a restart
|
||||
return
|
||||
@@ -229,7 +229,7 @@ def stop(pidfile):
|
||||
|
||||
|
||||
def serve(filename):
|
||||
logging.debug("Serving %s" % filename)
|
||||
logging.debug(_("Serving %s") % filename)
|
||||
name = os.path.basename(filename)
|
||||
OptionsClass = WrapTwistedOptions(TwistdServerOptions)
|
||||
options = OptionsClass()
|
||||
@@ -281,7 +281,7 @@ def serve(filename):
|
||||
else:
|
||||
logging.getLogger().setLevel(logging.WARNING)
|
||||
|
||||
logging.debug("Full set of FLAGS:")
|
||||
logging.debug(_("Full set of FLAGS:"))
|
||||
for flag in FLAGS:
|
||||
logging.debug("%s : %s" % (flag, FLAGS.get(flag, None)))
|
||||
|
||||
|
||||
159
nova/utils.py
159
nova/utils.py
@@ -21,24 +21,24 @@ System-level utilities and helper functions.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import functools
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import subprocess
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
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 flags
|
||||
from nova.exception import ProcessExecutionError
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ def import_class(import_str):
|
||||
__import__(mod_str)
|
||||
return getattr(sys.modules[mod_str], class_str)
|
||||
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):
|
||||
@@ -62,8 +62,53 @@ def import_object(import_str):
|
||||
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):
|
||||
logging.debug("Fetching %s" % url)
|
||||
logging.debug(_("Fetching %s") % url)
|
||||
# c = pycurl.Curl()
|
||||
# fp = open(target, "wb")
|
||||
# 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):
|
||||
logging.debug("Running cmd: %s", cmd)
|
||||
logging.debug(_("Running cmd (subprocess): %s"), cmd)
|
||||
env = os.environ.copy()
|
||||
if 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()
|
||||
obj.stdin.close()
|
||||
if obj.returncode:
|
||||
logging.debug("Result was %s" % (obj.returncode))
|
||||
if check_exit_code and obj.returncode != 0:
|
||||
(stdout, stderr) = result
|
||||
raise ProcessExecutionError(exit_code=obj.returncode,
|
||||
stdout=stdout,
|
||||
stderr=stderr,
|
||||
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
|
||||
|
||||
|
||||
@@ -122,14 +170,8 @@ def debug(arg):
|
||||
|
||||
|
||||
def runthis(prompt, cmd, check_exit_code=True):
|
||||
logging.debug("Running %s" % (cmd))
|
||||
exit_code = subprocess.call(cmd.split(" "))
|
||||
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)
|
||||
logging.debug(_("Running %s") % (cmd))
|
||||
rv, err = execute(cmd, check_exit_code=check_exit_code)
|
||||
|
||||
|
||||
def generate_uid(topic, size=8):
|
||||
@@ -159,8 +201,6 @@ def last_octet(address):
|
||||
|
||||
def get_my_ip():
|
||||
"""Returns the actual ip of the local machine."""
|
||||
if getattr(FLAGS, 'fake_tests', None):
|
||||
return '127.0.0.1'
|
||||
try:
|
||||
csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
csock.connect(('8.8.8.8', 80))
|
||||
@@ -168,17 +208,55 @@ def get_my_ip():
|
||||
csock.close()
|
||||
return addr
|
||||
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"
|
||||
|
||||
|
||||
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):
|
||||
"""Returns iso formatted utcnow."""
|
||||
if not at:
|
||||
at = datetime.datetime.utcnow()
|
||||
at = utcnow()
|
||||
return at.strftime(TIME_FORMAT)
|
||||
|
||||
|
||||
def parse_isotime(timestr):
|
||||
"""Turn an iso formatted time back into a datetime"""
|
||||
return datetime.datetime.strptime(timestr, TIME_FORMAT)
|
||||
|
||||
|
||||
@@ -212,7 +290,7 @@ class LazyPluggable(object):
|
||||
if not self.__backend:
|
||||
backend_name = self.__pivot.value
|
||||
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]
|
||||
if type(backend) == type(tuple()):
|
||||
@@ -231,10 +309,41 @@ class LazyPluggable(object):
|
||||
return getattr(backend, key)
|
||||
|
||||
|
||||
def deferredToThread(f):
|
||||
def g(*args, **kwargs):
|
||||
return deferToThread(f, *args, **kwargs)
|
||||
return g
|
||||
class LoopingCall(object):
|
||||
def __init__(self, f=None, *args, **kw):
|
||||
self.args = args
|
||||
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):
|
||||
|
||||
@@ -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
|
||||
122
run_tests.py
122
run_tests.py
@@ -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())
|
||||
12
run_tests.sh
12
run_tests.sh
@@ -36,7 +36,8 @@ done
|
||||
|
||||
if [ $never_venv -eq 1 ]; then
|
||||
# Just run the test suites in current environment
|
||||
python run_tests.py
|
||||
rm -f nova.sqlite
|
||||
nosetests -v
|
||||
exit
|
||||
fi
|
||||
|
||||
@@ -47,7 +48,8 @@ if [ $force -eq 1 ]; then
|
||||
fi
|
||||
|
||||
if [ -e ${venv} ]; then
|
||||
${with_venv} python run_tests.py $@
|
||||
${with_venv} rm -f nova.sqlite
|
||||
${with_venv} nosetests -v $@
|
||||
else
|
||||
if [ $always_venv -eq 1 ]; then
|
||||
# Automatically install the virtualenv
|
||||
@@ -59,9 +61,11 @@ else
|
||||
# Install the virtualenv and run the test suite in it
|
||||
python tools/install_venv.py
|
||||
else
|
||||
python run_tests.py
|
||||
rm -f nova.sqlite
|
||||
nosetests -v
|
||||
exit
|
||||
fi
|
||||
fi
|
||||
${with_venv} python run_tests.py $@
|
||||
${with_venv} rm -f nova.sqlite
|
||||
${with_venv} nosetests -v $@
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user