python-openstackclient 8.0.0__py3-none-any.whl → 8.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openstackclient/api/compute_v2.py +2 -2
- openstackclient/api/volume_v2.py +60 -0
- openstackclient/api/volume_v3.py +60 -0
- openstackclient/compute/client.py +5 -0
- openstackclient/compute/v2/console.py +7 -0
- openstackclient/compute/v2/console_connection.py +48 -0
- openstackclient/compute/v2/flavor.py +14 -1
- openstackclient/compute/v2/keypair.py +10 -3
- openstackclient/compute/v2/server.py +76 -13
- openstackclient/compute/v2/server_event.py +1 -1
- openstackclient/identity/common.py +85 -11
- openstackclient/identity/v3/application_credential.py +88 -87
- openstackclient/identity/v3/domain.py +67 -49
- openstackclient/identity/v3/group.py +113 -68
- openstackclient/identity/v3/project.py +42 -20
- openstackclient/identity/v3/role.py +7 -2
- openstackclient/identity/v3/user.py +38 -5
- openstackclient/image/client.py +5 -0
- openstackclient/image/v1/image.py +16 -1
- openstackclient/image/v2/cache.py +10 -6
- openstackclient/image/v2/image.py +59 -12
- openstackclient/image/v2/metadef_objects.py +8 -2
- openstackclient/image/v2/metadef_properties.py +9 -2
- openstackclient/network/client.py +0 -6
- openstackclient/network/v2/floating_ip.py +58 -29
- openstackclient/network/v2/network_qos_rule.py +3 -11
- openstackclient/network/v2/port.py +16 -0
- openstackclient/network/v2/router.py +1 -1
- openstackclient/network/v2/security_group.py +49 -7
- openstackclient/network/v2/security_group_rule.py +18 -1
- openstackclient/shell.py +1 -1
- openstackclient/tests/functional/base.py +5 -1
- openstackclient/tests/functional/compute/v2/test_keypair.py +41 -5
- openstackclient/tests/functional/identity/v3/test_access_rule.py +1 -1
- openstackclient/tests/functional/identity/v3/test_application_credential.py +7 -7
- openstackclient/tests/functional/image/v2/test_image.py +36 -14
- openstackclient/tests/functional/volume/v2/test_volume.py +1 -1
- openstackclient/tests/functional/volume/v3/test_volume.py +2 -2
- openstackclient/tests/unit/api/test_volume_v2.py +124 -0
- openstackclient/tests/unit/api/test_volume_v3.py +124 -0
- openstackclient/tests/unit/compute/v2/fakes.py +81 -305
- openstackclient/tests/unit/compute/v2/test_console.py +18 -1
- openstackclient/tests/unit/compute/v2/test_console_connection.py +72 -0
- openstackclient/tests/unit/compute/v2/test_flavor.py +160 -175
- openstackclient/tests/unit/compute/v2/test_keypair.py +12 -5
- openstackclient/tests/unit/compute/v2/test_server.py +211 -97
- openstackclient/tests/unit/compute/v2/test_server_backup.py +32 -71
- openstackclient/tests/unit/compute/v2/test_server_event.py +2 -2
- openstackclient/tests/unit/compute/v2/test_server_image.py +33 -72
- openstackclient/tests/unit/compute/v2/test_server_migration.py +4 -4
- openstackclient/tests/unit/identity/v3/test_application_credential.py +93 -65
- openstackclient/tests/unit/identity/v3/test_domain.py +117 -107
- openstackclient/tests/unit/identity/v3/test_group.py +353 -202
- openstackclient/tests/unit/identity/v3/test_project.py +46 -53
- openstackclient/tests/unit/identity/v3/test_role.py +2 -8
- openstackclient/tests/unit/identity/v3/test_user.py +86 -6
- openstackclient/tests/unit/image/v1/test_image.py +55 -9
- openstackclient/tests/unit/image/v2/test_image.py +128 -58
- openstackclient/tests/unit/image/v2/test_metadef_objects.py +22 -0
- openstackclient/tests/unit/image/v2/test_metadef_properties.py +24 -10
- openstackclient/tests/unit/network/v2/fakes.py +406 -485
- openstackclient/tests/unit/network/v2/test_floating_ip_network.py +13 -19
- openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py +2 -2
- openstackclient/tests/unit/network/v2/test_ndp_proxy.py +3 -5
- openstackclient/tests/unit/network/v2/test_network.py +4 -4
- openstackclient/tests/unit/network/v2/test_network_agent.py +15 -29
- openstackclient/tests/unit/network/v2/test_network_qos_policy.py +16 -19
- openstackclient/tests/unit/network/v2/test_network_qos_rule.py +79 -152
- openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py +4 -6
- openstackclient/tests/unit/network/v2/test_network_rbac.py +2 -2
- openstackclient/tests/unit/network/v2/test_port.py +57 -17
- openstackclient/tests/unit/network/v2/test_router.py +73 -57
- openstackclient/tests/unit/network/v2/test_security_group_network.py +31 -27
- openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py +1 -3
- openstackclient/tests/unit/network/v2/test_security_group_rule_network.py +82 -39
- openstackclient/tests/unit/volume/v2/fakes.py +1 -2
- openstackclient/tests/unit/volume/v2/test_service.py +57 -91
- openstackclient/tests/unit/volume/v2/test_volume.py +466 -410
- openstackclient/tests/unit/volume/v2/test_volume_backup.py +141 -148
- openstackclient/tests/unit/volume/v2/test_volume_snapshot.py +293 -283
- openstackclient/tests/unit/volume/v3/test_block_storage_log_level.py +61 -71
- openstackclient/tests/unit/volume/v3/test_service.py +221 -141
- openstackclient/tests/unit/volume/v3/test_volume.py +569 -534
- openstackclient/tests/unit/volume/v3/test_volume_attachment.py +1 -1
- openstackclient/tests/unit/volume/v3/test_volume_backup.py +198 -203
- openstackclient/tests/unit/volume/v3/test_volume_snapshot.py +682 -47
- openstackclient/volume/v2/service.py +41 -38
- openstackclient/volume/v2/volume.py +140 -88
- openstackclient/volume/v2/volume_backup.py +9 -3
- openstackclient/volume/v2/volume_snapshot.py +121 -84
- openstackclient/volume/v3/block_storage_log_level.py +22 -28
- openstackclient/volume/v3/service.py +105 -14
- openstackclient/volume/v3/volume.py +287 -99
- openstackclient/volume/v3/volume_backup.py +24 -19
- openstackclient/volume/v3/volume_group.py +1 -1
- openstackclient/volume/v3/volume_snapshot.py +485 -10
- {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/AUTHORS +13 -0
- python_openstackclient-8.2.0.dist-info/METADATA +264 -0
- {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/RECORD +104 -98
- {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/entry_points.txt +7 -6
- python_openstackclient-8.2.0.dist-info/pbr.json +1 -0
- python_openstackclient-8.0.0.dist-info/METADATA +0 -166
- python_openstackclient-8.0.0.dist-info/pbr.json +0 -1
- {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/LICENSE +0 -0
- {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/WHEEL +0 -0
- {python_openstackclient-8.0.0.dist-info → python_openstackclient-8.2.0.dist-info}/top_level.txt +0 -0
|
@@ -64,7 +64,7 @@ def list_security_groups(compute_client, all_projects=None):
|
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
def find_security_group(compute_client, name_or_id):
|
|
67
|
-
"""Find the
|
|
67
|
+
"""Find the security group for a given name or ID
|
|
68
68
|
|
|
69
69
|
https://docs.openstack.org/api-ref/compute/#show-security-group-details
|
|
70
70
|
|
|
@@ -240,7 +240,7 @@ def list_networks(compute_client):
|
|
|
240
240
|
|
|
241
241
|
|
|
242
242
|
def find_network(compute_client, name_or_id):
|
|
243
|
-
"""Find the
|
|
243
|
+
"""Find the network for a given name or ID
|
|
244
244
|
|
|
245
245
|
https://docs.openstack.org/api-ref/compute/#show-network-details
|
|
246
246
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
2
|
+
# not use this file except in compliance with the License. You may obtain
|
|
3
|
+
# a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
9
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
10
|
+
# License for the specific language governing permissions and limitations
|
|
11
|
+
# under the License.
|
|
12
|
+
|
|
13
|
+
"""Volume v2 API Library
|
|
14
|
+
|
|
15
|
+
A collection of wrappers for deprecated Block Storage v2 APIs that are not
|
|
16
|
+
intentionally supported by SDK.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import http
|
|
20
|
+
|
|
21
|
+
from openstack import exceptions as sdk_exceptions
|
|
22
|
+
from osc_lib import exceptions
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# consistency groups
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def find_consistency_group(compute_client, name_or_id):
|
|
29
|
+
"""Find the consistency group for a given name or ID
|
|
30
|
+
|
|
31
|
+
https://docs.openstack.org/api-ref/block-storage/v3/#show-a-consistency-group-s-details
|
|
32
|
+
|
|
33
|
+
:param volume_client: A volume client
|
|
34
|
+
:param name_or_id: The name or ID of the consistency group to look up
|
|
35
|
+
:returns: A consistency group object
|
|
36
|
+
:raises exception.NotFound: If a matching consistency group could not be
|
|
37
|
+
found or more than one match was found
|
|
38
|
+
"""
|
|
39
|
+
response = compute_client.get(f'/consistencygroups/{name_or_id}')
|
|
40
|
+
if response.status_code != http.HTTPStatus.NOT_FOUND:
|
|
41
|
+
# there might be other, non-404 errors
|
|
42
|
+
sdk_exceptions.raise_from_response(response)
|
|
43
|
+
return response.json()['consistencygroup']
|
|
44
|
+
|
|
45
|
+
response = compute_client.get('/consistencygroups')
|
|
46
|
+
sdk_exceptions.raise_from_response(response)
|
|
47
|
+
found = None
|
|
48
|
+
consistency_groups = response.json()['consistencygroups']
|
|
49
|
+
for consistency_group in consistency_groups:
|
|
50
|
+
if consistency_group['name'] == name_or_id:
|
|
51
|
+
if found:
|
|
52
|
+
raise exceptions.NotFound(
|
|
53
|
+
f'multiple matches found for {name_or_id}'
|
|
54
|
+
)
|
|
55
|
+
found = consistency_group
|
|
56
|
+
|
|
57
|
+
if not found:
|
|
58
|
+
raise exceptions.NotFound(f'{name_or_id} not found')
|
|
59
|
+
|
|
60
|
+
return found
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
2
|
+
# not use this file except in compliance with the License. You may obtain
|
|
3
|
+
# a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
9
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
10
|
+
# License for the specific language governing permissions and limitations
|
|
11
|
+
# under the License.
|
|
12
|
+
|
|
13
|
+
"""Volume v3 API Library
|
|
14
|
+
|
|
15
|
+
A collection of wrappers for deprecated Block Storage v3 APIs that are not
|
|
16
|
+
intentionally supported by SDK.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import http
|
|
20
|
+
|
|
21
|
+
from openstack import exceptions as sdk_exceptions
|
|
22
|
+
from osc_lib import exceptions
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# consistency groups
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def find_consistency_group(compute_client, name_or_id):
|
|
29
|
+
"""Find the consistency group for a given name or ID
|
|
30
|
+
|
|
31
|
+
https://docs.openstack.org/api-ref/block-storage/v3/#show-a-consistency-group-s-details
|
|
32
|
+
|
|
33
|
+
:param volume_client: A volume client
|
|
34
|
+
:param name_or_id: The name or ID of the consistency group to look up
|
|
35
|
+
:returns: A consistency group object
|
|
36
|
+
:raises exception.NotFound: If a matching consistency group could not be
|
|
37
|
+
found or more than one match was found
|
|
38
|
+
"""
|
|
39
|
+
response = compute_client.get(f'/consistencygroups/{name_or_id}')
|
|
40
|
+
if response.status_code != http.HTTPStatus.NOT_FOUND:
|
|
41
|
+
# there might be other, non-404 errors
|
|
42
|
+
sdk_exceptions.raise_from_response(response)
|
|
43
|
+
return response.json()['consistencygroup']
|
|
44
|
+
|
|
45
|
+
response = compute_client.get('/consistencygroups')
|
|
46
|
+
sdk_exceptions.raise_from_response(response)
|
|
47
|
+
found = None
|
|
48
|
+
consistency_groups = response.json()['consistencygroups']
|
|
49
|
+
for consistency_group in consistency_groups:
|
|
50
|
+
if consistency_group['name'] == name_or_id:
|
|
51
|
+
if found:
|
|
52
|
+
raise exceptions.NotFound(
|
|
53
|
+
f'multiple matches found for {name_or_id}'
|
|
54
|
+
)
|
|
55
|
+
found = consistency_group
|
|
56
|
+
|
|
57
|
+
if not found:
|
|
58
|
+
raise exceptions.NotFound(f'{name_or_id} not found')
|
|
59
|
+
|
|
60
|
+
return found
|
|
@@ -106,6 +106,13 @@ class ShowConsoleURL(command.ShowOne):
|
|
|
106
106
|
const='spice-html5',
|
|
107
107
|
help=_("Show SPICE console URL"),
|
|
108
108
|
)
|
|
109
|
+
type_group.add_argument(
|
|
110
|
+
'--spice-direct',
|
|
111
|
+
dest='url_type',
|
|
112
|
+
action='store_const',
|
|
113
|
+
const='spice-direct',
|
|
114
|
+
help=_("Show SPICE direct protocol native console URL"),
|
|
115
|
+
)
|
|
109
116
|
type_group.add_argument(
|
|
110
117
|
'--rdp',
|
|
111
118
|
dest='url_type',
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
2
|
+
# not use this file except in compliance with the License. You may obtain
|
|
3
|
+
# a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
9
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
10
|
+
# License for the specific language governing permissions and limitations
|
|
11
|
+
# under the License.
|
|
12
|
+
#
|
|
13
|
+
|
|
14
|
+
"""Compute v2 Console auth token implementations."""
|
|
15
|
+
|
|
16
|
+
from osc_lib.command import command
|
|
17
|
+
from osc_lib import utils
|
|
18
|
+
|
|
19
|
+
from openstackclient.i18n import _
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _get_console_connection_columns(item):
|
|
23
|
+
column_map: dict[str, str] = {}
|
|
24
|
+
hidden_columns = ['id', 'location', 'name']
|
|
25
|
+
return utils.get_osc_show_columns_for_sdk_resource(
|
|
26
|
+
item, column_map, hidden_columns
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ShowConsoleConnectionInformation(command.ShowOne):
|
|
31
|
+
_description = _("Show server's remote console connection information")
|
|
32
|
+
|
|
33
|
+
def get_parser(self, prog_name):
|
|
34
|
+
parser = super().get_parser(prog_name)
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
'token',
|
|
37
|
+
metavar='<token>',
|
|
38
|
+
help=_("Nova console token to lookup"),
|
|
39
|
+
)
|
|
40
|
+
return parser
|
|
41
|
+
|
|
42
|
+
def take_action(self, parsed_args):
|
|
43
|
+
compute_client = self.app.client_manager.compute
|
|
44
|
+
data = compute_client.validate_console_auth_token(parsed_args.token)
|
|
45
|
+
display_columns, columns = _get_console_connection_columns(data)
|
|
46
|
+
data = utils.get_dict_properties(data, columns)
|
|
47
|
+
|
|
48
|
+
return (display_columns, data)
|
|
@@ -156,12 +156,25 @@ class CreateFlavor(command.ShowOne):
|
|
|
156
156
|
msg = _("--project is only allowed with --private")
|
|
157
157
|
raise exceptions.CommandError(msg)
|
|
158
158
|
|
|
159
|
+
flavor_id = parsed_args.id
|
|
160
|
+
if parsed_args.id == 'auto':
|
|
161
|
+
# novaclient aliased 'auto' to mean "generate a UUID for me": we
|
|
162
|
+
# do the same to avoid breaking existing users
|
|
163
|
+
flavor_id = None
|
|
164
|
+
|
|
165
|
+
msg = _(
|
|
166
|
+
"Passing '--id auto' is deprecated. Nova will automatically "
|
|
167
|
+
"assign a UUID-like ID if no ID is provided. Omit the '--id' "
|
|
168
|
+
"parameter instead."
|
|
169
|
+
)
|
|
170
|
+
self.log.warning(msg)
|
|
171
|
+
|
|
159
172
|
args = {
|
|
160
173
|
'name': parsed_args.name,
|
|
161
174
|
'ram': parsed_args.ram,
|
|
162
175
|
'vcpus': parsed_args.vcpus,
|
|
163
176
|
'disk': parsed_args.disk,
|
|
164
|
-
'id':
|
|
177
|
+
'id': flavor_id,
|
|
165
178
|
'ephemeral': parsed_args.ephemeral,
|
|
166
179
|
'swap': parsed_args.swap,
|
|
167
180
|
'rxtx_factor': parsed_args.rxtx_factor,
|
|
@@ -300,6 +300,7 @@ class ListKeypair(command.Lister):
|
|
|
300
300
|
def take_action(self, parsed_args):
|
|
301
301
|
compute_client = self.app.client_manager.compute
|
|
302
302
|
identity_client = self.app.client_manager.identity
|
|
303
|
+
identity_sdk_client = self.app.client_manager.sdk_connection.identity
|
|
303
304
|
|
|
304
305
|
kwargs = {}
|
|
305
306
|
|
|
@@ -345,11 +346,17 @@ class ListKeypair(command.Lister):
|
|
|
345
346
|
parsed_args.project,
|
|
346
347
|
parsed_args.project_domain,
|
|
347
348
|
).id
|
|
348
|
-
|
|
349
|
+
assignments = identity_sdk_client.role_assignments(
|
|
350
|
+
scope_project_id=project
|
|
351
|
+
)
|
|
352
|
+
user_ids = set()
|
|
353
|
+
for assignment in assignments:
|
|
354
|
+
if assignment.user:
|
|
355
|
+
user_ids.add(assignment.user['id'])
|
|
349
356
|
|
|
350
357
|
data = []
|
|
351
|
-
for
|
|
352
|
-
kwargs['user_id'] =
|
|
358
|
+
for user_id in user_ids:
|
|
359
|
+
kwargs['user_id'] = user_id
|
|
353
360
|
data.extend(compute_client.keypairs(**kwargs))
|
|
354
361
|
elif parsed_args.user:
|
|
355
362
|
if not sdk_utils.supports_microversion(compute_client, '2.10'):
|
|
@@ -21,7 +21,6 @@ import getpass
|
|
|
21
21
|
import json
|
|
22
22
|
import logging
|
|
23
23
|
import os
|
|
24
|
-
import typing as ty
|
|
25
24
|
|
|
26
25
|
from cliff import columns as cliff_columns
|
|
27
26
|
import iso8601
|
|
@@ -185,6 +184,7 @@ def _prep_server_detail(compute_client, image_client, server, *, refresh=True):
|
|
|
185
184
|
'user_data': 'OS-EXT-SRV-ATTR:user_data',
|
|
186
185
|
'vm_state': 'OS-EXT-STS:vm_state',
|
|
187
186
|
'pinned_availability_zone': 'pinned_availability_zone',
|
|
187
|
+
'scheduler_hints': 'scheduler_hints',
|
|
188
188
|
}
|
|
189
189
|
# Some columns returned by openstacksdk should not be shown because they're
|
|
190
190
|
# either irrelevant or duplicates
|
|
@@ -205,7 +205,6 @@ def _prep_server_detail(compute_client, image_client, server, *, refresh=True):
|
|
|
205
205
|
'min_count',
|
|
206
206
|
'networks',
|
|
207
207
|
'personality',
|
|
208
|
-
'scheduler_hints',
|
|
209
208
|
# aliases
|
|
210
209
|
'volumes',
|
|
211
210
|
# unnecessary
|
|
@@ -236,6 +235,11 @@ def _prep_server_detail(compute_client, image_client, server, *, refresh=True):
|
|
|
236
235
|
|
|
237
236
|
info = data
|
|
238
237
|
|
|
238
|
+
# NOTE(dviroel): microversion 2.100 is now retrieving scheduler_hints
|
|
239
|
+
# content from request_spec on detailed responses
|
|
240
|
+
if not sdk_utils.supports_microversion(compute_client, '2.100'):
|
|
241
|
+
info.pop('scheduler_hints', None)
|
|
242
|
+
|
|
239
243
|
# Convert the image blob to a name
|
|
240
244
|
image_info = info.get('image', {})
|
|
241
245
|
if image_info and any(image_info.values()):
|
|
@@ -322,6 +326,11 @@ def _prep_server_detail(compute_client, image_client, server, *, refresh=True):
|
|
|
322
326
|
info['OS-EXT-STS:power_state']
|
|
323
327
|
)
|
|
324
328
|
|
|
329
|
+
if 'scheduler_hints' in info:
|
|
330
|
+
info['scheduler_hints'] = format_columns.DictListColumn(
|
|
331
|
+
info.pop('scheduler_hints', {}),
|
|
332
|
+
)
|
|
333
|
+
|
|
325
334
|
return info
|
|
326
335
|
|
|
327
336
|
|
|
@@ -1864,7 +1873,7 @@ class CreateServer(command.ShowOne):
|
|
|
1864
1873
|
|
|
1865
1874
|
# Default to empty list if nothing was specified and let nova
|
|
1866
1875
|
# decide the default behavior.
|
|
1867
|
-
networks:
|
|
1876
|
+
networks: str | list[dict[str, str]] | None = []
|
|
1868
1877
|
|
|
1869
1878
|
if 'auto' in parsed_args.nics or 'none' in parsed_args.nics:
|
|
1870
1879
|
if len(parsed_args.nics) > 1:
|
|
@@ -1938,9 +1947,9 @@ class CreateServer(command.ShowOne):
|
|
|
1938
1947
|
network['port'] = nic['port-id']
|
|
1939
1948
|
|
|
1940
1949
|
if nic['v4-fixed-ip']:
|
|
1941
|
-
network['
|
|
1950
|
+
network['fixed_ip'] = nic['v4-fixed-ip']
|
|
1942
1951
|
elif nic['v6-fixed-ip']:
|
|
1943
|
-
network['
|
|
1952
|
+
network['fixed_ip'] = nic['v6-fixed-ip']
|
|
1944
1953
|
|
|
1945
1954
|
if nic.get('tag'): # tags are optional
|
|
1946
1955
|
network['tag'] = nic['tag']
|
|
@@ -2120,13 +2129,11 @@ class CreateServer(command.ShowOne):
|
|
|
2120
2129
|
f.close()
|
|
2121
2130
|
|
|
2122
2131
|
if parsed_args.wait:
|
|
2123
|
-
if utils.wait_for_status(
|
|
2132
|
+
if not utils.wait_for_status(
|
|
2124
2133
|
compute_client.get_server,
|
|
2125
2134
|
server.id,
|
|
2126
2135
|
callback=_show_progress,
|
|
2127
2136
|
):
|
|
2128
|
-
self.app.stdout.write('\n')
|
|
2129
|
-
else:
|
|
2130
2137
|
msg = _('Error creating server: %s') % parsed_args.server_name
|
|
2131
2138
|
raise exceptions.CommandError(msg)
|
|
2132
2139
|
|
|
@@ -2834,14 +2841,20 @@ class ListServer(command.Lister):
|
|
|
2834
2841
|
'pinned_availability_zone',
|
|
2835
2842
|
'hypervisor_hostname',
|
|
2836
2843
|
'metadata',
|
|
2844
|
+
'scheduler_hints',
|
|
2837
2845
|
)
|
|
2838
2846
|
column_headers += (
|
|
2839
2847
|
'Availability Zone',
|
|
2840
2848
|
'Pinned Availability Zone',
|
|
2841
2849
|
'Host',
|
|
2842
2850
|
'Properties',
|
|
2851
|
+
'Scheduler Hints',
|
|
2843
2852
|
)
|
|
2844
2853
|
|
|
2854
|
+
if parsed_args.all_projects:
|
|
2855
|
+
columns += ('project_id',)
|
|
2856
|
+
column_headers += ('Project ID',)
|
|
2857
|
+
|
|
2845
2858
|
# support for additional columns
|
|
2846
2859
|
if parsed_args.columns:
|
|
2847
2860
|
for c in parsed_args.columns:
|
|
@@ -2884,6 +2897,12 @@ class ListServer(command.Lister):
|
|
|
2884
2897
|
if c in ('Properties', "properties"):
|
|
2885
2898
|
columns += ('Metadata',)
|
|
2886
2899
|
column_headers += ('Properties',)
|
|
2900
|
+
if c in (
|
|
2901
|
+
'scheduler_hints',
|
|
2902
|
+
"Scheduler Hints",
|
|
2903
|
+
):
|
|
2904
|
+
columns += ('scheduler_hints',)
|
|
2905
|
+
column_headers += ('Scheduler Hints',)
|
|
2887
2906
|
|
|
2888
2907
|
# remove duplicates
|
|
2889
2908
|
column_headers = tuple(dict.fromkeys(column_headers))
|
|
@@ -2986,7 +3005,7 @@ class ListServer(command.Lister):
|
|
|
2986
3005
|
# infrastructure failure situations.
|
|
2987
3006
|
# For those servers with partial constructs we just skip the
|
|
2988
3007
|
# processing of the image and flavor information.
|
|
2989
|
-
if
|
|
3008
|
+
if getattr(s, 'status') == 'UNKNOWN':
|
|
2990
3009
|
continue
|
|
2991
3010
|
|
|
2992
3011
|
if 'id' in s.image and s.image.id is not None:
|
|
@@ -3050,6 +3069,7 @@ class ListServer(command.Lister):
|
|
|
3050
3069
|
'metadata': format_columns.DictColumn,
|
|
3051
3070
|
'security_groups_name': format_columns.ListColumn,
|
|
3052
3071
|
'hypervisor_hostname': HostColumn,
|
|
3072
|
+
'scheduler_hints': format_columns.DictListColumn,
|
|
3053
3073
|
},
|
|
3054
3074
|
)
|
|
3055
3075
|
for s in data
|
|
@@ -3860,9 +3880,15 @@ host."""
|
|
|
3860
3880
|
compute_client.evacuate_server(server, **kwargs)
|
|
3861
3881
|
|
|
3862
3882
|
if parsed_args.wait:
|
|
3883
|
+
orig_status = server.status
|
|
3884
|
+
success = ['ACTIVE']
|
|
3885
|
+
if orig_status == 'SHUTOFF':
|
|
3886
|
+
success.append('SHUTOFF')
|
|
3887
|
+
|
|
3863
3888
|
if utils.wait_for_status(
|
|
3864
3889
|
compute_client.get_server,
|
|
3865
3890
|
server.id,
|
|
3891
|
+
success_status=success,
|
|
3866
3892
|
callback=_show_progress,
|
|
3867
3893
|
):
|
|
3868
3894
|
self.app.stdout.write(_('Complete\n'))
|
|
@@ -4462,15 +4488,27 @@ class SetServer(command.Command):
|
|
|
4462
4488
|
'(repeat option to set multiple properties)'
|
|
4463
4489
|
),
|
|
4464
4490
|
)
|
|
4491
|
+
parser.add_argument(
|
|
4492
|
+
'--auto-approve',
|
|
4493
|
+
action='store_true',
|
|
4494
|
+
help=_(
|
|
4495
|
+
"Allow server state override without asking for confirmation"
|
|
4496
|
+
),
|
|
4497
|
+
)
|
|
4465
4498
|
parser.add_argument(
|
|
4466
4499
|
'--state',
|
|
4467
4500
|
metavar='<state>',
|
|
4468
4501
|
choices=['active', 'error'],
|
|
4469
4502
|
help=_(
|
|
4470
|
-
'New server state
|
|
4471
|
-
'**WARNING**
|
|
4472
|
-
'
|
|
4473
|
-
'
|
|
4503
|
+
'New server state.'
|
|
4504
|
+
'**WARNING** Resetting the state is intended to work around '
|
|
4505
|
+
'servers stuck in an intermediate state, such as deleting. '
|
|
4506
|
+
'If the server is in an error state then this is almost '
|
|
4507
|
+
'never the correct command to run and you should prefer hard '
|
|
4508
|
+
'reboot where possible. In particular, if the server is in '
|
|
4509
|
+
'an error state due to a move operation, setting the state '
|
|
4510
|
+
'can result in instances that are no longer usable. Proceed '
|
|
4511
|
+
'with caution. (admin only)'
|
|
4474
4512
|
),
|
|
4475
4513
|
)
|
|
4476
4514
|
parser.add_argument(
|
|
@@ -4505,6 +4543,20 @@ class SetServer(command.Command):
|
|
|
4505
4543
|
)
|
|
4506
4544
|
return parser
|
|
4507
4545
|
|
|
4546
|
+
@staticmethod
|
|
4547
|
+
def ask_user_yesno(msg):
|
|
4548
|
+
"""Ask user Y/N question
|
|
4549
|
+
|
|
4550
|
+
:param str msg: question text
|
|
4551
|
+
:return bool: User choice
|
|
4552
|
+
"""
|
|
4553
|
+
while True:
|
|
4554
|
+
answer = getpass.getpass('{} [{}]: '.format(msg, 'y/n'))
|
|
4555
|
+
if answer in ('y', 'Y', 'yes'):
|
|
4556
|
+
return True
|
|
4557
|
+
elif answer in ('n', 'N', 'no'):
|
|
4558
|
+
return False
|
|
4559
|
+
|
|
4508
4560
|
def take_action(self, parsed_args):
|
|
4509
4561
|
compute_client = self.app.client_manager.compute
|
|
4510
4562
|
server = compute_client.find_server(
|
|
@@ -4555,6 +4607,17 @@ class SetServer(command.Command):
|
|
|
4555
4607
|
)
|
|
4556
4608
|
|
|
4557
4609
|
if parsed_args.state:
|
|
4610
|
+
if not parsed_args.auto_approve:
|
|
4611
|
+
if not self.ask_user_yesno(
|
|
4612
|
+
_(
|
|
4613
|
+
"Resetting the server state can make it much harder "
|
|
4614
|
+
"to recover a server from an error state. If the "
|
|
4615
|
+
"server is in error status due to a failed move "
|
|
4616
|
+
"operation then this is likely not the correct "
|
|
4617
|
+
"approach to fix the problem. Do you wish to continue?"
|
|
4618
|
+
)
|
|
4619
|
+
):
|
|
4620
|
+
return
|
|
4558
4621
|
compute_client.reset_server_state(server, state=parsed_args.state)
|
|
4559
4622
|
|
|
4560
4623
|
if parsed_args.root_password:
|
|
@@ -82,7 +82,7 @@ class ServerActionEventColumn(columns.FormattableColumn):
|
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
def _get_server_event_columns(item, client):
|
|
85
|
-
hidden_columns = ['name', 'server_id', 'links', 'location']
|
|
85
|
+
hidden_columns = ['name', 'server_id', 'links', 'location', 'finish_time']
|
|
86
86
|
|
|
87
87
|
if not sdk_utils.supports_microversion(client, '2.58'):
|
|
88
88
|
# updated_at was introduced in 2.58
|
|
@@ -189,6 +189,16 @@ def find_domain(identity_client, name_or_id):
|
|
|
189
189
|
)
|
|
190
190
|
|
|
191
191
|
|
|
192
|
+
def find_domain_id_sdk(
|
|
193
|
+
identity_client, name_or_id, *, validate_actor_existence=True
|
|
194
|
+
):
|
|
195
|
+
return _find_sdk_id(
|
|
196
|
+
identity_client.find_domain,
|
|
197
|
+
name_or_id=name_or_id,
|
|
198
|
+
validate_actor_existence=validate_actor_existence,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
192
202
|
def find_group(identity_client, name_or_id, domain_name_or_id=None):
|
|
193
203
|
if domain_name_or_id is None:
|
|
194
204
|
return _find_identity_resource(
|
|
@@ -204,6 +214,33 @@ def find_group(identity_client, name_or_id, domain_name_or_id=None):
|
|
|
204
214
|
)
|
|
205
215
|
|
|
206
216
|
|
|
217
|
+
def find_group_id_sdk(
|
|
218
|
+
identity_client,
|
|
219
|
+
name_or_id,
|
|
220
|
+
domain_name_or_id=None,
|
|
221
|
+
*,
|
|
222
|
+
validate_actor_existence=True,
|
|
223
|
+
):
|
|
224
|
+
if domain_name_or_id is None:
|
|
225
|
+
return _find_sdk_id(
|
|
226
|
+
identity_client.find_group,
|
|
227
|
+
name_or_id=name_or_id,
|
|
228
|
+
validate_actor_existence=validate_actor_existence,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
domain_id = find_domain_id_sdk(
|
|
232
|
+
identity_client,
|
|
233
|
+
name_or_id=domain_name_or_id,
|
|
234
|
+
validate_actor_existence=validate_actor_existence,
|
|
235
|
+
)
|
|
236
|
+
return _find_sdk_id(
|
|
237
|
+
identity_client.find_group,
|
|
238
|
+
name_or_id=name_or_id,
|
|
239
|
+
validate_actor_existence=validate_actor_existence,
|
|
240
|
+
domain_id=domain_id,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
207
244
|
def find_project(identity_client, name_or_id, domain_name_or_id=None):
|
|
208
245
|
if domain_name_or_id is None:
|
|
209
246
|
return _find_identity_resource(
|
|
@@ -229,6 +266,32 @@ def find_user(identity_client, name_or_id, domain_name_or_id=None):
|
|
|
229
266
|
)
|
|
230
267
|
|
|
231
268
|
|
|
269
|
+
def find_user_id_sdk(
|
|
270
|
+
identity_client,
|
|
271
|
+
name_or_id,
|
|
272
|
+
domain_name_or_id=None,
|
|
273
|
+
*,
|
|
274
|
+
validate_actor_existence=True,
|
|
275
|
+
):
|
|
276
|
+
if domain_name_or_id is None:
|
|
277
|
+
return _find_sdk_id(
|
|
278
|
+
identity_client.find_user,
|
|
279
|
+
name_or_id=name_or_id,
|
|
280
|
+
validate_actor_existence=validate_actor_existence,
|
|
281
|
+
)
|
|
282
|
+
domain_id = find_domain_id_sdk(
|
|
283
|
+
identity_client,
|
|
284
|
+
name_or_id=domain_name_or_id,
|
|
285
|
+
validate_actor_existence=validate_actor_existence,
|
|
286
|
+
)
|
|
287
|
+
return _find_sdk_id(
|
|
288
|
+
identity_client.find_user,
|
|
289
|
+
name_or_id=name_or_id,
|
|
290
|
+
validate_actor_existence=validate_actor_existence,
|
|
291
|
+
domain_id=domain_id,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
|
|
232
295
|
def _find_identity_resource(
|
|
233
296
|
identity_client_manager, name_or_id, resource_type, **kwargs
|
|
234
297
|
):
|
|
@@ -269,13 +332,20 @@ def _find_identity_resource(
|
|
|
269
332
|
return resource_type(None, {'id': name_or_id, 'name': name_or_id})
|
|
270
333
|
|
|
271
334
|
|
|
272
|
-
def
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
335
|
+
def _find_sdk_id(
|
|
336
|
+
find_command, name_or_id, *, validate_actor_existence=True, **kwargs
|
|
337
|
+
):
|
|
338
|
+
try:
|
|
339
|
+
resource = find_command(
|
|
340
|
+
name_or_id=name_or_id, ignore_missing=False, **kwargs
|
|
341
|
+
)
|
|
342
|
+
except sdk_exceptions.ForbiddenException:
|
|
343
|
+
return name_or_id
|
|
344
|
+
except sdk_exceptions.ResourceNotFound as exc:
|
|
345
|
+
if not validate_actor_existence:
|
|
346
|
+
return name_or_id
|
|
347
|
+
raise exceptions.CommandError from exc
|
|
348
|
+
return resource.id
|
|
279
349
|
|
|
280
350
|
|
|
281
351
|
def add_user_domain_option_to_parser(parser):
|
|
@@ -340,17 +410,21 @@ def add_inherited_option_to_parser(parser):
|
|
|
340
410
|
|
|
341
411
|
|
|
342
412
|
def add_resource_option_to_parser(parser):
|
|
343
|
-
|
|
344
|
-
|
|
413
|
+
immutable_group = parser.add_mutually_exclusive_group()
|
|
414
|
+
immutable_group.add_argument(
|
|
345
415
|
'--immutable',
|
|
346
416
|
action='store_true',
|
|
417
|
+
dest='immutable',
|
|
418
|
+
default=None,
|
|
347
419
|
help=_(
|
|
348
420
|
'Make resource immutable. An immutable project may not '
|
|
349
421
|
'be deleted or modified except to remove the immutable flag'
|
|
350
422
|
),
|
|
351
423
|
)
|
|
352
|
-
|
|
424
|
+
immutable_group.add_argument(
|
|
353
425
|
'--no-immutable',
|
|
354
|
-
action='
|
|
426
|
+
action='store_false',
|
|
427
|
+
dest='immutable',
|
|
428
|
+
default=None,
|
|
355
429
|
help=_('Make resource mutable (default)'),
|
|
356
430
|
)
|