python-openstackclient 7.2.1__py3-none-any.whl → 7.3.1__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.
Files changed (66) hide show
  1. openstackclient/common/limits.py +1 -1
  2. openstackclient/common/quota.py +7 -2
  3. openstackclient/compute/v2/server.py +38 -22
  4. openstackclient/compute/v2/usage.py +2 -2
  5. openstackclient/identity/common.py +22 -34
  6. openstackclient/identity/v3/credential.py +45 -28
  7. openstackclient/identity/v3/limit.py +15 -0
  8. openstackclient/identity/v3/region.py +23 -22
  9. openstackclient/identity/v3/registered_limit.py +18 -0
  10. openstackclient/identity/v3/role.py +287 -117
  11. openstackclient/identity/v3/role_assignment.py +1 -1
  12. openstackclient/identity/v3/service_provider.py +95 -45
  13. openstackclient/identity/v3/trust.py +114 -75
  14. openstackclient/image/v2/image.py +3 -0
  15. openstackclient/network/v2/network.py +33 -0
  16. openstackclient/network/v2/network_flavor_profile.py +1 -17
  17. openstackclient/network/v2/port.py +75 -20
  18. openstackclient/tests/functional/compute/v2/test_server.py +87 -1
  19. openstackclient/tests/functional/identity/v3/common.py +1 -1
  20. openstackclient/tests/functional/identity/v3/test_application_credential.py +2 -1
  21. openstackclient/tests/functional/identity/v3/test_role.py +24 -0
  22. openstackclient/tests/functional/identity/v3/test_role_assignment.py +8 -0
  23. openstackclient/tests/functional/identity/v3/test_service_provider.py +1 -5
  24. openstackclient/tests/functional/network/v2/test_port.py +107 -1
  25. openstackclient/tests/unit/compute/v2/fakes.py +0 -304
  26. openstackclient/tests/unit/compute/v2/test_aggregate.py +40 -31
  27. openstackclient/tests/unit/compute/v2/test_console.py +7 -3
  28. openstackclient/tests/unit/compute/v2/test_hypervisor.py +60 -53
  29. openstackclient/tests/unit/compute/v2/test_keypair.py +57 -69
  30. openstackclient/tests/unit/compute/v2/test_server.py +63 -5
  31. openstackclient/tests/unit/compute/v2/test_server_group.py +99 -105
  32. openstackclient/tests/unit/compute/v2/test_server_volume.py +12 -5
  33. openstackclient/tests/unit/compute/v2/test_service.py +83 -37
  34. openstackclient/tests/unit/compute/v2/test_usage.py +12 -7
  35. openstackclient/tests/unit/identity/v2_0/test_catalog.py +3 -6
  36. openstackclient/tests/unit/identity/v2_0/test_role.py +1 -2
  37. openstackclient/tests/unit/identity/v2_0/test_role_assignment.py +2 -1
  38. openstackclient/tests/unit/identity/v2_0/test_token.py +6 -20
  39. openstackclient/tests/unit/identity/v3/test_catalog.py +2 -5
  40. openstackclient/tests/unit/identity/v3/test_credential.py +74 -63
  41. openstackclient/tests/unit/identity/v3/test_project.py +1 -3
  42. openstackclient/tests/unit/identity/v3/test_region.py +74 -96
  43. openstackclient/tests/unit/identity/v3/test_role.py +679 -603
  44. openstackclient/tests/unit/identity/v3/test_role_assignment.py +263 -1
  45. openstackclient/tests/unit/identity/v3/test_service_provider.py +159 -209
  46. openstackclient/tests/unit/identity/v3/test_token.py +5 -20
  47. openstackclient/tests/unit/identity/v3/test_trust.py +137 -155
  48. openstackclient/tests/unit/image/v2/test_image.py +6 -0
  49. openstackclient/tests/unit/network/v2/fakes.py +3 -0
  50. openstackclient/tests/unit/network/v2/test_network.py +25 -0
  51. openstackclient/tests/unit/network/v2/test_network_flavor_profile.py +0 -35
  52. openstackclient/tests/unit/network/v2/test_port.py +128 -15
  53. openstackclient/tests/unit/utils.py +8 -2
  54. openstackclient/tests/unit/volume/v2/test_volume_backup.py +31 -13
  55. openstackclient/tests/unit/volume/v3/test_volume_backup.py +34 -13
  56. openstackclient/volume/v2/volume_backup.py +11 -2
  57. openstackclient/volume/v3/volume_backup.py +13 -2
  58. {python_openstackclient-7.2.1.dist-info → python_openstackclient-7.3.1.dist-info}/AUTHORS +2 -0
  59. {python_openstackclient-7.2.1.dist-info → python_openstackclient-7.3.1.dist-info}/METADATA +14 -16
  60. {python_openstackclient-7.2.1.dist-info → python_openstackclient-7.3.1.dist-info}/RECORD +65 -65
  61. {python_openstackclient-7.2.1.dist-info → python_openstackclient-7.3.1.dist-info}/WHEEL +1 -1
  62. {python_openstackclient-7.2.1.dist-info → python_openstackclient-7.3.1.dist-info}/entry_points.txt +0 -1
  63. python_openstackclient-7.3.1.dist-info/pbr.json +1 -0
  64. python_openstackclient-7.2.1.dist-info/pbr.json +0 -1
  65. {python_openstackclient-7.2.1.dist-info → python_openstackclient-7.3.1.dist-info}/LICENSE +0 -0
  66. {python_openstackclient-7.2.1.dist-info → python_openstackclient-7.3.1.dist-info}/top_level.txt +0 -0
