~libra-core/libra/master

« back to all changes in this revision

Viewing changes to libra/api/controllers/health_monitor.py

  • Committer: Monty Taylor
  • Date: 2015-10-17 20:03:27 UTC
  • Revision ID: git-v1:c7082fa72ac73b23b48ce63fc82aa7da2d3e5d6a
Retire stackforge/libra

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
 
# Copyright 2013 Hewlett-Packard Development Company, L.P.
3
 
#
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
7
 
#
8
 
#      http://www.apache.org/licenses/LICENSE-2.0
9
 
#
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
14
 
# under the License.
15
 
 
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
28
 
 
29
 
 
30
 
class HealthMonitorController(RestController):
31
 
 
32
 
    TIMEOUT_LIMIT = 3600
33
 
    DELAY_LIMIT = 3600
34
 
    PATH_LIMIT = 2000
35
 
 
36
 
    """functions for /loadbalancers/{loadBalancerId}/healthmonitor routing"""
37
 
    def __init__(self, load_balancer_id=None):
38
 
        self.lbid = load_balancer_id
39
 
 
40
 
    @wsme_pecan.wsexpose(None)
41
 
    def get(self):
42
 
        """Retrieve the health monitor configuration, if one exists.
43
 
        Url:
44
 
           GET /loadbalancers/{load_balancer_id}/healthmonitor
45
 
 
46
 
        Returns: dict
47
 
        """
48
 
        if not self.lbid:
49
 
            raise ClientSideError('Load Balancer ID has not been supplied')
50
 
 
51
 
        tenant_id = get_limited_to_project(request.headers)
52
 
        with db_session() as session:
53
 
            # grab the lb
54
 
            monitor = session.query(
55
 
                HealthMonitor.type, HealthMonitor.delay,
56
 
                HealthMonitor.timeout, HealthMonitor.attempts,
57
 
                HealthMonitor.path
58
 
            ).join(LoadBalancer.monitors).\
59
 
                filter(LoadBalancer.id == self.lbid).\
60
 
                filter(LoadBalancer.tenantid == tenant_id).\
61
 
                filter(LoadBalancer.status != 'DELETED').\
62
 
                first()
63
 
 
64
 
            response.status = 200
65
 
            if monitor is None:
66
 
                session.rollback()
67
 
                return {}
68
 
 
69
 
            monitor_data = {
70
 
                'type': monitor.type,
71
 
                'delay': monitor.delay,
72
 
                'timeout': monitor.timeout,
73
 
                'attemptsBeforeDeactivation': monitor.attempts
74
 
            }
75
 
 
76
 
            if monitor.path:
77
 
                monitor_data['path'] = monitor.path
78
 
 
79
 
            counter = session.query(Counters).\
80
 
                filter(Counters.name == 'api_healthmonitor_get').first()
81
 
            counter.value += 1
82
 
 
83
 
            session.commit()
84
 
        return monitor_data
85
 
 
86
 
    @wsme_pecan.wsexpose(LBMonitorResp, body=LBMonitorPut, status_code=202)
87
 
    def put(self, body=None):
88
 
        """Update the settings for a health monitor.
89
 
 
90
 
        :param load_balancer_id: id of lb
91
 
        :param *args: holds the posted json or xml data
92
 
 
93
 
        Url:
94
 
           PUT /loadbalancers/{load_balancer_id}/healthmonitor
95
 
 
96
 
        Returns: dict
97
 
        """
98
 
        if not self.lbid:
99
 
            raise ClientSideError('Load Balancer ID has not been supplied')
100
 
 
101
 
        tenant_id = get_limited_to_project(request.headers)
102
 
        with db_session() as session:
103
 
            # grab the lb
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()
109
 
 
110
 
            if query is None:
111
 
                session.rollback()
112
 
                raise NotFound("Load Balancer not found")
113
 
 
114
 
            lb, monitor = query
115
 
 
116
 
            if lb is None:
117
 
                session.rollback()
118
 
                raise NotFound('Load Balancer not found')
119
 
 
120
 
            # Check inputs
121
 
            if (
122
 
                body.type == Unset or
123
 
                body.type is None 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
130
 
            ):
131
 
                session.rollback()
132
 
                raise ClientSideError(
133
 
                    "Missing field(s): {0}, {1}, {2}, and {3} are required"
134
 
                    .format("type", "delay", "timeout",
135
 
                            "attemptsBeforeDeactivation")
136
 
                )
137
 
 
138
 
            data = {
139
 
                "lbid": self.lbid,
140
 
                "type": body.type,
141
 
                "delay": int(body.delay),
142
 
                "timeout": int(body.timeout),
143
 
                "attempts": int(body.attemptsBeforeDeactivation)
144
 
            }
