Skip to content

Commit

Permalink
New service /_/proxy/import/modules OKAPI-610 (#1034)
Browse files Browse the repository at this point in the history
This end-point has same permission as creation of single module: okapi.proxy.modules.post.

This service is also convenient in ProxyTest.testManyModules test.

Also perform log.warn with backtrace when Okapi reports 500.
  • Loading branch information
adamdickmeiss authored Nov 11, 2020
1 parent 45532b0 commit df00145
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 52 deletions.
2 changes: 1 addition & 1 deletion okapi-core/src/main/java/org/folio/okapi/MainVerticle.java
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ private Future<Void> checkInternalModules() {
}
logger.debug("Creating the internal Okapi module {} with interface version {}",
okapiModule, interfaceVersion);
moduleManager.create(md, true, true, true).onFailure(cause1 ->
moduleManager.createList(Arrays.asList(md), true, true, true).onFailure(cause1 ->
promise.fail(cause1) // something went badly wrong
).onSuccess(ires -> {
checkSuperTenant(okapiModule, promise);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,20 @@ public void setRequires(InterfaceDescriptor[] requires) {
this.requires = requires;
}

/**
* Set requires utililty.
* @param id interface ID
* @param version interface version
*/
@JsonIgnore
public void setRequires(String id, String version) {
InterfaceDescriptor interfaceDescriptor = new InterfaceDescriptor();
interfaceDescriptor.setId(id);
interfaceDescriptor.setVersion(version);
this.requires = new InterfaceDescriptor[1];
this.requires[0] = interfaceDescriptor;
}

/**
* Get provided interfaces.
* @return interfaces; empty list if none is defined
Expand Down Expand Up @@ -165,6 +179,21 @@ public void setProvides(InterfaceDescriptor[] provides) {
this.provides = provides;
}

/**
* Set provided interfacer utility.
* @param id interface ID
* @param version interface version
* @param entries routing entries
*/
@JsonIgnore
public void setProvidedHandler(String id, String version, RoutingEntry... entries) {
InterfaceDescriptor[] interfaceDescriptors = new InterfaceDescriptor[1];
InterfaceDescriptor interfaceDescriptor = interfaceDescriptors[0] = new InterfaceDescriptor();
interfaceDescriptor.setId(id);
interfaceDescriptor.setVersion(version);
interfaceDescriptor.setHandlers(entries);
}

public InterfaceDescriptor[] getOptional() {
return optional;
}
Expand Down
16 changes: 16 additions & 0 deletions okapi-core/src/main/java/org/folio/okapi/bean/RoutingEntry.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,22 @@ public enum ProxyType {
REQUEST_LOG
}

/**
* Constructor.
*/
public RoutingEntry() {
}

/**
* Constructor (utiliity).
* @param pathPattern pattern
* @param methods HTTP method
*/
public RoutingEntry(String pathPattern, String... methods) {
this.pathPattern = pathPattern;
this.methods = methods;
}

@JsonIgnore
private ProxyType proxyType = ProxyType.REQUEST_RESPONSE;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,13 @@ public static ModuleDescriptor moduleDescriptor(String okapiVersion) {
+ " \"permissionsRequired\" : [ \"okapi.discovery.nodes.get\" ], "
+ " \"type\" : \"internal\" "
+ " }, "
// import modules
+ " {"
+ " \"methods\" : [ \"POST\" ],"
+ " \"pathPattern\" : \"/_/proxy/import/modules\","
+ " \"permissionsRequired\" : [ \"okapi.proxy.modules.post\" ], "
+ " \"type\" : \"internal\" "
+ " },"
// Proxy service
+ " {" // proxy, modules
+ " \"methods\" : [ \"POST\" ],"
Expand Down Expand Up @@ -408,8 +415,8 @@ public static ModuleDescriptor moduleDescriptor(String okapiVersion) {
+ " \"description\" : \"Get a module\" "
+ " }, { "
+ " \"permissionName\" : \"okapi.proxy.modules.post\", "
+ " \"displayName\" : \"Okapi - declare a module\", "
+ " \"description\" : \"Declare a module\" "
+ " \"displayName\" : \"Okapi - announce a new module to the proxy\", "
+ " \"description\" : \"Announce a new module to the proxy\" "
+ " }, { "
+ " \"permissionName\" : \"okapi.proxy.modules.put\", "
+ " \"displayName\" : \"Okapi - update a module description\", "
Expand Down Expand Up @@ -851,20 +858,38 @@ private Future<String> listModulesFromInterface(ProxyContext pc, String tenantId
});
}

private Future<String> createModule(ProxyContext pc, String body) {
private Future<Void> createModules(ProxyContext pc, List<ModuleDescriptor> list) {
try {
final ModuleDescriptor md = Json.decodeValue(body, ModuleDescriptor.class);
HttpServerRequest req = pc.getCtx().request();
final boolean check = ModuleUtil.getParamBoolean(req, "check", true);
final boolean preRelease = ModuleUtil.getParamBoolean(req, "preRelease", true);
final boolean npmSnapshot = ModuleUtil.getParamBoolean(req, "npmSnapshot", true);

String validerr = md.validate(logger);
if (!validerr.isEmpty()) {
logger.info("createModule validate failed: {}", validerr);
return Future.failedFuture(new OkapiError(ErrorType.USER, validerr));
for (ModuleDescriptor md : list) {
String validerr = md.validate(logger);
if (!validerr.isEmpty()) {
logger.info("createModules validate failed: {}", validerr);
return Future.failedFuture(new OkapiError(ErrorType.USER, validerr));
}
}
return moduleManager.create(md, check, preRelease, npmSnapshot)
return moduleManager.createList(list, check, preRelease, npmSnapshot);
} catch (DecodeException ex) {
return Future.failedFuture(new OkapiError(ErrorType.USER, ex.getMessage()));
}
}

private Future<String> createModules(ProxyContext pc, String body) {
try {
final ModuleDescriptor[] modules = Json.decodeValue(body, ModuleDescriptor[].class);
return createModules(pc, Arrays.asList(modules)).map("");
} catch (DecodeException ex) {
return Future.failedFuture(new OkapiError(ErrorType.USER, ex.getMessage()));
}
}

private Future<String> createModule(ProxyContext pc, String body) {
try {
final ModuleDescriptor md = Json.decodeValue(body, ModuleDescriptor.class);
return createModules(pc, Arrays.asList(md))
.compose(res -> location(pc, md.getId(), null, Json.encodePrettily(md)));
} catch (DecodeException ex) {
return Future.failedFuture(new OkapiError(ErrorType.USER, ex.getMessage()));
Expand Down Expand Up @@ -1104,6 +1129,11 @@ public Future<String> internalService(String req, ProxyContext pc) {
// default to json replies, error code overrides to text/plain
pc.getCtx().response().putHeader("Content-Type", "application/json");
if (n >= 4 && p.startsWith("/_/proxy/")) { // need at least /_/proxy/something
// /_/proxy/import/modules
if (segments[3].equals("import") && n == 5 && segments[4].equals("modules")
&& m.equals(HttpMethod.POST)) {
return createModules(pc, req);
}
if (segments[3].equals("modules")
&& moduleManager != null) {
// /_/proxy/modules
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,22 +101,6 @@ private Future<Void> loadModules() {
});
}

/**
* Create a module.
*
* @param md module descriptor
* @param check whether to check dependencies
* @param preRelease whether to allow pre-release
* @param npmSnapshot whether to allow npm snapshot
* @return future
*/
public Future<Void> create(ModuleDescriptor md, boolean check, boolean preRelease,
boolean npmSnapshot) {
List<ModuleDescriptor> l = new LinkedList<>();
l.add(md);
return createList(l, check, preRelease, npmSnapshot);
}

/**
* Create a list of modules.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,12 @@ public void responseError(ErrorType t, Throwable cause) {
}

private void responseError(int code, Throwable cause) {
if (cause != null && cause.getMessage() != null) {
responseError(code, cause.getMessage());
} else {
responseError(code, messages.getMessage("10300"));
String msg = (cause != null && cause.getMessage() != null)
? cause.getMessage() : messages.getMessage("10300");
if (code == 500) {
logger.warn(msg, cause);
}
responseError(code, msg);
}

/**
Expand Down
44 changes: 44 additions & 0 deletions okapi-core/src/main/raml/okapi.raml
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,50 @@ types:
body:
text/plain:

/_/proxy/import/modules:
description: Proxy modules service, list import
post:
description: Announce a list of new modules to the proxy. Once successful a module
can be selected for a specific tenant. To announce only one module you may use
/_/proxy/modules.
queryParameters:
check:
description: Whether to check dependencies
type: boolean
required: false
default: true
preRelease:
description: Whether to allow pre-release modules in dependency check
type: boolean
required: false
default: true
npmSnapshot:
description: Whether to allow NPM module snapshots in dependency check
type: boolean
required: false
default: true
body:
application/json:
type: ModuleList
responses:
204:
description: OK
headers:
X-Okapi-Trace:
description: Okapi trace and timing
400:
description: Bad Request
body:
text/plain:
404:
description: Not Found
body:
text/plain:
500:
description: Server Error
body:
text/plain:

/_/proxy/modules:
description: Proxy modules service
post:
Expand Down
115 changes: 94 additions & 21 deletions okapi-core/src/test/java/org/folio/okapi/ProxyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.io.IOException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -4196,6 +4197,87 @@ private void setupBasicModule(String tenant, String moduleId, String tenantPermi
.then().statusCode(200).log().ifValidationFails();
}

@Test
public void testImportModules(TestContext context) {
RestAssuredClient c;

given()
.header("Content-Type", "application/json")
.body("{\"id\":").post("/_/proxy/import/modules")
.then().statusCode(400).body(containsString("Cannot deserialize instance"));

given()
.header("Content-Type", "application/json")
.body("[]").post("/_/proxy/import/modules?check=foo")
.then().statusCode(400).body(equalTo("Bad boolean for parameter check: foo"));

c = api.createRestAssured3();
c.given()
.header("Content-Type", "application/json")
.body("[]").post("/_/proxy/import/modules")
.then().statusCode(204);
Assert.assertTrue("raml: " + c.getLastReport().toString(),
c.getLastReport().isEmpty());

ModuleDescriptor mdA = new ModuleDescriptor();
mdA.setId("moduleA-1.0.0");
mdA.setProvidedHandler("intA", "1.0", new RoutingEntry("/a", "GET"));

ModuleDescriptor mdB = new ModuleDescriptor();
mdB.setId("moduleB-1.0.0");
mdB.setRequires("intA", "1.0");
List<ModuleDescriptor> modules = new LinkedList<>();
modules.add(mdB);
c = api.createRestAssured3();
c.given()
.header("Content-Type", "application/json")
.body(Json.encodePrettily(modules)).post("/_/proxy/import/modules")
.then().statusCode(400).body(equalTo("Missing dependency: moduleB-1.0.0 requires intA: 1.0"));
Assert.assertTrue("raml: " + c.getLastReport().toString(),
c.getLastReport().isEmpty());

// try again, but without checking .. therefore it should succeed
c = api.createRestAssured3();
c.given()
.header("Content-Type", "application/json")
.body(Json.encodePrettily(modules)).post("/_/proxy/import/modules?check=false&preRelease=false&npmSnapshot=false")
.then().statusCode(204);
Assert.assertTrue("raml: " + c.getLastReport().toString(),
c.getLastReport().isEmpty());

// remove again..
c = api.createRestAssured3();
c.given().delete("/_/proxy/modules/" + mdB.getId()).then().statusCode(204);
Assert.assertTrue("raml: " + c.getLastReport().toString(),
c.getLastReport().isEmpty());

modules.add(mdA);
c = api.createRestAssured3();
c.given()
.header("Content-Type", "application/json")
.body(Json.encodePrettily(modules)).post("/_/proxy/import/modules")
.then().statusCode(400);
Assert.assertTrue("raml: " + c.getLastReport().toString(),
c.getLastReport().isEmpty());

ModuleDescriptor mdC = new ModuleDescriptor();
mdC.setId("moduleC-1.0.0");
{
InterfaceDescriptor[] interfaceDescriptors = new InterfaceDescriptor[1];
InterfaceDescriptor interfaceDescriptor = interfaceDescriptors[0] = new InterfaceDescriptor();
interfaceDescriptor.setId("intA");
// note no version set
mdC.setRequires(interfaceDescriptors);
}
modules = new LinkedList<>();
modules.add(mdC);

given()
.header("Content-Type", "application/json")
.body(Json.encodePrettily(modules)).post("/_/proxy/import/modules")
.then().statusCode(400).body(equalTo("version is missing for module moduleC-1.0.0"));
}

@Test
public void testManyModules(TestContext context) throws IOException {
given()
Expand Down Expand Up @@ -4235,28 +4317,19 @@ public void testManyModules(TestContext context) throws IOException {
}
}

// we don't have a multi post for modules ... so we just try .. in some order
int pos = 0;
while (pos < modulesList.size()) {
JsonObject md = modulesList.getJsonObject(pos);
Response response = given()
.body(md.encode()).post("/_/proxy/modules")
.then().extract().response();
if (response.getStatusCode() == 201) {
JsonObject deployObject = new JsonObject()
.put("instId", "localhost-" + md.getString("id"))
.put("srvcId", md.getString("id"))
.put("url", "http://localhost:" + Integer.toString(portTimer));
given().body(deployObject.encode()).post("/_/discovery/modules")
.then().statusCode(201);
modulesList.remove(pos);
pos = 0;
} else {
context.assertEquals(400, response.getStatusCode());
pos++;
}
given()
.body(modulesJson).post("/_/proxy/import/modules")
.then().statusCode(204);
for (int i = 0; i < modulesList.size(); i++) {
JsonObject md = modulesList.getJsonObject(i);
JsonObject deployObject = new JsonObject()
.put("instId", "localhost-" + md.getString("id"))
.put("srvcId", md.getString("id"))
.put("url", "http://localhost:" + Integer.toString(portTimer));
given().body(deployObject.encode()).post("/_/discovery/modules")
.then().statusCode(201).log().ifValidationFails();
}
context.assertEquals(0, pos);

given()
.body(installJson).post("/_/proxy/tenants/testlib/install?invoke=true")
.then().statusCode(200);
Expand Down

0 comments on commit df00145

Please sign in to comment.