blob: e3b92451e9f56736ccfa4b3b2afafbe04c4a9491 [file] [log] [blame]
koder aka kdanilov4643fd62015-02-10 16:20:13 -08001import re
2import os
3import time
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +03004import os.path
koder aka kdanilove21d7472015-02-14 19:02:04 -08005import logging
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +03006import subprocess
koder aka kdanilov4643fd62015-02-10 16:20:13 -08007
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -08008from concurrent.futures import ThreadPoolExecutor
9
koder aka kdanilov4500a5f2015-04-17 16:55:17 +030010from novaclient.exceptions import NotFound
koder aka kdanilov4643fd62015-02-10 16:20:13 -080011from novaclient.client import Client as n_client
12from cinderclient.v1.client import Client as c_client
13
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030014import wally
15from wally.discover import Node
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030016
koder aka kdanilov4643fd62015-02-10 16:20:13 -080017
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030018logger = logging.getLogger("wally.vms")
koder aka kdanilove21d7472015-02-14 19:02:04 -080019
20
koder aka kdanilove87ae652015-04-20 02:14:35 +030021STORED_OPENSTACK_CREDS = None
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030022NOVA_CONNECTION = None
koder aka kdanilove87ae652015-04-20 02:14:35 +030023CINDER_CONNECTION = None
24
25
26def ostack_get_creds():
27 if STORED_OPENSTACK_CREDS is None:
28 env = os.environ.get
29 name = env('OS_USERNAME')
30 passwd = env('OS_PASSWORD')
31 tenant = env('OS_TENANT_NAME')
32 auth_url = env('OS_AUTH_URL')
33 return name, passwd, tenant, auth_url
34 else:
35 return STORED_OPENSTACK_CREDS
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030036
37
38def nova_connect(name=None, passwd=None, tenant=None, auth_url=None):
39 global NOVA_CONNECTION
koder aka kdanilove87ae652015-04-20 02:14:35 +030040 global STORED_OPENSTACK_CREDS
41
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030042 if NOVA_CONNECTION is None:
43 if name is None:
44 name, passwd, tenant, auth_url = ostack_get_creds()
koder aka kdanilove87ae652015-04-20 02:14:35 +030045 else:
46 STORED_OPENSTACK_CREDS = (name, passwd, tenant, auth_url)
47
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030048 NOVA_CONNECTION = n_client('1.1', name, passwd, tenant, auth_url)
49 return NOVA_CONNECTION
50
51
koder aka kdanilove87ae652015-04-20 02:14:35 +030052def cinder_connect(name=None, passwd=None, tenant=None, auth_url=None):
53 global CINDER_CONNECTION
54 global STORED_OPENSTACK_CREDS
55
56 if CINDER_CONNECTION is None:
57 if name is None:
58 name, passwd, tenant, auth_url = ostack_get_creds()
59 else:
60 STORED_OPENSTACK_CREDS = (name, passwd, tenant, auth_url)
61 CINDER_CONNECTION = c_client(name, passwd, tenant, auth_url)
62 return CINDER_CONNECTION
63
64
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030065def nova_disconnect():
66 global NOVA_CONNECTION
67 if NOVA_CONNECTION is not None:
68 NOVA_CONNECTION.close()
69 NOVA_CONNECTION = None
koder aka kdanilov4643fd62015-02-10 16:20:13 -080070
71
koder aka kdanilov4500a5f2015-04-17 16:55:17 +030072def prepare_os_subpr(name=None, passwd=None, tenant=None, auth_url=None):
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +030073 if name is None:
74 name, passwd, tenant, auth_url = ostack_get_creds()
75
76 params = {
77 'OS_USERNAME': name,
78 'OS_PASSWORD': passwd,
79 'OS_TENANT_NAME': tenant,
80 'OS_AUTH_URL': auth_url
81 }
82
koder aka kdanilov6b1341a2015-04-21 22:44:21 +030083 params_s = " ".join("{0}={1}".format(k, v) for k, v in params.items())
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +030084
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030085 spath = os.path.dirname(wally.__file__)
86 spath = os.path.dirname(spath)
87 spath = os.path.join(spath, 'scripts/prepare.sh')
88
89 cmd_templ = "env {params} bash {spath} >/dev/null"
90 cmd = cmd_templ.format(params=params_s, spath=spath)
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +030091 subprocess.call(cmd, shell=True)
92
koder aka kdanilov4500a5f2015-04-17 16:55:17 +030093
94def prepare_os(nova, params):
95 allow_ssh(nova, params['security_group'])
96
97 shed_ids = []
98 for shed_group in params['schedulers_groups']:
99 shed_ids.append(get_or_create_aa_group(nova, shed_group))
100
101 create_keypair(nova,
102 params['keypair_name'],
103 params['pub_key_path'],
104 params['priv_key_path'])
105
106 create_image(nova, params['image']['name'],
107 params['image']['url'])
108
109 create_flavor(nova, **params['flavor'])
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +0300110
111
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300112def get_or_create_aa_group(nova, name):
113 try:
114 group = nova.server_groups.find(name=name)
115 except NotFound:
116 group = nova.server_groups.create({'name': name,
117 'policies': ['anti-affinity']})
koder aka kdanilov652cd802015-04-13 12:21:07 +0300118
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300119 return group.id
koder aka kdanilov652cd802015-04-13 12:21:07 +0300120
121
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300122def allow_ssh(nova, group_name):
123 try:
124 secgroup = nova.security_groups.find(name=group_name)
125 except NotFound:
126 secgroup = nova.security_groups.create(group_name,
127 "allow ssh/ping to node")
128
koder aka kdanilov652cd802015-04-13 12:21:07 +0300129 nova.security_group_rules.create(secgroup.id,
130 ip_protocol="tcp",
131 from_port="22",
132 to_port="22",
133 cidr="0.0.0.0/0")
134
135 nova.security_group_rules.create(secgroup.id,
136 ip_protocol="icmp",
137 from_port=-1,
138 cidr="0.0.0.0/0",
139 to_port=-1)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300140 return secgroup.id
koder aka kdanilov652cd802015-04-13 12:21:07 +0300141
142
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300143def create_image(nova, name, url):
144 pass
145
146
147def create_flavor(nova, name, **params):
148 pass
149
150
151def create_keypair(nova, name, pub_key_path, priv_key_path):
152 try:
153 nova.keypairs.find(name=name)
154 except NotFound:
155 if os.path.exists(pub_key_path):
156 with open(pub_key_path) as pub_key_fd:
157 return nova.keypairs.create(name, pub_key_fd.read())
158 else:
159 key = nova.keypairs.create(name)
160
161 with open(priv_key_path, "w") as priv_key_fd:
162 priv_key_fd.write(key.private_key)
163
164 with open(pub_key_path, "w") as pub_key_fd:
165 pub_key_fd.write(key.public_key)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800166
167
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800168def create_volume(size, name):
koder aka kdanilove87ae652015-04-20 02:14:35 +0300169 cinder = cinder_connect()
170 # vol_id = "2974f227-8755-4333-bcae-cd9693cd5d04"
171 # logger.warning("Reusing volume {0}".format(vol_id))
172 # vol = cinder.volumes.get(vol_id)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800173 vol = cinder.volumes.create(size=size, display_name=name)
174 err_count = 0
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300175
koder aka kdanilove87ae652015-04-20 02:14:35 +0300176 while vol.status != 'available':
177 if vol.status == 'error':
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800178 if err_count == 3:
koder aka kdanilove21d7472015-02-14 19:02:04 -0800179 logger.critical("Fail to create volume")
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800180 raise RuntimeError("Fail to create volume")
181 else:
182 err_count += 1
183 cinder.volumes.delete(vol)
184 time.sleep(1)
185 vol = cinder.volumes.create(size=size, display_name=name)
186 continue
187 time.sleep(1)
koder aka kdanilove87ae652015-04-20 02:14:35 +0300188 vol = cinder.volumes.get(vol.id)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800189 return vol
190
191
192def wait_for_server_active(nova, server, timeout=240):
193 t = time.time()
194 while True:
koder aka kdanilov3f356262015-02-13 08:06:14 -0800195 time.sleep(1)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800196 sstate = getattr(server, 'OS-EXT-STS:vm_state').lower()
197
198 if sstate == 'active':
199 return True
200
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800201 if sstate == 'error':
202 return False
203
204 if time.time() - t > timeout:
205 return False
206
207 server = nova.servers.get(server)
208
209
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800210class Allocate(object):
211 pass
212
213
214def get_floating_ips(nova, pool, amount):
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800215 ip_list = nova.floating_ips.list()
216
217 if pool is not None:
218 ip_list = [ip for ip in ip_list if ip.pool == pool]
219
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800220 return [ip for ip in ip_list if ip.instance_id is None][:amount]
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800221
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800222
koder aka kdanilovcee43342015-04-14 22:52:53 +0300223def launch_vms(params):
koder aka kdanilovda45e882015-04-06 02:24:42 +0300224 logger.debug("Starting new nodes on openstack")
koder aka kdanilovcee43342015-04-14 22:52:53 +0300225 params = params.copy()
koder aka kdanilovda45e882015-04-06 02:24:42 +0300226 count = params.pop('count')
227
228 if isinstance(count, basestring):
229 assert count.startswith("x")
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300230 lst = NOVA_CONNECTION.services.list(binary='nova-compute')
koder aka kdanilovda45e882015-04-06 02:24:42 +0300231 srv_count = len([srv for srv in lst if srv.status == 'enabled'])
232 count = srv_count * int(count[1:])
233
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300234 srv_params = "img: {image[name]}, flavor: {flavor[name]}".format(**params)
koder aka kdanilov66839a92015-04-11 13:22:31 +0300235 msg_templ = "Will start {0} servers with next params: {1}"
koder aka kdanilovcee43342015-04-14 22:52:53 +0300236 logger.info(msg_templ.format(count, srv_params))
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300237 vm_creds = params.pop('creds')
koder aka kdanilovda45e882015-04-06 02:24:42 +0300238
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300239 params = params.copy()
240
241 params['img_name'] = params['image']['name']
242 params['flavor_name'] = params['flavor']['name']
243
244 del params['image']
245 del params['flavor']
246 del params['scheduler_group_name']
247 private_key_path = params.pop('private_key_path')
248
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300249 for ip, os_node in create_vms_mt(NOVA_CONNECTION, count, **params):
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300250 conn_uri = vm_creds.format(ip=ip, private_key_path=private_key_path)
251 yield Node(conn_uri, []), os_node.id
koder aka kdanilovda45e882015-04-06 02:24:42 +0300252
253
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300254def create_vms_mt(nova, amount, group_name, keypair_name, img_name,
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800255 flavor_name, vol_sz=None, network_zone_name=None,
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300256 flt_ip_pool=None, name_templ='wally-{id}',
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300257 scheduler_hints=None, security_group=None):
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800258
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800259 with ThreadPoolExecutor(max_workers=16) as executor:
koder aka kdanilov97644f92015-02-13 11:11:08 -0800260 if network_zone_name is not None:
261 network_future = executor.submit(nova.networks.find,
262 label=network_zone_name)
263 else:
264 network_future = None
265
266 fl_future = executor.submit(nova.flavors.find, name=flavor_name)
267 img_future = executor.submit(nova.images.find, name=img_name)
268
269 if flt_ip_pool is not None:
270 ips_future = executor.submit(get_floating_ips,
271 nova, flt_ip_pool, amount)
koder aka kdanilove21d7472015-02-14 19:02:04 -0800272 logger.debug("Wait for floating ip")
koder aka kdanilov97644f92015-02-13 11:11:08 -0800273 ips = ips_future.result()
274 ips += [Allocate] * (amount - len(ips))
275 else:
276 ips = [None] * amount
277
koder aka kdanilov7dec9df2015-02-15 21:35:19 -0800278 logger.debug("Getting flavor object")
koder aka kdanilov97644f92015-02-13 11:11:08 -0800279 fl = fl_future.result()
koder aka kdanilov7dec9df2015-02-15 21:35:19 -0800280 logger.debug("Getting image object")
koder aka kdanilov97644f92015-02-13 11:11:08 -0800281 img = img_future.result()
282
283 if network_future is not None:
koder aka kdanilove21d7472015-02-14 19:02:04 -0800284 logger.debug("Waiting for network results")
koder aka kdanilov97644f92015-02-13 11:11:08 -0800285 nics = [{'net-id': network_future.result().id}]
286 else:
287 nics = None
288
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300289 names = []
290 for i in range(amount):
291 names.append(name_templ.format(group=group_name, id=i))
koder aka kdanilov97644f92015-02-13 11:11:08 -0800292
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800293 futures = []
koder aka kdanilov168f6092015-04-19 02:33:38 +0300294 logger.debug("Requesting new vm's")
koder aka kdanilov6e2ae792015-03-04 18:02:24 -0800295
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800296 for name, flt_ip in zip(names, ips):
297 params = (nova, name, keypair_name, img, fl,
298 nics, vol_sz, flt_ip, scheduler_hints,
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300299 flt_ip_pool, [security_group])
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800300
301 futures.append(executor.submit(create_vm, *params))
koder aka kdanilove21d7472015-02-14 19:02:04 -0800302 res = [future.result() for future in futures]
303 logger.debug("Done spawning")
304 return res
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800305
306
307def create_vm(nova, name, keypair_name, img,
308 fl, nics, vol_sz=None,
309 flt_ip=False,
310 scheduler_hints=None,
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300311 pool=None,
312 security_groups=None):
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800313 for i in range(3):
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800314 srv = nova.servers.create(name,
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300315 flavor=fl,
316 image=img,
317 nics=nics,
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800318 key_name=keypair_name,
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300319 scheduler_hints=scheduler_hints,
320 security_groups=security_groups)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800321
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800322 if not wait_for_server_active(nova, srv):
323 msg = "Server {0} fails to start. Kill it and try again"
koder aka kdanilove21d7472015-02-14 19:02:04 -0800324 logger.debug(msg.format(srv))
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800325 nova.servers.delete(srv)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800326
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300327 for j in range(120):
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800328 all_id = set(alive_srv.id for alive_srv in nova.servers.list())
329 if srv.id not in all_id:
330 break
331 time.sleep(1)
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300332 else:
333 raise RuntimeError("Server {0} delete timeout".format(srv.id))
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800334 else:
335 break
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300336 else:
337 raise RuntimeError("Failed to start server".format(srv.id))
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800338
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800339 if vol_sz is not None:
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800340 vol = create_volume(vol_sz, name)
koder aka kdanilove87ae652015-04-20 02:14:35 +0300341 nova.volumes.create_server_volume(srv.id, vol.id, None)
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800342
343 if flt_ip is Allocate:
344 flt_ip = nova.floating_ips.create(pool)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300345
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800346 if flt_ip is not None:
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800347 srv.add_floating_ip(flt_ip)
Yulia Portnova0e64ea22015-03-20 17:27:22 +0200348
koder aka kdanilovda45e882015-04-06 02:24:42 +0300349 return flt_ip.ip, nova.servers.get(srv.id)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800350
351
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300352def clear_nodes(nodes_ids):
353 clear_all(NOVA_CONNECTION, nodes_ids, None)
gstepanov023c1e42015-04-08 15:50:19 +0300354
355
koder aka kdanilove87ae652015-04-20 02:14:35 +0300356def clear_all(nova, ids=None, name_templ=None):
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300357
358 def need_delete(srv):
359 if name_templ is not None:
360 return re.match(name_templ.format("\\d+"), srv.name) is not None
361 else:
362 return srv.id in ids
363
koder aka kdanilove87ae652015-04-20 02:14:35 +0300364 volumes_to_delete = []
365 cinder = cinder_connect()
366 for vol in cinder.volumes.list():
367 for attachment in vol.attachments:
368 if attachment['server_id'] in ids:
369 volumes_to_delete.append(vol)
370 break
371
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800372 deleted_srvs = set()
373 for srv in nova.servers.list():
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300374 if need_delete(srv):
koder aka kdanilove21d7472015-02-14 19:02:04 -0800375 logger.debug("Deleting server {0}".format(srv.name))
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800376 nova.servers.delete(srv)
377 deleted_srvs.add(srv.id)
378
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300379 count = 0
380 while True:
381 if count % 60 == 0:
382 logger.debug("Waiting till all servers are actually deleted")
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800383 all_id = set(srv.id for srv in nova.servers.list())
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300384 if len(all_id.intersection(deleted_srvs)) == 0:
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800385 break
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300386 count += 1
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800387 time.sleep(1)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300388 logger.debug("Done, deleting volumes")
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800389
390 # wait till vm actually deleted
391
koder aka kdanilov6b1341a2015-04-21 22:44:21 +0300392 # logger.warning("Volume deletion commented out")
393 for vol in volumes_to_delete:
394 logger.debug("Deleting volume " + vol.display_name)
395 cinder.volumes.delete(vol)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800396
koder aka kdanilove21d7472015-02-14 19:02:04 -0800397 logger.debug("Clearing done (yet some volumes may still deleting)")