ambari-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rnettle...@apache.org
Subject ambari git commit: AMBARI-7469. Add Visibility Attributes to Services. (Sebastian Toader via rnettleton)
Date Thu, 01 Oct 2015 22:00:49 GMT
Repository: ambari
Updated Branches:
  refs/heads/trunk 3b4117445 -> 647698507


AMBARI-7469. Add Visibility Attributes to Services. (Sebastian Toader via rnettleton)


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

Branch: refs/heads/trunk
Commit: 64769850741b7aa39c5193040b9682df56cb3fbf
Parents: 3b41174
Author: Bob Nettleton <rnettleton@hortonworks.com>
Authored: Thu Oct 1 18:00:05 2015 -0400
Committer: Bob Nettleton <rnettleton@hortonworks.com>
Committed: Thu Oct 1 18:00:05 2015 -0400

----------------------------------------------------------------------
 ambari-server/pom.xml                           |   6 +
 .../server/controller/StackServiceResponse.java |  13 ++
 .../internal/StackServiceResourceProvider.java  |  89 +++++---
 .../ambari/server/stack/ServiceModule.java      |  82 ++++++-
 .../apache/ambari/server/state/ServiceInfo.java | 138 ++++++++++--
 .../server/state/ServicePropertyInfo.java       |  77 +++++++
 .../src/main/resources/properties.json          |   1 +
 .../controller/StackServiceResponseTest.java    |  49 +++++
 .../StackServiceResourceProviderTest.java       | 152 +++++++++++++
 .../ambari/server/stack/ServiceModuleTest.java  | 183 ++++++++++++++--
 .../ambari/server/state/ServiceInfoTest.java    | 218 ++++++++++++++++++-
 .../server/state/ServicePropertyInfoTest.java   |  83 +++++++
 12 files changed, 1018 insertions(+), 73 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/64769850/ambari-server/pom.xml
----------------------------------------------------------------------
diff --git a/ambari-server/pom.xml b/ambari-server/pom.xml
index 3377961..83f39ec 100644
--- a/ambari-server/pom.xml
+++ b/ambari-server/pom.xml
@@ -1913,6 +1913,12 @@
       <scope>test</scope>
     </dependency>
     <dependency>
+      <groupId>nl.jqno.equalsverifier</groupId>
+      <artifactId>equalsverifier</artifactId>
+      <version>1.7.4</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
       <groupId>org.objenesis</groupId>
       <artifactId>objenesis-tck</artifactId>
       <version>1.2</version>