@@ -38,6 +38,20 @@ class AdminStateColumn(cliff_columns.FormattableColumn):
38
38
  return 'UP' if self._value else 'DOWN'
39
39
 
40
40
 
41
+ class SubPortColumn(format_columns.ListDictColumn):
42
+ def _retrieve_subports(self):
43
+ if isinstance(self._value, dict):
44
+ self._value = self._value['sub_ports']
45
+
46
+ def human_readable(self):
47
+ self._retrieve_subports()
48
+ return super().human_readable()
49
+
50
+ def machine_readable(self):
51
+ self._retrieve_subports()
52
+ return super().machine_readable()
53
+
54
+
41
55
  _formatters = {
42
56
  'admin_state_up': AdminStateColumn,
43
57
  'is_admin_state_up': AdminStateColumn,
@@ -52,6 +66,8 @@ _formatters = {
52
66
  'security_group_ids': format_columns.ListColumn,
53
67
  'tags': format_columns.ListColumn,
54
68
  }
69
+ _list_formatters = copy.deepcopy(_formatters)
70
+ _list_formatters.update({'trunk_details': SubPortColumn})
55
71
 
56
72
 
57
73
  def _get_columns(item):
@@ -93,6 +109,7 @@ def _get_columns(item):
93
109
  'status': 'status',
94
110
  'tags': 'tags',
95
111
  'trunk_details': 'trunk_details',
112
+ 'trusted': 'trusted',
96
113
  'updated_at': 'updated_at',
97
114
  }
98
115
  return (
@@ -222,6 +239,10 @@ def _get_attrs(client_manager, parsed_args):
222
239
  and parsed_args.hardware_offload_type
223
240
  ):
224
241
  attrs['hardware_offload_type'] = parsed_args.hardware_offload_type
242
+ if parsed_args.not_trusted:
243
+ attrs['trusted'] = False
244
+ if parsed_args.trusted:
245
+ attrs['trusted'] = True
225
246
 
226
247
  return attrs
227
248
 
@@ -388,6 +409,25 @@ def _add_updatable_args(parser, create=False):
388
409
  '(repeat option to set multiple hints)'
389
410
  ),
390
411
  )
412
+ port_trusted = parser.add_mutually_exclusive_group()
413
+ port_trusted.add_argument(
414
+ '--trusted',
415
+ action='store_true',
416
+ help=_(
417
+ "Set port to be trusted. This will be populated into the "
418
+ "'binding:profile' dictionary and passed to the services "
419
+ "which expect it in this dictionary (for example, Nova)"
420
+ ),
421
+ )
422
+ port_trusted.add_argument(
423
+ '--not-trusted',
424
+ action='store_true',
425
+ help=_(
426
+ "Set port to be not trusted. This will be populated into the "
427
+ "'binding:profile' dictionary and passed to the services "
428
+ "which expect it in this dictionary (for example, Nova)"
429
+ ),
430
+ )
391
431
 
