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

[ambari] branch trunk updated: AMBARI-24901. Handle complex "Add Service" request in ServiceResourceProvider (#2611)

This is an automated email from the ASF dual-hosted git repository.

adoroszlai 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 1d87a0a  AMBARI-24901. Handle complex "Add Service" request in ServiceResourceProvider (#2611)
1d87a0a is described below

commit 1d87a0a1ee8d5202ed1894bc2917fc8ab7d569fa
Author: Doroszlai, Attila <64...@users.noreply.github.com>
AuthorDate: Thu Nov 15 21:38:53 2018 +0100

    AMBARI-24901. Handle complex "Add Service" request in ServiceResourceProvider (#2611)
---
 .../ambari/server/api/services/ServiceService.java |   8 +-
 .../server/controller/AddServiceRequest.java       |  26 ++++-
 .../internal/ServiceResourceProvider.java          |  38 ++++++
 .../ambari/server/controller/internal/Stack.java   |   5 +
 .../server/topology/addservice/AddServiceInfo.java |  47 ++++++++
 .../addservice/AddServiceOrchestrator.java         | 127 +++++++++++++++++++++
 .../addservice/ResourceProviderAdapter.java        |  95 +++++++++++++++
 .../server/controller/AddServiceRequestTest.java   |   2 +-
 8 files changed, 339 insertions(+), 9 deletions(-)

diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ServiceService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ServiceService.java
index d12cbc4..9440f39 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ServiceService.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ServiceService.java
@@ -153,7 +153,7 @@ public class ServiceService extends BaseService {
   @Path("{serviceName}")
   @Produces(MediaType.TEXT_PLAIN)
   @ApiOperation(value = "Creates a service",
-      nickname = "ServiceService#createServices"
+      nickname = "ServiceService#createService"
   )
   @ApiImplicitParams({
       @ApiImplicitParam(dataType = SERVICE_REQUEST_TYPE, paramType = PARAM_TYPE_BODY)
@@ -176,7 +176,7 @@ public class ServiceService extends BaseService {
 
   /**
    * Handles: POST /clusters/{clusterId}/services
-   * Create multiple services.
+   * Create services, possibly more than one.
    *
    * @param body        http body
    * @param headers     http headers
@@ -185,8 +185,8 @@ public class ServiceService extends BaseService {
    */
   @POST
   @Produces(MediaType.TEXT_PLAIN)
-  @ApiOperation(value = "Creates a service",
-      nickname = "ServiceService#createService"
+  @ApiOperation(value = "Creates services",
+      nickname = "ServiceService#createServices"
   )
   @ApiImplicitParams({
       @ApiImplicitParam(dataType = SERVICE_REQUEST_TYPE, paramType = PARAM_TYPE_BODY, allowMultiple = true)
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
index 83a66d8..22a32ce 100644
--- 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
@@ -22,7 +22,11 @@ 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 static org.apache.ambari.server.controller.internal.ServiceResourceProvider.OPERATION_TYPE;
+import static org.apache.ambari.server.topology.Configurable.CONFIGURATIONS;
 
+import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
@@ -39,6 +43,8 @@ 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 com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableSet;
 
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
@@ -50,12 +56,16 @@ import io.swagger.annotations.ApiModelProperty;
 @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";
 
+  public static final Set<String> TOP_LEVEL_PROPERTIES = ImmutableSet.of(
+    OPERATION_TYPE, CONFIG_RECOMMENDATION_STRATEGY, PROVISION_ACTION_PROPERTY,
+    STACK_NAME, STACK_VERSION, SERVICES, COMPONENTS, CONFIGURATIONS
+  );
+
   private final OperationType operationType;
   private final ConfigRecommendationStrategy recommendationStrategy;
   private final ProvisionAction provisionAction;
@@ -73,7 +83,7 @@ public final class AddServiceRequest {
                            @JsonProperty(STACK_VERSION) String stackVersion,
                            @JsonProperty(SERVICES) Set<Service> services,
                            @JsonProperty(COMPONENTS)Set<Component> components,
-                           @JsonProperty(Configurable.CONFIGURATIONS) Collection<? extends Map<String, ?>> configs) {
+                           @JsonProperty(CONFIGURATIONS) Collection<? extends Map<String, ?>> configs) {
     this(operationType, recommendationStrategy, provisionAction, stackName, stackVersion, services, components,
       Configurable.parseConfigs(configs));
   }
@@ -99,6 +109,14 @@ public final class AddServiceRequest {
     checkArgument(!this.services.isEmpty() || !this.components.isEmpty(), "Either services or components must be specified");
   }
 
+  // TODO move to JsonUtils -- pick part of 0252c08d86f
+  public static AddServiceRequest of(String json) {
+    try {
+      return new ObjectMapper().readValue(json, AddServiceRequest.class);
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+  }
 
   @JsonProperty(OPERATION_TYPE)
   @ApiModelProperty(name = OPERATION_TYPE)
@@ -148,8 +166,8 @@ public final class AddServiceRequest {
     return configuration;
   }
 
-  @JsonProperty(Configurable.CONFIGURATIONS)
-  @ApiModelProperty(name = Configurable.CONFIGURATIONS)
+  @JsonProperty(CONFIGURATIONS)
+  @ApiModelProperty(name = CONFIGURATIONS)
   public Collection<Map<String, Map<String, ?>>> getConfigurationContents() {
     return Configurable.convertConfigToMap(configuration);
   }
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ServiceResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ServiceResourceProvider.java
index 936d767..6b561e8 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ServiceResourceProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ServiceResourceProvider.java
@@ -38,6 +38,8 @@ import org.apache.ambari.server.ParentObjectNotFoundException;
 import org.apache.ambari.server.RoleCommand;
 import org.apache.ambari.server.ServiceNotFoundException;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.controller.AddServiceRequest;
+import org.apache.ambari.server.controller.AddServiceRequest.OperationType;
 import org.apache.ambari.server.controller.AmbariManagementController;
 import org.apache.ambari.server.controller.KerberosHelper;
 import org.apache.ambari.server.controller.MaintenanceStateHelper;
@@ -76,6 +78,7 @@ import org.apache.ambari.server.state.ServiceInfo;
 import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.state.State;
 import org.apache.ambari.server.topology.STOMPComponentsDeleteHandler;
+import org.apache.ambari.server.topology.addservice.AddServiceOrchestrator;
 import org.apache.ambari.spi.RepositoryType;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang.StringUtils;
@@ -136,6 +139,8 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
   private static final String KERBEROS_ENABLED_PROPERTY_ID = PropertyHelper.getPropertyId(
       "ServiceInfo", "kerberos_enabled");
 
+  public static final String OPERATION_TYPE = "operation_type";
+
   protected static final String SERVICE_REPOSITORY_STATE = "ServiceInfo/repository_state";
 
   //Parameters from the predicate
@@ -180,6 +185,7 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
     PROPERTY_IDS.add(SSO_INTEGRATION_DESIRED_PROPERTY_ID);
     PROPERTY_IDS.add(SSO_INTEGRATION_REQUIRES_KERBEROS_PROPERTY_ID);
     PROPERTY_IDS.add(KERBEROS_ENABLED_PROPERTY_ID);
+    PROPERTY_IDS.add(OPERATION_TYPE);
 
     // keys
     KEY_PROPERTY_IDS.put(Resource.Type.Service, SERVICE_SERVICE_NAME_PROPERTY_ID);
@@ -197,6 +203,9 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
   @Inject
   private STOMPComponentsDeleteHandler STOMPComponentsDeleteHandler;
 
+  @Inject
+  private AddServiceOrchestrator addServiceOrchestrator;
+
   /**
    * Used to lookup the repository when creating services.
    */
@@ -232,6 +241,15 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
              ResourceAlreadyExistsException,
              NoSuchParentResourceException {
 
+    if (request.getProperties().size() == 1) {
+      Map<String, Object> requestProperties = request.getProperties().iterator().next();
+      if (isAddServiceRequest(requestProperties)) {
+        processAddServiceRequest(requestProperties, request.getRequestInfoProperties());
+        notifyCreate(Resource.Type.Service, request);
+        return getRequestStatus(null);
+      }
+    }
+
     final Set<ServiceRequest> requests = new HashSet<>();
     for (Map<String, Object> propertyMap : request.getProperties()) {
       requests.add(getRequest(propertyMap));
@@ -376,6 +394,7 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
         }
       }
     }
+    unsupportedProperties.removeAll(AddServiceRequest.TOP_LEVEL_PROPERTIES);
     return unsupportedProperties;
   }
 
@@ -1190,4 +1209,23 @@ public class ServiceResourceProvider extends AbstractControllerResourceProvider
     }
 
   }
+
+  private static boolean isAddServiceRequest(Map<String, Object> properties) {
+    return OperationType.ADD_SERVICE.name().equals(properties.get(OPERATION_TYPE));
+  }
+
+  private void processAddServiceRequest(Map<String, Object> requestProperties, Map<String, String> requestInfoProperties) throws NoSuchParentResourceException {
+    AddServiceRequest request = createAddServiceRequest(requestProperties, requestInfoProperties);
+    String clusterName = String.valueOf(requestProperties.get(SERVICE_CLUSTER_NAME_PROPERTY_ID));
+    try {
+      addServiceOrchestrator.processAddServiceRequest(getManagementController().getClusters().getCluster(clusterName), request);
+    } catch (AmbariException e) {
+      throw new NoSuchParentResourceException(e.getMessage(), e);
+    }
+  }
+
+  private static AddServiceRequest createAddServiceRequest(Map<String, Object> requestProperties, Map<String, String> requestInfoProperties) {
+    return AddServiceRequest.of(requestInfoProperties.get(Request.REQUEST_INFO_BODY_PROPERTY));
+  }
+
 }
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/Stack.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/Stack.java
index 0de4244..1c85d88 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/Stack.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/Stack.java
@@ -44,6 +44,7 @@ import org.apache.ambari.server.state.DependencyInfo;
 import org.apache.ambari.server.state.PropertyDependencyInfo;
 import org.apache.ambari.server.state.PropertyInfo;
 import org.apache.ambari.server.state.ServiceInfo;
+import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.state.ValueAttributesInfo;
 import org.apache.ambari.server.topology.Cardinality;
 import org.apache.ambari.server.topology.Configuration;
@@ -151,6 +152,10 @@ public class Stack {
     this(stack.getStackName(), stack.getStackVersion(), ambariManagementController);
   }
 
+  public Stack(StackId stackId, AmbariManagementController ambariManagementController) throws AmbariException {
+    this(stackId.getStackName(), stackId.getStackVersion(), ambariManagementController);
+  }
+
   /**
    * Constructor.
    *
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/AddServiceInfo.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/AddServiceInfo.java
new file mode 100644
index 0000000..24e530d
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/AddServiceInfo.java
@@ -0,0 +1,47 @@
+/*
+ * 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.addservice;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Processed info for adding new services/components to an existing cluster.
+ */
+public final class AddServiceInfo {
+
+  private final String clusterName;
+  private final Map<String, Map<String, Set<String>>> newServices;
+
+  public AddServiceInfo(String clusterName, Map<String, Map<String, Set<String>>> newServices) {
+    this.clusterName = clusterName;
+    this.newServices = newServices;
+  }
+
+  public String clusterName() {
+    return clusterName;
+  }
+
+  /**
+   * New services to be added to the cluster: service -> component -> host
+   * This should include both explicitly requested services, and services of the requested components.
+   */
+  public Map<String, Map<String, Set<String>>> newServices() {
+    return newServices;
+  }
+}
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/AddServiceOrchestrator.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/AddServiceOrchestrator.java
new file mode 100644
index 0000000..426c833
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/AddServiceOrchestrator.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.topology.addservice;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.controller.AddServiceRequest;
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.internal.Stack;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.StackId;
+import org.apache.ambari.server.state.State;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+public class AddServiceOrchestrator {
+
+  private static final Logger LOG = LoggerFactory.getLogger(AddServiceOrchestrator.class);
+
+  @Inject
+  private ResourceProviderAdapter resourceProviders;
+
+  @Inject
+  private AmbariManagementController controller;
+
+  public void processAddServiceRequest(Cluster cluster, AddServiceRequest request) {
+    LOG.info("Received {} request for {}: {}", request.getOperationType(), cluster.getClusterName(), request);
+
+    AddServiceInfo validatedRequest = validate(cluster, request);
+    AddServiceInfo requestWithLayout = recommendLayout(validatedRequest);
+    createResources(requestWithLayout);
+    createHostTasks(requestWithLayout);
+  }
+
+  /**
+   * Performs basic validation of the request and
+   * fills in details about the requested services and components.
+   *
+   * @return validated information about the requested services
+   */
+  private AddServiceInfo validate(Cluster cluster, AddServiceRequest request) {
+    LOG.info("Validating {}", request);
+
+    Map<String, Map<String, Set<String>>> newServices = new LinkedHashMap<>();
+
+    StackId stackId = new StackId(request.getStackName(), request.getStackVersion());
+    try {
+      Stack stack = new Stack(stackId, controller);
+      Set<String> existingServices = cluster.getServices().keySet();
+      for (AddServiceRequest.Component requestedComponent : request.getComponents()) {
+        String serviceName = stack.getServiceForComponent(requestedComponent.getName());
+        if (serviceName == null) {
+          String msg = String.format("No service found for component %s in stack %s", requestedComponent.getName(), stackId);
+          LOG.error(msg);
+          throw new IllegalArgumentException(msg);
+        }
+        if (existingServices.contains(serviceName)) {
+          String msg = String.format("Service %s already exists in cluster %s", serviceName, cluster.getClusterName());
+          LOG.error(msg);
+          throw new IllegalArgumentException(msg);
+        }
+
+        newServices.computeIfAbsent(serviceName, __ -> new HashMap<>())
+          .computeIfAbsent(requestedComponent.getName(), __ -> new HashSet<>())
+          .add(requestedComponent.getFqdn());
+      }
+    } catch (AmbariException e) {
+      LOG.error("Stack {} not found", stackId);
+      throw new IllegalArgumentException(e);
+    }
+
+    return new AddServiceInfo(cluster.getClusterName(), newServices);
+  }
+
+  /**
+   * Requests layout recommendation from the stack advisor.
+   * @return new request, updated based on the recommended layout
+   * @throws IllegalArgumentException if the request cannot be satisfied
+   */
+  private AddServiceInfo recommendLayout(AddServiceInfo request) {
+    LOG.info("Recommending layout for {}", request);
+    // TODO implement
+    return request;
+  }
+
+  /**
+   * Creates the service, component and host component resources for the request.
+   */
+  private void createResources(AddServiceInfo request) {
+    LOG.info("Creating resources for {}", request);
+    resourceProviders.createServices(request);
+    resourceProviders.createComponents(request);
+    resourceProviders.createHostComponents(request);
+    resourceProviders.updateServiceDesiredState(request, State.INSTALLED);
+    resourceProviders.updateServiceDesiredState(request, State.STARTED);
+  }
+
+  private void createHostTasks(AddServiceInfo request) {
+    LOG.info("Creating host tasks for {}", request);
+    // TODO implement
+  }
+
+}
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/ResourceProviderAdapter.java b/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/ResourceProviderAdapter.java
new file mode 100644
index 0000000..70b730e
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/ResourceProviderAdapter.java
@@ -0,0 +1,95 @@
+/*
+ * 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.addservice;
+
+import static java.util.stream.Collectors.toSet;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ambari.server.controller.internal.RequestImpl;
+import org.apache.ambari.server.controller.internal.ServiceResourceProvider;
+import org.apache.ambari.server.controller.spi.ClusterController;
+import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
+import org.apache.ambari.server.controller.spi.ResourceProvider;
+import org.apache.ambari.server.controller.spi.SystemException;
+import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
+import org.apache.ambari.server.controller.utilities.ClusterControllerHelper;
+import org.apache.ambari.server.state.State;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Creates resources using the resource providers.
+ * Translates {@link AddServiceInfo} to internal requests accepted by those.
+ */
+public class ResourceProviderAdapter {
+
+  private static final Logger LOG = LoggerFactory.getLogger(ResourceProviderAdapter.class);
+
+  public void createServices(AddServiceInfo request) {
+    LOG.info("Creating service resources for {}", request);
+
+    Set<Map<String, Object>> properties = request.newServices().keySet().stream()
+      .map(service -> createServiceRequestProperties(request, service))
+      .collect(toSet());
+
+    Request internalRequest = new RequestImpl(null, properties, null, null);
+    ResourceProvider rp = getClusterController().ensureResourceProvider(Resource.Type.Service);
+    try {
+      rp.createResources(internalRequest);
+    } catch (UnsupportedPropertyException | SystemException | ResourceAlreadyExistsException | NoSuchParentResourceException e) {
+      LOG.error("Error creating services", e);
+      throw new RuntimeException("Error creating services", e);
+    }
+  }
+
+  private static Map<String, Object> createServiceRequestProperties(AddServiceInfo request, String service) {
+    ImmutableMap.Builder<String, Object> properties = ImmutableMap.builder();
+
+    properties.put(ServiceResourceProvider.SERVICE_CLUSTER_NAME_PROPERTY_ID, request.clusterName());
+    properties.put(ServiceResourceProvider.SERVICE_SERVICE_NAME_PROPERTY_ID, service);
+    properties.put(ServiceResourceProvider.SERVICE_SERVICE_STATE_PROPERTY_ID, State.INIT.name());
+
+    return properties.build();
+  }
+
+  private ClusterController getClusterController() {
+    return ClusterControllerHelper.getClusterController();
+  }
+
+  public void createComponents(AddServiceInfo request) {
+    LOG.info("Creating component resources for {}", request);
+    // TODO implement
+  }
+
+  public void createHostComponents(AddServiceInfo request) {
+    LOG.info("Creating host component resources for {}", request);
+    // TODO implement
+  }
+
+  public void updateServiceDesiredState(AddServiceInfo request, State desiredState) {
+    LOG.info("Updating service desired state to {} for {}", desiredState, request);
+    // TODO implement, reuse parts of AmbariContext#createAmbariServiceAndComponentResources
+  }
+}
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
index 2ed98b7..cb8d03b 100644
--- 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
@@ -29,7 +29,7 @@ import static org.apache.ambari.server.controller.internal.BaseClusterRequest.PR
 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.controller.internal.ServiceResourceProvider.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;