blob: 5e09d6d2df92af9710e99c05918c016a92e7a3bf [file] [log] [blame]
koder aka kdanilov39e449e2016-12-17 15:15:26 +02001import os.path
2import socket
3import logging
4from typing import Dict, Any, List, Tuple, cast, Optional
5
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
13from .utils import LogError, StopTestError, get_creds_openrc
14
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:
38 if "openstack_openrc" in ctx.storage:
39 return ctx.storage.load(OSCreds, "openstack_openrc")
40
41 creds = None
42 os_creds = None
43 force_insecure = False
44 cfg = ctx.config
45
46 if 'openstack' in cfg.clouds:
47 os_cfg = cfg.clouds['openstack']
48 if 'OPENRC' in os_cfg:
49 logger.info("Using OS credentials from " + os_cfg['OPENRC'])
50 creds_tuple = get_creds_openrc(os_cfg['OPENRC'])
51 os_creds = OSCreds(*creds_tuple)
52 elif 'ENV' in os_cfg:
53 logger.info("Using OS credentials from shell environment")
54 os_creds = get_openstack_credentials()
55 elif 'OS_TENANT_NAME' in os_cfg:
56 logger.info("Using predefined credentials")
57 os_creds = OSCreds(os_cfg['OS_USERNAME'].strip(),
58 os_cfg['OS_PASSWORD'].strip(),
59 os_cfg['OS_TENANT_NAME'].strip(),
60 os_cfg['OS_AUTH_URL'].strip(),
61 os_cfg.get('OS_INSECURE', False))
62
63 elif 'OS_INSECURE' in os_cfg:
64 force_insecure = os_cfg.get('OS_INSECURE', False)
65
66 if os_creds is None and 'fuel' in cfg.clouds and 'openstack_env' in cfg.clouds['fuel'] and \
67 ctx.fuel_openstack_creds is not None:
68 logger.info("Using fuel creds")
69 creds = ctx.fuel_openstack_creds
70 elif os_creds is None:
71 logger.error("Can't found OS credentials")
72 raise StopTestError("Can't found OS credentials", None)
73
74 if creds is None:
75 creds = os_creds
76
77 if force_insecure and not creds.insecure:
78 creds = OSCreds(creds.name, creds.passwd, creds.tenant, creds.auth_url, True)
79
80 logger.debug(("OS_CREDS: user={0.name} tenant={0.tenant} " +
81 "auth_url={0.auth_url} insecure={0.insecure}").format(creds))
82
83 ctx.storage["openstack_openrc"] = creds # type: ignore
84 return creds
85
86
87def get_vm_keypair_path(cfg: Config) -> Tuple[str, str]:
88 key_name = cfg.vm_configs['keypair_name']
89 private_path = os.path.join(cfg.settings_dir, key_name + "_private.pem")
90 public_path = os.path.join(cfg.settings_dir, key_name + "_public.pub")
91 return (private_path, public_path)
92
93
94class DiscoverOSStage(Stage):
95 """Discover openstack nodes and VMS"""
96
97 config_block = 'openstack'
98
99 # discover FUEL cluster first
100 priority = StepOrder.DISCOVER + 1
101
102 @classmethod
103 def validate(cls, conf: ConfigBlock) -> None:
104 pass
105
106 def run(self, ctx: TestRun) -> None:
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200107 if 'all_nodes' in ctx.storage:
108 logger.debug("Skip openstack discovery, use previously discovered nodes")
109 return
110
111 ensure_connected_to_openstack(ctx)
112
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200113 cfg = ctx.config.openstack
114 os_nodes_auth = cfg.auth # type: str
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200115 if os_nodes_auth.count(":") == 2:
116 user, password, key_file = os_nodes_auth.split(":") # type: str, Optional[str], Optional[str]
117 if not password:
118 password = None
119 else:
120 user, password = os_nodes_auth.split(":")
121 key_file = None
122
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200123 if ctx.config.discovery not in ('disabled', 'metadata'):
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200124 services = ctx.os_connection.nova.services.list() # type: List[Any]
125 host_services_mapping = {} # type: Dict[str, List[str]]
126
127 for service in services:
128 ip = cast(str, socket.gethostbyname(service.host))
129 host_services_mapping.get(ip, []).append(service.binary)
130
131 logger.debug("Found %s openstack service nodes" % len(host_services_mapping))
132
133 for host, services in host_services_mapping.items():
134 creds = ConnCreds(host=host, user=user, passwd=password, key_file=key_file)
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200135 ctx.merge_node(creds, set(services))
136 # TODO: log OS nodes discovery results
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200137 else:
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200138 logger.info("Scip OS cluster discovery due to 'discovery' setting value")
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200139
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200140 private_key_path = get_vm_keypair_path(ctx.config)[0]
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200141
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200142 vm_creds = None # type: str
143 for vm_creds in cfg.get("vms", []):
144 user_name, vm_name_pattern = vm_creds.split("@", 1)
145 msg = "Vm like {} lookup failed".format(vm_name_pattern)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200146
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200147 with LogError(msg):
148 msg = "Looking for vm with name like {0}".format(vm_name_pattern)
149 logger.debug(msg)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200150
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200151 ensure_connected_to_openstack(ctx)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200152
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200153 for ip, vm_id in find_vms(ctx.os_connection, vm_name_pattern):
154 creds = ConnCreds(host=ip, user=user_name, key_file=private_key_path)
155 info = NodeInfo(creds, {'testnode'})
156 info.os_vm_id = vm_id
157 nid = info.node_id()
158 if nid in ctx.nodes_info:
159 logger.error("Test VM node has the same id(%s), as existing node %s", nid, ctx.nodes_info[nid])
160 raise StopTestError()
161 ctx.nodes_info[nid] = info
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200162
163
164class CreateOSVMSStage(Stage):
165 "Spawn new VM's in Openstack cluster"
166
167 priority = StepOrder.SPAWN # type: int
168 config_block = 'spawn_os_vms' # type: str
169
170 def run(self, ctx: TestRun) -> None:
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200171 if 'all_nodes' in ctx.storage:
172 ctx.os_spawned_nodes_ids = ctx.storage['os_spawned_nodes_ids'] # type: ignore
173 logger.info("Skipping OS VMS discovery/spawn as all data found in storage")
174 return
175
176 if 'os_spawned_nodes_ids' in ctx.storage:
177 logger.error("spawned_os_nodes_ids is found in storage, but no nodes_info is stored." +
178 "Fix this before continue")
179 raise StopTestError()
180
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200181 vm_spawn_config = ctx.config.spawn_os_vms
182 vm_image_config = ctx.config.vm_configs[vm_spawn_config.cfg_name]
183
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200184 ensure_connected_to_openstack(ctx)
185 params = vm_image_config.copy()
186 params.update(vm_spawn_config)
187 params.update(get_vm_keypair_path(ctx.config))
188 params['group_name'] = ctx.config.run_uuid
189 params['keypair_name'] = ctx.config.vm_configs['keypair_name']
190
191 if not ctx.config.openstack.get("skip_preparation", False):
192 logger.info("Preparing openstack")
193 prepare_os(ctx.os_connection, params)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200194 else:
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200195 logger.info("Scip openstack preparation as 'skip_preparation' is set")
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200196
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200197 ctx.os_spawned_nodes_ids = []
198 with ctx.get_pool() as pool:
199 for info in launch_vms(ctx.os_connection, params, pool):
200 info.roles.add('testnode')
201 nid = info.node_id()
202 if nid in ctx.nodes_info:
203 logger.error("Test VM node has the same id(%s), as existing node %s", nid, ctx.nodes_info[nid])
204 raise StopTestError()
205 ctx.nodes_info[nid] = info
206 ctx.os_spawned_nodes_ids.append(info.os_vm_id)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200207
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200208 ctx.storage['os_spawned_nodes_ids'] = ctx.os_spawned_nodes_ids # type: ignore
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200209
210 def cleanup(self, ctx: TestRun) -> None:
211 # keep nodes in case of error for future test restart
212 if not ctx.config.keep_vm and ctx.os_spawned_nodes_ids:
213 logger.info("Removing nodes")
214
215 clear_nodes(ctx.os_connection, ctx.os_spawned_nodes_ids)
216 del ctx.storage['spawned_os_nodes']
217
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200218 logger.info("OS spawned nodes has been successfully removed")
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200219
220
221
222# @contextlib.contextmanager
223# def suspend_vm_nodes_ctx(ctx: TestRun, unused_nodes: List[IRPCNode]) -> Iterator[List[int]]:
224#
225# pausable_nodes_ids = [cast(int, node.info.os_vm_id)
226# for node in unused_nodes
227# if node.info.os_vm_id is not None]
228#
229# non_pausable = len(unused_nodes) - len(pausable_nodes_ids)
230#
231# if non_pausable:
232# logger.warning("Can't pause {} nodes".format(non_pausable))
233#
234# if pausable_nodes_ids:
235# logger.debug("Try to pause {} unused nodes".format(len(pausable_nodes_ids)))
236# with ctx.get_pool() as pool:
237# openstack_api.pause(ctx.os_connection, pausable_nodes_ids, pool)
238#
239# try:
240# yield pausable_nodes_ids
241# finally:
242# if pausable_nodes_ids:
243# logger.debug("Unpausing {} nodes".format(len(pausable_nodes_ids)))
244# with ctx.get_pool() as pool:
245# openstack_api.unpause(ctx.os_connection, pausable_nodes_ids, pool)
246# def clouds_connect_stage(ctx: TestRun) -> None:
247 # TODO(koder): need to use this to connect to openstack in upper code
248 # conn = ctx.config['clouds/openstack']
249 # user, passwd, tenant = parse_creds(conn['creds'])
250 # auth_data = dict(auth_url=conn['auth_url'],
251 # username=user,
252 # api_key=passwd,
253 # project_id=tenant) # type: Dict[str, str]
254 # logger.debug("Discovering openstack nodes with connection details: %r", conn)
255 # connect to openstack, fuel
256
257 # # parse FUEL REST credentials
258 # username, tenant_name, password = parse_creds(fuel_data['creds'])
259 # creds = {"username": username,
260 # "tenant_name": tenant_name,
261 # "password": password}
262 #
263 # # connect to FUEL
264 # conn = fuel_rest_api.KeystoneAuth(fuel_data['url'], creds, headers=None)
265 # pass