blob: ff01dc51d80db8aec8b0a3a91cfdda7e75838f4e [file] [log] [blame]
/*
* 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.slider.agent.rest
import com.google.common.base.Preconditions
import com.sun.jersey.api.client.Client
import com.sun.jersey.api.client.ClientResponse
import com.sun.jersey.api.client.UniformInterfaceException
import com.sun.jersey.api.client.WebResource
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import org.apache.hadoop.yarn.webapp.NotFoundException
import org.apache.slider.api.StateValues
import org.apache.slider.api.types.ComponentInformation
import org.apache.slider.api.types.ContainerInformation
import org.apache.slider.common.tools.SliderUtils
import org.apache.slider.core.conf.AggregateConf
import org.apache.slider.core.conf.ConfTree
import org.apache.slider.core.conf.ConfTreeOperations
import org.apache.slider.core.restclient.HttpVerb
import org.apache.slider.server.appmaster.web.rest.application.ApplicationResource
import org.apache.slider.api.types.PingInformation
import org.apache.slider.test.Outcome
import javax.net.ssl.SSLException
import javax.ws.rs.core.MediaType
import static org.apache.slider.api.ResourceKeys.COMPONENT_INSTANCES
import static org.apache.slider.api.StatusKeys.*
import static org.apache.slider.common.SliderKeys.COMPONENT_AM
import static org.apache.slider.server.appmaster.web.rest.RestPaths.*
/**
* This class contains parts of tests that can be run
* against a deployed AM: local or remote.
* It uses Jersey WebResource... and must be passed a client
* that is either secure or not
* {@link WebResource}
*
*
*/
@CompileStatic
@Slf4j
class JerseyTestDelegates extends AbstractRestTestDelegate {
final String appmaster;
final String application;
final Client jersey;
final WebResource amResource
final WebResource appResource
JerseyTestDelegates(String appmaster, Client jersey,
boolean enableComplexVerbs = true) {
super(enableComplexVerbs)
this.jersey = jersey
this.appmaster = appmaster
application = appendToURL(appmaster, SLIDER_PATH_APPLICATION)
amResource = jersey.resource(appmaster)
amResource.type(MediaType.APPLICATION_JSON)
appResource = amResource.path(SLIDER_PATH_APPLICATION)
}
/**
* <T> T get(Class<T> c)
* Get operation against a path under the Application
* @param subpath path
* @return
*/
public <T> T jGetApplicationResource(String subpath, Class<T> c) {
return (T)jExec(HttpVerb.GET, subpath, c);
}
/**
* <T> T get(Class<T> c)
* Get operation against a path under the Application
* @param subpath path
* @return
*/
public <T> T jExec(HttpVerb method, String subpath, Class<T> c) {
WebResource resource = applicationResource(subpath);
jExec(method, resource, c);
}
public <T> T jExec(HttpVerb method, WebResource resource, Class<T> c) {
try {
Preconditions.checkArgument(c != null);
resource.accept(MediaType.APPLICATION_JSON_TYPE);
(T) resource.method(method.verb, c);
} catch (UniformInterfaceException ex) {
uprateFaults(method, resource, ex);
// never reached as the uprate throws the new exception
return null;
}
}
/**
* Create a resource under the application path
* @param subpath
* @return
*/
public WebResource applicationResource(String subpath) {
return appResource.path(subpath)
}
/**
* Convert faults to exceptions; pass through 200 responses
* @param method
* @param webResource
* @param ex
* @return
*/
public void uprateFaults(
HttpVerb method,
WebResource webResource,
UniformInterfaceException ex) {
uprateFaults(method.verb,
webResource.URI.toString(),
ex.response.status,
ex.response.toString())
}
/**
* <T> T get(Class<T> c)
* Get operation against a path under the AM
* @param path path
* @return
*/
public <T> T jGetAMResource(String path, Class<T> c) {
assert c
WebResource webResource = buildResource(path)
(T)webResource.get(c)
}
/**
* Get operation against a path under the AM
* @param path path
* @return the string value
*/
public String jerseyGet(String path) {
return jGetAMResource(path, String.class)
}
/**
* Build a resource against a path under the AM API
* @param path path
* @return a resource for use
*/
public WebResource buildResource(String path) {
assert path
String fullpath = SliderUtils.appendToURL(appmaster, path);
WebResource webResource = jersey.resource(fullpath);
webResource.type(MediaType.APPLICATION_JSON);
log.info("HTTP operation against $fullpath");
return webResource;
}
public void testJerseyGetConftree() throws Throwable {
jGetApplicationResource(LIVE_RESOURCES, ConfTree.class);
}
public void testCodahaleOperations() throws Throwable {
describe "Codahale operations"
jerseyGet("/")
jerseyGet(SYSTEM_THREADS)
jerseyGet(SYSTEM_HEALTHCHECK)
jerseyGet(SYSTEM_PING)
jerseyGet(SYSTEM_METRICS_JSON)
}
public void logCodahaleMetrics() {
// query Coda Hale metrics
log.info jerseyGet(SYSTEM_HEALTHCHECK)
log.info jerseyGet(SYSTEM_METRICS)
}
/**
* Fetch a typed entry <i>under the application path</i>
* @param subpath
* @param clazz
* @return
*/
public <T> T jFetchType(
String subpath, Class<T> clazz) {
(T)jGetApplicationResource(subpath, clazz)
}
public ConfTreeOperations jGetConfigTree(
String path) {
ConfTree ctree = jGetApplicationResource(path, ConfTree)
ConfTreeOperations tree = new ConfTreeOperations(ctree)
return tree
}
public void testMimeTypes() throws Throwable {
describe "Mime Types"
WebResource resource = applicationResource(LIVE_RESOURCES)
def response = resource.get(ClientResponse)
response.headers.each {key, val -> log.info("$key: $val")}
log.info response.toString()
assert response.type.equals(MediaType.APPLICATION_JSON_TYPE)
}
public void testLiveResources() throws Throwable {
describe "Live Resources"
ConfTreeOperations tree = jGetConfigTree(LIVE_RESOURCES)
log.info tree.toString()
def liveAM = tree.getComponent(COMPONENT_AM)
def desiredInstances = liveAM.getMandatoryOptionInt(COMPONENT_INSTANCES);
assert desiredInstances ==
liveAM.getMandatoryOptionInt(COMPONENT_INSTANCES_ACTUAL)
assert 1 == liveAM.getMandatoryOptionInt(COMPONENT_INSTANCES_STARTED)
assert 0 == liveAM.getMandatoryOptionInt(COMPONENT_INSTANCES_REQUESTING)
assert 0 == liveAM.getMandatoryOptionInt(COMPONENT_INSTANCES_FAILED)
assert 0 == liveAM.getMandatoryOptionInt(COMPONENT_INSTANCES_COMPLETED)
assert 0 == liveAM.getMandatoryOptionInt(COMPONENT_INSTANCES_RELEASING)
}
public void testLiveContainers() throws Throwable {
describe "Application REST ${LIVE_CONTAINERS}"
Map<String, ContainerInformation> containers =
jGetApplicationResource(LIVE_CONTAINERS, HashMap)
assert containers.size() == 1
log.info "${containers}"
ContainerInformation amContainerInfo =
(ContainerInformation) containers.values()[0]
assert amContainerInfo.containerId
def amContainerId = amContainerInfo.containerId
assert containers[amContainerId]
assert amContainerInfo.component == COMPONENT_AM
assert amContainerInfo.createTime > 0
assert amContainerInfo.exitCode == null
assert amContainerInfo.output == null
assert amContainerInfo.released == null
assert amContainerInfo.state == StateValues.STATE_LIVE
describe "containers"
ContainerInformation retrievedContainerInfo =
jFetchType(
LIVE_CONTAINERS + "/${amContainerId}",
ContainerInformation
)
assert retrievedContainerInfo.containerId == amContainerId
// fetch missing
try {
def result = jFetchType(
LIVE_CONTAINERS + "/unknown",
ContainerInformation
)
fail("expected an error, got $result")
} catch (NotFoundException e) {
// expected
}
describe "components"
Map<String, ComponentInformation> components =
jFetchType(LIVE_COMPONENTS, HashMap)
// two components
assert components.size() >= 1
log.info "${components}"
ComponentInformation amFullInfo = jFetchType(
LIVE_COMPONENTS + "/${COMPONENT_AM}",
ComponentInformation)
assert amFullInfo.containers.size() == 1
assert amFullInfo.containers[0] == amContainerId
}
/**
* Assert that a path resolves to an array list that contains
* those entries (and only those entries) expected
* @param appmaster AM ref
* @param path path under AM
* @param entries entries to assert the presence of
*/
public void assertPathServesList(
String appmaster,
String path,
List<String> entries) {
def list = jFetchType(path, ArrayList)
assert list.size() == entries.size()
assert entries.containsAll(list)
}
/**
* Fetch a list of URLs, all of which must be of the same type
* @param clazz class of resolved values
* @param appmaster URL to app master
* @param subpaths list of subpaths
* @return a map of paths to values
*/
public <T> Map<String, T> fetchTypeList(
Class<T> clazz, String appmaster, List<String> subpaths
) {
Map<String, T> results = [:]
subpaths.each { String it ->
results[it] = (jFetchType(it, clazz))
}
return results;
}
/**
* Test the rest model. For this to work the cluster has to be configured
* with the global option
* @param appmaster
*/
public void testRESTModel() {
describe "model"
assertPathServesList(appmaster,
MODEL,
ApplicationResource.MODEL_ENTRIES)
def unresolvedConf = jFetchType(MODEL_DESIRED, AggregateConf)
// log.info "Unresolved \n$unresolvedConf"
def unresolvedAppConf = unresolvedConf.appConfOperations
def sam = "slider-appmaster"
assert unresolvedAppConf.getComponentOpt(sam, TEST_GLOBAL_OPTION, "") == ""
def resolvedConf = jFetchType(MODEL_RESOLVED, AggregateConf)
// log.info "Resolved \n$resolvedConf"
assert resolvedConf.appConfOperations.getComponentOpt(
sam, TEST_GLOBAL_OPTION, "") == TEST_GLOBAL_OPTION_PRESENT
def unresolved = fetchTypeList(ConfTree, appmaster,
[MODEL_DESIRED_APPCONF, MODEL_DESIRED_RESOURCES])
assert unresolved[MODEL_DESIRED_APPCONF].components[sam]
[TEST_GLOBAL_OPTION] == null
def resolved = fetchTypeList(ConfTree, appmaster,
[MODEL_RESOLVED_APPCONF, MODEL_RESOLVED_RESOURCES])
assert resolved[MODEL_RESOLVED_APPCONF].
components[sam][TEST_GLOBAL_OPTION] == TEST_GLOBAL_OPTION_PRESENT
}
public void testPing() {
// GET
describe "pinging"
def pinged = jExec(HttpVerb.GET, ACTION_PING, PingInformation)
log.info "Ping GET: $pinged"
// HEAD
// jExec(HttpVerb.HEAD, ACTION_PING, PingResource)
jExec(HttpVerb.PUT, ACTION_PING, PingInformation)
jExec(HttpVerb.DELETE, ACTION_PING, PingInformation)
jExec(HttpVerb.POST, ACTION_PING, PingInformation)
ping(HttpVerb.PUT, ACTION_PING, "ping-text")
ping(HttpVerb.POST, ACTION_PING, "ping-text")
ping(HttpVerb.DELETE, ACTION_PING, "ping-text")
}
/**
* Execute a ping; assert that a response came back with the relevant
* verb if the verb has a response body
* @param method method to invoke
* @param subpath ping path
* @param payload payload
* @return the resource if the verb has a response
*/
private PingInformation ping(HttpVerb method, String subpath, Object payload) {
def actionPing = applicationResource(ACTION_PING)
def upload = method.hasUploadBody() ? payload : null
if (method.hasResponseBody()) {
def pinged = actionPing.method(method.verb, PingInformation, upload)
assert method.verb == pinged.verb
return pinged
} else {
actionPing.method(method.verb, upload)
return null
}
}
/**
* Test the stop command.
* Important: once executed, the AM is no longer there.
* This must be the last test in the sequence.
*/
/*
public void testStop() {
String target = appendToURL(appmaster, SLIDER_PATH_APPLICATION, ACTION_STOP)
describe "Stop URL $target"
URL targetUrl = new URL(target)
def outcome = connectionOperations.execHttpOperation(
HttpVerb.POST,
targetUrl,
new byte[0],
MediaType.TEXT_PLAIN)
log.info "Stopped: $outcome"
// await the shutdown
sleep(1000)
// now a ping is expected to fail
String ping = appendToURL(appmaster, SLIDER_PATH_APPLICATION, ACTION_PING)
URL pingUrl = new URL(ping)
repeatUntilSuccess("probe for missing registry entry",
this.&probePingFailing, 30000, 500,
[url: ping],
true,
"AM failed to shut down") {
def pinged = jFetchType(ACTION_PING + "?body=hello",
PingResource
)
fail("AM didn't shut down; Ping GET= $pinged")
}
}
*/
/**
* Probe that spins until the url specified by "url") refuses
* connections
* @param args argument map
* @return the outcome
*/
/*
Outcome probePingFailing(Map args) {
String ping = args["url"]
URL pingUrl = new URL(ping)
try {
def response = pingAction(HttpVerb.HEAD, pingUrl, "should not be running")
return Outcome.Retry
} catch (IOException e) {
// expected
return Outcome.Success
}
}
*/
public void testSuiteGetOperations() {
testCodahaleOperations()
testMimeTypes()
testJerseyGetConftree()
testLiveResources()
testLiveContainers();
testRESTModel()
}
public void testSuiteComplexVerbs() {
testPing();
}
/**
* Probe callback for is the web service live; currently
* checks the "/system/health" path.
* Raising an SSL exception is considered a sign of liveness.
* @param args args: ignored
* @return the outcome
*/
Outcome probeServiceLive(Map<String, String> args) {
try {
jerseyGet(SYSTEM_HEALTHCHECK)
return Outcome.Success
} catch (SSLException e) {
// SSL exception => success
return Outcome.Success
} catch (IOException e) {
def cause = e.getCause()
if (cause && cause instanceof SSLException) {
// nested SSL exception => success
return Outcome.Success
}
// any other IOE is a retry
return Outcome.Retry
}
}
}