Browse Source

Merge "Fix multi-listener load balancers" into stable/stein

changes/53/679753/1
Zuul 2 weeks ago
parent
commit
30c1da2157
67 changed files with 6620 additions and 2006 deletions
  1. 67
    283
      doc/source/contributor/api/haproxy-amphora-api.rst
  2. 1
    1
      octavia/amphorae/backends/agent/api_server/__init__.py
  3. 6
    6
      octavia/amphorae/backends/agent/api_server/amphora_info.py
  4. 2
    2
      octavia/amphorae/backends/agent/api_server/keepalived.py
  5. 2
    42
      octavia/amphorae/backends/agent/api_server/keepalivedlvs.py
  6. 88
    159
      octavia/amphorae/backends/agent/api_server/loadbalancer.py
  7. 35
    39
      octavia/amphorae/backends/agent/api_server/server.py
  8. 0
    12
      octavia/amphorae/backends/agent/api_server/udp_listener_base.py
  9. 89
    24
      octavia/amphorae/backends/agent/api_server/util.py
  10. 47
    30
      octavia/amphorae/backends/health_daemon/health_daemon.py
  11. 3
    1
      octavia/amphorae/backends/utils/haproxy_query.py
  12. 27
    46
      octavia/amphorae/drivers/driver_base.py
  13. 426
    148
      octavia/amphorae/drivers/haproxy/rest_api_driver.py
  14. 10
    5
      octavia/amphorae/drivers/keepalived/vrrp_rest_driver.py
  15. 33
    41
      octavia/amphorae/drivers/noop_driver/driver.py
  16. 3
    0
      octavia/cmd/health_manager.py
  17. 3
    0
      octavia/cmd/octavia_worker.py
  18. 0
    0
      octavia/common/jinja/haproxy/combined_listeners/__init__.py
  19. 475
    0
      octavia/common/jinja/haproxy/combined_listeners/jinja_cfg.py
  20. 52
    0
      octavia/common/jinja/haproxy/combined_listeners/templates/base.j2
  21. 40
    0
      octavia/common/jinja/haproxy/combined_listeners/templates/haproxy.cfg.j2
  22. 377
    0
      octavia/common/jinja/haproxy/combined_listeners/templates/macros.j2
  23. 0
    0
      octavia/common/jinja/haproxy/split_listeners/__init__.py
  24. 0
    0
      octavia/common/jinja/haproxy/split_listeners/jinja_cfg.py
  25. 0
    0
      octavia/common/jinja/haproxy/split_listeners/templates/base.j2
  26. 0
    0
      octavia/common/jinja/haproxy/split_listeners/templates/haproxy.cfg.j2
  27. 0
    0
      octavia/common/jinja/haproxy/split_listeners/templates/macros.j2
  28. 92
    6
      octavia/controller/healthmanager/health_drivers/update_db.py
  29. 2
    1
      octavia/controller/worker/controller_worker.py
  30. 2
    3
      octavia/controller/worker/flows/amphora_flows.py
  31. 3
    3
      octavia/controller/worker/flows/health_monitor_flows.py
  32. 3
    3
      octavia/controller/worker/flows/l7policy_flows.py
  33. 3
    3
      octavia/controller/worker/flows/l7rule_flows.py
  34. 4
    4
      octavia/controller/worker/flows/listener_flows.py
  35. 1
    1
      octavia/controller/worker/flows/load_balancer_flows.py
  36. 4
    4
      octavia/controller/worker/flows/member_flows.py
  37. 3
    3
      octavia/controller/worker/flows/pool_flows.py
  38. 13
    50
      octavia/controller/worker/tasks/amphora_driver_tasks.py
  39. 3
    1
      octavia/db/repositories.py
  40. 0
    62
      octavia/tests/functional/amphorae/backend/agent/api_server/test_keepalivedlvs.py
  41. 123
    177
      octavia/tests/functional/amphorae/backend/agent/api_server/test_server.py
  42. 2
    1
      octavia/tests/functional/db/test_repositories.py
  43. 33
    10
      octavia/tests/unit/amphorae/backends/agent/api_server/test_amphora_info.py
  44. 7
    7
      octavia/tests/unit/amphorae/backends/agent/api_server/test_haproxy_compatibility.py
  45. 0
    8
      octavia/tests/unit/amphorae/backends/agent/api_server/test_keepalivedlvs.py
  46. 86
    144
      octavia/tests/unit/amphorae/backends/agent/api_server/test_loadbalancer.py
  47. 100
    4
      octavia/tests/unit/amphorae/backends/agent/api_server/test_util.py
  48. 11
    39
      octavia/tests/unit/amphorae/backends/health_daemon/test_health_daemon.py
  49. 29
    20
      octavia/tests/unit/amphorae/backends/utils/test_haproxy_query.py
  50. 331
    318
      octavia/tests/unit/amphorae/drivers/haproxy/test_rest_api_driver_0_5.py
  51. 1299
    0
      octavia/tests/unit/amphorae/drivers/haproxy/test_rest_api_driver_1_0.py
  52. 16
    6
      octavia/tests/unit/amphorae/drivers/keepalived/test_vrrp_rest_driver.py
  53. 12
    17
      octavia/tests/unit/amphorae/drivers/test_noop_amphoraloadbalancer_driver.py
  54. 2
    1
      octavia/tests/unit/certificates/manager/test_barbican.py
  55. 0
    0
      octavia/tests/unit/common/jinja/haproxy/combined_listeners/__init__.py
  56. 1169
    0
      octavia/tests/unit/common/jinja/haproxy/combined_listeners/test_jinja_cfg.py
  57. 0
    0
      octavia/tests/unit/common/jinja/haproxy/split_listeners/__init__.py
  58. 151
    147
      octavia/tests/unit/common/jinja/haproxy/split_listeners/test_jinja_cfg.py
  59. 28
    27
      octavia/tests/unit/common/jinja/lvs/test_lvs_jinja_cfg.py
  60. 1067
    0
      octavia/tests/unit/common/sample_configs/sample_configs_combined.py
  61. 53
    8
      octavia/tests/unit/common/sample_configs/sample_configs_split.py
  62. 6
    6
      octavia/tests/unit/common/tls_utils/test_cert_parser.py
  63. 121
    6
      octavia/tests/unit/controller/healthmanager/health_drivers/test_update_db.py
  64. 2
    1
      octavia/tests/unit/controller/worker/flows/test_load_balancer_flows.py
  65. 40
    75
      octavia/tests/unit/controller/worker/tasks/test_amphora_driver_tasks.py
  66. 2
    1
      octavia/tests/unit/controller/worker/test_controller_worker.py
  67. 11
    0
      releasenotes/notes/haproxy-single-process-b17a3af3a97accea.yaml

+ 67
- 283
doc/source/contributor/api/haproxy-amphora-api.rst View File

@@ -29,9 +29,7 @@ communication is limited to fail-over protocols.)
29 29
 Versioning
30 30
 ----------
31 31
 All Octavia APIs (including internal APIs like this one) are versioned. For the
32
-purposes of this document, the initial version of this API shall be v0.5. (So,
33
-any reference to a *:version* variable should be replaced with the literal
34
-string 'v0.5'.)
32
+purposes of this document, the initial version of this API shall be 1.0.
35 33
 
36 34
 Response codes
37 35
 --------------
@@ -74,136 +72,6 @@ a secure way (ex. memory filesystem).
74 72
 API
75 73
 ===
76 74
 
77
-Get amphora topology
78
---------------------
79
-* **URL:** /*:version*/topology
80
-* **Method:** GET
81
-* **URL params:** none
82
-* **Data params:** none
83
-* **Success Response:**
84
-
85
-  * Code: 200
86
-
87
-    * Content: JSON formatted listing of this amphora's configured topology.
88
-
89
-* **Error Response:**
90
-
91
-  * none
92
-
93
-JSON Response attributes:
94
-
95
-* *hostname* - hostname of amphora
96
-* *uuid* - uuid of amphora
97
-* *topology* - One of: SINGLE, ACTIVE-STANDBY, ACTIVE-ACTIVE
98
-* *role* - One of ACTIVE, STANDBY (only applicable to ACTIVE-STANDBY)
99
-* *ha_ip* - only applicable to ACTIVE-STANDBY topology: Highly-available
100
-  routing IP address for the ACTIVE-STANDBY pair.
101
-
102
-**Examples**
103
-
104
-* Success code 200:
105
-
106
-::
107
-
108
-  JSON response:
109
-  {
110
-    'hostname': 'octavia-haproxy-img-00328',
111
-    'uuid': '6e2bc8a0-2548-4fb7-a5f0-fb1ef4a696ce',
112
-    'topology': 'SINGLE',
113
-    'role': 'ACTIVE',
114
-    'ha_ip': '',
115
-  }
116
-
117
-Set amphora topology
118
---------------------
119
-* **URL:** /*:version*/topology
120
-* **Method:** POST
121
-* **URL params:** none
122
-* **Data params:**
123
-
124
-  * *topology*: One of: SINGLE, ACTIVE-STANDBY, ACTIVE-ACTIVE
125
-  * *role*: One of: ACTIVE, STANDBY (only applicable to ACTIVE-STANDBY)
126
-  * *ha_ip*: (only applicable to ACTIVE-STANDBY) Highly-available IP for the
127
-    HA pair
128
-  * *secret*: (only applicable to ACTIVE-STANDBY topology) Shared secret used
129
-    for authentication with other HA pair member
130
-
131
-* **Success Response:**
132
-
133
-  * Code: 200
134
-
135
-    * Content: OK
136
-
137
-  * Code: 202
138
-
139
-    * Content: OK
140
-
141
-* **Error Response:**
142
-
143
-  * Code: 400
144
-
145
-    * Content: Invalid request.
146
-    * *(Response will also include information on which parameters did not*
147
-      *pass either a syntax check or other topology logic test)*
148
-
149
-  * Code: 503
150
-
151
-    * Content: Topology transition in progress
152
-
153
-* **Response:**
154
-
155
-| OK
156
-
157
-**Notes:** In an ACTIVE-STANDBY configuration, the 'role' parameter might
158
-change spontaneously due to a failure of one node. In other topologies, the
159
-role is not used.
160
-
161
-Also note that some topology changes can take several minutes to enact, yet
162
-we want all API commands to return in a matter of seconds. In this case, a
163
-topology change is initiated, and the amphora status changes from "OK" to
164
-"TOPOLOGY-CHANGE". The controller should not try to change any resources during
165
-this transition. (Any attempts will be met with an error.) Once the
166
-topology change is complete, amphora status should return to "OK". (When the
167
-UDP communication from amphorae to controller is defined, a 'transition
168
-complete' message is probably one good candidate for this type of UDP
169
-communication.)
170
-
171
-**Examples**
172
-
173
-* Success code 200:
174
-
175
-::
176
-
177
-  JSON POST parameters:
178
-  {
179
-    'topology': 'ACTIVE-STANDBY',
180
-    'role': 'ACTIVE',
181
-    'ha_ip': ' 203.0.113.2',
182
-    'secret': 'b20e06cf1abcf29c708d3b437f4a29892a0921d0',
183
-  }
184
-
185
-  Response:
186
-  OK
187
-
188
-* Error code 400:
189
-
190
-::
191
-
192
-  Response:
193
-  {
194
-    'message': 'Invalid request',
195
-    'details': 'Unknown topology: BAD_TEST_DATA',
196
-  }
197
-
198
-* Error code 503:
199
-
200
-::
201
-
202
-  Response:
203
-  {
204
-    'message': 'Topology transition in progress',
205
-  }
206
-
207 75
 Get amphora info
208 76
 ----------------
209 77
 * **URL:** /info
@@ -249,7 +117,7 @@ version string prepended to it.
249 117
 Get amphora details
250 118
 -------------------
251 119
 
252
-* **URL:** /*:version*/details
120
+* **URL:** /1.0/details
253 121
 * **Method:** GET
254 122
 * **URL params:** none
255 123
 * **Data params:** none
@@ -372,7 +240,7 @@ health of the amphora, currently-configured topology and role, etc.
372 240
 Get interface
373 241
 -------------
374 242
 
375
-* **URL:** /*:version*/interface/*:ip*
243
+* **URL:** /1.0/interface/*:ip*
376 244
 * **Method:** GET
377 245
 * **URL params:**
378 246
 
@@ -408,7 +276,7 @@ Get interface
408 276
 ::
409 277
 
410 278
   GET URL:
411
-  https://octavia-haproxy-img-00328.local/v0.5/interface/10.0.0.1
279
+  https://octavia-haproxy-img-00328.local/1.0/interface/10.0.0.1
412 280
 
413 281
   JSON Response:
414 282
       {
@@ -422,7 +290,7 @@ Get interface
422 290
 ::
423 291
 
424 292
   GET URL:
425
-  https://octavia-haproxy-img-00328.local/v0.5/interface/10.5.0.1
293
+  https://octavia-haproxy-img-00328.local/1.0/interface/10.5.0.1
426 294
 
427 295
   JSON Response:
428 296
       {
@@ -435,7 +303,7 @@ Get interface
435 303
 ::
436 304
 
437 305
   GET URL:
438
-  https://octavia-haproxy-img-00328.local/v0.5/interface/10.6.0.1.1
306
+  https://octavia-haproxy-img-00328.local/1.0/interface/10.6.0.1.1
439 307
 
440 308
   JSON Response:
441 309
       {
@@ -446,7 +314,7 @@ Get interface
446 314
 Get all listeners' statuses
447 315
 ---------------------------
448 316
 
449
-* **URL:** /*:version*/listeners
317
+* **URL:** /1.0/listeners
450 318
 * **Method:** GET
451 319
 * **URL params:** none
452 320
 * **Data params:** none
@@ -492,91 +360,14 @@ a valid haproxy configuration).
492 360
     'type': 'TERMINATED_HTTPS',
493 361
    }]
494 362
 
