Add sample parksmap app and grid template.
This commit is contained in:
@ -0,0 +1,18 @@
|
||||
package com.openshift.evg.roadshow;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
/**
|
||||
* Created by jmorales on 24/08/16.
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@ComponentScan(basePackages = "com.openshift.evg.roadshow.rest")
|
||||
public class ParksMapApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ParksMapApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,146 @@
|
||||
package com.openshift.evg.roadshow.rest;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.openshift.evg.roadshow.rest.gateway.helpers.EndpointRegistrar;
|
||||
import com.openshift.evg.roadshow.rest.gateway.helpers.EndpointWatcher;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.HasMetadata;
|
||||
import io.fabric8.kubernetes.client.KubernetesClientException;
|
||||
import io.fabric8.kubernetes.client.Watch;
|
||||
import io.fabric8.kubernetes.client.Watcher;
|
||||
import io.fabric8.openshift.client.DefaultOpenShiftClient;
|
||||
import io.fabric8.openshift.client.OpenShiftClient;
|
||||
|
||||
public abstract class AbstractResourceWatcher<T extends HasMetadata> implements Watcher<T> {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AbstractResourceWatcher.class);
|
||||
|
||||
private String currentNamespace = null;
|
||||
|
||||
private Watch watch;
|
||||
|
||||
private OpenShiftClient client = new DefaultOpenShiftClient();;
|
||||
|
||||
private EndpointRegistrar endpointRegistrar;
|
||||
|
||||
private Map<String, EndpointWatcher> endpointsWatchers = new HashMap<String, EndpointWatcher>();
|
||||
|
||||
@Override
|
||||
public void eventReceived(Action action, T t) {
|
||||
logger.info("Action: {}, Resource: {}", action, t);
|
||||
|
||||
String resourceName = t.getMetadata().getName();
|
||||
// }
|
||||
if (action == Action.ADDED) {
|
||||
logger.info("Resource {} added", resourceName);
|
||||
EndpointWatcher epW = endpointsWatchers.get(resourceName);
|
||||
if (epW == null) {
|
||||
epW = new EndpointWatcher(endpointRegistrar, client, currentNamespace, resourceName);
|
||||
endpointsWatchers.put(resourceName, epW);
|
||||
}
|
||||
} else if (action == Action.DELETED) {
|
||||
logger.info("Resource {} deleted", resourceName);
|
||||
EndpointWatcher epW = endpointsWatchers.get(resourceName);
|
||||
if (epW != null) {
|
||||
epW.close();
|
||||
endpointRegistrar.unregister(resourceName);
|
||||
endpointsWatchers.remove(resourceName);
|
||||
}
|
||||
} else if (action == Action.MODIFIED) {
|
||||
// TODO: Modification of a resource is cumbersome. Look into how to
|
||||
// best implement this
|
||||
EndpointWatcher epW = endpointsWatchers.get(resourceName);
|
||||
endpointsWatchers.remove(resourceName);
|
||||
endpointRegistrar.unregister(resourceName);
|
||||
epW = new EndpointWatcher(endpointRegistrar, client, currentNamespace, resourceName);
|
||||
endpointsWatchers.put(resourceName, epW);
|
||||
|
||||
} else if (action == Action.ERROR) {
|
||||
logger.error("Resource ERRORED");
|
||||
EndpointWatcher epW = endpointsWatchers.get(resourceName);
|
||||
endpointsWatchers.remove(resourceName);
|
||||
epW = new EndpointWatcher(endpointRegistrar, client, currentNamespace, resourceName);
|
||||
endpointsWatchers.put(resourceName, epW);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will get notified when Kubernetes client is closed
|
||||
*
|
||||
* @param e
|
||||
*/
|
||||
@Override
|
||||
public void onClose(KubernetesClientException e) {
|
||||
if (e != null) {
|
||||
// This is when the client is closed
|
||||
logger.error("[ERROR] There was an error in the client {}", e.getMessage());
|
||||
init(endpointRegistrar);
|
||||
} else {
|
||||
logger.info("Closing this Watcher");
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanUp() {
|
||||
if (watch != null)
|
||||
watch.close();
|
||||
// If this watch has been closed, we create complete set of new watches
|
||||
for (EndpointWatcher epWatcher : endpointsWatchers.values()) {
|
||||
epWatcher.close();
|
||||
}
|
||||
endpointsWatchers.clear();
|
||||
}
|
||||
|
||||
public void init(EndpointRegistrar endpointRegistrar) {
|
||||
cleanUp();
|
||||
|
||||
// BackendsController
|
||||
if (this.endpointRegistrar == null) {
|
||||
this.endpointRegistrar = endpointRegistrar;
|
||||
}
|
||||
|
||||
if (currentNamespace == null) {
|
||||
currentNamespace = client.getNamespace();
|
||||
}
|
||||
|
||||
logger.info("[INFO] {} is watching for resources started in namespace {} ", this.getClass().getName(),
|
||||
currentNamespace);
|
||||
|
||||
try {
|
||||
List<T> resources = listWatchedResources();
|
||||
for (T resource : resources) {
|
||||
String resourceName = resource.getMetadata().getName();
|
||||
EndpointWatcher endpointWatcher = endpointsWatchers.get(resourceName);
|
||||
if (endpointWatcher == null) {
|
||||
endpointWatcher = new EndpointWatcher(endpointRegistrar, client, currentNamespace, resourceName);
|
||||
endpointsWatchers.put(resourceName, endpointWatcher);
|
||||
}
|
||||
}
|
||||
|
||||
watch = doInit();
|
||||
} catch (KubernetesClientException e) {
|
||||
// If there is no proper permission, don't fail misserably
|
||||
logger.error(
|
||||
"Error initialiting application. Probably you need the appropriate permissions to view this namespace {}. {}",
|
||||
currentNamespace, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected OpenShiftClient getOpenShiftClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
protected String getNamespace() {
|
||||
return currentNamespace;
|
||||
}
|
||||
|
||||
protected abstract List<T> listWatchedResources();
|
||||
|
||||
protected abstract Watch doInit();
|
||||
|
||||
protected abstract String getUrl(String resourceName);
|
||||
}
|
||||
@ -0,0 +1,124 @@
|
||||
package com.openshift.evg.roadshow.rest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.openshift.evg.roadshow.rest.gateway.ApiGatewayController;
|
||||
import com.openshift.evg.roadshow.rest.gateway.DataGatewayController;
|
||||
import com.openshift.evg.roadshow.rest.gateway.helpers.EndpointRegistrar;
|
||||
import com.openshift.evg.roadshow.rest.gateway.model.Backend;
|
||||
|
||||
/**
|
||||
* Backend controller. Every time a backend appears/dissapears in OpenShift
|
||||
* it will send a notification to the web to show/hide the appropriate layer/map
|
||||
* <p>
|
||||
* Created by jmorales on 24/08/16.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/ws/backends")
|
||||
public class BackendsController implements EndpointRegistrar {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BackendsController.class);
|
||||
|
||||
@Value("${test}")
|
||||
private Boolean test;
|
||||
|
||||
@Autowired
|
||||
private SimpMessagingTemplate messagingTemplate;
|
||||
|
||||
@Autowired
|
||||
private ApiGatewayController apiGateway;
|
||||
|
||||
@Autowired
|
||||
private DataGatewayController dataGateway;
|
||||
|
||||
@Autowired
|
||||
private ServiceWatcher serviceWatcher;
|
||||
|
||||
@Autowired
|
||||
private RouteWatcher routeWatcher;
|
||||
|
||||
private Map<String, Backend> registeredBackends = new HashMap<String, Backend>();
|
||||
|
||||
/**
|
||||
* This method is used to start monitoring for services
|
||||
*/
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/init")
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
routeWatcher.init(this);
|
||||
serviceWatcher.init(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/register", produces = "application/json")
|
||||
public List<Backend> register(@RequestParam(value = "endpoint") String endpoint) {
|
||||
logger.info("Backends.register endpoint at ({})", endpoint);
|
||||
|
||||
Backend newBackend = null;
|
||||
|
||||
String endpointUrl = routeWatcher.getUrl(endpoint); // try to find a route for endpoint
|
||||
if (endpointUrl == null || endpointUrl.trim().equals("")) {
|
||||
endpointUrl = serviceWatcher.getUrl(endpoint); // otherwise, find a service for endpoint
|
||||
}
|
||||
|
||||
// Query for backend data.
|
||||
if (endpoint != null) {
|
||||
if ((newBackend = apiGateway.getFromRemote(endpointUrl)) != null) {
|
||||
// TODO: BackendId should not be fetched from remote. For now I replace the remote one with the local one.
|
||||
newBackend.setId(endpoint);
|
||||
// Register the new backend
|
||||
apiGateway.add(endpoint, endpointUrl);
|
||||
dataGateway.add(endpoint, endpointUrl);
|
||||
registeredBackends.put(endpoint, newBackend);
|
||||
|
||||
logger.info("Backend from server: ({}) ", newBackend);
|
||||
// Notify web
|
||||
messagingTemplate.convertAndSend("/topic/add", newBackend);
|
||||
} else {
|
||||
logger.info("Backend with provided id ({}) already registered", endpoint);
|
||||
}
|
||||
}
|
||||
return new ArrayList<Backend>(registeredBackends.values());
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/unregister", produces = "application/json")
|
||||
public List<Backend> unregister(@RequestParam(value = "endpointName") String endpointName) {
|
||||
logger.info("Backends.unregister service at ({})", endpointName);
|
||||
|
||||
Backend backend = null;
|
||||
if ((backend = registeredBackends.get(endpointName)) != null) {
|
||||
messagingTemplate.convertAndSend("/topic/remove", backend); // Notify web
|
||||
|
||||
registeredBackends.remove(endpointName);
|
||||
apiGateway.remove(endpointName);
|
||||
dataGateway.remove(endpointName);
|
||||
} else {
|
||||
logger.info("No backend at ({})", endpointName);
|
||||
}
|
||||
return new ArrayList<Backend>(registeredBackends.values());
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/list", produces = "application/json")
|
||||
public List<Backend> getAll() {
|
||||
logger.info("Backends: getAll");
|
||||
return new ArrayList<Backend>(registeredBackends.values());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package com.openshift.evg.roadshow.rest;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Healthz endpoint for liveness and readiness of the application
|
||||
*
|
||||
* Created by jmorales on 11/08/16.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/ws/healthz")
|
||||
public class Healthz {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/")
|
||||
public String healthz() {
|
||||
return "OK";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
package com.openshift.evg.roadshow.rest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import io.fabric8.kubernetes.client.Watch;
|
||||
import io.fabric8.openshift.api.model.Route;
|
||||
|
||||
@Component
|
||||
public class RouteWatcher extends AbstractResourceWatcher<Route> {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ServiceWatcher.class);
|
||||
|
||||
private static final String PARKSMAP_BACKEND_LABEL = "type=parksmap-backend";
|
||||
|
||||
@Override
|
||||
protected List<Route> listWatchedResources() {
|
||||
return getOpenShiftClient().routes().inNamespace(getNamespace()).withLabel(PARKSMAP_BACKEND_LABEL).list()
|
||||
.getItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Watch doInit() {
|
||||
return getOpenShiftClient().routes().inNamespace(getNamespace()).withLabel(PARKSMAP_BACKEND_LABEL).watch(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getUrl(String routeName) {
|
||||
List<Route> routes = getOpenShiftClient().routes().inNamespace(getNamespace()).withLabel(PARKSMAP_BACKEND_LABEL)
|
||||
.withField("metadata.name", routeName).list().getItems();
|
||||
if (routes.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Route route = routes.get(0);
|
||||
String routeUrl = "";
|
||||
try {
|
||||
String protocol = "http://";
|
||||
if((route.getSpec().getTls()!=null)&&(route.getSpec().getTls().getTermination()!=null)){
|
||||
protocol = "https://";
|
||||
}
|
||||
routeUrl = protocol + route.getSpec().getHost();
|
||||
} catch (Exception e) {
|
||||
logger.error("Route {} does not have a port assigned", routeName);
|
||||
}
|
||||
|
||||
logger.info("[INFO] Computed route URL: {}", routeUrl);
|
||||
|
||||
return routeUrl;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
package com.openshift.evg.roadshow.rest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.client.Watch;
|
||||
|
||||
@Component
|
||||
public class ServiceWatcher extends AbstractResourceWatcher<Service> {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ServiceWatcher.class);
|
||||
|
||||
private static final String PARKSMAP_BACKEND_LABEL = "type=parksmap-backend";
|
||||
|
||||
@Override
|
||||
protected List<Service> listWatchedResources() {
|
||||
return getOpenShiftClient().services().inNamespace(getNamespace()).withLabel(PARKSMAP_BACKEND_LABEL).list()
|
||||
.getItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Watch doInit() {
|
||||
return getOpenShiftClient().services().inNamespace(getNamespace()).withLabel(PARKSMAP_BACKEND_LABEL).watch(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getUrl(String serviceName) {
|
||||
List<Service> services = getOpenShiftClient().services().inNamespace(getNamespace())
|
||||
.withLabel(PARKSMAP_BACKEND_LABEL).withField("metadata.name", serviceName).list().getItems();
|
||||
if (services.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Service service = services.get(0);
|
||||
String serviceURL = "";
|
||||
int port = 8080;
|
||||
try {
|
||||
port = service.getSpec().getPorts().get(0).getPort();
|
||||
} catch (Exception e) {
|
||||
logger.error("Service {} does not have a port assigned", serviceName);
|
||||
}
|
||||
|
||||
serviceURL = "http://" + serviceName + ":" + port;
|
||||
|
||||
logger.info("[INFO] Computed service URL: {}", serviceURL);
|
||||
return serviceURL;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package com.openshift.evg.roadshow.rest;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||
|
||||
/**
|
||||
* This class provides support for websockets communication with the web Web
|
||||
* will use t2 topics: /topic/add Notification that there is a new backend
|
||||
* /topic/remove Notification of removal of backend
|
||||
* <p>
|
||||
* Created by jmorales on 26/08/16.
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureMessageBroker(MessageBrokerRegistry config) {
|
||||
config.enableSimpleBroker("/topic");
|
||||
config.setApplicationDestinationPrefixes("/app");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||
registry.addEndpoint("/socks-backends").withSockJS();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,101 @@
|
||||
package com.openshift.evg.roadshow.rest.gateway;
|
||||
|
||||
import com.openshift.evg.roadshow.rest.gateway.api.BackendServiceRemote;
|
||||
import com.openshift.evg.roadshow.rest.gateway.helpers.CustomErrorDecoder;
|
||||
import com.openshift.evg.roadshow.rest.gateway.model.Backend;
|
||||
|
||||
import feign.Feign;
|
||||
import feign.Retryer;
|
||||
import feign.jackson.JacksonDecoder;
|
||||
import feign.jackson.JacksonEncoder;
|
||||
import feign.jaxrs.JAXRSContract;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
/**
|
||||
* API Gateway. It will dispatch connections to the appropriate backend
|
||||
* <p>
|
||||
* Created by jmorales on 24/08/16.
|
||||
*/
|
||||
@Controller
|
||||
public class ApiGatewayController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ApiGatewayController.class);
|
||||
|
||||
private Map<String, BackendServiceRemote> remoteServices = new HashMap<String, BackendServiceRemote>();
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param backendId
|
||||
* @param url
|
||||
*/
|
||||
public final void add(String backendId, String url) {
|
||||
if (remoteServices.get(backendId) == null) {
|
||||
remoteServices.put(backendId, Feign.builder().client(CustomFeignClient.getClient()).contract(new JAXRSContract()).encoder(new JacksonEncoder())
|
||||
.decoder(new JacksonDecoder()).target(BackendServiceRemote.class, url));
|
||||
logger.info("Backend ({}) added to the API Gateway", backendId);
|
||||
} else {
|
||||
logger.error("This backend ({}) did already exist in the API Gateway", backendId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param backendId
|
||||
*/
|
||||
public final void remove(String backendId) {
|
||||
if (remoteServices.get(backendId) != null) {
|
||||
remoteServices.remove(backendId);
|
||||
logger.info("Backend ({}) removed from the API Gateway", backendId);
|
||||
} else {
|
||||
logger.error("This backend ({}) did NOT exist in the API Gateway", backendId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param backendId
|
||||
* @return
|
||||
*/
|
||||
public Backend getFromLocal(String backendId) {
|
||||
BackendServiceRemote backend = null;
|
||||
if ((backend = remoteServices.get(backendId)) != null) {
|
||||
logger.info("Calling remote service {}", backendId);
|
||||
try {
|
||||
return backend.get();
|
||||
} catch (Exception e) {
|
||||
logger.error("Error connecting to backend server {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param remoteURL
|
||||
* @return
|
||||
*/
|
||||
public Backend getFromRemote(String remoteURL) {
|
||||
logger.info("Calling remote service at {}", remoteURL);
|
||||
try {
|
||||
return Feign.builder().client(CustomFeignClient.getClient()).contract(new JAXRSContract()).encoder(new JacksonEncoder()).decoder(new JacksonDecoder())
|
||||
.retryer(new Retryer.Default(200, SECONDS.toMillis(1), 5)).errorDecoder(new CustomErrorDecoder())
|
||||
.target(BackendServiceRemote.class, remoteURL).get();
|
||||
} catch (Exception e) {
|
||||
logger.error("Error connecting to backend server {}", e.getMessage());
|
||||
logger.error("Error message",e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
package com.openshift.evg.roadshow.rest.gateway;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
import feign.Client;
|
||||
|
||||
class CustomFeignClient {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CustomFeignClient.class);
|
||||
/**
|
||||
* This method should not be used in production!! It is only in a poof of
|
||||
* concept!
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static SSLSocketFactory getSSLSocketFactory() {
|
||||
try {
|
||||
SSLContext context = SSLContext.getInstance("TLS");
|
||||
context.init(null, new X509TrustManager[] { new X509TrustManager() {
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
logger.info("checkClientTrusted");
|
||||
}
|
||||
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
logger.info("checkClientTrusted");
|
||||
}
|
||||
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
logger.info("checkClientTrusted");
|
||||
return new X509Certificate[0];
|
||||
}
|
||||
} }, new SecureRandom());
|
||||
logger.warn("Ignoring certification errors! Don't use in production!");
|
||||
return context.getSocketFactory();
|
||||
} catch (Exception exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public static Client getClient() {
|
||||
return new Client.Default(getSSLSocketFactory(), getHostNameVerifier());
|
||||
}
|
||||
|
||||
private static HostnameVerifier getHostNameVerifier() {
|
||||
HostnameVerifier hostnameVerifier= new HostnameVerifier(){
|
||||
|
||||
public boolean verify(String hostname,
|
||||
javax.net.ssl.SSLSession sslSession) {
|
||||
logger.warn("Ignoring hostname verification errors! Don't use in production!");
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return hostnameVerifier;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,86 @@
|
||||
package com.openshift.evg.roadshow.rest.gateway;
|
||||
|
||||
import com.openshift.evg.roadshow.rest.gateway.api.DataServiceRemote;
|
||||
import com.openshift.evg.roadshow.rest.gateway.model.DataPoint;
|
||||
import feign.Feign;
|
||||
import feign.jackson.JacksonDecoder;
|
||||
import feign.jackson.JacksonEncoder;
|
||||
import feign.jaxrs.JAXRSContract;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* API Gateway. It will dispatch connections to the appropriate backend
|
||||
*
|
||||
* Created by jmorales on 24/08/16.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/ws/data")
|
||||
public class DataGatewayController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DataGatewayController.class);
|
||||
|
||||
private Map<String, DataServiceRemote> remoteServices = new HashMap<String, DataServiceRemote>();
|
||||
|
||||
public DataGatewayController() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param backendId
|
||||
* @param url
|
||||
*/
|
||||
public final void add(String backendId, String url) {
|
||||
if (remoteServices.get(backendId) == null) {
|
||||
remoteServices.put(backendId, Feign.builder().client(CustomFeignClient.getClient()).contract(new JAXRSContract()).encoder(new JacksonEncoder())
|
||||
.decoder(new JacksonDecoder()).target(DataServiceRemote.class, url));
|
||||
logger.info("Backend ({}) added to the Data Gateway", backendId);
|
||||
} else {
|
||||
logger.error("This backend ({}) did already exist in the Data Gateway", backendId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param backendId
|
||||
*/
|
||||
public final void remove(String backendId) {
|
||||
if (remoteServices.get(backendId) != null) {
|
||||
remoteServices.remove(backendId);
|
||||
logger.info("Backend ({}) removed from the Data Gateway", backendId);
|
||||
} else {
|
||||
logger.error("This backend ({}) did NOT exist in the Data Gateway", backendId);
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/all", produces = "application/json")
|
||||
public List<DataPoint> getAll(@RequestParam(value = "service") String serviceURL) {
|
||||
DataServiceRemote remote = remoteServices.get(serviceURL);
|
||||
if (remote != null) {
|
||||
logger.info("[WEB-CALL] Calling remote service for {}", serviceURL);
|
||||
return remote.getAll();
|
||||
} else {
|
||||
logger.error("[WEB-CALL] No remote service for {}", serviceURL);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/within", produces = "application/json")
|
||||
public List<DataPoint> getWithin(@RequestParam(value = "service") String serviceURL, @RequestParam("lat1") float lat1,
|
||||
@RequestParam("lon1") float lon1, @RequestParam("lat2") float lat2, @RequestParam("lon2") float lon2) {
|
||||
DataServiceRemote remote = remoteServices.get(serviceURL);
|
||||
if (remote != null) {
|
||||
logger.info("[WEB-CALL] Calling remote service for {}", serviceURL);
|
||||
return remote.findWithin(lat1, lon1, lat2, lon2);
|
||||
} else {
|
||||
logger.error("[WEB-CALL] No remote service for {}", serviceURL);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package com.openshift.evg.roadshow.rest.gateway.api;
|
||||
|
||||
import com.openshift.evg.roadshow.rest.gateway.model.Backend;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
/**
|
||||
* Contract to use in the backends to provide backend information
|
||||
*
|
||||
* Created by jmorales on 26/09/16.
|
||||
*/
|
||||
@Path("/ws/info")
|
||||
public interface BackendServiceRemote {
|
||||
@GET
|
||||
@Path("/")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Backend get();
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package com.openshift.evg.roadshow.rest.gateway.api;
|
||||
|
||||
import com.openshift.evg.roadshow.rest.gateway.model.DataPoint;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by jmorales on 28/09/16.
|
||||
*/
|
||||
@RequestMapping("/ws/data")
|
||||
@Path("/ws/data")
|
||||
@RestController
|
||||
public interface DataServiceRemote {
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/", produces = "application/json")
|
||||
@GET()
|
||||
@Path("/all")
|
||||
@Produces("application/json")
|
||||
public List<DataPoint> getAll();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/within", produces = "application/json")
|
||||
@GET()
|
||||
@Path("/within")
|
||||
@Produces("application/json")
|
||||
public List<DataPoint> findWithin(@RequestParam("lat1") @QueryParam("lat1") float lat1,
|
||||
@RequestParam("lon1") @QueryParam("lon1") float lon1, @RequestParam("lat2") @QueryParam("lat2") float lat2,
|
||||
@RequestParam("lon2") @QueryParam("lon2") float lon2);
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package com.openshift.evg.roadshow.rest.gateway.helpers;
|
||||
|
||||
import feign.Response;
|
||||
import feign.RetryableException;
|
||||
import feign.codec.ErrorDecoder;
|
||||
|
||||
import static feign.FeignException.errorStatus;
|
||||
|
||||
/**
|
||||
* Created by jmorales on 04/10/16.
|
||||
*/
|
||||
public class CustomErrorDecoder implements ErrorDecoder {
|
||||
@Override
|
||||
public Exception decode(String s, Response response) {
|
||||
if (response.status() == 503)
|
||||
return new RetryableException("Error 503 from server. Let's retry", null);
|
||||
else
|
||||
return errorStatus(s, response);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package com.openshift.evg.roadshow.rest.gateway.helpers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.openshift.evg.roadshow.rest.gateway.model.Backend;
|
||||
|
||||
public interface EndpointRegistrar {
|
||||
List<Backend> register(String endpointName);
|
||||
|
||||
List<Backend> unregister(String endpointName);
|
||||
|
||||
void init();
|
||||
}
|
||||
@ -0,0 +1,104 @@
|
||||
package com.openshift.evg.roadshow.rest.gateway.helpers;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Endpoints;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.KubernetesClientException;
|
||||
import io.fabric8.kubernetes.client.Watch;
|
||||
import io.fabric8.kubernetes.client.Watcher;
|
||||
|
||||
/**
|
||||
*
|
||||
* Created by jmorales on 04/10/16.
|
||||
*/
|
||||
public class EndpointWatcher implements Watcher<Endpoints> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(EndpointWatcher.class);
|
||||
|
||||
private Watch watch;
|
||||
|
||||
private String namespace;
|
||||
|
||||
private String endpointName;
|
||||
|
||||
private KubernetesClient client;
|
||||
|
||||
private AtomicInteger endpointsAvailable = new AtomicInteger(0);
|
||||
|
||||
private EndpointRegistrar callback;
|
||||
|
||||
public EndpointWatcher(EndpointRegistrar callback, KubernetesClient client, String namespace, String endpointName) {
|
||||
this.client = client;
|
||||
this.namespace = namespace;
|
||||
this.endpointName = endpointName;
|
||||
this.callback = callback;
|
||||
logger.info("EndpointWatcher created for: endpoints/{} -n {}", endpointName, namespace);
|
||||
|
||||
if (hasEndpoints()) {
|
||||
callback.register(endpointName);
|
||||
} else {
|
||||
callback.unregister(endpointName);
|
||||
}
|
||||
// Create the watch
|
||||
watch = client.endpoints().inNamespace(namespace).withName(endpointName).watch(this);
|
||||
|
||||
}
|
||||
|
||||
private boolean hasEndpoints() {
|
||||
return hasEndpoints(client.endpoints().inNamespace(namespace).withName(endpointName).get());
|
||||
}
|
||||
|
||||
private boolean hasEndpoints(Endpoints endpoints) {
|
||||
int size = getEndpointsAddressSize(endpoints);
|
||||
if (size > 0) {
|
||||
endpointsAvailable.set(size);
|
||||
return size > 0;
|
||||
} else
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
private int getEndpointsAddressSize(Endpoints endpoints) {
|
||||
if (endpoints.getSubsets().size() > 0)
|
||||
return endpoints.getSubsets().get(0).getAddresses().size();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventReceived(Action action, Endpoints endpoints) {
|
||||
int current = getEndpointsAddressSize(endpoints);
|
||||
int previous = endpointsAvailable.getAndSet(current);
|
||||
if (previous != current) {
|
||||
logger.info("Endpoints changed, from {} to {}", previous, current);
|
||||
if (previous == 0) {
|
||||
if (current > 0) {
|
||||
logger.info("There are endpoints for {} available. Registering", endpointName);
|
||||
callback.register(endpointName);
|
||||
}
|
||||
}
|
||||
if (current == 0) {
|
||||
if (previous > 0) {
|
||||
logger.info("There's no endpoints for {}. Unregistering", endpointName);
|
||||
callback.unregister(endpointName);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.info("Endpoints changes ignored");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(KubernetesClientException e) {
|
||||
callback.init();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (watch != null)
|
||||
watch.close();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,121 @@
|
||||
package com.openshift.evg.roadshow.rest.gateway.model;
|
||||
|
||||
/**
|
||||
* This represents a backend. Once a backend is registered, a call to the
|
||||
* backend to get this information about it will be issued.
|
||||
*
|
||||
* Created by jmorales on 24/08/16.
|
||||
*/
|
||||
public class Backend {
|
||||
|
||||
public static final String BACKEND_TYPE_MARKER = "marker";
|
||||
public static final String BACKEND_TYPE_CLUSTER = "cluster";
|
||||
public static final String BACKEND_TYPE_TEMP = "temp";
|
||||
public static final String BACKEND_TYPE_HEATMAP = "heatmap";
|
||||
|
||||
public static final String BACKEND_SCOPE_ALL = "all";
|
||||
public static final String BACKEND_SCOPE_WITHIN = "within";
|
||||
|
||||
private String id;
|
||||
private String displayName;
|
||||
private Coordinates center = new Coordinates("0", "0");
|
||||
private int zoom = 1;
|
||||
private int maxZoom = 1;
|
||||
private String type = BACKEND_TYPE_CLUSTER;
|
||||
private boolean visible = true;
|
||||
private String scope = BACKEND_SCOPE_ALL;
|
||||
|
||||
public Backend() {
|
||||
}
|
||||
|
||||
public Backend(String id, String displayName, String service) {
|
||||
this.id = id;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public Backend(String id, String displayName, Coordinates center, int zoom) {
|
||||
this.id = id;
|
||||
this.displayName = displayName;
|
||||
this.center = center;
|
||||
this.zoom = zoom;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setDisplayName(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public Coordinates getCenter() {
|
||||
return center;
|
||||
}
|
||||
|
||||
public void setCenter(Coordinates center) {
|
||||
this.center = center;
|
||||
}
|
||||
|
||||
public int getZoom() {
|
||||
return zoom;
|
||||
}
|
||||
|
||||
public void setZoom(int zoom) {
|
||||
this.zoom = zoom;
|
||||
}
|
||||
|
||||
public int getMaxZoom() {
|
||||
return maxZoom;
|
||||
}
|
||||
|
||||
public void setMaxZoom(int maxzoom) {
|
||||
this.maxZoom = maxzoom;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
}
|
||||
|
||||
public void setVisible(boolean visible) {
|
||||
this.visible = visible;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public void setScope(String scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Backend{" +
|
||||
"id='" + id + '\'' +
|
||||
", displayName='" + displayName + '\'' +
|
||||
", center='" + center + '\'' +
|
||||
", zoom='" + zoom + '\'' +
|
||||
", type='" + type + '\'' +
|
||||
", scope='" + scope + '\'' +
|
||||
", visible='" + visible + '\'' +
|
||||
", maxZoom='" + maxZoom + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package com.openshift.evg.roadshow.rest.gateway.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TODO: Remove???
|
||||
* <p>
|
||||
* Created by jmorales on 18/08/16.
|
||||
*/
|
||||
public class Coordinates {
|
||||
private String latitude;
|
||||
private String longitude;
|
||||
|
||||
public Coordinates() {
|
||||
}
|
||||
|
||||
public Coordinates(String lat, String lng) {
|
||||
this.latitude = lat;
|
||||
this.longitude = lng;
|
||||
}
|
||||
|
||||
public Coordinates(List<?> position) {
|
||||
if (position.size() > 0)
|
||||
this.latitude = position.get(0).toString();
|
||||
if (position.size() > 1)
|
||||
this.longitude = position.get(1).toString();
|
||||
}
|
||||
|
||||
public String getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
public void setLatitude(String lat) {
|
||||
this.latitude = lat;
|
||||
}
|
||||
|
||||
public String getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
public void setLongitude(String lng) {
|
||||
this.longitude = lng;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Coordinates{" +
|
||||
"lat='" + latitude + '\'' +
|
||||
", lng='" + longitude + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
package com.openshift.evg.roadshow.rest.gateway.model;
|
||||
|
||||
public class DataPoint {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
|
||||
private Coordinates position;
|
||||
|
||||
private String longitude;
|
||||
private String latitude;
|
||||
|
||||
private String info;
|
||||
|
||||
public DataPoint() {
|
||||
}
|
||||
|
||||
public DataPoint(String id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Object getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Coordinates getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPosition(Coordinates position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public Object getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
public void setLongitude(String longitude) {
|
||||
this.longitude = longitude;
|
||||
}
|
||||
|
||||
public Object getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
public void setLatitude(String latitude) {
|
||||
this.latitude = latitude;
|
||||
}
|
||||
|
||||
public String getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
public void setInfo(String info) {
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NationalPark{" +
|
||||
"id=" + id +
|
||||
", name=" + name +
|
||||
", coordinates=" + position +
|
||||
", info=" + info +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user