octavia 15.0.0.0rc1__py3-none-any.whl → 16.0.0.0rc1__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 (97) hide show
  1. octavia/amphorae/backends/agent/api_server/keepalivedlvs.py +9 -0
  2. octavia/amphorae/backends/agent/api_server/loadbalancer.py +6 -6
  3. octavia/amphorae/backends/agent/api_server/plug.py +1 -1
  4. octavia/amphorae/backends/agent/api_server/util.py +35 -2
  5. octavia/amphorae/backends/health_daemon/status_message.py +1 -2
  6. octavia/amphorae/drivers/haproxy/rest_api_driver.py +12 -7
  7. octavia/api/drivers/amphora_driver/flavor_schema.py +5 -0
  8. octavia/api/drivers/noop_driver/driver.py +2 -1
  9. octavia/api/drivers/utils.py +12 -0
  10. octavia/api/root_controller.py +8 -2
  11. octavia/api/v2/controllers/base.py +8 -4
  12. octavia/api/v2/controllers/listener.py +12 -2
  13. octavia/api/v2/controllers/load_balancer.py +33 -1
  14. octavia/api/v2/controllers/member.py +58 -4
  15. octavia/api/v2/types/load_balancer.py +7 -1
  16. octavia/api/v2/types/member.py +3 -0
  17. octavia/common/base_taskflow.py +19 -10
  18. octavia/common/clients.py +8 -2
  19. octavia/common/config.py +17 -2
  20. octavia/common/constants.py +6 -0
  21. octavia/common/data_models.py +32 -2
  22. octavia/common/exceptions.py +5 -0
  23. octavia/common/utils.py +4 -1
  24. octavia/common/validate.py +16 -0
  25. octavia/compute/drivers/noop_driver/driver.py +30 -1
  26. octavia/controller/healthmanager/health_manager.py +7 -0
  27. octavia/controller/worker/v2/flows/amphora_flows.py +3 -5
  28. octavia/controller/worker/v2/flows/listener_flows.py +2 -1
  29. octavia/controller/worker/v2/flows/load_balancer_flows.py +38 -0
  30. octavia/controller/worker/v2/taskflow_jobboard_driver.py +34 -6
  31. octavia/controller/worker/v2/tasks/compute_tasks.py +9 -5
  32. octavia/controller/worker/v2/tasks/database_tasks.py +26 -6
  33. octavia/controller/worker/v2/tasks/network_tasks.py +118 -70
  34. octavia/db/base_models.py +29 -5
  35. octavia/db/migration/alembic_migrations/versions/3097e55493ae_add_sg_id_to_vip_table.py +39 -0
  36. octavia/db/migration/alembic_migrations/versions/8db7a6443785_add_member_vnic_type.py +36 -0
  37. octavia/db/migration/alembic_migrations/versions/fabf4983846b_add_member_port_table.py +40 -0
  38. octavia/db/models.py +43 -1
  39. octavia/db/repositories.py +88 -9
  40. octavia/network/base.py +29 -12
  41. octavia/network/data_models.py +2 -1
  42. octavia/network/drivers/neutron/allowed_address_pairs.py +55 -46
  43. octavia/network/drivers/neutron/base.py +28 -16
  44. octavia/network/drivers/neutron/utils.py +2 -2
  45. octavia/network/drivers/noop_driver/driver.py +150 -29
  46. octavia/policies/__init__.py +4 -0
  47. octavia/policies/advanced_rbac.py +95 -0
  48. octavia/policies/base.py +5 -101
  49. octavia/policies/keystone_default_roles.py +81 -0
  50. octavia/policies/loadbalancer.py +13 -0
  51. octavia/tests/common/constants.py +2 -1
  52. octavia/tests/common/sample_data_models.py +27 -14
  53. octavia/tests/functional/amphorae/backend/agent/api_server/test_server.py +5 -4
  54. octavia/tests/functional/api/drivers/driver_agent/test_driver_agent.py +2 -1
  55. octavia/tests/functional/api/v2/test_health_monitor.py +1 -1
  56. octavia/tests/functional/api/v2/test_l7policy.py +1 -1
  57. octavia/tests/functional/api/v2/test_listener.py +1 -1
  58. octavia/tests/functional/api/v2/test_load_balancer.py +150 -4
  59. octavia/tests/functional/api/v2/test_member.py +50 -0
  60. octavia/tests/functional/api/v2/test_pool.py +1 -1
  61. octavia/tests/functional/api/v2/test_quotas.py +5 -8
  62. octavia/tests/functional/db/base.py +6 -6
  63. octavia/tests/functional/db/test_models.py +124 -1
  64. octavia/tests/functional/db/test_repositories.py +237 -19
  65. octavia/tests/unit/amphorae/backends/agent/api_server/test_util.py +89 -1
  66. octavia/tests/unit/amphorae/drivers/haproxy/test_rest_api_driver_1_0.py +10 -7
  67. octavia/tests/unit/api/drivers/test_utils.py +6 -1
  68. octavia/tests/unit/certificates/generator/test_local.py +1 -1
  69. octavia/tests/unit/common/test_base_taskflow.py +4 -3
  70. octavia/tests/unit/compute/drivers/noop_driver/test_driver.py +28 -2
  71. octavia/tests/unit/controller/worker/v2/flows/test_load_balancer_flows.py +27 -1
  72. octavia/tests/unit/controller/worker/v2/tasks/test_database_tasks.py +28 -6
  73. octavia/tests/unit/controller/worker/v2/tasks/test_network_tasks.py +100 -79
  74. octavia/tests/unit/controller/worker/v2/test_taskflow_jobboard_driver.py +8 -0
  75. octavia/tests/unit/network/drivers/neutron/test_allowed_address_pairs.py +62 -45
  76. octavia/tests/unit/network/drivers/neutron/test_base.py +7 -7
  77. octavia/tests/unit/network/drivers/noop_driver/test_driver.py +55 -42
  78. {octavia-15.0.0.0rc1.data → octavia-16.0.0.0rc1.data}/data/share/octavia/diskimage-create/tox.ini +0 -1
  79. {octavia-15.0.0.0rc1.dist-info → octavia-16.0.0.0rc1.dist-info}/AUTHORS +3 -0
  80. octavia-16.0.0.0rc1.dist-info/METADATA +156 -0
  81. {octavia-15.0.0.0rc1.dist-info → octavia-16.0.0.0rc1.dist-info}/RECORD +95 -90
  82. {octavia-15.0.0.0rc1.dist-info → octavia-16.0.0.0rc1.dist-info}/WHEEL +1 -1
  83. {octavia-15.0.0.0rc1.dist-info → octavia-16.0.0.0rc1.dist-info}/entry_points.txt +1 -1
  84. octavia-16.0.0.0rc1.dist-info/pbr.json +1 -0
  85. octavia-15.0.0.0rc1.dist-info/METADATA +0 -156
  86. octavia-15.0.0.0rc1.dist-info/pbr.json +0 -1
  87. {octavia-15.0.0.0rc1.data → octavia-16.0.0.0rc1.data}/data/share/octavia/LICENSE +0 -0
  88. {octavia-15.0.0.0rc1.data → octavia-16.0.0.0rc1.data}/data/share/octavia/README.rst +0 -0
  89. {octavia-15.0.0.0rc1.data → octavia-16.0.0.0rc1.data}/data/share/octavia/diskimage-create/README.rst +0 -0
  90. {octavia-15.0.0.0rc1.data → octavia-16.0.0.0rc1.data}/data/share/octavia/diskimage-create/diskimage-create.sh +0 -0
  91. {octavia-15.0.0.0rc1.data → octavia-16.0.0.0rc1.data}/data/share/octavia/diskimage-create/image-tests.sh +0 -0
  92. {octavia-15.0.0.0rc1.data → octavia-16.0.0.0rc1.data}/data/share/octavia/diskimage-create/requirements.txt +0 -0
  93. {octavia-15.0.0.0rc1.data → octavia-16.0.0.0rc1.data}/data/share/octavia/diskimage-create/test-requirements.txt +0 -0
  94. {octavia-15.0.0.0rc1.data → octavia-16.0.0.0rc1.data}/data/share/octavia/diskimage-create/version.txt +0 -0
  95. {octavia-15.0.0.0rc1.data → octavia-16.0.0.0rc1.data}/scripts/octavia-wsgi +0 -0
  96. {octavia-15.0.0.0rc1.dist-info → octavia-16.0.0.0rc1.dist-info}/LICENSE +0 -0
  97. {octavia-15.0.0.0rc1.dist-info → octavia-16.0.0.0rc1.dist-info}/top_level.txt +0 -0
