blob: e25faef0556936174cb800b2e6e6a1ae2950129f [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
36 __config = None
37
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()
47 self._env = None
48 self.__config = config
49
50 if config.hardware.conf_path is not None:
51 self._devops_config.load_template(config.hardware.conf_path)
52 else:
53 raise Exception("Devops YAML template is not set in config object")
54
55 try:
56 self._get_env_by_name(self._d_env_name)
57 if not self.has_snapshot(config.hardware.current_snapshot):
58 raise exceptions.EnvironmentSnapshotMissing(
59 self._d_env_name, config.hardware.current_snapshot)
60 except error.DevopsObjNotFound:
61 LOG.info("Environment doesn't exist, creating a new one")
62 self._create_environment()
63 self.set_dns_config()
Dennis Dmitriev99b26fe2017-04-26 12:34:44 +030064 self.set_address_pools_config()
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030065
66 @property
67 def _devops_config(self):
68 return self.__devops_config
69
70 @_devops_config.setter
71 def _devops_config(self, conf):
72 """Setter for self.__devops_config
73
74 :param conf: tcp_tests.helpers.env_config.EnvironmentConfig
75 """
76 if not isinstance(conf, env_config.EnvironmentConfig):
77 msg = ("Unexpected type of devops config. Got '{0}' " +
78 "instead of '{1}'")
79 raise TypeError(
80 msg.format(
81 type(conf).__name__,
82 env_config.EnvironmentConfig.__name__
83 )
84 )
85 self.__devops_config = conf
86
87 def lvm_storages(self):
88 """Returns a dict object of lvm storages in current environment
89
90 returned data example:
91 {
92 "master": {
93 "id": "virtio-bff72959d1a54cb19d08"
94 },
95 "slave-0": {
96 "id": "virtio-5e33affc8fe44503839f"
97 },
98 "slave-1": {
99 "id": "virtio-10b6a262f1ec4341a1ba"
100 },
101 }
102
103 :rtype: dict
104 """
105 result = {}
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300106 for node in self._env.get_nodes(role__in=ext.UNDERLAY_NODE_ROLES):
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300107 lvm = filter(lambda x: x.volume.name == 'lvm', node.disk_devices)
108 if len(lvm) == 0:
109 continue
110 lvm = lvm[0]
111 result[node.name] = {}
112 result_node = result[node.name]
113 result_node['id'] = "{bus}-{serial}".format(
114 bus=lvm.bus,
115 serial=lvm.volume.serial[:20])
116 LOG.info("Got disk-id '{}' for node '{}'".format(
117 result_node['id'], node.name))
118 return result
119
120 @property
121 def _d_env_name(self):
122 """Get environment name from fuel devops config
123
124 :rtype: string
125 """
126 return self._devops_config['env_name']
127
128 def _get_env_by_name(self, name):
129 """Set existing environment by name
130
131 :param name: string
132 """
133 self._env = models.Environment.get(name=name)
134
135 def _get_default_node_group(self):
136 return self._env.get_group(name='default')
137
138 def _get_network_pool(self, net_pool_name):
139 default_node_group = self._get_default_node_group()
140 network_pool = default_node_group.get_network_pool(name=net_pool_name)
141 return network_pool
142
143 def get_ssh_data(self, roles=None):
144 """Generate ssh config for Underlay
145
146 :param roles: list of strings
147 """
148 if roles is None:
149 raise Exception("No roles specified for the environment!")
150
151 config_ssh = []
152 for d_node in self._env.get_nodes(role__in=roles):
153 ssh_data = {
154 'node_name': d_node.name,
Dennis Dmitriev474e3f72016-10-21 16:46:09 +0300155 'roles': [d_node.role],
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300156 'address_pool': self._get_network_pool(
disc5298382016-11-23 16:03:33 +0200157 ext.NETWORK_TYPE.admin).address_pool.name,
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300158 'host': self.node_ip(d_node),
159 'login': settings.SSH_NODE_CREDENTIALS['login'],
160 'password': settings.SSH_NODE_CREDENTIALS['password'],
161 }
162 config_ssh.append(ssh_data)
163 return config_ssh
164
165 def create_snapshot(self, name, description=None):
166 """Create named snapshot of current env.
167
168 - Create a libvirt snapshots for all nodes in the environment
169 - Save 'config' object to a file 'config_<name>.ini'
170
171 :name: string
172 """
Dennis Dmitriev99b26fe2017-04-26 12:34:44 +0300173 msg = "[ Create snapshot '{0}' ] {1}".format(name, description or '')
174 LOG.info("\n\n{0}\n{1}".format(msg, '*' * len(msg)))
175
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300176 self.__config.hardware.current_snapshot = name
Dennis Dmitriev99b26fe2017-04-26 12:34:44 +0300177 LOG.info("Set current snapshot in config to '{0}'".format(
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300178 self.__config.hardware.current_snapshot))
179 if self._env is not None:
180 LOG.info('trying to suspend ....')
181 self._env.suspend()
182 LOG.info('trying to snapshot ....')
183 self._env.snapshot(name, description=description, force=True)
184 LOG.info('trying to resume ....')
185 self._env.resume()
186 else:
187 raise exceptions.EnvironmentIsNotSet()
188 settings_oslo.save_config(self.__config, name, self._env.name)
189
Dennis Dmitriev99b26fe2017-04-26 12:34:44 +0300190 if settings.VIRTUAL_ENV:
191 venv_msg = "source {0}/bin/activate;\n".format(settings.VIRTUAL_ENV)
192 else:
193 venv_msg = ""
194 LOG.info("To revert the snapshot:\n\n"
195 "************************************\n"
196 "{venv_msg}"
197 "dos.py revert {env_name} {snapshot_name};\n"
198 "dos.py resume {env_name};\n"
199 "# dos.py time-sync {env_name}; # Optional\n"
200 "ssh {login}@{salt_master_host} # Password: {password}\n"
201 "************************************\n"
202 .format(venv_msg=venv_msg,
203 env_name=settings.ENV_NAME,
204 snapshot_name=name,
205 login=settings.SSH_NODE_CREDENTIALS['login'],
206 password=settings.SSH_NODE_CREDENTIALS['password'],
207 salt_master_host=self.__config.salt.salt_master_host))
208
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300209 def _get_snapshot_config_name(self, snapshot_name):
210 """Get config name for the environment"""
211 env_name = self._env.name
212 if env_name is None:
213 env_name = 'config'
214 test_config_path = os.path.join(
215 settings.LOGS_DIR, '{0}_{1}.ini'.format(env_name, snapshot_name))
216 return test_config_path
217
218 def revert_snapshot(self, name):
219 """Revert snapshot by name
220
221 - Revert a libvirt snapshots for all nodes in the environment
222 - Try to reload 'config' object from a file 'config_<name>.ini'
223 If the file not found, then pass with defaults.
224 - Set <name> as the current state of the environment after reload
225
226 :param name: string
227 """
228 LOG.info("Reverting from snapshot named '{0}'".format(name))
229 if self._env is not None:
230 self._env.revert(name=name)
231 LOG.info("Resuming environment after revert")
232 self._env.resume()
233 else:
234 raise exceptions.EnvironmentIsNotSet()
235
236 try:
237 test_config_path = self._get_snapshot_config_name(name)
238 settings_oslo.reload_snapshot_config(self.__config,
239 test_config_path)
240 except cfg.ConfigFilesNotFoundError as conf_err:
241 LOG.error("Config file(s) {0} not found!".format(
242 conf_err.config_files))
243
244 self.__config.hardware.current_snapshot = name
245
246 def _create_environment(self):
247 """Create environment and start VMs.
248
249 If config was provided earlier, we simply create and start VMs,
250 otherwise we tries to generate config from self.config_file,
251 """
252 if self._devops_config.config is None:
253 raise exceptions.DevopsConfigPathIsNotSet()
254 settings = self._devops_config
255 env_name = settings['env_name']
256 LOG.debug(
257 'Preparing to create environment named "{0}"'.format(env_name)
258 )
259 if env_name is None:
260 LOG.error('Environment name is not set!')
261 raise exceptions.EnvironmentNameIsNotSet()
262 try:
263 self._env = models.Environment.create_environment(
264 settings.config
265 )
266 except db.IntegrityError:
267 LOG.error(
dis2b2d8632016-12-08 17:56:57 +0200268 'Seems like environment {0} already exists or contain errors'
269 ' in template.'.format(env_name)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300270 )
dis2b2d8632016-12-08 17:56:57 +0200271 raise
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300272 self._env.define()
273 LOG.info(
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300274 'Environment "{0}" created'.format(env_name)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300275 )
276
277 def start(self):
278 """Method for start environment
279
280 """
281 if self._env is None:
282 raise exceptions.EnvironmentIsNotSet()
283 self._env.start()
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300284 LOG.info('Environment "{0}" started'.format(self._env.name))
285 for node in self._env.get_nodes(role__in=ext.UNDERLAY_NODE_ROLES):
286 LOG.info("Waiting for SSH on node '{}...'".format(node.name))
disc5298382016-11-23 16:03:33 +0200287 timeout = 480
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300288 helpers.wait(
289 lambda: helpers.tcp_ping(self.node_ip(node), 22),
290 timeout=timeout,
291 timeout_msg="Node '{}' didn't open SSH in {} sec".format(
292 node.name, timeout
293 )
294 )
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300295 LOG.info('Environment "{0}" ready'.format(self._env.name))
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300296
297 def resume(self):
298 """Resume environment"""
299 if self._env is None:
300 raise exceptions.EnvironmentIsNotSet()
301 self._env.resume()
302
303 def suspend(self):
304 """Suspend environment"""
305 if self._env is None:
306 raise exceptions.EnvironmentIsNotSet()
307 self._env.suspend()
308
309 def stop(self):
310 """Stop environment"""
311 if self._env is None:
312 raise exceptions.EnvironmentIsNotSet()
313 self._env.destroy()
314
315 def has_snapshot(self, name):
316 return self._env.has_snapshot(name)
317
318 def has_snapshot_config(self, name):
319 test_config_path = self._get_snapshot_config_name(name)
320 return os.path.isfile(test_config_path)
321
322 def delete_environment(self):
323 """Delete environment
324
325 """
326 LOG.debug("Deleting environment")
327 self._env.erase()
328
329 def __get_nodes_by_role(self, node_role):
330 """Get node by given role name
331
332 :param node_role: string
333 :rtype: devops.models.Node
334 """
335 LOG.debug('Trying to get nodes by role {0}'.format(node_role))
336 return self._env.get_nodes(role=node_role)
337
338 @property
339 def master_nodes(self):
340 """Get all master nodes
341
342 :rtype: list
343 """
344 nodes = self.__get_nodes_by_role(
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300345 node_role=ext.UNDERLAY_NODE_ROLES.salt_master)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300346 return nodes
347
348 @property
349 def slave_nodes(self):
350 """Get all slave nodes
351
352 :rtype: list
353 """
354 nodes = self.__get_nodes_by_role(
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300355 node_role=ext.UNDERLAY_NODE_ROLES.salt_minion)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300356 return nodes
357
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300358 @staticmethod
359 def node_ip(node):
360 """Determine node's IP
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300361
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300362 :param node: devops.models.Node
363 :return: string
364 """
365 LOG.debug('Trying to determine {0} ip.'.format(node.name))
366 return node.get_ip_address_by_network_name(
disc5298382016-11-23 16:03:33 +0200367 ext.NETWORK_TYPE.admin
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300368 )
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300369
370 @property
371 def nameserver(self):
disc5298382016-11-23 16:03:33 +0200372 return self._env.router(ext.NETWORK_TYPE.admin)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300373
374 def set_dns_config(self):
375 # Set local nameserver to use by default
376 if not self.__config.underlay.nameservers:
377 self.__config.underlay.nameservers = [self.nameserver]
378 if not self.__config.underlay.upstream_dns_servers:
379 self.__config.underlay.upstream_dns_servers = [self.nameserver]
Dennis Dmitriev99b26fe2017-04-26 12:34:44 +0300380
381 def set_address_pools_config(self):
382 """Store address pools CIDRs in config object"""
383 for ap in self._env.get_address_pools():
384 self.__config.underlay.address_pools[ap.name] = ap.net