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