@@ -17,16 +17,19 @@ import time
17
17
  from oslo_config import cfg
18
18
  from oslo_log import log as logging
19
19
  from oslo_utils import excutils
20
+ from sqlalchemy.orm import exc as sa_exception
20
21
  from taskflow import task
21
22
  from taskflow.types import failure
22
23
  import tenacity
23
24
 
24
25
  from octavia.common import constants
25
26
  from octavia.common import data_models
27
+ from octavia.common import exceptions
26
28
  from octavia.common import utils
27
29
  from octavia.controller.worker import task_utils
28
30
  from octavia.db import api as db_apis
29
31
  from octavia.db import repositories as repo
32
+ from octavia.i18n import _
30
33
  from octavia.network import base
31
34
  from octavia.network import data_models as n_data_models
32
35
 
@@ -43,6 +46,7 @@ class BaseNetworkTask(task.Task):
43
46
  self.task_utils = task_utils.TaskUtils()
44
47
  self.loadbalancer_repo = repo.LoadBalancerRepository()
45
48
  self.amphora_repo = repo.AmphoraRepository()
49
+ self.amphora_member_port_repo = repo.AmphoraMemberPortRepository()
46
50
 
47
51
  @property
48
52
  def network_driver(self):
@@ -78,6 +82,7 @@ class CalculateAmphoraDelta(BaseNetworkTask):
78
82
  loadbalancer[constants.VIP_NETWORK_ID]
