Sfoglia il codice sorgente

AMBARI-971. Add api support for creating multiple resources in a single request. (John Speidel via mahadev)

git-svn-id: https://svn.apache.org/repos/asf/incubator/ambari/branches/AMBARI-666@1406492 13f79535-47bb-0310-9956-ffa450edef68
Mahadev Konar 12 anni fa
parent
commit
29ded1af83
50 ha cambiato i file con 700 aggiunte e 362 eliminazioni
  1. 3 0
      AMBARI-666-CHANGES.txt
  2. 0 1
      ambari-agent/conf/unix/ambari-agent
  3. 2 2
      ambari-agent/conf/unix/ambari.ini
  4. 11 3
      ambari-agent/pom.xml
  5. 1 4
      ambari-agent/src/main/puppet/modules/hdp-hbase/manifests/init.pp
  6. 1 2
      ambari-agent/src/main/puppet/modules/hdp-repos/templates/repo.erb
  7. 5 1
      ambari-agent/src/main/puppet/modules/hdp/manifests/params.pp
  8. 4 0
      ambari-agent/src/main/python/ambari_agent/ActionQueue.py
  9. 79 0
      ambari-agent/src/main/python/ambari_agent/RepoInstaller.py
  10. 1 37
      ambari-agent/src/main/python/ambari_agent/manifestGenerator.py
  11. 68 35
      ambari-agent/src/main/python/ambari_agent/puppetExecutor.py
  12. 1 0
      ambari-server/conf/unix/ambari.properties
  13. 18 1
      ambari-server/pom.xml
  14. 6 7
      ambari-server/src/main/java/org/apache/ambari/server/api/handlers/BaseManagementHandler.java
  15. 25 17
      ambari-server/src/main/java/org/apache/ambari/server/api/query/QueryImpl.java
  16. 4 22
      ambari-server/src/main/java/org/apache/ambari/server/api/resources/BaseResourceDefinition.java
  17. 1 1
      ambari-server/src/main/java/org/apache/ambari/server/api/resources/ClusterResourceDefinition.java
  18. 10 17
      ambari-server/src/main/java/org/apache/ambari/server/api/resources/ComponentResourceDefinition.java
  19. 8 9
      ambari-server/src/main/java/org/apache/ambari/server/api/resources/ConfigurationResourceDefinition.java
  20. 7 14
      ambari-server/src/main/java/org/apache/ambari/server/api/resources/HostComponentResourceDefinition.java
  21. 9 3
      ambari-server/src/main/java/org/apache/ambari/server/api/resources/HostResourceDefinition.java
  22. 3 27
      ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceDefinition.java
  23. 3 2
      ambari-server/src/main/java/org/apache/ambari/server/api/services/BasePersistenceManager.java
  24. 19 0
      ambari-server/src/main/java/org/apache/ambari/server/api/services/ComponentService.java
  25. 9 8
      ambari-server/src/main/java/org/apache/ambari/server/api/services/ConfigurationService.java
  26. 17 8
      ambari-server/src/main/java/org/apache/ambari/server/api/services/CreatePersistenceManager.java
  27. 5 1
      ambari-server/src/main/java/org/apache/ambari/server/api/services/DeletePersistenceManager.java
  28. 19 0
      ambari-server/src/main/java/org/apache/ambari/server/api/services/HostComponentService.java
  29. 30 10
      ambari-server/src/main/java/org/apache/ambari/server/api/services/HostService.java
  30. 3 9
      ambari-server/src/main/java/org/apache/ambari/server/api/services/PersistKeyValueService.java
  31. 8 2
      ambari-server/src/main/java/org/apache/ambari/server/api/services/PersistenceManager.java
  32. 3 2
      ambari-server/src/main/java/org/apache/ambari/server/api/services/Request.java
  33. 1 1
      ambari-server/src/main/java/org/apache/ambari/server/api/services/RequestImpl.java
  34. 6 2
      ambari-server/src/main/java/org/apache/ambari/server/api/services/UpdatePersistenceManager.java
  35. 20 11
      ambari-server/src/main/java/org/apache/ambari/server/api/services/parsers/JsonPropertyParser.java
  36. 3 2
      ambari-server/src/main/java/org/apache/ambari/server/api/services/parsers/RequestBodyParser.java
  37. 2 0
      ambari-server/src/main/python/ambari-server.py
  38. 26 17
      ambari-server/src/main/python/bootstrap.py
  39. 38 7
      ambari-server/src/main/python/setupAgent.py
  40. 1 1
      ambari-server/src/main/resources/ca.config
  41. 6 8
      ambari-server/src/test/java/org/apache/ambari/server/api/handlers/CreateHandlerTest.java
  42. 7 12
      ambari-server/src/test/java/org/apache/ambari/server/api/handlers/DeleteHandlerTest.java
  43. 7 12
      ambari-server/src/test/java/org/apache/ambari/server/api/handlers/UpdateHandlerTest.java
  44. 1 6
      ambari-server/src/test/java/org/apache/ambari/server/api/query/QueryImplTest.java
  45. 1 1
      ambari-server/src/test/java/org/apache/ambari/server/api/services/ConfigurationServiceTest.java
  46. 59 13
      ambari-server/src/test/java/org/apache/ambari/server/api/services/CreatePersistenceManagerTest.java
  47. 1 1
      ambari-server/src/test/java/org/apache/ambari/server/api/services/DeletePersistenceManagerTest.java
  48. 57 3
      ambari-server/src/test/java/org/apache/ambari/server/api/services/HostServiceTest.java
  49. 15 12
      ambari-server/src/test/java/org/apache/ambari/server/api/services/UpdatePersistenceManagerTest.java
  50. 66 8
      ambari-server/src/test/java/org/apache/ambari/server/api/services/parsers/JsonPropertyParserTest.java

+ 3 - 0
AMBARI-666-CHANGES.txt

@@ -12,6 +12,9 @@ AMBARI-666 branch (unreleased changes)
 
   NEW FEATURES
 
+  AMBARI-971. Add api support for creating multiple resources in a single
+  request. (John Speidel via mahadev)
+
   AMBARI-970. Add additional Ganglia metrics and JMX properties. (Tom
   Beerbower via mahadev)
 

+ 0 - 1
ambari-agent/conf/unix/ambari-agent

@@ -6,7 +6,6 @@
 case "$1" in
   start)
         echo -e "Starting ambari-agent"
-        export AMBARI_PASSPHRASE=pass_phrase
         python /usr/lib/python2.6/site-packages/ambari_agent/main.py
         ;;
   stop)

+ 2 - 2
ambari-agent/conf/unix/ambari.ini

@@ -10,7 +10,7 @@ prefix=/tmp/ambari-agent
 installprefix=/var/ambari/
 
 [puppet]
-puppetmodules=/etc/ambari-agent/puppet
+puppetmodules=/var/lib/ambari-agent/puppet
 puppet_home=/usr/bin/puppet
 facter_home=/usr/bin/facter
 
@@ -19,7 +19,7 @@ maxretries=2
 sleepBetweenRetries=1
 
 [security]
-keysdir=/etc/ambari-agent/keys
+keysdir=/var/lib/ambari-agent/keys
 server_crt=ca.crt
 passphrase_env_var_name=AMBARI_PASSPHRASE
 

+ 11 - 3
ambari-agent/pom.xml

@@ -113,6 +113,9 @@
           <copyright>2012, Apache Software Foundation</copyright>
           <group>Development</group>
           <description>Maven Recipe: RPM Package.</description>
+          <requires>
+            <require>puppet = 2.7.9</require>
+          </requires>
           <mappings>
 
             <mapping>
@@ -125,7 +128,7 @@
             </mapping>
 
             <mapping>
-              <directory>/etc/${project.artifactId}/puppet</directory>
+              <directory>/var/lib/${project.artifactId}/puppet</directory>
               <sources>
                 <source>
                   <location>src/main/puppet</location>
@@ -155,9 +158,14 @@
             <mapping>
               <directory>/var/run/ambari</directory>
             </mapping>
-
             <mapping>
-              <directory>/etc/ambari-agent/keys</directory>
+              <directory>/var/lib/${project.artifactId}/keys</directory>
+            </mapping>
+            <mapping>
+              <directory>/var/log/ambari</directory>
+            </mapping>
+            <mapping>
+              <directory>/var/ambari</directory>
             </mapping>
 
             <!-- -->

+ 1 - 4
ambari-agent/src/main/puppet/modules/hdp-hbase/manifests/init.pp

@@ -40,9 +40,6 @@ class hdp-hbase(
       configuration => $configuration['hbase-site']
       }
     }
-    hdp-hbase::configfile { 'regionservers':}
-    Anchor['hdp-hbase::begin'] -> Hdp::Package['hbase'] -> Hdp::User[$hbase_user] -> Hdp::Directory[$config_dir] -> 
-    Hdp-hbase::Configfile<||> ->  Anchor['hdp-hbase::end']
 
   if has_key($configuration, 'hbase-policy') {
     configgenerator::configfile{'hbase-policy': 
@@ -91,7 +88,7 @@ define hdp-hbase::configfile(
   $mode = undef,
   $hbase_master_host = undef,
   $template_tag = undef,
-  $type = undef,
+  $type = undef
 ) 
 {
   if ($name == 'hadoop-metrics.properties') {

+ 1 - 2
ambari-agent/src/main/puppet/modules/hdp-repos/templates/repo.erb

@@ -22,6 +22,5 @@
 [<%=repo_id%>]
 name=<%=repo_name %>
 baseurl=<%=base_url %>
+path=/
 enabled=1
-
-

+ 5 - 1
ambari-agent/src/main/puppet/modules/hdp/manifests/params.pp

@@ -67,6 +67,9 @@ class hdp::params()
         /^6\..+$/: { $hdp_os_type = "rhel6" }
       }
     }
+    suse: {
+      $hdp_os_type = "suse"
+    }
     default: {
       hdp_fail("No support for os  ${hdp_os} ${hdp_os_version}")
     }
@@ -354,7 +357,8 @@ class hdp::params()
 
   $repos_paths = 
   {
-    centos6 => '/etc/yum.repos.d'
+    centos6 => '/etc/yum.repos.d',
+    suse => '/etc/zypp/repos.d'
   }
 
   }

+ 4 - 0
ambari-agent/src/main/python/ambari_agent/ActionQueue.py

@@ -133,6 +133,10 @@ class ActionQueue(threading.Thread):
                   'exitCode' : commandresult['exitcode'],
                   'serviceName' : serviceName,
                   'status' : status}
+    if roleResult['stdout'] == '':
+      roleResult['stdout'] = 'None'
+    if roleResult['stderr'] == '':
+      roleResult['stderr'] = 'None'
     result.append(roleResult)
     pass
     return result

+ 79 - 0
ambari-agent/src/main/python/ambari_agent/RepoInstaller.py

