Skip to content

Commit

Permalink
[HWKMETRICS-199] initia support for virtual clock in REST API tests
Browse files Browse the repository at this point in the history
H-Metrics will use the virtual clock when the system property
hawkular.metrics.use-virtual-clock is set to true. It will cause the
VirtualClockHandler to be deployed. It provides endpoints for fetching,
setting, and incrementing the clock.

There is still some work to do to make tests more repeatable. Tests still have
to spin for some short delay to allow time for task execution to complete. This
of course is problematic because there is no way to know how long to block. We
need additional endpoints that provide functionality that are used in other
tests. That is, tests essentially wait for notifications from the task
scheduler that it has finished work for a time slice.
  • Loading branch information
John Sanda committed Aug 11, 2015
1 parent f9a1aa7 commit 5cd1fbf
Show file tree
Hide file tree
Showing 12 changed files with 302 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,35 @@
*/
package org.hawkular.metrics.api.jaxrs;

import java.util.HashSet;
import java.util.Set;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

import org.hawkular.metrics.api.jaxrs.exception.mappers.BadRequestExceptionMapper;
import org.hawkular.metrics.api.jaxrs.exception.mappers.NotAcceptableExceptionMapper;
import org.hawkular.metrics.api.jaxrs.exception.mappers.NotAllowedExceptionMapper;
import org.hawkular.metrics.api.jaxrs.exception.mappers.NotFoundExceptionMapper;
import org.hawkular.metrics.api.jaxrs.exception.mappers.NotSupportedExceptionMapper;
import org.hawkular.metrics.api.jaxrs.exception.mappers.ReaderExceptionMapper;
import org.hawkular.metrics.api.jaxrs.filter.CorsFilter;
import org.hawkular.metrics.api.jaxrs.filter.EmptyPayloadFilter;
import org.hawkular.metrics.api.jaxrs.filter.MetricsServiceStateFilter;
import org.hawkular.metrics.api.jaxrs.filter.TenantFilter;
import org.hawkular.metrics.api.jaxrs.handler.AvailabilityHandler;
import org.hawkular.metrics.api.jaxrs.handler.BaseHandler;
import org.hawkular.metrics.api.jaxrs.handler.CounterHandler;
import org.hawkular.metrics.api.jaxrs.handler.GaugeHandler;
import org.hawkular.metrics.api.jaxrs.handler.MetricHandler;
import org.hawkular.metrics.api.jaxrs.handler.PingHandler;
import org.hawkular.metrics.api.jaxrs.handler.StatusHandler;
import org.hawkular.metrics.api.jaxrs.handler.TenantsHandler;
import org.hawkular.metrics.api.jaxrs.handler.VirtualClockHandler;
import org.hawkular.metrics.api.jaxrs.influx.InfluxSeriesHandler;
import org.hawkular.metrics.api.jaxrs.interceptor.EmptyPayloadInterceptor;
import org.hawkular.metrics.api.jaxrs.param.ConvertersProvider;
import org.hawkular.metrics.api.jaxrs.util.JacksonConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -35,4 +61,50 @@ public HawkularMetricsRestApp() {
logger.info("Hawkular Metrics starting ..");
}

@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<>();
classes.add(MetricHandler.class);
classes.add(AvailabilityHandler.class);
classes.add(InfluxSeriesHandler.class);
classes.add(TenantsHandler.class);
classes.add(GaugeHandler.class);
classes.add(CounterHandler.class);
classes.add(StatusHandler.class);
classes.add(BaseHandler.class);
classes.add(CounterHandler.class);
classes.add(PingHandler.class);

// Initially I tried to inject this using @Configurable and @ConfigurableProperty
// but null was returned. I assume it has something to do with initialization order.
// I think it is fine with accessing the system property though since this is only
// intended for automated tests where we do pass this and other config settings as
// system properties.
boolean useVirtualClock = Boolean.valueOf(System.getProperty("hawkular.metrics.use-virtual-clock", "false"));

if (useVirtualClock) {
logger.info("Deploying {}", VirtualClockHandler.class);
classes.add(VirtualClockHandler.class);
} else {
logger.info("Virtual clock is disabled");
}

classes.add(BadRequestExceptionMapper.class);
classes.add(NotAcceptableExceptionMapper.class);
classes.add(NotAllowedExceptionMapper.class);
classes.add(NotFoundExceptionMapper.class);
classes.add(ReaderExceptionMapper.class);
classes.add(NotSupportedExceptionMapper.class);

