Merge lp:~chiradeep/nova/msft-hyper-v-support into lp:~hudson-openstack/nova/trunk

Proposed by Chiradeep Vittal
Status: Merged
Approved by: Soren Hansen
Approved revision: 360
Merged at revision: 522
Proposed branch: lp:~chiradeep/nova/msft-hyper-v-support
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 657 lines (+572/-8)
6 files modified
.mailmap (+1/-0)
Authors (+1/-0)
nova/tests/hyperv_unittest.py (+71/-0)
nova/virt/connection.py (+3/-0)
nova/virt/hyperv.py (+459/-0)
nova/virt/images.py (+37/-8)
To merge this branch: bzr merge lp:~chiradeep/nova/msft-hyper-v-support
Reviewer Review Type Date Requested Status
Soren Hansen (community) Approve
Armando Migliaccio (community) Approve
Vish Ishaya (community) Approve
Devin Carlen (community) Approve
Chiradeep Vittal (community) Approve
Jay Pipes (community) Approve
Review via email: mp+43724@code.launchpad.net

This proposal supersedes a proposal from 2010-10-14.

Description of the change

Introduces basic support for spawning, rebooting and destroying vms when using Microsoft Hyper-V as the hypervisor.
Images need to be in VHD format. Note that although Hyper-V doesn't accept kernel and ramdisk
separate from the image, the nova objectstore api still expects an image to have an associated aki and ari. You can use dummy aki and ari images -- the hyper-v driver won't use them or try to download them.
Requires Python's WMI module.

To post a comment you must log in.
Revision history for this message
Jay Pipes (jaypipes) wrote : Posted in a previous version of this proposal
Download full text (3.6 KiB)

Hi Chiradeep!

Thanks for the contribution and a start of Windows support in OpenStack! :)

Here are my review notes for you.

1) Documentation is sparse :)

It would be excellent to get some overall design and module documentation for your work. :) This will help those coders in the future who are maintaining and enhancing the hyper-V work. Please see Ewan Mellor's excellent documentation in the xenapi virtualization layer for an example of the kind of documentation that is helpful for maintainers.

This documentation is, IMHO, pretty critical considering the sorry state of python-wmi documentation regarding virtual environments.

Additional code comments would be very useful in places such as the following:

181 + (job, ret_val) = vs_man_svc.DefineVirtualSystem(
182 + [], None, vs_gs_data.GetText_(1))[1:]

What does vs_gs_data.GetText(1) return? What does sv_man_svc.DefineVirtualSystem() return? Without understanding these types of things, maintaining developers have zero chance of successfully fixing bugs that may occur :)

2) Please use Nova's exceptions, which are tested for in the test suites and programs, instead of generic Exception:

402 + raise Exception('instance not present %s' % instance_id)

Should be replaced with:

from nova import exception
...
raise exception.NotFound('instance not found %s' % instance_id)

same for here:

360 + if vm is None:
361 + raise Exception('instance not present %s' % instance.name)

3) Your rewrite of the _fetch_s3_image() function is good, however, it removes the efficiency of using a shared process pool in Linux/BSD environments in favour of cross-platform compatibility. It would be nice to keep the existing code and use your rewrite in an if sys.platform.startswith('win'): block.

============

And here are some style-related things to fix up...

1) Please make sure imports are ordered according to the instructions in the HACKING file. In particular, please make sure system lib imports are first, in alphabetical order, followed by a newline, then imports of 3rd-party libraries (like wmi) in alphabetical order, followed by a newline and imports of nova modules.

2) Something is amiss here:

118 +HYPERV_POWER_STATE = {
119 + 3 : power_state.SHUTDOWN,
120 + 2 : power_state.RUNNING,
121 + 32768 : power_state.PAUSED,
122 + 32768: power_state.PAUSED, # TODO
123 + 3 : power_state.CRASHED
124 +}

The indexes 3 and 32768 are doubled up.

3) Define constants for "magic numbers"

183 + if (ret_val == 4096 ): #WMI job started

Feel free to either create a constant WMI_JOB_STARTED and use that instead of the number 4096 and putting a comment at the end of the line. Self-documenting constants++ :)

Same goes for these locations:

323 + while job.JobState == 4: #job started
327 + if (job.JobState != 7): #job success
385 + if (ret_val == 4096 ): #WMI job started
442 + if (return_val == 4096 ): #WMI job started

4) This looks hacky :)

212 + vcpus = long(str(instance['vcpus']))

I recommend just:

vcpus = long(instance['vcpus'])

5) Please remove extraneous commented-out debugging stuff:

213 + #vcpus = 1

6) Python != C ;)

Please remove outer extraneous parentheses:

286 + if (ret_va...

Read more...

review: Needs Fixing
Revision history for this message
Jay Pipes (jaypipes) wrote : Posted in a previous version of this proposal

Also, I forgot to mention that we'd really need some Hyper-V specific test cases when Monty and others get around to getting our testing hardware and platform up-to-speed with Windows stuff...

Revision history for this message
Chiradeep Vittal (chiradeep) wrote : Posted in a previous version of this proposal
Download full text (5.0 KiB)

Thanks Jay. I'll put together something along the lines of the XenAPI driver.
The WMI calls are well documented on MSDN. Also, the Python WMI library generates the docstring for any WMI function that lists the parameters and return tuple.
Not sure that I want to regurgitate *all* those details in this doc, but I'll see if I can highlight some of the exemplary calls.

Some more comments below, preceded by >>>>
________________________________________
From: <email address hidden> [<email address hidden>] On Behalf Of Jay Pipes [<email address hidden>]
Sent: Monday, October 11, 2010 10:11 AM
To: <email address hidden>
Subject: Re: [Merge] lp:~chiradeep/nova/msft-hyper-v-support into lp:nova

Review: Needs Fixing
Hi Chiradeep!

Thanks for the contribution and a start of Windows support in OpenStack! :)

Here are my review notes for you.

1) Documentation is sparse :)

It would be excellent to get some overall design and module documentation for your work. :) This will help those coders in the future who are maintaining and enhancing the hyper-V work. Please see Ewan Mellor's excellent documentation in the xenapi virtualization layer for an example of the kind of documentation that is helpful for maintainers.

This documentation is, IMHO, pretty critical considering the sorry state of python-wmi documentation regarding virtual environments.

Additional code comments would be very useful in places such as the following:

181 + (job, ret_val) = vs_man_svc.DefineVirtualSystem(
182 + [], None, vs_gs_data.GetText_(1))[1:]

What does vs_gs_data.GetText(1) return? What does sv_man_svc.DefineVirtualSystem() return? Without understanding these types of things, maintaining developers have zero chance of successfully fixing bugs that may occur :)

>>>> This is simply how WMI works. When you want to pass in object references, you generate an XML representation using GetText. I think that whoever maintains this
needs a bookmark to the MSDN libraries and the PowerShell libraries. Equally someone maintaining the XenApi driver or libvirt driver needs to *get* the object model and the conventions.

2) Please use Nova's exceptions, which are tested for in the test suites and programs, instead of generic Exception:

402 + raise Exception('instance not present %s' % instance_id)

Should be replaced with:

from nova import exception
...
raise exception.NotFound('instance not found %s' % instance_id)

same for here:

360 + if vm is None:
361 + raise Exception('instance not present %s' % instance.name)

>>>> sure.