392
432
 
393
433
  # TODO(abhiraut): Use the SDK resource mapped attribute names once the
@@ -534,7 +574,7 @@ class CreatePort(command.ShowOne, common.NeutronCommandWithExtraArgs):
534
574
  '--security-group',
535
575
  metavar='<security-group>',
536
576
  action='append',
537
- dest='security_group',
577
+ dest='security_groups',
538
578
  help=_(
539
579
  "Security group to associate with this port (name or ID) "
540
580
  "(repeat option to set multiple security groups)"
@@ -542,8 +582,9 @@ class CreatePort(command.ShowOne, common.NeutronCommandWithExtraArgs):
542
582
  )
543
583
  secgroups.add_argument(
544
584
  '--no-security-group',
545
- dest='no_security_group',
546
- action='store_true',
585
+ action='store_const',
586
+ const=[],
587
+ dest='security_groups',
547
588
  help=_("Associate no security groups with this port"),
548
589
  )
549
590
  parser.add_argument(
@@ -609,13 +650,11 @@ class CreatePort(command.ShowOne, common.NeutronCommandWithExtraArgs):
609
650
  elif parsed_args.no_fixed_ip:
610
651
  attrs['fixed_ips'] = []
611
652
 
612
- if parsed_args.security_group:
653
+ if parsed_args.security_groups is not None:
613
654
  attrs['security_group_ids'] = [
614
655
  client.find_security_group(sg, ignore_missing=False).id
615
- for sg in parsed_args.security_group
656
+ for sg in parsed_args.security_groups
616
657
  ]
617
- elif parsed_args.no_security_group:
618
- attrs['security_group_ids'] = []
619
658
 
620
659
  if parsed_args.allowed_address_pairs:
621
660
  attrs['allowed_address_pairs'] = _convert_address_pairs(
@@ -828,25 +867,30 @@ class ListPort(command.Lister):
828
867
  network_client = self.app.client_manager.network
829
868
  identity_client = self.app.client_manager.identity
830
869
 
831
- columns = (
870
+ columns = [
832
871
  'id',
833
872
  'name',
834
873
  'mac_address',
835
874
  'fixed_ips',
836
875
  'status',
837
- )
838
- column_headers = (
876
+ ]
877
+ column_headers = [
839
878
  'ID',
840
879
  'Name',
841
880
  'MAC Address',
842
881
  'Fixed IP Addresses',
843
882
  'Status',
844
- )
883
+ ]
845
884
 
846
885
  filters = {}
847
886
  if parsed_args.long:
848
- columns += ('security_group_ids', 'device_owner', 'tags')
849
- column_headers += ('Security Groups', 'Device Owner', 'Tags')
887
+ columns.extend(
888
+ ['security_groups', 'device_owner', 'tags', 'trunk_details']
889
+ )
890
+ column_headers.extend(
891
+ ['Security Groups', 'Device Owner', 'Tags', 'Trunk subports']
892
+ )
893
+
850
894
  if parsed_args.device_owner is not None:
851
895
  filters['device_owner'] = parsed_args.device_owner
852
896
  if parsed_args.device_id is not None:
@@ -894,6 +938,12 @@ class ListPort(command.Lister):
894
938
 
895
939
  data = network_client.ports(fields=columns, **filters)
896
940
 
941
+ if parsed_args.long:
942
+ columns = [
943
+ 'security_group_ids' if item == 'security_groups' else item
944
+ for item in columns
945
+ ]
946
+
897
947
  headers, attrs = utils.calculate_header_and_attrs(
898
948
  column_headers, columns, parsed_args
899
949
  )
@@ -903,7 +953,7 @@ class ListPort(command.Lister):
903
953
  utils.get_item_properties(
904
954
  s,
905
955
  attrs,
906
- formatters=_formatters,
956
+ formatters=_list_formatters,
907
957
  )
908
958
  for s in data
909
959
  ),
@@ -982,7 +1032,7 @@ class SetPort(common.NeutronCommandWithExtraArgs):
982
1032
  '--security-group',
983
1033
  metavar='<security-group>',
984
1034
  action='append',
985
- dest='security_group',
1035
+ dest='security_groups',
986
1036
  help=_(
987
1037
  "Security group to associate with this port (name or ID) "
988
1038
  "(repeat option to set multiple security groups)"
@@ -1083,7 +1133,7 @@ class SetPort(common.NeutronCommandWithExtraArgs):
1083
1133
 
1084
1134
  if parsed_args.no_security_group:
1085
1135
  attrs['security_group_ids'] = []
1086
- if parsed_args.security_group:
1136
+ if parsed_args.security_groups:
1087
1137
  if 'security_group_ids' not in attrs:
1088
1138
  # NOTE(dtroyer): Get existing security groups, iterate the
1089
1139
  # list to force a new list object to be
@@ -1094,7 +1144,7 @@ class SetPort(common.NeutronCommandWithExtraArgs):
1094
1144
  ]
1095
1145
  attrs['security_group_ids'].extend(
1096
1146
  client.find_security_group(sg, ignore_missing=False).id
1097
- for sg in parsed_args.security_group
1147
+ for sg in parsed_args.security_groups
1098
1148
  )
1099
1149
 
1100
1150
  if parsed_args.no_allowed_address_pair:
@@ -1137,6 +1187,11 @@ class SetPort(common.NeutronCommandWithExtraArgs):
1137
1187
  raise exceptions.CommandError(msg)
1138
1188
  attrs['hints'] = expanded_hints
1139
1189
 
1190
+ if parsed_args.not_trusted:
1191
+ attrs['trusted'] = False
1192
+ if parsed_args.trusted:
1193
+ attrs['trusted'] = True
1194
+
1140
1195
  attrs.update(
1141
1196
  self._parse_extra_properties(parsed_args.extra_properties)
1142
1197
  )
@@ -1202,7 +1257,7 @@ class UnsetPort(common.NeutronUnsetCommandWithExtraArgs):
1202
1257
  '--security-group',
1203
1258
  metavar='<security-group>',
1204
1259
  action='append',
1205
- dest='security_group_ids',
1260
+ dest='security_groups',
1206
1261
  help=_(
1207
1262
  "Security group which should be removed this port (name "
1208
1263
  "or ID) (repeat option to unset multiple security groups)"
@@ -1287,9 +1342,9 @@ class UnsetPort(common.NeutronUnsetCommandWithExtraArgs):
1287
1342
  msg = _("Port does not contain binding-profile %s") % key
1288
1343
  raise exceptions.CommandError(msg)
1289
1344
  attrs['binding:profile'] = tmp_binding_profile
1290
- if parsed_args.security_group_ids:
1345
+ if parsed_args.security_groups:
1291
1346
  try:
1292
- for sg in parsed_args.security_group_ids:
1347
+ for sg in parsed_args.security_groups:
1293
1348
  sg_id = client.find_security_group(
1294
1349
  sg, ignore_missing=False
1295
1350
  ).id
@@ -1295,7 +1295,7 @@ class ServerTests(common.ComputeTestCase):
1295
1295
  )
1296
1296
  if ip_address in cmd_output['addresses']['private']:
1297
1297
  # Hang out for a bit and try again
1298
- print('retrying add port check')
1298
+ print('retrying remove port check')
1299
1299
  wait_time += 10
1300
1300
  time.sleep(10)
1301
1301
  else:
@@ -1367,6 +1367,92 @@ class ServerTests(common.ComputeTestCase):
1367
1367
  addresses = cmd_output['addresses']['private']
1368
1368
  self.assertIn(ip_address, addresses)
1369
1369
 
1370
+ def test_server_add_remove_security_group(self):
1371
+ name = uuid.uuid4().hex
1372
+ cmd_output = self.openstack(
1373
+ 'server create '
1374
+ + '--network private '
1375
+ + '--flavor '
1376
+ + self.flavor_name
1377
+ + ' '
1378
+ + '--image '
1379
+ + self.image_name
1380
+ + ' '
1381
+ + '--wait '
1382
+ + name,
1383
+ parse_output=True,
1384
+ )
1385
+
1386
+ self.assertIsNotNone(cmd_output['id'])
1387
+ self.assertEqual(name, cmd_output['name'])
1388
+ self.addCleanup(self.openstack, 'server delete --wait ' + name)
1389
+
1390
+ # create security group
1391
+ security_group_name = uuid.uuid4().hex
1392
+
1393
+ cmd_output = self.openstack(
1394
+ 'security group list',
1395
+ parse_output=True,
1396
+ )
1397
+ self.assertNotIn(security_group_name, cmd_output)
1398
+
1399
+ cmd_output = self.openstack(
1400
+ 'security group create ' + security_group_name,
1401
+ parse_output=True,
1402
+ )
1403
+ self.assertIsNotNone(cmd_output['id'])
1404
+ self.addCleanup(
1405
+ self.openstack, 'security group delete ' + security_group_name
1406
+ )
1407
+
1408
+ # add security group to server, assert the name of the security group
1409
+ # appears
1410
+ self.openstack(
1411
+ 'server add security group ' + name + ' ' + security_group_name
1412
+ )
1413
+
1414
+ wait_time = 0
1415
+ while wait_time < 60:
1416
+ cmd_output = self.openstack(
1417
+ 'server show ' + name,
1418
+ parse_output=True,
1419
+ )
1420
+ if security_group_name not in [
1421
+ x['name'] for x in cmd_output['security_groups']
1422
+ ]:
1423
+ # Hang out for a bit and try again
1424
+ print('retrying add security group check')
1425
+ wait_time += 10
1426
+ time.sleep(10)
1427
+ else:
1428
+ break
1429
+ security_groups = [x['name'] for x in cmd_output['security_groups']]
1430
+ self.assertIn(security_group_name, security_groups)
1431
+
1432
+ # remove security group, assert the name of the security group doesn't
1433
+ # appear
1434
+ self.openstack(
1435
+ 'server remove security group ' + name + ' ' + security_group_name
1436
+ )
1437
+
1438
+ wait_time = 0
1439
+ while wait_time < 60:
1440
+ cmd_output = self.openstack(
1441
+ 'server show ' + name,
1442
+ parse_output=True,
1443
+ )
1444
+ if security_group_name not in [
1445
+ x['name'] for x in cmd_output['security_groups']
1446
+ ]:
1447
+ # Hang out for a bit and try again
1448
+ print('retrying remove security group check')
1449
+ wait_time += 10
1450
+ time.sleep(10)
1451
+ else:
1452
+ break
1453
+ security_groups = [x['name'] for x in cmd_output['security_groups']]
1454
+ self.assertNotIn(security_group_name, security_groups)
1455
+
1370
1456
  def test_server_add_remove_volume(self):
1371
1457
  volume_wait_for = volume_common.BaseVolumeTests.wait_for_status
1372
1458
 
@@ -49,7 +49,7 @@ class IdentityTests(base.TestCase):
49
49
  ]
