blob: 88189f574b163f184b61ed8bc02636e94cf4e8c4 [file] [log] [blame]
koder aka kdanilov39e449e2016-12-17 15:15:26 +02001import os.path
2import socket
3import logging
koder aka kdanilov23e6bdf2016-12-24 02:18:54 +02004from typing import Dict, Any, List, Tuple, cast, Optional
koder aka kdanilov39e449e2016-12-17 15:15:26 +02005
6from .node_interfaces import NodeInfo
7from .config import ConfigBlock, Config
8from .ssh_utils import ConnCreds
9from .openstack_api import (os_connect, find_vms,
10 OSCreds, get_openstack_credentials, prepare_os, launch_vms, clear_nodes)
11from .test_run_class import TestRun
12from .stage import Stage, StepOrder
koder aka kdanilovbbbe1dc2016-12-20 01:19:56 +020013from .utils import LogError, StopTestError, get_creds_openrc, to_ip
koder aka kdanilov39e449e2016-12-17 15:15:26 +020014
15
koder aka kdanilov962ee5f2016-12-19 02:40:08 +020016logger = logging.getLogger("wally")
koder aka kdanilov39e449e2016-12-17 15:15:26 +020017
18
19def get_floating_ip(vm: Any) -> str:
20 """Get VM floating IP address"""
21
22 for net_name, ifaces in vm.addresses.items():
23 for iface in ifaces:
24 if iface.get('OS-EXT-IPS:type') == "floating":
25 return iface['addr']
26
27 raise ValueError("VM {} has no floating ip".format(vm))
28
29
30def ensure_connected_to_openstack(ctx: TestRun) -> None:
31 if not ctx.os_connection is None:
32 if ctx.os_creds is None:
33 ctx.os_creds = get_OS_credentials(ctx)
34 ctx.os_connection = os_connect(ctx.os_creds)
35
36
37def get_OS_credentials(ctx: TestRun) -> OSCreds:
koder aka kdanilovffaf48d2016-12-27 02:25:29 +020038 stored = ctx.storage.get("openstack_openrc", None)
39 if stored is not None:
40 return OSCreds(*cast(List, stored))
koder aka kdanilov39e449e2016-12-17 15:15:26 +020041
koder aka kdanilov7f59d562016-12-26 01:34:23 +020042 creds = None # type: OSCreds
43 os_creds = None # type: OSCreds
koder aka kdanilov39e449e2016-12-17 15:15:26 +020044 force_insecure = False
45 cfg = ctx.config
46
47 if 'openstack' in cfg.clouds:
48 os_cfg = cfg.clouds['openstack']
49 if 'OPENRC' in os_cfg:
50 logger.info("Using OS credentials from " + os_cfg['OPENRC'])
51 creds_tuple = get_creds_openrc(os_cfg['OPENRC'])
52 os_creds = OSCreds(*creds_tuple)
53 elif 'ENV' in os_cfg:
54 logger.info("Using OS credentials from shell environment")
55 os_creds = get_openstack_credentials()
56 elif 'OS_TENANT_NAME' in os_cfg:
57 logger.info("Using predefined credentials")
58 os_creds = OSCreds(os_cfg['OS_USERNAME'].strip(),
59 os_cfg['OS_PASSWORD'].strip(),
60 os_cfg['OS_TENANT_NAME'].strip(),
61 os_cfg['OS_AUTH_URL'].strip(),
62 os_cfg.get('OS_INSECURE', False))
63
64 elif 'OS_INSECURE' in os_cfg:
65 force_insecure = os_cfg.get('OS_INSECURE', False)
66
67 if os_creds is None and 'fuel' in cfg.clouds and 'openstack_env' in cfg.clouds['fuel'] and \
68 ctx.fuel_openstack_creds is not None:
69 logger.info("Using fuel creds")
70 creds = ctx.fuel_openstack_creds
71 elif os_creds is None:
72 logger.error("Can't found OS credentials")
73 raise StopTestError("Can't found OS credentials", None)
74
75 if creds is None:
76 creds = os_creds
77
78 if force_insecure and not creds.insecure:
79 creds = OSCreds(creds.name, creds.passwd, creds.tenant, creds.auth_url, True)
80
81 logger.debug(("OS_CREDS: user={0.name} tenant={0.tenant} " +
82 "auth_url={0.auth_url} insecure={0.insecure}").format(creds))
83
koder aka kdanilov7f59d562016-12-26 01:34:23 +020084 ctx.storage.put(list(creds), "openstack_openrc")
koder aka kdanilov39e449e2016-12-17 15:15:26 +020085 return creds
86
87
88def get_vm_keypair_path(cfg: Config) -> Tuple[str, str]:
89 key_name = cfg.vm_configs['keypair_name']
90 private_path = os.path.join(cfg.settings_dir, key_name + "_private.pem")
91 public_path = os.path.join(cfg.settings_dir, key_name + "_public.pub")
92 return (private_path, public_path)
93
94
95class DiscoverOSStage(Stage):
96 """Discover openstack nodes and VMS"""
97
98 config_block = 'openstack'
99
100 # discover FUEL cluster first
101 priority = StepOrder.DISCOVER + 1
102
103 @classmethod
104 def validate(cls, conf: ConfigBlock) -> None:
105 pass
106
107 def run(self, ctx: TestRun) -> None:
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300108 if 'openstack' not in ctx.config.discover:
kdanylov aka koder150b2192017-04-01 16:53:01 +0300109 logger.debug("Skip openstack discovery due to settings")
110 return
111
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200112 if 'all_nodes' in ctx.storage:
113 logger.debug("Skip openstack discovery, use previously discovered nodes")
114 return
115
116 ensure_connected_to_openstack(ctx)
117
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200118 cfg = ctx.config.openstack
119 os_nodes_auth = cfg.auth # type: str
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200120 if os_nodes_auth.count(":") == 2:
121 user, password, key_file = os_nodes_auth.split(":") # type: str, Optional[str], Optional[str]
122 if not password:
123 password = None
124 else:
125 user, password = os_nodes_auth.split(":")
126 key_file = None
127
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300128 if 'metadata' not in ctx.config.discover:
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200129 services = ctx.os_connection.nova.services.list() # type: List[Any]
130 host_services_mapping = {} # type: Dict[str, List[str]]
131
132 for service in services:
133 ip = cast(str, socket.gethostbyname(service.host))
134 host_services_mapping.get(ip, []).append(service.binary)
135
136 logger.debug("Found %s openstack service nodes" % len(host_services_mapping))
137
138 for host, services in host_services_mapping.items():
koder aka kdanilovbbbe1dc2016-12-20 01:19:56 +0200139 creds = ConnCreds(host=to_ip(host), user=user, passwd=password, key_file=key_file)
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200140 ctx.merge_node(creds, set(services))
141 # TODO: log OS nodes discovery results
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200142 else:
kdanylov aka koder150b2192017-04-01 16:53:01 +0300143 logger.info("Skip OS cluster discovery due to 'discovery' setting value")
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200144
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200145 private_key_path = get_vm_keypair_path(ctx.config)[0]
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200146
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200147 vm_creds = None # type: str
148 for vm_creds in cfg.get("vms", []):
149 user_name, vm_name_pattern = vm_creds.split("@", 1)
150 msg = "Vm like {} lookup failed".format(vm_name_pattern)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200151
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200152 with LogError(msg):
153 msg = "Looking for vm with name like {0}".format(vm_name_pattern)
154 logger.debug(msg)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200155
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200156 ensure_connected_to_openstack(ctx)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200157
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200158 for ip, vm_id in find_vms(ctx.os_connection, vm_name_pattern):
koder aka kdanilovbbbe1dc2016-12-20 01:19:56 +0200159 creds = ConnCreds(host=to_ip(ip), user=user_name, key_file=private_key_path)
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200160 info = NodeInfo(creds, {'testnode'})
161 info.os_vm_id = vm_id
koder aka kdanilov108ac362017-01-19 20:17:16 +0200162 nid = info.node_id
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200163 if nid in ctx.nodes_info:
164 logger.error("Test VM node has the same id(%s), as existing node %s", nid, ctx.nodes_info[nid])
165 raise StopTestError()
166 ctx.nodes_info[nid] = info
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200167
168
169class CreateOSVMSStage(Stage):
170 "Spawn new VM's in Openstack cluster"
171
172 priority = StepOrder.SPAWN # type: int
173 config_block = 'spawn_os_vms' # type: str
174
175 def run(self, ctx: TestRun) -> None:
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200176 if 'all_nodes' in ctx.storage:
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200177 ctx.os_spawned_nodes_ids = ctx.storage.get('os_spawned_nodes_ids')
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200178 logger.info("Skipping OS VMS discovery/spawn as all data found in storage")
179 return
180
181 if 'os_spawned_nodes_ids' in ctx.storage:
182 logger.error("spawned_os_nodes_ids is found in storage, but no nodes_info is stored." +
183 "Fix this before continue")
184 raise StopTestError()
185
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200186 vm_spawn_config = ctx.config.spawn_os_vms
187 vm_image_config = ctx.config.vm_configs[vm_spawn_config.cfg_name]
188
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200189 ensure_connected_to_openstack(ctx)
190 params = vm_image_config.copy()
191 params.update(vm_spawn_config)
192 params.update(get_vm_keypair_path(ctx.config))
193 params['group_name'] = ctx.config.run_uuid
194 params['keypair_name'] = ctx.config.vm_configs['keypair_name']
195
196 if not ctx.config.openstack.get("skip_preparation", False):
197 logger.info("Preparing openstack")
198 prepare_os(ctx.os_connection, params)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200199 else:
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200200 logger.info("Scip openstack preparation as 'skip_preparation' is set")
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200201
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200202 ctx.os_spawned_nodes_ids = []
203 with ctx.get_pool() as pool:
204 for info in launch_vms(ctx.os_connection, params, pool):
205 info.roles.add('testnode')
koder aka kdanilov108ac362017-01-19 20:17:16 +0200206 nid = info.node_id
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200207 if nid in ctx.nodes_info:
208 logger.error("Test VM node has the same id(%s), as existing node %s", nid, ctx.nodes_info[nid])
209 raise StopTestError()
210 ctx.nodes_info[nid] = info
211 ctx.os_spawned_nodes_ids.append(info.os_vm_id)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200212
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200213 ctx.storage.put(ctx.os_spawned_nodes_ids, 'os_spawned_nodes_ids')
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200214
215 def cleanup(self, ctx: TestRun) -> None:
216 # keep nodes in case of error for future test restart
217 if not ctx.config.keep_vm and ctx.os_spawned_nodes_ids:
218 logger.info("Removing nodes")
219
220 clear_nodes(ctx.os_connection, ctx.os_spawned_nodes_ids)
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200221 ctx.storage.rm('spawned_os_nodes')
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200222
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200223 logger.info("OS spawned nodes has been successfully removed")
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200224
225
226
227# @contextlib.contextmanager
228# def suspend_vm_nodes_ctx(ctx: TestRun, unused_nodes: List[IRPCNode]) -> Iterator[List[int]]:
229#
230# pausable_nodes_ids = [cast(int, node.info.os_vm_id)
231# for node in unused_nodes
232# if node.info.os_vm_id is not None]
233#
234# non_pausable = len(unused_nodes) - len(pausable_nodes_ids)
235#
236# if non_pausable:
237# logger.warning("Can't pause {} nodes".format(non_pausable))
238#
239# if pausable_nodes_ids:
240# logger.debug("Try to pause {} unused nodes".format(len(pausable_nodes_ids)))
241# with ctx.get_pool() as pool:
242# openstack_api.pause(ctx.os_connection, pausable_nodes_ids, pool)
243#
244# try:
245# yield pausable_nodes_ids
246# finally:
247# if pausable_nodes_ids:
248# logger.debug("Unpausing {} nodes".format(len(pausable_nodes_ids)))
249# with ctx.get_pool() as pool:
250# openstack_api.unpause(ctx.os_connection, pausable_nodes_ids, pool)
251# def clouds_connect_stage(ctx: TestRun) -> None:
252 # TODO(koder): need to use this to connect to openstack in upper code
253 # conn = ctx.config['clouds/openstack']
254 # user, passwd, tenant = parse_creds(conn['creds'])
255 # auth_data = dict(auth_url=conn['auth_url'],
256 # username=user,
257 # api_key=passwd,
258 # project_id=tenant) # type: Dict[str, str]
259 # logger.debug("Discovering openstack nodes with connection details: %r", conn)
260 # connect to openstack, fuel
261
262 # # parse FUEL REST credentials
263 # username, tenant_name, password = parse_creds(fuel_data['creds'])
264 # creds = {"username": username,
265 # "tenant_name": tenant_name,
266 # "password": password}
267 #
268 # # connect to FUEL
269 # conn = fuel_rest_api.KeystoneAuth(fuel_data['url'], creds, headers=None)
270 # pass