@@ -0,0 +1,79 @@
+#!/usr/bin/env python2.6
+
+'''
+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 logging
+import os
+import json
+from shell import shellRunner
+from manifestGenerator import writeImports
+
+
+PUPPET_EXT=".pp"
+
+logger = logging.getLogger()
+
+class RepoInstaller:
+  def __init__(self, parsedJson, path, modulesdir, taskId):
+    self.parsedJson = parsedJson
+    self.path = path
+    self.modulesdir = modulesdir
+    self.taskId = taskId
+    self.sh = shellRunner()
+
+  def prepareReposInfo(self):
+    params = {}
+    self.repoInfoList = []
+    if self.parsedJson.has_key('hostLevelParams'):
+      params = self.parsedJson['hostLevelParams']
+    if params.has_key('repo_info'):
+      self.repoInfoList = params['repo_info']
+
+  def generateFiles(self):
+    repoPuppetFiles = []
+    for repo in self.repoInfoList:
+      repoFile = open(self.path + os.sep + repo['repo_id'] + '-' + str(self.taskId) + PUPPET_EXT, 'w+')
+      writeImports(repoFile, self.modulesdir, inputFileName='imports.txt')
+      repoFile.write('node /default/ {')
+      repoFile.write('class{ "hdp-repos::process_repo" : ' + ' os_type => "' + repo['os_type'] +
+      '", repo_id => "' + repo['repo_id'] + '", base_url => "' + repo['base_url'] +
+      '", repo_name => "' + repo['repo_name'] + '" }' )
+      repoFile.write('}')
+      repoFile.close()
+      repoPuppetFiles.append(repoFile.name)
+
+    return repoPuppetFiles
+
+  def installRepos(self):
+    self.prepareReposInfo()
+    repoPuppetFiles = self.generateFiles()
+    return repoPuppetFiles
+
+def main():
+  #Test code
+  jsonFile = open('test.json', 'r')
+  jsonStr = jsonFile.read() 
+  parsedJson = json.loads(jsonStr)
+  repoInstaller = RepoInstaller(parsedJson, '/tmp', '/home/centos/ambari_repo_info/ambari-agent/src/main/puppet/modules')
+  repoInstaller.installRepos()
+  
+if __name__ == '__main__':
+  main()
+
+

+ 1 - 37
ambari-agent/src/main/python/ambari_agent/manifestGenerator.py

@@ -24,8 +24,6 @@ import logging
 from uuid import getnode as get_mac
 from shell import shellRunner
 
-REPO_INFO_DIR="repos_info"
-PUPPET_EXT=".pp"
 
 logger = logging.getLogger()
 
@@ -138,9 +136,7 @@ def writeNodes(outputFile, clusterHostInfo):
 def writeParams(outputFile, params, modulesdir):
 
   for paramName in params.iterkeys():
-    # todo handle repo information properly
-    if paramName == 'repo_info':
-      processRepo(params[paramName],modulesdir)      
+    if paramName == 'repo_info':     
       continue
       
 
@@ -257,39 +253,7 @@ def writeStages(outputFile, numStages):
   
   outputFile.write('\n')
 
-def processRepo(repoInfoList, modulesdir):
 
-  if not os.path.exists(REPO_INFO_DIR):
-    os.makedirs(REPO_INFO_DIR)
-
-  for repo in repoInfoList:
-
-    repoFile = open(REPO_INFO_DIR + os.sep + repo['repo_id'] + PUPPET_EXT, 'w+')
-    writeImports(repoFile, modulesdir, inputFileName='imports.txt')
-    repoFile.write('node /default/ {')
-    repoFile.write('class{ "hdp-repos::process_repo" : ' + ' os_type => "' + repo['os_type'] +
-    '", repo_id => "' + repo['repo_id'] + '", base_url => "' + repo['base_url'] +
-    '", repo_name => "' + repo['repo_name'] + '" }' )
-    repoFile.write('}')
-    repoFile.close()
-
-
-def installRepos():
-  sh = shellRunner()
-  agentdir = os.getcwd()
-  confdir = os.path.abspath(agentdir + ".." + os.sep + ".." + 
-                               os.sep + ".." + os.sep + "puppet")
-
-  for repo in os.listdir(REPO_INFO_DIR):
-    if not repo.endswith(PUPPET_EXT):
-      continue
-    logfile = repo + '_log.log'
-    res = sh.run(['puppet apply', '--confdir=' + confdir, REPO_INFO_DIR + os.sep + repo,
-    '--logdest=' + agentdir + os.sep + REPO_INFO_DIR + os.sep + logfile])
-    if res['exitCode'] == 0:
-      logger.info('Repository ' + repo + ' was installed')
-    else:
-      logger.error('Repository ' + repo + ' wasn''t installed. Please find detailed info in logfile:' + logfile)
   
 def main():
   logging.basicConfig(level=logging.DEBUG)    

+ 68 - 35
ambari-agent/src/main/python/ambari_agent/puppetExecutor.py

@@ -22,6 +22,7 @@ import os.path
 import logging
 import subprocess
 from manifestGenerator import generateManifest
+from RepoInstaller import RepoInstaller
 import pprint
 from Grep import Grep
 
@@ -29,6 +30,9 @@ logger = logging.getLogger()
 
 class puppetExecutor:
 
+  """ Class that executes the commands that come from the server using puppet.
+  This is the class that provides the pluggable point for executing the puppet"""
+
   # How many lines from command output send to server
   OUTPUT_LAST_LINES = 10
   # How many lines from command error output send to server (before Err phrase)
@@ -38,9 +42,6 @@ class puppetExecutor:
 
   NO_ERROR = "none"
 
-  """ Class that executes the commands that come from the server using puppet.
-  This is the class that provides the pluggable point for executing the puppet"""
-  
   def __init__(self, puppetModule, puppetInstall, facterInstall, tmpDir):
     self.puppetModule = puppetModule
     self.puppetInstall = puppetInstall
@@ -76,39 +77,70 @@ class puppetExecutor:
       taskId = command['taskId']
       
     puppetEnv = os.environ
+    #Install repos
+    modulesdir = self.puppetModule + "/modules"
+    repoInstaller = RepoInstaller(command, self.tmpDir, modulesdir, taskId)
+
+    puppetFiles = repoInstaller.installRepos()
     siteppFileName = os.path.join(self.tmpDir, "site-" + str(taskId) + ".pp") 
-    generateManifest(command, siteppFileName, self.puppetModule + "/modules")
-    puppetcommand = self.puppetCommand(siteppFileName)
-    """ Run the command and make sure the output gets propagated"""
-    rubyLib = ""
-    if os.environ.has_key("RUBYLIB"):
-      rubyLib = os.environ["RUBYLIB"]
-      logger.info("Ruby Lib env from Env " + rubyLib)
-    rubyLib = rubyLib + ":" + self.facterLib() + ":" + self.puppetLib()
-    puppetEnv["RUBYLIB"] = rubyLib
-    logger.info("Setting RUBYLIB as: " + rubyLib)
-    logger.info("Running command " + pprint.pformat(puppetcommand))
-    puppet = subprocess.Popen(puppetcommand,
-                                  stdout=subprocess.PIPE,
-                                  stderr=subprocess.PIPE,
-                                  env=puppetEnv)
-    stderr_out = puppet.communicate()
-    error = self.NO_ERROR
-    returncode = 0
-    if puppet.returncode != 0 and puppet.returncode != 2:
-      returncode = puppet.returncode
-      error = stderr_out[1]
-      logging.error("Error running puppet: \n" + stderr_out[1])
-      pass
-    result["stderr"] = error
-    puppetOutput = stderr_out[0]
-    logger.info("Output from puppet :\n" + puppetOutput)
-    result["exitcode"] = returncode
+    puppetFiles.append(siteppFileName)
+    generateManifest(command, siteppFileName, modulesdir)
+    #Run all puppet commands, from manifest generator and for repos installation
+    #Appending outputs and errors, exitcode - maximal from all
+    for puppetFile in puppetFiles:
+      puppetcommand = self.puppetCommand(puppetFile)
+      """ Run the command and make sure the output gets propagated"""
+      rubyLib = ""
+      if os.environ.has_key("RUBYLIB"):
+        rubyLib = os.environ["RUBYLIB"]
+        logger.info("Ruby Lib env from Env " + rubyLib)
+      rubyLib = rubyLib + ":" + self.facterLib() + ":" + self.puppetLib()
+      puppetEnv["RUBYLIB"] = rubyLib
+      logger.info("Setting RUBYLIB as: " + rubyLib)
+      logger.info("Running command " + pprint.pformat(puppetcommand))
+      puppet = subprocess.Popen(puppetcommand,
+                                    stdout=subprocess.PIPE,
+                                    stderr=subprocess.PIPE,
+                                    env=puppetEnv)
+      stderr_out = puppet.communicate()
+      error = "none"
+      returncode = 0
+      if (puppet.returncode != 0 and puppet.returncode != 2) :
+        returncode = puppet.returncode
+        error = stderr_out[1]
+        logging.error("Error running puppet: \n" + stderr_out[1])
+        pass
+		
+      if result.has_key("stderr"):
+        result["stderr"] = result["stderr"] + os.linesep + error
+      else:
+        result["stderr"] = error
+      puppetOutput = stderr_out[0]
+      logger.info("Output from puppet :\n" + puppetOutput)
+      if result.has_key("exitcode"):
+        result["exitcode"] = max(returncode, result["exitcode"])
+      else:
+        result["exitcode"] = returncode
+        
+
+      if result.has_key("stdout"):
+        result["stdout"] = result["stdout"] + os.linesep + puppetOutput
+      else:
+        result["stdout"] = puppetOutput
+
     if error == self.NO_ERROR:
-      result["stdout"] = grep.tail(puppetOutput, self.OUTPUT_LAST_LINES)
+      if result.has_key("stdout"):
+        result["stdout"] = result["stdout"] + os.linesep + str(grep.tail(puppetOutput, self.OUTPUT_LAST_LINES))
+      else:
+        result["stdout"] = grep.tail(puppetOutput, self.OUTPUT_LAST_LINES)
     else:
-      result["stdout"] = grep.grep(puppetOutput, "err", self.ERROR_LAST_LINES_BEFORE, self.ERROR_LAST_LINES_AFTER)
+      if result.has_key("stdout"):
+        result["stdout"] = result["stdout"] + os.linesep + str(grep.grep(puppetOutput, "err", self.ERROR_LAST_LINES_BEFORE, self.ERROR_LAST_LINES_AFTER))
+      else:
+        result["stdout"] = str(grep.grep(puppetOutput, "err", self.ERROR_LAST_LINES_BEFORE, self.ERROR_LAST_LINES_AFTER))
+	
     logger.info("ExitCode : "  + str(result["exitcode"]))
+
     return result
  
 def main():
@@ -118,14 +150,15 @@ def main():
   jsonStr = jsonFile.read() 
   # Below is for testing only.
   
-  puppetInstance = puppetExecutor("/root/workspace/ambari-workspace/ambari-git/ambari-agent/src/main/puppet/",
-                                  "/root/workspace/puppet-install/puppet-2.7.9",
+  puppetInstance = puppetExecutor("/home/centos/ambari_repo_info/ambari-agent/src/main/puppet/",
+                                  "/usr/",
                                   "/root/workspace/puppet-install/facter-1.6.10/",
                                   "/tmp")
   jsonFile = open('test.json', 'r')
   jsonStr = jsonFile.read() 
   parsedJson = json.loads(jsonStr)
-  puppetInstance.runCommand(parsedJson)
+  result = puppetInstance.runCommand(parsedJson)
+  logger.debug(result)
   
 if __name__ == '__main__':
   main()

+ 1 - 0
ambari-server/conf/unix/ambari.properties

@@ -1,3 +1,4 @@
 security.server.keys_dir = /var/lib/ambari-server/keys
 resources.dir = /var/lib/ambari-server/resources
 jdk.url=http://public-repo-1.hortonworks.com/ARTIFACTS/jdk-6u31-linux-x64.bin
+metadata.path=/var/lib/ambari-server/resources/stacks

+ 18 - 1
ambari-server/pom.xml

@@ -22,7 +22,7 @@
   <artifactId>ambari-server</artifactId>
   <packaging>jar</packaging>
   <name>Ambari Server</name>
-  <version>1.0.3-SNAPSHOT</version>
+  <version>1.0.3</version>
   <description>Ambari Server</description>
   <build>
     <plugins>
@@ -75,6 +75,7 @@
           </workarea>
           -->
           <copyright>2012, Apache Software Foundation</copyright>
+          <version>${project.version}</version>
           <group>Development</group>
           <description>Maven Recipe: RPM Package.</description>
           <mappings>
@@ -155,9 +156,25 @@
                 <source>
                   <location>src/main/resources/Ambari-DDL.sql</location>
                 </source>
+                <source>
+                  <location>src/main/resources/stacks</location>
+                </source>
+              </sources>
+            </mapping>
+
+            <mapping>
+              <directory>/usr/lib/python2.6/site-packages/ambari_server</directory>
+              <sources>
+                <source>
+                  <location>src/main/python/bootstrap.py</location>
+                </source>
+                <source>
+                  <location>src/main/python/setupAgent.py</location>
+                </source>
               </sources>
             </mapping>
 
+
             <mapping>
               <directory>/var/run/ambari-server</directory>
             </mapping>

+ 6 - 7
ambari-server/src/main/java/org/apache/ambari/server/api/handlers/BaseManagementHandler.java

@@ -23,10 +23,13 @@ import org.apache.ambari.server.api.services.Request;
 import org.apache.ambari.server.api.services.Result;
 import org.apache.ambari.server.api.services.ResultImpl;
 import org.apache.ambari.server.api.util.TreeNode;
+import org.apache.ambari.server.controller.spi.PropertyId;
 import org.apache.ambari.server.controller.spi.RequestStatus;
 import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
 
+import java.util.Collections;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -36,10 +39,8 @@ public class BaseManagementHandler implements RequestHandler {
   @Override
   public Result handleRequest(Request request) {
     ResourceDefinition resource = request.getResourceDefinition();
-    resource.setProperties(request.getHttpBodyProperties());
-    RequestStatus status = request.getPersistenceManager().persist(resource);
-
-    return createResult(request, status);
+    return createResult(request, request.getPersistenceManager().persist(
+        resource, request.getHttpBodyProperties()));
   }
 
   private Result createResult(Request request, RequestStatus requestStatus) {
@@ -62,14 +63,12 @@ public class BaseManagementHandler implements RequestHandler {
     if (! isSynchronous) {
       Resource requestResource = requestStatus.getRequestResource();
       TreeNode<Resource> r = tree.addChild(requestResource, "request");
-      String requestHref = buildRequestHref(request, requestStatus);
-      r.setProperty("href", requestHref);
+      r.setProperty("href", buildRequestHref(request, requestStatus));
     }
 
     return result;
   }
 
-  //todo: this needs to be rewritten and needs to support operating on clusters collection
   private String buildRequestHref(Request request, RequestStatus requestStatus) {
     StringBuilder sb = new StringBuilder();
     String origHref = request.getURI();

+ 25 - 17
ambari-server/src/main/java/org/apache/ambari/server/api/query/QueryImpl.java

@@ -21,7 +21,6 @@ package org.apache.ambari.server.api.query;
 import org.apache.ambari.server.api.resources.ResourceDefinition;
 import org.apache.ambari.server.api.services.ResultImpl;
 import org.apache.ambari.server.api.util.TreeNodeImpl;
-import org.apache.ambari.server.controller.internal.ClusterControllerImpl;
 import org.apache.ambari.server.controller.internal.PropertyIdImpl;
 import org.apache.ambari.server.controller.utilities.ClusterControllerHelper;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
@@ -32,7 +31,6 @@ import org.apache.ambari.server.controller.predicate.EqualsPredicate;
 import org.apache.ambari.server.api.services.Result;
 import org.apache.ambari.server.controller.spi.*;
 import org.apache.ambari.server.api.util.TreeNode;
-import org.mortbay.log.Log;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -155,21 +153,12 @@ public class QueryImpl implements Query {
       // add a child node for the resource and provide a unique name.  The name is never used.
       //todo: provide a more meaningful node name
       TreeNode<Resource> node = tree.addChild(resource, resource.getType() + ":" + count++);
-      LOG.info("Resource object resource " + resource);
        for (Map.Entry<String, ResourceDefinition> entry : m_mapSubResources.entrySet()) {
         String subResCategory = entry.getKey();
         ResourceDefinition r = entry.getValue();
-        
-        r.setParentId(m_resourceDefinition.getType(), (String) (resource.getPropertyValue(
-            getClusterController().getSchema(m_resourceDefinition.getType()).
-                getKeyPropertyId(m_resourceDefinition.getType())).toString()));
-        
-        LOG.info("Setting various values for resource " + r.getId());
-        if (r.getResourceIds() != null) {
-          for (Map.Entry<Resource.Type, String> tentry: r.getResourceIds().entrySet()) {
-            LOG.info("Resource Id's " + tentry.getKey() + " value " + tentry.getValue());
-          }
-        }
+
+        setParentIdsOnSubResource(resource, r);
+
         TreeNode<Resource> childResult = r.getQuery().execute().getResultTree();
         childResult.setName(subResCategory);
         childResult.setProperty("isCollection", "false");
@@ -178,7 +167,6 @@ public class QueryImpl implements Query {
     }
     return result;
   }
-
   @Override
   public Predicate getInternalPredicate() {
     return createInternalPredicate(m_resourceDefinition);
@@ -272,7 +260,6 @@ public class QueryImpl implements Query {
   }
 
   private BasePredicate createInternalPredicate(ResourceDefinition resourceDefinition) {
-    //todo: account for user predicates
     Resource.Type resourceType = resourceDefinition.getType();
     Map<Resource.Type, String> mapResourceIds = resourceDefinition.getResourceIds();
     Schema schema = getClusterController().getSchema(resourceType);
@@ -283,7 +270,10 @@ public class QueryImpl implements Query {
       //todo: host_component queries and host is not available for component queries.
       //todo: this should be rectified when the data model is changed for host_component
       if (entry.getValue() != null) {
-        setPredicates.add(new EqualsPredicate(schema.getKeyPropertyId(entry.getKey()), entry.getValue()));
+        PropertyId keyPropertyId = schema.getKeyPropertyId(entry.getKey());
+        if (keyPropertyId != null) {
+          setPredicates.add(new EqualsPredicate(keyPropertyId, entry.getValue()));
+        }
       }
     }
 
@@ -358,6 +348,24 @@ public class QueryImpl implements Query {
     return r;
   }
 
+  private void setParentIdsOnSubResource(Resource resource, ResourceDefinition r) {
+    Map<Resource.Type, String> mapParentIds = m_resourceDefinition.getResourceIds();
+    Map<Resource.Type, String> mapResourceIds = new HashMap<Resource.Type, String>(mapParentIds.size());
+    for (Map.Entry<Resource.Type, String> resourceIdEntry : mapParentIds.entrySet()) {
+      Resource.Type type = resourceIdEntry.getKey();
+      String value = resourceIdEntry.getValue();
+
+      if (value == null) {
+        Object o = resource.getPropertyValue(getClusterController().getSchema(type).getKeyPropertyId(type));
+        value = o == null ? null : o.toString();
+      }
+      if (value != null) {
+        mapResourceIds.put(type, value);
+      }
+    }
+    r.setParentIds(mapResourceIds);
+  }
+
   Result createResult() {
     return new ResultImpl(true);
   }

+ 4 - 22
ambari-server/src/main/java/org/apache/ambari/server/api/resources/BaseResourceDefinition.java

@@ -56,10 +56,6 @@ public abstract class BaseResourceDefinition implements ResourceDefinition {
    */
   private Map<Resource.Type, String> m_mapResourceIds = new HashMap<Resource.Type, String>();
 
-  //TODO: Refactor out of this class when setProperties is moved.
-  private Map<PropertyId, Object> m_properties = new HashMap<PropertyId, Object>();
-
-
   /**
    * Constructor.
    *
@@ -73,8 +69,10 @@ public abstract class BaseResourceDefinition implements ResourceDefinition {
   }
 
   @Override
-  public void setParentId(Resource.Type type, String value) {
-    setResourceId(type, value);
+  public void setParentIds(Map<Resource.Type, String> mapIds) {
+    for (Map.Entry<Resource.Type, String> entry : mapIds.entrySet() ) {
+      setResourceId(entry.getKey(), entry.getValue());
+    }
   }
 
   @Override
@@ -119,22 +117,6 @@ public abstract class BaseResourceDefinition implements ResourceDefinition {
     return listProcessors;
   }
 
-  //todo: refactor set/get property methods out of this class
-  @Override
-  public void setProperty(PropertyId property, String value) {
-    m_properties.put(property, value);
-  }
-
-  @Override
-  public void setProperties(Map<PropertyId, String> mapProperties) {
-    m_properties.putAll(mapProperties);
-  }
-
-  @Override
-  public Map<PropertyId, Object> getProperties() {
-    return m_properties;
-  }
-
   @Override
   public boolean equals(Object o) {
     if (this == o) return true;

+ 1 - 1
ambari-server/src/main/java/org/apache/ambari/server/api/resources/ClusterResourceDefinition.java

@@ -61,7 +61,7 @@ public class ClusterResourceDefinition extends BaseResourceDefinition {
         Resource.Type.Service).getKeyPropertyId(Resource.Type.Service));
     mapChildren.put(serviceResource.getPluralName(), serviceResource);
 
-    HostResourceDefinition hostResource = new HostResourceDefinition(null, getId());
+    HostResourceDefinition hostResource = new HostResourceDefinition(null, getId(), true);
     hostResource.getQuery().addProperty(getClusterController().getSchema(
         Resource.Type.Host).getKeyPropertyId(Resource.Type.Host));
     mapChildren.put(hostResource.getPluralName(), hostResource);

+ 10 - 17
ambari-server/src/main/java/org/apache/ambari/server/api/resources/ComponentResourceDefinition.java

@@ -38,12 +38,6 @@ public class ComponentResourceDefinition extends BaseResourceDefinition {
    */
   private String m_clusterId;
 
-  /**
-   * value of serviceId foreign key
-   */
-  private String m_serviceId;
-
-
   /**
    * Constructor.
    *
@@ -54,9 +48,9 @@ public class ComponentResourceDefinition extends BaseResourceDefinition {
   public ComponentResourceDefinition(String id, String clusterId, String serviceId) {
     super(Resource.Type.Component, id);
     m_clusterId = clusterId;
-    m_serviceId = serviceId;
+
     setResourceId(Resource.Type.Cluster, m_clusterId);
-    setResourceId(Resource.Type.Service, m_serviceId);
+    setResourceId(Resource.Type.Service, serviceId);
   }
 
   @Override
@@ -94,17 +88,17 @@ public class ComponentResourceDefinition extends BaseResourceDefinition {
   }
 
   @Override
-  public void setParentId(Resource.Type type, String value) {
-    if (type == Resource.Type.HostComponent) {
-      setId(value);
-    } else {
-      super.setParentId(type, value);
+  public void setParentIds(Map<Resource.Type, String> mapIds) {
+    String id = mapIds.remove(Resource.Type.HostComponent);
+    if (id != null) {
+      setId(id);
     }
+    super.setParentIds(mapIds);
   }
 
   /**
-   * Base resource processor which generates href's.  This is called by the {@link org.apache.ambari.server.api.services.ResultPostProcessor} during post
-   * processing of a result.
+   * Base resource processor which generates href's.  This is called by the
+   * {@link org.apache.ambari.server.api.services.ResultPostProcessor} during post processing of a result.
    */
   private class ComponentHrefProcessor extends BaseHrefPostProcessor {
     @Override
@@ -113,12 +107,11 @@ public class ComponentResourceDefinition extends BaseResourceDefinition {
 
       if (parent.getParent() != null && parent.getParent().getObject().getType() == Resource.Type.HostComponent) {
         Resource r = resultNode.getObject();
-        String clusterId = getResourceIds().get(Resource.Type.Cluster);
         Schema schema = ClusterControllerHelper.getClusterController().getSchema(r.getType());
         Object serviceId = r.getPropertyValue(schema.getKeyPropertyId(Resource.Type.Service));
         Object componentId = r.getPropertyValue(schema.getKeyPropertyId(r.getType()));
 
-        href = href.substring(0, href.indexOf(clusterId) + clusterId.length() + 1) +
+        href = href.substring(0, href.indexOf("/hosts/") + 1) +
             "services/" + serviceId + "/components/" + componentId;
 
         resultNode.setProperty("href", href);

+ 8 - 9
ambari-server/src/main/java/org/apache/ambari/server/api/resources/ConfigurationResourceDefinition.java

@@ -49,9 +49,6 @@ public class ConfigurationResourceDefinition extends BaseResourceDefinition {
     super(Resource.Type.Configuration, configType);
     m_clusterId = clusterId;
     setResourceId(Resource.Type.Cluster, m_clusterId);
-
-    if (null != configTag)
-      setProperty(PropertyHelper.getPropertyId("tag", "Config"), configTag);
   }
 
   @Override
@@ -83,15 +80,17 @@ public class ConfigurationResourceDefinition extends BaseResourceDefinition {
     public void process(Request request, TreeNode<Resource> resultNode, String href) {
       if (resultNode.getObject().getType() == Resource.Type.Configuration) {
 
-        String clusterId = getResourceIds().get(Resource.Type.Cluster);
-        String type = (String) resultNode.getObject().getPropertyValue(PropertyHelper.getPropertyId("type"));
-        String tag = (String) resultNode.getObject().getPropertyValue(PropertyHelper.getPropertyId("tag"));
-
         if (! href.endsWith("/")) {
           href += '/';
         }
-        href = href.substring(0, href.indexOf(clusterId) + clusterId.length() + 1) +
-            "configurations?type=" + type + "&tag=" + tag;
+
+        String clustersToken = "/clusters";
+        int idx = href.indexOf(clustersToken) + clustersToken.length() + 1;
+        idx = href.indexOf("/", idx) + 1;
+
+        String type = (String) resultNode.getObject().getPropertyValue(PropertyHelper.getPropertyId("type"));
+        String tag = (String) resultNode.getObject().getPropertyValue(PropertyHelper.getPropertyId("tag"));
+        href = href.substring(0, idx) + "configurations?type=" + type + "&tag=" + tag;
 
         resultNode.setProperty("href", href);
       } else {

+ 7 - 14
ambari-server/src/main/java/org/apache/ambari/server/api/resources/HostComponentResourceDefinition.java

@@ -38,11 +38,6 @@ public class HostComponentResourceDefinition extends BaseResourceDefinition {
    */
   private String m_clusterId;
 
-  /**
-   * value of host id foreign key
-   */
-  private String m_hostId;
-
 
   /**
    * Constructor.
@@ -54,9 +49,8 @@ public class HostComponentResourceDefinition extends BaseResourceDefinition {
   public HostComponentResourceDefinition(String id, String clusterId, String hostId) {
     super(Resource.Type.HostComponent, id);
     m_clusterId = clusterId;
-    m_hostId = hostId;
     setResourceId(Resource.Type.Cluster, m_clusterId);
-    setResourceId(Resource.Type.Host, m_hostId);
+    setResourceId(Resource.Type.Host, hostId);
   }
 
   @Override
@@ -94,12 +88,12 @@ public class HostComponentResourceDefinition extends BaseResourceDefinition {
   }
 
   @Override
-  public void setParentId(Resource.Type type, String value) {
-    if (type == Resource.Type.Component) {
-      setId(value);
-    } else {
-      super.setParentId(type, value);
+  public void setParentIds(Map<Resource.Type, String> mapIds) {
+    String id = mapIds.remove(Resource.Type.Component);
+    if (id != null) {
+      setId(id);
     }
+    super.setParentIds(mapIds);
   }
 
 
@@ -114,12 +108,11 @@ public class HostComponentResourceDefinition extends BaseResourceDefinition {
 
       if (parent.getParent() != null && parent.getParent().getObject().getType() == Resource.Type.Component) {
         Resource r = resultNode.getObject();
-        String clusterId = getResourceIds().get(Resource.Type.Cluster);
         Schema schema = ClusterControllerHelper.getClusterController().getSchema(r.getType());
         Object host = r.getPropertyValue(schema.getKeyPropertyId(Resource.Type.Host));
         Object hostComponent = r.getPropertyValue(schema.getKeyPropertyId(r.getType()));
 
-        href = href.substring(0, href.indexOf(clusterId) + clusterId.length() + 1) +
+        href = href.substring(0, href.indexOf("/services/") + 1) +
             "hosts/" + host + "/host_components/" + hostComponent;
 
         resultNode.setProperty("href", href);

+ 9 - 3
ambari-server/src/main/java/org/apache/ambari/server/api/resources/HostResourceDefinition.java

@@ -36,14 +36,21 @@ public class HostResourceDefinition extends BaseResourceDefinition {
    */
   private String m_clusterId;
 
+  /**
+   * Whether the host resource is associated with a cluster.
+   */
+  private boolean m_attached;
+
   /**
    * Constructor.
    *
    * @param id        host id value
    * @param clusterId cluster id value
+   * @param attached
    */
-  public HostResourceDefinition(String id, String clusterId) {
+  public HostResourceDefinition(String id, String clusterId, boolean attached) {
     super(Resource.Type.Host, id);
+    m_attached = attached;
     m_clusterId = clusterId;
     setResourceId(Resource.Type.Cluster, m_clusterId);
     
@@ -73,8 +80,7 @@ public class HostResourceDefinition extends BaseResourceDefinition {
   public Map<String, ResourceDefinition> getSubResources() {
     Map<String, ResourceDefinition> mapChildren = new HashMap<String, ResourceDefinition>();
 
-    // !!! is this a host for a cluster
-    if (null != m_clusterId) {
+    if (m_attached) {
       HostComponentResourceDefinition hostComponentResource =
           new HostComponentResourceDefinition(null, m_clusterId, getId());
       hostComponentResource.getQuery().addProperty(getClusterController().getSchema(

+ 3 - 27
ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceDefinition.java

@@ -63,12 +63,11 @@ public interface ResourceDefinition {
   public Resource.Type getType();
 
   /**
-   * Set the value of the parent foreign key.
+   * Set the values of the parent foreign keys.
    *
-   * @param type  resource type of the parent
-   * @param value vale of the parent id
+   * @param mapIds  map of all parent foreign keys. Map from resource type to id value.
    */
-  public void setParentId(Resource.Type type, String value);
+  public void setParentIds(Map<Resource.Type, String> mapIds);
 
   /**
    * Obtain the primary and foreign key properties for the resource.
@@ -101,29 +100,6 @@ public interface ResourceDefinition {
    */
   public List<PostProcessor> getPostProcessors();
 
-  //TODO: refactor set/get Property methods out of this class
-  /**
-   * Set a property on this resource.
-   *
-   * @param property the property
-   * @param value    the value
-   */
-  public void setProperty(PropertyId property, String value);
-
-  /**
-   * Set a map of properties on the resource.
-   *
-   * @param mapProperties a map of properties
-   */
-  public void setProperties(Map<PropertyId, String> mapProperties);
-
-  /**
-   * Get the properties which have been set on this resource.
-   *
-   * @return the properties which have been set on this resource
-   */
-  public Map<PropertyId, Object> getProperties();
-
   /**
    * Resource specific result processor.
    * Used to provide resource specific processing of a result.

+ 3 - 2
ambari-server/src/main/java/org/apache/ambari/server/api/services/BasePersistenceManager.java

@@ -26,6 +26,7 @@ import org.apache.ambari.server.controller.spi.Request;
 
 import java.util.Collections;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Base PersistenceManager functionality.
@@ -36,7 +37,7 @@ public abstract class BasePersistenceManager implements PersistenceManager {
     return ClusterControllerHelper.getClusterController();
   }
 
-  protected Request createControllerRequest(Map<PropertyId, Object> properties) {
-    return PropertyHelper.getCreateRequest(Collections.singleton(properties));
+  protected Request createControllerRequest(Set<Map<PropertyId, Object>> setProperties) {
+    return PropertyHelper.getCreateRequest(setProperties);
   }
 }

+ 19 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/services/ComponentService.java

@@ -83,6 +83,25 @@ public class ComponentService extends BaseService {
         createResourceDefinition(null, m_clusterName, m_serviceName));
   }
 
+  /**
+   * Handles: POST /clusters/{clusterID}/services/{serviceID}/components
+   * Create components by specifying an array of components in the http body.
+   * This is used to create multiple components in a single request.
+   *
+   * @param body          http body
+   * @param headers       http headers
+   * @param ui            uri info
+   *
+   * @return status code only, 201 if successful
+   */
+  @POST
+  @Produces("text/plain")
+  public Response createComponents(String body, @Context HttpHeaders headers, @Context UriInfo ui) {
+
+    return handleRequest(headers, body, ui, Request.Type.POST,
+        createResourceDefinition(null, m_clusterName, m_serviceName));
+  }
+
   /**
    * Handles: POST /clusters/{clusterID}/services/{serviceID}/components/{componentID}
    * Create a specific component.

+ 9 - 8
ambari-server/src/main/java/org/apache/ambari/server/api/services/ConfigurationService.java

@@ -59,11 +59,12 @@ public class ConfigurationService extends BaseService {
   @Produces("text/plain")
   public Response getConfigurations(@Context HttpHeaders headers, @Context UriInfo ui) {
     return handleRequest(headers, null, ui, Request.Type.GET,
-        createResourceDefinition(null, null, m_clusterName));
+        createResourceDefinition(null, m_clusterName));
   }
 
   /**
-   * Handles URL: /clusters/{clusterId}/configurations.  The body should contain:
+   * Handles URL: /clusters/{clusterId}/configurations
+   * The body should contain:
    * <pre>
    * {
    *     "type":"type_string",
@@ -76,30 +77,30 @@ public class ConfigurationService extends BaseService {
    *     }
    * }
    * </pre>
-   * Get all services for a cluster.
+   *
+   * To create multiple configurations is a request, provide an array of configuration properties.
    *
    * @param headers http headers
    * @param ui      uri info
-   * @return service collection resource representation
+   * @return status code only, 201 if successful
    */
   @POST
   @Produces("text/plain")
   public Response createConfigurations(String body,@Context HttpHeaders headers, @Context UriInfo ui) {
 
     return handleRequest(headers, body, ui, Request.Type.POST,
-        createResourceDefinition(null, null, m_clusterName));
+        createResourceDefinition(null, m_clusterName));
   }
 
   /**
    * Create a service resource definition.
    *
    * @param configType  configuration type
-   * @param configTag   tag applied to the configuration
    * @param clusterName cluster name
    *
    * @return a service resource definition
    */
-  ResourceDefinition createResourceDefinition(String configType, String configTag, String clusterName) {
-    return new ConfigurationResourceDefinition(configType, configTag, clusterName);
+  ResourceDefinition createResourceDefinition(String configType, String clusterName) {
+    return new ConfigurationResourceDefinition(configType, null, clusterName);
   }
 }

+ 17 - 8
ambari-server/src/main/java/org/apache/ambari/server/api/services/CreatePersistenceManager.java

@@ -20,29 +20,38 @@ package org.apache.ambari.server.api.services;
 
 import org.apache.ambari.server.api.resources.ResourceDefinition;
 import org.apache.ambari.server.AmbariException;
-import org.apache.ambari.server.controller.spi.ClusterController;
-import org.apache.ambari.server.controller.spi.RequestStatus;
-import org.apache.ambari.server.controller.spi.Resource;
-import org.apache.ambari.server.controller.spi.Schema;
+import org.apache.ambari.server.controller.spi.*;
 
+import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Responsible for persisting the creation of a resource in the back end.
  */
 public class CreatePersistenceManager extends BasePersistenceManager {
   @Override
-  public RequestStatus persist(ResourceDefinition resource) {
+  public RequestStatus persist(ResourceDefinition resource, Set<Map<PropertyId, Object>> setProperties) {
     ClusterController controller = getClusterController();
     Map<Resource.Type, String> mapResourceIds = resource.getResourceIds();
     Resource.Type type = resource.getType();
     Schema schema = controller.getSchema(type);
 
-    for (Map.Entry<Resource.Type, String> entry : mapResourceIds.entrySet()) {
-      resource.setProperty(schema.getKeyPropertyId(entry.getKey()), entry.getValue());
+    if (setProperties.size() == 0) {
+      setProperties.add(new HashMap<PropertyId, Object>());
     }
+
+    for (Map<PropertyId, Object> mapProperties : setProperties) {
+      for (Map.Entry<Resource.Type, String> entry : mapResourceIds.entrySet()) {
+        PropertyId property = schema.getKeyPropertyId(entry.getKey());
+        if (! mapProperties.containsKey(property)) {
+          mapProperties.put(property, entry.getValue());
+        }
+      }
+    }
+
     try {
-      return controller.createResources(type, createControllerRequest(resource.getProperties()));
+      return controller.createResources(type, createControllerRequest(setProperties));
     } catch (AmbariException e) {
       //todo: handle exception
       throw new RuntimeException("Create of resource failed: " + e, e);

+ 5 - 1
ambari-server/src/main/java/org/apache/ambari/server/api/services/DeletePersistenceManager.java

@@ -20,15 +20,19 @@ package org.apache.ambari.server.api.services;
 
 import org.apache.ambari.server.api.resources.ResourceDefinition;
 import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.controller.spi.PropertyId;
 import org.apache.ambari.server.controller.spi.RequestStatus;
 
+import java.util.Map;
+import java.util.Set;
+
 
 /**
  * Responsible for persisting the deletion of a resource in the back end.
  */
 public class DeletePersistenceManager extends BasePersistenceManager {
   @Override
-  public RequestStatus persist(ResourceDefinition resource) {
+  public RequestStatus persist(ResourceDefinition resource, Set<Map<PropertyId, Object>> setProperties) {
     try {
       //todo: need to account for multiple resources and user predicate
       return getClusterController().deleteResources(resource.getType(),

+ 19 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/services/HostComponentService.java

@@ -83,6 +83,25 @@ public class HostComponentService extends BaseService {
         createResourceDefinition(null, m_clusterName, m_hostName));
   }
 
+  /**
+   * Handles POST /clusters/{clusterID}/hosts/{hostID}/host_components
+   * Create host components by specifying an array of host components in the http body.
+   * This is used to create multiple host components in a single request.
+   *
+   * @param body              http body
+   * @param headers           http headers
+   * @param ui                uri info
+   *
+   * @return status code only, 201 if successful
+   */
+  @POST
+  @Produces("text/plain")
+  public Response createHostComponents(String body, @Context HttpHeaders headers, @Context UriInfo ui) {
+
+    return handleRequest(headers, body, ui, Request.Type.POST,
+        createResourceDefinition(null, m_clusterName, m_hostName));
+  }
+
   /**
    * Handles POST /clusters/{clusterID}/hosts/{hostID}/host_components/{hostComponentID}
    * Create a specific host_component.

+ 30 - 10
ambari-server/src/main/java/org/apache/ambari/server/api/services/HostService.java

@@ -76,7 +76,7 @@ public class HostService extends BaseService {
                           @PathParam("hostName") String hostName) {
 
     return handleRequest(headers, null, ui, Request.Type.GET,
-        createResourceDefinition(hostName, m_clusterName));
+        createResourceDefinition(hostName, m_clusterName, ui));
   }
 
   /**
@@ -90,7 +90,26 @@ public class HostService extends BaseService {
   @GET
   @Produces("text/plain")
   public Response getHosts(@Context HttpHeaders headers, @Context UriInfo ui) {
-    return handleRequest(headers, null, ui, Request.Type.GET, createResourceDefinition(null, m_clusterName));
+    return handleRequest(headers, null, ui, Request.Type.GET, createResourceDefinition(null, m_clusterName, ui));
+  }
+
+  /**
+   * Handles POST /clusters/{clusterID}/hosts
+   * Create hosts by specifying an array of hosts in the http body.
+   * This is used to create multiple hosts in a single request.
+   *
+   * @param body     http body
+   * @param headers  http headers
+   * @param ui       uri info
+   *
+   * @return status code only, 201 if successful
+   */
+  @POST
+  @Produces("text/plain")
+  public Response createHosts(String body, @Context HttpHeaders headers, @Context UriInfo ui) {
+
+    return handleRequest(headers, body, ui, Request.Type.POST,
+        createResourceDefinition(null, m_clusterName, ui));
   }
 
   /**
@@ -111,7 +130,7 @@ public class HostService extends BaseService {
                           @PathParam("hostName") String hostName) {
 
     return handleRequest(headers, body, ui, Request.Type.POST,
-        createResourceDefinition(hostName, m_clusterName));
+        createResourceDefinition(hostName, m_clusterName, ui));
   }
 
   /**
@@ -132,7 +151,7 @@ public class HostService extends BaseService {
                           @PathParam("hostName") String hostName) {
 
     return handleRequest(headers, body, ui, Request.Type.PUT,
-        createResourceDefinition(hostName, m_clusterName));
+        createResourceDefinition(hostName, m_clusterName, ui));
   }
 
   /**
@@ -150,7 +169,7 @@ public class HostService extends BaseService {
   public Response updateHosts(String body, @Context HttpHeaders headers, @Context UriInfo ui) {
 
     return handleRequest(headers, body, ui, Request.Type.PUT,
-        createResourceDefinition(null, m_clusterName));
+        createResourceDefinition(null, m_clusterName, ui));
   }
 
   /**
@@ -170,7 +189,7 @@ public class HostService extends BaseService {
                              @PathParam("hostName") String hostName) {
 
     return handleRequest(headers, null, ui, Request.Type.DELETE,
-        createResourceDefinition(hostName, m_clusterName));
+        createResourceDefinition(hostName, m_clusterName, ui));
   }
 
   /**
@@ -187,11 +206,12 @@ public class HostService extends BaseService {
   /**
    * Create a host resource definition.
    *
-   * @param hostName    host name
-   * @param clusterName cluster name
+   * @param hostName     host name
+   * @param clusterName  cluster name
+   * @param ui           uri information
    * @return a host resource definition
    */
-  ResourceDefinition createResourceDefinition(String hostName, String clusterName) {
-    return new HostResourceDefinition(hostName, clusterName);
+  ResourceDefinition createResourceDefinition(String hostName, String clusterName, UriInfo ui) {
+    return new HostResourceDefinition(hostName, clusterName, ui.getRequestUri().toString().contains("/clusters/"));
   }
 }

+ 3 - 9
ambari-server/src/main/java/org/apache/ambari/server/api/services/PersistKeyValueService.java

@@ -21,14 +21,12 @@ package org.apache.ambari.server.api.services;
 import java.io.IOException;
 import java.util.Map;
 
-import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
 import javax.xml.bind.JAXBException;
 
@@ -36,8 +34,6 @@ import org.apache.ambari.server.state.fsm.InvalidStateTransitionException;
 import org.apache.ambari.server.utils.StageUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-import org.codehaus.jackson.JsonGenerationException;
-import org.codehaus.jackson.map.JsonMappingException;
 
 import com.google.inject.Inject;
 
@@ -53,10 +49,9 @@ public class PersistKeyValueService {
 
   @POST
   @Produces("text/plain")
-  public Response update(String keyValues,
-      @Context HttpServletRequest req)
+  public Response update(String keyValues)
       throws WebApplicationException, InvalidStateTransitionException,
-      JsonGenerationException, JsonMappingException, JAXBException, IOException {
+      JAXBException, IOException {
     LOG.info("Received message from UI " + keyValues);
     Map<String, String> keyValuesMap = StageUtils.fromJson(keyValues, Map.class);
     /* Call into the heartbeat handler */
@@ -77,8 +72,7 @@ public class PersistKeyValueService {
   
   @GET
   @Produces("text/plain")
-  public String getAllKeyValues() throws JsonGenerationException,
-    JsonMappingException, JAXBException, IOException {
+  public String getAllKeyValues() throws JAXBException, IOException {
     Map<String, String> ret = persistKeyVal.getAllKeyValues();
     String stringRet = StageUtils.jaxbToString(ret);
     LOG.info("Returning " + stringRet);

+ 8 - 2
ambari-server/src/main/java/org/apache/ambari/server/api/services/PersistenceManager.java

@@ -19,8 +19,12 @@
 package org.apache.ambari.server.api.services;
 
 import org.apache.ambari.server.api.resources.ResourceDefinition;
+import org.apache.ambari.server.controller.spi.PropertyId;
 import org.apache.ambari.server.controller.spi.RequestStatus;
 
+import java.util.Map;
+import java.util.Set;
+
 /**
  * Persistence manager which is responsible for persisting a resource state to the back end.
  * This includes create, update and delete operations.
@@ -29,10 +33,12 @@ public interface PersistenceManager {
   /**
    * Persist a resource to the back end.
    *
-   * @param resource  the resource to persist
+   *
+   * @param resource       resource definition for request
+   * @param setProperties  properties to be persisted.
    *
    * @return the request state.
    *
    */
-  public RequestStatus persist(ResourceDefinition resource);
+  public RequestStatus persist(ResourceDefinition resource, Set<Map<PropertyId, Object>> setProperties);
 }

+ 3 - 2
ambari-server/src/main/java/org/apache/ambari/server/api/services/Request.java

@@ -26,6 +26,7 @@ import org.apache.ambari.server.controller.spi.TemporalInfo;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Provides information on the current request.
@@ -120,9 +121,9 @@ public interface Request {
   /**
    * Obtain the properties which have been parsed from the http body.
    *
-   * @return a map containing the properties contained in the http body
+   * @return a set of maps containing the properties contained in the http body
    */
-  public Map<PropertyId, String> getHttpBodyProperties();
+  public Set<Map<PropertyId, Object>> getHttpBodyProperties();
 
     //TODO: refactor persistence mechanism
   /**

+ 1 - 1
ambari-server/src/main/java/org/apache/ambari/server/api/services/RequestImpl.java

@@ -194,7 +194,7 @@ public class RequestImpl implements Request {
   }
 
   @Override
-  public Map<PropertyId, String> getHttpBodyProperties() {
+  public Set<Map<PropertyId, Object>> getHttpBodyProperties() {
     return getHttpBodyParser().parse(getHttpBody());
   }
 

+ 6 - 2
ambari-server/src/main/java/org/apache/ambari/server/api/services/UpdatePersistenceManager.java

@@ -21,18 +21,22 @@ package org.apache.ambari.server.api.services;
 
 import org.apache.ambari.server.api.resources.ResourceDefinition;
 import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.controller.spi.PropertyId;
 import org.apache.ambari.server.controller.spi.RequestStatus;
 
+import java.util.Map;
+import java.util.Set;
+
 
 /**
  * Responsible for persisting the updating of a resource in the back end.
  */
 public class UpdatePersistenceManager extends BasePersistenceManager {
   @Override
-  public RequestStatus persist(ResourceDefinition resource) {
+  public RequestStatus persist(ResourceDefinition resource, Set<Map<PropertyId, Object>> setProperties) {
     try {
       return getClusterController().updateResources(resource.getType(), createControllerRequest(
-          resource.getProperties()), resource.getQuery().getInternalPredicate());
+          setProperties), resource.getQuery().getInternalPredicate());
     } catch (AmbariException e) {
       //todo: handle exception
       throw new RuntimeException("Update of resource failed: " + e, e);

+ 20 - 11
ambari-server/src/main/java/org/apache/ambari/server/api/services/parsers/JsonPropertyParser.java

@@ -24,42 +24,51 @@ import org.codehaus.jackson.JsonNode;
 import org.codehaus.jackson.map.ObjectMapper;
 
 import java.io.IOException;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
+import java.util.*;
 
 /**
  * JSON parser which parses a JSON string into a map of properties and values.
  */
 public class JsonPropertyParser implements RequestBodyParser {
-  private Map<PropertyId, String> m_properties = new HashMap<PropertyId, String>();
+  private Set<Map<PropertyId, Object>> m_setProperties = new HashSet<Map<PropertyId, Object>>();
+
 
   @Override
-  public Map<PropertyId, String> parse(String s) {
+  public Set<Map<PropertyId, Object>> parse(String s) {
+
     ObjectMapper mapper = new ObjectMapper();
 
     if (s != null && ! s.isEmpty()) {
+      s = ensureArrayFormat(s);
       try {
-        processNode(mapper.readValue(s, JsonNode.class), "");
+        JsonNode[] nodes = mapper.readValue(s, JsonNode[].class);
+        for(JsonNode node : nodes) {
+          Map<PropertyId, Object> mapProperties = new HashMap<PropertyId, Object>();
+          processNode(node, "", mapProperties);
+          m_setProperties.add(mapProperties);
+        }
       } catch (IOException e) {
         throw new RuntimeException("Unable to parse json: " + e, e);
       }
     }
-
-    return m_properties;
+    return m_setProperties;
   }
 
-  private void processNode(JsonNode node, String path) {
+  private void processNode(JsonNode node, String path, Map<PropertyId, Object> mapProperties) {
     Iterator<String> iter = node.getFieldNames();
     String name;
     while (iter.hasNext()) {
       name = iter.next();
       JsonNode child = node.get(name);
       if (child.isContainerNode()) {
-        processNode(child, path.isEmpty() ? name : path + '.' + name);
+        processNode(child, path.isEmpty() ? name : path + '.' + name, mapProperties);
       } else {
-        m_properties.put(PropertyHelper.getPropertyId(name, path), child.asText());
+        mapProperties.put(PropertyHelper.getPropertyId(name, path), child.asText());
       }
     }
   }
+
+  private String ensureArrayFormat(String s) {
+    return s.startsWith("[") ? s : '[' + s + ']';
+  }
 }

+ 3 - 2
ambari-server/src/main/java/org/apache/ambari/server/api/services/parsers/RequestBodyParser.java

@@ -21,6 +21,7 @@ package org.apache.ambari.server.api.services.parsers;
 import org.apache.ambari.server.controller.spi.PropertyId;
 
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Parse the provided String into a map of properties and associated values.
@@ -32,7 +33,7 @@ public interface RequestBodyParser {
    *
    * @param s  the string body to be parsed
    *
-   * @return a map of properties or an empty map if no properties exist
+   * @return a set of maps of properties or an empty set if no properties exist
    */
-  public Map<PropertyId, String> parse(String s);
+  public Set<Map<PropertyId, Object>> parse(String s);
 }

+ 2 - 0
ambari-server/src/main/python/ambari-server.py

@@ -41,6 +41,8 @@ IP_TBLS_ENABLED="Firewall is running"
 IP_TBLS_DISABLED="Firewall is not running"
 IP_TBLS_SRVC_NT_FND="iptables: unrecognized service"
 SERVER_START_CMD="{0}" + os.sep + "bin" + os.sep + "java -cp {1}"+ os.pathsep + ".." + os.sep + "lib" + os.sep + "ambari-server" + os.sep + "* org.apache.ambari.server.controller.AmbariServer"
+# uncomment for debug
+# SERVER_START_CMD="{0}" + os.sep + "bin" + os.sep + "java -Xdebug -Xrunjdwp:transport=dt_socket,address=5005,server=y,suspend=n -cp {1}"+ os.pathsep + ".." + os.sep + "lib" + os.sep + "ambari-server" + os.sep + "* org.apache.ambari.server.controller.AmbariServer"
 AMBARI_CONF_VAR="AMBARI_CONF_DIR"
 PG_ST_CMD = "service postgresql status"
 PG_START_CMD = "service postgresql start"

+ 26 - 17
ambari-server/src/main/python/bootstrap.py

@@ -64,10 +64,10 @@ class SCP(threading.Thread):
 
 class SSH(threading.Thread):
   """ Ssh implementation of this """
-  def __init__(self, sshKeyFile, host, commands):
+  def __init__(self, sshKeyFile, host, command):
     self.sshKeyFile = sshKeyFile
     self.host = host
-    self.commands = commands
+    self.command = command
     self.ret = {"exitstatus" : -1, "log": "FAILED"}
     threading.Thread.__init__(self)
     pass
@@ -81,7 +81,7 @@ class SSH(threading.Thread):
   def run(self):
     sshcommand = ["ssh", "-o", "ConnectTimeOut=3", "-o",
                    "StrictHostKeyChecking=no", "-i", self.sshKeyFile,
-                    self.host, ";".join(self.commands)]
+                    self.host, self.command]
     sshstat = subprocess.Popen(sshcommand, stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
     log = sshstat.communicate()
@@ -96,10 +96,10 @@ def splitlist(hosts, n):
 
 class PSSH:
   """Run SSH in parallel for a given list of hosts"""
-  def __init__(self, hosts, sshKeyFile, commands):
+  def __init__(self, hosts, sshKeyFile, command):
     self.hosts = hosts
     self.sshKeyFile = sshKeyFile
-    self.commands = commands
+    self.command = command
     self.ret = {}
     pass
     
@@ -112,7 +112,7 @@ class PSSH:
     for chunk in splitlist(self.hosts, 20):
       chunkstats = []
       for host in chunk:
-        ssh = SSH(self.sshKeyFile, host, self.commands)
+        ssh = SSH(self.sshKeyFile, host, self.command)
         ssh.start()
         chunkstats.append(ssh)
         pass
@@ -173,32 +173,41 @@ class BootStrap:
     return os.path.join(self.scriptDir, "setupAgent.py")
     
   def runSetupAgent(self):
-    commands = ["export AMBARI_PASSPHRASE=" + os.environ[AMBARI_PASSPHRASE_VAR], "/tmp/setupAgent.py"]
-    pssh = PSSH(self.hostlist, self.sshkeyFile, commands)
+    command = "python /tmp/setupAgent.py " + os.environ[AMBARI_PASSPHRASE_VAR]
+    pssh = PSSH(self.hostlist, self.sshkeyFile, command)
     pssh.run()
     out = pssh.getstatus()
     logging.info("Parallel ssh returns " + pprint.pformat(out))
-
-    """ Test code for setting env var on agent host before starting setupAgent.py
-    commands = ["export AMBARI_PASSPHRASE=" + os.environ[AMBARI_PASSPHRASE_VAR], "set"]
-    pssh = PSSH(self.hostlist, self.sshkeyFile, commands)
-    pssh.run()
-    out = pssh.getstatus()
-    logging.info("Look for AMBARI_PASSPHRASE in out " + pprint.pformat(out))
-    """
+    pass
 
   def copyNeededFiles(self):
     try:
       """Copying the files """
+      """ Uncomment when ambari.repo is ready
       fileToCopy = self.getRepoFile()
       pscp = PSCP(self.hostlist, self.sshkeyFile, fileToCopy, "/etc/yum.repos.d")
       pscp.run()
       out = pscp.getstatus()
       logging.info("Parallel scp return " + pprint.pformat(out))
+      """
+
+      """ Remove this block when ambari.repo is ready """
+      """ copy agent rpm to remote host """
+      pscp = PSCP(self.hostlist, self.sshkeyFile, "/home/centos/ambari/ambari-agent/target/rpm/ambari-agent/RPMS/noarch/ambari-agent*.rpm", "/tmp")
+      pscp.run()
+      out = pscp.getstatus()
+      logging.info("Parallel scp return " + pprint.pformat(out))
+
+
+      pscp = PSCP(self.hostlist, self.sshkeyFile, "/usr/lib/python2.6/site-packages/ambari_server/setupAgent.py", "/tmp")
+      pscp.run()
+      out = pscp.getstatus()
+      logging.info("Parallel scp return " + pprint.pformat(out))
+
     except Exception as e:
       logging.info("Traceback " + traceback.format_exc())
       pass
-       
+
     pass
   
   def run(self):

+ 38 - 7
ambari-server/src/main/python/setupAgent.py

@@ -29,26 +29,57 @@ import threading
 import traceback
 from pprint import pformat
 
-def installAgent():
-  """ Run yum install and make sure the agent install alright """
-  # TODO replace echo with yum
-  yumcommand = ["echo", "install", "ambari-agent"]
-  yumstat = subprocess.Popen(yumcommand, stdout=subprocess.PIPE)
-  log = yumstat.communicate(0)
+AMBARI_PASSPHRASE_VAR = "AMBARI_PASSPHRASE"
+
+def execOsCommand(osCommand):
+  """ Run yum install and make sure the puppet install alright """
+  osStat = subprocess.Popen(osCommand, stdout=subprocess.PIPE)
+  log = osStat.communicate(0)
   ret = {}
-  ret["exitstatus"] = yumstat.returncode
+  ret["exitstatus"] = osStat.returncode
   ret["log"] = log
   return ret
 
+def installPreReq():
+  """ Adds hdp repo
+  rpmCommand = ["rpm", "-Uvh", "http://public-repo-1.hortonworks.com/HDP-1.1.1.16/repos/centos6/hdp-release-1.1.1.16-1.el6.noarch.rpm"]
+  execOsCommand(rpmCommand)
+  """
+  yumCommand = ["yum", "-y", "install", "epel-release"]
+  execOsCommand(yumCommand)
+
+def installPuppet():
+  """ Run yum install and make sure the puppet install alright """
+  osCommand = ["useradd", "-G", "puppet", "puppet"]
+  execOsCommand(osCommand)
+  yumCommand = ["yum", "-y", "install", "puppet"]
+  return execOsCommand(yumCommand)
+
+def installAgent():
+  """ Run yum install and make sure the agent install alright """
+  # TODO replace rpm with yum -y
+  rpmCommand = ["yum", "install", "-y", "/tmp/ambari-agent*.rpm"]
+  return execOsCommand(rpmCommand)
+
 def configureAgent():
   """ Configure the agent so that it has all the configs knobs properly 
   installed """
   return
 
+def runAgent(passPhrase):
+  os.environ[AMBARI_PASSPHRASE_VAR] = passPhrase
+  subprocess.call("/usr/sbin/ambari-agent start", shell=True)
+
 def main(argv=None):
   scriptDir = os.path.realpath(os.path.dirname(argv[0]))
+  """ Parse the input"""
+  onlyargs = argv[1:]
+  passPhrase = onlyargs[0]
+  installPreReq()
+  # installPuppet()
   installAgent()
   configureAgent()
+  runAgent(passPhrase)
   
 if __name__ == '__main__':
   logging.basicConfig(level=logging.DEBUG)

+ 1 - 1
ambari-server/src/main/resources/ca.config

@@ -1,7 +1,7 @@
 [ ca ]
 default_ca             = CA_CLIENT
 [ CA_CLIENT ]
-dir		       = keystore/db
+dir		       = /var/lib/ambari-server/keys/db
 certs                  = $dir/certs
 new_certs_dir          = $dir/newcerts
 

+ 6 - 8
ambari-server/src/test/java/org/apache/ambari/server/api/handlers/CreateHandlerTest.java

@@ -48,7 +48,7 @@ public class CreateHandlerTest {
     Resource resource1 = createMock(Resource.class);
     Resource resource2 = createMock(Resource.class);
 
-    Map<PropertyId, String> resourceProperties = new HashMap<PropertyId, String>();
+    Set<Map<PropertyId, Object>> setResourceProperties = new HashSet<Map<PropertyId, Object>>();
 
     Set<Resource> setResources = new HashSet<Resource>();
     setResources.add(resource1);
@@ -56,10 +56,9 @@ public class CreateHandlerTest {
 
     // expectations
     expect(request.getResourceDefinition()).andReturn(resource);
-    expect(request.getHttpBodyProperties()).andReturn(resourceProperties);
-    resource.setProperties(resourceProperties);
+    expect(request.getHttpBodyProperties()).andReturn(setResourceProperties);
     expect(request.getPersistenceManager()).andReturn(pm);
-    expect(pm.persist(resource)).andReturn(status);
+    expect(pm.persist(resource, setResourceProperties)).andReturn(status);
     expect(status.getStatus()).andReturn(RequestStatus.Status.Complete);
     expect(status.getAssociatedResources()).andReturn(setResources);
     expect(resource1.getType()).andReturn(Resource.Type.Cluster).anyTimes();
@@ -100,7 +99,7 @@ public class CreateHandlerTest {
     Resource resource2 = createMock(Resource.class);
     Resource requestResource = createMock(Resource.class);
 
-    Map<PropertyId, String> resourceProperties = new HashMap<PropertyId, String>();
+    Set<Map<PropertyId, Object>> setResourceProperties = new HashSet<Map<PropertyId, Object>>();
 
     Set<Resource> setResources = new HashSet<Resource>();
     setResources.add(resource1);
@@ -108,10 +107,9 @@ public class CreateHandlerTest {
 
     // expectations
     expect(request.getResourceDefinition()).andReturn(resource);
-    expect(request.getHttpBodyProperties()).andReturn(resourceProperties);
-    resource.setProperties(resourceProperties);
+    expect(request.getHttpBodyProperties()).andReturn(setResourceProperties);
     expect(request.getPersistenceManager()).andReturn(pm);
-    expect(pm.persist(resource)).andReturn(status);
+    expect(pm.persist(resource, setResourceProperties)).andReturn(status);
     expect(status.getStatus()).andReturn(RequestStatus.Status.Accepted);
     expect(status.getAssociatedResources()).andReturn(setResources);
     expect(resource1.getType()).andReturn(Resource.Type.Cluster).anyTimes();

+ 7 - 12
ambari-server/src/test/java/org/apache/ambari/server/api/handlers/DeleteHandlerTest.java

@@ -29,10 +29,7 @@ import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
 import org.junit.Test;
 
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 import static org.easymock.EasyMock.*;
 import static org.junit.Assert.*;
@@ -51,7 +48,7 @@ public class DeleteHandlerTest {
     Resource resource1 = createMock(Resource.class);
     Resource resource2 = createMock(Resource.class);
 
-    Map<PropertyId, String> resourceProperties = new HashMap<PropertyId, String>();
+    Set<Map<PropertyId, Object>> setResourceProperties = new HashSet<Map<PropertyId, Object>>();
 
     Set<Resource> setResources = new HashSet<Resource>();
     setResources.add(resource1);
@@ -59,10 +56,9 @@ public class DeleteHandlerTest {
 
     // expectations
     expect(request.getResourceDefinition()).andReturn(resource);
-    expect(request.getHttpBodyProperties()).andReturn(resourceProperties);
-    resource.setProperties(resourceProperties);
+    expect(request.getHttpBodyProperties()).andReturn(setResourceProperties);
     expect(request.getPersistenceManager()).andReturn(pm);
-    expect(pm.persist(resource)).andReturn(status);
+    expect(pm.persist(resource, setResourceProperties)).andReturn(status);
     expect(status.getStatus()).andReturn(RequestStatus.Status.Complete);
     expect(status.getAssociatedResources()).andReturn(setResources);
     expect(resource1.getType()).andReturn(Resource.Type.Cluster).anyTimes();
@@ -103,7 +99,7 @@ public class DeleteHandlerTest {
     Resource resource2 = createMock(Resource.class);
     Resource requestResource = createMock(Resource.class);
 
-    Map<PropertyId, String> resourceProperties = new HashMap<PropertyId, String>();
+    Set<Map<PropertyId, Object>> setResourceProperties = new HashSet<Map<PropertyId, Object>>();
 
     Set<Resource> setResources = new HashSet<Resource>();
     setResources.add(resource1);
@@ -111,10 +107,9 @@ public class DeleteHandlerTest {
 
     // expectations
     expect(request.getResourceDefinition()).andReturn(resource);
-    expect(request.getHttpBodyProperties()).andReturn(resourceProperties);
-    resource.setProperties(resourceProperties);
+    expect(request.getHttpBodyProperties()).andReturn(setResourceProperties);
     expect(request.getPersistenceManager()).andReturn(pm);
-    expect(pm.persist(resource)).andReturn(status);
+    expect(pm.persist(resource, setResourceProperties)).andReturn(status);
     expect(status.getStatus()).andReturn(RequestStatus.Status.Accepted);
     expect(status.getAssociatedResources()).andReturn(setResources);
     expect(resource1.getType()).andReturn(Resource.Type.Cluster).anyTimes();

+ 7 - 12
ambari-server/src/test/java/org/apache/ambari/server/api/handlers/UpdateHandlerTest.java

@@ -29,10 +29,7 @@ import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
 import org.junit.Test;
 
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 import static org.easymock.EasyMock.*;
 import static org.junit.Assert.*;
@@ -51,7 +48,7 @@ public class UpdateHandlerTest {
     Resource resource1 = createMock(Resource.class);
     Resource resource2 = createMock(Resource.class);
 
-    Map<PropertyId, String> resourceProperties = new HashMap<PropertyId, String>();
+    Set<Map<PropertyId, Object>> setResourceProperties = new HashSet<Map<PropertyId, Object>>();
 
     Set<Resource> setResources = new HashSet<Resource>();
     setResources.add(resource1);
@@ -59,10 +56,9 @@ public class UpdateHandlerTest {
 
     // expectations
     expect(request.getResourceDefinition()).andReturn(resource);
-    expect(request.getHttpBodyProperties()).andReturn(resourceProperties);
-    resource.setProperties(resourceProperties);
+    expect(request.getHttpBodyProperties()).andReturn(setResourceProperties);
     expect(request.getPersistenceManager()).andReturn(pm);
-    expect(pm.persist(resource)).andReturn(status);
+    expect(pm.persist(resource, setResourceProperties)).andReturn(status);
     expect(status.getStatus()).andReturn(RequestStatus.Status.Complete);
     expect(status.getAssociatedResources()).andReturn(setResources);
     expect(resource1.getType()).andReturn(Resource.Type.Cluster).anyTimes();
@@ -103,7 +99,7 @@ public class UpdateHandlerTest {
     Resource resource2 = createMock(Resource.class);
     Resource requestResource = createMock(Resource.class);
 
-    Map<PropertyId, String> resourceProperties = new HashMap<PropertyId, String>();
+    Set<Map<PropertyId, Object>> setResourceProperties = new HashSet<Map<PropertyId, Object>>();
 
     Set<Resource> setResources = new HashSet<Resource>();
     setResources.add(resource1);
@@ -111,10 +107,9 @@ public class UpdateHandlerTest {
 
     // expectations
     expect(request.getResourceDefinition()).andReturn(resource);
-    expect(request.getHttpBodyProperties()).andReturn(resourceProperties);
-    resource.setProperties(resourceProperties);
+    expect(request.getHttpBodyProperties()).andReturn(setResourceProperties);
     expect(request.getPersistenceManager()).andReturn(pm);
-    expect(pm.persist(resource)).andReturn(status);
+    expect(pm.persist(resource, setResourceProperties)).andReturn(status);
     expect(status.getStatus()).andReturn(RequestStatus.Status.Accepted);
     expect(status.getAssociatedResources()).andReturn(setResources);
     expect(resource1.getType()).andReturn(Resource.Type.Cluster).anyTimes();

+ 1 - 6
ambari-server/src/test/java/org/apache/ambari/server/api/query/QueryImplTest.java

@@ -71,13 +71,8 @@ public class QueryImplTest {
         eq(predicate))).andReturn(listResources);
 
     expect(result.getResultTree()).andReturn(tree);
-    expect(componentResource.getPropertyValue(componentPropertyId)).andReturn("componentName");
-    hostComponentResourceDef.setParentId(Resource.Type.Component, "componentName");
 
-    //todo: debug output
-    expect(hostComponentResourceDef.getId()).andReturn(null).anyTimes();
-    expect(hostComponentResourceDef.getResourceIds()).andReturn(mapResourceIds).anyTimes();
-    // end todo
+    hostComponentResourceDef.setParentIds(mapResourceIds);
 
     expect(hostComponentResourceDef.getQuery()).andReturn(hostComponentQuery);
     expect(hostComponentQuery.execute()).andReturn(hostComponentQueryResult);

+ 1 - 1
ambari-server/src/test/java/org/apache/ambari/server/api/services/ConfigurationServiceTest.java

@@ -96,7 +96,7 @@ public class ConfigurationServiceTest {
     
 
     @Override
-    ResourceDefinition createResourceDefinition(String type, String tag, String clusterName) {
+    ResourceDefinition createResourceDefinition(String type, String clusterName) {
       assertEquals(m_clusterId, clusterName);
       return m_resourceDef;
     }

+ 59 - 13
ambari-server/src/test/java/org/apache/ambari/server/api/services/CreatePersistenceManagerTest.java

@@ -25,10 +25,13 @@ import org.apache.ambari.server.controller.spi.ClusterController;
 import org.apache.ambari.server.controller.spi.PropertyId;
 import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.controller.spi.Schema;
+import org.apache.ambari.server.controller.spi.Request;
 import org.junit.Test;
 
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 import static org.junit.Assert.*;
 
@@ -45,33 +48,76 @@ public class CreatePersistenceManagerTest {
     Schema schema = createMock(Schema.class);
     PropertyId clusterId = createStrictMock(PropertyId.class);
     PropertyId serviceId = createStrictMock(PropertyId.class);
-    org.apache.ambari.server.controller.spi.Request serverRequest =
-        createStrictMock(org.apache.ambari.server.controller.spi.Request.class);
+    Request serverRequest = createStrictMock(Request.class);
 
     Map<Resource.Type, String> mapResourceIds = new HashMap<Resource.Type, String>();
     mapResourceIds.put(Resource.Type.Cluster, "clusterId");
     mapResourceIds.put(Resource.Type.Service, "serviceId");
 
+    Set<Map<PropertyId, Object>> setProperties = new HashSet<Map<PropertyId, Object>>();
     Map<PropertyId, Object> mapProperties = new HashMap<PropertyId, Object>();
     mapProperties.put(clusterId, "clusterId");
     mapProperties.put(serviceId, "serviceId");
     mapProperties.put(PropertyHelper.getPropertyId("bar", "foo"), "value");
+    setProperties.add(mapProperties);
 
     //expectations
     expect(resource.getResourceIds()).andReturn(mapResourceIds);
     expect(resource.getType()).andReturn(Resource.Type.Component);
     expect(controller.getSchema(Resource.Type.Component)).andReturn(schema);
     expect(schema.getKeyPropertyId(Resource.Type.Cluster)).andReturn(clusterId);
-    resource.setProperty(clusterId, "clusterId");
     expect(schema.getKeyPropertyId(Resource.Type.Service)).andReturn(serviceId);
-    resource.setProperty(serviceId, "serviceId");
-    expect(resource.getProperties()).andReturn(mapProperties);
 
     expect(controller.createResources(Resource.Type.Component, serverRequest)).andReturn(new RequestStatusImpl(null));
 
     replay(resource, controller, schema, clusterId, serviceId, serverRequest);
 
-    new TestCreatePersistenceManager(controller, mapProperties, serverRequest).persist(resource);
+    new TestCreatePersistenceManager(controller, setProperties, serverRequest).persist(resource, setProperties);
+
+    verify(resource, controller, schema, clusterId, serviceId, serverRequest);
+
+  }
+
+  @Test
+  public void testPersist__MultipleResources() throws Exception {
+    ResourceDefinition resource = createMock(ResourceDefinition.class);
+    ClusterController controller = createMock(ClusterController.class);
+    Schema schema = createMock(Schema.class);
+    PropertyId clusterId = createStrictMock(PropertyId.class);
+    PropertyId serviceId = createStrictMock(PropertyId.class);
+    Request serverRequest = createStrictMock(Request.class);
+
+    Map<Resource.Type, String> mapResourceIds = new HashMap<Resource.Type, String>();
+    mapResourceIds.put(Resource.Type.Cluster, "clusterId");
+    mapResourceIds.put(Resource.Type.Service, "serviceId");
+
+    Set<Map<PropertyId, Object>> setProperties = new HashSet<Map<PropertyId, Object>>();
+
+    Map<PropertyId, Object> mapResourceProps1 = new HashMap<PropertyId, Object>();
+    mapResourceProps1.put(clusterId, "clusterId");
+    mapResourceProps1.put(serviceId, "serviceId");
+    mapResourceProps1.put(PropertyHelper.getPropertyId("bar", "foo"), "value");
+
+    Map<PropertyId, Object> mapResourceProps2 = new HashMap<PropertyId, Object>();
+    mapResourceProps2.put(clusterId, "clusterId");
+    mapResourceProps2.put(serviceId, "serviceId2");
+    mapResourceProps2.put(PropertyHelper.getPropertyId("bar2", "foo"), "value2");
+
+    setProperties.add(mapResourceProps1);
+    setProperties.add(mapResourceProps2);
+
+    //expectations
+    expect(resource.getResourceIds()).andReturn(mapResourceIds);
+    expect(resource.getType()).andReturn(Resource.Type.Component);
+    expect(controller.getSchema(Resource.Type.Component)).andReturn(schema);
+    expect(schema.getKeyPropertyId(Resource.Type.Cluster)).andReturn(clusterId).times(2);
+    expect(schema.getKeyPropertyId(Resource.Type.Service)).andReturn(serviceId).times(2);
+
+    expect(controller.createResources(Resource.Type.Component, serverRequest)).andReturn(new RequestStatusImpl(null));
+
+    replay(resource, controller, schema, clusterId, serviceId, serverRequest);
+
+    new TestCreatePersistenceManager(controller, setProperties, serverRequest).persist(resource, setProperties);
 
     verify(resource, controller, schema, clusterId, serviceId, serverRequest);
 
@@ -80,14 +126,14 @@ public class CreatePersistenceManagerTest {
   private class TestCreatePersistenceManager extends CreatePersistenceManager {
 
     private ClusterController m_controller;
-    private org.apache.ambari.server.controller.spi.Request m_request;
-    private Map<PropertyId, Object> m_mapProperties;
+    private Request m_request;
+    private Set<Map<PropertyId, Object>> m_setProperties;
 
     private TestCreatePersistenceManager(ClusterController controller,
-                                         Map<PropertyId, Object> mapProperties,
-                                         org.apache.ambari.server.controller.spi.Request controllerRequest) {
+                                         Set<Map<PropertyId, Object>> setProperties,
+                                         Request controllerRequest) {
       m_controller = controller;
-      m_mapProperties = mapProperties;
+      m_setProperties = setProperties;
       m_request = controllerRequest;
     }
 
@@ -97,8 +143,8 @@ public class CreatePersistenceManagerTest {
     }
 
     @Override
-    protected org.apache.ambari.server.controller.spi.Request createControllerRequest(Map<PropertyId, Object> properties) {
-      assertEquals(m_mapProperties, properties);
+    protected Request createControllerRequest(Set<Map<PropertyId, Object>> setProperties) {
+      assertEquals(m_setProperties, setProperties);
       return m_request;
     }
   }

+ 1 - 1
ambari-server/src/test/java/org/apache/ambari/server/api/services/DeletePersistenceManagerTest.java

@@ -49,7 +49,7 @@ public class DeletePersistenceManagerTest {
 
     replay(resource, controller, schema, query, predicate);
 
-    new TestDeletePersistenceManager(controller).persist(resource);
+    new TestDeletePersistenceManager(controller).persist(resource, null);
 
     verify(resource, controller, schema, query, predicate);
   }

+ 57 - 3
ambari-server/src/test/java/org/apache/ambari/server/api/services/HostServiceTest.java

@@ -134,7 +134,7 @@ public class HostServiceTest {
     String hostName = "hostName";
 
     // expectations
-    expect(requestFactory.createRequest(eq(httpHeaders), eq("body"), eq(uriInfo), eq(Request.Type.POST),
+    expect(requestFactory.createRequest(eq(httpHeaders), isNull(String.class), eq(uriInfo), eq(Request.Type.POST),
         eq(resourceDef))).andReturn(request);
 
     expect(requestHandler.handleRequest(request)).andReturn(result);
@@ -148,7 +148,61 @@ public class HostServiceTest {
 
     //test
     HostService hostService = new TestHostService(resourceDef, clusterName, hostName, requestFactory, responseFactory, requestHandler);
-    assertSame(response, hostService.createHost("body", httpHeaders, uriInfo, hostName));
+    assertSame(response, hostService.createHost(null, httpHeaders, uriInfo, hostName));
+
+    verify(resourceDef, resultSerializer, requestFactory, responseFactory, request, requestHandler,
+        result, response, httpHeaders, uriInfo);
+  }
+
+  @Test
+  public void testCreateHosts() {
+    ResourceDefinition resourceDef = createStrictMock(ResourceDefinition.class);
+    ResultSerializer resultSerializer = createStrictMock(ResultSerializer.class);
+    Object serializedResult = new Object();
+    RequestFactory requestFactory = createStrictMock(RequestFactory.class);
+    ResponseFactory responseFactory = createStrictMock(ResponseFactory.class);
+    Request request = createNiceMock(Request.class);
+    RequestHandler requestHandler = createStrictMock(RequestHandler.class);
+    Result result = createStrictMock(Result.class);
+    Response response = createStrictMock(Response.class);
+
+    HttpHeaders httpHeaders = createNiceMock(HttpHeaders.class);
+    UriInfo uriInfo = createNiceMock(UriInfo.class);
+
+    String clusterName = "clusterName";
+    String body = "[ " +
+        "{\"Hosts\" : {" +
+        "            \"cluster_name\" : \"mycluster\"," +
+        "            \"host_name\" : \"host1\"" +
+        "          }" +
+        "}," +
+        "{\"Hosts\" : {" +
+        "            \"cluster_name\" : \"mycluster\"," +
+        "            \"host_name\" : \"host2\"" +
+        "          }" +
+        "}," +
+        "{\"Hosts\" : {" +
+        "            \"cluster_name\" : \"mycluster\"," +
+        "            \"host_name\" : \"host3\"" +
+        "          }" +
+        "}]";
+
+    // expectations
+    expect(requestFactory.createRequest(eq(httpHeaders), eq(body), eq(uriInfo), eq(Request.Type.POST),
+        eq(resourceDef))).andReturn(request);
+
+    expect(requestHandler.handleRequest(request)).andReturn(result);
+    expect(request.getResultSerializer()).andReturn(resultSerializer);
+    expect(resultSerializer.serialize(result, uriInfo)).andReturn(serializedResult);
+    expect(result.isSynchronous()).andReturn(false).atLeastOnce();
+    expect(responseFactory.createResponse(Request.Type.POST, serializedResult, false)).andReturn(response);
+
+    replay(resourceDef, resultSerializer, requestFactory, responseFactory, request, requestHandler,
+        result, response, httpHeaders, uriInfo);
+
+    //test
+    HostService hostService = new TestHostService(resourceDef, clusterName, null, requestFactory, responseFactory, requestHandler);
+    assertSame(response, hostService.createHosts(body, httpHeaders, uriInfo));
 
     verify(resourceDef, resultSerializer, requestFactory, responseFactory, request, requestHandler,
         result, response, httpHeaders, uriInfo);
@@ -291,7 +345,7 @@ public class HostServiceTest {
     }
 
     @Override
-    ResourceDefinition createResourceDefinition(String hostName, String clusterName) {
+    ResourceDefinition createResourceDefinition(String hostName, String clusterName, UriInfo ui) {
       assertEquals(m_clusterId, clusterName);
       assertEquals(m_hostId, hostName);
       return m_resourceDef;

+ 15 - 12
ambari-server/src/test/java/org/apache/ambari/server/api/services/UpdatePersistenceManagerTest.java

@@ -23,11 +23,14 @@ import org.apache.ambari.server.api.resources.ResourceDefinition;
 import org.apache.ambari.server.controller.internal.RequestStatusImpl;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
 import org.apache.ambari.server.api.query.Query;
+import org.apache.ambari.server.controller.spi.Request;
 import org.apache.ambari.server.controller.spi.*;
 import org.junit.Test;
 
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 import static org.junit.Assert.*;
 
@@ -42,18 +45,17 @@ public class UpdatePersistenceManagerTest {
     ResourceDefinition resource = createMock(ResourceDefinition.class);
     ClusterController controller = createMock(ClusterController.class);
     Schema schema = createMock(Schema.class);
-    org.apache.ambari.server.controller.spi.Request serverRequest =
-        createStrictMock(org.apache.ambari.server.controller.spi.Request.class);
+    Request serverRequest = createStrictMock(Request.class);
     Query query = createMock(Query.class);
     Predicate predicate = createMock(Predicate.class);
 
-
+    Set<Map<PropertyId, Object>> setProperties = new HashSet<Map<PropertyId, Object>>();
     Map<PropertyId, Object> mapProperties = new HashMap<PropertyId, Object>();
     mapProperties.put(PropertyHelper.getPropertyId("bar", "foo"), "value");
+    setProperties.add(mapProperties);
 
     //expectations
     expect(resource.getType()).andReturn(Resource.Type.Component);
-    expect(resource.getProperties()).andReturn(mapProperties);
     expect(resource.getQuery()).andReturn(query);
     expect(query.getInternalPredicate()).andReturn(predicate);
 
@@ -61,7 +63,7 @@ public class UpdatePersistenceManagerTest {
 
     replay(resource, controller, schema, serverRequest, query, predicate);
 
-    new TestUpdatePersistenceManager(controller, mapProperties, serverRequest).persist(resource);
+    new TestUpdatePersistenceManager(controller, setProperties, serverRequest).persist(resource, setProperties);
 
     verify(resource, controller, schema, serverRequest, query, predicate);
   }
@@ -69,14 +71,14 @@ public class UpdatePersistenceManagerTest {
   private class TestUpdatePersistenceManager extends UpdatePersistenceManager {
 
     private ClusterController m_controller;
-    private org.apache.ambari.server.controller.spi.Request m_request;
-    private Map<PropertyId, Object> m_mapProperties;
+    private Request m_request;
+    private Set<Map<PropertyId, Object>> m_setProperties;
 
     private TestUpdatePersistenceManager(ClusterController controller,
-                                         Map<PropertyId, Object> mapProperties,
-                                         org.apache.ambari.server.controller.spi.Request controllerRequest) {
+                                         Set<Map<PropertyId, Object>> setProperties,
+                                         Request controllerRequest) {
       m_controller = controller;
-      m_mapProperties = mapProperties;
+      m_setProperties = setProperties;
       m_request = controllerRequest;
     }
 
@@ -86,8 +88,9 @@ public class UpdatePersistenceManagerTest {
     }
 
     @Override
-    protected org.apache.ambari.server.controller.spi.Request createControllerRequest(Map<PropertyId, Object> properties) {
-      assertEquals(m_mapProperties, properties);
+    protected Request createControllerRequest(Set<Map<PropertyId, Object>> setProperties) {
+      assertEquals(1, setProperties.size());
+      assertEquals(m_setProperties, setProperties);
       return m_request;
     }
   }

+ 66 - 8
ambari-server/src/test/java/org/apache/ambari/server/api/services/parsers/JsonPropertyParserTest.java

@@ -23,9 +23,11 @@ import org.apache.ambari.server.controller.spi.PropertyId;
 import org.junit.Test;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Unit tests for JsonPropertyParser.
@@ -45,10 +47,28 @@ public class JsonPropertyParserTest {
       "\"OuterCategory\" : { \"propName\" : 100, \"nested1\" : { \"nested2\" : { \"innerPropName\" : \"innerPropValue\" } } } }";
 
 
+  String clustersJson = "[ {" +
+      "\"Clusters\" : {\n" +
+      "    \"cluster_name\" : \"unitTestCluster1\"" +
+      "} }," +
+      "{" +
+      "\"Clusters\" : {\n" +
+      "    \"cluster_name\" : \"unitTestCluster2\"," +
+      "    \"property1\" : \"prop1Value\"" +
+      "} }," +
+      "{" +
+      "\"Clusters\" : {\n" +
+      "    \"cluster_name\" : \"unitTestCluster3\"," +
+      "    \"Category\" : { \"property2\" : \"prop2Value\"}" +
+      "} } ]";
+
+
   @Test
   public void testParse() throws Exception {
     RequestBodyParser parser = new JsonPropertyParser();
-    Map<PropertyId, String> mapProps = parser.parse(serviceJson);
+    Set<Map<PropertyId, Object>> setProps = parser.parse(serviceJson);
+
+    assertEquals(1, setProps.size());
 
     Map<PropertyId, Object> mapExpected = new HashMap<PropertyId, Object>();
     mapExpected.put(PropertyHelper.getPropertyId("service_name", "Services"), "HDFS");
@@ -60,23 +80,61 @@ public class JsonPropertyParserTest {
     mapExpected.put(PropertyHelper.getPropertyId("propName", "OuterCategory"), "100");
     mapExpected.put(PropertyHelper.getPropertyId("innerPropName", "OuterCategory.nested1.nested2"), "innerPropValue");
 
-    assertEquals(mapExpected, mapProps);
+    assertEquals(mapExpected, setProps.iterator().next());
   }
 
   @Test
   public void testParse_NullBody() {
     RequestBodyParser parser = new JsonPropertyParser();
-    Map<PropertyId, String> mapProps = parser.parse(null);
-    assertNotNull(mapProps);
-    assertEquals(0, mapProps.size());
+    Set<Map<PropertyId, Object>> setProps = parser.parse(null);
+    assertNotNull(setProps);
+    assertEquals(0, setProps.size());
   }
 
   @Test
   public void testParse_EmptyBody() {
     RequestBodyParser parser = new JsonPropertyParser();
-    Map<PropertyId, String> mapProps = parser.parse("");
-    assertNotNull(mapProps);
-    assertEquals(0, mapProps.size());
+    Set<Map<PropertyId, Object>> setProps = parser.parse("");
+    assertNotNull(setProps);
+    assertEquals(0, setProps.size());
+  }
+
+  @Test
+  public void testParse_Array() {
+    RequestBodyParser parser = new JsonPropertyParser();
+    Set<Map<PropertyId, Object>> setProps = parser.parse(clustersJson);
+    assertEquals(3, setProps.size());
+
+    boolean cluster1Matches = false;
+    boolean cluster2Matches = false;
+    boolean cluster3Matches = false;
+
+    Map<PropertyId, String> mapCluster1 = new HashMap<PropertyId, String>();
+    mapCluster1.put(PropertyHelper.getPropertyId("cluster_name", "Clusters"), "unitTestCluster1");
+
+    Map<PropertyId, String> mapCluster2 = new HashMap<PropertyId, String>();
+    mapCluster2.put(PropertyHelper.getPropertyId("cluster_name", "Clusters"), "unitTestCluster2");
+    mapCluster2.put(PropertyHelper.getPropertyId("property1", "Clusters"), "prop1Value");
+
+
+    Map<PropertyId, String> mapCluster3 = new HashMap<PropertyId, String>();
+    mapCluster3.put(PropertyHelper.getPropertyId("cluster_name", "Clusters"), "unitTestCluster3");
+    mapCluster3.put(PropertyHelper.getPropertyId("property2", "Clusters.Category"), "prop2Value");
+
+
+    for (Map<PropertyId, Object> mapProps : setProps) {
+      if (mapProps.equals(mapCluster1)) {
+        cluster1Matches = true;
+      } else if (mapProps.equals(mapCluster2)) {
+        cluster2Matches = true;
+      } else if (mapProps.equals(mapCluster3)) {
+        cluster3Matches = true;
+      }
+    }
+
+    assertTrue(cluster1Matches);
+    assertTrue(cluster2Matches);
+    assertTrue(cluster3Matches);
   }
 }