50
50
  ROLE_FIELDS = ['id', 'name', 'domain_id', 'description']
51
51
  SERVICE_FIELDS = ['id', 'enabled', 'name', 'type', 'description']
52
- REGION_FIELDS = ['description', 'enabled', 'parent_region', 'region']
52
+ REGION_FIELDS = ['description', 'parent_region', 'region']
53
53
  ENDPOINT_FIELDS = [
54
54
  'id',
55
55
  'region',
@@ -107,7 +107,8 @@ 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.utcnow() + datetime.timedelta(days=1)
110
+ datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
111
+ + datetime.timedelta(days=1)
111
112
  ).strftime('%Y-%m-%dT%H:%M:%S%z')
112
113
  role1, role2 = self._create_role_assignments()
113
114
  raw_output = self.openstack(
@@ -93,6 +93,30 @@ class RoleTests(common.IdentityTests):
93
93
  )
94
94
  self.assertEqual(0, len(raw_output))
95
95
 
96
+ def test_role_add_inherited(self):
97
+ role_name = self._create_dummy_role()
98
+ username = self._create_dummy_user()
99
+ raw_output = self.openstack(
100
+ 'role add '
101
+ f'--project {self.project_name} '
102
+ f'--project-domain {self.domain_name} '
103
+ f'--user {username} '
104
+ f'--user-domain {self.domain_name} '
105
+ '--inherited '
106
+ f'{role_name}'
107
+ )
108
+ self.addCleanup(
109
+ self.openstack,
110
+ 'role remove '
111
+ f'--project {self.project_name} '
112
+ f'--project-domain {self.domain_name} '
113
+ f'--user {username} '
114
+ f'--user-domain {self.domain_name} '
115
+ '--inherited '
116
+ f'{role_name}',
117
+ )
118
+ self.assertEqual(0, len(raw_output))
119
+
96
120
  def test_role_remove(self):