495
-Get a listener's status
496
------------------------
497
-
498
-* **URL:** /*:version*/listeners/*:listener*
499
-* **Method:** GET
500
-* **URL params:**
501
-
502
-  * *:listener* = Listener UUID
503
-
504
-* **Data params:** none
505
-* **Success Response:**
506
-
507
-  * Code: 200
508
-
509
-    * Content: JSON-formatted listener status
510
-
511
-* **Error Response:**
512
-
513
-  * Code: 404
514
-
515
-    * Content: Not Found
516
-
517
-JSON Response attributes:
518
-
519
-* *status* - One of the operational status: ACTIVE, STOPPED, ERROR -
520
-  future versions might support provisioning status:
521
-  PENDING_CREATE, PENDING_UPDATE, PENDING_DELETE, DELETED
522
-* *uuid* - Listener UUID
523
-* *type* - One of: TCP, HTTP, TERMINATED_HTTPS
524
-* *pools* - Map of pool UUIDs and their overall UP / DOWN / DEGRADED status
525
-* *members* - Map of member UUIDs and their overall UP / DOWN status
526
-
527
-
528
-**Notes:** Note that this returns a status if: the pid file exists,
529
-the stats socket exists, or an haproxy configuration is present (not
530
-just if there is a valid haproxy configuration).
531
-
532
-**Examples**
533
-
534
-* Success code 200:
535
-
536
-::
537
-
538
-  JSON Response:
539
-  {
540
-    'status': 'ACTIVE',
541
-    'uuid': 'e2dfddc0-5b9e-11e4-8ed6-0800200c9a66',
542
-    'type': 'HTTP',
543
-    'pools':[
544
-      {
545
-        'uuid': '399bbf4b-5f6c-4370-a61e-ed2ff2fc9387',
546
-        'status': 'UP',
547
-        'members':[
548
-          {'73f6d278-ae1c-4248-ad02-0bfd50d69aab': 'UP'},
549
-          {'2edca57c-5890-4bcb-ae67-4ef75776cc67': 'DOWN'},
550
-        ],
551
-      },
552
-      {
553
-        'uuid': '2250eb21-16ca-44bd-9b12-0b4eb3d18140',
554
-        'status': 'DOWN',
555
-        'members':[
556
-          {'130dff11-4aab-4ba8-a39b-8d77caa7a1ad': 'DOWN'},
557
-        ],
558
-      },
559
-    ],
560
-  }
561
-
562
-* Error code 404:
563
-
564
-::
363
+Start or Stop a load balancer
364
+-----------------------------
565 365
 
566
-    JSON Response:
567
-      {
568
-        'message': 'Listener Not Found',
569
-        'details': 'No listener with UUID: 04bff5c3-5862-4a13-b9e3-9b440d0ed50a',
570
-      }
571
-
572
-Start or Stop a listener
573
-------------------------
574
-
575
-* **URL:** /*:version*/listeners/*:listener*/*:action*
366
+* **URL:** /1.0/loadbalancer/*:object_id*/*:action*
576 367
 * **Method:** PUT
577 368
 * **URL params:**
578 369
 
579
-  * *:listener* = Listener UUID
370
+  * *:object_id* = Object UUID
580 371
   * *:action* = One of: start, stop, reload
581 372
 
582 373
 * **Data params:** none
@@ -612,7 +403,7 @@ Start or Stop a listener
612 403
 
613 404
 | OK
614 405
 | Configuration file is valid
615
-| haproxy daemon for 7e9f91eb-b3e6-4e3b-a1a7-d6f7fdc1de7c started (pid 32428)
406
+| haproxy daemon for 85e2111b-29c4-44be-94f3-e72045805801 started (pid 32428)
616 407
 
617 408
 **Examples:**
618 409
 
@@ -621,12 +412,12 @@ Start or Stop a listener
621 412
 ::
622 413
 
623 414
   PUT URL:
624
-  https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/start
415
+  https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/start
625 416
 
626 417
   JSON Response:
627 418
   {
628 419
     'message': 'OK',
629
-    'details': 'Configuration file is valid\nhaproxy daemon for 04bff5c3-5862-4a13-b9e3-9b440d0ed50a started',
420
+    'details': 'Configuration file is valid\nhaproxy daemon for 85e2111b-29c4-44be-94f3-e72045805801 started',
630 421
   }
631 422
 
632 423
 * Error code 400:
@@ -634,7 +425,7 @@ Start or Stop a listener
634 425
 ::
635 426
 
636 427
   PUT URL:
637
-  https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/BAD_TEST_DATA
428
+  https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/BAD_TEST_DATA
638 429
 
639 430
   JSON Response:
640 431
   {
@@ -647,12 +438,12 @@ Start or Stop a listener
647 438
 ::
648 439
 
649 440
   PUT URL:
650
-  https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/stop
441
+  https://octavia-haproxy-img-00328.local/1.0/loadbalancer/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/stop
651 442
 
652 443
   JSON Response:
653 444
   {
654 445
     'message': 'Listener Not Found',
655
-    'details': 'No listener with UUID: 04bff5c3-5862-4a13-b9e3-9b440d0ed50a',
446
+    'details': 'No loadbalancer with UUID: 04bff5c3-5862-4a13-b9e3-9b440d0ed50a',
656 447
   }
657 448
 
658 449
 * Error code 500:
@@ -660,7 +451,7 @@ Start or Stop a listener
660 451
 ::
661 452
 
662 453
   PUT URL:
663
-  https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/stop
454
+  https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/stop
664 455
 
665 456
   Response:
666 457
   {
@@ -680,7 +471,7 @@ Start or Stop a listener
680 471
 Delete a listener
681 472
 -----------------
682 473
 
683
-* **URL:** /*:version*/listeners/*:listener*
474
+* **URL:** /1.0/listeners/*:listener*
684 475
 * **Method:** DELETE
685 476
 * **URL params:**
686 477
 
@@ -724,7 +515,7 @@ Delete a listener
724 515
 ::
725 516
 
726 517
   DELETE URL:
727
-  https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a
518
+  https://octavia-haproxy-img-00328.local/1.0/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a
728 519
 
729 520
   JSON Response:
730 521
   {
@@ -736,7 +527,7 @@ Delete a listener
736 527
 ::
737 528
 
738 529
   DELETE URL:
739
-  https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a
530
+  https://octavia-haproxy-img-00328.local/1.0/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a
740 531
 
741 532
   JSON Response:
742 533
   {
@@ -756,11 +547,11 @@ Delete a listener
756 547
 Upload SSL certificate PEM file
757 548
 -------------------------------
758 549
 
759
-* **URL:** /*:version*/listeners/*:listener*/certificates/*:filename.pem*
550
+* **URL:** /1.0/loadbalancer/*:loadbalancer_id*/certificates/*:filename.pem*
760 551
 * **Method:** PUT
761 552
 * **URL params:**
762 553
 
763
-  * *:listener* = Listener UUID
554
+  * *:loadbalancer_id* = Load balancer UUID
764 555
   * *:filename* = PEM filename (see notes below for naming convention)
765 556
 
766 557
 * **Data params:** Certificate data. (PEM file should be a concatenation of
@@ -812,7 +603,7 @@ explicitly restarted
812 603
 ::
813 604
 
814 605
   PUT URI:
815
-  https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
606
+  https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/certificates/www.example.com.pem
816 607
   (Put data should contain the certificate information, concatenated as
817 608
   described above)
818 609
 
@@ -826,7 +617,7 @@ explicitly restarted
826 617
 ::
827 618
 
828 619
   PUT URI:
829
-  https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
620
+  https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/certificates/www.example.com.pem
830 621
   (If PUT data does not contain a certificate)
831 622
 
832 623
   JSON Response:
@@ -839,7 +630,7 @@ explicitly restarted
839 630
 ::
840 631
 
841 632
   PUT URI:
842
-  https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
633
+  https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/certificates/www.example.com.pem
843 634
   (If PUT data does not contain an RSA key)
844 635
 
845 636
   JSON Response:
@@ -852,7 +643,7 @@ explicitly restarted
852 643
 ::
853 644
 
854 645
   PUT URI:
855
-  https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
646
+  https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/certificates/www.example.com.pem
856 647
   (If the first certificate and the RSA key do not have the same modulus.)
857 648
 
858 649
   JSON Response:
@@ -865,15 +656,14 @@ explicitly restarted
865 656
 ::
866 657
 
867 658
   PUT URI:
868
-  https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
659
+  https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/certificates/www.example.com.pem
869 660
 
870 661
   JSON Response:
871 662
   {
872 663
     'message': 'Listener Not Found',
873
-    'details': 'No listener with UUID: 04bff5c3-5862-4a13-b9e3-9b440d0ed50a',
664
+    'details': 'No loadbalancer with UUID: 04bff5c3-5862-4a13-b9e3-9b440d0ed50a',
874 665
   }
875 666
 
876
-
877 667
 * Error code 503:
878 668
 
879 669
 ::
@@ -883,15 +673,14 @@ explicitly restarted
883 673
     'message': 'Topology transition in progress',
884 674
   }
885 675
 
886
-
887 676
 Get SSL certificate md5sum
888 677
 --------------------------
889 678
 
890
-* **URL:** /*:version*/listeners/*:listener*/certificates/*:filename.pem*
679
+* **URL:** /1.0/loadbalancer/*:loadbalancer_id*/certificates/*:filename.pem*
891 680
 * **Method:** GET
892 681
 * **URL params:**
893 682
 
894
-  * *:listener* = Listener UUID
683
+  * *:loadbalancer_id* = Load balancer UUID
895 684
   * *:filename* = PEM filename (see notes below for naming convention)
896 685
 
897 686
 * **Data params:** none
@@ -937,7 +726,7 @@ disclosing it over the wire from the amphora is a security risk.
937 726
     JSON Response:
938 727
       {
939 728
         'message': 'Listener Not Found',
940
-        'details': 'No listener with UUID: 04bff5c3-5862-4a13-b9e3-9b440d0ed50a',
729
+        'details': 'No loadbalancer with UUID: 04bff5c3-5862-4a13-b9e3-9b440d0ed50a',
941 730
       }
942 731
 
943 732
 * Error code 404:
@@ -953,11 +742,11 @@ disclosing it over the wire from the amphora is a security risk.
953 742
 Delete SSL certificate PEM file
954 743
 -------------------------------
955 744
 
956
-* **URL:** /*:version*/listeners/*:listener*/certificates/*:filename.pem*
745
+* **URL:** /1.0/loadbalancer/*:loadbalancer_id*/certificates/*:filename.pem*
957 746
 * **Method:** DELETE
958 747
 * **URL params:**
959 748
 
960
-  * *:listener* = Listener UUID
749
+  * *:loadbalancer_id* = Load balancer UUID
961 750
   * *:filename* = PEM filename (see notes below for naming convention)
962 751
 
963 752
 * **Data params:** none
@@ -988,7 +777,7 @@ Delete SSL certificate PEM file
988 777
 ::
989 778
 
990 779
   DELETE URL:
991
-  https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
780
+  https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/certificates/www.example.com.pem
992 781
 
993 782
   JSON Response:
994 783
   {
@@ -1000,7 +789,7 @@ Delete SSL certificate PEM file
1000 789
 ::
1001 790
 
1002 791
   DELETE URL:
1003
-  https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
792
+  https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/certificates/www.example.com.pem
1004 793
 
1005 794
  JSON Response:
1006 795
       {
@@ -1017,14 +806,14 @@ Delete SSL certificate PEM file
1017 806
     'message': 'Topology transition in progress',
1018 807
   }
1019 808
 
1020
-Upload listener haproxy configuration
1021
--------------------------------------
809
+Upload load balancer haproxy configuration
810
+------------------------------------------
1022 811
 
1023
-* **URL:** /*:version*/listeners/*:amphora_id*/*:listener*/haproxy
812
+* **URL:** /1.0/loadbalancer/*:amphora_id*/*:loadbalancer_id*/haproxy
1024 813
 * **Method:** PUT
1025 814
 * **URL params:**
1026 815
 
1027
-  * *:listener* = Listener UUID
816
+  * *:loadbalancer_id* = Load Balancer UUID
1028 817
   * *:amphora_id* = Amphora UUID
1029 818
 
1030 819
 * **Data params:** haproxy configuration file for the listener
@@ -1071,7 +860,7 @@ out of the haproxy daemon status interface for tracking health and stats).
1071 860
 ::
1072 861
 
1073 862
   PUT URL:
1074
-  https://octavia-haproxy-img-00328.local/v0.5/listeners/d459b1c8-54b0-4030-9bec-4f449e73b1ef/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/haproxy
863
+  https://octavia-haproxy-img-00328.local/1.0/loadbalancer/d459b1c8-54b0-4030-9bec-4f449e73b1ef/85e2111b-29c4-44be-94f3-e72045805801/haproxy
1075 864
   (Upload PUT data should be a raw haproxy.conf file.)
1076 865
 
1077 866
   JSON Response:
@@ -1098,14 +887,14 @@ out of the haproxy daemon status interface for tracking health and stats).
1098 887
     'message': 'Topology transition in progress',
1099 888
   }
1100 889
 
1101
-Get listener haproxy configuration
1102
-----------------------------------
890
+Get loadbalancer haproxy configuration
891
+--------------------------------------
1103 892
 
1104
-* **URL:** /*:version*/listeners/*:listener*/haproxy
893
+* **URL:** /1.0/loadbalancer/*:loadbalancer_id*/haproxy
1105 894
 * **Method:** GET
1106 895
 * **URL params:**
1107 896
 
1108
-  * *:listener* = Listener UUID
897
+  * *:loadbalancer_id* = Load balancer UUID
1109 898
 
1110 899
 * **Data params:** none
1111 900
 * **Success Response:**
@@ -1122,7 +911,7 @@ Get listener haproxy configuration
1122 911
 
1123 912
 * **Response:**
1124 913
 
1125
-| # Config file for 7e9f91eb-b3e6-4e3b-a1a7-d6f7fdc1de7c
914
+| # Config file for 85e2111b-29c4-44be-94f3-e72045805801
1126 915
 | (cut for brevity)
1127 916
 
1128 917
 * **Implied actions:** none
@@ -1134,11 +923,11 @@ Get listener haproxy configuration
1134 923
 ::
1135 924
 
1136 925
   GET URL:
1137
-  https://octavia-haproxy-img-00328.local/v0.5/listeners/7e9f91eb-b3e6-4e3b-a1a7-d6f7fdc1de7c/haproxy
926
+  https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/haproxy
1138 927
 
1139 928
   Response is the raw haproxy.cfg:
1140 929
 
1141
-  # Config file for 7e9f91eb-b3e6-4e3b-a1a7-d6f7fdc1de7c
930
+  # Config file for 85e2111b-29c4-44be-94f3-e72045805801
1142 931
   (cut for brevity)