classes.add(EmptyPayloadFilter.class);
classes.add(TenantFilter.class);
classes.add(MetricsServiceStateFilter.class);
classes.add(CorsFilter.class);

classes.add(EmptyPayloadInterceptor.class);
classes.add(ConvertersProvider.class);
classes.add(JacksonConfig.class);

return classes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,11 @@
import static org.hawkular.metrics.api.jaxrs.config.ConfigurationKey.CASSANDRA_NODES;
import static org.hawkular.metrics.api.jaxrs.config.ConfigurationKey.CASSANDRA_RESETDB;
import static org.hawkular.metrics.api.jaxrs.config.ConfigurationKey.CASSANDRA_USESSL;
import static org.hawkular.metrics.api.jaxrs.config.ConfigurationKey.TASK_SCHEDULER_TIME_UNITS;
import static org.hawkular.metrics.api.jaxrs.config.ConfigurationKey.USE_VIRTUAL_CLOCK;
import static org.hawkular.metrics.api.jaxrs.config.ConfigurationKey.WAIT_FOR_SERVICE;

import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
Expand All @@ -50,16 +49,19 @@
import org.hawkular.metrics.api.jaxrs.config.Configurable;
import org.hawkular.metrics.api.jaxrs.config.ConfigurationProperty;
import org.hawkular.metrics.api.jaxrs.util.Eager;
import org.hawkular.metrics.api.jaxrs.util.VirtualClock;
import org.hawkular.metrics.core.api.MetricsService;
import org.hawkular.metrics.core.impl.MetricsServiceImpl;
import org.hawkular.metrics.schema.SchemaManager;
import org.hawkular.metrics.tasks.api.Task2;
import org.hawkular.metrics.tasks.api.RepeatingTrigger;
import org.hawkular.metrics.tasks.api.TaskScheduler;
import org.hawkular.metrics.tasks.api.Trigger;
import org.hawkular.metrics.tasks.impl.Lease;
import org.hawkular.metrics.tasks.impl.Queries;
import org.hawkular.metrics.tasks.impl.TaskSchedulerImpl;
import org.hawkular.rx.cassandra.driver.RxSessionImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.schedulers.Schedulers;
import rx.schedulers.TestScheduler;

/**
* Bean created on startup to manage the lifecycle of the {@link MetricsService} instance shared in application scope.
Expand All @@ -85,6 +87,8 @@ public enum State {

private final ScheduledExecutorService lifecycleExecutor;

private VirtualClock virtualClock;

@Inject
@Configurable
@ConfigurationProperty(CASSANDRA_CQL_PORT)
Expand Down Expand Up @@ -112,8 +116,8 @@ public enum State {

@Inject
@Configurable
@ConfigurationProperty(TASK_SCHEDULER_TIME_UNITS)
private String timeUnits;
@ConfigurationProperty(USE_VIRTUAL_CLOCK)
private String useVirtualClock;

@Inject
@Configurable
Expand Down Expand Up @@ -192,25 +196,7 @@ private void startMetricsService() {
// probably move to the hawkular-commons repo.
initSchema();

taskScheduler = new TaskScheduler() {
@Override
public Observable<Lease> start() {
LOG.warn("Task scheduling is not yet supported");
return Observable.empty();
}

@Override
public Observable<Task2> scheduleTask(String name, String groupKey, int executionOrder,
Map<String, String> parameters, Trigger trigger) {
LOG.warn("Task scheduling is not yet supported");
return Observable.empty();
}

@Override
public void shutdown() {

}
};
initTaskScheduler();

metricsService = new MetricsServiceImpl();
metricsService.setTaskScheduler(taskScheduler);
Expand Down Expand Up @@ -275,6 +261,21 @@ private void initSchema() {
session.execute("USE " + keyspace);
}

private void initTaskScheduler() {
taskScheduler = new TaskSchedulerImpl(new RxSessionImpl(session), new Queries(session));
if (Boolean.valueOf(useVirtualClock.toLowerCase())) {
// We do not want to start the task scheduler when we are using the virtual
// clock. Instead we want to wait to start it until a client sets the virtual
// clock; otherwise, we will get a MissingBackpressureException.
TestScheduler scheduler = Schedulers.test();
virtualClock = new VirtualClock(scheduler);
RepeatingTrigger.now = scheduler::now;
((TaskSchedulerImpl) taskScheduler).setTickScheduler(scheduler);
} else {
taskScheduler.start();
}
}

/**
* @return a {@link MetricsService} instance to share in application scope
*/
Expand All @@ -284,6 +285,18 @@ public MetricsService getMetricsService() {
return metricsService;
}

