[jira] [HBASE-4867] A tool to merge configuration files
Summary: With our cluster configuration setup it would be good to have a tool that would merge HBase configuration files so that files appearing later in the list would override properties specified in earlier files. This way we could merge an application-specific configuration file with a cluster-specific configuration file (with the latter overriding the former) and produce a single HBase configuration file to install on the cluster. Test Plan: Run the tool on two configuration files (common and cluster-specific). Use the resulting configuration on a dev cluster. Reviewers: todd, Karthik, tedyu, stack, JIRA Reviewed By: Karthik CC: Karthik, mbautin, todd Differential Revision: 537 git-svn-id: https://svn.apache.org/repos/asf/hbase/trunk@1208832 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
54e463f519
commit
a9ca67a2c5
|
@ -0,0 +1,138 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
'''
|
||||||
|
Merges Hadoop/HBase configuration files in the given order, so that options
|
||||||
|
specified in later configuration files override those specified in earlier
|
||||||
|
files.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
from optparse import OptionParser
|
||||||
|
from xml.dom.minidom import parse, getDOMImplementation
|
||||||
|
|
||||||
|
|
||||||
|
class MergeConfTool:
|
||||||
|
'''
|
||||||
|
Merges the given set of Hadoop/HBase configuration files, with later files
|
||||||
|
overriding earlier ones.
|
||||||
|
'''
|
||||||
|
|
||||||
|
INDENT = ' ' * 2
|
||||||
|
|
||||||
|
# Description text is inside configuration, property, and description tags.
|
||||||
|
DESC_INDENT = INDENT * 3
|
||||||
|
|
||||||
|
def main(self):
|
||||||
|
'''The main entry point for the configuration merge tool.'''
|
||||||
|
self.parse_options()
|
||||||
|
self.merge()
|
||||||
|
|
||||||
|
def parse_options(self):
|
||||||
|
'''Parses command-line options.'''
|
||||||
|
parser = OptionParser(usage='%prog <input_conf_files> -o <output_file>')
|
||||||
|
parser.add_option('-o', '--output_file',
|
||||||
|
help='Destination configuration file')
|
||||||
|
opts, input_files = parser.parse_args()
|
||||||
|
if not opts.output_file:
|
||||||
|
self.fatal('--output_file is not specified')
|
||||||
|
if not input_files:
|
||||||
|
self.fatal('No input files specified')
|
||||||
|
for f_path in input_files:
|
||||||
|
if not os.path.isfile(f_path):
|
||||||
|
self.fatal('Input file %s does not exist' % f_path)
|
||||||
|
self.input_files = input_files
|
||||||
|
self.output_file = opts.output_file
|
||||||
|
|
||||||
|
def merge(self):
|
||||||
|
'''Merges input configuration files into the output file.'''
|
||||||
|
values = {} # Conf key to values
|
||||||
|
source_files = {} # Conf key to the file name where the value came from
|
||||||
|
descriptions = {} # Conf key to description (optional)
|
||||||
|
|
||||||
|
# Read input files in the given order and update configuration maps
|
||||||
|
for f_path in self.input_files:
|
||||||
|
self.current_file = f_path
|
||||||
|
f_basename = os.path.basename(f_path)
|
||||||
|
f_dom = parse(f_path)
|
||||||
|
for property in f_dom.getElementsByTagName('property'):
|
||||||
|
self.current_property = property
|
||||||
|
name = self.element_text('name')
|
||||||
|
value = self.element_text('value')
|
||||||
|
values[name] = value
|
||||||
|
source_files[name] = f_basename
|
||||||
|
|
||||||
|
if property.getElementsByTagName('description'):
|
||||||
|
descriptions[name] = self.element_text('description')
|
||||||
|
|
||||||
|
# Create the output configuration file
|
||||||
|
dom_impl = getDOMImplementation()
|
||||||
|
self.merged_conf = dom_impl.createDocument(None, 'configuration', None)
|
||||||
|
for k in sorted(values.keys()):
|
||||||
|
new_property = self.merged_conf.createElement('property')
|
||||||
|
c = self.merged_conf.createComment('from ' + source_files[k])
|
||||||
|
new_property.appendChild(c)
|
||||||
|
self.append_text_child(new_property, 'name', k)
|
||||||
|
self.append_text_child(new_property, 'value', values[k])
|
||||||
|
|
||||||
|
description = descriptions.get(k, None)
|
||||||
|
if description:
|
||||||
|
description = ' '.join(description.strip().split())
|
||||||
|
textwrap_kwargs = {}
|
||||||
|
if sys.version_info >= (2, 6):
|
||||||
|
textwrap_kwargs = dict(break_on_hyphens=False)
|
||||||
|
description = ('\n' + self.DESC_INDENT).join(
|
||||||
|
textwrap.wrap(description, 80 - len(self.DESC_INDENT),
|
||||||
|
break_long_words=False, **textwrap_kwargs))
|
||||||
|
self.append_text_child(new_property, 'description', description)
|
||||||
|
self.merged_conf.documentElement.appendChild(new_property)
|
||||||
|
|
||||||
|
pretty_conf = self.merged_conf.toprettyxml(indent=self.INDENT)
|
||||||
|
|
||||||
|
# Remove space before and after names and values. This way we don't have
|
||||||
|
# to worry about leading and trailing whitespace creeping in.
|
||||||
|
pretty_conf = re.sub(r'(?<=<name>)\s*', '', pretty_conf)
|
||||||
|
pretty_conf = re.sub(r'(?<=<value>)\s*', '', pretty_conf)
|
||||||
|
pretty_conf = re.sub(r'\s*(?=</name>)', '', pretty_conf)
|
||||||
|
pretty_conf = re.sub(r'\s*(?=</value>)', '', pretty_conf)
|
||||||
|
|
||||||
|
out_f = open(self.output_file, 'w')
|
||||||
|
try:
|
||||||
|
out_f.write(pretty_conf)
|
||||||
|
finally:
|
||||||
|
out_f.close()
|
||||||
|
|
||||||
|
def element_text(self, tag_name):
|
||||||
|
return self.whole_text(self.only_element(tag_name))
|
||||||
|
|
||||||
|
def fatal(self, msg):
|
||||||
|
print >> sys.stderr, msg
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def only_element(self, tag_name):
|
||||||
|
l = self.current_property.getElementsByTagName(tag_name)
|
||||||
|
if len(l) != 1:
|
||||||
|
self.fatal('Invalid property in %s, only one '
|
||||||
|
'"%s" element expected: %s' % (self.current_file, tag_name,
|
||||||
|
self.current_property.toxml()))
|
||||||
|
return l[0]
|
||||||
|
|
||||||
|
def whole_text(self, element):
|
||||||
|
if len(element.childNodes) > 1:
|
||||||
|
self.fatal('No more than one child expected in %s: %s' % (
|
||||||
|
self.current_file, element.toxml()))
|
||||||
|
if len(element.childNodes) == 1:
|
||||||
|
return element.childNodes[0].wholeText.strip()
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def append_text_child(self, property_element, tag_name, value):
|
||||||
|
element = self.merged_conf.createElement(tag_name)
|
||||||
|
element.appendChild(self.merged_conf.createTextNode(value))
|
||||||
|
property_element.appendChild(element)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
MergeConfTool().main()
|
||||||
|
|
Loading…
Reference in New Issue