Browse Source

Merge "xenapi: Remove driver and tests"

changes/52/750652/7
Zuul 1 week ago
committed by Gerrit Code Review
parent
commit
9d9f8131e8
43 changed files with 50 additions and 24482 deletions
  1. +0
    -2
      nova/conf/__init__.py
  2. +0
    -549
      nova/conf/xenserver.py
  3. +1
    -25
      nova/exception.py
  4. +0
    -37
      nova/privsep/xenapi.py
  5. +0
    -71
      nova/tests/unit/compute/test_compute_xen.py
  6. +3
    -5
      nova/tests/unit/virt/libvirt/test_driver.py
  7. +0
    -0
      nova/tests/unit/virt/xenapi/__init__.py
  8. +0
    -0
      nova/tests/unit/virt/xenapi/image/__init__.py
  9. +0
    -334
      nova/tests/unit/virt/xenapi/image/test_glance.py
  10. +0
    -244
      nova/tests/unit/virt/xenapi/image/test_utils.py
  11. +0
    -149
      nova/tests/unit/virt/xenapi/image/test_vdi_stream.py
  12. +0
    -204
      nova/tests/unit/virt/xenapi/image/test_vdi_through_dev.py
  13. +0
    -180
      nova/tests/unit/virt/xenapi/stubs.py
  14. +0
    -471
      nova/tests/unit/virt/xenapi/test_agent.py
  15. +0
    -433
      nova/tests/unit/virt/xenapi/test_driver.py
  16. +0
    -76
      nova/tests/unit/virt/xenapi/test_network_utils.py
  17. +0
    -206
      nova/tests/unit/virt/xenapi/test_vgpu.py
  18. +0
    -554
      nova/tests/unit/virt/xenapi/test_vif.py
  19. +0
    -2402
      nova/tests/unit/virt/xenapi/test_vm_utils.py
  20. +0
    -2405
      nova/tests/unit/virt/xenapi/test_vmops.py
  21. +0
    -416
      nova/tests/unit/virt/xenapi/test_volume_utils.py
  22. +0
    -547
      nova/tests/unit/virt/xenapi/test_volumeops.py
  23. +0
    -3860
      nova/tests/unit/virt/xenapi/test_xenapi.py
  24. +0
    -1101
      nova/tests/unit/virt/xenapi/vm_rrd.xml
  25. +0
    -21
      nova/virt/xenapi/__init__.py
  26. +0
    -442
      nova/virt/xenapi/agent.py
  27. +0
    -861
      nova/virt/xenapi/driver.py
  28. +0
    -1172
      nova/virt/xenapi/fake.py
  29. +0
    -570
      nova/virt/xenapi/host.py
  30. +0
    -0
      nova/virt/xenapi/image/__init__.py
  31. +0
    -93
      nova/virt/xenapi/image/glance.py
  32. +0
    -121
      nova/virt/xenapi/image/utils.py
  33. +0
    -85
      nova/virt/xenapi/image/vdi_stream.py
  34. +0
    -108
      nova/virt/xenapi/image/vdi_through_dev.py
  35. +0
    -52
      nova/virt/xenapi/network_utils.py
  36. +0
    -240
      nova/virt/xenapi/pool.py
  37. +0
    -51
      nova/virt/xenapi/pool_states.py
  38. +0
    -443
      nova/virt/xenapi/vif.py
  39. +0
    -2607
      nova/virt/xenapi/vm_utils.py
  40. +0
    -2725
      nova/virt/xenapi/vmops.py
  41. +0
    -394
      nova/virt/xenapi/volume_utils.py
  42. +0
    -226
      nova/virt/xenapi/volumeops.py
  43. +46
    -0
      releasenotes/notes/remove-xenapi-driver-194756049f22dc9e.yaml

+ 0
- 2
nova/conf/__init__.py View File

@@ -65,7 +65,6 @@ from nova.conf import vmware
from nova.conf import vnc
from nova.conf import workarounds
from nova.conf import wsgi
from nova.conf import xenserver
from nova.conf import zvm

CONF = cfg.CONF
@@ -116,5 +115,4 @@ vmware.register_opts(CONF)
vnc.register_opts(CONF)
workarounds.register_opts(CONF)
wsgi.register_opts(CONF)
xenserver.register_opts(CONF)
zvm.register_opts(CONF)

+ 0
- 549
nova/conf/xenserver.py View File

@@ -1,549 +0,0 @@
# Copyright 2016 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import socket

from oslo_config import cfg
from oslo_utils import units

xenserver_group = cfg.OptGroup('xenserver',
title='Xenserver Options',
help="""
.. warning:: The xenapi driver is deprecated and may be removed in a future
release. The driver is not tested by the OpenStack project nor
does it have clear maintainer(s) and thus its quality can not be
ensured. If you are using the driver in production please let us
know in freenode IRC and/or the openstack-discuss mailing list.

XenServer options are used when the compute_driver is set to use
XenServer (compute_driver=xenapi.XenAPIDriver).

Must specify connection_url, connection_password and ovs_integration_bridge to
use compute_driver=xenapi.XenAPIDriver.
""")

xenapi_agent_opts = [
cfg.IntOpt('agent_timeout',
default=30,
min=0,
help="""
Number of seconds to wait for agent's reply to a request.

Nova configures/performs certain administrative actions on a server with the
help of an agent that's installed on the server. The communication between
Nova and the agent is achieved via sharing messages, called records, over
xenstore, a shared storage across all the domains on a Xenserver host.
Operations performed by the agent on behalf of nova are: 'version',' key_init',
'password','resetnetwork','inject_file', and 'agentupdate'.

To perform one of the above operations, the xapi 'agent' plugin writes the
command and its associated parameters to a certain location known to the domain
and awaits response. On being notified of the message, the agent performs
appropriate actions on the server and writes the result back to xenstore. This
result is then read by the xapi 'agent' plugin to determine the success/failure
of the operation.

This config option determines how long the xapi 'agent' plugin shall wait to
read the response off of xenstore for a given request/command. If the agent on
the instance fails to write the result in this time period, the operation is
considered to have timed out.

Related options:

* ``agent_version_timeout``
* ``agent_resetnetwork_timeout``

"""),
cfg.IntOpt('agent_version_timeout',
default=300,
min=0,
help="""
Number of seconds to wait for agent't reply to version request.

This indicates the amount of time xapi 'agent' plugin waits for the agent to
respond to the 'version' request specifically. The generic timeout for agent
communication ``agent_timeout`` is ignored in this case.

During the build process the 'version' request is used to determine if the
agent is available/operational to perform other requests such as
'resetnetwork', 'password', 'key_init' and 'inject_file'. If the 'version' call
fails, the other configuration is skipped. So, this configuration option can
also be interpreted as time in which agent is expected to be fully operational.
"""),
cfg.IntOpt('agent_resetnetwork_timeout',
default=60,
min=0,
help="""
Number of seconds to wait for agent's reply to resetnetwork
request.

This indicates the amount of time xapi 'agent' plugin waits for the agent to
respond to the 'resetnetwork' request specifically. The generic timeout for
agent communication ``agent_timeout`` is ignored in this case.
"""),
cfg.StrOpt('agent_path',
default='usr/sbin/xe-update-networking',
help="""
Path to locate guest agent on the server.

Specifies the path in which the XenAPI guest agent should be located. If the
agent is present, network configuration is not injected into the image.

Related options:

For this option to have an effect:
* ``flat_injected`` should be set to ``True``
* ``compute_driver`` should be set to ``xenapi.XenAPIDriver``

"""),
cfg.BoolOpt('disable_agent',
default=False,
help="""
Disables the use of XenAPI agent.

This configuration option suggests whether the use of agent should be enabled
or not regardless of what image properties are present. Image properties have
an effect only when this is set to ``True``. Read description of config option
``use_agent_default`` for more information.

Related options:

* ``use_agent_default``

"""),
cfg.BoolOpt('use_agent_default',
default=False,
help="""
Whether or not to use the agent by default when its usage is enabled but not
indicated by the image.

The use of XenAPI agent can be disabled altogether using the configuration
option ``disable_agent``. However, if it is not disabled, the use of an agent
can still be controlled by the image in use through one of its properties,
``xenapi_use_agent``. If this property is either not present or specified
incorrectly on the image, the use of agent is determined by this configuration
option.

Note that if this configuration is set to ``True`` when the agent is not
present, the boot times will increase significantly.

Related options:

* ``disable_agent``

"""),
]


xenapi_session_opts = [
cfg.IntOpt('login_timeout',
default=10,
min=0,
help='Timeout in seconds for XenAPI login.'),
cfg.IntOpt('connection_concurrent',
default=5,
min=1,
help="""
Maximum number of concurrent XenAPI connections.

In nova, multiple XenAPI requests can happen at a time.
Configuring this option will parallelize access to the XenAPI
session, which allows you to make concurrent XenAPI connections.
"""),
]


