ambari-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From alejan...@apache.org
Subject [6/6] ambari git commit: AMBARI-12885. Dynamic stack extensions - install and upgrade support for custom services (Tim Thorpe via alejandro)
Date Mon, 20 Jun 2016 21:34:48 GMT
AMBARI-12885. Dynamic stack extensions - install and upgrade support for custom services (Tim Thorpe via alejandro)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/300a7e21
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/300a7e21
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/300a7e21

Branch: refs/heads/trunk
Commit: 300a7e21d109ef731af65dc8652e057065081bb2
Parents: 31dfc56
Author: Alejandro Fernandez <afernandez@hortonworks.com>
Authored: Mon Jun 20 12:19:40 2016 -0700
Committer: Alejandro Fernandez <afernandez@hortonworks.com>
Committed: Mon Jun 20 14:36:26 2016 -0700

----------------------------------------------------------------------
 ambari-server/conf/unix/ambari.properties       |   1 +
 ambari-server/src/main/assemblies/server.xml    |   4 +
 .../ExtensionLinkResourceDefinition.java        |  58 ++
 .../resources/ExtensionResourceDefinition.java  |  59 ++
 .../ExtensionVersionResourceDefinition.java     |  54 ++
 .../resources/ResourceInstanceFactoryImpl.java  |  12 +
 .../server/api/services/AmbariMetaInfo.java     |  42 +-
 .../api/services/ExtensionLinksService.java     | 101 ++++
 .../server/api/services/ExtensionsService.java  | 127 +++++
 .../server/api/services/StacksService.java      |  23 +
 .../server/configuration/Configuration.java     |   9 +
 .../controller/AmbariManagementController.java  |  49 +-
 .../AmbariManagementControllerImpl.java         | 270 ++++++++++
 .../server/controller/ExtensionLinkRequest.java |  86 +++
 .../controller/ExtensionLinkResponse.java       | 124 +++++
 .../server/controller/ExtensionRequest.java     |  43 ++
 .../server/controller/ExtensionResponse.java    |  61 +++
 .../controller/ExtensionVersionRequest.java     |  44 ++
 .../controller/ExtensionVersionResponse.java    | 101 ++++
 .../AbstractControllerResourceProvider.java     |   6 +
 .../server/controller/internal/Extension.java   | 282 ++++++++++
 .../internal/ExtensionLinkResourceProvider.java | 241 +++++++++
 .../internal/ExtensionResourceProvider.java     | 121 +++++
 .../ExtensionVersionResourceProvider.java       | 131 +++++
 .../ambari/server/controller/spi/Resource.java  |   6 +
 .../ambari/server/orm/dao/ExtensionDAO.java     | 168 ++++++
 .../ambari/server/orm/dao/ExtensionLinkDAO.java | 240 +++++++++
 .../server/orm/entities/ExtensionEntity.java    | 156 ++++++
 .../orm/entities/ExtensionLinkEntity.java       | 139 +++++
 .../apache/ambari/server/stack/BaseModule.java  |   4 +-
 .../server/stack/CommonServiceDirectory.java    |   7 +-
 .../ambari/server/stack/ComponentModule.java    |   3 +-
 .../server/stack/ConfigurationModule.java       |   3 +-
 .../ambari/server/stack/ExtensionDirectory.java | 196 +++++++
 .../ambari/server/stack/ExtensionHelper.java    | 167 ++++++
 .../ambari/server/stack/ExtensionModule.java    | 540 +++++++++++++++++++
 .../server/stack/ModuleFileUnmarshaller.java    |   4 +
 .../stack/QuickLinksConfigurationModule.java    |   2 +-
 .../ambari/server/stack/ServiceDirectory.java   | 114 +++-
 .../ambari/server/stack/ServiceModule.java      |  57 +-
 .../server/stack/StackDefinitionModule.java     |   3 +-
 .../ambari/server/stack/StackDirectory.java     |  32 --
 .../ambari/server/stack/StackManager.java       | 223 +++++++-
 .../server/stack/StackManagerFactory.java       |   3 +
 .../apache/ambari/server/stack/StackModule.java | 211 +++++++-
 .../server/stack/StackServiceDirectory.java     |   6 +-
 .../apache/ambari/server/stack/ThemeModule.java |   3 +-
 .../apache/ambari/server/state/ExtensionId.java | 160 ++++++
 .../ambari/server/state/ExtensionInfo.java      | 208 +++++++
 .../apache/ambari/server/state/ServiceInfo.java |  11 +
 .../apache/ambari/server/state/StackInfo.java   |  29 +
 .../state/stack/ExtensionMetainfoXml.java       | 204 +++++++
 .../server/state/stack/ServiceMetainfoXml.java  |   4 +
 .../server/upgrade/UpgradeCatalog240.java       |  53 +-
 .../main/resources/Ambari-DDL-Derby-CREATE.sql  |  26 +-
 .../main/resources/Ambari-DDL-MySQL-CREATE.sql  |  24 +-
 .../main/resources/Ambari-DDL-Oracle-CREATE.sql |  24 +-
 .../resources/Ambari-DDL-Postgres-CREATE.sql    |  24 +-
 .../Ambari-DDL-Postgres-EMBEDDED-CREATE.sql     |  26 +-
 .../resources/Ambari-DDL-SQLAnywhere-CREATE.sql |  24 +-
 .../resources/Ambari-DDL-SQLServer-CREATE.sql   |  24 +-
 .../src/main/resources/META-INF/persistence.xml |   2 +
 .../src/main/resources/extensions/README.txt    |  31 ++
 .../src/main/resources/key_properties.json      |  16 +-
 .../src/main/resources/properties.json          |  30 +-
 .../api/services/ExtensionsServiceTest.java     | 119 ++++
 .../internal/ExtensionResourceProviderTest.java |  91 ++++
 .../server/stack/ComponentModuleTest.java       |   2 +-
 .../QuickLinksConfigurationModuleTest.java      |   4 +-
 .../ambari/server/stack/ServiceModuleTest.java  |   2 +-
 .../stack/StackManagerCommonServicesTest.java   |  32 +-
 .../server/stack/StackManagerExtensionTest.java | 131 +++++
 .../server/stack/StackManagerMiscTest.java      |  47 +-
 .../ambari/server/stack/StackManagerTest.java   | 123 ++++-
 .../ambari/server/stack/ThemeModuleTest.java    |   2 +-
 .../server/upgrade/UpgradeCatalog240Test.java   |  43 +-
 .../resources/extensions/EXT/0.1/metainfo.xml   |  30 ++
 .../OOZIE2/configuration/oozie2-site.xml        | 245 +++++++++
 .../EXT/0.1/services/OOZIE2/metainfo.xml        | 110 ++++
 .../0.1/services/OOZIE2/package/dummy-script.py |  20 +
 .../EXT/0.1/services/PIG2/metainfo.xml          |  26 +
 .../resources/extensions/EXT/0.2/metainfo.xml   |  31 ++
 .../EXT/0.2/services/OOZIE2/metainfo.xml        | 110 ++++
 .../stacks/OTHER/1.0/services/PIG2/metainfo.xml |  30 ++
 .../stacks_with_extensions/HDP/0.1/metainfo.xml |  22 +
 .../HDP/0.1/repos/repoinfo.xml                  |  57 ++
 .../HDP/0.1/services/HDFS/metainfo.xml          |  46 ++
 .../HDP/0.1/services/MAPREDUCE/metainfo.xml     |  23 +
 .../HDP/0.1/services/PIG/metainfo.xml           |  26 +
 .../stacks_with_extensions/HDP/0.2/metainfo.xml |  22 +
 .../HDP/0.2/repos/repoinfo.xml                  |  57 ++
 .../HDP/0.2/services/HBASE/metainfo.xml         |  26 +
 .../0.2/services/HDFS/configuration/global.xml  | 145 +++++
 .../services/HDFS/configuration/hadoop-env.xml  | 223 ++++++++
 .../services/HDFS/configuration/hbase-site.xml  | 137 +++++
 .../services/HDFS/configuration/hdfs-log4j.xml  | 199 +++++++
 .../services/HDFS/configuration/hdfs-site.xml   | 396 ++++++++++++++
 .../HDP/0.2/services/HDFS/metainfo.xml          |  30 ++
 .../0.2/services/HDFS/package/dummy-script.py   |  20 +
 .../HDP/0.2/services/HIVE/metainfo.xml          |  26 +
 .../HDP/0.2/services/MAPREDUCE/metainfo.xml     |  23 +
 .../HDP/0.2/services/ZOOKEEPER/metainfo.xml     |  26 +
 102 files changed, 7755 insertions(+), 203 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/conf/unix/ambari.properties
