blob: cff615059b841c287a9008df02d355bc367c1e23 [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
16logger = logging.getLogger("wally.discover")
17
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:
107 cfg = ctx.config.openstack
108 os_nodes_auth = cfg.auth # type: str
109
110 if os_nodes_auth.count(":") == 2:
111 user, password, key_file = os_nodes_auth.split(":") # type: str, Optional[str], Optional[str]
112 if not password:
113 password = None
114 else:
115 user, password = os_nodes_auth.split(":")
116 key_file = None
117
118 ensure_connected_to_openstack(ctx)
119
120 if 'openstack_nodes' in ctx.storage:
121 ctx.nodes_info.extend(ctx.storage.load_list(NodeInfo, "openstack_nodes"))
122 else:
123 openstack_nodes = [] # type: List[NodeInfo]
124 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)
135 openstack_nodes.append(NodeInfo(creds, set(services)))
136
137 ctx.nodes_info.extend(openstack_nodes)
138 ctx.storage['openstack_nodes'] = openstack_nodes # type: ignore
139
140 if "reused_os_nodes" in ctx.storage:
141 ctx.nodes_info.extend(ctx.storage.load_list(NodeInfo, "reused_nodes"))
142 else:
143 reused_nodes = [] # type: List[NodeInfo]
144 private_key_path = get_vm_keypair_path(ctx.config)[0]
145
146 vm_creds = None # type: str
147 for vm_creds in cfg.get("vms", []):
148 user_name, vm_name_pattern = vm_creds.split("@", 1)
149 msg = "Vm like {} lookup failed".format(vm_name_pattern)
150
151 with LogError(msg):
152 msg = "Looking for vm with name like {0}".format(vm_name_pattern)
153 logger.debug(msg)
154
155 ensure_connected_to_openstack(ctx)
156
157 for ip, vm_id in find_vms(ctx.os_connection, vm_name_pattern):
158 creds = ConnCreds(host=ip, user=user_name, key_file=private_key_path)
159 node_info = NodeInfo(creds, {'testnode'})
160 node_info.os_vm_id = vm_id
161 reused_nodes.append(node_info)
162
163 ctx.nodes_info.extend(reused_nodes)
164 ctx.storage["reused_os_nodes"] = reused_nodes # type: ignore
165
166
167class CreateOSVMSStage(Stage):
168 "Spawn new VM's in Openstack cluster"
169
170 priority = StepOrder.SPAWN # type: int
171 config_block = 'spawn_os_vms' # type: str
172
173 def run(self, ctx: TestRun) -> None:
174 vm_spawn_config = ctx.config.spawn_os_vms
175 vm_image_config = ctx.config.vm_configs[vm_spawn_config.cfg_name]
176
177 if 'spawned_os_nodes' in ctx.storage:
178 ctx.nodes_info.extend(ctx.storage.load_list(NodeInfo, "spawned_os_nodes"))
179 else:
180 ensure_connected_to_openstack(ctx)
181 params = vm_image_config.copy()
182 params.update(vm_spawn_config)
183 params.update(get_vm_keypair_path(ctx.config))
184 params['group_name'] = ctx.config.run_uuid
185 params['keypair_name'] = ctx.config.vm_configs['keypair_name']
186
187 if not ctx.config.openstack.get("skip_preparation", False):
188 logger.info("Preparing openstack")
189 prepare_os(ctx.os_connection, params)
190
191 new_nodes = []
192 ctx.os_spawned_nodes_ids = []
193 with ctx.get_pool() as pool:
194 for node_info in launch_vms(ctx.os_connection, params, pool):
195 node_info.roles.add('testnode')
196 ctx.os_spawned_nodes_ids.append(node_info.os_vm_id)
197 new_nodes.append(node_info)
198
199 ctx.storage['spawned_os_nodes'] = new_nodes # type: ignore
200
201 def cleanup(self, ctx: TestRun) -> None:
202 # keep nodes in case of error for future test restart
203 if not ctx.config.keep_vm and ctx.os_spawned_nodes_ids:
204 logger.info("Removing nodes")
205
206 clear_nodes(ctx.os_connection, ctx.os_spawned_nodes_ids)
207 del ctx.storage['spawned_os_nodes']
208
209 logger.info("Nodes has been removed")
210
211
212
213# @contextlib.contextmanager
214# def suspend_vm_nodes_ctx(ctx: TestRun, unused_nodes: List[IRPCNode]) -> Iterator[List[int]]:
215#
216# pausable_nodes_ids = [cast(int, node.info.os_vm_id)
217# for node in unused_nodes
218# if node.info.os_vm_id is not None]
219#
220# non_pausable = len(unused_nodes) - len(pausable_nodes_ids)
221#
222# if non_pausable:
223# logger.warning("Can't pause {} nodes".format(non_pausable))
224#
225# if pausable_nodes_ids:
226# logger.debug("Try to pause {} unused nodes".format(len(pausable_nodes_ids)))
227# with ctx.get_pool() as pool:
228# openstack_api.pause(ctx.os_connection, pausable_nodes_ids, pool)
229#
230# try:
231# yield pausable_nodes_ids
232# finally:
233# if pausable_nodes_ids:
234# logger.debug("Unpausing {} nodes".format(len(pausable_nodes_ids)))
235# with ctx.get_pool() as pool:
236# openstack_api.unpause(ctx.os_connection, pausable_nodes_ids, pool)
237# def clouds_connect_stage(ctx: TestRun) -> None:
238 # TODO(koder): need to use this to connect to openstack in upper code
239 # conn = ctx.config['clouds/openstack']
240 # user, passwd, tenant = parse_creds(conn['creds'])
241 # auth_data = dict(auth_url=conn['auth_url'],
242 # username=user,
243 # api_key=passwd,
244 # project_id=tenant) # type: Dict[str, str]
245 # logger.debug("Discovering openstack nodes with connection details: %r", conn)
246 # connect to openstack, fuel
247
248 # # parse FUEL REST credentials
249 # username, tenant_name, password = parse_creds(fuel_data['creds'])
250 # creds = {"username": username,
251 # "tenant_name": tenant_name,
252 # "password": password}
253 #
254 # # connect to FUEL
255 # conn = fuel_rest_api.KeystoneAuth(fuel_data['url'], creds, headers=None)
256 # pass