Skip to content

Commit

Permalink
OLMIS-982: Nested locations/endpoints in service discovery.
Browse files Browse the repository at this point in the history
Pawel Nawrocki committed Dec 6, 2016

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 06c45f2 commit 763576d
Showing 18 changed files with 2,156 additions and 1,768 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*~
*.iml
*.ipr
*.iws
10 changes: 9 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -2,6 +2,14 @@ FROM anapsix/alpine-java:jre8

COPY build/libs/*.jar /service.jar
COPY build/demo-data /demo-data
COPY build/consul /consul
COPY run.sh /run.sh

RUN chmod +x run.sh \
&& apk update \
&& apk add nodejs \
&& mv consul/package.json package.json \
&& npm install

EXPOSE 8080
CMD java $JAVA_OPTS -jar /service.jar
CMD ./run.sh
17 changes: 11 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -187,12 +187,6 @@ sonarqube {
}
}

build {
dependsOn check
dependsOn jacocoTestReport
dependsOn demoDataSeed
}

pmd {
toolVersion = '5.4.0'
consoleOutput= true
@@ -221,9 +215,20 @@ processResources {
}
}

apply from: "registration.gradle"

build {
dependsOn check
dependsOn jacocoTestReport
dependsOn demoDataSeed
}

check {
dependsOn ramlToHtml
dependsOn ramlToSwagger
dependsOn copyRamlHtmlToBuild
dependsOn integrationTest
dependsOn copyConsulRegistrationToBuild
}

bootRun.dependsOn consulRegistration
11 changes: 11 additions & 0 deletions consul/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "referencedata",
"raml": "src/main/resources/api-definition.yaml",
"path": [
"referencedata",
"referencedata/docs",
"referencedata/docs/<all>",
"referencedata/webjars",
"referencedata/webjars/<all>"
]
}
13 changes: 13 additions & 0 deletions consul/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "openlmis-consul-registration",
"version": "0.0.1",

"devDependencies":
{
"uuid": "~3.0.0",
"raml-parser": "~0.8.0",
"command-line-args": "~3.0.0",
"deasync": "~0.1.9",
"ip": "~1.1.0"
}
}
303 changes: 303 additions & 0 deletions consul/registration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
const fs = require('fs');
const ip = require('ip');
const http = require('http');
const uuid = require('uuid');
const deasync = require('deasync');
const raml = require('raml-parser');
const commandLineArgs = require('command-line-args');

function ServiceRAMLParser() {
var self = this;

self.extractResources = function(filename) {
// Parse RAML file to retrieve available resources list
return raml.loadFile(filename).then(function(data) {
var resources = [];
data.resources.forEach(function(node) {
self.extractNodeResources(node).forEach(function(resource) {
resources.push(resource);
})
});
return resources;
});
}

self.extractNodeResources = function(node) {
var paths = [];

if (node.resources) {
node.resources.forEach(function(childNode) {
self.extractNodeResources(childNode).forEach(function(path) {
paths.push(node.relativeUri + path);
});
});
}

if (node.methods instanceof Array && node.methods.length > 0){
paths.push(node.relativeUri);
}

return paths;
}
}
function ServiceConsulRegistrator(host, port) {
self = this;
self.host = host;
self.port = port;

self.registerService = function(serviceData) {
return registerServiceBase(serviceData, 'PUT');
}

self.deregisterService = function(serviceData) {
return registerServiceBase(serviceData, 'DELETE');
}

self.registerResources = function(serviceData, resourceArray) {
return registerResourcesBase(serviceData, resourceArray, 'PUT');
}

self.deregisterResources = function(serviceData, resourceArray) {
return registerResourcesBase(serviceData, resourceArray, 'DELETE');
}

function registerServiceBase(serviceData, mode) {
var requestPath = mode === 'PUT' ? 'register' : 'deregister/' + serviceData.ID;
var request = http.request({
host: self.host,
port: self.port,
path: '/v1/agent/service/' + requestPath,
method: 'PUT'
});

request.write(JSON.stringify(serviceData));
request.end();

return request;
}

function registerResourcesBase(serviceData, resourceArray, mode) {
var requests = [];

// Customize each request parameters
resourceArray.forEach(function(resource) {
var path = '/v1/kv/resources' + resource;
var request = http.request({
host: self.host,
port: self.port,
path: path,
method: mode
});

requests.push(request);
});

// Start sending requests
requests.forEach(function(request) {
request.write(serviceData.Name);
request.end();
});

return requests;
}
}
function RegistrationService(host, port) {
var self = this;

self.consulHost = host;
self.consulPort = port;
self.filename = '.consul_service_id~';

self.registrator = new ServiceConsulRegistrator(self.consulHost, self.consulPort);;
self.parser = new ServiceRAMLParser();

self.register = function(args) {
registration.registerService(args.name);

if (args.raml) {
registration.registerRaml(args.name, args.raml);
}

if (args.path) {
registration.registerPath(args.name, args.path);
}
}

self.registerService = function(serviceName) {
return serviceRegistrationBase(serviceName, 'register');
}

self.registerRaml = function(serviceName, filename) {
return ramlRegistrationBase(serviceName, filename, 'register');
}

self.registerPath = function(serviceName, paths) {
return pathRegistrationBase(serviceName, paths, 'register');
}

self.deregister = function(args) {
registration.deregisterService(args.name);

if (args.raml) {
registration.deregisterRaml(args.name, args.raml);
}

if (args.path) {
registration.deregisterPath(args.name, args.path);
}

clearServiceId();
}

self.deregisterService = function(serviceName) {
return serviceRegistrationBase(serviceName, 'deregister');
}

self.deregisterRaml = function(serviceName, filename) {
return ramlRegistrationBase(serviceName, filename, 'deregister');
}

self.deregisterPath = function(serviceName, paths) {
return pathRegistrationBase(serviceName, paths, 'deregister');
}

function getServiceData(serviceName) {
var serviceId = getServiceId(serviceName);
return {
'ID': serviceId,
'Name': serviceName,
'Port': 8080,
'Address': ip.address(),
'Tags': ['openlmis-service'],
'EnableTagOverride': false
};
}

function getServiceId(serviceName) {
var serviceId = null;

try {
fs.accessSync(self.filename, fs.R_OK | fs.W_OK);
serviceId = fs.readFileSync(fs.openSync(self.filename, 'r+')).toString();
} catch (err) {
serviceId = uuid() + '-' + serviceName;
fs.writeSync(fs.openSync(self.filename, 'w+'), serviceId);
}

return serviceId;
}

function clearServiceId() {
try {
fs.unlinkSync(self.filename);
} catch (err) {
console.error("Service ID file could not be found or accessed.");
}

}

function serviceRegistrationBase(serviceName, mode) {
var serviceData = getServiceData(serviceName, mode);
if (mode === 'register') {
return self.registrator.registerService(serviceData);
} else {
return self.registrator.deregisterService(serviceData);
}
}

function ramlRegistrationBase(serviceName, filename, mode) {
var serviceData = getServiceData(serviceName, mode);
var resourceRequests;

self.parser.extractResources(filename).then(function(resources) {
if (mode === 'register') {
resourceRequests = self.registrator.registerResources(serviceData, resources);
} else {
resourceRequests = self.registrator.deregisterResources(serviceData, resources);
}
});

// Wait for RAML parsing
while(!resourceRequests) {
deasync.runLoopOnce();
}

return Promise.all(resourceRequests);
}

function pathRegistrationBase(serviceName, paths, mode) {
var serviceData = getServiceData(serviceName, mode);

for (var i = 0; i < paths.length; i++) {
if (!paths[i].startsWith("/")) {
paths[i] = "/" + paths[i];
}
}

if (mode === 'register') {
return self.registrator.registerResources(serviceData, paths);
} else {
return self.registrator.deregisterResources(serviceData, paths);
}
}
}

function processArgs() {
var args = commandLineArgs([
{ name: 'config-file', alias: 'f', type: String },
{ name: 'name', alias: 'n', type: String },
{ name: 'command', alias: 'c', type: String },
{ name: 'raml', alias: 'r', type: String },
{ name: 'path', alias: 'p', type: String, multiple: true }
]);

if (args['config-file']) {
// If other arguments are not present, override them with config file values
var config = JSON.parse(fs.readFileSync(fs.openSync(args['config-file'], 'r+')).toString());
var values = ['name', 'command', 'path', 'raml'];

for (var i = 0; i < values.length; i++) {
var keyword = values[i];
if (!(args[keyword])) {
args[keyword] = config[keyword];
}
}
}

if (!args.name) {
throw new Error("Name parameter is missing.");
} else if (!args.command) {
throw new Error("Command parameter is missing.");
} else if (!(args.raml || args.path)) {
throw new Error("You must either provide path or file parameter.");
}

return args;
}

try {
var consulHost = process.env.CONSUL_HOST || 'consul';
var consulPort = process.env.CONSUL_PORT || '8500';

// Retrieve arguments passed to script
var args = processArgs();

var registration = new RegistrationService(consulHost, consulPort);
var initialMessage = "Starting service " + args.command + "...";

if (args.command === 'register') {
console.log(initialMessage);
registration.register(args);
} else if (args.command === 'deregister') {
console.log(initialMessage);
registration.deregister(args);
} else {
throw new Error("Invalid command. It should be either 'register' or 'deregister'.")
}

console.log("Service " + args.command + " successful!");
} catch(err) {
console.error("Error during service registration:")
console.error(err.message);
process.exit(1);
}
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -11,7 +11,9 @@

"scripts":
{
"runApiSpecConverter": "api-spec-converter --from raml --to swagger_2 /app/build/resources/main/api-definition-raml.yaml > /app/build/resources/main/static/docs/api-definition.json",
"runApiSpecConverter": "api-spec-converter --from raml --to swagger_2 /app/build/resources/main/api-definition-raml.yaml > /app/build/resources/main/static/referencedata/docs/api-definition.json",
"runApiHtmlConverter": "raml2html --input /app/build/resources/main/api-definition-raml.yaml --output /app/build/resources/main/api-definition.html"
}
},

"files": [ "consul/package.json" ]
}
32 changes: 32 additions & 0 deletions registration.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
task consulRegistration(type:Exec) {
executable "node"
args "consul/registration.js", "-c", "register", "-f", "consul/config.json"
}

task consulDeregistration(type:Exec) {
executable "node"
args "consul/registration.js", "-c", "deregister", "-f", "consul/config.json"
}

task copyRegistrationToConsulBuild(type: Copy) {
from 'consul'
into 'build/consul'
}

task copyRamlToConsulBuild(type: Copy) {
from 'src/main/resources'
into 'build/consul'
include 'api-definition.yaml'
}

task copyRamlSchemasToConsulBuild(type: Copy) {
from 'src/main/resources/schemas'
into 'build/consul/schemas'
}

task copyConsulRegistrationToBuild(type:Copy)
copyConsulRegistrationToBuild {
dependsOn copyRegistrationToConsulBuild
dependsOn copyRamlSchemasToConsulBuild
dependsOn copyRamlToConsulBuild
}
3 changes: 3 additions & 0 deletions run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh
node consul/registration.js -c register -f consul/config.json -r consul/api-definition.yaml
java $JAVA_OPTS -jar /service.jar
Original file line number Diff line number Diff line change
@@ -73,18 +73,18 @@ public abstract class BaseWebIntegrationTest {
public BaseWebIntegrationTest() {

// This mocks the auth check to always return valid admin credentials.
wireMockRule.stubFor(post(urlEqualTo("/auth/oauth/check_token"))
wireMockRule.stubFor(post(urlEqualTo("/api/oauth/check_token"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody(MOCK_CHECK_RESULT)));

// This mocks the call to auth to post to an auth user.
wireMockRule.stubFor(post(urlPathEqualTo("/auth/api/users"))
wireMockRule.stubFor(post(urlPathEqualTo("/api/users"))
.willReturn(aResponse()
.withStatus(200)));

// This mocks the call to notification to post a notification.
wireMockRule.stubFor(post(urlPathEqualTo("/notification"))
wireMockRule.stubFor(post(urlPathEqualTo("/api/notification"))
.willReturn(aResponse()
.withStatus(200)));
}
Original file line number Diff line number Diff line change
@@ -90,7 +90,7 @@ public class UserControllerIntegrationTest extends BaseWebIntegrationTest {
private static final String SUPERVISORY_NODE_CODE = "SN1";
private static final String WAREHOUSE_CODE = "W1";
private static final String HOME_FACILITY_CODE = "HF1";
private static final String USER_API_STRING = "/auth/api/users";
private static final String USER_API_STRING = "/api/users";
private static final String RIGHT_ID_STRING = "rightId";
private static final String PROGRAM_ID_STRING = "programId";

Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@@ -13,8 +14,17 @@ public class CustomWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {

@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/docs").setViewName("redirect:" + serviceUrl + "/docs/");
registry.addViewController("/docs/").setViewName("forward:/docs/index.html");
registry.addViewController("/referencedata/docs")
.setViewName("redirect:" + serviceUrl + "/referencedata/docs/");
registry.addViewController("/referencedata/docs/")
.setViewName("forward:/referencedata/docs/index.html");
super.addViewControllers(registry);
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/referencedata/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
super.addResourceHandlers(registry);
}
}
Original file line number Diff line number Diff line change
@@ -63,9 +63,10 @@ protected void doFilterInternal(HttpServletRequest request,
http
.authorizeRequests()
.antMatchers(
"/",
"/referencedata",
"/webjars/**",
"/docs/**"
"/referencedata/webjars/**",
"/referencedata/docs/**"
).permitAll()
.antMatchers("/**").fullyAuthenticated();
}
Original file line number Diff line number Diff line change
@@ -135,7 +135,7 @@ private void saveAuthUser(User user, String token) {
userRequest.setEmail(user.getEmail());
userRequest.setReferenceDataUserId(user.getId());

String url = virtualHostBaseUrl + "/auth/api/users?access_token=" + token;
String url = virtualHostBaseUrl + "/api/users?access_token=" + token;
RestTemplate restTemplate = new RestTemplate();

restTemplate.postForObject(url, userRequest, Object.class);
@@ -146,7 +146,7 @@ private void saveAuthUser(User user, String token) {
*/
public void passwordReset(PasswordResetRequest passwordResetRequest, String token) {
try {
String url = virtualHostBaseUrl + "/auth/api/users/passwordReset?access_token=" + token;
String url = virtualHostBaseUrl + "/api/users/passwordReset?access_token=" + token;
RestTemplate restTemplate = new RestTemplate();

restTemplate.postForObject(url, passwordResetRequest, String.class);
@@ -162,7 +162,7 @@ public void passwordReset(PasswordResetRequest passwordResetRequest, String toke
*/
public void changePassword(PasswordChangeRequest passwordChangeRequest, String token) {
try {
String url = virtualHostBaseUrl + "/auth/api/users/changePassword?access_token=" + token;
String url = virtualHostBaseUrl + "/api/users/changePassword?access_token=" + token;

RestTemplate restTemplate = new RestTemplate();
restTemplate.postForObject(url, passwordChangeRequest, String.class);
@@ -196,7 +196,7 @@ private void sendResetPasswordEmail(User user, String authToken) {

private UUID createPasswordResetToken(UUID userId, String token) {
try {
String url = virtualHostBaseUrl + "/auth/api/users/passwordResetToken?userId=" + userId
String url = virtualHostBaseUrl + "/api/users/passwordResetToken?userId=" + userId
+ "&access_token=" + token;
RestTemplate restTemplate = new RestTemplate();

@@ -210,7 +210,7 @@ private void sendMail(String from, String to, String subject, String content, St
try {
NotificationRequest request = new NotificationRequest(from, to, subject, content, null);

String url = virtualHostBaseUrl + "/notification/notification?access_token=" + token;
String url = virtualHostBaseUrl + "/api/notification?access_token=" + token;
RestTemplate restTemplate = new RestTemplate();

restTemplate.postForObject(url, request, Object.class);
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@ public class VersionController {
*
* @return {Version} Returns version read from file.
*/
@RequestMapping("/")
@RequestMapping("/referencedata")
public Version display() {
LOGGER.debug("Returning version");
return new Version();
3,481 changes: 1,741 additions & 1,740 deletions src/main/resources/api-definition.yaml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -17,10 +17,10 @@ spring.jpa.show-sql=false

defaultLocale=en

service.url=http://${VIRTUAL_HOST:localhost}/referencedata
service.url=http://${VIRTUAL_HOST:localhost}

auth.server.baseUrl=http://${VIRTUAL_HOST:localhost}
auth.server.url=http://${VIRTUAL_HOST:localhost}/auth/oauth/check_token
auth.server.url=http://${VIRTUAL_HOST:localhost}/api/oauth/check_token
auth.server.clientId=trusted-client
auth.server.clientSecret=secret

Original file line number Diff line number Diff line change
@@ -41,8 +41,6 @@
var url = window.location.search.match(/url=([^&]+)/);
// Location without filename
var baseUrl = window.location.href.replace(/[#|?].*/, "").match(/^(http.+\/).+$/)[1];
// Location without filename and protocol
var basePath = baseUrl.replace(/.*?:\/\//g, "");

if (url && url.length > 1) {
url = decodeURIComponent(url[1]);
@@ -94,7 +92,7 @@

window.swaggerUi.load();
setTimeout(function(){
window.swaggerUi.api.setHost(basePath);
window.swaggerUi.api.setHost(window.location.hostname);
}, 1000);

function log() {

0 comments on commit 763576d

Please sign in to comment.