[WIFI-10736] Spin up separate ports for internal and external (#64)

Signed-off-by: Jun Woo Shin <jwoos@fb.com>
This commit is contained in:
Jun Woo Shin
2022-10-12 15:45:44 -04:00
committed by GitHub
parent e42eaa747b
commit da978611d0
9 changed files with 569 additions and 63 deletions

View File

@@ -11,7 +11,7 @@ RUN mkdir /owrrm-data
WORKDIR /usr/src/java
COPY docker-entrypoint.sh /
COPY --from=build /usr/src/java/owrrm/target/openwifi-rrm.jar /usr/local/bin/
EXPOSE 16789
EXPOSE 16789 16790
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["java", "-XX:+IdleTuningGcOnIdle", "-Xtune:virtualized", \
"-jar", "/usr/local/bin/openwifi-rrm.jar", \

View File

@@ -313,7 +313,11 @@ public class UCentralClient {
Map<String, Object> parameters
) {
return httpGet(
endpoint, service, parameters, connectTimeoutMs, socketTimeoutMs
endpoint,
service,
parameters,
connectTimeoutMs,
socketTimeoutMs
);
}
@@ -353,7 +357,11 @@ public class UCentralClient {
Object body
) {
return httpPost(
endpoint, service, body, connectTimeoutMs, socketTimeoutMs
endpoint,
service,
body,
connectTimeoutMs,
socketTimeoutMs
);
}

View File

@@ -12,7 +12,7 @@ public class WebTokenResult {
public String access_token;
public String refresh_token;
public String token_type;
public int expires_in;
public long expires_in;
public int idle_timeout;
public String username;
public long created;

View File

@@ -0,0 +1,157 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ThreadPool;
import spark.embeddedserver.jetty.JettyServerFactory;
import spark.utils.Assert;
/**
* Creates Jetty Server instances. Majority of the logic is taken from
* JettyServerFactory. The additional feature is that this class will actually
* set two connectors (original class doesn't set any connectors at all and
* leaves it up to the serivce start logic). Since we set two connectors here
* on the server, Spark uses the existing conectors instead of trying to spin
* up its own connectors. The other difference is that it uses a different
* ServerConnector constructor to avoid allocating additional threads that
* aren't necessary ({@link #makeConnector})
* @see EmbeddedJettyFactory
*/
public class CustomJettyServerFactory implements JettyServerFactory {
// normally this is set in EmbeddedJettyServer but since we create our own connectors here,
// we need the value here
private boolean trustForwardHeaders = true; // true by default
private final int internalPort;
private final int externalPort;
public CustomJettyServerFactory(int internalPort, int externalPort) {
this.internalPort = internalPort;
this.externalPort = externalPort;
}
public void setTrustForwardHeaders(boolean trustForwardHeaders) {
this.trustForwardHeaders = trustForwardHeaders;
}
/**
* This is basically
* spark.embeddedserver.jetty.SocketConnectorFactory.createSocketConnector,
* the only difference being that we use a different constructor for the
* Connector and that the private methods called are just inlined.
*/
public Connector makeConnector(
Server server,
String host,
int port,
boolean trustForwardHeaders
) {
Assert.notNull(server, "'server' must not be null");
Assert.notNull(host, "'host' must not be null");
// spark.embeddedserver.jetty.SocketConnectorFactory.createHttpConnectionFactory
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.setSecureScheme("https");
if (trustForwardHeaders) {
httpConfig.addCustomizer(new ForwardedRequestCustomizer());
}
HttpConnectionFactory httpConnectionFactory =
new HttpConnectionFactory(httpConfig);
ServerConnector connector = new ServerConnector(
server,
0, // acceptors, don't allocate separate threads for acceptor
0, // selectors, use default number
httpConnectionFactory
);
// spark.embeddedserver.jetty.SocketConnectorFactory.initializeConnector
connector.setIdleTimeout(TimeUnit.HOURS.toMillis(1));
connector.setHost(host);
connector.setPort(port);
return connector;
}
/**
* Creates a Jetty server.
*
* @param maxThreads maxThreads
* @param minThreads minThreads
* @param threadTimeoutMillis threadTimeoutMillis
* @return a new jetty server instance
*/
public Server create(
int maxThreads,
int minThreads,
int threadTimeoutMillis
) {
Server server;
if (maxThreads > 0) {
int max = maxThreads;
int min = (minThreads > 0) ? minThreads : 8;
int idleTimeout =
(threadTimeoutMillis > 0) ? threadTimeoutMillis : 60000;
server = new Server(new QueuedThreadPool(max, min, idleTimeout));
} else {
server = new Server();
}
Connector internalConnector = null;
if (internalPort != -1) {
internalConnector = makeConnector(
server,
"localhost",
internalPort,
trustForwardHeaders
);
}
Connector externalConnector = null;
if (externalPort != -1) {
externalConnector = makeConnector(
server,
"localhost",
externalPort,
trustForwardHeaders
);
}
if (internalConnector == null) {
server.setConnectors(new Connector[] { externalConnector });
} else if (externalConnector == null) {
server.setConnectors(new Connector[] { internalConnector });
} else {
server.setConnectors(
new Connector[] { internalConnector, externalConnector }
);
}
return server;
}
/**
* Creates a Jetty server with supplied thread pool
* @param threadPool thread pool
* @return a new jetty server instance
*/
@Override
public Server create(ThreadPool threadPool) {
return threadPool != null ? new Server(threadPool) : new Server();
}
}

View File

@@ -34,7 +34,7 @@ public class RRMConfig {
* Private endpoint for the RRM service
* ({@code SERVICECONFIG_PRIVATEENDPOINT})
*/
public String privateEndpoint = "http://owrrm.wlan.local:16789"; // see ApiServerParams.httpPort
public String privateEndpoint = "http://owrrm.wlan.local:16790"; // see ApiServerParams.internalHttpPort
/**
* Public endpoint for the RRM service
@@ -325,10 +325,16 @@ public class RRMConfig {
*/
public class ApiServerParams {
/**
* The HTTP port to listen on, or -1 to disable
* ({@code APISERVERPARAMS_HTTPPORT})
* The HTTP port to listen on for internal traffic, or -1 to disable
* ({@code APISERVERPARAMS_INTERNALHTTPPORT})
*/
public int httpPort = 16789;
public int internalHttpPort = 16790;
/**
* The HTTP port to listen on for external traffic, or -1 to disable
* ({@code APISERVERPARAMS_EXTERNALHTTPPORT})
*/
public int externalHttpPort = 16789;
/**
* Comma-separated list of all allowed CORS domains (exact match
@@ -543,8 +549,11 @@ public class RRMConfig {
}
ModuleConfig.ApiServerParams apiServerParams =
config.moduleConfig.apiServerParams;
if ((v = env.get("APISERVERPARAMS_HTTPPORT")) != null) {
apiServerParams.httpPort = Integer.parseInt(v);
if ((v = env.get("APISERVERPARAMS_INTERNALHTTPPORT")) != null) {
apiServerParams.internalHttpPort = Integer.parseInt(v);
}
if ((v = env.get("APISERVERPARAMS_EXTERNALHTTPPORT")) != null) {
apiServerParams.externalHttpPort = Integer.parseInt(v);
}
if ((v = env.get("APISERVERPARAMS_CORSDOMAINLIST")) != null) {
apiServerParams.corsDomainList = v;

View File

@@ -40,6 +40,7 @@ import com.facebook.openwifi.cloudsdk.models.gw.SystemInfoResults;
import com.facebook.openwifi.cloudsdk.models.gw.TokenValidationResult;
import com.facebook.openwifi.cloudsdk.models.prov.rrm.Algorithm;
import com.facebook.openwifi.cloudsdk.models.prov.rrm.Provider;
import com.facebook.openwifi.rrm.CustomJettyServerFactory;
import com.facebook.openwifi.rrm.DeviceConfig;
import com.facebook.openwifi.rrm.DeviceDataManager;
import com.facebook.openwifi.rrm.DeviceLayeredConfig;
@@ -81,7 +82,9 @@ import io.swagger.v3.oas.models.OpenAPI;
import spark.Request;
import spark.Response;
import spark.Route;
import spark.Spark;
import spark.Service;
import spark.embeddedserver.EmbeddedServers;
import spark.embeddedserver.jetty.EmbeddedJettyFactory;
/**
* HTTP API server.
@@ -110,6 +113,27 @@ public class ApiServer implements Runnable {
private static final Logger logger =
LoggerFactory.getLogger(ApiServer.class);
/**
* This is the identifier for the server factory that Spark should use. This
* particular identifier points to the custom factory that we register to
* enable running multiple ports on one service.
*
* @see #run()
*/
private static final String SPARK_EMBEDDED_SERVER_IDENTIFIER =
ApiServer.class.getName();
/**
* The Spark service instance. Normally, you would use the static methods on
* Spark, but since we need to spin up multiple instances of Spark for testing,
* we choose to go with instantiating the service ourselves. There is really no
* difference except with the static method, Spark calls ignite and holds a
* singleton instance for us.
*
* @see Spark
*/
private final Service service;
/** The module parameters. */
private final ApiServerParams params;
@@ -164,6 +188,7 @@ public class ApiServer implements Runnable {
UCentralClient client,
RRMScheduler scheduler
) {
this.service = Service.ignite();
this.params = params;
this.serviceConfig = serviceConfig;
this.serviceKey = Utils.generateServiceKey(serviceConfig);
@@ -194,64 +219,116 @@ public class ApiServer implements Runnable {
return ret;
}
/**
* Block until initialization finishes. Just calls the method on the
* underlying service.
*/
public void awaitInitialization() {
service.awaitInitialization();
}
@Override
public void run() {
this.startTimeMs = System.currentTimeMillis();
if (params.httpPort == -1) {
if (params.internalHttpPort == -1 && params.externalHttpPort == -1) {
logger.info("API server is disabled.");
return;
} else if (params.internalHttpPort == -1) {
logger.info("Internal API server is disabled");
} else if (params.externalHttpPort == -1) {
logger.info("External API server is disabled");
}
if (params.internalHttpPort == params.externalHttpPort) {
logger.error(
"Internal and external port cannot be the same - not starting API server"
);
return;
}
Spark.port(params.httpPort);
EmbeddedServers.add(
SPARK_EMBEDDED_SERVER_IDENTIFIER,
new EmbeddedJettyFactory(
new CustomJettyServerFactory(
params.internalHttpPort,
params.externalHttpPort
)
)
);
// use the embedded server factory added above, this is required so that we
// don't mess up the default factory which can and will be used for
// additional Spark services in testing
service.embeddedServerIdentifier(SPARK_EMBEDDED_SERVER_IDENTIFIER);
// Usually you would call this with an actual port and Spark would spin up a
// port on it. However, since we're putting our own connectors in so that we
// can use two ports and Spark has logic to use connectors that already exist
// so it doesn't matter what port we pass in here as long as it's not one of
// the actual ports we're using (Spark has some weird logic where it still
// tries to bind to the port).
// @see EmbeddedJettyServer
service.port(0);
// Configure API docs hosting
Spark.staticFiles.location("/public");
Spark.get("/openapi.yaml", this::getOpenApiYaml);
Spark.get("/openapi.json", this::getOpenApiJson);
service.staticFiles.location("/public");
service.get("/openapi.yaml", this::getOpenApiYaml);
service.get("/openapi.json", this::getOpenApiJson);
// Install routes
Spark.before(this::beforeFilter);
Spark.after(this::afterFilter);
Spark.options("/*", this::options);
Spark.get("/api/v1/system", new SystemEndpoint());
Spark.post("/api/v1/system", new SetSystemEndpoint());
Spark.get("/api/v1/provider", new ProviderEndpoint());
Spark.get("/api/v1/algorithms", new AlgorithmsEndpoint());
Spark.put("/api/v1/runRRM", new RunRRMEndpoint());
Spark.get("/api/v1/getTopology", new GetTopologyEndpoint());
Spark.post("/api/v1/setTopology", new SetTopologyEndpoint());
Spark.get(
service.before(this::beforeFilter);
service.after(this::afterFilter);
service.options("/*", this::options);
service.get("/api/v1/system", new SystemEndpoint());
service.post("/api/v1/system", new SetSystemEndpoint());
service.get("/api/v1/provider", new ProviderEndpoint());
service.get("/api/v1/algorithms", new AlgorithmsEndpoint());
service.put("/api/v1/runRRM", new RunRRMEndpoint());
service.get("/api/v1/getTopology", new GetTopologyEndpoint());
service.post("/api/v1/setTopology", new SetTopologyEndpoint());
service.get(
"/api/v1/getDeviceLayeredConfig",
new GetDeviceLayeredConfigEndpoint()
);
Spark.get("/api/v1/getDeviceConfig", new GetDeviceConfigEndpoint());
Spark.post(
service.get("/api/v1/getDeviceConfig", new GetDeviceConfigEndpoint());
service.post(
"/api/v1/setDeviceNetworkConfig",
new SetDeviceNetworkConfigEndpoint()
);
Spark.post(
service.post(
"/api/v1/setDeviceZoneConfig",
new SetDeviceZoneConfigEndpoint()
);
Spark.post(
service.post(
"/api/v1/setDeviceApConfig",
new SetDeviceApConfigEndpoint()
);
Spark.post(
service.post(
"/api/v1/modifyDeviceApConfig",
new ModifyDeviceApConfigEndpoint()
);
Spark.get("/api/v1/currentModel", new GetCurrentModelEndpoint());
Spark.get("/api/v1/optimizeChannel", new OptimizeChannelEndpoint());
Spark.get("/api/v1/optimizeTxPower", new OptimizeTxPowerEndpoint());
service.get("/api/v1/currentModel", new GetCurrentModelEndpoint());
service.get("/api/v1/optimizeChannel", new OptimizeChannelEndpoint());
service.get("/api/v1/optimizeTxPower", new OptimizeTxPowerEndpoint());
logger.info("API server listening on HTTP port {}", params.httpPort);
logger.info(
"API server listening for HTTP internal on port {} and external on port {}",
params.internalHttpPort,
params.externalHttpPort
);
}
/** Stop the server. */
public void shutdown() {
Spark.stop();
service.stop();
}
/**
* Block until stop finishes. Just calls the method on the underlying service.
*/
public void awaitStop() {
service.awaitStop();
}
/** Reconstructs a URL. */
@@ -269,15 +346,17 @@ public class ApiServer implements Runnable {
* HTTP 403 response and return false.
*/
private boolean performOpenWifiAuth(Request request, Response response) {
// TODO check if request came from internal endpoint
boolean internal = true;
String internalName = request.headers("X-INTERNAL-NAME");
if (internal && internalName != null) {
// Internal request, validate "X-API-KEY"
String apiKey = request.headers("X-API-KEY");
if (apiKey != null && apiKey.equals(serviceKey)) {
// auth success
return true;
int port = request.port();
boolean internal = port > 0 && port == params.internalHttpPort;
if (internal) {
String internalName = request.headers("X-INTERNAL-NAME");
if (internalName != null) {
// Internal request, validate "X-API-KEY"
String apiKey = request.headers("X-API-KEY");
if (apiKey != null && apiKey.equals(serviceKey)) {
// auth success
return true;
}
}
} else {
// External request, validate token:
@@ -297,7 +376,7 @@ public class ApiServer implements Runnable {
}
// auth failure
Spark.halt(403, "Forbidden");
service.halt(403, "Forbidden");
return false;
}
@@ -325,10 +404,11 @@ public class ApiServer implements Runnable {
private void beforeFilter(Request request, Response response) {
// Log requests
logger.debug(
"[{}] {} {}",
"[{}] {} {} on port {}",
request.ip(),
request.requestMethod(),
getFullUrl(request.pathInfo(), request.queryString())
getFullUrl(request.pathInfo(), request.queryString()),
request.port()
);
// Remove "Server: Jetty" header

View File

@@ -24,10 +24,12 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestMethodOrder;
import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent;
import com.facebook.openwifi.cloudsdk.UCentralClient;
import com.facebook.openwifi.cloudsdk.UCentralConstants;
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer;
@@ -39,18 +41,33 @@ import com.facebook.openwifi.rrm.RRMAlgorithm;
import com.facebook.openwifi.rrm.RRMConfig;
import com.facebook.openwifi.rrm.VersionProvider;
import com.facebook.openwifi.rrm.mysql.DatabaseManager;
import com.facebook.openwifi.rrm.services.MockOWSecService;
import com.facebook.openwifi.rrm.Utils;
import com.google.gson.Gson;
import kong.unirest.HttpResponse;
import kong.unirest.JsonNode;
import kong.unirest.Unirest;
import kong.unirest.json.JSONObject;
import spark.Spark;
/**
* A class for testing ApiServer. In order to test auth logic, you must tag
* the test "auth". Doing so will spin up a mock ow security service and
* enable auth on the server.
*/
@TestMethodOrder(OrderAnnotation.class)
public class ApiServerTest {
/** The test server port. */
private static final int TEST_PORT = spark.Service.SPARK_DEFAULT_PORT;
/** The test server port for external calls. */
private static final int TEST_EXTERNAL_PORT =
spark.Service.SPARK_DEFAULT_PORT;
/** The test server port for internal calls. */
private static final int TEST_INTERNAL_PORT =
TEST_EXTERNAL_PORT + 1;
/** The mock ow sec service port */
private static final int TEST_OWSEC_PORT =
TEST_INTERNAL_PORT + 1;
/** Test device data manager. */
private DeviceDataManager deviceDataManager;
@@ -61,15 +78,27 @@ public class ApiServerTest {
/** Test API server instance. */
private ApiServer server;
/** Mock OW sec service */
private MockOWSecService owSecService;
/** Test modeler instance. */
private Modeler modeler;
/** The Gson instance. */
private final Gson gson = new Gson();
/** Build an endpoint URL. */
/** Build an internal endpoint URL. */
private String endpoint(String path) {
return String.format("http://localhost:%d%s", TEST_PORT, path);
return endpoint(path, true);
}
/** Build an endpoint URL. */
private String endpoint(String path, boolean internal) {
return String.format(
"http://localhost:%d%s",
internal ? TEST_INTERNAL_PORT : TEST_EXTERNAL_PORT,
path
);
}
@BeforeEach
@@ -78,10 +107,43 @@ public class ApiServerTest {
// Create config
this.rrmConfig = new RRMConfig();
rrmConfig.moduleConfig.apiServerParams.httpPort = TEST_PORT;
rrmConfig.moduleConfig.apiServerParams.internalHttpPort =
TEST_INTERNAL_PORT;
rrmConfig.moduleConfig.apiServerParams.externalHttpPort =
TEST_EXTERNAL_PORT;
UCentralClient client = null;
boolean useAuth = testInfo.getTags().contains("auth");
if (useAuth) {
this.rrmConfig.moduleConfig.apiServerParams.useOpenWifiAuth = true;
// spin up mock owsec service
try {
owSecService = new MockOWSecService(TEST_OWSEC_PORT);
} catch (Exception e) {
fail("Could not instantiate OWSec.", e);
}
// Create uCentral client with mock services as necessary
client = new UCentralClient(
"rrm",
false,
null,
null,
null,
rrmConfig.uCentralConfig.uCentralSocketParams.connectTimeoutMs,
rrmConfig.uCentralConfig.uCentralSocketParams.socketTimeoutMs,
rrmConfig.uCentralConfig.uCentralSocketParams.wifiScanTimeoutMs
);
ServiceEvent owsecServiceEvent = new ServiceEvent();
owsecServiceEvent.type = "owsec";
owsecServiceEvent.privateEndPoint =
String.format("http://localhost:%d", owSecService.getPort());
client.setServiceEndpoint("owsec", owsecServiceEvent);
}
// Create clients (null for now)
UCentralClient client = null;
UCentralKafkaConsumer consumer = null;
DatabaseManager dbManager = null;
@@ -126,7 +188,7 @@ public class ApiServerTest {
);
try {
server.run();
Spark.awaitInitialization();
server.awaitInitialization();
} catch (Exception e) {
fail("Could not instantiate ApiServer.", e);
}
@@ -137,10 +199,16 @@ public class ApiServerTest {
// Destroy ApiServer
if (server != null) {
server.shutdown();
Spark.awaitStop();
server.awaitStop();
}
server = null;
// reset owsec server
if (owSecService != null) {
owSecService.stop();
}
owSecService = null;
// Reset Unirest client
// Without this, Unirest randomly throws:
// kong.unirest.UnirestException: java.net.SocketException: Software caused connection abort: recv failed
@@ -511,7 +579,10 @@ public class ApiServerTest {
@Order(1000)
void testDocs() throws Exception {
// Index page paths
assertEquals(200, Unirest.get(endpoint("/")).asString().getStatus());
assertEquals(
200,
Unirest.get(endpoint("/")).asString().getStatus()
);
assertEquals(
200,
Unirest.get(endpoint("/index.html")).asString().getStatus()
@@ -689,4 +760,56 @@ public class ApiServerTest {
Unirest.put(url + "?venue=asdf&algorithm=" + algorithms.get(0)).asString().getStatus()
);
}
@Test
@Tag("auth")
@Order(3000)
void test_publicEndpoint() throws Exception {
// no token
HttpResponse<String> resp =
Unirest.get(endpoint("/api/v1/getTopology", false)).asString();
assertEquals(403, resp.getStatus());
// bad token
resp = Unirest.get(endpoint("/api/v1/getTopology", false))
.header("Authorization", "Bearer some_bad_token")
.asString();
assertEquals(403, resp.getStatus());
// valid for 300 seconds (5 minutes)
String token = "this_is_a_good_token";
owSecService.addToken(token, 300);
// good token
resp = Unirest.get(endpoint("/api/v1/getTopology", false))
.header("Authorization", "Bearer " + token)
.asString();
assertEquals(200, resp.getStatus());
}
@Test
@Tag("auth")
@Order(3001)
void test_privateEndpoint() throws Exception {
// no headers
HttpResponse<String> resp =
Unirest.get(endpoint("/api/v1/getTopology", true)).asString();
assertEquals(403, resp.getStatus());
// bad headers
resp = Unirest.get(endpoint("/api/v1/getTopology", true))
.header("X-INTERNAL-NAME", "internal_name")
.header("X-API-KEY", "not_a_valid_key")
.asString();
assertEquals(403, resp.getStatus());
// good headers
resp = Unirest.get(endpoint("/api/v1/getTopology", true))
.header("X-INTERNAL-NAME", "internal_name")
.header(
"X-API-KEY",
Utils.generateServiceKey(rrmConfig.serviceConfig)
)
.asString();
assertEquals(200, resp.getStatus());
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.services;
import java.util.Map;
import java.util.HashMap;
import java.time.Instant;
import com.facebook.openwifi.cloudsdk.models.gw.TokenValidationResult;
import com.facebook.openwifi.cloudsdk.models.gw.UserInfo;
import com.facebook.openwifi.cloudsdk.models.gw.WebTokenResult;
import com.google.gson.Gson;
import spark.Service;
import spark.Request;
import spark.Response;
import spark.Route;
/**
* This is a mock OW Security service meant to be used in tests.
*
* @see <a href="https://github.com/Telecominfraproject/wlan-cloud-ucentralsec">owsec</a>
*/
public class MockOWSecService {
private class TokenInfo {
long expiry;
long created;
}
private final Gson gson = new Gson();
/** A mapping of valid tokens to their expiry time in seconds since epoch */
private Map<String, TokenInfo> validTokens;
/** The Spark service */
private Service service;
public MockOWSecService(int port) {
validTokens = new HashMap<>();
service = Service.ignite();
service.port(port);
service.get("/api/v1/validateToken", new ValidateTokenEndpoint());
service.get("/api/v1/oauth2", new ValidateTokenEndpoint());
service.get("/api/v1/systemEndpoints", new SystemEndpoint());
service.awaitInitialization();
}
public void stop() {
service.stop();
service.awaitStop();
}
public void addToken(String token, long expiresInSec) {
TokenInfo time = new TokenInfo();
time.created = Instant.now().getEpochSecond();
time.expiry = expiresInSec;
validTokens.put(token, time);
}
public void removeToken(String token) {
validTokens.remove(token);
}
public int getPort() { return service.port(); }
public class Oauth2Endpoint implements Route {
@Override
public String handle(Request request, Response response) {
response.status(501);
return "Not Implemented";
}
}
public class SystemEndpoint implements Route {
@Override
public String handle(Request request, Response response) {
response.status(501);
return "Not Implemented";
}
}
public class ValidateTokenEndpoint implements Route {
@Override
public String handle(Request request, Response response) {
String token = request.queryParams("token");
if (token == null) {
response.status(403);
return "Forbidden";
}
TokenInfo info = validTokens.get(token);
if (info == null) {
response.status(403);
return "Forbidden";
}
if (info.created + info.expiry < Instant.now().getEpochSecond()) {
response.status(403);
return "Forbidden";
}
TokenValidationResult result = new TokenValidationResult();
result.userInfo = new UserInfo();
result.tokenInfo = new WebTokenResult();
result.tokenInfo.access_token = token;
result.tokenInfo.created = info.created;
result.tokenInfo.expires_in = info.expiry;
return gson.toJson(result);
}
}
}

View File

@@ -2,7 +2,9 @@ import http from 'k6/http';
import { sleep } from 'k6';
const BASE_URL = 'http://localhost:16789/api/v1';
const INTERNAL_BASE_URL = 'http://localhost:16790/api/v1';
const EXTERNAL_BASE_URL = __ENV.SEPARATE_INTERNAL_EXTERNAL_PORTS ? 'http://localhost:16789/api/v1' : INTERNAL_BASE_URL;
export default function () {
const endpoints = [
@@ -12,13 +14,19 @@ export default function () {
'getToplogy',
'currentModel',
];
const requests = endpoints.map(endpoint => {
const internalRequests = endpoints.map(endpoint => {
return {
method: 'GET',
url: `${BASE_URL}/${endpoint}`,
url: `${INTERNAL_BASE_URL}/${endpoint}`,
};
});
const externalRequests = endpoints.map(endpoint => {
return {
method: 'GET',
url: `${EXTERNAL_BASE_URL}/${endpoint}`,
};
});
let responses = http.batch(requests);
let responses = http.batch([...internalRequests, ...externalRequests]);
sleep(0.1);
}