1143 932
 
1144 933
 * Error code 404:
@@ -1147,15 +936,14 @@ Get listener haproxy configuration
1147 936
 
1148 937
     JSON Response:
1149 938
       {
1150
-        'message': 'Listener Not Found',
1151
-        'details': 'No listener with UUID: 04bff5c3-5862-4a13-b9e3-9b440d0ed50a',
939
+        'message': 'Loadbalancer Not Found',
940
+        'details': 'No loadbalancer with UUID: 04bff5c3-5862-4a13-b9e3-9b440d0ed50a',
1152 941
       }
1153 942
 
1154
-
1155 943
 Plug VIP
1156 944
 --------
1157 945
 
1158
-* **URL:** /*:version*/plug/vip/*:ip*
946
+* **URL:** /1.0/plug/vip/*:ip*
1159 947
 * **Method:** Post
1160 948
 * **URL params:**
1161 949
 
@@ -1210,7 +998,7 @@ Plug VIP
1210 998
 ::
1211 999
 
1212 1000
   POST URL:
1213
-  https://octavia-haproxy-img-00328.local/v0.5/plug/vip/203.0.113.2
1001
+  https://octavia-haproxy-img-00328.local/1.0/plug/vip/203.0.113.2
1214 1002
 
1215 1003
   JSON POST parameters:
1216 1004
   {
@@ -1225,10 +1013,6 @@ Plug VIP
1225 1013
         'details': 'VIP 203.0.113.2 plugged on interface eth1'
1226 1014
       }
1227 1015
 
1228
-
1229
-
1230
-
1231
-
1232 1016
 * Error code 400:
1233 1017
 
1234 1018
 ::
@@ -1251,7 +1035,7 @@ Plug VIP
1251 1035
 Plug Network
1252 1036
 ------------
1253 1037
 
1254
-* **URL:** /*:version*/plug/network/
1038
+* **URL:** /1.0/plug/network/
1255 1039
 * **Method:** POST
1256 1040
 * **URL params:** none
1257 1041
 
@@ -1292,7 +1076,7 @@ Plug Network
1292 1076
 ::
1293 1077
 
1294 1078
   POST URL:
1295
-  https://octavia-haproxy-img-00328.local/v0.5/plug/network/
1079
+  https://octavia-haproxy-img-00328.local/1.0/plug/network/
1296 1080
 
1297 1081
   JSON POST parameters:
1298 1082
   {
@@ -1319,7 +1103,7 @@ Plug Network
1319 1103
 Upload SSL server certificate PEM file for Controller Communication
1320 1104
 -------------------------------------------------------------------
1321 1105
 
1322
-* **URL:** /*:version*/certificate
1106
+* **URL:** /1.0/certificate
1323 1107
 * **Method:** PUT
1324 1108
 
1325 1109
 * **Data params:** Certificate data. (PEM file should be a concatenation of
@@ -1362,7 +1146,7 @@ not be available for some time.
1362 1146
 ::
1363 1147
 
1364 1148
   PUT URI:
1365
-  https://octavia-haproxy-img-00328.local/v0.5/certificate
1149
+  https://octavia-haproxy-img-00328.local/1.0/certificate
1366 1150
   (Put data should contain the certificate information, concatenated as
1367 1151
   described above)
1368 1152
 
@@ -1376,7 +1160,7 @@ not be available for some time.
1376 1160
 ::
1377 1161
 
1378 1162
   PUT URI:
1379
-  https://octavia-haproxy-img-00328.local/v0.5/certificates
1163
+  https://octavia-haproxy-img-00328.local/1.0/certificates
1380 1164
   (If PUT data does not contain a certificate)
1381 1165
 
1382 1166
   JSON Response:
@@ -1389,7 +1173,7 @@ not be available for some time.
1389 1173
 ::
1390 1174
 
1391 1175
   PUT URI:
1392
-  https://octavia-haproxy-img-00328.local/v0.5/certificate
1176
+  https://octavia-haproxy-img-00328.local/1.0/certificate
1393 1177
   (If PUT data does not contain an RSA key)
1394 1178
 
1395 1179
   JSON Response:
@@ -1402,7 +1186,7 @@ not be available for some time.
1402 1186
 ::
1403 1187
 
1404 1188
   PUT URI:
1405
-  https://octavia-haproxy-img-00328.local/v0.5/certificate
1189
+  https://octavia-haproxy-img-00328.local/1.0/certificate
1406 1190
   (If the first certificate and the RSA key do not have the same modulus.)
1407 1191
 
1408 1192
   JSON Response:
@@ -1414,7 +1198,7 @@ not be available for some time.
1414 1198
 Upload keepalived configuration
1415 1199
 -------------------------------
1416 1200
 
1417
-* **URL:** /*:version*/vrrp/upload
1201
+* **URL:** /1.0/vrrp/upload
1418 1202
 * **Method:** PUT
1419 1203
 * **URL params:** none
1420 1204
 * **Data params:** none
@@ -1441,7 +1225,7 @@ OK
1441 1225
 ::
1442 1226
 
1443 1227
   PUT URI:
1444
-  https://octavia-haproxy-img-00328.local/v0.5/vrrp/upload
1228
+  https://octavia-haproxy-img-00328.local/1.0/vrrp/upload
1445 1229
 
1446 1230
   JSON Response:
1447 1231
   {
@@ -1452,7 +1236,7 @@ OK
1452 1236
 Start, Stop, or Reload keepalived
1453 1237
 ---------------------------------
1454 1238
 
1455
-* **URL:** /*:version*/vrrp/*:action*
1239
+* **URL:** /1.0/vrrp/*:action*
1456 1240
 * **Method:** PUT
1457 1241
 * **URL params:**
1458 1242
 
@@ -1489,7 +1273,7 @@ Start, Stop, or Reload keepalived
1489 1273
 ::
1490 1274
 
1491 1275
   PUT URL:
1492
-  https://octavia-haproxy-img-00328.local/v0.5/vrrp/start
1276
+  https://octavia-haproxy-img-00328.local/1.0/vrrp/start
1493 1277
 
1494 1278
   JSON Response:
1495 1279
   {
@@ -1502,7 +1286,7 @@ Start, Stop, or Reload keepalived
1502 1286
 ::
1503 1287
 
1504 1288
   PUT URL:
1505
-  https://octavia-haproxy-img-00328.local/v0.5/vrrp/BAD_TEST_DATA
1289
+  https://octavia-haproxy-img-00328.local/1.0/vrrp/BAD_TEST_DATA
1506 1290
 
1507 1291
   JSON Response:
1508 1292
   {
@@ -1515,7 +1299,7 @@ Start, Stop, or Reload keepalived
1515 1299
 ::
1516 1300
 
1517 1301
   PUT URL:
1518
-  https://octavia-haproxy-img-00328.local/v0.5/vrrp/stop
1302
+  https://octavia-haproxy-img-00328.local/1.0/vrrp/stop
1519 1303
 
1520 1304
   JSON Response:
1521 1305
   {
@@ -1526,7 +1310,7 @@ Start, Stop, or Reload keepalived
1526 1310
 Update the amphora agent configuration
1527 1311
 --------------------------------------
1528 1312
 
1529
-* **URL:** /*:version*/config
1313
+* **URL:** /1.0/config
1530 1314
 * **Method:** PUT
1531 1315
 
1532 1316
 * **Data params:** A amphora-agent configuration file
@@ -1561,7 +1345,7 @@ will be updated.
1561 1345
 ::
1562 1346
 
1563 1347
   PUT URL:
1564
-  https://octavia-haproxy-img-00328.local/v0.5/config
1348
+  https://octavia-haproxy-img-00328.local/1.0/config
1565 1349
   (Upload PUT data should be a raw amphora-agent.conf file.)
1566 1350
 
1567 1351
   JSON Response:

+ 1
- 1
octavia/amphorae/backends/agent/api_server/__init__.py View File

@@ -12,4 +12,4 @@
12 12
 #    License for the specific language governing permissions and limitations
13 13
 #    under the License.
14 14
 
15
-VERSION = '0.5'
15
+VERSION = '1.0'

+ 6
- 6
octavia/amphorae/backends/agent/api_server/amphora_info.py View File

@@ -46,7 +46,7 @@ class AmphoraInfo(object):
46 46
         return webob.Response(json=body)
47 47
 
48 48
     def compile_amphora_details(self, extend_udp_driver=None):
49
-        haproxy_listener_list = util.get_listeners()
49
+        haproxy_listener_list = sorted(util.get_listeners())
50 50
         extend_body = {}
51 51
         udp_listener_list = []
52 52
         if extend_udp_driver:
@@ -87,8 +87,8 @@ class AmphoraInfo(object):
87 87
                 'load': self._load(),
88 88
                 'topology': consts.TOPOLOGY_SINGLE,
89 89
                 'topology_status': consts.TOPOLOGY_STATUS_OK,
90
-                'listeners': list(
91
-                    set(haproxy_listener_list + udp_listener_list))
90
+                'listeners': sorted(list(
91
+                    set(haproxy_listener_list + udp_listener_list)))
92 92
                 if udp_listener_list else haproxy_listener_list,
93 93
                 'packages': {}}
94 94
         if extend_body:
@@ -101,10 +101,10 @@ class AmphoraInfo(object):
101 101
         version = subprocess.check_output(cmd.split())
102 102
         return version
103 103
 
104
-    def _count_haproxy_processes(self, listener_list):
104
+    def _count_haproxy_processes(self, lb_list):
105 105
         num = 0
106
-        for listener_id in listener_list:
107
-            if util.is_listener_running(listener_id):
106
+        for lb_id in lb_list:
107
+            if util.is_lb_running(lb_id):
108 108
                 # optional check if it's still running
109 109
                 num += 1
110 110
         return num

+ 2
- 2
octavia/amphorae/backends/agent/api_server/keepalived.py View File

@@ -21,7 +21,7 @@ import jinja2
21 21
 from oslo_log import log as logging
22 22
 import webob
23 23
 
24
-from octavia.amphorae.backends.agent.api_server import listener
24
+from octavia.amphorae.backends.agent.api_server import loadbalancer
25 25
 from octavia.amphorae.backends.agent.api_server import util
26 26
 from octavia.common import constants as consts
27 27
 
@@ -41,7 +41,7 @@ check_script_template = j2_env.get_template(consts.CHECK_SCRIPT_CONF)
41 41
 class Keepalived(object):
42 42
 
43 43
     def upload_keepalived_config(self):
44
-        stream = listener.Wrapped(flask.request.stream)
44
+        stream = loadbalancer.Wrapped(flask.request.stream)
45 45
 
46 46
         if not os.path.exists(util.keepalived_dir()):
47 47
             os.makedirs(util.keepalived_dir())

+ 2
- 42
octavia/amphorae/backends/agent/api_server/keepalivedlvs.py View File

@@ -24,10 +24,9 @@ from oslo_log import log as logging
24 24
 import webob
25 25
 from werkzeug import exceptions
26 26
 
27
-from octavia.amphorae.backends.agent.api_server import listener
27
+from octavia.amphorae.backends.agent.api_server import loadbalancer
28 28
 from octavia.amphorae.backends.agent.api_server import udp_listener_base
29 29
 from octavia.amphorae.backends.agent.api_server import util
30
-from octavia.amphorae.backends.utils import keepalivedlvs_query
31 30
 from octavia.common import constants as consts
32 31
 
33 32
 BUFFER = 100
@@ -49,7 +48,7 @@ class KeepalivedLvs(udp_listener_base.UdpListenerApiServerBase):
49 48
     _SUBSCRIBED_AMP_COMPILE = ['keepalived', 'ipvsadm']
50 49
 
51 50
     def upload_udp_listener_config(self, listener_id):
52
-        stream = listener.Wrapped(flask.request.stream)
51
+        stream = loadbalancer.Wrapped(flask.request.stream)
53 52
         NEED_CHECK = True
54 53
 
55 54
         if not os.path.exists(util.keepalived_lvs_dir()):
@@ -252,45 +251,6 @@ class KeepalivedLvs(udp_listener_base.UdpListenerApiServerBase):
252 251
             })
253 252
         return listeners
254 253
 
255
-    def get_udp_listener_status(self, listener_id):
256
-        """Gets the status of a UDP listener
257
-
258
-        This method will consult the stats socket
259
-        so calling this method will interfere with
260
-        the health daemon with the risk of the amphora
261
-        shut down
262
-
263
-        :param listener_id: The id of the listener
264
-        """
265
-        self._check_udp_listener_exists(listener_id)
266
-
267
-        status = self._check_udp_listener_status(listener_id)
268
-
269
-        if status != consts.ACTIVE:
270
-            stats = dict(
271
-                status=status,
272
-                uuid=listener_id,
273
-                type='UDP'
274
-            )
275
-            return webob.Response(json=stats)
276
-
277
-        stats = dict(
278
-            status=status,
279
-            uuid=listener_id,
280
-            type='UDP'
281
-        )
282
-
283
-        try:
284
-            pool = keepalivedlvs_query.get_udp_listener_pool_status(
285
-                listener_id)
286
-        except subprocess.CalledProcessError as e:
287
-            return webob.Response(json=dict(
288
-                message="Error getting kernel lvs status for udp listener "
289
-                        "{}".format(listener_id),
290
-                details=e.output), status=500)
291
-        stats['pools'] = [pool]
292
-        return webob.Response(json=stats)
293
-
294 254
     def delete_udp_listener(self, listener_id):
295 255
         try:
296 256
             self._check_udp_listener_exists(listener_id)

octavia/amphorae/backends/agent/api_server/listener.py → octavia/amphorae/backends/agent/api_server/loadbalancer.py View File

@@ -31,7 +31,6 @@ from werkzeug import exceptions
31 31
 from octavia.amphorae.backends.agent.api_server import haproxy_compatibility
32 32
 from octavia.amphorae.backends.agent.api_server import osutils
33 33
 from octavia.amphorae.backends.agent.api_server import util
34
-from octavia.amphorae.backends.utils import haproxy_query as query
35 34
 from octavia.common import constants as consts
36 35
 from octavia.common import utils as octavia_utils
37 36
 
