Remove the PowerVM driver
The PowerVM driver was deprecated in November 2021 as part of change Icdef0a03c3c6f56b08ec9685c6958d6917bc88cb. As noted there, all indications suggest that this driver is no longer maintained and may be abandonware. It's been some time and there's still no activity here so it's time to abandon this for real. This isn't as tied into the codebase as the old XenAPI driver was, so removal is mostly a case of deleting large swathes of code. Lovely. Change-Id: Ibf4f36136f2c65adad64f75d665c00cf2de4b400 Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
This commit is contained in:
committed by
Balazs Gibizer
parent
c36782a96a
commit
deae814611
+1
-1
@@ -10,7 +10,7 @@ OpenStack Nova
|
||||
|
||||
OpenStack Nova provides a cloud computing fabric controller, supporting a wide
|
||||
variety of compute technologies, including: libvirt (KVM, Xen, LXC and more),
|
||||
Hyper-V, VMware, OpenStack Ironic and PowerVM.
|
||||
Hyper-V, VMware and OpenStack Ironic.
|
||||
|
||||
Use the following resources to learn more.
|
||||
|
||||
|
||||
@@ -84,8 +84,6 @@ availability zones. Nova supports the following hypervisors:
|
||||
|
||||
- `Linux Containers (LXC) <https://linuxcontainers.org>`__
|
||||
|
||||
- `PowerVM <https://www.ibm.com/us-en/marketplace/ibm-powervm>`__
|
||||
|
||||
- `Quick Emulator (QEMU) <https://wiki.qemu.org/Manual>`__
|
||||
|
||||
- `Virtuozzo <https://www.virtuozzo.com/products/vz7.html>`__
|
||||
|
||||
@@ -39,9 +39,8 @@ compute host and image.
|
||||
|
||||
.. rubric:: Compute host requirements
|
||||
|
||||
The following virt drivers support the config drive: libvirt,
|
||||
Hyper-V, VMware, and (since 17.0.0 Queens) PowerVM. The Bare Metal service also
|
||||
supports the config drive.
|
||||
The following virt drivers support the config drive: libvirt, Hyper-V and
|
||||
VMware. The Bare Metal service also supports the config drive.
|
||||
|
||||
- To use config drives with libvirt or VMware, you must first
|
||||
install the :command:`genisoimage` package on each compute host. Use the
|
||||
@@ -56,8 +55,8 @@ supports the config drive.
|
||||
:oslo.config:option:`hyperv.qemu_img_cmd` config option to the full path to an
|
||||
:command:`qemu-img` command installation.
|
||||
|
||||
- To use config drives with PowerVM or the Bare Metal service, you do not need
|
||||
to prepare anything.
|
||||
- To use config drives with the Bare Metal service, you do not need to prepare
|
||||
anything.
|
||||
|
||||
.. rubric:: Image requirements
|
||||
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
=======
|
||||
PowerVM
|
||||
=======
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
OpenStack Compute supports the PowerVM hypervisor through `NovaLink`_. In the
|
||||
NovaLink architecture, a thin NovaLink virtual machine running on the Power
|
||||
system manages virtualization for that system. The ``nova-compute`` service
|
||||
can be installed on the NovaLink virtual machine and configured to use the
|
||||
PowerVM compute driver. No external management element (e.g. Hardware
|
||||
Management Console) is needed.
|
||||
|
||||
.. _NovaLink: https://www.ibm.com/support/knowledgecenter/en/POWER8/p8eig/p8eig_kickoff.htm
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
In order to function properly, the ``nova-compute`` service must be executed
|
||||
by a member of the ``pvm_admin`` group. Use the ``usermod`` command to add the
|
||||
user. For example, to add the ``stacker`` user to the ``pvm_admin`` group, execute:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# usermod -a -G pvm_admin stacker
|
||||
|
||||
The user must re-login for the change to take effect.
|
||||
|
||||
To enable the PowerVM compute driver, configure
|
||||
:oslo.config:option:`DEFAULT.compute_driver` = ``powervm.PowerVMDriver``. For
|
||||
example:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[DEFAULT]
|
||||
compute_driver = powervm.PowerVMDriver
|
||||
|
||||
The PowerVM driver supports two types of storage for ephemeral disks:
|
||||
``localdisk`` or ``ssp``. If ``localdisk`` is selected, you must specify which
|
||||
volume group should be used. E.g.:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[powervm]
|
||||
disk_driver = localdisk
|
||||
volume_group_name = openstackvg
|
||||
|
||||
.. note::
|
||||
|
||||
Using the ``rootvg`` volume group is strongly discouraged since ``rootvg``
|
||||
is used by the management partition and filling this will cause failures.
|
||||
|
||||
The PowerVM driver also supports configuring the default amount of physical
|
||||
processor compute power (known as "proc units") which will be given to each
|
||||
vCPU. This value will be used if the requested flavor does not specify the
|
||||
``powervm:proc_units`` extra-spec. A factor value of 1.0 means a whole physical
|
||||
processor, whereas 0.05 means 1/20th of a physical processor. E.g.:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[powervm]
|
||||
proc_units_factor = 0.1
|
||||
|
||||
|
||||
Volume Support
|
||||
--------------
|
||||
|
||||
Volume support is provided for the PowerVM virt driver via Cinder. Currently,
|
||||
the only supported volume protocol is `vSCSI`__ Fibre Channel. Attach, detach,
|
||||
and extend are the operations supported by the PowerVM vSCSI FC volume adapter.
|
||||
:term:`Boot From Volume` is not yet supported.
|
||||
|
||||
.. __: https://www.ibm.com/support/knowledgecenter/en/POWER8/p8hat/p8hat_virtualscsi.htm
|
||||
@@ -11,7 +11,6 @@ Hypervisors
|
||||
hypervisor-vmware
|
||||
hypervisor-hyper-v
|
||||
hypervisor-virtuozzo
|
||||
hypervisor-powervm
|
||||
hypervisor-zvm
|
||||
hypervisor-ironic
|
||||
|
||||
@@ -44,9 +43,6 @@ The following hypervisors are supported:
|
||||
* `Virtuozzo`_ 7.0.0 and newer - OS Containers and Kernel-based Virtual
|
||||
Machines supported. The supported formats include ploop and qcow2 images.
|
||||
|
||||
* `PowerVM`_ - Server virtualization with IBM PowerVM for AIX, IBM i, and Linux
|
||||
workloads on the Power Systems platform.
|
||||
|
||||
* `zVM`_ - Server virtualization on z Systems and IBM LinuxONE, it can run Linux,
|
||||
z/OS and more.
|
||||
|
||||
@@ -68,8 +64,6 @@ virt drivers:
|
||||
|
||||
* :oslo.config:option:`compute_driver` = ``hyperv.HyperVDriver``
|
||||
|
||||
* :oslo.config:option:`compute_driver` = ``powervm.PowerVMDriver``
|
||||
|
||||
* :oslo.config:option:`compute_driver` = ``zvm.ZVMDriver``
|
||||
|
||||
* :oslo.config:option:`compute_driver` = ``fake.FakeDriver``
|
||||
@@ -83,6 +77,5 @@ virt drivers:
|
||||
.. _VMware vSphere: https://www.vmware.com/support/vsphere-hypervisor.html
|
||||
.. _Hyper-V: https://docs.microsoft.com/en-us/windows-server/virtualization/hyper-v/hyper-v-technology-overview
|
||||
.. _Virtuozzo: https://www.virtuozzo.com/products/vz7.html
|
||||
.. _PowerVM: https://www.ibm.com/us-en/marketplace/ibm-powervm
|
||||
.. _zVM: https://www.ibm.com/it-infrastructure/z/zvm
|
||||
.. _Ironic: https://docs.openstack.org/ironic/latest/
|
||||
|
||||
@@ -406,7 +406,7 @@ Some of attributes that can be used as useful key and their values contains:
|
||||
* ``free_ram_mb`` (compared with a number, values like ``>= 4096``)
|
||||
* ``free_disk_mb`` (compared with a number, values like ``>= 10240``)
|
||||
* ``host`` (compared with a string, values like ``<in> compute``, ``s== compute_01``)
|
||||
* ``hypervisor_type`` (compared with a string, values like ``s== QEMU``, ``s== powervm``)
|
||||
* ``hypervisor_type`` (compared with a string, values like ``s== QEMU``, ``s== ironic``)
|
||||
* ``hypervisor_version`` (compared with a number, values like ``>= 1005003``, ``== 2000000``)
|
||||
* ``num_instances`` (compared with a number, values like ``<= 10``)
|
||||
* ``num_io_ops`` (compared with a number, values like ``<= 5``)
|
||||
|
||||
@@ -183,16 +183,6 @@ They are only supported by the HyperV virt driver.
|
||||
|
||||
.. extra-specs:: os
|
||||
|
||||
``powervm``
|
||||
~~~~~~~~~~~
|
||||
|
||||
The following extra specs are used to configure various attributes of
|
||||
instances when using the PowerVM virt driver.
|
||||
|
||||
They are only supported by the PowerVM virt driver.
|
||||
|
||||
.. extra-specs:: powervm
|
||||
|
||||
``vmware``
|
||||
~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -34,10 +34,6 @@ link=https://wiki.openstack.org/wiki/ThirdPartySystems/IBM_z/VM_CI
|
||||
title=Ironic CI
|
||||
link=
|
||||
|
||||
[target.powervm]
|
||||
title=IBM PowerVM CI
|
||||
link=https://wiki.openstack.org/wiki/ThirdPartySystems/IBM_PowerVM_CI
|
||||
|
||||
#
|
||||
# Lists all features
|
||||
#
|
||||
@@ -70,7 +66,6 @@ driver-notes-libvirt-virtuozzo-vm=This is not tested in a CI system, but it is i
|
||||
vmware=complete
|
||||
hyperv=complete
|
||||
ironic=unknown
|
||||
powervm=complete
|
||||
zvm=complete
|
||||
|
||||
[operation.snapshot-server]
|
||||
@@ -90,7 +85,6 @@ driver-notes-libvirt-virtuozzo-vm=This is not tested in a CI system, but it is i
|
||||
vmware=unknown
|
||||
hyperv=unknown
|
||||
ironic=unknown
|
||||
powervm=complete
|
||||
zvm=complete
|
||||
|
||||
[operation.power-ops]
|
||||
@@ -109,7 +103,6 @@ driver-notes-libvirt-virtuozzo-vm=This is not tested in a CI system, but it is i
|
||||
vmware=complete
|
||||
hyperv=complete
|
||||
ironic=unknown
|
||||
powervm=complete
|
||||
zvm=complete
|
||||
|
||||
[operation.rebuild-server]
|
||||
@@ -128,7 +121,6 @@ driver-notes-libvirt-virtuozzo-vm=This is not tested in a CI system, but it is i
|
||||
vmware=complete
|
||||
hyperv=complete
|
||||
ironic=unknown
|
||||
powervm=missing
|
||||
zvm=missing
|
||||
|
||||
[operation.resize-server]
|
||||
@@ -147,7 +139,6 @@ driver-notes-libvirt-virtuozzo-vm=This is not tested in a CI system, but it is i
|
||||
vmware=complete
|
||||
hyperv=complete
|
||||
ironic=unknown
|
||||
powervm=missing
|
||||
zvm=missing
|
||||
|
||||
[operation.server-volume-ops]
|
||||
@@ -165,9 +156,6 @@ libvirt-virtuozzo-vm=complete
|
||||
vmware=complete
|
||||
hyperv=complete
|
||||
ironic=missing
|
||||
powervm=complete
|
||||
driver-notes-powervm=This is not tested for every CI run. Add a
|
||||
"powervm:volume-check" comment to trigger a CI job running volume tests.
|
||||
zvm=missing
|
||||
|
||||
[operation.server-bdm]
|
||||
@@ -189,7 +177,6 @@ vmware=partial
|
||||
driver-notes-vmware=This is not tested in a CI system, but it is implemented.
|
||||
hyperv=complete:n
|
||||
ironic=missing
|
||||
powervm=missing
|
||||
zvm=missing
|
||||
|
||||
[operation.server-neutron]
|
||||
@@ -211,7 +198,6 @@ driver-notes-vmware=This is not tested in a CI system, but it is implemented.
|
||||
hyperv=partial
|
||||
driver-notes-hyperv=This is not tested in a CI system, but it is implemented.
|
||||
ironic=missing
|
||||
powervm=complete
|
||||
zvm=partial
|
||||
driver-notes-zvm=This is not tested in a CI system, but it is implemented.
|
||||
|
||||
@@ -232,7 +218,6 @@ vmware=partial
|
||||
driver-notes-vmware=This is not tested in a CI system, but it is implemented.
|
||||
hyperv=complete
|
||||
ironic=missing
|
||||
powervm=missing
|
||||
zvm=complete
|
||||
|
||||
[operation.server-suspend]
|
||||
@@ -252,7 +237,6 @@ driver-notes-libvirt-virtuozzo-vm=This is not tested in a CI system, but it is i
|
||||
vmware=complete
|
||||
hyperv=complete
|
||||
ironic=missing
|
||||
powervm=missing
|
||||
zvm=missing
|
||||
|
||||
[operation.server-consoleoutput]
|
||||
@@ -272,7 +256,6 @@ driver-notes-vmware=This is not tested in a CI system, but it is implemented.
|
||||
hyperv=partial
|
||||
driver-notes-hyperv=This is not tested in a CI system, but it is implemented.
|
||||
ironic=missing
|
||||
powervm=complete
|
||||
zvm=complete
|
||||
|
||||
[operation.server-rescue]
|
||||
@@ -293,7 +276,6 @@ vmware=complete
|
||||
hyperv=partial
|
||||
driver-notes-hyperv=This is not tested in a CI system, but it is implemented.
|
||||
ironic=missing
|
||||
powervm=missing
|
||||
zvm=missing
|
||||
|
||||
[operation.server-configdrive]
|
||||
@@ -314,7 +296,6 @@ vmware=complete
|
||||
hyperv=complete
|
||||
ironic=partial
|
||||
driver-notes-ironic=This is not tested in a CI system, but it is implemented.
|
||||
powervm=complete
|
||||
zvm=complete
|
||||
|
||||
[operation.server-changepassword]
|
||||
@@ -334,7 +315,6 @@ vmware=missing
|
||||
hyperv=partial
|
||||
driver-notes-hyperv=This is not tested in a CI system, but it is implemented.
|
||||
ironic=missing
|
||||
powervm=missing
|
||||
zvm=missing
|
||||
|
||||
[operation.server-shelve]
|
||||
@@ -354,5 +334,4 @@ libvirt-virtuozzo-vm=complete
|
||||
vmware=missing
|
||||
hyperv=complete
|
||||
ironic=missing
|
||||
powervm=complete
|
||||
zvm=missing
|
||||
|
||||
@@ -26,10 +26,6 @@ link=https://wiki.openstack.org/wiki/ThirdPartySystems/Hyper-V_CI
|
||||
title=Ironic
|
||||
link=http://docs.openstack.org/infra/manual/developers.html#project-gating
|
||||
|
||||
[target.powervm]
|
||||
title=PowerVM CI
|
||||
link=https://wiki.openstack.org/wiki/ThirdPartySystems/IBM_PowerVM_CI
|
||||
|
||||
|
||||
[operation.gpu-passthrough]
|
||||
title=GPU Passthrough
|
||||
@@ -51,7 +47,6 @@ driver-notes-libvirt-virtuozzo-vm=This is not tested in a CI system, but it is i
|
||||
vmware=missing
|
||||
hyperv=missing
|
||||
ironic=unknown
|
||||
powervm=missing
|
||||
|
||||
|
||||
[operation.virtual-gpu]
|
||||
@@ -67,4 +62,3 @@ libvirt-virtuozzo-vm=unknown
|
||||
vmware=missing
|
||||
hyperv=missing
|
||||
ironic=missing
|
||||
powervm=missing
|
||||
|
||||
@@ -104,9 +104,6 @@ title=Hyper-V
|
||||
[driver.ironic]
|
||||
title=Ironic
|
||||
|
||||
[driver.powervm]
|
||||
title=PowerVM
|
||||
|
||||
[driver.zvm]
|
||||
title=zVM
|
||||
|
||||
@@ -133,9 +130,6 @@ driver.hyperv=complete
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=complete
|
||||
driver-notes.powervm=This is not tested for every CI run. Add a
|
||||
"powervm:volume-check" comment to trigger a CI job running volume tests.
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.attach-tagged-volume]
|
||||
@@ -155,7 +149,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.detach-volume]
|
||||
@@ -174,9 +167,6 @@ driver.hyperv=complete
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=complete
|
||||
driver-notes.powervm=This is not tested for every CI run. Add a
|
||||
"powervm:volume-check" comment to trigger a CI job running volume tests.
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.extend-volume]
|
||||
@@ -202,9 +192,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=unknown
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=complete
|
||||
driver-notes.powervm=This is not tested for every CI run. Add a
|
||||
"powervm:volume-check" comment to trigger a CI job running volume tests.
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.attach-interface]
|
||||
@@ -232,7 +219,6 @@ driver-notes.hyperv=Works without issue if instance is off. When
|
||||
driver.ironic=complete
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver.powervm=complete
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.attach-tagged-interface]
|
||||
@@ -252,7 +238,6 @@ driver.hyperv=complete
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.detach-interface]
|
||||
@@ -274,7 +259,6 @@ driver-notes.hyperv=Works without issue if instance is off. When
|
||||
driver.ironic=complete
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver.powervm=complete
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.maintenance-mode]
|
||||
@@ -299,7 +283,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.evacuate]
|
||||
@@ -325,7 +308,6 @@ driver.hyperv=unknown
|
||||
driver.ironic=unknown
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=unknown
|
||||
|
||||
[operation.rebuild]
|
||||
@@ -348,7 +330,6 @@ driver.hyperv=complete
|
||||
driver.ironic=complete
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver.powervm=missing
|
||||
driver.zvm=unknown
|
||||
|
||||
[operation.get-guest-info]
|
||||
@@ -370,7 +351,6 @@ driver.hyperv=complete
|
||||
driver.ironic=complete
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver.powervm=complete
|
||||
driver.zvm=complete
|
||||
|
||||
[operation.get-host-uptime]
|
||||
@@ -390,7 +370,6 @@ driver.hyperv=complete
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver.powervm=complete
|
||||
driver.zvm=complete
|
||||
|
||||
[operation.get-host-ip]
|
||||
@@ -410,7 +389,6 @@ driver.hyperv=complete
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver.powervm=complete
|
||||
driver.zvm=complete
|
||||
|
||||
[operation.live-migrate]
|
||||
@@ -439,7 +417,6 @@ driver.hyperv=complete
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.force-live-migration-to-complete]
|
||||
@@ -471,7 +448,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.abort-in-progress-live-migration]
|
||||
@@ -500,7 +476,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=unknown
|
||||
driver.libvirt-vz-ct=unknown
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.launch]
|
||||
@@ -521,7 +496,6 @@ driver.hyperv=complete
|
||||
driver.ironic=complete
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver.powervm=complete
|
||||
driver.zvm=complete
|
||||
|
||||
[operation.pause]
|
||||
@@ -548,7 +522,6 @@ driver.hyperv=complete
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=complete
|
||||
|
||||
[operation.reboot]
|
||||
@@ -571,7 +544,6 @@ driver.hyperv=complete
|
||||
driver.ironic=complete
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver.powervm=complete
|
||||
driver.zvm=complete
|
||||
|
||||
[operation.rescue]
|
||||
@@ -597,7 +569,6 @@ driver.hyperv=complete
|
||||
driver.ironic=complete
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.resize]
|
||||
@@ -625,7 +596,6 @@ driver.libvirt-vz-vm=complete
|
||||
driver-notes.vz-vm=Resizing Virtuozzo instances implies guest filesystem resize also
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver-notes.vz-ct=Resizing Virtuozzo instances implies guest filesystem resize also
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.resume]
|
||||
@@ -644,7 +614,6 @@ driver.hyperv=complete
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.set-admin-password]
|
||||
@@ -677,7 +646,6 @@ driver.libvirt-vz-vm=complete
|
||||
driver-notes.libvirt-vz-vm=Requires libvirt>=2.0.0
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver-notes.libvirt-vz-ct=Requires libvirt>=2.0.0
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.snapshot]
|
||||
@@ -705,11 +673,6 @@ driver.hyperv=complete
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver.powervm=complete
|
||||
driver-notes.powervm=When using the localdisk disk driver, snapshot is only
|
||||
supported if I/O is being hosted by the management partition. If hosting I/O
|
||||
on traditional VIOS, we are limited by the fact that a VSCSI device can't be
|
||||
mapped to two partitions (the VIOS and the management) at once.
|
||||
driver.zvm=complete
|
||||
|
||||
[operation.suspend]
|
||||
@@ -742,7 +705,6 @@ driver.hyperv=complete
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.swap-volume]
|
||||
@@ -768,7 +730,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.terminate]
|
||||
@@ -794,7 +755,6 @@ driver.hyperv=complete
|
||||
driver.ironic=complete
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver.powervm=complete
|
||||
driver.zvm=complete
|
||||
|
||||
[operation.trigger-crash-dump]
|
||||
@@ -817,7 +777,6 @@ driver.hyperv=missing
|
||||
driver.ironic=complete
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.unpause]
|
||||
@@ -836,7 +795,6 @@ driver.hyperv=complete
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver.powervm=missing
|
||||
driver.zvm=complete
|
||||
|
||||
[guest.disk.autoconfig]
|
||||
@@ -857,7 +815,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=complete
|
||||
|
||||
[guest.disk.rate-limit]
|
||||
@@ -881,7 +838,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[guest.setup.configdrive]
|
||||
@@ -909,7 +865,6 @@ driver.hyperv=complete
|
||||
driver.ironic=complete
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=complete
|
||||
driver.zvm=complete
|
||||
|
||||
[guest.setup.inject.file]
|
||||
@@ -936,7 +891,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[guest.setup.inject.networking]
|
||||
@@ -967,7 +921,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[console.rdp]
|
||||
@@ -993,7 +946,6 @@ driver.hyperv=complete
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[console.serial.log]
|
||||
@@ -1020,7 +972,6 @@ driver.hyperv=complete
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=complete
|
||||
|
||||
[console.serial.interactive]
|
||||
@@ -1048,7 +999,6 @@ driver.hyperv=complete
|
||||
driver.ironic=complete
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[console.spice]
|
||||
@@ -1074,7 +1024,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[console.vnc]
|
||||
@@ -1100,7 +1049,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver.powervm=complete
|
||||
driver.zvm=missing
|
||||
|
||||
[storage.block]
|
||||
@@ -1128,9 +1076,6 @@ driver.hyperv=complete
|
||||
driver.ironic=complete
|
||||
driver.libvirt-vz-vm=partial
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=complete
|
||||
driver-notes.powervm=This is not tested for every CI run. Add a
|
||||
"powervm:volume-check" comment to trigger a CI job running volume tests.
|
||||
driver.zvm=missing
|
||||
|
||||
[storage.block.backend.fibrechannel]
|
||||
@@ -1152,9 +1097,6 @@ driver.hyperv=complete
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=complete
|
||||
driver-notes.powervm=This is not tested for every CI run. Add a
|
||||
"powervm:volume-check" comment to trigger a CI job running volume tests.
|
||||
driver.zvm=missing
|
||||
|
||||
[storage.block.backend.iscsi]
|
||||
@@ -1179,7 +1121,6 @@ driver.hyperv=complete
|
||||
driver.ironic=complete
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[storage.block.backend.iscsi.auth.chap]
|
||||
@@ -1201,7 +1142,6 @@ driver.hyperv=complete
|
||||
driver.ironic=complete
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[storage.image]
|
||||
@@ -1225,7 +1165,6 @@ driver.hyperv=complete
|
||||
driver.ironic=complete
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver.powervm=complete
|
||||
driver.zvm=complete
|
||||
|
||||
[operation.uefi-boot]
|
||||
@@ -1247,7 +1186,6 @@ driver.ironic=partial
|
||||
driver-notes.ironic=depends on hardware support
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.device-tags]
|
||||
@@ -1277,7 +1215,6 @@ driver.hyperv=complete
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=unknown
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.quiesce]
|
||||
@@ -1298,7 +1235,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.unquiesce]
|
||||
@@ -1317,7 +1253,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.multiattach-volume]
|
||||
@@ -1341,7 +1276,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.encrypted-volume]
|
||||
@@ -1371,7 +1305,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=unknown
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.trusted-certs]
|
||||
@@ -1394,7 +1327,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.file-backed-memory]
|
||||
@@ -1417,7 +1349,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.report-cpu-traits]
|
||||
@@ -1438,7 +1369,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.port-with-resource-request]
|
||||
@@ -1461,7 +1391,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.boot-encrypted-vm]
|
||||
@@ -1487,7 +1416,6 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.cache-images]
|
||||
@@ -1510,10 +1438,6 @@ driver.hyperv=partial
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=complete
|
||||
driver.libvirt-vz-ct=complete
|
||||
driver.powervm=partial
|
||||
driver-notes.powervm=The PowerVM driver does image caching natively when using
|
||||
the SSP disk driver. It does not use the config options in the [image_cache]
|
||||
group.
|
||||
driver.zvm=missing
|
||||
|
||||
[operation.boot-emulated-tpm]
|
||||
@@ -1537,5 +1461,4 @@ driver.hyperv=missing
|
||||
driver.ironic=missing
|
||||
driver.libvirt-vz-vm=missing
|
||||
driver.libvirt-vz-ct=missing
|
||||
driver.powervm=missing
|
||||
driver.zvm=missing
|
||||
|
||||
@@ -1,271 +0,0 @@
|
||||
# Copyright 2020 Red Hat, Inc. 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.
|
||||
|
||||
"""Validators for ``powervm`` namespaced extra specs.
|
||||
|
||||
These were all taken from the IBM documentation.
|
||||
|
||||
https://www.ibm.com/support/knowledgecenter/SSXK2N_1.4.4/com.ibm.powervc.standard.help.doc/powervc_pg_flavorsextraspecs_hmc.html
|
||||
"""
|
||||
|
||||
from nova.api.validation.extra_specs import base
|
||||
|
||||
|
||||
# TODO(stephenfin): A lot of these seem to overlap with existing 'hw:' extra
|
||||
# specs and could be deprecated in favour of those.
|
||||
EXTRA_SPEC_VALIDATORS = [
|
||||
base.ExtraSpecValidator(
|
||||
name='powervm:min_mem',
|
||||
description=(
|
||||
'Minimum memory (MB). If you do not specify the value, the value '
|
||||
'is defaulted to the value for ``memory_mb``.'
|
||||
),
|
||||
value={
|
||||
'type': int,
|
||||
'min': 256,
|
||||
'description': 'Integer >=256 divisible by LMB size of the target',
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='powervm:max_mem',
|
||||
description=(
|
||||
'Maximum memory (MB). If you do not specify the value, the value '
|
||||
'is defaulted to the value for ``memory_mb``.'
|
||||
),
|
||||
value={
|
||||
'type': int,
|
||||
'min': 256,
|
||||
'description': 'Integer >=256 divisible by LMB size of the target',
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='powervm:min_vcpu',
|
||||
description=(
|
||||
'Minimum virtual processors. Minimum resource that is required '
|
||||
'for LPAR to boot is 1. The maximum value can be equal to the '
|
||||
'value, which is set to vCPUs. If you specify the value of the '
|
||||
'attribute, you must also specify value of powervm:max_vcpu. '
|
||||
'Defaults to value set for vCPUs.'
|
||||
),
|
||||
value={
|
||||
'type': int,
|
||||
'min': 1,
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='powervm:max_vcpu',
|
||||
description=(
|
||||
'Minimum virtual processors. Minimum resource that is required '
|
||||
'for LPAR to boot is 1. The maximum value can be equal to the '
|
||||
'value, which is set to vCPUs. If you specify the value of the '
|
||||
'attribute, you must also specify value of powervm:max_vcpu. '
|
||||
'Defaults to value set for vCPUs.'
|
||||
),
|
||||
value={
|
||||
'type': int,
|
||||
'min': 1,
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='powervm:proc_units',
|
||||
description=(
|
||||
'The wanted ``proc_units``. The value for the attribute cannot be '
|
||||
'less than 1/10 of the value that is specified for Virtual '
|
||||
'CPUs (vCPUs) for hosts with firmware level 7.5 or earlier and '
|
||||
'1/20 of the value that is specified for vCPUs for hosts with '
|
||||
'firmware level 7.6 or later. If the value is not specified '
|
||||
'during deployment, it is defaulted to vCPUs * 0.5.'
|
||||
),
|
||||
value={
|
||||
'type': str,
|
||||
'pattern': r'\d+\.\d+',
|
||||
'description': (
|
||||
'Float (divisible by 0.1 for hosts with firmware level 7.5 or '
|
||||
'earlier and 0.05 for hosts with firmware level 7.6 or later)'
|
||||
),
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='powervm:min_proc_units',
|
||||
description=(
|
||||
'Minimum ``proc_units``. The minimum value for the attribute is '
|
||||
'0.1 for hosts with firmware level 7.5 or earlier and 0.05 for '
|
||||
'hosts with firmware level 7.6 or later. The maximum value must '
|
||||
'be equal to the maximum value of ``powervm:proc_units``. If you '
|
||||
'specify the attribute, you must also specify '
|
||||
'``powervm:proc_units``, ``powervm:max_proc_units``, '
|
||||
'``powervm:min_vcpu``, `powervm:max_vcpu``, and '
|
||||
'``powervm:dedicated_proc``. Set the ``powervm:dedicated_proc`` '
|
||||
'to false.'
|
||||
'\n'
|
||||
'The value for the attribute cannot be less than 1/10 of the '
|
||||
'value that is specified for powervm:min_vcpu for hosts with '
|
||||
'firmware level 7.5 or earlier and 1/20 of the value that is '
|
||||
'specified for ``powervm:min_vcpu`` for hosts with firmware '
|
||||
'level 7.6 or later. If you do not specify the value of the '
|
||||
'attribute during deployment, it is defaulted to equal the value '
|
||||
'of ``powervm:proc_units``.'
|
||||
),
|
||||
value={
|
||||
'type': str,
|
||||
'pattern': r'\d+\.\d+',
|
||||
'description': (
|
||||
'Float (divisible by 0.1 for hosts with firmware level 7.5 or '
|
||||
'earlier and 0.05 for hosts with firmware level 7.6 or later)'
|
||||
),
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='powervm:max_proc_units',
|
||||
description=(
|
||||
'Maximum ``proc_units``. The minimum value can be equal to `` '
|
||||
'``powervm:proc_units``. The maximum value for the attribute '
|
||||
'cannot be more than the value of the host for maximum allowed '
|
||||
'processors per partition. If you specify this attribute, you '
|
||||
'must also specify ``powervm:proc_units``, '
|
||||
'``powervm:min_proc_units``, ``powervm:min_vcpu``, '
|
||||
'``powervm:max_vcpu``, and ``powervm:dedicated_proc``. Set the '
|
||||
'``powervm:dedicated_proc`` to false.'
|
||||
'\n'
|
||||
'The value for the attribute cannot be less than 1/10 of the '
|
||||
'value that is specified for powervm:max_vcpu for hosts with '
|
||||
'firmware level 7.5 or earlier and 1/20 of the value that is '
|
||||
'specified for ``powervm:max_vcpu`` for hosts with firmware '
|
||||
'level 7.6 or later. If you do not specify the value of the '
|
||||
'attribute during deployment, the value is defaulted to equal the '
|
||||
'value of ``powervm:proc_units``.'
|
||||
),
|
||||
value={
|
||||
'type': str,
|
||||
'pattern': r'\d+\.\d+',
|
||||
'description': (
|
||||
'Float (divisible by 0.1 for hosts with firmware level 7.5 or '
|
||||
'earlier and 0.05 for hosts with firmware level 7.6 or later)'
|
||||
),
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='powervm:dedicated_proc',
|
||||
description=(
|
||||
'Use dedicated processors. The attribute defaults to false.'
|
||||
),
|
||||
value={
|
||||
'type': bool,
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='powervm:shared_weight',
|
||||
description=(
|
||||
'Shared processor weight. When ``powervm:dedicated_proc`` is set '
|
||||
'to true and ``powervm:uncapped`` is also set to true, the value '
|
||||
'of the attribute defaults to 128.'
|
||||
),
|
||||
value={
|
||||
'type': int,
|
||||
'min': 0,
|
||||
'max': 255,
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='powervm:availability_priority',
|
||||
description=(
|
||||
'Availability priority. The attribute priority of the server if '
|
||||
'there is a processor failure and there are not enough resources '
|
||||
'for all servers. VIOS and i5 need to remain high priority '
|
||||
'default of 191. The value of the attribute defaults to 128.'
|
||||
),
|
||||
value={
|
||||
'type': int,
|
||||
'min': 0,
|
||||
'max': 255,
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='powervm:uncapped',
|
||||
description=(
|
||||
'LPAR can use unused processor cycles that are beyond or exceed '
|
||||
'the wanted setting of the attribute. This attribute is '
|
||||
'supported only when ``powervm:dedicated_proc`` is set to false. '
|
||||
'When ``powervm:dedicated_proc`` is set to false, '
|
||||
'``powervm:uncapped`` defaults to true.'
|
||||
),
|
||||
value={
|
||||
'type': bool,
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='powervm:dedicated_sharing_mode',
|
||||
description=(
|
||||
'Sharing mode for dedicated processors. The attribute is '
|
||||
'supported only when ``powervm:dedicated_proc`` is set to true.'
|
||||
),
|
||||
value={
|
||||
'type': str,
|
||||
'enum': (
|
||||
'share_idle_procs',
|
||||
'keep_idle_procs',
|
||||
'share_idle_procs_active',
|
||||
'share_idle_procs_always',
|
||||
)
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='powervm:processor_compatibility',
|
||||
description=(
|
||||
'A processor compatibility mode is a value that is assigned to a '
|
||||
'logical partition by the hypervisor that specifies the processor '
|
||||
'environment in which the logical partition can successfully '
|
||||
'operate.'
|
||||
),
|
||||
value={
|
||||
'type': str,
|
||||
'enum': (
|
||||
'default',
|
||||
'POWER6',
|
||||
'POWER6+',
|
||||
'POWER6_Enhanced',
|
||||
'POWER6+_Enhanced',
|
||||
'POWER7',
|
||||
'POWER8'
|
||||
),
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='powervm:shared_proc_pool_name',
|
||||
description=(
|
||||
'Specifies the shared processor pool to be targeted during '
|
||||
'deployment of a virtual machine.'
|
||||
),
|
||||
value={
|
||||
'type': str,
|
||||
'description': 'String with upper limit of 14 characters',
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='powervm:srr_capability',
|
||||
description=(
|
||||
'If the value of simplified remote restart capability is set to '
|
||||
'true for the LPAR, you can remote restart the LPAR to supported '
|
||||
'CEC or host when the source CEC or host is down. The attribute '
|
||||
'defaults to false.'
|
||||
),
|
||||
value={
|
||||
'type': bool,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
return EXTRA_SPEC_VALIDATORS
|
||||
@@ -49,7 +49,6 @@ from nova.conf import novnc
|
||||
from nova.conf import paths
|
||||
from nova.conf import pci
|
||||
from nova.conf import placement
|
||||
from nova.conf import powervm
|
||||
from nova.conf import quota
|
||||
from nova.conf import rdp
|
||||
from nova.conf import rpc
|
||||
@@ -99,7 +98,6 @@ novnc.register_opts(CONF)
|
||||
paths.register_opts(CONF)
|
||||
pci.register_opts(CONF)
|
||||
placement.register_opts(CONF)
|
||||
powervm.register_opts(CONF)
|
||||
quota.register_opts(CONF)
|
||||
rdp.register_opts(CONF)
|
||||
rpc.register_opts(CONF)
|
||||
|
||||
@@ -41,7 +41,6 @@ Possible values:
|
||||
* ``ironic.IronicDriver``
|
||||
* ``vmwareapi.VMwareVCDriver``
|
||||
* ``hyperv.HyperVDriver``
|
||||
* ``powervm.PowerVMDriver``
|
||||
* ``zvm.ZVMDriver``
|
||||
"""),
|
||||
cfg.BoolOpt('allow_resize_to_same_host',
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
# Copyright 2018 IBM Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
|
||||
powervm_group = cfg.OptGroup(
|
||||
name="powervm",
|
||||
title="PowerVM Options",
|
||||
help="""
|
||||
PowerVM options allow cloud administrators to configure how OpenStack will work
|
||||
with the PowerVM hypervisor.
|
||||
""")
|
||||
|
||||
powervm_opts = [
|
||||
cfg.FloatOpt(
|
||||
'proc_units_factor',
|
||||
default=0.1,
|
||||
min=0.05,
|
||||
max=1,
|
||||
help="""
|
||||
Factor used to calculate the amount of physical processor compute power given
|
||||
to each vCPU. E.g. A value of 1.0 means a whole physical processor, whereas
|
||||
0.05 means 1/20th of a physical processor.
|
||||
"""),
|
||||
cfg.StrOpt('disk_driver',
|
||||
choices=['localdisk', 'ssp'], ignore_case=True,
|
||||
default='localdisk',
|
||||
help="""
|
||||
The disk driver to use for PowerVM disks. PowerVM provides support for
|
||||
localdisk and PowerVM Shared Storage Pool disk drivers.
|
||||
|
||||
Related options:
|
||||
|
||||
* volume_group_name - required when using localdisk
|
||||
|
||||
"""),
|
||||
cfg.StrOpt('volume_group_name',
|
||||
default='',
|
||||
help="""
|
||||
Volume Group to use for block device operations. If disk_driver is localdisk,
|
||||
then this attribute must be specified. It is strongly recommended NOT to use
|
||||
rootvg since that is used by the management partition and filling it will cause
|
||||
failures.
|
||||
"""),
|
||||
]
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_group(powervm_group)
|
||||
conf.register_opts(powervm_opts, group=powervm_group)
|
||||
|
||||
|
||||
def list_opts():
|
||||
return {powervm_group: powervm_opts}
|
||||
@@ -2140,11 +2140,6 @@ class InvalidPCINUMAAffinity(Invalid):
|
||||
msg_fmt = _("Invalid PCI NUMA affinity configured: %(policy)s")
|
||||
|
||||
|
||||
class PowerVMAPIFailed(NovaException):
|
||||
msg_fmt = _("PowerVM API failed to complete for instance=%(inst_name)s. "
|
||||
"%(reason)s")
|
||||
|
||||
|
||||
class TraitRetrievalFailed(NovaException):
|
||||
msg_fmt = _("Failed to retrieve traits from the placement API: %(error)s")
|
||||
|
||||
|
||||
@@ -340,42 +340,6 @@ class HyperVLiveMigrateData(LiveMigrateData):
|
||||
del primitive['is_shared_instance_path']
|
||||
|
||||
|
||||
@obj_base.NovaObjectRegistry.register
|
||||
class PowerVMLiveMigrateData(LiveMigrateData):
|
||||
# Version 1.0: Initial version
|
||||
# Version 1.1: Added the Virtual Ethernet Adapter VLAN mappings.
|
||||
# Version 1.2: Added old_vol_attachment_ids
|
||||
# Version 1.3: Added wait_for_vif_plugged
|
||||
# Version 1.4: Inherited vifs from LiveMigrateData
|
||||
VERSION = '1.4'
|
||||
|
||||
fields = {
|
||||
'host_mig_data': fields.DictOfNullableStringsField(),
|
||||
'dest_ip': fields.StringField(),
|
||||
'dest_user_id': fields.StringField(),
|
||||
'dest_sys_name': fields.StringField(),
|
||||
'public_key': fields.StringField(),
|
||||
'dest_proc_compat': fields.StringField(),
|
||||
'vol_data': fields.DictOfNullableStringsField(),
|
||||
'vea_vlan_mappings': fields.DictOfNullableStringsField(),
|
||||
}
|
||||
|
||||
def obj_make_compatible(self, primitive, target_version):
|
||||
super(PowerVMLiveMigrateData, self).obj_make_compatible(
|
||||
primitive, target_version)
|
||||
target_version = versionutils.convert_version_to_tuple(target_version)
|
||||
if target_version < (1, 4) and 'vifs' in primitive:
|
||||
del primitive['vifs']
|
||||
if target_version < (1, 3) and 'wait_for_vif_plugged' in primitive:
|
||||
del primitive['wait_for_vif_plugged']
|
||||
if target_version < (1, 2):
|
||||
if 'old_vol_attachment_ids' in primitive:
|
||||
del primitive['old_vol_attachment_ids']
|
||||
if target_version < (1, 1):
|
||||
if 'vea_vlan_mappings' in primitive:
|
||||
del primitive['vea_vlan_mappings']
|
||||
|
||||
|
||||
@obj_base.NovaObjectRegistry.register
|
||||
class VMwareLiveMigrateData(LiveMigrateData):
|
||||
VERSION = '1.0'
|
||||
|
||||
Vendored
+4
-8
@@ -29,7 +29,6 @@ import warnings
|
||||
import eventlet
|
||||
import fixtures
|
||||
import futurist
|
||||
import mock as mock_the_lib
|
||||
from openstack import service_description
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_config import cfg
|
||||
@@ -1609,13 +1608,10 @@ class GenericPoisonFixture(fixtures.Fixture):
|
||||
for component in components[1:]:
|
||||
current = getattr(current, component)
|
||||
|
||||
# TODO(stephenfin): Remove mock_the_lib check once pypowervm is
|
||||
# no longer using mock and we no longer have mock in
|
||||
# requirements
|
||||
if not isinstance(
|
||||
getattr(current, attribute),
|
||||
(mock.Mock, mock_the_lib.Mock),
|
||||
):
|
||||
# NOTE(stephenfin): There are a couple of mock libraries in use
|
||||
# (including mocked versions of mock from oslotest) so we can't
|
||||
# use isinstance checks here
|
||||
if 'mock' not in str(type(getattr(current, attribute))):
|
||||
self.useFixture(fixtures.MonkeyPatch(
|
||||
meth, poison_configure(meth, why)))
|
||||
except ImportError:
|
||||
|
||||
@@ -28,7 +28,7 @@ class TestValidators(test.NoDBTestCase):
|
||||
"""
|
||||
namespaces = {
|
||||
'accel', 'aggregate_instance_extra_specs', 'capabilities', 'hw',
|
||||
'hw_rng', 'hw_video', 'os', 'pci_passthrough', 'powervm', 'quota',
|
||||
'hw_rng', 'hw_video', 'os', 'pci_passthrough', 'quota',
|
||||
'resources(?P<group>([a-zA-Z0-9_-]{1,64})?)',
|
||||
'trait(?P<group>([a-zA-Z0-9_-]{1,64})?)', 'vmware',
|
||||
}
|
||||
@@ -101,9 +101,7 @@ class TestValidators(test.NoDBTestCase):
|
||||
valid_specs = (
|
||||
('hw:numa_nodes', '1'),
|
||||
('os:monitors', '1'),
|
||||
('powervm:shared_weight', '1'),
|
||||
('os:monitors', '8'),
|
||||
('powervm:shared_weight', '255'),
|
||||
)
|
||||
for key, value in valid_specs:
|
||||
validators.validate(key, value)
|
||||
@@ -113,9 +111,7 @@ class TestValidators(test.NoDBTestCase):
|
||||
('hw:serial_port_count', '!'), # NaN
|
||||
('hw:numa_nodes', '0'), # has min
|
||||
('os:monitors', '0'), # has min
|
||||
('powervm:shared_weight', '-1'), # has min
|
||||
('os:monitors', '9'), # has max
|
||||
('powervm:shared_weight', '256'), # has max
|
||||
)
|
||||
for key, value in invalid_specs:
|
||||
with testtools.ExpectedException(exception.ValidationError):
|
||||
|
||||
@@ -168,7 +168,7 @@ class BaseTestCase(test.TestCase):
|
||||
'uuid': uuids.fake_compute_node,
|
||||
'vcpus_used': 0,
|
||||
'deleted': 0,
|
||||
'hypervisor_type': 'powervm',
|
||||
'hypervisor_type': 'libvirt',
|
||||
'created_at': '2013-04-01T00:27:06.000000',
|
||||
'local_gb_used': 0,
|
||||
'updated_at': '2013-04-03T00:35:41.000000',
|
||||
@@ -178,7 +178,7 @@ class BaseTestCase(test.TestCase):
|
||||
'current_workload': 0,
|
||||
'vcpus': 16,
|
||||
'mapped': 1,
|
||||
'cpu_info': 'ppc64,powervm,3940',
|
||||
'cpu_info': 'ppc64,libvirt,3940',
|
||||
'running_vms': 0,
|
||||
'free_disk_gb': 259,
|
||||
'service_id': 7,
|
||||
|
||||
@@ -219,67 +219,6 @@ class TestRemoteHyperVLiveMigrateData(test_objects._RemoteTest,
|
||||
pass
|
||||
|
||||
|
||||
class _TestPowerVMLiveMigrateData(object):
|
||||
@staticmethod
|
||||
def _mk_obj():
|
||||
return migrate_data.PowerVMLiveMigrateData(
|
||||
host_mig_data=dict(one=2),
|
||||
dest_ip='1.2.3.4',
|
||||
dest_user_id='a_user',
|
||||
dest_sys_name='a_sys',
|
||||
public_key='a_key',
|
||||
dest_proc_compat='POWER7',
|
||||
vol_data=dict(three=4),
|
||||
vea_vlan_mappings=dict(five=6),
|
||||
old_vol_attachment_ids=dict(seven=8),
|
||||
wait_for_vif_plugged=True)
|
||||
|
||||
@staticmethod
|
||||
def _mk_leg():
|
||||
return {
|
||||
'host_mig_data': {'one': '2'},
|
||||
'dest_ip': '1.2.3.4',
|
||||
'dest_user_id': 'a_user',
|
||||
'dest_sys_name': 'a_sys',
|
||||
'public_key': 'a_key',
|
||||
'dest_proc_compat': 'POWER7',
|
||||
'vol_data': {'three': '4'},
|
||||
'vea_vlan_mappings': {'five': '6'},
|
||||
'old_vol_attachment_ids': {'seven': '8'},
|
||||
'wait_for_vif_plugged': True
|
||||
}
|
||||
|
||||
def test_migrate_data(self):
|
||||
obj = self._mk_obj()
|
||||
self.assertEqual('a_key', obj.public_key)
|
||||
obj.public_key = 'key2'
|
||||
self.assertEqual('key2', obj.public_key)
|
||||
|
||||
def test_obj_make_compatible(self):
|
||||
obj = self._mk_obj()
|
||||
|
||||
data = lambda x: x['nova_object.data']
|
||||
|
||||
primitive = data(obj.obj_to_primitive())
|
||||
self.assertIn('vea_vlan_mappings', primitive)
|
||||
primitive = data(obj.obj_to_primitive(target_version='1.0'))
|
||||
self.assertNotIn('vea_vlan_mappings', primitive)
|
||||
primitive = data(obj.obj_to_primitive(target_version='1.1'))
|
||||
self.assertNotIn('old_vol_attachment_ids', primitive)
|
||||
primitive = data(obj.obj_to_primitive(target_version='1.2'))
|
||||
self.assertNotIn('wait_for_vif_plugged', primitive)
|
||||
|
||||
|
||||
class TestPowerVMLiveMigrateData(test_objects._LocalTest,
|
||||
_TestPowerVMLiveMigrateData):
|
||||
pass
|
||||
|
||||
|
||||
class TestRemotePowerVMLiveMigrateData(test_objects._RemoteTest,
|
||||
_TestPowerVMLiveMigrateData):
|
||||
pass
|
||||
|
||||
|
||||
class TestVIFMigrateData(test.NoDBTestCase):
|
||||
|
||||
def test_get_dest_vif_source_vif_not_set(self):
|
||||
|
||||
@@ -1117,7 +1117,6 @@ object_data = {
|
||||
'PciDeviceList': '1.3-52ff14355491c8c580bdc0ba34c26210',
|
||||
'PciDevicePool': '1.1-3f5ddc3ff7bfa14da7f6c7e9904cc000',
|
||||
'PciDevicePoolList': '1.1-15ecf022a68ddbb8c2a6739cfc9f8f5e',
|
||||
'PowerVMLiveMigrateData': '1.4-a745f4eda16b45e1bc5686a0c498f27e',
|
||||
'Quotas': '1.3-3b2b91371f60e788035778fc5f87797d',
|
||||
'QuotasNoOp': '1.3-d1593cf969c81846bc8192255ea95cce',
|
||||
'RequestGroup': '1.3-0458d350a8ec9d0673f9be5640a990ce',
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
# Copyright 2014, 2017 IBM Corp.
|
||||
#
|
||||
# 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 unittest
|
||||
|
||||
from oslo_utils.fixture import uuidsentinel
|
||||
|
||||
from nova.compute import power_state
|
||||
from nova.compute import vm_states
|
||||
from nova import objects
|
||||
|
||||
try:
|
||||
import powervm # noqa: F401
|
||||
except ImportError:
|
||||
raise unittest.SkipTest(
|
||||
"The 'pypowervm' dependency is not installed."
|
||||
)
|
||||
|
||||
|
||||
TEST_FLAVOR = objects.flavor.Flavor(
|
||||
memory_mb=2048,
|
||||
swap=0,
|
||||
vcpu_weight=None,
|
||||
root_gb=10, id=2,
|
||||
name=u'm1.small',
|
||||
ephemeral_gb=0,
|
||||
rxtx_factor=1.0,
|
||||
flavorid=uuidsentinel.flav_id,
|
||||
vcpus=1)
|
||||
|
||||
TEST_INSTANCE = objects.Instance(
|
||||
id=1,
|
||||
uuid=uuidsentinel.inst_id,
|
||||
display_name='Fake Instance',
|
||||
root_gb=10,
|
||||
ephemeral_gb=0,
|
||||
instance_type_id=TEST_FLAVOR.id,
|
||||
system_metadata={'image_os_distro': 'rhel'},
|
||||
host='host1',
|
||||
flavor=TEST_FLAVOR,
|
||||
task_state=None,
|
||||
vm_state=vm_states.STOPPED,
|
||||
power_state=power_state.SHUTDOWN,
|
||||
)
|
||||
|
||||
IMAGE1 = {
|
||||
'id': uuidsentinel.img_id,
|
||||
'name': 'image1',
|
||||
'size': 300,
|
||||
'container_format': 'bare',
|
||||
'disk_format': 'raw',
|
||||
'checksum': 'b518a8ba2b152b5607aceb5703fac072',
|
||||
}
|
||||
TEST_IMAGE1 = objects.image_meta.ImageMeta.from_dict(IMAGE1)
|
||||
@@ -1,52 +0,0 @@
|
||||
# Copyright 2018 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from nova.virt.powervm.disk import driver as disk_dvr
|
||||
|
||||
|
||||
class FakeDiskAdapter(disk_dvr.DiskAdapter):
|
||||
"""A fake subclass of DiskAdapter.
|
||||
|
||||
This is done so that the abstract methods/properties can be stubbed and the
|
||||
class can be instantiated for testing.
|
||||
"""
|
||||
|
||||
def _vios_uuids(self):
|
||||
pass
|
||||
|
||||
def _disk_match_func(self, disk_type, instance):
|
||||
pass
|
||||
|
||||
def disconnect_disk_from_mgmt(self, vios_uuid, disk_name):
|
||||
pass
|
||||
|
||||
def capacity(self):
|
||||
pass
|
||||
|
||||
def capacity_used(self):
|
||||
pass
|
||||
|
||||
def detach_disk(self, instance):
|
||||
pass
|
||||
|
||||
def delete_disks(self, storage_elems):
|
||||
pass
|
||||
|
||||
def create_disk_from_image(self, context, instance, image_meta):
|
||||
pass
|
||||
|
||||
def attach_disk(self, instance, disk_info, stg_ftsk):
|
||||
pass
|
||||
@@ -1,60 +0,0 @@
|
||||
# Copyright 2015, 2018 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import fixtures
|
||||
from pypowervm import const as pvm_const
|
||||
|
||||
from nova import test
|
||||
from nova.tests.unit.virt.powervm.disk import fake_adapter
|
||||
|
||||
|
||||
class TestDiskAdapter(test.NoDBTestCase):
|
||||
"""Unit Tests for the generic storage driver."""
|
||||
|
||||
def setUp(self):
|
||||
super(TestDiskAdapter, self).setUp()
|
||||
|
||||
# Return the mgmt uuid
|
||||
self.mgmt_uuid = self.useFixture(fixtures.MockPatch(
|
||||
'nova.virt.powervm.mgmt.mgmt_uuid')).mock
|
||||
self.mgmt_uuid.return_value = 'mp_uuid'
|
||||
|
||||
# The values (adapter and host uuid) are not used in the base.
|
||||
# Default them to None. We use the fake adapter here because we can't
|
||||
# instantiate DiskAdapter which is an abstract base class.
|
||||
self.st_adpt = fake_adapter.FakeDiskAdapter(None, None)
|
||||
|
||||
@mock.patch("pypowervm.util.sanitize_file_name_for_api")
|
||||
def test_get_disk_name(self, mock_san):
|
||||
inst = mock.Mock()
|
||||
inst.configure_mock(name='a_name_that_is_longer_than_eight',
|
||||
uuid='01234567-abcd-abcd-abcd-123412341234')
|
||||
|
||||
# Long
|
||||
self.assertEqual(mock_san.return_value,
|
||||
self.st_adpt._get_disk_name('type', inst))
|
||||
mock_san.assert_called_with(inst.name, prefix='type_',
|
||||
max_len=pvm_const.MaxLen.FILENAME_DEFAULT)
|
||||
|
||||
mock_san.reset_mock()
|
||||
|
||||
# Short
|
||||
self.assertEqual(mock_san.return_value,
|
||||
self.st_adpt._get_disk_name('type', inst, short=True))
|
||||
mock_san.assert_called_with('a_name_t_0123', prefix='t_',
|
||||
max_len=pvm_const.MaxLen.VDISK_NAME)
|
||||
@@ -1,313 +0,0 @@
|
||||
# Copyright 2015, 2018 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import fixtures
|
||||
|
||||
from nova import exception
|
||||
from nova import test
|
||||
from oslo_utils.fixture import uuidsentinel as uuids
|
||||
from pypowervm import const as pvm_const
|
||||
from pypowervm.tasks import storage as tsk_stg
|
||||
from pypowervm.utils import transaction as pvm_tx
|
||||
from pypowervm.wrappers import storage as pvm_stg
|
||||
from pypowervm.wrappers import virtual_io_server as pvm_vios
|
||||
|
||||
from nova.virt.powervm.disk import driver as disk_dvr
|
||||
from nova.virt.powervm.disk import localdisk
|
||||
|
||||
|
||||
class TestLocalDisk(test.NoDBTestCase):
|
||||
"""Unit Tests for the LocalDisk storage driver."""
|
||||
|
||||
def setUp(self):
|
||||
super(TestLocalDisk, self).setUp()
|
||||
self.adpt = mock.Mock()
|
||||
|
||||
# The mock VIOS needs to have scsi_mappings as a list. Internals are
|
||||
# set by individual test cases as needed.
|
||||
smaps = [mock.Mock()]
|
||||
self.vio_wrap = mock.create_autospec(
|
||||
pvm_vios.VIOS, instance=True, scsi_mappings=smaps,
|
||||
uuid='vios-uuid')
|
||||
|
||||
# Return the mgmt uuid.
|
||||
self.mgmt_uuid = self.useFixture(fixtures.MockPatch(
|
||||
'nova.virt.powervm.mgmt.mgmt_uuid', autospec=True)).mock
|
||||
self.mgmt_uuid.return_value = 'mgmt_uuid'
|
||||
|
||||
self.pvm_uuid = self.useFixture(fixtures.MockPatch(
|
||||
'nova.virt.powervm.vm.get_pvm_uuid')).mock
|
||||
self.pvm_uuid.return_value = 'pvm_uuid'
|
||||
|
||||
# Set up for the mocks for the disk adapter.
|
||||
self.mock_find_vg = self.useFixture(fixtures.MockPatch(
|
||||
'pypowervm.tasks.storage.find_vg', autospec=True)).mock
|
||||
self.vg_uuid = uuids.vg_uuid
|
||||
self.vg = mock.Mock(spec=pvm_stg.VG, uuid=self.vg_uuid)
|
||||
self.mock_find_vg.return_value = (self.vio_wrap, self.vg)
|
||||
|
||||
self.flags(volume_group_name='fakevg', group='powervm')
|
||||
|
||||
# Mock the feed tasks.
|
||||
self.mock_afs = self.useFixture(fixtures.MockPatch(
|
||||
'pypowervm.utils.transaction.FeedTask.add_functor_subtask',
|
||||
autospec=True)).mock
|
||||
self.mock_wtsk = mock.create_autospec(
|
||||
pvm_tx.WrapperTask, instance=True)
|
||||
self.mock_wtsk.configure_mock(wrapper=self.vio_wrap)
|
||||
self.mock_ftsk = mock.create_autospec(pvm_tx.FeedTask, instance=True)
|
||||
self.mock_ftsk.configure_mock(
|
||||
wrapper_tasks={'vios-uuid': self.mock_wtsk})
|
||||
|
||||
# Create the adapter.
|
||||
self.ld_adpt = localdisk.LocalStorage(self.adpt, 'host_uuid')
|
||||
|
||||
def test_init(self):
|
||||
# Localdisk adapter already initialized in setUp()
|
||||
# From super __init__()
|
||||
self.assertEqual(self.adpt, self.ld_adpt._adapter)
|
||||
self.assertEqual('host_uuid', self.ld_adpt._host_uuid)
|
||||
self.assertEqual('mgmt_uuid', self.ld_adpt.mp_uuid)
|
||||
|
||||
# From LocalStorage __init__()
|
||||
self.assertEqual('fakevg', self.ld_adpt.vg_name)
|
||||
self.mock_find_vg.assert_called_once_with(self.adpt, 'fakevg')
|
||||
self.assertEqual('vios-uuid', self.ld_adpt._vios_uuid)
|
||||
self.assertEqual(self.vg_uuid, self.ld_adpt.vg_uuid)
|
||||
self.assertFalse(self.ld_adpt.capabilities['shared_storage'])
|
||||
self.assertFalse(self.ld_adpt.capabilities['has_imagecache'])
|
||||
self.assertFalse(self.ld_adpt.capabilities['snapshot'])
|
||||
|
||||
# Assert snapshot capability is true if hosting I/O on mgmt partition.
|
||||
self.mgmt_uuid.return_value = 'vios-uuid'
|
||||
self.ld_adpt = localdisk.LocalStorage(self.adpt, 'host_uuid')
|
||||
self.assertTrue(self.ld_adpt.capabilities['snapshot'])
|
||||
|
||||
# Assert volume_group_name is required.
|
||||
self.flags(volume_group_name=None, group='powervm')
|
||||
self.assertRaises(exception.OptRequiredIfOtherOptValue,
|
||||
localdisk.LocalStorage, self.adpt, 'host_uuid')
|
||||
|
||||
def test_vios_uuids(self):
|
||||
self.assertEqual(['vios-uuid'], self.ld_adpt._vios_uuids)
|
||||
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.gen_match_func', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.disk.driver.DiskAdapter._get_disk_name')
|
||||
def test_disk_match_func(self, mock_disk_name, mock_gen_match):
|
||||
mock_disk_name.return_value = 'disk_name'
|
||||
func = self.ld_adpt._disk_match_func('disk_type', 'instance')
|
||||
mock_disk_name.assert_called_once_with(
|
||||
'disk_type', 'instance', short=True)
|
||||
mock_gen_match.assert_called_once_with(
|
||||
pvm_stg.VDisk, names=['disk_name'])
|
||||
self.assertEqual(mock_gen_match.return_value, func)
|
||||
|
||||
@mock.patch('nova.virt.powervm.disk.localdisk.LocalStorage._get_vg_wrap')
|
||||
def test_capacity(self, mock_vg):
|
||||
"""Tests the capacity methods."""
|
||||
mock_vg.return_value = mock.Mock(
|
||||
capacity='5120', available_size='2048')
|
||||
self.assertEqual(5120.0, self.ld_adpt.capacity)
|
||||
self.assertEqual(3072.0, self.ld_adpt.capacity_used)
|
||||
|
||||
@mock.patch('pypowervm.tasks.storage.rm_vg_storage', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.disk.localdisk.LocalStorage._get_vg_wrap')
|
||||
def test_delete_disks(self, mock_vg, mock_rm_vg):
|
||||
self.ld_adpt.delete_disks('storage_elems')
|
||||
mock_vg.assert_called_once_with()
|
||||
mock_rm_vg.assert_called_once_with(
|
||||
mock_vg.return_value, vdisks='storage_elems')
|
||||
|
||||
@mock.patch('pypowervm.wrappers.virtual_io_server.VIOS.get')
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.remove_maps', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.gen_match_func', autospec=True)
|
||||
def test_detach_disk(self, mock_match_fn, mock_rm_maps, mock_vios):
|
||||
mock_match_fn.return_value = 'match_func'
|
||||
mock_vios.return_value = self.vio_wrap
|
||||
mock_map1 = mock.Mock(backing_storage='back_stor1')
|
||||
mock_map2 = mock.Mock(backing_storage='back_stor2')
|
||||
mock_rm_maps.return_value = [mock_map1, mock_map2]
|
||||
|
||||
back_stores = self.ld_adpt.detach_disk('instance')
|
||||
|
||||
self.assertEqual(['back_stor1', 'back_stor2'], back_stores)
|
||||
mock_match_fn.assert_called_once_with(pvm_stg.VDisk)
|
||||
mock_vios.assert_called_once_with(
|
||||
self.ld_adpt._adapter, uuid='vios-uuid',
|
||||
xag=[pvm_const.XAG.VIO_SMAP])
|
||||
mock_rm_maps.assert_called_with(self.vio_wrap, 'pvm_uuid',
|
||||
match_func=mock_match_fn.return_value)
|
||||
mock_vios.return_value.update.assert_called_once()
|
||||
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.remove_vdisk_mapping',
|
||||
autospec=True)
|
||||
def test_disconnect_disk_from_mgmt(self, mock_rm_vdisk_map):
|
||||
self.ld_adpt.disconnect_disk_from_mgmt('vios-uuid', 'disk_name')
|
||||
mock_rm_vdisk_map.assert_called_with(
|
||||
self.ld_adpt._adapter, 'vios-uuid', 'mgmt_uuid',
|
||||
disk_names=['disk_name'])
|
||||
|
||||
@mock.patch('nova.virt.powervm.disk.localdisk.LocalStorage._upload_image')
|
||||
def test_create_disk_from_image(self, mock_upload_image):
|
||||
mock_image_meta = mock.Mock()
|
||||
mock_image_meta.size = 30
|
||||
mock_upload_image.return_value = 'mock_img'
|
||||
|
||||
self.ld_adpt.create_disk_from_image(
|
||||
'context', 'instance', mock_image_meta)
|
||||
|
||||
mock_upload_image.assert_called_once_with(
|
||||
'context', 'instance', mock_image_meta)
|
||||
|
||||
@mock.patch('nova.image.glance.API.download')
|
||||
@mock.patch('nova.virt.powervm.disk.driver.IterableToFileAdapter')
|
||||
@mock.patch('pypowervm.tasks.storage.upload_new_vdisk')
|
||||
@mock.patch('nova.virt.powervm.disk.driver.DiskAdapter._get_disk_name')
|
||||
def test_upload_image(self, mock_name, mock_upload, mock_iter, mock_dl):
|
||||
mock_meta = mock.Mock(id='1', size=1073741824, disk_format='raw')
|
||||
mock_upload.return_value = ['mock_img']
|
||||
|
||||
mock_img = self.ld_adpt._upload_image('context', 'inst', mock_meta)
|
||||
|
||||
self.assertEqual('mock_img', mock_img)
|
||||
mock_name.assert_called_once_with(
|
||||
disk_dvr.DiskType.BOOT, 'inst', short=True)
|
||||
mock_dl.assert_called_once_with('context', '1')
|
||||
mock_iter.assert_called_once_with(mock_dl.return_value)
|
||||
mock_upload.assert_called_once_with(
|
||||
self.adpt, 'vios-uuid', self.vg_uuid, mock_iter.return_value,
|
||||
mock_name.return_value, 1073741824, d_size=1073741824,
|
||||
upload_type=tsk_stg.UploadType.IO_STREAM, file_format='raw')
|
||||
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.add_map', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.build_vscsi_mapping',
|
||||
autospec=True)
|
||||
def test_attach_disk(self, mock_bldmap, mock_addmap):
|
||||
def test_afs(add_func):
|
||||
# Verify the internal add_func
|
||||
self.assertEqual(mock_addmap.return_value, add_func(self.vio_wrap))
|
||||
mock_bldmap.assert_called_once_with(
|
||||
self.ld_adpt._host_uuid, self.vio_wrap, 'pvm_uuid',
|
||||
'disk_info')
|
||||
mock_addmap.assert_called_once_with(
|
||||
self.vio_wrap, mock_bldmap.return_value)
|
||||
|
||||
self.mock_wtsk.add_functor_subtask.side_effect = test_afs
|
||||
self.ld_adpt.attach_disk('instance', 'disk_info', self.mock_ftsk)
|
||||
self.pvm_uuid.assert_called_once_with('instance')
|
||||
self.assertEqual(1, self.mock_wtsk.add_functor_subtask.call_count)
|
||||
|
||||
@mock.patch('pypowervm.wrappers.storage.VG.get')
|
||||
def test_get_vg_wrap(self, mock_vg):
|
||||
vg_wrap = self.ld_adpt._get_vg_wrap()
|
||||
self.assertEqual(mock_vg.return_value, vg_wrap)
|
||||
mock_vg.assert_called_once_with(
|
||||
self.adpt, uuid=self.vg_uuid, parent_type=pvm_vios.VIOS,
|
||||
parent_uuid='vios-uuid')
|
||||
|
||||
@mock.patch('pypowervm.wrappers.virtual_io_server.VIOS.get')
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.find_maps', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.disk.localdisk.LocalStorage.'
|
||||
'_disk_match_func')
|
||||
def test_get_bootdisk_path(self, mock_match_fn, mock_findmaps,
|
||||
mock_vios):
|
||||
mock_vios.return_value = self.vio_wrap
|
||||
|
||||
# No maps found
|
||||
mock_findmaps.return_value = None
|
||||
devname = self.ld_adpt.get_bootdisk_path('inst', 'vios_uuid')
|
||||
self.pvm_uuid.assert_called_once_with('inst')
|
||||
mock_match_fn.assert_called_once_with(disk_dvr.DiskType.BOOT, 'inst')
|
||||
mock_vios.assert_called_once_with(
|
||||
self.adpt, uuid='vios_uuid', xag=[pvm_const.XAG.VIO_SMAP])
|
||||
mock_findmaps.assert_called_once_with(
|
||||
self.vio_wrap.scsi_mappings,
|
||||
client_lpar_id='pvm_uuid',
|
||||
match_func=mock_match_fn.return_value)
|
||||
self.assertIsNone(devname)
|
||||
|
||||
# Good map
|
||||
mock_lu = mock.Mock()
|
||||
mock_lu.server_adapter.backing_dev_name = 'devname'
|
||||
mock_findmaps.return_value = [mock_lu]
|
||||
devname = self.ld_adpt.get_bootdisk_path('inst', 'vios_uuid')
|
||||
self.assertEqual('devname', devname)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.find_maps')
|
||||
@mock.patch('pypowervm.wrappers.virtual_io_server.VIOS.get')
|
||||
@mock.patch('pypowervm.wrappers.storage.VG.get', new=mock.Mock())
|
||||
def test_get_bootdisk_iter(self, mock_vios, mock_find_maps, mock_lw):
|
||||
inst, lpar_wrap, vios1 = self._bld_mocks_for_instance_disk()
|
||||
mock_lw.return_value = lpar_wrap
|
||||
|
||||
# Good path
|
||||
mock_vios.return_value = vios1
|
||||
for vdisk, vios in self.ld_adpt._get_bootdisk_iter(inst):
|
||||
self.assertEqual(vios1.scsi_mappings[0].backing_storage, vdisk)
|
||||
self.assertEqual(vios1.uuid, vios.uuid)
|
||||
mock_vios.assert_called_once_with(
|
||||
self.adpt, uuid='vios-uuid', xag=[pvm_const.XAG.VIO_SMAP])
|
||||
|
||||
# Not found, no storage of that name.
|
||||
mock_vios.reset_mock()
|
||||
mock_find_maps.return_value = []
|
||||
for vdisk, vios in self.ld_adpt._get_bootdisk_iter(inst):
|
||||
self.fail('Should not have found any storage elements.')
|
||||
mock_vios.assert_called_once_with(
|
||||
self.adpt, uuid='vios-uuid', xag=[pvm_const.XAG.VIO_SMAP])
|
||||
|
||||
@mock.patch('nova.virt.powervm.disk.driver.DiskAdapter._get_bootdisk_iter',
|
||||
autospec=True)
|
||||
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.add_vscsi_mapping', autospec=True)
|
||||
def test_connect_instance_disk_to_mgmt(self, mock_add, mock_lw, mock_iter):
|
||||
inst, lpar_wrap, vios1 = self._bld_mocks_for_instance_disk()
|
||||
mock_lw.return_value = lpar_wrap
|
||||
|
||||
# Good path
|
||||
mock_iter.return_value = [(vios1.scsi_mappings[0].backing_storage,
|
||||
vios1)]
|
||||
vdisk, vios = self.ld_adpt.connect_instance_disk_to_mgmt(inst)
|
||||
self.assertEqual(vios1.scsi_mappings[0].backing_storage, vdisk)
|
||||
self.assertIs(vios1, vios)
|
||||
self.assertEqual(1, mock_add.call_count)
|
||||
mock_add.assert_called_with('host_uuid', vios, 'mgmt_uuid', vdisk)
|
||||
|
||||
# add_vscsi_mapping raises. Show-stopper since only one VIOS.
|
||||
mock_add.reset_mock()
|
||||
mock_add.side_effect = Exception
|
||||
self.assertRaises(exception.InstanceDiskMappingFailed,
|
||||
self.ld_adpt.connect_instance_disk_to_mgmt, inst)
|
||||
self.assertEqual(1, mock_add.call_count)
|
||||
|
||||
# Not found
|
||||
mock_add.reset_mock()
|
||||
mock_iter.return_value = []
|
||||
self.assertRaises(exception.InstanceDiskMappingFailed,
|
||||
self.ld_adpt.connect_instance_disk_to_mgmt, inst)
|
||||
self.assertFalse(mock_add.called)
|
||||
|
||||
def _bld_mocks_for_instance_disk(self):
|
||||
inst = mock.Mock()
|
||||
inst.name = 'Name Of Instance'
|
||||
inst.uuid = uuids.inst_uuid
|
||||
lpar_wrap = mock.Mock()
|
||||
lpar_wrap.id = 2
|
||||
vios1 = self.vio_wrap
|
||||
back_stor_name = 'b_Name_Of__' + inst.uuid[:4]
|
||||
vios1.scsi_mappings[0].backing_storage.name = back_stor_name
|
||||
return inst, lpar_wrap, vios1
|
||||
@@ -1,425 +0,0 @@
|
||||
# Copyright 2015, 2018 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import fixtures
|
||||
from oslo_utils import uuidutils
|
||||
from pypowervm import const as pvm_const
|
||||
from pypowervm import exceptions as pvm_exc
|
||||
from pypowervm.tasks import storage as tsk_stg
|
||||
from pypowervm.utils import transaction as pvm_tx
|
||||
from pypowervm.wrappers import cluster as pvm_clust
|
||||
from pypowervm.wrappers import storage as pvm_stg
|
||||
from pypowervm.wrappers import virtual_io_server as pvm_vios
|
||||
|
||||
from nova import exception
|
||||
from nova import test
|
||||
from nova.tests.unit.virt import powervm
|
||||
from nova.virt.powervm.disk import ssp as ssp_dvr
|
||||
from nova.virt.powervm import vm
|
||||
|
||||
FAKE_INST_UUID = uuidutils.generate_uuid(dashed=True)
|
||||
FAKE_INST_UUID_PVM = vm.get_pvm_uuid(mock.Mock(uuid=FAKE_INST_UUID))
|
||||
|
||||
|
||||
class TestSSPDiskAdapter(test.NoDBTestCase):
|
||||
"""Unit Tests for the LocalDisk storage driver."""
|
||||
|
||||
def setUp(self):
|
||||
super(TestSSPDiskAdapter, self).setUp()
|
||||
|
||||
self.inst = powervm.TEST_INSTANCE
|
||||
|
||||
self.apt = mock.Mock()
|
||||
self.host_uuid = 'host_uuid'
|
||||
|
||||
self.ssp_wrap = mock.create_autospec(pvm_stg.SSP, instance=True)
|
||||
|
||||
# SSP.refresh() returns itself
|
||||
self.ssp_wrap.refresh.return_value = self.ssp_wrap
|
||||
self.node1 = mock.create_autospec(pvm_clust.Node, instance=True)
|
||||
self.node2 = mock.create_autospec(pvm_clust.Node, instance=True)
|
||||
self.clust_wrap = mock.create_autospec(
|
||||
pvm_clust.Cluster, instance=True)
|
||||
self.clust_wrap.nodes = [self.node1, self.node2]
|
||||
self.clust_wrap.refresh.return_value = self.clust_wrap
|
||||
self.tier_wrap = mock.create_autospec(pvm_stg.Tier, instance=True)
|
||||
# Tier.refresh() returns itself
|
||||
self.tier_wrap.refresh.return_value = self.tier_wrap
|
||||
self.vio_wrap = mock.create_autospec(pvm_vios.VIOS, instance=True)
|
||||
|
||||
# For _cluster
|
||||
self.mock_clust = self.useFixture(fixtures.MockPatch(
|
||||
'pypowervm.wrappers.cluster.Cluster', autospec=True)).mock
|
||||
self.mock_clust.get.return_value = [self.clust_wrap]
|
||||
|
||||
# For _ssp
|
||||
self.mock_ssp_gbhref = self.useFixture(fixtures.MockPatch(
|
||||
'pypowervm.wrappers.storage.SSP.get_by_href')).mock
|
||||
self.mock_ssp_gbhref.return_value = self.ssp_wrap
|
||||
|
||||
# For _tier
|
||||
self.mock_get_tier = self.useFixture(fixtures.MockPatch(
|
||||
'pypowervm.tasks.storage.default_tier_for_ssp',
|
||||
autospec=True)).mock
|
||||
self.mock_get_tier.return_value = self.tier_wrap
|
||||
|
||||
# A FeedTask
|
||||
self.mock_wtsk = mock.create_autospec(
|
||||
pvm_tx.WrapperTask, instance=True)
|
||||
self.mock_wtsk.configure_mock(wrapper=self.vio_wrap)
|
||||
self.mock_ftsk = mock.create_autospec(pvm_tx.FeedTask, instance=True)
|
||||
self.mock_afs = self.mock_ftsk.add_functor_subtask
|
||||
self.mock_ftsk.configure_mock(
|
||||
wrapper_tasks={self.vio_wrap.uuid: self.mock_wtsk})
|
||||
|
||||
self.pvm_uuid = self.useFixture(fixtures.MockPatch(
|
||||
'nova.virt.powervm.vm.get_pvm_uuid')).mock
|
||||
|
||||
# Return the mgmt uuid
|
||||
self.mgmt_uuid = self.useFixture(fixtures.MockPatch(
|
||||
'nova.virt.powervm.mgmt.mgmt_uuid')).mock
|
||||
self.mgmt_uuid.return_value = 'mp_uuid'
|
||||
|
||||
# The SSP disk adapter
|
||||
self.ssp_drv = ssp_dvr.SSPDiskAdapter(self.apt, self.host_uuid)
|
||||
|
||||
def test_init(self):
|
||||
self.assertEqual(self.apt, self.ssp_drv._adapter)
|
||||
self.assertEqual(self.host_uuid, self.ssp_drv._host_uuid)
|
||||
self.mock_clust.get.assert_called_once_with(self.apt)
|
||||
self.assertEqual(self.mock_clust.get.return_value,
|
||||
[self.ssp_drv._clust])
|
||||
self.mock_ssp_gbhref.assert_called_once_with(
|
||||
self.apt, self.clust_wrap.ssp_uri)
|
||||
self.assertEqual(self.mock_ssp_gbhref.return_value, self.ssp_drv._ssp)
|
||||
self.mock_get_tier.assert_called_once_with(self.ssp_wrap)
|
||||
self.assertEqual(self.mock_get_tier.return_value, self.ssp_drv._tier)
|
||||
|
||||
def test_init_error(self):
|
||||
# Do these in reverse order to verify we trap all of 'em
|
||||
for raiser in (self.mock_get_tier, self.mock_ssp_gbhref,
|
||||
self.mock_clust.get):
|
||||
raiser.side_effect = pvm_exc.TimeoutError("timed out")
|
||||
self.assertRaises(exception.NotFound,
|
||||
ssp_dvr.SSPDiskAdapter, self.apt, self.host_uuid)
|
||||
raiser.side_effect = ValueError
|
||||
self.assertRaises(ValueError,
|
||||
ssp_dvr.SSPDiskAdapter, self.apt, self.host_uuid)
|
||||
|
||||
def test_capabilities(self):
|
||||
self.assertTrue(self.ssp_drv.capabilities.get('shared_storage'))
|
||||
self.assertFalse(self.ssp_drv.capabilities.get('has_imagecache'))
|
||||
self.assertTrue(self.ssp_drv.capabilities.get('snapshot'))
|
||||
|
||||
@mock.patch('pypowervm.util.get_req_path_uuid', autospec=True)
|
||||
def test_vios_uuids(self, mock_rpu):
|
||||
mock_rpu.return_value = self.host_uuid
|
||||
vios_uuids = self.ssp_drv._vios_uuids
|
||||
self.assertEqual({self.node1.vios_uuid, self.node2.vios_uuid},
|
||||
set(vios_uuids))
|
||||
mock_rpu.assert_has_calls(
|
||||
[mock.call(node.vios_uri, preserve_case=True, root=True)
|
||||
for node in [self.node1, self.node2]])
|
||||
|
||||
mock_rpu.reset_mock()
|
||||
|
||||
# Test VIOSes on other nodes, which won't have uuid or url
|
||||
node1 = mock.Mock(vios_uuid=None, vios_uri='uri1')
|
||||
node2 = mock.Mock(vios_uuid='2', vios_uri=None)
|
||||
# This mock is good and should be returned
|
||||
node3 = mock.Mock(vios_uuid='3', vios_uri='uri3')
|
||||
self.clust_wrap.nodes = [node1, node2, node3]
|
||||
self.assertEqual(['3'], self.ssp_drv._vios_uuids)
|
||||
# get_req_path_uuid was only called on the good one
|
||||
mock_rpu.assert_called_once_with('uri3', preserve_case=True, root=True)
|
||||
|
||||
def test_capacity(self):
|
||||
self.tier_wrap.capacity = 10
|
||||
self.assertAlmostEqual(10.0, self.ssp_drv.capacity)
|
||||
self.tier_wrap.refresh.assert_called_once_with()
|
||||
|
||||
def test_capacity_used(self):
|
||||
self.ssp_wrap.capacity = 4.56
|
||||
self.ssp_wrap.free_space = 1.23
|
||||
self.assertAlmostEqual((4.56 - 1.23), self.ssp_drv.capacity_used)
|
||||
self.ssp_wrap.refresh.assert_called_once_with()
|
||||
|
||||
@mock.patch('pypowervm.tasks.cluster_ssp.get_or_upload_image_lu',
|
||||
autospec=True)
|
||||
@mock.patch('nova.virt.powervm.disk.ssp.SSPDiskAdapter._vios_uuids',
|
||||
new_callable=mock.PropertyMock)
|
||||
@mock.patch('pypowervm.util.sanitize_file_name_for_api', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.storage.crt_lu', autospec=True)
|
||||
@mock.patch('nova.image.glance.API.download')
|
||||
@mock.patch('nova.virt.powervm.disk.driver.IterableToFileAdapter',
|
||||
autospec=True)
|
||||
def test_create_disk_from_image(self, mock_it2f, mock_dl, mock_crt_lu,
|
||||
mock_san, mock_vuuid, mock_goru):
|
||||
img = powervm.TEST_IMAGE1
|
||||
|
||||
mock_crt_lu.return_value = self.ssp_drv._ssp, 'boot_lu'
|
||||
mock_san.return_value = 'disk_name'
|
||||
mock_vuuid.return_value = ['vuuid']
|
||||
|
||||
self.assertEqual('boot_lu', self.ssp_drv.create_disk_from_image(
|
||||
'context', self.inst, img))
|
||||
mock_dl.assert_called_once_with('context', img.id)
|
||||
mock_san.assert_has_calls([
|
||||
mock.call(img.name, prefix='image_', suffix='_' + img.checksum),
|
||||
mock.call(self.inst.name, prefix='boot_')])
|
||||
mock_it2f.assert_called_once_with(mock_dl.return_value)
|
||||
mock_goru.assert_called_once_with(
|
||||
self.ssp_drv._tier, 'disk_name', 'vuuid',
|
||||
mock_it2f.return_value, img.size,
|
||||
upload_type=tsk_stg.UploadType.IO_STREAM)
|
||||
mock_crt_lu.assert_called_once_with(
|
||||
self.mock_get_tier.return_value, mock_san.return_value,
|
||||
self.inst.flavor.root_gb, typ=pvm_stg.LUType.DISK,
|
||||
clone=mock_goru.return_value)
|
||||
|
||||
@mock.patch('nova.virt.powervm.disk.ssp.SSPDiskAdapter._vios_uuids',
|
||||
new_callable=mock.PropertyMock)
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.add_map', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.build_vscsi_mapping',
|
||||
autospec=True)
|
||||
@mock.patch('pypowervm.wrappers.storage.LU', autospec=True)
|
||||
def test_connect_disk(self, mock_lu, mock_bldmap, mock_addmap,
|
||||
mock_vio_uuids):
|
||||
disk_info = mock.Mock()
|
||||
disk_info.configure_mock(name='dname', udid='dudid')
|
||||
mock_vio_uuids.return_value = [self.vio_wrap.uuid]
|
||||
|
||||
def test_afs(add_func):
|
||||
# Verify the internal add_func
|
||||
self.assertEqual(mock_addmap.return_value, add_func(self.vio_wrap))
|
||||
mock_bldmap.assert_called_once_with(
|
||||
self.host_uuid, self.vio_wrap, self.pvm_uuid.return_value,
|
||||
mock_lu.bld_ref.return_value)
|
||||
mock_addmap.assert_called_once_with(
|
||||
self.vio_wrap, mock_bldmap.return_value)
|
||||
self.mock_wtsk.add_functor_subtask.side_effect = test_afs
|
||||
|
||||
self.ssp_drv.attach_disk(self.inst, disk_info, self.mock_ftsk)
|
||||
mock_lu.bld_ref.assert_called_once_with(self.apt, 'dname', 'dudid')
|
||||
self.pvm_uuid.assert_called_once_with(self.inst)
|
||||
self.assertEqual(1, self.mock_wtsk.add_functor_subtask.call_count)
|
||||
|
||||
@mock.patch('pypowervm.tasks.storage.rm_tier_storage', autospec=True)
|
||||
def test_delete_disks(self, mock_rm_tstor):
|
||||
self.ssp_drv.delete_disks(['disk1', 'disk2'])
|
||||
mock_rm_tstor.assert_called_once_with(['disk1', 'disk2'],
|
||||
tier=self.ssp_drv._tier)
|
||||
|
||||
@mock.patch('nova.virt.powervm.disk.ssp.SSPDiskAdapter._vios_uuids',
|
||||
new_callable=mock.PropertyMock)
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.find_maps', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.remove_maps', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.gen_match_func', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.partition.build_active_vio_feed_task',
|
||||
autospec=True)
|
||||
def test_disconnect_disk(self, mock_bld_ftsk, mock_gmf, mock_rmmaps,
|
||||
mock_findmaps, mock_vio_uuids):
|
||||
mock_vio_uuids.return_value = [self.vio_wrap.uuid]
|
||||
mock_bld_ftsk.return_value = self.mock_ftsk
|
||||
lu1, lu2 = [mock.create_autospec(pvm_stg.LU, instance=True)] * 2
|
||||
# Two mappings have the same LU, to verify set behavior
|
||||
mock_findmaps.return_value = [
|
||||
mock.Mock(spec=pvm_vios.VSCSIMapping, backing_storage=lu)
|
||||
for lu in (lu1, lu2, lu1)]
|
||||
|
||||
def test_afs(rm_func):
|
||||
# verify the internal rm_func
|
||||
self.assertEqual(mock_rmmaps.return_value, rm_func(self.vio_wrap))
|
||||
mock_rmmaps.assert_called_once_with(
|
||||
self.vio_wrap, self.pvm_uuid.return_value,
|
||||
match_func=mock_gmf.return_value)
|
||||
self.mock_wtsk.add_functor_subtask.side_effect = test_afs
|
||||
|
||||
self.assertEqual(
|
||||
{lu1, lu2}, set(self.ssp_drv.detach_disk(self.inst)))
|
||||
mock_bld_ftsk.assert_called_once_with(
|
||||
self.apt, name='ssp', xag=[pvm_const.XAG.VIO_SMAP])
|
||||
self.pvm_uuid.assert_called_once_with(self.inst)
|
||||
mock_gmf.assert_called_once_with(pvm_stg.LU)
|
||||
self.assertEqual(1, self.mock_wtsk.add_functor_subtask.call_count)
|
||||
mock_findmaps.assert_called_once_with(
|
||||
self.vio_wrap.scsi_mappings,
|
||||
client_lpar_id=self.pvm_uuid.return_value,
|
||||
match_func=mock_gmf.return_value)
|
||||
self.mock_ftsk.execute.assert_called_once_with()
|
||||
|
||||
@mock.patch('pypowervm.wrappers.virtual_io_server.VIOS.get')
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.find_maps', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.disk.ssp.SSPDiskAdapter._disk_match_func')
|
||||
def test_get_bootdisk_path(self, mock_match_fn, mock_findmaps,
|
||||
mock_vios):
|
||||
mock_vios.return_value = self.vio_wrap
|
||||
|
||||
# No maps found
|
||||
mock_findmaps.return_value = None
|
||||
devname = self.ssp_drv.get_bootdisk_path('inst', 'vios_uuid')
|
||||
mock_vios.assert_called_once_with(
|
||||
self.apt, uuid='vios_uuid', xag=[pvm_const.XAG.VIO_SMAP])
|
||||
mock_findmaps.assert_called_once_with(
|
||||
self.vio_wrap.scsi_mappings,
|
||||
client_lpar_id=self.pvm_uuid.return_value,
|
||||
match_func=mock_match_fn.return_value)
|
||||
self.assertIsNone(devname)
|
||||
|
||||
# Good map
|
||||
mock_lu = mock.Mock()
|
||||
mock_lu.server_adapter.backing_dev_name = 'devname'
|
||||
mock_findmaps.return_value = [mock_lu]
|
||||
devname = self.ssp_drv.get_bootdisk_path('inst', 'vios_uuid')
|
||||
self.assertEqual('devname', devname)
|
||||
|
||||
@mock.patch('nova.virt.powervm.disk.ssp.SSPDiskAdapter.'
|
||||
'_vios_uuids', new_callable=mock.PropertyMock)
|
||||
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper', autospec=True)
|
||||
@mock.patch('pypowervm.wrappers.virtual_io_server.VIOS.get')
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.add_vscsi_mapping', autospec=True)
|
||||
def test_connect_instance_disk_to_mgmt(self, mock_add, mock_vio_get,
|
||||
mock_lw, mock_vio_uuids):
|
||||
inst, lpar_wrap, vio1, vio2, vio3 = self._bld_mocks_for_instance_disk()
|
||||
mock_lw.return_value = lpar_wrap
|
||||
mock_vio_uuids.return_value = [1, 2]
|
||||
|
||||
# Test with two VIOSes, both of which contain the mapping
|
||||
mock_vio_get.side_effect = [vio1, vio2]
|
||||
lu, vios = self.ssp_drv.connect_instance_disk_to_mgmt(inst)
|
||||
self.assertEqual('lu_udid', lu.udid)
|
||||
# Should hit on the first VIOS
|
||||
self.assertIs(vio1, vios)
|
||||
mock_add.assert_called_once_with(self.host_uuid, vio1, 'mp_uuid', lu)
|
||||
|
||||
# Now the first VIOS doesn't have the mapping, but the second does
|
||||
mock_add.reset_mock()
|
||||
mock_vio_get.side_effect = [vio3, vio2]
|
||||
lu, vios = self.ssp_drv.connect_instance_disk_to_mgmt(inst)
|
||||
self.assertEqual('lu_udid', lu.udid)
|
||||
# Should hit on the second VIOS
|
||||
self.assertIs(vio2, vios)
|
||||
self.assertEqual(1, mock_add.call_count)
|
||||
mock_add.assert_called_once_with(self.host_uuid, vio2, 'mp_uuid', lu)
|
||||
|
||||
# No hits
|
||||
mock_add.reset_mock()
|
||||
mock_vio_get.side_effect = [vio3, vio3]
|
||||
self.assertRaises(exception.InstanceDiskMappingFailed,
|
||||
self.ssp_drv.connect_instance_disk_to_mgmt, inst)
|
||||
self.assertEqual(0, mock_add.call_count)
|
||||
|
||||
# First add_vscsi_mapping call raises
|
||||
mock_vio_get.side_effect = [vio1, vio2]
|
||||
mock_add.side_effect = [Exception("mapping failed"), None]
|
||||
# Should hit on the second VIOS
|
||||
self.assertIs(vio2, vios)
|
||||
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.remove_lu_mapping', autospec=True)
|
||||
def test_disconnect_disk_from_mgmt(self, mock_rm_lu_map):
|
||||
self.ssp_drv.disconnect_disk_from_mgmt('vios_uuid', 'disk_name')
|
||||
mock_rm_lu_map.assert_called_with(self.apt, 'vios_uuid',
|
||||
'mp_uuid', disk_names=['disk_name'])
|
||||
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.gen_match_func', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.disk.ssp.SSPDiskAdapter._get_disk_name')
|
||||
def test_disk_match_func(self, mock_disk_name, mock_gen_match):
|
||||
mock_disk_name.return_value = 'disk_name'
|
||||
self.ssp_drv._disk_match_func('disk_type', 'instance')
|
||||
mock_disk_name.assert_called_once_with('disk_type', 'instance')
|
||||
mock_gen_match.assert_called_with(pvm_stg.LU, names=['disk_name'])
|
||||
|
||||
@mock.patch('nova.virt.powervm.disk.ssp.SSPDiskAdapter.'
|
||||
'_vios_uuids', new_callable=mock.PropertyMock)
|
||||
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper', autospec=True)
|
||||
@mock.patch('pypowervm.wrappers.virtual_io_server.VIOS.get')
|
||||
def test_get_bootdisk_iter(self, mock_vio_get, mock_lw, mock_vio_uuids):
|
||||
inst, lpar_wrap, vio1, vio2, vio3 = self._bld_mocks_for_instance_disk()
|
||||
mock_lw.return_value = lpar_wrap
|
||||
mock_vio_uuids.return_value = [1, 2]
|
||||
|
||||
# Test with two VIOSes, both of which contain the mapping. Force the
|
||||
# method to get the lpar_wrap.
|
||||
mock_vio_get.side_effect = [vio1, vio2]
|
||||
idi = self.ssp_drv._get_bootdisk_iter(inst)
|
||||
lu, vios = next(idi)
|
||||
self.assertEqual('lu_udid', lu.udid)
|
||||
self.assertEqual('vios1', vios.name)
|
||||
mock_vio_get.assert_called_once_with(self.apt, uuid=1,
|
||||
xag=[pvm_const.XAG.VIO_SMAP])
|
||||
lu, vios = next(idi)
|
||||
self.assertEqual('lu_udid', lu.udid)
|
||||
self.assertEqual('vios2', vios.name)
|
||||
mock_vio_get.assert_called_with(self.apt, uuid=2,
|
||||
xag=[pvm_const.XAG.VIO_SMAP])
|
||||
self.assertRaises(StopIteration, next, idi)
|
||||
self.assertEqual(2, mock_vio_get.call_count)
|
||||
mock_lw.assert_called_once_with(self.apt, inst)
|
||||
|
||||
# Same, but prove that breaking out of the loop early avoids the second
|
||||
# get call. Supply lpar_wrap from here on, and prove no calls to
|
||||
# get_instance_wrapper
|
||||
mock_vio_get.reset_mock()
|
||||
mock_lw.reset_mock()
|
||||
mock_vio_get.side_effect = [vio1, vio2]
|
||||
for lu, vios in self.ssp_drv._get_bootdisk_iter(inst):
|
||||
self.assertEqual('lu_udid', lu.udid)
|
||||
self.assertEqual('vios1', vios.name)
|
||||
break
|
||||
mock_vio_get.assert_called_once_with(self.apt, uuid=1,
|
||||
xag=[pvm_const.XAG.VIO_SMAP])
|
||||
|
||||
# Now the first VIOS doesn't have the mapping, but the second does
|
||||
mock_vio_get.reset_mock()
|
||||
mock_vio_get.side_effect = [vio3, vio2]
|
||||
idi = self.ssp_drv._get_bootdisk_iter(inst)
|
||||
lu, vios = next(idi)
|
||||
self.assertEqual('lu_udid', lu.udid)
|
||||
self.assertEqual('vios2', vios.name)
|
||||
mock_vio_get.assert_has_calls(
|
||||
[mock.call(self.apt, uuid=uuid, xag=[pvm_const.XAG.VIO_SMAP])
|
||||
for uuid in (1, 2)])
|
||||
self.assertRaises(StopIteration, next, idi)
|
||||
self.assertEqual(2, mock_vio_get.call_count)
|
||||
|
||||
# No hits
|
||||
mock_vio_get.reset_mock()
|
||||
mock_vio_get.side_effect = [vio3, vio3]
|
||||
self.assertEqual([], list(self.ssp_drv._get_bootdisk_iter(inst)))
|
||||
self.assertEqual(2, mock_vio_get.call_count)
|
||||
|
||||
def _bld_mocks_for_instance_disk(self):
|
||||
inst = mock.Mock()
|
||||
inst.name = 'my-instance-name'
|
||||
lpar_wrap = mock.Mock()
|
||||
lpar_wrap.id = 4
|
||||
lu_wrap = mock.Mock(spec=pvm_stg.LU)
|
||||
lu_wrap.configure_mock(name='boot_my_instance_name', udid='lu_udid')
|
||||
smap = mock.Mock(backing_storage=lu_wrap,
|
||||
server_adapter=mock.Mock(lpar_id=4))
|
||||
# Build mock VIOS Wrappers as the returns from VIOS.wrap.
|
||||
# vios1 and vios2 will both have the mapping for client ID 4 and LU
|
||||
# named boot_my_instance_name.
|
||||
smaps = [mock.Mock(), mock.Mock(), mock.Mock(), smap]
|
||||
vios1 = mock.Mock(spec=pvm_vios.VIOS)
|
||||
vios1.configure_mock(name='vios1', uuid='uuid1', scsi_mappings=smaps)
|
||||
vios2 = mock.Mock(spec=pvm_vios.VIOS)
|
||||
vios2.configure_mock(name='vios2', uuid='uuid2', scsi_mappings=smaps)
|
||||
# vios3 will not have the mapping
|
||||
vios3 = mock.Mock(spec=pvm_vios.VIOS)
|
||||
vios3.configure_mock(name='vios3', uuid='uuid3',
|
||||
scsi_mappings=[mock.Mock(), mock.Mock()])
|
||||
return inst, lpar_wrap, vios1, vios2, vios3
|
||||
@@ -1,68 +0,0 @@
|
||||
# Copyright 2015, 2018 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from nova import test
|
||||
|
||||
from nova.virt.powervm.tasks import image as tsk_img
|
||||
|
||||
|
||||
class TestImage(test.TestCase):
|
||||
def test_update_task_state(self):
|
||||
def func(task_state, expected_state='delirious'):
|
||||
self.assertEqual('task_state', task_state)
|
||||
self.assertEqual('delirious', expected_state)
|
||||
tf = tsk_img.UpdateTaskState(func, 'task_state')
|
||||
self.assertEqual('update_task_state_task_state', tf.name)
|
||||
tf.execute()
|
||||
|
||||
def func2(task_state, expected_state=None):
|
||||
self.assertEqual('task_state', task_state)
|
||||
self.assertEqual('expected_state', expected_state)
|
||||
tf = tsk_img.UpdateTaskState(func2, 'task_state',
|
||||
expected_state='expected_state')
|
||||
tf.execute()
|
||||
|
||||
# Validate args on taskflow.task.Task instantiation
|
||||
with mock.patch('taskflow.task.Task.__init__') as tf:
|
||||
tsk_img.UpdateTaskState(func, 'task_state')
|
||||
tf.assert_called_once_with(
|
||||
name='update_task_state_task_state')
|
||||
|
||||
@mock.patch('nova.virt.powervm.image.stream_blockdev_to_glance',
|
||||
autospec=True)
|
||||
@mock.patch('nova.virt.powervm.image.generate_snapshot_metadata',
|
||||
autospec=True)
|
||||
def test_stream_to_glance(self, mock_metadata, mock_stream):
|
||||
mock_metadata.return_value = 'metadata'
|
||||
mock_inst = mock.Mock()
|
||||
mock_inst.name = 'instance_name'
|
||||
tf = tsk_img.StreamToGlance('context', 'image_api', 'image_id',
|
||||
mock_inst)
|
||||
self.assertEqual('stream_to_glance', tf.name)
|
||||
tf.execute('disk_path')
|
||||
mock_metadata.assert_called_with('context', 'image_api', 'image_id',
|
||||
mock_inst)
|
||||
mock_stream.assert_called_with('context', 'image_api', 'image_id',
|
||||
'metadata', 'disk_path')
|
||||
|
||||
# Validate args on taskflow.task.Task instantiation
|
||||
with mock.patch('taskflow.task.Task.__init__') as tf:
|
||||
tsk_img.StreamToGlance(
|
||||
'context', 'image_api', 'image_id', mock_inst)
|
||||
tf.assert_called_once_with(
|
||||
name='stream_to_glance', requires='disk_path')
|
||||
@@ -1,323 +0,0 @@
|
||||
# Copyright 2015, 2017 IBM Corp.
|
||||
#
|
||||
# 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 copy
|
||||
from unittest import mock
|
||||
|
||||
import eventlet
|
||||
from pypowervm.wrappers import network as pvm_net
|
||||
|
||||
from nova import exception
|
||||
from nova import test
|
||||
from nova.tests.unit.virt import powervm
|
||||
from nova.virt.powervm.tasks import network as tf_net
|
||||
|
||||
|
||||
def cna(mac):
|
||||
"""Builds a mock Client Network Adapter for unit tests."""
|
||||
return mock.MagicMock(mac=mac, vswitch_uri='fake_href')
|
||||
|
||||
|
||||
class TestNetwork(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
super(TestNetwork, self).setUp()
|
||||
self.flags(host='host1')
|
||||
self.apt = mock.Mock()
|
||||
|
||||
self.mock_lpar_wrap = mock.MagicMock()
|
||||
self.mock_lpar_wrap.can_modify_io.return_value = True, None
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper')
|
||||
@mock.patch('nova.virt.powervm.vif.unplug')
|
||||
@mock.patch('nova.virt.powervm.vm.get_cnas')
|
||||
def test_unplug_vifs(self, mock_vm_get, mock_unplug, mock_get_wrap):
|
||||
"""Tests that a delete of the vif can be done."""
|
||||
inst = powervm.TEST_INSTANCE
|
||||
|
||||
# Mock up the CNA responses.
|
||||
cnas = [cna('AABBCCDDEEFF'), cna('AABBCCDDEE11'), cna('AABBCCDDEE22')]
|
||||
mock_vm_get.return_value = cnas
|
||||
|
||||
# Mock up the network info. This also validates that they will be
|
||||
# sanitized to upper case.
|
||||
net_info = [
|
||||
{'address': 'aa:bb:cc:dd:ee:ff'}, {'address': 'aa:bb:cc:dd:ee:22'},
|
||||
{'address': 'aa:bb:cc:dd:ee:33'}
|
||||
]
|
||||
|
||||
# Mock out the instance wrapper
|
||||
mock_get_wrap.return_value = self.mock_lpar_wrap
|
||||
|
||||
# Mock out the vif driver
|
||||
def validate_unplug(adapter, instance, vif, cna_w_list=None):
|
||||
self.assertEqual(adapter, self.apt)
|
||||
self.assertEqual(instance, inst)
|
||||
self.assertIn(vif, net_info)
|
||||
self.assertEqual(cna_w_list, cnas)
|
||||
|
||||
mock_unplug.side_effect = validate_unplug
|
||||
|
||||
# Run method
|
||||
p_vifs = tf_net.UnplugVifs(self.apt, inst, net_info)
|
||||
p_vifs.execute()
|
||||
|
||||
# Make sure the unplug was invoked, so that we know that the validation
|
||||
# code was called
|
||||
self.assertEqual(3, mock_unplug.call_count)
|
||||
|
||||
# Validate args on taskflow.task.Task instantiation
|
||||
with mock.patch('taskflow.task.Task.__init__') as tf:
|
||||
tf_net.UnplugVifs(self.apt, inst, net_info)
|
||||
tf.assert_called_once_with(name='unplug_vifs')
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper')
|
||||
def test_unplug_vifs_invalid_state(self, mock_get_wrap):
|
||||
"""Tests that the delete raises an exception if bad VM state."""
|
||||
inst = powervm.TEST_INSTANCE
|
||||
|
||||
# Mock out the instance wrapper
|
||||
mock_get_wrap.return_value = self.mock_lpar_wrap
|
||||
|
||||
# Mock that the state is incorrect
|
||||
self.mock_lpar_wrap.can_modify_io.return_value = False, 'bad'
|
||||
|
||||
# Run method
|
||||
p_vifs = tf_net.UnplugVifs(self.apt, inst, mock.Mock())
|
||||
self.assertRaises(exception.VirtualInterfaceUnplugException,
|
||||
p_vifs.execute)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vif.plug')
|
||||
@mock.patch('nova.virt.powervm.vm.get_cnas')
|
||||
def test_plug_vifs_rmc(self, mock_cna_get, mock_plug):
|
||||
"""Tests that a crt vif can be done with secure RMC."""
|
||||
inst = powervm.TEST_INSTANCE
|
||||
|
||||
# Mock up the CNA response. One should already exist, the other
|
||||
# should not.
|
||||
pre_cnas = [cna('AABBCCDDEEFF'), cna('AABBCCDDEE11')]
|
||||
mock_cna_get.return_value = copy.deepcopy(pre_cnas)
|
||||
|
||||
# Mock up the network info. This also validates that they will be
|
||||
# sanitized to upper case.
|
||||
net_info = [
|
||||
{'address': 'aa:bb:cc:dd:ee:ff', 'vnic_type': 'normal'},
|
||||
{'address': 'aa:bb:cc:dd:ee:22', 'vnic_type': 'normal'},
|
||||
]
|
||||
|
||||
# First run the CNA update, then the CNA create.
|
||||
mock_new_cna = mock.Mock(spec=pvm_net.CNA)
|
||||
mock_plug.side_effect = ['upd_cna', mock_new_cna]
|
||||
|
||||
# Run method
|
||||
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info)
|
||||
|
||||
all_cnas = p_vifs.execute(self.mock_lpar_wrap)
|
||||
|
||||
# new vif should be created twice.
|
||||
mock_plug.assert_any_call(self.apt, inst, net_info[0], new_vif=False)
|
||||
mock_plug.assert_any_call(self.apt, inst, net_info[1], new_vif=True)
|
||||
|
||||
# The Task provides the list of original CNAs plus only CNAs that were
|
||||
# created.
|
||||
self.assertEqual(pre_cnas + [mock_new_cna], all_cnas)
|
||||
|
||||
# Validate args on taskflow.task.Task instantiation
|
||||
with mock.patch('taskflow.task.Task.__init__') as tf:
|
||||
tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info)
|
||||
tf.assert_called_once_with(
|
||||
name='plug_vifs', provides='vm_cnas', requires=['lpar_wrap'])
|
||||
|
||||
@mock.patch('nova.virt.powervm.vif.plug')
|
||||
@mock.patch('nova.virt.powervm.vm.get_cnas')
|
||||
def test_plug_vifs_rmc_no_create(self, mock_vm_get, mock_plug):
|
||||
"""Verifies if no creates are needed, none are done."""
|
||||
inst = powervm.TEST_INSTANCE
|
||||
|
||||
# Mock up the CNA response. Both should already exist.
|
||||
mock_vm_get.return_value = [cna('AABBCCDDEEFF'), cna('AABBCCDDEE11')]
|
||||
|
||||
# Mock up the network info. This also validates that they will be
|
||||
# sanitized to upper case. This also validates that we don't call
|
||||
# get_vnics if no nets have vnic_type 'direct'.
|
||||
net_info = [
|
||||
{'address': 'aa:bb:cc:dd:ee:ff', 'vnic_type': 'normal'},
|
||||
{'address': 'aa:bb:cc:dd:ee:11', 'vnic_type': 'normal'}
|
||||
]
|
||||
|
||||
# Run method
|
||||
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info)
|
||||
p_vifs.execute(self.mock_lpar_wrap)
|
||||
|
||||
# The create should have been called with new_vif as False.
|
||||
mock_plug.assert_any_call(self.apt, inst, net_info[0], new_vif=False)
|
||||
mock_plug.assert_any_call(self.apt, inst, net_info[1], new_vif=False)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vif.plug')
|
||||
@mock.patch('nova.virt.powervm.vm.get_cnas')
|
||||
def test_plug_vifs_invalid_state(self, mock_vm_get, mock_plug):
|
||||
"""Tests that a crt_vif fails when the LPAR state is bad."""
|
||||
inst = powervm.TEST_INSTANCE
|
||||
|
||||
# Mock up the CNA response. Only doing one for simplicity
|
||||
mock_vm_get.return_value = []
|
||||
net_info = [{'address': 'aa:bb:cc:dd:ee:ff', 'vnic_type': 'normal'}]
|
||||
|
||||
# Mock that the state is incorrect
|
||||
self.mock_lpar_wrap.can_modify_io.return_value = False, 'bad'
|
||||
|
||||
# Run method
|
||||
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info)
|
||||
self.assertRaises(exception.VirtualInterfaceCreateException,
|
||||
p_vifs.execute, self.mock_lpar_wrap)
|
||||
|
||||
# The create should not have been invoked
|
||||
self.assertEqual(0, mock_plug.call_count)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vif.plug')
|
||||
@mock.patch('nova.virt.powervm.vm.get_cnas')
|
||||
def test_plug_vifs_timeout(self, mock_vm_get, mock_plug):
|
||||
"""Tests that crt vif failure via loss of neutron callback."""
|
||||
inst = powervm.TEST_INSTANCE
|
||||
|
||||
# Mock up the CNA response. Only doing one for simplicity
|
||||
mock_vm_get.return_value = [cna('AABBCCDDEE11')]
|
||||
|
||||
# Mock up the network info.
|
||||
net_info = [{'address': 'aa:bb:cc:dd:ee:ff', 'vnic_type': 'normal'}]
|
||||
|
||||
# Ensure that an exception is raised by a timeout.
|
||||
mock_plug.side_effect = eventlet.timeout.Timeout()
|
||||
|
||||
# Run method
|
||||
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info)
|
||||
self.assertRaises(exception.VirtualInterfaceCreateException,
|
||||
p_vifs.execute, self.mock_lpar_wrap)
|
||||
|
||||
# The create should have only been called once.
|
||||
self.assertEqual(1, mock_plug.call_count)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vif.unplug')
|
||||
@mock.patch('nova.virt.powervm.vif.plug')
|
||||
@mock.patch('nova.virt.powervm.vm.get_cnas')
|
||||
def test_plug_vifs_revert(self, mock_vm_get, mock_plug, mock_unplug):
|
||||
"""Tests that the revert flow works properly."""
|
||||
inst = powervm.TEST_INSTANCE
|
||||
|
||||
# Fake CNA list. The one pre-existing VIF should *not* get reverted.
|
||||
cna_list = [cna('AABBCCDDEEFF'), cna('FFEEDDCCBBAA')]
|
||||
mock_vm_get.return_value = cna_list
|
||||
|
||||
# Mock up the network info. Three roll backs.
|
||||
net_info = [
|
||||
{'address': 'aa:bb:cc:dd:ee:ff', 'vnic_type': 'normal'},
|
||||
{'address': 'aa:bb:cc:dd:ee:22', 'vnic_type': 'normal'},
|
||||
{'address': 'aa:bb:cc:dd:ee:33', 'vnic_type': 'normal'}
|
||||
]
|
||||
|
||||
# Make sure we test raising an exception
|
||||
mock_unplug.side_effect = [exception.NovaException(), None]
|
||||
|
||||
# Run method
|
||||
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info)
|
||||
p_vifs.execute(self.mock_lpar_wrap)
|
||||
p_vifs.revert(self.mock_lpar_wrap, mock.Mock(), mock.Mock())
|
||||
|
||||
# The unplug should be called twice. The exception shouldn't stop the
|
||||
# second call.
|
||||
self.assertEqual(2, mock_unplug.call_count)
|
||||
|
||||
# Make sure each call is invoked correctly. The first plug was not a
|
||||
# new vif, so it should not be reverted.
|
||||
c2 = mock.call(self.apt, inst, net_info[1], cna_w_list=cna_list)
|
||||
c3 = mock.call(self.apt, inst, net_info[2], cna_w_list=cna_list)
|
||||
mock_unplug.assert_has_calls([c2, c3])
|
||||
|
||||
@mock.patch('pypowervm.tasks.cna.crt_cna')
|
||||
@mock.patch('pypowervm.wrappers.network.VSwitch.search')
|
||||
@mock.patch('nova.virt.powervm.vif.plug')
|
||||
@mock.patch('nova.virt.powervm.vm.get_cnas')
|
||||
def test_plug_mgmt_vif(self, mock_vm_get, mock_plug, mock_vs_search,
|
||||
mock_crt_cna):
|
||||
"""Tests that a mgmt vif can be created."""
|
||||
inst = powervm.TEST_INSTANCE
|
||||
|
||||
# Mock up the rmc vswitch
|
||||
vswitch_w = mock.MagicMock()
|
||||
vswitch_w.href = 'fake_mgmt_uri'
|
||||
mock_vs_search.return_value = [vswitch_w]
|
||||
|
||||
# Run method such that it triggers a fresh CNA search
|
||||
p_vifs = tf_net.PlugMgmtVif(self.apt, inst)
|
||||
p_vifs.execute(None)
|
||||
|
||||
# With the default get_cnas mock (which returns a Mock()), we think we
|
||||
# found an existing management CNA.
|
||||
mock_crt_cna.assert_not_called()
|
||||
mock_vm_get.assert_called_once_with(
|
||||
self.apt, inst, vswitch_uri='fake_mgmt_uri')
|
||||
|
||||
# Now mock get_cnas to return no hits
|
||||
mock_vm_get.reset_mock()
|
||||
mock_vm_get.return_value = []
|
||||
p_vifs.execute(None)
|
||||
|
||||
# Get was called; and since it didn't have the mgmt CNA, so was plug.
|
||||
self.assertEqual(1, mock_crt_cna.call_count)
|
||||
mock_vm_get.assert_called_once_with(
|
||||
self.apt, inst, vswitch_uri='fake_mgmt_uri')
|
||||
|
||||
# Now pass CNAs, but not the mgmt vif, "from PlugVifs"
|
||||
cnas = [mock.Mock(vswitch_uri='uri1'), mock.Mock(vswitch_uri='uri2')]
|
||||
mock_crt_cna.reset_mock()
|
||||
mock_vm_get.reset_mock()
|
||||
p_vifs.execute(cnas)
|
||||
|
||||
# Get wasn't called, since the CNAs were passed "from PlugVifs"; but
|
||||
# since the mgmt vif wasn't included, plug was called.
|
||||
mock_vm_get.assert_not_called()
|
||||
mock_crt_cna.assert_called()
|
||||
|
||||
# Finally, pass CNAs including the mgmt.
|
||||
cnas.append(mock.Mock(vswitch_uri='fake_mgmt_uri'))
|
||||
mock_crt_cna.reset_mock()
|
||||
p_vifs.execute(cnas)
|
||||
|
||||
# Neither get nor plug was called.
|
||||
mock_vm_get.assert_not_called()
|
||||
mock_crt_cna.assert_not_called()
|
||||
|
||||
# Validate args on taskflow.task.Task instantiation
|
||||
with mock.patch('taskflow.task.Task.__init__') as tf:
|
||||
tf_net.PlugMgmtVif(self.apt, inst)
|
||||
tf.assert_called_once_with(
|
||||
name='plug_mgmt_vif', provides='mgmt_cna', requires=['vm_cnas'])
|
||||
|
||||
def test_get_vif_events(self):
|
||||
# Set up common mocks.
|
||||
inst = powervm.TEST_INSTANCE
|
||||
net_info = [mock.MagicMock(), mock.MagicMock()]
|
||||
net_info[0]['id'] = 'a'
|
||||
net_info[0].get.return_value = False
|
||||
net_info[1]['id'] = 'b'
|
||||
net_info[1].get.return_value = True
|
||||
|
||||
# Set up the runner.
|
||||
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info)
|
||||
p_vifs.crt_network_infos = net_info
|
||||
resp = p_vifs._get_vif_events()
|
||||
|
||||
# Only one should be returned since only one was active.
|
||||
self.assertEqual(1, len(resp))
|
||||
@@ -1,355 +0,0 @@
|
||||
# Copyright 2015, 2018 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import fixtures
|
||||
from pypowervm import exceptions as pvm_exc
|
||||
|
||||
from nova import exception
|
||||
from nova import test
|
||||
from nova.virt.powervm.tasks import storage as tf_stg
|
||||
|
||||
|
||||
class TestStorage(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestStorage, self).setUp()
|
||||
|
||||
self.adapter = mock.Mock()
|
||||
self.disk_dvr = mock.MagicMock()
|
||||
self.mock_cfg_drv = self.useFixture(fixtures.MockPatch(
|
||||
'nova.virt.powervm.media.ConfigDrivePowerVM')).mock
|
||||
self.mock_mb = self.mock_cfg_drv.return_value
|
||||
self.instance = mock.MagicMock()
|
||||
self.context = 'context'
|
||||
|
||||
def test_create_and_connect_cfg_drive(self):
|
||||
# With a specified FeedTask
|
||||
task = tf_stg.CreateAndConnectCfgDrive(
|
||||
self.adapter, self.instance, 'injected_files',
|
||||
'network_info', 'stg_ftsk', admin_pass='admin_pass')
|
||||
task.execute('mgmt_cna')
|
||||
self.mock_cfg_drv.assert_called_once_with(self.adapter)
|
||||
self.mock_mb.create_cfg_drv_vopt.assert_called_once_with(
|
||||
self.instance, 'injected_files', 'network_info', 'stg_ftsk',
|
||||
admin_pass='admin_pass', mgmt_cna='mgmt_cna')
|
||||
|
||||
# Normal revert
|
||||
task.revert('mgmt_cna', 'result', 'flow_failures')
|
||||
self.mock_mb.dlt_vopt.assert_called_once_with(self.instance,
|
||||
'stg_ftsk')
|
||||
|
||||
self.mock_mb.reset_mock()
|
||||
|
||||
# Revert when dlt_vopt fails
|
||||
self.mock_mb.dlt_vopt.side_effect = pvm_exc.Error('fake-exc')
|
||||
task.revert('mgmt_cna', 'result', 'flow_failures')
|
||||
self.mock_mb.dlt_vopt.assert_called_once()
|
||||
|
||||
self.mock_mb.reset_mock()
|
||||
|
||||
# Revert when media builder not created
|
||||
task.mb = None
|
||||
task.revert('mgmt_cna', 'result', 'flow_failures')
|
||||
self.mock_mb.assert_not_called()
|
||||
|
||||
# Validate args on taskflow.task.Task instantiation
|
||||
with mock.patch('taskflow.task.Task.__init__') as tf:
|
||||
tf_stg.CreateAndConnectCfgDrive(
|
||||
self.adapter, self.instance, 'injected_files',
|
||||
'network_info', 'stg_ftsk', admin_pass='admin_pass')
|
||||
tf.assert_called_once_with(name='cfg_drive', requires=['mgmt_cna'])
|
||||
|
||||
def test_delete_vopt(self):
|
||||
# Test with no FeedTask
|
||||
task = tf_stg.DeleteVOpt(self.adapter, self.instance)
|
||||
task.execute()
|
||||
self.mock_cfg_drv.assert_called_once_with(self.adapter)
|
||||
self.mock_mb.dlt_vopt.assert_called_once_with(
|
||||
self.instance, stg_ftsk=None)
|
||||
|
||||
self.mock_cfg_drv.reset_mock()
|
||||
self.mock_mb.reset_mock()
|
||||
|
||||
# With a specified FeedTask
|
||||
task = tf_stg.DeleteVOpt(self.adapter, self.instance, stg_ftsk='ftsk')
|
||||
task.execute()
|
||||
self.mock_cfg_drv.assert_called_once_with(self.adapter)
|
||||
self.mock_mb.dlt_vopt.assert_called_once_with(
|
||||
self.instance, stg_ftsk='ftsk')
|
||||
|
||||
# Validate args on taskflow.task.Task instantiation
|
||||
with mock.patch('taskflow.task.Task.__init__') as tf:
|
||||
tf_stg.DeleteVOpt(self.adapter, self.instance)
|
||||
tf.assert_called_once_with(name='vopt_delete')
|
||||
|
||||
def test_delete_disk(self):
|
||||
stor_adpt_mappings = mock.Mock()
|
||||
|
||||
task = tf_stg.DeleteDisk(self.disk_dvr)
|
||||
task.execute(stor_adpt_mappings)
|
||||
self.disk_dvr.delete_disks.assert_called_once_with(stor_adpt_mappings)
|
||||
|
||||
# Validate args on taskflow.task.Task instantiation
|
||||
with mock.patch('taskflow.task.Task.__init__') as tf:
|
||||
tf_stg.DeleteDisk(self.disk_dvr)
|
||||
tf.assert_called_once_with(
|
||||
name='delete_disk', requires=['stor_adpt_mappings'])
|
||||
|
||||
def test_detach_disk(self):
|
||||
task = tf_stg.DetachDisk(self.disk_dvr, self.instance)
|
||||
task.execute()
|
||||
self.disk_dvr.detach_disk.assert_called_once_with(self.instance)
|
||||
|
||||
# Validate args on taskflow.task.Task instantiation
|
||||
with mock.patch('taskflow.task.Task.__init__') as tf:
|
||||
tf_stg.DetachDisk(self.disk_dvr, self.instance)
|
||||
tf.assert_called_once_with(
|
||||
name='detach_disk', provides='stor_adpt_mappings')
|
||||
|
||||
def test_attach_disk(self):
|
||||
stg_ftsk = mock.Mock()
|
||||
disk_dev_info = mock.Mock()
|
||||
|
||||
task = tf_stg.AttachDisk(self.disk_dvr, self.instance, stg_ftsk)
|
||||
task.execute(disk_dev_info)
|
||||
self.disk_dvr.attach_disk.assert_called_once_with(
|
||||
self.instance, disk_dev_info, stg_ftsk)
|
||||
|
||||
task.revert(disk_dev_info, 'result', 'flow failures')
|
||||
self.disk_dvr.detach_disk.assert_called_once_with(self.instance)
|
||||
|
||||
self.disk_dvr.detach_disk.reset_mock()
|
||||
|
||||
# Revert failures are not raised
|
||||
self.disk_dvr.detach_disk.side_effect = pvm_exc.TimeoutError(
|
||||
"timed out")
|
||||
task.revert(disk_dev_info, 'result', 'flow failures')
|
||||
self.disk_dvr.detach_disk.assert_called_once_with(self.instance)
|
||||
|
||||
# Validate args on taskflow.task.Task instantiation
|
||||
with mock.patch('taskflow.task.Task.__init__') as tf:
|
||||
tf_stg.AttachDisk(self.disk_dvr, self.instance, stg_ftsk)
|
||||
tf.assert_called_once_with(
|
||||
name='attach_disk', requires=['disk_dev_info'])
|
||||
|
||||
def test_create_disk_for_img(self):
|
||||
image_meta = mock.Mock()
|
||||
|
||||
task = tf_stg.CreateDiskForImg(
|
||||
self.disk_dvr, self.context, self.instance, image_meta)
|
||||
task.execute()
|
||||
self.disk_dvr.create_disk_from_image.assert_called_once_with(
|
||||
self.context, self.instance, image_meta)
|
||||
|
||||
task.revert('result', 'flow failures')
|
||||
self.disk_dvr.delete_disks.assert_called_once_with(['result'])
|
||||
|
||||
self.disk_dvr.delete_disks.reset_mock()
|
||||
|
||||
# Delete not called if no result
|
||||
task.revert(None, None)
|
||||
self.disk_dvr.delete_disks.assert_not_called()
|
||||
|
||||
# Delete exception doesn't raise
|
||||
self.disk_dvr.delete_disks.side_effect = pvm_exc.TimeoutError(
|
||||
"timed out")
|
||||
task.revert('result', 'flow failures')
|
||||
self.disk_dvr.delete_disks.assert_called_once_with(['result'])
|
||||
|
||||
# Validate args on taskflow.task.Task instantiation
|
||||
with mock.patch('taskflow.task.Task.__init__') as tf:
|
||||
tf_stg.CreateDiskForImg(
|
||||
self.disk_dvr, self.context, self.instance, image_meta)
|
||||
tf.assert_called_once_with(
|
||||
name='create_disk_from_img', provides='disk_dev_info')
|
||||
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.find_maps', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.mgmt.discover_vscsi_disk', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.mgmt.remove_block_dev', autospec=True)
|
||||
def test_instance_disk_to_mgmt(self, mock_rm, mock_discover, mock_find):
|
||||
mock_discover.return_value = '/dev/disk'
|
||||
mock_instance = mock.Mock()
|
||||
mock_instance.name = 'instance_name'
|
||||
mock_stg = mock.Mock()
|
||||
mock_stg.name = 'stg_name'
|
||||
mock_vwrap = mock.Mock()
|
||||
mock_vwrap.name = 'vios_name'
|
||||
mock_vwrap.uuid = 'vios_uuid'
|
||||
mock_vwrap.scsi_mappings = ['mapping1']
|
||||
|
||||
disk_dvr = mock.MagicMock()
|
||||
disk_dvr.mp_uuid = 'mp_uuid'
|
||||
disk_dvr.connect_instance_disk_to_mgmt.return_value = (mock_stg,
|
||||
mock_vwrap)
|
||||
|
||||
def reset_mocks():
|
||||
mock_find.reset_mock()
|
||||
mock_discover.reset_mock()
|
||||
mock_rm.reset_mock()
|
||||
disk_dvr.reset_mock()
|
||||
|
||||
# Good path - find_maps returns one result
|
||||
mock_find.return_value = ['one_mapping']
|
||||
tf = tf_stg.InstanceDiskToMgmt(disk_dvr, mock_instance)
|
||||
self.assertEqual('instance_disk_to_mgmt', tf.name)
|
||||
self.assertEqual((mock_stg, mock_vwrap, '/dev/disk'), tf.execute())
|
||||
disk_dvr.connect_instance_disk_to_mgmt.assert_called_with(
|
||||
mock_instance)
|
||||
mock_find.assert_called_with(['mapping1'], client_lpar_id='mp_uuid',
|
||||
stg_elem=mock_stg)
|
||||
mock_discover.assert_called_with('one_mapping')
|
||||
tf.revert('result', 'failures')
|
||||
disk_dvr.disconnect_disk_from_mgmt.assert_called_with('vios_uuid',
|
||||
'stg_name')
|
||||
mock_rm.assert_called_with('/dev/disk')
|
||||
|
||||
# Good path - find_maps returns >1 result
|
||||
reset_mocks()
|
||||
mock_find.return_value = ['first_mapping', 'second_mapping']
|
||||
tf = tf_stg.InstanceDiskToMgmt(disk_dvr, mock_instance)
|
||||
self.assertEqual((mock_stg, mock_vwrap, '/dev/disk'), tf.execute())
|
||||
disk_dvr.connect_instance_disk_to_mgmt.assert_called_with(
|
||||
mock_instance)
|
||||
mock_find.assert_called_with(['mapping1'], client_lpar_id='mp_uuid',
|
||||
stg_elem=mock_stg)
|
||||
mock_discover.assert_called_with('first_mapping')
|
||||
tf.revert('result', 'failures')
|
||||
disk_dvr.disconnect_disk_from_mgmt.assert_called_with('vios_uuid',
|
||||
'stg_name')
|
||||
mock_rm.assert_called_with('/dev/disk')
|
||||
|
||||
# Management Partition is VIOS and NovaLink hosted storage
|
||||
reset_mocks()
|
||||
disk_dvr._vios_uuids = ['mp_uuid']
|
||||
dev_name = '/dev/vg/fake_name'
|
||||
disk_dvr.get_bootdisk_path.return_value = dev_name
|
||||
tf = tf_stg.InstanceDiskToMgmt(disk_dvr, mock_instance)
|
||||
self.assertEqual((None, None, dev_name), tf.execute())
|
||||
|
||||
# Management Partition is VIOS and not NovaLink hosted storage
|
||||
reset_mocks()
|
||||
disk_dvr._vios_uuids = ['mp_uuid']
|
||||
disk_dvr.get_bootdisk_path.return_value = None
|
||||
tf = tf_stg.InstanceDiskToMgmt(disk_dvr, mock_instance)
|
||||
tf.execute()
|
||||
disk_dvr.connect_instance_disk_to_mgmt.assert_called_with(
|
||||
mock_instance)
|
||||
|
||||
# Bad path - find_maps returns no results
|
||||
reset_mocks()
|
||||
mock_find.return_value = []
|
||||
tf = tf_stg.InstanceDiskToMgmt(disk_dvr, mock_instance)
|
||||
self.assertRaises(exception.NewMgmtMappingNotFoundException,
|
||||
tf.execute)
|
||||
disk_dvr.connect_instance_disk_to_mgmt.assert_called_with(
|
||||
mock_instance)
|
||||
# find_maps was still called
|
||||
mock_find.assert_called_with(['mapping1'], client_lpar_id='mp_uuid',
|
||||
stg_elem=mock_stg)
|
||||
# discover_vscsi_disk didn't get called
|
||||
self.assertEqual(0, mock_discover.call_count)
|
||||
tf.revert('result', 'failures')
|
||||
# disconnect_disk_from_mgmt got called
|
||||
disk_dvr.disconnect_disk_from_mgmt.assert_called_with('vios_uuid',
|
||||
'stg_name')
|
||||
# ...but remove_block_dev did not.
|
||||
self.assertEqual(0, mock_rm.call_count)
|
||||
|
||||
# Bad path - connect raises
|
||||
reset_mocks()
|
||||
disk_dvr.connect_instance_disk_to_mgmt.side_effect = (
|
||||
exception.InstanceDiskMappingFailed(instance_name='inst_name'))
|
||||
tf = tf_stg.InstanceDiskToMgmt(disk_dvr, mock_instance)
|
||||
self.assertRaises(exception.InstanceDiskMappingFailed, tf.execute)
|
||||
disk_dvr.connect_instance_disk_to_mgmt.assert_called_with(
|
||||
mock_instance)
|
||||
self.assertEqual(0, mock_find.call_count)
|
||||
self.assertEqual(0, mock_discover.call_count)
|
||||
# revert shouldn't call disconnect or remove
|
||||
tf.revert('result', 'failures')
|
||||
self.assertEqual(0, disk_dvr.disconnect_disk_from_mgmt.call_count)
|
||||
self.assertEqual(0, mock_rm.call_count)
|
||||
|
||||
# Validate args on taskflow.task.Task instantiation
|
||||
with mock.patch('taskflow.task.Task.__init__') as tf:
|
||||
tf_stg.InstanceDiskToMgmt(disk_dvr, mock_instance)
|
||||
tf.assert_called_once_with(
|
||||
name='instance_disk_to_mgmt',
|
||||
provides=['stg_elem', 'vios_wrap', 'disk_path'])
|
||||
|
||||
@mock.patch('nova.virt.powervm.mgmt.remove_block_dev', autospec=True)
|
||||
def test_remove_instance_disk_from_mgmt(self, mock_rm):
|
||||
disk_dvr = mock.MagicMock()
|
||||
mock_instance = mock.Mock()
|
||||
mock_instance.name = 'instance_name'
|
||||
mock_stg = mock.Mock()
|
||||
mock_stg.name = 'stg_name'
|
||||
mock_vwrap = mock.Mock()
|
||||
mock_vwrap.name = 'vios_name'
|
||||
mock_vwrap.uuid = 'vios_uuid'
|
||||
|
||||
tf = tf_stg.RemoveInstanceDiskFromMgmt(disk_dvr, mock_instance)
|
||||
self.assertEqual('remove_inst_disk_from_mgmt', tf.name)
|
||||
|
||||
# Boot disk not mapped to mgmt partition
|
||||
tf.execute(None, mock_vwrap, '/dev/disk')
|
||||
self.assertEqual(disk_dvr.disconnect_disk_from_mgmt.call_count, 0)
|
||||
self.assertEqual(mock_rm.call_count, 0)
|
||||
|
||||
# Boot disk mapped to mgmt partition
|
||||
tf.execute(mock_stg, mock_vwrap, '/dev/disk')
|
||||
disk_dvr.disconnect_disk_from_mgmt.assert_called_with('vios_uuid',
|
||||
'stg_name')
|
||||
mock_rm.assert_called_with('/dev/disk')
|
||||
|
||||
# Validate args on taskflow.task.Task instantiation
|
||||
with mock.patch('taskflow.task.Task.__init__') as tf:
|
||||
tf_stg.RemoveInstanceDiskFromMgmt(disk_dvr, mock_instance)
|
||||
tf.assert_called_once_with(
|
||||
name='remove_inst_disk_from_mgmt',
|
||||
requires=['stg_elem', 'vios_wrap', 'disk_path'])
|
||||
|
||||
def test_attach_volume(self):
|
||||
vol_dvr = mock.Mock(connection_info={'data': {'volume_id': '1'}})
|
||||
|
||||
task = tf_stg.AttachVolume(vol_dvr)
|
||||
task.execute()
|
||||
vol_dvr.attach_volume.assert_called_once_with()
|
||||
|
||||
task.revert('result', 'flow failures')
|
||||
vol_dvr.reset_stg_ftsk.assert_called_once_with()
|
||||
vol_dvr.detach_volume.assert_called_once_with()
|
||||
|
||||
# Validate args on taskflow.task.Task instantiation
|
||||
with mock.patch('taskflow.task.Task.__init__') as tf:
|
||||
tf_stg.AttachVolume(vol_dvr)
|
||||
tf.assert_called_once_with(name='attach_vol_1')
|
||||
|
||||
def test_detach_volume(self):
|
||||
vol_dvr = mock.Mock(connection_info={'data': {'volume_id': '1'}})
|
||||
|
||||
task = tf_stg.DetachVolume(vol_dvr)
|
||||
task.execute()
|
||||
vol_dvr.detach_volume.assert_called_once_with()
|
||||
|
||||
task.revert('result', 'flow failures')
|
||||
vol_dvr.reset_stg_ftsk.assert_called_once_with()
|
||||
vol_dvr.detach_volume.assert_called_once_with()
|
||||
|
||||
# Validate args on taskflow.task.Task instantiation
|
||||
with mock.patch('taskflow.task.Task.__init__') as tf:
|
||||
tf_stg.DetachVolume(vol_dvr)
|
||||
tf.assert_called_once_with(name='detach_vol_1')
|
||||
@@ -1,134 +0,0 @@
|
||||
# Copyright 2015, 2018 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from taskflow import engines as tf_eng
|
||||
from taskflow.patterns import linear_flow as tf_lf
|
||||
from taskflow import task as tf_tsk
|
||||
from unittest import mock
|
||||
|
||||
from nova import exception
|
||||
from nova import test
|
||||
from nova.virt.powervm.tasks import vm as tf_vm
|
||||
|
||||
|
||||
class TestVMTasks(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
super(TestVMTasks, self).setUp()
|
||||
self.apt = mock.Mock()
|
||||
self.instance = mock.Mock()
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper', autospec=True)
|
||||
def test_get(self, mock_get_wrap):
|
||||
get = tf_vm.Get(self.apt, self.instance)
|
||||
get.execute()
|
||||
mock_get_wrap.assert_called_once_with(self.apt, self.instance)
|
||||
|
||||
# Validate args on taskflow.task.Task instantiation
|
||||
with mock.patch('taskflow.task.Task.__init__') as tf:
|
||||
tf_vm.Get(self.apt, self.instance)
|
||||
tf.assert_called_once_with(name='get_vm', provides='lpar_wrap')
|
||||
|
||||
@mock.patch('pypowervm.tasks.storage.add_lpar_storage_scrub_tasks',
|
||||
autospec=True)
|
||||
@mock.patch('nova.virt.powervm.vm.create_lpar')
|
||||
def test_create(self, mock_vm_crt, mock_stg):
|
||||
lpar_entry = mock.Mock()
|
||||
|
||||
# Test create with normal (non-recreate) ftsk
|
||||
crt = tf_vm.Create(self.apt, 'host_wrapper', self.instance, 'ftsk')
|
||||
mock_vm_crt.return_value = lpar_entry
|
||||
crt.execute()
|
||||
|
||||
mock_vm_crt.assert_called_once_with(self.apt, 'host_wrapper',
|
||||
self.instance)
|
||||
|
||||
mock_stg.assert_called_once_with(
|
||||
[lpar_entry.id], 'ftsk', lpars_exist=True)
|
||||
mock_stg.assert_called_once_with([mock_vm_crt.return_value.id], 'ftsk',
|
||||
lpars_exist=True)
|
||||
|
||||
# Validate args on taskflow.task.Task instantiation
|
||||
with mock.patch('taskflow.task.Task.__init__') as tf:
|
||||
tf_vm.Create(self.apt, 'host_wrapper', self.instance, 'ftsk')
|
||||
tf.assert_called_once_with(name='crt_vm', provides='lpar_wrap')
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.power_on')
|
||||
def test_power_on(self, mock_pwron):
|
||||
pwron = tf_vm.PowerOn(self.apt, self.instance)
|
||||
pwron.execute()
|
||||
mock_pwron.assert_called_once_with(self.apt, self.instance)
|
||||
|
||||
# Validate args on taskflow.task.Task instantiation
|
||||
with mock.patch('taskflow.task.Task.__init__') as tf:
|
||||
tf_vm.PowerOn(self.apt, self.instance)
|
||||
tf.assert_called_once_with(name='pwr_vm')
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.power_on')
|
||||
@mock.patch('nova.virt.powervm.vm.power_off')
|
||||
def test_power_on_revert(self, mock_pwroff, mock_pwron):
|
||||
flow = tf_lf.Flow('revert_power_on')
|
||||
pwron = tf_vm.PowerOn(self.apt, self.instance)
|
||||
flow.add(pwron)
|
||||
|
||||
# Dummy Task that fails, triggering flow revert
|
||||
def failure(*a, **k):
|
||||
raise ValueError()
|
||||
flow.add(tf_tsk.FunctorTask(failure))
|
||||
|
||||
# When PowerOn.execute doesn't fail, revert calls power_off
|
||||
self.assertRaises(ValueError, tf_eng.run, flow)
|
||||
mock_pwron.assert_called_once_with(self.apt, self.instance)
|
||||
mock_pwroff.assert_called_once_with(self.apt, self.instance,
|
||||
force_immediate=True)
|
||||
|
||||
mock_pwron.reset_mock()
|
||||
mock_pwroff.reset_mock()
|
||||
|
||||
# When PowerOn.execute fails, revert doesn't call power_off
|
||||
mock_pwron.side_effect = exception.NovaException()
|
||||
self.assertRaises(exception.NovaException, tf_eng.run, flow)
|
||||
mock_pwron.assert_called_once_with(self.apt, self.instance)
|
||||
mock_pwroff.assert_not_called()
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.power_off')
|
||||
def test_power_off(self, mock_pwroff):
|
||||
# Default force_immediate
|
||||
pwroff = tf_vm.PowerOff(self.apt, self.instance)
|
||||
pwroff.execute()
|
||||
mock_pwroff.assert_called_once_with(self.apt, self.instance,
|
||||
force_immediate=False)
|
||||
|
||||
mock_pwroff.reset_mock()
|
||||
|
||||
# Explicit force_immediate
|
||||
pwroff = tf_vm.PowerOff(self.apt, self.instance, force_immediate=True)
|
||||
pwroff.execute()
|
||||
mock_pwroff.assert_called_once_with(self.apt, self.instance,
|
||||
force_immediate=True)
|
||||
|
||||
# Validate args on taskflow.task.Task instantiation
|
||||
with mock.patch('taskflow.task.Task.__init__') as tf:
|
||||
tf_vm.PowerOff(self.apt, self.instance)
|
||||
tf.assert_called_once_with(name='pwr_off_vm')
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.delete_lpar')
|
||||
def test_delete(self, mock_dlt):
|
||||
delete = tf_vm.Delete(self.apt, self.instance)
|
||||
delete.execute()
|
||||
mock_dlt.assert_called_once_with(self.apt, self.instance)
|
||||
|
||||
# Validate args on taskflow.task.Task instantiation
|
||||
with mock.patch('taskflow.task.Task.__init__') as tf:
|
||||
tf_vm.Delete(self.apt, self.instance)
|
||||
tf.assert_called_once_with(name='dlt_vm')
|
||||
@@ -1,649 +0,0 @@
|
||||
# Copyright 2016, 2018 IBM Corp.
|
||||
#
|
||||
# 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
|
||||
from unittest import mock
|
||||
|
||||
import fixtures
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils.fixture import uuidsentinel as uuids
|
||||
from pypowervm import const as pvm_const
|
||||
from pypowervm import exceptions as pvm_exc
|
||||
from pypowervm.helpers import log_helper as pvm_hlp_log
|
||||
from pypowervm.helpers import vios_busy as pvm_hlp_vbusy
|
||||
from pypowervm.utils import transaction as pvm_tx
|
||||
from pypowervm.wrappers import virtual_io_server as pvm_vios
|
||||
|
||||
from nova import block_device as nova_block_device
|
||||
from nova.compute import provider_tree
|
||||
from nova import conf as cfg
|
||||
from nova import exception
|
||||
from nova.objects import block_device as bdmobj
|
||||
from nova import test
|
||||
from nova.tests.unit.virt import powervm
|
||||
from nova.virt import block_device as nova_virt_bdm
|
||||
from nova.virt import driver as nova_driver
|
||||
from nova.virt.driver import ComputeDriver
|
||||
from nova.virt import hardware
|
||||
from nova.virt.powervm.disk import ssp
|
||||
from nova.virt.powervm import driver
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class TestPowerVMDriver(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestPowerVMDriver, self).setUp()
|
||||
self.drv = driver.PowerVMDriver('virtapi')
|
||||
self.adp = self.useFixture(fixtures.MockPatch(
|
||||
'pypowervm.adapter.Adapter', autospec=True)).mock
|
||||
self.drv.adapter = self.adp
|
||||
self.sess = self.useFixture(fixtures.MockPatch(
|
||||
'pypowervm.adapter.Session', autospec=True)).mock
|
||||
|
||||
self.pwron = self.useFixture(fixtures.MockPatch(
|
||||
'nova.virt.powervm.vm.power_on')).mock
|
||||
self.pwroff = self.useFixture(fixtures.MockPatch(
|
||||
'nova.virt.powervm.vm.power_off')).mock
|
||||
|
||||
# Create an instance to test with
|
||||
self.inst = powervm.TEST_INSTANCE
|
||||
|
||||
def test_driver_capabilities(self):
|
||||
"""Test the driver capabilities."""
|
||||
# check that the driver reports all capabilities
|
||||
self.assertEqual(set(ComputeDriver.capabilities),
|
||||
set(self.drv.capabilities))
|
||||
# check the values for each capability
|
||||
self.assertFalse(self.drv.capabilities['has_imagecache'])
|
||||
self.assertFalse(self.drv.capabilities['supports_evacuate'])
|
||||
self.assertFalse(
|
||||
self.drv.capabilities['supports_migrate_to_same_host'])
|
||||
self.assertTrue(self.drv.capabilities['supports_attach_interface'])
|
||||
self.assertFalse(self.drv.capabilities['supports_device_tagging'])
|
||||
self.assertFalse(
|
||||
self.drv.capabilities['supports_tagged_attach_interface'])
|
||||
self.assertFalse(
|
||||
self.drv.capabilities['supports_tagged_attach_volume'])
|
||||
self.assertTrue(self.drv.capabilities['supports_extend_volume'])
|
||||
self.assertFalse(self.drv.capabilities['supports_multiattach'])
|
||||
|
||||
@mock.patch('nova.image.glance.API')
|
||||
@mock.patch('pypowervm.tasks.storage.ComprehensiveScrub', autospec=True)
|
||||
@mock.patch('oslo_utils.importutils.import_object_ns', autospec=True)
|
||||
@mock.patch('pypowervm.wrappers.managed_system.System', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.partition.validate_vios_ready', autospec=True)
|
||||
def test_init_host(self, mock_vvr, mock_sys, mock_import, mock_scrub,
|
||||
mock_img):
|
||||
mock_hostw = mock.Mock(uuid='uuid')
|
||||
mock_sys.get.return_value = [mock_hostw]
|
||||
self.drv.init_host('host')
|
||||
self.sess.assert_called_once_with(conn_tries=60)
|
||||
self.adp.assert_called_once_with(
|
||||
self.sess.return_value, helpers=[
|
||||
pvm_hlp_log.log_helper, pvm_hlp_vbusy.vios_busy_retry_helper])
|
||||
mock_vvr.assert_called_once_with(self.drv.adapter)
|
||||
mock_sys.get.assert_called_once_with(self.drv.adapter)
|
||||
self.assertEqual(mock_hostw, self.drv.host_wrapper)
|
||||
mock_scrub.assert_called_once_with(self.drv.adapter)
|
||||
mock_scrub.return_value.execute.assert_called_once_with()
|
||||
mock_import.assert_called_once_with(
|
||||
'nova.virt.powervm.disk', 'localdisk.LocalStorage',
|
||||
self.drv.adapter, 'uuid')
|
||||
self.assertEqual(mock_import.return_value, self.drv.disk_dvr)
|
||||
mock_img.assert_called_once_with()
|
||||
self.assertEqual(mock_img.return_value, self.drv.image_api)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.get_pvm_uuid')
|
||||
@mock.patch('nova.virt.powervm.vm.get_vm_qp')
|
||||
@mock.patch('nova.virt.powervm.vm._translate_vm_state')
|
||||
def test_get_info(self, mock_tx_state, mock_qp, mock_uuid):
|
||||
mock_tx_state.return_value = 'fake-state'
|
||||
self.assertEqual(hardware.InstanceInfo('fake-state'),
|
||||
self.drv.get_info('inst'))
|
||||
mock_uuid.assert_called_once_with('inst')
|
||||
mock_qp.assert_called_once_with(
|
||||
self.drv.adapter, mock_uuid.return_value, 'PartitionState')
|
||||
mock_tx_state.assert_called_once_with(mock_qp.return_value)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.get_lpar_names')
|
||||
def test_list_instances(self, mock_names):
|
||||
mock_names.return_value = ['one', 'two', 'three']
|
||||
self.assertEqual(['one', 'two', 'three'], self.drv.list_instances())
|
||||
mock_names.assert_called_once_with(self.adp)
|
||||
|
||||
def test_get_available_nodes(self):
|
||||
self.flags(host='hostname')
|
||||
self.assertEqual(['hostname'], self.drv.get_available_nodes('node'))
|
||||
|
||||
@mock.patch('pypowervm.wrappers.managed_system.System', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.host.build_host_resource_from_ms')
|
||||
def test_get_available_resource(self, mock_bhrfm, mock_sys):
|
||||
mock_sys.get.return_value = ['sys']
|
||||
mock_bhrfm.return_value = {'foo': 'bar'}
|
||||
self.drv.disk_dvr = mock.create_autospec(ssp.SSPDiskAdapter,
|
||||
instance=True)
|
||||
self.assertEqual(
|
||||
{'foo': 'bar', 'local_gb': self.drv.disk_dvr.capacity,
|
||||
'local_gb_used': self.drv.disk_dvr.capacity_used},
|
||||
self.drv.get_available_resource('node'))
|
||||
mock_sys.get.assert_called_once_with(self.adp)
|
||||
mock_bhrfm.assert_called_once_with('sys')
|
||||
self.assertEqual('sys', self.drv.host_wrapper)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _update_provider_tree(self, allocations=None):
|
||||
"""Host resource dict gets converted properly to provider tree inv."""
|
||||
|
||||
with mock.patch('nova.virt.powervm.host.'
|
||||
'build_host_resource_from_ms') as mock_bhrfm:
|
||||
mock_bhrfm.return_value = {
|
||||
'vcpus': 8,
|
||||
'memory_mb': 2048,
|
||||
}
|
||||
self.drv.host_wrapper = 'host_wrapper'
|
||||
# Validate that this gets converted to int with floor
|
||||
self.drv.disk_dvr = mock.Mock(capacity=2091.8)
|
||||
exp_inv = {
|
||||
'VCPU': {
|
||||
'total': 8,
|
||||
'max_unit': 8,
|
||||
'allocation_ratio': 16.0,
|
||||
'reserved': 0,
|
||||
},
|
||||
'MEMORY_MB': {
|
||||
'total': 2048,
|
||||
'max_unit': 2048,
|
||||
'allocation_ratio': 1.5,
|
||||
'reserved': 512,
|
||||
},
|
||||
'DISK_GB': {
|
||||
'total': 2091,
|
||||
'max_unit': 2091,
|
||||
'allocation_ratio': 1.0,
|
||||
'reserved': 0,
|
||||
},
|
||||
}
|
||||
ptree = provider_tree.ProviderTree()
|
||||
ptree.new_root('compute_host', uuids.cn)
|
||||
# Let the caller muck with these
|
||||
yield ptree, exp_inv
|
||||
self.drv.update_provider_tree(ptree, 'compute_host',
|
||||
allocations=allocations)
|
||||
self.assertEqual(exp_inv, ptree.data('compute_host').inventory)
|
||||
mock_bhrfm.assert_called_once_with('host_wrapper')
|
||||
|
||||
def test_update_provider_tree(self):
|
||||
# Basic: no inventory already on the provider, no extra providers, no
|
||||
# aggregates or traits.
|
||||
with self._update_provider_tree():
|
||||
pass
|
||||
|
||||
def test_update_provider_tree_ignore_allocations(self):
|
||||
with self._update_provider_tree(allocations="This is ignored"):
|
||||
pass
|
||||
|
||||
def test_update_provider_tree_conf_overrides(self):
|
||||
# Non-default CONF values for allocation ratios and reserved.
|
||||
self.flags(cpu_allocation_ratio=12.3,
|
||||
reserved_host_cpus=4,
|
||||
ram_allocation_ratio=4.5,
|
||||
reserved_host_memory_mb=32,
|
||||
disk_allocation_ratio=6.7,
|
||||
# This gets int(ceil)'d
|
||||
reserved_host_disk_mb=5432.1)
|
||||
with self._update_provider_tree() as (_, exp_inv):
|
||||
exp_inv['VCPU']['allocation_ratio'] = 12.3
|
||||
exp_inv['VCPU']['reserved'] = 4
|
||||
exp_inv['MEMORY_MB']['allocation_ratio'] = 4.5
|
||||
exp_inv['MEMORY_MB']['reserved'] = 32
|
||||
exp_inv['DISK_GB']['allocation_ratio'] = 6.7
|
||||
exp_inv['DISK_GB']['reserved'] = 6
|
||||
|
||||
def test_update_provider_tree_complex_ptree(self):
|
||||
# Overrides inventory already on the provider; leaves other providers
|
||||
# and aggregates/traits alone.
|
||||
with self._update_provider_tree() as (ptree, exp_inv):
|
||||
ptree.update_inventory('compute_host', {
|
||||
# these should get blown away
|
||||
'VCPU': {
|
||||
'total': 16,
|
||||
'max_unit': 2,
|
||||
'allocation_ratio': 1.0,
|
||||
'reserved': 10,
|
||||
},
|
||||
'CUSTOM_BOGUS': {
|
||||
'total': 1234,
|
||||
}
|
||||
})
|
||||
ptree.update_aggregates('compute_host',
|
||||
[uuids.ss_agg, uuids.other_agg])
|
||||
ptree.update_traits('compute_host', ['CUSTOM_FOO', 'CUSTOM_BAR'])
|
||||
ptree.new_root('ssp', uuids.ssp)
|
||||
ptree.update_inventory('ssp', {'sentinel': 'inventory',
|
||||
'for': 'ssp'})
|
||||
ptree.update_aggregates('ssp', [uuids.ss_agg])
|
||||
ptree.new_child('sriov', 'compute_host', uuid=uuids.sriov)
|
||||
# Since CONF.cpu_allocation_ratio is not set and this is not
|
||||
# the initial upt call (so CONF.initial_cpu_allocation_ratio would
|
||||
# be used), the existing allocation ratio value from the tree is
|
||||
# used.
|
||||
exp_inv['VCPU']['allocation_ratio'] = 1.0
|
||||
|
||||
# Make sure the compute's agg and traits were left alone
|
||||
cndata = ptree.data('compute_host')
|
||||
self.assertEqual(set([uuids.ss_agg, uuids.other_agg]),
|
||||
cndata.aggregates)
|
||||
self.assertEqual(set(['CUSTOM_FOO', 'CUSTOM_BAR']), cndata.traits)
|
||||
# And the other providers were left alone
|
||||
self.assertEqual(set([uuids.cn, uuids.ssp, uuids.sriov]),
|
||||
set(ptree.get_provider_uuids()))
|
||||
# ...including the ssp's aggregates
|
||||
self.assertEqual(set([uuids.ss_agg]), ptree.data('ssp').aggregates)
|
||||
|
||||
@mock.patch('nova.virt.powervm.tasks.storage.AttachVolume.execute')
|
||||
@mock.patch('nova.virt.powervm.tasks.network.PlugMgmtVif.execute')
|
||||
@mock.patch('nova.virt.powervm.tasks.network.PlugVifs.execute')
|
||||
@mock.patch('nova.virt.powervm.media.ConfigDrivePowerVM')
|
||||
@mock.patch('nova.virt.configdrive.required_by')
|
||||
@mock.patch('nova.virt.powervm.vm.create_lpar')
|
||||
@mock.patch('pypowervm.tasks.partition.build_active_vio_feed_task',
|
||||
autospec=True)
|
||||
@mock.patch('pypowervm.tasks.storage.add_lpar_storage_scrub_tasks',
|
||||
autospec=True)
|
||||
def test_spawn_ops(self, mock_scrub, mock_bldftsk, mock_crt_lpar,
|
||||
mock_cdrb, mock_cfg_drv, mock_plug_vifs,
|
||||
mock_plug_mgmt_vif, mock_attach_vol):
|
||||
"""Validates the 'typical' spawn flow of the spawn of an instance. """
|
||||
mock_cdrb.return_value = True
|
||||
self.drv.host_wrapper = mock.Mock()
|
||||
self.drv.disk_dvr = mock.create_autospec(ssp.SSPDiskAdapter,
|
||||
instance=True)
|
||||
mock_ftsk = pvm_tx.FeedTask('fake', [mock.Mock(spec=pvm_vios.VIOS)])
|
||||
mock_bldftsk.return_value = mock_ftsk
|
||||
block_device_info = self._fake_bdms()
|
||||
self.drv.spawn('context', self.inst, 'img_meta', 'files', 'password',
|
||||
'allocs', network_info='netinfo',
|
||||
block_device_info=block_device_info)
|
||||
mock_crt_lpar.assert_called_once_with(
|
||||
self.adp, self.drv.host_wrapper, self.inst)
|
||||
mock_bldftsk.assert_called_once_with(
|
||||
self.adp, xag={pvm_const.XAG.VIO_SMAP, pvm_const.XAG.VIO_FMAP})
|
||||
self.assertTrue(mock_plug_vifs.called)
|
||||
self.assertTrue(mock_plug_mgmt_vif.called)
|
||||
mock_scrub.assert_called_once_with(
|
||||
[mock_crt_lpar.return_value.id], mock_ftsk, lpars_exist=True)
|
||||
self.drv.disk_dvr.create_disk_from_image.assert_called_once_with(
|
||||
'context', self.inst, 'img_meta')
|
||||
self.drv.disk_dvr.attach_disk.assert_called_once_with(
|
||||
self.inst, self.drv.disk_dvr.create_disk_from_image.return_value,
|
||||
mock_ftsk)
|
||||
self.assertEqual(2, mock_attach_vol.call_count)
|
||||
mock_cfg_drv.assert_called_once_with(self.adp)
|
||||
mock_cfg_drv.return_value.create_cfg_drv_vopt.assert_called_once_with(
|
||||
self.inst, 'files', 'netinfo', mock_ftsk, admin_pass='password',
|
||||
mgmt_cna=mock.ANY)
|
||||
self.pwron.assert_called_once_with(self.adp, self.inst)
|
||||
|
||||
mock_cfg_drv.reset_mock()
|
||||
mock_attach_vol.reset_mock()
|
||||
|
||||
# No config drive, no bdms
|
||||
mock_cdrb.return_value = False
|
||||
self.drv.spawn('context', self.inst, 'img_meta', 'files', 'password',
|
||||
'allocs')
|
||||
mock_cfg_drv.assert_not_called()
|
||||
mock_attach_vol.assert_not_called()
|
||||
|
||||
@mock.patch('nova.virt.powervm.tasks.storage.DetachVolume.execute')
|
||||
@mock.patch('nova.virt.powervm.tasks.network.UnplugVifs.execute')
|
||||
@mock.patch('nova.virt.powervm.vm.delete_lpar')
|
||||
@mock.patch('nova.virt.powervm.media.ConfigDrivePowerVM')
|
||||
@mock.patch('nova.virt.configdrive.required_by')
|
||||
@mock.patch('pypowervm.tasks.partition.build_active_vio_feed_task',
|
||||
autospec=True)
|
||||
def test_destroy(self, mock_bldftsk, mock_cdrb, mock_cfgdrv,
|
||||
mock_dlt_lpar, mock_unplug, mock_detach_vol):
|
||||
"""Validates PowerVM destroy."""
|
||||
self.drv.host_wrapper = mock.Mock()
|
||||
self.drv.disk_dvr = mock.create_autospec(ssp.SSPDiskAdapter,
|
||||
instance=True)
|
||||
|
||||
mock_ftsk = pvm_tx.FeedTask('fake', [mock.Mock(spec=pvm_vios.VIOS)])
|
||||
mock_bldftsk.return_value = mock_ftsk
|
||||
block_device_info = self._fake_bdms()
|
||||
|
||||
# Good path, with config drive, destroy disks
|
||||
mock_cdrb.return_value = True
|
||||
self.drv.destroy('context', self.inst, [],
|
||||
block_device_info=block_device_info)
|
||||
self.pwroff.assert_called_once_with(
|
||||
self.adp, self.inst, force_immediate=True)
|
||||
mock_bldftsk.assert_called_once_with(
|
||||
self.adp, xag=[pvm_const.XAG.VIO_SMAP])
|
||||
mock_unplug.assert_called_once()
|
||||
mock_cdrb.assert_called_once_with(self.inst)
|
||||
mock_cfgdrv.assert_called_once_with(self.adp)
|
||||
mock_cfgdrv.return_value.dlt_vopt.assert_called_once_with(
|
||||
self.inst, stg_ftsk=mock_bldftsk.return_value)
|
||||
self.assertEqual(2, mock_detach_vol.call_count)
|
||||
self.drv.disk_dvr.detach_disk.assert_called_once_with(
|
||||
self.inst)
|
||||
self.drv.disk_dvr.delete_disks.assert_called_once_with(
|
||||
self.drv.disk_dvr.detach_disk.return_value)
|
||||
mock_dlt_lpar.assert_called_once_with(self.adp, self.inst)
|
||||
|
||||
self.pwroff.reset_mock()
|
||||
mock_bldftsk.reset_mock()
|
||||
mock_unplug.reset_mock()
|
||||
mock_cdrb.reset_mock()
|
||||
mock_cfgdrv.reset_mock()
|
||||
self.drv.disk_dvr.detach_disk.reset_mock()
|
||||
self.drv.disk_dvr.delete_disks.reset_mock()
|
||||
mock_detach_vol.reset_mock()
|
||||
mock_dlt_lpar.reset_mock()
|
||||
|
||||
# No config drive, preserve disks, no block device info
|
||||
mock_cdrb.return_value = False
|
||||
self.drv.destroy('context', self.inst, [], block_device_info={},
|
||||
destroy_disks=False)
|
||||
mock_cfgdrv.return_value.dlt_vopt.assert_not_called()
|
||||
mock_detach_vol.assert_not_called()
|
||||
self.drv.disk_dvr.delete_disks.assert_not_called()
|
||||
|
||||
# Non-forced power_off, since preserving disks
|
||||
self.pwroff.assert_called_once_with(
|
||||
self.adp, self.inst, force_immediate=False)
|
||||
mock_bldftsk.assert_called_once_with(
|
||||
self.adp, xag=[pvm_const.XAG.VIO_SMAP])
|
||||
mock_unplug.assert_called_once()
|
||||
mock_cdrb.assert_called_once_with(self.inst)
|
||||
mock_cfgdrv.assert_not_called()
|
||||
mock_cfgdrv.return_value.dlt_vopt.assert_not_called()
|
||||
self.drv.disk_dvr.detach_disk.assert_called_once_with(
|
||||
self.inst)
|
||||
self.drv.disk_dvr.delete_disks.assert_not_called()
|
||||
mock_dlt_lpar.assert_called_once_with(self.adp, self.inst)
|
||||
|
||||
self.pwroff.reset_mock()
|
||||
mock_bldftsk.reset_mock()
|
||||
mock_unplug.reset_mock()
|
||||
mock_cdrb.reset_mock()
|
||||
mock_cfgdrv.reset_mock()
|
||||
self.drv.disk_dvr.detach_disk.reset_mock()
|
||||
self.drv.disk_dvr.delete_disks.reset_mock()
|
||||
mock_dlt_lpar.reset_mock()
|
||||
|
||||
# InstanceNotFound exception, non-forced
|
||||
self.pwroff.side_effect = exception.InstanceNotFound(
|
||||
instance_id='something')
|
||||
self.drv.destroy('context', self.inst, [], block_device_info={},
|
||||
destroy_disks=False)
|
||||
self.pwroff.assert_called_once_with(
|
||||
self.adp, self.inst, force_immediate=False)
|
||||
self.drv.disk_dvr.detach_disk.assert_not_called()
|
||||
mock_unplug.assert_not_called()
|
||||
self.drv.disk_dvr.delete_disks.assert_not_called()
|
||||
mock_dlt_lpar.assert_not_called()
|
||||
|
||||
self.pwroff.reset_mock()
|
||||
self.pwroff.side_effect = None
|
||||
mock_unplug.reset_mock()
|
||||
|
||||
# Convertible (PowerVM) exception
|
||||
mock_dlt_lpar.side_effect = pvm_exc.TimeoutError("Timed out")
|
||||
self.assertRaises(exception.InstanceTerminationFailure,
|
||||
self.drv.destroy, 'context', self.inst, [],
|
||||
block_device_info={})
|
||||
|
||||
# Everything got called
|
||||
self.pwroff.assert_called_once_with(
|
||||
self.adp, self.inst, force_immediate=True)
|
||||
mock_unplug.assert_called_once()
|
||||
self.drv.disk_dvr.detach_disk.assert_called_once_with(self.inst)
|
||||
self.drv.disk_dvr.delete_disks.assert_called_once_with(
|
||||
self.drv.disk_dvr.detach_disk.return_value)
|
||||
mock_dlt_lpar.assert_called_once_with(self.adp, self.inst)
|
||||
|
||||
# Other random exception raises directly
|
||||
mock_dlt_lpar.side_effect = ValueError()
|
||||
self.assertRaises(ValueError,
|
||||
self.drv.destroy, 'context', self.inst, [],
|
||||
block_device_info={})
|
||||
|
||||
@mock.patch('nova.virt.powervm.tasks.image.UpdateTaskState.'
|
||||
'execute', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.tasks.storage.InstanceDiskToMgmt.'
|
||||
'execute', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.tasks.image.StreamToGlance.execute')
|
||||
@mock.patch('nova.virt.powervm.tasks.storage.RemoveInstanceDiskFromMgmt.'
|
||||
'execute')
|
||||
def test_snapshot(self, mock_rm, mock_stream, mock_conn, mock_update):
|
||||
self.drv.disk_dvr = mock.Mock()
|
||||
self.drv.image_api = mock.Mock()
|
||||
mock_conn.return_value = 'stg_elem', 'vios_wrap', 'disk_path'
|
||||
self.drv.snapshot('context', self.inst, 'image_id',
|
||||
'update_task_state')
|
||||
self.assertEqual(2, mock_update.call_count)
|
||||
self.assertEqual(1, mock_conn.call_count)
|
||||
mock_stream.assert_called_once_with(disk_path='disk_path')
|
||||
mock_rm.assert_called_once_with(
|
||||
stg_elem='stg_elem', vios_wrap='vios_wrap', disk_path='disk_path')
|
||||
|
||||
self.drv.disk_dvr.capabilities = {'snapshot': False}
|
||||
self.assertRaises(exception.NotSupportedWithOption, self.drv.snapshot,
|
||||
'context', self.inst, 'image_id', 'update_task_state')
|
||||
|
||||
def test_power_on(self):
|
||||
self.drv.power_on('context', self.inst, 'network_info')
|
||||
self.pwron.assert_called_once_with(self.adp, self.inst)
|
||||
|
||||
def test_power_off(self):
|
||||
self.drv.power_off(self.inst)
|
||||
self.pwroff.assert_called_once_with(
|
||||
self.adp, self.inst, force_immediate=True, timeout=None)
|
||||
|
||||
def test_power_off_timeout(self):
|
||||
# Long timeout (retry interval means nothing on powervm)
|
||||
self.drv.power_off(self.inst, timeout=500, retry_interval=10)
|
||||
self.pwroff.assert_called_once_with(
|
||||
self.adp, self.inst, force_immediate=False, timeout=500)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.reboot')
|
||||
def test_reboot_soft(self, mock_reboot):
|
||||
inst = mock.Mock()
|
||||
self.drv.reboot('context', inst, 'network_info', 'SOFT')
|
||||
mock_reboot.assert_called_once_with(self.adp, inst, False)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.reboot')
|
||||
def test_reboot_hard(self, mock_reboot):
|
||||
inst = mock.Mock()
|
||||
self.drv.reboot('context', inst, 'network_info', 'HARD')
|
||||
mock_reboot.assert_called_once_with(self.adp, inst, True)
|
||||
|
||||
@mock.patch('nova.virt.powervm.driver.PowerVMDriver.plug_vifs')
|
||||
def test_attach_interface(self, mock_plug_vifs):
|
||||
self.drv.attach_interface('context', 'inst', 'image_meta', 'vif')
|
||||
mock_plug_vifs.assert_called_once_with('inst', ['vif'])
|
||||
|
||||
@mock.patch('nova.virt.powervm.driver.PowerVMDriver.unplug_vifs')
|
||||
def test_detach_interface(self, mock_unplug_vifs):
|
||||
self.drv.detach_interface('context', 'inst', 'vif')
|
||||
mock_unplug_vifs.assert_called_once_with('inst', ['vif'])
|
||||
|
||||
@mock.patch('nova.virt.powervm.tasks.vm.Get', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.tasks.base.run', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.tasks.network.PlugVifs', autospec=True)
|
||||
@mock.patch('taskflow.patterns.linear_flow.Flow', autospec=True)
|
||||
def test_plug_vifs(self, mock_tf, mock_plug_vifs, mock_tf_run, mock_get):
|
||||
# Successful plug
|
||||
mock_inst = mock.Mock()
|
||||
self.drv.plug_vifs(mock_inst, 'net_info')
|
||||
mock_get.assert_called_once_with(self.adp, mock_inst)
|
||||
mock_plug_vifs.assert_called_once_with(
|
||||
self.drv.virtapi, self.adp, mock_inst, 'net_info')
|
||||
add_calls = [mock.call(mock_get.return_value),
|
||||
mock.call(mock_plug_vifs.return_value)]
|
||||
mock_tf.return_value.add.assert_has_calls(add_calls)
|
||||
mock_tf_run.assert_called_once_with(
|
||||
mock_tf.return_value, instance=mock_inst)
|
||||
|
||||
# InstanceNotFound and generic exception both raise
|
||||
mock_tf_run.side_effect = exception.InstanceNotFound('id')
|
||||
exc = self.assertRaises(exception.VirtualInterfacePlugException,
|
||||
self.drv.plug_vifs, mock_inst, 'net_info')
|
||||
self.assertIn('instance', str(exc))
|
||||
mock_tf_run.side_effect = Exception
|
||||
exc = self.assertRaises(exception.VirtualInterfacePlugException,
|
||||
self.drv.plug_vifs, mock_inst, 'net_info')
|
||||
self.assertIn('unexpected', str(exc))
|
||||
|
||||
@mock.patch('nova.virt.powervm.tasks.base.run', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.tasks.network.UnplugVifs', autospec=True)
|
||||
@mock.patch('taskflow.patterns.linear_flow.Flow', autospec=True)
|
||||
def test_unplug_vifs(self, mock_tf, mock_unplug_vifs, mock_tf_run):
|
||||
# Successful unplug
|
||||
mock_inst = mock.Mock()
|
||||
self.drv.unplug_vifs(mock_inst, 'net_info')
|
||||
mock_unplug_vifs.assert_called_once_with(self.adp, mock_inst,
|
||||
'net_info')
|
||||
mock_tf.return_value.add.assert_called_once_with(
|
||||
mock_unplug_vifs.return_value)
|
||||
mock_tf_run.assert_called_once_with(mock_tf.return_value,
|
||||
instance=mock_inst)
|
||||
|
||||
# InstanceNotFound should pass
|
||||
mock_tf_run.side_effect = exception.InstanceNotFound(instance_id='1')
|
||||
self.drv.unplug_vifs(mock_inst, 'net_info')
|
||||
|
||||
# Raise InterfaceDetachFailed otherwise
|
||||
mock_tf_run.side_effect = Exception
|
||||
self.assertRaises(exception.InterfaceDetachFailed,
|
||||
self.drv.unplug_vifs, mock_inst, 'net_info')
|
||||
|
||||
@mock.patch('pypowervm.tasks.vterm.open_remotable_vnc_vterm',
|
||||
autospec=True)
|
||||
@mock.patch('nova.virt.powervm.vm.get_pvm_uuid',
|
||||
new=mock.Mock(return_value='uuid'))
|
||||
def test_get_vnc_console(self, mock_vterm):
|
||||
# Success
|
||||
mock_vterm.return_value = '10'
|
||||
resp = self.drv.get_vnc_console(mock.ANY, self.inst)
|
||||
self.assertEqual('127.0.0.1', resp.host)
|
||||
self.assertEqual('10', resp.port)
|
||||
self.assertEqual('uuid', resp.internal_access_path)
|
||||
mock_vterm.assert_called_once_with(
|
||||
mock.ANY, 'uuid', mock.ANY, vnc_path='uuid')
|
||||
|
||||
# VNC failure - exception is raised directly
|
||||
mock_vterm.side_effect = pvm_exc.VNCBasedTerminalFailedToOpen(err='xx')
|
||||
self.assertRaises(pvm_exc.VNCBasedTerminalFailedToOpen,
|
||||
self.drv.get_vnc_console, mock.ANY, self.inst)
|
||||
|
||||
# 404
|
||||
mock_vterm.side_effect = pvm_exc.HttpError(mock.Mock(status=404))
|
||||
self.assertRaises(exception.InstanceNotFound, self.drv.get_vnc_console,
|
||||
mock.ANY, self.inst)
|
||||
|
||||
@mock.patch('nova.virt.powervm.volume.fcvscsi.FCVscsiVolumeAdapter')
|
||||
def test_attach_volume(self, mock_vscsi_adpt):
|
||||
"""Validates the basic PowerVM attach volume."""
|
||||
# BDMs
|
||||
mock_bdm = self._fake_bdms()['block_device_mapping'][0]
|
||||
|
||||
with mock.patch.object(self.inst, 'save') as mock_save:
|
||||
# Invoke the method.
|
||||
self.drv.attach_volume('context', mock_bdm.get('connection_info'),
|
||||
self.inst, mock.sentinel.stg_ftsk)
|
||||
|
||||
# Verify the connect volume was invoked
|
||||
mock_vscsi_adpt.return_value.attach_volume.assert_called_once_with()
|
||||
mock_save.assert_called_once_with()
|
||||
|
||||
@mock.patch('nova.virt.powervm.volume.fcvscsi.FCVscsiVolumeAdapter')
|
||||
def test_detach_volume(self, mock_vscsi_adpt):
|
||||
"""Validates the basic PowerVM detach volume."""
|
||||
# BDMs
|
||||
mock_bdm = self._fake_bdms()['block_device_mapping'][0]
|
||||
|
||||
# Invoke the method, good path test.
|
||||
self.drv.detach_volume('context', mock_bdm.get('connection_info'),
|
||||
self.inst, mock.sentinel.stg_ftsk)
|
||||
# Verify the disconnect volume was invoked
|
||||
mock_vscsi_adpt.return_value.detach_volume.assert_called_once_with()
|
||||
|
||||
@mock.patch('nova.virt.powervm.volume.fcvscsi.FCVscsiVolumeAdapter')
|
||||
def test_extend_volume(self, mock_vscsi_adpt):
|
||||
mock_bdm = self._fake_bdms()['block_device_mapping'][0]
|
||||
self.drv.extend_volume(
|
||||
'context', mock_bdm.get('connection_info'), self.inst, 0)
|
||||
mock_vscsi_adpt.return_value.extend_volume.assert_called_once_with()
|
||||
|
||||
def test_vol_drv_iter(self):
|
||||
block_device_info = self._fake_bdms()
|
||||
bdms = nova_driver.block_device_info_get_mapping(block_device_info)
|
||||
vol_adpt = mock.Mock()
|
||||
|
||||
def _get_results(bdms):
|
||||
# Patch so we get the same mock back each time.
|
||||
with mock.patch('nova.virt.powervm.volume.fcvscsi.'
|
||||
'FCVscsiVolumeAdapter', return_value=vol_adpt):
|
||||
return [
|
||||
(bdm, vol_drv) for bdm, vol_drv in self.drv._vol_drv_iter(
|
||||
'context', self.inst, bdms)]
|
||||
|
||||
results = _get_results(bdms)
|
||||
self.assertEqual(
|
||||
'fake_vol1',
|
||||
results[0][0]['connection_info']['data']['volume_id'])
|
||||
self.assertEqual(vol_adpt, results[0][1])
|
||||
self.assertEqual(
|
||||
'fake_vol2',
|
||||
results[1][0]['connection_info']['data']['volume_id'])
|
||||
self.assertEqual(vol_adpt, results[1][1])
|
||||
|
||||
# Test with empty bdms
|
||||
self.assertEqual([], _get_results([]))
|
||||
|
||||
@staticmethod
|
||||
def _fake_bdms():
|
||||
def _fake_bdm(volume_id, target_lun):
|
||||
connection_info = {'driver_volume_type': 'fibre_channel',
|
||||
'data': {'volume_id': volume_id,
|
||||
'target_lun': target_lun,
|
||||
'initiator_target_map':
|
||||
{'21000024F5': ['50050768']}}}
|
||||
mapping_dict = {'source_type': 'volume', 'volume_id': volume_id,
|
||||
'destination_type': 'volume',
|
||||
'connection_info':
|
||||
jsonutils.dumps(connection_info),
|
||||
}
|
||||
bdm_dict = nova_block_device.BlockDeviceDict(mapping_dict)
|
||||
bdm_obj = bdmobj.BlockDeviceMapping(**bdm_dict)
|
||||
|
||||
return nova_virt_bdm.DriverVolumeBlockDevice(bdm_obj)
|
||||
|
||||
bdm_list = [_fake_bdm('fake_vol1', 0), _fake_bdm('fake_vol2', 1)]
|
||||
block_device_info = {'block_device_mapping': bdm_list}
|
||||
|
||||
return block_device_info
|
||||
|
||||
@mock.patch('nova.virt.powervm.volume.fcvscsi.wwpns', autospec=True)
|
||||
def test_get_volume_connector(self, mock_wwpns):
|
||||
vol_connector = self.drv.get_volume_connector(mock.Mock())
|
||||
self.assertEqual(mock_wwpns.return_value, vol_connector['wwpns'])
|
||||
self.assertFalse(vol_connector['multipath'])
|
||||
self.assertEqual(vol_connector['host'], CONF.host)
|
||||
self.assertIsNone(vol_connector['initiator'])
|
||||
@@ -1,62 +0,0 @@
|
||||
# Copyright 2016 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from pypowervm.wrappers import managed_system as pvm_ms
|
||||
from unittest import mock
|
||||
|
||||
from nova import test
|
||||
from nova.virt.powervm import host as pvm_host
|
||||
|
||||
|
||||
class TestPowerVMHost(test.NoDBTestCase):
|
||||
def test_host_resources(self):
|
||||
# Create objects to test with
|
||||
ms_wrapper = mock.create_autospec(pvm_ms.System, spec_set=True)
|
||||
asio = mock.create_autospec(pvm_ms.ASIOConfig, spec_set=True)
|
||||
ms_wrapper.configure_mock(
|
||||
proc_units_configurable=500,
|
||||
proc_units_avail=500,
|
||||
memory_configurable=5242880,
|
||||
memory_free=5242752,
|
||||
memory_region_size='big',
|
||||
asio_config=asio)
|
||||
self.flags(host='the_hostname')
|
||||
|
||||
# Run the actual test
|
||||
stats = pvm_host.build_host_resource_from_ms(ms_wrapper)
|
||||
self.assertIsNotNone(stats)
|
||||
|
||||
# Check for the presence of fields
|
||||
fields = (('vcpus', 500), ('vcpus_used', 0),
|
||||
('memory_mb', 5242880), ('memory_mb_used', 128),
|
||||
'hypervisor_type', 'hypervisor_version',
|
||||
('hypervisor_hostname', 'the_hostname'), 'cpu_info',
|
||||
'supported_instances', 'stats')
|
||||
for fld in fields:
|
||||
if isinstance(fld, tuple):
|
||||
value = stats.get(fld[0], None)
|
||||
self.assertEqual(value, fld[1])
|
||||
else:
|
||||
value = stats.get(fld, None)
|
||||
self.assertIsNotNone(value)
|
||||
# Check for individual stats
|
||||
hstats = (('proc_units', '500.00'), ('proc_units_used', '0.00'))
|
||||
for stat in hstats:
|
||||
if isinstance(stat, tuple):
|
||||
value = stats['stats'].get(stat[0], None)
|
||||
self.assertEqual(value, stat[1])
|
||||
else:
|
||||
value = stats['stats'].get(stat, None)
|
||||
self.assertIsNotNone(value)
|
||||
@@ -1,55 +0,0 @@
|
||||
# Copyright 2015, 2018 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from nova import test
|
||||
from nova.virt.powervm import image
|
||||
|
||||
|
||||
class TestImage(test.TestCase):
|
||||
|
||||
@mock.patch('nova.utils.temporary_chown', autospec=True)
|
||||
@mock.patch('nova.image.glance.API', autospec=True)
|
||||
def test_stream_blockdev_to_glance(self, mock_api, mock_chown):
|
||||
mock_open = mock.mock_open()
|
||||
with mock.patch('builtins.open', new=mock_open):
|
||||
image.stream_blockdev_to_glance('context', mock_api, 'image_id',
|
||||
'metadata', '/dev/disk')
|
||||
mock_chown.assert_called_with('/dev/disk')
|
||||
mock_open.assert_called_with('/dev/disk', 'rb')
|
||||
mock_api.update.assert_called_with('context', 'image_id', 'metadata',
|
||||
mock_open.return_value)
|
||||
|
||||
@mock.patch('nova.image.glance.API', autospec=True)
|
||||
def test_generate_snapshot_metadata(self, mock_api):
|
||||
mock_api.get.return_value = {'name': 'image_name'}
|
||||
mock_instance = mock.Mock()
|
||||
mock_instance.project_id = 'project_id'
|
||||
ret = image.generate_snapshot_metadata('context', mock_api, 'image_id',
|
||||
mock_instance)
|
||||
mock_api.get.assert_called_with('context', 'image_id')
|
||||
self.assertEqual({
|
||||
'name': 'image_name',
|
||||
'status': 'active',
|
||||
'disk_format': 'raw',
|
||||
'container_format': 'bare',
|
||||
'properties': {
|
||||
'image_location': 'snapshot',
|
||||
'image_state': 'available',
|
||||
'owner_id': 'project_id',
|
||||
}
|
||||
}, ret)
|
||||
@@ -1,204 +0,0 @@
|
||||
# Copyright 2015, 2017 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import fixtures
|
||||
from oslo_utils.fixture import uuidsentinel
|
||||
from pypowervm import const as pvm_const
|
||||
from pypowervm.tasks import scsi_mapper as tsk_map
|
||||
from pypowervm.tests import test_fixtures as pvm_fx
|
||||
from pypowervm.utils import transaction as pvm_tx
|
||||
from pypowervm.wrappers import network as pvm_net
|
||||
from pypowervm.wrappers import storage as pvm_stg
|
||||
from pypowervm.wrappers import virtual_io_server as pvm_vios
|
||||
|
||||
from nova import test
|
||||
from nova.virt.powervm import media as m
|
||||
|
||||
|
||||
class TestConfigDrivePowerVM(test.NoDBTestCase):
|
||||
"""Unit Tests for the ConfigDrivePowerVM class."""
|
||||
|
||||
def setUp(self):
|
||||
super(TestConfigDrivePowerVM, self).setUp()
|
||||
|
||||
self.apt = self.useFixture(pvm_fx.AdapterFx()).adpt
|
||||
|
||||
self.validate_vopt = self.useFixture(fixtures.MockPatch(
|
||||
'pypowervm.tasks.vopt.validate_vopt_repo_exists',
|
||||
autospec=True)).mock
|
||||
self.validate_vopt.return_value = 'vios_uuid', 'vg_uuid'
|
||||
|
||||
@mock.patch('nova.api.metadata.base.InstanceMetadata')
|
||||
@mock.patch('nova.virt.configdrive.ConfigDriveBuilder.make_drive')
|
||||
def test_crt_cfg_dr_iso(self, mock_mkdrv, mock_meta):
|
||||
"""Validates that the image creation method works."""
|
||||
cfg_dr_builder = m.ConfigDrivePowerVM(self.apt)
|
||||
self.assertTrue(self.validate_vopt.called)
|
||||
mock_instance = mock.MagicMock()
|
||||
mock_instance.uuid = uuidsentinel.inst_id
|
||||
mock_files = mock.MagicMock()
|
||||
mock_net = mock.MagicMock()
|
||||
iso_path = '/tmp/cfgdrv.iso'
|
||||
cfg_dr_builder._create_cfg_dr_iso(mock_instance, mock_files, mock_net,
|
||||
iso_path)
|
||||
self.assertEqual(mock_mkdrv.call_count, 1)
|
||||
|
||||
# Test retry iso create
|
||||
mock_mkdrv.reset_mock()
|
||||
mock_mkdrv.side_effect = [OSError, mock_mkdrv]
|
||||
cfg_dr_builder._create_cfg_dr_iso(mock_instance, mock_files, mock_net,
|
||||
iso_path)
|
||||
self.assertEqual(mock_mkdrv.call_count, 2)
|
||||
|
||||
@mock.patch('tempfile.NamedTemporaryFile')
|
||||
@mock.patch('nova.virt.powervm.vm.get_pvm_uuid')
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.build_vscsi_mapping')
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.add_map')
|
||||
@mock.patch('os.path.getsize')
|
||||
@mock.patch('pypowervm.tasks.storage.upload_vopt')
|
||||
@mock.patch('nova.virt.powervm.media.ConfigDrivePowerVM.'
|
||||
'_create_cfg_dr_iso')
|
||||
def test_create_cfg_drv_vopt(self, mock_ccdi, mock_upl, mock_getsize,
|
||||
mock_addmap, mock_bldmap, mock_vm_id,
|
||||
mock_ntf):
|
||||
cfg_dr = m.ConfigDrivePowerVM(self.apt)
|
||||
mock_instance = mock.MagicMock()
|
||||
mock_instance.uuid = uuidsentinel.inst_id
|
||||
mock_upl.return_value = 'vopt', 'f_uuid'
|
||||
fh = mock_ntf.return_value.__enter__.return_value
|
||||
fh.name = 'iso_path'
|
||||
wtsk = mock.create_autospec(pvm_tx.WrapperTask, instance=True)
|
||||
ftsk = mock.create_autospec(pvm_tx.FeedTask, instance=True)
|
||||
ftsk.configure_mock(wrapper_tasks={'vios_uuid': wtsk})
|
||||
|
||||
def test_afs(add_func):
|
||||
# Validate the internal add_func
|
||||
vio = mock.create_autospec(pvm_vios.VIOS)
|
||||
self.assertEqual(mock_addmap.return_value, add_func(vio))
|
||||
mock_vm_id.assert_called_once_with(mock_instance)
|
||||
mock_bldmap.assert_called_once_with(
|
||||
None, vio, mock_vm_id.return_value, 'vopt')
|
||||
mock_addmap.assert_called_once_with(vio, mock_bldmap.return_value)
|
||||
wtsk.add_functor_subtask.side_effect = test_afs
|
||||
|
||||
# calculate expected file name
|
||||
expected_file_name = 'cfg_' + mock_instance.uuid.replace('-', '')
|
||||
allowed_len = pvm_const.MaxLen.VOPT_NAME - 4 # '.iso' is 4 chars
|
||||
expected_file_name = expected_file_name[:allowed_len] + '.iso'
|
||||
|
||||
cfg_dr.create_cfg_drv_vopt(
|
||||
mock_instance, 'files', 'netinfo', ftsk, admin_pass='pass')
|
||||
|
||||
mock_ntf.assert_called_once_with(mode='rb')
|
||||
mock_ccdi.assert_called_once_with(mock_instance, 'files', 'netinfo',
|
||||
'iso_path', admin_pass='pass')
|
||||
mock_getsize.assert_called_once_with('iso_path')
|
||||
mock_upl.assert_called_once_with(self.apt, 'vios_uuid', fh,
|
||||
expected_file_name,
|
||||
mock_getsize.return_value)
|
||||
wtsk.add_functor_subtask.assert_called_once()
|
||||
|
||||
def test_sanitize_network_info(self):
|
||||
network_info = [{'type': 'lbr'}, {'type': 'pvm_sea'},
|
||||
{'type': 'ovs'}]
|
||||
|
||||
cfg_dr_builder = m.ConfigDrivePowerVM(self.apt)
|
||||
|
||||
resp = cfg_dr_builder._sanitize_network_info(network_info)
|
||||
expected_ret = [{'type': 'vif'}, {'type': 'vif'},
|
||||
{'type': 'ovs'}]
|
||||
self.assertEqual(resp, expected_ret)
|
||||
|
||||
@mock.patch('pypowervm.wrappers.storage.VG', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.storage.rm_vg_storage', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.vm.get_pvm_uuid')
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.gen_match_func', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.find_maps', autospec=True)
|
||||
@mock.patch('pypowervm.wrappers.virtual_io_server.VIOS', autospec=True)
|
||||
@mock.patch('taskflow.task.FunctorTask', autospec=True)
|
||||
def test_dlt_vopt(self, mock_functask, mock_vios, mock_find_maps, mock_gmf,
|
||||
mock_uuid, mock_rmstg, mock_vg):
|
||||
cfg_dr = m.ConfigDrivePowerVM(self.apt)
|
||||
wtsk = mock.create_autospec(pvm_tx.WrapperTask, instance=True)
|
||||
ftsk = mock.create_autospec(pvm_tx.FeedTask, instance=True)
|
||||
ftsk.configure_mock(wrapper_tasks={'vios_uuid': wtsk})
|
||||
|
||||
# Test with no media to remove
|
||||
mock_find_maps.return_value = []
|
||||
cfg_dr.dlt_vopt('inst', ftsk)
|
||||
mock_uuid.assert_called_once_with('inst')
|
||||
mock_gmf.assert_called_once_with(pvm_stg.VOptMedia)
|
||||
wtsk.add_functor_subtask.assert_called_once_with(
|
||||
tsk_map.remove_maps, mock_uuid.return_value,
|
||||
match_func=mock_gmf.return_value)
|
||||
ftsk.get_wrapper.assert_called_once_with('vios_uuid')
|
||||
mock_find_maps.assert_called_once_with(
|
||||
ftsk.get_wrapper.return_value.scsi_mappings,
|
||||
client_lpar_id=mock_uuid.return_value,
|
||||
match_func=mock_gmf.return_value)
|
||||
mock_functask.assert_not_called()
|
||||
|
||||
# Test with media to remove
|
||||
mock_find_maps.return_value = [mock.Mock(backing_storage=media)
|
||||
for media in ['m1', 'm2']]
|
||||
|
||||
def test_functor_task(rm_vopt):
|
||||
# Validate internal rm_vopt function
|
||||
rm_vopt()
|
||||
mock_vg.get.assert_called_once_with(
|
||||
self.apt, uuid='vg_uuid', parent_type=pvm_vios.VIOS,
|
||||
parent_uuid='vios_uuid')
|
||||
mock_rmstg.assert_called_once_with(
|
||||
mock_vg.get.return_value, vopts=['m1', 'm2'])
|
||||
return 'functor_task'
|
||||
mock_functask.side_effect = test_functor_task
|
||||
|
||||
cfg_dr.dlt_vopt('inst', ftsk)
|
||||
mock_functask.assert_called_once()
|
||||
ftsk.add_post_execute.assert_called_once_with('functor_task')
|
||||
|
||||
def test_mgmt_cna_to_vif(self):
|
||||
mock_cna = mock.Mock(spec=pvm_net.CNA, mac="FAD4433ED120")
|
||||
|
||||
# Run
|
||||
cfg_dr_builder = m.ConfigDrivePowerVM(self.apt)
|
||||
vif = cfg_dr_builder._mgmt_cna_to_vif(mock_cna)
|
||||
|
||||
# Validate
|
||||
self.assertEqual(vif.get('address'), "fa:d4:43:3e:d1:20")
|
||||
self.assertEqual(vif.get('id'), 'mgmt_vif')
|
||||
self.assertIsNotNone(vif.get('network'))
|
||||
self.assertEqual(1, len(vif.get('network').get('subnets')))
|
||||
subnet = vif.get('network').get('subnets')[0]
|
||||
self.assertEqual(6, subnet.get('version'))
|
||||
self.assertEqual('fe80::/64', subnet.get('cidr'))
|
||||
ip = subnet.get('ips')[0]
|
||||
self.assertEqual('fe80::f8d4:43ff:fe3e:d120', ip.get('address'))
|
||||
|
||||
def test_mac_to_link_local(self):
|
||||
mac = 'fa:d4:43:3e:d1:20'
|
||||
self.assertEqual('fe80::f8d4:43ff:fe3e:d120',
|
||||
m.ConfigDrivePowerVM._mac_to_link_local(mac))
|
||||
|
||||
mac = '00:00:00:00:00:00'
|
||||
self.assertEqual('fe80::0200:00ff:fe00:0000',
|
||||
m.ConfigDrivePowerVM._mac_to_link_local(mac))
|
||||
|
||||
mac = 'ff:ff:ff:ff:ff:ff'
|
||||
self.assertEqual('fe80::fdff:ffff:feff:ffff',
|
||||
m.ConfigDrivePowerVM._mac_to_link_local(mac))
|
||||
@@ -1,193 +0,0 @@
|
||||
# Copyright 2015, 2018 IBM Corp.
|
||||
#
|
||||
# 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 retrying
|
||||
from unittest import mock
|
||||
|
||||
from nova import exception
|
||||
from nova import test
|
||||
from pypowervm.tests import test_fixtures as pvm_fx
|
||||
from pypowervm.tests.test_utils import pvmhttp
|
||||
|
||||
from nova.virt.powervm import mgmt
|
||||
|
||||
LPAR_HTTPRESP_FILE = "lpar.txt"
|
||||
|
||||
|
||||
class TestMgmt(test.TestCase):
|
||||
def setUp(self):
|
||||
super(TestMgmt, self).setUp()
|
||||
self.apt = self.useFixture(pvm_fx.AdapterFx()).adpt
|
||||
|
||||
lpar_http = pvmhttp.load_pvm_resp(LPAR_HTTPRESP_FILE, adapter=self.apt)
|
||||
self.assertIsNotNone(
|
||||
lpar_http, "Could not load %s " % LPAR_HTTPRESP_FILE)
|
||||
|
||||
self.resp = lpar_http.response
|
||||
|
||||
@mock.patch('pypowervm.tasks.partition.get_this_partition', autospec=True)
|
||||
def test_mgmt_uuid(self, mock_get_partition):
|
||||
mock_get_partition.return_value = mock.Mock(uuid='mock_mgmt')
|
||||
adpt = mock.Mock()
|
||||
|
||||
# First run should call the partition only once
|
||||
self.assertEqual('mock_mgmt', mgmt.mgmt_uuid(adpt))
|
||||
mock_get_partition.assert_called_once_with(adpt)
|
||||
|
||||
# But a subsequent call should effectively no-op
|
||||
mock_get_partition.reset_mock()
|
||||
self.assertEqual('mock_mgmt', mgmt.mgmt_uuid(adpt))
|
||||
self.assertEqual(mock_get_partition.call_count, 0)
|
||||
|
||||
@mock.patch('glob.glob', autospec=True)
|
||||
@mock.patch('nova.privsep.path.writefile', autospec=True)
|
||||
@mock.patch('os.path.realpath', autospec=True)
|
||||
def test_discover_vscsi_disk(self, mock_realpath, mock_writefile,
|
||||
mock_glob):
|
||||
scanpath = '/sys/bus/vio/devices/30000005/host*/scsi_host/host*/scan'
|
||||
udid = ('275b5d5f88fa5611e48be9000098be9400'
|
||||
'13fb2aa55a2d7b8d150cb1b7b6bc04d6')
|
||||
devlink = ('/dev/disk/by-id/scsi-SIBM_3303_NVDISK' + udid)
|
||||
mapping = mock.Mock()
|
||||
mapping.client_adapter.lpar_slot_num = 5
|
||||
mapping.backing_storage.udid = udid
|
||||
# Realistically, first glob would return e.g. .../host0/.../host0/...
|
||||
# but it doesn't matter for test purposes.
|
||||
mock_glob.side_effect = [[scanpath], [devlink]]
|
||||
mgmt.discover_vscsi_disk(mapping)
|
||||
mock_glob.assert_has_calls(
|
||||
[mock.call(scanpath), mock.call('/dev/disk/by-id/*' + udid[-32:])])
|
||||
mock_writefile.assert_called_once_with(scanpath, 'a', '- - -')
|
||||
mock_realpath.assert_called_with(devlink)
|
||||
|
||||
@mock.patch('retrying.retry', autospec=True)
|
||||
@mock.patch('glob.glob', autospec=True)
|
||||
@mock.patch('nova.privsep.path.writefile', autospec=True)
|
||||
def test_discover_vscsi_disk_not_one_result(self, mock_writefile,
|
||||
mock_glob, mock_retry):
|
||||
"""Zero or more than one disk is found by discover_vscsi_disk."""
|
||||
def validate_retry(kwargs):
|
||||
self.assertIn('retry_on_result', kwargs)
|
||||
self.assertEqual(250, kwargs['wait_fixed'])
|
||||
self.assertEqual(300000, kwargs['stop_max_delay'])
|
||||
|
||||
def raiser(unused):
|
||||
raise retrying.RetryError(mock.Mock(attempt_number=123))
|
||||
|
||||
def retry_passthrough(**kwargs):
|
||||
validate_retry(kwargs)
|
||||
|
||||
def wrapped(_poll_for_dev):
|
||||
return _poll_for_dev
|
||||
return wrapped
|
||||
|
||||
def retry_timeout(**kwargs):
|
||||
validate_retry(kwargs)
|
||||
|
||||
def wrapped(_poll_for_dev):
|
||||
return raiser
|
||||
return wrapped
|
||||
|
||||
udid = ('275b5d5f88fa5611e48be9000098be9400'
|
||||
'13fb2aa55a2d7b8d150cb1b7b6bc04d6')
|
||||
mapping = mock.Mock()
|
||||
mapping.client_adapter.lpar_slot_num = 5
|
||||
mapping.backing_storage.udid = udid
|
||||
# No disks found
|
||||
mock_retry.side_effect = retry_timeout
|
||||
mock_glob.side_effect = lambda path: []
|
||||
self.assertRaises(exception.NoDiskDiscoveryException,
|
||||
mgmt.discover_vscsi_disk, mapping)
|
||||
# Multiple disks found
|
||||
mock_retry.side_effect = retry_passthrough
|
||||
mock_glob.side_effect = [['path'], ['/dev/sde', '/dev/sdf']]
|
||||
self.assertRaises(exception.UniqueDiskDiscoveryException,
|
||||
mgmt.discover_vscsi_disk, mapping)
|
||||
|
||||
@mock.patch('time.sleep', autospec=True)
|
||||
@mock.patch('os.path.realpath', autospec=True)
|
||||
@mock.patch('os.stat', autospec=True)
|
||||
@mock.patch('nova.privsep.path.writefile', autospec=True)
|
||||
def test_remove_block_dev(self, mock_writefile, mock_stat, mock_realpath,
|
||||
mock_sleep):
|
||||
link = '/dev/link/foo'
|
||||
realpath = '/dev/sde'
|
||||
delpath = '/sys/block/sde/device/delete'
|
||||
mock_realpath.return_value = realpath
|
||||
|
||||
# Good path
|
||||
mock_stat.side_effect = (None, None, OSError())
|
||||
mgmt.remove_block_dev(link)
|
||||
mock_realpath.assert_called_with(link)
|
||||
mock_stat.assert_has_calls([mock.call(realpath), mock.call(delpath),
|
||||
mock.call(realpath)])
|
||||
mock_writefile.assert_called_once_with(delpath, 'a', '1')
|
||||
self.assertEqual(0, mock_sleep.call_count)
|
||||
|
||||
# Device param not found
|
||||
mock_writefile.reset_mock()
|
||||
mock_stat.reset_mock()
|
||||
mock_stat.side_effect = (OSError(), None, None)
|
||||
self.assertRaises(exception.InvalidDevicePath, mgmt.remove_block_dev,
|
||||
link)
|
||||
# stat was called once; exec was not called
|
||||
self.assertEqual(1, mock_stat.call_count)
|
||||
self.assertEqual(0, mock_writefile.call_count)
|
||||
|
||||
# Delete special file not found
|
||||
mock_writefile.reset_mock()
|
||||
mock_stat.reset_mock()
|
||||
mock_stat.side_effect = (None, OSError(), None)
|
||||
self.assertRaises(exception.InvalidDevicePath, mgmt.remove_block_dev,
|
||||
link)
|
||||
# stat was called twice; exec was not called
|
||||
self.assertEqual(2, mock_stat.call_count)
|
||||
self.assertEqual(0, mock_writefile.call_count)
|
||||
|
||||
@mock.patch('retrying.retry')
|
||||
@mock.patch('os.path.realpath')
|
||||
@mock.patch('os.stat')
|
||||
@mock.patch('nova.privsep.path.writefile')
|
||||
def test_remove_block_dev_timeout(self, mock_dacw, mock_stat,
|
||||
mock_realpath, mock_retry):
|
||||
|
||||
def validate_retry(kwargs):
|
||||
self.assertIn('retry_on_result', kwargs)
|
||||
self.assertEqual(250, kwargs['wait_fixed'])
|
||||
self.assertEqual(10000, kwargs['stop_max_delay'])
|
||||
|
||||
def raiser(unused):
|
||||
raise retrying.RetryError(mock.Mock(attempt_number=123))
|
||||
|
||||
def retry_timeout(**kwargs):
|
||||
validate_retry(kwargs)
|
||||
|
||||
def wrapped(_poll_for_del):
|
||||
return raiser
|
||||
return wrapped
|
||||
|
||||
# Deletion was attempted, but device is still there
|
||||
link = '/dev/link/foo'
|
||||
delpath = '/sys/block/sde/device/delete'
|
||||
realpath = '/dev/sde'
|
||||
mock_realpath.return_value = realpath
|
||||
mock_stat.side_effect = lambda path: 1
|
||||
mock_retry.side_effect = retry_timeout
|
||||
|
||||
self.assertRaises(
|
||||
exception.DeviceDeletionException, mgmt.remove_block_dev, link)
|
||||
mock_realpath.assert_called_once_with(link)
|
||||
mock_dacw.assert_called_with(delpath, 'a', '1')
|
||||
@@ -1,327 +0,0 @@
|
||||
# Copyright 2017 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from pypowervm import exceptions as pvm_ex
|
||||
from pypowervm.wrappers import network as pvm_net
|
||||
from unittest import mock
|
||||
|
||||
from nova import exception
|
||||
from nova.network import model
|
||||
from nova import test
|
||||
from nova.virt.powervm import vif
|
||||
|
||||
|
||||
def cna(mac):
|
||||
"""Builds a mock Client Network Adapter for unit tests."""
|
||||
return mock.Mock(spec=pvm_net.CNA, mac=mac, vswitch_uri='fake_href')
|
||||
|
||||
|
||||
class TestVifFunctions(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestVifFunctions, self).setUp()
|
||||
|
||||
self.adpt = mock.Mock()
|
||||
|
||||
@mock.patch('nova.virt.powervm.vif.PvmOvsVifDriver')
|
||||
def test_build_vif_driver(self, mock_driver):
|
||||
# Valid vif type
|
||||
driver = vif._build_vif_driver(self.adpt, 'instance', {'type': 'ovs'})
|
||||
self.assertEqual(mock_driver.return_value, driver)
|
||||
|
||||
mock_driver.reset_mock()
|
||||
|
||||
# Fail if no vif type
|
||||
self.assertRaises(exception.VirtualInterfacePlugException,
|
||||
vif._build_vif_driver, self.adpt, 'instance',
|
||||
{'type': None})
|
||||
mock_driver.assert_not_called()
|
||||
|
||||
# Fail if invalid vif type
|
||||
self.assertRaises(exception.VirtualInterfacePlugException,
|
||||
vif._build_vif_driver, self.adpt, 'instance',
|
||||
{'type': 'bad_type'})
|
||||
mock_driver.assert_not_called()
|
||||
|
||||
@mock.patch('oslo_serialization.jsonutils.dumps')
|
||||
@mock.patch('pypowervm.wrappers.event.Event')
|
||||
def test_push_vif_event(self, mock_event, mock_dumps):
|
||||
mock_vif = mock.Mock(mac='MAC', href='HREF')
|
||||
vif._push_vif_event(self.adpt, 'action', mock_vif, mock.Mock(),
|
||||
'pvm_sea')
|
||||
mock_dumps.assert_called_once_with(
|
||||
{'provider': 'NOVA_PVM_VIF', 'action': 'action', 'mac': 'MAC',
|
||||
'type': 'pvm_sea'})
|
||||
mock_event.bld.assert_called_once_with(self.adpt, 'HREF',
|
||||
mock_dumps.return_value)
|
||||
mock_event.bld.return_value.create.assert_called_once_with()
|
||||
|
||||
mock_dumps.reset_mock()
|
||||
mock_event.bld.reset_mock()
|
||||
mock_event.bld.return_value.create.reset_mock()
|
||||
|
||||
# Exception reraises
|
||||
mock_event.bld.return_value.create.side_effect = IndexError
|
||||
self.assertRaises(IndexError, vif._push_vif_event, self.adpt, 'action',
|
||||
mock_vif, mock.Mock(), 'pvm_sea')
|
||||
mock_dumps.assert_called_once_with(
|
||||
{'provider': 'NOVA_PVM_VIF', 'action': 'action', 'mac': 'MAC',
|
||||
'type': 'pvm_sea'})
|
||||
mock_event.bld.assert_called_once_with(self.adpt, 'HREF',
|
||||
mock_dumps.return_value)
|
||||
mock_event.bld.return_value.create.assert_called_once_with()
|
||||
|
||||
@mock.patch('nova.virt.powervm.vif._push_vif_event')
|
||||
@mock.patch('nova.virt.powervm.vif._build_vif_driver')
|
||||
def test_plug(self, mock_bld_drv, mock_event):
|
||||
"""Test the top-level plug method."""
|
||||
mock_vif = {'address': 'MAC', 'type': 'pvm_sea'}
|
||||
|
||||
# 1) With new_vif=True (default)
|
||||
vnet = vif.plug(self.adpt, 'instance', mock_vif)
|
||||
|
||||
mock_bld_drv.assert_called_once_with(self.adpt, 'instance', mock_vif)
|
||||
mock_bld_drv.return_value.plug.assert_called_once_with(mock_vif,
|
||||
new_vif=True)
|
||||
self.assertEqual(mock_bld_drv.return_value.plug.return_value, vnet)
|
||||
mock_event.assert_called_once_with(self.adpt, 'plug', vnet, mock.ANY,
|
||||
'pvm_sea')
|
||||
|
||||
# Clean up
|
||||
mock_bld_drv.reset_mock()
|
||||
mock_bld_drv.return_value.plug.reset_mock()
|
||||
mock_event.reset_mock()
|
||||
|
||||
# 2) Plug returns None (which it should IRL whenever new_vif=False).
|
||||
mock_bld_drv.return_value.plug.return_value = None
|
||||
vnet = vif.plug(self.adpt, 'instance', mock_vif, new_vif=False)
|
||||
|
||||
mock_bld_drv.assert_called_once_with(self.adpt, 'instance', mock_vif)
|
||||
mock_bld_drv.return_value.plug.assert_called_once_with(mock_vif,
|
||||
new_vif=False)
|
||||
self.assertIsNone(vnet)
|
||||
mock_event.assert_not_called()
|
||||
|
||||
@mock.patch('nova.virt.powervm.vif._build_vif_driver')
|
||||
def test_plug_raises(self, mock_vif_drv):
|
||||
"""HttpError is converted to VirtualInterfacePlugException."""
|
||||
vif_drv = mock.Mock(plug=mock.Mock(side_effect=pvm_ex.HttpError(
|
||||
resp=mock.Mock())))
|
||||
mock_vif_drv.return_value = vif_drv
|
||||
mock_vif = {'address': 'vifaddr'}
|
||||
self.assertRaises(exception.VirtualInterfacePlugException,
|
||||
vif.plug, 'adap', 'inst', mock_vif,
|
||||
new_vif='new_vif')
|
||||
mock_vif_drv.assert_called_once_with('adap', 'inst', mock_vif)
|
||||
vif_drv.plug.assert_called_once_with(mock_vif, new_vif='new_vif')
|
||||
|
||||
@mock.patch('nova.virt.powervm.vif._push_vif_event')
|
||||
@mock.patch('nova.virt.powervm.vif._build_vif_driver')
|
||||
def test_unplug(self, mock_bld_drv, mock_event):
|
||||
"""Test the top-level unplug method."""
|
||||
mock_vif = {'address': 'MAC', 'type': 'pvm_sea'}
|
||||
|
||||
# 1) With default cna_w_list
|
||||
mock_bld_drv.return_value.unplug.return_value = 'vnet_w'
|
||||
vif.unplug(self.adpt, 'instance', mock_vif)
|
||||
mock_bld_drv.assert_called_once_with(self.adpt, 'instance', mock_vif)
|
||||
mock_bld_drv.return_value.unplug.assert_called_once_with(
|
||||
mock_vif, cna_w_list=None)
|
||||
mock_event.assert_called_once_with(self.adpt, 'unplug', 'vnet_w',
|
||||
mock.ANY, 'pvm_sea')
|
||||
# Clean up
|
||||
mock_bld_drv.reset_mock()
|
||||
mock_bld_drv.return_value.unplug.reset_mock()
|
||||
mock_event.reset_mock()
|
||||
|
||||
# 2) With specified cna_w_list
|
||||
mock_bld_drv.return_value.unplug.return_value = None
|
||||
vif.unplug(self.adpt, 'instance', mock_vif, cna_w_list='cnalist')
|
||||
mock_bld_drv.assert_called_once_with(self.adpt, 'instance', mock_vif)
|
||||
mock_bld_drv.return_value.unplug.assert_called_once_with(
|
||||
mock_vif, cna_w_list='cnalist')
|
||||
mock_event.assert_not_called()
|
||||
|
||||
@mock.patch('nova.virt.powervm.vif._build_vif_driver')
|
||||
def test_unplug_raises(self, mock_vif_drv):
|
||||
"""HttpError is converted to VirtualInterfacePlugException."""
|
||||
vif_drv = mock.Mock(unplug=mock.Mock(side_effect=pvm_ex.HttpError(
|
||||
resp=mock.Mock())))
|
||||
mock_vif_drv.return_value = vif_drv
|
||||
mock_vif = {'address': 'vifaddr'}
|
||||
self.assertRaises(exception.VirtualInterfaceUnplugException,
|
||||
vif.unplug, 'adap', 'inst', mock_vif,
|
||||
cna_w_list='cna_w_list')
|
||||
mock_vif_drv.assert_called_once_with('adap', 'inst', mock_vif)
|
||||
vif_drv.unplug.assert_called_once_with(
|
||||
mock_vif, cna_w_list='cna_w_list')
|
||||
|
||||
|
||||
class TestVifOvsDriver(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestVifOvsDriver, self).setUp()
|
||||
|
||||
self.adpt = mock.Mock()
|
||||
self.inst = mock.MagicMock(uuid='inst_uuid')
|
||||
self.drv = vif.PvmOvsVifDriver(self.adpt, self.inst)
|
||||
|
||||
@mock.patch('pypowervm.tasks.cna.crt_p2p_cna', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.partition.get_this_partition', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.vm.get_pvm_uuid')
|
||||
def test_plug(self, mock_pvm_uuid, mock_mgmt_lpar, mock_p2p_cna,):
|
||||
# Mock the data
|
||||
mock_pvm_uuid.return_value = 'lpar_uuid'
|
||||
mock_mgmt_lpar.return_value = mock.Mock(uuid='mgmt_uuid')
|
||||
# mock_trunk_dev_name.return_value = 'device'
|
||||
|
||||
cna_w, trunk_wraps = mock.MagicMock(), [mock.MagicMock()]
|
||||
mock_p2p_cna.return_value = cna_w, trunk_wraps
|
||||
|
||||
# Run the plug
|
||||
network_model = model.Model({'bridge': 'br0', 'meta': {'mtu': 1450}})
|
||||
mock_vif = model.VIF(address='aa:bb:cc:dd:ee:ff', id='vif_id',
|
||||
network=network_model, devname='device')
|
||||
self.drv.plug(mock_vif)
|
||||
|
||||
# Validate the calls
|
||||
ovs_ext_ids = ('iface-id=vif_id,iface-status=active,'
|
||||
'attached-mac=aa:bb:cc:dd:ee:ff,vm-uuid=inst_uuid')
|
||||
mock_p2p_cna.assert_called_once_with(
|
||||
self.adpt, None, 'lpar_uuid', ['mgmt_uuid'],
|
||||
'NovaLinkVEABridge', configured_mtu=1450, crt_vswitch=True,
|
||||
mac_addr='aa:bb:cc:dd:ee:ff', dev_name='device', ovs_bridge='br0',
|
||||
ovs_ext_ids=ovs_ext_ids)
|
||||
|
||||
@mock.patch('pypowervm.tasks.partition.get_this_partition', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.vm.get_pvm_uuid')
|
||||
@mock.patch('nova.virt.powervm.vm.get_cnas')
|
||||
@mock.patch('pypowervm.tasks.cna.find_trunks', autospec=True)
|
||||
def test_plug_existing_vif(self, mock_find_trunks, mock_get_cnas,
|
||||
mock_pvm_uuid, mock_mgmt_lpar):
|
||||
# Mock the data
|
||||
t1, t2 = mock.MagicMock(), mock.MagicMock()
|
||||
mock_find_trunks.return_value = [t1, t2]
|
||||
|
||||
mock_cna = mock.Mock(mac='aa:bb:cc:dd:ee:ff')
|
||||
mock_get_cnas.return_value = [mock_cna]
|
||||
|
||||
mock_pvm_uuid.return_value = 'lpar_uuid'
|
||||
|
||||
mock_mgmt_lpar.return_value = mock.Mock(uuid='mgmt_uuid')
|
||||
|
||||
self.inst = mock.MagicMock(uuid='c2e7ff9f-b9b6-46fa-8716-93bbb795b8b4')
|
||||
self.drv = vif.PvmOvsVifDriver(self.adpt, self.inst)
|
||||
|
||||
# Run the plug
|
||||
network_model = model.Model({'bridge': 'br0', 'meta': {'mtu': 1500}})
|
||||
mock_vif = model.VIF(address='aa:bb:cc:dd:ee:ff', id='vif_id',
|
||||
network=network_model, devname='devname')
|
||||
resp = self.drv.plug(mock_vif, new_vif=False)
|
||||
|
||||
self.assertIsNone(resp)
|
||||
|
||||
# Validate if trunk.update got invoked for all trunks of CNA of vif
|
||||
self.assertTrue(t1.update.called)
|
||||
self.assertTrue(t2.update.called)
|
||||
|
||||
@mock.patch('pypowervm.tasks.cna.find_trunks')
|
||||
@mock.patch('nova.virt.powervm.vm.get_cnas')
|
||||
def test_unplug(self, mock_get_cnas, mock_find_trunks):
|
||||
# Set up the mocks
|
||||
mock_cna = mock.Mock(mac='aa:bb:cc:dd:ee:ff')
|
||||
mock_get_cnas.return_value = [mock_cna]
|
||||
|
||||
t1, t2 = mock.MagicMock(), mock.MagicMock()
|
||||
mock_find_trunks.return_value = [t1, t2]
|
||||
|
||||
# Call the unplug
|
||||
mock_vif = {'address': 'aa:bb:cc:dd:ee:ff',
|
||||
'network': {'bridge': 'br-int'}}
|
||||
self.drv.unplug(mock_vif)
|
||||
|
||||
# The trunks and the cna should have been deleted
|
||||
self.assertTrue(t1.delete.called)
|
||||
self.assertTrue(t2.delete.called)
|
||||
self.assertTrue(mock_cna.delete.called)
|
||||
|
||||
|
||||
class TestVifSeaDriver(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestVifSeaDriver, self).setUp()
|
||||
|
||||
self.adpt = mock.Mock()
|
||||
self.inst = mock.Mock()
|
||||
self.drv = vif.PvmSeaVifDriver(self.adpt, self.inst)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.get_pvm_uuid')
|
||||
@mock.patch('pypowervm.tasks.cna.crt_cna')
|
||||
def test_plug_from_neutron(self, mock_crt_cna, mock_pvm_uuid):
|
||||
"""Tests that a VIF can be created. Mocks Neutron net"""
|
||||
|
||||
# Set up the mocks. Look like Neutron
|
||||
fake_vif = {'details': {'vlan': 5}, 'network': {'meta': {}},
|
||||
'address': 'aabbccddeeff'}
|
||||
|
||||
def validate_crt(adpt, host_uuid, lpar_uuid, vlan, mac_addr=None):
|
||||
self.assertIsNone(host_uuid)
|
||||
self.assertEqual(5, vlan)
|
||||
self.assertEqual('aabbccddeeff', mac_addr)
|
||||
return pvm_net.CNA.bld(self.adpt, 5, 'host_uuid',
|
||||
mac_addr=mac_addr)
|
||||
mock_crt_cna.side_effect = validate_crt
|
||||
|
||||
# Invoke
|
||||
resp = self.drv.plug(fake_vif)
|
||||
|
||||
# Validate (along with validate method above)
|
||||
self.assertEqual(1, mock_crt_cna.call_count)
|
||||
self.assertIsNotNone(resp)
|
||||
self.assertIsInstance(resp, pvm_net.CNA)
|
||||
|
||||
def test_plug_existing_vif(self):
|
||||
"""Tests that a VIF need not be created."""
|
||||
|
||||
# Set up the mocks
|
||||
fake_vif = {'network': {'meta': {'vlan': 5}},
|
||||
'address': 'aabbccddeeff'}
|
||||
|
||||
# Invoke
|
||||
resp = self.drv.plug(fake_vif, new_vif=False)
|
||||
|
||||
self.assertIsNone(resp)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.get_cnas')
|
||||
def test_unplug_vifs(self, mock_vm_get):
|
||||
"""Tests that a delete of the vif can be done."""
|
||||
# Mock up the CNA response. Two should already exist, the other
|
||||
# should not.
|
||||
cnas = [cna('AABBCCDDEEFF'), cna('AABBCCDDEE11'), cna('AABBCCDDEE22')]
|
||||
mock_vm_get.return_value = cnas
|
||||
|
||||
# Run method. The AABBCCDDEE11 won't be unplugged (wasn't invoked
|
||||
# below) and the last unplug will also just no-op because its not on
|
||||
# the VM.
|
||||
self.drv.unplug({'address': 'aa:bb:cc:dd:ee:ff'})
|
||||
self.drv.unplug({'address': 'aa:bb:cc:dd:ee:22'})
|
||||
self.drv.unplug({'address': 'aa:bb:cc:dd:ee:33'})
|
||||
|
||||
# The delete should have only been called once for each applicable vif.
|
||||
# The second CNA didn't have a matching mac so it should be skipped.
|
||||
self.assertEqual(1, cnas[0].delete.call_count)
|
||||
self.assertEqual(0, cnas[1].delete.call_count)
|
||||
self.assertEqual(1, cnas[2].delete.call_count)
|
||||
@@ -1,564 +0,0 @@
|
||||
# Copyright 2014, 2017 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import fixtures
|
||||
from pypowervm import exceptions as pvm_exc
|
||||
from pypowervm.helpers import log_helper as pvm_log
|
||||
from pypowervm.tests import test_fixtures as pvm_fx
|
||||
from pypowervm.utils import lpar_builder as lpar_bld
|
||||
from pypowervm.utils import uuid as pvm_uuid
|
||||
from pypowervm.wrappers import base_partition as pvm_bp
|
||||
from pypowervm.wrappers import logical_partition as pvm_lpar
|
||||
|
||||
from nova.compute import power_state
|
||||
from nova import exception
|
||||
from nova import test
|
||||
from nova.tests.unit.virt import powervm
|
||||
from nova.virt.powervm import vm
|
||||
|
||||
|
||||
class TestVMBuilder(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestVMBuilder, self).setUp()
|
||||
|
||||
self.adpt = mock.MagicMock()
|
||||
self.host_w = mock.MagicMock()
|
||||
self.lpar_b = vm.VMBuilder(self.host_w, self.adpt)
|
||||
|
||||
self.san_lpar_name = self.useFixture(fixtures.MockPatch(
|
||||
'pypowervm.util.sanitize_partition_name_for_api',
|
||||
autospec=True)).mock
|
||||
|
||||
self.inst = powervm.TEST_INSTANCE
|
||||
|
||||
@mock.patch('pypowervm.utils.lpar_builder.DefaultStandardize',
|
||||
autospec=True)
|
||||
@mock.patch('nova.virt.powervm.vm.get_pvm_uuid')
|
||||
@mock.patch('pypowervm.utils.lpar_builder.LPARBuilder', autospec=True)
|
||||
def test_vm_builder(self, mock_lpar_bldr, mock_uuid2pvm, mock_def_stdz):
|
||||
inst = mock.Mock()
|
||||
inst.configure_mock(
|
||||
name='lpar_name', uuid='lpar_uuid',
|
||||
flavor=mock.Mock(memory_mb='mem', vcpus='vcpus', extra_specs={}))
|
||||
vmb = vm.VMBuilder('host', 'adap')
|
||||
mock_def_stdz.assert_called_once_with('host', proc_units_factor=0.1)
|
||||
self.assertEqual(mock_lpar_bldr.return_value,
|
||||
vmb.lpar_builder(inst))
|
||||
self.san_lpar_name.assert_called_once_with('lpar_name')
|
||||
mock_uuid2pvm.assert_called_once_with(inst)
|
||||
mock_lpar_bldr.assert_called_once_with(
|
||||
'adap', {'name': self.san_lpar_name.return_value,
|
||||
'uuid': mock_uuid2pvm.return_value,
|
||||
'memory': 'mem',
|
||||
'vcpu': 'vcpus',
|
||||
'srr_capability': True}, mock_def_stdz.return_value)
|
||||
|
||||
# Assert non-default proc_units_factor.
|
||||
mock_def_stdz.reset_mock()
|
||||
self.flags(proc_units_factor=0.2, group='powervm')
|
||||
vmb = vm.VMBuilder('host', 'adap')
|
||||
mock_def_stdz.assert_called_once_with('host', proc_units_factor=0.2)
|
||||
|
||||
def test_format_flavor(self):
|
||||
"""Perform tests against _format_flavor."""
|
||||
# convert instance uuid to pypowervm uuid
|
||||
# LP 1561128, simplified remote restart is enabled by default
|
||||
lpar_attrs = {'memory': 2048,
|
||||
'name': self.san_lpar_name.return_value,
|
||||
'uuid': pvm_uuid.convert_uuid_to_pvm(
|
||||
self.inst.uuid).upper(),
|
||||
'vcpu': 1, 'srr_capability': True}
|
||||
|
||||
# Test dedicated procs
|
||||
self.inst.flavor.extra_specs = {'powervm:dedicated_proc': 'true'}
|
||||
test_attrs = dict(lpar_attrs, dedicated_proc='true')
|
||||
|
||||
self.assertEqual(self.lpar_b._format_flavor(self.inst), test_attrs)
|
||||
self.san_lpar_name.assert_called_with(self.inst.name)
|
||||
self.san_lpar_name.reset_mock()
|
||||
|
||||
# Test dedicated procs, min/max vcpu and sharing mode
|
||||
self.inst.flavor.extra_specs = {'powervm:dedicated_proc': 'true',
|
||||
'powervm:dedicated_sharing_mode':
|
||||
'share_idle_procs_active',
|
||||
'powervm:min_vcpu': '1',
|
||||
'powervm:max_vcpu': '3'}
|
||||
test_attrs = dict(lpar_attrs,
|
||||
dedicated_proc='true',
|
||||
sharing_mode='sre idle procs active',
|
||||
min_vcpu='1', max_vcpu='3')
|
||||
self.assertEqual(self.lpar_b._format_flavor(self.inst), test_attrs)
|
||||
self.san_lpar_name.assert_called_with(self.inst.name)
|
||||
self.san_lpar_name.reset_mock()
|
||||
|
||||
# Test shared proc sharing mode
|
||||
self.inst.flavor.extra_specs = {'powervm:uncapped': 'true'}
|
||||
test_attrs = dict(lpar_attrs, sharing_mode='uncapped')
|
||||
self.assertEqual(self.lpar_b._format_flavor(self.inst), test_attrs)
|
||||
self.san_lpar_name.assert_called_with(self.inst.name)
|
||||
self.san_lpar_name.reset_mock()
|
||||
|
||||
# Test availability priority
|
||||
self.inst.flavor.extra_specs = {'powervm:availability_priority': '150'}
|
||||
test_attrs = dict(lpar_attrs, avail_priority='150')
|
||||
self.assertEqual(self.lpar_b._format_flavor(self.inst), test_attrs)
|
||||
self.san_lpar_name.assert_called_with(self.inst.name)
|
||||
self.san_lpar_name.reset_mock()
|
||||
|
||||
# Test processor compatibility
|
||||
self.inst.flavor.extra_specs = {
|
||||
'powervm:processor_compatibility': 'POWER8'}
|
||||
test_attrs = dict(lpar_attrs, processor_compatibility='POWER8')
|
||||
self.assertEqual(self.lpar_b._format_flavor(self.inst), test_attrs)
|
||||
self.san_lpar_name.assert_called_with(self.inst.name)
|
||||
self.san_lpar_name.reset_mock()
|
||||
|
||||
# Test min, max proc units
|
||||
self.inst.flavor.extra_specs = {'powervm:min_proc_units': '0.5',
|
||||
'powervm:max_proc_units': '2.0'}
|
||||
test_attrs = dict(lpar_attrs, min_proc_units='0.5',
|
||||
max_proc_units='2.0')
|
||||
self.assertEqual(self.lpar_b._format_flavor(self.inst), test_attrs)
|
||||
self.san_lpar_name.assert_called_with(self.inst.name)
|
||||
self.san_lpar_name.reset_mock()
|
||||
|
||||
# Test min, max mem
|
||||
self.inst.flavor.extra_specs = {'powervm:min_mem': '1024',
|
||||
'powervm:max_mem': '4096'}
|
||||
test_attrs = dict(lpar_attrs, min_mem='1024', max_mem='4096')
|
||||
self.assertEqual(self.lpar_b._format_flavor(self.inst), test_attrs)
|
||||
self.san_lpar_name.assert_called_with(self.inst.name)
|
||||
self.san_lpar_name.reset_mock()
|
||||
|
||||
# Test remote restart set to false
|
||||
self.inst.flavor.extra_specs = {'powervm:srr_capability': 'false'}
|
||||
test_attrs = dict(lpar_attrs, srr_capability=False)
|
||||
self.assertEqual(self.lpar_b._format_flavor(self.inst), test_attrs)
|
||||
|
||||
# Unhandled powervm: key is ignored
|
||||
self.inst.flavor.extra_specs = {'powervm:srr_capability': 'false',
|
||||
'powervm:something_new': 'foo'}
|
||||
test_attrs = dict(lpar_attrs, srr_capability=False)
|
||||
self.assertEqual(self.lpar_b._format_flavor(self.inst), test_attrs)
|
||||
|
||||
# If we recognize a key, but don't handle it, we raise
|
||||
with mock.patch.object(self.lpar_b, '_is_pvm_valid_key',
|
||||
return_value=True):
|
||||
self.inst.flavor.extra_specs = {'powervm:srr_capability': 'false',
|
||||
'powervm:something_new': 'foo'}
|
||||
self.assertRaises(KeyError, self.lpar_b._format_flavor, self.inst)
|
||||
|
||||
@mock.patch('pypowervm.wrappers.shared_proc_pool.SharedProcPool.search')
|
||||
def test_spp_pool_id(self, mock_search):
|
||||
# The default pool is always zero. Validate the path.
|
||||
self.assertEqual(0, self.lpar_b._spp_pool_id('DefaultPool'))
|
||||
self.assertEqual(0, self.lpar_b._spp_pool_id(None))
|
||||
|
||||
# Further invocations require calls to the adapter. Build a minimal
|
||||
# mocked SPP wrapper
|
||||
spp = mock.MagicMock()
|
||||
spp.id = 1
|
||||
|
||||
# Three invocations. First has too many elems. Second has none.
|
||||
# Third is just right. :-)
|
||||
mock_search.side_effect = [[spp, spp], [], [spp]]
|
||||
|
||||
self.assertRaises(exception.ValidationError, self.lpar_b._spp_pool_id,
|
||||
'fake_name')
|
||||
self.assertRaises(exception.ValidationError, self.lpar_b._spp_pool_id,
|
||||
'fake_name')
|
||||
|
||||
self.assertEqual(1, self.lpar_b._spp_pool_id('fake_name'))
|
||||
|
||||
|
||||
class TestVM(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
super(TestVM, self).setUp()
|
||||
|
||||
self.apt = self.useFixture(pvm_fx.AdapterFx(
|
||||
traits=pvm_fx.LocalPVMTraits)).adpt
|
||||
self.apt.helpers = [pvm_log.log_helper]
|
||||
|
||||
self.san_lpar_name = self.useFixture(fixtures.MockPatch(
|
||||
'pypowervm.util.sanitize_partition_name_for_api')).mock
|
||||
self.san_lpar_name.side_effect = lambda name: name
|
||||
mock_entries = [mock.Mock(), mock.Mock()]
|
||||
self.resp = mock.MagicMock()
|
||||
self.resp.feed = mock.MagicMock(entries=mock_entries)
|
||||
|
||||
self.get_pvm_uuid = self.useFixture(fixtures.MockPatch(
|
||||
'nova.virt.powervm.vm.get_pvm_uuid')).mock
|
||||
|
||||
self.inst = powervm.TEST_INSTANCE
|
||||
|
||||
def test_translate_vm_state(self):
|
||||
self.assertEqual(power_state.RUNNING,
|
||||
vm._translate_vm_state('running'))
|
||||
self.assertEqual(power_state.RUNNING,
|
||||
vm._translate_vm_state('migrating running'))
|
||||
self.assertEqual(power_state.RUNNING,
|
||||
vm._translate_vm_state('starting'))
|
||||
self.assertEqual(power_state.RUNNING,
|
||||
vm._translate_vm_state('open firmware'))
|
||||
self.assertEqual(power_state.RUNNING,
|
||||
vm._translate_vm_state('shutting down'))
|
||||
self.assertEqual(power_state.RUNNING,
|
||||
vm._translate_vm_state('suspending'))
|
||||
|
||||
self.assertEqual(power_state.SHUTDOWN,
|
||||
vm._translate_vm_state('migrating not active'))
|
||||
self.assertEqual(power_state.SHUTDOWN,
|
||||
vm._translate_vm_state('not activated'))
|
||||
|
||||
self.assertEqual(power_state.NOSTATE,
|
||||
vm._translate_vm_state('unknown'))
|
||||
self.assertEqual(power_state.NOSTATE,
|
||||
vm._translate_vm_state('hardware discovery'))
|
||||
self.assertEqual(power_state.NOSTATE,
|
||||
vm._translate_vm_state('not available'))
|
||||
|
||||
self.assertEqual(power_state.SUSPENDED,
|
||||
vm._translate_vm_state('resuming'))
|
||||
self.assertEqual(power_state.SUSPENDED,
|
||||
vm._translate_vm_state('suspended'))
|
||||
|
||||
self.assertEqual(power_state.CRASHED,
|
||||
vm._translate_vm_state('error'))
|
||||
|
||||
@mock.patch('pypowervm.wrappers.logical_partition.LPAR', autospec=True)
|
||||
def test_get_lpar_names(self, mock_lpar):
|
||||
inst1 = mock.Mock()
|
||||
inst1.configure_mock(name='inst1')
|
||||
inst2 = mock.Mock()
|
||||
inst2.configure_mock(name='inst2')
|
||||
mock_lpar.search.return_value = [inst1, inst2]
|
||||
self.assertEqual({'inst1', 'inst2'}, set(vm.get_lpar_names('adap')))
|
||||
mock_lpar.search.assert_called_once_with(
|
||||
'adap', is_mgmt_partition=False)
|
||||
|
||||
@mock.patch('pypowervm.tasks.vterm.close_vterm', autospec=True)
|
||||
def test_dlt_lpar(self, mock_vterm):
|
||||
"""Performs a delete LPAR test."""
|
||||
vm.delete_lpar(self.apt, 'inst')
|
||||
self.get_pvm_uuid.assert_called_once_with('inst')
|
||||
self.apt.delete.assert_called_once_with(
|
||||
pvm_lpar.LPAR.schema_type, root_id=self.get_pvm_uuid.return_value)
|
||||
self.assertEqual(1, mock_vterm.call_count)
|
||||
|
||||
# Test Failure Path
|
||||
# build a mock response body with the expected HSCL msg
|
||||
resp = mock.Mock()
|
||||
resp.body = 'error msg: HSCL151B more text'
|
||||
self.apt.delete.side_effect = pvm_exc.Error(
|
||||
'Mock Error Message', response=resp)
|
||||
|
||||
# Reset counters
|
||||
self.apt.reset_mock()
|
||||
mock_vterm.reset_mock()
|
||||
|
||||
self.assertRaises(pvm_exc.Error, vm.delete_lpar, self.apt, 'inst')
|
||||
self.assertEqual(1, mock_vterm.call_count)
|
||||
self.assertEqual(1, self.apt.delete.call_count)
|
||||
|
||||
self.apt.reset_mock()
|
||||
mock_vterm.reset_mock()
|
||||
|
||||
# Test HttpError 404
|
||||
resp.status = 404
|
||||
self.apt.delete.side_effect = pvm_exc.HttpError(resp=resp)
|
||||
vm.delete_lpar(self.apt, 'inst')
|
||||
self.assertEqual(1, mock_vterm.call_count)
|
||||
self.assertEqual(1, self.apt.delete.call_count)
|
||||
|
||||
self.apt.reset_mock()
|
||||
mock_vterm.reset_mock()
|
||||
|
||||
# Test Other HttpError
|
||||
resp.status = 111
|
||||
self.apt.delete.side_effect = pvm_exc.HttpError(resp=resp)
|
||||
self.assertRaises(pvm_exc.HttpError, vm.delete_lpar, self.apt, 'inst')
|
||||
self.assertEqual(1, mock_vterm.call_count)
|
||||
self.assertEqual(1, self.apt.delete.call_count)
|
||||
|
||||
self.apt.reset_mock()
|
||||
mock_vterm.reset_mock()
|
||||
|
||||
# Test HttpError 404 closing vterm
|
||||
resp.status = 404
|
||||
mock_vterm.side_effect = pvm_exc.HttpError(resp=resp)
|
||||
vm.delete_lpar(self.apt, 'inst')
|
||||
self.assertEqual(1, mock_vterm.call_count)
|
||||
self.assertEqual(0, self.apt.delete.call_count)
|
||||
|
||||
self.apt.reset_mock()
|
||||
mock_vterm.reset_mock()
|
||||
|
||||
# Test Other HttpError closing vterm
|
||||
resp.status = 111
|
||||
mock_vterm.side_effect = pvm_exc.HttpError(resp=resp)
|
||||
self.assertRaises(pvm_exc.HttpError, vm.delete_lpar, self.apt, 'inst')
|
||||
self.assertEqual(1, mock_vterm.call_count)
|
||||
self.assertEqual(0, self.apt.delete.call_count)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.VMBuilder', autospec=True)
|
||||
@mock.patch('pypowervm.utils.validation.LPARWrapperValidator',
|
||||
autospec=True)
|
||||
def test_crt_lpar(self, mock_vld, mock_vmbldr):
|
||||
self.inst.flavor.extra_specs = {'powervm:dedicated_proc': 'true'}
|
||||
mock_bldr = mock.Mock(spec=lpar_bld.LPARBuilder)
|
||||
mock_vmbldr.return_value.lpar_builder.return_value = mock_bldr
|
||||
mock_pend_lpar = mock.create_autospec(pvm_lpar.LPAR, instance=True)
|
||||
mock_bldr.build.return_value = mock_pend_lpar
|
||||
|
||||
vm.create_lpar(self.apt, 'host', self.inst)
|
||||
mock_vmbldr.assert_called_once_with('host', self.apt)
|
||||
mock_vmbldr.return_value.lpar_builder.assert_called_once_with(
|
||||
self.inst)
|
||||
mock_bldr.build.assert_called_once_with()
|
||||
mock_vld.assert_called_once_with(mock_pend_lpar, 'host')
|
||||
mock_vld.return_value.validate_all.assert_called_once_with()
|
||||
mock_pend_lpar.create.assert_called_once_with(parent='host')
|
||||
|
||||
# Test to verify the LPAR Creation with invalid name specification
|
||||
mock_vmbldr.side_effect = lpar_bld.LPARBuilderException("Invalid Name")
|
||||
self.assertRaises(exception.BuildAbortException,
|
||||
vm.create_lpar, self.apt, 'host', self.inst)
|
||||
|
||||
# HttpError
|
||||
mock_vmbldr.side_effect = pvm_exc.HttpError(mock.Mock())
|
||||
self.assertRaises(exception.PowerVMAPIFailed,
|
||||
vm.create_lpar, self.apt, 'host', self.inst)
|
||||
|
||||
@mock.patch('pypowervm.wrappers.logical_partition.LPAR', autospec=True)
|
||||
def test_get_instance_wrapper(self, mock_lpar):
|
||||
resp = mock.Mock(status=404)
|
||||
mock_lpar.get.side_effect = pvm_exc.Error('message', response=resp)
|
||||
# vm.get_instance_wrapper(self.apt, instance, 'lpar_uuid')
|
||||
self.assertRaises(exception.InstanceNotFound, vm.get_instance_wrapper,
|
||||
self.apt, self.inst)
|
||||
|
||||
@mock.patch('pypowervm.tasks.power.power_on', autospec=True)
|
||||
@mock.patch('oslo_concurrency.lockutils.lock', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper')
|
||||
def test_power_on(self, mock_wrap, mock_lock, mock_power_on):
|
||||
entry = mock.Mock(state=pvm_bp.LPARState.NOT_ACTIVATED)
|
||||
mock_wrap.return_value = entry
|
||||
|
||||
vm.power_on(None, self.inst)
|
||||
mock_power_on.assert_called_once_with(entry, None)
|
||||
mock_lock.assert_called_once_with('power_%s' % self.inst.uuid)
|
||||
|
||||
mock_power_on.reset_mock()
|
||||
mock_lock.reset_mock()
|
||||
|
||||
stop_states = [
|
||||
pvm_bp.LPARState.RUNNING, pvm_bp.LPARState.STARTING,
|
||||
pvm_bp.LPARState.OPEN_FIRMWARE, pvm_bp.LPARState.SHUTTING_DOWN,
|
||||
pvm_bp.LPARState.ERROR, pvm_bp.LPARState.RESUMING,
|
||||
pvm_bp.LPARState.SUSPENDING]
|
||||
|
||||
for stop_state in stop_states:
|
||||
entry.state = stop_state
|
||||
vm.power_on(None, self.inst)
|
||||
mock_lock.assert_called_once_with('power_%s' % self.inst.uuid)
|
||||
mock_lock.reset_mock()
|
||||
self.assertEqual(0, mock_power_on.call_count)
|
||||
|
||||
@mock.patch('pypowervm.tasks.power.power_on', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper')
|
||||
def test_power_on_negative(self, mock_wrp, mock_power_on):
|
||||
mock_wrp.return_value = mock.Mock(state=pvm_bp.LPARState.NOT_ACTIVATED)
|
||||
|
||||
# Convertible (PowerVM) exception
|
||||
mock_power_on.side_effect = pvm_exc.VMPowerOnFailure(
|
||||
reason='Something bad', lpar_nm='TheLPAR')
|
||||
self.assertRaises(exception.InstancePowerOnFailure,
|
||||
vm.power_on, None, self.inst)
|
||||
|
||||
# Non-pvm error raises directly
|
||||
mock_power_on.side_effect = ValueError()
|
||||
self.assertRaises(ValueError, vm.power_on, None, self.inst)
|
||||
|
||||
@mock.patch('pypowervm.tasks.power.PowerOp', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.power.power_off_progressive', autospec=True)
|
||||
@mock.patch('oslo_concurrency.lockutils.lock', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper')
|
||||
def test_power_off(self, mock_wrap, mock_lock, mock_power_off, mock_pop):
|
||||
entry = mock.Mock(state=pvm_bp.LPARState.NOT_ACTIVATED)
|
||||
mock_wrap.return_value = entry
|
||||
|
||||
vm.power_off(None, self.inst)
|
||||
self.assertEqual(0, mock_power_off.call_count)
|
||||
self.assertEqual(0, mock_pop.stop.call_count)
|
||||
mock_lock.assert_called_once_with('power_%s' % self.inst.uuid)
|
||||
|
||||
stop_states = [
|
||||
pvm_bp.LPARState.RUNNING, pvm_bp.LPARState.STARTING,
|
||||
pvm_bp.LPARState.OPEN_FIRMWARE, pvm_bp.LPARState.SHUTTING_DOWN,
|
||||
pvm_bp.LPARState.ERROR, pvm_bp.LPARState.RESUMING,
|
||||
pvm_bp.LPARState.SUSPENDING]
|
||||
for stop_state in stop_states:
|
||||
entry.state = stop_state
|
||||
mock_power_off.reset_mock()
|
||||
mock_pop.stop.reset_mock()
|
||||
mock_lock.reset_mock()
|
||||
vm.power_off(None, self.inst)
|
||||
mock_power_off.assert_called_once_with(entry)
|
||||
self.assertEqual(0, mock_pop.stop.call_count)
|
||||
mock_lock.assert_called_once_with('power_%s' % self.inst.uuid)
|
||||
mock_power_off.reset_mock()
|
||||
mock_lock.reset_mock()
|
||||
vm.power_off(None, self.inst, force_immediate=True, timeout=5)
|
||||
self.assertEqual(0, mock_power_off.call_count)
|
||||
mock_pop.stop.assert_called_once_with(
|
||||
entry, opts=mock.ANY, timeout=5)
|
||||
self.assertEqual('PowerOff(immediate=true, operation=shutdown)',
|
||||
str(mock_pop.stop.call_args[1]['opts']))
|
||||
mock_lock.assert_called_once_with('power_%s' % self.inst.uuid)
|
||||
|
||||
@mock.patch('pypowervm.tasks.power.power_off_progressive', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper')
|
||||
def test_power_off_negative(self, mock_wrap, mock_power_off):
|
||||
"""Negative tests."""
|
||||
mock_wrap.return_value = mock.Mock(state=pvm_bp.LPARState.RUNNING)
|
||||
|
||||
# Raise the expected pypowervm exception
|
||||
mock_power_off.side_effect = pvm_exc.VMPowerOffFailure(
|
||||
reason='Something bad.', lpar_nm='TheLPAR')
|
||||
# We should get a valid Nova exception that the compute manager expects
|
||||
self.assertRaises(exception.InstancePowerOffFailure,
|
||||
vm.power_off, None, self.inst)
|
||||
|
||||
# Non-pvm error raises directly
|
||||
mock_power_off.side_effect = ValueError()
|
||||
self.assertRaises(ValueError, vm.power_off, None, self.inst)
|
||||
|
||||
@mock.patch('pypowervm.tasks.power.power_on', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.power.power_off_progressive', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.power.PowerOp', autospec=True)
|
||||
@mock.patch('oslo_concurrency.lockutils.lock', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper')
|
||||
def test_reboot(self, mock_wrap, mock_lock, mock_pop, mock_pwroff,
|
||||
mock_pwron):
|
||||
entry = mock.Mock(state=pvm_bp.LPARState.NOT_ACTIVATED)
|
||||
mock_wrap.return_value = entry
|
||||
|
||||
# No power_off
|
||||
vm.reboot('adap', self.inst, False)
|
||||
mock_lock.assert_called_once_with('power_%s' % self.inst.uuid)
|
||||
mock_wrap.assert_called_once_with('adap', self.inst)
|
||||
mock_pwron.assert_called_once_with(entry, None)
|
||||
self.assertEqual(0, mock_pwroff.call_count)
|
||||
self.assertEqual(0, mock_pop.stop.call_count)
|
||||
|
||||
mock_pwron.reset_mock()
|
||||
|
||||
# power_off (no power_on) hard
|
||||
entry.state = pvm_bp.LPARState.RUNNING
|
||||
vm.reboot('adap', self.inst, True)
|
||||
self.assertEqual(0, mock_pwron.call_count)
|
||||
self.assertEqual(0, mock_pwroff.call_count)
|
||||
mock_pop.stop.assert_called_once_with(entry, opts=mock.ANY)
|
||||
self.assertEqual(
|
||||
'PowerOff(immediate=true, operation=shutdown, restart=true)',
|
||||
str(mock_pop.stop.call_args[1]['opts']))
|
||||
|
||||
mock_pop.reset_mock()
|
||||
|
||||
# power_off (no power_on) soft
|
||||
entry.state = pvm_bp.LPARState.RUNNING
|
||||
vm.reboot('adap', self.inst, False)
|
||||
self.assertEqual(0, mock_pwron.call_count)
|
||||
mock_pwroff.assert_called_once_with(entry, restart=True)
|
||||
self.assertEqual(0, mock_pop.stop.call_count)
|
||||
|
||||
mock_pwroff.reset_mock()
|
||||
|
||||
# PowerVM error is converted
|
||||
mock_pop.stop.side_effect = pvm_exc.TimeoutError("Timed out")
|
||||
self.assertRaises(exception.InstanceRebootFailure,
|
||||
vm.reboot, 'adap', self.inst, True)
|
||||
|
||||
# Non-PowerVM error is raised directly
|
||||
mock_pwroff.side_effect = ValueError
|
||||
self.assertRaises(ValueError, vm.reboot, 'adap', self.inst, False)
|
||||
|
||||
@mock.patch('oslo_serialization.jsonutils.loads')
|
||||
def test_get_vm_qp(self, mock_loads):
|
||||
self.apt.helpers = ['helper1', pvm_log.log_helper, 'helper3']
|
||||
|
||||
# Defaults
|
||||
self.assertEqual(mock_loads.return_value,
|
||||
vm.get_vm_qp(self.apt, 'lpar_uuid'))
|
||||
self.apt.read.assert_called_once_with(
|
||||
'LogicalPartition', root_id='lpar_uuid', suffix_type='quick',
|
||||
suffix_parm=None)
|
||||
mock_loads.assert_called_once_with(self.apt.read.return_value.body)
|
||||
|
||||
self.apt.read.reset_mock()
|
||||
mock_loads.reset_mock()
|
||||
|
||||
# Specific qprop, no logging errors
|
||||
self.assertEqual(mock_loads.return_value,
|
||||
vm.get_vm_qp(self.apt, 'lpar_uuid', qprop='Prop',
|
||||
log_errors=False))
|
||||
self.apt.read.assert_called_once_with(
|
||||
'LogicalPartition', root_id='lpar_uuid', suffix_type='quick',
|
||||
suffix_parm='Prop', helpers=['helper1', 'helper3'])
|
||||
|
||||
resp = mock.MagicMock()
|
||||
resp.status = 404
|
||||
self.apt.read.side_effect = pvm_exc.HttpError(resp)
|
||||
self.assertRaises(exception.InstanceNotFound, vm.get_vm_qp, self.apt,
|
||||
'lpar_uuid', log_errors=False)
|
||||
|
||||
self.apt.read.side_effect = pvm_exc.Error("message", response=None)
|
||||
self.assertRaises(pvm_exc.Error, vm.get_vm_qp, self.apt,
|
||||
'lpar_uuid', log_errors=False)
|
||||
|
||||
resp.status = 500
|
||||
self.apt.read.side_effect = pvm_exc.Error("message", response=resp)
|
||||
self.assertRaises(pvm_exc.Error, vm.get_vm_qp, self.apt,
|
||||
'lpar_uuid', log_errors=False)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.get_pvm_uuid')
|
||||
@mock.patch('pypowervm.wrappers.network.CNA.search')
|
||||
@mock.patch('pypowervm.wrappers.network.CNA.get')
|
||||
def test_get_cnas(self, mock_get, mock_search, mock_uuid):
|
||||
# No kwargs: get
|
||||
self.assertEqual(mock_get.return_value, vm.get_cnas(self.apt, 'inst'))
|
||||
mock_uuid.assert_called_once_with('inst')
|
||||
mock_get.assert_called_once_with(self.apt, parent_type=pvm_lpar.LPAR,
|
||||
parent_uuid=mock_uuid.return_value)
|
||||
mock_search.assert_not_called()
|
||||
# With kwargs: search
|
||||
mock_get.reset_mock()
|
||||
mock_uuid.reset_mock()
|
||||
self.assertEqual(mock_search.return_value, vm.get_cnas(
|
||||
self.apt, 'inst', one=2, three=4))
|
||||
mock_uuid.assert_called_once_with('inst')
|
||||
mock_search.assert_called_once_with(
|
||||
self.apt, parent_type=pvm_lpar.LPAR,
|
||||
parent_uuid=mock_uuid.return_value, one=2, three=4)
|
||||
mock_get.assert_not_called()
|
||||
|
||||
def test_norm_mac(self):
|
||||
EXPECTED = "12:34:56:78:90:ab"
|
||||
self.assertEqual(EXPECTED, vm.norm_mac("12:34:56:78:90:ab"))
|
||||
self.assertEqual(EXPECTED, vm.norm_mac("1234567890ab"))
|
||||
self.assertEqual(EXPECTED, vm.norm_mac("12:34:56:78:90:AB"))
|
||||
self.assertEqual(EXPECTED, vm.norm_mac("1234567890AB"))
|
||||
@@ -1,456 +0,0 @@
|
||||
# Copyright 2015, 2018 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from pypowervm import const as pvm_const
|
||||
from pypowervm.tasks import hdisk
|
||||
from pypowervm.tests import test_fixtures as pvm_fx
|
||||
from pypowervm.utils import transaction as pvm_tx
|
||||
from pypowervm.wrappers import storage as pvm_stor
|
||||
from pypowervm.wrappers import virtual_io_server as pvm_vios
|
||||
from unittest import mock
|
||||
|
||||
from nova import conf as cfg
|
||||
from nova import exception as exc
|
||||
from nova import test
|
||||
from nova.virt.powervm.volume import fcvscsi
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
I_WWPN_1 = '21000024FF649104'
|
||||
I_WWPN_2 = '21000024FF649105'
|
||||
|
||||
|
||||
class TestVSCSIAdapter(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestVSCSIAdapter, self).setUp()
|
||||
|
||||
self.adpt = self.useFixture(pvm_fx.AdapterFx()).adpt
|
||||
self.wtsk = mock.create_autospec(pvm_tx.WrapperTask, instance=True)
|
||||
self.ftsk = mock.create_autospec(pvm_tx.FeedTask, instance=True)
|
||||
self.ftsk.configure_mock(wrapper_tasks={'vios_uuid': self.wtsk})
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.get_pvm_uuid')
|
||||
def init_vol_adpt(mock_pvm_uuid):
|
||||
con_info = {
|
||||
'serial': 'id',
|
||||
'data': {
|
||||
'initiator_target_map': {
|
||||
I_WWPN_1: ['t1'],
|
||||
I_WWPN_2: ['t2', 't3']
|
||||
},
|
||||
'target_lun': '1',
|
||||
'volume_id': 'a_volume_identifier',
|
||||
},
|
||||
}
|
||||
mock_inst = mock.MagicMock()
|
||||
mock_pvm_uuid.return_value = '1234'
|
||||
|
||||
return fcvscsi.FCVscsiVolumeAdapter(
|
||||
self.adpt, mock_inst, con_info, stg_ftsk=self.ftsk)
|
||||
self.vol_drv = init_vol_adpt()
|
||||
|
||||
@mock.patch('pypowervm.utils.transaction.FeedTask', autospec=True)
|
||||
@mock.patch('pypowervm.wrappers.virtual_io_server.VIOS', autospec=True)
|
||||
def test_reset_stg_ftsk(self, mock_vios, mock_ftsk):
|
||||
self.vol_drv.reset_stg_ftsk('stg_ftsk')
|
||||
self.assertEqual('stg_ftsk', self.vol_drv.stg_ftsk)
|
||||
|
||||
mock_vios.getter.return_value = 'getter'
|
||||
mock_ftsk.return_value = 'local_feed_task'
|
||||
self.vol_drv.reset_stg_ftsk()
|
||||
self.assertEqual('local_feed_task', self.vol_drv.stg_ftsk)
|
||||
mock_vios.getter.assert_called_once_with(
|
||||
self.adpt, xag=[pvm_const.XAG.VIO_SMAP])
|
||||
mock_ftsk.assert_called_once_with('local_feed_task', 'getter')
|
||||
|
||||
@mock.patch('pypowervm.tasks.partition.get_physical_wwpns', autospec=True)
|
||||
def test_wwpns(self, mock_vio_wwpns):
|
||||
mock_vio_wwpns.return_value = ['aa', 'bb']
|
||||
wwpns = fcvscsi.wwpns(self.adpt)
|
||||
self.assertListEqual(['aa', 'bb'], wwpns)
|
||||
mock_vio_wwpns.assert_called_once_with(self.adpt, force_refresh=False)
|
||||
|
||||
def test_set_udid(self):
|
||||
# Mock connection info
|
||||
self.vol_drv.connection_info['data'][fcvscsi.UDID_KEY] = None
|
||||
|
||||
# Set the UDID
|
||||
self.vol_drv._set_udid('udid')
|
||||
|
||||
# Verify
|
||||
self.assertEqual('udid',
|
||||
self.vol_drv.connection_info['data'][fcvscsi.UDID_KEY])
|
||||
|
||||
def test_get_udid(self):
|
||||
# Set the value to retrieve
|
||||
self.vol_drv.connection_info['data'][fcvscsi.UDID_KEY] = 'udid'
|
||||
retrieved_udid = self.vol_drv._get_udid()
|
||||
# Check key found
|
||||
self.assertEqual('udid', retrieved_udid)
|
||||
|
||||
# Check key not found
|
||||
self.vol_drv.connection_info['data'].pop(fcvscsi.UDID_KEY)
|
||||
retrieved_udid = self.vol_drv._get_udid()
|
||||
# Check key not found
|
||||
self.assertIsNone(retrieved_udid)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper')
|
||||
@mock.patch('pypowervm.utils.transaction.FeedTask', autospec=True)
|
||||
def test_attach_volume(self, mock_feed_task, mock_get_wrap):
|
||||
mock_lpar_wrap = mock.MagicMock()
|
||||
mock_lpar_wrap.can_modify_io.return_value = True, None
|
||||
mock_get_wrap.return_value = mock_lpar_wrap
|
||||
mock_attach_ftsk = mock_feed_task.return_value
|
||||
|
||||
# Pass if all vioses modified
|
||||
mock_ret = {'wrapper_task_rets': {'vios1': {'vio_modified': True},
|
||||
'vios2': {'vio_modified': True}}}
|
||||
mock_attach_ftsk.execute.return_value = mock_ret
|
||||
self.vol_drv.attach_volume()
|
||||
mock_feed_task.assert_called_once()
|
||||
mock_attach_ftsk.add_functor_subtask.assert_called_once_with(
|
||||
self.vol_drv._attach_volume_to_vio, provides='vio_modified',
|
||||
flag_update=False)
|
||||
mock_attach_ftsk.execute.assert_called_once()
|
||||
self.ftsk.execute.assert_called_once()
|
||||
|
||||
mock_feed_task.reset_mock()
|
||||
mock_attach_ftsk.reset_mock()
|
||||
self.ftsk.reset_mock()
|
||||
|
||||
# Pass if 1 vios modified
|
||||
mock_ret = {'wrapper_task_rets': {'vios1': {'vio_modified': True},
|
||||
'vios2': {'vio_modified': False}}}
|
||||
mock_attach_ftsk.execute.return_value = mock_ret
|
||||
self.vol_drv.attach_volume()
|
||||
mock_feed_task.assert_called_once()
|
||||
mock_attach_ftsk.add_functor_subtask.assert_called_once_with(
|
||||
self.vol_drv._attach_volume_to_vio, provides='vio_modified',
|
||||
flag_update=False)
|
||||
mock_attach_ftsk.execute.assert_called_once()
|
||||
self.ftsk.execute.assert_called_once()
|
||||
|
||||
# Raise if no vios modified
|
||||
mock_ret = {'wrapper_task_rets': {'vios1': {'vio_modified': False},
|
||||
'vios2': {'vio_modified': False}}}
|
||||
mock_attach_ftsk.execute.return_value = mock_ret
|
||||
self.assertRaises(exc.VolumeAttachFailed, self.vol_drv.attach_volume)
|
||||
|
||||
# Raise if vm in invalid state
|
||||
mock_lpar_wrap.can_modify_io.return_value = False, None
|
||||
self.assertRaises(exc.VolumeAttachFailed, self.vol_drv.attach_volume)
|
||||
|
||||
@mock.patch('nova.virt.powervm.volume.fcvscsi.FCVscsiVolumeAdapter.'
|
||||
'_set_udid')
|
||||
@mock.patch('nova.virt.powervm.volume.fcvscsi.FCVscsiVolumeAdapter.'
|
||||
'_add_append_mapping')
|
||||
@mock.patch('nova.virt.powervm.volume.fcvscsi.FCVscsiVolumeAdapter.'
|
||||
'_discover_volume_on_vios')
|
||||
@mock.patch('pypowervm.tasks.hdisk.good_discovery', autospec=True)
|
||||
def test_attach_volume_to_vio(self, mock_good_disc, mock_disc_vol,
|
||||
mock_add_map, mock_set_udid):
|
||||
# Setup mocks
|
||||
mock_vios = mock.MagicMock()
|
||||
mock_vios.uuid = 'uuid'
|
||||
mock_disc_vol.return_value = 'status', 'devname', 'udid'
|
||||
|
||||
# Bad discovery
|
||||
mock_good_disc.return_value = False
|
||||
ret = self.vol_drv._attach_volume_to_vio(mock_vios)
|
||||
self.assertFalse(ret)
|
||||
mock_disc_vol.assert_called_once_with(mock_vios)
|
||||
mock_good_disc.assert_called_once_with('status', 'devname')
|
||||
|
||||
# Good discovery
|
||||
mock_good_disc.return_value = True
|
||||
ret = self.vol_drv._attach_volume_to_vio(mock_vios)
|
||||
self.assertTrue(ret)
|
||||
mock_add_map.assert_called_once_with(
|
||||
'uuid', 'devname', tag='a_volume_identifier')
|
||||
mock_set_udid.assert_called_once_with('udid')
|
||||
|
||||
def test_extend_volume(self):
|
||||
# Ensure the method is implemented
|
||||
self.vol_drv.extend_volume()
|
||||
|
||||
@mock.patch('nova.virt.powervm.volume.fcvscsi.LOG')
|
||||
@mock.patch('pypowervm.tasks.hdisk.good_discovery', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.hdisk.discover_hdisk', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.hdisk.build_itls', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.volume.fcvscsi.FCVscsiVolumeAdapter.'
|
||||
'_get_hdisk_itls')
|
||||
def test_discover_volume_on_vios(self, mock_get_itls, mock_build_itls,
|
||||
mock_disc_hdisk, mock_good_disc,
|
||||
mock_log):
|
||||
mock_vios = mock.MagicMock()
|
||||
mock_vios.uuid = 'uuid'
|
||||
mock_get_itls.return_value = 'v_wwpns', 't_wwpns', 'lun'
|
||||
mock_build_itls.return_value = 'itls'
|
||||
mock_disc_hdisk.return_value = 'status', 'devname', 'udid'
|
||||
|
||||
# Good discovery
|
||||
mock_good_disc.return_value = True
|
||||
status, devname, udid = self.vol_drv._discover_volume_on_vios(
|
||||
mock_vios)
|
||||
self.assertEqual(mock_disc_hdisk.return_value[0], status)
|
||||
self.assertEqual(mock_disc_hdisk.return_value[1], devname)
|
||||
self.assertEqual(mock_disc_hdisk.return_value[2], udid)
|
||||
mock_get_itls.assert_called_once_with(mock_vios)
|
||||
mock_build_itls.assert_called_once_with('v_wwpns', 't_wwpns', 'lun')
|
||||
mock_disc_hdisk.assert_called_once_with(self.adpt, 'uuid', 'itls')
|
||||
mock_good_disc.assert_called_once_with('status', 'devname')
|
||||
mock_log.info.assert_called_once()
|
||||
mock_log.warning.assert_not_called()
|
||||
|
||||
mock_log.reset_mock()
|
||||
|
||||
# Bad discovery, not device in use status
|
||||
mock_good_disc.return_value = False
|
||||
self.vol_drv._discover_volume_on_vios(mock_vios)
|
||||
mock_log.warning.assert_not_called()
|
||||
mock_log.info.assert_not_called()
|
||||
|
||||
# Bad discovery, device in use status
|
||||
mock_disc_hdisk.return_value = (hdisk.LUAStatus.DEVICE_IN_USE, 'dev',
|
||||
'udid')
|
||||
self.vol_drv._discover_volume_on_vios(mock_vios)
|
||||
mock_log.warning.assert_called_once()
|
||||
|
||||
def test_get_hdisk_itls(self):
|
||||
"""Validates the _get_hdisk_itls method."""
|
||||
|
||||
mock_vios = mock.MagicMock()
|
||||
mock_vios.get_active_pfc_wwpns.return_value = [I_WWPN_1]
|
||||
|
||||
i_wwpn, t_wwpns, lun = self.vol_drv._get_hdisk_itls(mock_vios)
|
||||
self.assertListEqual([I_WWPN_1], i_wwpn)
|
||||
self.assertListEqual(['t1'], t_wwpns)
|
||||
self.assertEqual('1', lun)
|
||||
|
||||
mock_vios.get_active_pfc_wwpns.return_value = [I_WWPN_2]
|
||||
i_wwpn, t_wwpns, lun = self.vol_drv._get_hdisk_itls(mock_vios)
|
||||
self.assertListEqual([I_WWPN_2], i_wwpn)
|
||||
self.assertListEqual(['t2', 't3'], t_wwpns)
|
||||
|
||||
mock_vios.get_active_pfc_wwpns.return_value = ['12345']
|
||||
i_wwpn, t_wwpns, lun = self.vol_drv._get_hdisk_itls(mock_vios)
|
||||
self.assertListEqual([], i_wwpn)
|
||||
|
||||
@mock.patch('pypowervm.wrappers.storage.PV', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.build_vscsi_mapping',
|
||||
autospec=True)
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.add_map', autospec=True)
|
||||
def test_add_append_mapping(self, mock_add_map, mock_bld_map, mock_pv):
|
||||
def test_afs(add_func):
|
||||
mock_vios = mock.create_autospec(pvm_vios.VIOS)
|
||||
self.assertEqual(mock_add_map.return_value, add_func(mock_vios))
|
||||
mock_pv.bld.assert_called_once_with(self.adpt, 'devname', tag=None)
|
||||
mock_bld_map.assert_called_once_with(
|
||||
None, mock_vios, self.vol_drv.vm_uuid,
|
||||
mock_pv.bld.return_value)
|
||||
mock_add_map.assert_called_once_with(
|
||||
mock_vios, mock_bld_map.return_value)
|
||||
|
||||
self.wtsk.add_functor_subtask.side_effect = test_afs
|
||||
self.vol_drv._add_append_mapping('vios_uuid', 'devname')
|
||||
self.wtsk.add_functor_subtask.assert_called_once()
|
||||
|
||||
@mock.patch('nova.virt.powervm.volume.fcvscsi.LOG.warning')
|
||||
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper')
|
||||
@mock.patch('pypowervm.utils.transaction.FeedTask', autospec=True)
|
||||
def test_detach_volume(self, mock_feed_task, mock_get_wrap, mock_log):
|
||||
mock_lpar_wrap = mock.MagicMock()
|
||||
mock_lpar_wrap.can_modify_io.return_value = True, None
|
||||
mock_get_wrap.return_value = mock_lpar_wrap
|
||||
mock_detach_ftsk = mock_feed_task.return_value
|
||||
|
||||
# Multiple vioses modified
|
||||
mock_ret = {'wrapper_task_rets': {'vios1': {'vio_modified': True},
|
||||
'vios2': {'vio_modified': True}}}
|
||||
mock_detach_ftsk.execute.return_value = mock_ret
|
||||
self.vol_drv.detach_volume()
|
||||
mock_feed_task.assert_called_once()
|
||||
mock_detach_ftsk.add_functor_subtask.assert_called_once_with(
|
||||
self.vol_drv._detach_vol_for_vio, provides='vio_modified',
|
||||
flag_update=False)
|
||||
mock_detach_ftsk.execute.assert_called_once_with()
|
||||
self.ftsk.execute.assert_called_once_with()
|
||||
mock_log.assert_not_called()
|
||||
|
||||
# 1 vios modified
|
||||
mock_ret = {'wrapper_task_rets': {'vios1': {'vio_modified': True},
|
||||
'vios2': {'vio_modified': False}}}
|
||||
mock_detach_ftsk.execute.return_value = mock_ret
|
||||
self.vol_drv.detach_volume()
|
||||
mock_log.assert_not_called()
|
||||
|
||||
# No vioses modifed
|
||||
mock_ret = {'wrapper_task_rets': {'vios1': {'vio_modified': False},
|
||||
'vios2': {'vio_modified': False}}}
|
||||
mock_detach_ftsk.execute.return_value = mock_ret
|
||||
self.vol_drv.detach_volume()
|
||||
mock_log.assert_called_once()
|
||||
|
||||
# Raise if exception during execute
|
||||
mock_detach_ftsk.execute.side_effect = Exception()
|
||||
self.assertRaises(exc.VolumeDetachFailed, self.vol_drv.detach_volume)
|
||||
|
||||
# Raise if vm in invalid state
|
||||
mock_lpar_wrap.can_modify_io.return_value = False, None
|
||||
self.assertRaises(exc.VolumeDetachFailed, self.vol_drv.detach_volume)
|
||||
|
||||
@mock.patch('pypowervm.tasks.hdisk.good_discovery', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.volume.fcvscsi.FCVscsiVolumeAdapter.'
|
||||
'_discover_volume_on_vios')
|
||||
@mock.patch('nova.virt.powervm.volume.fcvscsi.FCVscsiVolumeAdapter.'
|
||||
'_add_remove_mapping')
|
||||
@mock.patch('nova.virt.powervm.volume.fcvscsi.FCVscsiVolumeAdapter.'
|
||||
'_add_remove_hdisk')
|
||||
@mock.patch('nova.virt.powervm.vm.get_vm_qp')
|
||||
def test_detach_vol_for_vio(self, mock_get_qp, mock_rm_hdisk, mock_rm_map,
|
||||
mock_disc_vol, mock_good_disc):
|
||||
# Good detach, bdm data is found
|
||||
self.vol_drv._set_udid('udid')
|
||||
mock_vios = mock.MagicMock()
|
||||
mock_vios.uuid = 'vios_uuid'
|
||||
mock_vios.hdisk_from_uuid.return_value = 'devname'
|
||||
mock_get_qp.return_value = 'part_id'
|
||||
ret = self.vol_drv._detach_vol_for_vio(mock_vios)
|
||||
self.assertTrue(ret)
|
||||
mock_vios.hdisk_from_uuid.assert_called_once_with('udid')
|
||||
mock_rm_map.assert_called_once_with('part_id', 'vios_uuid', 'devname')
|
||||
mock_rm_hdisk.assert_called_once_with(mock_vios, 'devname')
|
||||
|
||||
mock_vios.reset_mock()
|
||||
mock_rm_map.reset_mock()
|
||||
mock_rm_hdisk.reset_mock()
|
||||
|
||||
# Good detach, no udid
|
||||
self.vol_drv._set_udid(None)
|
||||
mock_disc_vol.return_value = 'status', 'devname', 'udid'
|
||||
mock_good_disc.return_value = True
|
||||
ret = self.vol_drv._detach_vol_for_vio(mock_vios)
|
||||
self.assertTrue(ret)
|
||||
mock_vios.hdisk_from_uuid.assert_not_called()
|
||||
mock_disc_vol.assert_called_once_with(mock_vios)
|
||||
mock_good_disc.assert_called_once_with('status', 'devname')
|
||||
mock_rm_map.assert_called_once_with('part_id', 'vios_uuid', 'devname')
|
||||
mock_rm_hdisk.assert_called_once_with(mock_vios, 'devname')
|
||||
|
||||
mock_vios.reset_mock()
|
||||
mock_disc_vol.reset_mock()
|
||||
mock_good_disc.reset_mock()
|
||||
mock_rm_map.reset_mock()
|
||||
mock_rm_hdisk.reset_mock()
|
||||
|
||||
# Good detach, no device name
|
||||
self.vol_drv._set_udid('udid')
|
||||
mock_vios.hdisk_from_uuid.return_value = None
|
||||
ret = self.vol_drv._detach_vol_for_vio(mock_vios)
|
||||
self.assertTrue(ret)
|
||||
mock_vios.hdisk_from_uuid.assert_called_once_with('udid')
|
||||
mock_disc_vol.assert_called_once_with(mock_vios)
|
||||
mock_good_disc.assert_called_once_with('status', 'devname')
|
||||
mock_rm_map.assert_called_once_with('part_id', 'vios_uuid', 'devname')
|
||||
mock_rm_hdisk.assert_called_once_with(mock_vios, 'devname')
|
||||
|
||||
mock_rm_map.reset_mock()
|
||||
mock_rm_hdisk.reset_mock()
|
||||
|
||||
# Bad detach, invalid state
|
||||
mock_good_disc.return_value = False
|
||||
ret = self.vol_drv._detach_vol_for_vio(mock_vios)
|
||||
self.assertFalse(ret)
|
||||
mock_rm_map.assert_not_called()
|
||||
mock_rm_hdisk.assert_not_called()
|
||||
|
||||
# Bad detach, exception discovering volume on vios
|
||||
mock_disc_vol.side_effect = Exception()
|
||||
ret = self.vol_drv._detach_vol_for_vio(mock_vios)
|
||||
self.assertFalse(ret)
|
||||
mock_rm_map.assert_not_called()
|
||||
mock_rm_hdisk.assert_not_called()
|
||||
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.gen_match_func', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.remove_maps', autospec=True)
|
||||
def test_add_remove_mapping(self, mock_rm_maps, mock_gen_match):
|
||||
def test_afs(rm_func):
|
||||
mock_vios = mock.create_autospec(pvm_vios.VIOS)
|
||||
self.assertEqual(mock_rm_maps.return_value, rm_func(mock_vios))
|
||||
mock_gen_match.assert_called_once_with(
|
||||
pvm_stor.PV, names=['devname'])
|
||||
mock_rm_maps.assert_called_once_with(
|
||||
mock_vios, 'vm_uuid', mock_gen_match.return_value)
|
||||
|
||||
self.wtsk.add_functor_subtask.side_effect = test_afs
|
||||
self.vol_drv._add_remove_mapping('vm_uuid', 'vios_uuid', 'devname')
|
||||
self.wtsk.add_functor_subtask.assert_called_once()
|
||||
|
||||
@mock.patch('pypowervm.tasks.hdisk.remove_hdisk', autospec=True)
|
||||
@mock.patch('taskflow.task.FunctorTask', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.volume.fcvscsi.FCVscsiVolumeAdapter.'
|
||||
'_check_host_mappings')
|
||||
def test_add_remove_hdisk(self, mock_check_maps, mock_functask,
|
||||
mock_rm_hdisk):
|
||||
mock_vios = mock.MagicMock()
|
||||
mock_vios.uuid = 'uuid'
|
||||
mock_check_maps.return_value = True
|
||||
self.vol_drv._add_remove_hdisk(mock_vios, 'devname')
|
||||
mock_functask.assert_not_called()
|
||||
self.ftsk.add_post_execute.assert_not_called()
|
||||
mock_check_maps.assert_called_once_with(mock_vios, 'devname')
|
||||
self.assertEqual(0, mock_rm_hdisk.call_count)
|
||||
|
||||
def test_functor_task(rm_hdisk, name=None):
|
||||
rm_hdisk()
|
||||
return 'functor_task'
|
||||
|
||||
mock_check_maps.return_value = False
|
||||
mock_functask.side_effect = test_functor_task
|
||||
self.vol_drv._add_remove_hdisk(mock_vios, 'devname')
|
||||
mock_functask.assert_called_once()
|
||||
self.ftsk.add_post_execute.assert_called_once_with('functor_task')
|
||||
mock_rm_hdisk.assert_called_once_with(self.adpt, CONF.host,
|
||||
'devname', 'uuid')
|
||||
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.gen_match_func', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.find_maps', autospec=True)
|
||||
def test_check_host_mappings(self, mock_find_maps, mock_gen_match):
|
||||
mock_vios = mock.MagicMock()
|
||||
mock_vios.uuid = 'uuid2'
|
||||
mock_v1 = mock.MagicMock(scsi_mappings='scsi_maps_1', uuid='uuid1')
|
||||
mock_v2 = mock.MagicMock(scsi_mappings='scsi_maps_2', uuid='uuid2')
|
||||
mock_feed = [mock_v1, mock_v2]
|
||||
self.ftsk.feed = mock_feed
|
||||
|
||||
# Multiple mappings found
|
||||
mock_find_maps.return_value = ['map1', 'map2']
|
||||
ret = self.vol_drv._check_host_mappings(mock_vios, 'devname')
|
||||
self.assertTrue(ret)
|
||||
mock_gen_match.assert_called_once_with(pvm_stor.PV, names=['devname'])
|
||||
mock_find_maps.assert_called_once_with('scsi_maps_2', None,
|
||||
mock_gen_match.return_value)
|
||||
|
||||
# One mapping found
|
||||
mock_find_maps.return_value = ['map1']
|
||||
ret = self.vol_drv._check_host_mappings(mock_vios, 'devname')
|
||||
self.assertFalse(ret)
|
||||
|
||||
# No mappings found
|
||||
mock_find_maps.return_value = []
|
||||
ret = self.vol_drv._check_host_mappings(mock_vios, 'devname')
|
||||
self.assertFalse(ret)
|
||||
@@ -1,17 +0,0 @@
|
||||
# Copyright 2017 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from nova.virt.powervm import driver
|
||||
|
||||
PowerVMDriver = driver.PowerVMDriver
|
||||
@@ -1,268 +0,0 @@
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# Copyright 2015, 2018 IBM Corp.
|
||||
#
|
||||
# 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 abc
|
||||
|
||||
import oslo_log.log as logging
|
||||
import pypowervm.const as pvm_const
|
||||
import pypowervm.tasks.scsi_mapper as tsk_map
|
||||
import pypowervm.util as pvm_u
|
||||
import pypowervm.wrappers.virtual_io_server as pvm_vios
|
||||
|
||||
from nova import exception
|
||||
from nova.virt.powervm import mgmt
|
||||
from nova.virt.powervm import vm
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DiskType(object):
|
||||
BOOT = 'boot'
|
||||
IMAGE = 'image'
|
||||
|
||||
|
||||
class IterableToFileAdapter(object):
|
||||
"""A degenerate file-like so that an iterable can be read like a file.
|
||||
|
||||
The Glance client returns an iterable, but PowerVM requires a file. This
|
||||
is the adapter between the two.
|
||||
"""
|
||||
|
||||
def __init__(self, iterable):
|
||||
self.iterator = iterable.__iter__()
|
||||
self.remaining_data = ''
|
||||
|
||||
def read(self, size):
|
||||
chunk = self.remaining_data
|
||||
try:
|
||||
while not chunk:
|
||||
chunk = next(self.iterator)
|
||||
except StopIteration:
|
||||
return ''
|
||||
return_value = chunk[0:size]
|
||||
self.remaining_data = chunk[size:]
|
||||
return return_value
|
||||
|
||||
|
||||
class DiskAdapter(metaclass=abc.ABCMeta):
|
||||
|
||||
capabilities = {
|
||||
'shared_storage': False,
|
||||
'has_imagecache': False,
|
||||
'snapshot': False,
|
||||
}
|
||||
|
||||
def __init__(self, adapter, host_uuid):
|
||||
"""Initialize the DiskAdapter.
|
||||
|
||||
:param adapter: The pypowervm adapter.
|
||||
:param host_uuid: The UUID of the PowerVM host.
|
||||
"""
|
||||
self._adapter = adapter
|
||||
self._host_uuid = host_uuid
|
||||
self.mp_uuid = mgmt.mgmt_uuid(self._adapter)
|
||||
|
||||
@abc.abstractproperty
|
||||
def _vios_uuids(self):
|
||||
"""List the UUIDs of the Virtual I/O Servers hosting the storage."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def _disk_match_func(self, disk_type, instance):
|
||||
"""Return a matching function to locate the disk for an instance.
|
||||
|
||||
:param disk_type: One of the DiskType enum values.
|
||||
:param instance: The instance whose disk is to be found.
|
||||
:return: Callable suitable for the match_func parameter of the
|
||||
pypowervm.tasks.scsi_mapper.find_maps method, with the
|
||||
following specification:
|
||||
def match_func(storage_elem)
|
||||
param storage_elem: A backing storage element wrapper (VOpt,
|
||||
VDisk, PV, or LU) to be analyzed.
|
||||
return: True if the storage_elem's mapping should be included;
|
||||
False otherwise.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_bootdisk_path(self, instance, vios_uuid):
|
||||
"""Find the local path for the instance's boot disk.
|
||||
|
||||
:param instance: nova.objects.instance.Instance object owning the
|
||||
requested disk.
|
||||
:param vios_uuid: PowerVM UUID of the VIOS to search for mappings.
|
||||
:return: Local path for instance's boot disk.
|
||||
"""
|
||||
vm_uuid = vm.get_pvm_uuid(instance)
|
||||
match_func = self._disk_match_func(DiskType.BOOT, instance)
|
||||
vios_wrap = pvm_vios.VIOS.get(self._adapter, uuid=vios_uuid,
|
||||
xag=[pvm_const.XAG.VIO_SMAP])
|
||||
maps = tsk_map.find_maps(vios_wrap.scsi_mappings,
|
||||
client_lpar_id=vm_uuid, match_func=match_func)
|
||||
if maps:
|
||||
return maps[0].server_adapter.backing_dev_name
|
||||
return None
|
||||
|
||||
def _get_bootdisk_iter(self, instance):
|
||||
"""Return an iterator of (storage_elem, VIOS) tuples for the instance.
|
||||
|
||||
This method returns an iterator of (storage_elem, VIOS) tuples, where
|
||||
storage_element is a pypowervm storage element wrapper associated with
|
||||
the instance boot disk and VIOS is the wrapper of the Virtual I/O
|
||||
server owning that storage element.
|
||||
|
||||
:param instance: nova.objects.instance.Instance object owning the
|
||||
requested disk.
|
||||
:return: Iterator of tuples of (storage_elem, VIOS).
|
||||
"""
|
||||
lpar_wrap = vm.get_instance_wrapper(self._adapter, instance)
|
||||
match_func = self._disk_match_func(DiskType.BOOT, instance)
|
||||
for vios_uuid in self._vios_uuids:
|
||||
vios_wrap = pvm_vios.VIOS.get(
|
||||
self._adapter, uuid=vios_uuid, xag=[pvm_const.XAG.VIO_SMAP])
|
||||
for scsi_map in tsk_map.find_maps(
|
||||
vios_wrap.scsi_mappings, client_lpar_id=lpar_wrap.id,
|
||||
match_func=match_func):
|
||||
yield scsi_map.backing_storage, vios_wrap
|
||||
|
||||
def connect_instance_disk_to_mgmt(self, instance):
|
||||
"""Connect an instance's boot disk to the management partition.
|
||||
|
||||
:param instance: The instance whose boot disk is to be mapped.
|
||||
:return stg_elem: The storage element (LU, VDisk, etc.) that was mapped
|
||||
:return vios: The EntryWrapper of the VIOS from which the mapping was
|
||||
made.
|
||||
:raise InstanceDiskMappingFailed: If the mapping could not be done.
|
||||
"""
|
||||
for stg_elem, vios in self._get_bootdisk_iter(instance):
|
||||
msg_args = {'disk_name': stg_elem.name, 'vios_name': vios.name}
|
||||
|
||||
# Create a new mapping. NOTE: If there's an existing mapping on
|
||||
# the other VIOS but not this one, we'll create a second mapping
|
||||
# here. It would take an extreme sequence of events to get to that
|
||||
# point, and the second mapping would be harmless anyway. The
|
||||
# alternative would be always checking all VIOSes for existing
|
||||
# mappings, which increases the response time of the common case by
|
||||
# an entire GET of VIOS+VIO_SMAP.
|
||||
LOG.debug("Mapping boot disk %(disk_name)s to the management "
|
||||
"partition from Virtual I/O Server %(vios_name)s.",
|
||||
msg_args, instance=instance)
|
||||
try:
|
||||
tsk_map.add_vscsi_mapping(self._host_uuid, vios, self.mp_uuid,
|
||||
stg_elem)
|
||||
# If that worked, we're done. add_vscsi_mapping logged.
|
||||
return stg_elem, vios
|
||||
except Exception:
|
||||
LOG.exception("Failed to map boot disk %(disk_name)s to the "
|
||||
"management partition from Virtual I/O Server "
|
||||
"%(vios_name)s.", msg_args, instance=instance)
|
||||
# Try the next hit, if available.
|
||||
# We either didn't find the boot dev, or failed all attempts to map it.
|
||||
raise exception.InstanceDiskMappingFailed(instance_name=instance.name)
|
||||
|
||||
@abc.abstractmethod
|
||||
def disconnect_disk_from_mgmt(self, vios_uuid, disk_name):
|
||||
"""Disconnect a disk from the management partition.
|
||||
|
||||
:param vios_uuid: The UUID of the Virtual I/O Server serving the
|
||||
mapping.
|
||||
:param disk_name: The name of the disk to unmap.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractproperty
|
||||
def capacity(self):
|
||||
"""Capacity of the storage in gigabytes.
|
||||
|
||||
Default is to make the capacity arbitrarily large.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractproperty
|
||||
def capacity_used(self):
|
||||
"""Capacity of the storage in gigabytes that is used.
|
||||
|
||||
Default is to say none of it is used.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@staticmethod
|
||||
def _get_disk_name(disk_type, instance, short=False):
|
||||
"""Generate a name for a virtual disk associated with an instance.
|
||||
|
||||
:param disk_type: One of the DiskType enum values.
|
||||
:param instance: The instance for which the disk is to be created.
|
||||
:param short: If True, the generated name will be limited to 15
|
||||
characters (the limit for virtual disk). If False, it
|
||||
will be limited by the API (79 characters currently).
|
||||
:return: The sanitized file name for the disk.
|
||||
"""
|
||||
prefix = '%s_' % (disk_type[0] if short else disk_type)
|
||||
base = ('%s_%s' % (instance.name[:8], instance.uuid[:4]) if short
|
||||
else instance.name)
|
||||
return pvm_u.sanitize_file_name_for_api(
|
||||
base, prefix=prefix, max_len=pvm_const.MaxLen.VDISK_NAME if short
|
||||
else pvm_const.MaxLen.FILENAME_DEFAULT)
|
||||
|
||||
@abc.abstractmethod
|
||||
def detach_disk(self, instance):
|
||||
"""Detaches the storage adapters from the image disk.
|
||||
|
||||
:param instance: instance to detach the image for.
|
||||
:return: A list of all the backing storage elements that were
|
||||
detached from the I/O Server and VM.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_disks(self, storage_elems):
|
||||
"""Removes the disks specified by the mappings.
|
||||
|
||||
:param storage_elems: A list of the storage elements that are to be
|
||||
deleted. Derived from the return value from
|
||||
detach_disk.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_disk_from_image(self, context, instance, image_meta):
|
||||
"""Creates a disk and copies the specified image to it.
|
||||
|
||||
Cleans up created disk if an error occurs.
|
||||
:param context: nova context used to retrieve image from glance
|
||||
:param instance: instance to create the disk for.
|
||||
:param image_meta: nova.objects.ImageMeta object with the metadata of
|
||||
the image of the instance.
|
||||
:return: The backing pypowervm storage object that was created.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def attach_disk(self, instance, disk_info, stg_ftsk):
|
||||
"""Attaches the disk image to the Virtual Machine.
|
||||
|
||||
:param instance: nova instance to attach the disk to.
|
||||
:param disk_info: The pypowervm storage element returned from
|
||||
create_disk_from_image. Ex. VOptMedia, VDisk, LU,
|
||||
or PV.
|
||||
:param stg_ftsk: (Optional) The pypowervm transaction FeedTask for the
|
||||
I/O Operations. If provided, the Virtual I/O Server
|
||||
mapping updates will be added to the FeedTask. This
|
||||
defers the updates to some later point in time. If
|
||||
the FeedTask is not provided, the updates will be run
|
||||
immediately when this method is executed.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
@@ -1,211 +0,0 @@
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# Copyright 2015, 2018 IBM Corp.
|
||||
#
|
||||
# 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 oslo_log.log as logging
|
||||
from pypowervm import const as pvm_const
|
||||
from pypowervm.tasks import scsi_mapper as tsk_map
|
||||
from pypowervm.tasks import storage as tsk_stg
|
||||
from pypowervm.wrappers import storage as pvm_stg
|
||||
from pypowervm.wrappers import virtual_io_server as pvm_vios
|
||||
|
||||
from nova import conf
|
||||
from nova import exception
|
||||
from nova.image import glance
|
||||
from nova.virt.powervm.disk import driver as disk_dvr
|
||||
from nova.virt.powervm import vm
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = conf.CONF
|
||||
IMAGE_API = glance.API()
|
||||
|
||||
|
||||
class LocalStorage(disk_dvr.DiskAdapter):
|
||||
|
||||
def __init__(self, adapter, host_uuid):
|
||||
super(LocalStorage, self).__init__(adapter, host_uuid)
|
||||
|
||||
self.capabilities = {
|
||||
'shared_storage': False,
|
||||
'has_imagecache': False,
|
||||
# NOTE(efried): 'snapshot' capability set dynamically below.
|
||||
}
|
||||
|
||||
# Query to get the Volume Group UUID
|
||||
if not CONF.powervm.volume_group_name:
|
||||
raise exception.OptRequiredIfOtherOptValue(
|
||||
if_opt='disk_driver', if_value='localdisk',
|
||||
then_opt='volume_group_name')
|
||||
self.vg_name = CONF.powervm.volume_group_name
|
||||
vios_w, vg_w = tsk_stg.find_vg(adapter, self.vg_name)
|
||||
self._vios_uuid = vios_w.uuid
|
||||
self.vg_uuid = vg_w.uuid
|
||||
# Set the 'snapshot' capability dynamically. If we're hosting I/O on
|
||||
# the management partition, we can snapshot. If we're hosting I/O on
|
||||
# traditional VIOS, we are limited by the fact that a VSCSI device
|
||||
# can't be mapped to two partitions (the VIOS and the management) at
|
||||
# once.
|
||||
self.capabilities['snapshot'] = self.mp_uuid == self._vios_uuid
|
||||
LOG.info("Local Storage driver initialized: volume group: '%s'",
|
||||
self.vg_name)
|
||||
|
||||
@property
|
||||
def _vios_uuids(self):
|
||||
"""List the UUIDs of the Virtual I/O Servers hosting the storage.
|
||||
|
||||
For localdisk, there's only one.
|
||||
"""
|
||||
return [self._vios_uuid]
|
||||
|
||||
@staticmethod
|
||||
def _disk_match_func(disk_type, instance):
|
||||
"""Return a matching function to locate the disk for an instance.
|
||||
|
||||
:param disk_type: One of the DiskType enum values.
|
||||
:param instance: The instance whose disk is to be found.
|
||||
:return: Callable suitable for the match_func parameter of the
|
||||
pypowervm.tasks.scsi_mapper.find_maps method.
|
||||
"""
|
||||
disk_name = LocalStorage._get_disk_name(
|
||||
disk_type, instance, short=True)
|
||||
return tsk_map.gen_match_func(pvm_stg.VDisk, names=[disk_name])
|
||||
|
||||
@property
|
||||
def capacity(self):
|
||||
"""Capacity of the storage in gigabytes."""
|
||||
vg_wrap = self._get_vg_wrap()
|
||||
return float(vg_wrap.capacity)
|
||||
|
||||
@property
|
||||
def capacity_used(self):
|
||||
"""Capacity of the storage in gigabytes that is used."""
|
||||
vg_wrap = self._get_vg_wrap()
|
||||
# Subtract available from capacity
|
||||
return float(vg_wrap.capacity) - float(vg_wrap.available_size)
|
||||
|
||||
def delete_disks(self, storage_elems):
|
||||
"""Removes the specified disks.
|
||||
|
||||
:param storage_elems: A list of the storage elements that are to be
|
||||
deleted. Derived from the return value from
|
||||
detach_disk.
|
||||
"""
|
||||
# All of localdisk is done against the volume group. So reload
|
||||
# that (to get new etag) and then update against it.
|
||||
tsk_stg.rm_vg_storage(self._get_vg_wrap(), vdisks=storage_elems)
|
||||
|
||||
def detach_disk(self, instance):
|
||||
"""Detaches the storage adapters from the image disk.
|
||||
|
||||
:param instance: Instance to disconnect the image for.
|
||||
:return: A list of all the backing storage elements that were
|
||||
disconnected from the I/O Server and VM.
|
||||
"""
|
||||
lpar_uuid = vm.get_pvm_uuid(instance)
|
||||
|
||||
# Build the match function
|
||||
match_func = tsk_map.gen_match_func(pvm_stg.VDisk)
|
||||
|
||||
vios_w = pvm_vios.VIOS.get(
|
||||
self._adapter, uuid=self._vios_uuid, xag=[pvm_const.XAG.VIO_SMAP])
|
||||
|
||||
# Remove the mappings.
|
||||
mappings = tsk_map.remove_maps(
|
||||
vios_w, lpar_uuid, match_func=match_func)
|
||||
|
||||
# Update the VIOS with the removed mappings.
|
||||
vios_w.update()
|
||||
|
||||
return [x.backing_storage for x in mappings]
|
||||
|
||||
def disconnect_disk_from_mgmt(self, vios_uuid, disk_name):
|
||||
"""Disconnect a disk from the management partition.
|
||||
|
||||
:param vios_uuid: The UUID of the Virtual I/O Server serving the
|
||||
mapping.
|
||||
:param disk_name: The name of the disk to unmap.
|
||||
"""
|
||||
tsk_map.remove_vdisk_mapping(self._adapter, vios_uuid, self.mp_uuid,
|
||||
disk_names=[disk_name])
|
||||
LOG.info("Unmapped boot disk %(disk_name)s from the management "
|
||||
"partition from Virtual I/O Server %(vios_name)s.",
|
||||
{'disk_name': disk_name, 'mp_uuid': self.mp_uuid,
|
||||
'vios_name': vios_uuid})
|
||||
|
||||
def create_disk_from_image(self, context, instance, image_meta):
|
||||
"""Creates a disk and copies the specified image to it.
|
||||
|
||||
Cleans up the created disk if an error occurs.
|
||||
|
||||
:param context: nova context used to retrieve image from glance
|
||||
:param instance: instance to create the disk for.
|
||||
:param image_meta: The metadata of the image of the instance.
|
||||
:return: The backing pypowervm storage object that was created.
|
||||
"""
|
||||
LOG.info('Create disk.', instance=instance)
|
||||
|
||||
return self._upload_image(context, instance, image_meta)
|
||||
|
||||
# TODO(esberglu): Copy vdisk when implementing image cache.
|
||||
|
||||
def _upload_image(self, context, instance, image_meta):
|
||||
"""Upload a new image.
|
||||
|
||||
:param context: Nova context used to retrieve image from glance.
|
||||
:param image_meta: The metadata of the image of the instance.
|
||||
:return: The virtual disk containing the image.
|
||||
"""
|
||||
|
||||
img_name = self._get_disk_name(disk_dvr.DiskType.BOOT, instance,
|
||||
short=True)
|
||||
|
||||
# TODO(esberglu) Add check for cached image when adding imagecache.
|
||||
|
||||
return tsk_stg.upload_new_vdisk(
|
||||
self._adapter, self._vios_uuid, self.vg_uuid,
|
||||
disk_dvr.IterableToFileAdapter(
|
||||
IMAGE_API.download(context, image_meta.id)), img_name,
|
||||
image_meta.size, d_size=image_meta.size,
|
||||
upload_type=tsk_stg.UploadType.IO_STREAM,
|
||||
file_format=image_meta.disk_format)[0]
|
||||
|
||||
def attach_disk(self, instance, disk_info, stg_ftsk):
|
||||
"""Attaches the disk image to the Virtual Machine.
|
||||
|
||||
:param instance: nova instance to connect the disk to.
|
||||
:param disk_info: The pypowervm storage element returned from
|
||||
create_disk_from_image. Ex. VOptMedia, VDisk, LU,
|
||||
or PV.
|
||||
:param stg_ftsk: The pypowervm transaction FeedTask for the
|
||||
I/O Operations. The Virtual I/O Server mapping updates
|
||||
will be added to the FeedTask. This defers the updates
|
||||
to some later point in time.
|
||||
"""
|
||||
lpar_uuid = vm.get_pvm_uuid(instance)
|
||||
|
||||
def add_func(vios_w):
|
||||
LOG.info("Adding logical volume disk connection to VIOS %(vios)s.",
|
||||
{'vios': vios_w.name}, instance=instance)
|
||||
mapping = tsk_map.build_vscsi_mapping(
|
||||
self._host_uuid, vios_w, lpar_uuid, disk_info)
|
||||
return tsk_map.add_map(vios_w, mapping)
|
||||
|
||||
stg_ftsk.wrapper_tasks[self._vios_uuid].add_functor_subtask(add_func)
|
||||
|
||||
def _get_vg_wrap(self):
|
||||
return pvm_stg.VG.get(self._adapter, uuid=self.vg_uuid,
|
||||
parent_type=pvm_vios.VIOS,
|
||||
parent_uuid=self._vios_uuid)
|
||||
@@ -1,258 +0,0 @@
|
||||
# Copyright 2015, 2018 IBM Corp.
|
||||
#
|
||||
# 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 oslo_log.log as logging
|
||||
from pypowervm import const as pvm_const
|
||||
from pypowervm import exceptions as pvm_exc
|
||||
from pypowervm.tasks import cluster_ssp as tsk_cs
|
||||
from pypowervm.tasks import partition as tsk_par
|
||||
from pypowervm.tasks import scsi_mapper as tsk_map
|
||||
from pypowervm.tasks import storage as tsk_stg
|
||||
import pypowervm.util as pvm_u
|
||||
import pypowervm.wrappers.cluster as pvm_clust
|
||||
import pypowervm.wrappers.storage as pvm_stg
|
||||
|
||||
from nova import exception
|
||||
from nova.image import glance
|
||||
from nova.virt.powervm.disk import driver as disk_drv
|
||||
from nova.virt.powervm import vm
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
IMAGE_API = glance.API()
|
||||
|
||||
|
||||
class SSPDiskAdapter(disk_drv.DiskAdapter):
|
||||
"""Provides a disk adapter for Shared Storage Pools.
|
||||
|
||||
Shared Storage Pools are a clustered file system technology that can link
|
||||
together Virtual I/O Servers.
|
||||
|
||||
This adapter provides the connection for nova ephemeral storage (not
|
||||
Cinder) to connect to virtual machines.
|
||||
"""
|
||||
|
||||
capabilities = {
|
||||
'shared_storage': True,
|
||||
# NOTE(efried): Whereas the SSP disk driver definitely does image
|
||||
# caching, it's not through the nova.virt.imagecache.ImageCacheManager
|
||||
# API. Setting `has_imagecache` to True here would have the side
|
||||
# effect of having a periodic task try to call this class's
|
||||
# manage_image_cache method (not implemented here; and a no-op in the
|
||||
# superclass) which would be harmless, but unnecessary.
|
||||
'has_imagecache': False,
|
||||
'snapshot': True,
|
||||
}
|
||||
|
||||
def __init__(self, adapter, host_uuid):
|
||||
"""Initialize the SSPDiskAdapter.
|
||||
|
||||
:param adapter: pypowervm.adapter.Adapter for the PowerVM REST API.
|
||||
:param host_uuid: PowerVM UUID of the managed system.
|
||||
"""
|
||||
super(SSPDiskAdapter, self).__init__(adapter, host_uuid)
|
||||
|
||||
try:
|
||||
self._clust = pvm_clust.Cluster.get(self._adapter)[0]
|
||||
self._ssp = pvm_stg.SSP.get_by_href(
|
||||
self._adapter, self._clust.ssp_uri)
|
||||
self._tier = tsk_stg.default_tier_for_ssp(self._ssp)
|
||||
except pvm_exc.Error:
|
||||
LOG.exception("A unique PowerVM Cluster and Shared Storage Pool "
|
||||
"is required in the default Tier.")
|
||||
raise exception.NotFound()
|
||||
|
||||
LOG.info(
|
||||
"SSP Storage driver initialized. Cluster '%(clust_name)s'; "
|
||||
"SSP '%(ssp_name)s'; Tier '%(tier_name)s'",
|
||||
{'clust_name': self._clust.name, 'ssp_name': self._ssp.name,
|
||||
'tier_name': self._tier.name})
|
||||
|
||||
@property
|
||||
def capacity(self):
|
||||
"""Capacity of the storage in gigabytes."""
|
||||
# Retrieving the Tier is faster (because don't have to refresh LUs.)
|
||||
return float(self._tier.refresh().capacity)
|
||||
|
||||
@property
|
||||
def capacity_used(self):
|
||||
"""Capacity of the storage in gigabytes that is used."""
|
||||
self._ssp = self._ssp.refresh()
|
||||
return float(self._ssp.capacity) - float(self._ssp.free_space)
|
||||
|
||||
def detach_disk(self, instance):
|
||||
"""Detaches the storage adapters from the disk.
|
||||
|
||||
:param instance: instance from which to detach the image.
|
||||
:return: A list of all the backing storage elements that were detached
|
||||
from the I/O Server and VM.
|
||||
"""
|
||||
stg_ftsk = tsk_par.build_active_vio_feed_task(
|
||||
self._adapter, name='ssp', xag=[pvm_const.XAG.VIO_SMAP])
|
||||
|
||||
lpar_uuid = vm.get_pvm_uuid(instance)
|
||||
match_func = tsk_map.gen_match_func(pvm_stg.LU)
|
||||
|
||||
def rm_func(vwrap):
|
||||
LOG.info("Removing SSP disk connection to VIOS %s.",
|
||||
vwrap.name, instance=instance)
|
||||
return tsk_map.remove_maps(vwrap, lpar_uuid,
|
||||
match_func=match_func)
|
||||
|
||||
# Remove the mapping from *each* VIOS on the LPAR's host.
|
||||
# The LPAR's host has to be self._host_uuid, else the PowerVM API will
|
||||
# fail.
|
||||
#
|
||||
# Note - this may not be all the VIOSes on the system...just the ones
|
||||
# in the SSP cluster.
|
||||
#
|
||||
# The mappings will normally be the same on all VIOSes, unless a VIOS
|
||||
# was down when a disk was added. So for the return value, we need to
|
||||
# collect the union of all relevant mappings from all VIOSes.
|
||||
lu_set = set()
|
||||
for vios_uuid in self._vios_uuids:
|
||||
# Add the remove for the VIO
|
||||
stg_ftsk.wrapper_tasks[vios_uuid].add_functor_subtask(rm_func)
|
||||
|
||||
# Find the active LUs so that a delete op knows what to remove.
|
||||
vios_w = stg_ftsk.wrapper_tasks[vios_uuid].wrapper
|
||||
mappings = tsk_map.find_maps(vios_w.scsi_mappings,
|
||||
client_lpar_id=lpar_uuid,
|
||||
match_func=match_func)
|
||||
if mappings:
|
||||
lu_set.update([x.backing_storage for x in mappings])
|
||||
|
||||
stg_ftsk.execute()
|
||||
|
||||
return list(lu_set)
|
||||
|
||||
def delete_disks(self, storage_elems):
|
||||
"""Removes the disks specified by the mappings.
|
||||
|
||||
:param storage_elems: A list of the storage elements (LU
|
||||
ElementWrappers) that are to be deleted. Derived
|
||||
from the return value from detach_disk.
|
||||
"""
|
||||
tsk_stg.rm_tier_storage(storage_elems, tier=self._tier)
|
||||
|
||||
def create_disk_from_image(self, context, instance, image_meta):
|
||||
"""Creates a boot disk and links the specified image to it.
|
||||
|
||||
If the specified image has not already been uploaded, an Image LU is
|
||||
created for it. A Disk LU is then created for the instance and linked
|
||||
to the Image LU.
|
||||
|
||||
:param context: nova context used to retrieve image from glance
|
||||
:param instance: instance to create the disk for.
|
||||
:param nova.objects.ImageMeta image_meta:
|
||||
The metadata of the image of the instance.
|
||||
:return: The backing pypowervm LU storage object that was created.
|
||||
"""
|
||||
LOG.info('SSP: Create boot disk from image %s.', image_meta.id,
|
||||
instance=instance)
|
||||
|
||||
image_lu = tsk_cs.get_or_upload_image_lu(
|
||||
self._tier, pvm_u.sanitize_file_name_for_api(
|
||||
image_meta.name, prefix=disk_drv.DiskType.IMAGE + '_',
|
||||
suffix='_' + image_meta.checksum),
|
||||
random.choice(self._vios_uuids), disk_drv.IterableToFileAdapter(
|
||||
IMAGE_API.download(context, image_meta.id)), image_meta.size,
|
||||
upload_type=tsk_stg.UploadType.IO_STREAM)
|
||||
|
||||
boot_lu_name = pvm_u.sanitize_file_name_for_api(
|
||||
instance.name, prefix=disk_drv.DiskType.BOOT + '_')
|
||||
|
||||
LOG.info('SSP: Disk name is %s', boot_lu_name, instance=instance)
|
||||
|
||||
return tsk_stg.crt_lu(
|
||||
self._tier, boot_lu_name, instance.flavor.root_gb,
|
||||
typ=pvm_stg.LUType.DISK, clone=image_lu)[1]
|
||||
|
||||
def attach_disk(self, instance, disk_info, stg_ftsk):
|
||||
"""Connects the disk image to the Virtual Machine.
|
||||
|
||||
:param instance: nova instance to which to attach the disk.
|
||||
:param disk_info: The pypowervm storage element returned from
|
||||
create_disk_from_image. Ex. VOptMedia, VDisk, LU,
|
||||
or PV.
|
||||
:param stg_ftsk: FeedTask to defer storage connectivity operations.
|
||||
"""
|
||||
# Create the LU structure
|
||||
lu = pvm_stg.LU.bld_ref(self._adapter, disk_info.name, disk_info.udid)
|
||||
lpar_uuid = vm.get_pvm_uuid(instance)
|
||||
|
||||
# This is the delay apply mapping
|
||||
def add_func(vios_w):
|
||||
LOG.info("Attaching SSP disk from VIOS %s.",
|
||||
vios_w.name, instance=instance)
|
||||
mapping = tsk_map.build_vscsi_mapping(
|
||||
self._host_uuid, vios_w, lpar_uuid, lu)
|
||||
return tsk_map.add_map(vios_w, mapping)
|
||||
|
||||
# Add the mapping to *each* VIOS on the LPAR's host.
|
||||
# The LPAR's host has to be self._host_uuid, else the PowerVM API will
|
||||
# fail.
|
||||
#
|
||||
# Note: this may not be all the VIOSes on the system - just the ones
|
||||
# in the SSP cluster.
|
||||
for vios_uuid in self._vios_uuids:
|
||||
stg_ftsk.wrapper_tasks[vios_uuid].add_functor_subtask(add_func)
|
||||
|
||||
@property
|
||||
def _vios_uuids(self):
|
||||
"""List the UUIDs of our cluster's VIOSes on this host.
|
||||
|
||||
(If a VIOS is not on this host, we can't interact with it, even if its
|
||||
URI and therefore its UUID happen to be available in the pypowervm
|
||||
wrapper.)
|
||||
|
||||
:return: A list of VIOS UUID strings.
|
||||
"""
|
||||
ret = []
|
||||
for n in self._clust.nodes:
|
||||
# Skip any nodes that we don't have the VIOS uuid or uri
|
||||
if not (n.vios_uuid and n.vios_uri):
|
||||
continue
|
||||
if self._host_uuid == pvm_u.get_req_path_uuid(
|
||||
n.vios_uri, preserve_case=True, root=True):
|
||||
ret.append(n.vios_uuid)
|
||||
return ret
|
||||
|
||||
def disconnect_disk_from_mgmt(self, vios_uuid, disk_name):
|
||||
"""Disconnect a disk from the management partition.
|
||||
|
||||
:param vios_uuid: The UUID of the Virtual I/O Server serving the
|
||||
mapping.
|
||||
:param disk_name: The name of the disk to unmap.
|
||||
"""
|
||||
tsk_map.remove_lu_mapping(self._adapter, vios_uuid, self.mp_uuid,
|
||||
disk_names=[disk_name])
|
||||
LOG.info("Unmapped boot disk %(disk_name)s from the management "
|
||||
"partition from Virtual I/O Server %(vios_uuid)s.",
|
||||
{'disk_name': disk_name, 'mp_uuid': self.mp_uuid,
|
||||
'vios_uuid': vios_uuid})
|
||||
|
||||
@staticmethod
|
||||
def _disk_match_func(disk_type, instance):
|
||||
"""Return a matching function to locate the disk for an instance.
|
||||
|
||||
:param disk_type: One of the DiskType enum values.
|
||||
:param instance: The instance whose disk is to be found.
|
||||
:return: Callable suitable for the match_func parameter of the
|
||||
pypowervm.tasks.scsi_mapper.find_maps method.
|
||||
"""
|
||||
disk_name = SSPDiskAdapter._get_disk_name(disk_type, instance)
|
||||
return tsk_map.gen_match_func(pvm_stg.LU, names=[disk_name])
|
||||
@@ -1,709 +0,0 @@
|
||||
# Copyright 2014, 2018 IBM Corp.
|
||||
#
|
||||
# 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.
|
||||
"""Connection to PowerVM hypervisor through NovaLink."""
|
||||
|
||||
import os_resource_classes as orc
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
from oslo_utils import importutils
|
||||
from pypowervm import adapter as pvm_apt
|
||||
from pypowervm import const as pvm_const
|
||||
from pypowervm import exceptions as pvm_exc
|
||||
from pypowervm.helpers import log_helper as log_hlp
|
||||
from pypowervm.helpers import vios_busy as vio_hlp
|
||||
from pypowervm.tasks import partition as pvm_par
|
||||
from pypowervm.tasks import storage as pvm_stor
|
||||
from pypowervm.tasks import vterm as pvm_vterm
|
||||
from pypowervm.wrappers import managed_system as pvm_ms
|
||||
from taskflow.patterns import linear_flow as tf_lf
|
||||
|
||||
from nova.compute import task_states
|
||||
from nova import conf as cfg
|
||||
from nova.console import type as console_type
|
||||
from nova import exception as exc
|
||||
from nova.i18n import _
|
||||
from nova.image import glance
|
||||
from nova.virt import configdrive
|
||||
from nova.virt import driver
|
||||
from nova.virt.powervm import host as pvm_host
|
||||
from nova.virt.powervm.tasks import base as tf_base
|
||||
from nova.virt.powervm.tasks import image as tf_img
|
||||
from nova.virt.powervm.tasks import network as tf_net
|
||||
from nova.virt.powervm.tasks import storage as tf_stg
|
||||
from nova.virt.powervm.tasks import vm as tf_vm
|
||||
from nova.virt.powervm import vm
|
||||
from nova.virt.powervm import volume
|
||||
from nova.virt.powervm.volume import fcvscsi
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
DISK_ADPT_NS = 'nova.virt.powervm.disk'
|
||||
DISK_ADPT_MAPPINGS = {
|
||||
'localdisk': 'localdisk.LocalStorage',
|
||||
'ssp': 'ssp.SSPDiskAdapter'
|
||||
}
|
||||
|
||||
|
||||
class PowerVMDriver(driver.ComputeDriver):
|
||||
"""PowerVM NovaLink Implementation of Compute Driver.
|
||||
|
||||
https://wiki.openstack.org/wiki/PowerVM
|
||||
"""
|
||||
|
||||
def __init__(self, virtapi):
|
||||
# NOTE(edmondsw) some of these will be dynamic in future, so putting
|
||||
# capabilities on the instance rather than on the class.
|
||||
self.capabilities = {
|
||||
'has_imagecache': False,
|
||||
'supports_bfv_rescue': False,
|
||||
'supports_evacuate': False,
|
||||
'supports_migrate_to_same_host': False,
|
||||
'supports_attach_interface': True,
|
||||
'supports_device_tagging': False,
|
||||
'supports_tagged_attach_interface': False,
|
||||
'supports_tagged_attach_volume': False,
|
||||
'supports_extend_volume': True,
|
||||
'supports_multiattach': False,
|
||||
'supports_trusted_certs': False,
|
||||
'supports_pcpus': False,
|
||||
'supports_accelerators': False,
|
||||
'supports_vtpm': False,
|
||||
'supports_secure_boot': False,
|
||||
'supports_socket_pci_numa_affinity': False,
|
||||
'supports_remote_managed_ports': False,
|
||||
|
||||
# Supported image types
|
||||
"supports_image_type_aki": False,
|
||||
"supports_image_type_ami": False,
|
||||
"supports_image_type_ari": False,
|
||||
"supports_image_type_iso": False,
|
||||
"supports_image_type_qcow2": False,
|
||||
"supports_image_type_raw": True,
|
||||
"supports_image_type_vdi": False,
|
||||
"supports_image_type_vhd": False,
|
||||
"supports_image_type_vhdx": False,
|
||||
"supports_image_type_vmdk": False,
|
||||
"supports_image_type_ploop": False,
|
||||
}
|
||||
super(PowerVMDriver, self).__init__(virtapi)
|
||||
|
||||
def init_host(self, host):
|
||||
"""Initialize anything that is necessary for the driver to function.
|
||||
|
||||
Includes catching up with currently running VMs on the given host.
|
||||
"""
|
||||
LOG.warning(
|
||||
'The powervm virt 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 maintainers and thus its quality'
|
||||
'can not be ensured. If you are using the driver in production '
|
||||
'please let us know the openstack-discuss mailing list or on IRC'
|
||||
)
|
||||
|
||||
# Build the adapter. May need to attempt the connection multiple times
|
||||
# in case the PowerVM management API service is starting.
|
||||
# TODO(efried): Implement async compute service enable/disable like
|
||||
# I73a34eb6e0ca32d03e54d12a5e066b2ed4f19a61
|
||||
self.adapter = pvm_apt.Adapter(
|
||||
pvm_apt.Session(conn_tries=60),
|
||||
helpers=[log_hlp.log_helper, vio_hlp.vios_busy_retry_helper])
|
||||
# Make sure the Virtual I/O Server(s) are available.
|
||||
pvm_par.validate_vios_ready(self.adapter)
|
||||
self.host_wrapper = pvm_ms.System.get(self.adapter)[0]
|
||||
|
||||
# Do a scrub of the I/O plane to make sure the system is in good shape
|
||||
LOG.info("Clearing stale I/O connections on driver init.")
|
||||
pvm_stor.ComprehensiveScrub(self.adapter).execute()
|
||||
|
||||
# Initialize the disk adapter
|
||||
self.disk_dvr = importutils.import_object_ns(
|
||||
DISK_ADPT_NS, DISK_ADPT_MAPPINGS[CONF.powervm.disk_driver.lower()],
|
||||
self.adapter, self.host_wrapper.uuid)
|
||||
self.image_api = glance.API()
|
||||
|
||||
LOG.info("The PowerVM compute driver has been initialized.")
|
||||
|
||||
@staticmethod
|
||||
def _log_operation(op, instance):
|
||||
"""Log entry point of driver operations."""
|
||||
LOG.info('Operation: %(op)s. Virtual machine display name: '
|
||||
'%(display_name)s, name: %(name)s',
|
||||
{'op': op, 'display_name': instance.display_name,
|
||||
'name': instance.name}, instance=instance)
|
||||
|
||||
def get_info(self, instance, use_cache=True):
|
||||
"""Get the current status of an instance.
|
||||
|
||||
:param instance: nova.objects.instance.Instance object
|
||||
:param use_cache: unused in this driver
|
||||
:returns: An InstanceInfo object.
|
||||
"""
|
||||
return vm.get_vm_info(self.adapter, instance)
|
||||
|
||||
def list_instances(self):
|
||||
"""Return the names of all the instances known to the virt host.
|
||||
|
||||
:return: VM Names as a list.
|
||||
"""
|
||||
return vm.get_lpar_names(self.adapter)
|
||||
|
||||
def get_available_nodes(self, refresh=False):
|
||||
"""Returns nodenames of all nodes managed by the compute service.
|
||||
|
||||
This method is for multi compute-nodes support. If a driver supports
|
||||
multi compute-nodes, this method returns a list of nodenames managed
|
||||
by the service. Otherwise, this method should return
|
||||
[hypervisor_hostname].
|
||||
"""
|
||||
|
||||
return [CONF.host]
|
||||
|
||||
def get_available_resource(self, nodename):
|
||||
"""Retrieve resource information.
|
||||
|
||||
This method is called when nova-compute launches, and as part of a
|
||||
periodic task.
|
||||
|
||||
:param nodename: Node from which the caller wants to get resources.
|
||||
A driver that manages only one node can safely ignore
|
||||
this.
|
||||
:return: Dictionary describing resources.
|
||||
"""
|
||||
# Do this here so it refreshes each time this method is called.
|
||||
self.host_wrapper = pvm_ms.System.get(self.adapter)[0]
|
||||
return self._get_available_resource()
|
||||
|
||||
def _get_available_resource(self):
|
||||
# Get host information
|
||||
data = pvm_host.build_host_resource_from_ms(self.host_wrapper)
|
||||
|
||||
# Add the disk information
|
||||
data["local_gb"] = self.disk_dvr.capacity
|
||||
data["local_gb_used"] = self.disk_dvr.capacity_used
|
||||
|
||||
return data
|
||||
|
||||
def update_provider_tree(self, provider_tree, nodename, allocations=None):
|
||||
"""Update a ProviderTree with current provider and inventory data.
|
||||
|
||||
:param nova.compute.provider_tree.ProviderTree provider_tree:
|
||||
A nova.compute.provider_tree.ProviderTree object representing all
|
||||
the providers in the tree associated with the compute node, and any
|
||||
sharing providers (those with the ``MISC_SHARES_VIA_AGGREGATE``
|
||||
trait) associated via aggregate with any of those providers (but
|
||||
not *their* tree- or aggregate-associated providers), as currently
|
||||
known by placement.
|
||||
:param nodename:
|
||||
String name of the compute node (i.e.
|
||||
ComputeNode.hypervisor_hostname) for which the caller is requesting
|
||||
updated provider information.
|
||||
:param allocations: Currently ignored by this driver.
|
||||
"""
|
||||
# Get (legacy) resource information. Same as get_available_resource,
|
||||
# but we don't need to refresh self.host_wrapper as it was *just*
|
||||
# refreshed by get_available_resource in the resource tracker's
|
||||
# update_available_resource flow.
|
||||
data = self._get_available_resource()
|
||||
|
||||
# NOTE(yikun): If the inv record does not exists, the allocation_ratio
|
||||
# will use the CONF.xxx_allocation_ratio value if xxx_allocation_ratio
|
||||
# is set, and fallback to use the initial_xxx_allocation_ratio
|
||||
# otherwise.
|
||||
inv = provider_tree.data(nodename).inventory
|
||||
ratios = self._get_allocation_ratios(inv)
|
||||
# TODO(efried): Fix these to reflect something like reality
|
||||
cpu_reserved = CONF.reserved_host_cpus
|
||||
mem_reserved = CONF.reserved_host_memory_mb
|
||||
disk_reserved = self._get_reserved_host_disk_gb_from_config()
|
||||
|
||||
inventory = {
|
||||
orc.VCPU: {
|
||||
'total': data['vcpus'],
|
||||
'max_unit': data['vcpus'],
|
||||
'allocation_ratio': ratios[orc.VCPU],
|
||||
'reserved': cpu_reserved,
|
||||
},
|
||||
orc.MEMORY_MB: {
|
||||
'total': data['memory_mb'],
|
||||
'max_unit': data['memory_mb'],
|
||||
'allocation_ratio': ratios[orc.MEMORY_MB],
|
||||
'reserved': mem_reserved,
|
||||
},
|
||||
orc.DISK_GB: {
|
||||
# TODO(efried): Proper DISK_GB sharing when SSP driver in play
|
||||
'total': int(data['local_gb']),
|
||||
'max_unit': int(data['local_gb']),
|
||||
'allocation_ratio': ratios[orc.DISK_GB],
|
||||
'reserved': disk_reserved,
|
||||
},
|
||||
}
|
||||
provider_tree.update_inventory(nodename, inventory)
|
||||
|
||||
def spawn(self, context, instance, image_meta, injected_files,
|
||||
admin_password, allocations, network_info=None,
|
||||
block_device_info=None, power_on=True, accel_info=None):
|
||||
"""Create a new instance/VM/domain on the virtualization platform.
|
||||
|
||||
Once this successfully completes, the instance should be
|
||||
running (power_state.RUNNING).
|
||||
|
||||
If this fails, any partial instance should be completely
|
||||
cleaned up, and the virtualization platform should be in the state
|
||||
that it was before this call began.
|
||||
|
||||
:param context: security context
|
||||
:param instance: nova.objects.instance.Instance
|
||||
This function should use the data there to guide
|
||||
the creation of the new instance.
|
||||
:param nova.objects.ImageMeta image_meta:
|
||||
The metadata of the image of the instance.
|
||||
:param injected_files: User files to inject into instance.
|
||||
:param admin_password: Administrator password to set in instance.
|
||||
:param allocations: Information about resources allocated to the
|
||||
instance via placement, of the form returned by
|
||||
SchedulerReportClient.get_allocations_for_consumer.
|
||||
:param network_info: instance network information
|
||||
:param block_device_info: Information about block devices to be
|
||||
attached to the instance.
|
||||
:param power_on: True if the instance should be powered on, False
|
||||
otherwise
|
||||
"""
|
||||
self._log_operation('spawn', instance)
|
||||
# Define the flow
|
||||
flow_spawn = tf_lf.Flow("spawn")
|
||||
|
||||
# This FeedTask accumulates VIOS storage connection operations to be
|
||||
# run in parallel. Include both SCSI and fibre channel mappings for
|
||||
# the scrubber.
|
||||
stg_ftsk = pvm_par.build_active_vio_feed_task(
|
||||
self.adapter, xag={pvm_const.XAG.VIO_SMAP, pvm_const.XAG.VIO_FMAP})
|
||||
|
||||
flow_spawn.add(tf_vm.Create(
|
||||
self.adapter, self.host_wrapper, instance, stg_ftsk))
|
||||
|
||||
# Create a flow for the IO
|
||||
flow_spawn.add(tf_net.PlugVifs(
|
||||
self.virtapi, self.adapter, instance, network_info))
|
||||
flow_spawn.add(tf_net.PlugMgmtVif(
|
||||
self.adapter, instance))
|
||||
|
||||
# Create the boot image.
|
||||
flow_spawn.add(tf_stg.CreateDiskForImg(
|
||||
self.disk_dvr, context, instance, image_meta))
|
||||
# Connects up the disk to the LPAR
|
||||
flow_spawn.add(tf_stg.AttachDisk(
|
||||
self.disk_dvr, instance, stg_ftsk=stg_ftsk))
|
||||
|
||||
# Extract the block devices.
|
||||
bdms = driver.block_device_info_get_mapping(block_device_info)
|
||||
|
||||
# Determine if there are volumes to connect. If so, add a connection
|
||||
# for each type.
|
||||
for bdm, vol_drv in self._vol_drv_iter(context, instance, bdms,
|
||||
stg_ftsk=stg_ftsk):
|
||||
# Connect the volume. This will update the connection_info.
|
||||
flow_spawn.add(tf_stg.AttachVolume(vol_drv))
|
||||
|
||||
# If the config drive is needed, add those steps. Should be done
|
||||
# after all the other I/O.
|
||||
if configdrive.required_by(instance):
|
||||
flow_spawn.add(tf_stg.CreateAndConnectCfgDrive(
|
||||
self.adapter, instance, injected_files, network_info,
|
||||
stg_ftsk, admin_pass=admin_password))
|
||||
|
||||
# Add the transaction manager flow at the end of the 'I/O
|
||||
# connection' tasks. This will run all the connections in parallel.
|
||||
flow_spawn.add(stg_ftsk)
|
||||
|
||||
# Last step is to power on the system.
|
||||
flow_spawn.add(tf_vm.PowerOn(self.adapter, instance))
|
||||