97
121
  role_name = self._create_dummy_role()
98
122
  username = self._create_dummy_user()
@@ -151,6 +151,14 @@ class RoleAssignmentTests(common.IdentityTests):
151
151
  '--inherited '
152
152
  f'{role_name}'
153
153
  )
154
+ self.addCleanup(
155
+ self.openstack,
156
+ 'role remove '
157
+ f'--project {self.project_name} '
158
+ f'--user {username} '
159
+ '--inherited '
160
+ f'{role_name}',
161
+ )
154
162
  self.assertEqual(0, len(raw_output))
155
163
 
156
164
  raw_output = self.openstack('role assignment list --inherited')
@@ -60,9 +60,5 @@ class ServiceProviderTests(common.IdentityTests):
60
60
  'description': new_description,
61
61
  }
62
62
  )
63
- self.assertEqual(0, len(raw_output))
64
- raw_output = self.openstack(
65
- f'service provider show {service_provider}'
66
- )
67
63
  updated_value = self.parse_show_as_object(raw_output)
68
- self.assertIn(new_description, updated_value['description'])
64
+ self.assertEqual(new_description, updated_value.get('description'))
@@ -81,10 +81,20 @@ class PortTests(common.NetworkTagTests):
81
81
  self.addCleanup(self.openstack, f'port delete {id1}')
