blob: 0f0199910e45200a668fab0b398adb85273f286c [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()
64
65 @property
66 def _devops_config(self):
67 return self.__devops_config
68
69 @_devops_config.setter
70 def _devops_config(self, conf):
71 """Setter for self.__devops_config
72
73 :param conf: tcp_tests.helpers.env_config.EnvironmentConfig
74 """
75 if not isinstance(conf, env_config.EnvironmentConfig):
76 msg = ("Unexpected type of devops config. Got '{0}' " +
77 "instead of '{1}'")
78 raise TypeError(
79 msg.format(
80 type(conf).__name__,
81 env_config.EnvironmentConfig.__name__
82 )
83 )
84 self.__devops_config = conf
85
86 def lvm_storages(self):
87 """Returns a dict object of lvm storages in current environment
88
89 returned data example:
90 {
91 "master": {
92 "id": "virtio-bff72959d1a54cb19d08"
93 },
94 "slave-0": {
95 "id": "virtio-5e33affc8fe44503839f"
96 },
97 "slave-1": {
98 "id": "virtio-10b6a262f1ec4341a1ba"
99 },
100 }
101
102 :rtype: dict
103 """
104 result = {}
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300105 for node in self._env.get_nodes(role__in=ext.UNDERLAY_NODE_ROLES):
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300106 lvm = filter(lambda x: x.volume.name == 'lvm', node.disk_devices)
107 if len(lvm) == 0:
108 continue
109 lvm = lvm[0]
110 result[node.name] = {}
111 result_node = result[node.name]
112 result_node['id'] = "{bus}-{serial}".format(
113 bus=lvm.bus,
114 serial=lvm.volume.serial[:20])
115 LOG.info("Got disk-id '{}' for node '{}'".format(
116 result_node['id'], node.name))
117 return result
118
119 @property
120 def _d_env_name(self):
121 """Get environment name from fuel devops config
122
123 :rtype: string
124 """
125 return self._devops_config['env_name']
126
127 def _get_env_by_name(self, name):
128 """Set existing environment by name
129
130 :param name: string
131 """
132 self._env = models.Environment.get(name=name)
133
134 def _get_default_node_group(self):
135 return self._env.get_group(name='default')
136
137 def _get_network_pool(self, net_pool_name):
138 default_node_group = self._get_default_node_group()
139 network_pool = default_node_group.get_network_pool(name=net_pool_name)
140 return network_pool
141
142 def get_ssh_data(self, roles=None):
143 """Generate ssh config for Underlay
144
145 :param roles: list of strings
146 """
147 if roles is None:
148 raise Exception("No roles specified for the environment!")
149
150 config_ssh = []
151 for d_node in self._env.get_nodes(role__in=roles):
152 ssh_data = {
153 'node_name': d_node.name,
Dennis Dmitriev474e3f72016-10-21 16:46:09 +0300154 'roles': [d_node.role],
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300155 'address_pool': self._get_network_pool(
disc5298382016-11-23 16:03:33 +0200156 ext.NETWORK_TYPE.admin).address_pool.name,
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300157 'host': self.node_ip(d_node),
158 'login': settings.SSH_NODE_CREDENTIALS['login'],
159 'password': settings.SSH_NODE_CREDENTIALS['password'],
160 }
161 config_ssh.append(ssh_data)
162 return config_ssh
163
164 def create_snapshot(self, name, description=None):
165 """Create named snapshot of current env.
166
167 - Create a libvirt snapshots for all nodes in the environment
168 - Save 'config' object to a file 'config_<name>.ini'
169
170 :name: string
171 """
172 LOG.info("Creating snapshot named '{0}'".format(name))
173 self.__config.hardware.current_snapshot = name
174 LOG.info("current config '{0}'".format(
175 self.__config.hardware.current_snapshot))
176 if self._env is not None:
177 LOG.info('trying to suspend ....')
178 self._env.suspend()
179 LOG.info('trying to snapshot ....')
180 self._env.snapshot(name, description=description, force=True)
181 LOG.info('trying to resume ....')
182 self._env.resume()
183 else:
184 raise exceptions.EnvironmentIsNotSet()
185 settings_oslo.save_config(self.__config, name, self._env.name)
186
187 def _get_snapshot_config_name(self, snapshot_name):
188 """Get config name for the environment"""
189 env_name = self._env.name
190 if env_name is None:
191 env_name = 'config'
192 test_config_path = os.path.join(
193 settings.LOGS_DIR, '{0}_{1}.ini'.format(env_name, snapshot_name))
194 return test_config_path
195
196 def revert_snapshot(self, name):
197 """Revert snapshot by name
198
199 - Revert a libvirt snapshots for all nodes in the environment
200 - Try to reload 'config' object from a file 'config_<name>.ini'
201 If the file not found, then pass with defaults.
202 - Set <name> as the current state of the environment after reload
203
204 :param name: string
205 """
206 LOG.info("Reverting from snapshot named '{0}'".format(name))
207 if self._env is not None:
208 self._env.revert(name=name)
209 LOG.info("Resuming environment after revert")
210 self._env.resume()
211 else:
212 raise exceptions.EnvironmentIsNotSet()
213
214 try:
215 test_config_path = self._get_snapshot_config_name(name)
216 settings_oslo.reload_snapshot_config(self.__config,
217 test_config_path)
218 except cfg.ConfigFilesNotFoundError as conf_err:
219 LOG.error("Config file(s) {0} not found!".format(
220 conf_err.config_files))
221
222 self.__config.hardware.current_snapshot = name
223
224 def _create_environment(self):
225 """Create environment and start VMs.
226
227 If config was provided earlier, we simply create and start VMs,
228 otherwise we tries to generate config from self.config_file,
229 """
230 if self._devops_config.config is None:
231 raise exceptions.DevopsConfigPathIsNotSet()
232 settings = self._devops_config
233 env_name = settings['env_name']
234 LOG.debug(
235 'Preparing to create environment named "{0}"'.format(env_name)
236 )
237 if env_name is None:
238 LOG.error('Environment name is not set!')
239 raise exceptions.EnvironmentNameIsNotSet()
240 try:
241 self._env = models.Environment.create_environment(
242 settings.config
243 )
244 except db.IntegrityError:
245 LOG.error(
dis2b2d8632016-12-08 17:56:57 +0200246 'Seems like environment {0} already exists or contain errors'
247 ' in template.'.format(env_name)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300248 )
dis2b2d8632016-12-08 17:56:57 +0200249 raise
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300250 self._env.define()
251 LOG.info(
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300252 'Environment "{0}" created'.format(env_name)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300253 )
254
255 def start(self):
256 """Method for start environment
257
258 """
259 if self._env is None:
260 raise exceptions.EnvironmentIsNotSet()
261 self._env.start()
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300262 LOG.info('Environment "{0}" started'.format(self._env.name))
263 for node in self._env.get_nodes(role__in=ext.UNDERLAY_NODE_ROLES):
264 LOG.info("Waiting for SSH on node '{}...'".format(node.name))
disc5298382016-11-23 16:03:33 +0200265 timeout = 480
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300266 helpers.wait(
267 lambda: helpers.tcp_ping(self.node_ip(node), 22),
268 timeout=timeout,
269 timeout_msg="Node '{}' didn't open SSH in {} sec".format(
270 node.name, timeout
271 )
272 )
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300273 LOG.info('Environment "{0}" ready'.format(self._env.name))
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300274
275 def resume(self):
276 """Resume environment"""
277 if self._env is None:
278 raise exceptions.EnvironmentIsNotSet()
279 self._env.resume()
280
281 def suspend(self):
282 """Suspend environment"""
283 if self._env is None:
284 raise exceptions.EnvironmentIsNotSet()
285 self._env.suspend()
286
287 def stop(self):
288 """Stop environment"""
289 if self._env is None:
290 raise exceptions.EnvironmentIsNotSet()
291 self._env.destroy()
292
293 def has_snapshot(self, name):
294 return self._env.has_snapshot(name)
295
296 def has_snapshot_config(self, name):
297 test_config_path = self._get_snapshot_config_name(name)
298 return os.path.isfile(test_config_path)
299
300 def delete_environment(self):
301 """Delete environment
302
303 """
304 LOG.debug("Deleting environment")
305 self._env.erase()
306
307 def __get_nodes_by_role(self, node_role):
308 """Get node by given role name
309
310 :param node_role: string
311 :rtype: devops.models.Node
312 """
313 LOG.debug('Trying to get nodes by role {0}'.format(node_role))
314 return self._env.get_nodes(role=node_role)
315
316 @property
317 def master_nodes(self):
318 """Get all master nodes
319
320 :rtype: list
321 """
322 nodes = self.__get_nodes_by_role(
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300323 node_role=ext.UNDERLAY_NODE_ROLES.salt_master)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300324 return nodes
325
326 @property
327 def slave_nodes(self):
328 """Get all slave nodes
329
330 :rtype: list
331 """
332 nodes = self.__get_nodes_by_role(
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300333 node_role=ext.UNDERLAY_NODE_ROLES.salt_minion)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300334 return nodes
335
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300336 @staticmethod
337 def node_ip(node):
338 """Determine node's IP
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300339
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300340 :param node: devops.models.Node
341 :return: string
342 """
343 LOG.debug('Trying to determine {0} ip.'.format(node.name))
344 return node.get_ip_address_by_network_name(
disc5298382016-11-23 16:03:33 +0200345 ext.NETWORK_TYPE.admin
Dennis Dmitriev53d3b772016-10-18 14:31:58 +0300346 )
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300347
348 @property
349 def nameserver(self):
disc5298382016-11-23 16:03:33 +0200350 return self._env.router(ext.NETWORK_TYPE.admin)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300351
352 def set_dns_config(self):
353 # Set local nameserver to use by default
354 if not self.__config.underlay.nameservers:
355 self.__config.underlay.nameservers = [self.nameserver]
356 if not self.__config.underlay.upstream_dns_servers:
357 self.__config.underlay.upstream_dns_servers = [self.nameserver]