From 35dc27bbcb3a29ae662a0f8ccfed7a0f76a49c13 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Mon, 19 Jul 2010 14:17:43 -0700 Subject: [PATCH 001/229] Ask curl to set exit code if resource was not found --- nova/compute/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/compute/node.py b/nova/compute/node.py index d681ec6611..8f829995b1 100644 --- a/nova/compute/node.py +++ b/nova/compute/node.py @@ -450,7 +450,7 @@ class Instance(object): def _fetch_s3_image(self, image, path): url = _image_url('%s/image' % image) - d = self._pool.simpleExecute('curl --silent %s -o %s' % (url, path)) + d = self._pool.simpleExecute('curl --silent --fail %s -o %s' % (url, path)) return d def _fetch_local_image(self, image, path): From 63513736b0340efd197a7b905208fa90d63ab260 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 20 Jul 2010 00:32:42 -0700 Subject: [PATCH 002/229] Able to boot without kernel or ramdisk. libvirt.xml.template is now a Cheetah template --- nova/compute/libvirt.xml.template | 26 +++++++++++++++----------- nova/compute/node.py | 26 ++++++++++++++++++++++---- nova/endpoint/cloud.py | 4 ++-- 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/nova/compute/libvirt.xml.template b/nova/compute/libvirt.xml.template index a763e8a4d7..6fb63feae8 100644 --- a/nova/compute/libvirt.xml.template +++ b/nova/compute/libvirt.xml.template @@ -1,31 +1,35 @@ - %(name)s + ${name} - hvm - %(basepath)s/kernel - %(basepath)s/ramdisk + hvm +#if $getVar('kernel', None) + ${kernel} + #if $getVar('ramdisk', None) + ${ramdisk} + #end if root=/dev/vda1 console=ttyS0 +#end if - %(memory_kb)s - %(vcpus)s + ${memory_kb} + ${vcpus} /usr/bin/kvm - + - - + + - + - %(nova)s + ${nova} diff --git a/nova/compute/node.py b/nova/compute/node.py index 8f829995b1..7c3b9a677f 100644 --- a/nova/compute/node.py +++ b/nova/compute/node.py @@ -34,6 +34,7 @@ from twisted.internet import defer from twisted.internet import task from twisted.application import service +from Cheetah.Template import Template try: import libvirt @@ -312,13 +313,30 @@ class Instance(object): def toXml(self): # TODO(termie): cache? logging.debug("Starting the toXML method") - libvirt_xml = open(FLAGS.libvirt_xml_template).read() + template_contents = open(FLAGS.libvirt_xml_template).read() xml_info = self.datamodel.copy() # TODO(joshua): Make this xml express the attached disks as well # TODO(termie): lazy lazy hack because xml is annoying xml_info['nova'] = json.dumps(self.datamodel.copy()) - libvirt_xml = libvirt_xml % xml_info + + if xml_info['kernel_id']: + xml_info['kernel'] = xml_info['basepath'] + "/kernel" + + if xml_info['ramdisk_id']: + xml_info['ramdisk'] = xml_info['basepath'] + "/ramdisk" + + if xml_info['ramdisk_id'] or xml_info['kernel_id']: + xml_info['disk'] = xml_info['basepath'] + "/disk" + else: + xml_info['disk'] = xml_info['basepath'] + "/disk-raw" + + try: + libvirt_xml = str(Template(template_contents, searchList=[ xml_info ] )) + except Exception as e: + logging.warning("Error running template: %s" % e) + raise + logging.debug("Finished the toXML method") return libvirt_xml @@ -487,9 +505,9 @@ class Instance(object): if not os.path.exists(basepath('disk')): yield _fetch_file(data['image_id'], basepath('disk-raw')) - if not os.path.exists(basepath('kernel')): + if data['kernel_id'] and not os.path.exists(basepath('kernel')): yield _fetch_file(data['kernel_id'], basepath('kernel')) - if not os.path.exists(basepath('ramdisk')): + if data['ramdisk_id'] and not os.path.exists(basepath('ramdisk')): yield _fetch_file(data['ramdisk_id'], basepath('ramdisk')) execute = lambda cmd, input=None: self._pool.simpleExecute(cmd=cmd, diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 3b7b4804b2..b7a4fe20ec 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -544,8 +544,8 @@ class CloudController(object): for num in range(int(kwargs['max_count'])): inst = self.instdir.new() inst['image_id'] = image_id - inst['kernel_id'] = kernel_id - inst['ramdisk_id'] = ramdisk_id + inst['kernel_id'] = kernel_id or '' + inst['ramdisk_id'] = ramdisk_id or '' inst['user_data'] = kwargs.get('user_data', '') inst['instance_type'] = kwargs.get('instance_type', 'm1.small') inst['reservation_id'] = reservation_id From 9cf32d6d65035299ecfcb0563cef8ddab3c0ee4c Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 20 Jul 2010 14:25:43 -0700 Subject: [PATCH 003/229] Able to set up DNS, and remove udev network rules --- nova/compute/disk.py | 29 ++++++++++++---- nova/compute/libvirt.xml.template | 1 + nova/compute/network.py | 3 ++ nova/compute/node.py | 57 ++++++++++++++++++++----------- nova/compute/resolv.conf.template | 1 + 5 files changed, 66 insertions(+), 25 deletions(-) create mode 100644 nova/compute/resolv.conf.template diff --git a/nova/compute/disk.py b/nova/compute/disk.py index 08a22556e5..abbbc01c23 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -84,7 +84,7 @@ def partition(infile, outfile, local_bytes=0, local_type='ext2', execute=None): % (infile, outfile, sector_size, primary_first)) @defer.inlineCallbacks -def inject_data(image, key=None, net=None, partition=None, execute=None): +def inject_data(image, key=None, net=None, dns=None, remove_network_udev=False, partition=None, execute=None): """Injects a ssh key and optionally net data into a disk image. it will mount the image as a fully partitioned disk and attempt to inject @@ -93,7 +93,7 @@ def inject_data(image, key=None, net=None, partition=None, execute=None): If partition is not specified it mounts the image as a single partition. """ - out, err = yield execute('sudo losetup -f --show %s' % image) + out, err = yield execute('sudo losetup --find --show %s' % image) if err: raise exception.Error('Could not attach image to loopback: %s' % err) device = out.strip() @@ -107,6 +107,8 @@ def inject_data(image, key=None, net=None, partition=None, execute=None): partition) else: mapped_device = device + + # Configure ext2fs so that it doesn't auto-check every N boots out, err = yield execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device) tmpdir = tempfile.mkdtemp() @@ -123,6 +125,11 @@ def inject_data(image, key=None, net=None, partition=None, execute=None): yield _inject_key_into_fs(key, tmpdir, execute=execute) if net: yield _inject_net_into_fs(net, tmpdir, execute=execute) + if dns: + yield _inject_dns_into_fs(dns, tmpdir, execute=execute) + if remove_network_udev: + yield _remove_network_udev(tmpdir, execute=execute) + finally: # unmount device yield execute('sudo umount %s' % mapped_device) @@ -134,11 +141,11 @@ def inject_data(image, key=None, net=None, partition=None, execute=None): yield execute('sudo kpartx -d %s' % device) finally: # remove loopback - yield execute('sudo losetup -d %s' % device) + yield execute('sudo losetup --detach %s' % device) @defer.inlineCallbacks def _inject_key_into_fs(key, fs, execute=None): - sshdir = os.path.join(os.path.join(fs, 'root'), '.ssh') + sshdir = os.path.join(fs, 'root', '.ssh') yield execute('sudo mkdir -p %s' % sshdir) # existing dir doesn't matter yield execute('sudo chown root %s' % sshdir) yield execute('sudo chmod 700 %s' % sshdir) @@ -147,7 +154,17 @@ def _inject_key_into_fs(key, fs, execute=None): @defer.inlineCallbacks def _inject_net_into_fs(net, fs, execute=None): - netfile = os.path.join(os.path.join(os.path.join( - fs, 'etc'), 'network'), 'interfaces') + netfile = os.path.join(fs, 'etc', 'network', 'interfaces') yield execute('sudo tee %s' % netfile, net) +@defer.inlineCallbacks +def _inject_dns_into_fs(dns, fs, execute=None): + dnsfile = os.path.join(fs, 'etc', 'resolv.conf') + yield execute('sudo tee %s' % dnsfile, dns) + +@defer.inlineCallbacks +def _remove_network_udev(fs, execute=None): + # This is correct for Ubuntu, but might not be right for other distros + rulesfile = os.path.join(fs, 'etc', 'udev', 'rules.d', '70-persistent-net.rules') + yield execute('rm -f %s' % rulesfile) + diff --git a/nova/compute/libvirt.xml.template b/nova/compute/libvirt.xml.template index 6fb63feae8..3bfec9b3a1 100644 --- a/nova/compute/libvirt.xml.template +++ b/nova/compute/libvirt.xml.template @@ -16,6 +16,7 @@ ${memory_kb} ${vcpus} + /usr/bin/kvm diff --git a/nova/compute/network.py b/nova/compute/network.py index bdb8a22f9d..0b24ca7d13 100644 --- a/nova/compute/network.py +++ b/nova/compute/network.py @@ -62,6 +62,9 @@ flags.DEFINE_list('simple_network_ips', ['192.168.0.2'], flags.DEFINE_string('simple_network_template', utils.abspath('compute/interfaces.template'), 'Template file for simple network') +flags.DEFINE_string('simple_network_dns_template', + utils.abspath('compute/resolv.conf.template'), + 'Template file for DNS settings for simple network') flags.DEFINE_string('simple_network_netmask', '255.255.255.0', 'Netmask for simple network') flags.DEFINE_string('simple_network_network', '192.168.0.0', diff --git a/nova/compute/node.py b/nova/compute/node.py index 7c3b9a677f..56429b0f0c 100644 --- a/nova/compute/node.py +++ b/nova/compute/node.py @@ -505,37 +505,56 @@ class Instance(object): if not os.path.exists(basepath('disk')): yield _fetch_file(data['image_id'], basepath('disk-raw')) - if data['kernel_id'] and not os.path.exists(basepath('kernel')): - yield _fetch_file(data['kernel_id'], basepath('kernel')) - if data['ramdisk_id'] and not os.path.exists(basepath('ramdisk')): - yield _fetch_file(data['ramdisk_id'], basepath('ramdisk')) + + using_kernel = data['kernel_id'] and True + + if using_kernel: + if os.path.exists(basepath('kernel')): + yield _fetch_file(data['kernel_id'], basepath('kernel')) + if data['ramdisk_id'] and not os.path.exists(basepath('ramdisk')): + yield _fetch_file(data['ramdisk_id'], basepath('ramdisk')) execute = lambda cmd, input=None: self._pool.simpleExecute(cmd=cmd, input=input, error_ok=1) + # For now, we assume that if we're not using a kernel, we're using a partitioned disk image + # where the target partition is the first partition + target_partition = None + if not using_kernel: + target_partition = "1" + key = data['key_data'] net = None + dns = None if FLAGS.simple_network: + network_info = { + 'address': data['private_dns_name'], + 'network': FLAGS.simple_network_network, + 'netmask': FLAGS.simple_network_netmask, + 'gateway': FLAGS.simple_network_gateway, + 'broadcast': FLAGS.simple_network_broadcast, + 'dns': FLAGS.simple_network_dns} + with open(FLAGS.simple_network_template) as f: - net = f.read() % {'address': data['private_dns_name'], - 'network': FLAGS.simple_network_network, - 'netmask': FLAGS.simple_network_netmask, - 'gateway': FLAGS.simple_network_gateway, - 'broadcast': FLAGS.simple_network_broadcast, - 'dns': FLAGS.simple_network_dns} - if key or net: + net = f.read() % network_info + + with open(FLAGS.simple_network_dns_template) as f: + dns =str(Template(f.read(), searchList=[ network_info ] )) + + if key or net or dns: logging.info('Injecting data into image %s', data['image_id']) - yield disk.inject_data(basepath('disk-raw'), key, net, execute=execute) + yield disk.inject_data(basepath('disk-raw'), key=key, net=net, dns=dns, remove_network_udev=True, partition=target_partition, execute=execute) - if os.path.exists(basepath('disk')): - yield self._pool.simpleExecute('rm -f %s' % basepath('disk')) - - bytes = (INSTANCE_TYPES[data['instance_type']]['local_gb'] - * 1024 * 1024 * 1024) - yield disk.partition( - basepath('disk-raw'), basepath('disk'), bytes, execute=execute) + if using_kernel: + if os.path.exists(basepath('disk')): + yield self._pool.simpleExecute('rm -f %s' % basepath('disk')) + bytes = (INSTANCE_TYPES[data['instance_type']]['local_gb'] + * 1024 * 1024 * 1024) + yield disk.partition( + basepath('disk-raw'), basepath('disk'), bytes, execute=execute) + @defer.inlineCallbacks @exception.wrap_exception def spawn(self): diff --git a/nova/compute/resolv.conf.template b/nova/compute/resolv.conf.template new file mode 100644 index 0000000000..7ddb551900 --- /dev/null +++ b/nova/compute/resolv.conf.template @@ -0,0 +1 @@ +nameserver ${dns} From 5bb1d8d5ab8581696a98a159b293b992e72eec49 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 20 Jul 2010 14:31:55 -0700 Subject: [PATCH 004/229] Cleanups --- nova/compute/node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/compute/node.py b/nova/compute/node.py index 56429b0f0c..ba140a219d 100644 --- a/nova/compute/node.py +++ b/nova/compute/node.py @@ -509,7 +509,7 @@ class Instance(object): using_kernel = data['kernel_id'] and True if using_kernel: - if os.path.exists(basepath('kernel')): + if not os.path.exists(basepath('kernel')): yield _fetch_file(data['kernel_id'], basepath('kernel')) if data['ramdisk_id'] and not os.path.exists(basepath('ramdisk')): yield _fetch_file(data['ramdisk_id'], basepath('ramdisk')) @@ -540,7 +540,7 @@ class Instance(object): net = f.read() % network_info with open(FLAGS.simple_network_dns_template) as f: - dns =str(Template(f.read(), searchList=[ network_info ] )) + dns = str(Template(f.read(), searchList=[ network_info ] )) if key or net or dns: logging.info('Injecting data into image %s', data['image_id']) From c599914304b262067c19b2968ab50826b4d9bcd3 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Fri, 30 Jul 2010 12:54:03 -0700 Subject: [PATCH 005/229] Removed duplicate toXml method --- nova/virt/libvirt_conn.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 43b5eded25..6bc5ebf898 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -228,20 +228,6 @@ class LibvirtConnection(object): return os.path.abspath(os.path.join(instance.datamodel['basepath'], path)) - def toXml(self, instance): - # TODO(termie): cache? - logging.debug("Starting the toXML method") - libvirt_xml = open(FLAGS.libvirt_xml_template).read() - xml_info = instance.datamodel.copy() - # TODO(joshua): Make this xml express the attached disks as well - - # TODO(termie): lazy lazy hack because xml is annoying - xml_info['nova'] = json.dumps(instance.datamodel.copy()) - libvirt_xml = libvirt_xml % xml_info - logging.debug("Finished the toXML method") - - return libvirt_xml - def toXml(self): # TODO(termie): cache? logging.debug("Starting the toXML method") From 6526f21d9bf5a53546240ec29099d68933165500 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Fri, 30 Jul 2010 13:13:00 -0700 Subject: [PATCH 006/229] Added Cheetah to pip-requires --- tools/pip-requires | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/pip-requires b/tools/pip-requires index 4eb47ca2b0..a235d62461 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -1,4 +1,5 @@ IPy==0.70 +Cheetah==2.4.2.1 M2Crypto==0.20.2 amqplib==0.6.1 anyjson==0.2.4 From 7985f5fb32432630b3fef775f601900f89346ed2 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Fri, 30 Jul 2010 13:35:21 -0700 Subject: [PATCH 007/229] Accept a configurable libvirt_uri --- nova/virt/libvirt_conn.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 6bc5ebf898..8d473296ad 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -49,6 +49,9 @@ FLAGS = flags.FLAGS flags.DEFINE_string('libvirt_xml_template', utils.abspath('compute/libvirt.xml.template'), 'Libvirt XML Template') +flags.DEFINE_string('libvirt_uri', + 'qemu:///system', + 'Libvirt URI to connect to') def get_connection(read_only): # These are loaded late so that there's no need to install these @@ -67,10 +70,11 @@ class LibvirtConnection(object): auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_NOECHOPROMPT], 'root', None] + libvirt_uri = str(FLAGS.libvirt_uri) if read_only: - self._conn = libvirt.openReadOnly('qemu:///system') + self._conn = libvirt.openReadOnly(libvirt_uri) else: - self._conn = libvirt.openAuth('qemu:///system', auth, 0) + self._conn = libvirt.openAuth(libvirt_uri, auth, 0) def list_instances(self): From 6227cb13c311624998b9c2c6e55fc0d261cfcb6c Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Sat, 31 Jul 2010 20:49:21 -0700 Subject: [PATCH 008/229] Recognize 'magic' kernel value that means "don't use a kernel" - currently aki-00000000 --- nova/endpoint/cloud.py | 10 ++++++++-- nova/flags.py | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index b4157e48c5..344352a93a 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -523,9 +523,15 @@ class CloudController(object): kernel_id = kwargs.get('kernel_id', kernel_id) ramdisk_id = kwargs.get('ramdisk_id', ramdisk_id) + if kernel_id == str(FLAGS.null_kernel): + kernel_id = None + ramdisk_id = None + # make sure we have access to kernel and ramdisk - self._get_image(context, kernel_id) - self._get_image(context, ramdisk_id) + if kernel_id: + self._get_image(context, kernel_id) + if ramdisk_id: + self._get_image(context, ramdisk_id) logging.debug("Going to run instances...") reservation_id = utils.generate_uid('r') diff --git a/nova/flags.py b/nova/flags.py index f35f5fa10c..caae33e14c 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -57,6 +57,9 @@ DEFINE_string('ec2_url', 'http://127.0.0.1:8773/services/Cloud', 'Url to ec2 api server') +DEFINE_string('null_kernel', + 'aki-00000000', + 'Kernel image that indicates not to use a kernel (use a raw disk image instead)') DEFINE_string('default_image', 'ami-11111', 'default image to use, testing only') From 6c32e87c1be80230cf058586cee5a94cd25670b8 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Sat, 31 Jul 2010 21:35:58 -0700 Subject: [PATCH 009/229] Fixed up some of the raw disk stuff that broke in the abstraction out of libvirt --- nova/compute/disk.py | 3 +++ nova/virt/libvirt_conn.py | 50 ++++++++++++++++++++++++++++----------- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/nova/compute/disk.py b/nova/compute/disk.py index 848ce8b541..cace849fad 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -109,6 +109,9 @@ def inject_data(image, key=None, net=None, dns=None, remove_network_udev=False, else: mapped_device = device + if not os.path.exists(mapped_device): + raise exception.Error('Mapped device was not found: %s' % mapped_device) + # Configure ext2fs so that it doesn't auto-check every N boots out, err = yield execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 8d473296ad..d181586787 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -195,32 +195,54 @@ class LibvirtConnection(object): user = manager.AuthManager().get_user(data['user_id']) if not os.path.exists(basepath('disk')): yield images.fetch(data['image_id'], basepath('disk-raw'), user) - if not os.path.exists(basepath('kernel')): - yield images.fetch(data['kernel_id'], basepath('kernel'), user) - if not os.path.exists(basepath('ramdisk')): - yield images.fetch(data['ramdisk_id'], basepath('ramdisk'), user) + + using_kernel = data['kernel_id'] and True + + if using_kernel: + if not os.path.exists(basepath('kernel')): + yield images.fetch(data['kernel_id'], basepath('kernel'), user) + if not os.path.exists(basepath('ramdisk')): + yield images.fetch(data['ramdisk_id'], basepath('ramdisk'), user) execute = lambda cmd, input=None: \ process.simple_execute(cmd=cmd, input=input, error_ok=1) + # For now, we assume that if we're not using a kernel, we're using a partitioned disk image + # where the target partition is the first partition + target_partition = None + if not using_kernel: + target_partition = "1" + key = data['key_data'] net = None if FLAGS.simple_network: - with open(FLAGS.simple_network_template) as f: - net = f.read() % {'address': data['private_dns_name'], + network_info = {'address': data['private_dns_name'], 'network': FLAGS.simple_network_network, 'netmask': FLAGS.simple_network_netmask, 'gateway': FLAGS.simple_network_gateway, 'broadcast': FLAGS.simple_network_broadcast, 'dns': FLAGS.simple_network_dns} - if key or net: - logging.info('Injecting data into image %s', data['image_id']) - yield disk.inject_data(basepath('disk-raw'), key, net, execute=execute) + + with open(FLAGS.simple_network_template) as f: + net = f.read() % network_info - if os.path.exists(basepath('disk')): - yield process.simple_execute('rm -f %s' % basepath('disk')) + with open(FLAGS.simple_network_dns_template) as f: + dns = str(Template(f.read(), searchList=[ network_info ] )) + + + if key or net or dns: + logging.info('Injecting data into image %s', data['image_id']) + try: + yield disk.inject_data(basepath('disk-raw'), key=key, net=net, dns=dns, remove_network_udev=True, partition=target_partition, execute=execute) + except Exception as e: + # This could be a windows image, or a vmdk format disk + logging.warn('Could not inject data; ignoring. (%s)' % e) + + if using_kernel: + if os.path.exists(basepath('disk')): + yield process.simple_execute('rm -f %s' % basepath('disk')) bytes = (instance_types.INSTANCE_TYPES[data['instance_type']]['local_gb'] * 1024 * 1024 * 1024) @@ -232,15 +254,15 @@ class LibvirtConnection(object): return os.path.abspath(os.path.join(instance.datamodel['basepath'], path)) - def toXml(self): + def toXml(self, instance): # TODO(termie): cache? logging.debug("Starting the toXML method") template_contents = open(FLAGS.libvirt_xml_template).read() - xml_info = self.datamodel.copy() + xml_info = instance.datamodel.copy() # TODO(joshua): Make this xml express the attached disks as well # TODO(termie): lazy lazy hack because xml is annoying - xml_info['nova'] = json.dumps(self.datamodel.copy()) + xml_info['nova'] = json.dumps(instance.datamodel.copy()) if xml_info['kernel_id']: xml_info['kernel'] = xml_info['basepath'] + "/kernel" From 759bab6059ef2e4c463a73e12fe85fe4b147eba7 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Sat, 21 Aug 2010 13:07:03 +0100 Subject: [PATCH 010/229] Clarified what the 'Mapped device not found' exception really means. Fixed TODO. Some formatting to be closer to 80 chars --- nova/compute/disk.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/nova/compute/disk.py b/nova/compute/disk.py index 6099eb4ba2..4ede9a7aa8 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -87,7 +87,9 @@ def partition(infile, outfile, local_bytes=0, local_type='ext2', execute=None): @defer.inlineCallbacks -def inject_data(image, key=None, net=None, dns=None, remove_network_udev=False, partition=None, execute=None): +def inject_data( image, key=None, net=None, dns=None, + remove_network_udev=False, + partition=None, execute=None): """Injects a ssh key and optionally net data into a disk image. it will mount the image as a fully partitioned disk and attempt to inject @@ -111,8 +113,12 @@ def inject_data(image, key=None, net=None, dns=None, remove_network_udev=False, else: mapped_device = device + # We can only loopback mount raw images. If the device isn't there, + # it's normally because it's a .vmdk or a .vdi etc if not os.path.exists(mapped_device): - raise exception.Error('Mapped device was not found: %s' % mapped_device) + raise exception.Error( + 'Mapped device was not found (we can only inject raw disk images): %s' + % mapped_device) # Configure ext2fs so that it doesn't auto-check every N boots out, err = yield execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device) @@ -172,7 +178,9 @@ def _inject_dns_into_fs(dns, fs, execute=None): @defer.inlineCallbacks def _remove_network_udev(fs, execute=None): - # This is correct for Ubuntu, but might not be right for other distros + # TODO(justinsb): This is correct for Ubuntu, but might not be right for + # other distros. There is a much bigger discussion to be had about what + # we inject and how we inject it. rulesfile = os.path.join(fs, 'etc', 'udev', 'rules.d', '70-persistent-net.rules') yield execute('rm -f %s' % rulesfile) From 9732c0af89f21490cc8d6bc80799bbc8b36fb441 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Thu, 14 Oct 2010 13:38:35 -0700 Subject: [PATCH 011/229] Minimized diff, fixed formatting --- nova/compute/disk.py | 22 +--------------------- nova/compute/resolv.conf.template | 1 - nova/flags.py | 3 ++- nova/virt/libvirt.uml.xml.template | 2 +- nova/virt/libvirt.xen.xml.template | 2 +- nova/virt/libvirt_conn.py | 2 ++ 6 files changed, 7 insertions(+), 25 deletions(-) delete mode 100644 nova/compute/resolv.conf.template diff --git a/nova/compute/disk.py b/nova/compute/disk.py index 4ede9a7aa8..b1ddb0a1f9 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -87,9 +87,7 @@ def partition(infile, outfile, local_bytes=0, local_type='ext2', execute=None): @defer.inlineCallbacks -def inject_data( image, key=None, net=None, dns=None, - remove_network_udev=False, - partition=None, execute=None): +def inject_data(image, key=None, net=None, partition=None, execute=None): """Injects a ssh key and optionally net data into a disk image. it will mount the image as a fully partitioned disk and attempt to inject @@ -137,11 +135,6 @@ def inject_data( image, key=None, net=None, dns=None, yield _inject_key_into_fs(key, tmpdir, execute=execute) if net: yield _inject_net_into_fs(net, tmpdir, execute=execute) - if dns: - yield _inject_dns_into_fs(dns, tmpdir, execute=execute) - if remove_network_udev: - yield _remove_network_udev(tmpdir, execute=execute) - finally: # unmount device yield execute('sudo umount %s' % mapped_device) @@ -171,16 +164,3 @@ def _inject_net_into_fs(net, fs, execute=None): netfile = os.path.join(fs, 'etc', 'network', 'interfaces') yield execute('sudo tee %s' % netfile, net) -@defer.inlineCallbacks -def _inject_dns_into_fs(dns, fs, execute=None): - dnsfile = os.path.join(fs, 'etc', 'resolv.conf') - yield execute('sudo tee %s' % dnsfile, dns) - -@defer.inlineCallbacks -def _remove_network_udev(fs, execute=None): - # TODO(justinsb): This is correct for Ubuntu, but might not be right for - # other distros. There is a much bigger discussion to be had about what - # we inject and how we inject it. - rulesfile = os.path.join(fs, 'etc', 'udev', 'rules.d', '70-persistent-net.rules') - yield execute('rm -f %s' % rulesfile) - diff --git a/nova/compute/resolv.conf.template b/nova/compute/resolv.conf.template deleted file mode 100644 index 7ddb551900..0000000000 --- a/nova/compute/resolv.conf.template +++ /dev/null @@ -1 +0,0 @@ -nameserver ${dns} diff --git a/nova/flags.py b/nova/flags.py index 2b96a15f7b..d2c22e46ba 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -202,7 +202,8 @@ DEFINE_string('default_ramdisk', 'ari-11111', DEFINE_string('default_instance_type', 'm1.small', 'default instance type to use, testing only') DEFINE_string('null_kernel', 'aki-00000000', - 'kernel image that indicates not to use a kernel, to use a raw disk image instead') + 'kernel image that indicates not to use a kernel, ' + ' but to use a raw disk image instead') DEFINE_string('vpn_image_id', 'ami-CLOUDPIPE', 'AMI for cloudpipe vpn server') DEFINE_string('vpn_key_suffix', diff --git a/nova/virt/libvirt.uml.xml.template b/nova/virt/libvirt.uml.xml.template index f6e5fad690..da95880492 100644 --- a/nova/virt/libvirt.uml.xml.template +++ b/nova/virt/libvirt.uml.xml.template @@ -12,7 +12,7 @@ - + diff --git a/nova/virt/libvirt.xen.xml.template b/nova/virt/libvirt.xen.xml.template index 9508ad3b70..8f650e5124 100644 --- a/nova/virt/libvirt.xen.xml.template +++ b/nova/virt/libvirt.xen.xml.template @@ -23,7 +23,7 @@ - + diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index ba5d6dbac2..ece98087be 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -72,6 +72,7 @@ flags.DEFINE_bool('allow_project_net_traffic', True, 'Whether to allow in project network traffic') + def get_connection(read_only): # These are loaded late so that there's no need to install these # libraries when not using libvirt. @@ -126,6 +127,7 @@ class LibvirtConnection(object): auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_NOECHOPROMPT], 'root', None] + if read_only: return libvirt.openReadOnly(uri) else: From f8028c0a4cd1c3cfb8a9c6b4c397fd67ce912cce Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Thu, 14 Oct 2010 13:48:34 -0700 Subject: [PATCH 012/229] Removed stray spaces that were causing an unnecessary diff line --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index ece98087be..68791c28ac 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -127,7 +127,7 @@ class LibvirtConnection(object): auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_NOECHOPROMPT], 'root', None] - + if read_only: return libvirt.openReadOnly(uri) else: From 98623a160078dfed7347dcd1539b0cd27e51644a Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Fri, 15 Oct 2010 11:06:16 -0700 Subject: [PATCH 013/229] Removed 'and True' oddity --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 68791c28ac..d78a8f0db2 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -324,7 +324,7 @@ class LibvirtConnection(object): if not os.path.exists(basepath('disk')): yield images.fetch(inst.image_id, basepath('disk-raw'), user, project) - using_kernel = inst.kernel_id and True + using_kernel = inst.kernel_id if using_kernel: if not os.path.exists(basepath('kernel')): yield images.fetch(inst.kernel_id, basepath('kernel'), user, project) From 2337fab0979b72bbc7e7730e94518a0e835a2751 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 25 Oct 2010 03:45:19 +0900 Subject: [PATCH 014/229] part way through porting the codebase off of twisted this provides a very basic eventlet-based service replacement for the twistd-based services, a replacement for task.LoopingCall also adds nova-combined with the goal of running a single service when doing local testing and dev --- bin/nova-combined | 61 ++++++++ bin/nova-compute | 12 +- bin/nova-network | 14 +- nova/compute/disk.py | 66 ++++----- nova/compute/manager.py | 28 ++-- nova/flags.py | 7 + nova/manager.py | 1 - nova/network/manager.py | 4 +- nova/server.py | 6 +- nova/service_eventlet.py | 288 ++++++++++++++++++++++++++++++++++++++ nova/utils.py | 36 +++++ nova/virt/fake.py | 6 +- nova/virt/libvirt_conn.py | 176 +++++++++++------------ nova/virt/xenapi.py | 103 ++++++-------- 14 files changed, 576 insertions(+), 232 deletions(-) create mode 100755 bin/nova-combined create mode 100644 nova/service_eventlet.py diff --git a/bin/nova-combined b/bin/nova-combined new file mode 100755 index 0000000000..65865acd98 --- /dev/null +++ b/bin/nova-combined @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" + Twistd daemon for the nova compute nodes. +""" + +import eventlet +eventlet.monkey_patch() + +import os +import sys + +from eventlet import greenthread + +# If ../nova/__init__.py exists, add ../ to Python search path, so that +# it will override what happens to be installed in /usr/(local/)lib/python... +possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), + os.pardir, + os.pardir)) +if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): + sys.path.insert(0, possible_topdir) + +from nova import api +from nova import flags +from nova import service_eventlet +from nova import wsgi + + +FLAGS = flags.FLAGS +flags.DEFINE_integer('api_port', 8773, 'API port') + + +if __name__ == '__main__': + FLAGS(sys.argv) + + compute = service_eventlet.Service.create(binary='nova-compute') + network = service_eventlet.Service.create(binary='nova-network') + volume = service_eventlet.Service.create(binary='nova-volume') + scheduler = service_eventlet.Service.create(binary='nova-scheduler') + #objectstore = service_eventlet.Service.create(binary='nova-objectstore') + + service_eventlet.serve(compute, network, volume, scheduler) + wsgi.run_server(api.API(), FLAGS.api_port) + diff --git a/bin/nova-compute b/bin/nova-compute index 1724e96594..600fbb8973 100755 --- a/bin/nova-compute +++ b/bin/nova-compute @@ -21,6 +21,9 @@ Twistd daemon for the nova compute nodes. """ +import eventlet +eventlet.monkey_patch() + import os import sys @@ -32,12 +35,7 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): sys.path.insert(0, possible_topdir) -from nova import service -from nova import twistd - +from nova import service_eventlet if __name__ == '__main__': - twistd.serve(__file__) - -if __name__ == '__builtin__': - application = service.Service.create() # pylint: disable=C0103 + service_eventlet.serve() diff --git a/bin/nova-network b/bin/nova-network index fa88aeb476..600fbb8973 100755 --- a/bin/nova-network +++ b/bin/nova-network @@ -18,9 +18,12 @@ # under the License. """ - Twistd daemon for the nova network nodes. + Twistd daemon for the nova compute nodes. """ +import eventlet +eventlet.monkey_patch() + import os import sys @@ -32,12 +35,7 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): sys.path.insert(0, possible_topdir) -from nova import service -from nova import twistd - +from nova import service_eventlet if __name__ == '__main__': - twistd.serve(__file__) - -if __name__ == '__builtin__': - application = service.Service.create() # pylint: disable-msg=C0103 + service_eventlet.serve() diff --git a/nova/compute/disk.py b/nova/compute/disk.py index e362b4507e..ad4c2c0925 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -25,8 +25,6 @@ import logging import os import tempfile -from twisted.internet import defer - from nova import exception from nova import flags @@ -38,7 +36,6 @@ flags.DEFINE_integer('block_size', 1024 * 1024 * 256, 'block_size to use for dd') -@defer.inlineCallbacks def partition(infile, outfile, local_bytes=0, resize=True, local_type='ext2', execute=None): """Takes a single partition represented by infile and writes a bootable @@ -60,10 +57,10 @@ def partition(infile, outfile, local_bytes=0, resize=True, file_size = os.path.getsize(infile) if resize and file_size < FLAGS.minimum_root_size: last_sector = FLAGS.minimum_root_size / sector_size - 1 - yield execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d' - % (infile, last_sector, sector_size)) - yield execute('e2fsck -fp %s' % infile, check_exit_code=False) - yield execute('resize2fs %s' % infile) + execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d' + % (infile, last_sector, sector_size)) + execute('e2fsck -fp %s' % infile, check_exit_code=False) + execute('resize2fs %s' % infile) file_size = FLAGS.minimum_root_size elif file_size % sector_size != 0: logging.warn("Input partition size not evenly divisible by" @@ -82,30 +79,29 @@ def partition(infile, outfile, local_bytes=0, resize=True, last_sector = local_last # e # create an empty file - yield execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d' - % (outfile, mbr_last, sector_size)) + execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d' + % (outfile, mbr_last, sector_size)) # make mbr partition - yield execute('parted --script %s mklabel msdos' % outfile) + execute('parted --script %s mklabel msdos' % outfile) # append primary file - yield execute('dd if=%s of=%s bs=%s conv=notrunc,fsync oflag=append' - % (infile, outfile, FLAGS.block_size)) + execute('dd if=%s of=%s bs=%s conv=notrunc,fsync oflag=append' + % (infile, outfile, FLAGS.block_size)) # make primary partition - yield execute('parted --script %s mkpart primary %ds %ds' - % (outfile, primary_first, primary_last)) + execute('parted --script %s mkpart primary %ds %ds' + % (outfile, primary_first, primary_last)) if local_bytes > 0: # make the file bigger - yield execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d' - % (outfile, last_sector, sector_size)) + execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d' + % (outfile, last_sector, sector_size)) # make and format local partition - yield execute('parted --script %s mkpartfs primary %s %ds %ds' - % (outfile, local_type, local_first, local_last)) + execute('parted --script %s mkpartfs primary %s %ds %ds' + % (outfile, local_type, local_first, local_last)) -@defer.inlineCallbacks def inject_data(image, key=None, net=None, partition=None, execute=None): """Injects a ssh key and optionally net data into a disk image. @@ -115,26 +111,26 @@ def inject_data(image, key=None, net=None, partition=None, execute=None): If partition is not specified it mounts the image as a single partition. """ - out, err = yield execute('sudo losetup -f --show %s' % image) + out, err = execute('sudo losetup -f --show %s' % image) if err: raise exception.Error('Could not attach image to loopback: %s' % err) device = out.strip() try: if not partition is None: # create partition - out, err = yield execute('sudo kpartx -a %s' % device) + out, err = execute('sudo kpartx -a %s' % device) if err: raise exception.Error('Failed to load partition: %s' % err) mapped_device = '/dev/mapper/%sp%s' % (device.split('/')[-1], partition) else: mapped_device = device - out, err = yield execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device) + out, err = execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device) tmpdir = tempfile.mkdtemp() try: # mount loopback to dir - out, err = yield execute( + out, err = execute( 'sudo mount %s %s' % (mapped_device, tmpdir)) if err: raise exception.Error('Failed to mount filesystem: %s' % err) @@ -142,35 +138,33 @@ def inject_data(image, key=None, net=None, partition=None, execute=None): try: if key: # inject key file - yield _inject_key_into_fs(key, tmpdir, execute=execute) + _inject_key_into_fs(key, tmpdir, execute=execute) if net: - yield _inject_net_into_fs(net, tmpdir, execute=execute) + _inject_net_into_fs(net, tmpdir, execute=execute) finally: # unmount device - yield execute('sudo umount %s' % mapped_device) + execute('sudo umount %s' % mapped_device) finally: # remove temporary directory - yield execute('rmdir %s' % tmpdir) + execute('rmdir %s' % tmpdir) if not partition is None: # remove partitions - yield execute('sudo kpartx -d %s' % device) + execute('sudo kpartx -d %s' % device) finally: # remove loopback - yield execute('sudo losetup -d %s' % device) + execute('sudo losetup -d %s' % device) -@defer.inlineCallbacks def _inject_key_into_fs(key, fs, execute=None): sshdir = os.path.join(os.path.join(fs, 'root'), '.ssh') - yield execute('sudo mkdir -p %s' % sshdir) # existing dir doesn't matter - yield execute('sudo chown root %s' % sshdir) - yield execute('sudo chmod 700 %s' % sshdir) + execute('sudo mkdir -p %s' % sshdir) # existing dir doesn't matter + execute('sudo chown root %s' % sshdir) + execute('sudo chmod 700 %s' % sshdir) keyfile = os.path.join(sshdir, 'authorized_keys') - yield execute('sudo tee -a %s' % keyfile, '\n' + key.strip() + '\n') + execute('sudo tee -a %s' % keyfile, '\n' + key.strip() + '\n') -@defer.inlineCallbacks def _inject_net_into_fs(net, fs, execute=None): netfile = os.path.join(os.path.join(os.path.join( fs, 'etc'), 'network'), 'interfaces') - yield execute('sudo tee %s' % netfile, net) + execute('sudo tee %s' % netfile, net) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 523bb88936..a105a1dd03 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -25,8 +25,6 @@ import datetime import logging import os -from twisted.internet import defer - from nova import exception from nova import flags from nova import manager @@ -54,7 +52,7 @@ class ComputeManager(manager.Manager): self.network_manager = utils.import_object(FLAGS.network_manager) self.volume_manager = utils.import_object(FLAGS.volume_manager) super(ComputeManager, self).__init__(*args, **kwargs) - + def _update_state(self, context, instance_id): """Update the state of an instance from the driver info""" # FIXME(ja): include other fields from state? @@ -62,12 +60,10 @@ class ComputeManager(manager.Manager): state = self.driver.get_info(instance_ref.name)['state'] self.db.instance_set_state(context, instance_id, state) - @defer.inlineCallbacks @exception.wrap_exception def refresh_security_group(self, context, security_group_id, **_kwargs): - yield self.driver.refresh_security_group(security_group_id) + self.driver.refresh_security_group(security_group_id) - @defer.inlineCallbacks @exception.wrap_exception def run_instance(self, context, instance_id, **_kwargs): """Launch a new instance with specified options.""" @@ -89,7 +85,7 @@ class ComputeManager(manager.Manager): 'spawning') try: - yield self.driver.spawn(instance_ref) + self.driver.spawn(instance_ref) now = datetime.datetime.utcnow() self.db.instance_update(context, instance_id, @@ -103,7 +99,6 @@ class ComputeManager(manager.Manager): self._update_state(context, instance_id) - @defer.inlineCallbacks @exception.wrap_exception def terminate_instance(self, context, instance_id): """Terminate an instance on this machine.""" @@ -116,12 +111,11 @@ class ComputeManager(manager.Manager): raise exception.Error('trying to destroy already destroyed' ' instance: %s' % instance_id) - yield self.driver.destroy(instance_ref) + self.driver.destroy(instance_ref) # TODO(ja): should we keep it in a terminated state for a bit? self.db.instance_destroy(context, instance_id) - @defer.inlineCallbacks @exception.wrap_exception def reboot_instance(self, context, instance_id): """Reboot an instance on this server.""" @@ -142,7 +136,7 @@ class ComputeManager(manager.Manager): instance_id, power_state.NOSTATE, 'rebooting') - yield self.driver.reboot(instance_ref) + self.driver.reboot(instance_ref) self._update_state(context, instance_id) @exception.wrap_exception @@ -154,7 +148,6 @@ class ComputeManager(manager.Manager): return self.driver.get_console_output(instance_ref) - @defer.inlineCallbacks @exception.wrap_exception def attach_volume(self, context, instance_id, volume_id, mountpoint): """Attach a volume to an instance.""" @@ -164,13 +157,12 @@ class ComputeManager(manager.Manager): instance_ref = self.db.instance_get(context, instance_id) dev_path = yield self.volume_manager.setup_compute_volume(context, volume_id) - yield self.driver.attach_volume(instance_ref['ec2_id'], - dev_path, - mountpoint) + self.driver.attach_volume(instance_ref['ec2_id'], + dev_path, + mountpoint) self.db.volume_attached(context, volume_id, instance_id, mountpoint) defer.returnValue(True) - @defer.inlineCallbacks @exception.wrap_exception def detach_volume(self, context, instance_id, volume_id): """Detach a volume from an instance.""" @@ -180,7 +172,7 @@ class ComputeManager(manager.Manager): volume_id) instance_ref = self.db.instance_get(context, instance_id) volume_ref = self.db.volume_get(context, volume_id) - yield self.driver.detach_volume(instance_ref['ec2_id'], - volume_ref['mountpoint']) + self.driver.detach_volume(instance_ref['ec2_id'], + volume_ref['mountpoint']) self.db.volume_detached(context, volume_id) defer.returnValue(True) diff --git a/nova/flags.py b/nova/flags.py index f3b0384ad8..da9987700d 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -139,6 +139,8 @@ class FlagValues(gflags.FlagValues): FLAGS = FlagValues() +gflags.FLAGS = FLAGS + def _wrapper(func): def _wrapped(*args, **kw): @@ -159,6 +161,11 @@ DEFINE_list = _wrapper(gflags.DEFINE_list) DEFINE_spaceseplist = _wrapper(gflags.DEFINE_spaceseplist) DEFINE_multistring = _wrapper(gflags.DEFINE_multistring) DEFINE_multi_int = _wrapper(gflags.DEFINE_multi_int) +DEFINE_flag = _wrapper(gflags.DEFINE_flag) + +HelpFlag = gflags.HelpFlag +HelpshortFlag = gflags.HelpshortFlag +HelpXMLFlag = gflags.HelpXMLFlag def DECLARE(name, module_string, flag_values=FLAGS): diff --git a/nova/manager.py b/nova/manager.py index 4244b2db4e..994d6e7af5 100644 --- a/nova/manager.py +++ b/nova/manager.py @@ -39,7 +39,6 @@ class Manager(object): db_driver = FLAGS.db_driver self.db = utils.import_object(db_driver) # pylint: disable-msg=C0103 - @defer.inlineCallbacks def periodic_tasks(self, context=None): """Tasks to be run at a periodic interval""" yield diff --git a/nova/network/manager.py b/nova/network/manager.py index fddb77663a..37a15ae050 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -25,7 +25,6 @@ import logging import math import IPy -from twisted.internet import defer from nova import context from nova import db @@ -315,10 +314,9 @@ class FlatDHCPManager(NetworkManager): class VlanManager(NetworkManager): """Vlan network with dhcp""" - @defer.inlineCallbacks def periodic_tasks(self, context=None): """Tasks to be run at a periodic interval""" - yield super(VlanManager, self).periodic_tasks(context) + super(VlanManager, self).periodic_tasks(context) now = datetime.datetime.utcnow() timeout = FLAGS.fixed_ip_disassociate_timeout time = now - datetime.timedelta(seconds=timeout) diff --git a/nova/server.py b/nova/server.py index cb424caa19..fba340a54d 100644 --- a/nova/server.py +++ b/nova/server.py @@ -135,9 +135,9 @@ def daemonize(args, name, main): with daemon.DaemonContext( detach_process=FLAGS.daemonize, working_directory=FLAGS.working_directory, - pidfile=pidlockfile.TimeoutPIDLockFile(FLAGS.pidfile, - acquire_timeout=1, - threaded=False), + #pidfile=pidlockfile.TimeoutPIDLockFile(FLAGS.pidfile, + # acquire_timeout=1, + # threaded=False), stdin=stdin, stdout=stdout, stderr=stderr, diff --git a/nova/service_eventlet.py b/nova/service_eventlet.py new file mode 100644 index 0000000000..eac45a9810 --- /dev/null +++ b/nova/service_eventlet.py @@ -0,0 +1,288 @@ +# 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. + +""" +Generic Node baseclass for all workers that run on hosts +""" + +import inspect +import logging +import os +import sys + +from eventlet import event +from eventlet import greenthread +from eventlet import greenpool + +from nova import context +from nova import db +from nova import exception +from nova import flags +from nova import rpc +from nova import utils + + +FLAGS = flags.FLAGS +flags.DEFINE_integer('report_interval', 10, + 'seconds between nodes reporting state to datastore', + lower_bound=1) + +flags.DEFINE_integer('periodic_interval', 60, + 'seconds between running periodic tasks', + lower_bound=1) + +flags.DEFINE_string('pidfile', None, + 'pidfile to use for this service') + + +flags.DEFINE_flag(flags.HelpFlag()) +flags.DEFINE_flag(flags.HelpshortFlag()) +flags.DEFINE_flag(flags.HelpXMLFlag()) + + +class Service(object): + """Base class for workers that run on hosts.""" + + def __init__(self, host, binary, topic, manager, report_interval=None, + periodic_interval=None, *args, **kwargs): + self.host = host + self.binary = binary + self.topic = topic + self.manager_class_name = manager + self.report_interval = report_interval + self.periodic_interval = periodic_interval + super(Service, self).__init__(*args, **kwargs) + self.saved_args, self.saved_kwargs = args, kwargs + + def start(self): + manager_class = utils.import_class(self.manager_class_name) + self.manager = manager_class(host=self.host, *self.saved_args, + **self.saved_kwargs) + self.manager.init_host() + self.model_disconnected = False + ctxt = context.get_admin_context() + try: + service_ref = db.service_get_by_args(ctxt, + self.host, + self.binary) + self.service_id = service_ref['id'] + except exception.NotFound: + self._create_service_ref(ctxt) + + conn1 = rpc.Connection.instance(new=True) + conn2 = rpc.Connection.instance(new=True) + if self.report_interval: + consumer_all = rpc.AdapterConsumer( + connection=conn1, + topic=self.topic, + proxy=self) + consumer_node = rpc.AdapterConsumer( + connection=conn2, + topic='%s.%s' % (self.topic, self.host), + proxy=self) + + consumer_all.attach_to_eventlet() + consumer_node.attach_to_eventlet() + + pulse = utils.LoopingCall(self.report_state) + pulse.start(interval=self.report_interval, now=False) + + if self.periodic_interval: + pulse = utils.LoopingCall(self.periodic_tasks) + pulse.start(interval=self.periodic_interval, now=False) + + def _create_service_ref(self, context): + service_ref = db.service_create(context, + {'host': self.host, + 'binary': self.binary, + 'topic': self.topic, + 'report_count': 0}) + self.service_id = service_ref['id'] + + def __getattr__(self, key): + manager = self.__dict__.get('manager', None) + return getattr(manager, key) + + @classmethod + def create(cls, + host=None, + binary=None, + topic=None, + manager=None, + report_interval=None, + periodic_interval=None): + """Instantiates class and passes back application object. + + Args: + host, defaults to FLAGS.host + binary, defaults to basename of executable + topic, defaults to bin_name - "nova-" part + manager, defaults to FLAGS._manager + report_interval, defaults to FLAGS.report_interval + periodic_interval, defaults to FLAGS.periodic_interval + """ + if not host: + host = FLAGS.host + if not binary: + binary = os.path.basename(inspect.stack()[-1][1]) + if not topic: + topic = binary.rpartition("nova-")[2] + if not manager: + manager = FLAGS.get('%s_manager' % topic, None) + if not report_interval: + report_interval = FLAGS.report_interval + if not periodic_interval: + periodic_interval = FLAGS.periodic_interval + logging.warn("Starting %s node", topic) + service_obj = cls(host, binary, topic, manager, + report_interval, periodic_interval) + + return service_obj + + def kill(self): + """Destroy the service object in the datastore""" + try: + db.service_destroy(context.get_admin_context(), self.service_id) + except exception.NotFound: + logging.warn("Service killed that has no database entry") + + def periodic_tasks(self): + """Tasks to be run at a periodic interval""" + self.manager.periodic_tasks(context.get_admin_context()) + + def report_state(self): + """Update the state of this service in the datastore.""" + ctxt = context.get_admin_context() + try: + try: + service_ref = db.service_get(ctxt, self.service_id) + except exception.NotFound: + logging.debug("The service database object disappeared, " + "Recreating it.") + self._create_service_ref(ctxt) + service_ref = db.service_get(ctxt, self.service_id) + + db.service_update(ctxt, + self.service_id, + {'report_count': service_ref['report_count'] + 1}) + + # TODO(termie): make this pattern be more elegant. + if getattr(self, "model_disconnected", False): + self.model_disconnected = False + logging.error("Recovered model server connection!") + + # TODO(vish): this should probably only catch connection errors + except Exception: # pylint: disable-msg=W0702 + if not getattr(self, "model_disconnected", False): + self.model_disconnected = True + logging.exception("model server went away") + + +def stop(pidfile): + """ + Stop the daemon + """ + # Get the pid from the pidfile + try: + pf = file(pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if not pid: + message = "pidfile %s does not exist. Daemon not running?\n" + sys.stderr.write(message % pidfile) + # Not an error in a restart + return + + # Try killing the daemon process + try: + while 1: + os.kill(pid, signal.SIGKILL) + time.sleep(0.1) + except OSError, err: + err = str(err) + if err.find("No such process") > 0: + if os.path.exists(pidfile): + os.remove(pidfile) + else: + print str(err) + sys.exit(1) + + +def serve(*services): + argv = FLAGS(sys.argv) + + if not services: + services = [Service.create()] + + name = '_'.join(x.binary for x in services) + logging.debug("Serving %s" % name) + + logging.getLogger('amqplib').setLevel(logging.DEBUG) + + if not FLAGS.pidfile: + FLAGS.pidfile = '%s.pid' % name + # NOTE(vish): if we're running nodaemon, redirect the log to stdout + #if FLAGS.nodaemon and not FLAGS.logfile: + # FLAGS.logfile = "-" + #if not FLAGS.logfile: + # FLAGS.logfile = '%s.log' % name + #if not FLAGS.prefix: + # FLAGS.prefix = name + #elif FLAGS.prefix.endswith('twisted'): + # FLAGS.prefix = FLAGS.prefix.replace('twisted', name) + + action = 'start' + if len(argv) > 1: + action = argv.pop() + + if action == 'stop': + stop(FLAGS.pidfile) + sys.exit() + elif action == 'restart': + stop(FLAGS.pidfile) + elif action == 'start': + pass + else: + print 'usage: %s [options] [start|stop|restart]' % argv[0] + sys.exit(1) + + #formatter = logging.Formatter( + # '(%(name)s): %(levelname)s %(message)s') + #handler = logging.StreamHandler() + #handler.setFormatter(formatter) + #logging.getLogger().addHandler(handler) + + if FLAGS.verbose: + logging.getLogger().setLevel(logging.DEBUG) + else: + logging.getLogger().setLevel(logging.WARNING) + + logging.debug("Full set of FLAGS:") + for flag in FLAGS: + logging.debug("%s : %s" % (flag, FLAGS.get(flag, None))) + + for x in services: + x.start() + + #while True: + # greenthread.sleep(5) + + diff --git a/nova/utils.py b/nova/utils.py index 7683fc9f45..a219e47bf8 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -29,6 +29,9 @@ import subprocess import socket import sys +from eventlet import event +from eventlet import greenthread + from twisted.internet.threads import deferToThread from nova import exception @@ -212,3 +215,36 @@ def deferredToThread(f): def g(*args, **kwargs): return deferToThread(f, *args, **kwargs) return g + + +class LoopingCall(object): + def __init__(self, f=None, *args, **kw): + self.args = args + self.kw = kw + self.f = f + self._running = False + + def start(self, interval, now=True): + self._running = True + done = event.Event() + def _inner(): + if not now: + greenthread.sleep(interval) + try: + while self._running: + self.f(*self.args, **self.kw) + greenthread.sleep(interval) + except Exception: + logging.exception('hhmm') + done.send_exception(*sys.exc_info()) + return + + done.send(True) + + greenthread.spawn(_inner) + return done + + def stop(self): + self._running = False + + diff --git a/nova/virt/fake.py b/nova/virt/fake.py index eaa2261f5c..0684a08779 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -24,8 +24,6 @@ This module also documents the semantics of real hypervisor connections. import logging -from twisted.internet import defer - from nova.compute import power_state @@ -105,7 +103,6 @@ class FakeConnection(object): fake_instance = FakeInstance() self.instances[instance.name] = fake_instance fake_instance._state = power_state.RUNNING - return defer.succeed(None) def reboot(self, instance): """ @@ -117,7 +114,7 @@ class FakeConnection(object): The work will be done asynchronously. This function returns a Deferred that allows the caller to detect when it is complete. """ - return defer.succeed(None) + pass def destroy(self, instance): """ @@ -130,7 +127,6 @@ class FakeConnection(object): Deferred that allows the caller to detect when it is complete. """ del self.instances[instance.name] - return defer.succeed(None) def attach_volume(self, instance_name, device_path, mountpoint): """Attach the disk at device_path to the instance at mountpoint""" diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 509ed97a05..9ca97bd1ba 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -25,10 +25,10 @@ import logging import os import shutil +from eventlet import event +from eventlet import tpool + import IPy -from twisted.internet import defer -from twisted.internet import task -from twisted.internet import threads from nova import context from nova import db @@ -145,13 +145,12 @@ class LibvirtConnection(object): except Exception as _err: pass # If the instance is already terminated, we're still happy - d = defer.Deferred() - d.addCallback(lambda _: self._cleanup(instance)) - # FIXME: What does this comment mean? - # TODO(termie): short-circuit me for tests - # WE'LL save this for when we do shutdown, + + done = event.Event() + + # We'll save this for when we do shutdown, # instead of destroy - but destroy returns immediately - timer = task.LoopingCall(f=None) + timer = utils.LoopingCall(f=None) def _wait_for_shutdown(): try: @@ -160,17 +159,26 @@ class LibvirtConnection(object): instance['id'], state) if state == power_state.SHUTDOWN: timer.stop() - d.callback(None) except Exception: db.instance_set_state(context.get_admin_context(), instance['id'], power_state.SHUTDOWN) timer.stop() - d.callback(None) timer.f = _wait_for_shutdown - timer.start(interval=0.5, now=True) - return d + timer_done = timer.start(interval=0.5, now=True) + + # NOTE(termie): this is strictly superfluous (we could put the + # cleanup code in the timer), but this emulates the + # previous model so I am keeping it around until + # everything has been vetted a bit + def _wait_for_timer(): + timer_done.wait() + self._cleanup(instance) + done.send() + + greenthread.spawn(_wait_for_time) + return done def _cleanup(self, instance): target = os.path.join(FLAGS.instances_path, instance['name']) @@ -179,32 +187,28 @@ class LibvirtConnection(object): if os.path.exists(target): shutil.rmtree(target) - @defer.inlineCallbacks @exception.wrap_exception def attach_volume(self, instance_name, device_path, mountpoint): - yield process.simple_execute("sudo virsh attach-disk %s %s %s" % - (instance_name, - device_path, - mountpoint.rpartition('/dev/')[2])) + process.simple_execute("sudo virsh attach-disk %s %s %s" % + (instance_name, + device_path, + mountpoint.rpartition('/dev/')[2])) - @defer.inlineCallbacks @exception.wrap_exception def detach_volume(self, instance_name, mountpoint): # NOTE(vish): despite the documentation, virsh detach-disk just # wants the device name without the leading /dev/ - yield process.simple_execute("sudo virsh detach-disk %s %s" % - (instance_name, - mountpoint.rpartition('/dev/')[2])) + process.simple_execute("sudo virsh detach-disk %s %s" % + (instance_name, + mountpoint.rpartition('/dev/')[2])) - @defer.inlineCallbacks @exception.wrap_exception def reboot(self, instance): xml = self.to_xml(instance) - yield self._conn.lookupByName(instance['name']).destroy() - yield self._conn.createXML(xml, 0) + self._conn.lookupByName(instance['name']).destroy() + self._conn.createXML(xml, 0) - d = defer.Deferred() - timer = task.LoopingCall(f=None) + timer = utils.LoopingCall(f=None) def _wait_for_reboot(): try: @@ -214,20 +218,16 @@ class LibvirtConnection(object): if state == power_state.RUNNING: logging.debug('instance %s: rebooted', instance['name']) timer.stop() - d.callback(None) except Exception, exn: logging.error('_wait_for_reboot failed: %s', exn) db.instance_set_state(context.get_admin_context(), instance['id'], power_state.SHUTDOWN) timer.stop() - d.callback(None) timer.f = _wait_for_reboot - timer.start(interval=0.5, now=True) - yield d + return timer.start(interval=0.5, now=True) - @defer.inlineCallbacks @exception.wrap_exception def spawn(self, instance): xml = self.to_xml(instance) @@ -235,16 +235,12 @@ class LibvirtConnection(object): instance['id'], power_state.NOSTATE, 'launching') - yield NWFilterFirewall(self._conn).\ - setup_nwfilters_for_instance(instance) - yield self._create_image(instance, xml) - yield self._conn.createXML(xml, 0) - # TODO(termie): this should actually register - # a callback to check for successful boot + NWFilterFirewall(self._conn).setup_nwfilters_for_instance(instance) + self._create_image(instance, xml) + self._conn.createXML(xml, 0) logging.debug("instance %s: is running", instance['name']) - local_d = defer.Deferred() - timer = task.LoopingCall(f=None) + timer = utils.LoopingCall(f=None) def _wait_for_boot(): try: @@ -254,7 +250,6 @@ class LibvirtConnection(object): if state == power_state.RUNNING: logging.debug('instance %s: booted', instance['name']) timer.stop() - local_d.callback(None) except: logging.exception('instance %s: failed to boot', instance['name']) @@ -262,10 +257,9 @@ class LibvirtConnection(object): instance['id'], power_state.SHUTDOWN) timer.stop() - local_d.callback(None) + timer.f = _wait_for_boot - timer.start(interval=0.5, now=True) - yield local_d + return timer.start(interval=0.5, now=True) def _flush_xen_console(self, virsh_output): logging.info('virsh said: %r' % (virsh_output,)) @@ -273,10 +267,9 @@ class LibvirtConnection(object): if virsh_output.startswith('/dev/'): logging.info('cool, it\'s a device') - d = process.simple_execute("sudo dd if=%s iflag=nonblock" % + r = process.simple_execute("sudo dd if=%s iflag=nonblock" % virsh_output, check_exit_code=False) - d.addCallback(lambda r: r[0]) - return d + return r[0] else: return '' @@ -296,21 +289,21 @@ class LibvirtConnection(object): def get_console_output(self, instance): console_log = os.path.join(FLAGS.instances_path, instance['name'], 'console.log') - d = process.simple_execute('sudo chown %d %s' % (os.getuid(), - console_log)) - if FLAGS.libvirt_type == 'xen': - # Xen is spethial - d.addCallback(lambda _: - process.simple_execute("virsh ttyconsole %s" % - instance['name'])) - d.addCallback(self._flush_xen_console) - d.addCallback(self._append_to_file, console_log) - else: - d.addCallback(lambda _: defer.succeed(console_log)) - d.addCallback(self._dump_file) - return d + + process.simple_execute('sudo chown %d %s' % (os.getuid(), + console_log)) + + if FLAGS.libvirt_type == 'xen': + # Xen is special + virsh_output = process.simple_execute("virsh ttyconsole %s" % + instance['name']) + data = self._flush_xen_console(virsh_output) + fpath = self._append_to_file(data, console_log) + else: + fpath = console_log + + return self._dump_file(fpath) - @defer.inlineCallbacks def _create_image(self, inst, libvirt_xml): # syntactic nicety basepath = lambda fname='': os.path.join(FLAGS.instances_path, @@ -318,8 +311,8 @@ class LibvirtConnection(object): fname) # ensure directories exist and are writable - yield process.simple_execute('mkdir -p %s' % basepath()) - yield process.simple_execute('chmod 0777 %s' % basepath()) + process.simple_execute('mkdir -p %s' % basepath()) + process.simple_execute('chmod 0777 %s' % basepath()) # TODO(termie): these are blocking calls, it would be great # if they weren't. @@ -335,19 +328,19 @@ class LibvirtConnection(object): project = manager.AuthManager().get_project(inst['project_id']) if not os.path.exists(basepath('disk')): - yield images.fetch(inst.image_id, basepath('disk-raw'), user, - project) + images.fetch(inst.image_id, basepath('disk-raw'), user, + project) if not os.path.exists(basepath('kernel')): - yield images.fetch(inst.kernel_id, basepath('kernel'), user, - project) + images.fetch(inst.kernel_id, basepath('kernel'), user, + project) if not os.path.exists(basepath('ramdisk')): - yield images.fetch(inst.ramdisk_id, basepath('ramdisk'), user, - project) - - execute = lambda cmd, process_input=None, check_exit_code=True: \ - process.simple_execute(cmd=cmd, - process_input=process_input, - check_exit_code=check_exit_code) + images.fetch(inst.ramdisk_id, basepath('ramdisk'), user, + project) + + def execute(cmd, process_input=None, check_exit_code=True): + return process.simple_execute(cmd=cmd, + process_input=process_input, + check_exit_code=check_exit_code) key = str(inst['key_data']) net = None @@ -369,23 +362,23 @@ class LibvirtConnection(object): if net: logging.info('instance %s: injecting net into image %s', inst['name'], inst.image_id) - yield disk.inject_data(basepath('disk-raw'), key, net, - execute=execute) + disk.inject_data(basepath('disk-raw'), key, net, + execute=execute) if os.path.exists(basepath('disk')): - yield process.simple_execute('rm -f %s' % basepath('disk')) + process.simple_execute('rm -f %s' % basepath('disk')) local_bytes = (instance_types.INSTANCE_TYPES[inst.instance_type] ['local_gb'] * 1024 * 1024 * 1024) resize = inst['instance_type'] != 'm1.tiny' - yield disk.partition(basepath('disk-raw'), basepath('disk'), - local_bytes, resize, execute=execute) + disk.partition(basepath('disk-raw'), basepath('disk'), + local_bytes, resize, execute=execute) if FLAGS.libvirt_type == 'uml': - yield process.simple_execute('sudo chown root %s' % - basepath('disk')) + process.simple_execute('sudo chown root %s' % + basepath('disk')) def to_xml(self, instance): # TODO(termie): cache? @@ -637,15 +630,15 @@ class NWFilterFirewall(object): def _define_filter(self, xml): if callable(xml): xml = xml() - d = threads.deferToThread(self._conn.nwfilterDefineXML, xml) - return d + + # execute in a native thread and block until done + tpool.execute(self._conn.nwfilterDefineXML, xml) @staticmethod def _get_net_and_mask(cidr): net = IPy.IP(cidr) return str(net.net()), str(net.netmask()) - @defer.inlineCallbacks def setup_nwfilters_for_instance(self, instance): """ Creates an NWFilter for the given instance. In the process, @@ -653,10 +646,10 @@ class NWFilterFirewall(object): the base filter are all in place. """ - yield self._define_filter(self.nova_base_ipv4_filter) - yield self._define_filter(self.nova_base_ipv6_filter) - yield self._define_filter(self.nova_dhcp_filter) - yield self._define_filter(self.nova_base_filter) + self._define_filter(self.nova_base_ipv4_filter) + self._define_filter(self.nova_base_ipv6_filter) + self._define_filter(self.nova_dhcp_filter) + self._define_filter(self.nova_base_filter) nwfilter_xml = "\n" \ " \n" % \ @@ -668,20 +661,19 @@ class NWFilterFirewall(object): net, mask = self._get_net_and_mask(network_ref['cidr']) project_filter = self.nova_project_filter(instance['project_id'], net, mask) - yield self._define_filter(project_filter) + self._define_filter(project_filter) nwfilter_xml += " \n" % \ instance['project_id'] for security_group in instance.security_groups: - yield self.ensure_security_group_filter(security_group['id']) + self.ensure_security_group_filter(security_group['id']) nwfilter_xml += " \n" % \ security_group['id'] nwfilter_xml += "" - yield self._define_filter(nwfilter_xml) - return + self._define_filter(nwfilter_xml) def ensure_security_group_filter(self, security_group_id): return self._define_filter( diff --git a/nova/virt/xenapi.py b/nova/virt/xenapi.py index a17e405ab1..f997d01d72 100644 --- a/nova/virt/xenapi.py +++ b/nova/virt/xenapi.py @@ -36,11 +36,10 @@ reactor thread if the VM.get_by_name_label or VM.get_record calls block. """ import logging +import sys import xmlrpclib -from twisted.internet import defer -from twisted.internet import reactor -from twisted.internet import task +from eventlet import tpool from nova import db from nova import flags @@ -110,36 +109,33 @@ class XenAPIConnection(object): return [self._conn.xenapi.VM.get_name_label(vm) \ for vm in self._conn.xenapi.VM.get_all()] - @defer.inlineCallbacks def spawn(self, instance): - vm = yield self._lookup(instance.name) + vm = self._lookup(instance.name) if vm is not None: raise Exception('Attempted to create non-unique name %s' % instance.name) network = db.project_get_network(None, instance.project_id) - network_ref = \ - yield self._find_network_with_bridge(network.bridge) + network_ref = self._find_network_with_bridge(network.bridge) user = AuthManager().get_user(instance.user_id) project = AuthManager().get_project(instance.project_id) - vdi_uuid = yield self._fetch_image( + vdi_uuid = self._fetch_image( instance.image_id, user, project, True) - kernel = yield self._fetch_image( + kernel = self._fetch_image( instance.kernel_id, user, project, False) - ramdisk = yield self._fetch_image( + ramdisk = self._fetch_image( instance.ramdisk_id, user, project, False) - vdi_ref = yield self._call_xenapi('VDI.get_by_uuid', vdi_uuid) + vdi_ref = self._call_xenapi('VDI.get_by_uuid', vdi_uuid) - vm_ref = yield self._create_vm(instance, kernel, ramdisk) - yield self._create_vbd(vm_ref, vdi_ref, 0, True) + vm_ref = self._create_vm(instance, kernel, ramdisk) + self._create_vbd(vm_ref, vdi_ref, 0, True) if network_ref: - yield self._create_vif(vm_ref, network_ref, instance.mac_address) + self._create_vif(vm_ref, network_ref, instance.mac_address) logging.debug('Starting VM %s...', vm_ref) - yield self._call_xenapi('VM.start', vm_ref, False, False) + self._call_xenapi('VM.start', vm_ref, False, False) logging.info('Spawning VM %s created %s.', instance.name, vm_ref) - @defer.inlineCallbacks def _create_vm(self, instance, kernel, ramdisk): """Create a VM record. Returns a Deferred that gives the new VM reference.""" @@ -177,11 +173,10 @@ class XenAPIConnection(object): 'other_config': {}, } logging.debug('Created VM %s...', instance.name) - vm_ref = yield self._call_xenapi('VM.create', rec) + vm_ref = self._call_xenapi('VM.create', rec) logging.debug('Created VM %s as %s.', instance.name, vm_ref) - defer.returnValue(vm_ref) + return vm_ref - @defer.inlineCallbacks def _create_vbd(self, vm_ref, vdi_ref, userdevice, bootable): """Create a VBD record. Returns a Deferred that gives the new VBD reference.""" @@ -200,12 +195,11 @@ class XenAPIConnection(object): vbd_rec['qos_algorithm_params'] = {} vbd_rec['qos_supported_algorithms'] = [] logging.debug('Creating VBD for VM %s, VDI %s ... ', vm_ref, vdi_ref) - vbd_ref = yield self._call_xenapi('VBD.create', vbd_rec) + vbd_ref = self._call_xenapi('VBD.create', vbd_rec) logging.debug('Created VBD %s for VM %s, VDI %s.', vbd_ref, vm_ref, vdi_ref) - defer.returnValue(vbd_ref) + return vbd_ref - @defer.inlineCallbacks def _create_vif(self, vm_ref, network_ref, mac_address): """Create a VIF record. Returns a Deferred that gives the new VIF reference.""" @@ -221,24 +215,22 @@ class XenAPIConnection(object): vif_rec['qos_algorithm_params'] = {} logging.debug('Creating VIF for VM %s, network %s ... ', vm_ref, network_ref) - vif_ref = yield self._call_xenapi('VIF.create', vif_rec) + vif_ref = self._call_xenapi('VIF.create', vif_rec) logging.debug('Created VIF %s for VM %s, network %s.', vif_ref, vm_ref, network_ref) - defer.returnValue(vif_ref) + return vif_ref - @defer.inlineCallbacks def _find_network_with_bridge(self, bridge): expr = 'field "bridge" = "%s"' % bridge - networks = yield self._call_xenapi('network.get_all_records_where', - expr) + networks = self._call_xenapi('network.get_all_records_where', + expr) if len(networks) == 1: - defer.returnValue(networks.keys()[0]) + return networks.keys()[0] elif len(networks) > 1: raise Exception('Found non-unique network for bridge %s' % bridge) else: raise Exception('Found no network for bridge %s' % bridge) - @defer.inlineCallbacks def _fetch_image(self, image, user, project, use_sr): """use_sr: True to put the image as a VDI in an SR, False to place it on dom0's filesystem. The former is for VM disks, the latter for @@ -255,33 +247,31 @@ class XenAPIConnection(object): args['password'] = user.secret if use_sr: args['add_partition'] = 'true' - task = yield self._async_call_plugin('objectstore', fn, args) - uuid = yield self._wait_for_task(task) - defer.returnValue(uuid) + task = self._async_call_plugin('objectstore', fn, args) + uuid = self._wait_for_task(task) + return uuid - @defer.inlineCallbacks def reboot(self, instance): - vm = yield self._lookup(instance.name) + vm = self._lookup(instance.name) if vm is None: raise Exception('instance not present %s' % instance.name) - task = yield self._call_xenapi('Async.VM.clean_reboot', vm) - yield self._wait_for_task(task) + task = self._call_xenapi('Async.VM.clean_reboot', vm) + self._wait_for_task(task) - @defer.inlineCallbacks def destroy(self, instance): - vm = yield self._lookup(instance.name) + vm = self._lookup(instance.name) if vm is None: # Don't complain, just return. This lets us clean up instances # that have already disappeared from the underlying platform. - defer.returnValue(None) + return try: - task = yield self._call_xenapi('Async.VM.hard_shutdown', vm) - yield self._wait_for_task(task) + task = self._call_xenapi('Async.VM.hard_shutdown', vm) + self._wait_for_task(task) except Exception, exc: logging.warn(exc) try: - task = yield self._call_xenapi('Async.VM.destroy', vm) - yield self._wait_for_task(task) + task = self._call_xenapi('Async.VM.destroy', vm) + self._wait_for_task(task) except Exception, exc: logging.warn(exc) @@ -299,7 +289,6 @@ class XenAPIConnection(object): def get_console_output(self, instance): return 'FAKE CONSOLE OUTPUT' - @utils.deferredToThread def _lookup(self, i): return self._lookup_blocking(i) @@ -316,35 +305,32 @@ class XenAPIConnection(object): def _wait_for_task(self, task): """Return a Deferred that will give the result of the given task. The task is polled until it completes.""" - d = defer.Deferred() - reactor.callLater(0, self._poll_task, task, d) - return d - @utils.deferredToThread - def _poll_task(self, task, deferred): + done = event.Event() + loop = utis.LoopingTask(self._poll_task, task, done) + loop.start(FLAGS.xenapi_task_poll_interval, now=True) + return done.wait() + + def _poll_task(self, task, done): """Poll the given XenAPI task, and fire the given Deferred if we get a result.""" try: - #logging.debug('Polling task %s...', task) status = self._conn.xenapi.task.get_status(task) if status == 'pending': - reactor.callLater(FLAGS.xenapi_task_poll_interval, - self._poll_task, task, deferred) + return elif status == 'success': result = self._conn.xenapi.task.get_result(task) logging.info('Task %s status: success. %s', task, result) - deferred.callback(_parse_xmlrpc_value(result)) + done.send(_parse_xmlrpc_value(result)) else: error_info = self._conn.xenapi.task.get_error_info(task) logging.warn('Task %s status: %s. %s', task, status, error_info) - deferred.errback(XenAPI.Failure(error_info)) - #logging.debug('Polling task %s done.', task) + done.send_exception(XenAPI.Failure(error_info)) except Exception, exc: logging.warn(exc) - deferred.errback(exc) + done.send_exception(*sys.exc_info()) - @utils.deferredToThread def _call_xenapi(self, method, *args): """Call the specified XenAPI method on a background thread. Returns a Deferred for the result.""" @@ -353,11 +339,10 @@ class XenAPIConnection(object): f = f.__getattr__(m) return f(*args) - @utils.deferredToThread def _async_call_plugin(self, plugin, fn, args): """Call Async.host.call_plugin on a background thread. Returns a Deferred with the task reference.""" - return _unwrap_plugin_exceptions( + return tpool.execute(_unwrap_plugin_exceptions, self._conn.xenapi.Async.host.call_plugin, self._get_xenapi_host(), plugin, fn, args) From a21c338c73c13281dfdd12ccb0c6168498855b9f Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Thu, 4 Nov 2010 15:50:23 -0700 Subject: [PATCH 015/229] Refactored smoketests to use novarc environment and to separate user and admin specific tests --- smoketests/admin_smoketests.py | 92 ++++ smoketests/{novatestcase.py => base.py} | 102 +++-- smoketests/flags.py | 13 +- smoketests/smoketest.py | 566 ------------------------ smoketests/user_smoketests.py | 326 ++++++++++++++ 5 files changed, 484 insertions(+), 615 deletions(-) create mode 100644 smoketests/admin_smoketests.py rename smoketests/{novatestcase.py => base.py} (51%) 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 0000000000..50bb3fa2e1 --- /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/novatestcase.py b/smoketests/base.py similarity index 51% rename from smoketests/novatestcase.py rename to smoketests/base.py index 513e0ca912..5a14d3e098 100644 --- a/smoketests/novatestcase.py +++ b/smoketests/base.py @@ -16,36 +16,26 @@ # 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 - -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 - +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.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') @@ -53,26 +43,50 @@ class NovaTestCase(unittest.TestCase): return client def can_ping(self, ip): - return commands.getstatusoutput('ping -c 1 %s' % ip)[0] == 0 + """ Attempt to ping the specified IP, and give up after 1 second. """ - @property - def admin(self): - return self.nova_admin.connection_for('admin') + # NOTE(devcamcar): ping timeout flag is different in OSX. + if sys.platform == 'darwin': + timeout_flag = 't' + else: + timeout_flag = 'w' - def connection_for(self, username): - return self.nova_admin.connection_for(username) + status, output = commands.getstatusoutput('ping -c1 -%s1 %s' % + (timeout_flag, ip)) + return status == 0 - def create_user(self, username): - return self.nova_admin.create_user(username) + 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') - def get_user(self, username): - return self.nova_admin.get_user(username) + 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.') - def delete_user(self, username): - return self.nova_admin.delete_user(username) + 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 get_signed_zip(self, username): - return self.nova_admin.get_zip(username) + 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: @@ -116,15 +130,25 @@ class NovaTestCase(unittest.TestCase): 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 run_tests(suites): + argv = FLAGS(sys.argv) - 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) + 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) - def tearDown_test_image(self, conn, image_id): - conn.deregister_image(image_id) diff --git a/smoketests/flags.py b/smoketests/flags.py index 3617fb797a..ae4d095085 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/smoketest.py b/smoketests/smoketest.py deleted file mode 100644 index ad95114d40..0000000000 --- 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 0000000000..d29e3aea32 --- /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)) From f127d85d7790585d6e735648dfab13416d79fbde Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 6 Nov 2010 00:02:36 +0000 Subject: [PATCH 016/229] Per-project vpns, certificates, and revocation --- CA/geninter.sh | 26 ++-- CA/genrootca.sh | 1 + CA/openssl.cnf.tmpl | 3 +- CA/{INTER => projects}/.gitignore | 0 CA/{INTER => projects}/.placeholder | 0 bin/nova-manage | 75 +++++++---- nova/api/__init__.py | 2 - nova/api/cloudpipe/__init__.py | 69 ---------- nova/auth/manager.py | 73 ++++++----- nova/cloudpipe/bootscript.sh | 63 --------- nova/cloudpipe/bootscript.template | 50 ++++++++ nova/cloudpipe/pipelib.py | 121 ++++++++++++------ nova/crypto.py | 191 ++++++++++++++++++++++------ nova/db/api.py | 39 ++++++ nova/db/sqlalchemy/api.py | 78 ++++++++++++ nova/db/sqlalchemy/models.py | 12 +- nova/tests/auth_unittest.py | 17 +-- 17 files changed, 529 insertions(+), 291 deletions(-) rename CA/{INTER => projects}/.gitignore (100%) rename CA/{INTER => projects}/.placeholder (100%) delete mode 100644 nova/api/cloudpipe/__init__.py delete mode 100755 nova/cloudpipe/bootscript.sh create mode 100755 nova/cloudpipe/bootscript.template diff --git a/CA/geninter.sh b/CA/geninter.sh index 7d6c280d5a..1fbcc9e73e 100755 --- a/CA/geninter.sh +++ b/CA/geninter.sh @@ -16,16 +16,24 @@ # License for the specific language governing permissions and limitations # under the License. -# ARG is the id of the user -export SUBJ="/C=US/ST=California/L=MountainView/O=AnsoLabs/OU=NovaDev/CN=customer-intCA-$1" -mkdir INTER/$1 -cd INTER/$1 +# $1 is the id of the project and $2 is the subject of the cert +NAME=$1 +SUBJ=$2 +mkdir -p projects/$NAME +cd projects/$NAME cp ../../openssl.cnf.tmpl openssl.cnf -sed -i -e s/%USERNAME%/$1/g openssl.cnf +sed -i -e s/%USERNAME%/$NAME/g openssl.cnf mkdir certs crl newcerts private +openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes echo "10" > serial touch index.txt -openssl genrsa -out private/cakey.pem 1024 -config ./openssl.cnf -batch -nodes -openssl req -new -sha2 -key private/cakey.pem -out ../../reqs/inter$1.csr -batch -subj "$SUBJ" -cd ../../ -openssl ca -extensions v3_ca -days 365 -out INTER/$1/cacert.pem -in reqs/inter$1.csr -config openssl.cnf -batch +# NOTE(vish): Disabling intermediate ca's because we don't actually need them. +# It makes more sense to have each project have its own root ca. +# openssl genrsa -out private/cakey.pem 1024 -config ./openssl.cnf -batch -nodes +# openssl req -new -sha256 -key private/cakey.pem -out ../../reqs/inter$NAME.csr -batch -subj "$SUBJ" +openssl ca -gencrl -config ./openssl.cnf -out crl.pem +if [ "`id -u`" != "`grep nova /etc/passwd | cut -d':' -f3`" ]; then + sudo chown -R nova:nogroup . +fi +# cd ../../ +# openssl ca -extensions v3_ca -days 365 -out INTER/$NAME/cacert.pem -in reqs/inter$NAME.csr -config openssl.cnf -batch diff --git a/CA/genrootca.sh b/CA/genrootca.sh index 31976092ef..8f2c3ee3fe 100755 --- a/CA/genrootca.sh +++ b/CA/genrootca.sh @@ -25,4 +25,5 @@ else openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes touch index.txt echo "10" > serial + openssl ca -gencrl -config ./openssl.cnf -out crl.pem fi diff --git a/CA/openssl.cnf.tmpl b/CA/openssl.cnf.tmpl index 639b8e80a5..dd81f1c2b6 100644 --- a/CA/openssl.cnf.tmpl +++ b/CA/openssl.cnf.tmpl @@ -24,7 +24,6 @@ dir = . [ ca ] default_ca = CA_default -unique_subject = no [ CA_default ] serial = $dir/serial @@ -32,6 +31,8 @@ database = $dir/index.txt new_certs_dir = $dir/newcerts certificate = $dir/cacert.pem private_key = $dir/private/cakey.pem +unique_subject = no +default_crl_days = 365 default_days = 365 default_md = md5 preserve = no diff --git a/CA/INTER/.gitignore b/CA/projects/.gitignore similarity index 100% rename from CA/INTER/.gitignore rename to CA/projects/.gitignore diff --git a/CA/INTER/.placeholder b/CA/projects/.placeholder similarity index 100% rename from CA/INTER/.placeholder rename to CA/projects/.placeholder diff --git a/bin/nova-manage b/bin/nova-manage index 08b3da1231..b788ee62d9 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -69,6 +69,7 @@ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): sys.path.insert(0, possible_topdir) from nova import context +from nova import crypto from nova import db from nova import exception from nova import flags @@ -93,32 +94,36 @@ class VpnCommands(object): self.manager = manager.AuthManager() self.pipe = pipelib.CloudPipe() - def list(self): - """Print a listing of the VPNs for all projects.""" + def list(self, project=None): + """Print a listing of the VPN data for one or all projects. + + args: [project=all]""" print "%-12s\t" % 'project', print "%-20s\t" % 'ip:port', + print "%-20s\t" % 'private_ip', print "%s" % 'state' - for project in self.manager.get_projects(): + if project: + projects = [self.manager.get_project(project)] + else: + projects = self.manager.get_projects() + for project in projects: print "%-12s\t" % project.name, - - try: - s = "%s:%s" % (project.vpn_ip, project.vpn_port) - except exception.NotFound: - s = "None" - print "%-20s\t" % s, - + ipport = "%s:%s" % (project.vpn_ip, project.vpn_port) + print "%-20s\t" % ipport, vpn = self._vpn_for(project.id) if vpn: - command = "ping -c1 -w1 %s > /dev/null; echo $?" - out, _err = utils.execute(command % vpn['private_dns_name'], - check_exit_code=False) - if out.strip() == '0': - net = 'up' - else: - net = 'down' - print vpn['private_dns_name'], - print vpn['node_name'], - print vpn['instance_id'], + net = 'down' + address = None + if vpn.get('fixed_ip', None): + address = vpn['fixed_ip']['address'] + command = "ping -c1 -w1 %s > /dev/null; echo $?" + out, _err = utils.execute(command % address, + check_exit_code=False) + if out.strip() == '0': + net = 'up' + print address, + print vpn['host'], + print vpn['ec2_id'], print vpn['state_description'], print net @@ -127,11 +132,11 @@ class VpnCommands(object): def _vpn_for(self, project_id): """Get the VPN instance for a project ID.""" - for instance in db.instance_get_all(context.get_admin_context()): + ctxt = context.get_admin_context() + for instance in db.instance_get_all_by_project(ctxt, project_id): if (instance['image_id'] == FLAGS.vpn_image_id and not instance['state_description'] in - ['shutting_down', 'shutdown'] - and instance['project_id'] == project_id): + ['shutting_down', 'shutdown']): return instance def spawn(self): @@ -146,6 +151,22 @@ class VpnCommands(object): """Start the VPN for a given project.""" self.pipe.launch_vpn_instance(project_id) + def change(self, project_id, ip, port): + """Change the ip and port for a vpn. + + args: project, ip, port""" + project = self.manager.get_project(project_id) + if not project: + print 'No project %s' % (project_id) + return + admin = context.get_admin_context() + network_ref = db.project_get_network(admin, project_id) + db.network_update(admin, + network_ref['id'], + {'vpn_public_address': ip, + 'vpn_public_port': int(port)}) + + class ShellCommands(object): def bpython(self): @@ -292,6 +313,14 @@ class UserCommands(object): is_admin = False self.manager.modify_user(name, access_key, secret_key, is_admin) + def revoke(self, user_id, project_id=None): + """revoke certs for a user + arguments: user_id [project_id]""" + if project_id: + crypto.revoke_certs_by_user_and_project(user_id, project_id) + else: + crypto.revoke_certs_by_user(user_id) + class ProjectCommands(object): """Class for managing projects.""" diff --git a/nova/api/__init__.py b/nova/api/__init__.py index 707c1623ea..176d571a82 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -25,7 +25,6 @@ import webob.dec from nova import flags from nova import wsgi -from nova.api import cloudpipe from nova.api import ec2 from nova.api import openstack from nova.api.ec2 import metadatarequesthandler @@ -74,7 +73,6 @@ class API(wsgi.Router): mapper.connect('%s/{path_info:.*}' % s, controller=mrh, conditions=ec2api_subdomain) - mapper.connect("/cloudpipe/{path_info:.*}", controller=cloudpipe.API()) super(API, self).__init__(mapper) @webob.dec.wsgify diff --git a/nova/api/cloudpipe/__init__.py b/nova/api/cloudpipe/__init__.py deleted file mode 100644 index 6d40990a88..0000000000 --- a/nova/api/cloudpipe/__init__.py +++ /dev/null @@ -1,69 +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. - -""" -REST API Request Handlers for CloudPipe -""" - -import logging -import urllib -import webob -import webob.dec -import webob.exc - -from nova import crypto -from nova import wsgi -from nova.auth import manager -from nova.api.ec2 import cloud - - -_log = logging.getLogger("api") -_log.setLevel(logging.DEBUG) - - -class API(wsgi.Application): - - def __init__(self): - self.controller = cloud.CloudController() - - @webob.dec.wsgify - def __call__(self, req): - if req.method == 'POST': - return self.sign_csr(req) - _log.debug("Cloudpipe path is %s" % req.path_info) - if req.path_info.endswith("/getca/"): - return self.send_root_ca(req) - return webob.exc.HTTPNotFound() - - def get_project_id_from_ip(self, ip): - # TODO(eday): This was removed with the ORM branch, fix! - instance = self.controller.get_instance_by_ip(ip) - return instance['project_id'] - - def send_root_ca(self, req): - _log.debug("Getting root ca") - project_id = self.get_project_id_from_ip(req.remote_addr) - res = webob.Response() - res.headers["Content-Type"] = "text/plain" - res.body = crypto.fetch_ca(project_id) - return res - - def sign_csr(self, req): - project_id = self.get_project_id_from_ip(req.remote_addr) - cert = self.str_params['cert'] - return crypto.sign_csr(urllib.unquote(cert), project_id) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 001a968752..c6d4b6e53e 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -64,12 +64,8 @@ flags.DEFINE_string('credential_key_file', 'pk.pem', 'Filename of private key in credentials zip') flags.DEFINE_string('credential_cert_file', 'cert.pem', 'Filename of certificate in credentials zip') -flags.DEFINE_string('credential_rc_file', 'novarc', +flags.DEFINE_string('credential_rc_file', '%src', 'Filename of rc in credentials zip') -flags.DEFINE_string('credential_cert_subject', - '/C=US/ST=California/L=MountainView/O=AnsoLabs/' - 'OU=NovaDev/CN=%s-%s', - 'Subject for certificate for users') flags.DEFINE_string('auth_driver', 'nova.auth.dbdriver.DbDriver', 'Driver that auth manager uses') @@ -625,27 +621,37 @@ class AuthManager(object): with self.driver() as drv: drv.modify_user(uid, access_key, secret_key, admin) - def get_credentials(self, user, project=None): + def get_credentials(self, user, project=None, use_dmz=True): """Get credential zip for user in project""" if not isinstance(user, User): user = self.get_user(user) if project is None: project = user.id pid = Project.safe_id(project) - rc = self.__generate_rc(user.access, user.secret, pid) - private_key, signed_cert = self._generate_x509_cert(user.id, pid) + private_key, signed_cert = crypto.generate_x509_cert(user.id, pid) tmpdir = tempfile.mkdtemp() zf = os.path.join(tmpdir, "temp.zip") zippy = zipfile.ZipFile(zf, 'w') - zippy.writestr(FLAGS.credential_rc_file, rc) + if use_dmz and FLAGS.region_list: + regions = {} + for item in FLAGS.region_list: + region, _sep, region_host = item.partition("=") + regions[region] = region_host + else: + regions = {'nova': FLAGS.cc_host} + for region, host in regions.iteritems(): + rc = self.__generate_rc(user.access, + user.secret, + pid, + use_dmz, + host) + zippy.writestr(FLAGS.credential_rc_file % region, rc) + zippy.writestr(FLAGS.credential_key_file, private_key) zippy.writestr(FLAGS.credential_cert_file, signed_cert) - try: - (vpn_ip, vpn_port) = self.get_project_vpn_data(project) - except exception.NotFound: - vpn_ip = None + (vpn_ip, vpn_port) = self.get_project_vpn_data(project) if vpn_ip: configfile = open(FLAGS.vpn_client_template, "r") s = string.Template(configfile.read()) @@ -656,10 +662,9 @@ class AuthManager(object): port=vpn_port) zippy.writestr(FLAGS.credential_vpn_file, config) else: - logging.warn("No vpn data for project %s" % - pid) + LOG.warn("No vpn data for project %s", pid) - zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(user.id)) + zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(pid)) zippy.close() with open(zf, 'rb') as f: read_buffer = f.read() @@ -667,38 +672,38 @@ class AuthManager(object): shutil.rmtree(tmpdir) return read_buffer - def get_environment_rc(self, user, project=None): + def get_environment_rc(self, user, project=None, use_dmz=True): """Get credential zip for user in project""" if not isinstance(user, User): user = self.get_user(user) if project is None: project = user.id pid = Project.safe_id(project) - return self.__generate_rc(user.access, user.secret, pid) + return self.__generate_rc(user.access, user.secret, pid, use_dmz) @staticmethod - def __generate_rc(access, secret, pid): + def __generate_rc(access, secret, pid, use_dmz=True, host=None): """Generate rc file for user""" + if use_dmz: + cc_host = FLAGS.cc_dmz + else: + cc_host = FLAGS.cc_host + # NOTE(vish): Always use the dmz since it is used from inside the + # instance + s3_host = FLAGS.s3_dmz + if host: + s3_host = host + cc_host = host rc = open(FLAGS.credentials_template).read() rc = rc % {'access': access, 'project': pid, 'secret': secret, - 'ec2': FLAGS.ec2_url, - 's3': 'http://%s:%s' % (FLAGS.s3_host, FLAGS.s3_port), + 'ec2': '%s://%s:%s%s' % (FLAGS.ec2_prefix, + cc_host, + FLAGS.cc_port, + FLAGS.ec2_suffix), + 's3': 'http://%s:%s' % (s3_host, FLAGS.s3_port), 'nova': FLAGS.ca_file, 'cert': FLAGS.credential_cert_file, 'key': FLAGS.credential_key_file} return rc - - def _generate_x509_cert(self, uid, pid): - """Generate x509 cert for user""" - (private_key, csr) = crypto.generate_x509_cert( - self.__cert_subject(uid)) - # TODO(joshua): This should be async call back to the cloud controller - signed_cert = crypto.sign_csr(csr, pid) - return (private_key, signed_cert) - - @staticmethod - def __cert_subject(uid): - """Helper to generate cert subject""" - return FLAGS.credential_cert_subject % (uid, utils.isotime()) diff --git a/nova/cloudpipe/bootscript.sh b/nova/cloudpipe/bootscript.sh deleted file mode 100755 index 30d9ad1021..0000000000 --- a/nova/cloudpipe/bootscript.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/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. - -# This gets zipped and run on the cloudpipe-managed OpenVPN server - -export SUPERVISOR="http://10.255.255.1:8773/cloudpipe" -export VPN_IP=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f2 | awk '{print $1}'` -export BROADCAST=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f3 | awk '{print $1}'` -export DHCP_MASK=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f4 | awk '{print $1}'` -export GATEWAY=`netstat -r | grep default | cut -d' ' -f10` -export SUBJ="/C=US/ST=California/L=MountainView/O=AnsoLabs/OU=NovaDev/CN=customer-vpn-$VPN_IP" - -DHCP_LOWER=`echo $BROADCAST | awk -F. '{print $1"."$2"."$3"." $4 - 10 }'` -DHCP_UPPER=`echo $BROADCAST | awk -F. '{print $1"."$2"."$3"." $4 - 1 }'` - -# generate a server DH -openssl dhparam -out /etc/openvpn/dh1024.pem 1024 - -# generate a server priv key -openssl genrsa -out /etc/openvpn/server.key 2048 - -# generate a server CSR -openssl req -new -key /etc/openvpn/server.key -out /etc/openvpn/server.csr -batch -subj "$SUBJ" - -# URLEncode the CSR -CSRTEXT=`cat /etc/openvpn/server.csr` -CSRTEXT=$(python -c "import urllib; print urllib.quote('''$CSRTEXT''')") - -# SIGN the csr and save as server.crt -# CURL fetch to the supervisor, POSTing the CSR text, saving the result as the CRT file -curl --fail $SUPERVISOR -d "cert=$CSRTEXT" > /etc/openvpn/server.crt -curl --fail $SUPERVISOR/getca/ > /etc/openvpn/ca.crt - -# Customize the server.conf.template -cd /etc/openvpn - -sed -e s/VPN_IP/$VPN_IP/g server.conf.template > server.conf -sed -i -e s/DHCP_SUBNET/$DHCP_MASK/g server.conf -sed -i -e s/DHCP_LOWER/$DHCP_LOWER/g server.conf -sed -i -e s/DHCP_UPPER/$DHCP_UPPER/g server.conf -sed -i -e s/max-clients\ 1/max-clients\ 10/g server.conf - -echo "\npush \"route 10.255.255.1 255.255.255.255 $GATEWAY\"\n" >> server.conf -echo "\npush \"route 10.255.255.253 255.255.255.255 $GATEWAY\"\n" >> server.conf -echo "\nduplicate-cn\n" >> server.conf - -/etc/init.d/openvpn start diff --git a/nova/cloudpipe/bootscript.template b/nova/cloudpipe/bootscript.template new file mode 100755 index 0000000000..11578c1349 --- /dev/null +++ b/nova/cloudpipe/bootscript.template @@ -0,0 +1,50 @@ +#!/bin/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. + +# This gets zipped and run on the cloudpipe-managed OpenVPN server + +export VPN_IP=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f2 | awk '{print $$1}'` +export BROADCAST=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f3 | awk '{print $$1}'` +export DHCP_MASK=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f4 | awk '{print $$1}'` +export GATEWAY=`netstat -r | grep default | cut -d' ' -f10` + +DHCP_LOWER=`echo $$BROADCAST | awk -F. '{print $$1"."$$2"."$$3"." $$4 - ${num_vpn} }'` +DHCP_UPPER=`echo $$BROADCAST | awk -F. '{print $$1"."$$2"."$$3"." $$4 - 1 }'` + +# generate a server DH +openssl dhparam -out /etc/openvpn/dh1024.pem 1024 + +cp crl.pem /etc/openvpn/ +cp server.key /etc/openvpn/ +cp ca.crt /etc/openvpn/ +cp server.crt /etc/openvpn/ +# Customize the server.conf.template +cd /etc/openvpn + +sed -e s/VPN_IP/$$VPN_IP/g server.conf.template > server.conf +sed -i -e s/DHCP_SUBNET/$$DHCP_MASK/g server.conf +sed -i -e s/DHCP_LOWER/$$DHCP_LOWER/g server.conf +sed -i -e s/DHCP_UPPER/$$DHCP_UPPER/g server.conf +sed -i -e s/max-clients\ 1/max-clients\ 10/g server.conf + +echo "push \"route ${dmz_net} ${dmz_mask} $$GATEWAY\"" >> server.conf +echo "duplicate-cn" >> server.conf +echo "crl-verify /etc/openvpn/crl.pem" >> server.conf + +/etc/init.d/openvpn start diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py index 3472201cdf..147b9dd7bd 100644 --- a/nova/cloudpipe/pipelib.py +++ b/nova/cloudpipe/pipelib.py @@ -22,13 +22,15 @@ an instance with it. """ -import base64 import logging import os +import string import tempfile import zipfile from nova import context +from nova import crypto +from nova import db from nova import exception from nova import flags from nova import utils @@ -39,8 +41,17 @@ from nova.api.ec2 import cloud FLAGS = flags.FLAGS flags.DEFINE_string('boot_script_template', - utils.abspath('cloudpipe/bootscript.sh'), + utils.abspath('cloudpipe/bootscript.template'), 'Template for script to run on cloudpipe instance boot') +flags.DEFINE_string('dmz_net', + '10.0.0.0', + 'Network to push into openvpn config') +flags.DEFINE_string('dmz_mask', + '255.255.255.0', + 'Netmask to push into openvpn config') + + +LOG = logging.getLogger('nova-cloudpipe') class CloudPipe(object): @@ -48,64 +59,96 @@ class CloudPipe(object): self.controller = cloud.CloudController() self.manager = manager.AuthManager() - def launch_vpn_instance(self, project_id): - logging.debug("Launching VPN for %s" % (project_id)) - project = self.manager.get_project(project_id) + def get_encoded_zip(self, project_id): # Make a payload.zip tmpfolder = tempfile.mkdtemp() filename = "payload.zip" zippath = os.path.join(tmpfolder, filename) z = zipfile.ZipFile(zippath, "w", zipfile.ZIP_DEFLATED) - - z.write(FLAGS.boot_script_template, 'autorun.sh') + shellfile = open(FLAGS.boot_script_template, "r") + s = string.Template(shellfile.read()) + shellfile.close() + boot_script = s.substitute(cc_dmz=FLAGS.cc_dmz, + cc_port=FLAGS.cc_port, + dmz_net=FLAGS.dmz_net, + dmz_mask=FLAGS.dmz_mask, + num_vpn=FLAGS.cnt_vpn_clients) + # genvpn, sign csr + crypto.generate_vpn_files(project_id) + z.writestr('autorun.sh', boot_script) + crl = os.path.join(crypto.ca_folder(project_id), 'crl.pem') + z.write(crl, 'crl.pem') + server_key = os.path.join(crypto.ca_folder(project_id), 'server.key') + z.write(server_key, 'server.key') + ca_crt = os.path.join(crypto.ca_path(project_id)) + z.write(ca_crt, 'ca.crt') + server_crt = os.path.join(crypto.ca_folder(project_id), 'server.crt') + z.write(server_crt, 'server.crt') z.close() - - key_name = self.setup_key_pair(project.project_manager_id, project_id) zippy = open(zippath, "r") - context = context.RequestContext(user=project.project_manager, - project=project) + # NOTE(vish): run instances expects encoded userdata, it is decoded + # in the get_metadata_call. autorun.sh also decodes the zip file, + # hence the double encoding. + encoded = zippy.read().encode("base64").encode("base64") + zippy.close() + return encoded - reservation = self.controller.run_instances(context, - # Run instances expects encoded userdata, it is decoded in the - # get_metadata_call. autorun.sh also decodes the zip file, hence - # the double encoding. - user_data=zippy.read().encode("base64").encode("base64"), + def launch_vpn_instance(self, project_id): + LOG.debug("Launching VPN for %s" % (project_id)) + project = self.manager.get_project(project_id) + ctxt = context.RequestContext(user=project.project_manager, + project=project) + key_name = self.setup_key_pair(ctxt) + group_name = self.setup_security_group(ctxt) + + reservation = self.controller.run_instances(ctxt, + user_data=self.get_encoded_zip(project_id), max_count=1, min_count=1, instance_type='m1.tiny', image_id=FLAGS.vpn_image_id, key_name=key_name, - security_groups=["vpn-secgroup"]) - zippy.close() + security_group=[group_name]) - def setup_key_pair(self, user_id, project_id): - key_name = '%s%s' % (project_id, FLAGS.vpn_key_suffix) + def setup_security_group(self, context): + group_name = '%s%s' % (context.project.id, FLAGS.vpn_key_suffix) + if db.security_group_exists(context, context.project.id, group_name): + return group_name + group = {'user_id': context.user.id, + 'project_id': context.project.id, + 'name': group_name, + 'description': 'Group for vpn'} + group_ref = db.security_group_create(context, group) + rule = {'parent_group_id': group_ref['id'], + 'cidr': '0.0.0.0/0', + 'protocol': 'udp', + 'from_port': 1194, + 'to_port': 1194} + db.security_group_rule_create(context, rule) + rule = {'parent_group_id': group_ref['id'], + 'cidr': '0.0.0.0/0', + 'protocol': 'icmp', + 'from_port': -1, + 'to_port': -1} + db.security_group_rule_create(context, rule) + # NOTE(vish): No need to trigger the group since the instance + # has not been run yet. + return group_name + + def setup_key_pair(self, context): + key_name = '%s%s' % (context.project.id, FLAGS.vpn_key_suffix) try: - private_key, fingerprint = self.manager.generate_key_pair(user_id, - key_name) + result = cloud._gen_key(context, context.user.id, key_name) + private_key = result['private_key'] try: - key_dir = os.path.join(FLAGS.keys_path, user_id) + key_dir = os.path.join(FLAGS.keys_path, context.user.id) if not os.path.exists(key_dir): os.makedirs(key_dir) - file_name = os.path.join(key_dir, '%s.pem' % key_name) - with open(file_name, 'w') as f: + key_path = os.path.join(key_dir, '%s.pem' % key_name) + with open(key_path, 'w') as f: f.write(private_key) except: pass except exception.Duplicate: pass return key_name - - # def setup_secgroups(self, username): - # conn = self.euca.connection_for(username) - # try: - # secgroup = conn.create_security_group("vpn-secgroup", - # "vpn-secgroup") - # secgroup.authorize(ip_protocol = "udp", from_port = "1194", - # to_port = "1194", cidr_ip = "0.0.0.0/0") - # secgroup.authorize(ip_protocol = "tcp", from_port = "80", - # to_port = "80", cidr_ip = "0.0.0.0/0") - # secgroup.authorize(ip_protocol = "tcp", from_port = "22", - # to_port = "22", cidr_ip = "0.0.0.0/0") - # except: - # pass diff --git a/nova/crypto.py b/nova/crypto.py index 16b4f5e1f2..9e29f0b8d9 100644 --- a/nova/crypto.py +++ b/nova/crypto.py @@ -17,7 +17,7 @@ # under the License. """ -Wrappers around standard crypto, including root and intermediate CAs, +Wrappers around standard crypto, including root and project CAs, SSH key_pairs and x509 certificates. """ @@ -33,28 +33,57 @@ import utils import M2Crypto -from nova import exception +from nova import context +from nova import db from nova import flags FLAGS = flags.FLAGS flags.DEFINE_string('ca_file', 'cacert.pem', 'Filename of root CA') +flags.DEFINE_string('key_file', + os.path.join('private', 'cakey.pem'), + 'Filename of private key') +flags.DEFINE_string('crl_file', 'crl.pem', + 'Filename of root Certificate Revokation List') flags.DEFINE_string('keys_path', utils.abspath('../keys'), 'Where we keep our keys') flags.DEFINE_string('ca_path', utils.abspath('../CA'), 'Where we keep our root CA') -flags.DEFINE_boolean('use_intermediate_ca', False, - 'Should we use intermediate CAs for each project?') +flags.DEFINE_boolean('use_project_ca', False, + 'Should we use a CA for each project?') +flags.DEFINE_string('user_cert_subject', + '/C=US/ST=California/L=MountainView/O=AnsoLabs/' + 'OU=NovaDev/CN=%s-%s-%s', + 'Subject for certificate for users, ' + '%s for project, user, timestamp') +flags.DEFINE_string('project_cert_subject', + '/C=US/ST=California/L=MountainView/O=AnsoLabs/' + 'OU=NovaDev/CN=project-ca-%s-%s', + 'Subject for certificate for projects, ' + '%s for project, timestamp') +flags.DEFINE_string('vpn_cert_subject', + '/C=US/ST=California/L=MountainView/O=AnsoLabs/' + 'OU=NovaDev/CN=project-vpn-%s-%s', + 'Subject for certificate for vpns, ' + '%s for project, timestamp') -def ca_path(project_id): - if project_id: - return "%s/INTER/%s/cacert.pem" % (FLAGS.ca_path, project_id) - return "%s/cacert.pem" % (FLAGS.ca_path) +def ca_folder(project_id=None): + if FLAGS.use_project_ca and project_id: + return os.path.join(FLAGS.ca_path, 'projects', project_id) + return FLAGS.ca_path + + +def ca_path(project_id=None): + return os.path.join(ca_folder(project_id), FLAGS.ca_file) + + +def key_path(project_id=None): + return os.path.join(ca_folder(project_id), FLAGS.key_file) def fetch_ca(project_id=None, chain=True): - if not FLAGS.use_intermediate_ca: + if not FLAGS.use_project_ca: project_id = None buffer = "" if project_id: @@ -91,8 +120,8 @@ def generate_key_pair(bits=1024): def ssl_pub_to_ssh_pub(ssl_public_key, name='root', suffix='nova'): - pub_key_buffer = M2Crypto.BIO.MemoryBuffer(ssl_public_key) - rsa_key = M2Crypto.RSA.load_pub_key_bio(pub_key_buffer) + buf = M2Crypto.BIO.MemoryBuffer(ssl_public_key) + rsa_key = M2Crypto.RSA.load_pub_key_bio(buf) e, n = rsa_key.pub() key_type = 'ssh-rsa' @@ -105,53 +134,137 @@ def ssl_pub_to_ssh_pub(ssl_public_key, name='root', suffix='nova'): return '%s %s %s@%s\n' % (key_type, b64_blob, name, suffix) -def generate_x509_cert(subject, bits=1024): +def revoke_cert(project_id, file_name): + """Revoke a cert by file name""" + start = os.getcwd() + os.chdir(ca_folder(project_id)) + # NOTE(vish): potential race condition here + utils.execute("openssl ca -config ./openssl.cnf -revoke '%s'" % file_name) + utils.execute("openssl ca -gencrl -config ./openssl.cnf -out '%s'" % + FLAGS.crl_file) + os.chdir(start) + + +def revoke_certs_by_user(user_id): + """Revoke all user certs""" + admin = context.get_admin_context() + for cert in db.certificate_get_all_by_user(admin, user_id): + revoke_cert(cert['project_id'], cert['file_name']) + + +def revoke_certs_by_project(project_id): + """Revoke all project certs""" + # NOTE(vish): This is somewhat useless because we can just shut down + # the vpn. + admin = context.get_admin_context() + for cert in db.certificate_get_all_by_project(admin, project_id): + revoke_cert(cert['project_id'], cert['file_name']) + + +def revoke_certs_by_user_and_project(user_id, project_id): + """Revoke certs for user in project""" + admin = context.get_admin_context() + for cert in db.certificate_get_all_by_user(admin, user_id, project_id): + revoke_cert(cert['project_id'], cert['file_name']) + + +def _project_cert_subject(project_id): + """Helper to generate user cert subject""" + return FLAGS.project_cert_subject % (project_id, utils.isotime()) + + +def _vpn_cert_subject(project_id): + """Helper to generate user cert subject""" + return FLAGS.vpn_cert_subject % (project_id, utils.isotime()) + + +def _user_cert_subject(user_id, project_id): + """Helper to generate user cert subject""" + return FLAGS.user_cert_subject % (project_id, user_id, utils.isotime()) + + +def generate_x509_cert(user_id, project_id, bits=1024): + """Generate and sign a cert for user in project""" + subject = _user_cert_subject(user_id, project_id) tmpdir = tempfile.mkdtemp() keyfile = os.path.abspath(os.path.join(tmpdir, 'temp.key')) csrfile = os.path.join(tmpdir, 'temp.csr') - logging.debug("openssl genrsa -out %s %s" % (keyfile, bits)) - utils.runthis("Generating private key: %s", - "openssl genrsa -out %s %s" % (keyfile, bits)) - utils.runthis("Generating CSR: %s", - "openssl req -new -key %s -out %s -batch -subj %s" % + utils.execute("openssl genrsa -out %s %s" % (keyfile, bits)) + utils.execute("openssl req -new -key %s -out %s -batch -subj %s" % (keyfile, csrfile, subject)) private_key = open(keyfile).read() csr = open(csrfile).read() shutil.rmtree(tmpdir) - return (private_key, csr) + (serial, signed_csr) = sign_csr(csr, project_id) + strserial = "%X" % serial + if(len(strserial) % 2): + strserial = "0%s" % strserial + fname = os.path.join(ca_folder(project_id), "newcerts/%s.pem" % strserial) + cert = {'user_id': user_id, + 'project_id': project_id, + 'file_name': fname} + db.certificate_create(context.get_admin_context(), cert) + return (private_key, signed_csr) -def sign_csr(csr_text, intermediate=None): - if not FLAGS.use_intermediate_ca: - intermediate = None - if not intermediate: - return _sign_csr(csr_text, FLAGS.ca_path) - user_ca = "%s/INTER/%s" % (FLAGS.ca_path, intermediate) - if not os.path.exists(user_ca): +def _ensure_project_folder(project_id): + if not os.path.exists(ca_path(project_id)): start = os.getcwd() - os.chdir(FLAGS.ca_path) - utils.runthis("Generating intermediate CA: %s", - "sh geninter.sh %s" % (intermediate)) + os.chdir(ca_folder()) + utils.execute("sh geninter.sh %s %s" % + (project_id, _project_cert_subject(project_id))) os.chdir(start) - return _sign_csr(csr_text, user_ca) + + +def generate_vpn_files(project_id): + project_folder = ca_folder(project_id) + csr_fn = os.path.join(project_folder, "server.csr") + crt_fn = os.path.join(project_folder, "server.crt") + + if os.path.exists(crt_fn): + return + _ensure_project_folder(project_id) + start = os.getcwd() + os.chdir(ca_folder()) + # TODO(vish): the shell scripts could all be done in python + utils.execute("sh genvpn.sh %s %s" % + (project_id, _vpn_cert_subject(project_id))) + with open(csr_fn, "r") as csrfile: + csr_text = csrfile.read() + (serial, signed_csr) = sign_csr(csr_text, project_id) + with open(crt_fn, "w") as crtfile: + crtfile.write(signed_csr) + os.chdir(start) + + +def sign_csr(csr_text, project_id=None): + if not FLAGS.use_project_ca: + project_id = None + if not project_id: + return _sign_csr(csr_text, ca_folder()) + _ensure_project_folder(project_id) + project_folder = ca_folder(project_id) + return _sign_csr(csr_text, ca_folder(project_id)) def _sign_csr(csr_text, ca_folder): tmpfolder = tempfile.mkdtemp() - csrfile = open("%s/inbound.csr" % (tmpfolder), "w") + inbound = os.path.join(tmpfolder, "inbound.csr") + outbound = os.path.join(tmpfolder, "outbound.csr") + csrfile = open(inbound, "w") csrfile.write(csr_text) csrfile.close() - logging.debug("Flags path: %s" % ca_folder) + logging.debug("Flags path: %s", ca_folder) start = os.getcwd() # Change working dir to CA os.chdir(ca_folder) - utils.runthis("Signing cert: %s", - "openssl ca -batch -out %s/outbound.crt " - "-config ./openssl.cnf -infiles %s/inbound.csr" % - (tmpfolder, tmpfolder)) + utils.execute("openssl ca -batch -out %s -config " + "./openssl.cnf -infiles %s" % (outbound, inbound)) + out, _err = utils.execute("openssl x509 -in %s -serial -noout" % outbound) + serial = int(out.rpartition("=")[2]) os.chdir(start) - with open("%s/outbound.crt" % (tmpfolder), "r") as crtfile: - return crtfile.read() + with open(outbound, "r") as crtfile: + return (serial, crtfile.read()) def mkreq(bits, subject="foo", ca=0): @@ -159,8 +272,7 @@ def mkreq(bits, subject="foo", ca=0): req = M2Crypto.X509.Request() rsa = M2Crypto.RSA.gen_key(bits, 65537, callback=lambda: None) pk.assign_rsa(rsa) - # Should not be freed here - rsa = None + rsa = None # should not be freed here req.set_pubkey(pk) req.set_subject(subject) req.sign(pk, 'sha512') @@ -224,7 +336,6 @@ def mkcacert(subject='nova', years=1): # IN THE SOFTWARE. # http://code.google.com/p/boto - def compute_md5(fp): """ @type fp: file diff --git a/nova/db/api.py b/nova/db/api.py index 80563c4529..aa77dd2ea3 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -117,6 +117,45 @@ def service_update(context, service_id, values): ################### +def certificate_create(context, values): + """Create a certificate from the values dictionary.""" + return IMPL.certificate_create(context, values) + + +def certificate_destroy(context, certificate_id): + """Destroy the certificate or raise if it does not exist.""" + return IMPL.certificate_destroy(context, certificate_id) + + +def certificate_get_all_by_project(context, project_id): + """Get all certificates for a project.""" + return IMPL.certificate_get_all_by_project(context, project_id) + + +def certificate_get_all_by_user(context, user_id): + """Get all certificates for a user.""" + return IMPL.certificate_get_all_by_user(context, user_id) + + +def certificate_get_all_by_user_and_project(context, user_id, project_id): + """Get all certificates for a user and project.""" + return IMPL.certificate_get_all_by_user_and_project(context, + user_id, + project_id) + + +def certificate_update(context, certificate_id, values): + """Set the given properties on an certificate and update it. + + Raises NotFound if service does not exist. + + """ + return IMPL.service_update(context, certificate_id, values) + + +################### + + def floating_ip_allocate_address(context, host, project_id): """Allocate free floating ip and return the address. diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index db4d9f68f3..9ae12f1783 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -253,6 +253,84 @@ def service_update(context, service_id, values): ################### +@require_admin_context +def certificate_get(context, certificate_id, session=None): + if not session: + session = get_session() + + result = session.query(models.Certificate).\ + filter_by(id=certificate_id).\ + filter_by(deleted=can_read_deleted(context)).\ + first() + + if not result: + raise exception.NotFound('No certificate for id %s' % certificate_id) + + return result + + +@require_admin_context +def certificate_create(context, values): + certificate_ref = models.Certificate() + for (key, value) in values.iteritems(): + certificate_ref[key] = value + certificate_ref.save() + return certificate_ref + + +@require_admin_context +def certificate_destroy(context, certificate_id): + session = get_session() + with session.begin(): + certificate_ref = certificate_get(context, + certificate_id, + session=session) + certificate_ref.delete(session=session) + + +@require_admin_context +def certificate_get_all_by_project(context, project_id): + session = get_session() + return session.query(models.Certificate).\ + filter_by(project_id=project_id).\ + filter_by(deleted=False).\ + all() + + +@require_admin_context +def certificate_get_all_by_user(context, user_id): + session = get_session() + return session.query(models.Certificate).\ + filter_by(user_id=user_id).\ + filter_by(deleted=False).\ + all() + + +@require_admin_context +def certificate_get_all_by_user_and_project(_context, user_id, project_id): + session = get_session() + return session.query(models.Certificate).\ + filter_by(user_id=user_id).\ + filter_by(project_id=project_id).\ + filter_by(deleted=False).\ + all() + + +@require_admin_context +def certificate_update(context, certificate_id, values): + session = get_session() + with session.begin(): + certificate_ref = certificate_get(context, + certificate_id, + session=session) + for (key, value) in values.iteritems(): + certificate_ref[key] = value + certificate_ref.save(session=session) + + +################### + + @require_context def floating_ip_allocate_address(context, host, project_id): authorize_project_context(context, project_id) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 1111b5cbd7..434f88e5e3 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -151,6 +151,16 @@ class Service(BASE, NovaBase): disabled = Column(Boolean, default=False) +class Certificate(BASE, NovaBase): + """Represents a an x509 certificate""" + __tablename__ = 'certificates' + id = Column(Integer, primary_key=True) + + user_id = Column(String(255)) + project_id = Column(String(255)) + file_name = Column(String(255)) + + class Instance(BASE, NovaBase): """Represents a guest vm""" __tablename__ = 'instances' @@ -521,7 +531,7 @@ def register_models(): """Register Models and create metadata""" from sqlalchemy import create_engine models = (Service, Instance, Volume, ExportDevice, IscsiTarget, FixedIp, - FloatingIp, Network, SecurityGroup, + FloatingIp, Network, SecurityGroup, Certificate, SecurityGroupIngressRule, SecurityGroupInstanceAssociation, AuthToken, User, Project) # , Image, Host engine = create_engine(FLAGS.sql_connection, echo=False) diff --git a/nova/tests/auth_unittest.py b/nova/tests/auth_unittest.py index fe891beeeb..0d2082bdb7 100644 --- a/nova/tests/auth_unittest.py +++ b/nova/tests/auth_unittest.py @@ -208,17 +208,13 @@ class AuthManagerTestCase(object): # so it probably belongs in crypto_unittest # but I'm leaving it where I found it. with user_and_project_generator(self.manager) as (user, project): - # NOTE(todd): Should mention why we must setup controller first - # (somebody please clue me in) - cloud_controller = cloud.CloudController() - cloud_controller.setup() - _key, cert_str = self.manager._generate_x509_cert('test1', - 'testproj') + # NOTE(vish): Setup runs genroot.sh if it hasn't been run + cloud.CloudController().setup() + _key, cert_str = crypto.generate_x509_cert(user.id, project.id) logging.debug(cert_str) - # Need to verify that it's signed by the right intermediate CA - full_chain = crypto.fetch_ca(project_id='testproj', chain=True) - int_cert = crypto.fetch_ca(project_id='testproj', chain=False) + full_chain = crypto.fetch_ca(project_id=project.id, chain=True) + int_cert = crypto.fetch_ca(project_id=project.id, chain=False) cloud_cert = crypto.fetch_ca() logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain) signed_cert = X509.load_cert_string(cert_str) @@ -227,7 +223,8 @@ class AuthManagerTestCase(object): cloud_cert = X509.load_cert_string(cloud_cert) self.assertTrue(signed_cert.verify(chain_cert.get_pubkey())) self.assertTrue(signed_cert.verify(int_cert.get_pubkey())) - if not FLAGS.use_intermediate_ca: + + if not FLAGS.use_project_ca: self.assertTrue(signed_cert.verify(cloud_cert.get_pubkey())) else: self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey())) From 79fa23620a1846eae28eaed26cd79973571d6b99 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 6 Nov 2010 00:58:05 +0000 Subject: [PATCH 017/229] add dmz to flags and change a couple defaults --- nova/flags.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/nova/flags.py b/nova/flags.py index 4ae86d9b2c..acdfc6f6dd 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -180,7 +180,8 @@ DEFINE_list('region_list', 'list of region=url pairs separated by commas') DEFINE_string('connection_type', 'libvirt', 'libvirt, xenapi or fake') DEFINE_integer('s3_port', 3333, 's3 port') -DEFINE_string('s3_host', '127.0.0.1', 's3 host') +DEFINE_string('s3_host', '127.0.0.1', 's3 host (for infrastructure)') +DEFINE_string('s3_dmz', '127.0.0.1', 's3 dmz ip (for instances)') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') @@ -197,7 +198,8 @@ DEFINE_string('rabbit_userid', 'guest', 'rabbit userid') DEFINE_string('rabbit_password', 'guest', 'rabbit password') DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host') DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') -DEFINE_string('cc_host', '127.0.0.1', 'ip of api server') +DEFINE_string('cc_host', '127.0.0.1', 'ip of api server (for infrastructure') +DEFINE_string('cc_dmz', '127.0.0.1', 'ip of api server (for instances)') DEFINE_integer('cc_port', 8773, 'cloud controller port') DEFINE_string('ec2_url', 'http://127.0.0.1:8773/services/Cloud', 'Url to ec2 api server') @@ -211,10 +213,10 @@ DEFINE_string('default_ramdisk', 'ari-11111', DEFINE_string('default_instance_type', 'm1.small', 'default instance type to use, testing only') -DEFINE_string('vpn_image_id', 'ami-CLOUDPIPE', 'AMI for cloudpipe vpn server') +DEFINE_string('vpn_image_id', 'ami-cloudpipe', 'AMI for cloudpipe vpn server') DEFINE_string('vpn_key_suffix', - '-key', - 'Suffix to add to project name for vpn key') + '-vpn', + 'Suffix to add to project name for vpn key and secgroups') DEFINE_integer('auth_token_ttl', 3600, 'Seconds for auth tokens to linger') From 398ec325160acf039e1070718e898339a4b2d268 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 15 Nov 2010 01:25:42 -0400 Subject: [PATCH 018/229] base commit --- nova/api/openstack/__init__.py | 47 +++++++++---- .../tests/api/openstack/test_restrictedapi.py | 70 +++++++++++++++++++ 2 files changed, 103 insertions(+), 14 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 1dd3ba770e..338d642bca 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'}) + 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()), + } - 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()) + 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 0000000000..560a66e8c8 --- /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() From 66a5ac31c4a5f24da9c0335cf934bbf545c0d95f Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 16 Nov 2010 02:54:13 -0400 Subject: [PATCH 019/229] Changed from fine-grained operation control to binary admin on/off setting. --- nova/api/openstack/__init__.py | 54 ++++++++----------- ...test_restrictedapi.py => test_adminapi.py} | 30 ++++------- 2 files changed, 31 insertions(+), 53 deletions(-) rename nova/tests/api/openstack/{test_restrictedapi.py => test_adminapi.py} (61%) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 338d642bca..23ac033cf3 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()), - } + mapper.resource("server", "servers", controller=servers.Controller(), + collection={'detail': 'GET'}, + member={'action': 'POST'}) - 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("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_restrictedapi.py b/nova/tests/api/openstack/test_adminapi.py similarity index 61% rename from nova/tests/api/openstack/test_restrictedapi.py rename to nova/tests/api/openstack/test_adminapi.py index 560a66e8c8..54cb40e842 100644 --- a/nova/tests/api/openstack/test_restrictedapi.py +++ b/nova/tests/api/openstack/test_adminapi.py @@ -22,7 +22,6 @@ 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 @@ -35,36 +34,27 @@ class RestrictedAPITest(unittest.TestCase): 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 + self.allow_admin = FLAGS.allow_admin_api def tearDown(self): self.stubs.UnsetAll() - FLAGS.nova_api_permitted_operations = self.original_permissions + FLAGS.allow_admin_api = self.allow_admin - def test_permitted(self): + 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') - 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) + # TODO: Confirm admin operations are available. - def test_bad_list(self): + 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') - 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) + # TODO: Confirm admin operations are unavailable. if __name__ == '__main__': unittest.main() From 10756392157aa5e6029a50a9f38718f3024731c7 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 16 Nov 2010 05:46:40 -0400 Subject: [PATCH 020/229] added myself to Authors file. Enjoy spiders. --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index ef1a535ca9..22acc34d23 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 From f0b53131569cd409a95c68b435ec56a69dcdc897 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 16 Nov 2010 05:53:21 -0400 Subject: [PATCH 021/229] 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 23ac033cf3..80b27c7e55 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 54cb40e842..1b2e1654d5 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) From e59e7e9c3ad3e25545555986dcce0c384f9a7b6e Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 17 Nov 2010 02:33:09 +0000 Subject: [PATCH 022/229] remove extra line and ref. to LOG that doesn't exist --- bin/nova-manage | 1 - nova/auth/manager.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index b788ee62d9..4ab2e98323 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -167,7 +167,6 @@ class VpnCommands(object): 'vpn_public_port': int(port)}) - class ShellCommands(object): def bpython(self): """Runs a bpython shell. diff --git a/nova/auth/manager.py b/nova/auth/manager.py index c6d4b6e53e..252c5e65b6 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -662,7 +662,7 @@ class AuthManager(object): port=vpn_port) zippy.writestr(FLAGS.credential_vpn_file, config) else: - LOG.warn("No vpn data for project %s", pid) + logging.warn("No vpn data for project %s", pid) zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(pid)) zippy.close() From 9ca0b3435d93a87407ca42a853562cd06aaa896e Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Mon, 22 Nov 2010 12:57:03 +0000 Subject: [PATCH 023/229] added placeholders --- nova/virt/xenapi.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/nova/virt/xenapi.py b/nova/virt/xenapi.py index 0f563aa41c..4ed5d047f8 100644 --- a/nova/virt/xenapi.py +++ b/nova/virt/xenapi.py @@ -296,6 +296,14 @@ class XenAPIConnection(object): yield self._wait_for_task(task) except Exception, exc: logging.warn(exc) + + @defer.inlineCallbacks + def attach_volume(self, instance_name, device_path, mountpoint): + return True + + @defer.inlineCallbacks + def detach_volume(self, instance_name, mountpoint): + return True def get_info(self, instance_id): vm = self._lookup_blocking(instance_id) From 1638e71c4442187a315f44840453ad14cafb36ac Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 23 Nov 2010 21:16:25 +0000 Subject: [PATCH 024/229] the serial returned by x509 is already formatted in hex --- nova/crypto.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/nova/crypto.py b/nova/crypto.py index 9e29f0b8d9..a438d232b2 100644 --- a/nova/crypto.py +++ b/nova/crypto.py @@ -196,10 +196,7 @@ def generate_x509_cert(user_id, project_id, bits=1024): csr = open(csrfile).read() shutil.rmtree(tmpdir) (serial, signed_csr) = sign_csr(csr, project_id) - strserial = "%X" % serial - if(len(strserial) % 2): - strserial = "0%s" % strserial - fname = os.path.join(ca_folder(project_id), "newcerts/%s.pem" % strserial) + fname = os.path.join(ca_folder(project_id), "newcerts/%s.pem" % serial) cert = {'user_id': user_id, 'project_id': project_id, 'file_name': fname} @@ -261,7 +258,7 @@ def _sign_csr(csr_text, ca_folder): utils.execute("openssl ca -batch -out %s -config " "./openssl.cnf -infiles %s" % (outbound, inbound)) out, _err = utils.execute("openssl x509 -in %s -serial -noout" % outbound) - serial = int(out.rpartition("=")[2]) + serial = out.rpartition("=")[2] os.chdir(start) with open(outbound, "r") as crtfile: return (serial, crtfile.read()) From a7a24e2ac54b88ba09afe5966806f42181784e9d Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 23 Nov 2010 21:48:32 +0000 Subject: [PATCH 025/229] add vpn ping and optimize vpn list --- bin/nova-manage | 27 ++++++++--------------- nova/db/api.py | 5 +++++ nova/db/sqlalchemy/api.py | 12 ++++++++++ nova/utils.py | 46 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 18 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 4ab2e98323..3d0122637b 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -106,39 +106,30 @@ class VpnCommands(object): projects = [self.manager.get_project(project)] else: projects = self.manager.get_projects() + # NOTE(vish): This hits the database a lot. We could optimize + # by getting all networks in one query and all vpns + # in aother query, then doing lookups by project for project in projects: print "%-12s\t" % project.name, ipport = "%s:%s" % (project.vpn_ip, project.vpn_port) print "%-20s\t" % ipport, - vpn = self._vpn_for(project.id) + ctxt = context.get_admin_context() + vpn = db.instance_get_project_vpn(ctxt, project.id) if vpn: - net = 'down' address = None + state = 'down' if vpn.get('fixed_ip', None): address = vpn['fixed_ip']['address'] - command = "ping -c1 -w1 %s > /dev/null; echo $?" - out, _err = utils.execute(command % address, - check_exit_code=False) - if out.strip() == '0': - net = 'up' + if utils.vpn_ping(project.vpn_ip, project.vpn_port): + state = 'up' print address, print vpn['host'], print vpn['ec2_id'], print vpn['state_description'], - print net - + print state else: print None - def _vpn_for(self, project_id): - """Get the VPN instance for a project ID.""" - ctxt = context.get_admin_context() - for instance in db.instance_get_all_by_project(ctxt, project_id): - if (instance['image_id'] == FLAGS.vpn_image_id - and not instance['state_description'] in - ['shutting_down', 'shutdown']): - return instance - def spawn(self): """Run all VPNs.""" for p in reversed(self.manager.get_projects()): diff --git a/nova/db/api.py b/nova/db/api.py index aa77dd2ea3..5e0852c8f1 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -326,6 +326,11 @@ def instance_get_floating_address(context, instance_id): return IMPL.instance_get_floating_address(context, instance_id) +def instance_get_project_vpn(context, project_id): + """Get a vpn instance by project or return None.""" + return IMPL.instance_get_project_vpn(context, project_id) + + def instance_get_by_internal_id(context, internal_id): """Get an instance by ec2 id.""" return IMPL.instance_get_by_internal_id(context, internal_id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 9ae12f1783..01e0b1fd68 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -724,6 +724,18 @@ def instance_get_all_by_reservation(context, reservation_id): all() +@require_admin_context +def instance_get_project_vpn(context, project_id): + session = get_session() + return session.query(models.Instance).\ + options(joinedload_all('fixed_ip.floating_ips')).\ + options(joinedload('security_groups')).\ + filter_by(project_id=project_id).\ + filter_by(image_id=FLAGS.vpn_image_id).\ + filter_by(deleted=can_read_deleted(context)).\ + first() + + @require_context def instance_get_by_internal_id(context, internal_id): session = get_session() diff --git a/nova/utils.py b/nova/utils.py index e7892a2125..b3297acd96 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -27,6 +27,7 @@ import os import random import subprocess import socket +import struct import sys from xml.sax import saxutils @@ -61,6 +62,51 @@ def import_object(import_str): return cls() +def vpn_ping(address, port, timeout=0.05, session_id=None): + """Sends a vpn negotiation packet and returns the server session. + + Returns False on a failure. Basic packet structure is below. + + Client packet (14 bytes):: + 0 1 8 9 13 + +-+--------+-----+ + |x| cli_id |?????| + +-+--------+-----+ + x = packet identifier 0x38 + cli_id = 64 bit identifier + ? = unknown, probably flags/padding + + Server packet (26 bytes):: + 0 1 8 9 13 14 21 2225 + +-+--------+-----+--------+----+ + |x| srv_id |?????| cli_id |????| + +-+--------+-----+--------+----+ + x = packet identifier 0x40 + cli_id = 64 bit identifier + ? = unknown, probably flags/padding + bit 9 was 1 and the rest were 0 in testing + """ + if session_id is None: + session_id = random.randint(0, 0xffffffffffffffff) + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + data = struct.pack("!BQxxxxxx", 0x38, session_id) + sock.sendto(data, (address, port)) + sock.settimeout(timeout) + try: + received = sock.recv(2048) + except socket.timeout: + return False + finally: + sock.close() + fmt = "!BQxxxxxQxxxx" + if len(received) != struct.calcsize(fmt): + print struct.calcsize(fmt) + return False + (identifier, server_sess, client_sess) = struct.unpack(fmt, received) + if identifier == 0x40 and client_sess == session_id: + return server_sess + + def fetchfile(url, target): logging.debug("Fetching %s" % url) # c = pycurl.Curl() From f74094cd303b21c12e8a83790d405bcae8103be8 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 24 Nov 2010 21:40:41 +0000 Subject: [PATCH 026/229] don't error on edge case where vpn has been launched but fails to get a network --- bin/nova-manage | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index 3d0122637b..7c07ce3f01 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -120,7 +120,8 @@ class VpnCommands(object): state = 'down' if vpn.get('fixed_ip', None): address = vpn['fixed_ip']['address'] - if utils.vpn_ping(project.vpn_ip, project.vpn_port): + if project.vpn_ip and utils.vpn_ping(project.vpn_ip, + project.vpn_port): state = 'up' print address, print vpn['host'], From 9f722a0bcdb987c228f4ebf1e42c904a26d0ef73 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Thu, 25 Nov 2010 10:42:06 +0000 Subject: [PATCH 027/229] first cut of changes for the attach_volume call --- nova/virt/xenapi.py | 94 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 87 insertions(+), 7 deletions(-) diff --git a/nova/virt/xenapi.py b/nova/virt/xenapi.py index 4ed5d047f8..ec5e7456ac 100644 --- a/nova/virt/xenapi.py +++ b/nova/virt/xenapi.py @@ -63,6 +63,9 @@ from nova.compute import instance_types from nova.compute import power_state from nova.virt import images +from xml.dom.minidom import parseString + + XenAPI = None @@ -90,7 +93,7 @@ XENAPI_POWER_STATE = { 'Halted': power_state.SHUTDOWN, 'Running': power_state.RUNNING, 'Paused': power_state.PAUSED, - 'Suspended': power_state.SHUTDOWN, # FIXME + 'Suspended': power_state.SHUTDOWN, # FIXME 'Crashed': power_state.CRASHED} @@ -126,7 +129,7 @@ class XenAPIConnection(object): def spawn(self, instance): vm = yield self._lookup(instance.name) if vm is not None: - raise Exception('Attempted to create non-unique name %s' % + raise Exception('Attempted to create non-unique name %s' % instance.name) network = db.project_get_network(None, instance.project_id) @@ -296,14 +299,34 @@ class XenAPIConnection(object): yield self._wait_for_task(task) except Exception, exc: logging.warn(exc) - + @defer.inlineCallbacks def attach_volume(self, instance_name, device_path, mountpoint): - return True + # NOTE: No Resource Pool concept so far + logging.debug("Attach_volume: %s, %s, %s", + instance_name, device_path, mountpoint) + volume_info = _parse_volume_info(device_path, mountpoint) + # Create the iSCSI SR, and the PDB through which hosts access SRs. + # But first, retrieve target info, like Host, IQN, LUN and SCSIID + target = yield self._get_target(volume_info) + label = 'SR-%s' % volume_info['volumeId'] + sr_ref = yield self._create_sr(target, label) + # Create VDI and attach VBD to VM + vm = None + try: + task = yield self._call_xenapi('', vm) + yield self._wait_for_task(task) + except Exception, exc: + logging.warn(exc) + yield True @defer.inlineCallbacks def detach_volume(self, instance_name, mountpoint): - return True + logging.debug("Detach_volume: %s, %s, %s", instance_name, mountpoint) + # Detach VBD from VM + # Forget SR/PDB info associated with host + # TODO: can we avoid destroying the SR every time we detach? + yield True def get_info(self, instance_id): vm = self._lookup_blocking(instance_id) @@ -333,6 +356,52 @@ class XenAPIConnection(object): else: return vms[0] + @utils.deferredToThread + def _get_target(self, volume_info): + return self._get_target_blocking(volume_info) + + def _get_target_blocking(self, volume_info): + target = {} + target['target'] = volume_info['targetHost'] + target['port'] = volume_info['targetPort'] + target['targetIQN'] = volume_info['iqn'] + # We expect SR_BACKEND_FAILURE_107 to retrieve params to create the SR + try: + self._conn.xenapi.SR.create(self._get_xenapi_host(), + target, '-1', '', '', + 'lvmoiscsi', '', False, {}) + except XenAPI.Failure, exc: + if exc.details[0] == 'SR_BACKEND_FAILURE_107': + xml_response = parseString(exc.details[3]) + isciTargets = xml_response.getElementsByTagName('iscsi-target') + # Make sure that only the correct Lun is visible + if len(isciTargets) > 1: + raise Exception('More than one ISCSI Target available') + isciLuns = isciTargets.item(0).getElementsByTagName('LUN') + if len(isciLuns) > 1: + raise Exception('More than one ISCSI Lun available') + # Parse params from the xml response into the dictionary + for n in isciLuns.item(0).childNodes: + if n.nodeType == 1: + target[n.nodeName] = str(n.firstChild.data).strip() + return target + + @utils.deferredToThread + def _create_sr(self, target, label): + return self._create_sr_blocking(target, label) + + def _create_sr_blocking(self, target, label): + # TODO: we might want to put all these string literals into constants + sr = self._conn.xenapi.SR.create(self._get_xenapi_host(), + target, + target['size'], + label, + '', + 'lvmoiscsi', + '', + True, {}) + return sr + def _wait_for_task(self, task): """Return a Deferred that will give the result of the given task. The task is polled until it completes.""" @@ -412,7 +481,18 @@ def _parse_xmlrpc_value(val): if not val: return val x = xmlrpclib.loads( - '' + - val + + '' + + val + '') return x[0][0] + + +def _parse_volume_info(device_path, mountpoint): + volume_info = {} + volume_info['volumeId'] = 'vol-qurmrzn9' + # XCP and XenServer add an x to the device name + volume_info['xenMountpoint'] = '/dev/xvdb' + volume_info['targetHost'] = '10.70.177.40' + volume_info['targetPort'] = '3260' # default 3260 + volume_info['iqn'] = 'iqn.2010-10.org.openstack:vol-qurmrzn9' + return volume_info From 688d564668aefa4b644236421a3a45fc90486634 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Thu, 25 Nov 2010 20:31:32 +0000 Subject: [PATCH 028/229] work on attach_volume, with a few things to iron out --- nova/virt/xenapi.py | 99 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 83 insertions(+), 16 deletions(-) diff --git a/nova/virt/xenapi.py b/nova/virt/xenapi.py index 5bf98468ec..d3167ebf35 100644 --- a/nova/virt/xenapi.py +++ b/nova/virt/xenapi.py @@ -147,7 +147,7 @@ class XenAPIConnection(object): vdi_ref = yield self._call_xenapi('VDI.get_by_uuid', vdi_uuid) vm_ref = yield self._create_vm(instance, kernel, ramdisk) - yield self._create_vbd(vm_ref, vdi_ref, 0, True) + yield self._create_vbd(vm_ref, vdi_ref, 0, True, True, False) if network_ref: yield self._create_vif(vm_ref, network_ref, instance.mac_address) logging.debug('Starting VM %s...', vm_ref) @@ -197,7 +197,21 @@ class XenAPIConnection(object): defer.returnValue(vm_ref) @defer.inlineCallbacks - def _create_vbd(self, vm_ref, vdi_ref, userdevice, bootable): + def _create_vdi(self, sr_ref, size, type, label, description, read_only, sharable): + vdi_rec = {} + vdi_rec['read_only'] = read_only + vdi_rec['SR'] = sr_ref + vdi_rec['virtual_size'] = str(size) + vdi_rec['name_label'] = label + vdi_rec['name_description'] = description + vdi_rec['sharable'] = sharable + vdi_rec['type'] = type + vdi_rec['other_config'] = {} + vdi_ref = yield self._call_xenapi('VDI.create', vdi_rec) + defer.returnValue(vdi_ref) + + @defer.inlineCallbacks + def _create_vbd(self, vm_ref, vdi_ref, userdevice, bootable, unpluggable, empty): """Create a VBD record. Returns a Deferred that gives the new VBD reference.""" @@ -208,8 +222,8 @@ class XenAPIConnection(object): vbd_rec['bootable'] = bootable vbd_rec['mode'] = 'RW' vbd_rec['type'] = 'disk' - vbd_rec['unpluggable'] = True - vbd_rec['empty'] = False + vbd_rec['unpluggable'] = unpluggable + vbd_rec['empty'] = empty vbd_rec['other_config'] = {} vbd_rec['qos_algorithm_type'] = '' vbd_rec['qos_algorithm_params'] = {} @@ -320,14 +334,33 @@ class XenAPIConnection(object): # But first, retrieve target info, like Host, IQN, LUN and SCSIID target = yield self._get_target(volume_info) label = 'SR-%s' % volume_info['volumeId'] - sr_ref = yield self._create_sr(target, label) + description = 'Attached-to:%s' % instance_name + # Create SR and check the physical space available for the VDI allocation + sr_ref = yield self._create_sr(target, label, description) + disk_size = yield self._get_sr_available_space(sr_ref) # Create VDI and attach VBD to VM - vm = None + vm_ref = yield self._lookup(instance_name) + logging.debug("Mounting disk of: %s GB", (disk_size / (1024*1024*1024.0))) try: - task = yield self._call_xenapi('', vm) - yield self._wait_for_task(task) + vdi_ref = yield self._create_vdi(sr_ref, disk_size, + 'user', volume_info['volumeId'], '', + False, False) except Exception, exc: logging.warn(exc) + if sr_ref: + yield self._destroy_sr(sr_ref) + raise Exception('Unable to create VDI on SR %s' % sr_ref) + else: + try: + userdevice = 2 # FIXME: this depends on the numbers of attached disks + vbd_ref = yield self._create_vbd(vm_ref, vdi_ref, userdevice, False, True, False) + task = yield self._call_xenapi('Async.VBD.plug', vbd_ref) + yield self._wait_for_task(task) + except Exception, exc: + logging.warn(exc) + if sr_ref: + yield self._destroy_sr(sr_ref) + raise Exception('Unable to create VBD on SR %s' % sr_ref) yield True @defer.inlineCallbacks @@ -395,23 +428,57 @@ class XenAPIConnection(object): if n.nodeType == 1: target[n.nodeName] = str(n.firstChild.data).strip() return target + + @utils.deferredToThread + def _get_sr_available_space(self, sr_ref): + return self._get_sr_available_space_blocking(sr_ref) + + def _get_sr_available_space_blocking(self, sr_ref): + pu = self._conn.xenapi.SR.get_physical_utilisation(sr_ref) + ps = self._conn.xenapi.SR.get_physical_size(sr_ref) + return (int(ps) - int(pu)) - (8 * 1024 * 1024) @utils.deferredToThread - def _create_sr(self, target, label): - return self._create_sr_blocking(target, label) + def _create_sr(self, target, label, description): + return self._create_sr_blocking(target, label, description) - def _create_sr_blocking(self, target, label): + def _create_sr_blocking(self, target, label, description): # TODO: we might want to put all these string literals into constants - sr = self._conn.xenapi.SR.create(self._get_xenapi_host(), + sr_ref = self._conn.xenapi.SR.create(self._get_xenapi_host(), target, target['size'], label, - '', + description, 'lvmoiscsi', '', True, {}) - return sr + # TODO: there might be some timing issues here + self._conn.xenapi.SR.scan(sr_ref) + return sr_ref + @defer.inlineCallbacks + def _destroy_sr(self, sr_ref): + # Some clean-up depending on the state of the SR + #yield self._destroy_vdbs(sr_ref) + #yield self._destroy_vdis(sr_ref) + # Destroy PDBs + pbds = yield self._conn.xenapi.SR.get_PBDs(sr_ref) + for pbd_ref in pbds: + try: + task = yield self._call_xenapi('Async.PBD.unplug', pbd_ref) + yield self._wait_for_task(task) + except Exception, exc: + logging.warn(exc) + else: + task = yield self._call_xenapi('Async.PBD.destroy', pbd_ref) + yield self._wait_for_task(task) + # Forget SR + try: + task = yield self._call_xenapi('Async.SR.forget', sr_ref) + yield self._wait_for_task(task) + except Exception, exc: + logging.warn(exc) + @utils.deferredToThread def _lookup_vm_vdis(self, vm): return self._lookup_vm_vdis_blocking(vm) @@ -524,8 +591,8 @@ def _parse_xmlrpc_value(val): def _parse_volume_info(device_path, mountpoint): volume_info = {} volume_info['volumeId'] = 'vol-qurmrzn9' - # XCP and XenServer add an x to the device name - volume_info['xenMountpoint'] = '/dev/xvdb' + # Because XCP/XS want an x beforehand + volume_info['xenMountpoint'] = '/dev/xvdc' volume_info['targetHost'] = '10.70.177.40' volume_info['targetPort'] = '3260' # default 3260 volume_info['iqn'] = 'iqn.2010-10.org.openstack:vol-qurmrzn9' From a9b900d24020b68284e402a98ee28c107de0bd71 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Thu, 25 Nov 2010 20:42:22 +0000 Subject: [PATCH 029/229] added attach_volume implementation --- nova/virt/xenapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/xenapi.py b/nova/virt/xenapi.py index d3167ebf35..236360f11f 100644 --- a/nova/virt/xenapi.py +++ b/nova/virt/xenapi.py @@ -593,7 +593,7 @@ def _parse_volume_info(device_path, mountpoint): volume_info['volumeId'] = 'vol-qurmrzn9' # Because XCP/XS want an x beforehand volume_info['xenMountpoint'] = '/dev/xvdc' - volume_info['targetHost'] = '10.70.177.40' + volume_info['targetHost'] = '' volume_info['targetPort'] = '3260' # default 3260 volume_info['iqn'] = 'iqn.2010-10.org.openstack:vol-qurmrzn9' return volume_info From c3072aea3dc5d44d26fcac5c7db65b8cc445fccc Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Fri, 26 Nov 2010 17:04:27 +0000 Subject: [PATCH 030/229] 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 ceade1d65c..95519d0005 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""" From 8a7e6e0f003e1b3837b918ac9af1564ac1665aae Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Fri, 26 Nov 2010 17:59:48 +0000 Subject: [PATCH 031/229] 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 95519d0005..fa48c8435f 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 { From a44ee54dfe3f243a44636e9224082e86fdee452f Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Sat, 27 Nov 2010 12:56:19 +0000 Subject: [PATCH 032/229] first cut of the refactoring of the XenAPIConnection class. Currently the class merged both the code for managing the XenAPI connection and the business logic for implementing Nova operations. If left like this, it would eventually become difficult to read, maintain and extend. The file was getting kind of big and cluttered, so a quick refactoring now will save a lot of headaches later. --- nova/virt/xenapi.py | 321 ++++------------------- nova/virt/xenapi/power_state.py | 26 ++ nova/virt/xenapi/xenapi.py | 439 ++++++++++++++++++++++++++++++++ 3 files changed, 514 insertions(+), 272 deletions(-) create mode 100644 nova/virt/xenapi/power_state.py create mode 100644 nova/virt/xenapi/xenapi.py diff --git a/nova/virt/xenapi.py b/nova/virt/xenapi.py index 3169562a53..93c1192056 100644 --- a/nova/virt/xenapi.py +++ b/nova/virt/xenapi.py @@ -54,14 +54,9 @@ from twisted.internet import defer from twisted.internet import reactor from twisted.internet import task -from nova import db -from nova import flags -from nova import process -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 +from xenapi import power_state +from xenapi import vmops +from xenapi import volumeops XenAPI = None @@ -86,14 +81,6 @@ flags.DEFINE_float('xenapi_task_poll_interval', 'connection_type=xenapi.') -XENAPI_POWER_STATE = { - 'Halted': power_state.SHUTDOWN, - 'Running': power_state.RUNNING, - 'Paused': power_state.PAUSED, - 'Suspended': power_state.SHUTDOWN, # FIXME - 'Crashed': power_state.CRASHED} - - def get_connection(_): """Note that XenAPI doesn't have a read-only connection mode, so the read_only parameter is ignored.""" @@ -115,273 +102,83 @@ def get_connection(_): class XenAPIConnection(object): def __init__(self, url, user, pw): - self._conn = XenAPI.Session(url) - self._conn.login_with_password(user, pw) - + self._session = XenAPISession(url, user, pw) + self._vmops = VMOps(sef._session) + self._volumeops = volumeOps(self._session) + def list_instances(self): - return [self._conn.xenapi.VM.get_name_label(vm) \ - for vm in self._conn.xenapi.VM.get_all()] - - @defer.inlineCallbacks + return self._vmops.list_instances() + def spawn(self, instance): - vm = yield self._lookup(instance.name) - if vm is not None: - raise Exception('Attempted to create non-unique name %s' % - instance.name) - - network = db.project_get_network(None, instance.project_id) - network_ref = \ - yield self._find_network_with_bridge(network.bridge) - - user = AuthManager().get_user(instance.user_id) - project = AuthManager().get_project(instance.project_id) - vdi_uuid = yield self._fetch_image( - instance.image_id, user, project, True) - kernel = yield self._fetch_image( - instance.kernel_id, user, project, False) - ramdisk = yield self._fetch_image( - instance.ramdisk_id, user, project, False) - vdi_ref = yield self._call_xenapi('VDI.get_by_uuid', vdi_uuid) - - vm_ref = yield self._create_vm(instance, kernel, ramdisk) - yield self._create_vbd(vm_ref, vdi_ref, 0, True) - if network_ref: - yield self._create_vif(vm_ref, network_ref, instance.mac_address) - logging.debug('Starting VM %s...', vm_ref) - yield self._call_xenapi('VM.start', vm_ref, False, False) - logging.info('Spawning VM %s created %s.', instance.name, vm_ref) - - @defer.inlineCallbacks - def _create_vm(self, instance, kernel, ramdisk): - """Create a VM record. Returns a Deferred that gives the new - VM reference.""" - - instance_type = instance_types.INSTANCE_TYPES[instance.instance_type] - mem = str(long(instance_type['memory_mb']) * 1024 * 1024) - vcpus = str(instance_type['vcpus']) - rec = { - 'name_label': instance.name, - 'name_description': '', - 'is_a_template': False, - 'memory_static_min': '0', - 'memory_static_max': mem, - 'memory_dynamic_min': mem, - 'memory_dynamic_max': mem, - 'VCPUs_at_startup': vcpus, - 'VCPUs_max': vcpus, - 'VCPUs_params': {}, - 'actions_after_shutdown': 'destroy', - 'actions_after_reboot': 'restart', - 'actions_after_crash': 'destroy', - 'PV_bootloader': '', - 'PV_kernel': kernel, - 'PV_ramdisk': ramdisk, - 'PV_args': 'root=/dev/xvda1', - 'PV_bootloader_args': '', - 'PV_legacy_args': '', - 'HVM_boot_policy': '', - 'HVM_boot_params': {}, - 'platform': {}, - 'PCI_bus': '', - 'recommendations': '', - 'affinity': '', - 'user_version': '0', - 'other_config': {}, - } - logging.debug('Created VM %s...', instance.name) - vm_ref = yield self._call_xenapi('VM.create', rec) - logging.debug('Created VM %s as %s.', instance.name, vm_ref) - defer.returnValue(vm_ref) - - @defer.inlineCallbacks - def _create_vbd(self, vm_ref, vdi_ref, userdevice, bootable): - """Create a VBD record. Returns a Deferred that gives the new - VBD reference.""" - - vbd_rec = {} - vbd_rec['VM'] = vm_ref - vbd_rec['VDI'] = vdi_ref - vbd_rec['userdevice'] = str(userdevice) - vbd_rec['bootable'] = bootable - vbd_rec['mode'] = 'RW' - vbd_rec['type'] = 'disk' - vbd_rec['unpluggable'] = True - vbd_rec['empty'] = False - vbd_rec['other_config'] = {} - vbd_rec['qos_algorithm_type'] = '' - vbd_rec['qos_algorithm_params'] = {} - vbd_rec['qos_supported_algorithms'] = [] - logging.debug('Creating VBD for VM %s, VDI %s ... ', vm_ref, vdi_ref) - vbd_ref = yield self._call_xenapi('VBD.create', vbd_rec) - logging.debug('Created VBD %s for VM %s, VDI %s.', vbd_ref, vm_ref, - vdi_ref) - defer.returnValue(vbd_ref) - - @defer.inlineCallbacks - def _create_vif(self, vm_ref, network_ref, mac_address): - """Create a VIF record. Returns a Deferred that gives the new - VIF reference.""" - - vif_rec = {} - vif_rec['device'] = '0' - vif_rec['network'] = network_ref - vif_rec['VM'] = vm_ref - vif_rec['MAC'] = mac_address - vif_rec['MTU'] = '1500' - vif_rec['other_config'] = {} - vif_rec['qos_algorithm_type'] = '' - vif_rec['qos_algorithm_params'] = {} - logging.debug('Creating VIF for VM %s, network %s ... ', vm_ref, - network_ref) - vif_ref = yield self._call_xenapi('VIF.create', vif_rec) - logging.debug('Created VIF %s for VM %s, network %s.', vif_ref, - vm_ref, network_ref) - defer.returnValue(vif_ref) - - @defer.inlineCallbacks - def _find_network_with_bridge(self, bridge): - expr = 'field "bridge" = "%s"' % bridge - networks = yield self._call_xenapi('network.get_all_records_where', - expr) - if len(networks) == 1: - defer.returnValue(networks.keys()[0]) - elif len(networks) > 1: - raise Exception('Found non-unique network for bridge %s' % bridge) - else: - raise Exception('Found no network for bridge %s' % bridge) - - @defer.inlineCallbacks - def _fetch_image(self, image, user, project, use_sr): - """use_sr: True to put the image as a VDI in an SR, False to place - it on dom0's filesystem. The former is for VM disks, the latter for - its kernel and ramdisk (if external kernels are being used). - Returns a Deferred that gives the new VDI UUID.""" - - url = images.image_url(image) - access = AuthManager().get_access_key(user, project) - logging.debug("Asking xapi to fetch %s as %s" % (url, access)) - fn = use_sr and 'get_vdi' or 'get_kernel' - args = {} - args['src_url'] = url - args['username'] = access - args['password'] = user.secret - if use_sr: - args['add_partition'] = 'true' - task = yield self._async_call_plugin('objectstore', fn, args) - uuid = yield self._wait_for_task(task) - defer.returnValue(uuid) - - @defer.inlineCallbacks + self._vmops.spawn(instance) + def reboot(self, instance): - vm = yield self._lookup(instance.name) - if vm is None: - raise Exception('instance not present %s' % instance.name) - task = yield self._call_xenapi('Async.VM.clean_reboot', vm) - yield self._wait_for_task(task) + self._vmops.reboot(instance) - @defer.inlineCallbacks def destroy(self, instance): - vm = yield self._lookup(instance.name) - if vm is None: - # Don't complain, just return. This lets us clean up instances - # that have already disappeared from the underlying platform. - defer.returnValue(None) - # Get the VDIs related to the VM - vdis = yield self._lookup_vm_vdis(vm) - try: - task = yield self._call_xenapi('Async.VM.hard_shutdown', vm) - yield self._wait_for_task(task) - except Exception, exc: - logging.warn(exc) - # Disk clean-up - if vdis: - for vdi in vdis: - try: - task = yield self._call_xenapi('Async.VDI.destroy', vdi) - yield self._wait_for_task(task) - except Exception, exc: - logging.warn(exc) - try: - task = yield self._call_xenapi('Async.VM.destroy', vm) - yield self._wait_for_task(task) - except Exception, exc: - logging.warn(exc) - + self._vmops.destroy(instance) + def get_info(self, instance_id): - vm = self._lookup_blocking(instance_id) - if vm is None: - raise Exception('instance not present %s' % instance_id) - rec = self._conn.xenapi.VM.get_record(vm) - return {'state': XENAPI_POWER_STATE[rec['power_state']], - 'max_mem': long(rec['memory_static_max']) >> 10, - 'mem': long(rec['memory_dynamic_max']) >> 10, - 'num_cpu': rec['VCPUs_max'], - 'cpu_time': 0} - + return self._vmops.get_info(instance_id) + def get_console_output(self, instance): - return 'FAKE CONSOLE OUTPUT' + return self._vmops.get_console_output(instance) + + def attach_volume(self, instance_name, device_path, mountpoint): + return self._volumeops.attach_volume(instance_name, device_path, mountpoint) + + def detach_volume(self, instance_name, mountpoint): + return self._volumeops.detach_volume(instance_name, mountpoint) + + +class XenAPISession(object): + def __init__(self, url, user, pw): + self._session = XenAPI.Session(url) + self._session.login_with_password(user, pw) @utils.deferredToThread - def _lookup(self, i): - return self._lookup_blocking(i) - - def _lookup_blocking(self, i): - vms = self._conn.xenapi.VM.get_by_name_label(i) - n = len(vms) - if n == 0: - return None - elif n > 1: - raise Exception('duplicate name found: %s' % i) - else: - return vms[0] + def call_xenapi(self, method, *args): + """Call the specified XenAPI method on a background thread. Returns + a Deferred for the result.""" + f = self._session.xenapi + for m in method.split('.'): + f = f.__getattr__(m) + return f(*args) @utils.deferredToThread - def _lookup_vm_vdis(self, vm): - return self._lookup_vm_vdis_blocking(vm) + def async_call_plugin(self, plugin, fn, args): + """Call Async.host.call_plugin on a background thread. Returns a + Deferred with the task reference.""" + return _unwrap_plugin_exceptions( + self._session.xenapi.Async.host.call_plugin, + self._get_xenapi_host(), plugin, fn, args) - def _lookup_vm_vdis_blocking(self, vm): - # Firstly we get the VBDs, then the VDIs. - # TODO: do we leave the read-only devices? - vbds = self._conn.xenapi.VM.get_VBDs(vm) - vdis = [] - if vbds: - for vbd in vbds: - try: - vdi = self._conn.xenapi.VBD.get_VDI(vbd) - # Test valid VDI - record = self._conn.xenapi.VDI.get_record(vdi) - except Exception, exc: - logging.warn(exc) - else: - vdis.append(vdi) - if len(vdis) > 0: - return vdis - else: - return None + def get_xenapi_host(self): + return self._session.xenapi.session.get_this_host(self._session.handle) - def _wait_for_task(self, task): + def wait_for_task(self, task): """Return a Deferred that will give the result of the given task. The task is polled until it completes.""" d = defer.Deferred() reactor.callLater(0, self._poll_task, task, d) return d - + @utils.deferredToThread def _poll_task(self, task, deferred): """Poll the given XenAPI task, and fire the given Deferred if we get a result.""" try: #logging.debug('Polling task %s...', task) - status = self._conn.xenapi.task.get_status(task) + status = self._session.xenapi.task.get_status(task) if status == 'pending': reactor.callLater(FLAGS.xenapi_task_poll_interval, self._poll_task, task, deferred) elif status == 'success': - result = self._conn.xenapi.task.get_result(task) + result = self._session.xenapi.task.get_result(task) logging.info('Task %s status: success. %s', task, result) deferred.callback(_parse_xmlrpc_value(result)) else: - error_info = self._conn.xenapi.task.get_error_info(task) + error_info = self._session.xenapi.task.get_error_info(task) logging.warn('Task %s status: %s. %s', task, status, error_info) deferred.errback(XenAPI.Failure(error_info)) @@ -390,26 +187,6 @@ class XenAPIConnection(object): logging.warn(exc) deferred.errback(exc) - @utils.deferredToThread - def _call_xenapi(self, method, *args): - """Call the specified XenAPI method on a background thread. Returns - a Deferred for the result.""" - f = self._conn.xenapi - for m in method.split('.'): - f = f.__getattr__(m) - return f(*args) - - @utils.deferredToThread - def _async_call_plugin(self, plugin, fn, args): - """Call Async.host.call_plugin on a background thread. Returns a - Deferred with the task reference.""" - return _unwrap_plugin_exceptions( - self._conn.xenapi.Async.host.call_plugin, - self._get_xenapi_host(), plugin, fn, args) - - def _get_xenapi_host(self): - return self._conn.xenapi.session.get_this_host(self._conn.handle) - def _unwrap_plugin_exceptions(func, *args, **kwargs): try: diff --git a/nova/virt/xenapi/power_state.py b/nova/virt/xenapi/power_state.py new file mode 100644 index 0000000000..d2d8fba425 --- /dev/null +++ b/nova/virt/xenapi/power_state.py @@ -0,0 +1,26 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Citrix Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from nova.compute import power_state + + +XENAPI_POWER_STATE = { + 'Halted': power_state.SHUTDOWN, + 'Running': power_state.RUNNING, + 'Paused': power_state.PAUSED, + 'Suspended': power_state.SHUTDOWN, # FIXME + 'Crashed': power_state.CRASHED} + \ No newline at end of file diff --git a/nova/virt/xenapi/xenapi.py b/nova/virt/xenapi/xenapi.py new file mode 100644 index 0000000000..ddbef4303f --- /dev/null +++ b/nova/virt/xenapi/xenapi.py @@ -0,0 +1,439 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Citrix Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +A connection to XenServer or Xen Cloud Platform. + +The concurrency model for this class is as follows: + +All XenAPI calls are on a thread (using t.i.t.deferToThread, via the decorator +deferredToThread). They are remote calls, and so may hang for the usual +reasons. They should not be allowed to block the reactor thread. + +All long-running XenAPI calls (VM.start, VM.reboot, etc) are called async +(using XenAPI.VM.async_start etc). These return a task, which can then be +polled for completion. Polling is handled using reactor.callLater. + +This combination of techniques means that we don't block the reactor thread at +all, and at the same time we don't hold lots of threads waiting for +long-running operations. + +FIXME: get_info currently doesn't conform to these rules, and will block the +reactor thread if the VM.get_by_name_label or VM.get_record calls block. + +**Related Flags** + +:xenapi_connection_url: URL for connection to XenServer/Xen Cloud Platform. +:xenapi_connection_username: Username for connection to XenServer/Xen Cloud + Platform (default: root). +:xenapi_connection_password: Password for connection to XenServer/Xen Cloud + Platform. +:xenapi_task_poll_interval: The interval (seconds) used for polling of + remote tasks (Async.VM.start, etc) + (default: 0.5). + +""" + +import logging +import xmlrpclib + +from twisted.internet import defer +from twisted.internet import reactor +from twisted.internet import task + +from nova import db +from nova import flags +from nova import process +from nova import utils +from nova.auth.manager import AuthManager # wrap this one +from nova.compute import instance_types # wrap this one +from xenapi import power_state +from nova.virt import images # wrap this one + +XenAPI = None + + +FLAGS = flags.FLAGS +flags.DEFINE_string('xenapi_connection_url', + None, + 'URL for connection to XenServer/Xen Cloud Platform.' + ' Required if connection_type=xenapi.') +flags.DEFINE_string('xenapi_connection_username', + 'root', + 'Username for connection to XenServer/Xen Cloud Platform.' + ' Used only if connection_type=xenapi.') +flags.DEFINE_string('xenapi_connection_password', + None, + 'Password for connection to XenServer/Xen Cloud Platform.' + ' Used only if connection_type=xenapi.') +flags.DEFINE_float('xenapi_task_poll_interval', + 0.5, + 'The interval used for polling of remote tasks ' + '(Async.VM.start, etc). Used only if ' + 'connection_type=xenapi.') + + +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 + if not url or password is None: + raise Exception('Must specify xenapi_connection_url, ' + 'xenapi_connection_username (optionally), and ' + 'xenapi_connection_password to use ' + 'connection_type=xenapi') + return XenAPIConnection(url, username, password) + + +class XenAPISession(object): + def __init__(self, url, user, pw): + self._session = XenAPI.Session(url) + self._session.login_with_password(user, pw) + + def session(self): + return self._session + + def list_instances(self): + return [self._session.xenapi.VM.get_name_label(vm) \ + for vm in self._session.xenapi.VM.get_all()] + + @defer.inlineCallbacks + def spawn(self, instance): + vm = yield self._lookup(instance.name) + if vm is not None: + raise Exception('Attempted to create non-unique name %s' % + instance.name) + + network = db.project_get_network(None, instance.project_id) + network_ref = \ + yield self._find_network_with_bridge(network.bridge) + + user = AuthManager().get_user(instance.user_id) + project = AuthManager().get_project(instance.project_id) + vdi_uuid = yield self._fetch_image( + instance.image_id, user, project, True) + kernel = yield self._fetch_image( + instance.kernel_id, user, project, False) + ramdisk = yield self._fetch_image( + instance.ramdisk_id, user, project, False) + vdi_ref = yield self._call_xenapi('VDI.get_by_uuid', vdi_uuid) + + vm_ref = yield self._create_vm(instance, kernel, ramdisk) + yield self._create_vbd(vm_ref, vdi_ref, 0, True) + if network_ref: + yield self._create_vif(vm_ref, network_ref, instance.mac_address) + logging.debug('Starting VM %s...', vm_ref) + yield self._call_xenapi('VM.start', vm_ref, False, False) + logging.info('Spawning VM %s created %s.', instance.name, vm_ref) + + @defer.inlineCallbacks + def _create_vm(self, instance, kernel, ramdisk): + """Create a VM record. Returns a Deferred that gives the new + VM reference.""" + + instance_type = instance_types.INSTANCE_TYPES[instance.instance_type] + mem = str(long(instance_type['memory_mb']) * 1024 * 1024) + vcpus = str(instance_type['vcpus']) + rec = { + 'name_label': instance.name, + 'name_description': '', + 'is_a_template': False, + 'memory_static_min': '0', + 'memory_static_max': mem, + 'memory_dynamic_min': mem, + 'memory_dynamic_max': mem, + 'VCPUs_at_startup': vcpus, + 'VCPUs_max': vcpus, + 'VCPUs_params': {}, + 'actions_after_shutdown': 'destroy', + 'actions_after_reboot': 'restart', + 'actions_after_crash': 'destroy', + 'PV_bootloader': '', + 'PV_kernel': kernel, + 'PV_ramdisk': ramdisk, + 'PV_args': 'root=/dev/xvda1', + 'PV_bootloader_args': '', + 'PV_legacy_args': '', + 'HVM_boot_policy': '', + 'HVM_boot_params': {}, + 'platform': {}, + 'PCI_bus': '', + 'recommendations': '', + 'affinity': '', + 'user_version': '0', + 'other_config': {}, + } + logging.debug('Created VM %s...', instance.name) + vm_ref = yield self._call_xenapi('VM.create', rec) + logging.debug('Created VM %s as %s.', instance.name, vm_ref) + defer.returnValue(vm_ref) + + @defer.inlineCallbacks + def _create_vbd(self, vm_ref, vdi_ref, userdevice, bootable): + """Create a VBD record. Returns a Deferred that gives the new + VBD reference.""" + + vbd_rec = {} + vbd_rec['VM'] = vm_ref + vbd_rec['VDI'] = vdi_ref + vbd_rec['userdevice'] = str(userdevice) + vbd_rec['bootable'] = bootable + vbd_rec['mode'] = 'RW' + vbd_rec['type'] = 'disk' + vbd_rec['unpluggable'] = True + vbd_rec['empty'] = False + vbd_rec['other_config'] = {} + vbd_rec['qos_algorithm_type'] = '' + vbd_rec['qos_algorithm_params'] = {} + vbd_rec['qos_supported_algorithms'] = [] + logging.debug('Creating VBD for VM %s, VDI %s ... ', vm_ref, vdi_ref) + vbd_ref = yield self._call_xenapi('VBD.create', vbd_rec) + logging.debug('Created VBD %s for VM %s, VDI %s.', vbd_ref, vm_ref, + vdi_ref) + defer.returnValue(vbd_ref) + + @defer.inlineCallbacks + def _create_vif(self, vm_ref, network_ref, mac_address): + """Create a VIF record. Returns a Deferred that gives the new + VIF reference.""" + + vif_rec = {} + vif_rec['device'] = '0' + vif_rec['network'] = network_ref + vif_rec['VM'] = vm_ref + vif_rec['MAC'] = mac_address + vif_rec['MTU'] = '1500' + vif_rec['other_config'] = {} + vif_rec['qos_algorithm_type'] = '' + vif_rec['qos_algorithm_params'] = {} + logging.debug('Creating VIF for VM %s, network %s ... ', vm_ref, + network_ref) + vif_ref = yield self._call_xenapi('VIF.create', vif_rec) + logging.debug('Created VIF %s for VM %s, network %s.', vif_ref, + vm_ref, network_ref) + defer.returnValue(vif_ref) + + @defer.inlineCallbacks + def _find_network_with_bridge(self, bridge): + expr = 'field "bridge" = "%s"' % bridge + networks = yield self._call_xenapi('network.get_all_records_where', + expr) + if len(networks) == 1: + defer.returnValue(networks.keys()[0]) + elif len(networks) > 1: + raise Exception('Found non-unique network for bridge %s' % bridge) + else: + raise Exception('Found no network for bridge %s' % bridge) + + @defer.inlineCallbacks + def _fetch_image(self, image, user, project, use_sr): + """use_sr: True to put the image as a VDI in an SR, False to place + it on dom0's filesystem. The former is for VM disks, the latter for + its kernel and ramdisk (if external kernels are being used). + Returns a Deferred that gives the new VDI UUID.""" + + url = images.image_url(image) + access = AuthManager().get_access_key(user, project) + logging.debug("Asking xapi to fetch %s as %s" % (url, access)) + fn = use_sr and 'get_vdi' or 'get_kernel' + args = {} + args['src_url'] = url + args['username'] = access + args['password'] = user.secret + if use_sr: + args['add_partition'] = 'true' + task = yield self._async_call_plugin('objectstore', fn, args) + uuid = yield self._wait_for_task(task) + defer.returnValue(uuid) + + @defer.inlineCallbacks + def reboot(self, instance): + vm = yield self._lookup(instance.name) + if vm is None: + raise Exception('instance not present %s' % instance.name) + task = yield self._call_xenapi('Async.VM.clean_reboot', vm) + yield self._wait_for_task(task) + + @defer.inlineCallbacks + def destroy(self, instance): + vm = yield self._lookup(instance.name) + if vm is None: + # Don't complain, just return. This lets us clean up instances + # that have already disappeared from the underlying platform. + defer.returnValue(None) + # Get the VDIs related to the VM + vdis = yield self._lookup_vm_vdis(vm) + try: + task = yield self._call_xenapi('Async.VM.hard_shutdown', vm) + yield self._wait_for_task(task) + except Exception, exc: + logging.warn(exc) + # Disk clean-up + if vdis: + for vdi in vdis: + try: + task = yield self._call_xenapi('Async.VDI.destroy', vdi) + yield self._wait_for_task(task) + except Exception, exc: + logging.warn(exc) + try: + task = yield self._call_xenapi('Async.VM.destroy', vm) + yield self._wait_for_task(task) + except Exception, exc: + logging.warn(exc) + + def get_info(self, instance_id): + vm = self._lookup_blocking(instance_id) + if vm is None: + raise Exception('instance not present %s' % instance_id) + rec = self._session.xenapi.VM.get_record(vm) + return {'state': XENAPI_POWER_STATE[rec['power_state']], + 'max_mem': long(rec['memory_static_max']) >> 10, + 'mem': long(rec['memory_dynamic_max']) >> 10, + 'num_cpu': rec['VCPUs_max'], + 'cpu_time': 0} + + def get_console_output(self, instance): + return 'FAKE CONSOLE OUTPUT' + + @utils.deferredToThread + def _lookup(self, i): + return self._lookup_blocking(i) + + def _lookup_blocking(self, i): + vms = self._session.xenapi.VM.get_by_name_label(i) + n = len(vms) + if n == 0: + return None + elif n > 1: + raise Exception('duplicate name found: %s' % i) + else: + return vms[0] + + @utils.deferredToThread + def _lookup_vm_vdis(self, vm): + return self._lookup_vm_vdis_blocking(vm) + + def _lookup_vm_vdis_blocking(self, vm): + # Firstly we get the VBDs, then the VDIs. + # TODO: do we leave the read-only devices? + vbds = self._session.xenapi.VM.get_VBDs(vm) + vdis = [] + if vbds: + for vbd in vbds: + try: + vdi = self._session.xenapi.VBD.get_VDI(vbd) + # Test valid VDI + record = self._session.xenapi.VDI.get_record(vdi) + except Exception, exc: + logging.warn(exc) + else: + vdis.append(vdi) + if len(vdis) > 0: + return vdis + else: + return None + + def _wait_for_task(self, task): + """Return a Deferred that will give the result of the given task. + The task is polled until it completes.""" + d = defer.Deferred() + reactor.callLater(0, self._poll_task, task, d) + return d + + @utils.deferredToThread + def _poll_task(self, task, deferred): + """Poll the given XenAPI task, and fire the given Deferred if we + get a result.""" + try: + #logging.debug('Polling task %s...', task) + status = self._session.xenapi.task.get_status(task) + if status == 'pending': + reactor.callLater(FLAGS.xenapi_task_poll_interval, + self._poll_task, task, deferred) + elif status == 'success': + result = self._session.xenapi.task.get_result(task) + logging.info('Task %s status: success. %s', task, result) + deferred.callback(_parse_xmlrpc_value(result)) + else: + error_info = self._session.xenapi.task.get_error_info(task) + logging.warn('Task %s status: %s. %s', task, status, + error_info) + deferred.errback(XenAPI.Failure(error_info)) + #logging.debug('Polling task %s done.', task) + except Exception, exc: + logging.warn(exc) + deferred.errback(exc) + + @utils.deferredToThread + def _call_xenapi(self, method, *args): + """Call the specified XenAPI method on a background thread. Returns + a Deferred for the result.""" + f = self._session.xenapi + for m in method.split('.'): + f = f.__getattr__(m) + return f(*args) + + @utils.deferredToThread + def _async_call_plugin(self, plugin, fn, args): + """Call Async.host.call_plugin on a background thread. Returns a + Deferred with the task reference.""" + return _unwrap_plugin_exceptions( + self._session.xenapi.Async.host.call_plugin, + self._get_xenapi_host(), plugin, fn, args) + + def _get_xenapi_host(self): + return self._session.xenapi.session.get_this_host(self._session.handle) + + +def _unwrap_plugin_exceptions(func, *args, **kwargs): + try: + return func(*args, **kwargs) + except XenAPI.Failure, exc: + logging.debug("Got exception: %s", exc) + if (len(exc.details) == 4 and + exc.details[0] == 'XENAPI_PLUGIN_EXCEPTION' and + exc.details[2] == 'Failure'): + params = None + try: + params = eval(exc.details[3]) + except: + raise exc + raise XenAPI.Failure(params) + else: + raise + except xmlrpclib.ProtocolError, exc: + logging.debug("Got exception: %s", exc) + raise + + +def _parse_xmlrpc_value(val): + """Parse the given value as if it were an XML-RPC value. This is + sometimes used as the format for the task.result field.""" + if not val: + return val + x = xmlrpclib.loads( + '' + + val + + '') + return x[0][0] From 541f8ce212a33d14ac5ba48b3dde6c43a60bc368 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Sat, 27 Nov 2010 13:33:38 +0000 Subject: [PATCH 033/229] typos and pep8 fixes --- nova/virt/xenapi.py | 35 +++++++++++++++++++-------------- nova/virt/xenapi/power_state.py | 1 - 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/nova/virt/xenapi.py b/nova/virt/xenapi.py index 93c1192056..a17894c849 100644 --- a/nova/virt/xenapi.py +++ b/nova/virt/xenapi.py @@ -55,8 +55,8 @@ from twisted.internet import reactor from twisted.internet import task from xenapi import power_state -from xenapi import vmops -from xenapi import volumeops +from xenapi import VMOps +from xenapi import VolumeOps XenAPI = None @@ -102,30 +102,32 @@ def get_connection(_): class XenAPIConnection(object): def __init__(self, url, user, pw): - self._session = XenAPISession(url, user, pw) - self._vmops = VMOps(sef._session) - self._volumeops = volumeOps(self._session) - + session = XenAPISession(url, user, pw) + self._vmops = VMOps(session) + self._volumeops = VolumeOps(session) + def list_instances(self): return self._vmops.list_instances() - + def spawn(self, instance): self._vmops.spawn(instance) - + def reboot(self, instance): - self._vmops.reboot(instance) + self._vmops.reboot(instance) def destroy(self, instance): self._vmops.destroy(instance) - + def get_info(self, instance_id): return self._vmops.get_info(instance_id) - + def get_console_output(self, instance): - return self._vmops.get_console_output(instance) - + return self._vmops.get_console_output(instance) + def attach_volume(self, instance_name, device_path, mountpoint): - return self._volumeops.attach_volume(instance_name, device_path, mountpoint) + return self._volumeops.attach_volume(instance_name, + device_path, + mountpoint) def detach_volume(self, instance_name, mountpoint): return self._volumeops.detach_volume(instance_name, mountpoint) @@ -136,6 +138,9 @@ class XenAPISession(object): self._session = XenAPI.Session(url) self._session.login_with_password(user, pw) + def get_session(self): + return self._session + @utils.deferredToThread def call_xenapi(self, method, *args): """Call the specified XenAPI method on a background thread. Returns @@ -162,7 +167,7 @@ class XenAPISession(object): d = defer.Deferred() reactor.callLater(0, self._poll_task, task, d) return d - + @utils.deferredToThread def _poll_task(self, task, deferred): """Poll the given XenAPI task, and fire the given Deferred if we diff --git a/nova/virt/xenapi/power_state.py b/nova/virt/xenapi/power_state.py index d2d8fba425..5892f0f485 100644 --- a/nova/virt/xenapi/power_state.py +++ b/nova/virt/xenapi/power_state.py @@ -23,4 +23,3 @@ XENAPI_POWER_STATE = { 'Paused': power_state.PAUSED, 'Suspended': power_state.SHUTDOWN, # FIXME 'Crashed': power_state.CRASHED} - \ No newline at end of file From b6bed02342ac716b3cb3847fb54b5f285995f3b7 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Sun, 28 Nov 2010 01:49:28 +0000 Subject: [PATCH 034/229] further refactoring --- nova/virt/xenapi.py | 15 +- nova/virt/xenapi/network_utils.py | 52 ++++ nova/virt/xenapi/vm_utils.py | 206 ++++++++++++++ nova/virt/xenapi/vmops.py | 126 +++++++++ nova/virt/xenapi/xenapi.py | 439 ------------------------------ 5 files changed, 391 insertions(+), 447 deletions(-) create mode 100644 nova/virt/xenapi/network_utils.py create mode 100644 nova/virt/xenapi/vm_utils.py create mode 100644 nova/virt/xenapi/vmops.py delete mode 100644 nova/virt/xenapi/xenapi.py diff --git a/nova/virt/xenapi.py b/nova/virt/xenapi.py index a17894c849..2f2cef75ef 100644 --- a/nova/virt/xenapi.py +++ b/nova/virt/xenapi.py @@ -54,7 +54,6 @@ from twisted.internet import defer from twisted.internet import reactor from twisted.internet import task -from xenapi import power_state from xenapi import VMOps from xenapi import VolumeOps @@ -138,8 +137,11 @@ class XenAPISession(object): self._session = XenAPI.Session(url) self._session.login_with_password(user, pw) - def get_session(self): - return self._session + def get_xenapi(self): + return self._session.xenapi + + def get_xenapi_host(self): + return self._session.xenapi.session.get_this_host(self._session.handle) @utils.deferredToThread def call_xenapi(self, method, *args): @@ -149,17 +151,14 @@ class XenAPISession(object): for m in method.split('.'): f = f.__getattr__(m) return f(*args) - + @utils.deferredToThread def async_call_plugin(self, plugin, fn, args): """Call Async.host.call_plugin on a background thread. Returns a Deferred with the task reference.""" return _unwrap_plugin_exceptions( self._session.xenapi.Async.host.call_plugin, - self._get_xenapi_host(), plugin, fn, args) - - def get_xenapi_host(self): - return self._session.xenapi.session.get_this_host(self._session.handle) + self.get_xenapi_host(), plugin, fn, args) def wait_for_task(self, task): """Return a Deferred that will give the result of the given task. diff --git a/nova/virt/xenapi/network_utils.py b/nova/virt/xenapi/network_utils.py new file mode 100644 index 0000000000..e062f916f0 --- /dev/null +++ b/nova/virt/xenapi/network_utils.py @@ -0,0 +1,52 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Citrix Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Helper methods for operations related to the management of network records and +their attributes like bridges, PIFs, QoS, as well as their lookup functions. +""" + +import logging +import xmlrpclib + +from twisted.internet import defer +from twisted.internet import reactor +from twisted.internet import task + +from nova import db +from nova import flags +from nova import process +from nova import utils +from nova.auth.manager import AuthManager # wrap this one +from nova.compute import instance_types # wrap this one +from nova.virt import images # wrap this one + +import power_state + + +class NetworkHelper(): + @classmethod + @defer.inlineCallbacks + def find_network_with_bridge(self, session, bridge): + expr = 'field "bridge" = "%s"' % bridge + networks = yield session.call_xenapi('network.get_all_records_where', + expr) + if len(networks) == 1: + defer.returnValue(networks.keys()[0]) + elif len(networks) > 1: + raise Exception('Found non-unique network for bridge %s' % bridge) + else: + raise Exception('Found no network for bridge %s' % bridge) \ No newline at end of file diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py new file mode 100644 index 0000000000..6fb409b26e --- /dev/null +++ b/nova/virt/xenapi/vm_utils.py @@ -0,0 +1,206 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Citrix Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Helper methods for operations related to the management of VM records and +their attributes like VDIs, VIFs, as well as their lookup functions. +""" + +import logging +import xmlrpclib + +from twisted.internet import defer +from twisted.internet import reactor +from twisted.internet import task + +from nova import db +from nova import flags +from nova import process +from nova import utils +from nova.auth.manager import AuthManager # wrap this one +from nova.compute import instance_types # wrap this one +from nova.virt import images # wrap this one + +import power_state + + +class VMHelper(): + @classmethod + @defer.inlineCallbacks + def create_vm(self, session, instance, kernel, ramdisk): + """Create a VM record. Returns a Deferred that gives the new + VM reference.""" + + instance_type = instance_types.INSTANCE_TYPES[instance.instance_type] + mem = str(long(instance_type['memory_mb']) * 1024 * 1024) + vcpus = str(instance_type['vcpus']) + rec = { + 'name_label': instance.name, + 'name_description': '', + 'is_a_template': False, + 'memory_static_min': '0', + 'memory_static_max': mem, + 'memory_dynamic_min': mem, + 'memory_dynamic_max': mem, + 'VCPUs_at_startup': vcpus, + 'VCPUs_max': vcpus, + 'VCPUs_params': {}, + 'actions_after_shutdown': 'destroy', + 'actions_after_reboot': 'restart', + 'actions_after_crash': 'destroy', + 'PV_bootloader': '', + 'PV_kernel': kernel, + 'PV_ramdisk': ramdisk, + 'PV_args': 'root=/dev/xvda1', + 'PV_bootloader_args': '', + 'PV_legacy_args': '', + 'HVM_boot_policy': '', + 'HVM_boot_params': {}, + 'platform': {}, + 'PCI_bus': '', + 'recommendations': '', + 'affinity': '', + 'user_version': '0', + 'other_config': {}, + } + logging.debug('Created VM %s...', instance.name) + vm_ref = yield session.call_xenapi('VM.create', rec) + logging.debug('Created VM %s as %s.', instance.name, vm_ref) + defer.returnValue(vm_ref) + + @classmethod + @defer.inlineCallbacks + def create_vbd(self, session, vm_ref, vdi_ref, userdevice, bootable): + """Create a VBD record. Returns a Deferred that gives the new + VBD reference.""" + + vbd_rec = {} + vbd_rec['VM'] = vm_ref + vbd_rec['VDI'] = vdi_ref + vbd_rec['userdevice'] = str(userdevice) + vbd_rec['bootable'] = bootable + vbd_rec['mode'] = 'RW' + vbd_rec['type'] = 'disk' + vbd_rec['unpluggable'] = True + vbd_rec['empty'] = False + vbd_rec['other_config'] = {} + vbd_rec['qos_algorithm_type'] = '' + vbd_rec['qos_algorithm_params'] = {} + vbd_rec['qos_supported_algorithms'] = [] + logging.debug('Creating VBD for VM %s, VDI %s ... ', vm_ref, vdi_ref) + vbd_ref = yield session.call_xenapi('VBD.create', vbd_rec) + logging.debug('Created VBD %s for VM %s, VDI %s.', vbd_ref, vm_ref, + vdi_ref) + defer.returnValue(vbd_ref) + + @classmethod + @defer.inlineCallbacks + def create_vif(self, session, vm_ref, network_ref, mac_address): + """Create a VIF record. Returns a Deferred that gives the new + VIF reference.""" + + vif_rec = {} + vif_rec['device'] = '0' + vif_rec['network'] = network_ref + vif_rec['VM'] = vm_ref + vif_rec['MAC'] = mac_address + vif_rec['MTU'] = '1500' + vif_rec['other_config'] = {} + vif_rec['qos_algorithm_type'] = '' + vif_rec['qos_algorithm_params'] = {} + logging.debug('Creating VIF for VM %s, network %s ... ', vm_ref, + network_ref) + vif_ref = yield session.call_xenapi('VIF.create', vif_rec) + logging.debug('Created VIF %s for VM %s, network %s.', vif_ref, + vm_ref, network_ref) + defer.returnValue(vif_ref) + + @classmethod + @defer.inlineCallbacks + def find_network_with_bridge(self, session, bridge): + expr = 'field "bridge" = "%s"' % bridge + networks = yield session.call_xenapi('network.get_all_records_where', + expr) + if len(networks) == 1: + defer.returnValue(networks.keys()[0]) + elif len(networks) > 1: + raise Exception('Found non-unique network for bridge %s' % bridge) + else: + raise Exception('Found no network for bridge %s' % bridge) + + @classmethod + @defer.inlineCallbacks + def fetch_image(self, session, image, user, project, use_sr): + """use_sr: True to put the image as a VDI in an SR, False to place + it on dom0's filesystem. The former is for VM disks, the latter for + its kernel and ramdisk (if external kernels are being used). + Returns a Deferred that gives the new VDI UUID.""" + + url = images.image_url(image) + access = AuthManager().get_access_key(user, project) + logging.debug("Asking xapi to fetch %s as %s" % (url, access)) + fn = use_sr and 'get_vdi' or 'get_kernel' + args = {} + args['src_url'] = url + args['username'] = access + args['password'] = user.secret + if use_sr: + args['add_partition'] = 'true' + task = yield session.async_call_plugin('objectstore', fn, args) + uuid = yield session.wait_for_task(task) + defer.returnValue(uuid) + + @classmethod + @utils.deferredToThread + def lookup(self, session, i): + return VMHelper.lookup_blocking(i) + + @classmethod + def lookup_blocking(self, session, i): + vms = session.get_xenapi().VM.get_by_name_label(i) + n = len(vms) + if n == 0: + return None + elif n > 1: + raise Exception('duplicate name found: %s' % i) + else: + return vms[0] + + @classmethod + @utils.deferredToThread + def lookup_vm_vdis(self, session, vm): + return VMHelper.lookup_vm_vdis_blocking(session, vm) + + @classmethod + def lookup_vm_vdis_blocking(self, session, vm): + # Firstly we get the VBDs, then the VDIs. + # TODO: do we leave the read-only devices? + vbds = session.get_xenapi().VM.get_VBDs(vm) + vdis = [] + if vbds: + for vbd in vbds: + try: + vdi = session.get_xenapi().VBD.get_VDI(vbd) + # Test valid VDI + record = session.get_xenapi().VDI.get_record(vdi) + except Exception, exc: + logging.warn(exc) + else: + vdis.append(vdi) + if len(vdis) > 0: + return vdis + else: + return None \ No newline at end of file diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py new file mode 100644 index 0000000000..03b7fc614f --- /dev/null +++ b/nova/virt/xenapi/vmops.py @@ -0,0 +1,126 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Citrix Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Management class for VM-related functions (spawn, reboot, etc). +""" + +import logging +import xmlrpclib + +from twisted.internet import defer +from twisted.internet import reactor +from twisted.internet import task + +from nova import db +from nova import flags +from nova import process +from nova import utils +from nova.auth.manager import AuthManager # wrap this one +from nova.compute import instance_types # wrap this one +from nova.virt import images # wrap this one + +import power_state +import VMHelper +import NetworkHelper + + +class VMOps(object): + def __init__(self, session): + self._session = session + + def list_instances(self): + return [self._session.get_xenapi().VM.get_name_label(vm) \ + for vm in self._session.get_xenapi().VM.get_all()] + + @defer.inlineCallbacks + def spawn(self, instance): + vm = yield VMHelper.lookup(self._session, instance.name) + if vm is not None: + raise Exception('Attempted to create non-unique name %s' % + instance.name) + + network = db.project_get_network(None, instance.project_id) + network_ref = \ + yield NetworkHelper.find_network_with_bridge(self._session, network.bridge) + + user = AuthManager().get_user(instance.user_id) + project = AuthManager().get_project(instance.project_id) + vdi_uuid = yield VMHelper.fetch_image(self._session, + instance.image_id, user, project, True) + kernel = yield VMHelper.fetch_image(self._session, + instance.kernel_id, user, project, False) + ramdisk = yield VMHelper.fetch_image(self._session, + instance.ramdisk_id, user, project, False) + vdi_ref = yield self._session.call_xenapi('VDI.get_by_uuid', vdi_uuid) + + vm_ref = yield VMHelper.create_vm(self._session, instance, kernel, ramdisk) + yield VMHelper.create_vbd(self._session, vm_ref, vdi_ref, 0, True) + if network_ref: + yield VMHelper.create_vif(self._session, vm_ref, network_ref, instance.mac_address) + logging.debug('Starting VM %s...', vm_ref) + yield self._session.call_xenapi('VM.start', vm_ref, False, False) + logging.info('Spawning VM %s created %s.', instance.name, vm_ref) + + @defer.inlineCallbacks + def reboot(self, instance): + vm = yield VMHelper.lookup(self._session, instance.name) + if vm is None: + raise Exception('instance not present %s' % instance.name) + task = yield self._session.call_xenapi('Async.VM.clean_reboot', vm) + yield self._session.wait_for_task(task) + + @defer.inlineCallbacks + def destroy(self, instance): + vm = yield VMHelper.lookup(self._session, instance.name) + if vm is None: + # Don't complain, just return. This lets us clean up instances + # that have already disappeared from the underlying platform. + defer.returnValue(None) + # Get the VDIs related to the VM + vdis = yield VMHelper.lookup_vm_vdis(self._session, vm) + try: + task = yield self._session.call_xenapi('Async.VM.hard_shutdown', vm) + yield self._session.wait_for_task(task) + except Exception, exc: + logging.warn(exc) + # Disk clean-up + if vdis: + for vdi in vdis: + try: + task = yield self._session.call_xenapi('Async.VDI.destroy', vdi) + yield self._session.wait_for_task(task) + except Exception, exc: + logging.warn(exc) + try: + task = yield self._session.call_xenapi('Async.VM.destroy', vm) + yield self._session.wait_for_task(task) + except Exception, exc: + logging.warn(exc) + + def get_info(self, instance_id): + 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) + return {'state': XENAPI_POWER_STATE[rec['power_state']], + 'max_mem': long(rec['memory_static_max']) >> 10, + 'mem': long(rec['memory_dynamic_max']) >> 10, + 'num_cpu': rec['VCPUs_max'], + 'cpu_time': 0} + + def get_console_output(self, instance): + return 'FAKE CONSOLE OUTPUT' \ No newline at end of file diff --git a/nova/virt/xenapi/xenapi.py b/nova/virt/xenapi/xenapi.py deleted file mode 100644 index ddbef4303f..0000000000 --- a/nova/virt/xenapi/xenapi.py +++ /dev/null @@ -1,439 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2010 Citrix Systems, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -A connection to XenServer or Xen Cloud Platform. - -The concurrency model for this class is as follows: - -All XenAPI calls are on a thread (using t.i.t.deferToThread, via the decorator -deferredToThread). They are remote calls, and so may hang for the usual -reasons. They should not be allowed to block the reactor thread. - -All long-running XenAPI calls (VM.start, VM.reboot, etc) are called async -(using XenAPI.VM.async_start etc). These return a task, which can then be -polled for completion. Polling is handled using reactor.callLater. - -This combination of techniques means that we don't block the reactor thread at -all, and at the same time we don't hold lots of threads waiting for -long-running operations. - -FIXME: get_info currently doesn't conform to these rules, and will block the -reactor thread if the VM.get_by_name_label or VM.get_record calls block. - -**Related Flags** - -:xenapi_connection_url: URL for connection to XenServer/Xen Cloud Platform. -:xenapi_connection_username: Username for connection to XenServer/Xen Cloud - Platform (default: root). -:xenapi_connection_password: Password for connection to XenServer/Xen Cloud - Platform. -:xenapi_task_poll_interval: The interval (seconds) used for polling of - remote tasks (Async.VM.start, etc) - (default: 0.5). - -""" - -import logging -import xmlrpclib - -from twisted.internet import defer -from twisted.internet import reactor -from twisted.internet import task - -from nova import db -from nova import flags -from nova import process -from nova import utils -from nova.auth.manager import AuthManager # wrap this one -from nova.compute import instance_types # wrap this one -from xenapi import power_state -from nova.virt import images # wrap this one - -XenAPI = None - - -FLAGS = flags.FLAGS -flags.DEFINE_string('xenapi_connection_url', - None, - 'URL for connection to XenServer/Xen Cloud Platform.' - ' Required if connection_type=xenapi.') -flags.DEFINE_string('xenapi_connection_username', - 'root', - 'Username for connection to XenServer/Xen Cloud Platform.' - ' Used only if connection_type=xenapi.') -flags.DEFINE_string('xenapi_connection_password', - None, - 'Password for connection to XenServer/Xen Cloud Platform.' - ' Used only if connection_type=xenapi.') -flags.DEFINE_float('xenapi_task_poll_interval', - 0.5, - 'The interval used for polling of remote tasks ' - '(Async.VM.start, etc). Used only if ' - 'connection_type=xenapi.') - - -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 - if not url or password is None: - raise Exception('Must specify xenapi_connection_url, ' - 'xenapi_connection_username (optionally), and ' - 'xenapi_connection_password to use ' - 'connection_type=xenapi') - return XenAPIConnection(url, username, password) - - -class XenAPISession(object): - def __init__(self, url, user, pw): - self._session = XenAPI.Session(url) - self._session.login_with_password(user, pw) - - def session(self): - return self._session - - def list_instances(self): - return [self._session.xenapi.VM.get_name_label(vm) \ - for vm in self._session.xenapi.VM.get_all()] - - @defer.inlineCallbacks - def spawn(self, instance): - vm = yield self._lookup(instance.name) - if vm is not None: - raise Exception('Attempted to create non-unique name %s' % - instance.name) - - network = db.project_get_network(None, instance.project_id) - network_ref = \ - yield self._find_network_with_bridge(network.bridge) - - user = AuthManager().get_user(instance.user_id) - project = AuthManager().get_project(instance.project_id) - vdi_uuid = yield self._fetch_image( - instance.image_id, user, project, True) - kernel = yield self._fetch_image( - instance.kernel_id, user, project, False) - ramdisk = yield self._fetch_image( - instance.ramdisk_id, user, project, False) - vdi_ref = yield self._call_xenapi('VDI.get_by_uuid', vdi_uuid) - - vm_ref = yield self._create_vm(instance, kernel, ramdisk) - yield self._create_vbd(vm_ref, vdi_ref, 0, True) - if network_ref: - yield self._create_vif(vm_ref, network_ref, instance.mac_address) - logging.debug('Starting VM %s...', vm_ref) - yield self._call_xenapi('VM.start', vm_ref, False, False) - logging.info('Spawning VM %s created %s.', instance.name, vm_ref) - - @defer.inlineCallbacks - def _create_vm(self, instance, kernel, ramdisk): - """Create a VM record. Returns a Deferred that gives the new - VM reference.""" - - instance_type = instance_types.INSTANCE_TYPES[instance.instance_type] - mem = str(long(instance_type['memory_mb']) * 1024 * 1024) - vcpus = str(instance_type['vcpus']) - rec = { - 'name_label': instance.name, - 'name_description': '', - 'is_a_template': False, - 'memory_static_min': '0', - 'memory_static_max': mem, - 'memory_dynamic_min': mem, - 'memory_dynamic_max': mem, - 'VCPUs_at_startup': vcpus, - 'VCPUs_max': vcpus, - 'VCPUs_params': {}, - 'actions_after_shutdown': 'destroy', - 'actions_after_reboot': 'restart', - 'actions_after_crash': 'destroy', - 'PV_bootloader': '', - 'PV_kernel': kernel, - 'PV_ramdisk': ramdisk, - 'PV_args': 'root=/dev/xvda1', - 'PV_bootloader_args': '', - 'PV_legacy_args': '', - 'HVM_boot_policy': '', - 'HVM_boot_params': {}, - 'platform': {}, - 'PCI_bus': '', - 'recommendations': '', - 'affinity': '', - 'user_version': '0', - 'other_config': {}, - } - logging.debug('Created VM %s...', instance.name) - vm_ref = yield self._call_xenapi('VM.create', rec) - logging.debug('Created VM %s as %s.', instance.name, vm_ref) - defer.returnValue(vm_ref) - - @defer.inlineCallbacks - def _create_vbd(self, vm_ref, vdi_ref, userdevice, bootable): - """Create a VBD record. Returns a Deferred that gives the new - VBD reference.""" - - vbd_rec = {} - vbd_rec['VM'] = vm_ref - vbd_rec['VDI'] = vdi_ref - vbd_rec['userdevice'] = str(userdevice) - vbd_rec['bootable'] = bootable - vbd_rec['mode'] = 'RW' - vbd_rec['type'] = 'disk' - vbd_rec['unpluggable'] = True - vbd_rec['empty'] = False - vbd_rec['other_config'] = {} - vbd_rec['qos_algorithm_type'] = '' - vbd_rec['qos_algorithm_params'] = {} - vbd_rec['qos_supported_algorithms'] = [] - logging.debug('Creating VBD for VM %s, VDI %s ... ', vm_ref, vdi_ref) - vbd_ref = yield self._call_xenapi('VBD.create', vbd_rec) - logging.debug('Created VBD %s for VM %s, VDI %s.', vbd_ref, vm_ref, - vdi_ref) - defer.returnValue(vbd_ref) - - @defer.inlineCallbacks - def _create_vif(self, vm_ref, network_ref, mac_address): - """Create a VIF record. Returns a Deferred that gives the new - VIF reference.""" - - vif_rec = {} - vif_rec['device'] = '0' - vif_rec['network'] = network_ref - vif_rec['VM'] = vm_ref - vif_rec['MAC'] = mac_address - vif_rec['MTU'] = '1500' - vif_rec['other_config'] = {} - vif_rec['qos_algorithm_type'] = '' - vif_rec['qos_algorithm_params'] = {} - logging.debug('Creating VIF for VM %s, network %s ... ', vm_ref, - network_ref) - vif_ref = yield self._call_xenapi('VIF.create', vif_rec) - logging.debug('Created VIF %s for VM %s, network %s.', vif_ref, - vm_ref, network_ref) - defer.returnValue(vif_ref) - - @defer.inlineCallbacks - def _find_network_with_bridge(self, bridge): - expr = 'field "bridge" = "%s"' % bridge - networks = yield self._call_xenapi('network.get_all_records_where', - expr) - if len(networks) == 1: - defer.returnValue(networks.keys()[0]) - elif len(networks) > 1: - raise Exception('Found non-unique network for bridge %s' % bridge) - else: - raise Exception('Found no network for bridge %s' % bridge) - - @defer.inlineCallbacks - def _fetch_image(self, image, user, project, use_sr): - """use_sr: True to put the image as a VDI in an SR, False to place - it on dom0's filesystem. The former is for VM disks, the latter for - its kernel and ramdisk (if external kernels are being used). - Returns a Deferred that gives the new VDI UUID.""" - - url = images.image_url(image) - access = AuthManager().get_access_key(user, project) - logging.debug("Asking xapi to fetch %s as %s" % (url, access)) - fn = use_sr and 'get_vdi' or 'get_kernel' - args = {} - args['src_url'] = url - args['username'] = access - args['password'] = user.secret - if use_sr: - args['add_partition'] = 'true' - task = yield self._async_call_plugin('objectstore', fn, args) - uuid = yield self._wait_for_task(task) - defer.returnValue(uuid) - - @defer.inlineCallbacks - def reboot(self, instance): - vm = yield self._lookup(instance.name) - if vm is None: - raise Exception('instance not present %s' % instance.name) - task = yield self._call_xenapi('Async.VM.clean_reboot', vm) - yield self._wait_for_task(task) - - @defer.inlineCallbacks - def destroy(self, instance): - vm = yield self._lookup(instance.name) - if vm is None: - # Don't complain, just return. This lets us clean up instances - # that have already disappeared from the underlying platform. - defer.returnValue(None) - # Get the VDIs related to the VM - vdis = yield self._lookup_vm_vdis(vm) - try: - task = yield self._call_xenapi('Async.VM.hard_shutdown', vm) - yield self._wait_for_task(task) - except Exception, exc: - logging.warn(exc) - # Disk clean-up - if vdis: - for vdi in vdis: - try: - task = yield self._call_xenapi('Async.VDI.destroy', vdi) - yield self._wait_for_task(task) - except Exception, exc: - logging.warn(exc) - try: - task = yield self._call_xenapi('Async.VM.destroy', vm) - yield self._wait_for_task(task) - except Exception, exc: - logging.warn(exc) - - def get_info(self, instance_id): - vm = self._lookup_blocking(instance_id) - if vm is None: - raise Exception('instance not present %s' % instance_id) - rec = self._session.xenapi.VM.get_record(vm) - return {'state': XENAPI_POWER_STATE[rec['power_state']], - 'max_mem': long(rec['memory_static_max']) >> 10, - 'mem': long(rec['memory_dynamic_max']) >> 10, - 'num_cpu': rec['VCPUs_max'], - 'cpu_time': 0} - - def get_console_output(self, instance): - return 'FAKE CONSOLE OUTPUT' - - @utils.deferredToThread - def _lookup(self, i): - return self._lookup_blocking(i) - - def _lookup_blocking(self, i): - vms = self._session.xenapi.VM.get_by_name_label(i) - n = len(vms) - if n == 0: - return None - elif n > 1: - raise Exception('duplicate name found: %s' % i) - else: - return vms[0] - - @utils.deferredToThread - def _lookup_vm_vdis(self, vm): - return self._lookup_vm_vdis_blocking(vm) - - def _lookup_vm_vdis_blocking(self, vm): - # Firstly we get the VBDs, then the VDIs. - # TODO: do we leave the read-only devices? - vbds = self._session.xenapi.VM.get_VBDs(vm) - vdis = [] - if vbds: - for vbd in vbds: - try: - vdi = self._session.xenapi.VBD.get_VDI(vbd) - # Test valid VDI - record = self._session.xenapi.VDI.get_record(vdi) - except Exception, exc: - logging.warn(exc) - else: - vdis.append(vdi) - if len(vdis) > 0: - return vdis - else: - return None - - def _wait_for_task(self, task): - """Return a Deferred that will give the result of the given task. - The task is polled until it completes.""" - d = defer.Deferred() - reactor.callLater(0, self._poll_task, task, d) - return d - - @utils.deferredToThread - def _poll_task(self, task, deferred): - """Poll the given XenAPI task, and fire the given Deferred if we - get a result.""" - try: - #logging.debug('Polling task %s...', task) - status = self._session.xenapi.task.get_status(task) - if status == 'pending': - reactor.callLater(FLAGS.xenapi_task_poll_interval, - self._poll_task, task, deferred) - elif status == 'success': - result = self._session.xenapi.task.get_result(task) - logging.info('Task %s status: success. %s', task, result) - deferred.callback(_parse_xmlrpc_value(result)) - else: - error_info = self._session.xenapi.task.get_error_info(task) - logging.warn('Task %s status: %s. %s', task, status, - error_info) - deferred.errback(XenAPI.Failure(error_info)) - #logging.debug('Polling task %s done.', task) - except Exception, exc: - logging.warn(exc) - deferred.errback(exc) - - @utils.deferredToThread - def _call_xenapi(self, method, *args): - """Call the specified XenAPI method on a background thread. Returns - a Deferred for the result.""" - f = self._session.xenapi - for m in method.split('.'): - f = f.__getattr__(m) - return f(*args) - - @utils.deferredToThread - def _async_call_plugin(self, plugin, fn, args): - """Call Async.host.call_plugin on a background thread. Returns a - Deferred with the task reference.""" - return _unwrap_plugin_exceptions( - self._session.xenapi.Async.host.call_plugin, - self._get_xenapi_host(), plugin, fn, args) - - def _get_xenapi_host(self): - return self._session.xenapi.session.get_this_host(self._session.handle) - - -def _unwrap_plugin_exceptions(func, *args, **kwargs): - try: - return func(*args, **kwargs) - except XenAPI.Failure, exc: - logging.debug("Got exception: %s", exc) - if (len(exc.details) == 4 and - exc.details[0] == 'XENAPI_PLUGIN_EXCEPTION' and - exc.details[2] == 'Failure'): - params = None - try: - params = eval(exc.details[3]) - except: - raise exc - raise XenAPI.Failure(params) - else: - raise - except xmlrpclib.ProtocolError, exc: - logging.debug("Got exception: %s", exc) - raise - - -def _parse_xmlrpc_value(val): - """Parse the given value as if it were an XML-RPC value. This is - sometimes used as the format for the task.result field.""" - if not val: - return val - x = xmlrpclib.loads( - '' + - val + - '') - return x[0][0] From c10a6f3e97a5871ac0cdce97bde89b3cee59d336 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Sun, 28 Nov 2010 15:12:37 +0000 Subject: [PATCH 035/229] other round of refactoring --- nova/virt/xenapi/novadeps.py | 97 +++++++++++++++++++ nova/virt/xenapi/vm_utils.py | 32 ++---- nova/virt/xenapi/vmops.py | 41 ++++---- .../xenapi/{power_state.py => volumeops.py} | 22 +++-- 4 files changed, 141 insertions(+), 51 deletions(-) create mode 100644 nova/virt/xenapi/novadeps.py rename nova/virt/xenapi/{power_state.py => volumeops.py} (64%) diff --git a/nova/virt/xenapi/novadeps.py b/nova/virt/xenapi/novadeps.py new file mode 100644 index 0000000000..a4e5122630 --- /dev/null +++ b/nova/virt/xenapi/novadeps.py @@ -0,0 +1,97 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Citrix Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from nova import db +from nova import flags +from nova import process +from nova import utils + +from nova.compute import power_state +from nova.auth.manager import AuthManager +from nova.compute import instance_types +from nova.virt import images + +XENAPI_POWER_STATE = { + 'Halted': power_state.SHUTDOWN, + 'Running': power_state.RUNNING, + 'Paused': power_state.PAUSED, + 'Suspended': power_state.SHUTDOWN, # FIXME + 'Crashed': power_state.CRASHED} + +class Instance(object): + + @classmethod + def get_name(self, instance): + return instance.name + + @classmethod + def get_type(self, instance): + return instance_types.INSTANCE_TYPES[instance.instance_type] + + @classmethod + def get_project(self, instance): + return AuthManager().get_project(instance.project_id) + + @classmethod + def get_project_id(self, instance): + return instance.project_id + + @classmethod + def get_image_id(self, instance): + return instance.image_id + + @classmethod + def get_kernel_id(self, instance): + return instance.kernel_id + + @classmethod + def get_ramdisk_id(self, instance): + return instance.ramdisk_id + + @classmethod + def get_network(self, instance): + return db.project_get_network(None, instance.project_id) + + @classmethod + def get_mac(self, instance): + return instance.mac_address + + @classmethod + def get_user(self, instance): + return AuthManager().get_user(instance.user_id) + + +class Network(object): + + @classmethod + def get_bridge(self, network): + return network.bridge + +class Image(object): + + @classmethod + def get_url(self, image): + return images.image_url(image) + +class User(object): + + @classmethod + def get_access(self, user, project): + return AuthManager().get_access_key(user, project) + + @classmethod + def get_secret(self, user): + return user.secret \ No newline at end of file diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 6fb409b26e..8329f0d7e2 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -30,11 +30,10 @@ from nova import db from nova import flags from nova import process from nova import utils -from nova.auth.manager import AuthManager # wrap this one -from nova.compute import instance_types # wrap this one -from nova.virt import images # wrap this one -import power_state +from novadeps import Instance +from novadeps import Image +from novadeps import User class VMHelper(): @@ -44,7 +43,7 @@ class VMHelper(): """Create a VM record. Returns a Deferred that gives the new VM reference.""" - instance_type = instance_types.INSTANCE_TYPES[instance.instance_type] + instance_type = Instance.get_type(instance) mem = str(long(instance_type['memory_mb']) * 1024 * 1024) vcpus = str(instance_type['vcpus']) rec = { @@ -76,9 +75,9 @@ class VMHelper(): 'user_version': '0', 'other_config': {}, } - logging.debug('Created VM %s...', instance.name) + logging.debug('Created VM %s...', Instance.get_name(instance)) vm_ref = yield session.call_xenapi('VM.create', rec) - logging.debug('Created VM %s as %s.', instance.name, vm_ref) + logging.debug('Created VM %s as %s.', Instance.get_name(instance), vm_ref) defer.returnValue(vm_ref) @classmethod @@ -128,19 +127,6 @@ class VMHelper(): vm_ref, network_ref) defer.returnValue(vif_ref) - @classmethod - @defer.inlineCallbacks - def find_network_with_bridge(self, session, bridge): - expr = 'field "bridge" = "%s"' % bridge - networks = yield session.call_xenapi('network.get_all_records_where', - expr) - if len(networks) == 1: - defer.returnValue(networks.keys()[0]) - elif len(networks) > 1: - raise Exception('Found non-unique network for bridge %s' % bridge) - else: - raise Exception('Found no network for bridge %s' % bridge) - @classmethod @defer.inlineCallbacks def fetch_image(self, session, image, user, project, use_sr): @@ -149,14 +135,14 @@ class VMHelper(): its kernel and ramdisk (if external kernels are being used). Returns a Deferred that gives the new VDI UUID.""" - url = images.image_url(image) - access = AuthManager().get_access_key(user, project) + url = Image.get_url(image) + access = User.get_access(user, project) logging.debug("Asking xapi to fetch %s as %s" % (url, access)) fn = use_sr and 'get_vdi' or 'get_kernel' args = {} args['src_url'] = url args['username'] = access - args['password'] = user.secret + args['password'] = User.get_secret(user) if use_sr: args['add_partition'] = 'true' task = yield session.async_call_plugin('objectstore', fn, args) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 03b7fc614f..abb4225020 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -25,18 +25,14 @@ from twisted.internet import defer from twisted.internet import reactor from twisted.internet import task -from nova import db -from nova import flags -from nova import process -from nova import utils -from nova.auth.manager import AuthManager # wrap this one -from nova.compute import instance_types # wrap this one -from nova.virt import images # wrap this one -import power_state import VMHelper import NetworkHelper +from novadeps import XENAPI_POWER_STATE +from novadeps import Auth +from novadeps import Instance +from novadeps import Network class VMOps(object): def __init__(self, session): @@ -48,44 +44,45 @@ class VMOps(object): @defer.inlineCallbacks def spawn(self, instance): - vm = yield VMHelper.lookup(self._session, instance.name) + vm = yield VMHelper.lookup(self._session, Instance.get_name(instance)) if vm is not None: raise Exception('Attempted to create non-unique name %s' % - instance.name) + Instance.get_name(instance)) - network = db.project_get_network(None, instance.project_id) + network = Instance.get_network(instance) network_ref = \ - yield NetworkHelper.find_network_with_bridge(self._session, network.bridge) + yield NetworkHelper.find_network_with_bridge(self._session, Network.get_bridge(network)) - user = AuthManager().get_user(instance.user_id) - project = AuthManager().get_project(instance.project_id) + user = Instance.get_user(instance) + project = Instance.get_project(instance) vdi_uuid = yield VMHelper.fetch_image(self._session, - instance.image_id, user, project, True) + Instance.get_image_id(instance), user, project, True) kernel = yield VMHelper.fetch_image(self._session, - instance.kernel_id, user, project, False) + Instance.get_kernel_id(instance), user, project, False) ramdisk = yield VMHelper.fetch_image(self._session, - instance.ramdisk_id, user, project, False) + Instance.get_ramdisk_id(instance), user, project, False) vdi_ref = yield self._session.call_xenapi('VDI.get_by_uuid', vdi_uuid) vm_ref = yield VMHelper.create_vm(self._session, instance, kernel, ramdisk) yield VMHelper.create_vbd(self._session, vm_ref, vdi_ref, 0, True) if network_ref: - yield VMHelper.create_vif(self._session, vm_ref, network_ref, instance.mac_address) + yield VMHelper.create_vif(self._session, vm_ref, network_ref, Instance.get_mac(instance)) logging.debug('Starting VM %s...', vm_ref) yield self._session.call_xenapi('VM.start', vm_ref, False, False) - logging.info('Spawning VM %s created %s.', instance.name, vm_ref) + logging.info('Spawning VM %s created %s.', Instance.get_name(instance), vm_ref) @defer.inlineCallbacks def reboot(self, instance): - vm = yield VMHelper.lookup(self._session, instance.name) + instance_name = Instance.get_name(instance) + vm = yield VMHelper.lookup(self._session, instance_name) if vm is None: - raise Exception('instance not present %s' % instance.name) + raise Exception('instance not present %s' % instance_name) task = yield self._session.call_xenapi('Async.VM.clean_reboot', vm) yield self._session.wait_for_task(task) @defer.inlineCallbacks def destroy(self, instance): - vm = yield VMHelper.lookup(self._session, instance.name) + vm = yield VMHelper.lookup(self._session, Instance.get_name(instance)) if vm is None: # Don't complain, just return. This lets us clean up instances # that have already disappeared from the underlying platform. diff --git a/nova/virt/xenapi/power_state.py b/nova/virt/xenapi/volumeops.py similarity index 64% rename from nova/virt/xenapi/power_state.py rename to nova/virt/xenapi/volumeops.py index 5892f0f485..f5b43adfb3 100644 --- a/nova/virt/xenapi/power_state.py +++ b/nova/virt/xenapi/volumeops.py @@ -14,12 +14,22 @@ # License for the specific language governing permissions and limitations # under the License. +""" +Management class for Storage-related functions (attach, detach, etc). +""" + +from twisted.internet import defer + +from nova import exception from nova.compute import power_state -XENAPI_POWER_STATE = { - 'Halted': power_state.SHUTDOWN, - 'Running': power_state.RUNNING, - 'Paused': power_state.PAUSED, - 'Suspended': power_state.SHUTDOWN, # FIXME - 'Crashed': power_state.CRASHED} +class VMOps(object): + def __init__(self, session): + self._session = session + + def attach_volume(self, instance_name, device_path, mountpoint): + return True + + def detach_volume(self, instance_name, mountpoint): + return True \ No newline at end of file From 9d26ad69bfeb88106a08f0f3f1e15ed621c18af2 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Mon, 29 Nov 2010 10:25:52 +0000 Subject: [PATCH 036/229] first cut of the refactoring of the XenAPIConnection class. Currently the class merged both the code for managing the XenAPI connection and the business logic for implementing Nova operations. If left like this, it would eventually become difficult to read, maintain and extend. The file was getting kind of big and cluttered, so a quick refactoring now will save a lot of headaches later --- nova/virt/xenapi/network_utils.py | 10 ---------- nova/virt/xenapi/vm_utils.py | 2 +- nova/virt/xenapi/vmops.py | 1 - nova/virt/xenapi/volumeops.py | 5 ----- 4 files changed, 1 insertion(+), 17 deletions(-) diff --git a/nova/virt/xenapi/network_utils.py b/nova/virt/xenapi/network_utils.py index e062f916f0..83ade13892 100644 --- a/nova/virt/xenapi/network_utils.py +++ b/nova/virt/xenapi/network_utils.py @@ -25,16 +25,6 @@ import xmlrpclib from twisted.internet import defer from twisted.internet import reactor from twisted.internet import task - -from nova import db -from nova import flags -from nova import process -from nova import utils -from nova.auth.manager import AuthManager # wrap this one -from nova.compute import instance_types # wrap this one -from nova.virt import images # wrap this one - -import power_state class NetworkHelper(): diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 8329f0d7e2..a1b444e417 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -152,7 +152,7 @@ class VMHelper(): @classmethod @utils.deferredToThread def lookup(self, session, i): - return VMHelper.lookup_blocking(i) + return VMHelper.lookup_blocking(session, i) @classmethod def lookup_blocking(self, session, i): diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index abb4225020..c04a9f4ec4 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -25,7 +25,6 @@ from twisted.internet import defer from twisted.internet import reactor from twisted.internet import task - import VMHelper import NetworkHelper diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xenapi/volumeops.py index f5b43adfb3..fd316a0b8f 100644 --- a/nova/virt/xenapi/volumeops.py +++ b/nova/virt/xenapi/volumeops.py @@ -18,11 +18,6 @@ Management class for Storage-related functions (attach, detach, etc). """ -from twisted.internet import defer - -from nova import exception -from nova.compute import power_state - class VMOps(object): def __init__(self, session): From 9e34c9c7dc88d9e361c7f2d05e06b53ff68ee53f Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Mon, 29 Nov 2010 12:52:03 +0000 Subject: [PATCH 037/229] fixed deps --- nova/virt/xapi/__init__.py | 15 +++++++++ nova/virt/{xenapi => xapi}/network_utils.py | 16 ++++----- nova/virt/{xenapi => xapi}/novadeps.py | 31 ++++++++++-------- nova/virt/{xenapi => xapi}/vm_utils.py | 36 ++++++++++----------- nova/virt/{xenapi => xapi}/vmops.py | 35 ++++++++++---------- nova/virt/{xenapi => xapi}/volumeops.py | 4 +-- nova/virt/xenapi.py | 11 ++++--- 7 files changed, 83 insertions(+), 65 deletions(-) create mode 100644 nova/virt/xapi/__init__.py rename nova/virt/{xenapi => xapi}/network_utils.py (90%) rename nova/virt/{xenapi => xapi}/novadeps.py (93%) rename nova/virt/{xenapi => xapi}/vm_utils.py (92%) rename nova/virt/{xenapi => xapi}/vmops.py (87%) rename nova/virt/{xenapi => xapi}/volumeops.py (95%) diff --git a/nova/virt/xapi/__init__.py b/nova/virt/xapi/__init__.py new file mode 100644 index 0000000000..3d598c463c --- /dev/null +++ b/nova/virt/xapi/__init__.py @@ -0,0 +1,15 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Citrix Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/nova/virt/xenapi/network_utils.py b/nova/virt/xapi/network_utils.py similarity index 90% rename from nova/virt/xenapi/network_utils.py rename to nova/virt/xapi/network_utils.py index 83ade13892..b58b9159c2 100644 --- a/nova/virt/xenapi/network_utils.py +++ b/nova/virt/xapi/network_utils.py @@ -15,19 +15,17 @@ # under the License. """ -Helper methods for operations related to the management of network records and +Helper methods for operations related to the management of network records and their attributes like bridges, PIFs, QoS, as well as their lookup functions. """ -import logging -import xmlrpclib - from twisted.internet import defer -from twisted.internet import reactor -from twisted.internet import task - - + + class NetworkHelper(): + def __init__(self, session): + return + @classmethod @defer.inlineCallbacks def find_network_with_bridge(self, session, bridge): @@ -39,4 +37,4 @@ class NetworkHelper(): elif len(networks) > 1: raise Exception('Found non-unique network for bridge %s' % bridge) else: - raise Exception('Found no network for bridge %s' % bridge) \ No newline at end of file + raise Exception('Found no network for bridge %s' % bridge) diff --git a/nova/virt/xenapi/novadeps.py b/nova/virt/xapi/novadeps.py similarity index 93% rename from nova/virt/xenapi/novadeps.py rename to nova/virt/xapi/novadeps.py index a4e5122630..8cb5e3246f 100644 --- a/nova/virt/xenapi/novadeps.py +++ b/nova/virt/xapi/novadeps.py @@ -22,7 +22,7 @@ from nova import utils from nova.compute import power_state from nova.auth.manager import AuthManager from nova.compute import instance_types -from nova.virt import images +from nova.virt import images XENAPI_POWER_STATE = { 'Halted': power_state.SHUTDOWN, @@ -30,33 +30,34 @@ XENAPI_POWER_STATE = { 'Paused': power_state.PAUSED, 'Suspended': power_state.SHUTDOWN, # FIXME 'Crashed': power_state.CRASHED} - + + class Instance(object): @classmethod def get_name(self, instance): return instance.name - + @classmethod def get_type(self, instance): return instance_types.INSTANCE_TYPES[instance.instance_type] - + @classmethod def get_project(self, instance): return AuthManager().get_project(instance.project_id) - + @classmethod def get_project_id(self, instance): return instance.project_id - + @classmethod def get_image_id(self, instance): return instance.image_id - + @classmethod def get_kernel_id(self, instance): return instance.kernel_id - + @classmethod def get_ramdisk_id(self, instance): return instance.ramdisk_id @@ -64,28 +65,30 @@ class Instance(object): @classmethod def get_network(self, instance): return db.project_get_network(None, instance.project_id) - + @classmethod def get_mac(self, instance): return instance.mac_address - + @classmethod def get_user(self, instance): return AuthManager().get_user(instance.user_id) - + class Network(object): @classmethod def get_bridge(self, network): return network.bridge - + + class Image(object): @classmethod def get_url(self, image): return images.image_url(image) - + + class User(object): @classmethod @@ -94,4 +97,4 @@ class User(object): @classmethod def get_secret(self, user): - return user.secret \ No newline at end of file + return user.secret diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xapi/vm_utils.py similarity index 92% rename from nova/virt/xenapi/vm_utils.py rename to nova/virt/xapi/vm_utils.py index a1b444e417..41f687ccbb 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xapi/vm_utils.py @@ -15,35 +15,32 @@ # under the License. """ -Helper methods for operations related to the management of VM records and +Helper methods for operations related to the management of VM records and their attributes like VDIs, VIFs, as well as their lookup functions. """ import logging -import xmlrpclib from twisted.internet import defer -from twisted.internet import reactor -from twisted.internet import task -from nova import db -from nova import flags -from nova import process from nova import utils from novadeps import Instance from novadeps import Image from novadeps import User - - + + class VMHelper(): + def __init__(self, session): + return + @classmethod @defer.inlineCallbacks def create_vm(self, session, instance, kernel, ramdisk): """Create a VM record. Returns a Deferred that gives the new VM reference.""" - instance_type = Instance.get_type(instance) + instance_type = Instance.get_type(instance) mem = str(long(instance_type['memory_mb']) * 1024 * 1024) vcpus = str(instance_type['vcpus']) rec = { @@ -77,10 +74,11 @@ class VMHelper(): } logging.debug('Created VM %s...', Instance.get_name(instance)) vm_ref = yield session.call_xenapi('VM.create', rec) - logging.debug('Created VM %s as %s.', Instance.get_name(instance), vm_ref) + logging.debug('Created VM %s as %s.', + Instance.get_name(instance), vm_ref) defer.returnValue(vm_ref) - - @classmethod + + @classmethod @defer.inlineCallbacks def create_vbd(self, session, vm_ref, vdi_ref, userdevice, bootable): """Create a VBD record. Returns a Deferred that gives the new @@ -105,7 +103,7 @@ class VMHelper(): vdi_ref) defer.returnValue(vbd_ref) - @classmethod + @classmethod @defer.inlineCallbacks def create_vif(self, session, vm_ref, network_ref, mac_address): """Create a VIF record. Returns a Deferred that gives the new @@ -147,9 +145,9 @@ class VMHelper(): args['add_partition'] = 'true' task = yield session.async_call_plugin('objectstore', fn, args) uuid = yield session.wait_for_task(task) - defer.returnValue(uuid) - - @classmethod + defer.returnValue(uuid) + + @classmethod @utils.deferredToThread def lookup(self, session, i): return VMHelper.lookup_blocking(session, i) @@ -165,7 +163,7 @@ class VMHelper(): else: return vms[0] - @classmethod + @classmethod @utils.deferredToThread def lookup_vm_vdis(self, session, vm): return VMHelper.lookup_vm_vdis_blocking(session, vm) @@ -189,4 +187,4 @@ class VMHelper(): if len(vdis) > 0: return vdis else: - return None \ No newline at end of file + return None diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xapi/vmops.py similarity index 87% rename from nova/virt/xenapi/vmops.py rename to nova/virt/xapi/vmops.py index c04a9f4ec4..d6ea5e7db7 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xapi/vmops.py @@ -19,24 +19,21 @@ Management class for VM-related functions (spawn, reboot, etc). """ import logging -import xmlrpclib from twisted.internet import defer -from twisted.internet import reactor -from twisted.internet import task - -import VMHelper -import NetworkHelper from novadeps import XENAPI_POWER_STATE -from novadeps import Auth from novadeps import Instance from novadeps import Network +from vm_utils import VMHelper +from network_utils import NetworkHelper + + class VMOps(object): def __init__(self, session): self._session = session - + def list_instances(self): return [self._session.get_xenapi().VM.get_name_label(vm) \ for vm in self._session.get_xenapi().VM.get_all()] @@ -48,9 +45,9 @@ class VMOps(object): raise Exception('Attempted to create non-unique name %s' % Instance.get_name(instance)) - network = Instance.get_network(instance) + bridge = Network.get_bridge(Instance.get_network(instance)) network_ref = \ - yield NetworkHelper.find_network_with_bridge(self._session, Network.get_bridge(network)) + yield NetworkHelper.find_network_with_bridge(self._session, bridge) user = Instance.get_user(instance) project = Instance.get_project(instance) @@ -61,14 +58,16 @@ class VMOps(object): ramdisk = yield VMHelper.fetch_image(self._session, Instance.get_ramdisk_id(instance), user, project, False) vdi_ref = yield self._session.call_xenapi('VDI.get_by_uuid', vdi_uuid) - - vm_ref = yield VMHelper.create_vm(self._session, instance, kernel, ramdisk) + vm_ref = yield VMHelper.create_vm(self._session, + instance, kernel, ramdisk) yield VMHelper.create_vbd(self._session, vm_ref, vdi_ref, 0, True) if network_ref: - yield VMHelper.create_vif(self._session, vm_ref, network_ref, Instance.get_mac(instance)) + yield VMHelper.create_vif(self._session, vm_ref, + network_ref, Instance.get_mac(instance)) logging.debug('Starting VM %s...', vm_ref) yield self._session.call_xenapi('VM.start', vm_ref, False, False) - logging.info('Spawning VM %s created %s.', Instance.get_name(instance), vm_ref) + logging.info('Spawning VM %s created %s.', Instance.get_name(instance), + vm_ref) @defer.inlineCallbacks def reboot(self, instance): @@ -89,7 +88,8 @@ class VMOps(object): # Get the VDIs related to the VM vdis = yield VMHelper.lookup_vm_vdis(self._session, vm) try: - task = yield self._session.call_xenapi('Async.VM.hard_shutdown', vm) + task = yield self._session.call_xenapi('Async.VM.hard_shutdown', + vm) yield self._session.wait_for_task(task) except Exception, exc: logging.warn(exc) @@ -97,7 +97,8 @@ class VMOps(object): if vdis: for vdi in vdis: try: - task = yield self._session.call_xenapi('Async.VDI.destroy', vdi) + task = yield self._session.call_xenapi('Async.VDI.destroy', + vdi) yield self._session.wait_for_task(task) except Exception, exc: logging.warn(exc) @@ -119,4 +120,4 @@ class VMOps(object): 'cpu_time': 0} def get_console_output(self, instance): - return 'FAKE CONSOLE OUTPUT' \ No newline at end of file + return 'FAKE CONSOLE OUTPUT' diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xapi/volumeops.py similarity index 95% rename from nova/virt/xenapi/volumeops.py rename to nova/virt/xapi/volumeops.py index fd316a0b8f..23f79adf71 100644 --- a/nova/virt/xenapi/volumeops.py +++ b/nova/virt/xapi/volumeops.py @@ -19,7 +19,7 @@ Management class for Storage-related functions (attach, detach, etc). """ -class VMOps(object): +class VolumeOps(object): def __init__(self, session): self._session = session @@ -27,4 +27,4 @@ class VMOps(object): return True def detach_volume(self, instance_name, mountpoint): - return True \ No newline at end of file + return True diff --git a/nova/virt/xenapi.py b/nova/virt/xenapi.py index 2f2cef75ef..613f19f823 100644 --- a/nova/virt/xenapi.py +++ b/nova/virt/xenapi.py @@ -52,10 +52,13 @@ import xmlrpclib from twisted.internet import defer from twisted.internet import reactor -from twisted.internet import task +#from twisted.internet import task -from xenapi import VMOps -from xenapi import VolumeOps +from nova import flags +from nova import utils + +from xapi.vmops import VMOps +from xapi.volumeops import VolumeOps XenAPI = None @@ -151,7 +154,7 @@ class XenAPISession(object): for m in method.split('.'): f = f.__getattr__(m) return f(*args) - + @utils.deferredToThread def async_call_plugin(self, plugin, fn, args): """Call Async.host.call_plugin on a background thread. Returns a From a82581cbada92d0e274438757f7beb3ed335da1b Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Mon, 29 Nov 2010 16:31:31 +0000 Subject: [PATCH 038/229] pep8 fixes and further round of refactoring --- nova/virt/connection.py | 4 ++-- nova/virt/{xapi => xenapi}/__init__.py | 0 nova/virt/{xapi => xenapi}/network_utils.py | 0 nova/virt/{xapi => xenapi}/novadeps.py | 5 ++++- nova/virt/{xapi => xenapi}/vm_utils.py | 2 +- nova/virt/{xapi => xenapi}/vmops.py | 0 nova/virt/{xapi => xenapi}/volumeops.py | 0 nova/virt/{xenapi.py => xenapi_conn.py} | 5 ++--- 8 files changed, 9 insertions(+), 7 deletions(-) rename nova/virt/{xapi => xenapi}/__init__.py (100%) rename nova/virt/{xapi => xenapi}/network_utils.py (100%) rename nova/virt/{xapi => xenapi}/novadeps.py (92%) rename nova/virt/{xapi => xenapi}/vm_utils.py (99%) rename nova/virt/{xapi => xenapi}/vmops.py (100%) rename nova/virt/{xapi => xenapi}/volumeops.py (100%) rename nova/virt/{xenapi.py => xenapi_conn.py} (98%) diff --git a/nova/virt/connection.py b/nova/virt/connection.py index 11f0fa8ced..c40bb4bb4d 100644 --- a/nova/virt/connection.py +++ b/nova/virt/connection.py @@ -25,7 +25,7 @@ import sys from nova import flags from nova.virt import fake from nova.virt import libvirt_conn -from nova.virt import xenapi +from nova.virt import xenapi_conn FLAGS = flags.FLAGS @@ -61,7 +61,7 @@ def get_connection(read_only=False): elif t == 'libvirt': conn = libvirt_conn.get_connection(read_only) elif t == 'xenapi': - conn = xenapi.get_connection(read_only) + conn = xenapi_conn.get_connection(read_only) else: raise Exception('Unknown connection type "%s"' % t) diff --git a/nova/virt/xapi/__init__.py b/nova/virt/xenapi/__init__.py similarity index 100% rename from nova/virt/xapi/__init__.py rename to nova/virt/xenapi/__init__.py diff --git a/nova/virt/xapi/network_utils.py b/nova/virt/xenapi/network_utils.py similarity index 100% rename from nova/virt/xapi/network_utils.py rename to nova/virt/xenapi/network_utils.py diff --git a/nova/virt/xapi/novadeps.py b/nova/virt/xenapi/novadeps.py similarity index 92% rename from nova/virt/xapi/novadeps.py rename to nova/virt/xenapi/novadeps.py index 8cb5e3246f..ba62468fbc 100644 --- a/nova/virt/xapi/novadeps.py +++ b/nova/virt/xenapi/novadeps.py @@ -18,6 +18,7 @@ from nova import db from nova import flags from nova import process from nova import utils +from nova import context from nova.compute import power_state from nova.auth.manager import AuthManager @@ -64,7 +65,9 @@ class Instance(object): @classmethod def get_network(self, instance): - return db.project_get_network(None, instance.project_id) + # TODO: is ge_admin_context the right context to retrieve? + return db.project_get_network(context.get_admin_context(), + instance.project_id) @classmethod def get_mac(self, instance): diff --git a/nova/virt/xapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py similarity index 99% rename from nova/virt/xapi/vm_utils.py rename to nova/virt/xenapi/vm_utils.py index 41f687ccbb..b68df27919 100644 --- a/nova/virt/xapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -33,7 +33,7 @@ from novadeps import User class VMHelper(): def __init__(self, session): return - + @classmethod @defer.inlineCallbacks def create_vm(self, session, instance, kernel, ramdisk): diff --git a/nova/virt/xapi/vmops.py b/nova/virt/xenapi/vmops.py similarity index 100% rename from nova/virt/xapi/vmops.py rename to nova/virt/xenapi/vmops.py diff --git a/nova/virt/xapi/volumeops.py b/nova/virt/xenapi/volumeops.py similarity index 100% rename from nova/virt/xapi/volumeops.py rename to nova/virt/xenapi/volumeops.py diff --git a/nova/virt/xenapi.py b/nova/virt/xenapi_conn.py similarity index 98% rename from nova/virt/xenapi.py rename to nova/virt/xenapi_conn.py index 613f19f823..0a73b47741 100644 --- a/nova/virt/xenapi.py +++ b/nova/virt/xenapi_conn.py @@ -52,13 +52,12 @@ import xmlrpclib from twisted.internet import defer from twisted.internet import reactor -#from twisted.internet import task from nova import flags from nova import utils -from xapi.vmops import VMOps -from xapi.volumeops import VolumeOps +from xenapi.vmops import VMOps +from xenapi.volumeops import VolumeOps XenAPI = None From 04b1740c991d6d499364c21c2524c46ed5fc2522 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Mon, 29 Nov 2010 17:26:44 +0000 Subject: [PATCH 039/229] changes --- nova/virt/xenapi.py | 93 +++++++++++++++++++++++++++++++++------------ 1 file changed, 68 insertions(+), 25 deletions(-) diff --git a/nova/virt/xenapi.py b/nova/virt/xenapi.py index 236360f11f..f2ba713063 100644 --- a/nova/virt/xenapi.py +++ b/nova/virt/xenapi.py @@ -198,6 +198,9 @@ class XenAPIConnection(object): @defer.inlineCallbacks def _create_vdi(self, sr_ref, size, type, label, description, read_only, sharable): + """Create a VDI record. Returns a Deferred that gives the new + VDI reference.""" + vdi_rec = {} vdi_rec['read_only'] = read_only vdi_rec['SR'] = sr_ref @@ -337,7 +340,8 @@ class XenAPIConnection(object): description = 'Attached-to:%s' % instance_name # Create SR and check the physical space available for the VDI allocation sr_ref = yield self._create_sr(target, label, description) - disk_size = yield self._get_sr_available_space(sr_ref) + disk_size = int(target['size']) + #disk_size = yield self._get_sr_available_space(sr_ref) # Create VDI and attach VBD to VM vm_ref = yield self._lookup(instance_name) logging.debug("Mounting disk of: %s GB", (disk_size / (1024*1024*1024.0))) @@ -347,20 +351,30 @@ class XenAPIConnection(object): False, False) except Exception, exc: logging.warn(exc) - if sr_ref: - yield self._destroy_sr(sr_ref) - raise Exception('Unable to create VDI on SR %s' % sr_ref) + yield self._destroy_sr(sr_ref) + raise Exception('Unable to create VDI on SR %s for instance %s' + % (sr_ref, + instance_name)) else: - try: + try: userdevice = 2 # FIXME: this depends on the numbers of attached disks vbd_ref = yield self._create_vbd(vm_ref, vdi_ref, userdevice, False, True, False) - task = yield self._call_xenapi('Async.VBD.plug', vbd_ref) - yield self._wait_for_task(task) except Exception, exc: logging.warn(exc) - if sr_ref: + yield self._destroy_sr(sr_ref) + raise Exception('Unable to create VBD on SR %s for instance %s' + % (sr_ref, + instance_name)) + else: + try: + raise Exception('') + task = yield self._call_xenapi('Async.VBD.plug', vbd_ref) + yield self._wait_for_task(task) + except Exception, exc: + logging.warn(exc) yield self._destroy_sr(sr_ref) - raise Exception('Unable to create VBD on SR %s' % sr_ref) + raise Exception('Unable to attach volume to instance %s' % instance_name) + yield True @defer.inlineCallbacks @@ -412,7 +426,7 @@ class XenAPIConnection(object): try: self._conn.xenapi.SR.create(self._get_xenapi_host(), target, '-1', '', '', - 'lvmoiscsi', '', False, {}) + 'iscsi', '', False, {}) except XenAPI.Failure, exc: if exc.details[0] == 'SR_BACKEND_FAILURE_107': xml_response = parseString(exc.details[3]) @@ -427,6 +441,9 @@ class XenAPIConnection(object): for n in isciLuns.item(0).childNodes: if n.nodeType == 1: target[n.nodeName] = str(n.firstChild.data).strip() + else: + logging.warn(exc) + raise Exception('Unable to access SR') return target @utils.deferredToThread @@ -444,24 +461,45 @@ class XenAPIConnection(object): def _create_sr_blocking(self, target, label, description): # TODO: we might want to put all these string literals into constants - sr_ref = self._conn.xenapi.SR.create(self._get_xenapi_host(), + sr_ref = self._conn.xenapi.SR.get_by_name_label(label) + if sr_ref is None: + sr_ref = self._conn.xenapi.SR.create(self._get_xenapi_host(), target, target['size'], label, description, - 'lvmoiscsi', + 'iscsi', '', True, {}) - # TODO: there might be some timing issues here - self._conn.xenapi.SR.scan(sr_ref) - return sr_ref + if sr_ref: + #self._conn.xenapi.SR.scan(sr_ref) + return sr_ref + else: + raise Exception('Unable to create SR') @defer.inlineCallbacks def _destroy_sr(self, sr_ref): # Some clean-up depending on the state of the SR - #yield self._destroy_vdbs(sr_ref) - #yield self._destroy_vdis(sr_ref) - # Destroy PDBs + # Remove VBDs + #vbds = yield self._conn.xenapi.SR.get_VBDs(sr_ref) + #for vbd_ref in vbds: + # try: + # task = yield self._call_xenapi('Async.VBD.destroy', vbd_ref) + # yield self._wait_for_task(task) + # except Exception, exc: + # logging.warn(exc) + # Remove VDIs + #======================================================================= + # vdis = yield self._conn.xenapi.SR.get_VDIs(sr_ref) + # for vdi_ref in vdis: + # try: + # task = yield self._call_xenapi('Async.VDI.destroy', vdi_ref) + # yield self._wait_for_task(task) + # except Exception, exc: + # logging.warn(exc) + #======================================================================= + sr_rec = self._conn.xenapi.SR.get_record(sr_ref) + # Detach from host pbds = yield self._conn.xenapi.SR.get_PBDs(sr_ref) for pbd_ref in pbds: try: @@ -469,16 +507,21 @@ class XenAPIConnection(object): yield self._wait_for_task(task) except Exception, exc: logging.warn(exc) - else: - task = yield self._call_xenapi('Async.PBD.destroy', pbd_ref) - yield self._wait_for_task(task) - # Forget SR + # Destroy SR try: - task = yield self._call_xenapi('Async.SR.forget', sr_ref) + task = yield self._call_xenapi('Async.SR.destroy', sr_ref) yield self._wait_for_task(task) except Exception, exc: logging.warn(exc) + def _sr_dispose_action(self, action, records): + for rec_ref in records: + try: + task = yield self._call_xenapi(action, rec_ref) + yield self._wait_for_task(task) + except Exception, exc: + logging.warn(exc) + @utils.deferredToThread def _lookup_vm_vdis(self, vm): return self._lookup_vm_vdis_blocking(vm) @@ -592,8 +635,8 @@ def _parse_volume_info(device_path, mountpoint): volume_info = {} volume_info['volumeId'] = 'vol-qurmrzn9' # Because XCP/XS want an x beforehand - volume_info['xenMountpoint'] = '/dev/xvdc' - volume_info['targetHost'] = '' + volume_info['mountpoint'] = '/dev/xvdc' # translate + volume_info['targetHost'] = '10.70.177.40' volume_info['targetPort'] = '3260' # default 3260 volume_info['iqn'] = 'iqn.2010-10.org.openstack:vol-qurmrzn9' return volume_info From 40de074f44059f89caa15420a7174f63c76eec48 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Tue, 30 Nov 2010 19:03:13 +0000 Subject: [PATCH 040/229] iscsi volumes attach/detach complete. There is only one minor issue on how to discover targets from device_path --- nova/virt/xenapi/vm_utils.py | 38 ++++++ nova/virt/xenapi/volume_utils.py | 210 +++++++++++++++++++++++++++++++ nova/virt/xenapi/volumeops.py | 93 +++++++++----- 3 files changed, 313 insertions(+), 28 deletions(-) create mode 100644 nova/virt/xenapi/volume_utils.py diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index b68df27919..6966e7b7bb 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -103,6 +103,44 @@ class VMHelper(): vdi_ref) defer.returnValue(vbd_ref) + @classmethod + @utils.deferredToThread + def find_vbd_by_number(self, session, vm_ref, number): + return VMHelper.find_vbd_by_number_blocking(session, vm_ref, number) + + @classmethod + def find_vbd_by_number_blocking(self, session, vm_ref, number): + vbds = session.get_xenapi().VM.get_VBDs(vm_ref) + if vbds: + for vbd in vbds: + try: + vbd_rec = session.get_xenapi().VBD.get_record(vbd) + if vbd_rec['userdevice'] == str(number): + return vbd + except Exception, exc: + logging.warn(exc) + raise Exception('VBD not found in instance %s' % vm_ref) + + @classmethod + @defer.inlineCallbacks + def unplug_vbd(self, session, vbd_ref): + try: + vbd_ref = yield session.call_xenapi('VBD.unplug', vbd_ref) + except Exception, exc: + logging.warn(exc) + if exc.details[0] != 'DEVICE_ALREADY_DETACHED': + raise Exception('Unable to unplug VBD %s' % vbd_ref) + + @classmethod + @defer.inlineCallbacks + def destroy_vbd(self, session, vbd_ref): + try: + task = yield session.call_xenapi('Async.VBD.destroy', vbd_ref) + yield session.wait_for_task(task) + except Exception, exc: + logging.warn(exc) + raise Exception('Unable to destroy VBD %s' % vbd_ref) + @classmethod @defer.inlineCallbacks def create_vif(self, session, vm_ref, network_ref, mac_address): diff --git a/nova/virt/xenapi/volume_utils.py b/nova/virt/xenapi/volume_utils.py new file mode 100644 index 0000000000..b982ac124b --- /dev/null +++ b/nova/virt/xenapi/volume_utils.py @@ -0,0 +1,210 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Citrix Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Helper methods for operations related to the management of volumes, +and storage repositories +""" + +import logging +import re +import string + +from twisted.internet import defer + +from nova import utils +from nova import flags + +FLAGS = flags.FLAGS + +#FIXME: replace with proper target discovery +flags.DEFINE_string('target_host', None, 'iSCSI Target Host') +flags.DEFINE_string('target_port', '3260', 'iSCSI Target Port, 3260 Default') +flags.DEFINE_string('iqn_prefix', 'iqn.2010-10.org.openstack', 'IQN Prefix') + + +class VolumeHelper(): + def __init__(self, session): + return + + @classmethod + @utils.deferredToThread + def create_iscsi_storage(self, session, target, port, target_iqn, + username, password, label, description): + + return VolumeHelper.create_iscsi_storage_blocking(session, target, + port, + target_iqn, + username, + password, + label, + description) + + @classmethod + def create_iscsi_storage_blocking(self, session, target, port, target_iqn, + username, password, label, description): + + sr_ref = session.get_xenapi().SR.get_by_name_label(label) + if len(sr_ref) == 0: + logging.debug('Introducing %s...' % label) + try: + sr_ref = session.get_xenapi().SR.create( + session.get_xenapi_host(), + {'target': target, + 'port': port, + 'targetIQN': target_iqn + # TODO: when/if chap authentication is used + #'chapuser': username, + #'chappassword': password + }, + '0', label, description, 'iscsi', '', False, {}) + logging.debug('Introduced %s as %s.' % (label, sr_ref)) + return sr_ref + except Exception, exc: + logging.warn(exc) + raise Exception('Unable to create Storage Repository') + else: + return sr_ref[0] + + @classmethod + @defer.inlineCallbacks + def find_sr_from_vbd(self, session, vbd_ref): + vdi_ref = yield session.get_xenapi().VBD.get_VDI(vbd_ref) + sr_ref = yield session.get_xenapi().VDI.get_SR(vdi_ref) + defer.returnValue(sr_ref) + + @classmethod + @utils.deferredToThread + def destroy_iscsi_storage(self, session, sr_ref): + VolumeHelper.destroy_iscsi_storage_blocking(session, sr_ref) + + @classmethod + def destroy_iscsi_storage_blocking(self, session, sr_ref): + logging.debug("Forgetting SR %s ... ", sr_ref) + pbds = [] + try: + pbds = session.get_xenapi().SR.get_PBDs(sr_ref) + except Exception, exc: + logging.warn('Ignoring exception %s when getting PBDs for %s', + exc, sr_ref) + for pbd in pbds: + try: + session.get_xenapi().PBD.unplug(pbd) + except Exception, exc: + logging.warn('Ignoring exception %s when unplugging PBD %s', + exc, pbd) + try: + session.get_xenapi().SR.forget(sr_ref) + logging.debug("Forgetting SR %s done.", sr_ref) + except Exception, exc: + logging.warn('Ignoring exception %s when forgetting SR %s', + exc, sr_ref) + + @classmethod + @utils.deferredToThread + def introduce_vdi(self, session, sr_ref): + return VolumeHelper.introduce_vdi_blocking(session, sr_ref) + + @classmethod + def introduce_vdi_blocking(self, session, sr_ref): + try: + vdis = session.get_xenapi().SR.get_VDIs(sr_ref) + except Exception, exc: + raise Exception('Unable to introduce VDI on SR %s' % sr_ref) + try: + vdi_rec = session.get_xenapi().VDI.get_record(vdis[0]) + except Exception, exc: + raise Exception('Unable to get record of VDI %s on' % vdis[0]) + else: + return session.get_xenapi().VDI.introduce( + vdi_rec['uuid'], + vdi_rec['name_label'], + vdi_rec['name_description'], + vdi_rec['SR'], + vdi_rec['type'], + vdi_rec['sharable'], + vdi_rec['read_only'], + vdi_rec['other_config'], + vdi_rec['location'], + vdi_rec['xenstore_data'], + vdi_rec['sm_config']) + + @classmethod + def parse_volume_info(self, device_path, mountpoint): + # Because XCP/XS want a device number instead of a mountpoint + device_number = VolumeHelper.mountpoint_to_number(mountpoint) + volume_id = _get_volume_id(device_path) + target_host = _get_target_host(device_path) + target_port = _get_target_port(device_path) + target_iqn = _get_iqn(device_path) + + if (device_number < 0) or \ + (volume_id is None) or \ + (target_host is None) or \ + (target_iqn is None): + raise Exception('Unable to obtain target information %s, %s' % + (device_path, mountpoint)) + + volume_info = {} + volume_info['deviceNumber'] = device_number + volume_info['volumeId'] = volume_id + volume_info['targetHost'] = target_host + volume_info['targetPort'] = target_port + volume_info['targeIQN'] = target_iqn + return volume_info + + @classmethod + def mountpoint_to_number(self, mountpoint): + if mountpoint.startswith('/dev/'): + mountpoint = mountpoint[5:] + if re.match('^[hs]d[a-p]$', mountpoint): + return (ord(mountpoint[2:3]) - ord('a')) + elif re.match('^vd[a-p]$', mountpoint): + return (ord(mountpoint[2:3]) - ord('a')) + elif re.match('^[0-9]+$', mountpoint): + return string.atoi(mountpoint, 10) + else: + logging.warn('Mountpoint cannot be translated: %s', mountpoint) + return -1 + + +def _get_volume_id(n): + # FIXME: n must contain at least the volume_id + # /vol- is for remote volumes + # -vol- is for local volumes + # see compute/manager->setup_compute_volume + volume_id = n[n.find('/vol-') + 1:] + if volume_id == n: + volume_id = n[n.find('-vol-') + 1:].replace('--', '-') + return volume_id + + +def _get_target_host(n): + # FIXME: if n is none fall back on flags + if n is None or FLAGS.target_host: + return FLAGS.target_host + + +def _get_target_port(n): + # FIXME: if n is none fall back on flags + return FLAGS.target_port + + +def _get_iqn(n): + # FIXME: n must contain at least the volume_id + volume_id = _get_volume_id(n) + if n is None or FLAGS.iqn_prefix: + return '%s:%s' % (FLAGS.iqn_prefix, volume_id) diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xenapi/volumeops.py index 5aefa06114..d5c3092403 100644 --- a/nova/virt/xenapi/volumeops.py +++ b/nova/virt/xenapi/volumeops.py @@ -17,6 +17,12 @@ """ Management class for Storage-related functions (attach, detach, etc). """ +import logging + +from twisted.internet import defer + +from volume_utils import VolumeHelper +from vm_utils import VMHelper class VolumeOps(object): @@ -25,58 +31,89 @@ class VolumeOps(object): @defer.inlineCallbacks def attach_volume(self, instance_name, device_path, mountpoint): + # Before we start, check that the VM exists + vm_ref = yield VMHelper.lookup(self._session, instance_name) + if vm_ref is None: + raise Exception('Instance %s does not exist' % instance_name) # NOTE: No Resource Pool concept so far logging.debug("Attach_volume: %s, %s, %s", instance_name, device_path, mountpoint) - volume_info = _parse_volume_info(device_path, mountpoint) + vol_rec = VolumeHelper.parse_volume_info(device_path, mountpoint) # Create the iSCSI SR, and the PDB through which hosts access SRs. # But first, retrieve target info, like Host, IQN, LUN and SCSIID - target = yield self._get_target(volume_info) - label = 'SR-%s' % volume_info['volumeId'] - description = 'Attached-to:%s' % instance_name - # Create SR and check the physical space available for the VDI allocation - sr_ref = yield self._create_sr(target, label, description) - disk_size = int(target['size']) - #disk_size = yield self._get_sr_available_space(sr_ref) - # Create VDI and attach VBD to VM - vm_ref = yield self._lookup(instance_name) - logging.debug("Mounting disk of: %s GB", (disk_size / (1024*1024*1024.0))) + label = 'SR-%s' % vol_rec['volumeId'] + description = 'Disk-for:%s' % instance_name + # Create SR + sr_ref = yield VolumeHelper.create_iscsi_storage(self._session, + vol_rec['targetHost'], + vol_rec['targetPort'], + vol_rec['targeIQN'], + '', # no CHAP auth + '', + label, + description) + # Introduce VDI and attach VBD to VM try: - vdi_ref = yield self._create_vdi(sr_ref, disk_size, - 'user', volume_info['volumeId'], '', - False, False) + vdi_ref = yield VolumeHelper.introduce_vdi(self._session, sr_ref) except Exception, exc: logging.warn(exc) - yield self._destroy_sr(sr_ref) + yield VolumeHelper.destroy_iscsi_storage(self._session, sr_ref) raise Exception('Unable to create VDI on SR %s for instance %s' % (sr_ref, instance_name)) else: try: - userdevice = 2 # FIXME: this depends on the numbers of attached disks - vbd_ref = yield self._create_vbd(vm_ref, vdi_ref, userdevice, False, True, False) + vbd_ref = yield VMHelper.create_vbd(self._session, + vm_ref, vdi_ref, + vol_rec['deviceNumber'], + False) except Exception, exc: logging.warn(exc) - yield self._destroy_sr(sr_ref) + yield VolumeHelper.destroy_iscsi_storage(self._session, sr_ref) raise Exception('Unable to create VBD on SR %s for instance %s' % (sr_ref, instance_name)) else: try: - raise Exception('') - task = yield self._call_xenapi('Async.VBD.plug', vbd_ref) - yield self._wait_for_task(task) + #raise Exception('') + task = yield self._session.call_xenapi('Async.VBD.plug', + vbd_ref) + yield self._session.wait_for_task(task) except Exception, exc: logging.warn(exc) - yield self._destroy_sr(sr_ref) - raise Exception('Unable to attach volume to instance %s' % instance_name) - + yield VolumeHelper.destroy_iscsi_storage(self._session, + sr_ref) + raise Exception('Unable to attach volume to instance %s' % + instance_name) yield True @defer.inlineCallbacks def detach_volume(self, instance_name, mountpoint): - logging.debug("Detach_volume: %s, %s, %s", instance_name, mountpoint) + # Before we start, check that the VM exists + vm_ref = yield VMHelper.lookup(self._session, instance_name) + if vm_ref is None: + raise Exception('Instance %s does not exist' % instance_name) # Detach VBD from VM - # Forget SR/PDB info associated with host - # TODO: can we avoid destroying the SR every time we detach? - yield True \ No newline at end of file + logging.debug("Detach_volume: %s, %s", instance_name, mountpoint) + device_number = VolumeHelper.mountpoint_to_number(mountpoint) + try: + vbd_ref = yield VMHelper.find_vbd_by_number(self._session, + vm_ref, device_number) + except Exception, exc: + logging.warn(exc) + raise Exception('Unable to locate volume %s' % mountpoint) + else: + try: + sr_ref = yield VolumeHelper.find_sr_from_vbd(self._session, + vbd_ref) + yield VMHelper.unplug_vbd(self._session, vbd_ref) + except Exception, exc: + logging.warn(exc) + raise Exception('Unable to detach volume %s' % mountpoint) + try: + yield VMHelper.destroy_vbd(self._session, vbd_ref) + except Exception, exc: + logging.warn(exc) + # Forget SR + yield VolumeHelper.destroy_iscsi_storage(self._session, sr_ref) + yield True From 4b74a1b243d87d53e660029728d12a9c067deeac Mon Sep 17 00:00:00 2001 From: Rick Clark Date: Tue, 30 Nov 2010 13:08:39 -0600 Subject: [PATCH 041/229] Cleaned up pep8 errors --- nova/api/ec2/cloud.py | 2 +- nova/compute/disk.py | 6 +++--- nova/virt/libvirt_conn.py | 13 ++++++------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 1cf4abcc90..a5acd1f6d8 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -801,7 +801,7 @@ class CloudController(object): if kernel_id == str(FLAGS.null_kernel): kernel_id = None ramdisk_id = None - + # make sure we have access to kernel and ramdisk if kernel_id: self.image_service.show(context, kernel_id) diff --git a/nova/compute/disk.py b/nova/compute/disk.py index d5a08c01f4..d762b8087b 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -137,9 +137,9 @@ def inject_data(image, key=None, net=None, partition=None, execute=None): # We can only loopback mount raw images. If the device isn't there, # it's normally because it's a .vmdk or a .vdi etc if not os.path.exists(mapped_device): - raise exception.Error( - 'Mapped device was not found (we can only inject raw disk images): %s' - % mapped_device) + raise exception.Error('Mapped device was not found (we can' + ' only inject raw disk images): %s' % + mapped_device) # Configure ext2fs so that it doesn't auto-check every N boots out, err = yield execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 09cb29773e..81dbbaad59 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -467,13 +467,13 @@ class LibvirtConnection(object): process_input=process_input, check_exit_code=check_exit_code) - # For now, we assume that if we're not using a kernel, we're using a - # partitioned disk image where the target partition is the first + # For now, we assume that if we're not using a kernel, we're using a + # partitioned disk image where the target partition is the first # partition target_partition = None if not using_kernel: target_partition = "1" - + key = str(inst['key_data']) net = None network_ref = db.network_get_by_instance(context.get_admin_context(), @@ -493,11 +493,10 @@ class LibvirtConnection(object): inst['name'], inst.image_id) if net: logging.info('instance %s: injecting net into image %s', - inst['name'], inst.image_id) - execute=execute) + inst['name'], inst.image_id) try: yield disk.inject_data(basepath('disk-raw'), key, net, - partition=target_partition, + partition=target_partition, execute=execute) except Exception as e: # This could be a windows image, or a vmdk format disk @@ -557,7 +556,7 @@ class LibvirtConnection(object): if xml_info['ramdisk_id'] or xml_info['kernel_id']: xml_info['disk'] = xml_info['basepath'] + "/disk" - xml = str(Template(self.libvirt_xml, searchList=[ xml_info ] )) + xml = str(Template(self.libvirt_xml, searchList=[xml_info])) logging.debug('instance %s: finished toXML method', instance['name']) return xml From ffa41022463c23a67dda2a6de74b6d5203cb37ac Mon Sep 17 00:00:00 2001 From: Rick Clark Date: Tue, 30 Nov 2010 16:09:31 -0600 Subject: [PATCH 042/229] Fixed termie's tiny bits from the prior merge request --- nova/compute/disk.py | 4 ++-- nova/flags.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/compute/disk.py b/nova/compute/disk.py index d762b8087b..9ba827519c 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -134,8 +134,8 @@ def inject_data(image, key=None, net=None, partition=None, execute=None): else: mapped_device = device - # We can only loopback mount raw images. If the device isn't there, - # it's normally because it's a .vmdk or a .vdi etc + # We can only loopback mount raw images. If the device isn't there, + # it's normally because it's a .vmdk or a .vdi etc if not os.path.exists(mapped_device): raise exception.Error('Mapped device was not found (we can' ' only inject raw disk images): %s' % diff --git a/nova/flags.py b/nova/flags.py index cf481b55f6..a610332014 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -237,7 +237,7 @@ DEFINE_string('default_ramdisk', 'ari-11111', DEFINE_string('default_instance_type', 'm1.small', 'default instance type to use, testing only') DEFINE_string('null_kernel', 'aki-00000000', - 'kernel image that indicates not to use a kernel, ' + 'kernel image that indicates not to use a kernel,' ' but to use a raw disk image instead') DEFINE_string('vpn_image_id', 'ami-CLOUDPIPE', 'AMI for cloudpipe vpn server') From aaee43a74264d5e6a4ccf638f882b19d477c3c9f Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Tue, 30 Nov 2010 23:12:19 +0000 Subject: [PATCH 043/229] 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 1a9f93a3b1..7eb934eca9 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 0000000000..8052c077d0 --- /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 12:02:02 +0000 Subject: [PATCH 044/229] minor refactoring --- nova/virt/xenapi/novadeps.py | 82 ++++++++++++++++++++++++++++++++ nova/virt/xenapi/volume_utils.py | 77 ------------------------------ nova/virt/xenapi/volumeops.py | 6 ++- 3 files changed, 86 insertions(+), 79 deletions(-) diff --git a/nova/virt/xenapi/novadeps.py b/nova/virt/xenapi/novadeps.py index ba62468fbc..db51982a69 100644 --- a/nova/virt/xenapi/novadeps.py +++ b/nova/virt/xenapi/novadeps.py @@ -14,6 +14,9 @@ # License for the specific language governing permissions and limitations # under the License. +import re +import string + from nova import db from nova import flags from nova import process @@ -32,6 +35,15 @@ XENAPI_POWER_STATE = { 'Suspended': power_state.SHUTDOWN, # FIXME 'Crashed': power_state.CRASHED} +from nova import flags + +FLAGS = flags.FLAGS + +#FIXME: replace with proper target discovery +flags.DEFINE_string('target_host', None, 'iSCSI Target Host') +flags.DEFINE_string('target_port', '3260', 'iSCSI Target Port, 3260 Default') +flags.DEFINE_string('iqn_prefix', 'iqn.2010-10.org.openstack', 'IQN Prefix') + class Instance(object): @@ -101,3 +113,73 @@ class User(object): @classmethod def get_secret(self, user): return user.secret + + +class Volume(object): + + @classmethod + def parse_volume_info(self, device_path, mountpoint): + # Because XCP/XS want a device number instead of a mountpoint + device_number = Volume.mountpoint_to_number(mountpoint) + volume_id = Volume.get_volume_id(device_path) + target_host = Volume.get_target_host(device_path) + target_port = Volume.get_target_port(device_path) + target_iqn = Volume.get_iqn(device_path) + + if (device_number < 0) or \ + (volume_id is None) or \ + (target_host is None) or \ + (target_iqn is None): + raise Exception('Unable to obtain target information %s, %s' % + (device_path, mountpoint)) + + volume_info = {} + volume_info['deviceNumber'] = device_number + volume_info['volumeId'] = volume_id + volume_info['targetHost'] = target_host + volume_info['targetPort'] = target_port + volume_info['targeIQN'] = target_iqn + return volume_info + + @classmethod + def mountpoint_to_number(self, mountpoint): + if mountpoint.startswith('/dev/'): + mountpoint = mountpoint[5:] + if re.match('^[hs]d[a-p]$', mountpoint): + return (ord(mountpoint[2:3]) - ord('a')) + elif re.match('^vd[a-p]$', mountpoint): + return (ord(mountpoint[2:3]) - ord('a')) + elif re.match('^[0-9]+$', mountpoint): + return string.atoi(mountpoint, 10) + else: + logging.warn('Mountpoint cannot be translated: %s', mountpoint) + return -1 + + @classmethod + def get_volume_id(self, n): + # FIXME: n must contain at least the volume_id + # /vol- is for remote volumes + # -vol- is for local volumes + # see compute/manager->setup_compute_volume + volume_id = n[n.find('/vol-') + 1:] + if volume_id == n: + volume_id = n[n.find('-vol-') + 1:].replace('--', '-') + return volume_id + + @classmethod + def get_target_host(self, n): + # FIXME: if n is none fall back on flags + if n is None or FLAGS.target_host: + return FLAGS.target_host + + @classmethod + def get_target_port(self, n): + # FIXME: if n is none fall back on flags + return FLAGS.target_port + + @classmethod + def get_iqn(self, n): + # FIXME: n must contain at least the volume_id + volume_id = Volume.get_volume_id(n) + if n is None or FLAGS.iqn_prefix: + return '%s:%s' % (FLAGS.iqn_prefix, volume_id) diff --git a/nova/virt/xenapi/volume_utils.py b/nova/virt/xenapi/volume_utils.py index b982ac124b..3b3e8894c4 100644 --- a/nova/virt/xenapi/volume_utils.py +++ b/nova/virt/xenapi/volume_utils.py @@ -20,20 +20,10 @@ and storage repositories """ import logging -import re -import string from twisted.internet import defer from nova import utils -from nova import flags - -FLAGS = flags.FLAGS - -#FIXME: replace with proper target discovery -flags.DEFINE_string('target_host', None, 'iSCSI Target Host') -flags.DEFINE_string('target_port', '3260', 'iSCSI Target Port, 3260 Default') -flags.DEFINE_string('iqn_prefix', 'iqn.2010-10.org.openstack', 'IQN Prefix') class VolumeHelper(): @@ -141,70 +131,3 @@ class VolumeHelper(): vdi_rec['location'], vdi_rec['xenstore_data'], vdi_rec['sm_config']) - - @classmethod - def parse_volume_info(self, device_path, mountpoint): - # Because XCP/XS want a device number instead of a mountpoint - device_number = VolumeHelper.mountpoint_to_number(mountpoint) - volume_id = _get_volume_id(device_path) - target_host = _get_target_host(device_path) - target_port = _get_target_port(device_path) - target_iqn = _get_iqn(device_path) - - if (device_number < 0) or \ - (volume_id is None) or \ - (target_host is None) or \ - (target_iqn is None): - raise Exception('Unable to obtain target information %s, %s' % - (device_path, mountpoint)) - - volume_info = {} - volume_info['deviceNumber'] = device_number - volume_info['volumeId'] = volume_id - volume_info['targetHost'] = target_host - volume_info['targetPort'] = target_port - volume_info['targeIQN'] = target_iqn - return volume_info - - @classmethod - def mountpoint_to_number(self, mountpoint): - if mountpoint.startswith('/dev/'): - mountpoint = mountpoint[5:] - if re.match('^[hs]d[a-p]$', mountpoint): - return (ord(mountpoint[2:3]) - ord('a')) - elif re.match('^vd[a-p]$', mountpoint): - return (ord(mountpoint[2:3]) - ord('a')) - elif re.match('^[0-9]+$', mountpoint): - return string.atoi(mountpoint, 10) - else: - logging.warn('Mountpoint cannot be translated: %s', mountpoint) - return -1 - - -def _get_volume_id(n): - # FIXME: n must contain at least the volume_id - # /vol- is for remote volumes - # -vol- is for local volumes - # see compute/manager->setup_compute_volume - volume_id = n[n.find('/vol-') + 1:] - if volume_id == n: - volume_id = n[n.find('-vol-') + 1:].replace('--', '-') - return volume_id - - -def _get_target_host(n): - # FIXME: if n is none fall back on flags - if n is None or FLAGS.target_host: - return FLAGS.target_host - - -def _get_target_port(n): - # FIXME: if n is none fall back on flags - return FLAGS.target_port - - -def _get_iqn(n): - # FIXME: n must contain at least the volume_id - volume_id = _get_volume_id(n) - if n is None or FLAGS.iqn_prefix: - return '%s:%s' % (FLAGS.iqn_prefix, volume_id) diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xenapi/volumeops.py index d5c3092403..ec43433294 100644 --- a/nova/virt/xenapi/volumeops.py +++ b/nova/virt/xenapi/volumeops.py @@ -24,6 +24,8 @@ from twisted.internet import defer from volume_utils import VolumeHelper from vm_utils import VMHelper +from novadeps import Volume + class VolumeOps(object): def __init__(self, session): @@ -38,9 +40,9 @@ class VolumeOps(object): # NOTE: No Resource Pool concept so far logging.debug("Attach_volume: %s, %s, %s", instance_name, device_path, mountpoint) - vol_rec = VolumeHelper.parse_volume_info(device_path, mountpoint) # Create the iSCSI SR, and the PDB through which hosts access SRs. # But first, retrieve target info, like Host, IQN, LUN and SCSIID + vol_rec = Volume.parse_volume_info(device_path, mountpoint) label = 'SR-%s' % vol_rec['volumeId'] description = 'Disk-for:%s' % instance_name # Create SR @@ -95,7 +97,7 @@ class VolumeOps(object): raise Exception('Instance %s does not exist' % instance_name) # Detach VBD from VM logging.debug("Detach_volume: %s, %s", instance_name, mountpoint) - device_number = VolumeHelper.mountpoint_to_number(mountpoint) + device_number = Volume.mountpoint_to_number(mountpoint) try: vbd_ref = yield VMHelper.find_vbd_by_number(self._session, vm_ref, device_number) From 364b4204ba9e4d04b0d0293a9c5fd62320ae3a63 Mon Sep 17 00:00:00 2001 From: Rick Clark Date: Wed, 1 Dec 2010 16:23:34 -0600 Subject: [PATCH 045/229] Changed null_kernel flag from aki-00000000 to nokernel --- nova/flags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/flags.py b/nova/flags.py index a610332014..be81fd7ed5 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -236,7 +236,7 @@ DEFINE_string('default_ramdisk', 'ari-11111', 'default ramdisk to use, testing only') DEFINE_string('default_instance_type', 'm1.small', 'default instance type to use, testing only') -DEFINE_string('null_kernel', 'aki-00000000', +DEFINE_string('null_kernel', 'nokernel', 'kernel image that indicates not to use a kernel,' ' but to use a raw disk image instead') From 3a96066987e42382e1ed75735473971ea90a76a1 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 1 Dec 2010 22:43:46 +0000 Subject: [PATCH 046/229] 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 1a9f93a3b1..f3eb8351a7 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 From fd44f9d2ec1d101960642a68d45bffc9c37f0d7f Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Thu, 2 Dec 2010 12:13:56 +0000 Subject: [PATCH 047/229] moved flags into xenapi/novadeps.py --- nova/virt/xenapi/novadeps.py | 40 ++++++++++++++++++++++++++++++++++++ nova/virt/xenapi_conn.py | 31 ++++++---------------------- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/nova/virt/xenapi/novadeps.py b/nova/virt/xenapi/novadeps.py index ba62468fbc..985998486d 100644 --- a/nova/virt/xenapi/novadeps.py +++ b/nova/virt/xenapi/novadeps.py @@ -33,6 +33,46 @@ XENAPI_POWER_STATE = { 'Crashed': power_state.CRASHED} +flags.DEFINE_string('xenapi_connection_url', + None, + 'URL for connection to XenServer/Xen Cloud Platform.' + ' Required if connection_type=xenapi.') +flags.DEFINE_string('xenapi_connection_username', + 'root', + 'Username for connection to XenServer/Xen Cloud Platform.' + ' Used only if connection_type=xenapi.') +flags.DEFINE_string('xenapi_connection_password', + None, + 'Password for connection to XenServer/Xen Cloud Platform.' + ' Used only if connection_type=xenapi.') +flags.DEFINE_float('xenapi_task_poll_interval', + 0.5, + 'The interval used for polling of remote tasks ' + '(Async.VM.start, etc). Used only if ' + 'connection_type=xenapi.') + + +class Configuration(object): + def __init__(self): + self._flags = flags.FLAGS + + @property + def xenapi_connection_url(self): + return self._flags.xenapi_connection_url + + @property + def xenapi_connection_username(self): + return self._flags.xenapi_connection_username + + @property + def xenapi_connection_password(self): + return self._flags.xenapi_connection_password + + @property + def xenapi_task_poll_interval(self): + return self._flags.xenapi_task_poll_interval + + class Instance(object): @classmethod diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 0a73b47741..51091ab19e 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -53,33 +53,14 @@ import xmlrpclib from twisted.internet import defer from twisted.internet import reactor -from nova import flags from nova import utils from xenapi.vmops import VMOps from xenapi.volumeops import VolumeOps +from xenapi.novadeps import Configuration XenAPI = None - - -FLAGS = flags.FLAGS -flags.DEFINE_string('xenapi_connection_url', - None, - 'URL for connection to XenServer/Xen Cloud Platform.' - ' Required if connection_type=xenapi.') -flags.DEFINE_string('xenapi_connection_username', - 'root', - 'Username for connection to XenServer/Xen Cloud Platform.' - ' Used only if connection_type=xenapi.') -flags.DEFINE_string('xenapi_connection_password', - None, - 'Password for connection to XenServer/Xen Cloud Platform.' - ' Used only if connection_type=xenapi.') -flags.DEFINE_float('xenapi_task_poll_interval', - 0.5, - 'The interval used for polling of remote tasks ' - '(Async.VM.start, etc). Used only if ' - 'connection_type=xenapi.') +Config = Configuration() def get_connection(_): @@ -90,9 +71,9 @@ def get_connection(_): global XenAPI if XenAPI is None: XenAPI = __import__('XenAPI') - url = FLAGS.xenapi_connection_url - username = FLAGS.xenapi_connection_username - password = FLAGS.xenapi_connection_password + url = Config.xenapi_connection_url + username = Config.xenapi_connection_username + password = Config.xenapi_connection_password if not url or password is None: raise Exception('Must specify xenapi_connection_url, ' 'xenapi_connection_username (optionally), and ' @@ -177,7 +158,7 @@ class XenAPISession(object): #logging.debug('Polling task %s...', task) status = self._session.xenapi.task.get_status(task) if status == 'pending': - reactor.callLater(FLAGS.xenapi_task_poll_interval, + reactor.callLater(Config.xenapi_task_poll_interval(), self._poll_task, task, deferred) elif status == 'success': result = self._session.xenapi.task.get_result(task) From b684bc26fc7c7f41cf90e0294af35b2bda243733 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Thu, 2 Dec 2010 12:36:05 +0000 Subject: [PATCH 048/229] typo fix --- nova/virt/xenapi_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 51091ab19e..948fade7e9 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -158,7 +158,7 @@ class XenAPISession(object): #logging.debug('Polling task %s...', task) status = self._session.xenapi.task.get_status(task) if status == 'pending': - reactor.callLater(Config.xenapi_task_poll_interval(), + reactor.callLater(Config.xenapi_task_poll_interval, self._poll_task, task, deferred) elif status == 'success': result = self._session.xenapi.task.get_result(task) From e4cfd7f3fe7d3c50d65c61abf21bf998fde85147 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Thu, 2 Dec 2010 14:09:23 +0000 Subject: [PATCH 049/229] minor refactoring after merge --- nova/virt/xenapi/novadeps.py | 28 ++++++++++++++++++++-------- nova/virt/xenapi_conn.py | 5 ++++- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/nova/virt/xenapi/novadeps.py b/nova/virt/xenapi/novadeps.py index b4802764e9..aa35351626 100644 --- a/nova/virt/xenapi/novadeps.py +++ b/nova/virt/xenapi/novadeps.py @@ -36,8 +36,6 @@ XENAPI_POWER_STATE = { 'Suspended': power_state.SHUTDOWN, # FIXME 'Crashed': power_state.CRASHED} -FLAGS = flags.FLAGS - flags.DEFINE_string('xenapi_connection_url', None, 'URL for connection to XenServer/Xen Cloud Platform.' @@ -81,6 +79,21 @@ class Configuration(object): def xenapi_task_poll_interval(self): return self._flags.xenapi_task_poll_interval + @property + def target_host(self): + return self._flags.target_host + + @property + def target_port(self): + return self._flags.target_port + + @property + def iqn_prefix(self): + return self._flags.iqn_prefix + + +config = Configuration() + class Instance(object): @@ -206,18 +219,17 @@ class Volume(object): @classmethod def get_target_host(self, n): # FIXME: if n is none fall back on flags - if n is None or FLAGS.target_host: - return FLAGS.target_host + if n is None or config.target_host: + return config.target_host @classmethod def get_target_port(self, n): # FIXME: if n is none fall back on flags - return FLAGS.target_port + return config.target_port @classmethod def get_iqn(self, n): # FIXME: n must contain at least the volume_id volume_id = Volume.get_volume_id(n) - if n is None or FLAGS.iqn_prefix: - return '%s:%s' % (FLAGS.iqn_prefix, volume_id) - + if n is None or config.iqn_prefix: + return '%s:%s' % (config.iqn_prefix, volume_id) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 948fade7e9..d3f66b12cd 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -44,7 +44,10 @@ reactor thread if the VM.get_by_name_label or VM.get_record calls block. :xenapi_task_poll_interval: The interval (seconds) used for polling of remote tasks (Async.VM.start, etc) (default: 0.5). - +:target_host: the iSCSI Target Host IP address, i.e. the IP + address for the nova-volume host +:target_port: iSCSI Target Port, 3260 Default +:iqn_prefix: IQN Prefix, e.g. 'iqn.2010-10.org.openstack' """ import logging From 1e050bb4a8eeb65a7ac25a9fb90493567b5b07f4 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Thu, 2 Dec 2010 15:18:45 +0100 Subject: [PATCH 050/229] 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 eb7c6b87b5..62eec8353f 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): From 26571952bb8f1015b11d6b9514d232ad8a20d837 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Thu, 2 Dec 2010 10:21:43 -0800 Subject: [PATCH 051/229] 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 b8f15019f4..0000000000 --- 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 e50906ae17..161d2d038d 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 11170bbf5e..d34dd78fb0 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 929342a1e9..da01ca61ad 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']}}) From a8df0a7d6c2de55d7906fa311f79887ccf575508 Mon Sep 17 00:00:00 2001 From: Anne Gentle Date: Thu, 2 Dec 2010 13:48:39 -0600 Subject: [PATCH 052/229] Fixing single node install doc --- doc/source/adminguide/multi.node.install.rst | 13 +++++------- doc/source/adminguide/single.node.install.rst | 20 +++++++++++++++---- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/doc/source/adminguide/multi.node.install.rst b/doc/source/adminguide/multi.node.install.rst index dcceb539bf..1eed30c5b3 100644 --- a/doc/source/adminguide/multi.node.install.rst +++ b/doc/source/adminguide/multi.node.install.rst @@ -19,7 +19,7 @@ Installing Nova on Multiple Servers =================================== When you move beyond evaluating the technology and into building an actual -production environemnt, you will need to know how to configure your datacenter +production environment, you will need to know how to configure your datacenter and how to deploy components across your clusters. This guide should help you through that process. @@ -161,7 +161,7 @@ Step 3 Setup the sql db GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION; SET PASSWORD FOR 'root'@'%' = PASSWORD('nova'); -7. branch and install Nova +7. Branch and install Nova :: @@ -186,9 +186,7 @@ Step 4 Setup Nova environment Note: The nova-manage service assumes that the first IP address is your network (like 192.168.0.0), that the 2nd IP is your gateway (192.168.0.1), and that the broadcast is the very last IP in the range you defined (192.168.0.255). If this is not the case you will need to manually edit the sql db 'networks' table.o. -On running this command, entries are made in the 'networks' and 'fixed_ips' table. However, one of the networks listed in the 'networks' table needs to be marked as bridge in order for the code to know that a bridge exists. We ended up doing this manually, (update query fired directly in the DB). Is there a better way to mark a network as bridged? - -Update: This has been resolved w.e.f 27/10. network is marked as bridged automatically based on the type of n/w manager selected. +On running this command, entries are made in the 'networks' and 'fixed_ips' table. However, one of the networks listed in the 'networks' table needs to be marked as bridge in order for the code to know that a bridge exists. The Network is marked as bridged automatically based on the type of network manager selected. More networking details to create a network bridge for flat network ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -233,7 +231,6 @@ unzip them in your home directory, and add them to your environment:: echo ". creds/novarc" >> ~/.bashrc ~/.bashrc - Step 6 Restart all relevant services ------------------------------------ @@ -249,8 +246,8 @@ Restart relevant nova services:: .. todo:: do we still need the content below? -Bare-metal Provisioning ------------------------ +Bare-metal Provisioning Notes +----------------------------- To install the base operating system you can use PXE booting. diff --git a/doc/source/adminguide/single.node.install.rst b/doc/source/adminguide/single.node.install.rst index 27597962aa..f6b2290bcf 100644 --- a/doc/source/adminguide/single.node.install.rst +++ b/doc/source/adminguide/single.node.install.rst @@ -63,8 +63,20 @@ You see an access key and a secret key export, such as these made-up ones::: export EC2_ACCESS_KEY=4e6498a2-blah-blah-blah-17d1333t97fd export EC2_SECRET_KEY=0a520304-blah-blah-blah-340sp34k05bbe9a7 +Step 5: Create the network +-------------------------- -Step 5: Create a project with the user you created +Type or copy/paste in the following line to create a network prior to creating a project. + +:: + + sudo nova-manage network create 10.0.0.0/8 1 64 + +For this command, the IP address is the cidr notation for your netmask, such as 192.168.1.0/24. The value 1 is the total number of networks you want made, and the 64 value is the total number of ips in all networks. + +After running this command, entries are made in the 'networks' and 'fixed_ips' table in the database. + +Step 6: Create a project with the user you created -------------------------------------------------- Type or copy/paste in the following line to create a project named IRT (for Ice Road Truckers, of course) with the newly-created user named anne. @@ -94,7 +106,7 @@ Type or copy/paste in the following line to create a project named IRT (for Ice Data Base Updated -Step 6: Unzip the nova.zip +Step 7: Unzip the nova.zip -------------------------- You should have a nova.zip file in your current working directory. Unzip it with this command: @@ -116,7 +128,7 @@ You'll see these files extract. extracting: cacert.pem -Step 7: Source the rc file +Step 8: Source the rc file -------------------------- Type or copy/paste the following to source the novarc file in your current working directory. @@ -125,7 +137,7 @@ Type or copy/paste the following to source the novarc file in your current worki . novarc -Step 8: Pat yourself on the back :) +Step 9: Pat yourself on the back :) ----------------------------------- Congratulations, your cloud is up and running, you’ve created an admin user, retrieved the user's credentials and put them in your environment. From 47b47bc4ae34f90a6d1c59718b5ee759fb7c7327 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Thu, 2 Dec 2010 15:26:14 -0800 Subject: [PATCH 053/229] 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 161d2d038d..7978e08a04 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 d34dd78fb0..1d93f783cf 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 da01ca61ad..457d6e27fc 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 2eee4e5062..aebb3d1b54 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() From da010f311c07ee31d7d00ceb48d0f8656f1825ca Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Fri, 3 Dec 2010 00:01:21 +0000 Subject: [PATCH 054/229] * Removes unused schema * Removes MUST uid from novaUser * Changes isAdmin to isNovaAdmin * Adds two new configuration options: ** ldap_user_id_attribute, with a default of uid ** ldap_user_name_attribute, with a default of cn * ldapdriver.py has been modified to use these changes Rationale: Removing uid from novaUser: Requiring uid makes the schema very posix specific. Other schemas don't use uid for identifiers at all. This change makes the schema more interoperable. Changing isAdmin to isNovaAdmin: This attribute is too generic. It doesn't describe what the user is an admin of, and in a pre-existing directory is out of place. This change is to make the attribute more specific to the software. Adding config options for id and name: This is another interoperability change. This change makes the driver more compatible with directories like AD, where sAMAccountName is used instead of uid. Also, some directory admins prefer to use displayName rather than CN for full names of users. --- nova/auth/ldapdriver.py | 21 ++++++++++++--------- nova/auth/nova_openldap.schema | 26 +++----------------------- nova/auth/nova_sun.schema | 6 ++---- nova/auth/openssh-lpk_openldap.schema | 19 ------------------- nova/auth/openssh-lpk_sun.schema | 10 ---------- 5 files changed, 17 insertions(+), 65 deletions(-) delete mode 100644 nova/auth/openssh-lpk_openldap.schema delete mode 100644 nova/auth/openssh-lpk_sun.schema diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index ceade1d65c..e4c36c28db 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -37,6 +37,8 @@ flags.DEFINE_string('ldap_url', 'ldap://localhost', flags.DEFINE_string('ldap_password', 'changeme', 'LDAP password') flags.DEFINE_string('ldap_user_dn', 'cn=Manager,dc=example,dc=com', 'DN of admin user') +flags.DEFINE_string('ldap_user_id_attribute', 'uid', 'Attribute to use as id') +flags.DEFINE_string('ldap_user_name_attribute', 'cn', 'Attribute to use as name') flags.DEFINE_string('ldap_user_unit', 'Users', 'OID for Users') flags.DEFINE_string('ldap_user_subtree', 'ou=Users,dc=example,dc=com', 'OU for Users') @@ -131,12 +133,12 @@ class LdapDriver(object): 'inetOrgPerson', 'novaUser']), ('ou', [FLAGS.ldap_user_unit]), - ('uid', [name]), + (FLAGS.ldap_user_id_attribute, [name]), ('sn', [name]), - ('cn', [name]), + (FLAGS.ldap_user_name_attribute, [name]), ('secretKey', [secret_key]), ('accessKey', [access_key]), - ('isAdmin', [str(is_admin).upper()]), + ('isNovaAdmin', [str(is_admin).upper()]), ] self.conn.add_s(self.__uid_to_dn(name), attr) return self.__to_user(dict(attr)) @@ -274,7 +276,7 @@ class LdapDriver(object): if secret_key: attr.append((self.ldap.MOD_REPLACE, 'secretKey', secret_key)) if admin is not None: - attr.append((self.ldap.MOD_REPLACE, 'isAdmin', str(admin).upper())) + attr.append((self.ldap.MOD_REPLACE, 'isNovaAdmin', str(admin).upper())) self.conn.modify_s(self.__uid_to_dn(uid), attr) def __user_exists(self, uid): @@ -450,11 +452,11 @@ class LdapDriver(object): if attr == None: return None return { - 'id': attr['uid'][0], - 'name': attr['cn'][0], + 'id': attr[FLAGS.ldap_user_id_attribute][0], + 'name': attr[FLAGS.ldap_user_name_attribute][0], 'access': attr['accessKey'][0], 'secret': attr['secretKey'][0], - 'admin': (attr['isAdmin'][0] == 'TRUE')} + 'admin': (attr['isNovaAdmin'][0] == 'TRUE')} def __to_project(self, attr): """Convert ldap attributes to Project object""" @@ -474,9 +476,10 @@ class LdapDriver(object): return dn.split(',')[0].split('=')[1] @staticmethod - def __uid_to_dn(dn): + def __uid_to_dn(uid): """Convert uid to dn""" - return 'uid=%s,%s' % (dn, FLAGS.ldap_user_subtree) + return FLAGS.ldap_user_id_attribute + '=%s,%s' \ + % (uid, FLAGS.ldap_user_subtree) class FakeLdapDriver(LdapDriver): diff --git a/nova/auth/nova_openldap.schema b/nova/auth/nova_openldap.schema index 4047361de4..9e528f58bf 100644 --- a/nova/auth/nova_openldap.schema +++ b/nova/auth/nova_openldap.schema @@ -30,20 +30,10 @@ attributetype ( SINGLE-VALUE ) -attributetype ( - novaAttrs:3 - NAME 'keyFingerprint' - DESC 'Fingerprint of private key' - EQUALITY caseIgnoreMatch - SUBSTR caseIgnoreSubstringsMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 - SINGLE-VALUE - ) - attributetype ( novaAttrs:4 - NAME 'isAdmin' - DESC 'Is user an administrator?' + NAME 'isNovaAdmin' + DESC 'Is user an nova administrator?' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE @@ -61,17 +51,7 @@ objectClass ( NAME 'novaUser' DESC 'access and secret keys' AUXILIARY - MUST ( uid ) - MAY ( accessKey $ secretKey $ isAdmin ) - ) - -objectClass ( - novaOCs:2 - NAME 'novaKeyPair' - DESC 'Key pair for User' - SUP top - STRUCTURAL - MUST ( cn $ sshPublicKey $ keyFingerprint ) + MAY ( accessKey $ secretKey $ isNovaAdmin ) ) objectClass ( diff --git a/nova/auth/nova_sun.schema b/nova/auth/nova_sun.schema index e925e05e48..decf10f06d 100644 --- a/nova/auth/nova_sun.schema +++ b/nova/auth/nova_sun.schema @@ -8,9 +8,7 @@ dn: cn=schema attributeTypes: ( 1.3.6.1.3.1.666.666.3.1 NAME 'accessKey' DESC 'Key for accessing data' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.3.1.666.666.3.2 NAME 'secretKey' DESC 'Secret key' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.3.1.666.666.3.3 NAME 'keyFingerprint' DESC 'Fingerprint of private key' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE) -attributeTypes: ( 1.3.6.1.3.1.666.666.3.4 NAME 'isAdmin' DESC 'Is user an administrator?' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) +attributeTypes: ( 1.3.6.1.3.1.666.666.3.4 NAME 'isNovaAdmin' DESC 'Is user a nova administrator?' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.3.1.666.666.3.5 NAME 'projectManager' DESC 'Project Managers of a project' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 ) -objectClasses: ( 1.3.6.1.3.1.666.666.4.1 NAME 'novaUser' DESC 'access and secret keys' SUP top AUXILIARY MUST ( uid ) MAY ( accessKey $ secretKey $ isAdmin ) ) -objectClasses: ( 1.3.6.1.3.1.666.666.4.2 NAME 'novaKeyPair' DESC 'Key pair for User' SUP top STRUCTURAL MUST ( cn $ sshPublicKey $ keyFingerprint ) ) +objectClasses: ( 1.3.6.1.3.1.666.666.4.1 NAME 'novaUser' DESC 'access and secret keys' SUP top AUXILIARY MAY ( accessKey $ secretKey $ isNovaAdmin ) ) objectClasses: ( 1.3.6.1.3.1.666.666.4.3 NAME 'novaProject' DESC 'Container for project' SUP groupOfNames STRUCTURAL MUST ( cn $ projectManager ) ) diff --git a/nova/auth/openssh-lpk_openldap.schema b/nova/auth/openssh-lpk_openldap.schema deleted file mode 100644 index 93351da6dd..0000000000 --- a/nova/auth/openssh-lpk_openldap.schema +++ /dev/null @@ -1,19 +0,0 @@ -# -# LDAP Public Key Patch schema for use with openssh-ldappubkey -# Author: Eric AUGE -# -# Based on the proposal of : Mark Ruijter -# - - -# octetString SYNTAX -attributetype ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey' - DESC 'MANDATORY: OpenSSH Public key' - EQUALITY octetStringMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 ) - -# printableString SYNTAX yes|no -objectclass ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY - DESC 'MANDATORY: OpenSSH LPK objectclass' - MAY ( sshPublicKey $ uid ) - ) diff --git a/nova/auth/openssh-lpk_sun.schema b/nova/auth/openssh-lpk_sun.schema deleted file mode 100644 index 5f52db3b65..0000000000 --- a/nova/auth/openssh-lpk_sun.schema +++ /dev/null @@ -1,10 +0,0 @@ -# -# LDAP Public Key Patch schema for use with openssh-ldappubkey -# Author: Eric AUGE -# -# Schema for Sun Directory Server. -# Based on the original schema, modified by Stefan Fischer. -# -dn: cn=schema -attributeTypes: ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey' DESC 'MANDATORY: OpenSSH Public key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 ) -objectClasses: ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY DESC 'MANDATORY: OpenSSH LPK objectclass' MAY ( sshPublicKey $ uid ) ) From 108bab90cb70798151b8e6a09d2176a3eb120380 Mon Sep 17 00:00:00 2001 From: Ryan Lucio Date: Thu, 2 Dec 2010 17:01:44 -0800 Subject: [PATCH 055/229] 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 fe0a9a9216..18ba80caf6 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)) From 4203aa1060e5a97bed86d2e201c4c2443ef7e042 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Fri, 3 Dec 2010 12:21:18 -0800 Subject: [PATCH 056/229] 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 7978e08a04..4eef5e1efb 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 a9da148677..b644876b0c 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 7b2b681616..11c3bd6dfb 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 457d6e27fc..995bed91bc 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 dd96490545..ef58f3490d 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 1f94feb08b..c6578023d9 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 7c0343942b..c3f129a32d 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 46b9c53487..8444b6fce6 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 a55449739e..6f3ef96cbb 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): From 4f2a8c5398d4d4848f441e366e8bcc5e97a0b34f Mon Sep 17 00:00:00 2001 From: Ryan Lucio Date: Fri, 3 Dec 2010 13:50:30 -0800 Subject: [PATCH 057/229] 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 dd96490545..2dc1402742 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 18ba80caf6..fe0a9a9216 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 9b0cdcc50d..b445932218 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 From 1637de18a86712c52d89441c154a8e9aae6fb503 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Mon, 6 Dec 2010 12:42:34 +0000 Subject: [PATCH 058/229] pylint and pep8 fixes --- nova/virt/xenapi/network_utils.py | 13 ++++--- nova/virt/xenapi/novadeps.py | 59 ++++++++++++++++++++++--------- nova/virt/xenapi/vm_utils.py | 30 ++++++++++------ nova/virt/xenapi/vmops.py | 18 +++++++--- nova/virt/xenapi/volumeops.py | 2 ++ nova/virt/xenapi_conn.py | 15 +++++++- 6 files changed, 101 insertions(+), 36 deletions(-) diff --git a/nova/virt/xenapi/network_utils.py b/nova/virt/xenapi/network_utils.py index b58b9159c2..8cb4cce3a7 100644 --- a/nova/virt/xenapi/network_utils.py +++ b/nova/virt/xenapi/network_utils.py @@ -15,20 +15,25 @@ # under the License. """ -Helper methods for operations related to the management of network records and -their attributes like bridges, PIFs, QoS, as well as their lookup functions. +Helper methods for operations related to the management of network +records and their attributes like bridges, PIFs, QoS, as well as +their lookup functions. """ from twisted.internet import defer class NetworkHelper(): - def __init__(self, session): + """ + The class that wraps the helper methods together. + """ + def __init__(self): return @classmethod @defer.inlineCallbacks - def find_network_with_bridge(self, session, bridge): + def find_network_with_bridge(cls, session, bridge): + """ Return the network on which the bridge is attached, if found """ expr = 'field "bridge" = "%s"' % bridge networks = yield session.call_xenapi('network.get_all_records_where', expr) diff --git a/nova/virt/xenapi/novadeps.py b/nova/virt/xenapi/novadeps.py index 985998486d..a68fd8e774 100644 --- a/nova/virt/xenapi/novadeps.py +++ b/nova/virt/xenapi/novadeps.py @@ -14,10 +14,14 @@ # License for the specific language governing permissions and limitations # under the License. +""" +It captures all the inner details of Nova classes and avoid their exposure +to the implementation of the XenAPI module. One benefit of this, is to avoid +sprawl of code changes +""" + from nova import db from nova import flags -from nova import process -from nova import utils from nova import context from nova.compute import power_state @@ -53,91 +57,114 @@ flags.DEFINE_float('xenapi_task_poll_interval', class Configuration(object): + """ Wraps Configuration details into common class """ def __init__(self): self._flags = flags.FLAGS @property def xenapi_connection_url(self): + """ Return the connection url """ return self._flags.xenapi_connection_url @property def xenapi_connection_username(self): + """ Return the username used for the connection """ return self._flags.xenapi_connection_username @property def xenapi_connection_password(self): + """ Return the password used for the connection """ return self._flags.xenapi_connection_password @property def xenapi_task_poll_interval(self): + """ Return the poll interval for the connection """ return self._flags.xenapi_task_poll_interval class Instance(object): + """ Wraps up instance specifics """ @classmethod - def get_name(self, instance): + def get_name(cls, instance): + """ The name of the instance """ return instance.name @classmethod - def get_type(self, instance): + def get_type(cls, instance): + """ The type of the instance """ return instance_types.INSTANCE_TYPES[instance.instance_type] @classmethod - def get_project(self, instance): + def get_project(cls, instance): + """ The project the instance belongs """ return AuthManager().get_project(instance.project_id) @classmethod - def get_project_id(self, instance): + def get_project_id(cls, instance): + """ The id of the project the instance belongs """ return instance.project_id @classmethod - def get_image_id(self, instance): + def get_image_id(cls, instance): + """ The instance's image id """ return instance.image_id @classmethod - def get_kernel_id(self, instance): + def get_kernel_id(cls, instance): + """ The instance's kernel id """ return instance.kernel_id @classmethod - def get_ramdisk_id(self, instance): + def get_ramdisk_id(cls, instance): + """ The instance's ramdisk id """ return instance.ramdisk_id @classmethod - def get_network(self, instance): + def get_network(cls, instance): + """ The network the instance is connected to """ # TODO: is ge_admin_context the right context to retrieve? return db.project_get_network(context.get_admin_context(), instance.project_id) @classmethod - def get_mac(self, instance): + def get_mac(cls, instance): + """ The instance's MAC address """ return instance.mac_address @classmethod - def get_user(self, instance): + def get_user(cls, instance): + """ The owner of the instance """ return AuthManager().get_user(instance.user_id) class Network(object): + """ Wraps up network specifics """ @classmethod - def get_bridge(self, network): + def get_bridge(cls, network): + """ the bridge for the network """ return network.bridge class Image(object): + """ Wraps up image specifics """ @classmethod - def get_url(self, image): + def get_url(cls, image): + """ the url to get the image from """ return images.image_url(image) class User(object): + """ Wraps up user specifics """ @classmethod - def get_access(self, user, project): + def get_access(cls, user, project): + """ access key """ return AuthManager().get_access_key(user, project) @classmethod - def get_secret(self, user): + def get_secret(cls, user): + """ access secret """ return user.secret diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index b68df27919..002f00c03f 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -31,12 +31,15 @@ from novadeps import User class VMHelper(): - def __init__(self, session): + """ + The class that wraps the helper methods together. + """ + def __init__(self): return @classmethod @defer.inlineCallbacks - def create_vm(self, session, instance, kernel, ramdisk): + def create_vm(cls, session, instance, kernel, ramdisk): """Create a VM record. Returns a Deferred that gives the new VM reference.""" @@ -80,7 +83,7 @@ class VMHelper(): @classmethod @defer.inlineCallbacks - def create_vbd(self, session, vm_ref, vdi_ref, userdevice, bootable): + def create_vbd(cls, session, vm_ref, vdi_ref, userdevice, bootable): """Create a VBD record. Returns a Deferred that gives the new VBD reference.""" @@ -105,7 +108,7 @@ class VMHelper(): @classmethod @defer.inlineCallbacks - def create_vif(self, session, vm_ref, network_ref, mac_address): + def create_vif(cls, session, vm_ref, network_ref, mac_address): """Create a VIF record. Returns a Deferred that gives the new VIF reference.""" @@ -127,7 +130,7 @@ class VMHelper(): @classmethod @defer.inlineCallbacks - def fetch_image(self, session, image, user, project, use_sr): + def fetch_image(cls, session, image, user, project, use_sr): """use_sr: True to put the image as a VDI in an SR, False to place it on dom0's filesystem. The former is for VM disks, the latter for its kernel and ramdisk (if external kernels are being used). @@ -135,7 +138,7 @@ class VMHelper(): url = Image.get_url(image) access = User.get_access(user, project) - logging.debug("Asking xapi to fetch %s as %s" % (url, access)) + logging.debug("Asking xapi to fetch %s as %s", url, access) fn = use_sr and 'get_vdi' or 'get_kernel' args = {} args['src_url'] = url @@ -149,11 +152,13 @@ class VMHelper(): @classmethod @utils.deferredToThread - def lookup(self, session, i): + def lookup(cls, session, i): + """ Look the instance i up, and returns it if available """ return VMHelper.lookup_blocking(session, i) @classmethod - def lookup_blocking(self, session, i): + def lookup_blocking(cls, session, i): + """ Synchronous lookup """ vms = session.get_xenapi().VM.get_by_name_label(i) n = len(vms) if n == 0: @@ -165,11 +170,13 @@ class VMHelper(): @classmethod @utils.deferredToThread - def lookup_vm_vdis(self, session, vm): + def lookup_vm_vdis(cls, session, vm): + """ Look for the VDIs that are attached to the VM """ return VMHelper.lookup_vm_vdis_blocking(session, vm) @classmethod - def lookup_vm_vdis_blocking(self, session, vm): + def lookup_vm_vdis_blocking(cls, session, vm): + """ Synchronous lookup_vm_vdis """ # Firstly we get the VBDs, then the VDIs. # TODO: do we leave the read-only devices? vbds = session.get_xenapi().VM.get_VBDs(vm) @@ -180,7 +187,8 @@ class VMHelper(): vdi = session.get_xenapi().VBD.get_VDI(vbd) # Test valid VDI record = session.get_xenapi().VDI.get_record(vdi) - except Exception, exc: + logging.debug('VDI %s is still available', record['uuid']) + except XenAPI.Failure, exc: logging.warn(exc) else: vdis.append(vdi) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index d6ea5e7db7..7ea8be999b 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -31,15 +31,20 @@ from network_utils import NetworkHelper class VMOps(object): + """ + Management class for VM-related tasks + """ def __init__(self, session): self._session = session def list_instances(self): + """ List VM instances """ return [self._session.get_xenapi().VM.get_name_label(vm) \ for vm in self._session.get_xenapi().VM.get_all()] @defer.inlineCallbacks def spawn(self, instance): + """ Create VM instance """ vm = yield VMHelper.lookup(self._session, Instance.get_name(instance)) if vm is not None: raise Exception('Attempted to create non-unique name %s' % @@ -71,6 +76,7 @@ class VMOps(object): @defer.inlineCallbacks def reboot(self, instance): + """ Reboot VM instance """ instance_name = Instance.get_name(instance) vm = yield VMHelper.lookup(self._session, instance_name) if vm is None: @@ -80,6 +86,7 @@ class VMOps(object): @defer.inlineCallbacks def destroy(self, instance): + """ Destroy VM instance """ vm = yield VMHelper.lookup(self._session, Instance.get_name(instance)) if vm is None: # Don't complain, just return. This lets us clean up instances @@ -91,7 +98,7 @@ class VMOps(object): task = yield self._session.call_xenapi('Async.VM.hard_shutdown', vm) yield self._session.wait_for_task(task) - except Exception, exc: + except XenAPI.Failure, exc: logging.warn(exc) # Disk clean-up if vdis: @@ -100,15 +107,16 @@ class VMOps(object): task = yield self._session.call_xenapi('Async.VDI.destroy', vdi) yield self._session.wait_for_task(task) - except Exception, exc: + except XenAPI.Failure, exc: logging.warn(exc) try: task = yield self._session.call_xenapi('Async.VM.destroy', vm) yield self._session.wait_for_task(task) - except Exception, exc: + except XenAPI.Failure, exc: logging.warn(exc) def get_info(self, instance_id): + """ Return data about VM instance """ vm = VMHelper.lookup_blocking(self._session, instance_id) if vm is None: raise Exception('instance not present %s' % instance_id) @@ -120,4 +128,6 @@ class VMOps(object): 'cpu_time': 0} def get_console_output(self, instance): - return 'FAKE CONSOLE OUTPUT' + """ Return snapshot of console """ + # TODO: implement this to fix pylint! + return 'FAKE CONSOLE OUTPUT of instance' diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xenapi/volumeops.py index 23f79adf71..a4c7a38619 100644 --- a/nova/virt/xenapi/volumeops.py +++ b/nova/virt/xenapi/volumeops.py @@ -24,7 +24,9 @@ class VolumeOps(object): self._session = session def attach_volume(self, instance_name, device_path, mountpoint): + # FIXME: that's going to be sorted when iscsi-xenapi lands in branch return True def detach_volume(self, instance_name, mountpoint): + # FIXME: that's going to be sorted when iscsi-xenapi lands in branch return True diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 948fade7e9..e5e67128a3 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -83,47 +83,59 @@ def get_connection(_): class XenAPIConnection(object): + """ A connection to XenServer or Xen Cloud Platform """ def __init__(self, url, user, pw): session = XenAPISession(url, user, pw) self._vmops = VMOps(session) self._volumeops = VolumeOps(session) def list_instances(self): + """ List VM instances """ return self._vmops.list_instances() def spawn(self, instance): + """ Create VM instance """ self._vmops.spawn(instance) def reboot(self, instance): + """ Reboot VM instance """ self._vmops.reboot(instance) def destroy(self, instance): + """ Destroy VM instance """ self._vmops.destroy(instance) def get_info(self, instance_id): + """ Return data about VM instance """ return self._vmops.get_info(instance_id) def get_console_output(self, instance): + """ Return snapshot of console """ return self._vmops.get_console_output(instance) def attach_volume(self, instance_name, device_path, mountpoint): + """ Attach volume storage to VM instance """ return self._volumeops.attach_volume(instance_name, device_path, mountpoint) def detach_volume(self, instance_name, mountpoint): + """ Detach volume storage to VM instance """ return self._volumeops.detach_volume(instance_name, mountpoint) class XenAPISession(object): + """ The session to invoke XenAPI SDK calls """ def __init__(self, url, user, pw): self._session = XenAPI.Session(url) self._session.login_with_password(user, pw) def get_xenapi(self): + """ Return the xenapi object """ return self._session.xenapi def get_xenapi_host(self): + """ Return the xenapi host """ return self._session.xenapi.session.get_this_host(self._session.handle) @utils.deferredToThread @@ -170,12 +182,13 @@ class XenAPISession(object): error_info) deferred.errback(XenAPI.Failure(error_info)) #logging.debug('Polling task %s done.', task) - except Exception, exc: + except XenAPI.Failure, exc: logging.warn(exc) deferred.errback(exc) def _unwrap_plugin_exceptions(func, *args, **kwargs): + """ Parse exception details """ try: return func(*args, **kwargs) except XenAPI.Failure, exc: From f25a25d2693d603eb9a6f87d9629d53542219736 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Mon, 6 Dec 2010 15:53:35 +0000 Subject: [PATCH 059/229] moved XenAPI namespace definition into xenapi/__init__.py --- nova/virt/xenapi/__init__.py | 11 +++++++++++ nova/virt/xenapi/vm_utils.py | 1 + nova/virt/xenapi/vmops.py | 1 + nova/virt/xenapi_conn.py | 7 +------ 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/nova/virt/xenapi/__init__.py b/nova/virt/xenapi/__init__.py index 3d598c463c..ece4304071 100644 --- a/nova/virt/xenapi/__init__.py +++ b/nova/virt/xenapi/__init__.py @@ -13,3 +13,14 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + +""" +This is loaded late so that there's no need to install this library +when not using XenAPI +""" + +XenAPI = None +global XenAPI + +if XenAPI is None: + XenAPI = __import__('XenAPI') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 002f00c03f..52ab2901d4 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -20,6 +20,7 @@ their attributes like VDIs, VIFs, as well as their lookup functions. """ import logging +import XenAPI from twisted.internet import defer diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 7ea8be999b..3db86f1791 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -19,6 +19,7 @@ Management class for VM-related functions (spawn, reboot, etc). """ import logging +import XenAPI from twisted.internet import defer diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index e5e67128a3..2839a753c5 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -58,19 +58,14 @@ from nova import utils from xenapi.vmops import VMOps from xenapi.volumeops import VolumeOps from xenapi.novadeps import Configuration +from xenapi import XenAPI -XenAPI = None Config = Configuration() 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 = Config.xenapi_connection_url username = Config.xenapi_connection_username password = Config.xenapi_connection_password From ee71c0accbb540bcb9d08cdcdc8b659f29a0edd6 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Mon, 6 Dec 2010 19:06:32 +0000 Subject: [PATCH 060/229] added interim solution for target discovery. Now info can either be passed via flags or discovered via iscsiadm. Long term solution is to add a few more fields to the db in the iscsi_target table with the necessary info and modify the iscsi driver to set them --- nova/virt/xenapi/novadeps.py | 89 +++++++++++++++++++++++++++-------- nova/virt/xenapi/volumeops.py | 2 +- 2 files changed, 71 insertions(+), 20 deletions(-) diff --git a/nova/virt/xenapi/novadeps.py b/nova/virt/xenapi/novadeps.py index 65576019e8..66c8233b87 100644 --- a/nova/virt/xenapi/novadeps.py +++ b/nova/virt/xenapi/novadeps.py @@ -27,6 +27,9 @@ import string from nova import db from nova import flags from nova import context +from nova import process + +from twisted.internet import defer from nova.compute import power_state from nova.auth.manager import AuthManager @@ -193,15 +196,28 @@ class User(object): class Volume(object): + """ Wraps up volume specifics """ @classmethod - def parse_volume_info(self, device_path, mountpoint): - # Because XCP/XS want a device number instead of a mountpoint + @defer.inlineCallbacks + def parse_volume_info(cls, device_path, mountpoint): + """ + Parse device_path and mountpoint as they can be used by XenAPI. + In particular, the mountpoint (e.g. /dev/sdc) must be translated + into a numeric literal. + FIXME: As for device_path, currently cannot be used as it is, + because it does not contain target information. As for interim + solution, target details are passed either via Flags or obtained + by iscsiadm. Long-term solution is to add a few more fields to the + db in the iscsi_target table with the necessary info and modify + the iscsi driver to set them. + """ device_number = Volume.mountpoint_to_number(mountpoint) volume_id = Volume.get_volume_id(device_path) - target_host = Volume.get_target_host(device_path) - target_port = Volume.get_target_port(device_path) - target_iqn = Volume.get_iqn(device_path) + (iscsi_name, iscsi_portal) = yield Volume.get_target(volume_id) + target_host = Volume.get_target_host(iscsi_portal) + target_port = Volume.get_target_port(iscsi_portal) + target_iqn = Volume.get_iqn(iscsi_name, volume_id) if (device_number < 0) or \ (volume_id is None) or \ @@ -216,10 +232,11 @@ class Volume(object): volume_info['targetHost'] = target_host volume_info['targetPort'] = target_port volume_info['targeIQN'] = target_iqn - return volume_info + defer.returnValue(volume_info) @classmethod - def mountpoint_to_number(self, mountpoint): + def mountpoint_to_number(cls, mountpoint): + """ Translate a mountpoint like /dev/sdc into a numberic """ if mountpoint.startswith('/dev/'): mountpoint = mountpoint[5:] if re.match('^[hs]d[a-p]$', mountpoint): @@ -233,8 +250,9 @@ class Volume(object): return -1 @classmethod - def get_volume_id(self, n): - # FIXME: n must contain at least the volume_id + def get_volume_id(cls, n): + """ Retrieve the volume id from device_path """ + # n must contain at least the volume_id # /vol- is for remote volumes # -vol- is for local volumes # see compute/manager->setup_compute_volume @@ -244,19 +262,52 @@ class Volume(object): return volume_id @classmethod - def get_target_host(self, n): - # FIXME: if n is none fall back on flags - if n is None or config.target_host: + def get_target_host(cls, n): + """ Retrieve target host """ + if n: + return n[0:n.find(':')] + elif n is None or config.target_host: return config.target_host @classmethod - def get_target_port(self, n): - # FIXME: if n is none fall back on flags - return config.target_port + def get_target_port(cls, n): + """ Retrieve target port """ + if n: + return n[n.find(':') + 1:] + elif n is None or config.target_port: + return config.target_port @classmethod - def get_iqn(self, n): - # FIXME: n must contain at least the volume_id - volume_id = Volume.get_volume_id(n) - if n is None or config.iqn_prefix: + def get_iqn(cls, n, id): + """ Retrieve target IQN """ + if n: + return n + elif n is None or config.iqn_prefix: + volume_id = Volume.get_volume_id(id) return '%s:%s' % (config.iqn_prefix, volume_id) + + @classmethod + @defer.inlineCallbacks + def get_target(self, volume_id): + """ + Gets iscsi name and portal from volume name and host. + For this method to work the following are needed: + 1) volume_ref['host'] to resolve the public IP address + 2) ietd to listen only to the public network interface + If any of the two are missing, fall back on Flags + """ + volume_ref = db.volume_get_by_ec2_id(context.get_admin_context(), + volume_id) + + (r, _e) = yield process.simple_execute("sudo iscsiadm -m discovery -t " + "sendtargets -p %s" % + volume_ref['host']) + if len(_e) == 0: + for target in r.splitlines(): + if volume_id in target: + (location, _sep, iscsi_name) = target.partition(" ") + break + iscsi_portal = location.split(",")[0] + defer.returnValue((iscsi_name, iscsi_portal)) + else: + defer.returnValue((None, None)) diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xenapi/volumeops.py index 6c48f64911..a052aaf952 100644 --- a/nova/virt/xenapi/volumeops.py +++ b/nova/virt/xenapi/volumeops.py @@ -47,7 +47,7 @@ class VolumeOps(object): instance_name, device_path, mountpoint) # Create the iSCSI SR, and the PDB through which hosts access SRs. # But first, retrieve target info, like Host, IQN, LUN and SCSIID - vol_rec = Volume.parse_volume_info(device_path, mountpoint) + vol_rec = yield Volume.parse_volume_info(device_path, mountpoint) label = 'SR-%s' % vol_rec['volumeId'] description = 'Disk-for:%s' % instance_name # Create SR From 06c52051005f5e43a1f543e2d1c5922aa91c7918 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Mon, 6 Dec 2010 19:29:00 +0000 Subject: [PATCH 061/229] minor changes to docstrings --- nova/virt/xenapi/novadeps.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/nova/virt/xenapi/novadeps.py b/nova/virt/xenapi/novadeps.py index 66c8233b87..3680a5dd29 100644 --- a/nova/virt/xenapi/novadeps.py +++ b/nova/virt/xenapi/novadeps.py @@ -60,10 +60,15 @@ flags.DEFINE_float('xenapi_task_poll_interval', 'The interval used for polling of remote tasks ' '(Async.VM.start, etc). Used only if ' 'connection_type=xenapi.') -#FIXME: replace with proper target discovery -flags.DEFINE_string('target_host', None, 'iSCSI Target Host') -flags.DEFINE_string('target_port', '3260', 'iSCSI Target Port, 3260 Default') -flags.DEFINE_string('iqn_prefix', 'iqn.2010-10.org.openstack', 'IQN Prefix') +flags.DEFINE_string('target_host', + None, + 'iSCSI Target Host') +flags.DEFINE_string('target_port', + '3260', + 'iSCSI Target Port, 3260 Default') +flags.DEFINE_string('iqn_prefix', + 'iqn.2010-10.org.openstack', + 'IQN Prefix') class Configuration(object): @@ -292,9 +297,9 @@ class Volume(object): """ Gets iscsi name and portal from volume name and host. For this method to work the following are needed: - 1) volume_ref['host'] to resolve the public IP address - 2) ietd to listen only to the public network interface - If any of the two are missing, fall back on Flags + 1) volume_ref['host'] must resolve to something rather than loopback + 2) ietd must bind only to the address as resolved above + If any of the two conditions are not met, fall back on Flags. """ volume_ref = db.volume_get_by_ec2_id(context.get_admin_context(), volume_id) From c2e328a158cadf45df9fb07f0c3da91f11ad416e Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Mon, 6 Dec 2010 19:46:42 +0000 Subject: [PATCH 062/229] fixed import module in __init__.py --- nova/virt/xenapi/__init__.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/nova/virt/xenapi/__init__.py b/nova/virt/xenapi/__init__.py index ece4304071..ed8c293a34 100644 --- a/nova/virt/xenapi/__init__.py +++ b/nova/virt/xenapi/__init__.py @@ -19,8 +19,5 @@ This is loaded late so that there's no need to install this library when not using XenAPI """ -XenAPI = None -global XenAPI - -if XenAPI is None: - XenAPI = __import__('XenAPI') +XenAPI = __import__('XenAPI') +global XenAPI \ No newline at end of file From 76fd35b62bf565fe626ca30c412178894d8e579c Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Mon, 6 Dec 2010 15:14:41 -0500 Subject: [PATCH 063/229] 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 a9da148677..e7ab17d038 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() From 88c0e3e380d50d5794970063bbe464171089f260 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Tue, 7 Dec 2010 04:41:53 +0000 Subject: [PATCH 064/229] 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 e50906ae17..a05dc0f1c4 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 929342a1e9..6830bacb84 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 From 09ebc4c33ff52c352cdab54fea41d1b116a446f4 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Tue, 7 Dec 2010 11:31:43 +0000 Subject: [PATCH 065/229] addressed review comments, complied with HACKING guidelines --- nova/virt/xenapi/__init__.py | 8 -- nova/virt/xenapi/novadeps.py | 170 ----------------------------------- nova/virt/xenapi/vm_utils.py | 36 +++++--- nova/virt/xenapi/vmops.py | 42 ++++----- nova/virt/xenapi_conn.py | 36 +++++--- 5 files changed, 70 insertions(+), 222 deletions(-) delete mode 100644 nova/virt/xenapi/novadeps.py diff --git a/nova/virt/xenapi/__init__.py b/nova/virt/xenapi/__init__.py index ed8c293a34..3d598c463c 100644 --- a/nova/virt/xenapi/__init__.py +++ b/nova/virt/xenapi/__init__.py @@ -13,11 +13,3 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - -""" -This is loaded late so that there's no need to install this library -when not using XenAPI -""" - -XenAPI = __import__('XenAPI') -global XenAPI \ No newline at end of file diff --git a/nova/virt/xenapi/novadeps.py b/nova/virt/xenapi/novadeps.py deleted file mode 100644 index a68fd8e774..0000000000 --- a/nova/virt/xenapi/novadeps.py +++ /dev/null @@ -1,170 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2010 Citrix Systems, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -It captures all the inner details of Nova classes and avoid their exposure -to the implementation of the XenAPI module. One benefit of this, is to avoid -sprawl of code changes -""" - -from nova import db -from nova import flags -from nova import context - -from nova.compute import power_state -from nova.auth.manager import AuthManager -from nova.compute import instance_types -from nova.virt import images - -XENAPI_POWER_STATE = { - 'Halted': power_state.SHUTDOWN, - 'Running': power_state.RUNNING, - 'Paused': power_state.PAUSED, - 'Suspended': power_state.SHUTDOWN, # FIXME - 'Crashed': power_state.CRASHED} - - -flags.DEFINE_string('xenapi_connection_url', - None, - 'URL for connection to XenServer/Xen Cloud Platform.' - ' Required if connection_type=xenapi.') -flags.DEFINE_string('xenapi_connection_username', - 'root', - 'Username for connection to XenServer/Xen Cloud Platform.' - ' Used only if connection_type=xenapi.') -flags.DEFINE_string('xenapi_connection_password', - None, - 'Password for connection to XenServer/Xen Cloud Platform.' - ' Used only if connection_type=xenapi.') -flags.DEFINE_float('xenapi_task_poll_interval', - 0.5, - 'The interval used for polling of remote tasks ' - '(Async.VM.start, etc). Used only if ' - 'connection_type=xenapi.') - - -class Configuration(object): - """ Wraps Configuration details into common class """ - def __init__(self): - self._flags = flags.FLAGS - - @property - def xenapi_connection_url(self): - """ Return the connection url """ - return self._flags.xenapi_connection_url - - @property - def xenapi_connection_username(self): - """ Return the username used for the connection """ - return self._flags.xenapi_connection_username - - @property - def xenapi_connection_password(self): - """ Return the password used for the connection """ - return self._flags.xenapi_connection_password - - @property - def xenapi_task_poll_interval(self): - """ Return the poll interval for the connection """ - return self._flags.xenapi_task_poll_interval - - -class Instance(object): - """ Wraps up instance specifics """ - - @classmethod - def get_name(cls, instance): - """ The name of the instance """ - return instance.name - - @classmethod - def get_type(cls, instance): - """ The type of the instance """ - return instance_types.INSTANCE_TYPES[instance.instance_type] - - @classmethod - def get_project(cls, instance): - """ The project the instance belongs """ - return AuthManager().get_project(instance.project_id) - - @classmethod - def get_project_id(cls, instance): - """ The id of the project the instance belongs """ - return instance.project_id - - @classmethod - def get_image_id(cls, instance): - """ The instance's image id """ - return instance.image_id - - @classmethod - def get_kernel_id(cls, instance): - """ The instance's kernel id """ - return instance.kernel_id - - @classmethod - def get_ramdisk_id(cls, instance): - """ The instance's ramdisk id """ - return instance.ramdisk_id - - @classmethod - def get_network(cls, instance): - """ The network the instance is connected to """ - # TODO: is ge_admin_context the right context to retrieve? - return db.project_get_network(context.get_admin_context(), - instance.project_id) - - @classmethod - def get_mac(cls, instance): - """ The instance's MAC address """ - return instance.mac_address - - @classmethod - def get_user(cls, instance): - """ The owner of the instance """ - return AuthManager().get_user(instance.user_id) - - -class Network(object): - """ Wraps up network specifics """ - - @classmethod - def get_bridge(cls, network): - """ the bridge for the network """ - return network.bridge - - -class Image(object): - """ Wraps up image specifics """ - - @classmethod - def get_url(cls, image): - """ the url to get the image from """ - return images.image_url(image) - - -class User(object): - """ Wraps up user specifics """ - - @classmethod - def get_access(cls, user, project): - """ access key """ - return AuthManager().get_access_key(user, project) - - @classmethod - def get_secret(cls, user): - """ access secret """ - return user.secret diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 52ab2901d4..407acda6e9 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -25,10 +25,17 @@ 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 novadeps import Instance -from novadeps import Image -from novadeps import User +XENAPI_POWER_STATE = { + 'Halted': power_state.SHUTDOWN, + 'Running': power_state.RUNNING, + 'Paused': power_state.PAUSED, + 'Suspended': power_state.SHUTDOWN, # FIXME + 'Crashed': power_state.CRASHED} class VMHelper(): @@ -44,7 +51,7 @@ class VMHelper(): """Create a VM record. Returns a Deferred that gives the new VM reference.""" - instance_type = Instance.get_type(instance) + instance_type = instance_types.INSTANCE_TYPES[instance.instance_type] mem = str(long(instance_type['memory_mb']) * 1024 * 1024) vcpus = str(instance_type['vcpus']) rec = { @@ -76,10 +83,9 @@ class VMHelper(): 'user_version': '0', 'other_config': {}, } - logging.debug('Created VM %s...', Instance.get_name(instance)) + logging.debug('Created VM %s...', instance.name) vm_ref = yield session.call_xenapi('VM.create', rec) - logging.debug('Created VM %s as %s.', - Instance.get_name(instance), vm_ref) + logging.debug('Created VM %s as %s.', instance.name, vm_ref) defer.returnValue(vm_ref) @classmethod @@ -137,14 +143,14 @@ class VMHelper(): its kernel and ramdisk (if external kernels are being used). Returns a Deferred that gives the new VDI UUID.""" - url = Image.get_url(image) - access = User.get_access(user, project) + url = images.image_url(image) + access = AuthManager().get_access_key(user, project) logging.debug("Asking xapi to fetch %s as %s", url, access) fn = use_sr and 'get_vdi' or 'get_kernel' args = {} args['src_url'] = url args['username'] = access - args['password'] = User.get_secret(user) + args['password'] = user.secret if use_sr: args['add_partition'] = 'true' task = yield session.async_call_plugin('objectstore', fn, args) @@ -179,7 +185,7 @@ class VMHelper(): def lookup_vm_vdis_blocking(cls, session, vm): """ Synchronous lookup_vm_vdis """ # Firstly we get the VBDs, then the VDIs. - # TODO: do we leave the read-only devices? + # TODO(Armando): do we leave the read-only devices? vbds = session.get_xenapi().VM.get_VBDs(vm) vdis = [] if vbds: @@ -197,3 +203,11 @@ class VMHelper(): return vdis else: return None + + @classmethod + def compile_info(cls, record): + return {'state': XENAPI_POWER_STATE[record['power_state']], + 'max_mem': long(record['memory_static_max']) >> 10, + 'mem': long(record['memory_dynamic_max']) >> 10, + 'num_cpu': record['VCPUs_max'], + 'cpu_time': 0} diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 3db86f1791..3696782b31 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -23,12 +23,11 @@ import XenAPI from twisted.internet import defer -from novadeps import XENAPI_POWER_STATE -from novadeps import Instance -from novadeps import Network - -from vm_utils import VMHelper -from network_utils import NetworkHelper +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 class VMOps(object): @@ -46,39 +45,40 @@ class VMOps(object): @defer.inlineCallbacks def spawn(self, instance): """ Create VM instance """ - vm = yield VMHelper.lookup(self._session, Instance.get_name(instance)) + vm = yield VMHelper.lookup(self._session, instance.name) if vm is not None: raise Exception('Attempted to create non-unique name %s' % - Instance.get_name(instance)) + instance.name) - bridge = Network.get_bridge(Instance.get_network(instance)) + bridge = db.project_get_network(context.get_admin_context(), + instance.project_id).bridge network_ref = \ yield NetworkHelper.find_network_with_bridge(self._session, bridge) - user = Instance.get_user(instance) - project = Instance.get_project(instance) + user = AuthManager().get_user(instance.user_id) + project = AuthManager().get_project(instance.project_id) vdi_uuid = yield VMHelper.fetch_image(self._session, - Instance.get_image_id(instance), user, project, True) + instance.image_id, user, project, True) kernel = yield VMHelper.fetch_image(self._session, - Instance.get_kernel_id(instance), user, project, False) + instance.kernel_id, user, project, False) ramdisk = yield VMHelper.fetch_image(self._session, - Instance.get_ramdisk_id(instance), user, project, False) + instance.ramdisk_id, user, project, False) vdi_ref = yield self._session.call_xenapi('VDI.get_by_uuid', vdi_uuid) vm_ref = yield VMHelper.create_vm(self._session, instance, kernel, ramdisk) yield VMHelper.create_vbd(self._session, vm_ref, vdi_ref, 0, True) if network_ref: yield VMHelper.create_vif(self._session, vm_ref, - network_ref, Instance.get_mac(instance)) + network_ref, instance.mac_address) logging.debug('Starting VM %s...', vm_ref) yield self._session.call_xenapi('VM.start', vm_ref, False, False) - logging.info('Spawning VM %s created %s.', Instance.get_name(instance), + logging.info('Spawning VM %s created %s.', instance.name, vm_ref) @defer.inlineCallbacks def reboot(self, instance): """ Reboot VM instance """ - instance_name = Instance.get_name(instance) + instance_name = instance.name vm = yield VMHelper.lookup(self._session, instance_name) if vm is None: raise Exception('instance not present %s' % instance_name) @@ -88,7 +88,7 @@ class VMOps(object): @defer.inlineCallbacks def destroy(self, instance): """ Destroy VM instance """ - vm = yield VMHelper.lookup(self._session, Instance.get_name(instance)) + vm = yield VMHelper.lookup(self._session, instance.name) if vm is None: # Don't complain, just return. This lets us clean up instances # that have already disappeared from the underlying platform. @@ -122,11 +122,7 @@ class VMOps(object): if vm is None: raise Exception('instance not present %s' % instance_id) rec = self._session.get_xenapi().VM.get_record(vm) - return {'state': XENAPI_POWER_STATE[rec['power_state']], - 'max_mem': long(rec['memory_static_max']) >> 10, - 'mem': long(rec['memory_dynamic_max']) >> 10, - 'num_cpu': rec['VCPUs_max'], - 'cpu_time': 0} + return VMHelper.compile_info(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 2839a753c5..a2eac4dc2b 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -49,26 +49,42 @@ 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 from nova import utils +from nova import flags +from nova.virt.xenapi.vmops import VMOps +from nova.virt.xenapi.volumeops import VolumeOps -from xenapi.vmops import VMOps -from xenapi.volumeops import VolumeOps -from xenapi.novadeps import Configuration -from xenapi import XenAPI - -Config = Configuration() +FLAGS = flags.FLAGS +flags.DEFINE_string('xenapi_connection_url', + None, + 'URL for connection to XenServer/Xen Cloud Platform.' + ' Required if connection_type=xenapi.') +flags.DEFINE_string('xenapi_connection_username', + 'root', + 'Username for connection to XenServer/Xen Cloud Platform.' + ' Used only if connection_type=xenapi.') +flags.DEFINE_string('xenapi_connection_password', + None, + 'Password for connection to XenServer/Xen Cloud Platform.' + ' Used only if connection_type=xenapi.') +flags.DEFINE_float('xenapi_task_poll_interval', + 0.5, + 'The interval used for polling of remote tasks ' + '(Async.VM.start, etc). Used only if ' + 'connection_type=xenapi.') def get_connection(_): """Note that XenAPI doesn't have a read-only connection mode, so the read_only parameter is ignored.""" - url = Config.xenapi_connection_url - username = Config.xenapi_connection_username - password = Config.xenapi_connection_password + url = FLAGS.xenapi_connection_url + username = FLAGS.xenapi_connection_username + password = FLAGS.xenapi_connection_password if not url or password is None: raise Exception('Must specify xenapi_connection_url, ' 'xenapi_connection_username (optionally), and ' @@ -165,7 +181,7 @@ class XenAPISession(object): #logging.debug('Polling task %s...', task) status = self._session.xenapi.task.get_status(task) if status == 'pending': - reactor.callLater(Config.xenapi_task_poll_interval, + reactor.callLater(FLAGS.xenapi_task_poll_interval, self._poll_task, task, deferred) elif status == 'success': result = self._session.xenapi.task.get_result(task) From e9597d1370211de15ca96f1fa52fcbe3c9166a7e Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Tue, 7 Dec 2010 14:15:22 +0000 Subject: [PATCH 066/229] fixed pylint violations that slipped out from a previous check --- nova/virt/xenapi/vm_utils.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 762cbae832..2ee6737ab5 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -29,6 +29,7 @@ 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.xenapi.volume_utils import StorageError XENAPI_POWER_STATE = { 'Halted': power_state.SHUTDOWN, @@ -115,11 +116,13 @@ class VMHelper(): @classmethod @utils.deferredToThread - def find_vbd_by_number(self, session, vm_ref, number): + def find_vbd_by_number(cls, session, vm_ref, number): + """ Get the VBD reference from the device number """ return VMHelper.find_vbd_by_number_blocking(session, vm_ref, number) @classmethod - def find_vbd_by_number_blocking(self, session, vm_ref, number): + def find_vbd_by_number_blocking(cls, session, vm_ref, number): + """ Synchronous find_vbd_by_number """ vbds = session.get_xenapi().VM.get_VBDs(vm_ref) if vbds: for vbd in vbds: @@ -127,29 +130,31 @@ class VMHelper(): vbd_rec = session.get_xenapi().VBD.get_record(vbd) if vbd_rec['userdevice'] == str(number): return vbd - except Exception, exc: + except XenAPI.Failure, exc: logging.warn(exc) raise Exception('VBD not found in instance %s' % vm_ref) @classmethod @defer.inlineCallbacks - def unplug_vbd(self, session, vbd_ref): + def unplug_vbd(cls, session, vbd_ref): + """ Unplug VBD from VM """ try: vbd_ref = yield session.call_xenapi('VBD.unplug', vbd_ref) - except Exception, exc: + except XenAPI.Failure, exc: logging.warn(exc) if exc.details[0] != 'DEVICE_ALREADY_DETACHED': - raise Exception('Unable to unplug VBD %s' % vbd_ref) + raise StorageError('Unable to unplug VBD %s' % vbd_ref) @classmethod @defer.inlineCallbacks - def destroy_vbd(self, session, vbd_ref): + def destroy_vbd(cls, session, vbd_ref): + """ Destroy VBD from host database """ try: task = yield session.call_xenapi('Async.VBD.destroy', vbd_ref) yield session.wait_for_task(task) - except Exception, exc: + except XenAPI.Failure, exc: logging.warn(exc) - raise Exception('Unable to destroy VBD %s' % vbd_ref) + raise StorageError('Unable to destroy VBD %s' % vbd_ref) @classmethod @defer.inlineCallbacks @@ -244,6 +249,7 @@ class VMHelper(): @classmethod def compile_info(cls, record): + """ Fill record with VM status information """ return {'state': XENAPI_POWER_STATE[record['power_state']], 'max_mem': long(record['memory_static_max']) >> 10, 'mem': long(record['memory_dynamic_max']) >> 10, From 88777c09ad909c68da8d433800cae862e9bbff4a Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Tue, 7 Dec 2010 14:26:38 +0000 Subject: [PATCH 067/229] and yet another pylint fix --- nova/virt/xenapi/vm_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 2ee6737ab5..039e729813 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -132,7 +132,7 @@ class VMHelper(): return vbd except XenAPI.Failure, exc: logging.warn(exc) - raise Exception('VBD not found in instance %s' % vm_ref) + raise StorageError('VBD not found in instance %s' % vm_ref) @classmethod @defer.inlineCallbacks From c0fc8a5e9e72ecb780258d9cf41b32973620eb4c Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Tue, 7 Dec 2010 15:35:56 +0000 Subject: [PATCH 068/229] small fixes on Exception handling --- nova/virt/xenapi/vm_utils.py | 2 +- nova/virt/xenapi/volume_utils.py | 10 +++++++--- nova/virt/xenapi/volumeops.py | 7 ++++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 039e729813..f298031368 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -132,7 +132,7 @@ class VMHelper(): return vbd except XenAPI.Failure, exc: logging.warn(exc) - raise StorageError('VBD not found in instance %s' % vm_ref) + raise StorageError('VBD not found in instance %s' % vm_ref) @classmethod @defer.inlineCallbacks diff --git a/nova/virt/xenapi/volume_utils.py b/nova/virt/xenapi/volume_utils.py index 48aff7ef5d..debaa6906c 100644 --- a/nova/virt/xenapi/volume_utils.py +++ b/nova/virt/xenapi/volume_utils.py @@ -95,9 +95,13 @@ class VolumeHelper(): @defer.inlineCallbacks def find_sr_from_vbd(cls, session, vbd_ref): """ Find the SR reference from the VBD reference """ - vdi_ref = yield session.get_xenapi().VBD.get_VDI(vbd_ref) - sr_ref = yield session.get_xenapi().VDI.get_SR(vdi_ref) - defer.returnValue(sr_ref) + try: + vdi_ref = yield session.get_xenapi().VBD.get_VDI(vbd_ref) + sr_ref = yield session.get_xenapi().VDI.get_SR(vdi_ref) + defer.returnValue(sr_ref) + except XenAPI.Failure, exc: + logging.warn(exc) + raise StorageError('Unable to find SR from VBD %s' % vbd_ref) @classmethod @utils.deferredToThread diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xenapi/volumeops.py index 4055688e38..b9f2607561 100644 --- a/nova/virt/xenapi/volumeops.py +++ b/nova/virt/xenapi/volumeops.py @@ -18,6 +18,7 @@ Management class for Storage-related functions (attach, detach, etc). """ import logging +import XenAPI from twisted.internet import defer @@ -68,10 +69,10 @@ class VolumeOps(object): vm_ref, vdi_ref, vol_rec['deviceNumber'], False) - except StorageError, exc: + except XenAPI.Failure, exc: logging.warn(exc) yield VolumeHelper.destroy_iscsi_storage(self._session, sr_ref) - raise StorageError('Unable to use SR %s for instance %s' + raise Exception('Unable to use SR %s for instance %s' % (sr_ref, instance_name)) else: @@ -79,7 +80,7 @@ class VolumeOps(object): task = yield self._session.call_xenapi('Async.VBD.plug', vbd_ref) yield self._session.wait_for_task(task) - except StorageError, exc: + except XenAPI.Failure, exc: logging.warn(exc) yield VolumeHelper.destroy_iscsi_storage(self._session, sr_ref) From c1a40a8381ae3e559b3faad4a93ffec1abe8907f Mon Sep 17 00:00:00 2001 From: Eric Day Date: Tue, 7 Dec 2010 10:06:49 -0800 Subject: [PATCH 069/229] 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 995bed91bc..cb23dae555 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, From d7ca22cce7df319efc57a2e8224016817c92bbdb Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Tue, 7 Dec 2010 18:57:44 +0000 Subject: [PATCH 070/229] 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 407acda6e9..99d484ca2b 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 3696782b31..d36cdaea5b 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 a2eac4dc2b..26b30bf927 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 From 994f2820676b47b4f2e919d5ae7d2f9eb66c4372 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 7 Dec 2010 20:25:24 +0100 Subject: [PATCH 071/229] Add Ryan Lucio to Authors --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index ef1a535ca9..62f0c49d5d 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 From 06c5889936cec1be503595915a0e0df2c4f925a8 Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Tue, 7 Dec 2010 19:35:05 +0000 Subject: [PATCH 072/229] Adding myself to the authors list --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index ef1a535ca9..a35398d5d3 100644 --- a/Authors +++ b/Authors @@ -25,3 +25,4 @@ Todd Willey Vishvananda Ishaya Youcef Laribi Zhixue Wu +Ryan Lane From bf34529e75022451f3833552df0e807139d0e498 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 7 Dec 2010 21:35:15 +0100 Subject: [PATCH 073/229] 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 856060afa2..667c63ad09 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() From d03620f31aac6e8720bb6dc19860cb609af878c6 Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Tue, 7 Dec 2010 21:13:54 +0000 Subject: [PATCH 074/229] Reverting last change --- Authors | 1 - 1 file changed, 1 deletion(-) diff --git a/Authors b/Authors index a35398d5d3..ef1a535ca9 100644 --- a/Authors +++ b/Authors @@ -25,4 +25,3 @@ Todd Willey Vishvananda Ishaya Youcef Laribi Zhixue Wu -Ryan Lane From a107c6f69237cd7488b9ff716e370dd01b8dd8bd Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 7 Dec 2010 16:06:55 -0600 Subject: [PATCH 075/229] 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 99d484ca2b..801867bd41 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 d36cdaea5b..ba73079ec7 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! From 699ac0785240307ef2396d688e6c0a2acb446665 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Tue, 7 Dec 2010 22:22:48 +0000 Subject: [PATCH 076/229] pylint fixes --- nova/virt/xenapi/vm_utils.py | 2 +- nova/virt/xenapi/volume_utils.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 35d89d8356..0549dc9fbc 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -44,7 +44,7 @@ class VMHelper(): """ The class that wraps the helper methods together. """ - def __init__(self, session): + def __init__(self): return @classmethod diff --git a/nova/virt/xenapi/volume_utils.py b/nova/virt/xenapi/volume_utils.py index 84eb82f159..051d0fe853 100644 --- a/nova/virt/xenapi/volume_utils.py +++ b/nova/virt/xenapi/volume_utils.py @@ -1,5 +1,4 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -from orca.scripts import self_voicing # Copyright (c) 2010 Citrix Systems, Inc. # @@ -46,7 +45,7 @@ class VolumeHelper(): """ The class that wraps the helper methods together. """ - def __init__(self, session): + def __init__(self): return @classmethod From d647d6b070e0a910a9f20cfc1106027c86858f30 Mon Sep 17 00:00:00 2001 From: Anne Gentle Date: Tue, 7 Dec 2010 16:48:42 -0600 Subject: [PATCH 077/229] Added livecd instructions plus fixed references to .conf files --- doc/source/adminguide/managing.networks.rst | 2 +- doc/source/adminguide/multi.node.install.rst | 31 +++--------- 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, 64 insertions(+), 30 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 b8563637e9..38c1cba78e 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 1eed30c5b3..3b06d7d911 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 --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 f6b2290bcf..8572c5a4a5 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 87db5af1eb..7c79d2a70f 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 0000000000000000000000000000000000000000..0fe3279cf89b2290e6365b784792df692c37595a GIT binary patch literal 27949 zcmeFZXIPV6(=H51N17r?6BHE@K`GL!DCmv!UL(>=C_(^%&{RMWP!Ui%gkGg3)Sw_; zdI=DEhY)%QBm{PR-beBMz90M8|L;FJj;vg)HEZUanRDh^i?`aEYV@=mv?L@X^iLi? z)FmMy>n9;0ZJ?$kj$q$@3@28rKviVL&?L%7gJt+{KQLr%|u&?{6j`QIVrO?3lj~Q>h*|^`u9jfw@sVs zH^GY2c$v14C+T8%uNRNCIB>*-~UXg|wfZ;VnHM{!n$>q2LYfrAseTYv8NnAM9IkHYa4cL@K zHF;Bs$m6vvp3#wX*^IfGpX&MS%8^)HxYrkbT5$0;8Oavaw+Ipe(L3wuJK90Q4rA+% zr01T!0jBdrkkIhbv4n8+li|LR8~9EK|S#zU0Jr07MJB%~OHLq)YIoRs7p z$Y4sctCWo2`0kOleHZtoU;Zv?OqD##KlX~3nr`muwI8$|DxvSCuG0yqaJ*Nrp)^s6 zc`v(ojzal^HrXdyTNMgzb}tI2VAlt!{pVI`#7P@pvt}5q3hvW|Jk;j9%#=cX^)<@_ z_RQ#ekH)TP@&+(vMo2y6xW=Xvny)&kH2FwQ=^bw_^Iin#2jg|6`S(vBTV|8%vKK^E zDal-I{9x5_-G;S-+9cXV)%$Ard%cdAi%Ma@p_Y&_3eOCBMZb!3sE~>TSd29K)F2}TMiRH2^QnaqL zCbZ{h`6GEFSt74RqF5SP;#hbD4sNa8q7X0;5V|EVU@fqB%UPiQR=SR`-lh(7{_BF_ z0+)j0d`-Q0?fZK2*?uY*`tX*WIem9SDbY=qQgTG3|2wm9Xx$;bC>@k;y|$X}iY@@M z2?=~cppR{*o2%Z|Jfsc~hLb7=r?+hNGdW&&eCH_V_|dV;adYtLU_fq8_NDBRTu@Fl zL?{ag0jt(4t3Ba=n5Vv?ocPd46Y)e+9s5e@dI!@T>X6r5&7kIs&9w3fmJyce({T#X z@UXz(z_$_CcQ1n8UVnbOJmK(t&Cg&fCcArf+twX+4$m)JkJ)6~f3mN#e`j^ae#0uo z{(&fs2)h`?y(N*U=Sfz$)}NilJsYhlos&Q1IvaYLd$U^STR*lAHjDP`M#YC4q>rTE zPp?kJ2ysSgyz7Y+N-Ft?zKW!jJy*{m`&K@Rz={`SAP>JBeqT*ajlTU>JENwsrb;hU zuWm1CFTLfhrPupEqhu@YJ&S~PvUeK+q?l`*?r-?}s{ zGA*xQr63DufwRJ4$NI-z$J57ejtR%fWRqm=}C@g7oX>ZfvXj$nPXk6)N=%lGr znVr~7neCV*SYs}_vIO#lvJJ4%Tq#L73A53VRXn=2Ty;Xfn@#N+cu}+pw@6JA5GF?sGBV83;szKU8(!uJ% zGTT+#r#8AabvE#>$j*;8-L~o7b4%wJKQ75ELKjXGs-G!d^fSYyAO&)4Oi_{r`B*$yQc+Zx&yne;j)_Woozye|s*Z;=t2M@q zUa`hs>3Fm{?oJ84amantJL)0lM{yG*J*}J~aJ+9^EH1C}Sn#;{^p7 zfG&dY`|th2{7n3;ar-!LoGs3iGJsNnGMX|uj5cg146N$Frw0jum{@CCfAn~@ll%7L zldR4Rf$D;2$1iJHUp||BHbIE|5Q(#9i)j>L6K524gORYdv9jHnHl;NSGo7w%Ha0Ph zHv@l7DA)XYrQ+&D&A2)2$g#*e*&aWk`9pZHbS7WOe{o!+Ug{8|J$NOmw_BK2m|a+m|FzbS6yN!-Wscd21y$7Y+~{(Qw~u?%CVb0k zP6xfSkiF9x8JB$BLRZ9bO0T=J-*ZHLs^}XE0;}^0!A8c`^--i705oQ<&Cpi}*S>35 zvR^b>f}!2!f_hHxeY=`*HZMQ+NaP@TLlaS7LF;7d4maq^h2Ct z4x-JXc76G&1u{`G>I$+60*NL`FW)bHxt}7GYVoNtU8Rd%f#0)qanh>PXVWd`SHK93l?ZT5nivmAy9|sPg=i{y&WfeXzobQ-Fx={HsMmJ<|F9&Z6 zw;fuNSrWR$b?M6`WTM<%4MQgb2_p|fTO&f2WLG_`v&B8i;BIkmJp9Xi&g&rf*&!!b-%FZ^Z z2@Pzr80@JV1||TzKf~H!;Sui3j}rEhJlM3Wcf$vSTem;Vdtx7yXo^Vn|A;LG?Rh6+ z?;R{+bTPS@E7P&{r;xkZZVMrOuW|xmCx~8Fl`I>cC);!;=Z6JE9+rBO7#8F-Z3 zicrta!C>dMu!%zovx+R>#gnnG*XjowoxaMzmm8oBS1RDJ4?AzS3+5|3R*--y_&e7t zGv?E@NSWiLy=V-wdTVzEA7p&;8P9&`=_czO4$2EOdg-&8*ishjKY$-2e4NQ@s%Lo0 zIL$?QzBvT6z{j$=g{=u5YFx?v(zLWTqK3?p!xPD`PuCY3{6b;pyAguK|gL} z3;x4Y9k$u>>6yx{VgXD6i-+#;M_&&PTT?w5p9j|okDl|lkX^iADWQJZ`65y#{8k>1 zksy|#?8?l`lSj(znPSauk1qY_jdFXusy_OdyQ8MVyR%Z@$1P34?t;ZaSFZc|O~pIv z-KO}$-qN&!z%Sn;KQwqWJSk)MdfF}5n>F&HGwB(`73G-d8Z@4XJ_NUu<|(XbCMH}2 zm0ogPnbMPcpjG$rr(Ar)VZ+WeQh^TMvS=1)dm=!(sMO8-=GpQr`h7uv+t)?+DC*6K zyB`{LY6pZx=0%#Ghc9%^chYpd{rsaKyKpgBPbQB-_p+|z(CQ!!yO;g8y?~g`mvEE% zae(J&pU^VAD_KtQ;|!qxWoGTy<>!t%t{zTL0vugT{(1SNHC25fKFWYIQL@a%&}}rJ zfDnMuo5EGcU=K=7N@JJpq(*OuIO`X6AL;m=Y($TW!#-o`RRByt=gN|0hO_^gJ z`z>1QQ%MT>2%LMawKRw zrShHgC;WS(FLy(mOrB{xHXk#}w)Cqk)_$$$N;CH2_A2u1+uoKdcr?!o^w;#g@0W+K!QK9_)*%>(B}APICBwWL z3ss<)VeF&_(Js)L(Yr-Q#OOyKbB}PQazQRdMS{6}gr`MAKHp0GEz1KP;V+<0^Sg)fYwJGuQPr}b4rg&pF+CX~hckBio59z6Xi83(SM( zU$liE$1VvP8JQvKMyVvy0{sXt?e%Pn4w8ErC;PT!1)FYSix|01TNSCi7_Nq1R;J7v z)8SiXc}HnSg;enf8~pGOgW4;z%)n4LNI|x|TH-$ukl1T&qCZ4$irU^=DK~!(wBoc0 z`o8mpu21M2qy^QR*ahI*)^<1etol%OSo4l$!+J-?k3tq@(ULfIyS0%Rto9rOOc>Tt2rw4_9XxPVt0 z*@l>$dBP){-}-1O@mJlH;m?dSjkC}aNlB8j_%t71n4%g7O_`3<@ohUT_P*)4oL5XW z{ziIw<J zdoTB?Hs%LPA2?y7znLZ1Hu&t(E#64LDk@|d#Ys>?g*T{==T3RJ=VG&Dt#B$4`>rOX6Zc(yYkMON}BaWIBSK29wYlnyt>hi{p7r3VKGHGRLJr z!xAjx&iFLgKnh^@p4@fx-6eY72ReERO#;GB|=A_}8k|Ca9mkmhrQ~v1W02eEJ?e zC%YhhQzVypnTgddfpk<`LIKKt;K`+$a=BN{++@;Fj!Q1xm+Hvru##e){M;M4H&YMI zA5FD3{5Ti;;~$%U4F3T=>FX-)3{3F9dunc_v{R~ExeJP?96>pisL2jf-Om+Ito8;u zfF5Da?}N`*(7SOoN_f&~KA9rLVs=<)Ssn!CLs!i?rSuELx2 zS2(Y0YH8f1+EPldgu^=0kK@Ti&&^N>hNUnP&f{TbI`0OUMOr+5?v&DC-iCL=^H_D| zbZ2s8?VhHCeoppTcJ}l%>30~~=w8q@)E&@~_I{a(TQNv-&Z5ta7SXUMF^3gyMZk=_ zuXxw*Py2rpdyjF)mTzqj>mAvTUsUgl?RnSe71Ste)R57zzo|0OH7zsK_I+hSBc!Cz zt$3y=$jK#WQfPc=JYAL=Umv9ObvgLl!*4VXLoVDSVF*57M0+&Lu}UE}OZSk%nOx>Q z=O=Q52aWx5KO32v$(ciMJ=njdz`ORo@bO(6g1Iw#kUW_SV zb!)O`(~8e1%BRQtHe}wgJi7jf@kZF-$K4A{9PzgeBt~RYNLOn zs2<7buC*t=W4C*>olM<9T|uWqjiw1=I})zD8>85KH}mS6aJ;~jK;_N$t8NAFOQZ^) zYI|r486@k5i@z49uWG37TNhu`MDd-sp>K>9X75W9{W8uYD4Jf8T`pRs30D$n1>t{Ae6qknE(L&KnI_47k|IcalPy84?>Rt&;U1uX%|tk`D`+dhWMi zJpby#l^Z+!_Nfz3cGSViu3tpl4FxI%lJDl-Nt7gvr^OXc=C^l!yB`lPTM1= zP1B^Ja0FZ;z#(8@za|H7XODa(+cz=eT{?qA=d3TR8F9;<+&b_$a!0Hlj-htg;vjwX z_O1;z`?pVfzSJ%rjkT^~MR?|oo{ws}+yfWln;V`?7aqwFfOx7sty*Ar>h{ziKnq*m zRKnmy%nX#Xq+-X|ZW3-|z%Iqx45rcdVc)Jvy_U*^^wWIC3-&EB32Zb6b6s7S=kGa(wBP83g{f~#tbCz;TG8VFm-hiT%{UooEEh9siyM1E?LWw0! z+>(UzwPX=lIy_}N?}}}yjq8P2_A=V^%ZgQ z`tV%w{NReyCUllrB@0u$lYm|0OUCt3z9)l)YK4o2RX^m4ntUf5egAFv1(}bJJE`(K z^-Kl}#jf}6+f3M@7sXd7Q`x%H>t=~n`mKsXHAk=Uz(=Y#R1BsrRj(IJe3L13#?Y%G zjNBH*yI~DYFLQ)uV=QF>{Dscd{#mfRvVtbRuNyjZ3&(Q}0}bUHJ3pQvRtAR@?c5sA z2MFPoTP8|`MK)T^zZRNG^V5trT-M0(|}s2920QY8rq zFUgaK%KE;f=qaicZiA_|9eXp0wV7 zl<0XQsLO&gnBs4eKTv=0z>lvH?FwyLQB1CUdeakj-A7C|dFHUT*2k_l#dBG<8#h_G z(%Zu>@8{JM!OSP_G5?VJ<>H44+eC{Wp(JF~yuttds3zN6=|g-H(A(dr>|1}usG!|j zke6n#eV^??Av|l0>Y-42zA+TfJhZ8~vS!^L5sT3*u;LX>C*{rKP3Wi=s!R!8orHb4Spq zcWcbn03>|!cmr5EgssfYH62a+1j#)RTcG)R?vFUn@-B58twLhg>H8(jEes8Fr9GEd zQ+=j&!Wp;$h5-Hpi3`L;oNZJEy;nCekr1PzRHsw zRYNMj6n)*6bDM6My%;VLiYziJn;pnePvli9v#2+8Ws$uc57oLcvAQ$+{gr`@l~l_- zD&@X&0_Ua8oHY3;cuJt!^p96bS226rcxy^nWvNj;!>BDjSg>mC^&$5hr8nmQv& zM%t{$IsP)SR=@{jUa)1;0J&_%mD*O>k-L}t);H_#BY{`&Z@c%u5d2pOD`~@3phDTE%~x$s>5XcI zEeu_0MRxEE{$;PB@%F7!boAa*k^Op1JrBJm?uUBW$lN)x)1ZeB!wiTR%4nq#znlRa z_WmA9QSow6qNF*roWhjuJ9G@?I#ox31nN(8nbtyqNIN-`qM>Hj893O&ca!~7Pj`>r z%SSoM4pyM$0`9J+TO@IaNKtE@9>NFcvnLess8IJnA5JrWHM`tq#WA2MTW(vACVv0M_7ZT*i zl@IqS5z;j%s1?gA0FSem_WlgAiYhB(Hk;bYZ=RJ_``sn&CN00qx| zzC+P$Z1qWdn=!(I(eVSzjq{vjg zPnqCFuvJ`{kQXun+Vcf^G-xnlAiOae_QD^CYz9q;U`A?P#w+S%O423X{BZ4t6%
}e#fYLsYShu0K+09Y&Cy3AOF6fTCa0y?hd8k&^msr)GTiXO`2s}E%&Wl&dB%Ys z=kV)e3k)MoTXm|jJ!U!2ji4$|FO5$RV>8NDp)G||Rdp3;hW`8Uwzo^~BoY!}+fRdg z4@R;S$eG4IkR8!a;m4P+BE9H&{t+6RXyLIh6Z(t;gmKGydkx(f9Yx2=vU7$&Nr0dU zYN)(<$>srXH8vB9P#`g5FY@e3x{nz(nP1j0Q$*G%uHY0*k@u5N`-Y(7&oQR{2O;fY zICidYip2dtpqS)b{1`j)Gch5)=5)45el+{g4k=J=8(N}J)hy`h!wDRHlO{YDB=x+lG zq8k|o#@nN5R6Ad(5)?Om;t73$un9oVqC%rp6TEN=FtCA5o~9n@Z3 zU2-F61f_^+17xgYpG7`eD#T*i(~s5s1NQ==J;COf-XT9POP*9wsOV~V>XPflKExNb z*bf>R4i2Z0r}BkVc9?>qrp(4R?Rz9S>T=q-4P^TIod3Gm?21SD1r8KAkz z=GoZP_v+OV6%nrv-^u7T7b~B+K%lI0=(&~x#_YiB->Dn~Q2>O`w4pFZ7X(E4eS%`9 zZJ2#CZ)72s9(#2XLHweSfFNqWg2HL$ni{^wPz&H+pS$4$g%gQk)d||(YvpcG-W4N2 zt`&FT{nL1WQ`b*0s1=h~0N!~4>_D^X_j~>%l{wsz6yNHA>=(%6^D|y?zUlQy&onMq zYCMkc2F2Vq{5cHRN-izWv&BDE10lPn+rGD zS&!81x%0W~Lo(jj06vkrz%q5B3jogx&S+zo?yezX74os|7I}^SOGV!aH`L7u7N~W% z{NvEm=`ws;(#mvpfSErwaT>hL=x5Wa8~xKUjM-n-ij^22d9Q|0s%3y34)|cKnrM)3 zxg(c?IGzyrTE@J$>3|dwZax!Mf_&a?3O*T!A|Y^@5{4NyK)PG|`gw#s=5QA?;@Do! z8>}QlK3Vpz?zETSQTp;kWHt8O;jM(eXVmPOoAGE1%T2W`LGI6OcL>rGnTo5v^ zFkJ|%lLgd#b%NB_qWzs`zj6p|O{#$>^(#3fPirfldzK^zNeZ+_2g@eC>N|xh)cP~V z8^t@;6rs5hX#`6X5&xv*p@6Rn(pD)GwS7KbW2noinCU&7UP1S~Ytxo>YAI)YwZ6GE zJ~VX`tB>RmdU?E3x7$~$wOM)fTnsKH(6{TcL3~YtBS<~4KaKl2WKg-{0DL}ciyAyf z#pY<^kCg@WXL6j3p$dy{`?MQd`6EBPY)ZDgh(Wpr%P zN-2^A)Q*FS53sfVuP`|=NyKez=MSr) zJIz-|5x@`xYf;OCS^%sfx+qYath9qvI6pT~S5rFWjYDar%4v`&NZrHM&{BczJs@6y&Xm_P=M7gD|sI6y2 z2zJkGA+1|7e59vIusAhC26VB?j!#olWqlZM%lK9NS}bO6e3J_3!?J?}`^)hWp-FLv2f1|>?>2FIP;g6- z9-4a6!-MK4Rrd1YxBp&coNS&X)Q2vH2-iFcuU+FTBVg40Hef-B4Ch|Mrht0?faplU z2YflDArt+zFHc;7uUunZd~qJyl+l;1Tc@qXNUV$5)HtQ3KrY$8Gi1brOd1WNWi2Zemu{IgKHRDUs-TwT z5FAGbui|ETe!24-GM>EI^Qkc8Pm_ph(COWg|iAuWj-l+D&Lg-bs z_2w2ThtVPIdd{2HWV*Vf1kIs$H;Sq@UJ2CAAx_!!zB5dGno(0rkuz6MZ!}^&-Mil? zvE*UlgH%yo`2O@g_b`2uGO;x9ey#M1ubHH z;Fa{GW>HExhIcD0lG6>3X%-?pR{| z?}?Lf7nb?yy$#clGYSW9UnCINPW!()YwDyAggb6EO_p@}Ock!DOkp~;xFGF3LGV(o z`!AW}dcfXV-=)Q?nq>M?JvyqUcZCD?7J>&A+ zGaoqI#tF3jM)?bKH^`(b?fbl&vc&G5DD-)wYUw#8$ghs%>KQ+-KINQ?qO94#^t9cq?j`_VAB&Wp8 za+{nsQl{`$(;xx6l`HbvKLzD_Iy$X^c!70;J~KDg&o3OLcQt*5@7iGCj^9!^Hd+q* zyhj)HNlz#1W5tBo!V*+KH(bt#(FT^3sF$g6>E&iZ(xbxjF(9Eu7MI%1W~&eNOLl<< z1rndes9|(JfD;AQR?18cf>A50S~E>*@p*tOt&J<5>BogjxE6Y>141i3??fJ72!MHi zz?sKOA8i_XqbFS!oZ?-^^1CJ_*88Ukm_CqbQ$P6SC%VF;qnECck*Vo;xqpUjJk!y~ z>QJj770h@z1s&2yn-f9hW35|~w)4*QutyQJiGUYfrEsoxU!!e2omE*sX?1f{tiQxd zM=a&u>xOV)M-XPmK^psV#|~fL*wVpS{eXq9DfG;ao#~2M-3bw8JQ^{h_NW0FbTTP0 znX%*K-0K7ME2L@OmVJ#T%2N-=j_`qA{S^{7lAm}^qK7x8x90$U2lZW7B<>;z91|}* zkigf{em@i5h!)2S4O%ER7+D-hKPAW*tTqP03r8pxFiTs36FqNB3Gb&^)eOgt;uLeg z@Z8ZY8*0iLYDXwu+FY;(cA18q1erU8H%#2y+ro9e;#vRJ;;pCLX|D05PMZRsR^+V7 zSz3|fwiYIPnsxHBe*L|A@6TzY;;r{$5N0Bxo~ehVFe)7K;)2-C65O5VzM)oF!s*7- z#n1zvw>M(d)l=b3#K=xUrg5R8WFYJYow)rDEjAJQPxY3(v9%9RCiD_#s96KhAd!=M zb5^y+Q|M=X+0OJcl|=!w7lr>+xu3`hnQc;Ii;bAV-N~Y9`z^a3lnI@TyI~O-_FvT% zT&Z+|1xk~vU-Oh}QoU|c!C@-W(^}IqgceH+{^Y4Np1;vE>nB zcgbN}q2Hs#o;G9P8;6feF3_<+Oz(6&hFJK!2x#Y2Xyj~K^2qi*$My+>7=N+1jB2{Y zrtN0B5(1aBBJun0wn@d!XF>K0WzHrG+1LIIhl~pM&$1O%d6V=q5=H#)!y&oUi&|-N z(zg^H2ea!I=Ak2Kmt{5&U^~!x*;QNqPmAXp1Jkzj-O>Vgg=)fF#Q@l&T@nGUV_tO+ zPbwr&Q&8sJz*vI!wmP$1F?4oAn#>Ksg-^j&h$FZ=yZ zM`)O~>QhF_n%{6>>N5v7a|aCaxbq zlZ0sMmWmYHghbO6u6%@r0m-hAA(9I074;zO5cc@z5wL0=cp6YXZQ-|NNXaYszLwPf zTu}~c0L&y_L1VwJ$!$IJi&(C&wpR)3^Ke1WCJAJX-NZP7q2Qw8b-c2 zGcUCiX|DUW^&iL(^TI`P>VA!HS-1Xdl7x)hA&~O7PKerZ2qht{qcLVVv#U}tNl)Kz z*!_}8gNav3k~Avoua%fGj@bV=64AwI$jDc3KGR`2Q#UD@xZ=6L{z6?a#D95^|H8DI+=fkCWJ$JwjB%?#4M7Xt)Uqe)p8G{2Q0odIo@_!|u~Zk6@`3D>3>`z4nqK&+s%?XL>w zzw4O7g8N?0Ri3wRDW+O=S84dmgLq5G6ds4SRHmwr=DKHJ3JW}om%RNfq^cRoS&vNq zQle!r^J?me8VSkSSne;-{qLt#zbz}_U-r+%-Bb#{?;HJF14MT^N7f@sHNlTrQ?M)_s90eh53phP`daAum9TYegW!mneI-lr1>kGjX9VFzaHzf#+8 z6|G;SUirzYNHsw<>z;>R%tAs?R>8P~H{CnGx|qKoOXqI6Fn1?ugqV-`kb5$vLr6WI z_P_kG-#EnG7;1edd7{OwZ=Gwk&ASl(LP5@q3e1lZ>?1Y;e@{g2ovjo_*Dt%f=ty@q zf5_Kho}ct?QSh5eYLAT7e#^!*RzSf)J77X!vdV2slDWiZ9?OkFv|tW_b48rfTQIfm z*7zOUmw#I|iAUpl%GFAa44>;T#q#aWjgEuA7Rmt8*>oj8opYE%q*ki@S*D{x>9$%1 zp`O(3`>m?P*|W%>QA?rAo{`M?2KQG?=(zrkCFZH9%l{J7WmLs$`Bey0hR-?=o#vH4 zTgxaQGf8b`PV7&%4t!CQnmfD|>)a)xK~XJHp%VmMcXT=xi2aR{jSVsdu`urDBVlKv zJ6$cBR4mqMlAjDggSiz#&?SP&?e-}1N&_v4zUaE&3JpnB{0Js#7;Z-39t`xQ-=aJ7Y0rbth!d1^P>Mco0@9)^kATFW?BW`8)+9H)`D9A1I#3a4&QpN}O5E z`zC36T=NQ7ubN46^Bm_keHiBsJ_$8GQQ?g)7eeC{B%wjbK~-}eSWa5o!9b#OespeH zeOclknKG(rAMWXi+uPk&!rx;BR^=>g=AW3#o{)X+jB$d&q24w;rno**!XJZk@b%yE znR9sBBJ0dfSFBKlljM>uqFDLku2te8ZHtw~6st9HjIq!*wA))G$IuE;;G%qP`BfP| zbI&An2Q+Cw~LaM3_O4Lp0D2Kya%39V)hAV8nm5(3kHBaBERnR;}+;dAdkRg+9zxJu~6UyLb* z{ZJU<|Kmm>30L0>$V#ACViG*o_sM=sLFBj_G*#=&4%WL{TykG{q7P92q4EC>r2Q-* zhCCU`p~`1*jg*|@!ZTVml?NMtp;>^!<)(_4#4BYYn!9+YG|vLTGc$?`nKyjGnOiQN zF+I=hWmkZNxdo};a%J40760T|$(jLHncKb$E4gvawn$4Bd1Tie?e_<;3D=ksSF(^5 z5^q{UJ*DJeEabRfZ{Gf!<-Q$IKNNF4InW<9Z^wP&DOH@<3F~@|MV1H3n``+d2LXY0 zx2^F3wKby9nS>hAIka+S&*^IvjoZroKXWK`GgI3`i#6u9^Hy3Q07l7W*a&tbmXU{T ziq!&!%R(OenVV@8AQ20(Z%e~bZtHMdS2$|vY=Unw7q{7kCbRlNp+YEP_UkI~@P7?^ zBRe>TKzcTCqwq}O17iv{Mds?OD$w3HXL}pH#nh9QFcXkgwb$bp>>3?0E|<4T(P=j| z{3jXm_8*h|g2|pwJnL_r5bft+8PfKXwZk;{Kn2<3@lTJ5kLNyO-Q{UIr(SL?PVKK; zxI>mOux?g?yV_jS2DR;aV7=~`|89oIwFwTi%H9@%P7z8Xm8#l#DDqn<(Nd4N=OubF_l*q?7e06`-!2e+y{ifuiOFxL}a}d%_q53^a z{0T#|G0fM0V)2IRIm?9l_Pak;d;jEF9z*{Z?XkZZ(M=+wwE1Wy`)7i0nnKSiFJ+0b zo_|f!sCR1YzWdwgM4oub0V=?yPy&1XvUM~u8$Tz%L3U#*G56%X_Mccf_JzE;wkO?3 zs_A|=wU;>rzQawJ)Wuy;of)943jZTazZ7XYw^032IQJS1iTaiImqtncY>v1MN7e|2 z*UR>MHUYj+)niod=vHjhC^Nd|E?cRh`OUhSvi5k#4QSXzgRD4De?T*j%oO!@q9>mK#2DnS=Wnh{)n z*5U#sb2koBF8-Br#VvS~2JY0qseC?4=4GxjI<NG zSHG2OW0aBkbnFfw`vPz{ytOSYNpYHo%>w3k40TV12l$#*xm3L?YbQ37!%^GF7GyK# zAvPR)WZi6g9N5%ThOhW58eQj~6*H}&nrj%nLn39J=R-Yf}q4(Wja^o)3|p`YXnd6FGd3+3P)5C?zxAwA~ zHE}a(a2rUk%W%2;%D3_}eSe_{2s}?^hqPmCiWcDdLKyp_MIsxta6e!%;R!jodwBKJo#B~kB z`X-xO8Sf1=bE6OH6FhG~9@l7JScTBVyyAx?|u>m9q6p)-UXdIQRYG z>pu&@h)X|j?$_MD*j{a5{+-WMqu?(_~TZNx6VJQW^ppiQ;5%g z9Jen{Z{cTQN0_#QaUT06v~g2fLE2N>`3X7%Gi(Ot*atm?IwO*T{G!~#|6uM{sl6;D zyX9`z?G!w1qAjg;=DnkLXJ2_q$stYbfwXEU{>AfWn__x@Ib=ESkC}+6`6l%}vj0mN zd-=a0^|_eQ!tuZ8KWRvDUkF3q{(sAdjbG(MLR_})nJmRLulFEj;=~G*BLi$bM;QQl zq5vBXyy5r&d8SD-x*tnztG{w%gkb~7XY8*tMoKvHtXC~ZLOU#3R&sT&-~(hcAvVq2 z7cl7z2v-!Yb8SBa9!zu{fr8!vVXbau7DWU})0+*k#J9h{3)D4c>XkZT#C3RoUNS-G z+Kg(AE7C4@8|qTMg4#h(_|*sz=`0jTm$(D^D|__6pt#d_#KpF0E45-PuM{qbjSeR! z+mE@3D?^Iyn5o{xU$6Q8r-0y{A{*G?=&3Sc>|a}+fKRKH!^$LL&OG1Y?SGQ1x|}rb z_3xlXbT>V64lzi^BT5IwkErl&bT$E+OLE;rViJ2Dul$#GG%h;adMMcq&S1GaeSULkM=lP?3XY7H=CAu6F*pOM#r0DxYGvki8Wy;HJh|FnL_4NPH5@O0!romSjm zC5uvqBU#|4r}$klvMF)3fBs;fdSj8OftwG~`xoRO1@fg&CnbtJ&0?dA%HTlRiV_^| z`&ls-VndNtE0J`ONZ1IDVdvbcJOH^OeMnCi>vhJ&@ejZ^$iFy|Qul@J+IvZk!1|el zY5^|8%(5z5k$%qKh9LqSO3X+el?r5yXp18G!Ms2~EFji<(ha+`QCnb}1vRBO=x+#;m}RArwsy!Ep1y?h-2p*A*gjgrj@r9;BWjOUUe-eVhB;7ScKs)eJCX zF07fmW7K&@(;PesMZ?0ke)R_X&eAF=`BUB`cE}e|6zN3xghNY@hct&%%K?!{w>ZQo z=XipU z(7Axm7!ow++*!T(7g`EFH-E76#ho##{yOrCK(?6C)|T-<4oAIFOg8Yre(M|c|0-Ka zLR6_2Ylv4-HC{(MBwss|l{#CK*v1&P5S96hyk!%UbihZ zu%>Q0^en#V@)Jjuggwv^)34>7y_O1AIw0S5cDfwg zdxii0)&DTOey@8a0N(Qd{EAoCW$WwT-3P9nUa^$-&u=UebA5@O@_E%`-5wK-Y#f1VBzK9s4P21l*`V`7 z_h3IQC^SmsySZ_H#Y%D4oldW>1xoDh?!jbw-HhwKa{^!Xl_V*;L1%_%J(DG*af^BI zjB^Y!S;~o&g3dV6a~8*5Tne|3tKy4gP8cZR~>lx6myeV93U+2Rsa4DiiH120cUYk0rSWuorzcoN;AD+XOE{wu zfK5v1)`YIEZWi$xtHZ&@c$d$d^9&ekfImQ^O70Hz+VYg>BeNbf9^lOC_7IG#@hevl zaash6^!g2a9X`PsG!}xlk@1Q`^VGdRjRG8-HZ^S_%v@qiQ+!TVymsI6uSW1OL~TY| zl;!95cf|{0CY;M!H(}9KS9~+-Kqyw@AD8@gn#xu~_|p*ndH|ySTA@=16M&V}7gY!2 zp4c%a!U=w>9?p}w)Sc+&VeZu$y7w{Ga217^E)?cQu1#9U>Zg+us>*HRx+arD-q0nk zE4pbRjPA;)w|KARLEiu&2IMI0G(j&_Y)y)uJust7P@=|6O}}39g6o0uy*<{2c6+g#V0Lq9_v4~E=zx-ss9WU;i zs9X-N-aej>x=WdP^tFW{uB4t+thP^{Vu_B9?*8*1)NG?ZBgt-X_D}Nr{VF4wD&b=| zQD6$py?oPe)&ni+oqw|34|uh0zCnh6!hfX<@X2Vki*swVQSk)Y7IWo8L1Z0TLWb){ zZKQ_Grd#H~v&5|zlPzt=r`~=B+VHH3Yu@7Hs$Yce;ahu=P!nGXD*uaCNSAUQM>E3` z(;NZ4+*RTg;~4P8^5;v1ymCRO&ND;rmAOY6ovqwcFtED110Bn`34tO*565cDWZZ|% zpnd!A9orcp=bo!n8N7ECpFA+}O%w4?gP-n&i`Se6*3IvQJHe(@3-T>KL^_YI$zs6< zJflB{1*pu6>jSZ0;Q=T7RjB^&w{MO4UB^xNSQ^cAH)Weg~Ot#|I+Gc_iHOg=Qjfv;D()Rhh>VeunZu$XvNfcl+3y4vU~VJQ*7 zpsLE5)HTalwH>fq3m8b>@>gB~$SGr|k1cvYkd8(_3~gGU6oF+bV9Taa0hrtNj%%4Q z3Oqgg0LBO{P3$Ugx6YK{axy_&i5JeV10n6z4Qvf8j-ItF2Tl8L$(IoCN=pE5G>moB zN4MFV76WB-^BqvZFW1M@=@=@v%^?y=@@2+60aESJhpYu|Wy-gOg-1dkMOpav=(Hbj zSKATlo@L`6NSYFgkU_-*>?gXjeUcOWbbUNOEi!mc=V`#0N~86hL`!cyAmvEd${q%sGR13)woO9d4%;5nyJHiKF3}uks3w%!T+u!rFJjW)c~>hFeM7Jp)oVc zSr5o9(&V~=_G?774)F|*L7IfQy;4-0_N#gW?dpWN5Vh0RF7`VOL2F2=REXLX%CjuU z7-8-kvEg=uM+5vgF=ZdrxM&IRLx|PQ?|Xbtz5}aMVI|vVu*&^)p8wa&7uL@b)}=Si zUl#kp3I<#uQJ<3Iiq26kuH}0SmQTqPBKkl0&0-K3Y{B3p{Rqd928Uvek_F>o)rmjK z{@PqFG>|T|wY9Z^*_IPreKdIRl^Ib{AsU~UNTnxzN88L8>MkF6>?>s1V5~kRXOql1 z)h{~J(<#4}qP{;y(DVAC48*L{-CbX->>aqomRdl>qKZlw!wCFhpRSK2=2=f2eL2B= zS@lN=diyzP&~nOA(tS6jDLZ^~hJmpOV3lHdvm zB59eH;*KbbsJQ_G?jR^!K6lQ&=Z|=|@9+CO?*X%Q8zMJxPEki`^SRwW1 zIl1Hpu0W*m>w_K5CS5+hfKn^0SDqhsSS(UP0bj*v$xitEzy);?Es)N<*JSkh4p4Xu zDf~AYG!bEn6kT^@d|#Fg&%D?mUDl1akUM7*fA)`LegfV=oCs{X+{;d)CS0}gMy~b1 zDBo7P8_gUFkyVEF;N%_~iMQQ&=R-SCbgg2(Z8^6~Ob;g01`ZZd{Pt!(yon5`8*|n4 zwu#$)hW5`eX!ri|59P3u2Xy|>VRsOx^d-oS@$t- zdu#H&h@cF&6=C_n%Ny1^0}btW-4U^yT33G33&*ebT2)49Yo5zzA1h6%u#6loR~`AP zTBLfo=ExEEj92Q{^qR1c+69zs9ayc|tI2kmSqh=cScQt+f6NK&@r?H^@r{+=E?Y)z zfbHfVIo^}6cOJ*|-X0j}I zm;_-9QF4|YS~93&$PsUAuVB8hkuSx!6G?jEy$p{MT>r}(k;tct>2JX0^}P*{iAWLM ztMUH&QK5y6_wLMU6xr(37775R|Igg+BVKdL7c#UrX`|kdJdN_d1O{AC0{lvpC%gKA zW0k}F3n7&;%L)GEKNIHff}gomtSCwdMBbJZ_}vyh57VY2OKuut5? zTYAE3QWczQKxL@s6yW%E7+nHOlpFn7p=*n}7L|XXz(~3YLDOq{*B-alWtLq^&h!Dh zVoWL{&lRp(U<>1%;;>eAAYAox$F$x--JOv7>(V(zbf}0d_X8|UL$>Cbu!nw@#GTVi zuNtCzydU)m;FYybk&f?~?tzxb)(*bgRoYm>cP3BB`2(fUDCg=rqQV^57$CG?yUqnW zp7}`0j1fmRFr;^N&rJOWi0N&2jSqT%&`u1MM_ymNy~#_lhl5*lIC`md<0AbSctD_n zmbR&WZ$_ihStljMNJYOdkJ&>a7j|3Izj*K#7DxH4I?v?~tE^ZueZ0)KdHvsJ_QF_7 z)CrBCx9>%psb5GBdrliRUG4~4ea@0T_tcHgS|jloCbG?0OAhF@0y7Bv{+zNhB)1Nq z_dC*Q%XhdboB5-PE-av|lVTdL4vmkgzi1)RXhcI)O3k|w1__WJ5A@X6P9{huktK0!&Fzlw0 z398BfS@VXw%x=(S?aUgB#H~zbeTCZEgiYYdYfOWq^BH<;cWs^F!8w4q9W$D&L`}aq zdwb0bPFJ^SxGP>eLSERd85FCnMSedUkwyN?9jn{^j0V;t6RQ0W{3b!6N95f3kJ>3Cg{9ugzBW+@LY<&3d9&-6 zJoInAE76#VfM0q%oHZ4GPNl)x$gp1#yBiKUX$VcxlgNN*r8U1TfM{W~4;0{r)$?d* zKl>+$aOb|aKI&$}al@8o?gK$gSAw;_RHUJ(tZI>ZG}3zM?jk?>3I8n8c(ZHdFiG2m zRIwE;kLx!!fBi04+Ym>IGuqFhMsqOhAII+@@|r#NLj+Ha3$?Tw<<7Iyv+ctmv;?w! zvZpJ}H3{oitj+Rs60wt@9W>IyzzKFIMgBus-i^Uxw6V2zLIv3aVD1qOEe5?_o%oma z&z!FwRqN7Son9PAvONNue`@c(XB&GN!KC+ytU$`OSI4eVJK{6$!N+IchFaZwul zV(jAkf|xQl%;>rL2hG!o;0U#q;iHLL6C$f#n=8K3ZC%|(W9NPoxrb#A_P)e!eIq-DIQWcivv$Ql{`>(ja*g%ZhoR3R(rer0xhGx&`dNUO8l>l)GmE zV;jwWSt~A6JfN7h$RaRo)WtWUS3#`Cui#B-_vzlbk>JezKPHfm+5YmseW1r6otoqN zARx&%N%@JAus-b%S8h@nM5D2v1Men^W9+6ho%^Lnj`Am}oGP?L+xlaQTk)g&w&k%R zjecop7hSc88mT#SJD|qUAwK%r7q?3ELHY0b>Rm*YWI~fQIF?(f;Odv}wAP6)(K~J) z2gOuXE_14xcIA-_yo~9QL;At;ys?MP5T-k>^JWe`G@YhnW*%d~R5wPB7+6Q#<{bF2 zd3#dXvU2&_@RMZ`In#I6GoMazCybELO5vM%2h_E-UcVR4&a4ON>fod|T%Gxmjm(tL zz;EBGZo6QwliRdJf9pzSiU&MKYhEZ$S*MhA=R^E;5&)fJZ?>2#6}w35OmWy9rBplQ zOJn1}*uSTy$#P3pSPZ6$e7i~|@4AUI>rb8hQPvhC;pG%kTy!|7R|yTmw@M@epP|?Y zb6gs@QhCh0+MYlI;Ew*|=<8{_Sj~+O3r;#X%jfHJ zZDQWaR(MCsqR(WWZv4**FM8xL@wbes1D{w;PCl+rg_fk}n4K6df#8M%HLb#)YKFJ} zmZEE0o}Mv+BV<=s^m7weQts^R_%bO)9+u3Ho|eAU(){Z5D>F?)*meHodDgK8PX4DY zc+54HhG#d zIZ#cc}Y0909fQF&|m2sC{QkDCWW>Av7zi*Kj zuQ^92dDz4jDHd@lB0o5Xv+`s*m%4C{qa4A(mR!HXzFcxw8Ik*n)N61dVp@wJ{S9GE-M0x+iKQ!fsV6;`x8S&Q%=YRylU|0~PG>Y3Q{qp81Q{OIPKoICJ? zj_i9jaoOfR&O(G&X<5Qtt*5nAmWDcqtQ_BMlI$R9f6^J~cePfH9k^TbPwq2Nrn#10 zrPP~MPtSQ?<0h(|%9Y#F0OZvQ!8c=e8lFjhSP>V zz=g0^@+Sv*aR@XUXuuX~)l){Q4bbs;Re{g-Q4jvi>VIP4i;vaTbQ|gWGQ14^<&Uvp z`}>dUrx_9<9Odh@F6Oa1^^5XUSIxq;A6@lA8bkAxQ8>`hs9T}%E~5BvJ9tF2crMyy zJHs|<@y4dCHElK1*}2qRY3FV29AB;JqQ};uUQ83&dj93fFxQ zBeC<^pRoLv=!mD$f}_(@HogoqMlm;7EBo!;H8D;n<)Kx*5D5!4g14GvHqB=1!Q`7s zJ6i5WbKLS^<1L--bV$c+6RsB<{bMYM7olV2-{f*rufQF{b)PMa_4#Yo#miyx5L)tH zyYkc19P;rabpAkfeJqP8*O}>N$OVadf6O}jp}m*wqXy=!hs&ipCty<_pW1;ZkL#Yg zb2Ca=O;uM1HB5bCG=zPP@Fg{|tv=3^a2Y}-KkK-c{oWH<{0d_}yDRwPc^Ly12YDIduwP%QNwiZu;gS-h6Q9>b^I_Quw-vJ2}@Lnd$Op zvW=?E+0iX)l)LYaQWeB65V%gpzYJD|UGPt##&d_O7U#rs?T6zC7$-aV7cbrrR7XMgtp(94njEmt_zGiD^| z4TIQiddw z;zV<9W~Vt_*7$UpP%J_q#L{CU4_hCGMiOd+IgZ&3em=oj@$rrkB9apGJ#Mf?PSp0a zx>w6uSSM!=`Ln^iP`AX|aPzg9!=fwqFRSG3j55!`zr4r#enK$PQV(eM?v)tLwmgm| zk`MyE+k1|&;%qv)RJO-mf6qqNp$6OdTk~y7UB$$!I-P-H} z)?K3j1bI#MgzZ`DL@c{6aqQm~IFd-mZG|v8+`0dfN-wgbqd?yuknUk` zn}W@b^xXB&rn+=$36{7NsOx9~4XXo>G+?a9;*StZISUh?k{^Wu!$Ikofq+9b#|;kr zYI;BLQFp@|WV_Jrd#5=s8;JIJ@8xjwy#az47ju~O5fcUx)V0yR2Gzws9YYC(q(eeM z-MGVp?(%W9WDW@dFUH2*XNh}PqK*g-6oLLA9#5xf5YlMsnSlpLiOl9jNTPTfCwg_7 zBugzEmPbjFGBkVr2$UDKKY)PthJ+8d$evL4%AbWw(cvuLDe@t{+c?MgWS5Cwd2@U% z^QT*S`f)==Pwf&XD|D^G&fm6#v&yKFY*CY-qn2BD)Bo7gJq4Qwj_A0>L~j&`Ftnf- zl+J*I?DnC^;Q`+@Jdm9#hm~l3?Z1(EJD8E)48t^Di?Qgs; z`4x$0)sx*&Q*_vbD3TyJ=|u&^u>;+yk|fk&jVS*`gGL^2v9ji*g~|iuslz+^ m>=!2c_WierEmKz*aev=8)7dI>#r zq!S=uXd%=u?)|#={(k4`+?{igo5_^*Uo&ex&w6IwJ=W8pzsP!#jEsz4^U*^?GBWah zGO}~cG*qM(!n?SeWMoViTvb&cYpSZ=eCz{qa`kW|BMUe;V(x6r*jrbcMMFnHr`ji{ z)pt{ly7T@E$_UBDjF)iTU+8+OH#H3+2N4vUA%M2S?%Q6l*+H&i@fK^EWY*fDt*@s7e&$AYZ^W>r;MKc z9-t_Ho3ib#@*dsOU)0|Y@LU@^452&J*~iqNQ=GkfDJ|L6^JM31BQ#PN$YcZFJm-yE zq=3DQry^73TGYHm{yc};o<6QTf|snv^;tG|c=*?_X9Ce`dZ!5QkW;q(U$Vq4F(t*2 z)0neV+T%ONc1k`0kDhC6%%-1p8$D6E(`*ygoo!OEf3d1MynOf9*K>aNMQNVHM88n< z{8R|=zhWcyE8|>0{iS#G+(!rMzg)fx26D`aUAnEwIrkwkDLi@M%z$X0eHOAQv)p6-i zuRXK5-jZFoNy`|{+E0G?h0>hN>L-o$xeEs`R00y=_sM1H#Z{zb8H8VmKR)lKqU1u3 zP*GT=V)()>O5XNWGJt;htGGFJ`cK}mmp5r>=WgBpanW1##e3O1wEU{9?*R@}7OJuD z6&5MZ-~aHKJmI3F>iNev{LZ^Q_j-`oPqBJI@*MINQ;x~1z%Fh0!^hk#mosQ?y<&WD z<714d+SqNqn<1A!zLkB*dYjqc#b@;?l_@nvmFS!KS9acle=yupnSZbI$R?N4@J3-& zjf(s&9~fwUW;ec3 z#0h4WdYkq@C`a0W+C?#qLMYZ$jSUWiL;D9->HMxVUV>7eTvm#)Q9t|fL{x|6Y4p`L znuWLc??3E)M3Y5@QUE=aP8W74qFSr$S}WJgX-lZ3$m@|>x@Xoct$S%eLgJTd43aqAwDI3d;T3hcm5{6Yy)AVO@k|+Ulk4) zdK4ai)-y_cEM=sW8>G5NAMqVOXZ*}mR(zAOj1m(W9BuUlXE>>Mx9fBp8b8R|FY{H*JxKo*ErWM*Udql!I1pC-0Qg``QW@5 zxX>q0I8?pqzJ@06!vgJ<`^gW@^e~z-+Ju)XcRDT$(uBWa{|5ec?b}7AB%8N3*)#Eg z7}T54=b`W3-r2qees{<2US-n3`}&^ePcJ)(I{mWmaB{I@u^)5Dbxv@uagKf}=)CbX z!})>u1+g0v=S7#qrtQ+69=7&$mh^74W^_*VD0VjYe(U?xI^P=CI`~bzcRMOE!X$en zTPnLYb5DpZQa8FcQYfu74tHykN`az@RpFgd)Ctp(z$MBEmIx^gMGg9PzIFyZVLjEp z%YBA@=lbY{d4z?8mCGKK*_4@h-9}K!q&B@j0uWdkxM9-zWcnA-H_<#I)a6$|cebw2 zh|MSgo&pq5j3_1);@J4O>v-n)_3_DZI{6fNJLOeM*7G(O3@_fJJ-o<7d+CA~?FCvn zn#?P1%$8T2u1GV*Uh`rM<$l3Dz!c$5lri2SbNO2UJ&NXPiT~V|Mr465V3llKf)bV(SueQDDiI@ACCn{tSavvxCyl zxh(l(CG|yjau&;sKS~z&m$(mwtlBl?;|v6&HPG{TQUZ7nSxZtz`mSn+X4= zfs7sH1RAaw5gxaE0+fG+&y{&w*xSUL4XIXJ`Wi&pb{iGzt;6d+*Jbb1FhOR{QbqikuM!nMBUHoMoj%b906Cz=a==0eowz{*oXX&ei z8={++oEOcO5IE3WSnrwWms>e^-{0<6s*rdK=umbMO%agg3Ap`G@ea2Gz*uV6Zq+Hp z)f_0x7<#*z`^C9SlcZd<2@FHQW@KPbrHN&-FFlmr} zh)u#p{F}HF`g3NXe3ZO4Kp}}g*&;3Q{Zgt_hEOIj0hz7ZbpycbTedj$v<$Loo@HmV z6xc(|*^wZo5%yycfBD$t-Cemi!rjBMOjXJ2~`N+A%rjZ9DYEmg_pVJs>_iI(n z63eBBD@PnLCnzRAPt;6fXvO`)k8|?vd(G+(?Hv}@=#$wNbc1v`daw4HS*BQ4S>1Pc zSf9`(q)Q-lKzj&&gw!lkGtfICfOlQkPiBA9sR?ux5@Fx|ut3u}EZGv185~C_1Mm1J z6GZnH_YC*)_c&)@O=obCT+l)|`ej}y;uJ&CPMyVINgt~u2|YOY@D(0@3-|E|Ho^r_ zHh>Pwl_*XDKFRt(jGylI8NDumv@y1C0*r!2#mdBIq`dt;K@5Gjzb4EK!&lnS8u`5i zyiOZVHa(3!eJ#I2D!0XdE{zs{HVZD-N8WGg4ALglxy0I+9cHdJB?Yz;v(6)552&TL zVzhJf_6T!ZgybQ>Pi02v;^|n$?WRGbTZKGoxw)>HvkHayfPeR^aK5@@Wzx0=745}2 zYdv#uQvNt?CuVQ5c58d~D9rpc<>ZO*;qID5>K7xPT z%@xqPT>ED8d%_b{z7qbuLPl@H;kW>AR!2)CdB}r1Cu(-Q-^s5@RZDBLxL=!;kKild zG7}(ND(9RHJXO1Y3D{TBD?f}|;t^ly2mz;DB zTz1`SW!gybfqr9Lk78o;K{I}45nF=UaqkQJ+bR~HMbT`& z75RWPXc!O{n-^=bi&*HI@4V3UF6BpIZqed%Bl&{!hAf7zL#u-qZumL>a^{yXNR6;) z8n^WwMGGyXy3!Su<7RF913xxYEZeynczL^NhPZlIXa#DvW@;}aMwzfB%anVVf<{9M zPeS&LrVncq4+-?Nm}FK&8clDbkY*`PW_Eg_AqJ`1+&wKk`mw)o+%W{5f8?OA1L{1X zh6-L2)0YtYy`f&Tx2>PFclwZ(uB|}J^eO6ODEMfkY_$9vk~r%_{W>BdjJOhEc_sEy z(4xID)db`Bw}DZf@Q#mkIaS}W2HkUNu9U87tGP56cc~r*zq#2ana{d>KM5>N-b6*6 zS{?14aP;0RRpdpB#S`5})?;S5Hhw_s`9~=~I68r@x~W4|s@t;XC0ecXE?oaqlxMj) z@RU9}Ki@RLAKB-wlz}Sxs`*_9uD)<3-bCVun3L~QS~+bK0TXvQwsTgjlU)Ag8%OMJ zFlRX`xp7&6hKeTkEOS2&zhYnXuV0FVYV$WegY^QWf(nl65AS`zb_j$LPNFDYkng=5 zdjU8<%g{*=zPLbZMGuO38*3bM%rU~2$qv6B6$xdB2+xRzr-){nXDrffm82I?>6zu_ z=2PX#<*_++WX~ySL;V$e#oQ&2y!w`WoqfM=>t)DvV+0@9-?ZTK`B45Ap3?B@v6ji3 zdy^t(HkF~SD4#mF`R#*5>>5OBy;cF-;V!vXLQjs#V}s3zx>ULNd*Iz-8dH6XQJ%l{ zr!q_ZKff%CM`&+m-ADN9KLWL#Y@^;#>z>q7tWa}vP5|OKZqs=xgPxVXN z60-5J3vCddB%W-&=^z`$Oj*@5Dd?@UxSi{VBW>#|piN|HsscgyDZt@tnAkJ^dFZ@P zTf{MJNx;m^3ez}BEu9q_bQ0)n0AG9uMytMil`T_(m%vI7z)_Mzv-EI^AAdoy<+tAKkbA!SJ|FyufuAr^-+QMrR{`q=R9n}kas$!bHk&h3 zGg~3TM~(3foxY*T#~c)I&)J8k-aq{M_!sj1+sAYL%uk)aCw@~_XH;8L3yo=H_vAz} z4_$WWig@e(4x*>RTf-yI`!U`!9;h#tktPdFm`^OqP>-+6n29$CXge$M=kZ-GD4`yI zEjP20;8WK-!mcs#$v9nQ?SL_VnD`CA7>mUHU{GQEq!+OOA9=^5roaF6R+iC{Vl=7DeX1X zm@g7>bozMq*Gt|L25L^aeZ`G{Vd>%BgNgW}xxo)F1~Lk6*cYM)+jn@^c(Zjpb$$C- z(CWy~p>q3fgqSZ@=?%@09oogaNd#rUC$o5Isyg8fn&Y`ME{-{v{ue&-#{~=uuZ+md zzY6V{lF=}I;}7B`O9~+KFd_S<%yE-uovi3G5pnjM?4qgF>d)wSu1Dv&qJ zK9_&Sp!3{B*4E+W;w{<^E_n*zYpNVIO7gF;{s;-A$3xnAxq0Rc^o?7p9~?XGFEWo& z(HngD%sR}-`YtI#qDp+1h3cKb*H=&0#G=eEZFqa!@{fC=@>I<`+%7FHaWJMTc3UD* z+~nSfY%DGFt97_j&OSW!xpTY5LV+uztCs5}SEvgwwgx*vWA{ov=;{e`K0i-M@mlfq^h0a4 z>DJ~S6tEv!4qB#KU`=#aNoQzMu*jM9Qx$xfVf8jRk!ob!tyDu{m|7}dGP%|t>;hKX zquYhjRndc3kX(jY)JE}`++z_Fj+O7ppx#*9Y%JsJU#Dp2F@m|84^qg#W z^z?N_sJB$It5JxK?Bhgoaf;dV0&g-HPUwyhRtC`nSH!-1_u$Je?7fTVL=`X@DjLq_ zDLCn5gL|gXHl4k_Eyf+D4u)3^O$`SO1oIhV@1x{lbt6X3aUxyPK*LT{H5tZC_U=bi+%FKqa%q zVQwB_Q$piIUyGyw)AjX(KFz=_29{U-{1`Hz+VAo6ouIcS(fpKSNA~83a7VXD0Rhz z(xfB(Gh-jh=*w!F!(n#^ua--&h?j^uNpy-L?fRa4a3+4S>~reYYX@{1d<%@zj%0hL zzaxov+9v)=r|F=nqBWqwT?k_)3O9$Un_r&C`AhRQSGBwn*o(_hTWG zbi)YASCaHK&9&$?Nvz&FH=P4LGDi3YI!!!voJ&AFyDGO*yhg7MTQyy0=J!@^X5p28 zonN6mex&yYa&$qa^WsdwXvkv7wNCESjB_8uVK3xVKRqb;w!|IDO@L3^1udA*zr4zM z7tiaQIiZQyhNgR^iaj&sujWq|DG*GSE5B!3`_t&DhJm-HmW9h}tGrG}qT8lrT5$vh zB^}}tGO$~pcjV+e`BDKrG3#G8JBiC%TfmxeD4z1|dlR2wRu9J3@yzjXbd$4JbN%kU zv)H?ockT=?^SIl^t45-cQi(!9_)>~^4|@{VdW7e9&c`W_6CRg*iR_f?x|_1| zph!34d|6`JBMxokTbXu3|Dz%6yrulooP|%tufb9GgJf=#FHeSF=?;MLzaW(~@k`R~ zx6{QGXpgAcZ>~6&Ie1-#-6+49&7xdRhPYY&{&U5rx0RpM%}WCCAB5kj@dvqHOQjn> z7g+Nm(Jo%x;I0LDozDWQCZV!kp!bF|A~gA9x>#TCWVA`_zE!ZA|sBCg8EJ?f+`@HMg+ zERwj>x9c$Bgj>od8JF6Fl=#YjnMco0_S6PbIt_zW(7t79l+!4dssapV}on!+)XzQ^Uw?h#bX{y zvO2Qov&4vq;E{-k7WbbH6r=S=_8n5c&;ZQlK|dco-W> z*hHGm%$Ai|Lj5*ziPTD~_uW@AeD&nFns)|=vi58IvX7>Cz3-EeUw%$Td6VoMjS3n0 zIwfj1Pb4o&1SJdf8&JbW3Y0w(KnM2uj)txkgx*s+o@3CbzQq3g+MfY`jlE3cF;T-0 zT{FtoDi7>+A6%2j!bk7+M*t*ovn|Rgm9PGp|9AK3=X+f%S;1aGheO#}rT}86ep#mf zgzNk8|Em`(jkRyDlM5$kvSC5e`v+hHm;|+t@%;zv|nSwgS<+ zU#J119Kjov*tguVkNl&89Ub$;J)()@=uqitzxY;bk=A22 zf~n!8ynxJe@_!xh#Pvdnb=b*XEq%>&*$grm(zsSsJXoN7g7w=S(14+06}LMK>>GVo z`utWhA+e|pgZD?%TcNJ&qouAp%e{$@p*`9nNJ$RxumScn9Hyn=HEFurAmF+Z%c0hc zb3NL}!L61&?QQ(W9pNda#jS#tt2Opbt<4e^NQS@;)vC3UDYA>d_W^iKwn75Br;W)& zs!cUev3 z&nib9t_|q3PjF1d>jMIJ!BgJtF|4arAf(iFNTmiO4_9y%w~2>t1<%&>rp-laiz%NG z(aez7`g(+V4Lo>TX%OE&4PEqJFGa-6Pe1z})9K-3KcW994e6%X@HUX}sgL4_A(u4Y z-RXd#UThnam_U~X-8q^bif{^<0s=6=pwYiOsb;AxD3(v%T?IGctD(gZHd;OOQeV=Q z8iQgR3_e{$Vuzi>%z`@J6?0J1-P)f@nb~W^)nv~4b}wOJHiXfE{Fa6GP$B37vRIj$ z{6^C66So|zVo%`-@oYI>65$Qjo8T<2Xd7d-bbo=XB3|Il5RIDzx>idyHBIBZ64Pr_ zMmMs8)(X{jLSP?o%ev`9S;(VEY9YbpWj~S^nYTUR^-k$*Mqta+#6(?J^d_(sev8Rh zD)ZU#g;owMcT~HYS65NB4+Y;%vheY1=2p7xnL)4`ky56!Jip1rEj_OK?Ty`Dv|@10 z48%c8gBfT$r2Aq!hW!yHba?^W=;@rNkrjB{*m(-YMkR-dbJQLMs`ad$eK+M4QMGK= z@SNFwzDs+y;E3H#RB~O(;&44;R*nNSZ2#EpsfKBpp^lWWaoT{48E|jZ5J>sxYaxs? zXv;HOKlyCE!Szf66L|)c8L!=|;Vn=;g!ASz9QUCMaIN7n5aIZd=Q5$(lSy36)y`oNy`vZl=^I>kerw<>MB5$=Sn`OKh`-U$alK5@7;8eIK` zv_WsbbWIQ0_Z{uOj9J`k4-GF=HM&B@LDhT2Ge<%7Q7iV}W! zw-gS!6fl+$U!R|r#te~8L5--9J`rQfCwCfQ{sdHwjG%!DW z^RVVE^*Ned%_p^3i1MUw$A0~rG(&d}oOY-*qRklerk&ka2VeiAlzPuGy;7lnRK-fC z>7}Qw+VDK)Wyu+*TYB+1X|^chUU?W&dO3E!!L`^|8{gt<0$;LeTK_)e;BR5w?X3K3 z%y^|E9CZ2lmyD}?r!Sfp`fZmXn==N-;j`Iw%v48P-!1GZ5fmgffaR^Vk!n4+%X$k1k~I)$GDW6h$(4?YY6U7AE)jjT8>V4JF)}4 zgIo+2db2|zLF*$0xz5>5Ama$!_N^79EEFTbnXz#$lIJ9bE71M07@h4S8X6D1H?h!t zb^^qrXNYsRgS22Gr`}}>%7mreiXGbwPH(NR8)Zt@K|QrwE8Nbg-*A^=PaeatZDH>L z@hrvHh)5N_}zvq6BWqjUqK<=jf5+GKe1%ZKJWFqoh2z3;HlR8=za9o;7< z)1!$0#d_Jl8cnF z$(q40qw{^UcV1#wP+;32G}ozPdpl(b1?4*;Rjezc- zU}3@s<^uuBjR6~XIA&DZ2e&M@_(C`OSkYE3``F#nZ%Rs70Ji>9Am=p2Y?x6aJ}sVv zYpKL=q{7gWMhL&s&oWlRRzu#{LMxU5@XRgfv^!KjGvGV*VEW7<1}nh;RSVkfEG{%? zSxCEmil<(X84n}Qo{1xgKr`krO=9cr>X1?6ZuS`ua;eRIh{81gS^JE1&v_ENc%n|p zx}{R=I+|(auIITa5*)3nTfN(;io;2oy)5Q2lk@OQCR!@S@F2u{mx zJ5a*;#NRyS`_0GAj_k+2Wno5O0yw!KN(Q7g>$6$dsye7JI6#o+%|u}-uxVCx1ro3+ z%PE@Lvm>zXVV4vlH)5vzpqE*#+6@Ac;Q*ukwyH-5=^@H@GYu8p>P!{Snn7DYgRLNt zmOv}VFY;cQ<^$#-2w)?(#`$d1&;JT;NDUBd+ko%VXF^McsUG%$0$t>*4v4XEuDxDt zlSnY=uqjhb#AelG;eKN)&|*Cjqa$O;v}FXb(#ABOFbtv3(4X?>omDsYc9U~$!}ci# zPdQrQuGHlt1_0m6ra^e0rBtlttnVPh34u$8LAEGBUul6KyMcg8;wD=Peu>#1xogy- z0(zS3FSVA}&ibkzJ|0ko zDp5u97_`cyMAvADCB-_M@$5!XL#;Es*A!qPyCFt9`{Q1nRI|SYoqJcU^yE~^Y31dp zViva?p`=ca8e4?3k1%QH4k$;@1k07Jij_ywVPo=cSI*GcE%M08%`PTo^X5k4?(SmK zxV^IgHe)ALSZQayTr2GCa8@@A@-qu-q5SL9Zf>=nj(cl$V{l0-P@qi-B2^4Qnwe)A zl-bo#9d2Ah-?pB&zU|Pc z+8b*frZe(ZmCHQzJd;B18t|X&fly*V3&p-l_oL01=zw;m@^XxC3C4m__UHo^U=_us zH3g&;a;u=`7YvLIMRqi~vA!;SXLcOa*xPD#aA7iXr5;j#CY+Gj4weQ+joq zU+{#9Uv;qbw35N!lSX_XgBI`jHGQNKPg6re+}?yk=Gl5ZCv%BA)9p?(w6hg?t7heqcygSJOy zumbl;1r%JN-9$E$knp{{)^AV_#{VNZ7jukL!`gE-$)4%n=NU1T0fjkD1Q~I%1eJIrawUYYN zv&L+xkNb|vo08@WL6e?{7>{0!?J8aKAMSIw;iHbuD_@R$7LD}*u$>&oe-A^|3c-O7y)yQ{B| zXhlMM=-tfb(u3(83`*&wcXGqa6Q5#t@T^outlx=VJK=+dpK8m7I%tU;L_znPO1)ehwkTLPg>3jT+u+CjT>%F?%e7TVk*B2-zPUI zP0O0MyXJ)&4+pmb>ui-E?HrKkG~?imZ)wIyH>(sHMprF&O2U>WKUw%%g8B1_DdkH3 zPsK{_Bf8rq8?d;pcdjk|=r!XW^A#Lo7EmtRl$3ziM z`(M{V_d-u54O$m0Tem=UZ2{|brLJdv3X$!skzwLTtM-F#`0rs&QC2<0R*1Yrq{3kA z^m+>`Xeh}OyV~2jr6Qb^ZnmEf43=STG4!>Xkl7XzFJZ6r21-UuM%p$sG=@1h+DA;! z3A4@%Y2@42af-RQ5IkG%olKXXYEBTQxOCBB=AB!evm_)-pFN28J7W&fmp|bw$RE;^ z75^ddpsa+crE9Ze-gSY(mE?weXnI{P9Cq(?%hq6VZFHUsc8_&rWTdy1`(9nk{EESC zlK<;YwpdhW8PTEU&(;kz3WU_?TSa(u5h0t|L!jf9vpAdG_?C=5#re_QM0b!*HgTPH z=$-O0E!KO#n5^Nq_CS~LMGLQQ3pyxycho=q#677LUz?BNOsr@d7Va&5T1#aH2?5K< zwN-KHJqE6=Q2#PT%-V*=MEcDfojLH*5hJUUKtuSdCEP`BsU6 zb!7*{SKpSCp+%5(vMqkZZ`;>4DeLIhp~l=L;sm$*^*t`}HAnyUrL%-kLV3&ktb^f* zTyFmmG2k)2JeQmE=+o}@nVeZqLSF{p#|>A`0wdr^JJF9JgW>q7)Vn4bb79aX!C=1V+j#R+kS|`557t zxOJ~;^-f>))NGA}Aj|LO^aoqfi?AqGH!yRf+aq_~uB2EE@9W_(Dd~TsnBOebhNvs! zS?5SBc8~+_xf+=iBtuMKFbk>j?j`U*Scb$tz6}qPGA@O3jvs^!VmckT!(wJMssQS? z=n}V-eSq(NioFC*enk#-pmWID`OaQiH_&HZ5^qdbUEIyvB%hvd-4L|oR>Uteb9;=# zpL*ibC)oEC%B)Lbtwfrm7%me))U0l{k~-b-BYu#1ak(!q;#Xgyr#ahu3ERNRk|Ol-hxg? zwflh&T`LTMLvNM5de|??N$V01-ztSxW&1mWm4yX&8)xM*y-H86G^>&-s1&xhyiZ-8 zt4Wgw+3FRXNK9I)s}DV!XK;WtrKh8mS4Vn`5Y2}lghKK19z0??=;TSQ!9`0Ee-UhC9kKF2mv~*C<3OG_p zh^@7p)g)@Wy_Oi5y3CUd>jjTs^CXmedqGpOV$PGY%UkOeU<ETL^a5Bc2_aOy z6GTiJC#lw-<&EvFt%Pjy8#ZBjmGhP4{PggT!;7Z7A*6G4Ox{n zhD=1X6{P_5;yt539#6y{--YeVtv9mh1|}f-gxh%v^r{QH;Vgh`2XBw6cRKSE^F)@S z?(P%oL+=Ii5@F$NTPX``T~wc+dN=-rF)838(mn(Rro z7Ikw2jYxUHtwL5#y35%fC^#gL)w_`RowuE*%@Haa-smek6%_I=Oo^``nb%k(zH)*-LNz@S-8ozS~ll*MPKCoy?)?g1&dgyZD3PTSez_ybK34kQ{Ws1 ztn|U;1_9vNwXI=cw)dmnaXsG}h!RfoNgR=R*RzeSorZXg;M_&WYu4Vbj{9^dFUG-oW_L2&z#iMKPpUEN1j+=eB&tGc=C^ z%`T(WL83!l7~?R~2SHtw7>EOZC@I{k$X)aKf>3r!Q>sSQ{ybQ z8jM~Ty>@a~7HmnJN6j~GH2Zkun`|kz#V9V9su!b$GxK_V-TEpk&8^odFP$3AkPC}) zHaqM{wgr{e6{R^^RkaU(GUvmQ`7tgj;=nIP6+H$LTO8LZIRqn?b>PLen@HxWCm!B#Jq`I_L3 zIK4}%;%7FBJs92N238E%v-J< zPcfKuy>PoEedLmaeKEXJ^m27 zi>Rk&bGiHU)pOn)W`psl_DQjbf9_POdHU->uI}Y0c-2?8zt&%-+@=2LCULBm6G`Xu zHX*5z5*}?c^kU4!`^ua$+TXtb_U#P(!yf>hN8Myy{p|Hvm z{!m(tF$wK|k@*}=wC;1NAP+y;e`X-TICuD{^260^w$wI>N&rgveU0^rPf_Jikjt(S2`Y*o06h6Ssj;Ao?YW6N35Z180E^ydq=UF?R zwV@4m^RUvt!N#Q>v}7Ct;A`{`SkiP3VdI zvU;fo8i4J-I1rI0qE(g8VA%R0^Ejkrs)5*Li}!*0;utY2U3-a{73SK{W^7A|`UZUUAwDg6bx*b@<62g;kk;wx+&+<)L|w(W={ za?HYXsVEK>dI^AqY}n>|GYSR(*Afuj zDL;OzIV2kCr#|EO-B0m{8ur!=uFClW2nj0I?ClBhpuFzGRly&6~Aq^rIcpcOm z_Ix6uboBJ4nK&)!_|^d4IFO=#0oOJEaMI=49utV2%~oTincr-B2X|?uZ)yQESn~VDK%lPj1RKY)4`*y`d9g7s5+vXzIlDDV-Z?X#&6oCQi>a?Y|qt?O2 z&0AvXS(pG*=u)KOyu7Jx9*L&c$^PzFYX&0GVh1xPZj033M#u?-*s0~-D%Ifdn*rP8 z2Syi;{SO>S@WL5B3d!MhWoa(&J+X8_PC0lBHCaSMr8S|;_Y@+mQ8SqA#PZp0AusEd z<*v%8%7sS3uvA?=VUIs?VXjAB%I5Ojfj$C=(qz4GAZ<>bfqept{r2Y^ucm%6r}slk z_36q>r9v5poy!3Gs+a!wuU4WdqHB-o}QjPwg7->LThdL zeffAIOI_@57BD^v+>VkKi$z4qZwWMsvtgT&z3@^*RO6~uZLC<$KK{;*So6%{OdE;Q zH1S2bbJ_l9?6UQ?%w!P@SlVV~hiEP*J(Dc=$K>z4qHZ}6E%g3m;n-`Bt2fwIvCelX z{`|Y6{^T<3Sr41!ngxJd65S11-eCDVEFY`o>?!9OUZ;_xWP10U)v1MdQ!$ve=YOv2 z)_$%bn?%?c|7RUinqZ*}cx6xLQvXjFkTy|-6#q*~!Ty*26WCOwHnE&J@_(Bunn|8c z{(ns8lGFbD4ecLWNh@q$%?Y=esdirZlS@cT*qOrt zwf+CzxbPA7Y+91!kuy#tNgmGtxOYo9Zrj--BNSj?8YgiZ@Q<_1rO7QIInD#Cg`FD< zDo&LxfPJ-pk2YEO<^_l8@J#^DM`NStDaKa3F%mIV@NM%?XgpWp3?CVL)e5FF`Zs)` z6st=E?W6vk>Y?69uaRj;E8$9_l{m;=zBGMf5dEuB&&SKoe?1jgPAtuss z-5TyYi8|iRdg6bnV1MnCC?&ZC?AbxgXZ$<*4~W9FVB7Sg+K}eIVjQK8GXf`!uzMcM zkMf`KA!_}W%R_j~6w{BoCj$ce6nT@k@4*P2opYXzZ|4^N%ZzXLH-^_e;C0P)sx{c| zOL{J~aG2(K*Wq989Djv{lq6>8MS7O(;S5)oktJ9W=pWoj5lle&E zTfQ0z@i{)k&`ZL2{3w$|{|S#LeKaIA9}j7TPgx?Z^U^GYlcz*Y1=zWO_aQzS?L z?CXCs;rGSH|KMA6meO>gy6GNdJZo-`J;`%R-uK^4B@1^VPY?^DE;&96<1V_{bEnt*vq+*0ZK^Y@n=DJ!s2}cW2Lx z*wIqL86Sv*{UMWovycy8o}MKj=t3m!rK(G1%^w!1KW8x|KmQN%N=exlR3Y1-GJ9h)$6+Qb^A~r>!)`8 zX7r2yc@38$6yC?WI%b_D)gdSS?PHrIeDNQ+`^)2Z!xKN<2kxBzjNcPaKT;p{sM+6- z3$!hVc{`Xn8YS}b>XqDGe(VJ@OV;22@5pZ5C%aa%No?nu{v6!j`=tU1@8g{7(0_OL zUsK5_-~3LF_0Aeg|8bARO%ijXSk3*<`#u(7QkI^};z;~Y4()kPiV?b;_^bcynhJ@4 zn4Xk-S9=?0(;Qwb{Q^gd!fMVyls5d-IbNr1AQwZ=_`xk%A{{r z^*50owCcIXHq<-m2$mU1w~wzD5p(NGzV*Kh-EpvBm4l6 z)SEbqzHSScY1?S5XRPK=MpQRjwzC#3R>g;=1sj$LYm5MyCED}*Y#Ynw`8Y|p!ersX zG{0yxXd@3Fd(IiUxpC3NhmurH%+-TH*KeG!iyQ|}bgLJI(F1G4SyG2ccVXZVs>cQ} zQ&*e@?J2VEDo>Nr+O+SPw63)ThGtn65G9H4UT#Qgm05cWHK=2k$_;$Rg?4D790Ab% zOW{PGiZm;m5bmLLBmpL!zvFLEUW>rvc`^l1uxq}7Y(uWo4lDLST)nzn_5sWkm(QNu6}{* z?LmF&@iSP)Fy#Zrg)PP zYvy9j5 z=`BFHCV&oS{T!@yQ%Qwq8Nf!l$aSJ2{1;ib2IO|P6}F!X?- z)N3sh5Du_qgD$7Nvd#uu3ODU?N^4FZ{3XA9nZL^CnX`9m&}ouXQ*){4R(`Tg&lvO9 z&WWn-4_rSkzSr}+R=D26(6Ikm$vvUDo7KmBacpro_pp{LCeA9l%x7s>!acp-%==Ea-MB7sI65AXN!Y0* zNjD;Yp(yl3-;SF_FMsC*16pl|1P+57& zLmWV3K|{g_m5YDjzz|Ok;i;F6PP!!i*6-((f+rj7F)b|bRyMvhFomISA=~QYy6sw7NIG`QXE@74 zY3P<2_@KEmpPg;XR@#h?OQEYqIt*mSRIZ)6l<5y>kr~& zkA^$#=(e2YiCw~LcgxxA>8gdINJ_|V*R7^|Y@{nUzP?;I8gA|=71$(>3`uWmluZwL zq71s&NONehf#GVaVYwH9)3;61-j)trT3D#T69TV)R~I4Rn`4%3W+h>JNlRUou3awy zu;-ObAmwC)pZvN-cJg*H3ZO2Q;v~HEd7L$E4W$S44LbR%@UaJ}*N4(Ex ztgI)MzeFdJqhM%4u38ZI_k)6;I<;Pm5S~qf{0h3;8r)g3>vKIFt=j-2i;A+D7u;;T zo#{`9{E*ySJBX6p-_*kJ4Gv&#&K&GL-^HgtL_*PW(Hty3s^FI?lKU`_n4 zu(Dhmx-eZGsL13XKMLOp6Y2*0rPY1+~Q-B@ua@a!0V1f~nGW5uS1pub!gp zk#M-O{ai*_ckfAjsvS46k4&s6a?y4$&WmMTgu%4q#;Y?9YEVMmqb=+`_YAYG!^-45 z4D>bscWRGa#oE{7({*T&bu|@j*{lHOn9t5LFg63*iBS*lv2Zd3Bdnj)^3k@(%Qb6u zvg{XZbz~kGHq#5KXm2}HSd4kcLT?W6>2^k#T=zckjI86RFy5^+?52S3ak|Fkp%bE7 zthc3K`&rLP|I+_Z-i=&c;> zqMQwZ1uj>0?vnPvftGID0LR%SL&2yOt4Lu_GB$s?_I$xE`7GNRD ztIsaeXNm|ts@HjdG*<~;-o!^c#e4Djk6`Y2h+j+$M$ES0!MtIZyO7d8pR^s5Ed=bn zSF-2{Zh&>+HM`zg?cP^zmq=@oK!e+GrRTwJti!_bvu5V2n7)j#;GJL(hK!J0sP^#88Y-9{LFU|@QJBs81phF1C)PLL_clfo zS+c`|95ZlnaW~XnA8E0&S+!y4hwI{p2ji$w3Sf=u9H1CiqldA6D1L(6MMV%_bS${N zJ2E9Mq*7oQBH4oz^jch0stMxnG4O^(hd(E+7cvK5A`-1J^Wq&{m|2o&HK%5knA8j^ zkwge5uhMg>xVTatiJRB)FUD=3r|UprWD&_!Q3>{=o;i+Xp-ykx4!TMaV;!cGvvF7~ zgr%ncoZgznzYq6ZcJ+F40PeiZd%LWn+Q6k+&6O zH501!(rH-{M+bT#IUXuwbhLvaTMlt}(XEnV!rVe_9%aoza#@5?h`j?eC>vnNdz7zE ztBmrrfP{QQ`18x$gHW^Un+h8(b*%;+OdfXJ!)O3es&eS$CRmTN$R(F^$oko6o@aVd z8OL5XQ;Zqecx3JrWy6K6#Dj}@b-UAb%=Kf?04g-(+AKbiaW@PI+O|DswpaT@k?2-- zQ2R9iK%yY+4ff89pRLV9j+l*#lV8f(UDnpy2=J2P-Uruiwu9zYKO`D(dNh6Z&uVL!t7L-`L7E)Dk zmaO|ZYCe!vlSe1#jIq0ZyJIUg`oLKH1Rkg}eBZoXx~OIV4Q_uA^`uP=#b|b+r@rX) zWuV1*5&7Qr4vx*#<>lzs4UwU;fO@YQ>HZB}q;N6px}>@0B_5t&s$~Ep_m~pLt`TB; zfG}Xs57e|DYv2RmU)lopK_yRxwFG-LA4C90YryqhQ2B1a-gCt} zK#cnF+IMdwHPsmJ2S!Y1eBM51BG1z4#eS1*h^Tw0z1|#dA*3IoDG__CJ2& zKqBptQ23j-_?b&LzHNLaDU)SU+2S3eel&%fex%KcE>c^nR&y|&c6yAJ9~K(+0Ac^f zy^WgaEd?T`jo&p0e6W^b-sDA_LMEFH_PAn_-L4|3Pk&eOyU|$*D|HUv$vlFG4Cg*u zvZ;iEQf^tngu>!ys#EEk18E4uk0;YsWNBDKSW&^WNScJ+?jb{Anw>8p$ zc+rcibFhIp)G_;}wzO`B4U>AJVY5wCO@6*L-P11E&RdDUX{wGhQLgo3X&-mI7$7BS z4oe*t7?uGw#Gp1j=>Nk;fDl^gRpTeI-T_i@SM0Q4&j!*Q_T>*W4rIVO21JPhz10`T zi>W%nE(npw`Ih3@zer=Pq>%4swWhGC+DQD1lV~4 zuRd`8y%hMFJB2yW)C_2@xtUzt5Red-Ct=Mz_B%2qv}U6w@cL#^=!Zo)Q;#NP3&!FL zPr*EGOz&Jss&o3#io8kD_KdB;?^XxjD|$Ngib^)!MzcT{YT8`bIQUQfQ~{}}K(8S) zvQ^kxC)YX(LY6Pro_o+SqYUon2#{+>8~oT-YRMZJ8c(% z9REx?5S-tZI$Oua+73PiQ+eLL_x9XK5=qh^|EKEfc?UGi;hFap@_#z;@G6~C5&M>^ zQTn4Q0NBqAR|YB@Hxc3CK#;L)^RC+z?4N()6)nnL5Ck4q${Y$693N@ft^`>9KV<$w|Qsk}e@yRv|Dhz5qDv3~9U z=;ReHyu8T$2tSMBi^E{LW@$}^_Abc^ElUI@&RsRrQ>~$dzUr#!$RwD+hyD)Nh>cjZ z;jH)3-W<8vYX0Idac5d`_z3cxawFAi_Yr+QCC&>zbp^_ z-$VyX|2$nJ9lRp-qtbXvBE(0u^UYAyMGG6eI9oWpd8^(hiMF+M%V|%M5@U#c8aCEv zy}svk*x9{Lknl)?aD(>B+L`rNadZ9re?=mGx39F-pM@dUyEL_t$g~JPXs&{ZuJS-u z6=zSL=}3)mXU^q~|GsT@yEn%#DQ!5BqWAWYIEU8Eo8L4i;72T3o)`KJCF+HC73fH% zNSrx(IFNLc;m?$IQu&efNL5(b<@K$69A05pxjaK#nuE+y83a*BE z+zk>vy_WY-CvJb+H0!k6=RR@!vqevCoHCi(6C=k&eE7?ugchYJX+| z_-OO-_wSNv{qlZmvKRz&Pu}J%tm{lK=5IC*Kmxg z`a!|8z-(@RPfl0@Mpd(`?WaK-6`kMmBpDH6kKZS6ZI-L6uPMNfY9`#~#n`Z9Vo#)F zB30Pt46@{e_?j?8R7s~fr3>eXO=<`S-_H?JCrhEooH^A&A#fd8zVZCfB4IbGGV#bn zCJWu}mNeMjOkrnHb^&Qu@%8W9B}=G(@Bq0HqIhfn>gBq=oSVUbT7+#=-4>+|X^*!} zLqHAm>Dq1WT=mecGp-8t^?JuEu(TcC&YVs;-r8_e!N=Tj;c6KO@6zCMWkN9=T#hx@ zrc^xg4$EoBFvTtQdK%Dfs$U9TnEp0`vxRAw>LSYbA(TRLmchl zvuarRTI&HtE|7$uB-br56Ek20LLQ_)%1pc0yj`t1;kM^#(;S@#m|d4Pj#_^Geus(; zz=I4X(PRE-0e}^1W9?mpGKSb!D%r>R;Q+jyF%AA>J6abgCJ1xJ@RKB#TF35wXH*+vVG;jt;TA+A> zaofKL+>|7#Appo4Zg~=^U8f+wZ&kpW{Rs}^p+PTxg`k}cI|v;EW?dz31=>B_G*J*9 z&t;u41zi6grn$W+d$U56q6sTq(@{wdBiGy(Ow^A~Zf}+~iO%o#i8RnGItjb4=ui^r z0)VUKb_<7aq)lC$3CN2AI@|9VQFYnd-c`$0MU*3PTzJ1DFap3(ydp)NrcC&giK{&i zJLguiE&#q>%9ps<_ib;(`eKuI@lgl!fcW)7OcP%{%TkYYcXFU8cq&8KY%ak>9UE|o zNbUuO9Ch|&(~VzkvrF-gGFq7SOo*(8OIPRw38`?w;ME-h_6{TWs3T#Q=jf1?bMvWK z%nS@jw;DQyS~EI%UhoKJ>x2^$3%1c7x3M*C{8{{TrH!$7 zF@Us$afo_Bc?|i!?!(--va%xa4Ylsd^6EuX7Z>@4N_>5PoKo2%kc*zYSP7yVt4!dd zUA6bDF{T@S#DKv;4&mB%&`i^u9&N_NiXBfR20$zijFaeS_q0MOnOZ{2s{}XH#RoIX z7MP>*Lm@mKrNM$~P>R->;!xgNl_RtA$hgn~01Ax-REkt)4*A~%A6qzqQk7UoE$3i3 zXY)*sk-Ozvf}XSsI8*cKZ7}7ph}lv#1#_<2+rwST$d$N*CTdud-#6|DzpuqZ*`L0n zm)vhjUYKw<;J47@*9jDBe-fv9gEfShh^lL>7Y^Zavz>-|Jzsj}3Z7i}ozW=y{S*FN z=?wer({TXeAGZ{$9a~@yYF>@qHdvE3_xj_bwt(=WT6^rysVJ0M)bEY=>!wMU)=W4#$s_iiv;ytq| za=(J+kb&7gvTRr%QEs;Fax+=y&#y`u#S?2~-h?N7GwX_OUqTDLp8moRsd|$ncanc? zGovmp-&k0trYd-;Ahi8PEAEaB4TW^Q1N~v{mKpBCRyRU7Qbp~w# zW%`r0db6T69^;M% z!ABa(KF!ezA;O=zgwx7hi0<^kxQD*p`{ow?!cWlJi z{rC2&!kIkiTXjqf_T62aRx{z|D4GKU&sBA*8YK*izb$&|)*lCBBn;F+%@?0DNidgE z4w)kRH)=2+RUBUiTJEuBOs1m+jka!VlsnCa`UC-LHN2+H>gt>(USy+SYGx+>s~qa$ zdcu8%qd$(MQ_?T*+(`L{3^}r<1N(gBg+c1!qvjOOVrLh)qiu(=+ijbihG06w*4UkR zCn&9<(|=Gy3G=&gDgC(m4{DqDMmOd-ye=oxA@@co2AyS7R(P}xXK(#QS_W9(4NA3= z``8dIe+-03rkuoExW3B{Z%}VL&9_iaiEm6xvft+Y9n@JsJMdP3Lx${SP*CW@uSmnh z(1n@l@ib7Im~11blU{F78WknsNhAr2B5d^BSLIiI&A05W zTVE-ZzdvR%=B8_CCfJ4zXe@>1dK!*|SsacefEG1=3DJV3j9A=K4oPfo$_%uk_-aIl zIl+ZUyr#B&fr?0JL!}nodP!NbqGYI=fHIRHZw+$_M!7;wNN#$&FoJ~R=S=9(5%F84 z;MNvijf3IaX81_V9(QCx&ce3R{R#&)l!w@kM{a-gSE!GTYs{e`viW?!;w2Er zxQsl}M^a0l#FM!#^Hi8V)Z-I@rNd9eB)(3Z@NI;9xQ(X(lV818@C}T@b|fR)TO(C< zL-@po!^L8n-zy1xDVvQ{@-WLS+hW0tIrpjHY3g8!ovjh7>fFNsv^x-zD&B!}ic@hM zmi3McZSPpR(X{_{xOr?M`L~b{P}AG?lf;2br$&OdL3yIh+$ zF-ARs!QgNkj={8;s;b)YJG`CR+Jgp!YBetnt{~73b6YdKVE62qqXn(qZq$PMC!1J9T?k@{I(X|pO_+>rCl$HA4f zx8o7M(e-!jXuYXKIQ<936~_Ct0nWk7CYR3eo+Xmj>(_P%cB9jd^lMF8opN$58@8_+ z8ob$&UJW@O(W{)ZIWCZH&xyr$3EF8^{}Jhe)zYQKMYYi4I4!a-zq46qStBeNPz$hl z_P*WY(8ep0IygMV&hYlgW=?!K(n)W>-=-0xua&x0e;HVy*67(Ge!Bw}$%Y0N{e{%< zN=u&NV3Vz2!kCMCLb0a?(z&YY51iK-3Dn-6DaT&SBHS7vF-(Mog&x$wIu~4uQ6;>I z@Y+Pvpl7L=(UITsv~uC>(%9I@a921m(h71)Q;mGl5<2RacDw?4^;A)2M|)| zIe`NHY^fvw7;T0DIc}AX^{d7!=f7k)y!A>k|KmI^Uya>kq6>KZ?A%|_;{-Nk1rpn1 z3z;`(SyF0x%Kw!Ft;Mj>|2oMEw@#F%3s9ne{&O@}ETsX#8_e?A#sBUCd=HS79x?y( z&!67{ZY)S_J|v~O@9`uUg%XrvDNDrHv*eQ>iV+K$GrfiY zC;kkIrvy>k?H1t-qfPUe=C|MG?6-MU_cx}{2Y_zpJl(;u$3bpcwJOXNWr4h~S-(YSD? z`#0GsVF`SP5i?cl~!F5__)C6|Wyfb$efKj+bL-{&N;z3T$NY zW?MYVUjXBWSP&dwwW#J$o2WhX273h{kV6b2X`A*5p5BHKJBMjeAw&f_^n++;{c<|p zh9+lo;6`OkB8Wn|JY$nDN|Ci{*!?%@O4-EC6P4IL3*bUtpn*~5al+!7%X>ugJ=$5# zT6VGKU~nY5XGqg&SVc5UeKSpD{dWKk3@3+Y$oHPt9%CoWeu(-T=Ht?Ukmz(m#DNps zDfTWT45re}PCJ|3FVv2PT$~;fSK)H0x`FD>u=0&steN4r;@bUTP!jsdoZ;Hd#fj|S^$EQ)Mk|0L z%K7dYw(={m`4elpER~9;A5O6F+)&67vwNNp@X%!cx;3!#_P^+a={2V z5$sd)Z?2K~O}+*<4r;iM*Y@D>0t(B*Zf2aL<{Dqd8zI9=IzfQ0!v{N&-qs4YuV%T|q4O{R3vta`n5YMI)NXU)b zcX(f%CD=1cmNx27@4=Xe#>YhqWMo@>4~=>@A4nS9| zVn<%a?@vHX4$MotxlNrsWfiu^{`U!TFNQnOR}euX;_sKIDqdIbakJ ze|7WlbmD`6$Jq}8K7*ZHbnyLe*loBo$UgL7Rn{SFI{ z#aL&}0LS^fQq}fod7EK{0^UWZsaVv(jex5}XgMQkpu=5K)qbrl&lx=6%s9{bW%y_2 zeA=+RA*~z(&?~leAs%kTEh2#G5(jsgpkW3Har<1M=JR_M{(OF&Y*fkiBS1X-t~lJ% z+t&f`0&bL$ZdfRs%Ia%@A@d&k;#eId>KH7!^(eGrqY=zI$J;w5+{ao z#eYgC=gH+QP{vAz)Iv54Xjg5(4zS_`^ubc`0a$L-6JEdvMM3dZ785aVcJf7ecq(=s z#sw`fcu#Lj9^U#b%?f{p`}EA`%y_AB5e+Y{u#AnD}HfV~5?X zcdFmNGtupFZ<<(zj`In*Z8}{;8wPr(%uCCHEYZrZ#{n05qJg8S+ffn&ooL`ff7ZVw zs`~Em7g&81Vz6H6aJmj{cQoM)O7x~)IGb&)+Q(9`{a{EitcS|Pb6j|{U^-O&EO?KY zMFOR8h-42%IiM{NGs;KktmG}Fk==@-e9_=y{=vP+KtXYiPTOrjm$#I;DtGJ|M8gIf z5_;M{+rjBSJhXQ=-1bn<2ZD7%inq4x^Yj~dQAJhSp))(7wLlm%7}w$YHWf^ohu?kn z-DPA{Sa4%|9Z(aiXY3pmhr22Z_Iow(#ZB>yWYvw-hxiSdi62bVP0VbRc&O9ubpRy_ zgUpHfso=%ZFjp)xm0!%aS=fblhLu)d-go%h{Iac~g$==hJGp7fX4y#rJMC51UZ?!) z%9BbQS?{k_^vj(UkAai#3mQ;cY7_R>z_q*IX*vScSbsqy?0yi?k3xT4<;{;oX3$e^ELb^ZN36HV54*By+|c;VP>9%h zN4Kx?BGnJHU{; zpYRg~nZzY-0dZ&q z*H5kV2LK^)c6dq%ZhCSVb`_TpkTL9(*c?ztLk4Xv#VS{&QIPP83x9|D$%UW>wR{ILTFw zTz7vr9@Niw0(Afg70^?o4Yma@nV<9j3{2b*APcuW7+G50;2ZX+N>;Q_MTAq{oSfkq zsKoQ8Z*I4%rrI9pZ5GH0ydN?GSoY{9p=3?LVi?UVG$ylMn^3-OX^-WBHpe#$E2#hQ zhOJ;mf`9Yy4w2Z_)xoK+&5;nnjl|}pXmx=s7NEb(s*O`aOvBGFVYaglvoai}Fp1h! z>#f=6e=^qI*MM1`yHj?qk2|!lHD1wM)^N0JD(l(yQOBxFk;gWEAZdRrM?pYEY^QWu z|7V`Awsc*8m`2TJgm7PsHlSVu?WwsXB_39>Z_ zn@^*-ptD2qKV|9f&eOqPoAfJxX;H&DObZI6WK_^~446vV(Zdd~*)f7H+QW-0C>p)| z+$G!$Ws}fUknY?oJ3KK9tkk^WEc=x+0)+$M0qO=+?Fj47oeL%{tv)+0{{ZHwGmzJ! z+hs$0%ngf%Zad%fQZv>C-tvevhpV|4l9O)9zF;Zm=|8CeiFwm_i^kGMWiVyr)0eV% zE&FNmdm~Pb-J@v6T;=CVf1N@hx z6b@WS)b<1(gH

Xa6dqOJKRNrx=6eJu^4`LUtXOwgZ|IKeVT>EAlzVULtk1gVe@ zqUJhGb7O(_B24E`mljNFEm?i-S79An#oMlt%Az^zA)#4Alu$u1 z0^?O>i{9#wt6L~5D9In#tDR7Bh;Ef@uT9)ZHnj_V!EJM?46pprw78@X2U~{4Pqk@- z_4Mfs`+;-&r{KJ) zxzX%mo8om(kmN~w8}QGB?2=#Or*oc^%{lA9<0(Y)4=)vAt%`1O{{@hAna+@ET%F&( z8kWQO?sqEjGrjKtS3I;J8o`&lJt zLox@t`RPT2DWXes@-|I{PR5iWdy*xpr+%0Ga^L=<5oLpYsTCBq^Wt#n{FXyT6-x8i z;!}@>s+9wKn?!K)Ft6B8{@Txhv@^}GfJ1{U*!$fAdWjjeX?VwQ^qkeiZl}IP@YYD| zAVhdTH-eSt=U?lj_0#lb(<{lTqPV&G#XV85?^kfAc96qMXm;7kJqgbA=}siP_2#FG zc-QMWVs>KkxRY&XSoQPMg{rU_!LG%ac^J+V90refY*%rBC79FAszH*`&XH*DMYOGw zmU$-iguE+QH38kS?Om6E|eoEvOGC@ zFp{?U|71q$DnXE7E4RLv2-F`7PN0B;Tsidsy>Wl<@nku}a@WCZu8y)v9)AJyAbrON zpl^nM_1BO(Cf4$PTqoi+{1M1p@wewCZv@l}d1r-t*=_Xvco!wT?r7`es5Q){8eOjd z!Rt|1U>&Tp(dq6kg39~2r;#yO3II^Nx!=G=Fr-ED2VY%R)Ls zS}$7q#KR30Vb{kjx?UgrG^*aP#!gzx{gj~q7fwTvv-H6g)Ts8D1+q*>fHz_Sb6Xb) zS1zvtTJPM{2A&2+%S3n^jDJeOO3)k@Xh%-HA7qMaE{GSJ4JG7=e)%*PSZ`Pm-HKrv z7WRPCt%7DdJ{$u6@0axgd6RoPV?c{IvOX-ax&#7 zK}$1#^fc-`&^xY|+IHsC3Fm#nkpV)!Cj{x&o4V|pKbbtKJ1GtBa#hfMu@Ol3FKo1h z>E5PAN-GB~N|xz6@*hcPsrP?kFn?8`#M0l_|1?RJA`hghD^~@xPw`Q}_s)6lTC|-m z&-4VuB;!oBPybMeCjnJ5prKmpc}M>Tu>#)PdIT_8^54=DupiM2{VE@;DxZf-BndP(&at#p4 zkRJn&y!h+IUr2=mmwLTcX8(KtfBb%22P(vmZWjL(2>hO92H?^iQl!SOmp@0-bb=rp z@SBpHdQSq7-2C5(`@a+Sd%HIN-yQe=V^h`fp5=fJn~0wFUs6Wjh_&phW@E~p+50V? z%6V_~g8T;0l3d_5kdgeMJJv#b*xA1n- z!ufC?OV5sJP~wVOt)L*PfC0<;DZgUk{lqRSN(EX?cpOO6%B8Q>?{+i?daUZ+FC^e- zb*Vh%90vyKD?z{Yl7V~l=Wzh=Hfwu5&^VuYd0$cb{ld)j0nl-t^YQ*9tHi;X%uKHv zhZtVjWVq@2_5zO~Vq$-x%y|%cU`!xjzzE1DNdtmm_r*TOuKS35Qy#n#aq@VUjFKL( zSfbwjgdw((GLzOY=Pm(E8V9PDFQ2jtV*=X$=KNJney}C?Da7qidBJY9JVOyo04;=1 zZfP$_uX=b9THOxSJ=;|etUbq95A{43uY=TQs~;S_`PlAzh?{(WG}UE4aw|M|)ni?; zzodS8bU?^^Ks`0bx$fjKH3P~*>5AM{X&UyQS;6ap3$L%MP#3ZB6EMEVPYAhg^+JaD zb#g?uP#eQWR%_;}2cEy*gki6+pM}7myr-nLFO*JeSNZG})Iet%-nA{1kxVGl&YTVt z;j5e1PmWQk!dNYI)7SK`O!1tB?sb$c93~TN3G1MP73U#u6-BpE&kAbeQlBQ=kvxHC zPLe;-1AiTOkcnGbscXt^u*~|_r}=Y>dU}fZJ1@JR@fI5#19lqK;|l8Hyjhz4+? z(=kc-)EbQG4A9W|Zqi@06F<4%BpFwKNOdHfc+}gPu4+)vU$-YAHV{K@uWB)I zoV9fruCzd_s;f9B`ThMAM>@Iql=6b=@zKYvy2+jXEjQnNtn=i4f|_2gK+iD&bu4Nf zpG^3g$)Zms)cJJXNtJ0oU%w*5saqt|a%eRfm_5}H+vJV+nLrm}aP@si^x7f{&Q*s; zO(Ze6tsM4G8tjItZPwOVemh33?v`&6B)ou*`&e8*t^l%{zO4i;R-`Q$SPu};-ObZa7@v|C@ z812WGjrRL`ZH8y^h_GgFt7!c|zCX&L~j*bto`TG6*pvt>- z^I}2An!YlRcOZrem?a%W=wx(%%yifz)m_!O^jy?##TF~d%aD*iN$7g&K6Fp7WcRJS zxbauE5&F4k#&Q7|zVfX^!E@jNPT@2y@)C<`OnW4fyPx_J`!eKjzVD5nv#6UlTkx&l z^ImA?IgHuz*FJP47&}ny%|xj(g!Xu?lnmrfJbyr#XXQ~P_i5C1KBX0uqCk3 zA7of3$Zli=QlQ7iO0jxlad{z#$3SyzeLO}*VlSlBd%t^Y7Uh#~3DNH7m8XxY_t}S~ ziVaRe$MTN`BzF_HQi6`vQx03=^E~ROf}tu6n*_HE&~bgTDrmQnBv>BStz)0$HUV^w z*6(@La6C12HIfUegfQ4`7%&0thAuckcOQ!$eKQ%jyTj>AXR%s<94Jp!RR9qX7cYi+ z1=rcBVho{UXQ0CgT7t0076}fM&f&)mbsT4MbM!`eHlIbRo%z?4@0{!nrvi-|&l_dX z2>}J_vjQ9ejC=u%`}}poDH#3iY_e`MPp6#H{=6HX&Xb;rejpPk()djHV6uXI-(A?Q zw*y`+f%8ggn($gKYp5HlsR&;s;A`q~x|wNPENiA#r;GtDk%uf@HW;j68?XYa;RF7{3dz@K_A=-2jI5V`grebFv|wq8oo zIQh7M*{*ph*1{nHKUeGa&qX=^$FDd=Ygt)Hw

@$7F8Zx^jh-l$e;Pg7IBK{F}Bn zea}m5rCN<_V&h{!A~J6>KJ>G+@G4!u_yrrqx~|FiPP0JvE&mm)$(c-u$#dI5QzpmM z%^sO2`C6}-I_SL3>h2#4Pv!JkK2cX^QV@xK!mn%YNKZ-WKQ?Ayz)a6)lWK2)P>7=C zeFb^_VC;$9dJz@}XTRFZp_=DS%bPs9)4v!xWE6Yti&$XC70J~ z$l&fqf^Qv!5iL#PKV|w+GgBuk{bcN(reM!3t7uX%mMZj@$<$ zWe<=;TQAvSUu!3x+aB@Md5X-jzwxXogPsr{4AJ|?OI}{5_j`W544LQEbf;>z8g64$ zZl3JXk3``mQTiSsxs7cVyAr4MfMpFPhW62<*B3GQ9AEsDk>6=VXbi{Wy&yDnI*(^O zrr4Kg48qi|gQQVs5La-iEteU4nl~}ev|F7+JmGggo`Xqin-A@@y?#B-aE8l{>G#qS zoiQO^w8pVnXUKAhT^Ao8zqB&MvQ}y@MH_wo>046hZGq3et~P6iIp+k^3Lf3(e?UU+ zrJ<>%cB5QQ?cBTQXy$f);r>p7MOe;8c>5EMpr?Fs?A+Y`b#-+&-k39X5sTfscST7` z#Bn^5*MmW1)=I*o{-IyzOE_2k!Inv&OoOp~aBI?A`MQh?N6+ZjpP;ydmdEP7sT>so zV7BK`b$<1sZ=%^%4c21AY{&}kj1S5t&l1K!CewUF&f1!q3Oh3|ELhj#$Iu8#45hgY{Gyt=#RIVOYbU$0Yqi@W#_nF_Q9Sm= zn$-w7^;d6D^}!+~5b4az%O8q~KQg{7be%pQ&$i2PEh_R|IX~6FQ0r8HcY~2Z#rb;` zg^kTT*&Eb&Je{IjHf zli@Go%89(d!+wSwneU>I!Dm5e`$qCTmT!q^vT9-H$kK3JjFB{lR}_eIUR)H@jx=a| zta^@2zj0Xb;lqdTl9PEToMz&D^X##PT6oOpQ!~?yVwa}+} z{tBwxU&_hk)7qS#-L!gpmmQg?{yN#`?&lUa@aL~0>M4U7w@OVcWbmtjjsMuoCMzWg z`IbUl^DAf1zlyhMf6?<#tls?;tz0JWF}ZYCN8d^m-7wa>ph>^UXCw3yTKIxJe4>z- z7L)h+Yi3eZkhOx|!zMC~EZh$n$N0q@(a2A+k#|1&ujRc6s1fSObynNFHh3! zTFjr0r@BG;X=s9;p}Sx$&EZn0%@qbLx&7p{PN8dzOp;(SYm^lMNeGz z?77hQiPG(?lZp)RHLACzP?yy=(SyoFT`t|p5@$is$dPwx(iuHVsBG01^#JIABVMXF zXke6A$QA4}^J=1`$bQ^uUPg58>$1b!Ffpk&Bv+;>0@h|hjfB*NM;{{J&l#`lSqtM0 zb;49CWZo%%|9-YJI3!?m33BdlFxLKv^ksz>zvetnja1km%5-&qJ3I54id& zqAuWbtn=c<&TgAkv7w%XNo^kLh&Dz6z000B`k(Kv{1i27x*!K6Bg}R8k}Zsz7nWjg zw3|My{x;v!tSC~rGyei2!5Nq~q|QRF&e+48mdTZ#<@zsvMfFnNy^Nh z2zB$)HEHF4#cQjjpK$CaioC+i;VBKR)UGjvX3_uO3N1gRl~w$Q>L~ZaVtk!GAXOz zjL)f3QoqmG6;eNc!nxO-heX4mp2nrXRg;aGyJ?&78)}dt>Up!EkB=|D*NXp8n{mUw zVe@kRWc|N8I}7K;i%GQwB{ErU{Dq4{tkyWgXfu= zrBOgi_AY*r;3a2wQa4D|7!3CTljKBWiM;QZ+Utj+XF2pvy%(!h=kLHQ=52G-ts|o3w}7 z2jl(pQ9-_ecfH)y66Tli@OpHXK;v7r)T=l8|6Ryd*qWFp-O7(`yE?C5^jM5=cQ542 zi|&G~H0>;On?vJgo(L`QWkbEm0ET*q)H@!}cN36ndhV}ZJ-%{f;#I$(_gr!5@#x3<6jOc#AYP+QG55tgOr*$HjTR zMp9P3WSq6`r@`2C-PJ_WCnRXNoA1#vKB<2j2!F>|;@U+p_O;EUD#*S=qx>b%H^Cv* z56)i{Q}(4bEcx9cpTo*n)pa^Ok;djh-M)%3pOx!7(kvYtG4$<+(|m#~V;e3TA`3Fp zAeJ86_;^poHnPH^;#3jpuBJ{Pk*sMa_ z>c!-2v+h9V@9QqA{V-bMPiMdUdOYJt-pR4<1Q@H zw^b--Y&Et^D{J=Lxh9;YI3lE}fXi&HFy;|F!lOzi>A1?4@|?@X&)*_m_1$d?xbv@2 zS#wMMrEnC&IAMzyItpXSNUa{UprZcq0Xb3s;sOFy7xbAKAot5-)-4fmifF; zW~pd}yPVMW;!O+->C?-W%*h5&4CCj>{v|6~PTr8V;`DTidT=3mgtVNI&XUPj5GR@3D#(ekl+zDETG4w1w0hj3? znwqBFZ;#=Cr~MeItPD<3UgQmIVBm z2T|y<3^w>yqQ~>2ZJ2M8O*G4f+M@4~g2I{WmSKWUkA=GTIwQVpZ8--D^M5dpGGkC8 z?eFiu<2Z7EDr>XdaM=^+dh*&>qf?5w zFYN(~(PJOy3A>~G!KVzy{EW3Xs-j8n!`QZ;N1cCVSmWCK;>_U|RoM)p$3Z6U{QVsT z7GHmT52&lV*m;sD3qi;o+0+lD>|F82HUk%Q5l-*mx5>09%L|LjQ^CCt*$3&GGcUj6 zGAO1)H5`bG2X1l1J(Xl2CI3+B`)^y-)FpKui?7z5Pg1j^92G5~`)8~ukm-T1gPj5& ziN-Op9$w2iGJMH4`C-ixH_VAG&KPbVHk^Df-_pq#7)X1LENl?59@Y_5&zAMJJ*vpH z+DgpjTG$jW?maouluG>LEvtV!>3>KX-4CU~{>avDT3fl^_ncQ-y?!HiZT{Lhiz%$= zn9GrSp3Dc!z!1uwXpT&^_Wzd%|GI!ldZ$G%V z1u=SS^B{Sa)_tX#U=kP*7moQ zVcnPSm8BiLVL>>2aITX{lFUvHK)ViBy5~?Y`a)a(L9ty5^Q5j%Itp5z!|UnjF!D@W z%*;@H2rC_-k3~Z*;6x^4*c;g?#2cA>f~}iZs+^AdWU?{xIk~?&a3xI+`f{WctH28 z!t_U>+?R`+5o_*u%8Oh5c&4&=EJmNV?qV34-kz^p7o;Fj(e-dmo5kq>O$j>tKgl!z*yc1nCmf~!j5{sJMvnitap?v)kk(2;% z(WU)~<0p(gC=@bSipOmWr+hKPy&~PL`t0`^Bh?5r)^U$r@G4Ho_Tv&gCM`;x_5TU* z0}uR-N5@W`D93UM&YrnBVbGmY>xG7PMECn?;^Z9|r{R;ckF->sQ~EJRva#%i2MI9osqtZW{IBd5;b!dn;7-*XURhKxc$dtJ5@ zV}E?Sdet(F92QPr1#FR?_A8Z;%9)-!KYRYM-kE>qq#rS-5m9SbCz9mYAa2 zkXdwNSTSU4%kf-^Hbkvy>%7cBm;U zKxws#-euhv6}kLwY?(ZN{q@(<67T#~$N1Rf+_`gtK;I3dg%5#%KtLcM5D*Cb4g`4F zrn+GyXpn2JGB`Yo|MJCBZ-rleknqpO!?xZ!qT@yx-7yw3PYksz~8%rk{f+l%K0R$eeeGiS6S3oGTOPE zl|s^vap)Y@l?FE9MrZ&2hnz$Gn{U2BL_~!2@51<)5imJ>_N*Y#fB$zu^iUum5D*9m z1pdMZunspnI|uK*_Z~8HZV(A%wdYN9Ark5D<%%btd1e>A7iQB1>(`s%}7A&lyJQ;p6QGKd%7A>kSL} z9mvc`K|T$bYDuTfQRmwkrIZFQs?xTdw=~)CcJ=>G=Z(p-@v+=jljP)NL7-(c#8x06 z5D*9m1Ox&C0fE4uhyV{{m_VN)sfw__76MF`j~t;@HtG5B7nzohO=yyVAkcrU=O 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 + From 17fd38e3cb277d51dcf9297178879a620623a855 Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Tue, 7 Dec 2010 23:46:18 +0000 Subject: [PATCH 078/229] 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 fa48c8435f..d54a0dfa65 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', From 45324fc9f15135437051eaaedda68a5ef1f0da7a Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Tue, 7 Dec 2010 23:53:01 +0000 Subject: [PATCH 079/229] 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 d54a0dfa65..5727c8da31 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', From abdb8080e365a584c64ce6562934eefb750568ba Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Wed, 8 Dec 2010 00:08:47 +0000 Subject: [PATCH 080/229] 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 5727c8da31..45ea0683d2 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', From 70371ab447bff6af36f12ad9594eb6ffdbff4396 Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Wed, 8 Dec 2010 00:26:41 +0000 Subject: [PATCH 081/229] 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 45ea0683d2..9baf45c924 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', From 9fdff2a0f0b45d7ddf1df58f83ac723fc8d99532 Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Wed, 8 Dec 2010 00:34:20 +0000 Subject: [PATCH 082/229] 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 9baf45c924..c10939d743 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], From 03920759ac485e76c9104b4c9a1bf53231e2c47c Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Wed, 8 Dec 2010 10:22:29 +0000 Subject: [PATCH 083/229] Removing novaProject from the schema. This change may look odd at first; here's how it works: Both roles are projects are groupOfNames. Previously, we were differentiating projects from project roles by using the novaProject objectclass on the project, and not on the roles. This change removes novaProject, and uses the owner attribute instead of the projectManager attribute. Only projects should have an owner. We can differentiate projects from project roles by checking for the existence of this attribute. To check for the existence of an attribute in LDAP, a wildcard search is used. The fake LDAP driver did not support wildcard searches, so I put in "all or nothing" support for it. The wildcard search support doesn't work exactly like wildcard searches in LDAP, but will work for the case that's required. --- nova/auth/fakeldap.py | 3 +++ nova/auth/ldapdriver.py | 16 ++++++++-------- nova/auth/nova_openldap.schema | 16 ---------------- nova/auth/nova_sun.schema | 2 -- nova/auth/opendj.sh | 2 -- nova/auth/slap.sh | 4 +--- 6 files changed, 12 insertions(+), 31 deletions(-) diff --git a/nova/auth/fakeldap.py b/nova/auth/fakeldap.py index 46e0135b4c..2dcb692673 100644 --- a/nova/auth/fakeldap.py +++ b/nova/auth/fakeldap.py @@ -119,6 +119,9 @@ def _match(key, value, attrs): """Match a given key and value against an attribute list.""" if key not in attrs: return False + # This is a wild card search. Implemented as all or nothing for now. + if value == "*": + return True if key != "objectclass": return value in attrs[key] # it is an objectclass check, so check subclasses diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index 8715156632..705e89ee87 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -106,7 +106,7 @@ class LdapDriver(object): """Retrieve project by id""" dn = 'cn=%s,%s' % (pid, FLAGS.ldap_project_subtree) - attr = self.__find_object(dn, '(objectclass=novaProject)') + attr = self.__find_object(dn, '(owner=*)') return self.__to_project(attr) def get_users(self): @@ -122,7 +122,7 @@ class LdapDriver(object): def get_projects(self, uid=None): """Retrieve list of projects""" - pattern = '(objectclass=novaProject)' + pattern = '(owner=*)' if uid: pattern = "(&%s(member=%s))" % (pattern, self.__uid_to_dn(uid)) attrs = self.__find_objects(FLAGS.ldap_project_subtree, @@ -205,10 +205,10 @@ class LdapDriver(object): if not manager_dn in members: members.append(manager_dn) attr = [ - ('objectclass', ['novaProject']), + ('objectclass', ['groupOfNames']), ('cn', [name]), ('description', [description]), - ('projectManager', [manager_dn]), + ('owner', [manager_dn]), ('member', members)] self.conn.add_s('cn=%s,%s' % (name, FLAGS.ldap_project_subtree), attr) return self.__to_project(dict(attr)) @@ -224,7 +224,7 @@ class LdapDriver(object): "manager %s doesn't exist" % manager_uid) manager_dn = self.__uid_to_dn(manager_uid) - attr.append((self.ldap.MOD_REPLACE, 'projectManager', manager_dn)) + attr.append((self.ldap.MOD_REPLACE, 'owner', manager_dn)) if description: attr.append((self.ldap.MOD_REPLACE, 'description', description)) self.conn.modify_s('cn=%s,%s' % (project_id, @@ -286,7 +286,7 @@ class LdapDriver(object): project_dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) roles = self.__find_objects(project_dn, '(&(&(objectclass=groupOfNames)' - '(!(objectclass=novaProject)))' + '(!(owner=*)))' '(member=%s))' % self.__uid_to_dn(uid)) return [role['cn'][0] for role in roles] @@ -385,7 +385,7 @@ class LdapDriver(object): def __find_role_dns(self, tree): """Find dns of role objects in given tree""" return self.__find_dns(tree, - '(&(objectclass=groupOfNames)(!(objectclass=novaProject)))') + '(&(objectclass=groupOfNames)(!(owner=*)))') def __find_group_dns_with_member(self, tree, uid): """Find dns of group objects in a given tree that contain member""" @@ -534,7 +534,7 @@ class LdapDriver(object): return { 'id': attr['cn'][0], 'name': attr['cn'][0], - 'project_manager_id': self.__dn_to_uid(attr['projectManager'][0]), + 'project_manager_id': self.__dn_to_uid(attr['owner'][0]), 'description': attr.get('description', [None])[0], 'member_ids': [self.__dn_to_uid(x) for x in member_dns]} diff --git a/nova/auth/nova_openldap.schema b/nova/auth/nova_openldap.schema index 9e528f58bf..1a10a445d9 100644 --- a/nova/auth/nova_openldap.schema +++ b/nova/auth/nova_openldap.schema @@ -39,13 +39,6 @@ attributetype ( SINGLE-VALUE ) -attributetype ( - novaAttrs:5 - NAME 'projectManager' - DESC 'Project Managers of a project' - SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 - ) - objectClass ( novaOCs:1 NAME 'novaUser' @@ -53,12 +46,3 @@ objectClass ( AUXILIARY MAY ( accessKey $ secretKey $ isNovaAdmin ) ) - -objectClass ( - novaOCs:3 - NAME 'novaProject' - DESC 'Container for project' - SUP groupOfNames - STRUCTURAL - MUST ( cn $ projectManager ) - ) diff --git a/nova/auth/nova_sun.schema b/nova/auth/nova_sun.schema index decf10f06d..1a04601b50 100644 --- a/nova/auth/nova_sun.schema +++ b/nova/auth/nova_sun.schema @@ -9,6 +9,4 @@ dn: cn=schema attributeTypes: ( 1.3.6.1.3.1.666.666.3.1 NAME 'accessKey' DESC 'Key for accessing data' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.3.1.666.666.3.2 NAME 'secretKey' DESC 'Secret key' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) attributeTypes: ( 1.3.6.1.3.1.666.666.3.4 NAME 'isNovaAdmin' DESC 'Is user a nova administrator?' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE ) -attributeTypes: ( 1.3.6.1.3.1.666.666.3.5 NAME 'projectManager' DESC 'Project Managers of a project' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 ) objectClasses: ( 1.3.6.1.3.1.666.666.4.1 NAME 'novaUser' DESC 'access and secret keys' SUP top AUXILIARY MAY ( accessKey $ secretKey $ isNovaAdmin ) ) -objectClasses: ( 1.3.6.1.3.1.666.666.4.3 NAME 'novaProject' DESC 'Container for project' SUP groupOfNames STRUCTURAL MUST ( cn $ projectManager ) ) diff --git a/nova/auth/opendj.sh b/nova/auth/opendj.sh index 8052c077d0..9a96003426 100755 --- a/nova/auth/opendj.sh +++ b/nova/auth/opendj.sh @@ -30,9 +30,7 @@ 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 </dev/null; echo "$PWD"/"${0##*/}")"` -cp $abspath/openssh-lpk_openldap.schema /etc/ldap/schema/openssh-lpk_openldap.schema -cp $abspath/nova_openldap.schema /etc/ldap/schema/nova_openldap.schema +cp $abspath/nova_openldap.schema /etc/ldap/schema/nova.schema mv /etc/ldap/slapd.conf /etc/ldap/slapd.conf.orig cat >/etc/ldap/slapd.conf </etc/ldap/slapd.conf < Date: Wed, 8 Dec 2010 16:23:59 +0000 Subject: [PATCH 084/229] Adding support for choosing a schema version, so that users can more easily migrate from an old schema to the new schema. --- nova/auth/ldapdriver.py | 79 ++++++++++++++++++++-------------- nova/auth/nova_openldap.schema | 4 +- nova/auth/nova_sun.schema | 5 ++- 3 files changed, 52 insertions(+), 36 deletions(-) diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index 705e89ee87..21d8f80658 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -32,6 +32,8 @@ from nova import flags FLAGS = flags.FLAGS +flags.DEFINE_integer('ldap_schema_version', 1, + 'Current version of the LDAP schema') flags.DEFINE_string('ldap_url', 'ldap://localhost', 'Point this at your ldap server') flags.DEFINE_string('ldap_password', 'changeme', 'LDAP password') @@ -75,10 +77,20 @@ class LdapDriver(object): Defines enter and exit and therefore supports the with/as syntax. """ + project_pattern = '(owner=*)' + isadmin_attribute = 'isNovaAdmin' + project_attribute = 'owner' + project_objectclass = 'groupOfNames' + def __init__(self): """Imports the LDAP module""" self.ldap = __import__('ldap') self.conn = None + if FLAGS.ldap_schema_version == 1: + LdapDriver.project_pattern = '(objectclass=novaProject)' + LdapDriver.isadmin_attribute = 'isAdmin' + LdapDriver.project_attribute = 'projectManager' + LdapDriver.project_objectclass = 'novaProject' def __enter__(self): """Creates the connection to LDAP""" @@ -106,7 +118,7 @@ class LdapDriver(object): """Retrieve project by id""" dn = 'cn=%s,%s' % (pid, FLAGS.ldap_project_subtree) - attr = self.__find_object(dn, '(owner=*)') + attr = self.__find_object(dn, LdapDriver.project_pattern) return self.__to_project(attr) def get_users(self): @@ -122,7 +134,7 @@ class LdapDriver(object): def get_projects(self, uid=None): """Retrieve list of projects""" - pattern = '(owner=*)' + pattern = LdapDriver.project_pattern if uid: pattern = "(&%s(member=%s))" % (pattern, self.__uid_to_dn(uid)) attrs = self.__find_objects(FLAGS.ldap_project_subtree, @@ -152,11 +164,11 @@ class LdapDriver(object): else: attr.append((self.ldap.MOD_ADD, 'accessKey', \ [access_key])) - if 'isNovaAdmin' in user.keys(): - attr.append((self.ldap.MOD_REPLACE, 'isNovaAdmin', \ + if LdapDriver.isadmin_attribute in user.keys(): + attr.append((self.ldap.MOD_REPLACE, LdapDriver.isadmin_attribute, \ [str(is_admin).upper()])) else: - attr.append((self.ldap.MOD_ADD, 'isNovaAdmin', \ + attr.append((self.ldap.MOD_ADD, LdapDriver.isadmin_attribute, \ [str(is_admin).upper()])) self.conn.modify_s(self.__uid_to_dn(name), attr) return self.get_user(name) @@ -175,7 +187,7 @@ class LdapDriver(object): (FLAGS.ldap_user_name_attribute, [name]), ('secretKey', [secret_key]), ('accessKey', [access_key]), - ('isNovaAdmin', [str(is_admin).upper()]), + (LdapDriver.isadmin_attribute, [str(is_admin).upper()]), ] self.conn.add_s(self.__uid_to_dn(name), attr) return self.__to_user(dict(attr)) @@ -205,10 +217,10 @@ class LdapDriver(object): if not manager_dn in members: members.append(manager_dn) attr = [ - ('objectclass', ['groupOfNames']), + ('objectclass', [LdapDriver.project_objectclass]), ('cn', [name]), ('description', [description]), - ('owner', [manager_dn]), + (LdapDriver.project_attribute, [manager_dn]), ('member', members)] self.conn.add_s('cn=%s,%s' % (name, FLAGS.ldap_project_subtree), attr) return self.__to_project(dict(attr)) @@ -224,7 +236,7 @@ class LdapDriver(object): "manager %s doesn't exist" % manager_uid) manager_dn = self.__uid_to_dn(manager_uid) - attr.append((self.ldap.MOD_REPLACE, 'owner', manager_dn)) + attr.append((self.ldap.MOD_REPLACE, LdapDriver.project_attribute, manager_dn)) if description: attr.append((self.ldap.MOD_REPLACE, 'description', description)) self.conn.modify_s('cn=%s,%s' % (project_id, @@ -284,10 +296,9 @@ class LdapDriver(object): return roles else: project_dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) - roles = self.__find_objects(project_dn, - '(&(&(objectclass=groupOfNames)' - '(!(owner=*)))' - '(member=%s))' % self.__uid_to_dn(uid)) + query = ('(&(&(objectclass=groupOfNames)(!%s))(member=%s))' % + (LdapDriver.project_pattern, self.__uid_to_dn(uid))) + roles = self.__find_objects(project_dn, query) return [role['cn'][0] for role in roles] def delete_user(self, uid): @@ -306,9 +317,9 @@ class LdapDriver(object): if 'accessKey' in user.keys(): attr.append((self.ldap.MOD_DELETE, 'accessKey', \ user['accessKey'])) - if 'isNovaAdmin' in user.keys(): - attr.append((self.ldap.MOD_DELETE, 'isNovaAdmin', \ - user['isNovaAdmin'])) + if LdapDriver.isadmin_attribute in user.keys(): + attr.append((self.ldap.MOD_DELETE, LdapDriver.isadmin_attribute, \ + user[LdapDriver.isadmin_attribute])) self.conn.modify_s(self.__uid_to_dn(uid), attr) else: # Delete entry @@ -330,7 +341,7 @@ class LdapDriver(object): if secret_key: attr.append((self.ldap.MOD_REPLACE, 'secretKey', secret_key)) if admin is not None: - attr.append((self.ldap.MOD_REPLACE, 'isNovaAdmin', str(admin).upper())) + attr.append((self.ldap.MOD_REPLACE, LdapDriver.isadmin_attribute, str(admin).upper())) self.conn.modify_s(self.__uid_to_dn(uid), attr) def __user_exists(self, uid): @@ -384,19 +395,20 @@ class LdapDriver(object): def __find_role_dns(self, tree): """Find dns of role objects in given tree""" - return self.__find_dns(tree, - '(&(objectclass=groupOfNames)(!(owner=*)))') + query = '(&(objectclass=groupOfNames)(!%s))' % LdapDriver.project_pattern + return self.__find_dns(tree, query) def __find_group_dns_with_member(self, tree, uid): """Find dns of group objects in a given tree that contain member""" - dns = self.__find_dns(tree, - '(&(objectclass=groupOfNames)(member=%s))' % - self.__uid_to_dn(uid)) + query = ('(&(objectclass=groupOfNames)(member=%s))' % + self.__uid_to_dn(uid)) + dns = self.__find_dns(tree, query) return dns def __group_exists(self, dn): """Check if group exists""" - return self.__find_object(dn, '(objectclass=groupOfNames)') is not None + query = '(objectclass=groupOfNames)' + return self.__find_object(dn, query) is not None @staticmethod def __role_to_dn(role, project_id=None): @@ -435,7 +447,7 @@ class LdapDriver(object): """Check if user is in group""" if not self.__user_exists(uid): raise exception.NotFound("User %s can't be searched in group " - "becuase the user doesn't exist" % (uid,)) + "because the user doesn't exist" % uid) if not self.__group_exists(group_dn): return False res = self.__find_object(group_dn, @@ -447,10 +459,10 @@ class LdapDriver(object): """Add user to group""" if not self.__user_exists(uid): raise exception.NotFound("User %s can't be added to the group " - "becuase the user doesn't exist" % (uid,)) + "because the user doesn't exist" % uid) if not self.__group_exists(group_dn): raise exception.NotFound("The group at dn %s doesn't exist" % - (group_dn,)) + group_dn) if self.__is_in_group(uid, group_dn): raise exception.Duplicate("User %s is already a member of " "the group %s" % (uid, group_dn)) @@ -461,13 +473,13 @@ class LdapDriver(object): """Remove user from group""" if not self.__group_exists(group_dn): raise exception.NotFound("The group at dn %s doesn't exist" % - (group_dn,)) + group_dn) if not self.__user_exists(uid): raise exception.NotFound("User %s can't be removed from the " - "group because the user doesn't exist" % (uid,)) + "group because the user doesn't exist" % uid) if not self.__is_in_group(uid, group_dn): raise exception.NotFound("User %s is not a member of the group" % - (uid,)) + uid) # NOTE(vish): remove user from group and any sub_groups sub_dns = self.__find_group_dns_with_member( group_dn, uid) @@ -489,7 +501,7 @@ class LdapDriver(object): """Remove user from all roles and projects""" if not self.__user_exists(uid): raise exception.NotFound("User %s can't be removed from all " - "because the user doesn't exist" % (uid,)) + "because the user doesn't exist" % uid) role_dns = self.__find_group_dns_with_member( FLAGS.role_project_subtree, uid) for role_dn in role_dns: @@ -516,13 +528,13 @@ class LdapDriver(object): if attr is None: return None if ('accessKey' in attr.keys() and 'secretKey' in attr.keys() \ - and 'isNovaAdmin' in attr.keys()): + and LdapDriver.isadmin_attribute in attr.keys()): return { 'id': attr[FLAGS.ldap_user_id_attribute][0], 'name': attr[FLAGS.ldap_user_name_attribute][0], 'access': attr['accessKey'][0], 'secret': attr['secretKey'][0], - 'admin': (attr['isNovaAdmin'][0] == 'TRUE')} + 'admin': (attr[LdapDriver.isadmin_attribute][0] == 'TRUE')} else: return None @@ -534,7 +546,8 @@ class LdapDriver(object): return { 'id': attr['cn'][0], 'name': attr['cn'][0], - 'project_manager_id': self.__dn_to_uid(attr['owner'][0]), + 'project_manager_id': + self.__dn_to_uid(attr[LdapDriver.project_attribute][0]), 'description': attr.get('description', [None])[0], 'member_ids': [self.__dn_to_uid(x) for x in member_dns]} diff --git a/nova/auth/nova_openldap.schema b/nova/auth/nova_openldap.schema index 1a10a445d9..daa3a8442c 100644 --- a/nova/auth/nova_openldap.schema +++ b/nova/auth/nova_openldap.schema @@ -1,7 +1,9 @@ # # Person object for Nova # inetorgperson with extra attributes -# Author: Vishvananda Ishaya +# Schema version: 2 +# Authors: Vishvananda Ishaya +# Ryan Lane # # diff --git a/nova/auth/nova_sun.schema b/nova/auth/nova_sun.schema index 1a04601b50..8e9052ded8 100644 --- a/nova/auth/nova_sun.schema +++ b/nova/auth/nova_sun.schema @@ -1,8 +1,9 @@ # # Person object for Nova # inetorgperson with extra attributes -# Author: Vishvananda Ishaya -# Modified for strict RFC 4512 compatibility by: Ryan Lane +# Schema version: 2 +# Authors: Vishvananda Ishaya +# Ryan Lane # # using internet experimental oid arc as per BP64 3.1 dn: cn=schema From 5e79e5957a016c1f38fb2d126f710078a4b7f9a2 Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Wed, 8 Dec 2010 16:26:12 +0000 Subject: [PATCH 085/229] Setting the default schema version to the new schema --- 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 21d8f80658..eac1db5471 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -32,7 +32,7 @@ from nova import flags FLAGS = flags.FLAGS -flags.DEFINE_integer('ldap_schema_version', 1, +flags.DEFINE_integer('ldap_schema_version', 2, 'Current version of the LDAP schema') flags.DEFINE_string('ldap_url', 'ldap://localhost', 'Point this at your ldap server') From 55bc83b07abc8700c2b619be6be88b348f42a4d8 Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Wed, 8 Dec 2010 16:38:35 +0000 Subject: [PATCH 086/229] PEP8 fixes --- nova/auth/ldapdriver.py | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index eac1db5471..870262a156 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -40,7 +40,8 @@ flags.DEFINE_string('ldap_password', 'changeme', 'LDAP password') flags.DEFINE_string('ldap_user_dn', 'cn=Manager,dc=example,dc=com', 'DN of admin user') flags.DEFINE_string('ldap_user_id_attribute', 'uid', 'Attribute to use as id') -flags.DEFINE_string('ldap_user_name_attribute', 'cn', 'Attribute to use as name') +flags.DEFINE_string('ldap_user_name_attribute', 'cn', + 'Attribute to use as name') flags.DEFINE_string('ldap_user_unit', 'Users', 'OID for Users') flags.DEFINE_string('ldap_user_subtree', 'ou=Users,dc=example,dc=com', 'OU for Users') @@ -153,23 +154,23 @@ class LdapDriver(object): # Malformed entries are useless, replace attributes found. attr = [] if 'secretKey' in user.keys(): - attr.append((self.ldap.MOD_REPLACE, 'secretKey', \ + attr.append((self.ldap.MOD_REPLACE, 'secretKey', [secret_key])) else: - attr.append((self.ldap.MOD_ADD, 'secretKey', \ + attr.append((self.ldap.MOD_ADD, 'secretKey', [secret_key])) if 'accessKey' in user.keys(): - attr.append((self.ldap.MOD_REPLACE, 'accessKey', \ + attr.append((self.ldap.MOD_REPLACE, 'accessKey', [access_key])) else: - attr.append((self.ldap.MOD_ADD, 'accessKey', \ + attr.append((self.ldap.MOD_ADD, 'accessKey', [access_key])) if LdapDriver.isadmin_attribute in user.keys(): - attr.append((self.ldap.MOD_REPLACE, LdapDriver.isadmin_attribute, \ - [str(is_admin).upper()])) + attr.append((self.ldap.MOD_REPLACE, + LdapDriver.isadmin_attribute, [str(is_admin).upper()])) else: - attr.append((self.ldap.MOD_ADD, LdapDriver.isadmin_attribute, \ - [str(is_admin).upper()])) + attr.append((self.ldap.MOD_ADD, + LdapDriver.isadmin_attribute, [str(is_admin).upper()])) self.conn.modify_s(self.__uid_to_dn(name), attr) return self.get_user(name) else: @@ -236,7 +237,8 @@ class LdapDriver(object): "manager %s doesn't exist" % manager_uid) manager_dn = self.__uid_to_dn(manager_uid) - attr.append((self.ldap.MOD_REPLACE, LdapDriver.project_attribute, manager_dn)) + attr.append((self.ldap.MOD_REPLACE, LdapDriver.project_attribute, + manager_dn)) if description: attr.append((self.ldap.MOD_REPLACE, 'description', description)) self.conn.modify_s('cn=%s,%s' % (project_id, @@ -312,14 +314,15 @@ class LdapDriver(object): # Retrieve user by name user = self.__get_ldap_user(uid) if 'secretKey' in user.keys(): - attr.append((self.ldap.MOD_DELETE, 'secretKey', \ - user['secretKey'])) + attr.append((self.ldap.MOD_DELETE, 'secretKey', + user['secretKey'])) if 'accessKey' in user.keys(): - attr.append((self.ldap.MOD_DELETE, 'accessKey', \ - user['accessKey'])) + attr.append((self.ldap.MOD_DELETE, 'accessKey', + user['accessKey'])) if LdapDriver.isadmin_attribute in user.keys(): - attr.append((self.ldap.MOD_DELETE, LdapDriver.isadmin_attribute, \ - user[LdapDriver.isadmin_attribute])) + attr.append((self.ldap.MOD_DELETE, + LdapDriver.isadmin_attribute, + user[LdapDriver.isadmin_attribute])) self.conn.modify_s(self.__uid_to_dn(uid), attr) else: # Delete entry @@ -341,7 +344,8 @@ class LdapDriver(object): if secret_key: attr.append((self.ldap.MOD_REPLACE, 'secretKey', secret_key)) if admin is not None: - attr.append((self.ldap.MOD_REPLACE, LdapDriver.isadmin_attribute, str(admin).upper())) + attr.append((self.ldap.MOD_REPLACE, LdapDriver.isadmin_attribute, + str(admin).upper())) self.conn.modify_s(self.__uid_to_dn(uid), attr) def __user_exists(self, uid): @@ -395,7 +399,8 @@ class LdapDriver(object): def __find_role_dns(self, tree): """Find dns of role objects in given tree""" - query = '(&(objectclass=groupOfNames)(!%s))' % LdapDriver.project_pattern + query = ('(&(objectclass=groupOfNames)(!%s))' % + LdapDriver.project_pattern) return self.__find_dns(tree, query) def __find_group_dns_with_member(self, tree, uid): From 180c94585ac9bb0e72a936f64ed27052af395999 Mon Sep 17 00:00:00 2001 From: Anne Gentle Date: Wed, 8 Dec 2010 11:59:37 -0600 Subject: [PATCH 087/229] 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 3b06d7d911..fcb76c5e53 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 From 63006a18701ff185e6837aa2b88f001052643460 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Wed, 8 Dec 2010 18:49:28 +0000 Subject: [PATCH 088/229] typo fix --- nova/virt/xenapi/volume_utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/virt/xenapi/volume_utils.py b/nova/virt/xenapi/volume_utils.py index 051d0fe853..d247066aa4 100644 --- a/nova/virt/xenapi/volume_utils.py +++ b/nova/virt/xenapi/volume_utils.py @@ -78,14 +78,14 @@ class VolumeHelper(): if 'chapuser' in info and 'chappassword' in info: record = {'target': info['targetHost'], 'port': info['targetPort'], - 'targetIQN': info['targeIQN'], + 'targetIQN': info['targetIQN'], 'chapuser': info['chapuser'], 'chappassword': info['chappassword'] } else: record = {'target': info['targetHost'], 'port': info['targetPort'], - 'targetIQN': info['targeIQN'] + 'targetIQN': info['targetIQN'] } try: sr_ref = session.get_xenapi().SR.create( @@ -211,7 +211,7 @@ class VolumeHelper(): volume_info['volumeId'] = volume_id volume_info['targetHost'] = target_host volume_info['targetPort'] = target_port - volume_info['targeIQN'] = target_iqn + volume_info['targetIQN'] = target_iqn defer.returnValue(volume_info) @classmethod From d4b6cfe98f0ce81c21a45f420ce30c5c693c1144 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 8 Dec 2010 14:05:50 -0600 Subject: [PATCH 089/229] 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 801867bd41..c87a131a01 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 ba73079ec7..542d4894cd 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 26b30bf927..ac5f5e342d 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) From fd7931847de7cb24c629380fb71bca7833710edc Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 8 Dec 2010 14:16:49 -0600 Subject: [PATCH 090/229] 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 c87a131a01..2a75f9dbf9 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 ac5f5e342d..2153810c85 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): From 708425aa5b42aae0f399b127ee5a648b7162b05e Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 8 Dec 2010 12:20:44 -0800 Subject: [PATCH 091/229] 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 5480733265..17a1a4c5c1 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 From 0ae019062712fd15dd9e040a3fa60546db9c4111 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 8 Dec 2010 23:47:25 +0000 Subject: [PATCH 092/229] added to Authors --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index 62f0c49d5d..a1703b279d 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 From 3c85f1b7ed593a2d4d126a34241f217da5cf7ce6 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 8 Dec 2010 17:18:27 -0800 Subject: [PATCH 093/229] intermediate commit to checkpoint progress all relevant tests are passing except volume, next step is volume manager fixery --- nova/manager.py | 3 +- nova/objectstore/image.py | 3 + nova/rpc.py | 75 ++++++------------- nova/service.py | 1 - nova/test.py | 48 ++++++------- nova/tests/api/__init__.py | 3 +- nova/tests/auth_unittest.py | 5 +- nova/tests/cloud_unittest.py | 4 +- nova/tests/compute_unittest.py | 39 +++++----- nova/tests/rpc_unittest.py | 34 ++++----- nova/tests/service_unittest.py | 9 +-- nova/tests/virt_unittest.py | 7 +- nova/tests/volume_unittest.py | 56 +++++++-------- nova/utils.py | 23 +++--- nova/virt/images.py | 9 +-- nova/virt/libvirt_conn.py | 28 ++++---- nova/virt/xenapi/vm_utils.py | 28 +++----- nova/virt/xenapi_conn.py | 1 - nova/volume/driver.py | 127 ++++++++++++++------------------- run_tests.py | 14 +++- 20 files changed, 226 insertions(+), 291 deletions(-) diff --git a/nova/manager.py b/nova/manager.py index 5e067bd088..a343d7fc6a 100644 --- a/nova/manager.py +++ b/nova/manager.py @@ -55,7 +55,6 @@ from nova import utils from nova import flags from nova.db import base -from twisted.internet import defer FLAGS = flags.FLAGS @@ -69,7 +68,7 @@ class Manager(base.Base): def periodic_tasks(self, context=None): """Tasks to be run at a periodic interval""" - yield + return def init_host(self): """Do any initialization that needs to be run if this is a standalone diff --git a/nova/objectstore/image.py b/nova/objectstore/image.py index 7292dbab8f..2fe0b0117c 100644 --- a/nova/objectstore/image.py +++ b/nova/objectstore/image.py @@ -26,6 +26,7 @@ Requires decryption using keys in the manifest. import binascii import glob import json +import logging import os import shutil import tarfile @@ -264,6 +265,8 @@ class Image(object): if err: raise exception.Error("Failed to decrypt initialization " "vector: %s" % err) + logging.debug(iv) + _out, err = utils.execute( 'openssl enc -d -aes-128-cbc -in %s -K %s -iv %s -out %s' % (encrypted_filename, key, iv, decrypted_filename), diff --git a/nova/rpc.py b/nova/rpc.py index 86a29574f3..652b9e4aa8 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -25,18 +25,18 @@ import json import logging import sys import time +import traceback import uuid from carrot import connection as carrot_connection from carrot import messaging from eventlet import greenthread -from twisted.internet import defer -from twisted.internet import task +from nova import context from nova import exception from nova import fakerabbit from nova import flags -from nova import context +from nova import utils FLAGS = flags.FLAGS @@ -128,17 +128,9 @@ class Consumer(messaging.Consumer): def attach_to_eventlet(self): """Only needed for unit tests!""" - def fetch_repeatedly(): - while True: - self.fetch(enable_callbacks=True) - greenthread.sleep(0.1) - greenthread.spawn(fetch_repeatedly) - - def attach_to_twisted(self): - """Attach a callback to twisted that fires 10 times a second""" - loop = task.LoopingCall(self.fetch, enable_callbacks=True) - loop.start(interval=0.1) - return loop + timer = utils.LoopingCall(self.fetch, enable_callbacks=True) + timer.start(0.1) + return timer class Publisher(messaging.Publisher): @@ -197,10 +189,13 @@ class AdapterConsumer(TopicConsumer): node_args = dict((str(k), v) for k, v in args.iteritems()) # NOTE(vish): magic is fun! # pylint: disable-msg=W0142 - d = defer.maybeDeferred(node_func, context=ctxt, **node_args) - if msg_id: - d.addCallback(lambda rval: msg_reply(msg_id, rval, None)) - d.addErrback(lambda e: msg_reply(msg_id, None, e)) + try: + rval = node_func(context=ctxt, **node_args) + if msg_id: + msg_reply(msg_id, rval, None) + except Exception as e: + if msg_id: + msg_reply(msg_id, None, sys.exc_info()) return @@ -244,11 +239,11 @@ def msg_reply(msg_id, reply=None, failure=None): failure should be a twisted failure object""" if failure: - message = failure.getErrorMessage() - traceback = failure.getTraceback() + message = str(failure[1]) + tb = traceback.format_exception(*failure) logging.error("Returning exception %s to caller", message) - logging.error(traceback) - failure = (failure.type.__name__, str(failure.value), traceback) + logging.error(tb) + failure = (failure[0].__name__, str(failure[1]), tb) conn = Connection.instance() publisher = DirectPublisher(connection=conn, msg_id=msg_id) try: @@ -313,8 +308,8 @@ def call(context, topic, msg): _pack_context(msg, context) class WaitMessage(object): - def __call__(self, data, message): + LOG.debug('data %s, msg %s', data, message) """Acks message and sets result.""" message.ack() if data['failure']: @@ -337,41 +332,11 @@ def call(context, topic, msg): except StopIteration: pass consumer.close() + if isinstance(wait_msg.result, Exception): + raise wait_msg.result return wait_msg.result -def call_twisted(context, topic, msg): - """Sends a message on a topic and wait for a response""" - LOG.debug("Making asynchronous call...") - msg_id = uuid.uuid4().hex - msg.update({'_msg_id': msg_id}) - LOG.debug("MSG_ID is %s" % (msg_id)) - _pack_context(msg, context) - - conn = Connection.instance() - d = defer.Deferred() - consumer = DirectConsumer(connection=conn, msg_id=msg_id) - - def deferred_receive(data, message): - """Acks message and callbacks or errbacks""" - message.ack() - if data['failure']: - return d.errback(RemoteError(*data['failure'])) - else: - return d.callback(data['result']) - - consumer.register_callback(deferred_receive) - injected = consumer.attach_to_twisted() - - # clean up after the injected listened and return x - d.addCallback(lambda x: injected.stop() and x or x) - - publisher = TopicPublisher(connection=conn, topic=topic) - publisher.send(msg) - publisher.close() - return d - - def cast(context, topic, msg): """Sends a message on a topic without waiting for a response""" LOG.debug("Making asynchronous cast...") diff --git a/nova/service.py b/nova/service.py index 9454d4049b..55a0bb2120 100644 --- a/nova/service.py +++ b/nova/service.py @@ -160,7 +160,6 @@ class Service(object, service.Service): except exception.NotFound: logging.warn("Service killed that has no database entry") - @defer.inlineCallbacks def periodic_tasks(self): """Tasks to be run at a periodic interval""" yield self.manager.periodic_tasks(context.get_admin_context()) diff --git a/nova/test.py b/nova/test.py index 5c2a728196..bbf063aca3 100644 --- a/nova/test.py +++ b/nova/test.py @@ -25,11 +25,11 @@ and some black magic for inline callbacks. import datetime import sys import time +import unittest import mox import stubout from twisted.internet import defer -from twisted.trial import unittest from nova import context from nova import db @@ -94,7 +94,7 @@ class TrialTestCase(unittest.TestCase): db.fixed_ip_disassociate_all_by_timeout(ctxt, FLAGS.host, self.start) db.network_disassociate_all(ctxt) - rpc.Consumer.attach_to_twisted = self.originalAttach + rpc.Consumer.attach_to_eventlet = self.originalAttach for x in self.injected: try: x.stop() @@ -125,31 +125,31 @@ class TrialTestCase(unittest.TestCase): for k, v in self._original_flags.iteritems(): setattr(FLAGS, k, v) - def run(self, result=None): - test_method = getattr(self, self._testMethodName) - setattr(self, - self._testMethodName, - self._maybeInlineCallbacks(test_method, result)) - rv = super(TrialTestCase, self).run(result) - setattr(self, self._testMethodName, test_method) - return rv + #def run(self, result=None): + # test_method = getattr(self, self._testMethodName) + # setattr(self, + # self._testMethodName, + # self._maybeInlineCallbacks(test_method, result)) + # rv = super(TrialTestCase, self).run(result) + # setattr(self, self._testMethodName, test_method) + # return rv - def _maybeInlineCallbacks(self, func, result): - def _wrapped(): - g = func() - if isinstance(g, defer.Deferred): - return g - if not hasattr(g, 'send'): - return defer.succeed(g) + #def _maybeInlineCallbacks(self, func, result): + # def _wrapped(): + # g = func() + # if isinstance(g, defer.Deferred): + # return g + # if not hasattr(g, 'send'): + # return defer.succeed(g) - inlined = defer.inlineCallbacks(func) - d = inlined() - return d - _wrapped.func_name = func.func_name - return _wrapped + # inlined = defer.inlineCallbacks(func) + # d = inlined() + # return d + # _wrapped.func_name = func.func_name + # return _wrapped def _monkey_patch_attach(self): - self.originalAttach = rpc.Consumer.attach_to_twisted + self.originalAttach = rpc.Consumer.attach_to_eventlet def _wrapped(innerSelf): rv = self.originalAttach(innerSelf) @@ -157,4 +157,4 @@ class TrialTestCase(unittest.TestCase): return rv _wrapped.func_name = self.originalAttach.func_name - rpc.Consumer.attach_to_twisted = _wrapped + rpc.Consumer.attach_to_eventlet = _wrapped diff --git a/nova/tests/api/__init__.py b/nova/tests/api/__init__.py index 9caa8c9d0d..cdc1bbf001 100644 --- a/nova/tests/api/__init__.py +++ b/nova/tests/api/__init__.py @@ -78,4 +78,5 @@ class Test(unittest.TestCase): if __name__ == '__main__': - unittest.main() + pass + #unittest.main() diff --git a/nova/tests/auth_unittest.py b/nova/tests/auth_unittest.py index fe891beeeb..129ff223d5 100644 --- a/nova/tests/auth_unittest.py +++ b/nova/tests/auth_unittest.py @@ -16,10 +16,13 @@ # License for the specific language governing permissions and limitations # under the License. -import logging +#import logging from M2Crypto import X509 import unittest +import eventlet +logging = eventlet.import_patched('logging') + from nova import crypto from nova import flags from nova import test diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index 9886a2449b..b7b856da52 100644 --- a/nova/tests/cloud_unittest.py +++ b/nova/tests/cloud_unittest.py @@ -27,8 +27,6 @@ import tempfile import time from eventlet import greenthread -from twisted.internet import defer -import unittest from xml.etree import ElementTree from nova import context @@ -186,7 +184,7 @@ class CloudTestCase(test.TrialTestCase): logging.debug("Need to watch instance %s until it's running..." % instance['instance_id']) while True: - rv = yield defer.succeed(time.sleep(1)) + greenthread.sleep(1) info = self.cloud._get_instance(instance['instance_id']) logging.debug(info['state']) if info['state'] == power_state.RUNNING: diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py index 6f3ef96cbb..67cea72c95 100644 --- a/nova/tests/compute_unittest.py +++ b/nova/tests/compute_unittest.py @@ -22,8 +22,6 @@ Tests For Compute import datetime import logging -from twisted.internet import defer - from nova import context from nova import db from nova import exception @@ -33,6 +31,7 @@ from nova import utils from nova.auth import manager from nova.compute import api as compute_api + FLAGS = flags.FLAGS @@ -94,24 +93,22 @@ class ComputeTestCase(test.TrialTestCase): db.security_group_destroy(self.context, group['id']) db.instance_destroy(self.context, ref[0]['id']) - @defer.inlineCallbacks def test_run_terminate(self): """Make sure it is possible to run and terminate instance""" instance_id = self._create_instance() - yield self.compute.run_instance(self.context, instance_id) + self.compute.run_instance(self.context, instance_id) instances = db.instance_get_all(context.get_admin_context()) logging.info("Running instances: %s", instances) self.assertEqual(len(instances), 1) - yield self.compute.terminate_instance(self.context, instance_id) + self.compute.terminate_instance(self.context, instance_id) instances = db.instance_get_all(context.get_admin_context()) logging.info("After terminating instances: %s", instances) self.assertEqual(len(instances), 0) - @defer.inlineCallbacks def test_run_terminate_timestamps(self): """Make sure timestamps are set for launched and destroyed""" instance_id = self._create_instance() @@ -119,42 +116,40 @@ class ComputeTestCase(test.TrialTestCase): self.assertEqual(instance_ref['launched_at'], None) self.assertEqual(instance_ref['deleted_at'], None) launch = datetime.datetime.utcnow() - yield self.compute.run_instance(self.context, instance_id) + self.compute.run_instance(self.context, instance_id) instance_ref = db.instance_get(self.context, instance_id) self.assert_(instance_ref['launched_at'] > launch) self.assertEqual(instance_ref['deleted_at'], None) terminate = datetime.datetime.utcnow() - yield self.compute.terminate_instance(self.context, instance_id) + self.compute.terminate_instance(self.context, instance_id) self.context = self.context.elevated(True) instance_ref = db.instance_get(self.context, instance_id) self.assert_(instance_ref['launched_at'] < terminate) self.assert_(instance_ref['deleted_at'] > terminate) - @defer.inlineCallbacks def test_reboot(self): """Ensure instance can be rebooted""" instance_id = self._create_instance() - yield self.compute.run_instance(self.context, instance_id) - yield self.compute.reboot_instance(self.context, instance_id) - yield self.compute.terminate_instance(self.context, instance_id) + self.compute.run_instance(self.context, instance_id) + self.compute.reboot_instance(self.context, instance_id) + self.compute.terminate_instance(self.context, instance_id) - @defer.inlineCallbacks def test_console_output(self): """Make sure we can get console output from instance""" instance_id = self._create_instance() - yield self.compute.run_instance(self.context, instance_id) + self.compute.run_instance(self.context, instance_id) - console = yield self.compute.get_console_output(self.context, + console = self.compute.get_console_output(self.context, instance_id) self.assert_(console) - yield self.compute.terminate_instance(self.context, instance_id) + self.compute.terminate_instance(self.context, instance_id) - @defer.inlineCallbacks def test_run_instance_existing(self): """Ensure failure when running an instance that already exists""" instance_id = self._create_instance() - yield self.compute.run_instance(self.context, instance_id) - self.assertFailure(self.compute.run_instance(self.context, - instance_id), - exception.Error) - yield self.compute.terminate_instance(self.context, instance_id) + self.compute.run_instance(self.context, instance_id) + self.assertRaises(exception.Error, + self.compute.run_instance, + self.context, + instance_id) + self.compute.terminate_instance(self.context, instance_id) diff --git a/nova/tests/rpc_unittest.py b/nova/tests/rpc_unittest.py index f35b65a39c..c2ad5cd795 100644 --- a/nova/tests/rpc_unittest.py +++ b/nova/tests/rpc_unittest.py @@ -20,8 +20,6 @@ Unit Tests for remote procedure calls using queue """ import logging -from twisted.internet import defer - from nova import context from nova import flags from nova import rpc @@ -40,23 +38,22 @@ class RpcTestCase(test.TrialTestCase): self.consumer = rpc.AdapterConsumer(connection=self.conn, topic='test', proxy=self.receiver) - self.consumer.attach_to_twisted() + self.consumer.attach_to_eventlet() self.context = context.get_admin_context() def test_call_succeed(self): """Get a value through rpc call""" value = 42 - result = yield rpc.call_twisted(self.context, - 'test', {"method": "echo", + result = rpc.call(self.context, 'test', {"method": "echo", "args": {"value": value}}) self.assertEqual(value, result) def test_context_passed(self): """Makes sure a context is passed through rpc call""" value = 42 - result = yield rpc.call_twisted(self.context, - 'test', {"method": "context", - "args": {"value": value}}) + result = rpc.call(self.context, + 'test', {"method": "context", + "args": {"value": value}}) self.assertEqual(self.context.to_dict(), result) def test_call_exception(self): @@ -67,14 +64,17 @@ class RpcTestCase(test.TrialTestCase): to an int in the test. """ value = 42 - self.assertFailure(rpc.call_twisted(self.context, 'test', - {"method": "fail", - "args": {"value": value}}), - rpc.RemoteError) + self.assertRaises(rpc.RemoteError, + rpc.call, + self.context, + 'test', + {"method": "fail", + "args": {"value": value}}) try: - yield rpc.call_twisted(self.context, - 'test', {"method": "fail", - "args": {"value": value}}) + rpc.call(self.context, + 'test', + {"method": "fail", + "args": {"value": value}}) self.fail("should have thrown rpc.RemoteError") except rpc.RemoteError as exc: self.assertEqual(int(exc.value), value) @@ -89,13 +89,13 @@ class TestReceiver(object): def echo(context, value): """Simply returns whatever value is sent in""" logging.debug("Received %s", value) - return defer.succeed(value) + return value @staticmethod def context(context, value): """Returns dictionary version of context""" logging.debug("Received %s", context) - return defer.succeed(context.to_dict()) + return context.to_dict() @staticmethod def fail(context, value): diff --git a/nova/tests/service_unittest.py b/nova/tests/service_unittest.py index a268bc4fe4..4f8d2d550f 100644 --- a/nova/tests/service_unittest.py +++ b/nova/tests/service_unittest.py @@ -143,7 +143,6 @@ class ServiceTestCase(test.TrialTestCase): # whether it is disconnected, it looks for a variable on itself called # 'model_disconnected' and report_state doesn't really do much so this # these are mostly just for coverage - @defer.inlineCallbacks def test_report_state_no_service(self): host = 'foo' binary = 'bar' @@ -174,9 +173,8 @@ class ServiceTestCase(test.TrialTestCase): topic, 'nova.tests.service_unittest.FakeManager') serv.startService() - yield serv.report_state() + serv.report_state() - @defer.inlineCallbacks def test_report_state_newly_disconnected(self): host = 'foo' binary = 'bar' @@ -205,10 +203,9 @@ class ServiceTestCase(test.TrialTestCase): topic, 'nova.tests.service_unittest.FakeManager') serv.startService() - yield serv.report_state() + serv.report_state() self.assert_(serv.model_disconnected) - @defer.inlineCallbacks def test_report_state_newly_connected(self): host = 'foo' binary = 'bar' @@ -240,6 +237,6 @@ class ServiceTestCase(test.TrialTestCase): 'nova.tests.service_unittest.FakeManager') serv.startService() serv.model_disconnected = True - yield serv.report_state() + serv.report_state() self.assert_(not serv.model_disconnected) diff --git a/nova/tests/virt_unittest.py b/nova/tests/virt_unittest.py index d49383fb7b..a4a8d3acf8 100644 --- a/nova/tests/virt_unittest.py +++ b/nova/tests/virt_unittest.py @@ -235,7 +235,7 @@ class NWFilterTestCase(test.TrialTestCase): 'project_id': 'fake'}) inst_id = instance_ref['id'] - def _ensure_all_called(_): + def _ensure_all_called(): instance_filter = 'nova-instance-%s' % instance_ref['name'] secgroup_filter = 'nova-secgroup-%s' % self.security_group['id'] for required in [secgroup_filter, 'allow-dhcp-server', @@ -253,7 +253,6 @@ class NWFilterTestCase(test.TrialTestCase): instance = db.instance_get(self.context, inst_id) d = self.fw.setup_nwfilters_for_instance(instance) - d.addCallback(_ensure_all_called) - d.addCallback(lambda _: self.teardown_security_group()) - + _ensure_all_called() + self.teardown_security_group() return d diff --git a/nova/tests/volume_unittest.py b/nova/tests/volume_unittest.py index 12321a96fb..93d2ceab79 100644 --- a/nova/tests/volume_unittest.py +++ b/nova/tests/volume_unittest.py @@ -21,8 +21,6 @@ Tests for Volume Code. """ import logging -from twisted.internet import defer - from nova import context from nova import exception from nova import db @@ -56,51 +54,48 @@ class VolumeTestCase(test.TrialTestCase): vol['attach_status'] = "detached" return db.volume_create(context.get_admin_context(), vol)['id'] - @defer.inlineCallbacks def test_create_delete_volume(self): """Test volume can be created and deleted.""" volume_id = self._create_volume() - yield self.volume.create_volume(self.context, volume_id) + self.volume.create_volume(self.context, volume_id) self.assertEqual(volume_id, db.volume_get(context.get_admin_context(), volume_id).id) - yield self.volume.delete_volume(self.context, volume_id) + self.volume.delete_volume(self.context, volume_id) self.assertRaises(exception.NotFound, db.volume_get, self.context, volume_id) - @defer.inlineCallbacks def test_too_big_volume(self): """Ensure failure if a too large of a volume is requested.""" # FIXME(vish): validation needs to move into the data layer in # volume_create - defer.returnValue(True) + return True try: volume_id = self._create_volume('1001') - yield self.volume.create_volume(self.context, volume_id) + self.volume.create_volume(self.context, volume_id) self.fail("Should have thrown TypeError") except TypeError: pass - @defer.inlineCallbacks def test_too_many_volumes(self): """Ensure that NoMoreTargets is raised when we run out of volumes.""" vols = [] total_slots = FLAGS.iscsi_num_targets for _index in xrange(total_slots): volume_id = self._create_volume() - yield self.volume.create_volume(self.context, volume_id) + self.volume.create_volume(self.context, volume_id) vols.append(volume_id) volume_id = self._create_volume() - self.assertFailure(self.volume.create_volume(self.context, - volume_id), - db.NoMoreTargets) + self.assertRaises(db.NoMoreTargets, + self.volume.create_volume, + self.context, + volume_id) db.volume_destroy(context.get_admin_context(), volume_id) for volume_id in vols: - yield self.volume.delete_volume(self.context, volume_id) + self.volume.delete_volume(self.context, volume_id) - @defer.inlineCallbacks def test_run_attach_detach_volume(self): """Make sure volume can be attached and detached from instance.""" inst = {} @@ -115,15 +110,15 @@ class VolumeTestCase(test.TrialTestCase): instance_id = db.instance_create(self.context, inst)['id'] mountpoint = "/dev/sdf" volume_id = self._create_volume() - yield self.volume.create_volume(self.context, volume_id) + self.volume.create_volume(self.context, volume_id) if FLAGS.fake_tests: db.volume_attached(self.context, volume_id, instance_id, mountpoint) else: - yield self.compute.attach_volume(self.context, - instance_id, - volume_id, - mountpoint) + self.compute.attach_volume(self.context, + instance_id, + volume_id, + mountpoint) vol = db.volume_get(context.get_admin_context(), volume_id) self.assertEqual(vol['status'], "in-use") self.assertEqual(vol['attach_status'], "attached") @@ -131,25 +126,26 @@ class VolumeTestCase(test.TrialTestCase): instance_ref = db.volume_get_instance(self.context, volume_id) self.assertEqual(instance_ref['id'], instance_id) - self.assertFailure(self.volume.delete_volume(self.context, volume_id), - exception.Error) + self.assertRaises(exception.Error, + self.volume.delete_volume, + self.context, + volume_id) if FLAGS.fake_tests: db.volume_detached(self.context, volume_id) else: - yield self.compute.detach_volume(self.context, - instance_id, - volume_id) + self.compute.detach_volume(self.context, + instance_id, + volume_id) vol = db.volume_get(self.context, volume_id) self.assertEqual(vol['status'], "available") - yield self.volume.delete_volume(self.context, volume_id) + self.volume.delete_volume(self.context, volume_id) self.assertRaises(exception.Error, db.volume_get, self.context, volume_id) db.instance_destroy(self.context, instance_id) - @defer.inlineCallbacks def test_concurrent_volumes_get_different_targets(self): """Ensure multiple concurrent volumes get different targets.""" volume_ids = [] @@ -164,15 +160,11 @@ class VolumeTestCase(test.TrialTestCase): self.assert_(iscsi_target not in targets) targets.append(iscsi_target) logging.debug("Target %s allocated", iscsi_target) - deferreds = [] total_slots = FLAGS.iscsi_num_targets for _index in xrange(total_slots): volume_id = self._create_volume() d = self.volume.create_volume(self.context, volume_id) - d.addCallback(_check) - d.addErrback(self.fail) - deferreds.append(d) - yield defer.DeferredList(deferreds) + _check(d) for volume_id in volume_ids: self.volume.delete_volume(self.context, volume_id) diff --git a/nova/utils.py b/nova/utils.py index 66047ae8bc..2c43203d88 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -34,8 +34,6 @@ from xml.sax import saxutils from eventlet import event from eventlet import greenthread -from twisted.internet.threads import deferToThread - from nova import exception from nova import flags from nova.exception import ProcessExecutionError @@ -78,7 +76,7 @@ def fetchfile(url, target): def execute(cmd, process_input=None, addl_env=None, check_exit_code=True): - logging.debug("Running cmd: %s", cmd) + logging.debug("Running cmd (subprocess): %s", cmd) env = os.environ.copy() if addl_env: env.update(addl_env) @@ -98,6 +96,10 @@ def execute(cmd, process_input=None, addl_env=None, check_exit_code=True): stdout=stdout, stderr=stderr, cmd=cmd) + # NOTE(termie): this appears to be necessary to let the subprocess call + # clean something up in between calls, without it two + # execute calls in a row hangs the second one + greenthread.sleep(0) return result @@ -126,13 +128,14 @@ def debug(arg): def runthis(prompt, cmd, check_exit_code=True): logging.debug("Running %s" % (cmd)) - exit_code = subprocess.call(cmd.split(" ")) - logging.debug(prompt % (exit_code)) - if check_exit_code and exit_code != 0: - raise ProcessExecutionError(exit_code=exit_code, - stdout=None, - stderr=None, - cmd=cmd) + rv, err = execute(cmd, check_exit_code=check_exit_code) + #exit_code = subprocess.call(cmd.split(" ")) + #logging.debug(prompt % (exit_code)) + #if check_exit_code and exit_code != 0: + # raise ProcessExecutionError(exit_code=exit_code, + # stdout=None, + # stderr=None, + # cmd=cmd) def generate_uid(topic, size=8): diff --git a/nova/virt/images.py b/nova/virt/images.py index 981aa5cf36..4d7c65f126 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -26,7 +26,7 @@ import time import urlparse from nova import flags -from nova import process +from nova import utils from nova.auth import manager from nova.auth import signer from nova.objectstore import image @@ -63,15 +63,16 @@ def _fetch_s3_image(image, path, user, project): cmd = ['/usr/bin/curl', '--fail', '--silent', url] for (k, v) in headers.iteritems(): - cmd += ['-H', '%s: %s' % (k, v)] + cmd += ['-H', '"%s: %s"' % (k, v)] cmd += ['-o', path] - return process.SharedPool().execute(executable=cmd[0], args=cmd[1:]) + cmd_out = ' '.join(cmd) + return utils.execute(cmd_out) def _fetch_local_image(image, path, user, project): source = _image_path('%s/image' % image) - return process.simple_execute('cp %s %s' % (source, path)) + return utils.execute('cp %s %s' % (source, path)) def _image_path(path): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index c09a7c01dc..715e7234cf 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -54,7 +54,6 @@ from nova import context from nova import db from nova import exception from nova import flags -from nova import process from nova import utils #from nova.api import context from nova.auth import manager @@ -366,8 +365,8 @@ class LibvirtConnection(object): if virsh_output.startswith('/dev/'): logging.info('cool, it\'s a device') - r = process.simple_execute("sudo dd if=%s iflag=nonblock" % - virsh_output, check_exit_code=False) + r = utils.execute("sudo dd if=%s iflag=nonblock" % + virsh_output, check_exit_code=False) return r[0] else: return '' @@ -389,13 +388,13 @@ class LibvirtConnection(object): console_log = os.path.join(FLAGS.instances_path, instance['name'], 'console.log') - process.simple_execute('sudo chown %d %s' % (os.getuid(), - console_log)) + utils.execute('sudo chown %d %s' % (os.getuid(), + console_log)) if FLAGS.libvirt_type == 'xen': # Xen is special - virsh_output = process.simple_execute("virsh ttyconsole %s" % - instance['name']) + virsh_output = utils.execute("virsh ttyconsole %s" % + instance['name']) data = self._flush_xen_console(virsh_output) fpath = self._append_to_file(data, console_log) else: @@ -411,8 +410,8 @@ class LibvirtConnection(object): prefix + fname) # ensure directories exist and are writable - process.simple_execute('mkdir -p %s' % basepath(prefix='')) - process.simple_execute('chmod 0777 %s' % basepath(prefix='')) + utils.execute('mkdir -p %s' % basepath(prefix='')) + utils.execute('chmod 0777 %s' % basepath(prefix='')) # TODO(termie): these are blocking calls, it would be great # if they weren't. @@ -443,9 +442,9 @@ class LibvirtConnection(object): project) def execute(cmd, process_input=None, check_exit_code=True): - return process.simple_execute(cmd=cmd, - process_input=process_input, - check_exit_code=check_exit_code) + return utils.execute(cmd=cmd, + process_input=process_input, + check_exit_code=check_exit_code) key = str(inst['key_data']) net = None @@ -471,7 +470,7 @@ class LibvirtConnection(object): execute=execute) if os.path.exists(basepath('disk')): - process.simple_execute('rm -f %s' % basepath('disk')) + utils.execute('rm -f %s' % basepath('disk')) local_bytes = (instance_types.INSTANCE_TYPES[inst.instance_type] ['local_gb'] @@ -485,8 +484,7 @@ class LibvirtConnection(object): local_bytes, resize, execute=execute) if FLAGS.libvirt_type == 'uml': - process.simple_execute('sudo chown root %s' % - basepath('disk')) + utils.execute('sudo chown root %s' % basepath('disk')) def to_xml(self, instance, rescue=False): # TODO(termie): cache? diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 99d484ca2b..b72b8e13d3 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -21,14 +21,13 @@ their attributes like VDIs, VIFs, as well as their lookup functions. import logging -from twisted.internet import defer - 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 + XENAPI_POWER_STATE = { 'Halted': power_state.SHUTDOWN, 'Running': power_state.RUNNING, @@ -36,6 +35,7 @@ XENAPI_POWER_STATE = { 'Suspended': power_state.SHUTDOWN, # FIXME 'Crashed': power_state.CRASHED} + XenAPI = None @@ -49,7 +49,6 @@ class VMHelper(): XenAPI = __import__('XenAPI') @classmethod - @defer.inlineCallbacks def create_vm(cls, session, instance, kernel, ramdisk): """Create a VM record. Returns a Deferred that gives the new VM reference.""" @@ -87,12 +86,11 @@ class VMHelper(): 'other_config': {}, } logging.debug('Created VM %s...', instance.name) - vm_ref = yield session.call_xenapi('VM.create', rec) + vm_ref = session.call_xenapi('VM.create', rec) logging.debug('Created VM %s as %s.', instance.name, vm_ref) - defer.returnValue(vm_ref) + return vm_ref @classmethod - @defer.inlineCallbacks def create_vbd(cls, session, vm_ref, vdi_ref, userdevice, bootable): """Create a VBD record. Returns a Deferred that gives the new VBD reference.""" @@ -111,13 +109,12 @@ class VMHelper(): vbd_rec['qos_algorithm_params'] = {} vbd_rec['qos_supported_algorithms'] = [] logging.debug('Creating VBD for VM %s, VDI %s ... ', vm_ref, vdi_ref) - vbd_ref = yield session.call_xenapi('VBD.create', vbd_rec) + vbd_ref = session.call_xenapi('VBD.create', vbd_rec) logging.debug('Created VBD %s for VM %s, VDI %s.', vbd_ref, vm_ref, vdi_ref) - defer.returnValue(vbd_ref) + return vbd_ref @classmethod - @defer.inlineCallbacks def create_vif(cls, session, vm_ref, network_ref, mac_address): """Create a VIF record. Returns a Deferred that gives the new VIF reference.""" @@ -133,13 +130,12 @@ class VMHelper(): vif_rec['qos_algorithm_params'] = {} logging.debug('Creating VIF for VM %s, network %s ... ', vm_ref, network_ref) - vif_ref = yield session.call_xenapi('VIF.create', vif_rec) + vif_ref = session.call_xenapi('VIF.create', vif_rec) logging.debug('Created VIF %s for VM %s, network %s.', vif_ref, vm_ref, network_ref) - defer.returnValue(vif_ref) + return vif_ref @classmethod - @defer.inlineCallbacks def fetch_image(cls, session, image, user, project, use_sr): """use_sr: True to put the image as a VDI in an SR, False to place it on dom0's filesystem. The former is for VM disks, the latter for @@ -156,12 +152,11 @@ class VMHelper(): args['password'] = user.secret if use_sr: args['add_partition'] = 'true' - task = yield session.async_call_plugin('objectstore', fn, args) - uuid = yield session.wait_for_task(task) - defer.returnValue(uuid) + task = session.async_call_plugin('objectstore', fn, args) + uuid = session.wait_for_task(task) + return uuid @classmethod - @utils.deferredToThread def lookup(cls, session, i): """ Look the instance i up, and returns it if available """ return VMHelper.lookup_blocking(session, i) @@ -179,7 +174,6 @@ class VMHelper(): return vms[0] @classmethod - @utils.deferredToThread def lookup_vm_vdis(cls, session, vm): """ Look for the VDIs that are attached to the VM """ return VMHelper.lookup_vm_vdis_blocking(session, vm) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index dacf9fe2b4..96d211cc08 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -167,7 +167,6 @@ class XenAPISession(object): self.get_xenapi_host(), plugin, fn, args) def wait_for_task(self, task): ->>>>>>> MERGE-SOURCE """Return a Deferred that will give the result of the given task. The task is polled until it completes.""" diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 156aad2a09..f675c91320 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -23,8 +23,6 @@ Drivers for volumes. import logging import os -from twisted.internet import defer - from nova import exception from nova import flags from nova import process @@ -55,14 +53,13 @@ flags.DEFINE_string('iscsi_ip_prefix', '127.0', class VolumeDriver(object): """Executes commands relating to Volumes.""" - def __init__(self, execute=process.simple_execute, + def __init__(self, execute=utils.execute, sync_exec=utils.execute, *args, **kwargs): # NOTE(vish): db is set by Manager self.db = None self._execute = execute self._sync_exec = sync_exec - @defer.inlineCallbacks def _try_execute(self, command): # NOTE(vish): Volume commands can partially fail due to timing, but # running them a second time on failure will usually @@ -70,15 +67,15 @@ class VolumeDriver(object): tries = 0 while True: try: - yield self._execute(command) - defer.returnValue(True) + self._execute(command) + return True except exception.ProcessExecutionError: tries = tries + 1 if tries >= FLAGS.num_shell_tries: raise logging.exception("Recovering from a failed execute." "Try number %s", tries) - yield self._execute("sleep %s" % tries ** 2) + self._execute("sleep %s" % tries ** 2) def check_for_setup_error(self): """Returns an error if prerequisites aren't met""" @@ -86,53 +83,46 @@ class VolumeDriver(object): raise exception.Error("volume group %s doesn't exist" % FLAGS.volume_group) - @defer.inlineCallbacks def create_volume(self, volume): """Creates a logical volume.""" if int(volume['size']) == 0: sizestr = '100M' else: sizestr = '%sG' % volume['size'] - yield self._try_execute("sudo lvcreate -L %s -n %s %s" % - (sizestr, + self._try_execute("sudo lvcreate -L %s -n %s %s" % + (sizestr, volume['name'], FLAGS.volume_group)) - @defer.inlineCallbacks def delete_volume(self, volume): """Deletes a logical volume.""" - yield self._try_execute("sudo lvremove -f %s/%s" % - (FLAGS.volume_group, + self._try_execute("sudo lvremove -f %s/%s" % + (FLAGS.volume_group, volume['name'])) - @defer.inlineCallbacks def local_path(self, volume): - yield # NOTE(vish): stops deprecation warning + # NOTE(vish): stops deprecation warning escaped_group = FLAGS.volume_group.replace('-', '--') escaped_name = volume['name'].replace('-', '--') - defer.returnValue("/dev/mapper/%s-%s" % (escaped_group, - escaped_name)) + return "/dev/mapper/%s-%s" % (escaped_group, + escaped_name) def ensure_export(self, context, volume): """Synchronously recreates an export for a logical volume.""" raise NotImplementedError() - @defer.inlineCallbacks def create_export(self, context, volume): """Exports the volume.""" raise NotImplementedError() - @defer.inlineCallbacks def remove_export(self, context, volume): """Removes an export for a logical volume.""" raise NotImplementedError() - @defer.inlineCallbacks def discover_volume(self, volume): """Discover volume on a remote host.""" raise NotImplementedError() - @defer.inlineCallbacks def undiscover_volume(self, volume): """Undiscover volume on a remote host.""" raise NotImplementedError() @@ -155,14 +145,13 @@ class AOEDriver(VolumeDriver): dev = {'shelf_id': shelf_id, 'blade_id': blade_id} self.db.export_device_create_safe(context, dev) - @defer.inlineCallbacks def create_export(self, context, volume): """Creates an export for a logical volume.""" self._ensure_blades(context) (shelf_id, blade_id) = self.db.volume_allocate_shelf_and_blade(context, volume['id']) - yield self._try_execute( + self._try_execute( "sudo vblade-persist setup %s %s %s /dev/%s/%s" % (shelf_id, blade_id, @@ -176,33 +165,30 @@ class AOEDriver(VolumeDriver): # still works for the other volumes, so we # just wait a bit for the current volume to # be ready and ignore any errors. - yield self._execute("sleep 2") - yield self._execute("sudo vblade-persist auto all", - check_exit_code=False) - yield self._execute("sudo vblade-persist start all", - check_exit_code=False) + self._execute("sleep 2") + self._execute("sudo vblade-persist auto all", + check_exit_code=False) + self._execute("sudo vblade-persist start all", + check_exit_code=False) - @defer.inlineCallbacks def remove_export(self, context, volume): """Removes an export for a logical volume.""" (shelf_id, blade_id) = self.db.volume_get_shelf_and_blade(context, volume['id']) - yield self._try_execute("sudo vblade-persist stop %s %s" % - (shelf_id, blade_id)) - yield self._try_execute("sudo vblade-persist destroy %s %s" % - (shelf_id, blade_id)) + self._try_execute("sudo vblade-persist stop %s %s" % + (shelf_id, blade_id)) + self._try_execute("sudo vblade-persist destroy %s %s" % + (shelf_id, blade_id)) - @defer.inlineCallbacks def discover_volume(self, _volume): """Discover volume on a remote host.""" - yield self._execute("sudo aoe-discover") - yield self._execute("sudo aoe-stat", check_exit_code=False) + self._execute("sudo aoe-discover") + self._execute("sudo aoe-stat", check_exit_code=False) - @defer.inlineCallbacks def undiscover_volume(self, _volume): """Undiscover volume on a remote host.""" - yield + pass class FakeAOEDriver(AOEDriver): @@ -252,7 +238,6 @@ class ISCSIDriver(VolumeDriver): target = {'host': host, 'target_num': target_num} self.db.iscsi_target_create_safe(context, target) - @defer.inlineCallbacks def create_export(self, context, volume): """Creates an export for a logical volume.""" self._ensure_iscsi_targets(context, volume['host']) @@ -261,61 +246,57 @@ class ISCSIDriver(VolumeDriver): volume['host']) iscsi_name = "%s%s" % (FLAGS.iscsi_target_prefix, volume['name']) volume_path = "/dev/%s/%s" % (FLAGS.volume_group, volume['name']) - yield self._execute("sudo ietadm --op new " - "--tid=%s --params Name=%s" % - (iscsi_target, iscsi_name)) - yield self._execute("sudo ietadm --op new --tid=%s " - "--lun=0 --params Path=%s,Type=fileio" % - (iscsi_target, volume_path)) + self._execute("sudo ietadm --op new " + "--tid=%s --params Name=%s" % + (iscsi_target, iscsi_name)) + self._execute("sudo ietadm --op new --tid=%s " + "--lun=0 --params Path=%s,Type=fileio" % + (iscsi_target, volume_path)) - @defer.inlineCallbacks def remove_export(self, context, volume): """Removes an export for a logical volume.""" iscsi_target = self.db.volume_get_iscsi_target_num(context, volume['id']) - yield self._execute("sudo ietadm --op delete --tid=%s " - "--lun=0" % iscsi_target) - yield self._execute("sudo ietadm --op delete --tid=%s" % - iscsi_target) + self._execute("sudo ietadm --op delete --tid=%s " + "--lun=0" % iscsi_target) + self._execute("sudo ietadm --op delete --tid=%s" % + iscsi_target) - @defer.inlineCallbacks def _get_name_and_portal(self, volume_name, host): """Gets iscsi name and portal from volume name and host.""" - (out, _err) = yield self._execute("sudo iscsiadm -m discovery -t " - "sendtargets -p %s" % host) + (out, _err) = self._execute("sudo iscsiadm -m discovery -t " + "sendtargets -p %s" % host) for target in out.splitlines(): if FLAGS.iscsi_ip_prefix in target and volume_name in target: (location, _sep, iscsi_name) = target.partition(" ") break iscsi_portal = location.split(",")[0] - defer.returnValue((iscsi_name, iscsi_portal)) + return (iscsi_name, iscsi_portal) - @defer.inlineCallbacks def discover_volume(self, volume): """Discover volume on a remote host.""" (iscsi_name, - iscsi_portal) = yield self._get_name_and_portal(volume['name'], - volume['host']) - yield self._execute("sudo iscsiadm -m node -T %s -p %s --login" % - (iscsi_name, iscsi_portal)) - yield self._execute("sudo iscsiadm -m node -T %s -p %s --op update " - "-n node.startup -v automatic" % - (iscsi_name, iscsi_portal)) - defer.returnValue("/dev/iscsi/%s" % volume['name']) + iscsi_portal) = self._get_name_and_portal(volume['name'], + volume['host']) + self._execute("sudo iscsiadm -m node -T %s -p %s --login" % + (iscsi_name, iscsi_portal)) + self._execute("sudo iscsiadm -m node -T %s -p %s --op update " + "-n node.startup -v automatic" % + (iscsi_name, iscsi_portal)) + return "/dev/iscsi/%s" % volume['name'] - @defer.inlineCallbacks def undiscover_volume(self, volume): """Undiscover volume on a remote host.""" (iscsi_name, - iscsi_portal) = yield self._get_name_and_portal(volume['name'], - volume['host']) - yield self._execute("sudo iscsiadm -m node -T %s -p %s --op update " - "-n node.startup -v manual" % - (iscsi_name, iscsi_portal)) - yield self._execute("sudo iscsiadm -m node -T %s -p %s --logout " % - (iscsi_name, iscsi_portal)) - yield self._execute("sudo iscsiadm -m node --op delete " - "--targetname %s" % iscsi_name) + iscsi_portal) = self._get_name_and_portal(volume['name'], + volume['host']) + self._execute("sudo iscsiadm -m node -T %s -p %s --op update " + "-n node.startup -v manual" % + (iscsi_name, iscsi_portal)) + self._execute("sudo iscsiadm -m node -T %s -p %s --logout " % + (iscsi_name, iscsi_portal)) + self._execute("sudo iscsiadm -m node --op delete " + "--targetname %s" % iscsi_name) class FakeISCSIDriver(ISCSIDriver): diff --git a/run_tests.py b/run_tests.py index 3d427d8af2..883d2b768c 100644 --- a/run_tests.py +++ b/run_tests.py @@ -39,11 +39,16 @@ Due to our use of multiprocessing it we frequently get some ignorable """ +import eventlet +eventlet.monkey_patch() + import __main__ import os import sys + from twisted.scripts import trial as trial_script +import unittest from nova import flags from nova import twistd @@ -56,12 +61,12 @@ from nova.tests.compute_unittest import * from nova.tests.flags_unittest import * from nova.tests.misc_unittest import * from nova.tests.network_unittest import * -from nova.tests.objectstore_unittest import * -from nova.tests.process_unittest import * +#from nova.tests.objectstore_unittest import * +#from nova.tests.process_unittest import * from nova.tests.quota_unittest import * from nova.tests.rpc_unittest import * from nova.tests.scheduler_unittest import * -from nova.tests.service_unittest import * +#from nova.tests.service_unittest import * from nova.tests.twistd_unittest import * from nova.tests.validator_unittest import * from nova.tests.virt_unittest import * @@ -82,6 +87,8 @@ if __name__ == '__main__': config = OptionsClass() argv = config.parseOptions() + argv = FLAGS(sys.argv) + FLAGS.verbose = True # TODO(termie): these should make a call instead of doing work on import @@ -90,6 +97,7 @@ if __name__ == '__main__': else: from nova.tests.real_flags import * + # Establish redirect for STDERR sys.stderr.flush() err = open(FLAGS.tests_stderr, 'w+', 0) From 653373842815a1ba9992d3d662431ba102ac8ce1 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 8 Dec 2010 17:21:43 -0800 Subject: [PATCH 094/229] port volume manager to eventlet also --- nova/volume/manager.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/nova/volume/manager.py b/nova/volume/manager.py index 589e7d7d97..7da125cac0 100644 --- a/nova/volume/manager.py +++ b/nova/volume/manager.py @@ -45,7 +45,6 @@ intact. import logging import datetime -from twisted.internet import defer from nova import context from nova import exception @@ -86,7 +85,6 @@ class VolumeManager(manager.Manager): for volume in volumes: self.driver.ensure_export(ctxt, volume) - @defer.inlineCallbacks def create_volume(self, context, volume_id): """Creates and exports the volume.""" context = context.elevated() @@ -102,19 +100,18 @@ class VolumeManager(manager.Manager): logging.debug("volume %s: creating lv of size %sG", volume_ref['name'], volume_ref['size']) - yield self.driver.create_volume(volume_ref) + self.driver.create_volume(volume_ref) logging.debug("volume %s: creating export", volume_ref['name']) - yield self.driver.create_export(context, volume_ref) + self.driver.create_export(context, volume_ref) now = datetime.datetime.utcnow() self.db.volume_update(context, volume_ref['id'], {'status': 'available', 'launched_at': now}) logging.debug("volume %s: created successfully", volume_ref['name']) - defer.returnValue(volume_id) + return volume_id - @defer.inlineCallbacks def delete_volume(self, context, volume_id): """Deletes and unexports volume.""" context = context.elevated() @@ -124,14 +121,13 @@ class VolumeManager(manager.Manager): if volume_ref['host'] != self.host: raise exception.Error("Volume is not local to this node") logging.debug("volume %s: removing export", volume_ref['name']) - yield self.driver.remove_export(context, volume_ref) + self.driver.remove_export(context, volume_ref) logging.debug("volume %s: deleting", volume_ref['name']) - yield self.driver.delete_volume(volume_ref) + self.driver.delete_volume(volume_ref) self.db.volume_destroy(context, volume_id) logging.debug("volume %s: deleted successfully", volume_ref['name']) - defer.returnValue(True) + return True - @defer.inlineCallbacks def setup_compute_volume(self, context, volume_id): """Setup remote volume on compute host. @@ -139,17 +135,16 @@ class VolumeManager(manager.Manager): context = context.elevated() volume_ref = self.db.volume_get(context, volume_id) if volume_ref['host'] == self.host and FLAGS.use_local_volumes: - path = yield self.driver.local_path(volume_ref) + path = self.driver.local_path(volume_ref) else: - path = yield self.driver.discover_volume(volume_ref) - defer.returnValue(path) + path = self.driver.discover_volume(volume_ref) + return path - @defer.inlineCallbacks def remove_compute_volume(self, context, volume_id): """Remove remote volume on compute host.""" context = context.elevated() volume_ref = self.db.volume_get(context, volume_id) if volume_ref['host'] == self.host and FLAGS.use_local_volumes: - defer.returnValue(True) + return True else: - yield self.driver.undiscover_volume(volume_ref) + self.driver.undiscover_volume(volume_ref) From 15f7361f5497c3d27dcafbb27d314af76069ed42 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 8 Dec 2010 17:25:57 -0800 Subject: [PATCH 095/229] remove some unused files --- nova/process.py | 209 ------------------------------- nova/tests/process_unittest.py | 132 ------------------- nova/tests/validator_unittest.py | 42 ------- nova/validate.py | 94 -------------- 4 files changed, 477 deletions(-) delete mode 100644 nova/process.py delete mode 100644 nova/tests/process_unittest.py delete mode 100644 nova/tests/validator_unittest.py delete mode 100644 nova/validate.py diff --git a/nova/process.py b/nova/process.py deleted file mode 100644 index b33df048bf..0000000000 --- a/nova/process.py +++ /dev/null @@ -1,209 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# Copyright 2010 FathomDB Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Process pool using twisted threading -""" - -import logging -import StringIO - -from twisted.internet import defer -from twisted.internet import error -from twisted.internet import protocol -from twisted.internet import reactor - -from nova import flags -from nova.exception import ProcessExecutionError - -FLAGS = flags.FLAGS -flags.DEFINE_integer('process_pool_size', 4, - 'Number of processes to use in the process pool') - - -# This is based on _BackRelay from twister.internal.utils, but modified to -# capture both stdout and stderr, without odd stderr handling, and also to -# handle stdin -class BackRelayWithInput(protocol.ProcessProtocol): - """ - Trivial protocol for communicating with a process and turning its output - into the result of a L{Deferred}. - - @ivar deferred: A L{Deferred} which will be called back with all of stdout - and all of stderr as well (as a tuple). C{terminate_on_stderr} is true - and any bytes are received over stderr, this will fire with an - L{_ProcessExecutionError} instance and the attribute will be set to - C{None}. - - @ivar onProcessEnded: If C{terminate_on_stderr} is false and bytes are - received over stderr, this attribute will refer to a L{Deferred} which - will be called back when the process ends. This C{Deferred} is also - associated with the L{_ProcessExecutionError} which C{deferred} fires - with earlier in this case so that users can determine when the process - has actually ended, in addition to knowing when bytes have been - received via stderr. - """ - - def __init__(self, deferred, cmd, started_deferred=None, - terminate_on_stderr=False, check_exit_code=True, - process_input=None): - self.deferred = deferred - self.cmd = cmd - self.stdout = StringIO.StringIO() - self.stderr = StringIO.StringIO() - self.started_deferred = started_deferred - self.terminate_on_stderr = terminate_on_stderr - self.check_exit_code = check_exit_code - self.process_input = process_input - self.on_process_ended = None - - def _build_execution_error(self, exit_code=None): - return ProcessExecutionError(cmd=self.cmd, - exit_code=exit_code, - stdout=self.stdout.getvalue(), - stderr=self.stderr.getvalue()) - - def errReceived(self, text): - self.stderr.write(text) - if self.terminate_on_stderr and (self.deferred is not None): - self.on_process_ended = defer.Deferred() - self.deferred.errback(self._build_execution_error()) - self.deferred = None - self.transport.loseConnection() - - def outReceived(self, text): - self.stdout.write(text) - - def processEnded(self, reason): - if self.deferred is not None: - stdout, stderr = self.stdout.getvalue(), self.stderr.getvalue() - exit_code = reason.value.exitCode - if self.check_exit_code and exit_code != 0: - self.deferred.errback(self._build_execution_error(exit_code)) - else: - try: - if self.check_exit_code: - reason.trap(error.ProcessDone) - self.deferred.callback((stdout, stderr)) - except: - # NOTE(justinsb): This logic is a little suspicious to me. - # If the callback throws an exception, then errback will - # be called also. However, this is what the unit tests - # test for. - exec_error = self._build_execution_error(exit_code) - self.deferred.errback(exec_error) - elif self.on_process_ended is not None: - self.on_process_ended.errback(reason) - - def connectionMade(self): - if self.started_deferred: - self.started_deferred.callback(self) - if self.process_input: - self.transport.write(str(self.process_input)) - self.transport.closeStdin() - - -def get_process_output(executable, args=None, env=None, path=None, - process_reactor=None, check_exit_code=True, - process_input=None, started_deferred=None, - terminate_on_stderr=False): - if process_reactor is None: - process_reactor = reactor - args = args and args or () - env = env and env and {} - deferred = defer.Deferred() - cmd = executable - if args: - cmd = " ".join([cmd] + args) - logging.debug("Running cmd: %s", cmd) - process_handler = BackRelayWithInput( - deferred, - cmd, - started_deferred=started_deferred, - check_exit_code=check_exit_code, - process_input=process_input, - terminate_on_stderr=terminate_on_stderr) - # NOTE(vish): commands come in as unicode, but self.executes needs - # strings or process.spawn raises a deprecation warning - executable = str(executable) - if not args is None: - args = [str(x) for x in args] - process_reactor.spawnProcess(process_handler, executable, - (executable,) + tuple(args), env, path) - return deferred - - -class ProcessPool(object): - """ A simple process pool implementation using Twisted's Process bits. - - This is pretty basic right now, but hopefully the API will be the correct - one so that it can be optimized later. - """ - def __init__(self, size=None): - self.size = size and size or FLAGS.process_pool_size - self._pool = defer.DeferredSemaphore(self.size) - - def simple_execute(self, cmd, **kw): - """ Weak emulation of the old utils.execute() function. - - This only exists as a way to quickly move old execute methods to - this new style of code. - - NOTE(termie): This will break on args with spaces in them. - """ - parsed = cmd.split(' ') - executable, args = parsed[0], parsed[1:] - return self.execute(executable, args, **kw) - - def execute(self, *args, **kw): - deferred = self._pool.acquire() - - def _associate_process(proto): - deferred.process = proto.transport - return proto.transport - - started = defer.Deferred() - started.addCallback(_associate_process) - kw.setdefault('started_deferred', started) - - deferred.process = None - deferred.started = started - - deferred.addCallback(lambda _: get_process_output(*args, **kw)) - deferred.addBoth(self._release) - return deferred - - def _release(self, retval=None): - self._pool.release() - return retval - - -class SharedPool(object): - _instance = None - - def __init__(self): - if SharedPool._instance is None: - self.__class__._instance = ProcessPool() - - def __getattr__(self, key): - return getattr(self._instance, key) - - -def simple_execute(cmd, **kwargs): - return SharedPool().simple_execute(cmd, **kwargs) diff --git a/nova/tests/process_unittest.py b/nova/tests/process_unittest.py deleted file mode 100644 index 67245af039..0000000000 --- a/nova/tests/process_unittest.py +++ /dev/null @@ -1,132 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import logging -from twisted.internet import defer -from twisted.internet import reactor -from xml.etree import ElementTree - -from nova import exception -from nova import flags -from nova import process -from nova import test -from nova import utils - -FLAGS = flags.FLAGS - - -class ProcessTestCase(test.TrialTestCase): - def setUp(self): - logging.getLogger().setLevel(logging.DEBUG) - super(ProcessTestCase, self).setUp() - - def test_execute_stdout(self): - pool = process.ProcessPool(2) - d = pool.simple_execute('echo test') - - def _check(rv): - self.assertEqual(rv[0], 'test\n') - self.assertEqual(rv[1], '') - - d.addCallback(_check) - d.addErrback(self.fail) - return d - - def test_execute_stderr(self): - pool = process.ProcessPool(2) - d = pool.simple_execute('cat BAD_FILE', check_exit_code=False) - - def _check(rv): - self.assertEqual(rv[0], '') - self.assert_('No such file' in rv[1]) - - d.addCallback(_check) - d.addErrback(self.fail) - return d - - def test_execute_unexpected_stderr(self): - pool = process.ProcessPool(2) - d = pool.simple_execute('cat BAD_FILE') - d.addCallback(lambda x: self.fail('should have raised an error')) - d.addErrback(lambda failure: failure.trap(IOError)) - return d - - def test_max_processes(self): - pool = process.ProcessPool(2) - d1 = pool.simple_execute('sleep 0.01') - d2 = pool.simple_execute('sleep 0.01') - d3 = pool.simple_execute('sleep 0.005') - d4 = pool.simple_execute('sleep 0.005') - - called = [] - - def _called(rv, name): - called.append(name) - - d1.addCallback(_called, 'd1') - d2.addCallback(_called, 'd2') - d3.addCallback(_called, 'd3') - d4.addCallback(_called, 'd4') - - # Make sure that d3 and d4 had to wait on the other two and were called - # in order - # NOTE(termie): there may be a race condition in this test if for some - # reason one of the sleeps takes longer to complete - # than it should - d4.addCallback(lambda x: self.assertEqual(called[2], 'd3')) - d4.addCallback(lambda x: self.assertEqual(called[3], 'd4')) - d4.addErrback(self.fail) - return d4 - - def test_kill_long_process(self): - pool = process.ProcessPool(2) - - d1 = pool.simple_execute('sleep 1') - d2 = pool.simple_execute('sleep 0.005') - - timeout = reactor.callLater(0.1, self.fail, 'should have been killed') - - # kill d1 and wait on it to end then cancel the timeout - d2.addCallback(lambda _: d1.process.signalProcess('KILL')) - d2.addCallback(lambda _: d1) - d2.addBoth(lambda _: timeout.active() and timeout.cancel()) - d2.addErrback(self.fail) - return d2 - - def test_process_exit_is_contained(self): - pool = process.ProcessPool(2) - - d1 = pool.simple_execute('sleep 1') - d1.addCallback(lambda x: self.fail('should have errbacked')) - d1.addErrback(lambda fail: fail.trap(IOError)) - reactor.callLater(0.05, d1.process.signalProcess, 'KILL') - - return d1 - - def test_shared_pool_is_singleton(self): - pool1 = process.SharedPool() - pool2 = process.SharedPool() - self.assertEqual(id(pool1._instance), id(pool2._instance)) - - def test_shared_pool_works_as_singleton(self): - d1 = process.simple_execute('sleep 1') - d2 = process.simple_execute('sleep 0.005') - # lp609749: would have failed with - # exceptions.AssertionError: Someone released me too many times: - # too many tokens! - return d1 diff --git a/nova/tests/validator_unittest.py b/nova/tests/validator_unittest.py deleted file mode 100644 index b5f1c0667f..0000000000 --- a/nova/tests/validator_unittest.py +++ /dev/null @@ -1,42 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import logging -import unittest - -from nova import flags -from nova import test -from nova import validate - - -class ValidationTestCase(test.TrialTestCase): - def setUp(self): - super(ValidationTestCase, self).setUp() - - def tearDown(self): - super(ValidationTestCase, self).tearDown() - - def test_type_validation(self): - self.assertTrue(type_case("foo", 5, 1)) - self.assertRaises(TypeError, type_case, "bar", "5", 1) - self.assertRaises(TypeError, type_case, None, 5, 1) - - -@validate.typetest(instanceid=str, size=int, number_of_instances=int) -def type_case(instanceid, size, number_of_instances): - return True diff --git a/nova/validate.py b/nova/validate.py deleted file mode 100644 index 7ea27daa6e..0000000000 --- a/nova/validate.py +++ /dev/null @@ -1,94 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Decorators for argument validation, courtesy of -http://rmi.net/~lutz/rangetest.html""" - - -def rangetest(**argchecks): - """Validate ranges for both + defaults""" - - def onDecorator(func): - """onCall remembers func and argchecks""" - import sys - code = func.__code__ if sys.version_info[0] == 3 else func.func_code - allargs = code.co_varnames[:code.co_argcount] - funcname = func.__name__ - - def onCall(*pargs, **kargs): - # all pargs match first N args by position - # the rest must be in kargs or omitted defaults - positionals = list(allargs) - positionals = positionals[:len(pargs)] - - for (argname, (low, high)) in argchecks.items(): - # for all args to be checked - if argname in kargs: - # was passed by name - if float(kargs[argname]) < low or \ - float(kargs[argname]) > high: - errmsg = '{0} argument "{1}" not in {2}..{3}' - errmsg = errmsg.format(funcname, argname, low, high) - raise TypeError(errmsg) - - elif argname in positionals: - # was passed by position - position = positionals.index(argname) - if float(pargs[position]) < low or \ - float(pargs[position]) > high: - errmsg = '{0} argument "{1}" with value of {4} ' \ - 'not in {2}..{3}' - errmsg = errmsg.format(funcname, argname, low, high, - pargs[position]) - raise TypeError(errmsg) - else: - pass - - return func(*pargs, **kargs) # okay: run original call - return onCall - return onDecorator - - -def typetest(**argchecks): - def onDecorator(func): - import sys - code = func.__code__ if sys.version_info[0] == 3 else func.func_code - allargs = code.co_varnames[:code.co_argcount] - funcname = func.__name__ - - def onCall(*pargs, **kargs): - positionals = list(allargs)[:len(pargs)] - for (argname, typeof) in argchecks.items(): - if argname in kargs: - if not isinstance(kargs[argname], typeof): - errmsg = '{0} argument "{1}" not of type {2}' - errmsg = errmsg.format(funcname, argname, typeof) - raise TypeError(errmsg) - elif argname in positionals: - position = positionals.index(argname) - if not isinstance(pargs[position], typeof): - errmsg = '{0} argument "{1}" with value of {2} ' \ - 'not of type {3}' - errmsg = errmsg.format(funcname, argname, - pargs[position], typeof) - raise TypeError(errmsg) - else: - pass - return func(*pargs, **kargs) - return onCall - return onDecorator From 783f4fa44b835ef6c399e18679774a2e4bc4124a Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Thu, 9 Dec 2010 10:40:07 +0000 Subject: [PATCH 096/229] 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 2a75f9dbf9..2b84601f25 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 542d4894cd..e7c3102a37 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 """ From b35b86c6f415a205b6ce49164cccb2a870c46fcb Mon Sep 17 00:00:00 2001 From: Chmouel Boudjnah Date: Thu, 9 Dec 2010 13:43:54 +0000 Subject: [PATCH 097/229] 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 a2679e0fc2..0e9600c00f 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 From 5f72a004dee0cb8de3f2daee1976fa978f6e51f3 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Thu, 9 Dec 2010 16:41:35 +0000 Subject: [PATCH 098/229] pause from compute.manager <-> xenapi --- nova/compute/manager.py | 32 ++++++++++++++++++++++++++++++++ nova/virt/xenapi/vmops.py | 20 ++++++++++++++++++++ nova/virt/xenapi_conn.py | 8 ++++++++ 3 files changed, 60 insertions(+) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index dd8d41129c..c0339a71f7 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -194,6 +194,38 @@ class ComputeManager(manager.Manager): yield self.driver.unrescue(instance_ref) self._update_state(context, instance_id) + @defer.inlineCallbacks + @exception.wrap_exception + def pause_instance(self, context, instance_id): + """Pause an instance on this server.""" + context = context.elevated() + instance_ref = self.db.instance_get(context, instance_id) + + logging.debug('instance %s: pausing', + instance_ref['internal_id']) + self.db.instance_set_state(context, + instance_id, + power_state.NOSTATE, + 'pausing') + yield self.driver.pause(instance_ref) + self._update_state(context, instance_id) + + @defer.inlineCallbacks + @exception.wrap_exception + def resume_instance(self, context, instance_id): + """Resume a paused instance on this server.""" + context = context.elevated() + instance_ref = self.db.instance_get(context, instance_id) + + logging.debug('instance %s: resuming', + instance_ref['internal_id']) + self.db.instance_set_state(context, + instance_id, + power_state.NOSTATE, + 'resume') + yield self.driver.resume(instance_ref) + self._update_state(context, instance_id) + @exception.wrap_exception def get_console_output(self, context, instance_id): """Send the console output for an instance.""" diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index d36cdaea5b..353e83873b 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -120,6 +120,26 @@ class VMOps(object): except XenAPI.Failure, exc: logging.warn(exc) + @defer.inlineCallbacks + def pause(self, instance): + """ Pause VM instance """ + instance_name = instance.name + vm = yield VMHelper.lookup(self._session, instance_name) + if vm is None: + raise Exception('instance not present %s' % instance_name) + task = yield self._session.call_xenapi('Async.VM.pause', vm) + yield self._session.wait_for_task(task) + + @defer.inlineCallbacks + def unpause(self, instance): + """ Unpause VM instance """ + instance_name = instance.name + vm = yield VMHelper.lookup(self._session, instance_name) + if vm is None: + raise Exception('instance not present %s' % instance_name) + task = yield self._session.call_xenapi('Async.VM.unpause', vm) + yield self._session.wait_for_task(task) + def get_info(self, instance_id): """ Return data about VM instance """ vm = VMHelper.lookup_blocking(self._session, instance_id) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 26b30bf927..df405e75fc 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -122,6 +122,14 @@ class XenAPIConnection(object): """ Destroy VM instance """ self._vmops.destroy(instance) + def pause(self, instance): + """ Pause VM instance """ + self._vmops.pause(instance) + + def unpause(self, instance): + """ Unpause paused VM instance """ + self._vmops.unpause(instance) + def get_info(self, instance_id): """ Return data about VM instance """ return self._vmops.get_info(instance_id) From 043d3ac3643e7183d4afe8c628ce90d62a468427 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 9 Dec 2010 11:08:24 -0600 Subject: [PATCH 099/229] 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 e7c3102a37..9bfd072671 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 """ From 50ac7dc67686742c3e57cc3a408ca9e8c988b89b Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 9 Dec 2010 17:30:03 +0000 Subject: [PATCH 100/229] 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 05f8c3d0b0..ebb13aedc5 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 9886a2449b..770c942198 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 From 470dcfdf793f9a8b34c320731dcfc8d403a61bd2 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 9 Dec 2010 11:32:23 -0600 Subject: [PATCH 101/229] 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 2b84601f25..ff062fba01 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: From 294102984964e8853fcaf32485c273cbdcc7748a Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Thu, 9 Dec 2010 17:54:30 +0000 Subject: [PATCH 102/229] changed resume to unpause --- nova/compute/manager.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c0339a71f7..61ed3136b5 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -212,18 +212,18 @@ class ComputeManager(manager.Manager): @defer.inlineCallbacks @exception.wrap_exception - def resume_instance(self, context, instance_id): - """Resume a paused instance on this server.""" + def unpause_instance(self, context, instance_id): + """Unpause a paused instance on this server.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - logging.debug('instance %s: resuming', + logging.debug('instance %s: unpausing', instance_ref['internal_id']) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, - 'resume') - yield self.driver.resume(instance_ref) + 'unpausing') + yield self.driver.unpause(instance_ref) self._update_state(context, instance_id) @exception.wrap_exception From 1c323efd0777587b44b275827187b7c5cd6afdc5 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Thu, 9 Dec 2010 09:57:15 -0800 Subject: [PATCH 103/229] 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 c9efe52226..b9ecbd9b8d 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 2050359154..fcda97ab19 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 cdbdc9bdd8..4a0a8e6f1c 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 6f2f6fed99..7704f48f18 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 c3f129a32d..21b8aac1c0 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 14e720be41..7b427c2dbb 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) From a00c8015e4ffe417f6c111a5eaf0578d9ef79b7d Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 9 Dec 2010 13:37:30 -0600 Subject: [PATCH 104/229] 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 ff062fba01..77edb576e8 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 From 4c9bf2f01fb712a3af6a9876a175a7a0638bcd59 Mon Sep 17 00:00:00 2001 From: Chmouel Boudjnah Date: Thu, 9 Dec 2010 20:18:06 +0000 Subject: [PATCH 105/229] 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 0e9600c00f..6e47170bde 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 From 90c89f5f7b24bb6c95d405d42f7f15292b5452a9 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 9 Dec 2010 17:03:49 -0400 Subject: [PATCH 106/229] pause and unpause code/tests in place. To the point it stuffs request in the queue. --- nova/api/openstack/__init__.py | 13 ++++++---- nova/api/openstack/servers.py | 33 ++++++++++++++++++++++++ nova/compute/api.py | 21 +++++++++++++++ nova/tests/api/openstack/test_servers.py | 31 +++++++++++++++++++++- 4 files changed, 92 insertions(+), 6 deletions(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index c9efe52226..24042b42b0 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -171,9 +171,16 @@ class APIRouter(wsgi.Router): def __init__(self): mapper = routes.Mapper() + + server_members = {'action': 'POST'} + if FLAGS.allow_admin_api: + logging.debug("Including admin operations in API.") + server_members['pause'] = 'POST' + server_members['unpause'] = 'POST' + mapper.resource("server", "servers", controller=servers.Controller(), collection={'detail': 'GET'}, - member={'action': 'POST'}) + member=server_members) mapper.resource("backup_schedule", "backup_schedules", controller=backup_schedules.Controller(), @@ -187,10 +194,6 @@ class APIRouter(wsgi.Router): 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/api/openstack/servers.py b/nova/api/openstack/servers.py index 6f2f6fed99..ade0d7eb9b 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -15,6 +15,9 @@ # License for the specific language governing permissions and limitations # under the License. +import logging +import traceback + from webob import exc from nova import context @@ -28,6 +31,10 @@ from nova.compute import power_state import nova.api.openstack +LOG = logging.getLogger('server') +LOG.setLevel(logging.DEBUG) + + def _entity_list(entities): """ Coerces a list of servers into proper dictionary format """ return dict(servers=entities) @@ -173,3 +180,29 @@ class Controller(wsgi.Controller): except: return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + + def pause(self, req, id): + """ Permit Admins to Pause the server. """ + user_id = req.environ['nova.context']['user']['id'] + ctxt = context.RequestContext(user_id, user_id) + try: + self.compute_api.pause(ctxt, id) + except: + readable = traceback.format_exc() + logging.error("Compute.api::pause %s", readable) + return faults.Fault(exc.HTTPUnprocessableEntity()) + return exc.HTTPAccepted() + + def unpause(self, req, id): + """ Permit Admins to Unpause the server. """ + user_id = req.environ['nova.context']['user']['id'] + ctxt = context.RequestContext(user_id, user_id) + try: + self.compute_api.unpause(ctxt, id) + except: + readable = traceback.format_exc() + logging.error("Compute.api::unpause %s", readable) + return faults.Fault(exc.HTTPUnprocessableEntity()) + return exc.HTTPAccepted() + + diff --git a/nova/compute/api.py b/nova/compute/api.py index 8e0efa4cc7..e25e6cc789 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -275,6 +275,27 @@ class ComputeAPI(base.Base): {"method": "reboot_instance", "args": {"instance_id": instance['id']}}) + def pause(self, context, instance_id): + """Pause the given instance.""" + logging.debug("IN PAUSE - 1") + instance = self.db.instance_get_by_internal_id(context, instance_id) + logging.debug("IN PAUSE - 2") + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "pause_instance", + "args": {"instance_id": instance['id']}}) + + def unpause(self, context, instance_id): + """Unpause 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": "unpause_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) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 8444b6fce6..636da30fd7 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -56,9 +56,11 @@ def instance_address(context, instance_id): def stub_instance(id, user_id=1): - return Instance(id=id + 123456, state=0, image_id=10, user_id=user_id, + return Instance(id=int(id) + 123456, state=0, image_id=10, user_id=user_id, display_name='server%s' % id, internal_id=id) +def fake_compute_api(cls, req, id): + return True class ServersTest(unittest.TestCase): def setUp(self): @@ -82,9 +84,12 @@ class ServersTest(unittest.TestCase): instance_address) self.stubs.Set(nova.db.api, 'instance_get_floating_address', instance_address) + self.stubs.Set(nova.compute.api.ComputeAPI, 'pause', fake_compute_api) + self.allow_admin = FLAGS.allow_admin_api def tearDown(self): self.stubs.UnsetAll() + FLAGS.allow_admin_api = self.allow_admin def test_get_server_by_id(self): req = webob.Request.blank('/v1.0/servers/1') @@ -211,6 +216,30 @@ class ServersTest(unittest.TestCase): self.assertEqual(s['imageId'], 10) i += 1 + def test_server_pause(self): + FLAGS.allow_admin_api = True + body = dict(server=dict( + name='server_test', imageId=2, flavorId=2, metadata={}, + personality={})) + req = webob.Request.blank('/v1.0/servers/1/pause') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(nova.api.API('os')) + self.assertEqual(res.status_int, 202) + + def test_server_unpause(self): + FLAGS.allow_admin_api = True + body = dict(server=dict( + name='server_test', imageId=2, flavorId=2, metadata={}, + personality={})) + req = webob.Request.blank('/v1.0/servers/1/unpause') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(nova.api.API('os')) + self.assertEqual(res.status_int, 202) + def test_server_reboot(self): body = dict(server=dict( name='server_test', imageId=2, flavorId=2, metadata={}, From 54e4174a0b6a3c1dd4105617b06bb7a69f45202c Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 9 Dec 2010 17:32:27 -0400 Subject: [PATCH 107/229] remove debug messages --- nova/compute/api.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index e25e6cc789..79da79cd16 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -277,9 +277,7 @@ class ComputeAPI(base.Base): def pause(self, context, instance_id): """Pause the given instance.""" - logging.debug("IN PAUSE - 1") instance = self.db.instance_get_by_internal_id(context, instance_id) - logging.debug("IN PAUSE - 2") host = instance['host'] rpc.cast(context, self.db.queue_get_for(context, FLAGS.compute_topic, host), From 00f329d479564ad8349ed32a27990da2ed3a396e Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 9 Dec 2010 14:36:23 -0800 Subject: [PATCH 108/229] make nova binaries use eventlet --- bin/nova-api | 10 +++++----- bin/nova-combined | 14 +++++++++++--- bin/nova-compute | 1 + bin/nova-network | 1 + bin/nova-scheduler | 15 ++++++--------- bin/nova-volume | 15 ++++++--------- nova/service_eventlet.py | 4 +++- 7 files changed, 33 insertions(+), 27 deletions(-) diff --git a/bin/nova-api b/bin/nova-api index a9c53dbcdf..3215ad5efa 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -32,9 +32,11 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): sys.path.insert(0, possible_topdir) +from nova import api from nova import flags from nova import utils from nova import server +from nova import wsgi FLAGS = flags.FLAGS flags.DEFINE_integer('osapi_port', 8774, 'OpenStack API port') @@ -43,9 +45,8 @@ flags.DEFINE_integer('ec2api_port', 8773, 'EC2 API port') flags.DEFINE_string('ec2api_host', '0.0.0.0', 'EC2 API host') -def main(_args): - from nova import api - from nova import wsgi +def main(): + FLAGS(sys.argv) server = wsgi.Server() server.start(api.API('os'), FLAGS.osapi_port, host=FLAGS.osapi_host) server.start(api.API('ec2'), FLAGS.ec2api_port, host=FLAGS.ec2api_host) @@ -53,5 +54,4 @@ def main(_args): if __name__ == '__main__': - utils.default_flagfile() - server.serve('nova-api', main) + main() diff --git a/bin/nova-combined b/bin/nova-combined index 65865acd98..c865843289 100755 --- a/bin/nova-combined +++ b/bin/nova-combined @@ -27,8 +27,6 @@ eventlet.monkey_patch() import os import sys -from eventlet import greenthread - # If ../nova/__init__.py exists, add ../ to Python search path, so that # it will override what happens to be installed in /usr/(local/)lib/python... possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), @@ -46,6 +44,12 @@ from nova import wsgi FLAGS = flags.FLAGS flags.DEFINE_integer('api_port', 8773, 'API port') +FLAGS = flags.FLAGS +flags.DEFINE_integer('osapi_port', 8774, 'OpenStack API port') +flags.DEFINE_string('osapi_host', '0.0.0.0', 'OpenStack API host') +flags.DEFINE_integer('ec2api_port', 8773, 'EC2 API port') +flags.DEFINE_string('ec2api_host', '0.0.0.0', 'EC2 API host') + if __name__ == '__main__': FLAGS(sys.argv) @@ -57,5 +61,9 @@ if __name__ == '__main__': #objectstore = service_eventlet.Service.create(binary='nova-objectstore') service_eventlet.serve(compute, network, volume, scheduler) - wsgi.run_server(api.API(), FLAGS.api_port) + + server = wsgi.Server() + server.start(api.API('os'), FLAGS.osapi_port, host=FLAGS.osapi_host) + server.start(api.API('ec2'), FLAGS.ec2api_port, host=FLAGS.ec2api_host) + server.wait() diff --git a/bin/nova-compute b/bin/nova-compute index 600fbb8973..307f7cb591 100755 --- a/bin/nova-compute +++ b/bin/nova-compute @@ -39,3 +39,4 @@ from nova import service_eventlet if __name__ == '__main__': service_eventlet.serve() + service_eventlet.wait() diff --git a/bin/nova-network b/bin/nova-network index 600fbb8973..307f7cb591 100755 --- a/bin/nova-network +++ b/bin/nova-network @@ -39,3 +39,4 @@ from nova import service_eventlet if __name__ == '__main__': service_eventlet.serve() + service_eventlet.wait() diff --git a/bin/nova-scheduler b/bin/nova-scheduler index 4d1a40cf10..e4aa0dcaeb 100755 --- a/bin/nova-scheduler +++ b/bin/nova-scheduler @@ -21,6 +21,9 @@ Twistd daemon for the nova scheduler nodes. """ +import eventlet +eventlet.monkey_patch() + import os import sys @@ -32,14 +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) -from nova import service -from nova import twistd -from nova import utils - +from nova import service_eventlet if __name__ == '__main__': - utils.default_flagfile() - twistd.serve(__file__) - -if __name__ == '__builtin__': - application = service.Service.create() + service_eventlet.serve() + service_eventlet.wait() diff --git a/bin/nova-volume b/bin/nova-volume index e7281d6c0b..395d160418 100755 --- a/bin/nova-volume +++ b/bin/nova-volume @@ -21,6 +21,9 @@ Twistd daemon for the nova volume nodes. """ +import eventlet +eventlet.monkey_patch() + import os import sys @@ -32,14 +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) -from nova import service -from nova import twistd -from nova import utils - +from nova import service_eventlet if __name__ == '__main__': - utils.default_flagfile() - twistd.serve(__file__) - -if __name__ == '__builtin__': - application = service.Service.create() # pylint: disable-msg=C0103 + service_eventlet.serve() + service_eventlet.wait() diff --git a/nova/service_eventlet.py b/nova/service_eventlet.py index eac45a9810..82291ffe13 100644 --- a/nova/service_eventlet.py +++ b/nova/service_eventlet.py @@ -285,4 +285,6 @@ def serve(*services): #while True: # greenthread.sleep(5) - +def wait(): + while True: + greenthread.sleep(5) From 2cad5375dc3784397ac8e6a0aa170a161e97ea7e Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 9 Dec 2010 14:37:43 -0800 Subject: [PATCH 109/229] whitespace fix --- nova/service_eventlet.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/service_eventlet.py b/nova/service_eventlet.py index 82291ffe13..83f0692ce1 100644 --- a/nova/service_eventlet.py +++ b/nova/service_eventlet.py @@ -285,6 +285,7 @@ def serve(*services): #while True: # greenthread.sleep(5) + def wait(): while True: greenthread.sleep(5) From 3b376b8ad167e91119e21180bbff41eceef22e26 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 9 Dec 2010 15:19:56 -0800 Subject: [PATCH 110/229] get service unittests runnning again --- nova/service_eventlet.py | 20 ++++-- nova/test.py | 118 ++++++++++++++++++++++++------- nova/tests/scheduler_unittest.py | 22 +++--- nova/tests/service_unittest.py | 44 ++++++------ nova/utils.py | 7 +- run_tests.py | 4 +- 6 files changed, 149 insertions(+), 66 deletions(-) diff --git a/nova/service_eventlet.py b/nova/service_eventlet.py index 83f0692ce1..576cf8b85a 100644 --- a/nova/service_eventlet.py +++ b/nova/service_eventlet.py @@ -68,6 +68,7 @@ class Service(object): self.periodic_interval = periodic_interval super(Service, self).__init__(*args, **kwargs) self.saved_args, self.saved_kwargs = args, kwargs + self.timers = [] def start(self): manager_class = utils.import_class(self.manager_class_name) @@ -96,15 +97,17 @@ class Service(object): topic='%s.%s' % (self.topic, self.host), proxy=self) - consumer_all.attach_to_eventlet() - consumer_node.attach_to_eventlet() + self.timers.append(consumer_all.attach_to_eventlet()) + self.timers.append(consumer_node.attach_to_eventlet()) pulse = utils.LoopingCall(self.report_state) pulse.start(interval=self.report_interval, now=False) + self.timers.append(pulse) if self.periodic_interval: - pulse = utils.LoopingCall(self.periodic_tasks) - pulse.start(interval=self.periodic_interval, now=False) + periodic = utils.LoopingCall(self.periodic_tasks) + periodic.start(interval=self.periodic_interval, now=False) + self.timers.append(periodic) def _create_service_ref(self, context): service_ref = db.service_create(context, @@ -156,11 +159,20 @@ class Service(object): def kill(self): """Destroy the service object in the datastore""" + self.stop() try: db.service_destroy(context.get_admin_context(), self.service_id) except exception.NotFound: logging.warn("Service killed that has no database entry") + def stop(self): + for x in self.timers: + try: + x.stop() + except Exception: + pass + self.timers = [] + def periodic_tasks(self): """Tasks to be run at a periodic interval""" self.manager.periodic_tasks(context.get_admin_context()) diff --git a/nova/test.py b/nova/test.py index bbf063aca3..e27d291662 100644 --- a/nova/test.py +++ b/nova/test.py @@ -30,6 +30,7 @@ import unittest import mox import stubout from twisted.internet import defer +from twisted.trial import unittest as trial_unittest from nova import context from nova import db @@ -54,7 +55,6 @@ def skip_if_fake(func): return func(*args, **kw) return _skipper - class TrialTestCase(unittest.TestCase): """Test case base class for all unit tests""" def setUp(self): @@ -125,29 +125,6 @@ class TrialTestCase(unittest.TestCase): for k, v in self._original_flags.iteritems(): setattr(FLAGS, k, v) - #def run(self, result=None): - # test_method = getattr(self, self._testMethodName) - # setattr(self, - # self._testMethodName, - # self._maybeInlineCallbacks(test_method, result)) - # rv = super(TrialTestCase, self).run(result) - # setattr(self, self._testMethodName, test_method) - # return rv - - #def _maybeInlineCallbacks(self, func, result): - # def _wrapped(): - # g = func() - # if isinstance(g, defer.Deferred): - # return g - # if not hasattr(g, 'send'): - # return defer.succeed(g) - - # inlined = defer.inlineCallbacks(func) - # d = inlined() - # return d - # _wrapped.func_name = func.func_name - # return _wrapped - def _monkey_patch_attach(self): self.originalAttach = rpc.Consumer.attach_to_eventlet @@ -158,3 +135,96 @@ class TrialTestCase(unittest.TestCase): _wrapped.func_name = self.originalAttach.func_name rpc.Consumer.attach_to_eventlet = _wrapped + + +class OLDTrialTestCase(trial_unittest.TestCase): + """Test case base class for all unit tests""" + def setUp(self): + """Run before each test method to initialize test environment""" + super(TrialTestCase, self).setUp() + # NOTE(vish): We need a better method for creating fixtures for tests + # now that we have some required db setup for the system + # to work properly. + self.start = datetime.datetime.utcnow() + ctxt = context.get_admin_context() + if db.network_count(ctxt) != 5: + network_manager.VlanManager().create_networks(ctxt, + FLAGS.fixed_range, + 5, 16, + FLAGS.vlan_start, + FLAGS.vpn_start) + + # emulate some of the mox stuff, we can't use the metaclass + # because it screws with our generators + self.mox = mox.Mox() + self.stubs = stubout.StubOutForTesting() + self.flag_overrides = {} + self.injected = [] + self._original_flags = FLAGS.FlagValuesDict() + + def tearDown(self): + """Runs after each test method to finalize/tear down test + environment.""" + try: + self.mox.UnsetStubs() + self.stubs.UnsetAll() + self.stubs.SmartUnsetAll() + self.mox.VerifyAll() + # NOTE(vish): Clean up any ips associated during the test. + ctxt = context.get_admin_context() + db.fixed_ip_disassociate_all_by_timeout(ctxt, FLAGS.host, + self.start) + db.network_disassociate_all(ctxt) + rpc.Consumer.attach_to_eventlet = self.originalAttach + for x in self.injected: + try: + x.stop() + except AssertionError: + pass + + if FLAGS.fake_rabbit: + fakerabbit.reset_all() + + db.security_group_destroy_all(ctxt) + super(TrialTestCase, self).tearDown() + finally: + self.reset_flags() + + def flags(self, **kw): + """Override flag variables for a test""" + for k, v in kw.iteritems(): + if k in self.flag_overrides: + self.reset_flags() + raise Exception( + 'trying to override already overriden flag: %s' % k) + self.flag_overrides[k] = getattr(FLAGS, k) + setattr(FLAGS, k, v) + + def reset_flags(self): + """Resets all flag variables for the test. Runs after each test""" + FLAGS.Reset() + for k, v in self._original_flags.iteritems(): + setattr(FLAGS, k, v) + + def run(self, result=None): + test_method = getattr(self, self._testMethodName) + setattr(self, + self._testMethodName, + self._maybeInlineCallbacks(test_method, result)) + rv = super(TrialTestCase, self).run(result) + setattr(self, self._testMethodName, test_method) + return rv + + def _maybeInlineCallbacks(self, func, result): + def _wrapped(): + g = func() + if isinstance(g, defer.Deferred): + return g + if not hasattr(g, 'send'): + return defer.succeed(g) + + inlined = defer.inlineCallbacks(func) + d = inlined() + return d + _wrapped.func_name = func.func_name + return _wrapped diff --git a/nova/tests/scheduler_unittest.py b/nova/tests/scheduler_unittest.py index cb5fe6b9ca..37d15567fa 100644 --- a/nova/tests/scheduler_unittest.py +++ b/nova/tests/scheduler_unittest.py @@ -22,7 +22,7 @@ Tests For Scheduler from nova import context from nova import db from nova import flags -from nova import service +from nova import service_eventlet as service from nova import test from nova import rpc from nova import utils @@ -122,12 +122,12 @@ class SimpleDriverTestCase(test.TrialTestCase): 'nova-compute', 'compute', FLAGS.compute_manager) - compute1.startService() + compute1.start() compute2 = service.Service('host2', 'nova-compute', 'compute', FLAGS.compute_manager) - compute2.startService() + compute2.start() hosts = self.scheduler.driver.hosts_up(self.context, 'compute') self.assertEqual(len(hosts), 2) compute1.kill() @@ -139,12 +139,12 @@ class SimpleDriverTestCase(test.TrialTestCase): 'nova-compute', 'compute', FLAGS.compute_manager) - compute1.startService() + compute1.start() compute2 = service.Service('host2', 'nova-compute', 'compute', FLAGS.compute_manager) - compute2.startService() + compute2.start() instance_id1 = self._create_instance() compute1.run_instance(self.context, instance_id1) instance_id2 = self._create_instance() @@ -162,12 +162,12 @@ class SimpleDriverTestCase(test.TrialTestCase): 'nova-compute', 'compute', FLAGS.compute_manager) - compute1.startService() + compute1.start() compute2 = service.Service('host2', 'nova-compute', 'compute', FLAGS.compute_manager) - compute2.startService() + compute2.start() instance_ids1 = [] instance_ids2 = [] for index in xrange(FLAGS.max_cores): @@ -195,12 +195,12 @@ class SimpleDriverTestCase(test.TrialTestCase): 'nova-volume', 'volume', FLAGS.volume_manager) - volume1.startService() + volume1.start() volume2 = service.Service('host2', 'nova-volume', 'volume', FLAGS.volume_manager) - volume2.startService() + volume2.start() volume_id1 = self._create_volume() volume1.create_volume(self.context, volume_id1) volume_id2 = self._create_volume() @@ -218,12 +218,12 @@ class SimpleDriverTestCase(test.TrialTestCase): 'nova-volume', 'volume', FLAGS.volume_manager) - volume1.startService() + volume1.start() volume2 = service.Service('host2', 'nova-volume', 'volume', FLAGS.volume_manager) - volume2.startService() + volume2.start() volume_ids1 = [] volume_ids2 = [] for index in xrange(FLAGS.max_gigabytes): diff --git a/nova/tests/service_unittest.py b/nova/tests/service_unittest.py index 4f8d2d550f..a2bac9af02 100644 --- a/nova/tests/service_unittest.py +++ b/nova/tests/service_unittest.py @@ -22,14 +22,11 @@ Unit Tests for remote procedure calls using queue import mox -from twisted.application.app import startApplication -from twisted.internet import defer - from nova import exception from nova import flags from nova import rpc from nova import test -from nova import service +from nova import service_eventlet as service from nova import manager FLAGS = flags.FLAGS @@ -63,7 +60,7 @@ class ServiceManagerTestCase(test.TrialTestCase): 'test', 'test', 'nova.tests.service_unittest.FakeManager') - serv.startService() + serv.start() self.assertEqual(serv.test_method(), 'manager') def test_override_manager_method(self): @@ -71,7 +68,7 @@ class ServiceManagerTestCase(test.TrialTestCase): 'test', 'test', 'nova.tests.service_unittest.FakeManager') - serv.startService() + serv.start() self.assertEqual(serv.test_method(), 'service') @@ -94,8 +91,8 @@ class ServiceTestCase(test.TrialTestCase): self.mox.StubOutWithMock(rpc, 'AdapterConsumer', use_mock_anything=True) - self.mox.StubOutWithMock( - service.task, 'LoopingCall', use_mock_anything=True) + #self.mox.StubOutWithMock( + # service.task, 'LoopingCall', use_mock_anything=True) rpc.AdapterConsumer(connection=mox.IgnoreArg(), topic=topic, proxy=mox.IsA(service.Service)).AndReturn( @@ -106,19 +103,19 @@ class ServiceTestCase(test.TrialTestCase): proxy=mox.IsA(service.Service)).AndReturn( rpc.AdapterConsumer) - rpc.AdapterConsumer.attach_to_twisted() - rpc.AdapterConsumer.attach_to_twisted() + rpc.AdapterConsumer.attach_to_eventlet() + rpc.AdapterConsumer.attach_to_eventlet() # Stub out looping call a bit needlessly since we don't have an easy # way to cancel it (yet) when the tests finishes - service.task.LoopingCall(mox.IgnoreArg()).AndReturn( - service.task.LoopingCall) - service.task.LoopingCall.start(interval=mox.IgnoreArg(), - now=mox.IgnoreArg()) - service.task.LoopingCall(mox.IgnoreArg()).AndReturn( - service.task.LoopingCall) - service.task.LoopingCall.start(interval=mox.IgnoreArg(), - now=mox.IgnoreArg()) + #service.task.LoopingCall(mox.IgnoreArg()).AndReturn( + # service.task.LoopingCall) + #service.task.LoopingCall.start(interval=mox.IgnoreArg(), + # now=mox.IgnoreArg()) + #service.task.LoopingCall(mox.IgnoreArg()).AndReturn( + # service.task.LoopingCall) + #service.task.LoopingCall.start(interval=mox.IgnoreArg(), + # now=mox.IgnoreArg()) service_create = {'host': host, 'binary': binary, @@ -135,8 +132,9 @@ class ServiceTestCase(test.TrialTestCase): service.db.service_create(mox.IgnoreArg(), service_create).AndReturn(service_ref) self.mox.ReplayAll() - - startApplication(app, False) + + app.start() + app.stop() self.assert_(app) # We're testing sort of weird behavior in how report_state decides @@ -172,7 +170,7 @@ class ServiceTestCase(test.TrialTestCase): binary, topic, 'nova.tests.service_unittest.FakeManager') - serv.startService() + serv.start() serv.report_state() def test_report_state_newly_disconnected(self): @@ -202,7 +200,7 @@ class ServiceTestCase(test.TrialTestCase): binary, topic, 'nova.tests.service_unittest.FakeManager') - serv.startService() + serv.start() serv.report_state() self.assert_(serv.model_disconnected) @@ -235,7 +233,7 @@ class ServiceTestCase(test.TrialTestCase): binary, topic, 'nova.tests.service_unittest.FakeManager') - serv.startService() + serv.start() serv.model_disconnected = True serv.report_state() diff --git a/nova/utils.py b/nova/utils.py index 2c43203d88..ddbe0479e7 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -253,13 +253,18 @@ class LoopingCall(object): return done.send(True) + + self.done = done greenthread.spawn(_inner) - return done + return self.done def stop(self): self._running = False + def wait(self): + return self.done.wait() + def xhtml_escape(value): """Escapes a string so it is valid within XML or XHTML. diff --git a/run_tests.py b/run_tests.py index 883d2b768c..33bf51b18e 100644 --- a/run_tests.py +++ b/run_tests.py @@ -48,7 +48,6 @@ import sys from twisted.scripts import trial as trial_script -import unittest from nova import flags from nova import twistd @@ -62,11 +61,10 @@ from nova.tests.flags_unittest import * from nova.tests.misc_unittest import * from nova.tests.network_unittest import * #from nova.tests.objectstore_unittest import * -#from nova.tests.process_unittest import * from nova.tests.quota_unittest import * from nova.tests.rpc_unittest import * from nova.tests.scheduler_unittest import * -#from nova.tests.service_unittest import * +from nova.tests.service_unittest import * from nova.tests.twistd_unittest import * from nova.tests.validator_unittest import * from nova.tests.virt_unittest import * From b3f5aba0c465b263c1d0a15c7d249dafb3a98e6c Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 9 Dec 2010 15:25:14 -0800 Subject: [PATCH 111/229] remove service and rename service_eventlet to service --- bin/nova-combined | 14 +- bin/nova-compute | 6 +- bin/nova-network | 6 +- bin/nova-scheduler | 6 +- bin/nova-volume | 6 +- nova/service.py | 169 ++++++++++++++--- nova/service_eventlet.py | 303 ------------------------------- nova/tests/scheduler_unittest.py | 2 +- nova/tests/service_unittest.py | 2 +- 9 files changed, 160 insertions(+), 354 deletions(-) delete mode 100644 nova/service_eventlet.py diff --git a/bin/nova-combined b/bin/nova-combined index c865843289..c70d1d9ef7 100755 --- a/bin/nova-combined +++ b/bin/nova-combined @@ -37,7 +37,7 @@ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): from nova import api from nova import flags -from nova import service_eventlet +from nova import service from nova import wsgi @@ -54,13 +54,13 @@ flags.DEFINE_string('ec2api_host', '0.0.0.0', 'EC2 API host') if __name__ == '__main__': FLAGS(sys.argv) - compute = service_eventlet.Service.create(binary='nova-compute') - network = service_eventlet.Service.create(binary='nova-network') - volume = service_eventlet.Service.create(binary='nova-volume') - scheduler = service_eventlet.Service.create(binary='nova-scheduler') - #objectstore = service_eventlet.Service.create(binary='nova-objectstore') + compute = service.Service.create(binary='nova-compute') + network = service.Service.create(binary='nova-network') + volume = service.Service.create(binary='nova-volume') + scheduler = service.Service.create(binary='nova-scheduler') + #objectstore = service.Service.create(binary='nova-objectstore') - service_eventlet.serve(compute, network, volume, scheduler) + service.serve(compute, network, volume, scheduler) server = wsgi.Server() server.start(api.API('os'), FLAGS.osapi_port, host=FLAGS.osapi_host) diff --git a/bin/nova-compute b/bin/nova-compute index 307f7cb591..4baf47e296 100755 --- a/bin/nova-compute +++ b/bin/nova-compute @@ -35,8 +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) -from nova import service_eventlet +from nova import service if __name__ == '__main__': - service_eventlet.serve() - service_eventlet.wait() + service.serve() + service.wait() diff --git a/bin/nova-network b/bin/nova-network index 307f7cb591..4baf47e296 100755 --- a/bin/nova-network +++ b/bin/nova-network @@ -35,8 +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) -from nova import service_eventlet +from nova import service if __name__ == '__main__': - service_eventlet.serve() - service_eventlet.wait() + service.serve() + service.wait() diff --git a/bin/nova-scheduler b/bin/nova-scheduler index e4aa0dcaeb..a1b7ddf60b 100755 --- a/bin/nova-scheduler +++ b/bin/nova-scheduler @@ -35,8 +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) -from nova import service_eventlet +from nova import service if __name__ == '__main__': - service_eventlet.serve() - service_eventlet.wait() + service.serve() + service.wait() diff --git a/bin/nova-volume b/bin/nova-volume index 395d160418..ba4a3a5023 100755 --- a/bin/nova-volume +++ b/bin/nova-volume @@ -35,8 +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) -from nova import service_eventlet +from nova import service if __name__ == '__main__': - service_eventlet.serve() - service_eventlet.wait() + service.serve() + service.wait() diff --git a/nova/service.py b/nova/service.py index 55a0bb2120..576cf8b85a 100644 --- a/nova/service.py +++ b/nova/service.py @@ -17,21 +17,17 @@ # under the License. """ -A service is a very thin wrapper around a Manager object. It exposes the -manager's public methods to other components of the system via rpc. It will -report state periodically to the database and is responsible for initiating -any periodic tasts that need to be executed on a given host. - -This module contains Service, a generic baseclass for all workers. +Generic Node baseclass for all workers that run on hosts """ import inspect import logging import os +import sys -from twisted.internet import defer -from twisted.internet import task -from twisted.application import service +from eventlet import event +from eventlet import greenthread +from eventlet import greenpool from nova import context from nova import db @@ -50,8 +46,16 @@ flags.DEFINE_integer('periodic_interval', 60, 'seconds between running periodic tasks', lower_bound=1) +flags.DEFINE_string('pidfile', None, + 'pidfile to use for this service') -class Service(object, service.Service): + +flags.DEFINE_flag(flags.HelpFlag()) +flags.DEFINE_flag(flags.HelpshortFlag()) +flags.DEFINE_flag(flags.HelpXMLFlag()) + + +class Service(object): """Base class for workers that run on hosts.""" def __init__(self, host, binary, topic, manager, report_interval=None, @@ -64,8 +68,9 @@ class Service(object, service.Service): self.periodic_interval = periodic_interval super(Service, self).__init__(*args, **kwargs) self.saved_args, self.saved_kwargs = args, kwargs + self.timers = [] - def startService(self): # pylint: disable-msg C0103 + def start(self): manager_class = utils.import_class(self.manager_class_name) self.manager = manager_class(host=self.host, *self.saved_args, **self.saved_kwargs) @@ -80,26 +85,29 @@ class Service(object, service.Service): except exception.NotFound: self._create_service_ref(ctxt) - conn = rpc.Connection.instance() + conn1 = rpc.Connection.instance(new=True) + conn2 = rpc.Connection.instance(new=True) if self.report_interval: consumer_all = rpc.AdapterConsumer( - connection=conn, + connection=conn1, topic=self.topic, proxy=self) consumer_node = rpc.AdapterConsumer( - connection=conn, + connection=conn2, topic='%s.%s' % (self.topic, self.host), proxy=self) - consumer_all.attach_to_twisted() - consumer_node.attach_to_twisted() - - pulse = task.LoopingCall(self.report_state) + self.timers.append(consumer_all.attach_to_eventlet()) + self.timers.append(consumer_node.attach_to_eventlet()) + + pulse = utils.LoopingCall(self.report_state) pulse.start(interval=self.report_interval, now=False) + self.timers.append(pulse) if self.periodic_interval: - pulse = task.LoopingCall(self.periodic_tasks) - pulse.start(interval=self.periodic_interval, now=False) + periodic = utils.LoopingCall(self.periodic_tasks) + periodic.start(interval=self.periodic_interval, now=False) + self.timers.append(periodic) def _create_service_ref(self, context): service_ref = db.service_create(context, @@ -114,7 +122,7 @@ class Service(object, service.Service): return getattr(manager, key) @classmethod - def create(cls, + def create(cls, host=None, binary=None, topic=None, @@ -147,24 +155,28 @@ class Service(object, service.Service): service_obj = cls(host, binary, topic, manager, report_interval, periodic_interval) - # This is the parent service that twistd will be looking for when it - # parses this file, return it so that we can get it into globals. - application = service.Application(binary) - service_obj.setServiceParent(application) - return application + return service_obj def kill(self): """Destroy the service object in the datastore""" + self.stop() try: db.service_destroy(context.get_admin_context(), self.service_id) except exception.NotFound: logging.warn("Service killed that has no database entry") + def stop(self): + for x in self.timers: + try: + x.stop() + except Exception: + pass + self.timers = [] + def periodic_tasks(self): """Tasks to be run at a periodic interval""" - yield self.manager.periodic_tasks(context.get_admin_context()) + self.manager.periodic_tasks(context.get_admin_context()) - @defer.inlineCallbacks def report_state(self): """Update the state of this service in the datastore.""" ctxt = context.get_admin_context() @@ -180,7 +192,7 @@ class Service(object, service.Service): db.service_update(ctxt, self.service_id, {'report_count': service_ref['report_count'] + 1}) - + # TODO(termie): make this pattern be more elegant. if getattr(self, "model_disconnected", False): self.model_disconnected = False @@ -191,4 +203,101 @@ class Service(object, service.Service): if not getattr(self, "model_disconnected", False): self.model_disconnected = True logging.exception("model server went away") - yield + + +def stop(pidfile): + """ + Stop the daemon + """ + # Get the pid from the pidfile + try: + pf = file(pidfile, 'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if not pid: + message = "pidfile %s does not exist. Daemon not running?\n" + sys.stderr.write(message % pidfile) + # Not an error in a restart + return + + # Try killing the daemon process + try: + while 1: + os.kill(pid, signal.SIGKILL) + time.sleep(0.1) + except OSError, err: + err = str(err) + if err.find("No such process") > 0: + if os.path.exists(pidfile): + os.remove(pidfile) + else: + print str(err) + sys.exit(1) + + +def serve(*services): + argv = FLAGS(sys.argv) + + if not services: + services = [Service.create()] + + name = '_'.join(x.binary for x in services) + logging.debug("Serving %s" % name) + + logging.getLogger('amqplib').setLevel(logging.DEBUG) + + if not FLAGS.pidfile: + FLAGS.pidfile = '%s.pid' % name + # NOTE(vish): if we're running nodaemon, redirect the log to stdout + #if FLAGS.nodaemon and not FLAGS.logfile: + # FLAGS.logfile = "-" + #if not FLAGS.logfile: + # FLAGS.logfile = '%s.log' % name + #if not FLAGS.prefix: + # FLAGS.prefix = name + #elif FLAGS.prefix.endswith('twisted'): + # FLAGS.prefix = FLAGS.prefix.replace('twisted', name) + + action = 'start' + if len(argv) > 1: + action = argv.pop() + + if action == 'stop': + stop(FLAGS.pidfile) + sys.exit() + elif action == 'restart': + stop(FLAGS.pidfile) + elif action == 'start': + pass + else: + print 'usage: %s [options] [start|stop|restart]' % argv[0] + sys.exit(1) + + #formatter = logging.Formatter( + # '(%(name)s): %(levelname)s %(message)s') + #handler = logging.StreamHandler() + #handler.setFormatter(formatter) + #logging.getLogger().addHandler(handler) + + if FLAGS.verbose: + logging.getLogger().setLevel(logging.DEBUG) + else: + logging.getLogger().setLevel(logging.WARNING) + + logging.debug("Full set of FLAGS:") + for flag in FLAGS: + logging.debug("%s : %s" % (flag, FLAGS.get(flag, None))) + + for x in services: + x.start() + + #while True: + # greenthread.sleep(5) + + +def wait(): + while True: + greenthread.sleep(5) diff --git a/nova/service_eventlet.py b/nova/service_eventlet.py deleted file mode 100644 index 576cf8b85a..0000000000 --- a/nova/service_eventlet.py +++ /dev/null @@ -1,303 +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. - -""" -Generic Node baseclass for all workers that run on hosts -""" - -import inspect -import logging -import os -import sys - -from eventlet import event -from eventlet import greenthread -from eventlet import greenpool - -from nova import context -from nova import db -from nova import exception -from nova import flags -from nova import rpc -from nova import utils - - -FLAGS = flags.FLAGS -flags.DEFINE_integer('report_interval', 10, - 'seconds between nodes reporting state to datastore', - lower_bound=1) - -flags.DEFINE_integer('periodic_interval', 60, - 'seconds between running periodic tasks', - lower_bound=1) - -flags.DEFINE_string('pidfile', None, - 'pidfile to use for this service') - - -flags.DEFINE_flag(flags.HelpFlag()) -flags.DEFINE_flag(flags.HelpshortFlag()) -flags.DEFINE_flag(flags.HelpXMLFlag()) - - -class Service(object): - """Base class for workers that run on hosts.""" - - def __init__(self, host, binary, topic, manager, report_interval=None, - periodic_interval=None, *args, **kwargs): - self.host = host - self.binary = binary - self.topic = topic - self.manager_class_name = manager - self.report_interval = report_interval - self.periodic_interval = periodic_interval - super(Service, self).__init__(*args, **kwargs) - self.saved_args, self.saved_kwargs = args, kwargs - self.timers = [] - - def start(self): - manager_class = utils.import_class(self.manager_class_name) - self.manager = manager_class(host=self.host, *self.saved_args, - **self.saved_kwargs) - self.manager.init_host() - self.model_disconnected = False - ctxt = context.get_admin_context() - try: - service_ref = db.service_get_by_args(ctxt, - self.host, - self.binary) - self.service_id = service_ref['id'] - except exception.NotFound: - self._create_service_ref(ctxt) - - conn1 = rpc.Connection.instance(new=True) - conn2 = rpc.Connection.instance(new=True) - if self.report_interval: - consumer_all = rpc.AdapterConsumer( - connection=conn1, - topic=self.topic, - proxy=self) - consumer_node = rpc.AdapterConsumer( - connection=conn2, - topic='%s.%s' % (self.topic, self.host), - proxy=self) - - self.timers.append(consumer_all.attach_to_eventlet()) - self.timers.append(consumer_node.attach_to_eventlet()) - - pulse = utils.LoopingCall(self.report_state) - pulse.start(interval=self.report_interval, now=False) - self.timers.append(pulse) - - if self.periodic_interval: - periodic = utils.LoopingCall(self.periodic_tasks) - periodic.start(interval=self.periodic_interval, now=False) - self.timers.append(periodic) - - def _create_service_ref(self, context): - service_ref = db.service_create(context, - {'host': self.host, - 'binary': self.binary, - 'topic': self.topic, - 'report_count': 0}) - self.service_id = service_ref['id'] - - def __getattr__(self, key): - manager = self.__dict__.get('manager', None) - return getattr(manager, key) - - @classmethod - def create(cls, - host=None, - binary=None, - topic=None, - manager=None, - report_interval=None, - periodic_interval=None): - """Instantiates class and passes back application object. - - Args: - host, defaults to FLAGS.host - binary, defaults to basename of executable - topic, defaults to bin_name - "nova-" part - manager, defaults to FLAGS._manager - report_interval, defaults to FLAGS.report_interval - periodic_interval, defaults to FLAGS.periodic_interval - """ - if not host: - host = FLAGS.host - if not binary: - binary = os.path.basename(inspect.stack()[-1][1]) - if not topic: - topic = binary.rpartition("nova-")[2] - if not manager: - manager = FLAGS.get('%s_manager' % topic, None) - if not report_interval: - report_interval = FLAGS.report_interval - if not periodic_interval: - periodic_interval = FLAGS.periodic_interval - logging.warn("Starting %s node", topic) - service_obj = cls(host, binary, topic, manager, - report_interval, periodic_interval) - - return service_obj - - def kill(self): - """Destroy the service object in the datastore""" - self.stop() - try: - db.service_destroy(context.get_admin_context(), self.service_id) - except exception.NotFound: - logging.warn("Service killed that has no database entry") - - def stop(self): - for x in self.timers: - try: - x.stop() - except Exception: - pass - self.timers = [] - - def periodic_tasks(self): - """Tasks to be run at a periodic interval""" - self.manager.periodic_tasks(context.get_admin_context()) - - def report_state(self): - """Update the state of this service in the datastore.""" - ctxt = context.get_admin_context() - try: - try: - service_ref = db.service_get(ctxt, self.service_id) - except exception.NotFound: - logging.debug("The service database object disappeared, " - "Recreating it.") - self._create_service_ref(ctxt) - service_ref = db.service_get(ctxt, self.service_id) - - db.service_update(ctxt, - self.service_id, - {'report_count': service_ref['report_count'] + 1}) - - # TODO(termie): make this pattern be more elegant. - if getattr(self, "model_disconnected", False): - self.model_disconnected = False - logging.error("Recovered model server connection!") - - # TODO(vish): this should probably only catch connection errors - except Exception: # pylint: disable-msg=W0702 - if not getattr(self, "model_disconnected", False): - self.model_disconnected = True - logging.exception("model server went away") - - -def stop(pidfile): - """ - Stop the daemon - """ - # Get the pid from the pidfile - try: - pf = file(pidfile, 'r') - pid = int(pf.read().strip()) - pf.close() - except IOError: - pid = None - - if not pid: - message = "pidfile %s does not exist. Daemon not running?\n" - sys.stderr.write(message % pidfile) - # Not an error in a restart - return - - # Try killing the daemon process - try: - while 1: - os.kill(pid, signal.SIGKILL) - time.sleep(0.1) - except OSError, err: - err = str(err) - if err.find("No such process") > 0: - if os.path.exists(pidfile): - os.remove(pidfile) - else: - print str(err) - sys.exit(1) - - -def serve(*services): - argv = FLAGS(sys.argv) - - if not services: - services = [Service.create()] - - name = '_'.join(x.binary for x in services) - logging.debug("Serving %s" % name) - - logging.getLogger('amqplib').setLevel(logging.DEBUG) - - if not FLAGS.pidfile: - FLAGS.pidfile = '%s.pid' % name - # NOTE(vish): if we're running nodaemon, redirect the log to stdout - #if FLAGS.nodaemon and not FLAGS.logfile: - # FLAGS.logfile = "-" - #if not FLAGS.logfile: - # FLAGS.logfile = '%s.log' % name - #if not FLAGS.prefix: - # FLAGS.prefix = name - #elif FLAGS.prefix.endswith('twisted'): - # FLAGS.prefix = FLAGS.prefix.replace('twisted', name) - - action = 'start' - if len(argv) > 1: - action = argv.pop() - - if action == 'stop': - stop(FLAGS.pidfile) - sys.exit() - elif action == 'restart': - stop(FLAGS.pidfile) - elif action == 'start': - pass - else: - print 'usage: %s [options] [start|stop|restart]' % argv[0] - sys.exit(1) - - #formatter = logging.Formatter( - # '(%(name)s): %(levelname)s %(message)s') - #handler = logging.StreamHandler() - #handler.setFormatter(formatter) - #logging.getLogger().addHandler(handler) - - if FLAGS.verbose: - logging.getLogger().setLevel(logging.DEBUG) - else: - logging.getLogger().setLevel(logging.WARNING) - - logging.debug("Full set of FLAGS:") - for flag in FLAGS: - logging.debug("%s : %s" % (flag, FLAGS.get(flag, None))) - - for x in services: - x.start() - - #while True: - # greenthread.sleep(5) - - -def wait(): - while True: - greenthread.sleep(5) diff --git a/nova/tests/scheduler_unittest.py b/nova/tests/scheduler_unittest.py index 37d15567fa..f442a4bc22 100644 --- a/nova/tests/scheduler_unittest.py +++ b/nova/tests/scheduler_unittest.py @@ -22,7 +22,7 @@ Tests For Scheduler from nova import context from nova import db from nova import flags -from nova import service_eventlet as service +from nova import service from nova import test from nova import rpc from nova import utils diff --git a/nova/tests/service_unittest.py b/nova/tests/service_unittest.py index a2bac9af02..9f6d1af7d9 100644 --- a/nova/tests/service_unittest.py +++ b/nova/tests/service_unittest.py @@ -26,7 +26,7 @@ from nova import exception from nova import flags from nova import rpc from nova import test -from nova import service_eventlet as service +from nova import service from nova import manager FLAGS = flags.FLAGS From a1640f352806ee12f6b485a8d69a65bd42b51411 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 9 Dec 2010 16:05:13 -0800 Subject: [PATCH 112/229] formatting and naming cleanup --- bin/nova-api | 6 +- bin/nova-combined | 4 +- bin/nova-compute | 4 +- bin/nova-network | 4 +- bin/nova-scheduler | 4 +- bin/nova-volume | 4 +- nova/flags.py | 4 +- nova/objectstore/image.py | 2 - nova/rpc.py | 5 +- nova/server.py | 151 ----------------------------- nova/test.py | 9 +- nova/tests/access_unittest.py | 2 +- nova/tests/api/__init__.py | 3 +- nova/tests/auth_unittest.py | 11 +-- nova/tests/cloud_unittest.py | 2 +- nova/tests/compute_unittest.py | 2 +- nova/tests/flags_unittest.py | 2 +- nova/tests/misc_unittest.py | 2 +- nova/tests/network_unittest.py | 2 +- nova/tests/objectstore_unittest.py | 4 +- nova/tests/quota_unittest.py | 2 +- nova/tests/rpc_unittest.py | 2 +- nova/tests/scheduler_unittest.py | 4 +- nova/tests/service_unittest.py | 4 +- nova/tests/virt_unittest.py | 4 +- nova/tests/volume_unittest.py | 2 +- nova/utils.py | 7 -- run_tests.py | 1 - 28 files changed, 39 insertions(+), 214 deletions(-) delete mode 100644 nova/server.py diff --git a/bin/nova-api b/bin/nova-api index 3215ad5efa..3505cefe7b 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -17,9 +17,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -""" -Nova API daemon. -""" + +"""Starter script for Nova API.""" import os import sys @@ -35,7 +34,6 @@ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): from nova import api from nova import flags from nova import utils -from nova import server from nova import wsgi FLAGS = flags.FLAGS diff --git a/bin/nova-combined b/bin/nova-combined index c70d1d9ef7..964ffd4384 100755 --- a/bin/nova-combined +++ b/bin/nova-combined @@ -17,9 +17,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" - Twistd daemon for the nova compute nodes. -""" +"""Combined starter script for Nova services.""" import eventlet eventlet.monkey_patch() diff --git a/bin/nova-compute b/bin/nova-compute index 4baf47e296..f224f0690d 100755 --- a/bin/nova-compute +++ b/bin/nova-compute @@ -17,9 +17,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" - Twistd daemon for the nova compute nodes. -""" +"""Starter script for Nova Compute.""" import eventlet eventlet.monkey_patch() diff --git a/bin/nova-network b/bin/nova-network index 4baf47e296..589c75e5ad 100755 --- a/bin/nova-network +++ b/bin/nova-network @@ -17,9 +17,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" - Twistd daemon for the nova compute nodes. -""" +"""Starter script for Nova Network.""" import eventlet eventlet.monkey_patch() diff --git a/bin/nova-scheduler b/bin/nova-scheduler index a1b7ddf60b..cb345aa3f4 100755 --- a/bin/nova-scheduler +++ b/bin/nova-scheduler @@ -17,9 +17,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" - Twistd daemon for the nova scheduler nodes. -""" +""" Starter script for Nova Scheduler.""" import eventlet eventlet.monkey_patch() diff --git a/bin/nova-volume b/bin/nova-volume index ba4a3a5023..2f74f4b9ab 100755 --- a/bin/nova-volume +++ b/bin/nova-volume @@ -17,9 +17,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" - Twistd daemon for the nova volume nodes. -""" +"""Starter script for Nova Volume.""" import eventlet eventlet.monkey_patch() diff --git a/nova/flags.py b/nova/flags.py index 034b136d8b..87444565ab 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -159,12 +159,11 @@ class StrWrapper(object): return str(val) raise KeyError(name) + FLAGS = FlagValues() gflags.FLAGS = FLAGS gflags.DEFINE_flag(gflags.HelpFlag(), FLAGS) -gflags.FLAGS = FLAGS - def _wrapper(func): def _wrapped(*args, **kw): @@ -187,6 +186,7 @@ DEFINE_multistring = _wrapper(gflags.DEFINE_multistring) DEFINE_multi_int = _wrapper(gflags.DEFINE_multi_int) DEFINE_flag = _wrapper(gflags.DEFINE_flag) + HelpFlag = gflags.HelpFlag HelpshortFlag = gflags.HelpshortFlag HelpXMLFlag = gflags.HelpXMLFlag diff --git a/nova/objectstore/image.py b/nova/objectstore/image.py index 2fe0b0117c..9e56e256c1 100644 --- a/nova/objectstore/image.py +++ b/nova/objectstore/image.py @@ -26,7 +26,6 @@ Requires decryption using keys in the manifest. import binascii import glob import json -import logging import os import shutil import tarfile @@ -265,7 +264,6 @@ class Image(object): if err: raise exception.Error("Failed to decrypt initialization " "vector: %s" % err) - logging.debug(iv) _out, err = utils.execute( 'openssl enc -d -aes-128-cbc -in %s -K %s -iv %s -out %s' diff --git a/nova/rpc.py b/nova/rpc.py index 652b9e4aa8..6a634a4eca 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -309,7 +309,6 @@ def call(context, topic, msg): class WaitMessage(object): def __call__(self, data, message): - LOG.debug('data %s, msg %s', data, message) """Acks message and sets result.""" message.ack() if data['failure']: @@ -332,6 +331,10 @@ def call(context, topic, msg): except StopIteration: pass consumer.close() + # NOTE(termie): this is a little bit of a change from the original + # twisted-based code where returning a Failure + # instance from a deferred call is very similar to + # raising an exception if isinstance(wait_msg.result, Exception): raise wait_msg.result return wait_msg.result diff --git a/nova/server.py b/nova/server.py deleted file mode 100644 index a060d3283e..0000000000 --- a/nova/server.py +++ /dev/null @@ -1,151 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Base functionality for nova daemons - gradually being replaced with twistd.py. -""" - -import daemon -from daemon import pidlockfile -import logging -import logging.handlers -import os -import signal -import sys -import time - -from nova import flags - - -FLAGS = flags.FLAGS -flags.DEFINE_bool('daemonize', False, 'daemonize this process') -# NOTE(termie): right now I am defaulting to using syslog when we daemonize -# it may be better to do something else -shrug- -# NOTE(Devin): I think we should let each process have its own log file -# and put it in /var/logs/nova/(appname).log -# This makes debugging much easier and cuts down on sys log -# clutter. -flags.DEFINE_bool('use_syslog', True, 'output to syslog when daemonizing') -flags.DEFINE_string('logfile', None, 'log file to output to') -flags.DEFINE_string('logdir', None, 'directory to keep log files in ' - '(will be prepended to $logfile)') -flags.DEFINE_string('pidfile', None, 'pid file to output to') -flags.DEFINE_string('working_directory', './', 'working directory...') -flags.DEFINE_integer('uid', os.getuid(), 'uid under which to run') -flags.DEFINE_integer('gid', os.getgid(), 'gid under which to run') - - -def stop(pidfile): - """ - Stop the daemon - """ - # Get the pid from the pidfile - try: - pid = int(open(pidfile, 'r').read().strip()) - except IOError: - message = "pidfile %s does not exist. Daemon not running?\n" - sys.stderr.write(message % pidfile) - return - - # Try killing the daemon process - try: - while 1: - os.kill(pid, signal.SIGTERM) - time.sleep(0.1) - except OSError, err: - err = str(err) - if err.find("No such process") > 0: - if os.path.exists(pidfile): - os.remove(pidfile) - else: - print str(err) - sys.exit(1) - - -def serve(name, main): - """Controller for server""" - argv = FLAGS(sys.argv) - - if not FLAGS.pidfile: - FLAGS.pidfile = '%s.pid' % name - - logging.debug("Full set of FLAGS: \n\n\n") - for flag in FLAGS: - logging.debug("%s : %s", flag, FLAGS.get(flag, None)) - - action = 'start' - if len(argv) > 1: - action = argv.pop() - - if action == 'stop': - stop(FLAGS.pidfile) - sys.exit() - elif action == 'restart': - stop(FLAGS.pidfile) - elif action == 'start': - pass - else: - print 'usage: %s [options] [start|stop|restart]' % argv[0] - sys.exit(1) - daemonize(argv, name, main) - - -def daemonize(args, name, main): - """Does the work of daemonizing the process""" - logging.getLogger('amqplib').setLevel(logging.WARN) - files_to_keep = [] - if FLAGS.daemonize: - logger = logging.getLogger() - formatter = logging.Formatter( - name + '(%(name)s): %(levelname)s %(message)s') - if FLAGS.use_syslog and not FLAGS.logfile: - syslog = logging.handlers.SysLogHandler(address='/dev/log') - syslog.setFormatter(formatter) - logger.addHandler(syslog) - files_to_keep.append(syslog.socket) - else: - if not FLAGS.logfile: - FLAGS.logfile = '%s.log' % name - if FLAGS.logdir: - FLAGS.logfile = os.path.join(FLAGS.logdir, FLAGS.logfile) - logfile = logging.FileHandler(FLAGS.logfile) - logfile.setFormatter(formatter) - logger.addHandler(logfile) - files_to_keep.append(logfile.stream) - stdin, stdout, stderr = None, None, None - else: - stdin, stdout, stderr = sys.stdin, sys.stdout, sys.stderr - - if FLAGS.verbose: - logging.getLogger().setLevel(logging.DEBUG) - else: - logging.getLogger().setLevel(logging.WARNING) - - with daemon.DaemonContext( - detach_process=FLAGS.daemonize, - working_directory=FLAGS.working_directory, - #pidfile=pidlockfile.TimeoutPIDLockFile(FLAGS.pidfile, - # acquire_timeout=1, - # threaded=False), - stdin=stdin, - stdout=stdout, - stderr=stderr, - uid=FLAGS.uid, - gid=FLAGS.gid, - files_preserve=files_to_keep): - main(args) diff --git a/nova/test.py b/nova/test.py index e27d291662..ecc97aa4d9 100644 --- a/nova/test.py +++ b/nova/test.py @@ -55,11 +55,11 @@ def skip_if_fake(func): return func(*args, **kw) return _skipper -class TrialTestCase(unittest.TestCase): +class TestCase(unittest.TestCase): """Test case base class for all unit tests""" def setUp(self): """Run before each test method to initialize test environment""" - super(TrialTestCase, self).setUp() + super(TestCase, self).setUp() # NOTE(vish): We need a better method for creating fixtures for tests # now that we have some required db setup for the system # to work properly. @@ -105,7 +105,7 @@ class TrialTestCase(unittest.TestCase): fakerabbit.reset_all() db.security_group_destroy_all(ctxt) - super(TrialTestCase, self).tearDown() + super(TestCase, self).tearDown() finally: self.reset_flags() @@ -137,7 +137,7 @@ class TrialTestCase(unittest.TestCase): rpc.Consumer.attach_to_eventlet = _wrapped -class OLDTrialTestCase(trial_unittest.TestCase): +class TrialTestCase(trial_unittest.TestCase): """Test case base class for all unit tests""" def setUp(self): """Run before each test method to initialize test environment""" @@ -175,7 +175,6 @@ class OLDTrialTestCase(trial_unittest.TestCase): db.fixed_ip_disassociate_all_by_timeout(ctxt, FLAGS.host, self.start) db.network_disassociate_all(ctxt) - rpc.Consumer.attach_to_eventlet = self.originalAttach for x in self.injected: try: x.stop() diff --git a/nova/tests/access_unittest.py b/nova/tests/access_unittest.py index 0f66c0a26b..58fdea3b5c 100644 --- a/nova/tests/access_unittest.py +++ b/nova/tests/access_unittest.py @@ -35,7 +35,7 @@ class Context(object): pass -class AccessTestCase(test.TrialTestCase): +class AccessTestCase(test.TestCase): def setUp(self): super(AccessTestCase, self).setUp() um = manager.AuthManager() diff --git a/nova/tests/api/__init__.py b/nova/tests/api/__init__.py index cdc1bbf001..9caa8c9d0d 100644 --- a/nova/tests/api/__init__.py +++ b/nova/tests/api/__init__.py @@ -78,5 +78,4 @@ class Test(unittest.TestCase): if __name__ == '__main__': - pass - #unittest.main() + unittest.main() diff --git a/nova/tests/auth_unittest.py b/nova/tests/auth_unittest.py index 129ff223d5..4508d67216 100644 --- a/nova/tests/auth_unittest.py +++ b/nova/tests/auth_unittest.py @@ -16,13 +16,10 @@ # License for the specific language governing permissions and limitations # under the License. -#import logging +import logging from M2Crypto import X509 import unittest -import eventlet -logging = eventlet.import_patched('logging') - from nova import crypto from nova import flags from nova import test @@ -329,12 +326,12 @@ class AuthManagerTestCase(object): self.assertTrue(user.is_admin()) -class AuthManagerLdapTestCase(AuthManagerTestCase, test.TrialTestCase): +class AuthManagerLdapTestCase(AuthManagerTestCase, test.TestCase): auth_driver = 'nova.auth.ldapdriver.FakeLdapDriver' def __init__(self, *args, **kwargs): AuthManagerTestCase.__init__(self) - test.TrialTestCase.__init__(self, *args, **kwargs) + test.TestCase.__init__(self, *args, **kwargs) import nova.auth.fakeldap as fakeldap FLAGS.redis_db = 8 if FLAGS.flush_db: @@ -346,7 +343,7 @@ class AuthManagerLdapTestCase(AuthManagerTestCase, test.TrialTestCase): self.skip = True -class AuthManagerDbTestCase(AuthManagerTestCase, test.TrialTestCase): +class AuthManagerDbTestCase(AuthManagerTestCase, test.TestCase): auth_driver = 'nova.auth.dbdriver.DbDriver' diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index b7b856da52..50834d9903 100644 --- a/nova/tests/cloud_unittest.py +++ b/nova/tests/cloud_unittest.py @@ -51,7 +51,7 @@ IMAGES_PATH = os.path.join(OSS_TEMPDIR, 'images') os.makedirs(IMAGES_PATH) -class CloudTestCase(test.TrialTestCase): +class CloudTestCase(test.TestCase): def setUp(self): super(CloudTestCase, self).setUp() self.flags(connection_type='fake', images_path=IMAGES_PATH) diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py index 67cea72c95..c6353d3575 100644 --- a/nova/tests/compute_unittest.py +++ b/nova/tests/compute_unittest.py @@ -35,7 +35,7 @@ from nova.compute import api as compute_api FLAGS = flags.FLAGS -class ComputeTestCase(test.TrialTestCase): +class ComputeTestCase(test.TestCase): """Test case for compute""" def setUp(self): logging.getLogger().setLevel(logging.DEBUG) diff --git a/nova/tests/flags_unittest.py b/nova/tests/flags_unittest.py index b97df075de..707300fcf6 100644 --- a/nova/tests/flags_unittest.py +++ b/nova/tests/flags_unittest.py @@ -24,7 +24,7 @@ FLAGS = flags.FLAGS flags.DEFINE_string('flags_unittest', 'foo', 'for testing purposes only') -class FlagsTestCase(test.TrialTestCase): +class FlagsTestCase(test.TestCase): def setUp(self): super(FlagsTestCase, self).setUp() diff --git a/nova/tests/misc_unittest.py b/nova/tests/misc_unittest.py index 667c63ad09..3eab1da0a1 100644 --- a/nova/tests/misc_unittest.py +++ b/nova/tests/misc_unittest.py @@ -20,7 +20,7 @@ from nova import test from nova.utils import parse_mailmap, str_dict_replace -class ProjectTestCase(test.TrialTestCase): +class ProjectTestCase(test.TestCase): def test_authors_up_to_date(self): if os.path.exists('../.bzr'): contributors = set() diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py index 6f4705719c..bcac205855 100644 --- a/nova/tests/network_unittest.py +++ b/nova/tests/network_unittest.py @@ -33,7 +33,7 @@ from nova.auth import manager FLAGS = flags.FLAGS -class NetworkTestCase(test.TrialTestCase): +class NetworkTestCase(test.TestCase): """Test cases for network code""" def setUp(self): super(NetworkTestCase, self).setUp() diff --git a/nova/tests/objectstore_unittest.py b/nova/tests/objectstore_unittest.py index 0617999234..ceac17adb6 100644 --- a/nova/tests/objectstore_unittest.py +++ b/nova/tests/objectstore_unittest.py @@ -54,7 +54,7 @@ os.makedirs(os.path.join(OSS_TEMPDIR, 'images')) os.makedirs(os.path.join(OSS_TEMPDIR, 'buckets')) -class ObjectStoreTestCase(test.TrialTestCase): +class ObjectStoreTestCase(test.TestCase): """Test objectstore API directly.""" def setUp(self): @@ -191,7 +191,7 @@ class TestSite(server.Site): protocol = TestHTTPChannel -class S3APITestCase(test.TrialTestCase): +class S3APITestCase(test.TestCase): """Test objectstore through S3 API.""" def setUp(self): diff --git a/nova/tests/quota_unittest.py b/nova/tests/quota_unittest.py index 1966b51f7b..8cf2a5e546 100644 --- a/nova/tests/quota_unittest.py +++ b/nova/tests/quota_unittest.py @@ -32,7 +32,7 @@ from nova.api.ec2 import cloud FLAGS = flags.FLAGS -class QuotaTestCase(test.TrialTestCase): +class QuotaTestCase(test.TestCase): def setUp(self): logging.getLogger().setLevel(logging.DEBUG) super(QuotaTestCase, self).setUp() diff --git a/nova/tests/rpc_unittest.py b/nova/tests/rpc_unittest.py index c2ad5cd795..4128c35b8c 100644 --- a/nova/tests/rpc_unittest.py +++ b/nova/tests/rpc_unittest.py @@ -29,7 +29,7 @@ from nova import test FLAGS = flags.FLAGS -class RpcTestCase(test.TrialTestCase): +class RpcTestCase(test.TestCase): """Test cases for rpc""" def setUp(self): super(RpcTestCase, self).setUp() diff --git a/nova/tests/scheduler_unittest.py b/nova/tests/scheduler_unittest.py index f442a4bc22..d1756b8fb5 100644 --- a/nova/tests/scheduler_unittest.py +++ b/nova/tests/scheduler_unittest.py @@ -44,7 +44,7 @@ class TestDriver(driver.Scheduler): return 'named_host' -class SchedulerTestCase(test.TrialTestCase): +class SchedulerTestCase(test.TestCase): """Test case for scheduler""" def setUp(self): super(SchedulerTestCase, self).setUp() @@ -73,7 +73,7 @@ class SchedulerTestCase(test.TrialTestCase): scheduler.named_method(ctxt, 'topic', num=7) -class SimpleDriverTestCase(test.TrialTestCase): +class SimpleDriverTestCase(test.TestCase): """Test case for simple driver""" def setUp(self): super(SimpleDriverTestCase, self).setUp() diff --git a/nova/tests/service_unittest.py b/nova/tests/service_unittest.py index 9f6d1af7d9..c94af4a230 100644 --- a/nova/tests/service_unittest.py +++ b/nova/tests/service_unittest.py @@ -45,7 +45,7 @@ class ExtendedService(service.Service): return 'service' -class ServiceManagerTestCase(test.TrialTestCase): +class ServiceManagerTestCase(test.TestCase): """Test cases for Services""" def test_attribute_error_for_no_manager(self): @@ -72,7 +72,7 @@ class ServiceManagerTestCase(test.TrialTestCase): self.assertEqual(serv.test_method(), 'service') -class ServiceTestCase(test.TrialTestCase): +class ServiceTestCase(test.TestCase): """Test cases for Services""" def setUp(self): diff --git a/nova/tests/virt_unittest.py b/nova/tests/virt_unittest.py index a4a8d3acf8..85e5698589 100644 --- a/nova/tests/virt_unittest.py +++ b/nova/tests/virt_unittest.py @@ -30,7 +30,7 @@ FLAGS = flags.FLAGS flags.DECLARE('instances_path', 'nova.compute.manager') -class LibvirtConnTestCase(test.TrialTestCase): +class LibvirtConnTestCase(test.TestCase): def setUp(self): super(LibvirtConnTestCase, self).setUp() self.manager = manager.AuthManager() @@ -123,7 +123,7 @@ class LibvirtConnTestCase(test.TrialTestCase): self.manager.delete_user(self.user) -class NWFilterTestCase(test.TrialTestCase): +class NWFilterTestCase(test.TestCase): def setUp(self): super(NWFilterTestCase, self).setUp() diff --git a/nova/tests/volume_unittest.py b/nova/tests/volume_unittest.py index 93d2ceab79..b13455fb07 100644 --- a/nova/tests/volume_unittest.py +++ b/nova/tests/volume_unittest.py @@ -31,7 +31,7 @@ from nova import utils FLAGS = flags.FLAGS -class VolumeTestCase(test.TrialTestCase): +class VolumeTestCase(test.TestCase): """Test Case for volumes.""" def setUp(self): diff --git a/nova/utils.py b/nova/utils.py index ddbe0479e7..22bf5d8cf4 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -129,13 +129,6 @@ def debug(arg): def runthis(prompt, cmd, check_exit_code=True): logging.debug("Running %s" % (cmd)) rv, err = execute(cmd, check_exit_code=check_exit_code) - #exit_code = subprocess.call(cmd.split(" ")) - #logging.debug(prompt % (exit_code)) - #if check_exit_code and exit_code != 0: - # raise ProcessExecutionError(exit_code=exit_code, - # stdout=None, - # stderr=None, - # cmd=cmd) def generate_uid(topic, size=8): diff --git a/run_tests.py b/run_tests.py index 33bf51b18e..6d7830a297 100644 --- a/run_tests.py +++ b/run_tests.py @@ -95,7 +95,6 @@ if __name__ == '__main__': else: from nova.tests.real_flags import * - # Establish redirect for STDERR sys.stderr.flush() err = open(FLAGS.tests_stderr, 'w+', 0) From af5c175dbc77048fb74311bf92569866676eee9c Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 9 Dec 2010 16:18:52 -0800 Subject: [PATCH 113/229] removed a few more references to twisted --- nova/rpc.py | 6 ++- nova/service.py | 2 - nova/virt/images.py | 2 +- nova/virt/xenapi/network_utils.py | 9 ++--- nova/virt/xenapi/vmops.py | 61 ++++++++++++++----------------- 5 files changed, 36 insertions(+), 44 deletions(-) diff --git a/nova/rpc.py b/nova/rpc.py index 6a634a4eca..b5df4904b1 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -237,7 +237,9 @@ class DirectPublisher(Publisher): def msg_reply(msg_id, reply=None, failure=None): """Sends a reply or an error on the channel signified by msg_id - failure should be a twisted failure object""" + failure should be a sys.exc_info() tuple. + + """ if failure: message = str(failure[1]) tb = traceback.format_exception(*failure) @@ -332,7 +334,7 @@ def call(context, topic, msg): pass consumer.close() # NOTE(termie): this is a little bit of a change from the original - # twisted-based code where returning a Failure + # non-eventlet code where returning a Failure # instance from a deferred call is very similar to # raising an exception if isinstance(wait_msg.result, Exception): diff --git a/nova/service.py b/nova/service.py index 576cf8b85a..5c171b2ae8 100644 --- a/nova/service.py +++ b/nova/service.py @@ -258,8 +258,6 @@ def serve(*services): # FLAGS.logfile = '%s.log' % name #if not FLAGS.prefix: # FLAGS.prefix = name - #elif FLAGS.prefix.endswith('twisted'): - # FLAGS.prefix = FLAGS.prefix.replace('twisted', name) action = 'start' if len(argv) > 1: diff --git a/nova/virt/images.py b/nova/virt/images.py index 4d7c65f126..1c9b2e0934 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -50,7 +50,7 @@ def _fetch_s3_image(image, path, user, project): # This should probably move somewhere else, like e.g. a download_as # method on User objects and at the same time get rewritten to use - # twisted web client. + # a web client. headers = {} headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) diff --git a/nova/virt/xenapi/network_utils.py b/nova/virt/xenapi/network_utils.py index 8cb4cce3a7..d8632f3939 100644 --- a/nova/virt/xenapi/network_utils.py +++ b/nova/virt/xenapi/network_utils.py @@ -20,8 +20,6 @@ records and their attributes like bridges, PIFs, QoS, as well as their lookup functions. """ -from twisted.internet import defer - class NetworkHelper(): """ @@ -31,14 +29,13 @@ class NetworkHelper(): return @classmethod - @defer.inlineCallbacks def find_network_with_bridge(cls, session, bridge): """ Return the network on which the bridge is attached, if found """ expr = 'field "bridge" = "%s"' % bridge - networks = yield session.call_xenapi('network.get_all_records_where', - expr) + networks = session.call_xenapi('network.get_all_records_where', + expr) if len(networks) == 1: - defer.returnValue(networks.keys()[0]) + return networks.keys()[0] elif len(networks) > 1: raise Exception('Found non-unique network for bridge %s' % bridge) else: diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index d36cdaea5b..0223e512a6 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -20,8 +20,6 @@ Management class for VM-related functions (spawn, reboot, etc). import logging -from twisted.internet import defer - from nova import db from nova import context from nova.auth.manager import AuthManager @@ -46,10 +44,9 @@ class VMOps(object): return [self._session.get_xenapi().VM.get_name_label(vm) \ for vm in self._session.get_xenapi().VM.get_all()] - @defer.inlineCallbacks def spawn(self, instance): """ Create VM instance """ - vm = yield VMHelper.lookup(self._session, instance.name) + vm = VMHelper.lookup(self._session, instance.name) if vm is not None: raise Exception('Attempted to create non-unique name %s' % instance.name) @@ -57,66 +54,64 @@ class VMOps(object): bridge = db.project_get_network(context.get_admin_context(), instance.project_id).bridge network_ref = \ - yield NetworkHelper.find_network_with_bridge(self._session, bridge) + NetworkHelper.find_network_with_bridge(self._session, bridge) user = AuthManager().get_user(instance.user_id) project = AuthManager().get_project(instance.project_id) - vdi_uuid = yield VMHelper.fetch_image(self._session, - instance.image_id, user, project, True) - kernel = yield VMHelper.fetch_image(self._session, - instance.kernel_id, user, project, False) - ramdisk = yield VMHelper.fetch_image(self._session, - instance.ramdisk_id, user, project, False) - vdi_ref = yield self._session.call_xenapi('VDI.get_by_uuid', vdi_uuid) - vm_ref = yield VMHelper.create_vm(self._session, - instance, kernel, ramdisk) - yield VMHelper.create_vbd(self._session, vm_ref, vdi_ref, 0, True) + vdi_uuid = VMHelper.fetch_image( + self._session, instance.image_id, user, project, True) + kernel = VMHelper.fetch_image( + self._session, instance.kernel_id, user, project, False) + ramdisk = VMHelper.fetch_image( + self._session, instance.ramdisk_id, user, project, False) + vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', vdi_uuid) + vm_ref = VMHelper.create_vm( + self._session, instance, kernel, ramdisk) + VMHelper.create_vbd(self._session, vm_ref, vdi_ref, 0, True) if network_ref: - yield VMHelper.create_vif(self._session, vm_ref, - network_ref, instance.mac_address) + VMHelper.create_vif(self._session, vm_ref, + network_ref, instance.mac_address) logging.debug('Starting VM %s...', vm_ref) - yield self._session.call_xenapi('VM.start', vm_ref, False, False) + self._session.call_xenapi('VM.start', vm_ref, False, False) logging.info('Spawning VM %s created %s.', instance.name, vm_ref) - @defer.inlineCallbacks def reboot(self, instance): """ Reboot VM instance """ instance_name = instance.name - vm = yield VMHelper.lookup(self._session, instance_name) + vm = VMHelper.lookup(self._session, instance_name) if vm is None: raise Exception('instance not present %s' % instance_name) - task = yield self._session.call_xenapi('Async.VM.clean_reboot', vm) - yield self._session.wait_for_task(task) + task = self._session.call_xenapi('Async.VM.clean_reboot', vm) + self._session.wait_for_task(task) - @defer.inlineCallbacks def destroy(self, instance): """ Destroy VM instance """ - vm = yield VMHelper.lookup(self._session, instance.name) + vm = VMHelper.lookup(self._session, instance.name) if vm is None: # Don't complain, just return. This lets us clean up instances # that have already disappeared from the underlying platform. - defer.returnValue(None) + return # Get the VDIs related to the VM - vdis = yield VMHelper.lookup_vm_vdis(self._session, vm) + vdis = VMHelper.lookup_vm_vdis(self._session, vm) try: - task = yield self._session.call_xenapi('Async.VM.hard_shutdown', + task = self._session.call_xenapi('Async.VM.hard_shutdown', vm) - yield self._session.wait_for_task(task) + self._session.wait_for_task(task) except XenAPI.Failure, exc: logging.warn(exc) # Disk clean-up if vdis: for vdi in vdis: try: - task = yield self._session.call_xenapi('Async.VDI.destroy', - vdi) - yield self._session.wait_for_task(task) + task = self._session.call_xenapi('Async.VDI.destroy', + vdi) + self._session.wait_for_task(task) except XenAPI.Failure, exc: logging.warn(exc) try: - task = yield self._session.call_xenapi('Async.VM.destroy', vm) - yield self._session.wait_for_task(task) + task = self._session.call_xenapi('Async.VM.destroy', vm) + self._session.wait_for_task(task) except XenAPI.Failure, exc: logging.warn(exc) From 4d06429290a373ae3a42b1f9b58d7253d269e048 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 9 Dec 2010 16:45:51 -0800 Subject: [PATCH 114/229] add back utils.default_flagflie --- bin/nova-api | 1 + bin/nova-combined | 2 ++ bin/nova-compute | 2 ++ bin/nova-network | 2 ++ bin/nova-scheduler | 2 ++ bin/nova-volume | 2 ++ 6 files changed, 11 insertions(+) diff --git a/bin/nova-api b/bin/nova-api index 3505cefe7b..210486666f 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -52,4 +52,5 @@ def main(): if __name__ == '__main__': + utils.default_flagfile() main() diff --git a/bin/nova-combined b/bin/nova-combined index 964ffd4384..e82b9c9b6f 100755 --- a/bin/nova-combined +++ b/bin/nova-combined @@ -36,6 +36,7 @@ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): from nova import api from nova import flags from nova import service +from nova import utils from nova import wsgi @@ -50,6 +51,7 @@ flags.DEFINE_string('ec2api_host', '0.0.0.0', 'EC2 API host') if __name__ == '__main__': + utils.default_flagfile() FLAGS(sys.argv) compute = service.Service.create(binary='nova-compute') diff --git a/bin/nova-compute b/bin/nova-compute index f224f0690d..8b02cd0f54 100755 --- a/bin/nova-compute +++ b/bin/nova-compute @@ -34,7 +34,9 @@ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): sys.path.insert(0, possible_topdir) from nova import service +from nova import utils if __name__ == '__main__': + utils.default_flagfile() service.serve() service.wait() diff --git a/bin/nova-network b/bin/nova-network index 589c75e5ad..01cc9d00db 100755 --- a/bin/nova-network +++ b/bin/nova-network @@ -34,7 +34,9 @@ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): sys.path.insert(0, possible_topdir) from nova import service +from nova import utils if __name__ == '__main__': + utils.default_flagfile() service.serve() service.wait() diff --git a/bin/nova-scheduler b/bin/nova-scheduler index cb345aa3f4..a46dd8dda2 100755 --- a/bin/nova-scheduler +++ b/bin/nova-scheduler @@ -34,7 +34,9 @@ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): sys.path.insert(0, possible_topdir) from nova import service +from nova import utils if __name__ == '__main__': + utils.default_flagfile() service.serve() service.wait() diff --git a/bin/nova-volume b/bin/nova-volume index 2f74f4b9ab..efb837ba21 100755 --- a/bin/nova-volume +++ b/bin/nova-volume @@ -34,7 +34,9 @@ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): sys.path.insert(0, possible_topdir) from nova import service +from nova import utils if __name__ == '__main__': + utils.default_flagfile() service.serve() service.wait() From 1d22b67c9dc0e01c8e4afb66fbc6c9be8f843974 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 10 Dec 2010 16:28:23 +0000 Subject: [PATCH 115/229] added unittest for pause --- nova/tests/compute_unittest.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py index 6f3ef96cbb..ad191795ca 100644 --- a/nova/tests/compute_unittest.py +++ b/nova/tests/compute_unittest.py @@ -138,6 +138,15 @@ class ComputeTestCase(test.TrialTestCase): yield self.compute.reboot_instance(self.context, instance_id) yield self.compute.terminate_instance(self.context, instance_id) + @defer.inlineCallbacks + def test_pause(self): + """Ensure instance can be paused""" + instance_id = self._create_instance() + yield self.compute.run_instance(self.context, instance_id) + yield self.compute.pause_instance(self.context, instance_id) + yield self.compute.unpause_instance(self.context, instance_id) + yield self.compute.terminate_instance(self.context, instance_id) + @defer.inlineCallbacks def test_console_output(self): """Make sure we can get console output from instance""" From f7862f6d212d52e09d2a3a076762c936618cf061 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Fri, 10 Dec 2010 17:55:21 +0000 Subject: [PATCH 116/229] added pause and unpause to fake connection --- nova/virt/fake.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/nova/virt/fake.py b/nova/virt/fake.py index f855523d36..4526f00426 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -133,6 +133,18 @@ class FakeConnection(object): """ return defer.succeed(None) + def pause(self, instance): + """ + Pause the specified instance. + """ + return defer.succeed(None) + + def unpause(self, instance): + """ + Unpause the specified instance. + """ + return defer.succeed(None) + def destroy(self, instance): """ Destroy (shutdown and delete) the specified instance. From 2a5ad56319dfdf75bf2eab1337032f035822f272 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Fri, 10 Dec 2010 18:37:17 +0000 Subject: [PATCH 117/229] There is always the odd change that one forgets! --- nova/virt/xenapi_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index b82862764a..4ace6da148 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -65,7 +65,7 @@ from nova.virt.xenapi.volumeops import VolumeOps FLAGS = flags.FLAGS flags.DEFINE_boolean('xenapi_use_fake_session', - True, + False, 'Set to true in order to use the fake XenAPI SDK') flags.DEFINE_string('xenapi_connection_url', None, From 8d08206cb4759328e7cf3b836eeff824e0d22052 Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Fri, 10 Dec 2010 18:49:54 +0000 Subject: [PATCH 118/229] Format fixes and modification of Vish's email address. --- nova/auth/ldapdriver.py | 41 ++++++++++++++++++---------------- nova/auth/nova_openldap.schema | 2 +- nova/auth/nova_sun.schema | 2 +- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index 870262a156..1b928e7d8e 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -125,7 +125,7 @@ class LdapDriver(object): def get_users(self): """Retrieve list of users""" attrs = self.__find_objects(FLAGS.ldap_user_subtree, - '(objectclass=novaUser)') + '(objectclass=novaUser)') users = [] for attr in attrs: user = self.__to_user(attr) @@ -155,22 +155,24 @@ class LdapDriver(object): attr = [] if 'secretKey' in user.keys(): attr.append((self.ldap.MOD_REPLACE, 'secretKey', - [secret_key])) + [secret_key])) else: attr.append((self.ldap.MOD_ADD, 'secretKey', - [secret_key])) + [secret_key])) if 'accessKey' in user.keys(): attr.append((self.ldap.MOD_REPLACE, 'accessKey', - [access_key])) + [access_key])) else: attr.append((self.ldap.MOD_ADD, 'accessKey', - [access_key])) + [access_key])) if LdapDriver.isadmin_attribute in user.keys(): attr.append((self.ldap.MOD_REPLACE, - LdapDriver.isadmin_attribute, [str(is_admin).upper()])) + LdapDriver.isadmin_attribute, + [str(is_admin).upper()])) else: attr.append((self.ldap.MOD_ADD, - LdapDriver.isadmin_attribute, [str(is_admin).upper()])) + LdapDriver.isadmin_attribute, + [str(is_admin).upper()])) self.conn.modify_s(self.__uid_to_dn(name), attr) return self.get_user(name) else: @@ -299,7 +301,7 @@ class LdapDriver(object): else: project_dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) query = ('(&(&(objectclass=groupOfNames)(!%s))(member=%s))' % - (LdapDriver.project_pattern, self.__uid_to_dn(uid))) + (LdapDriver.project_pattern, self.__uid_to_dn(uid))) roles = self.__find_objects(project_dn, query) return [role['cn'][0] for role in roles] @@ -363,7 +365,7 @@ class LdapDriver(object): def __get_ldap_user(self, uid): """Retrieve LDAP user entry by id""" attr = self.__find_object(self.__uid_to_dn(uid), - '(objectclass=novaUser)') + '(objectclass=novaUser)') return attr def __find_object(self, dn, query=None, scope=None): @@ -406,7 +408,7 @@ class LdapDriver(object): def __find_group_dns_with_member(self, tree, uid): """Find dns of group objects in a given tree that contain member""" query = ('(&(objectclass=groupOfNames)(member=%s))' % - self.__uid_to_dn(uid)) + self.__uid_to_dn(uid)) dns = self.__find_dns(tree, query) return dns @@ -436,7 +438,8 @@ class LdapDriver(object): for member_uid in member_uids: if not self.__user_exists(member_uid): raise exception.NotFound("Group can't be created " - "because user %s doesn't exist" % member_uid) + "because user %s doesn't exist" % + member_uid) members.append(self.__uid_to_dn(member_uid)) dn = self.__uid_to_dn(uid) if not dn in members: @@ -452,7 +455,7 @@ class LdapDriver(object): """Check if user is in group""" if not self.__user_exists(uid): raise exception.NotFound("User %s can't be searched in group " - "because the user doesn't exist" % uid) + "because the user doesn't exist" % uid) if not self.__group_exists(group_dn): return False res = self.__find_object(group_dn, @@ -464,7 +467,7 @@ class LdapDriver(object): """Add user to group""" if not self.__user_exists(uid): raise exception.NotFound("User %s can't be added to the group " - "because the user doesn't exist" % uid) + "because the user doesn't exist" % uid) if not self.__group_exists(group_dn): raise exception.NotFound("The group at dn %s doesn't exist" % group_dn) @@ -481,13 +484,13 @@ class LdapDriver(object): group_dn) if not self.__user_exists(uid): raise exception.NotFound("User %s can't be removed from the " - "group because the user doesn't exist" % uid) + "group because the user doesn't exist" % + uid) if not self.__is_in_group(uid, group_dn): raise exception.NotFound("User %s is not a member of the group" % uid) # NOTE(vish): remove user from group and any sub_groups - sub_dns = self.__find_group_dns_with_member( - group_dn, uid) + sub_dns = self.__find_group_dns_with_member(group_dn, uid) for sub_dn in sub_dns: self.__safe_remove_from_group(uid, sub_dn) @@ -506,7 +509,7 @@ class LdapDriver(object): """Remove user from all roles and projects""" if not self.__user_exists(uid): raise exception.NotFound("User %s can't be removed from all " - "because the user doesn't exist" % uid) + "because the user doesn't exist" % uid) role_dns = self.__find_group_dns_with_member( FLAGS.role_project_subtree, uid) for role_dn in role_dns: @@ -564,8 +567,8 @@ class LdapDriver(object): @staticmethod def __uid_to_dn(uid): """Convert uid to dn""" - return FLAGS.ldap_user_id_attribute + '=%s,%s' \ - % (uid, FLAGS.ldap_user_subtree) + return (FLAGS.ldap_user_id_attribute + '=%s,%s' + % (uid, FLAGS.ldap_user_subtree)) class FakeLdapDriver(LdapDriver): diff --git a/nova/auth/nova_openldap.schema b/nova/auth/nova_openldap.schema index daa3a8442c..539a5c42dd 100644 --- a/nova/auth/nova_openldap.schema +++ b/nova/auth/nova_openldap.schema @@ -2,7 +2,7 @@ # Person object for Nova # inetorgperson with extra attributes # Schema version: 2 -# Authors: Vishvananda Ishaya +# Authors: Vishvananda Ishaya # Ryan Lane # # diff --git a/nova/auth/nova_sun.schema b/nova/auth/nova_sun.schema index 8e9052ded8..4a6a788396 100644 --- a/nova/auth/nova_sun.schema +++ b/nova/auth/nova_sun.schema @@ -2,7 +2,7 @@ # Person object for Nova # inetorgperson with extra attributes # Schema version: 2 -# Authors: Vishvananda Ishaya +# Authors: Vishvananda Ishaya # Ryan Lane # # using internet experimental oid arc as per BP64 3.1 From c835c441981a17764931390bc1ace6121ab100a4 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Fri, 10 Dec 2010 11:53:17 -0800 Subject: [PATCH 119/229] port new patches --- nova/virt/xenapi/vmops.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 13871b4790..b6b92b9264 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -126,14 +126,13 @@ 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 = yield VMHelper.lookup(self._session, instance_id) + vm = VMHelper.lookup(self._session, instance_id) if vm is None: raise Exception("instance not present %s" % instance_id) - rec = yield self._session.get_xenapi().VM.get_record(vm) - defer.returnValue(VMHelper.compile_diagnostics(self._session, rec)) + rec = self._session.get_xenapi().VM.get_record(vm) + return VMHelper.compile_diagnostics(self._session, rec) def get_console_output(self, instance): """ Return snapshot of console """ From 68dbbbba34af234f2770b40c03e4e4bfa5ad78d8 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 10 Dec 2010 21:09:37 +0000 Subject: [PATCH 120/229] Includes kernel and ramdisk on register. Additinally removes a couple lines of cruft --- nova/objectstore/image.py | 48 ++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/nova/objectstore/image.py b/nova/objectstore/image.py index 7292dbab8f..ed0f75cd27 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' - try: - kernel_id = manifest.find("machine_configuration/kernel_id").text - if kernel_id == 'true': - image_type = 'kernel' - except: - kernel_id = None - - try: - ramdisk_id = manifest.find("machine_configuration/ramdisk_id").text - if ramdisk_id == 'true': - image_type = 'ramdisk' - 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} + 'architecture': 'x86_64', + 'imageType': 'machine' + } - if kernel_id: - info['kernelId'] = kernel_id + manifest = ElementTree.fromstring(bucket_object[manifest_path].read()) - if ramdisk_id: - info['ramdiskId'] = ramdisk_id + 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': + info['imageType'] = 'kernel' + else: + info['kernelId'] = kernel_id + except: + pass + try: + ramdisk_id = manifest.find("machine_configuration/ramdisk_id").text + if ramdisk_id == 'true': + info['imageType'] = 'ramdisk' + else: + info['ramdiskId'] = ramdisk_id + except: + pass def write_state(state): info['imageState'] = state From f09b008388b9ed8dbd1d3f74cb1e9f2a458a3000 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 10 Dec 2010 21:15:33 +0000 Subject: [PATCH 121/229] 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 ed0f75cd27..c4d752124c 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: From d60ed93a22b5cfb7dcaad2882b28a5a37c797af9 Mon Sep 17 00:00:00 2001 From: Anne Gentle Date: Fri, 10 Dec 2010 16:03:30 -0600 Subject: [PATCH 122/229] 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 dd01b20602..b9ba6208a4 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 From a6645d8a431ed933eef4ea6c42c0224ead6f2272 Mon Sep 17 00:00:00 2001 From: "jaypipes@gmail.com" <> Date: Sat, 11 Dec 2010 15:10:24 -0500 Subject: [PATCH 123/229] 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 a9c53dbcdf..2ae6a099af 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 ac6378f754..f57b68584b 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 17c62da0a0..81b9b6dd3f 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 aaf213923e..8dc87d0e2d 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 3d427d8af2..37a548e4ce 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 From 12802a76c775a35e9d5a651bf896cfa25bec547f Mon Sep 17 00:00:00 2001 From: "jaypipes@gmail.com" <> Date: Sat, 11 Dec 2010 15:23:40 -0500 Subject: [PATCH 124/229] First round of i18n-ifying strings in Nova --- nova/api/cloudpipe/__init__.py | 4 +- nova/api/ec2/__init__.py | 6 +-- nova/api/ec2/apirequest.py | 4 +- nova/api/ec2/cloud.py | 51 ++++++++++--------- nova/api/ec2/metadatarequesthandler.py | 2 +- nova/api/openstack/__init__.py | 6 +-- nova/auth/dbdriver.py | 20 ++++---- nova/auth/fakeldap.py | 2 +- nova/auth/ldapdriver.py | 69 ++++++++++++++------------ nova/auth/manager.py | 30 +++++------ nova/cloudpipe/pipelib.py | 2 +- nova/compute/api.py | 12 ++--- nova/compute/disk.py | 16 +++--- nova/compute/instance_types.py | 2 +- nova/compute/manager.py | 32 ++++++------ nova/compute/monitor.py | 12 ++--- nova/crypto.py | 18 +++---- nova/db/sqlalchemy/api.py | 48 +++++++++--------- nova/exception.py | 8 +-- nova/fakerabbit.py | 12 ++--- nova/image/glance.py | 8 +-- nova/image/s3.py | 3 +- nova/network/linux_net.py | 10 ++-- nova/network/manager.py | 17 ++++--- nova/objectstore/handler.py | 20 ++++---- nova/process.py | 2 +- nova/rpc.py | 36 +++++++------- nova/scheduler/chance.py | 2 +- nova/scheduler/driver.py | 2 +- nova/scheduler/manager.py | 2 +- nova/scheduler/simple.py | 13 ++--- nova/server.py | 4 +- nova/service.py | 12 ++--- nova/twistd.py | 6 +-- nova/utils.py | 14 +++--- nova/validate.py | 12 ++--- 36 files changed, 267 insertions(+), 252 deletions(-) diff --git a/nova/api/cloudpipe/__init__.py b/nova/api/cloudpipe/__init__.py index 6d40990a88..00ad389134 100644 --- a/nova/api/cloudpipe/__init__.py +++ b/nova/api/cloudpipe/__init__.py @@ -45,7 +45,7 @@ class API(wsgi.Application): def __call__(self, req): if req.method == 'POST': return self.sign_csr(req) - _log.debug("Cloudpipe path is %s" % req.path_info) + _log.debug(_("Cloudpipe path is %s") % req.path_info) if req.path_info.endswith("/getca/"): return self.send_root_ca(req) return webob.exc.HTTPNotFound() @@ -56,7 +56,7 @@ class API(wsgi.Application): return instance['project_id'] def send_root_ca(self, req): - _log.debug("Getting root ca") + _log.debug(_("Getting root ca")) project_id = self.get_project_id_from_ip(req.remote_addr) res = webob.Response() res.headers["Content-Type"] = "text/plain" diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index a6ee16c332..dd87d1f716 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -77,7 +77,7 @@ class Authenticate(wsgi.Middleware): req.host, req.path) except exception.Error, ex: - logging.debug("Authentication Failure: %s" % ex) + logging.debug(_("Authentication Failure: %s") % ex) raise webob.exc.HTTPForbidden() # Authenticated! @@ -120,9 +120,9 @@ class Router(wsgi.Middleware): except: raise webob.exc.HTTPBadRequest() - _log.debug('action: %s' % action) + _log.debug(_('action: %s') % action) for key, value in args.items(): - _log.debug('arg: %s\t\tval: %s' % (key, value)) + _log.debug(_('arg: %s\t\tval: %s') % (key, value)) # Success! req.environ['ec2.controller'] = controller diff --git a/nova/api/ec2/apirequest.py b/nova/api/ec2/apirequest.py index 5758781b69..a90fbeb0c2 100644 --- a/nova/api/ec2/apirequest.py +++ b/nova/api/ec2/apirequest.py @@ -92,8 +92,8 @@ class APIRequest(object): method = getattr(self.controller, _camelcase_to_underscore(self.action)) except AttributeError: - _error = ('Unsupported API request: controller = %s,' - 'action = %s') % (self.controller, self.action) + _error = _('Unsupported API request: controller = %s,' + 'action = %s') % (self.controller, self.action) _log.warning(_error) # TODO: Raise custom exception, trap in apiserver, # and reraise as 400 error. diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 05f8c3d0b0..896e6c2239 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -114,7 +114,7 @@ class CloudController(object): start = os.getcwd() os.chdir(FLAGS.ca_path) # TODO(vish): Do this with M2Crypto instead - utils.runthis("Generating root CA: %s", "sh genrootca.sh") + utils.runthis(_("Generating root CA: %s", "sh genrootca.sh")) os.chdir(start) def _get_mpi_data(self, context, project_id): @@ -318,11 +318,11 @@ class CloudController(object): ip_protocol = str(ip_protocol) if ip_protocol.upper() not in ['TCP', 'UDP', 'ICMP']: - raise InvalidInputException('%s is not a valid ipProtocol' % + raise InvalidInputException(_('%s is not a valid ipProtocol') % (ip_protocol,)) if ((min(from_port, to_port) < -1) or (max(from_port, to_port) > 65535)): - raise InvalidInputException('Invalid port range') + raise InvalidInputException(_('Invalid port range')) values['protocol'] = ip_protocol values['from_port'] = from_port @@ -360,7 +360,7 @@ class CloudController(object): criteria = self._revoke_rule_args_to_dict(context, **kwargs) if criteria == None: - raise exception.ApiError("No rule for the specified parameters.") + raise exception.ApiError(_("No rule for the specified parameters.")) for rule in security_group.rules: match = True @@ -371,7 +371,7 @@ class CloudController(object): db.security_group_rule_destroy(context, rule['id']) self._trigger_refresh_security_group(context, security_group) return True - raise exception.ApiError("No rule for the specified parameters.") + raise exception.ApiError(_("No rule for the specified parameters.")) # TODO(soren): This has only been tested with Boto as the client. # Unfortunately, it seems Boto is using an old API @@ -387,8 +387,8 @@ class CloudController(object): values['parent_group_id'] = security_group.id if self._security_group_rule_exists(security_group, values): - raise exception.ApiError('This rule already exists in group %s' % - group_name) + raise exception.ApiError(_('This rule already exists in group %s') + % group_name) security_group_rule = db.security_group_rule_create(context, values) @@ -416,7 +416,7 @@ class CloudController(object): def create_security_group(self, context, group_name, group_description): self.compute_api.ensure_default_security_group(context) if db.security_group_exists(context, context.project_id, group_name): - raise exception.ApiError('group %s already exists' % group_name) + raise exception.ApiError(_('group %s already exists') % group_name) group = {'user_id': context.user.id, 'project_id': context.project_id, @@ -527,13 +527,13 @@ class CloudController(object): def attach_volume(self, context, volume_id, instance_id, device, **kwargs): volume_ref = db.volume_get_by_ec2_id(context, volume_id) if not re.match("^/dev/[a-z]d[a-z]+$", device): - raise exception.ApiError("Invalid device specified: %s. " - "Example device: /dev/vdb" % device) + raise exception.ApiError(_("Invalid device specified: %s. " + "Example device: /dev/vdb") % device) # TODO(vish): abstract status checking? if volume_ref['status'] != "available": - raise exception.ApiError("Volume status must be available") + raise exception.ApiError(_("Volume status must be available")) if volume_ref['attach_status'] == "attached": - raise exception.ApiError("Volume is already attached") + raise exception.ApiError(_("Volume is already attached")) internal_id = ec2_id_to_internal_id(instance_id) instance_ref = self.compute_api.get_instance(context, internal_id) host = instance_ref['host'] @@ -555,10 +555,10 @@ class CloudController(object): instance_ref = db.volume_get_instance(context.elevated(), volume_ref['id']) if not instance_ref: - raise exception.ApiError("Volume isn't attached to anything!") + raise exception.ApiError(_("Volume isn't attached to anything!")) # TODO(vish): abstract status checking? if volume_ref['status'] == "available": - raise exception.ApiError("Volume is already detached") + raise exception.ApiError(_("Volume is already detached")) try: host = instance_ref['host'] rpc.cast(context, @@ -687,10 +687,11 @@ class CloudController(object): def allocate_address(self, context, **kwargs): # check quota if quota.allowed_floating_ips(context, 1) < 1: - logging.warn("Quota exceeeded for %s, tried to allocate address", + logging.warn(_("Quota exceeeded for %s, tried to allocate " + "address"), context.project_id) - raise quota.QuotaError("Address quota exceeded. You cannot " - "allocate any more addresses") + raise quota.QuotaError(_("Address quota exceeded. You cannot " + "allocate any more addresses")) network_topic = self._get_network_topic(context) public_ip = rpc.call(context, network_topic, @@ -803,7 +804,7 @@ class CloudController(object): # TODO: return error if not authorized volume_ref = db.volume_get_by_ec2_id(context, volume_id) if volume_ref['status'] != "available": - raise exception.ApiError("Volume status must be available") + raise exception.ApiError(_("Volume status must be available")) now = datetime.datetime.utcnow() db.volume_update(context, volume_ref['id'], {'status': 'deleting', 'terminated_at': now}) @@ -834,11 +835,12 @@ class CloudController(object): def describe_image_attribute(self, context, image_id, attribute, **kwargs): if attribute != 'launchPermission': - raise exception.ApiError('attribute not supported: %s' % attribute) + raise exception.ApiError(_('attribute not supported: %s') + % attribute) try: image = self.image_service.show(context, image_id) except IndexError: - raise exception.ApiError('invalid id: %s' % image_id) + raise exception.ApiError(_('invalid id: %s') % image_id) result = {'image_id': image_id, 'launchPermission': []} if image['isPublic']: result['launchPermission'].append({'group': 'all'}) @@ -848,13 +850,14 @@ class CloudController(object): operation_type, **kwargs): # TODO(devcamcar): Support users and groups other than 'all'. if attribute != 'launchPermission': - raise exception.ApiError('attribute not supported: %s' % attribute) + raise exception.ApiError(_('attribute not supported: %s') + % attribute) if not 'user_group' in kwargs: - raise exception.ApiError('user or group not specified') + raise exception.ApiError(_('user or group not specified')) if len(kwargs['user_group']) != 1 and kwargs['user_group'][0] != 'all': - raise exception.ApiError('only group "all" is supported') + raise exception.ApiError(_('only group "all" is supported')) if not operation_type in ['add', 'remove']: - raise exception.ApiError('operation_type must be add or remove') + raise exception.ApiError(_('operation_type must be add or remove')) return self.image_service.modify(context, image_id, operation_type) def update_image(self, context, image_id, **kwargs): diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py index 2f4f414ccb..0e9e686ff4 100644 --- a/nova/api/ec2/metadatarequesthandler.py +++ b/nova/api/ec2/metadatarequesthandler.py @@ -65,7 +65,7 @@ class MetadataRequestHandler(object): cc = cloud.CloudController() meta_data = cc.get_metadata(req.remote_addr) if meta_data is None: - logging.error('Failed to get metadata for ip: %s' % + logging.error(_('Failed to get metadata for ip: %s') % req.remote_addr) raise webob.exc.HTTPNotFound() data = self.lookup(req.path_info, meta_data) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index c9efe52226..45a2549c0b 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -65,7 +65,7 @@ class API(wsgi.Middleware): try: return req.get_response(self.application) except Exception as ex: - logging.warn("Caught error: %s" % str(ex)) + logging.warn(_("Caught error: %s") % str(ex)) logging.debug(traceback.format_exc()) exc = webob.exc.HTTPInternalServerError(explanation=str(ex)) return faults.Fault(exc) @@ -134,7 +134,7 @@ class RateLimitingMiddleware(wsgi.Middleware): if delay: # TODO(gundlach): Get the retry-after format correct. exc = webob.exc.HTTPRequestEntityTooLarge( - explanation='Too many requests.', + explanation=_('Too many requests.'), headers={'Retry-After': time.time() + delay}) raise faults.Fault(exc) return self.application @@ -188,7 +188,7 @@ class APIRouter(wsgi.Router): controller=sharedipgroups.Controller()) if FLAGS.allow_admin_api: - logging.debug("Including admin operations in API.") + logging.debug(_("Including admin operations in API.")) # TODO: Place routes for admin operations here. super(APIRouter, self).__init__(mapper) diff --git a/nova/auth/dbdriver.py b/nova/auth/dbdriver.py index a1584322b8..47e435cb68 100644 --- a/nova/auth/dbdriver.py +++ b/nova/auth/dbdriver.py @@ -37,7 +37,6 @@ class DbDriver(object): def __init__(self): """Imports the LDAP module""" pass - db def __enter__(self): return self @@ -83,7 +82,7 @@ class DbDriver(object): user_ref = db.user_create(context.get_admin_context(), values) return self._db_user_to_auth_user(user_ref) except exception.Duplicate, e: - raise exception.Duplicate('User %s already exists' % name) + raise exception.Duplicate(_('User %s already exists') % name) def _db_user_to_auth_user(self, user_ref): return {'id': user_ref['id'], @@ -105,8 +104,9 @@ class DbDriver(object): """Create a project""" manager = db.user_get(context.get_admin_context(), manager_uid) if not manager: - raise exception.NotFound("Project can't be created because " - "manager %s doesn't exist" % manager_uid) + raise exception.NotFound(_("Project can't be created because " + "manager %s doesn't exist") + % manager_uid) # description is a required attribute if description is None: @@ -133,8 +133,8 @@ class DbDriver(object): try: project = db.project_create(context.get_admin_context(), values) except exception.Duplicate: - raise exception.Duplicate("Project can't be created because " - "project %s already exists" % name) + raise exception.Duplicate(_("Project can't be created because " + "project %s already exists") % name) for member in members: db.project_add_member(context.get_admin_context(), @@ -155,8 +155,8 @@ class DbDriver(object): if manager_uid: manager = db.user_get(context.get_admin_context(), manager_uid) if not manager: - raise exception.NotFound("Project can't be modified because " - "manager %s doesn't exist" % + raise exception.NotFound(_("Project can't be modified because " + "manager %s doesn't exist") % manager_uid) values['project_manager'] = manager['id'] if description: @@ -243,8 +243,8 @@ class DbDriver(object): def _validate_user_and_project(self, user_id, project_id): user = db.user_get(context.get_admin_context(), user_id) if not user: - raise exception.NotFound('User "%s" not found' % user_id) + raise exception.NotFound(_('User "%s" not found') % user_id) project = db.project_get(context.get_admin_context(), project_id) if not project: - raise exception.NotFound('Project "%s" not found' % project_id) + raise exception.NotFound(_('Project "%s" not found') % project_id) return user, project diff --git a/nova/auth/fakeldap.py b/nova/auth/fakeldap.py index 46e0135b4c..cdab96b796 100644 --- a/nova/auth/fakeldap.py +++ b/nova/auth/fakeldap.py @@ -39,7 +39,7 @@ flags.DEFINE_integer('redis_db', 0, 'Multiple DB keeps tests away') class Redis(object): def __init__(self): if hasattr(self.__class__, '_instance'): - raise Exception('Attempted to instantiate singleton') + raise Exception(_('Attempted to instantiate singleton')) @classmethod def instance(cls): diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index c10939d743..e289ea5a26 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("LDAP object for %s doesn't exist" + raise exception.NotFound(_("LDAP object for %s doesn't exist") % name) else: attr = [ @@ -182,11 +182,12 @@ class LdapDriver(object): description=None, member_uids=None): """Create a project""" if self.__project_exists(name): - raise exception.Duplicate("Project can't be created because " - "project %s already exists" % name) + raise exception.Duplicate(_("Project can't be created because " + "project %s already exists") % name) if not self.__user_exists(manager_uid): - raise exception.NotFound("Project can't be created because " - "manager %s doesn't exist" % manager_uid) + raise exception.NotFound(_("Project can't be created because " + "manager %s doesn't exist") + % manager_uid) manager_dn = self.__uid_to_dn(manager_uid) # description is a required attribute if description is None: @@ -195,8 +196,8 @@ class LdapDriver(object): if member_uids is not None: for member_uid in member_uids: if not self.__user_exists(member_uid): - raise exception.NotFound("Project can't be created " - "because user %s doesn't exist" + raise exception.NotFound(_("Project can't be created " + "because user %s doesn't exist") % member_uid) members.append(self.__uid_to_dn(member_uid)) # always add the manager as a member because members is required @@ -218,9 +219,9 @@ class LdapDriver(object): attr = [] if manager_uid: if not self.__user_exists(manager_uid): - raise exception.NotFound("Project can't be modified because " - "manager %s doesn't exist" % - manager_uid) + raise exception.NotFound(_("Project can't be modified because " + "manager %s doesn't exist") + % manager_uid) manager_dn = self.__uid_to_dn(manager_uid) attr.append((self.ldap.MOD_REPLACE, 'projectManager', manager_dn)) if description: @@ -416,8 +417,9 @@ class LdapDriver(object): if member_uids is not None: for member_uid in member_uids: if not self.__user_exists(member_uid): - raise exception.NotFound("Group can't be created " - "because user %s doesn't exist" % member_uid) + raise exception.NotFound(_("Group can't be created " + "because user %s doesn't exist") + % member_uid) members.append(self.__uid_to_dn(member_uid)) dn = self.__uid_to_dn(uid) if not dn in members: @@ -432,8 +434,9 @@ class LdapDriver(object): def __is_in_group(self, uid, group_dn): """Check if user is in group""" if not self.__user_exists(uid): - raise exception.NotFound("User %s can't be searched in group " - "becuase the user doesn't exist" % (uid,)) + raise exception.NotFound(_("User %s can't be searched in group " + "because the user doesn't exist") + % uid) if not self.__group_exists(group_dn): return False res = self.__find_object(group_dn, @@ -444,28 +447,30 @@ class LdapDriver(object): def __add_to_group(self, uid, group_dn): """Add user to group""" if not self.__user_exists(uid): - raise exception.NotFound("User %s can't be added to the group " - "becuase the user doesn't exist" % (uid,)) + raise exception.NotFound(_("User %s can't be added to the group " + "because the user doesn't exist") + % uid) if not self.__group_exists(group_dn): - raise exception.NotFound("The group at dn %s doesn't exist" % - (group_dn,)) + raise exception.NotFound(_("The group at dn %s doesn't exist") + % group_dn) if self.__is_in_group(uid, group_dn): - raise exception.Duplicate("User %s is already a member of " - "the group %s" % (uid, group_dn)) + raise exception.Duplicate(_("User %s is already a member of " + "the group %s") % (uid, group_dn)) attr = [(self.ldap.MOD_ADD, 'member', self.__uid_to_dn(uid))] self.conn.modify_s(group_dn, attr) def __remove_from_group(self, uid, group_dn): """Remove user from group""" if not self.__group_exists(group_dn): - raise exception.NotFound("The group at dn %s doesn't exist" % - (group_dn,)) + raise exception.NotFound(_("The group at dn %s doesn't exist") + % group_dn) if not self.__user_exists(uid): - raise exception.NotFound("User %s can't be removed from the " - "group because the user doesn't exist" % (uid,)) + raise exception.NotFound(_("User %s can't be removed from the " + "group because the user doesn't exist") + % uid) if not self.__is_in_group(uid, group_dn): - raise exception.NotFound("User %s is not a member of the group" % - (uid,)) + raise exception.NotFound(_("User %s is not a member of the group") + % uid) # NOTE(vish): remove user from group and any sub_groups sub_dns = self.__find_group_dns_with_member( group_dn, uid) @@ -479,15 +484,16 @@ class LdapDriver(object): try: self.conn.modify_s(group_dn, attr) except self.ldap.OBJECT_CLASS_VIOLATION: - logging.debug("Attempted to remove the last member of a group. " - "Deleting the group at %s instead.", group_dn) + logging.debug(_("Attempted to remove the last member of a group. " + "Deleting the group at %s instead."), group_dn) self.__delete_group(group_dn) def __remove_from_all(self, uid): """Remove user from all roles and projects""" if not self.__user_exists(uid): - raise exception.NotFound("User %s can't be removed from all " - "because the user doesn't exist" % (uid,)) + raise exception.NotFound(_("User %s can't be removed from all " + "because the user doesn't exist") + % uid) role_dns = self.__find_group_dns_with_member( FLAGS.role_project_subtree, uid) for role_dn in role_dns: @@ -500,7 +506,8 @@ class LdapDriver(object): def __delete_group(self, group_dn): """Delete Group""" if not self.__group_exists(group_dn): - raise exception.NotFound("Group at dn %s doesn't exist" % group_dn) + raise exception.NotFound(_("Group at dn %s doesn't exist") + % group_dn) self.conn.delete_s(group_dn) def __delete_roles(self, project_dn): diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 11c3bd6dfb..417f2b76dc 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -257,12 +257,12 @@ class AuthManager(object): # TODO(vish): check for valid timestamp (access_key, _sep, project_id) = access.partition(':') - logging.info('Looking up user: %r', access_key) + logging.info(_('Looking up user: %r'), access_key) user = self.get_user_from_access_key(access_key) logging.info('user: %r', user) if user == None: - raise exception.NotFound('No user found for access key %s' % - access_key) + raise exception.NotFound(_('No user found for access key %s') + % access_key) # NOTE(vish): if we stop using project name as id we need better # logic to find a default project for user @@ -271,12 +271,12 @@ class AuthManager(object): project = self.get_project(project_id) if project == None: - raise exception.NotFound('No project called %s could be found' % - project_id) + raise exception.NotFound(_('No project called %s could be found') + % project_id) if not self.is_admin(user) and not self.is_project_member(user, project): - raise exception.NotFound('User %s is not a member of project %s' % - (user.id, project.id)) + raise exception.NotFound(_('User %s is not a member of project %s') + % (user.id, project.id)) if check_type == 's3': sign = signer.Signer(user.secret.encode()) expected_signature = sign.s3_authorization(headers, verb, path) @@ -284,7 +284,7 @@ class AuthManager(object): logging.debug('expected_signature: %s', expected_signature) logging.debug('signature: %s', signature) if signature != expected_signature: - raise exception.NotAuthorized('Signature does not match') + raise exception.NotAuthorized(_('Signature does not match')) elif check_type == 'ec2': # NOTE(vish): hmac can't handle unicode, so encode ensures that # secret isn't unicode @@ -294,7 +294,7 @@ class AuthManager(object): logging.debug('expected_signature: %s', expected_signature) logging.debug('signature: %s', signature) if signature != expected_signature: - raise exception.NotAuthorized('Signature does not match') + raise exception.NotAuthorized(_('Signature does not match')) return (user, project) def get_access_key(self, user, project): @@ -364,7 +364,7 @@ class AuthManager(object): with self.driver() as drv: if role == 'projectmanager': if not project: - raise exception.Error("Must specify project") + raise exception.Error(_("Must specify project")) return self.is_project_manager(user, project) global_role = drv.has_role(User.safe_id(user), @@ -398,9 +398,9 @@ class AuthManager(object): @param project: Project in which to add local role. """ if role not in FLAGS.allowed_roles: - raise exception.NotFound("The %s role can not be found" % role) + raise exception.NotFound(_("The %s role can not be found") % role) if project is not None and role in FLAGS.global_roles: - raise exception.NotFound("The %s role is global only" % role) + raise exception.NotFound(_("The %s role is global only") % role) with self.driver() as drv: drv.add_role(User.safe_id(user), role, Project.safe_id(project)) @@ -546,7 +546,8 @@ class AuthManager(object): Project.safe_id(project)) if not network_ref['vpn_public_port']: - raise exception.NotFound('project network data has not been set') + raise exception.NotFound(_('project network data has not ' + 'been set')) return (network_ref['vpn_public_address'], network_ref['vpn_public_port']) @@ -659,8 +660,7 @@ class AuthManager(object): port=vpn_port) zippy.writestr(FLAGS.credential_vpn_file, config) else: - logging.warn("No vpn data for project %s" % - pid) + logging.warn(_("No vpn data for project %s"), pid) zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(user.id)) zippy.close() diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py index 3472201cdf..bbe91a70c0 100644 --- a/nova/cloudpipe/pipelib.py +++ b/nova/cloudpipe/pipelib.py @@ -49,7 +49,7 @@ class CloudPipe(object): self.manager = manager.AuthManager() def launch_vpn_instance(self, project_id): - logging.debug("Launching VPN for %s" % (project_id)) + logging.debug(_("Launching VPN for %s") % (project_id)) project = self.manager.get_project(project_id) # Make a payload.zip tmpfolder = tempfile.mkdtemp() diff --git a/nova/compute/api.py b/nova/compute/api.py index 8e0efa4cc7..e701e540ef 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -120,7 +120,7 @@ class ComputeAPI(base.Base): elevated = context.elevated() instances = [] - logging.debug("Going to run %s instances...", num_instances) + logging.debug(_("Going to run %s instances..."), num_instances) for num in range(num_instances): instance = dict(mac_address=utils.generate_mac(), launch_index=num, @@ -157,7 +157,7 @@ class ComputeAPI(base.Base): {"method": "setup_fixed_ip", "args": {"address": address}}) - logging.debug("Casting to scheduler for %s/%s's instance %s", + 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, @@ -204,12 +204,12 @@ class ComputeAPI(base.Base): 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", + 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", + logging.warning(_("Instance %d is already being terminated"), instance_id) return @@ -223,7 +223,7 @@ class ComputeAPI(base.Base): address = self.db.instance_get_floating_address(context, instance['id']) if address: - logging.debug("Disassociating address %s" % 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? @@ -234,7 +234,7 @@ class ComputeAPI(base.Base): address = self.db.instance_get_fixed_address(context, instance['id']) if address: - logging.debug("Deallocating address %s" % 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. diff --git a/nova/compute/disk.py b/nova/compute/disk.py index 4338d39f08..8701c39685 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -70,12 +70,12 @@ def partition(infile, outfile, local_bytes=0, resize=True, yield execute('resize2fs %s' % infile) file_size = FLAGS.minimum_root_size elif file_size % sector_size != 0: - logging.warn("Input partition size not evenly divisible by" - " sector size: %d / %d", file_size, sector_size) + logging.warn(_("Input partition size not evenly divisible by" + " sector size: %d / %d"), file_size, sector_size) primary_sectors = file_size / sector_size if local_bytes % sector_size != 0: - logging.warn("Bytes for local storage not evenly divisible" - " by sector size: %d / %d", local_bytes, sector_size) + logging.warn(_("Bytes for local storage not evenly divisible" + " by sector size: %d / %d"), local_bytes, sector_size) local_sectors = local_bytes / sector_size mbr_last = 62 # a @@ -121,14 +121,15 @@ def inject_data(image, key=None, net=None, partition=None, execute=None): """ out, err = yield execute('sudo losetup -f --show %s' % image) if err: - raise exception.Error('Could not attach image to loopback: %s' % err) + raise exception.Error(_('Could not attach image to loopback: %s') + % err) device = out.strip() try: if not partition is None: # create partition out, err = yield execute('sudo kpartx -a %s' % device) if err: - raise exception.Error('Failed to load partition: %s' % err) + raise exception.Error(_('Failed to load partition: %s') % err) mapped_device = '/dev/mapper/%sp%s' % (device.split('/')[-1], partition) else: @@ -141,7 +142,8 @@ def inject_data(image, key=None, net=None, partition=None, execute=None): out, err = yield execute( 'sudo mount %s %s' % (mapped_device, tmpdir)) if err: - raise exception.Error('Failed to mount filesystem: %s' % err) + raise exception.Error(_('Failed to mount filesystem: %s') + % err) try: if key: diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index a2679e0fc2..000b3a6d9f 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -37,7 +37,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", + raise exception.ApiError(_("Unknown instance type: %s"), instance_type) return instance_type diff --git a/nova/compute/manager.py b/nova/compute/manager.py index dd8d41129c..a63ad5e1bd 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -91,8 +91,8 @@ class ComputeManager(manager.Manager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) if instance_ref['name'] in self.driver.list_instances(): - raise exception.Error("Instance has already been created") - logging.debug("instance %s: starting...", instance_id) + raise exception.Error(_("Instance has already been created")) + logging.debug(_("instance %s: starting..."), instance_id) self.network_manager.setup_compute_network(context, instance_id) self.db.instance_update(context, instance_id, @@ -111,7 +111,7 @@ class ComputeManager(manager.Manager): instance_id, {'launched_at': now}) except Exception: # pylint: disable-msg=W0702 - logging.exception("instance %s: Failed to spawn", + logging.exception(_("instance %s: Failed to spawn"), instance_ref['name']) self.db.instance_set_state(context, instance_id, @@ -124,7 +124,7 @@ class ComputeManager(manager.Manager): def terminate_instance(self, context, instance_id): """Terminate an instance on this machine.""" context = context.elevated() - logging.debug("instance %s: terminating", instance_id) + logging.debug(_("instance %s: terminating"), instance_id) instance_ref = self.db.instance_get(context, instance_id) volumes = instance_ref.get('volumes', []) or [] @@ -132,8 +132,8 @@ class ComputeManager(manager.Manager): self.detach_volume(context, instance_id, volume['id']) if instance_ref['state'] == power_state.SHUTOFF: self.db.instance_destroy(context, instance_id) - raise exception.Error('trying to destroy already destroyed' - ' instance: %s' % instance_id) + raise exception.Error(_('trying to destroy already destroyed' + ' instance: %s') % instance_id) yield self.driver.destroy(instance_ref) # TODO(ja): should we keep it in a terminated state for a bit? @@ -148,13 +148,13 @@ class ComputeManager(manager.Manager): self._update_state(context, instance_id) if instance_ref['state'] != power_state.RUNNING: - logging.warn('trying to reboot a non-running ' - 'instance: %s (state: %s excepted: %s)', + logging.warn(_('trying to reboot a non-running ' + 'instance: %s (state: %s excepted: %s)'), instance_ref['internal_id'], instance_ref['state'], power_state.RUNNING) - logging.debug('instance %s: rebooting', instance_ref['name']) + logging.debug(_('instance %s: rebooting'), instance_ref['name']) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, @@ -169,7 +169,7 @@ class ComputeManager(manager.Manager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - logging.debug('instance %s: rescuing', + logging.debug(_('instance %s: rescuing'), instance_ref['internal_id']) self.db.instance_set_state(context, instance_id, @@ -185,7 +185,7 @@ class ComputeManager(manager.Manager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - logging.debug('instance %s: unrescuing', + logging.debug(_('instance %s: unrescuing'), instance_ref['internal_id']) self.db.instance_set_state(context, instance_id, @@ -198,7 +198,7 @@ class ComputeManager(manager.Manager): def get_console_output(self, context, instance_id): """Send the console output for an instance.""" context = context.elevated() - logging.debug("instance %s: getting console output", instance_id) + logging.debug(_("instance %s: getting console output"), instance_id) instance_ref = self.db.instance_get(context, instance_id) return self.driver.get_console_output(instance_ref) @@ -208,7 +208,7 @@ class ComputeManager(manager.Manager): def attach_volume(self, context, instance_id, volume_id, mountpoint): """Attach a volume to an instance.""" context = context.elevated() - logging.debug("instance %s: attaching volume %s to %s", instance_id, + logging.debug(_("instance %s: attaching volume %s to %s"), instance_id, volume_id, mountpoint) instance_ref = self.db.instance_get(context, instance_id) dev_path = yield self.volume_manager.setup_compute_volume(context, @@ -225,7 +225,7 @@ class ComputeManager(manager.Manager): # NOTE(vish): The inline callback eats the exception info so we # log the traceback here and reraise the same # ecxception below. - logging.exception("instance %s: attach failed %s, removing", + logging.exception(_("instance %s: attach failed %s, removing"), instance_id, mountpoint) yield self.volume_manager.remove_compute_volume(context, volume_id) @@ -237,13 +237,13 @@ class ComputeManager(manager.Manager): def detach_volume(self, context, instance_id, volume_id): """Detach a volume from an instance.""" context = context.elevated() - logging.debug("instance %s: detaching volume %s", + logging.debug(_("instance %s: detaching volume %s"), instance_id, volume_id) instance_ref = self.db.instance_get(context, instance_id) volume_ref = self.db.volume_get(context, volume_id) if instance_ref['name'] not in self.driver.list_instances(): - logging.warn("Detaching volume from unknown instance %s", + logging.warn(_("Detaching volume from unknown instance %s"), instance_ref['name']) else: yield self.driver.detach_volume(instance_ref['name'], diff --git a/nova/compute/monitor.py b/nova/compute/monitor.py index 22653113a6..60c347a5ea 100644 --- a/nova/compute/monitor.py +++ b/nova/compute/monitor.py @@ -255,7 +255,7 @@ class Instance(object): Updates the instances statistics and stores the resulting graphs in the internal object store on the cloud controller. """ - logging.debug('updating %s...', self.instance_id) + logging.debug(_('updating %s...'), self.instance_id) try: data = self.fetch_cpu_stats() @@ -285,7 +285,7 @@ class Instance(object): graph_disk(self, '1w') graph_disk(self, '1m') except Exception: - logging.exception('unexpected error during update') + logging.exception(_('unexpected error during update')) self.last_updated = utcnow() @@ -351,7 +351,7 @@ class Instance(object): rd += rd_bytes wr += wr_bytes except TypeError: - logging.error('Cannot get blockstats for "%s" on "%s"', + logging.error(_('Cannot get blockstats for "%s" on "%s"'), disk, self.instance_id) raise @@ -373,7 +373,7 @@ class Instance(object): rx += stats[0] tx += stats[4] except TypeError: - logging.error('Cannot get ifstats for "%s" on "%s"', + logging.error(_('Cannot get ifstats for "%s" on "%s"'), interface, self.instance_id) raise @@ -408,7 +408,7 @@ class InstanceMonitor(object, service.Service): try: conn = virt_connection.get_connection(read_only=True) except Exception, exn: - logging.exception('unexpected exception getting connection') + logging.exception(_('unexpected exception getting connection')) time.sleep(FLAGS.monitoring_instances_delay) return @@ -423,7 +423,7 @@ class InstanceMonitor(object, service.Service): if not domain_id in self._instances: instance = Instance(conn, domain_id) self._instances[domain_id] = instance - logging.debug('Found instance: %s', domain_id) + logging.debug(_('Found instance: %s'), domain_id) for key in self._instances.keys(): instance = self._instances[key] diff --git a/nova/crypto.py b/nova/crypto.py index aacc50b179..af4a06a0c8 100644 --- a/nova/crypto.py +++ b/nova/crypto.py @@ -39,13 +39,13 @@ from nova import flags FLAGS = flags.FLAGS -flags.DEFINE_string('ca_file', 'cacert.pem', 'Filename of root CA') +flags.DEFINE_string('ca_file', 'cacert.pem', _('Filename of root CA')) flags.DEFINE_string('keys_path', '$state_path/keys', - 'Where we keep our keys') + _('Where we keep our keys')) flags.DEFINE_string('ca_path', '$state_path/CA', - 'Where we keep our root CA') + _('Where we keep our root CA')) flags.DEFINE_boolean('use_intermediate_ca', False, - 'Should we use intermediate CAs for each project?') + _('Should we use intermediate CAs for each project?')) def ca_path(project_id): @@ -111,9 +111,9 @@ def generate_x509_cert(subject, bits=1024): keyfile = os.path.abspath(os.path.join(tmpdir, 'temp.key')) csrfile = os.path.join(tmpdir, 'temp.csr') logging.debug("openssl genrsa -out %s %s" % (keyfile, bits)) - utils.runthis("Generating private key: %s", + utils.runthis(_("Generating private key: %s"), "openssl genrsa -out %s %s" % (keyfile, bits)) - utils.runthis("Generating CSR: %s", + utils.runthis(_("Generating CSR: %s"), "openssl req -new -key %s -out %s -batch -subj %s" % (keyfile, csrfile, subject)) private_key = open(keyfile).read() @@ -131,7 +131,7 @@ def sign_csr(csr_text, intermediate=None): if not os.path.exists(user_ca): start = os.getcwd() os.chdir(FLAGS.ca_path) - utils.runthis("Generating intermediate CA: %s", + utils.runthis(_("Generating intermediate CA: %s"), "sh geninter.sh %s" % (intermediate)) os.chdir(start) return _sign_csr(csr_text, user_ca) @@ -142,11 +142,11 @@ def _sign_csr(csr_text, ca_folder): csrfile = open("%s/inbound.csr" % (tmpfolder), "w") csrfile.write(csr_text) csrfile.close() - logging.debug("Flags path: %s" % ca_folder) + logging.debug(_("Flags path: %s") % ca_folder) start = os.getcwd() # Change working dir to CA os.chdir(ca_folder) - utils.runthis("Signing cert: %s", + utils.runthis(_("Signing cert: %s"), "openssl ca -batch -out %s/outbound.crt " "-config ./openssl.cnf -infiles %s/inbound.csr" % (tmpfolder, tmpfolder)) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 55036d1d10..4bae170a95 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -41,7 +41,7 @@ FLAGS = flags.FLAGS def is_admin_context(context): """Indicates if the request context is an administrator.""" if not context: - warnings.warn('Use of empty request context is deprecated', + warnings.warn(_('Use of empty request context is deprecated'), DeprecationWarning) raise Exception('die') return context.is_admin @@ -130,7 +130,7 @@ def service_get(context, service_id, session=None): first() if not result: - raise exception.NotFound('No service for id %s' % service_id) + raise exception.NotFound(_('No service for id %s') % service_id) return result @@ -227,7 +227,7 @@ def service_get_by_args(context, host, binary): filter_by(deleted=can_read_deleted(context)).\ first() if not result: - raise exception.NotFound('No service for %s, %s' % (host, binary)) + raise exception.NotFound(_('No service for %s, %s') % (host, binary)) return result @@ -491,7 +491,7 @@ def fixed_ip_get_by_address(context, address, session=None): options(joinedload('instance')).\ first() if not result: - raise exception.NotFound('No floating ip for address %s' % address) + raise exception.NotFound(_('No floating ip for address %s') % address) if is_user_context(context): authorize_project_context(context, result.instance.project_id) @@ -591,7 +591,7 @@ def instance_get(context, instance_id, session=None): filter_by(deleted=False).\ first() if not result: - raise exception.NotFound('No instance for id %s' % instance_id) + raise exception.NotFound(_('No instance for id %s') % instance_id) return result @@ -669,7 +669,7 @@ def instance_get_by_internal_id(context, internal_id): filter_by(deleted=False).\ first() if not result: - raise exception.NotFound('Instance %s not found' % (internal_id)) + raise exception.NotFound(_('Instance %s not found') % (internal_id)) return result @@ -790,7 +790,7 @@ def key_pair_get(context, user_id, name, session=None): filter_by(deleted=can_read_deleted(context)).\ first() if not result: - raise exception.NotFound('no keypair for user %s, name %s' % + raise exception.NotFound(_('no keypair for user %s, name %s') % (user_id, name)) return result @@ -905,7 +905,7 @@ def network_get(context, network_id, session=None): filter_by(deleted=False).\ first() if not result: - raise exception.NotFound('No network for id %s' % network_id) + raise exception.NotFound(_('No network for id %s') % network_id) return result @@ -933,7 +933,7 @@ def network_get_by_bridge(context, bridge): first() if not result: - raise exception.NotFound('No network for bridge %s' % bridge) + raise exception.NotFound(_('No network for bridge %s') % bridge) return result @@ -947,7 +947,7 @@ def network_get_by_instance(_context, instance_id): filter_by(deleted=False).\ first() if not rv: - raise exception.NotFound('No network for instance %s' % instance_id) + raise exception.NotFound(_('No network for instance %s') % instance_id) return rv @@ -961,7 +961,7 @@ def network_set_host(context, network_id, host_id): with_lockmode('update').\ first() if not network_ref: - raise exception.NotFound('No network for id %s' % network_id) + raise exception.NotFound(_('No network for id %s') % network_id) # NOTE(vish): if with_lockmode isn't supported, as in sqlite, # then this has concurrency issues @@ -1073,7 +1073,7 @@ def auth_get_token(_context, token_hash): filter_by(token_hash=token_hash).\ first() if not tk: - raise exception.NotFound('Token %s does not exist' % token_hash) + raise exception.NotFound(_('Token %s does not exist') % token_hash) return tk @@ -1097,7 +1097,7 @@ def quota_get(context, project_id, session=None): filter_by(deleted=can_read_deleted(context)).\ first() if not result: - raise exception.NotFound('No quota for project_id %s' % project_id) + raise exception.NotFound(_('No quota for project_id %s') % project_id) return result @@ -1252,7 +1252,7 @@ def volume_get(context, volume_id, session=None): filter_by(deleted=False).\ first() if not result: - raise exception.NotFound('No volume for id %s' % volume_id) + raise exception.NotFound(_('No volume for id %s') % volume_id) return result @@ -1308,7 +1308,7 @@ def volume_get_by_ec2_id(context, ec2_id): raise exception.NotAuthorized() if not result: - raise exception.NotFound('Volume %s not found' % ec2_id) + raise exception.NotFound(_('Volume %s not found') % ec2_id) return result @@ -1332,7 +1332,7 @@ def volume_get_instance(context, volume_id): options(joinedload('instance')).\ first() if not result: - raise exception.NotFound('Volume %s not found' % ec2_id) + raise exception.NotFound(_('Volume %s not found') % ec2_id) return result.instance @@ -1344,7 +1344,7 @@ def volume_get_shelf_and_blade(context, volume_id): filter_by(volume_id=volume_id).\ first() if not result: - raise exception.NotFound('No export device found for volume %s' % + raise exception.NotFound(_('No export device found for volume %s') % volume_id) return (result.shelf_id, result.blade_id) @@ -1357,7 +1357,7 @@ def volume_get_iscsi_target_num(context, volume_id): filter_by(volume_id=volume_id).\ first() if not result: - raise exception.NotFound('No target id found for volume %s' % + raise exception.NotFound(_('No target id found for volume %s') % volume_id) return result.target_num @@ -1402,7 +1402,7 @@ def security_group_get(context, security_group_id, session=None): options(joinedload_all('rules')).\ first() if not result: - raise exception.NotFound("No secuity group with id %s" % + raise exception.NotFound(_("No security group with id %s") % security_group_id) return result @@ -1419,7 +1419,7 @@ def security_group_get_by_name(context, project_id, group_name): first() if not result: raise exception.NotFound( - 'No security group named %s for project: %s' \ + _('No security group named %s for project: %s') % (group_name, project_id)) return result @@ -1507,7 +1507,7 @@ def security_group_rule_get(context, security_group_rule_id, session=None): filter_by(id=security_group_rule_id).\ first() if not result: - raise exception.NotFound("No secuity group rule with id %s" % + raise exception.NotFound(_("No secuity group rule with id %s") % security_group_rule_id) return result @@ -1543,7 +1543,7 @@ def user_get(context, id, session=None): first() if not result: - raise exception.NotFound('No user for id %s' % id) + raise exception.NotFound(_('No user for id %s') % id) return result @@ -1559,7 +1559,7 @@ def user_get_by_access_key(context, access_key, session=None): first() if not result: - raise exception.NotFound('No user for access key %s' % access_key) + raise exception.NotFound(_('No user for access key %s') % access_key) return result @@ -1621,7 +1621,7 @@ def project_get(context, id, session=None): first() if not result: - raise exception.NotFound("No project with id %s" % id) + raise exception.NotFound(_("No project with id %s") % id) return result diff --git a/nova/exception.py b/nova/exception.py index 6d6c373380..fd8b00368d 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -30,11 +30,11 @@ class ProcessExecutionError(IOError): def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None, description=None): if description is None: - description = "Unexpected error while running command." + description = _("Unexpected error while running command.") if exit_code is None: exit_code = '-' - message = "%s\nCommand: %s\nExit code: %s\nStdout: %r\nStderr: %r" % ( - description, cmd, exit_code, stdout, stderr) + message = _("%s\nCommand: %s\nExit code: %s\nStdout: %r\nStderr: %r")\ + % (description, cmd, exit_code, stdout, stderr) IOError.__init__(self, message) @@ -81,7 +81,7 @@ def wrap_exception(f): except Exception, e: if not isinstance(e, Error): #exc_type, exc_value, exc_traceback = sys.exc_info() - logging.exception('Uncaught exception') + logging.exception(_('Uncaught exception')) #logging.error(traceback.extract_stack(exc_traceback)) raise Error(str(e)) raise diff --git a/nova/fakerabbit.py b/nova/fakerabbit.py index c64617931c..41e686cffe 100644 --- a/nova/fakerabbit.py +++ b/nova/fakerabbit.py @@ -37,12 +37,12 @@ class Exchange(object): self._routes = {} def publish(self, message, routing_key=None): - logging.debug('(%s) publish (key: %s) %s', + logging.debug(_('(%s) publish (key: %s) %s'), self.name, routing_key, message) routing_key = routing_key.split('.')[0] if routing_key in self._routes: for f in self._routes[routing_key]: - logging.debug('Publishing to route %s', f) + logging.debug(_('Publishing to route %s'), f) f(message, routing_key=routing_key) def bind(self, callback, routing_key): @@ -82,16 +82,16 @@ class Backend(object): def queue_declare(self, queue, **kwargs): if queue not in self._queues: - logging.debug('Declaring queue %s', queue) + logging.debug(_('Declaring queue %s'), queue) self._queues[queue] = Queue(queue) def exchange_declare(self, exchange, type, *args, **kwargs): if exchange not in self._exchanges: - logging.debug('Declaring exchange %s', exchange) + logging.debug(_('Declaring exchange %s'), exchange) self._exchanges[exchange] = Exchange(exchange, type) def queue_bind(self, queue, exchange, routing_key, **kwargs): - logging.debug('Binding %s to %s with key %s', + logging.debug(_('Binding %s to %s with key %s'), queue, exchange, routing_key) self._exchanges[exchange].bind(self._queues[queue].push, routing_key) @@ -117,7 +117,7 @@ class Backend(object): content_type=content_type, content_encoding=content_encoding) message.result = True - logging.debug('Getting from %s: %s', queue, message) + logging.debug(_('Getting from %s: %s'), queue, message) return message def prepare_message(self, message_data, delivery_mode, diff --git a/nova/image/glance.py b/nova/image/glance.py index 1ca6cf2eb7..cb3936df11 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -77,8 +77,8 @@ class ParallaxClient(object): data = json.loads(res.read())['images'] return data else: - logging.warn("Parallax returned HTTP error %d from " - "request for /images", res.status_int) + logging.warn(_("Parallax returned HTTP error %d from " + "request for /images"), res.status_int) return [] finally: c.close() @@ -96,8 +96,8 @@ class ParallaxClient(object): data = json.loads(res.read())['images'] return data else: - logging.warn("Parallax returned HTTP error %d from " - "request for /images/detail", res.status_int) + logging.warn(_("Parallax returned HTTP error %d from " + "request for /images/detail"), res.status_int) return [] finally: c.close() diff --git a/nova/image/s3.py b/nova/image/s3.py index 0a25161de4..7b04aa0727 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -79,7 +79,8 @@ class S3ImageService(service.BaseImageService): result = self.index(context) result = [i for i in result if i['imageId'] == image_id] if not result: - raise exception.NotFound('Image %s could not be found' % image_id) + raise exception.NotFound(_('Image %s could not be found') + % image_id) image = result[0] return image diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 0fefd94156..16add76897 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -135,7 +135,7 @@ def ensure_vlan(vlan_num): """Create a vlan unless it already exists""" interface = "vlan%s" % vlan_num if not _device_exists(interface): - logging.debug("Starting VLAN inteface %s", interface) + logging.debug(_("Starting VLAN inteface %s"), interface) _execute("sudo vconfig set_name_type VLAN_PLUS_VID_NO_PAD") _execute("sudo vconfig add %s %s" % (FLAGS.vlan_interface, vlan_num)) _execute("sudo ifconfig %s up" % interface) @@ -145,7 +145,7 @@ def ensure_vlan(vlan_num): def ensure_bridge(bridge, interface, net_attrs=None): """Create a bridge unless it already exists""" if not _device_exists(bridge): - logging.debug("Starting Bridge interface for %s", interface) + logging.debug(_("Starting Bridge interface for %s"), interface) _execute("sudo brctl addbr %s" % bridge) _execute("sudo brctl setfd %s 0" % bridge) # _execute("sudo brctl setageing %s 10" % bridge) @@ -202,9 +202,9 @@ def update_dhcp(context, network_id): _execute('sudo kill -HUP %d' % pid) return except Exception as exc: # pylint: disable-msg=W0703 - logging.debug("Hupping dnsmasq threw %s", exc) + logging.debug(_("Hupping dnsmasq threw %s"), exc) else: - logging.debug("Pid %d is stale, relaunching dnsmasq", pid) + logging.debug(_("Pid %d is stale, relaunching dnsmasq"), pid) # FLAGFILE and DNSMASQ_INTERFACE in env env = {'FLAGFILE': FLAGS.dhcpbridge_flagfile, @@ -276,7 +276,7 @@ def _stop_dnsmasq(network): try: _execute('sudo kill -TERM %d' % pid) except Exception as exc: # pylint: disable-msg=W0703 - logging.debug("Killing dnsmasq threw %s", exc) + logging.debug(_("Killing dnsmasq threw %s"), exc) def _dhcp_file(bridge, kind): diff --git a/nova/network/manager.py b/nova/network/manager.py index a7298b47f0..e3677459d3 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -116,7 +116,7 @@ class NetworkManager(manager.Manager): def set_network_host(self, context, network_id): """Safely sets the host of the network.""" - logging.debug("setting network host") + logging.debug(_("setting network host")) host = self.db.network_set_host(context, network_id, self.host) @@ -175,10 +175,10 @@ class NetworkManager(manager.Manager): fixed_ip_ref = self.db.fixed_ip_get_by_address(context, address) instance_ref = fixed_ip_ref['instance'] if not instance_ref: - raise exception.Error("IP %s leased that isn't associated" % + raise exception.Error(_("IP %s leased that isn't associated") % address) if instance_ref['mac_address'] != mac: - raise exception.Error("IP %s leased to bad mac %s vs %s" % + raise exception.Error(_("IP %s leased to bad mac %s vs %s") % (address, instance_ref['mac_address'], mac)) now = datetime.datetime.utcnow() self.db.fixed_ip_update(context, @@ -186,7 +186,8 @@ class NetworkManager(manager.Manager): {'leased': True, 'updated_at': now}) if not fixed_ip_ref['allocated']: - logging.warn("IP %s leased that was already deallocated", address) + logging.warn(_("IP %s leased that was already deallocated"), + address) def release_fixed_ip(self, context, mac, address): """Called by dhcp-bridge when ip is released.""" @@ -194,13 +195,13 @@ class NetworkManager(manager.Manager): fixed_ip_ref = self.db.fixed_ip_get_by_address(context, address) instance_ref = fixed_ip_ref['instance'] if not instance_ref: - raise exception.Error("IP %s released that isn't associated" % + raise exception.Error(_("IP %s released that isn't associated") % address) if instance_ref['mac_address'] != mac: - raise exception.Error("IP %s released from bad mac %s vs %s" % + raise exception.Error(_("IP %s released from bad mac %s vs %s") % (address, instance_ref['mac_address'], mac)) if not fixed_ip_ref['leased']: - logging.warn("IP %s released that was not leased", address) + logging.warn(_("IP %s released that was not leased"), address) self.db.fixed_ip_update(context, fixed_ip_ref['address'], {'leased': False}) @@ -410,7 +411,7 @@ class VlanManager(NetworkManager): self.host, time) if num: - logging.debug("Dissassociated %s stale fixed ip(s)", num) + logging.debug(_("Dissassociated %s stale fixed ip(s)"), num) def init_host(self): """Do any initialization that needs to be run if this is a diff --git a/nova/objectstore/handler.py b/nova/objectstore/handler.py index c8920b00c8..0c71c3705f 100644 --- a/nova/objectstore/handler.py +++ b/nova/objectstore/handler.py @@ -102,7 +102,7 @@ def _render_parts(value, write_cb): _render_parts(subsubvalue, write_cb) write_cb('') else: - raise Exception("Unknown S3 value type %r", value) + raise Exception(_("Unknown S3 value type %r"), value) def get_argument(request, key, default_value): @@ -134,7 +134,7 @@ def get_context(request): check_type='s3') return context.RequestContext(user, project) except exception.Error as ex: - logging.debug("Authentication Failure: %s", ex) + logging.debug(_("Authentication Failure: %s"), ex) raise exception.NotAuthorized() @@ -227,7 +227,7 @@ class BucketResource(ErrorHandlingResource): def render_PUT(self, request): "Creates the bucket resource""" - logging.debug("Creating bucket %s", self.name) + logging.debug(_("Creating bucket %s"), self.name) logging.debug("calling bucket.Bucket.create(%r, %r)", self.name, request.context) @@ -237,7 +237,7 @@ class BucketResource(ErrorHandlingResource): def render_DELETE(self, request): """Deletes the bucket resource""" - logging.debug("Deleting bucket %s", self.name) + logging.debug(_("Deleting bucket %s"), self.name) bucket_object = bucket.Bucket(self.name) if not bucket_object.is_authorized(request.context): @@ -261,7 +261,7 @@ class ObjectResource(ErrorHandlingResource): Raises NotAuthorized if user in request context is not authorized to delete the object. """ - logging.debug("Getting object: %s / %s", self.bucket.name, self.name) + logging.debug(_("Getting object: %s / %s"), self.bucket.name, self.name) if not self.bucket.is_authorized(request.context): raise exception.NotAuthorized() @@ -279,7 +279,7 @@ class ObjectResource(ErrorHandlingResource): Raises NotAuthorized if user in request context is not authorized to delete the object. """ - logging.debug("Putting object: %s / %s", self.bucket.name, self.name) + logging.debug(_("Putting object: %s / %s"), self.bucket.name, self.name) if not self.bucket.is_authorized(request.context): raise exception.NotAuthorized() @@ -298,7 +298,7 @@ class ObjectResource(ErrorHandlingResource): authorized to delete the object. """ - logging.debug("Deleting object: %s / %s", + logging.debug(_("Deleting object: %s / %s"), self.bucket.name, self.name) @@ -394,17 +394,17 @@ class ImagesResource(resource.Resource): image_id = get_argument(request, 'image_id', u'') image_object = image.Image(image_id) if not image_object.is_authorized(request.context): - logging.debug("not authorized for render_POST in images") + logging.debug(_("not authorized for render_POST in images")) raise exception.NotAuthorized() operation = get_argument(request, 'operation', u'') if operation: # operation implies publicity toggle - logging.debug("handling publicity toggle") + logging.debug(_("handling publicity toggle")) image_object.set_public(operation == 'add') else: # other attributes imply update - logging.debug("update user fields") + logging.debug(_("update user fields")) clean_args = {} for arg in request.args.keys(): clean_args[arg] = request.args[arg][0] diff --git a/nova/process.py b/nova/process.py index b33df048bf..25b6723eca 100644 --- a/nova/process.py +++ b/nova/process.py @@ -131,7 +131,7 @@ def get_process_output(executable, args=None, env=None, path=None, cmd = executable if args: cmd = " ".join([cmd] + args) - logging.debug("Running cmd: %s", cmd) + logging.debug(_("Running cmd: %s"), cmd) process_handler = BackRelayWithInput( deferred, cmd, diff --git a/nova/rpc.py b/nova/rpc.py index 86a29574f3..cc3c7dfc8f 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -91,15 +91,15 @@ class Consumer(messaging.Consumer): self.failed_connection = False break except: # Catching all because carrot sucks - logging.exception("AMQP server on %s:%d is unreachable." \ - " Trying again in %d seconds." % ( + logging.exception(_("AMQP server on %s:%d is unreachable." + " Trying again in %d seconds.") % ( FLAGS.rabbit_host, FLAGS.rabbit_port, FLAGS.rabbit_retry_interval)) self.failed_connection = True if self.failed_connection: - logging.exception("Unable to connect to AMQP server" \ - " after %d tries. Shutting down." % FLAGS.rabbit_max_retries) + logging.exception(_("Unable to connect to AMQP server" + " after %d tries. Shutting down.") % FLAGS.rabbit_max_retries) sys.exit(1) def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): @@ -116,14 +116,14 @@ class Consumer(messaging.Consumer): self.declare() super(Consumer, self).fetch(no_ack, auto_ack, enable_callbacks) if self.failed_connection: - logging.error("Reconnected to queue") + logging.error(_("Reconnected to queue")) self.failed_connection = False # NOTE(vish): This is catching all errors because we really don't # exceptions to be logged 10 times a second if some # persistent failure occurs. except Exception: # pylint: disable-msg=W0703 if not self.failed_connection: - logging.exception("Failed to fetch message from queue") + logging.exception(_("Failed to fetch message from queue")) self.failed_connection = True def attach_to_eventlet(self): @@ -161,7 +161,7 @@ class TopicConsumer(Consumer): class AdapterConsumer(TopicConsumer): """Calls methods on a proxy object based on method and args""" def __init__(self, connection=None, topic="broadcast", proxy=None): - LOG.debug('Initing the Adapter Consumer for %s' % (topic)) + LOG.debug(_('Initing the Adapter Consumer for %s') % (topic)) self.proxy = proxy super(AdapterConsumer, self).__init__(connection=connection, topic=topic) @@ -176,7 +176,7 @@ class AdapterConsumer(TopicConsumer): Example: {'method': 'echo', 'args': {'value': 42}} """ - LOG.debug('received %s' % (message_data)) + LOG.debug(_('received %s') % (message_data)) msg_id = message_data.pop('_msg_id', None) ctxt = _unpack_context(message_data) @@ -189,8 +189,8 @@ class AdapterConsumer(TopicConsumer): # messages stay in the queue indefinitely, so for now # we just log the message and send an error string # back to the caller - LOG.warn('no method for message: %s' % (message_data)) - msg_reply(msg_id, 'No method for message: %s' % message_data) + LOG.warn(_('no method for message: %s') % (message_data)) + msg_reply(msg_id, _('No method for message: %s') % message_data) return node_func = getattr(self.proxy, str(method)) @@ -246,7 +246,7 @@ def msg_reply(msg_id, reply=None, failure=None): if failure: message = failure.getErrorMessage() traceback = failure.getTraceback() - logging.error("Returning exception %s to caller", message) + logging.error(_("Returning exception %s to caller"), message) logging.error(traceback) failure = (failure.type.__name__, str(failure.value), traceback) conn = Connection.instance() @@ -287,7 +287,7 @@ def _unpack_context(msg): if key.startswith('_context_'): value = msg.pop(key) context_dict[key[9:]] = value - LOG.debug('unpacked context: %s', context_dict) + LOG.debug(_('unpacked context: %s'), context_dict) return context.RequestContext.from_dict(context_dict) @@ -306,10 +306,10 @@ def _pack_context(msg, context): def call(context, topic, msg): """Sends a message on a topic and wait for a response""" - LOG.debug("Making asynchronous call...") + LOG.debug(_("Making asynchronous call...")) msg_id = uuid.uuid4().hex msg.update({'_msg_id': msg_id}) - LOG.debug("MSG_ID is %s" % (msg_id)) + LOG.debug(_("MSG_ID is %s") % (msg_id)) _pack_context(msg, context) class WaitMessage(object): @@ -345,7 +345,7 @@ def call_twisted(context, topic, msg): LOG.debug("Making asynchronous call...") msg_id = uuid.uuid4().hex msg.update({'_msg_id': msg_id}) - LOG.debug("MSG_ID is %s" % (msg_id)) + LOG.debug(_("MSG_ID is %s") % (msg_id)) _pack_context(msg, context) conn = Connection.instance() @@ -384,7 +384,7 @@ def cast(context, topic, msg): def generic_response(message_data, message): """Logs a result and exits""" - LOG.debug('response %s', message_data) + LOG.debug(_('response %s'), message_data) message.ack() sys.exit(0) @@ -393,8 +393,8 @@ def send_message(topic, message, wait=True): """Sends a message for testing""" msg_id = uuid.uuid4().hex message.update({'_msg_id': msg_id}) - LOG.debug('topic is %s', topic) - LOG.debug('message %s', message) + LOG.debug(_('topic is %s'), topic) + LOG.debug(_('message %s'), message) if wait: consumer = messaging.Consumer(connection=Connection.instance(), diff --git a/nova/scheduler/chance.py b/nova/scheduler/chance.py index 7fd09b053b..9deaa2777a 100644 --- a/nova/scheduler/chance.py +++ b/nova/scheduler/chance.py @@ -34,5 +34,5 @@ class ChanceScheduler(driver.Scheduler): hosts = self.hosts_up(context, topic) if not hosts: - raise driver.NoValidHost("No hosts found") + raise driver.NoValidHost(_("No hosts found")) return hosts[int(random.random() * len(hosts))] diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py index f271d573fd..08d7033f5e 100644 --- a/nova/scheduler/driver.py +++ b/nova/scheduler/driver.py @@ -58,4 +58,4 @@ class Scheduler(object): def schedule(self, context, topic, *_args, **_kwargs): """Must override at least this method for scheduler to work.""" - raise NotImplementedError("Must implement a fallback schedule") + raise NotImplementedError(_("Must implement a fallback schedule")) diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py index 60a3d2b4b2..44e21f2fdd 100644 --- a/nova/scheduler/manager.py +++ b/nova/scheduler/manager.py @@ -65,4 +65,4 @@ class SchedulerManager(manager.Manager): db.queue_get_for(context, topic, host), {"method": method, "args": kwargs}) - logging.debug("Casting to %s %s for %s", topic, host, method) + logging.debug(_("Casting to %s %s for %s"), topic, host, method) diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py index 7f50936564..f9171ab35d 100644 --- a/nova/scheduler/simple.py +++ b/nova/scheduler/simple.py @@ -47,7 +47,7 @@ class SimpleScheduler(chance.ChanceScheduler): for result in results: (service, instance_cores) = result if instance_cores + instance_ref['vcpus'] > FLAGS.max_cores: - raise driver.NoValidHost("All hosts have too many cores") + raise driver.NoValidHost(_("All hosts have too many cores")) if self.service_is_up(service): # NOTE(vish): this probably belongs in the manager, if we # can generalize this somehow @@ -57,7 +57,7 @@ class SimpleScheduler(chance.ChanceScheduler): {'host': service['host'], 'scheduled_at': now}) return service['host'] - raise driver.NoValidHost("No hosts found") + raise driver.NoValidHost(_("No hosts found")) def schedule_create_volume(self, context, volume_id, *_args, **_kwargs): """Picks a host that is up and has the fewest volumes.""" @@ -66,7 +66,8 @@ class SimpleScheduler(chance.ChanceScheduler): for result in results: (service, volume_gigabytes) = result if volume_gigabytes + volume_ref['size'] > FLAGS.max_gigabytes: - raise driver.NoValidHost("All hosts have too many gigabytes") + raise driver.NoValidHost(_("All hosts have too many " + "gigabytes")) if self.service_is_up(service): # NOTE(vish): this probably belongs in the manager, if we # can generalize this somehow @@ -76,7 +77,7 @@ class SimpleScheduler(chance.ChanceScheduler): {'host': service['host'], 'scheduled_at': now}) return service['host'] - raise driver.NoValidHost("No hosts found") + raise driver.NoValidHost(_("No hosts found")) def schedule_set_network_host(self, context, *_args, **_kwargs): """Picks a host that is up and has the fewest networks.""" @@ -85,7 +86,7 @@ class SimpleScheduler(chance.ChanceScheduler): for result in results: (service, instance_count) = result if instance_count >= FLAGS.max_networks: - raise driver.NoValidHost("All hosts have too many networks") + raise driver.NoValidHost(_("All hosts have too many networks")) if self.service_is_up(service): return service['host'] - raise driver.NoValidHost("No hosts found") + raise driver.NoValidHost(_("No hosts found")) diff --git a/nova/server.py b/nova/server.py index a0ee54681a..e5ce4475ad 100644 --- a/nova/server.py +++ b/nova/server.py @@ -58,7 +58,7 @@ def stop(pidfile): try: pid = int(open(pidfile, 'r').read().strip()) except IOError: - message = "pidfile %s does not exist. Daemon not running?\n" + message = _("pidfile %s does not exist. Daemon not running?\n") sys.stderr.write(message % pidfile) return @@ -84,7 +84,7 @@ def serve(name, main): if not FLAGS.pidfile: FLAGS.pidfile = '%s.pid' % name - logging.debug("Full set of FLAGS: \n\n\n") + logging.debug(_("Full set of FLAGS: \n\n\n")) for flag in FLAGS: logging.debug("%s : %s", flag, FLAGS.get(flag, None)) diff --git a/nova/service.py b/nova/service.py index 9454d4049b..348b1d192f 100644 --- a/nova/service.py +++ b/nova/service.py @@ -143,7 +143,7 @@ class Service(object, service.Service): report_interval = FLAGS.report_interval if not periodic_interval: periodic_interval = FLAGS.periodic_interval - logging.warn("Starting %s node", topic) + logging.warn(_("Starting %s node"), topic) service_obj = cls(host, binary, topic, manager, report_interval, periodic_interval) @@ -158,7 +158,7 @@ class Service(object, service.Service): try: db.service_destroy(context.get_admin_context(), self.service_id) except exception.NotFound: - logging.warn("Service killed that has no database entry") + logging.warn(_("Service killed that has no database entry")) @defer.inlineCallbacks def periodic_tasks(self): @@ -173,8 +173,8 @@ class Service(object, service.Service): try: service_ref = db.service_get(ctxt, self.service_id) except exception.NotFound: - logging.debug("The service database object disappeared, " - "Recreating it.") + logging.debug(_("The service database object disappeared, " + "Recreating it.")) self._create_service_ref(ctxt) service_ref = db.service_get(ctxt, self.service_id) @@ -185,11 +185,11 @@ class Service(object, service.Service): # TODO(termie): make this pattern be more elegant. if getattr(self, "model_disconnected", False): self.model_disconnected = False - logging.error("Recovered model server connection!") + logging.error(_("Recovered model server connection!")) # TODO(vish): this should probably only catch connection errors except Exception: # pylint: disable-msg=W0702 if not getattr(self, "model_disconnected", False): self.model_disconnected = True - logging.exception("model server went away") + logging.exception(_("model server went away")) yield diff --git a/nova/twistd.py b/nova/twistd.py index cb5648ce64..c5b7fed8c5 100644 --- a/nova/twistd.py +++ b/nova/twistd.py @@ -208,7 +208,7 @@ def stop(pidfile): pid = None if not pid: - message = "pidfile %s does not exist. Daemon not running?\n" + message = _("pidfile %s does not exist. Daemon not running?\n") sys.stderr.write(message % pidfile) # Not an error in a restart return @@ -229,7 +229,7 @@ def stop(pidfile): def serve(filename): - logging.debug("Serving %s" % filename) + logging.debug(_("Serving %s") % filename) name = os.path.basename(filename) OptionsClass = WrapTwistedOptions(TwistdServerOptions) options = OptionsClass() @@ -281,7 +281,7 @@ def serve(filename): else: logging.getLogger().setLevel(logging.WARNING) - logging.debug("Full set of FLAGS:") + logging.debug(_("Full set of FLAGS:")) for flag in FLAGS: logging.debug("%s : %s" % (flag, FLAGS.get(flag, None))) diff --git a/nova/utils.py b/nova/utils.py index 142584df8b..f6f03b5557 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -49,7 +49,7 @@ def import_class(import_str): __import__(mod_str) return getattr(sys.modules[mod_str], class_str) except (ImportError, ValueError, AttributeError): - raise exception.NotFound('Class %s cannot be found' % class_str) + raise exception.NotFound(_('Class %s cannot be found') % class_str) def import_object(import_str): @@ -63,7 +63,7 @@ def import_object(import_str): def fetchfile(url, target): - logging.debug("Fetching %s" % url) + logging.debug(_("Fetching %s") % url) # c = pycurl.Curl() # fp = open(target, "wb") # c.setopt(c.URL, url) @@ -75,7 +75,7 @@ def fetchfile(url, target): def execute(cmd, process_input=None, addl_env=None, check_exit_code=True): - logging.debug("Running cmd: %s", cmd) + logging.debug(_("Running cmd: %s"), cmd) env = os.environ.copy() if addl_env: env.update(addl_env) @@ -88,7 +88,7 @@ def execute(cmd, process_input=None, addl_env=None, check_exit_code=True): result = obj.communicate() obj.stdin.close() if obj.returncode: - logging.debug("Result was %s" % (obj.returncode)) + logging.debug(_("Result was %s") % (obj.returncode)) if check_exit_code and obj.returncode != 0: (stdout, stderr) = result raise ProcessExecutionError(exit_code=obj.returncode, @@ -122,7 +122,7 @@ def debug(arg): def runthis(prompt, cmd, check_exit_code=True): - logging.debug("Running %s" % (cmd)) + logging.debug(_("Running %s") % (cmd)) exit_code = subprocess.call(cmd.split(" ")) logging.debug(prompt % (exit_code)) if check_exit_code and exit_code != 0: @@ -161,7 +161,7 @@ def get_my_ip(): csock.close() return addr except socket.gaierror as ex: - logging.warn("Couldn't get IP, using 127.0.0.1 %s", ex) + logging.warn(_("Couldn't get IP, using 127.0.0.1 %s"), ex) return "127.0.0.1" @@ -205,7 +205,7 @@ class LazyPluggable(object): if not self.__backend: backend_name = self.__pivot.value if backend_name not in self.__backends: - raise exception.Error('Invalid backend: %s' % backend_name) + raise exception.Error(_('Invalid backend: %s') % backend_name) backend = self.__backends[backend_name] if type(backend) == type(tuple()): diff --git a/nova/validate.py b/nova/validate.py index 7ea27daa6e..49578a24de 100644 --- a/nova/validate.py +++ b/nova/validate.py @@ -42,7 +42,7 @@ def rangetest(**argchecks): # was passed by name if float(kargs[argname]) < low or \ float(kargs[argname]) > high: - errmsg = '{0} argument "{1}" not in {2}..{3}' + errmsg = _('{0} argument "{1}" not in {2}..{3}') errmsg = errmsg.format(funcname, argname, low, high) raise TypeError(errmsg) @@ -51,8 +51,8 @@ def rangetest(**argchecks): position = positionals.index(argname) if float(pargs[position]) < low or \ float(pargs[position]) > high: - errmsg = '{0} argument "{1}" with value of {4} ' \ - 'not in {2}..{3}' + errmsg = _('{0} argument "{1}" with value of {4} ' + 'not in {2}..{3}') errmsg = errmsg.format(funcname, argname, low, high, pargs[position]) raise TypeError(errmsg) @@ -76,14 +76,14 @@ def typetest(**argchecks): for (argname, typeof) in argchecks.items(): if argname in kargs: if not isinstance(kargs[argname], typeof): - errmsg = '{0} argument "{1}" not of type {2}' + errmsg = _('{0} argument "{1}" not of type {2}') errmsg = errmsg.format(funcname, argname, typeof) raise TypeError(errmsg) elif argname in positionals: position = positionals.index(argname) if not isinstance(pargs[position], typeof): - errmsg = '{0} argument "{1}" with value of {2} ' \ - 'not of type {3}' + errmsg = _('{0} argument "{1}" with value of {2} ' + 'not of type {3}') errmsg = errmsg.format(funcname, argname, pargs[position], typeof) raise TypeError(errmsg) From c00d99102c826f6a501ff7a530291dc8d7680df7 Mon Sep 17 00:00:00 2001 From: "jaypipes@gmail.com" <> Date: Sat, 11 Dec 2010 15:42:05 -0500 Subject: [PATCH 125/229] Final round of marking translation strings --- nova/virt/connection.py | 2 +- nova/virt/fake.py | 3 ++- nova/virt/libvirt_conn.py | 41 +++++++++++++++++++++------------------ nova/virt/xenapi_conn.py | 16 +++++++-------- nova/volume/driver.py | 10 +++++----- nova/volume/manager.py | 20 +++++++++---------- 6 files changed, 48 insertions(+), 44 deletions(-) diff --git a/nova/virt/connection.py b/nova/virt/connection.py index c40bb4bb4d..61e99944ef 100644 --- a/nova/virt/connection.py +++ b/nova/virt/connection.py @@ -66,6 +66,6 @@ def get_connection(read_only=False): raise Exception('Unknown connection type "%s"' % t) if conn is None: - logging.error('Failed to open connection to the hypervisor') + logging.error(_('Failed to open connection to the hypervisor')) sys.exit(1) return conn diff --git a/nova/virt/fake.py b/nova/virt/fake.py index f855523d36..26b01af918 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -167,7 +167,8 @@ class FakeConnection(object): knowledge of the instance """ if instance_name not in self.instances: - raise exception.NotFound("Instance %s Not Found" % instance_name) + raise exception.NotFound(_("Instance %s Not Found") + % instance_name) i = self.instances[instance_name] return {'state': i._state, 'max_mem': 0, diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 18085089f3..bc435f4b5d 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -132,7 +132,7 @@ class LibvirtConnection(object): @property def _conn(self): if not self._wrapped_conn or not self._test_connection(): - logging.debug('Connecting to libvirt: %s' % self.libvirt_uri) + logging.debug(_('Connecting to libvirt: %s') % self.libvirt_uri) self._wrapped_conn = self._connect(self.libvirt_uri, self.read_only) return self._wrapped_conn @@ -144,7 +144,7 @@ class LibvirtConnection(object): except libvirt.libvirtError as e: if e.get_error_code() == libvirt.VIR_ERR_SYSTEM_ERROR and \ e.get_error_domain() == libvirt.VIR_FROM_REMOTE: - logging.debug('Connection to libvirt broke') + logging.debug(_('Connection to libvirt broke')) return False raise @@ -214,7 +214,7 @@ class LibvirtConnection(object): def _cleanup(self, instance): target = os.path.join(FLAGS.instances_path, instance['name']) - logging.info('instance %s: deleting instance files %s', + logging.info(_('instance %s: deleting instance files %s'), instance['name'], target) if os.path.exists(target): shutil.rmtree(target) @@ -259,7 +259,7 @@ class LibvirtConnection(object): mount_device = mountpoint.rpartition("/")[2] xml = self._get_disk_xml(virt_dom.XMLDesc(0), mount_device) if not xml: - raise exception.NotFound("No disk at %s" % mount_device) + raise exception.NotFound(_("No disk at %s") % mount_device) virt_dom.detachDevice(xml) yield @@ -279,11 +279,11 @@ class LibvirtConnection(object): db.instance_set_state(context.get_admin_context(), instance['id'], state) if state == power_state.RUNNING: - logging.debug('instance %s: rebooted', instance['name']) + logging.debug(_('instance %s: rebooted'), instance['name']) timer.stop() d.callback(None) except Exception, exn: - logging.error('_wait_for_reboot failed: %s', exn) + logging.error(_('_wait_for_reboot failed: %s'), exn) db.instance_set_state(context.get_admin_context(), instance['id'], power_state.SHUTDOWN) @@ -314,11 +314,11 @@ class LibvirtConnection(object): state = self.get_info(instance['name'])['state'] db.instance_set_state(None, instance['id'], state) if state == power_state.RUNNING: - logging.debug('instance %s: rescued', instance['name']) + logging.debug(_('instance %s: rescued'), instance['name']) timer.stop() d.callback(None) except Exception, exn: - logging.error('_wait_for_rescue failed: %s', exn) + logging.error(_('_wait_for_rescue failed: %s'), exn) db.instance_set_state(None, instance['id'], power_state.SHUTDOWN) @@ -348,7 +348,7 @@ class LibvirtConnection(object): setup_nwfilters_for_instance(instance) yield self._create_image(instance, xml) yield self._conn.createXML(xml, 0) - logging.debug("instance %s: is running", instance['name']) + logging.debug(_("instance %s: is running"), instance['name']) local_d = defer.Deferred() timer = task.LoopingCall(f=None) @@ -359,11 +359,11 @@ class LibvirtConnection(object): db.instance_set_state(context.get_admin_context(), instance['id'], state) if state == power_state.RUNNING: - logging.debug('instance %s: booted', instance['name']) + logging.debug(_('instance %s: booted'), instance['name']) timer.stop() local_d.callback(None) except: - logging.exception('instance %s: failed to boot', + logging.exception(_('instance %s: failed to boot'), instance['name']) db.instance_set_state(context.get_admin_context(), instance['id'], @@ -379,7 +379,7 @@ class LibvirtConnection(object): virsh_output = virsh_output[0].strip() if virsh_output.startswith('/dev/'): - logging.info('cool, it\'s a device') + logging.info(_('cool, it\'s a device')) d = process.simple_execute("sudo dd if=%s iflag=nonblock" % virsh_output, check_exit_code=False) d.addCallback(lambda r: r[0]) @@ -388,7 +388,7 @@ class LibvirtConnection(object): return '' def _append_to_file(self, data, fpath): - logging.info('data: %r, fpath: %r' % (data, fpath)) + logging.info(_('data: %r, fpath: %r') % (data, fpath)) fp = open(fpath, 'a+') fp.write(data) return fpath @@ -431,7 +431,7 @@ class LibvirtConnection(object): # TODO(termie): these are blocking calls, it would be great # if they weren't. - logging.info('instance %s: Creating image', inst['name']) + logging.info(_('instance %s: Creating image'), inst['name']) f = open(basepath('libvirt.xml'), 'w') f.write(libvirt_xml) f.close() @@ -477,10 +477,10 @@ class LibvirtConnection(object): 'dns': network_ref['dns']} if key or net: if key: - logging.info('instance %s: injecting key into image %s', + logging.info(_('instance %s: injecting key into image %s'), inst['name'], inst.image_id) if net: - logging.info('instance %s: injecting net into image %s', + logging.info(_('instance %s: injecting net into image %s'), inst['name'], inst.image_id) yield disk.inject_data(basepath('disk-raw'), key, net, execute=execute) @@ -504,7 +504,8 @@ class LibvirtConnection(object): def to_xml(self, instance, rescue=False): # TODO(termie): cache? - logging.debug('instance %s: starting toXML method', instance['name']) + logging.debug(_('instance %s: starting toXML method'), + instance['name']) network = db.project_get_network(context.get_admin_context(), instance['project_id']) # FIXME(vish): stick this in db @@ -528,7 +529,8 @@ class LibvirtConnection(object): libvirt_xml = self.rescue_xml % xml_info else: libvirt_xml = self.libvirt_xml % xml_info - logging.debug('instance %s: finished toXML method', instance['name']) + logging.debug(_('instance %s: finished toXML method'), + instance['name']) return libvirt_xml @@ -536,7 +538,8 @@ class LibvirtConnection(object): try: virt_dom = self._conn.lookupByName(instance_name) except: - raise exception.NotFound("Instance %s not found" % instance_name) + raise exception.NotFound(_("Instance %s not found") + % instance_name) (state, max_mem, mem, num_cpu, cpu_time) = virt_dom.info() return {'state': state, 'max_mem': max_mem, diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 2153810c85..c8c451dfe5 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -92,10 +92,10 @@ def get_connection(_): username = FLAGS.xenapi_connection_username password = FLAGS.xenapi_connection_password if not url or password is None: - raise Exception('Must specify xenapi_connection_url, ' - 'xenapi_connection_username (optionally), and ' - 'xenapi_connection_password to use ' - 'connection_type=xenapi') + raise Exception(_('Must specify xenapi_connection_url, ' + 'xenapi_connection_username (optionally), and ' + 'xenapi_connection_password to use ' + 'connection_type=xenapi')) return XenAPIConnection(url, username, password) @@ -195,11 +195,11 @@ class XenAPISession(object): self._poll_task, task, deferred) elif status == 'success': result = self._session.xenapi.task.get_result(task) - logging.info('Task %s status: success. %s', task, result) + logging.info(_('Task %s status: success. %s'), task, result) deferred.callback(_parse_xmlrpc_value(result)) else: error_info = self._session.xenapi.task.get_error_info(task) - logging.warn('Task %s status: %s. %s', task, status, + logging.warn(_('Task %s status: %s. %s'), task, status, error_info) deferred.errback(XenAPI.Failure(error_info)) #logging.debug('Polling task %s done.', task) @@ -213,7 +213,7 @@ def _unwrap_plugin_exceptions(func, *args, **kwargs): try: return func(*args, **kwargs) except XenAPI.Failure, exc: - logging.debug("Got exception: %s", exc) + logging.debug(_("Got exception: %s"), exc) if (len(exc.details) == 4 and exc.details[0] == 'XENAPI_PLUGIN_EXCEPTION' and exc.details[2] == 'Failure'): @@ -226,7 +226,7 @@ def _unwrap_plugin_exceptions(func, *args, **kwargs): else: raise except xmlrpclib.ProtocolError, exc: - logging.debug("Got exception: %s", exc) + logging.debug(_("Got exception: %s"), exc) raise diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 156aad2a09..1e7b184bb8 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -76,14 +76,14 @@ class VolumeDriver(object): tries = tries + 1 if tries >= FLAGS.num_shell_tries: raise - logging.exception("Recovering from a failed execute." - "Try number %s", tries) + logging.exception(_("Recovering from a failed execute." + "Try number %s"), tries) yield self._execute("sleep %s" % tries ** 2) def check_for_setup_error(self): """Returns an error if prerequisites aren't met""" if not os.path.isdir("/dev/%s" % FLAGS.volume_group): - raise exception.Error("volume group %s doesn't exist" + raise exception.Error(_("volume group %s doesn't exist") % FLAGS.volume_group) @defer.inlineCallbacks @@ -220,7 +220,7 @@ class FakeAOEDriver(AOEDriver): @staticmethod def fake_execute(cmd, *_args, **_kwargs): """Execute that simply logs the command.""" - logging.debug("FAKE AOE: %s", cmd) + logging.debug(_("FAKE AOE: %s"), cmd) return (None, None) @@ -332,5 +332,5 @@ class FakeISCSIDriver(ISCSIDriver): @staticmethod def fake_execute(cmd, *_args, **_kwargs): """Execute that simply logs the command.""" - logging.debug("FAKE ISCSI: %s", cmd) + logging.debug(_("FAKE ISCSI: %s"), cmd) return (None, None) diff --git a/nova/volume/manager.py b/nova/volume/manager.py index 589e7d7d97..a48080511a 100644 --- a/nova/volume/manager.py +++ b/nova/volume/manager.py @@ -82,7 +82,7 @@ class VolumeManager(manager.Manager): self.driver.check_for_setup_error() ctxt = context.get_admin_context() volumes = self.db.volume_get_all_by_host(ctxt, self.host) - logging.debug("Re-exporting %s volumes", len(volumes)) + logging.debug(_("Re-exporting %s volumes"), len(volumes)) for volume in volumes: self.driver.ensure_export(ctxt, volume) @@ -91,7 +91,7 @@ class VolumeManager(manager.Manager): """Creates and exports the volume.""" context = context.elevated() volume_ref = self.db.volume_get(context, volume_id) - logging.info("volume %s: creating", volume_ref['name']) + logging.info(_("volume %s: creating"), volume_ref['name']) self.db.volume_update(context, volume_id, @@ -100,18 +100,18 @@ class VolumeManager(manager.Manager): # before passing it to the driver. volume_ref['host'] = self.host - logging.debug("volume %s: creating lv of size %sG", + logging.debug(_("volume %s: creating lv of size %sG"), volume_ref['name'], volume_ref['size']) yield self.driver.create_volume(volume_ref) - logging.debug("volume %s: creating export", volume_ref['name']) + logging.debug(_("volume %s: creating export"), volume_ref['name']) yield self.driver.create_export(context, volume_ref) now = datetime.datetime.utcnow() self.db.volume_update(context, volume_ref['id'], {'status': 'available', 'launched_at': now}) - logging.debug("volume %s: created successfully", volume_ref['name']) + logging.debug(_("volume %s: created successfully"), volume_ref['name']) defer.returnValue(volume_id) @defer.inlineCallbacks @@ -120,15 +120,15 @@ class VolumeManager(manager.Manager): context = context.elevated() volume_ref = self.db.volume_get(context, volume_id) if volume_ref['attach_status'] == "attached": - raise exception.Error("Volume is still attached") + raise exception.Error(_("Volume is still attached")) if volume_ref['host'] != self.host: - raise exception.Error("Volume is not local to this node") - logging.debug("volume %s: removing export", volume_ref['name']) + raise exception.Error(_("Volume is not local to this node")) + logging.debug(_("volume %s: removing export"), volume_ref['name']) yield self.driver.remove_export(context, volume_ref) - logging.debug("volume %s: deleting", volume_ref['name']) + logging.debug(_("volume %s: deleting"), volume_ref['name']) yield self.driver.delete_volume(volume_ref) self.db.volume_destroy(context, volume_id) - logging.debug("volume %s: deleted successfully", volume_ref['name']) + logging.debug(_("volume %s: deleted successfully"), volume_ref['name']) defer.returnValue(True) @defer.inlineCallbacks From 8e642730b1e32477bcd124592f2c9e00857da1b9 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Mon, 13 Dec 2010 20:02:27 +0300 Subject: [PATCH 126/229] 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 17a1a4c5c1..52451b8cbd 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 From 669d5f5612840c9ed6449d91ee5aae97842cac72 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Mon, 13 Dec 2010 18:22:56 +0000 Subject: [PATCH 127/229] second round for unit testing framework --- nova/tests/virt_unittest.py | 65 ++++++++++++++++++++++--------- nova/virt/xenapi/__init__.py | 5 +++ nova/virt/xenapi/fake.py | 75 ++++++++++++++++++++++++++++++++++-- nova/virt/xenapi/vmops.py | 3 +- nova/virt/xenapi_conn.py | 2 +- 5 files changed, 127 insertions(+), 23 deletions(-) diff --git a/nova/tests/virt_unittest.py b/nova/tests/virt_unittest.py index ba3fba83b3..6110226320 100644 --- a/nova/tests/virt_unittest.py +++ b/nova/tests/virt_unittest.py @@ -30,6 +30,9 @@ from nova.virt.xenapi import volume_utils FLAGS = flags.FLAGS flags.DECLARE('instances_path', 'nova.compute.manager') +# Those are XenAPI related +flags.DECLARE('target_host', 'nova.virt.xenapi_conn') +FLAGS.target_host = '127.0.0.1' class LibvirtConnTestCase(test.TrialTestCase): @@ -270,36 +273,62 @@ class XenAPIVolumeTestCase(test.TrialTestCase): self.helper = volume_utils.VolumeHelper self.helper.late_import() + def _create_volume(self, size='0'): + """Create a volume object.""" + vol = {} + vol['size'] = size + vol['user_id'] = 'fake' + vol['project_id'] = 'fake' + vol['host'] = 'localhost' + vol['availability_zone'] = FLAGS.storage_availability_zone + vol['status'] = "creating" + vol['attach_status'] = "detached" + return db.volume_create(context.get_admin_context(), vol) + def test_create_iscsi_storage_raise_no_exception(self): - info = self.helper.parse_volume_info(None, None) - label = 'SR-' - description = '' - self.helper.create_iscsi_storage_blocking(self.session, - info, - label, - description) + vol = self._create_volume() + info = yield self.helper.parse_volume_info(vol['ec2_id'], '/dev/sdc') + label = None # For testing new SRs + description = 'Test-SR' + self.session.fail_next_call = False + sr_ref = self.helper.create_iscsi_storage_blocking(self.session, + info, + label, + description) + self.assertEqual(sr_ref, self.session.SR.FAKE_REF) + db.volume_destroy(context.get_admin_context(), vol['id']) def test_create_iscsi_storage_raise_unable_to_create_sr_exception(self): - info = self.helper.parse_volume_info(None, None) - label = None + vol = self._create_volume() + info = yield self.helper.parse_volume_info(vol['ec2_id'], '/dev/sdc') + label = None # For testing new SRs description = None - self.assertFailure(self.helper.create_iscsi_storage_blocking(self.session, - info, - label, - description), - StorageError) + self.session.fail_next_call = True + self.assertRaises(volume_utils.StorageError, + self.helper.create_iscsi_storage_blocking, + self.session, + info, + label, + description) def test_find_sr_from_vbd_raise_no_exception(self): - pass + sr_ref = yield self.helper.find_sr_from_vbd(self.session, + self.session.VBD.FAKE_REF) + self.assertEqual(sr_ref, self.session.SR.FAKE_REF) - def test_destroy_iscsi_storage_raise_no_exception(self): + def test_destroy_iscsi_storage(self): pass def test_introduce_vdi_raise_no_exception(self): - pass + sr_ref = self.session.SR.FAKE_REF + self.helper.introduce_vdi_blocking(self.session, sr_ref) def test_introduce_vdi_raise_unable_get_vdi_record_exception(self): - pass + sr_ref = self.session.SR.FAKE_REF + self.session.fail_next_call = True + self.assertRaises(volume_utils.StorageError, + self.helper.introduce_vdi_blocking, + self.session, sr_ref) def tearDown(self): super(XenAPIVolumeTestCase, self).tearDown() diff --git a/nova/virt/xenapi/__init__.py b/nova/virt/xenapi/__init__.py index 3d598c463c..d9abe54c53 100644 --- a/nova/virt/xenapi/__init__.py +++ b/nova/virt/xenapi/__init__.py @@ -13,3 +13,8 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + +""" +:mod:`xenapi` -- Nova support for XenServer and XCP through XenAPI +================================================================== +""" diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index 75ff587e1f..3a01f9c3d2 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -1,4 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 +from twisted.web.domhelpers import _get +from aptdaemon.defer import defer # Copyright (c) 2010 Citrix Systems, Inc. # @@ -33,15 +35,15 @@ class Failure(Exception): class FakeXenAPISession(object): """ The session to invoke XenAPI SDK calls """ def __init__(self): - pass + self.fail_next_call = False def get_xenapi(self): """ Return the xenapi object """ - raise NotImplementedError() + return self def get_xenapi_host(self): """ Return the xenapi host """ - raise NotImplementedError() + return 'FAKE_XENAPI_HOST' def call_xenapi(self, method, *args): """Call the specified XenAPI method on a background thread. Returns @@ -57,3 +59,70 @@ class FakeXenAPISession(object): """Return a Deferred that will give the result of the given task. The task is polled until it completes.""" raise NotImplementedError() + + def __getattr__(self, name): + return FakeXenAPIObject(name, self) + + +class FakeXenAPIObject(object): + def __init__(self, name, session): + self.name = name + self.session = session + self.FAKE_REF = 'FAKE_REFERENCE_%s' % name + + def get_by_name_label(self, label): + if label is None: + return '' # 'No object found' + else: + return 'FAKE_OBJECT_%s_%s' % (self.name, label) + + def getter(self, *args): + self._check_fail() + return self.FAKE_REF + + def ref_list(self, *args): + self._check_fail() + return [FakeXenAPIRecord()] + + def __getattr__(self, name): + if name == 'create': + return self._create + elif name == 'get_record': + return self._record + elif name == 'introduce': + return self._introduce + elif name.startswith('get_'): + getter = 'get_%s' % self.name + if name == getter: + return self.getter + else: + child = name[name.find('_') + 1:] + if child.endswith('s'): + return FakeXenAPIObject(child[:-1], self.session).ref_list + else: + return FakeXenAPIObject(child, self.session).getter + + def _create(self, *args): + self._check_fail() + return self.FAKE_REF + + def _record(self, *args): + self._check_fail() + return FakeXenAPIRecord() + + def _introduce(self, *args): + self._check_fail() + pass + + def _check_fail(self): + if self.session.fail_next_call: + self.session.fail_next_call = False # Reset! + raise Failure('Unable to create %s' % self.name) + + +class FakeXenAPIRecord(dict): + def __init__(self): + pass + + def __getitem__(self, attr): + return '' diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index a5d923a3b7..9a8db0ad43 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -24,6 +24,7 @@ from twisted.internet import defer from nova import db from nova import context +from nova import exception from nova.auth.manager import AuthManager from nova.virt.xenapi.network_utils import NetworkHelper @@ -123,7 +124,7 @@ class VMOps(object): """ Return data about VM instance """ vm = VMHelper.lookup_blocking(self._session, instance_id) if vm is None: - raise Exception('instance not present %s' % instance_id) + raise exception.NotFound('Instance not found %s' % instance_id) rec = self._session.get_xenapi().VM.get_record(vm) return VMHelper.compile_info(rec) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 4ace6da148..d8d21e24de 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -91,7 +91,7 @@ flags.DEFINE_string('target_port', '3260', 'iSCSI Target Port, 3260 Default') flags.DEFINE_string('iqn_prefix', - 'iqn.2010-10.org.openstack', + 'iqn.2010-12.org.openstack', 'IQN Prefix') From fe667352c3e25c744a989ca45f4f9ed472778ae3 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Mon, 13 Dec 2010 18:43:24 +0000 Subject: [PATCH 128/229] removing imports that should have not been there --- nova/tests/virt_unittest.py | 3 ++- nova/virt/xenapi/fake.py | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/nova/tests/virt_unittest.py b/nova/tests/virt_unittest.py index 6110226320..1095662c30 100644 --- a/nova/tests/virt_unittest.py +++ b/nova/tests/virt_unittest.py @@ -317,7 +317,8 @@ class XenAPIVolumeTestCase(test.TrialTestCase): self.assertEqual(sr_ref, self.session.SR.FAKE_REF) def test_destroy_iscsi_storage(self): - pass + sr_ref = self.session.SR.FAKE_REF + self.helper.destroy_iscsi_storage_blocking(self.session, sr_ref) def test_introduce_vdi_raise_no_exception(self): sr_ref = self.session.SR.FAKE_REF diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index 3a01f9c3d2..2fed286094 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -1,6 +1,4 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -from twisted.web.domhelpers import _get -from aptdaemon.defer import defer # Copyright (c) 2010 Citrix Systems, Inc. # @@ -89,8 +87,10 @@ class FakeXenAPIObject(object): return self._create elif name == 'get_record': return self._record - elif name == 'introduce': - return self._introduce + elif name == 'introduce' or\ + name == 'forget' or\ + name == 'unplug': + return self._fake_action elif name.startswith('get_'): getter = 'get_%s' % self.name if name == getter: @@ -110,7 +110,7 @@ class FakeXenAPIObject(object): self._check_fail() return FakeXenAPIRecord() - def _introduce(self, *args): + def _fake_action(self, *args): self._check_fail() pass From c06ec98897e7c3e33f15d45ba2704b0d4b77a453 Mon Sep 17 00:00:00 2001 From: Chmouel Boudjnah Date: Mon, 13 Dec 2010 18:56:07 +0000 Subject: [PATCH 129/229] Add myself. --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index a1703b279d..64aabbdbc7 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 From e30801445f8b543d78494ca63be60f85b94d3a53 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Mon, 13 Dec 2010 20:31:33 +0000 Subject: [PATCH 130/229] moving xenapi unittests changes into another branch --- nova/tests/virt_unittest.py | 76 --------------------- nova/virt/xenapi/fake.py | 128 ------------------------------------ 2 files changed, 204 deletions(-) delete mode 100644 nova/virt/xenapi/fake.py diff --git a/nova/tests/virt_unittest.py b/nova/tests/virt_unittest.py index 1095662c30..d49383fb7b 100644 --- a/nova/tests/virt_unittest.py +++ b/nova/tests/virt_unittest.py @@ -25,14 +25,9 @@ from nova import utils from nova.api.ec2 import cloud from nova.auth import manager from nova.virt import libvirt_conn -from nova.virt.xenapi import fake -from nova.virt.xenapi import volume_utils FLAGS = flags.FLAGS flags.DECLARE('instances_path', 'nova.compute.manager') -# Those are XenAPI related -flags.DECLARE('target_host', 'nova.virt.xenapi_conn') -FLAGS.target_host = '127.0.0.1' class LibvirtConnTestCase(test.TrialTestCase): @@ -262,74 +257,3 @@ class NWFilterTestCase(test.TrialTestCase): d.addCallback(lambda _: self.teardown_security_group()) return d - - -class XenAPIVolumeTestCase(test.TrialTestCase): - - def setUp(self): - super(XenAPIVolumeTestCase, self).setUp() - self.flags(xenapi_use_fake_session=True) - self.session = fake.FakeXenAPISession() - self.helper = volume_utils.VolumeHelper - self.helper.late_import() - - def _create_volume(self, size='0'): - """Create a volume object.""" - vol = {} - vol['size'] = size - vol['user_id'] = 'fake' - vol['project_id'] = 'fake' - vol['host'] = 'localhost' - vol['availability_zone'] = FLAGS.storage_availability_zone - vol['status'] = "creating" - vol['attach_status'] = "detached" - return db.volume_create(context.get_admin_context(), vol) - - def test_create_iscsi_storage_raise_no_exception(self): - vol = self._create_volume() - info = yield self.helper.parse_volume_info(vol['ec2_id'], '/dev/sdc') - label = None # For testing new SRs - description = 'Test-SR' - self.session.fail_next_call = False - sr_ref = self.helper.create_iscsi_storage_blocking(self.session, - info, - label, - description) - self.assertEqual(sr_ref, self.session.SR.FAKE_REF) - db.volume_destroy(context.get_admin_context(), vol['id']) - - def test_create_iscsi_storage_raise_unable_to_create_sr_exception(self): - vol = self._create_volume() - info = yield self.helper.parse_volume_info(vol['ec2_id'], '/dev/sdc') - label = None # For testing new SRs - description = None - self.session.fail_next_call = True - self.assertRaises(volume_utils.StorageError, - self.helper.create_iscsi_storage_blocking, - self.session, - info, - label, - description) - - def test_find_sr_from_vbd_raise_no_exception(self): - sr_ref = yield self.helper.find_sr_from_vbd(self.session, - self.session.VBD.FAKE_REF) - self.assertEqual(sr_ref, self.session.SR.FAKE_REF) - - def test_destroy_iscsi_storage(self): - sr_ref = self.session.SR.FAKE_REF - self.helper.destroy_iscsi_storage_blocking(self.session, sr_ref) - - def test_introduce_vdi_raise_no_exception(self): - sr_ref = self.session.SR.FAKE_REF - self.helper.introduce_vdi_blocking(self.session, sr_ref) - - def test_introduce_vdi_raise_unable_get_vdi_record_exception(self): - sr_ref = self.session.SR.FAKE_REF - self.session.fail_next_call = True - self.assertRaises(volume_utils.StorageError, - self.helper.introduce_vdi_blocking, - self.session, sr_ref) - - def tearDown(self): - super(XenAPIVolumeTestCase, self).tearDown() diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py deleted file mode 100644 index 2fed286094..0000000000 --- a/nova/virt/xenapi/fake.py +++ /dev/null @@ -1,128 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2010 Citrix Systems, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -A fake XenAPI SDK. - -Allows for xenapi helper classes testing. -""" - - -class Failure(Exception): - def __init__(self, message=None): - super(Failure, self).__init__(message) - self.details = [] - - def __str__(self): - return 'Fake XenAPI Exception' - - -class FakeXenAPISession(object): - """ The session to invoke XenAPI SDK calls """ - def __init__(self): - self.fail_next_call = False - - def get_xenapi(self): - """ Return the xenapi object """ - return self - - def get_xenapi_host(self): - """ Return the xenapi host """ - return 'FAKE_XENAPI_HOST' - - def call_xenapi(self, method, *args): - """Call the specified XenAPI method on a background thread. Returns - a Deferred for the result.""" - raise NotImplementedError() - - def async_call_plugin(self, plugin, fn, args): - """Call Async.host.call_plugin on a background thread. Returns a - Deferred with the task reference.""" - raise NotImplementedError() - - def wait_for_task(self, task): - """Return a Deferred that will give the result of the given task. - The task is polled until it completes.""" - raise NotImplementedError() - - def __getattr__(self, name): - return FakeXenAPIObject(name, self) - - -class FakeXenAPIObject(object): - def __init__(self, name, session): - self.name = name - self.session = session - self.FAKE_REF = 'FAKE_REFERENCE_%s' % name - - def get_by_name_label(self, label): - if label is None: - return '' # 'No object found' - else: - return 'FAKE_OBJECT_%s_%s' % (self.name, label) - - def getter(self, *args): - self._check_fail() - return self.FAKE_REF - - def ref_list(self, *args): - self._check_fail() - return [FakeXenAPIRecord()] - - def __getattr__(self, name): - if name == 'create': - return self._create - elif name == 'get_record': - return self._record - elif name == 'introduce' or\ - name == 'forget' or\ - name == 'unplug': - return self._fake_action - elif name.startswith('get_'): - getter = 'get_%s' % self.name - if name == getter: - return self.getter - else: - child = name[name.find('_') + 1:] - if child.endswith('s'): - return FakeXenAPIObject(child[:-1], self.session).ref_list - else: - return FakeXenAPIObject(child, self.session).getter - - def _create(self, *args): - self._check_fail() - return self.FAKE_REF - - def _record(self, *args): - self._check_fail() - return FakeXenAPIRecord() - - def _fake_action(self, *args): - self._check_fail() - pass - - def _check_fail(self): - if self.session.fail_next_call: - self.session.fail_next_call = False # Reset! - raise Failure('Unable to create %s' % self.name) - - -class FakeXenAPIRecord(dict): - def __init__(self): - pass - - def __getitem__(self, attr): - return '' From 72b18d065669a01d8d083aa3edcc726be9be6547 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 14 Dec 2010 00:20:27 +0000 Subject: [PATCH 131/229] simplified version using original logic --- nova/objectstore/image.py | 51 ++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/nova/objectstore/image.py b/nova/objectstore/image.py index c4d752124c..34a90b0a27 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' + try: + kernel_id = manifest.find("machine_configuration/kernel_id").text + if kernel_id == 'true': + image_type = 'kernel' + except: + kernel_id = None + + try: + ramdisk_id = manifest.find("machine_configuration/ramdisk_id").text + if ramdisk_id == 'true': + image_type = 'ramdisk' + except: + 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': 'x86_64', - 'imageType': 'machine' - } + 'architecture': arch, + 'imageType': image_type} - manifest = ElementTree.fromstring(bucket_object[manifest_path].read()) + if kernel_id: + info['kernelId'] = kernel_id - 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 - except: - pass - try: - ramdisk_id = manifest.find("machine_configuration/ramdisk_id").text - if ramdisk_id == 'true': - info['imageType'] = 'ramdisk' - else: - info['ramdiskId'] = ramdisk_id - except: - pass + if ramdisk_id: + info['ramdiskId'] = ramdisk_id def write_state(state): info['imageState'] = state From 1395d31badc43bdce036e8da3927af22a22ca91e Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 13 Dec 2010 18:25:17 -0800 Subject: [PATCH 132/229] Fixed power state update with Twisted callback --- nova/compute/manager.py | 21 +++++++++++++++++---- nova/virt/xenapi/vmops.py | 12 ++++++++---- nova/virt/xenapi_conn.py | 8 ++++---- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 61ed3136b5..ae33fe5b9f 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -194,6 +194,13 @@ class ComputeManager(manager.Manager): yield self.driver.unrescue(instance_ref) self._update_state(context, instance_id) + @staticmethod + def _update_state_callback(self, context, instance_id, result): + """Update instance state when Deferred task completes. + This staticmethod must be wrappered in a + lambda to pass in self, context & instance_id.""" + self._update_state(context, instance_id) + @defer.inlineCallbacks @exception.wrap_exception def pause_instance(self, context, instance_id): @@ -207,8 +214,11 @@ class ComputeManager(manager.Manager): instance_id, power_state.NOSTATE, 'pausing') - yield self.driver.pause(instance_ref) - self._update_state(context, instance_id) + yield self.driver.pause(instance_ref, + lambda result : self._update_state_callback(self, + context, + instance_id, + result)) @defer.inlineCallbacks @exception.wrap_exception @@ -223,8 +233,11 @@ class ComputeManager(manager.Manager): instance_id, power_state.NOSTATE, 'unpausing') - yield self.driver.unpause(instance_ref) - self._update_state(context, instance_id) + yield self.driver.unpause(instance_ref, + lambda result : self._update_state_callback(self, + context, + instance_id, + result)) @exception.wrap_exception def get_console_output(self, context, instance_id): diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 353e83873b..03ee3dd585 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -121,24 +121,28 @@ class VMOps(object): logging.warn(exc) @defer.inlineCallbacks - def pause(self, instance): + def pause(self, instance, callback): """ Pause VM instance """ instance_name = instance.name vm = yield VMHelper.lookup(self._session, instance_name) if vm is None: raise Exception('instance not present %s' % instance_name) task = yield self._session.call_xenapi('Async.VM.pause', vm) - yield self._session.wait_for_task(task) + deferred = self._session.wait_for_task(task) + deferred.addCallback(callback) + yield deferred @defer.inlineCallbacks - def unpause(self, instance): + def unpause(self, instance, callback): """ Unpause VM instance """ instance_name = instance.name vm = yield VMHelper.lookup(self._session, instance_name) if vm is None: raise Exception('instance not present %s' % instance_name) task = yield self._session.call_xenapi('Async.VM.unpause', vm) - yield self._session.wait_for_task(task) + deferred = self._session.wait_for_task(task) + deferred.addCallback(callback) + yield deferred def get_info(self, instance_id): """ Return data about VM instance """ diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index df405e75fc..bcfb483232 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -122,13 +122,13 @@ class XenAPIConnection(object): """ Destroy VM instance """ self._vmops.destroy(instance) - def pause(self, instance): + def pause(self, instance, callback): """ Pause VM instance """ - self._vmops.pause(instance) + self._vmops.pause(instance, callback) - def unpause(self, instance): + def unpause(self, instance, callback): """ Unpause paused VM instance """ - self._vmops.unpause(instance) + self._vmops.unpause(instance, callback) def get_info(self, instance_id): """ Return data about VM instance """ From 99ba9bc7c4cd7bdb085e76a8f926ade27d558a84 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 14 Dec 2010 02:43:15 -0400 Subject: [PATCH 133/229] added callback param to fake_conn --- nova/virt/fake.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 4526f00426..c56907175e 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -133,13 +133,13 @@ class FakeConnection(object): """ return defer.succeed(None) - def pause(self, instance): + def pause(self, instance, callback): """ Pause the specified instance. """ return defer.succeed(None) - def unpause(self, instance): + def unpause(self, instance, callback): """ Unpause the specified instance. """ From 6e37cf42d758b5040442d9c296b21955d10a7327 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Tue, 14 Dec 2010 11:48:29 +0000 Subject: [PATCH 134/229] final cleanup, after moving unittest work into another branch --- nova/virt/xenapi/__init__.py | 30 ++++++++++++++ nova/virt/xenapi/fake.py | 66 +++++++++++++++++++++++++++++++ nova/virt/xenapi/network_utils.py | 3 +- nova/virt/xenapi/vm_utils.py | 19 +-------- nova/virt/xenapi/vmops.py | 6 ++- nova/virt/xenapi/volume_utils.py | 20 +--------- nova/virt/xenapi/volumeops.py | 8 ++-- nova/virt/xenapi_conn.py | 10 +++-- 8 files changed, 118 insertions(+), 44 deletions(-) create mode 100644 nova/virt/xenapi/fake.py diff --git a/nova/virt/xenapi/__init__.py b/nova/virt/xenapi/__init__.py index d9abe54c53..1a2903b98d 100644 --- a/nova/virt/xenapi/__init__.py +++ b/nova/virt/xenapi/__init__.py @@ -18,3 +18,33 @@ :mod:`xenapi` -- Nova support for XenServer and XCP through XenAPI ================================================================== """ + + +def load_sdk(flags): + """ + This method is used for loading the XenAPI SDK (fake or real) + """ + xenapi_module = \ + flags.xenapi_use_fake_session and 'nova.virt.xenapi.fake' or 'XenAPI' + from_list = \ + flags.xenapi_use_fake_session and ['fake'] or [] + + return __import__(xenapi_module, globals(), locals(), from_list, -1) + + +class HelperBase(): + """ + The class that wraps the helper methods together. + """ + XenAPI = None + + def __init__(self): + return + + @classmethod + def late_import(cls, FLAGS): + """ + Load XenAPI module in for helper class + """ + if cls.XenAPI is None: + cls.XenAPI = load_sdk(FLAGS) diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py new file mode 100644 index 0000000000..8d6a39a87e --- /dev/null +++ b/nova/virt/xenapi/fake.py @@ -0,0 +1,66 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Citrix Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +A fake XenAPI SDK. + +Allows for xenapi helper classes testing. +""" + + +class Failure(Exception): + def __init__(self, message=None): + super(Failure, self).__init__(message) + self.details = [] + + def __str__(self): + return 'Fake XenAPI Exception' + + +class FakeSession(object): + """ + The session to invoke XenAPI SDK calls. + FIXME(armando): this is a placeholder + for the xenapi unittests branch. + """ + def __init__(self, url): + pass + + def get_xenapi(self): + """ Return the xenapi object """ + raise NotImplementedError() + + def get_xenapi_host(self): + """ Return the xenapi host """ + raise NotImplementedError() + + def call_xenapi(self, method, *args): + """Call the specified XenAPI method on a background thread. Returns + a Deferred for the result.""" + raise NotImplementedError() + + def async_call_plugin(self, plugin, fn, args): + """Call Async.host.call_plugin on a background thread. Returns a + Deferred with the task reference.""" + raise NotImplementedError() + + def wait_for_task(self, task): + """Return a Deferred that will give the result of the given task. + The task is polled until it completes.""" + raise NotImplementedError() + + def __getattr__(self, name): + raise NotImplementedError() diff --git a/nova/virt/xenapi/network_utils.py b/nova/virt/xenapi/network_utils.py index 8cb4cce3a7..cb745cdafb 100644 --- a/nova/virt/xenapi/network_utils.py +++ b/nova/virt/xenapi/network_utils.py @@ -21,9 +21,10 @@ their lookup functions. """ from twisted.internet import defer +from nova.virt.xenapi import HelperBase -class NetworkHelper(): +class NetworkHelper(HelperBase): """ The class that wraps the helper methods together. """ diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 89831fe5d1..2b0691c016 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -32,6 +32,7 @@ from nova.auth.manager import AuthManager from nova.compute import instance_types from nova.compute import power_state from nova.virt import images +from nova.virt.xenapi import HelperBase from nova.virt.xenapi.volume_utils import StorageError FLAGS = flags.FLAGS @@ -44,29 +45,13 @@ XENAPI_POWER_STATE = { 'Crashed': power_state.CRASHED} -class VMHelper(): +class VMHelper(HelperBase): """ The class that wraps the helper methods together. """ - - XenAPI = None - def __init__(self): return - @classmethod - def late_import(cls): - """ - Load XenAPI module in for helper class - """ - xenapi_module = \ - FLAGS.xenapi_use_fake_session and 'nova.virt.xenapi.fake' or 'XenAPI' - from_list = \ - FLAGS.xenapi_use_fake_session and ['fake'] or [] - if cls.XenAPI is None: - cls.XenAPI = __import__(xenapi_module, - globals(), locals(), from_list, -1) - @classmethod @defer.inlineCallbacks def create_vm(cls, session, instance, kernel, ramdisk): diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 9a8db0ad43..c792459727 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -24,9 +24,11 @@ from twisted.internet import defer from nova import db from nova import context +from nova import flags from nova import exception from nova.auth.manager import AuthManager +from nova.virt.xenapi import load_sdk from nova.virt.xenapi.network_utils import NetworkHelper from nova.virt.xenapi.vm_utils import VMHelper @@ -36,10 +38,10 @@ class VMOps(object): Management class for VM-related tasks """ def __init__(self, session): - self.XenAPI = __import__('XenAPI') + self.XenAPI = load_sdk(flags.FLAGS) self._session = session # Load XenAPI module in the helper class - VMHelper.late_import() + VMHelper.late_import(flags.FLAGS) def list_instances(self): """ List VM instances """ diff --git a/nova/virt/xenapi/volume_utils.py b/nova/virt/xenapi/volume_utils.py index 05143ed91d..4482e465c2 100644 --- a/nova/virt/xenapi/volume_utils.py +++ b/nova/virt/xenapi/volume_utils.py @@ -30,7 +30,7 @@ from nova import context from nova import flags from nova import process from nova import utils - +from nova.virt.xenapi import HelperBase FLAGS = flags.FLAGS @@ -41,29 +41,13 @@ class StorageError(Exception): super(StorageError, self).__init__(message) -class VolumeHelper(): +class VolumeHelper(HelperBase): """ The class that wraps the helper methods together. """ - - XenAPI = None - def __init__(self): return - @classmethod - def late_import(cls): - """ - Load XenAPI module in for helper class - """ - xenapi_module = \ - FLAGS.xenapi_use_fake_session and 'nova.virt.xenapi.fake' or 'XenAPI' - from_list = \ - FLAGS.xenapi_use_fake_session and ['fake'] or [] - if cls.XenAPI is None: - cls.XenAPI = __import__(xenapi_module, - globals(), locals(), from_list, -1) - @classmethod @utils.deferredToThread def create_iscsi_storage(cls, session, info, label, description): diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xenapi/volumeops.py index b1afdc811d..1b337a6eda 100644 --- a/nova/virt/xenapi/volumeops.py +++ b/nova/virt/xenapi/volumeops.py @@ -21,6 +21,8 @@ import logging from twisted.internet import defer +from nova import flags +from nova.virt.xenapi import load_sdk from nova.virt.xenapi.vm_utils import VMHelper from nova.virt.xenapi.volume_utils import VolumeHelper from nova.virt.xenapi.volume_utils import StorageError @@ -31,11 +33,11 @@ class VolumeOps(object): Management class for Volume-related tasks """ def __init__(self, session): - self.XenAPI = __import__('XenAPI') + self.XenAPI = load_sdk(flags.FLAGS) self._session = session # Load XenAPI module in the helper classes respectively - VolumeHelper.late_import() - VMHelper.late_import() + VolumeHelper.late_import(flags.FLAGS) + VMHelper.late_import(flags.FLAGS) @defer.inlineCallbacks def attach_volume(self, instance_name, device_path, mountpoint): diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index d8d21e24de..649d5dd04d 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -59,6 +59,7 @@ from twisted.internet import reactor from nova import utils from nova import flags +from nova.virt.xenapi import load_sdk from nova.virt.xenapi.vmops import VMOps from nova.virt.xenapi.volumeops import VolumeOps @@ -91,7 +92,7 @@ flags.DEFINE_string('target_port', '3260', 'iSCSI Target Port, 3260 Default') flags.DEFINE_string('iqn_prefix', - 'iqn.2010-12.org.openstack', + 'iqn.2010-10.org.openstack', 'IQN Prefix') @@ -156,8 +157,11 @@ class XenAPISession(object): def __init__(self, url, user, pw): # This is loaded late so that there's no need to install this # library when not using XenAPI. - self.XenAPI = __import__('XenAPI') - self._session = self.XenAPI.Session(url) + self.XenAPI = load_sdk(FLAGS) + if FLAGS.xenapi_use_fake_session: + self._session = self.XenAPI.FakeSession(url) + else: + self._session = self.XenAPI.Session(url) self._session.login_with_password(user, pw) def get_xenapi(self): From d3a41eff912762dddd1516006da197f99af53b4e Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Tue, 14 Dec 2010 14:54:50 +0300 Subject: [PATCH 135/229] Added my contacts to Authors file --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index c9bf3b67c9..2810a1ddb9 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 From bfe019e0de486eea09e4702262cd228791a4694c Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 14 Dec 2010 15:33:18 +0100 Subject: [PATCH 136/229] Now that we have a templating engine, let's use it. Consolidate all the libvirt templates into one, extending the unit tests to make sure I didn't mess up. --- nova/tests/virt_unittest.py | 131 +++++++++++++++++---- nova/virt/libvirt.qemu.xml.template | 37 ------ nova/virt/libvirt.rescue.qemu.xml.template | 37 ------ nova/virt/libvirt.rescue.uml.xml.template | 26 ---- nova/virt/libvirt.rescue.xen.xml.template | 34 ------ nova/virt/libvirt.uml.xml.template | 26 ---- nova/virt/libvirt.xen.xml.template | 35 ------ nova/virt/libvirt.xml.template | 76 ++++++++++++ nova/virt/libvirt_conn.py | 60 ++-------- 9 files changed, 197 insertions(+), 265 deletions(-) delete mode 100644 nova/virt/libvirt.qemu.xml.template delete mode 100644 nova/virt/libvirt.rescue.qemu.xml.template delete mode 100644 nova/virt/libvirt.rescue.uml.xml.template delete mode 100644 nova/virt/libvirt.rescue.xen.xml.template delete mode 100644 nova/virt/libvirt.uml.xml.template delete mode 100644 nova/virt/libvirt.xen.xml.template create mode 100644 nova/virt/libvirt.xml.template diff --git a/nova/tests/virt_unittest.py b/nova/tests/virt_unittest.py index d49383fb7b..bcc995a5f8 100644 --- a/nova/tests/virt_unittest.py +++ b/nova/tests/virt_unittest.py @@ -40,19 +40,53 @@ class LibvirtConnTestCase(test.TrialTestCase): self.network = utils.import_object(FLAGS.network_manager) FLAGS.instances_path = '' - def test_get_uri_and_template(self): - ip = '10.11.12.13' + test_ip = '10.11.12.13' + test_instance = { + 'memory_kb' : '1024000', + 'basepath' : '/some/path', + 'bridge_name' : 'br100', + 'mac_address' : '02:12:34:46:56:67', + 'vcpus' : 2, + 'project_id' : 'fake', + 'bridge' : 'br101', + 'instance_type' : 'm1.small'} - instance = {'internal_id': 1, - 'memory_kb': '1024000', - 'basepath': '/some/path', - 'bridge_name': 'br100', - 'mac_address': '02:12:34:46:56:67', - 'vcpus': 2, - 'project_id': 'fake', - 'bridge': 'br101', - 'instance_type': 'm1.small'} + def test_xml_and_uri_no_ramdisk_no_kernel(self): + instance_data = dict(self.test_instance) + self.do_test_xml_and_uri(instance_data, + expect_kernel=False, expect_ramdisk=False) + def test_xml_and_uri_no_ramdisk(self): + instance_data = dict(self.test_instance) + instance_data['kernel_id'] = 'aki-deadbeef' + self.do_test_xml_and_uri(instance_data, + expect_kernel=True, expect_ramdisk=False) + + def test_xml_and_uri_no_kernel(self): + instance_data = dict(self.test_instance) + instance_data['ramdisk_id'] = 'ari-deadbeef' + self.do_test_xml_and_uri(instance_data, + expect_kernel=False, expect_ramdisk=False) + + def test_xml_and_uri(self): + instance_data = dict(self.test_instance) + instance_data['ramdisk_id'] = 'ari-deadbeef' + instance_data['kernel_id'] = 'aki-deadbeef' + self.do_test_xml_and_uri(instance_data, + expect_kernel=True, expect_ramdisk=True) + + def test_xml_and_uri_rescue(self): + instance_data = dict(self.test_instance) + instance_data['ramdisk_id'] = 'ari-deadbeef' + instance_data['kernel_id'] = 'aki-deadbeef' + self.do_test_xml_and_uri(instance_data, + expect_kernel=True, expect_ramdisk=True, + rescue=True) + + + def do_test_xml_and_uri(self, instance, + expect_ramdisk, expect_kernel, + rescue=False): user_context = context.RequestContext(project=self.project, user=self.user) instance_ref = db.instance_create(user_context, instance) @@ -60,13 +94,14 @@ class LibvirtConnTestCase(test.TrialTestCase): self.network.set_network_host(context.get_admin_context(), network_ref['id']) - fixed_ip = {'address': ip, - 'network_id': network_ref['id']} + fixed_ip = { 'address' : self.test_ip, + 'network_id' : network_ref['id'] } ctxt = context.get_admin_context() fixed_ip_ref = db.fixed_ip_create(ctxt, fixed_ip) - db.fixed_ip_update(ctxt, ip, {'allocated': True, - 'instance_id': instance_ref['id']}) + db.fixed_ip_update(ctxt, self.test_ip, + { 'allocated': True, + 'instance_id': instance_ref['id'] }) type_uri_map = {'qemu': ('qemu:///system', [(lambda t: t.find('.').get('type'), 'qemu'), @@ -78,23 +113,71 @@ class LibvirtConnTestCase(test.TrialTestCase): (lambda t: t.find('./devices/emulator'), None)]), 'uml': ('uml:///system', [(lambda t: t.find('.').get('type'), 'uml'), - (lambda t: t.find('./os/type').text, 'uml')])} + (lambda t: t.find('./os/type').text, 'uml')]), + 'xen': ('xen:///', + [(lambda t: t.find('.').get('type'), 'xen'), + (lambda t: t.find('./os/type').text, 'linux')]), + } + + for hypervisor_type in ['qemu', 'kvm', 'xen']: + check_list = type_uri_map[hypervisor_type][1] + + if rescue: + check = (lambda t: t.find('./os/kernel').text.split('/')[1], + 'rescue-kernel') + check_list.append(check) + check = (lambda t: t.find('./os/initrd').text.split('/')[1], + 'rescue-ramdisk') + check_list.append(check) + else: + if expect_kernel: + check = (lambda t: t.find('./os/kernel').text.split('/')[1], + 'kernel') + else: + check = (lambda t: t.find('./os/kernel'), None) + check_list.append(check) + + if expect_ramdisk: + check = (lambda t: t.find('./os/initrd').text.split('/')[1], + 'ramdisk') + else: + check = (lambda t: t.find('./os/initrd'), None) + check_list.append(check) common_checks = [ (lambda t: t.find('.').tag, 'domain'), - (lambda t: t.find('./devices/interface/filterref/parameter').\ - get('name'), 'IP'), - (lambda t: t.find('./devices/interface/filterref/parameter').\ - get('value'), '10.11.12.13')] + (lambda t: t.find('./devices/interface/filterref/parameter' + ).get('name'), 'IP'), + (lambda t: t.find('./devices/interface/filterref/parameter' + ).get('value'), '10.11.12.13'), + (lambda t: t.findall('./devices/interface/filterref/parameter' + )[1].get('name'), 'DHCPSERVER'), + (lambda t: t.findall('./devices/interface/filterref/parameter' + )[1].get('value'), '10.0.0.1'), + (lambda t: t.find('./devices/serial/source').get('path' + ).split('/')[1], 'console.log'), + (lambda t: t.find('./memory').text, '2097152')] + + if rescue: + common_checks += [(lambda t: t.findall('./devices/disk/source' + )[0].get('file').split('/')[1], + 'rescue-disk'), + (lambda t: t.findall('./devices/disk/source' + )[1].get('file').split('/')[1], + 'disk')] + else: + common_checks += [(lambda t: t.findall('./devices/disk/source' + )[0].get('file').split('/')[1], + 'disk')] for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems(): FLAGS.libvirt_type = libvirt_type conn = libvirt_conn.LibvirtConnection(True) - uri, _template, _rescue = conn.get_uri_and_templates() + uri = conn.get_uri() self.assertEquals(uri, expected_uri) - xml = conn.to_xml(instance_ref) + xml = conn.to_xml(instance_ref, rescue) tree = xml_to_tree(xml) for i, (check, expected_result) in enumerate(checks): self.assertEqual(check(tree), @@ -106,6 +189,8 @@ class LibvirtConnTestCase(test.TrialTestCase): expected_result, '%s failed common check %d' % (xml, i)) + # This test is supposed to make sure we don't override a specifically set uri + # # Deliberately not just assigning this string to FLAGS.libvirt_uri and # checking against that later on. This way we make sure the # implementation doesn't fiddle around with the FLAGS. @@ -114,7 +199,7 @@ class LibvirtConnTestCase(test.TrialTestCase): for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems(): FLAGS.libvirt_type = libvirt_type conn = libvirt_conn.LibvirtConnection(True) - uri, _template, _rescue = conn.get_uri_and_templates() + uri = conn.get_uri() self.assertEquals(uri, testuri) def tearDown(self): diff --git a/nova/virt/libvirt.qemu.xml.template b/nova/virt/libvirt.qemu.xml.template deleted file mode 100644 index 739eceaaaa..0000000000 --- a/nova/virt/libvirt.qemu.xml.template +++ /dev/null @@ -1,37 +0,0 @@ - - ${name} - - hvm -#if $getVar('kernel', None) - ${kernel} - #if $getVar('ramdisk', None) - ${ramdisk} - #end if - root=/dev/vda1 console=ttyS0 -#end if - - - - - ${memory_kb} - ${vcpus} - - - - - - - - - - - - - - - - - - - - diff --git a/nova/virt/libvirt.rescue.qemu.xml.template b/nova/virt/libvirt.rescue.qemu.xml.template deleted file mode 100644 index c0ffbdcee6..0000000000 --- a/nova/virt/libvirt.rescue.qemu.xml.template +++ /dev/null @@ -1,37 +0,0 @@ - - %(name)s - - hvm - %(basepath)s/rescue-kernel - %(basepath)s/rescue-ramdisk - root=/dev/vda1 console=ttyS0 - - - - - %(memory_kb)s - %(vcpus)s - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/nova/virt/libvirt.rescue.uml.xml.template b/nova/virt/libvirt.rescue.uml.xml.template deleted file mode 100644 index 836f475328..0000000000 --- a/nova/virt/libvirt.rescue.uml.xml.template +++ /dev/null @@ -1,26 +0,0 @@ - - %(name)s - %(memory_kb)s - - %(type)s - /usr/bin/linux - /dev/ubda1 - - - - - - - - - - - - - - - - - - - diff --git a/nova/virt/libvirt.rescue.xen.xml.template b/nova/virt/libvirt.rescue.xen.xml.template deleted file mode 100644 index 3b8d27237f..0000000000 --- a/nova/virt/libvirt.rescue.xen.xml.template +++ /dev/null @@ -1,34 +0,0 @@ - - %(name)s - - linux - %(basepath)s/kernel - %(basepath)s/ramdisk - /dev/xvda1 - ro - - - - - %(memory_kb)s - %(vcpus)s - - - - - - - - - - - - - - - - - - - - diff --git a/nova/virt/libvirt.uml.xml.template b/nova/virt/libvirt.uml.xml.template deleted file mode 100644 index da95880492..0000000000 --- a/nova/virt/libvirt.uml.xml.template +++ /dev/null @@ -1,26 +0,0 @@ - - ${name} - ${memory_kb} - - ${type} - /usr/bin/linux - /dev/ubda1 - - - - - - - - - - - - - - - - - - - diff --git a/nova/virt/libvirt.xen.xml.template b/nova/virt/libvirt.xen.xml.template deleted file mode 100644 index 8f650e5124..0000000000 --- a/nova/virt/libvirt.xen.xml.template +++ /dev/null @@ -1,35 +0,0 @@ - - ${name} - - linux -#if $getVar('kernel', None) - ${kernel} - #if $getVar('ramdisk', None) - ${ramdisk} - #end if - root=/dev/vda1 console=ttyS0 -#end if - /dev/xvda1 - ro - - - - - ${memory_kb} - ${vcpus} - - - - - - - - - - - - - - - - diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template new file mode 100644 index 0000000000..13d0873300 --- /dev/null +++ b/nova/virt/libvirt.xml.template @@ -0,0 +1,76 @@ + + ${name} + ${memory_kb} + +#if $type == 'uml' + #set $disk_prefix = 'ubd' + #set $disk_bus = 'uml' + uml + /usr/bin/linux + /dev/ubda1 +#else + #if $type == 'xen' + #set $disk_prefix = 'sd' + #set $disk_bus = 'scsi' + linux + /dev/xvda1 + #else + #set $disk_prefix = 'vd' + #set $disk_bus = 'virtio' + hvm + #end if + #if $getVar('rescue', False) + ${basepath}/rescue-kernel + ${basepath}/rescue-ramdisk + #else + #if $getVar('kernel', None) + ${kernel} + #if $type == 'xen' + ro + #else + root=/dev/vda1 console=ttyS0 + #end if + #if $getVar('ramdisk', None) + ${ramdisk} + #end if + #else + + #end if + #end if +#end if + + + + + ${vcpus} + +#if $getVar('rescue', False) + + + + + + + + +#else + + + + +#end if + + + + + + + + + + + + + + + diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 81dbbaad59..2865c18acd 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -27,12 +27,7 @@ Supports KVM, QEMU, UML, and XEN. :libvirt_type: Libvirt domain type. Can be kvm, qemu, uml, xen (default: kvm). :libvirt_uri: Override for the default libvirt URI (depends on libvirt_type). -:libvirt_xml_template: Libvirt XML Template (QEmu/KVM). -:libvirt_xen_xml_template: Libvirt XML Template (Xen). -:libvirt_uml_xml_template: Libvirt XML Template (User Mode Linux). -:libvirt_rescue_xml_template: XML template for rescue mode (KVM & QEMU). -:libvirt_rescue_xen_xml_template: XML templage for rescue mode (XEN). -:libvirt_rescue_uml_xml_template: XML template for rescue mode (UML). +:libvirt_xml_template: Libvirt XML Template. :rescue_image_id: Rescue ami image (default: ami-rescue). :rescue_kernel_id: Rescue aki image (default: aki-rescue). :rescue_ramdisk_id: Rescue ari image (default: ari-rescue). @@ -70,31 +65,13 @@ libxml2 = None FLAGS = flags.FLAGS -flags.DEFINE_string('libvirt_rescue_xml_template', - utils.abspath('virt/libvirt.rescue.qemu.xml.template'), - 'Libvirt RESCUE XML Template for QEmu/KVM') -flags.DEFINE_string('libvirt_rescue_xen_xml_template', - utils.abspath('virt/libvirt.rescue.xen.xml.template'), - 'Libvirt RESCUE XML Template for xen') -flags.DEFINE_string('libvirt_rescue_uml_xml_template', - utils.abspath('virt/libvirt.rescue.uml.xml.template'), - 'Libvirt RESCUE XML Template for user-mode-linux') # TODO(vish): These flags should probably go into a shared location flags.DEFINE_string('rescue_image_id', 'ami-rescue', 'Rescue ami image') flags.DEFINE_string('rescue_kernel_id', 'aki-rescue', 'Rescue aki image') flags.DEFINE_string('rescue_ramdisk_id', 'ari-rescue', 'Rescue ari image') flags.DEFINE_string('libvirt_xml_template', - utils.abspath('virt/libvirt.qemu.xml.template'), - 'Libvirt XML Template for QEmu/KVM') -flags.DEFINE_string('libvirt_xen_xml_template', - utils.abspath('virt/libvirt.xen.xml.template'), - 'Libvirt XML Template for Xen') -flags.DEFINE_string('libvirt_uml_xml_template', - utils.abspath('virt/libvirt.uml.xml.template'), - 'Libvirt XML Template for user-mode-linux') -flags.DEFINE_string('injected_network_template', - utils.abspath('virt/interfaces.template'), - 'Template file for injected network') + utils.abspath('virt/libvirt.xml.template'), + 'Libvirt XML Template') flags.DEFINE_string('libvirt_type', 'kvm', 'Libvirt domain type (valid options are: ' @@ -122,12 +99,9 @@ def get_connection(read_only): class LibvirtConnection(object): def __init__(self, read_only): - (self.libvirt_uri, - template_file, - rescue_file) = self.get_uri_and_templates() + self.libvirt_uri = self.get_uri() - self.libvirt_xml = open(template_file).read() - self.rescue_xml = open(rescue_file).read() + self.libvirt_xml = open(FLAGS.libvirt_xml_template).read() self._wrapped_conn = None self.read_only = read_only @@ -150,20 +124,14 @@ class LibvirtConnection(object): return False raise - def get_uri_and_templates(self): + def get_uri(self): if FLAGS.libvirt_type == 'uml': uri = FLAGS.libvirt_uri or 'uml:///system' - template_file = FLAGS.libvirt_uml_xml_template - rescue_file = FLAGS.libvirt_rescue_uml_xml_template elif FLAGS.libvirt_type == 'xen': uri = FLAGS.libvirt_uri or 'xen:///' - template_file = FLAGS.libvirt_xen_xml_template - rescue_file = FLAGS.libvirt_rescue_xen_xml_template else: uri = FLAGS.libvirt_uri or 'qemu:///system' - template_file = FLAGS.libvirt_xml_template - rescue_file = FLAGS.libvirt_rescue_xml_template - return uri, template_file, rescue_file + return uri def _connect(self, uri, read_only): auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_NOECHOPROMPT], @@ -543,18 +511,16 @@ class LibvirtConnection(object): 'bridge_name': network['bridge'], 'mac_address': instance['mac_address'], 'ip_address': ip_address, - 'dhcp_server': dhcp_server} - if rescue: - xml = self.rescue_xml % xml_info - else: - if xml_info['kernel_id']: + 'dhcp_server': dhcp_server, + 'rescue': rescue} + if not rescue: + if instance['kernel_id']: xml_info['kernel'] = xml_info['basepath'] + "/kernel" - if xml_info['ramdisk_id']: + if instance['ramdisk_id']: xml_info['ramdisk'] = xml_info['basepath'] + "/ramdisk" - if xml_info['ramdisk_id'] or xml_info['kernel_id']: - xml_info['disk'] = xml_info['basepath'] + "/disk" + xml_info['disk'] = xml_info['basepath'] + "/disk" xml = str(Template(self.libvirt_xml, searchList=[xml_info])) logging.debug('instance %s: finished toXML method', instance['name']) From fa7d288e6af3d997d6275d9e6778e932be9f1c3f Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 14 Dec 2010 17:56:42 -0400 Subject: [PATCH 137/229] pep8 --- nova/adminclient.py | 1 + nova/api/ec2/admin.py | 1 + nova/api/openstack/backup_schedules.py | 1 + nova/api/openstack/servers.py | 2 -- nova/compute/api.py | 1 - nova/compute/manager.py | 18 ++++++++---------- nova/db/sqlalchemy/api.py | 4 ++++ nova/exception.py | 3 +++ nova/process.py | 2 ++ nova/server.py | 2 +- nova/tests/api/openstack/test_servers.py | 4 ++-- nova/twistd.py | 2 +- nova/virt/fake.py | 1 + nova/virt/libvirt_conn.py | 5 +++-- nova/virt/xenapi/network_utils.py | 1 + nova/virt/xenapi/vm_utils.py | 1 + nova/virt/xenapi/vmops.py | 1 + nova/virt/xenapi/volumeops.py | 1 + nova/virt/xenapi_conn.py | 2 ++ 19 files changed, 34 insertions(+), 19 deletions(-) diff --git a/nova/adminclient.py b/nova/adminclient.py index 5a62cce7d2..6ae9f0c0f4 100644 --- a/nova/adminclient.py +++ b/nova/adminclient.py @@ -194,6 +194,7 @@ class HostInfo(object): class NovaAdminClient(object): + def __init__( self, clc_url=DEFAULT_CLC_URL, diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index 1c6ab688d4..fac01369ec 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -168,6 +168,7 @@ class AdminController(object): # FIXME(vish): these host commands don't work yet, perhaps some of the # required data can be retrieved from service objects? + def describe_hosts(self, _context, **_kwargs): """Returns status info for all nodes. Includes: * Disk Space diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index 3ed691d7b0..fc70b5c6c8 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -24,6 +24,7 @@ import nova.image.service class Controller(wsgi.Controller): + def __init__(self): pass diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index dcd959ae7d..5c3322f7ca 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -195,5 +195,3 @@ class Controller(wsgi.Controller): logging.error("Compute.api::unpause %s", readable) return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() - - diff --git a/nova/compute/api.py b/nova/compute/api.py index 79da79cd16..7420c40d2f 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -293,7 +293,6 @@ class ComputeAPI(base.Base): {"method": "unpause_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) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index ae33fe5b9f..0c0ba7450d 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -196,9 +196,7 @@ class ComputeManager(manager.Manager): @staticmethod def _update_state_callback(self, context, instance_id, result): - """Update instance state when Deferred task completes. - This staticmethod must be wrappered in a - lambda to pass in self, context & instance_id.""" + """Update instance state when Deferred task completes.""" self._update_state(context, instance_id) @defer.inlineCallbacks @@ -214,10 +212,10 @@ class ComputeManager(manager.Manager): instance_id, power_state.NOSTATE, 'pausing') - yield self.driver.pause(instance_ref, - lambda result : self._update_state_callback(self, - context, - instance_id, + yield self.driver.pause(instance_ref, + lambda result: self._update_state_callback(self, + context, + instance_id, result)) @defer.inlineCallbacks @@ -234,9 +232,9 @@ class ComputeManager(manager.Manager): power_state.NOSTATE, 'unpausing') yield self.driver.unpause(instance_ref, - lambda result : self._update_state_callback(self, - context, - instance_id, + lambda result: self._update_state_callback(self, + context, + instance_id, result)) @exception.wrap_exception diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 55036d1d10..9350636090 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -528,6 +528,8 @@ def fixed_ip_update(context, address, values): #TODO(gundlach): instance_create and volume_create are nearly identical #and should be refactored. I expect there are other copy-and-paste #functions between the two of them as well. + + @require_context def instance_create(context, values): """Create a new Instance record in the database. @@ -913,6 +915,8 @@ def network_get(context, network_id, session=None): # NOTE(vish): pylint complains because of the long method name, but # it fits with the names of the rest of the methods # pylint: disable-msg=C0103 + + @require_admin_context def network_get_associated_fixed_ips(context, network_id): session = get_session() diff --git a/nova/exception.py b/nova/exception.py index 6d6c373380..9af4017ba3 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -27,6 +27,7 @@ import traceback class ProcessExecutionError(IOError): + def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None, description=None): if description is None: @@ -39,11 +40,13 @@ class ProcessExecutionError(IOError): class Error(Exception): + def __init__(self, message=None): super(Error, self).__init__(message) class ApiError(Error): + def __init__(self, message='Unknown', code='Unknown'): self.message = message self.code = code diff --git a/nova/process.py b/nova/process.py index b33df048bf..39fddef6f7 100644 --- a/nova/process.py +++ b/nova/process.py @@ -40,6 +40,8 @@ flags.DEFINE_integer('process_pool_size', 4, # This is based on _BackRelay from twister.internal.utils, but modified to # capture both stdout and stderr, without odd stderr handling, and also to # handle stdin + + class BackRelayWithInput(protocol.ProcessProtocol): """ Trivial protocol for communicating with a process and turning its output diff --git a/nova/server.py b/nova/server.py index a0ee54681a..3b90861779 100644 --- a/nova/server.py +++ b/nova/server.py @@ -42,7 +42,7 @@ flags.DEFINE_bool('daemonize', False, 'daemonize this process') # clutter. flags.DEFINE_bool('use_syslog', True, 'output to syslog when daemonizing') flags.DEFINE_string('logfile', None, 'log file to output to') -flags.DEFINE_string('logdir', None, 'directory to keep log files in ' +flags.DEFINE_string('logdir', None, 'directory to keep log files in ' '(will be prepended to $logfile)') flags.DEFINE_string('pidfile', None, 'pid file to output to') flags.DEFINE_string('working_directory', './', 'working directory...') diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 8e48017d0f..ba432f6c32 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -84,9 +84,9 @@ class ServersTest(unittest.TestCase): instance_address) self.stubs.Set(nova.db.api, 'instance_get_floating_address', instance_address) - self.stubs.Set(nova.compute.api.ComputeAPI, 'pause', + self.stubs.Set(nova.compute.api.ComputeAPI, 'pause', fake_compute_api) - self.stubs.Set(nova.compute.api.ComputeAPI, 'unpause', + self.stubs.Set(nova.compute.api.ComputeAPI, 'unpause', fake_compute_api) self.allow_admin = FLAGS.allow_admin_api diff --git a/nova/twistd.py b/nova/twistd.py index cb5648ce64..e6c3101f1e 100644 --- a/nova/twistd.py +++ b/nova/twistd.py @@ -43,7 +43,7 @@ else: FLAGS = flags.FLAGS -flags.DEFINE_string('logdir', None, 'directory to keep log files in ' +flags.DEFINE_string('logdir', None, 'directory to keep log files in ' '(will be prepended to $logfile)') diff --git a/nova/virt/fake.py b/nova/virt/fake.py index c56907175e..59acabc217 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -259,5 +259,6 @@ class FakeConnection(object): class FakeInstance(object): + def __init__(self): self._state = power_state.NOSTATE diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 97ff49a10e..5939f0afe3 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -119,6 +119,7 @@ def get_connection(read_only): class LibvirtConnection(object): + def __init__(self, read_only): (self.libvirt_uri, template_file, @@ -298,12 +299,12 @@ class LibvirtConnection(object): @exception.wrap_exception def pause(self, instance, callback): raise exception.APIError("pause not supported for libvirt.") - + @defer.inlineCallbacks @exception.wrap_exception def unpause(self, instance, callback): raise exception.APIError("unpause not supported for libvirt.") - + @defer.inlineCallbacks @exception.wrap_exception def rescue(self, instance): diff --git a/nova/virt/xenapi/network_utils.py b/nova/virt/xenapi/network_utils.py index 8cb4cce3a7..cffaf7f23c 100644 --- a/nova/virt/xenapi/network_utils.py +++ b/nova/virt/xenapi/network_utils.py @@ -27,6 +27,7 @@ class NetworkHelper(): """ The class that wraps the helper methods together. """ + def __init__(self): return diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 77edb576e8..c17dc0beda 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -49,6 +49,7 @@ class VMHelper(): """ The class that wraps the helper methods together. """ + def __init__(self): return diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 7c5db0b730..405a8518e9 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -36,6 +36,7 @@ class VMOps(object): """ Management class for VM-related tasks """ + def __init__(self, session): global XenAPI if XenAPI is None: diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xenapi/volumeops.py index a4c7a38619..1943ccab0c 100644 --- a/nova/virt/xenapi/volumeops.py +++ b/nova/virt/xenapi/volumeops.py @@ -20,6 +20,7 @@ Management class for Storage-related functions (attach, detach, etc). class VolumeOps(object): + def __init__(self, session): self._session = session diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index ebd572258f..fa87bb779e 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -101,6 +101,7 @@ def get_connection(_): class XenAPIConnection(object): """ A connection to XenServer or Xen Cloud Platform """ + def __init__(self, url, user, pw): session = XenAPISession(url, user, pw) self._vmops = VMOps(session) @@ -155,6 +156,7 @@ class XenAPIConnection(object): class XenAPISession(object): """ The session to invoke XenAPI SDK calls """ + def __init__(self, url, user, pw): self._session = XenAPI.Session(url) self._session.login_with_password(user, pw) From aab6a89ba1e9ace73dcb4fa68a67957e29c47f84 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 14 Dec 2010 23:34:08 +0100 Subject: [PATCH 138/229] Don't attempt to fiddle with partitions for whole-disk-images. --- nova/compute/disk.py | 7 +++++++ nova/virt/libvirt_conn.py | 21 +++++++++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/nova/compute/disk.py b/nova/compute/disk.py index 9ba827519c..a77c30a19a 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -108,6 +108,13 @@ def partition(infile, outfile, local_bytes=0, resize=True, yield execute('parted --script %s mkpartfs primary %s %ds %ds' % (outfile, local_type, local_first, local_last)) +@defer.inlineCallbacks +def extend(image, size, execute): + file_size = os.path.getsize(image) + if file_size >= size: + return + yield execute('truncate -s size %s' % (image,)) + @defer.inlineCallbacks def inject_data(image, key=None, net=None, partition=None, execute=None): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 5dcb05b1f9..3529be333f 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -421,13 +421,13 @@ class LibvirtConnection(object): yield images.fetch(inst.image_id, basepath('disk-raw'), user, project) - if inst.kernel_id: + if inst['kernel_id']: if not os.path.exists(basepath('kernel')): - yield images.fetch(inst.kernel_id, basepath('kernel'), + yield images.fetch(inst['kernel_id'], basepath('kernel'), user, project) - if inst.ramdisk_id: + if inst['ramdisk_id']: if not os.path.exists(basepath('ramdisk')): - yield images.fetch(inst.ramdisk_id, basepath('ramdisk'), + yield images.fetch(inst['ramdisk_id'], basepath('ramdisk'), user, project) execute = lambda cmd, process_input = None, check_exit_code = True: \ @@ -439,7 +439,7 @@ class LibvirtConnection(object): # partitioned disk image where the target partition is the first # partition target_partition = None - if not inst.kernel_id: + if not inst['kernel_id']: target_partition = "1" key = str(inst['key_data']) @@ -472,7 +472,7 @@ class LibvirtConnection(object): ' into image %s (%s)', inst['name'], inst.image_id, e) - if inst.kernel_id: + if inst['kernel_id']: if os.path.exists(basepath('disk')): yield process.simple_execute('rm -f %s' % basepath('disk')) @@ -483,8 +483,13 @@ class LibvirtConnection(object): resize = True if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-': resize = False - yield disk.partition(basepath('disk-raw'), basepath('disk'), - local_bytes, resize, execute=execute) + + if inst['kernel_id']: + yield disk.partition(basepath('disk-raw'), basepath('disk'), + local_bytes, resize, execute=execute) + else: + os.rename(basepath('disk-raw'), basepath('disk')) + yield disk.extend(basepath('disk'), local_bytes, execute=execute) if FLAGS.libvirt_type == 'uml': yield process.simple_execute('sudo chown root %s' % From 9c2fa95298000c0c9d3953f0ef87823ed604949a Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 14 Dec 2010 23:35:54 +0100 Subject: [PATCH 139/229] Remove default_{kernel,ramdisk} flags. They are not used anymore. --- nova/flags.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nova/flags.py b/nova/flags.py index 45f5d74698..5c265f4eaa 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -228,10 +228,6 @@ DEFINE_string('ec2_url', 'http://127.0.0.1:8773/services/Cloud', DEFINE_string('default_image', 'ami-11111', 'default image to use, testing only') -DEFINE_string('default_kernel', 'aki-11111', - 'default kernel to use, testing only') -DEFINE_string('default_ramdisk', 'ari-11111', - 'default ramdisk to use, testing only') DEFINE_string('default_instance_type', 'm1.small', 'default instance type to use, testing only') DEFINE_string('null_kernel', 'nokernel', From 797e3f8a1cc72599aa8540b5655e29da8975e56f Mon Sep 17 00:00:00 2001 From: "jaypipes@gmail.com" <> Date: Tue, 14 Dec 2010 18:22:03 -0500 Subject: [PATCH 140/229] 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 4ed9e8365e..036b41e48b 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 9b6c40e820..5dac3ffe60 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 62eec8353f..0c1b621ed7 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 d1fb552612..86d04c723d 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 00ae27af93..9fbe228a2c 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 4d1a40cf10..41e1937c13 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 e7281d6c0b..4f2e962689 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 From a2a8406b5d793545c8ecb359e18b80bba618c509 Mon Sep 17 00:00:00 2001 From: termie Date: Tue, 14 Dec 2010 16:05:39 -0800 Subject: [PATCH 141/229] updates per review --- bin/nova-api | 9 ++-- bin/nova-combined | 3 -- bin/nova-scheduler | 2 +- nova/compute/manager.py | 4 +- nova/manager.py | 2 +- nova/rpc.py | 1 - nova/service.py | 69 +------------------------------ nova/tests/service_unittest.py | 13 ------ nova/utils.py | 2 +- nova/virt/fake.py | 4 +- nova/virt/libvirt_conn.py | 14 +++---- nova/virt/xenapi/network_utils.py | 5 +-- nova/virt/xenapi/vmops.py | 3 +- nova/virt/xenapi_conn.py | 6 ++- nova/volume/driver.py | 25 +++++------ run_tests.py | 5 --- 16 files changed, 35 insertions(+), 132 deletions(-) diff --git a/bin/nova-api b/bin/nova-api index 210486666f..3f433ea6dd 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -36,6 +36,7 @@ from nova import flags from nova import utils from nova import wsgi + FLAGS = flags.FLAGS flags.DEFINE_integer('osapi_port', 8774, 'OpenStack API port') flags.DEFINE_string('osapi_host', '0.0.0.0', 'OpenStack API host') @@ -43,14 +44,10 @@ flags.DEFINE_integer('ec2api_port', 8773, 'EC2 API port') flags.DEFINE_string('ec2api_host', '0.0.0.0', 'EC2 API host') -def main(): +if __name__ == '__main__': + utils.default_flagfile() FLAGS(sys.argv) server = wsgi.Server() server.start(api.API('os'), FLAGS.osapi_port, host=FLAGS.osapi_host) server.start(api.API('ec2'), FLAGS.ec2api_port, host=FLAGS.ec2api_host) server.wait() - - -if __name__ == '__main__': - utils.default_flagfile() - main() diff --git a/bin/nova-combined b/bin/nova-combined index e82b9c9b6f..5f635b3a3d 100755 --- a/bin/nova-combined +++ b/bin/nova-combined @@ -40,9 +40,6 @@ from nova import utils from nova import wsgi -FLAGS = flags.FLAGS -flags.DEFINE_integer('api_port', 8773, 'API port') - FLAGS = flags.FLAGS flags.DEFINE_integer('osapi_port', 8774, 'OpenStack API port') flags.DEFINE_string('osapi_host', '0.0.0.0', 'OpenStack API host') diff --git a/bin/nova-scheduler b/bin/nova-scheduler index a46dd8dda2..59cb060d43 100755 --- a/bin/nova-scheduler +++ b/bin/nova-scheduler @@ -17,7 +17,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" Starter script for Nova Scheduler.""" +"""Starter script for Nova Scheduler.""" import eventlet eventlet.monkey_patch() diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 051ce579d8..f90f28b78d 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -203,7 +203,7 @@ class ComputeManager(manager.Manager): volume_id, mountpoint) instance_ref = self.db.instance_get(context, instance_id) dev_path = self.volume_manager.setup_compute_volume(context, - volume_id) + volume_id) try: self.driver.attach_volume(instance_ref['name'], dev_path, @@ -238,7 +238,7 @@ class ComputeManager(manager.Manager): instance_ref['name']) else: self.driver.detach_volume(instance_ref['name'], - volume_ref['mountpoint']) + volume_ref['mountpoint']) self.volume_manager.remove_compute_volume(context, volume_id) self.db.volume_detached(context, volume_id) return True diff --git a/nova/manager.py b/nova/manager.py index a343d7fc6a..3d38504bd8 100644 --- a/nova/manager.py +++ b/nova/manager.py @@ -68,7 +68,7 @@ class Manager(base.Base): def periodic_tasks(self, context=None): """Tasks to be run at a periodic interval""" - return + pass def init_host(self): """Do any initialization that needs to be run if this is a standalone diff --git a/nova/rpc.py b/nova/rpc.py index b5df4904b1..6a3f552dbd 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -188,7 +188,6 @@ class AdapterConsumer(TopicConsumer): node_func = getattr(self.proxy, str(method)) node_args = dict((str(k), v) for k, v in args.iteritems()) # NOTE(vish): magic is fun! - # pylint: disable-msg=W0142 try: rval = node_func(context=ctxt, **node_args) if msg_id: diff --git a/nova/service.py b/nova/service.py index 5c171b2ae8..3d40e83a6c 100644 --- a/nova/service.py +++ b/nova/service.py @@ -205,39 +205,6 @@ class Service(object): logging.exception("model server went away") -def stop(pidfile): - """ - Stop the daemon - """ - # Get the pid from the pidfile - try: - pf = file(pidfile, 'r') - pid = int(pf.read().strip()) - pf.close() - except IOError: - pid = None - - if not pid: - message = "pidfile %s does not exist. Daemon not running?\n" - sys.stderr.write(message % pidfile) - # Not an error in a restart - return - - # Try killing the daemon process - try: - while 1: - os.kill(pid, signal.SIGKILL) - time.sleep(0.1) - except OSError, err: - err = str(err) - if err.find("No such process") > 0: - if os.path.exists(pidfile): - os.remove(pidfile) - else: - print str(err) - sys.exit(1) - - def serve(*services): argv = FLAGS(sys.argv) @@ -247,38 +214,7 @@ def serve(*services): name = '_'.join(x.binary for x in services) logging.debug("Serving %s" % name) - logging.getLogger('amqplib').setLevel(logging.DEBUG) - - if not FLAGS.pidfile: - FLAGS.pidfile = '%s.pid' % name - # NOTE(vish): if we're running nodaemon, redirect the log to stdout - #if FLAGS.nodaemon and not FLAGS.logfile: - # FLAGS.logfile = "-" - #if not FLAGS.logfile: - # FLAGS.logfile = '%s.log' % name - #if not FLAGS.prefix: - # FLAGS.prefix = name - - action = 'start' - if len(argv) > 1: - action = argv.pop() - - if action == 'stop': - stop(FLAGS.pidfile) - sys.exit() - elif action == 'restart': - stop(FLAGS.pidfile) - elif action == 'start': - pass - else: - print 'usage: %s [options] [start|stop|restart]' % argv[0] - sys.exit(1) - - #formatter = logging.Formatter( - # '(%(name)s): %(levelname)s %(message)s') - #handler = logging.StreamHandler() - #handler.setFormatter(formatter) - #logging.getLogger().addHandler(handler) + logging.getLogger('amqplib').setLevel(logging.WARN) if FLAGS.verbose: logging.getLogger().setLevel(logging.DEBUG) @@ -292,9 +228,6 @@ def serve(*services): for x in services: x.start() - #while True: - # greenthread.sleep(5) - def wait(): while True: diff --git a/nova/tests/service_unittest.py b/nova/tests/service_unittest.py index c94af4a230..6bdc7071cb 100644 --- a/nova/tests/service_unittest.py +++ b/nova/tests/service_unittest.py @@ -91,8 +91,6 @@ class ServiceTestCase(test.TestCase): self.mox.StubOutWithMock(rpc, 'AdapterConsumer', use_mock_anything=True) - #self.mox.StubOutWithMock( - # service.task, 'LoopingCall', use_mock_anything=True) rpc.AdapterConsumer(connection=mox.IgnoreArg(), topic=topic, proxy=mox.IsA(service.Service)).AndReturn( @@ -106,17 +104,6 @@ class ServiceTestCase(test.TestCase): rpc.AdapterConsumer.attach_to_eventlet() rpc.AdapterConsumer.attach_to_eventlet() - # Stub out looping call a bit needlessly since we don't have an easy - # way to cancel it (yet) when the tests finishes - #service.task.LoopingCall(mox.IgnoreArg()).AndReturn( - # service.task.LoopingCall) - #service.task.LoopingCall.start(interval=mox.IgnoreArg(), - # now=mox.IgnoreArg()) - #service.task.LoopingCall(mox.IgnoreArg()).AndReturn( - # service.task.LoopingCall) - #service.task.LoopingCall.start(interval=mox.IgnoreArg(), - # now=mox.IgnoreArg()) - service_create = {'host': host, 'binary': binary, 'topic': topic, diff --git a/nova/utils.py b/nova/utils.py index 22bf5d8cf4..5f2d472021 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -241,7 +241,7 @@ class LoopingCall(object): self.f(*self.args, **self.kw) greenthread.sleep(interval) except Exception: - logging.exception('hhmm') + logging.exception('in looping call') done.send_exception(*sys.exc_info()) return diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 91dc8173b3..77bc926c20 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -122,13 +122,13 @@ class FakeConnection(object): """ Rescue the specified instance. """ - return + pass def unrescue(self, instance): """ Unrescue the specified instance. """ - return + pass def destroy(self, instance): """ diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 715e7234cf..ba51f8f698 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -215,7 +215,7 @@ class LibvirtConnection(object): self._cleanup(instance) done.send() - greenthread.spawn(_wait_for_time) + greenthread.spawn(_wait_for_timer) return done def _cleanup(self, instance): @@ -365,9 +365,9 @@ class LibvirtConnection(object): if virsh_output.startswith('/dev/'): logging.info('cool, it\'s a device') - r = utils.execute("sudo dd if=%s iflag=nonblock" % - virsh_output, check_exit_code=False) - return r[0] + out, err = utils.execute("sudo dd if=%s iflag=nonblock" % + virsh_output, check_exit_code=False) + return out else: return '' @@ -388,8 +388,7 @@ class LibvirtConnection(object): console_log = os.path.join(FLAGS.instances_path, instance['name'], 'console.log') - utils.execute('sudo chown %d %s' % (os.getuid(), - console_log)) + utils.execute('sudo chown %d %s' % (os.getuid(), console_log)) if FLAGS.libvirt_type == 'xen': # Xen is special @@ -476,7 +475,6 @@ class LibvirtConnection(object): ['local_gb'] * 1024 * 1024 * 1024) - resize = inst['instance_type'] != 'm1.tiny' resize = True if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-': resize = False @@ -743,7 +741,7 @@ class NWFilterFirewall(object): if callable(xml): xml = xml() - # execute in a native thread and block until done + # execute in a native thread and block current greenthread until done tpool.execute(self._conn.nwfilterDefineXML, xml) @staticmethod diff --git a/nova/virt/xenapi/network_utils.py b/nova/virt/xenapi/network_utils.py index d8632f3939..0129543944 100644 --- a/nova/virt/xenapi/network_utils.py +++ b/nova/virt/xenapi/network_utils.py @@ -30,10 +30,9 @@ class NetworkHelper(): @classmethod def find_network_with_bridge(cls, session, bridge): - """ Return the network on which the bridge is attached, if found """ + """ Return the network on which the bridge is attached, if found.""" expr = 'field "bridge" = "%s"' % bridge - networks = session.call_xenapi('network.get_all_records_where', - expr) + networks = session.call_xenapi('network.get_all_records_where', expr) if len(networks) == 1: return networks.keys()[0] elif len(networks) > 1: diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index b6b92b9264..3034df9e1d 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -107,8 +107,7 @@ class VMOps(object): if vdis: for vdi in vdis: try: - task = self._session.call_xenapi('Async.VDI.destroy', - vdi) + task = self._session.call_xenapi('Async.VDI.destroy', vdi) self._session.wait_for_task(task) except XenAPI.Failure, exc: logging.warn(exc) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index df8e42d347..4243111330 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -175,9 +175,11 @@ class XenAPISession(object): The task is polled until it completes.""" done = event.Event() - loop = utis.LoopingTask(self._poll_task, task, done) + loop = utils.LoopingTask(self._poll_task, task, done) loop.start(FLAGS.xenapi_task_poll_interval, now=True) - return done.wait() + rv = done.wait() + loop.stop() + return rv def _poll_task(self, task, done): """Poll the given XenAPI task, and fire the given Deferred if we diff --git a/nova/volume/driver.py b/nova/volume/driver.py index f675c91320..1cd4c1fd45 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -22,10 +22,10 @@ Drivers for volumes. import logging import os +import time from nova import exception from nova import flags -from nova import process from nova import utils @@ -75,7 +75,7 @@ class VolumeDriver(object): raise logging.exception("Recovering from a failed execute." "Try number %s", tries) - self._execute("sleep %s" % tries ** 2) + time.sleep(tries ** 2) def check_for_setup_error(self): """Returns an error if prerequisites aren't met""" @@ -91,21 +91,20 @@ class VolumeDriver(object): sizestr = '%sG' % volume['size'] self._try_execute("sudo lvcreate -L %s -n %s %s" % (sizestr, - volume['name'], - FLAGS.volume_group)) + volume['name'], + FLAGS.volume_group)) def delete_volume(self, volume): """Deletes a logical volume.""" self._try_execute("sudo lvremove -f %s/%s" % (FLAGS.volume_group, - volume['name'])) + volume['name'])) def local_path(self, volume): # NOTE(vish): stops deprecation warning escaped_group = FLAGS.volume_group.replace('-', '--') escaped_name = volume['name'].replace('-', '--') - return "/dev/mapper/%s-%s" % (escaped_group, - escaped_name) + return "/dev/mapper/%s-%s" % (escaped_group, escaped_name) def ensure_export(self, context, volume): """Synchronously recreates an export for a logical volume.""" @@ -165,7 +164,7 @@ class AOEDriver(VolumeDriver): # still works for the other volumes, so we # just wait a bit for the current volume to # be ready and ignore any errors. - self._execute("sleep 2") + time.sleep(2) self._execute("sudo vblade-persist auto all", check_exit_code=False) self._execute("sudo vblade-persist start all", @@ -275,9 +274,8 @@ class ISCSIDriver(VolumeDriver): def discover_volume(self, volume): """Discover volume on a remote host.""" - (iscsi_name, - iscsi_portal) = self._get_name_and_portal(volume['name'], - volume['host']) + iscsi_name, iscsi_portal = self._get_name_and_portal(volume['name'], + volume['host']) self._execute("sudo iscsiadm -m node -T %s -p %s --login" % (iscsi_name, iscsi_portal)) self._execute("sudo iscsiadm -m node -T %s -p %s --op update " @@ -287,9 +285,8 @@ class ISCSIDriver(VolumeDriver): def undiscover_volume(self, volume): """Undiscover volume on a remote host.""" - (iscsi_name, - iscsi_portal) = self._get_name_and_portal(volume['name'], - volume['host']) + iscsi_name, iscsi_portal = self._get_name_and_portal(volume['name'], + volume['host']) self._execute("sudo iscsiadm -m node -T %s -p %s --op update " "-n node.startup -v manual" % (iscsi_name, iscsi_portal)) diff --git a/run_tests.py b/run_tests.py index 6d7830a297..9f37084120 100644 --- a/run_tests.py +++ b/run_tests.py @@ -46,7 +46,6 @@ import __main__ import os import sys - from twisted.scripts import trial as trial_script from nova import flags @@ -66,8 +65,6 @@ from nova.tests.rpc_unittest import * from nova.tests.scheduler_unittest import * from nova.tests.service_unittest import * from nova.tests.twistd_unittest import * -from nova.tests.validator_unittest import * -from nova.tests.virt_unittest import * from nova.tests.virt_unittest import * from nova.tests.volume_unittest import * @@ -85,8 +82,6 @@ if __name__ == '__main__': config = OptionsClass() argv = config.parseOptions() - argv = FLAGS(sys.argv) - FLAGS.verbose = True # TODO(termie): these should make a call instead of doing work on import From 99347717ed2c7e92b3dc3bd33c12a3a05e8e349d Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 15 Dec 2010 00:25:04 +0000 Subject: [PATCH 142/229] Lockout middleware for ec2 api --- nova/api/ec2/__init__.py | 70 +++++++++++++++++++++++++- nova/fakememcache.py | 50 +++++++++++++++++++ nova/tests/middleware_unittest.py | 82 +++++++++++++++++++++++++++++++ run_tests.py | 1 + 4 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 nova/fakememcache.py create mode 100644 nova/tests/middleware_unittest.py diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index a6ee16c332..19eb666cdb 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -22,12 +22,13 @@ Starting point for routing EC2 requests. import logging import routes +import time import webob import webob.dec import webob.exc -from nova import exception from nova import context +from nova import exception from nova import flags from nova import wsgi from nova.api.ec2 import apirequest @@ -37,6 +38,16 @@ from nova.auth import manager FLAGS = flags.FLAGS +flags.DEFINE_boolean('use_lockout', False, + 'Whether or not to use lockout middleware.') +flags.DEFINE_integer('lockout_attempts', 5, + 'Number of failed auths before lockout.') +flags.DEFINE_integer('lockout_minutes', 15, + 'Number of minutes to lockout if triggered.') +flags.DEFINE_list('lockout_memcached_servers', None, + 'Memcached servers or None for in process cache.') + + _log = logging.getLogger("api") _log.setLevel(logging.DEBUG) @@ -47,6 +58,63 @@ class API(wsgi.Middleware): def __init__(self): self.application = Authenticate(Router(Authorizer(Executor()))) + if FLAGS.use_lockout: + self.application = Lockout(self.application) + + +class Lockout(wsgi.Middleware): + """Only allow x failed auths in a y minute period. + + x = lockout_attempts flag + y = lockout_timeout flag + + Uses memcached if lockout_memcached_servers flag is set, otherwise it + uses a very simple in-proccess cache. Due to the simplicity of + the implementation, the timeout window is reset with every failed + request, so it actually blocks if there are x failed logins with no + more than y minutes between any two failures. + + There is a possible race condition where simultaneous requests could + sneak in before the lockout hits, but this is extremely rare and would + only result in a couple of extra failed attempts.""" + + def __init__(self, application, time_fn=time.time): + """The middleware can use a custom time function for testing.""" + self.time_fn = time_fn + if FLAGS.lockout_memcached_servers: + import memcache + else: + from nova import fakememcache as memcache + self.mc = memcache.Client(FLAGS.lockout_memcached_servers, debug=0) + super(Lockout, self).__init__(application) + + @webob.dec.wsgify + def __call__(self, req): + access_key = req.params['AWSAccessKeyId'] + failures_key = "%s-failures" % access_key + last_key = "%s-last" % access_key + now = self.time_fn() + timeout = now - FLAGS.lockout_minutes * 60 + # NOTE(vish): To use incr, failures has to be a string. + failures = int(self.mc.get(failures_key) or 0) + last = self.mc.get(last_key) + if (failures and failures >= FLAGS.lockout_attempts + and last > timeout): + self.mc.set(last_key, now) + detail = "Too many failed authentications." + raise webob.exc.HTTPForbidden(detail=detail) + res = req.get_response(self.application) + if res.status_int == 403: + if last > timeout: + failures = int(self.mc.incr(failures_key)) + if failures >= FLAGS.lockout_attempts: + _log.warn('Access key %s has had %d failed authentications' + ' and will be locked out for %d minutes.' % + (access_key, failures, FLAGS.lockout_minutes)) + else: + self.mc.set(failures_key, '1') + self.mc.set(last_key, now) + return res class Authenticate(wsgi.Middleware): diff --git a/nova/fakememcache.py b/nova/fakememcache.py new file mode 100644 index 0000000000..0b2e3b6c14 --- /dev/null +++ b/nova/fakememcache.py @@ -0,0 +1,50 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Super simple fake memcache client.""" + + +class Client(object): + """Replicates a tiny subset of memcached client interface.""" + __cache = {} + + def __init__(self, *args, **kwargs): + """Ignores all constructor params.""" + pass + + def get(self, key): + """Retrieves the value for a key or None.""" + return self.__cache.get(key, None) + + def set(self, key, value): + """Sets the value for a key.""" + self.__cache[key] = value + return True + + def add(self, key, value): + """Sets the value for a key if it doesn't exist.""" + if key in self.__cache: + return False + return self.set(key, value) + + def incr(self, key, delta=1): + """Increments the value for a key.""" + if not key in self.__cache: + return 0 + self.__cache[key] = str(int(self.__cache[key]) + 1) + return self.__cache[key] diff --git a/nova/tests/middleware_unittest.py b/nova/tests/middleware_unittest.py new file mode 100644 index 0000000000..bbbd4a5a70 --- /dev/null +++ b/nova/tests/middleware_unittest.py @@ -0,0 +1,82 @@ +# 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 webob +import webob.dec +import webob.exc + +from nova.api import ec2 +from nova import flags +from nova import test + + +FLAGS = flags.FLAGS + + +@webob.dec.wsgify +def conditional_forbid(req): + """Helper wsgi app returns 403 if param 'die' is 1.""" + if 'die' in req.params and req.params['die'] == '1': + raise webob.exc.HTTPForbidden() + return 'OK' + + +class LockoutTestCase(test.TrialTestCase): + """Test case for the Lockout middleware.""" + def setUp(self): # pylint: disable-msg=C0103 + self.local_time = 0 + self.lockout = ec2.Lockout(conditional_forbid, + time_fn=self._constant_time) + super(LockoutTestCase, self).setUp() + + def _constant_time(self): + """Helper method to force timeouts.""" + return self.local_time + + def _trigger_lockout(self, access_key): + """Send x failed requests where x = lockout_attempts.""" + for i in xrange(FLAGS.lockout_attempts): + req = webob.Request.blank('/?AWSAccessKeyId=%s&die=1' % access_key) + self.assertEqual(req.get_response(self.lockout).status_int, 403) + + def _is_locked_out(self, access_key): + """Sends a test request to see if key is locked out.""" + req = webob.Request.blank('/?AWSAccessKeyId=%s' % access_key) + return (req.get_response(self.lockout).status_int == 403) + + def _timeout(self): + """Increment time to 1 second past the lockout.""" + self.local_time = 1 + self.local_time + FLAGS.lockout_minutes * 60 + + def test_lockout(self): + self._trigger_lockout('test') + self.assertTrue(self._is_locked_out('test')) + + def test_timeout(self): + self._trigger_lockout('test') + self.assertTrue(self._is_locked_out('test')) + self._timeout() + self.assertFalse(self._is_locked_out('test')) + + def test_multiple_keys(self): + self._trigger_lockout('test1') + self.assertTrue(self._is_locked_out('test1')) + self.assertFalse(self._is_locked_out('test2')) + self._timeout() + self.assertFalse(self._is_locked_out('test1')) + self.assertFalse(self._is_locked_out('test2')) diff --git a/run_tests.py b/run_tests.py index 37a548e4ce..a0ef3fd99e 100644 --- a/run_tests.py +++ b/run_tests.py @@ -57,6 +57,7 @@ from nova.tests.auth_unittest import * from nova.tests.cloud_unittest import * from nova.tests.compute_unittest import * from nova.tests.flags_unittest import * +from nova.tests.middleware_unittest import * from nova.tests.misc_unittest import * from nova.tests.network_unittest import * from nova.tests.objectstore_unittest import * From aff411d80f4243ad0b40649af3a7586c7c38ac2d Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Wed, 15 Dec 2010 11:57:56 +0100 Subject: [PATCH 143/229] Make sure we unlock the bzr tree again in the authors unit test. --- nova/tests/misc_unittest.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/nova/tests/misc_unittest.py b/nova/tests/misc_unittest.py index 667c63ad09..2758276e5b 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)) + 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() + authors_file = open('../Authors', 'r').read() - missing = set() - for contributor in contributors: - if not contributor in authors_file: - missing.add(contributor) + 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) + self.assertTrue(len(missing) == 0, + '%r not listed in Authors' % missing) + finally: + tree.unlock() From e84f46e739c56b7ae186866f33c713a0ac98e770 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Wed, 15 Dec 2010 13:15:19 +0100 Subject: [PATCH 144/229] Make sure the new, consolidated template gets included. --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 982b727aa2..199ce30b6d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -13,7 +13,7 @@ include nova/cloudpipe/client.ovpn.template include nova/compute/fakevirtinstance.xml include nova/compute/interfaces.template include nova/virt/interfaces.template -include nova/virt/libvirt.*.xml.template +include nova/virt/libvirt*.xml.template include nova/tests/CA/ include nova/tests/CA/cacert.pem include nova/tests/CA/private/ From 17daec6992456efc70ffbf05423ea91123db1fc2 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Wed, 15 Dec 2010 20:17:44 +0300 Subject: [PATCH 145/229] Restore alphabetical order in Authors file --- Authors | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Authors b/Authors index 2810a1ddb9..749d784061 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 From b0279030127b7fe8df21db12a8727ea623ca46e2 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 15 Dec 2010 09:38:38 -0800 Subject: [PATCH 146/229] clean up code to use timeout instead of two keys --- nova/api/ec2/__init__.py | 58 ++++++++++++++----------------- nova/fakememcache.py | 38 ++++++++++++-------- nova/tests/middleware_unittest.py | 27 ++++++++------ 3 files changed, 68 insertions(+), 55 deletions(-) diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 19eb666cdb..381b0e8710 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -22,7 +22,6 @@ Starting point for routing EC2 requests. import logging import routes -import time import webob import webob.dec import webob.exc @@ -44,6 +43,8 @@ flags.DEFINE_integer('lockout_attempts', 5, 'Number of failed auths before lockout.') flags.DEFINE_integer('lockout_minutes', 15, 'Number of minutes to lockout if triggered.') +flags.DEFINE_integer('lockout_window', 15, + 'Number of minutes for lockout window.') flags.DEFINE_list('lockout_memcached_servers', None, 'Memcached servers or None for in process cache.') @@ -53,7 +54,6 @@ _log.setLevel(logging.DEBUG) class API(wsgi.Middleware): - """Routing for all EC2 API requests.""" def __init__(self): @@ -63,57 +63,53 @@ class API(wsgi.Middleware): class Lockout(wsgi.Middleware): - """Only allow x failed auths in a y minute period. + """Lockout for x minutes on y failed auths in a z minute period. - x = lockout_attempts flag - y = lockout_timeout flag + x = lockout_timeout flag + y = lockout_window flag + z = lockout_attempts flag Uses memcached if lockout_memcached_servers flag is set, otherwise it uses a very simple in-proccess cache. Due to the simplicity of - the implementation, the timeout window is reset with every failed - request, so it actually blocks if there are x failed logins with no - more than y minutes between any two failures. + the implementation, the timeout window is started with the first + failed request, so it will block if there are x failed logins within + that period. There is a possible race condition where simultaneous requests could sneak in before the lockout hits, but this is extremely rare and would only result in a couple of extra failed attempts.""" - def __init__(self, application, time_fn=time.time): - """The middleware can use a custom time function for testing.""" - self.time_fn = time_fn + def __init__(self, application, time_fn=None): + """middleware can pass a custom time function to fake for testing.""" if FLAGS.lockout_memcached_servers: import memcache + self.mc = memcache.Client(FLAGS.lockout_memcached_servers, + debug=0) else: - from nova import fakememcache as memcache - self.mc = memcache.Client(FLAGS.lockout_memcached_servers, debug=0) + from nova import fakememcache + self.mc = fakememcache.Client(time_fn=time_fn) super(Lockout, self).__init__(application) @webob.dec.wsgify def __call__(self, req): access_key = req.params['AWSAccessKeyId'] - failures_key = "%s-failures" % access_key - last_key = "%s-last" % access_key - now = self.time_fn() - timeout = now - FLAGS.lockout_minutes * 60 - # NOTE(vish): To use incr, failures has to be a string. + failures_key = "authfailures-%s" % access_key failures = int(self.mc.get(failures_key) or 0) - last = self.mc.get(last_key) - if (failures and failures >= FLAGS.lockout_attempts - and last > timeout): - self.mc.set(last_key, now) + if failures >= FLAGS.lockout_attempts: detail = "Too many failed authentications." raise webob.exc.HTTPForbidden(detail=detail) res = req.get_response(self.application) if res.status_int == 403: - if last > timeout: - failures = int(self.mc.incr(failures_key)) - if failures >= FLAGS.lockout_attempts: - _log.warn('Access key %s has had %d failed authentications' - ' and will be locked out for %d minutes.' % - (access_key, failures, FLAGS.lockout_minutes)) - else: - self.mc.set(failures_key, '1') - self.mc.set(last_key, now) + failures = self.mc.incr(failures_key) + if failures is None: + # NOTE(vish): To use incr, failures has to be a string. + self.mc.set(failures_key, '1', time=FLAGS.lockout_window * 60) + elif failures >= FLAGS.lockout_attempts: + _log.warn('Access key %s has had %d failed authentications' + ' and will be locked out for %d minutes.' % + (access_key, failures, FLAGS.lockout_minutes)) + self.mc.set(failures_key, str(failures), + time=FLAGS.lockout_minutes * 60) return res diff --git a/nova/fakememcache.py b/nova/fakememcache.py index 0b2e3b6c14..0b4037ef63 100644 --- a/nova/fakememcache.py +++ b/nova/fakememcache.py @@ -18,33 +18,43 @@ """Super simple fake memcache client.""" +import time + class Client(object): """Replicates a tiny subset of memcached client interface.""" - __cache = {} - def __init__(self, *args, **kwargs): - """Ignores all constructor params.""" - pass + def __init__(self, time_fn=time.time, *args, **kwargs): + """Time fn is to allow testing through a custom function""" + self.time_fn = time_fn + self.cache = {} def get(self, key): """Retrieves the value for a key or None.""" - return self.__cache.get(key, None) + (timeout, value) = self.cache.get(key, (0, None)) + if timeout == 0 or self.time_fn() < timeout: + return value + return None - def set(self, key, value): + def set(self, key, value, time=0, min_compress_len=0): """Sets the value for a key.""" - self.__cache[key] = value + timeout = 0 + if time != 0: + timeout = self.time_fn() + time + self.cache[key] = (timeout, value) return True - def add(self, key, value): + def add(self, key, value, time=0, min_compress_len=0): """Sets the value for a key if it doesn't exist.""" - if key in self.__cache: + if not self.get(key) is None: return False - return self.set(key, value) + return self.set(key, value, time, min_compress_len) def incr(self, key, delta=1): """Increments the value for a key.""" - if not key in self.__cache: - return 0 - self.__cache[key] = str(int(self.__cache[key]) + 1) - return self.__cache[key] + value = self.get(key) + if value is None: + return None + new_value = int(value) + delta + self.cache[key] = (self.cache[key][0], str(new_value)) + return new_value diff --git a/nova/tests/middleware_unittest.py b/nova/tests/middleware_unittest.py index bbbd4a5a70..61a790c1f4 100644 --- a/nova/tests/middleware_unittest.py +++ b/nova/tests/middleware_unittest.py @@ -48,9 +48,9 @@ class LockoutTestCase(test.TrialTestCase): """Helper method to force timeouts.""" return self.local_time - def _trigger_lockout(self, access_key): - """Send x failed requests where x = lockout_attempts.""" - for i in xrange(FLAGS.lockout_attempts): + def _send_bad_attempts(self, access_key, num_attempts=1): + """Fail x.""" + for i in xrange(num_attempts): req = webob.Request.blank('/?AWSAccessKeyId=%s&die=1' % access_key) self.assertEqual(req.get_response(self.lockout).status_int, 403) @@ -59,24 +59,31 @@ class LockoutTestCase(test.TrialTestCase): req = webob.Request.blank('/?AWSAccessKeyId=%s' % access_key) return (req.get_response(self.lockout).status_int == 403) - def _timeout(self): + def _advance_time(self, time): """Increment time to 1 second past the lockout.""" - self.local_time = 1 + self.local_time + FLAGS.lockout_minutes * 60 + self.local_time = self.local_time + time def test_lockout(self): - self._trigger_lockout('test') + self._send_bad_attempts('test', FLAGS.lockout_attempts) self.assertTrue(self._is_locked_out('test')) def test_timeout(self): - self._trigger_lockout('test') + self._send_bad_attempts('test', FLAGS.lockout_attempts) self.assertTrue(self._is_locked_out('test')) - self._timeout() + self._advance_time(FLAGS.lockout_minutes * 60) self.assertFalse(self._is_locked_out('test')) def test_multiple_keys(self): - self._trigger_lockout('test1') + self._send_bad_attempts('test1', FLAGS.lockout_attempts) self.assertTrue(self._is_locked_out('test1')) self.assertFalse(self._is_locked_out('test2')) - self._timeout() + self._advance_time(FLAGS.lockout_minutes * 60) self.assertFalse(self._is_locked_out('test1')) self.assertFalse(self._is_locked_out('test2')) + + def test_window_timeout(self): + self._send_bad_attempts('test', FLAGS.lockout_attempts - 1) + self.assertFalse(self._is_locked_out('test')) + self._advance_time(FLAGS.lockout_window * 60) + self._send_bad_attempts('test', FLAGS.lockout_attempts - 1) + self.assertFalse(self._is_locked_out('test')) From dada56794679b213b2d80e4e1f907a212b73f54e Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Wed, 15 Dec 2010 17:50:05 +0000 Subject: [PATCH 147/229] * code cleanup * revised unittest approach * added stubout and a number of tests --- nova/tests/xenapi_unittest.py | 136 +++++++++++++++++++++++++++++++--- nova/virt/xenapi/__init__.py | 22 +----- nova/virt/xenapi/fake.py | 40 +--------- nova/virt/xenapi/vmops.py | 6 +- nova/virt/xenapi/volumeops.py | 7 +- nova/virt/xenapi_conn.py | 24 +++--- 6 files changed, 146 insertions(+), 89 deletions(-) diff --git a/nova/tests/xenapi_unittest.py b/nova/tests/xenapi_unittest.py index c9be173276..b9955a9465 100644 --- a/nova/tests/xenapi_unittest.py +++ b/nova/tests/xenapi_unittest.py @@ -31,7 +31,7 @@ # under the License. -import mox +import stubout import uuid from twisted.internet import defer @@ -51,20 +51,34 @@ from nova.virt.xenapi import fake from nova.virt.xenapi import volume_utils from nova.virt.xenapi import vm_utils from nova.virt.xenapi import volumeops +from boto.ec2.volume import Volume FLAGS = flags.FLAGS +def stubout_session(stubs, cls): + def fake_import(self): + fake_module = 'nova.virt.xenapi.fake' + from_list = ['fake'] + return __import__(fake_module, globals(), locals(), from_list, -1) + + stubs.Set(xenapi_conn.XenAPISession, '_create_session', + lambda s, url: cls(url)) + stubs.Set(xenapi_conn.XenAPISession, 'get_imported_xenapi', + fake_import) + + class XenAPIVolumeTestCase(test.TrialTestCase): """ - This uses Ewan's fake session approach + Unit tests for VM operations """ def setUp(self): super(XenAPIVolumeTestCase, self).setUp() - FLAGS.xenapi_use_fake_session = True + self.stubs = stubout.StubOutForTesting() FLAGS.target_host = '127.0.0.1' FLAGS.xenapi_connection_url = 'test_url' FLAGS.xenapi_connection_password = 'test_pass' + fake.reset() def _create_volume(self, size='0'): """Create a volume object.""" @@ -78,11 +92,12 @@ class XenAPIVolumeTestCase(test.TrialTestCase): vol['attach_status'] = "detached" return db.volume_create(context.get_admin_context(), vol) - def test_create_iscsi_storage_raise_no_exception(self): - fake.reset() + def test_create_iscsi_storage(self): + """ This shows how to test helper classes' methods """ + stubout_session(self.stubs, FakeSessionForVolumeTests) session = xenapi_conn.XenAPISession('test_url', 'root', 'test_pass') helper = volume_utils.VolumeHelper - helper.late_import(FLAGS) + helper.XenAPI = session.get_imported_xenapi() vol = self._create_volume() info = yield helper.parse_volume_info(vol['ec2_id'], '/dev/sdc') label = 'SR-%s' % vol['ec2_id'] @@ -93,8 +108,25 @@ class XenAPIVolumeTestCase(test.TrialTestCase): description) db.volume_destroy(context.get_admin_context(), vol['id']) + def test_parse_volume_info_raise_exception(self): + """ This shows how to test helper classes' methods """ + stubout_session(self.stubs, FakeSessionForVolumeTests) + session = xenapi_conn.XenAPISession('test_url', 'root', 'test_pass') + helper = volume_utils.VolumeHelper + helper.XenAPI = session.get_imported_xenapi() + vol = self._create_volume() + # oops, wrong mount point! + info = helper.parse_volume_info(vol['ec2_id'], '/dev/sd') + + def check(exc): + self.assertIsInstance(exc.value, volume_utils.StorageError) + + info.addErrback(check) + db.volume_destroy(context.get_admin_context(), vol['id']) + def test_attach_volume(self): - fake.reset() + """ This shows how to test Ops classes' methods """ + stubout_session(self.stubs, FakeSessionForVolumeTests) conn = xenapi_conn.get_connection(False) volume = self._create_volume() instance = FakeInstance(1, 'fake', 'fake', 1, 2, 3, @@ -116,13 +148,34 @@ class XenAPIVolumeTestCase(test.TrialTestCase): result.addCallback(check) return result + def test_attach_volume_raise_exception(self): + """ This shows how to test when exceptions are raised """ + stubout_session(self.stubs, FakeSessionForVolumeFailedTests) + conn = xenapi_conn.get_connection(False) + volume = self._create_volume() + instance = FakeInstance(1, 'fake', 'fake', 1, 2, 3, + 'm1.large', 'aa:bb:cc:dd:ee:ff') + fake.create_vm(instance.name, 'Running') + result = conn.attach_volume(instance.name, volume['ec2_id'], + '/dev/sdc') + + def check(exc): + if exc: + pass + else: + self.fail('Oops, no exception has been raised!') + + result.addErrback(check) + return result + def tearDown(self): super(XenAPIVolumeTestCase, self).tearDown() + self.stubs.UnsetAll() class XenAPIVMTestCase(test.TrialTestCase): """ - This uses Ewan's fake session approach + Unit tests for VM operations """ def setUp(self): super(XenAPIVMTestCase, self).setUp() @@ -131,19 +184,20 @@ class XenAPIVMTestCase(test.TrialTestCase): admin=True) self.project = self.manager.create_project('fake', 'fake', 'fake') self.network = utils.import_object(FLAGS.network_manager) - FLAGS.xenapi_use_fake_session = True + self.stubs = stubout.StubOutForTesting() FLAGS.xenapi_connection_url = 'test_url' FLAGS.xenapi_connection_password = 'test_pass' fake.reset() fake.create_network('fake', FLAGS.flat_network_bridge) def test_list_instances_0(self): + stubout_session(self.stubs, FakeSessionForVMTests) conn = xenapi_conn.get_connection(False) instances = conn.list_instances() self.assertEquals(instances, []) - #test_list_instances_0.skip = "E" def test_spawn(self): + stubout_session(self.stubs, FakeSessionForVMTests) conn = xenapi_conn.get_connection(False) instance = FakeInstance(1, self.project.id, self.user.id, 1, 2, 3, 'm1.large', 'aa:bb:cc:dd:ee:ff') @@ -186,6 +240,7 @@ class XenAPIVMTestCase(test.TrialTestCase): super(XenAPIVMTestCase, self).tearDown() self.manager.delete_project(self.project) self.manager.delete_user(self.user) + self.stubs.UnsetAll() class FakeInstance(): @@ -199,3 +254,64 @@ class FakeInstance(): self.ramdisk_id = ramdisk_id self.instance_type = instance_type self.mac_address = mac_address + + +class FakeSessionForVMTests(fake.SessionBase): + def __init__(self, uri): + super(FakeSessionForVMTests, self).__init__(uri) + + def network_get_all_records_where(self, _1, _2): + return self.xenapi.network.get_all_records() + + def host_call_plugin(self, _1, _2, _3, _4, _5): + return '' + + def VM_start(self, _1, ref, _2, _3): + vm = fake.get_record('VM', ref) + if vm['power_state'] != 'Halted': + raise fake.Failure(['VM_BAD_POWER_STATE', ref, 'Halted', + vm['power_state']]) + vm['power_state'] = 'Running' + + +class FakeSessionForVolumeTests(fake.SessionBase): + def __init__(self, uri): + super(FakeSessionForVolumeTests, self).__init__(uri) + + def VBD_plug(self, _1, _2): + #FIXME(armando):make proper plug + pass + + def PBD_unplug(self, _1, _2): + #FIXME(armando):make proper unplug + pass + + def SR_forget(self, _1, _2): + #FIXME(armando):make proper forget + pass + + def VDI_introduce(self, _1, uuid, _2, _3, _4, _5, + _6, _7, _8, _9, _10, _11): + #FIXME(armando):make proper introduce + valid_vdi = False + refs = fake.get_all('VDI') + for ref in refs: + rec = fake.get_record('VDI', ref) + if rec['uuid'] == uuid: + valid_vdi = True + if not valid_vdi: + raise fake.Failure([['INVALID_VDI', 'session', self._session]]) + + +class FakeSessionForVolumeFailedTests(FakeSessionForVolumeTests): + def __init__(self, uri): + super(FakeSessionForVolumeFailedTests, self).__init__(uri) + + def VDI_introduce(self, _1, uuid, _2, _3, _4, _5, + _6, _7, _8, _9, _10, _11): + # test failure + raise fake.Failure([['INVALID_VDI', 'session', self._session]]) + + def VBD_plug(self, _1, _2): + # test failure + raise fake.Failure([['INVALID_VBD', 'session', self._session]]) diff --git a/nova/virt/xenapi/__init__.py b/nova/virt/xenapi/__init__.py index 1a2903b98d..c7038deae8 100644 --- a/nova/virt/xenapi/__init__.py +++ b/nova/virt/xenapi/__init__.py @@ -20,31 +20,11 @@ """ -def load_sdk(flags): - """ - This method is used for loading the XenAPI SDK (fake or real) - """ - xenapi_module = \ - flags.xenapi_use_fake_session and 'nova.virt.xenapi.fake' or 'XenAPI' - from_list = \ - flags.xenapi_use_fake_session and ['fake'] or [] - - return __import__(xenapi_module, globals(), locals(), from_list, -1) - - class HelperBase(): """ - The class that wraps the helper methods together. + The base for helper classes. This adds the XenAPI class attribute """ XenAPI = None def __init__(self): return - - @classmethod - def late_import(cls, FLAGS): - """ - Load XenAPI module in for helper class - """ - if cls.XenAPI is None: - cls.XenAPI = load_sdk(FLAGS) diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index f5fea3cc2a..038064e8ee 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -154,7 +154,7 @@ class Failure(Exception): def __str__(self): try: return str(self.details) - except Exception, exn: + except Exception, exc: return "XenAPI Fake Failure: %s" % str(self.details) def _details_map(self): @@ -324,8 +324,8 @@ class SessionBase(object): try: task['result'] = self.xenapi_request(func, params[1:]) task['status'] = 'success' - except Failure, exn: - task['error_info'] = exn.details + except Failure, exc: + task['error_info'] = exc.details task['status'] = 'failed' task['finished'] = datetime.datetime.now() return task_ref @@ -372,37 +372,3 @@ class _Dispatcher: def __call__(self, *args): return self.__send(self.__name, args) - - -class FakeSession(SessionBase): - def __init__(self, uri): - super(FakeSession, self).__init__(uri) - - def network_get_all_records_where(self, _1, _2): - return self.xenapi.network.get_all_records() - - def host_call_plugin(self, _1, _2, _3, _4, _5): - return '' - - def VM_start(self, _1, ref, _2, _3): - vm = get_record('VM', ref) - if vm['power_state'] != 'Halted': - raise Failure(['VM_BAD_POWER_STATE', ref, 'Halted', - vm['power_state']]) - vm['power_state'] = 'Running' - - def VBD_plug(self, _1, _2): - #FIXME(armando):make proper plug - pass - - def VDI_introduce(self, _1, uuid, _2, _3, _4, _5, - _6, _7, _8, _9, _10, _11): - #FIXME(armando):make proper introduce - valid_vdi = False - refs = get_all('VDI') - for ref in refs: - rec = get_record('VDI', ref) - if rec['uuid'] == uuid: - valid_vdi = True - if not valid_vdi: - raise Failure([['INVALID_VDI', 'session', self._session]]) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 36b8fecc2c..abaa1f5f11 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -28,7 +28,6 @@ from nova import flags from nova import exception from nova.auth.manager import AuthManager -from nova.virt.xenapi import load_sdk from nova.virt.xenapi.network_utils import NetworkHelper from nova.virt.xenapi.vm_utils import VMHelper @@ -38,10 +37,9 @@ class VMOps(object): Management class for VM-related tasks """ def __init__(self, session): - self.XenAPI = load_sdk(flags.FLAGS) + self.XenAPI = session.get_imported_xenapi() self._session = session - # Load XenAPI module in the helper class - VMHelper.late_import(flags.FLAGS) + VMHelper.XenAPI = self.XenAPI def list_instances(self): """ List VM instances """ diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xenapi/volumeops.py index 1b337a6eda..68806c4c26 100644 --- a/nova/virt/xenapi/volumeops.py +++ b/nova/virt/xenapi/volumeops.py @@ -22,7 +22,6 @@ import logging from twisted.internet import defer from nova import flags -from nova.virt.xenapi import load_sdk from nova.virt.xenapi.vm_utils import VMHelper from nova.virt.xenapi.volume_utils import VolumeHelper from nova.virt.xenapi.volume_utils import StorageError @@ -33,11 +32,11 @@ class VolumeOps(object): Management class for Volume-related tasks """ def __init__(self, session): - self.XenAPI = load_sdk(flags.FLAGS) + self.XenAPI = session.get_imported_xenapi() self._session = session # Load XenAPI module in the helper classes respectively - VolumeHelper.late_import(flags.FLAGS) - VMHelper.late_import(flags.FLAGS) + VolumeHelper.XenAPI = self.XenAPI + VMHelper.XenAPI = self.XenAPI @defer.inlineCallbacks def attach_volume(self, instance_name, device_path, mountpoint): diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 5f2b9c7c62..a7e3a67239 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -36,7 +36,6 @@ reactor thread if the VM.get_by_name_label or VM.get_record calls block. **Related Flags** -:xenapi_use_fake_session: To be set for unit testing :xenapi_connection_url: URL for connection to XenServer/Xen Cloud Platform. :xenapi_connection_username: Username for connection to XenServer/Xen Cloud Platform (default: root). @@ -59,15 +58,11 @@ from twisted.internet import reactor from nova import utils from nova import flags -from nova.virt.xenapi import load_sdk from nova.virt.xenapi.vmops import VMOps from nova.virt.xenapi.volumeops import VolumeOps FLAGS = flags.FLAGS -flags.DEFINE_boolean('xenapi_use_fake_session', - False, - 'Set to true in order to use the fake XenAPI SDK') flags.DEFINE_string('xenapi_connection_url', None, 'URL for connection to XenServer/Xen Cloud Platform.' @@ -159,15 +154,14 @@ class XenAPIConnection(object): class XenAPISession(object): """ The session to invoke XenAPI SDK calls """ def __init__(self, url, user, pw): - # This is loaded late so that there's no need to install this - # library when not using XenAPI. - self.XenAPI = load_sdk(FLAGS) - if FLAGS.xenapi_use_fake_session: - self._session = self.XenAPI.FakeSession(url) - else: - self._session = self.XenAPI.Session(url) + self.XenAPI = self.get_imported_xenapi() + self._session = self._create_session(url) self._session.login_with_password(user, pw) + def get_imported_xenapi(self): + """Stubout point. This can be replaced with a mock xenapi module.""" + return __import__('XenAPI') + def get_xenapi(self): """ Return the xenapi object """ return self._session.xenapi @@ -200,6 +194,10 @@ class XenAPISession(object): reactor.callLater(0, self._poll_task, task, d) return d + def _create_session(self, url): + """Stubout point. This can be replaced with a mock session.""" + return self.XenAPI.Session(url) + @utils.deferredToThread def _poll_task(self, task, deferred): """Poll the given XenAPI task, and fire the given Deferred if we @@ -220,7 +218,7 @@ class XenAPISession(object): error_info) deferred.errback(self.XenAPI.Failure(error_info)) #logging.debug('Polling task %s done.', task) - except self.XenAPI.Failure, exc: + except Exception, exc: logging.warn(exc) deferred.errback(exc) From e893be0a8d32cf1eb2c91187b81a6febf90e5b7c Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Wed, 15 Dec 2010 18:28:00 +0000 Subject: [PATCH 148/229] Adding back in openssh-lpk schema, as keys will likely be stored in LDAP again. --- nova/auth/opendj.sh | 1 + nova/auth/openssh-lpk_openldap.schema | 19 +++++++++++++++++++ nova/auth/openssh-lpk_sun.schema | 10 ++++++++++ nova/auth/slap.sh | 1 + 4 files changed, 31 insertions(+) create mode 100644 nova/auth/openssh-lpk_openldap.schema create mode 100644 nova/auth/openssh-lpk_sun.schema diff --git a/nova/auth/opendj.sh b/nova/auth/opendj.sh index 9a96003426..1a280e5a80 100755 --- a/nova/auth/opendj.sh +++ b/nova/auth/opendj.sh @@ -30,6 +30,7 @@ fi abspath=`dirname "$(cd "${0%/*}" 2>/dev/null; echo "$PWD"/"${0##*/}")"` schemapath='/var/opendj/instance/config/schema' +cp $abspath/openssh-lpk_sun.schema $schemapath/97-openssh-lpk_sun.ldif cp $abspath/nova_sun.schema $schemapath/98-nova_sun.ldif chown opendj:opendj $schemapath/98-nova_sun.ldif diff --git a/nova/auth/openssh-lpk_openldap.schema b/nova/auth/openssh-lpk_openldap.schema new file mode 100644 index 0000000000..93351da6dd --- /dev/null +++ b/nova/auth/openssh-lpk_openldap.schema @@ -0,0 +1,19 @@ +# +# LDAP Public Key Patch schema for use with openssh-ldappubkey +# Author: Eric AUGE +# +# Based on the proposal of : Mark Ruijter +# + + +# octetString SYNTAX +attributetype ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey' + DESC 'MANDATORY: OpenSSH Public key' + EQUALITY octetStringMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 ) + +# printableString SYNTAX yes|no +objectclass ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY + DESC 'MANDATORY: OpenSSH LPK objectclass' + MAY ( sshPublicKey $ uid ) + ) diff --git a/nova/auth/openssh-lpk_sun.schema b/nova/auth/openssh-lpk_sun.schema new file mode 100644 index 0000000000..5f52db3b65 --- /dev/null +++ b/nova/auth/openssh-lpk_sun.schema @@ -0,0 +1,10 @@ +# +# LDAP Public Key Patch schema for use with openssh-ldappubkey +# Author: Eric AUGE +# +# Schema for Sun Directory Server. +# Based on the original schema, modified by Stefan Fischer. +# +dn: cn=schema +attributeTypes: ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey' DESC 'MANDATORY: OpenSSH Public key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 ) +objectClasses: ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY DESC 'MANDATORY: OpenSSH LPK objectclass' MAY ( sshPublicKey $ uid ) ) diff --git a/nova/auth/slap.sh b/nova/auth/slap.sh index 36c4ba37b0..95c61dafdf 100755 --- a/nova/auth/slap.sh +++ b/nova/auth/slap.sh @@ -21,6 +21,7 @@ apt-get install -y slapd ldap-utils python-ldap abspath=`dirname "$(cd "${0%/*}" 2>/dev/null; echo "$PWD"/"${0##*/}")"` +cp $abspath/openssh-lpk_openldap.schema /etc/ldap/schema/openssh-lpk_openldap.schema cp $abspath/nova_openldap.schema /etc/ldap/schema/nova.schema mv /etc/ldap/slapd.conf /etc/ldap/slapd.conf.orig From fdf067037981c2b4b4501258919af0f9e1d0ec26 Mon Sep 17 00:00:00 2001 From: termie Date: Wed, 15 Dec 2010 10:38:30 -0800 Subject: [PATCH 149/229] add missing import --- nova/virt/xenapi_conn.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 4243111330..a88101ad02 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -51,6 +51,8 @@ import logging import sys import xmlrpclib +from eventlet import event + from nova import utils from nova import flags from nova.virt.xenapi.vmops import VMOps From dd4ee43cc2042299ed7a56b4690999fa1df120a1 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 15 Dec 2010 11:23:33 -0800 Subject: [PATCH 150/229] clean up tests and add overriden time method to utils --- nova/api/ec2/__init__.py | 11 ++++---- nova/fakememcache.py | 11 ++++---- nova/tests/middleware_unittest.py | 23 ++++++++--------- nova/utils.py | 42 +++++++++++++++++++++++++++++-- 4 files changed, 60 insertions(+), 27 deletions(-) diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 381b0e8710..5ae15f2ae3 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -79,15 +79,14 @@ class Lockout(wsgi.Middleware): sneak in before the lockout hits, but this is extremely rare and would only result in a couple of extra failed attempts.""" - def __init__(self, application, time_fn=None): - """middleware can pass a custom time function to fake for testing.""" + def __init__(self, application): + """middleware can use fake for testing.""" if FLAGS.lockout_memcached_servers: import memcache - self.mc = memcache.Client(FLAGS.lockout_memcached_servers, - debug=0) else: - from nova import fakememcache - self.mc = fakememcache.Client(time_fn=time_fn) + from nova import fakememcache as memcache + self.mc = memcache.Client(FLAGS.lockout_memcached_servers, + debug=0) super(Lockout, self).__init__(application) @webob.dec.wsgify diff --git a/nova/fakememcache.py b/nova/fakememcache.py index 0b4037ef63..67f46dbdca 100644 --- a/nova/fakememcache.py +++ b/nova/fakememcache.py @@ -18,21 +18,20 @@ """Super simple fake memcache client.""" -import time +import utils class Client(object): """Replicates a tiny subset of memcached client interface.""" - def __init__(self, time_fn=time.time, *args, **kwargs): - """Time fn is to allow testing through a custom function""" - self.time_fn = time_fn + def __init__(self, *args, **kwargs): + """Ignores the passed in args""" self.cache = {} def get(self, key): """Retrieves the value for a key or None.""" (timeout, value) = self.cache.get(key, (0, None)) - if timeout == 0 or self.time_fn() < timeout: + if timeout == 0 or utils.utcnow_ts() < timeout: return value return None @@ -40,7 +39,7 @@ class Client(object): """Sets the value for a key.""" timeout = 0 if time != 0: - timeout = self.time_fn() + time + timeout = utils.utcnow_ts() + time self.cache[key] = (timeout, value) return True diff --git a/nova/tests/middleware_unittest.py b/nova/tests/middleware_unittest.py index 61a790c1f4..0febf52d6c 100644 --- a/nova/tests/middleware_unittest.py +++ b/nova/tests/middleware_unittest.py @@ -16,6 +16,7 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime import webob import webob.dec import webob.exc @@ -23,6 +24,7 @@ import webob.exc from nova.api import ec2 from nova import flags from nova import test +from nova import utils FLAGS = flags.FLAGS @@ -39,14 +41,13 @@ def conditional_forbid(req): class LockoutTestCase(test.TrialTestCase): """Test case for the Lockout middleware.""" def setUp(self): # pylint: disable-msg=C0103 - self.local_time = 0 - self.lockout = ec2.Lockout(conditional_forbid, - time_fn=self._constant_time) super(LockoutTestCase, self).setUp() + utils.set_time_override() + self.lockout = ec2.Lockout(conditional_forbid) - def _constant_time(self): - """Helper method to force timeouts.""" - return self.local_time + def tearDown(self): # pylint: disable-msg=C0103 + utils.clear_time_override() + super(LockoutTestCase, self).tearDown() def _send_bad_attempts(self, access_key, num_attempts=1): """Fail x.""" @@ -59,10 +60,6 @@ class LockoutTestCase(test.TrialTestCase): req = webob.Request.blank('/?AWSAccessKeyId=%s' % access_key) return (req.get_response(self.lockout).status_int == 403) - def _advance_time(self, time): - """Increment time to 1 second past the lockout.""" - self.local_time = self.local_time + time - def test_lockout(self): self._send_bad_attempts('test', FLAGS.lockout_attempts) self.assertTrue(self._is_locked_out('test')) @@ -70,20 +67,20 @@ class LockoutTestCase(test.TrialTestCase): def test_timeout(self): self._send_bad_attempts('test', FLAGS.lockout_attempts) self.assertTrue(self._is_locked_out('test')) - self._advance_time(FLAGS.lockout_minutes * 60) + utils.advance_time_seconds(FLAGS.lockout_minutes * 60) self.assertFalse(self._is_locked_out('test')) def test_multiple_keys(self): self._send_bad_attempts('test1', FLAGS.lockout_attempts) self.assertTrue(self._is_locked_out('test1')) self.assertFalse(self._is_locked_out('test2')) - self._advance_time(FLAGS.lockout_minutes * 60) + utils.advance_time_seconds(FLAGS.lockout_minutes * 60) self.assertFalse(self._is_locked_out('test1')) self.assertFalse(self._is_locked_out('test2')) def test_window_timeout(self): self._send_bad_attempts('test', FLAGS.lockout_attempts - 1) self.assertFalse(self._is_locked_out('test')) - self._advance_time(FLAGS.lockout_window * 60) + utils.advance_time_seconds(FLAGS.lockout_window * 60) self._send_bad_attempts('test', FLAGS.lockout_attempts - 1) self.assertFalse(self._is_locked_out('test')) diff --git a/nova/utils.py b/nova/utils.py index 142584df8b..048a9d974d 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -21,7 +21,6 @@ System-level utilities and helper functions. """ import datetime -import functools import inspect import logging import os @@ -29,6 +28,7 @@ import random import subprocess import socket import sys +import time from xml.sax import saxutils from twisted.internet.threads import deferToThread @@ -165,13 +165,51 @@ def get_my_ip(): return "127.0.0.1" +def utcnow(): + """Overridable version of datetime.datetime.utcnow.""" + if utcnow.override_time: + return utcnow.override_time + return datetime.datetime.utcnow() + + +utcnow.override_time = None + + +def utcnow_ts(): + """Timestamp version of our utcnow function.""" + return time.mktime(utcnow().timetuple()) + + +def set_time_override(override_time=datetime.datetime.utcnow()): + """Override utils.utcnow to return a constant time.""" + utcnow.override_time = override_time + + +def advance_time_delta(timedelta): + """Advance overriden time using a datetime.timedelta.""" + assert(not utcnow.override_time is None) + utcnow.override_time += timedelta + + +def advance_time_seconds(seconds): + """Advance overriden time by seconds.""" + advance_time_delta(datetime.timedelta(0, seconds)) + + +def clear_time_override(): + """Remove the overridden time.""" + utcnow.override_time = None + + def isotime(at=None): + """Returns iso formatted utcnow.""" if not at: - at = datetime.datetime.utcnow() + at = utcnow() return at.strftime(TIME_FORMAT) def parse_isotime(timestr): + """Turn an iso formatted time back into a datetime""" return datetime.datetime.strptime(timestr, TIME_FORMAT) From 9a8113584edc9a8dbf42e7039b373429c11a7760 Mon Sep 17 00:00:00 2001 From: termie Date: Wed, 15 Dec 2010 11:53:18 -0800 Subject: [PATCH 151/229] fixes for xenapi (thanks sandywalsh) --- nova/virt/xenapi_conn.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index a88101ad02..09d399da4d 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -52,6 +52,7 @@ import sys import xmlrpclib from eventlet import event +from eventlet import tpool from nova import utils from nova import flags @@ -164,20 +165,20 @@ class XenAPISession(object): f = self._session.xenapi for m in method.split('.'): f = f.__getattr__(m) - return f(*args) + return tpool.execute(f, *args) def async_call_plugin(self, plugin, fn, args): """Call Async.host.call_plugin on a background thread.""" - return _unwrap_plugin_exceptions( - self._session.xenapi.Async.host.call_plugin, - self.get_xenapi_host(), plugin, fn, args) + return tpool.execute(_unwrap_plugin_exceptions, + self._session.xenapi.Async.host.call_plugin, + self.get_xenapi_host(), plugin, fn, args) def wait_for_task(self, task): """Return a Deferred that will give the result of the given task. The task is polled until it completes.""" done = event.Event() - loop = utils.LoopingTask(self._poll_task, task, done) + loop = utils.LoopingCall(self._poll_task, task, done) loop.start(FLAGS.xenapi_task_poll_interval, now=True) rv = done.wait() loop.stop() From e1da5d66b2e33a043e7e9ee357d9769276d6e302 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 15 Dec 2010 13:14:28 -0800 Subject: [PATCH 152/229] memcached requires strings not unicode --- nova/api/ec2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 5ae15f2ae3..def0ee207e 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -91,7 +91,7 @@ class Lockout(wsgi.Middleware): @webob.dec.wsgify def __call__(self, req): - access_key = req.params['AWSAccessKeyId'] + access_key = str(req.params['AWSAccessKeyId']) failures_key = "authfailures-%s" % access_key failures = int(self.mc.get(failures_key) or 0) if failures >= FLAGS.lockout_attempts: From a87b4081c6617ba193836ad12008204d62814549 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 15 Dec 2010 18:23:51 -0400 Subject: [PATCH 153/229] fixup after merge with trunk --- nova/virt/libvirt_conn.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index d7abc874a8..d4d616b310 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -295,12 +295,10 @@ class LibvirtConnection(object): def pause(self, instance, callback): raise exception.APIError("pause not supported for libvirt.") - @defer.inlineCallbacks @exception.wrap_exception def unpause(self, instance, callback): raise exception.APIError("unpause not supported for libvirt.") - @defer.inlineCallbacks @exception.wrap_exception def rescue(self, instance): self.destroy(instance, False) From 8060b526e1fecc66a6766de3f9e4b008e69af1e3 Mon Sep 17 00:00:00 2001 From: David Pravec Date: Thu, 16 Dec 2010 12:35:46 +0100 Subject: [PATCH 154/229] Make nova work even when user has LANG or LC_ALL configured Some commands are having different results when used in another language environment. For example ifconfig output parsing fails in my language. Also unittest using cat failed, as it didnt expect czech language in the error message. This small patch makes it work. Also adding myself to 'Authors' file. --- Authors | 1 + contrib/nova.sh | 2 +- contrib/puppet/files/production/nova-iptables | 2 ++ nova/cloudpipe/bootscript.sh | 1 + nova/tests/process_unittest.py | 2 +- tools/clean-vlans | 2 ++ tools/setup_iptables.sh | 2 +- 7 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Authors b/Authors index 565444ee12..6e0a7735f5 100644 --- a/Authors +++ b/Authors @@ -4,6 +4,7 @@ Anthony Young Armando Migliaccio Chris Behrens Chmouel Boudjnah +David Pravec Dean Troyer Devin Carlen Eldar Nugaev diff --git a/contrib/nova.sh b/contrib/nova.sh index 30df4edb65..da1ba030c3 100755 --- a/contrib/nova.sh +++ b/contrib/nova.sh @@ -15,7 +15,7 @@ if [ ! -n "$HOST_IP" ]; then # NOTE(vish): This will just get the first ip in the list, so if you # have more than one eth device set up, this will fail, and # you should explicitly set HOST_IP in your environment - HOST_IP=`ifconfig | grep -m 1 'inet addr:'| cut -d: -f2 | awk '{print $1}'` + HOST_IP=`LC_ALL=C ifconfig | grep -m 1 'inet addr:'| cut -d: -f2 | awk '{print $1}'` fi USE_MYSQL=${USE_MYSQL:-0} diff --git a/contrib/puppet/files/production/nova-iptables b/contrib/puppet/files/production/nova-iptables index b7b52df877..61e2ca2b92 100755 --- a/contrib/puppet/files/production/nova-iptables +++ b/contrib/puppet/files/production/nova-iptables @@ -30,6 +30,8 @@ if [ -f /etc/default/nova-iptables ] ; then . /etc/default/nova-iptables fi +export LC_ALL=C + API_PORT=${API_PORT:-"8773"} if [ ! -n "$IP" ]; then diff --git a/nova/cloudpipe/bootscript.sh b/nova/cloudpipe/bootscript.sh index 30d9ad1021..551b40f2fd 100755 --- a/nova/cloudpipe/bootscript.sh +++ b/nova/cloudpipe/bootscript.sh @@ -19,6 +19,7 @@ # This gets zipped and run on the cloudpipe-managed OpenVPN server +export LC_ALL=C export SUPERVISOR="http://10.255.255.1:8773/cloudpipe" export VPN_IP=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f2 | awk '{print $1}'` export BROADCAST=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f3 | awk '{print $1}'` diff --git a/nova/tests/process_unittest.py b/nova/tests/process_unittest.py index 67245af039..516b2452ff 100644 --- a/nova/tests/process_unittest.py +++ b/nova/tests/process_unittest.py @@ -49,7 +49,7 @@ class ProcessTestCase(test.TrialTestCase): def test_execute_stderr(self): pool = process.ProcessPool(2) - d = pool.simple_execute('cat BAD_FILE', check_exit_code=False) + d = pool.simple_execute('LC_ALL=C cat BAD_FILE', check_exit_code=False) def _check(rv): self.assertEqual(rv[0], '') diff --git a/tools/clean-vlans b/tools/clean-vlans index f5b0295ad1..820a9dbe5b 100755 --- a/tools/clean-vlans +++ b/tools/clean-vlans @@ -17,6 +17,8 @@ # License for the specific language governing permissions and limitations # under the License. +export LC_ALL=C + sudo ifconfig -a | grep br | grep -v bridge | cut -f1 -d" " | xargs -n1 -ifoo ifconfig foo down sudo ifconfig -a | grep br | grep -v bridge | cut -f1 -d" " | xargs -n1 -ifoo brctl delbr foo sudo ifconfig -a | grep vlan | grep -v vlan124 | grep -v vlan5 | cut -f1 -d" " | xargs -n1 -ifoo ifconfig foo down diff --git a/tools/setup_iptables.sh b/tools/setup_iptables.sh index 673353eb43..8be8cd8126 100755 --- a/tools/setup_iptables.sh +++ b/tools/setup_iptables.sh @@ -36,7 +36,7 @@ else # NOTE(vish): This will just get the first ip in the list, so if you # have more than one eth device set up, this will fail, and # you should explicitly pass in the ip of the instance - IP=`ifconfig | grep -m 1 'inet addr:'| cut -d: -f2 | awk '{print $1}'` + IP=`LC_ALL=C ifconfig | grep -m 1 'inet addr:'| cut -d: -f2 | awk '{print $1}'` fi if [ -n "$3" ]; then From a4db44b94e611798d57ad59f4d4dbb5fb00516db Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Thu, 16 Dec 2010 16:33:38 +0000 Subject: [PATCH 155/229] Removed FakeInstance and introduced stubout for DB. Code clean-up --- nova/tests/db/__init__.py | 33 +++++++ nova/tests/db/fakes.py | 61 +++++++++++++ nova/tests/xenapi/__init__.py | 33 +++++++ nova/tests/xenapi/stubs.py | 98 ++++++++++++++++++++ nova/tests/xenapi_unittest.py | 165 +++++++++------------------------- 5 files changed, 267 insertions(+), 123 deletions(-) create mode 100644 nova/tests/db/__init__.py create mode 100644 nova/tests/db/fakes.py create mode 100644 nova/tests/xenapi/__init__.py create mode 100644 nova/tests/xenapi/stubs.py diff --git a/nova/tests/db/__init__.py b/nova/tests/db/__init__.py new file mode 100644 index 0000000000..a157d7592d --- /dev/null +++ b/nova/tests/db/__init__.py @@ -0,0 +1,33 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Citrix Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright (c) 2010 Citrix Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Stubouts, mocks and fixtures for the test suite""" diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py new file mode 100644 index 0000000000..b3fb56c69f --- /dev/null +++ b/nova/tests/db/fakes.py @@ -0,0 +1,61 @@ +# 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. + +"""Stubouts, mocks and fixtures for the test suite""" + +import time + +from nova import db +from nova import utils +from nova.compute import instance_types + + +def stub_out_db_instance_api(stubs): + """ Stubs out the db API for creating Instances """ + + class FakeInstance(object): + """ Stubs out the Instance model """ + def __init__(self, values): + self.values = values + + def __getattr__(self, name): + return self.values[name] + + def fake_create(values): + """ Stubs out the db.instance_create method """ + + type_data = instance_types.INSTANCE_TYPES[values['instance_type']] + + base_options = { + 'name': values['name'], + 'reservation_id': utils.generate_uid('r'), + 'image_id': values['image_id'], + 'kernel_id': values['kernel_id'], + 'ramdisk_id': values['ramdisk_id'], + 'state_description': 'scheduling', + 'user_id': values['user_id'], + 'project_id': values['project_id'], + 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), + 'instance_type': values['instance_type'], + 'memory_mb': type_data['memory_mb'], + 'mac_address': values['mac_address'], + 'vcpus': type_data['vcpus'], + 'local_gb': type_data['local_gb'], + } + return FakeInstance(base_options) + + stubs.Set(db, 'instance_create', fake_create) diff --git a/nova/tests/xenapi/__init__.py b/nova/tests/xenapi/__init__.py new file mode 100644 index 0000000000..a157d7592d --- /dev/null +++ b/nova/tests/xenapi/__init__.py @@ -0,0 +1,33 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Citrix Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright (c) 2010 Citrix Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Stubouts, mocks and fixtures for the test suite""" diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py new file mode 100644 index 0000000000..5251893885 --- /dev/null +++ b/nova/tests/xenapi/stubs.py @@ -0,0 +1,98 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Citrix Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Stubouts, mocks and fixtures for the test suite""" + +from nova.virt import xenapi_conn +from nova.virt.xenapi import fake + + +def stubout_session(stubs, cls): + """ Stubs out two methods from XenAPISession """ + def fake_import(self): + """ Stubs out get_imported_xenapi of XenAPISession """ + fake_module = 'nova.virt.xenapi.fake' + from_list = ['fake'] + return __import__(fake_module, globals(), locals(), from_list, -1) + + stubs.Set(xenapi_conn.XenAPISession, '_create_session', + lambda s, url: cls(url)) + stubs.Set(xenapi_conn.XenAPISession, 'get_imported_xenapi', + fake_import) + + +class FakeSessionForVMTests(fake.SessionBase): + """ Stubs out a XenAPISession for VM tests """ + def __init__(self, uri): + super(FakeSessionForVMTests, self).__init__(uri) + + def network_get_all_records_where(self, _1, _2): + return self.xenapi.network.get_all_records() + + def host_call_plugin(self, _1, _2, _3, _4, _5): + return '' + + def VM_start(self, _1, ref, _2, _3): + vm = fake.get_record('VM', ref) + if vm['power_state'] != 'Halted': + raise fake.Failure(['VM_BAD_POWER_STATE', ref, 'Halted', + vm['power_state']]) + vm['power_state'] = 'Running' + + +class FakeSessionForVolumeTests(fake.SessionBase): + """ Stubs out a XenAPISession for Volume tests """ + def __init__(self, uri): + super(FakeSessionForVolumeTests, self).__init__(uri) + + def VBD_plug(self, _1, _2): + #FIXME(armando):make proper plug + pass + + def PBD_unplug(self, _1, _2): + #FIXME(armando):make proper unplug + pass + + def SR_forget(self, _1, _2): + #FIXME(armando):make proper forget + pass + + def VDI_introduce(self, _1, uuid, _2, _3, _4, _5, + _6, _7, _8, _9, _10, _11): + #FIXME(armando):make proper introduce + valid_vdi = False + refs = fake.get_all('VDI') + for ref in refs: + rec = fake.get_record('VDI', ref) + if rec['uuid'] == uuid: + valid_vdi = True + if not valid_vdi: + raise fake.Failure([['INVALID_VDI', 'session', self._session]]) + + +class FakeSessionForVolumeFailedTests(FakeSessionForVolumeTests): + """ Stubs out a XenAPISession for Volume tests: it injects failures """ + def __init__(self, uri): + super(FakeSessionForVolumeFailedTests, self).__init__(uri) + + def VDI_introduce(self, _1, uuid, _2, _3, _4, _5, + _6, _7, _8, _9, _10, _11): + # This is for testing failure + raise fake.Failure([['INVALID_VDI', 'session', self._session]]) + + def VBD_plug(self, _1, _2): + # This is for testing failure + raise fake.Failure([['INVALID_VBD', 'session', self._session]]) diff --git a/nova/tests/xenapi_unittest.py b/nova/tests/xenapi_unittest.py index b9955a9465..c2612a4c59 100644 --- a/nova/tests/xenapi_unittest.py +++ b/nova/tests/xenapi_unittest.py @@ -1,21 +1,5 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright (c) 2010 Citrix Systems, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -# vim: tabstop=4 shiftwidth=4 softtabstop=4 -# # Copyright (c) 2010 Citrix Systems, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -30,16 +14,14 @@ # License for the specific language governing permissions and limitations # under the License. +""" +Test suite for XenAPI +""" import stubout -import uuid - -from twisted.internet import defer -from twisted.internet import threads from nova import db from nova import context -from nova import exception from nova import flags from nova import test from nova import utils @@ -49,28 +31,15 @@ from nova.compute import power_state from nova.virt import xenapi_conn from nova.virt.xenapi import fake from nova.virt.xenapi import volume_utils -from nova.virt.xenapi import vm_utils -from nova.virt.xenapi import volumeops -from boto.ec2.volume import Volume +from nova.tests.db import fakes +from nova.tests.xenapi import stubs FLAGS = flags.FLAGS -def stubout_session(stubs, cls): - def fake_import(self): - fake_module = 'nova.virt.xenapi.fake' - from_list = ['fake'] - return __import__(fake_module, globals(), locals(), from_list, -1) - - stubs.Set(xenapi_conn.XenAPISession, '_create_session', - lambda s, url: cls(url)) - stubs.Set(xenapi_conn.XenAPISession, 'get_imported_xenapi', - fake_import) - - class XenAPIVolumeTestCase(test.TrialTestCase): """ - Unit tests for VM operations + Unit tests for Volume operations """ def setUp(self): super(XenAPIVolumeTestCase, self).setUp() @@ -78,7 +47,17 @@ class XenAPIVolumeTestCase(test.TrialTestCase): FLAGS.target_host = '127.0.0.1' FLAGS.xenapi_connection_url = 'test_url' FLAGS.xenapi_connection_password = 'test_pass' + fakes.stub_out_db_instance_api(self.stubs) fake.reset() + self.values = {'name': 1, + 'project_id': 'fake', + 'user_id': 'fake', + 'image_id': 1, + 'kernel_id': 2, + 'ramdisk_id': 3, + 'instance_type': 'm1.large', + 'mac_address': 'aa:bb:cc:dd:ee:ff', + } def _create_volume(self, size='0'): """Create a volume object.""" @@ -94,7 +73,7 @@ class XenAPIVolumeTestCase(test.TrialTestCase): def test_create_iscsi_storage(self): """ This shows how to test helper classes' methods """ - stubout_session(self.stubs, FakeSessionForVolumeTests) + stubs.stubout_session(self.stubs, stubs.FakeSessionForVolumeTests) session = xenapi_conn.XenAPISession('test_url', 'root', 'test_pass') helper = volume_utils.VolumeHelper helper.XenAPI = session.get_imported_xenapi() @@ -106,11 +85,13 @@ class XenAPIVolumeTestCase(test.TrialTestCase): info, label, description) + srs = fake.get_all('SR') + self.assertEqual(sr_ref, srs[0]) db.volume_destroy(context.get_admin_context(), vol['id']) def test_parse_volume_info_raise_exception(self): """ This shows how to test helper classes' methods """ - stubout_session(self.stubs, FakeSessionForVolumeTests) + stubs.stubout_session(self.stubs, stubs.FakeSessionForVolumeTests) session = xenapi_conn.XenAPISession('test_url', 'root', 'test_pass') helper = volume_utils.VolumeHelper helper.XenAPI = session.get_imported_xenapi() @@ -119,6 +100,7 @@ class XenAPIVolumeTestCase(test.TrialTestCase): info = helper.parse_volume_info(vol['ec2_id'], '/dev/sd') def check(exc): + """ handler """ self.assertIsInstance(exc.value, volume_utils.StorageError) info.addErrback(check) @@ -126,16 +108,16 @@ class XenAPIVolumeTestCase(test.TrialTestCase): def test_attach_volume(self): """ This shows how to test Ops classes' methods """ - stubout_session(self.stubs, FakeSessionForVolumeTests) + stubs.stubout_session(self.stubs, stubs.FakeSessionForVolumeTests) conn = xenapi_conn.get_connection(False) volume = self._create_volume() - instance = FakeInstance(1, 'fake', 'fake', 1, 2, 3, - 'm1.large', 'aa:bb:cc:dd:ee:ff') + instance = db.instance_create(self.values) fake.create_vm(instance.name, 'Running') result = conn.attach_volume(instance.name, volume['ec2_id'], '/dev/sdc') def check(_): + """ handler """ # check that the VM has a VBD attached to it # Get XenAPI reference for the VM vms = fake.get_all('VM') @@ -150,16 +132,17 @@ class XenAPIVolumeTestCase(test.TrialTestCase): def test_attach_volume_raise_exception(self): """ This shows how to test when exceptions are raised """ - stubout_session(self.stubs, FakeSessionForVolumeFailedTests) + stubs.stubout_session(self.stubs, + stubs.FakeSessionForVolumeFailedTests) conn = xenapi_conn.get_connection(False) volume = self._create_volume() - instance = FakeInstance(1, 'fake', 'fake', 1, 2, 3, - 'm1.large', 'aa:bb:cc:dd:ee:ff') + instance = db.instance_create(self.values) fake.create_vm(instance.name, 'Running') result = conn.attach_volume(instance.name, volume['ec2_id'], '/dev/sdc') def check(exc): + """ handler """ if exc: pass else: @@ -188,22 +171,32 @@ class XenAPIVMTestCase(test.TrialTestCase): FLAGS.xenapi_connection_url = 'test_url' FLAGS.xenapi_connection_password = 'test_pass' fake.reset() + fakes.stub_out_db_instance_api(self.stubs) fake.create_network('fake', FLAGS.flat_network_bridge) def test_list_instances_0(self): - stubout_session(self.stubs, FakeSessionForVMTests) + stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) conn = xenapi_conn.get_connection(False) instances = conn.list_instances() self.assertEquals(instances, []) def test_spawn(self): - stubout_session(self.stubs, FakeSessionForVMTests) + stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) + values = {'name': 1, + 'project_id': self.project.id, + 'user_id': self.user.id, + 'image_id': 1, + 'kernel_id': 2, + 'ramdisk_id': 3, + 'instance_type': 'm1.large', + 'mac_address': 'aa:bb:cc:dd:ee:ff', + } conn = xenapi_conn.get_connection(False) - instance = FakeInstance(1, self.project.id, self.user.id, 1, 2, 3, - 'm1.large', 'aa:bb:cc:dd:ee:ff') + instance = db.instance_create(values) result = conn.spawn(instance) def check(_): + """ handler """ instances = conn.list_instances() self.assertEquals(instances, [1]) @@ -241,77 +234,3 @@ class XenAPIVMTestCase(test.TrialTestCase): self.manager.delete_project(self.project) self.manager.delete_user(self.user) self.stubs.UnsetAll() - - -class FakeInstance(): - def __init__(self, name, project_id, user_id, image_id, kernel_id, - ramdisk_id, instance_type, mac_address): - self.name = name - self.project_id = project_id - self.user_id = user_id - self.image_id = image_id - self.kernel_id = kernel_id - self.ramdisk_id = ramdisk_id - self.instance_type = instance_type - self.mac_address = mac_address - - -class FakeSessionForVMTests(fake.SessionBase): - def __init__(self, uri): - super(FakeSessionForVMTests, self).__init__(uri) - - def network_get_all_records_where(self, _1, _2): - return self.xenapi.network.get_all_records() - - def host_call_plugin(self, _1, _2, _3, _4, _5): - return '' - - def VM_start(self, _1, ref, _2, _3): - vm = fake.get_record('VM', ref) - if vm['power_state'] != 'Halted': - raise fake.Failure(['VM_BAD_POWER_STATE', ref, 'Halted', - vm['power_state']]) - vm['power_state'] = 'Running' - - -class FakeSessionForVolumeTests(fake.SessionBase): - def __init__(self, uri): - super(FakeSessionForVolumeTests, self).__init__(uri) - - def VBD_plug(self, _1, _2): - #FIXME(armando):make proper plug - pass - - def PBD_unplug(self, _1, _2): - #FIXME(armando):make proper unplug - pass - - def SR_forget(self, _1, _2): - #FIXME(armando):make proper forget - pass - - def VDI_introduce(self, _1, uuid, _2, _3, _4, _5, - _6, _7, _8, _9, _10, _11): - #FIXME(armando):make proper introduce - valid_vdi = False - refs = fake.get_all('VDI') - for ref in refs: - rec = fake.get_record('VDI', ref) - if rec['uuid'] == uuid: - valid_vdi = True - if not valid_vdi: - raise fake.Failure([['INVALID_VDI', 'session', self._session]]) - - -class FakeSessionForVolumeFailedTests(FakeSessionForVolumeTests): - def __init__(self, uri): - super(FakeSessionForVolumeFailedTests, self).__init__(uri) - - def VDI_introduce(self, _1, uuid, _2, _3, _4, _5, - _6, _7, _8, _9, _10, _11): - # test failure - raise fake.Failure([['INVALID_VDI', 'session', self._session]]) - - def VBD_plug(self, _1, _2): - # test failure - raise fake.Failure([['INVALID_VBD', 'session', self._session]]) From 8152acf7c3df83a04591fdafb21201965da7bfad Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Thu, 16 Dec 2010 17:47:48 +0000 Subject: [PATCH 156/229] fake session clean-up --- nova/tests/db/__init__.py | 16 ---------------- nova/tests/xenapi/__init__.py | 16 ---------------- nova/virt/xenapi/fake.py | 34 ++++++++++++++++++++++------------ 3 files changed, 22 insertions(+), 44 deletions(-) diff --git a/nova/tests/db/__init__.py b/nova/tests/db/__init__.py index a157d7592d..dcd81b7432 100644 --- a/nova/tests/db/__init__.py +++ b/nova/tests/db/__init__.py @@ -1,21 +1,5 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright (c) 2010 Citrix Systems, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -# vim: tabstop=4 shiftwidth=4 softtabstop=4 -# # Copyright (c) 2010 Citrix Systems, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/nova/tests/xenapi/__init__.py b/nova/tests/xenapi/__init__.py index a157d7592d..dcd81b7432 100644 --- a/nova/tests/xenapi/__init__.py +++ b/nova/tests/xenapi/__init__.py @@ -1,21 +1,5 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright (c) 2010 Citrix Systems, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -# vim: tabstop=4 shiftwidth=4 softtabstop=4 -# # Copyright (c) 2010 Citrix Systems, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index 038064e8ee..a46f3fd800 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -105,9 +105,10 @@ def create_vdi(name_label, read_only, sr_ref, sharable): }) -def create_pbd(config, attached): +def create_pbd(config, sr_ref, attached): return _create_object('PBD', { 'device-config': config, + 'SR': sr_ref, 'currently-attached': attached, }) @@ -126,6 +127,21 @@ def _create_object(table, obj): return ref +def _create_sr(table, obj): + sr_type = obj[6] + # Forces fake to support iscsi only + if sr_type != 'iscsi': + raise Failure(['SR_UNKNOWN_DRIVER', sr_type]) + sr_ref = _create_object(table, obj[2]) + vdi_ref = create_vdi('', False, sr_ref, False) + pbd_ref = create_pbd('', sr_ref, True) + _db_content['SR'][sr_ref]['VDIs'] = [vdi_ref] + _db_content['SR'][sr_ref]['PBDs'] = [pbd_ref] + _db_content['VDI'][vdi_ref]['SR'] = sr_ref + _db_content['PBD'][pbd_ref]['SR'] = sr_ref + return sr_ref + + def get_all(table): return _db_content[table].keys() @@ -296,19 +312,13 @@ class SessionBase(object): def _create(self, name, params): self._check_session(params) - expected = 2 - if name == 'SR.create': - expected = 10 + is_sr_create = name == 'SR.create' + # Storage Repositories have a different API + expected = is_sr_create and 10 or 2 self._check_arg_count(params, expected) (cls, _) = name.split('.') - if name == 'SR.create': - vdi_ref = create_vdi('', False, '', False) - pbd_ref = create_pbd('', True) - params[2]['VDIs'] = [vdi_ref] - params[2]['PBDs'] = [pbd_ref] - ref = _create_object(cls, params[2]) - else: - ref = _create_object(cls, params[1]) + ref = is_sr_create and \ + _create_sr(cls, params) or _create_object(cls, params[1]) obj = get_record(cls, ref) # Add RO fields From e01e6d7976adfd99addf31f4f914c7625a394fda Mon Sep 17 00:00:00 2001 From: Cerberus Date: Thu, 16 Dec 2010 12:09:38 -0600 Subject: [PATCH 157/229] Moved implementation specific stuff from the middleware into their respective modules --- nova/api/openstack/__init__.py | 83 +++------------------ nova/api/openstack/auth.py | 20 +++-- nova/api/openstack/common.py | 17 +++++ nova/api/openstack/flavors.py | 3 +- nova/api/openstack/images.py | 6 +- nova/api/openstack/ratelimiting/__init__.py | 60 +++++++++++++++ nova/api/openstack/servers.py | 3 +- 7 files changed, 106 insertions(+), 86 deletions(-) create mode 100644 nova/api/openstack/common.py diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index b9ecbd9b8d..e941694d96 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -49,6 +49,10 @@ flags.DEFINE_string('nova_api_auth', 'nova.api.openstack.auth.BasicApiAuthManager', 'The auth mechanism to use for the OpenStack API implemenation') +flags.DEFINE_string('os_api_ratelimiting', + 'nova.api.openstack.ratelimiting.BasicRateLimiting', + 'Default ratelimiting implementation for the Openstack API') + flags.DEFINE_bool('allow_admin_api', False, 'When True, this API service will accept admin operations.') @@ -81,10 +85,10 @@ class AuthMiddleware(wsgi.Middleware): @webob.dec.wsgify def __call__(self, req): - if 'X-Auth-Token' not in req.headers: + if not self.auth_driver.has_authentication(req) return self.auth_driver.authenticate(req) - user = self.auth_driver.authorize_token(req.headers["X-Auth-Token"]) + user = self.auth_driver.get_user_by_authentication(req) if not user: return faults.Fault(webob.exc.HTTPUnauthorized()) @@ -104,62 +108,12 @@ class RateLimitingMiddleware(wsgi.Middleware): at the given host+port to keep rate counters. """ super(RateLimitingMiddleware, self).__init__(application) - if not service_host: - #TODO(gundlach): These limits were based on limitations of Cloud - #Servers. We should revisit them in Nova. - self.limiter = ratelimiting.Limiter(limits={ - 'DELETE': (100, ratelimiting.PER_MINUTE), - 'PUT': (10, ratelimiting.PER_MINUTE), - 'POST': (10, ratelimiting.PER_MINUTE), - 'POST servers': (50, ratelimiting.PER_DAY), - 'GET changes-since': (3, ratelimiting.PER_MINUTE), - }) - else: - self.limiter = ratelimiting.WSGIAppProxy(service_host) + self._limiting_driver = + utils.import_class(FLAGS.os_api_ratelimiting)(service_host) @webob.dec.wsgify def __call__(self, req): - """Rate limit the request. - - If the request should be rate limited, return a 413 status with a - Retry-After header giving the time when the request would succeed. - """ - action_name = self.get_action_name(req) - if not action_name: - # Not rate limited - return self.application - 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( - explanation='Too many requests.', - headers={'Retry-After': time.time() + delay}) - raise faults.Fault(exc) - return self.application - - def get_delay(self, action_name, username): - """Return the delay for the given action and username, or None if - the action would not be rate limited. - """ - if action_name == 'POST servers': - # "POST servers" is a POST, so it counts against "POST" too. - # Attempt the "POST" first, lest we are rate limited by "POST" but - # use up a precious "POST servers" call. - delay = self.limiter.perform("POST", username=username) - if delay: - return delay - return self.limiter.perform(action_name, username=username) - - def get_action_name(self, req): - """Return the action name for this request.""" - if req.method == 'GET' and 'changes-since' in req.GET: - return 'GET changes-since' - if req.method == 'POST' and req.path_info.startswith('/servers'): - return 'POST servers' - if req.method in ['PUT', 'POST', 'DELETE']: - return req.method - return None + return self._limiting_driver.limited_request(req) class APIRouter(wsgi.Router): @@ -191,22 +145,3 @@ class APIRouter(wsgi.Router): # TODO: Place routes for admin operations here. super(APIRouter, self).__init__(mapper) - - -def limited(items, req): - """Return a slice of items according to requested offset and limit. - - items - a sliceable - req - wobob.Request possibly containing offset and limit GET variables. - offset is where to start in the list, and limit is the maximum number - of items to return. - - If limit is not specified, 0, or > 1000, defaults to 1000. - """ - offset = int(req.GET.get('offset', 0)) - limit = int(req.GET.get('limit', 0)) - if not limit: - limit = 1000 - limit = min(1000, limit) - range_end = offset + limit - return items[offset:range_end] diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index fcda97ab19..da8ebcfcd6 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -7,6 +7,7 @@ import webob.exc import webob.dec from nova import auth +from nova import context from nova import db from nova import flags from nova import manager @@ -16,10 +17,6 @@ from nova.api.openstack import faults FLAGS = flags.FLAGS -class Context(object): - pass - - class BasicApiAuthManager(object): """ Implements a somewhat rudimentary version of OpenStack Auth""" @@ -28,9 +25,14 @@ class BasicApiAuthManager(object): db_driver = FLAGS.db_driver self.db = utils.import_object(db_driver) self.auth = auth.manager.AuthManager() - self.context = Context() super(BasicApiAuthManager, self).__init__() + def has_authentication(self, req): + return 'X-Auth-Token' in req.headers: + + def get_user_by_authentication(self, req): + return self.auth_driver.authorize_token(req.headers["X-Auth-Token"]) + def authenticate(self, req): # Unless the request is explicitly made against // don't # honor it @@ -68,11 +70,12 @@ class BasicApiAuthManager(object): This method will also remove the token if the timestamp is older than 2 days ago. """ - token = self.db.auth_get_token(self.context, token_hash) + ctxt = context.get_admin_context() + token = self.db.auth_get_token(ctxt, token_hash) if token: delta = datetime.datetime.now() - token.created_at if delta.days >= 2: - self.db.auth_destroy_token(self.context, token) + self.db.auth_destroy_token(ctxt, token) else: return self.auth.get_user(token.user_id) return None @@ -84,6 +87,7 @@ class BasicApiAuthManager(object): key - string API key req - webob.Request object """ + ctxt = context.get_admin_context() user = self.auth.get_user_from_access_key(key) if user and user.name == username: token_hash = hashlib.sha1('%s%s%f' % (username, key, @@ -95,6 +99,6 @@ class BasicApiAuthManager(object): token_dict['server_management_url'] = req.url token_dict['storage_url'] = '' token_dict['user_id'] = user.id - token = self.db.auth_create_token(self.context, token_dict) + token = self.db.auth_create_token(ctxt, token_dict) return token, user return None, None diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py new file mode 100644 index 0000000000..29e9a86233 --- /dev/null +++ b/nova/api/openstack/common.py @@ -0,0 +1,17 @@ +def limited(items, req): + """Return a slice of items according to requested offset and limit. + + items - a sliceable + req - wobob.Request possibly containing offset and limit GET variables. + offset is where to start in the list, and limit is the maximum number + of items to return. + + If limit is not specified, 0, or > 1000, defaults to 1000. + """ + offset = int(req.GET.get('offset', 0)) + limit = int(req.GET.get('limit', 0)) + if not limit: + limit = 1000 + limit = min(1000, limit) + range_end = offset + limit + return items[offset:range_end] diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index f23f74fd11..f620d41072 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -18,6 +18,7 @@ from webob import exc from nova.api.openstack import faults +from nova.api.openstack import common from nova.compute import instance_types from nova import wsgi import nova.api.openstack @@ -39,7 +40,7 @@ class Controller(wsgi.Controller): def detail(self, req): """Return all flavors in detail.""" items = [self.show(req, id)['flavor'] for id in self._all_ids()] - items = nova.api.openstack.limited(items, req) + items = common.limited(items, req) return dict(flavors=items) def show(self, req, id): diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 4a0a8e6f1c..fe8d9d75ff 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -22,6 +22,8 @@ from nova import utils from nova import wsgi import nova.api.openstack import nova.image.service + +from nova.api.openstack import common from nova.api.openstack import faults @@ -48,11 +50,11 @@ class Controller(wsgi.Controller): """Return all public images in detail.""" try: images = self._service.detail(req.environ['nova.context']) - images = nova.api.openstack.limited(images, req) + images = common.limited(images, req) except NotImplementedError: # Emulate detail() using repeated calls to show() images = self._service.index(ctxt) - images = nova.api.openstack.limited(images, req) + images = common.limited(images, req) images = [self._service.show(ctxt, i['id']) for i in images] return dict(images=images) diff --git a/nova/api/openstack/ratelimiting/__init__.py b/nova/api/openstack/ratelimiting/__init__.py index 918caf0559..d1da9afa78 100644 --- a/nova/api/openstack/ratelimiting/__init__.py +++ b/nova/api/openstack/ratelimiting/__init__.py @@ -14,6 +14,66 @@ PER_HOUR = 60 * 60 PER_DAY = 60 * 60 * 24 +class BasicRateLimiting(object): + """ Implements Rate limits as per the Rackspace CloudServers API spec. """ + + def __init__(self, service_host): + if not service_host: + #TODO(gundlach): These limits were based on limitations of Cloud + #Servers. We should revisit them in Nova. + self.limiter = ratelimiting.Limiter(limits={ + 'DELETE': (100, ratelimiting.PER_MINUTE), + 'PUT': (10, ratelimiting.PER_MINUTE), + 'POST': (10, ratelimiting.PER_MINUTE), + 'POST servers': (50, ratelimiting.PER_DAY), + 'GET changes-since': (3, ratelimiting.PER_MINUTE), + }) + else: + self.limiter = ratelimiting.WSGIAppProxy(service_host) + + def limited_request(self, req): + """Rate limit the request. + + If the request should be rate limited, return a 413 status with a + Retry-After header giving the time when the request would succeed. + """ + action_name = self.get_action_name(req) + if not action_name: + # Not rate limited + return self.application + 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( + explanation='Too many requests.', + headers={'Retry-After': time.time() + delay}) + raise faults.Fault(exc) + return self.application + + def get_delay(self, action_name, username): + """Return the delay for the given action and username, or None if + the action would not be rate limited. + """ + if action_name == 'POST servers': + # "POST servers" is a POST, so it counts against "POST" too. + # Attempt the "POST" first, lest we are rate limited by "POST" but + # use up a precious "POST servers" call. + delay = self.limiter.perform("POST", username=username) + if delay: + return delay + return self.limiter.perform(action_name, username=username) + + def get_action_name(self, req): + """Return the action name for this request.""" + if req.method == 'GET' and 'changes-since' in req.GET: + return 'GET changes-since' + if req.method == 'POST' and req.path_info.startswith('/servers'): + return 'POST servers' + if req.method in ['PUT', 'POST', 'DELETE']: + return req.method + return None + class Limiter(object): """Class providing rate limiting of arbitrary actions.""" diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 7704f48f18..9e6047805a 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -19,6 +19,7 @@ from webob import exc from nova import exception from nova import wsgi +from nova.api.openstack import common from nova.api.openstack import faults from nova.auth import manager as auth_manager from nova.compute import api as compute_api @@ -91,7 +92,7 @@ class Controller(wsgi.Controller): """ instance_list = self.compute_api.get_instances( req.environ['nova.context']) - limited_list = nova.api.openstack.limited(instance_list, req) + limited_list = common.limited(instance_list, req) res = [entity_maker(inst)['server'] for inst in limited_list] return _entity_list(res) From db96fd559d28bcfdf8cc29d79b9afca6dea1cfb7 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Thu, 16 Dec 2010 18:44:42 +0000 Subject: [PATCH 158/229] reviewed the FIXMEs, and spotted an uncaught exception in volume_utils...yay! --- nova/tests/virt_unittest.py | 2 -- nova/tests/xenapi/stubs.py | 24 +++++++++--------------- nova/tests/xenapi_unittest.py | 5 ++--- nova/virt/xenapi/fake.py | 1 + nova/virt/xenapi/volume_utils.py | 29 +++++++++++++++++------------ 5 files changed, 29 insertions(+), 32 deletions(-) diff --git a/nova/tests/virt_unittest.py b/nova/tests/virt_unittest.py index 52843b7037..d49383fb7b 100644 --- a/nova/tests/virt_unittest.py +++ b/nova/tests/virt_unittest.py @@ -25,8 +25,6 @@ from nova import utils from nova.api.ec2 import cloud from nova.auth import manager from nova.virt import libvirt_conn -from nova.virt.xenapi import fake -from nova.virt.xenapi import volume_utils FLAGS = flags.FLAGS flags.DECLARE('instances_path', 'nova.compute.manager') diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 5251893885..11dd535d44 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -58,21 +58,12 @@ class FakeSessionForVolumeTests(fake.SessionBase): def __init__(self, uri): super(FakeSessionForVolumeTests, self).__init__(uri) - def VBD_plug(self, _1, _2): - #FIXME(armando):make proper plug - pass - - def PBD_unplug(self, _1, _2): - #FIXME(armando):make proper unplug - pass - - def SR_forget(self, _1, _2): - #FIXME(armando):make proper forget - pass + def VBD_plug(self, _1, ref): + rec = fake.get_record('VBD', ref) + rec['currently-attached'] = True def VDI_introduce(self, _1, uuid, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11): - #FIXME(armando):make proper introduce valid_vdi = False refs = fake.get_all('VDI') for ref in refs: @@ -93,6 +84,9 @@ class FakeSessionForVolumeFailedTests(FakeSessionForVolumeTests): # This is for testing failure raise fake.Failure([['INVALID_VDI', 'session', self._session]]) - def VBD_plug(self, _1, _2): - # This is for testing failure - raise fake.Failure([['INVALID_VBD', 'session', self._session]]) + def PBD_unplug(self, _1, ref): + rec = fake.get_record('PBD', ref) + rec['currently-attached'] = False + + def SR_forget(self, _1, ref): + pass diff --git a/nova/tests/xenapi_unittest.py b/nova/tests/xenapi_unittest.py index c2612a4c59..839d6aa440 100644 --- a/nova/tests/xenapi_unittest.py +++ b/nova/tests/xenapi_unittest.py @@ -141,14 +141,13 @@ class XenAPIVolumeTestCase(test.TrialTestCase): result = conn.attach_volume(instance.name, volume['ec2_id'], '/dev/sdc') - def check(exc): + def check_exception(exc): """ handler """ if exc: pass else: self.fail('Oops, no exception has been raised!') - - result.addErrback(check) + result.addErrback(check_exception) return result def tearDown(self): diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index a46f3fd800..7877b5905b 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -102,6 +102,7 @@ def create_vdi(name_label, read_only, sr_ref, sharable): 'location': '', 'xenstore_data': '', 'sm_config': {}, + 'VBDs': {}, }) diff --git a/nova/virt/xenapi/volume_utils.py b/nova/virt/xenapi/volume_utils.py index 4482e465c2..8d1d6fb81d 100644 --- a/nova/virt/xenapi/volume_utils.py +++ b/nova/virt/xenapi/volume_utils.py @@ -152,18 +152,23 @@ class VolumeHelper(HelperBase): logging.warn(exc) raise StorageError('Unable to get record of VDI %s on' % vdis[0]) else: - return session.get_xenapi().VDI.introduce( - vdi_rec['uuid'], - vdi_rec['name_label'], - vdi_rec['name_description'], - vdi_rec['SR'], - vdi_rec['type'], - vdi_rec['sharable'], - vdi_rec['read_only'], - vdi_rec['other_config'], - vdi_rec['location'], - vdi_rec['xenstore_data'], - vdi_rec['sm_config']) + try: + vdi_ref = session.get_xenapi().VDI.introduce( + vdi_rec['uuid'], + vdi_rec['name_label'], + vdi_rec['name_description'], + vdi_rec['SR'], + vdi_rec['type'], + vdi_rec['sharable'], + vdi_rec['read_only'], + vdi_rec['other_config'], + vdi_rec['location'], + vdi_rec['xenstore_data'], + vdi_rec['sm_config']) + except cls.XenAPI.Failure, exc: + logging.warn(exc) + raise StorageError('Unable to introduce VDI for SR %s' + % sr_ref) @classmethod @defer.inlineCallbacks From 9b049acc27d477a1ab9e13c9e064e59d8bd0a3ae Mon Sep 17 00:00:00 2001 From: termie Date: Thu, 16 Dec 2010 10:52:30 -0800 Subject: [PATCH 159/229] pep8 fixes --- nova/compute/manager.py | 2 +- nova/service.py | 10 +++++----- nova/test.py | 1 + nova/tests/rpc_unittest.py | 2 +- nova/tests/service_unittest.py | 2 +- nova/utils.py | 7 ++++--- nova/virt/libvirt_conn.py | 12 ++++++------ nova/virt/xenapi_conn.py | 2 +- 8 files changed, 20 insertions(+), 18 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index f90f28b78d..7eb60e262d 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -64,7 +64,7 @@ class ComputeManager(manager.Manager): self.network_manager = utils.import_object(FLAGS.network_manager) self.volume_manager = utils.import_object(FLAGS.volume_manager) super(ComputeManager, self).__init__(*args, **kwargs) - + def _update_state(self, context, instance_id): """Update the state of an instance from the driver info.""" # FIXME(ja): include other fields from state? diff --git a/nova/service.py b/nova/service.py index 3d40e83a6c..ac30aaceba 100644 --- a/nova/service.py +++ b/nova/service.py @@ -99,7 +99,7 @@ class Service(object): self.timers.append(consumer_all.attach_to_eventlet()) self.timers.append(consumer_node.attach_to_eventlet()) - + pulse = utils.LoopingCall(self.report_state) pulse.start(interval=self.report_interval, now=False) self.timers.append(pulse) @@ -122,7 +122,7 @@ class Service(object): return getattr(manager, key) @classmethod - def create(cls, + def create(cls, host=None, binary=None, topic=None, @@ -192,7 +192,7 @@ class Service(object): db.service_update(ctxt, self.service_id, {'report_count': service_ref['report_count'] + 1}) - + # TODO(termie): make this pattern be more elegant. if getattr(self, "model_disconnected", False): self.model_disconnected = False @@ -207,7 +207,7 @@ class Service(object): def serve(*services): argv = FLAGS(sys.argv) - + if not services: services = [Service.create()] @@ -227,7 +227,7 @@ def serve(*services): for x in services: x.start() - + def wait(): while True: diff --git a/nova/test.py b/nova/test.py index ecc97aa4d9..7076f1bf4e 100644 --- a/nova/test.py +++ b/nova/test.py @@ -55,6 +55,7 @@ def skip_if_fake(func): return func(*args, **kw) return _skipper + class TestCase(unittest.TestCase): """Test case base class for all unit tests""" def setUp(self): diff --git a/nova/tests/rpc_unittest.py b/nova/tests/rpc_unittest.py index 4128c35b8c..a2495e65a2 100644 --- a/nova/tests/rpc_unittest.py +++ b/nova/tests/rpc_unittest.py @@ -67,7 +67,7 @@ class RpcTestCase(test.TestCase): self.assertRaises(rpc.RemoteError, rpc.call, self.context, - 'test', + 'test', {"method": "fail", "args": {"value": value}}) try: diff --git a/nova/tests/service_unittest.py b/nova/tests/service_unittest.py index 6bdc7071cb..47c092f8e2 100644 --- a/nova/tests/service_unittest.py +++ b/nova/tests/service_unittest.py @@ -119,7 +119,7 @@ class ServiceTestCase(test.TestCase): service.db.service_create(mox.IgnoreArg(), service_create).AndReturn(service_ref) self.mox.ReplayAll() - + app.start() app.stop() self.assert_(app) diff --git a/nova/utils.py b/nova/utils.py index 5f2d472021..ea1f04ca79 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -233,6 +233,7 @@ class LoopingCall(object): def start(self, interval, now=True): self._running = True done = event.Event() + def _inner(): if not now: greenthread.sleep(interval) @@ -244,14 +245,14 @@ class LoopingCall(object): logging.exception('in looping call') done.send_exception(*sys.exc_info()) return - + done.send(True) self.done = done - + greenthread.spawn(_inner) return self.done - + def stop(self): self._running = False diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index ba51f8f698..5a8c718506 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -205,7 +205,7 @@ class LibvirtConnection(object): timer.f = _wait_for_shutdown timer_done = timer.start(interval=0.5, now=True) - + # NOTE(termie): this is strictly superfluous (we could put the # cleanup code in the timer), but this emulates the # previous model so I am keeping it around until @@ -387,7 +387,7 @@ class LibvirtConnection(object): def get_console_output(self, instance): console_log = os.path.join(FLAGS.instances_path, instance['name'], 'console.log') - + utils.execute('sudo chown %d %s' % (os.getuid(), console_log)) if FLAGS.libvirt_type == 'xen': @@ -439,11 +439,11 @@ class LibvirtConnection(object): if not os.path.exists(basepath('ramdisk')): images.fetch(inst.ramdisk_id, basepath('ramdisk'), user, project) - + def execute(cmd, process_input=None, check_exit_code=True): - return utils.execute(cmd=cmd, - process_input=process_input, - check_exit_code=check_exit_code) + return utils.execute(cmd=cmd, + process_input=process_input, + check_exit_code=check_exit_code) key = str(inst['key_data']) net = None diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 09d399da4d..6beb08f5e8 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -200,7 +200,7 @@ class XenAPISession(object): error_info = self._session.xenapi.task.get_error_info(task) logging.warn('Task %s status: %s. %s', task, status, error_info) - done.send_exception(XenAPI.Failure(error_info)) + done.send_exception(XenAPI.Failure(error_info)) #logging.debug('Polling task %s done.', task) except XenAPI.Failure, exc: logging.warn(exc) From dc29400d104d34c6383132a43e018f7724e85ec3 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 16 Dec 2010 19:13:37 +0000 Subject: [PATCH 160/229] use getent, update docstring --- CA/genvpn.sh | 36 ++++++++++++++++++++++++++++++++++++ nova/auth/manager.py | 3 ++- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100755 CA/genvpn.sh diff --git a/CA/genvpn.sh b/CA/genvpn.sh new file mode 100755 index 0000000000..7e7db185d6 --- /dev/null +++ b/CA/genvpn.sh @@ -0,0 +1,36 @@ +#!/bin/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. + +# This gets zipped and run on the cloudpipe-managed OpenVPN server +NAME=$1 +SUBJ=$2 + +mkdir -p projects/$NAME +cd projects/$NAME + +# generate a server priv key +openssl genrsa -out server.key 2048 + +# generate a server CSR +openssl req -new -key server.key -out server.csr -batch -subj "$SUBJ" + +novauid=`getent passwd nova | awk -F: '{print $3}'` +if [ ! -z "${novauid}" ] && [ "`id -u`" != "${novauid}" ]; then + sudo chown -R nova:nogroup . +fi diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 783ef51af1..735473027b 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -65,7 +65,8 @@ flags.DEFINE_string('credential_key_file', 'pk.pem', flags.DEFINE_string('credential_cert_file', 'cert.pem', 'Filename of certificate in credentials zip') flags.DEFINE_string('credential_rc_file', '%src', - 'Filename of rc in credentials zip') + 'Filename of rc in credentials zip, %s will be ' + 'replaced by name of the region (nova by default)') flags.DEFINE_string('auth_driver', 'nova.auth.dbdriver.DbDriver', 'Driver that auth manager uses') From d283922defdda6ede5fa2e09656cd8d411a90096 Mon Sep 17 00:00:00 2001 From: "jaypipes@gmail.com" <> Date: Thu, 16 Dec 2010 14:47:42 -0500 Subject: [PATCH 161/229] PEP8 cleanups --- nova/api/ec2/cloud.py | 3 ++- nova/objectstore/handler.py | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 4b8b85b4c4..d638582a73 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -360,7 +360,8 @@ class CloudController(object): criteria = self._revoke_rule_args_to_dict(context, **kwargs) if criteria == None: - raise exception.ApiError(_("No rule for the specified parameters.")) + raise exception.ApiError(_("No rule for the specified " + "parameters.")) for rule in security_group.rules: match = True diff --git a/nova/objectstore/handler.py b/nova/objectstore/handler.py index 0c71c3705f..52257f69fc 100644 --- a/nova/objectstore/handler.py +++ b/nova/objectstore/handler.py @@ -261,7 +261,9 @@ class ObjectResource(ErrorHandlingResource): Raises NotAuthorized if user in request context is not authorized to delete the object. """ - logging.debug(_("Getting object: %s / %s"), self.bucket.name, self.name) + logging.debug(_("Getting object: %s / %s"), + self.bucket.name, + self.name) if not self.bucket.is_authorized(request.context): raise exception.NotAuthorized() @@ -279,7 +281,9 @@ class ObjectResource(ErrorHandlingResource): Raises NotAuthorized if user in request context is not authorized to delete the object. """ - logging.debug(_("Putting object: %s / %s"), self.bucket.name, self.name) + logging.debug(_("Putting object: %s / %s"), + self.bucket.name, + self.name) if not self.bucket.is_authorized(request.context): raise exception.NotAuthorized() From e306d236e3cf91c00a36940e277ad2d105b055a1 Mon Sep 17 00:00:00 2001 From: termie Date: Thu, 16 Dec 2010 12:45:06 -0800 Subject: [PATCH 162/229] pep8 fixes for bin --- bin/nova-combined | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/nova-combined b/bin/nova-combined index 5f635b3a3d..c6a04f7e93 100755 --- a/bin/nova-combined +++ b/bin/nova-combined @@ -50,7 +50,7 @@ flags.DEFINE_string('ec2api_host', '0.0.0.0', 'EC2 API host') if __name__ == '__main__': utils.default_flagfile() FLAGS(sys.argv) - + compute = service.Service.create(binary='nova-compute') network = service.Service.create(binary='nova-network') volume = service.Service.create(binary='nova-volume') @@ -63,4 +63,3 @@ if __name__ == '__main__': server.start(api.API('os'), FLAGS.osapi_port, host=FLAGS.osapi_host) server.start(api.API('ec2'), FLAGS.ec2api_port, host=FLAGS.ec2api_host) server.wait() - From 611935aa3e3a66e9638b0c127041a6fca4788b9c Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 16 Dec 2010 15:03:37 -0600 Subject: [PATCH 163/229] Put flags back in vm_utils --- nova/virt/xenapi/vm_utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index dde1384048..b83ae94751 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -21,15 +21,20 @@ their attributes like VDIs, VIFs, as well as their lookup functions. import logging import urllib + from xml.dom import minidom +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, @@ -37,7 +42,6 @@ XENAPI_POWER_STATE = { 'Suspended': power_state.SHUTDOWN, # FIXME 'Crashed': power_state.CRASHED} - XenAPI = None From e5a3d993cb13c8dc5e984a67521f77ce8fdf8e4c Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 16 Dec 2010 15:19:35 -0600 Subject: [PATCH 164/229] Removed unnecessary blank lines --- nova/virt/xenapi/vm_utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index b83ae94751..2f5d78e759 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -21,12 +21,10 @@ their attributes like VDIs, VIFs, as well as their lookup functions. import logging import urllib - from xml.dom import minidom 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 7954862c8133bacd5e612864a26e7d0ae9b0d663 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 16 Dec 2010 15:54:38 -0600 Subject: [PATCH 165/229] Added Instance Diagnostics DB model --- nova/db/sqlalchemy/models.py | 23 ++++++++++++++++++++--- nova/virt/xenapi/vm_utils.py | 6 +----- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index fe0a9a9216..61764ee8df 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -22,7 +22,7 @@ SQLAlchemy models for nova data. import datetime from sqlalchemy.orm import relationship, backref, object_mapper -from sqlalchemy import Column, Integer, String, schema +from sqlalchemy import Column, Integer, Float, String, schema from sqlalchemy import ForeignKey, DateTime, Boolean, Text from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.declarative import declarative_base @@ -226,6 +226,23 @@ class Instance(BASE, NovaBase): # 'shutdown', 'shutoff', 'crashed']) +class InstanceDiagnostics(BASE, NovaBase): + """Represents a guest VM's diagnostics""" + __tablename__ = "instance_diagnostics" + id = Column(Integer, primary_key=True) + instance_id = Column(Integer, ForeignKey('instances.id')) + + vbd_xvda_read = Column(Float) + vbd_xvda_write = Column(Float) + vbd_xvdb_read = Column(Float) + vbd_xvdb_write = Column(Float) + memory = Column(Float) + memory_internal_free = Column(Float) + cpu0 = Column(Float) + vif_0_tx = Column(Float) + vif_0_rx = Column(Float) + + class Volume(BASE, NovaBase): """Represents a block storage device that can be attached to a vm.""" __tablename__ = 'volumes' @@ -526,8 +543,8 @@ def register_models(): it will never need to be called explicitly elsewhere. """ from sqlalchemy import create_engine - models = (Service, Instance, Volume, ExportDevice, IscsiTarget, FixedIp, - FloatingIp, Network, SecurityGroup, + models = (Service, Instance, InstanceDiagnostics, Volume, ExportDevice, + IscsiTarget, FixedIp, FloatingIp, Network, SecurityGroup, SecurityGroupIngressRule, SecurityGroupInstanceAssociation, AuthToken, User, Project) # , Image, Host engine = create_engine(FLAGS.sql_connection, echo=False) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 2f5d78e759..659559c31a 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -228,11 +228,7 @@ class VMHelper(): 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"]} + diags = {} xml = get_rrd(host_ip, record["uuid"]) if xml: rrd = minidom.parseString(xml) From a6f90bacda223add276698958b2e7479bb6841e9 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 16 Dec 2010 23:25:21 +0000 Subject: [PATCH 166/229] make sure all network data is recreated when nova-network is rebooted --- nova/network/manager.py | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index 6a30f30b79..815fbfdc29 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -112,6 +112,16 @@ class NetworkManager(manager.Manager): ctxt = context.get_admin_context() for network in self.db.host_get_networks(ctxt, self.host): self._on_set_network_host(ctxt, network['id']) + floating_ips = self.db.floating_ip_get_all_by_host(ctxt, + self.host) + for floating_ip in floating_ips: + if floating_ip.get('fixed_ip', None): + fixed_address = floating_ip['fixed_ip']['address'] + # NOTE(vish): The False here is because we ignore the case + # that the ip is already bound. + self.driver.bind_floating_ip(floating_ip['address'], False) + self.driver.ensure_floating_forward(floating_ip['address'], + fixed_address) def set_network_host(self, context, network_id): """Safely sets the host of the network.""" @@ -444,12 +454,7 @@ class VlanManager(NetworkManager): def setup_fixed_ip(self, context, address): """Sets forwarding rules and dhcp for fixed ip.""" - fixed_ip_ref = self.db.fixed_ip_get_by_address(context, address) network_ref = self.db.fixed_ip_get_network(context, address) - if self.db.instance_is_vpn(context, fixed_ip_ref['instance_id']): - self.driver.ensure_vlan_forward(network_ref['vpn_public_address'], - network_ref['vpn_public_port'], - network_ref['vpn_private_address']) self.driver.update_dhcp(context, network_ref['id']) def setup_compute_network(self, context, instance_id): @@ -497,13 +502,24 @@ class VlanManager(NetworkManager): def _on_set_network_host(self, context, network_id): """Called when this host becomes the host for a network.""" network_ref = self.db.network_get(context, network_id) - net = {} - net['vpn_public_address'] = FLAGS.vpn_ip - db.network_update(context, network_id, net) + if not network_ref['vpn_public_address']: + net = {} + address = FLAGS.vpn_ip + net['vpn_public_address'] = address + db.network_update(context, network_id, net) + else: + address = network_ref['vpn_public_address'] self.driver.ensure_vlan_bridge(network_ref['vlan'], network_ref['bridge'], network_ref) - self.driver.update_dhcp(context, network_id) + # NOTE(vish): only ensure this forward if the address hasn't been set + # manually. + if address == FLAGS.vpn_ip: + self.driver.ensure_vlan_forward(FLAGS.vpn_ip, + network_ref['vpn_public_port'], + network_ref['vpn_private_address']) + if not FLAGS.fake_network: + self.driver.update_dhcp(context, network_id) @property def _bottom_reserved_ips(self): From 0d705117a0d0c04d845c5d146455cd11ba9af88c Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 16 Dec 2010 23:47:14 +0000 Subject: [PATCH 167/229] add conditional bind to linux net --- nova/network/linux_net.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 0fefd94156..40fe7619b7 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -77,10 +77,11 @@ def init_host(): {'range': FLAGS.fixed_range}) -def bind_floating_ip(floating_ip): +def bind_floating_ip(floating_ip, check_exit_code=True): """Bind ip to public interface""" _execute("sudo ip addr add %s dev %s" % (floating_ip, - FLAGS.public_interface)) + FLAGS.public_interface), + check_exit_code=check_exit_code) def unbind_floating_ip(floating_ip): From 86f71493fa5a02762bc7c56308c85b9182913efb Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 17 Dec 2010 00:43:18 +0000 Subject: [PATCH 168/229] move some flags around --- nova/api/__init__.py | 2 +- nova/api/ec2/cloud.py | 16 ++++++++++------ nova/flags.py | 12 +++++++----- nova/network/linux_net.py | 5 ++--- nova/utils.py | 5 ----- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/nova/api/__init__.py b/nova/api/__init__.py index 95fc6b1452..8034705700 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -29,7 +29,6 @@ import routes import webob.dec from nova import flags -from nova import utils from nova import wsgi from nova.api import ec2 from nova.api import openstack @@ -40,6 +39,7 @@ flags.DEFINE_string('osapi_subdomain', 'api', 'subdomain running the OpenStack API') flags.DEFINE_string('ec2api_subdomain', 'ec2', 'subdomain running the EC2 API') + FLAGS = flags.FLAGS diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index ebb13aedc5..684e29ee17 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -196,15 +196,19 @@ class CloudController(object): if FLAGS.region_list: regions = [] for region in FLAGS.region_list: - name, _sep, url = region.partition('=') + name, _sep, host = region.partition('=') + endpoint = '%s://%s:%s%s' % (FLAGS.ec2_prefix, + host, + FLAGS.cc_port, + FLAGS.ec2_suffix) regions.append({'regionName': name, - 'regionEndpoint': url}) + 'regionEndpoint': endpoint}) else: regions = [{'regionName': 'nova', - 'regionEndpoint': FLAGS.ec2_url}] - if region_name: - regions = [r for r in regions if r['regionName'] in region_name] - return {'regionInfo': regions} + 'regionEndpoint': '%s://%s:%s%s' % (FLAGS.ec2_prefix, + FLAGS.cc_host, + FLAGS.cc_port, + FLAGS.ec2_suffix)}] def describe_snapshots(self, context, diff --git a/nova/flags.py b/nova/flags.py index 9e99ffb5ec..95bfd37734 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -29,6 +29,7 @@ import sys import gflags +from nova import utils class FlagValues(gflags.FlagValues): """Extension of gflags.FlagValues that allows undefined and runtime flags. @@ -211,8 +212,8 @@ DEFINE_string('connection_type', 'libvirt', 'libvirt, xenapi or fake') DEFINE_string('aws_access_key_id', 'admin', 'AWS Access ID') DEFINE_string('aws_secret_access_key', 'admin', 'AWS Access Key') DEFINE_integer('s3_port', 3333, 's3 port') -DEFINE_string('s3_host', '127.0.0.1', 's3 host (for infrastructure)') -DEFINE_string('s3_dmz', '127.0.0.1', 's3 dmz ip (for instances)') +DEFINE_string('s3_host', utils.get_my_ip(), 's3 host (for infrastructure)') +DEFINE_string('s3_dmz', utils.get_my_ip(), 's3 dmz ip (for instances)') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') @@ -231,10 +232,11 @@ DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host') DEFINE_integer('rabbit_retry_interval', 10, 'rabbit connection retry interval') DEFINE_integer('rabbit_max_retries', 12, 'rabbit connection attempts') DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') -DEFINE_string('cc_dmz', '127.0.0.1', 'ip of api server (for instances)') +DEFINE_string('ec2_prefix', 'http', 'prefix for ec2') +DEFINE_string('cc_host', utils.get_my_ip(), 'ip of api server') +DEFINE_string('cc_dmz', utils.get_my_ip(), 'internal ip of api server') DEFINE_integer('cc_port', 8773, 'cloud controller port') -DEFINE_string('ec2_url', 'http://127.0.0.1:8773/services/Cloud', - 'Url to ec2 api server') +DEFINE_string('ec2_suffix', '/services/Cloud', 'suffix for ec2') DEFINE_string('default_image', 'ami-11111', 'default image to use, testing only') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index cb9f01a36a..8c7c528b50 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -46,8 +46,7 @@ flags.DEFINE_string('vlan_interface', 'eth0', 'network device for vlans') flags.DEFINE_string('dhcpbridge', _bin_file('nova-dhcpbridge'), 'location of nova-dhcpbridge') -flags.DEFINE_string('cc_host', utils.get_my_ip(), 'ip of api server') -flags.DEFINE_string('routing_source_ip', '127.0.0.1', +flags.DEFINE_string('routing_source_ip', utils.get_my_ip(), 'Public IP of network host') flags.DEFINE_bool('use_nova_chains', False, 'use the nova_ routing chains instead of default') @@ -59,7 +58,7 @@ def metadata_forward(): """Create forwarding rule for metadata""" _confirm_rule("PREROUTING", "-t nat -s 0.0.0.0/0 " "-d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j DNAT " - "--to-destination %s:%s" % (FLAGS.cc_host, FLAGS.cc_port)) + "--to-destination %s:%s" % (FLAGS.cc_dmz, FLAGS.cc_port)) def init_host(): diff --git a/nova/utils.py b/nova/utils.py index 11160c118a..082a42acf9 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -21,7 +21,6 @@ System-level utilities and helper functions. """ import datetime -import functools import inspect import logging import os @@ -36,11 +35,9 @@ from eventlet import event from eventlet import greenthread from nova import exception -from nova import flags from nova.exception import ProcessExecutionError -FLAGS = flags.FLAGS TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" @@ -197,8 +194,6 @@ def last_octet(address): def get_my_ip(): """Returns the actual ip of the local machine.""" - if getattr(FLAGS, 'fake_tests', None): - return '127.0.0.1' try: csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) csock.connect(('8.8.8.8', 80)) From 9e28957c45c69bf11a414faeb16a068f10a6a73d Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 17 Dec 2010 00:44:08 +0000 Subject: [PATCH 169/229] clean up use of iptables chains --- nova/network/linux_net.py | 104 ++++++++++++++++++++++++++++---------- 1 file changed, 78 insertions(+), 26 deletions(-) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 0fefd94156..8aca1c80ad 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -19,7 +19,6 @@ Implements vlans, bridges, and iptables rules using linux utilities. import logging import os -import signal # TODO(ja): does the definition of network_path belong here? @@ -48,39 +47,88 @@ flags.DEFINE_string('dhcpbridge', _bin_file('nova-dhcpbridge'), 'location of nova-dhcpbridge') flags.DEFINE_string('cc_host', utils.get_my_ip(), 'ip of api server') flags.DEFINE_integer('cc_port', 8773, 'cloud controller port') -flags.DEFINE_string('routing_source_ip', '127.0.0.1', +flags.DEFINE_string('routing_source_ip', utils.get_my_ip(), 'Public IP of network host') flags.DEFINE_bool('use_nova_chains', False, 'use the nova_ routing chains instead of default') - -DEFAULT_PORTS = [("tcp", 80), ("tcp", 22), ("udp", 1194), ("tcp", 443)] +flags.DEFINE_string('dns_server', None, + 'if set, uses specific dns server for dnsmasq') def metadata_forward(): """Create forwarding rule for metadata""" _confirm_rule("PREROUTING", "-t nat -s 0.0.0.0/0 " "-d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j DNAT " - "--to-destination %s:%s" % (FLAGS.cc_host, FLAGS.cc_port)) + "--to-destination %s:%s" % (FLAGS.cc_dmz, FLAGS.cc_port)) def init_host(): """Basic networking setup goes here""" + + if FLAGS.use_nova_chains: + _execute("sudo iptables -N nova_input", check_exit_code=False) + _execute("sudo iptables -D %s -j nova_input" % FLAGS.input_chain, + check_exit_code=False) + _execute("sudo iptables -A %s -j nova_input" % FLAGS.input_chain) + + _execute("sudo iptables -N nova_forward", check_exit_code=False) + _execute("sudo iptables -D FORWARD -j nova_forward", + check_exit_code=False) + _execute("sudo iptables -A FORWARD -j nova_forward") + + _execute("sudo iptables -N nova_output", check_exit_code=False) + _execute("sudo iptables -D OUTPUT -j nova_output", + check_exit_code=False) + _execute("sudo iptables -A OUTPUT -j nova_output") + + _execute("sudo iptables -t nat -N nova_prerouting", + check_exit_code=False) + _execute("sudo iptables -t nat -D PREROUTING -j nova_prerouting", + check_exit_code=False) + _execute("sudo iptables -t nat -A PREROUTING -j nova_prerouting") + + _execute("sudo iptables -t nat -N nova_postrouting", + check_exit_code=False) + _execute("sudo iptables -t nat -D POSTROUTING -j nova_postrouting", + check_exit_code=False) + _execute("sudo iptables -t nat -A POSTROUTING -j nova_postrouting") + + _execute("sudo iptables -t nat -N nova_snatting", + check_exit_code=False) + _execute("sudo iptables -t nat -D POSTROUTING -j nova_snatting", + check_exit_code=False) + _execute("sudo iptables -t nat -A POSTROUTING -j nova_snatting") + + _execute("sudo iptables -t nat -N nova_output", check_exit_code=False) + _execute("sudo iptables -t nat -D OUTPUT -j nova_output", + check_exit_code=False) + _execute("sudo iptables -t nat -A OUTPUT -j nova_output") + else: + # NOTE(vish): This makes it easy to ensure snatting rules always + # come after the accept rules in the postrouting chain + _execute("sudo iptables -t nat -N SNATTING", + check_exit_code=False) + _execute("sudo iptables -t nat -D POSTROUTING -j SNATTING", + check_exit_code=False) + _execute("sudo iptables -t nat -A POSTROUTING -j SNATTING") + # NOTE(devcamcar): Cloud public SNAT entries and the default # SNAT rule for outbound traffic. - _confirm_rule("POSTROUTING", "-t nat -s %s " + _confirm_rule("SNATTING", "-t nat -s %s " "-j SNAT --to-source %s" - % (FLAGS.fixed_range, FLAGS.routing_source_ip)) + % (FLAGS.fixed_range, FLAGS.routing_source_ip), append=True) - _confirm_rule("POSTROUTING", "-t nat -s %s -j MASQUERADE" % - FLAGS.fixed_range) + _confirm_rule("POSTROUTING", "-t nat -s %s -d %s -j ACCEPT" % + (FLAGS.fixed_range, FLAGS.dmz_cidr)) _confirm_rule("POSTROUTING", "-t nat -s %(range)s -d %(range)s -j ACCEPT" % {'range': FLAGS.fixed_range}) -def bind_floating_ip(floating_ip): +def bind_floating_ip(floating_ip, check_exit_code=True): """Bind ip to public interface""" _execute("sudo ip addr add %s dev %s" % (floating_ip, - FLAGS.public_interface)) + FLAGS.public_interface), + check_exit_code=check_exit_code) def unbind_floating_ip(floating_ip): @@ -102,27 +150,16 @@ def ensure_floating_forward(floating_ip, fixed_ip): """Ensure floating ip forwarding rule""" _confirm_rule("PREROUTING", "-t nat -d %s -j DNAT --to %s" % (floating_ip, fixed_ip)) - _confirm_rule("POSTROUTING", "-t nat -s %s -j SNAT --to %s" + _confirm_rule("SNATTING", "-t nat -s %s -j SNAT --to %s" % (fixed_ip, floating_ip)) - # TODO(joshua): Get these from the secgroup datastore entries - _confirm_rule("FORWARD", "-d %s -p icmp -j ACCEPT" - % (fixed_ip)) - for (protocol, port) in DEFAULT_PORTS: - _confirm_rule("FORWARD", "-d %s -p %s --dport %s -j ACCEPT" - % (fixed_ip, protocol, port)) def remove_floating_forward(floating_ip, fixed_ip): """Remove forwarding for floating ip""" _remove_rule("PREROUTING", "-t nat -d %s -j DNAT --to %s" % (floating_ip, fixed_ip)) - _remove_rule("POSTROUTING", "-t nat -s %s -j SNAT --to %s" + _remove_rule("SNATTING", "-t nat -s %s -j SNAT --to %s" % (fixed_ip, floating_ip)) - _remove_rule("FORWARD", "-d %s -p icmp -j ACCEPT" - % (fixed_ip)) - for (protocol, port) in DEFAULT_PORTS: - _remove_rule("FORWARD", "-d %s -p %s --dport %s -j ACCEPT" - % (fixed_ip, protocol, port)) def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): @@ -160,6 +197,15 @@ def ensure_bridge(bridge, interface, net_attrs=None): net_attrs['netmask'])) else: _execute("sudo ifconfig %s up" % bridge) + if FLAGS.use_nova_chains: + (out, err) = _execute("sudo iptables -N nova_forward", + check_exit_code=False) + if err != 'iptables: Chain already exists.\n': + # NOTE(vish): chain didn't exist link chain + _execute("sudo iptables -D FORWARD -j nova_forward", + check_exit_code=False) + _execute("sudo iptables -A FORWARD -j nova_forward") + _confirm_rule("FORWARD", "--in-interface %s -j ACCEPT" % bridge) _confirm_rule("FORWARD", "--out-interface %s -j ACCEPT" % bridge) @@ -236,13 +282,17 @@ def _device_exists(device): return not err -def _confirm_rule(chain, cmd): +def _confirm_rule(chain, cmd, append=False): """Delete and re-add iptables rule""" if FLAGS.use_nova_chains: chain = "nova_%s" % chain.lower() + if append: + loc = "-A" + else: + loc = "-I" _execute("sudo iptables --delete %s %s" % (chain, cmd), check_exit_code=False) - _execute("sudo iptables -I %s %s" % (chain, cmd)) + _execute("sudo iptables %s %s %s" % (loc, chain, cmd)) def _remove_rule(chain, cmd): @@ -265,6 +315,8 @@ def _dnsmasq_cmd(net): ' --dhcp-hostsfile=%s' % _dhcp_file(net['bridge'], 'conf'), ' --dhcp-script=%s' % FLAGS.dhcpbridge, ' --leasefile-ro'] + if FLAGS.dns_server: + cmd.append(' -h -R --server=%s' % FLAGS.dns_server) return ''.join(cmd) From baf0b1db2d4997f0e47277763e8ab393c131b8c8 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 17 Dec 2010 00:52:17 +0000 Subject: [PATCH 170/229] pep8 --- nova/flags.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/flags.py b/nova/flags.py index 95bfd37734..74badf6fd4 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -31,6 +31,7 @@ import gflags from nova import utils + class FlagValues(gflags.FlagValues): """Extension of gflags.FlagValues that allows undefined and runtime flags. From 8c343e1b4b92aa7b1062acebe8eaea402bc6ab4a Mon Sep 17 00:00:00 2001 From: Eric Day Date: Thu, 16 Dec 2010 17:05:54 -0800 Subject: [PATCH 171/229] First pass at converting run_tests.py to nosetests. The network and objctstore tests don't yet work. Also, we need to manually remove the sqlite file between runs. --- nova/test.py | 3 + nova/tests/api/__init__.py | 81 ------------ nova/tests/api/test.py | 81 ++++++++++++ nova/tests/api_integration.py | 54 -------- .../{access_unittest.py => test_access.py} | 0 nova/tests/{api_unittest.py => test_api.py} | 0 nova/tests/{auth_unittest.py => test_auth.py} | 0 .../{cloud_unittest.py => test_cloud.py} | 0 .../{compute_unittest.py => test_compute.py} | 0 .../{flags_unittest.py => test_flags.py} | 0 nova/tests/{misc_unittest.py => test_misc.py} | 8 +- .../{quota_unittest.py => test_quota.py} | 0 nova/tests/{rpc_unittest.py => test_rpc.py} | 0 ...cheduler_unittest.py => test_scheduler.py} | 2 +- .../{service_unittest.py => test_service.py} | 14 +- .../{twistd_unittest.py => test_twistd.py} | 0 nova/tests/{virt_unittest.py => test_virt.py} | 0 .../{volume_unittest.py => test_volume.py} | 0 run_tests.py | 125 ------------------ 19 files changed, 96 insertions(+), 272 deletions(-) create mode 100644 nova/tests/api/test.py delete mode 100644 nova/tests/api_integration.py rename nova/tests/{access_unittest.py => test_access.py} (100%) rename nova/tests/{api_unittest.py => test_api.py} (100%) rename nova/tests/{auth_unittest.py => test_auth.py} (100%) rename nova/tests/{cloud_unittest.py => test_cloud.py} (100%) rename nova/tests/{compute_unittest.py => test_compute.py} (100%) rename nova/tests/{flags_unittest.py => test_flags.py} (100%) rename nova/tests/{misc_unittest.py => test_misc.py} (89%) rename nova/tests/{quota_unittest.py => test_quota.py} (100%) rename nova/tests/{rpc_unittest.py => test_rpc.py} (100%) rename nova/tests/{scheduler_unittest.py => test_scheduler.py} (99%) rename nova/tests/{service_unittest.py => test_service.py} (93%) rename nova/tests/{twistd_unittest.py => test_twistd.py} (100%) rename nova/tests/{virt_unittest.py => test_virt.py} (100%) rename nova/tests/{volume_unittest.py => test_volume.py} (100%) delete mode 100644 run_tests.py diff --git a/nova/test.py b/nova/test.py index 7076f1bf4e..db5826c046 100644 --- a/nova/test.py +++ b/nova/test.py @@ -38,9 +38,12 @@ from nova import fakerabbit from nova import flags from nova import rpc from nova.network import manager as network_manager +from nova.tests import fake_flags FLAGS = flags.FLAGS +flags.DEFINE_bool('flush_db', True, + 'Flush the database before running fake tests') flags.DEFINE_bool('fake_tests', True, 'should we use everything for testing') diff --git a/nova/tests/api/__init__.py b/nova/tests/api/__init__.py index 9caa8c9d0d..e69de29bb2 100644 --- a/nova/tests/api/__init__.py +++ b/nova/tests/api/__init__.py @@ -1,81 +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. - -""" -Test for the root WSGI middleware for all API controllers. -""" - -import unittest - -import stubout -import webob -import webob.dec - -import nova.exception -from nova import api -from nova.tests.api.fakes import APIStub - - -class Test(unittest.TestCase): - - def setUp(self): - self.stubs = stubout.StubOutForTesting() - - def tearDown(self): - self.stubs.UnsetAll() - - def _request(self, url, subdomain, **kwargs): - environ_keys = {'HTTP_HOST': '%s.example.com' % subdomain} - environ_keys.update(kwargs) - req = webob.Request.blank(url, environ_keys) - return req.get_response(api.API('ec2')) - - def test_openstack(self): - self.stubs.Set(api.openstack, 'API', APIStub) - result = self._request('/v1.0/cloud', 'api') - self.assertEqual(result.body, "/cloud") - - def test_ec2(self): - self.stubs.Set(api.ec2, 'API', APIStub) - result = self._request('/services/cloud', 'ec2') - self.assertEqual(result.body, "/cloud") - - def test_not_found(self): - self.stubs.Set(api.ec2, 'API', APIStub) - self.stubs.Set(api.openstack, 'API', APIStub) - result = self._request('/test/cloud', 'ec2') - self.assertNotEqual(result.body, "/cloud") - - def test_query_api_versions(self): - result = self._request('/', 'api') - self.assertTrue('CURRENT' in result.body) - - def test_metadata(self): - def go(url): - result = self._request(url, 'ec2', REMOTE_ADDR='128.192.151.2') - # Each should get to the ORM layer and fail to find the IP - self.assertRaises(nova.exception.NotFound, go, '/latest/') - self.assertRaises(nova.exception.NotFound, go, '/2009-04-04/') - self.assertRaises(nova.exception.NotFound, go, '/1.0/') - - def test_ec2_root(self): - result = self._request('/', 'ec2') - self.assertTrue('2007-12-15\n' in result.body) - - -if __name__ == '__main__': - unittest.main() diff --git a/nova/tests/api/test.py b/nova/tests/api/test.py new file mode 100644 index 0000000000..9caa8c9d0d --- /dev/null +++ b/nova/tests/api/test.py @@ -0,0 +1,81 @@ +# 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. + +""" +Test for the root WSGI middleware for all API controllers. +""" + +import unittest + +import stubout +import webob +import webob.dec + +import nova.exception +from nova import api +from nova.tests.api.fakes import APIStub + + +class Test(unittest.TestCase): + + def setUp(self): + self.stubs = stubout.StubOutForTesting() + + def tearDown(self): + self.stubs.UnsetAll() + + def _request(self, url, subdomain, **kwargs): + environ_keys = {'HTTP_HOST': '%s.example.com' % subdomain} + environ_keys.update(kwargs) + req = webob.Request.blank(url, environ_keys) + return req.get_response(api.API('ec2')) + + def test_openstack(self): + self.stubs.Set(api.openstack, 'API', APIStub) + result = self._request('/v1.0/cloud', 'api') + self.assertEqual(result.body, "/cloud") + + def test_ec2(self): + self.stubs.Set(api.ec2, 'API', APIStub) + result = self._request('/services/cloud', 'ec2') + self.assertEqual(result.body, "/cloud") + + def test_not_found(self): + self.stubs.Set(api.ec2, 'API', APIStub) + self.stubs.Set(api.openstack, 'API', APIStub) + result = self._request('/test/cloud', 'ec2') + self.assertNotEqual(result.body, "/cloud") + + def test_query_api_versions(self): + result = self._request('/', 'api') + self.assertTrue('CURRENT' in result.body) + + def test_metadata(self): + def go(url): + result = self._request(url, 'ec2', REMOTE_ADDR='128.192.151.2') + # Each should get to the ORM layer and fail to find the IP + self.assertRaises(nova.exception.NotFound, go, '/latest/') + self.assertRaises(nova.exception.NotFound, go, '/2009-04-04/') + self.assertRaises(nova.exception.NotFound, go, '/1.0/') + + def test_ec2_root(self): + result = self._request('/', 'ec2') + self.assertTrue('2007-12-15\n' in result.body) + + +if __name__ == '__main__': + unittest.main() diff --git a/nova/tests/api_integration.py b/nova/tests/api_integration.py deleted file mode 100644 index 54403c6557..0000000000 --- a/nova/tests/api_integration.py +++ /dev/null @@ -1,54 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - - -import boto -from boto.ec2.regioninfo import RegionInfo -import unittest - - -ACCESS_KEY = 'fake' -SECRET_KEY = 'fake' -CLC_IP = '127.0.0.1' -CLC_PORT = 8773 -REGION = 'test' - - -def get_connection(): - return boto.connect_ec2( - aws_access_key_id=ACCESS_KEY, - aws_secret_access_key=SECRET_KEY, - is_secure=False, - region=RegionInfo(None, REGION, CLC_IP), - port=CLC_PORT, - path='/services/Cloud', - debug=99) - - -class APIIntegrationTests(unittest.TestCase): - def test_001_get_all_images(self): - conn = get_connection() - res = conn.get_all_images() - - -if __name__ == '__main__': - unittest.main() - -#print conn.get_all_key_pairs() -#print conn.create_key_pair -#print conn.create_security_group('name', 'description') diff --git a/nova/tests/access_unittest.py b/nova/tests/test_access.py similarity index 100% rename from nova/tests/access_unittest.py rename to nova/tests/test_access.py diff --git a/nova/tests/api_unittest.py b/nova/tests/test_api.py similarity index 100% rename from nova/tests/api_unittest.py rename to nova/tests/test_api.py diff --git a/nova/tests/auth_unittest.py b/nova/tests/test_auth.py similarity index 100% rename from nova/tests/auth_unittest.py rename to nova/tests/test_auth.py diff --git a/nova/tests/cloud_unittest.py b/nova/tests/test_cloud.py similarity index 100% rename from nova/tests/cloud_unittest.py rename to nova/tests/test_cloud.py diff --git a/nova/tests/compute_unittest.py b/nova/tests/test_compute.py similarity index 100% rename from nova/tests/compute_unittest.py rename to nova/tests/test_compute.py diff --git a/nova/tests/flags_unittest.py b/nova/tests/test_flags.py similarity index 100% rename from nova/tests/flags_unittest.py rename to nova/tests/test_flags.py diff --git a/nova/tests/misc_unittest.py b/nova/tests/test_misc.py similarity index 89% rename from nova/tests/misc_unittest.py rename to nova/tests/test_misc.py index 3d947427a3..33c1777d54 100644 --- a/nova/tests/misc_unittest.py +++ b/nova/tests/test_misc.py @@ -22,13 +22,13 @@ from nova.utils import parse_mailmap, str_dict_replace class ProjectTestCase(test.TestCase): def test_authors_up_to_date(self): - if os.path.exists('../.bzr'): + if os.path.exists('.bzr'): contributors = set() - mailmap = parse_mailmap('../.mailmap') + mailmap = parse_mailmap('.mailmap') import bzrlib.workingtree - tree = bzrlib.workingtree.WorkingTree.open('..') + tree = bzrlib.workingtree.WorkingTree.open('.') tree.lock_read() try: parents = tree.get_parent_ids() @@ -42,7 +42,7 @@ class ProjectTestCase(test.TestCase): email = author.split(' ')[-1] contributors.add(str_dict_replace(email, mailmap)) - authors_file = open('../Authors', 'r').read() + authors_file = open('Authors', 'r').read() missing = set() for contributor in contributors: diff --git a/nova/tests/quota_unittest.py b/nova/tests/test_quota.py similarity index 100% rename from nova/tests/quota_unittest.py rename to nova/tests/test_quota.py diff --git a/nova/tests/rpc_unittest.py b/nova/tests/test_rpc.py similarity index 100% rename from nova/tests/rpc_unittest.py rename to nova/tests/test_rpc.py diff --git a/nova/tests/scheduler_unittest.py b/nova/tests/test_scheduler.py similarity index 99% rename from nova/tests/scheduler_unittest.py rename to nova/tests/test_scheduler.py index d1756b8fb5..c56f696981 100644 --- a/nova/tests/scheduler_unittest.py +++ b/nova/tests/test_scheduler.py @@ -48,7 +48,7 @@ class SchedulerTestCase(test.TestCase): """Test case for scheduler""" def setUp(self): super(SchedulerTestCase, self).setUp() - self.flags(scheduler_driver='nova.tests.scheduler_unittest.TestDriver') + self.flags(scheduler_driver='nova.tests.test_scheduler.TestDriver') def test_fallback(self): scheduler = manager.SchedulerManager() diff --git a/nova/tests/service_unittest.py b/nova/tests/test_service.py similarity index 93% rename from nova/tests/service_unittest.py rename to nova/tests/test_service.py index 47c092f8e2..b30838ad73 100644 --- a/nova/tests/service_unittest.py +++ b/nova/tests/test_service.py @@ -30,7 +30,7 @@ from nova import service from nova import manager FLAGS = flags.FLAGS -flags.DEFINE_string("fake_manager", "nova.tests.service_unittest.FakeManager", +flags.DEFINE_string("fake_manager", "nova.tests.test_service.FakeManager", "Manager for testing") @@ -52,14 +52,14 @@ class ServiceManagerTestCase(test.TestCase): serv = service.Service('test', 'test', 'test', - 'nova.tests.service_unittest.FakeManager') + 'nova.tests.test_service.FakeManager') self.assertRaises(AttributeError, getattr, serv, 'test_method') def test_message_gets_to_manager(self): serv = service.Service('test', 'test', 'test', - 'nova.tests.service_unittest.FakeManager') + 'nova.tests.test_service.FakeManager') serv.start() self.assertEqual(serv.test_method(), 'manager') @@ -67,7 +67,7 @@ class ServiceManagerTestCase(test.TestCase): serv = ExtendedService('test', 'test', 'test', - 'nova.tests.service_unittest.FakeManager') + 'nova.tests.test_service.FakeManager') serv.start() self.assertEqual(serv.test_method(), 'service') @@ -156,7 +156,7 @@ class ServiceTestCase(test.TestCase): serv = service.Service(host, binary, topic, - 'nova.tests.service_unittest.FakeManager') + 'nova.tests.test_service.FakeManager') serv.start() serv.report_state() @@ -186,7 +186,7 @@ class ServiceTestCase(test.TestCase): serv = service.Service(host, binary, topic, - 'nova.tests.service_unittest.FakeManager') + 'nova.tests.test_service.FakeManager') serv.start() serv.report_state() self.assert_(serv.model_disconnected) @@ -219,7 +219,7 @@ class ServiceTestCase(test.TestCase): serv = service.Service(host, binary, topic, - 'nova.tests.service_unittest.FakeManager') + 'nova.tests.test_service.FakeManager') serv.start() serv.model_disconnected = True serv.report_state() diff --git a/nova/tests/twistd_unittest.py b/nova/tests/test_twistd.py similarity index 100% rename from nova/tests/twistd_unittest.py rename to nova/tests/test_twistd.py diff --git a/nova/tests/virt_unittest.py b/nova/tests/test_virt.py similarity index 100% rename from nova/tests/virt_unittest.py rename to nova/tests/test_virt.py diff --git a/nova/tests/volume_unittest.py b/nova/tests/test_volume.py similarity index 100% rename from nova/tests/volume_unittest.py rename to nova/tests/test_volume.py diff --git a/run_tests.py b/run_tests.py deleted file mode 100644 index 6a4b7f1aba..0000000000 --- a/run_tests.py +++ /dev/null @@ -1,125 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -This is our basic test running framework based on Twisted's Trial. - -Usage Examples: - - # to run all the tests - python run_tests.py - - # to run a specific test suite imported here - python run_tests.py NodeConnectionTestCase - - # to run a specific test imported here - python run_tests.py NodeConnectionTestCase.test_reboot - - # to run some test suites elsewhere - python run_tests.py nova.tests.node_unittest - python run_tests.py nova.tests.node_unittest.NodeConnectionTestCase - -Due to our use of multiprocessing it we frequently get some ignorable -'Interrupted system call' exceptions after test completion. - -""" - -import eventlet -eventlet.monkey_patch() - -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 -from nova import twistd - -from nova.tests.access_unittest import * -from nova.tests.api_unittest import * -from nova.tests.auth_unittest import * -from nova.tests.cloud_unittest import * -from nova.tests.compute_unittest import * -from nova.tests.flags_unittest import * -from nova.tests.misc_unittest import * -from nova.tests.network_unittest import * -#from nova.tests.objectstore_unittest import * -from nova.tests.quota_unittest import * -from nova.tests.rpc_unittest import * -from nova.tests.scheduler_unittest import * -from nova.tests.service_unittest import * -from nova.tests.twistd_unittest import * -from nova.tests.virt_unittest import * -from nova.tests.volume_unittest import * - - -FLAGS = flags.FLAGS -flags.DEFINE_bool('flush_db', True, - 'Flush the database before running fake tests') -flags.DEFINE_string('tests_stderr', 'run_tests.err.log', - 'Path to where to pipe STDERR during test runs.' - ' Default = "run_tests.err.log"') - - -if __name__ == '__main__': - OptionsClass = twistd.WrapTwistedOptions(trial_script.Options) - config = OptionsClass() - argv = config.parseOptions() - - FLAGS.verbose = True - - # TODO(termie): these should make a call instead of doing work on import - if FLAGS.fake_tests: - from nova.tests.fake_flags import * - else: - from nova.tests.real_flags import * - - # Establish redirect for STDERR - sys.stderr.flush() - err = open(FLAGS.tests_stderr, 'w+', 0) - os.dup2(err.fileno(), sys.stderr.fileno()) - - if len(argv) == 1 and len(config['tests']) == 0: - # If no tests were specified run the ones imported in this file - # NOTE(termie): "tests" is not a flag, just some Trial related stuff - config['tests'].update(['__main__']) - elif len(config['tests']): - # If we specified tests check first whether they are in __main__ - for arg in config['tests']: - key = arg.split('.')[0] - if hasattr(__main__, key): - config['tests'].remove(arg) - config['tests'].add('__main__.%s' % arg) - - trial_script._initialDebugSetup(config) - trialRunner = trial_script._makeRunner(config) - suite = trial_script._getSuite(config) - if config['until-failure']: - test_result = trialRunner.runUntilFailure(suite) - else: - test_result = trialRunner.run(suite) - if config.tracer: - sys.settrace(None) - results = config.tracer.results() - results.write_results(show_missing=1, summary=False, - coverdir=config.coverdir) - sys.exit(not test_result.wasSuccessful()) From b1d4579404f9e49fcdea23c21733fdf65edc1da3 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Thu, 16 Dec 2010 17:29:26 -0800 Subject: [PATCH 172/229] Fixed network test (thanks Vish!) and fixed run_tests.sh. --- bin/nova-dhcpbridge | 1 - nova/tests/{network_unittest.py => test_network.py} | 0 run_tests.sh | 12 ++++++++---- 3 files changed, 8 insertions(+), 5 deletions(-) rename nova/tests/{network_unittest.py => test_network.py} (100%) diff --git a/bin/nova-dhcpbridge b/bin/nova-dhcpbridge index 81b9b6dd3f..828aba3d1b 100755 --- a/bin/nova-dhcpbridge +++ b/bin/nova-dhcpbridge @@ -110,7 +110,6 @@ def main(): FLAGS.num_networks = 5 path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', - '_trial_temp', 'nova.sqlite')) FLAGS.sql_connection = 'sqlite:///%s' % path action = argv[1] diff --git a/nova/tests/network_unittest.py b/nova/tests/test_network.py similarity index 100% rename from nova/tests/network_unittest.py rename to nova/tests/test_network.py diff --git a/run_tests.sh b/run_tests.sh index a11dcd7cc5..67214996db 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -36,7 +36,8 @@ done if [ $never_venv -eq 1 ]; then # Just run the test suites in current environment - python run_tests.py + rm -f nova.sqlite + nosetests -v exit fi @@ -47,7 +48,8 @@ if [ $force -eq 1 ]; then fi if [ -e ${venv} ]; then - ${with_venv} python run_tests.py $@ + ${with_venv} rm -f nova.sqlite + ${with_venv} nosetests -v $@ else if [ $always_venv -eq 1 ]; then # Automatically install the virtualenv @@ -59,9 +61,11 @@ else # Install the virtualenv and run the test suite in it python tools/install_venv.py else - python run_tests.py + rm -f nova.sqlite + nosetests -v exit fi fi - ${with_venv} python run_tests.py $@ + ${with_venv} rm -f nova.sqlite + ${with_venv} nosetests -v $@ fi From d16a41f552c70708a909067ce8555b40c3785027 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Fri, 17 Dec 2010 11:07:59 -0600 Subject: [PATCH 173/229] Replaced redis with a modified dict class --- nova/auth/fakeldap.py | 102 +++++++++++++++++++++++------------- nova/auth/manager.py | 10 +++- nova/tests/auth_unittest.py | 5 +- 3 files changed, 77 insertions(+), 40 deletions(-) diff --git a/nova/auth/fakeldap.py b/nova/auth/fakeldap.py index 46e0135b4c..e46bb91ab5 100644 --- a/nova/auth/fakeldap.py +++ b/nova/auth/fakeldap.py @@ -15,7 +15,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -"""Fake LDAP server for test harness, backs to ReDIS. +"""Fake LDAP server for test harness. This class does very little error checking, and knows nothing about ldap class definitions. It implements the minimum emulation of the python ldap @@ -23,20 +23,11 @@ library to work with nova. """ +import fnmatch import json -import redis - -from nova import flags - -FLAGS = flags.FLAGS -flags.DEFINE_string('redis_host', '127.0.0.1', - 'Host that redis is running on.') -flags.DEFINE_integer('redis_port', 6379, - 'Port that redis is running on.') -flags.DEFINE_integer('redis_db', 0, 'Multiple DB keeps tests away') -class Redis(object): +class Store(object): def __init__(self): if hasattr(self.__class__, '_instance'): raise Exception('Attempted to instantiate singleton') @@ -44,13 +35,55 @@ class Redis(object): @classmethod def instance(cls): if not hasattr(cls, '_instance'): - inst = redis.Redis(host=FLAGS.redis_host, - port=FLAGS.redis_port, - db=FLAGS.redis_db) - cls._instance = inst + cls._instance = _StorageDict() return cls._instance +class _StorageDict(dict): + def keys(self, pat=None): + ret = super(_StorageDict, self).keys() + if pat is not None: + ret = fnmatch.filter(ret, pat) + return ret + + def delete(self, key): + try: + del self[key] + except KeyError: + pass + + def flushdb(self): + self.clear() + + def hgetall(self, key): + """Returns the hash for the given key; creates + the hash if the key doesn't exist.""" + try: + return self[key] + except KeyError: + self[key] = {} + return self[key] + + def hget(self, key, field): + hashdict = self.hgetall(key) + try: + return hashdict[field] + except KeyError: + hashdict[field] = {} + return hashdict[field] + + def hset(self, key, field, val): + hashdict = self.hgetall(key) + hashdict[field] = val + + def hmset(self, key, value_dict): + hashdict = self.hgetall(key) + for field, val in value_dict.items(): + hashdict[field] = val + + + + SCOPE_BASE = 0 SCOPE_ONELEVEL = 1 # Not implemented SCOPE_SUBTREE = 2 @@ -169,8 +202,6 @@ def _to_json(unencoded): class FakeLDAP(object): - #TODO(vish): refactor this class to use a wrapper instead of accessing - # redis directly """Fake LDAP connection.""" def simple_bind_s(self, dn, password): @@ -183,14 +214,14 @@ class FakeLDAP(object): def add_s(self, dn, attr): """Add an object with the specified attributes at dn.""" - key = "%s%s" % (self.__redis_prefix, dn) - + key = "%s%s" % (self.__prefix, dn) value_dict = dict([(k, _to_json(v)) for k, v in attr]) - Redis.instance().hmset(key, value_dict) + Store.instance().hmset(key, value_dict) + def delete_s(self, dn): """Remove the ldap object at specified dn.""" - Redis.instance().delete("%s%s" % (self.__redis_prefix, dn)) + Store.instance().delete("%s%s" % (self.__prefix, dn)) def modify_s(self, dn, attrs): """Modify the object at dn using the attribute list. @@ -201,18 +232,18 @@ class FakeLDAP(object): ([MOD_ADD | MOD_DELETE | MOD_REPACE], attribute, value) """ - redis = Redis.instance() - key = "%s%s" % (self.__redis_prefix, dn) + store = Store.instance() + key = "%s%s" % (self.__prefix, dn) for cmd, k, v in attrs: - values = _from_json(redis.hget(key, k)) + values = _from_json(store.hget(key, k)) if cmd == MOD_ADD: values.append(v) elif cmd == MOD_REPLACE: values = [v] else: values.remove(v) - values = redis.hset(key, k, _to_json(values)) + values = store.hset(key, k, _to_json(values)) def search_s(self, dn, scope, query=None, fields=None): """Search for all matching objects under dn using the query. @@ -226,16 +257,17 @@ class FakeLDAP(object): """ if scope != SCOPE_BASE and scope != SCOPE_SUBTREE: raise NotImplementedError(str(scope)) - redis = Redis.instance() + store = Store.instance() if scope == SCOPE_BASE: - keys = ["%s%s" % (self.__redis_prefix, dn)] + keys = ["%s%s" % (self.__prefix, dn)] else: - keys = redis.keys("%s*%s" % (self.__redis_prefix, dn)) + keys = store.keys("%s*%s" % (self.__prefix, dn)) + objects = [] for key in keys: - # get the attributes from redis - attrs = redis.hgetall(key) - # turn the values from redis into lists + # get the attributes from the store + attrs = store.hgetall(key) + # turn the values from the store into lists # pylint: disable-msg=E1103 attrs = dict([(k, _from_json(v)) for k, v in attrs.iteritems()]) @@ -244,13 +276,13 @@ class FakeLDAP(object): # filter the attributes by fields attrs = dict([(k, v) for k, v in attrs.iteritems() if not fields or k in fields]) - objects.append((key[len(self.__redis_prefix):], attrs)) + objects.append((key[len(self.__prefix):], attrs)) # pylint: enable-msg=E1103 if objects == []: raise NO_SUCH_OBJECT() return objects @property - def __redis_prefix(self): # pylint: disable-msg=R0201 - """Get the prefix to use for all redis keys.""" + def __prefix(self): # pylint: disable-msg=R0201 + """Get the prefix to use for all keys.""" return 'ldap:' diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 11c3bd6dfb..5a7020a938 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -478,10 +478,13 @@ class AuthManager(object): if member_users: member_users = [User.safe_id(u) for u in member_users] with self.driver() as drv: - project_dict = drv.create_project(name, + try: + project_dict = drv.create_project(name, User.safe_id(manager_user), description, member_users) + except: + project_dict = drv.get_project(name) if project_dict: project = Project(**project_dict) return project @@ -604,7 +607,10 @@ class AuthManager(object): if secret == None: secret = str(uuid.uuid4()) with self.driver() as drv: - user_dict = drv.create_user(name, access, secret, admin) + try: + user_dict = drv.create_user(name, access, secret, admin) + except: + user_dict = drv.get_user(name) if user_dict: return User(**user_dict) diff --git a/nova/tests/auth_unittest.py b/nova/tests/auth_unittest.py index 4508d67216..32cb2c5428 100644 --- a/nova/tests/auth_unittest.py +++ b/nova/tests/auth_unittest.py @@ -333,11 +333,10 @@ class AuthManagerLdapTestCase(AuthManagerTestCase, test.TestCase): AuthManagerTestCase.__init__(self) test.TestCase.__init__(self, *args, **kwargs) import nova.auth.fakeldap as fakeldap - FLAGS.redis_db = 8 if FLAGS.flush_db: - logging.info("Flushing redis datastore") + logging.info("Flushing datastore") try: - r = fakeldap.Redis.instance() + r = fakeldap.Store.instance() r.flushdb() except: self.skip = True From 02d2e305bd71aec3f723a42da620d2939e041f0c Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Fri, 17 Dec 2010 11:14:32 -0600 Subject: [PATCH 174/229] Fixed some old code that was merged incorrectly --- nova/auth/manager.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 5a7020a938..11c3bd6dfb 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -478,13 +478,10 @@ class AuthManager(object): if member_users: member_users = [User.safe_id(u) for u in member_users] with self.driver() as drv: - try: - project_dict = drv.create_project(name, + project_dict = drv.create_project(name, User.safe_id(manager_user), description, member_users) - except: - project_dict = drv.get_project(name) if project_dict: project = Project(**project_dict) return project @@ -607,10 +604,7 @@ class AuthManager(object): if secret == None: secret = str(uuid.uuid4()) with self.driver() as drv: - try: - user_dict = drv.create_user(name, access, secret, admin) - except: - user_dict = drv.get_user(name) + user_dict = drv.create_user(name, access, secret, admin) if user_dict: return User(**user_dict) From 56a0dfde6e7d598df15bdce2541cd60c7757f557 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Fri, 17 Dec 2010 11:24:06 -0600 Subject: [PATCH 175/229] pep8 cleanup --- nova/auth/fakeldap.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/nova/auth/fakeldap.py b/nova/auth/fakeldap.py index e46bb91ab5..1ac579dbd1 100644 --- a/nova/auth/fakeldap.py +++ b/nova/auth/fakeldap.py @@ -82,8 +82,6 @@ class _StorageDict(dict): hashdict[field] = val - - SCOPE_BASE = 0 SCOPE_ONELEVEL = 1 # Not implemented SCOPE_SUBTREE = 2 @@ -218,7 +216,6 @@ class FakeLDAP(object): value_dict = dict([(k, _to_json(v)) for k, v in attr]) Store.instance().hmset(key, value_dict) - def delete_s(self, dn): """Remove the ldap object at specified dn.""" Store.instance().delete("%s%s" % (self.__prefix, dn)) From 6383f7f9f63e348a12adeff66a266ef796d98ded Mon Sep 17 00:00:00 2001 From: Cerberus Date: Fri, 17 Dec 2010 11:54:59 -0600 Subject: [PATCH 176/229] Some typo fixes --- nova/api/openstack/__init__.py | 4 ++-- nova/api/openstack/auth.py | 4 ++-- nova/api/openstack/ratelimiting/__init__.py | 4 ++-- nova/tests/api/openstack/__init__.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index e941694d96..e78080012e 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -85,7 +85,7 @@ class AuthMiddleware(wsgi.Middleware): @webob.dec.wsgify def __call__(self, req): - if not self.auth_driver.has_authentication(req) + if not self.auth_driver.has_authentication(req): return self.auth_driver.authenticate(req) user = self.auth_driver.get_user_by_authentication(req) @@ -108,7 +108,7 @@ class RateLimitingMiddleware(wsgi.Middleware): at the given host+port to keep rate counters. """ super(RateLimitingMiddleware, self).__init__(application) - self._limiting_driver = + self._limiting_driver = \ utils.import_class(FLAGS.os_api_ratelimiting)(service_host) @webob.dec.wsgify diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index da8ebcfcd6..26cb50dca2 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -28,10 +28,10 @@ class BasicApiAuthManager(object): super(BasicApiAuthManager, self).__init__() def has_authentication(self, req): - return 'X-Auth-Token' in req.headers: + return 'X-Auth-Token' in req.headers def get_user_by_authentication(self, req): - return self.auth_driver.authorize_token(req.headers["X-Auth-Token"]) + return self.authorize_token(req.headers["X-Auth-Token"]) def authenticate(self, req): # Unless the request is explicitly made against // don't diff --git a/nova/api/openstack/ratelimiting/__init__.py b/nova/api/openstack/ratelimiting/__init__.py index d1da9afa78..2dc5ec32ef 100644 --- a/nova/api/openstack/ratelimiting/__init__.py +++ b/nova/api/openstack/ratelimiting/__init__.py @@ -21,7 +21,7 @@ class BasicRateLimiting(object): if not service_host: #TODO(gundlach): These limits were based on limitations of Cloud #Servers. We should revisit them in Nova. - self.limiter = ratelimiting.Limiter(limits={ + self.limiter = Limiter(limits={ 'DELETE': (100, ratelimiting.PER_MINUTE), 'PUT': (10, ratelimiting.PER_MINUTE), 'POST': (10, ratelimiting.PER_MINUTE), @@ -29,7 +29,7 @@ class BasicRateLimiting(object): 'GET changes-since': (3, ratelimiting.PER_MINUTE), }) else: - self.limiter = ratelimiting.WSGIAppProxy(service_host) + self.limiter = WSGIAppProxy(service_host) def limited_request(self, req): """Rate limit the request. diff --git a/nova/tests/api/openstack/__init__.py b/nova/tests/api/openstack/__init__.py index 2e357febec..4e4dfe4fc9 100644 --- a/nova/tests/api/openstack/__init__.py +++ b/nova/tests/api/openstack/__init__.py @@ -17,7 +17,7 @@ import unittest -from nova.api.openstack import limited +from nova.api.openstack.common import limited from nova.api.openstack import RateLimitingMiddleware from nova.tests.api.fakes import APIStub from webob import Request From e2a41d30c55edeba92f0e59f84e8d9eb7e16ca62 Mon Sep 17 00:00:00 2001 From: Jonathan Bryce Date: Fri, 17 Dec 2010 15:25:44 -0600 Subject: [PATCH 177/229] Adding in Ed Leafe so we can land his remove-redis test branch --- Authors | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Authors b/Authors index 565444ee12..fa38ef0b1b 100644 --- a/Authors +++ b/Authors @@ -6,6 +6,7 @@ Chris Behrens Chmouel Boudjnah Dean Troyer Devin Carlen +Ed Leafe Eldar Nugaev Eric Day Ewan Mellor @@ -14,6 +15,7 @@ Jay Pipes Jesse Andrews Joe Heck Joel Moore +Jonathan Bryce Josh Kearney Joshua McKenty Justin Santa Barbara From 75d6de8a67db02f886636edfedcf3f3fc8cff9cc Mon Sep 17 00:00:00 2001 From: Cerberus Date: Fri, 17 Dec 2010 16:03:21 -0600 Subject: [PATCH 178/229] Some tweaks --- nova/db/sqlalchemy/api.py | 3 +++ nova/tests/api/openstack/fakes.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 55036d1d10..0614c14e8d 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1062,11 +1062,13 @@ def iscsi_target_create_safe(context, values): ################### +@require_admin_context def auth_destroy_token(_context, token): session = get_session() session.delete(token) +@require_admin_context def auth_get_token(_context, token_hash): session = get_session() tk = session.query(models.AuthToken).\ @@ -1077,6 +1079,7 @@ def auth_get_token(_context, token_hash): return tk +@require_admin_context def auth_create_token(_context, token): tk = models.AuthToken() tk.update(token) diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 21b8aac1c0..6c30761d67 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -173,7 +173,7 @@ class FakeToken(object): class FakeRequestContext(object): - def __init__(self, user, project): + def __init__(self, user, project, *args, **kwargs): self.user_id = 1 self.project_id = 1 From 7f5ec9caa5cad314c6fec598d6c230dfbc8f0eae Mon Sep 17 00:00:00 2001 From: Jonathan Bryce Date: Fri, 17 Dec 2010 16:29:55 -0600 Subject: [PATCH 179/229] Removing unneeded Trial specific code --- nova/tests/auth_unittest.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/nova/tests/auth_unittest.py b/nova/tests/auth_unittest.py index 32cb2c5428..61ae43fb10 100644 --- a/nova/tests/auth_unittest.py +++ b/nova/tests/auth_unittest.py @@ -335,11 +335,8 @@ class AuthManagerLdapTestCase(AuthManagerTestCase, test.TestCase): import nova.auth.fakeldap as fakeldap if FLAGS.flush_db: logging.info("Flushing datastore") - try: - r = fakeldap.Store.instance() - r.flushdb() - except: - self.skip = True + r = fakeldap.Store.instance() + r.flushdb() class AuthManagerDbTestCase(AuthManagerTestCase, test.TestCase): From 5b8362d0f56bdbeba7ee8292222863a501bad6af Mon Sep 17 00:00:00 2001 From: Cerberus Date: Fri, 17 Dec 2010 16:56:42 -0600 Subject: [PATCH 180/229] A few more tweaks to get the OS API tests passing --- nova/tests/api/openstack/fakes.py | 5 +++-- nova/tests/api/openstack/test_servers.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 6c30761d67..96689d2cd3 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -29,8 +29,9 @@ 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 +from nova.image import local +from nova.image import service from nova.tests import fake_flags from nova.wsgi import Router @@ -75,7 +76,7 @@ def stub_out_image_service(stubs): def fake_image_show(meh, context, id): return dict(kernelId=1, ramdiskId=1) - stubs.Set(nova.image.local.LocalImageService, 'show', fake_image_show) + stubs.Set(local.LocalImageService, 'show', fake_image_show) def stub_out_auth(stubs): diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 8444b6fce6..dcd2fe7668 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -27,6 +27,7 @@ import nova.api.openstack from nova.api.openstack import servers import nova.db.api from nova.db.sqlalchemy.models import Instance +import nova.image import nova.rpc from nova.tests.api.openstack import fakes From ca81b0c12a3853942e9ce85154c38dad381ead0e Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Sat, 18 Dec 2010 00:50:49 +0000 Subject: [PATCH 181/229] fixed unittests and further clean-up post-eventlet merge --- nova/tests/xenapi_unittest.py | 48 +++++++++++-------------------- nova/virt/xenapi/__init__.py | 2 +- nova/virt/xenapi/network_utils.py | 2 +- nova/virt/xenapi/vm_utils.py | 3 +- nova/virt/xenapi/vmops.py | 8 ++++-- nova/virt/xenapi/volume_utils.py | 2 +- nova/virt/xenapi/volumeops.py | 1 - nova/virt/xenapi_conn.py | 8 +++--- 8 files changed, 29 insertions(+), 45 deletions(-) diff --git a/nova/tests/xenapi_unittest.py b/nova/tests/xenapi_unittest.py index 839d6aa440..74401ce5df 100644 --- a/nova/tests/xenapi_unittest.py +++ b/nova/tests/xenapi_unittest.py @@ -78,13 +78,10 @@ class XenAPIVolumeTestCase(test.TrialTestCase): helper = volume_utils.VolumeHelper helper.XenAPI = session.get_imported_xenapi() vol = self._create_volume() - info = yield helper.parse_volume_info(vol['ec2_id'], '/dev/sdc') + info = helper.parse_volume_info(vol['ec2_id'], '/dev/sdc') label = 'SR-%s' % vol['ec2_id'] description = 'Test-SR' - sr_ref = helper.create_iscsi_storage_blocking(session, - info, - label, - description) + sr_ref = helper.create_iscsi_storage(session, info, label, description) srs = fake.get_all('SR') self.assertEqual(sr_ref, srs[0]) db.volume_destroy(context.get_admin_context(), vol['id']) @@ -97,13 +94,10 @@ class XenAPIVolumeTestCase(test.TrialTestCase): helper.XenAPI = session.get_imported_xenapi() vol = self._create_volume() # oops, wrong mount point! - info = helper.parse_volume_info(vol['ec2_id'], '/dev/sd') - - def check(exc): - """ handler """ - self.assertIsInstance(exc.value, volume_utils.StorageError) - - info.addErrback(check) + self.assertRaises(volume_utils.StorageError, + helper.parse_volume_info, + vol['ec2_id'], + '/dev/sd') db.volume_destroy(context.get_admin_context(), vol['id']) def test_attach_volume(self): @@ -116,8 +110,7 @@ class XenAPIVolumeTestCase(test.TrialTestCase): result = conn.attach_volume(instance.name, volume['ec2_id'], '/dev/sdc') - def check(_): - """ handler """ + def check(): # check that the VM has a VBD attached to it # Get XenAPI reference for the VM vms = fake.get_all('VM') @@ -127,8 +120,7 @@ class XenAPIVolumeTestCase(test.TrialTestCase): vm_ref = vbd['VM'] self.assertEqual(vm_ref, vms[0]) - result.addCallback(check) - return result + check() def test_attach_volume_raise_exception(self): """ This shows how to test when exceptions are raised """ @@ -138,17 +130,11 @@ class XenAPIVolumeTestCase(test.TrialTestCase): volume = self._create_volume() instance = db.instance_create(self.values) fake.create_vm(instance.name, 'Running') - result = conn.attach_volume(instance.name, volume['ec2_id'], - '/dev/sdc') - - def check_exception(exc): - """ handler """ - if exc: - pass - else: - self.fail('Oops, no exception has been raised!') - result.addErrback(check_exception) - return result + self.assertRaises(Exception, + conn.attach_volume, + instance.name, + volume['ec2_id'], + '/dev/sdc') def tearDown(self): super(XenAPIVolumeTestCase, self).tearDown() @@ -192,10 +178,9 @@ class XenAPIVMTestCase(test.TrialTestCase): } conn = xenapi_conn.get_connection(False) instance = db.instance_create(values) - result = conn.spawn(instance) + conn.spawn(instance) - def check(_): - """ handler """ + def check(): instances = conn.list_instances() self.assertEquals(instances, [1]) @@ -225,8 +210,7 @@ class XenAPIVMTestCase(test.TrialTestCase): # Check that the VM is running according to XenAPI. self.assertEquals(vm['power_state'], 'Running') - result.addCallback(check) - return result + check() def tearDown(self): super(XenAPIVMTestCase, self).tearDown() diff --git a/nova/virt/xenapi/__init__.py b/nova/virt/xenapi/__init__.py index c7038deae8..c75162f085 100644 --- a/nova/virt/xenapi/__init__.py +++ b/nova/virt/xenapi/__init__.py @@ -20,7 +20,7 @@ """ -class HelperBase(): +class HelperBase(object): """ The base for helper classes. This adds the XenAPI class attribute """ diff --git a/nova/virt/xenapi/network_utils.py b/nova/virt/xenapi/network_utils.py index c292383b60..e783120fe3 100644 --- a/nova/virt/xenapi/network_utils.py +++ b/nova/virt/xenapi/network_utils.py @@ -29,7 +29,7 @@ class NetworkHelper(HelperBase): The class that wraps the helper methods together. """ def __init__(self): - return + super(NetworkHelper, self).__init__() @classmethod def find_network_with_bridge(cls, session, bridge): diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 57d419773a..911fcc9b29 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -24,7 +24,6 @@ import urllib from xml.dom import minidom 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 @@ -48,7 +47,7 @@ class VMHelper(HelperBase): The class that wraps the helper methods together. """ def __init__(self): - return + super(VMHelper, self).__init__() @classmethod def create_vm(cls, session, instance, kernel, ramdisk): diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 0be4ed07d6..aa3a3ae536 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -22,7 +22,6 @@ import logging from nova import db from nova import context -from nova import flags from nova import exception from nova import utils @@ -78,6 +77,7 @@ class VMOps(object): logging.info('Spawning VM %s created %s.', instance.name, vm_ref) + # NOTE(armando): Do we really need to do this in virt? timer = utils.LoopingCall(f=None) def _wait_for_boot(): @@ -88,7 +88,8 @@ class VMOps(object): if state == power_state.RUNNING: logging.debug('Instance %s: booted', instance['name']) timer.stop() - except: + except Exception, exc: + logging.warn(exc) logging.exception('instance %s: failed to boot', instance['name']) db.instance_set_state(context.get_admin_context(), @@ -131,6 +132,7 @@ class VMOps(object): self._session.wait_for_task(task) except self.XenAPI.Failure, exc: logging.warn(exc) + # VM Destroy try: task = self._session.call_xenapi('Async.VM.destroy', vm) self._session.wait_for_task(task) @@ -149,7 +151,7 @@ class VMOps(object): """Return data about VM diagnostics""" vm = VMHelper.lookup(self._session, instance_id) if vm is None: - raise exception.Exception("Instance not found %s" % instance_id) + raise exception.NotFound("Instance not found %s" % instance_id) rec = self._session.get_xenapi().VM.get_record(vm) return VMHelper.compile_diagnostics(self._session, rec) diff --git a/nova/virt/xenapi/volume_utils.py b/nova/virt/xenapi/volume_utils.py index 255f23d88c..5366078cee 100644 --- a/nova/virt/xenapi/volume_utils.py +++ b/nova/virt/xenapi/volume_utils.py @@ -43,7 +43,7 @@ class VolumeHelper(HelperBase): The class that wraps the helper methods together. """ def __init__(self): - return + super(VolumeHelper, self).__init__() @classmethod def create_iscsi_storage(cls, session, info, label, description): diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xenapi/volumeops.py index 6c75160736..9dbb1bb147 100644 --- a/nova/virt/xenapi/volumeops.py +++ b/nova/virt/xenapi/volumeops.py @@ -19,7 +19,6 @@ Management class for Storage-related functions (attach, detach, etc). """ import logging -from nova import flags from nova.virt.xenapi.vm_utils import VMHelper from nova.virt.xenapi.volume_utils import VolumeHelper from nova.virt.xenapi.volume_utils import StorageError diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 2072227447..2a8614cfdf 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -119,11 +119,11 @@ class XenAPIConnection(object): def spawn(self, instance): """ Create VM instance """ - return self._vmops.spawn(instance) + self._vmops.spawn(instance) def reboot(self, instance): """ Reboot VM instance """ - return self._vmops.reboot(instance) + self._vmops.reboot(instance) def destroy(self, instance): """ Destroy VM instance """ @@ -143,13 +143,13 @@ class XenAPIConnection(object): def attach_volume(self, instance_name, device_path, mountpoint): """ Attach volume storage to VM instance """ - return self._volumeops.attach_volume(instance_name, + self._volumeops.attach_volume(instance_name, device_path, mountpoint) def detach_volume(self, instance_name, mountpoint): """ Detach volume storage to VM instance """ - return self._volumeops.detach_volume(instance_name, mountpoint) + self._volumeops.detach_volume(instance_name, mountpoint) class XenAPISession(object): From ca1017988f98a246aa82c16f471791c7ea3eceec Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sun, 19 Dec 2010 20:05:41 +0000 Subject: [PATCH 182/229] Support proxying api by using X-Forwarded-For --- nova/api/ec2/__init__.py | 8 +++++++- nova/api/ec2/metadatarequesthandler.py | 11 +++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index a6ee16c332..c455144a9e 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -37,6 +37,9 @@ from nova.auth import manager FLAGS = flags.FLAGS +flags.DEFINE_boolean('use_forwarded_for', False, + 'Treat X-Forwarded-For as the canonical remote address. ' + 'Only enable this if you have a sanitizing proxy.') _log = logging.getLogger("api") _log.setLevel(logging.DEBUG) @@ -81,9 +84,12 @@ class Authenticate(wsgi.Middleware): raise webob.exc.HTTPForbidden() # Authenticated! + remote_address = req.remote_addr + if FLAGS.use_forwarded_for: + remote_address = req.headers.get('X-Forwarded-For', remote_address) ctxt = context.RequestContext(user=user, project=project, - remote_address=req.remote_addr) + remote_address=remote_address) req.environ['ec2.context'] = ctxt return self.application diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py index 2f4f414ccb..66439a0dfb 100644 --- a/nova/api/ec2/metadatarequesthandler.py +++ b/nova/api/ec2/metadatarequesthandler.py @@ -23,9 +23,13 @@ import logging import webob.dec import webob.exc +from nova import flags from nova.api.ec2 import cloud +FLAGS = flags.FLAGS + + class MetadataRequestHandler(object): """Serve metadata from the EC2 API.""" @@ -63,10 +67,13 @@ class MetadataRequestHandler(object): @webob.dec.wsgify def __call__(self, req): cc = cloud.CloudController() - meta_data = cc.get_metadata(req.remote_addr) + remote_address = req.remote_addr + if FLAGS.use_forwarded_for: + remote_address = req.headers.get('X-Forwarded-For', remote_address) + meta_data = cc.get_metadata(remote_address) if meta_data is None: logging.error('Failed to get metadata for ip: %s' % - req.remote_addr) + remote_address) raise webob.exc.HTTPNotFound() data = self.lookup(req.path_info, meta_data) if data is None: From d00a0cec7e8bb02b44c7d9fd94cb9763c37c505e Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Sun, 19 Dec 2010 22:05:46 -0400 Subject: [PATCH 183/229] pep8 (again) --- nova/tests/api/openstack/test_servers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index ba432f6c32..1283167a11 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -62,7 +62,9 @@ def stub_instance(id, user_id=1): def fake_compute_api(cls, req, id): return True + class ServersTest(unittest.TestCase): + def setUp(self): self.stubs = stubout.StubOutForTesting() fakes.FakeAuthManager.auth_data = {} From 8ddae1280da59a0e86e1daf1c8de97248ef6cb13 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Sun, 19 Dec 2010 22:14:36 -0400 Subject: [PATCH 184/229] pep8 (again again) --- nova/tests/api/openstack/test_servers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 1283167a11..3820f5f271 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -59,6 +59,7 @@ def stub_instance(id, user_id=1): return Instance(id=int(id) + 123456, state=0, image_id=10, user_id=user_id, display_name='server%s' % id, internal_id=id) + def fake_compute_api(cls, req, id): return True From 2eafa204703785f314226eeebb31a840d3dd502f Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 20 Dec 2010 11:31:21 -0600 Subject: [PATCH 185/229] Added InstanceActions DB model --- nova/db/sqlalchemy/models.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 61764ee8df..6eac1b8738 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -243,6 +243,17 @@ class InstanceDiagnostics(BASE, NovaBase): vif_0_rx = Column(Float) +class InstanceActions(BASE, NovaBase): + """Represents a guest VM's actions and results""" + __tablename__ = "instance_actions" + id = Column(Integer, primary_key=True) + instance_id = Column(Integer, ForeignKey('instances.id')) + + action = Column(String(255)) + result = Column(Boolean) + error = Column(Text) + + class Volume(BASE, NovaBase): """Represents a block storage device that can be attached to a vm.""" __tablename__ = 'volumes' @@ -543,10 +554,11 @@ def register_models(): it will never need to be called explicitly elsewhere. """ from sqlalchemy import create_engine - models = (Service, Instance, InstanceDiagnostics, Volume, ExportDevice, - IscsiTarget, FixedIp, FloatingIp, Network, SecurityGroup, - SecurityGroupIngressRule, SecurityGroupInstanceAssociation, - AuthToken, User, Project) # , Image, Host + models = (Service, Instance, InstanceDiagnostics, InstanceActions, + Volume, ExportDevice, IscsiTarget, FixedIp, FloatingIp, + Network, SecurityGroup, SecurityGroupIngressRule, + SecurityGroupInstanceAssociation, AuthToken, User, + Project) # , Image, Host engine = create_engine(FLAGS.sql_connection, echo=False) for model in models: model.metadata.create_all(engine) From e91590145962827e68bbb3518990d7d94e99608d Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 20 Dec 2010 20:37:56 +0100 Subject: [PATCH 186/229] PEP8 fixups --- nova/compute/disk.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/compute/disk.py b/nova/compute/disk.py index 3d5e077240..0169a98317 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -105,12 +105,14 @@ def partition(infile, outfile, local_bytes=0, resize=True, execute('parted --script %s mkpartfs primary %s %ds %ds' % (outfile, local_type, local_first, local_last)) + def extend(image, size, execute): file_size = os.path.getsize(image) if file_size >= size: return return execute('truncate -s size %s' % (image,)) + def inject_data(image, key=None, net=None, partition=None, execute=None): """Injects a ssh key and optionally net data into a disk image. From 27d89c2d425aefcd32549b08e3cff8b132d3e75b Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Mon, 20 Dec 2010 20:04:24 +0000 Subject: [PATCH 187/229] don't add the ip to bridge on compute hosts --- nova/network/manager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index 6a30f30b79..004dc6ff6f 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -361,8 +361,7 @@ class FlatDHCPManager(FlatManager): """Sets up matching network for compute hosts.""" network_ref = db.network_get_by_instance(context, instance_id) self.driver.ensure_bridge(network_ref['bridge'], - FLAGS.flat_interface, - network_ref) + FLAGS.flat_interface) def setup_fixed_ip(self, context, address): """Setup dhcp for this network.""" From f53532bf17d0fac1cc4a1f51f6c12e2ae12d0d74 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 20 Dec 2010 14:21:01 -0600 Subject: [PATCH 188/229] Make column names more generic --- nova/db/sqlalchemy/models.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 6eac1b8738..96d9815717 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -232,15 +232,13 @@ class InstanceDiagnostics(BASE, NovaBase): id = Column(Integer, primary_key=True) instance_id = Column(Integer, ForeignKey('instances.id')) - vbd_xvda_read = Column(Float) - vbd_xvda_write = Column(Float) - vbd_xvdb_read = Column(Float) - vbd_xvdb_write = Column(Float) - memory = Column(Float) - memory_internal_free = Column(Float) - cpu0 = Column(Float) - vif_0_tx = Column(Float) - vif_0_rx = Column(Float) + memory_available = Column(Float) + memory_free = Column(Float) + cpu_load = Column(Float) + disk_read = Column(Float) + disk_write = Column(Float) + net_tx = Column(Float) + net_rx = Column(Float) class InstanceActions(BASE, NovaBase): From de383023e4d5c30d3ad4474af104f6b659e1bd32 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Mon, 20 Dec 2010 21:04:54 +0000 Subject: [PATCH 189/229] directly copy ip allocation into compute --- nova/compute/api.py | 44 --------------------------------- nova/compute/manager.py | 55 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 46 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 8e0efa4cc7..2dae9cdbc8 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -145,18 +145,6 @@ class ComputeAPI(base.Base): 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 - # allocated after the host is assigned and setup - # can happen at the same time. - address = self.network_manager.allocate_fixed_ip(context, - instance_id, - is_vpn) - rpc.cast(elevated, - self._get_network_topic(context), - {"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) rpc.cast(context, @@ -219,28 +207,6 @@ class ComputeAPI(base.Base): 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, @@ -293,13 +259,3 @@ class ComputeAPI(base.Base): {"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/compute/manager.py b/nova/compute/manager.py index 7eb60e262d..6d705f983c 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -40,6 +40,7 @@ import logging from nova import exception from nova import flags from nova import manager +from nova import rpc from nova import utils from nova.compute import power_state @@ -76,6 +77,17 @@ class ComputeManager(manager.Manager): state = power_state.NOSTATE self.db.instance_set_state(context, instance_id, state) + 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) + @exception.wrap_exception def refresh_security_group(self, context, security_group_id, **_kwargs): """This call passes stright through to the virtualization driver.""" @@ -89,11 +101,26 @@ class ComputeManager(manager.Manager): if instance_ref['name'] in self.driver.list_instances(): raise exception.Error("Instance has already been created") logging.debug("instance %s: starting...", instance_id) - self.network_manager.setup_compute_network(context, instance_id) self.db.instance_update(context, instance_id, {'host': self.host}) + self.db.instance_set_state(context, + instance_id, + power_state.NOSTATE, + 'networking') + + is_vpn = instance_ref['image_id'] == FLAGS.vpn_image_id + address = self.network_manager.allocate_fixed_ip(context, + instance_id, + is_vpn) + rpc.cast(context, + self._get_network_topic(context), + {"method": "setup_fixed_ip", + "args": {"address": address}}) + + self.network_manager.setup_compute_network(context, instance_id) + # TODO(vish) check to make sure the availability zone matches self.db.instance_set_state(context, instance_id, @@ -119,9 +146,33 @@ class ComputeManager(manager.Manager): def terminate_instance(self, context, instance_id): """Terminate an instance on this machine.""" context = context.elevated() - logging.debug("instance %s: terminating", instance_id) instance_ref = self.db.instance_get(context, instance_id) + + address = self.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. + 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_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) + + logging.debug("instance %s: terminating", instance_id) + volumes = instance_ref.get('volumes', []) or [] for volume in volumes: self.detach_volume(context, instance_id, volume['id']) From 9b5d8600ab3cadd5f3174056eaedd0db420f1f1b Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 20 Dec 2010 22:53:07 +0100 Subject: [PATCH 190/229] Add my @linux2go.dk address to .mailmap --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 2a6eb8d7d6..8041e23418 100644 --- a/.mailmap +++ b/.mailmap @@ -19,6 +19,7 @@ + From 83cf1f7140c20ea2188272b57e4e2c1a95f8ff9e Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 21 Dec 2010 03:26:50 +0000 Subject: [PATCH 191/229] remove extra print statements --- nova/api/ec2/cloud.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 91b2f0064a..ad6debb111 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -723,10 +723,8 @@ class CloudController(object): floating_ip_ref = db.floating_ip_get_by_address(context, public_ip) # NOTE(vish): Perhaps we should just pass this on to compute and # let compute communicate with network. - print "in cloud get" network_topic = self.compute_api.get_network_topic(context, internal_id) - print "got the network topic", network_topic rpc.cast(context, network_topic, {"method": "associate_floating_ip", From 3a0878b7a94ba3411feb9a7944f42c9f352d3a45 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 21 Dec 2010 03:34:30 +0000 Subject: [PATCH 192/229] add a few extra joined objects to get instance --- nova/compute/manager.py | 1 - nova/db/sqlalchemy/api.py | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 3b7fd9c324..b3e97011f0 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -158,7 +158,6 @@ class ComputeManager(manager.Manager): instance_ref = self.db.instance_get(context, instance_id) - if not FLAGS.stub_network: address = self.db.instance_get_floating_address(context, instance_ref['id']) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index f7787d1f0c..d02084da75 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -580,13 +580,17 @@ def instance_get(context, instance_id, session=None): if is_admin_context(context): result = session.query(models.Instance).\ + options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ + options(joinedload('volumes')).\ filter_by(id=instance_id).\ filter_by(deleted=can_read_deleted(context)).\ first() elif is_user_context(context): result = session.query(models.Instance).\ + options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ + options(joinedload('volumes')).\ filter_by(project_id=context.project_id).\ filter_by(id=instance_id).\ filter_by(deleted=False).\ From a2019a14f7e7902c0bfef9fe3e9b576d9f45defe Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 21 Dec 2010 03:39:28 +0000 Subject: [PATCH 193/229] add missing greenthread import --- nova/virt/libvirt_conn.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 5a8c718506..feef1390cf 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -45,6 +45,7 @@ import logging import os import shutil +from eventlet import greenthread from eventlet import event from eventlet import tpool From fb24146290e6cf49397441d36878652da376f66d Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 21 Dec 2010 03:43:47 +0000 Subject: [PATCH 194/229] pep8 and removed extra imports --- nova/compute/api.py | 1 - nova/tests/cloud_unittest.py | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 606344c03d..1dbe6e02d0 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -275,4 +275,3 @@ class ComputeAPI(base.Base): self.db.queue_get_for(context, FLAGS.compute_topic, host), {"method": "unrescue_instance", "args": {"instance_id": instance['id']}}) - diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index 185e4b4e5d..af544e3cb1 100644 --- a/nova/tests/cloud_unittest.py +++ b/nova/tests/cloud_unittest.py @@ -22,12 +22,10 @@ import logging from M2Crypto import BIO from M2Crypto import RSA import os -import StringIO import tempfile import time from eventlet import greenthread -from xml.etree import ElementTree from nova import context from nova import crypto @@ -36,7 +34,6 @@ from nova import flags from nova import rpc from nova import service from nova import test -from nova import utils from nova.auth import manager from nova.compute import power_state from nova.api.ec2 import cloud @@ -75,7 +72,8 @@ class CloudTestCase(test.TestCase): self.user = self.manager.create_user('admin', 'admin', 'admin', True) self.project = self.manager.create_project('proj', 'admin', 'proj') self.context = context.RequestContext(user=self.user, - project=self.project) + project=self.project) + def tearDown(self): self.manager.delete_project(self.project) self.manager.delete_user(self.user) From d2eb04cea6b7f0a669758fc1fba32e77a008a7eb Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 21 Dec 2010 11:42:25 -0600 Subject: [PATCH 195/229] PEP8 cleanup --- nova/virt/xenapi/vmops.py | 16 ++++++++-------- nova/virt/xenapi_conn.py | 30 +++++++++++++++--------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index a18eacf07b..bedf131df4 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -44,12 +44,12 @@ class VMOps(object): VMHelper.late_import() def list_instances(self): - """ List VM instances """ + """List VM instances""" return [self._session.get_xenapi().VM.get_name_label(vm) \ for vm in self._session.get_xenapi().VM.get_all()] def spawn(self, instance): - """ Create VM instance """ + """Create VM instance""" vm = VMHelper.lookup(self._session, instance.name) if vm is not None: raise Exception('Attempted to create non-unique name %s' % @@ -81,7 +81,7 @@ class VMOps(object): vm_ref) def reboot(self, instance): - """ Reboot VM instance """ + """Reboot VM instance""" instance_name = instance.name vm = VMHelper.lookup(self._session, instance_name) if vm is None: @@ -90,7 +90,7 @@ class VMOps(object): self._session.wait_for_task(task) def destroy(self, instance): - """ Destroy VM instance """ + """Destroy VM instance""" vm = VMHelper.lookup(self._session, instance.name) if vm is None: # Don't complain, just return. This lets us clean up instances @@ -127,7 +127,7 @@ class VMOps(object): callback(ret) def pause(self, instance, callback): - """ Pause VM instance """ + """Pause VM instance""" instance_name = instance.name vm = VMHelper.lookup(self._session, instance_name) if vm is None: @@ -136,7 +136,7 @@ class VMOps(object): self._wait_with_callback(task, callback) def unpause(self, instance, callback): - """ Unpause VM instance """ + """Unpause VM instance""" instance_name = instance.name vm = VMHelper.lookup(self._session, instance_name) if vm is None: @@ -145,7 +145,7 @@ class VMOps(object): self._wait_with_callback(task, callback) def get_info(self, instance_id): - """ Return data about VM instance """ + """Return data about VM instance""" vm = VMHelper.lookup_blocking(self._session, instance_id) if vm is None: raise Exception('instance not present %s' % instance_id) @@ -161,6 +161,6 @@ class VMOps(object): return VMHelper.compile_diagnostics(self._session, rec) def get_console_output(self, instance): - """ Return snapshot of console """ + """Return snapshot of console""" # TODO: implement this to fix pylint! return 'FAKE CONSOLE OUTPUT of instance' diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 21ed2cd659..3a9084d892 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -101,7 +101,7 @@ def get_connection(_): class XenAPIConnection(object): - """ A connection to XenServer or Xen Cloud Platform """ + """A connection to XenServer or Xen Cloud Platform""" def __init__(self, url, user, pw): session = XenAPISession(url, user, pw) @@ -109,31 +109,31 @@ class XenAPIConnection(object): self._volumeops = VolumeOps(session) def list_instances(self): - """ List VM instances """ + """List VM instances""" return self._vmops.list_instances() def spawn(self, instance): - """ Create VM instance """ + """Create VM instance""" self._vmops.spawn(instance) def reboot(self, instance): - """ Reboot VM instance """ + """Reboot VM instance""" self._vmops.reboot(instance) def destroy(self, instance): - """ Destroy VM instance """ + """Destroy VM instance""" self._vmops.destroy(instance) def pause(self, instance, callback): - """ Pause VM instance """ + """Pause VM instance""" self._vmops.pause(instance, callback) def unpause(self, instance, callback): - """ Unpause paused VM instance """ + """Unpause paused VM instance""" self._vmops.unpause(instance, callback) def get_info(self, instance_id): - """ Return data about VM instance """ + """Return data about VM instance""" return self._vmops.get_info(instance_id) def get_diagnostics(self, instance_id): @@ -141,33 +141,33 @@ class XenAPIConnection(object): return self._vmops.get_diagnostics(instance_id) def get_console_output(self, instance): - """ Return snapshot of console """ + """Return snapshot of console""" return self._vmops.get_console_output(instance) def attach_volume(self, instance_name, device_path, mountpoint): - """ Attach volume storage to VM instance """ + """Attach volume storage to VM instance""" return self._volumeops.attach_volume(instance_name, device_path, mountpoint) def detach_volume(self, instance_name, mountpoint): - """ Detach volume storage to VM instance """ + """Detach volume storage to VM instance""" return self._volumeops.detach_volume(instance_name, mountpoint) class XenAPISession(object): - """ The session to invoke XenAPI SDK calls """ + """The session to invoke XenAPI SDK calls""" def __init__(self, url, user, pw): self._session = XenAPI.Session(url) self._session.login_with_password(user, pw) def get_xenapi(self): - """ Return the xenapi object """ + """Return the xenapi object""" return self._session.xenapi def get_xenapi_host(self): - """ Return the xenapi host """ + """Return the xenapi host""" return self._session.xenapi.session.get_this_host(self._session.handle) def call_xenapi(self, method, *args): @@ -218,7 +218,7 @@ class XenAPISession(object): def _unwrap_plugin_exceptions(func, *args, **kwargs): - """ Parse exception details """ + """Parse exception details""" try: return func(*args, **kwargs) except XenAPI.Failure, exc: From a17fa6df76a3215d84d99738a1a7752a54cfe914 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 21 Dec 2010 18:43:41 +0000 Subject: [PATCH 196/229] don't allocate networks when getting vpn info --- nova/auth/manager.py | 6 +++--- nova/db/sqlalchemy/api.py | 24 +++++++++++++----------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 74da8e0458..f9a7dd000c 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -540,10 +540,10 @@ class AuthManager(object): """ network_ref = db.project_get_network(context.get_admin_context(), - Project.safe_id(project)) + Project.safe_id(project), False) - if not network_ref['vpn_public_port']: - raise exception.NotFound('project network data has not been set') + if not network_ref: + return (None, None) return (network_ref['vpn_public_address'], network_ref['vpn_public_port']) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 6ecd824e16..35c2c76fd7 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1075,24 +1075,26 @@ def network_update(context, network_id, values): @require_context -def project_get_network(context, project_id): +def project_get_network(context, project_id, associate=True): session = get_session() - rv = session.query(models.Network).\ - filter_by(project_id=project_id).\ - filter_by(deleted=False).\ - first() - if not rv: + result = session.query(models.Network).\ + filter_by(project_id=project_id).\ + filter_by(deleted=False).\ + first() + if not result: + if not associate: + return None try: return network_associate(context, project_id) except IntegrityError: # NOTE(vish): We hit this if there is a race and two # processes are attempting to allocate the # network at the same time - rv = session.query(models.Network).\ - filter_by(project_id=project_id).\ - filter_by(deleted=False).\ - first() - return rv + result = session.query(models.Network).\ + filter_by(project_id=project_id).\ + filter_by(deleted=False).\ + first() + return result ################### From 902db577ea19459c9b01ed7b262024b900440573 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 21 Dec 2010 18:48:07 +0000 Subject: [PATCH 197/229] update db/api.py as well --- nova/db/api.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nova/db/api.py b/nova/db/api.py index 339e0a3ae0..69cfa33778 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -512,12 +512,14 @@ def network_update(context, network_id, values): ################### -def project_get_network(context, project_id): +def project_get_network(context, project_id, associate=True): """Return the network associated with the project. - Raises NotFound if no such network can be found. + If associate is true, it will attempt to associate a new + network if one is not found, otherwise it returns None. """ + return IMPL.project_get_network(context, project_id) From aa0639b00c3cd4b7bd5dd7dc9027e86d0f43150a Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 21 Dec 2010 18:57:11 +0000 Subject: [PATCH 198/229] change virtualization to not get network through project --- nova/virt/libvirt_conn.py | 4 ++-- nova/virt/xenapi/vmops.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index feef1390cf..9be3636619 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -488,8 +488,8 @@ class LibvirtConnection(object): def to_xml(self, instance, rescue=False): # TODO(termie): cache? logging.debug('instance %s: starting toXML method', instance['name']) - network = db.project_get_network(context.get_admin_context(), - instance['project_id']) + network = db.network_get_by_instance(context.get_admin_context(), + instance['id']) # FIXME(vish): stick this in db instance_type = instance['instance_type'] instance_type = instance_types.INSTANCE_TYPES[instance_type] diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 3034df9e1d..de40815e26 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -54,8 +54,8 @@ class VMOps(object): raise Exception('Attempted to create non-unique name %s' % instance.name) - bridge = db.project_get_network(context.get_admin_context(), - instance.project_id).bridge + bridge = db.network_get_by_instance(context.get_admin_context(), + instance['id'])['bridge'] network_ref = \ NetworkHelper.find_network_with_bridge(self._session, bridge) From 132b12e27fa69319f85dee0089fad1ba1a342fd8 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 21 Dec 2010 19:24:12 +0000 Subject: [PATCH 199/229] activate fake rabbit for debugging --- nova/tests/rpc_unittest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/tests/rpc_unittest.py b/nova/tests/rpc_unittest.py index 8c3e310376..6ea2edcab3 100644 --- a/nova/tests/rpc_unittest.py +++ b/nova/tests/rpc_unittest.py @@ -33,7 +33,6 @@ class RpcTestCase(test.TestCase): """Test cases for rpc""" def setUp(self): super(RpcTestCase, self).setUp() - self.flags(fake_rabbit=False) self.conn = rpc.Connection.instance(True) self.receiver = TestReceiver() self.consumer = rpc.AdapterConsumer(connection=self.conn, From b3fce81e384aec46c0963db1f144cc58d02340a4 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 21 Dec 2010 14:13:18 -0600 Subject: [PATCH 200/229] Log all XenAPI actions --- nova/db/api.py | 5 +++++ nova/db/sqlalchemy/api.py | 12 ++++++++++++ nova/db/sqlalchemy/models.py | 1 - nova/virt/xenapi/vmops.py | 16 ++++++++-------- nova/virt/xenapi_conn.py | 33 +++++++++++++++++++++++---------- 5 files changed, 48 insertions(+), 19 deletions(-) diff --git a/nova/db/api.py b/nova/db/api.py index 8f9dc24430..4e15596d9a 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -334,6 +334,11 @@ def instance_add_security_group(context, instance_id, security_group_id): security_group_id) +def instance_action_create(context, values): + """Create an instance action from the values dictionary.""" + return IMPL.instance_action_create(context, values) + + ################### diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 9350636090..63b367d2e0 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -749,6 +749,18 @@ def instance_add_security_group(context, instance_id, security_group_id): instance_ref.save(session=session) +@require_context +def instance_action_create(context, values): + """Create an instance action and the action results""" + action_ref = models.InstanceActions() + action_ref.update(values) + + session = get_session() + with session.begin(): + action_ref.save(session=session) + return action_ref + + ################### diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 96d9815717..eac6a304e9 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -248,7 +248,6 @@ class InstanceActions(BASE, NovaBase): instance_id = Column(Integer, ForeignKey('instances.id')) action = Column(String(255)) - result = Column(Boolean) error = Column(Text) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index bedf131df4..5b9495b677 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -87,7 +87,7 @@ class VMOps(object): if vm is None: raise Exception('instance not present %s' % instance_name) task = self._session.call_xenapi('Async.VM.clean_reboot', vm) - self._session.wait_for_task(task) + self._session.wait_for_task(instance.id, task) def destroy(self, instance): """Destroy VM instance""" @@ -101,7 +101,7 @@ class VMOps(object): try: task = self._session.call_xenapi('Async.VM.hard_shutdown', vm) - self._session.wait_for_task(task) + self._session.wait_for_task(instance.id, task) except XenAPI.Failure, exc: logging.warn(exc) # Disk clean-up @@ -109,19 +109,19 @@ class VMOps(object): for vdi in vdis: try: task = self._session.call_xenapi('Async.VDI.destroy', vdi) - self._session.wait_for_task(task) + self._session.wait_for_task(instance.id, task) except XenAPI.Failure, exc: logging.warn(exc) try: task = self._session.call_xenapi('Async.VM.destroy', vm) - self._session.wait_for_task(task) + self._session.wait_for_task(instance.id, task) except XenAPI.Failure, exc: logging.warn(exc) - def _wait_with_callback(self, task, callback): + def _wait_with_callback(self, instance_id, task, callback): ret = None try: - ret = self._session.wait_for_task(task) + ret = self._session.wait_for_task(instance_id, task) except XenAPI.Failure, exc: logging.warn(exc) callback(ret) @@ -133,7 +133,7 @@ class VMOps(object): if vm is None: raise Exception('instance not present %s' % instance_name) task = self._session.call_xenapi('Async.VM.pause', vm) - self._wait_with_callback(task, callback) + self._wait_with_callback(instance.id, task, callback) def unpause(self, instance, callback): """Unpause VM instance""" @@ -142,7 +142,7 @@ class VMOps(object): if vm is None: raise Exception('instance not present %s' % instance_name) task = self._session.call_xenapi('Async.VM.unpause', vm) - self._wait_with_callback(task, callback) + self._wait_with_callback(instance.id, task, callback) def get_info(self, instance_id): """Return data about VM instance""" diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 3a9084d892..33a55d7b2c 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -54,6 +54,8 @@ import xmlrpclib from eventlet import event from eventlet import tpool +from nova import context +from nova import db from nova import utils from nova import flags from nova.virt.xenapi.vmops import VMOps @@ -183,35 +185,46 @@ class XenAPISession(object): self._session.xenapi.Async.host.call_plugin, self.get_xenapi_host(), plugin, fn, args) - def wait_for_task(self, task): + def wait_for_task(self, instance_id, task): """Return a Deferred that will give the result of the given task. The task is polled until it completes.""" done = event.Event() - loop = utils.LoopingCall(self._poll_task, task, done) + loop = utils.LoopingCall(self._poll_task, instance_id, task, done) loop.start(FLAGS.xenapi_task_poll_interval, now=True) rv = done.wait() loop.stop() return rv - def _poll_task(self, task, done): + def _poll_task(self, instance_id, task, done): """Poll the given XenAPI task, and fire the given Deferred if we get a result.""" try: - #logging.debug('Polling task %s...', task) + name = self._session.xenapi.task.get_name_label(task) status = self._session.xenapi.task.get_status(task) - if status == 'pending': + action = dict( + instance_id=int(instance_id), + action=name, + error=None) + if status == "pending": return - elif status == 'success': + elif status == "success": result = self._session.xenapi.task.get_result(task) - logging.info('Task %s status: success. %s', task, result) + logging.info("Task [%s] %s status: success %s" % ( + name, + task, + result)) done.send(_parse_xmlrpc_value(result)) else: error_info = self._session.xenapi.task.get_error_info(task) - logging.warn('Task %s status: %s. %s', task, status, - error_info) + action["error"] = str(error_info) + logging.warn("Task [%s] %s status: %s %s" % ( + name, + task, + status, + error_info)) done.send_exception(XenAPI.Failure(error_info)) - #logging.debug('Polling task %s done.', task) + db.instance_action_create(context.get_admin_context(), action) except XenAPI.Failure, exc: logging.warn(exc) done.send_exception(*sys.exc_info()) From 7da5fdf45add8e5c049321c5553f98229446e6b9 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 21 Dec 2010 14:28:20 -0600 Subject: [PATCH 201/229] PEP8 cleanup --- nova/db/sqlalchemy/api.py | 2 +- nova/tests/virt_unittest.py | 46 +++++++++++++++++++------------------ 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 63b367d2e0..f409560cfe 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -751,7 +751,7 @@ def instance_add_security_group(context, instance_id, security_group_id): @require_context def instance_action_create(context, values): - """Create an instance action and the action results""" + """Create an instance action from the values dictionary.""" action_ref = models.InstanceActions() action_ref.update(values) diff --git a/nova/tests/virt_unittest.py b/nova/tests/virt_unittest.py index 9bbba4ba9f..0cf016380a 100644 --- a/nova/tests/virt_unittest.py +++ b/nova/tests/virt_unittest.py @@ -129,43 +129,45 @@ class LibvirtConnTestCase(test.TestCase): check_list.append(check) else: if expect_kernel: - check = (lambda t: t.find('./os/kernel').text.split('/' - )[1], 'kernel') + check = (lambda t: t.find('./os/kernel').text.split( + '/')[1], 'kernel') else: check = (lambda t: t.find('./os/kernel'), None) check_list.append(check) if expect_ramdisk: - check = (lambda t: t.find('./os/initrd').text.split('/' - )[1], 'ramdisk') + check = (lambda t: t.find('./os/initrd').text.split( + '/')[1], 'ramdisk') else: check = (lambda t: t.find('./os/initrd'), None) check_list.append(check) common_checks = [ (lambda t: t.find('.').tag, 'domain'), - (lambda t: t.find('./devices/interface/filterref/parameter' - ).get('name'), 'IP'), - (lambda t: t.find('./devices/interface/filterref/parameter' - ).get('value'), '10.11.12.13'), - (lambda t: t.findall('./devices/interface/filterref/parameter' - )[1].get('name'), 'DHCPSERVER'), - (lambda t: t.findall('./devices/interface/filterref/parameter' - )[1].get('value'), '10.0.0.1'), - (lambda t: t.find('./devices/serial/source').get('path' - ).split('/')[1], 'console.log'), + (lambda t: t.find( + './devices/interface/filterref/parameter').get('name'), 'IP'), + (lambda t: t.find( + './devices/interface/filterref/parameter').get( + 'value'), '10.11.12.13'), + (lambda t: t.findall( + './devices/interface/filterref/parameter')[1].get( + 'name'), 'DHCPSERVER'), + (lambda t: t.findall( + './devices/interface/filterref/parameter')[1].get( + 'value'), '10.0.0.1'), + (lambda t: t.find('./devices/serial/source').get( + 'path').split('/')[1], 'console.log'), (lambda t: t.find('./memory').text, '2097152')] if rescue: - common_checks += [(lambda t: t.findall('./devices/disk/source' - )[0].get('file').split('/')[1], - 'rescue-disk'), - (lambda t: t.findall('./devices/disk/source' - )[1].get('file').split('/')[1], - 'disk')] + common_checks += [(lambda t: t.findall( + './devices/disk/source')[0].get('file').split('/')[1], + 'rescue-disk'), (lambda t: t.findall( + './devices/disk/source')[1].get( + 'file').split('/')[1], 'disk')] else: - common_checks += [(lambda t: t.findall('./devices/disk/source' - )[0].get('file').split('/')[1], + common_checks += [(lambda t: t.findall( + './devices/disk/source')[0].get('file').split('/')[1], 'disk')] for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems(): From 40dba7dc0b8faef6dace3e9d54f43b19398c73fc Mon Sep 17 00:00:00 2001 From: Eric Day Date: Tue, 21 Dec 2010 13:00:30 -0800 Subject: [PATCH 202/229] Reworked fakerabbit backend so each connection has it's own. Moved queues and exchanges to be globals. --- nova/fakerabbit.py | 121 ++++++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 68 deletions(-) diff --git a/nova/fakerabbit.py b/nova/fakerabbit.py index c64617931c..792e4c3442 100644 --- a/nova/fakerabbit.py +++ b/nova/fakerabbit.py @@ -25,6 +25,9 @@ from carrot.backends import base from eventlet import greenthread +EXCHANGES = {} +QUEUES = {} + class Message(base.BaseMessage): pass @@ -68,81 +71,63 @@ class Queue(object): return self._queue.get() -class Backend(object): - """ Singleton backend for testing """ - class __impl(base.BaseBackend): - def __init__(self, *args, **kwargs): - #super(__impl, self).__init__(*args, **kwargs) - self._exchanges = {} - self._queues = {} +class Backend(base.BaseBackend): + def queue_declare(self, queue, **kwargs): + global QUEUES + if queue not in QUEUES: + logging.debug('Declaring queue %s', queue) + QUEUES[queue] = Queue(queue) - def _reset_all(self): - self._exchanges = {} - self._queues = {} + def exchange_declare(self, exchange, type, *args, **kwargs): + global EXCHANGES + if exchange not in EXCHANGES: + logging.debug('Declaring exchange %s', exchange) + EXCHANGES[exchange] = Exchange(exchange, type) - def queue_declare(self, queue, **kwargs): - if queue not in self._queues: - logging.debug('Declaring queue %s', queue) - self._queues[queue] = Queue(queue) + def queue_bind(self, queue, exchange, routing_key, **kwargs): + global EXCHANGES + global QUEUES + logging.debug('Binding %s to %s with key %s', + queue, exchange, routing_key) + EXCHANGES[exchange].bind(QUEUES[queue].push, routing_key) - def exchange_declare(self, exchange, type, *args, **kwargs): - if exchange not in self._exchanges: - logging.debug('Declaring exchange %s', exchange) - self._exchanges[exchange] = Exchange(exchange, type) + def declare_consumer(self, queue, callback, *args, **kwargs): + self.current_queue = queue + self.current_callback = callback - def queue_bind(self, queue, exchange, routing_key, **kwargs): - logging.debug('Binding %s to %s with key %s', - queue, exchange, routing_key) - self._exchanges[exchange].bind(self._queues[queue].push, - routing_key) + def consume(self, limit=None): + while True: + item = self.get(self.current_queue) + if item: + self.current_callback(item) + raise StopIteration() + greenthread.sleep(0) - def declare_consumer(self, queue, callback, *args, **kwargs): - self.current_queue = queue - self.current_callback = callback + def get(self, queue, no_ack=False): + global QUEUES + if not queue in QUEUES or not QUEUES[queue].size(): + return None + (message_data, content_type, content_encoding) = QUEUES[queue].pop() + message = Message(backend=self, body=message_data, + content_type=content_type, + content_encoding=content_encoding) + message.result = True + logging.debug('Getting from %s: %s', queue, message) + return message - def consume(self, *args, **kwargs): - while True: - item = self.get(self.current_queue) - if item: - self.current_callback(item) - raise StopIteration() - greenthread.sleep(0) + def prepare_message(self, message_data, delivery_mode, + content_type, content_encoding, **kwargs): + """Prepare message for sending.""" + return (message_data, content_type, content_encoding) - def get(self, queue, no_ack=False): - if not queue in self._queues or not self._queues[queue].size(): - return None - (message_data, content_type, content_encoding) = \ - self._queues[queue].pop() - message = Message(backend=self, body=message_data, - content_type=content_type, - content_encoding=content_encoding) - message.result = True - logging.debug('Getting from %s: %s', queue, message) - return message - - def prepare_message(self, message_data, delivery_mode, - content_type, content_encoding, **kwargs): - """Prepare message for sending.""" - return (message_data, content_type, content_encoding) - - def publish(self, message, exchange, routing_key, **kwargs): - if exchange in self._exchanges: - self._exchanges[exchange].publish( - message, routing_key=routing_key) - - __instance = None - - def __init__(self, *args, **kwargs): - if Backend.__instance is None: - Backend.__instance = Backend.__impl(*args, **kwargs) - self.__dict__['_Backend__instance'] = Backend.__instance - - def __getattr__(self, attr): - return getattr(self.__instance, attr) - - def __setattr__(self, attr, value): - return setattr(self.__instance, attr, value) + def publish(self, message, exchange, routing_key, **kwargs): + global EXCHANGES + if exchange in EXCHANGES: + EXCHANGES[exchange].publish(message, routing_key=routing_key) def reset_all(): - Backend()._reset_all() + global EXCHANGES + global QUEUES + EXCHANGES = {} + QUEUES = {} From db938f975da64540ebb942e9dfd640db4dd7f939 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 21 Dec 2010 21:34:51 +0000 Subject: [PATCH 203/229] removed unused import and fix docstring --- nova/api/ec2/cloud.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index ad6debb111..c845e6a2e5 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -27,7 +27,6 @@ import datetime import logging import re import os -import time from nova import context import IPy @@ -706,7 +705,7 @@ class CloudController(object): def release_address(self, context, public_ip, **kwargs): floating_ip_ref = db.floating_ip_get_by_address(context, public_ip) # NOTE(vish): We don't know which network host should get the ip - # when we allocate, so just send it to any one. This + # when we deallocate, so just send it to any one. This # will probably need to move into a network supervisor # at some point. rpc.cast(context, From 555bea30cddfd32c42b6d7453b5afd2e7fcfb7f2 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 21 Dec 2010 15:46:44 -0600 Subject: [PATCH 204/229] Filter templates and dom0 from list_instances() --- nova/virt/xenapi/vmops.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 5b9495b677..3b00ce8bfc 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -45,8 +45,12 @@ class VMOps(object): def list_instances(self): """List VM instances""" - return [self._session.get_xenapi().VM.get_name_label(vm) \ - for vm in self._session.get_xenapi().VM.get_all()] + vms = [] + for vm in self._session.get_xenapi().VM.get_all(): + rec = self._session.get_xenapi().VM.get_record(vm) + if not rec["is_a_template"] and not rec["is_control_domain"]: + vms.append(rec["name_label"]) + return vms def spawn(self, instance): """Create VM instance""" From f0195ebfd2cc56cee5797fff19fb9702c51df51b Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 21 Dec 2010 21:47:13 +0000 Subject: [PATCH 205/229] fix reboot command to work even if a host is rebooted --- nova/compute/manager.py | 4 +++- nova/virt/libvirt_conn.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index a84af6bb91..6d3ea966d6 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -138,8 +138,8 @@ class ComputeManager(manager.Manager): def reboot_instance(self, context, instance_id): """Reboot an instance on this server.""" context = context.elevated() - instance_ref = self.db.instance_get(context, instance_id) self._update_state(context, instance_id) + instance_ref = self.db.instance_get(context, instance_id) if instance_ref['state'] != power_state.RUNNING: logging.warn('trying to reboot a non-running ' @@ -153,6 +153,7 @@ class ComputeManager(manager.Manager): instance_id, power_state.NOSTATE, 'rebooting') + self.network_manager.setup_compute_network(context, instance_id) self.driver.reboot(instance_ref) self._update_state(context, instance_id) @@ -168,6 +169,7 @@ class ComputeManager(manager.Manager): instance_id, power_state.NOSTATE, 'rescuing') + self.network_manager.setup_compute_network(context, instance_id) self.driver.rescue(instance_ref) self._update_state(context, instance_id) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index ad101db2ae..845167d9f0 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -40,6 +40,7 @@ import logging import os import shutil +from eventlet import greenthread from eventlet import event from eventlet import tpool @@ -183,7 +184,8 @@ class LibvirtConnection(object): # everything has been vetted a bit def _wait_for_timer(): timer_done.wait() - self._cleanup(instance) + if cleanup: + self._cleanup(instance) done.send() greenthread.spawn(_wait_for_timer) From 3b05f5b5b46dd58a891f2e4c7a15231ea44a3e46 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 21 Dec 2010 15:56:12 -0600 Subject: [PATCH 206/229] Style correction --- nova/tests/virt_unittest.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nova/tests/virt_unittest.py b/nova/tests/virt_unittest.py index 0cf016380a..cb35db1e1d 100644 --- a/nova/tests/virt_unittest.py +++ b/nova/tests/virt_unittest.py @@ -160,11 +160,11 @@ class LibvirtConnTestCase(test.TestCase): (lambda t: t.find('./memory').text, '2097152')] if rescue: - common_checks += [(lambda t: t.findall( - './devices/disk/source')[0].get('file').split('/')[1], - 'rescue-disk'), (lambda t: t.findall( - './devices/disk/source')[1].get( - 'file').split('/')[1], 'disk')] + common_checks += [ + (lambda t: t.findall('./devices/disk/source')[0].get( + 'file').split('/')[1], 'rescue-disk'), + (lambda t: t.findall('./devices/disk/source')[1].get( + 'file').split('/')[1], 'disk')] else: common_checks += [(lambda t: t.findall( './devices/disk/source')[0].get('file').split('/')[1], From 7af11742b6bab492eb87c212d05bf77c0c13aea9 Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Wed, 22 Dec 2010 12:24:53 +0100 Subject: [PATCH 207/229] Populate user_data field from run-instances call parameter, default to empty string to avoid metadata base64 decoding failure, LP: #691598 --- nova/api/ec2/cloud.py | 1 + nova/compute/api.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 8375c4399b..13c2b4574b 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -756,6 +756,7 @@ class CloudController(object): display_name=kwargs.get('display_name'), description=kwargs.get('display_description'), key_name=kwargs.get('key_name'), + user_data=kwargs.get('user_data'), security_group=kwargs.get('security_group'), generate_hostname=internal_id_to_ec2_id) return self._format_run_instances(context, diff --git a/nova/compute/api.py b/nova/compute/api.py index c740814da5..8fdc0fc9e6 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -57,6 +57,7 @@ class ComputeAPI(base.Base): max_count=1, kernel_id=None, ramdisk_id=None, display_name='', description='', key_name=None, key_data=None, security_group='default', + user_data=None, generate_hostname=generate_default_hostname): """Create the number of instances requested if quote and other arguments check out ok.""" @@ -120,6 +121,7 @@ class ComputeAPI(base.Base): 'local_gb': type_data['local_gb'], 'display_name': display_name, 'display_description': description, + 'user_data': user_data or '', 'key_name': key_name, 'key_data': key_data} From f783e8ad65c2ba3f605bdc350ac64f4beaf27e9d Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Wed, 22 Dec 2010 13:52:44 +0100 Subject: [PATCH 208/229] Adding me in the Authors file --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index fa38ef0b1b..0b048becb9 100644 --- a/Authors +++ b/Authors @@ -27,6 +27,7 @@ Rick Clark Ryan Lucio Sandy Walsh Soren Hansen +Thierry Carrez Todd Willey Trey Morris Vishvananda Ishaya From 46c4d44affb289209dd6024cbb289b265d9c89c7 Mon Sep 17 00:00:00 2001 From: "jaypipes@gmail.com" <> Date: Wed, 22 Dec 2010 10:40:24 -0500 Subject: [PATCH 209/229] Problem was with a missplaced parentheses. ugh. --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index de5079286c..503a5fd6c9 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -114,7 +114,7 @@ class CloudController(object): start = os.getcwd() os.chdir(FLAGS.ca_path) # TODO(vish): Do this with M2Crypto instead - utils.runthis(_("Generating root CA: %s", "sh genrootca.sh")) + utils.runthis(_("Generating root CA: %s"), "sh genrootca.sh") os.chdir(start) def _get_mpi_data(self, context, project_id): From 56856ac1103ec9f3ba0f2da81832a59e7e773256 Mon Sep 17 00:00:00 2001 From: "jaypipes@gmail.com" <> Date: Wed, 22 Dec 2010 11:12:20 -0500 Subject: [PATCH 210/229] Fix doc building endpoint for gettext. --- doc/ext/nova_autodoc.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/ext/nova_autodoc.py b/doc/ext/nova_autodoc.py index 39aa2c2cfa..5429bb6562 100644 --- a/doc/ext/nova_autodoc.py +++ b/doc/ext/nova_autodoc.py @@ -1,5 +1,8 @@ +import gettext import os +gettext.install('nova') + from nova import utils def setup(app): From 21867297b673ec9fe055fb6c7e4a3dadcfa6fdd2 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 22 Dec 2010 12:39:59 -0600 Subject: [PATCH 211/229] Minor bug fix --- nova/api/__init__.py | 1 + nova/api/openstack/images.py | 1 + 2 files changed, 2 insertions(+) diff --git a/nova/api/__init__.py b/nova/api/__init__.py index 80f9f21092..e081ec10b6 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -24,6 +24,7 @@ Root WSGI middleware for all API controllers. :ec2api_subdomain: subdomain running the EC2 API (default: ec2) """ +import logging import routes import webob.dec diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index fe8d9d75ff..d3312aba86 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -53,6 +53,7 @@ class Controller(wsgi.Controller): images = common.limited(images, req) except NotImplementedError: # Emulate detail() using repeated calls to show() + ctxt = req.environ['nova.context'] images = self._service.index(ctxt) images = common.limited(images, req) images = [self._service.show(ctxt, i['id']) for i in images] From c4fb755b169895f9ffab6ab4d18f5227688b7ae4 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 22 Dec 2010 13:18:26 -0600 Subject: [PATCH 212/229] Abstracted auth and ratelimiting more --- nova/api/openstack/__init__.py | 56 +++------------------ nova/api/openstack/auth.py | 22 ++++++-- nova/api/openstack/ratelimiting/__init__.py | 22 ++++++-- 3 files changed, 43 insertions(+), 57 deletions(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index cdc25e2b74..b18edc8e7a 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -45,12 +45,12 @@ from nova.auth import manager FLAGS = flags.FLAGS -flags.DEFINE_string('nova_api_auth', - 'nova.api.openstack.auth.BasicApiAuthManager', +flags.DEFINE_string('os_api_auth', + 'nova.api.openstack.auth.AuthMiddleware', 'The auth mechanism to use for the OpenStack API implemenation') flags.DEFINE_string('os_api_ratelimiting', - 'nova.api.openstack.ratelimiting.BasicRateLimiting', + 'nova.api.openstack.ratelimiting.RateLimitingMiddleware', 'Default ratelimiting implementation for the Openstack API') flags.DEFINE_bool('allow_admin_api', @@ -62,7 +62,10 @@ class API(wsgi.Middleware): """WSGI entry point for all OpenStack API requests.""" def __init__(self): - app = AuthMiddleware(RateLimitingMiddleware(APIRouter())) + auth_middleware = utils.import_class(FLAGS.os_api_auth) + ratelimiting_middleware = \ + utils.import_class(FLAGS.os_api_ratelimiting) + app = auth_middleware(ratelimiting_middleware(APIRouter())) super(API, self).__init__(app) @webob.dec.wsgify @@ -76,51 +79,6 @@ class API(wsgi.Middleware): return faults.Fault(exc) -class AuthMiddleware(wsgi.Middleware): - """Authorize the openstack API request or return an HTTP Forbidden.""" - - def __init__(self, application): - self.auth_driver = utils.import_class(FLAGS.nova_api_auth)() - super(AuthMiddleware, self).__init__(application) - - @webob.dec.wsgify - def __call__(self, req): - if not self.auth_driver.has_authentication(req): - return self.auth_driver.authenticate(req) - - user = self.auth_driver.get_user_by_authentication(req) - - if not user: - return faults.Fault(webob.exc.HTTPUnauthorized()) - - req.environ['nova.context'] = context.RequestContext(user, user) - return self.application - - -class RateLimitingMiddleware(wsgi.Middleware): - """Rate limit incoming requests according to the OpenStack rate limits.""" - - def __init__(self, application, service_host=None): - """Create a rate limiting middleware that wraps the given application. - - By default, rate counters are stored in memory. If service_host is - specified, the middleware instead relies on the ratelimiting.WSGIApp - at the given host+port to keep rate counters. - """ - super(RateLimitingMiddleware, self).__init__(application) - self._limiting_driver = \ - utils.import_class(FLAGS.os_api_ratelimiting)(service_host) - - @webob.dec.wsgify - def __call__(self, req): - """Rate limit the request. - - If the request should be rate limited, return a 413 status with a - Retry-After header giving the time when the request would succeed. - """ - return self._limiting_driver.limited_request(req, self.application) - - class APIRouter(wsgi.Router): """ Routes requests on the OpenStack API to the appropriate controller diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 26cb50dca2..3850dd1f0f 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -16,16 +16,28 @@ from nova.api.openstack import faults FLAGS = flags.FLAGS +class AuthMiddleware(wsgi.Middleware): + """Authorize the openstack API request or return an HTTP Forbidden.""" -class BasicApiAuthManager(object): - """ Implements a somewhat rudimentary version of OpenStack Auth""" - - def __init__(self, db_driver=None): + def __init__(self, application): if not db_driver: db_driver = FLAGS.db_driver self.db = utils.import_object(db_driver) self.auth = auth.manager.AuthManager() - super(BasicApiAuthManager, self).__init__() + super(AuthMiddleware, self).__init__(application) + + @webob.dec.wsgify + def __call__(self, req): + if not self.has_authentication(req): + return self.authenticate(req) + + user = self.get_user_by_authentication(req) + + if not user: + return faults.Fault(webob.exc.HTTPUnauthorized()) + + req.environ['nova.context'] = context.RequestContext(user, user) + return self.application def has_authentication(self, req): return 'X-Auth-Token' in req.headers diff --git a/nova/api/openstack/ratelimiting/__init__.py b/nova/api/openstack/ratelimiting/__init__.py index 1bf44bc7b3..9892e792ec 100644 --- a/nova/api/openstack/ratelimiting/__init__.py +++ b/nova/api/openstack/ratelimiting/__init__.py @@ -14,11 +14,16 @@ PER_MINUTE = 60 PER_HOUR = 60 * 60 PER_DAY = 60 * 60 * 24 +class RateLimitingMiddleware(wsgi.Middleware): + """Rate limit incoming requests according to the OpenStack rate limits.""" -class BasicRateLimiting(object): - """ Implements Rate limits as per the Rackspace CloudServers API spec. """ + def __init__(self, application, service_host=None): + """Create a rate limiting middleware that wraps the given application. - def __init__(self, service_host): + By default, rate counters are stored in memory. If service_host is + specified, the middleware instead relies on the ratelimiting.WSGIApp + at the given host+port to keep rate counters. + """ if not service_host: #TODO(gundlach): These limits were based on limitations of Cloud #Servers. We should revisit them in Nova. @@ -31,6 +36,16 @@ class BasicRateLimiting(object): }) else: self.limiter = WSGIAppProxy(service_host) + super(RateLimitingMiddleware, self).__init__(application) + + @webob.dec.wsgify + def __call__(self, req): + """Rate limit the request. + + If the request should be rate limited, return a 413 status with a + Retry-After header giving the time when the request would succeed. + """ + return self.limited_request(req, self.application) def limited_request(self, req, application): """Rate limit the request. @@ -75,6 +90,7 @@ class BasicRateLimiting(object): return req.method return None + class Limiter(object): """Class providing rate limiting of arbitrary actions.""" From e419c27a00a85b7daba42f580e332d31713ae271 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 22 Dec 2010 13:33:26 -0600 Subject: [PATCH 213/229] Moved some things for testing --- nova/api/openstack/auth.py | 1 + nova/api/openstack/ratelimiting/__init__.py | 1 + nova/tests/api/openstack/__init__.py | 2 ++ 3 files changed, 4 insertions(+) diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 3850dd1f0f..6c3c870a1d 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -12,6 +12,7 @@ from nova import db from nova import flags from nova import manager from nova import utils +from nova import wsgi from nova.api.openstack import faults FLAGS = flags.FLAGS diff --git a/nova/api/openstack/ratelimiting/__init__.py b/nova/api/openstack/ratelimiting/__init__.py index 9892e792ec..a2e0734efb 100644 --- a/nova/api/openstack/ratelimiting/__init__.py +++ b/nova/api/openstack/ratelimiting/__init__.py @@ -6,6 +6,7 @@ import urllib import webob.dec import webob.exc +from nova import wsgi from nova.api.openstack import faults # Convenience constants for the limits dictionary passed to Limiter(). diff --git a/nova/tests/api/openstack/__init__.py b/nova/tests/api/openstack/__init__.py index fffc57e67f..efe73b8e2f 100644 --- a/nova/tests/api/openstack/__init__.py +++ b/nova/tests/api/openstack/__init__.py @@ -27,6 +27,8 @@ from webob import Request FLAGS = flags.FLAGS +RateLimitingMiddleware = utils.import_class(FLAGS.os_api_ratelimiting) + class RateLimitingMiddlewareTest(unittest.TestCase): def test_get_action_name(self): From 168cde072542f9f4df7e7eb26f6b632306c0b7d2 Mon Sep 17 00:00:00 2001 From: mdietz Date: Wed, 22 Dec 2010 19:52:13 +0000 Subject: [PATCH 214/229] Finished moving the middleware layers and fixed the API tests again --- nova/api/openstack/auth.py | 2 +- nova/tests/api/openstack/__init__.py | 13 ++++++------- nova/tests/api/openstack/fakes.py | 15 +++++++++------ nova/tests/api/openstack/test_auth.py | 4 ++-- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 6c3c870a1d..99cae2c755 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -20,7 +20,7 @@ FLAGS = flags.FLAGS class AuthMiddleware(wsgi.Middleware): """Authorize the openstack API request or return an HTTP Forbidden.""" - def __init__(self, application): + def __init__(self, application, db_driver=None): if not db_driver: db_driver = FLAGS.db_driver self.db = utils.import_object(db_driver) diff --git a/nova/tests/api/openstack/__init__.py b/nova/tests/api/openstack/__init__.py index efe73b8e2f..9e183bd0d4 100644 --- a/nova/tests/api/openstack/__init__.py +++ b/nova/tests/api/openstack/__init__.py @@ -19,15 +19,14 @@ import unittest from nova import context from nova import flags +from nova.api.openstack.ratelimiting import RateLimitingMiddleware from nova.api.openstack.common import limited -from nova.api.openstack import RateLimitingMiddleware from nova.tests.api.fakes import APIStub from nova import utils from webob import Request FLAGS = flags.FLAGS -RateLimitingMiddleware = utils.import_class(FLAGS.os_api_ratelimiting) class RateLimitingMiddlewareTest(unittest.TestCase): @@ -37,7 +36,7 @@ class RateLimitingMiddlewareTest(unittest.TestCase): def verify(method, url, action_name): req = Request.blank(url) req.method = method - action = middleware._limiting_driver.get_action_name(req) + action = middleware.get_action_name(req) self.assertEqual(action, action_name) verify('PUT', '/servers/4', 'PUT') @@ -70,7 +69,7 @@ class RateLimitingMiddlewareTest(unittest.TestCase): middleware = RateLimitingMiddleware(APIStub()) self.exhaust(middleware, 'POST', '/servers/4', 'usr1', 10) self.exhaust(middleware, 'POST', '/images/4', 'usr2', 10) - self.assertTrue(set(middleware._limiting_driver.limiter._levels) == \ + self.assertTrue(set(middleware.limiter._levels) == \ set(['usr1:POST', 'usr1:POST servers', 'usr2:POST'])) def test_POST_servers_action_correctly_ratelimited(self): @@ -79,15 +78,15 @@ class RateLimitingMiddlewareTest(unittest.TestCase): for i in range(5): self.exhaust(middleware, 'POST', '/servers/4', 'usr1', 10) # Reset the 'POST' action counter. - del middleware._limiting_driver.limiter._levels['usr1:POST'] + del middleware.limiter._levels['usr1:POST'] # All 50 daily "POST servers" actions should be all used up self.exhaust(middleware, 'POST', '/servers/4', 'usr1', 0) def test_proxy_ctor_works(self): middleware = RateLimitingMiddleware(APIStub()) - self.assertEqual(middleware._limiting_driver.limiter.__class__.__name__, "Limiter") + self.assertEqual(middleware.limiter.__class__.__name__, "Limiter") middleware = RateLimitingMiddleware(APIStub(), service_host='foobar') - self.assertEqual(middleware._limiting_driver.limiter.__class__.__name__, "WSGIAppProxy") + self.assertEqual(middleware.limiter.__class__.__name__, "WSGIAppProxy") class LimiterTest(unittest.TestCase): diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 96689d2cd3..f773b26a7b 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -29,6 +29,8 @@ from nova import exception as exc from nova import flags from nova import utils import nova.api.openstack.auth +from nova.api.openstack import auth +from nova.api.openstack import ratelimiting from nova.image import glance from nova.image import local from nova.image import service @@ -52,10 +54,11 @@ class FakeRouter(Router): return res -def fake_auth_init(self): +def fake_auth_init(self, application): self.db = FakeAuthDatabase() self.context = Context() self.auth = FakeAuthManager() + self.application = application @webob.dec.wsgify @@ -83,21 +86,21 @@ def stub_out_auth(stubs): def fake_auth_init(self, app): self.application = app - stubs.Set(nova.api.openstack.AuthMiddleware, + stubs.Set(nova.api.openstack.auth.AuthMiddleware, '__init__', fake_auth_init) - stubs.Set(nova.api.openstack.AuthMiddleware, + stubs.Set(nova.api.openstack.auth.AuthMiddleware, '__call__', fake_wsgi) def stub_out_rate_limiting(stubs): def fake_rate_init(self, app): - super(nova.api.openstack.RateLimitingMiddleware, self).__init__(app) + super(nova.api.openstack.ratelimiting.RateLimitingMiddleware, self).__init__(app) self.application = app - stubs.Set(nova.api.openstack.RateLimitingMiddleware, + stubs.Set(nova.api.openstack.ratelimiting.RateLimitingMiddleware, '__init__', fake_rate_init) - stubs.Set(nova.api.openstack.RateLimitingMiddleware, + stubs.Set(nova.api.openstack.ratelimiting.RateLimitingMiddleware, '__call__', fake_wsgi) diff --git a/nova/tests/api/openstack/test_auth.py b/nova/tests/api/openstack/test_auth.py index 7b427c2dbb..489a1dfbfd 100644 --- a/nova/tests/api/openstack/test_auth.py +++ b/nova/tests/api/openstack/test_auth.py @@ -34,7 +34,7 @@ class Test(unittest.TestCase): def setUp(self): self.stubs = stubout.StubOutForTesting() - self.stubs.Set(nova.api.openstack.auth.BasicApiAuthManager, + self.stubs.Set(nova.api.openstack.auth.AuthMiddleware, '__init__', fakes.fake_auth_init) self.stubs.Set(context, 'RequestContext', fakes.FakeRequestContext) fakes.FakeAuthManager.auth_data = {} @@ -131,7 +131,7 @@ class Test(unittest.TestCase): class TestLimiter(unittest.TestCase): def setUp(self): self.stubs = stubout.StubOutForTesting() - self.stubs.Set(nova.api.openstack.auth.BasicApiAuthManager, + self.stubs.Set(nova.api.openstack.auth.AuthMiddleware, '__init__', fakes.fake_auth_init) self.stubs.Set(context, 'RequestContext', fakes.FakeRequestContext) fakes.FakeAuthManager.auth_data = {} From c2faf1c5e689ac5e81068a305a624e626e9a87b5 Mon Sep 17 00:00:00 2001 From: mdietz Date: Wed, 22 Dec 2010 20:06:22 +0000 Subject: [PATCH 215/229] Forgot the copyright info --- nova/api/openstack/auth.py | 18 +++++++++++++++++- nova/api/openstack/ratelimiting/__init__.py | 17 +++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 99cae2c755..72ad4ffa9e 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -1,4 +1,20 @@ -import datetime +# 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 datetime + import hashlib import json import time diff --git a/nova/api/openstack/ratelimiting/__init__.py b/nova/api/openstack/ratelimiting/__init__.py index a2e0734efb..8ca575b360 100644 --- a/nova/api/openstack/ratelimiting/__init__.py +++ b/nova/api/openstack/ratelimiting/__init__.py @@ -1,3 +1,20 @@ +# 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 datetime + """Rate limiting of arbitrary actions.""" import httplib From 81191660cf6d1e5ea47630ed45041dc923f6b57a Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 22 Dec 2010 20:59:53 +0000 Subject: [PATCH 216/229] merge trunk and upgrade to cheetah templating --- .mailmap | 1 + Authors | 2 + MANIFEST.in | 2 +- nova/adminclient.py | 1 + nova/api/cloudpipe/__init__.py | 4 +- nova/api/ec2/__init__.py | 6 +- nova/api/ec2/admin.py | 1 + nova/api/ec2/apirequest.py | 4 +- nova/api/ec2/cloud.py | 54 +++--- nova/api/ec2/metadatarequesthandler.py | 2 +- nova/api/openstack/__init__.py | 17 +- nova/api/openstack/backup_schedules.py | 1 + nova/api/openstack/servers.py | 29 +++ nova/auth/dbdriver.py | 20 +-- nova/auth/fakeldap.py | 101 +++++++---- nova/auth/ldapdriver.py | 69 +++---- nova/auth/manager.py | 30 ++-- nova/cloudpipe/pipelib.py | 2 +- nova/compute/api.py | 47 +++-- nova/compute/disk.py | 38 ++-- nova/compute/instance_types.py | 3 +- nova/compute/manager.py | 73 ++++++-- nova/compute/monitor.py | 12 +- nova/crypto.py | 18 +- nova/db/api.py | 5 + nova/db/sqlalchemy/api.py | 64 ++++--- nova/db/sqlalchemy/models.py | 36 +++- nova/exception.py | 11 +- nova/fakerabbit.py | 12 +- nova/flags.py | 7 +- nova/image/glance.py | 8 +- nova/image/s3.py | 3 +- nova/network/linux_net.py | 10 +- nova/network/manager.py | 20 +-- nova/objectstore/handler.py | 24 +-- nova/rpc.py | 34 ++-- nova/scheduler/chance.py | 2 +- nova/scheduler/driver.py | 2 +- nova/scheduler/manager.py | 2 +- nova/scheduler/simple.py | 13 +- nova/service.py | 14 +- nova/tests/api/openstack/test_servers.py | 37 +++- nova/tests/auth_unittest.py | 10 +- nova/tests/compute_unittest.py | 8 + nova/tests/virt_unittest.py | 130 +++++++++++--- nova/twistd.py | 8 +- nova/utils.py | 14 +- nova/virt/connection.py | 2 +- nova/virt/fake.py | 16 +- nova/virt/libvirt.qemu.xml.template | 34 ---- ... => libvirt.rescue.qemu.xml.template.THIS} | 0 ...e => libvirt.rescue.uml.xml.template.THIS} | 0 nova/virt/libvirt.rescue.xen.xml.template | 34 ---- ...template => libvirt.uml.xml.template.THIS} | 0 nova/virt/libvirt.xen.xml.template | 30 ---- nova/virt/libvirt.xml.template | 79 ++++++++ nova/virt/libvirt_conn.py | 168 ++++++++++-------- nova/virt/xenapi/network_utils.py | 1 + nova/virt/xenapi/vm_utils.py | 7 +- nova/virt/xenapi/vmops.py | 55 ++++-- nova/virt/xenapi/volumeops.py | 1 + nova/virt/xenapi_conn.py | 81 ++++++--- nova/volume/driver.py | 10 +- nova/volume/manager.py | 20 +-- tools/pip-requires | 1 + 65 files changed, 976 insertions(+), 574 deletions(-) delete mode 100644 nova/virt/libvirt.qemu.xml.template rename nova/virt/{libvirt.rescue.qemu.xml.template => libvirt.rescue.qemu.xml.template.THIS} (100%) rename nova/virt/{libvirt.rescue.uml.xml.template => libvirt.rescue.uml.xml.template.THIS} (100%) delete mode 100644 nova/virt/libvirt.rescue.xen.xml.template rename nova/virt/{libvirt.uml.xml.template => libvirt.uml.xml.template.THIS} (100%) delete mode 100644 nova/virt/libvirt.xen.xml.template create mode 100644 nova/virt/libvirt.xml.template diff --git a/.mailmap b/.mailmap index 2a6eb8d7d6..8041e23418 100644 --- a/.mailmap +++ b/.mailmap @@ -19,6 +19,7 @@ + diff --git a/Authors b/Authors index 565444ee12..fa38ef0b1b 100644 --- a/Authors +++ b/Authors @@ -6,6 +6,7 @@ Chris Behrens Chmouel Boudjnah Dean Troyer Devin Carlen +Ed Leafe Eldar Nugaev Eric Day Ewan Mellor @@ -14,6 +15,7 @@ Jay Pipes Jesse Andrews Joe Heck Joel Moore +Jonathan Bryce Josh Kearney Joshua McKenty Justin Santa Barbara diff --git a/MANIFEST.in b/MANIFEST.in index 982b727aa2..199ce30b6d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -13,7 +13,7 @@ include nova/cloudpipe/client.ovpn.template include nova/compute/fakevirtinstance.xml include nova/compute/interfaces.template include nova/virt/interfaces.template -include nova/virt/libvirt.*.xml.template +include nova/virt/libvirt*.xml.template include nova/tests/CA/ include nova/tests/CA/cacert.pem include nova/tests/CA/private/ diff --git a/nova/adminclient.py b/nova/adminclient.py index 5a62cce7d2..6ae9f0c0f4 100644 --- a/nova/adminclient.py +++ b/nova/adminclient.py @@ -194,6 +194,7 @@ class HostInfo(object): class NovaAdminClient(object): + def __init__( self, clc_url=DEFAULT_CLC_URL, diff --git a/nova/api/cloudpipe/__init__.py b/nova/api/cloudpipe/__init__.py index 6d40990a88..00ad389134 100644 --- a/nova/api/cloudpipe/__init__.py +++ b/nova/api/cloudpipe/__init__.py @@ -45,7 +45,7 @@ class API(wsgi.Application): def __call__(self, req): if req.method == 'POST': return self.sign_csr(req) - _log.debug("Cloudpipe path is %s" % req.path_info) + _log.debug(_("Cloudpipe path is %s") % req.path_info) if req.path_info.endswith("/getca/"): return self.send_root_ca(req) return webob.exc.HTTPNotFound() @@ -56,7 +56,7 @@ class API(wsgi.Application): return instance['project_id'] def send_root_ca(self, req): - _log.debug("Getting root ca") + _log.debug(_("Getting root ca")) project_id = self.get_project_id_from_ip(req.remote_addr) res = webob.Response() res.headers["Content-Type"] = "text/plain" diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index a6ee16c332..dd87d1f716 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -77,7 +77,7 @@ class Authenticate(wsgi.Middleware): req.host, req.path) except exception.Error, ex: - logging.debug("Authentication Failure: %s" % ex) + logging.debug(_("Authentication Failure: %s") % ex) raise webob.exc.HTTPForbidden() # Authenticated! @@ -120,9 +120,9 @@ class Router(wsgi.Middleware): except: raise webob.exc.HTTPBadRequest() - _log.debug('action: %s' % action) + _log.debug(_('action: %s') % action) for key, value in args.items(): - _log.debug('arg: %s\t\tval: %s' % (key, value)) + _log.debug(_('arg: %s\t\tval: %s') % (key, value)) # Success! req.environ['ec2.controller'] = controller diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index 1c6ab688d4..fac01369ec 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -168,6 +168,7 @@ class AdminController(object): # FIXME(vish): these host commands don't work yet, perhaps some of the # required data can be retrieved from service objects? + def describe_hosts(self, _context, **_kwargs): """Returns status info for all nodes. Includes: * Disk Space diff --git a/nova/api/ec2/apirequest.py b/nova/api/ec2/apirequest.py index 5758781b69..a90fbeb0c2 100644 --- a/nova/api/ec2/apirequest.py +++ b/nova/api/ec2/apirequest.py @@ -92,8 +92,8 @@ class APIRequest(object): method = getattr(self.controller, _camelcase_to_underscore(self.action)) except AttributeError: - _error = ('Unsupported API request: controller = %s,' - 'action = %s') % (self.controller, self.action) + _error = _('Unsupported API request: controller = %s,' + 'action = %s') % (self.controller, self.action) _log.warning(_error) # TODO: Raise custom exception, trap in apiserver, # and reraise as 400 error. diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index ebb13aedc5..503a5fd6c9 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -114,7 +114,7 @@ class CloudController(object): start = os.getcwd() os.chdir(FLAGS.ca_path) # TODO(vish): Do this with M2Crypto instead - utils.runthis("Generating root CA: %s", "sh genrootca.sh") + utils.runthis(_("Generating root CA: %s"), "sh genrootca.sh") os.chdir(start) def _get_mpi_data(self, context, project_id): @@ -318,11 +318,11 @@ class CloudController(object): ip_protocol = str(ip_protocol) if ip_protocol.upper() not in ['TCP', 'UDP', 'ICMP']: - raise InvalidInputException('%s is not a valid ipProtocol' % + raise InvalidInputException(_('%s is not a valid ipProtocol') % (ip_protocol,)) if ((min(from_port, to_port) < -1) or (max(from_port, to_port) > 65535)): - raise InvalidInputException('Invalid port range') + raise InvalidInputException(_('Invalid port range')) values['protocol'] = ip_protocol values['from_port'] = from_port @@ -360,7 +360,8 @@ class CloudController(object): criteria = self._revoke_rule_args_to_dict(context, **kwargs) if criteria == None: - raise exception.ApiError("No rule for the specified parameters.") + raise exception.ApiError(_("No rule for the specified " + "parameters.")) for rule in security_group.rules: match = True @@ -371,7 +372,7 @@ class CloudController(object): db.security_group_rule_destroy(context, rule['id']) self._trigger_refresh_security_group(context, security_group) return True - raise exception.ApiError("No rule for the specified parameters.") + raise exception.ApiError(_("No rule for the specified parameters.")) # TODO(soren): This has only been tested with Boto as the client. # Unfortunately, it seems Boto is using an old API @@ -387,8 +388,8 @@ class CloudController(object): values['parent_group_id'] = security_group.id if self._security_group_rule_exists(security_group, values): - raise exception.ApiError('This rule already exists in group %s' % - group_name) + raise exception.ApiError(_('This rule already exists in group %s') + % group_name) security_group_rule = db.security_group_rule_create(context, values) @@ -416,7 +417,7 @@ class CloudController(object): def create_security_group(self, context, group_name, group_description): self.compute_api.ensure_default_security_group(context) if db.security_group_exists(context, context.project_id, group_name): - raise exception.ApiError('group %s already exists' % group_name) + raise exception.ApiError(_('group %s already exists') % group_name) group = {'user_id': context.user.id, 'project_id': context.project_id, @@ -529,13 +530,13 @@ class CloudController(object): def attach_volume(self, context, volume_id, instance_id, device, **kwargs): volume_ref = db.volume_get_by_ec2_id(context, volume_id) if not re.match("^/dev/[a-z]d[a-z]+$", device): - raise exception.ApiError("Invalid device specified: %s. " - "Example device: /dev/vdb" % device) + raise exception.ApiError(_("Invalid device specified: %s. " + "Example device: /dev/vdb") % device) # TODO(vish): abstract status checking? if volume_ref['status'] != "available": - raise exception.ApiError("Volume status must be available") + raise exception.ApiError(_("Volume status must be available")) if volume_ref['attach_status'] == "attached": - raise exception.ApiError("Volume is already attached") + raise exception.ApiError(_("Volume is already attached")) internal_id = ec2_id_to_internal_id(instance_id) instance_ref = self.compute_api.get_instance(context, internal_id) host = instance_ref['host'] @@ -557,10 +558,10 @@ class CloudController(object): instance_ref = db.volume_get_instance(context.elevated(), volume_ref['id']) if not instance_ref: - raise exception.ApiError("Volume isn't attached to anything!") + raise exception.ApiError(_("Volume isn't attached to anything!")) # TODO(vish): abstract status checking? if volume_ref['status'] == "available": - raise exception.ApiError("Volume is already detached") + raise exception.ApiError(_("Volume is already detached")) try: host = instance_ref['host'] rpc.cast(context, @@ -689,10 +690,11 @@ class CloudController(object): def allocate_address(self, context, **kwargs): # check quota if quota.allowed_floating_ips(context, 1) < 1: - logging.warn("Quota exceeeded for %s, tried to allocate address", + logging.warn(_("Quota exceeeded for %s, tried to allocate " + "address"), context.project_id) - raise quota.QuotaError("Address quota exceeded. You cannot " - "allocate any more addresses") + raise quota.QuotaError(_("Address quota exceeded. You cannot " + "allocate any more addresses")) network_topic = self._get_network_topic(context) public_ip = rpc.call(context, network_topic, @@ -751,7 +753,7 @@ class CloudController(object): kwargs['image_id'], min_count=int(kwargs.get('min_count', max_count)), max_count=max_count, - kernel_id=kwargs.get('kernel_id'), + kernel_id=kwargs.get('kernel_id', None), ramdisk_id=kwargs.get('ramdisk_id'), display_name=kwargs.get('display_name'), description=kwargs.get('display_description'), @@ -805,7 +807,7 @@ class CloudController(object): # TODO: return error if not authorized volume_ref = db.volume_get_by_ec2_id(context, volume_id) if volume_ref['status'] != "available": - raise exception.ApiError("Volume status must be available") + raise exception.ApiError(_("Volume status must be available")) now = datetime.datetime.utcnow() db.volume_update(context, volume_ref['id'], {'status': 'deleting', 'terminated_at': now}) @@ -836,11 +838,12 @@ class CloudController(object): def describe_image_attribute(self, context, image_id, attribute, **kwargs): if attribute != 'launchPermission': - raise exception.ApiError('attribute not supported: %s' % attribute) + raise exception.ApiError(_('attribute not supported: %s') + % attribute) try: image = self.image_service.show(context, image_id) except IndexError: - raise exception.ApiError('invalid id: %s' % image_id) + raise exception.ApiError(_('invalid id: %s') % image_id) result = {'image_id': image_id, 'launchPermission': []} if image['isPublic']: result['launchPermission'].append({'group': 'all'}) @@ -850,13 +853,14 @@ class CloudController(object): operation_type, **kwargs): # TODO(devcamcar): Support users and groups other than 'all'. if attribute != 'launchPermission': - raise exception.ApiError('attribute not supported: %s' % attribute) + raise exception.ApiError(_('attribute not supported: %s') + % attribute) if not 'user_group' in kwargs: - raise exception.ApiError('user or group not specified') + raise exception.ApiError(_('user or group not specified')) if len(kwargs['user_group']) != 1 and kwargs['user_group'][0] != 'all': - raise exception.ApiError('only group "all" is supported') + raise exception.ApiError(_('only group "all" is supported')) if not operation_type in ['add', 'remove']: - raise exception.ApiError('operation_type must be add or remove') + raise exception.ApiError(_('operation_type must be add or remove')) return self.image_service.modify(context, image_id, operation_type) def update_image(self, context, image_id, **kwargs): diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py index 2f4f414ccb..0e9e686ff4 100644 --- a/nova/api/ec2/metadatarequesthandler.py +++ b/nova/api/ec2/metadatarequesthandler.py @@ -65,7 +65,7 @@ class MetadataRequestHandler(object): cc = cloud.CloudController() meta_data = cc.get_metadata(req.remote_addr) if meta_data is None: - logging.error('Failed to get metadata for ip: %s' % + logging.error(_('Failed to get metadata for ip: %s') % req.remote_addr) raise webob.exc.HTTPNotFound() data = self.lookup(req.path_info, meta_data) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index b9ecbd9b8d..de95ee548a 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -66,7 +66,7 @@ class API(wsgi.Middleware): try: return req.get_response(self.application) except Exception as ex: - logging.warn("Caught error: %s" % str(ex)) + logging.warn(_("Caught error: %s") % str(ex)) logging.debug(traceback.format_exc()) exc = webob.exc.HTTPInternalServerError(explanation=str(ex)) return faults.Fault(exc) @@ -133,7 +133,7 @@ class RateLimitingMiddleware(wsgi.Middleware): if delay: # TODO(gundlach): Get the retry-after format correct. exc = webob.exc.HTTPRequestEntityTooLarge( - explanation='Too many requests.', + explanation=_('Too many requests.'), headers={'Retry-After': time.time() + delay}) raise faults.Fault(exc) return self.application @@ -170,9 +170,16 @@ class APIRouter(wsgi.Router): def __init__(self): mapper = routes.Mapper() + + server_members = {'action': 'POST'} + if FLAGS.allow_admin_api: + logging.debug("Including admin operations in API.") + server_members['pause'] = 'POST' + server_members['unpause'] = 'POST' + mapper.resource("server", "servers", controller=servers.Controller(), collection={'detail': 'GET'}, - member={'action': 'POST'}) + member=server_members) mapper.resource("backup_schedule", "backup_schedules", controller=backup_schedules.Controller(), @@ -186,10 +193,6 @@ class APIRouter(wsgi.Router): 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/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index 3ed691d7b0..fc70b5c6c8 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -24,6 +24,7 @@ import nova.image.service class Controller(wsgi.Controller): + def __init__(self): pass diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 7704f48f18..5c3322f7ca 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -15,6 +15,9 @@ # License for the specific language governing permissions and limitations # under the License. +import logging +import traceback + from webob import exc from nova import exception @@ -27,6 +30,10 @@ from nova.compute import power_state import nova.api.openstack +LOG = logging.getLogger('server') +LOG.setLevel(logging.DEBUG) + + def _entity_list(entities): """ Coerces a list of servers into proper dictionary format """ return dict(servers=entities) @@ -166,3 +173,25 @@ class Controller(wsgi.Controller): except: return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + + def pause(self, req, id): + """ Permit Admins to Pause the server. """ + ctxt = req.environ['nova.context'] + try: + self.compute_api.pause(ctxt, id) + except: + readable = traceback.format_exc() + logging.error("Compute.api::pause %s", readable) + return faults.Fault(exc.HTTPUnprocessableEntity()) + return exc.HTTPAccepted() + + def unpause(self, req, id): + """ Permit Admins to Unpause the server. """ + ctxt = req.environ['nova.context'] + try: + self.compute_api.unpause(ctxt, id) + except: + readable = traceback.format_exc() + logging.error("Compute.api::unpause %s", readable) + return faults.Fault(exc.HTTPUnprocessableEntity()) + return exc.HTTPAccepted() diff --git a/nova/auth/dbdriver.py b/nova/auth/dbdriver.py index a1584322b8..47e435cb68 100644 --- a/nova/auth/dbdriver.py +++ b/nova/auth/dbdriver.py @@ -37,7 +37,6 @@ class DbDriver(object): def __init__(self): """Imports the LDAP module""" pass - db def __enter__(self): return self @@ -83,7 +82,7 @@ class DbDriver(object): user_ref = db.user_create(context.get_admin_context(), values) return self._db_user_to_auth_user(user_ref) except exception.Duplicate, e: - raise exception.Duplicate('User %s already exists' % name) + raise exception.Duplicate(_('User %s already exists') % name) def _db_user_to_auth_user(self, user_ref): return {'id': user_ref['id'], @@ -105,8 +104,9 @@ class DbDriver(object): """Create a project""" manager = db.user_get(context.get_admin_context(), manager_uid) if not manager: - raise exception.NotFound("Project can't be created because " - "manager %s doesn't exist" % manager_uid) + raise exception.NotFound(_("Project can't be created because " + "manager %s doesn't exist") + % manager_uid) # description is a required attribute if description is None: @@ -133,8 +133,8 @@ class DbDriver(object): try: project = db.project_create(context.get_admin_context(), values) except exception.Duplicate: - raise exception.Duplicate("Project can't be created because " - "project %s already exists" % name) + raise exception.Duplicate(_("Project can't be created because " + "project %s already exists") % name) for member in members: db.project_add_member(context.get_admin_context(), @@ -155,8 +155,8 @@ class DbDriver(object): if manager_uid: manager = db.user_get(context.get_admin_context(), manager_uid) if not manager: - raise exception.NotFound("Project can't be modified because " - "manager %s doesn't exist" % + raise exception.NotFound(_("Project can't be modified because " + "manager %s doesn't exist") % manager_uid) values['project_manager'] = manager['id'] if description: @@ -243,8 +243,8 @@ class DbDriver(object): def _validate_user_and_project(self, user_id, project_id): user = db.user_get(context.get_admin_context(), user_id) if not user: - raise exception.NotFound('User "%s" not found' % user_id) + raise exception.NotFound(_('User "%s" not found') % user_id) project = db.project_get(context.get_admin_context(), project_id) if not project: - raise exception.NotFound('Project "%s" not found' % project_id) + raise exception.NotFound(_('Project "%s" not found') % project_id) return user, project diff --git a/nova/auth/fakeldap.py b/nova/auth/fakeldap.py index 46e0135b4c..33cd03430f 100644 --- a/nova/auth/fakeldap.py +++ b/nova/auth/fakeldap.py @@ -15,7 +15,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -"""Fake LDAP server for test harness, backs to ReDIS. +"""Fake LDAP server for test harness. This class does very little error checking, and knows nothing about ldap class definitions. It implements the minimum emulation of the python ldap @@ -23,34 +23,65 @@ library to work with nova. """ +import fnmatch import json -import redis - -from nova import flags - -FLAGS = flags.FLAGS -flags.DEFINE_string('redis_host', '127.0.0.1', - 'Host that redis is running on.') -flags.DEFINE_integer('redis_port', 6379, - 'Port that redis is running on.') -flags.DEFINE_integer('redis_db', 0, 'Multiple DB keeps tests away') -class Redis(object): +class Store(object): def __init__(self): if hasattr(self.__class__, '_instance'): - raise Exception('Attempted to instantiate singleton') + raise Exception(_('Attempted to instantiate singleton')) @classmethod def instance(cls): if not hasattr(cls, '_instance'): - inst = redis.Redis(host=FLAGS.redis_host, - port=FLAGS.redis_port, - db=FLAGS.redis_db) - cls._instance = inst + cls._instance = _StorageDict() return cls._instance +class _StorageDict(dict): + def keys(self, pat=None): + ret = super(_StorageDict, self).keys() + if pat is not None: + ret = fnmatch.filter(ret, pat) + return ret + + def delete(self, key): + try: + del self[key] + except KeyError: + pass + + def flushdb(self): + self.clear() + + def hgetall(self, key): + """Returns the hash for the given key; creates + the hash if the key doesn't exist.""" + try: + return self[key] + except KeyError: + self[key] = {} + return self[key] + + def hget(self, key, field): + hashdict = self.hgetall(key) + try: + return hashdict[field] + except KeyError: + hashdict[field] = {} + return hashdict[field] + + def hset(self, key, field, val): + hashdict = self.hgetall(key) + hashdict[field] = val + + def hmset(self, key, value_dict): + hashdict = self.hgetall(key) + for field, val in value_dict.items(): + hashdict[field] = val + + SCOPE_BASE = 0 SCOPE_ONELEVEL = 1 # Not implemented SCOPE_SUBTREE = 2 @@ -169,8 +200,6 @@ def _to_json(unencoded): class FakeLDAP(object): - #TODO(vish): refactor this class to use a wrapper instead of accessing - # redis directly """Fake LDAP connection.""" def simple_bind_s(self, dn, password): @@ -183,14 +212,13 @@ class FakeLDAP(object): def add_s(self, dn, attr): """Add an object with the specified attributes at dn.""" - key = "%s%s" % (self.__redis_prefix, dn) - + key = "%s%s" % (self.__prefix, dn) value_dict = dict([(k, _to_json(v)) for k, v in attr]) - Redis.instance().hmset(key, value_dict) + Store.instance().hmset(key, value_dict) def delete_s(self, dn): """Remove the ldap object at specified dn.""" - Redis.instance().delete("%s%s" % (self.__redis_prefix, dn)) + Store.instance().delete("%s%s" % (self.__prefix, dn)) def modify_s(self, dn, attrs): """Modify the object at dn using the attribute list. @@ -201,18 +229,18 @@ class FakeLDAP(object): ([MOD_ADD | MOD_DELETE | MOD_REPACE], attribute, value) """ - redis = Redis.instance() - key = "%s%s" % (self.__redis_prefix, dn) + store = Store.instance() + key = "%s%s" % (self.__prefix, dn) for cmd, k, v in attrs: - values = _from_json(redis.hget(key, k)) + values = _from_json(store.hget(key, k)) if cmd == MOD_ADD: values.append(v) elif cmd == MOD_REPLACE: values = [v] else: values.remove(v) - values = redis.hset(key, k, _to_json(values)) + values = store.hset(key, k, _to_json(values)) def search_s(self, dn, scope, query=None, fields=None): """Search for all matching objects under dn using the query. @@ -226,16 +254,17 @@ class FakeLDAP(object): """ if scope != SCOPE_BASE and scope != SCOPE_SUBTREE: raise NotImplementedError(str(scope)) - redis = Redis.instance() + store = Store.instance() if scope == SCOPE_BASE: - keys = ["%s%s" % (self.__redis_prefix, dn)] + keys = ["%s%s" % (self.__prefix, dn)] else: - keys = redis.keys("%s*%s" % (self.__redis_prefix, dn)) + keys = store.keys("%s*%s" % (self.__prefix, dn)) + objects = [] for key in keys: - # get the attributes from redis - attrs = redis.hgetall(key) - # turn the values from redis into lists + # get the attributes from the store + attrs = store.hgetall(key) + # turn the values from the store into lists # pylint: disable-msg=E1103 attrs = dict([(k, _from_json(v)) for k, v in attrs.iteritems()]) @@ -244,13 +273,13 @@ class FakeLDAP(object): # filter the attributes by fields attrs = dict([(k, v) for k, v in attrs.iteritems() if not fields or k in fields]) - objects.append((key[len(self.__redis_prefix):], attrs)) + objects.append((key[len(self.__prefix):], attrs)) # pylint: enable-msg=E1103 if objects == []: raise NO_SUCH_OBJECT() return objects @property - def __redis_prefix(self): # pylint: disable-msg=R0201 - """Get the prefix to use for all redis keys.""" + def __prefix(self): # pylint: disable-msg=R0201 + """Get the prefix to use for all keys.""" return 'ldap:' diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index c10939d743..e289ea5a26 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("LDAP object for %s doesn't exist" + raise exception.NotFound(_("LDAP object for %s doesn't exist") % name) else: attr = [ @@ -182,11 +182,12 @@ class LdapDriver(object): description=None, member_uids=None): """Create a project""" if self.__project_exists(name): - raise exception.Duplicate("Project can't be created because " - "project %s already exists" % name) + raise exception.Duplicate(_("Project can't be created because " + "project %s already exists") % name) if not self.__user_exists(manager_uid): - raise exception.NotFound("Project can't be created because " - "manager %s doesn't exist" % manager_uid) + raise exception.NotFound(_("Project can't be created because " + "manager %s doesn't exist") + % manager_uid) manager_dn = self.__uid_to_dn(manager_uid) # description is a required attribute if description is None: @@ -195,8 +196,8 @@ class LdapDriver(object): if member_uids is not None: for member_uid in member_uids: if not self.__user_exists(member_uid): - raise exception.NotFound("Project can't be created " - "because user %s doesn't exist" + raise exception.NotFound(_("Project can't be created " + "because user %s doesn't exist") % member_uid) members.append(self.__uid_to_dn(member_uid)) # always add the manager as a member because members is required @@ -218,9 +219,9 @@ class LdapDriver(object): attr = [] if manager_uid: if not self.__user_exists(manager_uid): - raise exception.NotFound("Project can't be modified because " - "manager %s doesn't exist" % - manager_uid) + raise exception.NotFound(_("Project can't be modified because " + "manager %s doesn't exist") + % manager_uid) manager_dn = self.__uid_to_dn(manager_uid) attr.append((self.ldap.MOD_REPLACE, 'projectManager', manager_dn)) if description: @@ -416,8 +417,9 @@ class LdapDriver(object): if member_uids is not None: for member_uid in member_uids: if not self.__user_exists(member_uid): - raise exception.NotFound("Group can't be created " - "because user %s doesn't exist" % member_uid) + raise exception.NotFound(_("Group can't be created " + "because user %s doesn't exist") + % member_uid) members.append(self.__uid_to_dn(member_uid)) dn = self.__uid_to_dn(uid) if not dn in members: @@ -432,8 +434,9 @@ class LdapDriver(object): def __is_in_group(self, uid, group_dn): """Check if user is in group""" if not self.__user_exists(uid): - raise exception.NotFound("User %s can't be searched in group " - "becuase the user doesn't exist" % (uid,)) + raise exception.NotFound(_("User %s can't be searched in group " + "because the user doesn't exist") + % uid) if not self.__group_exists(group_dn): return False res = self.__find_object(group_dn, @@ -444,28 +447,30 @@ class LdapDriver(object): def __add_to_group(self, uid, group_dn): """Add user to group""" if not self.__user_exists(uid): - raise exception.NotFound("User %s can't be added to the group " - "becuase the user doesn't exist" % (uid,)) + raise exception.NotFound(_("User %s can't be added to the group " + "because the user doesn't exist") + % uid) if not self.__group_exists(group_dn): - raise exception.NotFound("The group at dn %s doesn't exist" % - (group_dn,)) + raise exception.NotFound(_("The group at dn %s doesn't exist") + % group_dn) if self.__is_in_group(uid, group_dn): - raise exception.Duplicate("User %s is already a member of " - "the group %s" % (uid, group_dn)) + raise exception.Duplicate(_("User %s is already a member of " + "the group %s") % (uid, group_dn)) attr = [(self.ldap.MOD_ADD, 'member', self.__uid_to_dn(uid))] self.conn.modify_s(group_dn, attr) def __remove_from_group(self, uid, group_dn): """Remove user from group""" if not self.__group_exists(group_dn): - raise exception.NotFound("The group at dn %s doesn't exist" % - (group_dn,)) + raise exception.NotFound(_("The group at dn %s doesn't exist") + % group_dn) if not self.__user_exists(uid): - raise exception.NotFound("User %s can't be removed from the " - "group because the user doesn't exist" % (uid,)) + raise exception.NotFound(_("User %s can't be removed from the " + "group because the user doesn't exist") + % uid) if not self.__is_in_group(uid, group_dn): - raise exception.NotFound("User %s is not a member of the group" % - (uid,)) + raise exception.NotFound(_("User %s is not a member of the group") + % uid) # NOTE(vish): remove user from group and any sub_groups sub_dns = self.__find_group_dns_with_member( group_dn, uid) @@ -479,15 +484,16 @@ class LdapDriver(object): try: self.conn.modify_s(group_dn, attr) except self.ldap.OBJECT_CLASS_VIOLATION: - logging.debug("Attempted to remove the last member of a group. " - "Deleting the group at %s instead.", group_dn) + logging.debug(_("Attempted to remove the last member of a group. " + "Deleting the group at %s instead."), group_dn) self.__delete_group(group_dn) def __remove_from_all(self, uid): """Remove user from all roles and projects""" if not self.__user_exists(uid): - raise exception.NotFound("User %s can't be removed from all " - "because the user doesn't exist" % (uid,)) + raise exception.NotFound(_("User %s can't be removed from all " + "because the user doesn't exist") + % uid) role_dns = self.__find_group_dns_with_member( FLAGS.role_project_subtree, uid) for role_dn in role_dns: @@ -500,7 +506,8 @@ class LdapDriver(object): def __delete_group(self, group_dn): """Delete Group""" if not self.__group_exists(group_dn): - raise exception.NotFound("Group at dn %s doesn't exist" % group_dn) + raise exception.NotFound(_("Group at dn %s doesn't exist") + % group_dn) self.conn.delete_s(group_dn) def __delete_roles(self, project_dn): diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 11c3bd6dfb..417f2b76dc 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -257,12 +257,12 @@ class AuthManager(object): # TODO(vish): check for valid timestamp (access_key, _sep, project_id) = access.partition(':') - logging.info('Looking up user: %r', access_key) + logging.info(_('Looking up user: %r'), access_key) user = self.get_user_from_access_key(access_key) logging.info('user: %r', user) if user == None: - raise exception.NotFound('No user found for access key %s' % - access_key) + raise exception.NotFound(_('No user found for access key %s') + % access_key) # NOTE(vish): if we stop using project name as id we need better # logic to find a default project for user @@ -271,12 +271,12 @@ class AuthManager(object): project = self.get_project(project_id) if project == None: - raise exception.NotFound('No project called %s could be found' % - project_id) + raise exception.NotFound(_('No project called %s could be found') + % project_id) if not self.is_admin(user) and not self.is_project_member(user, project): - raise exception.NotFound('User %s is not a member of project %s' % - (user.id, project.id)) + raise exception.NotFound(_('User %s is not a member of project %s') + % (user.id, project.id)) if check_type == 's3': sign = signer.Signer(user.secret.encode()) expected_signature = sign.s3_authorization(headers, verb, path) @@ -284,7 +284,7 @@ class AuthManager(object): logging.debug('expected_signature: %s', expected_signature) logging.debug('signature: %s', signature) if signature != expected_signature: - raise exception.NotAuthorized('Signature does not match') + raise exception.NotAuthorized(_('Signature does not match')) elif check_type == 'ec2': # NOTE(vish): hmac can't handle unicode, so encode ensures that # secret isn't unicode @@ -294,7 +294,7 @@ class AuthManager(object): logging.debug('expected_signature: %s', expected_signature) logging.debug('signature: %s', signature) if signature != expected_signature: - raise exception.NotAuthorized('Signature does not match') + raise exception.NotAuthorized(_('Signature does not match')) return (user, project) def get_access_key(self, user, project): @@ -364,7 +364,7 @@ class AuthManager(object): with self.driver() as drv: if role == 'projectmanager': if not project: - raise exception.Error("Must specify project") + raise exception.Error(_("Must specify project")) return self.is_project_manager(user, project) global_role = drv.has_role(User.safe_id(user), @@ -398,9 +398,9 @@ class AuthManager(object): @param project: Project in which to add local role. """ if role not in FLAGS.allowed_roles: - raise exception.NotFound("The %s role can not be found" % role) + raise exception.NotFound(_("The %s role can not be found") % role) if project is not None and role in FLAGS.global_roles: - raise exception.NotFound("The %s role is global only" % role) + raise exception.NotFound(_("The %s role is global only") % role) with self.driver() as drv: drv.add_role(User.safe_id(user), role, Project.safe_id(project)) @@ -546,7 +546,8 @@ class AuthManager(object): Project.safe_id(project)) if not network_ref['vpn_public_port']: - raise exception.NotFound('project network data has not been set') + raise exception.NotFound(_('project network data has not ' + 'been set')) return (network_ref['vpn_public_address'], network_ref['vpn_public_port']) @@ -659,8 +660,7 @@ class AuthManager(object): port=vpn_port) zippy.writestr(FLAGS.credential_vpn_file, config) else: - logging.warn("No vpn data for project %s" % - pid) + logging.warn(_("No vpn data for project %s"), pid) zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(user.id)) zippy.close() diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py index 3472201cdf..bbe91a70c0 100644 --- a/nova/cloudpipe/pipelib.py +++ b/nova/cloudpipe/pipelib.py @@ -49,7 +49,7 @@ class CloudPipe(object): self.manager = manager.AuthManager() def launch_vpn_instance(self, project_id): - logging.debug("Launching VPN for %s" % (project_id)) + logging.debug(_("Launching VPN for %s") % (project_id)) project = self.manager.get_project(project_id) # Make a payload.zip tmpfolder = tempfile.mkdtemp() diff --git a/nova/compute/api.py b/nova/compute/api.py index 8e0efa4cc7..a33ed2dc47 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -73,14 +73,19 @@ class ComputeAPI(base.Base): is_vpn = image_id == FLAGS.vpn_image_id if not is_vpn: image = self.image_service.show(context, image_id) + + # If kernel_id/ramdisk_id isn't explicitly set in API call + # we take the defaults from the image's metadata if kernel_id is None: - kernel_id = image.get('kernelId', FLAGS.default_kernel) + kernel_id = image.get('kernelId', None) if ramdisk_id is None: - ramdisk_id = image.get('ramdiskId', FLAGS.default_ramdisk) + ramdisk_id = image.get('ramdiskId', None) # Make sure we have access to kernel and ramdisk - self.image_service.show(context, kernel_id) - self.image_service.show(context, ramdisk_id) + if kernel_id: + self.image_service.show(context, kernel_id) + if ramdisk_id: + self.image_service.show(context, ramdisk_id) if security_group is None: security_group = ['default'] @@ -103,8 +108,8 @@ class ComputeAPI(base.Base): base_options = { 'reservation_id': utils.generate_uid('r'), 'image_id': image_id, - 'kernel_id': kernel_id, - 'ramdisk_id': ramdisk_id, + 'kernel_id': kernel_id or '', + 'ramdisk_id': ramdisk_id or '', 'state_description': 'scheduling', 'user_id': context.user_id, 'project_id': context.project_id, @@ -120,7 +125,7 @@ class ComputeAPI(base.Base): elevated = context.elevated() instances = [] - logging.debug("Going to run %s instances...", num_instances) + logging.debug(_("Going to run %s instances..."), num_instances) for num in range(num_instances): instance = dict(mac_address=utils.generate_mac(), launch_index=num, @@ -157,7 +162,7 @@ class ComputeAPI(base.Base): {"method": "setup_fixed_ip", "args": {"address": address}}) - logging.debug("Casting to scheduler for %s/%s's instance %s", + 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, @@ -204,12 +209,12 @@ class ComputeAPI(base.Base): 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", + 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", + logging.warning(_("Instance %d is already being terminated"), instance_id) return @@ -223,7 +228,7 @@ class ComputeAPI(base.Base): address = self.db.instance_get_floating_address(context, instance['id']) if address: - logging.debug("Disassociating address %s" % 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? @@ -234,7 +239,7 @@ class ComputeAPI(base.Base): address = self.db.instance_get_fixed_address(context, instance['id']) if address: - logging.debug("Deallocating address %s" % 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. @@ -275,6 +280,24 @@ class ComputeAPI(base.Base): {"method": "reboot_instance", "args": {"instance_id": instance['id']}}) + def pause(self, context, instance_id): + """Pause 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": "pause_instance", + "args": {"instance_id": instance['id']}}) + + def unpause(self, context, instance_id): + """Unpause 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": "unpause_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) diff --git a/nova/compute/disk.py b/nova/compute/disk.py index 675cd02590..814a258cd6 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -67,12 +67,12 @@ def partition(infile, outfile, local_bytes=0, resize=True, execute('resize2fs %s' % infile) file_size = FLAGS.minimum_root_size elif file_size % sector_size != 0: - logging.warn("Input partition size not evenly divisible by" - " sector size: %d / %d", file_size, sector_size) + logging.warn(_("Input partition size not evenly divisible by" + " sector size: %d / %d"), file_size, sector_size) primary_sectors = file_size / sector_size if local_bytes % sector_size != 0: - logging.warn("Bytes for local storage not evenly divisible" - " by sector size: %d / %d", local_bytes, sector_size) + logging.warn(_("Bytes for local storage not evenly divisible" + " by sector size: %d / %d"), local_bytes, sector_size) local_sectors = local_bytes / sector_size mbr_last = 62 # a @@ -106,6 +106,13 @@ def partition(infile, outfile, local_bytes=0, resize=True, % (outfile, local_type, local_first, local_last)) +def extend(image, size, execute): + file_size = os.path.getsize(image) + if file_size >= size: + return + return execute('truncate -s size %s' % (image,)) + + def inject_data(image, key=None, net=None, partition=None, execute=None): """Injects a ssh key and optionally net data into a disk image. @@ -115,20 +122,30 @@ def inject_data(image, key=None, net=None, partition=None, execute=None): If partition is not specified it mounts the image as a single partition. """ - out, err = execute('sudo losetup -f --show %s' % image) + out, err = execute('sudo losetup --find --show %s' % image) if err: - raise exception.Error('Could not attach image to loopback: %s' % err) + raise exception.Error(_('Could not attach image to loopback: %s') + % err) device = out.strip() try: if not partition is None: # create partition out, err = execute('sudo kpartx -a %s' % device) if err: - raise exception.Error('Failed to load partition: %s' % err) + raise exception.Error(_('Failed to load partition: %s') % err) mapped_device = '/dev/mapper/%sp%s' % (device.split('/')[-1], partition) else: mapped_device = device + + # We can only loopback mount raw images. If the device isn't there, + # it's normally because it's a .vmdk or a .vdi etc + if not os.path.exists(mapped_device): + raise exception.Error('Mapped device was not found (we can' + ' only inject raw disk images): %s' % + mapped_device) + + # Configure ext2fs so that it doesn't auto-check every N boots out, err = execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device) tmpdir = tempfile.mkdtemp() @@ -137,7 +154,8 @@ def inject_data(image, key=None, net=None, partition=None, execute=None): out, err = execute( 'sudo mount %s %s' % (mapped_device, tmpdir)) if err: - raise exception.Error('Failed to mount filesystem: %s' % err) + raise exception.Error(_('Failed to mount filesystem: %s') + % err) try: if key: @@ -156,7 +174,7 @@ def inject_data(image, key=None, net=None, partition=None, execute=None): execute('sudo kpartx -d %s' % device) finally: # remove loopback - execute('sudo losetup -d %s' % device) + execute('sudo losetup --detach %s' % device) def _inject_key_into_fs(key, fs, execute=None): @@ -165,7 +183,7 @@ def _inject_key_into_fs(key, fs, execute=None): key is an ssh key string. fs is the path to the base of the filesystem into which to inject the key. """ - sshdir = os.path.join(os.path.join(fs, 'root'), '.ssh') + sshdir = os.path.join(fs, 'root', '.ssh') execute('sudo mkdir -p %s' % sshdir) # existing dir doesn't matter execute('sudo chown root %s' % sshdir) execute('sudo chmod 700 %s' % sshdir) diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index 6e47170bde..196d6a8dfa 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -38,7 +38,8 @@ 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 exception.ApiError(_("Unknown instance type: %s"), + instance_type) return instance_type diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 59d4fb29d4..cc607f9d46 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -93,8 +93,8 @@ class ComputeManager(manager.Manager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) if instance_ref['name'] in self.driver.list_instances(): - raise exception.Error("Instance has already been created") - logging.debug("instance %s: starting...", instance_id) + raise exception.Error(_("Instance has already been created")) + logging.debug(_("instance %s: starting..."), instance_id) self.network_manager.setup_compute_network(context, instance_id) self.db.instance_update(context, instance_id, @@ -113,7 +113,7 @@ class ComputeManager(manager.Manager): instance_id, {'launched_at': now}) except Exception: # pylint: disable-msg=W0702 - logging.exception("instance %s: Failed to spawn", + logging.exception(_("instance %s: Failed to spawn"), instance_ref['name']) self.db.instance_set_state(context, instance_id, @@ -125,7 +125,7 @@ class ComputeManager(manager.Manager): def terminate_instance(self, context, instance_id): """Terminate an instance on this machine.""" context = context.elevated() - logging.debug("instance %s: terminating", instance_id) + logging.debug(_("instance %s: terminating"), instance_id) instance_ref = self.db.instance_get(context, instance_id) volumes = instance_ref.get('volumes', []) or [] @@ -133,8 +133,8 @@ class ComputeManager(manager.Manager): self.detach_volume(context, instance_id, volume['id']) if instance_ref['state'] == power_state.SHUTOFF: self.db.instance_destroy(context, instance_id) - raise exception.Error('trying to destroy already destroyed' - ' instance: %s' % instance_id) + raise exception.Error(_('trying to destroy already destroyed' + ' instance: %s') % instance_id) self.driver.destroy(instance_ref) # TODO(ja): should we keep it in a terminated state for a bit? @@ -148,13 +148,13 @@ class ComputeManager(manager.Manager): self._update_state(context, instance_id) if instance_ref['state'] != power_state.RUNNING: - logging.warn('trying to reboot a non-running ' - 'instance: %s (state: %s excepted: %s)', + logging.warn(_('trying to reboot a non-running ' + 'instance: %s (state: %s excepted: %s)'), instance_ref['internal_id'], instance_ref['state'], power_state.RUNNING) - logging.debug('instance %s: rebooting', instance_ref['name']) + logging.debug(_('instance %s: rebooting'), instance_ref['name']) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, @@ -168,7 +168,7 @@ class ComputeManager(manager.Manager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - logging.debug('instance %s: rescuing', + logging.debug(_('instance %s: rescuing'), instance_ref['internal_id']) self.db.instance_set_state(context, instance_id, @@ -183,7 +183,7 @@ class ComputeManager(manager.Manager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - logging.debug('instance %s: unrescuing', + logging.debug(_('instance %s: unrescuing'), instance_ref['internal_id']) self.db.instance_set_state(context, instance_id, @@ -192,11 +192,52 @@ class ComputeManager(manager.Manager): self.driver.unrescue(instance_ref) self._update_state(context, instance_id) + @staticmethod + def _update_state_callback(self, context, instance_id, result): + """Update instance state when async task completes.""" + self._update_state(context, instance_id) + + @exception.wrap_exception + def pause_instance(self, context, instance_id): + """Pause an instance on this server.""" + context = context.elevated() + instance_ref = self.db.instance_get(context, instance_id) + + logging.debug('instance %s: pausing', + instance_ref['internal_id']) + self.db.instance_set_state(context, + instance_id, + power_state.NOSTATE, + 'pausing') + self.driver.pause(instance_ref, + lambda result: self._update_state_callback(self, + context, + instance_id, + result)) + + @exception.wrap_exception + def unpause_instance(self, context, instance_id): + """Unpause a paused instance on this server.""" + context = context.elevated() + instance_ref = self.db.instance_get(context, instance_id) + + logging.debug('instance %s: unpausing', + instance_ref['internal_id']) + self.db.instance_set_state(context, + instance_id, + power_state.NOSTATE, + 'unpausing') + self.driver.unpause(instance_ref, + lambda result: self._update_state_callback(self, + context, + instance_id, + result)) + @exception.wrap_exception def get_console_output(self, context, instance_id): """Send the console output for an instance.""" context = context.elevated() - logging.debug("instance %s: getting console output", instance_id) + logging.debug(_("instance %s: getting console output"), instance_id) instance_ref = self.db.instance_get(context, instance_id) return self.driver.get_console_output(instance_ref) @@ -205,7 +246,7 @@ class ComputeManager(manager.Manager): def attach_volume(self, context, instance_id, volume_id, mountpoint): """Attach a volume to an instance.""" context = context.elevated() - logging.debug("instance %s: attaching volume %s to %s", instance_id, + logging.debug(_("instance %s: attaching volume %s to %s"), instance_id, volume_id, mountpoint) instance_ref = self.db.instance_get(context, instance_id) dev_path = self.volume_manager.setup_compute_volume(context, @@ -222,7 +263,7 @@ class ComputeManager(manager.Manager): # NOTE(vish): The inline callback eats the exception info so we # log the traceback here and reraise the same # ecxception below. - logging.exception("instance %s: attach failed %s, removing", + logging.exception(_("instance %s: attach failed %s, removing"), instance_id, mountpoint) self.volume_manager.remove_compute_volume(context, volume_id) @@ -234,13 +275,13 @@ class ComputeManager(manager.Manager): def detach_volume(self, context, instance_id, volume_id): """Detach a volume from an instance.""" context = context.elevated() - logging.debug("instance %s: detaching volume %s", + logging.debug(_("instance %s: detaching volume %s"), instance_id, volume_id) instance_ref = self.db.instance_get(context, instance_id) volume_ref = self.db.volume_get(context, volume_id) if instance_ref['name'] not in self.driver.list_instances(): - logging.warn("Detaching volume from unknown instance %s", + logging.warn(_("Detaching volume from unknown instance %s"), instance_ref['name']) else: self.driver.detach_volume(instance_ref['name'], diff --git a/nova/compute/monitor.py b/nova/compute/monitor.py index 22653113a6..60c347a5ea 100644 --- a/nova/compute/monitor.py +++ b/nova/compute/monitor.py @@ -255,7 +255,7 @@ class Instance(object): Updates the instances statistics and stores the resulting graphs in the internal object store on the cloud controller. """ - logging.debug('updating %s...', self.instance_id) + logging.debug(_('updating %s...'), self.instance_id) try: data = self.fetch_cpu_stats() @@ -285,7 +285,7 @@ class Instance(object): graph_disk(self, '1w') graph_disk(self, '1m') except Exception: - logging.exception('unexpected error during update') + logging.exception(_('unexpected error during update')) self.last_updated = utcnow() @@ -351,7 +351,7 @@ class Instance(object): rd += rd_bytes wr += wr_bytes except TypeError: - logging.error('Cannot get blockstats for "%s" on "%s"', + logging.error(_('Cannot get blockstats for "%s" on "%s"'), disk, self.instance_id) raise @@ -373,7 +373,7 @@ class Instance(object): rx += stats[0] tx += stats[4] except TypeError: - logging.error('Cannot get ifstats for "%s" on "%s"', + logging.error(_('Cannot get ifstats for "%s" on "%s"'), interface, self.instance_id) raise @@ -408,7 +408,7 @@ class InstanceMonitor(object, service.Service): try: conn = virt_connection.get_connection(read_only=True) except Exception, exn: - logging.exception('unexpected exception getting connection') + logging.exception(_('unexpected exception getting connection')) time.sleep(FLAGS.monitoring_instances_delay) return @@ -423,7 +423,7 @@ class InstanceMonitor(object, service.Service): if not domain_id in self._instances: instance = Instance(conn, domain_id) self._instances[domain_id] = instance - logging.debug('Found instance: %s', domain_id) + logging.debug(_('Found instance: %s'), domain_id) for key in self._instances.keys(): instance = self._instances[key] diff --git a/nova/crypto.py b/nova/crypto.py index aacc50b179..af4a06a0c8 100644 --- a/nova/crypto.py +++ b/nova/crypto.py @@ -39,13 +39,13 @@ from nova import flags FLAGS = flags.FLAGS -flags.DEFINE_string('ca_file', 'cacert.pem', 'Filename of root CA') +flags.DEFINE_string('ca_file', 'cacert.pem', _('Filename of root CA')) flags.DEFINE_string('keys_path', '$state_path/keys', - 'Where we keep our keys') + _('Where we keep our keys')) flags.DEFINE_string('ca_path', '$state_path/CA', - 'Where we keep our root CA') + _('Where we keep our root CA')) flags.DEFINE_boolean('use_intermediate_ca', False, - 'Should we use intermediate CAs for each project?') + _('Should we use intermediate CAs for each project?')) def ca_path(project_id): @@ -111,9 +111,9 @@ def generate_x509_cert(subject, bits=1024): keyfile = os.path.abspath(os.path.join(tmpdir, 'temp.key')) csrfile = os.path.join(tmpdir, 'temp.csr') logging.debug("openssl genrsa -out %s %s" % (keyfile, bits)) - utils.runthis("Generating private key: %s", + utils.runthis(_("Generating private key: %s"), "openssl genrsa -out %s %s" % (keyfile, bits)) - utils.runthis("Generating CSR: %s", + utils.runthis(_("Generating CSR: %s"), "openssl req -new -key %s -out %s -batch -subj %s" % (keyfile, csrfile, subject)) private_key = open(keyfile).read() @@ -131,7 +131,7 @@ def sign_csr(csr_text, intermediate=None): if not os.path.exists(user_ca): start = os.getcwd() os.chdir(FLAGS.ca_path) - utils.runthis("Generating intermediate CA: %s", + utils.runthis(_("Generating intermediate CA: %s"), "sh geninter.sh %s" % (intermediate)) os.chdir(start) return _sign_csr(csr_text, user_ca) @@ -142,11 +142,11 @@ def _sign_csr(csr_text, ca_folder): csrfile = open("%s/inbound.csr" % (tmpfolder), "w") csrfile.write(csr_text) csrfile.close() - logging.debug("Flags path: %s" % ca_folder) + logging.debug(_("Flags path: %s") % ca_folder) start = os.getcwd() # Change working dir to CA os.chdir(ca_folder) - utils.runthis("Signing cert: %s", + utils.runthis(_("Signing cert: %s"), "openssl ca -batch -out %s/outbound.crt " "-config ./openssl.cnf -infiles %s/inbound.csr" % (tmpfolder, tmpfolder)) diff --git a/nova/db/api.py b/nova/db/api.py index 8f9dc24430..4e15596d9a 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -334,6 +334,11 @@ def instance_add_security_group(context, instance_id, security_group_id): security_group_id) +def instance_action_create(context, values): + """Create an instance action from the values dictionary.""" + return IMPL.instance_action_create(context, values) + + ################### diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 55036d1d10..a36f767a77 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -41,7 +41,7 @@ FLAGS = flags.FLAGS def is_admin_context(context): """Indicates if the request context is an administrator.""" if not context: - warnings.warn('Use of empty request context is deprecated', + warnings.warn(_('Use of empty request context is deprecated'), DeprecationWarning) raise Exception('die') return context.is_admin @@ -130,7 +130,7 @@ def service_get(context, service_id, session=None): first() if not result: - raise exception.NotFound('No service for id %s' % service_id) + raise exception.NotFound(_('No service for id %s') % service_id) return result @@ -227,7 +227,7 @@ def service_get_by_args(context, host, binary): filter_by(deleted=can_read_deleted(context)).\ first() if not result: - raise exception.NotFound('No service for %s, %s' % (host, binary)) + raise exception.NotFound(_('No service for %s, %s') % (host, binary)) return result @@ -491,7 +491,7 @@ def fixed_ip_get_by_address(context, address, session=None): options(joinedload('instance')).\ first() if not result: - raise exception.NotFound('No floating ip for address %s' % address) + raise exception.NotFound(_('No floating ip for address %s') % address) if is_user_context(context): authorize_project_context(context, result.instance.project_id) @@ -528,6 +528,8 @@ def fixed_ip_update(context, address, values): #TODO(gundlach): instance_create and volume_create are nearly identical #and should be refactored. I expect there are other copy-and-paste #functions between the two of them as well. + + @require_context def instance_create(context, values): """Create a new Instance record in the database. @@ -591,7 +593,7 @@ def instance_get(context, instance_id, session=None): filter_by(deleted=False).\ first() if not result: - raise exception.NotFound('No instance for id %s' % instance_id) + raise exception.NotFound(_('No instance for id %s') % instance_id) return result @@ -669,7 +671,7 @@ def instance_get_by_internal_id(context, internal_id): filter_by(deleted=False).\ first() if not result: - raise exception.NotFound('Instance %s not found' % (internal_id)) + raise exception.NotFound(_('Instance %s not found') % (internal_id)) return result @@ -747,6 +749,18 @@ def instance_add_security_group(context, instance_id, security_group_id): instance_ref.save(session=session) +@require_context +def instance_action_create(context, values): + """Create an instance action from the values dictionary.""" + action_ref = models.InstanceActions() + action_ref.update(values) + + session = get_session() + with session.begin(): + action_ref.save(session=session) + return action_ref + + ################### @@ -790,7 +804,7 @@ def key_pair_get(context, user_id, name, session=None): filter_by(deleted=can_read_deleted(context)).\ first() if not result: - raise exception.NotFound('no keypair for user %s, name %s' % + raise exception.NotFound(_('no keypair for user %s, name %s') % (user_id, name)) return result @@ -905,7 +919,7 @@ def network_get(context, network_id, session=None): filter_by(deleted=False).\ first() if not result: - raise exception.NotFound('No network for id %s' % network_id) + raise exception.NotFound(_('No network for id %s') % network_id) return result @@ -913,6 +927,8 @@ def network_get(context, network_id, session=None): # NOTE(vish): pylint complains because of the long method name, but # it fits with the names of the rest of the methods # pylint: disable-msg=C0103 + + @require_admin_context def network_get_associated_fixed_ips(context, network_id): session = get_session() @@ -933,7 +949,7 @@ def network_get_by_bridge(context, bridge): first() if not result: - raise exception.NotFound('No network for bridge %s' % bridge) + raise exception.NotFound(_('No network for bridge %s') % bridge) return result @@ -947,7 +963,7 @@ def network_get_by_instance(_context, instance_id): filter_by(deleted=False).\ first() if not rv: - raise exception.NotFound('No network for instance %s' % instance_id) + raise exception.NotFound(_('No network for instance %s') % instance_id) return rv @@ -961,7 +977,7 @@ def network_set_host(context, network_id, host_id): with_lockmode('update').\ first() if not network_ref: - raise exception.NotFound('No network for id %s' % network_id) + raise exception.NotFound(_('No network for id %s') % network_id) # NOTE(vish): if with_lockmode isn't supported, as in sqlite, # then this has concurrency issues @@ -1073,7 +1089,7 @@ def auth_get_token(_context, token_hash): filter_by(token_hash=token_hash).\ first() if not tk: - raise exception.NotFound('Token %s does not exist' % token_hash) + raise exception.NotFound(_('Token %s does not exist') % token_hash) return tk @@ -1097,7 +1113,7 @@ def quota_get(context, project_id, session=None): filter_by(deleted=can_read_deleted(context)).\ first() if not result: - raise exception.NotFound('No quota for project_id %s' % project_id) + raise exception.NotFound(_('No quota for project_id %s') % project_id) return result @@ -1252,7 +1268,7 @@ def volume_get(context, volume_id, session=None): filter_by(deleted=False).\ first() if not result: - raise exception.NotFound('No volume for id %s' % volume_id) + raise exception.NotFound(_('No volume for id %s') % volume_id) return result @@ -1308,7 +1324,7 @@ def volume_get_by_ec2_id(context, ec2_id): raise exception.NotAuthorized() if not result: - raise exception.NotFound('Volume %s not found' % ec2_id) + raise exception.NotFound(_('Volume %s not found') % ec2_id) return result @@ -1332,7 +1348,7 @@ def volume_get_instance(context, volume_id): options(joinedload('instance')).\ first() if not result: - raise exception.NotFound('Volume %s not found' % ec2_id) + raise exception.NotFound(_('Volume %s not found') % ec2_id) return result.instance @@ -1344,7 +1360,7 @@ def volume_get_shelf_and_blade(context, volume_id): filter_by(volume_id=volume_id).\ first() if not result: - raise exception.NotFound('No export device found for volume %s' % + raise exception.NotFound(_('No export device found for volume %s') % volume_id) return (result.shelf_id, result.blade_id) @@ -1357,7 +1373,7 @@ def volume_get_iscsi_target_num(context, volume_id): filter_by(volume_id=volume_id).\ first() if not result: - raise exception.NotFound('No target id found for volume %s' % + raise exception.NotFound(_('No target id found for volume %s') % volume_id) return result.target_num @@ -1402,7 +1418,7 @@ def security_group_get(context, security_group_id, session=None): options(joinedload_all('rules')).\ first() if not result: - raise exception.NotFound("No secuity group with id %s" % + raise exception.NotFound(_("No security group with id %s") % security_group_id) return result @@ -1419,7 +1435,7 @@ def security_group_get_by_name(context, project_id, group_name): first() if not result: raise exception.NotFound( - 'No security group named %s for project: %s' \ + _('No security group named %s for project: %s') % (group_name, project_id)) return result @@ -1507,7 +1523,7 @@ def security_group_rule_get(context, security_group_rule_id, session=None): filter_by(id=security_group_rule_id).\ first() if not result: - raise exception.NotFound("No secuity group rule with id %s" % + raise exception.NotFound(_("No secuity group rule with id %s") % security_group_rule_id) return result @@ -1543,7 +1559,7 @@ def user_get(context, id, session=None): first() if not result: - raise exception.NotFound('No user for id %s' % id) + raise exception.NotFound(_('No user for id %s') % id) return result @@ -1559,7 +1575,7 @@ def user_get_by_access_key(context, access_key, session=None): first() if not result: - raise exception.NotFound('No user for access key %s' % access_key) + raise exception.NotFound(_('No user for access key %s') % access_key) return result @@ -1621,7 +1637,7 @@ def project_get(context, id, session=None): first() if not result: - raise exception.NotFound("No project with id %s" % id) + raise exception.NotFound(_("No project with id %s") % id) return result diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index fe0a9a9216..eac6a304e9 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -22,7 +22,7 @@ SQLAlchemy models for nova data. import datetime from sqlalchemy.orm import relationship, backref, object_mapper -from sqlalchemy import Column, Integer, String, schema +from sqlalchemy import Column, Integer, Float, String, schema from sqlalchemy import ForeignKey, DateTime, Boolean, Text from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.declarative import declarative_base @@ -226,6 +226,31 @@ class Instance(BASE, NovaBase): # 'shutdown', 'shutoff', 'crashed']) +class InstanceDiagnostics(BASE, NovaBase): + """Represents a guest VM's diagnostics""" + __tablename__ = "instance_diagnostics" + id = Column(Integer, primary_key=True) + instance_id = Column(Integer, ForeignKey('instances.id')) + + memory_available = Column(Float) + memory_free = Column(Float) + cpu_load = Column(Float) + disk_read = Column(Float) + disk_write = Column(Float) + net_tx = Column(Float) + net_rx = Column(Float) + + +class InstanceActions(BASE, NovaBase): + """Represents a guest VM's actions and results""" + __tablename__ = "instance_actions" + id = Column(Integer, primary_key=True) + instance_id = Column(Integer, ForeignKey('instances.id')) + + action = Column(String(255)) + error = Column(Text) + + class Volume(BASE, NovaBase): """Represents a block storage device that can be attached to a vm.""" __tablename__ = 'volumes' @@ -526,10 +551,11 @@ def register_models(): it will never need to be called explicitly elsewhere. """ from sqlalchemy import create_engine - models = (Service, Instance, Volume, ExportDevice, IscsiTarget, FixedIp, - FloatingIp, Network, SecurityGroup, - SecurityGroupIngressRule, SecurityGroupInstanceAssociation, - AuthToken, User, Project) # , Image, Host + models = (Service, Instance, InstanceDiagnostics, InstanceActions, + Volume, ExportDevice, IscsiTarget, FixedIp, FloatingIp, + Network, SecurityGroup, SecurityGroupIngressRule, + SecurityGroupInstanceAssociation, AuthToken, User, + Project) # , Image, Host engine = create_engine(FLAGS.sql_connection, echo=False) for model in models: model.metadata.create_all(engine) diff --git a/nova/exception.py b/nova/exception.py index 6d6c373380..277033e0f3 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -27,23 +27,26 @@ import traceback class ProcessExecutionError(IOError): + def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None, description=None): if description is None: - description = "Unexpected error while running command." + description = _("Unexpected error while running command.") if exit_code is None: exit_code = '-' - message = "%s\nCommand: %s\nExit code: %s\nStdout: %r\nStderr: %r" % ( - description, cmd, exit_code, stdout, stderr) + message = _("%s\nCommand: %s\nExit code: %s\nStdout: %r\nStderr: %r")\ + % (description, cmd, exit_code, stdout, stderr) IOError.__init__(self, message) class Error(Exception): + def __init__(self, message=None): super(Error, self).__init__(message) class ApiError(Error): + def __init__(self, message='Unknown', code='Unknown'): self.message = message self.code = code @@ -81,7 +84,7 @@ def wrap_exception(f): except Exception, e: if not isinstance(e, Error): #exc_type, exc_value, exc_traceback = sys.exc_info() - logging.exception('Uncaught exception') + logging.exception(_('Uncaught exception')) #logging.error(traceback.extract_stack(exc_traceback)) raise Error(str(e)) raise diff --git a/nova/fakerabbit.py b/nova/fakerabbit.py index c64617931c..41e686cffe 100644 --- a/nova/fakerabbit.py +++ b/nova/fakerabbit.py @@ -37,12 +37,12 @@ class Exchange(object): self._routes = {} def publish(self, message, routing_key=None): - logging.debug('(%s) publish (key: %s) %s', + logging.debug(_('(%s) publish (key: %s) %s'), self.name, routing_key, message) routing_key = routing_key.split('.')[0] if routing_key in self._routes: for f in self._routes[routing_key]: - logging.debug('Publishing to route %s', f) + logging.debug(_('Publishing to route %s'), f) f(message, routing_key=routing_key) def bind(self, callback, routing_key): @@ -82,16 +82,16 @@ class Backend(object): def queue_declare(self, queue, **kwargs): if queue not in self._queues: - logging.debug('Declaring queue %s', queue) + logging.debug(_('Declaring queue %s'), queue) self._queues[queue] = Queue(queue) def exchange_declare(self, exchange, type, *args, **kwargs): if exchange not in self._exchanges: - logging.debug('Declaring exchange %s', exchange) + logging.debug(_('Declaring exchange %s'), exchange) self._exchanges[exchange] = Exchange(exchange, type) def queue_bind(self, queue, exchange, routing_key, **kwargs): - logging.debug('Binding %s to %s with key %s', + logging.debug(_('Binding %s to %s with key %s'), queue, exchange, routing_key) self._exchanges[exchange].bind(self._queues[queue].push, routing_key) @@ -117,7 +117,7 @@ class Backend(object): content_type=content_type, content_encoding=content_encoding) message.result = True - logging.debug('Getting from %s: %s', queue, message) + logging.debug(_('Getting from %s: %s'), queue, message) return message def prepare_message(self, message_data, delivery_mode, diff --git a/nova/flags.py b/nova/flags.py index 87444565ab..8fa0beb7a5 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -235,12 +235,11 @@ DEFINE_string('ec2_url', 'http://127.0.0.1:8773/services/Cloud', DEFINE_string('default_image', 'ami-11111', 'default image to use, testing only') -DEFINE_string('default_kernel', 'aki-11111', - 'default kernel to use, testing only') -DEFINE_string('default_ramdisk', 'ari-11111', - 'default ramdisk to use, testing only') DEFINE_string('default_instance_type', 'm1.small', 'default instance type to use, testing only') +DEFINE_string('null_kernel', 'nokernel', + 'kernel image that indicates not to use a kernel,' + ' but to use a raw disk image instead') DEFINE_string('vpn_image_id', 'ami-CLOUDPIPE', 'AMI for cloudpipe vpn server') DEFINE_string('vpn_key_suffix', diff --git a/nova/image/glance.py b/nova/image/glance.py index 1ca6cf2eb7..cb3936df11 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -77,8 +77,8 @@ class ParallaxClient(object): data = json.loads(res.read())['images'] return data else: - logging.warn("Parallax returned HTTP error %d from " - "request for /images", res.status_int) + logging.warn(_("Parallax returned HTTP error %d from " + "request for /images"), res.status_int) return [] finally: c.close() @@ -96,8 +96,8 @@ class ParallaxClient(object): data = json.loads(res.read())['images'] return data else: - logging.warn("Parallax returned HTTP error %d from " - "request for /images/detail", res.status_int) + logging.warn(_("Parallax returned HTTP error %d from " + "request for /images/detail"), res.status_int) return [] finally: c.close() diff --git a/nova/image/s3.py b/nova/image/s3.py index 0a25161de4..7b04aa0727 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -79,7 +79,8 @@ class S3ImageService(service.BaseImageService): result = self.index(context) result = [i for i in result if i['imageId'] == image_id] if not result: - raise exception.NotFound('Image %s could not be found' % image_id) + raise exception.NotFound(_('Image %s could not be found') + % image_id) image = result[0] return image diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 0fefd94156..16add76897 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -135,7 +135,7 @@ def ensure_vlan(vlan_num): """Create a vlan unless it already exists""" interface = "vlan%s" % vlan_num if not _device_exists(interface): - logging.debug("Starting VLAN inteface %s", interface) + logging.debug(_("Starting VLAN inteface %s"), interface) _execute("sudo vconfig set_name_type VLAN_PLUS_VID_NO_PAD") _execute("sudo vconfig add %s %s" % (FLAGS.vlan_interface, vlan_num)) _execute("sudo ifconfig %s up" % interface) @@ -145,7 +145,7 @@ def ensure_vlan(vlan_num): def ensure_bridge(bridge, interface, net_attrs=None): """Create a bridge unless it already exists""" if not _device_exists(bridge): - logging.debug("Starting Bridge interface for %s", interface) + logging.debug(_("Starting Bridge interface for %s"), interface) _execute("sudo brctl addbr %s" % bridge) _execute("sudo brctl setfd %s 0" % bridge) # _execute("sudo brctl setageing %s 10" % bridge) @@ -202,9 +202,9 @@ def update_dhcp(context, network_id): _execute('sudo kill -HUP %d' % pid) return except Exception as exc: # pylint: disable-msg=W0703 - logging.debug("Hupping dnsmasq threw %s", exc) + logging.debug(_("Hupping dnsmasq threw %s"), exc) else: - logging.debug("Pid %d is stale, relaunching dnsmasq", pid) + logging.debug(_("Pid %d is stale, relaunching dnsmasq"), pid) # FLAGFILE and DNSMASQ_INTERFACE in env env = {'FLAGFILE': FLAGS.dhcpbridge_flagfile, @@ -276,7 +276,7 @@ def _stop_dnsmasq(network): try: _execute('sudo kill -TERM %d' % pid) except Exception as exc: # pylint: disable-msg=W0703 - logging.debug("Killing dnsmasq threw %s", exc) + logging.debug(_("Killing dnsmasq threw %s"), exc) def _dhcp_file(bridge, kind): diff --git a/nova/network/manager.py b/nova/network/manager.py index 6a30f30b79..8c300e3055 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -115,7 +115,7 @@ class NetworkManager(manager.Manager): def set_network_host(self, context, network_id): """Safely sets the host of the network.""" - logging.debug("setting network host") + logging.debug(_("setting network host")) host = self.db.network_set_host(context, network_id, self.host) @@ -174,10 +174,10 @@ class NetworkManager(manager.Manager): fixed_ip_ref = self.db.fixed_ip_get_by_address(context, address) instance_ref = fixed_ip_ref['instance'] if not instance_ref: - raise exception.Error("IP %s leased that isn't associated" % + raise exception.Error(_("IP %s leased that isn't associated") % address) if instance_ref['mac_address'] != mac: - raise exception.Error("IP %s leased to bad mac %s vs %s" % + raise exception.Error(_("IP %s leased to bad mac %s vs %s") % (address, instance_ref['mac_address'], mac)) now = datetime.datetime.utcnow() self.db.fixed_ip_update(context, @@ -185,7 +185,8 @@ class NetworkManager(manager.Manager): {'leased': True, 'updated_at': now}) if not fixed_ip_ref['allocated']: - logging.warn("IP %s leased that was already deallocated", address) + logging.warn(_("IP %s leased that was already deallocated"), + address) def release_fixed_ip(self, context, mac, address): """Called by dhcp-bridge when ip is released.""" @@ -193,13 +194,13 @@ class NetworkManager(manager.Manager): fixed_ip_ref = self.db.fixed_ip_get_by_address(context, address) instance_ref = fixed_ip_ref['instance'] if not instance_ref: - raise exception.Error("IP %s released that isn't associated" % + raise exception.Error(_("IP %s released that isn't associated") % address) if instance_ref['mac_address'] != mac: - raise exception.Error("IP %s released from bad mac %s vs %s" % + raise exception.Error(_("IP %s released from bad mac %s vs %s") % (address, instance_ref['mac_address'], mac)) if not fixed_ip_ref['leased']: - logging.warn("IP %s released that was not leased", address) + logging.warn(_("IP %s released that was not leased"), address) self.db.fixed_ip_update(context, fixed_ip_ref['address'], {'leased': False}) @@ -361,8 +362,7 @@ class FlatDHCPManager(FlatManager): """Sets up matching network for compute hosts.""" network_ref = db.network_get_by_instance(context, instance_id) self.driver.ensure_bridge(network_ref['bridge'], - FLAGS.flat_interface, - network_ref) + FLAGS.flat_interface) def setup_fixed_ip(self, context, address): """Setup dhcp for this network.""" @@ -408,7 +408,7 @@ class VlanManager(NetworkManager): self.host, time) if num: - logging.debug("Dissassociated %s stale fixed ip(s)", num) + logging.debug(_("Dissassociated %s stale fixed ip(s)"), num) def init_host(self): """Do any initialization that needs to be run if this is a diff --git a/nova/objectstore/handler.py b/nova/objectstore/handler.py index c8920b00c8..52257f69fc 100644 --- a/nova/objectstore/handler.py +++ b/nova/objectstore/handler.py @@ -102,7 +102,7 @@ def _render_parts(value, write_cb): _render_parts(subsubvalue, write_cb) write_cb('') else: - raise Exception("Unknown S3 value type %r", value) + raise Exception(_("Unknown S3 value type %r"), value) def get_argument(request, key, default_value): @@ -134,7 +134,7 @@ def get_context(request): check_type='s3') return context.RequestContext(user, project) except exception.Error as ex: - logging.debug("Authentication Failure: %s", ex) + logging.debug(_("Authentication Failure: %s"), ex) raise exception.NotAuthorized() @@ -227,7 +227,7 @@ class BucketResource(ErrorHandlingResource): def render_PUT(self, request): "Creates the bucket resource""" - logging.debug("Creating bucket %s", self.name) + logging.debug(_("Creating bucket %s"), self.name) logging.debug("calling bucket.Bucket.create(%r, %r)", self.name, request.context) @@ -237,7 +237,7 @@ class BucketResource(ErrorHandlingResource): def render_DELETE(self, request): """Deletes the bucket resource""" - logging.debug("Deleting bucket %s", self.name) + logging.debug(_("Deleting bucket %s"), self.name) bucket_object = bucket.Bucket(self.name) if not bucket_object.is_authorized(request.context): @@ -261,7 +261,9 @@ class ObjectResource(ErrorHandlingResource): Raises NotAuthorized if user in request context is not authorized to delete the object. """ - logging.debug("Getting object: %s / %s", self.bucket.name, self.name) + logging.debug(_("Getting object: %s / %s"), + self.bucket.name, + self.name) if not self.bucket.is_authorized(request.context): raise exception.NotAuthorized() @@ -279,7 +281,9 @@ class ObjectResource(ErrorHandlingResource): Raises NotAuthorized if user in request context is not authorized to delete the object. """ - logging.debug("Putting object: %s / %s", self.bucket.name, self.name) + logging.debug(_("Putting object: %s / %s"), + self.bucket.name, + self.name) if not self.bucket.is_authorized(request.context): raise exception.NotAuthorized() @@ -298,7 +302,7 @@ class ObjectResource(ErrorHandlingResource): authorized to delete the object. """ - logging.debug("Deleting object: %s / %s", + logging.debug(_("Deleting object: %s / %s"), self.bucket.name, self.name) @@ -394,17 +398,17 @@ class ImagesResource(resource.Resource): image_id = get_argument(request, 'image_id', u'') image_object = image.Image(image_id) if not image_object.is_authorized(request.context): - logging.debug("not authorized for render_POST in images") + logging.debug(_("not authorized for render_POST in images")) raise exception.NotAuthorized() operation = get_argument(request, 'operation', u'') if operation: # operation implies publicity toggle - logging.debug("handling publicity toggle") + logging.debug(_("handling publicity toggle")) image_object.set_public(operation == 'add') else: # other attributes imply update - logging.debug("update user fields") + logging.debug(_("update user fields")) clean_args = {} for arg in request.args.keys(): clean_args[arg] = request.args[arg][0] diff --git a/nova/rpc.py b/nova/rpc.py index 6a3f552dbd..6e2cf051a6 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -91,15 +91,15 @@ class Consumer(messaging.Consumer): self.failed_connection = False break except: # Catching all because carrot sucks - logging.exception("AMQP server on %s:%d is unreachable." \ - " Trying again in %d seconds." % ( + logging.exception(_("AMQP server on %s:%d is unreachable." + " Trying again in %d seconds.") % ( FLAGS.rabbit_host, FLAGS.rabbit_port, FLAGS.rabbit_retry_interval)) self.failed_connection = True if self.failed_connection: - logging.exception("Unable to connect to AMQP server" \ - " after %d tries. Shutting down." % FLAGS.rabbit_max_retries) + logging.exception(_("Unable to connect to AMQP server" + " after %d tries. Shutting down.") % FLAGS.rabbit_max_retries) sys.exit(1) def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): @@ -116,14 +116,14 @@ class Consumer(messaging.Consumer): self.declare() super(Consumer, self).fetch(no_ack, auto_ack, enable_callbacks) if self.failed_connection: - logging.error("Reconnected to queue") + logging.error(_("Reconnected to queue")) self.failed_connection = False # NOTE(vish): This is catching all errors because we really don't # exceptions to be logged 10 times a second if some # persistent failure occurs. except Exception: # pylint: disable-msg=W0703 if not self.failed_connection: - logging.exception("Failed to fetch message from queue") + logging.exception(_("Failed to fetch message from queue")) self.failed_connection = True def attach_to_eventlet(self): @@ -153,7 +153,7 @@ class TopicConsumer(Consumer): class AdapterConsumer(TopicConsumer): """Calls methods on a proxy object based on method and args""" def __init__(self, connection=None, topic="broadcast", proxy=None): - LOG.debug('Initing the Adapter Consumer for %s' % (topic)) + LOG.debug(_('Initing the Adapter Consumer for %s') % (topic)) self.proxy = proxy super(AdapterConsumer, self).__init__(connection=connection, topic=topic) @@ -168,7 +168,7 @@ class AdapterConsumer(TopicConsumer): Example: {'method': 'echo', 'args': {'value': 42}} """ - LOG.debug('received %s' % (message_data)) + LOG.debug(_('received %s') % (message_data)) msg_id = message_data.pop('_msg_id', None) ctxt = _unpack_context(message_data) @@ -181,8 +181,8 @@ class AdapterConsumer(TopicConsumer): # messages stay in the queue indefinitely, so for now # we just log the message and send an error string # back to the caller - LOG.warn('no method for message: %s' % (message_data)) - msg_reply(msg_id, 'No method for message: %s' % message_data) + LOG.warn(_('no method for message: %s') % (message_data)) + msg_reply(msg_id, _('No method for message: %s') % message_data) return node_func = getattr(self.proxy, str(method)) @@ -242,7 +242,7 @@ def msg_reply(msg_id, reply=None, failure=None): if failure: message = str(failure[1]) tb = traceback.format_exception(*failure) - logging.error("Returning exception %s to caller", message) + logging.error(_("Returning exception %s to caller"), message) logging.error(tb) failure = (failure[0].__name__, str(failure[1]), tb) conn = Connection.instance() @@ -283,7 +283,7 @@ def _unpack_context(msg): if key.startswith('_context_'): value = msg.pop(key) context_dict[key[9:]] = value - LOG.debug('unpacked context: %s', context_dict) + LOG.debug(_('unpacked context: %s'), context_dict) return context.RequestContext.from_dict(context_dict) @@ -302,10 +302,10 @@ def _pack_context(msg, context): def call(context, topic, msg): """Sends a message on a topic and wait for a response""" - LOG.debug("Making asynchronous call...") + LOG.debug(_("Making asynchronous call...")) msg_id = uuid.uuid4().hex msg.update({'_msg_id': msg_id}) - LOG.debug("MSG_ID is %s" % (msg_id)) + LOG.debug(_("MSG_ID is %s") % (msg_id)) _pack_context(msg, context) class WaitMessage(object): @@ -353,7 +353,7 @@ def cast(context, topic, msg): def generic_response(message_data, message): """Logs a result and exits""" - LOG.debug('response %s', message_data) + LOG.debug(_('response %s'), message_data) message.ack() sys.exit(0) @@ -362,8 +362,8 @@ def send_message(topic, message, wait=True): """Sends a message for testing""" msg_id = uuid.uuid4().hex message.update({'_msg_id': msg_id}) - LOG.debug('topic is %s', topic) - LOG.debug('message %s', message) + LOG.debug(_('topic is %s'), topic) + LOG.debug(_('message %s'), message) if wait: consumer = messaging.Consumer(connection=Connection.instance(), diff --git a/nova/scheduler/chance.py b/nova/scheduler/chance.py index 7fd09b053b..9deaa2777a 100644 --- a/nova/scheduler/chance.py +++ b/nova/scheduler/chance.py @@ -34,5 +34,5 @@ class ChanceScheduler(driver.Scheduler): hosts = self.hosts_up(context, topic) if not hosts: - raise driver.NoValidHost("No hosts found") + raise driver.NoValidHost(_("No hosts found")) return hosts[int(random.random() * len(hosts))] diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py index f271d573fd..08d7033f5e 100644 --- a/nova/scheduler/driver.py +++ b/nova/scheduler/driver.py @@ -58,4 +58,4 @@ class Scheduler(object): def schedule(self, context, topic, *_args, **_kwargs): """Must override at least this method for scheduler to work.""" - raise NotImplementedError("Must implement a fallback schedule") + raise NotImplementedError(_("Must implement a fallback schedule")) diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py index 60a3d2b4b2..44e21f2fdd 100644 --- a/nova/scheduler/manager.py +++ b/nova/scheduler/manager.py @@ -65,4 +65,4 @@ class SchedulerManager(manager.Manager): db.queue_get_for(context, topic, host), {"method": method, "args": kwargs}) - logging.debug("Casting to %s %s for %s", topic, host, method) + logging.debug(_("Casting to %s %s for %s"), topic, host, method) diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py index 7f50936564..f9171ab35d 100644 --- a/nova/scheduler/simple.py +++ b/nova/scheduler/simple.py @@ -47,7 +47,7 @@ class SimpleScheduler(chance.ChanceScheduler): for result in results: (service, instance_cores) = result if instance_cores + instance_ref['vcpus'] > FLAGS.max_cores: - raise driver.NoValidHost("All hosts have too many cores") + raise driver.NoValidHost(_("All hosts have too many cores")) if self.service_is_up(service): # NOTE(vish): this probably belongs in the manager, if we # can generalize this somehow @@ -57,7 +57,7 @@ class SimpleScheduler(chance.ChanceScheduler): {'host': service['host'], 'scheduled_at': now}) return service['host'] - raise driver.NoValidHost("No hosts found") + raise driver.NoValidHost(_("No hosts found")) def schedule_create_volume(self, context, volume_id, *_args, **_kwargs): """Picks a host that is up and has the fewest volumes.""" @@ -66,7 +66,8 @@ class SimpleScheduler(chance.ChanceScheduler): for result in results: (service, volume_gigabytes) = result if volume_gigabytes + volume_ref['size'] > FLAGS.max_gigabytes: - raise driver.NoValidHost("All hosts have too many gigabytes") + raise driver.NoValidHost(_("All hosts have too many " + "gigabytes")) if self.service_is_up(service): # NOTE(vish): this probably belongs in the manager, if we # can generalize this somehow @@ -76,7 +77,7 @@ class SimpleScheduler(chance.ChanceScheduler): {'host': service['host'], 'scheduled_at': now}) return service['host'] - raise driver.NoValidHost("No hosts found") + raise driver.NoValidHost(_("No hosts found")) def schedule_set_network_host(self, context, *_args, **_kwargs): """Picks a host that is up and has the fewest networks.""" @@ -85,7 +86,7 @@ class SimpleScheduler(chance.ChanceScheduler): for result in results: (service, instance_count) = result if instance_count >= FLAGS.max_networks: - raise driver.NoValidHost("All hosts have too many networks") + raise driver.NoValidHost(_("All hosts have too many networks")) if self.service_is_up(service): return service['host'] - raise driver.NoValidHost("No hosts found") + raise driver.NoValidHost(_("No hosts found")) diff --git a/nova/service.py b/nova/service.py index ac30aaceba..f1f90742f8 100644 --- a/nova/service.py +++ b/nova/service.py @@ -151,7 +151,7 @@ class Service(object): report_interval = FLAGS.report_interval if not periodic_interval: periodic_interval = FLAGS.periodic_interval - logging.warn("Starting %s node", topic) + logging.warn(_("Starting %s node"), topic) service_obj = cls(host, binary, topic, manager, report_interval, periodic_interval) @@ -163,7 +163,7 @@ class Service(object): try: db.service_destroy(context.get_admin_context(), self.service_id) except exception.NotFound: - logging.warn("Service killed that has no database entry") + logging.warn(_("Service killed that has no database entry")) def stop(self): for x in self.timers: @@ -184,8 +184,8 @@ class Service(object): try: service_ref = db.service_get(ctxt, self.service_id) except exception.NotFound: - logging.debug("The service database object disappeared, " - "Recreating it.") + logging.debug(_("The service database object disappeared, " + "Recreating it.")) self._create_service_ref(ctxt) service_ref = db.service_get(ctxt, self.service_id) @@ -196,13 +196,13 @@ class Service(object): # TODO(termie): make this pattern be more elegant. if getattr(self, "model_disconnected", False): self.model_disconnected = False - logging.error("Recovered model server connection!") + logging.error(_("Recovered model server connection!")) # TODO(vish): this should probably only catch connection errors except Exception: # pylint: disable-msg=W0702 if not getattr(self, "model_disconnected", False): self.model_disconnected = True - logging.exception("model server went away") + logging.exception(_("model server went away")) def serve(*services): @@ -221,7 +221,7 @@ def serve(*services): else: logging.getLogger().setLevel(logging.WARNING) - logging.debug("Full set of FLAGS:") + logging.debug(_("Full set of FLAGS:")) for flag in FLAGS: logging.debug("%s : %s" % (flag, FLAGS.get(flag, None))) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 8444b6fce6..3820f5f271 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -56,11 +56,16 @@ def instance_address(context, instance_id): def stub_instance(id, user_id=1): - return Instance(id=id + 123456, state=0, image_id=10, user_id=user_id, + return Instance(id=int(id) + 123456, state=0, image_id=10, user_id=user_id, display_name='server%s' % id, internal_id=id) +def fake_compute_api(cls, req, id): + return True + + class ServersTest(unittest.TestCase): + def setUp(self): self.stubs = stubout.StubOutForTesting() fakes.FakeAuthManager.auth_data = {} @@ -82,9 +87,15 @@ class ServersTest(unittest.TestCase): instance_address) self.stubs.Set(nova.db.api, 'instance_get_floating_address', instance_address) + self.stubs.Set(nova.compute.api.ComputeAPI, 'pause', + fake_compute_api) + self.stubs.Set(nova.compute.api.ComputeAPI, 'unpause', + fake_compute_api) + self.allow_admin = FLAGS.allow_admin_api def tearDown(self): self.stubs.UnsetAll() + FLAGS.allow_admin_api = self.allow_admin def test_get_server_by_id(self): req = webob.Request.blank('/v1.0/servers/1') @@ -211,6 +222,30 @@ class ServersTest(unittest.TestCase): self.assertEqual(s['imageId'], 10) i += 1 + def test_server_pause(self): + FLAGS.allow_admin_api = True + body = dict(server=dict( + name='server_test', imageId=2, flavorId=2, metadata={}, + personality={})) + req = webob.Request.blank('/v1.0/servers/1/pause') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(nova.api.API('os')) + self.assertEqual(res.status_int, 202) + + def test_server_unpause(self): + FLAGS.allow_admin_api = True + body = dict(server=dict( + name='server_test', imageId=2, flavorId=2, metadata={}, + personality={})) + req = webob.Request.blank('/v1.0/servers/1/unpause') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(nova.api.API('os')) + self.assertEqual(res.status_int, 202) + def test_server_reboot(self): body = dict(server=dict( name='server_test', imageId=2, flavorId=2, metadata={}, diff --git a/nova/tests/auth_unittest.py b/nova/tests/auth_unittest.py index 4508d67216..61ae43fb10 100644 --- a/nova/tests/auth_unittest.py +++ b/nova/tests/auth_unittest.py @@ -333,14 +333,10 @@ class AuthManagerLdapTestCase(AuthManagerTestCase, test.TestCase): AuthManagerTestCase.__init__(self) test.TestCase.__init__(self, *args, **kwargs) import nova.auth.fakeldap as fakeldap - FLAGS.redis_db = 8 if FLAGS.flush_db: - logging.info("Flushing redis datastore") - try: - r = fakeldap.Redis.instance() - r.flushdb() - except: - self.skip = True + logging.info("Flushing datastore") + r = fakeldap.Store.instance() + r.flushdb() class AuthManagerDbTestCase(AuthManagerTestCase, test.TestCase): diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py index c6353d3575..187ca31deb 100644 --- a/nova/tests/compute_unittest.py +++ b/nova/tests/compute_unittest.py @@ -127,6 +127,14 @@ class ComputeTestCase(test.TestCase): self.assert_(instance_ref['launched_at'] < terminate) self.assert_(instance_ref['deleted_at'] > terminate) + def test_pause(self): + """Ensure instance can be paused""" + instance_id = self._create_instance() + self.compute.run_instance(self.context, instance_id) + self.compute.pause_instance(self.context, instance_id) + self.compute.unpause_instance(self.context, instance_id) + self.compute.terminate_instance(self.context, instance_id) + def test_reboot(self): """Ensure instance can be rebooted""" instance_id = self._create_instance() diff --git a/nova/tests/virt_unittest.py b/nova/tests/virt_unittest.py index d190cdabf1..7682f96622 100644 --- a/nova/tests/virt_unittest.py +++ b/nova/tests/virt_unittest.py @@ -40,19 +40,51 @@ class LibvirtConnTestCase(test.TestCase): self.network = utils.import_object(FLAGS.network_manager) FLAGS.instances_path = '' - def test_get_uri_and_template(self): - ip = '10.11.12.13' + test_ip = '10.11.12.13' + test_instance = {'memory_kb': '1024000', + 'basepath': '/some/path', + 'bridge_name': 'br100', + 'mac_address': '02:12:34:46:56:67', + 'vcpus': 2, + 'project_id': 'fake', + 'bridge': 'br101', + 'instance_type': 'm1.small'} - instance = {'internal_id': 1, - 'memory_kb': '1024000', - 'basepath': '/some/path', - 'bridge_name': 'br100', - 'mac_address': '02:12:34:46:56:67', - 'vcpus': 2, - 'project_id': 'fake', - 'bridge': 'br101', - 'instance_type': 'm1.small'} + def test_xml_and_uri_no_ramdisk_no_kernel(self): + instance_data = dict(self.test_instance) + self.do_test_xml_and_uri(instance_data, + expect_kernel=False, expect_ramdisk=False) + def test_xml_and_uri_no_ramdisk(self): + instance_data = dict(self.test_instance) + instance_data['kernel_id'] = 'aki-deadbeef' + self.do_test_xml_and_uri(instance_data, + expect_kernel=True, expect_ramdisk=False) + + def test_xml_and_uri_no_kernel(self): + instance_data = dict(self.test_instance) + instance_data['ramdisk_id'] = 'ari-deadbeef' + self.do_test_xml_and_uri(instance_data, + expect_kernel=False, expect_ramdisk=False) + + def test_xml_and_uri(self): + instance_data = dict(self.test_instance) + instance_data['ramdisk_id'] = 'ari-deadbeef' + instance_data['kernel_id'] = 'aki-deadbeef' + self.do_test_xml_and_uri(instance_data, + expect_kernel=True, expect_ramdisk=True) + + def test_xml_and_uri_rescue(self): + instance_data = dict(self.test_instance) + instance_data['ramdisk_id'] = 'ari-deadbeef' + instance_data['kernel_id'] = 'aki-deadbeef' + self.do_test_xml_and_uri(instance_data, + expect_kernel=True, expect_ramdisk=True, + rescue=True) + + def do_test_xml_and_uri(self, instance, + expect_ramdisk, expect_kernel, + rescue=False): user_context = context.RequestContext(project=self.project, user=self.user) instance_ref = db.instance_create(user_context, instance) @@ -60,13 +92,14 @@ class LibvirtConnTestCase(test.TestCase): self.network.set_network_host(context.get_admin_context(), network_ref['id']) - fixed_ip = {'address': ip, + fixed_ip = {'address': self.test_ip, 'network_id': network_ref['id']} ctxt = context.get_admin_context() fixed_ip_ref = db.fixed_ip_create(ctxt, fixed_ip) - db.fixed_ip_update(ctxt, ip, {'allocated': True, - 'instance_id': instance_ref['id']}) + db.fixed_ip_update(ctxt, self.test_ip, + {'allocated': True, + 'instance_id': instance_ref['id']}) type_uri_map = {'qemu': ('qemu:///system', [(lambda t: t.find('.').get('type'), 'qemu'), @@ -78,23 +111,73 @@ class LibvirtConnTestCase(test.TestCase): (lambda t: t.find('./devices/emulator'), None)]), 'uml': ('uml:///system', [(lambda t: t.find('.').get('type'), 'uml'), - (lambda t: t.find('./os/type').text, 'uml')])} + (lambda t: t.find('./os/type').text, 'uml')]), + 'xen': ('xen:///', + [(lambda t: t.find('.').get('type'), 'xen'), + (lambda t: t.find('./os/type').text, 'linux')]), + } + + for hypervisor_type in ['qemu', 'kvm', 'xen']: + check_list = type_uri_map[hypervisor_type][1] + + if rescue: + check = (lambda t: t.find('./os/kernel').text.split('/')[1], + 'rescue-kernel') + check_list.append(check) + check = (lambda t: t.find('./os/initrd').text.split('/')[1], + 'rescue-ramdisk') + check_list.append(check) + else: + if expect_kernel: + check = (lambda t: t.find('./os/kernel').text.split( + '/')[1], 'kernel') + else: + check = (lambda t: t.find('./os/kernel'), None) + check_list.append(check) + + if expect_ramdisk: + check = (lambda t: t.find('./os/initrd').text.split( + '/')[1], 'ramdisk') + else: + check = (lambda t: t.find('./os/initrd'), None) + check_list.append(check) common_checks = [ (lambda t: t.find('.').tag, 'domain'), - (lambda t: t.find('./devices/interface/filterref/parameter').\ - get('name'), 'IP'), - (lambda t: t.find('./devices/interface/filterref/parameter').\ - get('value'), '10.11.12.13')] + (lambda t: t.find( + './devices/interface/filterref/parameter').get('name'), 'IP'), + (lambda t: t.find( + './devices/interface/filterref/parameter').get( + 'value'), '10.11.12.13'), + (lambda t: t.findall( + './devices/interface/filterref/parameter')[1].get( + 'name'), 'DHCPSERVER'), + (lambda t: t.findall( + './devices/interface/filterref/parameter')[1].get( + 'value'), '10.0.0.1'), + (lambda t: t.find('./devices/serial/source').get( + 'path').split('/')[1], 'console.log'), + (lambda t: t.find('./memory').text, '2097152')] + + if rescue: + common_checks += [ + (lambda t: t.findall('./devices/disk/source')[0].get( + 'file').split('/')[1], 'rescue-disk'), + (lambda t: t.findall('./devices/disk/source')[1].get( + 'file').split('/')[1], 'disk')] + else: + common_checks += [(lambda t: t.findall( + './devices/disk/source')[0].get('file').split('/')[1], + 'disk')] for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems(): FLAGS.libvirt_type = libvirt_type conn = libvirt_conn.LibvirtConnection(True) - uri, _template, _rescue = conn.get_uri_and_templates() + uri = conn.get_uri() self.assertEquals(uri, expected_uri) - xml = conn.to_xml(instance_ref) + xml = conn.to_xml(instance_ref, rescue) tree = xml_to_tree(xml) for i, (check, expected_result) in enumerate(checks): self.assertEqual(check(tree), @@ -106,6 +189,9 @@ class LibvirtConnTestCase(test.TestCase): expected_result, '%s failed common check %d' % (xml, i)) + # This test is supposed to make sure we don't override a specifically + # set uri + # # Deliberately not just assigning this string to FLAGS.libvirt_uri and # checking against that later on. This way we make sure the # implementation doesn't fiddle around with the FLAGS. @@ -114,7 +200,7 @@ class LibvirtConnTestCase(test.TestCase): for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems(): FLAGS.libvirt_type = libvirt_type conn = libvirt_conn.LibvirtConnection(True) - uri, _template, _rescue = conn.get_uri_and_templates() + uri = conn.get_uri() self.assertEquals(uri, testuri) def tearDown(self): diff --git a/nova/twistd.py b/nova/twistd.py index cb5648ce64..29be9c4e14 100644 --- a/nova/twistd.py +++ b/nova/twistd.py @@ -43,7 +43,7 @@ else: FLAGS = flags.FLAGS -flags.DEFINE_string('logdir', None, 'directory to keep log files in ' +flags.DEFINE_string('logdir', None, 'directory to keep log files in ' '(will be prepended to $logfile)') @@ -208,7 +208,7 @@ def stop(pidfile): pid = None if not pid: - message = "pidfile %s does not exist. Daemon not running?\n" + message = _("pidfile %s does not exist. Daemon not running?\n") sys.stderr.write(message % pidfile) # Not an error in a restart return @@ -229,7 +229,7 @@ def stop(pidfile): def serve(filename): - logging.debug("Serving %s" % filename) + logging.debug(_("Serving %s") % filename) name = os.path.basename(filename) OptionsClass = WrapTwistedOptions(TwistdServerOptions) options = OptionsClass() @@ -281,7 +281,7 @@ def serve(filename): else: logging.getLogger().setLevel(logging.WARNING) - logging.debug("Full set of FLAGS:") + logging.debug(_("Full set of FLAGS:")) for flag in FLAGS: logging.debug("%s : %s" % (flag, FLAGS.get(flag, None))) diff --git a/nova/utils.py b/nova/utils.py index ea1f04ca79..16b509b48d 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -50,7 +50,7 @@ def import_class(import_str): __import__(mod_str) return getattr(sys.modules[mod_str], class_str) except (ImportError, ValueError, AttributeError): - raise exception.NotFound('Class %s cannot be found' % class_str) + raise exception.NotFound(_('Class %s cannot be found') % class_str) def import_object(import_str): @@ -64,7 +64,7 @@ def import_object(import_str): def fetchfile(url, target): - logging.debug("Fetching %s" % url) + logging.debug(_("Fetching %s") % url) # c = pycurl.Curl() # fp = open(target, "wb") # c.setopt(c.URL, url) @@ -76,7 +76,7 @@ def fetchfile(url, target): def execute(cmd, process_input=None, addl_env=None, check_exit_code=True): - logging.debug("Running cmd (subprocess): %s", cmd) + logging.debug(_("Running cmd (subprocess): %s"), cmd) env = os.environ.copy() if addl_env: env.update(addl_env) @@ -89,7 +89,7 @@ def execute(cmd, process_input=None, addl_env=None, check_exit_code=True): result = obj.communicate() obj.stdin.close() if obj.returncode: - logging.debug("Result was %s" % (obj.returncode)) + logging.debug(_("Result was %s") % (obj.returncode)) if check_exit_code and obj.returncode != 0: (stdout, stderr) = result raise ProcessExecutionError(exit_code=obj.returncode, @@ -127,7 +127,7 @@ def debug(arg): def runthis(prompt, cmd, check_exit_code=True): - logging.debug("Running %s" % (cmd)) + logging.debug(_("Running %s") % (cmd)) rv, err = execute(cmd, check_exit_code=check_exit_code) @@ -160,7 +160,7 @@ def get_my_ip(): csock.close() return addr except socket.gaierror as ex: - logging.warn("Couldn't get IP, using 127.0.0.1 %s", ex) + logging.warn(_("Couldn't get IP, using 127.0.0.1 %s"), ex) return "127.0.0.1" @@ -204,7 +204,7 @@ class LazyPluggable(object): if not self.__backend: backend_name = self.__pivot.value if backend_name not in self.__backends: - raise exception.Error('Invalid backend: %s' % backend_name) + raise exception.Error(_('Invalid backend: %s') % backend_name) backend = self.__backends[backend_name] if type(backend) == type(tuple()): diff --git a/nova/virt/connection.py b/nova/virt/connection.py index c40bb4bb4d..61e99944ef 100644 --- a/nova/virt/connection.py +++ b/nova/virt/connection.py @@ -66,6 +66,6 @@ def get_connection(read_only=False): raise Exception('Unknown connection type "%s"' % t) if conn is None: - logging.error('Failed to open connection to the hypervisor') + logging.error(_('Failed to open connection to the hypervisor')) sys.exit(1) return conn diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 76a04f18f3..238acf7984 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -136,6 +136,18 @@ class FakeConnection(object): """ pass + def pause(self, instance, callback): + """ + Pause the specified instance. + """ + pass + + def unpause(self, instance, callback): + """ + Unpause the specified instance. + """ + pass + def destroy(self, instance): """ Destroy (shutdown and delete) the specified instance. @@ -169,7 +181,8 @@ class FakeConnection(object): knowledge of the instance """ if instance_name not in self.instances: - raise exception.NotFound("Instance %s Not Found" % instance_name) + raise exception.NotFound(_("Instance %s Not Found") + % instance_name) i = self.instances[instance_name] return {'state': i._state, 'max_mem': 0, @@ -249,5 +262,6 @@ class FakeConnection(object): class FakeInstance(object): + def __init__(self): self._state = power_state.NOSTATE diff --git a/nova/virt/libvirt.qemu.xml.template b/nova/virt/libvirt.qemu.xml.template deleted file mode 100644 index d90afea81e..0000000000 --- a/nova/virt/libvirt.qemu.xml.template +++ /dev/null @@ -1,34 +0,0 @@ - - %(name)s - - hvm - %(basepath)s/kernel - %(basepath)s/ramdisk - root=/dev/vda1 console=ttyS0 - - - - - %(memory_kb)s - %(vcpus)s - - - - - - - - - - - - - %(extra_params)s - - - - - - - - diff --git a/nova/virt/libvirt.rescue.qemu.xml.template b/nova/virt/libvirt.rescue.qemu.xml.template.THIS similarity index 100% rename from nova/virt/libvirt.rescue.qemu.xml.template rename to nova/virt/libvirt.rescue.qemu.xml.template.THIS diff --git a/nova/virt/libvirt.rescue.uml.xml.template b/nova/virt/libvirt.rescue.uml.xml.template.THIS similarity index 100% rename from nova/virt/libvirt.rescue.uml.xml.template rename to nova/virt/libvirt.rescue.uml.xml.template.THIS diff --git a/nova/virt/libvirt.rescue.xen.xml.template b/nova/virt/libvirt.rescue.xen.xml.template deleted file mode 100644 index 3b8d27237f..0000000000 --- a/nova/virt/libvirt.rescue.xen.xml.template +++ /dev/null @@ -1,34 +0,0 @@ - - %(name)s - - linux - %(basepath)s/kernel - %(basepath)s/ramdisk - /dev/xvda1 - ro - - - - - %(memory_kb)s - %(vcpus)s - - - - - - - - - - - - - - - - - - - - diff --git a/nova/virt/libvirt.uml.xml.template b/nova/virt/libvirt.uml.xml.template.THIS similarity index 100% rename from nova/virt/libvirt.uml.xml.template rename to nova/virt/libvirt.uml.xml.template.THIS diff --git a/nova/virt/libvirt.xen.xml.template b/nova/virt/libvirt.xen.xml.template deleted file mode 100644 index 9677902c66..0000000000 --- a/nova/virt/libvirt.xen.xml.template +++ /dev/null @@ -1,30 +0,0 @@ - - %(name)s - - linux - %(basepath)s/kernel - %(basepath)s/ramdisk - /dev/xvda1 - ro - - - - - %(memory_kb)s - %(vcpus)s - - - - - - - - - - - - - - - - diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template new file mode 100644 index 0000000000..3fb2243dac --- /dev/null +++ b/nova/virt/libvirt.xml.template @@ -0,0 +1,79 @@ + + ${name} + ${memory_kb} + +#if $type == 'uml' + #set $disk_prefix = 'ubd' + #set $disk_bus = 'uml' + uml + /usr/bin/linux + /dev/ubda1 +#else + #if $type == 'xen' + #set $disk_prefix = 'sd' + #set $disk_bus = 'scsi' + linux + /dev/xvda1 + #else + #set $disk_prefix = 'vd' + #set $disk_bus = 'virtio' + hvm + #end if + #if $getVar('rescue', False) + ${basepath}/rescue-kernel + ${basepath}/rescue-ramdisk + #else + #if $getVar('kernel', None) + ${kernel} + #if $type == 'xen' + ro + #else + root=/dev/vda1 console=ttyS0 + #end if + #if $getVar('ramdisk', None) + ${ramdisk} + #end if + #else + + #end if + #end if +#end if + + + + + ${vcpus} + +#if $getVar('rescue', False) + + + + + + + + +#else + + + + +#end if + + + + + + + +#if $getVar('extra_params', False) + ${extra_params} +#end if + + + + + + + + diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 0d42ce2f82..16bcfe3c05 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -27,12 +27,7 @@ Supports KVM, QEMU, UML, and XEN. :libvirt_type: Libvirt domain type. Can be kvm, qemu, uml, xen (default: kvm). :libvirt_uri: Override for the default libvirt URI (depends on libvirt_type). -:libvirt_xml_template: Libvirt XML Template (QEmu/KVM). -:libvirt_xen_xml_template: Libvirt XML Template (Xen). -:libvirt_uml_xml_template: Libvirt XML Template (User Mode Linux). -:libvirt_rescue_xml_template: XML template for rescue mode (KVM & QEMU). -:libvirt_rescue_xen_xml_template: XML templage for rescue mode (XEN). -:libvirt_rescue_uml_xml_template: XML template for rescue mode (UML). +:libvirt_xml_template: Libvirt XML Template. :rescue_image_id: Rescue ami image (default: ami-rescue). :rescue_kernel_id: Rescue aki image (default: aki-rescue). :rescue_ramdisk_id: Rescue ari image (default: ari-rescue). @@ -62,36 +57,20 @@ from nova.compute import instance_types from nova.compute import power_state from nova.virt import images +from Cheetah.Template import Template + libvirt = None libxml2 = None FLAGS = flags.FLAGS -flags.DEFINE_string('libvirt_rescue_xml_template', - utils.abspath('virt/libvirt.rescue.qemu.xml.template'), - 'Libvirt RESCUE XML Template for QEmu/KVM') -flags.DEFINE_string('libvirt_rescue_xen_xml_template', - utils.abspath('virt/libvirt.rescue.xen.xml.template'), - 'Libvirt RESCUE XML Template for xen') -flags.DEFINE_string('libvirt_rescue_uml_xml_template', - utils.abspath('virt/libvirt.rescue.uml.xml.template'), - 'Libvirt RESCUE XML Template for user-mode-linux') # TODO(vish): These flags should probably go into a shared location flags.DEFINE_string('rescue_image_id', 'ami-rescue', 'Rescue ami image') flags.DEFINE_string('rescue_kernel_id', 'aki-rescue', 'Rescue aki image') flags.DEFINE_string('rescue_ramdisk_id', 'ari-rescue', 'Rescue ari image') flags.DEFINE_string('libvirt_xml_template', - utils.abspath('virt/libvirt.qemu.xml.template'), - 'Libvirt XML Template for QEmu/KVM') -flags.DEFINE_string('libvirt_xen_xml_template', - utils.abspath('virt/libvirt.xen.xml.template'), - 'Libvirt XML Template for Xen') -flags.DEFINE_string('libvirt_uml_xml_template', - utils.abspath('virt/libvirt.uml.xml.template'), - 'Libvirt XML Template for user-mode-linux') -flags.DEFINE_string('injected_network_template', - utils.abspath('virt/interfaces.template'), - 'Template file for injected network') + utils.abspath('virt/libvirt.xml.template'), + 'Libvirt XML Template') flags.DEFINE_string('libvirt_type', 'kvm', 'Libvirt domain type (valid options are: ' @@ -123,13 +102,11 @@ def _get_net_and_mask(cidr): class LibvirtConnection(object): - def __init__(self, read_only): - (self.libvirt_uri, - template_file, - rescue_file) = self.get_uri_and_templates() - self.libvirt_xml = open(template_file).read() - self.rescue_xml = open(rescue_file).read() + def __init__(self, read_only): + self.libvirt_uri = self.get_uri() + + self.libvirt_xml = open(FLAGS.libvirt_xml_template).read() self._wrapped_conn = None self.read_only = read_only @@ -139,7 +116,7 @@ class LibvirtConnection(object): @property def _conn(self): if not self._wrapped_conn or not self._test_connection(): - logging.debug('Connecting to libvirt: %s' % self.libvirt_uri) + logging.debug(_('Connecting to libvirt: %s') % self.libvirt_uri) self._wrapped_conn = self._connect(self.libvirt_uri, self.read_only) return self._wrapped_conn @@ -151,24 +128,18 @@ class LibvirtConnection(object): except libvirt.libvirtError as e: if e.get_error_code() == libvirt.VIR_ERR_SYSTEM_ERROR and \ e.get_error_domain() == libvirt.VIR_FROM_REMOTE: - logging.debug('Connection to libvirt broke') + logging.debug(_('Connection to libvirt broke')) return False raise - def get_uri_and_templates(self): + def get_uri(self): if FLAGS.libvirt_type == 'uml': uri = FLAGS.libvirt_uri or 'uml:///system' - template_file = FLAGS.libvirt_uml_xml_template - rescue_file = FLAGS.libvirt_rescue_uml_xml_template elif FLAGS.libvirt_type == 'xen': uri = FLAGS.libvirt_uri or 'xen:///' - template_file = FLAGS.libvirt_xen_xml_template - rescue_file = FLAGS.libvirt_rescue_xen_xml_template else: uri = FLAGS.libvirt_uri or 'qemu:///system' - template_file = FLAGS.libvirt_xml_template - rescue_file = FLAGS.libvirt_rescue_xml_template - return uri, template_file, rescue_file + return uri def _connect(self, uri, read_only): auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_NOECHOPROMPT], @@ -228,7 +199,7 @@ class LibvirtConnection(object): def _cleanup(self, instance): target = os.path.join(FLAGS.instances_path, instance['name']) - logging.info('instance %s: deleting instance files %s', + logging.info(_('instance %s: deleting instance files %s'), instance['name'], target) if os.path.exists(target): shutil.rmtree(target) @@ -270,7 +241,7 @@ class LibvirtConnection(object): mount_device = mountpoint.rpartition("/")[2] xml = self._get_disk_xml(virt_dom.XMLDesc(0), mount_device) if not xml: - raise exception.NotFound("No disk at %s" % mount_device) + raise exception.NotFound(_("No disk at %s") % mount_device) virt_dom.detachDevice(xml) @exception.wrap_exception @@ -286,10 +257,10 @@ class LibvirtConnection(object): db.instance_set_state(context.get_admin_context(), instance['id'], state) if state == power_state.RUNNING: - logging.debug('instance %s: rebooted', instance['name']) + logging.debug(_('instance %s: rebooted'), instance['name']) timer.stop() except Exception, exn: - logging.error('_wait_for_reboot failed: %s', exn) + logging.error(_('_wait_for_reboot failed: %s'), exn) db.instance_set_state(context.get_admin_context(), instance['id'], power_state.SHUTDOWN) @@ -298,6 +269,14 @@ class LibvirtConnection(object): timer.f = _wait_for_reboot return timer.start(interval=0.5, now=True) + @exception.wrap_exception + def pause(self, instance, callback): + raise exception.APIError("pause not supported for libvirt.") + + @exception.wrap_exception + def unpause(self, instance, callback): + raise exception.APIError("unpause not supported for libvirt.") + @exception.wrap_exception def rescue(self, instance): self.destroy(instance, False) @@ -316,10 +295,10 @@ class LibvirtConnection(object): state = self.get_info(instance['name'])['state'] db.instance_set_state(None, instance['id'], state) if state == power_state.RUNNING: - logging.debug('instance %s: rescued', instance['name']) + logging.debug(_('instance %s: rescued'), instance['name']) timer.stop() except Exception, exn: - logging.error('_wait_for_rescue failed: %s', exn) + logging.error(_('_wait_for_rescue failed: %s'), exn) db.instance_set_state(None, instance['id'], power_state.SHUTDOWN) @@ -344,7 +323,7 @@ class LibvirtConnection(object): NWFilterFirewall(self._conn).setup_nwfilters_for_instance(instance) self._create_image(instance, xml) self._conn.createXML(xml, 0) - logging.debug("instance %s: is running", instance['name']) + logging.debug(_("instance %s: is running"), instance['name']) timer = utils.LoopingCall(f=None) @@ -354,10 +333,10 @@ class LibvirtConnection(object): db.instance_set_state(context.get_admin_context(), instance['id'], state) if state == power_state.RUNNING: - logging.debug('instance %s: booted', instance['name']) + logging.debug(_('instance %s: booted'), instance['name']) timer.stop() except: - logging.exception('instance %s: failed to boot', + logging.exception(_('instance %s: failed to boot'), instance['name']) db.instance_set_state(context.get_admin_context(), instance['id'], @@ -372,7 +351,7 @@ class LibvirtConnection(object): virsh_output = virsh_output[0].strip() if virsh_output.startswith('/dev/'): - logging.info('cool, it\'s a device') + logging.info(_('cool, it\'s a device')) out, err = utils.execute("sudo dd if=%s iflag=nonblock" % virsh_output, check_exit_code=False) return out @@ -380,7 +359,7 @@ class LibvirtConnection(object): return '' def _append_to_file(self, data, fpath): - logging.info('data: %r, fpath: %r' % (data, fpath)) + logging.info(_('data: %r, fpath: %r') % (data, fpath)) fp = open(fpath, 'a+') fp.write(data) return fpath @@ -422,7 +401,7 @@ class LibvirtConnection(object): # TODO(termie): these are blocking calls, it would be great # if they weren't. - logging.info('instance %s: Creating image', inst['name']) + logging.info(_('instance %s: Creating image'), inst['name']) f = open(basepath('libvirt.xml'), 'w') f.write(libvirt_xml) f.close() @@ -441,18 +420,28 @@ class LibvirtConnection(object): if not os.path.exists(basepath('disk')): images.fetch(inst.image_id, basepath('disk-raw'), user, project) - if not os.path.exists(basepath('kernel')): - images.fetch(inst.kernel_id, basepath('kernel'), user, - project) - if not os.path.exists(basepath('ramdisk')): - images.fetch(inst.ramdisk_id, basepath('ramdisk'), user, - project) + + if inst['kernel_id']: + if not os.path.exists(basepath('kernel')): + images.fetch(inst['kernel_id'], basepath('kernel'), + user, project) + if inst['ramdisk_id']: + if not os.path.exists(basepath('ramdisk')): + images.fetch(inst['ramdisk_id'], basepath('ramdisk'), + user, project) def execute(cmd, process_input=None, check_exit_code=True): return utils.execute(cmd=cmd, process_input=process_input, check_exit_code=check_exit_code) + # For now, we assume that if we're not using a kernel, we're using a + # partitioned disk image where the target partition is the first + # partition + target_partition = None + if not inst['kernel_id']: + target_partition = "1" + key = str(inst['key_data']) net = None network_ref = db.network_get_by_instance(context.get_admin_context(), @@ -468,16 +457,24 @@ class LibvirtConnection(object): 'dns': network_ref['dns']} if key or net: if key: - logging.info('instance %s: injecting key into image %s', + logging.info(_('instance %s: injecting key into image %s'), inst['name'], inst.image_id) if net: - logging.info('instance %s: injecting net into image %s', - inst['name'], inst.image_id) - disk.inject_data(basepath('disk-raw'), key, net, - execute=execute) + logging.info(_('instance %s: injecting net into image %s'), + inst['name'], inst.image_id) + try: + disk.inject_data(basepath('disk-raw'), key, net, + partition=target_partition, + execute=execute) + except Exception as e: + # This could be a windows image, or a vmdk format disk + logging.warn(_('instance %s: ignoring error injecting data' + ' into image %s (%s)'), + inst['name'], inst.image_id, e) - if os.path.exists(basepath('disk')): - utils.execute('rm -f %s' % basepath('disk')) + if inst['kernel_id']: + if os.path.exists(basepath('disk')): + utils.execute('rm -f %s' % basepath('disk')) local_bytes = (instance_types.INSTANCE_TYPES[inst.instance_type] ['local_gb'] @@ -486,15 +483,21 @@ class LibvirtConnection(object): resize = True if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-': resize = False - disk.partition(basepath('disk-raw'), basepath('disk'), - local_bytes, resize, execute=execute) + + if inst['kernel_id']: + disk.partition(basepath('disk-raw'), basepath('disk'), + local_bytes, resize, execute=execute) + else: + os.rename(basepath('disk-raw'), basepath('disk')) + disk.extend(basepath('disk'), local_bytes, execute=execute) if FLAGS.libvirt_type == 'uml': utils.execute('sudo chown root %s' % basepath('disk')) def to_xml(self, instance, rescue=False): # TODO(termie): cache? - logging.debug('instance %s: starting toXML method', instance['name']) + logging.debug(_('instance %s: starting toXML method'), + instance['name']) network = db.project_get_network(context.get_admin_context(), instance['project_id']) # FIXME(vish): stick this in db @@ -523,20 +526,29 @@ class LibvirtConnection(object): 'mac_address': instance['mac_address'], 'ip_address': ip_address, 'dhcp_server': dhcp_server, - 'extra_params': extra_params} - if rescue: - libvirt_xml = self.rescue_xml % xml_info - else: - libvirt_xml = self.libvirt_xml % xml_info - logging.debug('instance %s: finished toXML method', instance['name']) + 'extra_params': extra_params, + 'rescue': rescue} + if not rescue: + if instance['kernel_id']: + xml_info['kernel'] = xml_info['basepath'] + "/kernel" - return libvirt_xml + if instance['ramdisk_id']: + xml_info['ramdisk'] = xml_info['basepath'] + "/ramdisk" + + xml_info['disk'] = xml_info['basepath'] + "/disk" + + xml = str(Template(self.libvirt_xml, searchList=[xml_info])) + logging.debug(_('instance %s: finished toXML method'), + instance['name']) + + return xml def get_info(self, instance_name): try: virt_dom = self._conn.lookupByName(instance_name) except: - raise exception.NotFound("Instance %s not found" % instance_name) + raise exception.NotFound(_("Instance %s not found") + % instance_name) (state, max_mem, mem, num_cpu, cpu_time) = virt_dom.info() return {'state': state, 'max_mem': max_mem, diff --git a/nova/virt/xenapi/network_utils.py b/nova/virt/xenapi/network_utils.py index 0129543944..ce2c68ce05 100644 --- a/nova/virt/xenapi/network_utils.py +++ b/nova/virt/xenapi/network_utils.py @@ -25,6 +25,7 @@ class NetworkHelper(): """ The class that wraps the helper methods together. """ + def __init__(self): return diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 2f5d78e759..badaaedc13 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -47,6 +47,7 @@ class VMHelper(): """ The class that wraps the helper methods together. """ + def __init__(self): return @@ -228,11 +229,7 @@ class VMHelper(): 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"]} + diags = {} xml = get_rrd(host_ip, record["uuid"]) if xml: rrd = minidom.parseString(xml) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 3034df9e1d..3b00ce8bfc 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -34,6 +34,7 @@ class VMOps(object): """ Management class for VM-related tasks """ + def __init__(self, session): global XenAPI if XenAPI is None: @@ -43,12 +44,16 @@ class VMOps(object): VMHelper.late_import() def list_instances(self): - """ List VM instances """ - return [self._session.get_xenapi().VM.get_name_label(vm) \ - for vm in self._session.get_xenapi().VM.get_all()] + """List VM instances""" + vms = [] + for vm in self._session.get_xenapi().VM.get_all(): + rec = self._session.get_xenapi().VM.get_record(vm) + if not rec["is_a_template"] and not rec["is_control_domain"]: + vms.append(rec["name_label"]) + return vms def spawn(self, instance): - """ Create VM instance """ + """Create VM instance""" vm = VMHelper.lookup(self._session, instance.name) if vm is not None: raise Exception('Attempted to create non-unique name %s' % @@ -80,16 +85,16 @@ class VMOps(object): vm_ref) def reboot(self, instance): - """ Reboot VM instance """ + """Reboot VM instance""" instance_name = instance.name vm = VMHelper.lookup(self._session, instance_name) if vm is None: raise Exception('instance not present %s' % instance_name) task = self._session.call_xenapi('Async.VM.clean_reboot', vm) - self._session.wait_for_task(task) + self._session.wait_for_task(instance.id, task) def destroy(self, instance): - """ Destroy VM instance """ + """Destroy VM instance""" vm = VMHelper.lookup(self._session, instance.name) if vm is None: # Don't complain, just return. This lets us clean up instances @@ -100,7 +105,7 @@ class VMOps(object): try: task = self._session.call_xenapi('Async.VM.hard_shutdown', vm) - self._session.wait_for_task(task) + self._session.wait_for_task(instance.id, task) except XenAPI.Failure, exc: logging.warn(exc) # Disk clean-up @@ -108,17 +113,43 @@ class VMOps(object): for vdi in vdis: try: task = self._session.call_xenapi('Async.VDI.destroy', vdi) - self._session.wait_for_task(task) + self._session.wait_for_task(instance.id, task) except XenAPI.Failure, exc: logging.warn(exc) try: task = self._session.call_xenapi('Async.VM.destroy', vm) - self._session.wait_for_task(task) + self._session.wait_for_task(instance.id, task) except XenAPI.Failure, exc: logging.warn(exc) + def _wait_with_callback(self, instance_id, task, callback): + ret = None + try: + ret = self._session.wait_for_task(instance_id, task) + except XenAPI.Failure, exc: + logging.warn(exc) + callback(ret) + + def pause(self, instance, callback): + """Pause VM instance""" + instance_name = instance.name + vm = VMHelper.lookup(self._session, instance_name) + if vm is None: + raise Exception('instance not present %s' % instance_name) + task = self._session.call_xenapi('Async.VM.pause', vm) + self._wait_with_callback(instance.id, task, callback) + + def unpause(self, instance, callback): + """Unpause VM instance""" + instance_name = instance.name + vm = VMHelper.lookup(self._session, instance_name) + if vm is None: + raise Exception('instance not present %s' % instance_name) + task = self._session.call_xenapi('Async.VM.unpause', vm) + self._wait_with_callback(instance.id, task, callback) + def get_info(self, instance_id): - """ Return data about VM instance """ + """Return data about VM instance""" vm = VMHelper.lookup_blocking(self._session, instance_id) if vm is None: raise Exception('instance not present %s' % instance_id) @@ -134,6 +165,6 @@ class VMOps(object): return VMHelper.compile_diagnostics(self._session, rec) def get_console_output(self, instance): - """ Return snapshot of console """ + """Return snapshot of console""" # TODO: implement this to fix pylint! return 'FAKE CONSOLE OUTPUT of instance' diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xenapi/volumeops.py index a4c7a38619..1943ccab0c 100644 --- a/nova/virt/xenapi/volumeops.py +++ b/nova/virt/xenapi/volumeops.py @@ -20,6 +20,7 @@ Management class for Storage-related functions (attach, detach, etc). class VolumeOps(object): + def __init__(self, session): self._session = session diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 6beb08f5e8..146e2f153a 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -54,6 +54,8 @@ import xmlrpclib from eventlet import event from eventlet import tpool +from nova import context +from nova import db from nova import utils from nova import flags from nova.virt.xenapi.vmops import VMOps @@ -93,38 +95,47 @@ def get_connection(_): username = FLAGS.xenapi_connection_username password = FLAGS.xenapi_connection_password if not url or password is None: - raise Exception('Must specify xenapi_connection_url, ' - 'xenapi_connection_username (optionally), and ' - 'xenapi_connection_password to use ' - 'connection_type=xenapi') + raise Exception(_('Must specify xenapi_connection_url, ' + 'xenapi_connection_username (optionally), and ' + 'xenapi_connection_password to use ' + 'connection_type=xenapi')) return XenAPIConnection(url, username, password) class XenAPIConnection(object): - """ A connection to XenServer or Xen Cloud Platform """ + """A connection to XenServer or Xen Cloud Platform""" + def __init__(self, url, user, pw): session = XenAPISession(url, user, pw) self._vmops = VMOps(session) self._volumeops = VolumeOps(session) def list_instances(self): - """ List VM instances """ + """List VM instances""" return self._vmops.list_instances() def spawn(self, instance): - """ Create VM instance """ + """Create VM instance""" self._vmops.spawn(instance) def reboot(self, instance): - """ Reboot VM instance """ + """Reboot VM instance""" self._vmops.reboot(instance) def destroy(self, instance): - """ Destroy VM instance """ + """Destroy VM instance""" self._vmops.destroy(instance) + def pause(self, instance, callback): + """Pause VM instance""" + self._vmops.pause(instance, callback) + + def unpause(self, instance, callback): + """Unpause paused VM instance""" + self._vmops.unpause(instance, callback) + def get_info(self, instance_id): - """ Return data about VM instance """ + """Return data about VM instance""" return self._vmops.get_info(instance_id) def get_diagnostics(self, instance_id): @@ -132,32 +143,33 @@ class XenAPIConnection(object): return self._vmops.get_diagnostics(instance_id) def get_console_output(self, instance): - """ Return snapshot of console """ + """Return snapshot of console""" return self._vmops.get_console_output(instance) def attach_volume(self, instance_name, device_path, mountpoint): - """ Attach volume storage to VM instance """ + """Attach volume storage to VM instance""" return self._volumeops.attach_volume(instance_name, device_path, mountpoint) def detach_volume(self, instance_name, mountpoint): - """ Detach volume storage to VM instance """ + """Detach volume storage to VM instance""" return self._volumeops.detach_volume(instance_name, mountpoint) class XenAPISession(object): - """ The session to invoke XenAPI SDK calls """ + """The session to invoke XenAPI SDK calls""" + def __init__(self, url, user, pw): self._session = XenAPI.Session(url) self._session.login_with_password(user, pw) def get_xenapi(self): - """ Return the xenapi object """ + """Return the xenapi object""" return self._session.xenapi def get_xenapi_host(self): - """ Return the xenapi host """ + """Return the xenapi host""" return self._session.xenapi.session.get_this_host(self._session.handle) def call_xenapi(self, method, *args): @@ -173,46 +185,57 @@ class XenAPISession(object): self._session.xenapi.Async.host.call_plugin, self.get_xenapi_host(), plugin, fn, args) - def wait_for_task(self, task): + def wait_for_task(self, instance_id, task): """Return a Deferred that will give the result of the given task. The task is polled until it completes.""" done = event.Event() - loop = utils.LoopingCall(self._poll_task, task, done) + loop = utils.LoopingCall(self._poll_task, instance_id, task, done) loop.start(FLAGS.xenapi_task_poll_interval, now=True) rv = done.wait() loop.stop() return rv - def _poll_task(self, task, done): + def _poll_task(self, instance_id, task, done): """Poll the given XenAPI task, and fire the given Deferred if we get a result.""" try: - #logging.debug('Polling task %s...', task) + name = self._session.xenapi.task.get_name_label(task) status = self._session.xenapi.task.get_status(task) - if status == 'pending': + action = dict( + instance_id=int(instance_id), + action=name, + error=None) + if status == "pending": return - elif status == 'success': + elif status == "success": result = self._session.xenapi.task.get_result(task) - logging.info('Task %s status: success. %s', task, result) + logging.info(_("Task [%s] %s status: success %s") % ( + name, + task, + result)) done.send(_parse_xmlrpc_value(result)) else: error_info = self._session.xenapi.task.get_error_info(task) - logging.warn('Task %s status: %s. %s', task, status, - error_info) + action["error"] = str(error_info) + logging.warn(_("Task [%s] %s status: %s %s") % ( + name, + task, + status, + error_info)) done.send_exception(XenAPI.Failure(error_info)) - #logging.debug('Polling task %s done.', task) + db.instance_action_create(context.get_admin_context(), action) except XenAPI.Failure, exc: logging.warn(exc) done.send_exception(*sys.exc_info()) def _unwrap_plugin_exceptions(func, *args, **kwargs): - """ Parse exception details """ + """Parse exception details""" try: return func(*args, **kwargs) except XenAPI.Failure, exc: - logging.debug("Got exception: %s", exc) + logging.debug(_("Got exception: %s"), exc) if (len(exc.details) == 4 and exc.details[0] == 'XENAPI_PLUGIN_EXCEPTION' and exc.details[2] == 'Failure'): @@ -225,7 +248,7 @@ def _unwrap_plugin_exceptions(func, *args, **kwargs): else: raise except xmlrpclib.ProtocolError, exc: - logging.debug("Got exception: %s", exc) + logging.debug(_("Got exception: %s"), exc) raise diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 1cd4c1fd45..8353b9712a 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -73,14 +73,14 @@ class VolumeDriver(object): tries = tries + 1 if tries >= FLAGS.num_shell_tries: raise - logging.exception("Recovering from a failed execute." - "Try number %s", tries) + logging.exception(_("Recovering from a failed execute." + "Try number %s"), tries) time.sleep(tries ** 2) def check_for_setup_error(self): """Returns an error if prerequisites aren't met""" if not os.path.isdir("/dev/%s" % FLAGS.volume_group): - raise exception.Error("volume group %s doesn't exist" + raise exception.Error(_("volume group %s doesn't exist") % FLAGS.volume_group) def create_volume(self, volume): @@ -205,7 +205,7 @@ class FakeAOEDriver(AOEDriver): @staticmethod def fake_execute(cmd, *_args, **_kwargs): """Execute that simply logs the command.""" - logging.debug("FAKE AOE: %s", cmd) + logging.debug(_("FAKE AOE: %s"), cmd) return (None, None) @@ -310,5 +310,5 @@ class FakeISCSIDriver(ISCSIDriver): @staticmethod def fake_execute(cmd, *_args, **_kwargs): """Execute that simply logs the command.""" - logging.debug("FAKE ISCSI: %s", cmd) + logging.debug(_("FAKE ISCSI: %s"), cmd) return (None, None) diff --git a/nova/volume/manager.py b/nova/volume/manager.py index 7da125cac0..966334c50d 100644 --- a/nova/volume/manager.py +++ b/nova/volume/manager.py @@ -81,7 +81,7 @@ class VolumeManager(manager.Manager): self.driver.check_for_setup_error() ctxt = context.get_admin_context() volumes = self.db.volume_get_all_by_host(ctxt, self.host) - logging.debug("Re-exporting %s volumes", len(volumes)) + logging.debug(_("Re-exporting %s volumes"), len(volumes)) for volume in volumes: self.driver.ensure_export(ctxt, volume) @@ -89,7 +89,7 @@ class VolumeManager(manager.Manager): """Creates and exports the volume.""" context = context.elevated() volume_ref = self.db.volume_get(context, volume_id) - logging.info("volume %s: creating", volume_ref['name']) + logging.info(_("volume %s: creating"), volume_ref['name']) self.db.volume_update(context, volume_id, @@ -98,18 +98,18 @@ class VolumeManager(manager.Manager): # before passing it to the driver. volume_ref['host'] = self.host - logging.debug("volume %s: creating lv of size %sG", + logging.debug(_("volume %s: creating lv of size %sG"), volume_ref['name'], volume_ref['size']) self.driver.create_volume(volume_ref) - logging.debug("volume %s: creating export", volume_ref['name']) + logging.debug(_("volume %s: creating export"), volume_ref['name']) self.driver.create_export(context, volume_ref) now = datetime.datetime.utcnow() self.db.volume_update(context, volume_ref['id'], {'status': 'available', 'launched_at': now}) - logging.debug("volume %s: created successfully", volume_ref['name']) + logging.debug(_("volume %s: created successfully"), volume_ref['name']) return volume_id def delete_volume(self, context, volume_id): @@ -117,15 +117,15 @@ class VolumeManager(manager.Manager): context = context.elevated() volume_ref = self.db.volume_get(context, volume_id) if volume_ref['attach_status'] == "attached": - raise exception.Error("Volume is still attached") + raise exception.Error(_("Volume is still attached")) if volume_ref['host'] != self.host: - raise exception.Error("Volume is not local to this node") - logging.debug("volume %s: removing export", volume_ref['name']) + raise exception.Error(_("Volume is not local to this node")) + logging.debug(_("volume %s: removing export"), volume_ref['name']) self.driver.remove_export(context, volume_ref) - logging.debug("volume %s: deleting", volume_ref['name']) + logging.debug(_("volume %s: deleting"), volume_ref['name']) self.driver.delete_volume(volume_ref) self.db.volume_destroy(context, volume_id) - logging.debug("volume %s: deleted successfully", volume_ref['name']) + logging.debug(_("volume %s: deleted successfully"), volume_ref['name']) return True def setup_compute_volume(self, context, volume_id): diff --git a/tools/pip-requires b/tools/pip-requires index 52451b8cbd..e9559521b5 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -2,6 +2,7 @@ SQLAlchemy==0.6.3 pep8==0.5.0 pylint==0.19 IPy==0.70 +Cheetah==2.4.2.1 M2Crypto==0.20.2 amqplib==0.6.1 anyjson==0.2.4 From 9e42bc18a12fe19ae333a3447d153bf3796d54d7 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 22 Dec 2010 21:38:06 +0000 Subject: [PATCH 217/229] add missing flag --- nova/network/linux_net.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 771f1c932e..931a895546 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -51,6 +51,8 @@ flags.DEFINE_bool('use_nova_chains', False, 'use the nova_ routing chains instead of default') flags.DEFINE_string('dns_server', None, 'if set, uses specific dns server for dnsmasq') +flags.DEFINE_string('dmz_cidr', '10.128.0.0/24', + 'dmz range that should be accepted') def metadata_forward(): From 1509d51c7e9be04f8ca7aa9fb50d7b06b20a4e71 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 22 Dec 2010 21:41:40 +0000 Subject: [PATCH 218/229] remove extra files that slipped in --- nova/virt/libvirt.uml.xml.template.THIS | 27 ------------------------- 1 file changed, 27 deletions(-) delete mode 100644 nova/virt/libvirt.uml.xml.template.THIS diff --git a/nova/virt/libvirt.uml.xml.template.THIS b/nova/virt/libvirt.uml.xml.template.THIS deleted file mode 100644 index 506f2ef72c..0000000000 --- a/nova/virt/libvirt.uml.xml.template.THIS +++ /dev/null @@ -1,27 +0,0 @@ - - %(name)s - %(memory_kb)s - - %(type)s - /usr/bin/linux - /dev/ubda1 - - - - - - - - - - - - - %(extra_params)s - - - - - - - From 775958e3a020b6b4b4c9fd4777aa72f7e9b0bdbc Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 22 Dec 2010 15:50:26 -0600 Subject: [PATCH 219/229] Accidentally yanked the datetime line in auth --- nova/api/openstack/__init__.py | 2 +- nova/api/openstack/auth.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index b18edc8e7a..c49399f281 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -74,7 +74,7 @@ class API(wsgi.Middleware): return req.get_response(self.application) except Exception as ex: logging.warn(_("Caught error: %s") % str(ex)) - logging.debug(traceback.format_exc()) + logging.error(traceback.format_exc()) exc = webob.exc.HTTPInternalServerError(explanation=str(ex)) return faults.Fault(exc) diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 72ad4ffa9e..c9d21ed49b 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License.import datetime +import datetime import hashlib import json import time From 0704c0c4073f6c03959c113f90c51dfe4d72fd76 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 22 Dec 2010 21:55:11 +0000 Subject: [PATCH 220/229] pep8 fix --- nova/fakerabbit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/fakerabbit.py b/nova/fakerabbit.py index 42daa9767f..79d8b894d7 100644 --- a/nova/fakerabbit.py +++ b/nova/fakerabbit.py @@ -28,6 +28,7 @@ from eventlet import greenthread EXCHANGES = {} QUEUES = {} + class Message(base.BaseMessage): pass From a7e5a4a39b93b32974ca82b77391368c4f01cdd8 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 22 Dec 2010 22:54:43 +0000 Subject: [PATCH 221/229] removed extra files --- .../libvirt.rescue.qemu.xml.template.THIS | 38 ------------------- .../virt/libvirt.rescue.uml.xml.template.THIS | 31 --------------- 2 files changed, 69 deletions(-) delete mode 100644 nova/virt/libvirt.rescue.qemu.xml.template.THIS delete mode 100644 nova/virt/libvirt.rescue.uml.xml.template.THIS diff --git a/nova/virt/libvirt.rescue.qemu.xml.template.THIS b/nova/virt/libvirt.rescue.qemu.xml.template.THIS deleted file mode 100644 index a3b88106cc..0000000000 --- a/nova/virt/libvirt.rescue.qemu.xml.template.THIS +++ /dev/null @@ -1,38 +0,0 @@ - - %(name)s - - hvm - %(basepath)s/rescue-kernel - %(basepath)s/rescue-ramdisk - root=/dev/vda1 console=ttyS0 - - - - - %(memory_kb)s - %(vcpus)s - - - - - - - - - - - - - - - - - %(extra_params)s - - - - - - - - diff --git a/nova/virt/libvirt.rescue.uml.xml.template.THIS b/nova/virt/libvirt.rescue.uml.xml.template.THIS deleted file mode 100644 index a254692d4a..0000000000 --- a/nova/virt/libvirt.rescue.uml.xml.template.THIS +++ /dev/null @@ -1,31 +0,0 @@ - - %(name)s - %(memory_kb)s - - %(type)s - /usr/bin/linux - /dev/ubda1 - - - - - - - - - - - - - - - - - %(extra_params)s - - - - - - - From 93dcd9088108b17c44337f556d0c624e802ba788 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 23 Dec 2010 01:32:57 +0000 Subject: [PATCH 222/229] fix commits from Anthony and Vish that were committed with the wrong email --- .mailmap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.mailmap b/.mailmap index 8041e23418..c01f964d25 100644 --- a/.mailmap +++ b/.mailmap @@ -24,7 +24,7 @@ + + # These are from people who failed to set a proper committer -. . -. From 588b39981d03c61c7b4eafdb489467ab57540d13 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 23 Dec 2010 02:03:39 +0000 Subject: [PATCH 223/229] Add Ryan Lane as well --- .mailmap | 3 +-- Authors | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.mailmap b/.mailmap index c01f964d25..9ab7db743f 100644 --- a/.mailmap +++ b/.mailmap @@ -26,5 +26,4 @@ -# These are from people who failed to set a proper committer -. + diff --git a/Authors b/Authors index 0b048becb9..250b3b2adb 100644 --- a/Authors +++ b/Authors @@ -24,6 +24,7 @@ Michael Gundlach Monty Taylor Paul Voccio Rick Clark +Ryan Lane Ryan Lucio Sandy Walsh Soren Hansen From 12a9dc88f6ae947d005568dd2e644566cd1a9677 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 22 Dec 2010 21:14:06 -0600 Subject: [PATCH 224/229] And the common module --- nova/api/openstack/common.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 29e9a86233..83919cc23e 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -1,3 +1,20 @@ +# 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. + def limited(items, req): """Return a slice of items according to requested offset and limit. From c273c2b93471ad0d3ab4990458147c253d22bdc5 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Thu, 23 Dec 2010 08:57:04 -0800 Subject: [PATCH 225/229] Added reference in setup.py so that python setup.py test works now. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index d88bc1e6f8..1abf4d9fe9 100644 --- a/setup.py +++ b/setup.py @@ -58,6 +58,7 @@ setup(name='nova', 'build_sphinx' : local_BuildDoc }, packages=find_packages(exclude=['bin', 'smoketests']), include_package_data=True, + test_suite='nose.collector', scripts=['bin/nova-api', 'bin/nova-compute', 'bin/nova-dhcpbridge', From ba6a99f926180d47870dcb18e4387d18cddad9b0 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Thu, 23 Dec 2010 11:58:13 -0600 Subject: [PATCH 226/229] Superfluous images include and added basic routes for shared ip groups --- nova/api/openstack/sharedipgroups.py | 20 +++++++++++++++++++- nova/tests/api/openstack/test_servers.py | 1 - 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/sharedipgroups.py b/nova/api/openstack/sharedipgroups.py index e805ca9f7b..423ee61e15 100644 --- a/nova/api/openstack/sharedipgroups.py +++ b/nova/api/openstack/sharedipgroups.py @@ -19,4 +19,22 @@ from nova import wsgi class Controller(wsgi.Controller): - pass + """ The Shared IP Groups Controller for the Openstack API """ + + def index(self, req): + raise NotImplementedError + + def show(self, req, id): + raise NotImplementedError + + def update(self, req, id): + raise NotImplementedError + + def delete(self, req, id): + raise NotImplementedError + + def detail(self, req): + raise NotImplementedError + + def create(self, req): + raise NotImplementedError diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 97cf3ed655..3820f5f271 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -27,7 +27,6 @@ import nova.api.openstack from nova.api.openstack import servers import nova.db.api from nova.db.sqlalchemy.models import Instance -import nova.image import nova.rpc from nova.tests.api.openstack import fakes From cb679a01e5905e4f7316f81de7c9ead9dc6536b8 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Thu, 23 Dec 2010 13:17:53 -0600 Subject: [PATCH 227/229] Pep8 cleanup --- nova/api/openstack/auth.py | 1 + nova/api/openstack/common.py | 2 ++ nova/api/openstack/ratelimiting/__init__.py | 1 + nova/api/openstack/sharedipgroups.py | 2 +- 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index c9d21ed49b..e24e58fd3d 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -34,6 +34,7 @@ from nova.api.openstack import faults FLAGS = flags.FLAGS + class AuthMiddleware(wsgi.Middleware): """Authorize the openstack API request or return an HTTP Forbidden.""" diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 83919cc23e..ac0572c967 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. + def limited(items, req): """Return a slice of items according to requested offset and limit. @@ -25,6 +26,7 @@ def limited(items, req): If limit is not specified, 0, or > 1000, defaults to 1000. """ + offset = int(req.GET.get('offset', 0)) limit = int(req.GET.get('limit', 0)) if not limit: diff --git a/nova/api/openstack/ratelimiting/__init__.py b/nova/api/openstack/ratelimiting/__init__.py index 8ca575b360..91a8b2e558 100644 --- a/nova/api/openstack/ratelimiting/__init__.py +++ b/nova/api/openstack/ratelimiting/__init__.py @@ -32,6 +32,7 @@ PER_MINUTE = 60 PER_HOUR = 60 * 60 PER_DAY = 60 * 60 * 24 + class RateLimitingMiddleware(wsgi.Middleware): """Rate limit incoming requests according to the OpenStack rate limits.""" diff --git a/nova/api/openstack/sharedipgroups.py b/nova/api/openstack/sharedipgroups.py index 423ee61e15..75d02905c2 100644 --- a/nova/api/openstack/sharedipgroups.py +++ b/nova/api/openstack/sharedipgroups.py @@ -32,7 +32,7 @@ class Controller(wsgi.Controller): def delete(self, req, id): raise NotImplementedError - + def detail(self, req): raise NotImplementedError From e275fbd8e16e6dc55c54072aa162815d522f9242 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Thu, 23 Dec 2010 13:30:24 -0600 Subject: [PATCH 228/229] One more time --- nova/tests/api/openstack/fakes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index f773b26a7b..79663e43a4 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -94,7 +94,7 @@ def stub_out_auth(stubs): def stub_out_rate_limiting(stubs): def fake_rate_init(self, app): - super(nova.api.openstack.ratelimiting.RateLimitingMiddleware, self).__init__(app) + super(ratelimiting.RateLimitingMiddleware, self).__init__(app) self.application = app stubs.Set(nova.api.openstack.ratelimiting.RateLimitingMiddleware, From f361891d100bbc9a5fb0f2ffd5424b6c69100133 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Fri, 24 Dec 2010 19:17:02 +0000 Subject: [PATCH 229/229] remove requirement of sudo on tests --- nova/tests/test_xenapi.py | 1 + nova/tests/xenapi/stubs.py | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index b5d3ea3958..ed2e4ffde7 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -48,6 +48,7 @@ class XenAPIVolumeTestCase(test.TestCase): FLAGS.xenapi_connection_url = 'test_url' FLAGS.xenapi_connection_password = 'test_pass' fakes.stub_out_db_instance_api(self.stubs) + stubs.stub_out_get_target(self.stubs) fake.reset() self.values = {'name': 1, 'id': 1, 'project_id': 'fake', diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 1dacad6a36..a7e592fee7 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -18,12 +18,13 @@ from nova.virt import xenapi_conn from nova.virt.xenapi import fake +from nova.virt.xenapi import volume_utils def stubout_session(stubs, cls): - """ Stubs out two methods from XenAPISession """ + """Stubs out two methods from XenAPISession""" def fake_import(self): - """ Stubs out get_imported_xenapi of XenAPISession """ + """Stubs out get_imported_xenapi of XenAPISession""" fake_module = 'nova.virt.xenapi.fake' from_list = ['fake'] return __import__(fake_module, globals(), locals(), from_list, -1) @@ -34,6 +35,14 @@ def stubout_session(stubs, cls): fake_import) +def stub_out_get_target(stubs): + """Stubs out _get_target in volume_utils""" + def fake_get_target(volume_id): + return (None, None) + + stubs.Set(volume_utils, '_get_target', fake_get_target) + + class FakeSessionForVMTests(fake.SessionBase): """ Stubs out a XenAPISession for VM tests """ def __init__(self, uri):