merging
This commit is contained in:
2
Authors
2
Authors
@@ -8,6 +8,7 @@ Chmouel Boudjnah <chmouel@chmouel.com>
|
||||
Chris Behrens <cbehrens@codestud.com>
|
||||
Cory Wright <corywright@gmail.com>
|
||||
David Pravec <David.Pravec@danix.org>
|
||||
Dan Prince <dan.prince@rackspace.com>
|
||||
Dean Troyer <dtroyer@gmail.com>
|
||||
Devin Carlen <devin.carlen@gmail.com>
|
||||
Ed Leafe <ed@leafe.com>
|
||||
@@ -23,6 +24,7 @@ Joe Heck <heckj@mac.com>
|
||||
Joel Moore <joelbm24@gmail.com>
|
||||
John Dewey <john@dewey.ws>
|
||||
Jonathan Bryce <jbryce@jbryce.com>
|
||||
Jordan Rinke <jordan@openstack.org>
|
||||
Josh Durgin <joshd@hq.newdream.net>
|
||||
Josh Kearney <josh.kearney@rackspace.com>
|
||||
Joshua McKenty <jmckenty@gmail.com>
|
||||
|
||||
@@ -495,9 +495,9 @@ class NetworkCommands(object):
|
||||
cidr=fixed_range,
|
||||
num_networks=int(num_networks),
|
||||
network_size=int(network_size),
|
||||
cidr_v6=fixed_range_v6,
|
||||
vlan_start=int(vlan_start),
|
||||
vpn_start=int(vpn_start),
|
||||
cidr_v6=fixed_range_v6)
|
||||
vpn_start=int(vpn_start))
|
||||
|
||||
|
||||
class ServiceCommands(object):
|
||||
|
||||
@@ -21,6 +21,7 @@ Nova User API client library.
|
||||
|
||||
import base64
|
||||
import boto
|
||||
import boto.exception
|
||||
import httplib
|
||||
|
||||
from boto.ec2.regioninfo import RegionInfo
|
||||
@@ -288,10 +289,14 @@ class NovaAdminClient(object):
|
||||
|
||||
def get_user(self, name):
|
||||
"""Grab a single user by name."""
|
||||
user = self.apiconn.get_object('DescribeUser', {'Name': name},
|
||||
UserInfo)
|
||||
if user.username != None:
|
||||
return user
|
||||
try:
|
||||
return self.apiconn.get_object('DescribeUser',
|
||||
{'Name': name},
|
||||
UserInfo)
|
||||
except boto.exception.BotoServerError, e:
|
||||
if e.status == 400 and e.error_code == 'NotFound':
|
||||
return None
|
||||
raise
|
||||
|
||||
def has_user(self, username):
|
||||
"""Determine if user exists."""
|
||||
@@ -376,6 +381,13 @@ class NovaAdminClient(object):
|
||||
'MemberUsers': member_users}
|
||||
return self.apiconn.get_object('RegisterProject', params, ProjectInfo)
|
||||
|
||||
def modify_project(self, projectname, manager_user=None, description=None):
|
||||
"""Modifies an existing project."""
|
||||
params = {'Name': projectname,
|
||||
'ManagerUser': manager_user,
|
||||
'Description': description}
|
||||
return self.apiconn.get_status('ModifyProject', params)
|
||||
|
||||
def delete_project(self, projectname):
|
||||
"""Permanently deletes the specified project."""
|
||||
return self.apiconn.get_object('DeregisterProject',
|
||||
|
||||
@@ -31,6 +31,7 @@ import cStringIO
|
||||
import json
|
||||
import logging
|
||||
import logging.handlers
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from nova import flags
|
||||
@@ -191,6 +192,12 @@ class NovaLogger(logging.Logger):
|
||||
kwargs.pop('exc_info')
|
||||
self.error(message, **kwargs)
|
||||
|
||||
|
||||
def handle_exception(type, value, tb):
|
||||
logging.root.critical(str(value), exc_info=(type, value, tb))
|
||||
|
||||
|
||||
sys.excepthook = handle_exception
|
||||
logging.setLoggerClass(NovaLogger)
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +43,9 @@ class SimpleScheduler(chance.ChanceScheduler):
|
||||
def schedule_run_instance(self, context, instance_id, *_args, **_kwargs):
|
||||
"""Picks a host that is up and has the fewest running instances."""
|
||||
instance_ref = db.instance_get(context, instance_id)
|
||||
if instance_ref['availability_zone'] and context.is_admin:
|
||||
if (instance_ref['availability_zone']
|
||||
and ':' in instance_ref['availability_zone']
|
||||
and context.is_admin):
|
||||
zone, _x, host = instance_ref['availability_zone'].partition(':')
|
||||
service = db.service_get_by_args(context.elevated(), host,
|
||||
'nova-compute')
|
||||
@@ -75,7 +77,9 @@ class SimpleScheduler(chance.ChanceScheduler):
|
||||
def schedule_create_volume(self, context, volume_id, *_args, **_kwargs):
|
||||
"""Picks a host that is up and has the fewest volumes."""
|
||||
volume_ref = db.volume_get(context, volume_id)
|
||||
if (':' in volume_ref['availability_zone']) and context.is_admin:
|
||||
if (volume_ref['availability_zone']
|
||||
and ':' in volume_ref['availability_zone']
|
||||
and context.is_admin):
|
||||
zone, _x, host = volume_ref['availability_zone'].partition(':')
|
||||
service = db.service_get_by_args(context.elevated(), host,
|
||||
'nova-volume')
|
||||
|
||||
@@ -36,6 +36,7 @@ from nova.auth import manager
|
||||
class FakeHttplibSocket(object):
|
||||
"""a fake socket implementation for httplib.HTTPResponse, trivial"""
|
||||
def __init__(self, response_string):
|
||||
self.response_string = response_string
|
||||
self._buffer = StringIO.StringIO(response_string)
|
||||
|
||||
def makefile(self, _mode, _other):
|
||||
@@ -66,13 +67,16 @@ class FakeHttplibConnection(object):
|
||||
# For some reason, the response doesn't have "HTTP/1.0 " prepended; I
|
||||
# guess that's a function the web server usually provides.
|
||||
resp = "HTTP/1.0 %s" % resp
|
||||
sock = FakeHttplibSocket(resp)
|
||||
self.http_response = httplib.HTTPResponse(sock)
|
||||
self.sock = FakeHttplibSocket(resp)
|
||||
self.http_response = httplib.HTTPResponse(self.sock)
|
||||
self.http_response.begin()
|
||||
|
||||
def getresponse(self):
|
||||
return self.http_response
|
||||
|
||||
def getresponsebody(self):
|
||||
return self.sock.response_string
|
||||
|
||||
def close(self):
|
||||
"""Required for compatibility with boto/tornado"""
|
||||
pass
|
||||
@@ -104,7 +108,7 @@ class ApiEc2TestCase(test.TestCase):
|
||||
self.app = ec2.Authenticate(ec2.Requestify(ec2.Executor(),
|
||||
'nova.api.ec2.cloud.CloudController'))
|
||||
|
||||
def expect_http(self, host=None, is_secure=False):
|
||||
def expect_http(self, host=None, is_secure=False, api_version=None):
|
||||
"""Returns a new EC2 connection"""
|
||||
self.ec2 = boto.connect_ec2(
|
||||
aws_access_key_id='fake',
|
||||
@@ -113,13 +117,31 @@ class ApiEc2TestCase(test.TestCase):
|
||||
region=regioninfo.RegionInfo(None, 'test', self.host),
|
||||
port=8773,
|
||||
path='/services/Cloud')
|
||||
if api_version:
|
||||
self.ec2.APIVersion = api_version
|
||||
|
||||
self.mox.StubOutWithMock(self.ec2, 'new_http_connection')
|
||||
http = FakeHttplibConnection(
|
||||
self.http = FakeHttplibConnection(
|
||||
self.app, '%s:8773' % (self.host), False)
|
||||
# pylint: disable-msg=E1103
|
||||
self.ec2.new_http_connection(host, is_secure).AndReturn(http)
|
||||
return http
|
||||
self.ec2.new_http_connection(host, is_secure).AndReturn(self.http)
|
||||
return self.http
|
||||
|
||||
def test_xmlns_version_matches_request_version(self):
|
||||
self.expect_http(api_version='2010-10-30')
|
||||
self.mox.ReplayAll()
|
||||
|
||||
user = self.manager.create_user('fake', 'fake', 'fake')
|
||||
project = self.manager.create_project('fake', 'fake', 'fake')
|
||||
|
||||
# Any request should be fine
|
||||
self.ec2.get_all_instances()
|
||||
self.assertTrue(self.ec2.APIVersion in self.http.getresponsebody(),
|
||||
'The version in the xmlns of the response does '
|
||||
'not match the API version given in the request.')
|
||||
|
||||
self.manager.delete_project(project)
|
||||
self.manager.delete_user(user)
|
||||
|
||||
def test_describe_instances(self):
|
||||
"""Test that, after creating a user and a project, the describe
|
||||
|
||||
@@ -49,7 +49,7 @@ class ComputeTestCase(test.TestCase):
|
||||
self.manager = manager.AuthManager()
|
||||
self.user = self.manager.create_user('fake', 'fake', 'fake')
|
||||
self.project = self.manager.create_project('fake', 'fake', 'fake')
|
||||
self.context = context.get_admin_context()
|
||||
self.context = context.RequestContext('fake', 'fake', False)
|
||||
|
||||
def tearDown(self):
|
||||
self.manager.delete_user(self.user)
|
||||
@@ -69,6 +69,13 @@ class ComputeTestCase(test.TestCase):
|
||||
inst['ami_launch_index'] = 0
|
||||
return db.instance_create(self.context, inst)['id']
|
||||
|
||||
def _create_group(self):
|
||||
values = {'name': 'testgroup',
|
||||
'description': 'testgroup',
|
||||
'user_id': self.user.id,
|
||||
'project_id': self.project.id}
|
||||
return db.security_group_create(self.context, values)
|
||||
|
||||
def test_create_instance_defaults_display_name(self):
|
||||
"""Verify that an instance cannot be created without a display_name."""
|
||||
cases = [dict(), dict(display_name=None)]
|
||||
@@ -82,23 +89,55 @@ class ComputeTestCase(test.TestCase):
|
||||
|
||||
def test_create_instance_associates_security_groups(self):
|
||||
"""Make sure create 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)
|
||||
group = self._create_group()
|
||||
ref = self.compute_api.create(
|
||||
self.context,
|
||||
instance_type=FLAGS.default_instance_type,
|
||||
image_id=None,
|
||||
security_group=['default'])
|
||||
security_group=['testgroup'])
|
||||
try:
|
||||
self.assertEqual(len(db.security_group_get_by_instance(
|
||||
self.context, ref[0]['id'])), 1)
|
||||
self.context, ref[0]['id'])), 1)
|
||||
group = db.security_group_get(self.context, group['id'])
|
||||
self.assert_(len(group.instances) == 1)
|
||||
finally:
|
||||
db.security_group_destroy(self.context, group['id'])
|
||||
db.instance_destroy(self.context, ref[0]['id'])
|
||||
|
||||
def test_destroy_instance_disassociates_security_groups(self):
|
||||
"""Make sure destroying disassociates security groups"""
|
||||
group = self._create_group()
|
||||
|
||||
ref = self.compute_api.create(
|
||||
self.context,
|
||||
instance_type=FLAGS.default_instance_type,
|
||||
image_id=None,
|
||||
security_group=['testgroup'])
|
||||
try:
|
||||
db.instance_destroy(self.context, ref[0]['id'])
|
||||
group = db.security_group_get(self.context, group['id'])
|
||||
self.assert_(len(group.instances) == 0)
|
||||
finally:
|
||||
db.security_group_destroy(self.context, group['id'])
|
||||
|
||||
def test_destroy_security_group_disassociates_instances(self):
|
||||
"""Make sure destroying security groups disassociates instances"""
|
||||
group = self._create_group()
|
||||
|
||||
ref = self.compute_api.create(
|
||||
self.context,
|
||||
instance_type=FLAGS.default_instance_type,
|
||||
image_id=None,
|
||||
security_group=['testgroup'])
|
||||
|
||||
try:
|
||||
db.security_group_destroy(self.context, group['id'])
|
||||
group = db.security_group_get(context.get_admin_context(
|
||||
read_deleted=True), group['id'])
|
||||
self.assert_(len(group.instances) == 0)
|
||||
finally:
|
||||
db.instance_destroy(self.context, ref[0]['id'])
|
||||
|
||||
def test_run_terminate(self):
|
||||
"""Make sure it is possible to run and terminate instance"""
|
||||
instance_id = self._create_instance()
|
||||
|
||||
100
nova/tests/test_localization.py
Normal file
100
nova/tests/test_localization.py
Normal file
@@ -0,0 +1,100 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011 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 glob
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import nova
|
||||
|
||||
|
||||
class LocalizationTestCase(unittest.TestCase):
|
||||
def test_multiple_positional_format_placeholders(self):
|
||||
pat = re.compile("\W_\(")
|
||||
single_pat = re.compile("\W%\W")
|
||||
root_path = os.path.dirname(nova.__file__)
|
||||
problems = {}
|
||||
for root, dirs, files in os.walk(root_path):
|
||||
for fname in files:
|
||||
if not fname.endswith(".py"):
|
||||
continue
|
||||
pth = os.path.join(root, fname)
|
||||
txt = fulltext = file(pth).read()
|
||||
txt_lines = fulltext.splitlines()
|
||||
if not pat.search(txt):
|
||||
continue
|
||||
problems[pth] = []
|
||||
pos = txt.find("_(")
|
||||
while pos > -1:
|
||||
# Make sure that this isn't part of a dunder;
|
||||
# e.g., __init__(...
|
||||
# or something like 'self.assert_(...'
|
||||
test_txt = txt[pos - 1: pos + 10]
|
||||
if not (pat.search(test_txt)):
|
||||
txt = txt[pos + 2:]
|
||||
pos = txt.find("_(")
|
||||
continue
|
||||
pos += 2
|
||||
txt = txt[pos:]
|
||||
innerChars = []
|
||||
# Count pairs of open/close parens until _() closing
|
||||
# paren is found.
|
||||
parenCount = 1
|
||||
pos = 0
|
||||
while parenCount > 0:
|
||||
char = txt[pos]
|
||||
if char == "(":
|
||||
parenCount += 1
|
||||
elif char == ")":
|
||||
parenCount -= 1
|
||||
innerChars.append(char)
|
||||
pos += 1
|
||||
inner_all = "".join(innerChars)
|
||||
# Filter out '%%' and '%('
|
||||
inner = inner_all.replace("%%", "").replace("%(", "")
|
||||
# Filter out the single '%' operators
|
||||
inner = single_pat.sub("", inner)
|
||||
# Within the remaining content, count %
|
||||
fmtCount = inner.count("%")
|
||||
if fmtCount > 1:
|
||||
inner_first = inner_all.splitlines()[0]
|
||||
lns = ["%s" % (p + 1)
|
||||
for p, t in enumerate(txt_lines)
|
||||
if inner_first in t]
|
||||
lnums = ", ".join(lns)
|
||||
# Using ugly string concatenation to avoid having
|
||||
# this test fail itself.
|
||||
inner_all = "_" + "(" + "%s" % inner_all
|
||||
problems[pth].append("Line: %s Text: %s" %
|
||||
(lnums, inner_all))
|
||||
# Look for more
|
||||
pos = txt.find("_(")
|
||||
if not problems[pth]:
|
||||
del problems[pth]
|
||||
if problems:
|
||||
out = ["Problem(s) found in localized string formatting",
|
||||
"(see http://www.gnu.org/software/hello/manual/"
|
||||
"gettext/Python.html for more information)",
|
||||
"",
|
||||
" ------------ Files to fix ------------"]
|
||||
for pth in problems:
|
||||
out.append(" %s:" % pth)
|
||||
for val in set(problems[pth]):
|
||||
out.append(" %s" % val)
|
||||
raise AssertionError("\n".join(out))
|
||||
@@ -221,7 +221,12 @@ class IptablesFirewallTestCase(test.TestCase):
|
||||
self.project = self.manager.create_project('fake', 'fake', 'fake')
|
||||
self.context = context.RequestContext('fake', 'fake')
|
||||
self.network = utils.import_object(FLAGS.network_manager)
|
||||
self.fw = libvirt_conn.IptablesFirewallDriver()
|
||||
|
||||
class FakeLibvirtConnection(object):
|
||||
pass
|
||||
self.fake_libvirt_connection = FakeLibvirtConnection()
|
||||
self.fw = libvirt_conn.IptablesFirewallDriver(
|
||||
get_connection=lambda: self.fake_libvirt_connection)
|
||||
|
||||
def tearDown(self):
|
||||
self.manager.delete_project(self.project)
|
||||
@@ -474,6 +479,19 @@ class NWFilterTestCase(test.TestCase):
|
||||
'project_id': 'fake'})
|
||||
inst_id = instance_ref['id']
|
||||
|
||||
ip = '10.11.12.13'
|
||||
|
||||
network_ref = db.project_get_network(self.context,
|
||||
'fake')
|
||||
|
||||
fixed_ip = {'address': ip,
|
||||
'network_id': network_ref['id']}
|
||||
|
||||
admin_ctxt = context.get_admin_context()
|
||||
db.fixed_ip_create(admin_ctxt, fixed_ip)
|
||||
db.fixed_ip_update(admin_ctxt, ip, {'allocated': True,
|
||||
'instance_id': instance_ref['id']})
|
||||
|
||||
def _ensure_all_called():
|
||||
instance_filter = 'nova-instance-%s' % instance_ref['name']
|
||||
secgroup_filter = 'nova-secgroup-%s' % self.security_group['id']
|
||||
|
||||
@@ -26,6 +26,8 @@ from nose import config
|
||||
from nose import result
|
||||
from nose import core
|
||||
|
||||
from nova import log as logging
|
||||
|
||||
|
||||
class NovaTestResult(result.TextTestResult):
|
||||
def __init__(self, *args, **kw):
|
||||
@@ -58,6 +60,7 @@ class NovaTestRunner(core.TextTestRunner):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig()
|
||||
c = config.Config(stream=sys.stdout,
|
||||
env=os.environ,
|
||||
verbosity=3,
|
||||
|
||||
Reference in New Issue
Block a user