3) Your rewrite of the _fetch_s3_image() function is good, however, it removes the efficiency of using a shared process pool in Linux/BSD environments in favour of cross-platform compatibility. It would be nice to keep the existing code and use your rewrite in an if sys.platform.startswith('win'): block.

>>>> sure

============

And here are some style-related things to fix up...

1) Please make sure imports are ordered according to the instructions in the HACKING file. In particular, please make sure system lib imports are first, in alphabetical order, followed by a newline, then imports of 3rd-party libraries ...

Read more...

Revision history for this message
Jay Pipes (jaypipes) wrote : Posted in a previous version of this proposal

> Thanks Jay. I'll put together something along the lines of the XenAPI driver.
> The WMI calls are well documented on MSDN. Also, the Python WMI library
> generates the docstring for any WMI function that lists the parameters and
> return tuple.
> Not sure that I want to regurgitate *all* those details in this doc, but I'll
> see if I can highlight some of the exemplary calls.

Well, just do your best to provide a general overview of how the WMI calls are made, what's expected back from them, etc. :)

> Additional code comments would be very useful in places such as the following:
>
> 181 + (job, ret_val) = vs_man_svc.DefineVirtualSystem(
> 182 + [], None, vs_gs_data.GetText_(1))[1:]
>
> What does vs_gs_data.GetText(1) return? What does
> sv_man_svc.DefineVirtualSystem() return? Without understanding these types of
> things, maintaining developers have zero chance of successfully fixing bugs
> that may occur :)
>
> >>>> This is simply how WMI works. When you want to pass in object references,
> you generate an XML representation using GetText. I think that whoever
> maintains this
> needs a bookmark to the MSDN libraries and the PowerShell libraries. Equally
> someone maintaining the XenApi driver or libvirt driver needs to *get* the
> object model and the conventions.

I will hold off judgment about the WMI and PowerShell APIs...

If you could just put the above note in your general documentation, and even provide a couple of those links for MSDN/PowerShell docs in there, that would be great! :)

Cheers!
jay

Revision history for this message
Soren Hansen (soren) wrote : Posted in a previous version of this proposal
Download full text (27.0 KiB)

On 13-10-2010 09:15, Chiradeep Vittal wrote:
> Chiradeep Vittal has proposed merging lp:~chiradeep/nova/msft-hyper-v-support into lp:nova.
>
> Requested reviews:
> Jay Pipes (jaypipes)
>
>
> Introduces basic support for spawning, rebooting and destroying vms when using Microsoft Hyper-V as the hypervisor.
> Images need to be in VHD format. Note that although Hyper-V doesn't accept kernel and ramdisk
> separate from the image, the nova objectstore api still expects an image to have an associated aki and ari. You can use dummy aki and ari images -- the hyper-v driver won't use them or try to download them.
> Requires Python's WMI module.
>

> === modified file 'nova/compute/manager.py'
> --- nova/compute/manager.py 2010-10-12 20:18:29 +0000
> +++ nova/compute/manager.py 2010-10-13 07:15:24 +0000
> @@ -39,6 +39,8 @@
> 'where instances are stored on disk')
> flags.DEFINE_string('compute_driver', 'nova.virt.connection.get_connection',
> 'Driver to use for volume creation')
> +flags.DEFINE_string('images_path', utils.abspath('../images'),
> + 'path to decrypted local images if not using s3')

Each flag must only be defined once. images_path is also defined in
nova.objectstore.images. Either import that or move the flag to a common
place and import that.

> === modified file 'nova/virt/connection.py'
> --- nova/virt/connection.py 2010-08-30 13:19:14 +0000
> +++ nova/virt/connection.py 2010-10-13 07:15:24 +0000
> @@ -26,6 +26,7 @@
> from nova.virt import fake
> from nova.virt import libvirt_conn
> from nova.virt import xenapi
> +from nova.virt import hyperv
>
>
> FLAGS = flags.FLAGS
> @@ -49,6 +50,8 @@
> conn = libvirt_conn.get_connection(read_only)
> elif t == 'xenapi':
> conn = xenapi.get_connection(read_only)
> + elif t == 'hyperv':
> + conn = hyperv.get_connection(read_only)
> else:
> raise Exception('Unknown connection type "%s"' % t)
>
>

This is troublesome. We have no python-wmi on linux, and unconditionally
importing the hyperv driver means unconditionally importing wmi which
fails spectacularly.

nova.virt.connection.get_connection should probably be refactored to use
nova.utils.import_object or similar, based on the defined
connection_type. That would solve this problem.

> === added file 'nova/virt/hyperv.py'
> --- nova/virt/hyperv.py 1970-01-01 00:00:00 +0000
> +++ nova/virt/hyperv.py 2010-10-13 07:15:24 +0000
> @@ -0,0 +1,453 @@
> +# vim: tabstop=4 shiftwidth=4 softtabstop=4
> +
> +# Copyright (c) 2010 Cloud.com, 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 L...

review: Needs Fixing
Revision history for this message
Jay Pipes (jaypipes) wrote : Posted in a previous version of this proposal

Hi again, Chiradeep :) Thanks much for addressing the documentation concerns and the style issues on the first review! I second Soren's suggestions/fixups.

Revision history for this message
Chiradeep Vittal (chiradeep) wrote : Posted in a previous version of this proposal
Download full text (28.5 KiB)

For the "import wmi" issue, I propose adopting the solution used in the xenapi
driver (use __import__).
I can raise a bug to get this fixed in the manner suggested by Soren.

Regarding long-running blocking operations (fetch image / start vm), I am not yet
familiar with the Twisted framework. I propose that we leave these as is and raise
bugs to fix later.

New exceptions: did you mean add one into nova/exception.py or add a new one
inside hyperv.py. I've added a couple in the latter.

Unit tests: I've added a unit test (hyperv_unittest.py). The unit test framework, I think
assumes that the controller and the driver are on the same host. I haven't managed
to port the entire controller to Windows (only the compute manager). There is a
dependency on where redis runs), so I haven't integrated it into the test case framework.
I am also not sure on how to make it conditional (on Linux) within the test case framework.

________________________________________
From: <email address hidden> [<email address hidden>] On Behalf Of Soren Hansen [<email address hidden>]
Sent: Wednesday, October 13, 2010 3:52 AM
To: <email address hidden>
Subject: Re: [Merge] lp:~chiradeep/nova/msft-hyper-v-support into lp:nova

Review: Needs Fixing
On 13-10-2010 09:15, Chiradeep Vittal wrote:
> Chiradeep Vittal has proposed merging lp:~chiradeep/nova/msft-hyper-v-support into lp:nova.
>
> Requested reviews:
> Jay Pipes (jaypipes)
>
>
> Introduces basic support for spawning, rebooting and destroying vms when using Microsoft Hyper-V as the hypervisor.
> Images need to be in VHD format. Note that although Hyper-V doesn't accept kernel and ramdisk
> separate from the image, the nova objectstore api still expects an image to have an associated aki and ari. You can use dummy aki and ari images -- the hyper-v driver won't use them or try to download them.
> Requires Python's WMI module.
>

> === modified file 'nova/compute/manager.py'
> --- nova/compute/manager.py 2010-10-12 20:18:29 +0000
> +++ nova/compute/manager.py 2010-10-13 07:15:24 +0000
> @@ -39,6 +39,8 @@
> 'where instances are stored on disk')
> flags.DEFINE_string('compute_driver', 'nova.virt.connection.get_connection',
> 'Driver to use for volume creation')
> +flags.DEFINE_string('images_path', utils.abspath('../images'),
> + 'path to decrypted local images if not using s3')

