mirror of https://github.com/grpc/grpc-java.git
xds: add OrcaServiceImpl (#8993)
This commit is contained in:
parent
6554061076
commit
72ae95792c
|
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 The gRPC Authors
|
||||||
|
*
|
||||||
|
* 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 io.grpc.xds;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import io.grpc.BindableService;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the service/APIs for Out-of-Band metrics reporting, only for utilization metrics.
|
||||||
|
* Register the returned service {@link #getService()} to the server, then a client can request
|
||||||
|
* for periodic load reports. A user should use the public set-APIs to update the server machine's
|
||||||
|
* utilization metrics data.
|
||||||
|
*/
|
||||||
|
@io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/9006")
|
||||||
|
public final class OrcaOobService {
|
||||||
|
/**
|
||||||
|
* Empty or invalid (non-positive) minInterval config in will be treated to this default value.
|
||||||
|
*/
|
||||||
|
public static final long DEFAULT_MIN_REPORT_INTERVAL_NANOS = TimeUnit.SECONDS.toNanos(30);
|
||||||
|
|
||||||
|
private final OrcaServiceImpl orcaService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an OOB metrics reporting service.
|
||||||
|
*
|
||||||
|
* @param minInterval configures the minimum metrics reporting interval for the service. Bad
|
||||||
|
* configuration (non-positive) will be overridden to service default (30s).
|
||||||
|
* Minimum metrics reporting interval means, if the setting in the client's
|
||||||
|
* request is invalid (non-positive) or below this value, they will be treated
|
||||||
|
* as this value.
|
||||||
|
*/
|
||||||
|
public OrcaOobService(long minInterval, TimeUnit timeUnit,
|
||||||
|
ScheduledExecutorService timeService) {
|
||||||
|
this.orcaService = new OrcaServiceImpl(minInterval > 0 ? timeUnit.toNanos(minInterval)
|
||||||
|
: DEFAULT_MIN_REPORT_INTERVAL_NANOS, checkNotNull(timeService));
|
||||||
|
}
|
||||||
|
|
||||||
|
public OrcaOobService(ScheduledExecutorService timeService) {
|
||||||
|
this(DEFAULT_MIN_REPORT_INTERVAL_NANOS, TimeUnit.NANOSECONDS, timeService);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the service instance to be bound to the server for ORCA OOB functionality.
|
||||||
|
*/
|
||||||
|
public BindableService getService() {
|
||||||
|
return orcaService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
int getClientsCount() {
|
||||||
|
return orcaService.clientCount.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the metrics value corresponding to the specified key.
|
||||||
|
*/
|
||||||
|
public void setUtilizationMetric(String key, double value) {
|
||||||
|
orcaService.setUtilizationMetric(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the whole metrics data using the specified map.
|
||||||
|
*/
|
||||||
|
public void setAllUtilizationMetrics(Map<String, Double> metrics) {
|
||||||
|
orcaService.setAllUtilizationMetrics(metrics);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the metrics data entry corresponding to the specified key.
|
||||||
|
*/
|
||||||
|
public void deleteUtilizationMetric(String key) {
|
||||||
|
orcaService.deleteUtilizationMetric(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the CPU utilization metrics data.
|
||||||
|
*/
|
||||||
|
public void setCpuUtilizationMetric(double value) {
|
||||||
|
orcaService.setCpuUtilizationMetric(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the CPU utilization metrics data.
|
||||||
|
*/
|
||||||
|
public void deleteCpuUtilizationMetric() {
|
||||||
|
orcaService.deleteCpuUtilizationMetric();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the memory utilization metrics data.
|
||||||
|
*/
|
||||||
|
public void setMemoryUtilizationMetric(double value) {
|
||||||
|
orcaService.setMemoryUtilizationMetric(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the memory utilization metrics data.
|
||||||
|
*/
|
||||||
|
public void deleteMemoryUtilizationMetric() {
|
||||||
|
orcaService.deleteMemoryUtilizationMetric();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 The gRPC Authors
|
||||||
|
*
|
||||||
|
* 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 io.grpc.xds;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import com.github.xds.data.orca.v3.OrcaLoadReport;
|
||||||
|
import com.github.xds.service.orca.v3.OpenRcaServiceGrpc;
|
||||||
|
import com.github.xds.service.orca.v3.OrcaLoadReportRequest;
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.protobuf.util.Durations;
|
||||||
|
import io.grpc.SynchronizationContext;
|
||||||
|
import io.grpc.stub.ServerCallStreamObserver;
|
||||||
|
import io.grpc.stub.StreamObserver;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
final class OrcaServiceImpl extends OpenRcaServiceGrpc.OpenRcaServiceImplBase {
|
||||||
|
private static final Logger logger = Logger.getLogger(OrcaServiceImpl.class.getName());
|
||||||
|
|
||||||
|
private final long minReportIntervalNanos;
|
||||||
|
private final ScheduledExecutorService timeService;
|
||||||
|
private volatile ConcurrentHashMap<String, Double> metricsData = new ConcurrentHashMap<>();
|
||||||
|
private volatile double cpuUtilization;
|
||||||
|
private volatile double memoryUtilization;
|
||||||
|
@VisibleForTesting
|
||||||
|
final AtomicInteger clientCount = new AtomicInteger(0);
|
||||||
|
|
||||||
|
public OrcaServiceImpl(long minReportIntervalNanos, ScheduledExecutorService timeService) {
|
||||||
|
this.minReportIntervalNanos = minReportIntervalNanos;
|
||||||
|
this.timeService = checkNotNull(timeService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void streamCoreMetrics(
|
||||||
|
OrcaLoadReportRequest request, StreamObserver<OrcaLoadReport> responseObserver) {
|
||||||
|
OrcaClient client = new OrcaClient(request, responseObserver);
|
||||||
|
client.run();
|
||||||
|
clientCount.getAndIncrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class OrcaClient implements Runnable {
|
||||||
|
final OrcaLoadReportRequest request;
|
||||||
|
final ServerCallStreamObserver<OrcaLoadReport> responseObserver;
|
||||||
|
SynchronizationContext.ScheduledHandle periodicReportTimer;
|
||||||
|
final long reportIntervalNanos;
|
||||||
|
final SynchronizationContext syncContext = new SynchronizationContext(
|
||||||
|
new Thread.UncaughtExceptionHandler() {
|
||||||
|
@Override
|
||||||
|
public void uncaughtException(Thread t, Throwable e) {
|
||||||
|
logger.log(Level.SEVERE, "Exception!" + e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
OrcaClient(OrcaLoadReportRequest request, StreamObserver<OrcaLoadReport> responseObserver) {
|
||||||
|
this.request = checkNotNull(request);
|
||||||
|
this.reportIntervalNanos = Math.max(Durations.toNanos(request.getReportInterval()),
|
||||||
|
minReportIntervalNanos);
|
||||||
|
this.responseObserver = (ServerCallStreamObserver<OrcaLoadReport>) responseObserver;
|
||||||
|
this.responseObserver.setOnCancelHandler(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
syncContext.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (periodicReportTimer != null) {
|
||||||
|
periodicReportTimer.cancel();
|
||||||
|
}
|
||||||
|
clientCount.getAndDecrement();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (periodicReportTimer != null && periodicReportTimer.isPending()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
OrcaLoadReport report = generateMetricsReport();
|
||||||
|
responseObserver.onNext(report);
|
||||||
|
periodicReportTimer = syncContext.schedule(OrcaClient.this, reportIntervalNanos,
|
||||||
|
TimeUnit.NANOSECONDS, timeService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrcaLoadReport generateMetricsReport() {
|
||||||
|
return OrcaLoadReport.newBuilder().setCpuUtilization(cpuUtilization)
|
||||||
|
.setMemUtilization(memoryUtilization)
|
||||||
|
.putAllUtilization(metricsData)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setUtilizationMetric(String key, double value) {
|
||||||
|
metricsData.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAllUtilizationMetrics(Map<String, Double> metrics) {
|
||||||
|
metricsData = new ConcurrentHashMap<>(metrics);
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteUtilizationMetric(String key) {
|
||||||
|
metricsData.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCpuUtilizationMetric(double value) {
|
||||||
|
cpuUtilization = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteCpuUtilizationMetric() {
|
||||||
|
cpuUtilization = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMemoryUtilizationMetric(double value) {
|
||||||
|
memoryUtilization = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteMemoryUtilizationMetric() {
|
||||||
|
memoryUtilization = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,297 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 The gRPC Authors
|
||||||
|
*
|
||||||
|
* 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 io.grpc.xds;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.reset;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
|
|
||||||
|
import com.github.xds.data.orca.v3.OrcaLoadReport;
|
||||||
|
import com.github.xds.service.orca.v3.OpenRcaServiceGrpc;
|
||||||
|
import com.github.xds.service.orca.v3.OrcaLoadReportRequest;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.protobuf.Duration;
|
||||||
|
import io.grpc.BindableService;
|
||||||
|
import io.grpc.CallOptions;
|
||||||
|
import io.grpc.ClientCall;
|
||||||
|
import io.grpc.ManagedChannel;
|
||||||
|
import io.grpc.Metadata;
|
||||||
|
import io.grpc.Server;
|
||||||
|
import io.grpc.Status;
|
||||||
|
import io.grpc.inprocess.InProcessChannelBuilder;
|
||||||
|
import io.grpc.inprocess.InProcessServerBuilder;
|
||||||
|
import io.grpc.internal.FakeClock;
|
||||||
|
import io.grpc.testing.GrpcCleanupRule;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.CyclicBarrier;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class OrcaServiceImplTest {
|
||||||
|
@Rule
|
||||||
|
public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
|
||||||
|
private ManagedChannel channel;
|
||||||
|
private Server oobServer;
|
||||||
|
private final FakeClock fakeClock = new FakeClock();
|
||||||
|
private OrcaOobService defaultTestService;
|
||||||
|
private final Random random = new Random();
|
||||||
|
@Mock
|
||||||
|
ClientCall.Listener<OrcaLoadReport> listener;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() throws Exception {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
defaultTestService = new OrcaOobService(1, TimeUnit.SECONDS,
|
||||||
|
fakeClock.getScheduledExecutorService());
|
||||||
|
startServerAndGetChannel(defaultTestService.getService());
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void teardown() throws Exception {
|
||||||
|
channel.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startServerAndGetChannel(BindableService orcaService) throws Exception {
|
||||||
|
oobServer = grpcCleanup.register(
|
||||||
|
InProcessServerBuilder.forName("orca-service-test")
|
||||||
|
.addService(orcaService)
|
||||||
|
.directExecutor()
|
||||||
|
.build()
|
||||||
|
.start());
|
||||||
|
channel = grpcCleanup.register(
|
||||||
|
InProcessChannelBuilder.forName("orca-service-test")
|
||||||
|
.directExecutor().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReportingLifeCycle() {
|
||||||
|
defaultTestService.setCpuUtilizationMetric(0.1);
|
||||||
|
Iterator<OrcaLoadReport> reports = OpenRcaServiceGrpc.newBlockingStub(channel)
|
||||||
|
.streamCoreMetrics(OrcaLoadReportRequest.newBuilder().build());
|
||||||
|
assertThat(reports.next()).isEqualTo(
|
||||||
|
OrcaLoadReport.newBuilder().setCpuUtilization(0.1).build());
|
||||||
|
assertThat(defaultTestService.getClientsCount()).isEqualTo(1);
|
||||||
|
assertThat(fakeClock.getPendingTasks().size()).isEqualTo(1);
|
||||||
|
assertThat(fakeClock.forwardTime(1, TimeUnit.SECONDS)).isEqualTo(1);
|
||||||
|
assertThat(reports.next()).isEqualTo(
|
||||||
|
OrcaLoadReport.newBuilder().setCpuUtilization(0.1).build());
|
||||||
|
assertThat(fakeClock.getPendingTasks().size()).isEqualTo(1);
|
||||||
|
channel.shutdownNow();
|
||||||
|
assertThat(defaultTestService.getClientsCount()).isEqualTo(0);
|
||||||
|
assertThat(fakeClock.getPendingTasks().size()).isEqualTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void testReportingLifeCycle_serverShutdown() {
|
||||||
|
ClientCall<OrcaLoadReportRequest, OrcaLoadReport> call = channel.newCall(
|
||||||
|
OpenRcaServiceGrpc.getStreamCoreMetricsMethod(), CallOptions.DEFAULT);
|
||||||
|
defaultTestService.setUtilizationMetric("buffer", 0.2);
|
||||||
|
call.start(listener, new Metadata());
|
||||||
|
call.sendMessage(OrcaLoadReportRequest.newBuilder()
|
||||||
|
.setReportInterval(Duration.newBuilder().setSeconds(0).setNanos(500).build()).build());
|
||||||
|
call.halfClose();
|
||||||
|
call.request(1);
|
||||||
|
OrcaLoadReport expect = OrcaLoadReport.newBuilder().putUtilization("buffer", 0.2).build();
|
||||||
|
assertThat(defaultTestService.getClientsCount()).isEqualTo(1);
|
||||||
|
verify(listener).onMessage(eq(expect));
|
||||||
|
reset(listener);
|
||||||
|
oobServer.shutdownNow();
|
||||||
|
assertThat(fakeClock.forwardTime(1, TimeUnit.SECONDS)).isEqualTo(0);
|
||||||
|
assertThat(defaultTestService.getClientsCount()).isEqualTo(0);
|
||||||
|
ArgumentCaptor<Status> callCloseCaptor = ArgumentCaptor.forClass(null);
|
||||||
|
verify(listener).onClose(callCloseCaptor.capture(), any());
|
||||||
|
assertThat(callCloseCaptor.getValue().getCode()).isEqualTo(Status.Code.UNAVAILABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void testRequestIntervalLess() {
|
||||||
|
ClientCall<OrcaLoadReportRequest, OrcaLoadReport> call = channel.newCall(
|
||||||
|
OpenRcaServiceGrpc.getStreamCoreMetricsMethod(), CallOptions.DEFAULT);
|
||||||
|
defaultTestService.setUtilizationMetric("buffer", 0.2);
|
||||||
|
call.start(listener, new Metadata());
|
||||||
|
call.sendMessage(OrcaLoadReportRequest.newBuilder()
|
||||||
|
.setReportInterval(Duration.newBuilder().setSeconds(0).setNanos(500).build()).build());
|
||||||
|
call.halfClose();
|
||||||
|
call.request(1);
|
||||||
|
OrcaLoadReport expect = OrcaLoadReport.newBuilder().putUtilization("buffer", 0.2).build();
|
||||||
|
verify(listener).onMessage(eq(expect));
|
||||||
|
reset(listener);
|
||||||
|
defaultTestService.deleteUtilizationMetric("buffer0");
|
||||||
|
assertThat(fakeClock.forwardTime(500, TimeUnit.NANOSECONDS)).isEqualTo(0);
|
||||||
|
verifyNoInteractions(listener);
|
||||||
|
assertThat(fakeClock.forwardTime(1, TimeUnit.SECONDS)).isEqualTo(1);
|
||||||
|
call.request(1);
|
||||||
|
verify(listener).onMessage(eq(expect));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void testRequestIntervalGreater() {
|
||||||
|
ClientCall<OrcaLoadReportRequest, OrcaLoadReport> call = channel.newCall(
|
||||||
|
OpenRcaServiceGrpc.getStreamCoreMetricsMethod(), CallOptions.DEFAULT);
|
||||||
|
defaultTestService.setUtilizationMetric("buffer", 0.2);
|
||||||
|
call.start(listener, new Metadata());
|
||||||
|
call.sendMessage(OrcaLoadReportRequest.newBuilder()
|
||||||
|
.setReportInterval(Duration.newBuilder().setSeconds(10).build()).build());
|
||||||
|
call.halfClose();
|
||||||
|
call.request(1);
|
||||||
|
OrcaLoadReport expect = OrcaLoadReport.newBuilder().putUtilization("buffer", 0.2).build();
|
||||||
|
verify(listener).onMessage(eq(expect));
|
||||||
|
reset(listener);
|
||||||
|
defaultTestService.deleteUtilizationMetric("buffer0");
|
||||||
|
assertThat(fakeClock.forwardTime(1, TimeUnit.SECONDS)).isEqualTo(0);
|
||||||
|
verifyNoInteractions(listener);
|
||||||
|
assertThat(fakeClock.forwardTime(9, TimeUnit.SECONDS)).isEqualTo(1);
|
||||||
|
call.request(1);
|
||||||
|
verify(listener).onMessage(eq(expect));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void testRequestIntervalDefault() throws Exception {
|
||||||
|
defaultTestService = new OrcaOobService(fakeClock.getScheduledExecutorService());
|
||||||
|
oobServer.shutdownNow();
|
||||||
|
startServerAndGetChannel(defaultTestService.getService());
|
||||||
|
ClientCall<OrcaLoadReportRequest, OrcaLoadReport> call = channel.newCall(
|
||||||
|
OpenRcaServiceGrpc.getStreamCoreMetricsMethod(), CallOptions.DEFAULT);
|
||||||
|
defaultTestService.setUtilizationMetric("buffer", 0.2);
|
||||||
|
call.start(listener, new Metadata());
|
||||||
|
call.sendMessage(OrcaLoadReportRequest.newBuilder()
|
||||||
|
.setReportInterval(Duration.newBuilder().setSeconds(10).build()).build());
|
||||||
|
call.halfClose();
|
||||||
|
call.request(1);
|
||||||
|
OrcaLoadReport expect = OrcaLoadReport.newBuilder().putUtilization("buffer", 0.2).build();
|
||||||
|
verify(listener).onMessage(eq(expect));
|
||||||
|
reset(listener);
|
||||||
|
defaultTestService.deleteUtilizationMetric("buffer0");
|
||||||
|
assertThat(fakeClock.forwardTime(10, TimeUnit.SECONDS)).isEqualTo(0);
|
||||||
|
verifyNoInteractions(listener);
|
||||||
|
assertThat(fakeClock.forwardTime(20, TimeUnit.SECONDS)).isEqualTo(1);
|
||||||
|
call.request(1);
|
||||||
|
verify(listener).onMessage(eq(expect));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipleClients() {
|
||||||
|
ClientCall<OrcaLoadReportRequest, OrcaLoadReport> call = channel.newCall(
|
||||||
|
OpenRcaServiceGrpc.getStreamCoreMetricsMethod(), CallOptions.DEFAULT);
|
||||||
|
defaultTestService.setUtilizationMetric("omg", 100);
|
||||||
|
call.start(listener, new Metadata());
|
||||||
|
call.sendMessage(OrcaLoadReportRequest.newBuilder().build());
|
||||||
|
call.halfClose();
|
||||||
|
call.request(1);
|
||||||
|
OrcaLoadReport expect = OrcaLoadReport.newBuilder().putUtilization("omg", 100).build();
|
||||||
|
verify(listener).onMessage(eq(expect));
|
||||||
|
defaultTestService.setMemoryUtilizationMetric(0.5);
|
||||||
|
ClientCall<OrcaLoadReportRequest, OrcaLoadReport> call2 = channel.newCall(
|
||||||
|
OpenRcaServiceGrpc.getStreamCoreMetricsMethod(), CallOptions.DEFAULT);
|
||||||
|
call2.start(listener, new Metadata());
|
||||||
|
call2.sendMessage(OrcaLoadReportRequest.newBuilder().build());
|
||||||
|
call2.halfClose();
|
||||||
|
call2.request(1);
|
||||||
|
expect = OrcaLoadReport.newBuilder(expect).setMemUtilization(0.5).build();
|
||||||
|
verify(listener).onMessage(eq(expect));
|
||||||
|
assertThat(defaultTestService.getClientsCount()).isEqualTo(2);
|
||||||
|
assertThat(fakeClock.getPendingTasks().size()).isEqualTo(2);
|
||||||
|
channel.shutdownNow();
|
||||||
|
assertThat(fakeClock.forwardTime(1, TimeUnit.SECONDS)).isEqualTo(0);
|
||||||
|
assertThat(defaultTestService.getClientsCount()).isEqualTo(0);
|
||||||
|
ArgumentCaptor<Status> callCloseCaptor = ArgumentCaptor.forClass(null);
|
||||||
|
verify(listener, times(2)).onClose(callCloseCaptor.capture(), any());
|
||||||
|
assertThat(callCloseCaptor.getValue().getCode()).isEqualTo(Status.Code.UNAVAILABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testApis() throws Exception {
|
||||||
|
Map<String, Double> firstUtilization = ImmutableMap.of("util", 0.1);
|
||||||
|
OrcaLoadReport goldenReport = OrcaLoadReport.newBuilder()
|
||||||
|
.setCpuUtilization(random.nextDouble())
|
||||||
|
.setMemUtilization(random.nextDouble())
|
||||||
|
.putAllUtilization(firstUtilization)
|
||||||
|
.putUtilization("queue", 1.0)
|
||||||
|
.build();
|
||||||
|
defaultTestService.setCpuUtilizationMetric(goldenReport.getCpuUtilization());
|
||||||
|
defaultTestService.setMemoryUtilizationMetric(goldenReport.getMemUtilization());
|
||||||
|
defaultTestService.setAllUtilizationMetrics(firstUtilization);
|
||||||
|
defaultTestService.setUtilizationMetric("queue", 1.0);
|
||||||
|
Iterator<OrcaLoadReport> reports = OpenRcaServiceGrpc.newBlockingStub(channel)
|
||||||
|
.streamCoreMetrics(OrcaLoadReportRequest.newBuilder().build());
|
||||||
|
assertThat(reports.next()).isEqualTo(goldenReport);
|
||||||
|
|
||||||
|
defaultTestService.deleteCpuUtilizationMetric();
|
||||||
|
defaultTestService.deleteMemoryUtilizationMetric();
|
||||||
|
fakeClock.forwardTime(1, TimeUnit.SECONDS);
|
||||||
|
goldenReport = OrcaLoadReport.newBuilder()
|
||||||
|
.putAllUtilization(firstUtilization)
|
||||||
|
.putUtilization("queue", 1.0)
|
||||||
|
.putUtilization("util", 0.1)
|
||||||
|
.build();
|
||||||
|
assertThat(reports.next()).isEqualTo(goldenReport);
|
||||||
|
defaultTestService.deleteUtilizationMetric("util-not-exist");
|
||||||
|
defaultTestService.deleteUtilizationMetric("queue-not-exist");
|
||||||
|
fakeClock.forwardTime(1, TimeUnit.SECONDS);
|
||||||
|
assertThat(reports.next()).isEqualTo(goldenReport);
|
||||||
|
|
||||||
|
CyclicBarrier barrier = new CyclicBarrier(2);
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
barrier.await();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new AssertionError(ex);
|
||||||
|
}
|
||||||
|
defaultTestService.deleteUtilizationMetric("util");
|
||||||
|
defaultTestService.setMemoryUtilizationMetric(0.4);
|
||||||
|
defaultTestService.setAllUtilizationMetrics(firstUtilization);
|
||||||
|
try {
|
||||||
|
barrier.await();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new AssertionError(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
barrier.await();
|
||||||
|
defaultTestService.setMemoryUtilizationMetric(0.4);
|
||||||
|
defaultTestService.deleteUtilizationMetric("util");
|
||||||
|
defaultTestService.setAllUtilizationMetrics(firstUtilization);
|
||||||
|
barrier.await();
|
||||||
|
goldenReport = OrcaLoadReport.newBuilder()
|
||||||
|
.putAllUtilization(firstUtilization)
|
||||||
|
.setMemUtilization(0.4)
|
||||||
|
.build();
|
||||||
|
fakeClock.forwardTime(1, TimeUnit.SECONDS);
|
||||||
|
assertThat(reports.next()).isEqualTo(goldenReport);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue