Merge "Microversion 2.73: Support adding the reason behind a server lock"
This commit is contained in:
commit
cf76777b61
|
@ -151,6 +151,7 @@ For different user roles, the user has different query options set:
|
|||
- ``tags`` (New in version 2.26)
|
||||
- ``tags-any`` (New in version 2.26)
|
||||
- ``changes-before`` (New in version 2.66)
|
||||
- ``locked`` (New in version 2.73)
|
||||
|
||||
Other options will be ignored by nova silently.
|
||||
|
||||
|
|
|
@ -975,6 +975,18 @@ locked_by_query_server:
|
|||
in: query
|
||||
required: false
|
||||
type: string
|
||||
locked_query_server:
|
||||
description: |
|
||||
Specify the ``locked`` query parameter to list all locked or unlocked
|
||||
instances. If the value is specified, ``1``, ``t``, ``true``,
|
||||
``on``, ``y`` and ``yes`` are treated as ``True``. ``0``, ``f``,
|
||||
``false``, ``off``, ``n`` and ``no`` are treated as ``False``.
|
||||
(They are case-insensitive.) Any other value provided will be considered
|
||||
invalid.
|
||||
in: query
|
||||
required: false
|
||||
type: boolean
|
||||
min_version: 2.73
|
||||
marker:
|
||||
description: |
|
||||
The ID of the last-seen item. Use the ``limit`` parameter to make an initial limited
|
||||
|
@ -1259,6 +1271,7 @@ sort_key_server:
|
|||
- ``key_name``
|
||||
- ``launch_index``
|
||||
- ``launched_at``
|
||||
- ``locked`` (New in version 2.73)
|
||||
- ``locked_by``
|
||||
- ``node``
|
||||
- ``power_state``
|
||||
|
@ -4315,9 +4328,11 @@ local_gb_used_total:
|
|||
lock:
|
||||
description: |
|
||||
The action to lock a server.
|
||||
This parameter can be ``null``.
|
||||
Up to microversion 2.73, this parameter should be ``null``.
|
||||
in: body
|
||||
required: true
|
||||
type: none
|
||||
type: object
|
||||
locked:
|
||||
description: |
|
||||
True if the instance is locked otherwise False.
|
||||
|
@ -4325,6 +4340,20 @@ locked:
|
|||
required: true
|
||||
type: boolean
|
||||
min_version: 2.9
|
||||
locked_reason_req:
|
||||
description: |
|
||||
The reason behind locking a server. Limited to 255 characters in length.
|
||||
in: body
|
||||
required: false
|
||||
type: string
|
||||
min_version: 2.73
|
||||
locked_reason_resp:
|
||||
description: |
|
||||
The reason behind locking a server.
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
min_version: 2.73
|
||||
mac_addr:
|
||||
description: |
|
||||
The MAC address.
|
||||
|
|
|
@ -383,15 +383,16 @@ See the "Lock, Unlock" item in `Server actions
|
|||
<https://developer.openstack.org/api-guide/compute/server_concepts.html#server-actions>`_
|
||||
for the restricted actions.
|
||||
But administrators can perform actions on the server
|
||||
even though the server is locked.
|
||||
even though the server is locked. Note that from microversion 2.73 it is
|
||||
possible to specify a reason when locking the server.
|
||||
|
||||
The `unlock action
|
||||
<https://developer.openstack.org/api-ref/compute/#unlock-server-unlock-action>`_
|
||||
will unlock a server in locked state so additional actions can
|
||||
be performed on the server by non-admin users.
|
||||
|
||||
You can know whether a server is locked or not
|
||||
by the `List Servers Detailed API
|
||||
You can know whether a server is locked or not and the ``locked_reason``
|
||||
(if specified, from the 2.73 microversion) by the `List Servers Detailed API
|
||||
<https://developer.openstack.org/api-ref/compute/#list-servers-detailed>`_
|
||||
or
|
||||
the `Show Server Details API
|
||||
|
@ -414,12 +415,18 @@ Request
|
|||
|
||||
- server_id: server_id_path
|
||||
- lock: lock
|
||||
- locked_reason: locked_reason_req
|
||||
|
||||
**Example Lock Server (lock Action)**
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/os-lock-server/lock-server.json
|
||||
:language: javascript
|
||||
|
||||
**Example Lock Server (lock Action) (v2.73)**
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/os-lock-server/v2.73/lock-server-with-reason.json
|
||||
:language: javascript
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
|
@ -626,10 +633,11 @@ Response
|
|||
- user_data: user_data_rebuild_resp
|
||||
- trusted_image_certificates: server_trusted_image_certificates_resp
|
||||
- server_groups: server_groups_2_71
|
||||
- locked_reason: locked_reason_resp
|
||||
|
||||
**Example Rebuild Server (rebuild Action) (v2.63)**
|
||||
**Example Rebuild Server (rebuild Action) (v2.73)**
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/servers/v2.63/server-action-rebuild-resp.json
|
||||
.. literalinclude:: ../../doc/api_samples/servers/v2.73/server-action-rebuild-resp.json
|
||||
:language: javascript
|
||||
|
||||
Remove (Disassociate) Floating Ip (removeFloatingIp Action) (DEPRECATED)
|
||||
|
|
|
@ -170,6 +170,7 @@ whitelist will be silently ignored.
|
|||
- ``tags`` (New in version 2.26)
|
||||
- ``tags-any`` (New in version 2.26)
|
||||
- ``changes-before`` (New in version 2.66)
|
||||
- ``locked`` (New in version 2.73)
|
||||
|
||||
|
||||
- For admin user, whitelist includes all filter keys mentioned in
|
||||
|
@ -240,6 +241,7 @@ Request
|
|||
- tags: tags_query
|
||||
- tags-any: tags_any_query
|
||||
- changes-before: changes_before_server
|
||||
- locked: locked_query_server
|
||||
|
||||
Response
|
||||
--------
|
||||
|
@ -577,6 +579,7 @@ Request
|
|||
- tags: tags_query
|
||||
- tags-any: tags_any_query
|
||||
- changes-before: changes_before_server
|
||||
- locked: locked_query_server
|
||||
|
||||
Response
|
||||
--------
|
||||
|
@ -646,10 +649,11 @@ Response
|
|||
- description: server_description_resp
|
||||
- tags: tags
|
||||
- trusted_image_certificates: server_trusted_image_certificates_resp
|
||||
- locked_reason: locked_reason_resp
|
||||
|
||||
**Example List Servers Detailed (2.63)**
|
||||
**Example List Servers Detailed (2.73)**
|
||||
|
||||
.. literalinclude:: /../../doc/api_samples/servers/v2.63/servers-details-resp.json
|
||||
.. literalinclude:: /../../doc/api_samples/servers/v2.73/servers-details-resp.json
|
||||
:language: javascript
|
||||
|
||||
**Example List Servers Detailed (2.69)**
|
||||
|
@ -769,10 +773,11 @@ Response
|
|||
- tags: tags
|
||||
- trusted_image_certificates: server_trusted_image_certificates_resp
|
||||
- server_groups: server_groups_2_71
|
||||
- locked_reason: locked_reason_resp
|
||||
|
||||
**Example Show Server Details (2.63)**
|
||||
**Example Show Server Details (2.73)**
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/servers/v2.63/server-get-resp.json
|
||||
.. literalinclude:: ../../doc/api_samples/servers/v2.73/server-get-resp.json
|
||||
:language: javascript
|
||||
|
||||
**Example Show Server Details (2.69)**
|
||||
|
@ -861,10 +866,11 @@ Response
|
|||
- tags: tags
|
||||
- trusted_image_certificates: server_trusted_image_certificates_resp
|
||||
- server_groups: server_groups_2_71
|
||||
- locked_reason: locked_reason_resp
|
||||
|
||||
**Example Update Server (2.63)**
|
||||
**Example Update Server (2.73)**
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/servers/v2.63/server-update-resp.json
|
||||
.. literalinclude:: ../../doc/api_samples/servers/v2.73/server-update-resp.json
|
||||
:language: javascript
|
||||
|
||||
Delete Server
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"lock": {"locked_reason": "I don't want to work"}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"lock": null
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"unlock": null
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"lock": {"locked_reason": "I don't want to work"}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"server": {
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"accessIPv4": "1.2.3.4",
|
||||
"accessIPv6": "80fe::",
|
||||
"addresses": {
|
||||
"private": [
|
||||
{
|
||||
"addr": "192.168.0.3",
|
||||
"version": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
"adminPass": "seekr3t",
|
||||
"created": "2019-04-23T17:10:22Z",
|
||||
"description": null,
|
||||
"flavor": {
|
||||
"disk": 1,
|
||||
"ephemeral": 0,
|
||||
"extra_specs": {},
|
||||
"original_name": "m1.tiny",
|
||||
"ram": 512,
|
||||
"swap": 0,
|
||||
"vcpus": 1
|
||||
},
|
||||
"hostId": "2091634baaccdc4c5a1d57069c833e402921df696b7f970791b12ec6",
|
||||
"id": "0c37a84a-c757-4f22-8c7f-0bf8b6970886",
|
||||
"image": {
|
||||
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/images/70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key_name": null,
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/servers/0c37a84a-c757-4f22-8c7f-0bf8b6970886",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/0c37a84a-c757-4f22-8c7f-0bf8b6970886",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"locked": false,
|
||||
"locked_reason": null,
|
||||
"metadata": {
|
||||
"meta_var": "meta_val"
|
||||
},
|
||||
"name": "foobar",
|
||||
"progress": 0,
|
||||
"server_groups": [],
|
||||
"status": "ACTIVE",
|
||||
"tags": [],
|
||||
"tenant_id": "6f70656e737461636b20342065766572",
|
||||
"trusted_image_certificates": null,
|
||||
"updated": "2019-04-23T17:10:24Z",
|
||||
"user_data": "ZWNobyAiaGVsbG8gd29ybGQi",
|
||||
"user_id": "fake"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"rebuild" : {
|
||||
"accessIPv4" : "1.2.3.4",
|
||||
"accessIPv6" : "80fe::",
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"imageRef" : "70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"name" : "foobar",
|
||||
"adminPass" : "seekr3t",
|
||||
"metadata" : {
|
||||
"meta_var" : "meta_val"
|
||||
},
|
||||
"user_data": "ZWNobyAiaGVsbG8gd29ybGQi"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"server" : {
|
||||
"accessIPv4": "1.2.3.4",
|
||||
"accessIPv6": "80fe::",
|
||||
"name" : "new-server-test",
|
||||
"imageRef" : "70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"flavorRef" : "1",
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"metadata" : {
|
||||
"My Server Name" : "Apache1"
|
||||
},
|
||||
"security_groups": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
],
|
||||
"user_data" : "IyEvYmluL2Jhc2gKL2Jpbi9zdQplY2hvICJJIGFtIGluIHlvdSEiCg==",
|
||||
"networks": "auto"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"server": {
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"adminPass": "kJTmMkszoB6A",
|
||||
"id": "ae10adbb-9b5e-4667-9cc5-05ebdc80a941",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/servers/ae10adbb-9b5e-4667-9cc5-05ebdc80a941",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/ae10adbb-9b5e-4667-9cc5-05ebdc80a941",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"security_groups": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
{
|
||||
"server": {
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"OS-EXT-AZ:availability_zone": "nova",
|
||||
"OS-EXT-SRV-ATTR:host": "compute",
|
||||
"OS-EXT-SRV-ATTR:hostname": "new-server-test",
|
||||
"OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini",
|
||||
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
|
||||
"OS-EXT-SRV-ATTR:kernel_id": "",
|
||||
"OS-EXT-SRV-ATTR:launch_index": 0,
|
||||
"OS-EXT-SRV-ATTR:ramdisk_id": "",
|
||||
"OS-EXT-SRV-ATTR:reservation_id": "r-t61j9da6",
|
||||
"OS-EXT-SRV-ATTR:root_device_name": "/dev/sda",
|
||||
"OS-EXT-SRV-ATTR:user_data": "IyEvYmluL2Jhc2gKL2Jpbi9zdQplY2hvICJJIGFtIGluIHlvdSEiCg==",
|
||||
"OS-EXT-STS:power_state": 1,
|
||||
"OS-EXT-STS:task_state": null,
|
||||
"OS-EXT-STS:vm_state": "active",
|
||||
"OS-SRV-USG:launched_at": "2019-04-23T15:19:10.855016",
|
||||
"OS-SRV-USG:terminated_at": null,
|
||||
"accessIPv4": "1.2.3.4",
|
||||
"accessIPv6": "80fe::",
|
||||
"addresses": {
|
||||
"private": [
|
||||
{
|
||||
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
|
||||
"OS-EXT-IPS:type": "fixed",
|
||||
"addr": "192.168.0.3",
|
||||
"version": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
"config_drive": "",
|
||||
"created": "2019-04-23T15:19:09Z",
|
||||
"description": null,
|
||||
"flavor": {
|
||||
"disk": 1,
|
||||
"ephemeral": 0,
|
||||
"extra_specs": {},
|
||||
"original_name": "m1.tiny",
|
||||
"ram": 512,
|
||||
"swap": 0,
|
||||
"vcpus": 1
|
||||
},
|
||||
"hostId": "2091634baaccdc4c5a1d57069c833e402921df696b7f970791b12ec6",
|
||||
"host_status": "UP",
|
||||
"id": "0e12087a-7c87-476a-8f84-7398e991cecc",
|
||||
"image": {
|
||||
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/images/70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key_name": null,
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/servers/0e12087a-7c87-476a-8f84-7398e991cecc",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/0e12087a-7c87-476a-8f84-7398e991cecc",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"locked": true,
|
||||
"locked_reason": "I don't want to work",
|
||||
"metadata": {
|
||||
"My Server Name": "Apache1"
|
||||
},
|
||||
"name": "new-server-test",
|
||||
"os-extended-volumes:volumes_attached": [],
|
||||
"progress": 0,
|
||||
"security_groups": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
],
|
||||
"server_groups": [],
|
||||
"status": "ACTIVE",
|
||||
"tags": [],
|
||||
"tenant_id": "6f70656e737461636b20342065766572",
|
||||
"trusted_image_certificates": null,
|
||||
"updated": "2019-04-23T15:19:11Z",
|
||||
"user_id": "fake"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"server": {
|
||||
"accessIPv4": "1.2.3.4",
|
||||
"accessIPv6": "80fe::",
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"name": "new-server-test",
|
||||
"description": "Sample description"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"server": {
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"accessIPv4": "1.2.3.4",
|
||||
"accessIPv6": "80fe::",
|
||||
"addresses": {
|
||||
"private": [
|
||||
{
|
||||
"addr": "192.168.0.3",
|
||||
"version": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
"created": "2019-04-23T17:37:48Z",
|
||||
"description": "Sample description",
|
||||
"flavor": {
|
||||
"disk": 1,
|
||||
"ephemeral": 0,
|
||||
"extra_specs": {},
|
||||
"original_name": "m1.tiny",
|
||||
"ram": 512,
|
||||
"swap": 0,
|
||||
"vcpus": 1
|
||||
},
|
||||
"hostId": "2091634baaccdc4c5a1d57069c833e402921df696b7f970791b12ec6",
|
||||
"id": "f9a6c4fe-28e0-48a9-b02c-164e4d04d0b2",
|
||||
"image": {
|
||||
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/images/70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/servers/f9a6c4fe-28e0-48a9-b02c-164e4d04d0b2",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/f9a6c4fe-28e0-48a9-b02c-164e4d04d0b2",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"locked": false,
|
||||
"locked_reason": null,
|
||||
"metadata": {
|
||||
"My Server Name": "Apache1"
|
||||
},
|
||||
"name": "new-server-test",
|
||||
"progress": 0,
|
||||
"server_groups": [],
|
||||
"status": "ACTIVE",
|
||||
"tags": [],
|
||||
"tenant_id": "6f70656e737461636b20342065766572",
|
||||
"trusted_image_certificates": null,
|
||||
"updated": "2019-04-23T17:37:48Z",
|
||||
"user_id": "fake"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
{
|
||||
"servers": [
|
||||
{
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"OS-EXT-AZ:availability_zone": "nova",
|
||||
"OS-EXT-SRV-ATTR:host": "compute",
|
||||
"OS-EXT-SRV-ATTR:hostname": "new-server-test",
|
||||
"OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini",
|
||||
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
|
||||
"OS-EXT-SRV-ATTR:kernel_id": "",
|
||||
"OS-EXT-SRV-ATTR:launch_index": 0,
|
||||
"OS-EXT-SRV-ATTR:ramdisk_id": "",
|
||||
"OS-EXT-SRV-ATTR:reservation_id": "r-l0i0clt2",
|
||||
"OS-EXT-SRV-ATTR:root_device_name": "/dev/sda",
|
||||
"OS-EXT-SRV-ATTR:user_data": "IyEvYmluL2Jhc2gKL2Jpbi9zdQplY2hvICJJIGFtIGluIHlvdSEiCg==",
|
||||
"OS-EXT-STS:power_state": 1,
|
||||
"OS-EXT-STS:task_state": null,
|
||||
"OS-EXT-STS:vm_state": "active",
|
||||
"OS-SRV-USG:launched_at": "2019-04-23T15:19:15.317839",
|
||||
"OS-SRV-USG:terminated_at": null,
|
||||
"accessIPv4": "1.2.3.4",
|
||||
"accessIPv6": "80fe::",
|
||||
"addresses": {
|
||||
"private": [
|
||||
{
|
||||
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
|
||||
"OS-EXT-IPS:type": "fixed",
|
||||
"addr": "192.168.0.3",
|
||||
"version": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
"config_drive": "",
|
||||
"created": "2019-04-23T15:19:14Z",
|
||||
"description": null,
|
||||
"flavor": {
|
||||
"disk": 1,
|
||||
"ephemeral": 0,
|
||||
"extra_specs": {},
|
||||
"original_name": "m1.tiny",
|
||||
"ram": 512,
|
||||
"swap": 0,
|
||||
"vcpus": 1
|
||||
},
|
||||
"hostId": "2091634baaccdc4c5a1d57069c833e402921df696b7f970791b12ec6",
|
||||
"host_status": "UP",
|
||||
"id": "2ce4c5b3-2866-4972-93ce-77a2ea46a7f9",
|
||||
"image": {
|
||||
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/images/70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key_name": null,
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/servers/2ce4c5b3-2866-4972-93ce-77a2ea46a7f9",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/2ce4c5b3-2866-4972-93ce-77a2ea46a7f9",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"locked": true,
|
||||
"locked_reason": "I don't want to work",
|
||||
"metadata": {
|
||||
"My Server Name": "Apache1"
|
||||
},
|
||||
"name": "new-server-test",
|
||||
"os-extended-volumes:volumes_attached": [],
|
||||
"progress": 0,
|
||||
"security_groups": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
],
|
||||
"status": "ACTIVE",
|
||||
"tags": [],
|
||||
"tenant_id": "6f70656e737461636b20342065766572",
|
||||
"trusted_image_certificates": null,
|
||||
"updated": "2019-04-23T15:19:15Z",
|
||||
"user_id": "fake"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -19,7 +19,7 @@
|
|||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.72",
|
||||
"version": "2.73",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.72",
|
||||
"version": "2.73",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
},
|
||||
"nova_object.name":"InstanceActionPayload",
|
||||
"nova_object.namespace":"nova",
|
||||
"nova_object.version":"1.7"
|
||||
"nova_object.version":"1.8"
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
]
|
||||
},
|
||||
"nova_object.name": "InstanceActionRebuildPayload",
|
||||
"nova_object.version": "1.8"
|
||||
"nova_object.version": "1.9"
|
||||
}
|
||||
|
|
|
@ -4,5 +4,5 @@
|
|||
"rescue_image_ref": "a2459075-d96c-40d5-893e-577ff92e721c"
|
||||
},
|
||||
"nova_object.name": "InstanceActionRescuePayload",
|
||||
"nova_object.version": "1.2"
|
||||
"nova_object.version": "1.3"
|
||||
}
|
||||
|
|
|
@ -27,5 +27,5 @@
|
|||
"task_state": "resize_prep"
|
||||
},
|
||||
"nova_object.name": "InstanceActionResizePrepPayload",
|
||||
"nova_object.version": "1.2"
|
||||
"nova_object.version": "1.3"
|
||||
}
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
},
|
||||
"nova_object.name":"InstanceActionSnapshotPayload",
|
||||
"nova_object.namespace":"nova",
|
||||
"nova_object.version":"1.8"
|
||||
"nova_object.version":"1.9"
|
||||
}
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
},
|
||||
"nova_object.name": "InstanceActionVolumePayload",
|
||||
"nova_object.namespace": "nova",
|
||||
"nova_object.version": "1.5"
|
||||
"nova_object.version": "1.6"
|
||||
}
|
|
@ -6,5 +6,5 @@
|
|||
},
|
||||
"nova_object.name": "InstanceActionVolumeSwapPayload",
|
||||
"nova_object.namespace": "nova",
|
||||
"nova_object.version": "1.7"
|
||||
"nova_object.version": "1.8"
|
||||
}
|
||||
|
|
|
@ -24,5 +24,5 @@
|
|||
"instance_name": "instance-00000001"
|
||||
},
|
||||
"nova_object.name":"InstanceCreatePayload",
|
||||
"nova_object.version": "1.11"
|
||||
"nova_object.version": "1.12"
|
||||
}
|
||||
|
|
|
@ -8,5 +8,5 @@
|
|||
},
|
||||
"nova_object.name":"InstanceExistsPayload",
|
||||
"nova_object.namespace":"nova",
|
||||
"nova_object.version":"1.1"
|
||||
"nova_object.version":"1.2"
|
||||
}
|
||||
|
|
|
@ -37,9 +37,10 @@
|
|||
"uuid":"178b0921-8f85-4257-88b6-2e743b5a975c",
|
||||
"request_id": "req-5b6c791d-5709-4f36-8fbe-c3e02869e35d",
|
||||
"action_initiator_user": "fake",
|
||||
"action_initiator_project": "6f70656e737461636b20342065766572"
|
||||
"action_initiator_project": "6f70656e737461636b20342065766572",
|
||||
"locked_reason": null
|
||||
},
|
||||
"nova_object.name":"InstancePayload",
|
||||
"nova_object.namespace":"nova",
|
||||
"nova_object.version":"1.7"
|
||||
"nova_object.version":"1.8"
|
||||
}
|
||||
|
|
|
@ -29,5 +29,5 @@
|
|||
},
|
||||
"nova_object.name": "InstanceUpdatePayload",
|
||||
"nova_object.namespace": "nova",
|
||||
"nova_object.version": "1.8"
|
||||
"nova_object.version": "1.9"
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"event_type":"instance.lock",
|
||||
"payload":{
|
||||
"$ref": "common_payloads/InstanceActionPayload.json#",
|
||||
"nova_object.data":{
|
||||
"locked":true,
|
||||
"locked_reason":"global warming"
|
||||
}
|
||||
},
|
||||
"priority":"INFO",
|
||||
"publisher_id":"nova-api:fake-mini"
|
||||
}
|
|
@ -3,7 +3,8 @@
|
|||
"payload":{
|
||||
"$ref": "common_payloads/InstanceActionPayload.json#",
|
||||
"nova_object.data":{
|
||||
"locked":true
|
||||
"locked":true,
|
||||
"locked_reason": null
|
||||
}
|
||||
},
|
||||
"priority":"INFO",
|
||||
|
|
|
@ -178,6 +178,12 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
|||
* 2.72 - Add support for neutron ports with resource request during server
|
||||
create. Server move operations are not yet supported for servers
|
||||
with such ports.
|
||||
* 2.73 - Adds support for specifying a reason when locking the server and
|
||||
exposes this via the response from ``GET /servers/detail``,
|
||||
``GET /servers/{server_id}``, ``PUT servers/{server_id}`` and
|
||||
``POST /servers/{server_id}/action`` where the action is rebuild.
|
||||
It also supports ``locked`` as a filter/sort parameter for
|
||||
``GET /servers/detail`` and ``GET /servers``.
|
||||
"""
|
||||
|
||||
# The minimum and maximum versions of the API supported
|
||||
|
@ -186,7 +192,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
|||
# Note(cyeoh): This only applies for the v2.1 API once microversions
|
||||
# support is fully merged. It does not affect the V2 API.
|
||||
_MIN_API_VERSION = "2.1"
|
||||
_MAX_API_VERSION = "2.72"
|
||||
_MAX_API_VERSION = "2.73"
|
||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||
|
||||
# Almost all proxy APIs which are related to network, images and baremetal
|
||||
|
|
|
@ -512,6 +512,21 @@ def is_all_tenants(search_opts):
|
|||
return all_tenants
|
||||
|
||||
|
||||
def is_locked(search_opts):
|
||||
"""Converts the value of the locked parameter to a boolean. Note that
|
||||
this function will be called only if locked exists in search_opts.
|
||||
|
||||
:param dict search_opts: The search options for a request
|
||||
:returns: boolean indicating if locked is being requested or not
|
||||
"""
|
||||
locked = search_opts.get('locked')
|
||||
try:
|
||||
locked = strutils.bool_from_string(locked, strict=True)
|
||||
except ValueError as err:
|
||||
raise exception.InvalidInput(six.text_type(err))
|
||||
return locked
|
||||
|
||||
|
||||
def supports_multiattach_volume(req):
|
||||
"""Check to see if the requested API version is high enough for multiattach
|
||||
|
||||
|
|
|
@ -13,8 +13,11 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from nova.api.openstack import api_version_request
|
||||
from nova.api.openstack import common
|
||||
from nova.api.openstack.compute.schemas import lock_server
|
||||
from nova.api.openstack import wsgi
|
||||
from nova.api import validation
|
||||
from nova import compute
|
||||
from nova.policies import lock_server as ls_policies
|
||||
|
||||
|
@ -27,6 +30,7 @@ class LockServerController(wsgi.Controller):
|
|||
@wsgi.response(202)
|
||||
@wsgi.expected_errors(404)
|
||||
@wsgi.action('lock')
|
||||
@validation.schema(lock_server.lock_v2_73, "2.73")
|
||||
def _lock(self, req, id, body):
|
||||
"""Lock a server instance."""
|
||||
context = req.environ['nova.context']
|
||||
|
@ -34,7 +38,11 @@ class LockServerController(wsgi.Controller):
|
|||
context.can(ls_policies.POLICY_ROOT % 'lock',
|
||||
target={'user_id': instance.user_id,
|
||||
'project_id': instance.project_id})
|
||||
self.compute_api.lock(context, instance)
|
||||
reason = None
|
||||
if (api_version_request.is_supported(req, min_version='2.73') and
|
||||
body['lock'] is not None):
|
||||
reason = body['lock'].get('locked_reason')
|
||||
self.compute_api.lock(context, instance, reason=reason)
|
||||
|
||||
@wsgi.response(202)
|
||||
@wsgi.expected_errors(404)
|
||||
|
|
|
@ -930,3 +930,13 @@ API limitations:
|
|||
request is not yet supported.
|
||||
|
||||
.. _QoS minimum bandwidth rule: https://docs.openstack.org/neutron/latest/admin/config-qos-min-bw.html
|
||||
|
||||
2.73
|
||||
----
|
||||
|
||||
API microversion 2.73 adds support for specifying a reason when locking the
|
||||
server and exposes this information via ``GET /servers/detail``,
|
||||
``GET /servers/{server_id}``, ``PUT servers/{server_id}`` and
|
||||
``POST /servers/{server_id}/action`` where the action is rebuild. It also
|
||||
supports ``locked`` as a filter/sort parameter for ``GET /servers/detail``
|
||||
and ``GET /servers``.
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
# 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.
|
||||
|
||||
lock_v2_73 = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'lock': {
|
||||
'type': ['object', 'null'],
|
||||
'properties': {
|
||||
'locked_reason': {
|
||||
'type': 'string', 'minLength': 1, 'maxLength': 255,
|
||||
},
|
||||
},
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
'required': ['lock'],
|
||||
'additionalProperties': False,
|
||||
}
|
|
@ -531,6 +531,9 @@ SERVER_LIST_IGNORE_SORT_KEY = [
|
|||
'shutdown_terminate', 'user_data', 'vcpus', 'vm_mode'
|
||||
]
|
||||
|
||||
# From microversion 2.73 we start offering locked as a valid sort key.
|
||||
SERVER_LIST_IGNORE_SORT_KEY_V273 = list(SERVER_LIST_IGNORE_SORT_KEY)
|
||||
SERVER_LIST_IGNORE_SORT_KEY_V273.remove('locked')
|
||||
|
||||
VALID_SORT_KEYS = {
|
||||
"type": "string",
|
||||
|
@ -545,6 +548,14 @@ VALID_SORT_KEYS = {
|
|||
SERVER_LIST_IGNORE_SORT_KEY
|
||||
}
|
||||
|
||||
# We reuse the existing list and add locked to the list of valid sort keys.
|
||||
VALID_SORT_KEYS_V273 = {
|
||||
"type": "string",
|
||||
"enum": ['locked'] + list(
|
||||
set(VALID_SORT_KEYS["enum"]) - set(SERVER_LIST_IGNORE_SORT_KEY)) +
|
||||
SERVER_LIST_IGNORE_SORT_KEY_V273
|
||||
}
|
||||
|
||||
query_params_v21 = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
|
@ -634,3 +645,9 @@ query_params_v266['properties'].update({
|
|||
'changes-before': multi_params({'type': 'string',
|
||||
'format': 'date-time'}),
|
||||
})
|
||||
|
||||
query_params_v273 = copy.deepcopy(query_params_v266)
|
||||
query_params_v273['properties'].update({
|
||||
'sort_key': multi_params(VALID_SORT_KEYS_V273),
|
||||
'locked': parameter_types.common_query_param,
|
||||
})
|
||||
|
|
|
@ -111,7 +111,8 @@ class ServersController(wsgi.Controller):
|
|||
self.network_api = network_api.API()
|
||||
|
||||
@wsgi.expected_errors((400, 403))
|
||||
@validation.query_schema(schema_servers.query_params_v266, '2.66')
|
||||
@validation.query_schema(schema_servers.query_params_v273, '2.73')
|
||||
@validation.query_schema(schema_servers.query_params_v266, '2.66', '2.72')
|
||||
@validation.query_schema(schema_servers.query_params_v226, '2.26', '2.65')
|
||||
@validation.query_schema(schema_servers.query_params_v21, '2.1', '2.25')
|
||||
def index(self, req):
|
||||
|
@ -125,7 +126,8 @@ class ServersController(wsgi.Controller):
|
|||
return servers
|
||||
|
||||
@wsgi.expected_errors((400, 403))
|
||||
@validation.query_schema(schema_servers.query_params_v266, '2.66')
|
||||
@validation.query_schema(schema_servers.query_params_v273, '2.73')
|
||||
@validation.query_schema(schema_servers.query_params_v266, '2.66', '2.72')
|
||||
@validation.query_schema(schema_servers.query_params_v226, '2.26', '2.65')
|
||||
@validation.query_schema(schema_servers.query_params_v21, '2.1', '2.25')
|
||||
def detail(self, req):
|
||||
|
@ -273,6 +275,9 @@ class ServersController(wsgi.Controller):
|
|||
# further down the stack.
|
||||
search_opts.pop('all_tenants', None)
|
||||
|
||||
if 'locked' in search_opts:
|
||||
search_opts['locked'] = common.is_locked(search_opts)
|
||||
|
||||
elevated = None
|
||||
if all_tenants:
|
||||
if is_detail:
|
||||
|
@ -291,9 +296,11 @@ class ServersController(wsgi.Controller):
|
|||
|
||||
limit, marker = common.get_limit_and_marker(req)
|
||||
sort_keys, sort_dirs = common.get_sort_params(req.params)
|
||||
blacklist = schema_servers.SERVER_LIST_IGNORE_SORT_KEY
|
||||
if api_version_request.is_supported(req, min_version='2.73'):
|
||||
blacklist = schema_servers.SERVER_LIST_IGNORE_SORT_KEY_V273
|
||||
sort_keys, sort_dirs = remove_invalid_sort_keys(
|
||||
context, sort_keys, sort_dirs,
|
||||
schema_servers.SERVER_LIST_IGNORE_SORT_KEY, ('host', 'node'))
|
||||
context, sort_keys, sort_dirs, blacklist, ('host', 'node'))
|
||||
|
||||
expected_attrs = []
|
||||
if is_detail:
|
||||
|
@ -303,6 +310,8 @@ class ServersController(wsgi.Controller):
|
|||
expected_attrs.append("tags")
|
||||
if api_version_request.is_supported(req, '2.63'):
|
||||
expected_attrs.append("trusted_certs")
|
||||
if api_version_request.is_supported(req, '2.73'):
|
||||
expected_attrs.append("system_metadata")
|
||||
|
||||
# merge our expected attrs with what the view builder needs for
|
||||
# showing details
|
||||
|
@ -1220,6 +1229,8 @@ class ServersController(wsgi.Controller):
|
|||
opt_list += TAG_SEARCH_FILTERS
|
||||
if api_version_request.is_supported(req, min_version='2.66'):
|
||||
opt_list += ('changes-before',)
|
||||
if api_version_request.is_supported(req, min_version='2.73'):
|
||||
opt_list += ('locked',)
|
||||
return opt_list
|
||||
|
||||
def _get_instance(self, context, instance_uuid):
|
||||
|
|
|
@ -330,6 +330,10 @@ class ViewBuilder(common.ViewBuilder):
|
|||
server["server"]["locked"] = (True if instance["locked_by"]
|
||||
else False)
|
||||
|
||||
if api_version_request.is_supported(request, min_version="2.73"):
|
||||
server["server"]["locked_reason"] = (instance.system_metadata.get(
|
||||
"locked_reason"))
|
||||
|
||||
if api_version_request.is_supported(request, min_version="2.19"):
|
||||
server["server"]["description"] = instance.get(
|
||||
"display_description")
|
||||
|
|
|
@ -4023,7 +4023,7 @@ class API(base.Base):
|
|||
return self.compute_rpcapi.get_console_output(context,
|
||||
instance=instance, tail_length=tail_length)
|
||||
|
||||
def lock(self, context, instance):
|
||||
def lock(self, context, instance, reason=None):
|
||||
"""Lock the given instance."""
|
||||
# Only update the lock if we are an admin (non-owner)
|
||||
is_owner = instance.project_id == context.project_id
|
||||
|
@ -4035,13 +4035,15 @@ class API(base.Base):
|
|||
instance_actions.LOCK)
|
||||
|
||||
@wrap_instance_event(prefix='api')
|
||||
def lock(self, context, instance):
|
||||
def lock(self, context, instance, reason=None):
|
||||
LOG.debug('Locking', instance=instance)
|
||||
instance.locked = True
|
||||
instance.locked_by = 'owner' if is_owner else 'admin'
|
||||
if reason:
|
||||
instance.system_metadata['locked_reason'] = reason
|
||||
instance.save()
|
||||
|
||||
lock(self, context, instance)
|
||||
lock(self, context, instance, reason=reason)
|
||||
compute_utils.notify_about_instance_action(
|
||||
context, instance, CONF.host,
|
||||
action=fields_obj.NotificationAction.LOCK,
|
||||
|
@ -4066,6 +4068,7 @@ class API(base.Base):
|
|||
LOG.debug('Unlocking', instance=instance)
|
||||
instance.locked = False
|
||||
instance.locked_by = None
|
||||
instance.system_metadata.pop('locked_reason', None)
|
||||
instance.save()
|
||||
|
||||
unlock(self, context, instance)
|
||||
|
|
|
@ -2036,7 +2036,7 @@ def instance_get_all_by_filters_sort(context, filters, limit=None, marker=None,
|
|||
|
||||
| ['project_id', 'user_id', 'image_ref',
|
||||
| 'vm_state', 'instance_type_id', 'uuid',
|
||||
| 'metadata', 'host', 'system_metadata']
|
||||
| 'metadata', 'host', 'system_metadata', 'locked']
|
||||
|
||||
|
||||
A third type of filter (also using exact matching), filters
|
||||
|
@ -2204,7 +2204,7 @@ def instance_get_all_by_filters_sort(context, filters, limit=None, marker=None,
|
|||
exact_match_filter_names = ['project_id', 'user_id', 'image_ref',
|
||||
'vm_state', 'instance_type_id', 'uuid',
|
||||
'metadata', 'host', 'task_state',
|
||||
'system_metadata']
|
||||
'system_metadata', 'locked']
|
||||
|
||||
# Filter the query
|
||||
query_prefix = _exact_instance_filter(query_prefix,
|
||||
|
|
|
@ -68,7 +68,8 @@ class InstancePayload(base.NotificationPayloadBase):
|
|||
# Version 1.6: Add request_id field
|
||||
# Version 1.7: Added action_initiator_user and action_initiator_project to
|
||||
# InstancePayload
|
||||
VERSION = '1.7'
|
||||
# Version 1.8: Added locked_reason field
|
||||
VERSION = '1.8'
|
||||
fields = {
|
||||
'uuid': fields.UUIDField(),
|
||||
'user_id': fields.StringField(nullable=True),
|
||||
|
@ -113,6 +114,7 @@ class InstancePayload(base.NotificationPayloadBase):
|
|||
'request_id': fields.StringField(nullable=True),
|
||||
'action_initiator_user': fields.StringField(nullable=True),
|
||||
'action_initiator_project': fields.StringField(nullable=True),
|
||||
'locked_reason': fields.StringField(nullable=True),
|
||||
}
|
||||
|
||||
def __init__(self, context, instance, bdms=None):
|
||||
|
@ -132,6 +134,7 @@ class InstancePayload(base.NotificationPayloadBase):
|
|||
context.user_id) else None
|
||||
self.action_initiator_user = context.user_id
|
||||
self.action_initiator_project = context.project_id
|
||||
self.locked_reason = instance.system_metadata.get("locked_reason")
|
||||
self.populate_schema(instance=instance)
|
||||
|
||||
|
||||
|
@ -147,7 +150,8 @@ class InstanceActionPayload(InstancePayload):
|
|||
# Version 1.6: Added request_id field to InstancePayload
|
||||
# Version 1.7: Added action_initiator_user and action_initiator_project to
|
||||
# InstancePayload
|
||||
VERSION = '1.7'
|
||||
# Version 1.8: Added locked_reason field to InstancePayload
|
||||
VERSION = '1.8'
|
||||
fields = {
|
||||
'fault': fields.ObjectField('ExceptionPayload', nullable=True),
|
||||
'request_id': fields.StringField(nullable=True),
|
||||
|
@ -169,7 +173,8 @@ class InstanceActionVolumePayload(InstanceActionPayload):
|
|||
# Version 1.4: Added request_id field to InstancePayload
|
||||
# Version 1.5: Added action_initiator_user and action_initiator_project to
|
||||
# InstancePayload
|
||||
VERSION = '1.5'
|
||||
# Version 1.6: Added locked_reason field to InstancePayload
|
||||
VERSION = '1.6'
|
||||
fields = {
|
||||
'volume_id': fields.UUIDField()
|
||||
}
|
||||
|
@ -194,7 +199,8 @@ class InstanceActionVolumeSwapPayload(InstanceActionPayload):
|
|||
# Version 1.6: Added request_id field to InstancePayload
|
||||
# Version 1.7: Added action_initiator_user and action_initiator_project to
|
||||
# InstancePayload
|
||||
VERSION = '1.7'
|
||||
# Version 1.8: Added locked_reason field to InstancePayload
|
||||
VERSION = '1.8'
|
||||
fields = {
|
||||
'old_volume_id': fields.UUIDField(),
|
||||
'new_volume_id': fields.UUIDField(),
|
||||
|
@ -229,7 +235,8 @@ class InstanceCreatePayload(InstanceActionPayload):
|
|||
# 1.10: Added action_initiator_user and action_initiator_project to
|
||||
# InstancePayload
|
||||
# 1.11: Added instance_name to InstanceCreatePayload
|
||||
VERSION = '1.11'
|
||||
# Version 1.12: Added locked_reason field to InstancePayload
|
||||
VERSION = '1.12'
|
||||
fields = {
|
||||
'keypairs': fields.ListOfObjectsField('KeypairPayload'),
|
||||
'tags': fields.ListOfStringsField(),
|
||||
|
@ -262,7 +269,8 @@ class InstanceActionResizePrepPayload(InstanceActionPayload):
|
|||
# Version 1.1: Added request_id field to InstancePayload
|
||||
# Version 1.2: Added action_initiator_user and action_initiator_project to
|
||||
# InstancePayload
|
||||
VERSION = '1.2'
|
||||
# Version 1.3: Added locked_reason field to InstancePayload
|
||||
VERSION = '1.3'
|
||||
fields = {
|
||||
'new_flavor': fields.ObjectField('FlavorPayload', nullable=True)
|
||||
}
|
||||
|
@ -287,7 +295,8 @@ class InstanceUpdatePayload(InstancePayload):
|
|||
# Version 1.7: Added request_id field to InstancePayload
|
||||
# Version 1.8: Added action_initiator_user and action_initiator_project to
|
||||
# InstancePayload
|
||||
VERSION = '1.8'
|
||||
# Version 1.9: Added locked_reason field to InstancePayload
|
||||
VERSION = '1.9'
|
||||
fields = {
|
||||
'state_update': fields.ObjectField('InstanceStateUpdatePayload'),
|
||||
'audit_period': fields.ObjectField('AuditPeriodPayload'),
|
||||
|
@ -314,7 +323,8 @@ class InstanceActionRescuePayload(InstanceActionPayload):
|
|||
# Version 1.1: Added request_id field to InstancePayload
|
||||
# Version 1.2: Added action_initiator_user and action_initiator_project to
|
||||
# InstancePayload
|
||||
VERSION = '1.2'
|
||||
# Version 1.3: Added locked_reason field to InstancePayload
|
||||
VERSION = '1.3'
|
||||
fields = {
|
||||
'rescue_image_ref': fields.UUIDField(nullable=True)
|
||||
}
|
||||
|
@ -338,7 +348,8 @@ class InstanceActionRebuildPayload(InstanceActionPayload):
|
|||
# signal the change of nova_object.name.
|
||||
# Version 1.8: Added action_initiator_user and action_initiator_project to
|
||||
# InstancePayload
|
||||
VERSION = '1.8'
|
||||
# Version 1.9: Added locked_reason field to InstancePayload
|
||||
VERSION = '1.9'
|
||||
fields = {
|
||||
'trusted_image_certificates': fields.ListOfStringsField(
|
||||
nullable=True)
|
||||
|
@ -688,7 +699,8 @@ class InstanceActionSnapshotPayload(InstanceActionPayload):
|
|||
# Version 1.7: Added request_id field to InstancePayload
|
||||
# Version 1.8: Added action_initiator_user and action_initiator_project to
|
||||
# InstancePayload
|
||||
VERSION = '1.8'
|
||||
# Version 1.9: Added locked_reason field to InstancePayload
|
||||
VERSION = '1.9'
|
||||
fields = {
|
||||
'snapshot_image_id': fields.UUIDField(),
|
||||
}
|
||||
|
@ -706,7 +718,8 @@ class InstanceExistsPayload(InstancePayload):
|
|||
# Version 1.0: Initial version
|
||||
# Version 1.1: Added action_initiator_user and action_initiator_project to
|
||||
# InstancePayload
|
||||
VERSION = '1.1'
|
||||
# Version 1.2: Added locked_reason field to InstancePayload
|
||||
VERSION = '1.2'
|
||||
fields = {
|
||||
'audit_period': fields.ObjectField('AuditPeriodPayload'),
|
||||
'bandwidth': fields.ListOfObjectsField('BandwidthPayload'),
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"lock": {"locked_reason": "I don't want to work"}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"lock": null
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"unlock": null
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"lock": {"locked_reason": "I don't want to work"}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"server": {
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"accessIPv4": "1.2.3.4",
|
||||
"accessIPv6": "80fe::",
|
||||
"addresses": {
|
||||
"private": [
|
||||
{
|
||||
"addr": "192.168.0.3",
|
||||
"version": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
"adminPass": "seekr3t",
|
||||
"created": "%(isotime)s",
|
||||
"description": null,
|
||||
"flavor": {
|
||||
"disk": 1,
|
||||
"ephemeral": 0,
|
||||
"extra_specs": {},
|
||||
"original_name": "m1.tiny",
|
||||
"ram": 512,
|
||||
"swap": 0,
|
||||
"vcpus": 1
|
||||
},
|
||||
"hostId": "2091634baaccdc4c5a1d57069c833e402921df696b7f970791b12ec6",
|
||||
"id": "%(id)s",
|
||||
"image": {
|
||||
"id": "%(uuid)s",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(compute_endpoint)s/images/%(uuid)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key_name": null,
|
||||
"links": [
|
||||
{
|
||||
"href": "%(versioned_compute_endpoint)s/servers/%(uuid)s",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "%(compute_endpoint)s/servers/%(id)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"locked": false,
|
||||
"locked_reason": null,
|
||||
"metadata": {
|
||||
"meta_var": "meta_val"
|
||||
},
|
||||
"name": "foobar",
|
||||
"progress": 0,
|
||||
"server_groups": [],
|
||||
"status": "ACTIVE",
|
||||
"tags": [],
|
||||
"tenant_id": "6f70656e737461636b20342065766572",
|
||||
"trusted_image_certificates": null,
|
||||
"updated": "%(isotime)s",
|
||||
"user_data": "ZWNobyAiaGVsbG8gd29ybGQi",
|
||||
"user_id": "fake"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"rebuild" : {
|
||||
"accessIPv4" : "%(access_ip_v4)s",
|
||||
"accessIPv6" : "%(access_ip_v6)s",
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"imageRef" : "%(uuid)s",
|
||||
"name" : "%(name)s",
|
||||
"adminPass" : "%(pass)s",
|
||||
"metadata" : {
|
||||
"meta_var" : "meta_val"
|
||||
},
|
||||
"user_data": "ZWNobyAiaGVsbG8gd29ybGQi"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"server" : {
|
||||
"accessIPv4": "%(access_ip_v4)s",
|
||||
"accessIPv6": "%(access_ip_v6)s",
|
||||
"name" : "new-server-test",
|
||||
"imageRef" : "%(image_id)s",
|
||||
"flavorRef" : "1",
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"metadata" : {
|
||||
"My Server Name" : "Apache1"
|
||||
},
|
||||
"security_groups": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
],
|
||||
"user_data" : "%(user_data)s",
|
||||
"networks": "auto"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"server": {
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"adminPass": "%(password)s",
|
||||
"id": "%(id)s",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(versioned_compute_endpoint)s/servers/%(uuid)s",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "%(compute_endpoint)s/servers/%(uuid)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"security_groups": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
{
|
||||
"server": {
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"OS-EXT-AZ:availability_zone": "nova",
|
||||
"OS-EXT-SRV-ATTR:host": "compute",
|
||||
"OS-EXT-SRV-ATTR:hostname": "new-server-test",
|
||||
"OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini",
|
||||
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
|
||||
"OS-EXT-SRV-ATTR:kernel_id": "",
|
||||
"OS-EXT-SRV-ATTR:launch_index": 0,
|
||||
"OS-EXT-SRV-ATTR:ramdisk_id": "",
|
||||
"OS-EXT-SRV-ATTR:reservation_id": "%(reservation_id)s",
|
||||
"OS-EXT-SRV-ATTR:root_device_name": "/dev/sda",
|
||||
"OS-EXT-SRV-ATTR:user_data": "IyEvYmluL2Jhc2gKL2Jpbi9zdQplY2hvICJJIGFtIGluIHlvdSEiCg==",
|
||||
"OS-EXT-STS:power_state": 1,
|
||||
"OS-EXT-STS:task_state": null,
|
||||
"OS-EXT-STS:vm_state": "active",
|
||||
"OS-SRV-USG:launched_at": "%(strtime)s",
|
||||
"OS-SRV-USG:terminated_at": null,
|
||||
"accessIPv4": "1.2.3.4",
|
||||
"accessIPv6": "80fe::",
|
||||
"addresses": {
|
||||
"private": [
|
||||
{
|
||||
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
|
||||
"OS-EXT-IPS:type": "fixed",
|
||||
"addr": "192.168.0.3",
|
||||
"version": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
"config_drive": "",
|
||||
"created": "%(isotime)s",
|
||||
"description": null,
|
||||
"flavor": {
|
||||
"disk": 1,
|
||||
"ephemeral": 0,
|
||||
"extra_specs": {},
|
||||
"original_name": "m1.tiny",
|
||||
"ram": 512,
|
||||
"swap": 0,
|
||||
"vcpus": 1
|
||||
},
|
||||
"hostId": "2091634baaccdc4c5a1d57069c833e402921df696b7f970791b12ec6",
|
||||
"host_status": "UP",
|
||||
"id": "%(id)s",
|
||||
"image": {
|
||||
"id": "%(uuid)s",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(compute_endpoint)s/images/%(uuid)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key_name": null,
|
||||
"links": [
|
||||
{
|
||||
"href": "%(versioned_compute_endpoint)s/servers/%(uuid)s",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "%(compute_endpoint)s/servers/%(id)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"locked": true,
|
||||
"locked_reason": "I don't want to work",
|
||||
"metadata": {
|
||||
"My Server Name": "Apache1"
|
||||
},
|
||||
"name": "new-server-test",
|
||||
"os-extended-volumes:volumes_attached": [],
|
||||
"progress": 0,
|
||||
"security_groups": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
],
|
||||
"server_groups": [],
|
||||
"status": "ACTIVE",
|
||||
"tags": [],
|
||||
"tenant_id": "6f70656e737461636b20342065766572",
|
||||
"trusted_image_certificates": null,
|
||||
"updated": "%(isotime)s",
|
||||
"user_id": "fake"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"server": {
|
||||
"accessIPv4": "%(access_ip_v4)s",
|
||||
"accessIPv6": "%(access_ip_v6)s",
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"name": "new-server-test",
|
||||
"description": "Sample description"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"server": {
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"accessIPv4": "1.2.3.4",
|
||||
"accessIPv6": "80fe::",
|
||||
"addresses": {
|
||||
"private": [
|
||||
{
|
||||
"addr": "192.168.0.3",
|
||||
"version": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
"created": "%(isotime)s",
|
||||
"description": "Sample description",
|
||||
"flavor": {
|
||||
"disk": 1,
|
||||
"ephemeral": 0,
|
||||
"extra_specs": {},
|
||||
"original_name": "m1.tiny",
|
||||
"ram": 512,
|
||||
"swap": 0,
|
||||
"vcpus": 1
|
||||
},
|
||||
"hostId": "2091634baaccdc4c5a1d57069c833e402921df696b7f970791b12ec6",
|
||||
"id": "%(id)s",
|
||||
"image": {
|
||||
"id": "%(uuid)s",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(compute_endpoint)s/images/%(uuid)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"href": "%(versioned_compute_endpoint)s/servers/%(uuid)s",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "%(compute_endpoint)s/servers/%(id)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"locked": false,
|
||||
"locked_reason": null,
|
||||
"metadata": {
|
||||
"My Server Name": "Apache1"
|
||||
},
|
||||
"name": "new-server-test",
|
||||
"progress": 0,
|
||||
"server_groups": [],
|
||||
"status": "ACTIVE",
|
||||
"tags": [],
|
||||
"tenant_id": "6f70656e737461636b20342065766572",
|
||||
"trusted_image_certificates": null,
|
||||
"updated": "%(isotime)s",
|
||||
"user_id": "fake"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
{
|
||||
"servers": [
|
||||
{
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"OS-EXT-AZ:availability_zone": "nova",
|
||||
"OS-EXT-SRV-ATTR:host": "compute",
|
||||
"OS-EXT-SRV-ATTR:hostname": "new-server-test",
|
||||
"OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini",
|
||||
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
|
||||
"OS-EXT-SRV-ATTR:kernel_id": "",
|
||||
"OS-EXT-SRV-ATTR:launch_index": 0,
|
||||
"OS-EXT-SRV-ATTR:ramdisk_id": "",
|
||||
"OS-EXT-SRV-ATTR:reservation_id": "%(reservation_id)s",
|
||||
"OS-EXT-SRV-ATTR:root_device_name": "/dev/sda",
|
||||
"OS-EXT-SRV-ATTR:user_data": "IyEvYmluL2Jhc2gKL2Jpbi9zdQplY2hvICJJIGFtIGluIHlvdSEiCg==",
|
||||
"OS-EXT-STS:power_state": 1,
|
||||
"OS-EXT-STS:task_state": null,
|
||||
"OS-EXT-STS:vm_state": "active",
|
||||
"OS-SRV-USG:launched_at": "%(strtime)s",
|
||||
"OS-SRV-USG:terminated_at": null,
|
||||
"accessIPv4": "1.2.3.4",
|
||||
"accessIPv6": "80fe::",
|
||||
"addresses": {
|
||||
"private": [
|
||||
{
|
||||
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
|
||||
"OS-EXT-IPS:type": "fixed",
|
||||
"addr": "%(ip)s",
|
||||
"version": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
"config_drive": "",
|
||||
"created": "%(isotime)s",
|
||||
"description": null,
|
||||
"flavor": {
|
||||
"disk": 1,
|
||||
"ephemeral": 0,
|
||||
"extra_specs": {},
|
||||
"original_name": "m1.tiny",
|
||||
"ram": 512,
|
||||
"swap": 0,
|
||||
"vcpus": 1
|
||||
},
|
||||
"hostId": "2091634baaccdc4c5a1d57069c833e402921df696b7f970791b12ec6",
|
||||
"host_status": "UP",
|
||||
"id": "%(id)s",
|
||||
"image": {
|
||||
"id": "%(uuid)s",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(compute_endpoint)s/images/%(uuid)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key_name": null,
|
||||
"links": [
|
||||
{
|
||||
"href": "%(versioned_compute_endpoint)s/servers/%(uuid)s",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "%(compute_endpoint)s/servers/%(id)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"locked": true,
|
||||
"locked_reason": "I don't want to work",
|
||||
"metadata": {
|
||||
"My Server Name": "Apache1"
|
||||
},
|
||||
"name": "new-server-test",
|
||||
"os-extended-volumes:volumes_attached": [],
|
||||
"progress": 0,
|
||||
"security_groups": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
],
|
||||
"status": "ACTIVE",
|
||||
"tags": [],
|
||||
"tenant_id": "6f70656e737461636b20342065766572",
|
||||
"trusted_image_certificates": null,
|
||||
"updated": "%(isotime)s",
|
||||
"user_id": "fake"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -39,3 +39,38 @@ class LockServerSamplesJsonTest(test_servers.ServersSampleBase):
|
|||
response = self._do_post('servers/%s/action' % self.uuid,
|
||||
'unlock-server', {})
|
||||
self.assertEqual(202, response.status_code)
|
||||
|
||||
|
||||
class LockServerSamplesJsonTestV273(test_servers.ServersSampleBase):
|
||||
sample_dir = "os-lock-server"
|
||||
microversion = '2.73'
|
||||
scenarios = [('v2_73', {'api_major_version': 'v2.1'})]
|
||||
|
||||
def setUp(self):
|
||||
"""setUp Method for LockServer api samples extension
|
||||
|
||||
This method creates the server that will be used in each test
|
||||
"""
|
||||
super(LockServerSamplesJsonTestV273, self).setUp()
|
||||
self.uuid = self._post_server()
|
||||
|
||||
def test_post_lock_server(self):
|
||||
# backwards compatibility.
|
||||
response = self._do_post('servers/%s/action' % self.uuid,
|
||||
name='lock-server', subs={})
|
||||
self.assertEqual(202, response.status_code)
|
||||
|
||||
def test_post_lock_server_with_reason(self):
|
||||
# Get api samples to lock server request.
|
||||
response = self._do_post('servers/%s/action' % self.uuid,
|
||||
name='lock-server-with-reason', subs={})
|
||||
self.assertEqual(202, response.status_code)
|
||||
|
||||
def test_post_unlock_server(self):
|
||||
# Get api samples to unlock server request.
|
||||
# We first call the previous test to lock the server with reason
|
||||
# and then unlock it to post a response for unlock.
|
||||
self.test_post_lock_server_with_reason()
|
||||
response = self._do_post('servers/%s/action' % self.uuid,
|
||||
name='unlock-server', subs={})
|
||||
self.assertEqual(202, response.status_code)
|
||||
|
|
|
@ -515,6 +515,62 @@ class ServersSampleJson271Test(ServersSampleBase):
|
|||
subs, response, 200)
|
||||
|
||||
|
||||
class ServersSampleJson273Test(ServersSampleBase):
|
||||
microversion = '2.73'
|
||||
scenarios = [('v2_73', {'api_major_version': 'v2.1'})]
|
||||
|
||||
def setUp(self):
|
||||
super(ServersSampleJson273Test, self).setUp()
|
||||
|
||||
def _post_server_and_lock(self):
|
||||
uuid = self._post_server(use_common_server_api_samples=False)
|
||||
reason = "I don't want to work"
|
||||
self._do_post('servers/%s/action' % uuid,
|
||||
'lock-server-with-reason',
|
||||
{"locked_reason": reason})
|
||||
return uuid
|
||||
|
||||
def test_servers_details_with_locked_reason(self):
|
||||
uuid = self._post_server_and_lock()
|
||||
response = self._do_get('servers/detail')
|
||||
subs = {'id': uuid}
|
||||
self._verify_response('servers-details-resp', subs, response, 200)
|
||||
|
||||
def test_server_get_with_locked_reason(self):
|
||||
uuid = self._post_server_and_lock()
|
||||
response = self._do_get('servers/%s' % uuid)
|
||||
subs = {'id': uuid}
|
||||
self._verify_response('server-get-resp', subs, response, 200)
|
||||
|
||||
def test_server_rebuild_with_empty_locked_reason(self):
|
||||
uuid = self._post_server(use_common_server_api_samples=False)
|
||||
image = fake.get_valid_image_id()
|
||||
params = {
|
||||
'uuid': image,
|
||||
'name': 'foobar',
|
||||
'pass': 'seekr3t',
|
||||
'hostid': '[a-f0-9]+',
|
||||
'access_ip_v4': '1.2.3.4',
|
||||
'access_ip_v6': '80fe::',
|
||||
}
|
||||
|
||||
resp = self._do_post('servers/%s/action' % uuid,
|
||||
'server-action-rebuild', params)
|
||||
subs = params.copy()
|
||||
del subs['uuid']
|
||||
self._verify_response('server-action-rebuild-resp', subs, resp, 202)
|
||||
|
||||
def test_update_server_with_empty_locked_reason(self):
|
||||
uuid = self._post_server(use_common_server_api_samples=False)
|
||||
subs = {}
|
||||
subs['hostid'] = '[a-f0-9]+'
|
||||
subs['access_ip_v4'] = '1.2.3.4'
|
||||
subs['access_ip_v6'] = '80fe::'
|
||||
response = self._do_put('servers/%s' % uuid,
|
||||
'server-update-req', subs)
|
||||
self._verify_response('server-update-resp', subs, response, 200)
|
||||
|
||||
|
||||
class ServersUpdateSampleJsonTest(ServersSampleBase):
|
||||
|
||||
def test_update_server(self):
|
||||
|
|
|
@ -400,6 +400,7 @@ class TestInstanceNotificationSample(
|
|||
self._test_interface_attach_and_detach,
|
||||
self._test_interface_attach_error,
|
||||
self._test_lock_unlock_instance,
|
||||
self._test_lock_unlock_instance_with_reason,
|
||||
]
|
||||
|
||||
for action in actions:
|
||||
|
@ -1934,6 +1935,30 @@ class TestInstanceNotificationSample(
|
|||
'uuid': server['id']},
|
||||
actual=fake_notifier.VERSIONED_NOTIFICATIONS[1])
|
||||
|
||||
def _test_lock_unlock_instance_with_reason(self, server):
|
||||
self.api.post_server_action(
|
||||
server['id'], {'lock': {"locked_reason": "global warming"}})
|
||||
self._wait_for_server_parameter(self.api, server, {'locked': True})
|
||||
self.api.post_server_action(server['id'], {'unlock': {}})
|
||||
self._wait_for_server_parameter(self.api, server, {'locked': False})
|
||||
# Two versioned notifications are generated
|
||||
# 0. instance-lock
|
||||
# 1. instance-unlock
|
||||
|
||||
self.assertEqual(2, len(fake_notifier.VERSIONED_NOTIFICATIONS))
|
||||
self._verify_notification(
|
||||
'instance-lock-with-reason',
|
||||
replacements={
|
||||
'reservation_id': server['reservation_id'],
|
||||
'uuid': server['id']},
|
||||
actual=fake_notifier.VERSIONED_NOTIFICATIONS[0])
|
||||
self._verify_notification(
|
||||
'instance-unlock',
|
||||
replacements={
|
||||
'reservation_id': server['reservation_id'],
|
||||
'uuid': server['id']},
|
||||
actual=fake_notifier.VERSIONED_NOTIFICATIONS[1])
|
||||
|
||||
|
||||
class TestInstanceNotificationSampleOldAttachFlow(
|
||||
TestInstanceNotificationSample):
|
||||
|
|
|
@ -14,7 +14,9 @@
|
|||
# under the License.
|
||||
|
||||
import mock
|
||||
import six
|
||||
|
||||
from nova.api.openstack import api_version_request
|
||||
from nova.api.openstack import common
|
||||
from nova.api.openstack.compute import lock_server as lock_server_v21
|
||||
from nova import context
|
||||
|
@ -40,10 +42,15 @@ class LockServerTestsV21(admin_only_action_common.CommonTests):
|
|||
lambda *a, **kw: self.controller)
|
||||
|
||||
def test_lock_unlock(self):
|
||||
self._test_actions(['_lock', '_unlock'])
|
||||
args_map = {'_lock': ((), {"reason": None})}
|
||||
body_map = {'_lock': {"lock": None}}
|
||||
self._test_actions(['_lock', '_unlock'], args_map=args_map,
|
||||
body_map=body_map)
|
||||
|
||||
def test_lock_unlock_with_non_existed_instance(self):
|
||||
self._test_actions_with_non_existed_instance(['_lock', '_unlock'])
|
||||
body_map = {'_lock': {"lock": None}}
|
||||
self._test_actions_with_non_existed_instance(['_lock', '_unlock'],
|
||||
body_map=body_map)
|
||||
|
||||
def test_unlock_not_authorized(self):
|
||||
instance = self._stub_instance_get()
|
||||
|
@ -83,6 +90,84 @@ class LockServerTestsV21(admin_only_action_common.CommonTests):
|
|||
self.controller._unlock(admin_req, instance.uuid, {'unlock': None})
|
||||
mock_unlock.assert_called_once_with(admin_ctxt, instance)
|
||||
|
||||
@mock.patch.object(common, 'get_instance')
|
||||
def test_unlock_with_any_body(self, get_instance_mock):
|
||||
instance = fake_instance.fake_instance_obj(
|
||||
self.req.environ['nova.context'])
|
||||
get_instance_mock.return_value = instance
|
||||
# This will pass since there is no schema validation.
|
||||
body = {'unlock': {'blah': 'blah'}}
|
||||
|
||||
with mock.patch.object(self.compute_api, 'unlock') as mock_lock:
|
||||
self.controller._unlock(self.req, instance.uuid, body=body)
|
||||
mock_lock.assert_called_once_with(
|
||||
self.req.environ['nova.context'], instance)
|
||||
|
||||
@mock.patch.object(common, 'get_instance')
|
||||
def test_lock_with_empty_dict_body_is_valid(self, get_instance_mock):
|
||||
# Empty dict with no key in the body is allowed.
|
||||
instance = fake_instance.fake_instance_obj(
|
||||
self.req.environ['nova.context'])
|
||||
get_instance_mock.return_value = instance
|
||||
body = {'lock': {}}
|
||||
|
||||
with mock.patch.object(self.compute_api, 'lock') as mock_lock:
|
||||
self.controller._lock(self.req, instance.uuid, body=body)
|
||||
mock_lock.assert_called_once_with(
|
||||
self.req.environ['nova.context'], instance, reason=None)
|
||||
|
||||
|
||||
class LockServerTestsV273(LockServerTestsV21):
|
||||
|
||||
def setUp(self):
|
||||
super(LockServerTestsV273, self).setUp()
|
||||
self.req.api_version_request = api_version_request.APIVersionRequest(
|
||||
'2.73')
|
||||
|
||||
@mock.patch.object(common, 'get_instance')
|
||||
def test_lock_with_reason_V273(self, get_instance_mock):
|
||||
instance = fake_instance.fake_instance_obj(
|
||||
self.req.environ['nova.context'])
|
||||
get_instance_mock.return_value = instance
|
||||
reason = "I don't want to work"
|
||||
body = {'lock': {"locked_reason": reason}}
|
||||
|
||||
with mock.patch.object(self.compute_api, 'lock') as mock_lock:
|
||||
self.controller._lock(self.req, instance.uuid, body=body)
|
||||
mock_lock.assert_called_once_with(
|
||||
self.req.environ['nova.context'], instance, reason=reason)
|
||||
|
||||
def test_lock_with_reason_exceeding_255_chars(self):
|
||||
instance = fake_instance.fake_instance_obj(
|
||||
self.req.environ['nova.context'])
|
||||
reason = 's' * 256
|
||||
body = {'lock': {"locked_reason": reason}}
|
||||
|
||||
exp = self.assertRaises(exception.ValidationError,
|
||||
self.controller._lock, self.req, instance.uuid, body=body)
|
||||
self.assertIn('is too long', six.text_type(exp))
|
||||
|
||||
def test_lock_with_reason_in_invalid_format(self):
|
||||
instance = fake_instance.fake_instance_obj(
|
||||
self.req.environ['nova.context'])
|
||||
reason = 256
|
||||
body = {'lock': {"locked_reason": reason}}
|
||||
|
||||
exp = self.assertRaises(exception.ValidationError,
|
||||
self.controller._lock, self.req, instance.uuid, body=body)
|
||||
self.assertIn("256 is not of type 'string'", six.text_type(exp))
|
||||
|
||||
def test_lock_with_invalid_paramater(self):
|
||||
# This will fail from 2.73 since we have a schema check that allows
|
||||
# only locked_reason
|
||||
instance = fake_instance.fake_instance_obj(
|
||||
self.req.environ['nova.context'])
|
||||
body = {'lock': {'blah': 'blah'}}
|
||||
|
||||
exp = self.assertRaises(exception.ValidationError,
|
||||
self.controller._lock, self.req, instance.uuid, body=body)
|
||||
self.assertIn("('blah' was unexpected)", six.text_type(exp))
|
||||
|
||||
|
||||
class LockServerPolicyEnforcementV21(test.NoDBTestCase):
|
||||
|
||||
|
@ -138,7 +223,7 @@ class LockServerPolicyEnforcementV21(test.NoDBTestCase):
|
|||
self.policy.set_rules({rule_name: "user_id:%(user_id)s"})
|
||||
self.controller._lock(self.req, fakes.FAKE_UUID, body={'lock': {}})
|
||||
lock_mock.assert_called_once_with(self.req.environ['nova.context'],
|
||||
instance)
|
||||
instance, reason=None)
|
||||
|
||||
def test_unlock_policy_failed(self):
|
||||
rule_name = "os_compute_api:os-lock-server:unlock"
|
||||
|
|
|
@ -793,7 +793,7 @@ class ServersControllerTest(ControllerTest):
|
|||
self.assertEqual(0, len(res_dict['servers']))
|
||||
self.mock_get_all.assert_called_once_with(
|
||||
req.environ['nova.context'],
|
||||
expected_attrs=expected_attrs,
|
||||
expected_attrs=sorted(expected_attrs),
|
||||
limit=1000, marker=None,
|
||||
search_opts={'deleted': False, 'project_id': 'fake'},
|
||||
sort_dirs=['desc'], sort_keys=['created_at'],
|
||||
|
@ -918,6 +918,15 @@ class ServersControllerTest(ControllerTest):
|
|||
expected_attrs=mock.ANY, sort_keys=[], sort_dirs=[],
|
||||
cell_down_support=False, all_tenants=False)
|
||||
|
||||
def test_get_servers_ignore_locked_sort_key(self):
|
||||
# Prior to microversion 2.73 locked sort key is ignored.
|
||||
req = self.req('/fake/servers?sort_key=locked&sort_dir=asc')
|
||||
self.controller.detail(req)
|
||||
self.mock_get_all.assert_called_once_with(
|
||||
mock.ANY, search_opts=mock.ANY, limit=mock.ANY, marker=mock.ANY,
|
||||
expected_attrs=mock.ANY, sort_keys=[], sort_dirs=[],
|
||||
cell_down_support=False, all_tenants=False)
|
||||
|
||||
def test_get_servers_ignore_sort_key_only_one_dir(self):
|
||||
req = self.req(
|
||||
'/fake/servers?sort_key=user_id&sort_key=vcpus&sort_dir=asc')
|
||||
|
@ -980,6 +989,30 @@ class ServersControllerTest(ControllerTest):
|
|||
sort_dirs=['desc'], sort_keys=['created_at'],
|
||||
cell_down_support=False, all_tenants=False)
|
||||
|
||||
def test_get_servers_with_locked_filter(self):
|
||||
# Prior to microversion 2.73 locked filter parameter is ignored.
|
||||
def fake_get_all(context, search_opts=None,
|
||||
limit=None, marker=None,
|
||||
expected_attrs=None, sort_keys=None, sort_dirs=None,
|
||||
cell_down_support=False, all_tenants=False):
|
||||
db_list = [fakes.stub_instance(100, uuid=uuids.fake)]
|
||||
return instance_obj._make_instance_list(
|
||||
context, objects.InstanceList(), db_list, FIELDS)
|
||||
|
||||
self.mock_get_all.side_effect = fake_get_all
|
||||
|
||||
req = self.req('/fake/servers?locked=true')
|
||||
servers = self.controller.index(req)['servers']
|
||||
|
||||
self.assertEqual(1, len(servers))
|
||||
self.assertEqual(uuids.fake, servers[0]['id'])
|
||||
self.mock_get_all.assert_called_once_with(
|
||||
req.environ['nova.context'], expected_attrs=[],
|
||||
limit=1000, marker=None,
|
||||
search_opts={'deleted': False, 'project_id': 'fake'},
|
||||
sort_dirs=['desc'], sort_keys=['created_at'],
|
||||
cell_down_support=False, all_tenants=False)
|
||||
|
||||
def test_get_servers_allows_image(self):
|
||||
def fake_get_all(context, search_opts=None,
|
||||
limit=None, marker=None,
|
||||
|
@ -2514,6 +2547,107 @@ class ServersControllerTestV271(ControllerTest):
|
|||
self.assertEqual(expect_sg, servers['server']['server_groups'])
|
||||
|
||||
|
||||
class ServersControllerTestV273(ControllerTest):
|
||||
"""Server Controller test for microversion 2.73
|
||||
|
||||
The intent here is simply to verify that when showing server details
|
||||
after microversion 2.73 the response will also have the locked_reason
|
||||
key for the servers.
|
||||
"""
|
||||
wsgi_api_version = '2.73'
|
||||
|
||||
def setUp(self):
|
||||
super(ServersControllerTestV273, self).setUp()
|
||||
|
||||
def req(self, url, use_admin_context=False):
|
||||
return fakes.HTTPRequest.blank(url,
|
||||
use_admin_context=use_admin_context,
|
||||
version=self.wsgi_api_version)
|
||||
|
||||
def test_get_servers_with_locked_filter(self):
|
||||
def fake_get_all(context, search_opts=None,
|
||||
limit=None, marker=None,
|
||||
expected_attrs=None, sort_keys=None, sort_dirs=None,
|
||||
cell_down_support=False, all_tenants=False):
|
||||
db_list = [fakes.stub_instance(
|
||||
100, uuid=uuids.fake, locked_by='fake')]
|
||||
return instance_obj._make_instance_list(
|
||||
context, objects.InstanceList(), db_list, FIELDS)
|
||||
|
||||
self.mock_get_all.side_effect = fake_get_all
|
||||
|
||||
req = self.req('/fake/servers?locked=true')
|
||||
servers = self.controller.index(req)['servers']
|
||||
|
||||
self.assertEqual(1, len(servers))
|
||||
self.assertEqual(uuids.fake, servers[0]['id'])
|
||||
search = {'deleted': False, 'project_id': 'fake', 'locked': True}
|
||||
self.mock_get_all.assert_called_once_with(
|
||||
req.environ['nova.context'], expected_attrs=[],
|
||||
limit=1000, marker=None,
|
||||
search_opts=search,
|
||||
sort_dirs=['desc'], sort_keys=['created_at'],
|
||||
cell_down_support=False, all_tenants=False)
|
||||
|
||||
def test_get_servers_with_locked_filter_invalid_value(self):
|
||||
def fake_get_all(context, search_opts=None,
|
||||
limit=None, marker=None,
|
||||
expected_attrs=None, sort_keys=None, sort_dirs=None,
|
||||
cell_down_support=False, all_tenants=False):
|
||||
db_list = [fakes.stub_instance(
|
||||
100, uuid=uuids.fake, locked_by='fake')]
|
||||
return instance_obj._make_instance_list(
|
||||
context, objects.InstanceList(), db_list, FIELDS)
|
||||
|
||||
self.mock_get_all.side_effect = fake_get_all
|
||||
|
||||
req = self.req('/fake/servers?locked=price')
|
||||
exp = self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.index, req)
|
||||
self.assertIn("Unrecognized value 'price'", six.text_type(exp))
|
||||
|
||||
def test_get_servers_with_locked_filter_empty_value(self):
|
||||
def fake_get_all(context, search_opts=None,
|
||||
limit=None, marker=None,
|
||||
expected_attrs=None, sort_keys=None, sort_dirs=None,
|
||||
cell_down_support=False, all_tenants=False):
|
||||
db_list = [fakes.stub_instance(
|
||||
100, uuid=uuids.fake, locked_by='fake')]
|
||||
return instance_obj._make_instance_list(
|
||||
context, objects.InstanceList(), db_list, FIELDS)
|
||||
|
||||
self.mock_get_all.side_effect = fake_get_all
|
||||
|
||||
req = self.req('/fake/servers?locked=')
|
||||
exp = self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.index, req)
|
||||
self.assertIn("Unrecognized value ''", six.text_type(exp))
|
||||
|
||||
def test_get_servers_with_locked_sort_key(self):
|
||||
def fake_get_all(context, search_opts=None,
|
||||
limit=None, marker=None,
|
||||
expected_attrs=None, sort_keys=None, sort_dirs=None,
|
||||
cell_down_support=False, all_tenants=False):
|
||||
db_list = [fakes.stub_instance(
|
||||
100, uuid=uuids.fake, locked_by='fake')]
|
||||
return instance_obj._make_instance_list(
|
||||
context, objects.InstanceList(), db_list, FIELDS)
|
||||
|
||||
self.mock_get_all.side_effect = fake_get_all
|
||||
|
||||
req = self.req('/fake/servers?sort_dir=desc&sort_key=locked')
|
||||
servers = self.controller.index(req)['servers']
|
||||
|
||||
self.assertEqual(1, len(servers))
|
||||
self.assertEqual(uuids.fake, servers[0]['id'])
|
||||
self.mock_get_all.assert_called_once_with(
|
||||
req.environ['nova.context'], expected_attrs=[],
|
||||
limit=1000, marker=None,
|
||||
search_opts={'deleted': False, 'project_id': 'fake'},
|
||||
sort_dirs=['desc'], sort_keys=['locked'],
|
||||
cell_down_support=False, all_tenants=False)
|
||||
|
||||
|
||||
class ServersControllerDeleteTest(ControllerTest):
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
@ -11257,6 +11257,28 @@ class ComputeAPITestCase(BaseTestCase):
|
|||
self.context, instance, CONF.host, action='lock',
|
||||
source='nova-api')
|
||||
|
||||
@mock.patch('nova.compute.utils.notify_about_instance_action')
|
||||
@mock.patch('nova.context.RequestContext.elevated')
|
||||
@mock.patch('nova.compute.api.API._record_action_start')
|
||||
@mock.patch.object(compute_utils, 'EventReporter')
|
||||
def test_lock_with_reason(self, mock_event, mock_record, mock_elevate,
|
||||
mock_notify):
|
||||
mock_elevate.return_value = self.context
|
||||
instance = self._create_fake_instance_obj()
|
||||
self.assertNotIn("locked_reason", instance.system_metadata)
|
||||
self.compute_api.lock(self.context, instance, reason="blah")
|
||||
self.assertEqual("blah", instance.system_metadata["locked_reason"])
|
||||
mock_record.assert_called_once_with(
|
||||
self.context, instance, instance_actions.LOCK
|
||||
)
|
||||
mock_event.assert_called_once_with(self.context,
|
||||
'api_lock',
|
||||
CONF.host,
|
||||
instance.uuid)
|
||||
mock_notify.assert_called_once_with(
|
||||
self.context, instance, CONF.host, action='lock',
|
||||
source='nova-api')
|
||||
|
||||
@mock.patch('nova.compute.utils.notify_about_instance_action')
|
||||
@mock.patch('nova.context.RequestContext.elevated')
|
||||
@mock.patch('nova.compute.api.API._record_action_start')
|
||||
|
@ -11278,6 +11300,29 @@ class ComputeAPITestCase(BaseTestCase):
|
|||
self.context, instance, CONF.host, action='unlock',
|
||||
source='nova-api')
|
||||
|
||||
@mock.patch('nova.compute.utils.notify_about_instance_action')
|
||||
@mock.patch('nova.context.RequestContext.elevated')
|
||||
@mock.patch('nova.compute.api.API._record_action_start')
|
||||
@mock.patch.object(compute_utils, 'EventReporter')
|
||||
def test_unlock_with_reason(self, mock_event, mock_record, mock_elevate,
|
||||
mock_notify):
|
||||
mock_elevate.return_value = self.context
|
||||
sm = {"locked_reason": "blah"}
|
||||
instance = self._create_fake_instance_obj(
|
||||
params={"system_metadata": sm})
|
||||
self.compute_api.unlock(self.context, instance)
|
||||
self.assertNotIn("locked_reason", instance.system_metadata)
|
||||
mock_record.assert_called_once_with(
|
||||
self.context, instance, instance_actions.UNLOCK
|
||||
)
|
||||
mock_event.assert_called_once_with(self.context,
|
||||
'api_unlock',
|
||||
CONF.host,
|
||||
instance.uuid)
|
||||
mock_notify.assert_called_once_with(
|
||||
self.context, instance, CONF.host, action='unlock',
|
||||
source='nova-api')
|
||||
|
||||
def test_add_remove_security_group(self):
|
||||
instance = self._create_fake_instance_obj()
|
||||
|
||||
|
|
|
@ -2236,6 +2236,19 @@ class InstanceTestCase(test.TestCase, ModelsObjectComparatorMixin):
|
|||
{'host': 'host1'})
|
||||
self._assertEqualListsOfInstances([instance], result)
|
||||
|
||||
def test_instance_get_all_by_filters_locked_key_true(self):
|
||||
instance = self.create_instance_with_args(locked=True)
|
||||
self.create_instance_with_args(locked=False)
|
||||
result = db.instance_get_all_by_filters(self.ctxt,
|
||||
{'locked': True})
|
||||
self._assertEqualListsOfInstances([instance], result)
|
||||
|
||||
def test_instance_get_all_by_filters_locked_key_false(self):
|
||||
self.create_instance_with_args(locked=True)
|
||||
result = db.instance_get_all_by_filters(self.ctxt,
|
||||
{'locked': False})
|
||||
self._assertEqualListsOfInstances([], result)
|
||||
|
||||
def test_instance_get_all_by_filters_metadata(self):
|
||||
instance = self.create_instance_with_args(metadata={'foo': 'bar'})
|
||||
self.create_instance_with_args()
|
||||
|
|
|
@ -43,6 +43,7 @@ class TestInstanceNotification(test.NoDBTestCase):
|
|||
uuid=uuids.instance1,
|
||||
locked=False,
|
||||
auto_disk_config=False,
|
||||
system_metadata={},
|
||||
**instance_values)
|
||||
self.payload = {
|
||||
'bandwidth': {},
|
||||
|
@ -118,6 +119,7 @@ class TestInstanceNotification(test.NoDBTestCase):
|
|||
instance = fake_instance.fake_instance_obj(ctxt)
|
||||
# Set some other fields otherwise populate_schema tries to hit the DB.
|
||||
instance.metadata = {}
|
||||
instance.system_metadata = {}
|
||||
instance.info_cache = objects.InstanceInfoCache(
|
||||
network_info=network_model.NetworkInfo([]))
|
||||
payload = instance_notification.InstancePayload(ctxt, instance)
|
||||
|
|
|
@ -383,35 +383,35 @@ notification_object_data = {
|
|||
'ImageMetaPayload': '1.0-0e65beeacb3393beed564a57bc2bc989',
|
||||
'ImageMetaPropsPayload': '1.0-0665065e198b4ab1b03aa80f442d2302',
|
||||
'InstanceActionNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
||||
'InstanceActionPayload': '1.7-8c77f0c85a83d325fded152376ca809a',
|
||||
'InstanceActionPayload': '1.8-4fa3da9cbf0761f1f700ae578f36dc2f',
|
||||
'InstanceActionRebuildNotification':
|
||||
'1.0-a73147b93b520ff0061865849d3dfa56',
|
||||
'InstanceActionRebuildPayload': '1.8-ab76ecbf73b82bc010ab82bdc2792e1d',
|
||||
'InstanceActionRebuildPayload': '1.9-10eebfbf6e944aaac43188173dff9e01',
|
||||
'InstanceActionRescueNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
||||
'InstanceActionRescuePayload': '1.2-b82aa24a966713dce26de3126716e8ef',
|
||||
'InstanceActionRescuePayload': '1.3-dbf4de42bc02ebc4cdbe42f90d343bfd',
|
||||
'InstanceActionResizePrepNotification':
|
||||
'1.0-a73147b93b520ff0061865849d3dfa56',
|
||||
'InstanceActionResizePrepPayload': '1.2-1b41bec00f2b679e77a906b1df0c1d5a',
|
||||
'InstanceActionResizePrepPayload': '1.3-baca73cc450f72d4e1ce6b9aca2bbdf6',
|
||||
'InstanceActionVolumeNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
||||
'InstanceActionVolumePayload': '1.5-3027aae42ee85155b2c378fad1f3b678',
|
||||
'InstanceActionVolumePayload': '1.6-0a30e870677e6166c50645623e287f78',
|
||||
'InstanceActionVolumeSwapNotification':
|
||||
'1.0-a73147b93b520ff0061865849d3dfa56',
|
||||
'InstanceActionVolumeSwapPayload': '1.7-d3252403a9437bcdc80f1075214f8b45',
|
||||
'InstanceActionVolumeSwapPayload': '1.8-d2255347cb2353cb12c174aad4dab93c',
|
||||
'InstanceCreateNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
||||
'InstanceCreatePayload': '1.11-7513127966bc8f270946634d099e71c0',
|
||||
'InstancePayload': '1.7-78354572f699b9a6ad9996b199d03375',
|
||||
'InstanceCreatePayload': '1.12-749f2da7c2435a0e55c076d6bf0ea81d',
|
||||
'InstancePayload': '1.8-60d62df5a6b6aa7817ec5d09f4b8a3e5',
|
||||
'InstanceActionSnapshotNotification':
|
||||
'1.0-a73147b93b520ff0061865849d3dfa56',
|
||||
'InstanceActionSnapshotPayload': '1.8-6a3a66f823b56268ea4b759c83e38c31',
|
||||
'InstanceActionSnapshotPayload': '1.9-c3e0bbaaefafdfa2f8e6e504c2c9b12c',
|
||||
'InstanceExistsNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
||||
'InstanceExistsPayload': '1.1-b7095abb18f5b75f39dc1aa59942535d',
|
||||
'InstanceExistsPayload': '1.2-e082c02438ee57164829afaeee3bf7f8',
|
||||
'InstanceNUMACellPayload': '1.0-2f13614648bc46f2e29578a206561ef6',
|
||||
'InstanceNUMATopologyPayload': '1.0-247361b152047c18ae9ad1da2544a3c9',
|
||||
'InstancePCIRequestPayload': '1.0-12d0d61baf183daaafd93cbeeed2956f',
|
||||
'InstancePCIRequestsPayload': '1.0-6751cffe0c0fabd212aad624f672429a',
|
||||
'InstanceStateUpdatePayload': '1.0-07e111c0fa0f6db0f79b0726d593e3da',
|
||||
'InstanceUpdateNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
||||
'InstanceUpdatePayload': '1.8-375131acb12e612a460f68211a2b3a35',
|
||||
'InstanceUpdatePayload': '1.9-0295e45efc2c6ba98fbca77bbddf882d',
|
||||
'IpPayload': '1.0-8ecf567a99e516d4af094439a7632d34',
|
||||
'KeypairNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
|
||||
'KeypairPayload': '1.0-6daebbbde0e1bf35c1556b1ecd9385c1',
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Added a new ``locked_reason`` option in microversion 2.73 to the
|
||||
``POST /servers/{server_id}/action`` request where the action is lock.
|
||||
It enables the user to specify a reason when locking a server. This
|
||||
information will be exposed through the response of the following APIs:
|
||||
|
||||
- ``GET servers/{server_id}``
|
||||
- ``GET /servers/detail``
|
||||
- ``POST /servers/{server_id}/action`` where the action is rebuild
|
||||
- ``PUT servers/{server_id}``
|
||||
|
||||
In addition, ``locked`` will be supported as a valid filter/sort parameter
|
||||
for ``GET /servers/detail`` and ``GET /servers`` so that users can filter
|
||||
servers based on their locked value. Also the instance action versioned
|
||||
notifications for the lock/unlock actions now contain the ``locked_reason``
|
||||
field.
|
Loading…
Reference in New Issue