| #!/usr/bin/env python |
| |
| ''' |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you 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. |
| ''' |
| |
| __all__ = ["Script"] |
| |
| import os |
| import sys |
| import json |
| import logging |
| import shutil |
| |
| from resource_management.core.environment import Environment |
| from resource_management.core.exceptions import Fail, ClientComponentHasNoStatus, ComponentIsNotRunning |
| from resource_management.core.resources.packaging import Package |
| from resource_management.core.resources import Tarball |
| from resource_management.core.resources import Directory |
| from resource_management.libraries.script.config_dictionary import ConfigDictionary |
| from resource_management.libraries.script.repo_installer import RepoInstaller |
| from resource_management.core.logger import Logger |
| |
| USAGE = """Usage: {0} <COMMAND> <JSON_CONFIG> <BASEDIR> <STROUTPUT> <LOGGING_LEVEL> |
| |
| <COMMAND> command type (INSTALL/CONFIGURE/START/STOP/SERVICE_CHECK...) |
| <JSON_CONFIG> path to command json file. Ex: /var/lib/ambari-agent/data/command-2.json |
| <BASEDIR> path to service metadata dir. Ex: /var/lib/ambari-agent/cache/stacks/HDP/2.0.6/services/HDFS |
| <STROUTPUT> path to file with structured command output (file will be created). Ex:/tmp/my.txt |
| <LOGGING_LEVEL> log level for stdout. Ex:DEBUG,INFO |
| """ |
| |
| class Script(object): |
| """ |
| Executes a command for custom service. stdout and stderr are written to |
| tmpoutfile and to tmperrfile respectively. |
| Script instances share configuration as a class parameter and therefore |
| different Script instances can not be used from different threads at |
| the same time within a single python process |
| |
| Accepted command line arguments mapping: |
| 1 command type (START/STOP/...) |
| 2 path to command json file |
| 3 path to service metadata dir (Directory "package" inside service directory) |
| 4 path to file with structured command output (file will be created) |
| """ |
| structuredOut = {} |
| |
| def put_structured_out(self, sout): |
| Script.structuredOut.update(sout) |
| try: |
| with open(self.stroutfile, 'w') as fp: |
| json.dump(Script.structuredOut, fp) |
| except IOError: |
| Script.structuredOut.update({"errMsg" : "Unable to write to " + self.stroutfile}) |
| |
| def execute(self): |
| """ |
| Sets up logging; |
| Parses command parameters and executes method relevant to command type |
| """ |
| # set up logging (two separate loggers for stderr and stdout with different loglevels) |
| logger = logging.getLogger('resource_management') |
| logger.setLevel(logging.DEBUG) |
| formatter = logging.Formatter('%(asctime)s - %(message)s') |
| chout = logging.StreamHandler(sys.stdout) |
| chout.setLevel(logging.INFO) |
| chout.setFormatter(formatter) |
| cherr = logging.StreamHandler(sys.stderr) |
| cherr.setLevel(logging.ERROR) |
| cherr.setFormatter(formatter) |
| logger.addHandler(cherr) |
| logger.addHandler(chout) |
| |
| # parse arguments |
| if len(sys.argv) < 6: |
| logger.error("Script expects at least 5 arguments") |
| print USAGE.format(os.path.basename(sys.argv[0])) # print to stdout |
| sys.exit(1) |
| |
| command_name = str.lower(sys.argv[1]) |
| command_data_file = sys.argv[2] |
| basedir = sys.argv[3] |
| self.stroutfile = sys.argv[4] |
| logging_level = sys.argv[5] |
| |
| logging_level_str = logging._levelNames[logging_level] |
| chout.setLevel(logging_level_str) |
| logger.setLevel(logging_level_str) |
| |
| try: |
| with open(command_data_file, "r") as f: |
| pass |
| Script.config = ConfigDictionary(json.load(f)) |
| except IOError: |
| logger.exception("Can not read json file with command parameters: ") |
| sys.exit(1) |
| # Run class method depending on a command type |
| try: |
| method = self.choose_method_to_execute(command_name) |
| with Environment(basedir) as env: |
| method(env) |
| except ClientComponentHasNoStatus or ComponentIsNotRunning: |
| # Support of component status checks. |
| # Non-zero exit code is interpreted as an INSTALLED status of a component |
| sys.exit(1) |
| except Fail: |
| logger.exception("Error while executing command '{0}':".format(command_name)) |
| sys.exit(1) |
| |
| |
| def choose_method_to_execute(self, command_name): |
| """ |
| Returns a callable object that should be executed for a given command. |
| """ |
| self_methods = dir(self) |
| if not command_name in self_methods: |
| raise Fail("Script '{0}' has no method '{1}'".format(sys.argv[0], command_name)) |
| method = getattr(self, command_name) |
| return method |
| |
| |
| @staticmethod |
| def get_config(): |
| """ |
| HACK. Uses static field to store configuration. This is a workaround for |
| "circular dependency" issue when importing params.py file and passing to |
| it a configuration instance. |
| """ |
| return Script.config |
| |
| |
| def install(self, env): |
| """ |
| Default implementation of install command is to install all packages |
| from a list, received from the server. |
| Feel free to override install() method with your implementation. It |
| usually makes sense to call install_packages() manually in this case |
| """ |
| self.install_packages(env) |
| |
| |
| def install_packages(self, env): |
| """ |
| List of packages that are required by service is received from the server |
| as a command parameter. The method installs all packages |
| from this list |
| """ |
| config = self.get_config() |
| repo_installed = False |
| |
| try: |
| package_list_str = config['hostLevelParams']['package_list'] |
| if isinstance(package_list_str,basestring) and len(package_list_str) > 0: |
| package_list = json.loads(package_list_str) |
| for package in package_list: |
| name = package['name'] |
| type = package['type'] |
| if type.lower() == "tarball": |
| if name.startswith(os.path.sep): |
| tarball = name |
| else: |
| basedir = env.config.basedir |
| tarball = os.path.join(basedir, name) |
| install_location = config['configurations']['global']['app_install_dir'] |
| Directory(install_location, action = "delete") |
| Directory(install_location) |
| Tarball(tarball, location=install_location) |
| elif type.lower() == "folder": |
| if name.startswith(os.path.sep): |
| src = name |
| else: |
| basedir = env.config.basedir |
| src = os.path.join(basedir, name) |
| dest = config['configurations']['global']['app_install_dir'] |
| Directory(dest, action = "delete") |
| Logger.info("Copying from " + src + " to " + dest) |
| shutil.copytree(src, dest) |
| else: |
| if not repo_installed: |
| RepoInstaller.install_repos(config) |
| repo_installed = True |
| Package(name) |
| except KeyError, e: |
| Logger.info("Error installing packages. " + repr(e)) |
| pass # No reason to worry |
| |
| #RepoInstaller.remove_repos(config) |
| |
| |
| |
| def fail_with_error(self, message): |
| """ |
| Prints error message and exits with non-zero exit code |
| """ |
| print("Error: " + message) |
| sys.stderr.write("Error: " + message) |
| sys.exit(1) |
| |
| def start(self, env): |
| """ |
| To be overridden by subclasses |
| """ |
| self.fail_with_error('start method isn\'t implemented') |
| |
| def stop(self, env): |
| """ |
| To be overridden by subclasses |
| """ |
| self.fail_with_error('stop method isn\'t implemented') |
| |
| def restart(self, env): |
| """ |
| Default implementation of restart command is to call stop and start methods |
| Feel free to override restart() method with your implementation. |
| For client components we call install |
| """ |
| config = self.get_config() |
| componentCategory = None |
| try : |
| componentCategory = config['roleParams']['component_category'] |
| except KeyError: |
| pass |
| |
| if componentCategory and componentCategory.strip().lower() == 'CLIENT'.lower(): |
| self.install(env) |
| else: |
| self.stop(env) |
| self.start(env) |
| |
| def configure(self, env): |
| """ |
| To be overridden by subclasses |
| """ |
| self.fail_with_error('configure method isn\'t implemented') |