82
82
  self.assertEqual(self.NAME, json_output.get('name'))
83
83
 
84
+ # sg for port2
85
+ sg_name1 = uuid.uuid4().hex
84
86
  json_output = self.openstack(
85
- f'port create --network {self.NETWORK_NAME} {self.NAME}x',
87
+ f'security group create {sg_name1}',
86
88
  parse_output=True,
87
89
  )
90
+ sg_id1 = json_output.get('id')
91
+ self.addCleanup(self.openstack, f'security group delete {sg_id1}')
92
+ json_output = self.openstack(
93
+ f'port create --network {self.NETWORK_NAME} '
94
+ f'--security-group {sg_name1} {self.NAME}x',
95
+ parse_output=True,
96
+ )
97
+
88
98
  id2 = json_output.get('id')
89
99
  self.assertIsNotNone(id2)
90
100
  mac2 = json_output.get('mac_address')
@@ -113,6 +123,12 @@ class PortTests(common.NetworkTagTests):
113
123
  id_list = [item.get('ID') for item in json_output]
114
124
  self.assertIn(id1, id_list)
115
125
  self.assertIn(id2, id_list)
126
+ item_sg_map = {
127
+ item.get('ID'): item.get('Security Groups') for item in json_output
128
+ }
129
+ self.assertIn(id1, item_sg_map.keys())
130
+ self.assertIn(id2, item_sg_map.keys())
131
+ self.assertIn([sg_id1], item_sg_map.values())
116
132
 
117
133
  # Test list --mac-address
