python-openstackclient 10.0.0__py3-none-any.whl → 10.1.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/common/availability_zone.py +1 -1
- openstackclient/common/module.py +21 -27
- openstackclient/common/pagination.py +42 -4
- openstackclient/common/project_cleanup.py +1 -2
- openstackclient/common/quota.py +9 -5
- openstackclient/compute/v2/flavor.py +3 -1
- openstackclient/compute/v2/hypervisor.py +2 -0
- openstackclient/compute/v2/keypair.py +6 -2
- openstackclient/compute/v2/server.py +21 -12
- openstackclient/compute/v2/server_event.py +8 -1
- openstackclient/compute/v2/server_group.py +2 -0
- openstackclient/compute/v2/server_migration.py +3 -0
- openstackclient/compute/v2/server_volume.py +3 -1
- openstackclient/compute/v2/service.py +3 -1
- openstackclient/compute/v2/usage.py +2 -2
- openstackclient/identity/common.py +5 -1
- openstackclient/identity/v3/access_rule.py +6 -0
- openstackclient/identity/v3/application_credential.py +10 -3
- openstackclient/identity/v3/credential.py +4 -2
- openstackclient/identity/v3/domain.py +4 -2
- openstackclient/identity/v3/endpoint.py +57 -45
- openstackclient/identity/v3/federation_protocol.py +7 -5
- openstackclient/identity/v3/group.py +11 -10
- openstackclient/identity/v3/identity_provider.py +4 -1
- openstackclient/identity/v3/limit.py +5 -2
- openstackclient/identity/v3/mapping.py +36 -19
- openstackclient/identity/v3/project.py +18 -5
- openstackclient/identity/v3/region.py +4 -2
- openstackclient/identity/v3/registered_limit.py +3 -2
- openstackclient/identity/v3/role.py +2 -1
- openstackclient/identity/v3/role_assignment.py +3 -2
- openstackclient/identity/v3/service.py +4 -2
- openstackclient/identity/v3/service_provider.py +4 -2
- openstackclient/identity/v3/trust.py +8 -5
- openstackclient/identity/v3/user.py +38 -11
- openstackclient/image/v2/cache.py +2 -2
- openstackclient/image/v2/image.py +15 -9
- openstackclient/image/v2/metadef_namespaces.py +11 -10
- openstackclient/image/v2/metadef_objects.py +5 -5
- openstackclient/image/v2/metadef_properties.py +7 -4
- openstackclient/image/v2/task.py +11 -22
- openstackclient/network/utils.py +0 -41
- openstackclient/network/v2/address_group.py +13 -1
- openstackclient/network/v2/address_scope.py +13 -8
- openstackclient/network/v2/bgpvpn/bgpvpn.py +33 -19
- openstackclient/network/v2/bgpvpn/network_association.py +25 -13
- openstackclient/network/v2/bgpvpn/port_association.py +35 -21
- openstackclient/network/v2/bgpvpn/router_association.py +27 -14
- openstackclient/network/v2/default_security_group_rule.py +14 -6
- openstackclient/network/v2/floating_ip.py +12 -4
- openstackclient/network/v2/floating_ip_port_forwarding.py +12 -2
- openstackclient/network/v2/fwaas/group.py +34 -1
- openstackclient/network/v2/fwaas/rule.py +39 -3
- openstackclient/network/v2/ip_availability.py +13 -4
- openstackclient/network/v2/l3_conntrack_helper.py +14 -1
- openstackclient/network/v2/local_ip.py +4 -1
- openstackclient/network/v2/local_ip_association.py +4 -1
- openstackclient/network/v2/ndp_proxy.py +4 -1
- openstackclient/network/v2/network.py +87 -20
- openstackclient/network/v2/network_agent.py +32 -10
- openstackclient/network/v2/network_auto_allocated_topology.py +6 -5
- openstackclient/network/v2/network_flavor.py +19 -6
- openstackclient/network/v2/network_flavor_profile.py +20 -6
- openstackclient/network/v2/network_meter.py +19 -6
- openstackclient/network/v2/network_meter_rule.py +20 -2
- openstackclient/network/v2/network_qos_policy.py +15 -7
- openstackclient/network/v2/network_qos_rule.py +16 -1
- openstackclient/network/v2/network_qos_rule_type.py +16 -5
- openstackclient/network/v2/network_rbac.py +12 -5
- openstackclient/network/v2/network_segment.py +13 -1
- openstackclient/network/v2/network_segment_range.py +15 -3
- openstackclient/network/v2/network_trunk.py +4 -1
- openstackclient/network/v2/port.py +88 -12
- openstackclient/network/v2/router.py +27 -16
- openstackclient/network/v2/security_group.py +18 -49
- openstackclient/network/v2/security_group_rule.py +18 -5
- openstackclient/network/v2/subnet.py +15 -7
- openstackclient/network/v2/subnet_pool.py +13 -8
- openstackclient/network/v2/taas/tap_flow.py +13 -3
- openstackclient/network/v2/taas/tap_mirror.py +7 -4
- openstackclient/network/v2/taas/tap_service.py +4 -1
- openstackclient/object/v1/container.py +3 -1
- openstackclient/object/v1/object.py +3 -1
- openstackclient/tests/functional/identity/v3/common.py +34 -0
- openstackclient/tests/functional/identity/v3/test_application_credential.py +1 -1
- openstackclient/tests/functional/identity/v3/test_mapping.py +81 -0
- openstackclient/tests/functional/volume/v3/test_volume_group.py +163 -0
- openstackclient/tests/unit/common/test_limits.py +1 -1
- openstackclient/tests/unit/common/test_module.py +77 -44
- openstackclient/tests/unit/common/test_quota.py +9 -0
- openstackclient/tests/unit/compute/v2/fakes.py +1 -57
- openstackclient/tests/unit/compute/v2/test_agent.py +4 -4
- openstackclient/tests/unit/compute/v2/test_aggregate.py +1 -1
- openstackclient/tests/unit/compute/v2/test_console.py +2 -2
- openstackclient/tests/unit/compute/v2/test_console_connection.py +1 -1
- openstackclient/tests/unit/compute/v2/test_flavor.py +1 -1
- openstackclient/tests/unit/compute/v2/test_host.py +3 -3
- openstackclient/tests/unit/compute/v2/test_hypervisor.py +2 -2
- openstackclient/tests/unit/compute/v2/test_hypervisor_stats.py +1 -1
- openstackclient/tests/unit/compute/v2/test_keypair.py +1 -1
- openstackclient/tests/unit/compute/v2/test_server.py +15 -15
- openstackclient/tests/unit/compute/v2/test_server_backup.py +1 -1
- openstackclient/tests/unit/compute/v2/test_server_event.py +2 -2
- openstackclient/tests/unit/compute/v2/test_server_group.py +1 -1
- openstackclient/tests/unit/compute/v2/test_server_image.py +1 -1
- openstackclient/tests/unit/compute/v2/test_server_migration.py +4 -4
- openstackclient/tests/unit/compute/v2/test_server_share.py +4 -4
- openstackclient/tests/unit/compute/v2/test_server_volume.py +2 -2
- openstackclient/tests/unit/compute/v2/test_service.py +3 -3
- openstackclient/tests/unit/compute/v2/test_usage.py +1 -1
- openstackclient/tests/unit/identity/v2_0/fakes.py +3 -7
- openstackclient/tests/unit/identity/v2_0/test_endpoint.py +1 -1
- openstackclient/tests/unit/identity/v2_0/test_project.py +1 -1
- openstackclient/tests/unit/identity/v2_0/test_role.py +1 -1
- openstackclient/tests/unit/identity/v2_0/test_role_assignment.py +1 -1
- openstackclient/tests/unit/identity/v2_0/test_service.py +1 -1
- openstackclient/tests/unit/identity/v2_0/test_token.py +2 -2
- openstackclient/tests/unit/identity/v2_0/test_user.py +1 -1
- openstackclient/tests/unit/identity/v3/fakes.py +5 -38
- openstackclient/tests/unit/identity/v3/test_access_rule.py +3 -3
- openstackclient/tests/unit/identity/v3/test_application_credential.py +4 -4
- openstackclient/tests/unit/identity/v3/test_credential.py +5 -5
- openstackclient/tests/unit/identity/v3/test_domain.py +5 -5
- openstackclient/tests/unit/identity/v3/test_endpoint.py +6 -6
- openstackclient/tests/unit/identity/v3/test_endpoint_group.py +1 -1
- openstackclient/tests/unit/identity/v3/test_group.py +8 -8
- openstackclient/tests/unit/identity/v3/test_implied_role.py +1 -1
- openstackclient/tests/unit/identity/v3/test_limit.py +5 -5
- openstackclient/tests/unit/identity/v3/test_mappings.py +163 -79
- openstackclient/tests/unit/identity/v3/test_project.py +28 -5
- openstackclient/tests/unit/identity/v3/test_protocol.py +3 -3
- openstackclient/tests/unit/identity/v3/test_region.py +5 -5
- openstackclient/tests/unit/identity/v3/test_registered_limit.py +5 -5
- openstackclient/tests/unit/identity/v3/test_role.py +8 -8
- openstackclient/tests/unit/identity/v3/test_role_assignment.py +1 -1
- openstackclient/tests/unit/identity/v3/test_service.py +5 -5
- openstackclient/tests/unit/identity/v3/test_token.py +2 -2
- openstackclient/tests/unit/identity/v3/test_trust.py +4 -4
- openstackclient/tests/unit/identity/v3/test_user.py +73 -6
- openstackclient/tests/unit/network/v2/fakes.py +5 -77
- openstackclient/tests/unit/network/v2/fwaas/test_group.py +28 -2
- openstackclient/tests/unit/network/v2/fwaas/test_rule.py +28 -3
- openstackclient/tests/unit/network/v2/test_address_group.py +24 -0
- openstackclient/tests/unit/network/v2/test_address_scope.py +24 -0
- openstackclient/tests/unit/network/v2/test_floating_ip.py +24 -0
- openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py +24 -0
- openstackclient/tests/unit/network/v2/test_ip_availability.py +25 -0
- openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py +29 -3
- openstackclient/tests/unit/network/v2/test_network.py +74 -12
- openstackclient/tests/unit/network/v2/test_network_agent.py +50 -1
- openstackclient/tests/unit/network/v2/test_network_flavor.py +24 -0
- openstackclient/tests/unit/network/v2/test_network_flavor_profile.py +24 -0
- openstackclient/tests/unit/network/v2/test_network_meter.py +24 -0
- openstackclient/tests/unit/network/v2/test_network_qos_policy.py +24 -0
- openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py +24 -0
- openstackclient/tests/unit/network/v2/test_network_rbac.py +24 -0
- openstackclient/tests/unit/network/v2/test_network_segment.py +24 -0
- openstackclient/tests/unit/network/v2/test_network_segment_range.py +24 -0
- openstackclient/tests/unit/network/v2/test_port.py +166 -0
- openstackclient/tests/unit/network/v2/test_router.py +28 -7
- openstackclient/tests/unit/network/v2/test_security_group.py +22 -0
- openstackclient/tests/unit/network/v2/test_security_group_rule.py +25 -0
- openstackclient/tests/unit/network/v2/test_subnet.py +28 -4
- openstackclient/tests/unit/network/v2/test_subnet_pool.py +24 -0
- openstackclient/tests/unit/volume/v2/fakes.py +20 -140
- openstackclient/tests/unit/volume/v2/test_volume_backup.py +5 -9
- openstackclient/tests/unit/volume/v2/test_volume_snapshot.py +6 -0
- openstackclient/tests/unit/volume/v3/fakes.py +204 -100
- openstackclient/tests/unit/volume/v3/test_backup_record.py +114 -0
- openstackclient/tests/unit/volume/v3/test_consistency_group.py +720 -0
- openstackclient/tests/unit/volume/v3/test_consistency_group_snapshot.py +354 -0
- openstackclient/tests/unit/volume/v3/test_qos_specs.py +455 -0
- openstackclient/tests/unit/volume/v3/test_volume_attachment.py +2 -0
- openstackclient/tests/unit/volume/v3/test_volume_backend.py +158 -0
- openstackclient/tests/unit/volume/v3/test_volume_backup.py +5 -9
- openstackclient/tests/unit/volume/v3/test_volume_group_type.py +65 -0
- openstackclient/tests/unit/volume/v3/test_volume_host.py +115 -0
- openstackclient/tests/unit/volume/v3/test_volume_snapshot.py +6 -0
- openstackclient/volume/v2/volume.py +4 -2
- openstackclient/volume/v2/volume_backup.py +2 -3
- openstackclient/volume/v2/volume_snapshot.py +3 -4
- openstackclient/volume/v3/backup_record.py +94 -0
- openstackclient/volume/v3/consistency_group.py +400 -0
- openstackclient/volume/v3/consistency_group_snapshot.py +225 -0
- openstackclient/volume/v3/qos_specs.py +389 -0
- openstackclient/volume/v3/volume.py +4 -2
- openstackclient/volume/v3/volume_attachment.py +5 -1
- openstackclient/volume/v3/volume_backend.py +130 -0
- openstackclient/volume/v3/volume_backup.py +2 -3
- openstackclient/volume/v3/volume_group_snapshot.py +4 -6
- openstackclient/volume/v3/volume_group_type.py +1 -1
- openstackclient/volume/v3/volume_host.py +74 -0
- openstackclient/volume/v3/volume_message.py +3 -1
- openstackclient/volume/v3/volume_snapshot.py +2 -1
- {python_openstackclient-10.0.0.dist-info → python_openstackclient-10.1.0.dist-info}/METADATA +3 -4
- {python_openstackclient-10.0.0.dist-info → python_openstackclient-10.1.0.dist-info}/RECORD +202 -188
- {python_openstackclient-10.0.0.dist-info → python_openstackclient-10.1.0.dist-info}/entry_points.txt +24 -24
- {python_openstackclient-10.0.0.dist-info → python_openstackclient-10.1.0.dist-info}/licenses/AUTHORS +5 -0
- python_openstackclient-10.1.0.dist-info/pbr.json +1 -0
- python_openstackclient-10.0.0.dist-info/pbr.json +0 -1
- {python_openstackclient-10.0.0.dist-info → python_openstackclient-10.1.0.dist-info}/WHEEL +0 -0
- {python_openstackclient-10.0.0.dist-info → python_openstackclient-10.1.0.dist-info}/licenses/LICENSE +0 -0
- {python_openstackclient-10.0.0.dist-info → python_openstackclient-10.1.0.dist-info}/top_level.txt +0 -0
|
@@ -20,6 +20,7 @@ import logging
|
|
|
20
20
|
from typing import Any
|
|
21
21
|
|
|
22
22
|
from cliff import columns as cliff_columns
|
|
23
|
+
from openstack.network.v2 import subnet as _subnet
|
|
23
24
|
from osc_lib.cli import format_columns
|
|
24
25
|
from osc_lib.cli import parseractions
|
|
25
26
|
from osc_lib import exceptions
|
|
@@ -27,6 +28,7 @@ from osc_lib import utils
|
|
|
27
28
|
from osc_lib.utils import tags as _tag
|
|
28
29
|
|
|
29
30
|
from openstackclient import command
|
|
31
|
+
from openstackclient.common import pagination
|
|
30
32
|
from openstackclient.i18n import _
|
|
31
33
|
from openstackclient.identity import common as identity_common
|
|
32
34
|
from openstackclient.network import common
|
|
@@ -166,7 +168,9 @@ def _get_common_parse_arguments(
|
|
|
166
168
|
)
|
|
167
169
|
|
|
168
170
|
|
|
169
|
-
def _get_columns(
|
|
171
|
+
def _get_columns(
|
|
172
|
+
item: _subnet.Subnet,
|
|
173
|
+
) -> tuple[tuple[str, ...], tuple[str, ...]]:
|
|
170
174
|
column_map = {
|
|
171
175
|
'is_dhcp_enabled': 'enable_dhcp',
|
|
172
176
|
'subnet_pool_id': 'subnetpool_id',
|
|
@@ -302,8 +306,6 @@ def _get_attrs(
|
|
|
302
306
|
return attrs
|
|
303
307
|
|
|
304
308
|
|
|
305
|
-
# TODO(abhiraut): Use the SDK resource mapped attribute names once the
|
|
306
|
-
# OSC minimum requirements include SDK 1.0.
|
|
307
309
|
class CreateSubnet(command.ShowOne, common.NeutronCommandWithExtraArgs):
|
|
308
310
|
_description = _("Create a subnet")
|
|
309
311
|
|
|
@@ -490,8 +492,6 @@ class DeleteSubnet(command.Command):
|
|
|
490
492
|
raise exceptions.CommandError(msg)
|
|
491
493
|
|
|
492
494
|
|
|
493
|
-
# TODO(abhiraut): Use only the SDK resource mapped attribute names once the
|
|
494
|
-
# OSC minimum requirements include SDK 1.0.
|
|
495
495
|
class ListSubnet(command.Lister):
|
|
496
496
|
_description = _("List subnets")
|
|
497
497
|
|
|
@@ -581,6 +581,7 @@ class ListSubnet(command.Lister):
|
|
|
581
581
|
),
|
|
582
582
|
)
|
|
583
583
|
_tag.add_tag_filtering_option_to_parser(parser, _('subnets'))
|
|
584
|
+
pagination.add_marker_pagination_option_to_parser(parser)
|
|
584
585
|
return parser
|
|
585
586
|
|
|
586
587
|
def take_action(
|
|
@@ -588,7 +589,9 @@ class ListSubnet(command.Lister):
|
|
|
588
589
|
) -> tuple[tuple[str, ...], Iterable[tuple[Any, ...]]]:
|
|
589
590
|
identity_client = self.app.client_manager.identity
|
|
590
591
|
network_client = self.app.client_manager.network
|
|
592
|
+
|
|
591
593
|
filters = {}
|
|
594
|
+
|
|
592
595
|
if parsed_args.ip_version:
|
|
593
596
|
filters['ip_version'] = parsed_args.ip_version
|
|
594
597
|
if parsed_args.dhcp:
|
|
@@ -622,7 +625,14 @@ class ListSubnet(command.Lister):
|
|
|
622
625
|
parsed_args.subnet_pool, ignore_missing=False
|
|
623
626
|
).id
|
|
624
627
|
filters['subnetpool_id'] = subnetpool_id
|
|
628
|
+
if parsed_args.marker is not None:
|
|
629
|
+
filters['marker'] = parsed_args.marker
|
|
630
|
+
if parsed_args.limit is not None:
|
|
631
|
+
filters['limit'] = parsed_args.limit
|
|
632
|
+
if parsed_args.max_items is not None:
|
|
633
|
+
filters['max_items'] = parsed_args.max_items
|
|
625
634
|
_tag.get_tag_filtering_args(parsed_args, filters)
|
|
635
|
+
|
|
626
636
|
data = network_client.subnets(**filters)
|
|
627
637
|
|
|
628
638
|
headers: tuple[str, ...] = ('ID', 'Name', 'Network', 'Subnet')
|
|
@@ -664,8 +674,6 @@ class ListSubnet(command.Lister):
|
|
|
664
674
|
)
|
|
665
675
|
|
|
666
676
|
|
|
667
|
-
# TODO(abhiraut): Use the SDK resource mapped attribute names once the
|
|
668
|
-
# OSC minimum requirements include SDK 1.0.
|
|
669
677
|
class SetSubnet(common.NeutronCommandWithExtraArgs):
|
|
670
678
|
_description = _("Set subnet properties")
|
|
671
679
|
|
|
@@ -18,6 +18,7 @@ from collections.abc import Iterable, Sequence
|
|
|
18
18
|
import logging
|
|
19
19
|
from typing import Any
|
|
20
20
|
|
|
21
|
+
from openstack.network.v2 import subnet_pool as _subnet_pool
|
|
21
22
|
from osc_lib.cli import format_columns
|
|
22
23
|
from osc_lib.cli import parseractions
|
|
23
24
|
from osc_lib import exceptions
|
|
@@ -25,15 +26,17 @@ from osc_lib import utils
|
|
|
25
26
|
from osc_lib.utils import tags as _tag
|
|
26
27
|
|
|
27
28
|
from openstackclient import command
|
|
29
|
+
from openstackclient.common import pagination
|
|
28
30
|
from openstackclient.i18n import _
|
|
29
31
|
from openstackclient.identity import common as identity_common
|
|
30
32
|
from openstackclient.network import common
|
|
31
33
|
|
|
32
|
-
|
|
33
34
|
LOG = logging.getLogger(__name__)
|
|
34
35
|
|
|
35
36
|
|
|
36
|
-
def _get_columns(
|
|
37
|
+
def _get_columns(
|
|
38
|
+
item: _subnet_pool.SubnetPool,
|
|
39
|
+
) -> tuple[tuple[str, ...], tuple[str, ...]]:
|
|
37
40
|
column_map = {
|
|
38
41
|
'default_prefix_length': 'default_prefixlen',
|
|
39
42
|
'is_shared': 'shared',
|
|
@@ -156,8 +159,6 @@ def _add_default_options(parser: argparse.ArgumentParser) -> None:
|
|
|
156
159
|
)
|
|
157
160
|
|
|
158
161
|
|
|
159
|
-
# TODO(rtheis): Use the SDK resource mapped attribute names once the
|
|
160
|
-
# OSC minimum requirements include SDK 1.0.
|
|
161
162
|
class CreateSubnetPool(command.ShowOne, common.NeutronCommandWithExtraArgs):
|
|
162
163
|
_description = _("Create subnet pool")
|
|
163
164
|
|
|
@@ -270,8 +271,6 @@ class DeleteSubnetPool(command.Command):
|
|
|
270
271
|
raise exceptions.CommandError(msg)
|
|
271
272
|
|
|
272
273
|
|
|
273
|
-
# TODO(rtheis): Use only the SDK resource mapped attribute names once the
|
|
274
|
-
# OSC minimum requirements include SDK 1.0.
|
|
275
274
|
class ListSubnetPool(command.Lister):
|
|
276
275
|
_description = _("List subnet pools")
|
|
277
276
|
|
|
@@ -334,6 +333,7 @@ class ListSubnetPool(command.Lister):
|
|
|
334
333
|
),
|
|
335
334
|
)
|
|
336
335
|
_tag.add_tag_filtering_option_to_parser(parser, _('subnet pools'))
|
|
336
|
+
pagination.add_marker_pagination_option_to_parser(parser)
|
|
337
337
|
return parser
|
|
338
338
|
|
|
339
339
|
def take_action(
|
|
@@ -366,7 +366,14 @@ class ListSubnetPool(command.Lister):
|
|
|
366
366
|
parsed_args.address_scope, ignore_missing=False
|
|
367
367
|
)
|
|
368
368
|
filters['address_scope_id'] = address_scope.id
|
|
369
|
+
if parsed_args.marker is not None:
|
|
370
|
+
filters['marker'] = parsed_args.marker
|
|
371
|
+
if parsed_args.limit is not None:
|
|
372
|
+
filters['limit'] = parsed_args.limit
|
|
373
|
+
if parsed_args.max_items is not None:
|
|
374
|
+
filters['max_items'] = parsed_args.max_items
|
|
369
375
|
_tag.get_tag_filtering_args(parsed_args, filters)
|
|
376
|
+
|
|
370
377
|
data = network_client.subnet_pools(**filters)
|
|
371
378
|
|
|
372
379
|
headers: tuple[str, ...] = ('ID', 'Name', 'Prefixes')
|
|
@@ -400,8 +407,6 @@ class ListSubnetPool(command.Lister):
|
|
|
400
407
|
)
|
|
401
408
|
|
|
402
409
|
|
|
403
|
-
# TODO(rtheis): Use the SDK resource mapped attribute names once the
|
|
404
|
-
# OSC minimum requirements include SDK 1.0.
|
|
405
410
|
class SetSubnetPool(common.NeutronCommandWithExtraArgs):
|
|
406
411
|
_description = _("Set subnet pool properties")
|
|
407
412
|
|
|
@@ -17,6 +17,7 @@ import logging
|
|
|
17
17
|
from collections.abc import Iterable, Sequence
|
|
18
18
|
from typing import Any
|
|
19
19
|
|
|
20
|
+
from openstack.network.v2 import tap_flow as _tap_flow
|
|
20
21
|
from osc_lib.cli import format_columns
|
|
21
22
|
from osc_lib.cli import identity as identity_utils
|
|
22
23
|
from osc_lib import exceptions
|
|
@@ -26,7 +27,6 @@ from osc_lib.utils import columns as column_util
|
|
|
26
27
|
from openstackclient import command
|
|
27
28
|
from openstackclient.i18n import _
|
|
28
29
|
from openstackclient.identity import common
|
|
29
|
-
from openstackclient.network.v2.taas import tap_service
|
|
30
30
|
|
|
31
31
|
LOG = logging.getLogger(__name__)
|
|
32
32
|
|
|
@@ -55,6 +55,16 @@ def _add_updatable_args(parser: argparse.ArgumentParser) -> None:
|
|
|
55
55
|
)
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
def _get_columns(
|
|
59
|
+
item: _tap_flow.TapFlow,
|
|
60
|
+
) -> tuple[tuple[str, ...], tuple[str, ...]]:
|
|
61
|
+
column_map: dict[str, str] = {}
|
|
62
|
+
hidden_columns = ['location', 'tenant_id']
|
|
63
|
+
return osc_utils.get_osc_show_columns_for_sdk_resource(
|
|
64
|
+
item, column_map, hidden_columns
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
58
68
|
class CreateTapFlow(command.ShowOne):
|
|
59
69
|
_description = _("Create a new tap flow.")
|
|
60
70
|
|
|
@@ -125,7 +135,7 @@ class CreateTapFlow(command.ShowOne):
|
|
|
125
135
|
parsed_args.project_domain,
|
|
126
136
|
).id
|
|
127
137
|
obj = client.create_tap_flow(**attrs)
|
|
128
|
-
display_columns, columns =
|
|
138
|
+
display_columns, columns = _get_columns(obj)
|
|
129
139
|
data = osc_utils.get_dict_properties(obj, columns)
|
|
130
140
|
return display_columns, data
|
|
131
141
|
|
|
@@ -185,7 +195,7 @@ class ShowTapFlow(command.ShowOne):
|
|
|
185
195
|
parsed_args.tap_flow, ignore_missing=False
|
|
186
196
|
).id
|
|
187
197
|
obj = client.get_tap_flow(id)
|
|
188
|
-
display_columns, columns =
|
|
198
|
+
display_columns, columns = _get_columns(obj)
|
|
189
199
|
data = osc_utils.get_dict_properties(obj, columns)
|
|
190
200
|
return display_columns, data
|
|
191
201
|
|
|
@@ -15,6 +15,7 @@ import logging
|
|
|
15
15
|
from collections.abc import Iterable, Sequence
|
|
16
16
|
from typing import Any
|
|
17
17
|
|
|
18
|
+
from openstack.network.v2 import tap_mirror as _tap_mirror
|
|
18
19
|
from osc_lib.cli import identity as identity_utils
|
|
19
20
|
from osc_lib import exceptions
|
|
20
21
|
from osc_lib import utils as osc_utils
|
|
@@ -42,7 +43,9 @@ _attr_map = [
|
|
|
42
43
|
]
|
|
43
44
|
|
|
44
45
|
|
|
45
|
-
def _get_columns(
|
|
46
|
+
def _get_columns(
|
|
47
|
+
item: _tap_mirror.TapMirror,
|
|
48
|
+
) -> tuple[tuple[str, ...], tuple[str, ...]]:
|
|
46
49
|
column_map: dict[str, str] = {}
|
|
47
50
|
hidden_columns = ['location', 'tenant_id']
|
|
48
51
|
return osc_utils.get_osc_show_columns_for_sdk_resource(
|
|
@@ -118,7 +121,7 @@ class CreateTapMirror(command.ShowOne):
|
|
|
118
121
|
parsed_args.project_domain,
|
|
119
122
|
).id
|
|
120
123
|
obj = client.create_tap_mirror(**attrs)
|
|
121
|
-
display_columns, columns =
|
|
124
|
+
display_columns, columns = _get_columns(obj)
|
|
122
125
|
data = osc_utils.get_dict_properties(obj, columns)
|
|
123
126
|
return display_columns, data
|
|
124
127
|
|
|
@@ -173,7 +176,7 @@ class ShowTapMirror(command.ShowOne):
|
|
|
173
176
|
parsed_args.tap_mirror, ignore_missing=False
|
|
174
177
|
).id
|
|
175
178
|
obj = client.get_tap_mirror(id)
|
|
176
|
-
display_columns, columns =
|
|
179
|
+
display_columns, columns = _get_columns(obj)
|
|
177
180
|
data = osc_utils.get_dict_properties(obj, columns)
|
|
178
181
|
return display_columns, data
|
|
179
182
|
|
|
@@ -243,6 +246,6 @@ class UpdateTapMirror(command.ShowOne):
|
|
|
243
246
|
if parsed_args.description is not None:
|
|
244
247
|
attrs['description'] = parsed_args.description
|
|
245
248
|
obj = client.update_tap_mirror(original_t_s, **attrs)
|
|
246
|
-
display_columns, columns =
|
|
249
|
+
display_columns, columns = _get_columns(obj)
|
|
247
250
|
data = osc_utils.get_dict_properties(obj, columns)
|
|
248
251
|
return display_columns, data
|
|
@@ -17,6 +17,7 @@ import logging
|
|
|
17
17
|
from collections.abc import Iterable, Sequence
|
|
18
18
|
from typing import Any
|
|
19
19
|
|
|
20
|
+
from openstack.network.v2 import tap_service as _tap_service
|
|
20
21
|
from osc_lib.cli import identity as identity_utils
|
|
21
22
|
from osc_lib import exceptions
|
|
22
23
|
from osc_lib import utils as osc_utils
|
|
@@ -47,7 +48,9 @@ def _add_updatable_args(parser: argparse.ArgumentParser) -> None:
|
|
|
47
48
|
)
|
|
48
49
|
|
|
49
50
|
|
|
50
|
-
def _get_columns(
|
|
51
|
+
def _get_columns(
|
|
52
|
+
item: _tap_service.TapService,
|
|
53
|
+
) -> tuple[tuple[str, ...], tuple[str, ...]]:
|
|
51
54
|
column_map: dict[str, str] = {}
|
|
52
55
|
hidden_columns = ['location', 'tenant_id']
|
|
53
56
|
return osc_utils.get_osc_show_columns_for_sdk_resource(
|
|
@@ -132,7 +132,9 @@ class ListContainer(command.Lister):
|
|
|
132
132
|
metavar="<prefix>",
|
|
133
133
|
help=_("Filter list using <prefix>"),
|
|
134
134
|
)
|
|
135
|
-
pagination.add_marker_pagination_option_to_parser(
|
|
135
|
+
pagination.add_marker_pagination_option_to_parser(
|
|
136
|
+
parser, include_max_items=False
|
|
137
|
+
)
|
|
136
138
|
parser.add_argument(
|
|
137
139
|
"--end-marker",
|
|
138
140
|
metavar="<end-marker>",
|
|
@@ -146,7 +146,9 @@ class ListObject(command.Lister):
|
|
|
146
146
|
metavar="<delimiter>",
|
|
147
147
|
help=_("Roll up items with <delimiter>"),
|
|
148
148
|
)
|
|
149
|
-
pagination.add_marker_pagination_option_to_parser(
|
|
149
|
+
pagination.add_marker_pagination_option_to_parser(
|
|
150
|
+
parser, include_max_items=False
|
|
151
|
+
)
|
|
150
152
|
parser.add_argument(
|
|
151
153
|
"--end-marker",
|
|
152
154
|
metavar="<end-marker>",
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
# License for the specific language governing permissions and limitations
|
|
11
11
|
# under the License.
|
|
12
12
|
|
|
13
|
+
import json
|
|
13
14
|
import os
|
|
15
|
+
import tempfile
|
|
14
16
|
from typing import ClassVar
|
|
15
17
|
|
|
16
18
|
import fixtures
|
|
@@ -75,6 +77,10 @@ class IdentityTests(base.TestCase):
|
|
|
75
77
|
]
|
|
76
78
|
ENDPOINT_LIST_PROJECT_HEADERS = ['ID', 'Name']
|
|
77
79
|
|
|
80
|
+
MAPPING_FIELDS = ['id', 'rules', 'schema_version']
|
|
81
|
+
|
|
82
|
+
MAPPING_LIST_HEADERS = ['ID', 'schema_version']
|
|
83
|
+
|
|
78
84
|
IDENTITY_PROVIDER_FIELDS = [
|
|
79
85
|
'description',
|
|
80
86
|
'enabled',
|
|
@@ -371,6 +377,34 @@ class IdentityTests(base.TestCase):
|
|
|
371
377
|
self.assert_show_fields(items, self.ENDPOINT_FIELDS)
|
|
372
378
|
return endpoint['id']
|
|
373
379
|
|
|
380
|
+
def _create_dummy_mapping(self, add_clean_up=True):
|
|
381
|
+
mapping = data_utils.rand_name('Mapping')
|
|
382
|
+
# Create rules file
|
|
383
|
+
with tempfile.NamedTemporaryFile(mode='w+') as f:
|
|
384
|
+
RULES = [
|
|
385
|
+
{
|
|
386
|
+
"local": [{"group": {"id": "85a868"}}],
|
|
387
|
+
"remote": [
|
|
388
|
+
{"type": "orgPersonType", "any_one_of": ["Employee"]},
|
|
389
|
+
{"type": "sn", "any_one_of": ["Young"]},
|
|
390
|
+
],
|
|
391
|
+
}
|
|
392
|
+
]
|
|
393
|
+
f.write(json.dumps(RULES))
|
|
394
|
+
f.flush()
|
|
395
|
+
raw_output = self.openstack(
|
|
396
|
+
f'mapping create {mapping} --rules {f.name} --schema-version 1.0'
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
if add_clean_up:
|
|
400
|
+
self.addCleanup(
|
|
401
|
+
self.openstack,
|
|
402
|
+
f'mapping delete {mapping}',
|
|
403
|
+
)
|
|
404
|
+
items = self.parse_show(raw_output)
|
|
405
|
+
self.assert_show_fields(items, self.MAPPING_FIELDS)
|
|
406
|
+
return mapping
|
|
407
|
+
|
|
374
408
|
def _create_dummy_idp(self, add_clean_up=True):
|
|
375
409
|
identity_provider = data_utils.rand_name('IdentityProvider')
|
|
376
410
|
description = data_utils.rand_name('description')
|
|
@@ -107,7 +107,7 @@ class ApplicationCredentialTests(common.IdentityTests):
|
|
|
107
107
|
secret = data_utils.rand_name('secret')
|
|
108
108
|
description = data_utils.rand_name('description')
|
|
109
109
|
tomorrow = (
|
|
110
|
-
datetime.datetime.now(datetime.
|
|
110
|
+
datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
|
|
111
111
|
+ datetime.timedelta(days=1)
|
|
112
112
|
).strftime('%Y-%m-%dT%H:%M:%S%z')
|
|
113
113
|
role1, role2 = self._create_role_assignments()
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
import json
|
|
14
|
+
import tempfile
|
|
15
|
+
|
|
16
|
+
from openstackclient.tests.functional.identity.v3 import common
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MappingTests(common.IdentityTests):
|
|
20
|
+
def test_mapping_create(self):
|
|
21
|
+
self._create_dummy_mapping()
|
|
22
|
+
|
|
23
|
+
def test_mapping_delete(self):
|
|
24
|
+
mapping = self._create_dummy_mapping(add_clean_up=False)
|
|
25
|
+
raw_output = self.openstack(f'mapping delete {mapping}')
|
|
26
|
+
self.assertEqual(0, len(raw_output))
|
|
27
|
+
|
|
28
|
+
def test_mapping_multi_delete(self):
|
|
29
|
+
mapping_1 = self._create_dummy_mapping(add_clean_up=False)
|
|
30
|
+
mapping_2 = self._create_dummy_mapping(add_clean_up=False)
|
|
31
|
+
raw_output = self.openstack(f'mapping delete {mapping_1} {mapping_2}')
|
|
32
|
+
self.assertEqual(0, len(raw_output))
|
|
33
|
+
|
|
34
|
+
def test_mapping_show(self):
|
|
35
|
+
mapping = self._create_dummy_mapping(add_clean_up=True)
|
|
36
|
+
raw_output = self.openstack(f'mapping show {mapping}')
|
|
37
|
+
items = self.parse_show(raw_output)
|
|
38
|
+
self.assert_show_fields(items, self.MAPPING_FIELDS)
|
|
39
|
+
|
|
40
|
+
def test_mapping_list(self):
|
|
41
|
+
self._create_dummy_mapping(add_clean_up=True)
|
|
42
|
+
raw_output = self.openstack('mapping list')
|
|
43
|
+
items = self.parse_listing(raw_output)
|
|
44
|
+
self.assert_table_structure(items, self.MAPPING_LIST_HEADERS)
|
|
45
|
+
|
|
46
|
+
def test_mapping_set(self):
|
|
47
|
+
mapping = self._create_dummy_mapping(add_clean_up=True)
|
|
48
|
+
new_schema_version = '2.0'
|
|
49
|
+
with tempfile.NamedTemporaryFile(mode='w+') as f:
|
|
50
|
+
NEW_RULES = [
|
|
51
|
+
{
|
|
52
|
+
"local": [{"group": {"id": "85a868"}}],
|
|
53
|
+
"remote": [
|
|
54
|
+
{"type": "orgPersonType", "any_one_of": ["Employee"]},
|
|
55
|
+
{"type": "sn", "any_one_of": ["Young"]},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"local": [
|
|
60
|
+
{"group": {"id": "0cd5e9"}},
|
|
61
|
+
{"user": {"name": "0cd5e9"}},
|
|
62
|
+
],
|
|
63
|
+
"remote": [
|
|
64
|
+
{"type": "UserName"},
|
|
65
|
+
{
|
|
66
|
+
"type": "orgPersonType",
|
|
67
|
+
"not_any_of": ["Contractor", "SubContractor"],
|
|
68
|
+
},
|
|
69
|
+
{"type": "LastName", "any_one_of": ["Bo"]},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
]
|
|
73
|
+
f.write(json.dumps(NEW_RULES))
|
|
74
|
+
f.flush()
|
|
75
|
+
raw_output = self.openstack(
|
|
76
|
+
f'mapping set {mapping} --rules {f.name} --schema-version {new_schema_version}'
|
|
77
|
+
)
|
|
78
|
+
self.assertEqual(0, len(raw_output))
|
|
79
|
+
raw_output = self.openstack(f'mapping show {mapping}')
|
|
80
|
+
updated_value = self.parse_show_as_object(raw_output)
|
|
81
|
+
self.assertEqual('2.0', updated_value['schema_version'])
|
|
@@ -0,0 +1,163 @@
|
|
|
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
|
+
from typing import ClassVar
|
|
14
|
+
import uuid
|
|
15
|
+
|
|
16
|
+
from openstackclient.tests.functional.volume.v3 import common
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class VolumeGroupTests(common.BaseVolumeTests):
|
|
20
|
+
"""Functional tests for volume group."""
|
|
21
|
+
|
|
22
|
+
API_VERSION = '3.13'
|
|
23
|
+
GROUP_TYPE_NAME = uuid.uuid4().hex
|
|
24
|
+
GROUP_TYPE_ID: ClassVar[str]
|
|
25
|
+
VOLUME_TYPE_ID: ClassVar[str]
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def setUpClass(cls):
|
|
29
|
+
super().setUpClass()
|
|
30
|
+
cmd_output = cls.openstack(
|
|
31
|
+
'--os-volume-api-version '
|
|
32
|
+
+ cls.API_VERSION
|
|
33
|
+
+ ' volume group type create '
|
|
34
|
+
+ cls.GROUP_TYPE_NAME,
|
|
35
|
+
parse_output=True,
|
|
36
|
+
)
|
|
37
|
+
cls.GROUP_TYPE_ID = cmd_output['ID']
|
|
38
|
+
|
|
39
|
+
volume_type_name = uuid.uuid4().hex
|
|
40
|
+
cmd_output = cls.openstack(
|
|
41
|
+
'volume type create ' + volume_type_name,
|
|
42
|
+
parse_output=True,
|
|
43
|
+
)
|
|
44
|
+
cls.VOLUME_TYPE_ID = cmd_output['id']
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def tearDownClass(cls):
|
|
48
|
+
try:
|
|
49
|
+
raw_output = cls.openstack(
|
|
50
|
+
'volume type delete ' + cls.VOLUME_TYPE_ID
|
|
51
|
+
)
|
|
52
|
+
cls.assertOutput('', raw_output)
|
|
53
|
+
raw_output = cls.openstack(
|
|
54
|
+
'--os-volume-api-version '
|
|
55
|
+
+ cls.API_VERSION
|
|
56
|
+
+ ' volume group type delete '
|
|
57
|
+
+ cls.GROUP_TYPE_NAME
|
|
58
|
+
)
|
|
59
|
+
cls.assertOutput('', raw_output)
|
|
60
|
+
finally:
|
|
61
|
+
super().tearDownClass()
|
|
62
|
+
|
|
63
|
+
def test_volume_group(self):
|
|
64
|
+
# create volume group
|
|
65
|
+
name = uuid.uuid4().hex
|
|
66
|
+
description = 'description-' + uuid.uuid4().hex
|
|
67
|
+
cmd_output = self.openstack(
|
|
68
|
+
'--os-volume-api-version '
|
|
69
|
+
+ self.API_VERSION
|
|
70
|
+
+ ' volume group create '
|
|
71
|
+
+ '--volume-group-type '
|
|
72
|
+
+ self.GROUP_TYPE_NAME
|
|
73
|
+
+ ' --volume-type '
|
|
74
|
+
+ self.VOLUME_TYPE_ID
|
|
75
|
+
+ ' --name '
|
|
76
|
+
+ name
|
|
77
|
+
+ ' --description '
|
|
78
|
+
+ description,
|
|
79
|
+
parse_output=True,
|
|
80
|
+
)
|
|
81
|
+
group_id = cmd_output['ID']
|
|
82
|
+
self.addCleanup(
|
|
83
|
+
self.wait_for_delete,
|
|
84
|
+
'--os-volume-api-version ' + self.API_VERSION + ' volume group',
|
|
85
|
+
group_id,
|
|
86
|
+
name_field='ID',
|
|
87
|
+
)
|
|
88
|
+
self.addCleanup(
|
|
89
|
+
self.openstack,
|
|
90
|
+
'--os-volume-api-version '
|
|
91
|
+
+ self.API_VERSION
|
|
92
|
+
+ ' volume group delete '
|
|
93
|
+
+ group_id,
|
|
94
|
+
fail_ok=True,
|
|
95
|
+
)
|
|
96
|
+
self.assertIsNotNone(group_id)
|
|
97
|
+
self.assertEqual(name, cmd_output['Name'])
|
|
98
|
+
self.assertEqual(description, cmd_output['Description'])
|
|
99
|
+
self.assertEqual(self.GROUP_TYPE_ID, cmd_output['Group Type'])
|
|
100
|
+
self.assertIn(self.VOLUME_TYPE_ID, cmd_output['Volume Types'])
|
|
101
|
+
|
|
102
|
+
# show volume group
|
|
103
|
+
cmd_output = self.openstack(
|
|
104
|
+
'--os-volume-api-version '
|
|
105
|
+
+ self.API_VERSION
|
|
106
|
+
+ ' volume group show '
|
|
107
|
+
+ group_id,
|
|
108
|
+
parse_output=True,
|
|
109
|
+
)
|
|
110
|
+
self.assertEqual(group_id, cmd_output['ID'])
|
|
111
|
+
self.assertEqual(name, cmd_output['Name'])
|
|
112
|
+
self.assertEqual(description, cmd_output['Description'])
|
|
113
|
+
|
|
114
|
+
# list volume group
|
|
115
|
+
cmd_output = self.openstack(
|
|
116
|
+
'--os-volume-api-version '
|
|
117
|
+
+ self.API_VERSION
|
|
118
|
+
+ ' volume group list',
|
|
119
|
+
parse_output=True,
|
|
120
|
+
)
|
|
121
|
+
self.assertIn(group_id, [group['ID'] for group in cmd_output])
|
|
122
|
+
self.assertIn(name, [group['Name'] for group in cmd_output])
|
|
123
|
+
|
|
124
|
+
# set volume group
|
|
125
|
+
new_name = uuid.uuid4().hex
|
|
126
|
+
new_description = 'description-' + uuid.uuid4().hex
|
|
127
|
+
self.openstack(
|
|
128
|
+
'--os-volume-api-version '
|
|
129
|
+
+ self.API_VERSION
|
|
130
|
+
+ ' volume group set '
|
|
131
|
+
+ '--name '
|
|
132
|
+
+ new_name
|
|
133
|
+
+ ' --description '
|
|
134
|
+
+ new_description
|
|
135
|
+
+ ' '
|
|
136
|
+
+ group_id,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# show updated volume group
|
|
140
|
+
cmd_output = self.openstack(
|
|
141
|
+
'--os-volume-api-version '
|
|
142
|
+
+ self.API_VERSION
|
|
143
|
+
+ ' volume group show '
|
|
144
|
+
+ group_id,
|
|
145
|
+
parse_output=True,
|
|
146
|
+
)
|
|
147
|
+
self.assertEqual(group_id, cmd_output['ID'])
|
|
148
|
+
self.assertEqual(new_name, cmd_output['Name'])
|
|
149
|
+
self.assertEqual(new_description, cmd_output['Description'])
|
|
150
|
+
|
|
151
|
+
# delete volume group
|
|
152
|
+
raw_output = self.openstack(
|
|
153
|
+
'--os-volume-api-version '
|
|
154
|
+
+ self.API_VERSION
|
|
155
|
+
+ ' volume group delete '
|
|
156
|
+
+ group_id,
|
|
157
|
+
)
|
|
158
|
+
self.assertOutput('', raw_output)
|
|
159
|
+
self.wait_for_delete(
|
|
160
|
+
'--os-volume-api-version ' + self.API_VERSION + ' volume group',
|
|
161
|
+
group_id,
|
|
162
|
+
name_field='ID',
|
|
163
|
+
)
|
|
@@ -16,7 +16,7 @@ from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
|
|
|
16
16
|
from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
class TestComputeLimits(compute_fakes.
|
|
19
|
+
class TestComputeLimits(compute_fakes.TestCompute):
|
|
20
20
|
absolute_columns = ['Name', 'Value']
|
|
21
21
|
rate_columns = ["Verb", "URI", "Value", "Remain", "Unit", "Next Available"]
|
|
22
22
|
|