YARN-996. REST API support for node resource configuration. Contributed by Inigo Goiri.

This commit is contained in:
Giovanni Matteo Fumarola 2019-02-11 15:27:43 -08:00
parent 1ce2e91c4b
commit 7536488bbd
18 changed files with 681 additions and 60 deletions

View File

@ -60,7 +60,8 @@ public class JAXBContextResolver implements ContextResolver<JAXBContext> {
final Class[] rootUnwrappedTypes =
{ NewApplication.class, ApplicationSubmissionContextInfo.class,
ContainerLaunchContextInfo.class, LocalResourceInfo.class,
DelegationToken.class, AppQueue.class, AppPriority.class };
DelegationToken.class, AppQueue.class, AppPriority.class,
ResourceOptionInfo.class };
this.typesContextMap = new HashMap<Class, JAXBContext>();
context =

View File

@ -57,6 +57,9 @@ public final class RMWSConsts {
/** Path for {@code RMWebServiceProtocol#getNode}. */
public static final String NODES_NODEID = "/nodes/{nodeId}";
/** Path for {@code RMWebServiceProtocol#updateNodeResource}. */
public static final String NODE_RESOURCE = "/nodes/{nodeId}/resource";
/**
* Path for {@code RMWebServiceProtocol#getApps} and
* {@code RMWebServiceProtocol#getApp}.

View File

@ -58,6 +58,8 @@ import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.RMQueueAclInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationDeleteRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationSubmissionRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationUpdateRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceOptionInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.SchedulerTypeInfo;
/**
@ -148,6 +150,19 @@ public interface RMWebServiceProtocol {
*/
NodeInfo getNode(String nodeId);
/**
* This method changes the resources of a specific node, and it is reachable
* by using {@link RMWSConsts#NODE_RESOURCE}.
*
* @param hsr The servlet request.
* @param nodeId The node we want to retrieve the information for.
* It is a PathParam.
* @param resourceOption The resource change.
* @throws AuthorizationException If the user is not authorized.
*/
ResourceInfo updateNodeResource(HttpServletRequest hsr, String nodeId,
ResourceOptionInfo resourceOption) throws AuthorizationException;
/**
* This method retrieves all the app reports in the cluster, and it is
* reachable by using {@link RMWSConsts#APPS}.

View File

@ -56,8 +56,6 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.CommonConfigurationKeys;
import org.apache.hadoop.http.JettyUtils;
@ -118,6 +116,7 @@ import org.apache.hadoop.yarn.api.records.ReservationRequest;
import org.apache.hadoop.yarn.api.records.ReservationRequestInterpreter;
import org.apache.hadoop.yarn.api.records.ReservationRequests;
import org.apache.hadoop.yarn.api.records.Resource;
import org.apache.hadoop.yarn.api.records.ResourceOption;
import org.apache.hadoop.yarn.api.records.YarnApplicationState;
import org.apache.hadoop.yarn.conf.YarnConfiguration;
import org.apache.hadoop.yarn.exceptions.YarnException;
@ -125,8 +124,11 @@ import org.apache.hadoop.yarn.exceptions.YarnRuntimeException;
import org.apache.hadoop.yarn.factories.RecordFactory;
import org.apache.hadoop.yarn.factory.providers.RecordFactoryProvider;
import org.apache.hadoop.yarn.security.client.RMDelegationTokenIdentifier;
import org.apache.hadoop.yarn.server.api.protocolrecords.UpdateNodeResourceRequest;
import org.apache.hadoop.yarn.server.resourcemanager.AdminService;
import org.apache.hadoop.yarn.server.resourcemanager.RMAuditLogger;
import org.apache.hadoop.yarn.server.resourcemanager.RMAuditLogger.AuditConstants;
import org.apache.hadoop.yarn.server.resourcemanager.RMContext;
import org.apache.hadoop.yarn.server.resourcemanager.RMServerUtils;
import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager;
import org.apache.hadoop.yarn.server.resourcemanager.nodelabels.NodeLabelsUtils;
@ -185,6 +187,7 @@ import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationSubmi
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationUpdateRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationUpdateResponseInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceOptionInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.SchedulerInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.SchedulerTypeInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.StatisticsItemInfo;
@ -202,6 +205,8 @@ import org.apache.hadoop.yarn.webapp.ForbiddenException;
import org.apache.hadoop.yarn.webapp.NotFoundException;
import org.apache.hadoop.yarn.webapp.util.WebAppUtils;
import org.apache.hadoop.yarn.webapp.dao.SchedConfUpdateInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Inject;
@ -211,8 +216,8 @@ import com.google.inject.Singleton;
@Path(RMWSConsts.RM_WEB_SERVICE_PATH)
public class RMWebServices extends WebServices implements RMWebServiceProtocol {
private static final Log LOG =
LogFactory.getLog(RMWebServices.class.getName());
private static final Logger LOG =
LoggerFactory.getLogger(RMWebServices.class.getName());
private final ResourceManager rm;
private static RecordFactory recordFactory =
@ -482,6 +487,64 @@ public class RMWebServices extends WebServices implements RMWebServiceProtocol {
return nodeInfo;
}
@POST
@Path(RMWSConsts.NODE_RESOURCE)
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8,
MediaType.APPLICATION_XML + "; " + JettyUtils.UTF_8 })
public ResourceInfo updateNodeResource(
@Context HttpServletRequest hsr,
@PathParam(RMWSConsts.NODEID) String nodeId,
ResourceOptionInfo resourceOption) throws AuthorizationException {
UserGroupInformation callerUGI = getCallerUserGroupInformation(hsr, true);
initForWritableEndpoints(callerUGI, false);
RMNode rmNode = getRMNode(nodeId);
Map<NodeId, ResourceOption> nodeResourceMap =
Collections.singletonMap(
rmNode.getNodeID(), resourceOption.getResourceOption());
UpdateNodeResourceRequest updateRequest =
UpdateNodeResourceRequest.newInstance(nodeResourceMap);
try {
RMContext rmContext = this.rm.getRMContext();
AdminService admin = rmContext.getRMAdminService();
admin.updateNodeResource(updateRequest);
} catch (YarnException e) {
String message = "Failed to update the node resource " +
rmNode.getNodeID() + ".";
LOG.error(message, e);
throw new YarnRuntimeException(message, e);
} catch (IOException e) {
LOG.error("Failed to update the node resource {}.",
rmNode.getNodeID(), e);
}
return new ResourceInfo(rmNode.getTotalCapability());
}
/**
* Get the RMNode in the RM from the node identifier.
* @param nodeId Node identifier.
* @return The RMNode in the RM.
*/
private RMNode getRMNode(final String nodeId) {
if (nodeId == null || nodeId.isEmpty()) {
throw new NotFoundException("nodeId, " + nodeId + ", is empty or null");
}
NodeId nid = NodeId.fromString(nodeId);
RMContext rmContext = this.rm.getRMContext();
RMNode ni = rmContext.getRMNodes().get(nid);
if (ni == null) {
ni = rmContext.getInactiveRMNodes().get(nid);
if (ni == null) {
throw new NotFoundException("nodeId, " + nodeId + ", is not found");
}
}
return ni;
}
@GET
@Path(RMWSConsts.APPS)
@Produces({ MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8,

View File

@ -64,6 +64,7 @@ public class NodeInfo {
protected ResourceInfo usedResource;
protected ResourceInfo availableResource;
protected NodeAttributesInfo nodeAttributesInfo;
private ResourceInfo totalResource;
public NodeInfo() {
} // JAXB needs this
@ -92,6 +93,7 @@ public class NodeInfo {
this.lastHealthUpdate = ni.getLastHealthReportTime();
this.healthReport = String.valueOf(ni.getHealthReport());
this.version = ni.getNodeManagerVersion();
this.totalResource = new ResourceInfo(ni.getTotalCapability());
// Status of opportunistic containers.
this.numRunningOpportContainers = 0;
@ -242,4 +244,11 @@ public class NodeInfo {
this.lastHealthUpdate = lastHealthUpdate;
}
public void setTotalResource(ResourceInfo total) {
this.totalResource = total;
}
public ResourceInfo getTotalResource() {
return this.totalResource;
}
}

View File

@ -0,0 +1,65 @@
/**
* 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.hadoop.yarn.server.resourcemanager.webapp.dao;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.apache.hadoop.yarn.api.records.ResourceOption;
/**
* A JAXB representation of a {link ResourceOption}.
*/
@XmlRootElement(name = "resourceOption")
@XmlAccessorType(XmlAccessType.NONE)
public class ResourceOptionInfo {
@XmlElement(name = "resource")
private ResourceInfo resource = new ResourceInfo();
@XmlElement(name = "overCommitTimeout")
private int overCommitTimeout;
/** Internal resource option for caching. */
private ResourceOption resourceOption;
public ResourceOptionInfo() {
} // JAXB needs this
public ResourceOptionInfo(ResourceOption resourceOption) {
if (resourceOption != null) {
this.resource = new ResourceInfo(resourceOption.getResource());
this.overCommitTimeout = resourceOption.getOverCommitTimeout();
}
}
public ResourceOption getResourceOption() {
if (resourceOption == null) {
resourceOption = ResourceOption.newInstance(
resource.getResource(), overCommitTimeout);
}
return resourceOption;
}
@Override
public String toString() {
return getResourceOption().toString();
}
}

View File

@ -0,0 +1,27 @@
/*
* 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.
*/
/**
* This package contains the web data access objects (DAO) for the RM.
*/
@InterfaceAudience.Private
@InterfaceStability.Unstable
package org.apache.hadoop.yarn.server.resourcemanager.webapp.dao;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;

View File

@ -25,28 +25,37 @@ import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.HashSet;
import java.util.TreeMap;
import java.util.Iterator;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.ws.rs.core.MediaType;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.http.JettyUtils;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
import org.apache.hadoop.security.authentication.server.PseudoAuthenticationHandler;
import org.apache.hadoop.yarn.api.records.ContainerStatus;
import org.apache.hadoop.yarn.api.records.NodeAttribute;
import org.apache.hadoop.yarn.api.records.NodeAttributeType;
import org.apache.hadoop.yarn.api.records.NodeId;
import org.apache.hadoop.yarn.api.records.NodeState;
import org.apache.hadoop.yarn.api.records.Resource;
import org.apache.hadoop.yarn.api.records.ResourceOption;
import org.apache.hadoop.yarn.api.records.ResourceUtilization;
import org.apache.hadoop.yarn.conf.YarnConfiguration;
import org.apache.hadoop.yarn.server.utils.BuilderUtils;
import org.apache.hadoop.yarn.server.api.records.NodeHealthStatus;
import org.apache.hadoop.yarn.server.api.records.NodeStatus;
@ -66,6 +75,9 @@ import org.apache.hadoop.yarn.server.resourcemanager.rmnode.RMNodeStatusEvent;
import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ResourceScheduler;
import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerNodeReport;
import org.apache.hadoop.yarn.server.resourcemanager.scheduler.constraint.AllocationTagsManager;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.NodeInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceOptionInfo;
import org.apache.hadoop.yarn.util.Records;
import org.apache.hadoop.yarn.util.RackResolver;
import org.apache.hadoop.yarn.util.YarnVersionInfo;
@ -85,6 +97,7 @@ import org.xml.sax.InputSource;
import com.google.common.base.Joiner;
import com.google.inject.Guice;
import com.google.inject.Singleton;
import com.google.inject.servlet.ServletModule;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.ClientResponse.Status;
@ -96,22 +109,58 @@ import com.sun.jersey.test.framework.WebAppDescriptor;
public class TestRMWebServicesNodes extends JerseyTestBase {
private static MockRM rm;
private static YarnConfiguration conf;
private static String userName;
private static class WebServletModule extends ServletModule {
@Override
protected void configureServlets() {
bind(JAXBContextResolver.class);
try {
userName = UserGroupInformation.getCurrentUser().getShortUserName();
} catch (IOException ioe) {
throw new RuntimeException("Unable to get current user name "
+ ioe.getMessage(), ioe);
}
conf = new YarnConfiguration();
conf.set(YarnConfiguration.YARN_ADMIN_ACL, userName);
bind(RMWebServices.class);
bind(GenericExceptionHandler.class);
rm = new MockRM(new Configuration());
rm = new MockRM(conf);
rm.getRMContext().getContainerTokenSecretManager().rollMasterKey();
rm.getRMContext().getNMTokenSecretManager().rollMasterKey();
rm.disableDrainEventsImplicitly();
bind(ResourceManager.class).toInstance(rm);
filter("/*").through(TestRMCustomAuthFilter.class);
serve("/*").with(GuiceContainer.class);
}
}
/**
* Custom filter to be able to test auth methods and let the other ones go.
*/
@Singleton
public static class TestRMCustomAuthFilter extends AuthenticationFilter {
@Override
protected Properties getConfiguration(String configPrefix,
FilterConfig filterConfig) throws ServletException {
Properties props = new Properties();
Enumeration<?> names = filterConfig.getInitParameterNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
if (name.startsWith(configPrefix)) {
String value = filterConfig.getInitParameter(name);
props.put(name.substring(configPrefix.length()), value);
}
}
props.put(AuthenticationFilter.AUTH_TYPE, "simple");
props.put(PseudoAuthenticationHandler.ANONYMOUS_ALLOWED, "true");
return props;
}
}
static {
GuiceServletConfig.setInjector(
Guice.createInjector(new WebServletModule()));
@ -541,7 +590,7 @@ public class TestRMWebServicesNodes extends JerseyTestBase {
}
private void verifyNonexistNodeException(String message, String type, String classname) {
assertTrue("exception message incorrect",
assertTrue("exception message incorrect: " + message,
"java.lang.Exception: nodeId, node_invalid:99, is not found"
.matches(message));
assertTrue("exception type incorrect", "NotFoundException".matches(type));
@ -714,6 +763,64 @@ public class TestRMWebServicesNodes extends JerseyTestBase {
verifyNodeInfo(info, rmnode1);
}
@Test
public void testUpdateNodeResource() throws Exception {
WebResource r = resource().path(RMWSConsts.RM_WEB_SERVICE_PATH);
r = r.queryParam("user.name", userName);
RMNode rmnode = getRunningRMNode("h1", 1234, 5120);
String rmnodeId = rmnode.getNodeID().toString();
assertEquals("h1:1234", rmnodeId);
// assert memory and default vcores
ClientResponse response = r.path(RMWSConsts.NODES).path(rmnodeId)
.accept(MediaType.APPLICATION_XML)
.get(ClientResponse.class);
NodeInfo nodeInfo0 = response.getEntity(NodeInfo.class);
ResourceInfo nodeResource0 = nodeInfo0.getTotalResource();
assertEquals(5120, nodeResource0.getMemorySize());
assertEquals(4, nodeResource0.getvCores());
// the RM needs to be running to process the resource update
rm.start();
// update memory to 8192MB and 5 cores
Resource resource = Resource.newInstance(8192, 5);
ResourceOptionInfo resourceOption = new ResourceOptionInfo(
ResourceOption.newInstance(resource, 1000));
response = r.path(RMWSConsts.NODES).path(rmnodeId).path("resource")
.entity(resourceOption, MediaType.APPLICATION_XML_TYPE)
.accept(MediaType.APPLICATION_XML)
.post(ClientResponse.class);
assertResponseStatusCode(Status.OK, response.getStatusInfo());
ResourceInfo updatedResource = response.getEntity(ResourceInfo.class);
assertEquals(8192, updatedResource.getMemorySize());
assertEquals(5, updatedResource.getvCores());
// assert updated memory and cores
response = r.path(RMWSConsts.NODES).path(rmnodeId)
.accept(MediaType.APPLICATION_XML)
.get(ClientResponse.class);
NodeInfo nodeInfo1 = response.getEntity(NodeInfo.class);
ResourceInfo nodeResource1 = nodeInfo1.getTotalResource();
assertEquals(8192, nodeResource1.getMemorySize());
assertEquals(5, nodeResource1.getvCores());
// test non existing node
response = r.path(RMWSConsts.NODES).path("badnode").path("resource")
.entity(resourceOption, MediaType.APPLICATION_XML_TYPE)
.accept(MediaType.APPLICATION_JSON)
.post(ClientResponse.class);
assertResponseStatusCode(Status.BAD_REQUEST, response.getStatusInfo());
JSONObject json = response.getEntity(JSONObject.class);
JSONObject exception = json.getJSONObject("RemoteException");
assertEquals("IllegalArgumentException", exception.getString("exception"));
String msg = exception.getString("message");
assertTrue("Wrong message: " + msg, msg.startsWith("Invalid NodeId"));
rm.stop();
}
public void verifyNodesXML(NodeList nodes, RMNode nm)
throws JSONException,
Exception {
@ -750,7 +857,7 @@ public class TestRMWebServicesNodes extends JerseyTestBase {
public void verifyNodeInfo(JSONObject nodeInfo, RMNode nm)
throws JSONException, Exception {
assertEquals("incorrect number of elements", 20, nodeInfo.length());
assertEquals("incorrect number of elements", 21, nodeInfo.length());
JSONObject resourceInfo = nodeInfo.getJSONObject("resourceUtilization");
verifyNodeInfoGeneric(nm, nodeInfo.getString("state"),

View File

@ -48,6 +48,13 @@
<artifactId>hadoop-yarn-common</artifactId>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-yarn-common</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-yarn-server-common</artifactId>

View File

@ -58,6 +58,8 @@ import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.RMQueueAclInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationDeleteRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationSubmissionRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationUpdateRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceOptionInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.SchedulerTypeInfo;
import org.apache.hadoop.yarn.server.webapp.dao.AppAttemptInfo;
import org.apache.hadoop.yarn.server.webapp.dao.ContainerInfo;
@ -155,6 +157,16 @@ public class DefaultRequestInterceptorREST
null);
}
@Override
public ResourceInfo updateNodeResource(HttpServletRequest hsr,
String nodeId, ResourceOptionInfo resourceOption) {
final String nodePath =
RMWSConsts.RM_WEB_SERVICE_PATH + RMWSConsts.NODES + "/" + nodeId;
return RouterWebServiceUtil.genericForward(webAppAddress, hsr,
ResourceInfo.class, HTTPMethods.POST,
nodePath + "/resource", resourceOption, null);
}
@Override
public AppsInfo getApps(HttpServletRequest hsr, String stateQuery,
Set<String> statesQuery, String finalStatusQuery, String userQuery,

View File

@ -21,9 +21,11 @@ package org.apache.hadoop.yarn.server.router.webapp;
import java.io.IOException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Callable;
@ -84,6 +86,8 @@ import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.RMQueueAclInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationDeleteRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationSubmissionRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationUpdateRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceOptionInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.SchedulerTypeInfo;
import org.apache.hadoop.yarn.server.router.RouterMetrics;
import org.apache.hadoop.yarn.server.router.RouterServerUtil;
@ -778,6 +782,20 @@ public class FederationInterceptorREST extends AbstractRESTRequestInterceptor {
};
}
/**
* Get the active subclusters in the federation.
* @return Map from subcluster id to its info.
* @throws NotFoundException If the subclusters cannot be found.
*/
private Map<SubClusterId, SubClusterInfo> getActiveSubclusters()
throws NotFoundException {
try {
return federationFacade.getSubClusters(true);
} catch (YarnException e) {
throw new NotFoundException(e.getMessage());
}
}
/**
* The YARN Router will forward to the request to all the SubClusters to find
* where the node is running.
@ -796,67 +814,115 @@ public class FederationInterceptorREST extends AbstractRESTRequestInterceptor {
*/
@Override
public NodeInfo getNode(String nodeId) {
Map<SubClusterId, SubClusterInfo> subClustersActive = null;
try {
subClustersActive = federationFacade.getSubClusters(true);
} catch (YarnException e) {
throw new NotFoundException(e.getMessage());
}
final Map<SubClusterId, SubClusterInfo> subClustersActive =
getActiveSubclusters();
if (subClustersActive.isEmpty()) {
throw new NotFoundException(
FederationPolicyUtils.NO_ACTIVE_SUBCLUSTER_AVAILABLE);
}
final Map<SubClusterInfo, NodeInfo> results =
getNode(subClustersActive.values(), nodeId);
// Send the requests in parallel
CompletionService<NodeInfo> compSvc =
new ExecutorCompletionService<NodeInfo>(this.threadpool);
for (final SubClusterInfo info : subClustersActive.values()) {
compSvc.submit(new Callable<NodeInfo>() {
@Override
public NodeInfo call() {
DefaultRequestInterceptorREST interceptor =
getOrCreateInterceptorForSubCluster(
info.getSubClusterId(), info.getRMWebServiceAddress());
try {
NodeInfo nodeInfo = interceptor.getNode(nodeId);
return nodeInfo;
} catch (Exception e) {
LOG.error("Subcluster {} failed to return nodeInfo.",
info.getSubClusterId());
return null;
}
}
});
}
// Collect all the responses in parallel
// Collect the responses
NodeInfo nodeInfo = null;
for (int i = 0; i < subClustersActive.size(); i++) {
for (NodeInfo nodeResponse : results.values()) {
try {
Future<NodeInfo> future = compSvc.take();
NodeInfo nodeResponse = future.get();
// Check if the node was found in this SubCluster
if (nodeResponse != null) {
// Check if the node was already found in a different SubCluster and
// it has an old health report
if (nodeInfo == null || nodeInfo.getLastHealthUpdate() <
nodeResponse.getLastHealthUpdate()) {
nodeInfo = nodeResponse;
}
// Check if the node was already found in a different SubCluster and
// it has an old health report
if (nodeInfo == null || nodeInfo.getLastHealthUpdate() <
nodeResponse.getLastHealthUpdate()) {
nodeInfo = nodeResponse;
}
} catch (Throwable e) {
LOG.warn("Failed to get node report ", e);
}
}
if (nodeInfo == null) {
throw new NotFoundException("nodeId, " + nodeId + ", is not found");
}
return nodeInfo;
}
/**
* Get a node and the subcluster where it is.
* @param subClusters Subclusters where to search.
* @param nodeId Identifier of the node we are looking for.
* @return Map between subcluster and node.
*/
private Map<SubClusterInfo, NodeInfo> getNode(
Collection<SubClusterInfo> subClusters, String nodeId) {
// Send the requests in parallel
CompletionService<NodeInfo> compSvc =
new ExecutorCompletionService<NodeInfo>(this.threadpool);
final Map<SubClusterInfo, Future<NodeInfo>> futures = new HashMap<>();
for (final SubClusterInfo subcluster : subClusters) {
final SubClusterId subclusterId = subcluster.getSubClusterId();
Future<NodeInfo> result = compSvc.submit(() -> {
try {
DefaultRequestInterceptorREST interceptor =
getOrCreateInterceptorForSubCluster(
subclusterId, subcluster.getRMWebServiceAddress());
return interceptor.getNode(nodeId);
} catch (Exception e) {
LOG.error("Subcluster {} failed to return nodeInfo.",
subclusterId);
return null;
}
});
futures.put(subcluster, result);
}
// Collect the results
final Map<SubClusterInfo, NodeInfo> results = new HashMap<>();
for (Entry<SubClusterInfo, Future<NodeInfo>> entry : futures.entrySet()) {
try {
final Future<NodeInfo> future = entry.getValue();
final NodeInfo nodeInfo = future.get();
// Check if the node was found in this SubCluster
if (nodeInfo != null) {
SubClusterInfo subcluster = entry.getKey();
results.put(subcluster, nodeInfo);
}
} catch (Throwable e) {
LOG.warn("Failed to get node report ", e);
}
}
return results;
}
/**
* Get the subcluster a node belongs to.
* @param nodeId Identifier of the node we are looking for.
* @return The subcluster containing the node.
* @throws NotFoundException If the node cannot be found.
*/
private SubClusterInfo getNodeSubcluster(String nodeId)
throws NotFoundException {
final Collection<SubClusterInfo> subClusters =
getActiveSubclusters().values();
final Map<SubClusterInfo, NodeInfo> results =
getNode(subClusters, nodeId);
SubClusterInfo subcluster = null;
NodeInfo nodeInfo = null;
for (Entry<SubClusterInfo, NodeInfo> entry : results.entrySet()) {
NodeInfo nodeResponse = entry.getValue();
if (nodeInfo == null || nodeInfo.getLastHealthUpdate() <
nodeResponse.getLastHealthUpdate()) {
subcluster = entry.getKey();
nodeInfo = nodeResponse;
}
}
if (subcluster == null) {
throw new NotFoundException(
"Cannot find " + nodeId + " in any subcluster");
}
return subcluster;
}
/**
* The YARN Router will forward the request to all the YARN RMs in parallel,
* after that it will remove all the duplicated NodeInfo by using the NodeId.
@ -881,10 +947,10 @@ public class FederationInterceptorREST extends AbstractRESTRequestInterceptor {
NodesInfo nodes = new NodesInfo();
Map<SubClusterId, SubClusterInfo> subClustersActive = null;
final Map<SubClusterId, SubClusterInfo> subClustersActive;
try {
subClustersActive = federationFacade.getSubClusters(true);
} catch (YarnException e) {
subClustersActive = getActiveSubclusters();
} catch (Exception e) {
LOG.error("Cannot get nodes: {}", e.getMessage());
return new NodesInfo();
}
@ -935,14 +1001,25 @@ public class FederationInterceptorREST extends AbstractRESTRequestInterceptor {
return RouterWebServiceUtil.deleteDuplicateNodesInfo(nodes.getNodes());
}
@Override
public ResourceInfo updateNodeResource(HttpServletRequest hsr,
String nodeId, ResourceOptionInfo resourceOption) {
SubClusterInfo subcluster = getNodeSubcluster(nodeId);
DefaultRequestInterceptorREST interceptor =
getOrCreateInterceptorForSubCluster(
subcluster.getSubClusterId(),
subcluster.getRMWebServiceAddress());
return interceptor.updateNodeResource(hsr, nodeId, resourceOption);
}
@Override
public ClusterMetricsInfo getClusterMetricsInfo() {
ClusterMetricsInfo metrics = new ClusterMetricsInfo();
Map<SubClusterId, SubClusterInfo> subClustersActive = null;
final Map<SubClusterId, SubClusterInfo> subClustersActive;
try {
subClustersActive = federationFacade.getSubClusters(true);
} catch (YarnException e) {
subClustersActive = getActiveSubclusters();
} catch (Exception e) {
LOG.error(e.getLocalizedMessage());
return metrics;
}

View File

@ -28,6 +28,7 @@ import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.FormParam;
@ -80,6 +81,8 @@ import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.RMQueueAclInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationDeleteRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationSubmissionRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationUpdateRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceOptionInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.SchedulerTypeInfo;
import org.apache.hadoop.yarn.server.router.Router;
import org.apache.hadoop.yarn.server.webapp.dao.ContainerInfo;
@ -394,6 +397,22 @@ public class RouterWebServices implements RMWebServiceProtocol {
return pipeline.getRootInterceptor().getNode(nodeId);
}
@POST
@Path(RMWSConsts.NODE_RESOURCE)
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8,
MediaType.APPLICATION_XML + "; " + JettyUtils.UTF_8 })
@Override
public ResourceInfo updateNodeResource(
@Context HttpServletRequest hsr,
@PathParam(RMWSConsts.NODEID) String nodeId,
ResourceOptionInfo resourceOption) throws AuthorizationException {
init();
RequestInterceptorChainWrapper pipeline = getInterceptorChain(null);
return pipeline.getRootInterceptor().updateNodeResource(
hsr, nodeId, resourceOption);
}
@GET
@Path(RMWSConsts.APPS)
@Produces({ MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8,

View File

@ -31,6 +31,7 @@ import javax.ws.rs.core.Response.Status;
import org.apache.hadoop.security.authorize.AuthorizationException;
import org.apache.hadoop.yarn.api.records.ApplicationId;
import org.apache.hadoop.yarn.api.records.Resource;
import org.apache.hadoop.yarn.exceptions.ApplicationNotFoundException;
import org.apache.hadoop.yarn.exceptions.YarnException;
import org.apache.hadoop.yarn.server.federation.store.records.SubClusterId;
@ -43,6 +44,7 @@ import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.NewApplication;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.NodeInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.NodesInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceOptionInfo;
import org.apache.hadoop.yarn.webapp.NotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -177,6 +179,16 @@ public class MockDefaultRequestInterceptorREST
return nodes;
}
@Override
public ResourceInfo updateNodeResource(HttpServletRequest hsr,
String nodeId, ResourceOptionInfo resourceOption) {
if (!isRunning) {
throw new RuntimeException("RM is stopped");
}
Resource resource = resourceOption.getResourceOption().getResource();
return new ResourceInfo(resource);
}
@Override
public ClusterMetricsInfo getClusterMetricsInfo() {
if (!isRunning) {

View File

@ -55,6 +55,8 @@ import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.RMQueueAclInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationDeleteRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationSubmissionRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationUpdateRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceOptionInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.SchedulerTypeInfo;
import org.apache.hadoop.yarn.server.webapp.dao.AppAttemptInfo;
import org.apache.hadoop.yarn.server.webapp.dao.ContainerInfo;
@ -114,6 +116,12 @@ public class MockRESTRequestInterceptor extends AbstractRESTRequestInterceptor {
return new NodeInfo();
}
@Override
public ResourceInfo updateNodeResource(HttpServletRequest hsr, String nodeId,
ResourceOptionInfo resourceOption) throws AuthorizationException {
return new ResourceInfo();
}
@SuppressWarnings("checkstyle:parameternumber")
@Override
public AppsInfo getApps(HttpServletRequest hsr, String stateQuery,
@ -349,5 +357,4 @@ public class MockRESTRequestInterceptor extends AbstractRESTRequestInterceptor {
String containerId) {
return new ContainerInfo();
}
}

View File

@ -53,6 +53,8 @@ import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.RMQueueAclInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationDeleteRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationSubmissionRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationUpdateRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceOptionInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.SchedulerTypeInfo;
import org.apache.hadoop.yarn.server.webapp.dao.AppAttemptInfo;
import org.apache.hadoop.yarn.server.webapp.dao.ContainerInfo;
@ -139,6 +141,13 @@ public class PassThroughRESTRequestInterceptor
return getNextInterceptor().getNode(nodeId);
}
@Override
public ResourceInfo updateNodeResource(HttpServletRequest hsr, String nodeId,
ResourceOptionInfo resourceOption) throws AuthorizationException {
return getNextInterceptor().updateNodeResource(
hsr, nodeId, resourceOption);
}
@Override
public AppsInfo getApps(HttpServletRequest hsr, String stateQuery,
Set<String> statesQuery, String finalStatusQuery, String userQuery,

View File

@ -25,6 +25,8 @@ import java.util.List;
import javax.ws.rs.core.Response;
import org.apache.hadoop.yarn.api.records.ApplicationId;
import org.apache.hadoop.yarn.api.records.Resource;
import org.apache.hadoop.yarn.api.records.ResourceOption;
import org.apache.hadoop.yarn.conf.YarnConfiguration;
import org.apache.hadoop.yarn.exceptions.YarnException;
import org.apache.hadoop.yarn.server.federation.policies.manager.UniformBroadcastPolicyManager;
@ -40,6 +42,8 @@ import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ClusterMetricsIn
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.NewApplication;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.NodeInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.NodesInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceOptionInfo;
import org.junit.Assert;
import org.junit.Test;
import org.slf4j.Logger;
@ -423,6 +427,24 @@ public class TestFederationInterceptorREST extends BaseRouterWebServicesTest {
// The remove duplicate operations is tested in TestRouterWebServiceUtil
}
/**
* This test validates the correctness of updateNodeResource().
*/
@Test
public void testUpdateNodeResource() {
List<NodeInfo> nodes = interceptor.getNodes(null).getNodes();
Assert.assertFalse(nodes.isEmpty());
final String nodeId = nodes.get(0).getNodeId();
ResourceOptionInfo resourceOption = new ResourceOptionInfo(
ResourceOption.newInstance(
Resource.newInstance(2048, 3), 1000));
ResourceInfo resource = interceptor.updateNodeResource(
null, nodeId, resourceOption);
Assert.assertNotNull(resource);
Assert.assertEquals(2048, resource.getMemorySize());
Assert.assertEquals(3, resource.getvCores());
}
/**
* This test validates the correctness of getClusterMetricsInfo in case each
* SubCluster provided a ClusterMetricsInfo with appsSubmitted set to the

View File

@ -44,6 +44,7 @@ import static org.apache.hadoop.yarn.server.resourcemanager.webapp.RMWSConsts.IN
import static org.apache.hadoop.yarn.server.resourcemanager.webapp.RMWSConsts.LABELS;
import static org.apache.hadoop.yarn.server.resourcemanager.webapp.RMWSConsts.LABEL_MAPPINGS;
import static org.apache.hadoop.yarn.server.resourcemanager.webapp.RMWSConsts.METRICS;
import static org.apache.hadoop.yarn.server.resourcemanager.webapp.RMWSConsts.NODE_RESOURCE;
import static org.apache.hadoop.yarn.server.resourcemanager.webapp.RMWSConsts.NODES;
import static org.apache.hadoop.yarn.server.resourcemanager.webapp.RMWSConsts.NODES_NODEID;
import static org.apache.hadoop.yarn.server.resourcemanager.webapp.RMWSConsts.NODES_NODEID_GETLABELS;
@ -63,6 +64,7 @@ import static org.apache.hadoop.yarn.server.resourcemanager.webapp.RMWSConsts.ST
import static org.apache.hadoop.yarn.server.resourcemanager.webapp.RMWSConsts.TIME;
import static org.apache.hadoop.yarn.server.router.webapp.HTTPMethods.POST;
import static org.apache.hadoop.yarn.server.router.webapp.HTTPMethods.PUT;
import static org.apache.hadoop.yarn.webapp.WebServicesTestUtils.assertResponseStatusCode;
import static org.apache.hadoop.yarn.webapp.util.WebAppUtils.getNMWebAppURLWithoutScheme;
import static org.apache.hadoop.yarn.webapp.util.WebAppUtils.getRMWebAppURLWithScheme;
import static org.apache.hadoop.yarn.webapp.util.WebAppUtils.getRouterWebAppURLWithScheme;
@ -87,6 +89,8 @@ import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.test.GenericTestUtils;
import org.apache.hadoop.util.concurrent.HadoopExecutors;
import org.apache.hadoop.yarn.api.records.NodeLabel;
import org.apache.hadoop.yarn.api.records.Resource;
import org.apache.hadoop.yarn.api.records.ResourceOption;
import org.apache.hadoop.yarn.conf.YarnConfiguration;
import org.apache.hadoop.yarn.server.nodemanager.NodeManager;
import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager;
@ -116,11 +120,13 @@ import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.NodesInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationDeleteRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationSubmissionRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ReservationUpdateRequestInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceOptionInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.SchedulerTypeInfo;
import org.apache.hadoop.yarn.server.router.Router;
import org.apache.hadoop.yarn.server.webapp.WebServices;
import org.apache.hadoop.yarn.server.webapp.dao.AppsInfo;
import org.apache.hadoop.yarn.server.webapp.dao.ContainersInfo;
import org.codehaus.jettison.json.JSONObject;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@ -131,6 +137,7 @@ import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.api.client.WebResource.Builder;
import net.jcip.annotations.NotThreadSafe;
@ -467,6 +474,47 @@ public class TestRouterWebServicesREST {
routerResponse.getVersion());
}
/**
* This test validates the correctness of
* {@link RMWebServiceProtocol#updateNodeResources()} inside Router.
*/
@Test
public void testUpdateNodeResource() throws Exception {
// wait until a node shows up and check the resources
GenericTestUtils.waitFor(() -> getNodeId() != null, 100, 5 * 1000);
String nodeId = getNodeId();
// assert memory and default vcores
List<NodeInfo> responses0 = performGetCalls(
RM_WEB_SERVICE_PATH + format(NODES_NODEID, getNodeId()),
NodeInfo.class, null, null);
NodeInfo nodeInfo0 = responses0.get(0);
assertEquals(8192, nodeInfo0.getTotalResource().getMemorySize());
assertEquals(8, nodeInfo0.getTotalResource().getvCores());
// update memory to 4096MB and 5 cores
Resource resource = Resource.newInstance(4096, 5);
ResourceOptionInfo resourceOption = new ResourceOptionInfo(
ResourceOption.newInstance(resource, 1000));
ClientResponse routerResponse = performCall(
RM_WEB_SERVICE_PATH + format(NODE_RESOURCE, nodeId),
null, null, resourceOption, POST);
assertResponseStatusCode(Status.OK, routerResponse.getStatusInfo());
JSONObject json = routerResponse.getEntity(JSONObject.class);
JSONObject totalResource = json.getJSONObject("resourceInfo");
assertEquals(resource.getMemorySize(), totalResource.getLong("memory"));
assertEquals(resource.getVirtualCores(), totalResource.getLong("vCores"));
// assert updated memory and cores
List<NodeInfo> responses1 = performGetCalls(
RM_WEB_SERVICE_PATH + format(NODES_NODEID, getNodeId()),
NodeInfo.class, null, null);
NodeInfo nodeInfo1 = responses1.get(0);
assertEquals(4096, nodeInfo1.getTotalResource().getMemorySize());
assertEquals(5, nodeInfo1.getTotalResource().getvCores());
}
/**
* This test validates the correctness of
* {@link RMWebServiceProtocol#getActivities()} inside Router.
@ -1338,7 +1386,11 @@ public class TestRouterWebServicesREST {
ClientResponse response =
toRM.accept(APPLICATION_XML).get(ClientResponse.class);
NodesInfo ci = response.getEntity(NodesInfo.class);
return ci.getNodes().get(0).getNodeId();
List<NodeInfo> nodes = ci.getNodes();
if (nodes.isEmpty()) {
return null;
}
return nodes.get(0).getNodeId();
}
private NewApplication getNewApplicationId() {

View File

@ -2817,6 +2817,7 @@ Use the following URI to obtain a Node Object, from a node identified by the nod
| usedVirtualCores | long | The total number of vCores currently used on the node |
| availableVirtualCores | long | The total number of vCores available on the node |
| resourceUtilization | object | Resource utilization on the node |
| totalResource | object | Resources on the node |
The *resourceUtilization* object contains the following elements:
@ -2871,6 +2872,11 @@ Response Body:
"aggregatedContainersPhysicalMemoryMB": 0,
"aggregatedContainersVirtualMemoryMB": 0,
"containersCPUUsage": 0
},
"totalResource":
{
"memory": 2048,
"vCores": 5
}
}
}
@ -2916,9 +2922,117 @@ Response Body:
<aggregatedContainersVirtualMemoryMB>0</aggregatedContainersVirtualMemoryMB>
<containersCPUUsage>0.0</containersCPUUsage>
</resourceUtilization>
<totalResource>
<memory>2048</memory>
<vCores>5</vCores>
</totalResource>
</node>
```
Cluster Node Update Resource API
--------------------------------
Update the total resources in a node.
### URI
Use the following URI to update the resources of a Node Object identified by the nodeid value.
http://rm-http-address:port/ws/v1/cluster/nodes/{nodeid}/resource
### HTTP Operations Supported
POST
### Query Parameters Supported
None
### Elements of the *resourceOption* object
| Item | Data Type | Description |
|:---- |:---- |:---- |
| memory | long | The total amount of memory to set on the node (in MB) |
| vcores | long | The total number of vCores to set on the node |
| overCommitTimeout | long | The timeout to preempt containers |
### Response Examples
**JSON response**
HTTP Request:
POST http://rm-http-address:port/ws/v1/cluster/nodes/h2:1235/resource
Request body:
```json
{
"resource":
{
"memory": 1024,
"vCores": 3
},
"overCommitTimeout": -1
}
```
Response Header:
HTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunked
Server: Jetty(6.1.26)
Response Body:
```json
{
"resourceInfo":
{
"memory": 8192,
"vCores": 5
}
}
```
**XML response**
HTTP Request:
GET http://rm-http-address:port/ws/v1/cluster/node/h2:1235/resource
Accept: application/xml
Request body:
```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resourceOption>
<resource>
<memory>8192</memory>
<vCores>5</vCores>
</resource>
<overCommitTimeout>1000</overCommitTimeout>
</resourceOption>
```
Response Header:
HTTP/1.1 200 OK
Content-Type: application/xml
Content-Length: 552
Server: Jetty(6.1.26)
Response Body:
```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resourceInfo>
<memory>8192</memory>
<vCores>5</vCores>
</resourceInfo>
```
Cluster Writeable APIs
----------------------