blob: 80818cffb9c7f2f39a146b7337c49730bc5134fb [file] [log] [blame]
Dennis Dmitriev6f59add2016-10-18 13:45:27 +03001# Copyright 2016 Mirantis, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15import os
16
17from devops import error
18from devops.helpers import helpers
19from devops import models
20from django import db
21from oslo_config import cfg
22
23from tcp_tests import settings
24from tcp_tests import settings_oslo
25from tcp_tests.helpers import env_config
26from tcp_tests.helpers import ext
27from tcp_tests.helpers import exceptions
28from tcp_tests import logger
29
30LOG = logger.logger
31
32
33class EnvironmentManager(object):
34 """Class-helper for creating VMs via devops environments"""
35
Dmitry Tyzhnenko1fb041c2017-04-28 16:07:48 +030036 __config = None
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030037
38 def __init__(self, config=None):
39 """Initializing class instance and create the environment
40
41 :param config: oslo.config object
42 :param config.hardware.conf_path: path to devops YAML template
43 :param config.hardware.current_snapshot: name of the snapshot that
44 descriebe environment status.
45 """
46 self.__devops_config = env_config.EnvironmentConfig()
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030047 self.__env = None
Dmitry Tyzhnenko1fb041c2017-04-28 16:07:48 +030048 self.__config = config
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030049
50 if config.hardware.conf_path is not None:
Artem Panchenkodb0a97f2017-06-27 19:09:13 +030051 options = {
52 'config': self.__config,
53 }
54 self._devops_config.load_template(config.hardware.conf_path,
55 options=options)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030056 else:
57 raise Exception("Devops YAML template is not set in config object")
58
59 try:
60 self._get_env_by_name(self._d_env_name)
61 if not self.has_snapshot(config.hardware.current_snapshot):
62 raise exceptions.EnvironmentSnapshotMissing(
63 self._d_env_name, config.hardware.current_snapshot)
64 except error.DevopsObjNotFound:
65 LOG.info("Environment doesn't exist, creating a new one")
66 self._create_environment()
67 self.set_dns_config()
Dennis Dmitriev99b26fe2017-04-26 12:34:44 +030068 self.set_address_pools_config()
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030069
70 @property
71 def _devops_config(self):
72 return self.__devops_config
73
74 @_devops_config.setter
75 def _devops_config(self, conf):
76 """Setter for self.__devops_config
77
78 :param conf: tcp_tests.helpers.env_config.EnvironmentConfig
79 """
80 if not isinstance(conf, env_config.EnvironmentConfig):
81 msg = ("Unexpected type of devops config. Got '{0}' " +
82 "instead of '{1}'")
83 raise TypeError(
84 msg.format(
85 type(conf).__name__,
86 env_config.EnvironmentConfig.__name__
87 )
88 )
89 self.__devops_config = conf
90
91 def lvm_storages(self):
92 """Returns a dict object of lvm storages in current environment
93
94 returned data example:
95 {
96 "master": {
97 "id": "virtio-bff72959d1a54cb19d08"
98 },
99 "slave-0": {
100 "id": "virtio-5e33affc8fe44503839f"
101 },
102 "slave-1": {
103 "id": "virtio-10b6a262f1ec4341a1ba"
104 },
105 }
106
107 :rtype: dict
108 """
109 result = {}
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300110 for node in self.__env.get_nodes(role__in=ext.UNDERLAY_NODE_ROLES):
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300111 lvm = filter(lambda x: x.volume.name == 'lvm', node.disk_devices)
112 if len(lvm) == 0:
113 continue
114 lvm = lvm[0]
115 result[node.name] = {}
116 result_node = result[node.name]
117 result_node['id'] = "{bus}-{serial}".format(
118 bus=lvm.bus,
119 serial=lvm.volume.serial[:20])
120 LOG.info("Got disk-id '{}' for node '{}'".format(
121 result_node['id'], node.name))
122 return result
123
124 @property
125 def _d_env_name(self):
126 """Get environment name from fuel devops config
127
128 :rtype: string
129 """
130 return self._devops_config['env_name']
131
132 def _get_env_by_name(self, name):
133 """Set existing environment by name
134
135 :param name: string
136 """
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300137 self.__env = models.Environment.get(name=name)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300138
139 def _get_default_node_group(self):
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300140 return self.__env.get_group(name='default')
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300141
142 def _get_network_pool(self, net_pool_name):
143 default_node_group = self._get_default_node_group()
144 network_pool = default_node_group.get_network_pool(name=net_pool_name)
145 return network_pool
146
147 def get_ssh_data(self, roles=None):
148 """Generate ssh config for Underlay
149
150 :param roles: list of strings
151 """
152 if roles is None:
153 raise Exception("No roles specified for the environment!")
154
155 config_ssh = []
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300156 for d_node in self.__env.get_nodes(role__in=roles):
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300157 ssh_data = {
158 'node_name': d_node.name,
Dennis Dmitriev474e3f72016-10-21 16:46:09 +0300159 'roles': [d_node.role],
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300160 'address_pool': self._get_network_pool(
disc5298382016-11-23 16:03:33 +0200161 ext.NETWORK_TYPE.admin).address_pool.name,
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300162 'host': self.node_ip(d_node),
163 'login': settings.SSH_NODE_CREDENTIALS['login'],
164 'password': settings.SSH_NODE_CREDENTIALS['password'],
Artem Panchenkodb0a97f2017-06-27 19:09:13 +0300165 'keys': [k['private'] for k in self.__config.underlay.ssh_keys]
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300166 }
167 config_ssh.append(ssh_data)
168 return config_ssh
169
Dennis Dmitriev411dd102017-09-15 16:04:47 +0300170 def create_snapshot(self, name, description=None, force=False):
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300171 """Create named snapshot of current env.
172
173 - Create a libvirt snapshots for all nodes in the environment
174 - Save 'config' object to a file 'config_<name>.ini'
175
176 :name: string
177 """
Dennis Dmitriev411dd102017-09-15 16:04:47 +0300178 if not settings.MAKE_SNAPSHOT_STAGES and not force:
179 msg = ("[ SKIP snapshot '{0}' because MAKE_SNAPSHOT_STAGES=false ]"
180 " {1}".format(name, description or ''))
181 LOG.info("\n\n{0}\n{1}".format(msg, '*' * len(msg)))
182 return
Dennis Dmitriev99b26fe2017-04-26 12:34:44 +0300183 msg = "[ Create snapshot '{0}' ] {1}".format(name, description or '')
184 LOG.info("\n\n{0}\n{1}".format(msg, '*' * len(msg)))
185
Dmitry Tyzhnenko1fb041c2017-04-28 16:07:48 +0300186 self.__config.hardware.current_snapshot = name
Dennis Dmitriev99b26fe2017-04-26 12:34:44 +0300187 LOG.info("Set current snapshot in config to '{0}'".format(
Dmitry Tyzhnenko1fb041c2017-04-28 16:07:48 +0300188 self.__config.hardware.current_snapshot))
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300189 if self.__env is not None:
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300190 LOG.info('trying to suspend ....')
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300191 self.__env.suspend()
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300192 LOG.info('trying to snapshot ....')
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300193 self.__env.snapshot(name, description=description, force=True)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300194 LOG.info('trying to resume ....')
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300195 self.__env.resume()
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300196 else:
197 raise exceptions.EnvironmentIsNotSet()
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300198 settings_oslo.save_config(self.__config, name, self.__env.name)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300199
Dennis Dmitriev99b26fe2017-04-26 12:34:44 +0300200 if settings.VIRTUAL_ENV:
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +0300201 venv_msg = "source {0}/bin/activate;\n".format(
202 settings.VIRTUAL_ENV)
Dennis Dmitriev99b26fe2017-04-26 12:34:44 +0300203 else:
204 venv_msg = ""
205 LOG.info("To revert the snapshot:\n\n"
206 "************************************\n"
207 "{venv_msg}"
208 "dos.py revert {env_name} {snapshot_name};\n"
209 "dos.py resume {env_name};\n"
210 "# dos.py time-sync {env_name}; # Optional\n"
Artem Panchenkodb0a97f2017-06-27 19:09:13 +0300211 "ssh -i {key_file} {login}@{salt_master_host} "
212 "# Optional password: {password}\n"
Dennis Dmitriev99b26fe2017-04-26 12:34:44 +0300213 "************************************\n"
214 .format(venv_msg=venv_msg,
Dennis Dmitriev68671a62017-05-13 16:40:32 +0300215 env_name=self._d_env_name,
Dennis Dmitriev99b26fe2017-04-26 12:34:44 +0300216 snapshot_name=name,
217 login=settings.SSH_NODE_CREDENTIALS['login'],
218 password=settings.SSH_NODE_CREDENTIALS['password'],
Artem Panchenkodb0a97f2017-06-27 19:09:13 +0300219 salt_master_host=self.__config.salt.salt_master_host,
220 key_file=self.__config.underlay.ssh_key_file))
Dennis Dmitriev99b26fe2017-04-26 12:34:44 +0300221
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300222 def _get_snapshot_config_name(self, snapshot_name):
223 """Get config name for the environment"""
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300224 env_name = self.__env.name
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300225 if env_name is None:
226 env_name = 'config'
227 test_config_path = os.path.join(
228 settings.LOGS_DIR, '{0}_{1}.ini'.format(env_name, snapshot_name))
229 return test_config_path
230
231 def revert_snapshot(self, name):
232 """Revert snapshot by name
233
234 - Revert a libvirt snapshots for all nodes in the environment
235 - Try to reload 'config' object from a file 'config_<name>.ini'
236 If the file not found, then pass with defaults.
237 - Set <name> as the current state of the environment after reload
238
239 :param name: string
240 """
Dennis Dmitriev411dd102017-09-15 16:04:47 +0300241 if not settings.MAKE_SNAPSHOT_STAGES:
242 LOG.info("SKIP reverting from snapshot '{0}' "
243 "because MAKE_SNAPSHOT_STAGES=false".format(name))
244 return
245
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300246 if self.__env is not None:
Dennis Dmitrieva5978eb2018-02-21 10:12:33 +0200247 LOG.info("Suspending environment to stop IO")
248 self.__env.suspend()
249 LOG.info("Reverting from snapshot named '{0}'".format(name))
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300250 self.__env.revert(name=name)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300251 LOG.info("Resuming environment after revert")
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300252 self.__env.resume()
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300253 else:
254 raise exceptions.EnvironmentIsNotSet()
255
256 try:
257 test_config_path = self._get_snapshot_config_name(name)
Dmitry Tyzhnenko1fb041c2017-04-28 16:07:48 +0300258 settings_oslo.reload_snapshot_config(self.__config,
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300259 test_config_path)
260 except cfg.ConfigFilesNotFoundError as conf_err:
261 LOG.error("Config file(s) {0} not found!".format(
262 conf_err.config_files))
263
Dmitry Tyzhnenko1fb041c2017-04-28 16:07:48 +0300264 self.__config.hardware.current_snapshot = name
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300265
266 def _create_environment(self):
267 """Create environment and start VMs.
268
269 If config was provided earlier, we simply create and start VMs,
270 otherwise we tries to generate config from self.config_file,
271 """
272 if self._devops_config.config is None:
273 raise exceptions.DevopsConfigPathIsNotSet()
274 settings = self._devops_config
275 env_name = settings['env_name']
276 LOG.debug(
277 'Preparing to create environment named "{0}"'.format(env_name)
278 )
279 if env_name is None:
280 LOG.error('Environment name is not set!')
281 raise exceptions.EnvironmentNameIsNotSet()
282 try:
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300283 self.__env = models.Environment.create_environment(
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300284 settings.config
285 )
286 except db.IntegrityError:
287 LOG.error(
dis2b2d8632016-12-08 17:56:57 +0200288 'Seems like environment {0} already exists or contain errors'
289 ' in template.'.format(env_name)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300290 )
dis2b2d8632016-12-08 17:56:57 +0200291 raise
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300292 self.__env.define()
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300293 LOG.info(
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300294 'Environment "{0}" created'.format(env_name)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300295 )
296
Dennis Dmitriev7b9538f2017-05-15 17:01:34 +0300297 def start(self, underlay_node_roles, timeout=480):
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300298 """Method for start environment
299
300 """
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300301 if self.__env is None:
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300302 raise exceptions.EnvironmentIsNotSet()
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300303 self.__env.start()
304 LOG.info('Environment "{0}" started'.format(self.__env.name))
Dennis Dmitriev7b9538f2017-05-15 17:01:34 +0300305 for node in self.__env.get_nodes(role__in=underlay_node_roles):
Dennis Dmitrieva63bac62017-05-15 18:36:26 +0300306 LOG.info("Waiting for SSH on node '{0}' / {1} ...".format(
307 node.name, self.node_ip(node)))
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300308 helpers.wait(
309 lambda: helpers.tcp_ping(self.node_ip(node), 22),
310 timeout=timeout,
311 timeout_msg="Node '{}' didn't open SSH in {} sec".format(
312 node.name, timeout
313 )
314 )
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300315 LOG.info('Environment "{0}" ready'.format(self.__env.name))
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300316
317 def resume(self):
318 """Resume environment"""
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300319 if self.__env is None:
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300320 raise exceptions.EnvironmentIsNotSet()
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300321 self.__env.resume()
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300322
323 def suspend(self):
324 """Suspend environment"""
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300325 if self.__env is None:
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300326 raise exceptions.EnvironmentIsNotSet()
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300327 self.__env.suspend()
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300328
329 def stop(self):
330 """Stop environment"""
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300331 if self.__env is None:
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300332 raise exceptions.EnvironmentIsNotSet()
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300333 self.__env.destroy()
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300334
Tatyana Leontoviche5ccdb32017-10-09 20:10:43 +0300335 def destroy_node(self, node_name):
336 """Destroy node"""
337 node = self.__env.get_node(name=node_name)
338 node.destroy()
339
340 def start_node(self, node_name):
341 """Start node"""
342 node = self.__env.get_node(name=node_name)
343 node.start()
344
345 def reboot_node(self, node_name):
346 """Reboot node"""
347 node = self.__env.get_node(name=node_name)
348 node.reboot()
349
350 def remove_node(self, node_name):
351 """Remove node"""
352 node = self.__env.get_node(name=node_name)
353 node.remove()
354
355 def wait_for_node_state(self, node_name, state, timeout):
356 node = self.__env.get_node(name=node_name)
357 if 'active' in state:
358 helpers.wait(lambda: node.is_active(),
359 timeout=timeout,
360 timeout_msg=('Node {0} failed '
361 'to become active'.format(node)))
362 else:
363 helpers.wait(lambda: not node.is_active(),
364 timeout=timeout,
365 timeout_msg=('Node {0} failed '
366 'to become active'.format(node)))
367
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300368 def has_snapshot(self, name):
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300369 return self.__env.has_snapshot(name)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300370
371 def has_snapshot_config(self, name):
372 test_config_path = self._get_snapshot_config_name(name)
373 return os.path.isfile(test_config_path)
374
375 def delete_environment(self):
376 """Delete environment
377
378 """
379 LOG.debug("Deleting environment")
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300380 self.__env.erase()
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300381
382 def __get_nodes_by_role(self, node_role):
383 """Get node by given role name
384
385 :param node_role: string
386 :rtype: devops.models.Node
387 """
388 LOG.debug('Trying to get nodes by role {0}'.format(node_role))
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300389 return self.__env.get_nodes(role=node_role)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300390
Tatyana Leontoviche5ccdb32017-10-09 20:10:43 +0300391 def __get_nodes_by_name(self, node_name):
392 """Get node by given role name
393
394 :param node_name: string
395 :rtype: devops.models.Node
396 """
397 LOG.debug('Trying to get nodes by role {0}'.format(node_name))
398 return self.__env.get_nodes(name=node_name)
399
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300400 @property
401 def master_nodes(self):
402 """Get all master nodes
403
404 :rtype: list
405 """
406 nodes = self.__get_nodes_by_role(
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300407 node_role=ext.UNDERLAY_NODE_ROLES.salt_master)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300408 return nodes
409
410 @property
411 def slave_nodes(self):
412 """Get all slave nodes
413
414 :rtype: list
415 """
416 nodes = self.__get_nodes_by_role(
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300417 node_role=ext.UNDERLAY_NODE_ROLES.salt_minion)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300418 return nodes
419
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300420 @staticmethod
421 def node_ip(node):
422 """Determine node's IP
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300423
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300424 :param node: devops.models.Node
425 :return: string
426 """
427 LOG.debug('Trying to determine {0} ip.'.format(node.name))
428 return node.get_ip_address_by_network_name(
disc5298382016-11-23 16:03:33 +0200429 ext.NETWORK_TYPE.admin
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300430 )
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300431
432 @property
433 def nameserver(self):
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300434 return self.__env.router(ext.NETWORK_TYPE.admin)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300435
436 def set_dns_config(self):
437 # Set local nameserver to use by default
Dmitry Tyzhnenko1fb041c2017-04-28 16:07:48 +0300438 if not self.__config.underlay.nameservers:
439 self.__config.underlay.nameservers = [self.nameserver]
440 if not self.__config.underlay.upstream_dns_servers:
441 self.__config.underlay.upstream_dns_servers = [self.nameserver]
Dennis Dmitriev99b26fe2017-04-26 12:34:44 +0300442
443 def set_address_pools_config(self):
444 """Store address pools CIDRs in config object"""
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300445 for ap in self.__env.get_address_pools():
Dmitry Tyzhnenko1fb041c2017-04-28 16:07:48 +0300446 self.__config.underlay.address_pools[ap.name] = ap.net