145
 
 
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":
149
 
                    session.rollback()
150
 
                    raise ClientSideError(
151
 
                        "Path argument is invalid with CONNECT type"
152
 
                    )
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
156
 
                # urllib.quote()
157
 
                data["path"] = quote(body.path, "/~+*,;:!$'[]()?&=#%")
158
 
                # If path is empty, set to /
159
 
                if len(data["path"]) == 0 or data["path"][0] != "/":
160
 
                    session.rollback()
161
 
                    raise ClientSideError(
162
 
                        "Path must begin with leading /"
163
 
                    )
164
 
 
165
 
                if len(data["path"]) > self.PATH_LIMIT:
166
 
                    raise ClientSideError(
167
 
                        "Path must be less than {0} characters"
168
 
                        .format(self.PATH_LIMIT)
169
 
                    )
170
 
            else:
171
 
                if body.type != "CONNECT":
172
 
                    session.rollback()
173
 
                    raise ClientSideError(
174
 
                        "Path argument is required"
175
 
                    )
176
 
                data["path"] = None
177
 
 
178
 
            # Check timeout limits. Must be > 0 and limited to 1 hour
179
 
            if data["timeout"] < 1 or data["timeout"] > self.TIMEOUT_LIMIT:
180
 
                session.rollback()
181
 
                raise ClientSideError(
182
 
                    "timeout must be between 1 and {0} seconds"
183
 
                    .format(self.TIMEOUT_LIMIT)
184
 
                )
185
 
 
186
 
            # Check delay limits. Must be > 0 and limited to 1 hour
187
 
            if data["delay"] < 1 or data["delay"] > self.DELAY_LIMIT:
188
 
                session.rollback()
189
 
                raise ClientSideError(
190
 
                    "delay must be between 1 and {0} seconds"
191
 
                    .format(self.DELAY_LIMIT)
192
 
                )
193
 
 
194
 
            if data["timeout"] > data["delay"]:
195
 
                session.rollback()
196
 
                raise ClientSideError(
197
 
                    "timeout cannot be greater than delay"
198
 
                )
199
 
 
200
 
            if (data["attempts"] < 1 or data["attempts"] > 10):
201
 
                session.rollback()
202
 
                raise ClientSideError(
203
 
                    "attemptsBeforeDeactivation must be between 1 and 10"
204
 
                )
205
 
 
206
 
            if monitor is None:
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"],
212
 
                    path=data["path"]
213
 
                )
214
 
                session.add(monitor)
215
 
            else:
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"]
222
 
 
223
 
            if lb.status in ImmutableStates:
224
 
                session.rollback()
225
 
                raise ImmutableEntity(
226
 
                    'Cannot modify a Load Balancer in a non-ACTIVE state'
227
 
                    ', current state: {0}'
228
 
                    .format(lb.status)
229
 
                )
230
 
 
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).\
236
 
                first()
237
 
 
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"]
246
 
 
247
 
            counter = session.query(Counters).\
248
 
                filter(Counters.name == 'api_healthmonitor_modify').first()
249
 
            counter.value += 1
250
 
            session.commit()
251
 
            submit_job(
252
 
                'UPDATE', device.name, device.id, lb.id
253
 
            )
254
 
            return return_data
255
 
 
256
 
    @wsme_pecan.wsexpose(None, status_code=202)
257
 
    def delete(self):
258
 
        """Remove the health monitor.
259
 
 
260
 
        :param load_balancer_id: id of lb
261
 
 
262
 
        Url:
263
 
           DELETE /loadbalancers/{load_balancer_id}/healthmonitor
264
 
 
265
 
        Returns: void
266
 
        """
267
 
        if not self.lbid:
268
 
            raise ClientSideError('Load Balancer ID has not been supplied')
269
 
 
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').\
278
 
                first()
279
 
 
280
 
            if query is None:
281
 
                session.rollback()
282
 
                raise NotFound("Load Balancer not found")
283
 
 
284
 
            lb, monitor = query
285
 
 
286
 
            if lb is None:
287
 
                session.rollback()
288
 
                raise NotFound("Load Balancer not found")
289
 
 
290
 
            if monitor is not None:
291
 
                session.delete(monitor)
292
 
                session.flush()
293
 
 
294
 
            device = session.query(
295
 
                Device.id, Device.name
296
 
            ).join(LoadBalancer.devices).\
297
 
                filter(LoadBalancer.id == self.lbid).\
298
 
                first()
299
 
            counter = session.query(Counters).\
300
 
                filter(Counters.name == 'api_healthmonitor.delete').first()
301
 
            counter.value += 1
302
 
            session.commit()
303
 
            submit_job(
304
 
                'UPDATE', device.name, device.id, self.lbid
305
 
            )
306
 
            return None