xenapi_vm_utils_opts = [
cfg.StrOpt('cache_images',
default='all',
choices=[
('all', 'Will cache all images'),
('some', 'Will only cache images that have the image_property '
'``cache_in_nova=True``'),
('none', 'Turns off caching entirely')],
help="""
Cache glance images locally.

The value for this option must be chosen from the choices listed
here. Configuring a value other than these will default to 'all'.

Note: There is nothing that deletes these images.
"""),
cfg.IntOpt('image_compression_level',
min=1,
max=9,
help="""
Compression level for images.

By setting this option we can configure the gzip compression level.
This option sets GZIP environment variable before spawning tar -cz
to force the compression level. It defaults to none, which means the
GZIP environment variable is not set and the default (usually -6)
is used.

Possible values:

* Range is 1-9, e.g., 9 for gzip -9, 9 being most
compressed but most CPU intensive on dom0.
* Any values out of this range will default to None.
"""),
cfg.StrOpt('default_os_type',
default='linux',
help='Default OS type used when uploading an image to glance'),
cfg.IntOpt('block_device_creation_timeout',
default=10,
min=1,
help='Time in secs to wait for a block device to be created'),
cfg.IntOpt('max_kernel_ramdisk_size',
default=16 * units.Mi,
help="""
Maximum size in bytes of kernel or ramdisk images.

Specifying the maximum size of kernel or ramdisk will avoid copying
large files to dom0 and fill up /boot/guest.
"""),
cfg.StrOpt('sr_matching_filter',
default='default-sr:true',
help="""
Filter for finding the SR to be used to install guest instances on.

Possible values:

* To use the Local Storage in default XenServer/XCP installations
set this flag to other-config:i18n-key=local-storage.
* To select an SR with a different matching criteria, you could
set it to other-config:my_favorite_sr=true.
* To fall back on the Default SR, as displayed by XenCenter,
set this flag to: default-sr:true.
"""),
cfg.BoolOpt('sparse_copy',
default=True,
help="""
Whether to use sparse_copy for copying data on a resize down.
(False will use standard dd). This speeds up resizes down
considerably since large runs of zeros won't have to be rsynced.
"""),
cfg.IntOpt('num_vbd_unplug_retries',
default=10,
min=0,
help="""
Maximum number of retries to unplug VBD.
If set to 0, should try once, no retries.
"""),
cfg.StrOpt('ipxe_network_name',
help="""
Name of network to use for booting iPXE ISOs.

An iPXE ISO is a specially crafted ISO which supports iPXE booting.
This feature gives a means to roll your own image.

By default this option is not set. Enable this option to
boot an iPXE ISO.

Related Options:

* `ipxe_boot_menu_url`
* `ipxe_mkisofs_cmd`
"""),
cfg.StrOpt('ipxe_boot_menu_url',
help="""
URL to the iPXE boot menu.

An iPXE ISO is a specially crafted ISO which supports iPXE booting.
This feature gives a means to roll your own image.

By default this option is not set. Enable this option to
boot an iPXE ISO.

Related Options:

* `ipxe_network_name`
* `ipxe_mkisofs_cmd`
"""),
cfg.StrOpt('ipxe_mkisofs_cmd',
default='mkisofs',
help="""
Name and optionally path of the tool used for ISO image creation.

An iPXE ISO is a specially crafted ISO which supports iPXE booting.
This feature gives a means to roll your own image.

Note: By default `mkisofs` is not present in the Dom0, so the
package can either be manually added to Dom0 or include the
`mkisofs` binary in the image itself.

Related Options:

* `ipxe_network_name`
* `ipxe_boot_menu_url`
"""),
]


xenapi_opts = [
cfg.StrOpt('connection_url',
help="""
URL for connection to XenServer/Xen Cloud Platform. A special value
of unix://local can be used to connect to the local unix socket.

Possible values:

* Any string that represents a URL. The connection_url is
generally the management network IP address of the XenServer.
* This option must be set if you chose the XenServer driver.
"""),
cfg.StrOpt('connection_username',
default='root',
help='Username for connection to XenServer/Xen Cloud Platform'),
cfg.StrOpt('connection_password',
secret=True,
help='Password for connection to XenServer/Xen Cloud Platform'),
cfg.FloatOpt('vhd_coalesce_poll_interval',
default=5.0,
min=0,
help="""
The interval used for polling of coalescing vhds.

This is the interval after which the task of coalesce VHD is
performed, until it reaches the max attempts that is set by
vhd_coalesce_max_attempts.

Related options:

* `vhd_coalesce_max_attempts`
"""),
cfg.BoolOpt('check_host',
default=True,
help="""
Ensure compute service is running on host XenAPI connects to.
This option must be set to false if the 'independent_compute'
option is set to true.

Possible values:

* Setting this option to true will make sure that compute service
is running on the same host that is specified by connection_url.
* Setting this option to false, doesn't perform the check.

Related options:

* `independent_compute`
"""),
cfg.IntOpt('vhd_coalesce_max_attempts',
default=20,
min=0,
help="""
Max number of times to poll for VHD to coalesce.

This option determines the maximum number of attempts that can be
made for coalescing the VHD before giving up.

Related opitons:

* `vhd_coalesce_poll_interval`
"""),
cfg.StrOpt('sr_base_path',
default='/var/run/sr-mount',
help='Base path to the storage repository on the XenServer host.'),
cfg.HostAddressOpt('target_host',
help="""
The iSCSI Target Host.

This option represents the hostname or ip of the iSCSI Target.
If the target host is not present in the connection information from
the volume provider then the value from this option is taken.

Possible values:

* Any string that represents hostname/ip of Target.
"""),
cfg.PortOpt('target_port',
default=3260,
help="""
The iSCSI Target Port.

This option represents the port of the iSCSI Target. If the
target port is not present in the connection information from the
volume provider then the value from this option is taken.
"""),
cfg.BoolOpt('independent_compute',
default=False,
help="""
Used to prevent attempts to attach VBDs locally, so Nova can
be run in a VM on a different host.

Related options:

* ``CONF.flat_injected`` (Must be False)
* ``CONF.xenserver.check_host`` (Must be False)
* ``CONF.default_ephemeral_format`` (Must be unset or 'ext3')
* Joining host aggregates (will error if attempted)
* Swap disks for Windows VMs (will error if attempted)
* Nova-based auto_configure_disk (will error if attempted)
""")
]

xenapi_vmops_opts = [
cfg.IntOpt('running_timeout',
default=60,
min=0,
help="""
Wait time for instances to go to running state.

Provide an integer value representing time in seconds to set the
wait time for an instance to go to running state.

When a request to create an instance is received by nova-api and
communicated to nova-compute, the creation of the instance occurs
through interaction with Xen via XenAPI in the compute node. Once
the node on which the instance(s) are to be launched is decided by
nova-schedule and the launch is triggered, a certain amount of wait
time is involved until the instance(s) can become available and
'running'. This wait time is defined by running_timeout. If the
instances do not go to running state within this specified wait
time, the launch expires and the instance(s) are set to 'error'
state.
"""),
# TODO(dharinic): Make this, a stevedore plugin
cfg.StrOpt('image_upload_handler',
default='',
deprecated_for_removal=True,
deprecated_since='18.0.0',
deprecated_reason="""
Instead of setting the class path here, we will use short names
to represent image handlers. The download and upload handlers
must also be matching. So another new option "image_handler"
will be used to set the short name for a specific image handler
for both image download and upload.
""",
help="""
Dom0 plugin driver used to handle image uploads.

Provide a string value representing a plugin driver required to
handle the image uploading to GlanceStore.

Images, and snapshots from XenServer need to be uploaded to the data
store for use. image_upload_handler takes in a value for the Dom0
plugin driver. This driver is then called to uplaod images to the
GlanceStore.
"""),
cfg.StrOpt('image_handler',
default='direct_vhd',
choices=[
('direct_vhd', 'This plugin directly processes the VHD files in '
'XenServer SR(Storage Repository). So this plugin only works '
'when the host\'s SR type is file system based e.g. ext, nfs.'),
('vdi_local_dev', 'This plugin implements an image handler which '
'attaches the instance\'s VDI as a local disk to the VM where '
'the OpenStack Compute service runs. It uploads the raw disk '
'to glance when creating image; when booting an instance from a '
'glance image, it downloads the image and streams it into the '
'disk which is attached to the compute VM.'),
('vdi_remote_stream', 'This plugin implements an image handler '
'which works as a proxy between glance and XenServer. The VHD '
'streams to XenServer via a remote import API supplied by XAPI '
'for image download; and for image upload, the VHD streams from '
'XenServer via a remote export API supplied by XAPI. This '
'plugin works for all SR types supported by XenServer.'),
],
help="""
The plugin used to handle image uploads and downloads.

Provide a short name representing an image driver required to
handle the image between compute host and glance.
"""),
]