79
83
  }
80
84
 
85
+ net_vnic_type_map = {}
81
86
  for pool in db_lb.pools:
82
87
  for member in pool.members:
83
88
  if (member.subnet_id and
@@ -85,6 +90,8 @@ class CalculateAmphoraDelta(BaseNetworkTask):
85
90
  constants.PENDING_DELETE):
86
91
  member_network = self.network_driver.get_subnet(
87
92
  member.subnet_id).network_id
93
+ net_vnic_type_map[member_network] = getattr(
94
+ member, 'vnic_type', constants.VNIC_TYPE_NORMAL)
88
95
  desired_subnet_to_net_map[member.subnet_id] = (
89
96
  member_network)
90
97
 
@@ -117,7 +124,8 @@ class CalculateAmphoraDelta(BaseNetworkTask):
117
124
  n_data_models.FixedIP(
118
125
  subnet_id=subnet_id)
119
126
  for subnet_id, net_id in desired_subnet_to_net_map.items()
120
- if net_id == add_net_id])
127
+ if net_id == add_net_id],
128
+ vnic_type=net_vnic_type_map[add_net_id])
121
129
  for add_net_id in add_ids]
122
130
 
123
131
  # Calculate member Subnet deltas
@@ -215,45 +223,6 @@ class GetPlumbedNetworks(BaseNetworkTask):
215
223
  amphora[constants.COMPUTE_ID])
216
224
 
217
225
 
218
- class PlugNetworks(BaseNetworkTask):
219
- """Task to plug the networks.
220
-
221
- This uses the delta to add all missing networks/nics
222
- """
223
-
224
- def execute(self, amphora, delta):
225
- """Update the amphora networks for the delta."""
226
-
227
- LOG.debug("Plug or unplug networks for amphora id: %s",
228
- amphora[constants.ID])
229
-
230
- if not delta:
231
- LOG.debug("No network deltas for amphora id: %s",
232
- amphora[constants.ID])
233
- return
234
-
235
- # add nics
236
- for nic in delta[constants.ADD_NICS]:
237
- self.network_driver.plug_network(amphora[constants.COMPUTE_ID],
238
- nic[constants.NETWORK_ID])
239
-
240
- def revert(self, amphora, delta, *args, **kwargs):
241
- """Handle a failed network plug by removing all nics added."""
242
-
243
- LOG.warning("Unable to plug networks for amp id %s",
244
- amphora[constants.ID])
245
- if not delta:
246
- return
247
-
248
- for nic in delta[constants.ADD_NICS]:
249
- try:
250
- self.network_driver.unplug_network(
251
- amphora[constants.COMPUTE_ID],
252
- nic[constants.NETWORK_ID])
253
- except base.NetworkNotFound:
254
- pass
255
-
256
-
257
226
  class UnPlugNetworks(BaseNetworkTask):
