You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by di...@apache.org on 2016/01/11 20:15:39 UTC

[03/13] ambari git commit: AMBARI-12885: Dynamic stack extensions - install and upgrade support for custom services (tthorpe via dili)

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
index 6c56a85..ebf82eb 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
@@ -24,6 +24,24 @@ CREATE TABLE stack(
   PRIMARY KEY (stack_id)
 );
 
+CREATE TABLE extension(
+  extension_id BIGINT NOT NULL,
+  extension_name VARCHAR(255) NOT NULL,
+  extension_version VARCHAR(255) NOT NULL,
+  PRIMARY KEY (extension_id),
+  CONSTRAINT unq_extension UNIQUE(extension_name, extension_version)
+);
+
+CREATE TABLE extensionlink(
+  link_id BIGINT NOT NULL,
+  stack_id BIGINT NOT NULL,
+  extension_id BIGINT NOT NULL,
+  PRIMARY KEY (link_id),
+  FOREIGN KEY (stack_id) REFERENCES stack(stack_id),
+  FOREIGN KEY (extension_id) REFERENCES extension(extension_id),
+  CONSTRAINT unq_extension_link UNIQUE(stack_id, extension_id)
+);
+
 CREATE TABLE clusters (
   cluster_id BIGINT NOT NULL,
   resource_id BIGINT NOT NULL,
@@ -104,10 +122,21 @@ CREATE TABLE cluster_version (
   user_name VARCHAR(32),
   PRIMARY KEY (id));
 
+CREATE TABLE cluster_extension_version (
+  id BIGINT NOT NULL,
+  repo_version_id BIGINT NOT NULL,
+  cluster_id BIGINT NOT NULL,
+  state VARCHAR(32) NOT NULL,
+  start_time BIGINT NOT NULL,
+  end_time BIGINT,
+  user_name VARCHAR(32),
+  PRIMARY KEY (id));
+
 CREATE TABLE hostcomponentdesiredstate (
   cluster_id BIGINT NOT NULL,
   component_name VARCHAR(255) NOT NULL,
   desired_stack_id BIGINT NOT NULL,
+  desired_extension_id BIGINT,
   desired_state VARCHAR(255) NOT NULL,
   host_id BIGINT NOT NULL,
   service_name VARCHAR(255) NOT NULL,
@@ -124,6 +153,7 @@ CREATE TABLE hostcomponentstate (
   component_name VARCHAR(255) NOT NULL,
   version VARCHAR(32) NOT NULL DEFAULT 'UNKNOWN',
   current_stack_id BIGINT NOT NULL,
+  current_extension_id BIGINT,
   current_state VARCHAR(255) NOT NULL,
   host_id BIGINT NOT NULL,
   service_name VARCHAR(255) NOT NULL,
@@ -170,6 +200,13 @@ CREATE TABLE host_version (
   state VARCHAR(32) NOT NULL,
   PRIMARY KEY (id));
 
+CREATE TABLE host_extension_version (
+  id BIGINT NOT NULL,
+  repo_version_id BIGINT NOT NULL,
+  host_id BIGINT NOT NULL,
+  state VARCHAR(32) NOT NULL,
+  PRIMARY KEY (id));
+
 CREATE TABLE servicecomponentdesiredstate (
   component_name VARCHAR(255) NOT NULL,
   cluster_id BIGINT NOT NULL,
@@ -550,7 +587,19 @@ CREATE TABLE repo_version (
   version VARCHAR(255) NOT NULL,
   display_name VARCHAR(128) NOT NULL,
   repositories TEXT NOT NULL,
-  PRIMARY KEY(repo_version_id)
+  PRIMARY KEY(repo_version_id),
+  FOREIGN KEY (stack_id) REFERENCES stack(stack_id)
+);
+
+CREATE TABLE extension_repo_version (
+  repo_version_id BIGINT NOT NULL,
+  extension_id BIGINT NOT NULL,
+  version VARCHAR(255) NOT NULL,
+  display_name VARCHAR(128) NOT NULL,
+  upgrade_package VARCHAR(255) NOT NULL,
+  repositories TEXT NOT NULL,
+  PRIMARY KEY repo_version_id),
+  FOREIGN KEY (extension_id) REFERENCES extension(extension_id)
 );
 
 CREATE TABLE widget (
@@ -676,6 +725,8 @@ ALTER TABLE clusterconfigmapping ADD CONSTRAINT clusterconfigmappingcluster_id F
 ALTER TABLE clusterstate ADD CONSTRAINT FK_clusterstate_cluster_id FOREIGN KEY (cluster_id) REFERENCES clusters (cluster_id);
 ALTER TABLE cluster_version ADD CONSTRAINT FK_cluster_version_cluster_id FOREIGN KEY (cluster_id) REFERENCES clusters (cluster_id);
 ALTER TABLE cluster_version ADD CONSTRAINT FK_cluster_version_repovers_id FOREIGN KEY (repo_version_id) REFERENCES repo_version (repo_version_id);
+ALTER TABLE cluster_extension_version ADD CONSTRAINT FK_cluster_extension_version_cluster_id FOREIGN KEY (cluster_id) REFERENCES clusters (cluster_id);
+ALTER TABLE cluster_extension_version ADD CONSTRAINT FK_cluster_extension_version_repovers_id FOREIGN KEY (repo_version_id) REFERENCES extension_repo_version (repo_version_id);
 ALTER TABLE hostcomponentdesiredstate ADD CONSTRAINT FK_hcdesiredstate_host_id FOREIGN KEY (host_id) REFERENCES hosts (host_id);
 ALTER TABLE hostcomponentdesiredstate ADD CONSTRAINT hstcmpnntdesiredstatecmpnntnme FOREIGN KEY (component_name, cluster_id, service_name) REFERENCES servicecomponentdesiredstate (component_name, cluster_id, service_name);
 ALTER TABLE hostcomponentstate ADD CONSTRAINT hstcomponentstatecomponentname FOREIGN KEY (component_name, cluster_id, service_name) REFERENCES servicecomponentdesiredstate (component_name, cluster_id, service_name);
@@ -683,6 +734,8 @@ ALTER TABLE hostcomponentstate ADD CONSTRAINT FK_hostcomponentstate_host_id FORE
 ALTER TABLE hoststate ADD CONSTRAINT FK_hoststate_host_id FOREIGN KEY (host_id) REFERENCES hosts (host_id);
 ALTER TABLE host_version ADD CONSTRAINT FK_host_version_host_id FOREIGN KEY (host_id) REFERENCES hosts (host_id);
 ALTER TABLE host_version ADD CONSTRAINT FK_host_version_repovers_id FOREIGN KEY (repo_version_id) REFERENCES repo_version (repo_version_id);
+ALTER TABLE host_extension_version ADD CONSTRAINT FK_host_extension_version_host_id FOREIGN KEY (host_id) REFERENCES hosts (host_id);
+ALTER TABLE host_extension_version ADD CONSTRAINT FK_host_extension_version_repovers_id FOREIGN KEY (repo_version_id) REFERENCES extension_repo_version (repo_version_id);
 ALTER TABLE servicecomponentdesiredstate ADD CONSTRAINT srvccmponentdesiredstatesrvcnm FOREIGN KEY (service_name, cluster_id) REFERENCES clusterservices (service_name, cluster_id);
 ALTER TABLE servicedesiredstate ADD CONSTRAINT servicedesiredstateservicename FOREIGN KEY (service_name, cluster_id) REFERENCES clusterservices (service_name, cluster_id);
 ALTER TABLE execution_command ADD CONSTRAINT FK_execution_command_task_id FOREIGN KEY (task_id) REFERENCES host_role_command (task_id);
@@ -746,11 +799,15 @@ ALTER TABLE clusterconfig ADD CONSTRAINT FK_clusterconfig_stack_id FOREIGN KEY (
 ALTER TABLE serviceconfig ADD CONSTRAINT FK_serviceconfig_stack_id FOREIGN KEY (stack_id) REFERENCES stack(stack_id);
 ALTER TABLE clusterstate ADD CONSTRAINT FK_cs_current_stack_id FOREIGN KEY (current_stack_id) REFERENCES stack(stack_id);
 ALTER TABLE hostcomponentdesiredstate ADD CONSTRAINT FK_hcds_desired_stack_id FOREIGN KEY (desired_stack_id) REFERENCES stack(stack_id);
+ALTER TABLE hostcomponentdesiredstate ADD CONSTRAINT FK_hcds_desired_extension_id FOREIGN KEY (desired_extension_id) REFERENCES extension(extension_id);
 ALTER TABLE hostcomponentstate ADD CONSTRAINT FK_hcs_current_stack_id FOREIGN KEY (current_stack_id) REFERENCES stack(stack_id);
+ALTER TABLE hostcomponentstate ADD CONSTRAINT FK_hcs_current_extension_id FOREIGN KEY (current_extension_id) REFERENCES extension(extension_id);
 ALTER TABLE servicecomponentdesiredstate ADD CONSTRAINT FK_scds_desired_stack_id FOREIGN KEY (desired_stack_id) REFERENCES stack(stack_id);
 ALTER TABLE servicedesiredstate ADD CONSTRAINT FK_sds_desired_stack_id FOREIGN KEY (desired_stack_id) REFERENCES stack(stack_id);
 ALTER TABLE blueprint ADD CONSTRAINT FK_blueprint_stack_id FOREIGN KEY (stack_id) REFERENCES stack(stack_id);
 ALTER TABLE repo_version ADD CONSTRAINT FK_repoversion_stack_id FOREIGN KEY (stack_id) REFERENCES stack(stack_id);
+ALTER TABLE extensionlink ADD CONSTRAINT FK_extensionlink_stack_id FOREIGN KEY (stack_id) REFERENCES stack(stack_id);
+ALTER TABLE extensionlink ADD CONSTRAINT FK_extensionlink_extension_id FOREIGN KEY (extension_id) REFERENCES extension(extension_id);
 
 -- Kerberos
 CREATE TABLE kerberos_principal (
@@ -984,10 +1041,16 @@ INSERT INTO ambari_sequences (sequence_name, sequence_value)
   union all
   select 'repo_version_id_seq', 0
   union all
+  select 'extension_repo_version_id_seq', 0
+  union all
   select 'cluster_version_id_seq', 0
   union all
   select 'host_version_id_seq', 0
   union all
+  select 'cluster_extension_version_id_seq', 0
+  union all
+  select 'host_extension_version_id_seq', 0
+  union all
   select 'service_config_id_seq', 1
   union all
   select 'upgrade_id_seq', 0
@@ -1002,6 +1065,10 @@ INSERT INTO ambari_sequences (sequence_name, sequence_value)
   union all
   select 'stack_id_seq', 0
   union all
+  select 'extension_id_seq', 0
+  union all
+  select 'link_id_seq', 0
+  union all
   select 'topology_host_info_id_seq', 0
   union all
   select 'topology_host_request_id_seq', 0

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/resources/META-INF/persistence.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/META-INF/persistence.xml b/ambari-server/src/main/resources/META-INF/persistence.xml
index 3979bc0..1f85931 100644
--- a/ambari-server/src/main/resources/META-INF/persistence.xml
+++ b/ambari-server/src/main/resources/META-INF/persistence.xml
@@ -25,6 +25,7 @@
     <class>org.apache.ambari.server.orm.entities.ClusterConfigEntity</class>
     <class>org.apache.ambari.server.orm.entities.ClusterConfigMappingEntity</class>
     <class>org.apache.ambari.server.orm.entities.ClusterEntity</class>
+    <class>org.apache.ambari.server.orm.entities.ClusterExtensionVersionEntity</class>
     <class>org.apache.ambari.server.orm.entities.ClusterServiceEntity</class>
     <class>org.apache.ambari.server.orm.entities.ClusterStateEntity</class>
     <class>org.apache.ambari.server.orm.entities.ClusterVersionEntity</class>
@@ -32,11 +33,15 @@
     <class>org.apache.ambari.server.orm.entities.ConfigGroupEntity</class>
     <class>org.apache.ambari.server.orm.entities.ConfigGroupHostMappingEntity</class>
     <class>org.apache.ambari.server.orm.entities.ExecutionCommandEntity</class>
+    <class>org.apache.ambari.server.orm.entities.ExtensionEntity</class>
+    <class>org.apache.ambari.server.orm.entities.ExtensionLinkEntity</class>
+    <class>org.apache.ambari.server.orm.entities.ExtensionRepositoryVersionEntity</class>
     <class>org.apache.ambari.server.orm.entities.GroupEntity</class>
     <class>org.apache.ambari.server.orm.entities.HostComponentDesiredStateEntity</class>
     <class>org.apache.ambari.server.orm.entities.HostComponentStateEntity</class>
     <class>org.apache.ambari.server.orm.entities.HostConfigMappingEntity</class>
     <class>org.apache.ambari.server.orm.entities.HostEntity</class>
+    <class>org.apache.ambari.server.orm.entities.HostExtensionVersionEntity</class>
     <class>org.apache.ambari.server.orm.entities.HostGroupComponentEntity</class>
     <class>org.apache.ambari.server.orm.entities.HostGroupConfigEntity</class>
     <class>org.apache.ambari.server.orm.entities.HostGroupEntity</class>

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/metainfo.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/metainfo.xml b/ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/metainfo.xml
index 66d3f52..4c33dfd 100644
--- a/ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/metainfo.xml
+++ b/ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/metainfo.xml
@@ -31,6 +31,7 @@
           <category>MASTER</category>
           <cardinality>1</cardinality>
           <versionAdvertised>false</versionAdvertised>
+          <setupHA>true</setupHA>
           <timelineAppid>AMS-HBASE</timelineAppid>
           <dependencies>
             <dependency>

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/resources/common-services/HBASE/0.96.0.2.0/metainfo.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/common-services/HBASE/0.96.0.2.0/metainfo.xml b/ambari-server/src/main/resources/common-services/HBASE/0.96.0.2.0/metainfo.xml
index 78aca3d..2d14e97 100644
--- a/ambari-server/src/main/resources/common-services/HBASE/0.96.0.2.0/metainfo.xml
+++ b/ambari-server/src/main/resources/common-services/HBASE/0.96.0.2.0/metainfo.xml
@@ -32,6 +32,7 @@
           <category>MASTER</category>
           <cardinality>1+</cardinality>
           <versionAdvertised>true</versionAdvertised>
+          <setupHA>true</setupHA>
           <timelineAppid>HBASE</timelineAppid>
           <dependencies>
             <dependency>

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/resources/common-services/HDFS/2.1.0.2.0/metainfo.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/common-services/HDFS/2.1.0.2.0/metainfo.xml b/ambari-server/src/main/resources/common-services/HDFS/2.1.0.2.0/metainfo.xml
index b678e51..b1f6836 100644
--- a/ambari-server/src/main/resources/common-services/HDFS/2.1.0.2.0/metainfo.xml
+++ b/ambari-server/src/main/resources/common-services/HDFS/2.1.0.2.0/metainfo.xml
@@ -147,6 +147,7 @@
           <!-- TODO: cardinality is conditional on HA topology -->
           <cardinality>0+</cardinality>
           <versionAdvertised>false</versionAdvertised>
+          <notValuable>true</notValuable>
           <commandScript>
             <script>scripts/zkfc_slave.py</script>
             <scriptType>PYTHON</scriptType>

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/resources/common-services/ZOOKEEPER/3.4.5.2.0/metainfo.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/common-services/ZOOKEEPER/3.4.5.2.0/metainfo.xml b/ambari-server/src/main/resources/common-services/ZOOKEEPER/3.4.5.2.0/metainfo.xml
index 4d14725..7486b98 100644
--- a/ambari-server/src/main/resources/common-services/ZOOKEEPER/3.4.5.2.0/metainfo.xml
+++ b/ambari-server/src/main/resources/common-services/ZOOKEEPER/3.4.5.2.0/metainfo.xml
@@ -31,6 +31,8 @@
           <category>MASTER</category>
           <cardinality>1+</cardinality>
           <versionAdvertised>true</versionAdvertised>
+          <setupHA>true</setupHA>
+          <idealMinimum>3</idealMinimum>
           <commandScript>
             <script>scripts/zookeeper_server.py</script>
             <scriptType>PYTHON</scriptType>

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/resources/key_properties.json
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/key_properties.json b/ambari-server/src/main/resources/key_properties.json
index 46a6cf9..8665907 100644
--- a/ambari-server/src/main/resources/key_properties.json
+++ b/ambari-server/src/main/resources/key_properties.json
@@ -79,13 +79,58 @@
     "StackService": "StackServiceComponents/service_name",
     "StackServiceComponent": "StackServiceComponents/component_name"
   },
-    "StackServiceComponentDependency": {
+  "StackServiceComponentDependency": {
     "Stack": "Dependencies/stack_name",
     "StackVersion": "Dependencies/stack_version",
     "StackService": "Dependencies/dependent_service_name",
     "StackServiceComponent": "Dependencies/dependent_component_name",
     "StackServiceComponentDependency": "Dependencies/component_name"
   },
+  "ExtensionLink": {
+    "ExtensionLink": "ExtensionLink/link_id",
+    "Stack": "ExtensionLink/stack_name",
+    "StackVersion": "ExtensionLink/stack_version",
+    "Extension": "ExtensionLink/extension_name",
+    "ExtensionVersion": "ExtensionLink/extension_version"
+  },
+  "Extension": {
+    "Extension": "Extensions/extension_name"
+  },
+  "ExtensionVersion": {
+    "Extension": "Versions/extension_name",
+    "ExtensionVersion": "Versions/extension_version"
+  },
+  "ExtensionService": {
+    "Extension": "ExtensionServices/extension_name",
+    "ExtensionVersion": "ExtensionServices/extension_version",
+    "ExtensionService": "ExtensionServices/service_name"
+  },
+  "ExtensionConfiguration": {
+    "Extension": "ExtensionConfigurations/extension_name",
+    "ExtensionVersion": "ExtensionConfigurations/extension_version",
+    "ExtensionService": "ExtensionConfigurations/service_name",
+    "ExtensionConfiguration": "ExtensionConfigurations/property_name"
+  },
+  "ExtensionConfigurationDependency": {
+    "Extension": "ExtensionConfigurationDependency/extension_name",
+    "ExtensionVersion": "ExtensionConfigurationDependency/extension_version",
+    "ExtensionService": "ExtensionConfigurationDependency/service_name",
+    "ExtensionConfiguration": "ExtensionConfigurationDependency/property_name",
+    "ExtensionConfigurationDependency": "ExtensionConfigurationDependency/dependency_name"
+  },
+  "ExtensionServiceComponent": {
+    "Extension": "ExtensionServiceComponents/extension_name",
+    "ExtensionVersion": "ExtensionServiceComponents/extension_version",
+    "ExtensionService": "ExtensionServiceComponents/service_name",
+    "ExtensionServiceComponent": "ExtensionServiceComponents/component_name"
+  },
+    "ExtensionServiceComponentDependency": {
+    "Extension": "Dependencies/extension_name",
+    "ExtensionVersion": "Dependencies/extension_version",
+    "ExtensionService": "Dependencies/dependent_service_name",
+    "ExtensionServiceComponent": "Dependencies/dependent_component_name",
+    "ExtensionServiceComponentDependency": "Dependencies/component_name"
+  },
   "DRFeed": {
     "DRFeed": "Feed/name"
   },
@@ -148,6 +193,11 @@
     "StackVersion": "StackLevelConfigurations/stack_version",
     "StackLevelConfiguration": "StackLevelConfigurations/property_name"  
   },
+  "ExtensionLevelConfiguration": {
+    "Extension": "ExtensionLevelConfigurations/extension_name",
+    "ExtensionVersion": "ExtensionLevelConfigurations/extension_version",
+    "ExtensionLevelConfiguration": "ExtensionLevelConfigurations/property_name"
+  },
   "KerberosDescriptor": {
     "KerberosDescriptor": "KerberosDescriptors/kerberos_descriptor_name"
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/resources/properties.json
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/properties.json b/ambari-server/src/main/resources/properties.json
index 4052ad2..d3c4b57 100644
--- a/ambari-server/src/main/resources/properties.json
+++ b/ambari-server/src/main/resources/properties.json
@@ -255,6 +255,87 @@
         "auto_deploy/enabled",
         "auto_deploy/location"
     ],
+    "ExtensionLink": [
+        "ExtensionLink/link_id",
+        "ExtensionLink/stack_name",
+        "ExtensionLink/stack_version",
+        "ExtensionLink/extension_name",
+        "ExtensionLink/extension_version",
+        "_"
+    ],
+    "Extension":[
+        "Extensions/extension_name",
+        "_"
+    ],
+    "ExtensionVersion":[
+        "Versions/extension_name",
+        "Versions/extension_version",
+        "Versions/min_upgrade_version",
+        "Versions/active",
+        "Versions/valid",
+        "Versions/extension-errors",
+        "Versions/parent_extension_version",
+        "Versions/config_types",
+        "Versions/upgrade_packs",
+        "_"
+    ],
+    "ExtensionService":[
+        "ExtensionServices/extension_name",
+        "ExtensionServices/extension_version",
+        "ExtensionServices/service_name",
+        "ExtensionServices/display_name",
+        "ExtensionServices/user_name",
+        "ExtensionServices/comments",
+        "ExtensionServices/service_version",
+        "ExtensionServices/config_types",
+        "ExtensionServices/service_check_supported",
+        "ExtensionServices/custom_commands",
+        "ExtensionServices/required_services",
+        "_"
+    ],
+    "ExtensionConfiguration":[
+        "ExtensionConfigurations/extension_name",
+        "ExtensionConfigurations/extension_version",
+        "ExtensionConfigurations/service_name",
+        "ExtensionConfigurations/property_name",
+        "ExtensionConfigurations/property_value",
+        "ExtensionConfigurations/property_value_attributes",
+        "ExtensionConfigurations/property_depends_on",
+        "ExtensionConfigurations/property_description",
+        "ExtensionConfigurations/property_display_name",
+        "ExtensionConfigurations/type",
+        "ExtensionConfigurations/final",
+        "ExtensionConfigurations/adding_forbidden",
+        "ExtensionConfigurations/do_not_extend",
+        "ExtensionConfigurations/property_type",
+        "_"
+    ],
+    "ExtensionServiceComponent":[
+        "ExtensionServiceComponents/extension_name",
+        "ExtensionServiceComponents/extension_version",
+        "ExtensionServiceComponents/service_name",
+        "ExtensionServiceComponents/component_name",
+        "ExtensionServiceComponents/display_name",
+        "ExtensionServiceComponents/component_category",
+        "ExtensionServiceComponents/is_client",
+        "ExtensionServiceComponents/is_master",
+        "ExtensionServiceComponents/cardinality",
+        "ExtensionServiceComponents/custom_commands",
+        "auto_deploy/enabled",
+        "auto_deploy/location",
+        "_"
+    ],
+    "ExtensionServiceComponentDependency" :[
+        "Dependencies/extension_name",
+        "Dependencies/extension_version",
+        "Dependencies/dependent_service_name",
+        "Dependencies/dependent_component_name",
+        "Dependencies/service_name",
+        "Dependencies/component_name",
+        "Dependencies/scope",
+        "auto_deploy/enabled",
+        "auto_deploy/location"
+    ],
     "DRFeed":[
         "Feed/name",
         "Feed/description",
@@ -458,10 +539,32 @@
         "StackConfigurationDependency/dependency_name",
         "_"
     ],
-  "KerberosDescriptor":[
-    "KerberosDescriptors/kerberos_descriptor_name",
-    "KerberosDescriptors/kerberos_descriptor_text",
-    "_"
-  ]
+    "ExtensionLevelConfiguration":[
+        "ExtensionLevelConfigurations/extension_name",
+        "ExtensionLevelConfigurations/extension_version",
+        "ExtensionLevelConfigurations/property_name",
+        "ExtensionLevelConfigurations/property_value",
+        "ExtensionLevelConfigurations/property_value_attributes",
+        "ExtensionLevelConfigurations/property_depends_on",
+        "ExtensionLevelConfigurations/property_description",
+        "ExtensionLevelConfigurations/type",
+        "ExtensionLevelConfigurations/final",
+        "ExtensionLevelConfigurations/property_type",
+        "_"
+    ],
+    "ExtensionConfigurationDependency":[
+        "ExtensionConfigurationDependency/extension_name",
+        "ExtensionConfigurationDependency/extension_version",
+        "ExtensionConfigurationDependency/service_name",
+        "ExtensionConfigurationDependency/property_name",
+        "ExtensionConfigurationDependency/dependency_type",
+        "ExtensionConfigurationDependency/dependency_name",
+        "_"
+    ],
+    "KerberosDescriptor":[
+        "KerberosDescriptors/kerberos_descriptor_name",
+        "KerberosDescriptors/kerberos_descriptor_text",
+        "_"
+    ]
 
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/resources/scripts/stack_layout_advisor.py
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/scripts/stack_layout_advisor.py b/ambari-server/src/main/resources/scripts/stack_layout_advisor.py
new file mode 100644
index 0000000..80c87f1
--- /dev/null
+++ b/ambari-server/src/main/resources/scripts/stack_layout_advisor.py
@@ -0,0 +1,147 @@
+#!/usr/bin/env ambari-python-wrap
+
+'''
+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 json
+import os
+import sys
+import traceback
+
+RECOMMEND_COMPONENT_LAYOUT_ACTION = 'recommend-component-layout'
+VALIDATE_COMPONENT_LAYOUT_ACTION = 'validate-component-layout'
+
+ALL_ACTIONS = [RECOMMEND_COMPONENT_LAYOUT_ACTION,
+               VALIDATE_COMPONENT_LAYOUT_ACTION]
+USAGE = "Usage: <action> <hosts_file> <services_file>\nPossible actions are: {0}\n".format( str(ALL_ACTIONS) )
+
+SCRIPT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
+STACK_LAYOUT_ADVISOR_PATH_TEMPLATE = os.path.join(SCRIPT_DIRECTORY, '../stacks/stack_advisor.py')
+STACK_LAYOUT_ADVISOR_DEFAULT_IMPL_CLASS = 'DefaultStackLayoutAdvisor'
+STACK_LAYOUT_ADVISOR_IMPL_PATH_TEMPLATE = os.path.join(SCRIPT_DIRECTORY, './../stacks/stack_layout_advisor.py')
+STACK_LAYOUT_ADVISOR_IMPL_CLASS_TEMPLATE = '{0}{1}StackAdvisor'
+
+
+class StackAdvisorException(Exception):
+  pass
+
+def loadJson(path):
+  try:
+    with open(path, 'r') as f:
+      return json.load(f)
+  except Exception, err:
+    raise StackAdvisorException("File not found at: {0}".format(path))
+
+
+def dumpJson(json_object, dump_file):
+  try:
+    with open(dump_file, 'w') as out:
+      json.dump(json_object, out, indent=1)
+  except Exception, err:
+    raise StackAdvisorException("Can not write to file {0} : {1}".format(dump_file, str(err)))
+
+
+def main(argv=None):
+  args = argv[1:]
+
+  if len(args) < 3:
+    sys.stderr.write(USAGE)
+    sys.exit(2)
+    pass
+
+  action = args[0]
+  if action not in ALL_ACTIONS:
+    sys.stderr.write(USAGE)
+    sys.exit(2)
+    pass
+
+  hostsFile = args[1]
+  servicesFile = args[2]
+
+  # Parse hostsFile and servicesFile
+  hosts = loadJson(hostsFile)
+  services = loadJson(servicesFile)
+
+  # Instantiate StackAdvisor and call action related method
+  stackName = services["Versions"]["stack_name"]
+  stackVersion = services["Versions"]["stack_version"]
+  parentVersions = []
+  if "stack_hierarchy" in services["Versions"]:
+    parentVersions = services["Versions"]["stack_hierarchy"]["stack_versions"]
+
+  stackAdvisor = instantiateStackAdvisor(stackName, stackVersion, parentVersions)
+
+  # Perform action
+  actionDir = os.path.realpath(os.path.dirname(args[1]))
+  result = {}
+  result_file = "non_valid_result_file.json"
+
+  if action == RECOMMEND_COMPONENT_LAYOUT_ACTION:
+    result = stackAdvisor.recommendComponentLayout(services, hosts)
+    result_file = os.path.join(actionDir, "component-layout.json")
+  elif action == VALIDATE_COMPONENT_LAYOUT_ACTION:
+    result = stackAdvisor.validateComponentLayout(services, hosts)
+    result_file = os.path.join(actionDir, "component-layout-validation.json")
+
+  dumpJson(result, result_file)
+  pass
+
+
+def instantiateStackAdvisor(stackName, stackVersion, parentVersions):
+  """Instantiates StackAdvisor implementation for the specified Stack"""
+  import imp
+
+  with open(STACK_LAYOUT_ADVISOR_PATH_TEMPLATE, 'rb') as fp:
+    default_stack_advisor = imp.load_module('stack_layout_advisor', fp, STACK_LAYOUT_ADVISOR_PATH_TEMPLATE, ('.py', 'rb', imp.PY_SOURCE))
+  className = STACK_LAYOUT_ADVISOR_DEFAULT_IMPL_CLASS
+  stack_advisor = default_stack_advisor
+
+  #versions = [stackVersion]
+  #versions.extend(parentVersions)
+
+  #for version in reversed(versions):
+  #  try:
+  #    path = STACK_LAYOUT_ADVISOR_IMPL_PATH_TEMPLATE.format(stackName, version)
+
+  #    with open(path, 'rb') as fp:
+  #      stack_advisor = imp.load_module('stack_advisor_impl', fp, path, ('.py', 'rb', imp.PY_SOURCE))
+  #    className = STACK_LAYOUT_ADVISOR_IMPL_CLASS_TEMPLATE.format(stackName, version.replace('.', ''))
+  #    print "StackAdvisor implementation for stack {0}, version {1} was loaded".format(stackName, version)
+  #  except Exception:
+  #    print "StackAdvisor implementation for stack {0}, version {1} was not found".format(stackName, version)
+
+  try:
+    clazz = getattr(stack_advisor, className)
+    print "Returning " + className + " implementation"
+    return clazz()
+  except Exception, e:
+    print "Returning default implementation"
+    return default_stack_advisor.DefaultStackAdvisor()
+
+
+if __name__ == '__main__':
+  try:
+    main(sys.argv)
+  except StackAdvisorException as stack_exception:
+    traceback.print_exc()
+    print "Error occured in stack advisor.\nError details: {0}".format(str(stack_exception))
+    sys.exit(1)
+  except Exception as e:
+    traceback.print_exc()
+    print "Error occured in stack advisor.\nError details: {0}".format(str(e))
+    sys.exit(2)

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/resources/stacks/HDP/2.3/services/HBASE/metainfo.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/stacks/HDP/2.3/services/HBASE/metainfo.xml b/ambari-server/src/main/resources/stacks/HDP/2.3/services/HBASE/metainfo.xml
index 90a31f5..5de9c7d 100644
--- a/ambari-server/src/main/resources/stacks/HDP/2.3/services/HBASE/metainfo.xml
+++ b/ambari-server/src/main/resources/stacks/HDP/2.3/services/HBASE/metainfo.xml
@@ -29,6 +29,7 @@
           <category>SLAVE</category>
           <cardinality>0+</cardinality>
           <versionAdvertised>true</versionAdvertised>
+          <useCardinalityForLayout>true</useCardinalityForLayout>
           <commandScript>
             <script>scripts/phoenix_queryserver.py</script>
             <scriptType>PYTHON</scriptType>

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/resources/stacks/HDP/2.3/services/HDFS/metainfo.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/stacks/HDP/2.3/services/HDFS/metainfo.xml b/ambari-server/src/main/resources/stacks/HDP/2.3/services/HDFS/metainfo.xml
index ca69195..4d2a7a2 100644
--- a/ambari-server/src/main/resources/stacks/HDP/2.3/services/HDFS/metainfo.xml
+++ b/ambari-server/src/main/resources/stacks/HDP/2.3/services/HDFS/metainfo.xml
@@ -28,6 +28,7 @@
           <displayName>NFSGateway</displayName>
           <cardinality>0+</cardinality>
           <versionAdvertised>false</versionAdvertised>
+          <useCardinalityForLayout>true</useCardinalityForLayout>
           <category>SLAVE</category>
           <commandScript>
             <script>scripts/nfsgateway.py</script>

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/resources/stacks/stack_advisor.py
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/stacks/stack_advisor.py b/ambari-server/src/main/resources/stacks/stack_advisor.py
index d993feb..d5ef10c 100644
--- a/ambari-server/src/main/resources/stacks/stack_advisor.py
+++ b/ambari-server/src/main/resources/stacks/stack_advisor.py
@@ -439,7 +439,7 @@ class DefaultStackAdvisor(StackAdvisor):
   pass
 
   def isComponentUsingCardinalityForLayout(self, componentName):
-    return False
+    return self.getComponentAttribute(component, "use_cardinality_for_layout")
 
   def createValidationResponse(self, services, validationItems):
     """Returns array of Validation objects about issues with hostnames components assigned to"""
@@ -607,9 +607,7 @@ class DefaultStackAdvisor(StackAdvisor):
     return componentName in masters
 
   def isComponentNotValuable(self, component):
-    componentName = self.getComponentName(component)
-    service = self.getNotValuableComponents()
-    return componentName in service
+    return self.getComponentAttribute(component, "not_valuable")
 
   def getMinComponentCount(self, component):
     componentName = self.getComponentName(component)
@@ -643,9 +641,7 @@ class DefaultStackAdvisor(StackAdvisor):
     return self.getComponentAttribute(component, "component_name")
 
   def isComponentNotPreferableOnAmbariServerHost(self, component):
-    componentName = self.getComponentName(component)
-    service = self.getNotPreferableOnServerComponents()
-    return componentName in service
+    return self.getComponentAttribute(component, "not_preferable_on_server_components")
 
   def isHostSuitableForComponent(self, host, component):
     return not (self.isComponentNotPreferableOnAmbariServerHost(component) and self.isLocalHost(host))
@@ -656,9 +652,6 @@ class DefaultStackAdvisor(StackAdvisor):
   def getNotValuableComponents(self):
     return []
 
-  def getNotPreferableOnServerComponents(self):
-    return []
-
   def getCardinalitiesDict(self):
     return {}
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/resources/stacks/stack_layout_advisor.py
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/stacks/stack_layout_advisor.py b/ambari-server/src/main/resources/stacks/stack_layout_advisor.py
new file mode 100644
index 0000000..1f65d70
--- /dev/null
+++ b/ambari-server/src/main/resources/stacks/stack_layout_advisor.py
@@ -0,0 +1,449 @@
+#!/usr/bin/env ambari-python-wrap
+"""
+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 socket
+import re
+
+class StackLayoutAdvisor(object):
+  """
+  Abstract class implemented by all stack layout advisors. Stack layout advisors advise on stack specific questions.
+
+  Currently stack layout advisors provide following abilities:
+  - Recommend where services should be installed in cluster
+  - Validate user selection of where services are installed on cluster
+
+  Each of the above methods is passed in parameters about services and hosts involved as described below.
+
+    @type services: dictionary
+    @param services: Dictionary containing all information about services selected by the user.
+      Example: {
+      "services": [
+        {
+          "StackServices": {
+            "service_name" : "HDFS",
+            "service_version" : "2.6.0.2.2",
+          },
+          "components" : [
+            {
+              "StackServiceComponents" : {
+                "cardinality" : "1+",
+                "component_category" : "SLAVE",
+                "component_name" : "DATANODE",
+                "display_name" : "DataNode",
+                "service_name" : "HDFS",
+                "hostnames" : []
+              },
+              "dependencies" : []
+            }, {
+              "StackServiceComponents" : {
+                "cardinality" : "1-2",
+                "component_category" : "MASTER",
+                "component_name" : "NAMENODE",
+                "display_name" : "NameNode",
+                "service_name" : "HDFS",
+                "hostnames" : []
+              },
+              "dependencies" : []
+            },
+            ...
+          ]
+        },
+        ...
+      ]
+    }
+  @type hosts: dictionary
+  @param hosts: Dictionary containing all information about hosts in this cluster
+    Example: {
+      "items": [
+        {
+          Hosts: {
+            "host_name": "c6401.ambari.apache.org",
+            "public_host_name" : "c6401.ambari.apache.org",
+            "ip": "192.168.1.101",
+            "cpu_count" : 1,
+            "disk_info" : [
+              {
+              "available" : "4564632",
+              "used" : "5230344",
+              "percent" : "54%",
+              "size" : "10319160",
+              "type" : "ext4",
+              "mountpoint" : "/"
+              },
+              {
+              "available" : "1832436",
+              "used" : "0",
+              "percent" : "0%",
+              "size" : "1832436",
+              "type" : "tmpfs",
+              "mountpoint" : "/dev/shm"
+              }
+            ],
+            "host_state" : "HEALTHY",
+            "os_arch" : "x86_64",
+            "os_type" : "centos6",
+            "total_mem" : 3664872
+          }
+        },
+        ...
+      ]
+    }
+
+    Each of the methods can either return recommendations or validations.
+
+    Recommendations are made in a Ambari Blueprints friendly format.
+    Validations are an array of validation objects.
+  """
+
+  def recommendComponentLayout(self, services, hosts):
+    """
+    Returns recommendation of which hosts various service components should be installed on.
+
+    This function takes as input all details about services being installed, and hosts
+    they are being installed into, to generate hostname assignments to various components
+    of each service.
+
+    @type services: dictionary
+    @param services: Dictionary containing all information about services selected by the user.
+    @type hosts: dictionary
+    @param hosts: Dictionary containing all information about hosts in this cluster
+    @rtype: dictionary
+    @return: Layout recommendation of service components on cluster hosts in Ambari Blueprints friendly format.
+        Example: {
+          "resources" : [
+            {
+              "hosts" : [
+                "c6402.ambari.apache.org",
+                "c6401.ambari.apache.org"
+              ],
+              "services" : [
+                "HDFS"
+              ],
+              "recommendations" : {
+                "blueprint" : {
+                  "host_groups" : [
+                    {
+                      "name" : "host-group-2",
+                      "components" : [
+                        { "name" : "JOURNALNODE" },
+                        { "name" : "ZKFC" },
+                        { "name" : "DATANODE" },
+                        { "name" : "SECONDARY_NAMENODE" }
+                      ]
+                    },
+                    {
+                      "name" : "host-group-1",
+                      "components" : [
+                        { "name" : "HDFS_CLIENT" },
+                        { "name" : "NAMENODE" },
+                        { "name" : "JOURNALNODE" },
+                        { "name" : "ZKFC" },
+                        { "name" : "DATANODE" }
+                      ]
+                    }
+                  ]
+                },
+                "blueprint_cluster_binding" : {
+                  "host_groups" : [
+                    {
+                      "name" : "host-group-1",
+                      "hosts" : [ { "fqdn" : "c6401.ambari.apache.org" } ]
+                    },
+                    {
+                      "name" : "host-group-2",
+                      "hosts" : [ { "fqdn" : "c6402.ambari.apache.org" } ]
+                    }
+                  ]
+                }
+              }
+            }
+          ]
+        }
+    """
+    pass
+
+  def validateComponentLayout(self, services, hosts):
+    """
+    Returns array of Validation issues with service component layout on hosts
+
+    This function takes as input all details about services being installed along with
+    hosts the components are being installed on (hostnames property is populated for
+    each component).
+
+    @type services: dictionary
+    @param services: Dictionary containing information about services and host layout selected by the user.
+    @type hosts: dictionary
+    @param hosts: Dictionary containing all information about hosts in this cluster
+    @rtype: dictionary
+    @return: Dictionary containing array of validation items
+        Example: {
+          "items": [
+            {
+              "type" : "host-group",
+              "level" : "ERROR",
+              "message" : "NameNode and Secondary NameNode should not be hosted on the same machine",
+              "component-name" : "NAMENODE",
+              "host" : "c6401.ambari.apache.org"
+            },
+            ...
+          ]
+        }
+    """
+    pass
+
+
+
+
+
+
+class DefaultStackLayoutAdvisor(StackLayoutAdvisor):
+  """
+  Default stack layout advisor implementation.
+
+  This implementation is used when a stack-version, or its hierarchy does not
+  have an advisor. Stack-versions can extend this class to provide their own
+  implementation
+  """
+
+  def recommendComponentLayout(self, services, hosts):
+    """Returns Services object with hostnames array populated for components"""
+
+    stackName = services["Versions"]["stack_name"]
+    stackVersion = services["Versions"]["stack_version"]
+    hostsList = [host["Hosts"]["host_name"] for host in hosts["items"]]
+    servicesList = [service["StackServices"]["service_name"] for service in services["services"]]
+
+    layoutRecommendations = self.createComponentLayoutRecommendations(services, hosts)
+
+    recommendations = {
+      "Versions": {"stack_name": stackName, "stack_version": stackVersion},
+      "hosts": hostsList,
+      "services": servicesList,
+      "recommendations": layoutRecommendations
+    }
+
+    return recommendations
+
+  def createComponentLayoutRecommendations(self, services, hosts):
+
+    recommendations = {
+      "blueprint": {
+        "host_groups": [ ]
+      },
+      "blueprint_cluster_binding": {
+        "host_groups": [ ]
+      }
+    }
+
+    hostsList = [host["Hosts"]["host_name"] for host in hosts["items"]]
+
+    hostsComponentsMap = {}
+    for hostName in hostsList:
+      if hostName not in hostsComponentsMap:
+        hostsComponentsMap[hostName] = []
+
+    #extend 'hostsComponentsMap' with MASTER components
+    for service in services["services"]:
+      masterComponents = [component for component in service["components"] if self.isMasterComponent(component)]
+      for component in masterComponents:
+        componentName = component["StackServiceComponents"]["component_name"]
+
+        if self.isComponentHostsPopulated(component):
+          hostsForComponent = component["StackServiceComponents"]["hostnames"]
+        else:
+
+          if len(hostsList) > 1 and self.isMasterComponentWithMultipleInstances(component):
+            hostsCount = self.getMinComponentCount(component)
+            if hostsCount > 1: # get first 'hostsCount' available hosts
+              hostsForComponent = []
+              hostIndex = 0
+              while hostsCount > len(hostsForComponent) and hostIndex < len(hostsList):
+                currentHost = hostsList[hostIndex]
+                if self.isHostSuitableForComponent(currentHost, component):
+                  hostsForComponent.append(currentHost)
+                hostIndex += 1
+            else:
+              hostsForComponent = [self.getHostForComponent(component, hostsList)]
+          else:
+            hostsForComponent = [self.getHostForComponent(component, hostsList)]
+
+        #extend 'hostsComponentsMap' with 'hostsForComponent'
+        for hostName in hostsForComponent:
+          hostsComponentsMap[hostName].append( { "name":componentName } )
+    #extend 'hostsComponentsMap' with Slave and Client Components
+    componentsListList = [service["components"] for service in services["services"]]
+    componentsList = [item for sublist in componentsListList for item in sublist]
+    usedHostsListList = [component["StackServiceComponents"]["hostnames"] for component in componentsList if not self.isComponentNotValuable(component)]
+    utilizedHosts = [item for sublist in usedHostsListList for item in sublist]
+    freeHosts = [hostName for hostName in hostsList if hostName not in utilizedHosts]
+
+    for service in services["services"]:
+      slaveClientComponents = [component for component in service["components"]
+                               if self.isSlaveComponent(component) or self.isClientComponent(component)]
+      for component in slaveClientComponents:
+        componentName = component["StackServiceComponents"]["component_name"]
+
+        if component["StackServiceComponents"]["cardinality"] == "ALL":
+          hostsForComponent = hostsList
+        else:
+          componentIsPopulated = self.isComponentHostsPopulated(component)
+          if componentIsPopulated:
+            hostsForComponent = component["StackServiceComponents"]["hostnames"]
+          else:
+            hostsForComponent = []
+
+          if self.isSlaveComponent(component):
+            cardinality = str(component["StackServiceComponents"]["cardinality"])
+            if self.isComponentUsingCardinalityForLayout(component) and cardinality:
+              # cardinality types: 1+, 1-2, 1, ALL
+              if "+" in cardinality:
+                hostsMin = int(cardinality[:-1])
+              elif "-" in cardinality:
+                nums = cardinality.split("-")
+                hostsMin = int(nums[0])
+              else:
+                hostsMin = int(cardinality)
+              if hostsMin > len(hostsForComponent):
+                hostsForComponent.extend(freeHosts[0:hostsMin-len(hostsForComponent)])
+            else:
+              hostsForComponent.extend(freeHosts)
+              if not hostsForComponent:  # hostsForComponent is empty
+                hostsForComponent = hostsList[-1:]
+            hostsForComponent = list(set(hostsForComponent))  # removing duplicates
+          elif self.isClientComponent(component) and not componentIsPopulated:
+            hostsForComponent = freeHosts[0:1]
+            if not hostsForComponent:  # hostsForComponent is empty
+              hostsForComponent = hostsList[-1:]
+
+        #extend 'hostsComponentsMap' with 'hostsForComponent'
+        for hostName in hostsForComponent:
+          if hostName not in hostsComponentsMap:
+            hostsComponentsMap[hostName] = []
+          hostsComponentsMap[hostName].append( { "name": componentName } )
+
+    #prepare 'host-group's from 'hostsComponentsMap'
+    host_groups = recommendations["blueprint"]["host_groups"]
+    bindings = recommendations["blueprint_cluster_binding"]["host_groups"]
+    index = 0
+    for key in hostsComponentsMap.keys():
+      index += 1
+      host_group_name = "host-group-{0}".format(index)
+      host_groups.append( { "name": host_group_name, "components": hostsComponentsMap[key] } )
+      bindings.append( { "name": host_group_name, "hosts": [{ "fqdn": key }] } )
+
+    return recommendations
+  pass
+
+  def isComponentUsingCardinalityForLayout(self, componentName):
+    return self.getComponentAttribute(component, "use_cardinality_for_layout")
+
+  def createValidationResponse(self, services, validationItems):
+    """Returns array of Validation objects about issues with hostnames components assigned to"""
+    stackName = services["Versions"]["stack_name"]
+    stackVersion = services["Versions"]["stack_version"]
+
+    validations = {
+      "Versions": {"stack_name": stackName, "stack_version": stackVersion},
+      "items": validationItems
+    }
+
+    return validations
+
+  def validateComponentLayout(self, services, hosts):
+    """Returns array of Validation objects about issues with hostnames components assigned to"""
+    validationItems = self.getComponentLayoutValidations(services, hosts)
+    return self.createValidationResponse(services, validationItems)
+
+  def getComponentLayoutValidations(self, services, hosts):
+    return []
+
+  def getConfigurationClusterSummary(self, servicesList, hosts, components, services):
+    pass
+
+  # Recommendation helper methods
+  def isComponentHostsPopulated(self, component):
+    hostnames = self.getComponentAttribute(component, "hostnames")
+    if hostnames is not None:
+      return len(hostnames) > 0
+    return False
+
+  def isClientComponent(self, component):
+    return self.getComponentAttribute(component, "component_category") == 'CLIENT'
+
+  def isSlaveComponent(self, component):
+    return self.getComponentAttribute(component, "component_category") == 'SLAVE'
+
+  def isMasterComponent(self, component):
+    return self.getComponentAttribute(component, "is_master")
+
+  def getComponentAttribute(self, component, attribute):
+    serviceComponent = component.get("StackServiceComponents", None)
+    if serviceComponent is None:
+      return None
+    return serviceComponent.get(attribute, None)
+
+  def isLocalHost(self, hostName):
+    return socket.getfqdn(hostName) == socket.getfqdn()
+
+  def isMasterComponentWithMultipleInstances(self, component):
+    return self.getComponentAttribute(component, "setup_ha")
+
+  def isComponentNotValuable(self, component):
+    return self.getComponentAttribute(component, "not_valuable")
+
+  def getMinComponentCount(self, component):
+    min = self.getComponentAttribute(component, "ideal_minimum")
+    if min is not None:
+      return min
+    return 1
+
+  def getHostForComponent(self, component, hostsList):
+    componentName = self.getComponentName(component)
+    print(componentName)
+
+    if len(hostsList) != 1:
+      scheme = self.getComponentAttribute(component, "layout")
+      if scheme is not None:
+        print(scheme)
+        entry = scheme['else']
+        print(entry)
+        hostIndex = next((index for key, index in scheme.iteritems() if isinstance(key, ( int, long )) and len(hostsList) < key), scheme['else'])
+      else:
+        hostIndex = 0
+      for host in hostsList[hostIndex:]:
+        if self.isHostSuitableForComponent(host, component):
+          return host
+    return hostsList[0]
+
+  def getComponentName(self, component):
+    return self.getComponentAttribute(component, "component_name")
+
+  def isComponentNotPreferableOnAmbariServerHost(self, component):
+    return self.getComponentAttribute(component, "not_preferable_on_server_components")
+
+  def isHostSuitableForComponent(self, host, component):
+    return not (self.isComponentNotPreferableOnAmbariServerHost(component) and self.isLocalHost(host))
+
+  def getComponentHostNames(self, servicesDict, serviceName, componentName):
+    for service in servicesDict["services"]:
+      if service["StackServices"]["service_name"] == serviceName:
+        for component in service['components']:
+          if component["StackServiceComponents"]["component_name"] == componentName:
+            return component["StackServiceComponents"]["hostnames"]
+  pass

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/test/java/org/apache/ambari/server/api/services/ExtensionsServiceTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/ExtensionsServiceTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/ExtensionsServiceTest.java
new file mode 100644
index 0000000..0989207
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/ExtensionsServiceTest.java
@@ -0,0 +1,215 @@
+/**
+ * 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.
+ */
+
+package org.apache.ambari.server.api.services;
+
+import org.apache.ambari.server.api.resources.ResourceInstance;
+import org.apache.ambari.server.api.services.parsers.RequestBodyParser;
+import org.apache.ambari.server.api.services.serializers.ResultSerializer;
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.notNull;
+import static org.easymock.EasyMock.same;
+import static org.junit.Assert.assertEquals;
+
+/**
+* Unit tests for ExtensionsService.
+*/
+public class ExtensionsServiceTest extends BaseServiceTest {
+
+  @Override
+  public List<ServiceTestInvocation> getTestInvocations() throws Exception {
+    List<ServiceTestInvocation> listInvocations = new ArrayList<ServiceTestInvocation>();
+
+    // getExtension
+    ExtensionsService service = new TestExtensionsService("extensionName", null);
+    Method m = service.getClass().getMethod("getExtension", String.class, HttpHeaders.class, UriInfo.class, String.class);
+    Object[] args = new Object[] {null, getHttpHeaders(), getUriInfo(), "extensionName"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, service, m, args, null));
+
+    //getExtensions
+    service = new TestExtensionsService(null, null);
+    m = service.getClass().getMethod("getExtensions", String.class, HttpHeaders.class, UriInfo.class);
+    args = new Object[] {null, getHttpHeaders(), getUriInfo()};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, service, m, args, null));
+
+    // getExtensionVersion
+    service = new TestExtensionsService("extensionName", "extensionVersion");
+    m = service.getClass().getMethod("getExtensionVersion", String.class, HttpHeaders.class, UriInfo.class, String.class, String.class);
+    args = new Object[] {null, getHttpHeaders(), getUriInfo(), "extensionName", "extensionVersion"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, service, m, args, null));
+
+    // getExtensionVersions
+    service = new TestExtensionsService("extensionName", null);
+    m = service.getClass().getMethod("getExtensionVersions", String.class, HttpHeaders.class, UriInfo.class, String.class);
+    args = new Object[] {null, getHttpHeaders(), getUriInfo(), "extensionName"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, service, m, args, null));
+
+    // getExtensionService
+    service = new TestExtensionsService("extensionName", null);
+    m = service.getClass().getMethod("getExtensionService", String.class, HttpHeaders.class, UriInfo.class, String.class,
+        String.class, String.class);
+    args = new Object[] {null, getHttpHeaders(), getUriInfo(), "extensionName", "extensionVersion", "service-name"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, service, m, args, null));
+
+    // getExtensionServices
+    service = new TestExtensionsService("extensionName", null);
+    m = service.getClass().getMethod("getExtensionServices", String.class, HttpHeaders.class, UriInfo.class, String.class, String.class);
+    args = new Object[] {null, getHttpHeaders(), getUriInfo(), "extensionName", "extensionVersion"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, service, m, args, null));
+
+    // getExtensionConfiguration
+    service = new TestExtensionsService("extensionName", "extensionVersion");
+    m = service.getClass().getMethod("getExtensionConfiguration", String.class, HttpHeaders.class, UriInfo.class,
+        String.class, String.class, String.class, String.class);
+    args = new Object[] {null, getHttpHeaders(), getUriInfo(), "extensionName", "extensionVersion", "service-name", "property-name"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, service, m, args, null));
+
+    // getExtensionConfigurations
+    service = new TestExtensionsService("extensionName", null);
+    m = service.getClass().getMethod("getExtensionConfigurations", String.class, HttpHeaders.class, UriInfo.class,
+        String.class, String.class, String.class);
+    args = new Object[] {null, getHttpHeaders(), getUriInfo(), "extensionName", "extensionVersion", "service-name"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, service, m, args, null));
+
+    // getServiceComponent
+    service = new TestExtensionsService("extensionName", "extensionVersion");
+    m = service.getClass().getMethod("getServiceComponent", String.class, HttpHeaders.class, UriInfo.class,
+        String.class, String.class, String.class, String.class);
+    args = new Object[] {null, getHttpHeaders(), getUriInfo(), "extensionName", "extensionVersion", "service-name", "component-name"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, service, m, args, null));
+
+    // getServiceComponents
+    service = new TestExtensionsService("extensionName", "extensionVersion");
+    m = service.getClass().getMethod("getServiceComponents", String.class, HttpHeaders.class, UriInfo.class,
+        String.class, String.class, String.class);
+    args = new Object[] {null, getHttpHeaders(), getUriInfo(), "extensionName", "extensionVersion", "service-name"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, service, m, args, null));
+
+    //get extension artifacts
+    service = new TestExtensionsService("extensionName", null);
+    m = service.getClass().getMethod("getExtensionArtifacts", String.class, HttpHeaders.class, UriInfo.class, String.class, String.class);
+    args = new Object[] {null, getHttpHeaders(), getUriInfo(), "extensionName", "extensionVersion"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, service, m, args, null));
+
+    //get extension artifact
+    service = new TestExtensionsService("extensionName", null);
+    m = service.getClass().getMethod("getExtensionArtifact", String.class, HttpHeaders.class, UriInfo.class, String.class,
+        String.class, String.class);
+    args = new Object[] {null, getHttpHeaders(), getUriInfo(), "extensionName", "extensionVersion", "artifact-name"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, service, m, args, null));
+
+    //get extension service artifacts
+    service = new TestExtensionsService("extensionName", "extensionVersion");
+    m = service.getClass().getMethod("getExtensionServiceArtifacts", String.class, HttpHeaders.class, UriInfo.class,
+        String.class, String.class, String.class);
+    args = new Object[] {null, getHttpHeaders(), getUriInfo(), "extensionName", "extensionVersion", "service-name"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, service, m, args, null));
+
+    //get extension service artifact
+    service = new TestExtensionsService("extensionName", "extensionVersion");
+    m = service.getClass().getMethod("getExtensionServiceArtifact", String.class, HttpHeaders.class, UriInfo.class,
+        String.class, String.class, String.class, String.class);
+    args = new Object[] {null, getHttpHeaders(), getUriInfo(), "extensionName", "extensionVersion", "service-name", "artifact-name"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, service, m, args, null));
+
+    return listInvocations;
+  }
+
+  private class TestExtensionsService extends ExtensionsService {
+
+    private String m_extensionId;
+    private String m_extensionVersion;
+
+    private TestExtensionsService(String extensionName, String extensionVersion) {
+      m_extensionId = extensionName;
+      m_extensionVersion = extensionVersion;
+    }
+
+    @Override
+    ResourceInstance createExtensionResource(String extensionName) {
+      assertEquals(m_extensionId, extensionName);
+      return getTestResource();
+    }
+
+    @Override
+    ResourceInstance createExtensionVersionResource(String extensionName, String extensionVersion) {
+      assertEquals(m_extensionId, extensionName);
+      assertEquals(m_extensionVersion, extensionVersion);
+      return getTestResource();
+    }
+
+    @Override
+    ResourceInstance createExtensionServiceResource(String extensionName,
+        String extensionVersion, String serviceName) {
+      return getTestResource();
+    }
+
+    @Override
+    ResourceInstance createExtensionConfigurationResource(String extensionName,
+        String extensionVersion, String serviceName, String propertyName) {
+      return getTestResource();
+    }
+
+    @Override
+    ResourceInstance createExtensionServiceComponentResource(String extensionName,
+        String extensionVersion, String serviceName, String componentName) {
+      return getTestResource();
+    }
+
+    @Override
+    ResourceInstance createExtensionArtifactsResource(String extensionName, String extensionVersion, String artifactName) {
+      return getTestResource();
+    }
+
+    @Override
+    ResourceInstance createExtensionServiceArtifactsResource(String extensionName, String extensionVersion, String serviceName, String artifactName) {
+      return getTestResource();
+    }
+
+    @Override
+    RequestFactory getRequestFactory() {
+      return getTestRequestFactory();
+    }
+
+    @Override
+    protected RequestBodyParser getBodyParser() {
+      return getTestBodyParser();
+    }
+
+    @Override
+    protected ResultSerializer getResultSerializer() {
+      return getTestResultSerializer();
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ExtensionResourceProviderTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ExtensionResourceProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ExtensionResourceProviderTest.java
new file mode 100644
index 0000000..8959e4d
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ExtensionResourceProviderTest.java
@@ -0,0 +1,91 @@
+/**
+ * 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.
+ */
+
+package org.apache.ambari.server.controller.internal;
+
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.ExtensionRequest;
+import org.apache.ambari.server.controller.ExtensionResponse;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.ResourceProvider;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.easymock.EasyMock;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+/**
+ * ExtensionResourceProvider Test
+ */
+public class ExtensionResourceProviderTest {
+  @Test
+  public void testGetResources() throws Exception {
+    Resource.Type type = Resource.Type.Extension;
+
+    AmbariManagementController managementController = createMock(AmbariManagementController.class);
+
+    Set<ExtensionResponse> allResponse = new HashSet<ExtensionResponse>();
+    allResponse.add(new ExtensionResponse("Extension1"));
+    allResponse.add(new ExtensionResponse("Extension2"));
+
+    // set expectations
+    expect(managementController.getExtensions(EasyMock.<Set<ExtensionRequest>>anyObject())).andReturn(allResponse).once();
+
+    // replay
+    replay(managementController);
+
+    ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
+        type,
+        PropertyHelper.getPropertyIds(type),
+        PropertyHelper.getKeyPropertyIds(type),
+        managementController);
+
+    Set<String> propertyIds = new HashSet<String>();
+
+    propertyIds.add(ExtensionResourceProvider.EXTENSION_NAME_PROPERTY_ID);
+
+    // create the request
+    Request request = PropertyHelper.getReadRequest(propertyIds);
+
+    // get all ... no predicate
+    Set<Resource> resources = provider.getResources(request, null);
+
+    Assert.assertEquals(2, resources.size());
+
+
+    Set<String> extensionNames = new HashSet<String>();
+    extensionNames.add("Extension1");
+    extensionNames.add("Extension2");
+
+    for (Resource resource : resources) {
+      String extensionName = (String) resource.getPropertyValue(ExtensionResourceProvider.EXTENSION_NAME_PROPERTY_ID);
+      Assert.assertTrue(extensionNames.contains(extensionName));
+    }
+
+    // verify
+    verify(managementController);
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/test/java/org/apache/ambari/server/stack/ComponentModuleTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/stack/ComponentModuleTest.java b/ambari-server/src/test/java/org/apache/ambari/server/stack/ComponentModuleTest.java
index 2f84f04..e80bf8b 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/stack/ComponentModuleTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/stack/ComponentModuleTest.java
@@ -394,7 +394,7 @@ public class ComponentModuleTest {
     ComponentModule component = new ComponentModule(info);
     ComponentModule parentComponent = new ComponentModule(parentInfo);
 
-    component.resolve(parentComponent, Collections.<String, StackModule>emptyMap(), Collections.<String, ServiceModule>emptyMap());
+    component.resolve(parentComponent, Collections.<String, StackModule>emptyMap(), Collections.<String, ServiceModule>emptyMap(), Collections.<String, ExtensionModule>emptyMap());
 
     return component;
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/test/java/org/apache/ambari/server/stack/ServiceModuleTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/stack/ServiceModuleTest.java b/ambari-server/src/test/java/org/apache/ambari/server/stack/ServiceModuleTest.java
index 92c1200..76a5bdf 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/stack/ServiceModuleTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/stack/ServiceModuleTest.java
@@ -1210,7 +1210,7 @@ public class ServiceModuleTest {
   }
 
   private void resolveService(ServiceModule service, ServiceModule parent) throws AmbariException {
-    service.resolve(parent, Collections.<String, StackModule>emptyMap(), Collections.<String, ServiceModule>emptyMap());
+    service.resolve(parent, Collections.<String, StackModule>emptyMap(), Collections.<String, ServiceModule>emptyMap(), Collections.<String, ExtensionModule>emptyMap());
     // during runtime this would be called by the Stack module when it's resolve completed
     service.finalizeModule();
     parent.finalizeModule();

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerCommonServicesTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerCommonServicesTest.java b/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerCommonServicesTest.java
index 6f3cf78..3fc382e 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerCommonServicesTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerCommonServicesTest.java
@@ -35,6 +35,8 @@ import java.util.Map;
 
 import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.metadata.ActionMetadata;
+import org.apache.ambari.server.orm.dao.ExtensionDAO;
+import org.apache.ambari.server.orm.dao.ExtensionLinkDAO;
 import org.apache.ambari.server.orm.dao.MetainfoDAO;
 import org.apache.ambari.server.orm.dao.StackDAO;
 import org.apache.ambari.server.orm.entities.StackEntity;
@@ -58,6 +60,8 @@ public class StackManagerCommonServicesTest {
   private static StackManager stackManager;
   private static MetainfoDAO metaInfoDao;
   private static StackDAO stackDao;
+  private static ExtensionDAO extensionDao;
+  private static ExtensionLinkDAO linkDao;
   private static ActionMetadata actionMetadata;
   private static OsFamily osFamily;
 
@@ -72,14 +76,18 @@ public class StackManagerCommonServicesTest {
 
     String commonServices = ClassLoader.getSystemClassLoader().getResource(
         "common-services").getPath();
-    return createTestStackManager(stack, commonServices);
+    String extensions = ClassLoader.getSystemClassLoader().getResource(
+            "extensions").getPath();
+    return createTestStackManager(stack, commonServices, extensions);
   }
 
   public static StackManager createTestStackManager(String stackRoot,
-      String commonServicesRoot) throws Exception {
+      String commonServicesRoot, String extensionRoot) throws Exception {
     // todo: dao , actionMetaData expectations
     metaInfoDao = createNiceMock(MetainfoDAO.class);
     stackDao = createNiceMock(StackDAO.class);
+    extensionDao = createNiceMock(ExtensionDAO.class);
+    linkDao = createNiceMock(ExtensionLinkDAO.class);
     actionMetadata = createNiceMock(ActionMetadata.class);
     Configuration config = createNiceMock(Configuration.class);
     StackEntity stackEntity = createNiceMock(StackEntity.class);
@@ -96,8 +104,8 @@ public class StackManagerCommonServicesTest {
 
     replay(metaInfoDao, actionMetadata);
 
-    StackManager stackManager = new StackManager(new File(stackRoot), new File(
-        commonServicesRoot), osFamily, metaInfoDao, actionMetadata, stackDao);
+    StackManager stackManager = new StackManager(new File(stackRoot), new File(commonServicesRoot),
+        new File(extensionRoot), osFamily, metaInfoDao, actionMetadata, stackDao, extensionDao, linkDao);
 
     EasyMock.verify( config, stackDao );
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerExtensionTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerExtensionTest.java b/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerExtensionTest.java
new file mode 100644
index 0000000..3ebe4f6
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerExtensionTest.java
@@ -0,0 +1,105 @@
+/**
+ * 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.
+ */
+
+package org.apache.ambari.server.stack;
+
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.util.Map;
+
+import org.apache.ambari.server.metadata.ActionMetadata;
+import org.apache.ambari.server.orm.dao.ExtensionDAO;
+import org.apache.ambari.server.orm.dao.ExtensionLinkDAO;
+import org.apache.ambari.server.orm.dao.MetainfoDAO;
+import org.apache.ambari.server.orm.dao.StackDAO;
+import org.apache.ambari.server.orm.entities.StackEntity;
+import org.apache.ambari.server.state.ExtensionInfo;
+import org.apache.ambari.server.state.ServiceInfo;
+import org.apache.ambari.server.state.StackInfo;
+import org.apache.ambari.server.state.stack.OsFamily;
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+/**
+ * StackManager extension unit tests.
+ */
+public class StackManagerExtensionTest  {
+
+  @Test
+  public void testExtensions() throws Exception {
+    MetainfoDAO metaInfoDao = createNiceMock(MetainfoDAO.class);
+    StackDAO stackDao = createNiceMock(StackDAO.class);
+    ExtensionDAO extensionDao = createNiceMock(ExtensionDAO.class);
+    ExtensionLinkDAO linkDao = createNiceMock(ExtensionLinkDAO.class);
+    ActionMetadata actionMetadata = createNiceMock(ActionMetadata.class);
+    OsFamily osFamily = createNiceMock(OsFamily.class);
+    StackEntity stackEntity = createNiceMock(StackEntity.class);
+
+    expect(
+        stackDao.find(EasyMock.anyObject(String.class),
+            EasyMock.anyObject(String.class))).andReturn(stackEntity).atLeastOnce();
+
+    replay(actionMetadata, stackDao, metaInfoDao, osFamily);
+
+    String stacks = ClassLoader.getSystemClassLoader().getResource("stacks_with_extensions").getPath();
+    String common = ClassLoader.getSystemClassLoader().getResource("common-services").getPath();
+    String extensions = ClassLoader.getSystemClassLoader().getResource("extensions").getPath();
+
+    StackManager stackManager = new StackManager(new File(stacks),
+        new File(common), new File(extensions), osFamily, metaInfoDao, actionMetadata, stackDao, extensionDao, linkDao);
+
+    ExtensionInfo extension = stackManager.getExtension("EXT", "0.1");
+    assertNull("EXT 0.1's parent: " + extension.getParentExtensionVersion(), extension.getParentExtensionVersion());
+    assertNotNull(extension.getService("OOZIE2"));
+    ServiceInfo oozie = extension.getService("OOZIE2");
+    assertNotNull("Package dir is " + oozie.getServicePackageFolder(), oozie.getServicePackageFolder());
+    assertTrue("Package dir is " + oozie.getServicePackageFolder(), oozie.getServicePackageFolder().contains("extensions/EXT/0.1/services/OOZIE2/package"));
+    assertEquals(oozie.getVersion(), "3.2.0");
+
+    extension = stackManager.getExtension("EXT", "0.2");
+    assertNotNull("EXT 0.2's parent: " + extension.getParentExtensionVersion(), extension.getParentExtensionVersion());
+    assertEquals("EXT 0.2's parent: " + extension.getParentExtensionVersion(), "0.1", extension.getParentExtensionVersion());
+    assertNotNull(extension.getService("OOZIE2"));
+    oozie = extension.getService("OOZIE2");
+    assertNotNull("Package dir is " + oozie.getServicePackageFolder(), oozie.getServicePackageFolder());
+    assertTrue("Package dir is " + oozie.getServicePackageFolder(), oozie.getServicePackageFolder().contains("extensions/EXT/0.1/services/OOZIE2/package"));
+    assertEquals(oozie.getVersion(), "4.0.0");
+
+    StackInfo stack = stackManager.getStack("HDP", "0.2");
+    assertNotNull(stack.getService("OOZIE2"));
+    oozie = stack.getService("OOZIE2");
+    assertNotNull("Package dir is " + oozie.getServicePackageFolder(), oozie.getServicePackageFolder());
+    assertTrue("Package dir is " + oozie.getServicePackageFolder(), oozie.getServicePackageFolder().contains("extensions/EXT/0.1/services/OOZIE2/package"));
+    assertEquals(oozie.getVersion(), "4.0.0");
+
+    assertTrue("Extensions found: " + stack.getExtensions().size(), stack.getExtensions().size() == 1);
+    extension = stack.getExtensions().iterator().next();
+    assertEquals("Extension name: " + extension.getName(), extension.getName(), "EXT");
+    assertEquals("Extension version: " + extension.getVersion(), extension.getVersion(), "0.2");
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerMiscTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerMiscTest.java b/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerMiscTest.java
index dda1e7a..c925b38 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerMiscTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerMiscTest.java
@@ -31,6 +31,8 @@ import java.util.Collection;
 
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.metadata.ActionMetadata;
+import org.apache.ambari.server.orm.dao.ExtensionDAO;
+import org.apache.ambari.server.orm.dao.ExtensionLinkDAO;
 import org.apache.ambari.server.orm.dao.MetainfoDAO;
 import org.apache.ambari.server.orm.dao.StackDAO;
 import org.apache.ambari.server.orm.entities.StackEntity;
@@ -48,6 +50,8 @@ public class StackManagerMiscTest  {
   public void testCycleDetection() throws Exception {
     MetainfoDAO metaInfoDao = createNiceMock(MetainfoDAO.class);
     StackDAO stackDao = createNiceMock(StackDAO.class);
+    ExtensionDAO extensionDao = createNiceMock(ExtensionDAO.class);
+    ExtensionLinkDAO linkDao = createNiceMock(ExtensionLinkDAO.class);
     ActionMetadata actionMetadata = createNiceMock(ActionMetadata.class);
     OsFamily osFamily = createNiceMock(OsFamily.class);
     StackEntity stackEntity = createNiceMock(StackEntity.class);
@@ -62,7 +66,7 @@ public class StackManagerMiscTest  {
       String stacksCycle1 = ClassLoader.getSystemClassLoader().getResource("stacks_with_cycle").getPath();
 
       StackManager stackManager = new StackManager(new File(stacksCycle1),
-          null, osFamily, metaInfoDao, actionMetadata, stackDao);
+          null, null, osFamily, metaInfoDao, actionMetadata, stackDao, extensionDao, linkDao);
 
       fail("Expected exception due to cyclic stack");
     } catch (AmbariException e) {
@@ -74,7 +78,7 @@ public class StackManagerMiscTest  {
           "stacks_with_cycle2").getPath();
 
       StackManager stackManager = new StackManager(new File(stacksCycle2),
-          null, osFamily, metaInfoDao, actionMetadata, stackDao);
+          null, null, osFamily, metaInfoDao, actionMetadata, stackDao, extensionDao, linkDao);
 
       fail("Expected exception due to cyclic stack");
     } catch (AmbariException e) {
@@ -91,6 +95,8 @@ public class StackManagerMiscTest  {
   public void testGetServiceInfoFromSingleStack() throws Exception {
     MetainfoDAO metaInfoDao = createNiceMock(MetainfoDAO.class);
     StackDAO stackDao = createNiceMock(StackDAO.class);
+    ExtensionDAO extensionDao = createNiceMock(ExtensionDAO.class);
+    ExtensionLinkDAO linkDao = createNiceMock(ExtensionLinkDAO.class);
     ActionMetadata actionMetadata = createNiceMock(ActionMetadata.class);
     OsFamily  osFamily = createNiceMock(OsFamily.class);
     StackEntity stackEntity = createNiceMock(StackEntity.class);
@@ -108,7 +114,7 @@ public class StackManagerMiscTest  {
 
     StackManager stackManager = new StackManager(new File(singleStack.replace(
         StackManager.PATH_DELIMITER, File.separator)),
-        null, osFamily, metaInfoDao, actionMetadata, stackDao);
+        null, null, osFamily, metaInfoDao, actionMetadata, stackDao, extensionDao, linkDao);
 
 
     Collection<StackInfo> stacks = stackManager.getStacks();

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerTest.java
index b690a6f..133c9ff 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/stack/StackManagerTest.java
@@ -31,6 +31,7 @@ import static org.junit.Assert.assertTrue;
 import java.io.File;
 import java.io.FileReader;
 import java.lang.reflect.Type;
+import java.net.URL;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -40,11 +41,14 @@ import java.util.Map;
 
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
+
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.Role;
 import org.apache.ambari.server.RoleCommand;
 import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.metadata.ActionMetadata;
+import org.apache.ambari.server.orm.dao.ExtensionDAO;
+import org.apache.ambari.server.orm.dao.ExtensionLinkDAO;
 import org.apache.ambari.server.orm.dao.MetainfoDAO;
 import org.apache.ambari.server.orm.dao.StackDAO;
 import org.apache.ambari.server.state.ClientConfigFileDefinition;
@@ -71,6 +75,8 @@ public class StackManagerTest {
   private static ActionMetadata actionMetadata;
   private static OsFamily osFamily;
   private static StackDAO stackDao;
+  private static ExtensionDAO extensionDao;
+  private static ExtensionLinkDAO linkDao;
 
   @BeforeClass
   public static void initStack() throws Exception{
@@ -86,6 +92,8 @@ public class StackManagerTest {
     // todo: dao , actionMetaData expectations
     metaInfoDao = createNiceMock(MetainfoDAO.class);
     stackDao = createNiceMock(StackDAO.class);
+    extensionDao = createNiceMock(ExtensionDAO.class);
+    linkDao = createNiceMock(ExtensionLinkDAO.class);
     actionMetadata = createNiceMock(ActionMetadata.class);
     Configuration config = createNiceMock(Configuration.class);
 
@@ -96,8 +104,8 @@ public class StackManagerTest {
 
     osFamily = new OsFamily(config);
 
-    StackManager stackManager = new StackManager(new File(stackRoot), null,
-        osFamily, metaInfoDao, actionMetadata, stackDao);
+    StackManager stackManager = new StackManager(new File(stackRoot), null, null,
+        osFamily, metaInfoDao, actionMetadata, stackDao, extensionDao, linkDao);
 
     verify(config, metaInfoDao, stackDao, actionMetadata);
 
@@ -269,7 +277,7 @@ public class StackManagerTest {
     assertEquals("1.0", stack.getVersion());
     Collection<ServiceInfo> services = stack.getServices();
 
-    assertEquals(3, services.size());
+    assertEquals(4, services.size());
 
     // hdfs service
     assertEquals(6, stack.getService("HDFS").getComponents().size());
@@ -357,7 +365,7 @@ public class StackManagerTest {
     StackInfo baseStack = stackManager.getStack("OTHER", "1.0");
     StackInfo stack = stackManager.getStack("OTHER", "2.0");
 
-    assertEquals(4, stack.getServices().size());
+    assertEquals(5, stack.getServices().size());
 
     ServiceInfo service = stack.getService("SQOOP2");
     ServiceInfo baseSqoopService = baseStack.getService("SQOOP2");
@@ -423,6 +431,19 @@ public class StackManagerTest {
   }
 
   @Test
+  public void testPackageInheritance() throws Exception{
+    StackInfo stack = stackManager.getStack("HDP", "2.0.7");
+    assertNotNull(stack.getService("HBASE"));
+    ServiceInfo hbase = stack.getService("HBASE");
+    assertNotNull("Package dir is " + hbase.getServicePackageFolder(), hbase.getServicePackageFolder());
+
+    stack = stackManager.getStack("HDP", "2.0.8");
+    assertNotNull(stack.getService("HBASE"));
+    hbase = stack.getService("HBASE");
+    assertNotNull("Package dir is " + hbase.getServicePackageFolder(), hbase.getServicePackageFolder());
+  }
+
+  @Test
   public void testMonitoringServicePropertyInheritance() throws Exception{
     StackInfo stack = stackManager.getStack("HDP", "2.0.8");
     Collection<ServiceInfo> allServices = stack.getServices();
@@ -620,6 +641,9 @@ public class StackManagerTest {
     ArrayList<String> hbaseMasterStartValues = (ArrayList<String>) generalDeps.get("HBASE_MASTER-START");
     assertTrue(hbaseMasterStartValues.get(0).equals("ZOOKEEPER_SERVER-START"));
 
+    ServiceInfo service = stack.getService("PIG");
+    assertNotNull("PIG's roll command order is null", service.getRoleCommandOrder());
+
     assertTrue(optionalNoGlusterfs.containsKey("NAMENODE-STOP"));
     ArrayList<String> nameNodeStopValues = (ArrayList<String>) optionalNoGlusterfs.get("NAMENODE-STOP");
     assertTrue(nameNodeStopValues.contains("JOBTRACKER-STOP"));
@@ -629,6 +653,7 @@ public class StackManagerTest {
     ArrayList<String> customMasterStartValues = (ArrayList<String>) generalDeps.get("CUSTOM_MASTER-START");
     assertTrue(customMasterStartValues.contains("ZOOKEEPER_SERVER-START"));
     assertTrue(customMasterStartValues.contains("NAMENODE-START"));
+
   }
 
   @Test
@@ -644,9 +669,19 @@ public class StackManagerTest {
 
     String stackRoot = ClassLoader.getSystemClassLoader().getResource("stacks").getPath().replace("test-classes","classes");
     String commonServices = ClassLoader.getSystemClassLoader().getResource("common-services").getPath().replace("test-classes","classes");
+    File extensions = null;
+
+    try {
+	  URL extensionsURL = ClassLoader.getSystemClassLoader().getResource("extensions");
+      if (extensionsURL != null)
+        extensions = new File(extensionsURL.getPath().replace("test-classes","classes"));
+    }
+    catch (Exception e) {}
 
     MetainfoDAO metaInfoDao = createNiceMock(MetainfoDAO.class);
     StackDAO stackDao = createNiceMock(StackDAO.class);
+    ExtensionDAO extensionDao = createNiceMock(ExtensionDAO.class);
+    ExtensionLinkDAO linkDao = createNiceMock(ExtensionLinkDAO.class);
     ActionMetadata actionMetadata = createNiceMock(ActionMetadata.class);
     Configuration config = createNiceMock(Configuration.class);
 
@@ -657,8 +692,8 @@ public class StackManagerTest {
 
     OsFamily osFamily = new OsFamily(config);
 
-    StackManager stackManager = new StackManager(new File(stackRoot), new File(commonServices),
-            osFamily, metaInfoDao, actionMetadata, stackDao);
+    StackManager stackManager = new StackManager(new File(stackRoot), new File(commonServices), extensions,
+            osFamily, metaInfoDao, actionMetadata, stackDao, extensionDao, linkDao);
 
     for (StackInfo stackInfo : stackManager.getStacks()) {
       for (ServiceInfo serviceInfo : stackInfo.getServices()) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/test/java/org/apache/ambari/server/stack/ThemeModuleTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/stack/ThemeModuleTest.java b/ambari-server/src/test/java/org/apache/ambari/server/stack/ThemeModuleTest.java
index f4b4b93..f4f45a7 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/stack/ThemeModuleTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/stack/ThemeModuleTest.java
@@ -36,7 +36,7 @@ public class ThemeModuleTest {
     ThemeModule parentModule = new ThemeModule(parentThemeFile);
     ThemeModule childModule = new ThemeModule(childThemeFile);
 
-    childModule.resolve(parentModule, null, null);
+    childModule.resolve(parentModule, null, null, null);
 
     Theme childTheme = childModule.getModuleInfo().getThemeMap().get(ThemeModule.THEME_KEY);
     Theme parentTheme = parentModule.getModuleInfo().getThemeMap().get(ThemeModule.THEME_KEY);