blob: 1e0265327591112364f5f72ab8e3e6c3c2dbd4b0 [file] [log] [blame]
koder aka kdanilov39e449e2016-12-17 15:15:26 +02001import os.path
2import socket
3import logging
kdanylov aka koder026e5f22017-05-15 01:04:39 +03004from typing import Dict, Any, List, Tuple, cast, Optional
kdanylov aka koderb0833332017-05-13 20:39:17 +03005
6from cephlib.common import to_ip
kdanylov aka koder026e5f22017-05-15 01:04:39 +03007from cephlib.node import NodeInfo
8from cephlib.ssh import ConnCreds
koder aka kdanilov39e449e2016-12-17 15:15:26 +02009
koder aka kdanilov39e449e2016-12-17 15:15:26 +020010from .config import ConfigBlock, Config
kdanylov aka koder13e58452018-07-15 02:51:51 +030011from .openstack_api import (os_connect, find_vms, OSConnection,
12 OSCreds, get_openstack_credentials_from_env, prepare_os, launch_vms, clear_nodes)
koder aka kdanilov39e449e2016-12-17 15:15:26 +020013from .test_run_class import TestRun
14from .stage import Stage, StepOrder
kdanylov aka koderb0833332017-05-13 20:39:17 +030015from .utils import LogError, StopTestError, get_creds_openrc
koder aka kdanilov39e449e2016-12-17 15:15:26 +020016
17
koder aka kdanilov962ee5f2016-12-19 02:40:08 +020018logger = logging.getLogger("wally")
koder aka kdanilov39e449e2016-12-17 15:15:26 +020019
20
21def get_floating_ip(vm: Any) -> str:
22 """Get VM floating IP address"""
23
24 for net_name, ifaces in vm.addresses.items():
25 for iface in ifaces:
26 if iface.get('OS-EXT-IPS:type') == "floating":
27 return iface['addr']
28
29 raise ValueError("VM {} has no floating ip".format(vm))
30
31
32def ensure_connected_to_openstack(ctx: TestRun) -> None:
33 if not ctx.os_connection is None:
34 if ctx.os_creds is None:
35 ctx.os_creds = get_OS_credentials(ctx)
36 ctx.os_connection = os_connect(ctx.os_creds)
37
38
39def get_OS_credentials(ctx: TestRun) -> OSCreds:
koder aka kdanilovffaf48d2016-12-27 02:25:29 +020040 stored = ctx.storage.get("openstack_openrc", None)
41 if stored is not None:
42 return OSCreds(*cast(List, stored))
koder aka kdanilov39e449e2016-12-17 15:15:26 +020043
kdanylov aka koder13e58452018-07-15 02:51:51 +030044 creds: OSCreds = None # type: ignore
45 os_creds: OSCreds = None # type: ignore
koder aka kdanilov39e449e2016-12-17 15:15:26 +020046 force_insecure = False
47 cfg = ctx.config
48
49 if 'openstack' in cfg.clouds:
50 os_cfg = cfg.clouds['openstack']
51 if 'OPENRC' in os_cfg:
kdanylov aka koder470a8fa2017-07-14 21:07:58 +030052 sett = os_cfg['OPENRC']
53 if isinstance(sett, str):
54 if 'ENV' == sett:
55 logger.info("Using OS credentials from shell environment")
kdanylov aka koder13e58452018-07-15 02:51:51 +030056 os_creds = get_openstack_credentials_from_env()
kdanylov aka koder470a8fa2017-07-14 21:07:58 +030057 else:
58 logger.info("Using OS credentials from " + os_cfg['OPENRC'])
59 creds_tuple = get_creds_openrc(sett)
60 os_creds = OSCreds(*creds_tuple)
61 else:
62 logger.info("Using predefined credentials")
63 os_creds = OSCreds(sett['OS_USERNAME'].strip(),
64 sett['OS_PASSWORD'].strip(),
65 sett['OS_TENANT_NAME'].strip(),
66 sett['OS_AUTH_URL'].strip(),
67 sett.get('OS_INSECURE', False))
koder aka kdanilov39e449e2016-12-17 15:15:26 +020068
kdanylov aka koder470a8fa2017-07-14 21:07:58 +030069 if 'insecure' in os_cfg:
70 force_insecure = os_cfg.get('insecure', False)
koder aka kdanilov39e449e2016-12-17 15:15:26 +020071
kdanylov aka koder13e58452018-07-15 02:51:51 +030072 if os_creds is None:
koder aka kdanilov39e449e2016-12-17 15:15:26 +020073 logger.error("Can't found OS credentials")
74 raise StopTestError("Can't found OS credentials", None)
75
76 if creds is None:
77 creds = os_creds
78
79 if force_insecure and not creds.insecure:
80 creds = OSCreds(creds.name, creds.passwd, creds.tenant, creds.auth_url, True)
81
82 logger.debug(("OS_CREDS: user={0.name} tenant={0.tenant} " +
83 "auth_url={0.auth_url} insecure={0.insecure}").format(creds))
84
koder aka kdanilov7f59d562016-12-26 01:34:23 +020085 ctx.storage.put(list(creds), "openstack_openrc")
koder aka kdanilov39e449e2016-12-17 15:15:26 +020086 return creds
87
88
89def get_vm_keypair_path(cfg: Config) -> Tuple[str, str]:
90 key_name = cfg.vm_configs['keypair_name']
91 private_path = os.path.join(cfg.settings_dir, key_name + "_private.pem")
92 public_path = os.path.join(cfg.settings_dir, key_name + "_public.pub")
93 return (private_path, public_path)
94
95
96class DiscoverOSStage(Stage):
97 """Discover openstack nodes and VMS"""
98
99 config_block = 'openstack'
100
kdanylov aka koder13e58452018-07-15 02:51:51 +0300101 priority = StepOrder.DISCOVER
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200102
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
kdanylov aka koder13e58452018-07-15 02:51:51 +0300118 os_conn: OSConnection = ctx.os_connection # type: ignore # remove Optional[]
119
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200120 cfg = ctx.config.openstack
121 os_nodes_auth = cfg.auth # type: str
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200122 if os_nodes_auth.count(":") == 2:
123 user, password, key_file = os_nodes_auth.split(":") # type: str, Optional[str], Optional[str]
124 if not password:
125 password = None
126 else:
127 user, password = os_nodes_auth.split(":")
128 key_file = None
129
kdanylov aka kodercdfcdaf2017-04-29 10:03:39 +0300130 if 'metadata' not in ctx.config.discover:
kdanylov aka koder13e58452018-07-15 02:51:51 +0300131 services: List[Any] = os_conn.nova.services.list()
132 host_services_mapping: Dict[str, List[str]] = {}
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200133
134 for service in services:
135 ip = cast(str, socket.gethostbyname(service.host))
136 host_services_mapping.get(ip, []).append(service.binary)
137
138 logger.debug("Found %s openstack service nodes" % len(host_services_mapping))
139
140 for host, services in host_services_mapping.items():
kdanylov aka koderb0833332017-05-13 20:39:17 +0300141 host_ip = to_ip(host)
142 if host != host_ip:
143 logger.info("Will use ip_addr %r instead of hostname %r", host_ip, host)
144 creds = ConnCreds(host=host_ip, user=user, passwd=password, key_file=key_file)
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200145 ctx.merge_node(creds, set(services))
146 # TODO: log OS nodes discovery results
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200147 else:
kdanylov aka koder150b2192017-04-01 16:53:01 +0300148 logger.info("Skip OS cluster discovery due to 'discovery' setting value")
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200149
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200150 private_key_path = get_vm_keypair_path(ctx.config)[0]
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200151
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200152 for vm_creds in cfg.get("vms", []):
kdanylov aka koder13e58452018-07-15 02:51:51 +0300153 user_name, vm_name_pattern = vm_creds.split("@", 1) # type: ignore
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200154 msg = "Vm like {} lookup failed".format(vm_name_pattern)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200155
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200156 with LogError(msg):
157 msg = "Looking for vm with name like {0}".format(vm_name_pattern)
158 logger.debug(msg)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200159
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200160 ensure_connected_to_openstack(ctx)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200161
kdanylov aka koder13e58452018-07-15 02:51:51 +0300162 for ip, vm_id in find_vms(os_conn, vm_name_pattern): # type: ignore
koder aka kdanilovbbbe1dc2016-12-20 01:19:56 +0200163 creds = ConnCreds(host=to_ip(ip), user=user_name, key_file=private_key_path)
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200164 info = NodeInfo(creds, {'testnode'})
165 info.os_vm_id = vm_id
koder aka kdanilov108ac362017-01-19 20:17:16 +0200166 nid = info.node_id
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200167 if nid in ctx.nodes_info:
168 logger.error("Test VM node has the same id(%s), as existing node %s", nid, ctx.nodes_info[nid])
169 raise StopTestError()
170 ctx.nodes_info[nid] = info
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200171
172
173class CreateOSVMSStage(Stage):
174 "Spawn new VM's in Openstack cluster"
175
176 priority = StepOrder.SPAWN # type: int
177 config_block = 'spawn_os_vms' # type: str
178
179 def run(self, ctx: TestRun) -> None:
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200180 if 'all_nodes' in ctx.storage:
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200181 ctx.os_spawned_nodes_ids = ctx.storage.get('os_spawned_nodes_ids')
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200182 logger.info("Skipping OS VMS discovery/spawn as all data found in storage")
183 return
184
185 if 'os_spawned_nodes_ids' in ctx.storage:
186 logger.error("spawned_os_nodes_ids is found in storage, but no nodes_info is stored." +
187 "Fix this before continue")
188 raise StopTestError()
189
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200190 vm_spawn_config = ctx.config.spawn_os_vms
191 vm_image_config = ctx.config.vm_configs[vm_spawn_config.cfg_name]
192
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200193 ensure_connected_to_openstack(ctx)
kdanylov aka koder13e58452018-07-15 02:51:51 +0300194 os_conn: OSConnection = ctx.os_connection # type: ignore # remove Optional[]
195
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200196 params = vm_image_config.copy()
197 params.update(vm_spawn_config)
198 params.update(get_vm_keypair_path(ctx.config))
199 params['group_name'] = ctx.config.run_uuid
200 params['keypair_name'] = ctx.config.vm_configs['keypair_name']
201
202 if not ctx.config.openstack.get("skip_preparation", False):
203 logger.info("Preparing openstack")
kdanylov aka koder13e58452018-07-15 02:51:51 +0300204 prepare_os(os_conn, params)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200205 else:
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200206 logger.info("Scip openstack preparation as 'skip_preparation' is set")
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200207
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200208 ctx.os_spawned_nodes_ids = []
209 with ctx.get_pool() as pool:
kdanylov aka koder13e58452018-07-15 02:51:51 +0300210 for info in launch_vms(os_conn, params, pool):
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200211 info.roles.add('testnode')
koder aka kdanilov108ac362017-01-19 20:17:16 +0200212 nid = info.node_id
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200213 if nid in ctx.nodes_info:
214 logger.error("Test VM node has the same id(%s), as existing node %s", nid, ctx.nodes_info[nid])
215 raise StopTestError()
216 ctx.nodes_info[nid] = info
217 ctx.os_spawned_nodes_ids.append(info.os_vm_id)
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200218
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200219 ctx.storage.put(ctx.os_spawned_nodes_ids, 'os_spawned_nodes_ids')
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200220
221 def cleanup(self, ctx: TestRun) -> None:
222 # keep nodes in case of error for future test restart
223 if not ctx.config.keep_vm and ctx.os_spawned_nodes_ids:
224 logger.info("Removing nodes")
225
kdanylov aka koder13e58452018-07-15 02:51:51 +0300226 clear_nodes(ctx.os_connection, ctx.os_spawned_nodes_ids) # type: ignore
koder aka kdanilov7f59d562016-12-26 01:34:23 +0200227 ctx.storage.rm('spawned_os_nodes')
koder aka kdanilov39e449e2016-12-17 15:15:26 +0200228
koder aka kdanilov962ee5f2016-12-19 02:40:08 +0200229 logger.info("OS spawned nodes has been successfully removed")