@@ -54,10 +53,6 @@ SYSVINIT_TEMPLATE = JINJA_ENV.get_template(SYSVINIT_CONF)
54 53
 SYSTEMD_TEMPLATE = JINJA_ENV.get_template(SYSTEMD_CONF)
55 54
 
56 55
 
57
-class ParsingError(Exception):
58
-    pass
59
-
60
-
61 56
 # Wrap a stream so we can compute the md5 while reading
62 57
 class Wrapped(object):
63 58
     def __init__(self, stream_):
@@ -77,37 +72,37 @@ class Wrapped(object):
77 72
         return getattr(self.stream, attr)
78 73
 
79 74
 
80
-class Listener(object):
75
+class Loadbalancer(object):
81 76
 
82 77
     def __init__(self):
83 78
         self._osutils = osutils.BaseOS.get_os_util()
84 79
 
85
-    def get_haproxy_config(self, listener_id):
80
+    def get_haproxy_config(self, lb_id):
86 81
         """Gets the haproxy config
87 82
 
88 83
         :param listener_id: the id of the listener
89 84
         """
90
-        self._check_listener_exists(listener_id)
91
-        with open(util.config_path(listener_id), 'r') as file:
85
+        self._check_lb_exists(lb_id)
86
+        with open(util.config_path(lb_id), 'r') as file:
92 87
             cfg = file.read()
93 88
             resp = webob.Response(cfg, content_type='text/plain')
94 89
             resp.headers['ETag'] = hashlib.md5(six.b(cfg)).hexdigest()  # nosec
95 90
             return resp
96 91
 
97
-    def upload_haproxy_config(self, amphora_id, listener_id):
92
+    def upload_haproxy_config(self, amphora_id, lb_id):
98 93
         """Upload the haproxy config
99 94
 
100 95
         :param amphora_id: The id of the amphora to update
101
-        :param listener_id: The id of the listener
96
+        :param lb_id: The id of the loadbalancer
102 97
         """
103 98
         stream = Wrapped(flask.request.stream)
104 99
         # We have to hash here because HAProxy has a string length limitation
105 100
         # in the configuration file "peer <peername>" lines
106 101
         peer_name = octavia_utils.base64_sha1_string(amphora_id).rstrip('=')
107
-        if not os.path.exists(util.haproxy_dir(listener_id)):
108
-            os.makedirs(util.haproxy_dir(listener_id))
102
+        if not os.path.exists(util.haproxy_dir(lb_id)):
103
+            os.makedirs(util.haproxy_dir(lb_id))
109 104
 
110
-        name = os.path.join(util.haproxy_dir(listener_id), 'haproxy.cfg.new')
105
+        name = os.path.join(util.haproxy_dir(lb_id), 'haproxy.cfg.new')
111 106
         flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
112 107
         # mode 00600
113 108
         mode = stat.S_IRUSR | stat.S_IWUSR
@@ -148,7 +143,7 @@ class Listener(object):
148 143
                 status=400)
149 144
 
150 145
         # file ok - move it
151
-        os.rename(name, util.config_path(listener_id))
146
+        os.rename(name, util.config_path(lb_id))
152 147
 
153 148
         try:
154 149
 
@@ -156,7 +151,7 @@ class Listener(object):
156 151
 
157 152
             LOG.debug('Found init system: %s', init_system)
158 153
 
159
-            init_path = util.init_path(listener_id, init_system)
154
+            init_path = util.init_path(lb_id, init_system)
160 155
 
161 156
             if init_system == consts.INIT_SYSTEMD:
162 157
                 template = SYSTEMD_TEMPLATE
@@ -194,9 +189,9 @@ class Listener(object):
194 189
 
195 190
                 text = template.render(
196 191
                     peer_name=peer_name,
197
-                    haproxy_pid=util.pid_path(listener_id),
192
+                    haproxy_pid=util.pid_path(lb_id),
198 193
                     haproxy_cmd=util.CONF.haproxy_amphora.haproxy_cmd,
199
-                    haproxy_cfg=util.config_path(listener_id),
194
+                    haproxy_cfg=util.config_path(lb_id),
200 195
                     haproxy_user_group_cfg=consts.HAPROXY_USER_GROUP_CFG,
201 196
                     respawn_count=util.CONF.haproxy_amphora.respawn_count,
202 197
                     respawn_interval=(util.CONF.haproxy_amphora.
@@ -212,25 +207,25 @@ class Listener(object):
212 207
         # Make sure the new service is enabled on boot
213 208
         if init_system == consts.INIT_SYSTEMD:
214 209
             util.run_systemctl_command(
215
-                consts.ENABLE, "haproxy-{list}".format(list=listener_id))
210
+                consts.ENABLE, "haproxy-{lb_id}".format(lb_id=lb_id))
216 211
         elif init_system == consts.INIT_SYSVINIT:
217 212
             try:
218 213
                 subprocess.check_output(init_enable_cmd.split(),
219 214
                                         stderr=subprocess.STDOUT)
220 215
             except subprocess.CalledProcessError as e:
221
-                LOG.error("Failed to enable haproxy-%(list)s service: "
222
-                          "%(err)s %(out)s", {'list': listener_id, 'err': e,
216
+                LOG.error("Failed to enable haproxy-%(lb_id)s service: "
217
+                          "%(err)s %(out)s", {'lb_id': lb_id, 'err': e,
223 218
                                               'out': e.output})
224 219
                 return webob.Response(json=dict(
225 220
                     message="Error enabling haproxy-{0} service".format(
226
-                            listener_id), details=e.output), status=500)
221
+                            lb_id), details=e.output), status=500)
227 222
 
228 223
         res = webob.Response(json={'message': 'OK'}, status=202)
229 224
         res.headers['ETag'] = stream.get_md5()
230 225
 
231 226
         return res
232 227
 
233
-    def start_stop_listener(self, listener_id, action):
228
+    def start_stop_lb(self, lb_id, action):
234 229
         action = action.lower()
235 230
         if action not in [consts.AMP_ACTION_START,
236 231
                           consts.AMP_ACTION_STOP,
@@ -239,30 +234,30 @@ class Listener(object):
239 234
                 message='Invalid Request',
240 235
                 details="Unknown action: {0}".format(action)), status=400)
241 236
 
242
-        self._check_listener_exists(listener_id)
237
+        self._check_lb_exists(lb_id)
243 238
 
244 239
         # Since this script should be created at LB create time
245 240
         # we can check for this path to see if VRRP is enabled
246 241
         # on this amphora and not write the file if VRRP is not in use
247 242
         if os.path.exists(util.keepalived_check_script_path()):
248
-            self.vrrp_check_script_update(listener_id, action)
243
+            self.vrrp_check_script_update(lb_id, action)
249 244
 
250 245
         # HAProxy does not start the process when given a reload
251 246
         # so start it if haproxy is not already running
252 247
         if action == consts.AMP_ACTION_RELOAD:
253
-            if consts.OFFLINE == self._check_haproxy_status(listener_id):
248
+            if consts.OFFLINE == self._check_haproxy_status(lb_id):
254 249
                 action = consts.AMP_ACTION_START
255 250
 
256
-        cmd = ("/usr/sbin/service haproxy-{listener_id} {action}".format(
257
-            listener_id=listener_id, action=action))
251
+        cmd = ("/usr/sbin/service haproxy-{lb_id} {action}".format(
252
+            lb_id=lb_id, action=action))
258 253
 
259 254
         try:
260 255
             subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
261 256
         except subprocess.CalledProcessError as e:
262 257
             if b'Job is already running' not in e.output:
263 258
                 LOG.debug(
264
-                    "Failed to %(action)s haproxy-%(list)s service: %(err)s "
265
-                    "%(out)s", {'action': action, 'list': listener_id,
259
+                    "Failed to %(action)s haproxy-%(lb_id)s service: %(err)s "
260
+                    "%(out)s", {'action': action, 'lb_id': lb_id,
266 261
                                 'err': e, 'out': e.output})
267 262
                 return webob.Response(json=dict(
268 263
                     message="Error {0}ing haproxy".format(action),
@@ -271,40 +266,40 @@ class Listener(object):
271 266
                       consts.AMP_ACTION_RELOAD]:
272 267
             return webob.Response(json=dict(
273 268
                 message='OK',
274
-                details='Listener {listener_id} {action}ed'.format(
275
-                    listener_id=listener_id, action=action)), status=202)
269
+                details='Listener {lb_id} {action}ed'.format(
270
+                    lb_id=lb_id, action=action)), status=202)
276 271
 
277 272
         details = (
278 273
             'Configuration file is valid\n'
279
-            'haproxy daemon for {0} started'.format(listener_id)
274
+            'haproxy daemon for {0} started'.format(lb_id)
280 275
         )
281 276
 
282 277
         return webob.Response(json=dict(message='OK', details=details),
283 278
                               status=202)
284 279
 
285
-    def delete_listener(self, listener_id):
280
+    def delete_lb(self, lb_id):
286 281
         try:
287
-            self._check_listener_exists(listener_id)
282
+            self._check_lb_exists(lb_id)
288 283
         except exceptions.HTTPException:
289 284
             return webob.Response(json={'message': 'OK'})
290 285
 
291 286
         # check if that haproxy is still running and if stop it
292
-        if os.path.exists(util.pid_path(listener_id)) and os.path.exists(
293
-                os.path.join('/proc', util.get_haproxy_pid(listener_id))):
294
-            cmd = "/usr/sbin/service haproxy-{0} stop".format(listener_id)
287
+        if os.path.exists(util.pid_path(lb_id)) and os.path.exists(
288
+                os.path.join('/proc', util.get_haproxy_pid(lb_id))):
289
+            cmd = "/usr/sbin/service haproxy-{0} stop".format(lb_id)
295 290
             try:
296 291
                 subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
297 292
             except subprocess.CalledProcessError as e:
298 293
                 LOG.error("Failed to stop haproxy-%s service: %s %s",
299
-                          listener_id, e, e.output)
294
+                          lb_id, e, e.output)
300 295
                 return webob.Response(json=dict(
301 296
                     message="Error stopping haproxy",
302 297
                     details=e.output), status=500)
303 298
 
304 299
         # parse config and delete stats socket
305 300
         try:
306
-            cfg = self._parse_haproxy_file(listener_id)
307
-            os.remove(cfg['stats_socket'])
301
+            stats_socket = util.parse_haproxy_file(lb_id)[0]
302
+            os.remove(stats_socket)
308 303
         except Exception:
309 304
             pass
310 305
 
@@ -313,22 +308,22 @@ class Listener(object):
313 308
         # on this amphora and not write the file if VRRP is not in use
314 309
         if os.path.exists(util.keepalived_check_script_path()):
315 310
             self.vrrp_check_script_update(
316
-                listener_id, action=consts.AMP_ACTION_STOP)
311
+                lb_id, action=consts.AMP_ACTION_STOP)
317 312
 
318 313
         # delete the ssl files
319 314
         try:
320
-            shutil.rmtree(self._cert_dir(listener_id))
315
+            shutil.rmtree(self._cert_dir(lb_id))
321 316
         except Exception:
322 317
             pass
323 318
 
324 319
         # disable the service
325 320
         init_system = util.get_os_init_system()
326
-        init_path = util.init_path(listener_id, init_system)
321
+        init_path = util.init_path(lb_id, init_system)
327 322
 
328 323
         if init_system == consts.INIT_SYSTEMD:
329 324
             util.run_systemctl_command(
330
-                consts.DISABLE, "haproxy-{list}".format(
331
-                    list=listener_id))
325
+                consts.DISABLE, "haproxy-{lb_id}".format(
326
+                    lb_id=lb_id))
332 327
         elif init_system == consts.INIT_SYSVINIT:
333 328
             init_disable_cmd = "insserv -r {file}".format(file=init_path)
334 329
         elif init_system != consts.INIT_UPSTART:
@@ -339,15 +334,15 @@ class Listener(object):
339 334
                 subprocess.check_output(init_disable_cmd.split(),
340 335
                                         stderr=subprocess.STDOUT)
341 336
             except subprocess.CalledProcessError as e:
342
-                LOG.error("Failed to disable haproxy-%(list)s service: "
343
-                          "%(err)s %(out)s", {'list': listener_id, 'err': e,
337
+                LOG.error("Failed to disable haproxy-%(lb_id)s service: "
338
+                          "%(err)s %(out)s", {'lb_id': lb_id, 'err': e,
344 339
                                               'out': e.output})
345 340
                 return webob.Response(json=dict(
346 341
                     message="Error disabling haproxy-{0} service".format(
347
-                            listener_id), details=e.output), status=500)
342
+                            lb_id), details=e.output), status=500)
348 343
 
349 344
         # delete the directory + init script for that listener
350
-        shutil.rmtree(util.haproxy_dir(listener_id))
345
+        shutil.rmtree(util.haproxy_dir(lb_id))
351 346
         if os.path.exists(init_path):
352 347
             os.remove(init_path)
353 348
 
