summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api-guide/source/general_info.rst4
-rw-r--r--api-ref/source/index.rst2
-rw-r--r--api-ref/source/os-cloudpipe.inc5
-rw-r--r--api-ref/source/os-flavor-access.inc7
-rw-r--r--api-ref/source/parameters.yaml36
-rw-r--r--api-ref/source/servers-admin-action.inc7
-rw-r--r--[-rwxr-xr-x]doc/notification_samples/instance-rebuild-end.json0
-rw-r--r--[-rwxr-xr-x]doc/notification_samples/instance-rebuild-error.json0
-rw-r--r--[-rwxr-xr-x]doc/notification_samples/instance-rebuild-start.json0
-rw-r--r--doc/notification_samples/instance-volume_attach-end.json76
-rw-r--r--doc/notification_samples/instance-volume_attach-error.json86
-rw-r--r--doc/notification_samples/instance-volume_attach-start.json76
-rw-r--r--doc/notification_samples/instance-volume_detach-end.json76
-rw-r--r--doc/notification_samples/instance-volume_detach-start.json76
-rw-r--r--doc/source/api_plugins.rst2
-rw-r--r--doc/source/conf.py2
-rw-r--r--doc/source/index.rst1
-rw-r--r--doc/source/man/nova-api-metadata.rst1
-rw-r--r--doc/source/man/nova-api-os-compute.rst1
-rw-r--r--doc/source/man/nova-api.rst4
-rw-r--r--doc/source/upgrade.rst4
-rw-r--r--doc/source/vendordata.rst12
-rw-r--r--doc/source/wsgi.rst36
-rw-r--r--nova/api/metadata/wsgi.py (renamed from nova/cloudpipe/__init__.py)18
-rw-r--r--nova/api/openstack/compute/agents.py22
-rw-r--r--nova/api/openstack/compute/assisted_volume_snapshots.py22
-rw-r--r--nova/api/openstack/compute/availability_zone.py23
-rw-r--r--nova/api/openstack/compute/cloudpipe.py137
-rw-r--r--nova/api/openstack/compute/extension_info.py36
-rw-r--r--nova/api/openstack/compute/fixed_ips.py20
-rw-r--r--nova/api/openstack/compute/flavor_access.py3
-rw-r--r--nova/api/openstack/compute/floating_ip_dns.py32
-rw-r--r--nova/api/openstack/compute/floating_ip_pools.py22
-rw-r--r--nova/api/openstack/compute/floating_ips.py17
-rw-r--r--nova/api/openstack/compute/floating_ips_bulk.py22
-rw-r--r--nova/api/openstack/compute/instance_usage_audit_log.py17
-rw-r--r--nova/api/openstack/compute/routes.py128
-rw-r--r--nova/api/openstack/compute/schemas/cloudpipe.py48
-rw-r--r--nova/api/openstack/compute/server_metadata.py35
-rw-r--r--nova/api/openstack/compute/wsgi.py72
-rw-r--r--nova/api/openstack/wsgi_app.py85
-rw-r--r--nova/cells/messaging.py4
-rwxr-xr-xnova/cloudpipe/bootscript.template54
-rw-r--r--nova/cloudpipe/client.ovpn.template43
-rw-r--r--nova/cloudpipe/pipelib.py150
-rw-r--r--nova/cmd/manage.py2
-rw-r--r--nova/compute/api.py36
-rw-r--r--nova/compute/manager.py51
-rw-r--r--nova/compute/resource_tracker.py8
-rw-r--r--nova/compute/utils.py30
-rw-r--r--nova/conf/__init__.py4
-rw-r--r--nova/conf/cloudpipe.py121
-rw-r--r--nova/conf/floating_ips.py128
-rw-r--r--nova/conf/network.py119
-rw-r--r--nova/conf/neutron.py16
-rw-r--r--nova/db/api.py11
-rw-r--r--nova/db/sqlalchemy/api.py29
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/361_add_compute_nodes_uuid_index.py41
-rw-r--r--nova/db/sqlalchemy/models.py1
-rw-r--r--nova/db/sqlalchemy/types.py2
-rw-r--r--nova/network/neutronv2/api.py58
-rw-r--r--nova/network/neutronv2/constants.py1
-rw-r--r--nova/network/security_group/security_group_base.py2
-rw-r--r--nova/notifications/objects/instance.py31
-rw-r--r--nova/objects/resource_provider.py121
-rw-r--r--nova/objects/service.py8
-rw-r--r--nova/policies/__init__.py2
-rw-r--r--nova/policies/cloudpipe.py49
-rw-r--r--nova/policies/config_drive.py19
-rw-r--r--nova/policies/extensions.py20
-rw-r--r--nova/policies/flavor_access.py58
-rw-r--r--nova/policies/flavor_manage.py19
-rw-r--r--nova/policies/flavor_rxtx.py22
-rw-r--r--nova/policies/flavors.py2
-rw-r--r--nova/policies/fping.py40
-rw-r--r--nova/policies/image_size.py19
-rw-r--r--nova/policies/instance_actions.py35
-rw-r--r--nova/policies/instance_usage_audit_log.py20
-rw-r--r--nova/policies/ips.py28
-rw-r--r--nova/policies/quota_class_sets.py28
-rw-r--r--nova/scheduler/client/report.py2
-rw-r--r--nova/tests/functional/api_sample_tests/api_samples/os-cloudpipe/cloud-pipe-create-req.json.tpl5
-rw-r--r--nova/tests/functional/api_sample_tests/api_samples/os-cloudpipe/cloud-pipe-create-resp.json.tpl3
-rw-r--r--nova/tests/functional/api_sample_tests/api_samples/os-cloudpipe/cloud-pipe-get-resp.json.tpl13
-rw-r--r--nova/tests/functional/api_sample_tests/api_samples/os-cloudpipe/cloud-pipe-update-req.json.tpl6
-rw-r--r--nova/tests/functional/api_sample_tests/test_cloudpipe.py74
-rw-r--r--nova/tests/functional/compute/test_resource_tracker.py1
-rw-r--r--nova/tests/functional/db/test_resource_provider.py104
-rw-r--r--nova/tests/functional/notification_sample_tests/test_instance.py104
-rw-r--r--nova/tests/functional/regressions/test_bug_1554631.py13
-rwxr-xr-xnova/tests/live_migration/hooks/ceph.sh34
-rwxr-xr-xnova/tests/live_migration/hooks/run_tests.sh2
-rwxr-xr-xnova/tests/live_migration/hooks/utils.sh62
-rw-r--r--nova/tests/unit/api/openstack/compute/test_cloudpipe.py168
-rw-r--r--nova/tests/unit/api/openstack/compute/test_cloudpipe_update.py59
-rw-r--r--nova/tests/unit/api/openstack/compute/test_flavor_access.py28
-rw-r--r--nova/tests/unit/api/openstack/compute/test_hosts.py2
-rw-r--r--nova/tests/unit/api/openstack/compute/test_hypervisors.py2
-rw-r--r--nova/tests/unit/api/openstack/compute/test_services.py2
-rw-r--r--nova/tests/unit/api/openstack/test_common.py2
-rw-r--r--nova/tests/unit/compute/test_compute.py61
-rw-r--r--nova/tests/unit/compute/test_compute_api.py46
-rw-r--r--nova/tests/unit/compute/test_compute_mgr.py33
-rw-r--r--nova/tests/unit/conductor/test_conductor.py2
-rw-r--r--nova/tests/unit/db/test_db_api.py72
-rw-r--r--nova/tests/unit/db/test_migrations.py4
-rw-r--r--nova/tests/unit/fake_policy.py1
-rw-r--r--nova/tests/unit/network/test_api.py2
-rw-r--r--nova/tests/unit/network/test_network_info.py2
-rw-r--r--nova/tests/unit/network/test_neutronv2.py92
-rw-r--r--nova/tests/unit/notifications/objects/test_notification.py2
-rw-r--r--nova/tests/unit/objects/test_objects.py2
-rw-r--r--nova/tests/unit/objects/test_service.py4
-rw-r--r--nova/tests/unit/scheduler/test_ironic_host_manager.py2
-rw-r--r--nova/tests/unit/test_pipelib.py77
-rw-r--r--nova/tests/unit/test_policy.py1
-rw-r--r--nova/tests/unit/test_utils.py20
-rw-r--r--nova/tests/unit/virt/libvirt/test_driver.py5
-rw-r--r--nova/tests/unit/virt/powervm/test_driver.py3
-rw-r--r--nova/utils.py43
-rw-r--r--nova/virt/libvirt/firewall.py7
-rw-r--r--nova/virt/libvirt/vif.py12
-rw-r--r--nova/volume/cinder.py4
-rw-r--r--releasenotes/notes/add-neutron-floating_pool-option-cba402c2de407b78.yaml7
-rw-r--r--releasenotes/notes/deprecate-more-nova-network-opts-a9f87c79f7d26438.yaml2
-rw-r--r--releasenotes/notes/project_id_validation-568d31c13c3ef735.yaml13
-rw-r--r--releasenotes/notes/remove-cloudpipe-api-f7aea9372046ecfc.yaml4
-rw-r--r--releasenotes/notes/retrieve_physical_network_from_multi-segment-eec5a490c1ed8739.yaml8
-rw-r--r--releasenotes/notes/service-uuid-online-migration-17d48f198a6d4deb.yaml6
-rw-r--r--releasenotes/notes/volume-attach-versioned-notifications-ef5afde3a5f6a749.yaml11
-rw-r--r--releasenotes/notes/wsgi-applications-8017c3192d2b143e.yaml8
-rw-r--r--requirements.txt2
-rw-r--r--setup.cfg12
-rw-r--r--test-requirements.txt2
134 files changed, 2157 insertions, 1806 deletions
diff --git a/api-guide/source/general_info.rst b/api-guide/source/general_info.rst
index 0af94e6bcb..da818ca4d1 100644
--- a/api-guide/source/general_info.rst
+++ b/api-guide/source/general_info.rst
@@ -114,10 +114,6 @@ In this section we focus on this related to networking.
TODO
-- **Cloudpipe**
-
- TODO
-
- **Extended Networks**
TODO
diff --git a/api-ref/source/index.rst b/api-ref/source/index.rst
index 0b7a37bf8b..1fd4481aac 100644
--- a/api-ref/source/index.rst
+++ b/api-ref/source/index.rst
@@ -52,7 +52,6 @@ the `API guide <http://developer.openstack.org/api-guide/compute/index.html>`_.
.. include:: os-services.inc
.. include:: os-simple-tenant-usage.inc
.. include:: os-server-external-events.inc
-.. include:: os-cloudpipe.inc
.. include:: extensions.inc
.. include:: os-networks.inc
.. include:: os-volumes.inc
@@ -79,3 +78,4 @@ This section contains the reference for APIs that were part of the OpenStack
Compute API in the past, but no longer exist.
.. include:: os-certificates.inc
+.. include:: os-cloudpipe.inc
diff --git a/api-ref/source/os-cloudpipe.inc b/api-ref/source/os-cloudpipe.inc
index 2b102acea1..54af4f183e 100644
--- a/api-ref/source/os-cloudpipe.inc
+++ b/api-ref/source/os-cloudpipe.inc
@@ -8,7 +8,8 @@
This API only works with ``nova-network`` which is
deprecated in favor of Neutron. It should be avoided
- in any new applications.
+ in any new applications. It was removed in the 16.0.0
+ Pike release.
Manages virtual VPNs for projects.
@@ -106,4 +107,4 @@ Request
Response
--------
-There is no body content for the response of a successful PUT request \ No newline at end of file
+There is no body content for the response of a successful PUT request
diff --git a/api-ref/source/os-flavor-access.inc b/api-ref/source/os-flavor-access.inc
index 48b69accbe..5d4b21420c 100644
--- a/api-ref/source/os-flavor-access.inc
+++ b/api-ref/source/os-flavor-access.inc
@@ -57,6 +57,9 @@ Normal response codes: 200
Error response codes: badRequest(400), unauthorized(401), forbidden(403),
itemNotFound(404), conflict(409)
+- 400 - BadRequest - if the `tenant` is not found in your OpenStack
+ deployment, a 400 is returned to prevent typos on the API call.
+
Request
-------
@@ -100,6 +103,9 @@ Normal response codes: 200
Error response codes: badRequest(400), unauthorized(401), forbidden(403),
itemNotFound(404), conflict(409)
+- 400 - BadRequest - if the `tenant` is not found in your OpenStack
+ deployment, a 400 is returned to prevent typos on the API call.
+
Request
-------
@@ -128,4 +134,3 @@ Response
.. literalinclude:: ../../doc/api_samples/flavor-access/flavor-access-remove-tenant-resp.json
:language: javascript
-
diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml
index 8f10485e3d..41b80eb8d0 100644
--- a/api-ref/source/parameters.yaml
+++ b/api-ref/source/parameters.yaml
@@ -1601,8 +1601,10 @@ cpu_info:
``vendor``, ``features`` and ``topology``. The content of this field is
hypervisor specific.
- NOTE: Since version 2.28 ``cpu_info`` field is returned as a dictionary
- instead of string.
+ .. note::
+
+ Since version 2.28 ``cpu_info`` field is returned as a dictionary
+ instead of string.
in: body
required: true
type: object
@@ -1621,9 +1623,12 @@ create_info_id:
create_info_id_resp:
description: |
Its the same arbitrary string which was sent in request body.
- NOTE: This string is passed back to user as it is and not being
- used in Nova internally. So use snapshot_id instead for further
- operation on this snapshot.
+
+ .. note::
+
+ This string is passed back to user as it is and not being
+ used in Nova internally. So use ``snapshot_id`` instead for further
+ operation on this snapshot.
in: body
required: true
type: string
@@ -2205,14 +2210,14 @@ flavor_server:
flavor_swap:
description: |
The size of a dedicated swap disk that will be allocated, in
- GiB. If 0 (the default), no dedicated swap disk will be created.
+ MiB. If 0 (the default), no dedicated swap disk will be created.
in: body
required: true
type: integer
flavor_swap_in:
description: |
The size of a dedicated swap disk that will be allocated, in
- GiB. If 0 (the default), no dedicated swap disk will be created.
+ MiB. If 0 (the default), no dedicated swap disk will be created.
in: body
required: false
type: integer
@@ -3131,9 +3136,14 @@ length:
description: |
The number of lines to fetch from the end of console log. All
lines will be returned if this is not specified.
+
+ .. note::
+
+ This parameter can be specified as not only 'integer' but also 'string'.
+
in: body
required: false
- type: string
+ type: integer
limits:
description: |
Data structure that contains both absolute limits within a deployment.
@@ -3650,10 +3660,10 @@ os-extended-volumes:volumes_attached.id:
type: string
os-getConsoleOutput:
description: |
- The action.
+ The action to get console output of the server.
in: body
required: true
- type: string
+ type: object
os-getRDPConsole:
description: |
The action.
@@ -4913,8 +4923,10 @@ user_data:
description: |
Configuration information or scripts to use upon launch. Must be Base64 encoded.
- NOTE: The 'null' value allowed in Nova legacy v2 API, but due to the strict
- input validation, it isn't allowed in Nova v2.1 API.
+ .. note::
+
+ The ``null`` value allowed in Nova legacy v2 API, but due to the strict
+ input validation, it isn't allowed in Nova v2.1 API.
in: body
required: false
type: string
diff --git a/api-ref/source/servers-admin-action.inc b/api-ref/source/servers-admin-action.inc
index aef7d11ea1..b7227d5c28 100644
--- a/api-ref/source/servers-admin-action.inc
+++ b/api-ref/source/servers-admin-action.inc
@@ -7,11 +7,8 @@
Enables administrators to perform an action on a server. Specify the
action in the request body.
-You can change the administrative password for a server and
-inject network information into a server.
-
-You can migrate, live-migrate, reset networking on, and reset the state
-of a server.
+You can inject network information into, migrate, live-migrate,
+reset networking on, and reset the state of a server.
Inject Network Information (injectNetworkInfo Action)
diff --git a/doc/notification_samples/instance-rebuild-end.json b/doc/notification_samples/instance-rebuild-end.json
index 0523ef1477..0523ef1477 100755..100644
--- a/doc/notification_samples/instance-rebuild-end.json
+++ b/doc/notification_samples/instance-rebuild-end.json
diff --git a/doc/notification_samples/instance-rebuild-error.json b/doc/notification_samples/instance-rebuild-error.json
index cf083da924..cf083da924 100755..100644
--- a/doc/notification_samples/instance-rebuild-error.json
+++ b/doc/notification_samples/instance-rebuild-error.json
diff --git a/doc/notification_samples/instance-rebuild-start.json b/doc/notification_samples/instance-rebuild-start.json
index 4ca41f3db4..4ca41f3db4 100755..100644
--- a/doc/notification_samples/instance-rebuild-start.json
+++ b/doc/notification_samples/instance-rebuild-start.json
diff --git a/doc/notification_samples/instance-volume_attach-end.json b/doc/notification_samples/instance-volume_attach-end.json
new file mode 100644
index 0000000000..a8161b2930
--- /dev/null
+++ b/doc/notification_samples/instance-volume_attach-end.json
@@ -0,0 +1,76 @@
+{
+ "event_type": "instance.volume_attach.end",
+ "payload": {
+ "nova_object.data": {
+ "architecture": null,
+ "availability_zone": "nova",
+ "created_at": "2012-10-29T13:42:11Z",
+ "deleted_at": null,
+ "display_name": "some-server",
+ "display_description":"some-server",
+ "fault": null,
+ "flavor": {
+ "nova_object.name": "FlavorPayload",
+ "nova_object.data": {
+ "flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3",
+ "name": "test_flavor",
+ "projects": null,
+ "root_gb": 1,
+ "vcpus": 1,
+ "ephemeral_gb": 0,
+ "memory_mb": 512,
+ "disabled": false,
+ "rxtx_factor": 1.0,
+ "extra_specs": {
+ "hw:watchdog_action": "disabled"
+ },
+ "swap": 0,
+ "is_public": true,
+ "vcpu_weight": 0
+ },
+ "nova_object.version": "1.3",
+ "nova_object.namespace": "nova"
+ },
+ "host": "compute",
+ "host_name": "some-server",
+ "image_uuid": "a2459075-d96c-40d5-893e-577ff92e721c",
+ "ip_addresses": [{
+ "nova_object.data": {
+ "address": "192.168.1.3",
+ "device_name": "tapce531f90-19",
+ "label": "private-network",
+ "meta": {},
+ "port_uuid": "ce531f90-199f-48c0-816c-13e38010b442",
+ "version": 4,
+ "mac": "fa:16:3e:4c:2c:30"
+ },
+ "nova_object.name": "IpPayload",
+ "nova_object.namespace": "nova",
+ "nova_object.version": "1.0"
+ }],
+ "kernel_id": "",
+ "launched_at": "2012-10-29T13:42:11Z",
+ "metadata": {},
+ "locked":false,
+ "node": "fake-mini",
+ "volume_id": "a07f71dc-8151-4e7d-a0cc-cd24a3f11113",
+ "os_type": null,
+ "power_state":"running",
+ "progress": 0,
+ "ramdisk_id": "",
+ "reservation_id": "r-6w6ruqaz",
+ "state": "active",
+ "task_state": null,
+ "tenant_id": "6f70656e737461636b20342065766572",
+ "terminated_at": null,
+ "auto_disk_config": "MANUAL",
+ "user_id": "fake",
+ "uuid": "0ab886d0-7443-4107-9265-48371bfa662b"
+ },
+ "nova_object.name": "InstanceActionVolumePayload",
+ "nova_object.namespace": "nova",
+ "nova_object.version": "1.0"
+ },
+ "priority": "INFO",
+ "publisher_id": "nova-compute:compute"
+}
diff --git a/doc/notification_samples/instance-volume_attach-error.json b/doc/notification_samples/instance-volume_attach-error.json
new file mode 100644
index 0000000000..425674a849
--- /dev/null
+++ b/doc/notification_samples/instance-volume_attach-error.json
@@ -0,0 +1,86 @@
+{
+ "event_type": "instance.volume_attach.error",
+ "payload": {
+ "nova_object.data": {
+ "architecture": null,
+ "availability_zone": "nova",
+ "created_at": "2012-10-29T13:42:11Z",
+ "deleted_at": null,
+ "display_name": "some-server",
+ "display_description":"some-server",
+ "fault": {
+ "nova_object.data": {
+ "exception": "CinderConnectionFailed",
+ "exception_message": "Connection to cinder host failed: Connection timed out",
+ "function_name": "attach_volume",
+ "module_name": "nova.tests.functional.notification_sample_tests.test_instance"
+ },
+ "nova_object.name": "ExceptionPayload",
+ "nova_object.namespace": "nova",
+ "nova_object.version": "1.0"
+ },
+ "flavor": {
+ "nova_object.name": "FlavorPayload",
+ "nova_object.data": {
+ "flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3",
+ "name": "test_flavor",
+ "projects": null,
+ "root_gb": 1,
+ "vcpus": 1,
+ "ephemeral_gb": 0,
+ "memory_mb": 512,
+ "disabled": false,
+ "rxtx_factor": 1.0,
+ "extra_specs": {
+ "hw:watchdog_action": "disabled"
+ },
+ "swap": 0,
+ "is_public": true,
+ "vcpu_weight": 0
+ },
+ "nova_object.version": "1.3",
+ "nova_object.namespace": "nova"
+ },
+ "host": "compute",
+ "host_name": "some-server",
+ "image_uuid": "a2459075-d96c-40d5-893e-577ff92e721c",
+ "ip_addresses": [{
+ "nova_object.data": {
+ "address": "192.168.1.3",
+ "device_name": "tapce531f90-19",
+ "label": "private-network",
+ "meta": {},
+ "port_uuid": "ce531f90-199f-48c0-816c-13e38010b442",
+ "version": 4,
+ "mac": "fa:16:3e:4c:2c:30"
+ },
+ "nova_object.name": "IpPayload",
+ "nova_object.namespace": "nova",
+ "nova_object.version": "1.0"
+ }],
+ "kernel_id": "",
+ "launched_at": "2012-10-29T13:42:11Z",
+ "metadata": {},
+ "locked":false,
+ "node": "fake-mini",
+ "volume_id": "a07f71dc-8151-4e7d-a0cc-cd24a3f11113",
+ "os_type": null,
+ "power_state":"running",
+ "progress": 0,
+ "ramdisk_id": "",
+ "reservation_id": "r-6w6ruqaz",
+ "state": "active",
+ "task_state": null,
+ "tenant_id": "6f70656e737461636b20342065766572",
+ "terminated_at": null,
+ "auto_disk_config": "MANUAL",
+ "user_id": "fake",
+ "uuid": "0ab886d0-7443-4107-9265-48371bfa662b"
+ },
+ "nova_object.name": "InstanceActionVolumePayload",
+ "nova_object.namespace": "nova",
+ "nova_object.version": "1.0"
+ },
+ "priority": "ERROR",
+ "publisher_id": "nova-compute:compute"
+}
diff --git a/doc/notification_samples/instance-volume_attach-start.json b/doc/notification_samples/instance-volume_attach-start.json
new file mode 100644
index 0000000000..e0fa8cac59
--- /dev/null
+++ b/doc/notification_samples/instance-volume_attach-start.json
@@ -0,0 +1,76 @@
+{
+ "event_type": "instance.volume_attach.start",
+ "payload": {
+ "nova_object.data": {
+ "architecture": null,
+ "availability_zone": "nova",
+ "created_at": "2012-10-29T13:42:11Z",
+ "deleted_at": null,
+ "display_name": "some-server",
+ "display_description":"some-server",
+ "fault": null,
+ "flavor": {
+ "nova_object.name": "FlavorPayload",
+ "nova_object.data": {
+ "flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3",
+ "name": "test_flavor",
+ "projects": null,
+ "root_gb": 1,
+ "vcpus": 1,
+ "ephemeral_gb": 0,
+ "memory_mb": 512,
+ "disabled": false,
+ "rxtx_factor": 1.0,
+ "extra_specs": {
+ "hw:watchdog_action": "disabled"
+ },
+ "swap": 0,
+ "is_public": true,
+ "vcpu_weight": 0
+ },
+ "nova_object.version": "1.3",
+ "nova_object.namespace": "nova"
+ },
+ "host": "compute",
+ "host_name": "some-server",
+ "image_uuid": "a2459075-d96c-40d5-893e-577ff92e721c",
+ "ip_addresses": [{
+ "nova_object.data": {
+ "address": "192.168.1.3",
+ "device_name": "tapce531f90-19",
+ "label": "private-network",
+ "meta": {},
+ "port_uuid": "ce531f90-199f-48c0-816c-13e38010b442",
+ "version": 4,
+ "mac": "fa:16:3e:4c:2c:30"
+ },
+ "nova_object.name": "IpPayload",
+ "nova_object.namespace": "nova",
+ "nova_object.version": "1.0"
+ }],
+ "kernel_id": "",
+ "launched_at": "2012-10-29T13:42:11Z",
+ "metadata": {},
+ "locked":false,
+ "node": "fake-mini",
+ "volume_id": "a07f71dc-8151-4e7d-a0cc-cd24a3f11113",
+ "os_type": null,
+ "power_state":"running",
+ "progress": 0,
+ "ramdisk_id": "",
+ "reservation_id": "r-6w6ruqaz",
+ "state": "active",
+ "task_state": null,
+ "tenant_id": "6f70656e737461636b20342065766572",
+ "terminated_at": null,
+ "auto_disk_config": "MANUAL",
+ "user_id": "fake",
+ "uuid": "0ab886d0-7443-4107-9265-48371bfa662b"
+ },
+ "nova_object.name": "InstanceActionVolumePayload",
+ "nova_object.namespace": "nova",
+ "nova_object.version": "1.0"
+ },
+ "priority": "INFO",
+ "publisher_id": "nova-compute:compute"
+}
diff --git a/doc/notification_samples/instance-volume_detach-end.json b/doc/notification_samples/instance-volume_detach-end.json
new file mode 100644
index 0000000000..f06ff24d79
--- /dev/null
+++ b/doc/notification_samples/instance-volume_detach-end.json
@@ -0,0 +1,76 @@
+{
+ "event_type": "instance.volume_detach.end",
+ "payload": {
+ "nova_object.data": {
+ "architecture": null,
+ "availability_zone": "nova",
+ "created_at": "2012-10-29T13:42:11Z",
+ "deleted_at": null,
+ "display_name": "some-server",
+ "display_description":"some-server",
+ "fault": null,
+ "flavor": {
+ "nova_object.name": "FlavorPayload",
+ "nova_object.data": {
+ "flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3",
+ "name": "test_flavor",
+ "projects": null,
+ "root_gb": 1,
+ "vcpus": 1,
+ "ephemeral_gb": 0,
+ "memory_mb": 512,
+ "disabled": false,
+ "rxtx_factor": 1.0,
+ "extra_specs": {
+ "hw:watchdog_action": "disabled"
+ },
+ "swap": 0,
+ "is_public": true,
+ "vcpu_weight": 0
+ },
+ "nova_object.version": "1.3",
+ "nova_object.namespace": "nova"
+ },
+ "host": "compute",
+ "host_name": "some-server",
+ "image_uuid": "a2459075-d96c-40d5-893e-577ff92e721c",
+ "ip_addresses": [{
+ "nova_object.data": {
+ "address": "192.168.1.3",
+ "device_name": "tapce531f90-19",
+ "label": "private-network",
+ "meta": {},
+ "port_uuid": "ce531f90-199f-48c0-816c-13e38010b442",
+ "version": 4,
+ "mac": "fa:16:3e:4c:2c:30"
+ },
+ "nova_object.name": "IpPayload",
+ "nova_object.namespace": "nova",
+ "nova_object.version": "1.0"
+ }],
+ "kernel_id": "",
+ "launched_at": "2012-10-29T13:42:11Z",
+ "metadata": {},
+ "locked":false,
+ "node": "fake-mini",
+ "volume_id": "a07f71dc-8151-4e7d-a0cc-cd24a3f11113",
+ "os_type": null,
+ "power_state":"running",
+ "progress": 0,
+ "ramdisk_id": "",
+ "reservation_id": "r-6w6ruqaz",
+ "state": "active",
+ "task_state": null,
+ "tenant_id": "6f70656e737461636b20342065766572",
+ "terminated_at": null,
+ "auto_disk_config": "MANUAL",
+ "user_id": "fake",
+ "uuid": "0ab886d0-7443-4107-9265-48371bfa662b"
+ },
+ "nova_object.name": "InstanceActionVolumePayload",
+ "nova_object.namespace": "nova",
+ "nova_object.version": "1.0"
+ },
+ "priority": "INFO",
+ "publisher_id": "nova-compute:compute"
+}
diff --git a/doc/notification_samples/instance-volume_detach-start.json b/doc/notification_samples/instance-volume_detach-start.json
new file mode 100644
index 0000000000..ab2d263b75
--- /dev/null
+++ b/doc/notification_samples/instance-volume_detach-start.json
@@ -0,0 +1,76 @@
+{
+ "event_type": "instance.volume_detach.start",
+ "payload": {
+ "nova_object.data": {
+ "architecture": null,
+ "availability_zone": "nova",
+ "created_at": "2012-10-29T13:42:11Z",
+ "deleted_at": null,
+ "display_name": "some-server",
+ "display_description":"some-server",
+ "fault": null,
+ "flavor": {
+ "nova_object.name": "FlavorPayload",
+ "nova_object.data": {
+ "flavorid": "a22d5517-147c-4147-a0d1-e698df5cd4e3",
+ "name": "test_flavor",
+ "projects": null,
+ "root_gb": 1,
+ "vcpus": 1,
+ "ephemeral_gb": 0,
+ "memory_mb": 512,
+ "disabled": false,
+ "rxtx_factor": 1.0,
+ "extra_specs": {
+ "hw:watchdog_action": "disabled"
+ },
+ "swap": 0,
+ "is_public": true,
+ "vcpu_weight": 0
+ },
+ "nova_object.version": "1.3",
+ "nova_object.namespace": "nova"
+ },
+ "host": "compute",
+ "host_name": "some-server",
+ "image_uuid": "a2459075-d96c-40d5-893e-577ff92e721c",
+ "ip_addresses": [{
+ "nova_object.data": {
+ "address": "192.168.1.3",
+ "device_name": "tapce531f90-19",
+ "label": "private-network",
+ "meta": {},
+ "port_uuid": "ce531f90-199f-48c0-816c-13e38010b442",
+ "version": 4,
+ "mac": "fa:16:3e:4c:2c:30"
+ },
+ "nova_object.name": "IpPayload",
+ "nova_object.namespace": "nova",
+ "nova_object.version": "1.0"
+ }],
+ "kernel_id": "",
+ "launched_at": "2012-10-29T13:42:11Z",
+ "metadata": {},
+ "locked":false,
+ "node": "fake-mini",
+ "volume_id": "a07f71dc-8151-4e7d-a0cc-cd24a3f11113",
+ "os_type": null,
+ "power_state":"running",
+ "progress": 0,
+ "ramdisk_id": "",
+ "reservation_id": "r-6w6ruqaz",
+ "state": "active",
+ "task_state": null,
+ "tenant_id": "6f70656e737461636b20342065766572",
+ "terminated_at": null,
+ "auto_disk_config": "MANUAL",
+ "user_id": "fake",
+ "uuid": "0ab886d0-7443-4107-9265-48371bfa662b"
+ },
+ "nova_object.name": "InstanceActionVolumePayload",
+ "nova_object.namespace": "nova",
+ "nova_object.version": "1.0"
+ },
+ "priority": "INFO",
+ "publisher_id": "nova-compute:compute"
+}
diff --git a/doc/source/api_plugins.rst b/doc/source/api_plugins.rst
index ca45e2bb08..10ca3a8fc6 100644
--- a/doc/source/api_plugins.rst
+++ b/doc/source/api_plugins.rst
@@ -39,7 +39,7 @@ please refer to the `OpenStack API WG
Basic plugin structure
----------------------
-A very basic skeleton of a v2.1 plugin can be seen `here in the unittests <http://git.openstack.org/cgit/openstack/nova/tree/nova/tests/unit/api/openstack/compute/test_plugins/basic.py>`_. An annotated version below::
+A very basic skeleton of a v2.1 plugin can be seen `here in the unittests <https://git.openstack.org/cgit/openstack/nova/tree/nova/tests/unit/api/openstack/compute/basic.py>`_. An annotated version below::
"""Basic Test Extension"""
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 19ede311b3..501ab34c40 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -123,8 +123,6 @@ man_pages = [
[u'OpenStack'], 1),
('man/nova-cells', 'nova-cells', u'Cloud controller fabric',
[u'OpenStack'], 1),
- ('man/nova-cert', 'nova-cert', u'Cloud controller fabric',
- [u'OpenStack'], 1),
('man/nova-compute', 'nova-compute', u'Cloud controller fabric',
[u'OpenStack'], 1),
('man/nova-console', 'nova-console', u'Cloud controller fabric',
diff --git a/doc/source/index.rst b/doc/source/index.rst
index d39c470865..8ff6ee3e20 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -159,6 +159,7 @@ Open Development.
quotas
threading
vmstates
+ wsgi
Architecture Evolution Plans
-----------------------------
diff --git a/doc/source/man/nova-api-metadata.rst b/doc/source/man/nova-api-metadata.rst
index a2afb15722..d55eb05fce 100644
--- a/doc/source/man/nova-api-metadata.rst
+++ b/doc/source/man/nova-api-metadata.rst
@@ -41,6 +41,7 @@ SEE ALSO
========
* `OpenStack Nova <https://docs.openstack.org/developer/nova>`__
+* `Using WSGI with Nova <https://docs.openstack.org/devloper/nova/wsgi.html>`__
BUGS
====
diff --git a/doc/source/man/nova-api-os-compute.rst b/doc/source/man/nova-api-os-compute.rst
index f096952153..4d687a53e1 100644
--- a/doc/source/man/nova-api-os-compute.rst
+++ b/doc/source/man/nova-api-os-compute.rst
@@ -41,6 +41,7 @@ SEE ALSO
========
* `OpenStack Nova <https://docs.openstack.org/developer/nova>`__
+* `Using WSGI with Nova <https://docs.openstack.org/devloper/nova/wsgi.html>`__
BUGS
====
diff --git a/doc/source/man/nova-api.rst b/doc/source/man/nova-api.rst
index 3be20f15f8..8589044afe 100644
--- a/doc/source/man/nova-api.rst
+++ b/doc/source/man/nova-api.rst
@@ -21,7 +21,8 @@ SYNOPSIS
DESCRIPTION
===========
-nova-api is a server daemon that serves the nova EC2 and OpenStack APIs in separate greenthreads
+nova-api is a server daemon that serves the metadata and compute APIs in
+separate greenthreads
OPTIONS
=======
@@ -41,6 +42,7 @@ SEE ALSO
========
* `OpenStack Nova <https://docs.openstack.org/developer/nova>`__
+* `Using WSGI with Nova <https://docs.openstack.org/devloper/nova/wsgi.html>`__
BUGS
====
diff --git a/doc/source/upgrade.rst b/doc/source/upgrade.rst
index 46dad7bddb..0eb0ef0563 100644
--- a/doc/source/upgrade.rst
+++ b/doc/source/upgrade.rst
@@ -86,7 +86,7 @@ same time.
``[upgrade_levels]compute=auto`` in nova.conf. It is safest to
start nova-conductor first and nova-api last. Note that you may
use a static alias name instead of ``auto``, such as
- ``[upgrade_levels]compute=newton``. Also note that this step is
+ ``[upgrade_levels]compute=<release_name>``. Also note that this step is
only required if compute services are not upgraded in lock-step
with the control services.
@@ -107,7 +107,7 @@ same time.
starts, it automatically detects which version of the compute RPC protocol
to use, and it can decide if it is safe to do any online data migrations.
Note, if you used a static value for the upgrade_level, such as
- ``[upgrade_levels]compute=newton``, you must update nova.conf to remove
+ ``[upgrade_levels]compute=<release_name>``, you must update nova.conf to remove
that configuration value and do a full service restart.
* Now all the services are upgraded and signaled, the system is able to use
diff --git a/doc/source/vendordata.rst b/doc/source/vendordata.rst
index facd2fe4e1..e108e3cad5 100644
--- a/doc/source/vendordata.rst
+++ b/doc/source/vendordata.rst
@@ -111,6 +111,12 @@ The following data is passed to your REST service as a JSON encoded POST:
| metadata | As specified by the user at boot time. |
+-------------+-------------------------------------------------+
-The REST service is also passed the Keystone authentication details for the
-original API request which caused this boot, which can be used by the REST
-service to determine if the action is authorized.
+Deployment considerations
+=========================
+
+Nova provides authentication to external metadata services in order to provide
+some level of certainty that the request came from nova. This is done by
+providing a service token with the request -- you can then just deploy your
+metadata service with the keystone authentication WSGI middleware. This is
+configured using the keystone authentication parameters in the
+``vendordata_dynamic_auth`` configuration group.
diff --git a/doc/source/wsgi.rst b/doc/source/wsgi.rst
new file mode 100644
index 0000000000..6b314b4832
--- /dev/null
+++ b/doc/source/wsgi.rst
@@ -0,0 +1,36 @@
+Using WSGI with Nova
+====================
+
+Though the compute and metadata APIs can be run using independent scripts that
+provide eventlet-based HTTP servers, it is generally considered more performant
+and flexible to run them using a generic HTTP server that supports WSGI_ (such
+as Apache_ or nginx_).
+
+The nova project provides two automatically generated entry points that
+support this: ``nova-api-wsgi`` and ``nova-metadata-wsgi``. These read
+``nova.conf`` and ``api-paste.ini`` and generate the required module-level
+``application`` that most WSGI servers require. If nova is installed using pip,
+these two scripts will be installed into whatever the expected ``bin``
+directory is for the environment.
+
+The new scripts replace older experimental scripts that could be found in the
+``nova/wsgi`` directory of the code repository. The new scripts are *not*
+experimental.
+
+When running the compute and metadata services with WSGI, sharing the compute
+and metadata service in the same process is not supported (as it is in the
+eventlet-based scripts).
+
+In devstack as of May 2017, the compute and metadata APIs are hosted by a
+Apache communicating with uwsgi_ via mod_proxy_uwsgi_. Inspecting the
+configuration created there can provide some guidance on one option for
+managing the WSGI scripts. It is important to remember, however, that one of
+the major features of using WSGI is that there are many different ways to host
+a WSGI application. Different servers make different choices about performance
+and configurability.
+
+.. _WSGI: https://www.python.org/dev/peps/pep-3333/
+.. _apache: http://httpd.apache.org/
+.. _nginx: http://nginx.org/en/
+.. _uwsgi: https://uwsgi-docs.readthedocs.io/
+.. _mod_proxy_uwsgi: http://uwsgi-docs.readthedocs.io/en/latest/Apache.html#mod-proxy-uwsgi
diff --git a/nova/cloudpipe/__init__.py b/nova/api/metadata/wsgi.py
index aa6c77a5aa..d2f33c2d6f 100644
--- a/nova/cloudpipe/__init__.py
+++ b/nova/api/metadata/wsgi.py
@@ -1,7 +1,3 @@
-# Copyright 2010 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# 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
@@ -13,12 +9,12 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
+"""WSGI application entry-point for Nova Metadata API, installed by pbr."""
+
+from nova.api.openstack import wsgi_app
+
+NAME = "metadata"
-"""
-:mod:`nova.cloudpipe` -- VPN Server Management
-=====================================================
-.. automodule:: nova.cloudpipe
- :platform: Unix
- :synopsis: An OpenVPN server for every nova user.
-"""
+def init_application():
+ return wsgi_app.init_application(NAME)
diff --git a/nova/api/openstack/compute/agents.py b/nova/api/openstack/compute/agents.py
index b1450fc681..489c85a920 100644
--- a/nova/api/openstack/compute/agents.py
+++ b/nova/api/openstack/compute/agents.py
@@ -25,9 +25,6 @@ from nova.policies import agents as agents_policies
from nova import utils
-ALIAS = "os-agents"
-
-
class AgentController(wsgi.Controller):
"""The agent is talking about guest agent.The host can use this for
things like accessing files on the disk, configuring networking,
@@ -166,22 +163,3 @@ class AgentController(wsgi.Controller):
except exception.AgentBuildExists as ex:
raise webob.exc.HTTPConflict(explanation=ex.format_message())
return {'agent': agent}
-
-
-class Agents(extensions.V21APIExtensionBase):
- """Agents support."""
-
- name = "Agents"
- alias = ALIAS
- version = 1
-
- def get_resources(self):
- resource = [extensions.ResourceExtension(ALIAS,
- AgentController())]
- return resource
-
- def get_controller_extensions(self):
- """It's an abstract function V21APIExtensionBase and the extension
- will not be loaded without it.
- """
- return []
diff --git a/nova/api/openstack/compute/assisted_volume_snapshots.py b/nova/api/openstack/compute/assisted_volume_snapshots.py
index 97052e346b..b2f60933a4 100644
--- a/nova/api/openstack/compute/assisted_volume_snapshots.py
+++ b/nova/api/openstack/compute/assisted_volume_snapshots.py
@@ -29,9 +29,6 @@ from nova import exception
from nova.policies import assisted_volume_snapshots as avs_policies
-ALIAS = 'os-assisted-volume-snapshots'
-
-
class AssistedVolumeSnapshotsController(wsgi.Controller):
"""The Assisted volume snapshots API controller for the OpenStack API."""
@@ -96,22 +93,3 @@ class AssistedVolumeSnapshotsController(wsgi.Controller):
# microversion, which we should just do in a single microversion
# across all APIs when we fix status code wrinkles.
raise exc.HTTPBadRequest(explanation=e.format_message())
-
-
-class AssistedVolumeSnapshots(extensions.V21APIExtensionBase):
- """Assisted volume snapshots."""
-
- name = "AssistedVolumeSnapshots"
- alias = ALIAS
- version = 1
-
- def get_resources(self):
- res = [extensions.ResourceExtension(ALIAS,
- AssistedVolumeSnapshotsController())]
- return res
-
- def get_controller_extensions(self):
- """It's an abstract function V21APIExtensionBase and the extension
- will not be loaded without it.
- """
- return []
diff --git a/nova/api/openstack/compute/availability_zone.py b/nova/api/openstack/compute/availability_zone.py
index e31f40aa42..33e61d3fb1 100644
--- a/nova/api/openstack/compute/availability_zone.py
+++ b/nova/api/openstack/compute/availability_zone.py
@@ -22,7 +22,6 @@ from nova.policies import availability_zone as az_policies
from nova import servicegroup
CONF = nova.conf.CONF
-ALIAS = "os-availability-zone"
ATTRIBUTE_NAME = "availability_zone"
@@ -121,28 +120,6 @@ class AvailabilityZoneController(wsgi.Controller):
return self._describe_availability_zones_verbose(context)
-class AvailabilityZone(extensions.V21APIExtensionBase):
- """1. Add availability_zone to the Create Server API.
- 2. Add availability zones describing.
- """
-
- name = "AvailabilityZone"
- alias = ALIAS
- version = 1
-
- def get_resources(self):
- resource = [extensions.ResourceExtension(ALIAS,
- AvailabilityZoneController(),
- collection_actions={'detail': 'GET'})]
- return resource
-
- def get_controller_extensions(self):
- """It's an abstract function V21APIExtensionBase and the extension
- will not be loaded without it.
- """
- return []
-
-
# NOTE(gmann): This function is not supposed to use 'body_deprecated_param'
# parameter as this is placed to handle scheduler_hint extension for V2.1.
def server_create(server_dict, create_kwargs, body_deprecated_param):
diff --git a/nova/api/openstack/compute/cloudpipe.py b/nova/api/openstack/compute/cloudpipe.py
index de7a5e5233..aff0482e66 100644
--- a/nova/api/openstack/compute/cloudpipe.py
+++ b/nova/api/openstack/compute/cloudpipe.py
@@ -14,26 +14,10 @@
"""Connect your vlan to the world."""
-from oslo_utils import fileutils
from webob import exc
-from nova.api.openstack.compute.schemas import cloudpipe
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
-from nova.api import validation
-from nova.cloudpipe import pipelib
-from nova import compute
-from nova.compute import utils as compute_utils
-from nova.compute import vm_states
-import nova.conf
-from nova import exception
-from nova.i18n import _
-from nova import network
-from nova import objects
-from nova.policies import cloudpipe as cp_policies
-from nova import utils
-
-CONF = nova.conf.CONF
ALIAS = 'os-cloudpipe'
@@ -41,130 +25,23 @@ ALIAS = 'os-cloudpipe'
class CloudpipeController(wsgi.Controller):
"""Handle creating and listing cloudpipe instances."""
- def __init__(self):
- self.compute_api = compute.API()
- self.network_api = network.API()
- self.cloudpipe = pipelib.CloudPipe()
- self.setup()
-
- def setup(self):
- """Ensure the keychains and folders exist."""
- # NOTE(vish): One of the drawbacks of doing this in the api is
- # the keys will only be on the api node that launched
- # the cloudpipe.
- fileutils.ensure_tree(CONF.crypto.keys_path)
-
- def _get_all_cloudpipes(self, context):
- """Get all cloudpipes."""
- instances = self.compute_api.get_all(context,
- search_opts={'deleted': False})
- return [instance for instance in instances
- if pipelib.is_vpn_image(instance.image_ref)
- and instance.vm_state != vm_states.DELETED]
-
- def _get_cloudpipe_for_project(self, context):
- """Get the cloudpipe instance for a project from context."""
- cloudpipes = self._get_all_cloudpipes(context) or [None]
- return cloudpipes[0]
-
- def _vpn_dict(self, context, project_id, instance):
- elevated = context.elevated()
- rv = {'project_id': project_id}
- if not instance:
- rv['state'] = 'pending'
- return rv
- rv['instance_id'] = instance.uuid
- rv['created_at'] = utils.isotime(instance.created_at)
- nw_info = compute_utils.get_nw_info_for_instance(instance)
- if not nw_info:
- return rv
- vif = nw_info[0]
- ips = [ip for ip in vif.fixed_ips() if ip['version'] == 4]
- if ips:
- rv['internal_ip'] = ips[0]['address']
- # NOTE(vish): Currently network_api.get does an owner check on
- # project_id. This is probably no longer necessary
- # but rather than risk changes in the db layer,
- # we are working around it here by changing the
- # project_id in the context. This can be removed
- # if we remove the project_id check in the db.
- elevated.project_id = project_id
- network = self.network_api.get(elevated, vif['network']['id'])
- if network:
- vpn_ip = network['vpn_public_address']
- vpn_port = network['vpn_public_port']
- rv['public_ip'] = vpn_ip
- rv['public_port'] = vpn_port
- if vpn_ip and vpn_port:
- if utils.vpn_ping(vpn_ip, vpn_port):
- rv['state'] = 'running'
- else:
- rv['state'] = 'down'
- else:
- rv['state'] = 'invalid'
- return rv
-
- @extensions.expected_errors((400, 403))
- @validation.schema(cloudpipe.create)
+ @extensions.expected_errors((410))
def create(self, req, body):
"""Create a new cloudpipe instance, if none exists.
Parameters: {cloudpipe: {'project_id': ''}}
"""
+ raise exc.HTTPGone()
- context = req.environ['nova.context']
- context.can(cp_policies.BASE_POLICY_NAME)
- params = body.get('cloudpipe', {})
- project_id = params.get('project_id', context.project_id)
- # NOTE(vish): downgrade to project context. Note that we keep
- # the same token so we can still talk to glance
- context.project_id = project_id
- context.user_id = 'project-vpn'
- context.is_admin = False
- context.roles = []
- instance = self._get_cloudpipe_for_project(context)
- if not instance:
- try:
- result = self.cloudpipe.launch_vpn_instance(context)
- instance = result[0][0]
- except exception.NoMoreNetworks:
- msg = _("Unable to claim IP for VPN instances, ensure it "
- "isn't running, and try again in a few minutes")
- raise exc.HTTPBadRequest(explanation=msg)
- return {'instance_id': instance.uuid}
-
- @extensions.expected_errors((400, 403, 404))
+ @extensions.expected_errors((410))
def index(self, req):
"""List running cloudpipe instances."""
- context = req.environ['nova.context']
- context.can(cp_policies.BASE_POLICY_NAME)
- vpns = [self._vpn_dict(context, x['project_id'], x)
- for x in self._get_all_cloudpipes(context)]
- return {'cloudpipes': vpns}
-
- @wsgi.response(202)
- @extensions.expected_errors(400)
- @validation.schema(cloudpipe.update)
+ raise exc.HTTPGone()
+
+ @extensions.expected_errors(410)
def update(self, req, id, body):
"""Configure cloudpipe parameters for the project."""
-
- context = req.environ['nova.context']
- context.can(cp_policies.BASE_POLICY_NAME)
-
- if id != "configure-project":
- msg = _("Unknown action %s") % id
- raise exc.HTTPBadRequest(explanation=msg)
-
- project_id = context.project_id
- networks = objects.NetworkList.get_by_project(context, project_id)
-
- params = body['configure_project']
- vpn_ip = params['vpn_ip']
- vpn_port = params['vpn_port']
- for nw in networks:
- nw.vpn_public_address = vpn_ip
- nw.vpn_public_port = vpn_port
- nw.save()
+ raise exc.HTTPGone()
class Cloudpipe(extensions.V21APIExtensionBase):
diff --git a/nova/api/openstack/compute/extension_info.py b/nova/api/openstack/compute/extension_info.py
index ec32e17a4e..4aa812e213 100644
--- a/nova/api/openstack/compute/extension_info.py
+++ b/nova/api/openstack/compute/extension_info.py
@@ -169,10 +169,23 @@ v21_to_v2_alias_mapping = {
# completely from the code we're going to have a static list here to
# keep the surface metadata the same.
hardcoded_extensions = [
+ {'name': 'Agents',
+ 'alias': 'os-agents',
+ 'description': 'Agents support.'
+ },
{'name': 'Aggregates',
'alias': 'os-aggregates',
'description': 'Admin-only aggregate administration.'
},
+ {'name': 'AssistedVolumeSnapshots',
+ 'alias': 'os-assisted-volume-snapshots',
+ 'description': 'Assisted volume snapshots.'
+ },
+ {'name': 'AvailabilityZone',
+ 'alias': 'os-availability-zone',
+ 'description': '1. Add availability_zone to the Create Server API.\n'
+ ' 2. Add availability zones describing.\n ',
+ },
{'name': 'DiskConfig',
'alias': 'os-disk-config',
'description': 'Disk Management Extension.'},
@@ -186,6 +199,9 @@ hardcoded_extensions = [
{'name': 'Personality',
'description': 'Personality support.',
'alias': 'os-personality'},
+ {'name': 'FixedIPs',
+ 'description': 'Fixed IPs support.',
+ 'alias': 'os-fixed-ips'},
{'name': 'Flavors',
'description': 'Flavors Extension.',
'alias': 'flavors'},
@@ -201,9 +217,27 @@ hardcoded_extensions = [
{'name': 'FlavorAccess',
'description': 'Flavor access support.',
'alias': 'os-flavor-access'},
+ {'name': 'FloatingIpDns',
+ 'description': 'Floating IP DNS support.',
+ 'alias': 'os-floating-ip-dns'},
+ {'name': 'FloatingIpPools',
+ 'description': 'Floating IPs support.',
+ 'alias': 'os-floating-ip-pools'},
+ {'name': 'FloatingIps',
+ 'description': 'Floating IPs support.',
+ 'alias': 'os-floating-ips'},
+ {'name': 'FloatingIpsBulk',
+ 'description': 'Bulk handling of Floating IPs.',
+ 'alias': 'os-floating-ips-bulk'},
+ {'name': 'OSInstanceUsageAuditLog',
+ 'description': 'Admin-only Task Log Monitoring.',
+ 'alias': 'os-instance-usage-audit-log'},
{'name': 'Keypairs',
'description': 'Keypair Support.',
- 'alias': 'os-keypairs'}
+ 'alias': 'os-keypairs'},
+ {'name': 'ServerMetadata',
+ 'description': 'Server metadata Support.',
+ 'alias': 'server-metadata'},
]
diff --git a/nova/api/openstack/compute/fixed_ips.py b/nova/api/openstack/compute/fixed_ips.py
index a2fc25a0ec..1814f042a8 100644
--- a/nova/api/openstack/compute/fixed_ips.py
+++ b/nova/api/openstack/compute/fixed_ips.py
@@ -26,8 +26,6 @@ from nova.i18n import _
from nova import objects
from nova.policies import fixed_ips as fi_policies
-ALIAS = 'os-fixed-ips'
-
class FixedIPController(wsgi.Controller):
@@ -108,21 +106,3 @@ class FixedIPController(wsgi.Controller):
except exception.FixedIpInvalid:
msg = _("Fixed IP %s not valid") % address
raise webob.exc.HTTPBadRequest(explanation=msg)
-
-
-class FixedIps(extensions.V21APIExtensionBase):
- """Fixed IPs support."""
-
- name = "FixedIPs"
- alias = ALIAS
- version = 1
-
- def get_resources(self):
- member_actions = {'action': 'POST'}
- resources = extensions.ResourceExtension(ALIAS,
- FixedIPController(),
- member_actions=member_actions)
- return [resources]
-
- def get_controller_extensions(self):
- return []
diff --git a/nova/api/openstack/compute/flavor_access.py b/nova/api/openstack/compute/flavor_access.py
index 163a327f3e..b591212d69 100644
--- a/nova/api/openstack/compute/flavor_access.py
+++ b/nova/api/openstack/compute/flavor_access.py
@@ -21,6 +21,7 @@ from nova.api.openstack import api_version_request
from nova.api.openstack import common
from nova.api.openstack.compute.schemas import flavor_access
from nova.api.openstack import extensions
+from nova.api.openstack import identity
from nova.api.openstack import wsgi
from nova.api import validation
from nova import exception
@@ -95,6 +96,7 @@ class FlavorActionController(wsgi.Controller):
vals = body['addTenantAccess']
tenant = vals['tenant']
+ identity.verify_project_id(context, tenant)
flavor = common.get_flavor(context, id)
@@ -120,6 +122,7 @@ class FlavorActionController(wsgi.Controller):
vals = body['removeTenantAccess']
tenant = vals['tenant']
+ identity.verify_project_id(context, tenant)
# NOTE(gibi): We have to load a flavor from the db here as
# flavor.remove_access() will try to emit a notification and that needs
diff --git a/nova/api/openstack/compute/floating_ip_dns.py b/nova/api/openstack/compute/floating_ip_dns.py
index ae7f8732c2..3e2d432126 100644
--- a/nova/api/openstack/compute/floating_ip_dns.py
+++ b/nova/api/openstack/compute/floating_ip_dns.py
@@ -29,9 +29,6 @@ from nova import network
from nova.policies import floating_ip_dns as fid_policies
-ALIAS = "os-floating-ip-dns"
-
-
def _translate_dns_entry_view(dns_entry):
result = {}
result['ip'] = dns_entry.get('ip')
@@ -255,32 +252,3 @@ class FloatingIPDNSEntryController(wsgi.Controller):
common.raise_feature_not_supported()
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.format_message())
-
-
-class FloatingIpDns(extensions.V21APIExtensionBase):
- """Floating IP DNS support."""
-
- name = "FloatingIpDns"
- alias = ALIAS
- version = 1
-
- def get_resources(self):
- resources = []
-
- res = extensions.ResourceExtension(ALIAS,
- controller=FloatingIPDNSDomainController())
- resources.append(res)
-
- res = extensions.ResourceExtension('entries',
- controller=FloatingIPDNSEntryController(),
- parent={'member_name': 'domain',
- 'collection_name': 'os-floating-ip-dns'})
- resources.append(res)
-
- return resources
-
- def get_controller_extensions(self):
- """It's an abstract function V21APIExtensionBase and the extension
- will not be loaded without it.
- """
- return []
diff --git a/nova/api/openstack/compute/floating_ip_pools.py b/nova/api/openstack/compute/floating_ip_pools.py
index d014b18b98..d1ddcb61a8 100644
--- a/nova/api/openstack/compute/floating_ip_pools.py
+++ b/nova/api/openstack/compute/floating_ip_pools.py
@@ -20,9 +20,6 @@ from nova import network
from nova.policies import floating_ip_pools as fip_policies
-ALIAS = 'os-floating-ip-pools'
-
-
def _translate_floating_ip_view(pool_name):
return {
'name': pool_name,
@@ -51,22 +48,3 @@ class FloatingIPPoolsController(wsgi.Controller):
context.can(fip_policies.BASE_POLICY_NAME)
pools = self.network_api.get_floating_ip_pools(context)
return _translate_floating_ip_pools_view(pools)
-
-
-class FloatingIpPools(extensions.V21APIExtensionBase):
- """Floating IPs support."""
-
- name = "FloatingIpPools"
- alias = ALIAS
- version = 1
-
- def get_resources(self):
- resource = [extensions.ResourceExtension(ALIAS,
- FloatingIPPoolsController())]
- return resource
-
- def get_controller_extensions(self):
- """It's an abstract function V21APIExtensionBase and the extension
- will not be loaded without it.
- """
- return []
diff --git a/nova/api/openstack/compute/floating_ips.py b/nova/api/openstack/compute/floating_ips.py
index 5492453094..e79bb46221 100644
--- a/nova/api/openstack/compute/floating_ips.py
+++ b/nova/api/openstack/compute/floating_ips.py
@@ -36,7 +36,6 @@ from nova.policies import floating_ips as fi_policies
LOG = logging.getLogger(__name__)
-ALIAS = 'os-floating-ips'
def _translate_floating_ip_view(floating_ip):
@@ -327,19 +326,3 @@ class FloatingIPActionController(wsgi.Controller):
msg = _("Floating IP %(address)s is not associated with instance "
"%(id)s.") % {'address': address, 'id': id}
raise webob.exc.HTTPConflict(explanation=msg)
-
-
-class FloatingIps(extensions.V21APIExtensionBase):
- """Floating IPs support."""
-
- name = "FloatingIps"
- alias = ALIAS
- version = 1
-
- def get_resources(self):
- resource = [extensions.ResourceExtension(ALIAS,
- FloatingIPController())]
- return resource
-
- def get_controller_extensions(self):
- return []
diff --git a/nova/api/openstack/compute/floating_ips_bulk.py b/nova/api/openstack/compute/floating_ips_bulk.py
index c0114fe70c..ea973b98d6 100644
--- a/nova/api/openstack/compute/floating_ips_bulk.py
+++ b/nova/api/openstack/compute/floating_ips_bulk.py
@@ -31,9 +31,6 @@ from nova.policies import floating_ips_bulk as fib_policies
CONF = nova.conf.CONF
-ALIAS = 'os-floating-ips-bulk'
-
-
class FloatingIPBulkController(wsgi.Controller):
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@@ -154,22 +151,3 @@ class FloatingIPBulkController(wsgi.Controller):
return net.iter_hosts()
except netaddr.AddrFormatError as exc:
raise exception.InvalidInput(reason=six.text_type(exc))
-
-
-class FloatingIpsBulk(extensions.V21APIExtensionBase):
- """Bulk handling of Floating IPs."""
-
- name = "FloatingIpsBulk"
- alias = ALIAS
- version = 1
-
- def get_resources(self):
- resource = [extensions.ResourceExtension(ALIAS,
- FloatingIPBulkController())]
- return resource
-
- def get_controller_extensions(self):
- """It's an abstract function V21APIExtensionBase and the extension
- will not be loaded without it.
- """
- return []
diff --git a/nova/api/openstack/compute/instance_usage_audit_log.py b/nova/api/openstack/compute/instance_usage_audit_log.py
index 3423db7de6..fb000afae2 100644
--- a/nova/api/openstack/compute/instance_usage_audit_log.py
+++ b/nova/api/openstack/compute/instance_usage_audit_log.py
@@ -28,8 +28,6 @@ from nova import utils
CONF = nova.conf.CONF
-ALIAS = 'os-instance-usage-audit-log'
-
class InstanceUsageAuditLogController(wsgi.Controller):
def __init__(self):
@@ -111,18 +109,3 @@ class InstanceUsageAuditLogController(wsgi.Controller):
total_errors=total_errors,
overall_status=overall_status,
log=log)
-
-
-class InstanceUsageAuditLog(extensions.V21APIExtensionBase):
- """Admin-only Task Log Monitoring."""
- name = "OSInstanceUsageAuditLog"
- alias = ALIAS
- version = 1
-
- def get_resources(self):
- ext = extensions.ResourceExtension('os-instance_usage_audit_log',
- InstanceUsageAuditLogController())
- return [ext]
-
- def get_controller_extensions(self):
- return []
diff --git a/nova/api/openstack/compute/routes.py b/nova/api/openstack/compute/routes.py
index 75b2241cf4..ff33c955e0 100644
--- a/nova/api/openstack/compute/routes.py
+++ b/nova/api/openstack/compute/routes.py
@@ -19,7 +19,10 @@ import functools
import nova.api.openstack
from nova.api.openstack.compute import admin_actions
from nova.api.openstack.compute import admin_password
+from nova.api.openstack.compute import agents
from nova.api.openstack.compute import aggregates
+from nova.api.openstack.compute import assisted_volume_snapshots
+from nova.api.openstack.compute import availability_zone
from nova.api.openstack.compute import config_drive
from nova.api.openstack.compute import console_output
from nova.api.openstack.compute import create_backup
@@ -30,13 +33,18 @@ from nova.api.openstack.compute import extended_server_attributes
from nova.api.openstack.compute import extended_status
from nova.api.openstack.compute import extended_volumes
from nova.api.openstack.compute import extension_info
+from nova.api.openstack.compute import fixed_ips
from nova.api.openstack.compute import flavor_access
from nova.api.openstack.compute import flavor_manage
from nova.api.openstack.compute import flavor_rxtx
from nova.api.openstack.compute import flavors
from nova.api.openstack.compute import flavors_extraspecs
+from nova.api.openstack.compute import floating_ip_dns
+from nova.api.openstack.compute import floating_ip_pools
from nova.api.openstack.compute import floating_ips
+from nova.api.openstack.compute import floating_ips_bulk
from nova.api.openstack.compute import hide_server_addresses
+from nova.api.openstack.compute import instance_usage_audit_log
from nova.api.openstack.compute import keypairs
from nova.api.openstack.compute import lock_server
from nova.api.openstack.compute import migrate_server
@@ -45,6 +53,7 @@ from nova.api.openstack.compute import pause_server
from nova.api.openstack.compute import remote_consoles
from nova.api.openstack.compute import rescue
from nova.api.openstack.compute import security_groups
+from nova.api.openstack.compute import server_metadata
from nova.api.openstack.compute import server_usage
from nova.api.openstack.compute import servers
from nova.api.openstack.compute import shelve
@@ -72,14 +81,31 @@ def _create_controller(main_controller, controller_list,
return controller
+agents_controller = functools.partial(
+ _create_controller, agents.AgentController, [], [])
+
+
aggregates_controller = functools.partial(
_create_controller, aggregates.AggregateController, [], [])
+assisted_volume_snapshots_controller = functools.partial(
+ _create_controller,
+ assisted_volume_snapshots.AssistedVolumeSnapshotsController, [], [])
+
+
+availability_zone_controller = functools.partial(
+ _create_controller, availability_zone.AvailabilityZoneController, [], [])
+
+
keypairs_controller = functools.partial(
_create_controller, keypairs.KeypairController, [], [])
+fixed_ips_controller = functools.partial(_create_controller,
+ fixed_ips.FixedIPController, [], [])
+
+
flavor_controller = functools.partial(_create_controller,
flavors.FlavorsController,
[
@@ -101,6 +127,30 @@ flavor_extraspec_controller = functools.partial(_create_controller,
flavors_extraspecs.FlavorExtraSpecsController, [], [])
+floating_ip_dns_controller = functools.partial(_create_controller,
+ floating_ip_dns.FloatingIPDNSDomainController, [], [])
+
+
+floating_ip_dnsentry_controller = functools.partial(_create_controller,
+ floating_ip_dns.FloatingIPDNSEntryController, [], [])
+
+
+floating_ip_pools_controller = functools.partial(_create_controller,
+ floating_ip_pools.FloatingIPPoolsController, [], [])
+
+
+floating_ips_controller = functools.partial(_create_controller,
+ floating_ips.FloatingIPController, [], [])
+
+
+floating_ips_bulk_controller = functools.partial(_create_controller,
+ floating_ips_bulk.FloatingIPBulkController, [], [])
+
+
+instance_usage_audit_log_controller = functools.partial(_create_controller,
+ instance_usage_audit_log.InstanceUsageAuditLogController, [], [])
+
+
server_controller = functools.partial(_create_controller,
servers.ServersController,
[
@@ -135,6 +185,9 @@ server_controller = functools.partial(_create_controller,
)
+server_metadata_controller = functools.partial(_create_controller,
+ server_metadata.ServerMetadataController, [], [])
+
# NOTE(alex_xu): This is structure of this route list as below:
# (
# ('Route path': {
@@ -179,6 +232,14 @@ ROUTE_LIST = (
('/flavors/{flavor_id}/os-flavor-access', {
'GET': [flavor_access_controller, 'index']
}),
+ ('/os-agents', {
+ 'GET': [agents_controller, 'index'],
+ 'POST': [agents_controller, 'create']
+ }),
+ ('/os-agents/{id}', {
+ 'PUT': [agents_controller, 'update'],
+ 'DELETE': [agents_controller, 'delete']
+ }),
('/os-aggregates', {
'GET': [aggregates_controller, 'index'],
'POST': [aggregates_controller, 'create']
@@ -191,6 +252,61 @@ ROUTE_LIST = (
('/os-aggregates/{id}/action', {
'POST': [aggregates_controller, 'action'],
}),
+ ('/os-assisted-volume-snapshots', {
+ 'POST': [assisted_volume_snapshots_controller, 'create']
+ }),
+ ('/os-assisted-volume-snapshots/{id}', {
+ 'DELETE': [assisted_volume_snapshots_controller, 'delete']
+ }),
+ ('/os-availability-zone', {
+ 'GET': [availability_zone_controller, 'index']
+ }),
+ ('/os-availability-zone/detail', {
+ 'GET': [availability_zone_controller, 'detail'],
+ }),
+ ('/os-fixed-ips/{id}', {
+ 'GET': [fixed_ips_controller, 'show']
+ }),
+ ('/os-fixed-ips/{id}/action', {
+ 'POST': [fixed_ips_controller, 'action'],
+ }),
+ ('/os-floating-ip-dns', {
+ 'GET': [floating_ip_dns_controller, 'index']
+ }),
+ ('/os-floating-ip-dns/{id}', {
+ 'PUT': [floating_ip_dns_controller, 'update'],
+ 'DELETE': [floating_ip_dns_controller, 'delete']
+ }),
+ ('/os-floating-ip-dns/{domain_id}/entries/{id}', {
+ 'GET': [floating_ip_dnsentry_controller, 'show'],
+ 'PUT': [floating_ip_dnsentry_controller, 'update'],
+ 'DELETE': [floating_ip_dnsentry_controller, 'delete']
+ }),
+ ('/os-floating-ip-pools', {
+ 'GET': [floating_ip_pools_controller, 'index'],
+ }),
+ ('/os-floating-ips', {
+ 'GET': [floating_ips_controller, 'index'],
+ 'POST': [floating_ips_controller, 'create']
+ }),
+ ('/os-floating-ips/{id}', {
+ 'GET': [floating_ips_controller, 'show'],
+ 'DELETE': [floating_ips_controller, 'delete']
+ }),
+ ('/os-floating-ips-bulk', {
+ 'GET': [floating_ips_bulk_controller, 'index'],
+ 'POST': [floating_ips_bulk_controller, 'create']
+ }),
+ ('/os-floating-ips-bulk/{id}', {
+ 'GET': [floating_ips_bulk_controller, 'show'],
+ 'PUT': [floating_ips_bulk_controller, 'update']
+ }),
+ ('/os-instance_usage_audit_log', {
+ 'GET': [instance_usage_audit_log_controller, 'index']
+ }),
+ ('/os-instance_usage_audit_log/{id}', {
+ 'GET': [instance_usage_audit_log_controller, 'show']
+ }),
('/os-keypairs', {
'GET': [keypairs_controller, 'index'],
'POST': [keypairs_controller, 'create']
@@ -228,7 +344,17 @@ ROUTE_LIST = (
}),
('/servers/{id}/action', {
'POST': [server_controller, 'action']
- })
+ }),
+ ('/servers/{server_id}/metadata', {
+ 'GET': [server_metadata_controller, 'index'],
+ 'POST': [server_metadata_controller, 'create'],
+ 'PUT': [server_metadata_controller, 'update_all'],
+ }),
+ ('/servers/{server_id}/metadata/{id}', {
+ 'GET': [server_metadata_controller, 'show'],
+ 'PUT': [server_metadata_controller, 'update'],
+ 'DELETE': [server_metadata_controller, 'delete'],
+ }),
)
diff --git a/nova/api/openstack/compute/schemas/cloudpipe.py b/nova/api/openstack/compute/schemas/cloudpipe.py
deleted file mode 100644
index 43d72d0f40..0000000000
--- a/nova/api/openstack/compute/schemas/cloudpipe.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# Copyright 2014 IBM Corporation. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from nova.api.validation import parameter_types
-
-
-create = {
- 'type': 'object',
- 'properties': {
- 'cloudpipe': {
- 'type': 'object',
- 'properties': {
- 'project_id': parameter_types.project_id,
- },
- 'additionalProperties': False,
- },
- },
- 'required': ['cloudpipe'],
- 'additionalProperties': False,
-}
-
-update = {
- 'type': 'object',
- 'properties': {
- 'configure_project': {
- 'type': 'object',
- 'properties': {
- 'vpn_ip': parameter_types.ip_address,
- 'vpn_port': parameter_types.tcp_udp_port,
- },
- 'required': ['vpn_ip', 'vpn_port'],
- 'additionalProperties': False,
- },
- },
- 'required': ['configure_project'],
- 'additionalProperties': False,
-}
diff --git a/nova/api/openstack/compute/server_metadata.py b/nova/api/openstack/compute/server_metadata.py
index a88d43a883..543a34da09 100644
--- a/nova/api/openstack/compute/server_metadata.py
+++ b/nova/api/openstack/compute/server_metadata.py
@@ -26,8 +26,6 @@ from nova import exception
from nova.i18n import _
from nova.policies import server_metadata as sm_policies
-ALIAS = 'server-metadata'
-
class ServerMetadataController(wsgi.Controller):
"""The server metadata API controller for the OpenStack API."""
@@ -163,36 +161,3 @@ class ServerMetadataController(wsgi.Controller):
except exception.InstanceInvalidState as state_error:
common.raise_http_conflict_for_instance_invalid_state(state_error,
'delete metadata', server_id)
-
-
-class ServerMetadata(extensions.V21APIExtensionBase):
- """Server Metadata API."""
- name = "ServerMetadata"
- alias = ALIAS
- version = 1
-
- def get_resources(self):
- parent = {'member_name': 'server',
- 'collection_name': 'servers'}
- resources = [extensions.ResourceExtension('metadata',
- ServerMetadataController(),
- member_name='server_meta',
- parent=parent,
- custom_routes_fn=
- self.server_metadata_map
- )]
- return resources
-
- def get_controller_extensions(self):
- return []
-
- def server_metadata_map(self, mapper, wsgi_resource):
- mapper.connect("metadata",
- "/{project_id}/servers/{server_id}/metadata",
- controller=wsgi_resource,
- action='update_all', conditions={"method": ['PUT']})
- # Also connect the non project_id routes
- mapper.connect("metadata",
- "/servers/{server_id}/metadata",
- controller=wsgi_resource,
- action='update_all', conditions={"method": ['PUT']})
diff --git a/nova/api/openstack/compute/wsgi.py b/nova/api/openstack/compute/wsgi.py
index 154a00da3a..0ecb5e8d52 100644
--- a/nova/api/openstack/compute/wsgi.py
+++ b/nova/api/openstack/compute/wsgi.py
@@ -9,78 +9,12 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-"""WSGI script for Nova API, installed by pbr."""
+"""WSGI application entry-point for Nova Compute API, installed by pbr."""
-import os
+from nova.api.openstack import wsgi_app
-from oslo_config import cfg
-from oslo_log import log as logging
-from paste import deploy
-
-from nova import config
-from nova import context
-from nova import exception
-from nova import objects
-from nova import service
-from nova import utils
-
-CONF = cfg.CONF
-
-CONFIG_FILES = ['api-paste.ini', 'nova.conf']
NAME = "osapi_compute"
-utils.monkey_patch()
-objects.register_all()
-
-
-def _get_config_files(env=None):
- if env is None:
- env = os.environ
- dirname = env.get('OS_NOVA_CONFIG_DIR', '/etc/nova').strip()
- return [os.path.join(dirname, config_file)
- for config_file in CONFIG_FILES]
-
-
-def _setup_service(host, name):
- ctxt = context.get_admin_context()
- service_ref = objects.Service.get_by_host_and_binary(
- ctxt, host, name)
- if service_ref:
- service._update_service_ref(service_ref)
- else:
- try:
- service_obj = objects.Service(ctxt)
- service_obj.host = host
- service_obj.binary = 'nova-%s' % name
- service_obj.topic = None
- service_obj.report_count = 0
- service_obj.create()
- except (exception.ServiceTopicExists,
- exception.ServiceBinaryExists):
- # If we race to create a record with a sibling, don't
- # fail here.
- pass
-
-
-def error_application(exc):
- # TODO(cdent): make this something other than a stub
- def application(environ, start_response):
- start_response('500 Internal Server Error', [
- ('Content-Type', 'text/plain; charset=UTF-8')])
- return ['Out of date API service %s\n' % exc]
- return application
-
def init_application():
- conf_files = _get_config_files()
- config.parse_args([], default_config_files=conf_files)
-
- logging.setup(CONF, "nova")
- try:
- _setup_service(CONF.host, NAME)
- except exception.ServiceTooOld as exc:
- return error_application(exc)
-
- conf = conf_files[0]
-
- return deploy.loadapp('config:%s' % conf, name=NAME)
+ return wsgi_app.init_application(NAME)
diff --git a/nova/api/openstack/wsgi_app.py b/nova/api/openstack/wsgi_app.py
new file mode 100644
index 0000000000..8b6e07aedd
--- /dev/null
+++ b/nova/api/openstack/wsgi_app.py
@@ -0,0 +1,85 @@
+# 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.
+"""WSGI application initialization for Nova APIs."""
+
+import os
+
+from oslo_config import cfg
+from oslo_log import log as logging
+from paste import deploy
+
+from nova import config
+from nova import context
+from nova import exception
+from nova import objects
+from nova import service
+from nova import utils
+
+CONF = cfg.CONF
+
+CONFIG_FILES = ['api-paste.ini', 'nova.conf']
+
+utils.monkey_patch()
+objects.register_all()
+
+
+def _get_config_files(env=None):
+ if env is None:
+ env = os.environ
+ dirname = env.get('OS_NOVA_CONFIG_DIR', '/etc/nova').strip()
+ return [os.path.join(dirname, config_file)
+ for config_file in CONFIG_FILES]
+
+
+def _setup_service(host, name):
+ ctxt = context.get_admin_context()
+ service_ref = objects.Service.get_by_host_and_binary(
+ ctxt, host, name)
+ if service_ref:
+ service._update_service_ref(service_ref)
+ else:
+ try:
+ service_obj = objects.Service(ctxt)
+ service_obj.host = host
+ service_obj.binary = 'nova-%s' % name
+ service_obj.topic = None
+ service_obj.report_count = 0
+ service_obj.create()
+ except (exception.ServiceTopicExists,
+ exception.ServiceBinaryExists):
+ # If we race to create a record with a sibling, don't
+ # fail here.
+ pass
+
+
+def error_application(exc, name):
+ # TODO(cdent): make this something other than a stub
+ def application(environ, start_response):
+ start_response('500 Internal Server Error', [
+ ('Content-Type', 'text/plain; charset=UTF-8')])
+ return ['Out of date %s service %s\n' % (name, exc)]
+ return application
+
+
+def init_application(name):
+ conf_files = _get_config_files()
+ config.parse_args([], default_config_files=conf_files)
+
+ logging.setup(CONF, "nova")
+ try:
+ _setup_service(CONF.host, name)
+ except exception.ServiceTooOld as exc:
+ return error_application(exc, name)
+
+ conf = conf_files[0]
+
+ return deploy.loadapp('config:%s' % conf, name=name)
diff --git a/nova/cells/messaging.py b/nova/cells/messaging.py
index 552a06da2b..0e678d0213 100644
--- a/nova/cells/messaging.py
+++ b/nova/cells/messaging.py
@@ -1007,8 +1007,10 @@ class _BroadcastMessageMethods(_BaseMessageMethods):
instance.obj_reset_changes(items_to_remove)
instance.cell_name = _reverse_path(message.routing_path)
+ # instance.display_name could be unicode
+ instance_repr = utils.get_obj_repr_unicode(instance)
LOG.debug("Got update for instance: %(instance)s",
- {'instance': instance}, instance_uuid=instance.uuid)
+ {'instance': instance_repr}, instance_uuid=instance.uuid)
expected_vm_state = self._get_expected_vm_state(instance)
expected_task_state = self._get_expected_task_state(instance)
diff --git a/nova/cloudpipe/bootscript.template b/nova/cloudpipe/bootscript.template
deleted file mode 100755
index c6fbc08621..0000000000
--- a/nova/cloudpipe/bootscript.template
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/bin/bash
-# Copyright 2010 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# 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.
-
-# This gets zipped and run on the cloudpipe-managed OpenVPN server
-
-export LC_ALL=C
-export VPN_IP=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f2 | awk '{print $$1}'`
-export BROADCAST=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f3 | awk '{print $$1}'`
-export DHCP_MASK=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f4 | awk '{print $$1}'`
-export GATEWAY=`netstat -r | grep default | cut -d' ' -f10`
-# Need a higher valued MAC address than eth0, to prevent the TAP MAC address
-# from becoming the bridge MAC address. Since Essex eth0 MAC starts with
-# FA:16:3E, we'll thus generate a MAC starting with FA:17:3E to be higher than eth0.
-export RANDOM_TAP_MAC=`openssl rand -hex 8 | sed 's/\(..\)/\1:/g' | cut -b-8 | awk '{print "FA:17:3E:"$$1}'`
-
-DHCP_LOWER=`echo $$BROADCAST | awk -F. '{print $$1"."$$2"."$$3"." $$4 - ${num_vpn} }'`
-DHCP_UPPER=`echo $$BROADCAST | awk -F. '{print $$1"."$$2"."$$3"." $$4 - 1 }'`
-
-# generate a server DH
-openssl dhparam -out /etc/openvpn/dh1024.pem 1024
-
-cp crl.pem /etc/openvpn/
-cp server.key /etc/openvpn/
-cp ca.crt /etc/openvpn/
-cp server.crt /etc/openvpn/
-# Customize the server.conf.template
-cd /etc/openvpn
-
-sed -e s/VPN_IP/$$VPN_IP/g server.conf.template > server.conf
-sed -i -e s/DHCP_SUBNET/$$DHCP_MASK/g server.conf
-sed -i -e s/DHCP_LOWER/$$DHCP_LOWER/g server.conf
-sed -i -e s/DHCP_UPPER/$$DHCP_UPPER/g server.conf
-sed -i -e s/max-clients\ 1/max-clients\ 10/g server.conf
-
-echo "push \"route ${dmz_net} ${dmz_mask} $$GATEWAY\"" >> server.conf
-echo "duplicate-cn" >> server.conf
-echo "crl-verify /etc/openvpn/crl.pem" >> server.conf
-echo "lladdr $$RANDOM_TAP_MAC" >> server.conf
-
-/etc/init.d/openvpn start
diff --git a/nova/cloudpipe/client.ovpn.template b/nova/cloudpipe/client.ovpn.template
deleted file mode 100644
index dd48b556f1..0000000000
--- a/nova/cloudpipe/client.ovpn.template
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2010 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# 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.
-
-# NOVA user connection
-# Edit the following lines to point to your cert files:
-cert {{ certfile }}
-key {{ keyfile }}
-
-ca cacert.pem
-
-client
-dev tap
-proto udp
-
-remote {{ ip }} {{ port }}
-resolv-retry infinite
-nobind
-
-# Downgrade privileges after initialization (non-Windows only)
-user nobody
-group nogroup
-comp-lzo
-
-# Set log file verbosity.
-verb 2
-
-keepalive 10 120
-ping-timer-rem
-persist-tun
-persist-key
diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py
deleted file mode 100644
index 8cbb1c3290..0000000000
--- a/nova/cloudpipe/pipelib.py
+++ /dev/null
@@ -1,150 +0,0 @@
-# Copyright 2010 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# 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.
-
-"""
-CloudPipe - Build a user-data payload zip file, and launch
-an instance with it.
-
-"""
-
-import base64
-import os
-import string
-import zipfile
-
-from oslo_log import log as logging
-from oslo_utils import fileutils
-
-from nova import compute
-from nova.compute import flavors
-import nova.conf
-from nova import crypto
-from nova import db
-from nova import exception
-from nova import utils
-
-
-CONF = nova.conf.CONF
-
-LOG = logging.getLogger(__name__)
-
-
-def is_vpn_image(image_id):
- return image_id == CONF.cloudpipe.vpn_image_id
-
-
-def _load_boot_script():
- with open(CONF.cloudpipe.boot_script_template, "r") as shellfile:
- s = string.Template(shellfile.read())
- return s.substitute(dmz_net=CONF.cloudpipe.dmz_net,
- dmz_mask=CONF.cloudpipe.dmz_mask,
- num_vpn=CONF.cnt_vpn_clients)
-
-
-class CloudPipe(object):
- def __init__(self):
- self.compute_api = compute.API()
-
- def get_encoded_zip(self, project_id):
- # Make a payload.zip
- with utils.tempdir() as tmpdir:
- filename = "payload.zip"
- zippath = os.path.join(tmpdir, filename)
- z = zipfile.ZipFile(zippath, "w", zipfile.ZIP_DEFLATED)
- boot_script = _load_boot_script()
- # genvpn, sign csr
- crypto.generate_vpn_files(project_id)
- z.writestr('autorun.sh', boot_script)
- crl = os.path.join(crypto.ca_folder(project_id), 'crl.pem')
- z.write(crl, 'crl.pem')
- server_key = os.path.join(crypto.ca_folder(project_id),
- 'server.key')
- z.write(server_key, 'server.key')
- ca_crt = os.path.join(crypto.ca_path(project_id))
- z.write(ca_crt, 'ca.crt')
- server_crt = os.path.join(crypto.ca_folder(project_id),
- 'server.crt')
- z.write(server_crt, 'server.crt')
- z.close()
- with open(zippath, "rb") as zippy:
- # NOTE(vish): run instances expects encoded userdata,
- # it is decoded in the get_metadata_call.
- # autorun.sh also decodes the zip file,
- # hence the double encoding.
- encoded = base64.b64encode(zippy.read())
- encoded = base64.b64encode(encoded)
-
- return encoded
-
- def launch_vpn_instance(self, context):
- LOG.debug("Launching VPN for %s", context.project_id)
- key_name = self.setup_key_pair(context)
- group_name = self.setup_security_group(context)
- flavor = flavors.get_flavor_by_name(CONF.cloudpipe.vpn_flavor)
- instance_name = '%s%s' % (context.project_id,
- CONF.cloudpipe.vpn_key_suffix)
- user_data = self.get_encoded_zip(context.project_id)
- return self.compute_api.create(context,
- flavor,
- CONF.cloudpipe.vpn_image_id,
- display_name=instance_name,
- user_data=user_data,
- key_name=key_name,
- security_groups=[group_name])
-
- def setup_security_group(self, context):
- group_name = '%s%s' % (context.project_id,
- CONF.cloudpipe.vpn_key_suffix)
- group = {'user_id': context.user_id,
- 'project_id': context.project_id,
- 'name': group_name,
- 'description': 'Group for vpn'}
- try:
- group_ref = db.security_group_create(context, group)
- except exception.SecurityGroupExists:
- return group_name
- rule = {'parent_group_id': group_ref['id'],
- 'cidr': '0.0.0.0/0',
- 'protocol': 'udp',
- 'from_port': 1194,
- 'to_port': 1194}
- db.security_group_rule_create(context, rule)
- rule = {'parent_group_id': group_ref['id'],
- 'cidr': '0.0.0.0/0',
- 'protocol': 'icmp',
- 'from_port': -1,
- 'to_port': -1}
- db.security_group_rule_create(context, rule)
- # NOTE(vish): No need to trigger the group since the instance
- # has not been run yet.
- return group_name
-
- def setup_key_pair(self, context):
- key_name = '%s%s' % (context.project_id,
- CONF.cloudpipe.vpn_key_suffix)
- try:
- keypair_api = compute.api.KeypairAPI()
- result, private_key = keypair_api.create_key_pair(context,
- context.user_id,
- key_name)
- key_dir = os.path.join(CONF.crypto.keys_path, context.user_id)
- fileutils.ensure_tree(key_dir)
- key_path = os.path.join(key_dir, '%s.pem' % key_name)
- with open(key_path, 'w') as f:
- f.write(private_key)
- except (exception.KeyPairExists, os.error, IOError):
- pass
- return key_name
diff --git a/nova/cmd/manage.py b/nova/cmd/manage.py
index a3aad09054..06119cd8d7 100644
--- a/nova/cmd/manage.py
+++ b/nova/cmd/manage.py
@@ -620,6 +620,8 @@ class DbCommands(object):
# NOTE(mriedem): This online migration is going to be backported to
# Newton also since it's an upgrade issue when upgrading from Mitaka.
build_request_obj.delete_build_requests_with_no_instance_uuid,
+ # Added in Pike
+ db.service_uuids_online_data_migration,
)
def __init__(self):
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 105c7dc394..b700672ba5 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -2110,7 +2110,8 @@ class API(base.Base):
else:
flavor = flavor or instance.flavor
instance_vcpus = flavor.vcpus
- instance_memory_mb = flavor.memory_mb
+ vram_mb = int(flavor.get('extra_specs', {}).get(VIDEO_RAM, 0))
+ instance_memory_mb = flavor.memory_mb + vram_mb
quotas = objects.Quotas(context=context)
quotas.reserve(project_id=project_id,
@@ -2154,21 +2155,26 @@ class API(base.Base):
for bdm in bdms:
if bdm.is_volume:
try:
- connector = self._get_stashed_volume_connector(
- bdm, instance)
- if connector:
- self.volume_api.terminate_connection(context,
- bdm.volume_id,
- connector)
+ if bdm.attachment_id:
+ self.volume_api.attachment_delete(context,
+ bdm.attachment_id)
else:
- LOG.debug('Unable to find connector for volume %s, '
- 'not attempting terminate_connection.',
- bdm.volume_id, instance=instance)
- # Attempt to detach the volume. If there was no connection
- # made in the first place this is just cleaning up the
- # volume state in the Cinder database.
- self.volume_api.detach(elevated, bdm.volume_id,
- instance.uuid)
+ connector = self._get_stashed_volume_connector(
+ bdm, instance)
+ if connector:
+ self.volume_api.terminate_connection(context,
+ bdm.volume_id,
+ connector)
+ else:
+ LOG.debug('Unable to find connector for volume %s,'
+ ' not attempting terminate_connection.',
+ bdm.volume_id, instance=instance)
+ # Attempt to detach the volume. If there was no
+ # connection made in the first place this is just
+ # cleaning up the volume state in the Cinder DB.
+ self.volume_api.detach(elevated, bdm.volume_id,
+ instance.uuid)
+
if bdm.delete_on_termination:
self.volume_api.delete(context, bdm.volume_id)
except Exception as exc:
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 9479a4158b..2c027c1226 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -55,7 +55,6 @@ from six.moves import range
from nova import block_device
from nova.cells import rpcapi as cells_rpcapi
-from nova.cloudpipe import pipelib
from nova import compute
from nova.compute import build_results
from nova.compute import claims
@@ -1460,7 +1459,7 @@ class ComputeManager(manager.Manager):
instance.save(expected_task_state=[None])
self._update_resource_tracker(context, instance)
- is_vpn = pipelib.is_vpn_image(instance.image_ref)
+ is_vpn = False
return network_model.NetworkInfoAsyncWrapper(
self._allocate_network_async, context, instance,
requested_networks, macs, security_groups, is_vpn,
@@ -1583,11 +1582,11 @@ class ComputeManager(manager.Manager):
self._block_device_info_to_legacy(block_device_info)
return block_device_info
- except exception.OverQuota:
- msg = _LW('Failed to create block device for instance due to '
- 'being over volume resource quota')
- LOG.warning(msg, instance=instance)
- raise exception.VolumeLimitExceeded()
+ except exception.OverQuota as e:
+ LOG.warning(_LW('Failed to create block device for instance due'
+ ' to exceeding volume related resource quota.'
+ ' Error: %s'), e.message, instance=instance)
+ raise
except Exception:
LOG.exception(_LE('Instance failed block device setup'),
@@ -2101,8 +2100,7 @@ class ComputeManager(manager.Manager):
if network_info is not None:
network_info.wait(do_raise=False)
except (exception.UnexpectedTaskStateError,
- exception.VolumeLimitExceeded,
- exception.InvalidBDM) as e:
+ exception.OverQuota, exception.InvalidBDM) as e:
# Make sure the async call finishes
if network_info is not None:
network_info.wait(do_raise=False)
@@ -4811,10 +4809,15 @@ class ComputeManager(manager.Manager):
{'volume_id': bdm.volume_id,
'mountpoint': bdm['mount_device']},
instance=instance)
+ compute_utils.notify_about_volume_attach_detach(
+ context, instance, self.host,
+ action=fields.NotificationAction.VOLUME_ATTACH,
+ phase=fields.NotificationPhase.START,
+ volume_id=bdm.volume_id)
try:
bdm.attach(context, instance, self.volume_api, self.driver,
do_driver_attach=True)
- except Exception:
+ except Exception as e:
with excutils.save_and_reraise_exception():
LOG.exception(_LE("Failed to attach %(volume_id)s "
"at %(mountpoint)s"),
@@ -4822,10 +4825,21 @@ class ComputeManager(manager.Manager):
'mountpoint': bdm['mount_device']},
instance=instance)
self.volume_api.unreserve_volume(context, bdm.volume_id)
+ compute_utils.notify_about_volume_attach_detach(
+ context, instance, self.host,
+ action=fields.NotificationAction.VOLUME_ATTACH,
+ phase=fields.NotificationPhase.ERROR,
+ exception=e,
+ volume_id=bdm.volume_id)
info = {'volume_id': bdm.volume_id}
self._notify_about_instance_usage(
context, instance, "volume.attach", extra_usage_info=info)
+ compute_utils.notify_about_volume_attach_detach(
+ context, instance, self.host,
+ action=fields.NotificationAction.VOLUME_ATTACH,
+ phase=fields.NotificationPhase.END,
+ volume_id=bdm.volume_id)
def _notify_volume_usage_detach(self, context, instance, bdm):
if CONF.volume_usage_poll_interval <= 0:
@@ -4869,6 +4883,11 @@ class ComputeManager(manager.Manager):
like rebuild, when we don't want to destroy BDM
"""
+ compute_utils.notify_about_volume_attach_detach(
+ context, instance, self.host,
+ action=fields.NotificationAction.VOLUME_DETACH,
+ phase=fields.NotificationPhase.START,
+ volume_id=volume_id)
bdm = objects.BlockDeviceMapping.get_by_volume_and_instance(
context, volume_id, instance.uuid)
@@ -4884,6 +4903,11 @@ class ComputeManager(manager.Manager):
info = dict(volume_id=volume_id)
self._notify_about_instance_usage(
context, instance, "volume.detach", extra_usage_info=info)
+ compute_utils.notify_about_volume_attach_detach(
+ context, instance, self.host,
+ action=fields.NotificationAction.VOLUME_DETACH,
+ phase=fields.NotificationPhase.END,
+ volume_id=volume_id)
if destroy_bdm:
bdm.destroy()
@@ -5543,10 +5567,12 @@ class ComputeManager(manager.Manager):
# Define domain at destination host, without doing it,
# pause/suspend/terminate do not work.
+ post_at_dest_success = True
try:
self.compute_rpcapi.post_live_migration_at_destination(ctxt,
instance, block_migration, dest)
except Exception as error:
+ post_at_dest_success = False
# We don't want to break _post_live_migration() if
# post_live_migration_at_destination() fails as it should never
# affect cleaning up source node.
@@ -5574,8 +5600,9 @@ class ComputeManager(manager.Manager):
self._notify_about_instance_usage(ctxt, instance,
"live_migration._post.end",
network_info=network_info)
- LOG.info(_LI('Migrating instance to %s finished successfully.'),
- dest, instance=instance)
+ if post_at_dest_success:
+ LOG.info('Migrating instance to %s finished successfully.',
+ dest, instance=instance)
if migrate_data and migrate_data.obj_attr_is_set('migration'):
migrate_data.migration.status = 'completed'
diff --git a/nova/compute/resource_tracker.py b/nova/compute/resource_tracker.py
index a753488a46..29de8c61d9 100644
--- a/nova/compute/resource_tracker.py
+++ b/nova/compute/resource_tracker.py
@@ -95,6 +95,7 @@ class ResourceTracker(object):
self.monitors = monitor_handler.monitors
self.old_resources = collections.defaultdict(objects.ComputeNode)
self.scheduler_client = scheduler_client.SchedulerClient()
+ self.reportclient = self.scheduler_client.reportclient
self.ram_allocation_ratio = CONF.ram_allocation_ratio
self.cpu_allocation_ratio = CONF.cpu_allocation_ratio
self.disk_allocation_ratio = CONF.disk_allocation_ratio
@@ -957,8 +958,7 @@ class ResourceTracker(object):
self.pci_tracker.update_pci_for_instance(context,
instance,
sign=sign)
- self.scheduler_client.reportclient.update_instance_allocation(
- cn, instance, sign)
+ self.reportclient.update_instance_allocation(cn, instance, sign)
# new instance, update compute node resource usage:
self._update_usage(self._get_usage_dict(instance), nodename,
sign=sign)
@@ -992,8 +992,8 @@ class ResourceTracker(object):
if instance.vm_state not in vm_states.ALLOW_RESOURCE_REMOVAL:
self._update_usage_from_instance(context, instance, nodename)
- self.scheduler_client.reportclient.remove_deleted_instances(
- cn, self.tracked_instances.values())
+ self.reportclient.remove_deleted_instances(cn,
+ self.tracked_instances.values())
cn.free_ram_mb = max(0, cn.free_ram_mb)
cn.free_disk_gb = max(0, cn.free_disk_gb)
diff --git a/nova/compute/utils.py b/nova/compute/utils.py
index ca3340d62d..823949be9e 100644
--- a/nova/compute/utils.py
+++ b/nova/compute/utils.py
@@ -366,6 +366,36 @@ def notify_about_instance_action(context, instance, host, action, phase=None,
notification.emit(context)
+def notify_about_volume_attach_detach(context, instance, host, action, phase,
+ binary='nova-compute', volume_id=None,
+ exception=None):
+ """Send versioned notification about the action made on the instance
+ :param instance: the instance which the action performed on
+ :param host: the host emitting the notification
+ :param action: the name of the action
+ :param phase: the phase of the action
+ :param binary: the binary emitting the notification
+ :param volume_id: id of the volume will be attached
+ :param exception: the thrown exception (used in error notifications)
+ """
+ fault, priority = _get_fault_and_priority_from_exc(exception)
+ payload = instance_notification.InstanceActionVolumePayload(
+ instance=instance,
+ fault=fault,
+ volume_id=volume_id)
+ notification = instance_notification.InstanceActionVolumeNotification(
+ context=context,
+ priority=priority,
+ publisher=notification_base.NotificationPublisher(
+ context=context, host=host, binary=binary),
+ event_type=notification_base.EventType(
+ object='instance',
+ action=action,
+ phase=phase),
+ payload=payload)
+ notification.emit(context)
+
+
def notify_about_volume_swap(context, instance, host, action, phase,
old_volume_id, new_volume_id, exception=None):
"""Send versioned notification about the volume swap action
diff --git a/nova/conf/__init__.py b/nova/conf/__init__.py
index 680fe11788..022927db80 100644
--- a/nova/conf/__init__.py
+++ b/nova/conf/__init__.py
@@ -25,7 +25,6 @@ from nova.conf import base
from nova.conf import cache
from nova.conf import cells
from nova.conf import cinder
-from nova.conf import cloudpipe
from nova.conf import compute
from nova.conf import conductor
from nova.conf import configdrive
@@ -35,7 +34,6 @@ from nova.conf import crypto
from nova.conf import database
from nova.conf import ephemeral_storage
from nova.conf import flavors
-from nova.conf import floating_ips
from nova.conf import glance
from nova.conf import guestfs
from nova.conf import hyperv
@@ -80,7 +78,6 @@ base.register_opts(CONF)
cache.register_opts(CONF)
cells.register_opts(CONF)
cinder.register_opts(CONF)
-cloudpipe.register_opts(CONF)
compute.register_opts(CONF)
conductor.register_opts(CONF)
configdrive.register_opts(CONF)
@@ -89,7 +86,6 @@ consoleauth.register_opts(CONF)
crypto.register_opts(CONF)
database.register_opts(CONF)
ephemeral_storage.register_opts(CONF)
-floating_ips.register_opts(CONF)
flavors.register_opts(CONF)
glance.register_opts(CONF)
guestfs.register_opts(CONF)
diff --git a/nova/conf/cloudpipe.py b/nova/conf/cloudpipe.py
deleted file mode 100644
index c58da86186..0000000000
--- a/nova/conf/cloudpipe.py
+++ /dev/null
@@ -1,121 +0,0 @@
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from oslo_config import cfg
-
-from nova.conf import paths
-
-cloudpipe_group = cfg.OptGroup(
- name='cloudpipe',
- title='Cloudpipe options')
-
-cloudpipe_opts = [
- cfg.StrOpt('vpn_image_id',
- default='0',
- deprecated_group='DEFAULT',
- help="""
-Image ID used when starting up a cloudpipe VPN client.
-
-An empty instance is created and configured with OpenVPN using
-boot_script_template. This instance would be snapshotted and stored
-in glance. ID of the stored image is used in 'vpn_image_id' to
-create cloudpipe VPN client.
-
-Possible values:
-
-* Any valid ID of a VPN image
-"""),
- cfg.StrOpt('vpn_flavor',
- default='m1.tiny',
- deprecated_group='DEFAULT',
- help="""
-Flavor for VPN instances.
-
-Possible values:
-
-* Any valid flavor name
-"""),
- cfg.StrOpt('boot_script_template',
- default=paths.basedir_def('nova/cloudpipe/bootscript.template'),
- deprecated_group='DEFAULT',
- help="""
-Template for cloudpipe instance boot script.
-
-Possible values:
-
-* Any valid path to a cloudpipe instance boot script template
-
-Related options:
-
-The following options are required to configure cloudpipe-managed
-OpenVPN server.
-
-* dmz_net
-* dmz_mask
-* cnt_vpn_clients
-"""),
- cfg.IPOpt('dmz_net',
- default='10.0.0.0',
- deprecated_group='DEFAULT',
- help="""
-Network to push into OpenVPN config.
-
-Note: Above mentioned OpenVPN config can be found at
-/etc/openvpn/server.conf.
-
-Possible values:
-
-* Any valid IPv4/IPV6 address
-
-Related options:
-
-* boot_script_template - dmz_net is pushed into bootscript.template
- to configure cloudpipe-managed OpenVPN server
-"""),
- cfg.IPOpt('dmz_mask',
- default='255.255.255.0',
- deprecated_group='DEFAULT',
- help="""
-Netmask to push into OpenVPN config.
-
-Possible values:
-
-* Any valid IPv4/IPV6 netmask
-
-Related options:
-
-* dmz_net - dmz_net and dmz_mask is pushed into bootscript.template
- to configure cloudpipe-managed OpenVPN server
-* boot_script_template
-"""),
- cfg.StrOpt('vpn_key_suffix',
- default='-vpn',
- deprecated_group='DEFAULT',
- help="""
-Suffix to add to project name for VPN key and secgroups
-
-Possible values:
-
-* Any string value representing the VPN key suffix
-""")
-]
-
-
-def register_opts(conf):
- conf.register_group(cloudpipe_group)
- conf.register_opts(cloudpipe_opts, group=cloudpipe_group)
-
-
-def list_opts():
- return {cloudpipe_group: cloudpipe_opts}
diff --git a/nova/conf/floating_ips.py b/nova/conf/floating_ips.py
deleted file mode 100644
index 7e77f7d5b9..0000000000
--- a/nova/conf/floating_ips.py
+++ /dev/null
@@ -1,128 +0,0 @@
-# Copyright 2016 Huawei Technology corp.
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from oslo_config import cfg
-
-
-floating_ip_opts = [
- cfg.StrOpt('default_floating_pool',
- default='nova',
- help="""
-Default pool for floating IPs.
-
-This option specifies the default floating IP pool for allocating floating IPs.
-
-While allocating a floating ip, users can optionally pass in the name of the
-pool they want to allocate from, otherwise it will be pulled from the
-default pool.
-
-If this option is not set, then 'nova' is used as default floating pool.
-
-Possible values:
-
-* Any string representing a floating IP pool name
-"""),
- cfg.BoolOpt('auto_assign_floating_ip',
- default=False,
- deprecated_for_removal=True,
- deprecated_since='15.0.0',
- deprecated_reason="""
-nova-network is deprecated, as are any related configuration options.
-""",
- help="""
-Autoassigning floating IP to VM
-
-When set to True, floating IP is auto allocated and associated
-to the VM upon creation.
-
-Related options:
-
-* use_neutron: this options only works with nova-network.
-"""),
- cfg.StrOpt('floating_ip_dns_manager',
- default='nova.network.noop_dns_driver.NoopDNSDriver',
- deprecated_for_removal=True,
- deprecated_since='15.0.0',
- deprecated_reason="""
-nova-network is deprecated, as are any related configuration options.
-""",
- help="""
-Full class name for the DNS Manager for floating IPs.
-
-This option specifies the class of the driver that provides functionality
-to manage DNS entries associated with floating IPs.
-
-When a user adds a DNS entry for a specified domain to a floating IP,
-nova will add a DNS entry using the specified floating DNS driver.
-When a floating IP is deallocated, its DNS entry will automatically be deleted.
-
-Possible values:
-
-* Full Python path to the class to be used
-
-Related options:
-
-* use_neutron: this options only works with nova-network.
-"""),
- cfg.StrOpt('instance_dns_manager',
- default='nova.network.noop_dns_driver.NoopDNSDriver',
- deprecated_for_removal=True,
- deprecated_since='15.0.0',
- deprecated_reason="""
-nova-network is deprecated, as are any related configuration options.
-""",
- help="""
-Full class name for the DNS Manager for instance IPs.
-
-This option specifies the class of the driver that provides functionality
-to manage DNS entries for instances.
-
-On instance creation, nova will add DNS entries for the instance name and
-id, using the specified instance DNS driver and domain. On instance deletion,
-nova will remove the DNS entries.
-
-Possible values:
-
-* Full Python path to the class to be used
-
-Related options:
-
-* use_neutron: this options only works with nova-network.
-"""),
- cfg.StrOpt('instance_dns_domain',
- default='',
- deprecated_for_removal=True,
- deprecated_since='15.0.0',
- deprecated_reason="""
-nova-network is deprecated, as are any related configuration options.
-""",
- help="""
-If specified, Nova checks if the availability_zone of every instance matches
-what the database says the availability_zone should be for the specified
-dns_domain.
-
-Related options:
-
-* use_neutron: this options only works with nova-network.
-""")
-]
-
-
-def register_opts(conf):
- conf.register_opts(floating_ip_opts)
-
-
-def list_opts():
- return {'DEFAULT': floating_ip_opts}
diff --git a/nova/conf/network.py b/nova/conf/network.py
index 9fe244112b..0ff401a243 100644
--- a/nova/conf/network.py
+++ b/nova/conf/network.py
@@ -195,8 +195,9 @@ Related options:
nova-network is deprecated, as are any related configuration options.
""",
help="""
-This is the public IP address for the cloudpipe VPN servers. It defaults to the
-IP address of the host.
+This option is no longer used since the /os-cloudpipe API was removed in the
+16.0.0 Pike release. This is the public IP address for the cloudpipe VPN
+servers. It defaults to the IP address of the host.
Please note that this option is only used when using nova-network instead of
Neutron in your deployment. It also will be ignored if the configuration option
@@ -1220,8 +1221,120 @@ Related options:
""")
]
+floating_ip_opts = [
+ cfg.StrOpt('default_floating_pool',
+ default='nova',
+ deprecated_for_removal=True,
+ deprecated_since='16.0.0',
+ deprecated_reason="""
+This option was used for two purposes: to set the floating IP pool name for
+nova-network and to do the same for neutron. nova-network is deprecated, as are
+any related configuration options. Users of neutron, meanwhile, should use the
+'default_floating_pool' option in the '[neutron]' group.
+""",
+ help="""
+Default pool for floating IPs.
+
+This option specifies the default floating IP pool for allocating floating IPs.
+
+While allocating a floating ip, users can optionally pass in the name of the
+pool they want to allocate from, otherwise it will be pulled from the
+default pool.
+
+If this option is not set, then 'nova' is used as default floating pool.
+
+Possible values:
+
+* Any string representing a floating IP pool name
+"""),
+ cfg.BoolOpt('auto_assign_floating_ip',
+ default=False,
+ deprecated_for_removal=True,
+ deprecated_since='15.0.0',
+ deprecated_reason="""
+nova-network is deprecated, as are any related configuration options.
+""",
+ help="""
+Autoassigning floating IP to VM
+
+When set to True, floating IP is auto allocated and associated
+to the VM upon creation.
+
+Related options:
+
+* use_neutron: this options only works with nova-network.
+"""),
+ cfg.StrOpt('floating_ip_dns_manager',
+ default='nova.network.noop_dns_driver.NoopDNSDriver',
+ deprecated_for_removal=True,
+ deprecated_since='15.0.0',
+ deprecated_reason="""
+nova-network is deprecated, as are any related configuration options.
+""",
+ help="""
+Full class name for the DNS Manager for floating IPs.
+
+This option specifies the class of the driver that provides functionality
+to manage DNS entries associated with floating IPs.
+
+When a user adds a DNS entry for a specified domain to a floating IP,
+nova will add a DNS entry using the specified floating DNS driver.
+When a floating IP is deallocated, its DNS entry will automatically be deleted.
+
+Possible values:
+
+* Full Python path to the class to be used
+
+Related options:
+
+* use_neutron: this options only works with nova-network.
+"""),
+ cfg.StrOpt('instance_dns_manager',
+ default='nova.network.noop_dns_driver.NoopDNSDriver',
+ deprecated_for_removal=True,
+ deprecated_since='15.0.0',
+ deprecated_reason="""
+nova-network is deprecated, as are any related configuration options.
+""",
+ help="""
+Full class name for the DNS Manager for instance IPs.
+
+This option specifies the class of the driver that provides functionality
+to manage DNS entries for instances.
+
+On instance creation, nova will add DNS entries for the instance name and
+id, using the specified instance DNS driver and domain. On instance deletion,
+nova will remove the DNS entries.
+
+Possible values:
+
+* Full Python path to the class to be used
+
+Related options:
+
+* use_neutron: this options only works with nova-network.
+"""),
+ cfg.StrOpt('instance_dns_domain',
+ default='',
+ deprecated_for_removal=True,
+ deprecated_since='15.0.0',
+ deprecated_reason="""
+nova-network is deprecated, as are any related configuration options.
+""",
+ help="""
+If specified, Nova checks if the availability_zone of every instance matches
+what the database says the availability_zone should be for the specified
+dns_domain.
+
+Related options:
+
+* use_neutron: this options only works with nova-network.
+""")
+]
+
+
ALL_DEFAULT_OPTS = (linux_net_opts + network_opts + ldap_dns_opts
- + rpcapi_opts + driver_opts)
+ + rpcapi_opts + driver_opts + floating_ip_opts)
def register_opts(conf):
diff --git a/nova/conf/neutron.py b/nova/conf/neutron.py
index a0359c6a36..46a73ca74f 100644
--- a/nova/conf/neutron.py
+++ b/nova/conf/neutron.py
@@ -52,12 +52,20 @@ region the request is coming from.
cfg.StrOpt('ovs_bridge',
default='br-int',
help="""
-Specifies the name of an integration bridge interface used by OpenvSwitch.
-This option is used only if Neutron does not specify the OVS bridge name.
+Default name for the Open vSwitch integration bridge.
-Possible values:
+Specifies the name of an integration bridge interface used by OpenvSwitch.
+This option is only used if Neutron does not specify the OVS bridge name in
+port binding responses.
+"""),
+ cfg.StrOpt('default_floating_pool',
+ default='nova',
+ help="""
+Default name for the floating IP pool.
-* Any string representing OVS bridge name.
+Specifies the name of floating IP pool used for allocating floating IPs. This
+option is only used if Neutron does not specify the floating IP pool name in
+port binding reponses.
"""),
cfg.IntOpt('extension_sync_interval',
default=600,
diff --git a/nova/db/api.py b/nova/db/api.py
index 377178404b..e0e01321f7 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -112,6 +112,13 @@ def service_get(context, service_id):
return IMPL.service_get(context, service_id)
+def service_get_by_uuid(context, service_uuid):
+ """Get a service by it's uuid or raise ServiceNotFound if it does not
+ exist.
+ """
+ return IMPL.service_get_by_uuid(context, service_uuid)
+
+
def service_get_minimum_version(context, binary):
"""Get the minimum service version in the database."""
return IMPL.service_get_minimum_version(context, binary)
@@ -2057,6 +2064,10 @@ def pcidevice_online_data_migration(context, max_count):
return IMPL.pcidevice_online_data_migration(context, max_count)
+def service_uuids_online_data_migration(context, max_count):
+ return IMPL.service_uuids_online_data_migration(context, max_count)
+
+
####################
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 4980917378..f3bb21cd02 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -475,6 +475,17 @@ def service_get(context, service_id):
return result
+@pick_context_manager_reader
+def service_get_by_uuid(context, service_uuid):
+ query = model_query(context, models.Service).filter_by(uuid=service_uuid)
+
+ result = query.first()
+ if not result:
+ raise exception.ServiceNotFound(service_id=service_uuid)
+
+ return result
+
+
@pick_context_manager_reader_allow_async
def service_get_minimum_version(context, binaries):
min_versions = context.session.query(
@@ -6524,6 +6535,24 @@ def archive_deleted_rows(max_rows=None):
return table_to_rows_archived
+@pick_context_manager_writer
+def service_uuids_online_data_migration(context, max_count):
+ from nova.objects import service
+
+ count_all = 0
+ count_hit = 0
+
+ db_services = model_query(context, models.Service).filter_by(
+ uuid=None).limit(max_count)
+ for db_service in db_services:
+ count_all += 1
+ service_obj = service.Service._from_db_object(
+ context, service.Service(), db_service)
+ if 'uuid' in service_obj:
+ count_hit += 1
+ return count_all, count_hit
+
+
####################
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/361_add_compute_nodes_uuid_index.py b/nova/db/sqlalchemy/migrate_repo/versions/361_add_compute_nodes_uuid_index.py
new file mode 100644
index 0000000000..be38d9f9a6
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/361_add_compute_nodes_uuid_index.py
@@ -0,0 +1,41 @@
+# Copyright 2017 Huawei Technologies Co.,LTD.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from oslo_log import log as logging
+from sqlalchemy import MetaData, Table, Index
+
+LOG = logging.getLogger(__name__)
+
+
+def _get_table_index(migrate_engine):
+ meta = MetaData()
+ meta.bind = migrate_engine
+ table = Table('compute_nodes', meta, autoload=True)
+ for idx in table.indexes:
+ if idx.columns.keys() == ['uuid']:
+ break
+ else:
+ idx = None
+ return table, idx
+
+
+def upgrade(migrate_engine):
+ table, index = _get_table_index(migrate_engine)
+ if index:
+ LOG.info('Skipped adding compute_nodes_uuid_idx because an '
+ 'equivalent index already exists.')
+ return
+ index = Index('compute_nodes_uuid_idx', table.c.uuid, unique=True)
+ index.create(migrate_engine)
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 766aa0bad5..9c13618d53 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -112,6 +112,7 @@ class ComputeNode(BASE, NovaBase, models.SoftDeleteMixin):
__tablename__ = 'compute_nodes'
__table_args__ = (
+ Index('compute_nodes_uuid_idx', 'uuid', unique=True),
schema.UniqueConstraint(
'host', 'hypervisor_hostname', 'deleted',
name="uniq_compute_nodes0host0hypervisor_hostname0deleted"),
diff --git a/nova/db/sqlalchemy/types.py b/nova/db/sqlalchemy/types.py
index b216abbae7..f274d3932e 100644
--- a/nova/db/sqlalchemy/types.py
+++ b/nova/db/sqlalchemy/types.py
@@ -59,7 +59,7 @@ class CIDR(types.TypeDecorator):
def process_bind_param(self, value, dialect):
"""Process/Formats the value before insert it into the db."""
# NOTE(sdague): normalize all the inserts
- if utils.is_valid_ipv6_cidr(value):
+ if netutils.is_valid_ipv6_cidr(value):
return utils.get_shortened_ipv6_cidr(value)
return value
diff --git a/nova/network/neutronv2/api.py b/nova/network/neutronv2/api.py
index 9d8340bc66..e6712fdaef 100644
--- a/nova/network/neutronv2/api.py
+++ b/nova/network/neutronv2/api.py
@@ -1019,6 +1019,10 @@ class API(base_api.NetworkAPI):
self._refresh_neutron_extensions_cache(context, neutron=neutron)
return constants.AUTO_ALLOCATE_TOPO_EXT in self.extensions
+ def _has_multi_provider_extension(self, context, neutron=None):
+ self._refresh_neutron_extensions_cache(context, neutron=neutron)
+ return constants.MULTI_NET_EXT in self.extensions
+
def _get_pci_device_profile(self, pci_dev):
dev_spec = self.pci_whitelist.get_devspec(pci_dev)
if dev_spec:
@@ -1402,6 +1406,37 @@ class API(base_api.NetworkAPI):
raise exception.FixedIpNotFoundForSpecificInstance(
instance_uuid=instance.uuid, ip=address)
+ def _get_phynet_info(self, context, neutron, net_id):
+ phynet_name = None
+ if self._has_multi_provider_extension(context, neutron=neutron):
+ network = neutron.show_network(net_id,
+ fields='segments').get('network')
+ segments = network.get('segments', {})
+ for net in segments:
+ # NOTE(vladikr): In general, "multi-segments" network is a
+ # combination of L2 segments. The current implementation
+ # contains a vxlan and vlan(s) segments, where only a vlan
+ # network will have a physical_network specified, but may
+ # change in the future. The purpose of this method
+ # is to find a first segment that provides a physical network.
+ # TODO(vladikr): Additional work will be required to handle the
+ # case of multiple vlan segments associated with different
+ # physical networks.
+ phynet_name = net.get('provider:physical_network')
+ if phynet_name:
+ return phynet_name
+ # Raising here as at least one segment should
+ # have a physical network provided.
+ if segments:
+ msg = (_("None of the segments of network %s provides a "
+ "physical_network") % net_id)
+ raise exception.NovaException(message=msg)
+
+ net = neutron.show_network(net_id,
+ fields='provider:physical_network').get('network')
+ phynet_name = net.get('provider:physical_network')
+ return phynet_name
+
def _get_port_vnic_info(self, context, neutron, port_id):
"""Retrieve port vnic info
@@ -1415,9 +1450,7 @@ class API(base_api.NetworkAPI):
network_model.VNIC_TYPE_NORMAL)
if vnic_type in network_model.VNIC_TYPES_SRIOV:
net_id = port['network_id']
- net = neutron.show_network(net_id,
- fields='provider:physical_network').get('network')
- phynet_name = net.get('provider:physical_network')
+ phynet_name = self._get_phynet_info(context, neutron, net_id)
return vnic_type, phynet_name
def create_pci_requests_for_sriov_ports(self, context, pci_requests,
@@ -1914,10 +1947,27 @@ class API(base_api.NetworkAPI):
% name_or_id)
raise exception.NovaException(message=msg)
+ def _get_default_floating_ip_pool_name(self):
+ """Get default pool name from config.
+
+ TODO(stephenfin): Remove this helper function in Queens, opting to
+ use the [neutron] option only.
+ """
+ if CONF.default_floating_pool != 'nova':
+ LOG.warning(_LW("Config option 'default_floating_pool' is set to "
+ "a non-default value. Falling back to this value "
+ "for now but this behavior will change in a "
+ "future release. You should unset this value "
+ "and set the '[neutron] default_floating_pool' "
+ "option instead."))
+ return CONF.default_floating_pool
+
+ return CONF.neutron.default_floating_pool
+
def allocate_floating_ip(self, context, pool=None):
"""Add a floating IP to a project from a pool."""
client = get_client(context)
- pool = pool or CONF.default_floating_pool
+ pool = pool or self._get_default_floating_ip_pool_name()
pool_id = self._get_floating_ip_pool_id_by_name_or_id(client, pool)
param = {'floatingip': {'floating_network_id': pool_id}}
diff --git a/nova/network/neutronv2/constants.py b/nova/network/neutronv2/constants.py
index a59de3de7f..200b6233f1 100644
--- a/nova/network/neutronv2/constants.py
+++ b/nova/network/neutronv2/constants.py
@@ -19,3 +19,4 @@ PORTBINDING_EXT = 'Port Binding'
VNIC_INDEX_EXT = 'VNIC Index'
DNS_INTEGRATION = 'DNS Integration'
AUTO_ALLOCATE_TOPO_EXT = 'Auto Allocated Topology Services'
+MULTI_NET_EXT = 'Multi Provider Network'
diff --git a/nova/network/security_group/security_group_base.py b/nova/network/security_group/security_group_base.py
index 0354b72e8b..dce18d98c1 100644
--- a/nova/network/security_group/security_group_base.py
+++ b/nova/network/security_group/security_group_base.py
@@ -18,12 +18,12 @@
# under the License.
from oslo_utils import encodeutils
+from oslo_utils import netutils as utils
from six.moves import urllib
from nova import exception
from nova.i18n import _
from nova.objects import security_group as security_group_obj
-from nova import utils
class SecurityGroupBase(object):
diff --git a/nova/notifications/objects/instance.py b/nova/notifications/objects/instance.py
index e0bd27795a..dc829a0739 100644
--- a/nova/notifications/objects/instance.py
+++ b/nova/notifications/objects/instance.py
@@ -130,6 +130,22 @@ class InstanceActionPayload(InstancePayload):
@nova_base.NovaObjectRegistry.register_notification
+class InstanceActionVolumePayload(InstanceActionPayload):
+ # Version 1.0: Initial version
+
+ VERSION = '1.0'
+ fields = {
+ 'volume_id': fields.UUIDField()
+ }
+
+ def __init__(self, instance, fault, volume_id):
+ super(InstanceActionVolumePayload, self).__init__(
+ instance=instance,
+ fault=fault,
+ volume_id=volume_id)
+
+
+@nova_base.NovaObjectRegistry.register_notification
class InstanceActionVolumeSwapPayload(InstanceActionPayload):
# No SCHEMA as all the additional fields are calculated
@@ -343,3 +359,18 @@ class InstanceActionVolumeSwapNotification(base.NotificationBase):
fields = {
'payload': fields.ObjectField('InstanceActionVolumeSwapPayload')
}
+
+
+@base.notification_sample('instance-volume_attach-start.json')
+@base.notification_sample('instance-volume_attach-end.json')
+@base.notification_sample('instance-volume_attach-error.json')
+@base.notification_sample('instance-volume_detach-start.json')
+@base.notification_sample('instance-volume_detach-end.json')
+@nova_base.NovaObjectRegistry.register_notification
+class InstanceActionVolumeNotification(base.NotificationBase):
+ # Version 1.0: Initial version
+ VERSION = '1.0'
+
+ fields = {
+ 'payload': fields.ObjectField('InstanceActionVolumePayload')
+ }
diff --git a/nova/objects/resource_provider.py b/nova/objects/resource_provider.py
index 96dd1543cb..8620f36a7f 100644
--- a/nova/objects/resource_provider.py
+++ b/nova/objects/resource_provider.py
@@ -34,12 +34,14 @@ from nova import objects
from nova.objects import base
from nova.objects import fields
+_TRAIT_TBL = models.Trait.__table__
_ALLOC_TBL = models.Allocation.__table__
_INV_TBL = models.Inventory.__table__
_RP_TBL = models.ResourceProvider.__table__
_RC_TBL = models.ResourceClass.__table__
_AGG_TBL = models.PlacementAggregate.__table__
_RP_AGG_TBL = models.ResourceProviderAggregate.__table__
+_RP_TRAIT_TBL = models.ResourceProviderTrait.__table__
_RC_CACHE = None
LOG = logging.getLogger(__name__)
@@ -584,6 +586,125 @@ class ResourceProvider(base.NovaObject):
self._set_traits_to_db(self._context, self, self.id, traits)
+@db_api.api_context_manager.reader
+def _get_providers_with_shared_capacity(ctx, rc_id, amount):
+ """Returns a list of resource provider IDs (internal IDs, not UUIDs)
+ that have capacity for a requested amount of a resource and indicate that
+ they share resource via an aggregate association.
+
+ Shared resource providers are marked with a standard trait called
+ MISC_SHARES_VIA_AGGREGATE. This indicates that the provider allows its
+ inventory to be consumed by other resource providers associated via an
+ aggregate link.
+
+ For example, assume we have two compute nodes, CN_1 and CN_2, each with
+ inventory of VCPU and MEMORY_MB but not DISK_GB (in other words, these are
+ compute nodes with no local disk). There is a resource provider called
+ "NFS_SHARE" that has an inventory of DISK_GB and has the
+ MISC_SHARES_VIA_AGGREGATE trait. Both the "CN_1" and "CN_2" compute node
+ resource providers and the "NFS_SHARE" resource provider are associated
+ with an aggregate called "AGG_1".
+
+ The scheduler needs to determine the resource providers that can fulfill a
+ request for 2 VCPU, 1024 MEMORY_MB and 100 DISK_GB.
+
+ Clearly, no single provider can satisfy the request for all three
+ resources, since neither compute node has DISK_GB inventory and the
+ NFS_SHARE provider has no VCPU or MEMORY_MB inventories.
+
+ However, if we consider the NFS_SHARE resource provider as providing
+ inventory of DISK_GB for both CN_1 and CN_2, we can include CN_1 and CN_2
+ as potential fits for the requested set of resources.
+
+ To facilitate that matching query, this function returns all providers that
+ indicate they share their inventory with providers in some aggregate and
+ have enough capacity for the requested amount of a resource.
+
+ To follow the example above, if we were to call
+ _get_providers_with_shared_capacity(ctx, "DISK_GB", 100), we would want to
+ get back the ID for the NFS_SHARE resource provider.
+ """
+ # The SQL we need to generate here looks like this:
+ #
+ # SELECT rp.id
+ # FROM resource_providers AS rp
+ # INNER JOIN resource_provider_traits AS rpt
+ # ON rp.id = rpt.resource_provider_id
+ # INNER JOIN traits AS t
+ # AND rpt.trait_id = t.id
+ # AND t.name = "MISC_SHARES_VIA_AGGREGATE"
+ # INNER JOIN inventories AS inv
+ # ON rp.id = inv.resource_provider_id
+ # AND inv.resource_class_id = $rc_id
+ # LEFT JOIN (
+ # SELECT resource_provider_id, SUM(used) as used
+ # FROM allocations
+ # WHERE resource_class_id = $rc_id
+ # GROUP BY resource_provider_id
+ # ) AS usage
+ # ON rp.id = usage.resource_provider_id
+ # WHERE COALESCE(usage.used, 0) + $amount <= (
+ # inv.total + inv.reserved) * inv.allocation_ratio
+ # ) AND
+ # inv.min_unit <= $amount AND
+ # inv.max_unit >= $amount AND
+ # $amount % inv.step_size = 0
+ # GROUP BY rp.id
+
+ rp_tbl = sa.alias(_RP_TBL, name='rp')
+ inv_tbl = sa.alias(_INV_TBL, name='inv')
+ t_tbl = sa.alias(_TRAIT_TBL, name='t')
+ rpt_tbl = sa.alias(_RP_TRAIT_TBL, name='rpt')
+
+ rp_to_rpt_join = sa.join(
+ rp_tbl, rpt_tbl,
+ rp_tbl.c.id == rpt_tbl.c.resource_provider_id,
+ )
+
+ rpt_to_t_join = sa.join(
+ rp_to_rpt_join, t_tbl,
+ sa.and_(
+ rpt_tbl.c.trait_id == t_tbl.c.id,
+ # TODO(jaypipes): Replace with os_traits.MISC_SHARE_VIA_AGGREGATE
+ # once os-traits released with that trait.
+ t_tbl.c.name == six.text_type('MISC_SHARES_VIA_AGGREGATE'),
+ ),
+ )
+
+ rp_to_inv_join = sa.join(
+ rpt_to_t_join, inv_tbl,
+ sa.and_(
+ rpt_tbl.c.resource_provider_id == inv_tbl.c.resource_provider_id,
+ inv_tbl.c.resource_class_id == rc_id,
+ ),
+ )
+
+ usage = sa.select([_ALLOC_TBL.c.resource_provider_id,
+ sql.func.sum(_ALLOC_TBL.c.used).label('used')])
+ usage = usage.where(_ALLOC_TBL.c.resource_class_id == rc_id)
+ usage = usage.group_by(_ALLOC_TBL.c.resource_provider_id)
+ usage = sa.alias(usage, name='usage')
+
+ inv_to_usage_join = sa.outerjoin(
+ rp_to_inv_join, usage,
+ inv_tbl.c.resource_provider_id == usage.c.resource_provider_id,
+ )
+
+ sel = sa.select([rp_tbl.c.id]).select_from(inv_to_usage_join)
+ sel = sel.where(
+ sa.and_(
+ func.coalesce(usage.c.used, 0) + amount <= (
+ inv_tbl.c.total - inv_tbl.c.reserved
+ ) * inv_tbl.c.allocation_ratio,
+ inv_tbl.c.min_unit <= amount,
+ inv_tbl.c.max_unit >= amount,
+ amount % inv_tbl.c.step_size == 0,
+ ),
+ )
+ sel = sel.group_by(rp_tbl.c.id)
+ return [r[0] for r in ctx.session.execute(sel)]
+
+
@base.NovaObjectRegistry.register
class ResourceProviderList(base.ObjectListBase, base.NovaObject):
# Version 1.0: Initial Version
diff --git a/nova/objects/service.py b/nova/objects/service.py
index 42088b7bfc..7b08d18471 100644
--- a/nova/objects/service.py
+++ b/nova/objects/service.py
@@ -130,7 +130,8 @@ class Service(base.NovaPersistentObject, base.NovaObject,
# Version 1.19: Added get_minimum_version()
# Version 1.20: Added get_minimum_version_multi()
# Version 1.21: Added uuid
- VERSION = '1.21'
+ # Version 1.22: Added get_by_uuid()
+ VERSION = '1.22'
fields = {
'id': fields.IntegerField(read_only=True),
@@ -267,6 +268,11 @@ class Service(base.NovaPersistentObject, base.NovaObject,
return cls._from_db_object(context, cls(), db_service)
@base.remotable_classmethod
+ def get_by_uuid(cls, context, service_uuid):
+ db_service = db.service_get_by_uuid(context, service_uuid)
+ return cls._from_db_object(context, cls(), db_service)
+
+ @base.remotable_classmethod
def get_by_host_and_topic(cls, context, host, topic):
db_service = db.service_get_by_host_and_topic(context, host, topic)
return cls._from_db_object(context, cls(), db_service)
diff --git a/nova/policies/__init__.py b/nova/policies/__init__.py
index 08d012273a..627b082b09 100644
--- a/nova/policies/__init__.py
+++ b/nova/policies/__init__.py
@@ -24,7 +24,6 @@ from nova.policies import baremetal_nodes
from nova.policies import base
from nova.policies import cells
from nova.policies import cells_scheduler
-from nova.policies import cloudpipe
from nova.policies import config_drive
from nova.policies import console_auth_tokens
from nova.policies import console_output
@@ -103,7 +102,6 @@ def list_rules():
base.list_rules(),
cells.list_rules(),
cells_scheduler.list_rules(),
- cloudpipe.list_rules(),
config_drive.list_rules(),
console_auth_tokens.list_rules(),
console_output.list_rules(),
diff --git a/nova/policies/cloudpipe.py b/nova/policies/cloudpipe.py
deleted file mode 100644
index 2951b83e69..0000000000
--- a/nova/policies/cloudpipe.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright 2016 Cloudbase Solutions Srl
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from nova.policies import base
-
-
-BASE_POLICY_NAME = 'os_compute_api:os-cloudpipe'
-
-
-cloudpipe_policies = [
- base.create_rule_default(
- BASE_POLICY_NAME,
- base.RULE_ADMIN_API,
- """List, create and update cloud pipes.
-
-os-cloudpipe API is deprecated as this works only with nova-network which \
-itself is deprecated.
-""",
- [
- {
- 'method': 'GET',
- 'path': '/os-cloudpipe'
- },
- {
- 'method': 'POST',
- 'path': '/os-cloudpipe'
- },
- {
- 'method': 'PUT',
- 'path': '/os-cloudpipe/configure-project'
- }
- ]),
-]
-
-
-def list_rules():
- return cloudpipe_policies
diff --git a/nova/policies/config_drive.py b/nova/policies/config_drive.py
index e6f482a8ae..8bafe855c0 100644
--- a/nova/policies/config_drive.py
+++ b/nova/policies/config_drive.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_policy import policy
-
from nova.policies import base
@@ -22,9 +20,20 @@ BASE_POLICY_NAME = 'os_compute_api:os-config-drive'
config_drive_policies = [
- policy.RuleDefault(
- name=BASE_POLICY_NAME,
- check_str=base.RULE_ADMIN_OR_OWNER),
+ base.create_rule_default(
+ BASE_POLICY_NAME,
+ base.RULE_ADMIN_OR_OWNER,
+ """Add 'config_drive' attribute in the server response.""",
+ [
+ {
+ 'method': 'GET',
+ 'path': '/servers/{id}'
+ },
+ {
+ 'method': 'GET',
+ 'path': '/servers/detail'
+ }
+ ]),
]
diff --git a/nova/policies/extensions.py b/nova/policies/extensions.py
index 53e191e8e5..a8ec51df7c 100644
--- a/nova/policies/extensions.py
+++ b/nova/policies/extensions.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_policy import policy
-
from nova.policies import base
@@ -22,9 +20,21 @@ BASE_POLICY_NAME = 'os_compute_api:extensions'
extensions_policies = [
- policy.RuleDefault(
- name=BASE_POLICY_NAME,
- check_str=base.RULE_ADMIN_OR_OWNER),
+ base.create_rule_default(
+ BASE_POLICY_NAME,
+ base.RULE_ADMIN_OR_OWNER,
+ "Lists available extensions and shows information for an extension "
+ "by alias.",
+ [
+ {
+ 'method': 'GET',
+ 'path': '/extensions'
+ },
+ {
+ 'method': 'GET',
+ 'path': '/extensions/{alias}'
+ }
+ ]),
]
diff --git a/nova/policies/flavor_access.py b/nova/policies/flavor_access.py
index cababbde90..0d9c2f1b36 100644
--- a/nova/policies/flavor_access.py
+++ b/nova/policies/flavor_access.py
@@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_policy import policy
from nova.policies import base
@@ -23,15 +22,54 @@ POLICY_ROOT = 'os_compute_api:os-flavor-access:%s'
flavor_access_policies = [
- policy.RuleDefault(
- name=POLICY_ROOT % 'add_tenant_access',
- check_str=base.RULE_ADMIN_API),
- policy.RuleDefault(
- name=POLICY_ROOT % 'remove_tenant_access',
- check_str=base.RULE_ADMIN_API),
- policy.RuleDefault(
- name=BASE_POLICY_NAME,
- check_str=base.RULE_ADMIN_OR_OWNER),
+ base.create_rule_default(
+ POLICY_ROOT % 'add_tenant_access',
+ base.RULE_ADMIN_API,
+ "Add flavor access to a tenant",
+ [
+ {
+ 'method': 'POST',
+ 'path': '/flavors/{flavor_id}/action (addTenantAccess)'
+ }
+ ]),
+ base.create_rule_default(
+ POLICY_ROOT % 'remove_tenant_access',
+ base.RULE_ADMIN_API,
+ "Remove flavor access from a tenant",
+ [
+ {
+ 'method': 'POST',
+ 'path': '/flavors/{flavor_id}/action (removeTenantAccess)'
+ }
+ ]),
+ base.create_rule_default(
+ BASE_POLICY_NAME,
+ base.RULE_ADMIN_OR_OWNER,
+ """Allow the listing of flavor access information
+
+Adds the os-flavor-access:is_public key into several flavor APIs.
+
+It also allows access to the full list of tenants that have access
+to a flavor via an os-flavor-access API.
+""",
+ [
+ {
+ 'method': 'GET',
+ 'path': '/flavors/{flavor_id}/os-flavor-access'
+ },
+ {
+ 'method': 'GET',
+ 'path': '/flavors/detail'
+ },
+ {
+ 'method': 'GET',
+ 'path': '/flavors/{flavor_id}'
+ },
+ {
+ 'method': 'POST',
+ 'path': '/flavors'
+ },
+ ]),
]
diff --git a/nova/policies/flavor_manage.py b/nova/policies/flavor_manage.py
index c29fa48b6f..aba5475800 100644
--- a/nova/policies/flavor_manage.py
+++ b/nova/policies/flavor_manage.py
@@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_policy import policy
from nova.policies import base
@@ -22,9 +21,21 @@ BASE_POLICY_NAME = 'os_compute_api:os-flavor-manage'
flavor_manage_policies = [
- policy.RuleDefault(
- name=BASE_POLICY_NAME,
- check_str=base.RULE_ADMIN_API),
+ base.create_rule_default(
+ BASE_POLICY_NAME,
+ base.RULE_ADMIN_API,
+ "Create and delete Flavors",
+ [
+ {
+ 'method': 'POST',
+ 'path': '/flavors'
+ },
+ {
+ 'method': 'DELETE',
+ 'path': '/flavors/{flavor_id}'
+ },
+
+ ]),
]
diff --git a/nova/policies/flavor_rxtx.py b/nova/policies/flavor_rxtx.py
index 3a6be53f11..9f8c0fb182 100644
--- a/nova/policies/flavor_rxtx.py
+++ b/nova/policies/flavor_rxtx.py
@@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_policy import policy
from nova.policies import base
@@ -22,9 +21,24 @@ BASE_POLICY_NAME = 'os_compute_api:os-flavor-rxtx'
flavor_rxtx_policies = [
- policy.RuleDefault(
- name=BASE_POLICY_NAME,
- check_str=base.RULE_ADMIN_OR_OWNER),
+ base.create_rule_default(
+ BASE_POLICY_NAME,
+ base.RULE_ADMIN_OR_OWNER,
+ "Adds the rxtx_factor key into some Flavor APIs",
+ [
+ {
+ 'method': 'GET',
+ 'path': '/flavors/detail'
+ },
+ {
+ 'method': 'GET',
+ 'path': '/flavors/{flavor_id}'
+ },
+ {
+ 'method': 'POST',
+ 'path': '/flavors'
+ },
+ ]),
]
diff --git a/nova/policies/flavors.py b/nova/policies/flavors.py
index 91b2c41b23..d9d544df10 100644
--- a/nova/policies/flavors.py
+++ b/nova/policies/flavors.py
@@ -22,6 +22,8 @@ BASE_POLICY_NAME = 'os_compute_api:flavors'
flavors_policies = [
+ # TODO(johngarbutt) this doesn't appear to be used in the code and
+ # as such should be removed.
policy.RuleDefault(
name=BASE_POLICY_NAME,
check_str=base.RULE_ADMIN_OR_OWNER),
diff --git a/nova/policies/fping.py b/nova/policies/fping.py
index 1b1dbe95f7..1750a28d21 100644
--- a/nova/policies/fping.py
+++ b/nova/policies/fping.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_policy import policy
-
from nova.policies import base
@@ -23,12 +21,38 @@ POLICY_ROOT = 'os_compute_api:os-fping:%s'
fping_policies = [
- policy.RuleDefault(
- name=POLICY_ROOT % 'all_tenants',
- check_str=base.RULE_ADMIN_API),
- policy.RuleDefault(
- name=BASE_POLICY_NAME,
- check_str=base.RULE_ADMIN_OR_OWNER),
+ base.create_rule_default(
+ POLICY_ROOT % 'all_tenants',
+ base.RULE_ADMIN_API,
+ """Pings instances for all projects and reports which instances
+are alive.
+
+os-fping API is deprecated as this works only with nova-network
+which itself is deprecated.""",
+ [
+ {
+ 'method': 'GET',
+ 'path': '/os-fping?all_tenants=true'
+ }
+ ]),
+ base.create_rule_default(
+ BASE_POLICY_NAME,
+ base.RULE_ADMIN_OR_OWNER,
+ """Pings instances, particular instance and reports which instances
+are alive.
+
+os-fping API is deprecated as this works only with nova-network
+which itself is deprecated.""",
+ [
+ {
+ 'method': 'GET',
+ 'path': '/os-fping'
+ },
+ {
+ 'method': 'GET',
+ 'path': '/os-fping/{instance_id}'
+ }
+ ])
]
diff --git a/nova/policies/image_size.py b/nova/policies/image_size.py
index df386bac78..b0e1b558c4 100644
--- a/nova/policies/image_size.py
+++ b/nova/policies/image_size.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_policy import policy
-
from nova.policies import base
@@ -22,9 +20,20 @@ BASE_POLICY_NAME = 'os_compute_api:image-size'
image_size_policies = [
- policy.RuleDefault(
- name=BASE_POLICY_NAME,
- check_str=base.RULE_ADMIN_OR_OWNER),
+ base.create_rule_default(
+ BASE_POLICY_NAME,
+ base.RULE_ADMIN_OR_OWNER,
+ """Add 'OS-EXT-IMG-SIZE:size' attribute in the image response.""",
+ [
+ {
+ 'method': 'GET',
+ 'path': '/images/{id}'
+ },
+ {
+ 'method': 'GET',
+ 'path': '/images/detail'
+ }
+ ]),
]
diff --git a/nova/policies/instance_actions.py b/nova/policies/instance_actions.py
index 1e9995a74d..ba723899bd 100644
--- a/nova/policies/instance_actions.py
+++ b/nova/policies/instance_actions.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_policy import policy
-
from nova.policies import base
@@ -23,12 +21,33 @@ POLICY_ROOT = 'os_compute_api:os-instance-actions:%s'
instance_actions_policies = [
- policy.RuleDefault(
- name=POLICY_ROOT % 'events',
- check_str=base.RULE_ADMIN_API),
- policy.RuleDefault(
- name=BASE_POLICY_NAME,
- check_str=base.RULE_ADMIN_OR_OWNER),
+ base.create_rule_default(
+ POLICY_ROOT % 'events',
+ base.RULE_ADMIN_API,
+ """Add events details in action details for a server.
+
+This check is performed only after the check
+os_compute_api:os-instance-actions passes""",
+ [
+ {
+ 'method': 'GET',
+ 'path': '/servers/{server_id}/os-instance-actions/{request_id}'
+ }
+ ]),
+ base.create_rule_default(
+ BASE_POLICY_NAME,
+ base.RULE_ADMIN_OR_OWNER,
+ """List actions and show action details for a server.""",
+ [
+ {
+ 'method': 'GET',
+ 'path': '/servers/{server_id}/os-instance-actions'
+ },
+ {
+ 'method': 'GET',
+ 'path': '/servers/{server_id}/os-instance-actions/{request_id}'
+ }
+ ]),
]
diff --git a/nova/policies/instance_usage_audit_log.py b/nova/policies/instance_usage_audit_log.py
index 08eec0806c..616a39d2a4 100644
--- a/nova/policies/instance_usage_audit_log.py
+++ b/nova/policies/instance_usage_audit_log.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_policy import policy
-
from nova.policies import base
@@ -22,9 +20,21 @@ BASE_POLICY_NAME = 'os_compute_api:os-instance-usage-audit-log'
instance_usage_audit_log_policies = [
- policy.RuleDefault(
- name=BASE_POLICY_NAME,
- check_str=base.RULE_ADMIN_API),
+ base.create_rule_default(
+ BASE_POLICY_NAME,
+ base.RULE_ADMIN_API,
+ """Lists all usage audits and that occurred before a specified time
+for all servers on all compute hosts where usage auditing is configured.""",
+ [
+ {
+ 'method': 'GET',
+ 'path': '/os-instance_usage_audit_log'
+ },
+ {
+ 'method': 'GET',
+ 'path': '/os-instance_usage_audit_log/{before_timestamp}'
+ }
+ ]),
]
diff --git a/nova/policies/ips.py b/nova/policies/ips.py
index a9888acb96..24e2397e2f 100644
--- a/nova/policies/ips.py
+++ b/nova/policies/ips.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_policy import policy
-
from nova.policies import base
@@ -22,12 +20,26 @@ POLICY_ROOT = 'os_compute_api:ips:%s'
ips_policies = [
- policy.RuleDefault(
- name=POLICY_ROOT % 'show',
- check_str=base.RULE_ADMIN_OR_OWNER),
- policy.RuleDefault(
- name=POLICY_ROOT % 'index',
- check_str=base.RULE_ADMIN_OR_OWNER),
+ base.create_rule_default(
+ POLICY_ROOT % 'show',
+ base.RULE_ADMIN_OR_OWNER,
+ """Shows IP addresses details for a network label of a server.""",
+ [
+ {
+ 'method': 'GET',
+ 'path': '/servers/{server_id}/ips/{network_label}'
+ }
+ ]),
+ base.create_rule_default(
+ POLICY_ROOT % 'index',
+ base.RULE_ADMIN_OR_OWNER,
+ """Lists IP addresses that are assigned to a server.""",
+ [
+ {
+ 'method': 'GET',
+ 'path': '/servers/{server_id}/ips'
+ }
+ ]),
]
diff --git a/nova/policies/quota_class_sets.py b/nova/policies/quota_class_sets.py
index 240e8aef44..2710436fcb 100644
--- a/nova/policies/quota_class_sets.py
+++ b/nova/policies/quota_class_sets.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_policy import policy
-
from nova.policies import base
@@ -22,12 +20,26 @@ POLICY_ROOT = 'os_compute_api:os-quota-class-sets:%s'
quota_class_sets_policies = [
- policy.RuleDefault(
- name=POLICY_ROOT % 'show',
- check_str='is_admin:True or quota_class:%(quota_class)s'),
- policy.RuleDefault(
- name=POLICY_ROOT % 'update',
- check_str=base.RULE_ADMIN_API),
+ base.create_rule_default(
+ POLICY_ROOT % 'show',
+ 'is_admin:True or quota_class:%(quota_class)s',
+ "List quotas for specific quota classs",
+ [
+ {
+ 'method': 'GET',
+ 'path': '/os-quota-class-sets/{quota_class}'
+ }
+ ]),
+ base.create_rule_default(
+ POLICY_ROOT % 'update',
+ base.RULE_ADMIN_API,
+ 'Update quotas for specific quota class',
+ [
+ {
+ 'method': 'PUT',
+ 'path': '/os-quota-class-sets/{quota_class}'
+ }
+ ]),
]
diff --git a/nova/scheduler/client/report.py b/nova/scheduler/client/report.py
index 87def6493d..6d59e66647 100644
--- a/nova/scheduler/client/report.py
+++ b/nova/scheduler/client/report.py
@@ -723,7 +723,7 @@ class SchedulerReportClient(object):
# TODO(cdent): When we're happy that all placement
# servers support microversion 1.7 we can remove this
# call and the associated code.
- LOG.debug('Falling back to placement API microverison 1.2 '
+ LOG.debug('Falling back to placement API microversion 1.2 '
'for resource class management.')
return self._get_or_create_resource_class(name)
else:
diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-cloudpipe/cloud-pipe-create-req.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-cloudpipe/cloud-pipe-create-req.json.tpl
deleted file mode 100644
index c8fc75995a..0000000000
--- a/nova/tests/functional/api_sample_tests/api_samples/os-cloudpipe/cloud-pipe-create-req.json.tpl
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "cloudpipe": {
- "project_id": "%(project_id)s"
- }
-}
diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-cloudpipe/cloud-pipe-create-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-cloudpipe/cloud-pipe-create-resp.json.tpl
deleted file mode 100644
index 6aa2ff60e2..0000000000
--- a/nova/tests/functional/api_sample_tests/api_samples/os-cloudpipe/cloud-pipe-create-resp.json.tpl
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "instance_id": "%(id)s"
-}
diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-cloudpipe/cloud-pipe-get-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-cloudpipe/cloud-pipe-get-resp.json.tpl
deleted file mode 100644
index 698008802e..0000000000
--- a/nova/tests/functional/api_sample_tests/api_samples/os-cloudpipe/cloud-pipe-get-resp.json.tpl
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "cloudpipes": [
- {
- "created_at": "%(isotime)s",
- "instance_id": "%(uuid)s",
- "internal_ip": "%(ip)s",
- "project_id": "%(project_id)s",
- "public_ip": "%(ip)s",
- "public_port": 22,
- "state": "down"
- }
- ]
-}
diff --git a/nova/tests/functional/api_sample_tests/api_samples/os-cloudpipe/cloud-pipe-update-req.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/os-cloudpipe/cloud-pipe-update-req.json.tpl
deleted file mode 100644
index 0ab9141aea..0000000000
--- a/nova/tests/functional/api_sample_tests/api_samples/os-cloudpipe/cloud-pipe-update-req.json.tpl
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "configure_project": {
- "vpn_ip": "%(vpn_ip)s",
- "vpn_port": "%(vpn_port)s"
- }
-}
diff --git a/nova/tests/functional/api_sample_tests/test_cloudpipe.py b/nova/tests/functional/api_sample_tests/test_cloudpipe.py
deleted file mode 100644
index 5026095d70..0000000000
--- a/nova/tests/functional/api_sample_tests/test_cloudpipe.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# Copyright 2014 IBM Corp.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import uuid as uuid_lib
-
-import nova.conf
-from nova.tests.functional.api_sample_tests import api_sample_base
-from nova.tests.unit.image import fake
-
-
-CONF = nova.conf.CONF
-
-
-class CloudPipeSampleTest(api_sample_base.ApiSampleTestBaseV21):
- ADMIN_API = True
- sample_dir = "os-cloudpipe"
-
- def setUp(self):
- super(CloudPipeSampleTest, self).setUp()
-
- def get_user_data(self, project_id):
- """Stub method to generate user data for cloudpipe tests."""
- return "VVNFUiBEQVRB\n"
-
- def network_api_get(self, context, network_uuid):
- """Stub to get a valid network and its information."""
- return {'vpn_public_address': '127.0.0.1',
- 'vpn_public_port': 22}
-
- self.stub_out('nova.cloudpipe.pipelib.CloudPipe.get_encoded_zip',
- get_user_data)
- self.stub_out('nova.network.api.API.get',
- network_api_get)
-
- def generalize_subs(self, subs, vanilla_regexes):
- subs['project_id'] = '[0-9a-f-]+'
- return subs
-
- def test_cloud_pipe_create(self):
- # Get api samples of cloud pipe extension creation.
- self.flags(vpn_image_id=fake.get_valid_image_id(), group='cloudpipe')
- subs = {'project_id': str(uuid_lib.uuid4().hex)}
- response = self._do_post('os-cloudpipe', 'cloud-pipe-create-req',
- subs)
- subs['image_id'] = CONF.cloudpipe.vpn_image_id
- self._verify_response('cloud-pipe-create-resp', subs, response, 200)
- return subs
-
- def test_cloud_pipe_list(self):
- # Get api samples of cloud pipe extension get request.
- subs = self.test_cloud_pipe_create()
- response = self._do_get('os-cloudpipe')
- subs['image_id'] = CONF.cloudpipe.vpn_image_id
- self._verify_response('cloud-pipe-get-resp', subs, response, 200)
-
- def test_cloud_pipe_update(self):
- subs = {'vpn_ip': '192.168.1.1',
- 'vpn_port': '2000'}
- response = self._do_put('os-cloudpipe/configure-project',
- 'cloud-pipe-update-req',
- subs)
- self.assertEqual(202, response.status_code)
- self.assertEqual("", response.text)
diff --git a/nova/tests/functional/compute/test_resource_tracker.py b/nova/tests/functional/compute/test_resource_tracker.py
index cda0ec7278..129698e4d0 100644
--- a/nova/tests/functional/compute/test_resource_tracker.py
+++ b/nova/tests/functional/compute/test_resource_tracker.py
@@ -145,6 +145,7 @@ class IronicResourceTrackerTest(test.TestCase):
self.driver_mock = driver
self.rt = resource_tracker.ResourceTracker(COMPUTE_HOST, driver)
self.rt.scheduler_client.reportclient = self.report_client
+ self.rt.reportclient = self.report_client
self.url = 'http://localhost/placement'
self.create_fixtures()
diff --git a/nova/tests/functional/db/test_resource_provider.py b/nova/tests/functional/db/test_resource_provider.py
index 8803f61909..8b96545478 100644
--- a/nova/tests/functional/db/test_resource_provider.py
+++ b/nova/tests/functional/db/test_resource_provider.py
@@ -1740,3 +1740,107 @@ class ResourceProviderTraitTestCase(ResourceProviderBaseCase):
self._assert_traits(['CUSTOM_TRAIT_C'],
objects.TraitList.get_all(self.context,
filters={'associated': False}))
+
+
+class SharedProviderTestCase(ResourceProviderBaseCase):
+ """Tests that the queries used to determine placement in deployments with
+ shared resource providers such as a shared disk pool result in accurate
+ reporting of inventory and usage.
+ """
+
+ def test_shared_provider_capacity(self):
+ """Sets up a resource provider that shares DISK_GB inventory via an
+ aggregate, a couple resource providers representing "local disk"
+ compute nodes and ensures the _get_providers_sharing_capacity()
+ function finds that provider and not providers of "local disk".
+ """
+ # Create the two "local disk" compute node providers
+ cn1_uuid = uuidsentinel.cn1
+ cn1 = objects.ResourceProvider(
+ self.context,
+ name='cn1',
+ uuid=cn1_uuid,
+ )
+ cn1.create()
+
+ cn2_uuid = uuidsentinel.cn2
+ cn2 = objects.ResourceProvider(
+ self.context,
+ name='cn2',
+ uuid=cn2_uuid,
+ )
+ cn2.create()
+
+ # Populate the two compute node providers with inventory, sans DISK_GB
+ for cn in (cn1, cn2):
+ vcpu = objects.Inventory(
+ resource_provider=cn,
+ resource_class=fields.ResourceClass.VCPU,
+ total=24,
+ reserved=0,
+ min_unit=1,
+ max_unit=24,
+ step_size=1,
+ allocation_ratio=16.0,
+ )
+ memory_mb = objects.Inventory(
+ resource_provider=cn,
+ resource_class=fields.ResourceClass.MEMORY_MB,
+ total=32768,
+ reserved=0,
+ min_unit=64,
+ max_unit=32768,
+ step_size=64,
+ allocation_ratio=1.5,
+ )
+ inv_list = objects.InventoryList(objects=[vcpu, memory_mb])
+ cn.set_inventory(inv_list)
+
+ # Create the shared storage pool
+ ss_uuid = uuidsentinel.ss
+ ss = objects.ResourceProvider(
+ self.context,
+ name='shared storage',
+ uuid=ss_uuid,
+ )
+ ss.create()
+
+ # Give the shared storage pool some inventory of DISK_GB
+ disk_gb = objects.Inventory(
+ resource_provider=ss,
+ resource_class=fields.ResourceClass.DISK_GB,
+ total=2000,
+ reserved=0,
+ min_unit=10,
+ max_unit=100,
+ step_size=10,
+ allocation_ratio=1.0,
+ )
+ disk_gb.obj_set_defaults()
+ inv_list = objects.InventoryList(objects=[disk_gb])
+ ss.set_inventory(inv_list)
+
+ # Mark the shared storage pool as having inventory shared among any
+ # provider associated via aggregate
+ t = objects.Trait(
+ self.context,
+ name="MISC_SHARES_VIA_AGGREGATE",
+ )
+ # TODO(jaypipes): Once MISC_SHARES_VIA_AGGREGATE is a standard
+ # os-traits trait, we won't need to create() here. Instead, we will
+ # just do:
+ # t = objects.Trait.get_by_name(
+ # self.context,
+ # "MISC_SHARES_VIA_AGGREGATE",
+ # )
+ t.create()
+ ss.set_traits(objects.TraitList(objects=[t]))
+
+ # OK, now that has all been set up, let's verify that we get the ID of
+ # the shared storage pool when we ask for 100 DISK_GB
+ got_ids = rp_obj._get_providers_with_shared_capacity(
+ self.context,
+ fields.ResourceClass.STANDARD.index(fields.ResourceClass.DISK_GB),
+ 100,
+ )
+ self.assertEqual([ss.id], got_ids)
diff --git a/nova/tests/functional/notification_sample_tests/test_instance.py b/nova/tests/functional/notification_sample_tests/test_instance.py
index 2763464706..38037d4f3a 100644
--- a/nova/tests/functional/notification_sample_tests/test_instance.py
+++ b/nova/tests/functional/notification_sample_tests/test_instance.py
@@ -83,6 +83,7 @@ class TestInstanceNotificationSample(
self._test_rescue_server,
self._test_unrescue_server,
self._test_soft_delete_server,
+ self._test_attach_volume_error,
]
for action in actions:
@@ -686,6 +687,11 @@ class TestInstanceNotificationSample(
def _attach_volume_to_server(self, server, volume_id):
self.api.post_server_volume(
server['id'], {"volumeAttachment": {"volumeId": volume_id}})
+ self._wait_for_notification('instance.volume_attach.end')
+
+ def _detach_volume_from_server(self, server, volume_id):
+ self.api.delete_server_volume(server['id'], volume_id)
+ self._wait_for_notification('instance.volume_detach.end')
def _volume_swap_server(self, server, attachement_id, volume_id):
self.api.put_server_volume(server['id'], attachement_id, volume_id)
@@ -702,19 +708,19 @@ class TestInstanceNotificationSample(
self.cinder.SWAP_NEW_VOL)
self._wait_until_swap_volume(server, self.cinder.SWAP_NEW_VOL)
- self.assertEqual(4, len(fake_notifier.VERSIONED_NOTIFICATIONS))
+ self.assertEqual(6, len(fake_notifier.VERSIONED_NOTIFICATIONS))
self._verify_notification(
'instance-volume_swap-start',
replacements={
'reservation_id': server['reservation_id'],
'uuid': server['id']},
- actual=fake_notifier.VERSIONED_NOTIFICATIONS[2])
+ actual=fake_notifier.VERSIONED_NOTIFICATIONS[4])
self._verify_notification(
'instance-volume_swap-end',
replacements={
'reservation_id': server['reservation_id'],
'uuid': server['id']},
- actual=fake_notifier.VERSIONED_NOTIFICATIONS[3])
+ actual=fake_notifier.VERSIONED_NOTIFICATIONS[5])
def test_volume_swap_server_with_error(self):
server = self._boot_a_server(
@@ -727,19 +733,21 @@ class TestInstanceNotificationSample(
self.cinder.SWAP_ERR_NEW_VOL)
self._wait_until_swap_volume_error()
- # Five versioned notifications are generated. We only rely on the
- # first four because _wait_until_swap_volume_error will return True
+ # Seven versioned notifications are generated. We only rely on the
+ # first six because _wait_until_swap_volume_error will return True
# after volume_api.unreserve is called on the cinder fixture, and that
# happens before the instance fault is handled in the compute manager
- # which generates the 5th notification (compute.exception).
+ # which generates the last notification (compute.exception).
# 0. instance-create-start
- # 1. instance-create-start
- # 2. instance-volume_swap-start
- # 3. instance-volume_swap-error
- # 4. compute.exception
- self.assertTrue(len(fake_notifier.VERSIONED_NOTIFICATIONS) >= 4,
+ # 1. instance-create-end
+ # 2. instance-volume_attach-start
+ # 3. instance-volume_attach-end
+ # 4. instance-volume_swap-start
+ # 5. instance-volume_swap-error
+ # 6. compute.exception
+ self.assertTrue(len(fake_notifier.VERSIONED_NOTIFICATIONS) >= 6,
'Unexpected number of versioned notifications. '
- 'Expected at least 4, got: %s' %
+ 'Expected at least 6, got: %s' %
len(fake_notifier.VERSIONED_NOTIFICATIONS))
self._verify_notification(
'instance-volume_swap-start',
@@ -748,13 +756,13 @@ class TestInstanceNotificationSample(
'old_volume_id': self.cinder.SWAP_ERR_OLD_VOL,
'reservation_id': server['reservation_id'],
'uuid': server['id']},
- actual=fake_notifier.VERSIONED_NOTIFICATIONS[2])
+ actual=fake_notifier.VERSIONED_NOTIFICATIONS[4])
self._verify_notification(
'instance-volume_swap-error',
replacements={
'reservation_id': server['reservation_id'],
'uuid': server['id']},
- actual=fake_notifier.VERSIONED_NOTIFICATIONS[3])
+ actual=fake_notifier.VERSIONED_NOTIFICATIONS[5])
def _test_revert_server(self, server):
pass
@@ -766,7 +774,42 @@ class TestInstanceNotificationSample(
pass
def _test_volume_attach_detach_server(self, server):
- pass
+ self._attach_volume_to_server(server, self.cinder.SWAP_OLD_VOL)
+
+ # 0. volume_attach-start
+ # 1. volume_attach-end
+ self.assertEqual(2, len(fake_notifier.VERSIONED_NOTIFICATIONS))
+ self._verify_notification(
+ 'instance-volume_attach-start',
+ replacements={
+ 'reservation_id': server['reservation_id'],
+ 'uuid': server['id']},
+ actual=fake_notifier.VERSIONED_NOTIFICATIONS[0])
+ self._verify_notification(
+ 'instance-volume_attach-end',
+ replacements={
+ 'reservation_id': server['reservation_id'],
+ 'uuid': server['id']},
+ actual=fake_notifier.VERSIONED_NOTIFICATIONS[1])
+
+ fake_notifier.reset()
+ self._detach_volume_from_server(server, self.cinder.SWAP_OLD_VOL)
+
+ # 0. volume_detach-start
+ # 1. volume_detach-end
+ self.assertEqual(2, len(fake_notifier.VERSIONED_NOTIFICATIONS))
+ self._verify_notification(
+ 'instance-volume_detach-start',
+ replacements={
+ 'reservation_id': server['reservation_id'],
+ 'uuid': server['id']},
+ actual=fake_notifier.VERSIONED_NOTIFICATIONS[0])
+ self._verify_notification(
+ 'instance-volume_detach-end',
+ replacements={
+ 'reservation_id': server['reservation_id'],
+ 'uuid': server['id']},
+ actual=fake_notifier.VERSIONED_NOTIFICATIONS[1])
def _test_rescue_server(self, server):
pass
@@ -776,3 +819,34 @@ class TestInstanceNotificationSample(
def _test_soft_delete_server(self, server):
pass
+
+ @mock.patch('nova.volume.cinder.API.attach')
+ def _test_attach_volume_error(self, server, mock_attach):
+ def attach_volume(*args, **kwargs):
+ raise exception.CinderConnectionFailed(
+ reason="Connection timed out")
+ mock_attach.side_effect = attach_volume
+
+ post = {"volumeAttachment": {"volumeId": self.cinder.SWAP_OLD_VOL}}
+ self.api.post_server_volume(server['id'], post)
+
+ self._wait_for_notification('instance.volume_attach.error')
+
+ # 0. volume_attach-start
+ # 1. volume_attach-error
+ # 2. compute.exception
+ # We only rely on the first 2 notifications, in this case we don't
+ # care about the exception notification.
+ self.assertLessEqual(2, len(fake_notifier.VERSIONED_NOTIFICATIONS))
+ self._verify_notification(
+ 'instance-volume_attach-start',
+ replacements={
+ 'reservation_id': server['reservation_id'],
+ 'uuid': server['id']},
+ actual=fake_notifier.VERSIONED_NOTIFICATIONS[0])
+ self._verify_notification(
+ 'instance-volume_attach-error',
+ replacements={
+ 'reservation_id': server['reservation_id'],
+ 'uuid': server['id']},
+ actual=fake_notifier.VERSIONED_NOTIFICATIONS[1])
diff --git a/nova/tests/functional/regressions/test_bug_1554631.py b/nova/tests/functional/regressions/test_bug_1554631.py
index 24b9260a23..8263a3818c 100644
--- a/nova/tests/functional/regressions/test_bug_1554631.py
+++ b/nova/tests/functional/regressions/test_bug_1554631.py
@@ -57,16 +57,17 @@ class TestCinderOverLimit(test.TestCase):
self.api = api_fixture.api
@mock.patch('nova.volume.cinder.cinderclient')
- def test_over_limit_volumes(self, mock_cinder):
- """Regression test for bug #1554631.
+ def test_over_limit_volumes_with_message(self, mock_cinder):
+ """Regression test for bug #1680457.
When the Cinder client returns OverLimit when trying to create
- a volume, an OverQuota exception should be raised with the value being
- volumes.
+ a volume, an OverQuota exception should be raised.
"""
cinder_client = mock.Mock()
mock_cinder.return_value = cinder_client
- exc = cinder_exceptions.OverLimit(413)
+ msg = ("VolumeSizeExceedsLimit: Requested volume size XG is larger"
+ " than maximum allowed limit YG.")
+ exc = cinder_exceptions.OverLimit(413, message=msg)
cinder_client.volumes.create.side_effect = exc
volume = {'display_name': 'vol1', 'size': 3}
@@ -74,7 +75,7 @@ class TestCinderOverLimit(test.TestCase):
self.api.post_volume, {'volume': volume})
self.assertEqual(403, e.response.status_code)
# Make sure we went over on volumes
- self.assertIn('volumes', e.response.text)
+ self.assertIn('VolumeSizeExceedsLimit', e.response.text)
@mock.patch('nova.volume.cinder.cinderclient')
def test_over_limit_snapshots(self, mock_cinder):
diff --git a/nova/tests/live_migration/hooks/ceph.sh b/nova/tests/live_migration/hooks/ceph.sh
index 6d59eb1384..e2edba04bf 100755
--- a/nova/tests/live_migration/hooks/ceph.sh
+++ b/nova/tests/live_migration/hooks/ceph.sh
@@ -60,15 +60,11 @@ function configure_and_start_glance {
echo 'check processes before glance-api stop'
$ANSIBLE primary --sudo -f 5 -i "$WORKSPACE/inventory" -m shell -a "ps aux | grep glance-api"
- #stop g-api
- stop 'primary' 'g-api'
+ # restart glance
+ $ANSIBLE primary --sudo -f 5 -i "$WORKSPACE/inventory" -m shell -a "systemctl restart devstack@g-api"
echo 'check processes after glance-api stop'
$ANSIBLE primary --sudo -f 5 -i "$WORKSPACE/inventory" -m shell -a "ps aux | grep glance-api"
-
- # restart glance
- sudo -H -u $STACK_USER bash -c "/tmp/start_process.sh g-api '/usr/local/bin/glance-api --config-file=/etc/glance/glance-api.conf'"
- $ANSIBLE primary --sudo -f 5 -i "$WORKSPACE/inventory" -m shell -a "ps aux | grep glance-api"
}
function _ceph_configure_nova {
@@ -109,25 +105,10 @@ function configure_and_start_nova {
echo 'check compute processes before restart'
$ANSIBLE all --sudo -f 5 -i "$WORKSPACE/inventory" -m shell -a "ps aux | grep compute"
- #stop nova-compute
- stop 'all' 'n-cpu'
+ # restart nova-compute
+ $ANSIBLE all --sudo -f 5 -i "$WORKSPACE/inventory" -m shell -a "systemctl restart devstack@n-cpu"
- echo 'check processes after compute stop'
- $ANSIBLE all --sudo -f 5 -i "$WORKSPACE/inventory" -m shell -a "ps aux | grep compute"
-
- # Determine what the libvirt is. If there is a group called "libvirtd"
- # in /etc/groups use that, otherwise default to "libvirt".
- # Note, new ubuntu and debian use libvirt, everything else is libvirtd.
- local libvirt_group
- libvirt_group=$(cut -d ':' -f 1 /etc/group | grep 'libvirtd$' || true)
- libvirt_group=${libvirt_group:-libvirt}
- # restart local nova-compute
- sudo -H -u $STACK_USER bash -c "/tmp/start_process.sh n-cpu '/usr/local/bin/nova-compute --config-file /etc/nova/nova.conf' $libvirt_group"
-
- # restart remote nova-compute
- for SUBNODE in $SUBNODES ; do
- ssh $SUBNODE "sudo -H -u $STACK_USER bash -c '/tmp/start_process.sh n-cpu \"/usr/local/bin/nova-compute --config-file /etc/nova/nova.conf\" $libvirt_group'"
- done
+ # test that they are all running again
$ANSIBLE all --sudo -f 5 -i "$WORKSPACE/inventory" -m shell -a "ps aux | grep compute"
}
@@ -156,9 +137,10 @@ function _ceph_configure_cinder {
function configure_and_start_cinder {
_ceph_configure_cinder
- stop 'primary' 'c-vol'
- sudo -H -u $STACK_USER bash -c "/tmp/start_process.sh c-vol '/usr/local/bin/cinder-volume --config-file /etc/cinder/cinder.conf'"
+ # restart cinder
+ $ANSIBLE primary --sudo -f 5 -i "$WORKSPACE/inventory" -m shell -a "systemctl restart devstack@c-vol"
+
source $BASE/new/devstack/openrc
export OS_USERNAME=admin
diff --git a/nova/tests/live_migration/hooks/run_tests.sh b/nova/tests/live_migration/hooks/run_tests.sh
index d1ef95b89a..7f5fe39901 100755
--- a/nova/tests/live_migration/hooks/run_tests.sh
+++ b/nova/tests/live_migration/hooks/run_tests.sh
@@ -23,8 +23,6 @@ SUBNODES=$(cat /etc/nodepool/sub_nodes_private)
SERVICE_HOST=$primary_node
STACK_USER=${STACK_USER:-stack}
-populate_start_script
-
echo '1. test with all local storage (use default for volumes)'
echo 'NOTE: test_volume_backed_live_migration is skipped due to https://bugs.launchpad.net/nova/+bug/1524898'
run_tempest "block migration test" "^.*test_live_migration(?!.*(test_volume_backed_live_migration))"
diff --git a/nova/tests/live_migration/hooks/utils.sh b/nova/tests/live_migration/hooks/utils.sh
index 2542521503..9f98ca2e25 100755
--- a/nova/tests/live_migration/hooks/utils.sh
+++ b/nova/tests/live_migration/hooks/utils.sh
@@ -9,65 +9,3 @@ function run_tempest {
die $LINENO "$message failure"
fi
}
-
-function populate_start_script {
- SCREEN_NAME=${SCREEN_NAME:-stack}
- DEST=${DEST:-/opt/stack}
- SERVICE_DIR=${SERVICE_DIR:-${DEST}/status}
- ENABLED_SERVICES=${ENABLED_SERVICES:-n-cpu,g-api,c-vol}
- LIBVIRT_GROUP=${LIBVIRT_GROUP:-libvirtd}
- TIMESTAMP_FORMAT=${TIMESTAMP_FORMAT:-"%F-%H%M%S"}
- LOGDAYS=${LOGDAYS:-7}
- CURRENT_LOG_TIME=$(date "+$TIMESTAMP_FORMAT")
-
- #creates script for starting process without screen and copies it to all
- # nodes
- #
- # args:
- # $1 - service name to start
- # $2 - command to execute
- # $3 - group to run under
- cat > /tmp/start_process.sh <<EOF
-set -x
-service=\$1
-command=\$2
-sg=\$3
-ENABLED_SERVICES=$ENABLED_SERVICES
-SCREEN_NAME=$SCREEN_NAME
-DEST=$DEST
-SERVICE_DIR=$SERVICE_DIR
-LOGDIR=$DEST/logs
-TIMESTAMP_FORMAT=$TIMESTAMP_FORMAT
-LOGDAYS=$LOGDAYS
-CURRENT_LOG_TIME=\$(date "+$TIMESTAMP_FORMAT")
-REAL_LOG_FILE="\$LOGDIR/\$service.log.\$CURRENT_LOG_TIME"
-if [[ -n "\$LOGDIR" ]]; then
- exec 1>&"\$REAL_LOG_FILE" 2>&1
- ln -sf "\$REAL_LOG_FILE" \$LOGDIR/\$service.log
- export PYTHONUNBUFFERED=1
-fi
-if [[ -n "\$sg" ]]; then
- setsid sg \$sg -c "\$command" & echo \$! >\$SERVICE_DIR/\$SCREEN_NAME/\$service.pid
-else
- setsid \$command & echo \$! >\$SERVICE_DIR/\$SCREEN_NAME/\$service.pid
-fi
-exit 0
-EOF
- chmod +x /tmp/start_process.sh
- $ANSIBLE subnodes --sudo -f 5 -i "$WORKSPACE/inventory" -m copy -a "src=/tmp/start_process.sh dest=/tmp/start_process.sh owner=$STACK_USER group=$STACK_USER mode=0777"
- $ANSIBLE subnodes --sudo -f 5 -i "$WORKSPACE/inventory" -m shell -a "ls -la /tmp/start_process.sh"
-}
-
-function stop {
- local target=$1
- local service=$2
- $ANSIBLE $target --sudo -f 5 -i "$WORKSPACE/inventory" -m shell -a "
-executable=/bin/bash
-BASE\=$BASE
-source $BASE/new/devstack/functions-common
-ENABLED_SERVICES\=$ENABLED_SERVICES
-SCREEN_NAME\=$SCREEN_NAME
-SERVICE_DIR\=$SERVICE_DIR
-stop_process $service
-"
-}
diff --git a/nova/tests/unit/api/openstack/compute/test_cloudpipe.py b/nova/tests/unit/api/openstack/compute/test_cloudpipe.py
index 0197bedba6..5fa5d4ea42 100644
--- a/nova/tests/unit/api/openstack/compute/test_cloudpipe.py
+++ b/nova/tests/unit/api/openstack/compute/test_cloudpipe.py
@@ -15,38 +15,14 @@
import uuid as uuid_lib
-import mock
-from oslo_utils import timeutils
from webob import exc
from nova.api.openstack.compute import cloudpipe as cloudpipe_v21
-from nova.compute import utils as compute_utils
-import nova.conf
-from nova import exception
-from nova import objects
from nova import test
from nova.tests.unit.api.openstack import fakes
-from nova.tests.unit import fake_network
-from nova.tests.unit import matchers
-from nova.tests import uuidsentinel as uuids
-from nova import utils
-
-CONF = nova.conf.CONF
project_id = str(uuid_lib.uuid4().hex)
-uuid = uuids.fake
-
-
-def fake_vpn_instance():
- return objects.Instance(
- id=7, image_ref=CONF.cloudpipe.vpn_image_id, vm_state='active',
- created_at=timeutils.parse_strtime('1981-10-20T00:00:00.000000'),
- uuid=uuid, project_id=project_id)
-
-
-def compute_api_get_all(context, search_opts=None):
- return [fake_vpn_instance()]
class CloudpipeTestV21(test.NoDBTestCase):
@@ -58,141 +34,15 @@ class CloudpipeTestV21(test.NoDBTestCase):
self.controller = self.cloudpipe.CloudpipeController()
self.req = fakes.HTTPRequest.blank('')
- def test_cloudpipe_list_no_network(self):
- with test.nested(
- mock.patch.object(compute_utils, 'get_nw_info_for_instance',
- return_value={}),
- mock.patch.object(self.controller.compute_api, "get_all",
- side_effect=compute_api_get_all)
- ) as (mock_utils_get_nw, mock_cpu_get_all):
- res_dict = self.controller.index(self.req)
- response = {'cloudpipes': [{'project_id': project_id,
- 'instance_id': uuid,
- 'created_at': '1981-10-20T00:00:00Z'}]}
-
- self.assertEqual(response, res_dict)
- self.assertTrue(mock_cpu_get_all.called)
- self.assertTrue(mock_utils_get_nw.called)
-
def test_cloudpipe_list(self):
-
- def network_api_get(context, network_id):
- self.assertEqual(context.project_id, project_id)
- return {'vpn_public_address': '127.0.0.1',
- 'vpn_public_port': 22}
-
- def fake_get_nw_info_for_instance(instance):
- return fake_network.fake_get_instance_nw_info(self)
-
- with test.nested(
- mock.patch.object(utils, 'vpn_ping', return_value=True),
- mock.patch.object(compute_utils, 'get_nw_info_for_instance',
- side_effect=fake_get_nw_info_for_instance),
- mock.patch.object(self.controller.network_api, "get",
- side_effect=network_api_get),
- mock.patch.object(self.controller.compute_api, "get_all",
- side_effect=compute_api_get_all)
- ) as (mock_vpn_ping, mock_utils_get, mock_nw_get, mock_cpu_get_all):
- res_dict = self.controller.index(self.req)
- response = {'cloudpipes': [{'project_id': project_id,
- 'internal_ip': '192.168.1.100',
- 'public_ip': '127.0.0.1',
- 'public_port': 22,
- 'state': 'running',
- 'instance_id': uuid,
- 'created_at': '1981-10-20T00:00:00Z'}]}
-
- self.assertThat(response, matchers.DictMatches(res_dict))
- self.assertTrue(mock_cpu_get_all.called)
- self.assertTrue(mock_nw_get.called)
- self.assertTrue(mock_utils_get.called)
- self.assertTrue(mock_vpn_ping.called)
+ self.assertRaises(exc.HTTPGone, self.controller.index, self.req)
def test_cloudpipe_create(self):
- def _launch_vpn_instance(context):
- return ([fake_vpn_instance()], 'fake-reservation')
-
- with test.nested(
- mock.patch.object(self.controller.compute_api, "get_all",
- return_value=[]),
- mock.patch.object(self.controller.cloudpipe,
- 'launch_vpn_instance',
- side_effect=_launch_vpn_instance),
- ) as (mock_cpu_get_all, mock_vpn_launch):
- body = {'cloudpipe': {'project_id': project_id}}
- res_dict = self.controller.create(self.req, body=body)
- response = {'instance_id': uuid}
-
- self.assertEqual(response, res_dict)
- self.assertTrue(mock_cpu_get_all.called)
- self.assertTrue(mock_vpn_launch.called)
-
- def test_cloudpipe_create_no_networks(self):
- with test.nested(
- mock.patch.object(self.controller.compute_api, "get_all",
- return_value=[]),
- mock.patch.object(self.controller.cloudpipe,
- 'launch_vpn_instance',
- side_effect=exception.NoMoreNetworks),
- ) as (mock_cpu_get_all, mock_vpn_launch):
- body = {'cloudpipe': {'project_id': project_id}}
- req = fakes.HTTPRequest.blank(self.url)
-
- self.assertRaises(exc.HTTPBadRequest,
- self.controller.create, req, body=body)
- self.assertTrue(mock_cpu_get_all.called)
- self.assertTrue(mock_vpn_launch.called)
-
- def test_cloudpipe_create_already_running(self):
- with test.nested(
- mock.patch.object(self.controller.cloudpipe,
- 'launch_vpn_instance'),
- mock.patch.object(self.controller.compute_api, "get_all",
- side_effect=compute_api_get_all),
- ) as (mock_vpn_launch, mock_cpu_get_all):
- body = {'cloudpipe': {'project_id': project_id}}
- req = fakes.HTTPRequest.blank(self.url)
- res_dict = self.controller.create(req, body=body)
- response = {'instance_id': uuid}
-
- self.assertEqual(response, res_dict)
- # cloudpipe.launch_vpn_instance() should not be called
- self.assertFalse(mock_vpn_launch.called)
- self.assertTrue(mock_cpu_get_all.called)
-
- def test_cloudpipe_create_with_bad_project_id_failed(self):
- body = {'cloudpipe': {'project_id': 'bad.project.id'}}
- req = fakes.HTTPRequest.blank(self.url)
- self.assertRaises(exception.ValidationError,
- self.controller.create, req, body=body)
-
-
-class CloudpipePolicyEnforcementV21(test.NoDBTestCase):
-
- def setUp(self):
- super(CloudpipePolicyEnforcementV21, self).setUp()
- self.controller = cloudpipe_v21.CloudpipeController()
- self.req = fakes.HTTPRequest.blank('')
-
- def _common_policy_check(self, func, *arg, **kwarg):
- rule_name = "os_compute_api:os-cloudpipe"
- rule = {rule_name: "project:non_fake"}
- self.policy.set_rules(rule)
- exc = self.assertRaises(
- exception.PolicyNotAuthorized, func, *arg, **kwarg)
- self.assertEqual(
- "Policy doesn't allow %s to be performed." % rule_name,
- exc.format_message())
-
- def test_list_policy_failed(self):
- self._common_policy_check(self.controller.index, self.req)
-
- def test_create_policy_failed(self):
- body = {'cloudpipe': {'project_id': uuid}}
- self._common_policy_check(self.controller.create, self.req, body=body)
-
- def test_update_policy_failed(self):
- body = {"configure_project": {'vpn_ip': '192.168.1.1',
- 'vpn_port': 2000}}
- self._common_policy_check(
- self.controller.update, self.req, uuid, body=body)
+ body = {'cloudpipe': {'project_id': project_id}}
+ self.assertRaises(exc.HTTPGone, self.controller.create,
+ self.req, body=body)
+
+ def test_cloudpipe_configure_project(self):
+ body = {"configure_project": {"vpn_ip": "1.2.3.4", "vpn_port": 222}}
+ self.assertRaises(exc.HTTPGone, self.controller.update,
+ self.req, 'configure-project', body=body)
diff --git a/nova/tests/unit/api/openstack/compute/test_cloudpipe_update.py b/nova/tests/unit/api/openstack/compute/test_cloudpipe_update.py
index a49a7b1540..64d9b13b07 100644
--- a/nova/tests/unit/api/openstack/compute/test_cloudpipe_update.py
+++ b/nova/tests/unit/api/openstack/compute/test_cloudpipe_update.py
@@ -15,74 +15,21 @@
import webob
from nova.api.openstack.compute import cloudpipe as clup_v21
-from nova import exception
from nova import test
from nova.tests.unit.api.openstack import fakes
-from nova.tests.unit import fake_network
-
-
-fake_networks = [fake_network.fake_network(1),
- fake_network.fake_network(2)]
-
-
-def fake_project_get_networks(context, project_id, associate=True):
- return fake_networks
-
-
-def fake_network_update(context, network_id, values):
- for network in fake_networks:
- if network['id'] == network_id:
- for key in values:
- network[key] = values[key]
class CloudpipeUpdateTestV21(test.NoDBTestCase):
- bad_request = exception.ValidationError
def setUp(self):
super(CloudpipeUpdateTestV21, self).setUp()
- self.stub_out("nova.db.project_get_networks",
- fake_project_get_networks)
- self.stub_out("nova.db.network_update", fake_network_update)
- self._setup()
- self.req = fakes.HTTPRequest.blank('')
-
- def _setup(self):
self.controller = clup_v21.CloudpipeController()
+ self.req = fakes.HTTPRequest.blank('')
def _check_status(self, expected_status, res, controller_method):
self.assertEqual(expected_status, controller_method.wsgi_code)
def test_cloudpipe_configure_project(self):
body = {"configure_project": {"vpn_ip": "1.2.3.4", "vpn_port": 222}}
- result = self.controller.update(self.req, 'configure-project',
- body=body)
- self._check_status(202, result, self.controller.update)
- self.assertEqual(fake_networks[0]['vpn_public_address'], "1.2.3.4")
- self.assertEqual(fake_networks[0]['vpn_public_port'], 222)
-
- def test_cloudpipe_configure_project_bad_url(self):
- body = {"configure_project": {"vpn_ip": "1.2.3.4", "vpn_port": 222}}
- self.assertRaises(webob.exc.HTTPBadRequest,
- self.controller.update, self.req,
- 'configure-projectx', body=body)
-
- def test_cloudpipe_configure_project_bad_data(self):
- body = {"configure_project": {"vpn_ipxx": "1.2.3.4", "vpn_port": 222}}
- self.assertRaises(self.bad_request,
- self.controller.update, self.req,
- 'configure-project', body=body)
-
- def test_cloudpipe_configure_project_bad_vpn_port(self):
- body = {"configure_project": {"vpn_ipxx": "1.2.3.4",
- "vpn_port": "foo"}}
- self.assertRaises(self.bad_request,
- self.controller.update, self.req,
- 'configure-project', body=body)
-
- def test_cloudpipe_configure_project_vpn_port_with_empty_string(self):
- body = {"configure_project": {"vpn_ipxx": "1.2.3.4",
- "vpn_port": ""}}
- self.assertRaises(self.bad_request,
- self.controller.update, self.req,
- 'configure-project', body=body)
+ self.assertRaises(webob.exc.HTTPGone, self.controller.update,
+ self.req, 'configure-project', body=body)
diff --git a/nova/tests/unit/api/openstack/compute/test_flavor_access.py b/nova/tests/unit/api/openstack/compute/test_flavor_access.py
index d600582137..2495eb1fda 100644
--- a/nova/tests/unit/api/openstack/compute/test_flavor_access.py
+++ b/nova/tests/unit/api/openstack/compute/test_flavor_access.py
@@ -383,6 +383,34 @@ class FlavorAccessTestV21(test.NoDBTestCase):
self.assertRaises(self.validation_ex,
remove_access, req, '2', body=body)
+ @mock.patch('nova.api.openstack.identity.verify_project_id',
+ side_effect=exc.HTTPBadRequest(
+ explanation="Project ID proj2 is not a valid project."))
+ def test_add_tenant_access_with_invalid_tenant(self, mock_verify):
+ """Tests the case that the tenant does not exist in Keystone."""
+ req = fakes.HTTPRequest.blank(self._prefix + '/flavors/2/action',
+ use_admin_context=True)
+ body = {'addTenantAccess': {'tenant': 'proj2'}}
+ self.assertRaises(exc.HTTPBadRequest,
+ self.flavor_action_controller._add_tenant_access,
+ req, '2', body=body)
+ mock_verify.assert_called_once_with(
+ req.environ['nova.context'], 'proj2')
+
+ @mock.patch('nova.api.openstack.identity.verify_project_id',
+ side_effect=exc.HTTPBadRequest(
+ explanation="Project ID proj2 is not a valid project."))
+ def test_remove_tenant_access_with_invalid_tenant(self, mock_verify):
+ """Tests the case that the tenant does not exist in Keystone."""
+ req = fakes.HTTPRequest.blank(self._prefix + '/flavors/2/action',
+ use_admin_context=True)
+ body = {'removeTenantAccess': {'tenant': 'proj2'}}
+ self.assertRaises(exc.HTTPBadRequest,
+ self.flavor_action_controller._remove_tenant_access,
+ req, '2', body=body)
+ mock_verify.assert_called_once_with(
+ req.environ['nova.context'], 'proj2')
+
class FlavorAccessPolicyEnforcementV21(test.NoDBTestCase):
diff --git a/nova/tests/unit/api/openstack/compute/test_hosts.py b/nova/tests/unit/api/openstack/compute/test_hosts.py
index 902a473cd8..308202e8b1 100644
--- a/nova/tests/unit/api/openstack/compute/test_hosts.py
+++ b/nova/tests/unit/api/openstack/compute/test_hosts.py
@@ -378,7 +378,7 @@ class HostTestCaseV21(test.TestCase):
s_ref = self._create_compute_service()
with mock.patch.object(self.controller.api,
'instance_get_all_by_host') as m:
- m.side_effect = exception.HostMappingNotFound
+ m.side_effect = exception.HostMappingNotFound(name='something')
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.show, self.req, s_ref['host'])
diff --git a/nova/tests/unit/api/openstack/compute/test_hypervisors.py b/nova/tests/unit/api/openstack/compute/test_hypervisors.py
index 04304cd49e..29e7303d12 100644
--- a/nova/tests/unit/api/openstack/compute/test_hypervisors.py
+++ b/nova/tests/unit/api/openstack/compute/test_hypervisors.py
@@ -639,7 +639,7 @@ class HypervisorsTestV21(test.NoDBTestCase):
req = self._get_request(True)
with mock.patch.object(self.controller.host_api,
'instance_get_all_by_host') as m:
- m.side_effect = exception.HostMappingNotFound
+ m.side_effect = exception.HostMappingNotFound(name='something')
self.assertRaises(exc.HTTPNotFound,
self.controller.servers, req, 'hyper')
diff --git a/nova/tests/unit/api/openstack/compute/test_services.py b/nova/tests/unit/api/openstack/compute/test_services.py
index 0be82436a5..41ea739f1c 100644
--- a/nova/tests/unit/api/openstack/compute/test_services.py
+++ b/nova/tests/unit/api/openstack/compute/test_services.py
@@ -494,7 +494,7 @@ class ServicesTestV21(test.TestCase):
body = {'host': 'invalid', 'binary': 'nova-compute'}
with mock.patch.object(self.controller.host_api,
'service_update') as m:
- m.side_effect = exception.HostMappingNotFound
+ m.side_effect = exception.HostMappingNotFound(name='something')
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.update,
self.req,
diff --git a/nova/tests/unit/api/openstack/test_common.py b/nova/tests/unit/api/openstack/test_common.py
index c696de13d5..15330c16bd 100644
--- a/nova/tests/unit/api/openstack/test_common.py
+++ b/nova/tests/unit/api/openstack/test_common.py
@@ -170,7 +170,7 @@ class SortParamUtilsTest(test.NoDBTestCase):
self.assertEqual(['desc'], sort_dirs)
def test_get_sort_params_override_defaults(self):
- '''Verifies that the defaults can be overriden.'''
+ '''Verifies that the defaults can be overridden.'''
sort_keys, sort_dirs = common.get_sort_params({}, default_key='key1',
default_dir='dir1')
self.assertEqual(['key1'], sort_keys)
diff --git a/nova/tests/unit/compute/test_compute.py b/nova/tests/unit/compute/test_compute.py
index 8cd31e2c61..e93316df23 100644
--- a/nova/tests/unit/compute/test_compute.py
+++ b/nova/tests/unit/compute/test_compute.py
@@ -23,7 +23,6 @@ import operator
import sys
import time
import traceback
-import uuid
import ddt
@@ -397,12 +396,17 @@ class ComputeVolumeTestCase(BaseTestCase):
self.compute.attach_volume(self.context, instance, bdm=fake_bdm)
self.assertEqual(self.cinfo.get('serial'), uuids.volume_id)
- def test_attach_volume_raises(self):
+ @mock.patch('nova.context.RequestContext.elevated')
+ @mock.patch('nova.compute.utils.notify_about_volume_attach_detach')
+ def test_attach_volume_raises(self, mock_notify, mock_elevate):
+ mock_elevate.return_value = self.context
+
fake_bdm = objects.BlockDeviceMapping(**self.fake_volume)
instance = self._create_fake_instance_obj()
+ expected_exception = test.TestingException()
def fake_attach(*args, **kwargs):
- raise test.TestingException
+ raise expected_exception
with test.nested(
mock.patch.object(driver_block_device.DriverVolumeBlockDevice,
@@ -417,6 +421,15 @@ class ComputeVolumeTestCase(BaseTestCase):
self.context, instance, fake_bdm)
self.assertTrue(mock_unreserve.called)
self.assertTrue(mock_destroy.called)
+ mock_notify.assert_has_calls([
+ mock.call(self.context, instance, 'fake-mini',
+ action='volume_attach', phase='start',
+ volume_id=uuids.volume_id),
+ mock.call(self.context, instance, 'fake-mini',
+ action='volume_attach', phase='error',
+ volume_id=uuids.volume_id,
+ exception=expected_exception),
+ ])
def test_detach_volume_api_raises(self):
fake_bdm = objects.BlockDeviceMapping(**self.fake_volume)
@@ -747,6 +760,8 @@ class ComputeVolumeTestCase(BaseTestCase):
mock_get_bdms.assert_called_once_with(ctxt, use_slave=True)
mock_update.assert_called_once_with(ctxt, [3, 4])
+ @mock.patch('nova.context.RequestContext.elevated')
+ @mock.patch('nova.compute.utils.notify_about_volume_attach_detach')
@mock.patch.object(objects.BlockDeviceMapping,
'get_by_volume_and_instance')
@mock.patch.object(fake.FakeDriver, 'block_stats')
@@ -754,7 +769,9 @@ class ComputeVolumeTestCase(BaseTestCase):
@mock.patch.object(fake.FakeDriver, 'get_all_volume_usage')
@mock.patch.object(fake.FakeDriver, 'instance_exists')
def test_detach_volume_usage(self, mock_exists, mock_get_all,
- mock_get_bdms, mock_stats, mock_get):
+ mock_get_bdms, mock_stats, mock_get,
+ mock_notify, mock_elevate):
+ mock_elevate.return_value = self.context
# Test that detach volume update the volume usage cache table correctly
instance = self._create_fake_instance_obj()
bdm = objects.BlockDeviceMapping(context=self.context,
@@ -830,6 +847,21 @@ class ComputeVolumeTestCase(BaseTestCase):
self.assertEqual(1, volume_usage['tot_writes'])
self.assertEqual(20, volume_usage['tot_write_bytes'])
+ mock_notify.assert_has_calls([
+ mock.call(self.context, instance, 'fake-mini',
+ action='volume_attach', phase='start',
+ volume_id=uuids.volume_id),
+ mock.call(self.context, instance, 'fake-mini',
+ action='volume_attach', phase='end',
+ volume_id=uuids.volume_id),
+ mock.call(self.context, instance, 'fake-mini',
+ action='volume_detach', phase='start',
+ volume_id=uuids.volume_id),
+ mock.call(self.context, instance, 'fake-mini',
+ action='volume_detach', phase='end',
+ volume_id=uuids.volume_id),
+ ])
+
mock_get.assert_called_once_with(self.context, uuids.volume_id,
instance.uuid)
mock_stats.assert_called_once_with(instance, 'vdb')
@@ -1292,7 +1324,7 @@ class ComputeVolumeTestCase(BaseTestCase):
self.instance_object, 'fake_id', 'fake_id2', {})
@mock.patch.object(cinder.API, 'create',
- side_effect=exception.OverQuota(overs='volumes'))
+ side_effect=exception.OverQuota(overs='something'))
def test_prep_block_device_over_quota_failure(self, mock_create):
instance = self._create_fake_instance_obj()
bdms = [
@@ -1309,7 +1341,7 @@ class ComputeVolumeTestCase(BaseTestCase):
})]
bdms = block_device_obj.block_device_make_list_from_dicts(
self.context, bdms)
- self.assertRaises(exception.VolumeLimitExceeded,
+ self.assertRaises(exception.OverQuota,
compute_manager.ComputeManager()._prep_block_device,
self.context, instance, bdms)
self.assertTrue(mock_create.called)
@@ -6187,6 +6219,10 @@ class ComputeTestCase(BaseTestCase):
update_available_resource.assert_has_calls([mock.call(c)])
self.assertEqual('completed', migration_obj.status)
mig_save.assert_called_once_with()
+ # assert we logged a success message
+ self.assertIn(
+ 'Migrating instance to desthost finished successfully.',
+ self.stdlog.logger.output)
def test_post_live_migration_exc_on_dest_works_correctly(self):
"""Confirm that post_live_migration() completes successfully
@@ -6238,6 +6274,10 @@ class ComputeTestCase(BaseTestCase):
migrate_data=migrate_data)
update_available_resource.assert_has_calls([mock.call(c)])
self.assertEqual('completed', migration_obj.status)
+ # assert we did not log a success message
+ self.assertNotIn(
+ 'Migrating instance to desthost finished successfully.',
+ self.stdlog.logger.output)
def test_post_live_migration_terminate_volume_connections(self):
c = context.get_admin_context()
@@ -10605,7 +10645,7 @@ class ComputeAPITestCase(BaseTestCase):
mock_migration.side_effect = fake_migrations
migrations = self.compute_api.get_migrations(self.context,
- filters)
+ filters)
self.assertEqual(1, len(migrations))
self.assertEqual(migrations[0].id, migration['id'])
mock_migration.assert_called_once_with(self.context, filters)
@@ -10615,23 +10655,22 @@ class ComputeAPITestCase(BaseTestCase):
migration = test_migration.fake_db_migration(
instance_uuid=uuids.instance)
mock_get.return_value = [migration]
- db.migration_get_in_progress_by_instance(self.context,
- uuids.instance)
migrations = self.compute_api.get_migrations_in_progress_by_instance(
self.context, uuids.instance)
self.assertEqual(1, len(migrations))
self.assertEqual(migrations[0].id, migration['id'])
+ mock_get.assert_called_once_with(self.context, uuids.instance, None)
@mock.patch("nova.db.migration_get_by_id_and_instance")
def test_get_migration_by_id_and_instance(self, mock_get):
migration = test_migration.fake_db_migration(
instance_uuid=uuids.instance)
mock_get.return_value = migration
- db.migration_get_by_id_and_instance(
- self.context, migration['id'], uuid)
res = self.compute_api.get_migration_by_id_and_instance(
self.context, migration['id'], uuids.instance)
self.assertEqual(res.id, migration['id'])
+ mock_get.assert_called_once_with(self.context, migration['id'],
+ uuids.instance)
class ComputeAPIIpFilterTestCase(test.NoDBTestCase):
diff --git a/nova/tests/unit/compute/test_compute_api.py b/nova/tests/unit/compute/test_compute_api.py
index e7fc89c1ea..03fce42235 100644
--- a/nova/tests/unit/compute/test_compute_api.py
+++ b/nova/tests/unit/compute/test_compute_api.py
@@ -846,9 +846,11 @@ class _ComputeAPIUnitTestMixIn(object):
inst = self._create_instance_obj()
inst.update(attrs)
inst._context = self.context
+ vram_mb = int(inst.flavor.get('extra_specs',
+ {}).get(compute_api.VIDEO_RAM, 0))
deltas = {'instances': -1,
'cores': -inst.flavor.vcpus,
- 'ram': -inst.flavor.memory_mb}
+ 'ram': -(inst.flavor.memory_mb + vram_mb)}
delete_time = datetime.datetime(1955, 11, 5, 9, 30,
tzinfo=iso8601.iso8601.Utc())
self.useFixture(utils_fixture.TimeFixture(delete_time))
@@ -995,6 +997,12 @@ class _ComputeAPIUnitTestMixIn(object):
def test_delete_in_resized(self):
self._test_delete('delete', vm_state=vm_states.RESIZED)
+ def test_delete_with_vram(self):
+ flavor = objects.Flavor(vcpus=1, memory_mb=512,
+ extra_specs={compute_api.VIDEO_RAM: "64"})
+ self._test_delete('delete',
+ flavor=flavor)
+
def test_delete_shelved(self):
fake_sys_meta = {'shelved_image_id': SHELVED_IMAGE}
self._test_delete('delete',
@@ -1175,7 +1183,8 @@ class _ComputeAPIUnitTestMixIn(object):
delete_on_termination=True,
connection_info=jsonutils.dumps(
conn_info
- ))
+ ),
+ attachment_id=None,)
loc_bdm = objects.BlockDeviceMapping(self.context, id=2,
instance_uuid=inst.uuid,
volume_id=uuids.volume_id2,
@@ -1200,6 +1209,39 @@ class _ComputeAPIUnitTestMixIn(object):
do_test(self)
+ @mock.patch.object(objects.BlockDeviceMapping, 'destroy')
+ def test_local_cleanup_bdm_volumes_with_attach_id(self, mock_destroy):
+ """Tests that we call volume_api.attachment_delete when we have an
+ attachment_id in the bdm.
+ """
+ instance = self._create_instance_obj()
+ conn_info = {'connector': {'host': instance.host}}
+ vol_bdm = objects.BlockDeviceMapping(
+ self.context,
+ id=1,
+ instance_uuid=instance.uuid,
+ volume_id=uuids.volume_id,
+ source_type='volume',
+ destination_type='volume',
+ delete_on_termination=True,
+ connection_info=jsonutils.dumps(conn_info),
+ attachment_id=uuids.attachment_id)
+ bdms = objects.BlockDeviceMappingList(objects=[vol_bdm])
+
+ @mock.patch.object(self.compute_api.volume_api, 'delete')
+ @mock.patch.object(self.compute_api.volume_api, 'attachment_delete')
+ @mock.patch.object(self.context, 'elevated', return_value=self.context)
+ def do_test(self, mock_elevated, mock_attach_delete, mock_delete):
+ self.compute_api._local_cleanup_bdm_volumes(
+ bdms, instance, self.context)
+
+ mock_attach_delete.assert_called_once_with(
+ self.context, vol_bdm.attachment_id)
+ mock_delete.assert_called_once_with(
+ self.context, vol_bdm.volume_id)
+ mock_destroy.assert_called_once_with()
+ do_test(self)
+
def test_get_stashed_volume_connector_none(self):
inst = self._create_instance_obj()
# connection_info isn't set
diff --git a/nova/tests/unit/compute/test_compute_mgr.py b/nova/tests/unit/compute/test_compute_mgr.py
index c0e9f27a53..fa88fade67 100644
--- a/nova/tests/unit/compute/test_compute_mgr.py
+++ b/nova/tests/unit/compute/test_compute_mgr.py
@@ -2451,8 +2451,9 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
@mock.patch.object(driver_bdm_volume, 'detach')
@mock.patch('nova.compute.manager.ComputeManager.'
'_notify_about_instance_usage')
- def _test_detach_volume(self, notify_inst_usage, detach,
- bdm_get, destroy_bdm=True):
+ @mock.patch('nova.compute.utils.notify_about_volume_attach_detach')
+ def _test_detach_volume(self, mock_notify_attach_detach, notify_inst_usage,
+ detach, bdm_get, destroy_bdm=True):
# TODO(lyarwood): Move into ../virt/test_block_device.py
volume_id = uuids.volume
inst_obj = mock.Mock()
@@ -2488,6 +2489,15 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
else:
self.assertFalse(bdm_destroy.called)
+ mock_notify_attach_detach.assert_has_calls([
+ mock.call(self.context, inst_obj, 'fake-mini',
+ action='volume_detach', phase='start',
+ volume_id=volume_id),
+ mock.call(self.context, inst_obj, 'fake-mini',
+ action='volume_detach', phase='end',
+ volume_id=volume_id),
+ ])
+
def test_detach_volume_evacuate(self):
"""For evacuate, terminate_connection is called with original host."""
# TODO(lyarwood): Move into ../virt/test_block_device.py
@@ -2522,8 +2532,11 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
@mock.patch('nova.objects.BlockDeviceMapping.get_by_volume_and_instance')
@mock.patch('nova.compute.manager.ComputeManager.'
'_notify_about_instance_usage')
- def _test_detach_volume_evacuate(self, conn_info_str, notify_inst_usage,
- bdm_get, expected=None):
+ @mock.patch('nova.compute.utils.notify_about_volume_attach_detach')
+ def _test_detach_volume_evacuate(self, conn_info_str,
+ mock_notify_attach_detach,
+ notify_inst_usage, bdm_get,
+ expected=None):
"""Re-usable code for detach volume evacuate test cases.
:param conn_info_str: String form of the stashed connector.
@@ -2571,6 +2584,15 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
extra_usage_info={'volume_id': volume_id}
)
+ mock_notify_attach_detach.assert_has_calls([
+ mock.call(self.context, instance, 'fake-mini',
+ action='volume_detach', phase='start',
+ volume_id=volume_id),
+ mock.call(self.context, instance, 'fake-mini',
+ action='volume_detach', phase='end',
+ volume_id=volume_id),
+ ])
+
def _test_rescue(self, clean_shutdown=True):
instance = fake_instance.fake_instance_obj(
self.context, vm_state=vm_states.ACTIVE)
@@ -4151,7 +4173,8 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
def test_build_and_run_volume_encryption_not_supported(self):
self._test_build_and_run_spawn_exceptions(
- exception.VolumeEncryptionNotSupported(reason=""))
+ exception.VolumeEncryptionNotSupported(volume_type='something',
+ volume_id='something'))
def test_build_and_run_invalid_input(self):
self._test_build_and_run_spawn_exceptions(
diff --git a/nova/tests/unit/conductor/test_conductor.py b/nova/tests/unit/conductor/test_conductor.py
index 6716a79974..cc550934fa 100644
--- a/nova/tests/unit/conductor/test_conductor.py
+++ b/nova/tests/unit/conductor/test_conductor.py
@@ -2442,7 +2442,7 @@ class ConductorTaskRPCAPITestCase(_BaseTaskTestCase,
def test(self, context, instance):
return mock.sentinel.iransofaraway
- mock_im.side_effect = exc.InstanceMappingNotFound
+ mock_im.side_effect = exc.InstanceMappingNotFound(uuid='something')
ctxt = mock.MagicMock()
inst = mock.MagicMock()
self.assertEqual(mock.sentinel.iransofaraway,
diff --git a/nova/tests/unit/db/test_db_api.py b/nova/tests/unit/db/test_db_api.py
index 251a6def79..63c3764d22 100644
--- a/nova/tests/unit/db/test_db_api.py
+++ b/nova/tests/unit/db/test_db_api.py
@@ -90,7 +90,7 @@ def _reservation_get(context, uuid):
def _make_compute_node(host, node, hv_type, service_id):
compute_node_dict = dict(vcpus=2, memory_mb=1024, local_gb=2048,
- uuid=uuidsentinel.fake_compute_node,
+ uuid=uuidutils.generate_uuid(),
vcpus_used=0, memory_mb_used=0,
local_gb_used=0, free_ram_mb=1024,
free_disk_gb=2048, hypervisor_type=hv_type,
@@ -3572,6 +3572,22 @@ class ServiceTestCase(test.TestCase, ModelsObjectComparatorMixin):
self._assertEqualObjects(service1, real_service1,
ignored_keys=['compute_node'])
+ def test_service_get_by_uuid(self):
+ service1 = self._create_service({'uuid': uuidsentinel.service1_uuid})
+ self._create_service({'host': 'some_other_fake_host',
+ 'uuid': uuidsentinel.other_uuid})
+ real_service1 = db.service_get_by_uuid(
+ self.ctxt, uuidsentinel.service1_uuid)
+ self._assertEqualObjects(service1, real_service1,
+ ignored_keys=['compute_node'])
+
+ def test_service_get_by_uuid_not_found(self):
+ """Asserts that ServiceNotFound is raised if a service is not found by
+ a given uuid.
+ """
+ self.assertRaises(exception.ServiceNotFound, db.service_get_by_uuid,
+ self.ctxt, uuidsentinel.service_not_found)
+
def test_service_get_minimum_version(self):
self._create_service({'version': 1,
'host': 'host3',
@@ -3764,6 +3780,43 @@ class ServiceTestCase(test.TestCase, ModelsObjectComparatorMixin):
self.assertRaises(exception.ServiceTopicExists, db.service_create,
self.ctxt, values)
+ def test_migrate_service_uuids(self):
+ # Start with nothing.
+ total, done = db.service_uuids_online_data_migration(self.ctxt, 10)
+ self.assertEqual(0, total)
+ self.assertEqual(0, done)
+
+ # Create two services, one with a uuid and one without.
+ db.service_create(self.ctxt,
+ dict(host='host1', binary='nova-compute',
+ topic='compute', report_count=1,
+ disabled=False))
+ db.service_create(self.ctxt,
+ dict(host='host2', binary='nova-compute',
+ topic='compute', report_count=1,
+ disabled=False, uuid=uuidsentinel.host2))
+
+ # Now migrate them, we should find one and update one.
+ total, done = db.service_uuids_online_data_migration(
+ self.ctxt, 10)
+ self.assertEqual(1, total)
+ self.assertEqual(1, done)
+
+ # Get the services back to make sure the original uuid didn't change.
+ services = db.service_get_all_by_binary(self.ctxt, 'nova-compute')
+ self.assertEqual(2, len(services))
+ for service in services:
+ if service['host'] == 'host2':
+ self.assertEqual(uuidsentinel.host2, service['uuid'])
+ else:
+ self.assertIsNotNone(service['uuid'])
+
+ # Run the online migration again to see nothing was processed.
+ total, done = db.service_uuids_online_data_migration(
+ self.ctxt, 10)
+ self.assertEqual(0, total)
+ self.assertEqual(0, done)
+
class BaseInstanceTypeTestCase(test.TestCase, ModelsObjectComparatorMixin):
def setUp(self):
@@ -7787,7 +7840,7 @@ class ComputeNodeTestCase(test.TestCase, ModelsObjectComparatorMixin):
disabled=False)
self.service = db.service_create(self.ctxt, self.service_dict)
self.compute_node_dict = dict(vcpus=2, memory_mb=1024, local_gb=2048,
- uuid=uuidsentinel.fake_compute_node,
+ uuid=uuidutils.generate_uuid(),
vcpus_used=0, memory_mb_used=0,
local_gb_used=0, free_ram_mb=1024,
free_disk_gb=2048, hypervisor_type="xen",
@@ -7835,12 +7888,14 @@ class ComputeNodeTestCase(test.TestCase, ModelsObjectComparatorMixin):
cn = dict(self.compute_node_dict,
hostname='foo',
hypervisor_hostname='foo',
- mapped=None)
+ mapped=None,
+ uuid=uuidutils.generate_uuid())
db.compute_node_create(self.ctxt, cn)
cn = dict(self.compute_node_dict,
hostname='bar',
hypervisor_hostname='nar',
- mapped=3)
+ mapped=3,
+ uuid=uuidutils.generate_uuid())
db.compute_node_create(self.ctxt, cn)
cns = db.compute_node_get_all_mapped_less_than(self.ctxt, 1)
self.assertEqual(2, len(cns))
@@ -7909,6 +7964,7 @@ class ComputeNodeTestCase(test.TestCase, ModelsObjectComparatorMixin):
# Create a compute node
compute_node_data = self.compute_node_dict.copy()
+ compute_node_data['uuid'] = uuidutils.generate_uuid()
compute_node_data['service_id'] = service['id']
compute_node_data['stats'] = jsonutils.dumps(self.stats.copy())
compute_node_data['hypervisor_hostname'] = 'hypervisor-%s' % x
@@ -7943,6 +7999,7 @@ class ComputeNodeTestCase(test.TestCase, ModelsObjectComparatorMixin):
for name in ['bm_node1', 'bm_node2']:
compute_node_data = self.compute_node_dict.copy()
+ compute_node_data['uuid'] = uuidutils.generate_uuid()
compute_node_data['service_id'] = service['id']
compute_node_data['stats'] = jsonutils.dumps(self.stats)
compute_node_data['hypervisor_hostname'] = name
@@ -7964,6 +8021,7 @@ class ComputeNodeTestCase(test.TestCase, ModelsObjectComparatorMixin):
service2['host'] = 'host2'
db.service_create(self.ctxt, service2)
compute_node_another_host = self.compute_node_dict.copy()
+ compute_node_another_host['uuid'] = uuidutils.generate_uuid()
compute_node_another_host['stats'] = jsonutils.dumps(self.stats)
compute_node_another_host['hypervisor_hostname'] = 'node_2'
compute_node_another_host['host'] = 'host2'
@@ -7978,6 +8036,7 @@ class ComputeNodeTestCase(test.TestCase, ModelsObjectComparatorMixin):
def test_compute_node_get_all_by_host_with_same_host(self):
# Create another node on top of the same service
compute_node_same_host = self.compute_node_dict.copy()
+ compute_node_same_host['uuid'] = uuidutils.generate_uuid()
compute_node_same_host['stats'] = jsonutils.dumps(self.stats)
compute_node_same_host['hypervisor_hostname'] = 'node_3'
@@ -8008,6 +8067,7 @@ class ComputeNodeTestCase(test.TestCase, ModelsObjectComparatorMixin):
def test_compute_nodes_get_by_service_id_multiple_results(self):
# Create another node on top of the same service
compute_node_same_host = self.compute_node_dict.copy()
+ compute_node_same_host['uuid'] = uuidutils.generate_uuid()
compute_node_same_host['stats'] = jsonutils.dumps(self.stats)
compute_node_same_host['hypervisor_hostname'] = 'node_2'
@@ -8030,6 +8090,7 @@ class ComputeNodeTestCase(test.TestCase, ModelsObjectComparatorMixin):
def test_compute_node_get_by_host_and_nodename(self):
# Create another node on top of the same service
compute_node_same_host = self.compute_node_dict.copy()
+ compute_node_same_host['uuid'] = uuidutils.generate_uuid()
compute_node_same_host['stats'] = jsonutils.dumps(self.stats)
compute_node_same_host['hypervisor_hostname'] = 'node_2'
@@ -8089,6 +8150,7 @@ class ComputeNodeTestCase(test.TestCase, ModelsObjectComparatorMixin):
self.compute_node_dict['service_id'] = service['id']
self.compute_node_dict['hypervisor_hostname'] = 'testhost' + str(i)
self.compute_node_dict['stats'] = jsonutils.dumps(self.stats)
+ self.compute_node_dict['uuid'] = uuidutils.generate_uuid()
node = db.compute_node_create(self.ctxt, self.compute_node_dict)
nodes_created.append(node)
nodes = db.compute_node_search_by_hypervisor(self.ctxt, 'host')
@@ -8191,6 +8253,7 @@ class ComputeNodeTestCase(test.TestCase, ModelsObjectComparatorMixin):
service2['host'] = 'host2'
db_service2 = db.service_create(self.ctxt, service2)
compute_node_old_host = self.compute_node_dict.copy()
+ compute_node_old_host['uuid'] = uuidutils.generate_uuid()
compute_node_old_host['stats'] = jsonutils.dumps(self.stats)
compute_node_old_host['hypervisor_hostname'] = 'node_2'
compute_node_old_host['service_id'] = db_service2['id']
@@ -8257,6 +8320,7 @@ class ComputeNodeTestCase(test.TestCase, ModelsObjectComparatorMixin):
# This test could be removed once we are sure that all compute nodes
# are populating the host field thanks to the ResourceTracker
compute_node_old_host_dict = self.compute_node_dict.copy()
+ compute_node_old_host_dict['uuid'] = uuidutils.generate_uuid()
compute_node_old_host_dict.pop('host')
item_old = db.compute_node_create(self.ctxt,
compute_node_old_host_dict)
diff --git a/nova/tests/unit/db/test_migrations.py b/nova/tests/unit/db/test_migrations.py
index 3887a8d286..885eca13f1 100644
--- a/nova/tests/unit/db/test_migrations.py
+++ b/nova/tests/unit/db/test_migrations.py
@@ -956,6 +956,10 @@ class NovaMigrationsCheckers(test_migrations.ModelsMigrationsSync,
self.assertColumnExists(engine, 'compute_nodes', 'mapped')
self.assertColumnExists(engine, 'shadow_compute_nodes', 'mapped')
+ def _check_361(self, engine, data):
+ self.assertIndexMembers(engine, 'compute_nodes',
+ 'compute_nodes_uuid_idx', ['uuid'])
+
class TestNovaMigrationsSQLite(NovaMigrationsCheckers,
test_base.DbTestCase,
diff --git a/nova/tests/unit/fake_policy.py b/nova/tests/unit/fake_policy.py
index 9fd8a6bbd6..11729fe82c 100644
--- a/nova/tests/unit/fake_policy.py
+++ b/nova/tests/unit/fake_policy.py
@@ -27,7 +27,6 @@ policy_data = """
"os_compute_api:os-attach-interfaces": "",
"os_compute_api:os-baremetal-nodes": "",
"os_compute_api:os-cells": "",
- "os_compute_api:os-cloudpipe": "",
"os_compute_api:os-config-drive": "",
"os_compute_api:os-console-output": "",
"os_compute_api:os-remote-consoles": "",
diff --git a/nova/tests/unit/network/test_api.py b/nova/tests/unit/network/test_api.py
index 00e620146e..9cac18843e 100644
--- a/nova/tests/unit/network/test_api.py
+++ b/nova/tests/unit/network/test_api.py
@@ -317,7 +317,7 @@ class ApiTestCase(test.TestCase):
def test_is_multi_host_instance_has_no_fixed_ip(self):
with mock.patch.object(self.network_api.db, 'fixed_ip_get_by_instance',
side_effect=exception.FixedIpNotFoundForInstance(
- instance_uuid=uuid)):
+ instance_uuid=FAKE_UUID)):
instance = objects.Instance(uuid=FAKE_UUID)
result, floats = (
self.network_api._get_multi_addresses(self.context, instance))
diff --git a/nova/tests/unit/network/test_network_info.py b/nova/tests/unit/network/test_network_info.py
index 0beea4744d..f7c80d58a4 100644
--- a/nova/tests/unit/network/test_network_info.py
+++ b/nova/tests/unit/network/test_network_info.py
@@ -647,7 +647,7 @@ iface eth0 inet static
template = self._setup_injected_network_scenario(dns=False)
self.assertEqual(expected, template)
- def test_injection_static_overriden_template(self):
+ def test_injection_static_overridden_template(self):
cfg.CONF.set_override(
'injected_network_template',
'nova/tests/unit/network/interfaces-override.template')
diff --git a/nova/tests/unit/network/test_neutronv2.py b/nova/tests/unit/network/test_neutronv2.py
index 3934b033cc..18bb9c0aa2 100644
--- a/nova/tests/unit/network/test_neutronv2.py
+++ b/nova/tests/unit/network/test_neutronv2.py
@@ -3139,6 +3139,98 @@ class TestNeutronv2(TestNeutronv2Base):
self.assertEqual(0, len(networks))
@mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock())
+ def test_get_port_vnic_info_multi_segment(self, mock_get_client):
+ api = neutronapi.API()
+ self.mox.ResetAll()
+ test_port = {
+ 'port': {'id': 'my_port_id1',
+ 'network_id': 'net-id',
+ 'binding:vnic_type': model.VNIC_TYPE_DIRECT,
+ },
+ }
+ test_net = {'network': {'segments':
+ [{'provider:physical_network': 'phynet10',
+ 'provider:segmentation_id': 1000,
+ 'provider:network_type': 'vlan'},
+ {'provider:physical_network': None,
+ 'provider:segmentation_id': 153,
+ 'provider:network_type': 'vxlan'}]}}
+ test_ext_list = {'extensions':
+ [{'name': 'Multi Provider Network',
+ 'alias': 'multi-segments'}]}
+
+ mock_client = mock_get_client()
+ mock_client.show_port.return_value = test_port
+ mock_client.list_extensions.return_value = test_ext_list
+ mock_client.show_network.return_value = test_net
+ vnic_type, phynet_name = api._get_port_vnic_info(
+ self.context, mock_client, test_port['port']['id'])
+
+ mock_client.show_network.assert_called_once_with(
+ test_port['port']['network_id'],
+ fields='segments')
+ self.assertEqual('phynet10', phynet_name)
+
+ @mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock())
+ def test_get_port_vnic_info_vlan_with_multi_segment_ext(self,
+ mock_get_client):
+ api = neutronapi.API()
+ self.mox.ResetAll()
+ test_port = {
+ 'port': {'id': 'my_port_id1',
+ 'network_id': 'net-id',
+ 'binding:vnic_type': model.VNIC_TYPE_DIRECT,
+ },
+ }
+ test_net = {'network': {'provider:physical_network': 'phynet10',
+ 'provider:segmentation_id': 1000,
+ 'provider:network_type': 'vlan'}}
+ test_ext_list = {'extensions':
+ [{'name': 'Multi Provider Network',
+ 'alias': 'multi-segments'}]}
+
+ mock_client = mock_get_client()
+ mock_client.show_port.return_value = test_port
+ mock_client.list_extensions.return_value = test_ext_list
+ mock_client.show_network.return_value = test_net
+ vnic_type, phynet_name = api._get_port_vnic_info(
+ self.context, mock_client, test_port['port']['id'])
+
+ mock_client.show_network.assert_called_with(
+ test_port['port']['network_id'],
+ fields='provider:physical_network')
+ self.assertEqual('phynet10', phynet_name)
+
+ @mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock())
+ def test_get_port_vnic_info_multi_segment_no_phynet(self, mock_get_client):
+ api = neutronapi.API()
+ self.mox.ResetAll()
+ test_port = {
+ 'port': {'id': 'my_port_id1',
+ 'network_id': 'net-id',
+ 'binding:vnic_type': model.VNIC_TYPE_DIRECT,
+ },
+ }
+ test_net = {'network': {'segments':
+ [{'provider:physical_network': None,
+ 'provider:segmentation_id': 1000,
+ 'provider:network_type': 'vlan'},
+ {'provider:physical_network': None,
+ 'provider:segmentation_id': 153,
+ 'provider:network_type': 'vlan'}]}}
+ test_ext_list = {'extensions':
+ [{'name': 'Multi Provider Network',
+ 'alias': 'multi-segments'}]}
+
+ mock_client = mock_get_client()
+ mock_client.show_port.return_value = test_port
+ mock_client.list_extensions.return_value = test_ext_list
+ mock_client.show_network.return_value = test_net
+ self.assertRaises(exception.NovaException,
+ api._get_port_vnic_info,
+ self.context, mock_client, test_port['port']['id'])
+
+ @mock.patch.object(neutronapi, 'get_client', return_value=mock.MagicMock())
def test_get_port_vnic_info_1(self, mock_get_client):
api = neutronapi.API()
self.mox.ResetAll()
diff --git a/nova/tests/unit/notifications/objects/test_notification.py b/nova/tests/unit/notifications/objects/test_notification.py
index 96cca0f13c..2d6b17b2cd 100644
--- a/nova/tests/unit/notifications/objects/test_notification.py
+++ b/nova/tests/unit/notifications/objects/test_notification.py
@@ -358,6 +358,8 @@ notification_object_data = {
'FlavorPayload': '1.3-6335e626893d7df5f96f87e6731fef56',
'InstanceActionNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
'InstanceActionPayload': '1.2-b7b2481bcd0e1edcc1970ef7150df5aa',
+ 'InstanceActionVolumeNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
+ 'InstanceActionVolumePayload': '1.0-20c0dca4cfaf1a68d3e8c45e5aca3907',
'InstanceActionVolumeSwapNotification':
'1.0-a73147b93b520ff0061865849d3dfa56',
'InstanceActionVolumeSwapPayload': '1.2-d7925b763e0795f8e5c1aa0e95bd67bd',
diff --git a/nova/tests/unit/objects/test_objects.py b/nova/tests/unit/objects/test_objects.py
index 757fcffb00..45e9e121ec 100644
--- a/nova/tests/unit/objects/test_objects.py
+++ b/nova/tests/unit/objects/test_objects.py
@@ -1157,7 +1157,7 @@ object_data = {
'SecurityGroupList': '1.0-dc8bbea01ba09a2edb6e5233eae85cbc',
'SecurityGroupRule': '1.1-ae1da17b79970012e8536f88cb3c6b29',
'SecurityGroupRuleList': '1.2-0005c47fcd0fb78dd6d7fd32a1409f5b',
- 'Service': '1.21-4ff88e4cd40f3b3ce923805f29d84ee0',
+ 'Service': '1.22-8a740459ab9bf258a19c8fcb875c2d9a',
'ServiceList': '1.19-5325bce13eebcbf22edc9678285270cc',
'TaskLog': '1.0-78b0534366f29aa3eebb01860fbe18fe',
'TaskLogList': '1.0-cc8cce1af8a283b9d28b55fcd682e777',
diff --git a/nova/tests/unit/objects/test_service.py b/nova/tests/unit/objects/test_service.py
index 6632e5273a..efaf87b30f 100644
--- a/nova/tests/unit/objects/test_service.py
+++ b/nova/tests/unit/objects/test_service.py
@@ -99,6 +99,10 @@ class _TestServiceObject(object):
def test_get_by_id(self):
self._test_query('service_get', 'get_by_id', 123)
+ def test_get_by_uuid(self):
+ self._test_query('service_get_by_uuid', 'get_by_uuid',
+ uuidsentinel.service_uuid)
+
def test_get_by_host_and_topic(self):
self._test_query('service_get_by_host_and_topic',
'get_by_host_and_topic', 'fake-host', 'fake-topic')
diff --git a/nova/tests/unit/scheduler/test_ironic_host_manager.py b/nova/tests/unit/scheduler/test_ironic_host_manager.py
index af130a4112..85873ca9bc 100644
--- a/nova/tests/unit/scheduler/test_ironic_host_manager.py
+++ b/nova/tests/unit/scheduler/test_ironic_host_manager.py
@@ -114,7 +114,7 @@ class IronicHostManagerTestCase(test.NoDBTestCase):
# for ironic compute nodes we always return an empty dict
self.assertEqual({}, rv)
- # base class implementation is overriden and not called
+ # base class implementation is overridden and not called
self.assertFalse(mock_get_instance_info.called)
@mock.patch.object(host_manager.HostManager, '_get_instance_info')
diff --git a/nova/tests/unit/test_pipelib.py b/nova/tests/unit/test_pipelib.py
deleted file mode 100644
index 6748d0d3e9..0000000000
--- a/nova/tests/unit/test_pipelib.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# Copyright 2011 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import mock
-from oslo_config import cfg
-
-from nova.cloudpipe import pipelib
-from nova import context
-from nova import crypto
-from nova import test
-from nova import utils
-
-CONF = cfg.CONF
-
-
-class PipelibTest(test.TestCase):
- def setUp(self):
- super(PipelibTest, self).setUp()
- self.cloudpipe = pipelib.CloudPipe()
- self.project = "222"
- self.user = "111"
- self.context = context.RequestContext(self.user, self.project)
-
- def test_get_encoded_zip(self):
- with utils.tempdir() as tmpdir:
- self.flags(ca_path=tmpdir, group='crypto')
- crypto.ensure_ca_filesystem()
-
- ret = self.cloudpipe.get_encoded_zip(self.project)
- self.assertTrue(ret)
-
- def test_launch_vpn_instance(self):
- @mock.patch.object(self.cloudpipe.compute_api, 'create',
- return_value=lambda *a, **kw: (None, "r-fakeres"))
- def _do_test(mock_create):
- with utils.tempdir() as tmpdir:
- self.flags(ca_path=tmpdir, keys_path=tmpdir, group='crypto')
- crypto.ensure_ca_filesystem()
- self.cloudpipe.launch_vpn_instance(self.context)
-
- _do_test()
-
- def test_setup_security_group(self):
- group_name = "%s%s" % (self.project, CONF.cloudpipe.vpn_key_suffix)
-
- # First attempt, does not exist (thus its created)
- res1_group = self.cloudpipe.setup_security_group(self.context)
- self.assertEqual(res1_group, group_name)
-
- # Second attempt, it exists in the DB
- res2_group = self.cloudpipe.setup_security_group(self.context)
- self.assertEqual(res1_group, res2_group)
-
- def test_setup_key_pair(self):
- key_name = "%s%s" % (self.project, CONF.cloudpipe.vpn_key_suffix)
- with utils.tempdir() as tmpdir:
- self.flags(keys_path=tmpdir, group='crypto')
-
- # First attempt, key does not exist (thus it is generated)
- res1_key = self.cloudpipe.setup_key_pair(self.context)
- self.assertEqual(res1_key, key_name)
-
- # Second attempt, it exists in the DB
- res2_key = self.cloudpipe.setup_key_pair(self.context)
- self.assertEqual(res2_key, res1_key)
diff --git a/nova/tests/unit/test_policy.py b/nova/tests/unit/test_policy.py
index 238e818ff5..329a659081 100644
--- a/nova/tests/unit/test_policy.py
+++ b/nova/tests/unit/test_policy.py
@@ -291,7 +291,6 @@ class RealRolePolicyTestCase(test.NoDBTestCase):
"os_compute_api:os-cells:delete",
"os_compute_api:os-cells:update",
"os_compute_api:os-cells:sync_instances",
-"os_compute_api:os-cloudpipe",
"os_compute_api:os-evacuate",
"os_compute_api:os-extended-server-attributes",
"os_compute_api:os-fixed-ips",
diff --git a/nova/tests/unit/test_utils.py b/nova/tests/unit/test_utils.py
index 8090301aa0..cbd13c3055 100644
--- a/nova/tests/unit/test_utils.py
+++ b/nova/tests/unit/test_utils.py
@@ -38,6 +38,7 @@ import nova
from nova import context
from nova import exception
from nova.objects import base as obj_base
+from nova.objects import instance as instance_obj
from nova import test
from nova.tests.unit.objects import test_objects
from nova.tests.unit import utils as test_utils
@@ -177,17 +178,6 @@ class GenericUtilsTestCase(test.NoDBTestCase):
self.assertEqual('&lt;', utils.xhtml_escape('<'))
self.assertEqual('&lt;foo&gt;', utils.xhtml_escape('<foo>'))
- def test_is_valid_ipv6_cidr(self):
- self.assertTrue(utils.is_valid_ipv6_cidr("2600::/64"))
- self.assertTrue(utils.is_valid_ipv6_cidr(
- "abcd:ef01:2345:6789:abcd:ef01:192.168.254.254/48"))
- self.assertTrue(utils.is_valid_ipv6_cidr(
- "0000:0000:0000:0000:0000:0000:0000:0001/32"))
- self.assertTrue(utils.is_valid_ipv6_cidr(
- "0000:0000:0000:0000:0000:0000:0000:0001"))
- self.assertFalse(utils.is_valid_ipv6_cidr("foo"))
- self.assertFalse(utils.is_valid_ipv6_cidr("127.0.0.1"))
-
def test_get_shortened_ipv6(self):
self.assertEqual("abcd:ef01:2345:6789:abcd:ef01:c0a8:fefe",
utils.get_shortened_ipv6(
@@ -243,6 +233,14 @@ class GenericUtilsTestCase(test.NoDBTestCase):
self.assertEqual(
value, utils.get_hash_str(base_unicode))
+ def test_get_obj_repr_unicode(self):
+ instance = instance_obj.Instance()
+ instance.display_name = u'\u00CD\u00F1st\u00E1\u00F1c\u00E9'
+ # should be a bytes string if python2 before conversion
+ self.assertIs(str, type(repr(instance)))
+ self.assertIs(six.text_type,
+ type(utils.get_obj_repr_unicode(instance)))
+
def test_use_rootwrap(self):
self.flags(disable_rootwrap=False, group='workarounds')
self.flags(rootwrap_config='foo')
diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py
index 2e7df3d66d..5dffc85685 100644
--- a/nova/tests/unit/virt/libvirt/test_driver.py
+++ b/nova/tests/unit/virt/libvirt/test_driver.py
@@ -5959,10 +5959,11 @@ class LibvirtConnTestCase(test.NoDBTestCase):
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
self.assertTrue(drvr.instance_exists(None))
- mock_get_guest.side_effect = exception.InstanceNotFound
+ mock_get_guest.side_effect = exception.InstanceNotFound(
+ instance_id='something')
self.assertFalse(drvr.instance_exists(None))
- mock_get_guest.side_effect = exception.InternalError
+ mock_get_guest.side_effect = exception.InternalError(err='something')
self.assertFalse(drvr.instance_exists(None))
def test_estimate_instance_overhead_spawn(self):
diff --git a/nova/tests/unit/virt/powervm/test_driver.py b/nova/tests/unit/virt/powervm/test_driver.py
index f8f9b44990..258eab1ecf 100644
--- a/nova/tests/unit/virt/powervm/test_driver.py
+++ b/nova/tests/unit/virt/powervm/test_driver.py
@@ -120,7 +120,8 @@ class TestPowerVMDriver(test.NoDBTestCase):
mock_del.reset_mock()
# InstanceNotFound exception, non-forced
- mock_pwroff.side_effect = exception.InstanceNotFound
+ mock_pwroff.side_effect = exception.InstanceNotFound(
+ instance_id='something')
self.drv.destroy('context', self.inst, [], block_device_info={},
destroy_disks=False)
mock_pwroff.assert_called_once_with(
diff --git a/nova/utils.py b/nova/utils.py
index 769bd47326..3a466652f4 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -539,14 +539,6 @@ def parse_server_string(server_str):
return ('', '')
-def is_valid_ipv6_cidr(address):
- try:
- netaddr.IPNetwork(address, version=6).cidr
- return True
- except (TypeError, netaddr.AddrFormatError):
- return False
-
-
def get_shortened_ipv6(address):
addr = netaddr.IPAddress(address, version=6)
return str(addr.ipv6())
@@ -557,29 +549,6 @@ def get_shortened_ipv6_cidr(address):
return str(net.cidr)
-def is_valid_cidr(address):
- """Check if address is valid
-
- The provided address can be a IPv6 or a IPv4
- CIDR address.
- """
- try:
- # Validate the correct CIDR Address
- netaddr.IPNetwork(address)
- except netaddr.AddrFormatError:
- return False
-
- # Prior validation partially verify /xx part
- # Verify it here
- ip_segment = address.split('/')
-
- if (len(ip_segment) <= 1 or
- ip_segment[1] == ''):
- return False
-
- return True
-
-
def get_ip_version(network):
"""Returns the IP version of a network (IPv4 or IPv6).
@@ -1258,6 +1227,18 @@ def get_sha256_str(base_str):
return hashlib.sha256(base_str).hexdigest()
+def get_obj_repr_unicode(obj):
+ """Returns a string representation of an object converted to unicode.
+
+ In the case of python 3, this just returns the repr() of the object,
+ else it converts the repr() to unicode.
+ """
+ obj_repr = repr(obj)
+ if not six.PY3:
+ obj_repr = six.text_type(obj_repr, 'utf-8')
+ return obj_repr
+
+
def filter_and_format_resource_metadata(resource_type, resource_list,
search_filts, metadata_type=None):
"""Get all metadata for a list of resources after filtering.
diff --git a/nova/virt/libvirt/firewall.py b/nova/virt/libvirt/firewall.py
index cbfa3a5bc4..8157e0aa03 100644
--- a/nova/virt/libvirt/firewall.py
+++ b/nova/virt/libvirt/firewall.py
@@ -23,7 +23,6 @@ from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import importutils
-from nova.cloudpipe import pipelib
import nova.conf
from nova.i18n import _LI
from nova.i18n import _LW
@@ -190,9 +189,7 @@ class NWFilterFirewall(base_firewall.FirewallDriver):
filters added to the list must also be correctly defined
within the subclass.
"""
- if pipelib.is_vpn_image(instance.image_ref):
- base_filter = 'nova-vpn'
- elif allow_dhcp:
+ if allow_dhcp:
base_filter = 'nova-base'
else:
base_filter = 'nova-nodhcp'
@@ -218,8 +215,6 @@ class NWFilterFirewall(base_firewall.FirewallDriver):
self._define_filter(self._filter_container('nova-nodhcp', filter_set))
filter_set.append('allow-dhcp-server')
self._define_filter(self._filter_container('nova-base', filter_set))
- self._define_filter(self._filter_container('nova-vpn',
- ['allow-dhcp-server']))
self._define_filter(self.nova_dhcp_filter())
self.static_filters_configured = True
diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py
index 9746e73ec6..d7b60ff1f2 100644
--- a/nova/virt/libvirt/vif.py
+++ b/nova/virt/libvirt/vif.py
@@ -522,9 +522,11 @@ class LibvirtGenericVIFDriver(object):
vif_type = vif['type']
vnic_type = vif['vnic_type']
+ # instance.display_name could be unicode
+ instance_repr = utils.get_obj_repr_unicode(instance)
LOG.debug('vif_type=%(vif_type)s instance=%(instance)s '
'vif=%(vif)s virt_type=%(virt_type)s',
- {'vif_type': vif_type, 'instance': instance,
+ {'vif_type': vif_type, 'instance': instance_repr,
'vif': vif, 'virt_type': virt_type})
if vif_type is None:
@@ -767,9 +769,11 @@ class LibvirtGenericVIFDriver(object):
def plug(self, instance, vif):
vif_type = vif['type']
+ # instance.display_name could be unicode
+ instance_repr = utils.get_obj_repr_unicode(instance)
LOG.debug('vif_type=%(vif_type)s instance=%(instance)s '
'vif=%(vif)s',
- {'vif_type': vif_type, 'instance': instance,
+ {'vif_type': vif_type, 'instance': instance_repr,
'vif': vif})
if vif_type is None:
@@ -955,9 +959,11 @@ class LibvirtGenericVIFDriver(object):
def unplug(self, instance, vif):
vif_type = vif['type']
+ # instance.display_name could be unicode
+ instance_repr = utils.get_obj_repr_unicode(instance)
LOG.debug('vif_type=%(vif_type)s instance=%(instance)s '
'vif=%(vif)s',
- {'vif_type': vif_type, 'instance': instance,
+ {'vif_type': vif_type, 'instance': instance_repr,
'vif': vif})
if vif_type is None:
diff --git a/nova/volume/cinder.py b/nova/volume/cinder.py
index 4b3cde1af1..c374680ecc 100644
--- a/nova/volume/cinder.py
+++ b/nova/volume/cinder.py
@@ -199,8 +199,8 @@ def translate_volume_exception(method):
res = method(self, ctx, volume_id, *args, **kwargs)
except (keystone_exception.NotFound, cinder_exception.NotFound):
_reraise(exception.VolumeNotFound(volume_id=volume_id))
- except cinder_exception.OverLimit:
- _reraise(exception.OverQuota(overs='volumes'))
+ except cinder_exception.OverLimit as e:
+ _reraise(exception.OverQuota(message=e.message))
return res
return translate_cinder_exception(wrapper)
diff --git a/releasenotes/notes/add-neutron-floating_pool-option-cba402c2de407b78.yaml b/releasenotes/notes/add-neutron-floating_pool-option-cba402c2de407b78.yaml
new file mode 100644
index 0000000000..b95d1ae60e
--- /dev/null
+++ b/releasenotes/notes/add-neutron-floating_pool-option-cba402c2de407b78.yaml
@@ -0,0 +1,7 @@
+---
+upgrade:
+ - |
+ 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.
+ Neutron users meanwhile should migrate to the new option.
diff --git a/releasenotes/notes/deprecate-more-nova-network-opts-a9f87c79f7d26438.yaml b/releasenotes/notes/deprecate-more-nova-network-opts-a9f87c79f7d26438.yaml
index f28b4aafb9..7a44031cd2 100644
--- a/releasenotes/notes/deprecate-more-nova-network-opts-a9f87c79f7d26438.yaml
+++ b/releasenotes/notes/deprecate-more-nova-network-opts-a9f87c79f7d26438.yaml
@@ -4,6 +4,8 @@ deprecations:
The following options, found in ``DEFAULT``, were only used for configuring
nova-network and are, like nova-network itself, now deprecated.
+ - ``default_floating_pool`` (neutron users should use the
+ ``neutron.default_floating_pool``)
- ``ipv6_backend``
- ``firewall_driver``
- ``metadata_host``
diff --git a/releasenotes/notes/project_id_validation-568d31c13c3ef735.yaml b/releasenotes/notes/project_id_validation-568d31c13c3ef735.yaml
index a7b51ceb64..8315d4bb98 100644
--- a/releasenotes/notes/project_id_validation-568d31c13c3ef735.yaml
+++ b/releasenotes/notes/project_id_validation-568d31c13c3ef735.yaml
@@ -1,10 +1,11 @@
---
fixes:
- |
- API calls to /os-quota-sets/* will now attempt to validate the
- project_id being opperated on with keystone. If the user has
- enough permissions in user, and the keystone project does not
- exist, a 400 will be returned to prevent invalidate quota data
- from being put in the Nova database. This fixes an effective
- silent error where this would be stored even if this was not a
+ API calls to ``/os-quota-sets`` and flavor access will now attempt
+ to validate the project_id being operated on with Keystone. If
+ the user token has enough permissions to perform
+ ``GET /v3/projects/{project_id}``, and the Keystone project
+ does not exist, a 400 BadRequest will be returned to prevent invalid
+ project data from being put in the Nova database. This fixes an effective
+ silent error where the project_id would be stored even if it was not a
valid project_id in the system.
diff --git a/releasenotes/notes/remove-cloudpipe-api-f7aea9372046ecfc.yaml b/releasenotes/notes/remove-cloudpipe-api-f7aea9372046ecfc.yaml
new file mode 100644
index 0000000000..c26efca16a
--- /dev/null
+++ b/releasenotes/notes/remove-cloudpipe-api-f7aea9372046ecfc.yaml
@@ -0,0 +1,4 @@
+---
+upgrade:
+ - The deprecated /os-cloudpipe API endpoint has been removed. Whenever calls
+ are made to that endpoint it now returns a 410 response.
diff --git a/releasenotes/notes/retrieve_physical_network_from_multi-segment-eec5a490c1ed8739.yaml b/releasenotes/notes/retrieve_physical_network_from_multi-segment-eec5a490c1ed8739.yaml
new file mode 100644
index 0000000000..9d942629f6
--- /dev/null
+++ b/releasenotes/notes/retrieve_physical_network_from_multi-segment-eec5a490c1ed8739.yaml
@@ -0,0 +1,8 @@
+---
+fixes:
+ - Physical network name will be retrieved from a multi-segement network.
+ The current implementation will retrieve the physical network name for the
+ first segment that provides it. This is mostly intended to support a
+ combinatin of vxlan and vlan segments. Additional work will be required to
+ support a case of multiple vlan segments associated with different
+ physical networks.
diff --git a/releasenotes/notes/service-uuid-online-migration-17d48f198a6d4deb.yaml b/releasenotes/notes/service-uuid-online-migration-17d48f198a6d4deb.yaml
new file mode 100644
index 0000000000..b13c4e1033
--- /dev/null
+++ b/releasenotes/notes/service-uuid-online-migration-17d48f198a6d4deb.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+ - |
+ An online data migration has been added to populate the ``services.uuid``
+ column in the nova database for non-deleted services records. Listing or
+ showing services out of the ``os-services`` API will have the same effect.
diff --git a/releasenotes/notes/volume-attach-versioned-notifications-ef5afde3a5f6a749.yaml b/releasenotes/notes/volume-attach-versioned-notifications-ef5afde3a5f6a749.yaml
new file mode 100644
index 0000000000..379e68abdd
--- /dev/null
+++ b/releasenotes/notes/volume-attach-versioned-notifications-ef5afde3a5f6a749.yaml
@@ -0,0 +1,11 @@
+---
+features:
+ - |
+ The following volume attach and volume detach versioned notifications have
+ been added to the nova-compute service:
+
+ * instance.volume_attach.start
+ * instance.volume_attach.end
+ * instance.volume_attach.error
+ * instance.volume_detach.start
+ * instance.volume_detach.end
diff --git a/releasenotes/notes/wsgi-applications-8017c3192d2b143e.yaml b/releasenotes/notes/wsgi-applications-8017c3192d2b143e.yaml
new file mode 100644
index 0000000000..7385263149
--- /dev/null
+++ b/releasenotes/notes/wsgi-applications-8017c3192d2b143e.yaml
@@ -0,0 +1,8 @@
+---
+upgrade:
+ - |
+ WSGI application scripts ``nova-api-wsgi`` and ``nova-metadata-wsgi`` are
+ now available. They allow running the compute and metadata APIs using a WSGI
+ server of choice (for example nginx and uwsgi, apache2 with mod_proxy_uwsgi
+ or gunicorn). The eventlet-based servers are still available, but the WSGI
+ options will allow greater deployment flexibility.
diff --git a/requirements.txt b/requirements.txt
index e4eb59929d..370c0b46d3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -26,7 +26,7 @@ enum34;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' #
iso8601>=0.1.11 # MIT
jsonschema!=2.5.0,<3.0.0,>=2.0.0 # MIT
python-cinderclient>=2.0.1 # Apache-2.0
-keystoneauth1>=2.18.0 # Apache-2.0
+keystoneauth1>=2.20.0 # Apache-2.0
python-neutronclient>=5.1.0 # Apache-2.0
python-glanceclient>=2.5.0 # Apache-2.0
requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
index 507955b5d3..ac93d64b3b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -49,7 +49,6 @@ console_scripts =
nova-api-metadata = nova.cmd.api_metadata:main
nova-api-os-compute = nova.cmd.api_os_compute:main
nova-cells = nova.cmd.cells:main
- nova-cert = nova.cmd.cert:main
nova-compute = nova.cmd.compute:main
nova-conductor = nova.cmd.conductor:main
nova-console = nova.cmd.console:main
@@ -70,12 +69,10 @@ console_scripts =
wsgi_scripts =
nova-placement-api = nova.api.openstack.placement.wsgi:init_application
nova-api-wsgi = nova.api.openstack.compute.wsgi:init_application
+ nova-metadata-wsgi = nova.api.metadata.wsgi:init_application
nova.api.v21.extensions =
- agents = nova.api.openstack.compute.agents:Agents
- assisted_volume_snapshots = nova.api.openstack.compute.assisted_volume_snapshots:AssistedVolumeSnapshots
attach_interfaces = nova.api.openstack.compute.attach_interfaces:AttachInterfaces
- availability_zone = nova.api.openstack.compute.availability_zone:AvailabilityZone
baremetal_nodes = nova.api.openstack.compute.baremetal_nodes:BareMetalNodes
block_device_mapping = nova.api.openstack.compute.block_device_mapping:BlockDeviceMapping
cells = nova.api.openstack.compute.cells:Cells
@@ -84,11 +81,6 @@ nova.api.v21.extensions =
console_auth_tokens = nova.api.openstack.compute.console_auth_tokens:ConsoleAuthTokens
consoles = nova.api.openstack.compute.consoles:Consoles
extension_info = nova.api.openstack.compute.extension_info:ExtensionInfo
- fixed_ips = nova.api.openstack.compute.fixed_ips:FixedIps
- floating_ip_dns = nova.api.openstack.compute.floating_ip_dns:FloatingIpDns
- floating_ip_pools = nova.api.openstack.compute.floating_ip_pools:FloatingIpPools
- floating_ips = nova.api.openstack.compute.floating_ips:FloatingIps
- floating_ips_bulk = nova.api.openstack.compute.floating_ips_bulk:FloatingIpsBulk
fping = nova.api.openstack.compute.fping:Fping
hosts = nova.api.openstack.compute.hosts:Hosts
hypervisors = nova.api.openstack.compute.hypervisors:Hypervisors
@@ -96,7 +88,6 @@ nova.api.v21.extensions =
image_metadata = nova.api.openstack.compute.image_metadata:ImageMetadata
image_size = nova.api.openstack.compute.image_size:ImageSize
instance_actions = nova.api.openstack.compute.instance_actions:InstanceActions
- instance_usage_audit_log = nova.api.openstack.compute.instance_usage_audit_log:InstanceUsageAuditLog
ips = nova.api.openstack.compute.ips:IPs
limits = nova.api.openstack.compute.limits:Limits
migrations = nova.api.openstack.compute.migrations:Migrations
@@ -110,7 +101,6 @@ nova.api.v21.extensions =
security_groups = nova.api.openstack.compute.security_groups:SecurityGroups
server_diagnostics = nova.api.openstack.compute.server_diagnostics:ServerDiagnostics
server_external_events = nova.api.openstack.compute.server_external_events:ServerExternalEvents
- server_metadata = nova.api.openstack.compute.server_metadata:ServerMetadata
server_migrations = nova.api.openstack.compute.server_migrations:ServerMigrations
server_password = nova.api.openstack.compute.server_password:ServerPassword
server_tags = nova.api.openstack.compute.server_tags:ServerTags
diff --git a/test-requirements.txt b/test-requirements.txt
index 59ebd11975..d47928f907 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -26,7 +26,7 @@ testscenarios>=0.4 # Apache-2.0/BSD
testtools>=1.4.0 # MIT
bandit>=1.1.0 # Apache-2.0
openstackdocstheme>=1.5.0 # Apache-2.0
-gabbi>=1.26.1 # Apache-2.0
+gabbi>=1.30.0 # Apache-2.0
# vmwareapi driver specific dependencies
oslo.vmware>=2.17.0 # Apache-2.0