Merge lp:~citrix-openstack/nova/vmware-vsphere-support into lp:~hudson-openstack/nova/trunk

Proposed by Sateesh
Status: Merged
Approved by: Soren Hansen
Approved revision: 476
Merged at revision: 881
Proposed branch: lp:~citrix-openstack/nova/vmware-vsphere-support
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 4928 lines (+4779/-1)
24 files modified
Authors (+1/-0)
doc/source/vmwareapi_readme.rst (+218/-0)
nova/console/vmrc.py (+144/-0)
nova/console/vmrc_manager.py (+158/-0)
nova/network/vmwareapi_net.py (+91/-0)
nova/tests/test_vmwareapi.py (+252/-0)
nova/tests/vmwareapi/__init__.py (+21/-0)
nova/tests/vmwareapi/db_fakes.py (+109/-0)
nova/tests/vmwareapi/stubs.py (+46/-0)
nova/virt/connection.py (+4/-1)
nova/virt/vmwareapi/__init__.py (+19/-0)
nova/virt/vmwareapi/error_util.py (+96/-0)
nova/virt/vmwareapi/fake.py (+711/-0)
nova/virt/vmwareapi/io_util.py (+168/-0)
nova/virt/vmwareapi/network_utils.py (+149/-0)
nova/virt/vmwareapi/read_write_util.py (+182/-0)
nova/virt/vmwareapi/vim.py (+176/-0)
nova/virt/vmwareapi/vim_util.py (+217/-0)
nova/virt/vmwareapi/vm_util.py (+306/-0)
nova/virt/vmwareapi/vmops.py (+789/-0)
nova/virt/vmwareapi/vmware_images.py (+201/-0)
nova/virt/vmwareapi_conn.py (+375/-0)
tools/esx/guest_tool.py (+345/-0)
tools/pip-requires (+1/-0)
To merge this branch: bzr merge lp:~citrix-openstack/nova/vmware-vsphere-support
Reviewer Review Type Date Requested Status
Rick Harris (community) Approve
Jay Pipes (community) Approve
Rick Clark (community) Approve
Ed Leafe Pending
Review via email: mp+53060@code.launchpad.net

Commit message

Implementation of blueprint hypervisor-vmware-vsphere-support. (Link to blueprint: https://blueprints.launchpad.net/nova/+spec/hypervisor-vmware-vsphere-support)

Adds support for hypervisor vmware ESX/ESXi server in OpenStack (Nova).

Key features included are,
1) Support for FLAT and VLAN networking model
2) Support for Guest console access through VMware vmrc
3) Integrated with Glance service for image storage and retrival

Documentation: A readme file at "doc/source/vmwareapi_readme.rst" encapsulates configuration/installation instructions required to use this module/feature.

Description of the change

Support for hypervisor vmware ESX/ESXi server in OpenStack.

Key features included are,
1) Support for FLAT and VLAN networking model
2) Support for Guest console access through VMware vmrc
3) Integrated with Glance service for image storage and retrival

Caveats: Stress/Scalability testing is in progress for this module.

Documentation: A readme file is provided as "doc/source/vmwareapi_readme.rst" that helps the user to tryout this module. All configuration/installation instructions are provided. Hope that helps.

To post a comment you must log in.
Revision history for this message
Jay Pipes (jaypipes) wrote :
Download full text (8.1 KiB)

Hi Sateesh!

Phew, this is a big patch! I commend you for being able to go through the VMWare SDK and SOAP API docs...

Some suggestions and style nits :)

47 +Currently supports Nova's flat networking model (Flat Manager).

You note in the description that you are supporting VLAN and FLAT. The above line is from the documentation. May want to correct the docs there.

601 + NetworkHelper.get_vlanid_and_vswicth_for_portgroup(session, bridge)
2139 + def get_vlanid_and_vswicth_for_portgroup(cls, session, pg_name):

Misspelling on the above lines... looks like some Autocomplete fail.

691 + """ Create and spawn the VM """

There are a number of docstrings which incorrectly have a space after and before the opening and closing """ marks. Please correct those. Should be like so:

"""Create and spawn the VM"""

For multi-line docstrings, we use the pep0257 recommendations, so:

"""Create and spawn the VM

This is a more descriptive description...
"""

or:

"""
Create and spawn the VM

This is a more descriptive description...
"""

or even:

"""
Create and spawn the VM

This is a more descriptive description...

"""

All of which will be identical once newline-trimmed, as most all doc-generation does.

See http://www.python.org/dev/peps/pep-0257/ for more details.

Some comments are like this:

03 + #Check if the vsiwtch associated is proper

and some are like this:

713 + # Get Nova record for VM

Please use the latter, with a space after the #.

857 === added file 'nova/tests/vmwareapi/db_fakes.py'

There are already a number of fakes in the test suite(s). Was it necessary to create another one just for the vmwareapi? Can we use one of the existing ones?

1101 +class VimException(Exception):
1102 + """The VIM Exception class"""

The acronym VIM is a bit confusing, and it is used in a number of files in this patch. Most everyone associates VIM with the ViM editor (just Google what is (a) VIM). What does VIM stand for here?

1886 === added file 'nova/virt/vmwareapi/io_util.py'

This file smells a bit like Not Invented Here syndrome... I think it would be best to use an existing library for this type of functionality. Try checking out Eventlet's greenthread-aware Queue class: http://eventlet.net/doc/modules/queue.html.

2279 +class GlanceHTTPWriteFile(ImageServiceFile):
2280 + """Glance file write handler class"""
2281 +
2282 + def __init__(self, host, port, image_id, file_size, os_type, adapter_type,
2283 + version=1, scheme="http"):
2284 + base_url = "%s://%s:%s/images/%s" % (scheme, host, port, image_id)
2285 + (scheme, netloc, path, params, query, fragment) = \
2286 + urlparse.urlparse(base_url)
2287 + if scheme == "http":
2288 + conn = httplib.HTTPConnection(netloc)
2289 + elif scheme == "https":
2290 + conn = httplib.HTTPSConnection(netloc)
2291 + conn.putrequest("PUT", path)
2292 + conn.putheader("User-Agent", USER_AGENT)
2293 + conn.putheader("Content-Length", file_size)
2294 + conn.putheader("Content-Type", "application/octet-stream")
2295 + conn.putheader("x-image-meta-store", "file")
2296 + conn.putheader("x-image-meta-is_public", "True")
2297 + conn.putheader("x-image-meta-type", "raw")
2298 + conn.putheader("x-image-meta-size", file_size)...

Read more...

review: Needs Fixing
Revision history for this message
Sateesh (sateesh-chodapuneedi) wrote :
Download full text (10.1 KiB)

Hi Jay,

Thanks a lot for your review comments. Yeah, this patch might have taken a lot of time from you!

I have incorporated the changes as follows,

* Updated document vmware_readme.rst to mention VLAN networking

* Corrected docstrings as per pep0257 recommentations.

* Stream-lined the comments. (Now all comments starts with "# "

* Updated code with locals() where ever applicable.

* About VIM in the source code/files: 'VIM' stands for VMware Virtual Infrastructure Methodology. We have used the terminology from VMware. Now added a question in FAQ inside vmware_readme.rst in doc/source. A reference doc can be seen at https://www.vmware.com/pdf/vim_datasheet.pdf

* About new fake db: The vmwareapi fake module uses a different set of fields and hence the structures required are different. Ex: bridge : 'xenbr0' does not hold good for VMware environment and bridge : 'vmnic0' is used instead. Also return values varies, hence went for implementing separate fake db.

* Now using eventlet library instead and removed io_utils.py from branch.

* Now using glance.client.Client instead of homegrown code to talk to Glance server to handle images.

* Corrected all mis-spelled function names and corresponding calls. Yeah, an auto-complete side-effect!

Please have a look at the branch and let me know your views. Thanks for your time.

Regards,
Sateesh

> Hi Sateesh!
>
> Phew, this is a big patch! I commend you for being able to go through the
> VMWare SDK and SOAP API docs...
>
> Some suggestions and style nits :)
>
> 47 +Currently supports Nova's flat networking model (Flat Manager).
>
> You note in the description that you are supporting VLAN and FLAT. The above
> line is from the documentation. May want to correct the docs there.
>
> 601 + NetworkHelper.get_vlanid_and_vswicth_for_portgroup(session, bridge)
> 2139 + def get_vlanid_and_vswicth_for_portgroup(cls, session, pg_name):
>
> Misspelling on the above lines... looks like some Autocomplete fail.
>
> 691 + """ Create and spawn the VM """
>
> There are a number of docstrings which incorrectly have a space after and
> before the opening and closing """ marks. Please correct those. Should be like
> so:
>
> """Create and spawn the VM"""
>
> For multi-line docstrings, we use the pep0257 recommendations, so:
>
> """Create and spawn the VM
>
> This is a more descriptive description...
> """
>
> or:
>
> """
> Create and spawn the VM
>
> This is a more descriptive description...
> """
>
> or even:
>
> """
> Create and spawn the VM
>
> This is a more descriptive description...
>
> """
>
> All of which will be identical once newline-trimmed, as most all doc-
> generation does.
>
> See http://www.python.org/dev/peps/pep-0257/ for more details.
>
> Some comments are like this:
>
> 03 + #Check if the vsiwtch associated is proper
>
> and some are like this:
>
> 713 + # Get Nova record for VM
>
> Please use the latter, with a space after the #.
>
> 857 === added file 'nova/tests/vmwareapi/db_fakes.py'
>
> There are already a number of fakes in the test suite(s). Was it necessary to
> create another one just for the vmwareapi? Can we use one of the existi...

Revision history for this message
Jay Pipes (jaypipes) wrote :
Download full text (4.0 KiB)

Hi again!

Great cleanups! Some other stuff I didn't catch ...

19 +..
20 +
21 + Copyright (c) 2010 Citrix Systems, Inc.
22 + Copyright 2010 OpenStack LLC.

That newline (line 20 in the diff) needs to be removed, otherwise the copyright notice is dumped into the doc output...

The RST file could use a little cleanup, too, and I'll ask Anne Gentle to assist you on a future commit with those. :)

702 + FLAGS.vmwareapi_host_ip = 'test_url'
703 + FLAGS.vmwareapi_host_username = 'test_username'
704 + FLAGS.vmwareapi_host_password = 'test_pass'

So, you are doing the above in the VMWareAPIVMTestCase.setUp() method. There is a subtle problem with that: it changes the value of those flags from their default value and does not reset the flag values to their default values after the tests complete. In other words, that makes the unit test have "side effects". While it's unlikely that other unit tests will need those flags, you may indeed add an additional unit test for VMWare stuff in the future, and you will rip your hair out trying to figure out the weirdness of why a FLAG value isn't what you expect. Believe me, I've been there! ;)

The solution is to do the following, since your VMWareAPIVMTestCase class derives from nova.test.TestCase:

def setUp(self):
    ...
    self.flags(vmwareapi_host_ip='test_url',
               vmwareapi_host_username='test_username',
               vmwareapi_host_password='test_pass')

nova.test.TestCase.tearDown() automatically resets any changed flags back to their original values.

1151 + """VI Attribute Error."""

s/VI/VIM

1227 +A fake VMWare VI API implementation.

s/VI/VIM

1259 + #We fake the datastore by keeping the file references as a list of
1260 + #names in the db
1617 + #Check if the remove is for a single file object or for a folder
1624 + #Removes the files in the folder and the folder too from the db
1850 + #This means that we are doing a search for the managed
1851 + #dataobects of the type in the inventory
1855 + #Create a temp Managed object which has the same ref
1856 + #as the parent object and copies just the properties
1857 + #asked for. We need .obj along with the propSet of
1858 + #just the properties asked for
1987 + #Meaning there are no networks on the host. suds responds with a ""
1988 + #in the parent property field rather than a [] in the
1989 + #ManagedObjectRefernce property field of the parent
2007 + #Get the list of vSwicthes on the Host System
2013 + #Meaning there are no vSwitches on the host. Shouldn't be the case,
2014 + #but just doing code check
2018 + #Get the vSwitch associated with the network adapter
2035 + #Meaning there are no physical nics on the host
2087 + #There can be a race condition when two instances try
2088 + #adding port groups at the same time. One succeeds, then
2089 + #the other one will get an exception. Since we are
2090 + #concerned with the port group being created, which is done
2091 + #by the other call, we can ignore the exception.
2480 + #For getting to hostFolder from datacnetr
2484 + #For getting to vmFolder from datacenter
2488 + #For getting Host System to virtual machine
2493 + #For getting to Host System from Compute Resource
2497 + #For getting ...

Read more...

review: Needs Fixing
Revision history for this message
Sateesh (sateesh-chodapuneedi) wrote :

Hi Jay,

Thanks for the second review.
Now incorporated changes as per review comments.

> That newline (line 20 in the diff) needs to be removed, otherwise the
> copyright notice is dumped into the doc output...

Removed the newline.

> def setUp(self):
> ...
> self.flags(vmwareapi_host_ip='test_url',
> vmwareapi_host_username='test_username',
> vmwareapi_host_password='test_pass')
>
> nova.test.TestCase.tearDown() automatically resets any changed flags back to
> their original values.

Replaced earlier code with this snippet.

> 1151 + """VI Attribute Error."""
>
> s/VI/VIM

It's now taken care.
>
> 1227 +A fake VMWare VI API implementation.
>
> s/VI/VIM

When it comes to API or SDK I think "VI API" or "VI SDK" stands more meaningful than "VIM API" or "VIM SDK". Few links I could refer for more insight into this are,
1) http://www.vmware.com/support/developer/vc-sdk/visdk25pubs/SDK-README.html
2) http://pubs.vmware.com/vi-sdk/visdk250/ReferenceGuide/
So, I haven't replaced those occurances of VI by VIM.

> 1259 + #We fake the datastore by keeping the file references as a list of
> .......
> One space after # in all those comments, please :)

Yeah, it's completed now.

> 1851 + #dataobects of the type in the inventory
>
> s/dataobects/dataobjects
>
> 2283 +RESP_NOT_XML_ERROR = 'Response is "text/html", not "text/xml'
>
> Missing closing " on text/xml.

Yes, taken care now.

> Finally, unless I'm mistaken, you removed the io_util.py file, and you said
> you are using eventlet now, but I don't see where? The io_util.py file was
> good in that it created a thread for both the image reader and writer. All I
> was saying in the prior review was to use eventlet's queue/greenthreads
> classes instead. Did you remove the multi-threading entirely?
Yes, now the multi-threading is introduced back through io_util.py again. It uses eventlets' greenthreads.

Kindly have another go at the branch. Thanks for your time.

Regards,
Sateesh

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

> > 1227 +A fake VMWare VI API implementation.
> >
> > s/VI/VIM
>
> When it comes to API or SDK I think "VI API" or "VI SDK" stands more
> meaningful than "VIM API" or "VIM SDK". Few links I could refer for more
> insight into this are,
> 1) http://www.vmware.com/support/developer/vc-sdk/visdk25pubs/SDK-README.html
> 2) http://pubs.vmware.com/vi-sdk/visdk250/ReferenceGuide/
> So, I haven't replaced those occurances of VI by VIM.

OK, no worries. :) Makes sense.

> > Finally, unless I'm mistaken, you removed the io_util.py file, and you said
> > you are using eventlet now, but I don't see where? The io_util.py file was
> > good in that it created a thread for both the image reader and writer. All I
> > was saying in the prior review was to use eventlet's queue/greenthreads
> > classes instead. Did you remove the multi-threading entirely?
> Yes, now the multi-threading is introduced back through io_util.py again. It
> uses eventlets' greenthreads.

Nice work, I think. It may be a little more complex than it needs to be, but we can work on it in the future... :)

> Kindly have another go at the branch. Thanks for your time.

Looks good. Thanks, Sateesh!

-jay

review: Approve
Revision history for this message
Rick Clark (dendrobates) wrote :

I've looked at the code (briefly) and successfully run all the tests, but I can't actually test that this works, because I don't have access to vsphere. I'm sure you guys are doing integration testing, could you tell us what kind of testing you are doing? Can you give us any visibility into your test environment?

I'm approving this because it looks good and doesn't appear to break anything else, but I would really like to see this in action before release.

review: Approve
Revision history for this message
OpenStack Infra (hudson-openstack) wrote :
Download full text (140.4 KiB)

The attempt to merge lp:~citrix-openstack/nova/vmware-vsphere-support into lp:nova failed. Below is the output from the failed tests.

AccountsTest
    test_account_create OK
    test_account_delete OK
    test_account_update OK
    test_get_account OK
AdminAPITest
    test_admin_disabled OK
    test_admin_enabled OK
APITest
    test_exceptions_are_converted_to_faults OK
Test
    test_authorize_token OK
    test_authorize_user OK
    test_bad_token OK
    test_bad_user_bad_key OK
    test_bad_user_good_key OK
    test_no_user OK
    test_token_expiry OK
TestFunctional
    test_token_doesnotexist OK
    test_token_expiry OK
TestLimiter
    test_authorize_token OK
LimiterTest
    test_limiter_custom_max_limit OK
    test_limiter_limit_and_offset OK
    test_limiter_limit_medium OK
    test_limiter_limit_over_max OK
    test_limiter_limit_zero OK
    test_limiter_negative_limit OK
    test_limiter_negative_offset OK
    test_limiter_nothing OK
    test_limiter_offset_bad OK
    test_limiter_offset_blank OK
    test_limiter_offset_medium OK
    test_limiter_offset_over_max OK
    test_limiter_offset_zero OK
TestFaults
    test_fault_parts OK
    test_raise OK
    test_retry_header OK
FlavorsTest
    test_get_flavor_by_id OK
    test_get_flavor_list OK
    test_get_flavor_list_detail OK
GlanceImageServiceTest
    test_create OK
    test_create_and_show_non_existing_image OK
    test_delete OK
    test_update OK
ImageControllerWithGlanceServiceTest
    test_get_image_details OK
    test_get_image_index OK
LocalImageServiceTest
    test_create OK
 ...

Revision history for this message
Ewan Mellor (ewanmellor) wrote :

The OpenStack Hudson machine needs suds installed -- that's why all the tests have failed.

This is in the pip-requires file as part of this patch, but that's obviously not being used. I will follow up on the mailing list.

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

I remember that we had a similar problem when a dependency on Cheetah was introduced, and that Soren promptly fixed that, you might want to check with him

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

Done.

Revision history for this message
OpenStack Infra (hudson-openstack) wrote :
Download full text (140.5 KiB)

The attempt to merge lp:~citrix-openstack/nova/vmware-vsphere-support into lp:nova failed. Below is the output from the failed tests.

AccountsTest
    test_account_create OK
    test_account_delete OK
    test_account_update OK
    test_get_account OK
AdminAPITest
    test_admin_disabled OK
    test_admin_enabled OK
APITest
    test_exceptions_are_converted_to_faults OK
Test
    test_authorize_token OK
    test_authorize_user OK
    test_bad_token OK
    test_bad_user_bad_key OK
    test_bad_user_good_key OK
    test_no_user OK
    test_token_expiry OK
TestFunctional
    test_token_doesnotexist OK
    test_token_expiry OK
TestLimiter
    test_authorize_token OK
LimiterTest
    test_limiter_custom_max_limit OK
    test_limiter_limit_and_offset OK
    test_limiter_limit_medium OK
    test_limiter_limit_over_max OK
    test_limiter_limit_zero OK
    test_limiter_negative_limit OK
    test_limiter_negative_offset OK
    test_limiter_nothing OK
    test_limiter_offset_bad OK
    test_limiter_offset_blank OK
    test_limiter_offset_medium OK
    test_limiter_offset_over_max OK
    test_limiter_offset_zero OK
TestFaults
    test_fault_parts OK
    test_raise OK
    test_retry_header OK
FlavorsTest
    test_get_flavor_by_id OK
    test_get_flavor_list OK
    test_get_flavor_list_detail OK
GlanceImageServiceTest
    test_create OK
    test_create_and_show_non_existing_image OK
    test_delete OK
    test_update OK
ImageControllerWithGlanceServiceTest
    test_get_image_details OK
    test_get_image_index OK
LocalImageServiceTest
    test_create OK
 ...

Revision history for this message
Rick Harris (rconradharris) wrote :

Marking Needs Review, comments will follow shortly.

review: Needs Fixing
Revision history for this message
Rick Harris (rconradharris) wrote :

Really impressive work, Sateesh. Bonus points for the helpful comments and
included documentation. Good stuff.

I'll start with some general notes, and then I'll move into specific lines.

General
=======

* There are some minor whitespacing issues (arg lists not lining up[1], etc). This
  isn't a huge deal, but any fixes here would improve the code a tad.

* I'm noticing quite a bit of overriding of __getattr__ and __setattr__. In my
  experience, this can lead to pain down the road since any bugs in the
  getattr/setattr overrides tend to manifest themselves in delightfully
  awful/hard to pin-point ways. I can't offer an alternative solution without
  really getting into the details, but, I wanted to raise the concern at
  least.

* I see a bit of Java-ish patterns which could be simplified in Python. For
  example, some classes that only use @classmethods or @staticmethods. Unless
  there is a specific reason to bind this to a class, it's often a good idea
  to leave them as naked functions.

* Noticed a few overly broad exception handlers which may end up trapping
  unexpected exceptions. It would be better to be as specific as possible, and
  if you're explicitly trying to ignore all exceptions, perhaps log any
  unrecognized exception.

[1] Example:

> 3153 +def get_add_vswitch_port_group_spec(client_factory, vswitch_name,
> 3154 + port_group_name, vlan_id):

Might be better as:

    def get_add_vswitch_port_group_spec(client_factory, vswitch_name,
                                        port_group_name, vlan_id):

Specifics
=========

> 621 + if network_ref == None:

When comparing None better to use identity comparison:

  if network_ref is None:

If its None-ness doesn't matter (as is often the case), you can simplify to:

  if not network_ref:

> 828 + self.assertTrue(len(instances) == 1)
> 831 + self.assertTrue(len(instances) == 0)

Better would be:

    self.assertEqual(len(instances), 1)

The fact that it passes both operands to the function means it can generate a
more helpful error message.

> 628 + try:
> 1629 + _db_content.get("files").remove(file)
> 1630 + except Exception:
> 1631 + pass
> 1632 +

I'm not sure which exception you're guarding against here, IndexError?
It's usually better to be more specific in catching errors, as an example:

  try:
      _db_content.get("files").remove(file)
  except OSError, e:
      # ENOENT is the only acceptable error
      if e.errno != errno.ENOENT:
          raise

review: Needs Fixing
Revision history for this message
Rick Harris (rconradharris) wrote :
Download full text (7.9 KiB)

[Notes above were cut-off, continues here...]

> 2193 + for elem in vswitches:
> 2194 + try:
> 2195 + for nic_elem in elem.pnic:
> 2196 + if str(nic_elem).split('-')[-1].find(vlan_interface) != -1:
> 2197 + return elem.name
> 2198 + except Exception:
> 2199 + pass

Same thing, a more specific exception handler would be easier to follow,
modify later if needed.

If you're want to skip errors (no matter what), it might be better to log
unexpectd errors, for example

    try:
        <a bunch of code which can raise>
    except IndexError:
        pass # this is okay
    except Exception, e:
        logging.error(_('Unexpected exception raised %s') % e)

> 1750 + "ds": ds,

One space ---> thattaway.

> 2213 + for pnic in physical_nics:
> 2214 + if vlan_interface == pnic.device:
> 2215 + return True
> 2216 + return False

This is fine code, nice and readable. Just as a point of discussion, the `any`
builtin could be used as well:

    return any((vlan_interface == pnic.device) for pnic in physical_nics)

Either way is fine, IMHO.

> 2318 +try:
> 2319 + READ_CHUNKSIZE = client.BaseClient.CHUNKSIZE
> 2320 +except:
> 2321 + READ_CHUNKSIZE = 65536

Better to qualify the exception handler, something like:

    try:
        READ_CHUNKSIZE = client.BaseClient.CHUNKSIZE
    except AttributeError:
        READ_CHUNKSIZE = 65536

> 2544 + # Use this when VMware fixes their faulty wsdl

Per HACKING, please prefix with TODO(sateesh) or FIXME(sateesh).

> 1364 +class VirtualDisk(DataObject):
> 1365 + """
> 1366 + Virtual Disk class. Does nothing special except setting
> 1367 + __class__.__name__ to 'VirtualDisk'. Refer place where __class__.__name__
> 1368 + is used in the code.
> 1369 + """
> 1370 +
> 1371 + def __init__(self):
> 1372 + DataObject.__init__(self)
> 1373 +
> 1374 +
> 1375 +class VirtualDiskFlatVer2BackingInfo(DataObject):
> 1376 + """VirtualDiskFlatVer2BackingInfo class."""
> 1377 +
> 1378 + def __init__(self):
> 1379 + DataObject.__init__(self)
> 1380 +
> 1381 +
> 1382 +class VirtualLsiLogicController(DataObject):
> 1383 + """VirtualLsiLogicController class."""
> 1384 +
> 1385 + def __init__(self):
> 1386 + DataObject.__init__(self)

Since the __init__ methods are just callng up, you can omit them:

    class VirtualLsiLogicController(DataObject):
        """VirtualLsiLogicController class."""
        pass

> 1351 +class DataObject(object):
> 1352 + """Data object base class."""
> 1353 +
> 1354 + def __init__(self):
> 1355 + pass
> 1356 +
> 1357 + def __getattr__(self, attr):
> 1358 + return object.__getattribute__(self, attr)
> 1359 +
> 1360 + def __setattr__(self, attr, value):
> 1361 + object.__setattr__(self, attr, value)

Since the __init__ is a no-op, you can just omit it; same for __setattr__.

__getattr__ is doing something a little strange, it's calling __getattribute__
instead of __getattr__. If this is desired, it definitely should have a
"NOTE(sateesh)" explaining why it's nec...

Read more...

Revision history for this message
Sateesh (sateesh-chodapuneedi) wrote :

Hi Rick,

Thanks for your review.

I have updated the blueprint (under section "QA" at http://wiki.openstack.org/VMware-vSphere-support) with the test plan. Also please find the attachment in the same section for the results collected so far.

> I've looked at the code (briefly) and successfully run all the tests, but I
> can't actually test that this works, because I don't have access to vsphere.
> I'm sure you guys are doing integration testing, could you tell us what kind
> of testing you are doing? Can you give us any visibility into your test
> environment?
>
> I'm approving this because it looks good and doesn't appear to break anything
> else, but I would really like to see this in action before release.

-Regards,
Sateesh

Revision history for this message
Sateesh (sateesh-chodapuneedi) wrote :

Hi Soren,

I think the version of suds installed is causing this issue. It should be 0.4 or later.

plugin is missing from suds of earlier versions (0.3.9.* or earlier)

Reference links:

suds version 0.3.9 classes : http://suds.sourcearchive.com/documentation/0.3.9/classes.html

suds version 0.4 classes : http://suds.sourcearchive.com/documentation/0.4/classes.html

I think "easy_install suds==0.4" should work.

Regards,
Sateesh

Revision history for this message
Sateesh (sateesh-chodapuneedi) wrote :
Download full text (12.2 KiB)

Hi Rick,

Thank you for reviewing the branch. I have incorporated the changes into the branch and please find the desription below.

Kindly have a go at the branch and let me know if you have any comments. Thanks for your time.

Regards,
Sateesh

-----------------------------------------------------------------------------------------------
Changes as per the comments:-
-----------------------------------------------------------------------------------------------

> There are some minor whitespacing issues (arg lists not lining up[1], etc). This
> isn't a huge deal, but any fixes here would improve the code a tad.

>>> Taken care of

* I'm noticing quite a bit of overriding of __getattr__ and __setattr__. In my
  experience, this can lead to pain down the road since any bugs in the
  getattr/setattr overrides tend to manifest themselves in delightfully
  awful/hard to pin-point ways. I can't offer an alternative solution without
  really getting into the details, but, I wanted to raise the concern at
  least.

>>> Removed unnecessary overrides

* I see a bit of Java-ish patterns which could be simplified in Python. For
  example, some classes that only use @classmethods or @staticmethods. Unless
  there is a specific reason to bind this to a class, it's often a good idea
  to leave them as naked functions.

>>> Removed unnecessary @classmethods (Removed from network_utils.py). Retaining only one static method in a class FaultCheckers in error_util.py that is required to group fault checkers in case we add the same for some other API calls.

* Noticed a few overly broad exception handlers which may end up trapping
  unexpected exceptions. It would be better to be as specific as possible, and
  if you're explicitly trying to ignore all exceptions, perhaps log any
  unrecognized exception.

>>> Updated code to catch specific exceptions wherever possible. At some places, where we are catching generic Exceptions, added log statements.

[1] Example:

> 3153 +def get_add_vswitch_port_group_spec(client_factory, vswitch_name,
> 3154 + port_group_name, vlan_id):

Might be better as:

    def get_add_vswitch_port_group_spec(client_factory, vswitch_name,
                                        port_group_name, vlan_id):
>>> Taken care of

Specifics
=========

> 621 + if network_ref == None:

When comparing None better to use identity comparison:

  if network_ref is None:

If its None-ness doesn't matter (as is often the case), you can simplify to:

  if not network_ref:

>>> Yes, makes sense. Taken care of.

> 828 + self.assertTrue(len(instances) == 1)
> 831 + self.assertTrue(len(instances) == 0)

Better would be:

    self.assertEqual(len(instances), 1)

The fact that it passes both operands to the function means it can generate a
more helpful error message.

>>> Yes, makes sense. Taken care of.

> 628 + try:
> 1629 + _db_content.get("files").remove(file)
> 1630 + except Exception:
> 1631 + pass
> 1632 +

I'm not sure which exception you're guarding against here, IndexError?
It's usually better to be more specific in catchi...

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

Holy code review thread... approved from me, though I'll note that Rick's review was excellent.

review: Approve
Revision history for this message
Rick Harris (rconradharris) wrote :

Looks great, thanks a bunch for the fixups.

review: Approve
Revision history for this message
OpenStack Infra (hudson-openstack) wrote :
Download full text (145.5 KiB)

The attempt to merge lp:~citrix-openstack/nova/vmware-vsphere-support into lp:nova failed. Below is the output from the failed tests.

AccountsTest
    test_account_create OK
    test_account_delete OK
    test_account_update OK
    test_get_account OK
AdminAPITest
    test_admin_disabled OK
    test_admin_enabled OK
APITest
    test_exceptions_are_converted_to_faults OK
Test
    test_authorize_token OK
    test_authorize_user OK
    test_bad_token OK
    test_bad_user_bad_key OK
    test_bad_user_good_key OK
    test_no_user OK
    test_token_expiry OK
TestFunctional
    test_token_doesnotexist OK
    test_token_expiry OK
TestLimiter
    test_authorize_token OK
LimiterTest
    test_limiter_custom_max_limit OK
    test_limiter_limit_and_offset OK
    test_limiter_limit_medium OK
    test_limiter_limit_over_max OK
    test_limiter_limit_zero OK
    test_limiter_negative_limit OK
    test_limiter_negative_offset OK
    test_limiter_nothing OK
    test_limiter_offset_bad OK
    test_limiter_offset_blank OK
    test_limiter_offset_medium OK
    test_limiter_offset_over_max OK
    test_limiter_offset_zero OK
TestFaults
    test_fault_parts OK
    test_raise OK
    test_retry_header OK
FlavorsTest
    test_get_flavor_by_id OK
    test_get_flavor_list OK
    test_get_flavor_list_detail OK
GlanceImageServiceTest
    test_create OK
    test_create_and_show_non_existing_image OK
    test_delete OK
    test_update OK
ImageControllerWithGlanceServiceTest
    test_get_image_details OK
    test_get_image_index OK
    test_show_image OK
LocalImageServiceTest
 ...

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

Soren, Monty

as Sateesh as pointed out, this might be due to a wrong version of suds installed on Hudson, could you double-check that please?

Many thanks!
Armando

> Hi Soren,
>
> I think the version of suds installed is causing this issue. It should be 0.4
> or later.
>
> plugin is missing from suds of earlier versions (0.3.9.* or earlier)
>
> Reference links:
>
> suds version 0.3.9 classes :
> http://suds.sourcearchive.com/documentation/0.3.9/classes.html
>
> suds version 0.4 classes :
> http://suds.sourcearchive.com/documentation/0.4/classes.html
>
> I think "easy_install suds==0.4" should work.
>
> Regards,
> Sateesh

Revision history for this message
Sateesh (sateesh-chodapuneedi) wrote :

Hi Jay,

Thanks a lot for reviewing the branch :)

