http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-admin/src/main/resources/ui/admin-web/app/views/remoteClusters/modals/changePassword.html ---------------------------------------------------------------------- diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/remoteClusters/modals/changePassword.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/remoteClusters/modals/changePassword.html new file mode 100644 index 0000000..a05e13d --- /dev/null +++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/remoteClusters/modals/changePassword.html @@ -0,0 +1,46 @@ +<!-- +* 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. +--> +<div class="modal-header"> + <h3 class="modal-title">{{'users.updateCredentials' | translate}}</h3> +</div> +<div class="modal-body"> + <form class="form-horizontal" novalidate name="form.passwordChangeForm" role="form" > + + <div class="form-group" ng-class="{'has-error' : (form.passwordChangeForm.currentPassword.$error.required && form.passwordChangeForm.submitted)}"> + <label for="" class="col-sm-4 control-label" >{{'users.roles.clusterUser' | translate}}</label> + <div class="col-sm-8"> + <input type="text" name="currentUserName" class="form-control bottom-margin" placeholder="Cluster User" required ng-model="passwordData.currentUserName" autocomplete="off"> + <div class="alert alert-danger no-margin-bottom" ng-show='form.passwordChangeForm.currentUserName.$error.required && form.passwordChangeForm.submitted'>{{'users.alerts.usernameRequired' | translate}}</div> + </div> + </div> + + <div class="form-group no-margin-bottom" ng-class="{'has-error' : (form.passwordChangeForm.password.$error.required && form.passwordChangeForm.submitted) || form.passwordChangeForm.confirmPassword.$error.passwordVerify}"> + <label for="" class="col-sm-4 control-label">{{'users.password' | translate}}</label> + <div class="col-sm-8"> + <input type="password" class="form-control bottom-margin" name="password" placeholder="Password" required ng-model="passwordData.password" autocomplete="off"> + <div class="alert alert-danger no-margin-bottom" ng-show='form.passwordChangeForm.password.$error.required && form.passwordChangeForm.submitted'>{{'users.alerts.passwordRequired' | translate}}</div> + <div class="alert alert-danger no-margin-bottom" ng-show='form.passwordChangeForm.confirmPassword.$error.passwordVerify'>{{'users.alerts.wrongPassword' | translate}}</div> + </div> + + </div> + </form> +</div> +<div class="modal-footer"> + <button class="btn btn-default" ng-click="cancel()">{{'common.controls.cancel' | translate}}</button> + <button class="btn btn-primary" ng-click="ok()">{{'common.controls.ok' | translate}}</button> +</div> \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-admin/src/main/resources/ui/admin-web/app/views/remoteClusters/remoteClusterPage.html ---------------------------------------------------------------------- diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/remoteClusters/remoteClusterPage.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/remoteClusters/remoteClusterPage.html new file mode 100644 index 0000000..0f82360 --- /dev/null +++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/remoteClusters/remoteClusterPage.html @@ -0,0 +1,66 @@ +<!-- +* 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. +--> +<ol class="breadcrumb"> + <li><a href="#/remoteClusters">{{'common.remoteClusters' | translate}}</a></li> + <li class="active">{{'common.register' | translate}}</li> +</ol> +<hr> +<form class="form-horizontal create-user-form" role="form" novalidate name="form" autocomplete="off"> + + <div class="form-group" ng-class="{'has-error' : form.user_name.$error.required && form.submitted}"> + <label for="clustername" class="col-sm-2 control-label">{{'views.clusterName' | translate}} </label> + <div class="col-sm-10"> + <input type="text" id="clustername" class="form-control" ng-pattern="/[a-zA-Z0-9\-]*/" name="cluster_name" placeholder="Ambari Cluster Name" ng-model="cluster.cluster_name" required autocomplete="off"> + <div class="alert alert-danger top-margin" ng-show="form.cluster_name.$error.required && form.submitted"> {{'common.alerts.fieldIsRequired' | translate}}</div> + <div class="alert alert-danger top-margin" ng-show="form.cluster_name.$error.pattern && form.submitted"> {{'views.alerts.noSpecialChars' | translate}}</div> + </div> + </div> + + <div class="form-group" ng-class="{'has-error' : form.user_name.$error.required && form.submitted}"> + <label for="clusterurl" class="col-sm-2 control-label">{{'users.ambariClusterURL' | translate}}</label> + <div class="col-sm-10"> + <input type="text" id="clusterurl" class="form-control" name="cluster_url" placeholder="http://ambari.server:8080/api/v1/clusters/c1" ng-model="cluster.cluster_url" required autocomplete="off"> + <div class="alert alert-danger top-margin" ng-show="form.cluster_url.$error.required && form.submitted"> {{'common.alerts.fieldIsRequired' | translate}}</div> + </div> + </div> + + <div class="form-group" ng-class="{'has-error' : form.user_name.$error.required && form.submitted}"> + <label for="clusteruser" class="col-sm-2 control-label"> {{'users.roles.clusterUser' | translate}}</label> + <div class="col-sm-10"> + <input type="text" id="clusteruser" class="form-control" name="cluster_user" placeholder="Cluster User" ng-model="cluster.cluster_user" required autocomplete="off"> + <div class="alert alert-danger top-margin" ng-show="form.cluster_user.$error.required && form.submitted"> {{'common.alerts.fieldIsRequired' | translate}}</div> + </div> + </div> + + <div class="form-group" ng-class="{'has-error' : (form.password.$error.required && form.submitted) || form.confirmPassword.$error.passwordVerify}"> + <label for="password" class="col-sm-2 control-label">{{'users.password' | translate}}</label> + <div class="col-sm-10"> + <input type="password" id="password" class="form-control bottom-margin userpassword" name="password" placeholder="Password" required ng-model="cluster.cluster_password" autocomplete="off"> + <div class="alert alert-danger" ng-show='form.confirmPassword.$error.passwordVerify'>Wrong Password</div> + <div class="alert alert-danger" ng-show='form.password.$error.required && form.submitted'>{{'common.alerts.passwordRequired' | translate}}</div> + </div> + </div> + + <div class="form-group"> + <div class="col-sm-offset-2 col-sm-10"> + <button class="btn btn-primary pull-right left-margin saveremotecluster" ng-click="registerRemoteCluster()">{{'common.controls.save' | translate}}</button> + <a class="btn btn-default pull-right cancel" href ng-click="cancel()"> {{'common.controls.cancel' | translate}}</a> + </div> + </div> + +</form> http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-server/src/main/java/org/apache/ambari/server/api/resources/RemoteClusterResourceDefinition.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/RemoteClusterResourceDefinition.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/RemoteClusterResourceDefinition.java new file mode 100644 index 0000000..b656a30 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/RemoteClusterResourceDefinition.java @@ -0,0 +1,51 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ambari.server.api.resources; + +import org.apache.ambari.server.controller.spi.Resource; + + +/** + * Remote Cluster resource definition. + */ +public class RemoteClusterResourceDefinition extends BaseResourceDefinition { + + // ----- Constructors ------------------------------------------------------ + + /** + * Construct a view resource definition. + */ + public RemoteClusterResourceDefinition() { + super(Resource.Type.RemoteCluster); + } + + + // ----- ResourceDefinition ------------------------------------------------ + + @Override + public String getPluralName() { + return "remote_clusters"; + } + + @Override + public String getSingularName() { + return "remote_cluster"; + } + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java index 0b77511..9c864b6 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java @@ -437,6 +437,10 @@ public class ResourceInstanceFactoryImpl implements ResourceInstanceFactory { resourceDefinition = new LoggingResourceDefinition(); break; + case RemoteCluster: + resourceDefinition = new RemoteClusterResourceDefinition(); + break; + default: throw new IllegalArgumentException("Unsupported resource type: " + type); } http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-server/src/main/java/org/apache/ambari/server/api/services/RemoteClustersService.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/RemoteClustersService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/RemoteClustersService.java new file mode 100644 index 0000000..d7c7a20 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/RemoteClustersService.java @@ -0,0 +1,139 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ambari.server.api.services; + +import com.google.common.base.Optional; +import org.apache.ambari.server.api.resources.ResourceInstance; +import org.apache.ambari.server.controller.spi.Resource; +import org.apache.ambari.server.security.authorization.AuthorizationException; + +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import java.util.Collections; + + +/** + * Service responsible for Remote Cluster resource requests. + */ +@Path("/remoteclusters") +public class RemoteClustersService extends BaseService { + + /** + * Get the list of all Remote Clusters + * @param headers + * @param ui + * @return collections of all remote clusters + */ + @GET + @Produces("text/plain") + public Response getRemoteClusters(@Context HttpHeaders headers, @Context UriInfo ui) { + return handleRequest(headers, null, ui, Request.Type.GET, createRemoteClusterResource(null)); + } + + + /** + * Create a new RemoteAmbariCluster + * @param body + * @param headers + * @param ui + * @param clusterName + * @return + */ + @POST + @Path("{clusterName}") + @Produces("text/plain") + public Response createRemoteCluster(String body, @Context HttpHeaders headers, @Context UriInfo ui, + @PathParam("clusterName") String clusterName) { + return handleRequest(headers, body, ui, Request.Type.POST, createRemoteClusterResource(clusterName)); + } + + + /** + * Update a Remote Cluster + * @param body + * @param headers + * @param ui + * @param clusterName + * @return + */ + @PUT + @Path("{clusterName}") + @Produces("text/plain") + public Response updateRemoteCluster(String body, @Context HttpHeaders headers, @Context UriInfo ui, + @PathParam("clusterName") String clusterName) { + return handleRequest(headers, body, ui, Request.Type.PUT, createRemoteClusterResource(clusterName)); + } + + /** + * Delete a Remote Cluster + * @param body + * @param headers + * @param ui + * @param clusterName + * @return + */ + @DELETE + @Path("{clusterName}") + @Produces("text/plain") + public Response deleteRemoteCluster(String body, @Context HttpHeaders headers, @Context UriInfo ui, + @PathParam("clusterName") String clusterName) { + return handleRequest(headers, body, ui, Request.Type.DELETE, createRemoteClusterResource(clusterName)); + } + + + /** + * Get information about a Remote Cluster + * @param headers + * @param ui + * @param clusterName + * @return + */ + @GET + @Path("{clusterName}") + @Produces("text/plain") + public Response getRemoteCluster(@Context HttpHeaders headers, @Context UriInfo ui, + @PathParam("clusterName") String clusterName) { + return handleRequest(headers, null, ui, Request.Type.GET, createRemoteClusterResource(clusterName)); + } + + + + + // ----- helper methods ---------------------------------------------------- + + /** + * Create a Remote Cluster resource. + * + * @param clusterName Name of the Cluster + * + * @return a RemoteCluster resource Instance + */ + private ResourceInstance createRemoteClusterResource(String clusterName) { + return createResource(Resource.Type.RemoteCluster,Collections.singletonMap(Resource.Type.RemoteCluster, clusterName)); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java index 4e7a032..35fdcf6 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java @@ -122,6 +122,8 @@ public class DefaultProviderModule extends AbstractProviderModule { return new SettingResourceProvider(); case Artifact: return new ArtifactResourceProvider(managementController); + case RemoteCluster: + return new RemoteClusterResourceProvider(); default: return AbstractControllerResourceProvider.getResourceProvider(type, propertyIds, http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RemoteClusterResourceProvider.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RemoteClusterResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RemoteClusterResourceProvider.java new file mode 100644 index 0000000..413dbff --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RemoteClusterResourceProvider.java @@ -0,0 +1,327 @@ +/** + * 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.base.Strings; +import com.google.inject.Inject; +import org.apache.ambari.server.AmbariException; +import org.apache.ambari.server.DuplicateResourceException; +import org.apache.ambari.server.StaticallyInject; +import org.apache.ambari.server.configuration.Configuration; +import org.apache.ambari.server.controller.predicate.EqualsPredicate; +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.RequestStatus; +import org.apache.ambari.server.controller.spi.Resource; +import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException; +import org.apache.ambari.server.controller.spi.SystemException; +import org.apache.ambari.server.controller.spi.UnsupportedPropertyException; +import org.apache.ambari.server.orm.dao.RemoteAmbariClusterDAO; +import org.apache.ambari.server.orm.entities.RemoteAmbariClusterEntity; +import org.apache.ambari.server.orm.entities.RemoteAmbariClusterServiceEntity; +import org.apache.ambari.server.security.authorization.RoleAuthorization; +import org.apache.ambari.server.view.RemoteAmbariClusterRegistry; +import org.apache.ambari.view.MaskException; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * Resource Provider for Remote Cluster + */ +@StaticallyInject +public class RemoteClusterResourceProvider extends AbstractAuthorizedResourceProvider { + + /** + * Remote Cluster property id constants. + */ + public static final String CLUSTER_NAME_PROPERTY_ID = "ClusterInfo/name"; + public static final String CLUSTER_URL_PROPERTY_ID = "ClusterInfo/url"; + public static final String USERNAME_PROPERTY_ID = "ClusterInfo/username"; + public static final String PASSWORD_PROPERTY_ID = "ClusterInfo/password"; + public static final String SERVICES_PROPERTY_ID = "ClusterInfo/services"; + + /** + * The logger. + */ + private final static Logger LOG = LoggerFactory.getLogger(RemoteClusterResourceProvider.class); + + /** + * The key property ids for a Remote Cluster resource. + */ + private static Map<Resource.Type, String> keyPropertyIds = new HashMap<Resource.Type, String>(); + static { + keyPropertyIds.put(Resource.Type.RemoteCluster, CLUSTER_NAME_PROPERTY_ID); + } + + /** + * The property ids for a Remote Cluster resource. + */ + private static Set<String> propertyIds = new HashSet<String>(); + static { + propertyIds.add(CLUSTER_NAME_PROPERTY_ID); + propertyIds.add(CLUSTER_URL_PROPERTY_ID); + propertyIds.add(USERNAME_PROPERTY_ID); + propertyIds.add(PASSWORD_PROPERTY_ID); + propertyIds.add(SERVICES_PROPERTY_ID); + } + + @Inject + private static RemoteAmbariClusterDAO remoteAmbariClusterDAO; + + @Inject + private static Configuration configuration; + + @Inject + private static RemoteAmbariClusterRegistry remoteAmbariClusterRegistry; + + /** + * Create a new resource provider. + */ + protected RemoteClusterResourceProvider() { + super(propertyIds, keyPropertyIds); + + EnumSet<RoleAuthorization> requiredAuthorizations = EnumSet.of(RoleAuthorization.AMBARI_ADD_DELETE_CLUSTERS); + setRequiredCreateAuthorizations(requiredAuthorizations); + setRequiredDeleteAuthorizations(requiredAuthorizations); + setRequiredUpdateAuthorizations(requiredAuthorizations); + } + + @Override + public Map<Resource.Type, String> getKeyPropertyIds() { + return keyPropertyIds; + } + + @Override + protected Set<String> getPKPropertyIds() { + return new HashSet<String>(keyPropertyIds.values()); + } + + @Override + public RequestStatus createResourcesAuthorized(Request request) throws SystemException, UnsupportedPropertyException, ResourceAlreadyExistsException, NoSuchParentResourceException { + for (Map<String, Object> properties : request.getProperties()) { + createResources(getCreateCommand(properties)); + } + notifyCreate(Resource.Type.RemoteCluster, request); + + return getRequestStatus(null); + } + + @Override + public Set<Resource> getResources(Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { + Set<Resource> resources = new HashSet<Resource>(); + Set<String> requestedIds = getRequestPropertyIds(request, predicate); + + Set<Map<String, Object>> propertyMaps = getPropertyMaps(predicate); + if (propertyMaps.isEmpty()) { + propertyMaps.add(Collections.<String, Object>emptyMap()); + } + + for (Map<String, Object> propertyMap : propertyMaps) { + + String clusterName = (String) propertyMap.get(CLUSTER_NAME_PROPERTY_ID); + + if(!Strings.isNullOrEmpty(clusterName)){ + RemoteAmbariClusterEntity cluster = remoteAmbariClusterDAO.findByName(clusterName); + if(cluster == null) { + throw new NoSuchResourceException(String.format("Cluster with name %s cannot be found",clusterName) ); + } + resources.add(toResource(requestedIds, cluster)); + }else { + for (RemoteAmbariClusterEntity cluster : remoteAmbariClusterDAO.findAll()){ + Resource resource = toResource(requestedIds, cluster); + resources.add(resource); + } + } + } + return resources; + } + + protected Resource toResource(Set<String> requestedIds, RemoteAmbariClusterEntity cluster) { + Resource resource = new ResourceImpl(Resource.Type.RemoteCluster); + setResourceProperty(resource, CLUSTER_NAME_PROPERTY_ID, cluster.getName(), requestedIds); + setResourceProperty(resource, CLUSTER_URL_PROPERTY_ID, cluster.getUrl(), requestedIds); + setResourceProperty(resource, USERNAME_PROPERTY_ID, cluster.getUsername(), requestedIds); + ArrayList<String> services = new ArrayList<String>(); + for (RemoteAmbariClusterServiceEntity remoteClusterServiceEntity : cluster.getServices()) { + services.add(remoteClusterServiceEntity.getServiceName()); + } + setResourceProperty(resource, SERVICES_PROPERTY_ID,services, requestedIds); + return resource; + } + + @Override + public RequestStatus updateResourcesAuthorized(Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { + Iterator<Map<String,Object>> iterator = request.getProperties().iterator(); + if (iterator.hasNext()) { + for (Map<String, Object> propertyMap : getPropertyMaps(iterator.next(), predicate)) { + modifyResources(getUpdateCommand(propertyMap)); + } + } + notifyUpdate(Resource.Type.RemoteCluster, request, predicate); + + return getRequestStatus(null); + } + + @Override + protected RequestStatus deleteResourcesAuthorized(Request request, Predicate predicate) + throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { + + modifyResources(getDeleteCommand(predicate)); + notifyDelete(Resource.Type.ViewInstance, predicate); + return getRequestStatus(null); + + } + + /** + * Get the command to create the RemoteAmbariCluster + * @param properties + * @return A command to create the RemoteAmbariCluster + */ + private Command<Void> getCreateCommand(final Map<String, Object> properties) { + return new Command<Void>() { + @Override + public Void invoke() throws AmbariException { + String name = (String)properties.get(CLUSTER_NAME_PROPERTY_ID); + + if(StringUtils.isEmpty(name)){ + throw new IllegalArgumentException("Cluster Name cannot ne null or Empty"); + } + + if(remoteAmbariClusterDAO.findByName(name) != null){ + throw new DuplicateResourceException(String.format("Remote cluster with name %s already exists",name)); + } + + saveOrUpdateRemoteAmbariClusterEntity(properties,false); + + return null; + } + }; + } + + /** + * Get the command to update the RemoteAmbariCluster + * @param properties + * @return A command to update the RemoteAmbariCluster + */ + private Command<Void> getUpdateCommand(final Map<String, Object> properties) { + return new Command<Void>() { + @Override + public Void invoke() throws AmbariException { + String name = (String)properties.get(CLUSTER_NAME_PROPERTY_ID); + + if(StringUtils.isEmpty(name)){ + throw new IllegalArgumentException("Cluster Name cannot ne null or Empty"); + } + + saveOrUpdateRemoteAmbariClusterEntity(properties,true); + return null; + + } + }; + } + + /** + * Save or update Remote Ambari Cluster Entity to database + * + * @param properties + * @param update + * @throws AmbariException + */ + private void saveOrUpdateRemoteAmbariClusterEntity(Map<String, Object> properties,boolean update) throws AmbariException { + String name = (String)properties.get(CLUSTER_NAME_PROPERTY_ID); + String url = (String)properties.get(CLUSTER_URL_PROPERTY_ID); + String username = (String)properties.get(USERNAME_PROPERTY_ID); + String password = (String)properties.get(PASSWORD_PROPERTY_ID); + + if(StringUtils.isEmpty(url) && StringUtils.isEmpty(username)){ + throw new IllegalArgumentException("Url or username cannot be null"); + } + + RemoteAmbariClusterEntity entity = remoteAmbariClusterDAO.findByName(name); + + if(update && entity == null){ + throw new IllegalArgumentException(String.format("Cannot find cluster with name : \"%s\"",name)); + }else if(!update && entity != null){ + throw new DuplicateResourceException(String.format("Cluster with name : \"%s\" already exists",name)); + } + + // Check Password not null for create + //Check username matches the entity username if password not present + if(StringUtils.isBlank(password) && !update){ + throw new IllegalArgumentException("Password cannot be null"); + }else if(StringUtils.isBlank(password) && update && !username.equals(entity.getUsername())){ + throw new IllegalArgumentException("Failed to update. Username does not match."); + } + + if (entity == null) { + entity = new RemoteAmbariClusterEntity(); + } + + entity.setName(name); + entity.setUrl(url); + try { + if(password != null) { + entity.setUsername(username); + entity.setPassword(password); + } + } catch (MaskException e) { + throw new IllegalArgumentException("Failed to create new Remote Cluster " + name + ". Illegal Password"); + } + + try { + remoteAmbariClusterRegistry.saveOrUpdate(entity,update); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to create new Remote Cluster " + name +". " + e.getMessage(),e); + } + } + + /** + * Get the command to delete the Cluster + * @param predicate + * @return The delete command + */ + private Command<Void> getDeleteCommand(final Predicate predicate) { + return new Command<Void>() { + @Override + public Void invoke() throws AmbariException { + Comparable deletedCluster = ((EqualsPredicate) predicate).getValue(); + String toDelete = deletedCluster.toString(); + RemoteAmbariClusterEntity clusterEntity = remoteAmbariClusterDAO.findByName(toDelete); + if(clusterEntity == null){ + throw new IllegalArgumentException("The Cluster "+ toDelete +" does not exist"); + } + + remoteAmbariClusterRegistry.delete(clusterEntity); + return null; + } + }; + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java index 605f68d..b8ed215 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ViewInstanceResourceProvider.java @@ -41,6 +41,7 @@ import org.apache.ambari.server.view.ViewRegistry; import org.apache.ambari.server.view.validation.InstanceValidationResultImpl; import org.apache.ambari.server.view.validation.ValidationException; import org.apache.ambari.server.view.validation.ValidationResultImpl; +import org.apache.ambari.view.ClusterType; import org.apache.ambari.view.validation.Validator; import java.util.Collections; @@ -72,6 +73,7 @@ public class ViewInstanceResourceProvider extends AbstractAuthorizedResourceProv public static final String CONTEXT_PATH_PROPERTY_ID = "ViewInstanceInfo/context_path"; public static final String STATIC_PROPERTY_ID = "ViewInstanceInfo/static"; public static final String CLUSTER_HANDLE_PROPERTY_ID = "ViewInstanceInfo/cluster_handle"; + public static final String CLUSTER_TYPE_PROPERTY_ID = "ViewInstanceInfo/cluster_type"; public static final String SHORT_URL_PROPERTY_ID = "ViewInstanceInfo/short_url"; public static final String SHORT_URL_NAME_PROPERTY_ID = "ViewInstanceInfo/short_url_name"; @@ -113,6 +115,7 @@ public class ViewInstanceResourceProvider extends AbstractAuthorizedResourceProv propertyIds.add(CONTEXT_PATH_PROPERTY_ID); propertyIds.add(STATIC_PROPERTY_ID); propertyIds.add(CLUSTER_HANDLE_PROPERTY_ID); + propertyIds.add(CLUSTER_TYPE_PROPERTY_ID); propertyIds.add(SHORT_URL_PROPERTY_ID); propertyIds.add(SHORT_URL_NAME_PROPERTY_ID); propertyIds.add(VALIDATION_RESULT_PROPERTY_ID); @@ -245,6 +248,7 @@ public class ViewInstanceResourceProvider extends AbstractAuthorizedResourceProv setResourceProperty(resource, VISIBLE_PROPERTY_ID, viewInstanceEntity.isVisible(), requestedIds); setResourceProperty(resource, STATIC_PROPERTY_ID, viewInstanceEntity.isXmlDriven(), requestedIds); setResourceProperty(resource, CLUSTER_HANDLE_PROPERTY_ID, viewInstanceEntity.getClusterHandle(), requestedIds); + setResourceProperty(resource, CLUSTER_TYPE_PROPERTY_ID, viewInstanceEntity.getClusterType(), requestedIds); ViewURLEntity viewUrl = viewInstanceEntity.getViewUrl(); if(viewUrl != null) { setResourceProperty(resource, SHORT_URL_PROPERTY_ID, viewUrl.getUrlSuffix(), requestedIds); @@ -351,6 +355,11 @@ public class ViewInstanceResourceProvider extends AbstractAuthorizedResourceProv viewInstanceEntity.setClusterHandle((String) properties.get(CLUSTER_HANDLE_PROPERTY_ID)); } + if (properties.containsKey(CLUSTER_TYPE_PROPERTY_ID)) { + String clusterType = (String) properties.get(CLUSTER_TYPE_PROPERTY_ID); + viewInstanceEntity.setClusterType(ClusterType.valueOf(clusterType)); + } + Map<String, String> instanceProperties = new HashMap<String, String>(); boolean isUserAdmin = viewRegistry.checkAdmin(); http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java index 386e657..99e4ccd 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java @@ -156,7 +156,8 @@ public interface Resource { UserAuthorization, VersionDefinition, ClusterKerberosDescriptor, - LoggingQuery; + LoggingQuery, + RemoteCluster; /** * Get the {@link Type} that corresponds to this InternalType. @@ -274,6 +275,7 @@ public interface Resource { public static final Type VersionDefinition = InternalType.VersionDefinition.getType(); public static final Type ClusterKerberosDescriptor = InternalType.ClusterKerberosDescriptor.getType(); public static final Type LoggingQuery = InternalType.LoggingQuery.getType(); + public static final Type RemoteCluster = InternalType.RemoteCluster.getType(); /** * The type name. http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/RemoteAmbariClusterDAO.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/RemoteAmbariClusterDAO.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/RemoteAmbariClusterDAO.java new file mode 100644 index 0000000..72ab368 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/RemoteAmbariClusterDAO.java @@ -0,0 +1,101 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ambari.server.orm.dao; + +import com.google.common.base.Optional; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; +import com.google.inject.persist.Transactional; +import org.apache.ambari.server.orm.RequiresSession; +import org.apache.ambari.server.orm.entities.RemoteAmbariClusterEntity; +import org.apache.ambari.server.orm.entities.ViewURLEntity; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import java.util.List; + +/** + * Remote Ambari Cluster Data Access Object. + */ +@Singleton +public class RemoteAmbariClusterDAO { + /** + * JPA entity manager + */ + @Inject + private Provider<EntityManager> entityManagerProvider; + @Inject + private DaoUtils daoUtils; + + + /** + * Find all view instances. + * + * @return all views or an empty List + */ + @RequiresSession + public List<RemoteAmbariClusterEntity> findAll() { + TypedQuery<RemoteAmbariClusterEntity> query = entityManagerProvider.get(). + createNamedQuery("allRemoteAmbariClusters", RemoteAmbariClusterEntity.class); + + return query.getResultList(); + } + + /** + * Find Cluster by name + * @param clusterName + * @return + */ + @RequiresSession + public RemoteAmbariClusterEntity findByName(String clusterName) { + TypedQuery<RemoteAmbariClusterEntity> query = entityManagerProvider.get(). + createNamedQuery("remoteAmbariClusterByName", RemoteAmbariClusterEntity.class); + query.setParameter("clusterName", clusterName); + return daoUtils.selectSingle(query); + } + + /** + * Save a Cluster entity + * @param entity + */ + @Transactional + public void save(RemoteAmbariClusterEntity entity) { + entityManagerProvider.get().persist(entity); + } + + /** + * Update and merge a Remote Ambari Cluster entity + * @param entity + */ + @Transactional + public void update(RemoteAmbariClusterEntity entity) { + entityManagerProvider.get().merge(entity); + + } + + /** + * Remove a cluster entity + * @param clusterEntity + */ + @Transactional + public void delete(RemoteAmbariClusterEntity clusterEntity) { + entityManagerProvider.get().remove(clusterEntity); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/RemoteAmbariClusterEntity.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/RemoteAmbariClusterEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/RemoteAmbariClusterEntity.java new file mode 100644 index 0000000..99c9f2a --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/RemoteAmbariClusterEntity.java @@ -0,0 +1,201 @@ +/** + * 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.orm.entities; + +import org.apache.ambari.server.view.DefaultMasker; +import org.apache.ambari.view.MaskException; +import org.apache.ambari.view.Masker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.TableGenerator; +import java.util.Collection; + +/** + * Remote Ambari Managed Cluster + */ +@Table(name = "remoteambaricluster") +@TableGenerator(name = "remote_cluster_id_generator", + table = "ambari_sequences", pkColumnName = "sequence_name", valueColumnName = "sequence_value" + , pkColumnValue = "remote_cluster_id_seq" + , initialValue = 1 +) + +@NamedQueries({ + @NamedQuery(name = "allRemoteAmbariClusters", + query = "SELECT remoteAmbariCluster FROM RemoteAmbariClusterEntity remoteambaricluster"), + @NamedQuery(name = "remoteAmbariClusterByName", query = + "SELECT remoteAmbariCluster " + + "FROM RemoteAmbariClusterEntity remoteAmbariCluster " + + "WHERE remoteAmbariCluster.name=:clusterName")}) +@Entity +public class RemoteAmbariClusterEntity { + + /** + * The logger. + */ + protected final static Logger LOG = LoggerFactory.getLogger(RemoteAmbariClusterEntity.class); + + @Id + @Column(name = "cluster_id", nullable = false) + @GeneratedValue(strategy = GenerationType.TABLE, generator = "remote_cluster_id_generator") + private Long id; + + @Column(name = "name", nullable = false, insertable = true, updatable = false) + private String name; + + @Column(name = "url", nullable = false, insertable = true, updatable = true) + private String url; + + @Column(name = "username", nullable = false, insertable = true, updatable = true) + private String username; + + @Column(name = "password", nullable = false, insertable = true, updatable = true) + private String password; + + @OneToMany(cascade = CascadeType.ALL, mappedBy = "cluster") + private Collection<RemoteAmbariClusterServiceEntity> services; + + private static Masker masker = new DefaultMasker(); + + /** + * Get the id + * + * @return id + */ + public Long getId() { + return id; + } + + /** + * Set the id of cluster + * + * @param id + */ + public void setId(Long id) { + this.id = id; + } + + /** + * Get the name of cluster + * + * @return name + */ + public String getName() { + return name; + } + + /** + * Set the cluster name + * + * @param name + */ + public void setName(String name) { + this.name = name; + } + + /** + * Get the cluster url + * + * @return url + */ + public String getUrl() { + return url; + } + + /** + * Set the url + * + * @param url + */ + public void setUrl(String url) { + this.url = url; + } + + /** + * Get username + * + * @return username + */ + public String getUsername() { + return username; + } + + /** + * Set the username + * + * @param username + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * Get the password + * + * @return password + */ + public String getPassword() { + try { + return masker.unmask(password); + } catch (MaskException e) { + // Log exception + LOG.error("Unable to unmask password for Remote Cluster : "+name , e); + } + return null; + } + + /** + * Set the password + * + * @param password + * @throws MaskException + */ + public void setPassword(String password) throws MaskException { + this.password = masker.mask(password); + } + + /** + * Get the services installed on the cluster + * + * @return services + */ + public Collection<RemoteAmbariClusterServiceEntity> getServices() { + return services; + } + + /** + * Set the services installed on the cluster + * + * @param services + */ + public void setServices(Collection<RemoteAmbariClusterServiceEntity> services) { + this.services = services; + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/RemoteAmbariClusterServiceEntity.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/RemoteAmbariClusterServiceEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/RemoteAmbariClusterServiceEntity.java new file mode 100644 index 0000000..55e8b2c --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/RemoteAmbariClusterServiceEntity.java @@ -0,0 +1,108 @@ +/** + * 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.orm.entities; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.TableGenerator; + +/** + * Remote Ambari Service to Remote Cluster Mapping + */ +@Table(name = "remoteambariclusterservice") +@TableGenerator(name = "remote_cluster_service_id_generator", + table = "ambari_sequences", pkColumnName = "sequence_name", valueColumnName = "sequence_value" + , pkColumnValue = "remote_cluster_service_id_seq" + , initialValue = 1 +) +@Entity +public class RemoteAmbariClusterServiceEntity { + + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.TABLE, generator = "remote_cluster_service_id_generator") + private Long id; + + @Column(name = "service_name", nullable = false, insertable = true, updatable = false) + private String serviceName; + + @ManyToOne + @JoinColumn(name = "cluster_id", referencedColumnName = "cluster_id", nullable = false) + private RemoteAmbariClusterEntity cluster; + + /** + * Get Id + * + * @return id + */ + public Long getId() { + return id; + } + + /** + * Set id for the service + * + * @param id + */ + public void setId(Long id) { + this.id = id; + } + + /** + * Get cluster attached to the service + * + * @return cluster + */ + public RemoteAmbariClusterEntity getCluster() { + return cluster; + } + + /** + * Set cluster for the service + * + * @param cluster + */ + public void setCluster(RemoteAmbariClusterEntity cluster) { + this.cluster = cluster; + } + + /** + * Get service name + * + * @return service name + */ + public String getServiceName() { + return serviceName; + } + + /** + * Set service name + * + * @param serviceName + */ + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewEntity.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewEntity.java index 29dc2a7..74de530 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewEntity.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewEntity.java @@ -56,6 +56,9 @@ import java.util.Set; query = "SELECT view FROM ViewEntity view") @Entity public class ViewEntity implements ViewDefinition { + + public static final String AMBARI_ONLY = "AMBARI-ONLY"; + /** * The unique view name. */ @@ -729,6 +732,12 @@ public class ViewEntity implements ViewDefinition { this.configuration = configuration; this.clusterConfigurable = false; + if(configuration.getClusterConfigOptions() != null && + configuration.getClusterConfigOptions().equals(AMBARI_ONLY)){ + this.clusterConfigurable = true; + return; + } + // if any of the parameters contain a cluster config element then the view is cluster configurable for (ParameterConfig parameterConfig : configuration.getParameters()) { String clusterConfig = parameterConfig.getClusterConfig(); http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewInstanceEntity.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewInstanceEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewInstanceEntity.java index 2d6e5ba..ba5774f 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewInstanceEntity.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ViewInstanceEntity.java @@ -28,6 +28,8 @@ import javax.persistence.Basic; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @@ -122,6 +124,13 @@ public class ViewInstanceEntity implements ViewInstanceDefinition { private String clusterHandle; /** + * Cluster Type for cluster Handle + */ + @Enumerated(EnumType.STRING) + @Column(name = "cluster_type", nullable = false) + private ClusterType clusterType; + + /** * Visible flag. */ @Column @@ -251,6 +260,7 @@ public class ViewInstanceEntity implements ViewInstanceDefinition { this.clusterHandle = null; this.visible = instanceConfig.isVisible() ? 'Y' : 'N'; this.alterNames = 1; + this.clusterType = ClusterType.LOCAL_AMBARI; String label = instanceConfig.getLabel(); this.label = (label == null || label.length() == 0) ? view.getLabel() : label; @@ -442,6 +452,24 @@ public class ViewInstanceEntity implements ViewInstanceDefinition { } /** + * Get the type of cluster the view instance is attached to + * + * @return clusterType the type of cluster for cluster handle + */ + public ClusterType getClusterType() { + return clusterType; + } + + /** + * Set the type of cluster for cluster handle + * + * @param clusterType + */ + public void setClusterType(ClusterType clusterType) { + this.clusterType = clusterType; + } + + /** * Set the visible flag. * * @param visible visible flag @@ -1029,4 +1057,5 @@ public class ViewInstanceEntity implements ViewInstanceDefinition { return instanceName; } } + } http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog240.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog240.java b/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog240.java index 4ed4a13..3547ad3 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog240.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog240.java @@ -75,6 +75,7 @@ import org.apache.ambari.server.state.ServiceInfo; import org.apache.ambari.server.state.StackId; import org.apache.ambari.server.state.StackInfo; import org.apache.ambari.server.state.State; +import org.apache.ambari.view.ClusterType; import org.apache.ambari.server.state.stack.WidgetLayout; import org.apache.ambari.server.state.stack.WidgetLayoutInfo; import org.apache.commons.lang.StringUtils; @@ -219,6 +220,14 @@ public class UpgradeCatalog240 extends AbstractUpgradeCatalog { return "2.3.0"; } + public static final String CLUSTER_TYPE_COLUMN = "cluster_type"; + public static final String REMOTE_AMBARI_CLUSTER_TABLE = "remoteambaricluster"; + public static final String REMOTE_AMBARI_CLUSTER_SERVICE_TABLE = "remoteambariclusterservice"; + + public static final String CLUSTER_ID = "cluster_id"; + public static final String SERVICE_NAME = "service_name"; + public static final String CLUSTER_NAME = "name"; + @Override protected void executeDDLUpdates() throws AmbariException, SQLException { @@ -236,6 +245,32 @@ public class UpgradeCatalog240 extends AbstractUpgradeCatalog { updateHostRoleCommandTableDDL(); createViewUrlTableDDL(); updateViewInstanceEntityTable(); + createRemoteClusterTable(); + } + + private void createRemoteClusterTable() throws SQLException { + + List<DBColumnInfo> columns = new ArrayList<>(); + LOG.info("Creating {} table", REMOTE_AMBARI_CLUSTER_TABLE); + columns.add(new DBColumnInfo(CLUSTER_ID, Long.class, null, null, false)); + columns.add(new DBColumnInfo(CLUSTER_NAME, String.class, 255, null, false)); + columns.add(new DBColumnInfo("url", String.class, 255, null, false)); + columns.add(new DBColumnInfo("username", String.class, 255, null, false)); + columns.add(new DBColumnInfo("password", String.class, 255, null, false)); + dbAccessor.createTable(REMOTE_AMBARI_CLUSTER_TABLE, columns, CLUSTER_ID); + dbAccessor.addUniqueConstraint(REMOTE_AMBARI_CLUSTER_TABLE , "unq_remote_ambari_cluster" , CLUSTER_NAME); + addSequence("remote_cluster_id_seq", 1L, false); + + List<DBColumnInfo> remoteClusterServiceColumns = new ArrayList<>(); + LOG.info("Creating {} table", REMOTE_AMBARI_CLUSTER_SERVICE_TABLE); + remoteClusterServiceColumns.add(new DBColumnInfo(ID, Long.class, null, null, false)); + remoteClusterServiceColumns.add(new DBColumnInfo(SERVICE_NAME, String.class, 255, null, false)); + remoteClusterServiceColumns.add(new DBColumnInfo(CLUSTER_ID, Long.class, null, null, false)); + dbAccessor.createTable(REMOTE_AMBARI_CLUSTER_SERVICE_TABLE, remoteClusterServiceColumns, ID); + dbAccessor.addFKConstraint(REMOTE_AMBARI_CLUSTER_SERVICE_TABLE, "FK_remote_ambari_cluster_id", + CLUSTER_ID, REMOTE_AMBARI_CLUSTER_TABLE, CLUSTER_ID, false); + addSequence("remote_cluster_service_id_seq", 1L, false); + } private void createViewUrlTableDDL() throws SQLException { @@ -256,6 +291,8 @@ public class UpgradeCatalog240 extends AbstractUpgradeCatalog { new DBColumnInfo(SHORT_URL_COLUMN, Long.class, null, null, true)); dbAccessor.addFKConstraint(VIEWINSTANCE_TABLE, "FK_instance_url_id", SHORT_URL_COLUMN, VIEWURL_TABLE, URL_ID_COLUMN, false); + dbAccessor.addColumn(VIEWINSTANCE_TABLE, + new DBColumnInfo(CLUSTER_TYPE_COLUMN, String.class, 100, ClusterType.LOCAL_AMBARI, false)); } private void updateClusterTableDDL() throws SQLException { http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-server/src/main/java/org/apache/ambari/server/view/RemoteAmbariCluster.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/RemoteAmbariCluster.java b/ambari-server/src/main/java/org/apache/ambari/server/view/RemoteAmbariCluster.java new file mode 100644 index 0000000..f661844 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/view/RemoteAmbariCluster.java @@ -0,0 +1,190 @@ +/** + * 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.view; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import org.apache.ambari.server.configuration.Configuration; +import org.apache.ambari.server.orm.entities.RemoteAmbariClusterEntity; +import org.apache.ambari.view.AmbariHttpException; +import org.apache.ambari.view.AmbariStreamProvider; +import org.apache.ambari.view.cluster.Cluster; +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +/** + * View associated Remote cluster implementation. + */ +public class RemoteAmbariCluster implements Cluster { + + private String name; + + private AmbariStreamProvider streamProvider; + + private final LoadingCache<String, JsonElement> configurationCache = CacheBuilder.newBuilder() + .expireAfterWrite(10, TimeUnit.SECONDS) + .build(new CacheLoader<String, JsonElement>() { + @Override + public JsonElement load(String url) throws Exception { + return readFromUrlJSON(url); + } + }); + + + /** + * Constructor for Remote Ambari Cluster + * + * @param remoteAmbariClusterEntity + */ + public RemoteAmbariCluster(RemoteAmbariClusterEntity remoteAmbariClusterEntity, Configuration config) { + String [] urlSplit = remoteAmbariClusterEntity.getUrl().split("/"); + + // remoteAmbariClusterEntity.getName() is not the actual name of Remote Cluster + // We need to extract the name from cluster url which is like. http://host:port/api/vi/clusters/${clusterName} + this.name = urlSplit[urlSplit.length -1]; + + this.streamProvider = new RemoteAmbariStreamProvider( + remoteAmbariClusterEntity.getUrl(), remoteAmbariClusterEntity.getUsername(), + remoteAmbariClusterEntity.getPassword(),config.getRequestConnectTimeout(),config.getRequestReadTimeout()); + } + + /** + * Constructor for Remote Ambari Cluster + * + * @param name + * @param streamProvider + */ + public RemoteAmbariCluster(String name, AmbariStreamProvider streamProvider) { + this.name = name; + this.streamProvider = streamProvider; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public String getConfigurationValue(String type, String key) { + JsonElement config = null; + try { + String desiredTag = getDesiredConfig(type); + if(desiredTag != null){ + config = configurationCache.get(String.format("/configurations?(type=%s&tag=%s)", type, desiredTag)); + } + } catch (ExecutionException e) { + throw new RemoteAmbariConfigurationReadException("Can't retrieve configuration from Remote Ambari", e); + } + + if(config == null || !config.isJsonObject()) return null; + JsonElement items = config.getAsJsonObject().get("items"); + + if(items == null || !items.isJsonArray()) return null; + JsonElement item = items.getAsJsonArray().get(0); + + if(item == null || !item.isJsonObject()) return null; + JsonElement properties = item.getAsJsonObject().get("properties"); + + if(properties == null || !properties.isJsonObject()) return null; + JsonElement property = properties.getAsJsonObject().get(key); + + if(property == null || !property.isJsonPrimitive()) return null; + + return property.getAsJsonPrimitive().getAsString(); + } + + /** + * Get list of services installed on the remote cluster + * + * @return list of services Available on cluster + */ + public Set<String> getServices() throws IOException, AmbariHttpException { + Set<String> services = new HashSet<String>(); + String path = "?fields=services/ServiceInfo/service_name"; + JsonElement config = configurationCache.getUnchecked(path); + + if(config != null && config.isJsonObject()){ + JsonElement items = config.getAsJsonObject().get("services"); + if(items != null && items.isJsonArray()){ + for (JsonElement item : items.getAsJsonArray()) { + JsonElement serviceInfo = item.getAsJsonObject().get("ServiceInfo"); + if(serviceInfo != null && serviceInfo.isJsonObject()){ + String serviceName = serviceInfo.getAsJsonObject().get("service_name").getAsString(); + services.add(serviceName); + } + } + } + } + + return services; + } + + /** + * Get the current tag for the config type + * + * @param type + * @return + * @throws ExecutionException + */ + private String getDesiredConfig(String type) throws ExecutionException { + JsonElement desiredConfigResponse = configurationCache.get("?fields=services/ServiceInfo,hosts,Clusters"); + + if(desiredConfigResponse == null || !desiredConfigResponse.isJsonObject()) return null; + JsonElement clusters = desiredConfigResponse.getAsJsonObject().get("Clusters"); + + if(clusters == null || !clusters.isJsonObject()) return null; + JsonElement desiredConfig = clusters.getAsJsonObject().get("desired_configs"); + + if(desiredConfig == null || !desiredConfig.isJsonObject()) return null; + JsonElement desiredConfigForType = desiredConfig.getAsJsonObject().get(type); + + if(desiredConfigForType == null || !desiredConfigForType.isJsonObject()) return null; + JsonElement typeJson = desiredConfigForType.getAsJsonObject().get("tag"); + + if( typeJson == null || !(typeJson.isJsonPrimitive())) return null; + + return typeJson.getAsJsonPrimitive().getAsString(); + } + + /** + * Read the content of the url from remote cluster + * + * @param url + * @return + * @throws IOException + * @throws AmbariHttpException + */ + private JsonElement readFromUrlJSON(String url) throws IOException, AmbariHttpException { + InputStream inputStream = streamProvider.readFrom(url, "GET", (String)null, null); + String response = IOUtils.toString(inputStream); + return new JsonParser().parse(response); + } + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-server/src/main/java/org/apache/ambari/server/view/RemoteAmbariClusterRegistry.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/RemoteAmbariClusterRegistry.java b/ambari-server/src/main/java/org/apache/ambari/server/view/RemoteAmbariClusterRegistry.java new file mode 100644 index 0000000..38a47a4 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/view/RemoteAmbariClusterRegistry.java @@ -0,0 +1,116 @@ +/** + * 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.view; + +import org.apache.ambari.server.configuration.Configuration; +import org.apache.ambari.server.orm.dao.RemoteAmbariClusterDAO; +import org.apache.ambari.server.orm.entities.RemoteAmbariClusterEntity; +import org.apache.ambari.server.orm.entities.RemoteAmbariClusterServiceEntity; +import org.apache.ambari.view.AmbariHttpException; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Registry for Remote Ambari Cluster + */ +@Singleton +public class RemoteAmbariClusterRegistry { + + private ConcurrentHashMap<String,RemoteAmbariCluster> clusterMap = new ConcurrentHashMap<String,RemoteAmbariCluster>(); + + @Inject + private RemoteAmbariClusterDAO remoteAmbariClusterDAO; + + @Inject + private Configuration configuration; + + public RemoteAmbariCluster get(String clusterName){ + RemoteAmbariCluster remoteAmbariCluster = clusterMap.get(clusterName); + if(remoteAmbariCluster == null){ + RemoteAmbariCluster cluster = getCluster(clusterName); + RemoteAmbariCluster oldCluster = clusterMap.putIfAbsent(clusterName, cluster); + if(oldCluster == null) remoteAmbariCluster = cluster; + else remoteAmbariCluster = oldCluster; + } + return remoteAmbariCluster; + } + + + private RemoteAmbariCluster getCluster(String clusterName) { + RemoteAmbariClusterEntity remoteAmbariClusterEntity = remoteAmbariClusterDAO.findByName(clusterName); + RemoteAmbariCluster remoteAmbariCluster = new RemoteAmbariCluster(remoteAmbariClusterEntity, configuration); + return remoteAmbariCluster; + } + + /** + * Update the remote cluster properties + * + * @param entity + */ + public void update(RemoteAmbariClusterEntity entity){ + remoteAmbariClusterDAO.update(entity); + clusterMap.remove(entity.getName()); + } + + /** + * Remove the cluster entity from registry and database + * + * @param entity + */ + public void delete(RemoteAmbariClusterEntity entity) { + remoteAmbariClusterDAO.delete(entity); + clusterMap.remove(entity.getName()); + } + + /** + * Save Remote Cluster Entity after setting services. + * + * @param entity + * @param update + * @throws IOException + * @throws AmbariHttpException + */ + public void saveOrUpdate(RemoteAmbariClusterEntity entity, boolean update) throws IOException, AmbariHttpException { + RemoteAmbariCluster cluster = new RemoteAmbariCluster(entity,configuration); + Set<String> services = cluster.getServices(); + Collection<RemoteAmbariClusterServiceEntity> serviceEntities = new ArrayList<RemoteAmbariClusterServiceEntity>(); + + for (String service : services) { + RemoteAmbariClusterServiceEntity serviceEntity = new RemoteAmbariClusterServiceEntity(); + serviceEntity.setServiceName(service); + serviceEntity.setCluster(entity); + serviceEntities.add(serviceEntity); + } + + entity.setServices(serviceEntities); + + if(update){ + update(entity); + }else{ + remoteAmbariClusterDAO.save(entity); + } + } + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-server/src/main/java/org/apache/ambari/server/view/RemoteAmbariConfigurationReadException.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/RemoteAmbariConfigurationReadException.java b/ambari-server/src/main/java/org/apache/ambari/server/view/RemoteAmbariConfigurationReadException.java new file mode 100644 index 0000000..a80fe35 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/view/RemoteAmbariConfigurationReadException.java @@ -0,0 +1,29 @@ +/** + * 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.view; + +/** + * Exception while reading Remote Ambari Configuration + */ +public class RemoteAmbariConfigurationReadException extends RuntimeException{ + + public RemoteAmbariConfigurationReadException(String message, Throwable cause) { + super(message, cause); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-server/src/main/java/org/apache/ambari/server/view/RemoteAmbariStreamProvider.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/RemoteAmbariStreamProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/view/RemoteAmbariStreamProvider.java new file mode 100644 index 0000000..f043521 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/view/RemoteAmbariStreamProvider.java @@ -0,0 +1,118 @@ +/** + * 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.view; + +import org.apache.ambari.server.configuration.ComponentSSLConfiguration; +import org.apache.ambari.server.controller.internal.URLStreamProvider; +import org.apache.ambari.server.proxy.ProxyService; +import org.apache.ambari.view.AmbariHttpException; +import org.apache.ambari.view.AmbariStreamProvider; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Provider of an input stream for a request to the Remote Ambari server. + */ +public class RemoteAmbariStreamProvider implements AmbariStreamProvider { + + private String baseUrl; + + private String username; + + private String password; + + private URLStreamProvider urlStreamProvider; + + public RemoteAmbariStreamProvider(String baseUrl, String username, String password, int connectTimeout, int readTimeout) { + this.baseUrl = baseUrl; + this.username = username; + this.password = password; + + ComponentSSLConfiguration sslConfiguration = ComponentSSLConfiguration.instance(); + this.urlStreamProvider = + new URLStreamProvider( + connectTimeout, + readTimeout, + sslConfiguration.getTruststorePath(), + sslConfiguration.getTruststorePassword(), + sslConfiguration.getTruststoreType()); + } + + @Override + public InputStream readFrom(String path, String requestMethod, String body, Map<String, String> headers) throws IOException, AmbariHttpException { + return getInputStream(urlStreamProvider.processURL(getUrl(path), requestMethod, body, addHeaders(headers))); + } + + @Override + public InputStream readFrom(String path, String requestMethod, InputStream body, Map<String, String> headers) throws IOException, AmbariHttpException { + return getInputStream(urlStreamProvider.processURL(getUrl(path), requestMethod, body, addHeaders(headers))); + } + + private InputStream getInputStream(HttpURLConnection connection) throws IOException, AmbariHttpException { + int responseCode = connection.getResponseCode(); + if(responseCode >= ProxyService.HTTP_ERROR_RANGE_START){ + throw new AmbariHttpException(IOUtils.toString(connection.getErrorStream()),responseCode); + } + return connection.getInputStream(); + } + + private String getUrl(String path){ + String basePath = baseUrl; + return path.startsWith("/") ? basePath + path : basePath + "/" + path; + } + + private void addRequestedByHeaders(HashMap<String, String> newHeaders) { + newHeaders.put("X-Requested-By", "AMBARI"); + } + + private Map<String,List<String>> modifyHeaders(Map<String,String> headers){ + Map<String, List<String>> headerMap = new HashMap<String, List<String>>(); + for (Map.Entry<String, String> entry : headers.entrySet()) { + headerMap.put(entry.getKey(), Collections.singletonList(entry.getValue())); + } + return headerMap; + } + + private Map<String, List<String>> addHeaders(Map<String, String> customHeaders) { + HashMap<String, String> newHeaders = new HashMap<String, String>(); + if (customHeaders != null) + newHeaders.putAll(customHeaders); + + addBasicAuthHeaders(newHeaders); + addRequestedByHeaders(newHeaders); + return modifyHeaders(newHeaders); + } + + private void addBasicAuthHeaders(HashMap<String, String> headers) { + String authString = username + ":" + password; + byte[] authEncBytes = Base64.encodeBase64(authString.getBytes()); + String authStringEnc = new String(authEncBytes); + + headers.put("Authorization", "Basic " + authStringEnc); + } + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-server/src/main/java/org/apache/ambari/server/view/ViewAmbariStreamProvider.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewAmbariStreamProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewAmbariStreamProvider.java index 1dacd92..5e0f3fa 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewAmbariStreamProvider.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewAmbariStreamProvider.java @@ -21,11 +21,14 @@ package org.apache.ambari.server.view; import org.apache.ambari.server.controller.AmbariManagementController; import org.apache.ambari.server.controller.AmbariSessionManager; import org.apache.ambari.server.controller.internal.URLStreamProvider; +import org.apache.ambari.server.proxy.ProxyService; +import org.apache.ambari.view.AmbariHttpException; import org.apache.ambari.view.AmbariStreamProvider; import org.apache.commons.io.IOUtils; import java.io.IOException; import java.io.InputStream; +import java.net.HttpURLConnection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -73,39 +76,37 @@ public class ViewAmbariStreamProvider implements AmbariStreamProvider { // ----- AmbariStreamProvider ----------------------------------------------- @Override - public InputStream readFrom(String path, String requestMethod, String body, Map<String, String> headers, - boolean useAmbariSession) throws IOException { - return getInputStream(path, requestMethod, headers, useAmbariSession, body == null ? null : body.getBytes()); + public InputStream readFrom(String path, String requestMethod, String body, Map<String, String> headers + ) throws IOException, AmbariHttpException { + return getInputStream(path, requestMethod, headers, body == null ? null : body.getBytes()); } @Override - public InputStream readFrom(String path, String requestMethod, InputStream body, Map<String, String> headers, - boolean useAmbariSession) throws IOException { + public InputStream readFrom(String path, String requestMethod, InputStream body, Map<String, String> headers + ) throws IOException, AmbariHttpException { - return getInputStream(path, requestMethod, headers, useAmbariSession, body == null ? null : IOUtils.toByteArray(body)); + return getInputStream(path, requestMethod, headers, body == null ? null : IOUtils.toByteArray(body)); } // ----- helper methods ---------------------------------------------------- - private InputStream getInputStream(String path, String requestMethod, Map<String, String> headers, - boolean useAmbariSession, byte[] body) throws IOException { + private InputStream getInputStream(String path, String requestMethod, Map<String, String> headers + , byte[] body) throws IOException, AmbariHttpException { // add the Ambari session cookie to the given headers - if (useAmbariSession) { - String sessionId = ambariSessionManager.getCurrentSessionId(); - if (sessionId != null) { + String sessionId = ambariSessionManager.getCurrentSessionId(); + if (sessionId != null) { - String ambariSessionCookie = ambariSessionManager.getSessionCookie() + "=" + sessionId; + String ambariSessionCookie = ambariSessionManager.getSessionCookie() + "=" + sessionId; - if (headers == null || headers.isEmpty()) { - headers = Collections.singletonMap(URLStreamProvider.COOKIE, ambariSessionCookie); - } else { - headers = new HashMap<String, String>(headers); + if (headers == null || headers.isEmpty()) { + headers = Collections.singletonMap(URLStreamProvider.COOKIE, ambariSessionCookie); + } else { + headers = new HashMap<String, String>(headers); - String cookies = headers.get(URLStreamProvider.COOKIE); + String cookies = headers.get(URLStreamProvider.COOKIE); - headers.put(URLStreamProvider.COOKIE, URLStreamProvider.appendCookie(cookies, ambariSessionCookie)); - } + headers.put(URLStreamProvider.COOKIE, URLStreamProvider.appendCookie(cookies, ambariSessionCookie)); } } @@ -115,8 +116,16 @@ public class ViewAmbariStreamProvider implements AmbariStreamProvider { headerMap.put(entry.getKey(), Collections.singletonList(entry.getValue())); } - return streamProvider.processURL(controller.getAmbariServerURI(path.startsWith("/") ? path : "/" + path), - requestMethod, body, headerMap).getInputStream(); + return getInputStream(streamProvider.processURL(controller.getAmbariServerURI(path.startsWith("/") ? path : "/" + path), + requestMethod, body, headerMap)); + } + + private InputStream getInputStream(HttpURLConnection connection) throws IOException, AmbariHttpException { + int responseCode = connection.getResponseCode(); + if(responseCode >= ProxyService.HTTP_ERROR_RANGE_START){ + throw new AmbariHttpException(IOUtils.toString(connection.getErrorStream()),responseCode); + } + return connection.getInputStream(); } } http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java index ba7f446..e98a4cd 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java @@ -31,6 +31,7 @@ import org.apache.ambari.server.view.persistence.DataStoreImpl; import org.apache.ambari.server.view.persistence.DataStoreModule; import org.apache.ambari.server.view.validation.ValidationException; import org.apache.ambari.view.AmbariStreamProvider; +import org.apache.ambari.view.ClusterType; import org.apache.ambari.view.DataStore; import org.apache.ambari.view.ImpersonatorSetting; import org.apache.ambari.view.MaskException; @@ -54,7 +55,6 @@ import org.apache.hadoop.security.authentication.util.KerberosUtil; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.Velocity; import org.apache.velocity.exception.ParseErrorException; -import sun.security.krb5.KrbException; import java.io.StringWriter; import java.io.Writer; @@ -305,6 +305,27 @@ public class ViewContextImpl implements ViewContext, ViewController { } @Override + public AmbariStreamProvider getAmbariClusterStreamProvider() { + + String clusterHandle = viewInstanceEntity.getClusterHandle(); + ClusterType clusterType = viewInstanceEntity.getClusterType(); + + AmbariStreamProvider clusterStreamProvider = null; + + if(clusterHandle != null && clusterType == ClusterType.LOCAL_AMBARI){ + + clusterStreamProvider = getAmbariStreamProvider(); + + } else if(clusterHandle != null && clusterType == ClusterType.REMOTE_AMBARI){ + + clusterStreamProvider = viewRegistry.createRemoteAmbariStreamProvider(clusterHandle); + + } + + return clusterStreamProvider; + } + + @Override public synchronized DataStore getDataStore() { if (viewInstanceEntity != null) { if (dataStore == null) { http://git-wip-us.apache.org/repos/asf/ambari/blob/c150f0de/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java index d2d48a9..6d4ef82 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewRegistry.java @@ -45,6 +45,7 @@ import org.apache.ambari.server.orm.dao.MemberDAO; import org.apache.ambari.server.orm.dao.PermissionDAO; import org.apache.ambari.server.orm.dao.PrincipalDAO; import org.apache.ambari.server.orm.dao.PrivilegeDAO; +import org.apache.ambari.server.orm.dao.RemoteAmbariClusterDAO; import org.apache.ambari.server.orm.dao.ResourceDAO; import org.apache.ambari.server.orm.dao.ResourceTypeDAO; import org.apache.ambari.server.orm.dao.UserDAO; @@ -55,6 +56,7 @@ import org.apache.ambari.server.orm.entities.MemberEntity; import org.apache.ambari.server.orm.entities.PermissionEntity; import org.apache.ambari.server.orm.entities.PrincipalEntity; import org.apache.ambari.server.orm.entities.PrivilegeEntity; +import org.apache.ambari.server.orm.entities.RemoteAmbariClusterEntity; import org.apache.ambari.server.orm.entities.ResourceEntity; import org.apache.ambari.server.orm.entities.ResourceTypeEntity; import org.apache.ambari.server.orm.entities.UserEntity; @@ -83,6 +85,8 @@ import org.apache.ambari.server.view.configuration.PropertyConfig; import org.apache.ambari.server.view.configuration.ResourceConfig; import org.apache.ambari.server.view.configuration.ViewConfig; import org.apache.ambari.server.view.validation.ValidationException; +import org.apache.ambari.view.AmbariStreamProvider; +import org.apache.ambari.view.ClusterType; import org.apache.ambari.view.Masker; import org.apache.ambari.view.SystemException; import org.apache.ambari.view.View; @@ -134,6 +138,7 @@ public class ViewRegistry { protected static final int DEFAULT_REQUEST_READ_TIMEOUT = 10000; private static final String VIEW_AMBARI_VERSION_REGEXP = "^((\\d+\\.)?)*(\\*|\\d+)$"; private static final String VIEW_LOG_FILE = "view.log4j.properties"; + public static final String API_PREFIX = "/api/v1/clusters/"; /** * Thread pool @@ -281,6 +286,18 @@ public class ViewRegistry { @Inject AmbariSessionManager ambariSessionManager; + /** + * Registry for Remote Ambari Cluster + */ + @Inject + RemoteAmbariClusterRegistry remoteAmbariClusterRegistry; + + /** + * Remote Ambari Cluster Dao + */ + @Inject + RemoteAmbariClusterDAO remoteAmbariClusterDAO; + // ----- Constructors ----------------------------------------------------- @@ -896,12 +913,14 @@ public class ViewRegistry { if (viewInstance != null) { String clusterId = viewInstance.getClusterHandle(); - if (clusterId != null) { + if (clusterId != null && viewInstance.getClusterType() == ClusterType.LOCAL_AMBARI) { try { return new ClusterImpl(clustersProvider.get().getCluster(clusterId)); } catch (AmbariException e) { LOG.warn("Could not find the cluster identified by " + clusterId + "."); } + } else if(clusterId != null && viewInstance.getClusterType() == ClusterType.REMOTE_AMBARI){ + return remoteAmbariClusterRegistry.get(clusterId); } } return null; @@ -1409,6 +1428,7 @@ public class ViewRegistry { instance1.setResource(instance2.getResource()); instance1.setViewInstanceId(instance2.getViewInstanceId()); instance1.setClusterHandle(instance2.getClusterHandle()); + instance1.setClusterType(instance2.getClusterType()); instance1.setData(instance2.getData()); instance1.setEntities(instance2.getEntities()); instance1.setProperties(instance2.getProperties()); @@ -1884,6 +1904,34 @@ public class ViewRegistry { return new ViewAmbariStreamProvider(streamProvider, ambariSessionManager, AmbariServer.getController()); } + /** + * Get Remote Ambari Cluster Stream provider + * + * @param clusterName + * @return + */ + protected AmbariStreamProvider createRemoteAmbariStreamProvider(String clusterName){ + RemoteAmbariClusterEntity clusterEntity = remoteAmbariClusterDAO.findByName(clusterName); + if(clusterEntity != null) { + return new RemoteAmbariStreamProvider(getBaseurl(clusterEntity.getUrl()), + clusterEntity.getUsername(),clusterEntity.getPassword(), + configuration.getViewAmbariRequestConnectTimeout(),configuration.getViewAmbariRequestReadTimeout()); + } + return null; + } + + /** + * Get baseurl of the cluster + * baseurl wil be http://host:port + * + * @param url will be in format like http://host:port/api/v1/clusters/clusterName + * @return baseurl + */ + private String getBaseurl(String url){ + int index = url.indexOf(API_PREFIX); + return url.substring(0,index); + } +