Each flag must only be defined once. images_path is also defined in
nova.objectstore.images. Either import that or move the flag to a common
place and import that.

>>>> fixed by reverting.

> === modified file 'nova/virt/connection.py'
> --- nova/virt/connection.py 2010-08-30 13:19:14 +0000
> +++ nova/virt/connection.py 2010-10-13 07:15:24 +0000
> @@ -26,6 +26,7 @@
> from nova.virt import fake
> from nova.virt import libvirt_conn
> from nova.virt import xenapi
> +from nova.virt import hyperv
>
>
> FLAGS = flags.FLAGS
> @@ -49,6 +50,8 @@
> conn = libvirt_conn.get_connection(read_only)
> elif t == 'xenapi':
> conn = xenapi.get_connection(read_only)
> + elif t == 'hyperv':
> + conn = hyperv.get_connection(read_only)
> else:
> ...

Revision history for this message
Soren Hansen (soren) wrote : Posted in a previous version of this proposal
Download full text (28.3 KiB)

On 14-10-2010 08:26, Chiradeep Vittal wrote:
> === added file 'nova/tests/hyperv_unittest.py'
> --- nova/tests/hyperv_unittest.py 1970-01-01 00:00:00 +0000
> +++ nova/tests/hyperv_unittest.py 2010-10-14 06:26:41 +0000
> @@ -0,0 +1,67 @@
> +# vim: tabstop=4 shiftwidth=4 softtabstop=4
> +#
> +# Copyright 2010 Cloud.com, 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.
> +"""
> +Tests For Hyper-V driver
> +"""
> +
> +import random
> +
> +from nova import db
> +from nova import flags
> +from nova import test
> +
> +from nova.virt import hyperv
> +
> +FLAGS = flags.FLAGS
> +FLAGS.connection_type = 'hyperv'
> +# Redis is probably not running on Hyper-V host.
> +# Change this to the actual Redis host
> +FLAGS.redis_host = '127.0.0.1'
> +
> +
> +class HyperVTestCase(test.TrialTestCase):
> + """Test cases for the Hyper-V driver"""
> + def setUp(self): # pylint: disable-msg=C0103
> + pass

Why define it at all?

> +
> + def test_create_destroy(self):
> + """Create a VM and destroy it"""
> + instance = {'internal_id' : random.randint(1, 1000000),
> + 'memory_mb' : '1024',
> + 'mac_address' : '02:12:34:46:56:67',
> + 'vcpus' : 2,
> + 'project_id' : 'fake',
> + 'instance_type' : 'm1.small'}
> +
> + instance_ref = db.instance_create(None, instance)
> +
> + conn = hyperv.get_connection(False)
> + conn._create_vm(instance_ref) # pylint: disable-msg=W0212
> + found = [n for n in conn.list_instances()
> + if n == instance_ref['name']]
> + self.assertTrue(len(found) == 1)
> + info = conn.get_info(instance_ref['name'])
> + #Unfortunately since the vm is not running at this point,
> + #we cannot obtain memory information from get_info
> + self.assertEquals(info['num_cpu'], instance_ref['vcpus'])
> +
> + conn.destroy(instance_ref)
> + found = [n for n in conn.list_instances()
> + if n == instance_ref['name']]
> + self.assertTrue(len(found) == 0)

Wouldn't this actually run the VM? The purpose of unit tests is to be
able to test things in units, so to verify that each piece of the puzzle
acts the way it should. Ideally, you should include a mock wmi module
that stubs out all the relevant methods so that we take the actual
hypervisor out of the equation.

> +
> + def tearDown(self): # pylint: disable-msg=C0103
> + pass

Again, why define it at all?

> === modified file 'nova/virt/connection.py'
> --- nova/virt/conne...

Revision history for this message
Soren Hansen (soren) wrote : Posted in a previous version of this proposal

Apologies for messing up the formatting. I've given my mail client a thorough beating. It promises never to do that again.

Revision history for this message
Jay Pipes (jaypipes) wrote : Posted in a previous version of this proposal

Thanks for correcting and responding to all the comments from Soren and myself, Chiradeep. Good work :)

review: Approve
Revision history for this message
Chiradeep Vittal (chiradeep) wrote : Posted in a previous version of this proposal
Download full text (30.4 KiB)

> On 14-10-2010 08:26, Chiradeep Vittal wrote:
> > === added file 'nova/tests/hyperv_unittest.py'
> > --- nova/tests/hyperv_unittest.py 1970-01-01 00:00:00 +0000
> > +++ nova/tests/hyperv_unittest.py 2010-10-14 06:26:41 +0000
> > @@ -0,0 +1,67 @@
> > +# vim: tabstop=4 shiftwidth=4 softtabstop=4
> > +#
> > +# Copyright 2010 Cloud.com, 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.
> > +"""
> > +Tests For Hyper-V driver
> > +"""
> > +
> > +import random
> > +
> > +from nova import db
> > +from nova import flags
> > +from nova import test
> > +
> > +from nova.virt import hyperv
> > +
> > +FLAGS = flags.FLAGS
> > +FLAGS.connection_type = 'hyperv'
> > +# Redis is probably not running on Hyper-V host.
> > +# Change this to the actual Redis host
> > +FLAGS.redis_host = '127.0.0.1'
> > +
> > +
> > +class HyperVTestCase(test.TrialTestCase):
> > + """Test cases for the Hyper-V driver"""
> > + def setUp(self): # pylint: disable-msg=C0103
> > + pass
>
> Why define it at all?
>
> > +
> > + def test_create_destroy(self):
> > + """Create a VM and destroy it"""
> > + instance = {'internal_id' : random.randint(1, 1000000),
> > + 'memory_mb' : '1024',
> > + 'mac_address' : '02:12:34:46:56:67',
> > + 'vcpus' : 2,
> > + 'project_id' : 'fake',
> > + 'instance_type' : 'm1.small'}
> > +
> > + instance_ref = db.instance_create(None, instance)
> > +
> > + conn = hyperv.get_connection(False)
> > + conn._create_vm(instance_ref) # pylint: disable-msg=W0212
> > + found = [n for n in conn.list_instances()
> > + if n == instance_ref['name']]
> > + self.assertTrue(len(found) == 1)
> > + info = conn.get_info(instance_ref['name'])
> > + #Unfortunately since the vm is not running at this point,
> > + #we cannot obtain memory information from get_info
> > + self.assertEquals(info['num_cpu'], instance_ref['vcpus'])
> > +
> > + conn.destroy(instance_ref)
> > + found = [n for n in conn.list_instances()
> > + if n == instance_ref['name']]
> > + self.assertTrue(len(found) == 0)
>
> Wouldn't this actually run the VM? The purpose of unit tests is to be
> able to test things in units, so to verify that each piece of the puzzle
> acts the way it should. Ideally, you should include a mock wmi module
> that stubs out all the relevant methods so that we take the actual
> hypervisor out of the equation.
> ...

Revision history for this message
Soren Hansen (soren) wrote : Posted in a previous version of this proposal

I just realised I forgot to vote in my last review..

Anyways, even with these last changes, I'm really not happy with the tests. They're no good if we can't run them.

Ideally, the mock thing would actually pretend to be Hyper-V, but failing that, it's not that hard to replace e.g. your _conn object with one that logs all calls and return values so that you can record them once and then write a simple shim that gives back the same responses when called the same way. I would say that's a minimal requirement. It doesn't cover what happens in case of errors or anything like that, but it's a start, and /especially/ when none of the rest of us have access to a Hyper-V box, it's essential that we have a way to check if things will continue to work.

