From a21c338c73c13281dfdd12ccb0c6168498855b9f Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Thu, 4 Nov 2010 15:50:23 -0700 Subject: Refactored smoketests to use novarc environment and to separate user and admin specific tests --- smoketests/admin_smoketests.py | 92 +++++++ smoketests/base.py | 154 +++++++++++ smoketests/flags.py | 13 +- smoketests/novatestcase.py | 130 ---------- smoketests/smoketest.py | 566 ----------------------------------------- smoketests/user_smoketests.py | 326 ++++++++++++++++++++++++ 6 files changed, 575 insertions(+), 706 deletions(-) create mode 100644 smoketests/admin_smoketests.py create mode 100644 smoketests/base.py delete mode 100644 smoketests/novatestcase.py delete mode 100644 smoketests/smoketest.py create mode 100644 smoketests/user_smoketests.py diff --git a/smoketests/admin_smoketests.py b/smoketests/admin_smoketests.py new file mode 100644 index 000000000..50bb3fa2e --- /dev/null +++ b/smoketests/admin_smoketests.py @@ -0,0 +1,92 @@ +# 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 os +import random +import sys +import time +import unittest +import zipfile + +from nova import adminclient +from smoketests import flags +from smoketests import base + + +SUITE_NAMES = '[user]' + +FLAGS = flags.FLAGS +flags.DEFINE_string('suite', None, 'Specific test suite to run ' + SUITE_NAMES) + +# TODO(devamcar): Use random tempfile +ZIP_FILENAME = '/tmp/nova-me-x509.zip' + +TEST_PREFIX = 'test%s' % int(random.random()*1000000) +TEST_USERNAME = '%suser' % TEST_PREFIX +TEST_PROJECTNAME = '%sproject' % TEST_PREFIX + + +class AdminSmokeTestCase(base.SmokeTestCase): + def setUp(self): + self.admin = adminclient.NovaAdminClient( + access_key=os.getenv('EC2_ACCESS_KEY'), + secret_key=os.getenv('EC2_SECRET_KEY'), + clc_url=os.getenv('EC2_URL'), + region=FLAGS.region) + + +class UserTests(AdminSmokeTestCase): + """ Test admin credentials and user creation. """ + + def test_001_admin_can_connect(self): + conn = self.admin.connection_for('admin', 'admin') + self.assert_(conn) + + def test_002_admin_can_create_user(self): + user = self.admin.create_user(TEST_USERNAME) + self.assertEqual(user.username, TEST_USERNAME) + + def test_003_admin_can_create_project(self): + project = self.admin.create_project(TEST_PROJECTNAME, + TEST_USERNAME) + self.assertEqual(project.projectname, TEST_PROJECTNAME) + + def test_004_user_can_download_credentials(self): + buf = self.admin.get_zip(TEST_USERNAME, TEST_PROJECTNAME) + output = open(ZIP_FILENAME, 'w') + output.write(buf) + output.close() + + zip = zipfile.ZipFile(ZIP_FILENAME, 'a', zipfile.ZIP_DEFLATED) + bad = zip.testzip() + zip.close() + + self.failIf(bad) + + def test_999_tearDown(self): + self.admin.delete_project(TEST_PROJECTNAME) + self.admin.delete_user(TEST_USERNAME) + try: + os.remove(ZIP_FILENAME) + except: + pass + +if __name__ == "__main__": + suites = {'user': unittest.makeSuite(UserTests)} + sys.exit(base.run_tests(suites)) + diff --git a/smoketests/base.py b/smoketests/base.py new file mode 100644 index 000000000..5a14d3e09 --- /dev/null +++ b/smoketests/base.py @@ -0,0 +1,154 @@ +# 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 +import commands +import httplib +import os +import paramiko +import random +import sys +import unittest +from boto.ec2.regioninfo import RegionInfo + +from smoketests import flags + +FLAGS = flags.FLAGS + + +class SmokeTestCase(unittest.TestCase): + def connect_ssh(self, ip, key_name): + # TODO(devcamcar): set a more reasonable connection timeout time + key = paramiko.RSAKey.from_private_key_file('/tmp/%s.pem' % key_name) + client = paramiko.SSHClient() + client.set_missing_host_key_policy(paramiko.WarningPolicy()) + client.connect(ip, username='root', pkey=key) + stdin, stdout, stderr = client.exec_command('uptime') + print 'uptime: ', stdout.read() + return client + + def can_ping(self, ip): + """ Attempt to ping the specified IP, and give up after 1 second. """ + + # NOTE(devcamcar): ping timeout flag is different in OSX. + if sys.platform == 'darwin': + timeout_flag = 't' + else: + timeout_flag = 'w' + + status, output = commands.getstatusoutput('ping -c1 -%s1 %s' % + (timeout_flag, ip)) + return status == 0 + + def connection_for_env(self, **kwargs): + """ + Returns a boto ec2 connection for the current environment. + """ + access_key = os.getenv('EC2_ACCESS_KEY') + secret_key = os.getenv('EC2_SECRET_KEY') + clc_url = os.getenv('EC2_URL') + + if not access_key or not secret_key or not clc_url: + raise Exception('Missing EC2 environment variables. Please source ' + 'the appropriate novarc file before running this ' + 'test.') + + parts = self.split_clc_url(clc_url) + return boto.connect_ec2(aws_access_key_id=access_key, + aws_secret_access_key=secret_key, + is_secure=parts['is_secure'], + region=RegionInfo(None, + 'nova', + parts['ip']), + port=parts['port'], + path='/services/Cloud', + **kwargs) + + def split_clc_url(self, clc_url): + """ + Splits a cloud controller endpoint url. + """ + parts = httplib.urlsplit(clc_url) + is_secure = parts.scheme == 'https' + ip, port = parts.netloc.split(':') + return {'ip': ip, 'port': int(port), 'is_secure': is_secure} + + def create_key_pair(self, conn, key_name): + try: + os.remove('/tmp/%s.pem' % key_name) + except: + pass + key = conn.create_key_pair(key_name) + key.save('/tmp/') + return key + + def delete_key_pair(self, conn, key_name): + conn.delete_key_pair(key_name) + try: + os.remove('/tmp/%s.pem' % key_name) + except: + pass + + def bundle_image(self, image, kernel=False): + cmd = 'euca-bundle-image -i %s' % image + if kernel: + cmd += ' --kernel true' + status, output = commands.getstatusoutput(cmd) + if status != 0: + print '%s -> \n %s' % (cmd, output) + raise Exception(output) + return True + + def upload_image(self, bucket_name, image): + cmd = 'euca-upload-bundle -b %s -m /tmp/%s.manifest.xml' % (bucket_name, image) + status, output = commands.getstatusoutput(cmd) + if status != 0: + print '%s -> \n %s' % (cmd, output) + raise Exception(output) + return True + + def delete_bundle_bucket(self, bucket_name): + cmd = 'euca-delete-bundle --clear -b %s' % (bucket_name) + status, output = commands.getstatusoutput(cmd) + if status != 0: + print '%s -> \n%s' % (cmd, output) + raise Exception(output) + return True + +def run_tests(suites): + argv = FLAGS(sys.argv) + + if not os.getenv('EC2_ACCESS_KEY'): + print >> sys.stderr, 'Missing EC2 environment variables. Please ' \ + 'source the appropriate novarc file before ' \ + 'running this test.' + return 1 + + if FLAGS.suite: + try: + suite = suites[FLAGS.suite] + except KeyError: + print >> sys.stderr, 'Available test suites:', \ + ', '.join(suites.keys()) + return 1 + + unittest.TextTestRunner(verbosity=2).run(suite) + else: + for suite in suites.itervalues(): + unittest.TextTestRunner(verbosity=2).run(suite) + diff --git a/smoketests/flags.py b/smoketests/flags.py index 3617fb797..ae4d09508 100644 --- a/smoketests/flags.py +++ b/smoketests/flags.py @@ -1,7 +1,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. +# Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -33,13 +33,6 @@ DEFINE_bool = DEFINE_bool # __GLOBAL FLAGS ONLY__ # Define any app-specific flags in their own files, docs at: # http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#39 -DEFINE_string('admin_access_key', 'admin', 'Access key for admin user') -DEFINE_string('admin_secret_key', 'admin', 'Secret key for admin user') -DEFINE_string('clc_ip', '127.0.0.1', 'IP of cloud controller API') -DEFINE_string('bundle_kernel', 'openwrt-x86-vmlinuz', - 'Local kernel file to use for bundling tests') -DEFINE_string('bundle_image', 'openwrt-x86-ext2.image', - 'Local image file to use for bundling tests') -#DEFINE_string('vpn_image_id', 'ami-CLOUDPIPE', -# 'AMI for cloudpipe vpn server') +DEFINE_string('region', 'nova', 'Region to use') +DEFINE_string('test_image', 'ami-tiny', 'Image to use for launch tests') diff --git a/smoketests/novatestcase.py b/smoketests/novatestcase.py deleted file mode 100644 index 513e0ca91..000000000 --- a/smoketests/novatestcase.py +++ /dev/null @@ -1,130 +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 commands -import os -import random -import sys -import unittest - - -import paramiko - -from nova import adminclient -from smoketests import flags - -FLAGS = flags.FLAGS - - -class NovaTestCase(unittest.TestCase): - def setUp(self): - self.nova_admin = adminclient.NovaAdminClient( - access_key=FLAGS.admin_access_key, - secret_key=FLAGS.admin_secret_key, - clc_ip=FLAGS.clc_ip) - - def tearDown(self): - pass - - def connect_ssh(self, ip, key_name): - # TODO(devcamcar): set a more reasonable connection timeout time - key = paramiko.RSAKey.from_private_key_file('/tmp/%s.pem' % key_name) - client = paramiko.SSHClient() - client.load_system_host_keys() - client.set_missing_host_key_policy(paramiko.WarningPolicy()) - client.connect(ip, username='root', pkey=key) - stdin, stdout, stderr = client.exec_command('uptime') - print 'uptime: ', stdout.read() - return client - - def can_ping(self, ip): - return commands.getstatusoutput('ping -c 1 %s' % ip)[0] == 0 - - @property - def admin(self): - return self.nova_admin.connection_for('admin') - - def connection_for(self, username): - return self.nova_admin.connection_for(username) - - def create_user(self, username): - return self.nova_admin.create_user(username) - - def get_user(self, username): - return self.nova_admin.get_user(username) - - def delete_user(self, username): - return self.nova_admin.delete_user(username) - - def get_signed_zip(self, username): - return self.nova_admin.get_zip(username) - - def create_key_pair(self, conn, key_name): - try: - os.remove('/tmp/%s.pem' % key_name) - except: - pass - key = conn.create_key_pair(key_name) - key.save('/tmp/') - return key - - def delete_key_pair(self, conn, key_name): - conn.delete_key_pair(key_name) - try: - os.remove('/tmp/%s.pem' % key_name) - except: - pass - - def bundle_image(self, image, kernel=False): - cmd = 'euca-bundle-image -i %s' % image - if kernel: - cmd += ' --kernel true' - status, output = commands.getstatusoutput(cmd) - if status != 0: - print '%s -> \n %s' % (cmd, output) - raise Exception(output) - return True - - def upload_image(self, bucket_name, image): - cmd = 'euca-upload-bundle -b %s -m /tmp/%s.manifest.xml' % (bucket_name, image) - status, output = commands.getstatusoutput(cmd) - if status != 0: - print '%s -> \n %s' % (cmd, output) - raise Exception(output) - return True - - def delete_bundle_bucket(self, bucket_name): - cmd = 'euca-delete-bundle --clear -b %s' % (bucket_name) - status, output = commands.getstatusoutput(cmd) - if status != 0: - print '%s -> \n%s' % (cmd, output) - raise Exception(output) - return True - - def register_image(self, bucket_name, manifest): - conn = nova_admin.connection_for('admin') - return conn.register_image("%s/%s.manifest.xml" % (bucket_name, manifest)) - - def setUp_test_image(self, image, kernel=False): - self.bundle_image(image, kernel=kernel) - bucket = "auto_test_%s" % int(random.random() * 1000000) - self.upload_image(bucket, image) - return self.register_image(bucket, image) - - def tearDown_test_image(self, conn, image_id): - conn.deregister_image(image_id) diff --git a/smoketests/smoketest.py b/smoketests/smoketest.py deleted file mode 100644 index ad95114d4..000000000 --- a/smoketests/smoketest.py +++ /dev/null @@ -1,566 +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 commands -import os -import random -import re -import sys -import time -import unittest -import zipfile - - -import paramiko - -from smoketests import flags -from smoketests import novatestcase - -SUITE_NAMES = '[user, image, security, public_network, volume]' - -FLAGS = flags.FLAGS -flags.DEFINE_string('suite', None, 'Specific test suite to run ' + SUITE_NAMES) - -# TODO(devamcar): Use random tempfile -ZIP_FILENAME = '/tmp/nova-me-x509.zip' - -data = {} - -test_prefix = 'test%s' % int(random.random()*1000000) -test_username = '%suser' % test_prefix -test_bucket = '%s_bucket' % test_prefix -test_key = '%s_key' % test_prefix - -# Test admin credentials and user creation -class UserTests(novatestcase.NovaTestCase): - def test_001_admin_can_connect(self): - conn = self.connection_for('admin') - self.assert_(conn) - - def test_002_admin_can_create_user(self): - userinfo = self.create_user(test_username) - self.assertEqual(userinfo.username, test_username) - - def test_003_user_can_download_credentials(self): - buf = self.get_signed_zip(test_username) - output = open(ZIP_FILENAME, 'w') - output.write(buf) - output.close() - - zip = zipfile.ZipFile(ZIP_FILENAME, 'a', zipfile.ZIP_DEFLATED) - bad = zip.testzip() - zip.close() - - self.failIf(bad) - - def test_999_tearDown(self): - self.delete_user(test_username) - user = self.get_user(test_username) - self.assert_(user is None) - try: - os.remove(ZIP_FILENAME) - except: - pass - -# Test image bundling, registration, and launching -class ImageTests(novatestcase.NovaTestCase): - def test_000_setUp(self): - self.create_user(test_username) - - def test_001_admin_can_bundle_image(self): - self.assertTrue(self.bundle_image(FLAGS.bundle_image)) - - def test_002_admin_can_upload_image(self): - self.assertTrue(self.upload_image(test_bucket, FLAGS.bundle_image)) - - def test_003_admin_can_register_image(self): - image_id = self.register_image(test_bucket, FLAGS.bundle_image) - self.assert_(image_id is not None) - data['image_id'] = image_id - - def test_004_admin_can_bundle_kernel(self): - self.assertTrue(self.bundle_image(FLAGS.bundle_kernel, kernel=True)) - - def test_005_admin_can_upload_kernel(self): - self.assertTrue(self.upload_image(test_bucket, FLAGS.bundle_kernel)) - - def test_006_admin_can_register_kernel(self): - # FIXME(devcamcar): registration should verify that bucket/manifest - # exists before returning successfully. - kernel_id = self.register_image(test_bucket, FLAGS.bundle_kernel) - self.assert_(kernel_id is not None) - data['kernel_id'] = kernel_id - - def test_007_admin_images_are_available_within_10_seconds(self): - for i in xrange(10): - image = self.admin.get_image(data['image_id']) - if image and image.state == 'available': - break - time.sleep(1) - else: - print image.state - self.assert_(False) # wasn't available within 10 seconds - self.assert_(image.type == 'machine') - - for i in xrange(10): - kernel = self.admin.get_image(data['kernel_id']) - if kernel and kernel.state == 'available': - break - time.sleep(1) - else: - self.assert_(False) # wasn't available within 10 seconds - self.assert_(kernel.type == 'kernel') - - def test_008_admin_can_describe_image_attribute(self): - attrs = self.admin.get_image_attribute(data['image_id'], - 'launchPermission') - self.assert_(attrs.name, 'launch_permission') - - def test_009_me_cannot_see_non_public_images(self): - conn = self.connection_for(test_username) - images = conn.get_all_images(image_ids=[data['image_id']]) - self.assertEqual(len(images), 0) - - def test_010_admin_can_modify_image_launch_permission(self): - conn = self.connection_for(test_username) - - self.admin.modify_image_attribute(image_id=data['image_id'], - operation='add', - attribute='launchPermission', - groups='all') - - image = conn.get_image(data['image_id']) - self.assertEqual(image.id, data['image_id']) - - def test_011_me_can_list_public_images(self): - conn = self.connection_for(test_username) - images = conn.get_all_images(image_ids=[data['image_id']]) - self.assertEqual(len(images), 1) - pass - - def test_012_me_can_see_launch_permission(self): - attrs = self.admin.get_image_attribute(data['image_id'], - 'launchPermission') - self.assert_(attrs.name, 'launch_permission') - self.assert_(attrs.groups[0], 'all') - - # FIXME: add tests that user can launch image - -# def test_013_user_can_launch_admin_public_image(self): -# # TODO: Use openwrt kernel instead of default kernel -# conn = self.connection_for(test_username) -# reservation = conn.run_instances(data['image_id']) -# self.assertEqual(len(reservation.instances), 1) -# data['my_instance_id'] = reservation.instances[0].id - -# def test_014_instances_launch_within_30_seconds(self): -# pass - -# def test_015_user_can_terminate(self): -# conn = self.connection_for(test_username) -# terminated = conn.terminate_instances( -# instance_ids=[data['my_instance_id']]) -# self.assertEqual(len(terminated), 1) - - def test_016_admin_can_deregister_kernel(self): - self.assertTrue(self.admin.deregister_image(data['kernel_id'])) - - def test_017_admin_can_deregister_image(self): - self.assertTrue(self.admin.deregister_image(data['image_id'])) - - def test_018_admin_can_delete_bundle(self): - self.assertTrue(self.delete_bundle_bucket(test_bucket)) - - def test_999_tearDown(self): - data = {} - self.delete_user(test_username) - - -# Test key pairs and security groups -class SecurityTests(novatestcase.NovaTestCase): - def test_000_setUp(self): - self.create_user(test_username + '_me') - self.create_user(test_username + '_you') - data['image_id'] = 'ami-tiny' - - def test_001_me_can_create_keypair(self): - conn = self.connection_for(test_username + '_me') - key = self.create_key_pair(conn, test_key) - self.assertEqual(key.name, test_key) - - def test_002_you_can_create_keypair(self): - conn = self.connection_for(test_username + '_you') - key = self.create_key_pair(conn, test_key+ 'yourkey') - self.assertEqual(key.name, test_key+'yourkey') - - def test_003_me_can_create_instance_with_keypair(self): - conn = self.connection_for(test_username + '_me') - reservation = conn.run_instances(data['image_id'], key_name=test_key) - self.assertEqual(len(reservation.instances), 1) - data['my_instance_id'] = reservation.instances[0].id - - def test_004_me_can_obtain_private_ip_within_60_seconds(self): - conn = self.connection_for(test_username + '_me') - reservations = conn.get_all_instances([data['my_instance_id']]) - instance = reservations[0].instances[0] - # allow 60 seconds to exit pending with IP - for x in xrange(60): - instance.update() - if instance.state != u'pending': - break - time.sleep(1) - else: - self.assert_(False) - # self.assertEqual(instance.state, u'running') - ip = reservations[0].instances[0].private_dns_name - self.failIf(ip == '0.0.0.0') - data['my_private_ip'] = ip - print data['my_private_ip'], - - def test_005_can_ping_private_ip(self): - for x in xrange(120): - # ping waits for 1 second - status, output = commands.getstatusoutput( - 'ping -c1 -w1 %s' % data['my_private_ip']) - if status == 0: - break - else: - self.assert_('could not ping instance') - #def test_005_me_cannot_ssh_when_unauthorized(self): - # self.assertRaises(paramiko.SSHException, self.connect_ssh, - # data['my_private_ip'], 'mykey') - - #def test_006_me_can_authorize_ssh(self): - # conn = self.connection_for(test_username + '_me') - # self.assertTrue( - # conn.authorize_security_group( - # 'default', - # ip_protocol='tcp', - # from_port=22, - # to_port=22, - # cidr_ip='0.0.0.0/0' - # ) - # ) - - def test_007_me_can_ssh_when_authorized(self): - conn = self.connect_ssh(data['my_private_ip'], test_key) - conn.close() - - #def test_008_me_can_revoke_ssh_authorization(self): - # conn = self.connection_for('me') - # self.assertTrue( - # conn.revoke_security_group( - # 'default', - # ip_protocol='tcp', - # from_port=22, - # to_port=22, - # cidr_ip='0.0.0.0/0' - # ) - # ) - - #def test_009_you_cannot_ping_my_instance(self): - # TODO: should ping my_private_ip from with an instance started by you. - #self.assertFalse(self.can_ping(data['my_private_ip'])) - - def test_010_you_cannot_ssh_to_my_instance(self): - try: - conn = self.connect_ssh(data['my_private_ip'], - test_key + 'yourkey') - conn.close() - except paramiko.SSHException: - pass - else: - self.fail("expected SSHException") - - def test_999_tearDown(self): - conn = self.connection_for(test_username + '_me') - self.delete_key_pair(conn, test_key) - if data.has_key('my_instance_id'): - conn.terminate_instances([data['my_instance_id']]) - - conn = self.connection_for(test_username + '_you') - self.delete_key_pair(conn, test_key + 'yourkey') - - conn = self.connection_for('admin') - self.delete_user(test_username + '_me') - self.delete_user(test_username + '_you') - #self.tearDown_test_image(conn, data['image_id']) - -# TODO: verify wrt image boots -# build python into wrt image -# build boto/m2crypto into wrt image -# build euca2ools into wrt image -# build a script to download and unpack credentials -# - return "ok" to stdout for comparison in self.assertEqual() -# build a script to bundle the instance -# build a script to upload the bundle - -# status, output = commands.getstatusoutput('cmd') -# if status == 0: -# print 'ok' -# else: -# print output - -# Testing rebundling -class RebundlingTests(novatestcase.NovaTestCase): - def test_000_setUp(self): - self.create_user('me') - self.create_user('you') - # TODO: create keypair for me - # upload smoketest img - # run instance - - def test_001_me_can_download_credentials_within_instance(self): - conn = self.connect_ssh(data['my_private_ip'], 'mykey') - stdin, stdout = conn.exec_command( - 'python ~/smoketests/install-credentials.py') - conn.close() - self.assertEqual(stdout, 'ok') - - def test_002_me_can_rebundle_within_instance(self): - conn = self.connect_ssh(data['my_private_ip'], 'mykey') - stdin, stdout = conn.exec_command( - 'python ~/smoketests/rebundle-instance.py') - conn.close() - self.assertEqual(stdout, 'ok') - - def test_003_me_can_upload_image_within_instance(self): - conn = self.connect_ssh(data['my_private_ip'], 'mykey') - stdin, stdout = conn.exec_command( - 'python ~/smoketests/upload-bundle.py') - conn.close() - self.assertEqual(stdout, 'ok') - - def test_004_me_can_register_image_within_instance(self): - conn = self.connect_ssh(data['my_private_ip'], 'mykey') - stdin, stdout = conn.exec_command( - 'python ~/smoketests/register-image.py') - conn.close() - if re.matches('ami-{\w+}', stdout): - data['my_image_id'] = stdout.strip() - else: - self.fail('expected ami-nnnnnn, got:\n ' + stdout) - - def test_005_you_cannot_see_my_private_image(self): - conn = self.connection_for('you') - image = conn.get_image(data['my_image_id']) - self.assertEqual(image, None) - - def test_006_me_can_make_image_public(self): - conn = self.connection_for(test_username) - conn.modify_image_attribute(image_id=data['my_image_id'], - operation='add', - attribute='launchPermission', - groups='all') - - def test_007_you_can_see_my_public_image(self): - conn = self.connection_for('you') - image = conn.get_image(data['my_image_id']) - self.assertEqual(image.id, data['my_image_id']) - - def test_999_tearDown(self): - self.delete_user('me') - self.delete_user('you') - - #if data.has_key('image_id'): - # deregister rebundled image - - # TODO: tear down instance - # delete keypairs - data = {} - -# Test elastic IPs -class ElasticIPTests(novatestcase.NovaTestCase): - def test_000_setUp(self): - data['image_id'] = 'ami-tiny' - - self.create_user('me') - conn = self.connection_for('me') - self.create_key_pair(conn, 'mykey') - - conn = self.connection_for('admin') - #data['image_id'] = self.setUp_test_image(FLAGS.bundle_image) - - def test_001_me_can_launch_image_with_keypair(self): - conn = self.connection_for('me') - reservation = conn.run_instances(data['image_id'], key_name='mykey') - self.assertEqual(len(reservation.instances), 1) - data['my_instance_id'] = reservation.instances[0].id - - def test_002_me_can_allocate_elastic_ip(self): - conn = self.connection_for('me') - data['my_public_ip'] = conn.allocate_address() - self.assert_(data['my_public_ip'].public_ip) - - def test_003_me_can_associate_ip_with_instance(self): - self.assertTrue(data['my_public_ip'].associate(data['my_instance_id'])) - - def test_004_me_can_ssh_with_public_ip(self): - conn = self.connect_ssh(data['my_public_ip'].public_ip, 'mykey') - conn.close() - - def test_005_me_can_disassociate_ip_from_instance(self): - self.assertTrue(data['my_public_ip'].disassociate()) - - def test_006_me_can_deallocate_elastic_ip(self): - self.assertTrue(data['my_public_ip'].delete()) - - def test_999_tearDown(self): - conn = self.connection_for('me') - self.delete_key_pair(conn, 'mykey') - - conn = self.connection_for('admin') - #self.tearDown_test_image(conn, data['image_id']) - data = {} - -ZONE = 'nova' -DEVICE = 'vdb' -# Test iscsi volumes -class VolumeTests(novatestcase.NovaTestCase): - def test_000_setUp(self): - self.create_user(test_username) - data['image_id'] = 'ami-tiny' # A7370FE3 - - conn = self.connection_for(test_username) - self.create_key_pair(conn, test_key) - reservation = conn.run_instances(data['image_id'], - instance_type='m1.tiny', - key_name=test_key) - data['instance_id'] = reservation.instances[0].id - data['private_ip'] = reservation.instances[0].private_dns_name - # wait for instance to show up - for x in xrange(120): - # ping waits for 1 second - status, output = commands.getstatusoutput( - 'ping -c1 -w1 %s' % data['private_ip']) - if status == 0: - break - else: - self.fail('unable to ping instance') - - def test_001_me_can_create_volume(self): - conn = self.connection_for(test_username) - volume = conn.create_volume(1, ZONE) - self.assertEqual(volume.size, 1) - data['volume_id'] = volume.id - # give network time to find volume - time.sleep(5) - - def test_002_me_can_attach_volume(self): - conn = self.connection_for(test_username) - conn.attach_volume( - volume_id = data['volume_id'], - instance_id = data['instance_id'], - device = '/dev/%s' % DEVICE - ) - # give instance time to recognize volume - time.sleep(5) - - def test_003_me_can_mount_volume(self): - conn = self.connect_ssh(data['private_ip'], test_key) - # FIXME(devcamcar): the tiny image doesn't create the node properly - # this will make /dev/vd* if it doesn't exist - stdin, stdout, stderr = conn.exec_command( - 'grep %s /proc/partitions |' + \ - '`awk \'{print "mknod /dev/"$4" b "$1" "$2}\'`' % DEVICE) - commands = [] - commands.append('mkdir -p /mnt/vol') - commands.append('mkfs.ext2 /dev/%s' % DEVICE) - commands.append('mount /dev/%s /mnt/vol' % DEVICE) - commands.append('echo success') - stdin, stdout, stderr = conn.exec_command(' && '.join(commands)) - out = stdout.read() - conn.close() - if not out.strip().endswith('success'): - self.fail('Unable to mount: %s %s' % (out, stderr.read())) - - def test_004_me_can_write_to_volume(self): - conn = self.connect_ssh(data['private_ip'], test_key) - # FIXME(devcamcar): This doesn't fail if the volume hasn't been mounted - stdin, stdout, stderr = conn.exec_command( - 'echo hello > /mnt/vol/test.txt') - err = stderr.read() - conn.close() - if len(err) > 0: - self.fail('Unable to write to mount: %s' % (err)) - - def test_005_volume_is_correct_size(self): - conn = self.connect_ssh(data['private_ip'], test_key) - stdin, stdout, stderr = conn.exec_command( - "df -h | grep %s | awk {'print $2'}" % DEVICE) - out = stdout.read() - conn.close() - if not out.strip() == '1007.9M': - self.fail('Volume is not the right size: %s %s' % (out, stderr.read())) - - def test_006_me_can_umount_volume(self): - conn = self.connect_ssh(data['private_ip'], test_key) - stdin, stdout, stderr = conn.exec_command('umount /mnt/vol') - err = stderr.read() - conn.close() - if len(err) > 0: - self.fail('Unable to unmount: %s' % (err)) - - def test_007_me_can_detach_volume(self): - conn = self.connection_for(test_username) - self.assertTrue(conn.detach_volume(volume_id = data['volume_id'])) - - def test_008_me_can_delete_volume(self): - conn = self.connection_for(test_username) - self.assertTrue(conn.delete_volume(data['volume_id'])) - - def test_009_volume_size_must_be_int(self): - conn = self.connection_for(test_username) - self.assertRaises(Exception, conn.create_volume, 'foo', ZONE) - - def test_999_tearDown(self): - global data - conn = self.connection_for(test_username) - self.delete_key_pair(conn, test_key) - if data.has_key('instance_id'): - conn.terminate_instances([data['instance_id']]) - self.delete_user(test_username) - data = {} - -def build_suites(): - return { - 'user': unittest.makeSuite(UserTests), - 'image': unittest.makeSuite(ImageTests), - 'security': unittest.makeSuite(SecurityTests), - 'public_network': unittest.makeSuite(ElasticIPTests), - 'volume': unittest.makeSuite(VolumeTests), - } - -def main(): - argv = FLAGS(sys.argv) - suites = build_suites() - - if FLAGS.suite: - try: - suite = suites[FLAGS.suite] - except KeyError: - print >> sys.stderr, 'Available test suites:', SUITE_NAMES - return 1 - - unittest.TextTestRunner(verbosity=2).run(suite) - else: - for suite in suites.itervalues(): - unittest.TextTestRunner(verbosity=2).run(suite) - -if __name__ == "__main__": - sys.exit(main()) diff --git a/smoketests/user_smoketests.py b/smoketests/user_smoketests.py new file mode 100644 index 000000000..d29e3aea3 --- /dev/null +++ b/smoketests/user_smoketests.py @@ -0,0 +1,326 @@ +# 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 commands +import os +import random +import socket +import sys +import time +import unittest + +from smoketests import flags +from smoketests import base + + +SUITE_NAMES = '[image, instance, volume]' + +FLAGS = flags.FLAGS +flags.DEFINE_string('suite', None, 'Specific test suite to run ' + SUITE_NAMES) +flags.DEFINE_string('bundle_kernel', 'openwrt-x86-vmlinuz', + 'Local kernel file to use for bundling tests') +flags.DEFINE_string('bundle_image', 'openwrt-x86-ext2.image', + 'Local image file to use for bundling tests') + +TEST_PREFIX = 'test%s' % int (random.random()*1000000) +TEST_BUCKET = '%s_bucket' % TEST_PREFIX +TEST_KEY = '%s_key' % TEST_PREFIX +TEST_DATA = {} + + +class UserSmokeTestCase(base.SmokeTestCase): + def setUp(self): + global TEST_DATA + self.conn = self.connection_for_env() + self.data = TEST_DATA + + +class ImageTests(UserSmokeTestCase): + def test_001_can_bundle_image(self): + self.assertTrue(self.bundle_image(FLAGS.bundle_image)) + + def test_002_can_upload_image(self): + self.assertTrue(self.upload_image(TEST_BUCKET, FLAGS.bundle_image)) + + def test_003_can_register_image(self): + image_id = self.conn.register_image('%s/%s.manifest.xml' % + (TEST_BUCKET, FLAGS.bundle_image)) + self.assert_(image_id is not None) + self.data['image_id'] = image_id + + def test_004_can_bundle_kernel(self): + self.assertTrue(self.bundle_image(FLAGS.bundle_kernel, kernel=True)) + + def test_005_can_upload_kernel(self): + self.assertTrue(self.upload_image(TEST_BUCKET, FLAGS.bundle_kernel)) + + def test_006_can_register_kernel(self): + kernel_id = self.conn.register_image('%s/%s.manifest.xml' % + (TEST_BUCKET, FLAGS.bundle_kernel)) + self.assert_(kernel_id is not None) + self.data['kernel_id'] = kernel_id + + def test_007_images_are_available_within_10_seconds(self): + for i in xrange(10): + image = self.conn.get_image(self.data['image_id']) + if image and image.state == 'available': + break + time.sleep(1) + else: + print image.state + self.assert_(False) # wasn't available within 10 seconds + self.assert_(image.type == 'machine') + + for i in xrange(10): + kernel = self.conn.get_image(self.data['kernel_id']) + if kernel and kernel.state == 'available': + break + time.sleep(1) + else: + self.assert_(False) # wasn't available within 10 seconds + self.assert_(kernel.type == 'kernel') + + def test_008_can_describe_image_attribute(self): + attrs = self.conn.get_image_attribute(self.data['image_id'], + 'launchPermission') + self.assert_(attrs.name, 'launch_permission') + + def test_009_can_modify_image_launch_permission(self): + self.conn.modify_image_attribute(image_id=self.data['image_id'], + operation='add', + attribute='launchPermission', + groups='all') + image = self.conn.get_image(self.data['image_id']) + self.assertEqual(image.id, self.data['image_id']) + + def test_010_can_see_launch_permission(self): + attrs = self.conn.get_image_attribute(self.data['image_id'], + 'launchPermission') + self.assert_(attrs.name, 'launch_permission') + self.assert_(attrs.attrs['groups'][0], 'all') + + def test_011_user_can_deregister_kernel(self): + self.assertTrue(self.conn.deregister_image(self.data['kernel_id'])) + + def test_012_can_deregister_image(self): + self.assertTrue(self.conn.deregister_image(self.data['image_id'])) + + def test_013_can_delete_bundle(self): + self.assertTrue(self.delete_bundle_bucket(TEST_BUCKET)) + + +class InstanceTests(UserSmokeTestCase): + def test_001_can_create_keypair(self): + key = self.create_key_pair(self.conn, TEST_KEY) + self.assertEqual(key.name, TEST_KEY) + + def test_002_can_create_instance_with_keypair(self): + reservation = self.conn.run_instances(FLAGS.test_image, + key_name=TEST_KEY, + instance_type='m1.tiny') + self.assertEqual(len(reservation.instances), 1) + self.data['instance_id'] = reservation.instances[0].id + + def test_003_instance_runs_within_60_seconds(self): + reservations = self.conn.get_all_instances([data['instance_id']]) + instance = reservations[0].instances[0] + # allow 60 seconds to exit pending with IP + for x in xrange(60): + instance.update() + if instance.state == u'running': + break + time.sleep(1) + else: + self.fail('instance failed to start') + ip = reservations[0].instances[0].private_dns_name + self.failIf(ip == '0.0.0.0') + self.data['private_ip'] = ip + print self.data['private_ip'] + + def test_004_can_ping_private_ip(self): + for x in xrange(120): + # ping waits for 1 second + status, output = commands.getstatusoutput( + 'ping -c1 %s' % self.data['private_ip']) + if status == 0: + break + else: + self.fail('could not ping instance') + + def test_005_can_ssh_to_private_ip(self): + for x in xrange(30): + try: + conn = self.connect_ssh(self.data['private_ip'], TEST_KEY) + conn.close() + except Exception: + time.sleep(1) + else: + break + else: + self.fail('could not ssh to instance') + + def test_006_can_allocate_elastic_ip(self): + result = self.conn.allocate_address() + self.assertTrue(hasattr(result, 'public_ip')) + self.data['public_ip'] = result.public_ip + + def test_007_can_associate_ip_with_instance(self): + result = self.conn.associate_address(self.data['instance_id'], + self.data['public_ip']) + self.assertTrue(result) + + def test_008_can_ssh_with_public_ip(self): + for x in xrange(30): + try: + conn = self.connect_ssh(self.data['public_ip'], TEST_KEY) + conn.close() + except socket.error: + time.sleep(1) + else: + break + else: + self.fail('could not ssh to instance') + + def test_009_can_disassociate_ip_from_instance(self): + result = self.conn.disassociate_address(self.data['public_ip']) + self.assertTrue(result) + + def test_010_can_deallocate_elastic_ip(self): + result = self.conn.release_address(self.data['public_ip']) + self.assertTrue(result) + + def test_999_tearDown(self): + self.delete_key_pair(self.conn, TEST_KEY) + if self.data.has_key('instance_id'): + self.conn.terminate_instances([data['instance_id']]) + + +class VolumeTests(UserSmokeTestCase): + def setUp(self): + super(VolumeTests, self).setUp() + self.device = '/dev/vdb' + + def test_000_setUp(self): + self.create_key_pair(self.conn, TEST_KEY) + reservation = self.conn.run_instances(FLAGS.test_image, + instance_type='m1.tiny', + key_name=TEST_KEY) + instance = reservation.instances[0] + self.data['instance'] = instance + for x in xrange(120): + if self.can_ping(instance.private_dns_name): + break + else: + self.fail('unable to start instance') + + def test_001_can_create_volume(self): + volume = self.conn.create_volume(1, 'nova') + self.assertEqual(volume.size, 1) + self.data['volume'] = volume + # Give network time to find volume. + time.sleep(5) + + def test_002_can_attach_volume(self): + volume = self.data['volume'] + + for x in xrange(10): + if volume.status == u'available': + break + time.sleep(5) + volume.update() + else: + self.fail('cannot attach volume with state %s' % volume.status) + + volume.attach(self.data['instance'].id, self.device) + + # Volumes seems to report "available" too soon. + for x in xrange(10): + if volume.status == u'in-use': + break + time.sleep(5) + volume.update() + + self.assertEqual(volume.status, u'in-use') + + # Give instance time to recognize volume. + time.sleep(5) + + def test_003_can_mount_volume(self): + ip = self.data['instance'].private_dns_name + conn = self.connect_ssh(ip, TEST_KEY) + commands = [] + commands.append('mkdir -p /mnt/vol') + commands.append('mkfs.ext2 %s' % self.device) + commands.append('mount %s /mnt/vol' % self.device) + commands.append('echo success') + stdin, stdout, stderr = conn.exec_command(' && '.join(commands)) + out = stdout.read() + conn.close() + if not out.strip().endswith('success'): + self.fail('Unable to mount: %s %s' % (out, stderr.read())) + + def test_004_can_write_to_volume(self): + ip = self.data['instance'].private_dns_name + conn = self.connect_ssh(ip, TEST_KEY) + # FIXME(devcamcar): This doesn't fail if the volume hasn't been mounted + stdin, stdout, stderr = conn.exec_command( + 'echo hello > /mnt/vol/test.txt') + err = stderr.read() + conn.close() + if len(err) > 0: + self.fail('Unable to write to mount: %s' % (err)) + + def test_005_volume_is_correct_size(self): + ip = self.data['instance'].private_dns_name + conn = self.connect_ssh(ip, TEST_KEY) + stdin, stdout, stderr = conn.exec_command( + "df -h | grep %s | awk {'print $2'}" % self.device) + out = stdout.read() + conn.close() + if not out.strip() == '1008M': + self.fail('Volume is not the right size: %s %s' % + (out, stderr.read())) + + def test_006_me_can_umount_volume(self): + ip = self.data['instance'].private_dns_name + conn = self.connect_ssh(ip, TEST_KEY) + stdin, stdout, stderr = conn.exec_command('umount /mnt/vol') + err = stderr.read() + conn.close() + if len(err) > 0: + self.fail('Unable to unmount: %s' % (err)) + + def test_007_me_can_detach_volume(self): + result = self.conn.detach_volume(volume_id=self.data['volume'].id) + self.assertTrue(result) + time.sleep(5) + + def test_008_me_can_delete_volume(self): + result = self.conn.delete_volume(self.data['volume'].id) + self.assertTrue(result) + + def test_999_tearDown(self): + self.conn.terminate_instances([self.data['instance'].id]) + self.conn.delete_key_pair(TEST_KEY) + + +if __name__ == "__main__": + suites = {'image': unittest.makeSuite(ImageTests), + 'instance': unittest.makeSuite(InstanceTests), + 'volume': unittest.makeSuite(VolumeTests)} + sys.exit(base.run_tests(suites)) -- cgit From 398ec325160acf039e1070718e898339a4b2d268 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 15 Nov 2010 01:25:42 -0400 Subject: base commit --- nova/api/openstack/__init__.py | 49 ++++++++++++------ nova/tests/api/openstack/test_restrictedapi.py | 70 ++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 15 deletions(-) create mode 100644 nova/tests/api/openstack/test_restrictedapi.py diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 1dd3ba770..338d642bc 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -47,6 +47,9 @@ flags.DEFINE_string('nova_api_auth', 'nova.api.openstack.auth.BasicApiAuthManager', 'The auth mechanism to use for the OpenStack API implemenation') +flags.DEFINE_list('nova_api_permitted_operations', + [], + 'A comma-separated list of permitted api operations. Empty for all.') class API(wsgi.Middleware): """WSGI entry point for all OpenStack API requests.""" @@ -165,21 +168,37 @@ class APIRouter(wsgi.Router): def __init__(self): mapper = routes.Mapper() - mapper.resource("server", "servers", controller=servers.Controller(), - collection={'detail': 'GET'}, - member={'action': 'POST'}) - - mapper.resource("backup_schedule", "backup_schedules", - controller=backup_schedules.Controller(), - parent_resource=dict(member_name='server', - collection_name='servers')) - - mapper.resource("image", "images", controller=images.Controller(), - collection={'detail': 'GET'}) - mapper.resource("flavor", "flavors", controller=flavors.Controller(), - collection={'detail': 'GET'}) - mapper.resource("sharedipgroup", "sharedipgroups", - controller=sharedipgroups.Controller()) + commands = { + "server" : dict(plural='servers', + controller=servers.Controller(), + collection={'detail': 'GET'}, + member={'action': 'POST'}), + "backup_schedule" : dict(plural='backup_schedules', + controller=backup_schedules.Controller(), + parent_resource=dict(member_name='server', + collection_name='servers')), + "image" : dict(plural='images', + controller=images.Controller(), + collection={'detail': 'GET'}), + "flavor" : dict(plural='flavors', + controller=flavors.Controller(), + collection={'detail': 'GET'}), + "sharedipgroup" : dict(plural="sharedipgroups", + controller=sharedipgroups.Controller()), + } + + permitted = commands.keys() + if len(FLAGS.nova_api_permitted_operations) > 0: + permitted = FLAGS.nova_api_permitted_operations + logging.debug("Permitted operation set: %s" % (permitted,)) + for command in permitted: + options = commands.get(command, None) + if not options: + logging.warning("Unknown option in nova_api_permitted_operations: '%s' (skipping)" % (command,)) + continue + collection = options['plural'] + del options['plural'] + mapper.resource(command, collection, **options) super(APIRouter, self).__init__(mapper) diff --git a/nova/tests/api/openstack/test_restrictedapi.py b/nova/tests/api/openstack/test_restrictedapi.py new file mode 100644 index 000000000..560a66e8c --- /dev/null +++ b/nova/tests/api/openstack/test_restrictedapi.py @@ -0,0 +1,70 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# 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 unittest + +import stubout +import webob + +import nova.api +from nova import flags +from nova.api.openstack import flavors +from nova.tests.api.openstack import fakes + +FLAGS = flags.FLAGS + +class RestrictedAPITest(unittest.TestCase): + def setUp(self): + self.stubs = stubout.StubOutForTesting() + fakes.FakeAuthManager.auth_data = {} + fakes.FakeAuthDatabase.data = {} + fakes.stub_out_networking(self.stubs) + fakes.stub_out_rate_limiting(self.stubs) + fakes.stub_out_auth(self.stubs) + self.original_permissions = FLAGS.nova_api_permitted_operations + + def tearDown(self): + self.stubs.UnsetAll() + FLAGS.nova_api_permitted_operations = self.original_permissions + + def test_permitted(self): + req = webob.Request.blank('/v1.0/flavors') + FLAGS.nova_api_permitted_operations = ["server", "backup_schedule", "image", "flavor", "sharedipgroup"] + res = req.get_response(nova.api.API('os')) + self.assertEqual(res.status_int, 200) + + def test_bad_list(self): + req = webob.Request.blank('/v1.0/flavors') + FLAGS.nova_api_permitted_operations = ["foo", "bar", "zoo"] + res = req.get_response(nova.api.API('os')) + self.assertEqual(res.status_int, 404) + + def test_default_all_permitted(self): + req = webob.Request.blank('/v1.0/flavors') + # empty means all operations available. + FLAGS.nova_api_permitted_operations = [] + res = req.get_response(nova.api.API('os')) + self.assertEqual(res.status_int, 200) + + def test_disallowed(self): + req = webob.Request.blank('/v1.0/flavors') + FLAGS.nova_api_permitted_operations = ["server", "backup_schedule", "image", "sharedipgroup"] + res = req.get_response(nova.api.API('os')) + self.assertEqual(res.status_int, 404) + +if __name__ == '__main__': + unittest.main() -- cgit From 66a5ac31c4a5f24da9c0335cf934bbf545c0d95f Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 16 Nov 2010 02:54:13 -0400 Subject: Changed from fine-grained operation control to binary admin on/off setting. --- nova/api/openstack/__init__.py | 56 ++++++++------------- nova/tests/api/openstack/test_adminapi.py | 60 ++++++++++++++++++++++ nova/tests/api/openstack/test_restrictedapi.py | 70 -------------------------- 3 files changed, 82 insertions(+), 104 deletions(-) create mode 100644 nova/tests/api/openstack/test_adminapi.py delete mode 100644 nova/tests/api/openstack/test_restrictedapi.py diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 338d642bc..23ac033cf 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -47,9 +47,9 @@ flags.DEFINE_string('nova_api_auth', 'nova.api.openstack.auth.BasicApiAuthManager', 'The auth mechanism to use for the OpenStack API implemenation') -flags.DEFINE_list('nova_api_permitted_operations', - [], - 'A comma-separated list of permitted api operations. Empty for all.') +flags.DEFINE_bool('allow_admin_api', + False, + 'When True, this API service will accept admin operations.') class API(wsgi.Middleware): """WSGI entry point for all OpenStack API requests.""" @@ -168,37 +168,25 @@ class APIRouter(wsgi.Router): def __init__(self): mapper = routes.Mapper() - commands = { - "server" : dict(plural='servers', - controller=servers.Controller(), - collection={'detail': 'GET'}, - member={'action': 'POST'}), - "backup_schedule" : dict(plural='backup_schedules', - controller=backup_schedules.Controller(), - parent_resource=dict(member_name='server', - collection_name='servers')), - "image" : dict(plural='images', - controller=images.Controller(), - collection={'detail': 'GET'}), - "flavor" : dict(plural='flavors', - controller=flavors.Controller(), - collection={'detail': 'GET'}), - "sharedipgroup" : dict(plural="sharedipgroups", - controller=sharedipgroups.Controller()), - } - - permitted = commands.keys() - if len(FLAGS.nova_api_permitted_operations) > 0: - permitted = FLAGS.nova_api_permitted_operations - logging.debug("Permitted operation set: %s" % (permitted,)) - for command in permitted: - options = commands.get(command, None) - if not options: - logging.warning("Unknown option in nova_api_permitted_operations: '%s' (skipping)" % (command,)) - continue - collection = options['plural'] - del options['plural'] - mapper.resource(command, collection, **options) + mapper.resource("server", "servers", controller=servers.Controller(), + collection={'detail': 'GET'}, + member={'action': 'POST'}) + + mapper.resource("backup_schedule", "backup_schedules", + controller=backup_schedules.Controller(), + parent_resource=dict(member_name='server', + collection_name='servers')) + + mapper.resource("image", "images", controller=images.Controller(), + collection={'detail': 'GET'}) + mapper.resource("flavor", "flavors", controller=flavors.Controller(), + collection={'detail': 'GET'}) + mapper.resource("sharedipgroup", "sharedipgroups", + controller=sharedipgroups.Controller()) + + if FLAGS.allow_admin_api: + logging.debug("Including admin operations in API.") + # TODO: Place routes for admin operations here. super(APIRouter, self).__init__(mapper) diff --git a/nova/tests/api/openstack/test_adminapi.py b/nova/tests/api/openstack/test_adminapi.py new file mode 100644 index 000000000..54cb40e84 --- /dev/null +++ b/nova/tests/api/openstack/test_adminapi.py @@ -0,0 +1,60 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# 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 unittest + +import stubout +import webob + +import nova.api +from nova import flags +from nova.tests.api.openstack import fakes + +FLAGS = flags.FLAGS + +class RestrictedAPITest(unittest.TestCase): + def setUp(self): + self.stubs = stubout.StubOutForTesting() + fakes.FakeAuthManager.auth_data = {} + fakes.FakeAuthDatabase.data = {} + fakes.stub_out_networking(self.stubs) + fakes.stub_out_rate_limiting(self.stubs) + fakes.stub_out_auth(self.stubs) + self.allow_admin = FLAGS.allow_admin_api + + def tearDown(self): + self.stubs.UnsetAll() + FLAGS.allow_admin_api = self.allow_admin + + def test_admin_enabled(self): + FLAGS.allow_admin_api = True + # We should still be able to access public operations. + req = webob.Request.blank('/v1.0/flavors') + res = req.get_response(nova.api.API('os')) + self.assertEqual(res.status_int, 200) + # TODO: Confirm admin operations are available. + + def test_admin_disabled(self): + FLAGS.allow_admin_api = False + # We should still be able to access public operations. + req = webob.Request.blank('/v1.0/flavors') + res = req.get_response(nova.api.API('os')) + self.assertEqual(res.status_int, 200) + # TODO: Confirm admin operations are unavailable. + +if __name__ == '__main__': + unittest.main() diff --git a/nova/tests/api/openstack/test_restrictedapi.py b/nova/tests/api/openstack/test_restrictedapi.py deleted file mode 100644 index 560a66e8c..000000000 --- a/nova/tests/api/openstack/test_restrictedapi.py +++ /dev/null @@ -1,70 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 OpenStack LLC. -# 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 unittest - -import stubout -import webob - -import nova.api -from nova import flags -from nova.api.openstack import flavors -from nova.tests.api.openstack import fakes - -FLAGS = flags.FLAGS - -class RestrictedAPITest(unittest.TestCase): - def setUp(self): - self.stubs = stubout.StubOutForTesting() - fakes.FakeAuthManager.auth_data = {} - fakes.FakeAuthDatabase.data = {} - fakes.stub_out_networking(self.stubs) - fakes.stub_out_rate_limiting(self.stubs) - fakes.stub_out_auth(self.stubs) - self.original_permissions = FLAGS.nova_api_permitted_operations - - def tearDown(self): - self.stubs.UnsetAll() - FLAGS.nova_api_permitted_operations = self.original_permissions - - def test_permitted(self): - req = webob.Request.blank('/v1.0/flavors') - FLAGS.nova_api_permitted_operations = ["server", "backup_schedule", "image", "flavor", "sharedipgroup"] - res = req.get_response(nova.api.API('os')) - self.assertEqual(res.status_int, 200) - - def test_bad_list(self): - req = webob.Request.blank('/v1.0/flavors') - FLAGS.nova_api_permitted_operations = ["foo", "bar", "zoo"] - res = req.get_response(nova.api.API('os')) - self.assertEqual(res.status_int, 404) - - def test_default_all_permitted(self): - req = webob.Request.blank('/v1.0/flavors') - # empty means all operations available. - FLAGS.nova_api_permitted_operations = [] - res = req.get_response(nova.api.API('os')) - self.assertEqual(res.status_int, 200) - - def test_disallowed(self): - req = webob.Request.blank('/v1.0/flavors') - FLAGS.nova_api_permitted_operations = ["server", "backup_schedule", "image", "sharedipgroup"] - res = req.get_response(nova.api.API('os')) - self.assertEqual(res.status_int, 404) - -if __name__ == '__main__': - unittest.main() -- cgit From 10756392157aa5e6029a50a9f38718f3024731c7 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 16 Nov 2010 05:46:40 -0400 Subject: added myself to Authors file. Enjoy spiders. --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index ef1a535ca..22acc34d2 100644 --- a/Authors +++ b/Authors @@ -20,6 +20,7 @@ Michael Gundlach Monty Taylor Paul Voccio Rick Clark +Sandy Walsh Soren Hansen Todd Willey Vishvananda Ishaya -- cgit From f0b53131569cd409a95c68b435ec56a69dcdc897 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 16 Nov 2010 05:53:21 -0400 Subject: PEP8 fixes --- nova/api/openstack/__init__.py | 1 + nova/tests/api/openstack/test_adminapi.py | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 23ac033cf..80b27c7e5 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -51,6 +51,7 @@ flags.DEFINE_bool('allow_admin_api', False, 'When True, this API service will accept admin operations.') + class API(wsgi.Middleware): """WSGI entry point for all OpenStack API requests.""" diff --git a/nova/tests/api/openstack/test_adminapi.py b/nova/tests/api/openstack/test_adminapi.py index 54cb40e84..1b2e1654d 100644 --- a/nova/tests/api/openstack/test_adminapi.py +++ b/nova/tests/api/openstack/test_adminapi.py @@ -26,7 +26,8 @@ from nova.tests.api.openstack import fakes FLAGS = flags.FLAGS -class RestrictedAPITest(unittest.TestCase): + +class AdminAPITest(unittest.TestCase): def setUp(self): self.stubs = stubout.StubOutForTesting() fakes.FakeAuthManager.auth_data = {} @@ -42,7 +43,7 @@ class RestrictedAPITest(unittest.TestCase): def test_admin_enabled(self): FLAGS.allow_admin_api = True - # We should still be able to access public operations. + # We should still be able to access public operations. req = webob.Request.blank('/v1.0/flavors') res = req.get_response(nova.api.API('os')) self.assertEqual(res.status_int, 200) @@ -50,7 +51,7 @@ class RestrictedAPITest(unittest.TestCase): def test_admin_disabled(self): FLAGS.allow_admin_api = False - # We should still be able to access public operations. + # We should still be able to access public operations. req = webob.Request.blank('/v1.0/flavors') res = req.get_response(nova.api.API('os')) self.assertEqual(res.status_int, 200) -- cgit From c3072aea3dc5d44d26fcac5c7db65b8cc445fccc Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Fri, 26 Nov 2010 17:04:27 +0000 Subject: Adding support for modification only of user accounts. --- nova/auth/ldapdriver.py | 110 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 84 insertions(+), 26 deletions(-) diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index ceade1d65..95519d000 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -40,6 +40,8 @@ flags.DEFINE_string('ldap_user_dn', 'cn=Manager,dc=example,dc=com', 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', @@ -89,8 +91,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): @@ -110,7 +111,12 @@ class LdapDriver(object): """Retrieve list of users""" attrs = self.__find_objects(FLAGS.ldap_user_subtree, '(objectclass=novaUser)') - return [self.__to_user(attr) for attr in attrs] + users = [] + for attr in attrs: + user = self.__to_user(attr) + if user != None: + users.append(user) + return users def get_projects(self, uid=None): """Retrieve list of projects""" @@ -125,21 +131,46 @@ 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) + if user.has_key('accessKey') and user.has_key('secretKey') and user.has_key('isAdmin'): + raise exception.Duplicate("LDAP user %s already exists" % name) + else: + # Entry could be malformed, test for missing attrs. + # Malformed entries are useless, replace attributes found. + attr = [] + if user.has_key('secretKey'): + attr.append((self.ldap.MOD_REPLACE, 'secretKey', [secret_key])) + else: + attr.append((self.ldap.MOD_ADD, 'secretKey', [secret_key])) + if user.has_key('accessKey'): + attr.append((self.ldap.MOD_REPLACE, 'accessKey', [access_key])) + else: + attr.append((self.ldap.MOD_ADD, 'accessKey', [access_key])) + if user.has_key('isAdmin'): + attr.append((self.ldap.MOD_REPLACE, 'isAdmin', [str(is_admin).upper()])) + else: + attr.append((self.ldap.MOD_ADD, 'isAdmin', [str(is_admin).upper()])) + self.conn.modify_s(self.__uid_to_dn(name), attr) + return self.get_user(name) + else: + 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)) def create_project(self, name, manager_uid, description=None, member_uids=None): @@ -256,7 +287,21 @@ 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 user.has_key('secretKey'): + attr.append((self.ldap.MOD_DELETE, 'secretKey', user['secretKey'])) + if user.has_key('accessKey'): + attr.append((self.ldap.MOD_DELETE, 'accessKey', user['accessKey'])) + if user.has_key('isAdmin'): + attr.append((self.ldap.MOD_DELETE, 'isAdmin', user['isAdmin'])) + 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 +310,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 = [] @@ -281,10 +326,20 @@ class LdapDriver(object): """Check if user exists""" return self.get_user(uid) != None + def __ldap_user_exists(self, uid): + """Check if the user exists in ldap""" + return self.__get_ldap_user(uid) != None + def __project_exists(self, project_id): """Check if project exists""" return self.get_project(project_id) != 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""" objects = self.__find_objects(dn, query, scope) @@ -449,12 +504,15 @@ class LdapDriver(object): """Convert ldap attributes to User object""" if attr == None: return None - return { - 'id': attr['uid'][0], - 'name': attr['cn'][0], - 'access': attr['accessKey'][0], - 'secret': attr['secretKey'][0], - 'admin': (attr['isAdmin'][0] == 'TRUE')} + if (attr.has_key('accessKey') and attr.has_key('secretKey') and attr.has_key('isAdmin')): + return { + 'id': attr['uid'][0], + 'name': attr['cn'][0], + 'access': attr['accessKey'][0], + 'secret': attr['secretKey'][0], + 'admin': (attr['isAdmin'][0] == 'TRUE')} + else: + return None def __to_project(self, attr): """Convert ldap attributes to Project object""" -- cgit From 8a7e6e0f003e1b3837b918ac9af1564ac1665aae Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Fri, 26 Nov 2010 17:59:48 +0000 Subject: PEP fixes --- nova/auth/ldapdriver.py | 72 ++++++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index 95519d000..fa48c8435 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -91,7 +91,7 @@ class LdapDriver(object): def get_user(self, uid): """Retrieve user by id""" - attr = self.__get_ldap_user(uid) + attr = self.__get_ldap_user(uid) return self.__to_user(attr) def get_user_from_access_key(self, access): @@ -111,11 +111,11 @@ class LdapDriver(object): """Retrieve list of users""" attrs = self.__find_objects(FLAGS.ldap_user_subtree, '(objectclass=novaUser)') - users = [] - for attr in attrs: - user = self.__to_user(attr) - if user != None: - users.append(user) + 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): @@ -135,24 +135,32 @@ class LdapDriver(object): if self.__ldap_user_exists(name): # Retrieve user by name user = self.__get_ldap_user(name) - if user.has_key('accessKey') and user.has_key('secretKey') and user.has_key('isAdmin'): - raise exception.Duplicate("LDAP user %s already exists" % name) + if user.has_key('accessKey') and user.has_key('secretKey') \ + and user.has_key('isAdmin'): + raise exception.Duplicate("LDAP user %s already exists" \ + % name) else: # Entry could be malformed, test for missing attrs. # Malformed entries are useless, replace attributes found. attr = [] if user.has_key('secretKey'): - attr.append((self.ldap.MOD_REPLACE, 'secretKey', [secret_key])) + attr.append((self.ldap.MOD_REPLACE, 'secretKey', \ + [secret_key])) else: - attr.append((self.ldap.MOD_ADD, 'secretKey', [secret_key])) + attr.append((self.ldap.MOD_ADD, 'secretKey', \ + [secret_key])) if user.has_key('accessKey'): - attr.append((self.ldap.MOD_REPLACE, 'accessKey', [access_key])) + attr.append((self.ldap.MOD_REPLACE, 'accessKey', \ + [access_key])) else: - attr.append((self.ldap.MOD_ADD, 'accessKey', [access_key])) + attr.append((self.ldap.MOD_ADD, 'accessKey', \ + [access_key])) if user.has_key('isAdmin'): - attr.append((self.ldap.MOD_REPLACE, 'isAdmin', [str(is_admin).upper()])) + attr.append((self.ldap.MOD_REPLACE, 'isAdmin', \ + [str(is_admin).upper()])) else: - attr.append((self.ldap.MOD_ADD, 'isAdmin', [str(is_admin).upper()])) + attr.append((self.ldap.MOD_ADD, 'isAdmin', \ + [str(is_admin).upper()])) self.conn.modify_s(self.__uid_to_dn(name), attr) return self.get_user(name) else: @@ -186,7 +194,7 @@ class LdapDriver(object): 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 " @@ -293,11 +301,14 @@ class LdapDriver(object): # Retrieve user by name user = self.__get_ldap_user(uid) if user.has_key('secretKey'): - attr.append((self.ldap.MOD_DELETE, 'secretKey', user['secretKey'])) + attr.append((self.ldap.MOD_DELETE, 'secretKey', \ + user['secretKey'])) if user.has_key('accessKey'): - attr.append((self.ldap.MOD_DELETE, 'accessKey', user['accessKey'])) + attr.append((self.ldap.MOD_DELETE, 'accessKey', \ + user['accessKey'])) if user.has_key('isAdmin'): - attr.append((self.ldap.MOD_DELETE, 'isAdmin', user['isAdmin'])) + attr.append((self.ldap.MOD_DELETE, 'isAdmin', \ + user['isAdmin'])) self.conn.modify_s(self.__uid_to_dn(uid), attr) else: # Delete entry @@ -324,18 +335,18 @@ class LdapDriver(object): 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) != None + 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""" + """Retrieve LDAP user entry by id""" attr = self.__find_object(self.__uid_to_dn(uid), '(objectclass=novaUser)') return attr @@ -385,12 +396,12 @@ class LdapDriver(object): def __group_exists(self, dn): """Check if group exists""" - return self.__find_object(dn, '(objectclass=groupOfNames)') != None + return self.__find_object(dn, '(objectclass=groupOfNames)') 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, @@ -404,7 +415,7 @@ 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 " @@ -430,7 +441,7 @@ class LdapDriver(object): 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""" @@ -502,21 +513,22 @@ class LdapDriver(object): @staticmethod def __to_user(attr): """Convert ldap attributes to User object""" - if attr == None: + if attr is None: return None - if (attr.has_key('accessKey') and attr.has_key('secretKey') and attr.has_key('isAdmin')): + if (attr.has_key('accessKey') and attr.has_key('secretKey') \ + and attr.has_key('isAdmin')): return { 'id': attr['uid'][0], 'name': attr['cn'][0], 'access': attr['accessKey'][0], 'secret': attr['secretKey'][0], 'admin': (attr['isAdmin'][0] == 'TRUE')} - else: + else: return None 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 { -- cgit From aaee43a74264d5e6a4ccf638f882b19d477c3c9f Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Tue, 30 Nov 2010 23:12:19 +0000 Subject: Added a script to use OpenDJ as an LDAP server instead of OpenLDAP. Also modified nova.sh to add an USE_OPENDJ option, that will be checked when USE_LDAP is set. --- contrib/nova.sh | 10 ++++- nova/auth/opendj.sh | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 1 deletion(-) create mode 100755 nova/auth/opendj.sh diff --git a/contrib/nova.sh b/contrib/nova.sh index 1a9f93a3b..7eb934eca 100755 --- a/contrib/nova.sh +++ b/contrib/nova.sh @@ -22,6 +22,8 @@ USE_MYSQL=${USE_MYSQL:-0} MYSQL_PASS=${MYSQL_PASS:-nova} TEST=${TEST:-0} USE_LDAP=${USE_LDAP:-0} +# Use OpenDJ instead of OpenLDAP when using LDAP +USE_OPENDJ=${USE_OPENDJ:-0} LIBVIRT_TYPE=${LIBVIRT_TYPE:-qemu} NET_MAN=${NET_MAN:-VlanManager} # NOTE(vish): If you are using FlatDHCP on multiple hosts, set the interface @@ -113,7 +115,13 @@ if [ "$CMD" == "run" ]; then rm $NOVA_DIR/nova.sqlite fi if [ "$USE_LDAP" == 1 ]; then - sudo $NOVA_DIR/nova/auth/slap.sh + if [ "$USE_OPENDJ" == 1 ]; then + echo '--ldap_user_dn=cn=Directory Manager' >> \ + /etc/nova/nova-manage.conf + sudo $NOVA_DIR/nova/auth/opendj.sh + else + sudo $NOVA_DIR/nova/auth/slap.sh + fi fi rm -rf $NOVA_DIR/instances mkdir -p $NOVA_DIR/instances diff --git a/nova/auth/opendj.sh b/nova/auth/opendj.sh new file mode 100755 index 000000000..8052c077d --- /dev/null +++ b/nova/auth/opendj.sh @@ -0,0 +1,119 @@ +#!/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/97-openssh-lpk_sun.ldif +chown opendj:opendj $schemapath/98-nova_sun.ldif + +cat >/etc/ldap/ldap.conf </etc/ldap/base.ldif < Date: Wed, 1 Dec 2010 22:43:46 +0000 Subject: fix nova.sh to reflect new location of ppa --- contrib/nova.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/nova.sh b/contrib/nova.sh index 1a9f93a3b..f3eb8351a 100755 --- a/contrib/nova.sh +++ b/contrib/nova.sh @@ -70,7 +70,7 @@ fi # You should only have to run this once if [ "$CMD" == "install" ]; then sudo apt-get install -y python-software-properties - sudo add-apt-repository ppa:nova-core/ppa + sudo add-apt-repository ppa:nova-core/trunk sudo apt-get update sudo apt-get install -y dnsmasq kpartx kvm gawk iptables ebtables sudo apt-get install -y user-mode-linux kvm libvirt-bin -- cgit From 1e050bb4a8eeb65a7ac25a9fb90493567b5b07f4 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Thu, 2 Dec 2010 15:18:45 +0100 Subject: Add a helpful error message to nova-manage in case of NoMoreNetworks. This is one of the most common problems people have, and the solution is not currently easily discoverable. This should address that. --- bin/nova-manage | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index eb7c6b87b..62eec8353 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -359,9 +359,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): -- cgit From 26571952bb8f1015b11d6b9514d232ad8a20d837 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Thu, 2 Dec 2010 10:21:43 -0800 Subject: Moved reboot/rescue methods into nova.compute.api. --- nova/api/cloud.py | 58 ------------------------------------------- nova/api/ec2/cloud.py | 7 +++--- nova/api/openstack/servers.py | 3 +-- nova/compute/api.py | 27 ++++++++++++++++++++ 4 files changed, 31 insertions(+), 64 deletions(-) delete mode 100644 nova/api/cloud.py diff --git a/nova/api/cloud.py b/nova/api/cloud.py deleted file mode 100644 index b8f15019f..000000000 --- a/nova/api/cloud.py +++ /dev/null @@ -1,58 +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. - -""" -Methods for API calls to control instances via AMQP. -""" - - -from nova import db -from nova import flags -from nova import rpc - -FLAGS = flags.FLAGS - - -def reboot(instance_id, context=None): - """Reboot the given instance.""" - instance_ref = db.instance_get_by_internal_id(context, instance_id) - host = instance_ref['host'] - rpc.cast(context, - db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "reboot_instance", - "args": {"instance_id": instance_ref['id']}}) - - -def rescue(instance_id, context): - """Rescue the given instance.""" - instance_ref = db.instance_get_by_internal_id(context, instance_id) - host = instance_ref['host'] - rpc.cast(context, - db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "rescue_instance", - "args": {"instance_id": instance_ref['id']}}) - - -def unrescue(instance_id, context): - """Unrescue the given instance.""" - instance_ref = db.instance_get_by_internal_id(context, instance_id) - host = instance_ref['host'] - rpc.cast(context, - db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "unrescue_instance", - "args": {"instance_id": instance_ref['id']}}) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index e50906ae1..161d2d038 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -41,7 +41,6 @@ from nova import rpc from nova import utils from nova.compute import api as compute_api from nova.compute import instance_types -from nova.api import cloud from nova.image.s3 import S3ImageService @@ -834,19 +833,19 @@ class CloudController(object): """instance_id is a list of instance ids""" for ec2_id in instance_id: internal_id = ec2_id_to_internal_id(ec2_id) - cloud.reboot(internal_id, context=context) + self.compute_api.reboot(context, internal_id) return True def rescue_instance(self, context, instance_id, **kwargs): """This is an extension to the normal ec2_api""" internal_id = ec2_id_to_internal_id(instance_id) - cloud.rescue(internal_id, context=context) + self.compute_api.rescue(context, internal_id) return True def unrescue_instance(self, context, instance_id, **kwargs): """This is an extension to the normal ec2_api""" internal_id = ec2_id_to_internal_id(instance_id) - cloud.unrescue(internal_id, context=context) + self.compute_api.unrescue(context, internal_id) return True def update_instance(self, context, ec2_id, **kwargs): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 11170bbf5..d34dd78fb 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -25,7 +25,6 @@ from nova import rpc from nova import utils from nova import wsgi from nova import context -from nova.api import cloud from nova.api.openstack import faults from nova.compute import api as compute_api from nova.compute import instance_types @@ -191,7 +190,7 @@ class Controller(wsgi.Controller): inst_ref = self.db.instance_get_by_internal_id(ctxt, int(id)) if not inst_ref or (inst_ref and not inst_ref.user_id == user_id): return faults.Fault(exc.HTTPUnprocessableEntity()) - cloud.reboot(id) + self.compute_api.reboot(ctxt, id) def _get_network_topic(self, context): """Retrieves the network host for a project""" diff --git a/nova/compute/api.py b/nova/compute/api.py index 929342a1e..da01ca61a 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -210,3 +210,30 @@ class ComputeAPI(base.Base): """ self.db.instance_update(context, instance_id, kwargs) + + def reboot(self, context, instance_id): + """Reboot the given instance.""" + instance = self.db.instance_get_by_internal_id(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "reboot_instance", + "args": {"instance_id": instance['id']}}) + + def rescue(self, context, instance_id): + """Rescue the given instance.""" + instance = self.db.instance_get_by_internal_id(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "rescue_instance", + "args": {"instance_id": instance['id']}}) + + def unrescue(self, context, instance_id): + """Unrescue the given instance.""" + instance = self.db.instance_get_by_internal_id(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "unrescue_instance", + "args": {"instance_id": instance['id']}}) -- cgit From 47b47bc4ae34f90a6d1c59718b5ee759fb7c7327 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Thu, 2 Dec 2010 15:26:14 -0800 Subject: Pushed terminate instance and network manager/topic methods into network.compute.api. --- nova/api/ec2/cloud.py | 65 ++----------------------- nova/api/openstack/servers.py | 26 +++------- nova/compute/api.py | 82 ++++++++++++++++++++++++++++---- nova/tests/api/openstack/test_servers.py | 13 +++++ 4 files changed, 98 insertions(+), 88 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 161d2d038..7978e08a0 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -94,7 +94,7 @@ class CloudController(object): """ def __init__(self): self.network_manager = utils.import_object(FLAGS.network_manager) - self.compute_api = compute_api.ComputeAPI() + self.compute_api = compute_api.ComputeAPI(self.network_manager) self.image_service = S3ImageService() self.setup() @@ -752,7 +752,6 @@ class CloudController(object): instance_types.get_by_type(kwargs.get('instance_type', None)), self.image_service, kwargs['image_id'], - self._get_network_topic(context), min_count=int(kwargs.get('min_count', max_count)), max_count=max_count, kernel_id=kwargs.get('kernel_id'), @@ -768,65 +767,11 @@ class CloudController(object): def terminate_instances(self, context, instance_id, **kwargs): """Terminate each instance in instance_id, which is a list of ec2 ids. - - instance_id is a kwarg so its name cannot be modified. - """ - ec2_id_list = instance_id + instance_id is a kwarg so its name cannot be modified.""" logging.debug("Going to start terminating instances") - for id_str in ec2_id_list: - internal_id = ec2_id_to_internal_id(id_str) - logging.debug("Going to try and terminate %s" % id_str) - try: - instance_ref = db.instance_get_by_internal_id(context, - internal_id) - except exception.NotFound: - logging.warning("Instance %s was not found during terminate", - id_str) - continue - - if (instance_ref['state_description'] == 'terminating'): - logging.warning("Instance %s is already being terminated", - id_str) - continue - now = datetime.datetime.utcnow() - self.compute_api.update_instance(context, - instance_ref['id'], - state_description='terminating', - state=0, - terminated_at=now) - - # FIXME(ja): where should network deallocate occur? - address = db.instance_get_floating_address(context, - instance_ref['id']) - if address: - logging.debug("Disassociating address %s" % address) - # NOTE(vish): Right now we don't really care if the ip is - # disassociated. We may need to worry about - # checking this later. Perhaps in the scheduler? - network_topic = self._get_network_topic(context) - rpc.cast(context, - network_topic, - {"method": "disassociate_floating_ip", - "args": {"floating_address": address}}) - - address = db.instance_get_fixed_address(context, - instance_ref['id']) - if address: - logging.debug("Deallocating address %s" % address) - # NOTE(vish): Currently, nothing needs to be done on the - # network node until release. If this changes, - # we will need to cast here. - self.network_manager.deallocate_fixed_ip(context.elevated(), - address) - - host = instance_ref['host'] - if host: - rpc.cast(context, - db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "terminate_instance", - "args": {"instance_id": instance_ref['id']}}) - else: - db.instance_destroy(context, instance_ref['id']) + for ec2_id in instance_id: + internal_id = ec2_id_to_internal_id(ec2_id) + self.compute_api.delete_instance(context, internal_id) return True def reboot_instances(self, context, instance_id, **kwargs): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index d34dd78fb..1d93f783c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -20,11 +20,12 @@ import time import webob from webob import exc +from nova import context +from nova import exception from nova import flags from nova import rpc from nova import utils from nova import wsgi -from nova import context from nova.api.openstack import faults from nova.compute import api as compute_api from nova.compute import instance_types @@ -94,7 +95,6 @@ class Controller(wsgi.Controller): if not db_driver: db_driver = FLAGS.db_driver self.db_driver = utils.import_object(db_driver) - self.network_manager = utils.import_object(FLAGS.network_manager) self.compute_api = compute_api.ComputeAPI() super(Controller, self).__init__() @@ -132,11 +132,11 @@ class Controller(wsgi.Controller): """ Destroys a server """ user_id = req.environ['nova.context']['user']['id'] ctxt = context.RequestContext(user_id, user_id) - instance = self.db_driver.instance_get_by_internal_id(ctxt, int(id)) - if instance and instance['user_id'] == user_id: - self.db_driver.instance_destroy(ctxt, id) - return faults.Fault(exc.HTTPAccepted()) - return faults.Fault(exc.HTTPNotFound()) + try: + self.compute_api.delete_instance(ctxt, int(id)) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + return faults.Fault(exc.HTTPAccepted()) def create(self, req): """ Creates a new server for a given user """ @@ -151,7 +151,6 @@ class Controller(wsgi.Controller): instance_types.get_by_flavor_id(env['server']['flavorId']), utils.import_object(FLAGS.image_service), env['server']['imageId'], - self._get_network_topic(ctxt), name=env['server']['name'], description=env['server']['name'], key_name=key_pair['name'], @@ -191,14 +190,3 @@ class Controller(wsgi.Controller): if not inst_ref or (inst_ref and not inst_ref.user_id == user_id): return faults.Fault(exc.HTTPUnprocessableEntity()) self.compute_api.reboot(ctxt, id) - - def _get_network_topic(self, context): - """Retrieves the network host for a project""" - network_ref = self.network_manager.get_network(context) - host = network_ref['host'] - if not host: - host = rpc.call(context, - FLAGS.network_topic, - {"method": "set_network_host", - "args": {"network_id": network_ref['id']}}) - return self.db_driver.queue_get_for(context, FLAGS.network_topic, host) diff --git a/nova/compute/api.py b/nova/compute/api.py index da01ca61a..457d6e27f 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -20,6 +20,7 @@ Handles all API requests relating to instances (guest vms). """ +import datetime import logging import time @@ -43,17 +44,17 @@ def generate_default_hostname(internal_id): class ComputeAPI(base.Base): """API for interacting with the compute manager.""" - def __init__(self, **kwargs): - self.network_manager = utils.import_object(FLAGS.network_manager) + def __init__(self, network_manager=None, **kwargs): + if not network_manager: + network_manager = utils.import_object(FLAGS.network_manager) + self.network_manager = network_manager super(ComputeAPI, self).__init__(**kwargs) - # TODO(eday): network_topic arg should go away once we push network - # allocation into the scheduler or compute worker. def create_instances(self, context, instance_type, image_service, image_id, - network_topic, min_count=1, max_count=1, - kernel_id=None, ramdisk_id=None, name='', - description='', user_data='', key_name=None, - key_data=None, security_group='default', + min_count=1, max_count=1, kernel_id=None, + ramdisk_id=None, name='', description='', + user_data='', key_name=None, key_data=None, + security_group='default', generate_hostname=generate_default_hostname): """Create the number of instances requested if quote and other arguments check out ok.""" @@ -139,7 +140,7 @@ class ComputeAPI(base.Base): instance_id, is_vpn) rpc.cast(elevated, - network_topic, + self._get_network_topic(context), {"method": "setup_fixed_ip", "args": {"address": address}}) @@ -211,6 +212,58 @@ class ComputeAPI(base.Base): """ self.db.instance_update(context, instance_id, kwargs) + def delete_instance(self, context, instance_id): + logging.debug("Going to try and terminate %d" % instance_id) + try: + instance = self.db.instance_get_by_internal_id(context, + instance_id) + except exception.NotFound as e: + logging.warning("Instance %d was not found during terminate", + instance_id) + raise e + + if (instance['state_description'] == 'terminating'): + logging.warning("Instance %d is already being terminated", + instance_id) + return + + self.update_instance(context, + instance['id'], + state_description='terminating', + state=0, + terminated_at=datetime.datetime.utcnow()) + + # FIXME(ja): where should network deallocate occur? + address = self.db.instance_get_floating_address(context, + instance['id']) + if address: + logging.debug("Disassociating address %s" % address) + # NOTE(vish): Right now we don't really care if the ip is + # disassociated. We may need to worry about + # checking this later. Perhaps in the scheduler? + rpc.cast(context, + self._get_network_topic(context), + {"method": "disassociate_floating_ip", + "args": {"floating_address": address}}) + + address = self.db.instance_get_fixed_address(context, instance['id']) + if address: + logging.debug("Deallocating address %s" % address) + # NOTE(vish): Currently, nothing needs to be done on the + # network node until release. If this changes, + # we will need to cast here. + self.network_manager.deallocate_fixed_ip(context.elevated(), + address) + + host = instance['host'] + if host: + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "terminate_instance", + "args": {"instance_id": instance['id']}}) + else: + self.db.instance_destroy(context, instance['id']) + def reboot(self, context, instance_id): """Reboot the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) @@ -237,3 +290,14 @@ class ComputeAPI(base.Base): self.db.queue_get_for(context, FLAGS.compute_topic, host), {"method": "unrescue_instance", "args": {"instance_id": instance['id']}}) + + def _get_network_topic(self, context): + """Retrieves the network host for a project""" + network_ref = self.network_manager.get_network(context) + host = network_ref['host'] + if not host: + host = rpc.call(context, + FLAGS.network_topic, + {"method": "set_network_host", + "args": {"network_id": network_ref['id']}}) + return self.db.queue_get_for(context, FLAGS.network_topic, host) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 2eee4e506..aebb3d1b5 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -47,6 +47,14 @@ def return_security_group(context, instance_id, security_group_id): pass +def instance_update(context, instance_id, kwargs): + pass + + +def instance_address(context, instance_id): + return None + + def stub_instance(id, user_id=1): return Instance(id=id, state=0, image_id=10, display_name='server%s' % id, user_id=user_id) @@ -69,6 +77,11 @@ class ServersTest(unittest.TestCase): return_servers) self.stubs.Set(nova.db.api, 'instance_add_security_group', return_security_group) + self.stubs.Set(nova.db.api, 'instance_update', instance_update) + self.stubs.Set(nova.db.api, 'instance_get_fixed_address', + instance_address) + self.stubs.Set(nova.db.api, 'instance_get_floating_address', + instance_address) def tearDown(self): self.stubs.UnsetAll() -- cgit From 108bab90cb70798151b8e6a09d2176a3eb120380 Mon Sep 17 00:00:00 2001 From: Ryan Lucio Date: Thu, 2 Dec 2010 17:01:44 -0800 Subject: Updated sqlalchemy model to make the internal_id column of the instances table as unsigned integer --- nova/db/sqlalchemy/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index fe0a9a921..18ba80caf 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -27,6 +27,7 @@ from sqlalchemy import ForeignKey, DateTime, Boolean, Text from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.schema import ForeignKeyConstraint +from sqlalchemy.databases import mysql from nova.db.sqlalchemy.session import get_session @@ -155,7 +156,7 @@ class Instance(BASE, NovaBase): """Represents a guest vm.""" __tablename__ = 'instances' id = Column(Integer, primary_key=True) - internal_id = Column(Integer, unique=True) + internal_id = Column(mysql.MSInteger(unsigned=True), unique=True) admin_pass = Column(String(255)) -- cgit From 4203aa1060e5a97bed86d2e201c4c2443ef7e042 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Fri, 3 Dec 2010 12:21:18 -0800 Subject: Finished cleaning up the openstack servers API, it no longer touches the database directly. Also cleaned up similar things in ec2 API and refactored a couple methods in nova.compute.api to accomodate this work. --- nova/api/ec2/cloud.py | 25 ++++----- nova/api/openstack/servers.py | 48 ++++++---------- nova/auth/manager.py | 4 ++ nova/compute/api.py | 96 +++++++++++++++----------------- nova/db/sqlalchemy/api.py | 1 + nova/flags.py | 2 +- nova/tests/api/openstack/fakes.py | 3 +- nova/tests/api/openstack/test_servers.py | 10 ++-- nova/tests/compute_unittest.py | 24 +++----- 9 files changed, 94 insertions(+), 119 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 7978e08a0..4eef5e1ef 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -41,7 +41,6 @@ from nova import rpc from nova import utils from nova.compute import api as compute_api from nova.compute import instance_types -from nova.image.s3 import S3ImageService FLAGS = flags.FLAGS @@ -94,8 +93,9 @@ class CloudController(object): """ def __init__(self): self.network_manager = utils.import_object(FLAGS.network_manager) - self.compute_api = compute_api.ComputeAPI(self.network_manager) - self.image_service = S3ImageService() + self.image_service = utils.import_object(FLAGS.image_service) + self.compute_api = compute_api.ComputeAPI(self.network_manager, + self.image_service) self.setup() def __str__(self): @@ -119,7 +119,7 @@ class CloudController(object): def _get_mpi_data(self, context, project_id): result = {} - for instance in db.instance_get_all_by_project(context, project_id): + for instance in self.compute_api.get_instances(context, project_id): if instance['fixed_ip']: line = '%s slots=%d' % (instance['fixed_ip']['address'], instance['vcpus']) @@ -438,7 +438,7 @@ class CloudController(object): # instance_id is passed in as a list of instances ec2_id = instance_id[0] internal_id = ec2_id_to_internal_id(ec2_id) - instance_ref = db.instance_get_by_internal_id(context, internal_id) + instance_ref = self.compute_api.get_instance(context, internal_id) output = rpc.call(context, '%s.%s' % (FLAGS.compute_topic, instance_ref['host']), @@ -535,7 +535,7 @@ class CloudController(object): if volume_ref['attach_status'] == "attached": raise exception.ApiError("Volume is already attached") internal_id = ec2_id_to_internal_id(instance_id) - instance_ref = db.instance_get_by_internal_id(context, internal_id) + instance_ref = self.compute_api.get_instance(context, internal_id) host = instance_ref['host'] rpc.cast(context, db.queue_get_for(context, FLAGS.compute_topic, host), @@ -613,11 +613,7 @@ class CloudController(object): instances = db.instance_get_all_by_reservation(context, reservation_id) else: - if context.user.is_admin(): - instances = db.instance_get_all(context) - else: - instances = db.instance_get_all_by_project(context, - context.project_id) + instances = self.compute_api.get_instances(context) for instance in instances: if not context.user.is_admin(): if instance['image_id'] == FLAGS.vpn_image_id: @@ -714,7 +710,7 @@ class CloudController(object): def associate_address(self, context, instance_id, public_ip, **kwargs): internal_id = ec2_id_to_internal_id(instance_id) - instance_ref = db.instance_get_by_internal_id(context, internal_id) + instance_ref = self.compute_api.get_instance(context, internal_id) fixed_address = db.instance_get_fixed_address(context, instance_ref['id']) floating_ip_ref = db.floating_ip_get_by_address(context, public_ip) @@ -750,13 +746,12 @@ class CloudController(object): max_count = int(kwargs.get('max_count', 1)) instances = self.compute_api.create_instances(context, instance_types.get_by_type(kwargs.get('instance_type', None)), - self.image_service, kwargs['image_id'], min_count=int(kwargs.get('min_count', max_count)), max_count=max_count, kernel_id=kwargs.get('kernel_id'), ramdisk_id=kwargs.get('ramdisk_id'), - name=kwargs.get('display_name'), + display_name=kwargs.get('display_name'), description=kwargs.get('display_description'), user_data=kwargs.get('user_data', ''), key_name=kwargs.get('key_name'), @@ -801,7 +796,7 @@ class CloudController(object): changes[field] = kwargs[field] if changes: internal_id = ec2_id_to_internal_id(ec2_id) - inst = db.instance_get_by_internal_id(context, internal_id) + inst = self.compute_api.get_instance(context, internal_id) db.instance_update(context, inst['id'], kwargs) return True diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index a9da14867..b644876b0 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -15,23 +15,17 @@ # License for the specific language governing permissions and limitations # under the License. -import webob from webob import exc from nova import context from nova import exception -from nova import flags -from nova import rpc -from nova import utils from nova import wsgi from nova.api.openstack import faults +from nova.auth import manager as auth_manager from nova.compute import api as compute_api from nova.compute import instance_types from nova.compute import power_state import nova.api.openstack -import nova.image.service - -FLAGS = flags.FLAGS def _entity_list(entities): @@ -79,10 +73,7 @@ class Controller(wsgi.Controller): "server": ["id", "imageId", "name", "flavorId", "hostId", "status", "progress"]}}} - def __init__(self, db_driver=None): - if not db_driver: - db_driver = FLAGS.db_driver - self.db_driver = utils.import_object(db_driver) + def __init__(self): self.compute_api = compute_api.ComputeAPI() super(Controller, self).__init__() @@ -101,7 +92,7 @@ class Controller(wsgi.Controller): """ user_id = req.environ['nova.context']['user']['id'] ctxt = context.RequestContext(user_id, user_id) - instance_list = self.db_driver.instance_get_all_by_user(ctxt, user_id) + instance_list = self.compute_api.get_instances(ctxt) limited_list = nova.api.openstack.limited(instance_list, req) res = [entity_maker(inst)['server'] for inst in limited_list] return _entity_list(res) @@ -110,7 +101,7 @@ class Controller(wsgi.Controller): """ Returns server details by server id """ user_id = req.environ['nova.context']['user']['id'] ctxt = context.RequestContext(user_id, user_id) - inst = self.db_driver.instance_get_by_internal_id(ctxt, int(id)) + inst = self.compute_api.get_instance(ctxt, int(id)) if inst: if inst.user_id == user_id: return _entity_detail(inst) @@ -134,12 +125,11 @@ class Controller(wsgi.Controller): user_id = req.environ['nova.context']['user']['id'] ctxt = context.RequestContext(user_id, user_id) - key_pair = self.db_driver.key_pair_get_all_by_user(None, user_id)[0] + key_pair = auth_manager.AuthManager.get_key_pairs(ctxt)[0] instances = self.compute_api.create_instances(ctxt, instance_types.get_by_flavor_id(env['server']['flavorId']), - utils.import_object(FLAGS.image_service), env['server']['imageId'], - name=env['server']['name'], + display_name=env['server']['name'], description=env['server']['name'], key_name=key_pair['name'], key_data=key_pair['public_key']) @@ -149,27 +139,24 @@ class Controller(wsgi.Controller): """ Updates the server name or password """ user_id = req.environ['nova.context']['user']['id'] ctxt = context.RequestContext(user_id, user_id) - inst_dict = self._deserialize(req.body, req) - if not inst_dict: return faults.Fault(exc.HTTPUnprocessableEntity()) - instance = self.db_driver.instance_get_by_internal_id(ctxt, int(id)) - if not instance or instance.user_id != user_id: - return faults.Fault(exc.HTTPNotFound()) - update_dict = {} if 'adminPass' in inst_dict['server']: update_dict['admin_pass'] = inst_dict['server']['adminPass'] if 'name' in inst_dict['server']: update_dict['display_name'] = inst_dict['server']['name'] - self.compute_api.update_instance(ctxt, instance['id'], update_dict) + try: + self.compute_api.update_instance(ctxt, instance['id'], update_dict) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) return exc.HTTPNoContent() def action(self, req, id): - """ multi-purpose method used to reboot, rebuild, and + """ Multi-purpose method used to reboot, rebuild, and resize a server """ user_id = req.environ['nova.context']['user']['id'] ctxt = context.RequestContext(user_id, user_id) @@ -177,10 +164,11 @@ class Controller(wsgi.Controller): try: reboot_type = input_dict['reboot']['type'] except Exception: - raise faults.Fault(webob.exc.HTTPNotImplemented()) - inst_ref = self.db.instance_get_by_internal_id(ctxt, int(id)) - if not inst_ref or (inst_ref and not inst_ref.user_id == user_id): + raise faults.Fault(exc.HTTPNotImplemented()) + try: + # TODO(gundlach): pass reboot_type, support soft reboot in + # virt driver + self.compute_api.reboot(ctxt, id) + except: return faults.Fault(exc.HTTPUnprocessableEntity()) - # TODO(gundlach): pass reboot_type, support soft reboot in - # virt driver - self.compute_api.reboot(ctxt, id) + return exc.HTTPNoContent() diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 7b2b68161..11c3bd6df 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -624,6 +624,10 @@ class AuthManager(object): with self.driver() as drv: drv.modify_user(uid, access_key, secret_key, admin) + @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): """Get credential zip for user in project""" if not isinstance(user, User): diff --git a/nova/compute/api.py b/nova/compute/api.py index 457d6e27f..995bed91b 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -44,16 +44,19 @@ def generate_default_hostname(internal_id): class ComputeAPI(base.Base): """API for interacting with the compute manager.""" - def __init__(self, network_manager=None, **kwargs): + def __init__(self, network_manager=None, image_service=None, **kwargs): if not network_manager: network_manager = utils.import_object(FLAGS.network_manager) self.network_manager = network_manager + if not image_service: + image_service = utils.import_object(FLAGS.image_service) + self.image_service = image_service super(ComputeAPI, self).__init__(**kwargs) - def create_instances(self, context, instance_type, image_service, image_id, - min_count=1, max_count=1, kernel_id=None, - ramdisk_id=None, name='', description='', - user_data='', key_name=None, key_data=None, + def create_instances(self, context, instance_type, image_id, min_count=1, + max_count=1, kernel_id=None, ramdisk_id=None, + display_name='', description='', user_data='', + key_name=None, key_data=None, security_group='default', generate_hostname=generate_default_hostname): """Create the number of instances requested if quote and @@ -70,15 +73,15 @@ class ComputeAPI(base.Base): is_vpn = image_id == FLAGS.vpn_image_id if not is_vpn: - image = image_service.show(context, image_id) + image = self.image_service.show(context, image_id) if kernel_id is None: kernel_id = image.get('kernelId', FLAGS.default_kernel) if ramdisk_id is None: ramdisk_id = image.get('ramdiskId', FLAGS.default_ramdisk) # Make sure we have access to kernel and ramdisk - image_service.show(context, kernel_id) - image_service.show(context, ramdisk_id) + self.image_service.show(context, kernel_id) + self.image_service.show(context, ramdisk_id) if security_group is None: security_group = ['default'] @@ -111,7 +114,7 @@ class ComputeAPI(base.Base): 'memory_mb': type_data['memory_mb'], 'vcpus': type_data['vcpus'], 'local_gb': type_data['local_gb'], - 'display_name': name, + 'display_name': display_name, 'display_description': description, 'key_name': key_name, 'key_data': key_data} @@ -123,14 +126,25 @@ class ComputeAPI(base.Base): instance = dict(mac_address=utils.generate_mac(), launch_index=num, **base_options) - instance_ref = self.create_instance(context, security_groups, - **instance) - instance_id = instance_ref['id'] - internal_id = instance_ref['internal_id'] - hostname = generate_hostname(internal_id) - self.update_instance(context, instance_id, hostname=hostname) - instances.append(dict(id=instance_id, internal_id=internal_id, - hostname=hostname, **instance)) + instance = self.db.instance_create(context, instance) + instance_id = instance['id'] + internal_id = instance['internal_id'] + + elevated = context.elevated() + if not security_groups: + security_groups = [] + for security_group_id in security_groups: + self.db.instance_add_security_group(elevated, + instance_id, + security_group_id) + + # Set sane defaults if not specified + updates = dict(hostname=generate_hostname(internal_id)) + if 'display_name' not in instance: + updates['display_name'] = "Server %s" % internal_id + + instance = self.update_instance(context, instance_id, **updates) + instances.append(instance) # TODO(vish): This probably should be done in the scheduler # or in compute as a call. The network should be @@ -165,39 +179,6 @@ class ComputeAPI(base.Base): 'project_id': context.project_id} group = db.security_group_create(context, values) - def create_instance(self, context, security_groups=None, **kwargs): - """Creates the instance in the datastore and returns the - new instance as a mapping - - :param context: The security context - :param security_groups: list of security group ids to - attach to the instance - :param kwargs: All additional keyword args are treated - as data fields of the instance to be - created - - :retval Returns a mapping of the instance information - that has just been created - - """ - instance_ref = self.db.instance_create(context, kwargs) - inst_id = instance_ref['id'] - # Set sane defaults if not specified - if kwargs.get('display_name') is None: - display_name = "Server %s" % instance_ref['internal_id'] - instance_ref['display_name'] = display_name - self.db.instance_update(context, inst_id, - {'display_name': display_name}) - - elevated = context.elevated() - if not security_groups: - security_groups = [] - for security_group_id in security_groups: - self.db.instance_add_security_group(elevated, - inst_id, - security_group_id) - return instance_ref - def update_instance(self, context, instance_id, **kwargs): """Updates the instance in the datastore. @@ -210,7 +191,7 @@ class ComputeAPI(base.Base): :retval None """ - self.db.instance_update(context, instance_id, kwargs) + return self.db.instance_update(context, instance_id, kwargs) def delete_instance(self, context, instance_id): logging.debug("Going to try and terminate %d" % instance_id) @@ -264,6 +245,19 @@ class ComputeAPI(base.Base): else: self.db.instance_destroy(context, instance['id']) + def get_instances(self, context, project_id=None): + if project_id or not context.is_admin: + if not context.project: + return self.db.instance_get_all_by_user(context, + context.user_id) + if project_id is None: + project_id = context.project_id + return self.db.instance_get_all_by_project(context, project_id) + return self.db.instance_get_all(context) + + def get_instance(self, context, instance_id): + return self.db.instance_get_by_internal_id(context, instance_id) + def reboot(self, context, instance_id): """Reboot the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index dd9649054..ef58f3490 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -732,6 +732,7 @@ def instance_update(context, instance_id, values): instance_ref = instance_get(context, instance_id, session=session) instance_ref.update(values) instance_ref.save(session=session) + return instance_ref def instance_add_security_group(context, instance_id, security_group_id): diff --git a/nova/flags.py b/nova/flags.py index 1f94feb08..c6578023d 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -259,7 +259,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(), diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 7c0343942..c3f129a32 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -67,8 +67,7 @@ def fake_wsgi(self, req): def stub_out_key_pair_funcs(stubs): def key_pair(context, user_id): return [dict(name='key', public_key='public_key')] - stubs.Set(nova.db.api, 'key_pair_get_all_by_user', - key_pair) + stubs.Set(nova.db, 'key_pair_get_all_by_user', key_pair) def stub_out_image_service(stubs): diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 46b9c5348..8444b6fce 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -48,7 +48,7 @@ def return_security_group(context, instance_id, security_group_id): def instance_update(context, instance_id, kwargs): - pass + return stub_instance(instance_id) def instance_address(context, instance_id): @@ -106,11 +106,11 @@ class ServersTest(unittest.TestCase): i += 1 def test_create_instance(self): - def server_update(context, id, params): - pass - def instance_create(context, inst): - return {'id': 1, 'internal_id': 1} + return {'id': 1, 'internal_id': 1, 'display_name': ''} + + def server_update(context, id, params): + return instance_create(context, id) def fake_method(*args, **kwargs): pass diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py index a55449739..6f3ef96cb 100644 --- a/nova/tests/compute_unittest.py +++ b/nova/tests/compute_unittest.py @@ -72,33 +72,27 @@ 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): -- cgit From 4f2a8c5398d4d4848f441e366e8bcc5e97a0b34f Mon Sep 17 00:00:00 2001 From: Ryan Lucio Date: Fri, 3 Dec 2010 13:50:30 -0800 Subject: Decreased the maximum value for instance-id generation from uint32 to int32 to avoid truncation when being entered into the instance table. Reverted fix to make internal_id column a uint --- nova/db/sqlalchemy/api.py | 2 +- nova/db/sqlalchemy/models.py | 3 +-- nova/image/local.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index dd9649054..2dc140274 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -543,7 +543,7 @@ def instance_create(context, values): with session.begin(): while instance_ref.internal_id == None: # Instances have integer internal ids. - internal_id = random.randint(0, 2 ** 32 - 1) + internal_id = random.randint(0, 2 ** 31 - 1) if not instance_internal_id_exists(context, internal_id, session=session): instance_ref.internal_id = internal_id diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 18ba80caf..fe0a9a921 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -27,7 +27,6 @@ from sqlalchemy import ForeignKey, DateTime, Boolean, Text from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.schema import ForeignKeyConstraint -from sqlalchemy.databases import mysql from nova.db.sqlalchemy.session import get_session @@ -156,7 +155,7 @@ class Instance(BASE, NovaBase): """Represents a guest vm.""" __tablename__ = 'instances' id = Column(Integer, primary_key=True) - internal_id = Column(mysql.MSInteger(unsigned=True), unique=True) + internal_id = Column(Integer, unique=True) admin_pass = Column(String(255)) diff --git a/nova/image/local.py b/nova/image/local.py index 9b0cdcc50..b44593221 100644 --- a/nova/image/local.py +++ b/nova/image/local.py @@ -59,7 +59,7 @@ class LocalImageService(service.BaseImageService): """ Store the image data and return the new image id. """ - id = random.randint(0, 2 ** 32 - 1) + id = random.randint(0, 2 ** 31 - 1) data['id'] = id self.update(context, id, data) return id -- cgit From 76fd35b62bf565fe626ca30c412178894d8e579c Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Mon, 6 Dec 2010 15:14:41 -0500 Subject: Don't wrap HTTPAccepted in a fault. Correctly pass kwargs to update_instance. --- nova/api/openstack/servers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index a9da14867..e7ab17d03 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -124,7 +124,7 @@ class Controller(wsgi.Controller): self.compute_api.delete_instance(ctxt, int(id)) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return faults.Fault(exc.HTTPAccepted()) + return exc.HTTPAccepted() def create(self, req): """ Creates a new server for a given user """ @@ -165,7 +165,7 @@ class Controller(wsgi.Controller): if 'name' in inst_dict['server']: update_dict['display_name'] = inst_dict['server']['name'] - self.compute_api.update_instance(ctxt, instance['id'], update_dict) + self.compute_api.update_instance(ctxt, instance['id'], **update_dict) return exc.HTTPNoContent() def action(self, req, id): @@ -184,3 +184,4 @@ class Controller(wsgi.Controller): # TODO(gundlach): pass reboot_type, support soft reboot in # virt driver self.compute_api.reboot(ctxt, id) + return exc.HTTPAccepted() -- cgit From 88c0e3e380d50d5794970063bbe464171089f260 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Tue, 7 Dec 2010 04:41:53 +0000 Subject: modified a few files --- nova/api/ec2/cloud.py | 1 - nova/compute/api.py | 14 ++++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index e50906ae1..a05dc0f1c 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -760,7 +760,6 @@ class CloudController(object): ramdisk_id=kwargs.get('ramdisk_id'), name=kwargs.get('display_name'), description=kwargs.get('display_description'), - user_data=kwargs.get('user_data', ''), key_name=kwargs.get('key_name'), security_group=kwargs.get('security_group'), generate_hostname=internal_id_to_ec2_id) diff --git a/nova/compute/api.py b/nova/compute/api.py index 929342a1e..6830bacb8 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -52,7 +52,7 @@ class ComputeAPI(base.Base): def create_instances(self, context, instance_type, image_service, image_id, network_topic, min_count=1, max_count=1, kernel_id=None, ramdisk_id=None, name='', - description='', user_data='', key_name=None, + description='', key_name=None, key_data=None, security_group='default', generate_hostname=generate_default_hostname): """Create the number of instances requested if quote and @@ -143,8 +143,8 @@ class ComputeAPI(base.Base): {"method": "setup_fixed_ip", "args": {"address": address}}) - logging.debug("Casting to scheduler for %s/%s's instance %s" % - (context.project_id, context.user_id, instance_id)) + logging.debug("Casting to scheduler for %s/%s's instance %s", + context.project_id, context.user_id, instance_id) rpc.cast(context, FLAGS.scheduler_topic, {"method": "run_instance", @@ -154,6 +154,12 @@ class ComputeAPI(base.Base): return instances def ensure_default_security_group(self, context): + """ Create security group for the security context if it + does not already exist + + :param context: the security context + + """ try: db.security_group_get_by_name(context, context.project_id, 'default') @@ -162,7 +168,7 @@ class ComputeAPI(base.Base): 'description': 'default', 'user_id': context.user_id, 'project_id': context.project_id} - group = db.security_group_create(context, values) + db.security_group_create(context, values) def create_instance(self, context, security_groups=None, **kwargs): """Creates the instance in the datastore and returns the -- cgit From c1a40a8381ae3e559b3faad4a93ffec1abe8907f Mon Sep 17 00:00:00 2001 From: Eric Day Date: Tue, 7 Dec 2010 10:06:49 -0800 Subject: Added docstring for get_instances. --- nova/compute/api.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nova/compute/api.py b/nova/compute/api.py index 995bed91b..cb23dae55 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -246,6 +246,9 @@ class ComputeAPI(base.Base): self.db.instance_destroy(context, instance['id']) def get_instances(self, context, project_id=None): + """Get all instances, possibly filtered by project ID or + user ID. If there is no filter and the context is an admin, + it will retreive all instances in the system.""" if project_id or not context.is_admin: if not context.project: return self.db.instance_get_all_by_user(context, -- cgit From d7ca22cce7df319efc57a2e8224016817c92bbdb Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Tue, 7 Dec 2010 18:57:44 +0000 Subject: importing XenAPI module loaded late --- nova/virt/xenapi/vm_utils.py | 9 ++++++--- nova/virt/xenapi/vmops.py | 6 +++++- nova/virt/xenapi_conn.py | 8 +++++++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 407acda6e..99d484ca2 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -20,15 +20,14 @@ their attributes like VDIs, VIFs, as well as their lookup functions. """ import logging -import XenAPI from twisted.internet import defer from nova import utils from nova.auth.manager import AuthManager from nova.compute import instance_types -from nova.virt import images from nova.compute import power_state +from nova.virt import images XENAPI_POWER_STATE = { 'Halted': power_state.SHUTDOWN, @@ -37,13 +36,17 @@ XENAPI_POWER_STATE = { 'Suspended': power_state.SHUTDOWN, # FIXME 'Crashed': power_state.CRASHED} +XenAPI = None + class VMHelper(): """ The class that wraps the helper methods together. """ def __init__(self): - return + global XenAPI + if XenAPI is None: + XenAPI = __import__('XenAPI') @classmethod @defer.inlineCallbacks diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 3696782b3..d36cdaea5 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -19,7 +19,6 @@ Management class for VM-related functions (spawn, reboot, etc). """ import logging -import XenAPI from twisted.internet import defer @@ -29,12 +28,17 @@ from nova.auth.manager import AuthManager from nova.virt.xenapi.network_utils import NetworkHelper from nova.virt.xenapi.vm_utils import VMHelper +XenAPI = None + class VMOps(object): """ Management class for VM-related tasks """ def __init__(self, session): + global XenAPI + if XenAPI is None: + XenAPI = __import__('XenAPI') self._session = session def list_instances(self): diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index a2eac4dc2..26b30bf92 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -49,7 +49,6 @@ reactor thread if the VM.get_by_name_label or VM.get_record calls block. import logging import xmlrpclib -import XenAPI from twisted.internet import defer from twisted.internet import reactor @@ -78,10 +77,17 @@ flags.DEFINE_float('xenapi_task_poll_interval', '(Async.VM.start, etc). Used only if ' 'connection_type=xenapi.') +XenAPI = None + def get_connection(_): """Note that XenAPI doesn't have a read-only connection mode, so the read_only parameter is ignored.""" + # This is loaded late so that there's no need to install this + # library when not using XenAPI. + global XenAPI + if XenAPI is None: + XenAPI = __import__('XenAPI') url = FLAGS.xenapi_connection_url username = FLAGS.xenapi_connection_username password = FLAGS.xenapi_connection_password -- cgit From 994f2820676b47b4f2e919d5ae7d2f9eb66c4372 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 7 Dec 2010 20:25:24 +0100 Subject: Add Ryan Lucio to Authors --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index ef1a535ca..62f0c49d5 100644 --- a/Authors +++ b/Authors @@ -20,6 +20,7 @@ Michael Gundlach Monty Taylor Paul Voccio Rick Clark +Ryan Lucio Soren Hansen Todd Willey Vishvananda Ishaya -- cgit From 06c5889936cec1be503595915a0e0df2c4f925a8 Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Tue, 7 Dec 2010 19:35:05 +0000 Subject: Adding myself to the authors list --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index ef1a535ca..a35398d5d 100644 --- a/Authors +++ b/Authors @@ -25,3 +25,4 @@ Todd Willey Vishvananda Ishaya Youcef Laribi Zhixue Wu +Ryan Lane -- cgit From bf34529e75022451f3833552df0e807139d0e498 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 7 Dec 2010 21:35:15 +0100 Subject: Make sure Authors check also works for pending merges (otherwise stuff can get merged that will make the next merge fail this check). --- nova/tests/misc_unittest.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/nova/tests/misc_unittest.py b/nova/tests/misc_unittest.py index 856060afa..667c63ad0 100644 --- a/nova/tests/misc_unittest.py +++ b/nova/tests/misc_unittest.py @@ -15,7 +15,6 @@ # under the License. import os -import subprocess from nova import test from nova.utils import parse_mailmap, str_dict_replace @@ -24,18 +23,23 @@ 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] + contributors = set() + 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)) + import bzrlib.workingtree + tree = bzrlib.workingtree.WorkingTree.open('..') + tree.lock_read() + 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() -- cgit From d03620f31aac6e8720bb6dc19860cb609af878c6 Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Tue, 7 Dec 2010 21:13:54 +0000 Subject: Reverting last change --- Authors | 1 - 1 file changed, 1 deletion(-) diff --git a/Authors b/Authors index a35398d5d..ef1a535ca 100644 --- a/Authors +++ b/Authors @@ -25,4 +25,3 @@ Todd Willey Vishvananda Ishaya Youcef Laribi Zhixue Wu -Ryan Lane -- cgit From a107c6f69237cd7488b9ff716e370dd01b8dd8bd Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 7 Dec 2010 16:06:55 -0600 Subject: Initial diagnostics import -- needs testing and cleanup. --- nova/virt/xenapi/vm_utils.py | 15 +++++++++++++++ nova/virt/xenapi/vmops.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 99d484ca2..801867bd4 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -20,15 +20,20 @@ their attributes like VDIs, VIFs, as well as their lookup functions. """ import logging +import urllib from twisted.internet import defer +from nova import flags from nova import utils + from nova.auth.manager import AuthManager from nova.compute import instance_types from nova.compute import power_state from nova.virt import images +FLAGS = flags.FLAGS + XENAPI_POWER_STATE = { 'Halted': power_state.SHUTDOWN, 'Running': power_state.RUNNING, @@ -214,3 +219,13 @@ class VMHelper(): 'mem': long(record['memory_dynamic_max']) >> 10, 'num_cpu': record['VCPUs_max'], 'cpu_time': 0} + + +def get_rrd(host, uuid): + """Return the VM RRD XML as a string""" + xml = urllib.urlopen("http://%s:%s@%s/vm_rrd?uuid=%s" % ( + FLAGS.xenapi_connection_username, + FLAGS.xenapi_connection_password, + host, + uuid)) + return xml.read() diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index d36cdaea5..ba73079ec 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -21,9 +21,11 @@ Management class for VM-related functions (spawn, reboot, etc). import logging from twisted.internet import defer +from xml.dom.minidom import parseString from nova import db from nova import context + from nova.auth.manager import AuthManager from nova.virt.xenapi.network_utils import NetworkHelper from nova.virt.xenapi.vm_utils import VMHelper @@ -128,6 +130,37 @@ class VMOps(object): rec = self._session.get_xenapi().VM.get_record(vm) return VMHelper.compile_info(rec) + def get_diagnostics(self, instance_id): + """Return data about the VM diagnostics""" + vm = VMHelper.lookup_blocking(self._session, instance_id) + if vm is None: + raise Exception("instance not present %s" % instance_id) + rec = self._session.get_xenapi().VM.get_record(vm) + try: + metrics = self._session.get_xenapi().VM_guest_metrics.get_record( + rec["guest_metrics"]) + diags = { + "Power State": rec["power_state"], + "Dom ID": rec["domid"], + "UUID": rec["uuid"], + "Kernel": metrics["os_version"]["uname"], + "Distro": metrics["os_version"]["name"]} + + xml = get_rrd(self._session.get_xenapi_host()["address"], + rec["uuid"]) + rrd = parseString(xml) + for i, node in enumerate(rrd.firstChild.childNodes): + # We don't want all of the extra garbage + if i >= 3 and i <= 11: + ref = node.childNodes + # Name and Value + diags[ref[0].firstChild.data] = ref[6].firstChild.data + + return {rec["name_label"]: diags} + except XenAPI.Failure as e: + return { + rec["name_label"]: "Unable to retrieve diagnostics: %s" % e} + def get_console_output(self, instance): """ Return snapshot of console """ # TODO: implement this to fix pylint! -- cgit From d647d6b070e0a910a9f20cfc1106027c86858f30 Mon Sep 17 00:00:00 2001 From: Anne Gentle Date: Tue, 7 Dec 2010 16:48:42 -0600 Subject: Added livecd instructions plus fixed references to .conf files --- doc/source/adminguide/managing.networks.rst | 2 +- doc/source/adminguide/multi.node.install.rst | 27 ++++----------- doc/source/adminguide/single.node.install.rst | 4 +-- doc/source/cloud101.rst | 9 +++-- doc/source/images/novascreens.png | Bin 0 -> 27949 bytes doc/source/images/novashvirtually.png | Bin 0 -> 39000 bytes doc/source/index.rst | 2 -- doc/source/livecd.rst | 46 ++++++++++++++++++++++++++ 8 files changed, 62 insertions(+), 28 deletions(-) create mode 100644 doc/source/images/novascreens.png create mode 100644 doc/source/images/novashvirtually.png diff --git a/doc/source/adminguide/managing.networks.rst b/doc/source/adminguide/managing.networks.rst index b8563637e..38c1cba78 100644 --- a/doc/source/adminguide/managing.networks.rst +++ b/doc/source/adminguide/managing.networks.rst @@ -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: diff --git a/doc/source/adminguide/multi.node.install.rst b/doc/source/adminguide/multi.node.install.rst index 1eed30c5b..3b06d7d91 100644 --- a/doc/source/adminguide/multi.node.install.rst +++ b/doc/source/adminguide/multi.node.install.rst @@ -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= -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,17 @@ Note: CC_ADDR= --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= # 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= # 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 +106,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. :: diff --git a/doc/source/adminguide/single.node.install.rst b/doc/source/adminguide/single.node.install.rst index f6b2290bc..8572c5a4a 100644 --- a/doc/source/adminguide/single.node.install.rst +++ b/doc/source/adminguide/single.node.install.rst @@ -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 @@ -139,7 +139,7 @@ Type or copy/paste the following to source the novarc file in your current worki 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. diff --git a/doc/source/cloud101.rst b/doc/source/cloud101.rst index 87db5af1e..7c79d2a70 100644 --- a/doc/source/cloud101.rst +++ b/doc/source/cloud101.rst @@ -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 ------------------ diff --git a/doc/source/images/novascreens.png b/doc/source/images/novascreens.png new file mode 100644 index 000000000..0fe3279cf Binary files /dev/null and b/doc/source/images/novascreens.png differ diff --git a/doc/source/images/novashvirtually.png b/doc/source/images/novashvirtually.png new file mode 100644 index 000000000..02c7e767c Binary files /dev/null and b/doc/source/images/novashvirtually.png differ diff --git a/doc/source/index.rst b/doc/source/index.rst index 9b2c8e1f8..dd01b2060 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -62,8 +62,6 @@ Administrator's Documentation adminguide/single.node.install adminguide/multi.node.install -.. todo:: add swiftadmin - Developer Docs ============== diff --git a/doc/source/livecd.rst b/doc/source/livecd.rst index 82cf4658a..b355fa180 100644 --- a/doc/source/livecd.rst +++ b/doc/source/livecd.rst @@ -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 + -- cgit From 17fd38e3cb277d51dcf9297178879a620623a855 Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Tue, 7 Dec 2010 23:46:18 +0000 Subject: Removing redundant check --- nova/auth/ldapdriver.py | 49 ++++++++++++++++++++++--------------------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index fa48c8435..d54a0dfa6 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -135,34 +135,29 @@ class LdapDriver(object): if self.__ldap_user_exists(name): # Retrieve user by name user = self.__get_ldap_user(name) - if user.has_key('accessKey') and user.has_key('secretKey') \ - and user.has_key('isAdmin'): - raise exception.Duplicate("LDAP user %s already exists" \ - % name) + # Entry could be malformed, test for missing attrs. + # Malformed entries are useless, replace attributes found. + attr = [] + if user.has_key('secretKey'): + attr.append((self.ldap.MOD_REPLACE, 'secretKey', \ + [secret_key])) else: - # Entry could be malformed, test for missing attrs. - # Malformed entries are useless, replace attributes found. - attr = [] - if user.has_key('secretKey'): - attr.append((self.ldap.MOD_REPLACE, 'secretKey', \ - [secret_key])) - else: - attr.append((self.ldap.MOD_ADD, 'secretKey', \ - [secret_key])) - if user.has_key('accessKey'): - attr.append((self.ldap.MOD_REPLACE, 'accessKey', \ - [access_key])) - else: - attr.append((self.ldap.MOD_ADD, 'accessKey', \ - [access_key])) - if user.has_key('isAdmin'): - attr.append((self.ldap.MOD_REPLACE, 'isAdmin', \ - [str(is_admin).upper()])) - else: - attr.append((self.ldap.MOD_ADD, 'isAdmin', \ - [str(is_admin).upper()])) - self.conn.modify_s(self.__uid_to_dn(name), attr) - return self.get_user(name) + attr.append((self.ldap.MOD_ADD, 'secretKey', \ + [secret_key])) + if user.has_key('accessKey'): + attr.append((self.ldap.MOD_REPLACE, 'accessKey', \ + [access_key])) + else: + attr.append((self.ldap.MOD_ADD, 'accessKey', \ + [access_key])) + if user.has_key('isAdmin'): + attr.append((self.ldap.MOD_REPLACE, 'isAdmin', \ + [str(is_admin).upper()])) + else: + attr.append((self.ldap.MOD_ADD, 'isAdmin', \ + [str(is_admin).upper()])) + self.conn.modify_s(self.__uid_to_dn(name), attr) + return self.get_user(name) else: attr = [ ('objectclass', ['person', -- cgit From 45324fc9f15135437051eaaedda68a5ef1f0da7a Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Tue, 7 Dec 2010 23:53:01 +0000 Subject: Raising an exception if the user doesn't exist before trying to modify its attributes --- nova/auth/ldapdriver.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index d54a0dfa6..5727c8da3 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -158,6 +158,8 @@ class LdapDriver(object): [str(is_admin).upper()])) self.conn.modify_s(self.__uid_to_dn(name), attr) return self.get_user(name) + else: + raise exception.NotFound("User %s doesn't exist" % name) else: attr = [ ('objectclass', ['person', -- cgit From abdb8080e365a584c64ce6562934eefb750568ba Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Wed, 8 Dec 2010 00:08:47 +0000 Subject: Clarifying previously commited exception message --- nova/auth/ldapdriver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index 5727c8da3..45ea0683d 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -159,7 +159,7 @@ class LdapDriver(object): self.conn.modify_s(self.__uid_to_dn(name), attr) return self.get_user(name) else: - raise exception.NotFound("User %s doesn't exist" % name) + raise exception.NotFound("LDAP object for %s doesn't exist" % name) else: attr = [ ('objectclass', ['person', -- cgit From 70371ab447bff6af36f12ad9594eb6ffdbff4396 Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Wed, 8 Dec 2010 00:26:41 +0000 Subject: pep8 fix --- nova/auth/ldapdriver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index 45ea0683d..9baf45c92 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -159,7 +159,8 @@ class LdapDriver(object): 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) + raise exception.NotFound("LDAP object for %s doesn't exist" + % name) else: attr = [ ('objectclass', ['person', -- cgit From 9fdff2a0f0b45d7ddf1df58f83ac723fc8d99532 Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Wed, 8 Dec 2010 00:34:20 +0000 Subject: More pep8 fixes to remove deprecated functions --- nova/auth/ldapdriver.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index 9baf45c92..c10939d74 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -138,19 +138,19 @@ class LdapDriver(object): # Entry could be malformed, test for missing attrs. # Malformed entries are useless, replace attributes found. attr = [] - if user.has_key('secretKey'): + 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 user.has_key('accessKey'): + 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 user.has_key('isAdmin'): + if 'isAdmin' in user.keys(): attr.append((self.ldap.MOD_REPLACE, 'isAdmin', \ [str(is_admin).upper()])) else: @@ -298,13 +298,13 @@ class LdapDriver(object): attr = [] # Retrieve user by name user = self.__get_ldap_user(uid) - if user.has_key('secretKey'): + if 'secretKey' in user.keys(): attr.append((self.ldap.MOD_DELETE, 'secretKey', \ user['secretKey'])) - if user.has_key('accessKey'): + if 'accessKey' in user.keys(): attr.append((self.ldap.MOD_DELETE, 'accessKey', \ user['accessKey'])) - if user.has_key('isAdmin'): + if 'isAdmin' in user.keys(): attr.append((self.ldap.MOD_DELETE, 'isAdmin', \ user['isAdmin'])) self.conn.modify_s(self.__uid_to_dn(uid), attr) @@ -513,8 +513,8 @@ class LdapDriver(object): """Convert ldap attributes to User object""" if attr is None: return None - if (attr.has_key('accessKey') and attr.has_key('secretKey') \ - and attr.has_key('isAdmin')): + if ('accessKey' in attr.keys() and 'secretKey' in attr.keys() \ + and 'isAdmin' in attr.keys()): return { 'id': attr['uid'][0], 'name': attr['cn'][0], -- cgit From 180c94585ac9bb0e72a936f64ed27052af395999 Mon Sep 17 00:00:00 2001 From: Anne Gentle Date: Wed, 8 Dec 2010 11:59:37 -0600 Subject: removing extraneous config ilnes --- doc/source/adminguide/multi.node.install.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/doc/source/adminguide/multi.node.install.rst b/doc/source/adminguide/multi.node.install.rst index 3b06d7d91..fcb76c5e5 100644 --- a/doc/source/adminguide/multi.node.install.rst +++ b/doc/source/adminguide/multi.node.install.rst @@ -89,9 +89,6 @@ Nova development has consolidated all .conf files to nova.conf as of November 20 --fixed_range= # 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 - --fixed_range= # 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 - #. Create a nova group:: sudo addgroup nova -- cgit From d4b6cfe98f0ce81c21a45f420ce30c5c693c1144 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 8 Dec 2010 14:05:50 -0600 Subject: Got get_diagnostics in working order --- nova/virt/xenapi/vm_utils.py | 23 +++++++++++++++++++++++ nova/virt/xenapi/vmops.py | 28 ++-------------------------- nova/virt/xenapi_conn.py | 4 ++++ 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 801867bd4..c87a131a0 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -23,6 +23,7 @@ import logging import urllib from twisted.internet import defer +from xml.dom.minidom import parseString from nova import flags from nova import utils @@ -220,6 +221,28 @@ class VMHelper(): 'num_cpu': record['VCPUs_max'], 'cpu_time': 0} + @classmethod + def compile_diagnostics(cls, session, record): + try: + host = session.get_xenapi_host() + host_ip = session.get_xenapi().host.get_record(host)["address"] + metrics = session.get_xenapi().VM_guest_metrics.get_record( + record["guest_metrics"]) + diags = { + "Kernel": metrics["os_version"]["uname"], + "Distro": metrics["os_version"]["name"]} + xml = get_rrd(host_ip, record["uuid"]) + rrd = parseString(xml) + for i, node in enumerate(rrd.firstChild.childNodes): + # We don't want all of the extra garbage + if i >= 3 and i <= 11: + ref = node.childNodes + # Name and Value + diags[ref[0].firstChild.data] = ref[6].firstChild.data + return diags + except XenAPI.Failure as e: + return {"Unable to retrieve diagnostics": e} + def get_rrd(host, uuid): """Return the VM RRD XML as a string""" diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index ba73079ec..542d4894c 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -21,7 +21,6 @@ Management class for VM-related functions (spawn, reboot, etc). import logging from twisted.internet import defer -from xml.dom.minidom import parseString from nova import db from nova import context @@ -131,35 +130,12 @@ class VMOps(object): return VMHelper.compile_info(rec) def get_diagnostics(self, instance_id): - """Return data about the VM diagnostics""" + """Return data about VM diagnostics""" vm = VMHelper.lookup_blocking(self._session, instance_id) if vm is None: raise Exception("instance not present %s" % instance_id) rec = self._session.get_xenapi().VM.get_record(vm) - try: - metrics = self._session.get_xenapi().VM_guest_metrics.get_record( - rec["guest_metrics"]) - diags = { - "Power State": rec["power_state"], - "Dom ID": rec["domid"], - "UUID": rec["uuid"], - "Kernel": metrics["os_version"]["uname"], - "Distro": metrics["os_version"]["name"]} - - xml = get_rrd(self._session.get_xenapi_host()["address"], - rec["uuid"]) - rrd = parseString(xml) - for i, node in enumerate(rrd.firstChild.childNodes): - # We don't want all of the extra garbage - if i >= 3 and i <= 11: - ref = node.childNodes - # Name and Value - diags[ref[0].firstChild.data] = ref[6].firstChild.data - - return {rec["name_label"]: diags} - except XenAPI.Failure as e: - return { - rec["name_label"]: "Unable to retrieve diagnostics: %s" % e} + return VMHelper.compile_diagnostics(self._session, rec) def get_console_output(self, instance): """ Return snapshot of console """ diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 26b30bf92..ac5f5e342 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -126,6 +126,10 @@ class XenAPIConnection(object): """ Return data about VM instance """ return self._vmops.get_info(instance_id) + def get_diagnostics(self, instance_id): + """ Return data about VM diagnostics """ + return self._vmops.get_diagnostics(instance_id) + def get_console_output(self, instance): """ Return snapshot of console """ return self._vmops.get_console_output(instance) -- cgit From fd7931847de7cb24c629380fb71bca7833710edc Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 8 Dec 2010 14:16:49 -0600 Subject: Fixed docstrings --- nova/virt/xenapi/vm_utils.py | 1 + nova/virt/xenapi_conn.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index c87a131a0..2a75f9dbf 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -223,6 +223,7 @@ class VMHelper(): @classmethod def compile_diagnostics(cls, session, record): + """Compile VM diagnostics data""" try: host = session.get_xenapi_host() host_ip = session.get_xenapi().host.get_record(host)["address"] diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index ac5f5e342..2153810c8 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -127,7 +127,7 @@ class XenAPIConnection(object): return self._vmops.get_info(instance_id) def get_diagnostics(self, instance_id): - """ Return data about VM diagnostics """ + """Return data about VM diagnostics""" return self._vmops.get_diagnostics(instance_id) def get_console_output(self, instance): -- cgit From 708425aa5b42aae0f399b127ee5a648b7162b05e Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 8 Dec 2010 12:20:44 -0800 Subject: add bzr to the dev dependencies --- tools/pip-requires | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/pip-requires b/tools/pip-requires index 548073326..17a1a4c5c 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -20,3 +20,4 @@ mox==0.5.0 -f http://pymox.googlecode.com/files/mox-0.5.0.tar.gz greenlet==0.3.1 nose +bzr -- cgit From 0ae019062712fd15dd9e040a3fa60546db9c4111 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 8 Dec 2010 23:47:25 +0000 Subject: added to Authors --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index 62f0c49d5..a1703b279 100644 --- a/Authors +++ b/Authors @@ -23,6 +23,7 @@ Rick Clark Ryan Lucio Soren Hansen Todd Willey +Trey Morris Vishvananda Ishaya Youcef Laribi Zhixue Wu -- cgit From 783f4fa44b835ef6c399e18679774a2e4bc4124a Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Thu, 9 Dec 2010 10:40:07 +0000 Subject: fixed how the XenAPI library is loaded --- nova/virt/xenapi/vm_utils.py | 9 +++++++++ nova/virt/xenapi/vmops.py | 2 ++ 2 files changed, 11 insertions(+) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 2a75f9dbf..2b84601f2 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -50,6 +50,15 @@ class VMHelper(): The class that wraps the helper methods together. """ def __init__(self): + return + + @classmethod + def late_import(cls): + """ + Load the XenAPI module in for helper class, if required. + This is to avoid to install the XenAPI library when other + hypervisors are used + """ global XenAPI if XenAPI is None: XenAPI = __import__('XenAPI') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 542d4894c..e7c3102a3 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -41,6 +41,8 @@ class VMOps(object): if XenAPI is None: XenAPI = __import__('XenAPI') self._session = session + # Load XenAPI module in the helper class + VMHelper.late_import() def list_instances(self): """ List VM instances """ -- cgit From b35b86c6f415a205b6ce49164cccb2a870c46fcb Mon Sep 17 00:00:00 2001 From: Chmouel Boudjnah Date: Thu, 9 Dec 2010 13:43:54 +0000 Subject: fixes exception throwing with wrong instance type. --- nova/compute/instance_types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index a2679e0fc..0e9600c00 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -22,6 +22,7 @@ The built-in instance properties. """ from nova import flags +from nova.exception import ApiError FLAGS = flags.FLAGS INSTANCE_TYPES = { @@ -37,8 +38,7 @@ def get_by_type(instance_type): if instance_type is None: return FLAGS.default_instance_type if instance_type not in INSTANCE_TYPES: - raise exception.ApiError("Unknown instance type: %s", - instance_type) + raise ApiError("Unknown instance type: %s" % instance_type) return instance_type -- cgit From 043d3ac3643e7183d4afe8c628ce90d62a468427 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 9 Dec 2010 11:08:24 -0600 Subject: Make get_diagnostics async --- nova/virt/xenapi/vmops.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index e7c3102a3..9bfd07267 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -131,13 +131,14 @@ class VMOps(object): rec = self._session.get_xenapi().VM.get_record(vm) return VMHelper.compile_info(rec) + @defer.inlineCallbacks def get_diagnostics(self, instance_id): """Return data about VM diagnostics""" - vm = VMHelper.lookup_blocking(self._session, instance_id) + vm = yield VMHelper.lookup(self._session, instance_id) if vm is None: raise Exception("instance not present %s" % instance_id) - rec = self._session.get_xenapi().VM.get_record(vm) - return VMHelper.compile_diagnostics(self._session, rec) + rec = yield self._session.get_xenapi().VM.get_record(vm) + defer.returnValue(VMHelper.compile_diagnostics(self._session, rec)) def get_console_output(self, instance): """ Return snapshot of console """ -- cgit From 50ac7dc67686742c3e57cc3a408ca9e8c988b89b Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 9 Dec 2010 17:30:03 +0000 Subject: filter describe volumes by supplied ids. Includes unittest. --- nova/api/ec2/cloud.py | 6 ++++-- nova/tests/cloud_unittest.py | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 05f8c3d0b..ebb13aedc 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -450,13 +450,15 @@ class CloudController(object): "Timestamp": now, "output": base64.b64encode(output)} - def describe_volumes(self, context, **kwargs): + def describe_volumes(self, context, volume_id=None, **kwargs): if context.user.is_admin(): volumes = db.volume_get_all(context) else: volumes = db.volume_get_all_by_project(context, context.project_id) - volumes = [self._format_volume(context, v) for v in volumes] + # NOTE(vish): volume_id is an optional list of volume ids to filter by. + volumes = [self._format_volume(context, v) for v in volumes + if volume_id is None or v['ec2_id'] in volume_id] return {'volumeSet': volumes} diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index 9886a2449..770c94219 100644 --- a/nova/tests/cloud_unittest.py +++ b/nova/tests/cloud_unittest.py @@ -126,6 +126,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 -- cgit From 470dcfdf793f9a8b34c320731dcfc8d403a61bd2 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 9 Dec 2010 11:32:23 -0600 Subject: Import module instead of function --- nova/virt/xenapi/vm_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 2b84601f2..ff062fba0 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -23,7 +23,7 @@ import logging import urllib from twisted.internet import defer -from xml.dom.minidom import parseString +from xml.dom import minidom from nova import flags from nova import utils @@ -242,7 +242,7 @@ class VMHelper(): "Kernel": metrics["os_version"]["uname"], "Distro": metrics["os_version"]["name"]} xml = get_rrd(host_ip, record["uuid"]) - rrd = parseString(xml) + rrd = minidom.parseString(xml) for i, node in enumerate(rrd.firstChild.childNodes): # We don't want all of the extra garbage if i >= 3 and i <= 11: -- cgit From 1c323efd0777587b44b275827187b7c5cd6afdc5 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Thu, 9 Dec 2010 09:57:15 -0800 Subject: Changed OpenStack API auth layer to inject a RequestContext rather than building one everywhere we need it. --- nova/api/openstack/__init__.py | 9 ++++---- nova/api/openstack/auth.py | 4 +--- nova/api/openstack/images.py | 9 ++------ nova/api/openstack/servers.py | 41 +++++++++++++++-------------------- nova/tests/api/openstack/fakes.py | 13 ++++++++--- nova/tests/api/openstack/test_auth.py | 3 +++ 6 files changed, 37 insertions(+), 42 deletions(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index c9efe5222..b9ecbd9b8 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -30,6 +30,7 @@ import webob.dec import webob.exc import webob +from nova import context from nova import flags from nova import utils from nova import wsgi @@ -88,9 +89,7 @@ class AuthMiddleware(wsgi.Middleware): if not user: return faults.Fault(webob.exc.HTTPUnauthorized()) - if 'nova.context' not in req.environ: - req.environ['nova.context'] = {} - req.environ['nova.context']['user'] = user + req.environ['nova.context'] = context.RequestContext(user, user) return self.application @@ -125,12 +124,12 @@ class RateLimitingMiddleware(wsgi.Middleware): If the request should be rate limited, return a 413 status with a Retry-After header giving the time when the request would succeed. """ - user_id = req.environ['nova.context']['user']['id'] action_name = self.get_action_name(req) if not action_name: # Not rate limited return self.application - delay = self.get_delay(action_name, user_id) + delay = self.get_delay(action_name, + req.environ['nova.context'].user_id) if delay: # TODO(gundlach): Get the retry-after format correct. exc = webob.exc.HTTPRequestEntityTooLarge( diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 205035915..fcda97ab1 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -74,9 +74,7 @@ class BasicApiAuthManager(object): if delta.days >= 2: self.db.auth_destroy_token(self.context, token) else: - #TODO(gundlach): Why not just return dict(id=token.user_id)? - user = self.auth.get_user(token.user_id) - return {'id': user.id} + return self.auth.get_user(token.user_id) return None def _authorize_user(self, username, key, req): diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index cdbdc9bdd..4a0a8e6f1 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -17,7 +17,6 @@ from webob import exc -from nova import context from nova import flags from nova import utils from nova import wsgi @@ -47,10 +46,8 @@ class Controller(wsgi.Controller): def detail(self, req): """Return all public images in detail.""" - user_id = req.environ['nova.context']['user']['id'] - ctxt = context.RequestContext(user_id, user_id) try: - images = self._service.detail(ctxt) + images = self._service.detail(req.environ['nova.context']) images = nova.api.openstack.limited(images, req) except NotImplementedError: # Emulate detail() using repeated calls to show() @@ -61,9 +58,7 @@ class Controller(wsgi.Controller): def show(self, req, id): """Return data about the given image id.""" - user_id = req.environ['nova.context']['user']['id'] - ctxt = context.RequestContext(user_id, user_id) - return dict(image=self._service.show(ctxt, id)) + return dict(image=self._service.show(req.environ['nova.context'], id)) def delete(self, req, id): # Only public images are supported for now. diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 6f2f6fed9..7704f48f1 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -17,7 +17,6 @@ from webob import exc -from nova import context from nova import exception from nova import wsgi from nova.api.openstack import faults @@ -90,29 +89,26 @@ class Controller(wsgi.Controller): entity_maker - either _entity_detail or _entity_inst """ - user_id = req.environ['nova.context']['user']['id'] - ctxt = context.RequestContext(user_id, user_id) - instance_list = self.compute_api.get_instances(ctxt) + instance_list = self.compute_api.get_instances( + req.environ['nova.context']) limited_list = nova.api.openstack.limited(instance_list, req) res = [entity_maker(inst)['server'] for inst in limited_list] return _entity_list(res) def show(self, req, id): """ Returns server details by server id """ - user_id = req.environ['nova.context']['user']['id'] - ctxt = context.RequestContext(user_id, user_id) - inst = self.compute_api.get_instance(ctxt, int(id)) - if inst: - if inst.user_id == user_id: - return _entity_detail(inst) - raise faults.Fault(exc.HTTPNotFound()) + try: + instance = self.compute_api.get_instance( + req.environ['nova.context'], int(id)) + return _entity_detail(instance) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) def delete(self, req, id): """ Destroys a server """ - user_id = req.environ['nova.context']['user']['id'] - ctxt = context.RequestContext(user_id, user_id) try: - self.compute_api.delete_instance(ctxt, int(id)) + self.compute_api.delete_instance(req.environ['nova.context'], + int(id)) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) return exc.HTTPAccepted() @@ -123,10 +119,10 @@ class Controller(wsgi.Controller): if not env: return faults.Fault(exc.HTTPUnprocessableEntity()) - user_id = req.environ['nova.context']['user']['id'] - ctxt = context.RequestContext(user_id, user_id) - key_pair = auth_manager.AuthManager.get_key_pairs(ctxt)[0] - instances = self.compute_api.create_instances(ctxt, + key_pair = auth_manager.AuthManager.get_key_pairs( + req.environ['nova.context'])[0] + instances = self.compute_api.create_instances( + req.environ['nova.context'], instance_types.get_by_flavor_id(env['server']['flavorId']), env['server']['imageId'], display_name=env['server']['name'], @@ -137,8 +133,6 @@ class Controller(wsgi.Controller): def update(self, req, id): """ Updates the server name or password """ - user_id = req.environ['nova.context']['user']['id'] - ctxt = context.RequestContext(user_id, user_id) inst_dict = self._deserialize(req.body, req) if not inst_dict: return faults.Fault(exc.HTTPUnprocessableEntity()) @@ -150,7 +144,8 @@ class Controller(wsgi.Controller): update_dict['display_name'] = inst_dict['server']['name'] try: - self.compute_api.update_instance(ctxt, instance['id'], + self.compute_api.update_instance(req.environ['nova.context'], + instance['id'], **update_dict) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) @@ -159,8 +154,6 @@ class Controller(wsgi.Controller): def action(self, req, id): """ Multi-purpose method used to reboot, rebuild, and resize a server """ - user_id = req.environ['nova.context']['user']['id'] - ctxt = context.RequestContext(user_id, user_id) input_dict = self._deserialize(req.body, req) try: reboot_type = input_dict['reboot']['type'] @@ -169,7 +162,7 @@ class Controller(wsgi.Controller): try: # TODO(gundlach): pass reboot_type, support soft reboot in # virt driver - self.compute_api.reboot(ctxt, id) + self.compute_api.reboot(req.environ['nova.context'], id) except: return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index c3f129a32..21b8aac1c 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -24,9 +24,10 @@ import webob import webob.dec from nova import auth -from nova import utils -from nova import flags +from nova import context from nova import exception as exc +from nova import flags +from nova import utils import nova.api.openstack.auth from nova.image import service from nova.image import glance @@ -58,7 +59,7 @@ def fake_auth_init(self): @webob.dec.wsgify def fake_wsgi(self, req): - req.environ['nova.context'] = dict(user=dict(id=1)) + req.environ['nova.context'] = context.RequestContext(1, 1) if req.body: req.environ['inst_dict'] = json.loads(req.body) return self.application @@ -171,6 +172,12 @@ class FakeToken(object): setattr(self, k, v) +class FakeRequestContext(object): + def __init__(self, user, project): + self.user_id = 1 + self.project_id = 1 + + class FakeAuthDatabase(object): data = {} diff --git a/nova/tests/api/openstack/test_auth.py b/nova/tests/api/openstack/test_auth.py index 14e720be4..7b427c2db 100644 --- a/nova/tests/api/openstack/test_auth.py +++ b/nova/tests/api/openstack/test_auth.py @@ -26,6 +26,7 @@ import nova.api import nova.api.openstack.auth import nova.auth.manager from nova import auth +from nova import context from nova.tests.api.openstack import fakes @@ -35,6 +36,7 @@ class Test(unittest.TestCase): self.stubs = stubout.StubOutForTesting() self.stubs.Set(nova.api.openstack.auth.BasicApiAuthManager, '__init__', fakes.fake_auth_init) + self.stubs.Set(context, 'RequestContext', fakes.FakeRequestContext) fakes.FakeAuthManager.auth_data = {} fakes.FakeAuthDatabase.data = {} fakes.stub_out_rate_limiting(self.stubs) @@ -131,6 +133,7 @@ class TestLimiter(unittest.TestCase): self.stubs = stubout.StubOutForTesting() self.stubs.Set(nova.api.openstack.auth.BasicApiAuthManager, '__init__', fakes.fake_auth_init) + self.stubs.Set(context, 'RequestContext', fakes.FakeRequestContext) fakes.FakeAuthManager.auth_data = {} fakes.FakeAuthDatabase.data = {} fakes.stub_out_networking(self.stubs) -- cgit From a00c8015e4ffe417f6c111a5eaf0578d9ef79b7d Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 9 Dec 2010 13:37:30 -0600 Subject: Added exception handling to get_rrd() --- nova/virt/xenapi/vm_utils.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index ff062fba0..77edb576e 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -242,13 +242,14 @@ class VMHelper(): "Kernel": metrics["os_version"]["uname"], "Distro": metrics["os_version"]["name"]} xml = get_rrd(host_ip, record["uuid"]) - rrd = minidom.parseString(xml) - for i, node in enumerate(rrd.firstChild.childNodes): - # We don't want all of the extra garbage - if i >= 3 and i <= 11: - ref = node.childNodes - # Name and Value - diags[ref[0].firstChild.data] = ref[6].firstChild.data + if xml: + rrd = minidom.parseString(xml) + for i, node in enumerate(rrd.firstChild.childNodes): + # We don't want all of the extra garbage + if i >= 3 and i <= 11: + ref = node.childNodes + # Name and Value + diags[ref[0].firstChild.data] = ref[6].firstChild.data return diags except XenAPI.Failure as e: return {"Unable to retrieve diagnostics": e} @@ -256,9 +257,12 @@ class VMHelper(): def get_rrd(host, uuid): """Return the VM RRD XML as a string""" - xml = urllib.urlopen("http://%s:%s@%s/vm_rrd?uuid=%s" % ( - FLAGS.xenapi_connection_username, - FLAGS.xenapi_connection_password, - host, - uuid)) - return xml.read() + try: + xml = urllib.urlopen("http://%s:%s@%s/vm_rrd?uuid=%s" % ( + FLAGS.xenapi_connection_username, + FLAGS.xenapi_connection_password, + host, + uuid)) + return xml.read() + except IOError: + return None -- cgit From 4c9bf2f01fb712a3af6a9876a175a7a0638bcd59 Mon Sep 17 00:00:00 2001 From: Chmouel Boudjnah Date: Thu, 9 Dec 2010 20:18:06 +0000 Subject: import module and not classe directely as per Soren recommendation. --- nova/compute/instance_types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index 0e9600c00..6e47170bd 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -22,7 +22,7 @@ The built-in instance properties. """ from nova import flags -from nova.exception import ApiError +from nova import exception FLAGS = flags.FLAGS INSTANCE_TYPES = { @@ -38,7 +38,7 @@ def get_by_type(instance_type): if instance_type is None: return FLAGS.default_instance_type if instance_type not in INSTANCE_TYPES: - raise ApiError("Unknown instance type: %s" % instance_type) + raise exception.ApiError("Unknown instance type: %s" % instance_type) return instance_type -- cgit From 68dbbbba34af234f2770b40c03e4e4bfa5ad78d8 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 10 Dec 2010 21:09:37 +0000 Subject: Includes kernel and ramdisk on register. Additinally removes a couple lines of cruft --- nova/objectstore/image.py | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/nova/objectstore/image.py b/nova/objectstore/image.py index 7292dbab8..ed0f75cd2 100644 --- a/nova/objectstore/image.py +++ b/nova/objectstore/image.py @@ -21,15 +21,12 @@ Take uploaded bucket contents and register them as disk images (AMIs). Requires decryption using keys in the manifest. """ -# TODO(jesse): Got these from Euca2ools, will need to revisit them - import binascii import glob import json import os import shutil import tarfile -import tempfile from xml.etree import ElementTree from nova import exception @@ -185,33 +182,38 @@ class Image(object): manifest = ElementTree.fromstring(bucket_object[manifest_path].read()) image_type = 'machine' + info = { + 'imageId': image_id, + 'imageLocation': image_location, + 'imageOwnerId': context.project_id, + 'isPublic': False, # FIXME: grab public from manifest + 'architecture': 'x86_64', + 'imageType': 'machine' + } + + manifest = ElementTree.fromstring(bucket_object[manifest_path].read()) + + try: + architecture = manifest.find("machine_configuration/kernel_id").text + info['architecture'] = architecture + except: + pass try: kernel_id = manifest.find("machine_configuration/kernel_id").text if kernel_id == 'true': - image_type = 'kernel' + info['imageType'] = 'kernel' + else: + info['kernelId'] = kernel_id except: - kernel_id = None - + pass try: ramdisk_id = manifest.find("machine_configuration/ramdisk_id").text if ramdisk_id == 'true': - image_type = 'ramdisk' + info['imageType'] = 'ramdisk' + else: + info['ramdiskId'] = ramdisk_id except: - ramdisk_id = None - - info = { - 'imageId': image_id, - 'imageLocation': image_location, - 'imageOwnerId': context.project_id, - 'isPublic': False, # FIXME: grab public from manifest - 'architecture': 'x86_64', # FIXME: grab architecture from manifest - 'imageType': image_type} - - if kernel_id: - info['kernelId'] = kernel_id - - if ramdisk_id: - info['ramdiskId'] = ramdisk_id + pass def write_state(state): info['imageState'] = state -- cgit From f09b008388b9ed8dbd1d3f74cb1e9f2a458a3000 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 10 Dec 2010 21:15:33 +0000 Subject: fix pep8 --- nova/objectstore/image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/objectstore/image.py b/nova/objectstore/image.py index ed0f75cd2..c4d752124 100644 --- a/nova/objectstore/image.py +++ b/nova/objectstore/image.py @@ -194,8 +194,8 @@ class Image(object): manifest = ElementTree.fromstring(bucket_object[manifest_path].read()) try: - architecture = manifest.find("machine_configuration/kernel_id").text - info['architecture'] = architecture + arch = manifest.find("machine_configuration/kernel_id").text + info['architecture'] = arch except: pass try: -- cgit From d60ed93a22b5cfb7dcaad2882b28a5a37c797af9 Mon Sep 17 00:00:00 2001 From: Anne Gentle Date: Fri, 10 Dec 2010 16:03:30 -0600 Subject: Fixed spelling errors in index.rst --- doc/source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/index.rst b/doc/source/index.rst index dd01b2060..b9ba6208a 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -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 -- cgit From a6645d8a431ed933eef4ea6c42c0224ead6f2272 Mon Sep 17 00:00:00 2001 From: "jaypipes@gmail.com" <> Date: Sat, 11 Dec 2010 15:10:24 -0500 Subject: Initial i18n commit for endpoints. All endpoints must install gettext, which injects the _ function into the builtins. --- bin/nova-api | 3 +++ bin/nova-compute | 3 +++ bin/nova-dhcpbridge | 3 +++ nova/tests/__init__.py | 5 +++++ run_tests.py | 3 +++ 5 files changed, 17 insertions(+) diff --git a/bin/nova-api b/bin/nova-api index a9c53dbcd..2ae6a099a 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -21,6 +21,7 @@ Nova API daemon. """ +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 server diff --git a/bin/nova-compute b/bin/nova-compute index ac6378f75..f57b68584 100755 --- a/bin/nova-compute +++ b/bin/nova-compute @@ -21,6 +21,7 @@ Twistd daemon for the nova compute nodes. """ +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 service from nova import twistd from nova import utils diff --git a/bin/nova-dhcpbridge b/bin/nova-dhcpbridge index 17c62da0a..81b9b6dd3 100755 --- a/bin/nova-dhcpbridge +++ b/bin/nova-dhcpbridge @@ -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 diff --git a/nova/tests/__init__.py b/nova/tests/__init__.py index aaf213923..8dc87d0e2 100644 --- a/nova/tests/__init__.py +++ b/nova/tests/__init__.py @@ -29,3 +29,8 @@ .. moduleauthor:: Manish Singh .. moduleauthor:: Andy Smith """ + +# 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) diff --git a/run_tests.py b/run_tests.py index 3d427d8af..37a548e4c 100644 --- a/run_tests.py +++ b/run_tests.py @@ -40,9 +40,12 @@ Due to our use of multiprocessing it we frequently get some ignorable """ import __main__ +import gettext import os import sys +gettext.install('nova', unicode=1) + from twisted.scripts import trial as trial_script from nova import flags -- cgit From 8e642730b1e32477bcd124592f2c9e00857da1b9 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Mon, 13 Dec 2010 20:02:27 +0300 Subject: Added Twisted version dependency into pip-requires --- tools/pip-requires | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/pip-requires b/tools/pip-requires index 17a1a4c5c..52451b8cb 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -21,3 +21,4 @@ mox==0.5.0 greenlet==0.3.1 nose bzr +Twisted>=10.1.0 \ No newline at end of file -- cgit From c06ec98897e7c3e33f15d45ba2704b0d4b77a453 Mon Sep 17 00:00:00 2001 From: Chmouel Boudjnah Date: Mon, 13 Dec 2010 18:56:07 +0000 Subject: Add myself. --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index a1703b279..64aabbdbc 100644 --- a/Authors +++ b/Authors @@ -3,6 +3,7 @@ Anne Gentle Anthony Young Armando Migliaccio Chris Behrens +Chmouel Boudjnah Dean Troyer Devin Carlen Eric Day -- cgit From 72b18d065669a01d8d083aa3edcc726be9be6547 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 14 Dec 2010 00:20:27 +0000 Subject: simplified version using original logic --- nova/objectstore/image.py | 49 ++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/nova/objectstore/image.py b/nova/objectstore/image.py index c4d752124..34a90b0a2 100644 --- a/nova/objectstore/image.py +++ b/nova/objectstore/image.py @@ -21,6 +21,7 @@ Take uploaded bucket contents and register them as disk images (AMIs). Requires decryption using keys in the manifest. """ + import binascii import glob import json @@ -182,38 +183,38 @@ class Image(object): manifest = ElementTree.fromstring(bucket_object[manifest_path].read()) image_type = 'machine' - info = { - 'imageId': image_id, - 'imageLocation': image_location, - 'imageOwnerId': context.project_id, - 'isPublic': False, # FIXME: grab public from manifest - 'architecture': 'x86_64', - 'imageType': 'machine' - } - - manifest = ElementTree.fromstring(bucket_object[manifest_path].read()) - - try: - arch = manifest.find("machine_configuration/kernel_id").text - info['architecture'] = arch - except: - pass try: kernel_id = manifest.find("machine_configuration/kernel_id").text if kernel_id == 'true': - info['imageType'] = 'kernel' - else: - info['kernelId'] = kernel_id + image_type = 'kernel' except: - pass + kernel_id = None + try: ramdisk_id = manifest.find("machine_configuration/ramdisk_id").text if ramdisk_id == 'true': - info['imageType'] = 'ramdisk' - else: - info['ramdiskId'] = ramdisk_id + image_type = 'ramdisk' except: - pass + ramdisk_id = None + + try: + arch = manifest.find("machine_configuration/architecture").text + except: + arch = 'x86_64' + + info = { + 'imageId': image_id, + 'imageLocation': image_location, + 'imageOwnerId': context.project_id, + 'isPublic': False, # FIXME: grab public from manifest + 'architecture': arch, + 'imageType': image_type} + + if kernel_id: + info['kernelId'] = kernel_id + + if ramdisk_id: + info['ramdiskId'] = ramdisk_id def write_state(state): info['imageState'] = state -- cgit From d3a41eff912762dddd1516006da197f99af53b4e Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Tue, 14 Dec 2010 14:54:50 +0300 Subject: Added my contacts to Authors file --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index c9bf3b67c..2810a1ddb 100644 --- a/Authors +++ b/Authors @@ -28,3 +28,4 @@ Trey Morris Vishvananda Ishaya Youcef Laribi Zhixue Wu +Eldar Nugaev \ No newline at end of file -- cgit From 797e3f8a1cc72599aa8540b5655e29da8975e56f Mon Sep 17 00:00:00 2001 From: "jaypipes@gmail.com" <> Date: Tue, 14 Dec 2010 18:22:03 -0500 Subject: For some reason, I forgot to commit the other endpoints... --- bin/nova-import-canonical-imagestore | 3 +++ bin/nova-instancemonitor | 3 +++ bin/nova-manage | 3 +++ bin/nova-network | 3 +++ bin/nova-objectstore | 3 +++ bin/nova-scheduler | 3 +++ bin/nova-volume | 3 +++ 7 files changed, 21 insertions(+) diff --git a/bin/nova-import-canonical-imagestore b/bin/nova-import-canonical-imagestore index 4ed9e8365..036b41e48 100755 --- a/bin/nova-import-canonical-imagestore +++ b/bin/nova-import-canonical-imagestore @@ -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 diff --git a/bin/nova-instancemonitor b/bin/nova-instancemonitor index 9b6c40e82..5dac3ffe6 100755 --- a/bin/nova-instancemonitor +++ b/bin/nova-instancemonitor @@ -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 diff --git a/bin/nova-manage b/bin/nova-manage index 62eec8353..0c1b621ed 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -53,6 +53,7 @@ CLI interface for nova management. """ +import gettext import logging import os import sys @@ -68,6 +69,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 exception diff --git a/bin/nova-network b/bin/nova-network index d1fb55261..86d04c723 100755 --- a/bin/nova-network +++ b/bin/nova-network @@ -21,6 +21,7 @@ Twistd daemon for the nova network nodes. """ +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 service from nova import twistd from nova import utils diff --git a/bin/nova-objectstore b/bin/nova-objectstore index 00ae27af9..9fbe228a2 100755 --- a/bin/nova-objectstore +++ b/bin/nova-objectstore @@ -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 diff --git a/bin/nova-scheduler b/bin/nova-scheduler index 4d1a40cf1..41e1937c1 100755 --- a/bin/nova-scheduler +++ b/bin/nova-scheduler @@ -21,6 +21,7 @@ Twistd daemon for the nova scheduler nodes. """ +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 service from nova import twistd from nova import utils diff --git a/bin/nova-volume b/bin/nova-volume index e7281d6c0..4f2e96268 100755 --- a/bin/nova-volume +++ b/bin/nova-volume @@ -21,6 +21,7 @@ Twistd daemon for the nova volume nodes. """ +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 service from nova import twistd from nova import utils -- cgit From aff411d80f4243ad0b40649af3a7586c7c38ac2d Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Wed, 15 Dec 2010 11:57:56 +0100 Subject: Make sure we unlock the bzr tree again in the authors unit test. --- nova/tests/misc_unittest.py | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/nova/tests/misc_unittest.py b/nova/tests/misc_unittest.py index 667c63ad0..2758276e5 100644 --- a/nova/tests/misc_unittest.py +++ b/nova/tests/misc_unittest.py @@ -30,23 +30,26 @@ class ProjectTestCase(test.TrialTestCase): import bzrlib.workingtree tree = bzrlib.workingtree.WorkingTree.open('..') tree.lock_read() - 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) + 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() -- cgit From 17daec6992456efc70ffbf05423ea91123db1fc2 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Wed, 15 Dec 2010 20:17:44 +0300 Subject: Restore alphabetical order in Authors file --- Authors | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Authors b/Authors index 2810a1ddb..749d78406 100644 --- a/Authors +++ b/Authors @@ -5,6 +5,7 @@ Armando Migliaccio Chris Behrens Dean Troyer Devin Carlen +Eldar Nugaev Eric Day Ewan Mellor Hisaki Ohara @@ -28,4 +29,3 @@ Trey Morris Vishvananda Ishaya Youcef Laribi Zhixue Wu -Eldar Nugaev \ No newline at end of file -- cgit