@@ -364,68 +359,29 @@ class Listener(object):
364 359
         """
365 360
         listeners = list()
366 361
 
367
-        for listener in util.get_listeners():
368
-            status = self._check_listener_status(listener)
369
-            listener_type = ''
370
-
371
-            if status == consts.ACTIVE:
372
-                listener_type = self._parse_haproxy_file(listener)['mode']
362
+        for lb in util.get_loadbalancers():
363
+            stats_socket, listeners_on_lb = util.parse_haproxy_file(lb)
373 364
 
374
-            listeners.append({
375
-                'status': status,
376
-                'uuid': listener,
377
-                'type': listener_type,
378
-            })
365
+            for listener_id, listener in listeners_on_lb.items():
366
+                listeners.append({
367
+                    'status': consts.ACTIVE,
368
+                    'uuid': listener_id,
369
+                    'type': listener['mode'],
370
+                })
379 371
 
380 372
         if other_listeners:
381 373
             listeners = listeners + other_listeners
382 374
         return webob.Response(json=listeners, content_type='application/json')
383 375
 
384
-    def get_listener_status(self, listener_id):
385
-        """Gets the status of a listener
386
-
387
-        This method will consult the stats socket
388
-        so calling this method will interfere with
389
-        the health daemon with the risk of the amphora
390
-        shut down
391
-
392
-        Currently type==SSL is not detected
393
-        :param listener_id: The id of the listener
394
-        """
395
-        self._check_listener_exists(listener_id)
396
-
397
-        status = self._check_listener_status(listener_id)
398
-
399
-        if status != consts.ACTIVE:
400
-            stats = dict(
401
-                status=status,
402
-                uuid=listener_id,
403
-                type=''
404
-            )
405
-            return webob.Response(json=stats)
406
-
407
-        cfg = self._parse_haproxy_file(listener_id)
408
-        stats = dict(
409
-            status=status,
410
-            uuid=listener_id,
411
-            type=cfg['mode']
412
-        )
413
-
414
-        # read stats socket
415
-        q = query.HAProxyQuery(cfg['stats_socket'])
416
-        servers = q.get_pool_status()
417
-        stats['pools'] = list(servers.values())
418
-        return webob.Response(json=stats)
419
-
420
-    def upload_certificate(self, listener_id, filename):
376
+    def upload_certificate(self, lb_id, filename):
421 377
         self._check_ssl_filename_format(filename)
422 378
 
423 379
         # create directory if not already there
424
-        if not os.path.exists(self._cert_dir(listener_id)):
425
-            os.makedirs(self._cert_dir(listener_id))
380
+        if not os.path.exists(self._cert_dir(lb_id)):
381
+            os.makedirs(self._cert_dir(lb_id))
426 382
 
427 383
         stream = Wrapped(flask.request.stream)
428
-        file = self._cert_file_path(listener_id, filename)
384
+        file = self._cert_file_path(lb_id, filename)
429 385
         flags = os.O_WRONLY | os.O_CREAT
430 386
         # mode 00600
431 387
         mode = stat.S_IRUSR | stat.S_IWUSR
@@ -439,10 +395,10 @@ class Listener(object):
439 395
         resp.headers['ETag'] = stream.get_md5()
440 396
         return resp
441 397
 
442
-    def get_certificate_md5(self, listener_id, filename):
398
+    def get_certificate_md5(self, lb_id, filename):
443 399
         self._check_ssl_filename_format(filename)
444 400
 
445
-        cert_path = self._cert_file_path(listener_id, filename)
401
+        cert_path = self._cert_file_path(lb_id, filename)
446 402
         path_exists = os.path.exists(cert_path)
447 403
         if not path_exists:
448 404
             return webob.Response(json=dict(
@@ -457,60 +413,34 @@ class Listener(object):
457 413
             resp.headers['ETag'] = md5
458 414
             return resp
459 415
 
460
-    def delete_certificate(self, listener_id, filename):
416
+    def delete_certificate(self, lb_id, filename):
461 417
         self._check_ssl_filename_format(filename)
462
-        if os.path.exists(self._cert_file_path(listener_id, filename)):
463
-            os.remove(self._cert_file_path(listener_id, filename))
418
+        if os.path.exists(self._cert_file_path(lb_id, filename)):
419
+            os.remove(self._cert_file_path(lb_id, filename))
464 420
         return webob.Response(json=dict(message='OK'))
465 421
 
466
-    def _check_listener_status(self, listener_id):
467
-        if os.path.exists(util.pid_path(listener_id)):
422
+    def _get_listeners_on_lb(self, lb_id):
423
+        if os.path.exists(util.pid_path(lb_id)):
468 424
             if os.path.exists(
469
-                    os.path.join('/proc', util.get_haproxy_pid(listener_id))):
425
+                    os.path.join('/proc', util.get_haproxy_pid(lb_id))):
470 426
                 # Check if the listener is disabled
471
-                with open(util.config_path(listener_id), 'r') as file:
427
+                with open(util.config_path(lb_id), 'r') as file:
472 428
                     cfg = file.read()
473
-                    m = re.search('frontend {}'.format(listener_id), cfg)
474
-                    if m:
475
-                        return consts.ACTIVE
476
-                    return consts.OFFLINE
429
+                    m = re.findall('^frontend (.*)$', cfg, re.MULTILINE)
430
+                    return m or []
477 431
             else:  # pid file but no process...
478
-                return consts.ERROR
432
+                return []
479 433
         else:
480
-            return consts.OFFLINE
481
-
482
-    def _parse_haproxy_file(self, listener_id):
483
-        with open(util.config_path(listener_id), 'r') as file:
484
-            cfg = file.read()
434
+            return []
485 435
 
486
-            m = re.search('mode\s+(http|tcp)', cfg)
487
-            if not m:
488
-                raise ParsingError()
489
-            mode = m.group(1).upper()
490
-
491
-            m = re.search('stats socket\s+(\S+)', cfg)
492
-            if not m:
493
-                raise ParsingError()
494
-            stats_socket = m.group(1)
495
-
496
-            m = re.search('ssl crt\s+(\S+)', cfg)
497
-            ssl_crt = None
498
-            if m:
499
-                ssl_crt = m.group(1)
500
-                mode = 'TERMINATED_HTTPS'
501
-
502
-            return dict(mode=mode,
503
-                        stats_socket=stats_socket,
504
-                        ssl_crt=ssl_crt)
505
-
506
-    def _check_listener_exists(self, listener_id):
507
-        # check if we know about that listener
508
-        if not os.path.exists(util.config_path(listener_id)):
436
+    def _check_lb_exists(self, lb_id):
437
+        # check if we know about that lb
438
+        if lb_id not in util.get_loadbalancers():
509 439
             raise exceptions.HTTPException(
510 440
                 response=webob.Response(json=dict(
511
-                    message='Listener Not Found',
512
-                    details="No listener with UUID: {0}".format(
513
-                        listener_id)), status=404))
441
+                    message='Loadbalancer Not Found',
442
+                    details="No loadbalancer with UUID: {0}".format(
443
+                        lb_id)), status=404))
514 444
 
515 445
     def _check_ssl_filename_format(self, filename):
516 446
         # check if the format is (xxx.)*xxx.pem
@@ -519,20 +449,19 @@ class Listener(object):
519 449
                 response=webob.Response(json=dict(
520 450
                     message='Filename has wrong format'), status=400))
521 451
 
522
-    def _cert_dir(self, listener_id):
523
-        return os.path.join(util.CONF.haproxy_amphora.base_cert_dir,
524
-                            listener_id)
452
+    def _cert_dir(self, lb_id):
453
+        return os.path.join(util.CONF.haproxy_amphora.base_cert_dir, lb_id)
525 454
 
526
-    def _cert_file_path(self, listener_id, filename):
527
-        return os.path.join(self._cert_dir(listener_id), filename)
455
+    def _cert_file_path(self, lb_id, filename):
456
+        return os.path.join(self._cert_dir(lb_id), filename)
528 457
 
529
-    def vrrp_check_script_update(self, listener_id, action):
530
-        listener_ids = util.get_listeners()
458
+    def vrrp_check_script_update(self, lb_id, action):
459
+        lb_ids = util.get_loadbalancers()
531 460
         if action == consts.AMP_ACTION_STOP:
532
-            listener_ids.remove(listener_id)
461
+            lb_ids.remove(lb_id)
533 462
         args = []
534
-        for lid in listener_ids:
535
-            args.append(util.haproxy_sock_path(lid))
463
+        for lbid in lb_ids:
464
+            args.append(util.haproxy_sock_path(lbid))
536 465
 
537 466
         if not os.path.exists(util.keepalived_dir()):
538 467
             os.makedirs(util.keepalived_dir())
@@ -542,9 +471,9 @@ class Listener(object):
542 471
         with open(util.haproxy_check_script_path(), 'w') as text_file:
543 472
             text_file.write(cmd)
544 473
 
545
-    def _check_haproxy_status(self, listener_id):
546
-        if os.path.exists(util.pid_path(listener_id)):
474
+    def _check_haproxy_status(self, lb_id):
475
+        if os.path.exists(util.pid_path(lb_id)):
547 476
             if os.path.exists(
548
-                    os.path.join('/proc', util.get_haproxy_pid(listener_id))):
477
+                    os.path.join('/proc', util.get_haproxy_pid(lb_id))):
549 478
                 return consts.ACTIVE
550 479
         return consts.OFFLINE

+ 35
- 39
octavia/amphorae/backends/agent/api_server/server.py View File

@@ -26,7 +26,7 @@ from octavia.amphorae.backends.agent import api_server
26 26
 from octavia.amphorae.backends.agent.api_server import amphora_info
27 27
 from octavia.amphorae.backends.agent.api_server import certificate_update
28 28
 from octavia.amphorae.backends.agent.api_server import keepalived
29
-from octavia.amphorae.backends.agent.api_server import listener
29
+from octavia.amphorae.backends.agent.api_server import loadbalancer
30 30
 from octavia.amphorae.backends.agent.api_server import osutils
31 31
 from octavia.amphorae.backends.agent.api_server import plug
32 32
 from octavia.amphorae.backends.agent.api_server import udp_listener_base
@@ -56,7 +56,7 @@ class Server(object):
56 56
         self.app = flask.Flask(__name__)
57 57
         self._osutils = osutils.BaseOS.get_os_util()
58 58
         self._keepalived = keepalived.Keepalived()
59
-        self._listener = listener.Listener()
59
+        self._loadbalancer = loadbalancer.Loadbalancer()
60 60
         self._udp_listener = (udp_listener_base.UdpListenerApiServerBase.
61 61
                               get_server_driver())
62 62
         self._plug = plug.Plug(self._osutils)
@@ -64,8 +64,10 @@ class Server(object):
64 64
 
65 65
         register_app_error_handler(self.app)
66 66
 
67
+        self.app.add_url_rule(rule='/', view_func=self.version_discovery,
68
+                              methods=['GET'])
67 69
         self.app.add_url_rule(rule=PATH_PREFIX +
68
-                              '/listeners/<amphora_id>/<listener_id>/haproxy',
70
+                              '/loadbalancer/<amphora_id>/<lb_id>/haproxy',
69 71
                               view_func=self.upload_haproxy_config,
70 72
                               methods=['PUT'])
71 73
         self.app.add_url_rule(rule=PATH_PREFIX +
@@ -74,7 +76,7 @@ class Server(object):
74 76
                               view_func=self.upload_udp_listener_config,
75 77
                               methods=['PUT'])
76 78
         self.app.add_url_rule(rule=PATH_PREFIX +
77
-                              '/listeners/<listener_id>/haproxy',
79
+                              '/loadbalancer/<lb_id>/haproxy',
78 80
                               view_func=self.get_haproxy_config,
79 81
                               methods=['GET'])
80 82
         self.app.add_url_rule(rule=PATH_PREFIX +
@@ -82,11 +84,11 @@ class Server(object):
82 84
                               view_func=self.get_udp_listener_config,
83 85
                               methods=['GET'])
84 86
         self.app.add_url_rule(rule=PATH_PREFIX +
85
-                              '/listeners/<listener_id>/<action>',
86
-                              view_func=self.start_stop_listener,
87
+                              '/loadbalancer/<object_id>/<action>',
88
+                              view_func=self.start_stop_lb_object,
87 89
                               methods=['PUT'])
88
-        self.app.add_url_rule(rule=PATH_PREFIX + '/listeners/<listener_id>',
89
-                              view_func=self.delete_listener,
90
+        self.app.add_url_rule(rule=PATH_PREFIX + '/listeners/<object_id>',
91
+                              view_func=self.delete_lb_object,
90 92
                               methods=['DELETE'])
91 93
         self.app.add_url_rule(rule=PATH_PREFIX + '/config',
92 94
                               view_func=self.upload_config,
@@ -100,18 +102,15 @@ class Server(object):
100 102
         self.app.add_url_rule(rule=PATH_PREFIX + '/listeners',
101 103
                               view_func=self.get_all_listeners_status,
102 104
                               methods=['GET'])
103
-        self.app.add_url_rule(rule=PATH_PREFIX + '/listeners/<listener_id>',
104
-                              view_func=self.get_listener_status,
105
-                              methods=['GET'])
106
-        self.app.add_url_rule(rule=PATH_PREFIX + '/listeners/<listener_id>'
105
+        self.app.add_url_rule(rule=PATH_PREFIX + '/loadbalancer/<lb_id>'
107 106
                               '/certificates/<filename>',
108 107
                               view_func=self.upload_certificate,
109 108
                               methods=['PUT'])
110
-        self.app.add_url_rule(rule=PATH_PREFIX + '/listeners/<listener_id>'
109
+        self.app.add_url_rule(rule=PATH_PREFIX + '/loadbalancer/<lb_id>'
111 110
                               '/certificates/<filename>',
112 111
                               view_func=self.get_certificate_md5,
113 112
                               methods=['GET'])
114
-        self.app.add_url_rule(rule=PATH_PREFIX + '/listeners/<listener_id>'
113
+        self.app.add_url_rule(rule=PATH_PREFIX + '/loadbalancer/<lb_id>'
115 114
                               '/certificates/<filename>',
116 115
                               view_func=self.delete_certificate,
117 116
                               methods=['DELETE'])
@@ -133,30 +132,30 @@ class Server(object):
133 132
                               view_func=self.get_interface,
134 133
                               methods=['GET'])
135 134
 
136
-    def upload_haproxy_config(self, amphora_id, listener_id):
137
-        return self._listener.upload_haproxy_config(amphora_id, listener_id)
135
+    def upload_haproxy_config(self, amphora_id, lb_id):
136
+        return self._loadbalancer.upload_haproxy_config(amphora_id, lb_id)
138 137
 
139 138
     def upload_udp_listener_config(self, amphora_id, listener_id):
140 139
         return self._udp_listener.upload_udp_listener_config(listener_id)
141 140
 
142
-    def get_haproxy_config(self, listener_id):
143
-        return self._listener.get_haproxy_config(listener_id)
141
+    def get_haproxy_config(self, lb_id):
142
+        return self._loadbalancer.get_haproxy_config(lb_id)
144 143
 
145 144
     def get_udp_listener_config(self, listener_id):
146 145
         return self._udp_listener.get_udp_listener_config(listener_id)
147 146
 
148
-    def start_stop_listener(self, listener_id, action):
149
-        protocol = util.get_listener_protocol(listener_id)
147
+    def start_stop_lb_object(self, object_id, action):
148
+        protocol = util.get_protocol_for_lb_object(object_id)
150 149
         if protocol == 'UDP':
151 150
             return self._udp_listener.manage_udp_listener(
152
-                listener_id, action)
153
-        return self._listener.start_stop_listener(listener_id, action)
151
+                listener_id=object_id, action=action)
152
+        return self._loadbalancer.start_stop_lb(lb_id=object_id, action=action)
154 153
 
155
-    def delete_listener(self, listener_id):
156
-        protocol = util.get_listener_protocol(listener_id)
154
+    def delete_lb_object(self, object_id):
155
+        protocol = util.get_protocol_for_lb_object(object_id)
157 156
         if protocol == 'UDP':
158
-            return self._udp_listener.delete_udp_listener(listener_id)
159
-        return self._listener.delete_listener(listener_id)
157
+            return self._udp_listener.delete_udp_listener(object_id)
158
+        return self._loadbalancer.delete_lb(object_id)
160 159
 
161 160
     def get_details(self):
162 161
         return self._amphora_info.compile_amphora_details(
@@ -168,23 +167,17 @@ class Server(object):
168 167
 
169 168
     def get_all_listeners_status(self):
170 169
         udp_listeners = self._udp_listener.get_all_udp_listeners_status()
171
-        return self._listener.get_all_listeners_status(
170
+        return self._loadbalancer.get_all_listeners_status(
172 171
             other_listeners=udp_listeners)
173 172
 
174
-    def get_listener_status(self, listener_id):
175
-        protocol = util.get_listener_protocol(listener_id)
176
-        if protocol == 'UDP':
177
-            return self._udp_listener.get_udp_listener_status(listener_id)
178
-        return self._listener.get_listener_status(listener_id)
173
+    def upload_certificate(self, lb_id, filename):
174
+        return self._loadbalancer.upload_certificate(lb_id, filename)
179 175
 
180
-    def upload_certificate(self, listener_id, filename):
181
-        return self._listener.upload_certificate(listener_id, filename)
176
+    def get_certificate_md5(self, lb_id, filename):
177
+        return self._loadbalancer.get_certificate_md5(lb_id, filename)
182 178
 
183
-    def get_certificate_md5(self, listener_id, filename):
184
-        return self._listener.get_certificate_md5(listener_id, filename)
185
-
186
-    def delete_certificate(self, listener_id, filename):
187
-        return self._listener.delete_certificate(listener_id, filename)
179
+    def delete_certificate(self, lb_id, filename):
180
+        return self._loadbalancer.delete_certificate(lb_id, filename)
188 181
 
189 182
     def plug_vip(self, vip):
190 183
         # Catch any issues with the subnet info json
@@ -251,3 +244,6 @@ class Server(object):
251 244
                 details=str(e)), status=500)
252 245
 
253 246
         return webob.Response(json={'message': 'OK'}, status=202)
247
+
248
+    def version_discovery(self):
249
+        return webob.Response(json={'api_version': api_server.VERSION})

+ 0
- 12
octavia/amphorae/backends/agent/api_server/udp_listener_base.py View File

@@ -98,18 +98,6 @@ class UdpListenerApiServerBase(object):
98 98
         """