@Produces
@ApplicationScoped
public VirtualClock getVirtualClock() {
return virtualClock;
}

@Produces
@ApplicationScoped
public TaskScheduler getTaskScheduler() {
return taskScheduler;
}

@PreDestroy
void destroy() {
Future stopFuture = lifecycleExecutor.submit(this::stopMetricsService);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public enum ConfigurationKey {
CASSANDRA_RESETDB("cassandra.resetdb", null, null, true),
CASSANDRA_USESSL("hawkular-metrics.cassandra-use-ssl", "false", "CASSANDRA_USESSL", false),
WAIT_FOR_SERVICE("hawkular.metrics.waitForService", null, null, true),
TASK_SCHEDULER_TIME_UNITS("hawkular.scheduler.time-units", "minutes", "SCHEDULER_TIME_UNITS", false);
USE_VIRTUAL_CLOCK("hawkular.metrics.use-virtual-clock", "false", "USE_VIRTUAL_CLOCK", false);

private final String name;
private final String env;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.hawkular.metrics.api.jaxrs.ApiError;
import org.hawkular.metrics.api.jaxrs.handler.BaseHandler;
import org.hawkular.metrics.api.jaxrs.handler.StatusHandler;
import org.hawkular.metrics.api.jaxrs.handler.VirtualClockHandler;

/**
* @author Stefan Negrea
Expand All @@ -52,7 +53,7 @@ public void filter(ContainerRequestContext requestContext) throws IOException {
String path = uriInfo.getPath();

if (path.startsWith("/tenants") || path.startsWith("/db") || path.startsWith(StatusHandler.PATH)
|| path.equals(BaseHandler.PATH)) {
|| path.equals(BaseHandler.PATH) || path.startsWith(VirtualClockHandler.PATH)) {
// Tenants, Influx and status handlers do not check the tenant header
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright 2014-2015 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.hawkular.metrics.api.jaxrs.handler;

import static javax.ws.rs.core.MediaType.APPLICATION_JSON;

import java.util.Map;

import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;

import com.google.common.collect.ImmutableMap;
import org.hawkular.metrics.api.jaxrs.param.Duration;
import org.hawkular.metrics.api.jaxrs.util.VirtualClock;
import org.hawkular.metrics.tasks.api.TaskScheduler;


/**
* @author jsanda
*/
@Path("/clock")
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
public class VirtualClockHandler {

public static final String PATH = "/clock";

@Inject
private VirtualClock virtualClock;

@Inject
private TaskScheduler taskScheduler;

private static boolean started;

@GET
public Response getTime() {
return Response.ok(ImmutableMap.<String, Object>of("now", virtualClock.now())).build();
}

@PUT
public Response setTime(Map<String, Object> params) {
Long time = (Long) params.get("time");
virtualClock.advanceTimeTo(time);
if (!started) {
taskScheduler.start();
started = true;
}
return Response.ok().build();
}

@POST
public Response incrementTime(Duration duration) {
virtualClock.advanceTimeBy(duration.getValue(), duration.getTimeUnit());
return Response.ok().build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2014-2015 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.hawkular.metrics.api.jaxrs.util;

import java.util.concurrent.TimeUnit;

import rx.schedulers.TestScheduler;

/**
* @author jsanda
*/
public class VirtualClock {

private TestScheduler scheduler;

public VirtualClock() {
}

public VirtualClock(TestScheduler scheduler) {
this.scheduler = scheduler;
}

public long now() {
return scheduler.now();
}

public void advanceTimeTo(long time) {
scheduler.advanceTimeTo(time, TimeUnit.MILLISECONDS);
}

public void advanceTimeBy(long duration, TimeUnit timeUnit) {
scheduler.advanceTimeBy(duration, timeUnit);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ public void startUp(Session session, String keyspace, boolean resetDb, boolean c

this.metricRegistry = metricRegistry;
initMetrics();

GenerateRate generateRates = new GenerateRate(this);
taskScheduler.subscribe(generateRates);
}

void loadDataRetentions() {
Expand Down

0 comments on commit 5cd1fbf

Please sign in to comment.