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

Proposed by Chiradeep Vittal
Status: Superseded
Proposed branch: lp:~chiradeep/nova/msft-hyper-v-support
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 678 lines (+585/-24)
5 files modified
nova/tests/hyperv_unittest.py (+67/-0)
nova/twistd.py (+20/-17)
nova/virt/connection.py (+3/-0)
nova/virt/hyperv.py (+459/-0)
nova/virt/images.py (+36/-7)
To merge this branch: bzr merge lp:~chiradeep/nova/msft-hyper-v-support
Reviewer Review Type Date Requested Status
Soren Hansen (community) Approve
Chiradeep Vittal (community) Needs Resubmitting
Jay Pipes (community) Approve
Review via email: mp+38387@code.launchpad.net

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

This proposal has been superseded by a proposal from 2010-12-15.

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 :
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 :

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 :

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 :
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 :

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 :

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 :

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 :

> 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 :

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 :

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 :

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 :

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 :

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 :

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 :

Ping?

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

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.

349. By Chiradeep Vittal <chiradeep@chiradeep-lt2>

Merged from trunk and fixed merge issues.
Also fixed pep8 issues

350. By Chiradeep Vittal <chiradeep@chiradeep-lt2>

remove some logging

351. By Chiradeep Vittal <chiradeep@chiradeep-lt2>

Merge from trunk: process replaced with util

352. By Chiradeep Vittal <chiradeep@chiradeep-lt2>

Merge from trunk: process replaced with util

353. By Chiradeep Vittal <chiradeep@chiradeep-lt2>

Merge from trunk again -- get rid of twistd dependencies

354. By Chiradeep Vittal <chiradeep@chiradeep-lt2>

i18n logging and exception strings

355. By Chiradeep Vittal <chiradeep@chiradeep-lt2>

Merge from trunk again

356. By Chiradeep Vittal <chiradeep@chiradeep-lt2>

Revert some unneeded formatting since twistd is no longer used

357. By Chiradeep Vittal <chiradeep@chiradeep-lt2>

Redis dependency no longer needed

358. By Chiradeep Vittal <chiradeep@chiradeep-lt2>

need one more newline

359. By Chiradeep Vittal

Make test case work again

360. By Chiradeep Vittal

