script.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. #!/usr/bin/env python
  2. '''
  3. Licensed to the Apache Software Foundation (ASF) under one
  4. or more contributor license agreements. See the NOTICE file
  5. distributed with this work for additional information
  6. regarding copyright ownership. The ASF licenses this file
  7. to you under the Apache License, Version 2.0 (the
  8. "License"); you may not use this file except in compliance
  9. with the License. You may obtain a copy of the License at
  10. http://www.apache.org/licenses/LICENSE-2.0
  11. Unless required by applicable law or agreed to in writing, software
  12. distributed under the License is distributed on an "AS IS" BASIS,
  13. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. See the License for the specific language governing permissions and
  15. limitations under the License.
  16. '''
  17. __all__ = ["Script"]
  18. import os
  19. import sys
  20. import json
  21. import logging
  22. from resource_management.core.environment import Environment
  23. from resource_management.core.exceptions import Fail, ClientComponentHasNoStatus, ComponentIsNotRunning
  24. from resource_management.core.resources.packaging import Package
  25. from resource_management.libraries.script.config_dictionary import ConfigDictionary
  26. USAGE = """Usage: {0} <COMMAND> <JSON_CONFIG> <BASEDIR> <STROUTPUT> <LOGGING_LEVEL> <TMP_DIR>
  27. <COMMAND> command type (INSTALL/CONFIGURE/START/STOP/SERVICE_CHECK...)
  28. <JSON_CONFIG> path to command json file. Ex: /var/lib/ambari-agent/data/command-2.json
  29. <BASEDIR> path to service metadata dir. Ex: /var/lib/ambari-agent/cache/stacks/HDP/2.0.6/services/HDFS
  30. <STROUTPUT> path to file with structured command output (file will be created). Ex:/tmp/my.txt
  31. <LOGGING_LEVEL> log level for stdout. Ex:DEBUG,INFO
  32. <TMP_DIR> temporary directory for executable scripts. Ex: /var/lib/ambari-agent/data/tmp
  33. """
  34. class Script(object):
  35. """
  36. Executes a command for custom service. stdout and stderr are written to
  37. tmpoutfile and to tmperrfile respectively.
  38. Script instances share configuration as a class parameter and therefore
  39. different Script instances can not be used from different threads at
  40. the same time within a single python process
  41. Accepted command line arguments mapping:
  42. 1 command type (START/STOP/...)
  43. 2 path to command json file
  44. 3 path to service metadata dir (Directory "package" inside service directory)
  45. 4 path to file with structured command output (file will be created)
  46. """
  47. structuredOut = {}
  48. def put_structured_out(self, sout):
  49. Script.structuredOut.update(sout)
  50. try:
  51. with open(self.stroutfile, 'w') as fp:
  52. json.dump(Script.structuredOut, fp)
  53. except IOError:
  54. Script.structuredOut.update({"errMsg" : "Unable to write to " + self.stroutfile})
  55. def execute(self):
  56. """
  57. Sets up logging;
  58. Parses command parameters and executes method relevant to command type
  59. """
  60. # set up logging (two separate loggers for stderr and stdout with different loglevels)
  61. logger = logging.getLogger('resource_management')
  62. logger.setLevel(logging.DEBUG)
  63. formatter = logging.Formatter('%(asctime)s - %(message)s')
  64. chout = logging.StreamHandler(sys.stdout)
  65. chout.setLevel(logging.INFO)
  66. chout.setFormatter(formatter)
  67. cherr = logging.StreamHandler(sys.stderr)
  68. cherr.setLevel(logging.ERROR)
  69. cherr.setFormatter(formatter)
  70. logger.addHandler(cherr)
  71. logger.addHandler(chout)
  72. # parse arguments
  73. if len(sys.argv) < 7:
  74. logger.error("Script expects at least 6 arguments")
  75. print USAGE.format(os.path.basename(sys.argv[0])) # print to stdout
  76. sys.exit(1)
  77. command_name = str.lower(sys.argv[1])
  78. command_data_file = sys.argv[2]
  79. basedir = sys.argv[3]
  80. self.stroutfile = sys.argv[4]
  81. logging_level = sys.argv[5]
  82. Script.tmp_dir = sys.argv[6]
  83. logging_level_str = logging._levelNames[logging_level]
  84. chout.setLevel(logging_level_str)
  85. logger.setLevel(logging_level_str)
  86. try:
  87. with open(command_data_file, "r") as f:
  88. pass
  89. Script.config = ConfigDictionary(json.load(f))
  90. except IOError:
  91. logger.exception("Can not read json file with command parameters: ")
  92. sys.exit(1)
  93. # Run class method depending on a command type
  94. try:
  95. method = self.choose_method_to_execute(command_name)
  96. with Environment(basedir) as env:
  97. method(env)
  98. except ClientComponentHasNoStatus or ComponentIsNotRunning:
  99. # Support of component status checks.
  100. # Non-zero exit code is interpreted as an INSTALLED status of a component
  101. sys.exit(1)
  102. except Fail:
  103. logger.exception("Error while executing command '{0}':".format(command_name))
  104. sys.exit(1)
  105. def choose_method_to_execute(self, command_name):
  106. """
  107. Returns a callable object that should be executed for a given command.
  108. """
  109. self_methods = dir(self)
  110. if not command_name in self_methods:
  111. raise Fail("Script '{0}' has no method '{1}'".format(sys.argv[0], command_name))
  112. method = getattr(self, command_name)
  113. return method
  114. @staticmethod
  115. def get_config():
  116. """
  117. HACK. Uses static field to store configuration. This is a workaround for
  118. "circular dependency" issue when importing params.py file and passing to
  119. it a configuration instance.
  120. """
  121. return Script.config
  122. @staticmethod
  123. def get_tmp_dir():
  124. """
  125. HACK. Uses static field to avoid "circular dependency" issue when
  126. importing params.py.
  127. """
  128. return Script.tmp_dir
  129. def install(self, env):
  130. """
  131. Default implementation of install command is to install all packages
  132. from a list, received from the server.
  133. Feel free to override install() method with your implementation. It
  134. usually makes sense to call install_packages() manually in this case
  135. """
  136. self.install_packages(env)
  137. def install_packages(self, env, exclude_packages=[]):
  138. """
  139. List of packages that are required< by service is received from the server
  140. as a command parameter. The method installs all packages
  141. from this list
  142. """
  143. config = self.get_config()
  144. try:
  145. package_list_str = config['hostLevelParams']['package_list']
  146. if isinstance(package_list_str,basestring) and len(package_list_str) > 0:
  147. package_list = json.loads(package_list_str)
  148. for package in package_list:
  149. if not package['name'] in exclude_packages:
  150. name = package['name']
  151. Package(name)
  152. except KeyError:
  153. pass # No reason to worry
  154. #RepoInstaller.remove_repos(config)
  155. def fail_with_error(self, message):
  156. """
  157. Prints error message and exits with non-zero exit code
  158. """
  159. print("Error: " + message)
  160. sys.stderr.write("Error: " + message)
  161. sys.exit(1)
  162. def start(self, env):
  163. """
  164. To be overridden by subclasses
  165. """
  166. self.fail_with_error('start method isn\'t implemented')
  167. def stop(self, env):
  168. """
  169. To be overridden by subclasses
  170. """
  171. self.fail_with_error('stop method isn\'t implemented')
  172. def restart(self, env):
  173. """
  174. Default implementation of restart command is to call stop and start methods
  175. Feel free to override restart() method with your implementation.
  176. For client components we call install
  177. """
  178. config = self.get_config()
  179. componentCategory = None
  180. try :
  181. componentCategory = config['roleParams']['component_category']
  182. except KeyError:
  183. pass
  184. if componentCategory and componentCategory.strip().lower() == 'CLIENT'.lower():
  185. self.install(env)
  186. else:
  187. self.stop(env)
  188. self.start(env)
  189. def configure(self, env):
  190. """
  191. To be overridden by subclasses
  192. """
  193. self.fail_with_error('configure method isn\'t implemented')