xenapi_volume_utils_opts = [
cfg.IntOpt('introduce_vdi_retry_wait',
default=20,
min=0,
help="""
Number of seconds to wait for SR to settle if the VDI
does not exist when first introduced.

Some SRs, particularly iSCSI connections are slow to see the VDIs
right after they got introduced. Setting this option to a
time interval will make the SR to wait for that time period
before raising VDI not found exception.
""")
]

xenapi_ovs_integration_bridge_opts = [
cfg.StrOpt('ovs_integration_bridge',
help="""
The name of the integration Bridge that is used with xenapi
when connecting with Open vSwitch.

Note: The value of this config option is dependent on the
environment, therefore this configuration value must be set
accordingly if you are using XenAPI.

Possible values:

* Any string that represents a bridge name.
"""),
]

xenapi_pool_opts = [
# TODO(macsz): This should be deprecated. Until providing solid reason,
# leaving it as-it-is.
cfg.BoolOpt('use_join_force',
default=True,
help="""
When adding new host to a pool, this will append a --force flag to the
command, forcing hosts to join a pool, even if they have different CPUs.

Since XenServer version 5.6 it is possible to create a pool of hosts that have
different CPU capabilities. To accommodate CPU differences, XenServer limited
features it uses to determine CPU compatibility to only the ones that are
exposed by CPU and support for CPU masking was added.
Despite this effort to level differences between CPUs, it is still possible
that adding new host will fail, thus option to force join was introduced.
"""),
]

xenapi_console_opts = [
cfg.StrOpt('console_public_hostname',
default=socket.gethostname(),
sample_default='<current_hostname>',
deprecated_group='DEFAULT',
help="""
Publicly visible name for this console host.

Possible values:

* Current hostname (default) or any string representing hostname.
"""),
]

ALL_XENSERVER_OPTS = (xenapi_agent_opts +
xenapi_session_opts +
xenapi_vm_utils_opts +
xenapi_opts +
xenapi_vmops_opts +
xenapi_volume_utils_opts +
xenapi_ovs_integration_bridge_opts +
xenapi_pool_opts +
xenapi_console_opts)


def register_opts(conf):
conf.register_group(xenserver_group)
conf.register_opts(ALL_XENSERVER_OPTS, group=xenserver_group)


def list_opts():
return {xenserver_group: ALL_XENSERVER_OPTS}

+ 1
- 25
nova/exception.py View File

@@ -713,19 +713,11 @@ class ImageDeleteConflict(NovaException):
msg_fmt = _("Conflict deleting image. Reason: %(reason)s.")


class ImageHandlerUnsupported(NovaException):
msg_fmt = _("Error: unsupported image handler %(image_handler)s.")


class PreserveEphemeralNotSupported(Invalid):
msg_fmt = _("The current driver does not support "
"preserving ephemeral partitions.")


class StorageRepositoryNotFound(NotFound):
msg_fmt = _("Cannot find SR to read/write VDI.")


class InstanceMappingNotFound(NotFound):
msg_fmt = _("Instance %(uuid)s has no mapping to a cell.")

@@ -1193,10 +1185,6 @@ class BootFromVolumeRequiredForZeroDiskFlavor(Forbidden):
"zero disk.")


class InsufficientFreeMemory(NovaException):
msg_fmt = _("Insufficient free memory on compute node to start %(uuid)s.")


class NoValidHost(NovaException):
msg_fmt = _("No valid host was found. %(reason)s")

@@ -1255,6 +1243,7 @@ class PortLimitExceeded(QuotaError):
msg_fmt = _("Maximum number of ports exceeded")


# TODO(stephenfin): Remove this XenAPI relic
class AggregateError(NovaException):
msg_fmt = _("Aggregate %(aggregate_id)s: action '%(action)s' "
"caused an error: %(reason)s.")
@@ -1458,19 +1447,6 @@ class ObjectActionError(NovaException):
msg_fmt = _('Object action %(action)s failed because: %(reason)s')


class AgentError(NovaException):
msg_fmt = _('Error during following call to agent: %(method)s')


class AgentTimeout(AgentError):
msg_fmt = _('Unable to contact guest agent. '
'The following call timed out: %(method)s')


class AgentNotImplemented(AgentError):
msg_fmt = _('Agent does not support the call: %(method)s')


class InstanceGroupNotFound(NotFound):
msg_fmt = _("Instance group %(group_uuid)s could not be found.")



+ 0
- 37
nova/privsep/xenapi.py View File

@@ -1,37 +0,0 @@
# Copyright 2018 Michael Still and Aptira
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

"""
xenapi specific routines.
"""

from oslo_concurrency import processutils

import nova.privsep


@nova.privsep.sys_admin_pctxt.entrypoint
def xenstore_read(path):
return processutils.execute('xenstore-read', path)


@nova.privsep.sys_admin_pctxt.entrypoint
def block_copy(src_path, dst_path, block_size, num_blocks):
processutils.execute('dd',
'if=%s' % src_path,
'of=%s' % dst_path,
'bs=%d' % block_size,
'count=%d' % num_blocks,
'iflag=direct,sync',
'oflag=direct,sync')

+ 0
- 71
nova/tests/unit/compute/test_compute_xen.py View File

@@ -1,71 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

"""Tests for expectations of behaviour from the Xen driver."""

import mock

from nova.compute import manager
from nova.compute import power_state
from nova import context
from nova import objects
from nova.objects import instance as instance_obj
from nova.tests.unit.compute import eventlet_utils
from nova.tests.unit import fake_instance
from nova.tests.unit.virt.xenapi import stubs
from nova.virt.xenapi import vm_utils


class ComputeXenTestCase(stubs.XenAPITestBaseNoDB):
def setUp(self):
super(ComputeXenTestCase, self).setUp()
self.flags(compute_driver='xenapi.XenAPIDriver')
self.flags(connection_url='http://localhost',
connection_password='test_pass',
group='xenserver')

stubs.stubout_session(self, stubs.FakeSessionForVMTests)
self.compute = manager.ComputeManager()
# execute power syncing synchronously for testing:
self.compute._sync_power_pool = eventlet_utils.SyncPool()

def test_sync_power_states_instance_not_found(self):
db_instance = fake_instance.fake_db_instance()
ctxt = context.get_admin_context()
instance_list = instance_obj._make_instance_list(ctxt,
objects.InstanceList(), [db_instance], None)
instance = instance_list[0]

@mock.patch.object(vm_utils, 'lookup')
@mock.patch.object(objects.InstanceList, 'get_by_host')
@mock.patch.object(self.compute.driver, 'get_num_instances')
@mock.patch.object(self.compute, '_sync_instance_power_state')
def do_test(mock_compute_sync_powerstate,
mock_compute_get_num_instances,
mock_instance_list_get_by_host,
mock_vm_utils_lookup):
mock_instance_list_get_by_host.return_value = instance_list
mock_compute_get_num_instances.return_value = 1
mock_vm_utils_lookup.return_value = None

self.compute._sync_power_states(ctxt)

mock_instance_list_get_by_host.assert_called_once_with(
ctxt, self.compute.host, expected_attrs=[], use_slave=True)
mock_compute_get_num_instances.assert_called_once_with()
mock_compute_sync_powerstate.assert_called_once_with(
ctxt, instance, power_state.NOSTATE, use_slave=True)
mock_vm_utils_lookup.assert_called_once_with(
self.compute.driver._session, instance['name'],
False)

do_test()

+ 3
- 5
nova/tests/unit/virt/libvirt/test_driver.py View File

@@ -19727,17 +19727,15 @@ class LibvirtConnTestCase(test.NoDBTestCase,
def test_unplug_vifs_ignores_errors(self):
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI())
with mock.patch.object(drvr, 'vif_driver') as vif_driver:
vif_driver.unplug.side_effect = exception.AgentError(
method='unplug')
vif_driver.unplug.side_effect = exception.InternalError('foo')
drvr._unplug_vifs('inst', [1], ignore_errors=True)
vif_driver.unplug.assert_called_once_with('inst', 1)

