blob: 52491860fd8f90d9150d2b98da48e63950bc6158 [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
20from pepper.libpepper import Pepper
21from tcp_tests import settings
22from tcp_tests import logger
23from tcp_tests.managers.execute_commands import ExecuteCommandsMixin
24
25LOG = logger.logger
26
27
28class SaltManager(ExecuteCommandsMixin):
Dennis Dmitriev010f4cd2016-11-01 20:43:51 +020029 """docstring for SaltManager"""
30
Dmitry Tyzhnenkobc0f8262017-04-28 15:39:26 +030031 __config = None
32 __underlay = None
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030033 _map = {
34 'enforceState': 'enforce_state',
35 'enforceStates': 'enforce_states',
36 'runState': 'run_state',
37 'runStates': 'run_states',
38 }
Dennis Dmitriev010f4cd2016-11-01 20:43:51 +020039
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030040 def __init__(self, config, underlay, host=None, port='6969'):
Dmitry Tyzhnenkobc0f8262017-04-28 15:39:26 +030041 self.__config = config
42 self.__underlay = underlay
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030043 self.__port = port
44 self.__host = host
45 self.__api = None
46 self.__user = settings.SALT_USER
47 self.__password = settings.SALT_PASSWORD
Dennis Dmitrieveac3aab2017-07-12 16:36:41 +030048 self._salt = self
Dennis Dmitriev010f4cd2016-11-01 20:43:51 +020049
Dmitry Tyzhnenkobc0f8262017-04-28 15:39:26 +030050 super(SaltManager, self).__init__(config=config, underlay=underlay)
Dennis Dmitriev010f4cd2016-11-01 20:43:51 +020051
52 def install(self, commands):
Dina Belovae6fdffb2017-09-19 13:58:34 -070053 # if self.__config.salt.salt_master_host == '0.0.0.0':
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030054 # # Temporary workaround. Underlay should be extended with roles
55 # salt_nodes = self.__underlay.node_names()
56 # self.__config.salt.salt_master_host = \
57 # self.__underlay.host_by_node_name(salt_nodes[0])
Dennis Dmitriev010f4cd2016-11-01 20:43:51 +020058
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030059 self.execute_commands(commands=commands,
60 label="Install and configure salt")
61
62 @property
63 def port(self):
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030064 return self.__port
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030065
66 @property
67 def host(self):
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030068 if self.__host:
69 return self.__host
70 else:
Dina Belovae6fdffb2017-09-19 13:58:34 -070071 # TODO(ddmitriev): consider to add a check and raise
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030072 # exception if 'salt_master_host' is not initialized.
73 return self.__config.salt.salt_master_host
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030074
75 @property
76 def api(self):
77 def login():
78 LOG.info("Authentication in Salt API")
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030079 self.__api.login(
80 username=self.__user,
81 password=self.__password,
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030082 eauth='pam')
83 return datetime.now()
84
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030085 if self.__api:
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030086 if (datetime.now() - self.__session_start).seconds < 5 * 60:
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030087 return self.__api
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030088 else:
89 # FIXXME: Change to debug
90 LOG.info("Session's expired")
91 self.__session_start = login()
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030092 return self.__api
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030093
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030094 url = "http://{host}:{port}".format(
95 host=self.host, port=self.port)
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030096 LOG.info("Connecting to Salt API {0}".format(url))
97 self.__api = Pepper(url)
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030098 self.__session_start = login()
Dennis Dmitriev2d60c8e2017-05-12 18:34:01 +030099 return self.__api
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +0300100
101 def local(self, tgt, fun, args=None, kwargs=None):
102 return self.api.local(tgt, fun, args, kwargs, expr_form='compound')
103
104 def local_async(self, tgt, fun, args=None, kwargs=None):
105 return self.api.local_async(tgt, fun, args, kwargs)
106
107 def lookup_result(self, jid):
108 return self.api.lookup_jid(jid)
109
110 def check_result(self, r):
111 if len(r.get('return', [])) == 0:
112 raise LookupError("Result is empty or absent")
113
114 result = r['return'][0]
Dmitry Tyzhnenkobc0f8262017-04-28 15:39:26 +0300115 if len(result) == 0:
116 raise LookupError("Result is empty or absent")
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +0300117 LOG.info("Job has result for %s nodes", result.keys())
118 fails = defaultdict(list)
119 for h in result:
120 host_result = result[h]
121 LOG.info("On %s executed:", h)
122 if isinstance(host_result, list):
123 fails[h].append(host_result)
124 continue
125 for t in host_result:
126 task = host_result[t]
127 if task['result'] is False:
128 fails[h].append(task)
129 LOG.error("%s - %s", t, task['result'])
130 else:
131 LOG.info("%s - %s", t, task['result'])
132
133 return fails if fails else None
134
135 def enforce_state(self, tgt, state, args=None, kwargs=None):
136 r = self.local(tgt=tgt, fun='state.sls', args=state)
137 f = self.check_result(r)
138 return r, f
139
140 def enforce_states(self, tgt, state, args=None, kwargs=None):
141 rets = []
142 for s in state:
143 r = self.enforce_state(tgt=tgt, state=s)
144 rets.append(r)
145 return rets
146
147 def run_state(self, tgt, state, args=None, kwargs=None):
148 return self.local(tgt=tgt, fun=state, args=args, kwargs=kwargs), None
149
150 def run_states(self, tgt, state, args=None, kwargs=None):
151 rets = []
152 for s in state:
153 r = self.run_state(tgt=tgt, state=s, args=args, kwargs=kwargs)
154 rets.append(r)
155 return rets
Artem Panchenko0594cd72017-06-12 13:25:26 +0300156
157 def get_pillar(self, tgt, pillar):
158 result = self.local(tgt=tgt, fun='pillar.get', args=pillar)
159 return result['return']
Dmitry Tyzhnenkob8641832017-11-07 17:02:47 +0200160
Dennis Dmitriev2d643bc2017-12-04 12:23:47 +0200161 def get_grains(self, tgt, grains):
162 result = self.local(tgt=tgt, fun='grains.get', args=grains)
163 return result['return']
164
Dmitry Tyzhnenkob8641832017-11-07 17:02:47 +0200165 def get_ssh_data(self):
166 """Generate ssh config for Underlay
167
168 :param roles: list of strings
169 """
170
171 pool_name = self.__config.underlay.net_mgmt
172 pool_net = netaddr.IPNetwork(self.__config.underlay.address_pools[
173 self.__config.underlay.net_mgmt])
174 hosts = self.local('*', 'grains.item', ['host', 'ipv4'])
175
176 if len(hosts.get('return', [])) == 0:
177 raise LookupError("Hosts is empty or absent")
178 hosts = hosts['return'][0]
179 if len(hosts) == 0:
180 raise LookupError("Hosts is empty or absent")
181
182 def host(node_name, ip):
183 return {
184 'roles': ['salt_minion'],
185 'keys': [
186 k['private'] for k in self.__config.underlay.ssh_keys
187 ],
188 'node_name': node_name,
189 'host': ip,
190 'address_pool': pool_name,
191 'login': settings.SSH_NODE_CREDENTIALS['login'],
192 'password': settings.SSH_NODE_CREDENTIALS['password']
193 }
194
195 return [
196 host(k, next(i for i in v['ipv4'] if i in pool_net))
197 for k, v in hosts.items()
198 if next(i for i in v['ipv4'] if i in pool_net)]
Dennis Dmitriev2d643bc2017-12-04 12:23:47 +0200199
200 def service_status(self, tgt, service):
201 result = self.local(tgt=tgt, fun='service.status', args=service)
202 return result['return']
203
204 def service_restart(self, tgt, service):
205 result = self.local(tgt=tgt, fun='service.restart', args=service)
206 return result['return']
207
208 def service_stop(self, tgt, service):
209 result = self.local(tgt=tgt, fun='service.stop', args=service)
210 return result['return']