summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.zuul.yaml19
-rw-r--r--doc/api_samples/os-evacuate/v2.95/server-evacuate-find-host-req.json1
-rw-r--r--doc/api_samples/os-evacuate/v2.95/server-evacuate-req.json3
-rw-r--r--doc/source/admin/availability-zones.rst2
-rw-r--r--doc/source/admin/cpu-topologies.rst91
-rw-r--r--doc/source/admin/live-migration-usage.rst2
-rw-r--r--doc/source/admin/remote-console-access.rst10
-rw-r--r--doc/source/admin/scheduling.rst31
-rw-r--r--doc/source/admin/upgrades.rst20
-rw-r--r--doc/source/contributor/ptl-guide.rst73
-rw-r--r--doc/source/reference/database-migrations.rst12
-rw-r--r--doc/source/reference/isolate-aggregates.rst2
-rw-r--r--mypy-files.txt4
-rw-r--r--nova/api/openstack/compute/rest_api_version_history.rst12
-rw-r--r--nova/compute/manager.py24
-rw-r--r--nova/compute/resource_tracker.py38
-rw-r--r--nova/conductor/manager.py4
-rw-r--r--nova/conf/libvirt.py18
-rw-r--r--nova/conf/scheduler.py43
-rw-r--r--nova/conf/spice.py53
-rw-r--r--nova/db/api/legacy_migrations/README4
-rw-r--r--nova/db/api/legacy_migrations/manage.py20
-rw-r--r--nova/db/api/legacy_migrations/migrate.cfg20
-rw-r--r--nova/db/api/legacy_migrations/versions/067_train.py602
-rw-r--r--nova/db/api/legacy_migrations/versions/068_placeholder.py22
-rw-r--r--nova/db/api/legacy_migrations/versions/069_placeholder.py22
-rw-r--r--nova/db/api/legacy_migrations/versions/070_placeholder.py22
-rw-r--r--nova/db/api/legacy_migrations/versions/071_placeholder.py22
-rw-r--r--nova/db/api/legacy_migrations/versions/072_placeholder.py22
-rw-r--r--nova/db/api/legacy_migrations/versions/073_placeholder.py22
-rw-r--r--nova/db/api/legacy_migrations/versions/074_placeholder.py22
-rw-r--r--nova/db/api/legacy_migrations/versions/075_placeholder.py22
-rw-r--r--nova/db/api/legacy_migrations/versions/076_placeholder.py22
-rw-r--r--nova/db/api/legacy_migrations/versions/077_placeholder.py22
-rw-r--r--nova/db/api/legacy_migrations/versions/078_placeholder.py22
-rw-r--r--nova/db/api/legacy_migrations/versions/079_placeholder.py22
-rw-r--r--nova/db/api/legacy_migrations/versions/080_placeholder.py22
-rw-r--r--nova/db/api/legacy_migrations/versions/081_placeholder.py22
-rw-r--r--nova/db/api/legacy_migrations/versions/082_placeholder.py22
-rw-r--r--nova/db/api/legacy_migrations/versions/083_placeholder.py22
-rw-r--r--nova/db/api/legacy_migrations/versions/084_placeholder.py22
-rw-r--r--nova/db/api/legacy_migrations/versions/085_placeholder.py22
-rw-r--r--nova/db/api/legacy_migrations/versions/086_placeholder.py22
-rw-r--r--nova/db/api/legacy_migrations/versions/087_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/README4
-rw-r--r--nova/db/main/legacy_migrations/__init__.py0
-rw-r--r--nova/db/main/legacy_migrations/manage.py20
-rw-r--r--nova/db/main/legacy_migrations/migrate.cfg20
-rw-r--r--nova/db/main/legacy_migrations/versions/402_train.py1619
-rw-r--r--nova/db/main/legacy_migrations/versions/403_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/versions/404_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/versions/405_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/versions/406_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/versions/407_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/versions/408_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/versions/409_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/versions/410_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/versions/411_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/versions/412_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/versions/413_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/versions/414_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/versions/415_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/versions/416_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/versions/417_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/versions/418_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/versions/419_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/versions/420_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/versions/421_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/versions/422_placeholder.py22
-rw-r--r--nova/db/main/legacy_migrations/versions/__init__.py0
-rw-r--r--nova/db/migration.py77
-rw-r--r--nova/exception.py2
-rw-r--r--nova/filesystem.py59
-rw-r--r--nova/network/neutron.py21
-rw-r--r--nova/objects/service.py23
-rw-r--r--nova/pci/request.py4
-rw-r--r--nova/pci/stats.py2
-rw-r--r--nova/pci/whitelist.py2
-rw-r--r--nova/scheduler/client/report.py2
-rw-r--r--nova/scheduler/filters/__init__.py41
-rw-r--r--nova/scheduler/filters/numa_topology_filter.py40
-rw-r--r--nova/scheduler/filters/pci_passthrough_filter.py33
-rw-r--r--nova/scheduler/manager.py35
-rw-r--r--nova/scheduler/utils.py11
-rw-r--r--nova/scheduler/weights/hypervisor_version.py39
-rw-r--r--nova/test.py7
-rw-r--r--nova/tests/fixtures/__init__.py2
-rw-r--r--nova/tests/fixtures/filesystem.py81
-rw-r--r--nova/tests/functional/libvirt/test_pci_sriov_servers.py41
-rw-r--r--nova/tests/functional/libvirt/test_power_manage.py270
-rw-r--r--nova/tests/functional/regressions/test_bug_1995153.py107
-rw-r--r--nova/tests/functional/test_servers.py38
-rw-r--r--nova/tests/unit/compute/test_compute.py21
-rw-r--r--nova/tests/unit/compute/test_compute_mgr.py51
-rw-r--r--nova/tests/unit/compute/test_resource_tracker.py21
-rw-r--r--nova/tests/unit/compute/test_shelve.py10
-rw-r--r--nova/tests/unit/db/api/test_migrations.py44
-rw-r--r--nova/tests/unit/db/main/test_migrations.py44
-rw-r--r--nova/tests/unit/db/test_migration.py189
-rw-r--r--nova/tests/unit/scheduler/fakes.py2
-rw-r--r--nova/tests/unit/scheduler/test_manager.py36
-rw-r--r--nova/tests/unit/scheduler/weights/test_weights_hypervisor_version.py97
-rw-r--r--nova/tests/unit/test_filesystem.py52
-rw-r--r--nova/tests/unit/virt/libvirt/cpu/__init__.py (renamed from nova/db/api/legacy_migrations/__init__.py)0
-rw-r--r--nova/tests/unit/virt/libvirt/cpu/test_api.py194
-rw-r--r--nova/tests/unit/virt/libvirt/cpu/test_core.py122
-rw-r--r--nova/tests/unit/virt/libvirt/test_config.py26
-rw-r--r--nova/tests/unit/virt/libvirt/test_driver.py95
-rw-r--r--nova/tests/unit/virt/libvirt/test_host.py6
-rw-r--r--nova/tests/unit/virt/test_hardware.py50
-rw-r--r--nova/utils.py47
-rw-r--r--nova/virt/hardware.py19
-rw-r--r--nova/virt/hyperv/driver.py8
-rw-r--r--nova/virt/libvirt/config.py24
-rw-r--r--nova/virt/libvirt/cpu/__init__.py (renamed from nova/db/api/legacy_migrations/versions/__init__.py)0
-rw-r--r--nova/virt/libvirt/cpu/api.py157
-rw-r--r--nova/virt/libvirt/cpu/core.py78
-rw-r--r--nova/virt/libvirt/driver.py39
-rw-r--r--nova/virt/libvirt/event.py7
-rw-r--r--nova/virt/libvirt/host.py8
-rw-r--r--nova/virt/libvirt/utils.py6
-rw-r--r--releasenotes/notes/add-spice-compression-support-e41676f445544e8d.yaml23
-rw-r--r--releasenotes/notes/antelope-prelude-4a99907b00e739f8.yaml51
-rw-r--r--releasenotes/notes/bp-libvirt-cpu-state-mgmt-fbc9c1f9f473003c.yaml18
-rw-r--r--releasenotes/notes/hyperv-experimental-antelope-372e18a05cafc295.yaml6
-rw-r--r--releasenotes/notes/hypervisor-version-weigher-d0bba77e720edafe.yaml20
-rw-r--r--releasenotes/notes/port-binding-removed-shelved-offloaded-f1772a64be007b24.yaml6
-rw-r--r--releasenotes/notes/remove-sqlalchemy-migrate-907c200314884d81.yaml5
-rw-r--r--releasenotes/source/2023.1.rst6
-rw-r--r--releasenotes/source/index.rst1
-rw-r--r--releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po89
-rw-r--r--requirements.txt1
-rw-r--r--tox.ini2
133 files changed, 2476 insertions, 3793 deletions
diff --git a/.zuul.yaml b/.zuul.yaml
index 25d6cc6819..9c41476e68 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -419,6 +419,7 @@
# Added in Yoga.
NOVNC_FROM_PACKAGE: False
NOVA_USE_UNIFIED_LIMITS: True
+ MYSQL_REDUCE_MEMORY: True
devstack_services:
# Disable OVN services
br-ex-tcpdump: false
@@ -608,6 +609,7 @@
GLANCE_USE_IMPORT_WORKFLOW: True
DEVSTACK_PARALLEL: True
GLANCE_LIMIT_IMAGE_SIZE_TOTAL: 2048
+ MYSQL_REDUCE_MEMORY: True
# NOTE(danms): This job is pretty heavy as it is, so we disable some
# services that are not relevant to the nova-glance-ceph scenario
# that this job is intended to validate.
@@ -656,15 +658,6 @@
image_conversion:
output_format: raw
-# TODO(gmann): As per the 2023.1 testing runtime, we need to run at least
-# one job on Focal. This job can be removed as per the future testing
-# runtime (whenever we drop the Ubuntu Focal testing).
-- job:
- name: tempest-integrated-compute-ubuntu-focal
- description: This is integrated compute job testing on Ubuntu Focal(20.04)
- parent: tempest-integrated-compute
- nodeset: openstack-single-node-focal
-
# TODO(gmann): Remove this jobs once all the required services for intergrate
# compute gate (Cinder, Glance, Neutron) by default enable scope and new
# defaults which means all the nova jobs will be tested with new RBAC in
@@ -751,11 +744,9 @@
- ^setup.cfg$
- ^tools/.*$
- ^tox.ini$
- - tempest-integrated-compute-ubuntu-focal:
- irrelevant-files: *policies-irrelevant-files
- tempest-integrated-compute-enforce-scope-new-defaults:
irrelevant-files: *policies-irrelevant-files
- - grenade-skip-level:
+ - grenade-skip-level-always:
irrelevant-files: *policies-irrelevant-files
- nova-grenade-multinode:
irrelevant-files: *policies-irrelevant-files
@@ -788,10 +779,10 @@
- ^(?!nova/network/.*)(?!nova/virt/libvirt/vif.py).*$
- tempest-integrated-compute:
irrelevant-files: *policies-irrelevant-files
- - tempest-integrated-compute-ubuntu-focal:
- irrelevant-files: *policies-irrelevant-files
- tempest-integrated-compute-enforce-scope-new-defaults:
irrelevant-files: *policies-irrelevant-files
+ - grenade-skip-level-always:
+ irrelevant-files: *policies-irrelevant-files
- nova-grenade-multinode:
irrelevant-files: *policies-irrelevant-files
- tempest-ipv6-only:
diff --git a/doc/api_samples/os-evacuate/v2.95/server-evacuate-find-host-req.json b/doc/api_samples/os-evacuate/v2.95/server-evacuate-find-host-req.json
index ae9fb0a67b..8ad929226e 100644
--- a/doc/api_samples/os-evacuate/v2.95/server-evacuate-find-host-req.json
+++ b/doc/api_samples/os-evacuate/v2.95/server-evacuate-find-host-req.json
@@ -1,5 +1,4 @@
{
"evacuate": {
- "targetState": "stopped"
}
}
diff --git a/doc/api_samples/os-evacuate/v2.95/server-evacuate-req.json b/doc/api_samples/os-evacuate/v2.95/server-evacuate-req.json
index a9f809c830..d192892cdc 100644
--- a/doc/api_samples/os-evacuate/v2.95/server-evacuate-req.json
+++ b/doc/api_samples/os-evacuate/v2.95/server-evacuate-req.json
@@ -1,6 +1,5 @@
{
"evacuate": {
- "host": "testHost",
- "targetState": "stopped"
+ "host": "testHost"
}
}
diff --git a/doc/source/admin/availability-zones.rst b/doc/source/admin/availability-zones.rst
index aff8a0ab31..28c4451b60 100644
--- a/doc/source/admin/availability-zones.rst
+++ b/doc/source/admin/availability-zones.rst
@@ -39,7 +39,7 @@ when comparing availability zones and host aggregates:
The use of the default availability zone name in requests can be very
error-prone. Since the user can see the list of availability zones, they
have no way to know whether the default availability zone name (currently
- ``nova``) is provided because an host belongs to an aggregate whose AZ
+ ``nova``) is provided because a host belongs to an aggregate whose AZ
metadata key is set to ``nova``, or because there is at least one host
not belonging to any aggregate. Consequently, it is highly recommended
for users to never ever ask for booting an instance by specifying an
diff --git a/doc/source/admin/cpu-topologies.rst b/doc/source/admin/cpu-topologies.rst
index 9770639c3a..082c88f655 100644
--- a/doc/source/admin/cpu-topologies.rst
+++ b/doc/source/admin/cpu-topologies.rst
@@ -730,6 +730,97 @@ CPU policy, meanwhile, will consume ``VCPU`` inventory.
.. _configure-hyperv-numa:
+Configuring CPU power management for dedicated cores
+----------------------------------------------------
+
+.. versionchanged:: 27.0.0
+
+ This feature was only introduced by the 2023.1 Antelope release
+
+.. important::
+
+ The functionality described below is currently only supported by the
+ libvirt/KVM driver.
+
+For power saving reasons, operators can decide to turn down the power usage of
+CPU cores whether they are in use or not. For obvious reasons, Nova only allows
+to change the power consumption of a dedicated CPU core and not a shared one.
+Accordingly, usage of this feature relies on the reading of
+:oslo.config:option:`compute.cpu_dedicated_set` config option to know which CPU
+cores to handle.
+The main action to enable the power management of dedicated cores is to set
+:oslo.config:option:`libvirt.cpu_power_management` config option to ``True``.
+
+By default, if this option is enabled, Nova will lookup the dedicated cores and
+power them down at the compute service startup. Then, once an instance starts
+by being attached to a dedicated core, this below core will be powered up right
+before the libvirt guest starts. On the other way, once an instance is stopped,
+migrated or deleted, then the corresponding dedicated core will be powered down.
+
+There are two distinct strategies for powering up or down :
+
+- the default is to offline the CPU core and online it when needed.
+- an alternative strategy is to use two distinct CPU governors for the up state
+ and the down state.
+
+The strategy can be chosen using
+:oslo.config:option:`libvirt.cpu_power_management_strategy` config option.
+``cpu_state`` supports the first online/offline strategy, while ``governor``
+sets the alternative strategy.
+We default to turning off the cores as it provides you the best power savings
+while there could be other tools outside Nova to manage the governor, like
+tuned. That being said, we also provide a way to automatically change the
+governors on the fly, as explained below.
+
+If the strategy is set to ``governor``, a couple of config options are provided
+to define which exact CPU govenor to use for each of the up and down states :
+
+- :oslo.config:option:`libvirt.cpu_power_governor_low` will define the governor
+ to use for the powerdown state (defaults to ``powersave``)
+- :oslo.config:option:`libvirt.cpu_power_governor_high` will define the
+ governor to use for the powerup state (defaults to ``performance``)
+
+.. important::
+ This is the responsibility of the operator to ensure that the govenors
+ defined by the configuration options are currently supported by the OS
+ underlying kernel that runs the compute service.
+
+ As a side note, we recommend the ``schedutil`` governor as an alternative for
+ the high-power state (if the kernel supports it) as the CPU frequency is
+ dynamically set based on CPU task states. Other governors may be worth to
+ be tested, including ``conservative`` and ``ondemand`` which are quite a bit
+ more power consuming than ``schedutil`` but more efficient than
+ ``performance``. See `Linux kernel docs`_ for further explanations.
+
+.. _`Linux kernel docs`: https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt
+
+As an example, a ``nova.conf`` part of configuration would look like::
+
+ [compute]
+ cpu_dedicated_set=2-17
+
+ [libvirt]
+ cpu_power_management=True
+ cpu_power_management_strategy=cpu_state
+
+.. warning::
+
+ The CPU core #0 has a special meaning in most of the recent Linux kernels.
+ This is always highly discouraged to use it for CPU pinning but please
+ refrain to have it power managed or you could have surprises if Nova turns
+ it off !
+
+One last important note : you can decide to change the CPU management strategy
+during the compute lifecycle, or you can currently already manage the CPU
+states. For ensuring that Nova can correctly manage the CPU performances, we
+added a couple of checks at startup that refuse to start nova-compute service
+if those arbitrary rules aren't enforced :
+
+- if the operator opts for ``cpu_state`` strategy, then all dedicated CPU
+ governors *MUST* be identical.
+- if they decide using ``governor``, then all dedicated CPU cores *MUST* be
+ online.
+
Configuring Hyper-V compute nodes for instance NUMA policies
------------------------------------------------------------
diff --git a/doc/source/admin/live-migration-usage.rst b/doc/source/admin/live-migration-usage.rst
index 783ab5e27c..32c67c2b0a 100644
--- a/doc/source/admin/live-migration-usage.rst
+++ b/doc/source/admin/live-migration-usage.rst
@@ -102,7 +102,7 @@ Manual selection of the destination host
.. code-block:: console
- $ openstack server migrate d1df1b5a-70c4-4fed-98b7-423362f2c47c --live HostC
+ $ openstack server migrate d1df1b5a-70c4-4fed-98b7-423362f2c47c --live-migration --host HostC
#. Confirm that the instance has been migrated successfully:
diff --git a/doc/source/admin/remote-console-access.rst b/doc/source/admin/remote-console-access.rst
index 015c6522d0..9b28646d27 100644
--- a/doc/source/admin/remote-console-access.rst
+++ b/doc/source/admin/remote-console-access.rst
@@ -366,6 +366,16 @@ Replace ``IP_ADDRESS`` with the IP address from which the proxy is accessible
by the outside world. For example, this may be the management interface IP
address of the controller or the VIP.
+Optionally, the :program:`nova-compute` service supports the following
+additional options to configure compression settings (algorithms and modes)
+for SPICE consoles.
+
+- :oslo.config:option:`spice.image_compression`
+- :oslo.config:option:`spice.jpeg_compression`
+- :oslo.config:option:`spice.zlib_compression`
+- :oslo.config:option:`spice.playback_compression`
+- :oslo.config:option:`spice.streaming_mode`
+
Serial
------
diff --git a/doc/source/admin/scheduling.rst b/doc/source/admin/scheduling.rst
index 9071c92ac9..353514ab55 100644
--- a/doc/source/admin/scheduling.rst
+++ b/doc/source/admin/scheduling.rst
@@ -1049,6 +1049,37 @@ Otherwise, it will fall back to the
more than one value is found for a host in aggregate metadata, the minimum
value will be used.
+``HypervisorVersionWeigher``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 28.0.0 (Bobcat)
+
+Weigh hosts by their relative hypervisor version reported by the virt driver.
+
+While the hypervisor_version filed for all virt drivers is an int,
+each nova virt driver uses a different algorithm to convert the hypervisor-specific
+version sequence into an int. As such the values are not directly comparable between
+hosts with different hypervisors.
+
+For example, the ironic virt driver uses the ironic API micro-version as the hypervisor
+version for a given node. The libvirt driver uses the libvirt version
+i.e. Libvirt `7.1.123` becomes `700100123` vs Ironic `1.82` becomes `1`
+Hyper-V `6.3` becomes `6003`.
+
+If you have a mixed virt driver deployment in the ironic vs non-ironic
+case nothing special needs to be done. ironic nodes are scheduled using custom
+resource classes so ironic flavors will never match non-ironic compute nodes.
+
+If a deployment has multiple non-ironic virt drivers it is recommended to use aggregates
+to group hosts by virt driver. While this is not strictly required, it is
+desirable to avoid bias towards one virt driver.
+see :ref:`filtering_hosts_by_isolating_aggregates` and :ref:`AggregateImagePropertiesIsolation`
+for more information.
+
+The default behavior of the HypervisorVersionWeigher is to select newer hosts.
+If you prefer to invert the behavior set the
+:oslo.config:option:`filter_scheduler.hypervisor_version_weight_multiplier` option
+to a negative number and the weighing has the opposite effect of the default.
Utilization-aware scheduling
----------------------------
diff --git a/doc/source/admin/upgrades.rst b/doc/source/admin/upgrades.rst
index 00a714970b..61fd0cf258 100644
--- a/doc/source/admin/upgrades.rst
+++ b/doc/source/admin/upgrades.rst
@@ -41,21 +41,27 @@ Rolling upgrade process
To reduce downtime, the compute services can be upgraded in a rolling fashion.
It means upgrading a few services at a time. This results in a condition where
both old (N) and new (N+1) nova-compute services co-exist for a certain time
-period. Note that, there is no upgrade of the hypervisor here, this is just
+period (or even N with N+2 upgraded nova-compute services, see below).
+Note that, there is no upgrade of the hypervisor here, this is just
upgrading the nova services. If reduced downtime is not a concern (or lower
complexity is desired), all services may be taken down and restarted at the
same time.
.. important::
- Nova does not currently support the coexistence of N and N+2 or greater
- :program:`nova-compute` or :program:`nova-conductor` services in the same
- deployment. The `nova-conductor`` service will fail to start when a
- ``nova-compute`` service that is older than the previous release (N-2 or
- greater) is detected. Similarly, in a :doc:`deployment with multiple cells
+ As of OpenStack 2023.1 (Antelope), Nova supports the coexistence of N and
+ N-2 (Yoga) :program:`nova-compute` or :program:`nova-conductor` services in
+ the same deployment. The `nova-conductor`` service will fail to start when
+ a ``nova-compute`` service that is older than the support envelope is
+ detected. This varies by release and the support envelope will be explained
+ in the release notes. Similarly, in a :doc:`deployment with multiple cells
</admin/cells>`, neither the super conductor service nor any per-cell
conductor service will start if any other conductor service in the
- deployment is older than the previous release.
+ deployment is older than the N-2 release.
+
+ Releases older than 2023.1 will only support rolling upgrades for a single
+ release difference between :program:`nova-compute` and
+ :program:`nova-conductor` services.
#. Before maintenance window:
diff --git a/doc/source/contributor/ptl-guide.rst b/doc/source/contributor/ptl-guide.rst
index 813f1bc83e..b530b100bc 100644
--- a/doc/source/contributor/ptl-guide.rst
+++ b/doc/source/contributor/ptl-guide.rst
@@ -29,7 +29,11 @@ New PTL
* Get acquainted with the release schedule
- * Example: https://wiki.openstack.org/wiki/Nova/Stein_Release_Schedule
+ * Example: https://releases.openstack.org/antelope/schedule.html
+
+ * Also, note that we usually create a specific wiki page for each cycle like
+ https://wiki.openstack.org/wiki/Nova/2023.1_Release_Schedule but it's
+ preferred to use the main release schedule above.
Project Team Gathering
----------------------
@@ -37,30 +41,34 @@ Project Team Gathering
* Create PTG planning etherpad, retrospective etherpad and alert about it in
nova meeting and dev mailing list
- * Example: https://etherpad.openstack.org/p/nova-ptg-stein
+ * Example: https://etherpad.opendev.org/p/nova-antelope-ptg
* Run sessions at the PTG
-* Have a priorities discussion at the PTG
+* Do a retro of the previous cycle
- * Example: https://etherpad.openstack.org/p/nova-ptg-stein-priorities
+* Make agreement on the agenda for this release, including but not exhaustively:
-* Sign up for group photo at the PTG (if applicable)
+ * Number of review days, for either specs or implementation
+ * Define the Spec approval and Feature freeze dates
+ * Modify the release schedule if needed by adding the new dates.
+ As an example : https://review.opendev.org/c/openstack/releases/+/877094
+
+* Discuss the implications of the `SLURP or non-SLURP`__ current release
-* Open review runways for the cycle
+.. __: https://governance.openstack.org/tc/resolutions/20220210-release-cadence-adjustment.html
+
+* Sign up for group photo at the PTG (if applicable)
- * Example: https://etherpad.openstack.org/p/nova-runways-stein
After PTG
---------
* Send PTG session summaries to the dev mailing list
-* Make sure the cycle priorities spec gets reviewed and merged
-
- * Example: https://specs.openstack.org/openstack/nova-specs/priorities/stein-priorities.html
+* Add `RFE bugs`__ if you have action items that are simple to do but without a owner yet.
-* Run the count-blueprints script daily to gather data for the cycle burndown chart
+.. __: https://bugs.launchpad.net/nova/+bugs?field.tag=rfe
A few weeks before milestone 1
------------------------------
@@ -70,12 +78,13 @@ A few weeks before milestone 1
* Periodically check the series goals others have proposed in the “Set series
goals” link:
- * Example: https://blueprints.launchpad.net/nova/stein/+setgoals
+ * Example: https://blueprints.launchpad.net/nova/antelope/+setgoals
Milestone 1
-----------
-* Do milestone release of nova and python-novaclient (in launchpad only)
+* Do milestone release of nova and python-novaclient (in launchpad only, can be
+ optional)
* This is launchpad bookkeeping only. With the latest release team changes,
projects no longer do milestone releases. See: https://releases.openstack.org/reference/release_models.html#cycle-with-milestones-legacy
@@ -87,6 +96,8 @@ Milestone 1
the minor version to leave room for future stable branch releases
* os-vif
+ * placement
+ * os-traits / os-resource-classes
* Release stable branches of nova
@@ -117,28 +128,26 @@ Summit
* Prepare the on-boarding session materials. Enlist help of others
+* Prepare the operator meet-and-greet session. Enlist help of others
+
A few weeks before milestone 2
------------------------------
* Plan a spec review day (optional)
-* Periodically check the series goals others have proposed in the “Set series
- goals” link:
-
- * Example: https://blueprints.launchpad.net/nova/stein/+setgoals
-
Milestone 2
-----------
-* Spec freeze
+* Spec freeze (if agreed)
-* Release nova and python-novaclient
+* Release nova and python-novaclient (if new features were merged)
* Release other libraries as needed
* Stable branch releases of nova
* For nova, set the launchpad milestone release as “released” with the date
+ (can be optional)
Shortly after spec freeze
-------------------------
@@ -146,7 +155,7 @@ Shortly after spec freeze
* Create a blueprint status etherpad to help track, especially non-priority
blueprint work, to help things get done by Feature Freeze (FF). Example:
- * https://etherpad.openstack.org/p/nova-stein-blueprint-status
+ * https://etherpad.opendev.org/p/nova-antelope-blueprint-status
* Create or review a patch to add the next release’s specs directory so people
can propose specs for next release after spec freeze for current release
@@ -155,13 +164,15 @@ Non-client library release freeze
---------------------------------
* Final release for os-vif
+* Final release for os-traits
+* Final release for os-resource-classes
Milestone 3
-----------
* Feature freeze day
-* Client library freeze, release python-novaclient
+* Client library freeze, release python-novaclient and osc-placement
* Close out all blueprints, including “catch all” blueprints like mox,
versioned notifications
@@ -170,7 +181,7 @@ Milestone 3
* For nova, set the launchpad milestone release as “released” with the date
-* Write the `cycle highlights
+* Start writing the `cycle highlights
<https://docs.openstack.org/project-team-guide/release-management.html#cycle-highlights>`__
Week following milestone 3
@@ -199,7 +210,7 @@ A few weeks before RC
* Make a RC1 todos etherpad and tag bugs as ``<release>-rc-potential`` and keep
track of them, example:
- * https://etherpad.openstack.org/p/nova-stein-rc-potential
+ * https://etherpad.opendev.org/p/nova-antelope-rc-potential
* Go through the bug list and identify any rc-potential bugs and tag them
@@ -242,7 +253,7 @@ RC
* Example: https://review.opendev.org/644412
-* Write the cycle-highlights in marketing-friendly sentences and propose to the
+* Push the cycle-highlights in marketing-friendly sentences and propose to the
openstack/releases repo. Usually based on reno prelude but made more readable
and friendly
@@ -257,11 +268,13 @@ Immediately after RC
* https://wiki.openstack.org/wiki/Nova/ReleaseChecklist
- * Drop old RPC compat code (if there was a RPC major version bump)
+ * Drop old RPC compat code (if there was a RPC major version bump and if
+ agreed on at the PTG)
* Example: https://review.opendev.org/543580
- * Bump the oldest supported compute service version
+ * Bump the oldest supported compute service version (if master branch is now
+ on a non-SLURP version)
* https://review.opendev.org/#/c/738482/
@@ -275,7 +288,9 @@ Immediately after RC
* Set the previous to last series status to “supported”
-* Repeat launchpad steps ^ for python-novaclient
+* Repeat launchpad steps ^ for python-novaclient (optional)
+
+* Repeat launchpad steps ^ for placement
* Register milestones in launchpad for the new cycle based on the new cycle
release schedule
@@ -293,7 +308,7 @@ Immediately after RC
* Create new release wiki:
- * Example: https://wiki.openstack.org/wiki/Nova/Train_Release_Schedule
+ * Example: https://wiki.openstack.org/wiki/Nova/2023.1_Release_Schedule
* Update the contributor guide for the new cycle
diff --git a/doc/source/reference/database-migrations.rst b/doc/source/reference/database-migrations.rst
index add7597e93..ea2b9050d9 100644
--- a/doc/source/reference/database-migrations.rst
+++ b/doc/source/reference/database-migrations.rst
@@ -24,6 +24,10 @@ Schema migrations
The database migration engine was changed from ``sqlalchemy-migrate`` to
``alembic``.
+.. versionchanged:: 27.0.0 (Antelope)
+
+ The legacy ``sqlalchemy-migrate``-based database migrations were removed.
+
The `alembic`__ database migration tool is used to manage schema migrations in
nova. The migration files and related metadata can be found in
``nova/db/api/migrations`` (for the API database) and
@@ -36,10 +40,10 @@ respectively.
.. note::
- There are also legacy migrations provided in the ``legacy_migrations``
- subdirectory for both the API and main databases. These are provided to
- facilitate upgrades from pre-Xena (24.0.0) deployments and will be removed
- in a future release. They should not be modified or extended.
+ There were also legacy migrations provided in the ``legacy_migrations``
+ subdirectory for both the API and main databases. These were provided to
+ facilitate upgrades from pre-Xena (24.0.0) deployments. They were removed
+ in the 27.0.0 (Antelope) release.
The best reference for alembic is the `alembic documentation`__, but a small
example is provided here. You can create the migration either manually or
diff --git a/doc/source/reference/isolate-aggregates.rst b/doc/source/reference/isolate-aggregates.rst
index f5487df912..7b493f4db9 100644
--- a/doc/source/reference/isolate-aggregates.rst
+++ b/doc/source/reference/isolate-aggregates.rst
@@ -13,6 +13,8 @@
License for the specific language governing permissions and limitations
under the License.
+.. _filtering_hosts_by_isolating_aggregates:
+
Filtering hosts by isolating aggregates
=======================================
diff --git a/mypy-files.txt b/mypy-files.txt
index 5a3b9ab339..391ed58d87 100644
--- a/mypy-files.txt
+++ b/mypy-files.txt
@@ -1,6 +1,7 @@
nova/compute/manager.py
nova/compute/pci_placement_translator.py
nova/crypto.py
+nova/filesystem.py
nova/limit/local.py
nova/limit/placement.py
nova/network/neutron.py
@@ -13,6 +14,9 @@ nova/virt/driver.py
nova/virt/hardware.py
nova/virt/libvirt/machine_type_utils.py
nova/virt/libvirt/__init__.py
+nova/virt/libvirt/cpu/__init__.py
+nova/virt/libvirt/cpu/api.py
+nova/virt/libvirt/cpu/core.py
nova/virt/libvirt/driver.py
nova/virt/libvirt/event.py
nova/virt/libvirt/guest.py
diff --git a/nova/api/openstack/compute/rest_api_version_history.rst b/nova/api/openstack/compute/rest_api_version_history.rst
index b34510be5c..c7a2777d3a 100644
--- a/nova/api/openstack/compute/rest_api_version_history.rst
+++ b/nova/api/openstack/compute/rest_api_version_history.rst
@@ -1231,17 +1231,19 @@ image similar to the result of rebuilding an ephemeral disk.
2.94
----------------------
+----
The ``hostname`` parameter to the ``POST /servers`` (create server), ``PUT
/servers/{id}`` (update server) and ``POST /servers/{server_id}/action
(rebuild)`` (rebuild server) APIs is now allowed to be a Fully Qualified Domain
Name (FQDN).
+.. _microversion 2.95:
-2.95
----------------------
+2.95 (Maximum in 2023.1 Antelope)
+---------------------------------
Any evacuated instances will be now stopped at destination. This
-requires minimun compute version 27.0.0 (antelope 2023.1). Operators
-can still use previous microversion for older behavior.
+requires minimun nova release 27.0.0, OpenStack release 2023.1
+Antelope. Operators can still use previous microversion for older
+behavior.
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 952ab3e199..5c42aa4d89 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -3791,9 +3791,21 @@ class ComputeManager(manager.Manager):
try:
compute_node = self._get_compute_info(context, self.host)
scheduled_node = compute_node.hypervisor_hostname
- except exception.ComputeHostNotFound:
+ except exception.ComputeHostNotFound as e:
+ # This means we were asked to rebuild one of our own
+ # instances, or another instance as a target of an
+ # evacuation, but we are unable to find a matching compute
+ # node.
LOG.exception('Failed to get compute_info for %s',
self.host)
+ self._set_migration_status(migration, 'failed')
+ self._notify_instance_rebuild_error(context, instance, e,
+ bdms)
+ raise exception.InstanceFaultRollback(
+ inner_exception=exception.BuildAbortException(
+ instance_uuid=instance.uuid,
+ reason=e.format_message()))
+
else:
scheduled_node = instance.node
@@ -6874,6 +6886,9 @@ class ComputeManager(manager.Manager):
current_power_state = self._get_power_state(instance)
network_info = self.network_api.get_instance_nw_info(context, instance)
+ ports_id = [vif['id'] for vif in network_info]
+ self.network_api.unbind_ports(context, ports_id, detach=False)
+
block_device_info = self._get_instance_block_device_info(context,
instance,
bdms=bdms)
@@ -8913,7 +8928,8 @@ class ComputeManager(manager.Manager):
# in order to be able to track and abort it in the future.
self._waiting_live_migrations[instance.uuid] = (None, None)
try:
- future = self._live_migration_executor.submit(
+ future = nova.utils.pass_context(
+ self._live_migration_executor.submit,
self._do_live_migration, context, dest, instance,
block_migration, migration, migrate_data)
self._waiting_live_migrations[instance.uuid] = (migration, future)
@@ -10197,7 +10213,9 @@ class ComputeManager(manager.Manager):
else:
LOG.debug('Triggering sync for uuid %s', uuid)
self._syncs_in_progress[uuid] = True
- self._sync_power_pool.spawn_n(_sync, db_instance)
+ nova.utils.pass_context(self._sync_power_pool.spawn_n,
+ _sync,
+ db_instance)
def _query_driver_power_state_and_sync(self, context, db_instance):
if db_instance.task_state is not None:
diff --git a/nova/compute/resource_tracker.py b/nova/compute/resource_tracker.py
index 3f911f3708..9ee6670c17 100644
--- a/nova/compute/resource_tracker.py
+++ b/nova/compute/resource_tracker.py
@@ -146,16 +146,20 @@ class ResourceTracker(object):
during the instance build.
"""
if self.disabled(nodename):
- # instance_claim() was called before update_available_resource()
- # (which ensures that a compute node exists for nodename). We
- # shouldn't get here but in case we do, just set the instance's
- # host and nodename attribute (probably incorrect) and return a
- # NoopClaim.
- # TODO(jaypipes): Remove all the disabled junk from the resource
- # tracker. Servicegroup API-level active-checking belongs in the
- # nova-compute manager.
- self._set_instance_host_and_node(instance, nodename)
- return claims.NopClaim()
+ # If we get here, it means we are trying to claim for an instance
+ # that was scheduled to a node that we do not have in our list,
+ # or is in some other way unmanageable by this node. This would
+ # mean that we are unable to account for resources, create
+ # allocations in placement, or do any of the other accounting
+ # necessary for this to work. In the past, this situation was
+ # effectively ignored silently, but in a world where we track
+ # resources with placement and instance assignment to compute nodes
+ # by service, we can no longer be leaky.
+ raise exception.ComputeResourcesUnavailable(
+ ('Attempt to claim resources for instance %(inst)s '
+ 'on unknown node %(node)s failed') % {
+ 'inst': instance.uuid,
+ 'node': nodename})
# sanity checks:
if instance.host:
@@ -280,9 +284,17 @@ class ResourceTracker(object):
context, instance, new_flavor, nodename, move_type)
if self.disabled(nodename):
- # compute_driver doesn't support resource tracking, just
- # generate the migration record and continue the resize:
- return claims.NopClaim(migration=migration)
+ # This means we were asked to accept an incoming migration to a
+ # node that we do not own or track. We really should not get here,
+ # but if we do, we must refuse to continue with the migration
+ # process, since we cannot account for those resources, create
+ # allocations in placement, etc. This has been a silent resource
+ # leak in the past, but it must be a hard failure now.
+ raise exception.ComputeResourcesUnavailable(
+ ('Attempt to claim move resources for instance %(inst)s on '
+ 'unknown node %(node)s failed') % {
+ 'inst': instance.uuid,
+ 'node': 'nodename'})
cn = self.compute_nodes[nodename]
diff --git a/nova/conductor/manager.py b/nova/conductor/manager.py
index 8177519331..4b34b8339c 100644
--- a/nova/conductor/manager.py
+++ b/nova/conductor/manager.py
@@ -2096,8 +2096,8 @@ class ComputeTaskManager:
skipped_host(target_ctxt, host, image_ids)
continue
- fetch_pool.spawn_n(wrap_cache_images, target_ctxt, host,
- image_ids)
+ utils.pass_context(fetch_pool.spawn_n, wrap_cache_images,
+ target_ctxt, host, image_ids)
# Wait until all those things finish
fetch_pool.waitall()
diff --git a/nova/conf/libvirt.py b/nova/conf/libvirt.py
index 16a3f63090..204fe5c4b8 100644
--- a/nova/conf/libvirt.py
+++ b/nova/conf/libvirt.py
@@ -1478,6 +1478,23 @@ Related options:
"""),
]
+libvirt_cpu_mgmt_opts = [
+ cfg.BoolOpt('cpu_power_management',
+ default=False,
+ help='Use libvirt to manage CPU cores performance.'),
+ cfg.StrOpt('cpu_power_management_strategy',
+ choices=['cpu_state', 'governor'],
+ default='cpu_state',
+ help='Tuning strategy to reduce CPU power consumption when '
+ 'unused'),
+ cfg.StrOpt('cpu_power_governor_low',
+ default='powersave',
+ help='Governor to use in order '
+ 'to reduce CPU power consumption'),
+ cfg.StrOpt('cpu_power_governor_high',
+ default='performance',
+ help='Governor to use in order to have best CPU performance'),
+]
ALL_OPTS = list(itertools.chain(
libvirt_general_opts,
@@ -1499,6 +1516,7 @@ ALL_OPTS = list(itertools.chain(
libvirt_volume_nvmeof_opts,
libvirt_pmem_opts,
libvirt_vtpm_opts,
+ libvirt_cpu_mgmt_opts,
))
diff --git a/nova/conf/scheduler.py b/nova/conf/scheduler.py
index c75bd07c5b..c7aa2ad76d 100644
--- a/nova/conf/scheduler.py
+++ b/nova/conf/scheduler.py
@@ -464,6 +464,49 @@ Possible values:
* An integer or float value, where the value corresponds to the multipler
ratio for this weigher.
"""),
+ cfg.FloatOpt("hypervisor_version_weight_multiplier",
+ default=1.0,
+ help="""
+Hypervisor Version weight multiplier ratio.
+
+The multiplier is used for weighting hosts based on the reported
+hypervisor version.
+Negative numbers indicate preferring older hosts,
+the default is to prefer newer hosts to aid with upgrades.
+
+Possible values:
+
+* An integer or float value, where the value corresponds to the multiplier
+ ratio for this weigher.
+
+Example:
+
+* Strongly prefer older hosts
+
+ .. code-block:: ini
+
+ [filter_scheduler]
+ hypervisor_version_weight_multiplier=-1000
+
+
+* Moderately prefer new hosts
+
+ .. code-block:: ini
+
+ [filter_scheduler]
+ hypervisor_version_weight_multiplier=2.5
+
+* Disable weigher influence
+
+ .. code-block:: ini
+
+ [filter_scheduler]
+ hypervisor_version_weight_multiplier=0
+
+Related options:
+
+* ``[filter_scheduler] weight_classes``
+"""),
cfg.FloatOpt("io_ops_weight_multiplier",
default=-1.0,
help="""
diff --git a/nova/conf/spice.py b/nova/conf/spice.py
index 59ed4e80a0..e5854946f1 100644
--- a/nova/conf/spice.py
+++ b/nova/conf/spice.py
@@ -85,6 +85,59 @@ Agent. With the Spice agent installed the following features are enabled:
needing to click inside the console or press keys to release it. The
performance of mouse movement is also improved.
"""),
+ cfg.StrOpt('image_compression',
+ advanced=True,
+ choices=[
+ ('auto_glz', 'enable image compression mode to choose between glz '
+ 'and quic algorithm, based on image properties'),
+ ('auto_lz', 'enable image compression mode to choose between lz '
+ 'and quic algorithm, based on image properties'),
+ ('quic', 'enable image compression based on the SFALIC algorithm'),
+ ('glz', 'enable image compression using lz with history based '
+ 'global dictionary'),
+ ('lz', 'enable image compression with the Lempel-Ziv algorithm'),
+ ('off', 'disable image compression')
+ ],
+ help="""
+Configure the SPICE image compression (lossless).
+"""),
+ cfg.StrOpt('jpeg_compression',
+ advanced=True,
+ choices=[
+ ('auto', 'enable JPEG image compression automatically'),
+ ('never', 'disable JPEG image compression'),
+ ('always', 'enable JPEG image compression')
+ ],
+ help="""
+Configure the SPICE wan image compression (lossy for slow links).
+"""),
+ cfg.StrOpt('zlib_compression',
+ advanced=True,
+ choices=[
+ ('auto', 'enable zlib image compression automatically'),
+ ('never', 'disable zlib image compression'),
+ ('always', 'enable zlib image compression')
+ ],
+ help="""
+Configure the SPICE wan image compression (lossless for slow links).
+"""),
+ cfg.BoolOpt('playback_compression',
+ advanced=True,
+ help="""
+Enable the SPICE audio stream compression (using celt).
+"""),
+ cfg.StrOpt('streaming_mode',
+ advanced=True,
+ choices=[
+ ('filter', 'SPICE server adds additional filters to decide if '
+ 'video streaming should be activated'),
+ ('all', 'any fast-refreshing window can be encoded into a video '
+ 'stream'),
+ ('off', 'no video detection and (lossy) compression is performed')
+ ],
+ help="""
+Configure the SPICE video stream detection and (lossy) compression.
+"""),
cfg.URIOpt('html5proxy_base_url',
default='http://127.0.0.1:6082/spice_auto.html',
help="""
diff --git a/nova/db/api/legacy_migrations/README b/nova/db/api/legacy_migrations/README
deleted file mode 100644
index 6218f8cac4..0000000000
--- a/nova/db/api/legacy_migrations/README
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a database migration repository.
-
-More information at
-http://code.google.com/p/sqlalchemy-migrate/
diff --git a/nova/db/api/legacy_migrations/manage.py b/nova/db/api/legacy_migrations/manage.py
deleted file mode 100644
index 6c2b3842ba..0000000000
--- a/nova/db/api/legacy_migrations/manage.py
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2012 OpenStack Foundation
-#
-# 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 migrate.versioning.shell import main
-
-
-if __name__ == '__main__':
- main(debug='False', repository='.')
diff --git a/nova/db/api/legacy_migrations/migrate.cfg b/nova/db/api/legacy_migrations/migrate.cfg
deleted file mode 100644
index 3e2ccef016..0000000000
--- a/nova/db/api/legacy_migrations/migrate.cfg
+++ /dev/null
@@ -1,20 +0,0 @@
-[db_settings]
-# Used to identify which repository this database is versioned under.
-# You can use the name of your project.
-repository_id=nova_api
-
-# The name of the database table used to track the schema version.
-# This name shouldn't already be used by your project.
-# If this is changed once a database is under version control, you'll need to
-# change the table name in each database too.
-version_table=migrate_version
-
-# When committing a change script, Migrate will attempt to generate the
-# sql for all supported databases; normally, if one of them fails - probably
-# because you don't have that database installed - it is ignored and the
-# commit continues, perhaps ending successfully.
-# Databases in this list MUST compile successfully during a commit, or the
-# entire commit will fail. List the databases your application will actually
-# be using to ensure your updates to that database work properly.
-# This must be a list; example: ['postgres','sqlite']
-required_dbs=[]
diff --git a/nova/db/api/legacy_migrations/versions/067_train.py b/nova/db/api/legacy_migrations/versions/067_train.py
deleted file mode 100644
index 6b82b17e4b..0000000000
--- a/nova/db/api/legacy_migrations/versions/067_train.py
+++ /dev/null
@@ -1,602 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from migrate.changeset.constraint import ForeignKeyConstraint
-from migrate import UniqueConstraint
-import sqlalchemy as sa
-from sqlalchemy import dialects
-
-from nova.db import types
-from nova.objects import keypair
-
-
-def InetSmall():
- return sa.String(length=39).with_variant(
- dialects.postgresql.INET(), 'postgresql'
- )
-
-
-def upgrade(migrate_engine):
- meta = sa.MetaData()
- # NOTE(stephenfin): This is not compatible with SQLAlchemy 2.0 but neither
- # is sqlalchemy-migrate which requires this. We'll remove these migrations
- # when dropping SQLAlchemy < 2.x support
- meta.bind = migrate_engine
-
- cell_mappings = sa.Table('cell_mappings', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('uuid', sa.String(length=36), nullable=False),
- sa.Column('name', sa.String(length=255)),
- sa.Column('transport_url', sa.Text()),
- sa.Column('database_connection', sa.Text()),
- # NOTE(stephenfin): These were originally added by sqlalchemy-migrate
- # which did not generate the constraints
- sa.Column(
- 'disabled', sa.Boolean(create_constraint=False), default=False),
- UniqueConstraint('uuid', name='uniq_cell_mappings0uuid'),
- sa.Index('uuid_idx', 'uuid'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- host_mappings = sa.Table('host_mappings', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('cell_id', sa.Integer, nullable=False),
- sa.Column('host', sa.String(length=255), nullable=False),
- UniqueConstraint(
- 'host', name='uniq_host_mappings0host'),
- sa.Index('host_idx', 'host'),
- ForeignKeyConstraint(
- columns=['cell_id'], refcolumns=[cell_mappings.c.id]),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- instance_mappings = sa.Table('instance_mappings', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('instance_uuid', sa.String(length=36), nullable=False),
- sa.Column('cell_id', sa.Integer, nullable=True),
- sa.Column('project_id', sa.String(length=255), nullable=False),
- # NOTE(stephenfin): These were originally added by sqlalchemy-migrate
- # which did not generate the constraints
- sa.Column(
- 'queued_for_delete', sa.Boolean(create_constraint=False),
- default=False),
- sa.Column('user_id', sa.String(length=255), nullable=True),
- UniqueConstraint(
- 'instance_uuid', name='uniq_instance_mappings0instance_uuid'),
- sa.Index('instance_uuid_idx', 'instance_uuid'),
- sa.Index('project_id_idx', 'project_id'),
- sa.Index(
- 'instance_mappings_user_id_project_id_idx', 'user_id',
- 'project_id'),
- ForeignKeyConstraint(
- columns=['cell_id'], refcolumns=[cell_mappings.c.id]),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- flavors = sa.Table('flavors', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('name', sa.String(length=255), nullable=False),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('memory_mb', sa.Integer, nullable=False),
- sa.Column('vcpus', sa.Integer, nullable=False),
- sa.Column('swap', sa.Integer, nullable=False),
- sa.Column('vcpu_weight', sa.Integer),
- sa.Column('flavorid', sa.String(length=255), nullable=False),
- sa.Column('rxtx_factor', sa.Float),
- sa.Column('root_gb', sa.Integer),
- sa.Column('ephemeral_gb', sa.Integer),
- sa.Column('disabled', sa.Boolean),
- sa.Column('is_public', sa.Boolean),
- sa.Column('description', sa.Text()),
- UniqueConstraint('flavorid', name='uniq_flavors0flavorid'),
- UniqueConstraint('name', name='uniq_flavors0name'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- flavor_extra_specs = sa.Table('flavor_extra_specs', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('flavor_id', sa.Integer, nullable=False),
- sa.Column('key', sa.String(length=255), nullable=False),
- sa.Column('value', sa.String(length=255)),
- UniqueConstraint(
- 'flavor_id', 'key', name='uniq_flavor_extra_specs0flavor_id0key'),
- sa.Index('flavor_extra_specs_flavor_id_key_idx', 'flavor_id', 'key'),
- ForeignKeyConstraint(columns=['flavor_id'], refcolumns=[flavors.c.id]),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- flavor_projects = sa.Table('flavor_projects', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('flavor_id', sa.Integer, nullable=False),
- sa.Column('project_id', sa.String(length=255), nullable=False),
- UniqueConstraint(
- 'flavor_id', 'project_id',
- name='uniq_flavor_projects0flavor_id0project_id'),
- ForeignKeyConstraint(
- columns=['flavor_id'], refcolumns=[flavors.c.id]),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- request_specs = sa.Table('request_specs', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('instance_uuid', sa.String(36), nullable=False),
- sa.Column('spec', types.MediumText(), nullable=False),
- UniqueConstraint(
- 'instance_uuid', name='uniq_request_specs0instance_uuid'),
- sa.Index('request_spec_instance_uuid_idx', 'instance_uuid'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- build_requests = sa.Table('build_requests', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('request_spec_id', sa.Integer, nullable=True),
- sa.Column('project_id', sa.String(length=255), nullable=False),
- sa.Column('user_id', sa.String(length=255), nullable=True),
- sa.Column('display_name', sa.String(length=255)),
- sa.Column('instance_metadata', sa.Text),
- sa.Column('progress', sa.Integer),
- sa.Column('vm_state', sa.String(length=255)),
- sa.Column('task_state', sa.String(length=255)),
- sa.Column('image_ref', sa.String(length=255)),
- sa.Column('access_ip_v4', InetSmall()),
- sa.Column('access_ip_v6', InetSmall()),
- sa.Column('info_cache', sa.Text),
- sa.Column('security_groups', sa.Text, nullable=True),
- sa.Column('config_drive', sa.Boolean, default=False, nullable=True),
- sa.Column('key_name', sa.String(length=255)),
- sa.Column(
- 'locked_by',
- sa.Enum('owner', 'admin', name='build_requests0locked_by')),
- sa.Column('instance_uuid', sa.String(length=36)),
- sa.Column('instance', types.MediumText()),
- sa.Column('block_device_mappings', types.MediumText()),
- sa.Column('tags', sa.Text()),
- UniqueConstraint(
- 'instance_uuid', name='uniq_build_requests0instance_uuid'),
- sa.Index('build_requests_project_id_idx', 'project_id'),
- sa.Index('build_requests_instance_uuid_idx', 'instance_uuid'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- keypairs = sa.Table('key_pairs', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('name', sa.String(255), nullable=False),
- sa.Column('user_id', sa.String(255), nullable=False),
- sa.Column('fingerprint', sa.String(255)),
- sa.Column('public_key', sa.Text()),
- sa.Column(
- 'type',
- sa.Enum('ssh', 'x509', metadata=meta, name='keypair_types'),
- nullable=False, server_default=keypair.KEYPAIR_TYPE_SSH),
- UniqueConstraint(
- 'user_id', 'name', name='uniq_key_pairs0user_id0name'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- projects = sa.Table('projects', meta,
- sa.Column(
- 'id', sa.Integer, primary_key=True, nullable=False,
- autoincrement=True),
- sa.Column('external_id', sa.String(length=255), nullable=False),
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- UniqueConstraint('external_id', name='uniq_projects0external_id'),
- mysql_engine='InnoDB',
- mysql_charset='latin1',
- )
-
- users = sa.Table('users', meta,
- sa.Column(
- 'id', sa.Integer, primary_key=True, nullable=False,
- autoincrement=True),
- sa.Column('external_id', sa.String(length=255), nullable=False),
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- UniqueConstraint('external_id', name='uniq_users0external_id'),
- mysql_engine='InnoDB',
- mysql_charset='latin1',
- )
-
- resource_classes = sa.Table('resource_classes', meta,
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('name', sa.String(length=255), nullable=False),
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- UniqueConstraint('name', name='uniq_resource_classes0name'),
- mysql_engine='InnoDB',
- mysql_charset='latin1'
- )
-
- nameargs = {}
- if migrate_engine.name == 'mysql':
- nameargs['collation'] = 'utf8_bin'
-
- resource_providers = sa.Table(
- 'resource_providers', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('uuid', sa.String(36), nullable=False),
- sa.Column('name', sa.Unicode(200, **nameargs), nullable=True),
- sa.Column('generation', sa.Integer, default=0),
- sa.Column('can_host', sa.Integer, default=0),
- sa.Column(
- 'root_provider_id', sa.Integer,
- sa.ForeignKey('resource_providers.id')),
- sa.Column(
- 'parent_provider_id', sa.Integer,
- sa.ForeignKey('resource_providers.id')),
- UniqueConstraint('uuid', name='uniq_resource_providers0uuid'),
- UniqueConstraint('name', name='uniq_resource_providers0name'),
- sa.Index('resource_providers_name_idx', 'name'),
- sa.Index('resource_providers_uuid_idx', 'uuid'),
- sa.Index(
- 'resource_providers_root_provider_id_idx', 'root_provider_id'),
- sa.Index(
- 'resource_providers_parent_provider_id_idx', 'parent_provider_id'),
- mysql_engine='InnoDB',
- mysql_charset='latin1'
- )
-
- inventories = sa.Table(
- 'inventories', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('resource_provider_id', sa.Integer, nullable=False),
- sa.Column('resource_class_id', sa.Integer, nullable=False),
- sa.Column('total', sa.Integer, nullable=False),
- sa.Column('reserved', sa.Integer, nullable=False),
- sa.Column('min_unit', sa.Integer, nullable=False),
- sa.Column('max_unit', sa.Integer, nullable=False),
- sa.Column('step_size', sa.Integer, nullable=False),
- sa.Column('allocation_ratio', sa.Float, nullable=False),
- sa.Index(
- 'inventories_resource_provider_id_idx', 'resource_provider_id'),
- sa.Index(
- 'inventories_resource_provider_resource_class_idx',
- 'resource_provider_id', 'resource_class_id'),
- sa.Index(
- 'inventories_resource_class_id_idx', 'resource_class_id'),
- UniqueConstraint(
- 'resource_provider_id', 'resource_class_id',
- name='uniq_inventories0resource_provider_resource_class'),
- mysql_engine='InnoDB',
- mysql_charset='latin1'
- )
-
- traits = sa.Table(
- 'traits', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column(
- 'id', sa.Integer, primary_key=True, nullable=False,
- autoincrement=True),
- sa.Column('name', sa.Unicode(255, **nameargs), nullable=False),
- UniqueConstraint('name', name='uniq_traits0name'),
- mysql_engine='InnoDB',
- mysql_charset='latin1',
- )
-
- allocations = sa.Table(
- 'allocations', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('resource_provider_id', sa.Integer, nullable=False),
- sa.Column('consumer_id', sa.String(36), nullable=False),
- sa.Column('resource_class_id', sa.Integer, nullable=False),
- sa.Column('used', sa.Integer, nullable=False),
- sa.Index(
- 'allocations_resource_provider_class_used_idx',
- 'resource_provider_id', 'resource_class_id', 'used'),
- sa.Index(
- 'allocations_resource_class_id_idx', 'resource_class_id'),
- sa.Index('allocations_consumer_id_idx', 'consumer_id'),
- mysql_engine='InnoDB',
- mysql_charset='latin1'
- )
-
- consumers = sa.Table(
- 'consumers', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column(
- 'id', sa.Integer, primary_key=True, nullable=False,
- autoincrement=True),
- sa.Column('uuid', sa.String(length=36), nullable=False),
- sa.Column('project_id', sa.Integer, nullable=False),
- sa.Column('user_id', sa.Integer, nullable=False),
- sa.Column(
- 'generation', sa.Integer, default=0, server_default=sa.text('0'),
- nullable=False),
- sa.Index('consumers_project_id_uuid_idx', 'project_id', 'uuid'),
- sa.Index(
- 'consumers_project_id_user_id_uuid_idx', 'project_id', 'user_id',
- 'uuid'),
- UniqueConstraint('uuid', name='uniq_consumers0uuid'),
- mysql_engine='InnoDB',
- mysql_charset='latin1',
- )
-
- resource_provider_aggregates = sa.Table(
- 'resource_provider_aggregates', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column(
- 'resource_provider_id', sa.Integer, primary_key=True,
- nullable=False),
- sa.Column(
- 'aggregate_id', sa.Integer, primary_key=True, nullable=False),
- sa.Index(
- 'resource_provider_aggregates_aggregate_id_idx', 'aggregate_id'),
- mysql_engine='InnoDB',
- mysql_charset='latin1'
- )
-
- resource_provider_traits = sa.Table(
- 'resource_provider_traits', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column(
- 'trait_id', sa.Integer, sa.ForeignKey('traits.id'),
- primary_key=True, nullable=False),
- sa.Column(
- 'resource_provider_id', sa.Integer, primary_key=True,
- nullable=False),
- sa.Index(
- 'resource_provider_traits_resource_provider_trait_idx',
- 'resource_provider_id', 'trait_id'),
- ForeignKeyConstraint(
- columns=['resource_provider_id'],
- refcolumns=[resource_providers.c.id]),
- mysql_engine='InnoDB',
- mysql_charset='latin1',
- )
-
- placement_aggregates = sa.Table('placement_aggregates', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('uuid', sa.String(length=36), index=True),
- UniqueConstraint('uuid', name='uniq_placement_aggregates0uuid'),
- mysql_engine='InnoDB',
- mysql_charset='latin1'
- )
-
- aggregates = sa.Table('aggregates', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('uuid', sa.String(length=36)),
- sa.Column('name', sa.String(length=255)),
- sa.Index('aggregate_uuid_idx', 'uuid'),
- UniqueConstraint('name', name='uniq_aggregate0name'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- aggregate_hosts = sa.Table('aggregate_hosts', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('host', sa.String(length=255)),
- sa.Column(
- 'aggregate_id', sa.Integer, sa.ForeignKey('aggregates.id'),
- nullable=False),
- UniqueConstraint(
- 'host', 'aggregate_id',
- name='uniq_aggregate_hosts0host0aggregate_id'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- aggregate_metadata = sa.Table('aggregate_metadata', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column(
- 'aggregate_id', sa.Integer, sa.ForeignKey('aggregates.id'),
- nullable=False),
- sa.Column('key', sa.String(length=255), nullable=False),
- sa.Column('value', sa.String(length=255), nullable=False),
- UniqueConstraint(
- 'aggregate_id', 'key',
- name='uniq_aggregate_metadata0aggregate_id0key'),
- sa.Index('aggregate_metadata_key_idx', 'key'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- groups = sa.Table('instance_groups', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('user_id', sa.String(length=255)),
- sa.Column('project_id', sa.String(length=255)),
- sa.Column('uuid', sa.String(length=36), nullable=False),
- sa.Column('name', sa.String(length=255)),
- UniqueConstraint(
- 'uuid', name='uniq_instance_groups0uuid'),
- mysql_engine='InnoDB',
- mysql_charset='utf8',
- )
-
- group_policy = sa.Table('instance_group_policy', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('policy', sa.String(length=255)),
- sa.Column(
- 'group_id', sa.Integer, sa.ForeignKey('instance_groups.id'),
- nullable=False),
- sa.Column('rules', sa.Text),
- sa.Index('instance_group_policy_policy_idx', 'policy'),
- mysql_engine='InnoDB',
- mysql_charset='utf8',
- )
-
- group_member = sa.Table('instance_group_member', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('instance_uuid', sa.String(length=255)),
- sa.Column(
- 'group_id', sa.Integer, sa.ForeignKey('instance_groups.id'),
- nullable=False),
- sa.Index('instance_group_member_instance_idx', 'instance_uuid'),
- mysql_engine='InnoDB',
- mysql_charset='utf8',
- )
-
- quota_classes = sa.Table('quota_classes', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('class_name', sa.String(length=255)),
- sa.Column('resource', sa.String(length=255)),
- sa.Column('hard_limit', sa.Integer),
- sa.Index('quota_classes_class_name_idx', 'class_name'),
- mysql_engine='InnoDB',
- mysql_charset='utf8',
- )
-
- quota_usages = sa.Table('quota_usages', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('project_id', sa.String(length=255)),
- sa.Column('resource', sa.String(length=255), nullable=False),
- sa.Column('in_use', sa.Integer, nullable=False),
- sa.Column('reserved', sa.Integer, nullable=False),
- sa.Column('until_refresh', sa.Integer),
- sa.Column('user_id', sa.String(length=255)),
- sa.Index('quota_usages_project_id_idx', 'project_id'),
- sa.Index('quota_usages_user_id_idx', 'user_id'),
- mysql_engine='InnoDB',
- mysql_charset='utf8',
- )
-
- quotas = sa.Table('quotas', meta,
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('project_id', sa.String(length=255)),
- sa.Column('resource', sa.String(length=255), nullable=False),
- sa.Column('hard_limit', sa.Integer),
- UniqueConstraint(
- 'project_id', 'resource', name='uniq_quotas0project_id0resource'),
- mysql_engine='InnoDB',
- mysql_charset='utf8',
- )
-
- project_user_quotas = sa.Table('project_user_quotas', meta,
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('user_id', sa.String(length=255), nullable=False),
- sa.Column('project_id', sa.String(length=255), nullable=False),
- sa.Column('resource', sa.String(length=255), nullable=False),
- sa.Column('hard_limit', sa.Integer, nullable=True),
- UniqueConstraint(
- 'user_id', 'project_id', 'resource',
- name='uniq_project_user_quotas0user_id0project_id0resource'),
- sa.Index(
- 'project_user_quotas_project_id_idx', 'project_id'),
- sa.Index(
- 'project_user_quotas_user_id_idx', 'user_id'),
- mysql_engine='InnoDB',
- mysql_charset='utf8',
- )
-
- reservations = sa.Table('reservations', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('uuid', sa.String(length=36), nullable=False),
- sa.Column(
- 'usage_id', sa.Integer, sa.ForeignKey('quota_usages.id'),
- nullable=False),
- sa.Column('project_id', sa.String(length=255)),
- sa.Column('resource', sa.String(length=255)),
- sa.Column('delta', sa.Integer, nullable=False),
- sa.Column('expire', sa.DateTime),
- sa.Column('user_id', sa.String(length=255)),
- sa.Index('reservations_project_id_idx', 'project_id'),
- sa.Index('reservations_uuid_idx', 'uuid'),
- sa.Index('reservations_expire_idx', 'expire'),
- sa.Index('reservations_user_id_idx', 'user_id'),
- mysql_engine='InnoDB',
- mysql_charset='utf8',
- )
-
- tables = [
- cell_mappings,
- host_mappings,
- instance_mappings,
- flavors,
- flavor_extra_specs,
- flavor_projects,
- request_specs,
- build_requests,
- keypairs,
- projects,
- users,
- resource_classes,
- resource_providers,
- inventories,
- traits,
- allocations,
- consumers,
- resource_provider_aggregates,
- resource_provider_traits,
- placement_aggregates,
- aggregates,
- aggregate_hosts,
- aggregate_metadata,
- groups,
- group_policy,
- group_member,
- quota_classes,
- quota_usages,
- quotas,
- project_user_quotas,
- reservations,
- ]
- for table in tables:
- table.create(checkfirst=True)
diff --git a/nova/db/api/legacy_migrations/versions/068_placeholder.py b/nova/db/api/legacy_migrations/versions/068_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/api/legacy_migrations/versions/068_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/api/legacy_migrations/versions/069_placeholder.py b/nova/db/api/legacy_migrations/versions/069_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/api/legacy_migrations/versions/069_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/api/legacy_migrations/versions/070_placeholder.py b/nova/db/api/legacy_migrations/versions/070_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/api/legacy_migrations/versions/070_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/api/legacy_migrations/versions/071_placeholder.py b/nova/db/api/legacy_migrations/versions/071_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/api/legacy_migrations/versions/071_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/api/legacy_migrations/versions/072_placeholder.py b/nova/db/api/legacy_migrations/versions/072_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/api/legacy_migrations/versions/072_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/api/legacy_migrations/versions/073_placeholder.py b/nova/db/api/legacy_migrations/versions/073_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/api/legacy_migrations/versions/073_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/api/legacy_migrations/versions/074_placeholder.py b/nova/db/api/legacy_migrations/versions/074_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/api/legacy_migrations/versions/074_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/api/legacy_migrations/versions/075_placeholder.py b/nova/db/api/legacy_migrations/versions/075_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/api/legacy_migrations/versions/075_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/api/legacy_migrations/versions/076_placeholder.py b/nova/db/api/legacy_migrations/versions/076_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/api/legacy_migrations/versions/076_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/api/legacy_migrations/versions/077_placeholder.py b/nova/db/api/legacy_migrations/versions/077_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/api/legacy_migrations/versions/077_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/api/legacy_migrations/versions/078_placeholder.py b/nova/db/api/legacy_migrations/versions/078_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/api/legacy_migrations/versions/078_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/api/legacy_migrations/versions/079_placeholder.py b/nova/db/api/legacy_migrations/versions/079_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/api/legacy_migrations/versions/079_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/api/legacy_migrations/versions/080_placeholder.py b/nova/db/api/legacy_migrations/versions/080_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/api/legacy_migrations/versions/080_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/api/legacy_migrations/versions/081_placeholder.py b/nova/db/api/legacy_migrations/versions/081_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/api/legacy_migrations/versions/081_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/api/legacy_migrations/versions/082_placeholder.py b/nova/db/api/legacy_migrations/versions/082_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/api/legacy_migrations/versions/082_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/api/legacy_migrations/versions/083_placeholder.py b/nova/db/api/legacy_migrations/versions/083_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/api/legacy_migrations/versions/083_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/api/legacy_migrations/versions/084_placeholder.py b/nova/db/api/legacy_migrations/versions/084_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/api/legacy_migrations/versions/084_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/api/legacy_migrations/versions/085_placeholder.py b/nova/db/api/legacy_migrations/versions/085_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/api/legacy_migrations/versions/085_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/api/legacy_migrations/versions/086_placeholder.py b/nova/db/api/legacy_migrations/versions/086_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/api/legacy_migrations/versions/086_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/api/legacy_migrations/versions/087_placeholder.py b/nova/db/api/legacy_migrations/versions/087_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/api/legacy_migrations/versions/087_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/README b/nova/db/main/legacy_migrations/README
deleted file mode 100644
index c5f51f2280..0000000000
--- a/nova/db/main/legacy_migrations/README
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a database migration repository.
-
-More information at
-https://sqlalchemy-migrate.readthedocs.io/en/latest/
diff --git a/nova/db/main/legacy_migrations/__init__.py b/nova/db/main/legacy_migrations/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
--- a/nova/db/main/legacy_migrations/__init__.py
+++ /dev/null
diff --git a/nova/db/main/legacy_migrations/manage.py b/nova/db/main/legacy_migrations/manage.py
deleted file mode 100644
index 6c2b3842ba..0000000000
--- a/nova/db/main/legacy_migrations/manage.py
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2012 OpenStack Foundation
-#
-# 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 migrate.versioning.shell import main
-
-
-if __name__ == '__main__':
- main(debug='False', repository='.')
diff --git a/nova/db/main/legacy_migrations/migrate.cfg b/nova/db/main/legacy_migrations/migrate.cfg
deleted file mode 100644
index 006e01e406..0000000000
--- a/nova/db/main/legacy_migrations/migrate.cfg
+++ /dev/null
@@ -1,20 +0,0 @@
-[db_settings]
-# Used to identify which repository this database is versioned under.
-# You can use the name of your project.
-repository_id=nova
-
-# The name of the database table used to track the schema version.
-# This name shouldn't already be used by your project.
-# If this is changed once a database is under version control, you'll need to
-# change the table name in each database too.
-version_table=migrate_version
-
-# When committing a change script, Migrate will attempt to generate the
-# sql for all supported databases; normally, if one of them fails - probably
-# because you don't have that database installed - it is ignored and the
-# commit continues, perhaps ending successfully.
-# Databases in this list MUST compile successfully during a commit, or the
-# entire commit will fail. List the databases your application will actually
-# be using to ensure your updates to that database work properly.
-# This must be a list; example: ['postgres','sqlite']
-required_dbs=[]
diff --git a/nova/db/main/legacy_migrations/versions/402_train.py b/nova/db/main/legacy_migrations/versions/402_train.py
deleted file mode 100644
index 5a39d87f8c..0000000000
--- a/nova/db/main/legacy_migrations/versions/402_train.py
+++ /dev/null
@@ -1,1619 +0,0 @@
-# Copyright 2012 OpenStack Foundation
-#
-# 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 migrate.changeset import UniqueConstraint
-from oslo_log import log as logging
-import sqlalchemy as sa
-from sqlalchemy import dialects
-from sqlalchemy.ext import compiler
-from sqlalchemy import types as sqla_types
-
-from nova.db import types
-from nova.objects import keypair
-
-LOG = logging.getLogger(__name__)
-
-
-def Inet():
- return sa.String(length=43).with_variant(
- dialects.postgresql.INET(), 'postgresql',
- )
-
-
-def InetSmall():
- return sa.String(length=39).with_variant(
- dialects.postgresql.INET(), 'postgresql',
- )
-
-
-# We explicitly name many of our foreignkeys for MySQL so they match Havana
-@compiler.compiles(sa.ForeignKeyConstraint, 'postgresql')
-def process(element, compiler, **kw):
- element.name = None
- return compiler.visit_foreign_key_constraint(element, **kw)
-
-
-def _create_shadow_tables(migrate_engine):
- meta = sa.MetaData()
- meta.reflect(migrate_engine)
- table_names = list(meta.tables.keys())
-
- # NOTE(stephenfin): This is not compatible with SQLAlchemy 2.0 but neither
- # is sqlalchemy-migrate which requires this. We'll remove these migrations
- # when dropping SQLAlchemy < 2.x support
- meta.bind = migrate_engine
-
- for table_name in table_names:
- # Skip tables that are not soft-deletable
- if table_name in (
- 'tags',
- 'resource_providers',
- 'inventories',
- 'allocations',
- 'resource_provider_aggregates',
- 'console_auth_tokens',
- ):
- continue
-
- table = sa.Table(table_name, meta, autoload_with=migrate_engine)
-
- columns = []
- for column in table.columns:
- column_copy = None
-
- # NOTE(boris-42): BigInteger is not supported by sqlite, so after
- # copy it will have NullType. The other types that are used in Nova
- # are supported by sqlite
- if isinstance(column.type, sqla_types.NullType):
- column_copy = sa.Column(
- column.name, sa.BigInteger(), default=0,
- )
-
- if table_name == 'instances' and column.name == 'locked_by':
- enum = sa.Enum(
- 'owner', 'admin', name='shadow_instances0locked_by',
- )
- column_copy = sa.Column(column.name, enum)
-
- # TODO(stephenfin): Fix these various bugs in a follow-up
-
- # 244_increase_user_id_length_volume_usage_cache; this
- # alteration should apply to shadow tables also
-
- if table_name == 'volume_usage_cache' and column.name == 'user_id':
- # nullable should be True
- column_copy = sa.Column('user_id', sa.String(36))
-
- # 247_nullable_mismatch; these alterations should apply to shadow
- # tables also
-
- if table_name == 'quota_usages' and column.name == 'resources':
- # nullable should be False
- column_copy = sa.Column('resource', sa.String(length=255))
-
- if table_name == 'pci_devices':
- if column.name == 'deleted':
- # nullable should be True
- column_copy = sa.Column(
- 'deleted', sa.Integer, default=0, nullable=False,
- )
-
- if column.name == 'product_id':
- # nullable should be False
- column_copy = sa.Column('product_id', sa.String(4))
-
- if column.name == 'vendor_id':
- # nullable should be False
- column_copy = sa.Column('vendor_id', sa.String(4))
-
- if column.name == 'dev_type':
- # nullable should be False
- column_copy = sa.Column('dev_type', sa.String(8))
-
- # 280_add_nullable_false_to_keypairs_name; this should apply to the
- # shadow table also
-
- if table_name == 'key_pairs' and column.name == 'name':
- # nullable should be False
- column_copy = sa.Column('name', sa.String(length=255))
-
- # NOTE(stephenfin): By default, 'sqlalchemy.Enum' will issue a
- # 'CREATE TYPE' command on PostgreSQL, even if the type already
- # exists. We work around this by using the PostgreSQL-specific
- # 'sqlalchemy.dialects.postgresql.ENUM' type and setting
- # 'create_type' to 'False'. See [1] for more information.
- #
- # [1] https://stackoverflow.com/a/28894354/613428
- if migrate_engine.name == 'postgresql':
- if table_name == 'key_pairs' and column.name == 'type':
- enum = dialects.postgresql.ENUM(
- 'ssh', 'x509', name='keypair_types', create_type=False)
- column_copy = sa.Column(
- column.name, enum, nullable=False,
- server_default=keypair.KEYPAIR_TYPE_SSH)
- elif (
- table_name == 'migrations' and
- column.name == 'migration_type'
- ):
- enum = dialects.postgresql.ENUM(
- 'migration', 'resize', 'live-migration', 'evacuation',
- name='migration_type', create_type=False)
- column_copy = sa.Column(column.name, enum, nullable=True)
-
- if column_copy is None:
- # NOTE(stephenfin): Yes, this is private. Yes, this is what we
- # were told to use. Blame zzzeek!
- column_copy = column._copy()
-
- columns.append(column_copy)
-
- shadow_table = sa.Table(
- 'shadow_' + table_name, meta, *columns, mysql_engine='InnoDB',
- )
-
- try:
- shadow_table.create()
- except Exception:
- LOG.info(repr(shadow_table))
- LOG.exception('Exception while creating table.')
- raise
-
- # TODO(stephenfin): Fix these various bugs in a follow-up
-
- # 252_add_instance_extra_table; we don't create indexes for shadow tables
- # in general and these should be removed
-
- table = sa.Table(
- 'shadow_instance_extra', meta, autoload_with=migrate_engine,
- )
- idx = sa.Index('shadow_instance_extra_idx', table.c.instance_uuid)
- idx.create(migrate_engine)
-
- # 373_migration_uuid; we should't create indexes for shadow tables
-
- table = sa.Table('shadow_migrations', meta, autoload_with=migrate_engine)
- idx = sa.Index('shadow_migrations_uuid', table.c.uuid, unique=True)
- idx.create(migrate_engine)
-
-
-def upgrade(migrate_engine):
- meta = sa.MetaData()
- # NOTE(stephenfin): This is not compatible with SQLAlchemy 2.0 but neither
- # is sqlalchemy-migrate which requires this. We'll remove these migrations
- # when dropping SQLAlchemy < 2.x support
- meta.bind = migrate_engine
-
- agent_builds = sa.Table('agent_builds', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('hypervisor', sa.String(length=255)),
- sa.Column('os', sa.String(length=255)),
- sa.Column('architecture', sa.String(length=255)),
- sa.Column('version', sa.String(length=255)),
- sa.Column('url', sa.String(length=255)),
- sa.Column('md5hash', sa.String(length=255)),
- sa.Column('deleted', sa.Integer),
- sa.Index(
- 'agent_builds_hypervisor_os_arch_idx',
- 'hypervisor', 'os', 'architecture'),
- UniqueConstraint(
- 'hypervisor', 'os', 'architecture', 'deleted',
- name='uniq_agent_builds0hypervisor0os0architecture0deleted'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- aggregate_hosts = sa.Table('aggregate_hosts', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('host', sa.String(length=255)),
- sa.Column(
- 'aggregate_id', sa.Integer, sa.ForeignKey('aggregates.id'),
- nullable=False),
- sa.Column('deleted', sa.Integer),
- UniqueConstraint(
- 'host', 'aggregate_id', 'deleted',
- name='uniq_aggregate_hosts0host0aggregate_id0deleted'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- aggregate_metadata = sa.Table('aggregate_metadata', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column(
- 'aggregate_id', sa.Integer, sa.ForeignKey('aggregates.id'),
- nullable=False),
- sa.Column('key', sa.String(length=255), nullable=False),
- sa.Column('value', sa.String(length=255), nullable=False),
- sa.Column('deleted', sa.Integer),
- sa.Index('aggregate_metadata_key_idx', 'key'),
- sa.Index('aggregate_metadata_value_idx', 'value'),
- UniqueConstraint(
- 'aggregate_id', 'key', 'deleted',
- name='uniq_aggregate_metadata0aggregate_id0key0deleted'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- aggregates = sa.Table('aggregates', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('name', sa.String(length=255)),
- sa.Column('deleted', sa.Integer),
- sa.Column('uuid', sa.String(36)),
- sa.Index('aggregate_uuid_idx', 'uuid'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- allocations = sa.Table('allocations', meta,
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('resource_provider_id', sa.Integer, nullable=False),
- sa.Column('consumer_id', sa.String(36), nullable=False),
- sa.Column('resource_class_id', sa.Integer, nullable=False),
- sa.Column('used', sa.Integer, nullable=False),
- sa.Index(
- 'allocations_resource_provider_class_used_idx',
- 'resource_provider_id', 'resource_class_id', 'used'),
- sa.Index('allocations_consumer_id_idx', 'consumer_id'),
- sa.Index('allocations_resource_class_id_idx', 'resource_class_id'),
- mysql_engine='InnoDB',
- mysql_charset='latin1',
- )
-
- block_device_mapping = sa.Table('block_device_mapping', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('device_name', sa.String(length=255), nullable=True),
- sa.Column('delete_on_termination', sa.Boolean),
- sa.Column('snapshot_id', sa.String(length=36), nullable=True),
- sa.Column('volume_id', sa.String(length=36), nullable=True),
- sa.Column('volume_size', sa.Integer),
- sa.Column('no_device', sa.Boolean),
- sa.Column('connection_info', types.MediumText()),
- sa.Column(
- 'instance_uuid', sa.String(length=36),
- sa.ForeignKey(
- 'instances.uuid',
- name='block_device_mapping_instance_uuid_fkey')),
- sa.Column('deleted', sa.Integer),
- sa.Column('source_type', sa.String(length=255), nullable=True),
- sa.Column('destination_type', sa.String(length=255), nullable=True),
- sa.Column('guest_format', sa.String(length=255), nullable=True),
- sa.Column('device_type', sa.String(length=255), nullable=True),
- sa.Column('disk_bus', sa.String(length=255), nullable=True),
- sa.Column('boot_index', sa.Integer),
- sa.Column('image_id', sa.String(length=36), nullable=True),
- sa.Column('tag', sa.String(255)),
- sa.Column('attachment_id', sa.String(36), nullable=True),
- sa.Column('uuid', sa.String(36), nullable=True),
- sa.Column('volume_type', sa.String(255), nullable=True),
- sa.Index('snapshot_id', 'snapshot_id'),
- sa.Index('volume_id', 'volume_id'),
- sa.Index('block_device_mapping_instance_uuid_idx', 'instance_uuid'),
- sa.Index(
- 'block_device_mapping_instance_uuid_device_name_idx',
- 'instance_uuid', 'device_name'),
- sa.Index(
- 'block_device_mapping_instance_uuid_volume_id_idx',
- 'instance_uuid', 'volume_id'),
- UniqueConstraint('uuid', name='uniq_block_device_mapping0uuid'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- bw_usage_cache = sa.Table('bw_usage_cache', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('start_period', sa.DateTime, nullable=False),
- sa.Column('last_refreshed', sa.DateTime),
- sa.Column('bw_in', sa.BigInteger),
- sa.Column('bw_out', sa.BigInteger),
- sa.Column('mac', sa.String(length=255)),
- sa.Column('uuid', sa.String(length=36)),
- sa.Column('last_ctr_in', sa.BigInteger()),
- sa.Column('last_ctr_out', sa.BigInteger()),
- sa.Column('deleted', sa.Integer),
- sa.Index(
- 'bw_usage_cache_uuid_start_period_idx',
- 'uuid', 'start_period'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- cells = sa.Table('cells', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('api_url', sa.String(length=255)),
- sa.Column('weight_offset', sa.Float),
- sa.Column('weight_scale', sa.Float),
- sa.Column('name', sa.String(length=255)),
- sa.Column('is_parent', sa.Boolean),
- sa.Column('deleted', sa.Integer),
- sa.Column('transport_url', sa.String(length=255), nullable=False),
- UniqueConstraint(
- 'name', 'deleted',
- name='uniq_cells0name0deleted'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- certificates = sa.Table('certificates', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('user_id', sa.String(length=255)),
- sa.Column('project_id', sa.String(length=255)),
- sa.Column('file_name', sa.String(length=255)),
- sa.Column('deleted', sa.Integer),
- sa.Index(
- 'certificates_project_id_deleted_idx',
- 'project_id', 'deleted'),
- sa.Index('certificates_user_id_deleted_idx', 'user_id', 'deleted'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- compute_nodes = sa.Table('compute_nodes', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('service_id', sa.Integer, nullable=True),
- sa.Column('vcpus', sa.Integer, nullable=False),
- sa.Column('memory_mb', sa.Integer, nullable=False),
- sa.Column('local_gb', sa.Integer, nullable=False),
- sa.Column('vcpus_used', sa.Integer, nullable=False),
- sa.Column('memory_mb_used', sa.Integer, nullable=False),
- sa.Column('local_gb_used', sa.Integer, nullable=False),
- sa.Column('hypervisor_type', types.MediumText(), nullable=False),
- sa.Column('hypervisor_version', sa.Integer, nullable=False),
- sa.Column('cpu_info', types.MediumText(), nullable=False),
- sa.Column('disk_available_least', sa.Integer),
- sa.Column('free_ram_mb', sa.Integer),
- sa.Column('free_disk_gb', sa.Integer),
- sa.Column('current_workload', sa.Integer),
- sa.Column('running_vms', sa.Integer),
- sa.Column('hypervisor_hostname', sa.String(length=255)),
- sa.Column('deleted', sa.Integer),
- sa.Column('host_ip', InetSmall()),
- sa.Column('supported_instances', sa.Text),
- sa.Column('pci_stats', sa.Text, nullable=True),
- sa.Column('metrics', sa.Text, nullable=True),
- sa.Column('extra_resources', sa.Text, nullable=True),
- sa.Column('stats', sa.Text, default='{}'),
- sa.Column('numa_topology', sa.Text, nullable=True),
- sa.Column('host', sa.String(255), nullable=True),
- sa.Column('ram_allocation_ratio', sa.Float, nullable=True),
- sa.Column('cpu_allocation_ratio', sa.Float, nullable=True),
- sa.Column('uuid', sa.String(36), nullable=True),
- sa.Column('disk_allocation_ratio', sa.Float, nullable=True),
- sa.Column('mapped', sa.Integer, default=0, nullable=True),
- sa.Index('compute_nodes_uuid_idx', 'uuid', unique=True),
- UniqueConstraint(
- 'host', 'hypervisor_hostname', 'deleted',
- name='uniq_compute_nodes0host0hypervisor_hostname0deleted',
- ),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- console_auth_tokens = sa.Table('console_auth_tokens', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('token_hash', sa.String(255), nullable=False),
- sa.Column('console_type', sa.String(255), nullable=False),
- sa.Column('host', sa.String(255), nullable=False),
- sa.Column('port', sa.Integer, nullable=False),
- sa.Column('internal_access_path', sa.String(255)),
- sa.Column('instance_uuid', sa.String(36), nullable=False),
- sa.Column('expires', sa.Integer, nullable=False),
- sa.Column('access_url_base', sa.String(255), nullable=True),
- sa.Index('console_auth_tokens_instance_uuid_idx', 'instance_uuid'),
- sa.Index('console_auth_tokens_host_expires_idx', 'host', 'expires'),
- sa.Index('console_auth_tokens_token_hash_idx', 'token_hash'),
- sa.Index(
- 'console_auth_tokens_token_hash_instance_uuid_idx',
- 'token_hash', 'instance_uuid'),
- UniqueConstraint(
- 'token_hash', name='uniq_console_auth_tokens0token_hash'),
- mysql_engine='InnoDB',
- mysql_charset='utf8',
- )
-
- console_pools = sa.Table('console_pools', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('address', InetSmall()),
- sa.Column('username', sa.String(length=255)),
- sa.Column('password', sa.String(length=255)),
- sa.Column('console_type', sa.String(length=255)),
- sa.Column('public_hostname', sa.String(length=255)),
- sa.Column('host', sa.String(length=255)),
- sa.Column('compute_host', sa.String(length=255)),
- sa.Column('deleted', sa.Integer),
- UniqueConstraint(
- 'host', 'console_type', 'compute_host', 'deleted',
- name='uniq_console_pools0host0console_type0compute_host0deleted'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- consoles = sa.Table('consoles', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('instance_name', sa.String(length=255)),
- sa.Column('password', sa.String(length=255)),
- sa.Column('port', sa.Integer),
- sa.Column('pool_id', sa.Integer, sa.ForeignKey('console_pools.id')),
- sa.Column(
- 'instance_uuid', sa.String(length=36),
- sa.ForeignKey(
- 'instances.uuid', name='consoles_instance_uuid_fkey')),
- sa.Column('deleted', sa.Integer),
- sa.Index('consoles_instance_uuid_idx', 'instance_uuid'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- dns_domains = sa.Table('dns_domains', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('deleted', sa.Boolean),
- sa.Column(
- 'domain', sa.String(length=255), primary_key=True, nullable=False),
- sa.Column('scope', sa.String(length=255)),
- sa.Column('availability_zone', sa.String(length=255)),
- sa.Column('project_id', sa.String(length=255)),
- sa.Index('dns_domains_domain_deleted_idx', 'domain', 'deleted'),
- sa.Index('dns_domains_project_id_idx', 'project_id'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- fixed_ips = sa.Table('fixed_ips', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('address', InetSmall()),
- sa.Column('network_id', sa.Integer),
- sa.Column('allocated', sa.Boolean),
- sa.Column('leased', sa.Boolean),
- sa.Column('reserved', sa.Boolean),
- sa.Column('virtual_interface_id', sa.Integer),
- sa.Column('host', sa.String(length=255)),
- sa.Column(
- 'instance_uuid', sa.String(length=36),
- sa.ForeignKey(
- 'instances.uuid', name='fixed_ips_instance_uuid_fkey'),
- ),
- sa.Column('deleted', sa.Integer),
- sa.Index('network_id', 'network_id'),
- sa.Index('address', 'address'),
- sa.Index('fixed_ips_instance_uuid_fkey', 'instance_uuid'),
- sa.Index(
- 'fixed_ips_virtual_interface_id_fkey',
- 'virtual_interface_id'),
- sa.Index('fixed_ips_host_idx', 'host'),
- sa.Index(
- 'fixed_ips_network_id_host_deleted_idx', 'network_id',
- 'host', 'deleted'),
- sa.Index(
- 'fixed_ips_address_reserved_network_id_deleted_idx',
- 'address', 'reserved',
- 'network_id', 'deleted'),
- sa.Index(
- 'fixed_ips_deleted_allocated_idx',
- 'address', 'deleted', 'allocated'),
- sa.Index(
- 'fixed_ips_deleted_allocated_updated_at_idx',
- 'deleted', 'allocated', 'updated_at'),
- UniqueConstraint(
- 'address', 'deleted',
- name='uniq_fixed_ips0address0deleted'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- floating_ips = sa.Table('floating_ips', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('address', InetSmall()),
- sa.Column('fixed_ip_id', sa.Integer),
- sa.Column('project_id', sa.String(length=255)),
- sa.Column('host', sa.String(length=255)),
- sa.Column('auto_assigned', sa.Boolean),
- sa.Column('pool', sa.String(length=255)),
- sa.Column('interface', sa.String(length=255)),
- sa.Column('deleted', sa.Integer),
- sa.Index('fixed_ip_id', 'fixed_ip_id'),
- sa.Index('floating_ips_host_idx', 'host'),
- sa.Index('floating_ips_project_id_idx', 'project_id'),
- sa.Index(
- 'floating_ips_pool_deleted_fixed_ip_id_project_id_idx',
- 'pool', 'deleted', 'fixed_ip_id', 'project_id'),
- UniqueConstraint(
- 'address', 'deleted',
- name='uniq_floating_ips0address0deleted'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- instance_faults = sa.Table('instance_faults', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column(
- 'instance_uuid', sa.String(length=36),
- sa.ForeignKey(
- 'instances.uuid', name='fk_instance_faults_instance_uuid')),
- sa.Column('code', sa.Integer, nullable=False),
- sa.Column('message', sa.String(length=255)),
- sa.Column('details', types.MediumText()),
- sa.Column('host', sa.String(length=255)),
- sa.Column('deleted', sa.Integer),
- sa.Index('instance_faults_host_idx', 'host'),
- sa.Index(
- 'instance_faults_instance_uuid_deleted_created_at_idx',
- 'instance_uuid', 'deleted', 'created_at'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- instance_id_mappings = sa.Table('instance_id_mappings', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('uuid', sa.String(36), nullable=False),
- sa.Column('deleted', sa.Integer),
- sa.Index('ix_instance_id_mappings_uuid', 'uuid'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- instance_info_caches = sa.Table('instance_info_caches', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('network_info', types.MediumText()),
- sa.Column(
- 'instance_uuid', sa.String(length=36),
- sa.ForeignKey(
- 'instances.uuid',
- name='instance_info_caches_instance_uuid_fkey'),
- nullable=False),
- sa.Column('deleted', sa.Integer),
- UniqueConstraint(
- 'instance_uuid',
- name='uniq_instance_info_caches0instance_uuid'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- groups = sa.Table('instance_groups', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('deleted', sa.Integer),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('user_id', sa.String(length=255)),
- sa.Column('project_id', sa.String(length=255)),
- sa.Column('uuid', sa.String(length=36), nullable=False),
- sa.Column('name', sa.String(length=255)),
- UniqueConstraint(
- 'uuid', 'deleted',
- name='uniq_instance_groups0uuid0deleted'),
- mysql_engine='InnoDB',
- mysql_charset='utf8',
- )
-
- group_policy = sa.Table('instance_group_policy', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('deleted', sa.Integer),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('policy', sa.String(length=255)),
- sa.Column(
- 'group_id', sa.Integer, sa.ForeignKey('instance_groups.id'),
- nullable=False),
- sa.Index('instance_group_policy_policy_idx', 'policy'),
- mysql_engine='InnoDB',
- mysql_charset='utf8',
- )
-
- group_member = sa.Table('instance_group_member', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('deleted', sa.Integer),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('instance_id', sa.String(length=255)),
- sa.Column(
- 'group_id', sa.Integer, sa.ForeignKey('instance_groups.id'),
- nullable=False),
- sa.Index(
- 'instance_group_member_instance_idx',
- 'instance_id'),
- mysql_engine='InnoDB',
- mysql_charset='utf8',
- )
-
- instance_metadata = sa.Table('instance_metadata', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('key', sa.String(length=255)),
- sa.Column('value', sa.String(length=255)),
- sa.Column(
- 'instance_uuid', sa.String(length=36),
- sa.ForeignKey(
- 'instances.uuid', name='instance_metadata_instance_uuid_fkey'),
- nullable=True),
- sa.Column('deleted', sa.Integer),
- sa.Index('instance_metadata_instance_uuid_idx', 'instance_uuid'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- instance_system_metadata = sa.Table('instance_system_metadata', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column(
- 'instance_uuid', sa.String(length=36),
- sa.ForeignKey(
- 'instances.uuid', name='instance_system_metadata_ibfk_1'),
- nullable=False),
- sa.Column('key', sa.String(length=255), nullable=False),
- sa.Column('value', sa.String(length=255)),
- sa.Column('deleted', sa.Integer),
- sa.Index('instance_uuid', 'instance_uuid'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- # TODO(stephenfin): Remove this table since it has been moved to the API DB
- instance_type_extra_specs = sa.Table('instance_type_extra_specs', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column(
- 'instance_type_id', sa.Integer, sa.ForeignKey('instance_types.id'),
- nullable=False),
- sa.Column('key', sa.String(length=255)),
- sa.Column('value', sa.String(length=255)),
- sa.Column('deleted', sa.Integer),
- sa.Index(
- 'instance_type_extra_specs_instance_type_id_key_idx',
- 'instance_type_id', 'key'),
- UniqueConstraint(
- 'instance_type_id', 'key', 'deleted',
- name='uniq_instance_type_extra_specs0instance_type_id0key0deleted'
- ),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- # TODO(stephenfin): Remove this table since it has been moved to the API DB
- instance_type_projects = sa.Table('instance_type_projects', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column(
- 'instance_type_id', sa.Integer,
- sa.ForeignKey(
- 'instance_types.id', name='instance_type_projects_ibfk_1'),
- nullable=False),
- sa.Column('project_id', sa.String(length=255)),
- sa.Column('deleted', sa.Integer),
- UniqueConstraint(
- 'instance_type_id', 'project_id', 'deleted',
- name='uniq_instance_type_projects0instance_type_id0project_id'
- '0deleted'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- # TODO(stephenfin): Remove this table since it has been moved to the API DB
- instance_types = sa.Table('instance_types', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('name', sa.String(length=255)),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('memory_mb', sa.Integer, nullable=False),
- sa.Column('vcpus', sa.Integer, nullable=False),
- sa.Column('swap', sa.Integer, nullable=False),
- sa.Column('vcpu_weight', sa.Integer),
- sa.Column('flavorid', sa.String(length=255)),
- sa.Column('rxtx_factor', sa.Float),
- sa.Column('root_gb', sa.Integer),
- sa.Column('ephemeral_gb', sa.Integer),
- sa.Column('disabled', sa.Boolean),
- sa.Column('is_public', sa.Boolean),
- sa.Column('deleted', sa.Integer),
- UniqueConstraint(
- 'name', 'deleted',
- name='uniq_instance_types0name0deleted'),
- UniqueConstraint(
- 'flavorid', 'deleted',
- name='uniq_instance_types0flavorid0deleted'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- instances = sa.Table('instances', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('internal_id', sa.Integer),
- sa.Column('user_id', sa.String(length=255)),
- sa.Column('project_id', sa.String(length=255)),
- sa.Column('image_ref', sa.String(length=255)),
- sa.Column('kernel_id', sa.String(length=255)),
- sa.Column('ramdisk_id', sa.String(length=255)),
- sa.Column('launch_index', sa.Integer),
- sa.Column('key_name', sa.String(length=255)),
- sa.Column('key_data', types.MediumText()),
- sa.Column('power_state', sa.Integer),
- sa.Column('vm_state', sa.String(length=255)),
- sa.Column('memory_mb', sa.Integer),
- sa.Column('vcpus', sa.Integer),
- sa.Column('hostname', sa.String(length=255)),
- sa.Column('host', sa.String(length=255)),
- sa.Column('user_data', types.MediumText()),
- sa.Column('reservation_id', sa.String(length=255)),
- sa.Column('launched_at', sa.DateTime),
- sa.Column('terminated_at', sa.DateTime),
- sa.Column('display_name', sa.String(length=255)),
- sa.Column('display_description', sa.String(length=255)),
- sa.Column('availability_zone', sa.String(length=255)),
- sa.Column('locked', sa.Boolean),
- sa.Column('os_type', sa.String(length=255)),
- sa.Column('launched_on', types.MediumText()),
- sa.Column('instance_type_id', sa.Integer),
- sa.Column('vm_mode', sa.String(length=255)),
- sa.Column('uuid', sa.String(length=36), nullable=False),
- sa.Column('architecture', sa.String(length=255)),
- sa.Column('root_device_name', sa.String(length=255)),
- sa.Column('access_ip_v4', InetSmall()),
- sa.Column('access_ip_v6', InetSmall()),
- sa.Column('config_drive', sa.String(length=255)),
- sa.Column('task_state', sa.String(length=255)),
- sa.Column('default_ephemeral_device', sa.String(length=255)),
- sa.Column('default_swap_device', sa.String(length=255)),
- sa.Column('progress', sa.Integer),
- sa.Column('auto_disk_config', sa.Boolean),
- sa.Column('shutdown_terminate', sa.Boolean),
- sa.Column('disable_terminate', sa.Boolean),
- sa.Column('root_gb', sa.Integer),
- sa.Column('ephemeral_gb', sa.Integer),
- sa.Column('cell_name', sa.String(length=255)),
- sa.Column('node', sa.String(length=255)),
- sa.Column('deleted', sa.Integer),
- sa.Column(
- 'locked_by',
- sa.Enum('owner', 'admin', name='instances0locked_by')),
- sa.Column('cleaned', sa.Integer, default=0),
- sa.Column('ephemeral_key_uuid', sa.String(36)),
- # NOTE(danms): This column originally included default=False. We
- # discovered in bug #1862205 that this will attempt to rewrite
- # the entire instances table with that value, which can time out
- # for large data sets (and does not even abort).
- # NOTE(stephenfin): This was originally added by sqlalchemy-migrate
- # which did not generate the constraints
- sa.Column('hidden', sa.Boolean(create_constraint=False)),
- sa.Index('uuid', 'uuid', unique=True),
- sa.Index('instances_reservation_id_idx', 'reservation_id'),
- sa.Index(
- 'instances_terminated_at_launched_at_idx',
- 'terminated_at', 'launched_at'),
- sa.Index(
- 'instances_task_state_updated_at_idx',
- 'task_state', 'updated_at'),
- sa.Index('instances_uuid_deleted_idx', 'uuid', 'deleted'),
- sa.Index('instances_host_node_deleted_idx', 'host', 'node', 'deleted'),
- sa.Index(
- 'instances_host_deleted_cleaned_idx',
- 'host', 'deleted', 'cleaned'),
- sa.Index('instances_project_id_deleted_idx', 'project_id', 'deleted'),
- sa.Index('instances_deleted_created_at_idx', 'deleted', 'created_at'),
- sa.Index('instances_project_id_idx', 'project_id'),
- sa.Index(
- 'instances_updated_at_project_id_idx',
- 'updated_at', 'project_id'),
- UniqueConstraint('uuid', name='uniq_instances0uuid'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- instance_actions = sa.Table('instance_actions', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('action', sa.String(length=255)),
- sa.Column(
- 'instance_uuid', sa.String(length=36),
- sa.ForeignKey(
- 'instances.uuid', name='fk_instance_actions_instance_uuid')),
- sa.Column('request_id', sa.String(length=255)),
- sa.Column('user_id', sa.String(length=255)),
- sa.Column('project_id', sa.String(length=255)),
- sa.Column('start_time', sa.DateTime),
- sa.Column('finish_time', sa.DateTime),
- sa.Column('message', sa.String(length=255)),
- sa.Column('deleted', sa.Integer),
- sa.Index('instance_uuid_idx', 'instance_uuid'),
- sa.Index('request_id_idx', 'request_id'),
- sa.Index(
- 'instance_actions_instance_uuid_updated_at_idx',
- 'instance_uuid', 'updated_at'),
- mysql_engine='InnoDB',
- mysql_charset='utf8',
- )
-
- instance_actions_events = sa.Table('instance_actions_events', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('event', sa.String(length=255)),
- sa.Column(
- 'action_id', sa.Integer, sa.ForeignKey('instance_actions.id')),
- sa.Column('start_time', sa.DateTime),
- sa.Column('finish_time', sa.DateTime),
- sa.Column('result', sa.String(length=255)),
- sa.Column('traceback', sa.Text),
- sa.Column('deleted', sa.Integer),
- sa.Column('host', sa.String(255)),
- sa.Column('details', sa.Text),
- mysql_engine='InnoDB',
- mysql_charset='utf8',
- )
-
- instance_extra = sa.Table('instance_extra', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('deleted', sa.Integer),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column(
- 'instance_uuid', sa.String(length=36),
- sa.ForeignKey(
- 'instances.uuid', name='instance_extra_instance_uuid_fkey'),
- nullable=False),
- sa.Column('numa_topology', sa.Text, nullable=True),
- sa.Column('pci_requests', sa.Text, nullable=True),
- sa.Column('flavor', sa.Text, nullable=True),
- sa.Column('vcpu_model', sa.Text, nullable=True),
- sa.Column('migration_context', sa.Text, nullable=True),
- sa.Column('keypairs', sa.Text, nullable=True),
- sa.Column('device_metadata', sa.Text, nullable=True),
- sa.Column('trusted_certs', sa.Text, nullable=True),
- sa.Column('vpmems', sa.Text, nullable=True),
- sa.Column('resources', sa.Text, nullable=True),
- sa.Index('instance_extra_idx', 'instance_uuid'),
- mysql_engine='InnoDB',
- mysql_charset='utf8',
- )
-
- inventories = sa.Table('inventories', meta,
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('resource_provider_id', sa.Integer, nullable=False),
- sa.Column('resource_class_id', sa.Integer, nullable=False),
- sa.Column('total', sa.Integer, nullable=False),
- sa.Column('reserved', sa.Integer, nullable=False),
- sa.Column('min_unit', sa.Integer, nullable=False),
- sa.Column('max_unit', sa.Integer, nullable=False),
- sa.Column('step_size', sa.Integer, nullable=False),
- sa.Column('allocation_ratio', sa.Float, nullable=False),
- sa.Index(
- 'inventories_resource_provider_id_idx', 'resource_provider_id'),
- sa.Index(
- 'inventories_resource_class_id_idx', 'resource_class_id'),
- sa.Index(
- 'inventories_resource_provider_resource_class_idx',
- 'resource_provider_id', 'resource_class_id'),
- UniqueConstraint(
- 'resource_provider_id', 'resource_class_id',
- name='uniq_inventories0resource_provider_resource_class'),
- mysql_engine='InnoDB',
- mysql_charset='latin1',
- )
-
- key_pairs = sa.Table('key_pairs', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('name', sa.String(length=255), nullable=False),
- sa.Column('user_id', sa.String(length=255)),
- sa.Column('fingerprint', sa.String(length=255)),
- sa.Column('public_key', types.MediumText()),
- sa.Column('deleted', sa.Integer),
- sa.Column(
- 'type', sa.Enum('ssh', 'x509', name='keypair_types'),
- nullable=False, server_default=keypair.KEYPAIR_TYPE_SSH),
- UniqueConstraint(
- 'user_id', 'name', 'deleted',
- name='uniq_key_pairs0user_id0name0deleted'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- migrations = sa.Table('migrations', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('source_compute', sa.String(length=255)),
- sa.Column('dest_compute', sa.String(length=255)),
- sa.Column('dest_host', sa.String(length=255)),
- sa.Column('status', sa.String(length=255)),
- sa.Column(
- 'instance_uuid', sa.String(length=36),
- sa.ForeignKey(
- 'instances.uuid', name='fk_migrations_instance_uuid')),
- sa.Column('old_instance_type_id', sa.Integer),
- sa.Column('new_instance_type_id', sa.Integer),
- sa.Column('source_node', sa.String(length=255)),
- sa.Column('dest_node', sa.String(length=255)),
- sa.Column('deleted', sa.Integer),
- sa.Column(
- 'migration_type',
- sa.Enum(
- 'migration', 'resize', 'live-migration', 'evacuation',
- name='migration_type'),
- nullable=True),
- # NOTE(stephenfin): This was originally added by sqlalchemy-migrate
- # which did not generate the constraints
- sa.Column(
- 'hidden', sa.Boolean(create_constraint=False), default=False),
- sa.Column('memory_total', sa.BigInteger, nullable=True),
- sa.Column('memory_processed', sa.BigInteger, nullable=True),
- sa.Column('memory_remaining', sa.BigInteger, nullable=True),
- sa.Column('disk_total', sa.BigInteger, nullable=True),
- sa.Column('disk_processed', sa.BigInteger, nullable=True),
- sa.Column('disk_remaining', sa.BigInteger, nullable=True),
- sa.Column('uuid', sa.String(36)),
- # NOTE(stephenfin): This was originally added by sqlalchemy-migrate
- # which did not generate the constraints
- sa.Column(
- 'cross_cell_move', sa.Boolean(create_constraint=False),
- default=False),
- sa.Column('user_id', sa.String(255), nullable=True),
- sa.Column('project_id', sa.String(255), nullable=True),
- sa.Index('migrations_uuid', 'uuid', unique=True),
- sa.Index(
- 'migrations_instance_uuid_and_status_idx',
- 'deleted', 'instance_uuid', 'status'),
- sa.Index('migrations_updated_at_idx', 'updated_at'),
- # mysql-specific index by leftmost 100 chars. (mysql gets angry if the
- # index key length is too long.)
- sa.Index(
- 'migrations_by_host_nodes_and_status_idx',
- 'deleted', 'source_compute', 'dest_compute', 'source_node',
- 'dest_node', 'status',
- mysql_length={
- 'source_compute': 100,
- 'dest_compute': 100,
- 'source_node': 100,
- 'dest_node': 100,
- }),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- networks = sa.Table('networks', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('injected', sa.Boolean),
- sa.Column('cidr', Inet()),
- sa.Column('netmask', InetSmall()),
- sa.Column('bridge', sa.String(length=255)),
- sa.Column('gateway', InetSmall()),
- sa.Column('broadcast', InetSmall()),
- sa.Column('dns1', InetSmall()),
- sa.Column('vlan', sa.Integer),
- sa.Column('vpn_public_address', InetSmall()),
- sa.Column('vpn_public_port', sa.Integer),
- sa.Column('vpn_private_address', InetSmall()),
- sa.Column('dhcp_start', InetSmall()),
- sa.Column('project_id', sa.String(length=255)),
- sa.Column('host', sa.String(length=255)),
- sa.Column('cidr_v6', Inet()),
- sa.Column('gateway_v6', InetSmall()),
- sa.Column('label', sa.String(length=255)),
- sa.Column('netmask_v6', InetSmall()),
- sa.Column('bridge_interface', sa.String(length=255)),
- sa.Column('multi_host', sa.Boolean),
- sa.Column('dns2', InetSmall()),
- sa.Column('uuid', sa.String(length=36)),
- sa.Column('priority', sa.Integer),
- sa.Column('rxtx_base', sa.Integer),
- sa.Column('deleted', sa.Integer),
- sa.Column('mtu', sa.Integer),
- sa.Column('dhcp_server', types.IPAddress),
- # NOTE(stephenfin): These were originally added by sqlalchemy-migrate
- # which did not generate the constraints
- sa.Column(
- 'enable_dhcp', sa.Boolean(create_constraint=False), default=True),
- sa.Column(
- 'share_address', sa.Boolean(create_constraint=False),
- default=False),
- sa.Index('networks_host_idx', 'host'),
- sa.Index('networks_cidr_v6_idx', 'cidr_v6'),
- sa.Index('networks_bridge_deleted_idx', 'bridge', 'deleted'),
- sa.Index('networks_project_id_deleted_idx', 'project_id', 'deleted'),
- sa.Index(
- 'networks_uuid_project_id_deleted_idx',
- 'uuid', 'project_id', 'deleted'),
- sa.Index('networks_vlan_deleted_idx', 'vlan', 'deleted'),
- UniqueConstraint('vlan', 'deleted', name='uniq_networks0vlan0deleted'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- pci_devices = sa.Table('pci_devices', meta,
- sa.Column('created_at', sa.DateTime(timezone=False)),
- sa.Column('updated_at', sa.DateTime(timezone=False)),
- sa.Column('deleted_at', sa.DateTime(timezone=False)),
- sa.Column('deleted', sa.Integer, default=0, nullable=True),
- sa.Column('id', sa.Integer, primary_key=True),
- sa.Column(
- 'compute_node_id', sa.Integer,
- sa.ForeignKey(
- 'compute_nodes.id', name='pci_devices_compute_node_id_fkey'),
- nullable=False),
- sa.Column('address', sa.String(12), nullable=False),
- sa.Column('product_id', sa.String(4), nullable=False),
- sa.Column('vendor_id', sa.String(4), nullable=False),
- sa.Column('dev_type', sa.String(8), nullable=False),
- sa.Column('dev_id', sa.String(255)),
- sa.Column('label', sa.String(255), nullable=False),
- sa.Column('status', sa.String(36), nullable=False),
- sa.Column('extra_info', sa.Text, nullable=True),
- sa.Column('instance_uuid', sa.String(36), nullable=True),
- sa.Column('request_id', sa.String(36), nullable=True),
- sa.Column('numa_node', sa.Integer, default=None),
- sa.Column('parent_addr', sa.String(12), nullable=True),
- sa.Column('uuid', sa.String(36)),
- sa.Index(
- 'ix_pci_devices_instance_uuid_deleted',
- 'instance_uuid', 'deleted'),
- sa.Index(
- 'ix_pci_devices_compute_node_id_deleted',
- 'compute_node_id', 'deleted'),
- sa.Index(
- 'ix_pci_devices_compute_node_id_parent_addr_deleted',
- 'compute_node_id', 'parent_addr', 'deleted'),
- UniqueConstraint(
- 'compute_node_id', 'address', 'deleted',
- name='uniq_pci_devices0compute_node_id0address0deleted'),
- mysql_engine='InnoDB',
- mysql_charset='utf8')
-
- provider_fw_rules = sa.Table('provider_fw_rules', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('protocol', sa.String(length=5)),
- sa.Column('from_port', sa.Integer),
- sa.Column('to_port', sa.Integer),
- sa.Column('cidr', Inet()),
- sa.Column('deleted', sa.Integer),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- quota_classes = sa.Table('quota_classes', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('class_name', sa.String(length=255)),
- sa.Column('resource', sa.String(length=255)),
- sa.Column('hard_limit', sa.Integer),
- sa.Column('deleted', sa.Integer),
- sa.Index('ix_quota_classes_class_name', 'class_name'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- quota_usages = sa.Table('quota_usages', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('project_id', sa.String(length=255)),
- sa.Column('resource', sa.String(length=255), nullable=False),
- sa.Column('in_use', sa.Integer, nullable=False),
- sa.Column('reserved', sa.Integer, nullable=False),
- sa.Column('until_refresh', sa.Integer),
- sa.Column('deleted', sa.Integer),
- sa.Column('user_id', sa.String(length=255)),
- sa.Index('ix_quota_usages_project_id', 'project_id'),
- sa.Index('ix_quota_usages_user_id_deleted', 'user_id', 'deleted'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- quotas = sa.Table('quotas', meta,
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('project_id', sa.String(length=255)),
- sa.Column('resource', sa.String(length=255), nullable=False),
- sa.Column('hard_limit', sa.Integer),
- sa.Column('deleted', sa.Integer),
- UniqueConstraint(
- 'project_id', 'resource', 'deleted',
- name='uniq_quotas0project_id0resource0deleted'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- project_user_quotas = sa.Table('project_user_quotas', meta,
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('deleted', sa.Integer),
- sa.Column('user_id', sa.String(length=255), nullable=False),
- sa.Column('project_id', sa.String(length=255), nullable=False),
- sa.Column('resource', sa.String(length=255), nullable=False),
- sa.Column('hard_limit', sa.Integer, nullable=True),
- sa.Index(
- 'project_user_quotas_project_id_deleted_idx',
- 'project_id', 'deleted'),
- sa.Index(
- 'project_user_quotas_user_id_deleted_idx',
- 'user_id', 'deleted'),
- UniqueConstraint(
- 'user_id', 'project_id', 'resource', 'deleted',
- name='uniq_project_user_quotas0user_id0project_id0resource0'
- 'deleted'),
- mysql_engine='InnoDB',
- mysql_charset='utf8',
- )
-
- reservations = sa.Table('reservations', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('uuid', sa.String(length=36), nullable=False),
- sa.Column(
- 'usage_id', sa.Integer,
- sa.ForeignKey('quota_usages.id', name='reservations_ibfk_1'),
- nullable=False),
- sa.Column('project_id', sa.String(length=255)),
- sa.Column('resource', sa.String(length=255)),
- sa.Column('delta', sa.Integer, nullable=False),
- sa.Column('expire', sa.DateTime),
- sa.Column('deleted', sa.Integer),
- sa.Column('user_id', sa.String(length=255)),
- sa.Index('ix_reservations_project_id', 'project_id'),
- sa.Index('ix_reservations_user_id_deleted', 'user_id', 'deleted'),
- sa.Index('reservations_uuid_idx', 'uuid'),
- sa.Index('reservations_deleted_expire_idx', 'deleted', 'expire'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- resource_providers = sa.Table('resource_providers', meta,
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('uuid', sa.String(36), nullable=False),
- sa.Column('name', sa.Unicode(200), nullable=True),
- sa.Column('generation', sa.Integer, default=0),
- sa.Column('can_host', sa.Integer, default=0),
- UniqueConstraint('uuid', name='uniq_resource_providers0uuid'),
- UniqueConstraint('name', name='uniq_resource_providers0name'),
- sa.Index('resource_providers_name_idx', 'name'),
- sa.Index('resource_providers_uuid_idx', 'uuid'),
- mysql_engine='InnoDB',
- mysql_charset='latin1',
- )
-
- resource_provider_aggregates = sa.Table(
- 'resource_provider_aggregates', meta,
- sa.Column(
- 'resource_provider_id', sa.Integer, primary_key=True,
- nullable=False),
- sa.Column(
- 'aggregate_id', sa.Integer, primary_key=True, nullable=False),
- sa.Index(
- 'resource_provider_aggregates_aggregate_id_idx', 'aggregate_id'),
- mysql_engine='InnoDB',
- mysql_charset='latin1',
- )
-
- s3_images = sa.Table('s3_images', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('uuid', sa.String(length=36), nullable=False),
- sa.Column('deleted', sa.Integer),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- security_group_instance_association = sa.Table(
- 'security_group_instance_association', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column(
- 'security_group_id', sa.Integer,
- sa.ForeignKey(
- 'security_groups.id',
- name='security_group_instance_association_ibfk_1'),
- ),
- sa.Column(
- 'instance_uuid', sa.String(length=36),
- sa.ForeignKey(
- 'instances.uuid',
- name='security_group_instance_association_instance_uuid_fkey'),
- ),
- sa.Column('deleted', sa.Integer),
- sa.Index(
- 'security_group_instance_association_instance_uuid_idx',
- 'instance_uuid'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- security_group_rules = sa.Table('security_group_rules', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column(
- 'parent_group_id', sa.Integer,
- sa.ForeignKey('security_groups.id')),
- sa.Column('protocol', sa.String(length=255)),
- sa.Column('from_port', sa.Integer),
- sa.Column('to_port', sa.Integer),
- sa.Column('cidr', Inet()),
- sa.Column('group_id', sa.Integer, sa.ForeignKey('security_groups.id')),
- sa.Column('deleted', sa.Integer),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- security_groups = sa.Table('security_groups', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('name', sa.String(length=255)),
- sa.Column('description', sa.String(length=255)),
- sa.Column('user_id', sa.String(length=255)),
- sa.Column('project_id', sa.String(length=255)),
- sa.Column('deleted', sa.Integer),
- UniqueConstraint(
- 'project_id', 'name', 'deleted',
- name='uniq_security_groups0project_id0name0deleted'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- security_group_default_rules = sa.Table(
- 'security_group_default_rules', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('deleted', sa.Integer, default=0),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('protocol', sa.String(length=5)),
- sa.Column('from_port', sa.Integer),
- sa.Column('to_port', sa.Integer),
- sa.Column('cidr', Inet()),
- mysql_engine='InnoDB',
- mysql_charset='utf8',
- )
-
- services = sa.Table('services', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('host', sa.String(length=255)),
- sa.Column('binary', sa.String(length=255)),
- sa.Column('topic', sa.String(length=255)),
- sa.Column('report_count', sa.Integer, nullable=False),
- sa.Column('disabled', sa.Boolean),
- sa.Column('deleted', sa.Integer),
- sa.Column('disabled_reason', sa.String(length=255)),
- sa.Column('last_seen_up', sa.DateTime, nullable=True),
- # NOTE(stephenfin): This was originally added by sqlalchemy-migrate
- # which did not generate the constraints
- sa.Column(
- 'forced_down', sa.Boolean(create_constraint=False), default=False),
- sa.Column('version', sa.Integer, default=0),
- sa.Column('uuid', sa.String(36), nullable=True),
- sa.Index('services_uuid_idx', 'uuid', unique=True),
- UniqueConstraint(
- 'host', 'topic', 'deleted',
- name='uniq_services0host0topic0deleted'),
- UniqueConstraint(
- 'host', 'binary', 'deleted',
- name='uniq_services0host0binary0deleted'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- snapshot_id_mappings = sa.Table('snapshot_id_mappings', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('uuid', sa.String(length=36), nullable=False),
- sa.Column('deleted', sa.Integer),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- snapshots = sa.Table('snapshots', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column(
- 'id', sa.String(length=36), primary_key=True, nullable=False),
- sa.Column('volume_id', sa.String(length=36), nullable=False),
- sa.Column('user_id', sa.String(length=255)),
- sa.Column('project_id', sa.String(length=255)),
- sa.Column('status', sa.String(length=255)),
- sa.Column('progress', sa.String(length=255)),
- sa.Column('volume_size', sa.Integer),
- sa.Column('scheduled_at', sa.DateTime),
- sa.Column('display_name', sa.String(length=255)),
- sa.Column('display_description', sa.String(length=255)),
- sa.Column('deleted', sa.String(length=36)),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- tags = sa.Table('tags', meta,
- sa.Column(
- 'resource_id', sa.String(36), primary_key=True, nullable=False),
- sa.Column('tag', sa.Unicode(80), primary_key=True, nullable=False),
- sa.Index('tags_tag_idx', 'tag'),
- mysql_engine='InnoDB',
- mysql_charset='utf8',
- )
-
- task_log = sa.Table('task_log', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('task_name', sa.String(length=255), nullable=False),
- sa.Column('state', sa.String(length=255), nullable=False),
- sa.Column('host', sa.String(length=255), nullable=False),
- sa.Column('period_beginning', sa.DateTime, nullable=False),
- sa.Column('period_ending', sa.DateTime, nullable=False),
- sa.Column('message', sa.String(length=255), nullable=False),
- sa.Column('task_items', sa.Integer),
- sa.Column('errors', sa.Integer),
- sa.Column('deleted', sa.Integer),
- sa.Index('ix_task_log_period_beginning', 'period_beginning'),
- sa.Index('ix_task_log_host', 'host'),
- sa.Index('ix_task_log_period_ending', 'period_ending'),
- UniqueConstraint(
- 'task_name', 'host', 'period_beginning', 'period_ending',
- name='uniq_task_log0task_name0host0period_beginning0period_ending',
- ),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- virtual_interfaces = sa.Table('virtual_interfaces', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('address', sa.String(length=255)),
- sa.Column('network_id', sa.Integer),
- sa.Column('uuid', sa.String(length=36)),
- sa.Column(
- 'instance_uuid', sa.String(length=36),
- sa.ForeignKey(
- 'instances.uuid',
- name='virtual_interfaces_instance_uuid_fkey'),
- nullable=True),
- sa.Column('deleted', sa.Integer),
- sa.Column('tag', sa.String(255)),
- sa.Index('virtual_interfaces_instance_uuid_fkey', 'instance_uuid'),
- sa.Index('virtual_interfaces_network_id_idx', 'network_id'),
- sa.Index('virtual_interfaces_uuid_idx', 'uuid'),
- UniqueConstraint(
- 'address', 'deleted',
- name='uniq_virtual_interfaces0address0deleted'),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- volume_id_mappings = sa.Table('volume_id_mappings', meta,
- sa.Column('created_at', sa.DateTime),
- sa.Column('updated_at', sa.DateTime),
- sa.Column('deleted_at', sa.DateTime),
- sa.Column('id', sa.Integer, primary_key=True, nullable=False),
- sa.Column('uuid', sa.String(length=36), nullable=False),
- sa.Column('deleted', sa.Integer),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- volume_usage_cache = sa.Table('volume_usage_cache', meta,
- sa.Column('created_at', sa.DateTime(timezone=False)),
- sa.Column('updated_at', sa.DateTime(timezone=False)),
- sa.Column('deleted_at', sa.DateTime(timezone=False)),
- sa.Column('id', sa.Integer(), primary_key=True, nullable=False),
- sa.Column('volume_id', sa.String(36), nullable=False),
- sa.Column('tot_last_refreshed', sa.DateTime(timezone=False)),
- sa.Column('tot_reads', sa.BigInteger(), default=0),
- sa.Column('tot_read_bytes', sa.BigInteger(), default=0),
- sa.Column('tot_writes', sa.BigInteger(), default=0),
- sa.Column('tot_write_bytes', sa.BigInteger(), default=0),
- sa.Column('curr_last_refreshed', sa.DateTime(timezone=False)),
- sa.Column('curr_reads', sa.BigInteger(), default=0),
- sa.Column('curr_read_bytes', sa.BigInteger(), default=0),
- sa.Column('curr_writes', sa.BigInteger(), default=0),
- sa.Column('curr_write_bytes', sa.BigInteger(), default=0),
- sa.Column('deleted', sa.Integer),
- sa.Column('instance_uuid', sa.String(length=36)),
- sa.Column('project_id', sa.String(length=36)),
- sa.Column('user_id', sa.String(length=64)),
- sa.Column('availability_zone', sa.String(length=255)),
- mysql_engine='InnoDB',
- mysql_charset='utf8'
- )
-
- # create all tables
- tables = [instances, aggregates, console_auth_tokens,
- console_pools, instance_types,
- security_groups, snapshots,
- # those that are children and others later
- agent_builds, aggregate_hosts, aggregate_metadata,
- block_device_mapping, bw_usage_cache, cells,
- certificates, compute_nodes, consoles,
- dns_domains, fixed_ips, floating_ips,
- instance_faults, instance_id_mappings, instance_info_caches,
- instance_metadata, instance_system_metadata,
- instance_type_extra_specs, instance_type_projects,
- instance_actions, instance_actions_events, instance_extra,
- groups, group_policy, group_member,
- key_pairs, migrations, networks,
- pci_devices, provider_fw_rules, quota_classes, quota_usages,
- quotas, project_user_quotas,
- reservations, s3_images, security_group_instance_association,
- security_group_rules, security_group_default_rules,
- services, snapshot_id_mappings, tags, task_log,
- virtual_interfaces,
- volume_id_mappings,
- volume_usage_cache,
- resource_providers, inventories, allocations,
- resource_provider_aggregates]
-
- for table in tables:
- try:
- table.create()
- except Exception:
- LOG.info(repr(table))
- LOG.exception('Exception while creating table.')
- raise
-
- # MySQL specific indexes
- if migrate_engine.name == 'mysql':
- # NOTE(stephenfin): For some reason, we have to put this within the if
- # statement to avoid it being evaluated for the sqlite case. Even
- # though we don't call create except in the MySQL case... Failure to do
- # this will result in the following ugly error message:
- #
- # sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such
- # index: instance_type_id
- #
- # Yeah, I don't get it either...
- mysql_specific_indexes = [
- sa.Index(
- 'instance_type_id',
- instance_type_projects.c.instance_type_id),
- sa.Index('usage_id', reservations.c.usage_id),
- sa.Index(
- 'security_group_id',
- security_group_instance_association.c.security_group_id),
- ]
-
- for index in mysql_specific_indexes:
- index.create(migrate_engine)
-
- if migrate_engine.name == 'mysql':
- # In Folsom we explicitly converted migrate_version to UTF8.
- with migrate_engine.connect() as conn:
- conn.exec_driver_sql(
- 'ALTER TABLE migrate_version CONVERT TO CHARACTER SET utf8'
- )
- # Set default DB charset to UTF8.
- conn.exec_driver_sql(
- 'ALTER DATABASE `%s` DEFAULT CHARACTER SET utf8' % (
- migrate_engine.url.database,
- )
- )
-
- # NOTE(cdent): The resource_providers table is defined as latin1 to
- # be more efficient. Now we need the name column to be UTF8. We
- # modify it here otherwise the declarative handling in sqlalchemy
- # gets confused.
- conn.exec_driver_sql(
- 'ALTER TABLE resource_providers MODIFY name '
- 'VARCHAR(200) CHARACTER SET utf8'
- )
-
- _create_shadow_tables(migrate_engine)
-
- # TODO(stephenfin): Fix these various bugs in a follow-up
-
- # 298_mysql_extra_specs_binary_collation; we should update the shadow table
- # also
-
- if migrate_engine.name == 'mysql':
- with migrate_engine.connect() as conn:
- # Use binary collation for extra specs table
- conn.exec_driver_sql(
- 'ALTER TABLE instance_type_extra_specs '
- 'CONVERT TO CHARACTER SET utf8 '
- 'COLLATE utf8_bin'
- )
diff --git a/nova/db/main/legacy_migrations/versions/403_placeholder.py b/nova/db/main/legacy_migrations/versions/403_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/main/legacy_migrations/versions/403_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/versions/404_placeholder.py b/nova/db/main/legacy_migrations/versions/404_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/main/legacy_migrations/versions/404_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/versions/405_placeholder.py b/nova/db/main/legacy_migrations/versions/405_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/main/legacy_migrations/versions/405_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/versions/406_placeholder.py b/nova/db/main/legacy_migrations/versions/406_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/main/legacy_migrations/versions/406_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/versions/407_placeholder.py b/nova/db/main/legacy_migrations/versions/407_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/main/legacy_migrations/versions/407_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/versions/408_placeholder.py b/nova/db/main/legacy_migrations/versions/408_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/main/legacy_migrations/versions/408_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/versions/409_placeholder.py b/nova/db/main/legacy_migrations/versions/409_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/main/legacy_migrations/versions/409_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/versions/410_placeholder.py b/nova/db/main/legacy_migrations/versions/410_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/main/legacy_migrations/versions/410_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/versions/411_placeholder.py b/nova/db/main/legacy_migrations/versions/411_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/main/legacy_migrations/versions/411_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/versions/412_placeholder.py b/nova/db/main/legacy_migrations/versions/412_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/main/legacy_migrations/versions/412_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/versions/413_placeholder.py b/nova/db/main/legacy_migrations/versions/413_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/main/legacy_migrations/versions/413_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/versions/414_placeholder.py b/nova/db/main/legacy_migrations/versions/414_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/main/legacy_migrations/versions/414_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/versions/415_placeholder.py b/nova/db/main/legacy_migrations/versions/415_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/main/legacy_migrations/versions/415_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/versions/416_placeholder.py b/nova/db/main/legacy_migrations/versions/416_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/main/legacy_migrations/versions/416_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/versions/417_placeholder.py b/nova/db/main/legacy_migrations/versions/417_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/main/legacy_migrations/versions/417_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/versions/418_placeholder.py b/nova/db/main/legacy_migrations/versions/418_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/main/legacy_migrations/versions/418_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/versions/419_placeholder.py b/nova/db/main/legacy_migrations/versions/419_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/main/legacy_migrations/versions/419_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/versions/420_placeholder.py b/nova/db/main/legacy_migrations/versions/420_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/main/legacy_migrations/versions/420_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/versions/421_placeholder.py b/nova/db/main/legacy_migrations/versions/421_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/main/legacy_migrations/versions/421_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/versions/422_placeholder.py b/nova/db/main/legacy_migrations/versions/422_placeholder.py
deleted file mode 100644
index 7a93224504..0000000000
--- a/nova/db/main/legacy_migrations/versions/422_placeholder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-# This is a placeholder for backports.
-# Do not use this number for new work. New work starts after
-# all the placeholders.
-#
-# See this for more information:
-# http://lists.openstack.org/pipermail/openstack-dev/2013-March/006827.html
-
-
-def upgrade(migrate_engine):
- pass
diff --git a/nova/db/main/legacy_migrations/versions/__init__.py b/nova/db/main/legacy_migrations/versions/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
--- a/nova/db/main/legacy_migrations/versions/__init__.py
+++ /dev/null
diff --git a/nova/db/migration.py b/nova/db/migration.py
index 80410c3192..2b185af1a6 100644
--- a/nova/db/migration.py
+++ b/nova/db/migration.py
@@ -19,24 +19,12 @@ import os
from alembic import command as alembic_api
from alembic import config as alembic_config
from alembic.runtime import migration as alembic_migration
-from migrate import exceptions as migrate_exceptions
-from migrate.versioning import api as migrate_api
-from migrate.versioning import repository as migrate_repository
from oslo_log import log as logging
from nova.db.api import api as api_db_api
from nova.db.main import api as main_db_api
from nova import exception
-MIGRATE_INIT_VERSION = {
- 'main': 401,
- 'api': 66,
-}
-ALEMBIC_INIT_VERSION = {
- 'main': '8f2f1571d55b',
- 'api': 'd67eeaabee36',
-}
-
LOG = logging.getLogger(__name__)
@@ -48,16 +36,6 @@ def _get_engine(database='main', context=None):
return api_db_api.get_engine()
-def _find_migrate_repo(database='main'):
- """Get the path for the migrate repository."""
-
- path = os.path.join(
- os.path.abspath(os.path.dirname(__file__)),
- database, 'legacy_migrations')
-
- return migrate_repository.Repository(path)
-
-
def _find_alembic_conf(database='main'):
"""Get the path for the alembic repository."""
@@ -73,35 +51,6 @@ def _find_alembic_conf(database='main'):
return config
-def _is_database_under_migrate_control(engine, repository):
- try:
- migrate_api.db_version(engine, repository)
- return True
- except migrate_exceptions.DatabaseNotControlledError:
- return False
-
-
-def _is_database_under_alembic_control(engine):
- with engine.connect() as conn:
- context = alembic_migration.MigrationContext.configure(conn)
- return bool(context.get_current_revision())
-
-
-def _init_alembic_on_legacy_database(engine, database, repository, config):
- """Init alembic in an existing environment with sqlalchemy-migrate."""
- LOG.info(
- 'The database is still under sqlalchemy-migrate control; '
- 'applying any remaining sqlalchemy-migrate-based migrations '
- 'and fake applying the initial alembic migration'
- )
- migrate_api.upgrade(engine, repository)
-
- # re-use the connection rather than creating a new one
- with engine.begin() as connection:
- config.attributes['connection'] = connection
- alembic_api.stamp(config, ALEMBIC_INIT_VERSION[database])
-
-
def _upgrade_alembic(engine, config, version):
# re-use the connection rather than creating a new one
with engine.begin() as connection:
@@ -126,7 +75,6 @@ def db_sync(version=None, database='main', context=None):
engine = _get_engine(database, context=context)
- repository = _find_migrate_repo(database)
config = _find_alembic_conf(database)
# discard the URL stored in alembic.ini in favour of the URL configured
# for the engine, casting from 'sqlalchemy.engine.url.URL' to str in the
@@ -138,16 +86,6 @@ def db_sync(version=None, database='main', context=None):
url = str(engine.url).replace('%', '%%')
config.set_main_option('sqlalchemy.url', url)
- # if we're in a deployment where sqlalchemy-migrate is already present,
- # then apply all the updates for that and fake apply the initial alembic
- # migration; if we're not then 'upgrade' will take care of everything
- # this should be a one-time operation
- if (
- _is_database_under_migrate_control(engine, repository) and
- not _is_database_under_alembic_control(engine)
- ):
- _init_alembic_on_legacy_database(engine, database, repository, config)
-
# apply anything later
LOG.info('Applying migration(s)')
@@ -161,17 +99,10 @@ def db_version(database='main', context=None):
if database not in ('main', 'api'):
raise exception.Invalid('%s is not a valid database' % database)
- repository = _find_migrate_repo(database)
engine = _get_engine(database, context=context)
- migrate_version = None
- if _is_database_under_migrate_control(engine, repository):
- migrate_version = migrate_api.db_version(engine, repository)
-
- alembic_version = None
- if _is_database_under_alembic_control(engine):
- with engine.connect() as conn:
- m_context = alembic_migration.MigrationContext.configure(conn)
- alembic_version = m_context.get_current_revision()
+ with engine.connect() as conn:
+ m_context = alembic_migration.MigrationContext.configure(conn)
+ version = m_context.get_current_revision()
- return alembic_version or migrate_version
+ return version
diff --git a/nova/exception.py b/nova/exception.py
index f5993e79f8..0c0ffa85a1 100644
--- a/nova/exception.py
+++ b/nova/exception.py
@@ -2517,7 +2517,7 @@ class DuplicateRecord(NovaException):
class NotSupportedComputeForEvacuateV295(NotSupported):
- msg_fmt = _("Starting to microversion 2.95, evacuate API will stop "
+ msg_fmt = _("Starting with microversion 2.95, evacuate API will stop "
"instance on destination. To evacuate before upgrades are "
"complete please use an older microversion. Required version "
"for compute %(expected), current version %(currently)s")
diff --git a/nova/filesystem.py b/nova/filesystem.py
new file mode 100644
index 0000000000..5394d2d835
--- /dev/null
+++ b/nova/filesystem.py
@@ -0,0 +1,59 @@
+# 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.
+
+"""Functions to address filesystem calls, particularly sysfs."""
+
+import os
+
+from oslo_log import log as logging
+
+from nova import exception
+
+LOG = logging.getLogger(__name__)
+
+
+SYS = '/sys'
+
+
+# NOTE(bauzas): this method is deliberately not wrapped in a privsep entrypoint
+def read_sys(path: str) -> str:
+ """Reads the content of a file in the sys filesystem.
+
+ :param path: relative or absolute. If relative, will be prefixed by /sys.
+ :returns: contents of that file.
+ :raises: nova.exception.FileNotFound if we can't read that file.
+ """
+ try:
+ # The path can be absolute with a /sys prefix but that's fine.
+ with open(os.path.join(SYS, path), mode='r') as data:
+ return data.read()
+ except (OSError, ValueError) as exc:
+ raise exception.FileNotFound(file_path=path) from exc
+
+
+# NOTE(bauzas): this method is deliberately not wrapped in a privsep entrypoint
+# In order to correctly use it, you need to decorate the caller with a specific
+# privsep entrypoint.
+def write_sys(path: str, data: str) -> None:
+ """Writes the content of a file in the sys filesystem with data.
+
+ :param path: relative or absolute. If relative, will be prefixed by /sys.
+ :param data: the data to write.
+ :returns: contents of that file.
+ :raises: nova.exception.FileNotFound if we can't write that file.
+ """
+ try:
+ # The path can be absolute with a /sys prefix but that's fine.
+ with open(os.path.join(SYS, path), mode='w') as fd:
+ fd.write(data)
+ except (OSError, ValueError) as exc:
+ raise exception.FileNotFound(file_path=path) from exc
diff --git a/nova/network/neutron.py b/nova/network/neutron.py
index 27e7d06455..6c9f19f010 100644
--- a/nova/network/neutron.py
+++ b/nova/network/neutron.py
@@ -612,10 +612,22 @@ class API:
raise exception.ExternalNetworkAttachForbidden(
network_uuid=net['id'])
+ def unbind_ports(self, context, ports, detach=True):
+ """Unbind and detach the given ports by clearing their
+ device_owner and dns_name.
+ The device_id will also be cleaned if detach=True.
+
+ :param context: The request context.
+ :param ports: list of port IDs.
+ """
+ neutron = get_client(context)
+ self._unbind_ports(context, ports, neutron, detach=detach)
+
def _unbind_ports(self, context, ports,
- neutron, port_client=None):
- """Unbind the given ports by clearing their device_id,
+ neutron, port_client=None, detach=True):
+ """Unbind and detach the given ports by clearing their
device_owner and dns_name.
+ The device_id will also be cleaned if detach=True.
:param context: The request context.
:param ports: list of port IDs.
@@ -638,11 +650,12 @@ class API:
port_req_body: ty.Dict[str, ty.Any] = {
'port': {
- 'device_id': '',
- 'device_owner': '',
constants.BINDING_HOST_ID: None,
}
}
+ if detach:
+ port_req_body['port']['device_id'] = ''
+ port_req_body['port']['device_owner'] = ''
try:
port = self._show_port(
context, port_id, neutron_client=neutron,
diff --git a/nova/objects/service.py b/nova/objects/service.py
index 0ed443ef17..1a4629cc84 100644
--- a/nova/objects/service.py
+++ b/nova/objects/service.py
@@ -237,15 +237,30 @@ SERVICE_VERSION_HISTORY = (
# local node identity for single-node systems.
NODE_IDENTITY_VERSION = 65
-# This is used to raise an error at service startup if older than N-1 computes
-# are detected. Update this at the beginning of every release cycle to point to
-# the smallest service version that was added in N-1.
-OLDEST_SUPPORTED_SERVICE_VERSION = 'Yoga'
+# This is used to raise an error at service startup if older than supported
+# computes are detected.
+# NOTE(sbauza) : Please modify it this way :
+# * At the beginning of a non-SLURP release (eg. 2023.2 Bobcat) (or just after
+# the previous SLURP release RC1, like 2023.1 Antelope), please bump
+# OLDEST_SUPPORTED_SERVICE_VERSION to the previous SLURP release (in that
+# example, Antelope)
+# * At the beginning of a SLURP release (eg. 2024.1 C) (or just after the
+# previous non-SLURP release RC1, like 2023.2 Bobcat), please keep the
+# OLDEST_SUPPORTED_SERVICE_VERSION value using the previous SLURP release
+# (in that example, Antelope)
+# * At the end of any release (SLURP or non-SLURP), please modify
+# SERVICE_VERSION_ALIASES to add a key/value with key being the release name
+# and value be the latest service version that the release supports (for
+# example, before Bobcat RC1, please add 'Bobcat': XX where X is the latest
+# servion version that was added)
+OLDEST_SUPPORTED_SERVICE_VERSION = 'Antelope'
SERVICE_VERSION_ALIASES = {
'Victoria': 52,
'Wallaby': 54,
'Xena': 57,
'Yoga': 61,
+ 'Zed': 64,
+ 'Antelope': 66,
}
diff --git a/nova/pci/request.py b/nova/pci/request.py
index 27ada6c045..8ae2385549 100644
--- a/nova/pci/request.py
+++ b/nova/pci/request.py
@@ -168,7 +168,7 @@ def _get_alias_from_config() -> Alias:
def _translate_alias_to_requests(
- alias_spec: str, affinity_policy: str = None,
+ alias_spec: str, affinity_policy: ty.Optional[str] = None,
) -> ty.List['objects.InstancePCIRequest']:
"""Generate complete pci requests from pci aliases in extra_spec."""
pci_aliases = _get_alias_from_config()
@@ -255,7 +255,7 @@ def get_instance_pci_request_from_vif(
def get_pci_requests_from_flavor(
- flavor: 'objects.Flavor', affinity_policy: str = None,
+ flavor: 'objects.Flavor', affinity_policy: ty.Optional[str] = None,
) -> 'objects.InstancePCIRequests':
"""Validate and return PCI requests.
diff --git a/nova/pci/stats.py b/nova/pci/stats.py
index 5c5f7c669c..c6e4844b34 100644
--- a/nova/pci/stats.py
+++ b/nova/pci/stats.py
@@ -82,7 +82,7 @@ class PciDeviceStats(object):
self,
numa_topology: 'objects.NUMATopology',
stats: 'objects.PCIDevicePoolList' = None,
- dev_filter: whitelist.Whitelist = None,
+ dev_filter: ty.Optional[whitelist.Whitelist] = None,
) -> None:
self.numa_topology = numa_topology
self.pools = (
diff --git a/nova/pci/whitelist.py b/nova/pci/whitelist.py
index 8862a0ef4f..152cc29ca6 100644
--- a/nova/pci/whitelist.py
+++ b/nova/pci/whitelist.py
@@ -33,7 +33,7 @@ class Whitelist(object):
assignable.
"""
- def __init__(self, whitelist_spec: str = None) -> None:
+ def __init__(self, whitelist_spec: ty.Optional[str] = None) -> None:
"""White list constructor
For example, the following json string specifies that devices whose
diff --git a/nova/scheduler/client/report.py b/nova/scheduler/client/report.py
index 1242752be1..7c14f3d7ef 100644
--- a/nova/scheduler/client/report.py
+++ b/nova/scheduler/client/report.py
@@ -1047,7 +1047,7 @@ class SchedulerReportClient(object):
context: nova_context.RequestContext,
rp_uuid: str,
traits: ty.Iterable[str],
- generation: int = None
+ generation: ty.Optional[int] = None
):
"""Replace a provider's traits with those specified.
diff --git a/nova/scheduler/filters/__init__.py b/nova/scheduler/filters/__init__.py
index 74e24b7bc3..785a13279e 100644
--- a/nova/scheduler/filters/__init__.py
+++ b/nova/scheduler/filters/__init__.py
@@ -16,8 +16,12 @@
"""
Scheduler host filters
"""
+from oslo_log import log as logging
+
from nova import filters
+LOG = logging.getLogger(__name__)
+
class BaseHostFilter(filters.BaseFilter):
"""Base class for host filters."""
@@ -53,6 +57,43 @@ class BaseHostFilter(filters.BaseFilter):
raise NotImplementedError()
+class CandidateFilterMixin:
+ """Mixing that helps to implement a Filter that needs to filter host by
+ Placement allocation candidates.
+ """
+
+ def filter_candidates(self, host_state, filter_func):
+ """Checks still viable allocation candidates by the filter_func and
+ keep only those that are passing it.
+
+ :param host_state: HostState object holding the list of still viable
+ allocation candidates
+ :param filter_func: A callable that takes an allocation candidate and
+ returns a True like object if the candidate passed the filter or a
+ False like object if it doesn't.
+ """
+ good_candidates = []
+ for candidate in host_state.allocation_candidates:
+ LOG.debug(
+ f'{self.__class__.__name__} tries allocation candidate: '
+ f'{candidate}',
+ )
+ if filter_func(candidate):
+ LOG.debug(
+ f'{self.__class__.__name__} accepted allocation '
+ f'candidate: {candidate}',
+ )
+ good_candidates.append(candidate)
+ else:
+ LOG.debug(
+ f'{self.__class__.__name__} rejected allocation '
+ f'candidate: {candidate}',
+ )
+
+ host_state.allocation_candidates = good_candidates
+ return good_candidates
+
+
class HostFilterHandler(filters.BaseFilterHandler):
def __init__(self):
super(HostFilterHandler, self).__init__(BaseHostFilter)
diff --git a/nova/scheduler/filters/numa_topology_filter.py b/nova/scheduler/filters/numa_topology_filter.py
index 7ec9ca5648..ae50db90e5 100644
--- a/nova/scheduler/filters/numa_topology_filter.py
+++ b/nova/scheduler/filters/numa_topology_filter.py
@@ -20,7 +20,10 @@ from nova.virt import hardware
LOG = logging.getLogger(__name__)
-class NUMATopologyFilter(filters.BaseHostFilter):
+class NUMATopologyFilter(
+ filters.BaseHostFilter,
+ filters.CandidateFilterMixin,
+):
"""Filter on requested NUMA topology."""
# NOTE(sean-k-mooney): In change I0322d872bdff68936033a6f5a54e8296a6fb343
@@ -97,34 +100,19 @@ class NUMATopologyFilter(filters.BaseHostFilter):
if network_metadata:
limits.network_metadata = network_metadata
- good_candidates = []
- for candidate in host_state.allocation_candidates:
- LOG.debug(
- 'NUMATopologyFilter tries allocation candidate: %s, %s',
- candidate, requested_topology
- )
- instance_topology = (hardware.numa_fit_instance_to_host(
- host_topology, requested_topology,
+ good_candidates = self.filter_candidates(
+ host_state,
+ lambda candidate: hardware.numa_fit_instance_to_host(
+ host_topology,
+ requested_topology,
limits=limits,
pci_requests=pci_requests,
pci_stats=host_state.pci_stats,
- provider_mapping=candidate['mappings'],
- ))
- if instance_topology:
- LOG.debug(
- 'NUMATopologyFilter accepted allocation candidate: %s',
- candidate
- )
- good_candidates.append(candidate)
- else:
- LOG.debug(
- 'NUMATopologyFilter rejected allocation candidate: %s',
- candidate
- )
-
- host_state.allocation_candidates = good_candidates
-
- if not host_state.allocation_candidates:
+ provider_mapping=candidate["mappings"],
+ ),
+ )
+
+ if not good_candidates:
LOG.debug("%(host)s, %(node)s fails NUMA topology "
"requirements. The instance does not fit on this "
"host.", {'host': host_state.host,
diff --git a/nova/scheduler/filters/pci_passthrough_filter.py b/nova/scheduler/filters/pci_passthrough_filter.py
index 36f0b5901c..992879072a 100644
--- a/nova/scheduler/filters/pci_passthrough_filter.py
+++ b/nova/scheduler/filters/pci_passthrough_filter.py
@@ -20,7 +20,10 @@ from nova.scheduler import filters
LOG = logging.getLogger(__name__)
-class PciPassthroughFilter(filters.BaseHostFilter):
+class PciPassthroughFilter(
+ filters.BaseHostFilter,
+ filters.CandidateFilterMixin,
+):
"""Pci Passthrough Filter based on PCI request
Filter that schedules instances on a host if the host has devices
@@ -54,28 +57,12 @@ class PciPassthroughFilter(filters.BaseHostFilter):
{'host_state': host_state, 'requests': pci_requests})
return False
- good_candidates = []
- for candidate in host_state.allocation_candidates:
- LOG.debug(
- 'PciPassthroughFilter tries allocation candidate: %s',
- candidate
- )
- if host_state.pci_stats.support_requests(
- pci_requests.requests,
- provider_mapping=candidate['mappings']
- ):
- LOG.debug(
- 'PciPassthroughFilter accepted allocation candidate: %s',
- candidate
- )
- good_candidates.append(candidate)
- else:
- LOG.debug(
- 'PciPassthroughFilter rejected allocation candidate: %s',
- candidate
- )
-
- host_state.allocation_candidates = good_candidates
+ good_candidates = self.filter_candidates(
+ host_state,
+ lambda candidate: host_state.pci_stats.support_requests(
+ pci_requests.requests, provider_mapping=candidate["mappings"]
+ ),
+ )
if not good_candidates:
LOG.debug("%(host_state)s doesn't have the required PCI devices"
diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py
index 11581c4f2d..620519d403 100644
--- a/nova/scheduler/manager.py
+++ b/nova/scheduler/manager.py
@@ -23,6 +23,7 @@ import collections
import copy
import random
+from keystoneauth1 import exceptions as ks_exc
from oslo_log import log as logging
import oslo_messaging as messaging
from oslo_serialization import jsonutils
@@ -67,10 +68,42 @@ class SchedulerManager(manager.Manager):
self.host_manager = host_manager.HostManager()
self.servicegroup_api = servicegroup.API()
self.notifier = rpc.get_notifier('scheduler')
- self.placement_client = report.report_client_singleton()
+ self._placement_client = None
+
+ try:
+ # Test our placement client during initialization
+ self.placement_client
+ except (ks_exc.EndpointNotFound,
+ ks_exc.DiscoveryFailure,
+ ks_exc.RequestTimeout,
+ ks_exc.GatewayTimeout,
+ ks_exc.ConnectFailure) as e:
+ # Non-fatal, likely transient (although not definitely);
+ # continue startup but log the warning so that when things
+ # fail later, it will be clear why we can not do certain
+ # things.
+ LOG.warning('Unable to initialize placement client (%s); '
+ 'Continuing with startup, but scheduling '
+ 'will not be possible.', e)
+ except (ks_exc.MissingAuthPlugin,
+ ks_exc.Unauthorized) as e:
+ # This is almost definitely fatal mis-configuration. The
+ # Unauthorized error might be transient, but it is
+ # probably reasonable to consider it fatal.
+ LOG.error('Fatal error initializing placement client; '
+ 'config is incorrect or incomplete: %s', e)
+ raise
+ except Exception as e:
+ # Unknown/unexpected errors here are fatal
+ LOG.error('Fatal error initializing placement client: %s', e)
+ raise
super().__init__(service_name='scheduler', *args, **kwargs)
+ @property
+ def placement_client(self):
+ return report.report_client_singleton()
+
@periodic_task.periodic_task(
spacing=CONF.scheduler.discover_hosts_in_cells_interval,
run_immediately=True)
diff --git a/nova/scheduler/utils.py b/nova/scheduler/utils.py
index c7e6ffed97..02c44093bd 100644
--- a/nova/scheduler/utils.py
+++ b/nova/scheduler/utils.py
@@ -1080,6 +1080,17 @@ _SUPPORTS_SOFT_AFFINITY = None
_SUPPORTS_SOFT_ANTI_AFFINITY = None
+def reset_globals():
+ global _SUPPORTS_AFFINITY
+ _SUPPORTS_AFFINITY = None
+ global _SUPPORTS_ANTI_AFFINITY
+ _SUPPORTS_ANTI_AFFINITY = None
+ global _SUPPORTS_SOFT_AFFINITY
+ _SUPPORTS_SOFT_AFFINITY = None
+ global _SUPPORTS_SOFT_ANTI_AFFINITY
+ _SUPPORTS_SOFT_ANTI_AFFINITY = None
+
+
def _get_group_details(context, instance_uuid, user_group_hosts=None):
"""Provide group_hosts and group_policies sets related to instances if
those instances are belonging to a group and if corresponding filters are
diff --git a/nova/scheduler/weights/hypervisor_version.py b/nova/scheduler/weights/hypervisor_version.py
new file mode 100644
index 0000000000..0cd7b0a824
--- /dev/null
+++ b/nova/scheduler/weights/hypervisor_version.py
@@ -0,0 +1,39 @@
+# 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.
+
+"""
+Hypervisor Version Weigher. Weigh hosts by their relative hypervior version.
+
+The default is to select newer hosts. If you prefer
+to invert the behavior set the 'hypervisor_version_weight_multiplier' option
+to a negative number and the weighing has the opposite effect of the default.
+"""
+
+import nova.conf
+from nova.scheduler import utils
+from nova.scheduler import weights
+
+CONF = nova.conf.CONF
+
+
+class HypervisorVersionWeigher(weights.BaseHostWeigher):
+
+ def weight_multiplier(self, host_state):
+ """Override the weight multiplier."""
+ return utils.get_weight_multiplier(
+ host_state, 'hypervisor_version_weight_multiplier',
+ CONF.filter_scheduler.hypervisor_version_weight_multiplier)
+
+ def _weigh_object(self, host_state, weight_properties):
+ """Higher weights win. We want newer hosts by default."""
+ # convert None to 0
+ return host_state.hypervisor_version or 0
diff --git a/nova/test.py b/nova/test.py
index 0f7965ea33..e37967b06d 100644
--- a/nova/test.py
+++ b/nova/test.py
@@ -62,6 +62,7 @@ from nova import objects
from nova.objects import base as objects_base
from nova import quota
from nova.scheduler.client import report
+from nova.scheduler import utils as scheduler_utils
from nova.tests import fixtures as nova_fixtures
from nova.tests.unit import matchers
from nova import utils
@@ -310,6 +311,12 @@ class TestCase(base.BaseTestCase):
if self.STUB_COMPUTE_ID:
self.useFixture(nova_fixtures.ComputeNodeIdFixture())
+ # Reset globals indicating affinity filter support. Some tests may set
+ # self.flags(enabled_filters=...) which could make the affinity filter
+ # support globals get set to a non-default configuration which affects
+ # all other tests.
+ scheduler_utils.reset_globals()
+
def _setup_cells(self):
"""Setup a normal cellsv2 environment.
diff --git a/nova/tests/fixtures/__init__.py b/nova/tests/fixtures/__init__.py
index df254608fd..9ff4a2a601 100644
--- a/nova/tests/fixtures/__init__.py
+++ b/nova/tests/fixtures/__init__.py
@@ -16,6 +16,8 @@ from .cast_as_call import CastAsCallFixture # noqa: F401
from .cinder import CinderFixture # noqa: F401
from .conf import ConfFixture # noqa: F401, F403
from .cyborg import CyborgFixture # noqa: F401
+from .filesystem import SysFileSystemFixture # noqa: F401
+from .filesystem import TempFileSystemFixture # noqa: F401
from .glance import GlanceFixture # noqa: F401
from .libvirt import LibvirtFixture # noqa: F401
from .libvirt_imagebackend import LibvirtImageBackendFixture # noqa: F401
diff --git a/nova/tests/fixtures/filesystem.py b/nova/tests/fixtures/filesystem.py
new file mode 100644
index 0000000000..932d42fe27
--- /dev/null
+++ b/nova/tests/fixtures/filesystem.py
@@ -0,0 +1,81 @@
+# 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 os
+import shutil
+import tempfile
+from unittest import mock
+
+import fixtures
+
+from nova import filesystem
+from nova.virt.libvirt.cpu import core
+
+
+SYS = 'sys'
+
+
+class TempFileSystemFixture(fixtures.Fixture):
+ """Creates a fake / filesystem"""
+
+ def _setUp(self):
+ self.temp_dir = tempfile.TemporaryDirectory(prefix='fake_fs')
+ # NOTE(sbauza): I/O disk errors may raise an exception here, as we
+ # don't ignore them. If that's causing a problem in our CI jobs, the
+ # recommended solution is to use shutil.rmtree instead of cleanup()
+ # with ignore_errors parameter set to True (or wait for the minimum
+ # python version to be 3.10 as TemporaryDirectory will provide
+ # ignore_cleanup_errors parameter)
+ self.addCleanup(self.temp_dir.cleanup)
+
+
+class SysFileSystemFixture(TempFileSystemFixture):
+ """Creates a fake /sys filesystem"""
+
+ def __init__(self, cpus_supported=None):
+ self.cpus_supported = cpus_supported or 10
+
+ def _setUp(self):
+ super()._setUp()
+ self.sys_path = os.path.join(self.temp_dir.name, SYS)
+ self.addCleanup(shutil.rmtree, self.sys_path, ignore_errors=True)
+
+ sys_patcher = mock.patch(
+ 'nova.filesystem.SYS',
+ new_callable=mock.PropertyMock(return_value=self.sys_path))
+ self.sys_mock = sys_patcher.start()
+ self.addCleanup(sys_patcher.stop)
+
+ avail_path_patcher = mock.patch(
+ 'nova.virt.libvirt.cpu.core.AVAILABLE_PATH',
+ new_callable=mock.PropertyMock(
+ return_value=os.path.join(self.sys_path,
+ 'devices/system/cpu/present')))
+ self.avail_path_mock = avail_path_patcher.start()
+ self.addCleanup(avail_path_patcher.stop)
+
+ cpu_path_patcher = mock.patch(
+ 'nova.virt.libvirt.cpu.core.CPU_PATH_TEMPLATE',
+ new_callable=mock.PropertyMock(
+ return_value=os.path.join(self.sys_path,
+ 'devices/system/cpu/cpu%(core)s')))
+ self.cpu_path_mock = cpu_path_patcher.start()
+ self.addCleanup(cpu_path_patcher.stop)
+
+ for cpu_nr in range(self.cpus_supported):
+ cpu_dir = os.path.join(self.cpu_path_mock % {'core': cpu_nr})
+ os.makedirs(os.path.join(cpu_dir, 'cpufreq'))
+ filesystem.write_sys(
+ os.path.join(cpu_dir, 'cpufreq/scaling_governor'),
+ data='powersave')
+ filesystem.write_sys(core.AVAILABLE_PATH,
+ f'0-{self.cpus_supported - 1}')
diff --git a/nova/tests/functional/libvirt/test_pci_sriov_servers.py b/nova/tests/functional/libvirt/test_pci_sriov_servers.py
index 6b8b254af9..098a0e857b 100644
--- a/nova/tests/functional/libvirt/test_pci_sriov_servers.py
+++ b/nova/tests/functional/libvirt/test_pci_sriov_servers.py
@@ -1549,7 +1549,11 @@ class VDPAServersTest(_PCIServersWithMigrationTestBase):
'not supported for instance with vDPA ports',
ex.response.text)
+ # NOTE(sbauza): Now we're post-Antelope release, we don't need to support
+ # this test
def test_attach_interface_service_version_61(self):
+ self.flags(disable_compute_service_check_for_ffu=True,
+ group='workarounds')
with mock.patch(
"nova.objects.service.get_minimum_version_all_cells",
return_value=61
@@ -1578,7 +1582,11 @@ class VDPAServersTest(_PCIServersWithMigrationTestBase):
self.assertEqual(hostname, port['binding:host_id'])
self.assertEqual(server['id'], port['device_id'])
+ # NOTE(sbauza): Now we're post-Antelope release, we don't need to support
+ # this test
def test_detach_interface_service_version_61(self):
+ self.flags(disable_compute_service_check_for_ffu=True,
+ group='workarounds')
with mock.patch(
"nova.objects.service.get_minimum_version_all_cells",
return_value=61
@@ -1612,10 +1620,7 @@ class VDPAServersTest(_PCIServersWithMigrationTestBase):
# to any host but should still be owned by the vm
port = self.neutron.show_port(vdpa_port['id'])['port']
self.assertEqual(server['id'], port['device_id'])
- # FIXME(sean-k-mooney): we should be unbinding the port from
- # the host when we shelve offload but we don't today.
- # This is unrelated to vdpa port and is a general issue.
- self.assertEqual(hostname, port['binding:host_id'])
+ self.assertIsNone(port['binding:host_id'])
self.assertIn('binding:profile', port)
self.assertIsNone(server['OS-EXT-SRV-ATTR:hypervisor_hostname'])
self.assertIsNone(server['OS-EXT-SRV-ATTR:host'])
@@ -1637,9 +1642,7 @@ class VDPAServersTest(_PCIServersWithMigrationTestBase):
self.assertPCIDeviceCounts(hostname, total=num_pci, free=num_pci)
self.assertIsNone(server['OS-EXT-SRV-ATTR:hypervisor_hostname'])
port = self.neutron.show_port(vdpa_port['id'])['port']
- # FIXME(sean-k-mooney): shelve offload should unbind the port
- # self.assertEqual('', port['binding:host_id'])
- self.assertEqual(hostname, port['binding:host_id'])
+ self.assertIsNone(port['binding:host_id'])
server = self._unshelve_server(server)
self.assertPCIDeviceCounts(hostname, total=num_pci, free=num_pci - 2)
@@ -1670,9 +1673,7 @@ class VDPAServersTest(_PCIServersWithMigrationTestBase):
self.assertPCIDeviceCounts(source, total=num_pci, free=num_pci)
self.assertIsNone(server['OS-EXT-SRV-ATTR:hypervisor_hostname'])
port = self.neutron.show_port(vdpa_port['id'])['port']
- # FIXME(sean-k-mooney): shelve should unbind the port
- # self.assertEqual('', port['binding:host_id'])
- self.assertEqual(source, port['binding:host_id'])
+ self.assertIsNone(port['binding:host_id'])
# force the unshelve to the other host
self.api.put_service(
@@ -1871,7 +1872,11 @@ class VDPAServersTest(_PCIServersWithMigrationTestBase):
self.assertEqual(
dest, server['OS-EXT-SRV-ATTR:hypervisor_hostname'])
+ # NOTE(sbauza): Now we're post-Antelope release, we don't need to support
+ # this test
def test_suspend_and_resume_service_version_62(self):
+ self.flags(disable_compute_service_check_for_ffu=True,
+ group='workarounds')
with mock.patch(
"nova.objects.service.get_minimum_version_all_cells",
return_value=62
@@ -1890,7 +1895,11 @@ class VDPAServersTest(_PCIServersWithMigrationTestBase):
self.assertPCIDeviceCounts(source, total=num_pci, free=num_pci - 2)
self.assertEqual('ACTIVE', server['status'])
+ # NOTE(sbauza): Now we're post-Antelope release, we don't need to support
+ # this test
def test_live_migrate_service_version_62(self):
+ self.flags(disable_compute_service_check_for_ffu=True,
+ group='workarounds')
with mock.patch(
"nova.objects.service.get_minimum_version_all_cells",
return_value=62
@@ -3926,17 +3935,7 @@ class RemoteManagedServersTest(_PCIServersWithMigrationTestBase):
port = self.neutron.show_port(uuids.dpu_tunnel_port)['port']
self.assertIn('binding:profile', port)
- self.assertEqual(
- {
- 'pci_vendor_info': '15b3:101e',
- 'pci_slot': '0000:82:00.4',
- 'physical_network': None,
- 'pf_mac_address': '52:54:00:1e:59:02',
- 'vf_num': 3,
- 'card_serial_number': 'MT0000X00002',
- },
- port['binding:profile'],
- )
+ self.assertEqual({}, port['binding:profile'])
def test_suspend(self):
self.start_compute()
diff --git a/nova/tests/functional/libvirt/test_power_manage.py b/nova/tests/functional/libvirt/test_power_manage.py
new file mode 100644
index 0000000000..9f80446bd6
--- /dev/null
+++ b/nova/tests/functional/libvirt/test_power_manage.py
@@ -0,0 +1,270 @@
+# 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 context as nova_context
+from nova import exception
+from nova import objects
+from nova.tests import fixtures as nova_fixtures
+from nova.tests.fixtures import libvirt as fakelibvirt
+from nova.tests.functional.libvirt import base
+from nova.virt import hardware
+from nova.virt.libvirt.cpu import api as cpu_api
+
+
+class PowerManagementTestsBase(base.ServersTestBase):
+
+ ADDITIONAL_FILTERS = ['NUMATopologyFilter']
+
+ ADMIN_API = True
+
+ def setUp(self):
+ super(PowerManagementTestsBase, self).setUp()
+
+ self.ctxt = nova_context.get_admin_context()
+
+ # Mock the 'NUMATopologyFilter' filter, as most tests need to inspect
+ # this
+ host_manager = self.scheduler.manager.host_manager
+ numa_filter_class = host_manager.filter_cls_map['NUMATopologyFilter']
+ host_pass_mock = mock.Mock(wraps=numa_filter_class().host_passes)
+ _p = mock.patch('nova.scheduler.filters'
+ '.numa_topology_filter.NUMATopologyFilter.host_passes',
+ side_effect=host_pass_mock)
+ self.mock_filter = _p.start()
+ self.addCleanup(_p.stop)
+
+ # for the sake of resizing, we need to patch the two methods below
+ self.useFixture(fixtures.MockPatch(
+ 'nova.virt.libvirt.LibvirtDriver._get_instance_disk_info',
+ return_value=[]))
+ self.useFixture(fixtures.MockPatch('os.rename'))
+
+ self.useFixture(nova_fixtures.PrivsepFixture())
+
+ # Defining the main flavor for 4 vCPUs all pinned
+ self.extra_spec = {
+ 'hw:cpu_policy': 'dedicated',
+ 'hw:cpu_thread_policy': 'prefer',
+ }
+ self.pcpu_flavor_id = self._create_flavor(
+ vcpu=4, extra_spec=self.extra_spec)
+
+ def _assert_server_cpus_state(self, server, expected='online'):
+ inst = objects.Instance.get_by_uuid(self.ctxt, server['id'])
+ if not inst.numa_topology:
+ self.fail('Instance should have a NUMA topology in order to know '
+ 'its physical CPUs')
+ instance_pcpus = inst.numa_topology.cpu_pinning
+ self._assert_cpu_set_state(instance_pcpus, expected=expected)
+ return instance_pcpus
+
+ def _assert_cpu_set_state(self, cpu_set, expected='online'):
+ for i in cpu_set:
+ core = cpu_api.Core(i)
+ if expected == 'online':
+ self.assertTrue(core.online, f'{i} is not online')
+ elif expected == 'offline':
+ self.assertFalse(core.online, f'{i} is online')
+ elif expected == 'powersave':
+ self.assertEqual('powersave', core.governor)
+ elif expected == 'performance':
+ self.assertEqual('performance', core.governor)
+
+
+class PowerManagementTests(PowerManagementTestsBase):
+ """Test suite for a single host with 9 dedicated cores and 1 used for OS"""
+
+ def setUp(self):
+ super(PowerManagementTests, self).setUp()
+
+ self.useFixture(nova_fixtures.SysFileSystemFixture())
+
+ # Definining the CPUs to be pinned.
+ self.flags(cpu_dedicated_set='1-9', cpu_shared_set=None,
+ group='compute')
+ self.flags(vcpu_pin_set=None)
+ self.flags(cpu_power_management=True, group='libvirt')
+
+ self.flags(allow_resize_to_same_host=True)
+ self.host_info = fakelibvirt.HostInfo(cpu_nodes=1, cpu_sockets=1,
+ cpu_cores=5, cpu_threads=2)
+ self.compute1 = self.start_compute(host_info=self.host_info,
+ hostname='compute1')
+
+ # All cores are shutdown at startup, let's check.
+ cpu_dedicated_set = hardware.get_cpu_dedicated_set()
+ self._assert_cpu_set_state(cpu_dedicated_set, expected='offline')
+
+ def test_hardstop_compute_service_if_wrong_opt(self):
+ self.flags(cpu_dedicated_set=None, cpu_shared_set=None,
+ group='compute')
+ self.flags(vcpu_pin_set=None)
+ self.flags(cpu_power_management=True, group='libvirt')
+ self.assertRaises(exception.InvalidConfiguration,
+ self.start_compute, host_info=self.host_info,
+ hostname='compute2')
+
+ def test_create_server(self):
+ server = self._create_server(
+ flavor_id=self.pcpu_flavor_id,
+ expected_state='ACTIVE')
+ # Let's verify that the pinned CPUs are now online
+ self._assert_server_cpus_state(server, expected='online')
+
+ # Verify that the unused CPUs are still offline
+ inst = objects.Instance.get_by_uuid(self.ctxt, server['id'])
+ instance_pcpus = inst.numa_topology.cpu_pinning
+ cpu_dedicated_set = hardware.get_cpu_dedicated_set()
+ unused_cpus = cpu_dedicated_set - instance_pcpus
+ self._assert_cpu_set_state(unused_cpus, expected='offline')
+
+ def test_stop_start_server(self):
+ server = self._create_server(
+ flavor_id=self.pcpu_flavor_id,
+ expected_state='ACTIVE')
+
+ server = self._stop_server(server)
+ # Let's verify that the pinned CPUs are now stopped...
+ self._assert_server_cpus_state(server, expected='offline')
+
+ server = self._start_server(server)
+ # ...and now, they should be back.
+ self._assert_server_cpus_state(server, expected='online')
+
+ def test_resize(self):
+ server = self._create_server(
+ flavor_id=self.pcpu_flavor_id,
+ expected_state='ACTIVE')
+ server_pcpus = self._assert_server_cpus_state(server,
+ expected='online')
+
+ new_flavor_id = self._create_flavor(
+ vcpu=5, extra_spec=self.extra_spec)
+ self._resize_server(server, new_flavor_id)
+ server2_pcpus = self._assert_server_cpus_state(server,
+ expected='online')
+ # Even if the resize is not confirmed yet, the original guest is now
+ # destroyed so the cores are now offline.
+ self._assert_cpu_set_state(server_pcpus, expected='offline')
+
+ # let's revert the resize
+ self._revert_resize(server)
+ # So now the original CPUs will be online again, while the previous
+ # cores should be back offline.
+ self._assert_cpu_set_state(server_pcpus, expected='online')
+ self._assert_cpu_set_state(server2_pcpus, expected='offline')
+
+ def test_changing_strategy_fails(self):
+ # As a reminder, all cores have been shutdown before.
+ # Now we want to change the strategy and then we restart the service
+ self.flags(cpu_power_management_strategy='governor', group='libvirt')
+ # See, this is not possible as we would have offline CPUs.
+ self.assertRaises(exception.InvalidConfiguration,
+ self.restart_compute_service, hostname='compute1')
+
+
+class PowerManagementTestsGovernor(PowerManagementTestsBase):
+ """Test suite for speific governor usage (same 10-core host)"""
+
+ def setUp(self):
+ super(PowerManagementTestsGovernor, self).setUp()
+
+ self.useFixture(nova_fixtures.SysFileSystemFixture())
+
+ # Definining the CPUs to be pinned.
+ self.flags(cpu_dedicated_set='1-9', cpu_shared_set=None,
+ group='compute')
+ self.flags(vcpu_pin_set=None)
+ self.flags(cpu_power_management=True, group='libvirt')
+ self.flags(cpu_power_management_strategy='governor', group='libvirt')
+
+ self.flags(allow_resize_to_same_host=True)
+ self.host_info = fakelibvirt.HostInfo(cpu_nodes=1, cpu_sockets=1,
+ cpu_cores=5, cpu_threads=2)
+ self.compute1 = self.start_compute(host_info=self.host_info,
+ hostname='compute1')
+
+ def test_create(self):
+ cpu_dedicated_set = hardware.get_cpu_dedicated_set()
+ # With the governor strategy, cores are still online but run with a
+ # powersave governor.
+ self._assert_cpu_set_state(cpu_dedicated_set, expected='powersave')
+
+ # Now, start an instance
+ server = self._create_server(
+ flavor_id=self.pcpu_flavor_id,
+ expected_state='ACTIVE')
+ # When pinned cores are run, the governor state is now performance
+ self._assert_server_cpus_state(server, expected='performance')
+
+ def test_changing_strategy_fails(self):
+ # Arbitratly set a core governor strategy to be performance
+ cpu_api.Core(1).set_high_governor()
+ # and then forget about it while changing the strategy.
+ self.flags(cpu_power_management_strategy='cpu_state', group='libvirt')
+ # This time, this wouldn't be acceptable as some core would have a
+ # difference performance while Nova would only online/offline it.
+ self.assertRaises(exception.InvalidConfiguration,
+ self.restart_compute_service, hostname='compute1')
+
+
+class PowerManagementMixedInstances(PowerManagementTestsBase):
+ """Test suite for a single host with 6 dedicated cores, 3 shared and one
+ OS-restricted.
+ """
+
+ def setUp(self):
+ super(PowerManagementMixedInstances, self).setUp()
+
+ self.useFixture(nova_fixtures.SysFileSystemFixture())
+
+ # Definining 6 CPUs to be dedicated, not all of them in a series.
+ self.flags(cpu_dedicated_set='1-3,5-7', cpu_shared_set='4,8-9',
+ group='compute')
+ self.flags(vcpu_pin_set=None)
+ self.flags(cpu_power_management=True, group='libvirt')
+
+ self.host_info = fakelibvirt.HostInfo(cpu_nodes=1, cpu_sockets=1,
+ cpu_cores=5, cpu_threads=2)
+ self.compute1 = self.start_compute(host_info=self.host_info,
+ hostname='compute1')
+
+ # Make sure only 6 are offline now
+ cpu_dedicated_set = hardware.get_cpu_dedicated_set()
+ self._assert_cpu_set_state(cpu_dedicated_set, expected='offline')
+
+ # cores 4 and 8-9 should be online
+ self._assert_cpu_set_state({4, 8, 9}, expected='online')
+
+ def test_standard_server_works_and_passes(self):
+
+ std_flavor_id = self._create_flavor(vcpu=2)
+ self._create_server(flavor_id=std_flavor_id, expected_state='ACTIVE')
+
+ # Since this is an instance with floating vCPUs on the shared set, we
+ # can only lookup the host CPUs and see they haven't changed state.
+ cpu_dedicated_set = hardware.get_cpu_dedicated_set()
+ self._assert_cpu_set_state(cpu_dedicated_set, expected='offline')
+ self._assert_cpu_set_state({4, 8, 9}, expected='online')
+
+ # We can now try to boot an instance with pinned CPUs to test the mix
+ pinned_server = self._create_server(
+ flavor_id=self.pcpu_flavor_id,
+ expected_state='ACTIVE')
+ # We'll see that its CPUs are now online
+ self._assert_server_cpus_state(pinned_server, expected='online')
+ # but it doesn't change the shared set
+ self._assert_cpu_set_state({4, 8, 9}, expected='online')
diff --git a/nova/tests/functional/regressions/test_bug_1995153.py b/nova/tests/functional/regressions/test_bug_1995153.py
new file mode 100644
index 0000000000..f4e61d06df
--- /dev/null
+++ b/nova/tests/functional/regressions/test_bug_1995153.py
@@ -0,0 +1,107 @@
+# Copyright (C) 2023 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.
+
+
+import fixtures
+from unittest import mock
+
+from oslo_serialization import jsonutils
+from oslo_utils import units
+
+from nova.objects import fields
+from nova.tests.fixtures import libvirt as fakelibvirt
+from nova.tests.functional import integrated_helpers
+from nova.tests.functional.libvirt import base
+
+
+class Bug1995153RegressionTest(
+ base.ServersTestBase,
+ integrated_helpers.InstanceHelperMixin
+):
+
+ ADDITIONAL_FILTERS = ['NUMATopologyFilter', 'PciPassthroughFilter']
+
+ ALIAS_NAME = 'a1'
+ PCI_DEVICE_SPEC = [jsonutils.dumps(
+ {
+ 'vendor_id': fakelibvirt.PCI_VEND_ID,
+ 'product_id': fakelibvirt.PCI_PROD_ID,
+ }
+ )]
+ # we set the numa_affinity policy to required to ensure strict affinity
+ # between pci devices and the guest cpu and memory will be enforced.
+ PCI_ALIAS = [jsonutils.dumps(
+ {
+ 'vendor_id': fakelibvirt.PCI_VEND_ID,
+ 'product_id': fakelibvirt.PCI_PROD_ID,
+ 'name': ALIAS_NAME,
+ 'device_type': fields.PciDeviceType.STANDARD,
+ 'numa_policy': fields.PCINUMAAffinityPolicy.REQUIRED,
+ }
+ )]
+
+ def setUp(self):
+ super(Bug1995153RegressionTest, self).setUp()
+ self.flags(
+ device_spec=self.PCI_DEVICE_SPEC,
+ alias=self.PCI_ALIAS,
+ group='pci'
+ )
+ host_manager = self.scheduler.manager.host_manager
+ pci_filter_class = host_manager.filter_cls_map['PciPassthroughFilter']
+ host_pass_mock = mock.Mock(wraps=pci_filter_class().host_passes)
+ self.mock_filter = self.useFixture(fixtures.MockPatch(
+ 'nova.scheduler.filters.pci_passthrough_filter'
+ '.PciPassthroughFilter.host_passes',
+ side_effect=host_pass_mock)).mock
+
+ def test_socket_policy_bug_1995153(self):
+ """Previously, the numa_usage_from_instance_numa() method in
+ hardware.py saved the host NUMAToplogy object with NUMACells that have
+ no `socket` set. This was an omission in the original implementation of
+ the `socket` PCI NUMA affinity policy. The consequence was that any
+ code path that called into numa_usage_from_instance_numa() would
+ clobber the host NUMA topology in the database with a socket-less
+ version. Booting an instance with NUMA toplogy would do that, for
+ example. If then a second instance was booted with the `socket` PCI
+ NUMA affinity policy, it would read the socket-less host NUMATopology
+ from the database, and error out with a NotImplementedError. This was
+ bug 1995153. Demonstrate that this is fixed.
+ """
+ host_info = fakelibvirt.HostInfo(
+ cpu_nodes=2, cpu_sockets=1, cpu_cores=2, cpu_threads=2,
+ kB_mem=(16 * units.Gi) // units.Ki)
+ self.flags(cpu_dedicated_set='0-3', group='compute')
+ pci_info = fakelibvirt.HostPCIDevicesInfo(num_pci=1, numa_node=1)
+
+ self.start_compute(host_info=host_info, pci_info=pci_info)
+
+ extra_spec = {
+ 'hw:cpu_policy': 'dedicated',
+ 'pci_passthrough:alias': '%s:1' % self.ALIAS_NAME,
+ 'hw:pci_numa_affinity_policy': 'socket'
+ }
+ # Boot a first instance with a guest NUMA topology to run the
+ # numa_usage_from_instance_numa() and update the host NUMATopology in
+ # the database.
+ self._create_server(
+ flavor_id=self._create_flavor(
+ extra_spec={'hw:cpu_policy': 'dedicated'}))
+
+ # Boot an instance with the `socket` PCI NUMA affinity policy and
+ # assert that it boots correctly now.
+ flavor_id = self._create_flavor(extra_spec=extra_spec)
+ self._create_server(flavor_id=flavor_id)
+ self.assertTrue(self.mock_filter.called)
diff --git a/nova/tests/functional/test_servers.py b/nova/tests/functional/test_servers.py
index 43208aa812..5887c99081 100644
--- a/nova/tests/functional/test_servers.py
+++ b/nova/tests/functional/test_servers.py
@@ -6526,3 +6526,41 @@ class PortAndFlavorAccelsServerCreateTest(AcceleratorServerBase):
binding_profile = neutronapi.get_binding_profile(updated_port)
self.assertNotIn('arq_uuid', binding_profile)
self.assertNotIn('pci_slot', binding_profile)
+
+
+class PortBindingShelvedServerTest(integrated_helpers._IntegratedTestBase):
+ """Tests for servers with ports."""
+
+ compute_driver = 'fake.SmallFakeDriver'
+
+ def setUp(self):
+ super(PortBindingShelvedServerTest, self).setUp()
+ self.flavor_id = self._create_flavor(
+ disk=10, ephemeral=20, swap=5 * 1024)
+
+ def test_shelve_offload_with_port(self):
+ # Do not wait before offloading
+ self.flags(shelved_offload_time=0)
+
+ server = self._create_server(
+ flavor_id=self.flavor_id,
+ networks=[{'port': self.neutron.port_1['id']}])
+
+ port = self.neutron.show_port(self.neutron.port_1['id'])['port']
+
+ # Assert that the port is actually associated to the instance
+ self.assertEqual(port['device_id'], server['id'])
+ self.assertEqual(port['binding:host_id'], 'compute')
+ self.assertEqual(port['binding:status'], 'ACTIVE')
+
+ # Do shelve
+ server = self._shelve_server(server, 'SHELVED_OFFLOADED')
+
+ # Retrieve the updated port
+ port = self.neutron.show_port(self.neutron.port_1['id'])['port']
+
+ # Assert that the port is still associated to the instance
+ # but the binding is not on the compute anymore
+ self.assertEqual(port['device_id'], server['id'])
+ self.assertIsNone(port['binding:host_id'])
+ self.assertNotIn('binding:status', port)
diff --git a/nova/tests/unit/compute/test_compute.py b/nova/tests/unit/compute/test_compute.py
index 49cf15ec17..36bcd368dc 100644
--- a/nova/tests/unit/compute/test_compute.py
+++ b/nova/tests/unit/compute/test_compute.py
@@ -5672,6 +5672,7 @@ class ComputeTestCase(BaseTestCase,
pagesize=2048,
cpu_usage=2,
memory_usage=0,
+ socket=0,
pinned_cpus=set([1, 2]),
siblings=[set([1]), set([2])],
mempages=[objects.NUMAPagesTopology(
@@ -5687,6 +5688,7 @@ class ComputeTestCase(BaseTestCase,
pagesize=2048,
memory_usage=0,
cpu_usage=0,
+ socket=0,
siblings=[set([3]), set([4])],
mempages=[objects.NUMAPagesTopology(
size_kb=2048, total=256, used=0)])
@@ -13513,7 +13515,8 @@ class EvacuateHostTestCase(BaseTestCase):
super(EvacuateHostTestCase, self).tearDown()
def _rebuild(self, on_shared_storage=True, migration=None,
- send_node=False, vm_states_is_stopped=False):
+ send_node=False, vm_states_is_stopped=False,
+ expect_error=False):
network_api = self.compute.network_api
ctxt = context.get_admin_context()
@@ -13560,6 +13563,11 @@ class EvacuateHostTestCase(BaseTestCase):
action='power_off', phase='start'),
mock.call(ctxt, self.inst, self.inst.host,
action='power_off', phase='end')])
+ elif expect_error:
+ mock_notify_rebuild.assert_has_calls([
+ mock.call(ctxt, self.inst, self.compute.host,
+ phase='error', exception=mock.ANY, bdms=bdms)])
+ return
else:
mock_notify_rebuild.assert_has_calls([
mock.call(ctxt, self.inst, self.inst.host, phase='start',
@@ -13614,14 +13622,15 @@ class EvacuateHostTestCase(BaseTestCase):
mock.patch.object(self.compute, '_get_compute_info',
side_effect=fake_get_compute_info)
) as (mock_inst, mock_get):
- self._rebuild()
+ self.assertRaises(exception.InstanceFaultRollback,
+ self._rebuild, expect_error=True)
# Should be on destination host
instance = db.instance_get(self.context, self.inst.id)
- self.assertEqual(instance['host'], self.compute.host)
- self.assertIsNone(instance['node'])
- self.assertTrue(mock_inst.called)
- self.assertTrue(mock_get.called)
+ self.assertEqual('fake_host_2', instance['host'])
+ self.assertEqual('fakenode2', instance['node'])
+ mock_inst.assert_not_called()
+ mock_get.assert_called_once_with(mock.ANY, self.compute.host)
def test_rebuild_on_host_node_passed(self):
patch_get_info = mock.patch.object(self.compute, '_get_compute_info')
diff --git a/nova/tests/unit/compute/test_compute_mgr.py b/nova/tests/unit/compute/test_compute_mgr.py
index 16de724a42..73c9d32197 100644
--- a/nova/tests/unit/compute/test_compute_mgr.py
+++ b/nova/tests/unit/compute/test_compute_mgr.py
@@ -2560,10 +2560,11 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase,
self.assertFalse(mock_get_info.called)
self.assertFalse(mock_sync_power_state.called)
+ @mock.patch('nova.compute.resource_tracker.ResourceTracker.instance_claim')
@mock.patch('nova.compute.manager.ComputeManager.'
'_sync_instance_power_state')
def test_query_driver_power_state_and_sync_not_found_driver(
- self, mock_sync_power_state):
+ self, mock_sync_power_state, mock_claim):
error = exception.InstanceNotFound(instance_id=1)
with mock.patch.object(self.compute.driver,
'get_info', side_effect=error) as mock_get_info:
@@ -6568,6 +6569,8 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
fake_rt = fake_resource_tracker.FakeResourceTracker(self.compute.host,
self.compute.driver)
self.compute.rt = fake_rt
+ self.compute.driver._set_nodes([self.node])
+ self.compute.rt.compute_nodes = {self.node: objects.ComputeNode()}
self.allocations = {
uuids.provider1: {
@@ -6857,6 +6860,7 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
mock_get_arqs.assert_called_once_with(
self.instance.uuid, only_resolved=True)
+ @mock.patch('nova.compute.resource_tracker.ResourceTracker.instance_claim')
@mock.patch.object(fake_driver.FakeDriver, 'spawn')
@mock.patch('nova.objects.Instance.save')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
@@ -6868,7 +6872,7 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
@mock.patch.object(manager.ComputeManager, '_notify_about_instance_usage')
def test_spawn_called_with_accel_info(self, mock_ins_usage,
mock_ins_create, mock_dev_tag, mock_certs, mock_req_group_map,
- mock_get_allocations, mock_ins_save, mock_spawn):
+ mock_get_allocations, mock_ins_save, mock_spawn, mock_claim):
accel_info = [{'k1': 'v1', 'k2': 'v2'}]
@@ -7142,13 +7146,15 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
self.security_groups, self.block_device_mapping,
request_spec={}, host_lists=[fake_host_list])
+ @mock.patch('nova.compute.resource_tracker.ResourceTracker.instance_claim')
@mock.patch.object(manager.ComputeManager, '_shutdown_instance')
@mock.patch.object(manager.ComputeManager, '_build_networks_for_instance')
@mock.patch.object(fake_driver.FakeDriver, 'spawn')
@mock.patch.object(objects.Instance, 'save')
@mock.patch.object(manager.ComputeManager, '_notify_about_instance_usage')
def test_rescheduled_exception_with_non_ascii_exception(self,
- mock_notify, mock_save, mock_spawn, mock_build, mock_shutdown):
+ mock_notify, mock_save, mock_spawn, mock_build, mock_shutdown,
+ mock_claim):
exc = exception.NovaException(u's\xe9quence')
mock_build.return_value = self.network_info
@@ -7164,7 +7170,6 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
self.accel_uuids)
mock_save.assert_has_calls([
mock.call(),
- mock.call(),
mock.call(expected_task_state='block_device_mapping'),
])
mock_notify.assert_has_calls([
@@ -7670,6 +7675,7 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
self.assertEqual(10, mock_failed.call_count)
mock_succeeded.assert_not_called()
+ @mock.patch('nova.compute.resource_tracker.ResourceTracker.instance_claim')
@mock.patch.object(manager.ComputeManager, '_shutdown_instance')
@mock.patch.object(manager.ComputeManager, '_build_networks_for_instance')
@mock.patch.object(fake_driver.FakeDriver, 'spawn')
@@ -7677,7 +7683,7 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
@mock.patch.object(manager.ComputeManager, '_notify_about_instance_usage')
def _test_instance_exception(self, exc, raised_exc,
mock_notify, mock_save, mock_spawn,
- mock_build, mock_shutdown):
+ mock_build, mock_shutdown, mock_claim):
"""This method test the instance related InstanceNotFound
and reschedule on exception errors. The test cases get from
arguments.
@@ -7700,7 +7706,6 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
mock_save.assert_has_calls([
mock.call(),
- mock.call(),
mock.call(expected_task_state='block_device_mapping')])
mock_notify.assert_has_calls([
mock.call(self.context, self.instance, 'create.start',
@@ -7811,11 +7816,12 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
'_shutdown_instance'),
mock.patch.object(self.compute,
'_validate_instance_group_policy'),
+ mock.patch.object(self.compute.rt, 'instance_claim'),
mock.patch('nova.compute.utils.notify_about_instance_create')
) as (spawn, save,
_build_networks_for_instance, _notify_about_instance_usage,
_shutdown_instance, _validate_instance_group_policy,
- mock_notify):
+ mock_claim, mock_notify):
self.assertRaises(exception.BuildAbortException,
self.compute._build_and_run_instance, self.context,
@@ -7846,7 +7852,6 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
save.assert_has_calls([
mock.call(),
- mock.call(),
mock.call(
expected_task_state=task_states.BLOCK_DEVICE_MAPPING)])
@@ -7908,11 +7913,12 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
request_spec={}, host_lists=[fake_host_list])
mock_nil.assert_called_once_with(self.instance)
+ @mock.patch('nova.compute.resource_tracker.ResourceTracker.instance_claim')
@mock.patch.object(manager.ComputeManager, '_build_resources')
@mock.patch.object(objects.Instance, 'save')
@mock.patch.object(manager.ComputeManager, '_notify_about_instance_usage')
def test_build_resources_buildabort_reraise(self, mock_notify, mock_save,
- mock_build):
+ mock_build, mock_claim):
exc = exception.BuildAbortException(
instance_uuid=self.instance.uuid, reason='')
mock_build.side_effect = exc
@@ -7926,7 +7932,6 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
self.node, self.limits, self.filter_properties,
request_spec=[], accel_uuids=self.accel_uuids)
- mock_save.assert_called_once_with()
mock_notify.assert_has_calls([
mock.call(self.context, self.instance, 'create.start',
extra_usage_info={'image_name': self.image.get('name')}),
@@ -8581,10 +8586,11 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
ctxt, instance, req_networks)
warning_mock.assert_not_called()
+ @mock.patch('nova.compute.resource_tracker.ResourceTracker.instance_claim')
@mock.patch('nova.compute.utils.notify_about_instance_create')
@mock.patch.object(manager.ComputeManager, '_instance_update')
def test_launched_at_in_create_end_notification(self,
- mock_instance_update, mock_notify_instance_create):
+ mock_instance_update, mock_notify_instance_create, mock_claim):
def fake_notify(*args, **kwargs):
if args[2] == 'create.end':
@@ -8624,6 +8630,7 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
self.flags(default_access_ip_network_name='test1')
instance = fake_instance.fake_db_instance()
+ @mock.patch.object(self.compute.rt, 'instance_claim')
@mock.patch.object(db, 'instance_update_and_get_original',
return_value=({}, instance))
@mock.patch.object(self.compute.driver, 'spawn')
@@ -8632,7 +8639,7 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
@mock.patch.object(db, 'instance_extra_update_by_uuid')
@mock.patch.object(self.compute, '_notify_about_instance_usage')
def _check_access_ip(mock_notify, mock_extra, mock_networks,
- mock_spawn, mock_db_update):
+ mock_spawn, mock_db_update, mock_claim):
self.compute._build_and_run_instance(self.context, self.instance,
self.image, self.injected_files, self.admin_pass,
self.requested_networks, self.security_groups,
@@ -8653,8 +8660,10 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
_check_access_ip()
+ @mock.patch('nova.compute.resource_tracker.ResourceTracker.instance_claim')
@mock.patch.object(manager.ComputeManager, '_instance_update')
- def test_create_error_on_instance_delete(self, mock_instance_update):
+ def test_create_error_on_instance_delete(self, mock_instance_update,
+ mock_claim):
def fake_notify(*args, **kwargs):
if args[2] == 'create.error':
@@ -8668,7 +8677,7 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
mock.patch.object(self.compute,
'_build_networks_for_instance', return_value=[]),
mock.patch.object(self.instance, 'save',
- side_effect=[None, None, None, exc]),
+ side_effect=[None, None, exc]),
mock.patch.object(self.compute, '_notify_about_instance_usage',
side_effect=fake_notify)
) as (mock_spawn, mock_networks, mock_save, mock_notify):
@@ -8697,7 +8706,8 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
mock.patch.object(
self.compute, '_build_networks_for_instance', return_value=[]),
mock.patch.object(self.instance, 'save'),
- ) as (mock_spawn, mock_networks, mock_save):
+ mock.patch.object(self.compute.rt, 'instance_claim'),
+ ) as (mock_spawn, mock_networks, mock_save, mock_claim):
self.compute._build_and_run_instance(
self.context,
self.instance, self.image, self.injected_files,
@@ -8747,7 +8757,8 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
mock.patch.object(self.instance, 'save'),
mock.patch('nova.scheduler.client.report.'
'SchedulerReportClient._get_resource_provider'),
- ) as (mock_spawn, mock_networks, mock_save, mock_get_rp):
+ mock.patch.object(self.compute.rt, 'instance_claim'),
+ ) as (mock_spawn, mock_networks, mock_save, mock_get_rp, mock_claim):
mock_get_rp.return_value = {
'uuid': uuids.rp1,
'name': 'compute1:sriov-agent:ens3'
@@ -9648,9 +9659,15 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
self.assertEqual(driver_console.get_connection_info.return_value,
console)
+ @mock.patch('nova.utils.pass_context')
@mock.patch('nova.compute.manager.ComputeManager.'
'_do_live_migration')
- def _test_max_concurrent_live(self, mock_lm):
+ def _test_max_concurrent_live(self, mock_lm, mock_pass_context):
+ # pass_context wraps the function, which doesn't work with a mock
+ # So we simply mock it too
+ def _mock_pass_context(runner, func, *args, **kwargs):
+ return runner(func, *args, **kwargs)
+ mock_pass_context.side_effect = _mock_pass_context
@mock.patch('nova.objects.Migration.save')
def _do_it(mock_mig_save):
diff --git a/nova/tests/unit/compute/test_resource_tracker.py b/nova/tests/unit/compute/test_resource_tracker.py
index cd36b8987f..919dcb8334 100644
--- a/nova/tests/unit/compute/test_resource_tracker.py
+++ b/nova/tests/unit/compute/test_resource_tracker.py
@@ -181,6 +181,7 @@ _NUMA_HOST_TOPOLOGIES = {
memory=_2MB,
cpu_usage=0,
memory_usage=0,
+ socket=0,
mempages=[_NUMA_PAGE_TOPOLOGIES['2mb*1024']],
siblings=[set([1]), set([2])],
pinned_cpus=set()),
@@ -191,6 +192,7 @@ _NUMA_HOST_TOPOLOGIES = {
memory=_2MB,
cpu_usage=0,
memory_usage=0,
+ socket=0,
mempages=[_NUMA_PAGE_TOPOLOGIES['2mb*1024']],
siblings=[set([3]), set([4])],
pinned_cpus=set())]),
@@ -2231,14 +2233,19 @@ class TestInstanceClaim(BaseTestCase):
self.rt.compute_nodes = {}
self.assertTrue(self.rt.disabled(_NODENAME))
- with mock.patch.object(self.instance, 'save'):
- claim = self.rt.instance_claim(mock.sentinel.ctx, self.instance,
- _NODENAME, self.allocations, None)
+ # Reset all changes to the instance to make sure that we can detect
+ # any manipulation after the failure.
+ self.instance.obj_reset_changes(recursive=True)
- self.assertEqual(self.rt.host, self.instance.host)
- self.assertEqual(self.rt.host, self.instance.launched_on)
- self.assertEqual(_NODENAME, self.instance.node)
- self.assertIsInstance(claim, claims.NopClaim)
+ with mock.patch.object(self.instance, 'save') as mock_save:
+ self.assertRaises(exc.ComputeResourcesUnavailable,
+ self.rt.instance_claim,
+ mock.sentinel.ctx, self.instance,
+ _NODENAME, self.allocations, None)
+ mock_save.assert_not_called()
+
+ # Make sure the instance was not touched by the failed claim process
+ self.assertEqual(set(), self.instance.obj_what_changed())
@mock.patch('nova.compute.utils.is_volume_backed_instance')
@mock.patch('nova.objects.MigrationList.get_in_progress_and_error')
diff --git a/nova/tests/unit/compute/test_shelve.py b/nova/tests/unit/compute/test_shelve.py
index f95a722ced..62321bddec 100644
--- a/nova/tests/unit/compute/test_shelve.py
+++ b/nova/tests/unit/compute/test_shelve.py
@@ -209,6 +209,7 @@ class ShelveComputeManagerTestCase(test_compute.BaseTestCase):
instance = self._shelve_offload(clean_shutdown=False)
mock_power_off.assert_called_once_with(instance, 0, 0)
+ @mock.patch.object(neutron_api.API, 'unbind_ports')
@mock.patch.object(compute_utils, 'EventReporter')
@mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid')
@mock.patch.object(nova.compute.manager.ComputeManager,
@@ -225,7 +226,7 @@ class ShelveComputeManagerTestCase(test_compute.BaseTestCase):
def _shelve_offload(self, mock_notify, mock_notify_instance_usage,
mock_get_power_state, mock_update_resource_tracker,
mock_delete_alloc, mock_terminate, mock_get_bdms,
- mock_event, clean_shutdown=True):
+ mock_event, mock_unbind_ports, clean_shutdown=True):
host = 'fake-mini'
instance = self._create_fake_instance_obj(params={'host': host})
instance.task_state = task_states.SHELVING
@@ -278,6 +279,9 @@ class ShelveComputeManagerTestCase(test_compute.BaseTestCase):
instance.uuid,
graceful_exit=False)
+ mock_unbind_ports.assert_called_once_with(
+ self.context, mock.ANY, detach=False)
+
return instance
@mock.patch('nova.compute.utils.'
@@ -642,7 +646,7 @@ class ShelveComputeManagerTestCase(test_compute.BaseTestCase):
self.compute.unshelve_instance(
self.context, instance, image=None,
- filter_properties={}, node='fake-node', request_spec=request_spec,
+ filter_properties={}, node='fakenode2', request_spec=request_spec,
accel_uuids=[])
mock_update_pci.assert_called_once_with(
@@ -696,7 +700,7 @@ class ShelveComputeManagerTestCase(test_compute.BaseTestCase):
self.assertRaises(test.TestingException,
self.compute.unshelve_instance, self.context, instance,
image=shelved_image, filter_properties={},
- node='fake-node', request_spec=fake_spec, accel_uuids=[])
+ node='fakenode2', request_spec=fake_spec, accel_uuids=[])
self.assertEqual(instance.image_ref, initial_image_ref)
@mock.patch.object(objects.InstanceList, 'get_by_filters')
diff --git a/nova/tests/unit/db/api/test_migrations.py b/nova/tests/unit/db/api/test_migrations.py
index 3b9b17aab2..7c99f2f44a 100644
--- a/nova/tests/unit/db/api/test_migrations.py
+++ b/nova/tests/unit/db/api/test_migrations.py
@@ -25,7 +25,6 @@ from unittest import mock
from alembic import command as alembic_api
from alembic import script as alembic_script
-from migrate.versioning import api as migrate_api
from oslo_db.sqlalchemy import enginefacade
from oslo_db.sqlalchemy import test_fixtures
from oslo_db.sqlalchemy import test_migrations
@@ -127,47 +126,6 @@ class TestModelsSyncPostgreSQL(
FIXTURE = test_fixtures.PostgresqlOpportunisticFixture
-class NovaModelsMigrationsLegacySync(NovaModelsMigrationsSync):
- """Test that the models match the database after old migrations are run."""
-
- def db_sync(self, engine):
- # the 'nova.db.migration.db_sync' method will not use the legacy
- # sqlalchemy-migrate-based migration flow unless the database is
- # already controlled with sqlalchemy-migrate, so we need to manually
- # enable version controlling with this tool to test this code path
- repository = migration._find_migrate_repo(database='api')
- migrate_api.version_control(
- engine, repository, migration.MIGRATE_INIT_VERSION['api'])
-
- # now we can apply migrations as expected and the legacy path will be
- # followed
- super().db_sync(engine)
-
-
-class TestModelsLegacySyncSQLite(
- NovaModelsMigrationsLegacySync,
- test_fixtures.OpportunisticDBTestMixin,
- testtools.TestCase,
-):
- pass
-
-
-class TestModelsLegacySyncMySQL(
- NovaModelsMigrationsLegacySync,
- test_fixtures.OpportunisticDBTestMixin,
- testtools.TestCase,
-):
- FIXTURE = test_fixtures.MySQLOpportunisticFixture
-
-
-class TestModelsLegacySyncPostgreSQL(
- NovaModelsMigrationsLegacySync,
- test_fixtures.OpportunisticDBTestMixin,
- testtools.TestCase,
-):
- FIXTURE = test_fixtures.PostgresqlOpportunisticFixture
-
-
class NovaMigrationsWalk(
test_fixtures.OpportunisticDBTestMixin, test.NoDBTestCase,
):
@@ -180,7 +138,7 @@ class NovaMigrationsWalk(
super().setUp()
self.engine = enginefacade.writer.get_engine()
self.config = migration._find_alembic_conf('api')
- self.init_version = migration.ALEMBIC_INIT_VERSION['api']
+ self.init_version = 'd67eeaabee36'
def _migrate_up(self, connection, revision):
if revision == self.init_version: # no tests for the initial revision
diff --git a/nova/tests/unit/db/main/test_migrations.py b/nova/tests/unit/db/main/test_migrations.py
index e52deb262a..579888cfd2 100644
--- a/nova/tests/unit/db/main/test_migrations.py
+++ b/nova/tests/unit/db/main/test_migrations.py
@@ -30,7 +30,6 @@ from unittest import mock
from alembic import command as alembic_api
from alembic import script as alembic_script
import fixtures
-from migrate.versioning import api as migrate_api
from oslo_db.sqlalchemy import enginefacade
from oslo_db.sqlalchemy import test_fixtures
from oslo_db.sqlalchemy import test_migrations
@@ -174,47 +173,6 @@ class TestModelsSyncPostgreSQL(
FIXTURE = test_fixtures.PostgresqlOpportunisticFixture
-class NovaModelsMigrationsLegacySync(NovaModelsMigrationsSync):
- """Test that the models match the database after old migrations are run."""
-
- def db_sync(self, engine):
- # the 'nova.db.migration.db_sync' method will not use the legacy
- # sqlalchemy-migrate-based migration flow unless the database is
- # already controlled with sqlalchemy-migrate, so we need to manually
- # enable version controlling with this tool to test this code path
- repository = migration._find_migrate_repo(database='main')
- migrate_api.version_control(
- engine, repository, migration.MIGRATE_INIT_VERSION['main'])
-
- # now we can apply migrations as expected and the legacy path will be
- # followed
- super().db_sync(engine)
-
-
-class TestModelsLegacySyncSQLite(
- NovaModelsMigrationsLegacySync,
- test_fixtures.OpportunisticDBTestMixin,
- base.BaseTestCase,
-):
- pass
-
-
-class TestModelsLegacySyncMySQL(
- NovaModelsMigrationsLegacySync,
- test_fixtures.OpportunisticDBTestMixin,
- base.BaseTestCase,
-):
- FIXTURE = test_fixtures.MySQLOpportunisticFixture
-
-
-class TestModelsLegacySyncPostgreSQL(
- NovaModelsMigrationsLegacySync,
- test_fixtures.OpportunisticDBTestMixin,
- base.BaseTestCase,
-):
- FIXTURE = test_fixtures.PostgresqlOpportunisticFixture
-
-
class NovaMigrationsWalk(
test_fixtures.OpportunisticDBTestMixin, test.NoDBTestCase,
):
@@ -227,7 +185,7 @@ class NovaMigrationsWalk(
super().setUp()
self.engine = enginefacade.writer.get_engine()
self.config = migration._find_alembic_conf('main')
- self.init_version = migration.ALEMBIC_INIT_VERSION['main']
+ self.init_version = '8f2f1571d55b'
def assertIndexExists(self, connection, table_name, index):
self.assertTrue(
diff --git a/nova/tests/unit/db/test_migration.py b/nova/tests/unit/db/test_migration.py
index ca86f6347c..17a099a8cc 100644
--- a/nova/tests/unit/db/test_migration.py
+++ b/nova/tests/unit/db/test_migration.py
@@ -12,14 +12,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import glob
-import os
from unittest import mock
import urllib
from alembic.runtime import migration as alembic_migration
-from migrate import exceptions as migrate_exceptions
-from migrate.versioning import api as migrate_api
from nova.db.api import api as api_db_api
from nova.db.main import api as main_db_api
@@ -68,17 +64,9 @@ class TestDBSync(test.NoDBTestCase):
migration.db_sync, '402')
@mock.patch.object(migration, '_upgrade_alembic')
- @mock.patch.object(migration, '_init_alembic_on_legacy_database')
- @mock.patch.object(migration, '_is_database_under_alembic_control')
- @mock.patch.object(migration, '_is_database_under_migrate_control')
@mock.patch.object(migration, '_find_alembic_conf')
- @mock.patch.object(migration, '_find_migrate_repo')
@mock.patch.object(migration, '_get_engine')
- def _test_db_sync(
- self, has_migrate, has_alembic, mock_get_engine, mock_find_repo,
- mock_find_conf, mock_is_migrate, mock_is_alembic, mock_init,
- mock_upgrade,
- ):
+ def test_db_sync(self, mock_get_engine, mock_find_conf, mock_upgrade):
# return an encoded URL to mimic sqlalchemy
mock_get_engine.return_value.url = (
@@ -86,13 +74,10 @@ class TestDBSync(test.NoDBTestCase):
'read_default_file=%2Fetc%2Fmy.cnf.d%2Fnova.cnf'
'&read_default_group=nova'
)
- mock_is_migrate.return_value = has_migrate
- mock_is_alembic.return_value = has_alembic
migration.db_sync()
mock_get_engine.assert_called_once_with('main', context=None)
- mock_find_repo.assert_called_once_with('main')
mock_find_conf.assert_called_once_with('main')
mock_find_conf.return_value.set_main_option.assert_called_once_with(
'sqlalchemy.url',
@@ -100,93 +85,25 @@ class TestDBSync(test.NoDBTestCase):
'read_default_file=%%2Fetc%%2Fmy.cnf.d%%2Fnova.cnf' # ...
'&read_default_group=nova'
)
- mock_is_migrate.assert_called_once_with(
- mock_get_engine.return_value, mock_find_repo.return_value)
-
- if has_migrate:
- mock_is_alembic.assert_called_once_with(
- mock_get_engine.return_value)
- else:
- mock_is_alembic.assert_not_called()
-
- # we should only attempt the upgrade of the remaining
- # sqlalchemy-migrate-based migrations and fake apply of the initial
- # alembic migrations if sqlalchemy-migrate is in place but alembic
- # hasn't been used yet
- if has_migrate and not has_alembic:
- mock_init.assert_called_once_with(
- mock_get_engine.return_value, 'main',
- mock_find_repo.return_value, mock_find_conf.return_value)
- else:
- mock_init.assert_not_called()
- # however, we should always attempt to upgrade the requested migration
- # to alembic
mock_upgrade.assert_called_once_with(
- mock_get_engine.return_value, mock_find_conf.return_value, None)
-
- def test_db_sync_new_deployment(self):
- """Mimic a new deployment without existing sqlalchemy-migrate cruft."""
- has_migrate = False
- has_alembic = False
- self._test_db_sync(has_migrate, has_alembic)
-
- def test_db_sync_with_existing_migrate_database(self):
- """Mimic a deployment currently managed by sqlalchemy-migrate."""
- has_migrate = True
- has_alembic = False
- self._test_db_sync(has_migrate, has_alembic)
-
- def test_db_sync_with_existing_alembic_database(self):
- """Mimic a deployment that's already switched to alembic."""
- has_migrate = True
- has_alembic = True
- self._test_db_sync(has_migrate, has_alembic)
+ mock_get_engine.return_value, mock_find_conf.return_value, None,
+ )
@mock.patch.object(alembic_migration.MigrationContext, 'configure')
-@mock.patch.object(migrate_api, 'db_version')
-@mock.patch.object(migration, '_is_database_under_alembic_control')
-@mock.patch.object(migration, '_is_database_under_migrate_control')
@mock.patch.object(migration, '_get_engine')
-@mock.patch.object(migration, '_find_migrate_repo')
class TestDBVersion(test.NoDBTestCase):
def test_db_version_invalid_database(
- self, mock_find_repo, mock_get_engine, mock_is_migrate,
- mock_is_alembic, mock_migrate_version, mock_m_context_configure,
+ self, mock_get_engine, mock_m_context_configure,
):
"""We only have two databases."""
self.assertRaises(
exception.Invalid, migration.db_version, database='invalid')
- def test_db_version_migrate(
- self, mock_find_repo, mock_get_engine, mock_is_migrate,
- mock_is_alembic, mock_migrate_version, mock_m_context_configure,
- ):
- """Database is controlled by sqlalchemy-migrate."""
- mock_is_migrate.return_value = True
- mock_is_alembic.return_value = False
-
- ret = migration.db_version('main')
- self.assertEqual(mock_migrate_version.return_value, ret)
-
- mock_find_repo.assert_called_once_with('main')
- mock_get_engine.assert_called_once_with('main', context=None)
- mock_is_migrate.assert_called_once()
- mock_is_alembic.assert_called_once()
- mock_migrate_version.assert_called_once_with(
- mock_get_engine.return_value, mock_find_repo.return_value)
- mock_m_context_configure.assert_not_called()
-
- def test_db_version_alembic(
- self, mock_find_repo, mock_get_engine, mock_is_migrate,
- mock_is_alembic, mock_migrate_version, mock_m_context_configure,
- ):
+ def test_db_version(self, mock_get_engine, mock_m_context_configure):
"""Database is controlled by alembic."""
- mock_is_migrate.return_value = False
- mock_is_alembic.return_value = True
-
ret = migration.db_version('main')
mock_m_context = mock_m_context_configure.return_value
self.assertEqual(
@@ -194,31 +111,9 @@ class TestDBVersion(test.NoDBTestCase):
ret
)
- mock_find_repo.assert_called_once_with('main')
mock_get_engine.assert_called_once_with('main', context=None)
- mock_is_migrate.assert_called_once()
- mock_is_alembic.assert_called_once()
- mock_migrate_version.assert_not_called()
mock_m_context_configure.assert_called_once()
- def test_db_version_not_controlled(
- self, mock_find_repo, mock_get_engine, mock_is_migrate,
- mock_is_alembic, mock_migrate_version, mock_m_context_configure,
- ):
- """Database is not controlled."""
- mock_is_migrate.return_value = False
- mock_is_alembic.return_value = False
-
- ret = migration.db_version()
- self.assertIsNone(ret)
-
- mock_find_repo.assert_called_once_with('main')
- mock_get_engine.assert_called_once_with('main', context=None)
- mock_is_migrate.assert_called_once()
- mock_is_alembic.assert_called_once()
- mock_migrate_version.assert_not_called()
- mock_m_context_configure.assert_not_called()
-
class TestGetEngine(test.NoDBTestCase):
@@ -237,77 +132,3 @@ class TestGetEngine(test.NoDBTestCase):
engine = migration._get_engine('api')
self.assertEqual('engine', engine)
mock_get_engine.assert_called_once_with()
-
-
-class TestDatabaseUnderVersionControl(test.NoDBTestCase):
-
- @mock.patch.object(migrate_api, 'db_version')
- def test__is_database_under_migrate_control__true(self, mock_db_version):
- ret = migration._is_database_under_migrate_control('engine', 'repo')
- self.assertTrue(ret)
-
- mock_db_version.assert_called_once_with('engine', 'repo')
-
- @mock.patch.object(migrate_api, 'db_version')
- def test__is_database_under_migrate_control__false(self, mock_db_version):
- mock_db_version.side_effect = \
- migrate_exceptions.DatabaseNotControlledError()
-
- ret = migration._is_database_under_migrate_control('engine', 'repo')
- self.assertFalse(ret)
-
- mock_db_version.assert_called_once_with('engine', 'repo')
-
- @mock.patch.object(alembic_migration.MigrationContext, 'configure')
- def test__is_database_under_alembic_control__true(self, mock_configure):
- context = mock_configure.return_value
- context.get_current_revision.return_value = 'foo'
- engine = mock.MagicMock()
-
- ret = migration._is_database_under_alembic_control(engine)
- self.assertTrue(ret)
-
- context.get_current_revision.assert_called_once_with()
-
- @mock.patch.object(alembic_migration.MigrationContext, 'configure')
- def test__is_database_under_alembic_control__false(self, mock_configure):
- context = mock_configure.return_value
- context.get_current_revision.return_value = None
- engine = mock.MagicMock()
-
- ret = migration._is_database_under_alembic_control(engine)
- self.assertFalse(ret)
-
- context.get_current_revision.assert_called_once_with()
-
-
-class ProjectTestCase(test.NoDBTestCase):
-
- def test_no_migrations_have_downgrade(self):
- topdir = os.path.normpath(os.path.dirname(__file__) + '/../../../')
- # Walk both the nova_api and nova (cell) database migrations.
- includes_downgrade = []
- for directory in (
- os.path.join(topdir, 'db', 'main', 'legacy_migrations'),
- os.path.join(topdir, 'db', 'api', 'legacy_migrations'),
- ):
- py_glob = os.path.join(directory, 'versions', '*.py')
- for path in glob.iglob(py_glob):
- has_upgrade = False
- has_downgrade = False
- with open(path, "r") as f:
- for line in f:
- if 'def upgrade(' in line:
- has_upgrade = True
- if 'def downgrade(' in line:
- has_downgrade = True
-
- if has_upgrade and has_downgrade:
- fname = os.path.basename(path)
- includes_downgrade.append(fname)
-
- helpful_msg = (
- "The following migrations have a downgrade "
- "which is not supported:"
- "\n\t%s" % '\n\t'.join(sorted(includes_downgrade)))
- self.assertFalse(includes_downgrade, helpful_msg)
diff --git a/nova/tests/unit/scheduler/fakes.py b/nova/tests/unit/scheduler/fakes.py
index 658c82c20e..f5dcf87e4a 100644
--- a/nova/tests/unit/scheduler/fakes.py
+++ b/nova/tests/unit/scheduler/fakes.py
@@ -34,6 +34,7 @@ NUMA_TOPOLOGY = objects.NUMATopology(cells=[
memory=512,
cpu_usage=0,
memory_usage=0,
+ socket=0,
pinned_cpus=set(),
mempages=[
objects.NUMAPagesTopology(size_kb=16, total=387184, used=0),
@@ -46,6 +47,7 @@ NUMA_TOPOLOGY = objects.NUMATopology(cells=[
memory=512,
cpu_usage=0,
memory_usage=0,
+ socket=0,
pinned_cpus=set(),
mempages=[
objects.NUMAPagesTopology(size_kb=4, total=1548736, used=0),
diff --git a/nova/tests/unit/scheduler/test_manager.py b/nova/tests/unit/scheduler/test_manager.py
index e7866069b3..e992fe6034 100644
--- a/nova/tests/unit/scheduler/test_manager.py
+++ b/nova/tests/unit/scheduler/test_manager.py
@@ -19,6 +19,7 @@ Tests For Scheduler
from unittest import mock
+from keystoneauth1 import exceptions as ks_exc
import oslo_messaging as messaging
from oslo_serialization import jsonutils
from oslo_utils.fixture import uuidsentinel as uuids
@@ -1688,6 +1689,41 @@ class SchedulerManagerTestCase(test.NoDBTestCase):
mock_log_warning.assert_not_called()
mock_log_debug.assert_called_once_with(msg)
+ @mock.patch('nova.scheduler.client.report.report_client_singleton')
+ @mock.patch.object(manager, 'LOG')
+ @mock.patch('nova.scheduler.host_manager.HostManager')
+ @mock.patch('nova.servicegroup.API')
+ @mock.patch('nova.rpc.get_notifier')
+ def test_init_lazy_placement_client(self, mock_rpc, mock_sg, mock_hm,
+ mock_log, mock_report):
+ # Simulate keytone or placement being offline at startup
+ mock_report.side_effect = ks_exc.RequestTimeout
+ mgr = manager.SchedulerManager()
+ mock_report.assert_called_once_with()
+ self.assertTrue(mock_log.warning.called)
+
+ # Make sure we're raising the actual error to subsequent callers
+ self.assertRaises(ks_exc.RequestTimeout, lambda: mgr.placement_client)
+
+ # Simulate recovery of the keystone or placement service
+ mock_report.reset_mock(side_effect=True)
+ mgr.placement_client
+ mock_report.assert_called_once_with()
+
+ @mock.patch('nova.scheduler.client.report.report_client_singleton')
+ @mock.patch('nova.scheduler.host_manager.HostManager')
+ @mock.patch('nova.servicegroup.API')
+ @mock.patch('nova.rpc.get_notifier')
+ def test_init_lazy_placement_client_failures(self, mock_rpc, mock_sg,
+ mock_hm, mock_report):
+ # Certain keystoneclient exceptions are fatal
+ mock_report.side_effect = ks_exc.Unauthorized
+ self.assertRaises(ks_exc.Unauthorized, manager.SchedulerManager)
+
+ # Anything else is fatal
+ mock_report.side_effect = test.TestingException
+ self.assertRaises(test.TestingException, manager.SchedulerManager)
+
class SchedulerManagerAllocationCandidateTestCase(test.NoDBTestCase):
diff --git a/nova/tests/unit/scheduler/weights/test_weights_hypervisor_version.py b/nova/tests/unit/scheduler/weights/test_weights_hypervisor_version.py
new file mode 100644
index 0000000000..c6e4abd4cd
--- /dev/null
+++ b/nova/tests/unit/scheduler/weights/test_weights_hypervisor_version.py
@@ -0,0 +1,97 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+"""
+Tests For Scheduler hypervisor version weights.
+"""
+
+from nova.scheduler import weights
+from nova.scheduler.weights import hypervisor_version
+from nova import test
+from nova.tests.unit.scheduler import fakes
+
+
+class HypervisorVersionWeigherTestCase(test.NoDBTestCase):
+ def setUp(self):
+ super().setUp()
+ self.weight_handler = weights.HostWeightHandler()
+ self.weighers = [hypervisor_version.HypervisorVersionWeigher()]
+
+ def _get_weighed_host(self, hosts, weight_properties=None):
+ if weight_properties is None:
+ weight_properties = {}
+ return self.weight_handler.get_weighed_objects(self.weighers,
+ hosts, weight_properties)[0]
+
+ def _get_all_hosts(self):
+ host_values = [
+ ('host1', 'node1', {'hypervisor_version': 1}),
+ ('host2', 'node2', {'hypervisor_version': 200}),
+ ('host3', 'node3', {'hypervisor_version': 100}),
+ ('host4', 'node4', {'hypervisor_version': 1000}),
+ ]
+ return [fakes.FakeHostState(host, node, values)
+ for host, node, values in host_values]
+
+ def test_multiplier_default(self):
+ hostinfo_list = self._get_all_hosts()
+ weighed_host = self._get_weighed_host(hostinfo_list)
+ self.assertEqual(1.0, weighed_host.weight)
+ self.assertEqual('host4', weighed_host.obj.host)
+
+ def test_multiplier_default_full_ordering(self):
+ hostinfo_list = self._get_all_hosts()
+ weighed_hosts = self.weight_handler.get_weighed_objects(
+ self.weighers, hostinfo_list, {}
+ )
+ expected_hosts = [fakes.FakeHostState(host, node, values)
+ for host, node, values in [
+ ('host4', 'node4', {'hypervisor_version': 1000}),
+ ('host2', 'node2', {'hypervisor_version': 200}),
+ ('host3', 'node3', {'hypervisor_version': 100}),
+ ('host1', 'node1', {'hypervisor_version': 1}),
+ ]]
+ for actual, expected in zip(
+ weighed_hosts,
+ expected_hosts
+ ):
+ self.assertEqual(actual.obj.host, expected.host)
+
+ def test_multiplier_none(self):
+ multi = 0.0
+ self.flags(
+ hypervisor_version_weight_multiplier=multi,
+ group='filter_scheduler'
+ )
+ hostinfo_list = self._get_all_hosts()
+ weighed_host = self._get_weighed_host(hostinfo_list)
+ self.assertEqual(multi, weighed_host.weight)
+
+ def test_multiplier_positive(self):
+ multi = 2.0
+ self.flags(
+ hypervisor_version_weight_multiplier=multi,
+ group='filter_scheduler'
+ )
+ hostinfo_list = self._get_all_hosts()
+ weighed_host = self._get_weighed_host(hostinfo_list)
+ self.assertEqual(1.0 * multi, weighed_host.weight)
+ self.assertEqual('host4', weighed_host.obj.host)
+
+ def test_multiplier_negative(self):
+ multi = -1.0
+ self.flags(
+ hypervisor_version_weight_multiplier=multi,
+ group='filter_scheduler'
+ )
+ hostinfo_list = self._get_all_hosts()
+ weighed_host = self._get_weighed_host(hostinfo_list)
+ self.assertEqual('host1', weighed_host.obj.host)
diff --git a/nova/tests/unit/test_filesystem.py b/nova/tests/unit/test_filesystem.py
new file mode 100644
index 0000000000..85f16157ee
--- /dev/null
+++ b/nova/tests/unit/test_filesystem.py
@@ -0,0 +1,52 @@
+# 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 os
+from unittest import mock
+
+from nova import exception
+from nova import filesystem
+from nova import test
+
+
+class TestFSCommon(test.NoDBTestCase):
+
+ def test_read_sys(self):
+ open_mock = mock.mock_open(read_data='bar')
+ with mock.patch('builtins.open', open_mock) as m_open:
+ self.assertEqual('bar', filesystem.read_sys('foo'))
+ expected_path = os.path.join(filesystem.SYS, 'foo')
+ m_open.assert_called_once_with(expected_path, mode='r')
+
+ def test_read_sys_error(self):
+ with mock.patch('builtins.open',
+ side_effect=OSError('error')) as m_open:
+ self.assertRaises(exception.FileNotFound,
+ filesystem.read_sys, 'foo')
+ expected_path = os.path.join(filesystem.SYS, 'foo')
+ m_open.assert_called_once_with(expected_path, mode='r')
+
+ def test_write_sys(self):
+ open_mock = mock.mock_open()
+ with mock.patch('builtins.open', open_mock) as m_open:
+ self.assertIsNone(filesystem.write_sys('foo', 'bar'))
+ expected_path = os.path.join(filesystem.SYS, 'foo')
+ m_open.assert_called_once_with(expected_path, mode='w')
+ open_mock().write.assert_called_once_with('bar')
+
+ def test_write_sys_error(self):
+ with mock.patch('builtins.open',
+ side_effect=OSError('fake_error')) as m_open:
+ self.assertRaises(exception.FileNotFound,
+ filesystem.write_sys, 'foo', 'bar')
+ expected_path = os.path.join(filesystem.SYS, 'foo')
+ m_open.assert_called_once_with(expected_path, mode='w')
diff --git a/nova/db/api/legacy_migrations/__init__.py b/nova/tests/unit/virt/libvirt/cpu/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/nova/db/api/legacy_migrations/__init__.py
+++ b/nova/tests/unit/virt/libvirt/cpu/__init__.py
diff --git a/nova/tests/unit/virt/libvirt/cpu/test_api.py b/nova/tests/unit/virt/libvirt/cpu/test_api.py
new file mode 100644
index 0000000000..b5bcb762f3
--- /dev/null
+++ b/nova/tests/unit/virt/libvirt/cpu/test_api.py
@@ -0,0 +1,194 @@
+# 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 exception
+from nova import objects
+from nova import test
+from nova.virt.libvirt.cpu import api
+from nova.virt.libvirt.cpu import core
+
+
+class TestAPI(test.NoDBTestCase):
+
+ def setUp(self):
+ super(TestAPI, self).setUp()
+ self.core_1 = api.Core(1)
+
+ # Create a fake instance with two pinned CPUs but only one is on the
+ # dedicated set
+ numa_topology = objects.InstanceNUMATopology(cells=[
+ objects.InstanceNUMACell(cpu_pinning_raw={'0': '0', '2': '2'}),
+ ])
+ self.fake_inst = objects.Instance(numa_topology=numa_topology)
+
+ @mock.patch.object(core, 'get_online')
+ def test_online(self, mock_get_online):
+ mock_get_online.return_value = True
+ self.assertTrue(self.core_1.online)
+ mock_get_online.assert_called_once_with(self.core_1.ident)
+
+ @mock.patch.object(core, 'set_online')
+ def test_set_online(self, mock_set_online):
+ self.core_1.online = True
+ mock_set_online.assert_called_once_with(self.core_1.ident)
+
+ @mock.patch.object(core, 'set_offline')
+ def test_set_offline(self, mock_set_offline):
+ self.core_1.online = False
+ mock_set_offline.assert_called_once_with(self.core_1.ident)
+
+ def test_hash(self):
+ self.assertEqual(hash(self.core_1.ident), hash(self.core_1))
+
+ @mock.patch.object(core, 'get_governor')
+ def test_governor(self, mock_get_governor):
+ mock_get_governor.return_value = 'fake_governor'
+ self.assertEqual('fake_governor', self.core_1.governor)
+ mock_get_governor.assert_called_once_with(self.core_1.ident)
+
+ @mock.patch.object(core, 'set_governor')
+ def test_set_governor_low(self, mock_set_governor):
+ self.flags(cpu_power_governor_low='fake_low_gov', group='libvirt')
+ self.core_1.set_low_governor()
+ mock_set_governor.assert_called_once_with(self.core_1.ident,
+ 'fake_low_gov')
+
+ @mock.patch.object(core, 'set_governor')
+ def test_set_governor_high(self, mock_set_governor):
+ self.flags(cpu_power_governor_high='fake_high_gov', group='libvirt')
+ self.core_1.set_high_governor()
+ mock_set_governor.assert_called_once_with(self.core_1.ident,
+ 'fake_high_gov')
+
+ @mock.patch.object(core, 'set_online')
+ def test_power_up_online(self, mock_online):
+ self.flags(cpu_power_management=True, group='libvirt')
+ self.flags(cpu_dedicated_set='0-1', group='compute')
+
+ api.power_up(self.fake_inst)
+ # only core #0 can be set as core #2 is not on the dedicated set
+ # As a reminder, core(i).online calls set_online(i)
+ mock_online.assert_called_once_with(0)
+
+ @mock.patch.object(core, 'set_governor')
+ def test_power_up_governor(self, mock_set_governor):
+ self.flags(cpu_power_management=True, group='libvirt')
+ self.flags(cpu_power_management_strategy='governor', group='libvirt')
+ self.flags(cpu_dedicated_set='0-1', group='compute')
+
+ api.power_up(self.fake_inst)
+ # only core #0 can be set as core #2 is not on the dedicated set
+ # As a reminder, core(i).set_high_governor calls set_governor(i)
+ mock_set_governor.assert_called_once_with(0, 'performance')
+
+ @mock.patch.object(core, 'set_online')
+ def test_power_up_skipped(self, mock_online):
+ self.flags(cpu_power_management=False, group='libvirt')
+ api.power_up(self.fake_inst)
+ mock_online.assert_not_called()
+
+ @mock.patch.object(core, 'set_online')
+ def test_power_up_skipped_if_standard_instance(self, mock_online):
+ self.flags(cpu_power_management=True, group='libvirt')
+ api.power_up(objects.Instance(numa_topology=None))
+ mock_online.assert_not_called()
+
+ @mock.patch.object(core, 'set_offline')
+ def test_power_down_offline(self, mock_offline):
+ self.flags(cpu_power_management=True, group='libvirt')
+ self.flags(cpu_dedicated_set='0-1', group='compute')
+
+ api.power_down(self.fake_inst)
+ # only core #0 can be set as core #2 is not on the dedicated set
+ # As a reminder, core(i).online calls set_online(i)
+ mock_offline.assert_called_once_with(0)
+
+ @mock.patch.object(core, 'set_governor')
+ def test_power_down_governor(self, mock_set_governor):
+ self.flags(cpu_power_management=True, group='libvirt')
+ self.flags(cpu_power_management_strategy='governor', group='libvirt')
+ self.flags(cpu_dedicated_set='0-1', group='compute')
+
+ api.power_down(self.fake_inst)
+ # only core #0 can be set as core #2 is not on the dedicated set
+ # As a reminder, core(i).set_high_governor calls set_governor(i)
+ mock_set_governor.assert_called_once_with(0, 'powersave')
+
+ @mock.patch.object(core, 'set_offline')
+ def test_power_down_skipped(self, mock_offline):
+ self.flags(cpu_power_management=False, group='libvirt')
+ api.power_down(self.fake_inst)
+ mock_offline.assert_not_called()
+
+ @mock.patch.object(core, 'set_offline')
+ def test_power_down_skipped_if_standard_instance(self, mock_offline):
+ self.flags(cpu_power_management=True, group='libvirt')
+ api.power_down(objects.Instance(numa_topology=None))
+ mock_offline.assert_not_called()
+
+ @mock.patch.object(core, 'set_offline')
+ def test_power_down_all_dedicated_cpus_offline(self, mock_offline):
+ self.flags(cpu_power_management=True, group='libvirt')
+ self.flags(cpu_dedicated_set='0-1', group='compute')
+
+ api.power_down_all_dedicated_cpus()
+ # All dedicated CPUs are turned offline
+ mock_offline.assert_has_calls([mock.call(0), mock.call(1)])
+
+ @mock.patch.object(core, 'set_governor')
+ def test_power_down_all_dedicated_cpus_governor(self, mock_set_governor):
+ self.flags(cpu_power_management=True, group='libvirt')
+ self.flags(cpu_power_management_strategy='governor', group='libvirt')
+ self.flags(cpu_dedicated_set='0-1', group='compute')
+
+ api.power_down_all_dedicated_cpus()
+ # All dedicated CPUs are turned offline
+ mock_set_governor.assert_has_calls([mock.call(0, 'powersave'),
+ mock.call(1, 'powersave')])
+
+ @mock.patch.object(core, 'set_offline')
+ def test_power_down_all_dedicated_cpus_skipped(self, mock_offline):
+ self.flags(cpu_power_management=False, group='libvirt')
+ api.power_down_all_dedicated_cpus()
+ mock_offline.assert_not_called()
+
+ def test_power_down_all_dedicated_cpus_wrong_config(self):
+ self.flags(cpu_power_management=True, group='libvirt')
+ self.flags(cpu_dedicated_set=None, group='compute')
+ self.assertRaises(exception.InvalidConfiguration,
+ api.power_down_all_dedicated_cpus)
+
+ @mock.patch.object(core, 'get_governor')
+ @mock.patch.object(core, 'get_online')
+ def test_validate_all_dedicated_cpus_for_governor(self, mock_get_online,
+ mock_get_governor):
+ self.flags(cpu_power_management=True, group='libvirt')
+ self.flags(cpu_dedicated_set='0-1', group='compute')
+ self.flags(cpu_power_management_strategy='governor', group='libvirt')
+ mock_get_governor.return_value = 'performance'
+ mock_get_online.side_effect = (True, False)
+ self.assertRaises(exception.InvalidConfiguration,
+ api.validate_all_dedicated_cpus)
+
+ @mock.patch.object(core, 'get_governor')
+ @mock.patch.object(core, 'get_online')
+ def test_validate_all_dedicated_cpus_for_cpu_state(self, mock_get_online,
+ mock_get_governor):
+ self.flags(cpu_power_management=True, group='libvirt')
+ self.flags(cpu_dedicated_set='0-1', group='compute')
+ self.flags(cpu_power_management_strategy='cpu_state', group='libvirt')
+ mock_get_online.return_value = True
+ mock_get_governor.side_effect = ('powersave', 'performance')
+ self.assertRaises(exception.InvalidConfiguration,
+ api.validate_all_dedicated_cpus)
diff --git a/nova/tests/unit/virt/libvirt/cpu/test_core.py b/nova/tests/unit/virt/libvirt/cpu/test_core.py
new file mode 100644
index 0000000000..a3cba00d3b
--- /dev/null
+++ b/nova/tests/unit/virt/libvirt/cpu/test_core.py
@@ -0,0 +1,122 @@
+# 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 exception
+from nova import test
+from nova.tests import fixtures
+from nova.virt.libvirt.cpu import core
+
+
+class TestCore(test.NoDBTestCase):
+
+ @mock.patch.object(core.filesystem, 'read_sys')
+ @mock.patch.object(core.hardware, 'parse_cpu_spec')
+ def test_get_available_cores(self, mock_parse_cpu_spec, mock_read_sys):
+ mock_read_sys.return_value = '1-2'
+ mock_parse_cpu_spec.return_value = set([1, 2])
+ self.assertEqual(set([1, 2]), core.get_available_cores())
+ mock_read_sys.assert_called_once_with(core.AVAILABLE_PATH)
+ mock_parse_cpu_spec.assert_called_once_with('1-2')
+
+ @mock.patch.object(core.filesystem, 'read_sys')
+ @mock.patch.object(core.hardware, 'parse_cpu_spec')
+ def test_get_available_cores_none(
+ self, mock_parse_cpu_spec, mock_read_sys):
+ mock_read_sys.return_value = ''
+ self.assertEqual(set(), core.get_available_cores())
+ mock_parse_cpu_spec.assert_not_called()
+
+ @mock.patch.object(core, 'get_available_cores')
+ def test_exists(self, mock_get_available_cores):
+ mock_get_available_cores.return_value = set([1])
+ self.assertTrue(core.exists(1))
+ mock_get_available_cores.assert_called_once_with()
+ self.assertFalse(core.exists(2))
+
+ @mock.patch.object(
+ core, 'CPU_PATH_TEMPLATE',
+ new_callable=mock.PropertyMock(return_value='/sys/blah%(core)s'))
+ @mock.patch.object(core, 'exists')
+ def test_gen_cpu_path(self, mock_exists, mock_cpu_path):
+ mock_exists.return_value = True
+ self.assertEqual('/sys/blah1', core.gen_cpu_path(1))
+ mock_exists.assert_called_once_with(1)
+
+ @mock.patch.object(core, 'exists')
+ def test_gen_cpu_path_raises(self, mock_exists):
+ mock_exists.return_value = False
+ self.assertRaises(ValueError, core.gen_cpu_path, 1)
+ self.assertIn('Unable to access CPU: 1', self.stdlog.logger.output)
+
+
+class TestCoreHelpers(test.NoDBTestCase):
+
+ def setUp(self):
+ super(TestCoreHelpers, self).setUp()
+ self.useFixture(fixtures.PrivsepFixture())
+ _p1 = mock.patch.object(core, 'exists', return_value=True)
+ self.mock_exists = _p1.start()
+ self.addCleanup(_p1.stop)
+
+ _p2 = mock.patch.object(core, 'gen_cpu_path',
+ side_effect=lambda x: '/fakesys/blah%s' % x)
+ self.mock_gen_cpu_path = _p2.start()
+ self.addCleanup(_p2.stop)
+
+ @mock.patch.object(core.filesystem, 'read_sys')
+ def test_get_online(self, mock_read_sys):
+ mock_read_sys.return_value = '1'
+ self.assertTrue(core.get_online(1))
+ mock_read_sys.assert_called_once_with('/fakesys/blah1/online')
+
+ @mock.patch.object(core.filesystem, 'read_sys')
+ def test_get_online_not_exists(self, mock_read_sys):
+ mock_read_sys.side_effect = exception.FileNotFound(file_path='foo')
+ self.assertTrue(core.get_online(1))
+ mock_read_sys.assert_called_once_with('/fakesys/blah1/online')
+
+ @mock.patch.object(core.filesystem, 'write_sys')
+ @mock.patch.object(core, 'get_online')
+ def test_set_online(self, mock_get_online, mock_write_sys):
+ mock_get_online.return_value = True
+ self.assertTrue(core.set_online(1))
+ mock_write_sys.assert_called_once_with('/fakesys/blah1/online',
+ data='1')
+ mock_get_online.assert_called_once_with(1)
+
+ @mock.patch.object(core.filesystem, 'write_sys')
+ @mock.patch.object(core, 'get_online')
+ def test_set_offline(self, mock_get_online, mock_write_sys):
+ mock_get_online.return_value = False
+ self.assertTrue(core.set_offline(1))
+ mock_write_sys.assert_called_once_with('/fakesys/blah1/online',
+ data='0')
+ mock_get_online.assert_called_once_with(1)
+
+ @mock.patch.object(core.filesystem, 'read_sys')
+ def test_get_governor(self, mock_read_sys):
+ mock_read_sys.return_value = 'fake_gov'
+ self.assertEqual('fake_gov', core.get_governor(1))
+ mock_read_sys.assert_called_once_with(
+ '/fakesys/blah1/cpufreq/scaling_governor')
+
+ @mock.patch.object(core, 'get_governor')
+ @mock.patch.object(core.filesystem, 'write_sys')
+ def test_set_governor(self, mock_write_sys, mock_get_governor):
+ mock_get_governor.return_value = 'fake_gov'
+ self.assertEqual('fake_gov',
+ core.set_governor(1, 'fake_gov'))
+ mock_write_sys.assert_called_once_with(
+ '/fakesys/blah1/cpufreq/scaling_governor', data='fake_gov')
+ mock_get_governor.assert_called_once_with(1)
diff --git a/nova/tests/unit/virt/libvirt/test_config.py b/nova/tests/unit/virt/libvirt/test_config.py
index 8f840e8859..3d0b5ae685 100644
--- a/nova/tests/unit/virt/libvirt/test_config.py
+++ b/nova/tests/unit/virt/libvirt/test_config.py
@@ -1537,7 +1537,7 @@ class LibvirtConfigGuestInputTest(LibvirtConfigBaseTest):
class LibvirtConfigGuestGraphicsTest(LibvirtConfigBaseTest):
- def test_config_graphics(self):
+ def test_config_graphics_vnc(self):
obj = config.LibvirtConfigGuestGraphics()
obj.type = "vnc"
obj.autoport = True
@@ -1549,6 +1549,30 @@ class LibvirtConfigGuestGraphicsTest(LibvirtConfigBaseTest):
<graphics type="vnc" autoport="yes" keymap="en_US" listen="127.0.0.1"/>
""")
+ def test_config_graphics_spice(self):
+ obj = config.LibvirtConfigGuestGraphics()
+ obj.type = "spice"
+ obj.autoport = False
+ obj.keymap = "en_US"
+ obj.listen = "127.0.0.1"
+
+ obj.image_compression = "auto_glz"
+ obj.jpeg_compression = "auto"
+ obj.zlib_compression = "always"
+ obj.playback_compression = True
+ obj.streaming_mode = "filter"
+
+ xml = obj.to_xml()
+ self.assertXmlEqual(xml, """
+ <graphics type="spice" autoport="no" keymap="en_US" listen="127.0.0.1">
+ <image compression="auto_glz"/>
+ <jpeg compression="auto"/>
+ <zlib compression="always"/>
+ <playback compression="on"/>
+ <streaming mode="filter"/>
+ </graphics>
+ """)
+
class LibvirtConfigGuestHostdev(LibvirtConfigBaseTest):
diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py
index 23d2c2f8ce..66dbf795d8 100644
--- a/nova/tests/unit/virt/libvirt/test_driver.py
+++ b/nova/tests/unit/virt/libvirt/test_driver.py
@@ -3399,7 +3399,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.assertEqual(
"Memory encryption requested by hw:mem_encryption extra spec in "
"m1.fake flavor but image fake_image doesn't have "
- "'hw_firmware_type' property set to 'uefi'", str(exc))
+ "'hw_firmware_type' property set to 'uefi' or volume-backed "
+ "instance was requested", str(exc))
def test_sev_enabled_host_extra_spec_no_machine_type(self):
exc = self.assertRaises(exception.InvalidMachineType,
@@ -5821,6 +5822,11 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.assertEqual(cfg.devices[3].type, 'vnc')
self.assertEqual(cfg.devices[3].listen, '10.0.0.1')
self.assertIsNone(cfg.devices[3].keymap)
+ self.assertIsNone(cfg.devices[3].image_compression)
+ self.assertIsNone(cfg.devices[3].jpeg_compression)
+ self.assertIsNone(cfg.devices[3].zlib_compression)
+ self.assertIsNone(cfg.devices[3].playback_compression)
+ self.assertIsNone(cfg.devices[3].streaming_mode)
def test_get_guest_config_with_vnc_and_tablet(self):
self.flags(enabled=True, group='vnc')
@@ -5851,6 +5857,11 @@ class LibvirtConnTestCase(test.NoDBTestCase,
vconfig.LibvirtConfigMemoryBalloon)
self.assertEqual(cfg.devices[3].type, 'vnc')
+ self.assertIsNone(cfg.devices[3].image_compression)
+ self.assertIsNone(cfg.devices[3].jpeg_compression)
+ self.assertIsNone(cfg.devices[3].zlib_compression)
+ self.assertIsNone(cfg.devices[3].playback_compression)
+ self.assertIsNone(cfg.devices[3].streaming_mode)
self.assertEqual(cfg.devices[5].type, 'tablet')
def test_get_guest_config_with_spice_and_tablet(self):
@@ -5887,6 +5898,11 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.assertEqual(cfg.devices[3].type, 'spice')
self.assertEqual(cfg.devices[3].listen, '10.0.0.1')
self.assertIsNone(cfg.devices[3].keymap)
+ self.assertIsNone(cfg.devices[3].image_compression)
+ self.assertIsNone(cfg.devices[3].jpeg_compression)
+ self.assertIsNone(cfg.devices[3].zlib_compression)
+ self.assertIsNone(cfg.devices[3].playback_compression)
+ self.assertIsNone(cfg.devices[3].streaming_mode)
self.assertEqual(cfg.devices[5].type, 'tablet')
@mock.patch.object(host.Host, "_check_machine_type", new=mock.Mock())
@@ -5946,8 +5962,57 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.assertEqual(cfg.devices[3].target_name, "com.redhat.spice.0")
self.assertEqual(cfg.devices[3].type, 'spicevmc')
self.assertEqual(cfg.devices[4].type, "spice")
+ self.assertIsNone(cfg.devices[4].image_compression)
+ self.assertIsNone(cfg.devices[4].jpeg_compression)
+ self.assertIsNone(cfg.devices[4].zlib_compression)
+ self.assertIsNone(cfg.devices[4].playback_compression)
+ self.assertIsNone(cfg.devices[4].streaming_mode)
self.assertEqual(cfg.devices[5].type, video_type)
+ def test_get_guest_config_with_spice_compression(self):
+ self.flags(enabled=False, group='vnc')
+ self.flags(virt_type='kvm', group='libvirt')
+ self.flags(enabled=True,
+ agent_enabled=False,
+ image_compression='auto_lz',
+ jpeg_compression='never',
+ zlib_compression='always',
+ playback_compression=False,
+ streaming_mode='all',
+ server_listen='10.0.0.1',
+ group='spice')
+ self.flags(pointer_model='usbtablet')
+
+ cfg = self._get_guest_config_with_graphics()
+
+ self.assertEqual(len(cfg.devices), 9)
+ self.assertIsInstance(cfg.devices[0],
+ vconfig.LibvirtConfigGuestDisk)
+ self.assertIsInstance(cfg.devices[1],
+ vconfig.LibvirtConfigGuestDisk)
+ self.assertIsInstance(cfg.devices[2],
+ vconfig.LibvirtConfigGuestSerial)
+ self.assertIsInstance(cfg.devices[3],
+ vconfig.LibvirtConfigGuestGraphics)
+ self.assertIsInstance(cfg.devices[4],
+ vconfig.LibvirtConfigGuestVideo)
+ self.assertIsInstance(cfg.devices[5],
+ vconfig.LibvirtConfigGuestInput)
+ self.assertIsInstance(cfg.devices[6],
+ vconfig.LibvirtConfigGuestRng)
+ self.assertIsInstance(cfg.devices[7],
+ vconfig.LibvirtConfigGuestUSBHostController)
+ self.assertIsInstance(cfg.devices[8],
+ vconfig.LibvirtConfigMemoryBalloon)
+
+ self.assertEqual(cfg.devices[3].type, 'spice')
+ self.assertEqual(cfg.devices[3].listen, '10.0.0.1')
+ self.assertEqual(cfg.devices[3].image_compression, 'auto_lz')
+ self.assertEqual(cfg.devices[3].jpeg_compression, 'never')
+ self.assertEqual(cfg.devices[3].zlib_compression, 'always')
+ self.assertFalse(cfg.devices[3].playback_compression)
+ self.assertEqual(cfg.devices[3].streaming_mode, 'all')
+
@mock.patch.object(host.Host, 'get_guest')
@mock.patch.object(libvirt_driver.LibvirtDriver,
'_get_serial_ports_from_guest')
@@ -9169,6 +9234,34 @@ class LibvirtConnTestCase(test.NoDBTestCase,
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
self.assertRaises(exception.Invalid, drvr._get_pcpu_available)
+ @mock.patch('nova.virt.libvirt.host.Host.get_available_cpus',
+ return_value=set([0, 1, 2, 3]))
+ def test_get_pcpu_available_for_power_mgmt(self, get_available_cpus):
+ """Test what happens when the '[compute] cpu_dedicated_set' config
+ option is set and power management is defined.
+ """
+ self.flags(vcpu_pin_set=None)
+ self.flags(cpu_dedicated_set='2-3', cpu_shared_set=None,
+ group='compute')
+ self.flags(cpu_power_management=True, group='libvirt')
+ drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
+ pcpus = drvr._get_pcpu_available()
+ self.assertEqual(set([2, 3]), pcpus)
+
+ @mock.patch('nova.virt.libvirt.host.Host.get_available_cpus',
+ return_value=set([4, 5]))
+ def test_get_pcpu_available__cpu_dedicated_set_invalid_for_pm(self,
+ get_available_cpus):
+ """Test what happens when the '[compute] cpu_dedicated_set' config
+ option is set but it's invalid with power management set.
+ """
+ self.flags(vcpu_pin_set=None)
+ self.flags(cpu_dedicated_set='4-6', cpu_shared_set=None,
+ group='compute')
+ self.flags(cpu_power_management=True, group='libvirt')
+ drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
+ self.assertRaises(exception.Invalid, drvr._get_pcpu_available)
+
@mock.patch('nova.virt.libvirt.host.Host.get_online_cpus',
return_value=set([0, 1, 2, 3]))
def test_get_vcpu_available(self, get_online_cpus):
diff --git a/nova/tests/unit/virt/libvirt/test_host.py b/nova/tests/unit/virt/libvirt/test_host.py
index 534d4489d7..a76dc83105 100644
--- a/nova/tests/unit/virt/libvirt/test_host.py
+++ b/nova/tests/unit/virt/libvirt/test_host.py
@@ -1052,6 +1052,12 @@ Active: 8381604 kB
'iowait': 6121490000000},
stats)
+ @mock.patch.object(fakelibvirt.virConnect, "getCPUMap")
+ def test_get_available_cpus(self, mock_map):
+ mock_map.return_value = (4, [True, True, False, False], None)
+ result = self.host.get_available_cpus()
+ self.assertEqual(result, {0, 1, 2, 3})
+
@mock.patch.object(fakelibvirt.virConnect, "defineXML")
def test_write_instance_config(self, mock_defineXML):
fake_dom_xml = """
diff --git a/nova/tests/unit/virt/test_hardware.py b/nova/tests/unit/virt/test_hardware.py
index 016c478f8c..ab51a3e26c 100644
--- a/nova/tests/unit/virt/test_hardware.py
+++ b/nova/tests/unit/virt/test_hardware.py
@@ -2023,6 +2023,7 @@ class NUMATopologyTest(test.NoDBTestCase):
memory=256,
cpu_usage=0,
memory_usage=0,
+ socket=0,
pinned_cpus=set(),
mempages=[
objects.NUMAPagesTopology(size_kb=4, total=32768, used=0),
@@ -2036,6 +2037,7 @@ class NUMATopologyTest(test.NoDBTestCase):
memory=256,
cpu_usage=0,
memory_usage=0,
+ socket=1,
pinned_cpus=set(),
mempages=[
objects.NUMAPagesTopology(size_kb=4, total=32768, used=64),
@@ -2049,6 +2051,7 @@ class NUMATopologyTest(test.NoDBTestCase):
memory=2,
cpu_usage=0,
memory_usage=0,
+ socket=2,
pinned_cpus=set(),
mempages=[
objects.NUMAPagesTopology(size_kb=4, total=512, used=16)],
@@ -2130,6 +2133,7 @@ class NUMATopologyTest(test.NoDBTestCase):
memory=160,
cpu_usage=0,
memory_usage=0,
+ socket=0,
pinned_cpus=set(),
mempages=[
objects.NUMAPagesTopology(size_kb=4, total=32768, used=32),
@@ -2170,6 +2174,7 @@ class NUMATopologyTest(test.NoDBTestCase):
memory=1024,
cpu_usage=0,
memory_usage=0,
+ socket=0,
pinned_cpus=set(),
mempages=[
objects.NUMAPagesTopology(size_kb=4, total=512, used=0)],
@@ -2181,6 +2186,7 @@ class NUMATopologyTest(test.NoDBTestCase):
memory=512,
cpu_usage=0,
memory_usage=0,
+ socket=0,
pinned_cpus=set(),
mempages=[
objects.NUMAPagesTopology(size_kb=4, total=512, used=0)],
@@ -2192,6 +2198,7 @@ class NUMATopologyTest(test.NoDBTestCase):
memory=512,
cpu_usage=0,
memory_usage=0,
+ socket=0,
pinned_cpus=set(),
mempages=[
objects.NUMAPagesTopology(size_kb=4, total=512, used=0)],
@@ -2258,6 +2265,7 @@ class NUMATopologyTest(test.NoDBTestCase):
memory=1024,
cpu_usage=2,
memory_usage=512,
+ socket=0,
mempages=[
objects.NUMAPagesTopology(size_kb=4, total=512, used=0)],
siblings=[set([0]), set([1]), set([2]), set([3])],
@@ -2269,6 +2277,7 @@ class NUMATopologyTest(test.NoDBTestCase):
memory=512,
cpu_usage=1,
memory_usage=512,
+ socket=0,
pinned_cpus=set(),
mempages=[
objects.NUMAPagesTopology(size_kb=4, total=512, used=0)],
@@ -2280,6 +2289,7 @@ class NUMATopologyTest(test.NoDBTestCase):
memory=256,
cpu_usage=0,
memory_usage=0,
+ socket=0,
pinned_cpus=set(),
mempages=[
objects.NUMAPagesTopology(size_kb=4, total=512, used=0)],
@@ -2330,6 +2340,7 @@ class NUMATopologyTest(test.NoDBTestCase):
memory=512,
cpu_usage=0,
memory_usage=0,
+ socket=0,
pinned_cpus=set(),
mempages=[objects.NUMAPagesTopology(
size_kb=2048, total=512, used=128,
@@ -2342,6 +2353,7 @@ class NUMATopologyTest(test.NoDBTestCase):
memory=512,
cpu_usage=0,
memory_usage=0,
+ socket=0,
pinned_cpus=set(),
mempages=[objects.NUMAPagesTopology(
size_kb=1048576, total=5, used=2,
@@ -2606,6 +2618,7 @@ class VirtNUMAHostTopologyTestCase(test.NoDBTestCase):
memory=2048,
cpu_usage=2,
memory_usage=2048,
+ socket=0,
pinned_cpus=set(),
mempages=[objects.NUMAPagesTopology(
size_kb=4, total=524288, used=0)],
@@ -2616,6 +2629,7 @@ class VirtNUMAHostTopologyTestCase(test.NoDBTestCase):
memory=2048,
cpu_usage=2,
memory_usage=2048,
+ socket=0,
pinned_cpus=set(),
mempages=[objects.NUMAPagesTopology(
size_kb=4, total=524288, used=0)],
@@ -4162,6 +4176,7 @@ class CPUPinningTestCase(test.NoDBTestCase, _CPUPinningTestCaseBase):
memory=4096,
cpu_usage=0,
memory_usage=0,
+ socket=0,
pinned_cpus=set(),
siblings=[set([0]), set([1]), set([2]), set([3])],
mempages=[objects.NUMAPagesTopology(
@@ -4191,6 +4206,7 @@ class CPUPinningTestCase(test.NoDBTestCase, _CPUPinningTestCaseBase):
memory=4096,
cpu_usage=0,
memory_usage=0,
+ socket=0,
pinned_cpus=set([0, 1, 3]),
mempages=[objects.NUMAPagesTopology(
size_kb=4, total=524288, used=0)],
@@ -4220,6 +4236,7 @@ class CPUPinningTestCase(test.NoDBTestCase, _CPUPinningTestCaseBase):
memory=4096,
cpu_usage=0,
memory_usage=0,
+ socket=0,
pinned_cpus=set(),
siblings=[set([0]), set([1]), set([2]), set([3])],
mempages=[objects.NUMAPagesTopology(
@@ -4248,6 +4265,7 @@ class CPUPinningTestCase(test.NoDBTestCase, _CPUPinningTestCaseBase):
memory=4096,
cpu_usage=0,
memory_usage=0,
+ socket=0,
pinned_cpus=set(),
siblings=[set([0, 2]), set([1, 3])],
mempages=[objects.NUMAPagesTopology(
@@ -4274,6 +4292,7 @@ class CPUPinningTestCase(test.NoDBTestCase, _CPUPinningTestCaseBase):
memory=4096,
cpu_usage=0,
memory_usage=0,
+ socket=0,
pinned_cpus=set([0, 1, 2, 3]),
siblings=[set([0, 2]), set([1, 3])],
mempages=[objects.NUMAPagesTopology(
@@ -4300,6 +4319,7 @@ class CPUPinningTestCase(test.NoDBTestCase, _CPUPinningTestCaseBase):
memory=4096,
cpu_usage=0,
memory_usage=0,
+ socket=0,
pinned_cpus=set(),
siblings=[set([0]), set([1]), set([2]), set([3])],
mempages=[objects.NUMAPagesTopology(
@@ -4326,6 +4346,7 @@ class CPUPinningTestCase(test.NoDBTestCase, _CPUPinningTestCaseBase):
memory=4096,
cpu_usage=0,
memory_usage=0,
+ socket=0,
pinned_cpus=set([0, 1, 2, 3]),
siblings=[set([0]), set([1]), set([2]), set([3])],
mempages=[objects.NUMAPagesTopology(
@@ -4355,6 +4376,7 @@ class CPUPinningTestCase(test.NoDBTestCase, _CPUPinningTestCaseBase):
memory=4096,
cpu_usage=0,
memory_usage=0,
+ socket=0,
pinned_cpus=set([2]),
siblings=[set([0, 4]), set([1, 5]), set([2, 6]), set([3, 7])],
mempages=[objects.NUMAPagesTopology(
@@ -4385,6 +4407,7 @@ class CPUPinningTestCase(test.NoDBTestCase, _CPUPinningTestCaseBase):
memory=4096,
cpu_usage=2,
memory_usage=0,
+ socket=0,
pinned_cpus=set([2, 6, 7]),
siblings=[set([0, 4]), set([1, 5]), set([2, 6]), set([3, 7])],
mempages=[objects.NUMAPagesTopology(
@@ -4417,6 +4440,7 @@ class CPUPinningTestCase(test.NoDBTestCase, _CPUPinningTestCaseBase):
cpu_usage=2,
memory_usage=0,
pinned_cpus=set(),
+ socket=0,
siblings=[{cpu} for cpu in range(8)],
mempages=[objects.NUMAPagesTopology(
size_kb=4, total=524288, used=0)]
@@ -4450,6 +4474,7 @@ class CPUPinningTestCase(test.NoDBTestCase, _CPUPinningTestCaseBase):
memory=4096,
cpu_usage=2,
memory_usage=0,
+ socket=0,
pinned_cpus=set([0, 1, 2, 3]),
siblings=[{cpu} for cpu in range(8)],
mempages=[objects.NUMAPagesTopology(
@@ -4492,6 +4517,7 @@ class CPUPinningTestCase(test.NoDBTestCase, _CPUPinningTestCaseBase):
memory=4096,
cpu_usage=2,
memory_usage=0,
+ socket=0,
pinned_cpus=set(),
siblings=[set([0, 5]), set([1, 6]), set([2, 7]), set([3, 8]),
set([4, 9])],
@@ -4531,6 +4557,7 @@ class CPUPinningTestCase(test.NoDBTestCase, _CPUPinningTestCaseBase):
memory=4096,
cpu_usage=2,
memory_usage=0,
+ socket=0,
pinned_cpus=set([0, 1, 2, 5, 6, 7]),
siblings=[set([0, 5]), set([1, 6]), set([2, 7]), set([3, 8]),
set([4, 9])],
@@ -4766,6 +4793,7 @@ class EmulatorThreadsTestCase(test.NoDBTestCase):
memory=2048,
cpu_usage=0,
memory_usage=0,
+ socket=0,
pinned_cpus=set(),
siblings=[set([0]), set([1])],
mempages=[objects.NUMAPagesTopology(
@@ -4777,6 +4805,7 @@ class EmulatorThreadsTestCase(test.NoDBTestCase):
memory=2048,
cpu_usage=0,
memory_usage=0,
+ socket=0,
pinned_cpus=set(),
siblings=[set([2]), set([3])],
mempages=[objects.NUMAPagesTopology(
@@ -5364,7 +5393,7 @@ class MemEncryptionRequestedWithoutUEFITestCase(
expected_error = (
"Memory encryption requested by %(requesters)s but image "
"%(image_name)s doesn't have 'hw_firmware_type' property "
- "set to 'uefi'"
+ "set to 'uefi' or volume-backed instance was requested"
)
def _test_encrypted_memory_support_no_uefi(self, enc_extra_spec,
@@ -5491,6 +5520,25 @@ class MemEncryptionRequiredTestCase(test.NoDBTestCase):
(self.flavor_name, self.image_id)
)
+ def test_encrypted_memory_support_flavor_for_volume(self):
+ extra_specs = {'hw:mem_encryption': True}
+
+ flavor = objects.Flavor(name=self.flavor_name,
+ extra_specs=extra_specs)
+ # Following image_meta is typical for root Cinder volume
+ image_meta = objects.ImageMeta.from_dict({
+ 'min_disk': 0,
+ 'min_ram': 0,
+ 'properties': {},
+ 'size': 0,
+ 'status': 'active'})
+ # Confirm that exception.FlavorImageConflict is raised when
+ # flavor with hw:mem_encryption flag is used to create
+ # volume-backed instance
+ self.assertRaises(exception.FlavorImageConflict,
+ hw.get_mem_encryption_constraint, flavor,
+ image_meta)
+
class PCINUMAAffinityPolicyTest(test.NoDBTestCase):
diff --git a/nova/utils.py b/nova/utils.py
index 664056a09f..b5d45c58b5 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -632,15 +632,13 @@ def _serialize_profile_info():
return trace_info
-def spawn(func, *args, **kwargs):
- """Passthrough method for eventlet.spawn.
-
- This utility exists so that it can be stubbed for testing without
- interfering with the service spawns.
+def pass_context(runner, func, *args, **kwargs):
+ """Generalised passthrough method
- It will also grab the context from the threadlocal store and add it to
- the store on the new thread. This allows for continuity in logging the
- context when using this method to spawn a new thread.
+ It will grab the context from the threadlocal store and add it to
+ the store on the runner. This allows for continuity in logging the
+ context when using this method to spawn a new thread through the
+ runner function
"""
_context = common_context.get_current()
profiler_info = _serialize_profile_info()
@@ -655,11 +653,11 @@ def spawn(func, *args, **kwargs):
profiler.init(**profiler_info)
return func(*args, **kwargs)
- return eventlet.spawn(context_wrapper, *args, **kwargs)
+ return runner(context_wrapper, *args, **kwargs)
-def spawn_n(func, *args, **kwargs):
- """Passthrough method for eventlet.spawn_n.
+def spawn(func, *args, **kwargs):
+ """Passthrough method for eventlet.spawn.
This utility exists so that it can be stubbed for testing without
interfering with the service spawns.
@@ -668,25 +666,26 @@ def spawn_n(func, *args, **kwargs):
the store on the new thread. This allows for continuity in logging the
context when using this method to spawn a new thread.
"""
- _context = common_context.get_current()
- profiler_info = _serialize_profile_info()
- @functools.wraps(func)
- def context_wrapper(*args, **kwargs):
- # NOTE: If update_store is not called after spawn_n it won't be
- # available for the logger to pull from threadlocal storage.
- if _context is not None:
- _context.update_store()
- if profiler_info and profiler:
- profiler.init(**profiler_info)
- func(*args, **kwargs)
+ return pass_context(eventlet.spawn, func, *args, **kwargs)
+
+
+def spawn_n(func, *args, **kwargs):
+ """Passthrough method for eventlet.spawn_n.
+
+ This utility exists so that it can be stubbed for testing without
+ interfering with the service spawns.
- eventlet.spawn_n(context_wrapper, *args, **kwargs)
+ It will also grab the context from the threadlocal store and add it to
+ the store on the new thread. This allows for continuity in logging the
+ context when using this method to spawn a new thread.
+ """
+ pass_context(eventlet.spawn_n, func, *args, **kwargs)
def tpool_execute(func, *args, **kwargs):
"""Run func in a native thread"""
- tpool.execute(func, *args, **kwargs)
+ return pass_context(tpool.execute, func, *args, **kwargs)
def is_none_string(val):
diff --git a/nova/virt/hardware.py b/nova/virt/hardware.py
index 96a7198db2..292536735a 100644
--- a/nova/virt/hardware.py
+++ b/nova/virt/hardware.py
@@ -1213,10 +1213,13 @@ def _check_for_mem_encryption_requirement_conflicts(
"image %(image_name)s which has hw_mem_encryption property "
"explicitly set to %(image_val)s"
)
+ # image_meta.name is not set if image object represents root
+ # Cinder volume.
+ image_name = (image_meta.name if 'name' in image_meta else None)
data = {
'flavor_name': flavor.name,
'flavor_val': flavor_mem_enc_str,
- 'image_name': image_meta.name,
+ 'image_name': image_name,
'image_val': image_mem_enc,
}
raise exception.FlavorImageConflict(emsg % data)
@@ -1228,10 +1231,15 @@ def _check_mem_encryption_uses_uefi_image(requesters, image_meta):
emsg = _(
"Memory encryption requested by %(requesters)s but image "
- "%(image_name)s doesn't have 'hw_firmware_type' property set to 'uefi'"
+ "%(image_name)s doesn't have 'hw_firmware_type' property set to "
+ "'uefi' or volume-backed instance was requested"
)
+ # image_meta.name is not set if image object represents root Cinder
+ # volume, for this case FlavorImageConflict should be raised, but
+ # image_meta.name can't be extracted.
+ image_name = (image_meta.name if 'name' in image_meta else None)
data = {'requesters': " and ".join(requesters),
- 'image_name': image_meta.name}
+ 'image_name': image_name}
raise exception.FlavorImageConflict(emsg % data)
@@ -1260,12 +1268,14 @@ def _check_mem_encryption_machine_type(image_meta, machine_type=None):
if mach_type is None:
return
+ # image_meta.name is not set if image object represents root Cinder volume.
+ image_name = (image_meta.name if 'name' in image_meta else None)
# Could be something like pc-q35-2.11 if a specific version of the
# machine type is required, so do substring matching.
if 'q35' not in mach_type:
raise exception.InvalidMachineType(
mtype=mach_type,
- image_id=image_meta.id, image_name=image_meta.name,
+ image_id=image_meta.id, image_name=image_name,
reason=_("q35 type is required for SEV to work"))
@@ -2573,6 +2583,7 @@ def numa_usage_from_instance_numa(host_topology, instance_topology,
cpuset=host_cell.cpuset,
pcpuset=host_cell.pcpuset,
memory=host_cell.memory,
+ socket=host_cell.socket,
cpu_usage=0,
memory_usage=0,
mempages=host_cell.mempages,
diff --git a/nova/virt/hyperv/driver.py b/nova/virt/hyperv/driver.py
index 1291f975ad..ba18c85cf7 100644
--- a/nova/virt/hyperv/driver.py
+++ b/nova/virt/hyperv/driver.py
@@ -146,6 +146,14 @@ class HyperVDriver(driver.ComputeDriver):
'in Rocky.')
def init_host(self, host):
+ LOG.warning(
+ 'The hyperv driver is not tested by the OpenStack project nor '
+ 'does it have clear maintainer(s) and thus its quality can not be '
+ 'ensured. It should be considered experimental and may be removed '
+ 'in a future release. If you are using the driver in production '
+ 'please let us know via the openstack-discuss mailing list.'
+ )
+
self._serialconsoleops.start_console_handlers()
event_handler = eventhandler.InstanceEventHandler(
state_change_callback=self.emit_event)
diff --git a/nova/virt/libvirt/config.py b/nova/virt/libvirt/config.py
index 0db2dc6b67..231283b8dd 100644
--- a/nova/virt/libvirt/config.py
+++ b/nova/virt/libvirt/config.py
@@ -2047,6 +2047,12 @@ class LibvirtConfigGuestGraphics(LibvirtConfigGuestDevice):
self.keymap = None
self.listen = None
+ self.image_compression = None
+ self.jpeg_compression = None
+ self.zlib_compression = None
+ self.playback_compression = None
+ self.streaming_mode = None
+
def format_dom(self):
dev = super(LibvirtConfigGuestGraphics, self).format_dom()
@@ -2057,6 +2063,24 @@ class LibvirtConfigGuestGraphics(LibvirtConfigGuestDevice):
if self.listen:
dev.set("listen", self.listen)
+ if self.type == "spice":
+ if self.image_compression is not None:
+ dev.append(etree.Element(
+ 'image', compression=self.image_compression))
+ if self.jpeg_compression is not None:
+ dev.append(etree.Element(
+ 'jpeg', compression=self.jpeg_compression))
+ if self.zlib_compression is not None:
+ dev.append(etree.Element(
+ 'zlib', compression=self.zlib_compression))
+ if self.playback_compression is not None:
+ dev.append(etree.Element(
+ 'playback', compression=self.get_on_off_str(
+ self.playback_compression)))
+ if self.streaming_mode is not None:
+ dev.append(etree.Element(
+ 'streaming', mode=self.streaming_mode))
+
return dev
diff --git a/nova/db/api/legacy_migrations/versions/__init__.py b/nova/virt/libvirt/cpu/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/nova/db/api/legacy_migrations/versions/__init__.py
+++ b/nova/virt/libvirt/cpu/__init__.py
diff --git a/nova/virt/libvirt/cpu/api.py b/nova/virt/libvirt/cpu/api.py
new file mode 100644
index 0000000000..1c17458d6b
--- /dev/null
+++ b/nova/virt/libvirt/cpu/api.py
@@ -0,0 +1,157 @@
+# 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 dataclasses import dataclass
+
+from oslo_log import log as logging
+
+import nova.conf
+from nova import exception
+from nova.i18n import _
+from nova import objects
+from nova.virt import hardware
+from nova.virt.libvirt.cpu import core
+
+LOG = logging.getLogger(__name__)
+
+CONF = nova.conf.CONF
+
+
+@dataclass
+class Core:
+ """Class to model a CPU core as reported by sysfs.
+
+ It may be a physical CPU core or a hardware thread on a shared CPU core
+ depending on if the system supports SMT.
+ """
+
+ # NOTE(sbauza): ident is a mandatory field.
+ # The CPU core id/number
+ ident: int
+
+ @property
+ def online(self) -> bool:
+ return core.get_online(self.ident)
+
+ @online.setter
+ def online(self, state: bool) -> None:
+ if state:
+ core.set_online(self.ident)
+ else:
+ core.set_offline(self.ident)
+
+ def __hash__(self):
+ return hash(self.ident)
+
+ def __eq__(self, other):
+ return self.ident == other.ident
+
+ def __str__(self):
+ return str(self.ident)
+
+ @property
+ def governor(self) -> str:
+ return core.get_governor(self.ident)
+
+ def set_high_governor(self) -> None:
+ core.set_governor(self.ident, CONF.libvirt.cpu_power_governor_high)
+
+ def set_low_governor(self) -> None:
+ core.set_governor(self.ident, CONF.libvirt.cpu_power_governor_low)
+
+
+def power_up(instance: objects.Instance) -> None:
+ if not CONF.libvirt.cpu_power_management:
+ return
+ if instance.numa_topology is None:
+ return
+
+ cpu_dedicated_set = hardware.get_cpu_dedicated_set() or set()
+ pcpus = instance.numa_topology.cpu_pinning
+ powered_up = set()
+ for pcpu in pcpus:
+ if pcpu in cpu_dedicated_set:
+ pcpu = Core(pcpu)
+ if CONF.libvirt.cpu_power_management_strategy == 'cpu_state':
+ pcpu.online = True
+ else:
+ pcpu.set_high_governor()
+ powered_up.add(str(pcpu))
+ LOG.debug("Cores powered up : %s", powered_up)
+
+
+def power_down(instance: objects.Instance) -> None:
+ if not CONF.libvirt.cpu_power_management:
+ return
+ if instance.numa_topology is None:
+ return
+
+ cpu_dedicated_set = hardware.get_cpu_dedicated_set() or set()
+ pcpus = instance.numa_topology.cpu_pinning
+ powered_down = set()
+ for pcpu in pcpus:
+ if pcpu in cpu_dedicated_set:
+ pcpu = Core(pcpu)
+ if CONF.libvirt.cpu_power_management_strategy == 'cpu_state':
+ pcpu.online = False
+ else:
+ pcpu.set_low_governor()
+ powered_down.add(str(pcpu))
+ LOG.debug("Cores powered down : %s", powered_down)
+
+
+def power_down_all_dedicated_cpus() -> None:
+ if not CONF.libvirt.cpu_power_management:
+ return
+ if (CONF.libvirt.cpu_power_management and
+ not CONF.compute.cpu_dedicated_set
+ ):
+ msg = _("'[compute]/cpu_dedicated_set' is mandatory to be set if "
+ "'[libvirt]/cpu_power_management' is set."
+ "Please provide the CPUs that can be pinned or don't use the "
+ "power management if you only use shared CPUs.")
+ raise exception.InvalidConfiguration(msg)
+
+ cpu_dedicated_set = hardware.get_cpu_dedicated_set() or set()
+ for pcpu in cpu_dedicated_set:
+ pcpu = Core(pcpu)
+ if CONF.libvirt.cpu_power_management_strategy == 'cpu_state':
+ pcpu.online = False
+ else:
+ pcpu.set_low_governor()
+ LOG.debug("Cores powered down : %s", cpu_dedicated_set)
+
+
+def validate_all_dedicated_cpus() -> None:
+ if not CONF.libvirt.cpu_power_management:
+ return
+ cpu_dedicated_set = hardware.get_cpu_dedicated_set() or set()
+ governors = set()
+ cpu_states = set()
+ for pcpu in cpu_dedicated_set:
+ pcpu = Core(pcpu)
+ # we need to collect the governors strategy and the CPU states
+ governors.add(pcpu.governor)
+ cpu_states.add(pcpu.online)
+ if CONF.libvirt.cpu_power_management_strategy == 'cpu_state':
+ # all the cores need to have the same governor strategy
+ if len(governors) > 1:
+ msg = _("All the cores need to have the same governor strategy"
+ "before modifying the CPU states. You can reboot the "
+ "compute node if you prefer.")
+ raise exception.InvalidConfiguration(msg)
+ elif CONF.libvirt.cpu_power_management_strategy == 'governor':
+ # all the cores need to be online
+ if False in cpu_states:
+ msg = _("All the cores need to be online before modifying the "
+ "governor strategy.")
+ raise exception.InvalidConfiguration(msg)
diff --git a/nova/virt/libvirt/cpu/core.py b/nova/virt/libvirt/cpu/core.py
new file mode 100644
index 0000000000..782f028fee
--- /dev/null
+++ b/nova/virt/libvirt/cpu/core.py
@@ -0,0 +1,78 @@
+# 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 os
+import typing as ty
+
+from oslo_log import log as logging
+
+from nova import exception
+from nova import filesystem
+import nova.privsep
+from nova.virt import hardware
+
+LOG = logging.getLogger(__name__)
+
+AVAILABLE_PATH = '/sys/devices/system/cpu/present'
+
+CPU_PATH_TEMPLATE = '/sys/devices/system/cpu/cpu%(core)s'
+
+
+def get_available_cores() -> ty.Set[int]:
+ cores = filesystem.read_sys(AVAILABLE_PATH)
+ return hardware.parse_cpu_spec(cores) if cores else set()
+
+
+def exists(core: int) -> bool:
+ return core in get_available_cores()
+
+
+def gen_cpu_path(core: int) -> str:
+ if not exists(core):
+ LOG.warning('Unable to access CPU: %s', core)
+ raise ValueError('CPU: %(core)s does not exist', core)
+ return CPU_PATH_TEMPLATE % {'core': core}
+
+
+def get_online(core: int) -> bool:
+ try:
+ online = filesystem.read_sys(
+ os.path.join(gen_cpu_path(core), 'online')).strip()
+ except exception.FileNotFound:
+ # The online file may not exist if we haven't written it yet.
+ # By default, this means that the CPU is online.
+ online = '1'
+ return online == '1'
+
+
+@nova.privsep.sys_admin_pctxt.entrypoint
+def set_online(core: int) -> bool:
+ filesystem.write_sys(os.path.join(gen_cpu_path(core), 'online'), data='1')
+ return get_online(core)
+
+
+def set_offline(core: int) -> bool:
+ filesystem.write_sys(os.path.join(gen_cpu_path(core), 'online'), data='0')
+ return not get_online(core)
+
+
+def get_governor(core: int) -> str:
+ return filesystem.read_sys(
+ os.path.join(gen_cpu_path(core), 'cpufreq/scaling_governor')).strip()
+
+
+@nova.privsep.sys_admin_pctxt.entrypoint
+def set_governor(core: int, governor: str) -> str:
+ filesystem.write_sys(
+ os.path.join(gen_cpu_path(core), 'cpufreq/scaling_governor'),
+ data=governor)
+ return get_governor(core)
diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py
index 542383cbad..fe48960296 100644
--- a/nova/virt/libvirt/driver.py
+++ b/nova/virt/libvirt/driver.py
@@ -114,6 +114,7 @@ from nova.virt.image import model as imgmodel
from nova.virt import images
from nova.virt.libvirt import blockinfo
from nova.virt.libvirt import config as vconfig
+from nova.virt.libvirt.cpu import api as libvirt_cpu
from nova.virt.libvirt import designer
from nova.virt.libvirt import event as libvirtevent
from nova.virt.libvirt import guest as libvirt_guest
@@ -817,6 +818,18 @@ class LibvirtDriver(driver.ComputeDriver):
"force_raw_images to True.")
raise exception.InvalidConfiguration(msg)
+ # NOTE(sbauza): We verify first if the dedicated CPU performances were
+ # modified by Nova before. Note that it can provide an exception if
+ # either the governor strategies are different between the cores or if
+ # the cores are offline.
+ libvirt_cpu.validate_all_dedicated_cpus()
+ # NOTE(sbauza): We powerdown all dedicated CPUs but if some instances
+ # exist that are pinned for some CPUs, then we'll later powerup those
+ # CPUs when rebooting the instance in _init_instance()
+ # Note that it can provide an exception if the config options are
+ # wrongly modified.
+ libvirt_cpu.power_down_all_dedicated_cpus()
+
# TODO(sbauza): Remove this code once mediated devices are persisted
# across reboots.
self._recreate_assigned_mediated_devices()
@@ -1512,6 +1525,8 @@ class LibvirtDriver(driver.ComputeDriver):
# NOTE(GuanQiang): teardown container to avoid resource leak
if CONF.libvirt.virt_type == 'lxc':
self._teardown_container(instance)
+ # We're sure the instance is gone, we can shutdown the core if so
+ libvirt_cpu.power_down(instance)
def destroy(self, context, instance, network_info, block_device_info=None,
destroy_disks=True, destroy_secrets=True):
@@ -3164,6 +3179,7 @@ class LibvirtDriver(driver.ComputeDriver):
current_power_state = guest.get_power_state(self._host)
+ libvirt_cpu.power_up(instance)
# TODO(stephenfin): Any reason we couldn't use 'self.resume' here?
guest.launch(pause=current_power_state == power_state.PAUSED)
@@ -7300,6 +7316,11 @@ class LibvirtDriver(driver.ComputeDriver):
graphics = vconfig.LibvirtConfigGuestGraphics()
graphics.type = "spice"
graphics.listen = CONF.spice.server_listen
+ graphics.image_compression = CONF.spice.image_compression
+ graphics.jpeg_compression = CONF.spice.jpeg_compression
+ graphics.zlib_compression = CONF.spice.zlib_compression
+ graphics.playback_compression = CONF.spice.playback_compression
+ graphics.streaming_mode = CONF.spice.streaming_mode
guest.add_device(graphics)
add_video_driver = True
@@ -7615,7 +7636,7 @@ class LibvirtDriver(driver.ComputeDriver):
instance: 'objects.Instance',
power_on: bool = True,
pause: bool = False,
- post_xml_callback: ty.Callable = None,
+ post_xml_callback: ty.Optional[ty.Callable] = None,
) -> libvirt_guest.Guest:
"""Create a Guest from XML.
@@ -7641,6 +7662,7 @@ class LibvirtDriver(driver.ComputeDriver):
post_xml_callback()
if power_on or pause:
+ libvirt_cpu.power_up(instance)
guest.launch(pause=pause)
return guest
@@ -7675,7 +7697,7 @@ class LibvirtDriver(driver.ComputeDriver):
block_device_info: ty.Optional[ty.Dict[str, ty.Any]],
power_on: bool = True,
vifs_already_plugged: bool = False,
- post_xml_callback: ty.Callable = None,
+ post_xml_callback: ty.Optional[ty.Callable] = None,
external_events: ty.Optional[ty.List[ty.Tuple[str, str]]] = None,
cleanup_instance_dir: bool = False,
cleanup_instance_disks: bool = False,
@@ -7745,15 +7767,18 @@ class LibvirtDriver(driver.ComputeDriver):
if not CONF.compute.cpu_dedicated_set:
return set()
- online_cpus = self._host.get_online_cpus()
+ if CONF.libvirt.cpu_power_management:
+ available_cpus = self._host.get_available_cpus()
+ else:
+ available_cpus = self._host.get_online_cpus()
dedicated_cpus = hardware.get_cpu_dedicated_set()
- if not dedicated_cpus.issubset(online_cpus):
+ if not dedicated_cpus.issubset(available_cpus):
msg = _("Invalid '[compute] cpu_dedicated_set' config: one or "
- "more of the configured CPUs is not online. Online "
- "cpuset(s): %(online)s, configured cpuset(s): %(req)s")
+ "more of the configured CPUs is not available. Available "
+ "cpuset(s): %(available)s, configured cpuset(s): %(req)s")
raise exception.Invalid(msg % {
- 'online': sorted(online_cpus),
+ 'available': sorted(available_cpus),
'req': sorted(dedicated_cpus)})
return dedicated_cpus
diff --git a/nova/virt/libvirt/event.py b/nova/virt/libvirt/event.py
index a7d2a3624f..56951dc11c 100644
--- a/nova/virt/libvirt/event.py
+++ b/nova/virt/libvirt/event.py
@@ -9,6 +9,8 @@
# 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 typing as ty
+
from nova.virt import event
@@ -22,7 +24,10 @@ class LibvirtEvent(event.InstanceEvent):
class DeviceEvent(LibvirtEvent):
"""Base class for device related libvirt events"""
- def __init__(self, uuid: str, dev: str, timestamp: float = None):
+ def __init__(self,
+ uuid: str,
+ dev: str,
+ timestamp: ty.Optional[float] = None):
super().__init__(uuid, timestamp)
self.dev = dev
diff --git a/nova/virt/libvirt/host.py b/nova/virt/libvirt/host.py
index 6820cca920..1ae86d9f47 100644
--- a/nova/virt/libvirt/host.py
+++ b/nova/virt/libvirt/host.py
@@ -740,6 +740,14 @@ class Host(object):
return doms
+ def get_available_cpus(self):
+ """Get the set of CPUs that exist on the host.
+
+ :returns: set of CPUs, raises libvirtError on error
+ """
+ cpus, cpu_map, online = self.get_connection().getCPUMap()
+ return {cpu for cpu in range(cpus)}
+
def get_online_cpus(self):
"""Get the set of CPUs that are online on the host
diff --git a/nova/virt/libvirt/utils.py b/nova/virt/libvirt/utils.py
index adb2ec45a1..e1298ee5c8 100644
--- a/nova/virt/libvirt/utils.py
+++ b/nova/virt/libvirt/utils.py
@@ -261,8 +261,8 @@ def copy_image(
dest: str,
host: ty.Optional[str] = None,
receive: bool = False,
- on_execute: ty.Callable = None,
- on_completion: ty.Callable = None,
+ on_execute: ty.Optional[ty.Callable] = None,
+ on_completion: ty.Optional[ty.Callable] = None,
compression: bool = True,
) -> None:
"""Copy a disk image to an existing directory
@@ -639,7 +639,7 @@ def mdev_name2uuid(mdev_name: str) -> str:
return str(uuid.UUID(mdev_uuid))
-def mdev_uuid2name(mdev_uuid: str, parent: str = None) -> str:
+def mdev_uuid2name(mdev_uuid: str, parent: ty.Optional[str] = None) -> str:
"""Convert an mdev uuid (of the form 8-4-4-4-12) and optionally its parent
device to a name (of the form mdev_<uuid_with_underscores>[_<pciid>]).
diff --git a/releasenotes/notes/add-spice-compression-support-e41676f445544e8d.yaml b/releasenotes/notes/add-spice-compression-support-e41676f445544e8d.yaml
new file mode 100644
index 0000000000..b370889171
--- /dev/null
+++ b/releasenotes/notes/add-spice-compression-support-e41676f445544e8d.yaml
@@ -0,0 +1,23 @@
+---
+features:
+ - |
+ The following SPICE-related options are added to the ``spice``
+ configuration group of a Nova configuration:
+
+ - ``image_compression``
+ - ``jpeg_compression``
+ - ``zlib_compression``
+ - ``playback_compression``
+ - ``streaming_mode``
+
+ These configuration options can be used to enable and set the
+ SPICE compression settings for libvirt (QEMU/KVM) provisioned
+ instances. Each configuration option is optional and can be set
+ explictly to configure the associated SPICE compression setting
+ for libvirt. If all configuration options are not set, then none
+ of the SPICE compression settings will be configured for libvirt,
+ which corresponds to the behavior before this change. In this case,
+ the built-in defaults from the libvirt backend (e.g. QEMU) are used.
+
+ Note that those options are only taken into account if SPICE support
+ is enabled (and the VNC support is disabled).
diff --git a/releasenotes/notes/antelope-prelude-4a99907b00e739f8.yaml b/releasenotes/notes/antelope-prelude-4a99907b00e739f8.yaml
new file mode 100644
index 0000000000..66890684af
--- /dev/null
+++ b/releasenotes/notes/antelope-prelude-4a99907b00e739f8.yaml
@@ -0,0 +1,51 @@
+---
+prelude: |
+ The OpenStack 2023.1 (Nova 27.0.0) release includes many new features and
+ bug fixes. Please be sure to read the upgrade section which describes the
+ required actions to upgrade your cloud from 26.0.0 (Zed) to 27.0.0 (2023.1).
+ As a reminder, OpenStack 2023.1 is our first `Skip-Level-Upgrade Release`__
+ (starting from now, we name it a `SLURP release`) where you can
+ rolling-upgrade your compute services from OpenStack Yoga as an experimental
+ feature. Next SLURP release will be 2024.1.
+
+ .. __: https://governance.openstack.org/tc/resolutions/20220210-release-cadence-adjustment.html
+
+ There are a few major changes worth mentioning. This is not an exhaustive
+ list:
+
+ - The latest Compute API microversion supported for 2023.1 is `v2.95`__.
+
+ .. __: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-2023.1
+
+ - `PCI devices can now be scheduled <https://docs.openstack.org/nova/latest/admin/pci-passthrough.html#pci-tracking-in-placement>`_
+ by Nova using the Placement API on a opt-in basis. This will help the
+ nova-scheduler service to better schedule flavors that use PCI
+ (non-Neutron related) resources, will generate less reschedules if an
+ instance cannot be created on a candidate and will help the nova-scheduler
+ to not miss valid candidates if the list was too large.
+
+ - Operators can now ask Nova to `manage the power consumption of dedicated
+ CPUs <https://docs.openstack.org/nova/latest/admin/cpu-topologies.html#configuring-cpu-power-management-for-dedicated-cores>`_
+ so as to either offline them or change their governor if they're
+ currently not in use by any instance or if the instance is stopped.
+
+ - Nova will prevent unexpected compute service renames by `persisting a unique
+ compute UUID on local disk <https://docs.openstack.org/nova/latest/admin/compute-node-identification.html>`_.
+ This stored UUID will be considered the source of truth for knowing whether
+ the compute service hostame has been modified or not. As a reminder,
+ changing a compute hostname is forbidden, particularly when this compute is
+ currently running instances on top of it.
+
+ - `SPICE consoles <https://docs.openstack.org/nova/latest/admin/remote-console-access.html#spice-console>`_
+ can now be configured with compression settings which include choices of the
+ compression algorithm and the compression mode.
+
+ - Fully-Qualified Domain Names are now considered valid for an instance
+ hostname if you use the 2.94 API microversion.
+
+ - By opting into 2.95 API microversion, evacuated instances will remain
+ stopped on the destination host until manually started.
+
+ - Nova APIs now `by default support new RBAC policies <https://docs.openstack.org/nova/latest/configuration/policy.html>`
+ and scopes. See our `Policy Concepts documention <https://docs.openstack.org/nova/latest/configuration/policy-concepts.html>`
+ for further details.
diff --git a/releasenotes/notes/bp-libvirt-cpu-state-mgmt-fbc9c1f9f473003c.yaml b/releasenotes/notes/bp-libvirt-cpu-state-mgmt-fbc9c1f9f473003c.yaml
new file mode 100644
index 0000000000..95422fce67
--- /dev/null
+++ b/releasenotes/notes/bp-libvirt-cpu-state-mgmt-fbc9c1f9f473003c.yaml
@@ -0,0 +1,18 @@
+---
+features:
+ - |
+ This is now possible to configure nova-compute services using libvirt driver
+ by setting ``[libvirt]cpu_power_management`` to ``True`` in order to let the
+ service to powering down or up physical CPUs depending on whether those CPUs
+ are pinned or not to instances. In order on to support this feature, the
+ compute service needs to be set with ``[compute]cpu_dedicated_set``. If so,
+ all the related CPUs will be powering down until they are used by an
+ instance where the related pinned CPU will be powering up just before
+ starting the guest. If ``[compute]cpu_dedicated_set`` isn't set, then the
+ compute service will refuse to start.
+ By default the power strategy will offline CPUs when powering down and
+ online the CPUs on powering up but another strategy is possible by using
+ ``[libvirt]cpu_power_management_strategy=governor`` which will rather modify
+ the related CPU governor using ``[libvirt]cpu_power_governor_low`` and
+ ``[libvirt]cpu_power_governor_high`` configuration values (respective
+ defaults being ``powersave`` and ``performance``)
diff --git a/releasenotes/notes/hyperv-experimental-antelope-372e18a05cafc295.yaml b/releasenotes/notes/hyperv-experimental-antelope-372e18a05cafc295.yaml
new file mode 100644
index 0000000000..85b874fb69
--- /dev/null
+++ b/releasenotes/notes/hyperv-experimental-antelope-372e18a05cafc295.yaml
@@ -0,0 +1,6 @@
+---
+deprecations:
+ - |
+ The hyperv driver is marked as experimental and may be removed in a
+ future release. The driver is not tested by the OpenStack project and
+ does not have a clear maintainer.
diff --git a/releasenotes/notes/hypervisor-version-weigher-d0bba77e720edafe.yaml b/releasenotes/notes/hypervisor-version-weigher-d0bba77e720edafe.yaml
new file mode 100644
index 0000000000..31f2c70926
--- /dev/null
+++ b/releasenotes/notes/hypervisor-version-weigher-d0bba77e720edafe.yaml
@@ -0,0 +1,20 @@
+---
+features:
+ - |
+ A new hypervisor version weigher has been added to prefer selecting hosts
+ with newer hypervisors installed. For the libvirt driver, this is the version
+ of libvirt on the compute node not the version of qemu. As with all
+ weighers this is enabled by default and its behavior can be modified using
+ the new ``hypervisor_version_weight_multiplier`` config option in the
+ ``filter_scheduler`` section.
+upgrade:
+ - |
+ A new hypervisor version weigher has been added that will prefer selecting
+ hosts with a newer hypervisor installed. This can help simplify rolling
+ upgrades by preferring the already upgraded hosts when moving workloads around
+ using live or cold migration. To restore the old behavior either remove
+ the weigher from the list of enabled weighers or set
+ ``[filter_scheduler] hypervisor_version_weight_multiplier=0``. The default
+ value of the hypervisor_version_weight_multiplier is 1 so only a mild
+ preference is given to new hosts, higher values will make the effect
+ more pronounced and negative values will prefer older hosts.
diff --git a/releasenotes/notes/port-binding-removed-shelved-offloaded-f1772a64be007b24.yaml b/releasenotes/notes/port-binding-removed-shelved-offloaded-f1772a64be007b24.yaml
new file mode 100644
index 0000000000..7e2dccbbf4
--- /dev/null
+++ b/releasenotes/notes/port-binding-removed-shelved-offloaded-f1772a64be007b24.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+ - |
+ [`bug 1983471 <https://bugs.launchpad.net/nova/+bug/1983471>`_]
+ When offloading a shelved instance, the compute will now remove the
+ binding so instance ports will appear as "unbound" in neutron.
diff --git a/releasenotes/notes/remove-sqlalchemy-migrate-907c200314884d81.yaml b/releasenotes/notes/remove-sqlalchemy-migrate-907c200314884d81.yaml
new file mode 100644
index 0000000000..c08080a806
--- /dev/null
+++ b/releasenotes/notes/remove-sqlalchemy-migrate-907c200314884d81.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+ - |
+ The legacy ``sqlalchemy-migrate`` migrations, which have been deprecated
+ since Wallaby, have been removed. There should be no end-user impact.
diff --git a/releasenotes/source/2023.1.rst b/releasenotes/source/2023.1.rst
new file mode 100644
index 0000000000..d1238479ba
--- /dev/null
+++ b/releasenotes/source/2023.1.rst
@@ -0,0 +1,6 @@
+===========================
+2023.1 Series Release Notes
+===========================
+
+.. release-notes::
+ :branch: stable/2023.1
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index 6bff00e25a..ed6f8c2d07 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -6,6 +6,7 @@ Nova Release Notes
:maxdepth: 1
unreleased
+ 2023.1
zed
yoga
xena
diff --git a/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po
index d90391af7c..c0bd8bc9a8 100644
--- a/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po
+++ b/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po
@@ -2,15 +2,16 @@
# Andi Chandler <andi@gowling.com>, 2018. #zanata
# Andi Chandler <andi@gowling.com>, 2020. #zanata
# Andi Chandler <andi@gowling.com>, 2022. #zanata
+# Andi Chandler <andi@gowling.com>, 2023. #zanata
msgid ""
msgstr ""
"Project-Id-Version: nova\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-09-16 12:59+0000\n"
+"POT-Creation-Date: 2023-03-06 19:02+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2022-09-15 09:04+0000\n"
+"PO-Revision-Date: 2023-01-26 10:17+0000\n"
"Last-Translator: Andi Chandler <andi@gowling.com>\n"
"Language-Team: English (United Kingdom)\n"
"Language: en_GB\n"
@@ -382,9 +383,6 @@ msgstr "20.5.0"
msgid "20.6.1"
msgstr "20.6.1"
-msgid "20.6.1-29"
-msgstr "20.6.1-29"
-
msgid "204 NoContent on success"
msgstr "204 NoContent on success"
@@ -409,9 +407,6 @@ msgstr "21.2.2"
msgid "21.2.3"
msgstr "21.2.3"
-msgid "21.2.4-12"
-msgstr "21.2.4-12"
-
msgid "22.0.0"
msgstr "22.0.0"
@@ -433,9 +428,6 @@ msgstr "22.3.0"
msgid "22.4.0"
msgstr "22.4.0"
-msgid "22.4.0-6"
-msgstr "22.4.0-6"
-
msgid "23.0.0"
msgstr "23.0.0"
@@ -451,8 +443,8 @@ msgstr "23.2.0"
msgid "23.2.1"
msgstr "23.2.1"
-msgid "23.2.1-13"
-msgstr "23.2.1-13"
+msgid "23.2.2"
+msgstr "23.2.2"
msgid "24.0.0"
msgstr "24.0.0"
@@ -463,8 +455,8 @@ msgstr "24.1.0"
msgid "24.1.1"
msgstr "24.1.1"
-msgid "24.1.1-7"
-msgstr "24.1.1-7"
+msgid "24.2.0"
+msgstr "24.2.0"
msgid "25.0.0"
msgstr "25.0.0"
@@ -472,8 +464,14 @@ msgstr "25.0.0"
msgid "25.0.1"
msgstr "25.0.1"
-msgid "25.0.1-5"
-msgstr "25.0.1-5"
+msgid "25.1.0"
+msgstr "25.1.0"
+
+msgid "26.0.0"
+msgstr "26.0.0"
+
+msgid "26.1.0"
+msgstr "26.1.0"
msgid "400 for unknown param for query param and for request body."
msgstr "400 for unknown param for query param and for request body."
@@ -488,6 +486,24 @@ msgid "409 Conflict if inventory in use or if some other request concurrently"
msgstr "409 Conflict if inventory in use or if some other request concurrently"
msgid ""
+"A ``--dry-run`` option has been added to the ``nova-manage placement "
+"heal_allocations`` CLI which allows running the command to get output "
+"without committing any changes to placement."
+msgstr ""
+"A ``--dry-run`` option has been added to the ``nova-manage placement "
+"heal_allocations`` CLI which allows running the command to get output "
+"without committing any changes to placement."
+
+msgid ""
+"A ``--force`` flag is provided to skip the above checks but caution should "
+"be taken as this could easily lead to the underlying ABI of the instance "
+"changing when moving between machine types."
+msgstr ""
+"A ``--force`` flag is provided to skip the above checks but caution should "
+"be taken as this could easily lead to the underlying ABI of the instance "
+"changing when moving between machine types."
+
+msgid ""
"A ``default_floating_pool`` configuration option has been added in the "
"``[neutron]`` group. The existing ``default_floating_pool`` option in the "
"``[DEFAULT]`` group is retained and should be used by nova-network users. "
@@ -571,6 +587,24 @@ msgid "Stein Series Release Notes"
msgstr "Stein Series Release Notes"
msgid ""
+"The XenServer configuration option 'iqn_prefix' has been removed. It was not "
+"used anywhere and has no effect on any code, so there should be no impact."
+msgstr ""
+"The XenServer configuration option 'iqn_prefix' has been removed. It was not "
+"used anywhere and has no effect on any code, so there should be no impact."
+
+msgid ""
+"The ``api_rate_limit`` configuration option has been removed. The option was "
+"disabled by default back in the Havana release since it's effectively broken "
+"for more than one API worker. It has been removed because the legacy v2 API "
+"code that was using it has also been removed."
+msgstr ""
+"The ``api_rate_limit`` configuration option has been removed. The option was "
+"disabled by default back in the Havana release since it's effectively broken "
+"for more than one API worker. It has been removed because the legacy v2 API "
+"code that was using it has also been removed."
+
+msgid ""
"The ``nova-manage vm list`` command is deprecated and will be removed in the "
"15.0.0 Ocata release. Use the ``nova list`` command from python-novaclient "
"instead."
@@ -580,6 +614,24 @@ msgstr ""
"instead."
msgid ""
+"The default flavors that nova has previously had are no longer created as "
+"part of the first database migration. New deployments will need to create "
+"appropriate flavors before first use."
+msgstr ""
+"The default flavours that Nova previously had are no longer created as part "
+"of the first database migration. New deployments will need to create "
+"appropriate flavours before first use."
+
+msgid ""
+"The network configuration option 'fake_call' has been removed. It hasn't "
+"been used for several cycles, and has no effect on any code, so there should "
+"be no impact."
+msgstr ""
+"The network configuration option 'fake_call' has been removed. It hasn't "
+"been used for several cycles, and has no effect on any code, so there should "
+"be no impact."
+
+msgid ""
"These commands only work with nova-network which is itself deprecated in "
"favor of Neutron."
msgstr ""
@@ -611,6 +663,9 @@ msgstr "Xena Series Release Notes"
msgid "Yoga Series Release Notes"
msgstr "Yoga Series Release Notes"
+msgid "Zed Series Release Notes"
+msgstr "Zed Series Release Notes"
+
msgid "kernels 3.x: 8"
msgstr "kernels 3.x: 8"
diff --git a/requirements.txt b/requirements.txt
index 9954d06bc9..e885a4a66f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -16,7 +16,6 @@ greenlet>=0.4.15 # MIT
PasteDeploy>=1.5.0 # MIT
Paste>=2.0.2 # MIT
PrettyTable>=0.7.1 # BSD
-sqlalchemy-migrate>=0.13.0 # Apache-2.0
alembic>=1.5.0 # MIT
netaddr>=0.7.18 # BSD
netifaces>=0.10.4 # MIT
diff --git a/tox.ini b/tox.ini
index 097edbe827..77c0d9b9d3 100644
--- a/tox.ini
+++ b/tox.ini
@@ -42,7 +42,7 @@ commands =
env TEST_OSPROFILER=1 stestr run --combine --no-discover 'nova.tests.unit.test_profiler'
stestr slowest
-[testenv:functional{,-py38,-py39,-py310}]
+[testenv:functional{,-py38,-py39,-py310,-py311}]
description =
Run functional tests.
# As nova functional tests import the PlacementFixture from the placement