def test_unplug_vifs_reports_errors(self):
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI())
with mock.patch.object(drvr, 'vif_driver') as vif_driver:
vif_driver.unplug.side_effect = exception.AgentError(
method='unplug')
self.assertRaises(exception.AgentError,
vif_driver.unplug.side_effect = exception.InternalError('foo')
self.assertRaises(exception.InternalError,
drvr.unplug_vifs, 'inst', [1])
vif_driver.unplug.assert_called_once_with('inst', 1)



+ 0
- 0
nova/tests/unit/virt/xenapi/__init__.py View File


+ 0
- 0
nova/tests/unit/virt/xenapi/image/__init__.py View File


+ 0
- 334
nova/tests/unit/virt/xenapi/image/test_glance.py View File

@@ -1,334 +0,0 @@
# Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import random
import time

import mock
from os_xenapi.client import exception as xenapi_exception
from os_xenapi.client import host_glance
from os_xenapi.client import XenAPI

from nova.compute import utils as compute_utils
from nova import context
from nova import exception
from nova.image import glance as common_glance
from nova.tests.unit.virt.xenapi import stubs
from nova import utils
from nova.virt.xenapi import driver as xenapi_conn
from nova.virt.xenapi import fake
from nova.virt.xenapi.image import glance
from nova.virt.xenapi import vm_utils


class TestGlanceStore(stubs.XenAPITestBaseNoDB):
def setUp(self):
super(TestGlanceStore, self).setUp()
self.store = glance.GlanceStore()

self.flags(api_servers=['http://localhost:9292'], group='glance')
self.flags(connection_url='http://localhost',
connection_password='test_pass',
group='xenserver')

self.context = context.RequestContext(
'user', 'project', auth_token='foobar')

fake.reset()
stubs.stubout_session(self, fake.SessionBase)
driver = xenapi_conn.XenAPIDriver(False)
self.session = driver._session

self.stub_out('nova.virt.xenapi.vm_utils.get_sr_path',
lambda *a, **kw: '/fake/sr/path')

self.instance = {'uuid': 'blah',
'system_metadata': [],
'auto_disk_config': True,
'os_type': 'default',
'xenapi_use_agent': 'true'}

def _get_params(self):
return {'image_id': 'fake_image_uuid',
'endpoint': 'http://localhost:9292',
'sr_path': '/fake/sr/path',
'api_version': 2,
'extra_headers': {'X-Auth-Token': 'foobar',
'X-Roles': '',
'X-Tenant-Id': 'project',
'X-User-Id': 'user',
'X-Identity-Status': 'Confirmed'}}

def _get_download_params(self):
params = self._get_params()
params['uuid_stack'] = ['uuid1']
return params

@mock.patch.object(vm_utils, '_make_uuid_stack', return_value=['uuid1'])
def test_download_image(self, mock_make_uuid_stack):
params = self._get_download_params()
with mock.patch.object(self.session, 'call_plugin_serialized'
) as mock_call_plugin:
self.store.download_image(self.context, self.session,
self.instance, 'fake_image_uuid')

mock_call_plugin.assert_called_once_with('glance.py',
'download_vhd2',
**params)
mock_make_uuid_stack.assert_called_once_with()

@mock.patch.object(vm_utils, '_make_uuid_stack', return_value=['uuid1'])
@mock.patch.object(random, 'shuffle')
@mock.patch.object(time, 'sleep')
@mock.patch.object(compute_utils, 'add_instance_fault_from_exc')
def test_download_image_retry(self, mock_fault, mock_sleep,
mock_shuffle, mock_make_uuid_stack):
params = self._get_download_params()
self.flags(num_retries=2, group='glance')

params.pop("endpoint")
calls = [mock.call('glance.py', 'download_vhd2',
endpoint='http://10.0.1.1:9292',
**params),
mock.call('glance.py', 'download_vhd2',
endpoint='http://10.0.0.1:9293',
**params)]

glance_api_servers = ['http://10.0.1.1:9292',
'http://10.0.0.1:9293']
self.flags(api_servers=glance_api_servers, group='glance')

with (mock.patch.object(self.session, 'call_plugin_serialized')
) as mock_call_plugin_serialized:
error_details = ["", "", "RetryableError", ""]
error = self.session.XenAPI.Failure(details=error_details)
mock_call_plugin_serialized.side_effect = [error, "success"]

self.store.download_image(self.context, self.session,
self.instance, 'fake_image_uuid')

mock_call_plugin_serialized.assert_has_calls(calls)

self.assertEqual(1, mock_fault.call_count)

def _get_upload_params(self, auto_disk_config=True,
expected_os_type='default'):
params = {}
params['vdi_uuids'] = ['fake_vdi_uuid']
params['properties'] = {'auto_disk_config': auto_disk_config,
'os_type': expected_os_type}
return params

@mock.patch.object(utils, 'get_auto_disk_config_from_instance')
@mock.patch.object(common_glance, 'generate_identity_headers')
@mock.patch.object(vm_utils, 'get_sr_path')
@mock.patch.object(host_glance, 'upload_vhd')
def test_upload_image(self, mock_upload, mock_sr_path, mock_extra_header,
mock_disk_config):
params = self._get_upload_params()
mock_upload.return_value = 'fake_upload'
mock_sr_path.return_value = 'fake_sr_path'
mock_extra_header.return_value = 'fake_extra_header'
mock_disk_config.return_value = 'true'
self.store.upload_image(self.context, self.session, self.instance,
'fake_image_uuid', ['fake_vdi_uuid'])

mock_sr_path.assert_called_once_with(self.session)
mock_extra_header.assert_called_once_with(self.context)
mock_upload.assert_called_once_with(
self.session, 3, mock.ANY, mock.ANY, 'fake_image_uuid',
'fake_sr_path', 'fake_extra_header', **params)

@mock.patch.object(utils, 'get_auto_disk_config_from_instance')
@mock.patch.object(common_glance, 'generate_identity_headers')
@mock.patch.object(vm_utils, 'get_sr_path')
@mock.patch.object(host_glance, 'upload_vhd')
def test_upload_image_None_os_type(self, mock_upload, mock_sr_path,
mock_extra_header, mock_disk_config):
self.instance['os_type'] = None
mock_sr_path.return_value = 'fake_sr_path'
mock_extra_header.return_value = 'fake_extra_header'
mock_upload.return_value = 'fake_upload'
mock_disk_config.return_value = 'true'
params = self._get_upload_params(True, 'linux')
self.store.upload_image(self.context, self.session, self.instance,
'fake_image_uuid', ['fake_vdi_uuid'])

mock_sr_path.assert_called_once_with(self.session)
mock_extra_header.assert_called_once_with(self.context)
mock_upload.assert_called_once_with(
self.session, 3, mock.ANY, mock.ANY, 'fake_image_uuid',
'fake_sr_path', 'fake_extra_header', **params)
mock_disk_config.assert_called_once_with(self.instance)

@mock.patch.object(utils, 'get_auto_disk_config_from_instance')
@mock.patch.object(common_glance, 'generate_identity_headers')
@mock.patch.object(vm_utils, 'get_sr_path')
@mock.patch.object(host_glance, 'upload_vhd')
def test_upload_image_no_os_type(self, mock_upload, mock_sr_path,
mock_extra_header, mock_disk_config):
mock_sr_path.return_value = 'fake_sr_path'
mock_extra_header.return_value = 'fake_extra_header'
mock_upload.return_value = 'fake_upload'
del self.instance['os_type']
params = self._get_upload_params(True, 'linux')
self.store.upload_image(self.context, self.session, self.instance,
'fake_image_uuid', ['fake_vdi_uuid'])

mock_sr_path.assert_called_once_with(self.session)
mock_extra_header.assert_called_once_with(self.context)
mock_upload.assert_called_once_with(
self.session, 3, mock.ANY, mock.ANY, 'fake_image_uuid',
'fake_sr_path', 'fake_extra_header', **params)
mock_disk_config.assert_called_once_with(self.instance)

@mock.patch.object(common_glance, 'generate_identity_headers')
@mock.patch.object(vm_utils, 'get_sr_path')
@mock.patch.object(host_glance, 'upload_vhd')
def test_upload_image_auto_config_disk_disabled(
self, mock_upload, mock_sr_path, mock_extra_header):
mock_sr_path.return_value = 'fake_sr_path'
mock_extra_header.return_value = 'fake_extra_header'
mock_upload.return_value = 'fake_upload'
sys_meta = [{"key": "image_auto_disk_config", "value": "Disabled"}]
self.instance["system_metadata"] = sys_meta
params = self._get_upload_params("disabled")
self.store.upload_image(self.context, self.session, self.instance,
'fake_image_uuid', ['fake_vdi_uuid'])

mock_sr_path.assert_called_once_with(self.session)
mock_extra_header.assert_called_once_with(self.context)
mock_upload.assert_called_once_with(
self.session, 3, mock.ANY, mock.ANY, 'fake_image_uuid',
'fake_sr_path', 'fake_extra_header', **params)

@mock.patch.object(common_glance, 'generate_identity_headers')
@mock.patch.object(vm_utils, 'get_sr_path')
@mock.patch.object(host_glance, 'upload_vhd')
def test_upload_image_raises_exception(self, mock_upload, mock_sr_path,
mock_extra_header):

mock_sr_path.return_value = 'fake_sr_path'
mock_extra_header.return_value = 'fake_extra_header'
mock_upload.side_effect = RuntimeError
params = self._get_upload_params()
self.assertRaises(RuntimeError, self.store.upload_image,
self.context, self.session, self.instance,
'fake_image_uuid', ['fake_vdi_uuid'])

mock_sr_path.assert_called_once_with(self.session)
mock_extra_header.assert_called_once_with(self.context)
mock_upload.assert_called_once_with(
self.session, 3, mock.ANY, mock.ANY, 'fake_image_uuid',
'fake_sr_path', 'fake_extra_header', **params)

@mock.patch.object(time, 'sleep')
@mock.patch.object(compute_utils, 'add_instance_fault_from_exc')
def test_upload_image_retries_then_raises_exception(self,
mock_add_inst,
mock_time_sleep):
self.flags(num_retries=2, group='glance')
params = self._get_params()
params.update(self._get_upload_params())

error_details = ["", "", "RetryableError", ""]
error = XenAPI.Failure(details=error_details)

with mock.patch.object(self.session, 'call_plugin_serialized',
side_effect=error) as mock_call_plugin:
self.assertRaises(exception.CouldNotUploadImage,
self.store.upload_image,
self.context, self.session, self.instance,
'fake_image_uuid', ['fake_vdi_uuid'])

time_sleep_args = [mock.call(0.5), mock.call(1)]
call_plugin_args = [
mock.call('glance.py', 'upload_vhd2', **params),
mock.call('glance.py', 'upload_vhd2', **params),
mock.call('glance.py', 'upload_vhd2', **params)]
add_inst_args = [
mock.call(self.context, self.instance, error,
(XenAPI.Failure, error, mock.ANY)),
mock.call(self.context, self.instance, error,
(XenAPI.Failure, error, mock.ANY)),
mock.call(self.context, self.instance, error,
(XenAPI.Failure, error, mock.ANY))]
mock_time_sleep.assert_has_calls(time_sleep_args)
mock_call_plugin.assert_has_calls(call_plugin_args)
mock_add_inst.assert_has_calls(add_inst_args)

@mock.patch.object(time, 'sleep')
@mock.patch.object(compute_utils, 'add_instance_fault_from_exc')
def test_upload_image_retries_on_signal_exception(self,
mock_add_inst,
mock_time_sleep):
self.flags(num_retries=2, group='glance')
params = self._get_params()
params.update(self._get_upload_params())

error_details = ["", "task signaled", "", ""]
error = XenAPI.Failure(details=error_details)

# Note(johngarbutt) XenServer 6.1 and later has this error
error_details_v61 = ["", "signal: SIGTERM", "", ""]
error_v61 = self.session.XenAPI.Failure(details=error_details_v61)

with mock.patch.object(self.session, 'call_plugin_serialized',
side_effect=[error, error_v61, None]
) as mock_call_plugin:
self.store.upload_image(self.context, self.session, self.instance,
'fake_image_uuid', ['fake_vdi_uuid'])

time_sleep_args = [mock.call(0.5), mock.call(1)]
call_plugin_args = [
mock.call('glance.py', 'upload_vhd2', **params),
mock.call('glance.py', 'upload_vhd2', **params),
mock.call('glance.py', 'upload_vhd2', **params)]
add_inst_args = [
mock.call(self.context, self.instance, error,
(XenAPI.Failure, error, mock.ANY)),
mock.call(self.context, self.instance, error_v61,
(XenAPI.Failure, error_v61, mock.ANY))]
mock_time_sleep.assert_has_calls(time_sleep_args)
mock_call_plugin.assert_has_calls(call_plugin_args)
mock_add_inst.assert_has_calls(add_inst_args)

@mock.patch.object(utils, 'get_auto_disk_config_from_instance')
@mock.patch.object(common_glance, 'generate_identity_headers')
@mock.patch.object(vm_utils, 'get_sr_path')
@mock.patch.object(host_glance, 'upload_vhd')
def test_upload_image_raises_exception_image_not_found(self,
mock_upload,
mock_sr_path,
mock_extra_header,
mock_disk_config):
params = self._get_upload_params()
mock_upload.return_value = 'fake_upload'
mock_sr_path.return_value = 'fake_sr_path'
mock_extra_header.return_value = 'fake_extra_header'
mock_disk_config.return_value = 'true'
image_id = 'fake_image_id'
mock_upload.side_effect = xenapi_exception.PluginImageNotFound(
image_id=image_id
)
self.assertRaises(exception.ImageNotFound, self.store.upload_image,
self.context, self.session, self.instance,
'fake_image_uuid', ['fake_vdi_uuid'])

mock_sr_path.assert_called_once_with(self.session)
mock_extra_header.assert_called_once_with(self.context)
mock_upload.assert_called_once_with(
self.session, 3, mock.ANY, mock.ANY, 'fake_image_uuid',
'fake_sr_path', 'fake_extra_header', **params)

+ 0
- 244
nova/tests/unit/virt/xenapi/image/test_utils.py View File

@@ -1,244 +0,0 @@
# Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import tarfile

import mock

from nova import test
from nova.virt.xenapi.image import utils


@mock.patch.object(utils, 'IMAGE_API')
class GlanceImageTestCase(test.NoDBTestCase):

def _get_image(self):
return utils.GlanceImage(mock.sentinel.context,
mock.sentinel.image_ref)

def test_meta(self, mocked):
mocked.get.return_value = mock.sentinel.meta

image = self._get_image()
self.assertEqual(mock.sentinel.meta, image.meta)
mocked.get.assert_called_once_with(mock.sentinel.context,
mock.sentinel.image_ref)

def test_download_to(self, mocked):
mocked.download.return_value = None

image = self._get_image()
result = image.download_to(mock.sentinel.fobj)
self.assertIsNone(result)
mocked.download.assert_called_once_with(mock.sentinel.context,
mock.sentinel.image_ref,
mock.sentinel.fobj)

def test_is_raw_tgz_empty_meta(self, mocked):
mocked.get.return_value = {}

image = self._get_image()
self.assertFalse(image.is_raw_tgz())

def test_is_raw_tgz_for_raw_tgz(self, mocked):
mocked.get.return_value = {
'disk_format': 'raw',
'container_format': 'tgz'
}

image = self._get_image()
self.assertTrue(image.is_raw_tgz())

def test_data(self, mocked):
mocked.download.return_value = mock.sentinel.image
image = self._get_image()

self.assertEqual(mock.sentinel.image, image.data())


class RawImageTestCase(test.NoDBTestCase):
@mock.patch.object(utils, 'GlanceImage', spec_set=True, autospec=True)
def test_get_size(self, mock_glance_image):
mock_glance_image.meta = {'size': '123'}
raw_image = utils.RawImage(mock_glance_image)

self.assertEqual(123, raw_image.get_size())

@mock.patch.object(utils, 'GlanceImage', spec_set=True, autospec=True)
def test_stream_to(self, mock_glance_image):
mock_glance_image.download_to.return_value = 'result'
raw_image = utils.RawImage(mock_glance_image)

self.assertEqual('result', raw_image.stream_to('file'))
mock_glance_image.download_to.assert_called_once_with('file')


class TestIterableBasedFile(test.NoDBTestCase):
def test_constructor(self):
class FakeIterable(object):
def __iter__(_self):
return 'iterator'

the_file = utils.IterableToFileAdapter(FakeIterable())

self.assertEqual('iterator', the_file.iterator)

def test_read_one_character(self):
the_file = utils.IterableToFileAdapter([
'chunk1', 'chunk2'
])

self.assertEqual('c', the_file.read(1))

def test_read_stores_remaining_characters(self):
the_file = utils.IterableToFileAdapter([
'chunk1', 'chunk2'
])

the_file.read(1)

self.assertEqual('hunk1', the_file.remaining_data)

def test_read_remaining_characters(self):
the_file = utils.IterableToFileAdapter([
'chunk1', 'chunk2'
])

self.assertEqual('c', the_file.read(1))
self.assertEqual('h', the_file.read(1))

def test_read_reached_end_of_file(self):
the_file = utils.IterableToFileAdapter([
'chunk1', 'chunk2'
])

self.assertEqual('chunk1', the_file.read(100))
self.assertEqual('chunk2', the_file.read(100))
self.assertEqual('', the_file.read(100))

def test_empty_chunks(self):
the_file = utils.IterableToFileAdapter([
'', '', 'chunk2'
])

self.assertEqual('chunk2', the_file.read(100))


class RawTGZTestCase(test.NoDBTestCase):
@mock.patch.object(utils.RawTGZImage, '_as_file', return_value='the_file')
@mock.patch.object(utils.tarfile, 'open', return_value='tf')
def test_as_tarfile(self, mock_open, mock_as_file):
image = utils.RawTGZImage(None)
result = image._as_tarfile()
self.assertEqual('tf', result)
mock_as_file.assert_called_once_with()
mock_open.assert_called_once_with(mode='r|gz', fileobj='the_file')

@mock.patch.object(utils, 'GlanceImage', spec_set=True, autospec=True)
@mock.patch.object(utils, 'IterableToFileAdapter',
return_value='data-as-file')
def test_as_file(self, mock_adapter, mock_glance_image):
mock_glance_image.data.return_value = 'iterable-data'
image = utils.RawTGZImage(mock_glance_image)
result = image._as_file()
self.assertEqual('data-as-file', result)
mock_glance_image.data.assert_called_once_with()
mock_adapter.assert_called_once_with('iterable-data')

@mock.patch.object(tarfile, 'TarFile', spec_set=True, autospec=True)
@mock.patch.object(tarfile, 'TarInfo', autospec=True)
@mock.patch.object(utils.RawTGZImage, '_as_tarfile')
def test_get_size(self, mock_as_tar, mock_tar_info, mock_tar_file):
mock_tar_file.next.return_value = mock_tar_info
mock_tar_info.size = 124
mock_as_tar.return_value = mock_tar_file

image = utils.RawTGZImage(None)
result = image.get_size()

self.assertEqual(124, result)
self.assertEqual(image._tar_info, mock_tar_info)
self.assertEqual(image._tar_file, mock_tar_file)
mock_as_tar.assert_called_once_with()
mock_tar_file.next.assert_called_once_with()

@mock.patch.object(tarfile, 'TarFile', spec_set=True, autospec=True)
@mock.patch.object(tarfile, 'TarInfo', autospec=True)
@mock.patch.object(utils.RawTGZImage, '_as_tarfile')
def test_get_size_called_twice(self, mock_as_tar, mock_tar_info,
mock_tar_file):
mock_tar_file.next.return_value = mock_tar_info
mock_tar_info.size = 124
mock_as_tar.return_value = mock_tar_file

image = utils.RawTGZImage(None)
image.get_size()
result = image.get_size()

self.assertEqual(124, result)
self.assertEqual(image._tar_info, mock_tar_info)
self.assertEqual(image._tar_file, mock_tar_file)
mock_as_tar.assert_called_once_with()
mock_tar_file.next.assert_called_once_with()

@mock.patch.object(tarfile, 'TarFile', spec_set=True, autospec=True)
@mock.patch.object(tarfile, 'TarInfo', spec_set=True, autospec=True)
@mock.patch.object(utils.RawTGZImage, '_as_tarfile')
@mock.patch.object(utils.shutil, 'copyfileobj')
def test_stream_to_without_size_retrieved(self, mock_copyfile,
mock_as_tar, mock_tar_info,
mock_tar_file):
target_file = mock.create_autospec(open)
source_file = mock.create_autospec(open)
mock_tar_file.next.return_value = mock_tar_info
mock_tar_file.extractfile.return_value = source_file
mock_as_tar.return_value = mock_tar_file

image = utils.RawTGZImage(None)
image._image_service_and_image_id = ('service', 'id')
image.stream_to(target_file)

mock_as_tar.assert_called_once_with()
mock_tar_file.next.assert_called_once_with()
mock_tar_file.extractfile.assert_called_once_with(mock_tar_info)
mock_copyfile.assert_called_once_with(
source_file, target_file)
mock_tar_file.close.assert_called_once_with()

@mock.patch.object(tarfile, 'TarFile', spec_set=True, autospec=True)
@mock.patch.object(tarfile, 'TarInfo', autospec=True)
@mock.patch.object(utils.RawTGZImage, '_as_tarfile')
@mock.patch.object(utils.shutil, 'copyfileobj')
def test_stream_to_with_size_retrieved(self, mock_copyfile,
mock_as_tar, mock_tar_info,
mock_tar_file):
target_file = mock.create_autospec(open)
source_file = mock.create_autospec(open)
mock_tar_info.size = 124
mock_tar_file.next.return_value = mock_tar_info
mock_tar_file.extractfile.return_value = source_file
mock_as_tar.return_value = mock_tar_file

image = utils.RawTGZImage(None)
image._image_service_and_image_id = ('service', 'id')
image.get_size()
image.stream_to(target_file)

mock_as_tar.assert_called_once_with()
mock_tar_file.next.assert_called_once_with()
mock_tar_file.extractfile.assert_called_once_with(mock_tar_info)
mock_copyfile.assert_called_once_with(
source_file, target_file)
mock_tar_file.close.assert_called_once_with()

+ 0
- 149
nova/tests/unit/virt/xenapi/image/test_vdi_stream.py View File

@@ -1,149 +0,0 @@
# Copyright 2017 Citrix System
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import mock

from os_xenapi.client import exception as xenapi_except
from os_xenapi.client import image

from nova import context
from nova import exception
from nova.image.glance import API as image_api
from nova.tests.unit.virt.xenapi import stubs
from nova.virt.xenapi.image import utils
from nova.virt.xenapi.image import vdi_stream
from nova.virt.xenapi import vm_utils


class TestVdiStreamStore(stubs.XenAPITestBaseNoDB):
def setUp(self):
super(TestVdiStreamStore, self).setUp()
self.store = vdi_stream.VdiStreamStore()

self.flags(connection_url='test_url',
image_compression_level=5,
group='xenserver')

self.session = mock.Mock()
self.context = context.RequestContext(
'user', 'project', auth_token='foobar')
self.instance = {'uuid': 'e6ad57c9-115e-4b7d-a872-63cea0ac3cf2',
'system_metadata': [],
'auto_disk_config': True,
'os_type': 'default',
'xenapi_use_agent': 'true'}

@mock.patch.object(image_api, 'download',
return_value='fake_data')
@mock.patch.object(utils, 'IterableToFileAdapter',
return_value='fake_stream')
@mock.patch.object(vm_utils, 'safe_find_sr',
return_value='fake_sr_ref')
@mock.patch.object(image, 'stream_to_vdis')
def test_download_image(self, stream_to, find_sr, to_file, download):
self.store.download_image(self.context, self.session,
self.instance, 'fake_image_uuid')

download.assert_called_once_with(self.context, 'fake_image_uuid')
to_file.assert_called_once_with('fake_data')
find_sr.assert_called_once_with(self.session)
stream_to.assert_called_once_with(self.context, self.session,
self.instance, 'test_url',
'fake_sr_ref', 'fake_stream')

@mock.patch.object(image_api, 'download',
return_value='fake_data')
@mock.patch.object(utils, 'IterableToFileAdapter',
return_value='fake_stream')
@mock.patch.object(vm_utils, 'safe_find_sr',
return_value='fake_sr_ref')
@mock.patch.object(image, 'stream_to_vdis',
side_effect=xenapi_except.OsXenApiException)
def test_download_image_exception(self, stream_to, find_sr, to_file,
download):
self.assertRaises(exception.CouldNotFetchImage,
self.store.download_image,
self.context, self.session,
self.instance, 'fake_image_uuid')

@mock.patch.object(vdi_stream.VdiStreamStore, '_get_metadata',
return_value='fake_meta_data')
@mock.patch.object(image, 'stream_from_vdis',
return_value='fake_data')
@mock.patch.object(utils, 'IterableToFileAdapter',
return_value='fake_stream')
@mock.patch.object(image_api, 'update')
def test_upload_image(self, update, to_file, to_stream, get):
fake_vdi_uuids = ['fake-vdi-uuid']
self.store.upload_image(self.context, self.session,
self.instance, 'fake_image_uuid',
fake_vdi_uuids)

get.assert_called_once_with(self.context, self.instance,
'fake_image_uuid')
to_stream.assert_called_once_with(self.context, self.session,
self.instance, 'test_url',
fake_vdi_uuids, compresslevel=5)
to_file.assert_called_once_with('fake_data')
update.assert_called_once_with(self.context, 'fake_image_uuid',
'fake_meta_data', data='fake_stream')

@mock.patch.object(vdi_stream.VdiStreamStore, '_get_metadata')
@mock.patch.object(image, 'stream_from_vdis',
side_effect=xenapi_except.OsXenApiException)
@mock.patch.object(utils, 'IterableToFileAdapter',
return_value='fake_stream')
@mock.patch.object(image_api, 'update')
def test_upload_image_exception(self, update, to_file, to_stream, get):
fake_vdi_uuids = ['fake-vdi-uuid']
self.assertRaises(exception.CouldNotUploadImage,
self.store.upload_image,
self.context, self.session,
self.instance, 'fake_image_uuid',
fake_vdi_uuids)

@mock.patch.object(image_api, 'get',
return_value={})
def test_get_metadata(self, image_get):
expect_metadata = {'disk_format': 'vhd',
'container_format': 'ovf',
'auto_disk_config': 'True',
'os_type': 'default',
'size': 0}

result = self.store._get_metadata(self.context, self.instance,
'fake_image_uuid')

self.assertEqual(result, expect_metadata)

@mock.patch.object(image_api, 'get',
return_value={})
def test_get_metadata_disabled(self, image_get):
# Verify the metadata contains auto_disk_config=disabled, when
# image_auto_disk_config is ""Disabled".
self.instance['system_metadata'] = [
{"key": "image_auto_disk_config",
"value": "Disabled"}]

expect_metadata = {'disk_format': 'vhd',
'container_format': 'ovf',
'auto_disk_config': 'disabled',
'os_type': 'default',
'size': 0}

result = self.store._get_metadata(self.context, self.instance,
'fake_image_uuid')

self.assertEqual(result, expect_metadata)

+ 0
- 204
nova/tests/unit/virt/xenapi/image/test_vdi_through_dev.py View File

@@ -1,204 +0,0 @@
# Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import contextlib
import tarfile

import eventlet
import mock
from os_xenapi.client import session as xenapi_session
import six

from nova.image import glance
from nova import test
from nova.virt.xenapi.image import vdi_through_dev


@contextlib.contextmanager
def fake_context(result=None):
yield result


class TestDelegatingToCommand(test.NoDBTestCase):
def test_upload_image_is_delegated_to_command(self):
command = mock.create_autospec(vdi_through_dev.UploadToGlanceAsRawTgz,
spec_set=True)
command.upload_image.return_value = 'result'

with mock.patch.object(vdi_through_dev, 'UploadToGlanceAsRawTgz',
return_value=command) as mock_upload:
store = vdi_through_dev.VdiThroughDevStore()
result = store.upload_image(
'ctx', 'session', 'instance', 'image_id', 'vdis')

self.assertEqual('result', result)
mock_upload.assert_called_once_with(
'ctx', 'session', 'instance', 'image_id', 'vdis')
command.upload_image.assert_called_once_with()


class TestUploadToGlanceAsRawTgz(test.NoDBTestCase):
@mock.patch.object(vdi_through_dev.vm_utils, 'vdi_attached')
@mock.patch.object(vdi_through_dev.utils, 'make_dev_path')
@mock.patch.object(vdi_through_dev.utils, 'temporary_chown')
def test_upload_image(self, mock_vdi_temp_chown,
mock_vdi_make_dev_path, mock_vdi_attached):
mock_vdi_attached.return_value = fake_context('dev')
mock_vdi_make_dev_path.return_value = 'devpath'
mock_vdi_temp_chown.return_value = fake_context()

store = vdi_through_dev.UploadToGlanceAsRawTgz(
'context', 'session', 'instance', 'id', ['vdi0', 'vdi1'])

with test.nested(
mock.patch.object(store, '_perform_upload'),
mock.patch.object(store, '_get_vdi_ref',
return_value='vdi_ref'),
) as (mock_upload, mock_get_vdi):

store.upload_image()

mock_get_vdi.assert_called_once_with()
mock_upload.assert_called_once_with('devpath')
mock_vdi_attached.assert_called_once_with(
'session', 'vdi_ref', read_only=True)
mock_vdi_make_dev_path.assert_called_once_with('dev')
mock_vdi_temp_chown.assert_called_once_with('devpath')

def test__perform_upload(self):
producer = mock.create_autospec(vdi_through_dev.TarGzProducer,
spec_set=True)
consumer = mock.create_autospec(glance.UpdateGlanceImage,
spec_set=True)
pool = mock.create_autospec(eventlet.GreenPool,
spec_set=True)
store = vdi_through_dev.UploadToGlanceAsRawTgz(
'context', 'session', 'instance', 'id', ['vdi0', 'vdi1'])

with test.nested(
mock.patch.object(store, '_create_pipe',
return_value=('readfile', 'writefile')),
mock.patch.object(store, '_get_virtual_size',
return_value='324'),
mock.patch.object(glance, 'UpdateGlanceImage',
return_value=consumer),
mock.patch.object(vdi_through_dev, 'TarGzProducer',
return_value=producer),
mock.patch.object(vdi_through_dev.eventlet, 'GreenPool',
return_value=pool)
) as (mock_create_pipe, mock_virtual_size,
mock_upload, mock_TarGzProducer, mock_greenpool):
producer.get_metadata.return_value = "metadata"

store._perform_upload('devpath')

producer.get_metadata.assert_called_once_with()
mock_virtual_size.assert_called_once_with()
mock_create_pipe.assert_called_once_with()
mock_TarGzProducer.assert_called_once_with(
'devpath', 'writefile', '324', 'disk.raw')
mock_upload.assert_called_once_with(
'context', 'id', 'metadata', 'readfile')
mock_greenpool.assert_called_once_with()
pool.spawn.assert_has_calls([mock.call(producer.start),
mock.call(consumer.start)])
pool.waitall.assert_called_once_with()

def test__get_vdi_ref(self):
session = mock.create_autospec(xenapi_session.XenAPISession,
spec_set=True)
store = vdi_through_dev.UploadToGlanceAsRawTgz(
'context', session, 'instance', 'id', ['vdi0', 'vdi1'])
session.call_xenapi.return_value = 'vdi_ref'

self.assertEqual('vdi_ref', store._get_vdi_ref())
session.call_xenapi.assert_called_once_with(
'VDI.get_by_uuid', 'vdi0')

def test__get_virtual_size(self):
session = mock.create_autospec(xenapi_session.XenAPISession,
spec_set=True)
store = vdi_through_dev.UploadToGlanceAsRawTgz(
'context', session, 'instance', 'id', ['vdi0', 'vdi1'])

with mock.patch.object(store, '_get_vdi_ref',
return_value='vdi_ref') as mock_get_vdi:
store._get_virtual_size()

mock_get_vdi.assert_called_once_with()
session.call_xenapi.assert_called_once_with(
'VDI.get_virtual_size', 'vdi_ref')

@mock.patch.object(vdi_through_dev.os, 'pipe')
@mock.patch.object(vdi_through_dev.greenio, 'GreenPipe')
def test__create_pipe(self, mock_vdi_greenpipe, mock_vdi_os_pipe):
store = vdi_through_dev.UploadToGlanceAsRawTgz(
'context', 'session', 'instance', 'id', ['vdi0', 'vdi1'])

mock_vdi_os_pipe.return_value = ('rpipe', 'wpipe')
mock_vdi_greenpipe.side_effect = ['rfile', 'wfile']

result = store._create_pipe()
self.assertEqual(('rfile', 'wfile'), result)
mock_vdi_os_pipe.assert_called_once_with()
mock_vdi_greenpipe.assert_has_calls(
[mock.call('rpipe', 'rb', 0),
mock.call('wpipe', 'wb', 0)])


class TestTarGzProducer(test.NoDBTestCase):
def test_constructor(self):
producer = vdi_through_dev.TarGzProducer('devpath', 'writefile',
'100', 'fname')

self.assertEqual('devpath', producer.fpath)
self.assertEqual('writefile', producer.output)
self.assertEqual('100', producer.size)
self.assertEqual('writefile', producer.output)

@mock.patch.object(vdi_through_dev.tarfile, 'TarInfo')
@mock.patch.object(vdi_through_dev.tarfile, 'open')
def test_start(self, mock_tar_open, mock_tar_TarInfo):
outf = six.StringIO()
producer = vdi_through_dev.TarGzProducer('fpath', outf,
'100', 'fname')

tfile = mock.create_autospec(tarfile.TarFile, spec_set=True)
tinfo = mock.create_autospec(tarfile.TarInfo)

inf = mock.create_autospec(open, spec_set=True)

mock_tar_open.return_value = fake_context(tfile)
mock_tar_TarInfo.return_value = tinfo

with mock.patch.object(producer, '_open_file',
return_value=fake_context(inf)
) as mock_open_file:
producer.start()

self.assertEqual(100, tinfo.size)
mock_tar_TarInfo.assert_called_once_with(name='fname')
mock_tar_open.assert_called_once_with(fileobj=outf, mode='w|gz')
mock_open_file.assert_called_once_with('fpath', 'rb')
tfile.addfile.assert_called_once_with(tinfo, fileobj=inf)

def test_get_metadata(self):
producer = vdi_through_dev.TarGzProducer('devpath', 'writefile',
'100', 'fname')

self.assertEqual({
'disk_format': 'raw',
'container_format': 'tgz'},
producer.get_metadata())

+ 0
- 180
nova/tests/unit/virt/xenapi/stubs.py View File

@@ -1,180 +0,0 @@
# Copyright (c) 2010 Citrix Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

"""Stubouts, mocks and fixtures for the test suite."""

import pickle
import random
import sys

import fixtures
import mock
from os_xenapi.client import session
from os_xenapi.client import XenAPI

from nova import test
from nova.virt.xenapi import fake


def stubout_session(test, cls, product_version=(5, 6, 2),
product_brand='XenServer', platform_version=(1, 9, 0),
**opt_args):
"""Stubs out methods from XenAPISession."""
test.stub_out('os_xenapi.client.session.XenAPISession._create_session',
lambda s, url: cls(url, **opt_args))
test.stub_out('os_xenapi.client.session.XenAPISession.'
'_get_product_version_and_brand',
lambda s: (product_version, product_brand))
test.stub_out('os_xenapi.client.session.XenAPISession.'
'_get_platform_version',
lambda s: platform_version)


def _make_fake_vdi():
sr_ref = fake.get_all('SR')[0]
vdi_ref = fake.create_vdi('', sr_ref)
vdi_rec = fake.get_record('VDI', vdi_ref)
return vdi_rec['uuid']


class FakeSessionForVMTests(fake.SessionBase):
"""Stubs out a XenAPISession for VM tests."""

def host_call_plugin(self, _1, _2, plugin, method, _5):
plugin = plugin.rstrip('.py')

if plugin == 'glance' and method == 'download_vhd2':
root_uuid = _make_fake_vdi()
return pickle.dumps(dict(root=dict(uuid=root_uuid)))
else:
return (super(FakeSessionForVMTests, self).
host_call_plugin(_1, _2, plugin, method, _5))

def VM_start(self, _1, ref, _2, _3):
vm = fake.get_record('VM', ref)
if vm['power_state'] != 'Halted':
raise XenAPI.Failure(['VM_BAD_POWER_STATE', ref, 'Halted',
vm['power_state']])
vm['power_state'] = 'Running'
vm['is_a_template'] = False
vm['is_control_domain'] = False
vm['domid'] = random.randrange(1, 1 << 16)
return vm

def VM_start_on(self, _1, vm_ref, host_ref, _2, _3):
vm_rec = self.VM_start(_1, vm_ref, _2, _3)
vm_rec['resident_on'] = host_ref

def VDI_snapshot(self, session_ref, vm_ref, _1):
sr_ref = "fakesr"
return fake.create_vdi('fakelabel', sr_ref, read_only=True)

def SR_scan(self, session_ref, sr_ref):
pass


class ReplaceModule(fixtures.Fixture):
"""Replace a module with a fake module."""

def __init__(self, name, new_value):
self.name = name
self.new_value = new_value

def _restore(self, old_value):
sys.modules[self.name] = old_value

def setUp(self):
super(ReplaceModule, self).setUp()
old_value = sys.modules.get(self.name)
sys.modules[self.name] = self.new_value
self.addCleanup(self._restore, old_value)


class FakeSessionForVolumeTests(fake.SessionBase):
"""Stubs out a XenAPISession for Volume tests."""
def VDI_introduce(self, _1, uuid, _2, _3, _4, _5,
_6, _7, _8, _9, _10, _11):
valid_vdi = False
refs = fake.get_all('VDI')
for ref in refs:
rec = fake.get_record('VDI', ref)
if rec['uuid'] == uuid:
valid_vdi = True
if not valid_vdi:
raise XenAPI.Failure([['INVALID_VDI', 'session', self._session]])


class FakeSessionForVolumeFailedTests(FakeSessionForVolumeTests):
"""Stubs out a XenAPISession for Volume tests: it injects failures."""
def VDI_introduce(self, _1, uuid, _2, _3, _4, _5,
_6, _7, _8, _9, _10, _11):
# This is for testing failure
raise XenAPI.Failure([['INVALID_VDI', 'session', self._session]])

def PBD_unplug(self, _1, ref):
rec = fake.get_record('PBD', ref)
rec['currently-attached'] = False

def SR_forget(self, _1, ref):
pass


class FakeSessionForFailedMigrateTests(FakeSessionForVMTests):
def VM_assert_can_migrate(self, session, vmref, migrate_data,
live, vdi_map, vif_map, options):
raise XenAPI.Failure("XenAPI VM.assert_can_migrate failed")

def host_migrate_receive(self, session, hostref, networkref, options):
raise XenAPI.Failure("XenAPI host.migrate_receive failed")

def VM_migrate_send(self, session, vmref, migrate_data, islive, vdi_map,
vif_map, options):
raise XenAPI.Failure("XenAPI VM.migrate_send failed")


# FIXME(sirp): XenAPITestBase is deprecated, all tests should be converted
# over to use XenAPITestBaseNoDB
class XenAPITestBase(test.TestCase):
def setUp(self):
super(XenAPITestBase, self).setUp()
self.useFixture(ReplaceModule('XenAPI', fake))
fake.reset()

def stubout_get_this_vm_uuid(self):
def f(session):
vms = [rec['uuid'] for rec
in fake.get_all_records('VM').values()
if rec['is_control_domain']]
return vms[0]
self.stub_out('nova.virt.xenapi.vm_utils.get_this_vm_uuid', f)


class XenAPITestBaseNoDB(test.NoDBTestCase):
def setUp(self):
super(XenAPITestBaseNoDB, self).setUp()
self.useFixture(ReplaceModule('XenAPI', fake))
fake.reset()

@staticmethod
def get_fake_session(error=None):
fake_session = mock.MagicMock()
session.apply_session_helpers(fake_session)

if error is not None:
class FakeException(Exception):
details = [error, "a", "b", "c"]

fake_session.XenAPI.Failure = FakeException
fake_session.call_xenapi.side_effect = FakeException

return fake_session

+ 0
- 471
nova/tests/unit/virt/xenapi/test_agent.py View File

@@ -1,471 +0,0 @@
# Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import base64
import time

import mock
from os_xenapi.client import host_agent
from os_xenapi.client import XenAPI
from oslo_concurrency import processutils
from oslo_utils import uuidutils

from nova import exception
from nova import test
from nova.virt.xenapi import agent


def _get_fake_instance(**kwargs):
system_metadata = []
for k, v in kwargs.items():
system_metadata.append({
"key": k,
"value": v
})

return {
"system_metadata": system_metadata,
"uuid": "uuid",
"key_data": "ssh-rsa asdf",
"os_type": "asdf",
}


class AgentTestCaseBase(test.NoDBTestCase):
def _create_agent(self, instance, session="session"):
self.session = session
self.virtapi = "virtapi"
self.vm_ref = "vm_ref"
return agent.XenAPIBasedAgent(self.session, self.virtapi,
instance, self.vm_ref)


class AgentImageFlagsTestCase(AgentTestCaseBase):
def test_agent_is_present(self):
self.flags(use_agent_default=False, group='xenserver')
instance = {"system_metadata":
[{"key": "image_xenapi_use_agent", "value": "true"}]}
self.assertTrue(agent.should_use_agent(instance))

def test_agent_is_disabled(self):
self.flags(use_agent_default=True, group='xenserver')
instance = {"system_metadata":
[{"key": "image_xenapi_use_agent", "value": "false"}]}
self.assertFalse(agent.should_use_agent(instance))

def test_agent_uses_deafault_when_prop_invalid(self):
self.flags(use_agent_default=True, group='xenserver')
instance = {"system_metadata":
[{"key": "image_xenapi_use_agent", "value": "bob"}],
"uuid": "uuid"}
self.assertTrue(agent.should_use_agent(instance))

def test_agent_default_not_present(self):
self.flags(use_agent_default=False, group='xenserver')
instance = {"system_metadata": []}
self.assertFalse(agent.should_use_agent(instance))

def test_agent_default_present(self):
self.flags(use_agent_default=True, group='xenserver')
instance = {"system_metadata": []}
self.assertTrue(agent.should_use_agent(instance))


class SysMetaKeyTestBase(object):
key = None

def _create_agent_with_value(self, value):
kwargs = {self.key: value}
instance = _get_fake_instance(**kwargs)
return self._create_agent(instance)

def test_get_sys_meta_key_true(self):
agent = self._create_agent_with_value("true")
self.assertTrue(agent._get_sys_meta_key(self.key))

def test_get_sys_meta_key_false(self):
agent = self._create_agent_with_value("False")
self.assertFalse(agent._get_sys_meta_key(self.key))

def test_get_sys_meta_key_invalid_is_false(self):
agent = self._create_agent_with_value("invalid")
self.assertFalse(agent._get_sys_meta_key(self.key))

def test_get_sys_meta_key_missing_is_false(self):
instance = _get_fake_instance()
agent = self._create_agent(instance)
self.assertFalse(agent._get_sys_meta_key(self.key))


class SkipSshFlagTestCase(SysMetaKeyTestBase, AgentTestCaseBase):
key = "image_xenapi_skip_agent_inject_ssh"

def test_skip_ssh_key_inject(self):
agent = self._create_agent_with_value("True")
self.assertTrue(agent._skip_ssh_key_inject())


class SkipFileInjectAtBootFlagTestCase(SysMetaKeyTestBase, AgentTestCaseBase):
key = "image_xenapi_skip_agent_inject_files_at_boot"

def test_skip_inject_files_at_boot(self):
agent = self._create_agent_with_value("True")
self.assertTrue(agent._skip_inject_files_at_boot())


class InjectSshTestCase(AgentTestCaseBase):
@mock.patch.object(agent.XenAPIBasedAgent, 'inject_file')
def test_inject_ssh_key_succeeds(self, mock_inject_file):
instance = _get_fake_instance()
agent = self._create_agent(instance)

agent.inject_ssh_key()
mock_inject_file.assert_called_once_with("/root/.ssh/authorized_keys",