99 99
         pass
100 100
 
101
-    @abc.abstractmethod
102
-    def get_udp_listener_status(self, listener_id):
103
-        """Gets the status of a UDP listener
104
-
105
-        :param listener_id: The id of the listener
106
-
107
-        :returns: HTTP response with status code.
108
-        :raises Exception: If the listener is failed to find.
109
-
110
-        """
111
-        pass
112
-
113 101
     @abc.abstractmethod
114 102
     def delete_udp_listener(self, listener_id):
115 103
         """Delete a UDP Listener from a amphora

+ 89
- 24
octavia/amphorae/backends/agent/api_server/util.py View File

@@ -28,23 +28,32 @@ from octavia.common import constants as consts
28 28
 CONF = cfg.CONF
29 29
 LOG = logging.getLogger(__name__)
30 30
 
31
+FRONTEND_BACKEND_PATTERN = re.compile(r'\n(frontend|backend)\s+(\S+)\n')
32
+LISTENER_MODE_PATTERN = re.compile(r'^\s+mode\s+(.*)$', re.MULTILINE)
33
+TLS_CERT_PATTERN = re.compile(r'^\s+bind\s+\S+\s+ssl crt\s+(.*)$',
34
+                              re.MULTILINE)
35
+STATS_SOCKET_PATTERN = re.compile(r'stats socket\s+(\S+)')
36
+
37
+
38
+class ParsingError(Exception):
39
+    pass
40
+
31 41
 
32 42
 class UnknownInitError(Exception):
33 43
     pass
34 44
 
35 45
 
36
-def init_path(listener_id, init_system):
46
+def init_path(lb_id, init_system):
37 47
     if init_system == consts.INIT_SYSTEMD:
38 48
         return os.path.join(consts.SYSTEMD_DIR,
39
-                            'haproxy-{0}.service'.format(listener_id))
40
-    elif init_system == consts.INIT_UPSTART:
49
+                            'haproxy-{0}.service'.format(lb_id))
50
+    if init_system == consts.INIT_UPSTART:
41 51
         return os.path.join(consts.UPSTART_DIR,
42
-                            'haproxy-{0}.conf'.format(listener_id))
43
-    elif init_system == consts.INIT_SYSVINIT:
52
+                            'haproxy-{0}.conf'.format(lb_id))
53
+    if init_system == consts.INIT_SYSVINIT:
44 54
         return os.path.join(consts.SYSVINIT_DIR,
45
-                            'haproxy-{0}'.format(listener_id))
46
-    else:
47
-        raise UnknownInitError()
55
+                            'haproxy-{0}'.format(lb_id))
56
+    raise UnknownInitError()
48 57
 
49 58
 
50 59
 def keepalived_lvs_dir():
@@ -93,20 +102,20 @@ def keepalived_lvs_cfg_path(listener_id):
93 102
                         str(listener_id))
94 103
 
95 104
 
96
-def haproxy_dir(listener_id):
97
-    return os.path.join(CONF.haproxy_amphora.base_path, listener_id)
105
+def haproxy_dir(lb_id):
106
+    return os.path.join(CONF.haproxy_amphora.base_path, lb_id)
98 107
 
99 108
 
100
-def pid_path(listener_id):
101
-    return os.path.join(haproxy_dir(listener_id), listener_id + '.pid')
109
+def pid_path(lb_id):
110
+    return os.path.join(haproxy_dir(lb_id), lb_id + '.pid')
102 111
 
103 112
 
104
-def config_path(listener_id):
105
-    return os.path.join(haproxy_dir(listener_id), 'haproxy.cfg')
113
+def config_path(lb_id):
114
+    return os.path.join(haproxy_dir(lb_id), 'haproxy.cfg')
106 115
 
107 116
 
108
-def get_haproxy_pid(listener_id):
109
-    with open(pid_path(listener_id), 'r') as f:
117
+def get_haproxy_pid(lb_id):
118
+    with open(pid_path(lb_id), 'r') as f:
110 119
         return f.readline().rstrip()
111 120
 
112 121
 
@@ -116,8 +125,8 @@ def get_keepalivedlvs_pid(listener_id):
116 125
         return f.readline().rstrip()
117 126
 
118 127
 
119
-def haproxy_sock_path(listener_id):
120
-    return os.path.join(CONF.haproxy_amphora.base_path, listener_id + '.sock')
128
+def haproxy_sock_path(lb_id):
129
+    return os.path.join(CONF.haproxy_amphora.base_path, lb_id + '.sock')
121 130
 
122 131
 
123 132
 def haproxy_check_script_path():
@@ -171,16 +180,28 @@ def get_listeners():
171 180
     :returns: An array with the ids of all listeners, e.g. ['123', '456', ...]
172 181
               or [] if no listeners exist
173 182
     """
183
+    listeners = []
184
+    for lb_id in get_loadbalancers():
185
+        listeners_on_lb = parse_haproxy_file(lb_id)[1]
186
+        listeners.extend(list(listeners_on_lb.keys()))
187
+    return listeners
188
+
189
+
190
+def get_loadbalancers():
191
+    """Get Load balancers
174 192
 
193
+    :returns: An array with the ids of all load balancers,
194
+              e.g. ['123', '456', ...] or [] if no loadbalancers exist
195
+    """
175 196
     if os.path.exists(CONF.haproxy_amphora.base_path):
176 197
         return [f for f in os.listdir(CONF.haproxy_amphora.base_path)
177 198
                 if os.path.exists(config_path(f))]
178 199
     return []
179 200
 
180 201
 
181
-def is_listener_running(listener_id):
182
-    return os.path.exists(pid_path(listener_id)) and os.path.exists(
183
-        os.path.join('/proc', get_haproxy_pid(listener_id)))
202
+def is_lb_running(lb_id):
203
+    return os.path.exists(pid_path(lb_id)) and os.path.exists(
204
+        os.path.join('/proc', get_haproxy_pid(lb_id)))
184 205
 
185 206
 
186 207
 def get_udp_listeners():
@@ -254,7 +275,7 @@ def run_systemctl_command(command, service):
254 275
                                       'err': e, 'out': e.output})
255 276
 
256 277
 
257
-def get_listener_protocol(listener_id):
278
+def get_protocol_for_lb_object(object_id):
258 279
     """Returns the L4 protocol for a listener.
259 280
 
260 281
     If the listener is a TCP based listener (haproxy) return TCP.
@@ -264,8 +285,52 @@ def get_listener_protocol(listener_id):
264 285
     :param listener_id: The ID of the listener to identify.
265 286
     :returns: TCP, UDP, or None
266 287
     """
267
-    if os.path.exists(config_path(listener_id)):
288
+    if os.path.exists(config_path(object_id)):
268 289
         return consts.PROTOCOL_TCP
269
-    elif os.path.exists(keepalived_lvs_cfg_path(listener_id)):
290
+    if os.path.exists(keepalived_lvs_cfg_path(object_id)):
270 291
         return consts.PROTOCOL_UDP
271 292
     return None
293
+
294
+
295
+def parse_haproxy_file(lb_id):
296
+    with open(config_path(lb_id), 'r') as file:
297
+        cfg = file.read()
298
+
299
+        listeners = {}
300
+
301
+        m = FRONTEND_BACKEND_PATTERN.split(cfg)
302
+        last_token = None
303
+        last_id = None
304
+        for section in m:
305
+            if last_token is None:
306
+                # We aren't in a section yet, see if this line starts one
307
+                if section == 'frontend':
308
+                    last_token = section
309
+            elif last_token == 'frontend':
310
+                # We're in a frontend section, save the id for later
311
+                last_token = last_token + "_id"
312
+                last_id = section
313
+            elif last_token == 'frontend_id':
314
+                # We're in a frontend section and already have the id
315
+                # Look for the mode
316
+                mode_matcher = LISTENER_MODE_PATTERN.search(section)
317
+                if not mode_matcher:
318
+                    raise ParsingError()
319
+                listeners[last_id] = {
320
+                    'mode': mode_matcher.group(1).upper(),
321
+                }
322
+                # Now see if this is a TLS frontend
323
+                tls_matcher = TLS_CERT_PATTERN.search(section)
324
+                if tls_matcher:
325
+                    # TODO(rm_work): Can't we have terminated tcp?
326
+                    listeners[last_id]['mode'] = 'TERMINATED_HTTPS'
327
+                    listeners[last_id]['ssl_crt'] = tls_matcher.group(1)
328
+                # Clear out the token and id and start over
329
+                last_token = last_id = None
330
+
331
+        m = STATS_SOCKET_PATTERN.search(cfg)
332
+        if not m:
333
+            raise ParsingError()
334
+        stats_socket = m.group(1)
335
+
336
+        return stats_socket, listeners

+ 47
- 30
octavia/amphorae/backends/health_daemon/health_daemon.py View File

@@ -43,18 +43,19 @@ SEQ = 0
43 43
 # incompatible changes.
44 44
 #
45 45
 # ver 1 - Adds UDP listener status when no pool or members are present
46
+# ver 2 - Switch to all listeners in a single combined haproxy config
46 47
 #
47
-MSG_VER = 1
48
+MSG_VER = 2
48 49
 
49 50
 
50 51
 def list_sock_stat_files(hadir=None):
51 52
     stat_sock_files = {}
52 53
     if hadir is None:
53 54
         hadir = CONF.haproxy_amphora.base_path
54
-    listener_ids = util.get_listeners()
55
-    for listener_id in listener_ids:
56
-        sock_file = listener_id + ".sock"
57
-        stat_sock_files[listener_id] = os.path.join(hadir, sock_file)
55
+    lb_ids = util.get_loadbalancers()
56
+    for lb_id in lb_ids:
57
+        sock_file = lb_id + ".sock"
58
+        stat_sock_files[lb_id] = os.path.join(hadir, sock_file)
58 59
     return stat_sock_files
59 60
 
60 61
 
@@ -121,39 +122,55 @@ def get_stats(stat_sock_file):
121 122
 
122 123
 
123 124
 def build_stats_message():
125
+    # Example version 2 message without UDP:
126
+    # {
127
+    #   "id": "<amphora_id>",
128
+    #   "seq": 67,
129
+    #   "listeners": {
130
+    #     "<listener_id>": {
131
+    #       "status": "OPEN",
132
+    #       "stats": {
133
+    #         "tx": 0,
134
+    #         "rx": 0,
135
+    #         "conns": 0,
136
+    #         "totconns": 0,
137
+    #         "ereq": 0
138
+    #       }
139
+    #     }
140
+    #  },
141
+    #  "pools": {
142
+    #    "<pool_id>:<listener_id>": {
143
+    #      "status": "UP",
144
+    #      "members": {
145
+    #        "<member_id>": "no check"
146
+    #      }
147
+    #    }
148
+    #  },
149
+    #  "ver": 2
150
+    # }
124 151
     global SEQ
125 152
     msg = {'id': CONF.amphora_agent.amphora_id,
126
-           'seq': SEQ, "listeners": {},
153
+           'seq': SEQ, 'listeners': {}, 'pools': {},
127 154
            'ver': MSG_VER}
128 155
     SEQ += 1
129 156
     stat_sock_files = list_sock_stat_files()
130
-    for listener_id, stat_sock_file in stat_sock_files.items():
131
-        listener_dict = {'pools': {},
132
-                         'status': 'DOWN',
133
-                         'stats': {
134
-                             'tx': 0,
135
-                             'rx': 0,
136
-                             'conns': 0,
137
-                             'totconns': 0,
138
-                             'ereq': 0}}
139
-        msg['listeners'][listener_id] = listener_dict
140
-        if util.is_listener_running(listener_id):
157
+    # TODO(rm_work) There should only be one of these in the new config system
158
+    for lb_id, stat_sock_file in stat_sock_files.items():
159
+        if util.is_lb_running(lb_id):
141 160
             (stats, pool_status) = get_stats(stat_sock_file)
