|  | #    Copyright 2016 Mirantis, Inc. | 
|  | # | 
|  | #    Licensed under the Apache License, Version 2.0 (the "License"); you may | 
|  | #    not use this file except in compliance with the License. You may obtain | 
|  | #    a copy of the License at | 
|  | # | 
|  | #         http://www.apache.org/licenses/LICENSE-2.0 | 
|  | # | 
|  | #    Unless required by applicable law or agreed to in writing, software | 
|  | #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | 
|  | #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | 
|  | #    License for the specific language governing permissions and limitations | 
|  | #    under the License. | 
|  |  | 
|  | import netaddr | 
|  |  | 
|  | from collections import defaultdict | 
|  |  | 
|  | from datetime import datetime | 
|  | from pepper.libpepper import Pepper | 
|  | from tcp_tests import settings | 
|  | from tcp_tests import logger | 
|  | from tcp_tests.managers.execute_commands import ExecuteCommandsMixin | 
|  |  | 
|  | LOG = logger.logger | 
|  |  | 
|  |  | 
|  | class SaltManager(ExecuteCommandsMixin): | 
|  | """docstring for SaltManager""" | 
|  |  | 
|  | __config = None | 
|  | __underlay = None | 
|  | _map = { | 
|  | 'enforceState': 'enforce_state', | 
|  | 'enforceStates': 'enforce_states', | 
|  | 'runState': 'run_state', | 
|  | 'runStates': 'run_states', | 
|  | } | 
|  |  | 
|  | def __init__(self, config, underlay, host=None, port='6969'): | 
|  | self.__config = config | 
|  | self.__underlay = underlay | 
|  | self.__port = port | 
|  | self.__host = host | 
|  | self.__api = None | 
|  | self.__user = settings.SALT_USER | 
|  | self.__password = settings.SALT_PASSWORD | 
|  | self._salt = self | 
|  |  | 
|  | super(SaltManager, self).__init__(config=config, underlay=underlay) | 
|  |  | 
|  | def install(self, commands): | 
|  | # if self.__config.salt.salt_master_host == '0.0.0.0': | 
|  | #    # Temporary workaround. Underlay should be extended with roles | 
|  | #    salt_nodes = self.__underlay.node_names() | 
|  | #    self.__config.salt.salt_master_host = \ | 
|  | #        self.__underlay.host_by_node_name(salt_nodes[0]) | 
|  |  | 
|  | self.execute_commands(commands=commands, | 
|  | label="Install and configure salt") | 
|  |  | 
|  | @property | 
|  | def port(self): | 
|  | return self.__port | 
|  |  | 
|  | @property | 
|  | def host(self): | 
|  | if self.__host: | 
|  | return self.__host | 
|  | else: | 
|  | # TODO(ddmitriev): consider to add a check and raise | 
|  | # exception if 'salt_master_host' is not initialized. | 
|  | return self.__config.salt.salt_master_host | 
|  |  | 
|  | @property | 
|  | def api(self): | 
|  | def login(): | 
|  | LOG.info("Authentication in Salt API") | 
|  | self.__api.login( | 
|  | username=self.__user, | 
|  | password=self.__password, | 
|  | eauth='pam') | 
|  | return datetime.now() | 
|  |  | 
|  | if self.__api: | 
|  | if (datetime.now() - self.__session_start).seconds < 5 * 60: | 
|  | return self.__api | 
|  | else: | 
|  | # FIXXME: Change to debug | 
|  | LOG.info("Session's expired") | 
|  | self.__session_start = login() | 
|  | return self.__api | 
|  |  | 
|  | url = "http://{host}:{port}".format( | 
|  | host=self.host, port=self.port) | 
|  | LOG.info("Connecting to Salt API {0}".format(url)) | 
|  | self.__api = Pepper(url) | 
|  | self.__session_start = login() | 
|  | return self.__api | 
|  |  | 
|  | def local(self, tgt, fun, args=None, kwargs=None): | 
|  | return self.api.local(tgt, fun, args, kwargs, expr_form='compound') | 
|  |  | 
|  | def local_async(self, tgt, fun, args=None, kwargs=None): | 
|  | return self.api.local_async(tgt, fun, args, kwargs) | 
|  |  | 
|  | def lookup_result(self, jid): | 
|  | return self.api.lookup_jid(jid) | 
|  |  | 
|  | def check_result(self, r): | 
|  | if len(r.get('return', [])) == 0: | 
|  | raise LookupError("Result is empty or absent") | 
|  |  | 
|  | result = r['return'][0] | 
|  | if len(result) == 0: | 
|  | raise LookupError("Result is empty or absent") | 
|  | LOG.info("Job has result for %s nodes", result.keys()) | 
|  | fails = defaultdict(list) | 
|  | for h in result: | 
|  | host_result = result[h] | 
|  | LOG.info("On %s executed:", h) | 
|  | if isinstance(host_result, list): | 
|  | fails[h].append(host_result) | 
|  | continue | 
|  | for t in host_result: | 
|  | task = host_result[t] | 
|  | if task['result'] is False: | 
|  | fails[h].append(task) | 
|  | LOG.error("%s - %s", t, task['result']) | 
|  | else: | 
|  | LOG.info("%s - %s", t, task['result']) | 
|  |  | 
|  | return fails if fails else None | 
|  |  | 
|  | def enforce_state(self, tgt, state, args=None, kwargs=None): | 
|  | r = self.local(tgt=tgt, fun='state.sls', args=state) | 
|  | f = self.check_result(r) | 
|  | return r, f | 
|  |  | 
|  | def enforce_states(self, tgt, state, args=None, kwargs=None): | 
|  | rets = [] | 
|  | for s in state: | 
|  | r = self.enforce_state(tgt=tgt, state=s) | 
|  | rets.append(r) | 
|  | return rets | 
|  |  | 
|  | def run_state(self, tgt, state, args=None, kwargs=None): | 
|  | return self.local(tgt=tgt, fun=state, args=args, kwargs=kwargs), None | 
|  |  | 
|  | def run_states(self, tgt, state, args=None, kwargs=None): | 
|  | rets = [] | 
|  | for s in state: | 
|  | r = self.run_state(tgt=tgt, state=s, args=args, kwargs=kwargs) | 
|  | rets.append(r) | 
|  | return rets | 
|  |  | 
|  | def get_pillar(self, tgt, pillar): | 
|  | result = self.local(tgt=tgt, fun='pillar.get', args=pillar) | 
|  | return result['return'] | 
|  |  | 
|  | def get_ssh_data(self): | 
|  | """Generate ssh config for Underlay | 
|  |  | 
|  | :param roles: list of strings | 
|  | """ | 
|  |  | 
|  | pool_name = self.__config.underlay.net_mgmt | 
|  | pool_net = netaddr.IPNetwork(self.__config.underlay.address_pools[ | 
|  | self.__config.underlay.net_mgmt]) | 
|  | hosts = self.local('*', 'grains.item', ['host', 'ipv4']) | 
|  |  | 
|  | if len(hosts.get('return', [])) == 0: | 
|  | raise LookupError("Hosts is empty or absent") | 
|  | hosts = hosts['return'][0] | 
|  | if len(hosts) == 0: | 
|  | raise LookupError("Hosts is empty or absent") | 
|  |  | 
|  | def host(node_name, ip): | 
|  | return { | 
|  | 'roles': ['salt_minion'], | 
|  | 'keys': [ | 
|  | k['private'] for k in self.__config.underlay.ssh_keys | 
|  | ], | 
|  | 'node_name': node_name, | 
|  | 'host': ip, | 
|  | 'address_pool': pool_name, | 
|  | 'login': settings.SSH_NODE_CREDENTIALS['login'], | 
|  | 'password': settings.SSH_NODE_CREDENTIALS['password'] | 
|  | } | 
|  |  | 
|  | return [ | 
|  | host(k, next(i for i in v['ipv4'] if i in pool_net)) | 
|  | for k, v in hosts.items() | 
|  | if next(i for i in v['ipv4'] if i in pool_net)] |