----------------------------------------------------------------------
diff --git a/ambari-server/conf/unix/ambari.properties b/ambari-server/conf/unix/ambari.properties
index a88a025..abd72ad 100644
--- a/ambari-server/conf/unix/ambari.properties
+++ b/ambari-server/conf/unix/ambari.properties
@@ -43,6 +43,7 @@ jce.download.supported=true
 
 metadata.path=$ROOT/var/lib/ambari-server/resources/stacks
 common.services.path=$ROOT/var/lib/ambari-server/resources/common-services
+extensions.path=/var/lib/ambari-server/resources/extensions
 server.version.file=$ROOT/var/lib/ambari-server/resources/version
 webapp.dir=$ROOT/usr/lib/ambari-server/web
 pid.dir=$ROOT/var/run/ambari-server

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/assemblies/server.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/assemblies/server.xml b/ambari-server/src/main/assemblies/server.xml
index 1560d8d..2439e17 100644
--- a/ambari-server/src/main/assemblies/server.xml
+++ b/ambari-server/src/main/assemblies/server.xml
@@ -155,6 +155,10 @@
       <outputDirectory>/var/lib/ambari-server/resources/stacks/${stack.distribution}</outputDirectory>
     </fileSet>
     <fileSet>
+      <directory>src/main/resources/extensions</directory>
+      <outputDirectory>/var/lib/ambari-server/resources/extensions</outputDirectory>
+    </fileSet>
+    <fileSet>
       <directory>src/main/python/ambari_server</directory>
       <outputDirectory>/usr/lib/python2.6/site-packages/ambari_server</outputDirectory>
     </fileSet>

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ExtensionLinkResourceDefinition.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ExtensionLinkResourceDefinition.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ExtensionLinkResourceDefinition.java
new file mode 100644
index 0000000..f03f4f7
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ExtensionLinkResourceDefinition.java
@@ -0,0 +1,58 @@
+/**
+ * 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.resources;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.Resource.Type;
+
+/**
+ * An extension version is like a stack version but it contains custom services.  Linking an extension
+ * version to the current stack version allows the cluster to install the custom services contained in
+ * the extension version.
+ */
+public class ExtensionLinkResourceDefinition extends BaseResourceDefinition {
+
+  public ExtensionLinkResourceDefinition(Type resourceType) {
+    super(Resource.Type.ExtensionLink);
+  }
+
+  public ExtensionLinkResourceDefinition() {
+    super(Resource.Type.ExtensionLink);
+  }
+
+  @Override
+  public String getPluralName() {
+    return "links";
+  }
+
+  @Override
+  public String getSingularName() {
+    return "link";
+  }
+
+  @Override
+  public Set<SubResourceDefinition> getSubResourceDefinitions() {
+    Set<SubResourceDefinition> setChildren = new HashSet<SubResourceDefinition>();
+    return setChildren;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ExtensionResourceDefinition.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ExtensionResourceDefinition.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ExtensionResourceDefinition.java
new file mode 100644
index 0000000..bad78b9
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ExtensionResourceDefinition.java
@@ -0,0 +1,59 @@
+/**
+ * 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.resources;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.Resource.Type;
+
+/**
+ * An extension version is like a stack version but it contains custom services.  Linking an extension
+ * version to the current stack version allows the cluster to install the custom services contained in
+ * the extension version.
+ */
+public class ExtensionResourceDefinition extends BaseResourceDefinition {
+
+  public ExtensionResourceDefinition(Type resourceType) {
+    super(Resource.Type.Extension);
+  }
+
+  public ExtensionResourceDefinition() {
+    super(Resource.Type.Extension);
+  }
+
+  @Override
+  public String getPluralName() {
+    return "extensions";
+  }
+
+  @Override
+  public String getSingularName() {
+    return "extension";
+  }
+
+  @Override
+  public Set<SubResourceDefinition> getSubResourceDefinitions() {
+    Set<SubResourceDefinition> setChildren = new HashSet<SubResourceDefinition>();
+    setChildren.add(new SubResourceDefinition(Resource.Type.ExtensionVersion));
+    return setChildren;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ExtensionVersionResourceDefinition.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ExtensionVersionResourceDefinition.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ExtensionVersionResourceDefinition.java
new file mode 100644
index 0000000..824640a
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ExtensionVersionResourceDefinition.java
@@ -0,0 +1,54 @@
+/**
+ * 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.resources;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.ambari.server.controller.spi.Resource;
+
+/**
+ * An extension version is like a stack version but it contains custom services.  Linking an extension
+ * version to the current stack version allows the cluster to install the custom services contained in
+ * the extension version.
+ */
+public class ExtensionVersionResourceDefinition extends BaseResourceDefinition {
+
+  public ExtensionVersionResourceDefinition() {
+    super(Resource.Type.ExtensionVersion);
+  }
+
+  @Override
+  public String getPluralName() {
+    return "versions";
+  }
+
+  @Override
+  public String getSingularName() {
+    return "version";
+  }
+
+  @Override
+  public Set<SubResourceDefinition> getSubResourceDefinitions() {
+
+    Set<SubResourceDefinition> children = new HashSet<SubResourceDefinition>();
+
+    return children;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
index cf7c391..a2aeffa 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
@@ -187,6 +187,18 @@ public class ResourceInstanceFactoryImpl implements ResourceInstanceFactory {
         resourceDefinition = new StackConfigurationDependencyResourceDefinition();
         break;
 
+      case Extension:
+        resourceDefinition = new ExtensionResourceDefinition();
+        break;
+
+      case ExtensionVersion:
+        resourceDefinition = new ExtensionVersionResourceDefinition();
+        break;
+
+      case ExtensionLink:
+        resourceDefinition = new ExtensionLinkResourceDefinition();
+        break;
+
       case OperatingSystem:
         resourceDefinition = new OperatingSystemResourceDefinition();
         break;

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
index f0928cf..92d47df 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
@@ -63,6 +63,7 @@ import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.ComponentInfo;
 import org.apache.ambari.server.state.DependencyInfo;
+import org.apache.ambari.server.state.ExtensionInfo;
 import org.apache.ambari.server.state.OperatingSystemInfo;
 import org.apache.ambari.server.state.PropertyInfo;
 import org.apache.ambari.server.state.RepositoryInfo;
@@ -108,12 +109,12 @@ public class AmbariMetaInfo {
   public static final String SERVICE_ADVISOR_FILE_NAME = "service_advisor.py";
 
   /**
-   * The filename name for a Kerberos descriptor file at either the stack or service level
+   * The filename for a Kerberos descriptor file at either the stack or service level
    */
   public static final String KERBEROS_DESCRIPTOR_FILE_NAME = "kerberos.json";
 
   /**
-   * The filename name for a Widgets descriptor file at either the stack or service level
+   * The filename for a Widgets descriptor file at either the stack or service level
    */
   public static final String WIDGETS_DESCRIPTOR_FILE_NAME = "widgets.json";
 
@@ -156,6 +157,7 @@ public class AmbariMetaInfo {
 
   private File stackRoot;
   private File commonServicesRoot;
+  private File extensionsRoot;
   private File serverVersionFile;
   private File customActionRoot;
   private Map<String, VersionDefinitionXml> versionDefinitions = null;
@@ -237,6 +239,11 @@ public class AmbariMetaInfo {
       commonServicesRoot = new File(commonServicesPath);
     }
 
+    String extensionsPath = conf.getExtensionsPath();
+    if (extensionsPath != null && !extensionsPath.isEmpty()) {
+      extensionsRoot = new File(extensionsPath);
+    }
+
     String serverVersionFilePath = conf.getServerVersionFilePath();
     serverVersionFile = new File(serverVersionFilePath);
 
@@ -255,7 +262,7 @@ public class AmbariMetaInfo {
 
     readServerVersion();
 
-    stackManager = stackManagerFactory.create(stackRoot, commonServicesRoot,
+    stackManager = stackManagerFactory.create(stackRoot, commonServicesRoot, extensionsRoot,
         osFamily, false);
 
     getCustomActionDefinitions(customActionRoot);
@@ -629,6 +636,30 @@ public class AmbariMetaInfo {
     return parents;
   }
 
+  public Collection<ExtensionInfo> getExtensions() {
+    return stackManager.getExtensions();
+  }
+
+  public Collection<ExtensionInfo> getExtensions(String extensionName) throws AmbariException {
+    Collection<ExtensionInfo> extensions = stackManager.getExtensions(extensionName);
+
+    if (extensions.isEmpty()) {
+      throw new StackAccessException("extensionName=" + extensionName);
+    }
+
+    return extensions;
+  }
+
+  public ExtensionInfo getExtension(String extensionName, String version) throws AmbariException {
+    ExtensionInfo result = stackManager.getExtension(extensionName, version);
+
+    if (result == null) {
+      throw new StackAccessException("Extension " + extensionName + " " + version + " is not found in Ambari metainfo");
+    }
+
+    return result;
+  }
+
   public Set<PropertyInfo> getServiceProperties(String stackName, String version, String serviceName)
       throws AmbariException {
 
@@ -700,7 +731,6 @@ public class AmbariMetaInfo {
     return propertyResult;
   }
 
-
   /**
    * Lists operatingsystems supported by stack
    */
@@ -862,6 +892,10 @@ public class AmbariMetaInfo {
     return stackRoot;
   }
 
+  public File getExtensionsRoot() {
+    return extensionsRoot;
+  }
+
   /**
    * Return metrics for a stack service.
    */

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/api/services/ExtensionLinksService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ExtensionLinksService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ExtensionLinksService.java
new file mode 100644
index 0000000..4bacfbf
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ExtensionLinksService.java
@@ -0,0 +1,101 @@
+/**
+ * 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 java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+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 org.apache.ambari.server.api.resources.ResourceInstance;
+import org.apache.ambari.server.controller.spi.Resource;
+
+/**
+ * Service for extension link management.
+ *
+ * An extension version is like a stack version but it contains custom services.  Linking an extension
+ * version to the current stack version allows the cluster to install the custom services contained in
+ * the extension version.
+ */
+@Path("/links/")
+public class ExtensionLinksService extends BaseService {
+
+  @GET
+  @Produces("text/plain")
+  public Response getExtensionLinks(String body, @Context HttpHeaders headers, @Context UriInfo ui) {
+
+    return handleRequest(headers, body, ui, Request.Type.GET, createExtensionLinkResource(null));
+  }
+
+  @GET
+  @Path("{linkId}")
+  @Produces("text/plain")
+  public Response getExtensionLink(String body, @Context HttpHeaders headers,
+                                  @Context UriInfo ui, @PathParam("linkId") String linkId) {
+
+    return handleRequest(headers, body, ui, Request.Type.GET, createExtensionLinkResource(linkId));
+  }
+
+  @POST
+  @Produces("text/plain")
+  public Response createExtensionLink(String body, @Context HttpHeaders headers, @Context UriInfo ui) {
+    return handleRequest(headers, body, ui, Request.Type.POST, createExtensionLinkResource(null));
+  }
+
+  @DELETE
+  @Path("{linkId}")
+  @Produces("text/plain")
+  public Response deleteExtensionLink(@Context HttpHeaders headers, @Context UriInfo ui,
+                                @PathParam("linkId") String linkId) {
+
+    return handleRequest(headers, null, ui, Request.Type.DELETE, createExtensionLinkResource(linkId));
+  }
+
+  @PUT
+  @Produces("text/plain")
+  public Response updateExtensionLink(String body, @Context HttpHeaders headers, @Context UriInfo ui) {
+    return handleRequest(headers, body, ui, Request.Type.PUT, createExtensionLinkResource(null));
+  }
+
+  @PUT
+  @Path("{linkId}")
+  @Produces("text/plain")
+  public Response updateExtensionLink(String body, @Context HttpHeaders headers, @Context UriInfo ui,
+          @PathParam("linkId") String linkId) {
+
+    return handleRequest(headers, body, ui, Request.Type.PUT, createExtensionLinkResource(linkId));
+  }
+
+  ResourceInstance createExtensionLinkResource(String linkId) {
+    Map<Resource.Type, String> mapIds = new HashMap<Resource.Type, String>();
+    mapIds.put(Resource.Type.ExtensionLink, linkId);
+    return createResource(Resource.Type.ExtensionLink, mapIds);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/api/services/ExtensionsService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ExtensionsService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ExtensionsService.java
new file mode 100644
index 0000000..176c259
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ExtensionsService.java
@@ -0,0 +1,127 @@
+/**
+ * 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 java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+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 org.apache.ambari.server.api.resources.ResourceInstance;
+import org.apache.ambari.server.controller.spi.Resource;
+
+/**
+ * Service for extensions management.
+ *
+ * An extension version is like a stack version but it contains custom services.  Linking an extension
+ * version to the current stack version allows the cluster to install the custom services contained in
+ * the extension version.
+ */
+@Path("/extensions/")
+public class ExtensionsService extends BaseService {
+
+  @GET
+  @Produces("text/plain")
+  public Response getExtensions(String body, @Context HttpHeaders headers, @Context UriInfo ui) {
+
+    return handleRequest(headers, body, ui, Request.Type.GET,
+        createExtensionResource(null));
+  }
+
+  @GET
+  @Path("{extensionName}")
+  @Produces("text/plain")
+  public Response getExtension(String body, @Context HttpHeaders headers,
+                           @Context UriInfo ui,
+                           @PathParam("extensionName") String extensionName) {
+
+    return handleRequest(headers, body, ui, Request.Type.GET,
+        createExtensionResource(extensionName));
+  }
+
+  @GET
+  @Path("{extensionName}/versions")
+  @Produces("text/plain")
+  public Response getExtensionVersions(String body,
+                                   @Context HttpHeaders headers,
+                                   @Context UriInfo ui, @PathParam("extensionName") String extensionName) {
+
+    return handleRequest(headers, body, ui, Request.Type.GET,
+        createExtensionVersionResource(extensionName, null));
+  }
+
+  @GET
+  @Path("{extensionName}/versions/{extensionVersion}")
+  @Produces("text/plain")
+  public Response getExtensionVersion(String body,
+                                  @Context HttpHeaders headers,
+                                  @Context UriInfo ui, @PathParam("extensionName") String extensionName,
+                                  @PathParam("extensionVersion") String extensionVersion) {
+
+    return handleRequest(headers, body, ui, Request.Type.GET,
+        createExtensionVersionResource(extensionName, extensionVersion));
+  }
+
+  @GET
+  @Path("{extensionName}/versions/{extensionVersion}/links")
+  @Produces("text/plain")
+  public Response getExtensionVersionLinks(String body,
+                                  @Context HttpHeaders headers,
+                                  @Context UriInfo ui, @PathParam("extensionName") String extensionName,
+                                  @PathParam("extensionVersion") String extensionVersion) {
+
+    return handleRequest(headers, body, ui, Request.Type.GET,
+        createExtensionLinkResource(null, null, extensionName, extensionVersion));
+  }
+
+  ResourceInstance createExtensionVersionResource(String extensionName,
+                                              String extensionVersion) {
+    Map<Resource.Type, String> mapIds = new HashMap<Resource.Type, String>();
+    mapIds.put(Resource.Type.Extension, extensionName);
+    mapIds.put(Resource.Type.ExtensionVersion, extensionVersion);
+
+    return createResource(Resource.Type.ExtensionVersion, mapIds);
+  }
+
+  ResourceInstance createExtensionLinkResource(String stackName, String stackVersion,
+                                  String extensionName, String extensionVersion) {
+    Map<Resource.Type, String> mapIds = new HashMap<Resource.Type, String>();
+    mapIds.put(Resource.Type.Stack, stackName);
+    mapIds.put(Resource.Type.StackVersion, stackVersion);
+    mapIds.put(Resource.Type.Extension, extensionName);
+    mapIds.put(Resource.Type.ExtensionVersion, extensionVersion);
+
+    return createResource(Resource.Type.ExtensionLink, mapIds);
+  }
+
+  ResourceInstance createExtensionResource(String extensionName) {
+
+    return createResource(Resource.Type.Extension,
+        Collections.singletonMap(Resource.Type.Extension, extensionName));
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/api/services/StacksService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/StacksService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/StacksService.java
index 557ce98..2c85e01 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/StacksService.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/StacksService.java
@@ -83,6 +83,18 @@ public class StacksService extends BaseService {
   }
 
   @GET
+  @Path("{stackName}/versions/{stackVersion}/links")
+  @Produces("text/plain")
+  public Response getStackVersionLinks(String body,
+                                  @Context HttpHeaders headers,
+                                  @Context UriInfo ui, @PathParam("stackName") String stackName,
+                                  @PathParam("stackVersion") String stackVersion) {
+
+    return handleRequest(headers, body, ui, Request.Type.GET,
+        createExtensionLinkResource(stackName, stackVersion, null, null));
+  }
+
+  @GET
   @Path("{stackName}/versions/{stackVersion}/configurations")
   @Produces("text/plain")
   public Response getStackLevelConfigurations(String body, @Context HttpHeaders headers,
@@ -491,6 +503,17 @@ public class StacksService extends BaseService {
     return createResource(Resource.Type.QuickLink, mapIds);
   }
 
+  ResourceInstance createExtensionLinkResource(String stackName, String stackVersion,
+                                  String extensionName, String extensionVersion) {
+    Map<Resource.Type, String> mapIds = new HashMap<Resource.Type, String>();
+    mapIds.put(Resource.Type.Stack, stackName);
+    mapIds.put(Resource.Type.StackVersion, stackVersion);
+    mapIds.put(Resource.Type.Extension, extensionName);
+    mapIds.put(Resource.Type.ExtensionVersion, extensionVersion);
+
+    return createResource(Resource.Type.ExtensionLink, mapIds);
+  }
+
   ResourceInstance createStackResource(String stackName) {
 
     return createResource(Resource.Type.Stack,

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
index 2b7fca0..d736303 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
@@ -137,6 +137,7 @@ public class Configuration {
   public static final String RESOURCES_DIR_KEY = "resources.dir";
   public static final String METADATA_DIR_PATH = "metadata.path";
   public static final String COMMON_SERVICES_DIR_PATH = "common.services.path";
+  public static final String EXTENSIONS_DIR_PATH = "extensions.path";
   public static final String MPACKS_STAGING_DIR_PATH = "mpacks.staging.path";
   public static final String SERVER_VERSION_FILE = "server.version.file";
   public static final String SERVER_VERSION_KEY = "version";
@@ -1479,6 +1480,14 @@ public class Configuration {
   }
 
   /**
+   * Gets ambari extensions-path
+   * @return String
+   */
+  public String getExtensionsPath() {
+    return properties.getProperty(EXTENSIONS_DIR_PATH);
+  }
+
+  /**
    * Gets ambari management packs staging directory
    * @return String
    */

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java
index b488af3..947a9f4 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java
@@ -34,6 +34,7 @@ import org.apache.ambari.server.controller.metrics.timeline.cache.TimelineMetric
 import org.apache.ambari.server.events.AmbariEvent;
 import org.apache.ambari.server.events.publishers.AmbariEventPublisher;
 import org.apache.ambari.server.metadata.RoleCommandOrder;
+import org.apache.ambari.server.orm.entities.ExtensionLinkEntity;
 import org.apache.ambari.server.scheduler.ExecutionScheduleManager;
 import org.apache.ambari.server.security.authorization.AuthorizationException;
 import org.apache.ambari.server.security.encryption.CredentialStoreService;
@@ -356,6 +357,52 @@ public interface AmbariManagementController {
   RequestStatusResponse updateStacks() throws AmbariException;
 
   /**
+   * Create a link between an extension and a stack
+   *
+   * @throws AmbariException if we fail to link the extension to the stack
+   */
+  public void createExtensionLink(ExtensionLinkRequest request) throws AmbariException;
+
+  /**
+   * Update a link between an extension and a stack
+   *
+   * @throws AmbariException if we fail to link the extension to the stack
+   */
+  public void updateExtensionLink(ExtensionLinkRequest request) throws AmbariException;
+
+  /**
+   * Update a link between an extension and a stack
+   *
+   * @throws AmbariException if we fail to link the extension to the stack
+   */
+  public void updateExtensionLink(ExtensionLinkEntity linkEntity) throws AmbariException;
+
+  /**
+   * Delete a link between an extension and a stack
+   *
+   * @throws AmbariException if we fail to unlink the extension from the stack
+   */
+  public void deleteExtensionLink(ExtensionLinkRequest request) throws AmbariException;
+
+  /**
+   * Get supported extensions.
+   *
+   * @param requests the extensions
+   * @return a set of extensions responses
+   * @throws  AmbariException if the resources cannot be read
+   */
+  public Set<ExtensionResponse> getExtensions(Set<ExtensionRequest> requests) throws AmbariException;
+
+  /**
+   * Get supported extension versions.
+   *
+   * @param requests the extension versions
+   * @return a set of extension versions responses
+   * @throws  AmbariException if the resources cannot be read
+   */
+  public Set<ExtensionVersionResponse> getExtensionVersions(Set<ExtensionVersionRequest> requests) throws AmbariException;
+
+  /**
    * Get supported stacks versions.
    *
    * @param requests the stacks versions
@@ -366,7 +413,6 @@ public interface AmbariManagementController {
    */
   Set<StackVersionResponse> getStackVersions(Set<StackVersionRequest> requests) throws AmbariException;
 
-
   /**
    * Get repositories by stack name, version and operating system.
    *
@@ -431,7 +477,6 @@ public interface AmbariManagementController {
    */
   Set<StackServiceComponentResponse> getStackComponents(Set<StackServiceComponentRequest> requests) throws AmbariException;
 
-
   /**
    * Get operating systems by stack name, version.
    *

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
index b5c6fc2..7dbb1ad 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
@@ -65,6 +65,8 @@ import java.util.TreeMap;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Lock;
 
+import javax.persistence.RollbackException;
+
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.ClusterNotFoundException;
 import org.apache.ambari.server.DuplicateResourceException;
@@ -102,14 +104,20 @@ import org.apache.ambari.server.metadata.ActionMetadata;
 import org.apache.ambari.server.metadata.RoleCommandOrder;
 import org.apache.ambari.server.orm.dao.ClusterDAO;
 import org.apache.ambari.server.orm.dao.ClusterVersionDAO;
+import org.apache.ambari.server.orm.dao.ExtensionDAO;
+import org.apache.ambari.server.orm.dao.ExtensionLinkDAO;
 import org.apache.ambari.server.orm.dao.RepositoryVersionDAO;
+import org.apache.ambari.server.orm.dao.StackDAO;
 import org.apache.ambari.server.orm.dao.WidgetDAO;
 import org.apache.ambari.server.orm.dao.WidgetLayoutDAO;
 import org.apache.ambari.server.orm.entities.ClusterEntity;
 import org.apache.ambari.server.orm.entities.ClusterVersionEntity;
+import org.apache.ambari.server.orm.entities.ExtensionEntity;
+import org.apache.ambari.server.orm.entities.ExtensionLinkEntity;
 import org.apache.ambari.server.orm.entities.OperatingSystemEntity;
 import org.apache.ambari.server.orm.entities.RepositoryEntity;
 import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
+import org.apache.ambari.server.orm.entities.StackEntity;
 import org.apache.ambari.server.orm.entities.WidgetEntity;
 import org.apache.ambari.server.orm.entities.WidgetLayoutEntity;
 import org.apache.ambari.server.orm.entities.WidgetLayoutUserWidgetEntity;
@@ -129,6 +137,8 @@ import org.apache.ambari.server.security.ldap.LdapBatchDto;
 import org.apache.ambari.server.security.ldap.LdapSyncDto;
 import org.apache.ambari.server.serveraction.kerberos.KerberosInvalidConfigurationException;
 import org.apache.ambari.server.serveraction.kerberos.KerberosOperationException;
+import org.apache.ambari.server.stack.ExtensionHelper;
+import org.apache.ambari.server.stack.StackManager;
 import org.apache.ambari.server.stageplanner.RoleGraph;
 import org.apache.ambari.server.stageplanner.RoleGraphFactory;
 import org.apache.ambari.server.state.Cluster;
@@ -139,6 +149,7 @@ import org.apache.ambari.server.state.Config;
 import org.apache.ambari.server.state.ConfigFactory;
 import org.apache.ambari.server.state.ConfigHelper;
 import org.apache.ambari.server.state.DesiredConfig;
+import org.apache.ambari.server.state.ExtensionInfo;
 import org.apache.ambari.server.state.Host;
 import org.apache.ambari.server.state.HostComponentAdminState;
 import org.apache.ambari.server.state.HostState;
@@ -166,6 +177,7 @@ import org.apache.ambari.server.state.configgroup.ConfigGroupFactory;
 import org.apache.ambari.server.state.repository.VersionDefinitionXml;
 import org.apache.ambari.server.state.scheduler.RequestExecutionFactory;
 import org.apache.ambari.server.state.stack.RepositoryXml;
+import org.apache.ambari.server.state.stack.ServiceMetainfoXml;
 import org.apache.ambari.server.state.stack.WidgetLayout;
 import org.apache.ambari.server.state.stack.WidgetLayoutInfo;
 import org.apache.ambari.server.state.svccomphost.ServiceComponentHostInstallEvent;
@@ -271,6 +283,13 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
 
   private MaintenanceStateHelper maintenanceStateHelper;
 
+  @Inject
+  private ExtensionLinkDAO linkDAO;
+  @Inject
+  private ExtensionDAO extensionDAO;
+  @Inject
+  private StackDAO stackDAO;
+
   /**
    * The KerberosHelper to help setup for enabling for disabling Kerberos
    */
@@ -3783,6 +3802,94 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
   }
 
   @Override
+  public Set<ExtensionResponse> getExtensions(Set<ExtensionRequest> requests)
+      throws AmbariException {
+    Set<ExtensionResponse> response = new HashSet<ExtensionResponse>();
+    for (ExtensionRequest request : requests) {
+      try {
+        response.addAll(getExtensions(request));
+      } catch (StackAccessException e) {
+        if (requests.size() == 1) {
+          // only throw exception if 1 request.
+          // there will be > 1 request in case of OR predicate
+          throw e;
+        }
+      }
+    }
+    return response;
+
+  }
+
+
+  private Set<ExtensionResponse> getExtensions(ExtensionRequest request)
+      throws AmbariException {
+    Set<ExtensionResponse> response;
+
+    String extensionName = request.getExtensionName();
+
+    if (extensionName != null) {
+      // this will throw an exception if the extension doesn't exist
+      ambariMetaInfo.getExtensions(extensionName);
+      response = Collections.singleton(new ExtensionResponse(extensionName));
+    } else {
+      Collection<ExtensionInfo> supportedExtensions = ambariMetaInfo.getExtensions();
+      response = new HashSet<ExtensionResponse>();
+      for (ExtensionInfo extension: supportedExtensions) {
+        response.add(new ExtensionResponse(extension.getName()));
+      }
+    }
+    return response;
+  }
+
+  @Override
+  public Set<ExtensionVersionResponse> getExtensionVersions(
+      Set<ExtensionVersionRequest> requests) throws AmbariException {
+    Set<ExtensionVersionResponse> response = new HashSet<ExtensionVersionResponse>();
+    for (ExtensionVersionRequest request : requests) {
+      String extensionName = request.getExtensionName();
+      try {
+        Set<ExtensionVersionResponse> stackVersions = getExtensionVersions(request);
+        for (ExtensionVersionResponse stackVersionResponse : stackVersions) {
+          stackVersionResponse.setExtensionName(extensionName);
+        }
+        response.addAll(stackVersions);
+      } catch (StackAccessException e) {
+        if (requests.size() == 1) {
+          // only throw exception if 1 request.
+          // there will be > 1 request in case of OR predicate
+          throw e;
+        }
+      }
+    }
+
+    return response;
+  }
+
+  private Set<ExtensionVersionResponse> getExtensionVersions(ExtensionVersionRequest request) throws AmbariException {
+    Set<ExtensionVersionResponse> response;
+
+    String extensionName = request.getExtensionName();
+    String extensionVersion = request.getExtensionVersion();
+
+    if (extensionVersion != null) {
+      ExtensionInfo extensionInfo = ambariMetaInfo.getExtension(extensionName, extensionVersion);
+      response = Collections.singleton(extensionInfo.convertToResponse());
+    } else {
+      try {
+        Collection<ExtensionInfo> extensionInfos = ambariMetaInfo.getExtensions(extensionName);
+        response = new HashSet<ExtensionVersionResponse>();
+        for (ExtensionInfo extensionInfo: extensionInfos) {
+          response.add(extensionInfo.convertToResponse());
+        }
+      } catch (StackAccessException e) {
+        response = Collections.emptySet();
+      }
+    }
+
+    return response;
+  }
+
+  @Override
   public Set<RepositoryResponse> getRepositories(Set<RepositoryRequest> requests)
       throws AmbariException {
     Set<RepositoryResponse> response = new HashSet<RepositoryResponse>();
@@ -4848,4 +4955,167 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
       }
     }
   }
+
+  /**
+   * This method will delete a link between an extension version and a stack version (Extension Link).
+   *
+   * An extension version is like a stack version but it contains custom services.  Linking an extension
+   * version to the current stack version allows the cluster to install the custom services contained in
+   * the extension version.
+   */
+  @Override
+  public void deleteExtensionLink(ExtensionLinkRequest request) throws AmbariException {
+    if (request.getLinkId() == null) {
+      throw new IllegalArgumentException("Link ID should be provided");
+    }
+    ExtensionLinkEntity linkEntity = null;
+    try {
+      linkEntity = linkDAO.findById(new Long(request.getLinkId()));
+    } catch (RollbackException e) {
+      throw new AmbariException("Unable to find extension link"
+            + ", linkId=" + request.getLinkId(), e);
+    }
+
+    StackInfo stackInfo = ambariMetaInfo.getStack(linkEntity.getStack().getStackName(), linkEntity.getStack().getStackVersion());
+
+    if (stackInfo == null)
+      throw new StackAccessException("stackName=" + linkEntity.getStack().getStackName() + ", stackVersion=" + linkEntity.getStack().getStackVersion());
+
+    ExtensionInfo extensionInfo = ambariMetaInfo.getExtension(linkEntity.getExtension().getExtensionName(), linkEntity.getExtension().getExtensionVersion());
+
+    if (extensionInfo == null)
+      throw new StackAccessException("extensionName=" + linkEntity.getExtension().getExtensionName() + ", extensionVersion=" + linkEntity.getExtension().getExtensionVersion());
+
+    ExtensionHelper.validateDeleteLink(getClusters(), stackInfo, extensionInfo);
+    ambariMetaInfo.getStackManager().unlinkStackAndExtension(stackInfo, extensionInfo);
+
+    try {
+      linkDAO.remove(linkEntity);
+    } catch (RollbackException e) {
+      throw new AmbariException("Unable to delete extension link"
+              + ", linkId=" + request.getLinkId()
+              + ", stackName=" + request.getStackName()
+              + ", stackVersion=" + request.getStackVersion()
+              + ", extensionName=" + request.getExtensionName()
+              + ", extensionVersion=" + request.getExtensionVersion(), e);
+    }
+  }
+
+  /**
+   * This method will create a link between an extension version and a stack version (Extension Link).
+   *
+   * An extension version is like a stack version but it contains custom services.  Linking an extension
+   * version to the current stack version allows the cluster to install the custom services contained in
+   * the extension version.
+   */
+  @Override
+  public void createExtensionLink(ExtensionLinkRequest request) throws AmbariException {
+    validateCreateExtensionLinkRequest(request);
+
+    StackInfo stackInfo = ambariMetaInfo.getStack(request.getStackName(), request.getStackVersion());
+
+    if (stackInfo == null)
+      throw new StackAccessException("stackName=" + request.getStackName() + ", stackVersion=" + request.getStackVersion());
+
+    ExtensionInfo extensionInfo = ambariMetaInfo.getExtension(request.getExtensionName(), request.getExtensionVersion());
+
+    if (extensionInfo == null)
+      throw new StackAccessException("extensionName=" + request.getExtensionName() + ", extensionVersion=" + request.getExtensionVersion());
+
+    ExtensionHelper.validateCreateLink(stackInfo, extensionInfo);
+    ExtensionLinkEntity linkEntity = createExtensionLinkEntity(request);
+    ambariMetaInfo.getStackManager().linkStackToExtension(stackInfo, extensionInfo);
+
+    try {
+      linkDAO.create(linkEntity);
+      linkEntity = linkDAO.merge(linkEntity);
+    } catch (RollbackException e) {
+      String message = "Unable to create extension link";
+      LOG.debug(message, e);
+      String errorMessage = message
+              + ", stackName=" + request.getStackName()
+              + ", stackVersion=" + request.getStackVersion()
+              + ", extensionName=" + request.getExtensionName()
+              + ", extensionVersion=" + request.getExtensionVersion();
+      LOG.warn(errorMessage);
+      throw new AmbariException(errorMessage, e);
+    }
+  }
+
+  /**
+   * This method will update a link between an extension version and a stack version (Extension Link).
+   * Updating will only force ambari server to reread the stack and extension directories.
+   *
+   * An extension version is like a stack version but it contains custom services.  Linking an extension
+   * version to the current stack version allows the cluster to install the custom services contained in
+   * the extension version.
+   */
+  @Override
+  public void updateExtensionLink(ExtensionLinkRequest request) throws AmbariException {
+    if (request.getLinkId() == null) {
+      throw new AmbariException("Link ID should be provided");
+    }
+    ExtensionLinkEntity linkEntity = null;
+    try {
+      linkEntity = linkDAO.findById(new Long(request.getLinkId()));
+    } catch (RollbackException e) {
+      throw new AmbariException("Unable to find extension link"
+            + ", linkId=" + request.getLinkId(), e);
+    }
+    updateExtensionLink(linkEntity);
+  }
+
+  /**
+   * This method will update a link between an extension version and a stack version (Extension Link).
+   * Updating will only force ambari server to reread the stack and extension directories.
+   *
+   * An extension version is like a stack version but it contains custom services.  Linking an extension
+   * version to the current stack version allows the cluster to install the custom services contained in
+   * the extension version.
+   */
+  @Override
+  public void updateExtensionLink(ExtensionLinkEntity linkEntity) throws AmbariException {
+    StackInfo stackInfo = ambariMetaInfo.getStack(linkEntity.getStack().getStackName(), linkEntity.getStack().getStackVersion());
+
+    if (stackInfo == null)
+      throw new StackAccessException("stackName=" + linkEntity.getStack().getStackName() + ", stackVersion=" + linkEntity.getStack().getStackVersion());
+
+    ExtensionInfo extensionInfo = ambariMetaInfo.getExtension(linkEntity.getExtension().getExtensionName(), linkEntity.getExtension().getExtensionVersion());
+
+    if (extensionInfo == null)
+      throw new StackAccessException("extensionName=" + linkEntity.getExtension().getExtensionName() + ", extensionVersion=" + linkEntity.getExtension().getExtensionVersion());
+
+    ambariMetaInfo.getStackManager().linkStackToExtension(stackInfo, extensionInfo);
+  }
+
+  private void validateCreateExtensionLinkRequest(ExtensionLinkRequest request) throws AmbariException {
+    if (request.getStackName() == null
+            || request.getStackVersion() == null
+            || request.getExtensionName() == null
+            || request.getExtensionVersion() == null) {
+
+      throw new IllegalArgumentException("Stack name, stack version, extension name and extension version should be provided");
+    }
+
+    ExtensionLinkEntity entity = linkDAO.findByStackAndExtension(request.getStackName(), request.getStackVersion(),
+            request.getExtensionName(), request.getExtensionVersion());
+
+    if (entity != null) {
+      throw new AmbariException("The stack and extension are already linked"
+                + ", stackName=" + request.getStackName()
+                + ", stackVersion=" + request.getStackVersion()
+                + ", extensionName=" + request.getExtensionName()
+                + ", extensionVersion=" + request.getExtensionVersion());
+    }
+  }
+
+  private ExtensionLinkEntity createExtensionLinkEntity(ExtensionLinkRequest request) throws AmbariException {
+    StackEntity stack = stackDAO.find(request.getStackName(), request.getStackVersion());
+    ExtensionEntity extension = extensionDAO.find(request.getExtensionName(), request.getExtensionVersion());
+
+    ExtensionLinkEntity linkEntity = new ExtensionLinkEntity();
+    linkEntity.setStack(stack);
+    linkEntity.setExtension(extension);
+    return linkEntity;
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionLinkRequest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionLinkRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionLinkRequest.java
new file mode 100644
index 0000000..2c61de0
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionLinkRequest.java
@@ -0,0 +1,86 @@
+/**
+ * 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;
+
+/**
+ * An extension version is like a stack version but it contains custom services.  Linking an extension
+ * version to the current stack version allows the cluster to install the custom services contained in
+ * the extension version.
+ */
+public class ExtensionLinkRequest {
+
+  private String linkId;
+
+  private String stackName;
+
+  private String stackVersion;
+
+  private String extensionName;
+
+  private String extensionVersion;
+
+  public ExtensionLinkRequest(String linkId, String stackName, String stackVersion, String extensionName, String extensionVersion) {
+    this.setLinkId(linkId);
+    this.setStackName(stackName);
+    this.setStackVersion(stackVersion);
+    this.setExtensionName(extensionName);
+    this.setExtensionVersion(extensionVersion);
+  }
+
+  public String getLinkId() {
+    return linkId;
+  }
+
+  public void setLinkId(String linkId) {
+    this.linkId = linkId;
+  }
+
+  public String getStackName() {
+    return stackName;
+  }
+
+  public void setStackName(String stackName) {
+    this.stackName = stackName;
+  }
+
+  public String getStackVersion() {
+    return stackVersion;
+  }
+
+  public void setStackVersion(String stackVersion) {
+    this.stackVersion = stackVersion;
+  }
+
+  public String getExtensionName() {
+    return extensionName;
+  }
+
+  public void setExtensionName(String extensionName) {
+    this.extensionName = extensionName;
+  }
+
+  public String getExtensionVersion() {
+    return extensionVersion;
+  }
+
+  public void setExtensionVersion(String extensionVersion) {
+    this.extensionVersion = extensionVersion;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionLinkResponse.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionLinkResponse.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionLinkResponse.java
new file mode 100644
index 0000000..df262b8
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionLinkResponse.java
@@ -0,0 +1,124 @@
+/**
+ * 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;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.ambari.server.stack.Validable;
+
+/**
+ * An extension version is like a stack version but it contains custom services.  Linking an extension
+ * version to the current stack version allows the cluster to install the custom services contained in
+ * the extension version.
+ */
+public class ExtensionLinkResponse implements Validable {
+
+  private String linkId;
+
+  private String stackName;
+
+  private String stackVersion;
+
+  private String extensionName;
+
+  private String extensionVersion;
+
+  private boolean valid;
+
+  private Set<String> errorSet = new HashSet<String>();
+
+  public ExtensionLinkResponse(String linkId, String stackName, String stackVersion, String extensionName,
+                              String extensionVersion, boolean valid, Collection errorSet) {
+
+    setLinkId(linkId);
+    setStackName(stackName);
+    setStackVersion(stackVersion);
+    setExtensionName(extensionName);
+    setExtensionVersion(extensionVersion);
+    setValid(valid);
+    addErrors(errorSet);
+  }
+
+  public String getLinkId() {
+    return linkId;
+  }
+
+  public void setLinkId(String linkId) {
+    this.linkId = linkId;
+  }
+
+  public String getStackName() {
+    return stackName;
+  }
+
+  public void setStackName(String stackName) {
+    this.stackName = stackName;
+  }
+
+  public String getStackVersion() {
+    return stackVersion;
+  }
+
+  public void setStackVersion(String stackVersion) {
+    this.stackVersion = stackVersion;
+  }
+
+  public String getExtensionName() {
+    return extensionName;
+  }
+
+  public void setExtensionName(String extensionName) {
+    this.extensionName = extensionName;
+  }
+
+  public String getExtensionVersion() {
+    return extensionVersion;
+  }
+
+  public void setExtensionVersion(String extensionVersion) {
+    this.extensionVersion = extensionVersion;
+  }
+
+  @Override
+  public boolean isValid() {
+    return valid;
+  }
+
+  @Override
+  public void setValid(boolean valid) {
+    this.valid = valid;
+  }
+
+  @Override
+  public void addError(String error) {
+    errorSet.add(error);
+  }
+
+  @Override
+  public Collection<String> getErrors() {
+    return errorSet;
+  }
+
+  @Override
+  public void addErrors(Collection<String> errors) {
+    this.errorSet.addAll(errors);
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionRequest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionRequest.java
new file mode 100644
index 0000000..f825c7b
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionRequest.java
@@ -0,0 +1,43 @@
+/**
+ * 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;
+
+/**
+ * An extension version is like a stack version but it contains custom services.  Linking an extension
+ * version to the current stack version allows the cluster to install the custom services contained in
+ * the extension version.
+ */
+public class ExtensionRequest {
+
+  public ExtensionRequest(String extensionName) {
+    this.setExtensionName(extensionName);
+
+  }
+
+  public String getExtensionName() {
+    return extensionName;
+  }
+
+  public void setExtensionName(String extensionName) {
+    this.extensionName = extensionName;
+  }
+
+  private String extensionName;
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionResponse.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionResponse.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionResponse.java
new file mode 100644
index 0000000..50ab439
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionResponse.java
@@ -0,0 +1,61 @@
+/**
+ * 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;
+
+/**
+ * An extension version is like a stack version but it contains custom services.  Linking an extension
+ * version to the current stack version allows the cluster to install the custom services contained in
+ * the extension version.
+ */
+public class ExtensionResponse {
+
+  private String extensionName;
+
+  public ExtensionResponse(String extensionName) {
+    setExtensionName(extensionName);
+  }
+
+  public String getExtensionName() {
+    return extensionName;
+  }
+
+  public void setExtensionName(String extensionName) {
+    this.extensionName = extensionName;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = 1;
+    result = 31 + getExtensionName().hashCode();
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (!(obj instanceof ExtensionResponse)) {
+      return false;
+    }
+    if (this == obj) {
+      return true;
+    }
+    ExtensionResponse extensionResponse = (ExtensionResponse) obj;
+    return getExtensionName().equals(extensionResponse.getExtensionName());
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionVersionRequest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionVersionRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionVersionRequest.java
new file mode 100644
index 0000000..32228f7
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionVersionRequest.java
@@ -0,0 +1,44 @@
+/**
+ * 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;
+
+/**
+ * An extension version is like a stack version but it contains custom services.  Linking an extension
+ * version to the current stack version allows the cluster to install the custom services contained in
+ * the extension version.
+ */
+public class ExtensionVersionRequest extends ExtensionRequest {
+
+  public ExtensionVersionRequest(String extensionName, String extensionVersion) {
+    super(extensionName);
+    setExtensionVersion(extensionVersion);
+  }
+
+  public String getExtensionVersion() {
+    return extensionVersion;
+  }
+
+  public void setExtensionVersion(String extensionVersion) {
+    this.extensionVersion = extensionVersion;
+  }
+
+  private String extensionVersion;
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionVersionResponse.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionVersionResponse.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionVersionResponse.java
new file mode 100644
index 0000000..bf2db5c
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/ExtensionVersionResponse.java
@@ -0,0 +1,101 @@
+/**
+ * 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;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ambari.server.stack.Validable;
+
+/**
+ * An extension version is like a stack version but it contains custom services.  Linking an extension
+ * version to the current stack version allows the cluster to install the custom services contained in
+ * the extension version.
+ */
+public class ExtensionVersionResponse implements Validable{
+
+  private String extensionName;
+  private String extensionVersion;
+  private boolean valid;
+  private String parentVersion;
+
+  public ExtensionVersionResponse(String extensionVersion, String parentVersion,
+                              boolean valid, Collection errorSet) {
+    setExtensionVersion(extensionVersion);
+    setParentVersion(parentVersion);
+    setValid(valid);
+    addErrors(errorSet);
+  }
+
+  @Override
+  public boolean isValid() {
+    return valid;
+  }
+
+  @Override
+  public void setValid(boolean valid) {
+    this.valid = valid;
+  }
+
+  private Set<String> errorSet = new HashSet<String>();
+
+  @Override
+  public void addError(String error) {
+    errorSet.add(error);
+  }
+
+  @Override
+  public Collection<String> getErrors() {
+    return errorSet;
+  }
+
+  @Override
+  public void addErrors(Collection<String> errors) {
+    this.errorSet.addAll(errors);
+  }
+
+
+  public String getExtensionName() {
+    return extensionName;
+  }
+
+  public void setExtensionName(String extensionName) {
+    this.extensionName = extensionName;
+  }
+
+  public String getExtensionVersion() {
+    return extensionVersion;
+  }
+
+  public void setExtensionVersion(String extensionVersion) {
+    this.extensionVersion = extensionVersion;
+  }
+
+  public String getParentVersion() {
+    return parentVersion;
+  }
+
+  public void setParentVersion(String parentVersion) {
+    this.parentVersion = parentVersion;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
index 3721113..b26814a 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractControllerResourceProvider.java
@@ -173,6 +173,12 @@ public abstract class AbstractControllerResourceProvider extends AbstractAuthori
         return new StackConfigurationDependencyResourceProvider(propertyIds, keyPropertyIds, managementController);
       case StackLevelConfiguration:
         return new StackLevelConfigurationResourceProvider(propertyIds, keyPropertyIds, managementController);
+      case ExtensionLink:
+          return new ExtensionLinkResourceProvider(propertyIds, keyPropertyIds, managementController);
+      case Extension:
+        return new ExtensionResourceProvider(propertyIds, keyPropertyIds, managementController);
+      case ExtensionVersion:
+        return new ExtensionVersionResourceProvider(propertyIds, keyPropertyIds, managementController);
       case RootService:
         return new RootServiceResourceProvider(propertyIds, keyPropertyIds, managementController);
       case RootServiceComponent:

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/Extension.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/Extension.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/Extension.java
new file mode 100644
index 0000000..682adb8
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/Extension.java
@@ -0,0 +1,282 @@
+/**
+ * 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 java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.orm.entities.ExtensionEntity;
+import org.apache.ambari.server.state.AutoDeployInfo;
+import org.apache.ambari.server.state.ComponentInfo;
+import org.apache.ambari.server.state.DependencyInfo;
+import org.apache.ambari.server.state.PropertyInfo;
+import org.apache.ambari.server.topology.Cardinality;
+import org.apache.ambari.server.topology.Configuration;
+
+/**
+ * Encapsulates extension information.
+ *
+ * An extension version is like a stack version but it contains custom services.  Linking an extension
+ * version to the current stack version allows the cluster to install the custom services contained in
+ * the extension version.
+ */
+public class Extension {
+  /**
+   * Extension name
+   */
+  private String name;
+
+  /**
+   * Extension version
+   */
+  private String version;
+
+  /**
+   * Map of service name to components
+   */
+  private Map<String, Collection<String>> serviceComponents =
+      new HashMap<String, Collection<String>>();
+
+  /**
+   * Map of component to service
+   */
+  private Map<String, String> componentService = new HashMap<String, String>();
+
+  /**
+   * Map of component to dependencies
+   */
+  private Map<String, Collection<DependencyInfo>> dependencies =
+      new HashMap<String, Collection<DependencyInfo>>();
+
+  /**
+   * Map of dependency to conditional service
+   */
+  private Map<DependencyInfo, String> dependencyConditionalServiceMap =
+      new HashMap<DependencyInfo, String>();
+
+  /**
+   * Map of database component name to configuration property which indicates whether
+   * the database in to be managed or if it is an external non-managed instance.
+   * If the value of the config property starts with 'New', the database is determined
+   * to be managed, otherwise it is non-managed.
+   */
+  private Map<String, String> dbDependencyInfo = new HashMap<String, String>();
+
+  /**
+   * Map of component to required cardinality
+   */
+  private Map<String, String> cardinalityRequirements = new HashMap<String, String>();
+
+  /**
+   * Map of component to auto-deploy information
+   */
+  private Map<String, AutoDeployInfo> componentAutoDeployInfo =
+      new HashMap<String, AutoDeployInfo>();
+
+  /**
+   * Ambari Management Controller, used to obtain Extension definitions
+   */
+  private final AmbariManagementController controller;
+
+
+  /**
+   * Constructor.
+   *
+   * @param extension
+   *          the extension (not {@code null}).
+   * @param ambariManagementController
+   *          the management controller (not {@code null}).
+   * @throws AmbariException
+   */
+  public Extension(ExtensionEntity extension, AmbariManagementController ambariManagementController) throws AmbariException {
+    this(extension.getExtensionName(), extension.getExtensionVersion(), ambariManagementController);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param name     extension name
+   * @param version  extension version
+   *
+   * @throws AmbariException an exception occurred getting extension information
+   *                         for the specified name and version
+   */
+  //todo: don't pass management controller in constructor
+  public Extension(String name, String version, AmbariManagementController controller) throws AmbariException {
+    this.name = name;
+    this.version = version;
+    this.controller = controller;
+  }
+
+  /**
+   * Obtain extension name.
+   *
+   * @return extension name
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * Obtain extension version.
+   *
+   * @return extension version
+   */
+  public String getVersion() {
+    return version;
+  }
+
+
+  Map<DependencyInfo, String> getDependencyConditionalServiceMap() {
+    return dependencyConditionalServiceMap;
+  }
+
+  /**
+   * Get services contained in the extension.
+   *
+   * @return collection of all services for the extension
+   */
+  public Collection<String> getServices() {
+    return serviceComponents.keySet();
+  }
+
+  /**
+   * Get components contained in the extension for the specified service.
+   *
+   * @param service  service name
+   *
+   * @return collection of component names for the specified service
+   */
+  public Collection<String> getComponents(String service) {
+    return serviceComponents.get(service);
+  }
+
+  /**
+   * Get all service components
+   *
+   * @return map of service to associated components
+   */
+  public Map<String, Collection<String>> getComponents() {
+    Map<String, Collection<String>> serviceComponents = new HashMap<String, Collection<String>>();
+    for (String service : getServices()) {
+      Collection<String> components = new HashSet<String>();
+      components.addAll(getComponents(service));
+      serviceComponents.put(service, components);
+    }
+    return serviceComponents;
+  }
+
+  /**
+   * Get info for the specified component.
+   *
+   * @param component  component name
+   *
+   * @return component information for the requested component
+   *         or null if the component doesn't exist in the extension
+   */
+  public ComponentInfo getComponentInfo(String component) {
+    ComponentInfo componentInfo = null;
+    String service = getServiceForComponent(component);
+    if (service != null) {
+      try {
+        componentInfo = controller.getAmbariMetaInfo().getComponent(
+            getName(), getVersion(), service, component);
+      } catch (AmbariException e) {
+        // just return null if component doesn't exist
+      }
+    }
+    return componentInfo;
+  }
+
+  /**
+   * Get the service for the specified component.
+   *
+   * @param component  component name
+   *
+   * @return service name that contains tha specified component
+   */
+  public String getServiceForComponent(String component) {
+    return componentService.get(component);
+  }
+
+  /**
+   * Get the names of the services which contains the specified components.
+   *
+   * @param components collection of components
+   *
+   * @return collection of services which contain the specified components
+   */
+  public Collection<String> getServicesForComponents(Collection<String> components) {
+    Set<String> services = new HashSet<String>();
+    for (String component : components) {
+      services.add(getServiceForComponent(component));
+    }
+
+    return services;
+  }
+
+  /**
+   * Return the dependencies specified for the given component.
+   *
+   * @param component  component to get dependency information for
+   *
+   * @return collection of dependency information for the specified component
+   */
+  //todo: full dependency graph
+  public Collection<DependencyInfo> getDependenciesForComponent(String component) {
+    return dependencies.containsKey(component) ? dependencies.get(component) :
+        Collections.<DependencyInfo>emptySet();
+  }
+
+  /**
+   * Get the service, if any, that a component dependency is conditional on.
+   *
+   * @param dependency  dependency to get conditional service for
+   *
+   * @return conditional service for provided component or null if dependency
+   *         is not conditional on a service
+   */
+  public String getConditionalServiceForDependency(DependencyInfo dependency) {
+    return dependencyConditionalServiceMap.get(dependency);
+  }
+
+  public String getExternalComponentConfig(String component) {
+    return dbDependencyInfo.get(component);
+  }
+
+  /**
+   * Obtain the required cardinality for the specified component.
+   */
+  public Cardinality getCardinality(String component) {
+    return new Cardinality(cardinalityRequirements.get(component));
+  }
+
+  /**
+   * Obtain auto-deploy information for the specified component.
+   */
+  public AutoDeployInfo getAutoDeployInfo(String component) {
+    return componentAutoDeployInfo.get(component);
+  }
+}


Mime
View raw message