142
-            listener_dict = msg['listeners'][listener_id]
143 161
             for row in stats:
144 162
                 if row['svname'] == 'FRONTEND':
145
-                    listener_dict['stats']['tx'] = int(row['bout'])
146
-                    listener_dict['stats']['rx'] = int(row['bin'])
147
-                    listener_dict['stats']['conns'] = int(row['scur'])
148
-                    listener_dict['stats']['totconns'] = int(row['stot'])
149
-                    listener_dict['stats']['ereq'] = int(row['ereq'])
150
-                    listener_dict['status'] = row['status']
151
-            for oid, pool in pool_status.items():
152
-                if oid != listener_id:
153
-                    pool_id = oid
154
-                    pools = listener_dict['pools']
155
-                    pools[pool_id] = {"status": pool['status'],
156
-                                      "members": pool['members']}
163
+                    listener_id = row['pxname']
164
+                    msg['listeners'][listener_id] = {
165
+                        'status': row['status'],
166
+                        'stats': {'tx': int(row['bout']),
167
+                                  'rx': int(row['bin']),
168
+                                  'conns': int(row['scur']),
169
+                                  'totconns': int(row['stot']),
170
+                                  'ereq': int(row['ereq'])}}
171
+            for pool_id, pool in pool_status.items():
172
+                msg['pools'][pool_id] = {"status": pool['status'],
173
+                                         "members": pool['members']}
157 174
 
158 175
     # UDP listener part
159 176
     udp_listener_ids = util.get_udp_listeners()

+ 3
- 1
octavia/amphorae/backends/utils/haproxy_query.py View File

@@ -124,7 +124,9 @@ class HAProxyQuery(object):
124 124
                 final_results[line['pxname']] = dict(members={})
125 125
 
126 126
             if line['svname'] == 'BACKEND':
127
-                final_results[line['pxname']]['uuid'] = line['pxname']
127
+                pool_id, listener_id = line['pxname'].split(':')
128
+                final_results[line['pxname']]['pool_uuid'] = pool_id
129
+                final_results[line['pxname']]['listener_uuid'] = listener_id
128 130
                 final_results[line['pxname']]['status'] = line['status']
129 131
             else:
130 132
                 final_results[line['pxname']]['members'][line['svname']] = (

+ 27
- 46
octavia/amphorae/drivers/driver_base.py View File

@@ -22,17 +22,19 @@ import six
22 22
 class AmphoraLoadBalancerDriver(object):
23 23
 
24 24
     @abc.abstractmethod
25
-    def update_amphora_listeners(self, listeners, amphora_id, timeout_dict):
25
+    def update_amphora_listeners(self, loadbalancer, amphora,
26
+                                 timeout_dict):
26 27
         """Update the amphora with a new configuration.
27 28
 
28
-        :param listeners: List of listeners to update.
29
-        :type listener: list
30
-        :param amphora_id: The ID of the amphora to update
31
-        :type amphora_id: string
29
+        :param loadbalancer: List of listeners to update.
30
+        :type loadbalancer: list(octavia.db.models.Listener)
31
+        :param amphora: The index of the specific amphora to update
32
+        :type amphora: octavia.db.models.Amphora
32 33
         :param timeout_dict: Dictionary of timeout values for calls to the
33 34
                              amphora. May contain: req_conn_timeout,
34 35
                              req_read_timeout, conn_max_retries,
35 36
                              conn_retry_interval
37
+        :type timeout_dict: dict
36 38
         :returns: None
37 39
 
38 40
         Builds a new configuration, pushes it to the amphora, and reloads
@@ -41,14 +43,12 @@ class AmphoraLoadBalancerDriver(object):
41 43
         pass
42 44
 
43 45
     @abc.abstractmethod
44
-    def update(self, listener, vip):
46
+    def update(self, loadbalancer):
45 47
         """Update the amphora with a new configuration.
46 48
 
47
-        :param listener: listener object,
48
-                         need to use its protocol_port property
49
-        :type listener: object
50
-        :param vip: vip object, need to use its ip_address property
51
-        :type vip: object
49
+        :param loadbalancer: loadbalancer object, need to use its
50
+                             vip.ip_address property
51
+        :type loadbalancer: octavia.db.models.LoadBalancer
52 52
         :returns: None
53 53
 
54 54
         At this moment, we just build the basic structure for testing, will
@@ -57,32 +57,13 @@ class AmphoraLoadBalancerDriver(object):
57 57
         pass
58 58
 
59 59
     @abc.abstractmethod
60
-    def stop(self, listener, vip):
61
-        """Stop the listener on the vip.
62
-
63
-        :param listener: listener object,
64
-                         need to use its protocol_port property
65
-        :type listener: object
66
-        :param vip: vip object, need to use its ip_address property
67
-        :type vip: object
68
-        :returns: return a value list (listener, vip, status flag--suspend)
69
-
70
-        At this moment, we just build the basic structure for testing, will
71
-        add more function along with the development.
72
-        """
73
-        pass
74
-
75
-    @abc.abstractmethod
76
-    def start(self, listener, vip, amphora):
77
-        """Start the listener on the vip.
60
+    def start(self, loadbalancer, amphora):
61
+        """Start the listeners on the amphora.
78 62
 
79
-        :param listener: listener object,
80
-                         need to use its protocol_port property
81
-        :type listener: object
82
-        :param vip: vip object, need to use its ip_address property
83
-        :type vip: object
63
+        :param loadbalancer: loadbalancer object to start listeners
64
+        :type loadbalancer: octavia.db.models.LoadBalancer
84 65
         :param amphora: Amphora to start. If None, start on all amphora
85
-        :type amphora: object
66
+        :type amphora: octavia.db.models.Amphora
86 67
         :returns: return a value list (listener, vip, status flag--enable)
87 68
 
88 69
         At this moment, we just build the basic structure for testing, will
@@ -91,14 +72,12 @@ class AmphoraLoadBalancerDriver(object):
91 72
         pass
92 73
 
93 74
     @abc.abstractmethod
94
-    def delete(self, listener, vip):
75
+    def delete(self, listener):
95 76
         """Delete the listener on the vip.
96 77
 
97 78
         :param listener: listener object,
98 79
                          need to use its protocol_port property
99
-        :type listener: object
100
-        :param vip: vip object, need to use its ip_address property
101
-        :type vip: object
80
+        :type listener: octavia.db.models.Listener
102 81
         :returns: return a value list (listener, vip, status flag--delete)
103 82
 
104 83
         At this moment, we just build the basic structure for testing, will
@@ -111,7 +90,7 @@ class AmphoraLoadBalancerDriver(object):
111 90
         """Returns information about the amphora.
112 91
 
113 92
         :param amphora: amphora object, need to use its id property
114
-        :type amphora: object
93
+        :type amphora: octavia.db.models.Amphora
115 94
         :returns: return a value list (amphora.id, status flag--'info')
116 95
 
117 96
         At this moment, we just build the basic structure for testing, will
@@ -128,7 +107,7 @@ class AmphoraLoadBalancerDriver(object):
128 107
         """Return ceilometer ready diagnostic data.
129 108
 
130 109
         :param amphora: amphora object, need to use its id property
131
-        :type amphora: object
110
+        :type amphora: octavia.db.models.Amphora
132 111
         :returns: return a value list (amphora.id, status flag--'ge
133 112
                   t_diagnostics')
134 113
 
@@ -145,7 +124,7 @@ class AmphoraLoadBalancerDriver(object):
145 124
         """Finalize the amphora before any listeners are configured.
146 125
 
147 126
         :param amphora: amphora object, need to use its id property
148
-        :type amphora: object
127
+        :type amphora: octavia.db.models.Amphora
149 128
         :returns: None
150 129
 
151 130
         At this moment, we just build the basic structure for testing, will
@@ -159,6 +138,8 @@ class AmphoraLoadBalancerDriver(object):
159 138
     def post_vip_plug(self, amphora, load_balancer, amphorae_network_config):
160 139
         """Called after network driver has allocated and plugged the VIP
161 140
 
141
+        :param amphora:
142
+        :type amphora: octavia.db.models.Amphora
162 143
         :param load_balancer: A load balancer that just had its vip allocated
163 144
                               and plugged in the network driver.
164 145
         :type load_balancer: octavia.common.data_models.LoadBalancer
@@ -177,7 +158,7 @@ class AmphoraLoadBalancerDriver(object):
177 158
         """Called after amphora added to network
178 159
 
179 160
         :param amphora: amphora object, needs id and network ip(s)
180
-        :type amphora: object
161
+        :type amphora: octavia.db.models.Amphora
181 162
         :param port: contains information of the plugged port
182 163
         :type port: octavia.network.data_models.Port
183 164
 
@@ -192,7 +173,7 @@ class AmphoraLoadBalancerDriver(object):
192 173
         """Start health checks.
193 174
 
194 175
         :param health_mixin: health mixin object
195
-        :type amphora: object
176
+        :type health_mixin: HealthMixin
196 177
 
197 178
         Starts listener process and calls HealthMixin to update
198 179
         databases information.
@@ -211,7 +192,7 @@ class AmphoraLoadBalancerDriver(object):
211 192
         """Upload cert info to the amphora.
212 193
 
213 194
         :param amphora: amphora object, needs id and network ip(s)
214
-        :type amphora: object
195
+        :type amphora: octavia.db.models.Amphora
215 196
         :param pem_file: a certificate file
216 197
         :type pem_file: file object
217 198
 
@@ -223,7 +204,7 @@ class AmphoraLoadBalancerDriver(object):
223 204
         """Upload and update the amphora agent configuration.
224 205
 
225 206
         :param amphora: amphora object, needs id and network ip(s)
226
-        :type amphora: object
207
+        :type amphora: octavia.db.models.Amphora
227 208
         :param agent_config: The new amphora agent configuration file.
228 209
         :type agent_config: string
229 210
         """

+ 426
- 148
octavia/amphorae/drivers/haproxy/rest_api_driver.py View File

@@ -31,7 +31,8 @@ from octavia.amphorae.drivers.haproxy import exceptions as exc
31 31
 from octavia.amphorae.drivers.keepalived import vrrp_rest_driver
32 32
 from octavia.common.config import cfg
33 33
 from octavia.common import constants as consts
34
-from octavia.common.jinja.haproxy import jinja_cfg
34
+import octavia.common.jinja.haproxy.combined_listeners.jinja_cfg as jinja_combo
35
+import octavia.common.jinja.haproxy.split_listeners.jinja_cfg as jinja_split
35 36
 from octavia.common.jinja.lvs import jinja_cfg as jinja_udp_cfg
36 37
 from octavia.common.tls_utils import cert_parser
37 38
 from octavia.common import utils
@@ -50,14 +51,23 @@ class HaproxyAmphoraLoadBalancerDriver(
50 51
 
51 52
     def __init__(self):
52 53
         super(HaproxyAmphoraLoadBalancerDriver, self).__init__()
53
-        self.client = AmphoraAPIClient()
54
+        self.clients = {
55
+            'base': AmphoraAPIClientBase(),
56
+            '0.5': AmphoraAPIClient0_5(),
57
+            '1.0': AmphoraAPIClient1_0(),
58
+        }
54 59
         self.cert_manager = stevedore_driver.DriverManager(
55 60
             namespace='octavia.cert_manager',
56 61
             name=CONF.certificates.cert_manager,
57 62
             invoke_on_load=True,
58 63
         ).driver
59 64
 
60
-        self.jinja = jinja_cfg.JinjaTemplater(
65
+        self.jinja_combo = jinja_combo.JinjaTemplater(
66
+            base_amp_path=CONF.haproxy_amphora.base_path,
67
+            base_crt_dir=CONF.haproxy_amphora.base_cert_dir,
68
+            haproxy_template=CONF.haproxy_amphora.haproxy_template,
69
+            connection_logging=CONF.haproxy_amphora.connection_logging)
70
+        self.jinja_split = jinja_split.JinjaTemplater(
61 71
             base_amp_path=CONF.haproxy_amphora.base_path,
62 72
             base_crt_dir=CONF.haproxy_amphora.base_cert_dir,
63 73
             haproxy_template=CONF.haproxy_amphora.haproxy_template,
@@ -71,21 +81,39 @@ class HaproxyAmphoraLoadBalancerDriver(
71 81
 
72 82
         :returns version_list: A list with the major and minor numbers
73 83
         """
84
+        self._populate_amphora_api_version(amphora)
85
+        amp_info = self.clients[amphora.api_version].get_info(amphora)
86
+        haproxy_version_string = amp_info['haproxy_version']
87
+
88
+        return haproxy_version_string.split('.')[:2]
74 89
 
75
-        version_string = self.client.get_info(amphora)['haproxy_version']
90
+    def _populate_amphora_api_version(self, amphora):
91
+        """Populate the amphora object with the api_version
76 92
 
77
-        return version_string.split('.')[:2]
93
+        This will query the amphora for version discovery and populate
94
+        the api_version string attribute on the amphora object.
78 95
 
79
-    def update_amphora_listeners(self, listeners, amphora_index,
80
-                                 amphorae, timeout_dict=None):
96
+        :returns: None
97
+        """
98
+        if not getattr(amphora, 'api_version', None):
99
+            try:
100
+                amphora.api_version = self.clients['base'].get_api_version(
101
+                    amphora)['api_version']
102
+            except exc.NotFound:
103
+                # Amphora is too old for version discovery, default to 0.5
104
+                amphora.api_version = '0.5'
105
+        LOG.debug('Amphora %s has API version %s',
106
+                  amphora.id, amphora.api_version)
107
+        return list(map(int, amphora.api_version.split('.')))
108
+
109
+    def update_amphora_listeners(self, loadbalancer, amphora,
110
+                                 timeout_dict=None):
81 111
         """Update the amphora with a new configuration.
82 112
 
83
-        :param listeners: List of listeners to update.
84
-        :type listener: list
85
-        :param amphora_index: The index of the amphora to update
86
-        :type amphora_index: integer
87
-        :param amphorae: List of amphorae
88
-        :type amphorae: list
113
+        :param loadbalancer: The load balancer to update
114
+        :type loadbalancer: object
115
+        :param amphora: The amphora to update
116
+        :type amphora: object
89 117
         :param timeout_dict: Dictionary of timeout values for calls to the
90 118
                              amphora. May contain: req_conn_timeout,
91 119
                              req_read_timeout, conn_max_retries,
@@ -95,46 +123,82 @@ class HaproxyAmphoraLoadBalancerDriver(
95 123
         Updates the configuration of the listeners on a single amphora.
96 124
         """
97 125
         # if the amphora does not yet have listeners, no need to update them.
98
-        if not listeners:
126
+        if not loadbalancer.listeners:
99 127
             LOG.debug('No listeners found to update.')
100 128
             return
101
-        amp = amphorae[amphora_index]
102
-        if amp is None or amp.status == consts.DELETED:
129
+        if amphora is None or amphora.status == consts.DELETED:
103 130
             return
104 131
 
105
-        haproxy_versions = self._get_haproxy_versions(amp)
132
+        # Check which HAProxy version is on the amp
133
+        haproxy_versions = self._get_haproxy_versions(amphora)
134
+        # Check which config style to use
135
+        api_version = self._populate_amphora_api_version(amphora)
136
+        if api_version[0] == 0 and api_version[1] <= 5:  # 0.5 or earlier
137
+            split_config = True
138
+            LOG.warning(
139
+                'Amphora %s for loadbalancer %s needs upgrade to single '
140
+                'process mode.', amphora.id, loadbalancer.id)
141
+        else:
142
+            split_config = False
143
+            LOG.debug('Amphora %s for loadbalancer %s is already in single '
144
+                      'process mode.', amphora.id, loadbalancer.id)
106 145
 
107
-        # TODO(johnsom) remove when we don't have a process per listener
108
-        for listener in listeners:
146
+        has_tcp = False
147
+        for listener in loadbalancer.listeners:
109 148
             LOG.debug("%s updating listener %s on amphora %s",
110
-                      self.__class__.__name__, listener.id, amp.id)
149
+                      self.__class__.__name__, listener.id, amphora.id)
111 150
             if listener.protocol == 'UDP':
112 151
                 # Generate Keepalived LVS configuration from listener object
113 152
                 config = self.udp_jinja.build_config(listener=listener)
114
-                self.client.upload_udp_config(amp, listener.id, config,
115
-                                              timeout_dict=timeout_dict)
116
-                self.client.reload_listener(amp, listener.id,
117
-                                            timeout_dict=timeout_dict)
153
+                self.clients[amphora.api_version].upload_udp_config(
154
+                    amphora, listener.id, config, timeout_dict=timeout_dict)
155
+                self.clients[amphora.api_version].reload_listener(
156
+                    amphora, listener.id, timeout_dict=timeout_dict)
118 157
             else:
119
-                certs = self._process_tls_certificates(listener)
158
+                has_tcp = True
159
+                if split_config:
160
+                    obj_id = listener.id
161
+                else:
162
+                    obj_id = loadbalancer.id
163
+
164
+                certs = self._process_tls_certificates(
165
+                    listener, amphora, obj_id)
120 166
                 client_ca_filename = self._process_secret(
121
-                    listener, listener.client_ca_tls_certificate_id)
167
+                    listener, listener.client_ca_tls_certificate_id,
168
+                    amphora, obj_id)
122 169
                 crl_filename = self._process_secret(
123
-                    listener, listener.client_crl_container_id)
124
-                pool_tls_certs = self._process_listener_pool_certs(listener)
125
-
126
-                # Generate HaProxy configuration from listener object
127
-                config = self.jinja.build_config(
128
-                    host_amphora=amp, listener=listener,
129
-                    tls_cert=certs['tls_cert'],
130
-                    haproxy_versions=haproxy_versions,
131
-                    client_ca_filename=client_ca_filename,
132
-                    client_crl=crl_filename,
133
-                    pool_tls_certs=pool_tls_certs)
134
-                self.client.upload_config(amp, listener.id, config,
135
-                                          timeout_dict=timeout_dict)
136
-                self.client.reload_listener(amp, listener.id,
137
-                                            timeout_dict=timeout_dict)
170
+                    listener, listener.client_crl_container_id,
171
+                    amphora, obj_id)
172
+                pool_tls_certs = self._process_listener_pool_certs(
173
+                    listener, amphora, obj_id)
174
+
175
+                if split_config:
176
+                    config = self.jinja_split.build_config(
177
+                        host_amphora=amphora, listener=listener,
178
+                        tls_cert=certs['tls_cert'],
179
+                        haproxy_versions=haproxy_versions,
180
+                        client_ca_filename=client_ca_filename,
181
+                        client_crl=crl_filename,
182
+                        pool_tls_certs=pool_tls_certs)
183
+                    self.clients[amphora.api_version].upload_config(
184
+                        amphora, listener.id, config,
185
+                        timeout_dict=timeout_dict)
186
+                    self.clients[amphora.api_version].reload_listener(
187
+                        amphora, listener.id, timeout_dict=timeout_dict)
188
+
189
+        if has_tcp and not split_config:
190
+            # Generate HaProxy configuration from listener object
191
+            config = self.jinja_combo.build_config(
192
+                host_amphora=amphora, listeners=loadbalancer.listeners,
193
+                tls_cert=certs['tls_cert'],
194
+                haproxy_versions=haproxy_versions,
195
+                client_ca_filename=client_ca_filename,
196
+                client_crl=crl_filename,
197
+                pool_tls_certs=pool_tls_certs)
198
+            self.clients[amphora.api_version].upload_config(
199
+                amphora, loadbalancer.id, config, timeout_dict=timeout_dict)
200
+            self.clients[amphora.api_version].reload_listener(
201
+                amphora, loadbalancer.id, timeout_dict=timeout_dict)
138 202
 
139 203
     def _udp_update(self, listener, vip):
140 204
         LOG.debug("Amphora %s keepalivedlvs, updating "
@@ -145,69 +209,132 @@ class HaproxyAmphoraLoadBalancerDriver(
145 209
         for amp in listener.load_balancer.amphorae:
146 210
             if amp.status != consts.DELETED:
147 211
                 # Generate Keepalived LVS configuration from listener object
212
+                self._populate_amphora_api_version(amp)
148 213
                 config = self.udp_jinja.build_config(listener=listener)
149
-                self.client.upload_udp_config(amp, listener.id, config)
150
-                self.client.reload_listener(amp, listener.id)
151
-
152
-    def update(self, listener, vip):
153
-        if listener.protocol == 'UDP':
154
-            self._udp_update(listener, vip)
155
-        else:
156
-            LOG.debug("Amphora %s haproxy, updating listener %s, "
157
-                      "vip %s", self.__class__.__name__,
158
-                      listener.protocol_port,
159
-                      vip.ip_address)
160
-
161
-            # Process listener certificate info
162
-            certs = self._process_tls_certificates(listener)
163
-            client_ca_filename = self._process_secret(
164
-                listener, listener.client_ca_tls_certificate_id)
165
-            crl_filename = self._process_secret(
166
-                listener, listener.client_crl_container_id)
167
-            pool_tls_certs = self._process_listener_pool_certs(listener)
168
-
169
-            for amp in listener.load_balancer.amphorae:
170
-                if amp.status != consts.DELETED:
214
+                self.clients[amp.api_version].upload_udp_config(
215
+                    amp, listener.id, config)
216
+                self.clients[amp.api_version].reload_listener(
217
+                    amp, listener.id)
171 218
 
172
-                    haproxy_versions = self._get_haproxy_versions(amp)
173
-
174
-                    # Generate HaProxy configuration from listener object
175
-                    config = self.jinja.build_config(
176
-                        host_amphora=amp, listener=listener,
177
-                        tls_cert=certs['tls_cert'],
178
-                        haproxy_versions=haproxy_versions,
179
-                        client_ca_filename=client_ca_filename,
180
-                        client_crl=crl_filename,
181
-                        pool_tls_certs=pool_tls_certs)
182
-                    self.client.upload_config(amp, listener.id, config)
183
-                    self.client.reload_listener(amp, listener.id)
219
+    def update(self, loadbalancer):
220
+        for amphora in loadbalancer.amphorae:
221
+            if amphora.status != consts.DELETED:
222
+                self.update_amphora_listeners(loadbalancer, amphora)
184 223
 
185 224
     def upload_cert_amp(self, amp, pem):
186 225
         LOG.debug("Amphora %s updating cert in REST driver "
187 226
                   "with amphora id %s,",
188 227
                   self.__class__.__name__, amp.id)
189
-        self.client.update_cert_for_rotation(amp, pem)
228
+        self._populate_amphora_api_version(amp)
229
+        self.clients[amp.api_version].update_cert_for_rotation(amp, pem)
190 230
 
191
-    def _apply(self, func, listener=None, amphora=None, *args):
231
+    def _apply(self, func_name, loadbalancer, amphora=None, *args):
192 232
         if amphora is None:
193
-            for amp in listener.load_balancer.amphorae:
194
-                if amp.status != consts.DELETED:
195
-                    func(amp, listener.id, *args)
233
+            amphorae = loadbalancer.amphorae
196 234
         else:
197
-            if amphora.status != consts.DELETED:
198
-                func(amphora, listener.id, *args)
199
-
200
-    def stop(self, listener, vip):
201
-        self._apply(self.client.stop_listener, listener)
235
+            amphorae = [amphora]
202 236
 
203
-    def start(self, listener, vip, amphora=None):
204
-        self._apply(self.client.start_listener, listener, amphora)
237
+        for amp in amphorae:
238
+            if amp.status != consts.DELETED:
239
+                api_version = self._populate_amphora_api_version(amp)
240
+                # Check which config style to use
241
+                if api_version[0] == 0 and api_version[1] <= 5:
242
+                    # 0.5 or earlier
243
+                    LOG.warning(
244
+                        'Amphora %s for loadbalancer %s needs upgrade to '
245
+                        'single process mode.', amp.id, loadbalancer.id)
246
+                    for listener in loadbalancer.listeners:
247
+                        getattr(self.clients[amp.api_version], func_name)(
248
+                            amp, listener.id, *args)
249
+                else:
250
+                    LOG.debug(
251
+                        'Amphora %s for loadbalancer %s is already in single '
252
+                        'process mode.', amp.id, loadbalancer.id)
253
+                    has_tcp = False
254
+                    for listener in loadbalancer.listeners:
255
+                        if listener.protocol == consts.PROTOCOL_UDP:
256
+                            getattr(self.clients[amp.api_version], func_name)(
257
+                                amp, listener.id, *args)
258
+                        else:
259
+                            has_tcp = True
260
+                    if has_tcp:
261
+                        getattr(self.clients[amp.api_version], func_name)(
262
+                            amp, loadbalancer.id, *args)
263
+
264
+    def start(self, loadbalancer, amphora=None):
265
+        self._apply('start_listener', loadbalancer, amphora)
266
+
267
+    def delete(self, listener):
268
+        # Delete any UDP listeners the old way (we didn't update the way they
269
+        # are configured)
270
+        loadbalancer = listener.load_balancer
271
+        if listener.protocol == consts.PROTOCOL_UDP:
272
+            for amp in loadbalancer.amphorae:
273
+                if amp.status != consts.DELETED:
274
+                    self._populate_amphora_api_version(amp)
275
+                    self.clients[amp.api_version].delete_listener(
276
+                        amp, listener.id)
277
+            return
205 278
 
206
-    def delete(self, listener, vip):
207
-        self._apply(self.client.delete_listener, listener)
279
+        # In case the listener is not UDP, things get more complicated.
280
+        # We need to do this individually for each amphora in case some are
281
+        # using split config and others are using combined config.
282
+        for amp in loadbalancer.amphorae:
283
+            if amp.status != consts.DELETED:
284
+                api_version = self._populate_amphora_api_version(amp)
285
+                # Check which config style to use
286
+                if api_version[0] == 0 and api_version[1] <= 5:
287
+                    # 0.5 or earlier
288
+                    LOG.warning(
289
+                        'Amphora %s for loadbalancer %s needs upgrade to '
290
+                        'single process mode.', amp.id, loadbalancer.id)
291
+                    self.clients[amp.api_version].delete_listener(
292
+                        amp, listener.id)
293
+                else:
294
+                    LOG.debug(
295
+                        'Amphora %s for loadbalancer %s is already in single '
296
+                        'process mode.', amp.id, loadbalancer.id)
297
+                    self._combined_config_delete(amp, listener)
298
+
299
+    def _combined_config_delete(self, amphora, listener):
300
+        # Remove the listener from the listener list on the LB before
301
+        # passing the whole thing over to update (so it'll actually delete)
302
+        listener.load_balancer.listeners.remove(listener)
303
+
304
+        # Check if there's any certs that we need to delete
305
+        certs = self._process_tls_certificates(listener)
306
+        certs_to_delete = set()
307
+        if certs['tls_cert']:
308
+            certs_to_delete.add(certs['tls_cert'].id)
309
+        for sni_cert in certs['sni_certs']:
310
+            certs_to_delete.add(sni_cert.id)
311
+
312
+        # Delete them (they'll be recreated before the reload if they are
313
+        # needed for other listeners anyway)
314
+        self._populate_amphora_api_version(amphora)
315
+        for cert_id in certs_to_delete:
316
+            self.clients[amphora.api_version].delete_cert_pem(
317
+                amphora, listener.load_balancer.id,
318
+                '{id}.pem'.format(id=cert_id))
319
+
320
+        # See how many non-UDP listeners we have left
321
+        non_udp_listener_count = len([
322
+            1 for l in listener.load_balancer.listeners
323
+            if l.protocol != consts.PROTOCOL_UDP])
324
+        if non_udp_listener_count > 0:
325
+            # We have other listeners, so just update is fine.
326
+            # TODO(rm_work): This is a little inefficient since this duplicates
327
+            # a lot of the detection logic that has already been done, but it
328
+            # is probably safer to re-use the existing code-path.
329
+            self.update_amphora_listeners(listener.load_balancer, amphora)
330
+        else:
331
+            # Deleting the last listener, so really do the delete
332
+            self.clients[amphora.api_version].delete_listener(
333
+                amphora, listener.load_balancer.id)
208 334
 
209 335
     def get_info(sel