Also, you're referencing something called VmResourceAllocationException. There's no such thing.

review: Disapprove
Revision history for this message
Soren Hansen (soren) wrote : Posted in a previous version of this proposal

I should note, by the way, that I really appreciate the work you're
doing. Hyper-V support is a major step towards total world domination
:) I guess that's why I really want to make sure us Linux geeks have a
way to make sure we don't break it :)

Revision history for this message
Chiradeep Vittal (chiradeep) wrote : Posted in a previous version of this proposal

It isn't just the _conn object that needs to be mocked. We retrieve lots of different types of WMI objects (Msvm_*) and call methods on them. This might be best served by a mocking framework. I need more time to investigate. Do you have any recommendations?
I don't think this is a couple of hours work. Can we get the merge in and raise a bug to keep track
of the unit test issue?

review: Needs Resubmitting
Revision history for this message
Soren Hansen (soren) wrote : Posted in a previous version of this proposal

> It isn't just the _conn object that needs to be mocked. We retrieve lots of
> different types of WMI objects (Msvm_*) and call methods on them. This might
> be best served by a mocking framework. I need more time to investigate. Do you
> have any recommendations?

I'm afraid not. I don't recall having needed anything advanced like this myself.

> I don't think this is a couple of hours work. Can we get the merge in and
> raise a bug to keep track of the unit test issue?

We're well past feature freeze (and even final freeze), and we don't have a
branch open for our next release yet, so we can't actually technically merge
it yet anyway :-/

Revision history for this message
Jay Pipes (jaypipes) wrote : Posted in a previous version of this proposal

On Thu, Oct 14, 2010 at 5:26 PM, Chiradeep Vittal <email address hidden> wrote:
> Review: Resubmit
> It isn't just the _conn object that needs to be mocked. We retrieve lots of different types of WMI objects (Msvm_*) and call methods on them. This might be best served by a mocking framework. I need more time to investigate. Do you have any recommendations?

We use a mocking framework called pymox already. Check out the code
in /nova/tests/api/openstack/fakes.py and the test files in that same
directory for examples of how it's used. I'm happy to help you if you
get a bit lost. Hop into IRC and we can chat about it :)

> I don't think this is a couple of hours work. Can we get the merge in and raise a bug to keep track
> of the unit test issue?

As Soren said, we couldn't do the merge right now into trunk because
we're in code freeze (only bug fixes can go in now). Once we get the
next release series branch going, we'll merge into there.

-jay

Revision history for this message
Soren Hansen (soren) wrote : Posted in a previous version of this proposal

Sorry about not getting back to this sooner.

Ok, let's merge it, and once I finish my libvirt tests refactoring, you can perhaps look at that to see an example of what I'm talking about.

review: Approve
Revision history for this message
OpenStack Infra (hudson-openstack) wrote : Posted in a previous version of this proposal

Attempt to merge into lp:nova failed due to conflicts:

text conflict in nova/virt/images.py

Revision history for this message
Soren Hansen (soren) wrote : Posted in a previous version of this proposal

Chiradeep, the merge was approved, but failed.

You need to merge with trunk, commit, and push again.

Revision history for this message
Chiradeep Vittal (chiradeep) wrote : Posted in a previous version of this proposal

Thanks. I'll fix it

--
Chiradeep

On Nov 29, 2010, at 1:12 PM, Soren Hansen <email address hidden> wrote:

> Chiradeep, the merge was approved, but failed.
>
> You need to merge with trunk, commit, and push again.
> --
> https://code.launchpad.net/~chiradeep/nova/msft-hyper-v-support/+merge/38387
> You are the owner of lp:~chiradeep/nova/msft-hyper-v-support.

Revision history for this message
Soren Hansen (soren) wrote : Posted in a previous version of this proposal

Great. Let me know if you need help.

--
Soren Hansen
Ubuntu Developer    http://www.ubuntu.com/
OpenStack Developer http://www.openstack.org/

Revision history for this message
Soren Hansen (soren) wrote : Posted in a previous version of this proposal

Ping?

Revision history for this message
Chiradeep Vittal (chiradeep) wrote : Posted in a previous version of this proposal

Sorry about that. Was busy with a move. Just got Internet/phone etc going again. Will commit this week.

________________________________________
From: <email address hidden> [<email address hidden>] On Behalf Of Soren Hansen [<email address hidden>]
Sent: Monday, December 13, 2010 4:02 AM
To: <email address hidden>
Subject: Re: [Merge] lp:~chiradeep/nova/msft-hyper-v-support into lp:nova

Ping?
--
https://code.launchpad.net/~chiradeep/nova/msft-hyper-v-support/+merge/38387
You are the owner of lp:~chiradeep/nova/msft-hyper-v-support.

Revision history for this message
Soren Hansen (soren) wrote :

Great, thanks!

review: Approve
Revision history for this message
Jay Pipes (jaypipes) wrote :

Looks great, Chiradeep!

Warning: if the eventlet_merge branch gets merged into trunk before this, you will need to resolve a couple conflicts (process.simple_execute no longer exists IIRC..)

Just a heads up...not sure which will hit trunk first...

review: Approve
Revision history for this message
Chiradeep Vittal (chiradeep) :
review: Approve
Revision history for this message
Thierry Carrez (ttx) wrote :

@Chiradeep: the long-awaited eventlet branch got merged now, please check your branch for conflicts.

Revision history for this message
Devin Carlen (devcamcar) wrote :

Awesome, will be great when this lands. Looks like we're just waiting for a fresh merge before this gets approved.

review: Approve
Revision history for this message
Soren Hansen (soren) wrote :

Chiradeep, this branch needs to be updated to the new, Twistedless world order. You need to get rid of the yields, you probably don't need the changes to nova/twistd.py anymore. Also, a round of testing to check that it still actually works would probably be a good idea :)

I'm also not sure you still need the redis_host stuff?

review: Needs Fixing
Revision history for this message
Chiradeep Vittal (chiradeep) wrote :

Yes, I was in the process of testing it out. Will update you later.

On 01/03/2011 01:27 AM, Soren Hansen wrote:
> Review: Needs Fixing
> Chiradeep, this branch needs to be updated to the new, Twistedless world order. You need to get rid of the yields, you probably don't need the changes to nova/twistd.py anymore. Also, a round of testing to check that it still actually works would probably be a good idea :)
>
> I'm also not sure you still need the redis_host stuff?
>

Revision history for this message
Chiradeep Vittal (chiradeep) wrote :

Fixed.
-- Removed dependencies on twisted
-- fixed logging and exceptions strings to enable i18n
-- removed redis dependency in unittest

On 01/03/2011 01:27 AM, Soren Hansen wrote:
> Review: Needs Fixing
> Chiradeep, this branch needs to be updated to the new, Twistedless world order. You need to get rid of the yields, you probably don't need the changes to nova/twistd.py anymore. Also, a round of testing to check that it still actually works would probably be a good idea :)
>
> I'm also not sure you still need the redis_host stuff?
>

Revision history for this message
Soren Hansen (soren) wrote :

You need to add yourself to the Authors file.

You probably want to do so by adding this to Authors:
Chiradeep Vittal <email address hidden>

and this to .mailmap:
<email address hidden> <chiradeep@chiradeep-lt2>

