This repository was archived by the owner on Apr 30, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 41
Expand file tree
/
Copy pathapi.py
More file actions
210 lines (159 loc) · 6.99 KB
/
api.py
File metadata and controls
210 lines (159 loc) · 6.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
import json
import logging
from datetime import datetime
import os
import importlib
from flask import request
from flask.ext.restful import Resource
from ..stats import get_stats
from .. import settings
from ..services import host
from ..services import query
logger = logging.getLogger('resources.api')
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(name)s: %(levelname)s %(message)s')
class BackendSelector(object):
def __init__(self):
self.storage = self.get_storage()
def get_storage(self):
return settings.value.BACKEND_STORAGE
def plugins_exist(self):
return 'plugins' in os.listdir(os.getcwd())
def assemble_plugin_backend_location(self):
return 'plugins.{}.app.services.query'.format(self.storage)
def assemble_plugin_backend_class_name(self):
return '{}QueryBackend'.format(self.storage)
def get_query_plugin_from_location_and_name(self, backend_location, backend_name):
try:
query_module = importlib.import_module(backend_location)
except ImportError:
raise ImportError("Verify {} is a valid path".format(backend_location))
try:
query_backend = getattr(query_module, backend_name)()
except AttributeError:
raise AttributeError("Verify {} has classname {}".format(query_module, backend_name))
return query_backend
def select(self):
"""
Select backend storage based on the global settings.
"""
if self.storage == 'DynamoDB':
return query.DynamoQueryBackend()
elif self.storage == 'InMemory':
return query.MemoryQueryBackend()
elif self.storage == 'InFile':
return query.LocalFileQueryBackend()
elif self.plugins_exist():
# import the query backend starting from the plugins folder
query_location_from_plugins = self.assemble_plugin_backend_location()
backend_name = self.assemble_plugin_backend_class_name()
query_backend = self.get_query_plugin_from_location_and_name(
query_location_from_plugins, backend_name)
return query_backend
else:
raise ValueError('Unknown backend storage type specified: {}'.format(self.storage))
# Run this to make sure that BACKEND_STORAGE is of known type.
BACKEND_STORAGE = BackendSelector().select()
class HostSerializer(object):
@staticmethod
def serialize(hosts):
"""Makes host dictionary serializable
:param hosts: list of hosts, each host is defined by dict host info
:type hosts: dict
:returns: list of host info dictionaries
:rtype: list of dict
"""
for _host in hosts:
_host['last_check_in'] = str(_host['last_check_in'])
return hosts
class Registration(Resource):
def get(self, service):
"""Return all the hosts registered for this service"""
host_service = host.HostService(BACKEND_STORAGE)
hosts = host_service.list(service)
response = {
'service': service,
'env': settings.value.APPLICATION_ENV,
'hosts': HostSerializer.serialize(hosts)
}
return response, 200
def post(self, service):
"""Update or add a service registration given the host information in this request"""
ip_address = self._get_param('ip', None)
if not ip_address and self._get_param('auto_ip', None):
# Discovery ELB is the single proxy, take last ip in route
forwarded_for = request.remote_addr
parts = forwarded_for.split('.')
# 192.168.0.0/16
valid = (len(parts) == 4 and
int(parts[0]) == 192 and
int(parts[1]) == 168 and
0 <= int(parts[2]) <= 255 and
0 <= int(parts[3]) <= 255)
if valid:
ip_address = forwarded_for
logger.info('msg="auto_ip success" service={}, auto_ip={}'
.format(service, ip_address))
else:
logger.warn('msg="auto_ip invalid" service={} auto_ip={}'
.format(service, ip_address))
service_repo_name = self._get_param('service_repo_name', '')
port = int(self._get_param('port', -1))
revision = self._get_param('revision', None)
last_check_in = datetime.utcnow()
tags = self._get_param('tags', '{}')
try:
tags = json.loads(tags)
except ValueError as ex:
logger.exception("Failed to parse tags json: {}. Exception: {}".format(tags, ex))
return {"error": "Invalid json supplied in tags"}, 400
host_service = host.HostService(BACKEND_STORAGE)
success = host_service.update(service, ip_address, service_repo_name,
port, revision, last_check_in, tags)
statsd = get_stats("registration")
if success:
response_code = 200
statsd.incr("%s.success" % service)
else:
response_code = 400
statsd.incr("%s.failure" % service)
return {}, response_code
def delete(self, service, ip_address):
"""Delete a host from dynamo"""
host_service = host.HostService(BACKEND_STORAGE)
success = host_service.delete(service, ip_address)
response_code = 200 if success else 400
return {}, response_code
def _get_param(self, param, default=None):
"""Return the request parameter. Returns default if the param was not found"""
return request.form[param] if param in request.form else default
class RepoRegistration(Resource):
def get(self, service_repo_name):
"""Return all the hosts that belong to the service_repo_name"""
host_service = host.HostService(BACKEND_STORAGE)
hosts = host_service.list_by_service_repo_name(service_repo_name)
response = {
'service_repo_name': service_repo_name,
'env': settings.value.APPLICATION_ENV,
'hosts': HostSerializer.serialize(hosts)
}
return response, 200
class LoadBalancing(Resource):
def post(self, service, ip_address=None):
weight = request.form.get('load_balancing_weight')
if not weight:
return {"error": "Required parameter 'weight' is missing."}, 400
try:
weight = int(weight)
except ValueError:
weight = None
if not weight or not 1 <= weight <= 100:
return {"error": ("Invalid load_balancing_weight. Supply an "
"integer between 1 and 100.")}, 400
host_service = host.HostService(BACKEND_STORAGE)
if ip_address:
if not host_service.set_tag(service, ip_address, 'load_balancing_weight', weight):
return {"error": "Host not found"}, 404
else:
host_service.set_tag_all(service, 'load_balancing_weight', weight)
return "", 204