blob: 075f348ad88a3e7e939d46ff726f9b41880a993a [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 kdanilov4643fd62015-02-10 16:20:13 -08008
koder aka kdanilov22d134e2016-11-08 11:33:19 +02009from typing import Dict, Any, Iterable, Generator, NamedTuple
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +030010from concurrent.futures import ThreadPoolExecutor
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -080011
koder aka kdanilov4500a5f2015-04-17 16:55:17 +030012from novaclient.exceptions import NotFound
koder aka kdanilov4643fd62015-02-10 16:20:13 -080013from novaclient.client import Client as n_client
14from cinderclient.v1.client import Client as c_client
15
koder aka kdanilov22d134e2016-11-08 11:33:19 +020016from .inode import NodeInfo
koder aka kdanilov34052012015-08-27 18:32:11 +030017
koder aka kdanilova94dfe12015-08-19 13:04:51 +030018__doc__ = """
19Module used to reliably spawn set of VM's, evenly distributed across
20openstack cluster. Main functions:
21
22 get_OS_credentials - extract openstack credentials from different sources
23 nova_connect - connect to nova api
24 cinder_connect - connect to cinder api
25 find - find VM with given prefix in name
26 prepare_OS - prepare tenant for usage
27 launch_vms - reliably start set of VM in parallel with volumes and floating IP
28 clear_all - clear VM and volumes
29"""
30
koder aka kdanilov4643fd62015-02-10 16:20:13 -080031
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030032logger = logging.getLogger("wally.vms")
koder aka kdanilove21d7472015-02-14 19:02:04 -080033
34
koder aka kdanilove87ae652015-04-20 02:14:35 +030035STORED_OPENSTACK_CREDS = None
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030036NOVA_CONNECTION = None
koder aka kdanilove87ae652015-04-20 02:14:35 +030037CINDER_CONNECTION = None
38
39
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030040def is_connected() -> bool:
koder aka kdanilov416b87a2015-05-12 00:26:04 +030041 return NOVA_CONNECTION is not None
42
43
koder aka kdanilov22d134e2016-11-08 11:33:19 +020044OSCreds = NamedTuple("OSCreds",
45 [("name", str),
46 ("passwd", str),
47 ("tenant", str),
48 ("auth_url", str),
49 ("insecure", bool)])
koder aka kdanilovb7197432015-07-15 00:40:43 +030050
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 kdanilov22d134e2016-11-08 11:33:19 +020054 is_insecure = \
55 os.environ.get('OS_INSECURE', 'False').lower() in ('true', 'yes')
koder aka kdanilovb7197432015-07-15 00:40:43 +030056 return OSCreds(os.environ.get('OS_USERNAME'),
57 os.environ.get('OS_PASSWORD'),
58 os.environ.get('OS_TENANT_NAME'),
59 os.environ.get('OS_AUTH_URL'),
koder aka kdanilov22d134e2016-11-08 11:33:19 +020060 is_insecure)
koder aka kdanilove87ae652015-04-20 02:14:35 +030061 else:
62 return STORED_OPENSTACK_CREDS
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030063
64
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030065def nova_connect(os_creds: OSCreds=None) -> n_client:
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030066 global NOVA_CONNECTION
koder aka kdanilove87ae652015-04-20 02:14:35 +030067 global STORED_OPENSTACK_CREDS
68
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030069 if NOVA_CONNECTION is None:
koder aka kdanilovb7197432015-07-15 00:40:43 +030070 if os_creds is None:
71 os_creds = ostack_get_creds()
koder aka kdanilove87ae652015-04-20 02:14:35 +030072 else:
koder aka kdanilovb7197432015-07-15 00:40:43 +030073 STORED_OPENSTACK_CREDS = os_creds
koder aka kdanilove87ae652015-04-20 02:14:35 +030074
koder aka kdanilovb7197432015-07-15 00:40:43 +030075 NOVA_CONNECTION = n_client('1.1',
76 os_creds.name,
77 os_creds.passwd,
78 os_creds.tenant,
79 os_creds.auth_url,
80 insecure=os_creds.insecure)
koder aka kdanilov1c2b5112015-04-10 16:53:51 +030081 return NOVA_CONNECTION
82
83
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +030084def cinder_connect(os_creds: OSCreds=None) -> c_client:
koder aka kdanilove87ae652015-04-20 02:14:35 +030085 global CINDER_CONNECTION
86 global STORED_OPENSTACK_CREDS
87
88 if CINDER_CONNECTION is None:
koder aka kdanilovb7197432015-07-15 00:40:43 +030089 if os_creds is None:
90 os_creds = ostack_get_creds()
koder aka kdanilove87ae652015-04-20 02:14:35 +030091 else:
koder aka kdanilovb7197432015-07-15 00:40:43 +030092 STORED_OPENSTACK_CREDS = os_creds
93 CINDER_CONNECTION = c_client(os_creds.name,
94 os_creds.passwd,
95 os_creds.tenant,
96 os_creds.auth_url,
97 insecure=os_creds.insecure)
koder aka kdanilove87ae652015-04-20 02:14:35 +030098 return CINDER_CONNECTION
99
100
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300101def find_vms(nova: n_client, name_prefix: str) -> Iterable[str, int]:
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300102 for srv in nova.servers.list():
103 if srv.name.startswith(name_prefix):
104 for ips in srv.addresses.values():
105 for ip in ips:
106 if ip.get("OS-EXT-IPS:type", None) == 'floating':
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300107 yield ip['addr'], srv.id
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300108 break
109
110
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200111def pause(ids: Iterable[str]) -> None:
112 def pause_vm(conn: n_client, vm_id: str) -> None:
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300113 vm = conn.servers.get(vm_id)
114 if vm.status == 'ACTIVE':
115 vm.pause()
116
117 conn = nova_connect()
118 with ThreadPoolExecutor(max_workers=16) as executor:
119 futures = [executor.submit(pause_vm, conn, vm_id)
120 for vm_id in ids]
121 for future in futures:
122 future.result()
123
124
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200125def unpause(ids: Iterable[str], max_resume_time=10) -> None:
126 def unpause(conn: n_client, vm_id: str) -> None:
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300127 vm = conn.servers.get(vm_id)
128 if vm.status == 'PAUSED':
129 vm.unpause()
130
131 for i in range(max_resume_time * 10):
132 vm = conn.servers.get(vm_id)
133 if vm.status != 'PAUSED':
134 return
135 time.sleep(0.1)
136 raise RuntimeError("Can't unpause vm {0}".format(vm_id))
137
138 conn = nova_connect()
139 with ThreadPoolExecutor(max_workers=16) as executor:
140 futures = [executor.submit(unpause, conn, vm_id)
141 for vm_id in ids]
142
143 for future in futures:
144 future.result()
145
146
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300147def prepare_os(nova: n_client, params: Dict[str, Any], os_creds: OSCreds) -> None:
koder aka kdanilov34052012015-08-27 18:32:11 +0300148 """prepare openstack for futher usage
149
150 Creates server groups, security rules, keypair, flavor
151 and upload VM image from web. In case if object with
152 given name already exists, skip preparation part.
153 Don't check, that existing object has required attributes
154
155 params:
156 nova: novaclient connection
157 params: dict {
158 security_group:str - security group name with allowed ssh and ping
159 aa_group_name:str - template for anti-affinity group names. Should
160 receive one integer parameter, like "cbt_aa_{0}"
161 keypair_name: str - OS keypair name
162 keypair_file_public: str - path to public key file
163 keypair_file_private: str - path to private key file
164
165 flavor:dict - flavor params
166 name, ram_size, hdd_size, cpu_count
167 as for novaclient.Client.flavor.create call
168
169 image:dict - image params
170 'name': image name
171 'url': image url
172 }
173 os_creds: OSCreds
174 max_vm_per_compute: int=8 maximum expected amount of VM, per
175 compute host. Used to create appropriate
176 count of server groups for even placement
177
178 returns: None
179 """
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300180 allow_ssh(nova, params['security_group'])
181
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300182 MAX_VM_PER_NODE = 8
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300183 serv_groups = map(params['aa_group_name'].format,
184 range(MAX_VM_PER_NODE))
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300185
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300186 for serv_groups in serv_groups:
187 get_or_create_aa_group(nova, serv_groups)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300188
189 create_keypair(nova,
190 params['keypair_name'],
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300191 params['keypair_file_public'],
192 params['keypair_file_private'])
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300193
koder aka kdanilov34052012015-08-27 18:32:11 +0300194 create_image(os_creds, nova, params['image']['name'],
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300195 params['image']['url'])
196
197 create_flavor(nova, **params['flavor'])
koder aka kdanilov4e9f3ed2015-04-14 11:26:12 +0300198
199
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300200def create_keypair(nova: n_client, name: str, pub_key_path: str, priv_key_path: str):
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300201 """create and upload keypair into nova, if doesn't exists yet
202
203 Create and upload keypair into nova, if keypair with given bane
204 doesn't exists yet. Uses key from files, if file doesn't exists -
205 create new keys, and store'em into files.
206
207 parameters:
208 nova: nova connection
209 name: str - ketpair name
210 pub_key_path: str - path for public key
211 priv_key_path: str - path for private key
212
213 returns: None
214 """
215
koder aka kdanilov0fdaaee2015-06-30 11:10:48 +0300216 pub_key_exists = os.path.exists(pub_key_path)
217 priv_key_exists = os.path.exists(priv_key_path)
218
219 try:
220 kpair = nova.keypairs.find(name=name)
221 # if file not found- delete and recreate
222 except NotFound:
223 kpair = None
224
225 if pub_key_exists and not priv_key_exists:
226 raise EnvironmentError("Private key file doesn't exists")
227
228 if not pub_key_exists and priv_key_exists:
229 raise EnvironmentError("Public key file doesn't exists")
230
231 if kpair is None:
232 if pub_key_exists:
233 with open(pub_key_path) as pub_key_fd:
234 return nova.keypairs.create(name, pub_key_fd.read())
235 else:
236 key = nova.keypairs.create(name)
237
238 with open(priv_key_path, "w") as priv_key_fd:
239 priv_key_fd.write(key.private_key)
240 os.chmod(priv_key_path, stat.S_IREAD | stat.S_IWRITE)
241
242 with open(pub_key_path, "w") as pub_key_fd:
243 pub_key_fd.write(key.public_key)
244 elif not priv_key_exists:
245 raise EnvironmentError("Private key file doesn't exists," +
246 " but key uploaded openstack." +
247 " Either set correct path to private key" +
248 " or remove key from openstack")
249
250
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300251def get_or_create_aa_group(nova: n_client, name: str) -> int:
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300252 """create anti-affinity server group, if doesn't exists yet
253
254 parameters:
255 nova: nova connection
256 name: str - group name
257
258 returns: str - group id
259 """
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300260 try:
261 group = nova.server_groups.find(name=name)
262 except NotFound:
koder aka kdanilov34052012015-08-27 18:32:11 +0300263 group = nova.server_groups.create(name=name,
264 policies=['anti-affinity'])
koder aka kdanilov652cd802015-04-13 12:21:07 +0300265
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300266 return group.id
koder aka kdanilov652cd802015-04-13 12:21:07 +0300267
268
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300269def allow_ssh(nova: n_client, group_name: str) -> int:
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300270 """create sequrity group for ping and ssh
271
272 parameters:
273 nova: nova connection
274 group_name: str - group name
275
276 returns: str - group id
277 """
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300278 try:
279 secgroup = nova.security_groups.find(name=group_name)
280 except NotFound:
281 secgroup = nova.security_groups.create(group_name,
282 "allow ssh/ping to node")
283
koder aka kdanilov34052012015-08-27 18:32:11 +0300284 nova.security_group_rules.create(secgroup.id,
285 ip_protocol="tcp",
286 from_port="22",
287 to_port="22",
288 cidr="0.0.0.0/0")
koder aka kdanilov652cd802015-04-13 12:21:07 +0300289
koder aka kdanilov34052012015-08-27 18:32:11 +0300290 nova.security_group_rules.create(secgroup.id,
291 ip_protocol="icmp",
292 from_port=-1,
293 cidr="0.0.0.0/0",
294 to_port=-1)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300295 return secgroup.id
koder aka kdanilov652cd802015-04-13 12:21:07 +0300296
297
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300298def create_image(nova: n_client, os_creds: OSCreds, name: str, url: str):
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300299 """upload image into glance from given URL, if given image doesn't exisis yet
300
301 parameters:
302 nova: nova connection
303 os_creds: OSCreds object - openstack credentials, should be same,
304 as used when connectiong given novaclient
305 name: str - image name
306 url: str - image download url
307
308 returns: None
309 """
310 try:
311 nova.images.find(name=name)
312 return
313 except NotFound:
314 pass
315
316 tempnam = os.tempnam()
317
318 try:
319 urllib.urlretrieve(url, tempnam)
320
321 cmd = "OS_USERNAME={0.name}"
322 cmd += " OS_PASSWORD={0.passwd}"
323 cmd += " OS_TENANT_NAME={0.tenant}"
324 cmd += " OS_AUTH_URL={0.auth_url}"
325 cmd += " glance {1} image-create --name {2} $opts --file {3}"
326 cmd += " --disk-format qcow2 --container-format bare --is-public true"
327
328 cmd = cmd.format(os_creds,
329 '--insecure' if os_creds.insecure else "",
330 name,
331 tempnam)
332 finally:
333 if os.path.exists(tempnam):
334 os.unlink(tempnam)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300335
336
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300337def create_flavor(nova: n_client, name: str, ram_size: int, hdd_size: int, cpu_count: int):
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300338 """create flavor, if doesn't exisis yet
339
340 parameters:
341 nova: nova connection
342 name: str - flavor name
343 ram_size: int - ram size (UNIT?)
344 hdd_size: int - root hdd size (UNIT?)
345 cpu_count: int - cpu cores
346
347 returns: None
348 """
349 try:
350 nova.flavors.find(name)
351 return
352 except NotFound:
353 pass
354
355 nova.flavors.create(name, cpu_count, ram_size, hdd_size)
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300356
357
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300358def create_volume(size: int, name: str):
koder aka kdanilove87ae652015-04-20 02:14:35 +0300359 cinder = cinder_connect()
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800360 vol = cinder.volumes.create(size=size, display_name=name)
361 err_count = 0
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300362
koder aka kdanilove87ae652015-04-20 02:14:35 +0300363 while vol.status != 'available':
364 if vol.status == 'error':
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800365 if err_count == 3:
koder aka kdanilove21d7472015-02-14 19:02:04 -0800366 logger.critical("Fail to create volume")
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800367 raise RuntimeError("Fail to create volume")
368 else:
369 err_count += 1
370 cinder.volumes.delete(vol)
371 time.sleep(1)
372 vol = cinder.volumes.create(size=size, display_name=name)
373 continue
374 time.sleep(1)
koder aka kdanilove87ae652015-04-20 02:14:35 +0300375 vol = cinder.volumes.get(vol.id)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800376 return vol
377
378
koder aka kdanilov3b4da8b2016-10-17 00:17:53 +0300379def wait_for_server_active(nova: n_client, server, timeout: int=300)-> None:
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300380 """waiting till server became active
381
382 parameters:
383 nova: nova connection
384 server: server object
385 timeout: int - seconds to wait till raise an exception
386
387 returns: None
388 """
389
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800390 t = time.time()
391 while True:
koder aka kdanilov3f356262015-02-13 08:06:14 -0800392 time.sleep(1)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800393 sstate = getattr(server, 'OS-EXT-STS:vm_state').lower()
394
395 if sstate == 'active':
396 return True
397
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800398 if sstate == 'error':
399 return False
400
401 if time.time() - t > timeout:
402 return False
403
404 server = nova.servers.get(server)
405
406
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800407class Allocate(object):
408 pass
409
410
411def get_floating_ips(nova, pool, amount):
koder aka kdanilova94dfe12015-08-19 13:04:51 +0300412 """allocate flationg ips
413
414 parameters:
415 nova: nova connection
416 pool:str floating ip pool name
417 amount:int - ip count
418
419 returns: [ip object]
420 """
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800421 ip_list = nova.floating_ips.list()
422
423 if pool is not None:
424 ip_list = [ip for ip in ip_list if ip.pool == pool]
425
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800426 return [ip for ip in ip_list if ip.instance_id is None][:amount]
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800427
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800428
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200429def launch_vms(nova, params, already_has_count=0) -> Iterator[NodeInfo]:
koder aka kdanilov34052012015-08-27 18:32:11 +0300430 """launch virtual servers
431
432 Parameters:
433 nova: nova client
434 params: dict {
435 count: str or int - server count. If count is string it should be in
436 one of bext forms: "=INT" or "xINT". First mean
437 to spawn (INT - already_has_count) servers, and
438 all should be evenly distributed across all compute
439 nodes. xINT mean spawn COMPUTE_COUNT * INT servers.
440 image: dict {'name': str - image name}
441 flavor: dict {'name': str - flavor name}
442 group_name: str - group name, used to create uniq server name
443 keypair_name: str - ssh keypais name
444 keypair_file_private: str - path to private key
445 user: str - vm user name
446 vol_sz: int or None - volume size, or None, if no volume
447 network_zone_name: str - network zone name
448 flt_ip_pool: str - floating ip pool
449 name_templ: str - server name template, should receive two parameters
450 'group and id, like 'cbt-{group}-{id}'
451 aa_group_name: str scheduler group name
452 security_group: str - security group name
453 }
454 already_has_count: int=0 - how many servers already exists. Used to distribute
455 new servers evenly across all compute nodes, taking
456 old server in accout
457 returns: generator of str - server credentials, in format USER@IP:KEY_PATH
458
459 """
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300460 logger.debug("Calculating new vm count")
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300461 count = params['count']
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300462 nova = nova_connect()
463 lst = nova.services.list(binary='nova-compute')
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300464 srv_count = len([srv for srv in lst if srv.status == 'enabled'])
koder aka kdanilovda45e882015-04-06 02:24:42 +0300465
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200466 if isinstance(count, str):
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300467 if count.startswith("x"):
468 count = srv_count * int(count[1:])
469 else:
470 assert count.startswith('=')
471 count = int(count[1:]) - already_has_count
472
473 if count <= 0:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300474 logger.debug("Not need new vms")
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300475 return
koder aka kdanilovda45e882015-04-06 02:24:42 +0300476
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300477 logger.debug("Starting new nodes on openstack")
478
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200479 assert isinstance(count, int)
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300480
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300481 srv_params = "img: {image[name]}, flavor: {flavor[name]}".format(**params)
koder aka kdanilov66839a92015-04-11 13:22:31 +0300482 msg_templ = "Will start {0} servers with next params: {1}"
koder aka kdanilovcee43342015-04-14 22:52:53 +0300483 logger.info(msg_templ.format(count, srv_params))
koder aka kdanilovda45e882015-04-06 02:24:42 +0300484
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300485 vm_params = dict(
486 img_name=params['image']['name'],
487 flavor_name=params['flavor']['name'],
488 group_name=params['group_name'],
489 keypair_name=params['keypair_name'],
490 vol_sz=params.get('vol_sz'),
491 network_zone_name=params.get("network_zone_name"),
492 flt_ip_pool=params.get('flt_ip_pool'),
493 name_templ=params.get('name_templ'),
494 scheduler_hints={"group": params['aa_group_name']},
495 security_group=params['security_group'],
496 sec_group_size=srv_count
497 )
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300498
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300499 # precache all errors before start creating vms
500 private_key_path = params['keypair_file_private']
501 creds = params['image']['creds']
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300502
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300503 for ip, os_node in create_vms_mt(NOVA_CONNECTION, count, **vm_params):
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300504 conn_uri = creds.format(ip=ip, private_key_path=private_key_path)
koder aka kdanilov22d134e2016-11-08 11:33:19 +0200505 yield NodeInfo(conn_uri, []), os_node.id
koder aka kdanilovda45e882015-04-06 02:24:42 +0300506
507
koder aka kdanilov34052012015-08-27 18:32:11 +0300508def get_free_server_grpoups(nova, template):
509 """get fre server groups, that match given name template
510
511 parameters:
512 nova: nova connection
513 template:str - name template
514 amount:int - ip count
515
516 returns: generator or str - server group names
517 """
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300518 for g in nova.server_groups.list():
519 if g.members == []:
520 if re.match(template, g.name):
Michael Semenov8ba6e232015-08-28 10:57:18 +0000521 yield str(g.id)
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300522
523
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300524def create_vms_mt(nova, amount, group_name, keypair_name, img_name,
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800525 flavor_name, vol_sz=None, network_zone_name=None,
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300526 flt_ip_pool=None, name_templ='wally-{id}',
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300527 scheduler_hints=None, security_group=None,
528 sec_group_size=None):
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800529
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800530 with ThreadPoolExecutor(max_workers=16) as executor:
koder aka kdanilov97644f92015-02-13 11:11:08 -0800531 if network_zone_name is not None:
532 network_future = executor.submit(nova.networks.find,
533 label=network_zone_name)
534 else:
535 network_future = None
536
537 fl_future = executor.submit(nova.flavors.find, name=flavor_name)
538 img_future = executor.submit(nova.images.find, name=img_name)
539
540 if flt_ip_pool is not None:
541 ips_future = executor.submit(get_floating_ips,
542 nova, flt_ip_pool, amount)
koder aka kdanilove21d7472015-02-14 19:02:04 -0800543 logger.debug("Wait for floating ip")
koder aka kdanilov97644f92015-02-13 11:11:08 -0800544 ips = ips_future.result()
545 ips += [Allocate] * (amount - len(ips))
546 else:
547 ips = [None] * amount
548
koder aka kdanilov7dec9df2015-02-15 21:35:19 -0800549 logger.debug("Getting flavor object")
koder aka kdanilov97644f92015-02-13 11:11:08 -0800550 fl = fl_future.result()
koder aka kdanilov7dec9df2015-02-15 21:35:19 -0800551 logger.debug("Getting image object")
koder aka kdanilov97644f92015-02-13 11:11:08 -0800552 img = img_future.result()
553
554 if network_future is not None:
koder aka kdanilove21d7472015-02-14 19:02:04 -0800555 logger.debug("Waiting for network results")
koder aka kdanilov97644f92015-02-13 11:11:08 -0800556 nics = [{'net-id': network_future.result().id}]
557 else:
558 nics = None
559
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300560 names = []
561 for i in range(amount):
562 names.append(name_templ.format(group=group_name, id=i))
koder aka kdanilov97644f92015-02-13 11:11:08 -0800563
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800564 futures = []
koder aka kdanilov168f6092015-04-19 02:33:38 +0300565 logger.debug("Requesting new vm's")
koder aka kdanilov6e2ae792015-03-04 18:02:24 -0800566
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300567 orig_scheduler_hints = scheduler_hints.copy()
568
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300569 MAX_SHED_GROUPS = 32
570 for start_idx in range(MAX_SHED_GROUPS):
571 pass
572
573 group_name_template = scheduler_hints['group'].format("\\d+")
574 groups = list(get_free_server_grpoups(nova, group_name_template + "$"))
575 groups.sort()
576
577 for idx, (name, flt_ip) in enumerate(zip(names, ips), 2):
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300578
579 scheduler_hints = None
580 if orig_scheduler_hints is not None and sec_group_size is not None:
581 if "group" in orig_scheduler_hints:
582 scheduler_hints = orig_scheduler_hints.copy()
koder aka kdanilovd5ed4da2015-05-07 23:33:23 +0300583 scheduler_hints['group'] = groups[idx // sec_group_size]
koder aka kdanilovc368eb62015-04-28 18:22:01 +0300584
585 if scheduler_hints is None:
586 scheduler_hints = orig_scheduler_hints.copy()
587
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800588 params = (nova, name, keypair_name, img, fl,
589 nics, vol_sz, flt_ip, scheduler_hints,
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300590 flt_ip_pool, [security_group])
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800591
592 futures.append(executor.submit(create_vm, *params))
koder aka kdanilove21d7472015-02-14 19:02:04 -0800593 res = [future.result() for future in futures]
594 logger.debug("Done spawning")
595 return res
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800596
597
598def create_vm(nova, name, keypair_name, img,
599 fl, nics, vol_sz=None,
600 flt_ip=False,
601 scheduler_hints=None,
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300602 pool=None,
603 security_groups=None):
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800604 for i in range(3):
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800605 srv = nova.servers.create(name,
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300606 flavor=fl,
607 image=img,
608 nics=nics,
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800609 key_name=keypair_name,
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300610 scheduler_hints=scheduler_hints,
611 security_groups=security_groups)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800612
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800613 if not wait_for_server_active(nova, srv):
614 msg = "Server {0} fails to start. Kill it and try again"
koder aka kdanilove21d7472015-02-14 19:02:04 -0800615 logger.debug(msg.format(srv))
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800616 nova.servers.delete(srv)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800617
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300618 try:
619 for j in range(120):
620 srv = nova.servers.get(srv.id)
621 time.sleep(1)
622 else:
623 msg = "Server {0} delete timeout".format(srv.id)
624 raise RuntimeError(msg)
625 except NotFound:
626 pass
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800627 else:
628 break
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300629 else:
630 raise RuntimeError("Failed to start server".format(srv.id))
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800631
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800632 if vol_sz is not None:
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800633 vol = create_volume(vol_sz, name)
koder aka kdanilove87ae652015-04-20 02:14:35 +0300634 nova.volumes.create_server_volume(srv.id, vol.id, None)
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800635
636 if flt_ip is Allocate:
637 flt_ip = nova.floating_ips.create(pool)
koder aka kdanilovda45e882015-04-06 02:24:42 +0300638
koder aka kdanilov7acd6bd2015-02-12 14:28:30 -0800639 if flt_ip is not None:
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800640 srv.add_floating_ip(flt_ip)
Yulia Portnova0e64ea22015-03-20 17:27:22 +0200641
koder aka kdanilovda45e882015-04-06 02:24:42 +0300642 return flt_ip.ip, nova.servers.get(srv.id)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800643
644
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300645def clear_nodes(nodes_ids):
646 clear_all(NOVA_CONNECTION, nodes_ids, None)
gstepanov023c1e42015-04-08 15:50:19 +0300647
648
koder aka kdanilov765920a2016-04-12 00:35:48 +0300649MAX_SERVER_DELETE_TIME = 120
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300650
koder aka kdanilov1c2b5112015-04-10 16:53:51 +0300651
koder aka kdanilov765920a2016-04-12 00:35:48 +0300652def clear_all(nova, ids=None, name_templ=None,
653 max_server_delete_time=MAX_SERVER_DELETE_TIME):
654 try:
655 def need_delete(srv):
656 if name_templ is not None:
657 return re.match(name_templ.format("\\d+"), srv.name) is not None
658 else:
659 return srv.id in ids
660
661 volumes_to_delete = []
662 cinder = cinder_connect()
663 for vol in cinder.volumes.list():
664 for attachment in vol.attachments:
665 if attachment['server_id'] in ids:
666 volumes_to_delete.append(vol)
667 break
668
669 deleted_srvs = set()
670 for srv in nova.servers.list():
671 if need_delete(srv):
672 logger.debug("Deleting server {0}".format(srv.name))
673 nova.servers.delete(srv)
674 deleted_srvs.add(srv.id)
675
676 count = 0
677 while count < max_server_delete_time:
678 if count % 60 == 0:
679 logger.debug("Waiting till all servers are actually deleted")
680 all_id = set(srv.id for srv in nova.servers.list())
681 if len(all_id.intersection(deleted_srvs)) == 0:
koder aka kdanilove87ae652015-04-20 02:14:35 +0300682 break
koder aka kdanilov765920a2016-04-12 00:35:48 +0300683 count += 1
684 time.sleep(1)
685 else:
686 logger.warning("Failed to remove servers. " +
687 "You, probably, need to remove them manually")
688 return
689 logger.debug("Done, deleting volumes")
koder aka kdanilove87ae652015-04-20 02:14:35 +0300690
koder aka kdanilov765920a2016-04-12 00:35:48 +0300691 # wait till vm actually deleted
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800692
koder aka kdanilov765920a2016-04-12 00:35:48 +0300693 # logger.warning("Volume deletion commented out")
694 for vol in volumes_to_delete:
695 logger.debug("Deleting volume " + vol.display_name)
696 cinder.volumes.delete(vol)
koder aka kdanilov4643fd62015-02-10 16:20:13 -0800697
koder aka kdanilov765920a2016-04-12 00:35:48 +0300698 logger.debug("Clearing done (yet some volumes may still deleting)")
699 except:
700 logger.exception("During removing servers. " +
701 "You, probably, need to remove them manually")