This is an automated email from the ASF dual-hosted git repository. benyoka pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/ambari.git
The following commit(s) were added to refs/heads/trunk by this push: new 664d14d AMBARI-24881 Add Service Request JSON (benyoka) (#2594) 664d14d is described below commit 664d14dae54655a092a304400944bae7e24d1275 Author: benyoka <beny...@users.noreply.github.com> AuthorDate: Tue Nov 13 13:45:28 2018 +0100 AMBARI-24881 Add Service Request JSON (benyoka) (#2594) * AMBARI-24881 Add Service Request JSON (benyoka) * AMBARI-24881 review comments (benyoka) --- .../server/controller/AddServiceRequest.java | 250 ++++++++++++++++++++ .../ambari/server/topology/Configurable.java | 147 ++++++++++++ .../server/controller/AddServiceRequestTest.java | 263 +++++++++++++++++++++ .../ambari/server/topology/ConfigurableTest.java | 104 ++++++++ .../resources/add_service_api/configurable.json | 16 ++ .../resources/add_service_api/configurable2.json | 8 + .../test/resources/add_service_api/request1.json | 39 +++ .../test/resources/add_service_api/request2.json | 18 ++ .../test/resources/add_service_api/request3.json | 6 + .../test/resources/add_service_api/request4.json | 13 + .../add_service_api/request_invalid_1.json | 23 ++ .../add_service_api/request_invalid_2.json | 40 ++++ 12 files changed, 927 insertions(+) diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/AddServiceRequest.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/AddServiceRequest.java new file mode 100644 index 0000000..83a66d8 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/AddServiceRequest.java @@ -0,0 +1,250 @@ +/* + * 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 static com.google.common.base.Preconditions.checkArgument; +import static java.util.Collections.emptySet; +import static org.apache.ambari.server.controller.internal.BaseClusterRequest.PROVISION_ACTION_PROPERTY; +import static org.apache.ambari.server.controller.internal.ProvisionClusterRequest.CONFIG_RECOMMENDATION_STRATEGY; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.apache.ambari.annotations.ApiIgnore; +import org.apache.ambari.server.controller.internal.ProvisionAction; +import org.apache.ambari.server.topology.ConfigRecommendationStrategy; +import org.apache.ambari.server.topology.Configurable; +import org.apache.ambari.server.topology.Configuration; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +/** + * Data object representing an add service request. + */ +@ApiModel +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public final class AddServiceRequest { + + static final String OPERATION_TYPE = "operation_type"; + static final String STACK_NAME = "stack_name"; + static final String STACK_VERSION = "stack_version"; + static final String SERVICES = "services"; + static final String COMPONENTS = "components"; + + private final OperationType operationType; + private final ConfigRecommendationStrategy recommendationStrategy; + private final ProvisionAction provisionAction; + private final String stackName; + private final String stackVersion; + private final Set<Service> services; + private final Set<Component> components; + private final Configuration configuration; + + @JsonCreator + public AddServiceRequest(@JsonProperty(OPERATION_TYPE) OperationType operationType, + @JsonProperty(CONFIG_RECOMMENDATION_STRATEGY) ConfigRecommendationStrategy recommendationStrategy, + @JsonProperty(PROVISION_ACTION_PROPERTY)ProvisionAction provisionAction, + @JsonProperty(STACK_NAME) String stackName, + @JsonProperty(STACK_VERSION) String stackVersion, + @JsonProperty(SERVICES) Set<Service> services, + @JsonProperty(COMPONENTS)Set<Component> components, + @JsonProperty(Configurable.CONFIGURATIONS) Collection<? extends Map<String, ?>> configs) { + this(operationType, recommendationStrategy, provisionAction, stackName, stackVersion, services, components, + Configurable.parseConfigs(configs)); + } + + + private AddServiceRequest(OperationType operationType, + ConfigRecommendationStrategy recommendationStrategy, + ProvisionAction provisionAction, + String stackName, + String stackVersion, + Set<Service> services, + Set<Component> components, + Configuration configuration) { + this.operationType = null != operationType ? operationType : OperationType.ADD_SERVICE; + this.recommendationStrategy = null != recommendationStrategy ? recommendationStrategy : ConfigRecommendationStrategy.NEVER_APPLY; + this.provisionAction = null != provisionAction ? provisionAction : ProvisionAction.INSTALL_AND_START; + this.stackName = stackName; + this.stackVersion = stackVersion; + this.services = null != services ? services : emptySet(); + this.components = null != components ? components : emptySet(); + this.configuration = null != configuration ? configuration : new Configuration(new HashMap<>(), new HashMap<>()); + + checkArgument(!this.services.isEmpty() || !this.components.isEmpty(), "Either services or components must be specified"); + } + + + @JsonProperty(OPERATION_TYPE) + @ApiModelProperty(name = OPERATION_TYPE) + public OperationType getOperationType() { + return operationType; + } + + @JsonProperty(CONFIG_RECOMMENDATION_STRATEGY) + @ApiModelProperty(name = CONFIG_RECOMMENDATION_STRATEGY) + public ConfigRecommendationStrategy getRecommendationStrategy() { + return recommendationStrategy; + } + + @JsonProperty(PROVISION_ACTION_PROPERTY) + @ApiModelProperty(name = PROVISION_ACTION_PROPERTY) + public ProvisionAction getProvisionAction() { + return provisionAction; + } + + @JsonProperty(STACK_NAME) + @ApiModelProperty(name = STACK_NAME) + public String getStackName() { + return stackName; + } + + @JsonProperty(STACK_VERSION) + @ApiModelProperty(name = STACK_VERSION) + public String getStackVersion() { + return stackVersion; + } + + @JsonProperty(SERVICES) + @ApiModelProperty(name = SERVICES) + public Set<Service> getServices() { + return services; + } + + @JsonProperty(COMPONENTS) + @ApiModelProperty(name = COMPONENTS) + public Set<Component> getComponents() { + return components; + } + + @JsonIgnore + @ApiIgnore + public Configuration getConfiguration() { + return configuration; + } + + @JsonProperty(Configurable.CONFIGURATIONS) + @ApiModelProperty(name = Configurable.CONFIGURATIONS) + public Collection<Map<String, Map<String, ?>>> getConfigurationContents() { + return Configurable.convertConfigToMap(configuration); + } + +// ------- inner classes ------- + + public enum OperationType { + ADD_SERVICE, DELETE_SERVICE, MOVE_SERVICE + } + + public static class Component { + static final String COMPONENT_NAME = "component_name"; + static final String FQDN = "fqdn"; + + private String name; + private String fqdn; + + public static final Component of(String name, String fqdn) { + Component component = new Component(); + component.setName(name); + component.setFqdn(fqdn); + return component; + } + + @JsonProperty(COMPONENT_NAME) + @ApiModelProperty(name = COMPONENT_NAME) + public String getName() { + return name; + } + + @JsonProperty(COMPONENT_NAME) + public void setName(String name) { + this.name = name; + } + + @JsonProperty(FQDN) + @ApiModelProperty(name = FQDN) + public String getFqdn() { + return fqdn; + } + + @JsonProperty(FQDN) + public void setFqdn(String fqdn) { + this.fqdn = fqdn; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Component component = (Component) o; + return Objects.equals(name, component.name) && + Objects.equals(fqdn, component.fqdn); + } + + @Override + public int hashCode() { + return Objects.hash(name, fqdn); + } + } + + @ApiModel + public static class Service { + static final String NAME = "name"; + + private String name; + + public static final Service of(String name) { + Service service = new Service(); + service.setName(name); + return service; + } + + @JsonProperty(NAME) + @ApiModelProperty(name = NAME) + public String getName() { + return name; + } + + @JsonProperty(NAME) + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Service service = (Service) o; + return Objects.equals(name, service.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + } +} diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/Configurable.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/Configurable.java new file mode 100644 index 0000000..e8f780d --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/Configurable.java @@ -0,0 +1,147 @@ +/* + * 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.topology; + +import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.PROPERTIES_ATTRIBUTES_PROPERTY_ID; +import static org.apache.ambari.server.controller.internal.BlueprintResourceProvider.PROPERTIES_PROPERTY_ID; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +import org.apache.ambari.annotations.ApiIgnore; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; + +import io.swagger.annotations.ApiModelProperty; + +/** + * Provides support for JSON serializaion of {@link Configuration} objects. Can handle both plain JSON and Ambari style + * flattened JSON such as {@code "hdfs-site/properties/dfs.replication": "3"}. Objects may implement this interface or + * call its static utility methods. + */ +public interface Configurable { + + public static final String CONFIGURATIONS = "configurations"; + + @JsonIgnore + @ApiIgnore + void setConfiguration(Configuration configuration); + + @JsonIgnore + @ApiIgnore + Configuration getConfiguration(); + + @JsonProperty(CONFIGURATIONS) + @ApiModelProperty(name = CONFIGURATIONS) + default void setConfigs(Collection<? extends Map<String, ?>> configs) { + setConfiguration(parseConfigs(configs)); + } + + @JsonProperty(CONFIGURATIONS) + @ApiModelProperty(name = CONFIGURATIONS) + default Collection<Map<String, Map<String, ?>>> getConfigs() { + return convertConfigToMap(getConfiguration()); + } + + /** + * Parses configuration maps The configs can be in fully structured JSON, e.g. + * <code> + * [{"hdfs-site": + * "properties": { + * ""dfs.replication": "3", + * ... + * }, + * properties_attributes: {} + * }] + * </code> + * or flattened like + * <code> + * [{ + * "hdfs-site/properties/dfs.replication": "3", + * ... + * }] + * </code> + * In the latter case it calls {@link ConfigurationFactory#getConfiguration(Collection)} + * @param configs + * @return + */ + static Configuration parseConfigs(@Nullable Collection<? extends Map<String, ?>> configs) { + Configuration configuration; + + if (null == configs) { + configuration = new Configuration(new HashMap<>(), new HashMap<>()); + } + else if (!configs.isEmpty() && configs.iterator().next().keySet().iterator().next().contains("/")) { + // Configuration has keys with slashes like "zk.cfg/properties/dataDir" means it is coming through + // the resource framework and must be parsed with configuration factories + configuration = new ConfigurationFactory().getConfiguration((Collection<Map<String, String>>)configs); + } + else { + // If the configuration does not have keys with slashes it means it is coming from plain JSON and needs to be + // parsed accordingly. + Map<String, Map<String, String>> allProperties = new HashMap<>(); + Map<String, Map<String, Map<String, String>>> allAttributes = new HashMap<>(); + configs.forEach( item -> { + String configName = item.keySet().iterator().next(); + Map<String, Object> configData = (Map<String, Object>) item.get(configName); + if (configData.containsKey(PROPERTIES_PROPERTY_ID)) { + Map<String, String> properties = (Map<String, String>)configData.get(PROPERTIES_PROPERTY_ID); + allProperties.put(configName, properties); + } + if (configData.containsKey(PROPERTIES_ATTRIBUTES_PROPERTY_ID)) { + Map<String, Map<String, String>> attributes = + (Map<String, Map<String, String>>)configData.get(PROPERTIES_ATTRIBUTES_PROPERTY_ID); + allAttributes.put(configName, attributes); + } + }); + configuration = new Configuration(allProperties, allAttributes); + } + return configuration; + } + + /** + * Converts {@link Configuration} objects to a collection easily serializable to Json + * @param configuration the configuration to convert + * @return the resulting collection + */ + static Collection<Map<String, Map<String, ?>>> convertConfigToMap(Configuration configuration) { + Collection<Map<String, Map<String, ?>>> configurations = new ArrayList<>(); + Set<String> allConfigTypes = Sets.union(configuration.getProperties().keySet(), configuration.getAttributes().keySet()); + for (String configType: allConfigTypes) { + Map<String, Map<String, ? extends Object>> configData = new HashMap<>(); + if (configuration.getProperties().containsKey(configType)) { + configData.put(PROPERTIES_PROPERTY_ID, configuration.getProperties().get(configType)); + } + if (configuration.getAttributes().containsKey(configType)) { + configData.put(PROPERTIES_ATTRIBUTES_PROPERTY_ID, configuration.getAttributes().get(configType)); + } + configurations.add(ImmutableMap.of(configType, configData)); + } + return configurations; + } + +} diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/AddServiceRequestTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/AddServiceRequestTest.java new file mode 100644 index 0000000..2ed98b7 --- /dev/null +++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/AddServiceRequestTest.java @@ -0,0 +1,263 @@ +/* + * 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 static org.apache.ambari.server.controller.AddServiceRequest.COMPONENTS; +import static org.apache.ambari.server.controller.AddServiceRequest.Component; +import static org.apache.ambari.server.controller.AddServiceRequest.OperationType.ADD_SERVICE; +import static org.apache.ambari.server.controller.AddServiceRequest.SERVICES; +import static org.apache.ambari.server.controller.AddServiceRequest.STACK_NAME; +import static org.apache.ambari.server.controller.AddServiceRequest.STACK_VERSION; +import static org.apache.ambari.server.controller.AddServiceRequest.Service; +import static org.apache.ambari.server.controller.internal.BaseClusterRequest.PROVISION_ACTION_PROPERTY; +import static org.apache.ambari.server.controller.internal.ProvisionAction.INSTALL_AND_START; +import static org.apache.ambari.server.controller.internal.ProvisionAction.INSTALL_ONLY; +import static org.apache.ambari.server.controller.internal.ProvisionClusterRequest.CONFIG_RECOMMENDATION_STRATEGY; +import static org.apache.ambari.server.serveraction.kerberos.KerberosServerAction.OPERATION_TYPE; +import static org.apache.ambari.server.topology.ConfigRecommendationStrategy.ALWAYS_APPLY; +import static org.apache.ambari.server.topology.ConfigRecommendationStrategy.NEVER_APPLY; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +import org.apache.ambari.server.controller.internal.ProvisionAction; +import org.apache.ambari.server.topology.ConfigRecommendationStrategy; +import org.apache.ambari.server.topology.Configurable; +import org.apache.ambari.server.topology.Configuration; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.io.Resources; + +/** + * Tests for {@link AddServiceRequest} serialization / deserialization / syntax validation + */ +public class AddServiceRequestTest { + + private static String REQUEST_ALL_FIELDS_SET; + private static String REQUEST_MINIMAL_SERVICES_AND_COMPONENTS; + private static String REQUEST_MINIMAL_SERVICES_ONLY; + private static String REQUEST_MINIMAL_COMPONENTS_ONLY; + private static String REQUEST_INVALID_NO_SERVICES_AND_COMPONENTS; + private static String REQUEST_INVALID_INVALID_FIELD; + + + private ObjectMapper mapper = new ObjectMapper(); + + @BeforeClass + public static void setUpClass() { + REQUEST_ALL_FIELDS_SET = read("add_service_api/request1.json"); + REQUEST_MINIMAL_SERVICES_AND_COMPONENTS = read("add_service_api/request2.json"); + REQUEST_MINIMAL_SERVICES_ONLY = read("add_service_api/request3.json"); + REQUEST_MINIMAL_COMPONENTS_ONLY = read("add_service_api/request4.json"); + REQUEST_INVALID_NO_SERVICES_AND_COMPONENTS = read("add_service_api/request_invalid_1.json"); + REQUEST_INVALID_INVALID_FIELD = read("add_service_api/request_invalid_2.json"); + } + + @Test + public void testDeserialize_basic() throws Exception { + AddServiceRequest request = mapper.readValue(REQUEST_ALL_FIELDS_SET, AddServiceRequest.class); + + assertEquals(ADD_SERVICE, request.getOperationType()); + assertEquals(ALWAYS_APPLY, request.getRecommendationStrategy()); + assertEquals(INSTALL_ONLY, request.getProvisionAction()); + assertEquals("HDP", request.getStackName()); + assertEquals("3.0", request.getStackVersion()); + + Configuration configuration = request.getConfiguration(); + assertEquals( + ImmutableMap.of("storm-site", ImmutableMap.of("final", ImmutableMap.of("fs.defaultFS", "true"))), + configuration.getAttributes()); + assertEquals( + ImmutableMap.of("storm-site", ImmutableMap.of("ipc.client.connect.max.retries", "50")), + configuration.getProperties()); + + assertEquals( + ImmutableSet.of(Component.of("NIMBUS", "c7401.ambari.apache.org"), Component.of("BEACON_SERVER", "c7402.ambari.apache.org")), + request.getComponents()); + + assertEquals( + ImmutableSet.of(Service.of("STORM"), Service.of("BEACON")), + request.getServices()); + + } + + @Test + public void testDeserialize_defaultAndEmptyValues() throws Exception { + AddServiceRequest request = mapper.readValue(REQUEST_MINIMAL_SERVICES_AND_COMPONENTS, AddServiceRequest.class); + + // filled-out values + assertEquals( + ImmutableSet.of(Component.of("NIMBUS", "c7401.ambari.apache.org"), Component.of("BEACON_SERVER", "c7402.ambari.apache.org")), + request.getComponents()); + + assertEquals( + ImmutableSet.of(Service.of("STORM"), Service.of("BEACON")), + request.getServices()); + + // default / empty values + assertEquals(ADD_SERVICE, request.getOperationType()); + assertEquals(NEVER_APPLY, request.getRecommendationStrategy()); + assertEquals(INSTALL_AND_START, request.getProvisionAction()); + assertNull(request.getStackName()); + assertNull(request.getStackVersion()); + + Configuration configuration = request.getConfiguration(); + assertTrue(configuration.getFullAttributes().isEmpty()); + assertTrue(configuration.getFullProperties().isEmpty()); + } + + @Test + public void testDeserialize_onlyServices() throws Exception { + AddServiceRequest request = mapper.readValue(REQUEST_MINIMAL_SERVICES_ONLY, AddServiceRequest.class); + + // filled-out values + assertEquals( + ImmutableSet.of(Service.of("STORM"), Service.of("BEACON")), + request.getServices()); + + // default / empty values + assertEquals(ADD_SERVICE, request.getOperationType()); + assertEquals(NEVER_APPLY, request.getRecommendationStrategy()); + assertEquals(INSTALL_AND_START, request.getProvisionAction()); + assertNull(request.getStackName()); + assertNull(request.getStackVersion()); + + Configuration configuration = request.getConfiguration(); + assertTrue(configuration.getFullAttributes().isEmpty()); + assertTrue(configuration.getFullProperties().isEmpty()); + + assertTrue(request.getComponents().isEmpty()); + } + + @Test + public void testDeserialize_onlyComponents() throws Exception { + AddServiceRequest request = mapper.readValue(REQUEST_MINIMAL_COMPONENTS_ONLY, AddServiceRequest.class); + + // filled-out values + assertEquals( + ImmutableSet.of(Component.of("NIMBUS", "c7401.ambari.apache.org"), Component.of("BEACON_SERVER", "c7402.ambari.apache.org")), + request.getComponents()); + + // default / empty values + assertEquals(ADD_SERVICE, request.getOperationType()); + assertEquals(NEVER_APPLY, request.getRecommendationStrategy()); + assertEquals(INSTALL_AND_START, request.getProvisionAction()); + assertNull(request.getStackName()); + assertNull(request.getStackVersion()); + + Configuration configuration = request.getConfiguration(); + assertTrue(configuration.getFullAttributes().isEmpty()); + assertTrue(configuration.getFullProperties().isEmpty()); + + assertTrue(request.getServices().isEmpty()); + } + + @Test(expected = JsonProcessingException.class) + public void testDeserialize_invalid_noServicesAndComponents() throws Exception { + mapper.readValue(REQUEST_INVALID_NO_SERVICES_AND_COMPONENTS, AddServiceRequest.class); + } + + @Test(expected = JsonProcessingException.class) + public void testDeserialize_invalid_invalidField() throws Exception { + mapper.readValue(REQUEST_INVALID_INVALID_FIELD, AddServiceRequest.class); + } + + @Test + public void testSerialize_basic() throws Exception { + AddServiceRequest request = mapper.readValue(REQUEST_ALL_FIELDS_SET, AddServiceRequest.class); + + Map<String, ?> serialized = serialize(request); + + assertEquals(AddServiceRequest.OperationType.ADD_SERVICE.name(), serialized.get(OPERATION_TYPE)); + assertEquals(ConfigRecommendationStrategy.ALWAYS_APPLY.name(), serialized.get(CONFIG_RECOMMENDATION_STRATEGY)); + assertEquals(ProvisionAction.INSTALL_ONLY.name(), serialized.get(PROVISION_ACTION_PROPERTY)); + assertEquals("HDP", serialized.get(STACK_NAME)); + assertEquals("3.0", serialized.get(STACK_VERSION)); + + assertEquals( + ImmutableSet.of(ImmutableMap.of(Service.NAME, "BEACON"), ImmutableMap.of(Service.NAME, "STORM")), + ImmutableSet.copyOf((List<String>) serialized.get(SERVICES)) ); + + assertEquals( + ImmutableSet.of( + ImmutableMap.of(Component.COMPONENT_NAME, "NIMBUS", Component.FQDN, "c7401.ambari.apache.org"), + ImmutableMap.of(Component.COMPONENT_NAME, "BEACON_SERVER", Component.FQDN, "c7402.ambari.apache.org")), + ImmutableSet.copyOf((List<String>) serialized.get(COMPONENTS)) ); + + assertEquals( + ImmutableList.of( + ImmutableMap.of( + "storm-site", + ImmutableMap.of( + "properties", ImmutableMap.of("ipc.client.connect.max.retries", "50"), + "properties_attributes", ImmutableMap.of("final", ImmutableMap.of("fs.defaultFS", "true")) + ) + ) + ), + serialized.get(Configurable.CONFIGURATIONS) + ); + } + + @Test + public void testSerialize_EmptyOmitted() throws Exception { + AddServiceRequest request = mapper.readValue(REQUEST_MINIMAL_SERVICES_ONLY, AddServiceRequest.class); + Map<String, ?> serialized = serialize(request); + + assertEquals(AddServiceRequest.OperationType.ADD_SERVICE.name(), serialized.get(OPERATION_TYPE)); + assertEquals(ProvisionAction.INSTALL_AND_START.name(), serialized.get(PROVISION_ACTION_PROPERTY)); + assertEquals( + ImmutableSet.of(ImmutableMap.of(Service.NAME, "BEACON"), ImmutableMap.of(Service.NAME, "STORM")), + ImmutableSet.copyOf((List<String>) serialized.get(SERVICES)) ); + + assertFalse(serialized.containsKey(STACK_NAME)); + assertFalse(serialized.containsKey(STACK_VERSION)); + assertFalse(serialized.containsKey(Configurable.CONFIGURATIONS)); + assertFalse(serialized.containsKey(COMPONENTS)); + + } + + private Map<String, ?> serialize(AddServiceRequest request) throws IOException { + String serialized = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(request); + return mapper.readValue(serialized, new TypeReference<Map<String, ?>>() {}); + } + + private static String read(String resourceName) { + try { + return Resources.toString(Resources.getResource(resourceName), StandardCharsets.UTF_8); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } + +} \ No newline at end of file diff --git a/ambari-server/src/test/java/org/apache/ambari/server/topology/ConfigurableTest.java b/ambari-server/src/test/java/org/apache/ambari/server/topology/ConfigurableTest.java new file mode 100644 index 0000000..69aec1f --- /dev/null +++ b/ambari-server/src/test/java/org/apache/ambari/server/topology/ConfigurableTest.java @@ -0,0 +1,104 @@ +/* + * 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.topology; + +import static org.junit.Assert.assertEquals; + +import java.net.URL; + +import org.junit.Before; +import org.junit.Test; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.Resources; + +public class ConfigurableTest { + public static final String JSON_LOCATION = "add_service_api/configurable.json"; + public static final String JSON_LOCATION2 = "add_service_api/configurable2.json"; + + private TestConfigurable configurable; + private ObjectMapper mapper; + + @Before + public void setUp() throws Exception { + mapper = new ObjectMapper(); + URL url = Resources.getResource(JSON_LOCATION); + configurable = new ObjectMapper().readValue(url, TestConfigurable.class); + } + + /** + * Parse normal JSON configuration + */ + @Test + public void testParseConfigurable() throws Exception { + assertEquals(ImmutableMap.of("zoo.cfg", ImmutableMap.of("dataDir", "/zookeeper1")), + configurable.getConfiguration().getProperties()); + assertEquals( + ImmutableMap.of("zoo.cfg", + ImmutableMap.of("final", + ImmutableMap.of("someProp", "true"))), + configurable.getConfiguration().getAttributes()); + } + + /** + * Deserialize normal JSON configuration + */ + @Test + public void testSerializaDeserialize() throws Exception { + String persisted = mapper.writeValueAsString(configurable); + Configurable restored = mapper.readValue(persisted, TestConfigurable.class); + assertEquals(configurable.getConfiguration().getProperties(), restored.getConfiguration().getProperties()); + assertEquals(configurable.getConfiguration().getAttributes(), restored.getConfiguration().getAttributes()); + } + + /** + * Parse flattened configuration + */ + @Test + public void testParseConfigurableFromResoueceManager() throws Exception{ + mapper = new ObjectMapper(); + URL url = Resources.getResource(JSON_LOCATION2); + configurable = new ObjectMapper().readValue(url, TestConfigurable.class); + + assertEquals(ImmutableMap.of("zoo.cfg", ImmutableMap.of("dataDir", "/zookeeper1")), + configurable.getConfiguration().getProperties()); + assertEquals( + ImmutableMap.of("zoo.cfg", + ImmutableMap.of("final", + ImmutableMap.of("someProp", "true"))), + configurable.getConfiguration().getAttributes()); + } + +} + +class TestConfigurable implements Configurable { + Configuration configuration; + + @Override + public Configuration getConfiguration() { + return configuration; + } + + @Override + public void setConfiguration(Configuration configuration) { + this.configuration = configuration; + } + +} \ No newline at end of file diff --git a/ambari-server/src/test/resources/add_service_api/configurable.json b/ambari-server/src/test/resources/add_service_api/configurable.json new file mode 100644 index 0000000..cefa23b --- /dev/null +++ b/ambari-server/src/test/resources/add_service_api/configurable.json @@ -0,0 +1,16 @@ +{ + "configurations" : [ + { + "zoo.cfg" : { + "properties" : { + "dataDir" : "/zookeeper1" + }, + "properties_attributes": { + "final": { + "someProp": "true" + } + } + } + } + ] +} \ No newline at end of file diff --git a/ambari-server/src/test/resources/add_service_api/configurable2.json b/ambari-server/src/test/resources/add_service_api/configurable2.json new file mode 100644 index 0000000..c27ecf4 --- /dev/null +++ b/ambari-server/src/test/resources/add_service_api/configurable2.json @@ -0,0 +1,8 @@ +{ + "configurations" : [ + { + "zoo.cfg/properties/dataDir" : "/zookeeper1", + "zoo.cfg/properties_attributes/final/someProp": "true" + } + ] +} \ No newline at end of file diff --git a/ambari-server/src/test/resources/add_service_api/request1.json b/ambari-server/src/test/resources/add_service_api/request1.json new file mode 100644 index 0000000..cf4ce62 --- /dev/null +++ b/ambari-server/src/test/resources/add_service_api/request1.json @@ -0,0 +1,39 @@ +{ + "operation_type" : "ADD_SERVICE", + "config_recommendation_strategy" : "ALWAYS_APPLY", + "provision_action" : "INSTALL_ONLY", + "stack_name" : "HDP", + "stack_version" : "3.0", + + "services": [ + { "name" : "STORM" }, + { "name" : "BEACON" } + ], + + "components" : [ + { + "component_name" : "NIMBUS", + "fqdn" : "c7401.ambari.apache.org" + }, + { + "component_name" : "BEACON_SERVER", + "fqdn" : "c7402.ambari.apache.org" + } + ], + + "configurations" : [ + { + "storm-site" : { + "properties_attributes" : { + "final" : { + "fs.defaultFS" : "true" + } + }, + "properties" : { + "ipc.client.connect.max.retries" : "50" + } + } + } + ] + +} \ No newline at end of file diff --git a/ambari-server/src/test/resources/add_service_api/request2.json b/ambari-server/src/test/resources/add_service_api/request2.json new file mode 100644 index 0000000..f0e540f --- /dev/null +++ b/ambari-server/src/test/resources/add_service_api/request2.json @@ -0,0 +1,18 @@ +{ + "services": [ + { "name" : "STORM" }, + { "name" : "BEACON" } + ], + + "components" : [ + { + "component_name" : "NIMBUS", + "fqdn" : "c7401.ambari.apache.org" + }, + { + "component_name" : "BEACON_SERVER", + "fqdn" : "c7402.ambari.apache.org" + } + ] + +} \ No newline at end of file diff --git a/ambari-server/src/test/resources/add_service_api/request3.json b/ambari-server/src/test/resources/add_service_api/request3.json new file mode 100644 index 0000000..4e715c1 --- /dev/null +++ b/ambari-server/src/test/resources/add_service_api/request3.json @@ -0,0 +1,6 @@ +{ + "services": [ + { "name" : "STORM" }, + { "name" : "BEACON" } + ] +} \ No newline at end of file diff --git a/ambari-server/src/test/resources/add_service_api/request4.json b/ambari-server/src/test/resources/add_service_api/request4.json new file mode 100644 index 0000000..b6fd0ea --- /dev/null +++ b/ambari-server/src/test/resources/add_service_api/request4.json @@ -0,0 +1,13 @@ +{ + "components" : [ + { + "component_name" : "NIMBUS", + "fqdn" : "c7401.ambari.apache.org" + }, + { + "component_name" : "BEACON_SERVER", + "fqdn" : "c7402.ambari.apache.org" + } + ] + +} \ No newline at end of file diff --git a/ambari-server/src/test/resources/add_service_api/request_invalid_1.json b/ambari-server/src/test/resources/add_service_api/request_invalid_1.json new file mode 100644 index 0000000..432970f --- /dev/null +++ b/ambari-server/src/test/resources/add_service_api/request_invalid_1.json @@ -0,0 +1,23 @@ +{ + "operation_type" : "ADD_SERVICE", + "config_recommendation_strategy" : "ALWAYS_APPLY", + "provision_action" : "INSTALL_ONLY", + "stack_name" : "HDP", + "stack_version" : "3.0", + + "configurations" : [ + { + "storm-site" : { + "properties_attributes" : { + "final" : { + "fs.defaultFS" : "true" + } + }, + "properties" : { + "ipc.client.connect.max.retries" : "50" + } + } + } + ] + +} \ No newline at end of file diff --git a/ambari-server/src/test/resources/add_service_api/request_invalid_2.json b/ambari-server/src/test/resources/add_service_api/request_invalid_2.json new file mode 100644 index 0000000..a017c0b --- /dev/null +++ b/ambari-server/src/test/resources/add_service_api/request_invalid_2.json @@ -0,0 +1,40 @@ +{ + "mpack" : "HDPCORE-2.0", + "operation_type" : "ADD_SERVICE", + "config_recommendation_strategy" : "ALWAYS_APPLY", + "provision_action" : "INSTALL_ONLY", + "stack_name" : "HDP", + "stack_version" : "3.0", + + "services": [ + { "name" : "STORM" }, + { "name" : "BEACON" } + ], + + "components" : [ + { + "component_name" : "NIMBUS", + "fqdn" : "c7401.ambari.apache.org" + }, + { + "component_name" : "BEACON_SERVER", + "fqdn" : "c7402.ambari.apache.org" + } + ], + + "configurations" : [ + { + "storm-site" : { + "properties_attributes" : { + "final" : { + "fs.defaultFS" : "true" + } + }, + "properties" : { + "ipc.client.connect.max.retries" : "50" + } + } + } + ] + +} \ No newline at end of file