Regards,
Sateesh

> Holy code review thread... approved from me, though I'll note that Rick's
> review was excellent.

Revision history for this message
Sateesh (sateesh-chodapuneedi) wrote :

Hi Rick Harris,

Thanks a lot for reviewing the branch :)

Regards,
Sateesh

> Looks great, thanks a bunch for the fixups.

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

I just pushed a fresh version of suds to the Hudson box. Retrying.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Authors'
2--- Authors 2011-03-21 14:06:42 +0000
3+++ Authors 2011-03-24 16:38:31 +0000
4@@ -61,6 +61,7 @@
5 Ryan Lucio <rlucio@internap.com>
6 Salvatore Orlando <salvatore.orlando@eu.citrix.com>
7 Sandy Walsh <sandy.walsh@rackspace.com>
8+Sateesh Chodapuneedi <sateesh.chodapuneedi@citrix.com>
9 Soren Hansen <soren.hansen@rackspace.com>
10 Thierry Carrez <thierry@openstack.org>
11 Todd Willey <todd@ansolabs.com>
12
13=== added file 'doc/source/images/vmwareapi_blockdiagram.jpg'
14Binary files doc/source/images/vmwareapi_blockdiagram.jpg 1970-01-01 00:00:00 +0000 and doc/source/images/vmwareapi_blockdiagram.jpg 2011-03-24 16:38:31 +0000 differ
15=== added file 'doc/source/vmwareapi_readme.rst'
16--- doc/source/vmwareapi_readme.rst 1970-01-01 00:00:00 +0000
17+++ doc/source/vmwareapi_readme.rst 2011-03-24 16:38:31 +0000
18@@ -0,0 +1,218 @@
19+..
20+ Copyright (c) 2010 Citrix Systems, Inc.
21+ Copyright 2010 OpenStack LLC.
22+
23+ Licensed under the Apache License, Version 2.0 (the "License"); you may
24+ not use this file except in compliance with the License. You may obtain
25+ a copy of the License at
26+
27+ http://www.apache.org/licenses/LICENSE-2.0
28+
29+ Unless required by applicable law or agreed to in writing, software
30+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
31+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
32+ License for the specific language governing permissions and limitations
33+ under the License.
34+
35+VMware ESX/ESXi Server Support for OpenStack Compute
36+====================================================
37+
38+Introduction
39+------------
40+A module named 'vmwareapi' is added to 'nova.virt' to add support of VMware ESX/ESXi hypervisor to OpenStack compute (Nova). Nova may now use VMware vSphere as a compute provider.
41+
42+The basic requirement is to support VMware vSphere 4.1 as a compute provider within Nova. As the deployment architecture, support both ESX and ESXi. VM storage is restricted to VMFS volumes on local drives. vCenter is not required by the current design, and is not currently supported. Instead, Nova Compute talks directly to ESX/ESXi.
43+
44+The 'vmwareapi' module is integrated with Glance, so that VM images can be streamed from there for boot on ESXi using Glance server for image storage & retrieval.
45+
46+Currently supports Nova's flat networking model (Flat Manager) & VLAN networking model.
47+
48+.. image:: images/vmwareapi_blockdiagram.jpg
49+
50+
51+System Requirements
52+-------------------
53+Following software components are required for building the cloud using OpenStack on top of ESX/ESXi Server(s):
54+
55+* OpenStack
56+* Glance Image service
57+* VMware ESX v4.1 or VMware ESXi(licensed) v4.1
58+
59+VMware ESX Requirements
60+-----------------------
61+* ESX credentials with administration/root privileges
62+* Single local hard disk at the ESX host
63+* An ESX Virtual Machine Port Group (For Flat Networking)
64+* An ESX physical network adapter (For VLAN networking)
65+* Need to enable "vSphere Web Access" in "vSphere client" UI at Configuration->Security Profile->Firewall
66+
67+Python dependencies
68+-------------------
69+* suds-0.4
70+
71+* Installation procedure on Ubuntu/Debian
72+
73+::
74+
75+ easy_install suds==0.4
76+
77+
78+Configuration flags required for nova-compute
79+---------------------------------------------
80+::
81+
82+ --connection_type=vmwareapi
83+ --vmwareapi_host_ip=<VMware ESX Host IP>
84+ --vmwareapi_host_username=<VMware ESX Username>
85+ --vmwareapi_host_password=<VMware ESX Password>
86+ --network_driver=nova.network.vmwareapi_net [Optional, only for VLAN Networking]
87+ --vlan_interface=<Physical ethernet adapter name in VMware ESX host for vlan networking E.g vmnic0> [Optional, only for VLAN Networking]
88+
89+
90+Configuration flags required for nova-network
91+---------------------------------------------
92+::
93+
94+ --network_manager=nova.network.manager.FlatManager [or nova.network.manager.VlanManager]
95+ --flat_network_bridge=<ESX Virtual Machine Port Group> [Optional, only for Flat Networking]
96+
97+
98+Configuration flags required for nova-console
99+---------------------------------------------
100+::
101+
102+ --console_manager=nova.console.vmrc_manager.ConsoleVMRCManager
103+ --console_driver=nova.console.vmrc.VMRCSessionConsole [Optional, only for OTP (One time Passwords) as against host credentials]
104+
105+
106+Other flags
107+-----------
108+::
109+
110+ --image_service=nova.image.glance.GlanceImageService
111+ --glance_host=<Glance Host>
112+ --vmwareapi_wsdl_loc=<http://<WEB SERVER>/vimService.wsdl>
113+
114+Note:- Due to a faulty wsdl being shipped with ESX vSphere 4.1 we need a working wsdl which can to be mounted on any webserver. Follow the below steps to download the SDK,
115+
116+* Go to http://www.vmware.com/support/developer/vc-sdk/
117+* Go to section VMware vSphere Web Services SDK 4.0
118+* Click "Downloads"
119+* Enter VMware credentials when prompted for download
120+* Unzip the downloaded file vi-sdk-4.0.0-xxx.zip
121+* Go to SDK->WSDL->vim25 & host the files "vimService.wsdl" and "vim.wsdl" in a WEB SERVER
122+* Set the flag "--vmwareapi_wsdl_loc" with url, "http://<WEB SERVER>/vimService.wsdl"
123+
124+
125+VLAN Network Manager
126+--------------------
127+VLAN network support is added through a custom network driver in the nova-compute node i.e "nova.network.vmwareapi_net" and it uses a Physical ethernet adapter on the VMware ESX/ESXi host for VLAN Networking (the name of the ethernet adapter is specified as vlan_interface flag in the nova-compute configuration flag) in the nova-compute node.
128+
129+Using the physical adapter name the associated Virtual Switch will be determined. In VMware ESX there can be only one Virtual Switch associated with a Physical adapter.
130+
131+When VM Spawn request is issued with a VLAN ID the work flow looks like,
132+
133+1. Check that a Physical adapter with the given name exists. If no, throw an error.If yes, goto next step.
134+
135+2. Check if a Virtual Switch is associated with the Physical ethernet adapter with vlan interface name. If no, throw an error. If yes, goto next step.
136+
137+3. Check if a port group with the network bridge name exists. If no, create a port group in the Virtual switch with the give name and VLAN id and goto step 6. If yes, goto next step.
138+
139+4. Check if the port group is associated with the Virtual Switch. If no, throw an error. If yes, goto next step.
140+
141+5. Check if the port group is associated with the given VLAN Id. If no, throw an error. If yes, goto next step.
142+
143+6. Spawn the VM using this Port Group as the Network Name for the VM.
144+
145+
146+Guest console Support
147+---------------------
148+| VMware VMRC console is a built-in console method providing graphical control of the VM remotely.
149+|
150+| VMRC Console types supported:
151+| # Host based credentials
152+| Not secure (Sends ESX admin credentials in clear text)
153+|
154+| # OTP (One time passwords)
155+| Secure but creates multiple session entries in DB for each OpenStack console create request.
156+| Console sessions created is can be used only once.
157+|
158+| Install browser based VMware ESX plugins/activex on the client machine to connect
159+|
160+| Windows:-
161+| Internet Explorer:
162+| https://<VMware ESX Host>/ui/plugin/vmware-vmrc-win32-x86.exe
163+|
164+| Mozilla Firefox:
165+| https://<VMware ESX Host>/ui/plugin/vmware-vmrc-win32-x86.xpi
166+|
167+| Linux:-
168+| Mozilla Firefox
169+| 32-Bit Linux:
170+| https://<VMware ESX Host>/ui/plugin/vmware-vmrc-linux-x86.xpi
171+|
172+| 64-Bit Linux:
173+| https://<VMware ESX Host>/ui/plugin/vmware-vmrc-linux-x64.xpi
174+|
175+| OpenStack Console Details:
176+| console_type = vmrc+credentials | vmrc+session
177+| host = <VMware ESX Host>
178+| port = <VMware ESX Port>
179+| password = {'vm_id': <VMware VM ID>,'username':<VMware ESX Username>, 'password':<VMware ESX Password>} //base64 + json encoded
180+|
181+| Instantiate the plugin/activex object
182+| # In Internet Explorer
183+| <object id='vmrc' classid='CLSID:B94C2238-346E-4C5E-9B36-8CC627F35574'>
184+| </object>
185+|
186+| # Mozilla Firefox and other browsers
187+| <object id='vmrc' type='application/x-vmware-vmrc;version=2.5.0.0'>
188+| </object>
189+|
190+| Open vmrc connection
191+| # Host based credentials [type=vmrc+credentials]
192+| <script type="text/javascript">
193+| var MODE_WINDOW = 2;
194+| var vmrc = document.getElementById('vmrc');
195+| vmrc.connect(<VMware ESX Host> + ':' + <VMware ESX Port>, <VMware ESX Username>, <VMware ESX Password>, '', <VMware VM ID>, MODE_WINDOW);
196+| </script>
197+|
198+| # OTP (One time passwords) [type=vmrc+session]
199+| <script type="text/javascript">
200+| var MODE_WINDOW = 2;
201+| var vmrc = document.getElementById('vmrc');
202+| vmrc.connectWithSession(<VMware ESX Host> + ':' + <VMware ESX Port>, <VMware VM ID>, <VMware ESX Password>, MODE_WINDOW);
203+| </script>
204+
205+
206+Assumptions
207+-----------
208+1. The VMware images uploaded to the image repositories have VMware Tools installed.
209+
210+
211+FAQ
212+---
213+
214+1. What type of disk images are supported?
215+
216+* Only VMware VMDK's are currently supported and of that support is available only for thick disks, thin provisioned disks are not supported.
217+
218+
219+2. How is IP address information injected into the guest?
220+
221+* IP address information is injected through 'machine.id' vmx parameter (equivalent to XenStore in XenServer). This information can be retrived inside the guest using VMware tools.
222+
223+
224+3. What is the guest tool?
225+
226+* The guest tool is a small python script that should be run either as a service or added to system startup. This script configures networking on the guest. The guest tool is available at tools/esx/guest_tool.py
227+
228+
229+4. What type of consoles are supported?
230+
231+* VMware VMRC based consoles are supported. There are 2 options for credentials one is OTP (Secure but creates multiple session entries in DB for each OpenStack console create request.) & other is host based credentials (It may not be secure as ESX credentials are transmitted as clear text).
232+
233+5. What does 'Vim' refer to as far as vmwareapi module is concerned?
234+
235+* Vim refers to VMware Virtual Infrastructure Methodology. This is not to be confused with "VIM" editor.
236+
237
238=== added file 'nova/console/vmrc.py'
239--- nova/console/vmrc.py 1970-01-01 00:00:00 +0000
240+++ nova/console/vmrc.py 2011-03-24 16:38:31 +0000
241@@ -0,0 +1,144 @@
242+# vim: tabstop=4 shiftwidth=4 softtabstop=4
243+
244+# Copyright (c) 2011 Citrix Systems, Inc.
245+# Copyright 2011 OpenStack LLC.
246+#
247+# Licensed under the Apache License, Version 2.0 (the "License"); you may
248+# not use this file except in compliance with the License. You may obtain
249+# a copy of the License at
250+#
251+# http://www.apache.org/licenses/LICENSE-2.0
252+#
253+# Unless required by applicable law or agreed to in writing, software
254+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
255+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
256+# License for the specific language governing permissions and limitations
257+# under the License.
258+
259+"""
260+VMRC console drivers.
261+"""
262+
263+import base64
264+import json
265+
266+from nova import exception
267+from nova import flags
268+from nova import log as logging
269+from nova.virt.vmwareapi import vim_util
270+
271+flags.DEFINE_integer('console_vmrc_port',
272+ 443,
273+ "port for VMware VMRC connections")
274+flags.DEFINE_integer('console_vmrc_error_retries',
275+ 10,
276+ "number of retries for retrieving VMRC information")
277+
278+FLAGS = flags.FLAGS
279+
280+
281+class VMRCConsole(object):
282+ """VMRC console driver with ESX credentials."""
283+
284+ def __init__(self):
285+ super(VMRCConsole, self).__init__()
286+
287+ @property
288+ def console_type(self):
289+ return 'vmrc+credentials'
290+
291+ def get_port(self, context):
292+ """Get available port for consoles."""
293+ return FLAGS.console_vmrc_port
294+
295+ def setup_console(self, context, console):
296+ """Sets up console."""
297+ pass
298+
299+ def teardown_console(self, context, console):
300+ """Tears down console."""
301+ pass
302+
303+ def init_host(self):
304+ """Perform console initialization."""
305+ pass
306+
307+ def fix_pool_password(self, password):
308+ """Encode password."""
309+ # TODO(sateesh): Encrypt pool password
310+ return password
311+
312+ def generate_password(self, vim_session, pool, instance_name):
313+ """
314+ Returns VMRC Connection credentials.
315+
316+ Return string is of the form '<VM PATH>:<ESX Username>@<ESX Password>'.
317+ """
318+ username, password = pool['username'], pool['password']
319+ vms = vim_session._call_method(vim_util, "get_objects",
320+ "VirtualMachine", ["name", "config.files.vmPathName"])
321+ vm_ds_path_name = None
322+ vm_ref = None
323+ for vm in vms:
324+ vm_name = None
325+ ds_path_name = None
326+ for prop in vm.propSet:
327+ if prop.name == "name":
328+ vm_name = prop.val
329+ elif prop.name == "config.files.vmPathName":
330+ ds_path_name = prop.val
331+ if vm_name == instance_name:
332+ vm_ref = vm.obj
333+ vm_ds_path_name = ds_path_name
334+ break
335+ if vm_ref is None:
336+ raise exception.NotFound(_("instance - %s not present") %
337+ instance_name)
338+ json_data = json.dumps({"vm_id": vm_ds_path_name,
339+ "username": username,
340+ "password": password})
341+ return base64.b64encode(json_data)
342+
343+ def is_otp(self):
344+ """Is one time password or not."""
345+ return False
346+
347+
348+class VMRCSessionConsole(VMRCConsole):
349+ """VMRC console driver with VMRC One Time Sessions."""
350+
351+ def __init__(self):
352+ super(VMRCSessionConsole, self).__init__()
353+
354+ @property
355+ def console_type(self):
356+ return 'vmrc+session'
357+
358+ def generate_password(self, vim_session, pool, instance_name):
359+ """
360+ Returns a VMRC Session.
361+
362+ Return string is of the form '<VM MOID>:<VMRC Ticket>'.
363+ """
364+ vms = vim_session._call_method(vim_util, "get_objects",
365+ "VirtualMachine", ["name"])
366+ vm_ref = None
367+ for vm in vms:
368+ if vm.propSet[0].val == instance_name:
369+ vm_ref = vm.obj
370+ if vm_ref is None:
371+ raise exception.NotFound(_("instance - %s not present") %
372+ instance_name)
373+ virtual_machine_ticket = \
374+ vim_session._call_method(
375+ vim_session._get_vim(),
376+ "AcquireCloneTicket",
377+ vim_session._get_vim().get_service_content().sessionManager)
378+ json_data = json.dumps({"vm_id": str(vm_ref.value),
379+ "username": virtual_machine_ticket,
380+ "password": virtual_machine_ticket})
381+ return base64.b64encode(json_data)
382+
383+ def is_otp(self):
384+ """Is one time password or not."""
385+ return True
386
387=== added file 'nova/console/vmrc_manager.py'
388--- nova/console/vmrc_manager.py 1970-01-01 00:00:00 +0000
389+++ nova/console/vmrc_manager.py 2011-03-24 16:38:31 +0000
390@@ -0,0 +1,158 @@
391+# vim: tabstop=4 shiftwidth=4 softtabstop=4
392+
393+# Copyright (c) 2011 Citrix Systems, Inc.
394+# Copyright 2011 OpenStack LLC.
395+#
396+# Licensed under the Apache License, Version 2.0 (the "License"); you may
397+# not use this file except in compliance with the License. You may obtain
398+# a copy of the License at
399+#
400+# http://www.apache.org/licenses/LICENSE-2.0
401+#
402+# Unless required by applicable law or agreed to in writing, software
403+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
404+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
405+# License for the specific language governing permissions and limitations
406+# under the License.
407+
408+"""
409+VMRC Console Manager.
410+"""
411+
412+from nova import exception
413+from nova import flags
414+from nova import log as logging
415+from nova import manager
416+from nova import rpc
417+from nova import utils
418+from nova.virt.vmwareapi_conn import VMWareAPISession
419+
420+LOG = logging.getLogger("nova.console.vmrc_manager")
421+
422+FLAGS = flags.FLAGS
423+flags.DEFINE_string('console_public_hostname',
424+ '',
425+ 'Publicly visible name for this console host')
426+flags.DEFINE_string('console_driver',
427+ 'nova.console.vmrc.VMRCConsole',
428+ 'Driver to use for the console')
429+
430+
431+class ConsoleVMRCManager(manager.Manager):
432+
433+ """
434+ Manager to handle VMRC connections needed for accessing instance consoles.
435+ """
436+
437+ def __init__(self, console_driver=None, *args, **kwargs):
438+ self.driver = utils.import_object(FLAGS.console_driver)
439+ super(ConsoleVMRCManager, self).__init__(*args, **kwargs)
440+
441+ def init_host(self):
442+ self.sessions = {}
443+ self.driver.init_host()
444+
445+ def _get_vim_session(self, pool):
446+ """Get VIM session for the pool specified."""
447+ vim_session = None
448+ if pool['id'] not in self.sessions.keys():
449+ vim_session = VMWareAPISession(pool['address'],
450+ pool['username'],
451+ pool['password'],
452+ FLAGS.console_vmrc_error_retries)
453+ self.sessions[pool['id']] = vim_session
454+ return self.sessions[pool['id']]
455+
456+ def _generate_console(self, context, pool, name, instance_id, instance):
457+ """Sets up console for the instance."""
458+ LOG.debug(_("Adding console"))
459+
460+ password = self.driver.generate_password(
461+ self._get_vim_session(pool),
462+ pool,
463+ instance.name)
464+
465+ console_data = {'instance_name': name,
466+ 'instance_id': instance_id,
467+ 'password': password,
468+ 'pool_id': pool['id']}
469+ console_data['port'] = self.driver.get_port(context)
470+ console = self.db.console_create(context, console_data)
471+ self.driver.setup_console(context, console)
472+ return console
473+
474+ @exception.wrap_exception
475+ def add_console(self, context, instance_id, password=None,
476+ port=None, **kwargs):
477+ """
478+ Adds a console for the instance. If it is one time password, then we
479+ generate new console credentials.
480+ """
481+ instance = self.db.instance_get(context, instance_id)
482+ host = instance['host']
483+ name = instance['name']
484+ pool = self.get_pool_for_instance_host(context, host)
485+ try:
486+ console = self.db.console_get_by_pool_instance(context,
487+ pool['id'],
488+ instance_id)
489+ if self.driver.is_otp():
490+ console = self._generate_console(
491+ context,
492+ pool,
493+ name,
494+ instance_id,
495+ instance)
496+ except exception.NotFound:
497+ console = self._generate_console(
498+ context,
499+ pool,
500+ name,
501+ instance_id,
502+ instance)
503+ return console['id']
504+
505+ @exception.wrap_exception
506+ def remove_console(self, context, console_id, **_kwargs):
507+ """Removes a console entry."""
508+ try:
509+ console = self.db.console_get(context, console_id)
510+ except exception.NotFound:
511+ LOG.debug(_("Tried to remove non-existent console "
512+ "%(console_id)s.") %
513+ {'console_id': console_id})
514+ return
515+ LOG.debug(_("Removing console "
516+ "%(console_id)s.") %
517+ {'console_id': console_id})
518+ self.db.console_delete(context, console_id)
519+ self.driver.teardown_console(context, console)
520+
521+ def get_pool_for_instance_host(self, context, instance_host):
522+ """Gets console pool info for the instance."""
523+ context = context.elevated()
524+ console_type = self.driver.console_type
525+ try:
526+ pool = self.db.console_pool_get_by_host_type(context,
527+ instance_host,
528+ self.host,
529+ console_type)
530+ except exception.NotFound:
531+ pool_info = rpc.call(context,
532+ self.db.queue_get_for(context,
533+ FLAGS.compute_topic,
534+ instance_host),
535+ {"method": "get_console_pool_info",
536+ "args": {"console_type": console_type}})
537+ pool_info['password'] = self.driver.fix_pool_password(
538+ pool_info['password'])
539+ pool_info['host'] = self.host
540+ # ESX Address or Proxy Address
541+ public_host_name = pool_info['address']
542+ if FLAGS.console_public_hostname:
543+ public_host_name = FLAGS.console_public_hostname
544+ pool_info['public_hostname'] = public_host_name
545+ pool_info['console_type'] = console_type
546+ pool_info['compute_host'] = instance_host
547+ pool = self.db.console_pool_create(context, pool_info)
548+ return pool
549
550=== added file 'nova/network/vmwareapi_net.py'
551--- nova/network/vmwareapi_net.py 1970-01-01 00:00:00 +0000
552+++ nova/network/vmwareapi_net.py 2011-03-24 16:38:31 +0000
553@@ -0,0 +1,91 @@
554+# vim: tabstop=4 shiftwidth=4 softtabstop=4
555+
556+# Copyright (c) 2011 Citrix Systems, Inc.
557+# Copyright 2011 OpenStack LLC.
558+#
559+# Licensed under the Apache License, Version 2.0 (the "License"); you may
560+# not use this file except in compliance with the License. You may obtain
561+# a copy of the License at
562+#
563+# http://www.apache.org/licenses/LICENSE-2.0
564+#
565+# Unless required by applicable law or agreed to in writing, software
566+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
567+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
568+# License for the specific language governing permissions and limitations
569+# under the License.
570+
571+"""
572+Implements vlans for vmwareapi.
573+"""
574+
575+from nova import db
576+from nova import exception
577+from nova import flags
578+from nova import log as logging
579+from nova import utils
580+from nova.virt.vmwareapi_conn import VMWareAPISession
581+from nova.virt.vmwareapi import network_utils
582+
583+LOG = logging.getLogger("nova.network.vmwareapi_net")
584+
585+FLAGS = flags.FLAGS
586+flags.DEFINE_string('vlan_interface', 'vmnic0',
587+ 'Physical network adapter name in VMware ESX host for '
588+ 'vlan networking')
589+
590+
591+def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None):
592+ """Create a vlan and bridge unless they already exist."""
593+ # Open vmwareapi session
594+ host_ip = FLAGS.vmwareapi_host_ip
595+ host_username = FLAGS.vmwareapi_host_username
596+ host_password = FLAGS.vmwareapi_host_password
597+ if not host_ip or host_username is None or host_password is None:
598+ raise Exception(_("Must specify vmwareapi_host_ip,"
599+ "vmwareapi_host_username "
600+ "and vmwareapi_host_password to use"
601+ "connection_type=vmwareapi"))
602+ session = VMWareAPISession(host_ip, host_username, host_password,
603+ FLAGS.vmwareapi_api_retry_count)
604+ vlan_interface = FLAGS.vlan_interface
605+ # Check if the vlan_interface physical network adapter exists on the host
606+ if not network_utils.check_if_vlan_interface_exists(session,
607+ vlan_interface):
608+ raise exception.NotFound(_("There is no physical network adapter with "
609+ "the name %s on the ESX host") % vlan_interface)
610+
611+ # Get the vSwitch associated with the Physical Adapter
612+ vswitch_associated = network_utils.get_vswitch_for_vlan_interface(
613+ session, vlan_interface)
614+ if vswitch_associated is None:
615+ raise exception.NotFound(_("There is no virtual switch associated "
616+ "with the physical network adapter with name %s") %
617+ vlan_interface)
618+ # Check whether bridge already exists and retrieve the the ref of the
619+ # network whose name_label is "bridge"
620+ network_ref = network_utils.get_network_with_the_name(session, bridge)
621+ if network_ref is None:
622+ # Create a port group on the vSwitch associated with the vlan_interface
623+ # corresponding physical network adapter on the ESX host
624+ network_utils.create_port_group(session, bridge, vswitch_associated,
625+ vlan_num)
626+ else:
627+ # Get the vlan id and vswitch corresponding to the port group
628+ pg_vlanid, pg_vswitch = \
629+ network_utils.get_vlanid_and_vswitch_for_portgroup(session, bridge)
630+
631+ # Check if the vsiwtch associated is proper
632+ if pg_vswitch != vswitch_associated:
633+ raise exception.Invalid(_("vSwitch which contains the port group "
634+ "%(bridge)s is not associated with the desired "
635+ "physical adapter. Expected vSwitch is "
636+ "%(vswitch_associated)s, but the one associated"
637+ " is %(pg_vswitch)s") % locals())
638+
639+ # Check if the vlan id is proper for the port group
640+ if pg_vlanid != vlan_num:
641+ raise exception.Invalid(_("VLAN tag is not appropriate for the "
642+ "port group %(bridge)s. Expected VLAN tag is "
643+ "%(vlan_num)s, but the one associated with the "
644+ "port group is %(pg_vlanid)s") % locals())
645
646=== added file 'nova/tests/test_vmwareapi.py'
647--- nova/tests/test_vmwareapi.py 1970-01-01 00:00:00 +0000
648+++ nova/tests/test_vmwareapi.py 2011-03-24 16:38:31 +0000
649@@ -0,0 +1,252 @@
650+# vim: tabstop=4 shiftwidth=4 softtabstop=4
651+
652+# Copyright (c) 2011 Citrix Systems, Inc.
653+# Copyright 2011 OpenStack LLC.
654+#
655+# Licensed under the Apache License, Version 2.0 (the "License"); you may
656+# not use this file except in compliance with the License. You may obtain
657+# a copy of the License at
658+#
659+# http://www.apache.org/licenses/LICENSE-2.0
660+#
661+# Unless required by applicable law or agreed to in writing, software
662+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
663+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
664+# License for the specific language governing permissions and limitations
665+# under the License.
666+
667+"""
668+Test suite for VMWareAPI.
669+"""
670+
671+import stubout
672+
673+from nova import context
674+from nova import db
675+from nova import flags
676+from nova import test
677+from nova import utils
678+from nova.auth import manager
679+from nova.compute import power_state
680+from nova.tests.glance import stubs as glance_stubs
681+from nova.tests.vmwareapi import db_fakes
682+from nova.tests.vmwareapi import stubs
683+from nova.virt import vmwareapi_conn
684+from nova.virt.vmwareapi import fake as vmwareapi_fake
685+
686+
687+FLAGS = flags.FLAGS
688+
689+
690+class VMWareAPIVMTestCase(test.TestCase):
691+ """Unit tests for Vmware API connection calls."""
692+
693+ def setUp(self):
694+ super(VMWareAPIVMTestCase, self).setUp()
695+ self.flags(vmwareapi_host_ip='test_url',
696+ vmwareapi_host_username='test_username',
697+ vmwareapi_host_password='test_pass')
698+ self.manager = manager.AuthManager()
699+ self.user = self.manager.create_user('fake', 'fake', 'fake',
700+ admin=True)
701+ self.project = self.manager.create_project('fake', 'fake', 'fake')
702+ self.network = utils.import_object(FLAGS.network_manager)
703+ self.stubs = stubout.StubOutForTesting()
704+ vmwareapi_fake.reset()
705+ db_fakes.stub_out_db_instance_api(self.stubs)
706+ stubs.set_stubs(self.stubs)
707+ glance_stubs.stubout_glance_client(self.stubs,
708+ glance_stubs.FakeGlance)
709+ self.conn = vmwareapi_conn.get_connection(False)
710+
711+ def _create_instance_in_the_db(self):
712+ values = {'name': 1,
713+ 'id': 1,
714+ 'project_id': self.project.id,
715+ 'user_id': self.user.id,
716+ 'image_id': "1",
717+ 'kernel_id': "1",
718+ 'ramdisk_id': "1",
719+ 'instance_type': 'm1.large',
720+ 'mac_address': 'aa:bb:cc:dd:ee:ff',
721+ }
722+ self.instance = db.instance_create(values)
723+
724+ def _create_vm(self):
725+ """Create and spawn the VM."""
726+ self._create_instance_in_the_db()
727+ self.type_data = db.instance_type_get_by_name(None, 'm1.large')
728+ self.conn.spawn(self.instance)
729+ self._check_vm_record()
730+
731+ def _check_vm_record(self):
732+ """
733+ Check if the spawned VM's properties correspond to the instance in
734+ the db.
735+ """
736+ instances = self.conn.list_instances()
737+ self.assertEquals(len(instances), 1)
738+
739+ # Get Nova record for VM
740+ vm_info = self.conn.get_info(1)
741+
742+ # Get record for VM
743+ vms = vmwareapi_fake._get_objects("VirtualMachine")
744+ vm = vms[0]
745+
746+ # Check that m1.large above turned into the right thing.
747+ mem_kib = long(self.type_data['memory_mb']) << 10
748+ vcpus = self.type_data['vcpus']
749+ self.assertEquals(vm_info['max_mem'], mem_kib)
750+ self.assertEquals(vm_info['mem'], mem_kib)
751+ self.assertEquals(vm.get("summary.config.numCpu"), vcpus)
752+ self.assertEquals(vm.get("summary.config.memorySizeMB"),
753+ self.type_data['memory_mb'])
754+
755+ # Check that the VM is running according to Nova
756+ self.assertEquals(vm_info['state'], power_state.RUNNING)
757+
758+ # Check that the VM is running according to vSphere API.
759+ self.assertEquals(vm.get("runtime.powerState"), 'poweredOn')
760+
761+ def _check_vm_info(self, info, pwr_state=power_state.RUNNING):
762+ """
763+ Check if the get_info returned values correspond to the instance
764+ object in the db.
765+ """
766+ mem_kib = long(self.type_data['memory_mb']) << 10
767+ self.assertEquals(info["state"], pwr_state)
768+ self.assertEquals(info["max_mem"], mem_kib)
769+ self.assertEquals(info["mem"], mem_kib)
770+ self.assertEquals(info["num_cpu"], self.type_data['vcpus'])
771+
772+ def test_list_instances(self):
773+ instances = self.conn.list_instances()
774+ self.assertEquals(len(instances), 0)
775+
776+ def test_list_instances_1(self):
777+ self._create_vm()
778+ instances = self.conn.list_instances()
779+ self.assertEquals(len(instances), 1)
780+
781+ def test_spawn(self):
782+ self._create_vm()
783+ info = self.conn.get_info(1)
784+ self._check_vm_info(info, power_state.RUNNING)
785+
786+ def test_snapshot(self):
787+ self._create_vm()
788+ info = self.conn.get_info(1)
789+ self._check_vm_info(info, power_state.RUNNING)
790+ self.conn.snapshot(self.instance, "Test-Snapshot")
791+ info = self.conn.get_info(1)
792+ self._check_vm_info(info, power_state.RUNNING)
793+
794+ def test_snapshot_non_existent(self):
795+ self._create_instance_in_the_db()
796+ self.assertRaises(Exception, self.conn.snapshot, self.instance,
797+ "Test-Snapshot")
798+
799+ def test_reboot(self):
800+ self._create_vm()
801+ info = self.conn.get_info(1)
802+ self._check_vm_info(info, power_state.RUNNING)
803+ self.conn.reboot(self.instance)
804+ info = self.conn.get_info(1)
805+ self._check_vm_info(info, power_state.RUNNING)
806+
807+ def test_reboot_non_existent(self):
808+ self._create_instance_in_the_db()
809+ self.assertRaises(Exception, self.conn.reboot, self.instance)
810+
811+ def test_reboot_not_poweredon(self):
812+ self._create_vm()
813+ info = self.conn.get_info(1)
814+ self._check_vm_info(info, power_state.RUNNING)
815+ self.conn.suspend(self.instance, self.dummy_callback_handler)
816+ info = self.conn.get_info(1)
817+ self._check_vm_info(info, power_state.PAUSED)
818+ self.assertRaises(Exception, self.conn.reboot, self.instance)
819+
820+ def test_suspend(self):
821+ self._create_vm()
822+ info = self.conn.get_info(1)
823+ self._check_vm_info(info, power_state.RUNNING)
824+ self.conn.suspend(self.instance, self.dummy_callback_handler)
825+ info = self.conn.get_info(1)
826+ self._check_vm_info(info, power_state.PAUSED)
827+
828+ def test_suspend_non_existent(self):
829+ self._create_instance_in_the_db()
830+ self.assertRaises(Exception, self.conn.suspend, self.instance,
831+ self.dummy_callback_handler)
832+
833+ def test_resume(self):
834+ self._create_vm()
835+ info = self.conn.get_info(1)
836+ self._check_vm_info(info, power_state.RUNNING)
837+ self.conn.suspend(self.instance, self.dummy_callback_handler)
838+ info = self.conn.get_info(1)
839+ self._check_vm_info(info, power_state.PAUSED)
840+ self.conn.resume(self.instance, self.dummy_callback_handler)
841+ info = self.conn.get_info(1)
842+ self._check_vm_info(info, power_state.RUNNING)
843+
844+ def test_resume_non_existent(self):
845+ self._create_instance_in_the_db()
846+ self.assertRaises(Exception, self.conn.resume, self.instance,
847+ self.dummy_callback_handler)
848+
849+ def test_resume_not_suspended(self):
850+ self._create_vm()
851+ info = self.conn.get_info(1)
852+ self._check_vm_info(info, power_state.RUNNING)
853+ self.assertRaises(Exception, self.conn.resume, self.instance,
854+ self.dummy_callback_handler)
855+
856+ def test_get_info(self):
857+ self._create_vm()
858+ info = self.conn.get_info(1)
859+ self._check_vm_info(info, power_state.RUNNING)
860+
861+ def test_destroy(self):
862+ self._create_vm()
863+ info = self.conn.get_info(1)
864+ self._check_vm_info(info, power_state.RUNNING)
865+ instances = self.conn.list_instances()
866+ self.assertEquals(len(instances), 1)
867+ self.conn.destroy(self.instance)
868+ instances = self.conn.list_instances()
869+ self.assertEquals(len(instances), 0)
870+
871+ def test_destroy_non_existent(self):
872+ self._create_instance_in_the_db()
873+ self.assertEquals(self.conn.destroy(self.instance), None)
874+
875+ def test_pause(self):
876+ pass
877+
878+ def test_unpause(self):
879+ pass
880+
881+ def test_diagnostics(self):
882+ pass
883+
884+ def test_get_console_output(self):
885+ pass
886+
887+ def test_get_ajax_console(self):
888+ pass
889+
890+ def dummy_callback_handler(self, ret):
891+ """
892+ Dummy callback function to be passed to suspend, resume, etc., calls.
893+ """
894+ pass
895+
896+ def tearDown(self):
897+ super(VMWareAPIVMTestCase, self).tearDown()
898+ vmwareapi_fake.cleanup()
899+ self.manager.delete_project(self.project)
900+ self.manager.delete_user(self.user)
901+ self.stubs.UnsetAll()
902
903=== added directory 'nova/tests/vmwareapi'
904=== added file 'nova/tests/vmwareapi/__init__.py'
905--- nova/tests/vmwareapi/__init__.py 1970-01-01 00:00:00 +0000
906+++ nova/tests/vmwareapi/__init__.py 2011-03-24 16:38:31 +0000
907@@ -0,0 +1,21 @@
908+# vim: tabstop=4 shiftwidth=4 softtabstop=4
909+
910+# Copyright (c) 2011 Citrix Systems, Inc.
911+# Copyright 2011 OpenStack LLC.
912+#
913+# Licensed under the Apache License, Version 2.0 (the "License"); you may
914+# not use this file except in compliance with the License. You may obtain
915+# a copy of the License at
916+#
917+# http://www.apache.org/licenses/LICENSE-2.0
918+#
919+# Unless required by applicable law or agreed to in writing, software
920+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
921+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
922+# License for the specific language governing permissions and limitations
923+# under the License.
924+
925+"""
926+:mod:`vmwareapi` -- Stubs for VMware API
927+=======================================
928+"""
929
930=== added file 'nova/tests/vmwareapi/db_fakes.py'
931--- nova/tests/vmwareapi/db_fakes.py 1970-01-01 00:00:00 +0000
932+++ nova/tests/vmwareapi/db_fakes.py 2011-03-24 16:38:31 +0000
933@@ -0,0 +1,109 @@
934+# vim: tabstop=4 shiftwidth=4 softtabstop=4
935+
936+# Copyright (c) 2011 Citrix Systems, Inc.
937+# Copyright 2011 OpenStack LLC.
938+#
939+# Licensed under the Apache License, Version 2.0 (the "License"); you may
940+# not use this file except in compliance with the License. You may obtain
941+# a copy of the License at
942+#
943+# http://www.apache.org/licenses/LICENSE-2.0
944+#
945+# Unless required by applicable law or agreed to in writing, software
946+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
947+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
948+# License for the specific language governing permissions and limitations
949+# under the License.
950+
951+"""
952+Stubouts, mocks and fixtures for the test suite
953+"""
954+
955+import time
956+
957+from nova import db
958+from nova import utils
959+
960+
961+def stub_out_db_instance_api(stubs):
962+ """Stubs out the db API for creating Instances."""
963+
964+ INSTANCE_TYPES = {
965+ 'm1.tiny': dict(memory_mb=512, vcpus=1, local_gb=0, flavorid=1),
966+ 'm1.small': dict(memory_mb=2048, vcpus=1, local_gb=20, flavorid=2),
967+ 'm1.medium':
968+ dict(memory_mb=4096, vcpus=2, local_gb=40, flavorid=3),
969+ 'm1.large': dict(memory_mb=8192, vcpus=4, local_gb=80, flavorid=4),
970+ 'm1.xlarge':
971+ dict(memory_mb=16384, vcpus=8, local_gb=160, flavorid=5)}
972+
973+ class FakeModel(object):
974+ """Stubs out for model."""
975+
976+ def __init__(self, values):
977+ self.values = values
978+
979+ def __getattr__(self, name):
980+ return self.values[name]
981+
982+ def __getitem__(self, key):
983+ if key in self.values:
984+ return self.values[key]
985+ else:
986+ raise NotImplementedError()
987+
988+ def fake_instance_create(values):
989+ """Stubs out the db.instance_create method."""
990+
991+ type_data = INSTANCE_TYPES[values['instance_type']]
992+
993+ base_options = {
994+ 'name': values['name'],
995+ 'id': values['id'],
996+ 'reservation_id': utils.generate_uid('r'),
997+ 'image_id': values['image_id'],
998+ 'kernel_id': values['kernel_id'],
999+ 'ramdisk_id': values['ramdisk_id'],
1000+ 'state_description': 'scheduling',
1001+ 'user_id': values['user_id'],
1002+ 'project_id': values['project_id'],
1003+ 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
1004+ 'instance_type': values['instance_type'],
1005+ 'memory_mb': type_data['memory_mb'],
1006+ 'mac_address': values['mac_address'],
1007+ 'vcpus': type_data['vcpus'],
1008+ 'local_gb': type_data['local_gb'],
1009+ }
1010+ return FakeModel(base_options)
1011+
1012+ def fake_network_get_by_instance(context, instance_id):
1013+ """Stubs out the db.network_get_by_instance method."""
1014+
1015+ fields = {
1016+ 'bridge': 'vmnet0',
1017+ 'netmask': '255.255.255.0',
1018+ 'gateway': '10.10.10.1',
1019+ 'vlan': 100}
1020+ return FakeModel(fields)
1021+
1022+ def fake_instance_action_create(context, action):
1023+ """Stubs out the db.instance_action_create method."""
1024+ pass
1025+
1026+ def fake_instance_get_fixed_address(context, instance_id):
1027+ """Stubs out the db.instance_get_fixed_address method."""
1028+ return '10.10.10.10'
1029+
1030+ def fake_instance_type_get_all(context, inactive=0):
1031+ return INSTANCE_TYPES
1032+
1033+ def fake_instance_type_get_by_name(context, name):
1034+ return INSTANCE_TYPES[name]
1035+
1036+ stubs.Set(db, 'instance_create', fake_instance_create)
1037+ stubs.Set(db, 'network_get_by_instance', fake_network_get_by_instance)
1038+ stubs.Set(db, 'instance_action_create', fake_instance_action_create)
1039+ stubs.Set(db, 'instance_get_fixed_address',
1040+ fake_instance_get_fixed_address)
1041+ stubs.Set(db, 'instance_type_get_all', fake_instance_type_get_all)
1042+ stubs.Set(db, 'instance_type_get_by_name', fake_instance_type_get_by_name)
1043
1044=== added file 'nova/tests/vmwareapi/stubs.py'
1045--- nova/tests/vmwareapi/stubs.py 1970-01-01 00:00:00 +0000
1046+++ nova/tests/vmwareapi/stubs.py 2011-03-24 16:38:31 +0000
1047@@ -0,0 +1,46 @@
1048+# vim: tabstop=4 shiftwidth=4 softtabstop=4
1049+
1050+# Copyright (c) 2011 Citrix Systems, Inc.
1051+# Copyright 2011 OpenStack LLC.
1052+#
1053+# Licensed under the Apache License, Version 2.0 (the "License"); you may
1054+# not use this file except in compliance with the License. You may obtain
1055+# a copy of the License at
1056+#
1057+# http://www.apache.org/licenses/LICENSE-2.0
1058+#
1059+# Unless required by applicable law or agreed to in writing, software
1060+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1061+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1062+# License for the specific language governing permissions and limitations
1063+# under the License.
1064+
1065+"""
1066+Stubouts for the test suite
1067+"""
1068+
1069+from nova.virt import vmwareapi_conn
1070+from nova.virt.vmwareapi import fake
1071+from nova.virt.vmwareapi import vmware_images
1072+
1073+
1074+def fake_get_vim_object(arg):
1075+ """Stubs out the VMWareAPISession's get_vim_object method."""
1076+ return fake.FakeVim()
1077+
1078+
1079+def fake_is_vim_object(arg, module):
1080+ """Stubs out the VMWareAPISession's is_vim_object method."""
1081+ return isinstance(module, fake.FakeVim)
1082+
1083+
1084+def set_stubs(stubs):
1085+ """Set the stubs."""
1086+ stubs.Set(vmware_images, 'fetch_image', fake.fake_fetch_image)
1087+ stubs.Set(vmware_images, 'get_vmdk_size_and_properties',
1088+ fake.fake_get_vmdk_size_and_properties)
1089+ stubs.Set(vmware_images, 'upload_image', fake.fake_upload_image)
1090+ stubs.Set(vmwareapi_conn.VMWareAPISession, "_get_vim_object",
1091+ fake_get_vim_object)
1092+ stubs.Set(vmwareapi_conn.VMWareAPISession, "_is_vim_object",
1093+ fake_is_vim_object)
1094
1095=== modified file 'nova/virt/connection.py'
1096--- nova/virt/connection.py 2011-03-23 23:49:50 +0000
1097+++ nova/virt/connection.py 2011-03-24 16:38:31 +0000
1098@@ -26,9 +26,10 @@
1099 from nova import utils
1100 from nova.virt import driver
1101 from nova.virt import fake
1102+from nova.virt import hyperv
1103 from nova.virt import libvirt_conn
1104+from nova.virt import vmwareapi_conn
1105 from nova.virt import xenapi_conn
1106-from nova.virt import hyperv
1107
1108
1109 LOG = logging.getLogger("nova.virt.connection")
1110@@ -68,6 +69,8 @@
1111 conn = xenapi_conn.get_connection(read_only)
1112 elif t == 'hyperv':
1113 conn = hyperv.get_connection(read_only)
1114+ elif t == 'vmwareapi':
1115+ conn = vmwareapi_conn.get_connection(read_only)
1116 else:
1117 raise Exception('Unknown connection type "%s"' % t)
1118
1119
1120=== added directory 'nova/virt/vmwareapi'
1121=== added file 'nova/virt/vmwareapi/__init__.py'
1122--- nova/virt/vmwareapi/__init__.py 1970-01-01 00:00:00 +0000
1123+++ nova/virt/vmwareapi/__init__.py 2011-03-24 16:38:31 +0000
1124@@ -0,0 +1,19 @@
1125+# vim: tabstop=4 shiftwidth=4 softtabstop=4
1126+
1127+# Copyright (c) 2011 Citrix Systems, Inc.
1128+# Copyright 2011 OpenStack LLC.
1129+#
1130+# Licensed under the Apache License, Version 2.0 (the "License"); you may
1131+# not use this file except in compliance with the License. You may obtain
1132+# a copy of the License at
1133+#
1134+# http://www.apache.org/licenses/LICENSE-2.0
1135+#
1136+# Unless required by applicable law or agreed to in writing, software
1137+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1138+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1139+# License for the specific language governing permissions and limitations
1140+# under the License.
1141+"""
1142+:mod:`vmwareapi` -- Nova support for VMware ESX/ESXi Server through VMware API.
1143+"""
1144
1145=== added file 'nova/virt/vmwareapi/error_util.py'
1146--- nova/virt/vmwareapi/error_util.py 1970-01-01 00:00:00 +0000
1147+++ nova/virt/vmwareapi/error_util.py 2011-03-24 16:38:31 +0000
1148@@ -0,0 +1,96 @@
1149+# vim: tabstop=4 shiftwidth=4 softtabstop=4
1150+
1151+# Copyright (c) 2011 Citrix Systems, Inc.
1152+# Copyright 2011 OpenStack LLC.
1153+#
1154+# Licensed under the Apache License, Version 2.0 (the "License"); you may
1155+# not use this file except in compliance with the License. You may obtain
1156+# a copy of the License at
1157+#
1158+# http://www.apache.org/licenses/LICENSE-2.0
1159+#
1160+# Unless required by applicable law or agreed to in writing, software
1161+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1162+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1163+# License for the specific language governing permissions and limitations
1164+# under the License.
1165+
1166+"""
1167+Exception classes and SOAP response error checking module.
1168+"""
1169+
1170+FAULT_NOT_AUTHENTICATED = "NotAuthenticated"
1171+FAULT_ALREADY_EXISTS = "AlreadyExists"
1172+
1173+
1174+class VimException(Exception):
1175+ """The VIM Exception class."""
1176+
1177+ def __init__(self, exception_summary, excep):
1178+ Exception.__init__(self)
1179+ self.exception_summary = exception_summary
1180+ self.exception_obj = excep
1181+
1182+ def __str__(self):
1183+ return self.exception_summary + str(self.exception_obj)
1184+
1185+
1186+class SessionOverLoadException(VimException):
1187+ """Session Overload Exception."""
1188+ pass
1189+
1190+
1191+class VimAttributeError(VimException):
1192+ """VI Attribute Error."""
1193+ pass
1194+
1195+
1196+class VimFaultException(Exception):
1197+ """The VIM Fault exception class."""
1198+
1199+ def __init__(self, fault_list, excep):
1200+ Exception.__init__(self)
1201+ self.fault_list = fault_list
1202+ self.exception_obj = excep
1203+
1204+ def __str__(self):
1205+ return str(self.exception_obj)
1206+
1207+
1208+class FaultCheckers(object):
1209+ """
1210+ Methods for fault checking of SOAP response. Per Method error handlers
1211+ for which we desire error checking are defined. SOAP faults are
1212+ embedded in the SOAP messages as properties and not as SOAP faults.
1213+ """
1214+
1215+ @staticmethod
1216+ def retrieveproperties_fault_checker(resp_obj):
1217+ """
1218+ Checks the RetrieveProperties response for errors. Certain faults
1219+ are sent as part of the SOAP body as property of missingSet.
1220+ For example NotAuthenticated fault.
1221+ """
1222+ fault_list = []
1223+ if not resp_obj:
1224+ # This is the case when the session has timed out. ESX SOAP server
1225+ # sends an empty RetrievePropertiesResponse. Normally missingSet in
1226+ # the returnval field has the specifics about the error, but that's
1227+ # not the case with a timed out idle session. It is as bad as a
1228+ # terminated session for we cannot use the session. So setting
1229+ # fault to NotAuthenticated fault.
1230+ fault_list = ["NotAuthenticated"]
1231+ else:
1232+ for obj_cont in resp_obj:
1233+ if hasattr(obj_cont, "missingSet"):
1234+ for missing_elem in obj_cont.missingSet:
1235+ fault_type = \
1236+ missing_elem.fault.fault.__class__.__name__
1237+ # Fault needs to be added to the type of fault for
1238+ # uniformity in error checking as SOAP faults define
1239+ fault_list.append(fault_type)
1240+ if fault_list:
1241+ exc_msg_list = ', '.join(fault_list)
1242+ raise VimFaultException(fault_list, Exception(_("Error(s) %s "
1243+ "occurred in the call to RetrieveProperties") %
1244+ exc_msg_list))
1245
1246=== added file 'nova/virt/vmwareapi/fake.py'
1247--- nova/virt/vmwareapi/fake.py 1970-01-01 00:00:00 +0000
1248+++ nova/virt/vmwareapi/fake.py 2011-03-24 16:38:31 +0000
1249@@ -0,0 +1,711 @@
1250+# vim: tabstop=4 shiftwidth=4 softtabstop=4
1251+
1252+# Copyright (c) 2011 Citrix Systems, Inc.
1253+# Copyright 2011 OpenStack LLC.
1254+#
1255+# Licensed under the Apache License, Version 2.0 (the "License"); you may
1256+# not use this file except in compliance with the License. You may obtain
1257+# a copy of the License at
1258+#
1259+# http://www.apache.org/licenses/LICENSE-2.0
1260+#
1261+# Unless required by applicable law or agreed to in writing, software
1262+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1263+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1264+# License for the specific language governing permissions and limitations
1265+# under the License.
1266+
1267+"""
1268+A fake VMWare VI API implementation.
1269+"""
1270+
1271+from pprint import pformat
1272+import uuid
1273+
1274+from nova import exception
1275+from nova import log as logging
1276+from nova.virt.vmwareapi import vim
1277+from nova.virt.vmwareapi import error_util
1278+
1279+_CLASSES = ['Datacenter', 'Datastore', 'ResourcePool', 'VirtualMachine',
1280+ 'Network', 'HostSystem', 'HostNetworkSystem', 'Task', 'session',
1281+ 'files']
1282+
1283+_FAKE_FILE_SIZE = 1024
1284+
1285+_db_content = {}
1286+
1287+LOG = logging.getLogger("nova.virt.vmwareapi.fake")
1288+
1289+
1290+def log_db_contents(msg=None):
1291+ """Log DB Contents."""
1292+ text = msg or ""
1293+ content = pformat(_db_content)
1294+ LOG.debug(_("%(text)s: _db_content => %(content)s") % locals())
1295+
1296+
1297+def reset():
1298+ """Resets the db contents."""
1299+ for c in _CLASSES:
1300+ # We fake the datastore by keeping the file references as a list of
1301+ # names in the db
1302+ if c == 'files':
1303+ _db_content[c] = []
1304+ else:
1305+ _db_content[c] = {}
1306+ create_network()
1307+ create_host_network_system()
1308+ create_host()
1309+ create_datacenter()
1310+ create_datastore()
1311+ create_res_pool()
1312+
1313+
1314+def cleanup():
1315+ """Clear the db contents."""
1316+ for c in _CLASSES:
1317+ _db_content[c] = {}
1318+
1319+
1320+def _create_object(table, table_obj):
1321+ """Create an object in the db."""
1322+ _db_content[table][table_obj.obj] = table_obj
1323+
1324+
1325+def _get_objects(obj_type):
1326+ """Get objects of the type."""
1327+ lst_objs = []
1328+ for key in _db_content[obj_type]:
1329+ lst_objs.append(_db_content[obj_type][key])
1330+ return lst_objs
1331+
1332+
1333+class Prop(object):
1334+ """Property Object base class."""
1335+
1336+ def __init__(self):
1337+ self.name = None
1338+ self.val = None
1339+
1340+
1341+class ManagedObject(object):
1342+ """Managed Data Object base class."""
1343+
1344+ def __init__(self, name="ManagedObject", obj_ref=None):
1345+ """Sets the obj property which acts as a reference to the object."""
1346+ super(ManagedObject, self).__setattr__('objName', name)
1347+ if obj_ref is None:
1348+ obj_ref = str(uuid.uuid4())
1349+ object.__setattr__(self, 'obj', obj_ref)
1350+ object.__setattr__(self, 'propSet', [])
1351+
1352+ def set(self, attr, val):
1353+ """
1354+ Sets an attribute value. Not using the __setattr__ directly for we
1355+ want to set attributes of the type 'a.b.c' and using this function
1356+ class we set the same.
1357+ """
1358+ self.__setattr__(attr, val)
1359+
1360+ def get(self, attr):
1361+ """
1362+ Gets an attribute. Used as an intermediary to get nested
1363+ property like 'a.b.c' value.
1364+ """
1365+ return self.__getattr__(attr)
1366+
1367+ def __setattr__(self, attr, val):
1368+ for prop in self.propSet:
1369+ if prop.name == attr:
1370+ prop.val = val
1371+ return
1372+ elem = Prop()
1373+ elem.name = attr
1374+ elem.val = val
1375+ self.propSet.append(elem)
1376+
1377+ def __getattr__(self, attr):
1378+ for elem in self.propSet:
1379+ if elem.name == attr:
1380+ return elem.val
1381+ raise exception.Error(_("Property %(attr)s not set for the managed "
1382+ "object %(objName)s") %
1383+ {'attr': attr,
1384+ 'objName': self.objName})
1385+
1386+
1387+class DataObject(object):
1388+ """Data object base class."""
1389+ pass
1390+
1391+
1392+class VirtualDisk(DataObject):
1393+ """
1394+ Virtual Disk class. Does nothing special except setting
1395+ __class__.__name__ to 'VirtualDisk'. Refer place where __class__.__name__
1396+ is used in the code.
1397+ """
1398+ pass
1399+
1400+
1401+class VirtualDiskFlatVer2BackingInfo(DataObject):
1402+ """VirtualDiskFlatVer2BackingInfo class."""
1403+ pass
1404+
1405+
1406+class VirtualLsiLogicController(DataObject):
1407+ """VirtualLsiLogicController class."""
1408+ pass
1409+
1410+
1411+class VirtualMachine(ManagedObject):
1412+ """Virtual Machine class."""
1413+
1414+ def __init__(self, **kwargs):
1415+ super(VirtualMachine, self).__init__("VirtualMachine")
1416+ self.set("name", kwargs.get("name"))
1417+ self.set("runtime.connectionState",
1418+ kwargs.get("conn_state", "connected"))
1419+ self.set("summary.config.guestId", kwargs.get("guest", "otherGuest"))
1420+ ds_do = DataObject()
1421+ ds_do.ManagedObjectReference = [kwargs.get("ds").obj]
1422+ self.set("datastore", ds_do)
1423+ self.set("summary.guest.toolsStatus", kwargs.get("toolsstatus",
1424+ "toolsOk"))
1425+ self.set("summary.guest.toolsRunningStatus", kwargs.get(
1426+ "toolsrunningstate", "guestToolsRunning"))
1427+ self.set("runtime.powerState", kwargs.get("powerstate", "poweredOn"))
1428+ self.set("config.files.vmPathName", kwargs.get("vmPathName"))
1429+ self.set("summary.config.numCpu", kwargs.get("numCpu", 1))
1430+ self.set("summary.config.memorySizeMB", kwargs.get("mem", 1))
1431+ self.set("config.hardware.device", kwargs.get("virtual_disk", None))
1432+ self.set("config.extraConfig", kwargs.get("extra_config", None))
1433+
1434+ def reconfig(self, factory, val):
1435+ """
1436+ Called to reconfigure the VM. Actually customizes the property
1437+ setting of the Virtual Machine object.
1438+ """
1439+ try:
1440+ # Case of Reconfig of VM to attach disk
1441+ controller_key = val.deviceChange[1].device.controllerKey
1442+ filename = val.deviceChange[1].device.backing.fileName
1443+
1444+ disk = VirtualDisk()
1445+ disk.controllerKey = controller_key
1446+
1447+ disk_backing = VirtualDiskFlatVer2BackingInfo()
1448+ disk_backing.fileName = filename
1449+ disk_backing.key = -101
1450+ disk.backing = disk_backing
1451+
1452+ controller = VirtualLsiLogicController()
1453+ controller.key = controller_key
1454+
1455+ self.set("config.hardware.device", [disk, controller])
1456+ except AttributeError:
1457+ # Case of Reconfig of VM to set extra params
1458+ self.set("config.extraConfig", val.extraConfig)
1459+
1460+
1461+class Network(ManagedObject):
1462+ """Network class."""
1463+
1464+ def __init__(self):
1465+ super(Network, self).__init__("Network")
1466+ self.set("summary.name", "vmnet0")
1467+
1468+
1469+class ResourcePool(ManagedObject):
1470+ """Resource Pool class."""
1471+
1472+ def __init__(self):
1473+ super(ResourcePool, self).__init__("ResourcePool")
1474+ self.set("name", "ResPool")
1475+
1476+
1477+class Datastore(ManagedObject):
1478+ """Datastore class."""
1479+
1480+ def __init__(self):
1481+ super(Datastore, self).__init__("Datastore")
1482+ self.set("summary.type", "VMFS")
1483+ self.set("summary.name", "fake-ds")
1484+
1485+
1486+class HostNetworkSystem(ManagedObject):
1487+ """HostNetworkSystem class."""
1488+
1489+ def __init__(self):
1490+ super(HostNetworkSystem, self).__init__("HostNetworkSystem")
1491+ self.set("name", "networkSystem")
1492+
1493+ pnic_do = DataObject()
1494+ pnic_do.device = "vmnic0"
1495+
1496+ net_info_pnic = DataObject()
1497+ net_info_pnic.PhysicalNic = [pnic_do]
1498+
1499+ self.set("networkInfo.pnic", net_info_pnic)
1500+
1501+
1502+class HostSystem(ManagedObject):
1503+ """Host System class."""
1504+
1505+ def __init__(self):
1506+ super(HostSystem, self).__init__("HostSystem")
1507+ self.set("name", "ha-host")
1508+ if _db_content.get("HostNetworkSystem", None) is None:
1509+ create_host_network_system()
1510+ host_net_key = _db_content["HostNetworkSystem"].keys()[0]
1511+ host_net_sys = _db_content["HostNetworkSystem"][host_net_key].obj
1512+ self.set("configManager.networkSystem", host_net_sys)
1513+
1514+ if _db_content.get("Network", None) is None:
1515+ create_network()
1516+ net_ref = _db_content["Network"][_db_content["Network"].keys()[0]].obj
1517+ network_do = DataObject()
1518+ network_do.ManagedObjectReference = [net_ref]
1519+ self.set("network", network_do)
1520+
1521+ vswitch_do = DataObject()
1522+ vswitch_do.pnic = ["vmnic0"]
1523+ vswitch_do.name = "vSwitch0"
1524+ vswitch_do.portgroup = ["PortGroup-vmnet0"]
1525+
1526+ net_swicth = DataObject()
1527+ net_swicth.HostVirtualSwitch = [vswitch_do]
1528+ self.set("config.network.vswitch", net_swicth)
1529+
1530+ host_pg_do = DataObject()
1531+ host_pg_do.key = "PortGroup-vmnet0"
1532+
1533+ pg_spec = DataObject()
1534+ pg_spec.vlanId = 0
1535+ pg_spec.name = "vmnet0"
1536+
1537+ host_pg_do.spec = pg_spec
1538+
1539+ host_pg = DataObject()
1540+ host_pg.HostPortGroup = [host_pg_do]
1541+ self.set("config.network.portgroup", host_pg)
1542+
1543+ def _add_port_group(self, spec):
1544+ """Adds a port group to the host system object in the db."""
1545+ pg_name = spec.name
1546+ vswitch_name = spec.vswitchName
1547+ vlanid = spec.vlanId
1548+
1549+ vswitch_do = DataObject()
1550+ vswitch_do.pnic = ["vmnic0"]
1551+ vswitch_do.name = vswitch_name
1552+ vswitch_do.portgroup = ["PortGroup-%s" % pg_name]
1553+
1554+ vswitches = self.get("config.network.vswitch").HostVirtualSwitch
1555+ vswitches.append(vswitch_do)
1556+
1557+ host_pg_do = DataObject()
1558+ host_pg_do.key = "PortGroup-%s" % pg_name
1559+
1560+ pg_spec = DataObject()
1561+ pg_spec.vlanId = vlanid
1562+ pg_spec.name = pg_name
1563+
1564+ host_pg_do.spec = pg_spec
1565+ host_pgrps = self.get("config.network.portgroup").HostPortGroup
1566+ host_pgrps.append(host_pg_do)
1567+
1568+
1569+class Datacenter(ManagedObject):
1570+ """Datacenter class."""
1571+
1572+ def __init__(self):
1573+ super(Datacenter, self).__init__("Datacenter")
1574+ self.set("name", "ha-datacenter")
1575+ self.set("vmFolder", "vm_folder_ref")
1576+ if _db_content.get("Network", None) is None:
1577+ create_network()
1578+ net_ref = _db_content["Network"][_db_content["Network"].keys()[0]].obj
1579+ network_do = DataObject()
1580+ network_do.ManagedObjectReference = [net_ref]
1581+ self.set("network", network_do)
1582+
1583+
1584+class Task(ManagedObject):
1585+ """Task class."""
1586+
1587+ def __init__(self, task_name, state="running"):
1588+ super(Task, self).__init__("Task")
1589+ info = DataObject
1590+ info.name = task_name
1591+ info.state = state
1592+ self.set("info", info)
1593+
1594+
1595+def create_host_network_system():
1596+ host_net_system = HostNetworkSystem()
1597+ _create_object("HostNetworkSystem", host_net_system)
1598+
1599+
1600+def create_host():
1601+ host_system = HostSystem()
1602+ _create_object('HostSystem', host_system)
1603+
1604+
1605+def create_datacenter():
1606+ data_center = Datacenter()
1607+ _create_object('Datacenter', data_center)
1608+
1609+
1610+def create_datastore():
1611+ data_store = Datastore()
1612+ _create_object('Datastore', data_store)
1613+
1614+
1615+def create_res_pool():
1616+ res_pool = ResourcePool()
1617+ _create_object('ResourcePool', res_pool)
1618+
1619+
1620+def create_network():
1621+ network = Network()
1622+ _create_object('Network', network)
1623+
1624+
1625+def create_task(task_name, state="running"):
1626+ task = Task(task_name, state)
1627+ _create_object("Task", task)
1628+ return task
1629+
1630+
1631+def _add_file(file_path):
1632+ """Adds a file reference to the db."""
1633+ _db_content["files"].append(file_path)
1634+
1635+
1636+def _remove_file(file_path):
1637+ """Removes a file reference from the db."""
1638+ if _db_content.get("files") is None:
1639+ raise exception.NotFound(_("No files have been added yet"))
1640+ # Check if the remove is for a single file object or for a folder
1641+ if file_path.find(".vmdk") != -1:
1642+ if file_path not in _db_content.get("files"):
1643+ raise exception.NotFound(_("File- '%s' is not there in the "
1644+ "datastore") % file_path)
1645+ _db_content.get("files").remove(file_path)
1646+ else:
1647+ # Removes the files in the folder and the folder too from the db
1648+ for file in _db_content.get("files"):
1649+ if file.find(file_path) != -1:
1650+ lst_files = _db_content.get("files")
1651+ if lst_files and lst_files.count(file):
1652+ lst_files.remove(file)
1653+
1654+
1655+def fake_fetch_image(image, instance, **kwargs):
1656+ """Fakes fetch image call. Just adds a reference to the db for the file."""
1657+ ds_name = kwargs.get("datastore_name")
1658+ file_path = kwargs.get("file_path")
1659+ ds_file_path = "[" + ds_name + "] " + file_path
1660+ _add_file(ds_file_path)
1661+
1662+
1663+def fake_upload_image(image, instance, **kwargs):
1664+ """Fakes the upload of an image."""
1665+ pass
1666+
1667+
1668+def fake_get_vmdk_size_and_properties(image_id, instance):
1669+ """Fakes the file size and properties fetch for the image file."""
1670+ props = {"vmware_ostype": "otherGuest",
1671+ "vmware_adaptertype": "lsiLogic"}
1672+ return _FAKE_FILE_SIZE, props
1673+
1674+
1675+def _get_vm_mdo(vm_ref):
1676+ """Gets the Virtual Machine with the ref from the db."""
1677+ if _db_content.get("VirtualMachine", None) is None:
1678+ raise exception.NotFound(_("There is no VM registered"))
1679+ if vm_ref not in _db_content.get("VirtualMachine"):
1680+ raise exception.NotFound(_("Virtual Machine with ref %s is not "
1681+ "there") % vm_ref)
1682+ return _db_content.get("VirtualMachine")[vm_ref]
1683+
1684+
1685+class FakeFactory(object):
1686+ """Fake factory class for the suds client."""
1687+
1688+ def create(self, obj_name):
1689+ """Creates a namespace object."""
1690+ return DataObject()
1691+
1692+
1693+class FakeVim(object):
1694+ """Fake VIM Class."""
1695+
1696+ def __init__(self, protocol="https", host="localhost", trace=None):
1697+ """
1698+ Initializes the suds client object, sets the service content
1699+ contents and the cookies for the session.
1700+ """
1701+ self._session = None
1702+ self.client = DataObject()
1703+ self.client.factory = FakeFactory()
1704+
1705+ transport = DataObject()
1706+ transport.cookiejar = "Fake-CookieJar"
1707+ options = DataObject()
1708+ options.transport = transport
1709+
1710+ self.client.options = options
1711+
1712+ service_content = self.client.factory.create('ns0:ServiceContent')
1713+ service_content.propertyCollector = "PropCollector"
1714+ service_content.virtualDiskManager = "VirtualDiskManager"
1715+ service_content.fileManager = "FileManager"
1716+ service_content.rootFolder = "RootFolder"
1717+ service_content.sessionManager = "SessionManager"
1718+ self._service_content = service_content
1719+
1720+ def get_service_content(self):
1721+ return self._service_content
1722+
1723+ def __repr__(self):
1724+ return "Fake VIM Object"
1725+
1726+ def __str__(self):
1727+ return "Fake VIM Object"
1728+
1729+ def _login(self):
1730+ """Logs in and sets the session object in the db."""
1731+ self._session = str(uuid.uuid4())
1732+ session = DataObject()
1733+ session.key = self._session
1734+ _db_content['session'][self._session] = session
1735+ return session
1736+
1737+ def _logout(self):
1738+ """Logs out and remove the session object ref from the db."""
1739+ s = self._session
1740+ self._session = None
1741+ if s not in _db_content['session']:
1742+ raise exception.Error(
1743+ _("Logging out a session that is invalid or already logged "
1744+ "out: %s") % s)
1745+ del _db_content['session'][s]
1746+
1747+ def _terminate_session(self, *args, **kwargs):
1748+ """Terminates a session."""
1749+ s = kwargs.get("sessionId")[0]
1750+ if s not in _db_content['session']:
1751+ return
1752+ del _db_content['session'][s]
1753+
1754+ def _check_session(self):
1755+ """Checks if the session is active."""
1756+ if (self._session is None or self._session not in
1757+ _db_content['session']):
1758+ LOG.debug(_("Session is faulty"))
1759+ raise error_util.VimFaultException(
1760+ [error_util.FAULT_NOT_AUTHENTICATED],
1761+ _("Session Invalid"))
1762+
1763+ def _create_vm(self, method, *args, **kwargs):
1764+ """Creates and registers a VM object with the Host System."""
1765+ config_spec = kwargs.get("config")
1766+ ds = _db_content["Datastore"][_db_content["Datastore"].keys()[0]]
1767+ vm_dict = {"name": config_spec.name,
1768+ "ds": ds,
1769+ "powerstate": "poweredOff",
1770+ "vmPathName": config_spec.files.vmPathName,
1771+ "numCpu": config_spec.numCPUs,
1772+ "mem": config_spec.memoryMB}
1773+ virtual_machine = VirtualMachine(**vm_dict)
1774+ _create_object("VirtualMachine", virtual_machine)
1775+ task_mdo = create_task(method, "success")
1776+ return task_mdo.obj
1777+
1778+ def _reconfig_vm(self, method, *args, **kwargs):
1779+ """Reconfigures a VM and sets the properties supplied."""
1780+ vm_ref = args[0]
1781+ vm_mdo = _get_vm_mdo(vm_ref)
1782+ vm_mdo.reconfig(self.client.factory, kwargs.get("spec"))
1783+ task_mdo = create_task(method, "success")
1784+ return task_mdo.obj
1785+
1786+ def _create_copy_disk(self, method, vmdk_file_path):
1787+ """Creates/copies a vmdk file object in the datastore."""
1788+ # We need to add/create both .vmdk and .-flat.vmdk files
1789+ flat_vmdk_file_path = \
1790+ vmdk_file_path.replace(".vmdk", "-flat.vmdk")
1791+ _add_file(vmdk_file_path)
1792+ _add_file(flat_vmdk_file_path)
1793+ task_mdo = create_task(method, "success")
1794+ return task_mdo.obj
1795+
1796+ def _snapshot_vm(self, method):
1797+ """Snapshots a VM. Here we do nothing for faking sake."""
1798+ task_mdo = create_task(method, "success")
1799+ return task_mdo.obj
1800+
1801+ def _delete_disk(self, method, *args, **kwargs):
1802+ """Deletes .vmdk and -flat.vmdk files corresponding to the VM."""
1803+ vmdk_file_path = kwargs.get("name")
1804+ flat_vmdk_file_path = \
1805+ vmdk_file_path.replace(".vmdk", "-flat.vmdk")
1806+ _remove_file(vmdk_file_path)
1807+ _remove_file(flat_vmdk_file_path)
1808+ task_mdo = create_task(method, "success")
1809+ return task_mdo.obj
1810+
1811+ def _delete_file(self, method, *args, **kwargs):
1812+ """Deletes a file from the datastore."""
1813+ _remove_file(kwargs.get("name"))
1814+ task_mdo = create_task(method, "success")
1815+ return task_mdo.obj
1816+
1817+ def _just_return(self):
1818+ """Fakes a return."""
1819+ return
1820+
1821+ def _unregister_vm(self, method, *args, **kwargs):
1822+ """Unregisters a VM from the Host System."""
1823+ vm_ref = args[0]
1824+ _get_vm_mdo(vm_ref)
1825+ del _db_content["VirtualMachine"][vm_ref]
1826+
1827+ def _search_ds(self, method, *args, **kwargs):
1828+ """Searches the datastore for a file."""
1829+ ds_path = kwargs.get("datastorePath")
1830+ if _db_content.get("files", None) is None:
1831+ raise exception.NotFound(_("No files have been added yet"))
1832+ for file in _db_content.get("files"):
1833+ if file.find(ds_path) != -1:
1834+ task_mdo = create_task(method, "success")
1835+ return task_mdo.obj
1836+ task_mdo = create_task(method, "error")
1837+ return task_mdo.obj
1838+
1839+ def _make_dir(self, method, *args, **kwargs):
1840+ """Creates a directory in the datastore."""
1841+ ds_path = kwargs.get("name")
1842+ if _db_content.get("files", None) is None:
1843+ raise exception.NotFound(_("No files have been added yet"))
1844+ _db_content["files"].append(ds_path)
1845+
1846+ def _set_power_state(self, method, vm_ref, pwr_state="poweredOn"):
1847+ """Sets power state for the VM."""
1848+ if _db_content.get("VirtualMachine", None) is None:
1849+ raise exception.NotFound(_(" No Virtual Machine has been "
1850+ "registered yet"))
1851+ if vm_ref not in _db_content.get("VirtualMachine"):
1852+ raise exception.NotFound(_("Virtual Machine with ref %s is not "
1853+ "there") % vm_ref)
1854+ vm_mdo = _db_content.get("VirtualMachine").get(vm_ref)
1855+ vm_mdo.set("runtime.powerState", pwr_state)
1856+ task_mdo = create_task(method, "success")
1857+ return task_mdo.obj
1858+
1859+ def _retrieve_properties(self, method, *args, **kwargs):
1860+ """Retrieves properties based on the type."""
1861+ spec_set = kwargs.get("specSet")[0]
1862+ type = spec_set.propSet[0].type
1863+ properties = spec_set.propSet[0].pathSet
1864+ objs = spec_set.objectSet
1865+ lst_ret_objs = []
1866+ for obj in objs:
1867+ try:
1868+ obj_ref = obj.obj
1869+ # This means that we are doing a search for the managed
1870+ # dataobjects of the type in the inventory
1871+ if obj_ref == "RootFolder":
1872+ for mdo_ref in _db_content[type]:
1873+ mdo = _db_content[type][mdo_ref]
1874+ # Create a temp Managed object which has the same ref
1875+ # as the parent object and copies just the properties
1876+ # asked for. We need .obj along with the propSet of
1877+ # just the properties asked for
1878+ temp_mdo = ManagedObject(mdo.objName, mdo.obj)
1879+ for prop in properties:
1880+ temp_mdo.set(prop, mdo.get(prop))
1881+ lst_ret_objs.append(temp_mdo)
1882+ else:
1883+ if obj_ref in _db_content[type]:
1884+ mdo = _db_content[type][obj_ref]
1885+ temp_mdo = ManagedObject(mdo.objName, obj_ref)
1886+ for prop in properties:
1887+ temp_mdo.set(prop, mdo.get(prop))
1888+ lst_ret_objs.append(temp_mdo)
1889+ except Exception, exc:
1890+ LOG.exception(exc)
1891+ continue
1892+ return lst_ret_objs
1893+
1894+ def _add_port_group(self, method, *args, **kwargs):
1895+ """Adds a port group to the host system."""
1896+ host_mdo = \
1897+ _db_content["HostSystem"][_db_content["HostSystem"].keys()[0]]
1898+ host_mdo._add_port_group(kwargs.get("portgrp"))
1899+
1900+ def __getattr__(self, attr_name):
1901+ if attr_name != "Login":
1902+ self._check_session()
1903+ if attr_name == "Login":
1904+ return lambda *args, **kwargs: self._login()
1905+ elif attr_name == "Logout":
1906+ self._logout()
1907+ elif attr_name == "TerminateSession":
1908+ return lambda *args, **kwargs: self._terminate_session(
1909+ *args, **kwargs)
1910+ elif attr_name == "CreateVM_Task":
1911+ return lambda *args, **kwargs: self._create_vm(attr_name,
1912+ *args, **kwargs)
1913+ elif attr_name == "ReconfigVM_Task":
1914+ return lambda *args, **kwargs: self._reconfig_vm(attr_name,
1915+ *args, **kwargs)
1916+ elif attr_name == "CreateVirtualDisk_Task":
1917+ return lambda *args, **kwargs: self._create_copy_disk(attr_name,
1918+ kwargs.get("name"))
1919+ elif attr_name == "DeleteDatastoreFile_Task":
1920+ return lambda *args, **kwargs: self._delete_file(attr_name,
1921+ *args, **kwargs)
1922+ elif attr_name == "PowerOnVM_Task":
1923+ return lambda *args, **kwargs: self._set_power_state(attr_name,
1924+ args[0], "poweredOn")
1925+ elif attr_name == "PowerOffVM_Task":
1926+ return lambda *args, **kwargs: self._set_power_state(attr_name,
1927+ args[0], "poweredOff")
1928+ elif attr_name == "RebootGuest":
1929+ return lambda *args, **kwargs: self._just_return()
1930+ elif attr_name == "ResetVM_Task":
1931+ return lambda *args, **kwargs: self._set_power_state(attr_name,
1932+ args[0], "poweredOn")
1933+ elif attr_name == "SuspendVM_Task":
1934+ return lambda *args, **kwargs: self._set_power_state(attr_name,
1935+ args[0], "suspended")
1936+ elif attr_name == "CreateSnapshot_Task":
1937+ return lambda *args, **kwargs: self._snapshot_vm(attr_name)
1938+ elif attr_name == "CopyVirtualDisk_Task":
1939+ return lambda *args, **kwargs: self._create_copy_disk(attr_name,
1940+ kwargs.get("destName"))
1941+ elif attr_name == "DeleteVirtualDisk_Task":
1942+ return lambda *args, **kwargs: self._delete_disk(attr_name,
1943+ *args, **kwargs)
1944+ elif attr_name == "UnregisterVM":
1945+ return lambda *args, **kwargs: self._unregister_vm(attr_name,
1946+ *args, **kwargs)
1947+ elif attr_name == "SearchDatastore_Task":
1948+ return lambda *args, **kwargs: self._search_ds(attr_name,
1949+ *args, **kwargs)
1950+ elif attr_name == "MakeDirectory":
1951+ return lambda *args, **kwargs: self._make_dir(attr_name,
1952+ *args, **kwargs)
1953+ elif attr_name == "RetrieveProperties":
1954+ return lambda *args, **kwargs: self._retrieve_properties(
1955+ attr_name, *args, **kwargs)
1956+ elif attr_name == "AcquireCloneTicket":
1957+ return lambda *args, **kwargs: self._just_return()
1958+ elif attr_name == "AddPortGroup":
1959+ return lambda *args, **kwargs: self._add_port_group(attr_name,
1960+ *args, **kwargs)
1961
1962=== added file 'nova/virt/vmwareapi/io_util.py'
1963--- nova/virt/vmwareapi/io_util.py 1970-01-01 00:00:00 +0000
1964+++ nova/virt/vmwareapi/io_util.py 2011-03-24 16:38:31 +0000
1965@@ -0,0 +1,168 @@
1966+# vim: tabstop=4 shiftwidth=4 softtabstop=4
1967+
1968+# Copyright (c) 2011 Citrix Systems, Inc.
1969+# Copyright 2011 OpenStack LLC.
1970+#
1971+# Licensed under the Apache License, Version 2.0 (the "License"); you may
1972+# not use this file except in compliance with the License. You may obtain
1973+# a copy of the License at
1974+#
1975+# http://www.apache.org/licenses/LICENSE-2.0
1976+#
1977+# Unless required by applicable law or agreed to in writing, software
1978+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1979+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1980+# License for the specific language governing permissions and limitations
1981+# under the License.
1982+
1983+"""
1984+Utility classes for defining the time saving transfer of data from the reader
1985+to the write using a LightQueue as a Pipe between the reader and the writer.
1986+"""
1987+
1988+from eventlet import event
1989+from eventlet import greenthread
1990+from eventlet.queue import LightQueue
1991+
1992+from glance import client
1993+
1994+from nova import exception
1995+from nova import log as logging
1996+
1997+LOG = logging.getLogger("nova.virt.vmwareapi.io_util")
1998+
1999+IO_THREAD_SLEEP_TIME = .01
2000+GLANCE_POLL_INTERVAL = 5
2001+
2002+
2003+class ThreadSafePipe(LightQueue):
2004+ """The pipe to hold the data which the reader writes to and the writer
2005+ reads from."""
2006+
2007+ def __init__(self, maxsize, transfer_size):
2008+ LightQueue.__init__(self, maxsize)
2009+ self.transfer_size = transfer_size
2010+ self.transferred = 0
2011+
2012+ def read(self, chunk_size):
2013+ """Read data from the pipe. Chunksize if ignored for we have ensured
2014+ that the data chunks written to the pipe by readers is the same as the
2015+ chunks asked for by the Writer."""
2016+ if self.transferred < self.transfer_size:
2017+ data_item = self.get()
2018+ self.transferred += len(data_item)
2019+ return data_item
2020+ else:
2021+ return ""
2022+
2023+ def write(self, data):
2024+ """Put a data item in the pipe."""
2025+ self.put(data)
2026+
2027+ def close(self):
2028+ """A place-holder to maintain consistency."""
2029+ pass
2030+
2031+
2032+class GlanceWriteThread(object):
2033+ """Ensures that image data is written to in the glance client and that
2034+ it is in correct ('active')state."""
2035+
2036+ def __init__(self, input, glance_client, image_id, image_meta={}):
2037+ self.input = input
2038+ self.glance_client = glance_client
2039+ self.image_id = image_id
2040+ self.image_meta = image_meta
2041+ self._running = False
2042+
2043+ def start(self):
2044+ self.done = event.Event()
2045+
2046+ def _inner():
2047+ """Function to do the image data transfer through an update
2048+ and thereon checks if the state is 'active'."""
2049+ self.glance_client.update_image(self.image_id,
2050+ image_meta=self.image_meta,
2051+ image_data=self.input)
2052+ self._running = True
2053+ while self._running:
2054+ try:
2055+ image_status = \
2056+ self.glance_client.get_image_meta(self.image_id).get(
2057+ "status")
2058+ if image_status == "active":
2059+ self.stop()
2060+ self.done.send(True)
2061+ # If the state is killed, then raise an exception.
2062+ elif image_status == "killed":
2063+ self.stop()
2064+ exc_msg = _("Glance image %s is in killed state") %\
2065+ self.image_id
2066+ LOG.exception(exc_msg)
2067+ self.done.send_exception(exception.Error(exc_msg))
2068+ elif image_status in ["saving", "queued"]:
2069+ greenthread.sleep(GLANCE_POLL_INTERVAL)
2070+ else:
2071+ self.stop()
2072+ exc_msg = _("Glance image "
2073+ "%(image_id)s is in unknown state "
2074+ "- %(state)s") % {
2075+ "image_id": self.image_id,
2076+ "state": image_status}
2077+ LOG.exception(exc_msg)
2078+ self.done.send_exception(exception.Error(exc_msg))
2079+ except Exception, exc:
2080+ self.stop()
2081+ self.done.send_exception(exc)
2082+
2083+ greenthread.spawn(_inner)
2084+ return self.done
2085+
2086+ def stop(self):
2087+ self._running = False
2088+
2089+ def wait(self):
2090+ return self.done.wait()
2091+
2092+ def close(self):
2093+ pass
2094+
2095+
2096+class IOThread(object):
2097+ """Class that reads chunks from the input file and writes them to the
2098+ output file till the transfer is completely done."""
2099+
2100+ def __init__(self, input, output):
2101+ self.input = input
2102+ self.output = output
2103+ self._running = False
2104+ self.got_exception = False
2105+
2106+ def start(self):
2107+ self.done = event.Event()
2108+
2109+ def _inner():
2110+ """Read data from the input and write the same to the output
2111+ until the transfer completes."""
2112+ self._running = True
2113+ while self._running:
2114+ try:
2115+ data = self.input.read(None)
2116+ if not data:
2117+ self.stop()
2118+ self.done.send(True)
2119+ self.output.write(data)
2120+ greenthread.sleep(IO_THREAD_SLEEP_TIME)
2121+ except Exception, exc:
2122+ self.stop()
2123+ LOG.exception(exc)
2124+ self.done.send_exception(exc)
2125+
2126+ greenthread.spawn(_inner)
2127+ return self.done
2128+
2129+ def stop(self):
2130+ self._running = False
2131+
2132+ def wait(self):
2133+ return self.done.wait()
2134
2135=== added file 'nova/virt/vmwareapi/network_utils.py'
2136--- nova/virt/vmwareapi/network_utils.py 1970-01-01 00:00:00 +0000
2137+++ nova/virt/vmwareapi/network_utils.py 2011-03-24 16:38:31 +0000
2138@@ -0,0 +1,149 @@
2139+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2140+
2141+# Copyright (c) 2011 Citrix Systems, Inc.
2142+# Copyright 2011 OpenStack LLC.
2143+#
2144+# Licensed under the Apache License, Version 2.0 (the "License"); you may
2145+# not use this file except in compliance with the License. You may obtain
2146+# a copy of the License at
2147+#
2148+# http://www.apache.org/licenses/LICENSE-2.0
2149+#
2150+# Unless required by applicable law or agreed to in writing, software
2151+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
2152+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
2153+# License for the specific language governing permissions and limitations
2154+# under the License.
2155+
2156+"""
2157+Utility functions for ESX Networking.
2158+"""
2159+
2160+from nova import exception
2161+from nova import log as logging
2162+from nova.virt.vmwareapi import error_util
2163+from nova.virt.vmwareapi import vim_util
2164+from nova.virt.vmwareapi import vm_util
2165+
2166+LOG = logging.getLogger("nova.virt.vmwareapi.network_utils")
2167+
2168+
2169+def get_network_with_the_name(session, network_name="vmnet0"):
2170+ """
2171+ Gets reference to the network whose name is passed as the
2172+ argument.
2173+ """
2174+ hostsystems = session._call_method(vim_util, "get_objects",
2175+ "HostSystem", ["network"])
2176+ vm_networks_ret = hostsystems[0].propSet[0].val
2177+ # Meaning there are no networks on the host. suds responds with a ""
2178+ # in the parent property field rather than a [] in the
2179+ # ManagedObjectRefernce property field of the parent
2180+ if not vm_networks_ret:
2181+ return None
2182+ vm_networks = vm_networks_ret.ManagedObjectReference
2183+ networks = session._call_method(vim_util,
2184+ "get_properties_for_a_collection_of_objects",
2185+ "Network", vm_networks, ["summary.name"])
2186+ for network in networks:
2187+ if network.propSet[0].val == network_name:
2188+ return network.obj
2189+ return None
2190+
2191+
2192+def get_vswitch_for_vlan_interface(session, vlan_interface):
2193+ """
2194+ Gets the vswitch associated with the physical network adapter
2195+ with the name supplied.
2196+ """
2197+ # Get the list of vSwicthes on the Host System
2198+ host_mor = session._call_method(vim_util, "get_objects",
2199+ "HostSystem")[0].obj
2200+ vswitches_ret = session._call_method(vim_util,
2201+ "get_dynamic_property", host_mor,
2202+ "HostSystem", "config.network.vswitch")
2203+ # Meaning there are no vSwitches on the host. Shouldn't be the case,
2204+ # but just doing code check
2205+ if not vswitches_ret:
2206+ return
2207+ vswitches = vswitches_ret.HostVirtualSwitch
2208+ # Get the vSwitch associated with the network adapter
2209+ for elem in vswitches:
2210+ try:
2211+ for nic_elem in elem.pnic:
2212+ if str(nic_elem).split('-')[-1].find(vlan_interface) != -1:
2213+ return elem.name
2214+ # Catching Attribute error as a vSwitch may not be associated with a
2215+ # physical NIC.
2216+ except AttributeError:
2217+ pass
2218+
2219+
2220+def check_if_vlan_interface_exists(session, vlan_interface):
2221+ """Checks if the vlan_inteface exists on the esx host."""
2222+ host_net_system_mor = session._call_method(vim_util, "get_objects",
2223+ "HostSystem", ["configManager.networkSystem"])[0].propSet[0].val
2224+ physical_nics_ret = session._call_method(vim_util,
2225+ "get_dynamic_property", host_net_system_mor,
2226+ "HostNetworkSystem", "networkInfo.pnic")
2227+ # Meaning there are no physical nics on the host
2228+ if not physical_nics_ret:
2229+ return False
2230+ physical_nics = physical_nics_ret.PhysicalNic
2231+ for pnic in physical_nics:
2232+ if vlan_interface == pnic.device:
2233+ return True
2234+ return False
2235+
2236+
2237+def get_vlanid_and_vswitch_for_portgroup(session, pg_name):
2238+ """Get the vlan id and vswicth associated with the port group."""
2239+ host_mor = session._call_method(vim_util, "get_objects",
2240+ "HostSystem")[0].obj
2241+ port_grps_on_host_ret = session._call_method(vim_util,
2242+ "get_dynamic_property", host_mor,
2243+ "HostSystem", "config.network.portgroup")
2244+ if not port_grps_on_host_ret:
2245+ excep = ("ESX SOAP server returned an empty port group "
2246+ "for the host system in its response")
2247+ LOG.exception(excep)
2248+ raise exception.Error(_(excep))
2249+ port_grps_on_host = port_grps_on_host_ret.HostPortGroup
2250+ for p_gp in port_grps_on_host:
2251+ if p_gp.spec.name == pg_name:
2252+ p_grp_vswitch_name = p_gp.vswitch.split("-")[-1]
2253+ return p_gp.spec.vlanId, p_grp_vswitch_name
2254+
2255+
2256+def create_port_group(session, pg_name, vswitch_name, vlan_id=0):
2257+ """
2258+ Creates a port group on the host system with the vlan tags
2259+ supplied. VLAN id 0 means no vlan id association.
2260+ """
2261+ client_factory = session._get_vim().client.factory
2262+ add_prt_grp_spec = vm_util.get_add_vswitch_port_group_spec(
2263+ client_factory,
2264+ vswitch_name,
2265+ pg_name,
2266+ vlan_id)
2267+ host_mor = session._call_method(vim_util, "get_objects",
2268+ "HostSystem")[0].obj
2269+ network_system_mor = session._call_method(vim_util,
2270+ "get_dynamic_property", host_mor,
2271+ "HostSystem", "configManager.networkSystem")
2272+ LOG.debug(_("Creating Port Group with name %s on "
2273+ "the ESX host") % pg_name)
2274+ try:
2275+ session._call_method(session._get_vim(),
2276+ "AddPortGroup", network_system_mor,
2277+ portgrp=add_prt_grp_spec)
2278+ except error_util.VimFaultException, exc:
2279+ # There can be a race condition when two instances try
2280+ # adding port groups at the same time. One succeeds, then
2281+ # the other one will get an exception. Since we are
2282+ # concerned with the port group being created, which is done
2283+ # by the other call, we can ignore the exception.
2284+ if error_util.FAULT_ALREADY_EXISTS not in exc.fault_list:
2285+ raise exception.Error(exc)
2286+ LOG.debug(_("Created Port Group with name %s on "
2287+ "the ESX host") % pg_name)
2288
2289=== added file 'nova/virt/vmwareapi/read_write_util.py'
2290--- nova/virt/vmwareapi/read_write_util.py 1970-01-01 00:00:00 +0000
2291+++ nova/virt/vmwareapi/read_write_util.py 2011-03-24 16:38:31 +0000
2292@@ -0,0 +1,182 @@
2293+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2294+
2295+# Copyright (c) 2011 Citrix Systems, Inc.
2296+# Copyright 2011 OpenStack LLC.
2297+#
2298+# Licensed under the Apache License, Version 2.0 (the "License"); you may
2299+# not use this file except in compliance with the License. You may obtain
2300+# a copy of the License at
2301+#
2302+# http://www.apache.org/licenses/LICENSE-2.0
2303+#
2304+# Unless required by applicable law or agreed to in writing, software
2305+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
2306+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
2307+# License for the specific language governing permissions and limitations
2308+# under the License.
2309+
2310+"""Classes to handle image files
2311+
2312+Collection of classes to handle image upload/download to/from Image service
2313+(like Glance image storage and retrieval service) from/to ESX/ESXi server.
2314+
2315+"""
2316+
2317+import httplib
2318+import urllib
2319+import urllib2
2320+import urlparse
2321+
2322+from eventlet import event
2323+from eventlet import greenthread
2324+
2325+from glance import client
2326+
2327+from nova import flags
2328+from nova import log as logging
2329+
2330+LOG = logging.getLogger("nova.virt.vmwareapi.read_write_util")
2331+
2332+FLAGS = flags.FLAGS
2333+
2334+USER_AGENT = "OpenStack-ESX-Adapter"
2335+
2336+try:
2337+ READ_CHUNKSIZE = client.BaseClient.CHUNKSIZE
2338+except AttributeError:
2339+ READ_CHUNKSIZE = 65536
2340+
2341+
2342+class GlanceFileRead(object):
2343+ """Glance file read handler class."""
2344+
2345+ def __init__(self, glance_read_iter):
2346+ self.glance_read_iter = glance_read_iter
2347+ self.iter = self.get_next()
2348+
2349+ def read(self, chunk_size):
2350+ """Read an item from the queue. The chunk size is ignored for the
2351+ Client ImageBodyIterator uses its own CHUNKSIZE."""
2352+ try:
2353+ return self.iter.next()
2354+ except StopIteration:
2355+ return ""
2356+
2357+ def get_next(self):
2358+ """Get the next item from the image iterator."""
2359+ for data in self.glance_read_iter:
2360+ yield data
2361+
2362+ def close(self):
2363+ """A dummy close just to maintain consistency."""
2364+ pass
2365+
2366+
2367+class VMwareHTTPFile(object):
2368+ """Base class for HTTP file."""
2369+
2370+ def __init__(self, file_handle):
2371+ self.eof = False
2372+ self.file_handle = file_handle
2373+
2374+ def set_eof(self, eof):
2375+ """Set the end of file marker."""
2376+ self.eof = eof
2377+
2378+ def get_eof(self):
2379+ """Check if the end of file has been reached."""
2380+ return self.eof
2381+
2382+ def close(self):
2383+ """Close the file handle."""
2384+ try:
2385+ self.file_handle.close()
2386+ except Exception, exc:
2387+ LOG.exception(exc)
2388+
2389+ def __del__(self):
2390+ """Close the file handle on garbage collection."""
2391+ self.close()
2392+
2393+ def _build_vim_cookie_headers(self, vim_cookies):
2394+ """Build ESX host session cookie headers."""
2395+ cookie_header = ""
2396+ for vim_cookie in vim_cookies:
2397+ cookie_header = vim_cookie.name + "=" + vim_cookie.value
2398+ break
2399+ return cookie_header
2400+
2401+ def write(self, data):
2402+ """Write data to the file."""
2403+ raise NotImplementedError
2404+
2405+ def read(self, chunk_size):
2406+ """Read a chunk of data."""
2407+ raise NotImplementedError
2408+
2409+ def get_size(self):
2410+ """Get size of the file to be read."""
2411+ raise NotImplementedError
2412+
2413+
2414+class VMWareHTTPWriteFile(VMwareHTTPFile):
2415+ """VMWare file write handler class."""
2416+
2417+ def __init__(self, host, data_center_name, datastore_name, cookies,
2418+ file_path, file_size, scheme="https"):
2419+ base_url = "%s://%s/folder/%s" % (scheme, host, file_path)
2420+ param_list = {"dcPath": data_center_name, "dsName": datastore_name}
2421+ base_url = base_url + "?" + urllib.urlencode(param_list)
2422+ (scheme, netloc, path, params, query, fragment) = \
2423+ urlparse.urlparse(base_url)
2424+ if scheme == "http":
2425+ conn = httplib.HTTPConnection(netloc)
2426+ elif scheme == "https":
2427+ conn = httplib.HTTPSConnection(netloc)
2428+ conn.putrequest("PUT", path + "?" + query)
2429+ conn.putheader("User-Agent", USER_AGENT)
2430+ conn.putheader("Content-Length", file_size)
2431+ conn.putheader("Cookie", self._build_vim_cookie_headers(cookies))
2432+ conn.endheaders()
2433+ self.conn = conn
2434+ VMwareHTTPFile.__init__(self, conn)
2435+
2436+ def write(self, data):
2437+ """Write to the file."""
2438+ self.file_handle.send(data)
2439+
2440+ def close(self):
2441+ """Get the response and close the connection."""
2442+ try:
2443+ self.conn.getresponse()
2444+ except Exception, excep:
2445+ LOG.debug(_("Exception during HTTP connection close in "
2446+ "VMWareHTTpWrite. Exception is %s") % excep)
2447+ super(VMWareHTTPWriteFile, self).close()
2448+
2449+
2450+class VmWareHTTPReadFile(VMwareHTTPFile):
2451+ """VMWare file read handler class."""
2452+
2453+ def __init__(self, host, data_center_name, datastore_name, cookies,
2454+ file_path, scheme="https"):
2455+ base_url = "%s://%s/folder/%s" % (scheme, host,
2456+ urllib.pathname2url(file_path))
2457+ param_list = {"dcPath": data_center_name, "dsName": datastore_name}
2458+ base_url = base_url + "?" + urllib.urlencode(param_list)
2459+ headers = {'User-Agent': USER_AGENT,
2460+ 'Cookie': self._build_vim_cookie_headers(cookies)}
2461+ request = urllib2.Request(base_url, None, headers)
2462+ conn = urllib2.urlopen(request)
2463+ VMwareHTTPFile.__init__(self, conn)
2464+
2465+ def read(self, chunk_size):
2466+ """Read a chunk of data."""
2467+ # We are ignoring the chunk size passed for we want the pipe to hold
2468+ # data items of the chunk-size that Glance Client uses for read
2469+ # while writing.
2470+ return self.file_handle.read(READ_CHUNKSIZE)
2471+
2472+ def get_size(self):
2473+ """Get size of the file to be read."""
2474+ return self.file_handle.headers.get("Content-Length", -1)
2475
2476=== added file 'nova/virt/vmwareapi/vim.py'
2477--- nova/virt/vmwareapi/vim.py 1970-01-01 00:00:00 +0000
2478+++ nova/virt/vmwareapi/vim.py 2011-03-24 16:38:31 +0000
2479@@ -0,0 +1,176 @@
2480+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2481+
2482+# Copyright (c) 2011 Citrix Systems, Inc.
2483+# Copyright 2011 OpenStack LLC.
2484+#
2485+# Licensed under the Apache License, Version 2.0 (the "License"); you may
2486+# not use this file except in compliance with the License. You may obtain
2487+# a copy of the License at
2488+#
2489+# http://www.apache.org/licenses/LICENSE-2.0
2490+#
2491+# Unless required by applicable law or agreed to in writing, software
2492+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
2493+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
2494+# License for the specific language governing permissions and limitations
2495+# under the License.
2496+
2497+"""
2498+Classes for making VMware VI SOAP calls.
2499+"""
2500+
2501+import httplib
2502+
2503+from suds import WebFault
2504+from suds.client import Client
2505+from suds.plugin import MessagePlugin
2506+from suds.sudsobject import Property
2507+
2508+from nova import flags
2509+from nova.virt.vmwareapi import error_util
2510+
2511+RESP_NOT_XML_ERROR = 'Response is "text/html", not "text/xml"'
2512+CONN_ABORT_ERROR = 'Software caused connection abort'
2513+ADDRESS_IN_USE_ERROR = 'Address already in use'
2514+
2515+FLAGS = flags.FLAGS
2516+flags.DEFINE_string('vmwareapi_wsdl_loc',
2517+ None,
2518+ 'VIM Service WSDL Location'
2519+ 'e.g http://<server>/vimService.wsdl'
2520+ 'Due to a bug in vSphere ESX 4.1 default wsdl'
2521+ 'Refer readme-vmware to setup')
2522+
2523+
2524+class VIMMessagePlugin(MessagePlugin):
2525+
2526+ def addAttributeForValue(self, node):
2527+ # suds does not handle AnyType properly.
2528+ # VI SDK requires type attribute to be set when AnyType is used
2529+ if node.name == 'value':
2530+ node.set('xsi:type', 'xsd:string')
2531+
2532+ def marshalled(self, context):
2533+ """suds will send the specified soap envelope.
2534+ Provides the plugin with the opportunity to prune empty
2535+ nodes and fixup nodes before sending it to the server.
2536+ """
2537+ # suds builds the entire request object based on the wsdl schema.
2538+ # VI SDK throws server errors if optional SOAP nodes are sent without
2539+ # values, e.g. <test/> as opposed to <test>test</test>
2540+ context.envelope.prune()
2541+ context.envelope.walk(self.addAttributeForValue)
2542+
2543+
2544+class Vim:
2545+ """The VIM Object."""
2546+
2547+ def __init__(self,
2548+ protocol="https",
2549+ host="localhost"):
2550+ """
2551+ Creates the necessary Communication interfaces and gets the
2552+ ServiceContent for initiating SOAP transactions.
2553+
2554+ protocol: http or https
2555+ host : ESX IPAddress[:port] or ESX Hostname[:port]
2556+ """
2557+ self._protocol = protocol
2558+ self._host_name = host
2559+ wsdl_url = FLAGS.vmwareapi_wsdl_loc
2560+ if wsdl_url is None:
2561+ raise Exception(_("Must specify vmwareapi_wsdl_loc"))
2562+ # TODO(sateesh): Use this when VMware fixes their faulty wsdl
2563+ #wsdl_url = '%s://%s/sdk/vimService.wsdl' % (self._protocol,
2564+ # self._host_name)
2565+ url = '%s://%s/sdk' % (self._protocol, self._host_name)
2566+ self.client = Client(wsdl_url, location=url,
2567+ plugins=[VIMMessagePlugin()])
2568+ self._service_content = \
2569+ self.RetrieveServiceContent("ServiceInstance")
2570+
2571+ def get_service_content(self):
2572+ """Gets the service content object."""
2573+ return self._service_content
2574+
2575+ def __getattr__(self, attr_name):
2576+ """Makes the API calls and gets the result."""
2577+ try:
2578+ return object.__getattr__(self, attr_name)
2579+ except AttributeError:
2580+
2581+ def vim_request_handler(managed_object, **kwargs):
2582+ """
2583+ Builds the SOAP message and parses the response for fault
2584+ checking and other errors.
2585+
2586+ managed_object : Managed Object Reference or Managed
2587+ Object Name
2588+ **kwargs : Keyword arguments of the call
2589+ """
2590+ # Dynamic handler for VI SDK Calls
2591+ try:
2592+ request_mo = \
2593+ self._request_managed_object_builder(managed_object)
2594+ request = getattr(self.client.service, attr_name)
2595+ response = request(request_mo, **kwargs)
2596+ # To check for the faults that are part of the message body
2597+ # and not returned as Fault object response from the ESX
2598+ # SOAP server
2599+ if hasattr(error_util.FaultCheckers,
2600+ attr_name.lower() + "_fault_checker"):
2601+ fault_checker = getattr(error_util.FaultCheckers,
2602+ attr_name.lower() + "_fault_checker")
2603+ fault_checker(response)
2604+ return response
2605+ # Catch the VimFaultException that is raised by the fault
2606+ # check of the SOAP response
2607+ except error_util.VimFaultException, excep:
2608+ raise
2609+ except WebFault, excep:
2610+ doc = excep.document
2611+ detail = doc.childAtPath("/Envelope/Body/Fault/detail")
2612+ fault_list = []
2613+ for child in detail.getChildren():
2614+ fault_list.append(child.get("type"))
2615+ raise error_util.VimFaultException(fault_list, excep)
2616+ except AttributeError, excep:
2617+ raise error_util.VimAttributeError(_("No such SOAP method "
2618+ "'%s' provided by VI SDK") % (attr_name), excep)
2619+ except (httplib.CannotSendRequest,
2620+ httplib.ResponseNotReady,
2621+ httplib.CannotSendHeader), excep:
2622+ raise error_util.SessionOverLoadException(_("httplib "
2623+ "error in %s: ") % (attr_name), excep)
2624+ except Exception, excep:
2625+ # Socket errors which need special handling for they
2626+ # might be caused by ESX API call overload
2627+ if (str(excep).find(ADDRESS_IN_USE_ERROR) != -1 or
2628+ str(excep).find(CONN_ABORT_ERROR)) != -1:
2629+ raise error_util.SessionOverLoadException(_("Socket "
2630+ "error in %s: ") % (attr_name), excep)
2631+ # Type error that needs special handling for it might be
2632+ # caused by ESX host API call overload
2633+ elif str(excep).find(RESP_NOT_XML_ERROR) != -1:
2634+ raise error_util.SessionOverLoadException(_("Type "
2635+ "error in %s: ") % (attr_name), excep)
2636+ else:
2637+ raise error_util.VimException(
2638+ _("Exception in %s ") % (attr_name), excep)
2639+ return vim_request_handler
2640+
2641+ def _request_managed_object_builder(self, managed_object):
2642+ """Builds the request managed object."""
2643+ # Request Managed Object Builder
2644+ if type(managed_object) == type(""):
2645+ mo = Property(managed_object)
2646+ mo._type = managed_object
2647+ else:
2648+ mo = managed_object
2649+ return mo
2650+
2651+ def __repr__(self):
2652+ return "VIM Object"
2653+
2654+ def __str__(self):
2655+ return "VIM Object"
2656
2657=== added file 'nova/virt/vmwareapi/vim_util.py'
2658--- nova/virt/vmwareapi/vim_util.py 1970-01-01 00:00:00 +0000
2659+++ nova/virt/vmwareapi/vim_util.py 2011-03-24 16:38:31 +0000
2660@@ -0,0 +1,217 @@
2661+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2662+
2663+# Copyright (c) 2011 Citrix Systems, Inc.
2664+# Copyright 2011 OpenStack LLC.
2665+#
2666+# Licensed under the Apache License, Version 2.0 (the "License"); you may
2667+# not use this file except in compliance with the License. You may obtain
2668+# a copy of the License at
2669+#
2670+# http://www.apache.org/licenses/LICENSE-2.0
2671+#
2672+# Unless required by applicable law or agreed to in writing, software
2673+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
2674+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
2675+# License for the specific language governing permissions and limitations
2676+# under the License.
2677+
2678+"""
2679+The VMware API utility module.
2680+"""
2681+
2682+
2683+def build_selection_spec(client_factory, name):
2684+ """Builds the selection spec."""
2685+ sel_spec = client_factory.create('ns0:SelectionSpec')
2686+ sel_spec.name = name
2687+ return sel_spec
2688+
2689+
2690+def build_traversal_spec(client_factory, name, spec_type, path, skip,
2691+ select_set):
2692+ """Builds the traversal spec object."""
2693+ traversal_spec = client_factory.create('ns0:TraversalSpec')
2694+ traversal_spec.name = name
2695+ traversal_spec.type = spec_type
2696+ traversal_spec.path = path
2697+ traversal_spec.skip = skip
2698+ traversal_spec.selectSet = select_set
2699+ return traversal_spec
2700+
2701+
2702+def build_recursive_traversal_spec(client_factory):
2703+ """
2704+ Builds the Recursive Traversal Spec to traverse the object managed
2705+ object hierarchy.
2706+ """
2707+ visit_folders_select_spec = build_selection_spec(client_factory,
2708+ "visitFolders")
2709+ # For getting to hostFolder from datacenter
2710+ dc_to_hf = build_traversal_spec(client_factory, "dc_to_hf", "Datacenter",
2711+ "hostFolder", False,
2712+ [visit_folders_select_spec])
2713+ # For getting to vmFolder from datacenter
2714+ dc_to_vmf = build_traversal_spec(client_factory, "dc_to_vmf", "Datacenter",
2715+ "vmFolder", False,
2716+ [visit_folders_select_spec])
2717+ # For getting Host System to virtual machine
2718+ h_to_vm = build_traversal_spec(client_factory, "h_to_vm", "HostSystem",
2719+ "vm", False,
2720+ [visit_folders_select_spec])
2721+
2722+ # For getting to Host System from Compute Resource
2723+ cr_to_h = build_traversal_spec(client_factory, "cr_to_h",
2724+ "ComputeResource", "host", False, [])
2725+
2726+ # For getting to datastore from Compute Resource
2727+ cr_to_ds = build_traversal_spec(client_factory, "cr_to_ds",
2728+ "ComputeResource", "datastore", False, [])
2729+
2730+ rp_to_rp_select_spec = build_selection_spec(client_factory, "rp_to_rp")
2731+ rp_to_vm_select_spec = build_selection_spec(client_factory, "rp_to_vm")
2732+ # For getting to resource pool from Compute Resource
2733+ cr_to_rp = build_traversal_spec(client_factory, "cr_to_rp",
2734+ "ComputeResource", "resourcePool", False,
2735+ [rp_to_rp_select_spec, rp_to_vm_select_spec])
2736+
2737+ # For getting to child res pool from the parent res pool
2738+ rp_to_rp = build_traversal_spec(client_factory, "rp_to_rp", "ResourcePool",
2739+ "resourcePool", False,
2740+ [rp_to_rp_select_spec, rp_to_vm_select_spec])
2741+
2742+ # For getting to Virtual Machine from the Resource Pool
2743+ rp_to_vm = build_traversal_spec(client_factory, "rp_to_vm", "ResourcePool",
2744+ "vm", False,
2745+ [rp_to_rp_select_spec, rp_to_vm_select_spec])
2746+
2747+ # Get the assorted traversal spec which takes care of the objects to
2748+ # be searched for from the root folder
2749+ traversal_spec = build_traversal_spec(client_factory, "visitFolders",
2750+ "Folder", "childEntity", False,
2751+ [visit_folders_select_spec, dc_to_hf,
2752+ dc_to_vmf, cr_to_ds, cr_to_h, cr_to_rp,
2753+ rp_to_rp, h_to_vm, rp_to_vm])
2754+ return traversal_spec
2755+
2756+
2757+def build_property_spec(client_factory, type="VirtualMachine",
2758+ properties_to_collect=["name"],
2759+ all_properties=False):
2760+ """Builds the Property Spec."""
2761+ property_spec = client_factory.create('ns0:PropertySpec')
2762+ property_spec.all = all_properties
2763+ property_spec.pathSet = properties_to_collect
2764+ property_spec.type = type
2765+ return property_spec
2766+
2767+
2768+def build_object_spec(client_factory, root_folder, traversal_specs):
2769+ """Builds the object Spec."""
2770+ object_spec = client_factory.create('ns0:ObjectSpec')
2771+ object_spec.obj = root_folder
2772+ object_spec.skip = False
2773+ object_spec.selectSet = traversal_specs
2774+ return object_spec
2775+
2776+
2777+def build_property_filter_spec(client_factory, property_specs, object_specs):
2778+ """Builds the Property Filter Spec."""
2779+ property_filter_spec = client_factory.create('ns0:PropertyFilterSpec')
2780+ property_filter_spec.propSet = property_specs
2781+ property_filter_spec.objectSet = object_specs
2782+ return property_filter_spec
2783+
2784+
2785+def get_object_properties(vim, collector, mobj, type, properties):
2786+ """Gets the properties of the Managed object specified."""
2787+ client_factory = vim.client.factory
2788+ if mobj is None:
2789+ return None
2790+ usecoll = collector
2791+ if usecoll is None:
2792+ usecoll = vim.get_service_content().propertyCollector
2793+ property_filter_spec = client_factory.create('ns0:PropertyFilterSpec')
2794+ property_spec = client_factory.create('ns0:PropertySpec')
2795+ property_spec.all = (properties is None or len(properties) == 0)
2796+ property_spec.pathSet = properties
2797+ property_spec.type = type
2798+ object_spec = client_factory.create('ns0:ObjectSpec')
2799+ object_spec.obj = mobj
2800+ object_spec.skip = False
2801+ property_filter_spec.propSet = [property_spec]
2802+ property_filter_spec.objectSet = [object_spec]
2803+ return vim.RetrieveProperties(usecoll, specSet=[property_filter_spec])
2804+
2805+
2806+def get_dynamic_property(vim, mobj, type, property_name):
2807+ """Gets a particular property of the Managed Object."""
2808+ obj_content = \
2809+ get_object_properties(vim, None, mobj, type, [property_name])
2810+ property_value = None
2811+ if obj_content:
2812+ dynamic_property = obj_content[0].propSet
2813+ if dynamic_property:
2814+ property_value = dynamic_property[0].val
2815+ return property_value
2816+
2817+
2818+def get_objects(vim, type, properties_to_collect=["name"], all=False):
2819+ """Gets the list of objects of the type specified."""
2820+ client_factory = vim.client.factory
2821+ object_spec = build_object_spec(client_factory,
2822+ vim.get_service_content().rootFolder,
2823+ [build_recursive_traversal_spec(client_factory)])
2824+ property_spec = build_property_spec(client_factory, type=type,
2825+ properties_to_collect=properties_to_collect,
2826+ all_properties=all)
2827+ property_filter_spec = build_property_filter_spec(client_factory,
2828+ [property_spec],
2829+ [object_spec])
2830+ return vim.RetrieveProperties(vim.get_service_content().propertyCollector,
2831+ specSet=[property_filter_spec])
2832+
2833+
2834+def get_prop_spec(client_factory, spec_type, properties):
2835+ """Builds the Property Spec Object."""
2836+ prop_spec = client_factory.create('ns0:PropertySpec')
2837+ prop_spec.type = spec_type
2838+ prop_spec.pathSet = properties
2839+ return prop_spec
2840+
2841+
2842+def get_obj_spec(client_factory, obj, select_set=None):
2843+ """Builds the Object Spec object."""
2844+ obj_spec = client_factory.create('ns0:ObjectSpec')
2845+ obj_spec.obj = obj
2846+ obj_spec.skip = False
2847+ if select_set is not None:
2848+ obj_spec.selectSet = select_set
2849+ return obj_spec
2850+
2851+
2852+def get_prop_filter_spec(client_factory, obj_spec, prop_spec):
2853+ """Builds the Property Filter Spec Object."""
2854+ prop_filter_spec = \
2855+ client_factory.create('ns0:PropertyFilterSpec')
2856+ prop_filter_spec.propSet = prop_spec
2857+ prop_filter_spec.objectSet = obj_spec
2858+ return prop_filter_spec
2859+
2860+
2861+def get_properties_for_a_collection_of_objects(vim, type,
2862+ obj_list, properties):
2863+ """
2864+ Gets the list of properties for the collection of
2865+ objects of the type specified.
2866+ """
2867+ client_factory = vim.client.factory
2868+ if len(obj_list) == 0:
2869+ return []
2870+ prop_spec = get_prop_spec(client_factory, type, properties)
2871+ lst_obj_specs = []
2872+ for obj in obj_list:
2873+ lst_obj_specs.append(get_obj_spec(client_factory, obj))
2874+ prop_filter_spec = get_prop_filter_spec(client_factory,
2875+ lst_obj_specs, [prop_spec])
2876+ return vim.RetrieveProperties(vim.get_service_content().propertyCollector,
2877+ specSet=[prop_filter_spec])
2878
2879=== added file 'nova/virt/vmwareapi/vm_util.py'
2880--- nova/virt/vmwareapi/vm_util.py 1970-01-01 00:00:00 +0000
2881+++ nova/virt/vmwareapi/vm_util.py 2011-03-24 16:38:31 +0000
2882@@ -0,0 +1,306 @@
2883+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2884+
2885+# Copyright (c) 2011 Citrix Systems, Inc.
2886+# Copyright 2011 OpenStack LLC.
2887+#
2888+# Licensed under the Apache License, Version 2.0 (the "License"); you may
2889+# not use this file except in compliance with the License. You may obtain
2890+# a copy of the License at
2891+#
2892+# http://www.apache.org/licenses/LICENSE-2.0
2893+#
2894+# Unless required by applicable law or agreed to in writing, software
2895+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
2896+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
2897+# License for the specific language governing permissions and limitations
2898+# under the License.
2899+"""
2900+The VMware API VM utility module to build SOAP object specs.
2901+"""
2902+
2903+
2904+def build_datastore_path(datastore_name, path):
2905+ """Build the datastore compliant path."""
2906+ return "[%s] %s" % (datastore_name, path)
2907+
2908+
2909+def split_datastore_path(datastore_path):
2910+ """
2911+ Split the VMWare style datastore path to get the Datastore
2912+ name and the entity path.
2913+ """
2914+ spl = datastore_path.split('[', 1)[1].split(']', 1)
2915+ path = ""
2916+ if len(spl) == 1:
2917+ datastore_url = spl[0]
2918+ else:
2919+ datastore_url, path = spl
2920+ return datastore_url, path.strip()
2921+
2922+
2923+def get_vm_create_spec(client_factory, instance, data_store_name,
2924+ network_name="vmnet0",
2925+ os_type="otherGuest"):
2926+ """Builds the VM Create spec."""
2927+ config_spec = client_factory.create('ns0:VirtualMachineConfigSpec')
2928+ config_spec.name = instance.name
2929+ config_spec.guestId = os_type
2930+
2931+ vm_file_info = client_factory.create('ns0:VirtualMachineFileInfo')
2932+ vm_file_info.vmPathName = "[" + data_store_name + "]"
2933+ config_spec.files = vm_file_info
2934+
2935+ tools_info = client_factory.create('ns0:ToolsConfigInfo')
2936+ tools_info.afterPowerOn = True
2937+ tools_info.afterResume = True
2938+ tools_info.beforeGuestStandby = True
2939+ tools_info.beforeGuestShutdown = True
2940+ tools_info.beforeGuestReboot = True
2941+
2942+ config_spec.tools = tools_info
2943+ config_spec.numCPUs = int(instance.vcpus)
2944+ config_spec.memoryMB = int(instance.memory_mb)
2945+
2946+ nic_spec = create_network_spec(client_factory,
2947+ network_name, instance.mac_address)
2948+
2949+ device_config_spec = [nic_spec]
2950+
2951+ config_spec.deviceChange = device_config_spec
2952+ return config_spec
2953+
2954+
2955+def create_controller_spec(client_factory, key):
2956+ """
2957+ Builds a Config Spec for the LSI Logic Controller's addition
2958+ which acts as the controller for the virtual hard disk to be attached
2959+ to the VM.
2960+ """
2961+ # Create a controller for the Virtual Hard Disk
2962+ virtual_device_config = \
2963+ client_factory.create('ns0:VirtualDeviceConfigSpec')
2964+ virtual_device_config.operation = "add"
2965+ virtual_lsi = \
2966+ client_factory.create('ns0:VirtualLsiLogicController')
2967+ virtual_lsi.key = key
2968+ virtual_lsi.busNumber = 0
2969+ virtual_lsi.sharedBus = "noSharing"
2970+ virtual_device_config.device = virtual_lsi
2971+ return virtual_device_config
2972+
2973+
2974+def create_network_spec(client_factory, network_name, mac_address):
2975+ """
2976+ Builds a config spec for the addition of a new network
2977+ adapter to the VM.
2978+ """
2979+ network_spec = \
2980+ client_factory.create('ns0:VirtualDeviceConfigSpec')
2981+ network_spec.operation = "add"
2982+
2983+ # Get the recommended card type for the VM based on the guest OS of the VM
2984+ net_device = client_factory.create('ns0:VirtualPCNet32')
2985+
2986+ backing = \
2987+ client_factory.create('ns0:VirtualEthernetCardNetworkBackingInfo')
2988+ backing.deviceName = network_name
2989+
2990+ connectable_spec = \
2991+ client_factory.create('ns0:VirtualDeviceConnectInfo')
2992+ connectable_spec.startConnected = True
2993+ connectable_spec.allowGuestControl = True
2994+ connectable_spec.connected = True
2995+
2996+ net_device.connectable = connectable_spec
2997+ net_device.backing = backing
2998+
2999+ # The Server assigns a Key to the device. Here we pass a -ve temporary key.
3000+ # -ve because actual keys are +ve numbers and we don't
3001+ # want a clash with the key that server might associate with the device
3002+ net_device.key = -47
3003+ net_device.addressType = "manual"
3004+ net_device.macAddress = mac_address
3005+ net_device.wakeOnLanEnabled = True
3006+
3007+ network_spec.device = net_device
3008+ return network_spec
3009+
3010+
3011+def get_vmdk_attach_config_spec(client_factory, disksize, file_path,
3012+ adapter_type="lsiLogic"):
3013+ """Builds the vmdk attach config spec."""
3014+ config_spec = client_factory.create('ns0:VirtualMachineConfigSpec')
3015+
3016+ # The controller Key pertains to the Key of the LSI Logic Controller, which
3017+ # controls this Hard Disk
3018+ device_config_spec = []
3019+ # For IDE devices, there are these two default controllers created in the
3020+ # VM having keys 200 and 201
3021+ if adapter_type == "ide":
3022+ controller_key = 200
3023+ else:
3024+ controller_key = -101
3025+ controller_spec = create_controller_spec(client_factory,
3026+ controller_key)
3027+ device_config_spec.append(controller_spec)
3028+ virtual_device_config_spec = create_virtual_disk_spec(client_factory,
3029+ disksize, controller_key, file_path)
3030+
3031+ device_config_spec.append(virtual_device_config_spec)
3032+
3033+ config_spec.deviceChange = device_config_spec
3034+ return config_spec
3035+
3036+
3037+def get_vmdk_file_path_and_adapter_type(client_factory, hardware_devices):
3038+ """Gets the vmdk file path and the storage adapter type."""
3039+ if hardware_devices.__class__.__name__ == "ArrayOfVirtualDevice":
3040+ hardware_devices = hardware_devices.VirtualDevice
3041+ vmdk_file_path = None
3042+ vmdk_controler_key = None
3043+
3044+ adapter_type_dict = {}
3045+ for device in hardware_devices:
3046+ if device.__class__.__name__ == "VirtualDisk" and \
3047+ device.backing.__class__.__name__ \
3048+ == "VirtualDiskFlatVer2BackingInfo":
3049+ vmdk_file_path = device.backing.fileName
3050+ vmdk_controler_key = device.controllerKey
3051+ elif device.__class__.__name__ == "VirtualLsiLogicController":
3052+ adapter_type_dict[device.key] = "lsiLogic"
3053+ elif device.__class__.__name__ == "VirtualBusLogicController":
3054+ adapter_type_dict[device.key] = "busLogic"
3055+ elif device.__class__.__name__ == "VirtualIDEController":
3056+ adapter_type_dict[device.key] = "ide"
3057+ elif device.__class__.__name__ == "VirtualLsiLogicSASController":
3058+ adapter_type_dict[device.key] = "lsiLogic"
3059+
3060+ adapter_type = adapter_type_dict.get(vmdk_controler_key, "")
3061+
3062+ return vmdk_file_path, adapter_type
3063+
3064+
3065+def get_copy_virtual_disk_spec(client_factory, adapter_type="lsilogic"):
3066+ """Builds the Virtual Disk copy spec."""
3067+ dest_spec = client_factory.create('ns0:VirtualDiskSpec')
3068+ dest_spec.adapterType = adapter_type
3069+ dest_spec.diskType = "thick"
3070+ return dest_spec
3071+
3072+
3073+def get_vmdk_create_spec(client_factory, size_in_kb, adapter_type="lsiLogic"):
3074+ """Builds the virtual disk create spec."""
3075+ create_vmdk_spec = \
3076+ client_factory.create('ns0:FileBackedVirtualDiskSpec')
3077+ create_vmdk_spec.adapterType = adapter_type
3078+ create_vmdk_spec.diskType = "thick"
3079+ create_vmdk_spec.capacityKb = size_in_kb
3080+ return create_vmdk_spec
3081+
3082+
3083+def create_virtual_disk_spec(client_factory, disksize, controller_key,
3084+ file_path=None):
3085+ """
3086+ Builds spec for the creation of a new/ attaching of an already existing
3087+ Virtual Disk to the VM.
3088+ """
3089+ virtual_device_config = \
3090+ client_factory.create('ns0:VirtualDeviceConfigSpec')
3091+ virtual_device_config.operation = "add"
3092+ if file_path is None:
3093+ virtual_device_config.fileOperation = "create"
3094+
3095+ virtual_disk = client_factory.create('ns0:VirtualDisk')
3096+
3097+ disk_file_backing = \
3098+ client_factory.create('ns0:VirtualDiskFlatVer2BackingInfo')
3099+ disk_file_backing.diskMode = "persistent"
3100+ disk_file_backing.thinProvisioned = False
3101+ if file_path is not None:
3102+ disk_file_backing.fileName = file_path
3103+ else:
3104+ disk_file_backing.fileName = ""
3105+
3106+ connectable_spec = client_factory.create('ns0:VirtualDeviceConnectInfo')
3107+ connectable_spec.startConnected = True
3108+ connectable_spec.allowGuestControl = False
3109+ connectable_spec.connected = True
3110+
3111+ virtual_disk.backing = disk_file_backing
3112+ virtual_disk.connectable = connectable_spec
3113+
3114+ # The Server assigns a Key to the device. Here we pass a -ve random key.
3115+ # -ve because actual keys are +ve numbers and we don't
3116+ # want a clash with the key that server might associate with the device
3117+ virtual_disk.key = -100
3118+ virtual_disk.controllerKey = controller_key
3119+ virtual_disk.unitNumber = 0
3120+ virtual_disk.capacityInKB = disksize
3121+
3122+ virtual_device_config.device = virtual_disk
3123+
3124+ return virtual_device_config
3125+
3126+
3127+def get_dummy_vm_create_spec(client_factory, name, data_store_name):
3128+ """Builds the dummy VM create spec."""
3129+ config_spec = client_factory.create('ns0:VirtualMachineConfigSpec')
3130+
3131+ config_spec.name = name
3132+ config_spec.guestId = "otherGuest"
3133+
3134+ vm_file_info = client_factory.create('ns0:VirtualMachineFileInfo')
3135+ vm_file_info.vmPathName = "[" + data_store_name + "]"
3136+ config_spec.files = vm_file_info
3137+
3138+ tools_info = client_factory.create('ns0:ToolsConfigInfo')
3139+ tools_info.afterPowerOn = True
3140+ tools_info.afterResume = True
3141+ tools_info.beforeGuestStandby = True
3142+ tools_info.beforeGuestShutdown = True
3143+ tools_info.beforeGuestReboot = True
3144+
3145+ config_spec.tools = tools_info
3146+ config_spec.numCPUs = 1
3147+ config_spec.memoryMB = 4
3148+
3149+ controller_key = -101
3150+ controller_spec = create_controller_spec(client_factory, controller_key)
3151+ disk_spec = create_virtual_disk_spec(client_factory, 1024, controller_key)
3152+
3153+ device_config_spec = [controller_spec, disk_spec]
3154+
3155+ config_spec.deviceChange = device_config_spec
3156+ return config_spec
3157+
3158+
3159+def get_machine_id_change_spec(client_factory, mac, ip_addr, netmask, gateway):
3160+ """Builds the machine id change config spec."""
3161+ machine_id_str = "%s;%s;%s;%s" % (mac, ip_addr, netmask, gateway)
3162+ virtual_machine_config_spec = \
3163+ client_factory.create('ns0:VirtualMachineConfigSpec')
3164+
3165+ opt = client_factory.create('ns0:OptionValue')
3166+ opt.key = "machine.id"
3167+ opt.value = machine_id_str
3168+ virtual_machine_config_spec.extraConfig = [opt]
3169+ return virtual_machine_config_spec
3170+
3171+
3172+def get_add_vswitch_port_group_spec(client_factory, vswitch_name,
3173+ port_group_name, vlan_id):
3174+ """Builds the virtual switch port group add spec."""
3175+ vswitch_port_group_spec = client_factory.create('ns0:HostPortGroupSpec')
3176+ vswitch_port_group_spec.name = port_group_name
3177+ vswitch_port_group_spec.vswitchName = vswitch_name
3178+
3179+ # VLAN ID of 0 means that VLAN tagging is not to be done for the network.
3180+ vswitch_port_group_spec.vlanId = int(vlan_id)
3181+
3182+ policy = client_factory.create('ns0:HostNetworkPolicy')
3183+ nicteaming = client_factory.create('ns0:HostNicTeamingPolicy')
3184+ nicteaming.notifySwitches = True
3185+ policy.nicTeaming = nicteaming
3186+
3187+ vswitch_port_group_spec.policy = policy
3188+ return vswitch_port_group_spec
3189
3190=== added file 'nova/virt/vmwareapi/vmops.py'
3191--- nova/virt/vmwareapi/vmops.py 1970-01-01 00:00:00 +0000
3192+++ nova/virt/vmwareapi/vmops.py 2011-03-24 16:38:31 +0000
3193@@ -0,0 +1,789 @@
3194+# vim: tabstop=4 shiftwidth=4 softtabstop=4
3195+
3196+# Copyright (c) 2011 Citrix Systems, Inc.
3197+# Copyright 2011 OpenStack LLC.
3198+#
3199+# Licensed under the Apache License, Version 2.0 (the "License"); you may
3200+# not use this file except in compliance with the License. You may obtain
3201+# a copy of the License at
3202+#
3203+# http://www.apache.org/licenses/LICENSE-2.0
3204+#
3205+# Unless required by applicable law or agreed to in writing, software
3206+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
3207+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3208+# License for the specific language governing permissions and limitations
3209+# under the License.
3210+
3211+"""
3212+Class for VM tasks like spawn, snapshot, suspend, resume etc.
3213+"""
3214+
3215+import base64
3216+import os
3217+import time
3218+import urllib
3219+import urllib2
3220+import uuid
3221+
3222+from nova import context
3223+from nova import db
3224+from nova import exception
3225+from nova import flags
3226+from nova import log as logging
3227+from nova.compute import power_state
3228+from nova.virt.vmwareapi import vim_util
3229+from nova.virt.vmwareapi import vm_util
3230+from nova.virt.vmwareapi import vmware_images
3231+from nova.virt.vmwareapi import network_utils
3232+
3233+FLAGS = flags.FLAGS
3234+LOG = logging.getLogger("nova.virt.vmwareapi.vmops")
3235+
3236+VMWARE_POWER_STATES = {
3237+ 'poweredOff': power_state.SHUTDOWN,
3238+ 'poweredOn': power_state.RUNNING,
3239+ 'suspended': power_state.PAUSED}
3240+
3241+
3242+class VMWareVMOps(object):
3243+ """Management class for VM-related tasks."""
3244+
3245+ def __init__(self, session):
3246+ """Initializer."""
3247+ self._session = session
3248+
3249+ def _wait_with_callback(self, instance_id, task, callback):
3250+ """Waits for the task to finish and does a callback after."""
3251+ ret = None
3252+ try:
3253+ ret = self._session._wait_for_task(instance_id, task)
3254+ except Exception, excep:
3255+ LOG.exception(excep)
3256+ callback(ret)
3257+
3258+ def list_instances(self):
3259+ """Lists the VM instances that are registered with the ESX host."""
3260+ LOG.debug(_("Getting list of instances"))
3261+ vms = self._session._call_method(vim_util, "get_objects",
3262+ "VirtualMachine",
3263+ ["name", "runtime.connectionState"])
3264+ lst_vm_names = []
3265+ for vm in vms:
3266+ vm_name = None
3267+ conn_state = None
3268+ for prop in vm.propSet:
3269+ if prop.name == "name":
3270+ vm_name = prop.val
3271+ elif prop.name == "runtime.connectionState":
3272+ conn_state = prop.val
3273+ # Ignoring the oprhaned or inaccessible VMs
3274+ if conn_state not in ["orphaned", "inaccessible"]:
3275+ lst_vm_names.append(vm_name)
3276+ LOG.debug(_("Got total of %s instances") % str(len(lst_vm_names)))
3277+ return lst_vm_names
3278+
3279+ def spawn(self, instance):
3280+ """
3281+ Creates a VM instance.
3282+
3283+ Steps followed are:
3284+ 1. Create a VM with no disk and the specifics in the instance object
3285+ like RAM size.
3286+ 2. Create a dummy vmdk of the size of the disk file that is to be
3287+ uploaded. This is required just to create the metadata file.
3288+ 3. Delete the -flat.vmdk file created in the above step and retain
3289+ the metadata .vmdk file.
3290+ 4. Upload the disk file.
3291+ 5. Attach the disk to the VM by reconfiguring the same.
3292+ 6. Power on the VM.
3293+ """
3294+ vm_ref = self._get_vm_ref_from_the_name(instance.name)
3295+ if vm_ref:
3296+ raise exception.Duplicate(_("Attempted to create a VM with a name"
3297+ " %s, but that already exists on the host") % instance.name)
3298+
3299+ client_factory = self._session._get_vim().client.factory
3300+ service_content = self._session._get_vim().get_service_content()
3301+
3302+ network = db.network_get_by_instance(context.get_admin_context(),
3303+ instance['id'])
3304+
3305+ net_name = network['bridge']
3306+
3307+ def _check_if_network_bridge_exists():
3308+ network_ref = \
3309+ network_utils.get_network_with_the_name(self._session,
3310+ net_name)
3311+ if network_ref is None:
3312+ raise exception.NotFound(_("Network with the name '%s' doesn't"
3313+ " exist on the ESX host") % net_name)
3314+
3315+ _check_if_network_bridge_exists()
3316+
3317+ def _get_datastore_ref():
3318+ """Get the datastore list and choose the first local storage."""
3319+ data_stores = self._session._call_method(vim_util, "get_objects",
3320+ "Datastore", ["summary.type", "summary.name"])
3321+ for elem in data_stores:
3322+ ds_name = None
3323+ ds_type = None
3324+ for prop in elem.propSet:
3325+ if prop.name == "summary.type":
3326+ ds_type = prop.val
3327+ elif prop.name == "summary.name":
3328+ ds_name = prop.val
3329+ # Local storage identifier
3330+ if ds_type == "VMFS":
3331+ data_store_name = ds_name
3332+ return data_store_name
3333+
3334+ if data_store_name is None:
3335+ msg = _("Couldn't get a local Datastore reference")
3336+ LOG.exception(msg)
3337+ raise exception.Error(msg)
3338+
3339+ data_store_name = _get_datastore_ref()
3340+
3341+ def _get_image_properties():
3342+ """
3343+ Get the Size of the flat vmdk file that is there on the storage
3344+ repository.
3345+ """
3346+ image_size, image_properties = \
3347+ vmware_images.get_vmdk_size_and_properties(
3348+ instance.image_id, instance)
3349+ vmdk_file_size_in_kb = int(image_size) / 1024
3350+ os_type = image_properties.get("vmware_ostype", "otherGuest")
3351+ adapter_type = image_properties.get("vmware_adaptertype",
3352+ "lsiLogic")
3353+ return vmdk_file_size_in_kb, os_type, adapter_type
3354+
3355+ vmdk_file_size_in_kb, os_type, adapter_type = _get_image_properties()
3356+
3357+ def _get_vmfolder_and_res_pool_mors():
3358+ """Get the Vm folder ref from the datacenter."""
3359+ dc_objs = self._session._call_method(vim_util, "get_objects",
3360+ "Datacenter", ["vmFolder"])
3361+ # There is only one default datacenter in a standalone ESX host
3362+ vm_folder_mor = dc_objs[0].propSet[0].val
3363+
3364+ # Get the resource pool. Taking the first resource pool coming our
3365+ # way. Assuming that is the default resource pool.
3366+ res_pool_mor = self._session._call_method(vim_util, "get_objects",
3367+ "ResourcePool")[0].obj
3368+ return vm_folder_mor, res_pool_mor
3369+
3370+ vm_folder_mor, res_pool_mor = _get_vmfolder_and_res_pool_mors()
3371+
3372+ # Get the create vm config spec
3373+ config_spec = vm_util.get_vm_create_spec(client_factory, instance,
3374+ data_store_name, net_name, os_type)
3375+
3376+ def _execute_create_vm():
3377+ """Create VM on ESX host."""
3378+ LOG.debug(_("Creating VM with the name %s on the ESX host") %
3379+ instance.name)
3380+ # Create the VM on the ESX host
3381+ vm_create_task = self._session._call_method(
3382+ self._session._get_vim(),
3383+ "CreateVM_Task", vm_folder_mor,
3384+ config=config_spec, pool=res_pool_mor)
3385+ self._session._wait_for_task(instance.id, vm_create_task)
3386+
3387+ LOG.debug(_("Created VM with the name %s on the ESX host") %
3388+ instance.name)
3389+
3390+ _execute_create_vm()
3391+
3392+ # Set the machine id for the VM for setting the IP
3393+ self._set_machine_id(client_factory, instance)
3394+
3395+ # Naming the VM files in correspondence with the VM instance name
3396+ # The flat vmdk file name
3397+ flat_uploaded_vmdk_name = "%s/%s-flat.vmdk" % (instance.name,
3398+ instance.name)
3399+ # The vmdk meta-data file
3400+ uploaded_vmdk_name = "%s/%s.vmdk" % (instance.name, instance.name)
3401+ flat_uploaded_vmdk_path = vm_util.build_datastore_path(data_store_name,
3402+ flat_uploaded_vmdk_name)
3403+ uploaded_vmdk_path = vm_util.build_datastore_path(data_store_name,
3404+ uploaded_vmdk_name)
3405+
3406+ def _create_virtual_disk():
3407+ """Create a virtual disk of the size of flat vmdk file."""
3408+ # Create a Virtual Disk of the size of the flat vmdk file. This is
3409+ # done just to generate the meta-data file whose specifics
3410+ # depend on the size of the disk, thin/thick provisioning and the
3411+ # storage adapter type.
3412+ # Here we assume thick provisioning and lsiLogic for the adapter
3413+ # type
3414+ LOG.debug(_("Creating Virtual Disk of size "
3415+ "%(vmdk_file_size_in_kb)s KB and adapter type "
3416+ "%(adapter_type)s on the ESX host local store"
3417+ " %(data_store_name)s") %
3418+ {"vmdk_file_size_in_kb": vmdk_file_size_in_kb,
3419+ "adapter_type": adapter_type,
3420+ "data_store_name": data_store_name})
3421+ vmdk_create_spec = vm_util.get_vmdk_create_spec(client_factory,
3422+ vmdk_file_size_in_kb, adapter_type)
3423+ vmdk_create_task = self._session._call_method(
3424+ self._session._get_vim(),
3425+ "CreateVirtualDisk_Task",
3426+ service_content.virtualDiskManager,
3427+ name=uploaded_vmdk_path,
3428+ datacenter=self._get_datacenter_name_and_ref()[0],
3429+ spec=vmdk_create_spec)
3430+ self._session._wait_for_task(instance.id, vmdk_create_task)
3431+ LOG.debug(_("Created Virtual Disk of size %(vmdk_file_size_in_kb)s"
3432+ " KB on the ESX host local store "
3433+ "%(data_store_name)s") %
3434+ {"vmdk_file_size_in_kb": vmdk_file_size_in_kb,
3435+ "data_store_name": data_store_name})
3436+
3437+ _create_virtual_disk()
3438+
3439+ def _delete_disk_file():
3440+ LOG.debug(_("Deleting the file %(flat_uploaded_vmdk_path)s "
3441+ "on the ESX host local"
3442+ "store %(data_store_name)s") %
3443+ {"flat_uploaded_vmdk_path": flat_uploaded_vmdk_path,
3444+ "data_store_name": data_store_name})
3445+ # Delete the -flat.vmdk file created. .vmdk file is retained.
3446+ vmdk_delete_task = self._session._call_method(
3447+ self._session._get_vim(),
3448+ "DeleteDatastoreFile_Task",
3449+ service_content.fileManager,
3450+ name=flat_uploaded_vmdk_path)
3451+ self._session._wait_for_task(instance.id, vmdk_delete_task)
3452+ LOG.debug(_("Deleted the file %(flat_uploaded_vmdk_path)s on the "
3453+ "ESX host local store %(data_store_name)s") %
3454+ {"flat_uploaded_vmdk_path": flat_uploaded_vmdk_path,
3455+ "data_store_name": data_store_name})
3456+
3457+ _delete_disk_file()
3458+
3459+ cookies = self._session._get_vim().client.options.transport.cookiejar
3460+
3461+ def _fetch_image_on_esx_datastore():
3462+ """Fetch image from Glance to ESX datastore."""
3463+ LOG.debug(_("Downloading image file data %(image_id)s to the ESX "
3464+ "data store %(data_store_name)s") %
3465+ ({'image_id': instance.image_id,
3466+ 'data_store_name': data_store_name}))
3467+ # Upload the -flat.vmdk file whose meta-data file we just created
3468+ # above
3469+ vmware_images.fetch_image(
3470+ instance.image_id,
3471+ instance,
3472+ host=self._session._host_ip,
3473+ data_center_name=self._get_datacenter_name_and_ref()[1],
3474+ datastore_name=data_store_name,
3475+ cookies=cookies,
3476+ file_path=flat_uploaded_vmdk_name)
3477+ LOG.debug(_("Downloaded image file data %(image_id)s to the ESX "
3478+ "data store %(data_store_name)s") %
3479+ ({'image_id': instance.image_id,
3480+ 'data_store_name': data_store_name}))
3481+ _fetch_image_on_esx_datastore()
3482+
3483+ vm_ref = self._get_vm_ref_from_the_name(instance.name)
3484+
3485+ def _attach_vmdk_to_the_vm():
3486+ """
3487+ Attach the vmdk uploaded to the VM. VM reconfigure is done
3488+ to do so.
3489+ """
3490+ vmdk_attach_config_spec = vm_util.get_vmdk_attach_config_spec(
3491+ client_factory,
3492+ vmdk_file_size_in_kb, uploaded_vmdk_path,
3493+ adapter_type)
3494+ LOG.debug(_("Reconfiguring VM instance %s to attach the image "
3495+ "disk") % instance.name)
3496+ reconfig_task = self._session._call_method(
3497+ self._session._get_vim(),
3498+ "ReconfigVM_Task", vm_ref,
3499+ spec=vmdk_attach_config_spec)
3500+ self._session._wait_for_task(instance.id, reconfig_task)
3501+ LOG.debug(_("Reconfigured VM instance %s to attach the image "
3502+ "disk") % instance.name)
3503+
3504+ _attach_vmdk_to_the_vm()
3505+
3506+ def _power_on_vm():
3507+ """Power on the VM."""
3508+ LOG.debug(_("Powering on the VM instance %s") % instance.name)
3509+ # Power On the VM
3510+ power_on_task = self._session._call_method(
3511+ self._session._get_vim(),
3512+ "PowerOnVM_Task", vm_ref)
3513+ self._session._wait_for_task(instance.id, power_on_task)
3514+ LOG.debug(_("Powered on the VM instance %s") % instance.name)
3515+ _power_on_vm()
3516+
3517+ def snapshot(self, instance, snapshot_name):
3518+ """
3519+ Create snapshot from a running VM instance.
3520+ Steps followed are:
3521+ 1. Get the name of the vmdk file which the VM points to right now.
3522+ Can be a chain of snapshots, so we need to know the last in the
3523+ chain.
3524+ 2. Create the snapshot. A new vmdk is created which the VM points to
3525+ now. The earlier vmdk becomes read-only.
3526+ 3. Call CopyVirtualDisk which coalesces the disk chain to form a single
3527+ vmdk, rather a .vmdk metadata file and a -flat.vmdk disk data file.
3528+ 4. Now upload the -flat.vmdk file to the image store.
3529+ 5. Delete the coalesced .vmdk and -flat.vmdk created.
3530+ """
3531+ vm_ref = self._get_vm_ref_from_the_name(instance.name)
3532+ if vm_ref is None:
3533+ raise exception.NotFound(_("instance - %s not present") %
3534+ instance.name)
3535+
3536+ client_factory = self._session._get_vim().client.factory
3537+ service_content = self._session._get_vim().get_service_content()
3538+
3539+ def _get_vm_and_vmdk_attribs():
3540+ # Get the vmdk file name that the VM is pointing to
3541+ hardware_devices = self._session._call_method(vim_util,
3542+ "get_dynamic_property", vm_ref,
3543+ "VirtualMachine", "config.hardware.device")
3544+ vmdk_file_path_before_snapshot, adapter_type = \
3545+ vm_util.get_vmdk_file_path_and_adapter_type(client_factory,
3546+ hardware_devices)
3547+ datastore_name = vm_util.split_datastore_path(
3548+ vmdk_file_path_before_snapshot)[0]
3549+ os_type = self._session._call_method(vim_util,
3550+ "get_dynamic_property", vm_ref,
3551+ "VirtualMachine", "summary.config.guestId")
3552+ return (vmdk_file_path_before_snapshot, adapter_type,
3553+ datastore_name, os_type)
3554+
3555+ vmdk_file_path_before_snapshot, adapter_type, datastore_name,\
3556+ os_type = _get_vm_and_vmdk_attribs()
3557+
3558+ def _create_vm_snapshot():
3559+ # Create a snapshot of the VM
3560+ LOG.debug(_("Creating Snapshot of the VM instance %s ") %
3561+ instance.name)
3562+ snapshot_task = self._session._call_method(
3563+ self._session._get_vim(),
3564+ "CreateSnapshot_Task", vm_ref,
3565+ name="%s-snapshot" % instance.name,
3566+ description="Taking Snapshot of the VM",
3567+ memory=True,
3568+ quiesce=True)
3569+ self._session._wait_for_task(instance.id, snapshot_task)
3570+ LOG.debug(_("Created Snapshot of the VM instance %s ") %
3571+ instance.name)
3572+
3573+ _create_vm_snapshot()
3574+
3575+ def _check_if_tmp_folder_exists():
3576+ # Copy the contents of the VM that were there just before the
3577+ # snapshot was taken
3578+ ds_ref_ret = vim_util.get_dynamic_property(
3579+ self._session._get_vim(),
3580+ vm_ref,
3581+ "VirtualMachine",
3582+ "datastore")
3583+ if not ds_ref_ret:
3584+ raise exception.NotFound(_("Failed to get the datastore "
3585+ "reference(s) which the VM uses"))
3586+ ds_ref = ds_ref_ret.ManagedObjectReference[0]
3587+ ds_browser = vim_util.get_dynamic_property(
3588+ self._session._get_vim(),
3589+ ds_ref,
3590+ "Datastore",
3591+ "browser")
3592+ # Check if the vmware-tmp folder exists or not. If not, create one
3593+ tmp_folder_path = vm_util.build_datastore_path(datastore_name,
3594+ "vmware-tmp")
3595+ if not self._path_exists(ds_browser, tmp_folder_path):
3596+ self._mkdir(vm_util.build_datastore_path(datastore_name,
3597+ "vmware-tmp"))
3598+
3599+ _check_if_tmp_folder_exists()
3600+
3601+ # Generate a random vmdk file name to which the coalesced vmdk content
3602+ # will be copied to. A random name is chosen so that we don't have
3603+ # name clashes.
3604+ random_name = str(uuid.uuid4())
3605+ dest_vmdk_file_location = vm_util.build_datastore_path(datastore_name,
3606+ "vmware-tmp/%s.vmdk" % random_name)
3607+ dc_ref = self._get_datacenter_name_and_ref()[0]
3608+
3609+ def _copy_vmdk_content():
3610+ # Copy the contents of the disk ( or disks, if there were snapshots
3611+ # done earlier) to a temporary vmdk file.
3612+ copy_spec = vm_util.get_copy_virtual_disk_spec(client_factory,
3613+ adapter_type)
3614+ LOG.debug(_("Copying disk data before snapshot of the VM "
3615+ " instance %s") % instance.name)
3616+ copy_disk_task = self._session._call_method(
3617+ self._session._get_vim(),
3618+ "CopyVirtualDisk_Task",
3619+ service_content.virtualDiskManager,
3620+ sourceName=vmdk_file_path_before_snapshot,
3621+ sourceDatacenter=dc_ref,
3622+ destName=dest_vmdk_file_location,
3623+ destDatacenter=dc_ref,
3624+ destSpec=copy_spec,
3625+ force=False)
3626+ self._session._wait_for_task(instance.id, copy_disk_task)
3627+ LOG.debug(_("Copied disk data before snapshot of the VM "
3628+ "instance %s") % instance.name)
3629+
3630+ _copy_vmdk_content()
3631+
3632+ cookies = self._session._get_vim().client.options.transport.cookiejar
3633+
3634+ def _upload_vmdk_to_image_repository():
3635+ # Upload the contents of -flat.vmdk file which has the disk data.
3636+ LOG.debug(_("Uploading image %s") % snapshot_name)
3637+ vmware_images.upload_image(
3638+ snapshot_name,
3639+ instance,
3640+ os_type=os_type,
3641+ adapter_type=adapter_type,
3642+ image_version=1,
3643+ host=self._session._host_ip,
3644+ data_center_name=self._get_datacenter_name_and_ref()[1],
3645+ datastore_name=datastore_name,
3646+ cookies=cookies,
3647+ file_path="vmware-tmp/%s-flat.vmdk" % random_name)
3648+ LOG.debug(_("Uploaded image %s") % snapshot_name)
3649+
3650+ _upload_vmdk_to_image_repository()
3651+
3652+ def _clean_temp_data():
3653+ """
3654+ Delete temporary vmdk files generated in image handling
3655+ operations.
3656+ """
3657+ # Delete the temporary vmdk created above.
3658+ LOG.debug(_("Deleting temporary vmdk file %s")
3659+ % dest_vmdk_file_location)
3660+ remove_disk_task = self._session._call_method(
3661+ self._session._get_vim(),
3662+ "DeleteVirtualDisk_Task",
3663+ service_content.virtualDiskManager,
3664+ name=dest_vmdk_file_location,
3665+ datacenter=dc_ref)
3666+ self._session._wait_for_task(instance.id, remove_disk_task)
3667+ LOG.debug(_("Deleted temporary vmdk file %s")
3668+ % dest_vmdk_file_location)
3669+
3670+ _clean_temp_data()
3671+
3672+ def reboot(self, instance):
3673+ """Reboot a VM instance."""
3674+ vm_ref = self._get_vm_ref_from_the_name(instance.name)
3675+ if vm_ref is None:
3676+ raise exception.NotFound(_("instance - %s not present") %
3677+ instance.name)
3678+ lst_properties = ["summary.guest.toolsStatus", "runtime.powerState",
3679+ "summary.guest.toolsRunningStatus"]
3680+ props = self._session._call_method(vim_util, "get_object_properties",
3681+ None, vm_ref, "VirtualMachine",
3682+ lst_properties)
3683+ pwr_state = None
3684+ tools_status = None
3685+ tools_running_status = False
3686+ for elem in props:
3687+ for prop in elem.propSet:
3688+ if prop.name == "runtime.powerState":
3689+ pwr_state = prop.val
3690+ elif prop.name == "summary.guest.toolsStatus":
3691+ tools_status = prop.val
3692+ elif prop.name == "summary.guest.toolsRunningStatus":
3693+ tools_running_status = prop.val
3694+
3695+ # Raise an exception if the VM is not powered On.
3696+ if pwr_state not in ["poweredOn"]:
3697+ raise exception.Invalid(_("instance - %s not poweredOn. So can't "
3698+ "be rebooted.") % instance.name)
3699+
3700+ # If latest vmware tools are installed in the VM, and that the tools
3701+ # are running, then only do a guest reboot. Otherwise do a hard reset.
3702+ if (tools_status == "toolsOk" and
3703+ tools_running_status == "guestToolsRunning"):
3704+ LOG.debug(_("Rebooting guest OS of VM %s") % instance.name)
3705+ self._session._call_method(self._session._get_vim(), "RebootGuest",
3706+ vm_ref)
3707+ LOG.debug(_("Rebooted guest OS of VM %s") % instance.name)
3708+ else:
3709+ LOG.debug(_("Doing hard reboot of VM %s") % instance.name)
3710+ reset_task = self._session._call_method(self._session._get_vim(),
3711+ "ResetVM_Task", vm_ref)
3712+ self._session._wait_for_task(instance.id, reset_task)
3713+ LOG.debug(_("Did hard reboot of VM %s") % instance.name)
3714+
3715+ def destroy(self, instance):
3716+ """
3717+ Destroy a VM instance. Steps followed are:
3718+ 1. Power off the VM, if it is in poweredOn state.
3719+ 2. Un-register a VM.
3720+ 3. Delete the contents of the folder holding the VM related data.
3721+ """
3722+ try:
3723+ vm_ref = self._get_vm_ref_from_the_name(instance.name)
3724+ if vm_ref is None:
3725+ LOG.debug(_("instance - %s not present") % instance.name)
3726+ return
3727+ lst_properties = ["config.files.vmPathName", "runtime.powerState"]
3728+ props = self._session._call_method(vim_util,
3729+ "get_object_properties",
3730+ None, vm_ref, "VirtualMachine", lst_properties)
3731+ pwr_state = None
3732+ for elem in props:
3733+ vm_config_pathname = None
3734+ for prop in elem.propSet:
3735+ if prop.name == "runtime.powerState":
3736+ pwr_state = prop.val
3737+ elif prop.name == "config.files.vmPathName":
3738+ vm_config_pathname = prop.val
3739+ if vm_config_pathname:
3740+ datastore_name, vmx_file_path = \
3741+ vm_util.split_datastore_path(vm_config_pathname)
3742+ # Power off the VM if it is in PoweredOn state.
3743+ if pwr_state == "poweredOn":
3744+ LOG.debug(_("Powering off the VM %s") % instance.name)
3745+ poweroff_task = self._session._call_method(
3746+ self._session._get_vim(),
3747+ "PowerOffVM_Task", vm_ref)
3748+ self._session._wait_for_task(instance.id, poweroff_task)
3749+ LOG.debug(_("Powered off the VM %s") % instance.name)
3750+
3751+ # Un-register the VM
3752+ try:
3753+ LOG.debug(_("Unregistering the VM %s") % instance.name)
3754+ self._session._call_method(self._session._get_vim(),
3755+ "UnregisterVM", vm_ref)
3756+ LOG.debug(_("Unregistered the VM %s") % instance.name)
3757+ except Exception, excep:
3758+ LOG.warn(_("In vmwareapi:vmops:destroy, got this exception"
3759+ " while un-registering the VM: %s") % str(excep))
3760+
3761+ # Delete the folder holding the VM related content on
3762+ # the datastore.
3763+ try:
3764+ dir_ds_compliant_path = vm_util.build_datastore_path(
3765+ datastore_name,
3766+ os.path.dirname(vmx_file_path))
3767+ LOG.debug(_("Deleting contents of the VM %(name)s from "
3768+ "datastore %(datastore_name)s") %
3769+ ({'name': instance.name,
3770+ 'datastore_name': datastore_name}))
3771+ delete_task = self._session._call_method(
3772+ self._session._get_vim(),
3773+ "DeleteDatastoreFile_Task",
3774+ self._session._get_vim().get_service_content().fileManager,
3775+ name=dir_ds_compliant_path)
3776+ self._session._wait_for_task(instance.id, delete_task)
3777+ LOG.debug(_("Deleted contents of the VM %(name)s from "
3778+ "datastore %(datastore_name)s") %
3779+ ({'name': instance.name,
3780+ 'datastore_name': datastore_name}))
3781+ except Exception, excep:
3782+ LOG.warn(_("In vmwareapi:vmops:destroy, "
3783+ "got this exception while deleting"
3784+ " the VM contents from the disk: %s")
3785+ % str(excep))
3786+ except Exception, exc:
3787+ LOG.exception(exc)
3788+
3789+ def pause(self, instance, callback):
3790+ """Pause a VM instance."""
3791+ raise exception.APIError("pause not supported for vmwareapi")
3792+
3793+ def unpause(self, instance, callback):
3794+ """Un-Pause a VM instance."""
3795+ raise exception.APIError("unpause not supported for vmwareapi")
3796+
3797+ def suspend(self, instance, callback):
3798+ """Suspend the specified instance."""
3799+ vm_ref = self._get_vm_ref_from_the_name(instance.name)
3800+ if vm_ref is None:
3801+ raise exception.NotFound(_("instance - %s not present") %
3802+ instance.name)
3803+
3804+ pwr_state = self._session._call_method(vim_util,
3805+ "get_dynamic_property", vm_ref,
3806+ "VirtualMachine", "runtime.powerState")
3807+ # Only PoweredOn VMs can be suspended.
3808+ if pwr_state == "poweredOn":
3809+ LOG.debug(_("Suspending the VM %s ") % instance.name)
3810+ suspend_task = self._session._call_method(self._session._get_vim(),
3811+ "SuspendVM_Task", vm_ref)
3812+ self._wait_with_callback(instance.id, suspend_task, callback)
3813+ LOG.debug(_("Suspended the VM %s ") % instance.name)
3814+ # Raise Exception if VM is poweredOff
3815+ elif pwr_state == "poweredOff":
3816+ raise exception.Invalid(_("instance - %s is poweredOff and hence "
3817+ " can't be suspended.") % instance.name)
3818+ LOG.debug(_("VM %s was already in suspended state. So returning "
3819+ "without doing anything") % instance.name)
3820+
3821+ def resume(self, instance, callback):
3822+ """Resume the specified instance."""
3823+ vm_ref = self._get_vm_ref_from_the_name(instance.name)
3824+ if vm_ref is None:
3825+ raise exception.NotFound(_("instance - %s not present") %
3826+ instance.name)
3827+
3828+ pwr_state = self._session._call_method(vim_util,
3829+ "get_dynamic_property", vm_ref,
3830+ "VirtualMachine", "runtime.powerState")
3831+ if pwr_state.lower() == "suspended":
3832+ LOG.debug(_("Resuming the VM %s") % instance.name)
3833+ suspend_task = self._session._call_method(
3834+ self._session._get_vim(),
3835+ "PowerOnVM_Task", vm_ref)
3836+ self._wait_with_callback(instance.id, suspend_task, callback)
3837+ LOG.debug(_("Resumed the VM %s ") % instance.name)
3838+ else:
3839+ raise exception.Invalid(_("instance - %s not in Suspended state "
3840+ "and hence can't be Resumed.") % instance.name)
3841+
3842+ def get_info(self, instance_name):
3843+ """Return data about the VM instance."""
3844+ vm_ref = self._get_vm_ref_from_the_name(instance_name)
3845+ if vm_ref is None:
3846+ raise exception.NotFound(_("instance - %s not present") %
3847+ instance_name)
3848+
3849+ lst_properties = ["summary.config.numCpu",
3850+ "summary.config.memorySizeMB",
3851+ "runtime.powerState"]
3852+ vm_props = self._session._call_method(vim_util,
3853+ "get_object_properties", None, vm_ref, "VirtualMachine",
3854+ lst_properties)
3855+ max_mem = None
3856+ pwr_state = None
3857+ num_cpu = None
3858+ for elem in vm_props:
3859+ for prop in elem.propSet:
3860+ if prop.name == "summary.config.numCpu":
3861+ num_cpu = int(prop.val)
3862+ elif prop.name == "summary.config.memorySizeMB":
3863+ # In MB, but we want in KB
3864+ max_mem = int(prop.val) * 1024
3865+ elif prop.name == "runtime.powerState":
3866+ pwr_state = VMWARE_POWER_STATES[prop.val]
3867+
3868+ return {'state': pwr_state,
3869+ 'max_mem': max_mem,
3870+ 'mem': max_mem,
3871+ 'num_cpu': num_cpu,
3872+ 'cpu_time': 0}
3873+
3874+ def get_diagnostics(self, instance):
3875+ """Return data about VM diagnostics."""
3876+ raise exception.APIError("get_diagnostics not implemented for "
3877+ "vmwareapi")
3878+
3879+ def get_console_output(self, instance):
3880+ """Return snapshot of console."""
3881+ vm_ref = self._get_vm_ref_from_the_name(instance.name)
3882+ if vm_ref is None:
3883+ raise exception.NotFound(_("instance - %s not present") %
3884+ instance.name)
3885+ param_list = {"id": str(vm_ref)}
3886+ base_url = "%s://%s/screen?%s" % (self._session._scheme,
3887+ self._session._host_ip,
3888+ urllib.urlencode(param_list))
3889+ request = urllib2.Request(base_url)
3890+ base64string = base64.encodestring(
3891+ '%s:%s' % (
3892+ self._session._host_username,
3893+ self._session._host_password)).replace('\n', '')
3894+ request.add_header("Authorization", "Basic %s" % base64string)
3895+ result = urllib2.urlopen(request)
3896+ if result.code == 200:
3897+ return result.read()
3898+ else:
3899+ return ""
3900+
3901+ def get_ajax_console(self, instance):
3902+ """Return link to instance's ajax console."""
3903+ return 'http://fakeajaxconsole/fake_url'
3904+
3905+ def _set_machine_id(self, client_factory, instance):
3906+ """
3907+ Set the machine id of the VM for guest tools to pick up and change
3908+ the IP.
3909+ """
3910+ vm_ref = self._get_vm_ref_from_the_name(instance.name)
3911+ if vm_ref is None:
3912+ raise exception.NotFound(_("instance - %s not present") %
3913+ instance.name)
3914+ network = db.network_get_by_instance(context.get_admin_context(),
3915+ instance['id'])
3916+ mac_addr = instance.mac_address
3917+ net_mask = network["netmask"]
3918+ gateway = network["gateway"]
3919+ ip_addr = db.instance_get_fixed_address(context.get_admin_context(),
3920+ instance['id'])
3921+ machine_id_chanfge_spec = \
3922+ vm_util.get_machine_id_change_spec(client_factory, mac_addr,
3923+ ip_addr, net_mask, gateway)
3924+ LOG.debug(_("Reconfiguring VM instance %(name)s to set the machine id "
3925+ "with ip - %(ip_addr)s") %
3926+ ({'name': instance.name,
3927+ 'ip_addr': ip_addr}))
3928+ reconfig_task = self._session._call_method(self._session._get_vim(),
3929+ "ReconfigVM_Task", vm_ref,
3930+ spec=machine_id_chanfge_spec)
3931+ self._session._wait_for_task(instance.id, reconfig_task)
3932+ LOG.debug(_("Reconfigured VM instance %(name)s to set the machine id "
3933+ "with ip - %(ip_addr)s") %
3934+ ({'name': instance.name,
3935+ 'ip_addr': ip_addr}))
3936+
3937+ def _get_datacenter_name_and_ref(self):
3938+ """Get the datacenter name and the reference."""
3939+ dc_obj = self._session._call_method(vim_util, "get_objects",
3940+ "Datacenter", ["name"])
3941+ return dc_obj[0].obj, dc_obj[0].propSet[0].val
3942+
3943+ def _path_exists(self, ds_browser, ds_path):
3944+ """Check if the path exists on the datastore."""
3945+ search_task = self._session._call_method(self._session._get_vim(),
3946+ "SearchDatastore_Task",
3947+ ds_browser,
3948+ datastorePath=ds_path)
3949+ # Wait till the state changes from queued or running.
3950+ # If an error state is returned, it means that the path doesn't exist.
3951+ while True:
3952+ task_info = self._session._call_method(vim_util,
3953+ "get_dynamic_property",
3954+ search_task, "Task", "info")
3955+ if task_info.state in ['queued', 'running']:
3956+ time.sleep(2)
3957+ continue
3958+ break
3959+ if task_info.state == "error":
3960+ return False
3961+ return True
3962+
3963+ def _mkdir(self, ds_path):
3964+ """
3965+ Creates a directory at the path specified. If it is just "NAME",
3966+ then a directory with this name is created at the topmost level of the
3967+ DataStore.
3968+ """
3969+ LOG.debug(_("Creating directory with path %s") % ds_path)
3970+ self._session._call_method(self._session._get_vim(), "MakeDirectory",
3971+ self._session._get_vim().get_service_content().fileManager,
3972+ name=ds_path, createParentDirectories=False)
3973+ LOG.debug(_("Created directory with path %s") % ds_path)
3974+
3975+ def _get_vm_ref_from_the_name(self, vm_name):
3976+ """Get reference to the VM with the name specified."""
3977+ vms = self._session._call_method(vim_util, "get_objects",
3978+ "VirtualMachine", ["name"])
3979+ for vm in vms:
3980+ if vm.propSet[0].val == vm_name:
3981+ return vm.obj
3982+ return None
3983
3984=== added file 'nova/virt/vmwareapi/vmware_images.py'
3985--- nova/virt/vmwareapi/vmware_images.py 1970-01-01 00:00:00 +0000
3986+++ nova/virt/vmwareapi/vmware_images.py 2011-03-24 16:38:31 +0000
3987@@ -0,0 +1,201 @@
3988+# vim: tabstop=4 shiftwidth=4 softtabstop=4
3989+
3990+# Copyright (c) 2011 Citrix Systems, Inc.
3991+# Copyright 2011 OpenStack LLC.
3992+#
3993+# Licensed under the Apache License, Version 2.0 (the "License"); you may
3994+# not use this file except in compliance with the License. You may obtain
3995+# a copy of the License at
3996+#
3997+# http://www.apache.org/licenses/LICENSE-2.0
3998+#
3999+# Unless required by applicable law or agreed to in writing, software
4000+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
4001+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
4002+# License for the specific language governing permissions and limitations
4003+# under the License.
4004+"""
4005+Utility functions for Image transfer.
4006+"""
4007+
4008+from glance import client
4009+
4010+from nova import exception
4011+from nova import flags
4012+from nova import log as logging
4013+from nova.virt.vmwareapi import io_util
4014+from nova.virt.vmwareapi import read_write_util
4015+
4016+LOG = logging.getLogger("nova.virt.vmwareapi.vmware_images")
4017+
4018+FLAGS = flags.FLAGS
4019+
4020+QUEUE_BUFFER_SIZE = 10
4021+
4022+
4023+def start_transfer(read_file_handle, data_size, write_file_handle=None,
4024+ glance_client=None, image_id=None, image_meta={}):
4025+ """Start the data transfer from the reader to the writer.
4026+ Reader writes to the pipe and the writer reads from the pipe. This means
4027+ that the total transfer time boils down to the slower of the read/write
4028+ and not the addition of the two times."""
4029+ # The pipe that acts as an intermediate store of data for reader to write
4030+ # to and writer to grab from.
4031+ thread_safe_pipe = io_util.ThreadSafePipe(QUEUE_BUFFER_SIZE, data_size)
4032+ # The read thread. In case of glance it is the instance of the
4033+ # GlanceFileRead class. The glance client read returns an iterator
4034+ # and this class wraps that iterator to provide datachunks in calls
4035+ # to read.
4036+ read_thread = io_util.IOThread(read_file_handle, thread_safe_pipe)
4037+
4038+ # In case of Glance - VMWare transfer, we just need a handle to the
4039+ # HTTP Connection that is to send transfer data to the VMWare datastore.
4040+ if write_file_handle:
4041+ write_thread = io_util.IOThread(thread_safe_pipe, write_file_handle)
4042+ # In case of VMWare - Glance transfer, we relinquish VMWare HTTP file read
4043+ # handle to Glance Client instance, but to be sure of the transfer we need
4044+ # to be sure of the status of the image on glnace changing to active.
4045+ # The GlanceWriteThread handles the same for us.
4046+ elif glance_client and image_id:
4047+ write_thread = io_util.GlanceWriteThread(thread_safe_pipe,
4048+ glance_client, image_id, image_meta)
4049+ # Start the read and write threads.
4050+ read_event = read_thread.start()
4051+ write_event = write_thread.start()
4052+ try:
4053+ # Wait on the read and write events to signal their end
4054+ read_event.wait()
4055+ write_event.wait()
4056+ except Exception, exc:
4057+ # In case of any of the reads or writes raising an exception,
4058+ # stop the threads so that we un-necessarily don't keep the other one
4059+ # waiting.
4060+ read_thread.stop()
4061+ write_thread.stop()
4062+
4063+ # Log and raise the exception.
4064+ LOG.exception(exc)
4065+ raise exception.Error(exc)
4066+ finally:
4067+ # No matter what, try closing the read and write handles, if it so
4068+ # applies.
4069+ read_file_handle.close()
4070+ if write_file_handle:
4071+ write_file_handle.close()
4072+
4073+
4074+def fetch_image(image, instance, **kwargs):
4075+ """Fetch an image for attaching to the newly created VM."""
4076+ # Depending upon the image service, make appropriate image service call
4077+ if FLAGS.image_service == "nova.image.glance.GlanceImageService":
4078+ func = _get_glance_image
4079+ elif FLAGS.image_service == "nova.image.s3.S3ImageService":
4080+ func = _get_s3_image
4081+ elif FLAGS.image_service == "nova.image.local.LocalImageService":
4082+ func = _get_local_image
4083+ else:
4084+ raise NotImplementedError(_("The Image Service %s is not implemented")
4085+ % FLAGS.image_service)
4086+ return func(image, instance, **kwargs)
4087+
4088+
4089+def upload_image(image, instance, **kwargs):
4090+ """Upload the newly snapshotted VM disk file."""
4091+ # Depending upon the image service, make appropriate image service call
4092+ if FLAGS.image_service == "nova.image.glance.GlanceImageService":
4093+ func = _put_glance_image
4094+ elif FLAGS.image_service == "nova.image.s3.S3ImageService":
4095+ func = _put_s3_image
4096+ elif FLAGS.image_service == "nova.image.local.LocalImageService":
4097+ func = _put_local_image
4098+ else:
4099+ raise NotImplementedError(_("The Image Service %s is not implemented")
4100+ % FLAGS.image_service)
4101+ return func(image, instance, **kwargs)
4102+
4103+
4104+def _get_glance_image(image, instance, **kwargs):
4105+ """Download image from the glance image server."""
4106+ LOG.debug(_("Downloading image %s from glance image server") % image)
4107+ glance_client = client.Client(FLAGS.glance_host, FLAGS.glance_port)
4108+ metadata, read_iter = glance_client.get_image(image)
4109+ read_file_handle = read_write_util.GlanceFileRead(read_iter)
4110+ file_size = int(metadata['size'])
4111+ write_file_handle = read_write_util.VMWareHTTPWriteFile(
4112+ kwargs.get("host"),
4113+ kwargs.get("data_center_name"),
4114+ kwargs.get("datastore_name"),
4115+ kwargs.get("cookies"),
4116+ kwargs.get("file_path"),
4117+ file_size)
4118+ start_transfer(read_file_handle, file_size,
4119+ write_file_handle=write_file_handle)
4120+ LOG.debug(_("Downloaded image %s from glance image server") % image)
4121+
4122+
4123+def _get_s3_image(image, instance, **kwargs):
4124+ """Download image from the S3 image server."""
4125+ raise NotImplementedError
4126+
4127+
4128+def _get_local_image(image, instance, **kwargs):
4129+ """Download image from the local nova compute node."""
4130+ raise NotImplementedError
4131+
4132+
4133+def _put_glance_image(image, instance, **kwargs):
4134+ """Upload the snapshotted vm disk file to Glance image server."""
4135+ LOG.debug(_("Uploading image %s to the Glance image server") % image)
4136+ read_file_handle = read_write_util.VmWareHTTPReadFile(
4137+ kwargs.get("host"),
4138+ kwargs.get("data_center_name"),
4139+ kwargs.get("datastore_name"),
4140+ kwargs.get("cookies"),
4141+ kwargs.get("file_path"))
4142+ file_size = read_file_handle.get_size()
4143+ glance_client = client.Client(FLAGS.glance_host, FLAGS.glance_port)
4144+ # The properties and other fields that we need to set for the image.
4145+ image_metadata = {"is_public": True,
4146+ "disk_format": "vmdk",
4147+ "container_format": "bare",
4148+ "type": "vmdk",
4149+ "properties": {"vmware_adaptertype":
4150+ kwargs.get("adapter_type"),
4151+ "vmware_ostype": kwargs.get("os_type"),
4152+ "vmware_image_version":
4153+ kwargs.get("image_version")}}
4154+ start_transfer(read_file_handle, file_size, glance_client=glance_client,
4155+ image_id=image, image_meta=image_metadata)
4156+ LOG.debug(_("Uploaded image %s to the Glance image server") % image)
4157+
4158+
4159+def _put_local_image(image, instance, **kwargs):
4160+ """Upload the snapshotted vm disk file to the local nova compute node."""
4161+ raise NotImplementedError
4162+
4163+
4164+def _put_s3_image(image, instance, **kwargs):
4165+ """Upload the snapshotted vm disk file to S3 image server."""
4166+ raise NotImplementedError
4167+
4168+
4169+def get_vmdk_size_and_properties(image, instance):
4170+ """
4171+ Get size of the vmdk file that is to be downloaded for attach in spawn.
4172+ Need this to create the dummy virtual disk for the meta-data file. The
4173+ geometry of the disk created depends on the size.
4174+ """
4175+
4176+ LOG.debug(_("Getting image size for the image %s") % image)
4177+ if FLAGS.image_service == "nova.image.glance.GlanceImageService":
4178+ glance_client = client.Client(FLAGS.glance_host,
4179+ FLAGS.glance_port)
4180+ meta_data = glance_client.get_image_meta(image)
4181+ size, properties = meta_data["size"], meta_data["properties"]
4182+ elif FLAGS.image_service == "nova.image.s3.S3ImageService":
4183+ raise NotImplementedError
4184+ elif FLAGS.image_service == "nova.image.local.LocalImageService":
4185+ raise NotImplementedError
4186+ LOG.debug(_("Got image size of %(size)s for the image %(image)s") %
4187+ locals())
4188+ return size, properties
4189
4190=== added file 'nova/virt/vmwareapi_conn.py'
4191--- nova/virt/vmwareapi_conn.py 1970-01-01 00:00:00 +0000
4192+++ nova/virt/vmwareapi_conn.py 2011-03-24 16:38:31 +0000
4193@@ -0,0 +1,375 @@
4194+# vim: tabstop=4 shiftwidth=4 softtabstop=4
4195+
4196+# Copyright (c) 2011 Citrix Systems, Inc.
4197+# Copyright 2011 OpenStack LLC.
4198+#
4199+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4200+# not use this file except in compliance with the License. You may obtain
4201+# a copy of the License at
4202+#
4203+# http://www.apache.org/licenses/LICENSE-2.0
4204+#
4205+# Unless required by applicable law or agreed to in writing, software
4206+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
4207+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
4208+# License for the specific language governing permissions and limitations
4209+# under the License.
4210+
4211+"""
4212+A connection to the VMware ESX platform.
4213+
4214+**Related Flags**
4215+
4216+:vmwareapi_host_ip: IPAddress of VMware ESX server.
4217+:vmwareapi_host_username: Username for connection to VMware ESX Server.
4218+:vmwareapi_host_password: Password for connection to VMware ESX Server.
4219+:vmwareapi_task_poll_interval: The interval (seconds) used for polling of
4220+ remote tasks
4221+ (default: 1.0).
4222+:vmwareapi_api_retry_count: The API retry count in case of failure such as
4223+ network failures (socket errors etc.)
4224+ (default: 10).
4225+
4226+"""
4227+
4228+import time
4229+
4230+from eventlet import event
4231+
4232+from nova import context
4233+from nova import db
4234+from nova import exception
4235+from nova import flags
4236+from nova import log as logging
4237+from nova import utils
4238+from nova.virt.vmwareapi import error_util
4239+from nova.virt.vmwareapi import vim
4240+from nova.virt.vmwareapi import vim_util
4241+from nova.virt.vmwareapi.vmops import VMWareVMOps
4242+
4243+LOG = logging.getLogger("nova.virt.vmwareapi_conn")
4244+
4245+FLAGS = flags.FLAGS
4246+flags.DEFINE_string('vmwareapi_host_ip',
4247+ None,
4248+ 'URL for connection to VMWare ESX host.'
4249+ 'Required if connection_type is vmwareapi.')
4250+flags.DEFINE_string('vmwareapi_host_username',
4251+ None,
4252+ 'Username for connection to VMWare ESX host.'
4253+ 'Used only if connection_type is vmwareapi.')
4254+flags.DEFINE_string('vmwareapi_host_password',
4255+ None,
4256+ 'Password for connection to VMWare ESX host.'
4257+ 'Used only if connection_type is vmwareapi.')
4258+flags.DEFINE_float('vmwareapi_task_poll_interval',
4259+ 5.0,
4260+ 'The interval used for polling of remote tasks '
4261+ 'Used only if connection_type is vmwareapi')
4262+flags.DEFINE_float('vmwareapi_api_retry_count',
4263+ 10,
4264+ 'The number of times we retry on failures, '
4265+ 'e.g., socket error, etc.'
4266+ 'Used only if connection_type is vmwareapi')
4267+flags.DEFINE_string('vmwareapi_vlan_interface',
4268+ 'vmnic0',
4269+ 'Physical ethernet adapter name for vlan networking')
4270+
4271+TIME_BETWEEN_API_CALL_RETRIES = 2.0
4272+
4273+
4274+class Failure(Exception):
4275+ """Base Exception class for handling task failures."""
4276+
4277+ def __init__(self, details):
4278+ self.details = details
4279+
4280+ def __str__(self):
4281+ return str(self.details)
4282+
4283+
4284+def get_connection(_):
4285+ """Sets up the ESX host connection."""
4286+ host_ip = FLAGS.vmwareapi_host_ip
4287+ host_username = FLAGS.vmwareapi_host_username
4288+ host_password = FLAGS.vmwareapi_host_password
4289+ api_retry_count = FLAGS.vmwareapi_api_retry_count
4290+ if not host_ip or host_username is None or host_password is None:
4291+ raise Exception(_("Must specify vmwareapi_host_ip,"
4292+ "vmwareapi_host_username "
4293+ "and vmwareapi_host_password to use"
4294+ "connection_type=vmwareapi"))
4295+ return VMWareESXConnection(host_ip, host_username, host_password,
4296+ api_retry_count)
4297+
4298+
4299+class VMWareESXConnection(object):
4300+ """The ESX host connection object."""
4301+
4302+ def __init__(self, host_ip, host_username, host_password,
4303+ api_retry_count, scheme="https"):
4304+ session = VMWareAPISession(host_ip, host_username, host_password,
4305+ api_retry_count, scheme=scheme)
4306+ self._vmops = VMWareVMOps(session)
4307+
4308+ def init_host(self, host):
4309+ """Do the initialization that needs to be done."""
4310+ # FIXME(sateesh): implement this
4311+ pass
4312+
4313+ def list_instances(self):
4314+ """List VM instances."""
4315+ return self._vmops.list_instances()
4316+
4317+ def spawn(self, instance):
4318+ """Create VM instance."""
4319+ self._vmops.spawn(instance)
4320+
4321+ def snapshot(self, instance, name):
4322+ """Create snapshot from a running VM instance."""
4323+ self._vmops.snapshot(instance, name)
4324+
4325+ def reboot(self, instance):
4326+ """Reboot VM instance."""
4327+ self._vmops.reboot(instance)
4328+
4329+ def destroy(self, instance):
4330+ """Destroy VM instance."""
4331+ self._vmops.destroy(instance)
4332+
4333+ def pause(self, instance, callback):
4334+ """Pause VM instance."""
4335+ self._vmops.pause(instance, callback)
4336+
4337+ def unpause(self, instance, callback):
4338+ """Unpause paused VM instance."""
4339+ self._vmops.unpause(instance, callback)
4340+
4341+ def suspend(self, instance, callback):
4342+ """Suspend the specified instance."""
4343+ self._vmops.suspend(instance, callback)
4344+
4345+ def resume(self, instance, callback):
4346+ """Resume the suspended VM instance."""
4347+ self._vmops.resume(instance, callback)
4348+
4349+ def get_info(self, instance_id):
4350+ """Return info about the VM instance."""
4351+ return self._vmops.get_info(instance_id)
4352+
4353+ def get_diagnostics(self, instance):
4354+ """Return data about VM diagnostics."""
4355+ return self._vmops.get_info(instance)
4356+
4357+ def get_console_output(self, instance):
4358+ """Return snapshot of console."""
4359+ return self._vmops.get_console_output(instance)
4360+
4361+ def get_ajax_console(self, instance):
4362+ """Return link to instance's ajax console."""
4363+ return self._vmops.get_ajax_console(instance)
4364+
4365+ def attach_volume(self, instance_name, device_path, mountpoint):
4366+ """Attach volume storage to VM instance."""
4367+ pass
4368+
4369+ def detach_volume(self, instance_name, mountpoint):
4370+ """Detach volume storage to VM instance."""
4371+ pass
4372+
4373+ def get_console_pool_info(self, console_type):
4374+ """Get info about the host on which the VM resides."""
4375+ return {'address': FLAGS.vmwareapi_host_ip,
4376+ 'username': FLAGS.vmwareapi_host_username,
4377+ 'password': FLAGS.vmwareapi_host_password}
4378+
4379+ def update_available_resource(self, ctxt, host):
4380+ """This method is supported only by libvirt."""
4381+ return
4382+
4383+
4384+class VMWareAPISession(object):
4385+ """
4386+ Sets up a session with the ESX host and handles all
4387+ the calls made to the host.
4388+ """
4389+
4390+ def __init__(self, host_ip, host_username, host_password,
4391+ api_retry_count, scheme="https"):
4392+ self._host_ip = host_ip
4393+ self._host_username = host_username
4394+ self._host_password = host_password
4395+ self.api_retry_count = api_retry_count
4396+ self._scheme = scheme
4397+ self._session_id = None
4398+ self.vim = None
4399+ self._create_session()
4400+
4401+ def _get_vim_object(self):
4402+ """Create the VIM Object instance."""
4403+ return vim.Vim(protocol=self._scheme, host=self._host_ip)
4404+
4405+ def _create_session(self):
4406+ """Creates a session with the ESX host."""
4407+ while True:
4408+ try:
4409+ # Login and setup the session with the ESX host for making
4410+ # API calls
4411+ self.vim = self._get_vim_object()
4412+ session = self.vim.Login(
4413+ self.vim.get_service_content().sessionManager,
4414+ userName=self._host_username,
4415+ password=self._host_password)
4416+ # Terminate the earlier session, if possible ( For the sake of
4417+ # preserving sessions as there is a limit to the number of
4418+ # sessions we can have )
4419+ if self._session_id:
4420+ try:
4421+ self.vim.TerminateSession(
4422+ self.vim.get_service_content().sessionManager,
4423+ sessionId=[self._session_id])
4424+ except Exception, excep:
4425+ # This exception is something we can live with. It is
4426+ # just an extra caution on our side. The session may
4427+ # have been cleared. We could have made a call to
4428+ # SessionIsActive, but that is an overhead because we
4429+ # anyway would have to call TerminateSession.
4430+ LOG.debug(excep)
4431+ self._session_id = session.key
4432+ return
4433+ except Exception, excep:
4434+ LOG.critical(_("In vmwareapi:_create_session, "
4435+ "got this exception: %s") % excep)
4436+ raise exception.Error(excep)
4437+
4438+ def __del__(self):
4439+ """Logs-out the session."""
4440+ # Logout to avoid un-necessary increase in session count at the
4441+ # ESX host
4442+ try:
4443+ self.vim.Logout(self.vim.get_service_content().sessionManager)
4444+ except Exception, excep:
4445+ # It is just cautionary on our part to do a logout in del just
4446+ # to ensure that the session is not left active.
4447+ LOG.debug(excep)
4448+
4449+ def _is_vim_object(self, module):
4450+ """Check if the module is a VIM Object instance."""
4451+ return isinstance(module, vim.Vim)
4452+
4453+ def _call_method(self, module, method, *args, **kwargs):
4454+ """
4455+ Calls a method within the module specified with
4456+ args provided.
4457+ """
4458+ args = list(args)
4459+ retry_count = 0
4460+ exc = None
4461+ last_fault_list = []
4462+ while True:
4463+ try:
4464+ if not self._is_vim_object(module):
4465+ # If it is not the first try, then get the latest
4466+ # vim object
4467+ if retry_count > 0:
4468+ args = args[1:]
4469+ args = [self.vim] + args
4470+ retry_count += 1
4471+ temp_module = module
4472+
4473+ for method_elem in method.split("."):
4474+ temp_module = getattr(temp_module, method_elem)
4475+
4476+ return temp_module(*args, **kwargs)
4477+ except error_util.VimFaultException, excep:
4478+ # If it is a Session Fault Exception, it may point
4479+ # to a session gone bad. So we try re-creating a session
4480+ # and then proceeding ahead with the call.
4481+ exc = excep
4482+ if error_util.FAULT_NOT_AUTHENTICATED in excep.fault_list:
4483+ # Because of the idle session returning an empty
4484+ # RetrievePropertiesResponse and also the same is returned
4485+ # when there is say empty answer to the query for
4486+ # VMs on the host ( as in no VMs on the host), we have no
4487+ # way to differentiate.
4488+ # So if the previous response was also am empty response
4489+ # and after creating a new session, we get the same empty
4490+ # response, then we are sure of the response being supposed
4491+ # to be empty.
4492+ if error_util.FAULT_NOT_AUTHENTICATED in last_fault_list:
4493+ return []
4494+ last_fault_list = excep.fault_list
4495+ self._create_session()
4496+ else:
4497+ # No re-trying for errors for API call has gone through
4498+ # and is the caller's fault. Caller should handle these
4499+ # errors. e.g, InvalidArgument fault.
4500+ break
4501+ except error_util.SessionOverLoadException, excep:
4502+ # For exceptions which may come because of session overload,
4503+ # we retry
4504+ exc = excep
4505+ except Exception, excep:
4506+ # If it is a proper exception, say not having furnished
4507+ # proper data in the SOAP call or the retry limit having
4508+ # exceeded, we raise the exception
4509+ exc = excep
4510+ break
4511+ # If retry count has been reached then break and
4512+ # raise the exception
4513+ if retry_count > self.api_retry_count:
4514+ break
4515+ time.sleep(TIME_BETWEEN_API_CALL_RETRIES)
4516+
4517+ LOG.critical(_("In vmwareapi:_call_method, "
4518+ "got this exception: %s") % exc)
4519+ raise
4520+
4521+ def _get_vim(self):
4522+ """Gets the VIM object reference."""
4523+ if self.vim is None:
4524+ self._create_session()
4525+ return self.vim
4526+
4527+ def _wait_for_task(self, instance_id, task_ref):
4528+ """
4529+ Return a Deferred that will give the result of the given task.
4530+ The task is polled until it completes.
4531+ """
4532+ done = event.Event()
4533+ loop = utils.LoopingCall(self._poll_task, instance_id, task_ref,
4534+ done)
4535+ loop.start(FLAGS.vmwareapi_task_poll_interval, now=True)
4536+ ret_val = done.wait()
4537+ loop.stop()
4538+ return ret_val
4539+
4540+ def _poll_task(self, instance_id, task_ref, done):
4541+ """
4542+ Poll the given task, and fires the given Deferred if we
4543+ get a result.
4544+ """
4545+ try:
4546+ task_info = self._call_method(vim_util, "get_dynamic_property",
4547+ task_ref, "Task", "info")
4548+ task_name = task_info.name
4549+ action = dict(
4550+ instance_id=int(instance_id),
4551+ action=task_name[0:255],
4552+ error=None)
4553+ if task_info.state in ['queued', 'running']:
4554+ return
4555+ elif task_info.state == 'success':
4556+ LOG.debug(_("Task [%(task_name)s] %(task_ref)s "
4557+ "status: success") % locals())
4558+ done.send("success")
4559+ else:
4560+ error_info = str(task_info.error.localizedMessage)
4561+ action["error"] = error_info
4562+ LOG.warn(_("Task [%(task_name)s] %(task_ref)s "
4563+ "status: error %(error_info)s") % locals())
4564+ done.send_exception(exception.Error(error_info))
4565+ db.instance_action_create(context.get_admin_context(), action)
4566+ except Exception, excep:
4567+ LOG.warn(_("In vmwareapi:_poll_task, Got this error %s") % excep)
4568+ done.send_exception(excep)
4569
4570=== added directory 'tools/esx'
4571=== added file 'tools/esx/guest_tool.py'
4572--- tools/esx/guest_tool.py 1970-01-01 00:00:00 +0000
4573+++ tools/esx/guest_tool.py 2011-03-24 16:38:31 +0000
4574@@ -0,0 +1,345 @@
4575+# vim: tabstop=4 shiftwidth=4 softtabstop=4
4576+
4577+# Copyright (c) 2011 Citrix Systems, Inc.
4578+# Copyright 2011 OpenStack LLC.
4579+#
4580+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4581+# not use this file except in compliance with the License. You may obtain
4582+# a copy of the License at
4583+#
4584+# http://www.apache.org/licenses/LICENSE-2.0
4585+#
4586+# Unless required by applicable law or agreed to in writing, software
4587+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
4588+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
4589+# License for the specific language governing permissions and limitations
4590+# under the License.
4591+
4592+"""
4593+Guest tools for ESX to set up network in the guest.
4594+On Windows we require pyWin32 installed on Python.
4595+"""
4596+
4597+import array
4598+import logging
4599+import os
4600+import platform
4601+import socket
4602+import struct
4603+import subprocess
4604+import sys
4605+import time
4606+
4607+PLATFORM_WIN = 'win32'
4608+PLATFORM_LINUX = 'linux2'
4609+ARCH_32_BIT = '32bit'
4610+ARCH_64_BIT = '64bit'
4611+NO_MACHINE_ID = 'No machine id'
4612+
4613+# Logging
4614+FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
4615+if sys.platform == PLATFORM_WIN:
4616+ LOG_DIR = os.path.join(os.environ.get('ALLUSERSPROFILE'), 'openstack')
4617+elif sys.platform == PLATFORM_LINUX:
4618+ LOG_DIR = '/var/log/openstack'
4619+else:
4620+ LOG_DIR = 'logs'
4621+if not os.path.exists(LOG_DIR):
4622+ os.mkdir(LOG_DIR)
4623+LOG_FILENAME = os.path.join(LOG_DIR, 'openstack-guest-tools.log')
4624+logging.basicConfig(filename=LOG_FILENAME, format=FORMAT)
4625+
4626+if sys.hexversion < 0x3000000:
4627+ _byte = ord # 2.x chr to integer
4628+else:
4629+ _byte = int # 3.x byte to integer
4630+
4631+
4632+class ProcessExecutionError:
4633+ """Process Execution Error Class."""
4634+
4635+ def __init__(self, exit_code, stdout, stderr, cmd):
4636+ self.exit_code = exit_code
4637+ self.stdout = stdout
4638+ self.stderr = stderr
4639+ self.cmd = cmd
4640+
4641+ def __str__(self):
4642+ return str(self.exit_code)
4643+
4644+
4645+def _bytes2int(bytes):
4646+ """Convert bytes to int."""
4647+ intgr = 0
4648+ for byt in bytes:
4649+ intgr = (intgr << 8) + _byte(byt)
4650+ return intgr
4651+
4652+
4653+def _parse_network_details(machine_id):
4654+ """
4655+ Parse the machine.id field to get MAC, IP, Netmask and Gateway fields
4656+ machine.id is of the form MAC;IP;Netmask;Gateway;Broadcast;DNS1,DNS2
4657+ where ';' is the separator.
4658+ """
4659+ network_details = []
4660+ if machine_id[1].strip() == "1":
4661+ pass
4662+ else:
4663+ network_info_list = machine_id[0].split(';')
4664+ assert len(network_info_list) % 6 == 0
4665+ no_grps = len(network_info_list) / 6
4666+ i = 0
4667+ while i < no_grps:
4668+ k = i * 6
4669+ network_details.append((
4670+ network_info_list[k].strip().lower(),
4671+ network_info_list[k + 1].strip(),
4672+ network_info_list[k + 2].strip(),
4673+ network_info_list[k + 3].strip(),
4674+ network_info_list[k + 4].strip(),
4675+ network_info_list[k + 5].strip().split(',')))
4676+ i += 1
4677+ return network_details
4678+
4679+
4680+def _get_windows_network_adapters():
4681+ """Get the list of windows network adapters."""
4682+ import win32com.client
4683+ wbem_locator = win32com.client.Dispatch('WbemScripting.SWbemLocator')
4684+ wbem_service = wbem_locator.ConnectServer('.', 'root\cimv2')
4685+ wbem_network_adapters = wbem_service.InstancesOf('Win32_NetworkAdapter')
4686+ network_adapters = []
4687+ for wbem_network_adapter in wbem_network_adapters:
4688+ if wbem_network_adapter.NetConnectionStatus == 2 or \
4689+ wbem_network_adapter.NetConnectionStatus == 7:
4690+ adapter_name = wbem_network_adapter.NetConnectionID
4691+ mac_address = wbem_network_adapter.MacAddress.lower()
4692+ wbem_network_adapter_config = \
4693+ wbem_network_adapter.associators_(
4694+ 'Win32_NetworkAdapterSetting',
4695+ 'Win32_NetworkAdapterConfiguration')[0]
4696+ ip_address = ''
4697+ subnet_mask = ''
4698+ if wbem_network_adapter_config.IPEnabled:
4699+ ip_address = wbem_network_adapter_config.IPAddress[0]
4700+ subnet_mask = wbem_network_adapter_config.IPSubnet[0]
4701+ #wbem_network_adapter_config.DefaultIPGateway[0]
4702+ network_adapters.append({'name': adapter_name,
4703+ 'mac-address': mac_address,
4704+ 'ip-address': ip_address,
4705+ 'subnet-mask': subnet_mask})
4706+ return network_adapters
4707+
4708+
4709+def _get_linux_network_adapters():
4710+ """Get the list of Linux network adapters."""
4711+ import fcntl
4712+ max_bytes = 8096
4713+ arch = platform.architecture()[0]
4714+ if arch == ARCH_32_BIT:
4715+ offset1 = 32
4716+ offset2 = 32
4717+ elif arch == ARCH_64_BIT:
4718+ offset1 = 16
4719+ offset2 = 40
4720+ else:
4721+ raise OSError(_("Unknown architecture: %s") % arch)
4722+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
4723+ names = array.array('B', '\0' * max_bytes)
4724+ outbytes = struct.unpack('iL', fcntl.ioctl(
4725+ sock.fileno(),
4726+ 0x8912,
4727+ struct.pack('iL', max_bytes, names.buffer_info()[0])))[0]
4728+ adapter_names = \
4729+ [names.tostring()[n_counter:n_counter + offset1].split('\0', 1)[0]
4730+ for n_counter in xrange(0, outbytes, offset2)]
4731+ network_adapters = []
4732+ for adapter_name in adapter_names:
4733+ ip_address = socket.inet_ntoa(fcntl.ioctl(
4734+ sock.fileno(),
4735+ 0x8915,
4736+ struct.pack('256s', adapter_name))[20:24])
4737+ subnet_mask = socket.inet_ntoa(fcntl.ioctl(
4738+ sock.fileno(),
4739+ 0x891b,
4740+ struct.pack('256s', adapter_name))[20:24])
4741+ raw_mac_address = '%012x' % _bytes2int(fcntl.ioctl(
4742+ sock.fileno(),
4743+ 0x8927,
4744+ struct.pack('256s', adapter_name))[18:24])
4745+ mac_address = ":".join([raw_mac_address[m_counter:m_counter + 2]
4746+ for m_counter in range(0, len(raw_mac_address), 2)]).lower()
4747+ network_adapters.append({'name': adapter_name,
4748+ 'mac-address': mac_address,
4749+ 'ip-address': ip_address,
4750+ 'subnet-mask': subnet_mask})
4751+ return network_adapters
4752+
4753+
4754+def _get_adapter_name_and_ip_address(network_adapters, mac_address):
4755+ """Get the adapter name based on the MAC address."""
4756+ adapter_name = None
4757+ ip_address = None
4758+ for network_adapter in network_adapters:
4759+ if network_adapter['mac-address'] == mac_address.lower():
4760+ adapter_name = network_adapter['name']
4761+ ip_address = network_adapter['ip-address']
4762+ break
4763+ return adapter_name, ip_address
4764+
4765+
4766+def _get_win_adapter_name_and_ip_address(mac_address):
4767+ """Get Windows network adapter name."""
4768+ network_adapters = _get_windows_network_adapters()
4769+ return _get_adapter_name_and_ip_address(network_adapters, mac_address)
4770+
4771+
4772+def _get_linux_adapter_name_and_ip_address(mac_address):
4773+ """Get Linux network adapter name."""
4774+ network_adapters = _get_linux_network_adapters()
4775+ return _get_adapter_name_and_ip_address(network_adapters, mac_address)
4776+
4777+
4778+def _execute(cmd_list, process_input=None, check_exit_code=True):
4779+ """Executes the command with the list of arguments specified."""
4780+ cmd = ' '.join(cmd_list)
4781+ logging.debug(_("Executing command: '%s'") % cmd)
4782+ env = os.environ.copy()
4783+ obj = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
4784+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
4785+ result = None
4786+ if process_input != None:
4787+ result = obj.communicate(process_input)
4788+ else:
4789+ result = obj.communicate()
4790+ obj.stdin.close()
4791+ if obj.returncode:
4792+ logging.debug(_("Result was %s") % obj.returncode)
4793+ if check_exit_code and obj.returncode != 0:
4794+ (stdout, stderr) = result
4795+ raise ProcessExecutionError(exit_code=obj.returncode,
4796+ stdout=stdout,
4797+ stderr=stderr,
4798+ cmd=cmd)
4799+ time.sleep(0.1)
4800+ return result
4801+
4802+
4803+def _windows_set_networking():
4804+ """Set IP address for the windows VM."""
4805+ program_files = os.environ.get('PROGRAMFILES')
4806+ program_files_x86 = os.environ.get('PROGRAMFILES(X86)')
4807+ vmware_tools_bin = None
4808+ if os.path.exists(os.path.join(program_files, 'VMware', 'VMware Tools',
4809+ 'vmtoolsd.exe')):
4810+ vmware_tools_bin = os.path.join(program_files, 'VMware',
4811+ 'VMware Tools', 'vmtoolsd.exe')
4812+ elif os.path.exists(os.path.join(program_files, 'VMware', 'VMware Tools',
4813+ 'VMwareService.exe')):
4814+ vmware_tools_bin = os.path.join(program_files, 'VMware',
4815+ 'VMware Tools', 'VMwareService.exe')
4816+ elif program_files_x86 and os.path.exists(os.path.join(program_files_x86,
4817+ 'VMware', 'VMware Tools',
4818+ 'VMwareService.exe')):
4819+ vmware_tools_bin = os.path.join(program_files_x86, 'VMware',
4820+ 'VMware Tools', 'VMwareService.exe')
4821+ if vmware_tools_bin:
4822+ cmd = ['"' + vmware_tools_bin + '"', '--cmd', 'machine.id.get']
4823+ for network_detail in _parse_network_details(_execute(cmd,
4824+ check_exit_code=False)):
4825+ mac_address, ip_address, subnet_mask, gateway, broadcast,\
4826+ dns_servers = network_detail
4827+ adapter_name, current_ip_address = \
4828+ _get_win_adapter_name_and_ip_address(mac_address)
4829+ if adapter_name and not ip_address == current_ip_address:
4830+ cmd = ['netsh', 'interface', 'ip', 'set', 'address',
4831+ 'name="%s"' % adapter_name, 'source=static', ip_address,
4832+ subnet_mask, gateway, '1']
4833+ _execute(cmd)
4834+ # Windows doesn't let you manually set the broadcast address
4835+ for dns_server in dns_servers:
4836+ if dns_server:
4837+ cmd = ['netsh', 'interface', 'ip', 'add', 'dns',
4838+ 'name="%s"' % adapter_name, dns_server]
4839+ _execute(cmd)
4840+ else:
4841+ logging.warn(_("VMware Tools is not installed"))
4842+
4843+
4844+def _filter_duplicates(all_entries):
4845+ final_list = []
4846+ for entry in all_entries:
4847+ if entry and entry not in final_list:
4848+ final_list.append(entry)
4849+ return final_list
4850+
4851+
4852+def _set_rhel_networking(network_details=[]):
4853+ all_dns_servers = []
4854+ for network_detail in network_details:
4855+ mac_address, ip_address, subnet_mask, gateway, broadcast,\
4856+ dns_servers = network_detail
4857+ all_dns_servers.extend(dns_servers)
4858+ adapter_name, current_ip_address = \
4859+ _get_linux_adapter_name_and_ip_address(mac_address)
4860+ if adapter_name and not ip_address == current_ip_address:
4861+ interface_file_name = \
4862+ '/etc/sysconfig/network-scripts/ifcfg-%s' % adapter_name
4863+ # Remove file
4864+ os.remove(interface_file_name)
4865+ # Touch file
4866+ _execute(['touch', interface_file_name])
4867+ interface_file = open(interface_file_name, 'w')
4868+ interface_file.write('\nDEVICE=%s' % adapter_name)
4869+ interface_file.write('\nUSERCTL=yes')
4870+ interface_file.write('\nONBOOT=yes')
4871+ interface_file.write('\nBOOTPROTO=static')
4872+ interface_file.write('\nBROADCAST=%s' % broadcast)
4873+ interface_file.write('\nNETWORK=')
4874+ interface_file.write('\nGATEWAY=%s' % gateway)
4875+ interface_file.write('\nNETMASK=%s' % subnet_mask)
4876+ interface_file.write('\nIPADDR=%s' % ip_address)
4877+ interface_file.write('\nMACADDR=%s' % mac_address)
4878+ interface_file.close()
4879+ if all_dns_servers:
4880+ dns_file_name = "/etc/resolv.conf"
4881+ os.remove(dns_file_name)
4882+ _execute(['touch', dns_file_name])
4883+ dns_file = open(dns_file_name, 'w')
4884+ dns_file.write("; generated by OpenStack guest tools")
4885+ unique_entries = _filter_duplicates(all_dns_servers)
4886+ for dns_server in unique_entries:
4887+ dns_file.write("\nnameserver %s" % dns_server)
4888+ dns_file.close()
4889+ _execute(['/sbin/service', 'network', 'restart'])
4890+
4891+
4892+def _linux_set_networking():
4893+ """Set IP address for the Linux VM."""
4894+ vmware_tools_bin = None
4895+ if os.path.exists('/usr/sbin/vmtoolsd'):
4896+ vmware_tools_bin = '/usr/sbin/vmtoolsd'
4897+ elif os.path.exists('/usr/bin/vmtoolsd'):
4898+ vmware_tools_bin = '/usr/bin/vmtoolsd'
4899+ elif os.path.exists('/usr/sbin/vmware-guestd'):
4900+ vmware_tools_bin = '/usr/sbin/vmware-guestd'
4901+ elif os.path.exists('/usr/bin/vmware-guestd'):
4902+ vmware_tools_bin = '/usr/bin/vmware-guestd'
4903+ if vmware_tools_bin:
4904+ cmd = [vmware_tools_bin, '--cmd', 'machine.id.get']
4905+ network_details = _parse_network_details(_execute(cmd,
4906+ check_exit_code=False))
4907+ # TODO(sateesh): For other distros like ubuntu, suse, debian, BSD, etc.
4908+ _set_rhel_networking(network_details)
4909+ else:
4910+ logging.warn(_("VMware Tools is not installed"))
4911+
4912+if __name__ == '__main__':
4913+ pltfrm = sys.platform
4914+ if pltfrm == PLATFORM_WIN:
4915+ _windows_set_networking()
4916+ elif pltfrm == PLATFORM_LINUX:
4917+ _linux_set_networking()
4918+ else:
4919+ raise NotImplementedError(_("Platform not implemented: '%s'") % pltfrm)
4920
4921=== modified file 'tools/pip-requires'
4922--- tools/pip-requires 2011-03-03 14:55:02 +0000
4923+++ tools/pip-requires 2011-03-24 16:38:31 +0000
4924@@ -30,3 +30,4 @@
4925 netaddr
4926 sphinx
4927 glance
4928+suds==0.4