To tell bzr about your real e-mail, do this:
bzr whoami "Chiradeep Vittal <email address hidden>"

Revision history for this message
Armando Migliaccio (armando-migliaccio) wrote :

I think the test case should no longer inherit from TrialTestCase (line 36 nova/tests/hyperv_unittest.py) since we've moved away from Twisted Trial

review: Needs Fixing
Revision history for this message
Armando Migliaccio (armando-migliaccio) wrote :

A couple of questions about the unittest provided with this branch:

- does the test require an actual connection to hyperv? I couldn't find where this is stubbed out
- any particular reasons of calling the private method _create_vm, rather the public spawn method?
- does this test actually run/pass at all?

The call to

  instance_ref = db.instance_create(None, instance)

should raise an exception about the deprecation of a null context

Revision history for this message
Chiradeep Vittal (chiradeep) wrote :

Yes the test case is indeed broken. I am inclined to delete it since the
reviewers want a test case that can run without hyper-v. This would
involve a large amount of mocking of the Hyper-V WMI provider.

On 01/05/2011 05:02 AM, Armando Migliaccio wrote:
> A couple of questions about the unittest provided with this branch:
>
> - does the test require an actual connection to hyperv? I couldn't find where this is stubbed out
> - any particular reasons of calling the private method _create_vm, rather the public spawn method?
> - does this test actually run/pass at all?
>
> The call to
>
> instance_ref = db.instance_create(None, instance)
>
> should raise an exception about the deprecation of a null context
>

359. By Chiradeep Vittal

Make test case work again

360. By Chiradeep Vittal

Add to Authors and mailmap

Revision history for this message
Vish Ishaya (vishvananda) wrote :

looks like this can finally be merged. Will wait for a final approval from soren.

review: Approve
Revision history for this message
Armando Migliaccio (armando-migliaccio) wrote :

Hi Chiradeep,

thanks for the reply!

One more thing: the naming convention for unittest files has changed from <testname>_unittest.py to test_<testname>.py. I noticed that without renaming your test, run_test.sh does not process the file.

If you do rename the file, then the test will be run and (in my case) it fails because of a missing wmi module.

My take is that if the test isn't ready, it's better to remove it from the diff, as this might delay the merging process.

Revision history for this message
Armando Migliaccio (armando-migliaccio) :
review: Approve
Revision history for this message
Soren Hansen (soren) wrote :

