blob: 406b24d8dc3b0a7332d14b825f2e36467dc6c276 [file] [log] [blame]
koder aka kdanilov4643fd62015-02-10 16:20:13 -08001import re
2import os
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +03003import stat
koder aka kdanilov4643fd62015-02-10 16:20:13 -08004import time
koder aka kdanilova94dfe12015-08-19 13:04:51 +03005import urllib
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +03006import os.path
koder aka kdanilove21d7472015-02-14 19:02:04 -08007import logging
koder aka kdanilov76471642015-08-14 11:44:43 +03008import warnings
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +03009import subprocess
koder aka kdanilovb7197432015-07-15 00:40:43 +030010import collections
koder aka kdanilov4643fd62015-02-10 16:20:13 -080011
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +030012from concurrent.futures import ThreadPoolExecutor
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -080013
koder aka kdanilov4500a5f2015-04-17 16:55:17 +030014from novaclient.exceptions import NotFound
koder aka kdanilov4643fd62015-02-10 16:20:13 -080015from novaclient.client import Client as n_client
16from cinderclient.v1.client import Client as c_client
17
koder aka kdanilov34052012015-08-27 18:32:11 +030018import wally
19from wally.discover import Node
20
21
koder aka kdanilova94dfe12015-08-19 13:04:51 +030022__doc__ = """
23Module used to reliably spawn set of VM's, evenly distributed across
24openstack cluster. Main functions:
25
26 get_OS_credentials - extract openstack credentials from different sources
27 nova_connect - connect to nova api
28 cinder_connect - connect to cinder api
29 find - find VM with given prefix in name
30 prepare_OS - prepare tenant for usage
31 launch_vms - reliably start set of VM in parallel with volumes and floating IP
32 clear_all - clear VM and volumes
33"""
34
koder aka kdanilov4643fd62015-02-10 16:20:13 -080035
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030036logger = logging.getLogger("wally.vms")
koder aka kdanilove21d7472015-02-14 19:02:04 -080037
38
koder aka kdanilove87ae652015-04-20 02:14:35 +030039STORED_OPENSTACK_CREDS = None
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030040NOVA_CONNECTION = None
koder aka kdanilove87ae652015-04-20 02:14:35 +030041CINDER_CONNECTION = None
42
43
koder aka kdanilov416b87a2015-05-12 00:26:04 +030044def is_connected():
45 return NOVA_CONNECTION is not None
46
47
koder aka kdanilovb7197432015-07-15 00:40:43 +030048OSCreds = collections.namedtuple("OSCreds",
49 ["name", "passwd",
50 "tenant", "auth_url", "insecure"])
51
52
koder aka kdanilove87ae652015-04-20 02:14:35 +030053def ostack_get_creds():
54 if STORED_OPENSTACK_CREDS is None:
koder aka kdanilovb7197432015-07-15 00:40:43 +030055 return OSCreds(os.environ.get('OS_USERNAME'),
56 os.environ.get('OS_PASSWORD'),
57 os.environ.get('OS_TENANT_NAME'),
58 os.environ.get('OS_AUTH_URL'),
59 os.environ.get('OS_INSECURE', False))
koder aka kdanilove87ae652015-04-20 02:14:35 +030060 else:
61 return STORED_OPENSTACK_CREDS
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030062
63
koder aka kdanilovb7197432015-07-15 00:40:43 +030064def nova_connect(os_creds=None):
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030065 global NOVA_CONNECTION
koder aka kdanilove87ae652015-04-20 02:14:35 +030066 global STORED_OPENSTACK_CREDS
67
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030068 if NOVA_CONNECTION is None:
koder aka kdanilovb7197432015-07-15 00:40:43 +030069 if os_creds is None:
70 os_creds = ostack_get_creds()
koder aka kdanilove87ae652015-04-20 02:14:35 +030071 else:
koder aka kdanilovb7197432015-07-15 00:40:43 +030072 STORED_OPENSTACK_CREDS = os_creds
koder aka kdanilove87ae652015-04-20 02:14:35 +030073
koder aka kdanilovb7197432015-07-15 00:40:43 +030074 NOVA_CONNECTION = n_client('1.1',
75 os_creds.name,
76 os_creds.passwd,
77 os_creds.tenant,
78 os_creds.auth_url,
79 insecure=os_creds.insecure)
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030080 return NOVA_CONNECTION
81
82
koder aka kdanilovb7197432015-07-15 00:40:43 +030083def cinder_connect(os_creds=None):
koder aka kdanilove87ae652015-04-20 02:14:35 +030084 global CINDER_CONNECTION
85 global STORED_OPENSTACK_CREDS
86
87 if CINDER_CONNECTION is None:
koder aka kdanilovb7197432015-07-15 00:40:43 +030088 if os_creds is None:
89 os_creds = ostack_get_creds()
koder aka kdanilove87ae652015-04-20 02:14:35 +030090 else:
koder aka kdanilovb7197432015-07-15 00:40:43 +030091 STORED_OPENSTACK_CREDS = os_creds
92 CINDER_CONNECTION = c_client(os_creds.name,
93 os_creds.passwd,
94 os_creds.tenant,
95 os_creds.auth_url,
96 insecure=os_creds.insecure)
koder aka kdanilove87ae652015-04-20 02:14:35 +030097 return CINDER_CONNECTION
98
99
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300100def prepare_os_subpr(nova, params, os_creds, max_vm_per_compute=8):
koder aka kdanilovb7197432015-07-15 00:40:43 +0300101 if os_creds is None:
102 os_creds = ostack_get_creds()
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +0300103
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300104 serv_groups = " ".join(map(params['aa_group_name'].format,
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300105 range(max_vm_per_compute)))
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +0300106
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300107 image_name = params['image']['name']
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300108 env = os.environ.copy()
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300109
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300110 env.update(dict(
koder aka kdanilovb7197432015-07-15 00:40:43 +0300111 OS_USERNAME=os_creds.name,
112 OS_PASSWORD=os_creds.passwd,
113 OS_TENANT_NAME=os_creds.tenant,
114 OS_AUTH_URL=os_creds.auth_url,
115 OS_INSECURE="1" if os_creds.insecure else "0",
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +0300116
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300117 FLAVOR_NAME=params['flavor']['name'],
118 FLAVOR_RAM=str(params['flavor']['ram_size']),
119 FLAVOR_HDD=str(params['flavor']['hdd_size']),
120 FLAVOR_CPU_COUNT=str(params['flavor']['cpu_count']),
121
122 SERV_GROUPS=serv_groups,
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300123
124 SECGROUP=params['security_group'],
125
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300126 IMAGE_NAME=image_name,
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300127 IMAGE_URL=params['image']['url'],
128 ))
129
130 spath = os.path.dirname(os.path.dirname(wally.__file__))
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300131 spath = os.path.join(spath, 'scripts/prepare.sh')
132
koder aka kdanilov76471642015-08-14 11:44:43 +0300133 with warnings.catch_warnings():
134 warnings.simplefilter("ignore")
135 fname = os.tempnam()
136
137 cmd = "bash {spath} >{fname} 2>&1".format(spath=spath, fname=fname)
138 try:
139 subprocess.check_call(cmd, shell=True, env=env)
140 except:
141 logger.error("Prepare failed. Logs in " + fname)
142 with open(fname) as fd:
143 logger.error("Message:\n " + fd.read().replace("\n", "\n "))
144 raise
145 os.unlink(fname)
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +0300146
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300147 while True:
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300148 status = nova.images.find(name=image_name).status
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300149 if status == 'ACTIVE':
150 break
151 msg = "Image {0} is still in {1} state. Waiting 10 more seconds"
koder aka kdanilov6ab4d432015-06-22 00:26:28 +0300152 logger.info(msg.format(image_name, status))
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300153 time.sleep(10)
154
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300155 create_keypair(nova,
156 params['keypair_name'],
157 params['keypair_file_public'],
158 params['keypair_file_private'])
159
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300160
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300161def find_vms(nova, name_prefix):
162 for srv in nova.servers.list():
163 if srv.name.startswith(name_prefix):
164 for ips in srv.addresses.values():
165 for ip in ips:
166 if ip.get("OS-EXT-IPS:type", None) == 'floating':
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300167 yield ip['addr'], srv.id
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300168 break
169
170
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300171def pause(ids):
172 def pause_vm(conn, vm_id):
173 vm = conn.servers.get(vm_id)
174 if vm.status == 'ACTIVE':
175 vm.pause()
176
177 conn = nova_connect()
178 with ThreadPoolExecutor(max_workers=16) as executor:
179 futures = [executor.submit(pause_vm, conn, vm_id)
180 for vm_id in ids]
181 for future in futures:
182 future.result()
183
184
185def unpause(ids, max_resume_time=10):
186 def unpause(conn, vm_id):
187 vm = conn.servers.get(vm_id)
188 if vm.status == 'PAUSED':
189 vm.unpause()
190
191 for i in range(max_resume_time * 10):
192 vm = conn.servers.get(vm_id)
193 if vm.status != 'PAUSED':
194 return
195 time.sleep(0.1)
196 raise RuntimeError("Can't unpause vm {0}".format(vm_id))
197
198 conn = nova_connect()
199 with ThreadPoolExecutor(max_workers=16) as executor:
200 futures = [executor.submit(unpause, conn, vm_id)
201 for vm_id in ids]
202
203 for future in futures:
204 future.result()
205
206
koder aka kdanilov34052012015-08-27 18:32:11 +0300207def prepare_os(nova, params, os_creds, max_vm_per_compute=8):
208 """prepare openstack for futher usage
209
210 Creates server groups, security rules, keypair, flavor
211 and upload VM image from web. In case if object with
212 given name already exists, skip preparation part.
213 Don't check, that existing object has required attributes
214
215 params:
216 nova: novaclient connection
217 params: dict {
218 security_group:str - security group name with allowed ssh and ping
219 aa_group_name:str - template for anti-affinity group names. Should
220 receive one integer parameter, like "cbt_aa_{0}"
221 keypair_name: str - OS keypair name
222 keypair_file_public: str - path to public key file
223 keypair_file_private: str - path to private key file
224
225 flavor:dict - flavor params
226 name, ram_size, hdd_size, cpu_count
227 as for novaclient.Client.flavor.create call
228
229 image:dict - image params
230 'name': image name
231 'url': image url
232 }
233 os_creds: OSCreds
234 max_vm_per_compute: int=8 maximum expected amount of VM, per
235 compute host. Used to create appropriate
236 count of server groups for even placement
237
238 returns: None
239 """
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300240 allow_ssh(nova, params['security_group'])
241
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300242 MAX_VM_PER_NODE = 8
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300243 serv_groups = map(params['aa_group_name'].format,
244 range(MAX_VM_PER_NODE))
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300245
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300246 for serv_groups in serv_groups:
247 get_or_create_aa_group(nova, serv_groups)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300248
249 create_keypair(nova,
250 params['keypair_name'],
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300251 params['keypair_file_public'],
252 params['keypair_file_private'])
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300253
koder aka kdanilov34052012015-08-27 18:32:11 +0300254 create_image(os_creds, nova, params['image']['name'],
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300255 params['image']['url'])
256
257 create_flavor(nova, **params['flavor'])
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +0300258
259
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300260def create_keypair(nova, name, pub_key_path, priv_key_path):
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300261 """create and upload keypair into nova, if doesn't exists yet
262
263 Create and upload keypair into nova, if keypair with given bane
264 doesn't exists yet. Uses key from files, if file doesn't exists -
265 create new keys, and store'em into files.
266
267 parameters:
268 nova: nova connection
269 name: str - ketpair name
270 pub_key_path: str - path for public key
271 priv_key_path: str - path for private key
272
273 returns: None
274 """
275
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300276 pub_key_exists = os.path.exists(pub_key_path)
277 priv_key_exists = os.path.exists(priv_key_path)
278
279 try:
280 kpair = nova.keypairs.find(name=name)
281 # if file not found- delete and recreate
282 except NotFound:
283 kpair = None
284
285 if pub_key_exists and not priv_key_exists:
286 raise EnvironmentError("Private key file doesn't exists")
287
288 if not pub_key_exists and priv_key_exists:
289 raise EnvironmentError("Public key file doesn't exists")
290
291 if kpair is None:
292 if pub_key_exists:
293 with open(pub_key_path) as pub_key_fd:
294 return nova.keypairs.create(name, pub_key_fd.read())
295 else:
296 key = nova.keypairs.create(name)
297
298 with open(priv_key_path, "w") as priv_key_fd:
299 priv_key_fd.write(key.private_key)
300 os.chmod(priv_key_path, stat.S_IREAD | stat.S_IWRITE)
301
302 with open(pub_key_path, "w") as pub_key_fd:
303 pub_key_fd.write(key.public_key)
304 elif not priv_key_exists:
305 raise EnvironmentError("Private key file doesn't exists," +
306 " but key uploaded openstack." +
307 " Either set correct path to private key" +
308 " or remove key from openstack")
309
310
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300311def get_or_create_aa_group(nova, name):
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300312 """create anti-affinity server group, if doesn't exists yet
313
314 parameters:
315 nova: nova connection
316 name: str - group name
317
318 returns: str - group id
319 """
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300320 try:
321 group = nova.server_groups.find(name=name)
322 except NotFound:
koder aka kdanilov34052012015-08-27 18:32:11 +0300323 group = nova.server_groups.create(name=name,
324 policies=['anti-affinity'])
koder aka kdanilov652cd802015-04-13 12:21:07 +0300325
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300326 return group.id
koder aka kdanilov652cd802015-04-13 12:21:07 +0300327
328
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300329def allow_ssh(nova, group_name):
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300330 """create sequrity group for ping and ssh
331
332 parameters:
333 nova: nova connection
334 group_name: str - group name
335
336 returns: str - group id
337 """
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300338 try:
339 secgroup = nova.security_groups.find(name=group_name)
340 except NotFound:
341 secgroup = nova.security_groups.create(group_name,
342 "allow ssh/ping to node")
343
koder aka kdanilov34052012015-08-27 18:32:11 +0300344 nova.security_group_rules.create(secgroup.id,
345 ip_protocol="tcp",
346 from_port="22",
347 to_port="22",
348 cidr="0.0.0.0/0")
koder aka kdanilov652cd802015-04-13 12:21:07 +0300349
koder aka kdanilov34052012015-08-27 18:32:11 +0300350 nova.security_group_rules.create(secgroup.id,
351 ip_protocol="icmp",
352 from_port=-1,
353 cidr="0.0.0.0/0",
354 to_port=-1)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300355 return secgroup.id
koder aka kdanilov652cd802015-04-13 12:21:07 +0300356
357
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300358def create_image(nova, os_creds, name, url):
359 """upload image into glance from given URL, if given image doesn't exisis yet
360
361 parameters:
362 nova: nova connection
363 os_creds: OSCreds object - openstack credentials, should be same,
364 as used when connectiong given novaclient
365 name: str - image name
366 url: str - image download url
367
368 returns: None
369 """
370 try:
371 nova.images.find(name=name)
372 return
373 except NotFound:
374 pass
375
376 tempnam = os.tempnam()
377
378 try:
379 urllib.urlretrieve(url, tempnam)
380
381 cmd = "OS_USERNAME={0.name}"
382 cmd += " OS_PASSWORD={0.passwd}"
383 cmd += " OS_TENANT_NAME={0.tenant}"
384 cmd += " OS_AUTH_URL={0.auth_url}"
385 cmd += " glance {1} image-create --name {2} $opts --file {3}"
386 cmd += " --disk-format qcow2 --container-format bare --is-public true"
387
388 cmd = cmd.format(os_creds,
389 '--insecure' if os_creds.insecure else "",
390 name,
391 tempnam)
392 finally:
393 if os.path.exists(tempnam):
394 os.unlink(tempnam)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300395
396
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300397def create_flavor(nova, name, ram_size, hdd_size, cpu_count):
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300398 """create flavor, if doesn't exisis yet
399
400 parameters:
401 nova: nova connection
402 name: str - flavor name
403 ram_size: int - ram size (UNIT?)
404 hdd_size: int - root hdd size (UNIT?)
405 cpu_count: int - cpu cores
406
407 returns: None
408 """
409 try:
410 nova.flavors.find(name)
411 return
412 except NotFound:
413 pass
414
415 nova.flavors.create(name, cpu_count, ram_size, hdd_size)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300416
417
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800418def create_volume(size, name):
koder aka kdanilove87ae652015-04-20 02:14:35 +0300419 cinder = cinder_connect()
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800420 vol = cinder.volumes.create(size=size, display_name=name)
421 err_count = 0
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300422
koder aka kdanilove87ae652015-04-20 02:14:35 +0300423 while vol.status != 'available':
424 if vol.status == 'error':
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800425 if err_count == 3:
koder aka kdanilove21d7472015-02-14 19:02:04 -0800426 logger.critical("Fail to create volume")
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800427 raise RuntimeError("Fail to create volume")
428 else:
429 err_count += 1
430 cinder.volumes.delete(vol)
431 time.sleep(1)
432 vol = cinder.volumes.create(size=size, display_name=name)
433 continue
434 time.sleep(1)
koder aka kdanilove87ae652015-04-20 02:14:35 +0300435 vol = cinder.volumes.get(vol.id)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800436 return vol
437
438
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300439def wait_for_server_active(nova, server, timeout=300):
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300440 """waiting till server became active
441
442 parameters:
443 nova: nova connection
444 server: server object
445 timeout: int - seconds to wait till raise an exception
446
447 returns: None
448 """
449
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800450 t = time.time()
451 while True:
koder aka kdanilov3f356262015-02-13 08:06:14 -0800452 time.sleep(1)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800453 sstate = getattr(server, 'OS-EXT-STS:vm_state').lower()
454
455 if sstate == 'active':
456 return True
457
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800458 if sstate == 'error':
459 return False
460
461 if time.time() - t > timeout:
462 return False
463
464 server = nova.servers.get(server)
465
466
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800467class Allocate(object):
468 pass
469
470
471def get_floating_ips(nova, pool, amount):
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300472 """allocate flationg ips
473
474 parameters:
475 nova: nova connection
476 pool:str floating ip pool name
477 amount:int - ip count
478
479 returns: [ip object]
480 """
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800481 ip_list = nova.floating_ips.list()
482
483 if pool is not None:
484 ip_list = [ip for ip in ip_list if ip.pool == pool]
485
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800486 return [ip for ip in ip_list if ip.instance_id is None][:amount]
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800487
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800488
koder aka kdanilov34052012015-08-27 18:32:11 +0300489def launch_vms(nova, params, already_has_count=0):
490 """launch virtual servers
491
492 Parameters:
493 nova: nova client
494 params: dict {
495 count: str or int - server count. If count is string it should be in
496 one of bext forms: "=INT" or "xINT". First mean
497 to spawn (INT - already_has_count) servers, and
498 all should be evenly distributed across all compute
499 nodes. xINT mean spawn COMPUTE_COUNT * INT servers.
500 image: dict {'name': str - image name}
501 flavor: dict {'name': str - flavor name}
502 group_name: str - group name, used to create uniq server name
503 keypair_name: str - ssh keypais name
504 keypair_file_private: str - path to private key
505 user: str - vm user name
506 vol_sz: int or None - volume size, or None, if no volume
507 network_zone_name: str - network zone name
508 flt_ip_pool: str - floating ip pool
509 name_templ: str - server name template, should receive two parameters
510 'group and id, like 'cbt-{group}-{id}'
511 aa_group_name: str scheduler group name
512 security_group: str - security group name
513 }
514 already_has_count: int=0 - how many servers already exists. Used to distribute
515 new servers evenly across all compute nodes, taking
516 old server in accout
517 returns: generator of str - server credentials, in format USER@IP:KEY_PATH
518
519 """
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300520 logger.debug("Calculating new vm count")
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300521 count = params['count']
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300522 nova = nova_connect()
523 lst = nova.services.list(binary='nova-compute')
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300524 srv_count = len([srv for srv in lst if srv.status == 'enabled'])
koder aka kdanilovda45e882015-04-06 02:24:42 +0300525
526 if isinstance(count, basestring):
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300527 if count.startswith("x"):
528 count = srv_count * int(count[1:])
529 else:
530 assert count.startswith('=')
531 count = int(count[1:]) - already_has_count
532
533 if count <= 0:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300534 logger.debug("Not need new vms")
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300535 return
koder aka kdanilovda45e882015-04-06 02:24:42 +0300536
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300537 logger.debug("Starting new nodes on openstack")
538
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300539 assert isinstance(count, (int, long))
540
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300541 srv_params = "img: {image[name]}, flavor: {flavor[name]}".format(**params)
koder aka kdanilov66839a92015-04-11 13:22:31 +0300542 msg_templ = "Will start {0} servers with next params: {1}"
koder aka kdanilovcee43342015-04-14 22:52:53 +0300543 logger.info(msg_templ.format(count, srv_params))
koder aka kdanilovda45e882015-04-06 02:24:42 +0300544
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300545 vm_params = dict(
546 img_name=params['image']['name'],
547 flavor_name=params['flavor']['name'],
548 group_name=params['group_name'],
549 keypair_name=params['keypair_name'],
550 vol_sz=params.get('vol_sz'),
551 network_zone_name=params.get("network_zone_name"),
552 flt_ip_pool=params.get('flt_ip_pool'),
553 name_templ=params.get('name_templ'),
554 scheduler_hints={"group": params['aa_group_name']},
555 security_group=params['security_group'],
556 sec_group_size=srv_count
557 )
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300558
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300559 # precache all errors before start creating vms
560 private_key_path = params['keypair_file_private']
561 creds = params['image']['creds']
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300562
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300563 for ip, os_node in create_vms_mt(NOVA_CONNECTION, count, **vm_params):
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300564 conn_uri = creds.format(ip=ip, private_key_path=private_key_path)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300565 yield Node(conn_uri, []), os_node.id
koder aka kdanilovda45e882015-04-06 02:24:42 +0300566
567
koder aka kdanilov34052012015-08-27 18:32:11 +0300568def get_free_server_grpoups(nova, template):
569 """get fre server groups, that match given name template
570
571 parameters:
572 nova: nova connection
573 template:str - name template
574 amount:int - ip count
575
576 returns: generator or str - server group names
577 """
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300578 for g in nova.server_groups.list():
579 if g.members == []:
580 if re.match(template, g.name):
Michael Semenov8ba6e232015-08-28 10:57:18 +0000581 yield str(g.id)
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300582
583
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300584def create_vms_mt(nova, amount, group_name, keypair_name, img_name,
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800585 flavor_name, vol_sz=None, network_zone_name=None,
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300586 flt_ip_pool=None, name_templ='wally-{id}',
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300587 scheduler_hints=None, security_group=None,
588 sec_group_size=None):
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800589
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800590 with ThreadPoolExecutor(max_workers=16) as executor:
koder aka kdanilov97644f92015-02-13 11:11:08 -0800591 if network_zone_name is not None:
592 network_future = executor.submit(nova.networks.find,
593 label=network_zone_name)
594 else:
595 network_future = None
596
597 fl_future = executor.submit(nova.flavors.find, name=flavor_name)
598 img_future = executor.submit(nova.images.find, name=img_name)
599
600 if flt_ip_pool is not None:
601 ips_future = executor.submit(get_floating_ips,
602 nova, flt_ip_pool, amount)
koder aka kdanilove21d7472015-02-14 19:02:04 -0800603 logger.debug("Wait for floating ip")
koder aka kdanilov97644f92015-02-13 11:11:08 -0800604 ips = ips_future.result()
605 ips += [Allocate] * (amount - len(ips))
606 else:
607 ips = [None] * amount
608
koder aka kdanilov7dec9df2015-02-15 21:35:19 -0800609 logger.debug("Getting flavor object")
koder aka kdanilov97644f92015-02-13 11:11:08 -0800610 fl = fl_future.result()
koder aka kdanilov7dec9df2015-02-15 21:35:19 -0800611 logger.debug("Getting image object")
koder aka kdanilov97644f92015-02-13 11:11:08 -0800612 img = img_future.result()
613
614 if network_future is not None:
koder aka kdanilove21d7472015-02-14 19:02:04 -0800615 logger.debug("Waiting for network results")
koder aka kdanilov97644f92015-02-13 11:11:08 -0800616 nics = [{'net-id': network_future.result().id}]
617 else:
618 nics = None
619
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300620 names = []
621 for i in range(amount):
622 names.append(name_templ.format(group=group_name, id=i))
koder aka kdanilov97644f92015-02-13 11:11:08 -0800623
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800624 futures = []
koder aka kdanilov168f6092015-04-19 02:33:38 +0300625 logger.debug("Requesting new vm's")
koder aka kdanilov6e2ae792015-03-04 18:02:24 -0800626
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300627 orig_scheduler_hints = scheduler_hints.copy()
628
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300629 MAX_SHED_GROUPS = 32
630 for start_idx in range(MAX_SHED_GROUPS):
631 pass
632
633 group_name_template = scheduler_hints['group'].format("\\d+")
634 groups = list(get_free_server_grpoups(nova, group_name_template + "$"))
635 groups.sort()
636
637 for idx, (name, flt_ip) in enumerate(zip(names, ips), 2):
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300638
639 scheduler_hints = None
640 if orig_scheduler_hints is not None and sec_group_size is not None:
641 if "group" in orig_scheduler_hints:
642 scheduler_hints = orig_scheduler_hints.copy()
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300643 scheduler_hints['group'] = groups[idx // sec_group_size]
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300644
645 if scheduler_hints is None:
646 scheduler_hints = orig_scheduler_hints.copy()
647
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800648 params = (nova, name, keypair_name, img, fl,
649 nics, vol_sz, flt_ip, scheduler_hints,
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300650 flt_ip_pool, [security_group])
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800651
652 futures.append(executor.submit(create_vm, *params))
koder aka kdanilove21d7472015-02-14 19:02:04 -0800653 res = [future.result() for future in futures]
654 logger.debug("Done spawning")
655 return res
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800656
657
658def create_vm(nova, name, keypair_name, img,
659 fl, nics, vol_sz=None,
660 flt_ip=False,
661 scheduler_hints=None,
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300662 pool=None,
663 security_groups=None):
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800664 for i in range(3):
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800665 srv = nova.servers.create(name,
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300666 flavor=fl,
667 image=img,
668 nics=nics,
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800669 key_name=keypair_name,
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300670 scheduler_hints=scheduler_hints,
671 security_groups=security_groups)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800672
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800673 if not wait_for_server_active(nova, srv):
674 msg = "Server {0} fails to start. Kill it and try again"
koder aka kdanilove21d7472015-02-14 19:02:04 -0800675 logger.debug(msg.format(srv))
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800676 nova.servers.delete(srv)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800677
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300678 try:
679 for j in range(120):
680 srv = nova.servers.get(srv.id)
681 time.sleep(1)
682 else:
683 msg = "Server {0} delete timeout".format(srv.id)
684 raise RuntimeError(msg)
685 except NotFound:
686 pass
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800687 else:
688 break
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300689 else:
690 raise RuntimeError("Failed to start server".format(srv.id))
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800691
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800692 if vol_sz is not None:
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800693 vol = create_volume(vol_sz, name)
koder aka kdanilove87ae652015-04-20 02:14:35 +0300694 nova.volumes.create_server_volume(srv.id, vol.id, None)
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800695
696 if flt_ip is Allocate:
697 flt_ip = nova.floating_ips.create(pool)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300698
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800699 if flt_ip is not None:
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800700 srv.add_floating_ip(flt_ip)
Yulia Portnova0e64ea22015-03-20 17:27:22 +0200701
koder aka kdanilovda45e882015-04-06 02:24:42 +0300702 return flt_ip.ip, nova.servers.get(srv.id)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800703
704
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300705def clear_nodes(nodes_ids):
706 clear_all(NOVA_CONNECTION, nodes_ids, None)
gstepanov023c1e42015-04-08 15:50:19 +0300707
708
koder aka kdanilove87ae652015-04-20 02:14:35 +0300709def clear_all(nova, ids=None, name_templ=None):
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300710
711 def need_delete(srv):
712 if name_templ is not None:
713 return re.match(name_templ.format("\\d+"), srv.name) is not None
714 else:
715 return srv.id in ids
716
koder aka kdanilove87ae652015-04-20 02:14:35 +0300717 volumes_to_delete = []
718 cinder = cinder_connect()
719 for vol in cinder.volumes.list():
720 for attachment in vol.attachments:
721 if attachment['server_id'] in ids:
722 volumes_to_delete.append(vol)
723 break
724
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800725 deleted_srvs = set()
726 for srv in nova.servers.list():
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300727 if need_delete(srv):
koder aka kdanilove21d7472015-02-14 19:02:04 -0800728 logger.debug("Deleting server {0}".format(srv.name))
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800729 nova.servers.delete(srv)
730 deleted_srvs.add(srv.id)
731
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300732 count = 0
733 while True:
734 if count % 60 == 0:
735 logger.debug("Waiting till all servers are actually deleted")
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800736 all_id = set(srv.id for srv in nova.servers.list())
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300737 if len(all_id.intersection(deleted_srvs)) == 0:
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800738 break
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300739 count += 1
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800740 time.sleep(1)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300741 logger.debug("Done, deleting volumes")
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800742
743 # wait till vm actually deleted
744
koder aka kdanilov6b1341a2015-04-21 22:44:21 +0300745 # logger.warning("Volume deletion commented out")
746 for vol in volumes_to_delete:
747 logger.debug("Deleting volume " + vol.display_name)
748 cinder.volumes.delete(vol)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800749
koder aka kdanilove21d7472015-02-14 19:02:04 -0800750 logger.debug("Clearing done (yet some volumes may still deleting)")