|  | #    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 yaml | 
|  | from tcp_tests import logger | 
|  | from tcp_tests.managers.execute_commands import ExecuteCommandsMixin | 
|  |  | 
|  | LOG = logger.logger | 
|  |  | 
|  |  | 
|  | class ReclassManager(ExecuteCommandsMixin): | 
|  | """docstring for ReclassManager""" | 
|  |  | 
|  | __config = None | 
|  | __underlay = None | 
|  | reclass_tools_cmd = ". venv-reclass-tools/bin/activate; reclass-tools " | 
|  | tgt = "cfg01"  # place where the reclass-tools installed | 
|  |  | 
|  | def __init__(self, config, underlay): | 
|  | self.__config = config | 
|  | self.__underlay = underlay | 
|  |  | 
|  | reclass_node = [node_name | 
|  | for node_name in self.__underlay.node_names() | 
|  | if self.tgt in node_name] | 
|  | self.ssh = self.__underlay.remote(node_name=reclass_node[0]) | 
|  |  | 
|  | super(ReclassManager, self).__init__(config=config, underlay=underlay) | 
|  |  | 
|  | def check_existence(self, key): | 
|  | """ | 
|  | Returns True if reclass contains that key. | 
|  | :param key: string | 
|  | :return: boolean | 
|  | """ | 
|  | if key in self.ssh.check_call( | 
|  | "{reclass_tools} get-key {key} /srv/salt/reclass/classes". | 
|  | format(reclass_tools=self.reclass_tools_cmd, | 
|  | key=key)): | 
|  | LOG.warning("({}) key already exists in reclass".format(key)) | 
|  | return True | 
|  | return False | 
|  |  | 
|  | def add_key(self, key, value, short_path): | 
|  | """ | 
|  | Shows alert if key exists | 
|  |  | 
|  | :param key: string, parameters which will be added or updated | 
|  | :param value: value of key | 
|  | :param short_path: path to reclass yaml file. | 
|  | It takes into account default path where the reclass locates. | 
|  | May look like cluster/*/cicd/control/leader.yml | 
|  | :return: None | 
|  | """ | 
|  |  | 
|  | value = str(value).replace('"', "'") | 
|  | # let's escape $ symbol for bash-like command | 
|  | value = str(value).replace("$", r"\$") | 
|  |  | 
|  | # value can contain a space symbol. So value should be in quotes | 
|  | cmd = "{reclass_tools} add-key {key} \"{value}\" \ | 
|  | /srv/salt/reclass/classes/{path}".format( | 
|  | reclass_tools=self.reclass_tools_cmd, | 
|  | key=key, | 
|  | value=value, | 
|  | path=short_path) | 
|  | LOG.info("Add key to reclass: \n  {cmd} ".format(cmd=cmd)) | 
|  | self.ssh.check_call(cmd) | 
|  |  | 
|  | def get_key(self, key, file_name): | 
|  | """Find a key in a YAML | 
|  |  | 
|  | :param key: string, parameter to add | 
|  | :param file_name: name of YAML file to find a key | 
|  | :return: str, key if found | 
|  | """ | 
|  | LOG.debug("Try to get '{key}' key from '{file}' file".format( | 
|  | file=file_name, | 
|  | key=key | 
|  | )) | 
|  | request_key = self.ssh.check_call( | 
|  | "{reclass_tools} get-key {key} " | 
|  | "/srv/salt/reclass/classes/{file_name}". | 
|  | format(reclass_tools=self.reclass_tools_cmd, | 
|  | key=key, | 
|  | file_name=file_name))['stdout'] | 
|  |  | 
|  | LOG.debug("Raw output from reclass.get_key {}".format(request_key)) | 
|  | encoded_request_key = ''.join(request_key).encode(encoding='UTF-8') | 
|  | value = yaml.load(encoded_request_key) | 
|  | LOG.info("From reclass.get_key {}: {}".format(key, value)) | 
|  | return value | 
|  |  | 
|  | def add_bool_key(self, key, value, short_path): | 
|  | """ | 
|  | Shows alert if key exists | 
|  |  | 
|  | :param key: string, parameters which will be added or updated | 
|  | :param value: value of key | 
|  | :param short_path: path to reclass yaml file. | 
|  | It takes into account default path where the reclass locates. | 
|  | May look like cluster/*/cicd/control/leader.yml | 
|  | :return: None | 
|  | """ | 
|  | self.check_existence(key) | 
|  | self.ssh.check_call( | 
|  | "{reclass_tools} add-bool-key {key} {value} \ | 
|  | /srv/salt/reclass/classes/{path}".format( | 
|  | reclass_tools=self.reclass_tools_cmd, | 
|  | key=key, | 
|  | value=value, | 
|  | path=short_path | 
|  | ), raise_on_err=False) | 
|  |  | 
|  | def add_class(self, value, short_path): | 
|  | """ | 
|  | Shows warning if class exists | 
|  | :param value: role to add to 'classes' parameter in the reclass | 
|  | :param short_path: path to reclass yaml file. | 
|  | It takes into account default path where the reclass locates. | 
|  | May look like cluster/*/cicd/control/leader.yml | 
|  | :return: None | 
|  | """ | 
|  | if value in self.ssh.check_call( | 
|  | "{reclass_tools} get-key classes \ | 
|  | /srv/salt/reclass/classes/{path}".format( | 
|  | reclass_tools=self.reclass_tools_cmd, | 
|  | path=short_path | 
|  | )): | 
|  | LOG.warning("Class {} already exists in {}".format( | 
|  | value, | 
|  | short_path | 
|  | )) | 
|  | return | 
|  |  | 
|  | self.ssh.check_call( | 
|  | "{reclass_tools} add-key classes {value} \ | 
|  | /srv/salt/reclass/classes/{path} --merge".format( | 
|  | reclass_tools=self.reclass_tools_cmd, | 
|  | value=value, | 
|  | path=short_path | 
|  | )) | 
|  |  | 
|  | def delete_class(self, value, short_path): | 
|  | """ | 
|  | Shows warning if class doesn't exist | 
|  | :param value: role to delete from 'classes' parameter in the reclass | 
|  | :param short_path: path to reclass yaml file. | 
|  | It takes into account default path where the reclass locates. | 
|  | May look like cluster/*/cicd/control/leader.yml | 
|  | :return: None | 
|  | """ | 
|  | current_content = self.get_key('classes', short_path) | 
|  | if value not in current_content: | 
|  | LOG.info("{value} not found in classes in {path}".format( | 
|  | value=value, | 
|  | path=short_path | 
|  | )) | 
|  | return | 
|  |  | 
|  | new_content = current_content | 
|  | new_content.remove(value) | 
|  |  | 
|  | self.add_key("classes", new_content, short_path) | 
|  |  | 
|  | def delete_key(self, key, short_path): | 
|  | """ | 
|  | Remove key from the provided file | 
|  |  | 
|  | :param value: string, parameter which will be deleted | 
|  | :param short_path: string,, path to reclass yaml file. | 
|  | It takes into account default path where the reclass locates. | 
|  | May look like cluster/*/cicd/control/leader.yml | 
|  | :return: None | 
|  | """ | 
|  | self.ssh.check_call( | 
|  | "{reclass_tools} del-key {key} \ | 
|  | /srv/salt/reclass/classes/{path}".format( | 
|  | reclass_tools=self.reclass_tools_cmd, | 
|  | key=key, | 
|  | path=short_path | 
|  | )) | 
|  |  | 
|  | def merge_context(self, yaml_context, short_path): | 
|  | """ | 
|  | Merge | 
|  |  | 
|  | :param yaml_context: string, yaml with extra context | 
|  | :param short_path: string, path to reclass yaml file. | 
|  | It takes into account default path where the reclass locates. | 
|  | May look like cluster/*/cicd/control/leader.yml | 
|  | :return: None | 
|  | """ | 
|  | tmp_file = "/tmp/extra_context.yaml" | 
|  | with open(tmp_file, "w") as f: | 
|  | f.write(yaml_context) | 
|  |  | 
|  | self.ssh.upload(tmp_file, tmp_file) | 
|  | self.ssh.check_call( | 
|  | "{reclass_tools} merge-context {yaml} \ | 
|  | /srv/salt/reclass/classes/{path}".format( | 
|  | reclass_tools=self.reclass_tools_cmd, | 
|  | yaml=tmp_file, | 
|  | path=short_path | 
|  | )) | 
|  |  | 
|  | def create_yaml_with_context(self, yaml_context, short_path): | 
|  | """ | 
|  | Create yaml file with context | 
|  |  | 
|  | :param yaml_context: string, yaml with extra context | 
|  | :param short_path: string, path to reclass yaml file. | 
|  | """ | 
|  | tmp_file = "/tmp/extra_context.yaml" | 
|  | with open(tmp_file, "w") as f: | 
|  | f.write(yaml_context) | 
|  |  | 
|  | self.ssh.upload(tmp_file, tmp_file) | 
|  | self.ssh.check_call( | 
|  | "cat {yaml} > \ | 
|  | /srv/salt/reclass/classes/{path}".format( | 
|  | yaml=tmp_file, | 
|  | path=short_path | 
|  | )) | 
|  |  | 
|  | def commit(self, text_commit): | 
|  | self.ssh.check_call( | 
|  | "cd /srv/salt/reclass; git add -u && git commit --allow-empty " | 
|  | "-m '{text}'".format(text=text_commit)) |