blob: 1ff5324004a151a2a9c57b04a2689e80a552d408 [file] [log] [blame]
Dennis Dmitriev010f4cd2016-11-01 20:43:51 +02001# 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
Dmitry Tyzhnenkob8641832017-11-07 17:02:47 +020015import netaddr
16
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030017from collections import defaultdict
18
19from datetime import datetime
Dennis Dmitrievb8115f52017-12-15 13:09:56 +020020from pepper import libpepper
21from tcp_tests.helpers import utils
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030022from tcp_tests import settings
23from tcp_tests import logger
24from tcp_tests.managers.execute_commands import ExecuteCommandsMixin
25
26LOG = logger.logger
27
28
29class SaltManager(ExecuteCommandsMixin):
Dennis Dmitriev010f4cd2016-11-01 20:43:51 +020030 """docstring for SaltManager"""
31
Dmitry Tyzhnenkobc0f8262017-04-28 15:39:26 +030032 __config = None
33 __underlay = None
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030034 _map = {
35 'enforceState': 'enforce_state',
36 'enforceStates': 'enforce_states',
37 'runState': 'run_state',
38 'runStates': 'run_states',
39 }
Dennis Dmitriev010f4cd2016-11-01 20:43:51 +020040
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030041 def __init__(self, config, underlay, host=None, port='6969'):
Dmitry Tyzhnenkobc0f8262017-04-28 15:39:26 +030042 self.__config = config
43 self.__underlay = underlay
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030044 self.__port = port
45 self.__host = host
46 self.__api = None
47 self.__user = settings.SALT_USER
48 self.__password = settings.SALT_PASSWORD
Dennis Dmitrieveac3aab2017-07-12 16:36:41 +030049 self._salt = self
Dennis Dmitriev010f4cd2016-11-01 20:43:51 +020050
Dmitry Tyzhnenkobc0f8262017-04-28 15:39:26 +030051 super(SaltManager, self).__init__(config=config, underlay=underlay)
Dennis Dmitriev010f4cd2016-11-01 20:43:51 +020052
53 def install(self, commands):
Dina Belovae6fdffb2017-09-19 13:58:34 -070054 # if self.__config.salt.salt_master_host == '0.0.0.0':
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030055 # # Temporary workaround. Underlay should be extended with roles
56 # salt_nodes = self.__underlay.node_names()
57 # self.__config.salt.salt_master_host = \
58 # self.__underlay.host_by_node_name(salt_nodes[0])
Dennis Dmitriev010f4cd2016-11-01 20:43:51 +020059
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030060 self.execute_commands(commands=commands,
61 label="Install and configure salt")
62
63 @property
64 def port(self):
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030065 return self.__port
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030066
67 @property
68 def host(self):
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030069 if self.__host:
70 return self.__host
71 else:
Dina Belovae6fdffb2017-09-19 13:58:34 -070072 # TODO(ddmitriev): consider to add a check and raise
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030073 # exception if 'salt_master_host' is not initialized.
74 return self.__config.salt.salt_master_host
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030075
76 @property
77 def api(self):
78 def login():
79 LOG.info("Authentication in Salt API")
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030080 self.__api.login(
81 username=self.__user,
82 password=self.__password,
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030083 eauth='pam')
84 return datetime.now()
85
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030086 if self.__api:
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030087 if (datetime.now() - self.__session_start).seconds < 5 * 60:
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030088 return self.__api
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030089 else:
90 # FIXXME: Change to debug
91 LOG.info("Session's expired")
92 self.__session_start = login()
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030093 return self.__api
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030094
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030095 url = "http://{host}:{port}".format(
96 host=self.host, port=self.port)
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030097 LOG.info("Connecting to Salt API {0}".format(url))
Dennis Dmitrievb8115f52017-12-15 13:09:56 +020098 self.__api = libpepper.Pepper(url)
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030099 self.__session_start = login()
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +0300100 return self.__api
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +0300101
102 def local(self, tgt, fun, args=None, kwargs=None):
103 return self.api.local(tgt, fun, args, kwargs, expr_form='compound')
104
105 def local_async(self, tgt, fun, args=None, kwargs=None):
106 return self.api.local_async(tgt, fun, args, kwargs)
107
108 def lookup_result(self, jid):
109 return self.api.lookup_jid(jid)
110
111 def check_result(self, r):
112 if len(r.get('return', [])) == 0:
113 raise LookupError("Result is empty or absent")
114
115 result = r['return'][0]
Dmitry Tyzhnenkobc0f8262017-04-28 15:39:26 +0300116 if len(result) == 0:
117 raise LookupError("Result is empty or absent")
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +0300118 LOG.info("Job has result for %s nodes", result.keys())
119 fails = defaultdict(list)
120 for h in result:
121 host_result = result[h]
122 LOG.info("On %s executed:", h)
123 if isinstance(host_result, list):
124 fails[h].append(host_result)
125 continue
126 for t in host_result:
127 task = host_result[t]
128 if task['result'] is False:
129 fails[h].append(task)
130 LOG.error("%s - %s", t, task['result'])
131 else:
132 LOG.info("%s - %s", t, task['result'])
133
134 return fails if fails else None
135
136 def enforce_state(self, tgt, state, args=None, kwargs=None):
137 r = self.local(tgt=tgt, fun='state.sls', args=state)
138 f = self.check_result(r)
139 return r, f
140
141 def enforce_states(self, tgt, state, args=None, kwargs=None):
142 rets = []
143 for s in state:
144 r = self.enforce_state(tgt=tgt, state=s)
145 rets.append(r)
146 return rets
147
148 def run_state(self, tgt, state, args=None, kwargs=None):
149 return self.local(tgt=tgt, fun=state, args=args, kwargs=kwargs), None
150
151 def run_states(self, tgt, state, args=None, kwargs=None):
152 rets = []
153 for s in state:
154 r = self.run_state(tgt=tgt, state=s, args=args, kwargs=kwargs)
155 rets.append(r)
156 return rets
Artem Panchenko0594cd72017-06-12 13:25:26 +0300157
158 def get_pillar(self, tgt, pillar):
159 result = self.local(tgt=tgt, fun='pillar.get', args=pillar)
160 return result['return']
Dmitry Tyzhnenkob8641832017-11-07 17:02:47 +0200161
Dennis Dmitriev2d643bc2017-12-04 12:23:47 +0200162 def get_grains(self, tgt, grains):
163 result = self.local(tgt=tgt, fun='grains.get', args=grains)
164 return result['return']
165
Dmitry Tyzhnenkob8641832017-11-07 17:02:47 +0200166 def get_ssh_data(self):
167 """Generate ssh config for Underlay
168
169 :param roles: list of strings
170 """
171
172 pool_name = self.__config.underlay.net_mgmt
173 pool_net = netaddr.IPNetwork(self.__config.underlay.address_pools[
174 self.__config.underlay.net_mgmt])
175 hosts = self.local('*', 'grains.item', ['host', 'ipv4'])
176
177 if len(hosts.get('return', [])) == 0:
178 raise LookupError("Hosts is empty or absent")
179 hosts = hosts['return'][0]
180 if len(hosts) == 0:
181 raise LookupError("Hosts is empty or absent")
182
183 def host(node_name, ip):
184 return {
185 'roles': ['salt_minion'],
186 'keys': [
187 k['private'] for k in self.__config.underlay.ssh_keys
188 ],
189 'node_name': node_name,
190 'host': ip,
191 'address_pool': pool_name,
192 'login': settings.SSH_NODE_CREDENTIALS['login'],
193 'password': settings.SSH_NODE_CREDENTIALS['password']
194 }
195
196 return [
197 host(k, next(i for i in v['ipv4'] if i in pool_net))
198 for k, v in hosts.items()
199 if next(i for i in v['ipv4'] if i in pool_net)]
Dennis Dmitriev2d643bc2017-12-04 12:23:47 +0200200
201 def service_status(self, tgt, service):
202 result = self.local(tgt=tgt, fun='service.status', args=service)
203 return result['return']
204
205 def service_restart(self, tgt, service):
206 result = self.local(tgt=tgt, fun='service.restart', args=service)
207 return result['return']
208
209 def service_stop(self, tgt, service):
210 result = self.local(tgt=tgt, fun='service.stop', args=service)
211 return result['return']
Dennis Dmitrievb8115f52017-12-15 13:09:56 +0200212
213 @utils.retry(3, exception=libpepper.PepperException)
214 def sync_time(self, tgt='*'):
215 LOG.info("NTP time sync on the salt minions '{0}'".format(tgt))
216 # Force authentication update on the next API access
217 # because previous authentication most probably is not valid
218 # before or after time sync.
219 self.__api = None
220 self.run_state(
221 tgt,
222 'cmd.run', 'service ntp stop; ntpd -gq; service ntp start')
223 new_time_res = self.run_state(tgt, 'cmd.run', 'date')
224 for node_name, time in sorted(new_time_res[0]['return'][0].items()):
225 LOG.info("{0}: {1}".format(node_name, time))
226 self.__api = None