Add to Authors and mailmap

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'nova/tests/hyperv_unittest.py'
2--- nova/tests/hyperv_unittest.py 1970-01-01 00:00:00 +0000
3+++ nova/tests/hyperv_unittest.py 2010-12-15 00:52:11 +0000
4@@ -0,0 +1,67 @@
5+# vim: tabstop=4 shiftwidth=4 softtabstop=4
6+#
7+# Copyright 2010 Cloud.com, Inc
8+#
9+# Licensed under the Apache License, Version 2.0 (the "License"); you may
10+# not use this file except in compliance with the License. You may obtain
11+# a copy of the License at
12+#
13+# http://www.apache.org/licenses/LICENSE-2.0
14+#
15+# Unless required by applicable law or agreed to in writing, software
16+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
17+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
18+# License for the specific language governing permissions and limitations
19+# under the License.
20+"""
21+Tests For Hyper-V driver
22+"""
23+
24+import random
25+
26+from nova import db
27+from nova import flags
28+from nova import test
29+
30+from nova.virt import hyperv
31+
32+FLAGS = flags.FLAGS
33+FLAGS.connection_type = 'hyperv'
34+# Redis is probably not running on Hyper-V host.
35+# Change this to the actual Redis host
36+FLAGS.redis_host = '127.0.0.1'
37+
38+
39+class HyperVTestCase(test.TrialTestCase):
40+ """Test cases for the Hyper-V driver"""
41+ def setUp(self): # pylint: disable-msg=C0103
42+ pass
43+
44+ def test_create_destroy(self):
45+ """Create a VM and destroy it"""
46+ instance = {'internal_id': random.randint(1, 1000000),
47+ 'memory_mb': '1024',
48+ 'mac_address': '02:12:34:46:56:67',
49+ 'vcpu': 2,
50+ 'project_id': 'fake',
51+ 'instance_type': 'm1.small'}
52+
53+ instance_ref = db.instance_create(None, instance)
54+
55+ conn = hyperv.get_connection(False)
56+ conn._create_vm(instance_ref) # pylint: disable-msg=W0212
57+ found = [n for n in conn.list_instances()
58+ if n == instance_ref['name']]
59+ self.assertTrue(len(found) == 1)
60+ info = conn.get_info(instance_ref['name'])
61+ #Unfortunately since the vm is not running at this point,
62+ #we cannot obtain memory information from get_info
63+ self.assertEquals(info['num_cpu'], instance_ref['vcpus'])
64+
65+ conn.destroy(instance_ref)
66+ found = [n for n in conn.list_instances()
67+ if n == instance_ref['name']]
68+ self.assertTrue(len(found) == 0)
69+
70+ def tearDown(self): # pylint: disable-msg=C0103
71+ pass
72
73=== modified file 'nova/twistd.py'
74--- nova/twistd.py 2010-11-23 20:52:00 +0000
75+++ nova/twistd.py 2010-12-15 00:52:11 +0000
76@@ -237,23 +237,26 @@
77 logging.getLogger('amqplib').setLevel(logging.WARN)
78 FLAGS.python = filename
79 FLAGS.no_save = True
80- if not FLAGS.pidfile:
81- FLAGS.pidfile = '%s.pid' % name
82- elif FLAGS.pidfile.endswith('twistd.pid'):
83- FLAGS.pidfile = FLAGS.pidfile.replace('twistd.pid', '%s.pid' % name)
84- # NOTE(vish): if we're running nodaemon, redirect the log to stdout
85- if FLAGS.nodaemon and not FLAGS.logfile:
86- FLAGS.logfile = "-"
87- if not FLAGS.logfile:
88- FLAGS.logfile = '%s.log' % name
89- elif FLAGS.logfile.endswith('twistd.log'):
90- FLAGS.logfile = FLAGS.logfile.replace('twistd.log', '%s.log' % name)
91- if FLAGS.logdir:
92- FLAGS.logfile = os.path.join(FLAGS.logdir, FLAGS.logfile)
93- if not FLAGS.prefix:
94- FLAGS.prefix = name
95- elif FLAGS.prefix.endswith('twisted'):
96- FLAGS.prefix = FLAGS.prefix.replace('twisted', name)
97+ if sys.platform != 'win32':
98+ if not FLAGS.pidfile:
99+ FLAGS.pidfile = '%s.pid' % name
100+ elif FLAGS.pidfile.endswith('twistd.pid'):
101+ FLAGS.pidfile = FLAGS.pidfile.replace('twistd.pid',
102+ '%s.pid' % name)
103+ # NOTE(vish): if we're running nodaemon, redirect the log to stdout
104+ if FLAGS.nodaemon and not FLAGS.logfile:
105+ FLAGS.logfile = "-"
106+ if not FLAGS.logfile:
107+ FLAGS.logfile = '%s.log' % name
108+ elif FLAGS.logfile.endswith('twistd.log'):
109+ FLAGS.logfile = FLAGS.logfile.replace('twistd.log',
110+ '%s.log' % name)
111+ if FLAGS.logdir:
112+ FLAGS.logfile = os.path.join(FLAGS.logdir, FLAGS.logfile)
113+ if not FLAGS.prefix:
114+ FLAGS.prefix = name
115+ elif FLAGS.prefix.endswith('twisted'):
116+ FLAGS.prefix = FLAGS.prefix.replace('twisted', name)
117
118 action = 'start'
119 if len(argv) > 1:
120
121=== modified file 'nova/virt/connection.py'
122--- nova/virt/connection.py 2010-11-29 16:31:31 +0000
123+++ nova/virt/connection.py 2010-12-15 00:52:11 +0000
124@@ -26,6 +26,7 @@
125 from nova.virt import fake
126 from nova.virt import libvirt_conn
127 from nova.virt import xenapi_conn
128+from nova.virt import hyperv
129
130
131 FLAGS = flags.FLAGS
132@@ -62,6 +63,8 @@
133 conn = libvirt_conn.get_connection(read_only)
134 elif t == 'xenapi':
135 conn = xenapi_conn.get_connection(read_only)
136+ elif t == 'hyperv':
137+ conn = hyperv.get_connection(read_only)
138 else:
139 raise Exception('Unknown connection type "%s"' % t)
140
141
142=== added file 'nova/virt/hyperv.py'
143--- nova/virt/hyperv.py 1970-01-01 00:00:00 +0000
144+++ nova/virt/hyperv.py 2010-12-15 00:52:11 +0000
145@@ -0,0 +1,459 @@
146+# vim: tabstop=4 shiftwidth=4 softtabstop=4
147+
148+# Copyright (c) 2010 Cloud.com, Inc
149+#
150+# Licensed under the Apache License, Version 2.0 (the "License"); you may
151+# not use this file except in compliance with the License. You may obtain
152+# a copy of the License at
153+#
154+# http://www.apache.org/licenses/LICENSE-2.0
155+#
156+# Unless required by applicable law or agreed to in writing, software
157+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
158+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
159+# License for the specific language governing permissions and limitations
160+# under the License.
161+
162+"""
163+A connection to Hyper-V .
164+Uses Windows Management Instrumentation (WMI) calls to interact with Hyper-V
165+Hyper-V WMI usage:
166+ http://msdn.microsoft.com/en-us/library/cc723875%28v=VS.85%29.aspx
167+The Hyper-V object model briefly:
168+ The physical computer and its hosted virtual machines are each represented
169+ by the Msvm_ComputerSystem class.
170+
171+ Each virtual machine is associated with a
172+ Msvm_VirtualSystemGlobalSettingData (vs_gs_data) instance and one or more
173+ Msvm_VirtualSystemSettingData (vmsetting) instances. For each vmsetting
174+ there is a series of Msvm_ResourceAllocationSettingData (rasd) objects.
175+ The rasd objects describe the settings for each device in a VM.
176+ Together, the vs_gs_data, vmsettings and rasds describe the configuration
177+ of the virtual machine.
178+
179+ Creating new resources such as disks and nics involves cloning a default
180+ rasd object and appropriately modifying the clone and calling the
181+ AddVirtualSystemResources WMI method
182+ Changing resources such as memory uses the ModifyVirtualSystemResources
183+ WMI method
184+
185+Using the Python WMI library:
186+ Tutorial:
187+ http://timgolden.me.uk/python/wmi/tutorial.html
188+ Hyper-V WMI objects can be retrieved simply by using the class name
189+ of the WMI object and optionally specifying a column to filter the
190+ result set. More complex filters can be formed using WQL (sql-like)
191+ queries.
192+ The parameters and return tuples of WMI method calls can gleaned by
193+ examining the doc string. For example:
194+ >>> vs_man_svc.ModifyVirtualSystemResources.__doc__
195+ ModifyVirtualSystemResources (ComputerSystem, ResourceSettingData[])
196+ => (Job, ReturnValue)'
197+ When passing setting data (ResourceSettingData) to the WMI method,
198+ an XML representation of the data is passed in using GetText_(1).
199+ Available methods on a service can be determined using method.keys():
200+ >>> vs_man_svc.methods.keys()
201+ vmsettings and rasds for a vm can be retrieved using the 'associators'
202+ method with the appropriate return class.
203+ Long running WMI commands generally return a Job (an instance of
204+ Msvm_ConcreteJob) whose state can be polled to determine when it finishes
205+
206+"""
207+
208+import os
209+import logging
210+import time
211+
212+from twisted.internet import defer
213+
214+from nova import exception
215+from nova import flags
216+from nova.auth import manager
217+from nova.compute import power_state
218+from nova.virt import images
219+
220+wmi = None
221+
222+
223+FLAGS = flags.FLAGS
224+
225+
226+HYPERV_POWER_STATE = {
227+ 3: power_state.SHUTDOWN,
228+ 2: power_state.RUNNING,
229+ 32768: power_state.PAUSED,
230+}
231+
232+
233+REQ_POWER_STATE = {
234+ 'Enabled': 2,
235+ 'Disabled': 3,
236+ 'Reboot': 10,
237+ 'Reset': 11,
238+ 'Paused': 32768,
239+ 'Suspended': 32769
240+}
241+
242+
243+WMI_JOB_STATUS_STARTED = 4096
244+WMI_JOB_STATE_RUNNING = 4
245+WMI_JOB_STATE_COMPLETED = 7
246+
247+
248+def get_connection(_):
249+ global wmi
250+ if wmi is None:
251+ wmi = __import__('wmi')
252+ return HyperVConnection()
253+
254+
255+class HyperVConnection(object):
256+ def __init__(self):
257+ self._conn = wmi.WMI(moniker='//./root/virtualization')
258+ self._cim_conn = wmi.WMI(moniker='//./root/cimv2')
259+
260+ def list_instances(self):
261+ """ Return the names of all the instances known to Hyper-V. """
262+ vms = [v.ElementName \
263+ for v in self._conn.Msvm_ComputerSystem(['ElementName'])]
264+ return vms
265+
266+ @defer.inlineCallbacks
267+ def spawn(self, instance):
268+ """ Create a new VM and start it."""
269+ vm = yield self._lookup(instance.name)
270+ if vm is not None:
271+ raise exception.Duplicate('Attempted to create duplicate name %s' %
272+ instance.name)
273+
274+ user = manager.AuthManager().get_user(instance['user_id'])
275+ project = manager.AuthManager().get_project(instance['project_id'])
276+ #Fetch the file, assume it is a VHD file.
277+ base_vhd_filename = os.path.join(FLAGS.instances_path,
278+ instance['str_id'])
279+ vhdfile = "%s.vhd" % (base_vhd_filename)
280+ yield images.fetch(instance['image_id'], vhdfile, user, project)
281+
282+ try:
283+ yield self._create_vm(instance)
284+
285+ yield self._create_disk(instance['name'], vhdfile)
286+ yield self._create_nic(instance['name'], instance['mac_address'])
287+
288+ logging.debug('Starting VM %s ', instance.name)
289+ yield self._set_vm_state(instance['name'], 'Enabled')
290+ logging.info('Started VM %s ', instance.name)
291+ except Exception as exn:
292+ logging.error('spawn vm failed: %s', exn)
293+ self.destroy(instance)
294+
295+ def _create_vm(self, instance):
296+ """Create a VM but don't start it. """
297+ vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
298+
299+ vs_gs_data = self._conn.Msvm_VirtualSystemGlobalSettingData.new()
300+ vs_gs_data.ElementName = instance['name']
301+ (job, ret_val) = vs_man_svc.DefineVirtualSystem(
302+ [], None, vs_gs_data.GetText_(1))[1:]
303+ if ret_val == WMI_JOB_STATUS_STARTED:
304+ success = self._check_job_status(job)
305+ else:
306+ success = (ret_val == 0)
307+
308+ if not success:
309+ raise Exception('Failed to create VM %s', instance.name)
310+
311+ logging.debug('Created VM %s...', instance.name)
312+ vm = self._conn.Msvm_ComputerSystem(ElementName=instance.name)[0]
313+
314+ vmsettings = vm.associators(
315+ wmi_result_class='Msvm_VirtualSystemSettingData')
316+ vmsetting = [s for s in vmsettings
317+ if s.SettingType == 3][0] # avoid snapshots
318+ memsetting = vmsetting.associators(
319+ wmi_result_class='Msvm_MemorySettingData')[0]
320+ #No Dynamic Memory, so reservation, limit and quantity are identical.
321+ mem = long(str(instance['memory_mb']))
322+ memsetting.VirtualQuantity = mem
323+ memsetting.Reservation = mem
324+ memsetting.Limit = mem
325+
326+ (job, ret_val) = vs_man_svc.ModifyVirtualSystemResources(
327+ vm.path_(), [memsetting.GetText_(1)])
328+ logging.debug('Set memory for vm %s...', instance.name)
329+ procsetting = vmsetting.associators(
330+ wmi_result_class='Msvm_ProcessorSettingData')[0]
331+ vcpus = long(instance['vcpus'])
332+ procsetting.VirtualQuantity = vcpus
333+ procsetting.Reservation = vcpus
334+ procsetting.Limit = vcpus
335+
336+ (job, ret_val) = vs_man_svc.ModifyVirtualSystemResources(
337+ vm.path_(), [procsetting.GetText_(1)])
338+ logging.debug('Set vcpus for vm %s...', instance.name)
339+
340+ def _create_disk(self, vm_name, vhdfile):
341+ """Create a disk and attach it to the vm"""
342+ logging.debug("Creating disk for %s by attaching disk file %s",
343+ vm_name, vhdfile)
344+ #Find the IDE controller for the vm.
345+ vms = self._conn.MSVM_ComputerSystem(ElementName=vm_name)
346+ vm = vms[0]
347+ vmsettings = vm.associators(
348+ wmi_result_class='Msvm_VirtualSystemSettingData')
349+ rasds = vmsettings[0].associators(
350+ wmi_result_class='MSVM_ResourceAllocationSettingData')
351+ ctrller = [r for r in rasds
352+ if r.ResourceSubType == 'Microsoft Emulated IDE Controller'\
353+ and r.Address == "0"]
354+ #Find the default disk drive object for the vm and clone it.
355+ diskdflt = self._conn.query(
356+ "SELECT * FROM Msvm_ResourceAllocationSettingData \
357+ WHERE ResourceSubType LIKE 'Microsoft Synthetic Disk Drive'\
358+ AND InstanceID LIKE '%Default%'")[0]
359+ diskdrive = self._clone_wmi_obj(
360+ 'Msvm_ResourceAllocationSettingData', diskdflt)
361+ #Set the IDE ctrller as parent.
362+ diskdrive.Parent = ctrller[0].path_()
363+ diskdrive.Address = 0
364+ #Add the cloned disk drive object to the vm.
365+ new_resources = self._add_virt_resource(diskdrive, vm)
366+ if new_resources is None:
367+ raise Exception('Failed to add diskdrive to VM %s',
368+ vm_name)
369+ diskdrive_path = new_resources[0]
370+ logging.debug("New disk drive path is %s", diskdrive_path)
371+ #Find the default VHD disk object.
372+ vhddefault = self._conn.query(
373+ "SELECT * FROM Msvm_ResourceAllocationSettingData \
374+ WHERE ResourceSubType LIKE 'Microsoft Virtual Hard Disk' AND \
375+ InstanceID LIKE '%Default%' ")[0]
376+
377+ #Clone the default and point it to the image file.
378+ vhddisk = self._clone_wmi_obj(
379+ 'Msvm_ResourceAllocationSettingData', vhddefault)
380+ #Set the new drive as the parent.
381+ vhddisk.Parent = diskdrive_path
382+ vhddisk.Connection = [vhdfile]
383+
384+ #Add the new vhd object as a virtual hard disk to the vm.
385+ new_resources = self._add_virt_resource(vhddisk, vm)
386+ if new_resources is None:
387+ raise Exception('Failed to add vhd file to VM %s',
388+ vm_name)
389+ logging.info("Created disk for %s ", vm_name)
390+
391+ def _create_nic(self, vm_name, mac):
392+ """Create a (emulated) nic and attach it to the vm"""
393+ logging.debug("Creating nic for %s ", vm_name)
394+ #Find the vswitch that is connected to the physical nic.
395+ vms = self._conn.Msvm_ComputerSystem(ElementName=vm_name)
396+ extswitch = self._find_external_network()
397+ vm = vms[0]
398+ switch_svc = self._conn.Msvm_VirtualSwitchManagementService()[0]
399+ #Find the default nic and clone it to create a new nic for the vm.
400+ #Use Msvm_SyntheticEthernetPortSettingData for Windows or Linux with
401+ #Linux Integration Components installed.
402+ emulatednics_data = self._conn.Msvm_EmulatedEthernetPortSettingData()
403+ default_nic_data = [n for n in emulatednics_data
404+ if n.InstanceID.rfind('Default') > 0]
405+ new_nic_data = self._clone_wmi_obj(
406+ 'Msvm_EmulatedEthernetPortSettingData',
407+ default_nic_data[0])
408+ #Create a port on the vswitch.
409+ (new_port, ret_val) = switch_svc.CreateSwitchPort(vm_name, vm_name,
410+ "", extswitch.path_())
411+ if ret_val != 0:
412+ logging.error("Failed creating a new port on the external vswitch")
413+ raise Exception('Failed creating port for %s',
414+ vm_name)
415+ logging.debug("Created switch port %s on switch %s",
416+ vm_name, extswitch.path_())
417+ #Connect the new nic to the new port.
418+ new_nic_data.Connection = [new_port]
419+ new_nic_data.ElementName = vm_name + ' nic'
420+ new_nic_data.Address = ''.join(mac.split(':'))
421+ new_nic_data.StaticMacAddress = 'TRUE'
422+ #Add the new nic to the vm.
423+ new_resources = self._add_virt_resource(new_nic_data, vm)
424+ if new_resources is None:
425+ raise Exception('Failed to add nic to VM %s',
426+ vm_name)
427+ logging.info("Created nic for %s ", vm_name)
428+
429+ def _add_virt_resource(self, res_setting_data, target_vm):
430+ """Add a new resource (disk/nic) to the VM"""
431+ vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
432+ (job, new_resources, ret_val) = vs_man_svc.\
433+ AddVirtualSystemResources([res_setting_data.GetText_(1)],
434+ target_vm.path_())
435+ success = True
436+ if ret_val == WMI_JOB_STATUS_STARTED:
437+ success = self._check_job_status(job)
438+ else:
439+ success = (ret_val == 0)
440+ if success:
441+ return new_resources
442+ else:
443+ return None
444+
445+ #TODO: use the reactor to poll instead of sleep
446+ def _check_job_status(self, jobpath):
447+ """Poll WMI job state for completion"""
448+ #Jobs have a path of the form:
449+ #\\WIN-P5IG7367DAG\root\virtualization:Msvm_ConcreteJob.InstanceID=
450+ #"8A496B9C-AF4D-4E98-BD3C-1128CD85320D"
451+ inst_id = jobpath.split('=')[1].strip('"')
452+ jobs = self._conn.Msvm_ConcreteJob(InstanceID=inst_id)
453+ if len(jobs) == 0:
454+ return False
455+ job = jobs[0]
456+ while job.JobState == WMI_JOB_STATE_RUNNING:
457+ time.sleep(0.1)
458+ job = self._conn.Msvm_ConcreteJob(InstanceID=inst_id)[0]
459+ if job.JobState != WMI_JOB_STATE_COMPLETED:
460+ logging.debug("WMI job failed: %s", job.ErrorSummaryDescription)
461+ return False
462+ logging.debug("WMI job succeeded: %s, Elapsed=%s ", job.Description,
463+ job.ElapsedTime)
464+ return True
465+
466+ def _find_external_network(self):
467+ """Find the vswitch that is connected to the physical nic.
468+ Assumes only one physical nic on the host
469+ """
470+ #If there are no physical nics connected to networks, return.
471+ bound = self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE')
472+ if len(bound) == 0:
473+ return None
474+ return self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE')[0]\
475+ .associators(wmi_result_class='Msvm_SwitchLANEndpoint')[0]\
476+ .associators(wmi_result_class='Msvm_SwitchPort')[0]\
477+ .associators(wmi_result_class='Msvm_VirtualSwitch')[0]
478+
479+ def _clone_wmi_obj(self, wmi_class, wmi_obj):
480+ """Clone a WMI object"""
481+ cl = self._conn.__getattr__(wmi_class) # get the class
482+ newinst = cl.new()
483+ #Copy the properties from the original.
484+ for prop in wmi_obj._properties:
485+ newinst.Properties_.Item(prop).Value =\
486+ wmi_obj.Properties_.Item(prop).Value
487+ return newinst
488+
489+ @defer.inlineCallbacks
490+ def reboot(self, instance):
491+ """Reboot the specified instance."""
492+ vm = yield self._lookup(instance.name)
493+ if vm is None:
494+ raise exception.NotFound('instance not present %s' % instance.name)
495+ self._set_vm_state(instance.name, 'Reboot')
496+
497+ @defer.inlineCallbacks
498+ def destroy(self, instance):
499+ """Destroy the VM. Also destroy the associated VHD disk files"""
500+ logging.debug("Got request to destroy vm %s", instance.name)
501+ vm = yield self._lookup(instance.name)
502+ if vm is None:
503+ defer.returnValue(None)
504+ vm = self._conn.Msvm_ComputerSystem(ElementName=instance.name)[0]
505+ vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
506+ #Stop the VM first.
507+ self._set_vm_state(instance.name, 'Disabled')
508+ vmsettings = vm.associators(
509+ wmi_result_class='Msvm_VirtualSystemSettingData')
510+ rasds = vmsettings[0].associators(
511+ wmi_result_class='MSVM_ResourceAllocationSettingData')
512+ disks = [r for r in rasds \
513+ if r.ResourceSubType == 'Microsoft Virtual Hard Disk']
514+ diskfiles = []
515+ #Collect disk file information before destroying the VM.
516+ for disk in disks:
517+ diskfiles.extend([c for c in disk.Connection])
518+ #Nuke the VM. Does not destroy disks.
519+ (job, ret_val) = vs_man_svc.DestroyVirtualSystem(vm.path_())
520+ if ret_val == WMI_JOB_STATUS_STARTED:
521+ success = self._check_job_status(job)
522+ elif ret_val == 0:
523+ success = True
524+ if not success:
525+ raise Exception('Failed to destroy vm %s' % instance.name)
526+ #Delete associated vhd disk files.
527+ for disk in diskfiles:
528+ vhdfile = self._cim_conn.CIM_DataFile(Name=disk)
529+ for vf in vhdfile:
530+ vf.Delete()
531+ logging.debug("Deleted disk %s vm %s", vhdfile, instance.name)
532+
533+ def get_info(self, instance_id):
534+ """Get information about the VM"""
535+ vm = self._lookup(instance_id)
536+ if vm is None:
537+ raise exception.NotFound('instance not present %s' % instance_id)
538+ vm = self._conn.Msvm_ComputerSystem(ElementName=instance_id)[0]
539+ vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
540+ vmsettings = vm.associators(
541+ wmi_result_class='Msvm_VirtualSystemSettingData')
542+ settings_paths = [v.path_() for v in vmsettings]
543+ #See http://msdn.microsoft.com/en-us/library/cc160706%28VS.85%29.aspx
544+ summary_info = vs_man_svc.GetSummaryInformation(
545+ [4, 100, 103, 105], settings_paths)[1]
546+ info = summary_info[0]
547+ logging.debug("Got Info for vm %s: state=%s, mem=%s, num_cpu=%s, \
548+ cpu_time=%s", instance_id,
549+ str(HYPERV_POWER_STATE[info.EnabledState]),
550+ str(info.MemoryUsage),
551+ str(info.NumberOfProcessors),
552+ str(info.UpTime))
553+
554+ return {'state': HYPERV_POWER_STATE[info.EnabledState],
555+ 'max_mem': info.MemoryUsage,
556+ 'mem': info.MemoryUsage,
557+ 'num_cpu': info.NumberOfProcessors,
558+ 'cpu_time': info.UpTime}
559+
560+ def _lookup(self, i):
561+ vms = self._conn.Msvm_ComputerSystem(ElementName=i)
562+ n = len(vms)
563+ if n == 0:
564+ return None
565+ elif n > 1:
566+ raise Exception('duplicate name found: %s' % i)
567+ else:
568+ return vms[0].ElementName
569+
570+ def _set_vm_state(self, vm_name, req_state):
571+ """Set the desired state of the VM"""
572+ vms = self._conn.Msvm_ComputerSystem(ElementName=vm_name)
573+ if len(vms) == 0:
574+ return False
575+ (job, ret_val) = vms[0].RequestStateChange(REQ_POWER_STATE[req_state])
576+ success = False
577+ if ret_val == WMI_JOB_STATUS_STARTED:
578+ success = self._check_job_status(job)
579+ elif ret_val == 0:
580+ success = True
581+ elif ret_val == 32775:
582+ #Invalid state for current operation. Typically means it is
583+ #already in the state requested
584+ success = True
585+ if success:
586+ logging.info("Successfully changed vm state of %s to %s",
587+ vm_name, req_state)
588+ else:
589+ logging.error("Failed to change vm state of %s to %s",
590+ vm_name, req_state)
591+ raise Exception("Failed to change vm state of %s to %s",
592+ vm_name, req_state)
593+
594+ def attach_volume(self, instance_name, device_path, mountpoint):
595+ vm = self._lookup(instance_name)
596+ if vm is None:
597+ raise exception.NotFound('Cannot attach volume to missing %s vm' %
598+ instance_name)
599+
600+ def detach_volume(self, instance_name, mountpoint):
601+ vm = self._lookup(instance_name)
602+ if vm is None:
603+ raise exception.NotFound('Cannot detach volume from missing %s ' %
604+ instance_name)
605
606=== modified file 'nova/virt/images.py'
607--- nova/virt/images.py 2010-10-22 00:15:21 +0000
608+++ nova/virt/images.py 2010-12-15 00:52:11 +0000
609@@ -21,8 +21,12 @@
610 Handling of VM disk images.
611 """
612
613+import logging
614 import os.path
615+import shutil
616+import sys
617 import time
618+import urllib2
619 import urlparse
620
621 from nova import flags
622@@ -45,6 +49,25 @@
623 return f(image, path, user, project)
624
625
626+def _fetch_image_no_curl(url, path, headers):
627+ request = urllib2.Request(url)
628+ for (k, v) in headers.iteritems():
629+ request.add_header(k, v)
630+
631+ def urlretrieve(urlfile, fpath):
632+ chunk = 1 * 1024 * 1024
633+ f = open(fpath, "wb")
634+ while 1:
635+ data = urlfile.read(chunk)
636+ if not data:
637+ break
638+ f.write(data)
639+
640+ urlopened = urllib2.urlopen(request)
641+ urlretrieve(urlopened, path)
642+ logging.debug("Finished retreving %s -- placed in %s", url, path)
643+
644+
645 def _fetch_s3_image(image, path, user, project):
646 url = image_url(image)
647
648@@ -61,17 +84,23 @@
649 url_path)
650 headers['Authorization'] = 'AWS %s:%s' % (access, signature)
651
652- cmd = ['/usr/bin/curl', '--fail', '--silent', url]
653- for (k, v) in headers.iteritems():
654- cmd += ['-H', '%s: %s' % (k, v)]
655+ if sys.platform.startswith('win'):
656+ return _fetch_image_no_curl(url, path, headers)
657+ else:
658+ cmd = ['/usr/bin/curl', '--fail', '--silent', url]
659+ for (k, v) in headers.iteritems():
660+ cmd += ['-H', '%s: %s' % (k, v)]
661
662- cmd += ['-o', path]
663- return process.SharedPool().execute(executable=cmd[0], args=cmd[1:])
664+ cmd += ['-o', path]
665+ return process.SharedPool().execute(executable=cmd[0], args=cmd[1:])
666
667
668 def _fetch_local_image(image, path, user, project):
669- source = _image_path('%s/image' % image)
670- return process.simple_execute('cp %s %s' % (source, path))
671+ source = _image_path(os.path.join(image, 'image'))
672+ if sys.platform.startswith('win'):
673+ return shutil.copy(source, path)
674+ else:
675+ return process.simple_execute('cp %s %s' % (source, path))
676
677
678 def _image_path(path):