Updated MicroProfile 1.2 Conference Demo - Minishift/Wildfly-Swarm 2018.2.0
Posted by starksm64 Feb 22, 2018
MicroProfile 1.2 Conference App Demo
This blog talks about running an updated version of the MicroProfile 1.2 based conference demo application on a local minishift cluster along with the latest Wildfly-Swarm 2018.2.0 supporting MicroProfile 1.2. You can find a PDF version of this blog along with a video walkthrough at: Release MicroProfile 1.2 Features Companion Demo - Minishift/WildFly Swarm 2018.2.0 · MicroProfileJWT/microprofile-confe…
Prerequisites
Install the Minishift binary
Install VirtualBox if needed
Clone the https://github.com/MicroProfileJWT/microprofile-conference.git project
cd microprofile-conference
Start minishift using the config-minishift.sh script in the microprofile-conference root directory
Configure your environment:
[starksm64-microprofile-conference 524]$ eval $(minishift oc-env) [starksm64-microprofile-conference 525]$ type oc oc is /Users/starksm/.minishift/cache/oc/v3.7.1/darwin/oc [starksm64-microprofile-conference 526]$ eval $(minishift docker-env) [starksm64-microprofile-conference 1568]$ oc login $(minishift ip):8443 -u admin -p admin Login successful. You have access to the following projects and can switch between them with 'oc project <projectname>': default kube-public kube-system * myproject openshift openshift-infra openshift-node Using project "myproject".
open the minishift console uisng
minishift console
Build and Deploy the Microservices
Build the services:
[starksm64-microprofile-conference 1559]$ mvn clean install -DskipTests=true [INFO] Scanning for projects... [INFO] ------------------------------------------------------------------------ [INFO] Reactor Build Order: [INFO] [INFO] Conference [INFO] Conference :: Bootstrap Data [INFO] Conference :: Authorization [INFO] Conference :: Session [INFO] Conference :: Vote [INFO] Conference :: Speaker [INFO] Conference :: Schedule [INFO] Conference :: Web [INFO] Conference :: Start ... [INFO] ------------------------------------------------------------------------ [INFO] Reactor Summary: [INFO] [INFO] Conference ......................................... SUCCESS [ 0.693 s] [INFO] Conference :: Bootstrap Data ....................... SUCCESS [ 2.593 s] [INFO] Conference :: Authorization ........................ SUCCESS [ 12.907 s] [INFO] Conference :: Session .............................. SUCCESS [ 8.802 s] [INFO] Conference :: Vote ................................. SUCCESS [ 12.265 s] [INFO] Conference :: Speaker .............................. SUCCESS [ 9.020 s] [INFO] Conference :: Schedule ............................. SUCCESS [ 15.670 s] [INFO] Conference :: Web .................................. SUCCESS [ 33.957 s] [INFO] Conference :: Start ................................ SUCCESS [ 0.032 s] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 01:36 min [INFO] Finished at: 2018-02-16T00:00:40-08:00 [INFO] Final Memory: 112M/1154M [INFO] ------------------------------------------------------------------------
Install the services into the minishift environment using the cloud-deploy.sh script:
[starksm64-microprofile-conference 1571]$ ./cloud-deploy.sh [INFO] Scanning for projects... [INFO] ... deployment "microservice-vote" created service "microservice-vote" created route "microservice-vote" exposed [starksm64-microprofile-conference 1572]$ oc status In project My Project (myproject) on server https://192.168.99.100:8443 http://microservice-authz-myproject.192.168.99.100.nip.io to pod port http (svc/microservice-authz) pod/microservice-authz-3124937629-wl8g7 runs example/microservice-authz:latest http://microservice-schedule-myproject.192.168.99.100.nip.io to pod port http (svc/microservice-schedule) pod/microservice-schedule-3040366544-n82zt runs example/microservice-schedule:latest http://microservice-session-myproject.192.168.99.100.nip.io to pod port http (svc/microservice-session) pod/microservice-session-1164112827-r8z9r runs example/microservice-session:latest http://microservice-speaker-myproject.192.168.99.100.nip.io to pod port http (svc/microservice-speaker) pod/microservice-speaker-2311407995-4mt9p runs example/microservice-speaker:latest http://microservice-vote-myproject.192.168.99.100.nip.io to pod port http (svc/microservice-vote) pod/microservice-vote-2774736211-wzzhz runs example/microservice-vote:latest View details with 'oc describe <resource>/<name>' or list everything with 'oc get all'. [starksm64-microprofile-conference 1573]$
Update the web-application/src/main/local/webapp/WEB-INF/conference.properties service URLs to use the value for
minishift ip
in your environment. In my environment 192.168.99.100 is the IP address. Globally replace 192.168.99.100 with whatever is returned in your minishift setup.Run the web application front end
mvn package tomee:run -pl :web-application -DskipTests [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building Conference :: Web 1.0.0-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] ... miTargets] to [true] as the property does not exist. INFO - Starting ProtocolHandler [http-nio-8080] INFO - Starting ProtocolHandler [ajp-nio-8009] INFO - Server startup in 3177 ms
Open the web application http://localhost:8080/
Code Walkthrough
In this section we take a look at the code behind the Microprofile features in use in the conference application.
MP-JWT
The JWT RBAC for MicroProfile(MP-JWT) feature defines how JSON web tokens(JWT) may be used for authentication and role based authorization. The MP-JWT feature also defines an API for accessing the claims associated with JWTs. In the conference application demo, the microservice-session uses the JWT groups claim and a custom application claim. The following code snippet demonstrates the MP-JWT API.
Code from: microservice-session/src/main/java/io/microprofile/showcase/session/SessionResource.java |
import org.eclipse.microprofile.jwt.JsonWebToken; @ApplicationScoped public class SessionResource { /** * The current MP-JWT for the authenticated user */ @Inject JsonWebToken jwt; ... @GET @Produces(MediaType.APPLICATION_JSON) @Timed public Collection<Session> allSessions(@Context SecurityContext securityContext) throws Exception { requestCount.inc(); if (jwt == null) {
// User was not authenticated System.out.printf("allSessions, no token\n"); return Collections.emptyList(); } String userName = jwt.getName(); // Use the isUserInRole of container to check for VIP role in the JWT groups claim boolean isVIP = securityContext.isUserInRole("VIP");
System.out.printf("allSessions(%s), isVIP=%s, User token: %s\n", userName, isVIP, jwt); // Check if the user has a session_time_preference custom claim in the token Optional<String> sessionTimePref = jwt.claim("session_time_preference");
if(sessionTimePref.isPresent()) { // Create a session filter for the time preference... } // If the user does NOT have a VIP role, filter out the VIP sessions Collection<Session> sessions; if (!isVIP) {
sessions = sessionStore.getSessions() .stream() .filter(session -> !session.isVIPOnly()) .collect(Collectors.toList()); } else { sessions = sessionStore.getSessions(); } return sessions; }
1 | Injection of the MP-JWT token as a JsonWebToken interface. |
2 | If there is no token, return an empty collection of sessions |
3 | Check for a VIP role in the token using the container’s isUserInRole(String) method. This internally maps to the token’s MP-JWT defined group claims. |
4 | Illustrates programatic lookup of a custom claim not defined by the MP-JWT spec. In this example the session results would be filtered to only return those matching the session time of day preference. |
5 | This code block makes a check of the incoming MP-JWT token to see if it has a group claim that contains the VIP value. If the VIP claim does not exist, the sessions are filtered to remove those that the isVIPOnly property. Otherwise, all sessions are returned if the token has the VIP group. |
MP-Configuration
The Microprofile config(MP-Config) supports injection and programtic lookup of external configuration information via a common API. The MP-Config spec defines 3 common configuration sources: * System environment variables * System properties * A META-INF/microprofile-config.properties
SPIs are defined for adding configuration sources as well as for converting from string to arbitrary types.
The microprofile conference app makes use of injection of META-INF/microprofile-config.properties, environment variables, and the conversion SPI. The first code snippet we will look at injects a value from the bundled META-INF/microprofile-config.properties as a java.security.PrivateKey
. The AuthzResource
from the microservice-authz project shows the injection:
Code from: microservice-authz/src/main/java/io/microprofile/showcase/tokens/AuthzResource.java,PrivateKeyConverter.java |
import java.security.PrivateKey; @ApplicationScoped public class AuthzResource { /** * An example of injecting a custom property type */ @ConfigProperty(name="authz.signingKey") @Inject private PrivateKey signingKey; ...
# The META-INF/microprofile-config.properties entries authz.signingKey=/privateKey.pem
import java.security.PrivateKey; import org.eclipse.microprofile.config.spi.Converter; import static io.microprofile.showcase.tokens.TokenUtils.readPrivateKey; /** * A custom configuration converter for {@linkplain PrivateKey} injection using * {@linkplain org.eclipse.microprofile.config.inject.ConfigProperty} */ public class PrivateKeyConverter implements Converter<PrivateKey> {
/** * Converts a string to a PrivateKey by loading it as a classpath resource * @param s - the string value to convert * @return the PrivateKey loaded as a resource * @throws IllegalArgumentException - on failure to load the key */ @Override public PrivateKey convert(String s) throws IllegalArgumentException { PrivateKey pk = null; try { pk = readPrivateKey(s); } catch (Exception e) { IllegalArgumentException ex = new IllegalArgumentException("Failed to parse "); ex.initCause(e); throw ex; } return pk; } }
1 | The config property name reference to match against a config source. |
2 | The custom value PrivateKey value injection site. |
3 | The mapping from the referenced "authz.signingKey" name to a string value in the standard META-INF/microprofile-config.properties. |
4 | The custom converter implementation that takes the input string value and transforms it into a PrivateKey by loading it as a resource from the classpath. |
A further example usage of the MP-Config will be seen in the next section on the health check feature.
MP-Health
The Microprofile health check(MP-Health) feature allows on to define application health check endpoints as commonly used in cloud environment to validate avaiability and liveness. The MP-Health feature supports this along with an ability to define a JSON payload that can be used to convey additional information.
The following microservice-session MP-Health code snippet shows an example health implementation that makes use of the MP-Config API to inject configuration that is used during construction the health response.
Code from: microservice-session/src/main/java/io/microprofile/showcase/session/SessionCheck.java |
import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.health.Health; import org.eclipse.microprofile.health.HealthCheck; import org.eclipse.microprofile.health.HealthCheckResponse; @Health
@ApplicationScoped public class SessionCheck implements HealthCheck {
@Inject private SessionStore sessionStore; @Inject @ConfigProperty(name = "sessionCountName", defaultValue = "sessionCount")
private String sessionCountName; @ConfigProperty(name = "JAR_SHA256")
@Inject private String jarSha256; @Override public HealthCheckResponse call() {
return HealthCheckResponse.named("sessions-check") .withData(sessionCountName, sessionStore.getSessions().size())
.withData("lastCheckDate", new Date().toString()) .withData("jarSHA256", jarSha256) .up() .build(); } }
1 | The annotation marking the bean as a health check endpoint. |
2 | The HealthCheck interface the endpoint implements to provide the health callback. |
3 | An example of externalizing a data label used in health check response whose value is defined in the application META-INF/microprofile-config.properties. |
4 | An example of injection of a config value whose source is an environment variable that is defined in the microservice-session openshift deployment descriptor. |
5 | The HealthCheck call endpoint that returns the HealthCheckResponse . |
6 | The various withData calls add labelled values, including the injected config values, to the JSON payload. |
MP-Metrics
The Microprofile metrics(MP-Metrics) feature aims to provide a unified way for Microprofile services to export Monitoring data via common API.
Code from: microservice-session/src/main/java/io/microprofile/showcase/session/SessionResource.java |
import org.eclipse.microprofile.metrics.Counter; import org.eclipse.microprofile.metrics.Histogram; import org.eclipse.microprofile.metrics.Metadata; import org.eclipse.microprofile.metrics.MetricRegistry; import org.eclipse.microprofile.metrics.MetricType; import org.eclipse.microprofile.metrics.annotation.Metric; import org.eclipse.microprofile.metrics.annotation.Timed; @ApplicationScoped public class SessionResource { @Inject @Metric(name = "requestCount", description = "All JAX-RS request made to the SessionResource", displayName = "SessionResource#requestCount")
private Counter requestCount; /** * The application metrics registry that allows access to any metric to be accessed/created */ @Inject private MetricRegistry metrics; @PostConstruct void init() { Collection<Session> sessions = sessionStore.getSessions(); System.out.printf("SessionResource.init, session count=%d\n", sessions.size()); // Create a histogram of the session abstract word counts Metadata metadata = new Metadata(SessionResource.class.getName()+".abstractWordCount", MetricType.HISTOGRAM);
metadata.setDescription("Word count histogram for the session abstracts"); Histogram abstractWordCount = metrics.histogram(metadata);
for(Session session : sessions) { String[] words = session.getAbstract().split("\\s+"); abstractWordCount.update(words.length);
} } @GET @Produces(MediaType.APPLICATION_JSON) @Timed public Collection<Session> allSessions(@Context SecurityContext securityContext) throws Exception { requestCount.inc();... } @GET @Path("/{sessionId}") @Produces(MediaType.APPLICATION_JSON) @Timed(6) public Response retrieveSession(@PathParam("sessionId") final String sessionId) throws Exception { requestCount.inc();... } @GET @Path("/{sessionId}/speakers") @Produces(MediaType.APPLICATION_JSON) @Timed public Response sessionSpeakers(@PathParam("sessionId") final String sessionId) throws Exception { ... }
1 | Define a Counter type metric named requestCount. |
2 | Injection of the MetricRegistry interface allows for programmatic creation and lookup of metrics as will be done in init(). |
3 | Sets up the metadata for an abstractWordCount metric of type Histogram. |
4 | The actual creation of the Histogram metric via the injected MetricRegistry instance. |
5 | Population of the abstractWordCount from the various session abstracts. |
6 | The allSessions, retrieveSession and sessionSpeakers endpoint methods are annotated with @Timed to indicate that the MP-Metrics layer should intercept the method invocations and create statistics for them. |
7 | Programmatic updates of the injected requestCount metric are seen in the allSessions and retrieveSession endpoint methods. |