blob: 759d63b23c71f95287cd8b6f615953f1c490227a [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 kdanilovb7197432015-07-15 00:40:43 +03008import collections
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +03009from typing import Dict, Any, Iterable
koder aka kdanilov4643fd62015-02-10 16:20:13 -080010
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +030011from concurrent.futures import ThreadPoolExecutor
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -080012
koder aka kdanilov4500a5f2015-04-17 16:55:17 +030013from novaclient.exceptions import NotFound
koder aka kdanilov4643fd62015-02-10 16:20:13 -080014from novaclient.client import Client as n_client
15from cinderclient.v1.client import Client as c_client
16
koder aka kdanilov34052012015-08-27 18:32:11 +030017import wally
18from wally.discover import Node
19
20
koder aka kdanilova94dfe12015-08-19 13:04:51 +030021__doc__ = """
22Module used to reliably spawn set of VM's, evenly distributed across
23openstack cluster. Main functions:
24
25 get_OS_credentials - extract openstack credentials from different sources
26 nova_connect - connect to nova api
27 cinder_connect - connect to cinder api
28 find - find VM with given prefix in name
29 prepare_OS - prepare tenant for usage
30 launch_vms - reliably start set of VM in parallel with volumes and floating IP
31 clear_all - clear VM and volumes
32"""
33
koder aka kdanilov4643fd62015-02-10 16:20:13 -080034
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030035logger = logging.getLogger("wally.vms")
koder aka kdanilove21d7472015-02-14 19:02:04 -080036
37
koder aka kdanilove87ae652015-04-20 02:14:35 +030038STORED_OPENSTACK_CREDS = None
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030039NOVA_CONNECTION = None
koder aka kdanilove87ae652015-04-20 02:14:35 +030040CINDER_CONNECTION = None
41
42
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030043def is_connected() -> bool:
koder aka kdanilov416b87a2015-05-12 00:26:04 +030044 return NOVA_CONNECTION is not None
45
46
koder aka kdanilovb7197432015-07-15 00:40:43 +030047OSCreds = collections.namedtuple("OSCreds",
48 ["name", "passwd",
49 "tenant", "auth_url", "insecure"])
50
51
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030052def ostack_get_creds() -> OSCreds:
koder aka kdanilove87ae652015-04-20 02:14:35 +030053 if STORED_OPENSTACK_CREDS is None:
koder aka kdanilovb7197432015-07-15 00:40:43 +030054 return OSCreds(os.environ.get('OS_USERNAME'),
55 os.environ.get('OS_PASSWORD'),
56 os.environ.get('OS_TENANT_NAME'),
57 os.environ.get('OS_AUTH_URL'),
58 os.environ.get('OS_INSECURE', False))
koder aka kdanilove87ae652015-04-20 02:14:35 +030059 else:
60 return STORED_OPENSTACK_CREDS
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030061
62
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030063def nova_connect(os_creds: OSCreds=None) -> n_client:
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030064 global NOVA_CONNECTION
koder aka kdanilove87ae652015-04-20 02:14:35 +030065 global STORED_OPENSTACK_CREDS
66
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030067 if NOVA_CONNECTION is None:
koder aka kdanilovb7197432015-07-15 00:40:43 +030068 if os_creds is None:
69 os_creds = ostack_get_creds()
koder aka kdanilove87ae652015-04-20 02:14:35 +030070 else:
koder aka kdanilovb7197432015-07-15 00:40:43 +030071 STORED_OPENSTACK_CREDS = os_creds
koder aka kdanilove87ae652015-04-20 02:14:35 +030072
koder aka kdanilovb7197432015-07-15 00:40:43 +030073 NOVA_CONNECTION = n_client('1.1',
74 os_creds.name,
75 os_creds.passwd,
76 os_creds.tenant,
77 os_creds.auth_url,
78 insecure=os_creds.insecure)
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030079 return NOVA_CONNECTION
80
81
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030082def cinder_connect(os_creds: OSCreds=None) -> c_client:
koder aka kdanilove87ae652015-04-20 02:14:35 +030083 global CINDER_CONNECTION
84 global STORED_OPENSTACK_CREDS
85
86 if CINDER_CONNECTION is None:
koder aka kdanilovb7197432015-07-15 00:40:43 +030087 if os_creds is None:
88 os_creds = ostack_get_creds()
koder aka kdanilove87ae652015-04-20 02:14:35 +030089 else:
koder aka kdanilovb7197432015-07-15 00:40:43 +030090 STORED_OPENSTACK_CREDS = os_creds
91 CINDER_CONNECTION = c_client(os_creds.name,
92 os_creds.passwd,
93 os_creds.tenant,
94 os_creds.auth_url,
95 insecure=os_creds.insecure)
koder aka kdanilove87ae652015-04-20 02:14:35 +030096 return CINDER_CONNECTION
97
98
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030099def find_vms(nova: n_client, name_prefix: str) -> Iterable[str, int]:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300100 for srv in nova.servers.list():
101 if srv.name.startswith(name_prefix):
102 for ips in srv.addresses.values():
103 for ip in ips:
104 if ip.get("OS-EXT-IPS:type", None) == 'floating':
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300105 yield ip['addr'], srv.id
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300106 break
107
108
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300109def pause(ids: Iterable[int]) -> None:
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300110 def pause_vm(conn, vm_id):
111 vm = conn.servers.get(vm_id)
112 if vm.status == 'ACTIVE':
113 vm.pause()
114
115 conn = nova_connect()
116 with ThreadPoolExecutor(max_workers=16) as executor:
117 futures = [executor.submit(pause_vm, conn, vm_id)
118 for vm_id in ids]
119 for future in futures:
120 future.result()
121
122
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300123def unpause(ids: Iterable[int], max_resume_time=10) -> None:
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300124 def unpause(conn, vm_id):
125 vm = conn.servers.get(vm_id)
126 if vm.status == 'PAUSED':
127 vm.unpause()
128
129 for i in range(max_resume_time * 10):
130 vm = conn.servers.get(vm_id)
131 if vm.status != 'PAUSED':
132 return
133 time.sleep(0.1)
134 raise RuntimeError("Can't unpause vm {0}".format(vm_id))
135
136 conn = nova_connect()
137 with ThreadPoolExecutor(max_workers=16) as executor:
138 futures = [executor.submit(unpause, conn, vm_id)
139 for vm_id in ids]
140
141 for future in futures:
142 future.result()
143
144
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300145def prepare_os(nova: n_client, params: Dict[str, Any], os_creds: OSCreds) -> None:
koder aka kdanilov34052012015-08-27 18:32:11 +0300146 """prepare openstack for futher usage
147
148 Creates server groups, security rules, keypair, flavor
149 and upload VM image from web. In case if object with
150 given name already exists, skip preparation part.
151 Don't check, that existing object has required attributes
152
153 params:
154 nova: novaclient connection
155 params: dict {
156 security_group:str - security group name with allowed ssh and ping
157 aa_group_name:str - template for anti-affinity group names. Should
158 receive one integer parameter, like "cbt_aa_{0}"
159 keypair_name: str - OS keypair name
160 keypair_file_public: str - path to public key file
161 keypair_file_private: str - path to private key file
162
163 flavor:dict - flavor params
164 name, ram_size, hdd_size, cpu_count
165 as for novaclient.Client.flavor.create call
166
167 image:dict - image params
168 'name': image name
169 'url': image url
170 }
171 os_creds: OSCreds
172 max_vm_per_compute: int=8 maximum expected amount of VM, per
173 compute host. Used to create appropriate
174 count of server groups for even placement
175
176 returns: None
177 """
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300178 allow_ssh(nova, params['security_group'])
179
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300180 MAX_VM_PER_NODE = 8
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300181 serv_groups = map(params['aa_group_name'].format,
182 range(MAX_VM_PER_NODE))
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300183
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300184 for serv_groups in serv_groups:
185 get_or_create_aa_group(nova, serv_groups)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300186
187 create_keypair(nova,
188 params['keypair_name'],
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300189 params['keypair_file_public'],
190 params['keypair_file_private'])
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300191
koder aka kdanilov34052012015-08-27 18:32:11 +0300192 create_image(os_creds, nova, params['image']['name'],
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300193 params['image']['url'])
194
195 create_flavor(nova, **params['flavor'])
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +0300196
197
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300198def create_keypair(nova: n_client, name: str, pub_key_path: str, priv_key_path: str):
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300199 """create and upload keypair into nova, if doesn't exists yet
200
201 Create and upload keypair into nova, if keypair with given bane
202 doesn't exists yet. Uses key from files, if file doesn't exists -
203 create new keys, and store'em into files.
204
205 parameters:
206 nova: nova connection
207 name: str - ketpair name
208 pub_key_path: str - path for public key
209 priv_key_path: str - path for private key
210
211 returns: None
212 """
213
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300214 pub_key_exists = os.path.exists(pub_key_path)
215 priv_key_exists = os.path.exists(priv_key_path)
216
217 try:
218 kpair = nova.keypairs.find(name=name)
219 # if file not found- delete and recreate
220 except NotFound:
221 kpair = None
222
223 if pub_key_exists and not priv_key_exists:
224 raise EnvironmentError("Private key file doesn't exists")
225
226 if not pub_key_exists and priv_key_exists:
227 raise EnvironmentError("Public key file doesn't exists")
228
229 if kpair is None:
230 if pub_key_exists:
231 with open(pub_key_path) as pub_key_fd:
232 return nova.keypairs.create(name, pub_key_fd.read())
233 else:
234 key = nova.keypairs.create(name)
235
236 with open(priv_key_path, "w") as priv_key_fd:
237 priv_key_fd.write(key.private_key)
238 os.chmod(priv_key_path, stat.S_IREAD | stat.S_IWRITE)
239
240 with open(pub_key_path, "w") as pub_key_fd:
241 pub_key_fd.write(key.public_key)
242 elif not priv_key_exists:
243 raise EnvironmentError("Private key file doesn't exists," +
244 " but key uploaded openstack." +
245 " Either set correct path to private key" +
246 " or remove key from openstack")
247
248
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300249def get_or_create_aa_group(nova: n_client, name: str) -> int:
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300250 """create anti-affinity server group, if doesn't exists yet
251
252 parameters:
253 nova: nova connection
254 name: str - group name
255
256 returns: str - group id
257 """
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300258 try:
259 group = nova.server_groups.find(name=name)
260 except NotFound:
koder aka kdanilov34052012015-08-27 18:32:11 +0300261 group = nova.server_groups.create(name=name,
262 policies=['anti-affinity'])
koder aka kdanilov652cd802015-04-13 12:21:07 +0300263
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300264 return group.id
koder aka kdanilov652cd802015-04-13 12:21:07 +0300265
266
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300267def allow_ssh(nova: n_client, group_name: str) -> int:
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300268 """create sequrity group for ping and ssh
269
270 parameters:
271 nova: nova connection
272 group_name: str - group name
273
274 returns: str - group id
275 """
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300276 try:
277 secgroup = nova.security_groups.find(name=group_name)
278 except NotFound:
279 secgroup = nova.security_groups.create(group_name,
280 "allow ssh/ping to node")
281
koder aka kdanilov34052012015-08-27 18:32:11 +0300282 nova.security_group_rules.create(secgroup.id,
283 ip_protocol="tcp",
284 from_port="22",
285 to_port="22",
286 cidr="0.0.0.0/0")
koder aka kdanilov652cd802015-04-13 12:21:07 +0300287
koder aka kdanilov34052012015-08-27 18:32:11 +0300288 nova.security_group_rules.create(secgroup.id,
289 ip_protocol="icmp",
290 from_port=-1,
291 cidr="0.0.0.0/0",
292 to_port=-1)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300293 return secgroup.id
koder aka kdanilov652cd802015-04-13 12:21:07 +0300294
295
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300296def create_image(nova: n_client, os_creds: OSCreds, name: str, url: str):
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300297 """upload image into glance from given URL, if given image doesn't exisis yet
298
299 parameters:
300 nova: nova connection
301 os_creds: OSCreds object - openstack credentials, should be same,
302 as used when connectiong given novaclient
303 name: str - image name
304 url: str - image download url
305
306 returns: None
307 """
308 try:
309 nova.images.find(name=name)
310 return
311 except NotFound:
312 pass
313
314 tempnam = os.tempnam()
315
316 try:
317 urllib.urlretrieve(url, tempnam)
318
319 cmd = "OS_USERNAME={0.name}"
320 cmd += " OS_PASSWORD={0.passwd}"
321 cmd += " OS_TENANT_NAME={0.tenant}"
322 cmd += " OS_AUTH_URL={0.auth_url}"
323 cmd += " glance {1} image-create --name {2} $opts --file {3}"
324 cmd += " --disk-format qcow2 --container-format bare --is-public true"
325
326 cmd = cmd.format(os_creds,
327 '--insecure' if os_creds.insecure else "",
328 name,
329 tempnam)
330 finally:
331 if os.path.exists(tempnam):
332 os.unlink(tempnam)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300333
334
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300335def create_flavor(nova: n_client, name: str, ram_size: int, hdd_size: int, cpu_count: int):
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300336 """create flavor, if doesn't exisis yet
337
338 parameters:
339 nova: nova connection
340 name: str - flavor name
341 ram_size: int - ram size (UNIT?)
342 hdd_size: int - root hdd size (UNIT?)
343 cpu_count: int - cpu cores
344
345 returns: None
346 """
347 try:
348 nova.flavors.find(name)
349 return
350 except NotFound:
351 pass
352
353 nova.flavors.create(name, cpu_count, ram_size, hdd_size)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300354
355
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300356def create_volume(size: int, name: str):
koder aka kdanilove87ae652015-04-20 02:14:35 +0300357 cinder = cinder_connect()
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800358 vol = cinder.volumes.create(size=size, display_name=name)
359 err_count = 0
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300360
koder aka kdanilove87ae652015-04-20 02:14:35 +0300361 while vol.status != 'available':
362 if vol.status == 'error':
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800363 if err_count == 3:
koder aka kdanilove21d7472015-02-14 19:02:04 -0800364 logger.critical("Fail to create volume")
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800365 raise RuntimeError("Fail to create volume")
366 else:
367 err_count += 1
368 cinder.volumes.delete(vol)
369 time.sleep(1)
370 vol = cinder.volumes.create(size=size, display_name=name)
371 continue
372 time.sleep(1)
koder aka kdanilove87ae652015-04-20 02:14:35 +0300373 vol = cinder.volumes.get(vol.id)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800374 return vol
375
376
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300377def wait_for_server_active(nova: n_client, server, timeout: int=300)-> None:
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300378 """waiting till server became active
379
380 parameters:
381 nova: nova connection
382 server: server object
383 timeout: int - seconds to wait till raise an exception
384
385 returns: None
386 """
387
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800388 t = time.time()
389 while True:
koder aka kdanilov3f356262015-02-13 08:06:14 -0800390 time.sleep(1)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800391 sstate = getattr(server, 'OS-EXT-STS:vm_state').lower()
392
393 if sstate == 'active':
394 return True
395
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800396 if sstate == 'error':
397 return False
398
399 if time.time() - t > timeout:
400 return False
401
402 server = nova.servers.get(server)
403
404
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800405class Allocate(object):
406 pass
407
408
409def get_floating_ips(nova, pool, amount):
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300410 """allocate flationg ips
411
412 parameters:
413 nova: nova connection
414 pool:str floating ip pool name
415 amount:int - ip count
416
417 returns: [ip object]
418 """
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800419 ip_list = nova.floating_ips.list()
420
421 if pool is not None:
422 ip_list = [ip for ip in ip_list if ip.pool == pool]
423
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800424 return [ip for ip in ip_list if ip.instance_id is None][:amount]
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800425
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800426
koder aka kdanilov34052012015-08-27 18:32:11 +0300427def launch_vms(nova, params, already_has_count=0):
428 """launch virtual servers
429
430 Parameters:
431 nova: nova client
432 params: dict {
433 count: str or int - server count. If count is string it should be in
434 one of bext forms: "=INT" or "xINT". First mean
435 to spawn (INT - already_has_count) servers, and
436 all should be evenly distributed across all compute
437 nodes. xINT mean spawn COMPUTE_COUNT * INT servers.
438 image: dict {'name': str - image name}
439 flavor: dict {'name': str - flavor name}
440 group_name: str - group name, used to create uniq server name
441 keypair_name: str - ssh keypais name
442 keypair_file_private: str - path to private key
443 user: str - vm user name
444 vol_sz: int or None - volume size, or None, if no volume
445 network_zone_name: str - network zone name
446 flt_ip_pool: str - floating ip pool
447 name_templ: str - server name template, should receive two parameters
448 'group and id, like 'cbt-{group}-{id}'
449 aa_group_name: str scheduler group name
450 security_group: str - security group name
451 }
452 already_has_count: int=0 - how many servers already exists. Used to distribute
453 new servers evenly across all compute nodes, taking
454 old server in accout
455 returns: generator of str - server credentials, in format USER@IP:KEY_PATH
456
457 """
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300458 logger.debug("Calculating new vm count")
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300459 count = params['count']
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300460 nova = nova_connect()
461 lst = nova.services.list(binary='nova-compute')
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300462 srv_count = len([srv for srv in lst if srv.status == 'enabled'])
koder aka kdanilovda45e882015-04-06 02:24:42 +0300463
464 if isinstance(count, basestring):
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300465 if count.startswith("x"):
466 count = srv_count * int(count[1:])
467 else:
468 assert count.startswith('=')
469 count = int(count[1:]) - already_has_count
470
471 if count <= 0:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300472 logger.debug("Not need new vms")
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300473 return
koder aka kdanilovda45e882015-04-06 02:24:42 +0300474
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300475 logger.debug("Starting new nodes on openstack")
476
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300477 assert isinstance(count, (int, long))
478
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300479 srv_params = "img: {image[name]}, flavor: {flavor[name]}".format(**params)
koder aka kdanilov66839a92015-04-11 13:22:31 +0300480 msg_templ = "Will start {0} servers with next params: {1}"
koder aka kdanilovcee43342015-04-14 22:52:53 +0300481 logger.info(msg_templ.format(count, srv_params))
koder aka kdanilovda45e882015-04-06 02:24:42 +0300482
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300483 vm_params = dict(
484 img_name=params['image']['name'],
485 flavor_name=params['flavor']['name'],
486 group_name=params['group_name'],
487 keypair_name=params['keypair_name'],
488 vol_sz=params.get('vol_sz'),
489 network_zone_name=params.get("network_zone_name"),
490 flt_ip_pool=params.get('flt_ip_pool'),
491 name_templ=params.get('name_templ'),
492 scheduler_hints={"group": params['aa_group_name']},
493 security_group=params['security_group'],
494 sec_group_size=srv_count
495 )
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300496
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300497 # precache all errors before start creating vms
498 private_key_path = params['keypair_file_private']
499 creds = params['image']['creds']
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300500
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300501 for ip, os_node in create_vms_mt(NOVA_CONNECTION, count, **vm_params):
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300502 conn_uri = creds.format(ip=ip, private_key_path=private_key_path)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300503 yield Node(conn_uri, []), os_node.id
koder aka kdanilovda45e882015-04-06 02:24:42 +0300504
505
koder aka kdanilov34052012015-08-27 18:32:11 +0300506def get_free_server_grpoups(nova, template):
507 """get fre server groups, that match given name template
508
509 parameters:
510 nova: nova connection
511 template:str - name template
512 amount:int - ip count
513
514 returns: generator or str - server group names
515 """
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300516 for g in nova.server_groups.list():
517 if g.members == []:
518 if re.match(template, g.name):
Michael Semenov8ba6e232015-08-28 10:57:18 +0000519 yield str(g.id)
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300520
521
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300522def create_vms_mt(nova, amount, group_name, keypair_name, img_name,
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800523 flavor_name, vol_sz=None, network_zone_name=None,
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300524 flt_ip_pool=None, name_templ='wally-{id}',
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300525 scheduler_hints=None, security_group=None,
526 sec_group_size=None):
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800527
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800528 with ThreadPoolExecutor(max_workers=16) as executor:
koder aka kdanilov97644f92015-02-13 11:11:08 -0800529 if network_zone_name is not None:
530 network_future = executor.submit(nova.networks.find,
531 label=network_zone_name)
532 else:
533 network_future = None
534
535 fl_future = executor.submit(nova.flavors.find, name=flavor_name)
536 img_future = executor.submit(nova.images.find, name=img_name)
537
538 if flt_ip_pool is not None:
539 ips_future = executor.submit(get_floating_ips,
540 nova, flt_ip_pool, amount)
koder aka kdanilove21d7472015-02-14 19:02:04 -0800541 logger.debug("Wait for floating ip")
koder aka kdanilov97644f92015-02-13 11:11:08 -0800542 ips = ips_future.result()
543 ips += [Allocate] * (amount - len(ips))
544 else:
545 ips = [None] * amount
546
koder aka kdanilov7dec9df2015-02-15 21:35:19 -0800547 logger.debug("Getting flavor object")
koder aka kdanilov97644f92015-02-13 11:11:08 -0800548 fl = fl_future.result()
koder aka kdanilov7dec9df2015-02-15 21:35:19 -0800549 logger.debug("Getting image object")
koder aka kdanilov97644f92015-02-13 11:11:08 -0800550 img = img_future.result()
551
552 if network_future is not None:
koder aka kdanilove21d7472015-02-14 19:02:04 -0800553 logger.debug("Waiting for network results")
koder aka kdanilov97644f92015-02-13 11:11:08 -0800554 nics = [{'net-id': network_future.result().id}]
555 else:
556 nics = None
557
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300558 names = []
559 for i in range(amount):
560 names.append(name_templ.format(group=group_name, id=i))
koder aka kdanilov97644f92015-02-13 11:11:08 -0800561
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800562 futures = []
koder aka kdanilov168f6092015-04-19 02:33:38 +0300563 logger.debug("Requesting new vm's")
koder aka kdanilov6e2ae792015-03-04 18:02:24 -0800564
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300565 orig_scheduler_hints = scheduler_hints.copy()
566
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300567 MAX_SHED_GROUPS = 32
568 for start_idx in range(MAX_SHED_GROUPS):
569 pass
570
571 group_name_template = scheduler_hints['group'].format("\\d+")
572 groups = list(get_free_server_grpoups(nova, group_name_template + "$"))
573 groups.sort()
574
575 for idx, (name, flt_ip) in enumerate(zip(names, ips), 2):
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300576
577 scheduler_hints = None
578 if orig_scheduler_hints is not None and sec_group_size is not None:
579 if "group" in orig_scheduler_hints:
580 scheduler_hints = orig_scheduler_hints.copy()
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300581 scheduler_hints['group'] = groups[idx // sec_group_size]
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300582
583 if scheduler_hints is None:
584 scheduler_hints = orig_scheduler_hints.copy()
585
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800586 params = (nova, name, keypair_name, img, fl,
587 nics, vol_sz, flt_ip, scheduler_hints,
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300588 flt_ip_pool, [security_group])
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800589
590 futures.append(executor.submit(create_vm, *params))
koder aka kdanilove21d7472015-02-14 19:02:04 -0800591 res = [future.result() for future in futures]
592 logger.debug("Done spawning")
593 return res
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800594
595
596def create_vm(nova, name, keypair_name, img,
597 fl, nics, vol_sz=None,
598 flt_ip=False,
599 scheduler_hints=None,
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300600 pool=None,
601 security_groups=None):
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800602 for i in range(3):
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800603 srv = nova.servers.create(name,
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300604 flavor=fl,
605 image=img,
606 nics=nics,
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800607 key_name=keypair_name,
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300608 scheduler_hints=scheduler_hints,
609 security_groups=security_groups)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800610
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800611 if not wait_for_server_active(nova, srv):
612 msg = "Server {0} fails to start. Kill it and try again"
koder aka kdanilove21d7472015-02-14 19:02:04 -0800613 logger.debug(msg.format(srv))
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800614 nova.servers.delete(srv)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800615
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300616 try:
617 for j in range(120):
618 srv = nova.servers.get(srv.id)
619 time.sleep(1)
620 else:
621 msg = "Server {0} delete timeout".format(srv.id)
622 raise RuntimeError(msg)
623 except NotFound:
624 pass
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800625 else:
626 break
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300627 else:
628 raise RuntimeError("Failed to start server".format(srv.id))
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800629
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800630 if vol_sz is not None:
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800631 vol = create_volume(vol_sz, name)
koder aka kdanilove87ae652015-04-20 02:14:35 +0300632 nova.volumes.create_server_volume(srv.id, vol.id, None)
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800633
634 if flt_ip is Allocate:
635 flt_ip = nova.floating_ips.create(pool)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300636
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800637 if flt_ip is not None:
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800638 srv.add_floating_ip(flt_ip)
Yulia Portnova0e64ea22015-03-20 17:27:22 +0200639
koder aka kdanilovda45e882015-04-06 02:24:42 +0300640 return flt_ip.ip, nova.servers.get(srv.id)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800641
642
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300643def clear_nodes(nodes_ids):
644 clear_all(NOVA_CONNECTION, nodes_ids, None)
gstepanov023c1e42015-04-08 15:50:19 +0300645
646
koder aka kdanilov765920a2016-04-12 00:35:48 +0300647MAX_SERVER_DELETE_TIME = 120
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300648
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300649
koder aka kdanilov765920a2016-04-12 00:35:48 +0300650def clear_all(nova, ids=None, name_templ=None,
651 max_server_delete_time=MAX_SERVER_DELETE_TIME):
652 try:
653 def need_delete(srv):
654 if name_templ is not None:
655 return re.match(name_templ.format("\\d+"), srv.name) is not None
656 else:
657 return srv.id in ids
658
659 volumes_to_delete = []
660 cinder = cinder_connect()
661 for vol in cinder.volumes.list():
662 for attachment in vol.attachments:
663 if attachment['server_id'] in ids:
664 volumes_to_delete.append(vol)
665 break
666
667 deleted_srvs = set()
668 for srv in nova.servers.list():
669 if need_delete(srv):
670 logger.debug("Deleting server {0}".format(srv.name))
671 nova.servers.delete(srv)
672 deleted_srvs.add(srv.id)
673
674 count = 0
675 while count < max_server_delete_time:
676 if count % 60 == 0:
677 logger.debug("Waiting till all servers are actually deleted")
678 all_id = set(srv.id for srv in nova.servers.list())
679 if len(all_id.intersection(deleted_srvs)) == 0:
koder aka kdanilove87ae652015-04-20 02:14:35 +0300680 break
koder aka kdanilov765920a2016-04-12 00:35:48 +0300681 count += 1
682 time.sleep(1)
683 else:
684 logger.warning("Failed to remove servers. " +
685 "You, probably, need to remove them manually")
686 return
687 logger.debug("Done, deleting volumes")
koder aka kdanilove87ae652015-04-20 02:14:35 +0300688
koder aka kdanilov765920a2016-04-12 00:35:48 +0300689 # wait till vm actually deleted
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800690
koder aka kdanilov765920a2016-04-12 00:35:48 +0300691 # logger.warning("Volume deletion commented out")
692 for vol in volumes_to_delete:
693 logger.debug("Deleting volume " + vol.display_name)
694 cinder.volumes.delete(vol)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800695
koder aka kdanilov765920a2016-04-12 00:35:48 +0300696 logger.debug("Clearing done (yet some volumes may still deleting)")
697 except:
698 logger.exception("During removing servers. " +
699 "You, probably, need to remove them manually")