1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
# Copyright 2013 Hewlett-Packard Development Company, L.P.
4
# Licensed under the Apache License, Version 2.0 (the "License"); you may
5
# not use this file except in compliance with the License. You may obtain
6
# a copy of the License at
8
# http://www.apache.org/licenses/LICENSE-2.0
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
# License for the specific language governing permissions and limitations
16
from pecan import request, response
17
from pecan.rest import RestController
18
import wsmeext.pecan as wsme_pecan
19
from wsme.exc import ClientSideError
20
from wsme import Unset
21
from urllib import quote
22
from libra.common.api.lbaas import LoadBalancer, db_session
23
from libra.common.api.lbaas import Device, HealthMonitor, Counters
24
from libra.api.acl import get_limited_to_project
25
from libra.api.model.validators import LBMonitorPut, LBMonitorResp
26
from libra.common.api.gearman_client import submit_job
27
from libra.api.library.exp import NotFound, ImmutableEntity, ImmutableStates
30
class HealthMonitorController(RestController):
36
"""functions for /loadbalancers/{loadBalancerId}/healthmonitor routing"""
37
def __init__(self, load_balancer_id=None):
38
self.lbid = load_balancer_id
40
@wsme_pecan.wsexpose(None)
42
"""Retrieve the health monitor configuration, if one exists.
44
GET /loadbalancers/{load_balancer_id}/healthmonitor
49
raise ClientSideError('Load Balancer ID has not been supplied')
51
tenant_id = get_limited_to_project(request.headers)
52
with db_session() as session:
54
monitor = session.query(
55
HealthMonitor.type, HealthMonitor.delay,
56
HealthMonitor.timeout, HealthMonitor.attempts,
58
).join(LoadBalancer.monitors).\
59
filter(LoadBalancer.id == self.lbid).\
60
filter(LoadBalancer.tenantid == tenant_id).\
61
filter(LoadBalancer.status != 'DELETED').\
71
'delay': monitor.delay,
72
'timeout': monitor.timeout,
73
'attemptsBeforeDeactivation': monitor.attempts
77
monitor_data['path'] = monitor.path
79
counter = session.query(Counters).\
80
filter(Counters.name == 'api_healthmonitor_get').first()
86
@wsme_pecan.wsexpose(LBMonitorResp, body=LBMonitorPut, status_code=202)
87
def put(self, body=None):
88
"""Update the settings for a health monitor.
90
:param load_balancer_id: id of lb
91
:param *args: holds the posted json or xml data
94
PUT /loadbalancers/{load_balancer_id}/healthmonitor
99
raise ClientSideError('Load Balancer ID has not been supplied')
101
tenant_id = get_limited_to_project(request.headers)
102
with db_session() as session:
104
query = session.query(LoadBalancer, HealthMonitor).\
105
outerjoin(LoadBalancer.monitors).\
106
filter(LoadBalancer.id == self.lbid).\
107
filter(LoadBalancer.tenantid == tenant_id).\
108
filter(LoadBalancer.status != 'DELETED').first()
112
raise NotFound("Load Balancer not found")
118
raise NotFound('Load Balancer not found')
122
body.type == Unset or
124
body.delay == Unset or
125
body.delay is None or
126
body.timeout == Unset or
127
body.timeout is None or
128
body.attemptsBeforeDeactivation == Unset or
129
body.attemptsBeforeDeactivation is None
132
raise ClientSideError(
133
"Missing field(s): {0}, {1}, {2}, and {3} are required"
134
.format("type", "delay", "timeout",
135
"attemptsBeforeDeactivation")
141
"delay": int(body.delay),
142
"timeout": int(body.timeout),
143
"attempts": int(body.attemptsBeforeDeactivation)
146
# Path only required when type is not CONNECT
147
if body.path != Unset and body.path is not None:
148
if body.type == "CONNECT":
150
raise ClientSideError(
151
"Path argument is invalid with CONNECT type"
153
# Encode everything apart from allowed characters
154
# This ignore list in the second parameter is everything in
155
# RFC3986 section 2 that isn't already ignored by
157
data["path"] = quote(body.path, "/~+*,;:!$'[]()?&=#%")
158
# If path is empty, set to /
159
if len(data["path"]) == 0 or data["path"][0] != "/":
161
raise ClientSideError(
162
"Path must begin with leading /"
165
if len(data["path"]) > self.PATH_LIMIT:
166
raise ClientSideError(
167
"Path must be less than {0} characters"
168
.format(self.PATH_LIMIT)
171
if body.type != "CONNECT":
173
raise ClientSideError(
174
"Path argument is required"
178
# Check timeout limits. Must be > 0 and limited to 1 hour
179
if data["timeout"] < 1 or data["timeout"] > self.TIMEOUT_LIMIT:
181
raise ClientSideError(
182
"timeout must be between 1 and {0} seconds"
183
.format(self.TIMEOUT_LIMIT)
186
# Check delay limits. Must be > 0 and limited to 1 hour
187
if data["delay"] < 1 or data["delay"] > self.DELAY_LIMIT:
189
raise ClientSideError(
190
"delay must be between 1 and {0} seconds"
191
.format(self.DELAY_LIMIT)
194
if data["timeout"] > data["delay"]:
196
raise ClientSideError(
197
"timeout cannot be greater than delay"
200
if (data["attempts"] < 1 or data["attempts"] > 10):
202
raise ClientSideError(
203
"attemptsBeforeDeactivation must be between 1 and 10"
207
# This is ok for LBs that already existed without
208
# monitoring. Create a new entry.
209
monitor = HealthMonitor(
210
lbid=self.lbid, type=data["type"], delay=data["delay"],
211
timeout=data["timeout"], attempts=data["attempts"],
216
# Modify the existing entry.
217
monitor.type = data["type"]
218
monitor.delay = data["delay"]
219
monitor.timeout = data["timeout"]
220
monitor.attempts = data["attempts"]
221
monitor.path = data["path"]
223
if lb.status in ImmutableStates:
225
raise ImmutableEntity(
226
'Cannot modify a Load Balancer in a non-ACTIVE state'
227
', current state: {0}'
231
lb.status = 'PENDING_UPDATE'
232
device = session.query(
233
Device.id, Device.name, Device.status
234
).join(LoadBalancer.devices).\
235
filter(LoadBalancer.id == self.lbid).\
238
return_data = LBMonitorResp()
239
return_data.type = data["type"]
240
return_data.delay = str(data["delay"])
241
return_data.timeout = str(data["timeout"])
242
return_data.attemptsBeforeDeactivation =\
243
str(data["attempts"])
244
if ((data["path"] is not None) and (len(data["path"]) > 0)):
245
return_data.path = data["path"]
247
counter = session.query(Counters).\
248
filter(Counters.name == 'api_healthmonitor_modify').first()
252
'UPDATE', device.name, device.id, lb.id
256
@wsme_pecan.wsexpose(None, status_code=202)
258
"""Remove the health monitor.
260
:param load_balancer_id: id of lb
263
DELETE /loadbalancers/{load_balancer_id}/healthmonitor
268
raise ClientSideError('Load Balancer ID has not been supplied')
270
tenant_id = get_limited_to_project(request.headers)
271
with db_session() as session:
272
query = session.query(
273
LoadBalancer, HealthMonitor
274
).outerjoin(LoadBalancer.monitors).\
275
filter(LoadBalancer.tenantid == tenant_id).\
276
filter(LoadBalancer.id == self.lbid).\
277
filter(LoadBalancer.status != 'DELETED').\
282
raise NotFound("Load Balancer not found")
288
raise NotFound("Load Balancer not found")
290
if monitor is not None:
291
session.delete(monitor)
294
device = session.query(
295
Device.id, Device.name
296
).join(LoadBalancer.devices).\
297
filter(LoadBalancer.id == self.lbid).\
299
counter = session.query(Counters).\
300
filter(Counters.name == 'api_healthmonitor.delete').first()
304
'UPDATE', device.name, device.id, self.lbid