258
227
  """Task to unplug the networks
259
228
 
@@ -316,6 +285,14 @@ class HandleNetworkDelta(BaseNetworkTask):
316
285
  fixed_ip.subnet = self.network_driver.get_subnet(
317
286
  fixed_ip.subnet_id)
318
287
 
288
+ def _cleanup_port(self, port_id, compute_id):
289
+ try:
290
+ self.network_driver.delete_port(port_id)
291
+ except Exception:
292
+ LOG.error(f'Unable to delete port {port_id} after failing to plug '
293
+ f'the port into compute {compute_id}. This port '
294
+ f'may now be abandoned in neutron.')
295
+
319
296
  def execute(self, amphora, delta):
320
297
  """Handle network plugging based off deltas."""
321
298
  session = db_apis.get_session()
@@ -324,20 +301,43 @@ class HandleNetworkDelta(BaseNetworkTask):
324
301
  id=amphora.get(constants.ID))
325
302
  updated_ports = {}
326
303
  for nic in delta[constants.ADD_NICS]:
304
+ network_id = nic[constants.NETWORK_ID]
327
305
  subnet_id = nic[constants.FIXED_IPS][0][constants.SUBNET_ID]
328
- interface = self.network_driver.plug_network(
329
- db_amp.compute_id, nic[constants.NETWORK_ID])
330
- port = self.network_driver.get_port(interface.port_id)
331
- # nova may plugged undesired subnets (it plugs one of the subnets
332
- # of the network), we can safely unplug the subnets we don't need,
333
- # the desired subnet will be added in the 'ADD_SUBNETS' loop.
334
- extra_subnets = [
335
- fixed_ip.subnet_id
336
- for fixed_ip in port.fixed_ips
337
- if fixed_ip.subnet_id != subnet_id]
338
- for subnet_id in extra_subnets:
339
- port = self.network_driver.unplug_fixed_ip(
340
- port_id=interface.port_id, subnet_id=subnet_id)
306
+
307
+ try:
308
+ port = self.network_driver.create_port(
309
+ network_id,
310
+ name=f'octavia-lb-member-{amphora.get(constants.ID)}',
311
+ vnic_type=nic[constants.VNIC_TYPE])
312
+ except exceptions.NotFound as e:
313
+ if 'Network' in str(e):
314
+ raise base.NetworkNotFound(str(e))
315
+ raise base.CreatePortException(str(e))
316
+ except Exception as e:
317
+ message = _(f'Error creating a port on network {network_id}. ')
318
+ LOG.exception(message)
319
+ raise base.CreatePortException(message) from e
320
+
321
+ try:
322
+ self.network_driver.plug_port(db_amp, port)
323
+ except exceptions.NotFound as e:
324
+ self._cleanup_port(port.id, db_amp.compute_id)
325
+ if 'Instance' in str(e):
326
+ raise base.AmphoraNotFound(str(e))
327
+ raise base.PlugNetworkException(str(e))
328
+ except Exception as e:
329
+ self._cleanup_port(port.id, db_amp.compute_id)
330
+ message = _('Error plugging amphora (compute_id: '
331
+ '{compute_id}) into network {network_id}.').format(
332
+ compute_id=db_amp.compute_id, network_id=network_id)
333
+ LOG.exception(message)
334
+ raise base.PlugNetworkException(message) from e
335
+ with session.begin():
336
+ self.amphora_member_port_repo.create(
337
+ session, port_id=port.id,
338
+ amphora_id=amphora.get(constants.ID),
339
+ network_id=network_id)
340
+
341
341
  self._fill_port_info(port)
342
342
  updated_ports[port.network_id] = port.to_dict(recurse=True)
343
343
 
@@ -395,6 +395,14 @@ class HandleNetworkDelta(BaseNetworkTask):
395
395
  self.network_driver.delete_port(port_id)
396
396
  except Exception:
397
397
  LOG.exception("Unable to delete the port")
398
+ try:
399
+ with session.begin():
400
+ self.amphora_member_port_repo.delete(session,
401
+ port_id=port_id)
402
+ except sa_exception.NoResultFound:
403
+ # Passively fail here for upgrade compatibility
404
+ LOG.warning("No Amphora member port records found for "
405
+ "port_id: %s", port_id)
398
406
 
399
407
  updated_ports.pop(network_id, None)
400
408
  return {amphora[constants.ID]: list(updated_ports.values())}
@@ -497,6 +505,20 @@ class UpdateVIPSecurityGroup(BaseNetworkTask):
497
505
  return sg_id
498
506
 
499
507
 
508
+ class UpdateAmphoraSecurityGroup(BaseNetworkTask):
509
+ """Task to update SGs for an Amphora."""
510
+
511
+ def execute(self, loadbalancer_id: str):
512
+ session = db_apis.get_session()
513
+ with session.begin():
514
+ db_lb = self.loadbalancer_repo.get(
515
+ session, id=loadbalancer_id)
516
+ for amp in db_lb.amphorae:
517
+ self.network_driver.update_aap_port_sg(db_lb,
518
+ amp,
519
+ db_lb.vip)
520
+
521
+
500
522
  class GetSubnetFromVIP(BaseNetworkTask):
501
523
  """Task to plumb a VIP."""
502
524
 
@@ -536,11 +558,10 @@ class PlugVIPAmphora(BaseNetworkTask):
536
558
  """Handle a failure to plumb a vip."""
537
559
  if isinstance(result, failure.Failure):
538
560
  return
561
+ lb_id = loadbalancer[constants.LOADBALANCER_ID]
539
562
  LOG.warning("Unable to plug VIP for amphora id %s "
540
563
  "load balancer id %s",
541
- amphora.get(constants.ID),
542
- loadbalancer[constants.LOADBALANCER_ID])
543
-
564
+ amphora.get(constants.ID), lb_id)
544
565
  try:
545
566
  session = db_apis.get_session()
546
567
  with session.begin():
@@ -550,15 +571,16 @@ class PlugVIPAmphora(BaseNetworkTask):
550
571
  db_amp.ha_port_id = result[constants.HA_PORT_ID]
551
572
  db_subnet = self.network_driver.get_subnet(
552
573
  subnet[constants.ID])
553
- db_lb = self.loadbalancer_repo.get(
554
- session,
555
- id=loadbalancer[constants.LOADBALANCER_ID])
556
-
574
+ db_lb = self.loadbalancer_repo.get(session, id=lb_id)
557
575
  self.network_driver.unplug_aap_port(db_lb.vip,
558
576
  db_amp, db_subnet)
559
577
  except Exception as e:
560
- LOG.error('Failed to unplug AAP port. Resources may still be in '
561
- 'use for VIP: %s due to error: %s', db_lb.vip, str(e))
578
+ LOG.error(
579
+ 'Failed to unplug AAP port for load balancer: %s. '
580
+ 'Resources may still be in use for VRRP port: %s. '
581
+ 'Due to error: %s',
582
+ lb_id, result[constants.VRRP_PORT_ID], str(e)
583
+ )
562
584
 
563
585
 
564
586
  class UnplugVIP(BaseNetworkTask):
@@ -906,24 +928,29 @@ class DeletePort(BaseNetworkTask):
906
928
  """Delete the network port."""
907
929
  if port_id is None:
908
930
  return
909
- if self.execute.retry.statistics.get(constants.ATTEMPT_NUMBER, 1) == 1:
931
+ # tenacity 8.5.0 moves statistics from the retry object to the function
932
+ try:
933
+ retry_statistics = self.execute.statistics
934
+ except AttributeError:
935
+ retry_statistics = self.execute.retry.statistics
936
+
937
+ if retry_statistics.get(constants.ATTEMPT_NUMBER, 1) == 1:
910
938
  LOG.debug("Deleting network port %s", port_id)
911
939
  else:
912
940
  LOG.warning('Retrying network port %s delete attempt %s of %s.',
913
941
  port_id,
914
- self.execute.retry.statistics[
915
- constants.ATTEMPT_NUMBER],
942
+ retry_statistics[constants.ATTEMPT_NUMBER],
916
943
  self.execute.retry.stop.max_attempt_number)
917
944
  # Let the Taskflow engine know we are working and alive
918
945
  # Don't use get with a default for 'attempt_number', we need to fail
919
946
  # if that number is missing.
920
947
  self.update_progress(
921
- self.execute.retry.statistics[constants.ATTEMPT_NUMBER] /
948
+ retry_statistics[constants.ATTEMPT_NUMBER] /
922
949
  self.execute.retry.stop.max_attempt_number)
923
950
  try:
924
951
  self.network_driver.delete_port(port_id)
925
952
  except Exception:
926
- if (self.execute.retry.statistics[constants.ATTEMPT_NUMBER] !=
953
+ if (retry_statistics[constants.ATTEMPT_NUMBER] !=
927
954
  self.execute.retry.stop.max_attempt_number):
928
955
  LOG.warning('Network port delete for port id: %s failed. '
929
956
  'Retrying.', port_id)
@@ -950,6 +977,22 @@ class DeletePort(BaseNetworkTask):
950
977
  raise
951
978
 
952
979
 
980
+ class DeleteAmphoraMemberPorts(BaseNetworkTask):
981
+ """Task to delete all of the member ports on an Amphora."""
982
+
983
+ def execute(self, amphora_id, passive_failure=False):
984
+ delete_port = DeletePort()
985
+ session = db_apis.get_session()
986
+
987
+ with session.begin():
988
+ ports = self.amphora_member_port_repo.get_port_ids(
989
+ session, amphora_id)
990
+ for port in ports:
991
+ delete_port.execute(port, passive_failure)
992
+ with session.begin():
993
+ self.amphora_member_port_repo.delete(session, port_id=port)
994
+
995
+
953
996
  class CreateVIPBasePort(BaseNetworkTask):
954
997
  """Task to create the VIP base port for an amphora."""
955
998
 
@@ -963,16 +1006,21 @@ class CreateVIPBasePort(BaseNetworkTask):
963
1006
  def execute(self, vip, vip_sg_id, amphora_id, additional_vips):
964
1007
  port_name = constants.AMP_BASE_PORT_PREFIX + amphora_id
965
1008
  fixed_ips = [{constants.SUBNET_ID: vip[constants.SUBNET_ID]}]
966
- sg_id = []
1009
+ sg_ids = []
1010
+ # NOTE(gthiemonge) clarification:
1011
+ # - vip_sg_id is the ID of the SG created and managed by Octavia.
1012
+ # - vip['sg_ids'] are the IDs of the SGs provided by the user.
967
1013
  if vip_sg_id:
968
- sg_id = [vip_sg_id]
1014
+ sg_ids = [vip_sg_id]
1015
+ if vip["sg_ids"]:
1016
+ sg_ids += vip["sg_ids"]
969
1017
  secondary_ips = [vip[constants.IP_ADDRESS]]
970
1018
  for add_vip in additional_vips:
971
1019
  secondary_ips.append(add_vip[constants.IP_ADDRESS])
972
1020
  port = self.network_driver.create_port(
973
1021
  vip[constants.NETWORK_ID], name=port_name, fixed_ips=fixed_ips,
974
1022
  secondary_ips=secondary_ips,
975
- security_group_ids=sg_id,
1023
+ security_group_ids=sg_ids,
976
1024
  qos_policy_id=vip[constants.QOS_POLICY_ID])
977
1025
  LOG.info('Created port %s with ID %s for amphora %s',
978
1026
  port_name, port.id, amphora_id)
octavia/db/base_models.py CHANGED
@@ -11,6 +11,7 @@
11
11
  # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
12
  # License for the specific language governing permissions and limitations
13
13
  # under the License.
14
+ from typing import Optional
14
15
 
15
16
  from wsme import types as wtypes
16
17
 
@@ -58,9 +59,14 @@ class OctaviaBase(models.ModelBase):
58
59
  if obj.__class__.__name__ in ['AdditionalVip']:
59
60
  return (obj.__class__.__name__ +
60
61
  obj.load_balancer_id + obj.subnet_id)
62
+ if obj.__class__.__name__ in ['VipSecurityGroup']:
63
+ return obj.__class__.__name__ + obj.load_balancer_id + obj.sg_id
64
+ if obj.__class__.__name__ in ['AmphoraMemberPort']:
65
+ return obj.__class__.__name__ + obj.port_id
61
66
  raise NotImplementedError
62
67
 
63
- def to_data_model(self, _graph_nodes=None):
68
+ def to_data_model(
69
+ self, _graph_nodes=None, recursion_depth: Optional[int] = None):
64
70
  """Converts to a data model graph.