http://git-wip-us.apache.org/repos/asf/ambari/blob/64769850/ambari-server/src/main/java/org/apache/ambari/server/controller/StackServiceResponse.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/StackServiceResponse.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/StackServiceResponse.java
index d16f4d6..ca1968e 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/StackServiceResponse.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/StackServiceResponse.java
@@ -47,6 +47,8 @@ public class StackServiceResponse {
 
   private List<String> requiredServices;
 
+  private Map<String, String> serviceProperties;
+
   /**
    * A File pointing to the service-level Kerberos descriptor file
    *
@@ -84,6 +86,8 @@ public class StackServiceResponse {
     }
 
     kerberosDescriptorFile = service.getKerberosDescriptorFile();
+
+    serviceProperties = service.getServiceProperties();
   }
 
   public String getStackName() {
@@ -207,4 +211,13 @@ public String getServiceDisplayName() {
   public List<String> getCustomCommands() {
     return customCommands;
   }
+
+  /**
+   * Get the service properties of this service.
+   * @return the properties or an empty map (never {@code null}).
+   */
+  public Map<String, String> getServiceProperties() {
+    return serviceProperties;
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/64769850/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackServiceResourceProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackServiceResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackServiceResourceProvider.java
index dffc74c..6c6fa91 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackServiceResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/StackServiceResourceProvider.java
@@ -25,15 +25,23 @@ import org.apache.ambari.server.StaticallyInject;
 import org.apache.ambari.server.controller.AmbariManagementController;
 import org.apache.ambari.server.controller.StackServiceRequest;
 import org.apache.ambari.server.controller.StackServiceResponse;
-import org.apache.ambari.server.controller.spi.*;
+import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
+import org.apache.ambari.server.controller.spi.NoSuchResourceException;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.controller.spi.Resource.Type;
+import org.apache.ambari.server.controller.spi.SystemException;
+import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
-import org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor;
 import org.apache.ambari.server.state.kerberos.KerberosServiceDescriptorFactory;
 
-import java.io.File;
-import java.io.IOException;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
 
 @StaticallyInject
 public class StackServiceResourceProvider extends ReadOnlyResourceProvider {
@@ -74,9 +82,12 @@ public class StackServiceResourceProvider extends ReadOnlyResourceProvider {
   private static final String CUSTOM_COMMANDS_PROPERTY_ID = PropertyHelper.getPropertyId(
       "StackServices", "custom_commands");
 
+  private static final String SERVICE_PROPERTIES_PROPERTY_ID = PropertyHelper.getPropertyId(
+    "StackServices", "properties");
+
   private static Set<String> pkPropertyIds = new HashSet<String>(
-      Arrays.asList(new String[] { STACK_NAME_PROPERTY_ID,
-          STACK_VERSION_PROPERTY_ID, SERVICE_NAME_PROPERTY_ID }));
+      Arrays.asList(new String[]{STACK_NAME_PROPERTY_ID,
+        STACK_VERSION_PROPERTY_ID, SERVICE_NAME_PROPERTY_ID}));
 
   /**
    * KerberosServiceDescriptorFactory used to create KerberosServiceDescriptor instances
@@ -93,7 +104,7 @@ public class StackServiceResourceProvider extends ReadOnlyResourceProvider {
   @Override
   public Set<Resource> getResources(Request request, Predicate predicate)
       throws SystemException, UnsupportedPropertyException,
-      NoSuchResourceException, NoSuchParentResourceException {
+    NoSuchResourceException, NoSuchParentResourceException {
 
     final Set<StackServiceRequest> requests = new HashSet<StackServiceRequest>();
 
@@ -117,48 +128,60 @@ public class StackServiceResourceProvider extends ReadOnlyResourceProvider {
     Set<Resource> resources = new HashSet<Resource>();
 
     for (StackServiceResponse response : responses) {
-      Resource resource = new ResourceImpl(Resource.Type.StackService);
+      Resource resource = createResource(response, requestedIds);
 
-      setResourceProperty(resource, STACK_NAME_PROPERTY_ID,
-          response.getStackName(), requestedIds);
+      resources.add(resource);
+    }
+
+    return resources;
+  }
 
-      setResourceProperty(resource, STACK_VERSION_PROPERTY_ID,
-          response.getStackVersion(), requestedIds);
+  private Resource createResource(StackServiceResponse response, Set<String> requestedIds) {
+    Resource resource = new ResourceImpl(Type.StackService);
+
+    setResourceProperty(resource, STACK_NAME_PROPERTY_ID,
+        response.getStackName(), requestedIds);
 
       setResourceProperty(resource, SERVICE_NAME_PROPERTY_ID,
           response.getServiceName(), requestedIds);
       
       setResourceProperty(resource, SERVICE_TYPE_PROPERTY_ID,
-    		  response.getServiceType(), requestedIds); 
+    		  response.getServiceType(), requestedIds);
 
-      setResourceProperty(resource, SERVICE_DISPLAY_NAME_PROPERTY_ID,
-          response.getServiceDisplayName(), requestedIds);
+    setResourceProperty(resource, STACK_VERSION_PROPERTY_ID,
+        response.getStackVersion(), requestedIds);
 
-      setResourceProperty(resource, USER_NAME_PROPERTY_ID,
-          response.getUserName(), requestedIds);
+    setResourceProperty(resource, SERVICE_NAME_PROPERTY_ID,
+        response.getServiceName(), requestedIds);
 
-      setResourceProperty(resource, COMMENTS_PROPERTY_ID,
-          response.getComments(), requestedIds);
+    setResourceProperty(resource, SERVICE_DISPLAY_NAME_PROPERTY_ID,
+        response.getServiceDisplayName(), requestedIds);
 
-      setResourceProperty(resource, VERSION_PROPERTY_ID,
-          response.getServiceVersion(), requestedIds);
+    setResourceProperty(resource, USER_NAME_PROPERTY_ID,
+        response.getUserName(), requestedIds);
 
-      setResourceProperty(resource, CONFIG_TYPES,
-          response.getConfigTypes(), requestedIds);
+    setResourceProperty(resource, COMMENTS_PROPERTY_ID,
+        response.getComments(), requestedIds);
 
-      setResourceProperty(resource, REQUIRED_SERVICES_ID,
-          response.getRequiredServices(), requestedIds);
+    setResourceProperty(resource, VERSION_PROPERTY_ID,
+        response.getServiceVersion(), requestedIds);
 
-      setResourceProperty(resource, SERVICE_CHECK_SUPPORTED_PROPERTY_ID,
-          response.isServiceCheckSupported(), requestedIds);
+    setResourceProperty(resource, CONFIG_TYPES,
+        response.getConfigTypes(), requestedIds);
 
-      setResourceProperty(resource, CUSTOM_COMMANDS_PROPERTY_ID,
-          response.getCustomCommands(), requestedIds);
+    setResourceProperty(resource, REQUIRED_SERVICES_ID,
+        response.getRequiredServices(), requestedIds);
 
-      resources.add(resource);
-    }
+    setResourceProperty(resource, SERVICE_CHECK_SUPPORTED_PROPERTY_ID,
+        response.isServiceCheckSupported(), requestedIds);
 
-    return resources;
+    setResourceProperty(resource, CUSTOM_COMMANDS_PROPERTY_ID,
+        response.getCustomCommands(), requestedIds);
+
+    setResourceProperty(resource, SERVICE_PROPERTIES_PROPERTY_ID,
+      response.getServiceProperties(), requestedIds);
+
+    return resource;
   }
 
   private StackServiceRequest getRequest(Map<String, Object> properties) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/64769850/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java
index e51eb21..c2a2a0c 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java
@@ -18,6 +18,24 @@
 
 package org.apache.ambari.server.stack;
 
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.Sets;
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.state.ComponentInfo;
+import org.apache.ambari.server.state.CustomCommandDefinition;
+import org.apache.ambari.server.state.PropertyInfo;
+import org.apache.ambari.server.state.ServiceInfo;
+import org.apache.ambari.server.state.ServicePropertyInfo;
+import org.apache.ambari.server.state.ThemeInfo;
+
+import javax.annotation.Nullable;
 import java.io.File;
 import java.util.Collection;
 import java.util.Collections;
@@ -27,13 +45,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import org.apache.ambari.server.AmbariException;
-import org.apache.ambari.server.api.services.AmbariMetaInfo;
-import org.apache.ambari.server.state.ComponentInfo;
-import org.apache.ambari.server.state.CustomCommandDefinition;
-import org.apache.ambari.server.state.PropertyInfo;
-import org.apache.ambari.server.state.ServiceInfo;
-import org.apache.ambari.server.state.ThemeInfo;
 
 /**
  * Service module which provides all functionality related to parsing and fully
@@ -118,6 +129,8 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
     populateComponentModules();
     populateConfigurationModules();
     populateThemeModules();
+
+    validateServiceInfo();
   }
 
   @Override
@@ -129,6 +142,10 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
   public void resolve(
       ServiceModule parentModule, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices)
       throws AmbariException {
+
+    if (!serviceInfo.isValid() || !parentModule.isValid())
+      return;
+
     ServiceInfo parent = parentModule.getModuleInfo();
     
     if (serviceInfo.getComment() == null) {
@@ -188,6 +205,47 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
     mergeConfigurations(parentModule, allStacks, commonServices);
     mergeThemes(parentModule, allStacks, commonServices);
     mergeExcludedConfigTypes(parent);
+
+
+    mergeServiceProperties(parent.getServicePropertyList());
+
+  }
+
+  /**
+   * Merges service properties from parent into the the service properties of this this service.
+   * Current properties overrides properties with same name from parent
+   * @param other service properties to merge with the current service property list
+   */
+  private void mergeServiceProperties(List<ServicePropertyInfo> other) {
+    if (!other.isEmpty()) {
+      List<ServicePropertyInfo> servicePropertyList = serviceInfo.getServicePropertyList();
+      List<ServicePropertyInfo> servicePropertiesToAdd = Lists.newArrayList();
+
+      Set<String> servicePropertyNames = Sets.newTreeSet(
+        Iterables.transform(servicePropertyList, new Function<ServicePropertyInfo, String>() {
+          @Nullable
+          @Override
+          public String apply(ServicePropertyInfo serviceProperty) {
+            return serviceProperty.getName();
+          }
+        })
+      );
+
+      for (ServicePropertyInfo otherServiceProperty : other) {
+        if (!servicePropertyNames.contains(otherServiceProperty.getName()))
+          servicePropertiesToAdd.add(otherServiceProperty);
+      }
+
+      List<ServicePropertyInfo> mergedServicePropertyList =
+        ImmutableList.<ServicePropertyInfo>builder()
+          .addAll(servicePropertyList)
+          .addAll(servicePropertiesToAdd)
+          .build();
+
+      serviceInfo.setServicePropertyList(mergedServicePropertyList);
+
+      validateServiceInfo();
+    }
   }
 
   /**
@@ -480,5 +538,13 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
   @Override
   public void setErrors(Collection error) {
     this.errorSet.addAll(error);
-  }  
+  }
+
+
+  private void validateServiceInfo() {
+    if (!serviceInfo.isValid()) {
+      setValid(false);
+      setErrors(serviceInfo.getErrors());
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/64769850/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java b/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java
index d9a8a51..a58cda3 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java
@@ -18,7 +18,27 @@
 
 package org.apache.ambari.server.state;
 
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.stack.Validable;
+import org.apache.ambari.server.state.stack.MetricDefinition;
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.map.annotate.JsonFilter;
+
+import javax.xml.bind.Unmarshaller;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlElements;
+import javax.xml.bind.annotation.XmlTransient;
 import java.io.File;
+import java.util.AbstractMap;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -29,23 +49,13 @@ import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
 
-import javax.xml.bind.annotation.XmlAccessType;
-import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlElementWrapper;
-import javax.xml.bind.annotation.XmlElements;
-import javax.xml.bind.annotation.XmlTransient;
-
-import org.apache.ambari.server.api.services.AmbariMetaInfo;
-import org.apache.ambari.server.stack.Validable;
-import org.apache.ambari.server.state.stack.MetricDefinition;
-import org.codehaus.jackson.annotate.JsonIgnore;
-import org.codehaus.jackson.map.annotate.JsonFilter;
-
 @XmlAccessorType(XmlAccessType.FIELD)
 @JsonFilter("propertiesfilter")
 public class ServiceInfo implements Validable{
 
+  public static final AbstractMap.SimpleEntry<String, String> DEFAULT_SERVICE_INSTALLABLE_PROPERTY = new AbstractMap.SimpleEntry("installable", "true");
+  public static final AbstractMap.SimpleEntry<String, String> DEFAULT_SERVICE_MANAGED_PROPERTY = new AbstractMap.SimpleEntry("managed", "true");
+  public static final AbstractMap.SimpleEntry<String, String> DEFAULT_SERVICE_MONITORED_PROPERTY = new AbstractMap.SimpleEntry("monitored", "true");
   /**
    * Format version. Added at schema ver 2
    */
@@ -57,6 +67,8 @@ public class ServiceInfo implements Validable{
   private String version;
   private String comment;
   private String serviceType;
+
+  @XmlTransient
   private List<PropertyInfo> properties;
 
   @XmlElementWrapper(name="components")
@@ -130,8 +142,17 @@ public class ServiceInfo implements Validable{
   @XmlTransient
   private boolean valid = true;
 
+  @XmlElementWrapper(name = "properties")
+  @XmlElement(name="property")
+  private List<ServicePropertyInfo> servicePropertyList = Lists.newArrayList();
+
+
+
+  @XmlTransient
+  private Map<String, String> servicePropertyMap = ImmutableMap.copyOf(ensureMandatoryServiceProperties(Maps.<String, String>newHashMap()));
+
   /**
-   * 
+   *
    * @return valid xml flag
    */
   @Override
@@ -360,6 +381,7 @@ public String getVersion() {
     sb.append(version);
     sb.append("\ncomment:");
     sb.append(comment);
+
     //for (PropertyInfo property : getProperties()) {
     //  sb.append("\tProperty name=" + property.getName() +
     //"\nproperty value=" + property.getValue() + "\ndescription=" + property.getDescription());
@@ -746,4 +768,92 @@ public String getVersion() {
   public void setThemesMap(Map<String, ThemeInfo> themesMap) {
     this.themesMap = themesMap;
   }
+
+
+  public List<ServicePropertyInfo> getServicePropertyList() {
+    return servicePropertyList;
+  }
+
+  public void setServicePropertyList(List<ServicePropertyInfo> servicePropertyList) {
+    this.servicePropertyList = servicePropertyList;
+    afterServicePropertyListSet();
+  }
+
+  private void afterServicePropertyListSet(){
+    validateServiceProperties();
+    buildServiceProperties();
+  }
+
+
+  /**
+   * Returns the service properties defined in the xml service definition.
+   * @return Service property map
+   */
+  public Map<String, String> getServiceProperties()  {
+    return servicePropertyMap;
+  }
+
+  /**
+   * Constructs the map that stores the service properties defined in the xml service definition.
+   * The keys are the property names and values the property values.
+   * It ensures that missing required service properties are added with default values.
+   */
+  private void buildServiceProperties() {
+    if (isValid()) {
+      Map<String, String> properties = Maps.newHashMap();
+      for (ServicePropertyInfo property : getServicePropertyList()) {
+        properties.put(property.getName(), property.getValue());
+      }
+      servicePropertyMap = ImmutableMap.copyOf(ensureMandatoryServiceProperties(properties));
+    }
+    else
+      servicePropertyMap = ImmutableMap.of();
+
+
+  }
+
+  private Map<String, String> ensureMandatoryServiceProperties(Map<String, String> properties) {
+    return ensureVisibilityServiceProperties(properties);
+  }
+
+  private Map<String, String> ensureVisibilityServiceProperties(Map<String, String> properties) {
+    if (!properties.containsKey(DEFAULT_SERVICE_INSTALLABLE_PROPERTY.getKey()))
+      properties.put(DEFAULT_SERVICE_INSTALLABLE_PROPERTY.getKey(), DEFAULT_SERVICE_INSTALLABLE_PROPERTY.getValue());
+
+    if (!properties.containsKey(DEFAULT_SERVICE_MANAGED_PROPERTY.getKey()))
+      properties.put(DEFAULT_SERVICE_MANAGED_PROPERTY.getKey(), DEFAULT_SERVICE_MANAGED_PROPERTY.getValue());
+
+
+    if (!properties.containsKey(DEFAULT_SERVICE_MONITORED_PROPERTY.getKey()))
+      properties.put(DEFAULT_SERVICE_MONITORED_PROPERTY.getKey(), DEFAULT_SERVICE_MONITORED_PROPERTY.getValue());
+
+    return properties;
+  }
+
+  void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
+    afterServicePropertyListSet();
+  }
+
+
+  private void validateServiceProperties() {
+    // Verify if there are duplicate service properties by name
+    Multimap<String, ServicePropertyInfo> servicePropsByName = Multimaps.index(
+      getServicePropertyList(),
+      new Function<ServicePropertyInfo, String>() {
+        @Override
+        public String apply(ServicePropertyInfo servicePropertyInfo) {
+          return servicePropertyInfo.getName();
+        }
+      }
+
+    );
+
+    for (String propertyName: servicePropsByName.keySet()) {
+      if (servicePropsByName.get(propertyName).size() > 1) {
+        setValid(false);
+        setErrors("Duplicate service property with name '" + propertyName + "' found in " + getName() + ":" + getVersion() + " service definition !");
+      }
+    }
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/64769850/ambari-server/src/main/java/org/apache/ambari/server/state/ServicePropertyInfo.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/ServicePropertyInfo.java b/ambari-server/src/main/java/org/apache/ambari/server/state/ServicePropertyInfo.java
new file mode 100644
index 0000000..c152cfd
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/ServicePropertyInfo.java
@@ -0,0 +1,77 @@
+/**
+ * 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.state;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+
+@XmlAccessorType(XmlAccessType.FIELD)
+public final class ServicePropertyInfo {
+  /**
+   * Name of the service property
+   */
+  private String name;
+
+  /**
+   * Value of the service property
+   */
+  private String value;
+
+
+  public String getName() {
+    return name;
+  }
+
+  public String getValue() {
+    return value;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public void setValue(String value) {
+    this.value = value;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+
+    if (o == null || !(o instanceof ServicePropertyInfo)) return false;
+
+    ServicePropertyInfo that = (ServicePropertyInfo) o;
+
+    return new EqualsBuilder()
+      .append(name, that.name)
+      .append(value, that.value)
+      .isEquals();
+  }
+
+  @Override
+  public int hashCode() {
+    return new HashCodeBuilder(17, 37)
+      .append(name)
+      .append(value)
+      .toHashCode();
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/64769850/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 0837af2..639670e 100644
--- a/ambari-server/src/main/resources/properties.json
+++ b/ambari-server/src/main/resources/properties.json
@@ -208,6 +208,7 @@
         "StackServices/service_check_supported",
         "StackServices/custom_commands",
         "StackServices/required_services",
+        "StackServices/properties",
         "_"
     ],
     "StackConfiguration":[

http://git-wip-us.apache.org/repos/asf/ambari/blob/64769850/ambari-server/src/test/java/org/apache/ambari/server/controller/StackServiceResponseTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/StackServiceResponseTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/StackServiceResponseTest.java
new file mode 100644
index 0000000..6fcae58
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/StackServiceResponseTest.java
@@ -0,0 +1,49 @@
+/**
+ * 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 org.apache.ambari.server.state.ServiceInfo;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+
+
+
+public class StackServiceResponseTest {
+
+  private ServiceInfo serviceInfo;
+
+  @Before
+  public void setUp() {
+    serviceInfo = new ServiceInfo();
+  }
+
+  @Test
+  public void testDefaultServiceVisibilityProperties() {
+    StackServiceResponse stackServiceResponse = new StackServiceResponse(serviceInfo);
+
+
+    assertTrue("true".equals(stackServiceResponse.getServiceProperties().get(ServiceInfo.DEFAULT_SERVICE_INSTALLABLE_PROPERTY.getKey())));
+    assertTrue("true".equals(stackServiceResponse.getServiceProperties().get(ServiceInfo.DEFAULT_SERVICE_MANAGED_PROPERTY.getKey())));
+    assertTrue("true".equals(stackServiceResponse.getServiceProperties().get(ServiceInfo.DEFAULT_SERVICE_MONITORED_PROPERTY.getKey())));
+  }
+
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/64769850/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/StackServiceResourceProviderTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/StackServiceResourceProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/StackServiceResourceProviderTest.java
new file mode 100644
index 0000000..349c2bd
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/StackServiceResourceProviderTest.java
@@ -0,0 +1,152 @@
+/**
+ * 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 com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.StackServiceResponse;
+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.apache.ambari.server.state.ServiceInfo;
+import org.apache.ambari.server.state.ServicePropertyInfo;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.easymock.EasyMock.anyObject;
+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;
+
+
+
+public class StackServiceResourceProviderTest {
+
+  private final String  SERVICE_PROPERTIES_PROPERTY_ID = PropertyHelper.getPropertyId("StackServices", "properties");
+  private final ServicePropertyInfo P1 = new ServicePropertyInfo();
+  private final ServicePropertyInfo P2 = new ServicePropertyInfo();
+
+  private Map<String, String> TEST_SERVICE_PROPERTIES = null;
+
+  private  List<ServicePropertyInfo> TEST_SERVICE_PROPERTY_LIST = null;
+
+  @Before
+  public void setUp() throws Exception {
+    P1.setName("P1");
+    P1.setValue("V1");
+
+    P2.setName("P2");
+    P2.setValue("V2");
+
+    TEST_SERVICE_PROPERTY_LIST = ImmutableList.of(P1, P2);
+    TEST_SERVICE_PROPERTIES = ImmutableMap.of(P1.getName(), P1.getValue(), P2.getName(), P2.getValue());
+  }
+
+  @Test
+  public void testGetServiceProperties() throws Exception {
+    // Given
+    AmbariManagementController managementController = createNiceMock(AmbariManagementController.class);
+    Resource.Type type = Resource.Type.StackService;
+
+    StackServiceResponse stackServiceResponse = createNiceMock(StackServiceResponse.class);
+    expect(stackServiceResponse.getServiceProperties()).andReturn(TEST_SERVICE_PROPERTIES);
+
+    expect(managementController.getStackServices(anyObject(Set.class)))
+      .andReturn(ImmutableSet.of(stackServiceResponse));
+
+    replay(managementController, stackServiceResponse);
+
+    Request request = PropertyHelper.getReadRequest(SERVICE_PROPERTIES_PROPERTY_ID);
+
+    ResourceProvider stackServiceResourceProvider = AbstractControllerResourceProvider.getResourceProvider(type,
+      PropertyHelper.getPropertyIds(type),
+      PropertyHelper.getKeyPropertyIds(type),
+      managementController);
+
+    // When
+    Set<Resource> resources = stackServiceResourceProvider.getResources(request, null);
+
+    // Then
+    Resource expected =  new ResourceImpl(type);
+
+
+    BaseProvider.setResourceProperty(expected, SERVICE_PROPERTIES_PROPERTY_ID, TEST_SERVICE_PROPERTIES, ImmutableSet.of(SERVICE_PROPERTIES_PROPERTY_ID));
+
+    assertEquals(ImmutableSet.of(expected), resources);
+
+    verify(managementController, stackServiceResponse);
+
+  }
+
+
+  @Test
+  public void testGetVisibilityServiceProperties() throws Exception {
+    // Given
+    AmbariManagementController managementController = createNiceMock(AmbariManagementController.class);
+    Resource.Type type = Resource.Type.StackService;
+
+
+
+    ServiceInfo serviceInfo = new ServiceInfo() ;
+    serviceInfo.setServicePropertyList(TEST_SERVICE_PROPERTY_LIST);
+
+    StackServiceResponse stackServiceResponse = new StackServiceResponse(serviceInfo);
+
+
+
+    expect(managementController.getStackServices(anyObject(Set.class)))
+      .andReturn(ImmutableSet.of(stackServiceResponse));
+
+    replay(managementController);
+
+    Request request = PropertyHelper.getReadRequest(SERVICE_PROPERTIES_PROPERTY_ID);
+
+    ResourceProvider stackServiceResourceProvider = AbstractControllerResourceProvider.getResourceProvider(type,
+      PropertyHelper.getPropertyIds(type),
+      PropertyHelper.getKeyPropertyIds(type),
+      managementController);
+
+    // When
+    Set<Resource> resources = stackServiceResourceProvider.getResources(request, null);
+    Map<String, String> expectedServiceProperties = ImmutableMap.<String, String>builder()
+      .putAll(TEST_SERVICE_PROPERTIES)
+      .put(ServiceInfo.DEFAULT_SERVICE_INSTALLABLE_PROPERTY)
+      .put(ServiceInfo.DEFAULT_SERVICE_MANAGED_PROPERTY)
+      .put(ServiceInfo.DEFAULT_SERVICE_MONITORED_PROPERTY)
+      .build();
+
+    // Then
+    Resource expected =  new ResourceImpl(type);
+    BaseProvider.setResourceProperty(expected, SERVICE_PROPERTIES_PROPERTY_ID, expectedServiceProperties, ImmutableSet.of(SERVICE_PROPERTIES_PROPERTY_ID));
+
+    assertEquals(ImmutableSet.of(expected), resources);
+
+    verify(managementController);
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/64769850/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 2737695..92c1200 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
@@ -18,14 +18,17 @@
 
 package org.apache.ambari.server.stack;
 
-import static org.easymock.EasyMock.createNiceMock;
-import static org.easymock.EasyMock.createStrictMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.verify;
-import static org.easymock.EasyMock.anyObject;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.state.CommandScriptDefinition;
+import org.apache.ambari.server.state.ComponentInfo;
+import org.apache.ambari.server.state.CustomCommandDefinition;
+import org.apache.ambari.server.state.PropertyInfo;
+import org.apache.ambari.server.state.ServiceInfo;
+import org.apache.ambari.server.state.ServiceOsSpecific;
+import org.apache.ambari.server.state.ServicePropertyInfo;
+import org.junit.Test;
 
 import java.io.File;
 import java.lang.reflect.Field;
@@ -38,14 +41,15 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import org.apache.ambari.server.AmbariException;
-import org.apache.ambari.server.state.CommandScriptDefinition;
-import org.apache.ambari.server.state.ComponentInfo;
-import org.apache.ambari.server.state.CustomCommandDefinition;
-import org.apache.ambari.server.state.PropertyInfo;
-import org.apache.ambari.server.state.ServiceInfo;
-import org.apache.ambari.server.state.ServiceOsSpecific;
-import org.junit.Test;
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.createStrictMock;
+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.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 /**
  * ServiceModule unit tests.
@@ -943,6 +947,153 @@ public class ServiceModuleTest {
     verify(context);
   }
 
+  @Test
+  public void testInvalidServiceInfo() {
+    // Given
+    ServiceInfo serviceInfo = new ServiceInfo();
+    serviceInfo.setName("TEST_SERVICE");
+    serviceInfo.setVersion("1.0.0");
+    serviceInfo.setValid(false);
+    serviceInfo.setErrors("Test error message");
+
+
+    // When
+    ServiceModule serviceModule = createServiceModule(serviceInfo);
+
+    // Then
+    assertFalse("Service module should be invalid due to the service info being invalid !", serviceModule.isValid());
+
+    assertTrue("Service module error collection should contain error message that caused service info being invalid !", serviceModule.getErrors().contains("Test error message"));
+  }
+
+
+  @Test
+  public void testMergeServicePropertiesInheritFromParent() throws Exception {
+    // Given
+    ServiceInfo serviceInfo = new ServiceInfo();
+    ServiceInfo parentServiceInfo = new ServiceInfo();
+
+    ServicePropertyInfo p1 = new ServicePropertyInfo();
+    p1.setName("P1");
+    p1.setValue("V1");
+
+    ServicePropertyInfo p2 = new ServicePropertyInfo();
+    p2.setName("P2");
+    p2.setValue("V2");
+
+
+    List<ServicePropertyInfo> parentServicePropertyList = Lists.newArrayList(p1, p2);
+
+    parentServiceInfo.setServicePropertyList(parentServicePropertyList);
+
+
+    // When
+    ServiceModule serviceModule = resolveService(serviceInfo, parentServiceInfo);
+
+    // Then
+    Map<String, String> parentServiceProperties =  ImmutableMap.<String, String>builder()
+      .put("P1", "V1")
+      .put("P2", "V2")
+      .put(ServiceInfo.DEFAULT_SERVICE_INSTALLABLE_PROPERTY)
+      .put(ServiceInfo.DEFAULT_SERVICE_MANAGED_PROPERTY)
+      .put(ServiceInfo.DEFAULT_SERVICE_MONITORED_PROPERTY)
+      .build();
+
+
+    assertEquals(parentServicePropertyList, serviceModule.getModuleInfo().getServicePropertyList());
+    assertEquals(parentServiceProperties, serviceModule.getModuleInfo().getServiceProperties());
+  }
+
+  @Test
+  public void testMergeServicePropertiesInheritFromEmptyParent() throws Exception {
+    // Parent has no properties defined thus no service properties inherited
+
+    // Given
+    ServiceInfo serviceInfo = new ServiceInfo();
+    ServiceInfo parentServiceInfo = new ServiceInfo();
+
+    ServicePropertyInfo p1 = new ServicePropertyInfo();
+    p1.setName("P1");
+    p1.setValue("V1");
+
+    ServicePropertyInfo p2 = new ServicePropertyInfo();
+    p2.setName("P2");
+    p2.setValue("V2");
+
+
+    List<ServicePropertyInfo> servicePropertyList = Lists.newArrayList(p1, p2);
+
+    serviceInfo.setServicePropertyList(servicePropertyList);
+
+
+    // When
+    ServiceModule serviceModule = resolveService(serviceInfo, parentServiceInfo);
+
+    // Then
+    Map<String, String> serviceProperties = ImmutableMap.<String, String>builder()
+      .put("P1", "V1")
+      .put("P2", "V2")
+      .put(ServiceInfo.DEFAULT_SERVICE_INSTALLABLE_PROPERTY)
+      .put(ServiceInfo.DEFAULT_SERVICE_MANAGED_PROPERTY)
+      .put(ServiceInfo.DEFAULT_SERVICE_MONITORED_PROPERTY)
+      .build();
+
+    assertEquals(servicePropertyList, serviceModule.getModuleInfo().getServicePropertyList());
+    assertEquals(serviceProperties, serviceModule.getModuleInfo().getServiceProperties());
+  }
+
+
+  @Test
+  public void testMergeServiceProperties() throws Exception {
+    // Given
+    ServiceInfo serviceInfo = new ServiceInfo();
+    ServiceInfo parentServiceInfo = new ServiceInfo();
+
+    ServicePropertyInfo p1 = new ServicePropertyInfo();
+    p1.setName("P1");
+    p1.setValue("V1");
+
+    ServicePropertyInfo p2 = new ServicePropertyInfo();
+    p2.setName("P2");
+    p2.setValue("V2");
+
+    ServicePropertyInfo p2Override = new ServicePropertyInfo();
+    p2Override.setName("P2");
+    p2Override.setValue("V2_OVERRIDE");
+
+    ServicePropertyInfo p3 = new ServicePropertyInfo();
+    p3.setName("P3");
+    p3.setValue("V3");
+
+    List<ServicePropertyInfo> parentServicePropertyList = Lists.newArrayList(p1, p2);
+    parentServiceInfo.setServicePropertyList(parentServicePropertyList);
+
+    List<ServicePropertyInfo> servicePropertyList = Lists.newArrayList(p2Override, p3);
+    serviceInfo.setServicePropertyList(servicePropertyList);
+
+
+    // When
+    ServiceModule serviceModule = resolveService(serviceInfo, parentServiceInfo);
+
+    // Then
+    List<ServicePropertyInfo> expectedPropertyList = Lists.newArrayList(p1, p2Override, p3);
+    Map<String, String> expectedServiceProperties = ImmutableMap.<String, String>builder()
+      .put("P1", "V1")
+      .put("P2", "V2_OVERRIDE")
+      .put("P3", "V3")
+      .put(ServiceInfo.DEFAULT_SERVICE_INSTALLABLE_PROPERTY)
+      .put(ServiceInfo.DEFAULT_SERVICE_MANAGED_PROPERTY)
+      .put(ServiceInfo.DEFAULT_SERVICE_MONITORED_PROPERTY)
+      .build();
+
+    List<ServicePropertyInfo> actualPropertyList = serviceModule.getModuleInfo().getServicePropertyList();
+
+
+    assertTrue(actualPropertyList.containsAll(expectedPropertyList) && expectedPropertyList.containsAll(actualPropertyList));
+    assertEquals(expectedServiceProperties, serviceModule.getModuleInfo().getServiceProperties());
+  }
+
+
   private ServiceModule createServiceModule(ServiceInfo serviceInfo) {
     String configType = "type1";
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/64769850/ambari-server/src/test/java/org/apache/ambari/server/state/ServiceInfoTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/state/ServiceInfoTest.java b/ambari-server/src/test/java/org/apache/ambari/server/state/ServiceInfoTest.java
index 75e0991..43cea91 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/state/ServiceInfoTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/state/ServiceInfoTest.java
@@ -18,6 +18,7 @@
 
 package org.apache.ambari.server.state;
 
+import com.google.common.collect.Lists;
 import org.apache.ambari.server.state.stack.ServiceMetainfoXml;
 import org.junit.Test;
 
@@ -27,6 +28,7 @@ import javax.xml.bind.Unmarshaller;
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import static org.junit.Assert.*;
@@ -103,10 +105,10 @@ public class ServiceInfoTest {
 
     Map<String, ServiceInfo> serviceInfoMap = getServiceInfo(serviceInfoXml);
 
-    assertEquals("CUSTOM_metrics.json",serviceInfoMap.get("CUSTOM").getMetricsFileName());
+    assertEquals("CUSTOM_metrics.json", serviceInfoMap.get("CUSTOM").getMetricsFileName());
     assertEquals("CUSTOM_widgets.json", serviceInfoMap.get("CUSTOM").getWidgetsFileName());
     assertEquals("metrics.json", serviceInfoMap.get("DEFAULT").getMetricsFileName());
-    assertEquals("widgets.json",serviceInfoMap.get("DEFAULT").getWidgetsFileName());
+    assertEquals("widgets.json", serviceInfoMap.get("DEFAULT").getWidgetsFileName());
   }
 
   @Test
@@ -120,6 +122,218 @@ public class ServiceInfoTest {
     assertFalse(serviceInfo.isRestartRequiredAfterRackChange());
   }
 
+  @Test
+  public void testServiceProperties() throws Exception {
+    String serviceInfoXml =
+      "<metainfo>" +
+      "  <schemaVersion>2.0</schemaVersion>" +
+      "  <services>" +
+      "    <service>" +
+      "      <name>WITH_PROPS</name>" +
+      "      <displayName>WITH_PROPS</displayName>" +
+      "      <properties>" +
+      "        <property>" +
+      "          <name>PROP1</name>" +
+      "          <value>VAL1</value>" +
+      "        </property>" +
+      "        <property>" +
+      "          <name>PROP2</name>" +
+      "          <value>VAL2</value>" +
+      "        </property>" +
+      "      </properties>" +
+      "    </service>" +
+      "  </services>" +
+      "</metainfo>";
+
+    Map<String, ServiceInfo> serviceInfoMap = getServiceInfo(serviceInfoXml);
+
+    Map<String, String> serviceProperties = serviceInfoMap.get("WITH_PROPS").getServiceProperties();
+
+    assertTrue(serviceProperties.containsKey("PROP1"));
+    assertEquals("VAL1", serviceProperties.get("PROP1"));
+
+    assertTrue(serviceProperties.containsKey("PROP2"));
+    assertEquals("VAL2", serviceProperties.get("PROP2"));
+
+  }
+
+
+  @Test
+  public void testDefaultVisibilityServiceProperties() throws Exception {
+    // Given
+    String serviceInfoXml =
+      "<metainfo>" +
+        "  <schemaVersion>2.0</schemaVersion>" +
+        "  <services>" +
+        "    <service>" +
+        "      <name>WITH_PROPS</name>" +
+        "      <displayName>WITH_PROPS</displayName>" +
+        "      <properties>" +
+        "        <property>" +
+        "          <name>PROP1</name>" +
+        "          <value>VAL1</value>" +
+        "        </property>" +
+        "        <property>" +
+        "          <name>PROP2</name>" +
+        "          <value>VAL2</value>" +
+        "        </property>" +
+        "      </properties>" +
+        "    </service>" +
+        "  </services>" +
+        "</metainfo>";
+
+    // When
+    Map<String, ServiceInfo> serviceInfoMap = getServiceInfo(serviceInfoXml);
+
+    // Then
+    Map<String, String> serviceProperties = serviceInfoMap.get("WITH_PROPS").getServiceProperties();
+
+
+    assertTrue("true".equals(serviceProperties.get(ServiceInfo.DEFAULT_SERVICE_INSTALLABLE_PROPERTY.getKey())));
+    assertTrue("true".equals(serviceProperties.get(ServiceInfo.DEFAULT_SERVICE_MANAGED_PROPERTY.getKey())));
+    assertTrue("true".equals(serviceProperties.get(ServiceInfo.DEFAULT_SERVICE_MONITORED_PROPERTY.getKey())));
+  }
+
+  @Test
+  public void testVisibilityServicePropertyOverride() throws Exception {
+    // Given
+    String serviceInfoXml =
+      "<metainfo>" +
+        "  <schemaVersion>2.0</schemaVersion>" +
+        "  <services>" +
+        "    <service>" +
+        "      <name>WITH_PROPS</name>" +
+        "      <displayName>WITH_PROPS</displayName>" +
+        "      <properties>" +
+        "        <property>" +
+        "          <name>PROP1</name>" +
+        "          <value>VAL1</value>" +
+        "        </property>" +
+        "        <property>" +
+        "          <name>PROP2</name>" +
+        "          <value>VAL2</value>" +
+        "        </property>" +
+        "        <property>" +
+        "          <name>managed</name>" +
+        "          <value>false</value>" +
+        "        </property>" +
+        "      </properties>" +
+        "    </service>" +
+        "  </services>" +
+        "</metainfo>";
+
+    // When
+    Map<String, ServiceInfo> serviceInfoMap = getServiceInfo(serviceInfoXml);
+
+    // Then
+    Map<String, String> serviceProperties = serviceInfoMap.get("WITH_PROPS").getServiceProperties();
+
+
+    assertTrue("true".equals(serviceProperties.get(ServiceInfo.DEFAULT_SERVICE_INSTALLABLE_PROPERTY.getKey())));
+    assertTrue("false".equals(serviceProperties.get(ServiceInfo.DEFAULT_SERVICE_MANAGED_PROPERTY.getKey())));
+    assertTrue("true".equals(serviceProperties.get(ServiceInfo.DEFAULT_SERVICE_MONITORED_PROPERTY.getKey())));
+
+  }
+
+  @Test
+  public void testDuplicateServicePropertyValidationAfterXmlDeserialization() throws Exception
+  {
+    // Given
+    String serviceInfoXml =
+      "<metainfo>" +
+        "  <schemaVersion>2.0</schemaVersion>" +
+        "  <services>" +
+        "    <service>" +
+        "      <version>1.0</version>" +
+        "      <name>WITH_DUPLICATE_PROPS</name>" +
+        "      <displayName>WITH_PROPS</displayName>" +
+        "      <properties>" +
+        "        <property>" +
+        "          <name>PROP1</name>" +
+        "          <value>VAL1</value>" +
+        "        </property>" +
+        "        <property>" +
+        "          <name>PROP1</name>" +
+        "          <value>VAL2</value>" +
+        "        </property>" +
+        "        <property>" +
+        "          <name>managed</name>" +
+        "          <value>false</value>" +
+        "        </property>" +
+        "      </properties>" +
+        "    </service>" +
+        "  </services>" +
+        "</metainfo>";
+
+    // When
+    Map<String, ServiceInfo> serviceInfoMap = getServiceInfo(serviceInfoXml);
+    ServiceInfo serviceInfo = serviceInfoMap.get("WITH_DUPLICATE_PROPS");
+
+    // Then
+    assertFalse("Service info should be in invalid state due to duplicate service property names !", serviceInfo.isValid());
+
+    assertTrue("Service info error collection should contain the name of the duplicate service property !", serviceInfo.getErrors().contains("Duplicate service property with name 'PROP1' found in " + serviceInfo.getName() + ":" + serviceInfo.getVersion() + " service definition !"));
+
+  }
+
+  @Test
+  public void testDuplicateServicePropertyValidationAfterSet() {
+    // Given
+    ServicePropertyInfo p1 = new ServicePropertyInfo();
+    p1.setName("PROP1");
+    p1.setValue("V1");
+
+    ServicePropertyInfo p2 = new ServicePropertyInfo();
+    p2.setName("PROP1");
+    p2.setValue("V2");
+
+    List<ServicePropertyInfo> servicePropertyList = Lists.newArrayList(p1, p2);
+
+    ServiceInfo serviceInfo = new ServiceInfo();
+    serviceInfo.setName("TEST_NAME");
+    serviceInfo.setVersion("TEST_VERSION");
+    serviceInfo.setServicePropertyList(servicePropertyList);
+
+    // Then
+    assertFalse("Service info should be in invalid state due to duplicate service property names !", serviceInfo.isValid());
+
+    assertTrue("Service info error collection should contain the name of the duplicate service property !", serviceInfo.getErrors().contains("Duplicate service property with name 'PROP1' found in " + serviceInfo.getName() + ":" + serviceInfo.getVersion() + " service definition !"));
+
+  }
+
+  @Test
+  public void testSetServicePropertiesAfterPropertyListSet() {
+    // Given
+    ServicePropertyInfo p1 = new ServicePropertyInfo();
+    p1.setName("PROP1");
+    p1.setValue("V1");
+
+    ServicePropertyInfo p2 = new ServicePropertyInfo();
+    p2.setName("PROP2");
+    p2.setValue("V2");
+
+    List<ServicePropertyInfo> servicePropertyList = Lists.newArrayList(p1, p2);
+
+    ServiceInfo serviceInfo = new ServiceInfo();
+    serviceInfo.setName("TEST_NAME");
+    serviceInfo.setVersion("TEST_VERSION");
+    serviceInfo.setServicePropertyList(servicePropertyList);
+
+    // When
+    Map<String, String> serviceProperties = serviceInfo.getServiceProperties();
+
+    // Then
+    assertTrue(serviceProperties.containsKey("PROP1"));
+    assertEquals("V1", serviceProperties.get("PROP1"));
+
+    assertTrue(serviceProperties.containsKey("PROP2"));
+    assertEquals("V2", serviceProperties.get("PROP2"));
+
+    assertTrue("true".equals(serviceProperties.get(ServiceInfo.DEFAULT_SERVICE_INSTALLABLE_PROPERTY.getKey())));
+    assertTrue("true".equals(serviceProperties.get(ServiceInfo.DEFAULT_SERVICE_MANAGED_PROPERTY.getKey())));
+    assertTrue("true".equals(serviceProperties.get(ServiceInfo.DEFAULT_SERVICE_MONITORED_PROPERTY.getKey())));
+  }
+
   public static Map<String, ServiceInfo> getServiceInfo(String xml) throws JAXBException {
     InputStream configStream = new ByteArrayInputStream(xml.getBytes());
     JAXBContext jaxbContext = JAXBContext.newInstance(ServiceMetainfoXml.class);

http://git-wip-us.apache.org/repos/asf/ambari/blob/64769850/ambari-server/src/test/java/org/apache/ambari/server/state/ServicePropertyInfoTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/state/ServicePropertyInfoTest.java b/ambari-server/src/test/java/org/apache/ambari/server/state/ServicePropertyInfoTest.java
new file mode 100644
index 0000000..e56d218
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/state/ServicePropertyInfoTest.java
@@ -0,0 +1,83 @@
+/**
+ * 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.state;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import nl.jqno.equalsverifier.Warning;
+import org.junit.Test;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.transform.stream.StreamSource;
+import java.io.ByteArrayInputStream;
+
+import static junit.framework.Assert.assertEquals;
+
+public class ServicePropertyInfoTest {
+  private static final String XML =
+    "<property>\n" +
+    "  <name>prop_name</name>\n" +
+    "  <value>prop_value</value>\n"+
+    "</property>";
+
+  @Test
+  public void testName() throws Exception {
+    // Given
+    ServicePropertyInfo p = getServiceProperty(XML);
+
+    // When
+    String name = p.getName();
+
+    // Then
+    assertEquals("prop_name", name);
+  }
+
+  @Test
+  public void testValue() throws Exception {
+    // Given
+    ServicePropertyInfo p = getServiceProperty(XML);
+
+    // When
+    String value = p.getValue();
+
+    // Then
+    assertEquals("prop_value", value);
+  }
+
+  @Test
+  public void testEquals() throws Exception {
+    EqualsVerifier.forClass(ServicePropertyInfo.class)
+      .suppress(Warning.NONFINAL_FIELDS)
+      .verify();
+  }
+
+
+  public static ServicePropertyInfo getServiceProperty(String xml) throws JAXBException {
+    JAXBContext jaxbContext = JAXBContext.newInstance(ServicePropertyInfo.class);
+    Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
+
+    return unmarshaller.unmarshal(
+      new StreamSource(
+        new ByteArrayInputStream(xml.getBytes())
+      ),
+      ServicePropertyInfo.class)
+    .getValue();
+  }
+}


Mime
View raw message