rock'n'roll.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '.mailmap'
--- .mailmap 2010-12-30 17:26:21 +0000
+++ .mailmap 2011-01-05 23:07:23 +0000
@@ -30,3 +30,4 @@
30<rconradharris@gmail.com> <rick.harris@rackspace.com> 30<rconradharris@gmail.com> <rick.harris@rackspace.com>
31<corywright@gmail.com> <cory.wright@rackspace.com>31<corywright@gmail.com> <cory.wright@rackspace.com>
32<ant@openstack.org> <amesserl@rackspace.com>32<ant@openstack.org> <amesserl@rackspace.com>
33<chiradeep@cloud.com> <chiradeep@chiradeep-lt2>
3334
=== modified file 'Authors'
--- Authors 2010-12-30 17:26:21 +0000
+++ Authors 2011-01-05 23:07:23 +0000
@@ -3,6 +3,7 @@
3Anthony Young <sleepsonthefloor@gmail.com>3Anthony Young <sleepsonthefloor@gmail.com>
4Antony Messerli <ant@openstack.org>4Antony Messerli <ant@openstack.org>
5Armando Migliaccio <Armando.Migliaccio@eu.citrix.com>5Armando Migliaccio <Armando.Migliaccio@eu.citrix.com>
6Chiradeep Vittal <chiradeep@cloud.com>
6Chris Behrens <cbehrens@codestud.com>7Chris Behrens <cbehrens@codestud.com>
7Chmouel Boudjnah <chmouel@chmouel.com>8Chmouel Boudjnah <chmouel@chmouel.com>
8Cory Wright <corywright@gmail.com>9Cory Wright <corywright@gmail.com>
910
=== added file 'nova/tests/hyperv_unittest.py'
--- nova/tests/hyperv_unittest.py 1970-01-01 00:00:00 +0000
+++ nova/tests/hyperv_unittest.py 2011-01-05 23:07:23 +0000
@@ -0,0 +1,71 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2#
3# Copyright 2010 Cloud.com, Inc
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16"""
17Tests For Hyper-V driver
18"""
19
20import random
21
22from nova import context
23from nova import db
24from nova import flags
25from nova import test
26from nova.auth import manager
27from nova.virt import hyperv
28
29FLAGS = flags.FLAGS
30FLAGS.connection_type = 'hyperv'
31
32
33class HyperVTestCase(test.TestCase):
34 """Test cases for the Hyper-V driver"""
35 def setUp(self):
36 super(HyperVTestCase, self).setUp()
37 self.manager = manager.AuthManager()
38 self.user = self.manager.create_user('fake', 'fake', 'fake',
39 admin=True)
40 self.project = self.manager.create_project('fake', 'fake', 'fake')
41 self.context = context.RequestContext(self.user, self.project)
42
43 def test_create_destroy(self):
44 """Create a VM and destroy it"""
45 instance = {'internal_id': random.randint(1, 1000000),
46 'memory_mb': '1024',
47 'mac_address': '02:12:34:46:56:67',
48 'vcpus': 2,
49 'project_id': 'fake',
50 'instance_type': 'm1.small'}
51 instance_ref = db.instance_create(self.context, instance)
52
53 conn = hyperv.get_connection(False)
54 conn._create_vm(instance_ref) # pylint: disable-msg=W0212
55 found = [n for n in conn.list_instances()
56 if n == instance_ref['name']]
57 self.assertTrue(len(found) == 1)
58 info = conn.get_info(instance_ref['name'])
59 #Unfortunately since the vm is not running at this point,
60 #we cannot obtain memory information from get_info
61 self.assertEquals(info['num_cpu'], instance_ref['vcpus'])
62
63 conn.destroy(instance_ref)
64 found = [n for n in conn.list_instances()
65 if n == instance_ref['name']]
66 self.assertTrue(len(found) == 0)
67
68 def tearDown(self):
69 super(HyperVTestCase, self).tearDown()
70 self.manager.delete_project(self.project)
71 self.manager.delete_user(self.user)
072
=== modified file 'nova/virt/connection.py'
--- nova/virt/connection.py 2010-12-22 20:59:53 +0000
+++ nova/virt/connection.py 2011-01-05 23:07:23 +0000
@@ -26,6 +26,7 @@
26from nova.virt import fake26from nova.virt import fake
27from nova.virt import libvirt_conn27from nova.virt import libvirt_conn
28from nova.virt import xenapi_conn28from nova.virt import xenapi_conn
29from nova.virt import hyperv
2930
3031
31FLAGS = flags.FLAGS32FLAGS = flags.FLAGS
@@ -62,6 +63,8 @@
62 conn = libvirt_conn.get_connection(read_only)63 conn = libvirt_conn.get_connection(read_only)
63 elif t == 'xenapi':64 elif t == 'xenapi':
64 conn = xenapi_conn.get_connection(read_only)65 conn = xenapi_conn.get_connection(read_only)
66 elif t == 'hyperv':
67 conn = hyperv.get_connection(read_only)
65 else:68 else:
66 raise Exception('Unknown connection type "%s"' % t)69 raise Exception('Unknown connection type "%s"' % t)
6770
6871
=== added file 'nova/virt/hyperv.py'
--- nova/virt/hyperv.py 1970-01-01 00:00:00 +0000
+++ nova/virt/hyperv.py 2011-01-05 23:07:23 +0000
@@ -0,0 +1,459 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright (c) 2010 Cloud.com, Inc
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
17"""
18A connection to Hyper-V .
19Uses Windows Management Instrumentation (WMI) calls to interact with Hyper-V
20Hyper-V WMI usage:
21 http://msdn.microsoft.com/en-us/library/cc723875%28v=VS.85%29.aspx
22The Hyper-V object model briefly:
23 The physical computer and its hosted virtual machines are each represented
24 by the Msvm_ComputerSystem class.
25
26 Each virtual machine is associated with a
27 Msvm_VirtualSystemGlobalSettingData (vs_gs_data) instance and one or more
28 Msvm_VirtualSystemSettingData (vmsetting) instances. For each vmsetting
29 there is a series of Msvm_ResourceAllocationSettingData (rasd) objects.
30 The rasd objects describe the settings for each device in a VM.
31 Together, the vs_gs_data, vmsettings and rasds describe the configuration
32 of the virtual machine.
33
34 Creating new resources such as disks and nics involves cloning a default
35 rasd object and appropriately modifying the clone and calling the
36 AddVirtualSystemResources WMI method
37 Changing resources such as memory uses the ModifyVirtualSystemResources
38 WMI method
39
40Using the Python WMI library:
41 Tutorial:
42 http://timgolden.me.uk/python/wmi/tutorial.html
43 Hyper-V WMI objects can be retrieved simply by using the class name
44 of the WMI object and optionally specifying a column to filter the
45 result set. More complex filters can be formed using WQL (sql-like)
46 queries.
47 The parameters and return tuples of WMI method calls can gleaned by
48 examining the doc string. For example:
49 >>> vs_man_svc.ModifyVirtualSystemResources.__doc__
50 ModifyVirtualSystemResources (ComputerSystem, ResourceSettingData[])
51 => (Job, ReturnValue)'
52 When passing setting data (ResourceSettingData) to the WMI method,
53 an XML representation of the data is passed in using GetText_(1).
54 Available methods on a service can be determined using method.keys():
55 >>> vs_man_svc.methods.keys()
56 vmsettings and rasds for a vm can be retrieved using the 'associators'
57 method with the appropriate return class.
58 Long running WMI commands generally return a Job (an instance of
59 Msvm_ConcreteJob) whose state can be polled to determine when it finishes
60
61"""
62
63import os
64import logging
65import time
66
67from nova import exception
68from nova import flags
69from nova.auth import manager
70from nova.compute import power_state
71from nova.virt import images
72
73wmi = None
74
75
76FLAGS = flags.FLAGS
77
78
79HYPERV_POWER_STATE = {
80 3: power_state.SHUTDOWN,
81 2: power_state.RUNNING,
82 32768: power_state.PAUSED,
83}
84
85
86REQ_POWER_STATE = {
87 'Enabled': 2,
88 'Disabled': 3,
89 'Reboot': 10,
90 'Reset': 11,
91 'Paused': 32768,
92 'Suspended': 32769
93}
94
95
96WMI_JOB_STATUS_STARTED = 4096
97WMI_JOB_STATE_RUNNING = 4
98WMI_JOB_STATE_COMPLETED = 7
99
100
101def get_connection(_):
102 global wmi
103 if wmi is None:
104 wmi = __import__('wmi')
105 return HyperVConnection()
106
107
108class HyperVConnection(object):
109 def __init__(self):
110 self._conn = wmi.WMI(moniker='//./root/virtualization')
111 self._cim_conn = wmi.WMI(moniker='//./root/cimv2')
112
113 def init_host(self):
114 #FIXME(chiradeep): implement this
115 logging.debug(_('In init host'))
116 pass
117
118 def list_instances(self):
119 """ Return the names of all the instances known to Hyper-V. """
120 vms = [v.ElementName \
121 for v in self._conn.Msvm_ComputerSystem(['ElementName'])]
122 return vms
123
124 def spawn(self, instance):
125 """ Create a new VM and start it."""
126 vm = self._lookup(instance.name)
127 if vm is not None:
128 raise exception.Duplicate(_('Attempt to create duplicate vm %s') %
129 instance.name)
130
131 user = manager.AuthManager().get_user(instance['user_id'])
132 project = manager.AuthManager().get_project(instance['project_id'])
133 #Fetch the file, assume it is a VHD file.
134 base_vhd_filename = os.path.join(FLAGS.instances_path,
135 instance.name)
136 vhdfile = "%s.vhd" % (base_vhd_filename)
137 images.fetch(instance['image_id'], vhdfile, user, project)
138
139 try:
140 self._create_vm(instance)
141
142 self._create_disk(instance['name'], vhdfile)
143 self._create_nic(instance['name'], instance['mac_address'])
144
145 logging.debug(_('Starting VM %s '), instance.name)
146 self._set_vm_state(instance['name'], 'Enabled')
147 logging.info(_('Started VM %s '), instance.name)
148 except Exception as exn:
149 logging.error(_('spawn vm failed: %s'), exn)
150 self.destroy(instance)
151
152 def _create_vm(self, instance):
153 """Create a VM but don't start it. """
154 vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
155
156 vs_gs_data = self._conn.Msvm_VirtualSystemGlobalSettingData.new()
157 vs_gs_data.ElementName = instance['name']
158 (job, ret_val) = vs_man_svc.DefineVirtualSystem(
159 [], None, vs_gs_data.GetText_(1))[1:]
160 if ret_val == WMI_JOB_STATUS_STARTED:
161 success = self._check_job_status(job)
162 else:
163 success = (ret_val == 0)
164
165 if not success:
166 raise Exception(_('Failed to create VM %s'), instance.name)
167
168 logging.debug(_('Created VM %s...'), instance.name)
169 vm = self._conn.Msvm_ComputerSystem(ElementName=instance.name)[0]
170
171 vmsettings = vm.associators(
172 wmi_result_class='Msvm_VirtualSystemSettingData')
173 vmsetting = [s for s in vmsettings
174 if s.SettingType == 3][0] # avoid snapshots
175 memsetting = vmsetting.associators(
176 wmi_result_class='Msvm_MemorySettingData')[0]
177 #No Dynamic Memory, so reservation, limit and quantity are identical.
178 mem = long(str(instance['memory_mb']))
179 memsetting.VirtualQuantity = mem
180 memsetting.Reservation = mem
181 memsetting.Limit = mem
182
183 (job, ret_val) = vs_man_svc.ModifyVirtualSystemResources(
184 vm.path_(), [memsetting.GetText_(1)])
185 logging.debug(_('Set memory for vm %s...'), instance.name)
186 procsetting = vmsetting.associators(
187 wmi_result_class='Msvm_ProcessorSettingData')[0]
188 vcpus = long(instance['vcpus'])
189 procsetting.VirtualQuantity = vcpus
190 procsetting.Reservation = vcpus
191 procsetting.Limit = vcpus
192
193 (job, ret_val) = vs_man_svc.ModifyVirtualSystemResources(
194 vm.path_(), [procsetting.GetText_(1)])
195 logging.debug(_('Set vcpus for vm %s...'), instance.name)
196
197 def _create_disk(self, vm_name, vhdfile):
198 """Create a disk and attach it to the vm"""
199 logging.debug(_('Creating disk for %s by attaching disk file %s'),
200 vm_name, vhdfile)
201 #Find the IDE controller for the vm.
202 vms = self._conn.MSVM_ComputerSystem(ElementName=vm_name)
203 vm = vms[0]
204 vmsettings = vm.associators(
205 wmi_result_class='Msvm_VirtualSystemSettingData')
206 rasds = vmsettings[0].associators(
207 wmi_result_class='MSVM_ResourceAllocationSettingData')
208 ctrller = [r for r in rasds
209 if r.ResourceSubType == 'Microsoft Emulated IDE Controller'\
210 and r.Address == "0"]
211 #Find the default disk drive object for the vm and clone it.
212 diskdflt = self._conn.query(
213 "SELECT * FROM Msvm_ResourceAllocationSettingData \
214 WHERE ResourceSubType LIKE 'Microsoft Synthetic Disk Drive'\
215 AND InstanceID LIKE '%Default%'")[0]
216 diskdrive = self._clone_wmi_obj(
217 'Msvm_ResourceAllocationSettingData', diskdflt)
218 #Set the IDE ctrller as parent.
219 diskdrive.Parent = ctrller[0].path_()
220 diskdrive.Address = 0
221 #Add the cloned disk drive object to the vm.
222 new_resources = self._add_virt_resource(diskdrive, vm)
223 if new_resources is None:
224 raise Exception(_('Failed to add diskdrive to VM %s'),
225 vm_name)
226 diskdrive_path = new_resources[0]
227 logging.debug(_('New disk drive path is %s'), diskdrive_path)
228 #Find the default VHD disk object.
229 vhddefault = self._conn.query(
230 "SELECT * FROM Msvm_ResourceAllocationSettingData \
231 WHERE ResourceSubType LIKE 'Microsoft Virtual Hard Disk' AND \
232 InstanceID LIKE '%Default%' ")[0]
233
234 #Clone the default and point it to the image file.
235 vhddisk = self._clone_wmi_obj(
236 'Msvm_ResourceAllocationSettingData', vhddefault)
237 #Set the new drive as the parent.
238 vhddisk.Parent = diskdrive_path
239 vhddisk.Connection = [vhdfile]
240
241 #Add the new vhd object as a virtual hard disk to the vm.
242 new_resources = self._add_virt_resource(vhddisk, vm)
243 if new_resources is None:
244 raise Exception(_('Failed to add vhd file to VM %s'),
245 vm_name)
246 logging.info(_('Created disk for %s'), vm_name)
247
248 def _create_nic(self, vm_name, mac):
249 """Create a (emulated) nic and attach it to the vm"""
250 logging.debug(_('Creating nic for %s '), vm_name)
251 #Find the vswitch that is connected to the physical nic.
252 vms = self._conn.Msvm_ComputerSystem(ElementName=vm_name)
253 extswitch = self._find_external_network()
254 vm = vms[0]
255 switch_svc = self._conn.Msvm_VirtualSwitchManagementService()[0]
256 #Find the default nic and clone it to create a new nic for the vm.
257 #Use Msvm_SyntheticEthernetPortSettingData for Windows or Linux with
258 #Linux Integration Components installed.
259 emulatednics_data = self._conn.Msvm_EmulatedEthernetPortSettingData()
260 default_nic_data = [n for n in emulatednics_data
261 if n.InstanceID.rfind('Default') > 0]
262 new_nic_data = self._clone_wmi_obj(
263 'Msvm_EmulatedEthernetPortSettingData',
264 default_nic_data[0])
265 #Create a port on the vswitch.
266 (new_port, ret_val) = switch_svc.CreateSwitchPort(vm_name, vm_name,
267 "", extswitch.path_())
268 if ret_val != 0:
269 logging.error(_('Failed creating a port on the external vswitch'))
270 raise Exception(_('Failed creating port for %s'),
271 vm_name)
272 logging.debug(_("Created switch port %s on switch %s"),
273 vm_name, extswitch.path_())
274 #Connect the new nic to the new port.
275 new_nic_data.Connection = [new_port]
276 new_nic_data.ElementName = vm_name + ' nic'
277 new_nic_data.Address = ''.join(mac.split(':'))
278 new_nic_data.StaticMacAddress = 'TRUE'
279 #Add the new nic to the vm.
280 new_resources = self._add_virt_resource(new_nic_data, vm)
281 if new_resources is None:
282 raise Exception(_('Failed to add nic to VM %s'),
283 vm_name)
284 logging.info(_("Created nic for %s "), vm_name)
285
286 def _add_virt_resource(self, res_setting_data, target_vm):
287 """Add a new resource (disk/nic) to the VM"""
288 vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
289 (job, new_resources, ret_val) = vs_man_svc.\
290 AddVirtualSystemResources([res_setting_data.GetText_(1)],
291 target_vm.path_())
292 success = True
293 if ret_val == WMI_JOB_STATUS_STARTED:
294 success = self._check_job_status(job)
295 else:
296 success = (ret_val == 0)
297 if success:
298 return new_resources
299 else:
300 return None
301
302 #TODO: use the reactor to poll instead of sleep
303 def _check_job_status(self, jobpath):
304 """Poll WMI job state for completion"""
305 #Jobs have a path of the form:
306 #\\WIN-P5IG7367DAG\root\virtualization:Msvm_ConcreteJob.InstanceID=
307 #"8A496B9C-AF4D-4E98-BD3C-1128CD85320D"
308 inst_id = jobpath.split('=')[1].strip('"')
309 jobs = self._conn.Msvm_ConcreteJob(InstanceID=inst_id)
310 if len(jobs) == 0:
311 return False
312 job = jobs[0]
313 while job.JobState == WMI_JOB_STATE_RUNNING:
314 time.sleep(0.1)
315 job = self._conn.Msvm_ConcreteJob(InstanceID=inst_id)[0]
316 if job.JobState != WMI_JOB_STATE_COMPLETED:
317 logging.debug(_("WMI job failed: %s"), job.ErrorSummaryDescription)
318 return False
319 logging.debug(_("WMI job succeeded: %s, Elapsed=%s "), job.Description,
320 job.ElapsedTime)
321 return True
322
323 def _find_external_network(self):
324 """Find the vswitch that is connected to the physical nic.
325 Assumes only one physical nic on the host
326 """
327 #If there are no physical nics connected to networks, return.
328 bound = self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE')
329 if len(bound) == 0:
330 return None
331 return self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE')[0]\
332 .associators(wmi_result_class='Msvm_SwitchLANEndpoint')[0]\
333 .associators(wmi_result_class='Msvm_SwitchPort')[0]\
334 .associators(wmi_result_class='Msvm_VirtualSwitch')[0]
335
336 def _clone_wmi_obj(self, wmi_class, wmi_obj):
337 """Clone a WMI object"""
338 cl = self._conn.__getattr__(wmi_class) # get the class
339 newinst = cl.new()
340 #Copy the properties from the original.
341 for prop in wmi_obj._properties:
342 newinst.Properties_.Item(prop).Value =\
343 wmi_obj.Properties_.Item(prop).Value
344 return newinst
345
346 def reboot(self, instance):
347 """Reboot the specified instance."""
348 vm = self._lookup(instance.name)
349 if vm is None:
350 raise exception.NotFound('instance not present %s' % instance.name)
351 self._set_vm_state(instance.name, 'Reboot')
352
353 def destroy(self, instance):
354 """Destroy the VM. Also destroy the associated VHD disk files"""
355 logging.debug(_("Got request to destroy vm %s"), instance.name)
356 vm = self._lookup(instance.name)
357 if vm is None:
358 return
359 vm = self._conn.Msvm_ComputerSystem(ElementName=instance.name)[0]
360 vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
361 #Stop the VM first.
362 self._set_vm_state(instance.name, 'Disabled')
363 vmsettings = vm.associators(
364 wmi_result_class='Msvm_VirtualSystemSettingData')
365 rasds = vmsettings[0].associators(
366 wmi_result_class='MSVM_ResourceAllocationSettingData')
367 disks = [r for r in rasds \
368 if r.ResourceSubType == 'Microsoft Virtual Hard Disk']
369 diskfiles = []
370 #Collect disk file information before destroying the VM.
371 for disk in disks:
372 diskfiles.extend([c for c in disk.Connection])
373 #Nuke the VM. Does not destroy disks.
374 (job, ret_val) = vs_man_svc.DestroyVirtualSystem(vm.path_())
375 if ret_val == WMI_JOB_STATUS_STARTED:
376 success = self._check_job_status(job)
377 elif ret_val == 0:
378 success = True
379 if not success:
380 raise Exception(_('Failed to destroy vm %s') % instance.name)
381 #Delete associated vhd disk files.
382 for disk in diskfiles:
383 vhdfile = self._cim_conn.CIM_DataFile(Name=disk)
384 for vf in vhdfile:
385 vf.Delete()
386 logging.debug(_("Del: disk %s vm %s"), vhdfile, instance.name)
387
388 def get_info(self, instance_id):
389 """Get information about the VM"""
390 vm = self._lookup(instance_id)
391 if vm is None:
392 raise exception.NotFound('instance not present %s' % instance_id)
393 vm = self._conn.Msvm_ComputerSystem(ElementName=instance_id)[0]
394 vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
395 vmsettings = vm.associators(
396 wmi_result_class='Msvm_VirtualSystemSettingData')
397 settings_paths = [v.path_() for v in vmsettings]
398 #See http://msdn.microsoft.com/en-us/library/cc160706%28VS.85%29.aspx
399 summary_info = vs_man_svc.GetSummaryInformation(
400 [4, 100, 103, 105], settings_paths)[1]
401 info = summary_info[0]
402 logging.debug(_("Got Info for vm %s: state=%s, mem=%s, num_cpu=%s, \
403 cpu_time=%s"), instance_id,
404 str(HYPERV_POWER_STATE[info.EnabledState]),
405 str(info.MemoryUsage),
406 str(info.NumberOfProcessors),
407 str(info.UpTime))
408
409 return {'state': HYPERV_POWER_STATE[info.EnabledState],
410 'max_mem': info.MemoryUsage,
411 'mem': info.MemoryUsage,
412 'num_cpu': info.NumberOfProcessors,
413 'cpu_time': info.UpTime}
414
415 def _lookup(self, i):
416 vms = self._conn.Msvm_ComputerSystem(ElementName=i)
417 n = len(vms)
418 if n == 0:
419 return None
420 elif n > 1:
421 raise Exception(_('duplicate name found: %s') % i)
422 else:
423 return vms[0].ElementName
424
425 def _set_vm_state(self, vm_name, req_state):
426 """Set the desired state of the VM"""
427 vms = self._conn.Msvm_ComputerSystem(ElementName=vm_name)
428 if len(vms) == 0:
429 return False
430 (job, ret_val) = vms[0].RequestStateChange(REQ_POWER_STATE[req_state])
431 success = False
432 if ret_val == WMI_JOB_STATUS_STARTED:
433 success = self._check_job_status(job)
434 elif ret_val == 0:
435 success = True
436 elif ret_val == 32775:
437 #Invalid state for current operation. Typically means it is
438 #already in the state requested
439 success = True
440 if success:
441 logging.info(_("Successfully changed vm state of %s to %s"),
442 vm_name, req_state)
443 else:
444 logging.error(_("Failed to change vm state of %s to %s"),
445 vm_name, req_state)
446 raise Exception(_("Failed to change vm state of %s to %s"),
447 vm_name, req_state)
448
449 def attach_volume(self, instance_name, device_path, mountpoint):
450 vm = self._lookup(instance_name)
451 if vm is None:
452 raise exception.NotFound('Cannot attach volume to missing %s vm' %
453 instance_name)
454
455 def detach_volume(self, instance_name, mountpoint):
456 vm = self._lookup(instance_name)
457 if vm is None:
458 raise exception.NotFound('Cannot detach volume from missing %s ' %
459 instance_name)
0460
=== modified file 'nova/virt/images.py'
--- nova/virt/images.py 2010-12-10 00:18:52 +0000
+++ nova/virt/images.py 2011-01-05 23:07:23 +0000
@@ -21,8 +21,12 @@
21Handling of VM disk images.21Handling of VM disk images.
22"""22"""
2323
24import logging
24import os.path25import os.path
26import shutil
27import sys
25import time28import time
29import urllib2
26import urlparse30import urlparse
2731
28from nova import flags32from nova import flags
@@ -45,6 +49,25 @@
45 return f(image, path, user, project)49 return f(image, path, user, project)
4650
4751
52def _fetch_image_no_curl(url, path, headers):
53 request = urllib2.Request(url)
54 for (k, v) in headers.iteritems():
55 request.add_header(k, v)
56
57 def urlretrieve(urlfile, fpath):
58 chunk = 1 * 1024 * 1024
59 f = open(fpath, "wb")
60 while 1:
61 data = urlfile.read(chunk)
62 if not data:
63 break
64 f.write(data)
65
66 urlopened = urllib2.urlopen(request)
67 urlretrieve(urlopened, path)
68 logging.debug(_("Finished retreving %s -- placed in %s"), url, path)
69
70
48def _fetch_s3_image(image, path, user, project):71def _fetch_s3_image(image, path, user, project):
49 url = image_url(image)72 url = image_url(image)
5073
@@ -61,18 +84,24 @@
61 url_path)84 url_path)
62 headers['Authorization'] = 'AWS %s:%s' % (access, signature)85 headers['Authorization'] = 'AWS %s:%s' % (access, signature)
6386
64 cmd = ['/usr/bin/curl', '--fail', '--silent', url]87 if sys.platform.startswith('win'):
65 for (k, v) in headers.iteritems():88 return _fetch_image_no_curl(url, path, headers)
66 cmd += ['-H', '"%s: %s"' % (k, v)]89 else:
90 cmd = ['/usr/bin/curl', '--fail', '--silent', url]
91 for (k, v) in headers.iteritems():
92 cmd += ['-H', '%s: %s' % (k, v)]
6793
68 cmd += ['-o', path]94 cmd += ['-o', path]
69 cmd_out = ' '.join(cmd)95 cmd_out = ' '.join(cmd)
70 return utils.execute(cmd_out)96 return utils.execute(cmd_out)
7197
7298
73def _fetch_local_image(image, path, user, project):99def _fetch_local_image(image, path, user, project):
74 source = _image_path('%s/image' % image)100 source = _image_path(os.path.join(image, 'image'))
75 return utils.execute('cp %s %s' % (source, path))101 if sys.platform.startswith('win'):
102 return shutil.copy(source, path)
103 else:
104 return utils.execute('cp %s %s' % (source, path))
76105
77106
78def _image_path(path):107def _image_path(path):