Merge lp:~chiradeep/nova/msft-hyper-v-support into lp:~hudson-openstack/nova/trunk
- msft-hyper-v-support
- Merge into trunk
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 |
Related bugs: | |
Related blueprints: |
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.
Commit message
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.
Jay Pipes (jaypipes) wrote : Posted in a previous version of this proposal | # |
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...
Chiradeep Vittal (chiradeep) 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.
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_
182 + [], None, vs_gs_data.
What does vs_gs_data.
>>>> 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.
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.
>>>> 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 ...
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_
> 182 + [], None, vs_gs_data.
>
> What does vs_gs_data.
> sv_man_
> 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
Soren Hansen (soren) wrote : Posted in a previous version of this proposal | # |
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/
> --- nova/compute/
> +++ nova/compute/
> @@ -39,6 +39,8 @@
> 'where instances are stored on disk')
> flags.DEFINE_
> 'Driver to use for volume creation')
> +flags.
> + 'path to decrypted local images if not using s3')
Each flag must only be defined once. images_path is also defined in
nova.objectstor
place and import that.
> === modified file 'nova/virt/
> --- nova/virt/
> +++ nova/virt/
> @@ -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_
> elif t == 'xenapi':
> conn = xenapi.
> + elif t == 'hyperv':
> + conn = hyperv.
> 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.
nova.utils.
connection_type. That would solve this problem.
> === added file 'nova/virt/
> --- 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://
> +#
> +# 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...
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.
Chiradeep Vittal (chiradeep) wrote : Posted in a previous version of this proposal | # |
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_
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/
> --- nova/compute/
> +++ nova/compute/
> @@ -39,6 +39,8 @@
> 'where instances are stored on disk')
> flags.DEFINE_
> 'Driver to use for volume creation')
> +flags.
> + 'path to decrypted local images if not using s3')
Each flag must only be defined once. images_path is also defined in
nova.objectstor
place and import that.
>>>> fixed by reverting.
> === modified file 'nova/virt/
> --- nova/virt/
> +++ nova/virt/
> @@ -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_
> elif t == 'xenapi':
> conn = xenapi.
> + elif t == 'hyperv':
> + conn = hyperv.
> else:
> ...
Soren Hansen (soren) wrote : | # |
On 14-10-2010 08:26, Chiradeep Vittal wrote:
> === added file 'nova/tests/
> --- nova/tests/
> +++ nova/tests/
> @@ -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://
> +#
> +# 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.
> +# 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 cases for the Hyper-V driver"""
> + def setUp(self): # pylint: disable-msg=C0103
> + pass
Why define it at all?
> +
> + def test_create_
> + """Create a VM and destroy it"""
> + instance = {'internal_id' : random.randint(1, 1000000),
> + 'memory_mb' : '1024',
> + 'mac_address' : '02:12:
> + 'vcpus' : 2,
> + 'project_id' : 'fake',
> + 'instance_type' : 'm1.small'}
> +
> + instance_ref = db.instance_
> +
> + conn = hyperv.
> + conn._create_
> + found = [n for n in conn.list_
> + if n == instance_
> + self.assertTrue
> + info = conn.get_
> + #Unfortunately since the vm is not running at this point,
> + #we cannot obtain memory information from get_info
> + self.assertEqua
> +
> + conn.destroy(
> + found = [n for n in conn.list_
> + if n == instance_
> + self.assertTrue
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/
> --- nova/virt/conne...
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.
Jay Pipes (jaypipes) wrote : | # |
Thanks for correcting and responding to all the comments from Soren and myself, Chiradeep. Good work :)
Chiradeep Vittal (chiradeep) wrote : | # |
> On 14-10-2010 08:26, Chiradeep Vittal wrote:
> > === added file 'nova/tests/
> > --- nova/tests/
> > +++ nova/tests/
> > @@ -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://
> > +#
> > +# 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.
> > +# 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 cases for the Hyper-V driver"""
> > + def setUp(self): # pylint: disable-msg=C0103
> > + pass
>
> Why define it at all?
>
> > +
> > + def test_create_
> > + """Create a VM and destroy it"""
> > + instance = {'internal_id' : random.randint(1, 1000000),
> > + 'memory_mb' : '1024',
> > + 'mac_address' : '02:12:
> > + 'vcpus' : 2,
> > + 'project_id' : 'fake',
> > + 'instance_type' : 'm1.small'}
> > +
> > + instance_ref = db.instance_
> > +
> > + conn = hyperv.
> > + conn._create_
> > + found = [n for n in conn.list_
> > + if n == instance_
> > + self.assertTrue
> > + info = conn.get_
> > + #Unfortunately since the vm is not running at this point,
> > + #we cannot obtain memory information from get_info
> > + self.assertEqua
> > +
> > + conn.destroy(
> > + found = [n for n in conn.list_
> > + if n == instance_
> > + self.assertTrue
>
> 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.
> ...
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 VmResourceAlloc
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 :)
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?
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 :-/
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/
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
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.
OpenStack Infra (hudson-openstack) wrote : | # |
Attempt to merge into lp:nova failed due to conflicts:
text conflict in nova/virt/images.py
Soren Hansen (soren) wrote : | # |
Chiradeep, the merge was approved, but failed.
You need to merge with trunk, commit, and push again.
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:/
> You are the owner of lp:~chiradeep/nova/msft-hyper-v-support.
Soren Hansen (soren) wrote : | # |
Great. Let me know if you need help.
--
Soren Hansen
Ubuntu Developer   http://
OpenStack Developer http://
Soren Hansen (soren) wrote : | # |
Ping?
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:/
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
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): |
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.DefineVirtu alSystem( GetText_ (1))[1: ]
182 + [], None, vs_gs_data.
What does vs_gs_data. GetText( 1) return? What does sv_man_ svc.DefineVirtu alSystem( ) 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 NotFound( 'instance not found %s' % instance_id)
...
raise exception.
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 = { SHUTDOWN, RUNNING,
119 + 3 : power_state.
120 + 2 : power_state.
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...