-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathelb.py
201 lines (151 loc) · 5.29 KB
/
elb.py
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
# coding: utf-8
import random
import requests
class ELBError(Exception):
pass
class ELBRespError(ELBError):
pass
class ELBRuleError(ELBError):
pass
class Rule(object):
def next(self):
return []
def dump(self):
return {}
class UARule(Rule):
"""User-Agent rule checks if user-agent matches `pattern`,
if success, next rule will be `succ` to process, otherwise `fail` will
be the next."""
def __init__(self, name, pattern, succ, fail):
self.name = name
self.pattern = pattern
self.succ = succ
self.fail = fail
def next(self):
return [self.succ, self.fail]
def dump(self):
return {
'type': 'ua',
'args': {
'succ': self.succ,
'fail': self.fail,
'pattern': self.pattern,
},
}
class BackendRule(Rule):
"""Backend rule is the destination of all rules.
`servername` will be passed to nginx `proxy_pass`.
`servername` can be either a name of `upstream`,
or an IP address directly to proxy pass.
"""
def __init__(self, name, servername):
self.name = name
self.servername = servername
def dump(self):
return {
'type': 'backend',
'args': {
'servername': self.servername,
},
}
class PathRule(Rule):
"""Path rule checks if requested path matches `pattern`, `regex` can be
set to True to indicate that `pattern` is used as a regex.
`rewrite` and `rewrite` can be used to set `rewrite` in nginx.
"""
def __init__(self, name, pattern, regex, rewrite, succ, fail):
self.name = name
self.pattern = pattern
self.regex = bool(regex)
self.rewrite = bool(rewrite)
self.succ = succ
self.fail = fail
def next(self):
return [self.succ, self.fail]
def dump(self):
return {
'type': 'path',
'args': {
'succ': self.succ,
'fail': self.fail,
'pattern': self.pattern,
},
}
class RuleSet(object):
"""a set of rules"""
def __init__(self, init, rules):
self.init = init
self.rules = rules
def check_rules(self):
"""
1. `init` must be in one of the rules
2. if any rule has a `succ` or `fail`, must be in one of the rules
"""
rulenames = set([rule.name for rule in self.rules])
next_rulenames = set([name for rule in self.rules for name in rule.next()])
return self.init in rulenames and next_rulenames.issubset(rulenames)
def dump(self):
if not self.check_rules():
raise ELBRuleError('Error in rules')
rules = {r.name: r.dump() for r in self.rules}
return {'init': self.init, 'rules': rules}
class UpStream(object):
"""upstream for nginx.
`servers` is a dict, key in format `IP:port`, value as a string.
value will be set as additional info for nginx upstream such like `weight=10`.
"""
def __init__(self, backendname, servers):
self.backendname = backendname
self.servers = servers
def dump(self):
return {self.backendname: self.servers}
class ELB(object):
def __init__(self, base):
if not base.startswith('http://'):
base = 'http://' + base
self.base = base
self.session = requests.Session()
def req(self, method, url, params=None, json=None):
resp = self.session.request(method, url=url, params=params, json=json, timeout=2)
if resp.status_code != 200:
raise ELBRespError(resp.content)
return resp.json()
def get_upstream(self):
url = self.base + '/__erulb__/upstream'
return self.req('GET', url)
def set_upstream(self, upstream):
url = self.base + '/__erulb__/upstream'
return self.req('PUT', url, json=upstream.dump())
def delete_upstream(self, upstreams):
url = self.base + '/__erulb__/upstream'
return self.req('DELETE', url, json=upstreams)
def get_domain_rules(self):
url = self.base + '/__erulb__/domain'
return self.req('GET', url)
def set_domain_rules(self, domain, ruleset):
url = self.base + '/__erulb__/domain'
json = {domain: ruleset.dump()}
return self.req('PUT', url, json=json)
def delete_domain_rules(self, domains):
url = self.base + '/__erulb__/domain'
return self.req('DELETE', url, json=domains)
def dump_to_etcd(self):
url = self.base + '/__erulb__/dump'
return self.req('PUT', url)
class ELBSet(object):
"""a set of ELB instances, they share the same etcd"""
def __init__(self, bases):
self.elbs = [ELB(b) for b in bases]
def __getattr__(self, name):
"""`get_upstream`, `get_domain_rules`, `dump_to_etcd` only need to call one of these instances.
other methods will need to call every instance of els.
"""
if name in ('get_upstream', 'get_domain_rules', 'dump_to_etcd'):
e = random.choice(self.elbs)
return getattr(e, name)
elbs = self.elbs
def method(*args, **kwargs):
for e in elbs:
m = getattr(e, name)
m(*args, **kwargs)
return method