118
134
  json_output = self.openstack(
@@ -127,6 +143,17 @@ class PortTests(common.NetworkTagTests):
127
143
  self.assertNotIn(mac1, item_map.values())
128
144
  self.assertIn(mac2, item_map.values())
129
145
 
146
+ # Test list --security-group
147
+ json_output = self.openstack(
148
+ f'port list --security-group {sg_id1}',
149
+ parse_output=True,
150
+ )
151
+ item_map = {
152
+ item.get('ID'): item.get('Security Groups') for item in json_output
153
+ }
154
+ self.assertNotIn(id1, item_map.keys())
155
+ self.assertIn(id2, item_map.keys())
156
+
130
157
  # Test list with unknown fields
131
158
  json_output = self.openstack(
132
159
  'port list -c ID -c Name -c device_id',
@@ -262,3 +289,82 @@ class PortTests(common.NetworkTagTests):
262
289
  f'{self.base_command} create --network {self.NETWORK_NAME} {args} {name}',
263
290
  parse_output=True,
264
291
  )
292
+
293
+ def _trunk_creation(self):
294
+ pport = uuid.uuid4().hex
295
+ sport1 = uuid.uuid4().hex
296
+ sport2 = uuid.uuid4().hex
297
+ trunk = uuid.uuid4().hex
298
+ json_output = self.openstack(
299
+ 'port create ' f'--network {self.NETWORK_NAME} {pport}',
300
+ parse_output=True,
301
+ )
302
+ pport_id = json_output.get('id')
303
+ json_output = self.openstack(
304
+ 'port create ' f'--network {self.NETWORK_NAME} {sport1}',
305
+ parse_output=True,
306
+ )
307
+ sport1_id = json_output.get('id')
308
+ json_output = self.openstack(
309
+ 'port create ' f'--network {self.NETWORK_NAME} {sport2}',
310
+ parse_output=True,
311
+ )
312
+ sport2_id = json_output.get('id')
313
+
314
+ self.openstack(
315
+ f'network trunk create --parent-port {pport} {trunk}',
316
+ )
317
+ self.openstack(
318
+ f'network trunk set --subport port={sport1},'
319
+ f'segmentation-type=vlan,segmentation-id=100 {trunk}',
320
+ )
321
+ self.openstack(
322
+ f'network trunk set --subport port={sport2},'
323
+ f'segmentation-type=vlan,segmentation-id=101 {trunk}',
324
+ )
325
+
326
+ # NOTE(ralonsoh): keep this order to first delete the trunk and then
327
+ # the ports.
328
+ self.addCleanup(self.openstack, f'port delete {pport_id}')
329
+ self.addCleanup(self.openstack, f'port delete {sport1_id}')
330
+ self.addCleanup(self.openstack, f'port delete {sport2_id}')
331
+ self.addCleanup(self.openstack, f'network trunk delete {trunk}')
332
+
333
+ return pport_id, sport1_id, sport2_id
334
+
335
+ def check_subports(self, subports, pport_id, sport1_id, sport2_id):
336
+ self.assertEqual(2, len(subports))
337
+ for subport in subports:
338
+ if subport['port_id'] == sport1_id:
339
+ self.assertEqual(100, subport['segmentation_id'])
340
+ elif subport['port_id'] == sport2_id:
341
+ self.assertEqual(101, subport['segmentation_id'])
342
+ else:
343
+ self.fail(
344
+ f'Port {pport_id} does not have subport '
345
+ f'{subport["port_id"]}'
346
+ )
347
+ self.assertEqual('vlan', subport['segmentation_type'])
348
+
349
+ def test_port_list_with_trunk(self):
350
+ pport_id, sport1_id, sport2_id = self._trunk_creation()
351
+
352
+ # List all ports with "--long" flag to retrieve the trunk details
353
+ json_output = self.openstack(
354
+ 'port list --long',
355
+ parse_output=True,
356
+ )
357
+ port = next(port for port in json_output if port['ID'] == pport_id)
358
+ subports = port['Trunk subports']
359
+ self.check_subports(subports, pport_id, sport1_id, sport2_id)
360
+
361
+ def test_port_show_with_trunk(self):
362
+ pport_id, sport1_id, sport2_id = self._trunk_creation()
363
+
364
+ # List all ports with "--long" flag to retrieve the trunk details
365
+ port = self.openstack(
366
+ f'port show {pport_id}',
367
+ parse_output=True,
368
+ )
369
+ subports = port['trunk_details']['sub_ports']
370
+ self.check_subports(subports, pport_id, sport1_id, sport2_id)