65
71
 
66
72
  In order to make the resulting data model graph usable no matter how
@@ -71,6 +77,13 @@ class OctaviaBase(models.ModelBase):
71
77
  method. Should not be called from the outside.
72
78
  Contains a dictionary of all OctaviaBase type
73
79
  objects in the generated graph
80
+ :param recursion_depth: Used only for configuring recursion.
81
+ This option allows to limit recursion depth.
82
+ It could be used when we need only main node
83
+ and its first level relationships.
84
+ It allows to save time on recursion calls for
85
+ huge graphs, when only main object is
86
+ necessary.
74
87
  """
75
88
  _graph_nodes = _graph_nodes or {}
76
89
  if not self.__data_model__:
@@ -88,9 +101,16 @@ class OctaviaBase(models.ModelBase):
88
101
  dm_self = self.__data_model__(**dm_kwargs)
89
102
  dm_key = self._get_unique_key(dm_self)
90
103
  _graph_nodes.update({dm_key: dm_self})
104
+ new_depth = recursion_depth
105
+ need_recursion = recursion_depth is None or recursion_depth > 0
106
+ # decrease depth of recursion on new recursion call
107
+ if new_depth:
108
+ new_depth -= 1
91
109
  for attr_name in attr_names:
92
110
  attr = getattr(self, attr_name)
93
- if isinstance(attr, OctaviaBase) and attr.__class__:
111
+ if (need_recursion and
112
+ isinstance(attr, OctaviaBase) and
113
+ attr.__class__):
94
114
  # If this attr is already in the graph node list, just
95
115
  # reference it there and don't recurse.
96
116
  ukey = self._get_unique_key(attr)
@@ -98,18 +118,22 @@ class OctaviaBase(models.ModelBase):
98
118
  setattr(dm_self, attr_name, _graph_nodes[ukey])
99
119
  else:
100
120
  setattr(dm_self, attr_name, attr.to_data_model(
101
- _graph_nodes=_graph_nodes))
121
+ _graph_nodes=_graph_nodes,
122
+ recursion_depth=new_depth))
102
123
  elif isinstance(attr, (collections.InstrumentedList, list)):
103
124
  setattr(dm_self, attr_name, [])
104
125
  listref = getattr(dm_self, attr_name)
105
126
  for item in attr:
106
- if isinstance(item, OctaviaBase) and item.__class__:
127
+ if (need_recursion and
128
+ isinstance(item, OctaviaBase) and
129
+ item.__class__):
107
130
  ukey = self._get_unique_key(item)
108
131
  if ukey in _graph_nodes.keys():
109
132
  listref.append(_graph_nodes[ukey])
110
133
  else:
111
134
  listref.append(
112
- item.to_data_model(_graph_nodes=_graph_nodes))
135
+ item.to_data_model(_graph_nodes=_graph_nodes,
136
+ recursion_depth=new_depth))
113
137
  elif not isinstance(item, OctaviaBase):
114
138
  listref.append(item)
115
139
  return dm_self
@@ -0,0 +1,39 @@
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
+ """add sg_id to vip table
14
+
15
+ Revision ID: 3097e55493ae
16
+ Revises: db2a73e82626
17
+ Create Date: 2024-04-05 10:04:32.015445
18
+
19
+ """
20
+
21
+ from alembic import op
22
+ import sqlalchemy as sa
23
+
24
+
25
+ # revision identifiers, used by Alembic.
26
+ revision = '3097e55493ae'
27
+ down_revision = 'db2a73e82626'
28
+
29
+
30
+ def upgrade():
31
+ op.create_table(
32
+ "vip_security_group",
33
+ sa.Column("load_balancer_id", sa.String(36), nullable=False),
34
+ sa.Column("sg_id", sa.String(36), nullable=False),
35
+ sa.ForeignKeyConstraint(["load_balancer_id"],
36
+ ["vip.load_balancer_id"],
37
+ name="fk_vip_sg_vip_lb_id"),
38
+ sa.PrimaryKeyConstraint("load_balancer_id", "sg_id")
39
+ )
@@ -0,0 +1,36 @@
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
+ """Add member vnic_type
14
+
15
+ Revision ID: 8db7a6443785
16
+ Revises: 3097e55493ae
17
+ Create Date: 2024-03-29 20:34:37.263847
18
+
19
+ """
20
+
21
+ from alembic import op
22
+ import sqlalchemy as sa
23
+
24
+ from octavia.common import constants
25
+
26
+ # revision identifiers, used by Alembic.
27
+ revision = '8db7a6443785'
28
+ down_revision = '3097e55493ae'
29
+
30
+
31
+ def upgrade():
32
+ op.add_column(
33
+ u'member',
34
+ sa.Column(u'vnic_type', sa.String(64), nullable=False,
35
+ server_default=constants.VNIC_TYPE_NORMAL)
36
+ )
@@ -0,0 +1,40 @@
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
+ """add_member_port_table
14
+
15
+ Revision ID: fabf4983846b
16
+ Revises: 8db7a6443785
17
+ Create Date: 2024-08-30 23:12:01.713217
18
+
19
+ """
20
+ from alembic import op
21
+ import sqlalchemy as sa
22
+
23
+ # revision identifiers, used by Alembic.
24
+ revision = 'fabf4983846b'
25
+ down_revision = '8db7a6443785'
26
+
27
+
28
+ def upgrade():
29
+ op.create_table(
30
+ 'amphora_member_port',
31
+ sa.Column('port_id', sa.String(36), primary_key=True),
32
+ sa.Column('amphora_id', sa.String(36), nullable=False, index=True),
33
+ sa.Column('network_id', sa.String(36)),
34
+ sa.Column('created_at', sa.DateTime()),
35
+ sa.Column('updated_at', sa.DateTime())
36
+ )
37
+ op.create_foreign_key(
38
+ 'fk_member_port_amphora_id', 'amphora_member_port',
39
+ 'amphora', ['amphora_id'], ['id']
40
+ )
octavia/db/models.py CHANGED
@@ -229,6 +229,7 @@ class Member(base_models.BASE, base_models.IdMixin, base_models.ProjectMixin,
229
229
  nullable=False)
230
230
  enabled = sa.Column(sa.Boolean(), nullable=False)
231
231
  pool = orm.relationship("Pool", back_populates="members")
232
+ vnic_type = sa.Column(sa.String(64), nullable=True)
232
233
 
233
234
  _tags = orm.relationship(
234
235
  'Tags',
@@ -246,7 +247,7 @@ class Member(base_models.BASE, base_models.IdMixin, base_models.ProjectMixin,
246
247
  f"ip_address={self.ip_address!r}, "
247
248
  f"protocol_port={self.protocol_port!r}, "
248
249
  f"operating_status={self.operating_status!r}, "
249
- f"weight={self.weight!r})")
250
+ f"weight={self.weight!r}, vnic_type={self.vnic_type!r})")
250
251
 
251
252
 
252
253
  class HealthMonitor(base_models.BASE, base_models.IdMixin,
@@ -508,6 +509,14 @@ class Vip(base_models.BASE):
508
509
  octavia_owned = sa.Column(sa.Boolean(), nullable=True)
509
510
  vnic_type = sa.Column(sa.String(64), nullable=True)
510
511
 
512
+ sgs = orm.relationship(
513
+ "VipSecurityGroup", cascade="all,delete-orphan",
514
+ uselist=True, backref=orm.backref("vip", uselist=False))
515
+
516
+ @property
517
+ def sg_ids(self) -> list[str]:
518
+ return [sg.sg_id for sg in self.sgs]
519
+
511
520
 
512
521
  class AdditionalVip(base_models.BASE):
513
522
 
@@ -976,3 +985,36 @@ class ListenerCidr(base_models.BASE):
976
985
  sa.ForeignKey("listener.id", name="fk_listener_cidr_listener_id"),
977
986
  nullable=False)
978
987
  cidr = sa.Column(sa.String(64), nullable=False)
988
+
989
+
990
+ class VipSecurityGroup(base_models.BASE):
991
+
992
+ __data_model__ = data_models.VipSecurityGroup
993
+
994
+ __tablename__ = "vip_security_group"
995
+ __table_args__ = (
996
+ sa.PrimaryKeyConstraint('load_balancer_id', 'sg_id'),
997
+ )
998
+
999
+ load_balancer_id = sa.Column(
1000
+ sa.String(36),
1001
+ sa.ForeignKey("vip.load_balancer_id", name="fk_vip_sg_vip_lb_id"),
1002
+ nullable=False)
1003
+ sg_id = sa.Column(sa.String(64), nullable=False)
1004
+
1005
+
1006
+ class AmphoraMemberPort(base_models.BASE, models.TimestampMixin):
1007
+
1008
+ __data_model__ = data_models.AmphoraMemberPort
1009
+
1010
+ __tablename__ = "amphora_member_port"
1011
+
1012
+ port_id = sa.Column(
1013
+ sa.String(36),
1014
+ primary_key=True)
1015
+ amphora_id = sa.Column(
1016
+ sa.String(36),
1017
+ sa.ForeignKey("amphora.id", name="fk_member_port_amphora_id"),
1018
+ nullable=False)
1019
+ network_id = sa.Column(
1020
+ sa.String(36))