/
tpldemo.py
executable file
·308 lines (233 loc) · 8.72 KB
/
tpldemo.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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
#!/usr/bin/python
"""
A demo exercising testpool behavior. Read the quick start guide in
order to configure Testpool server and then come back to this script.
This demo supports both the fake and docker products. Fake is a good
tool for seeing behavior and development without having to setup additional
tools. To run the demo, in one shell
./bin/tpl-daemon
In another shell:
./bin/tpl-db
Finally in another shell,
tpl-demo -v
The docker product requires install docker locally in order to see the demo in
action. Check the dashboard to see the status of the various pool of resources
at:
http://127.0.0.1:8000/testpool/view/dashboard
To run docker demo:
docker pull nginx:latest
tpl-demo --product docker -v
"""
import sys
import random
import time
import argparse
import logging
import requests
class Rest(object):
""" Example calling REST interface. """
# pylint: disable=too-many-arguments
def __init__(self, hostname):
fmt = "http://%s:8000/testpool/api/v1/"
self.url = fmt % hostname
def pool_add(self, name, connection, product, template_name, resource_max):
""" Add a pool. """
params = {
"connection": connection,
"product": product,
"resource_max": resource_max,
"template_name": template_name
}
url = self.url + "pool/add/%s" % name
rtc = requests.post(url, params=params)
rtc.raise_for_status()
return rtc
def pool_remove(self, name, immediate=False):
""" Remove a pool. """
fmt = self.url + "pool/remove/%s?immediate=%s"
url = fmt % (name, immediate)
rtc = requests.delete(url)
rtc.raise_for_status()
return rtc
def acquire(self, pool_name):
""" Acquire a resource. """
url = self.url + "pool/acquire/%s" % pool_name
return requests.get(url)
def release(self, rsrc):
""" Release a resource. """
url = self.url + "pool/release/%s" % rsrc
return requests.get(url)
FAKE_POOL_NAMES = ["ubuntu16.04", "centos7.0", "vmware", "ESX6.5"]
DOCKER_POOL_NAMES = ["nginx"]
KVM_POOL_NAMES = ["ubuntu1804"]
class PoolBaseIfc(object):
""" Interface for pool behavior. """
def __init__(self, rest, product):
self.rest = rest
self.product = product
def add(self):
""" Add pools. """
# pylint: disable=no-self-use
raise ValueError("add not implemented")
def remove(self):
""" Create all of the pools. """
for name in FAKE_POOL_NAMES + DOCKER_POOL_NAMES:
try:
logging.info("remove pool %s", name)
self.rest.pool_remove(name, immediate=True)
except Exception as arg: # pylint: disable=broad-except
logging.info(arg)
class FakePool(PoolBaseIfc):
""" Handle fake demo. """
def add(self):
""" Add fake pool. """
resource_maxes = [10, 40, 100, 50]
for (name, resource_max) in zip(FAKE_POOL_NAMES, resource_maxes):
template_name = "template_name.%s" % name
logging.info("adding pool %s", name)
self.rest.pool_add(name, "localhost", self.product, template_name,
resource_max)
return FAKE_POOL_NAMES
def sleep(self):
""" Sleep between fake operations. """
# pylint: disable=no-self-use
time.sleep(1)
class DockerPool(PoolBaseIfc):
""" Handle docker demo. """
def add(self):
""" Add docker pool. """
resource_maxes = [10]
template_name = "nginx:latest"
for (name, resource_max) in zip(DOCKER_POOL_NAMES, resource_maxes):
logging.info("adding pool %s", name)
self.rest.pool_add(name, "localhost", self.product, template_name,
resource_max)
return DOCKER_POOL_NAMES
def sleep(self):
""" Sleep between docker operations. """
# pylint: disable=no-self-use
time.sleep(10)
class KvmPool(PoolBaseIfc):
""" Handle KVM demo. """
def add(self):
""" Add docker pool. """
resource_maxes = [5]
template_name = "test.template"
for (name, resource_max) in zip(KVM_POOL_NAMES, resource_maxes):
logging.info("adding pool %s", name)
self.rest.pool_add(name, "qemu:///system", self.product,
template_name, resource_max)
return KVM_POOL_NAMES
def sleep(self):
""" Sleep between docker operations. """
# pylint: disable=no-self-use
time.sleep(120)
def pool_base_get(rest, product):
""" Return appropriate pool manager. """
if product == "fake":
return FakePool(rest, product)
elif product == "docker":
return DockerPool(rest, product)
elif product == "kvm":
return KvmPool(rest, product)
else:
raise ValueError("unsupported product %s" % product)
class State(object):
""" Track demo action. """
ACTIVE = 0
RELEASE = 1
WAIT = 2
def __init__(self):
self.stages = [60, 30, 30]
self._stage = State.ACTIVE
self._count = 0
def count(self):
""" Return count. """
return self._count
def next(self):
""" Return the next stage. """
self._count += 1
if self._count >= self.stages[self._stage]:
self._stage += 1
self._stage %= len(self.stages)
return self._stage
def do_start(args):
""" Start demo.
The demo will randomally acquire resources. Then after 60 seconds, free
all resources, wait 30 seconds and then go back to acquiring resources.
"""
actions = ["release", "acquire"]
rest = Rest(args.hostname)
acquired_resources = []
pool = pool_base_get(rest, args.product)
pool.remove()
if args.cleanup:
return 0
pool_names = pool.add()
count = 0
state = State()
while args.count == -1 or count < args.count:
value = state.next()
if value == State.ACTIVE:
logging.info("acquire and releasing resources")
action = random.choice(actions)
pool_name = random.choice(pool_names)
if action == "acquire":
logging.info("acquire %s", pool_name)
resp = rest.acquire(pool_name)
if resp.status_code == 200:
rsrc = resp.json()
acquired_resources.append(rsrc)
logging.info("acquired %s:%s", pool_name, rsrc)
else:
logging.info("%s: %s", resp.status_code,
resp.json()["msg"])
elif action == "release" and acquired_resources:
logging.info("release %s", pool_name)
index = random.randrange(0, len(acquired_resources))
rest.release(rsrc)
del acquired_resources[index]
logging.info("released %s:%s", pool_name, rsrc)
elif value == State.RELEASE and state.count() == 0:
logging.info("releasing all resources")
while acquired_resources:
rsrc = acquired_resources.pop()
rest.release(rsrc)
elif value == State.WAIT and state.count() == 0:
logging.info("sleeping")
pool.sleep()
return 0
def args_process(args):
""" Process any generic parameters. """
if args.verbose == 1:
logging.basicConfig(level=logging.INFO)
logging.info("verbosity level set to INFO")
elif args.verbose > 1:
logging.basicConfig(level=logging.DEBUG)
logging.info("verbosity level set to DEBUG")
def argparser(progname):
""" Create top level argument parser. """
arg_parser = argparse.ArgumentParser(prog=progname, description=__doc__)
arg_parser.add_argument('--count', required=False, default=-1, type=int,
help="How many seconds to run the demo.")
arg_parser.add_argument('--verbose', '-v', required=False, action="count",
help="enable debug verbosity.")
arg_parser.add_argument('--hostname', default="127.0.0.1",
help="Location of the testpool daemon")
arg_parser.add_argument('--product', default="fake",
help="Product used for the demo")
arg_parser.add_argument('--cleanup', default=False, action="store_true",
help="Remove all demo pools")
return arg_parser
def main():
""" Entry point. """
parser = argparser("demo")
args = parser.parse_args()
args_process(args)
return do_start(args)
if __name__ == "__main__":
try:
main()
except Exception, arg: # pylint: disable=broad-except
logging.exception(arg)
sys.exit(1)