瀏覽代碼

AMBARI-7966. delete and set action does not work for configs.sh script

Srimanth Gunturi 10 年之前
父節點
當前提交
2d5623fd5b

+ 254 - 0
ambari-server/src/main/resources/scripts/configs.py

@@ -0,0 +1,254 @@
+#!/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.
+'''
+
+import sys
+import urllib2
+import time
+import json
+import base64
+
+HTTP_PROTOCOL = 'http'
+HTTPS_PROTOCOL = 'https'
+
+SET_ACTION = 'set'
+GET_ACTION = 'get'
+DELETE_ACTION = 'delete'
+
+GET_REQUEST_TYPE = 'GET'
+PUT_REQUEST_TYPE = 'PUT'
+
+# JSON Keywords
+PROPERTIES = 'properties'
+ATTRIBUTES = 'properties_attributes'
+CLUSTERS = 'Clusters'
+DESIRED_CONFIGS = 'desired_configs'
+TYPE = 'type'
+TAG = 'tag'
+ITEMS = 'items'
+TAG_PREFIX = 'version'
+
+CLUSTERS_URL = '/api/v1/clusters/{0}'
+DESIRED_CONFIGS_URL = CLUSTERS_URL + '?fields=Clusters/desired_configs'
+CONFIGURATION_URL = CLUSTERS_URL + '/configurations?type={1}&tag={2}'
+
+FILE_FORMAT = \
+"""
+"properties": {
+  "key1": "value1"
+  "key2": "value2"
+},
+"properties_attributes": {
+  "attribute": {
+    "key1": "value1"
+    "key2": "value2"
+  }
+}
+"""
+
+class UsageException(Exception):
+  pass
+
+def api_accessor(host, login, password, protocol, port):
+  def do_request(api_url, request_type=GET_REQUEST_TYPE, request_body=''):
+    try:
+      url = '{0}://{1}:{2}{3}'.format(protocol, host, port, api_url)
+      admin_auth = base64.encodestring('%s:%s' % (login, password)).replace('\n', '')
+      request = urllib2.Request(url)
+      request.add_header('Authorization', 'Basic %s' % admin_auth)
+      request.add_header('X-Requested-By', 'ambari')
+      request.add_data(request_body)
+      request.get_method = lambda: request_type
+      response = urllib2.urlopen(request)
+      response_body = response.read()
+    except Exception as exc:
+      raise Exception('Problem with accessing api. Reason: {0}'.format(exc))
+    return response_body
+  return do_request
+
+def get_config_tag(cluster, config_type, accessor):
+  response = accessor(DESIRED_CONFIGS_URL.format(cluster))
+  try:
+    desired_tags = json.loads(response)
+    current_config_tag = desired_tags[CLUSTERS][DESIRED_CONFIGS][config_type][TAG]
+  except Exception as exc:
+    raise Exception('"{0}" not found in server response. Response:\n{1}'.format(config_type, response))
+  return current_config_tag
+
+def create_new_desired_config(cluster, config_type, properties, attributes, accessor):
+  new_tag = TAG_PREFIX + str(int(time.time() * 1000000))
+  new_config = {
+    CLUSTERS: {
+      DESIRED_CONFIGS: {
+        TYPE: config_type,
+        TAG: new_tag,
+        PROPERTIES: properties
+      }
+    }
+  }
+  if len(attributes.keys()) > 0:
+    new_config[CLUSTERS][DESIRED_CONFIGS][ATTRIBUTES] = attributes
+  request_body = json.dumps(new_config)
+  new_file = 'doSet_{0}.json'.format(new_tag)
+  print '### PUTting json into: {0}'.format(new_file)
+  output_to_file(new_file)(new_config)
+  accessor(CLUSTERS_URL.format(cluster), PUT_REQUEST_TYPE, request_body)
+  print '### NEW Site:{0}, Tag:{1}'.format(config_type, new_tag)
+
+def get_current_config(cluster, config_type, accessor):
+  config_tag = get_config_tag(cluster, config_type, accessor)
+  print "### on (Site:{0}, Tag:{1})".format(config_type, config_tag)
+  response = accessor(CONFIGURATION_URL.format(cluster, config_type, config_tag))
+  config_by_tag = json.loads(response)
+  current_config = config_by_tag[ITEMS][0]
+  return current_config[PROPERTIES], current_config.get(ATTRIBUTES, {})
+
+def update_config(cluster, config_type, config_updater, accessor):
+  properties, attributes = config_updater(cluster, config_type, accessor)
+  create_new_desired_config(cluster, config_type, properties, attributes, accessor)
+
+def update_specific_property(config_name, config_value):
+  def update(cluster, config_type, accessor):
+    properties, attributes = get_current_config(cluster, config_type, accessor)
+    properties[config_name] = config_value
+    return properties, attributes
+  return update
+
+def update_from_file(config_file):
+  def update(cluster, config_type, accessor):
+    try:
+      with open(config_file) as in_file:
+        file_content = in_file.read()
+    except Exception as e:
+      raise Exception('Cannot find file "{0}" to PUT'.format(config_file))
+    try:
+      file_properties = json.loads('{' + file_content + '}')
+    except Exception as e:
+      raise Exception('File "{0}" should be in the following JSON format ("properties_attributes" is optional):\n{1}'.format(config_file, FILE_FORMAT))
+    new_properties = file_properties.get(PROPERTIES, {})
+    new_attributes = file_properties.get(ATTRIBUTES, {})
+    print '### PUTting file: "{0}"'.format(config_file)
+    return new_properties, new_attributes
+  return update
+
+def delete_specific_property(config_name):
+  def update(cluster, config_type, accessor):
+    properties, attributes = get_current_config(cluster, config_type, accessor)
+    properties.pop(config_name, None)
+    for attribute_values in attributes.values():
+      attribute_values.pop(config_name, None)
+    return properties, attributes
+  return update
+
+def format_json(dictionary, tab_level=0):
+  output = ''
+  tab = ' ' * 2 * tab_level
+  for key, value in dictionary.iteritems():
+    output += ',\n{0}"{1}": '.format(tab, key)
+    if isinstance(value, dict):
+      output += '{\n' + format_json(value, tab_level + 1) + tab + '}'
+    else:
+      output += '"{0}"'.format(value)
+  output += '\n'
+  return output[2:]
+
+def output_to_file(filename):
+  def output(config):
+    with open(filename, 'w') as out_file:
+      out_file.write(format_json(config))
+  return output
+
+def output_to_console(config):
+  print format_json(config)
+
+def get_config(cluster, config_type, accessor, output):
+  properties, attributes = get_current_config(cluster, config_type, accessor)
+  config = {PROPERTIES: properties}
+  if len(attributes.keys()) > 0:
+    config[ATTRIBUTES] = attributes
+  output(config)
+
+def set_properties(cluster, config_type, args, accessor):
+  print '### Performing "set" content:'
+  if len(args) == 0:
+    raise UsageException("Not enough arguments. Expected config key and value or filename.")
+
+  if len(args) == 1:
+    config_file = args[0]
+    updater = update_from_file(config_file)
+    print '### from file "{0}"'.format(config_file)
+  else:
+    config_name = args[0]
+    config_value = args[1]
+    updater = update_specific_property(config_name, config_value)
+    print '### new property - "{0}":"{1}"'.format(config_name, config_value)
+  update_config(cluster, config_type, updater, accessor)
+
+def delete_properties(cluster, config_type, args, accessor):
+  print '### Performing "delete":'
+  if len(args) == 0:
+    raise UsageException("Not enough arguments. Expected config key.")
+
+  config_name = args[0]
+  print '### on property "{0}"'.format(config_name)
+  update_config(cluster, config_type, delete_specific_property(config_name), accessor)
+
+def get_properties(cluster, config_type, args, accessor):
+  print '### Performing "get" content:'
+  if len(args) > 0:
+    filename = args[0]
+    output = output_to_file(filename)
+    print '### to file "{0}"'.format(filename)
+  else:
+    output = output_to_console
+  get_config(cluster, config_type, accessor, output)
+
+def main():
+  if len(sys.argv) < 9:
+    raise UsageException('Not enough arguments.')
+  args = sys.argv[1:]
+  user = args[0]
+  password = args[1]
+  port = args[2]
+  protocol = args[3]
+  action = args[4]
+  host = args[5]
+  cluster = args[6]
+  config_type = args[7]
+  action_args = args[8:]
+  accessor = api_accessor(host, user, password, protocol, port)
+  if action == SET_ACTION:
+    set_properties(cluster, config_type, action_args, accessor)
+  elif action == GET_ACTION:
+    get_properties(cluster, config_type, action_args, accessor)
+  elif action == DELETE_ACTION:
+    delete_properties(cluster, config_type, action_args, accessor)
+  else:
+    raise UsageException('Action "{0}" is not supported. Supported actions: "get", "set", "delete".'.format(action))
+
+if __name__ == "__main__":
+  try:
+    main()
+  except UsageException as usage_exc:
+    print '[ERROR]   {0}'.format(usage_exc)
+    sys.exit(2)
+  except Exception as exc:
+    for line in str(exc).split('\n'):
+      print '[ERROR]   {0}'.format(line)
+    sys.exit(1)

+ 69 - 42
ambari-server/src/main/resources/scripts/configs.sh

@@ -115,45 +115,60 @@ doConfigUpdate () {
   MODE=$1
   currentSiteTag
   echo "########## Performing '$MODE' $CONFIGKEY:$CONFIGVALUE on (Site:$SITE, Tag:$SITETAG)";
-  propertiesStarted=0;
+  propertiesStarted=0
+  attributesStarted=0
+  currentLevel=0
   curl -k -s -u $USERID:$PASSWD "$AMBARIURL/api/v1/clusters/$CLUSTER/configurations?type=$SITE&tag=$SITETAG" | while read -r line; do
-    ## echo ">>> $line";
-    if [ "$propertiesStarted" -eq 0 -a "`echo $line | grep "\"properties\""`" ]; then
-      propertiesStarted=1
-    fi;
-    if [ "$propertiesStarted" -eq 1 ]; then
-      if [ "$line" == "}" ]; then
+    if [ "$propertiesStarted" -eq 0 -a "$attributesStarted" -eq 0 ]; then
+      if [ "$line" = "\"properties_attributes\" : {" ]; then
+        attributesStarted=$currentLevel
+      elif [ "$line" = "\"properties\" : {" ]; then
+        propertiesStarted=$currentLevel
+      fi
+    fi
+    if [ "$propertiesStarted" -gt 0 ]; then
+      if [ "`echo $line | grep -E "},?$"`" ]; then
         ## Properties ended
         ## Add property
+        # Remove the last ,
+        propLen=${#newProperties}
+        lastChar=${newProperties:$propLen-1:1}
+        if [ "$lastChar" == "," ]; then
+          newProperties=${newProperties:0:$propLen-1}
+        fi
         if [ "$MODE" == "set" ]; then
-          newProperties="$newProperties, \"$CONFIGKEY\" : \"$CONFIGVALUE\" ";
-        elif [ "$MODE" == "delete" ]; then
-          # Remove the last ,
-          propLen=${#newProperties}
-          lastChar=${newProperties:$propLen-1:1}
-          if [ "$lastChar" == "," ]; then
-            newProperties=${newProperties:0:$propLen-1}
-          fi
+          newProperties="$newProperties, \"$CONFIGKEY\" : \"$CONFIGVALUE\" "
         fi
         newProperties=$newProperties$line
-        propertiesStarted=0;
-        
-        newTag=`date "+%s%N"`
-        newTag="version${newTag}"
-        finalJson="{ \"Clusters\": { \"desired_config\": {\"type\": \"$SITE\", \"tag\":\"$newTag\", $newProperties}}}"
-        newFile="doSet_$newTag.json"
-        echo "########## PUTting json into: $newFile"
-        echo $finalJson > $newFile
-        curl -k -u $USERID:$PASSWD -X PUT -H "X-Requested-By: ambari" "$AMBARIURL/api/v1/clusters/$CLUSTER" --data @$newFile
-        currentSiteTag
-        echo "########## NEW Site:$SITE, Tag:$SITETAG";
-      elif [ "`echo $line | grep "\"$CONFIGKEY\""`" ]; then
+        propertiesStarted=0
+      elif [ "`echo $line | grep "\\\"$CONFIGKEY\\\""`" ]; then
         echo "########## Config found. Skipping origin value"
       else
         newProperties=$newProperties$line
       fi
+    elif [ "$attributesStarted" -gt 0 ]; then
+      newProperties=$newProperties$line
     fi
-  done;
+    if [ "`echo $line | grep -E "{$"`" ]; then
+        currentLevel=$((currentLevel+1))
+    elif [ "`echo $line | grep -E "},?$"`" ]; then
+        currentLevel=$((currentLevel-1))
+        if [ "$currentLevel" == 1 ]; then
+          newTag=`date "+%s%N"`
+          newTag="version${newTag}"
+          finalJson="{ \"Clusters\": { \"desired_config\": {\"type\": \"$SITE\", \"tag\":\"$newTag\", $newProperties}}}"
+          newFile="doSet_$newTag.json"
+          echo "########## PUTting json into: $newFile"
+          echo $finalJson > $newFile
+          curl -k -u $USERID:$PASSWD -X PUT -H "X-Requested-By: ambari" "$AMBARIURL/api/v1/clusters/$CLUSTER" --data @$newFile
+          currentSiteTag
+          echo "########## NEW Site:$SITE, Tag:$SITETAG";
+        fi
+    fi
+    if [ "$attributesStarted" -eq "$currentLevel" ]; then
+      attributesStarted=0
+    fi
+  done
 }
 
 #############################################
@@ -163,22 +178,28 @@ doConfigUpdate () {
 doConfigFileUpdate () {
   FILENAME=$1
   if [ -f $FILENAME ]; then
-    if [ "1" == "`grep -n \"\"properties\"\" $FILENAME | cut -d : -f 1`" ]; then
+    if [ "1" == "`grep -n \"\\\"properties\\\"\" $FILENAME | cut -d : -f 1`" ]; then
       newTag=`date "+%s%N"`
       newTag="version${newTag}"
       newProperties=`cat $FILENAME`;
       finalJson="{ \"Clusters\": { \"desired_config\": {\"type\": \"$SITE\", \"tag\":\"$newTag\", $newProperties}}}"
-      newFile="$FILENAME"
+      newFile="doSet_$newTag.json"
       echo $finalJson>$newFile
       echo "########## PUTting file:\"$FILENAME\" into config(type:\"$SITE\", tag:$newTag) via $newFile"
       curl -k -u $USERID:$PASSWD -X PUT -H "X-Requested-By: ambari" "$AMBARIURL/api/v1/clusters/$CLUSTER" --data @$newFile
       currentSiteTag
       echo "########## NEW Site:$SITE, Tag:$SITETAG";
     else
-      echo "[ERROR] File \"$FILENAME\" should be in the following JSON format:";
+      echo "[ERROR] File \"$FILENAME\" should be in the following JSON format (\"properties_attributes\" is optional):";
       echo "[ERROR]   \"properties\": {";
       echo "[ERROR]     \"key1\": \"value1\",";
       echo "[ERROR]     \"key2\": \"value2\",";
+      echo "[ERROR]   },";
+      echo "[ERROR]   \"properties_attributes\": {";
+      echo "[ERROR]     \"final\": {";
+      echo "[ERROR]       \"key1\": \"value1\",";
+      echo "[ERROR]       \"key2\": \"value2\",";
+      echo "[ERROR]     }";
       echo "[ERROR]   }";
       exit 1;
     fi
@@ -196,28 +217,34 @@ doConfigFileUpdate () {
 doGet () {
   FILENAME=$1
   if [ -n $FILENAME -a -f $FILENAME ]; then
-    rm -f $FILENAME;
+    rm -f $FILENAME
   fi
   currentSiteTag
   echo "########## Performing 'GET' on (Site:$SITE, Tag:$SITETAG)";
-  propertiesStarted=0;
+  propertiesStarted=0
   curl -k -s -u $USERID:$PASSWD "$AMBARIURL/api/v1/clusters/$CLUSTER/configurations?type=$SITE&tag=$SITETAG" | while read -r line; do
-    ## echo ">>> $line";
-    if [ "$propertiesStarted" -eq 0 -a "`echo $line | grep "\"properties\""`" ]; then
-      propertiesStarted=1
-    fi;
-    if [ "$propertiesStarted" -eq 1 ]; then
-      if [ "$line" == "}" ]; then
-        ## Properties ended
-        propertiesStarted=0;
+    # echo ">>> $line";
+    if [ "$propertiesStarted" -eq 0 ]; then
+      if [ "`echo $line | grep "\"properties\""`" -o "`echo $line | grep "\"properties_attributes\""`" ]; then
+        propertiesStarted=$currentLevel
       fi
+    fi
+    if [ "$propertiesStarted" -gt "0" ]; then
       if [ -z $FILENAME ]; then
         echo $line
       else
         echo $line >> $FILENAME
       fi
     fi
-  done;
+    if [ "`echo $line | grep -E "{$"`" ]; then
+        currentLevel=$((currentLevel+1))
+    elif [ "`echo $line | grep -E "},?$"`" ]; then
+        currentLevel=$((currentLevel-1))
+    fi
+    if [ "$propertiesStarted" -eq "$currentLevel" ]; then
+      propertiesStarted=0
+    fi
+  done
 }
 
 case "$1" in

+ 186 - 0
ambari-server/src/test/python/TestConfigs.py

@@ -0,0 +1,186 @@
+'''
+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.
+'''
+
+import StringIO
+import imp
+import os
+import sys
+import json
+from mock.mock import patch, MagicMock
+from unittest import TestCase
+
+def get_configs():
+  test_directory = os.path.dirname(os.path.abspath(__file__))
+  configs_path = os.path.join(test_directory, '../../main/resources/scripts/configs.py')
+  with open(configs_path, 'rb') as fp:
+    return imp.load_module('configs', fp, configs_path, ('.py', 'rb', imp.PY_SOURCE))
+
+configs = get_configs()
+
+class TestConfigs(TestCase):
+  def setUp(self):
+    out = StringIO.StringIO()
+    sys.stdout = out
+
+  def tearDown(self):
+    sys.stdout = sys.__stdout__
+
+  def get_url_open_side_effect(self, response_mapping):
+    def urlopen_side_effect(request):
+      response = MagicMock()
+      request_type = request.get_method()
+      request_url = request.get_full_url()
+      if request_type in response_mapping:
+        req_type = response_mapping[request_type]
+        type_responses = req_type.get('body', None)
+        if type_responses is not None:
+          response_data = type_responses[request_url]
+          response.read.side_effect = [response_data]
+        request_check = req_type.get('request_assertion', None)
+        if request_check is not None:
+          request_body = json.loads(request.get_data())
+          request_check.get(request_url, lambda x: None)(request_body)
+      return response
+    return urlopen_side_effect
+
+  @patch.object(configs, 'output_to_file')
+  @patch('urllib2.urlopen')
+  def test_get_config_to_file(self, urlopen_method, to_file_method):
+    response_mapping = {
+      'GET': {
+        'body': {
+          'http://localhost:8081/api/v1/clusters/cluster1?fields=Clusters/desired_configs' : '{"Clusters":{"desired_configs":{"hdfs-site":{"tag":"version1"}}}}',
+          'http://localhost:8081/api/v1/clusters/cluster1/configurations?type=hdfs-site&tag=version1': '{"items":[{"properties":{"config1": "value1", "config2": "value2"}}]}'
+        }
+      }
+    }
+    def config_assertion(config):
+      self.assertEquals(config['properties'], {'config1': 'value1', 'config2': 'value2'})
+    urlopen_method.side_effect = self.get_url_open_side_effect(response_mapping)
+    to_file_method.return_value = config_assertion
+    sys.argv = ['configs.py', 'user', 'password', '8081', 'http', 'get', 'localhost', 'cluster1', 'hdfs-site', '1.conf']
+    configs.main()
+
+  @patch.object(configs, 'output_to_file')
+  @patch('urllib2.urlopen')
+  def test_update_specific_config(self, urlopen_method, to_file_method):
+    response_mapping = {
+      'GET': {
+        'body': {
+          'https://localhost:8081/api/v1/clusters/cluster1?fields=Clusters/desired_configs' : '{"Clusters":{"desired_configs":{"hdfs-site":{"tag":"version1"}}}}',
+          'https://localhost:8081/api/v1/clusters/cluster1/configurations?type=hdfs-site&tag=version1': '{"items":[{"properties":{"config1": "value1", "config2": "value2"}}]}'
+        }
+      },
+      'PUT': {
+        'request_assertion': {
+          'https://localhost:8081/api/v1/clusters/cluster1':
+            lambda request_body: self.assertEquals(request_body['Clusters']['desired_configs']['properties'], {"config1": "value3", "config2": "value2"})
+        }
+      }
+    }
+    urlopen_method.side_effect = self.get_url_open_side_effect(response_mapping)
+    sys.argv = ['configs.py', 'user', 'password', '8081', 'https', 'set', 'localhost', 'cluster1', 'hdfs-site', 'config1', 'value3']
+    configs.main()
+
+  @patch.object(configs, 'output_to_file')
+  @patch('urllib2.urlopen')
+  def test_update_from_file(self, urlopen_method, to_file_method):
+    response_mapping = {
+      'GET': {
+        'body': {
+          'https://localhost:8081/api/v1/clusters/cluster1?fields=Clusters/desired_configs' : '{"Clusters":{"desired_configs":{"hdfs-site":{"tag":"version1"}}}}',
+          'https://localhost:8081/api/v1/clusters/cluster1/configurations?type=hdfs-site&tag=version1': '{"items":[{"properties":{"config1": "value1", "config2": "value2"}}]}'
+        }
+      },
+      'PUT': {
+        'request_assertion': {
+          'https://localhost:8081/api/v1/clusters/cluster1':
+            lambda request_body: self.assertEquals(request_body['Clusters']['desired_configs']['properties'], {"config1": "value3", "config2": "value2"})
+        }
+      }
+    }
+    urlopen_method.side_effect = self.get_url_open_side_effect(response_mapping)
+    sys.argv = ['configs.py', 'user', 'password', '8081', 'https', 'set', 'localhost', 'cluster1', 'hdfs-site', 'config1', 'value3']
+    configs.main()
+
+  @patch.object(configs, 'output_to_file')
+  @patch('urllib2.urlopen')
+  def test_update_specific_config_with_attributes(self, urlopen_method, to_file_method):
+    def update_check(request_body):
+      self.assertEquals(request_body['Clusters']['desired_configs']['properties'], {"config1": "value4", "config2": "value2", "config3": "value3"})
+      self.assertEquals(request_body['Clusters']['desired_configs']['properties_attributes'], {"final":{"config1": "true", "config3": "true"}})
+    response_mapping = {
+      'GET': {
+        'body': {
+          'https://localhost:8081/api/v1/clusters/cluster2?fields=Clusters/desired_configs' : '{"Clusters":{"desired_configs":{"hdfs-site":{"tag":"version12"}}}}',
+          'https://localhost:8081/api/v1/clusters/cluster2/configurations?type=hdfs-site&tag=version12': '{"items":[{"properties":{"config1": "value1", "config2": "value2", "config3": "value3"}, "properties_attributes":{"final":{"config1": "true", "config3": "true"}}}]}',
+        }
+      },
+      'PUT': {
+        'request_assertion': {
+          'https://localhost:8081/api/v1/clusters/cluster2': update_check
+        }
+      }
+    }
+    urlopen_method.side_effect = self.get_url_open_side_effect(response_mapping)
+    sys.argv = ['configs.py', 'user', 'password', '8081', 'https', 'set', 'localhost', 'cluster2', 'hdfs-site', 'config1', 'value4']
+    configs.main()
+
+  @patch.object(configs, 'output_to_file')
+  @patch('urllib2.urlopen')
+  def test_delete_config(self, urlopen_method, to_file_method):
+    response_mapping = {
+      'GET': {
+        'body': {
+          'https://localhost:8081/api/v1/clusters/cluster1?fields=Clusters/desired_configs' : '{"Clusters":{"desired_configs":{"hdfs-site":{"tag":"version1"}}}}',
+          'https://localhost:8081/api/v1/clusters/cluster1/configurations?type=hdfs-site&tag=version1': '{"items":[{"properties":{"config1": "value1", "config2": "value2"}}]}'
+        }
+      },
+      'PUT': {
+        'request_assertion': {
+          'https://localhost:8081/api/v1/clusters/cluster1':
+            lambda request_body: self.assertEquals(request_body['Clusters']['desired_configs']['properties'], {"config2": "value2"})
+        }
+      }
+    }
+    urlopen_method.side_effect = self.get_url_open_side_effect(response_mapping)
+    sys.argv = ['configs.py', 'user', 'password', '8081', 'https', 'delete', 'localhost', 'cluster1', 'hdfs-site', 'config1']
+    configs.main()
+
+  @patch.object(configs, 'output_to_file')
+  @patch('urllib2.urlopen')
+  def test_delete_config_with_attributes(self, urlopen_method, to_file_method):
+    def delete_check(request_body):
+      self.assertEquals(request_body['Clusters']['desired_configs']['properties'], {"config2": "value2", "config3": "value3"})
+      self.assertEquals(request_body['Clusters']['desired_configs']['properties_attributes'], {"final":{"config3": "true"}})
+    response_mapping = {
+      'GET': {
+        'body': {
+          'https://localhost:8081/api/v1/clusters/cluster2?fields=Clusters/desired_configs' : '{"Clusters":{"desired_configs":{"hdfs-site":{"tag":"version12"}}}}',
+          'https://localhost:8081/api/v1/clusters/cluster2/configurations?type=hdfs-site&tag=version12': '{"items":[{"properties":{"config1": "value1", "config2": "value2", "config3": "value3"}, "properties_attributes":{"final":{"config1": "true", "config3": "true"}}}]}',
+          }
+      },
+      'PUT': {
+        'request_assertion': {
+          'https://localhost:8081/api/v1/clusters/cluster2': delete_check
+        }
+      }
+    }
+    urlopen_method.side_effect = self.get_url_open_side_effect(response_mapping)
+    sys.argv = ['configs.py', 'user', 'password', '8081', 'https', 'delete', 'localhost', 'cluster2', 'hdfs-site', 'config1']
+    configs.main()