1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.media.audiotestharness.server; 17 18 import com.android.media.audiotestharness.proto.AudioTestHarnessGrpc; 19 import com.android.media.audiotestharness.server.utility.PortUtility; 20 21 import com.google.common.annotations.VisibleForTesting; 22 import com.google.common.base.Preconditions; 23 import com.google.inject.ConfigurationException; 24 import com.google.inject.Guice; 25 import com.google.inject.Injector; 26 27 import io.grpc.Server; 28 import io.grpc.ServerBuilder; 29 30 import java.io.IOException; 31 import java.util.concurrent.Executor; 32 import java.util.concurrent.ExecutorService; 33 import java.util.concurrent.Executors; 34 import java.util.logging.Level; 35 import java.util.logging.Logger; 36 37 /** 38 * gRPC Server to the Audio Test Harness Infrastructure. 39 * 40 * <p>This class is designed in such a way that the spin up and tear down of the gRPC server, 41 * including all dependencies are abstracted away from any users of Audio Test Harness. It is 42 * possible to use the service implementation directly and customize the deployment of the service, 43 * however, this is meant as an easy alternative for users who just want a working system and don't 44 * care how it works under the hood. 45 */ 46 public final class AudioTestHarnessGrpcServer implements AutoCloseable { 47 48 private static final int TESTING_PORT = 8080; 49 50 private static final Logger LOGGER = 51 Logger.getLogger(AudioTestHarnessGrpcServer.class.getName()); 52 53 /** Number of threads used by the {@link #mExecutor} for task execution. */ 54 private static final int THREAD_COUNT = 16; 55 56 /** The port on which the gRPC Server should be executed on. */ 57 private final int mPort; 58 59 /** 60 * {@link Executor} used for background task execution. These tasks can be the handling of gRPC 61 * calls or other background tasks in the system such as the publishing of audio sample data by 62 * a {@link com.android.media.audiotestharness.server.core.AudioCapturer}. 63 * 64 * <p>This {@link ExecutorService} utilizes a fixed-thread pool with the number of threads set 65 * by the {@link #THREAD_COUNT} constant. 66 */ 67 private final ExecutorService mExecutor; 68 69 /** {@link Injector} used for dependency management and injection. */ 70 private final Injector mInjector; 71 72 /** Underlying {@link Server} that manages gRPC calls. */ 73 private Server mServer; 74 AudioTestHarnessGrpcServer(int port, ExecutorService executor, Injector injector)75 private AudioTestHarnessGrpcServer(int port, ExecutorService executor, Injector injector) { 76 LOGGER.finest(String.format("new AudioTestHarnessGrpcServer on Port (%d)", port)); 77 78 this.mPort = port; 79 this.mExecutor = executor; 80 this.mInjector = injector; 81 } 82 83 /** 84 * Creates a new {@link AudioTestHarnessGrpcServer} with the provided port, {@link 85 * ExecutorService}, and {@link Injector}. 86 * 87 * <p>This method is primarily available for unit testing. In production use, the {@link 88 * #createOnPort(int)} or {@link #createWithDefault()} methods should be used instead. 89 * 90 * @throws NullPointerException if either the executor or injector is null. 91 */ 92 @VisibleForTesting create( int port, ExecutorService executor, Injector injector)93 static AudioTestHarnessGrpcServer create( 94 int port, ExecutorService executor, Injector injector) { 95 return new AudioTestHarnessGrpcServer( 96 port, 97 Preconditions.checkNotNull(executor, "Executor cannot be null."), 98 Preconditions.checkNotNull(injector, "Injector cannot be null.")); 99 } 100 101 /** Creates a new {@link AudioTestHarnessGrpcServer} with the provided port. */ createOnPort(int port)102 public static AudioTestHarnessGrpcServer createOnPort(int port) { 103 LOGGER.finest( 104 String.format( 105 "Using default Fixed Thread Pool Executor with %d threads and default" 106 + " Injector using %s", 107 THREAD_COUNT, AudioTestHarnessServerModule.class.getName())); 108 109 ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT); 110 Injector injector = Guice.createInjector(AudioTestHarnessServerModule.create(executor)); 111 112 return create(port, executor, injector); 113 } 114 115 /** 116 * Creates a new {@link AudioTestHarnessGrpcServer} on an available port in the dynamic port 117 * range. 118 */ createWithDefault()119 public static AudioTestHarnessGrpcServer createWithDefault() { 120 return createOnPort(PortUtility.nextAvailablePort()); 121 } 122 main(String[] args)123 public static void main(String[] args) { 124 try (AudioTestHarnessGrpcServer server = createOnPort(TESTING_PORT)) { 125 server.open(); 126 127 // Ensure that resources are cleanly stopped upon JVM shutdown. 128 Runtime.getRuntime().addShutdownHook(new Thread(server::close)); 129 130 // Wait for server execution to complete. So that CTRL + C or some other mechanism must 131 // be used to kill the server. 132 server.mServer.awaitTermination(); 133 134 } catch (Exception e) { 135 LOGGER.log(Level.SEVERE, "Unable to start the Audio Test Harness gRPC Server.", e); 136 } 137 } 138 139 /** 140 * Starts the Audio Test Harness gRPC Server listening on {@link #mPort}. 141 * 142 * @throws IOException if any errors occur while provisioning or starting the server. 143 */ open()144 public void open() throws IOException { 145 Preconditions.checkState(mServer == null, "Server has already been opened."); 146 147 ServerBuilder<?> serverBuilder = ServerBuilder.forPort(mPort).executor(mExecutor); 148 149 try { 150 serverBuilder.addService( 151 mInjector.getInstance(AudioTestHarnessGrpc.AudioTestHarnessImplBase.class)); 152 } catch (ConfigurationException ce) { 153 throw new IOException( 154 "Unable to create new Audio Test Harness gRPC Server, failed to resolve" 155 + " required dependency.", 156 ce); 157 } 158 159 mServer = serverBuilder.build(); 160 LOGGER.fine("Successfully created Audio Test Harness gRPC Server."); 161 162 mServer.start(); 163 LOGGER.info( 164 String.format( 165 "Started Audio Test Harness gRPC Server Listening on Port %d", mPort)); 166 } 167 168 /** 169 * Stops the Audio Test Harness gRPC Server immediately and closes any underlying resources 170 * being used by the server. 171 */ 172 @Override close()173 public void close() { 174 175 LOGGER.info("Stopping Audio Test Harness gRPC Server"); 176 if (mServer != null) { 177 mServer.shutdownNow(); 178 } else { 179 LOGGER.warning( 180 "mServer is null indicating that the Audio Test Harness gRPC Server was never" 181 + " started."); 182 } 183 184 mExecutor.shutdownNow(); 185 } 186 getPort()187 public int getPort() { 188 return mPort; 189 } 190 } 191