{"id":71,"date":"2019-04-08T17:35:24","date_gmt":"2019-04-08T17:35:24","guid":{"rendered":"http:\/\/boernig.de\/wordpress\/?p=71"},"modified":"2019-04-08T17:35:24","modified_gmt":"2019-04-08T17:35:24","slug":"quarkus-openshift-quickstart-a-bonuslab-for-the-cloud-native-developer-workshop","status":"publish","type":"post","link":"http:\/\/boernig.de\/wordpress\/2019\/04\/08\/quarkus-openshift-quickstart-a-bonuslab-for-the-cloud-native-developer-workshop\/","title":{"rendered":"Quarkus OpenShift Quickstart &#8211; A Bonuslab for the Cloud Native Developer Workshop"},"content":{"rendered":"<p>Quarkus (<a href=\"https:\/\/quarkus.io\/\">https:\/\/quarkus.io\/<\/a>) &#8211; A Supersonic Subatomic Java has recently been announced from some Red Hat Developers. The idea is to build a Java runtime that is native to Kubernetes and small and fast so it can also used in serverless scenarios or on very small edge devices.<\/p>\n<p>With Quarkus it is possible to create a &#8220;native&#8221; build that produces a small self-contained binary that can run in any container image.<\/p>\n<p>The goal of this blog post is to provide a simple hands-on lab for Openshift to get started with Quarkus and to do something useful.<\/p>\n<p>I must admit that I shamelessly build upon work done by my great colleagues, namely from Karsten Gresch from this post\u00a0<a href=\"https:\/\/medium.com\/@gresch\/quarkus-native-builds-with-openshift-s2i-9474ed4386a1\">https:\/\/medium.com\/@gresch\/quarkus-native-builds-with-openshift-s2i-9474ed4386a1<\/a><\/p>\n<p>The source code and the application code I got from\u00a0 \u00a0<a href=\"http:\/\/www.mastertheboss.com\/soa-cloud\/quarkus\/getting-started-with-quarkus\">http:\/\/www.mastertheboss.com\/soa-cloud\/quarkus\/getting-started-with-quarkus<\/a><\/p>\n<p>Ok, lets get our hands dirty with it!<\/p>\n<h2>Importing the source code<\/h2>\n<p>I put the code in my github repository &#8220;quarkus-demo&#8221;:\u00a0<a href=\"https:\/\/github.com\/iboernig\/quarkus-demo\">https:\/\/github.com\/iboernig\/quarkus-demo<\/a><\/p>\n<p>If you are working with the Eclipse Che based cloud native workshop, make sure you are in the \/projects\/labs\/ directory:<\/p>\n<pre>$ cd \/projects\/labs<\/pre>\n<p>Then clone the git repository:<\/p>\n<pre>$ git clone\u00a0\u00a0https:\/\/github.com\/iboernig\/quarkus-demo.git<\/pre>\n<p>The quarkus-demo directory should now show up in the project explorer on the left.<\/p>\n<p>Here you can left-click on that repository and convert to a maven project (same as for the other projects). This is not a necessary step, since we will not use the local maven but will use OpenShift s2i to build and deploy the application.<\/p>\n<h2>Examining the source code<\/h2>\n<p>In the src\/main\/java\/org.acme.quickstart directory we at first the simple REST Endpoint example. It contains the class GreetingResource.java:<\/p>\n<pre>package org.acme.quickstart;\r\n\r\n\r\nimport javax.inject.Inject;\r\nimport javax.ws.rs.GET;\r\nimport javax.ws.rs.Path;\r\nimport javax.ws.rs.PathParam;\r\nimport javax.ws.rs.Produces;\r\nimport javax.ws.rs.core.MediaType;\r\n\r\n@Path(\"\/hello\")\r\npublic class GreetingResource {\r\n\r\n    @Inject\r\n    GreetingService service;\r\n\r\n    @GET\r\n    @Produces(MediaType.TEXT_PLAIN)\r\n    @Path(\"\/greeting\/{name}\")\r\n    public String greeting(@PathParam(\"name\") String name) {\r\n        return service.greeting(name);\r\n    }\r\n\r\n    @GET\r\n    @Produces(MediaType.TEXT_PLAIN)\r\n    public String hello() {\r\n        return \"hello\";\r\n    }\r\n}<\/pre>\n<p>and the simple GreetingService.java:<\/p>\n<pre>package org.acme.quickstart;\r\n\r\nimport javax.enterprise.context.ApplicationScoped;\r\n\r\n@ApplicationScoped\r\npublic class GreetingService {\r\n\r\n   public String greeting(String name) {\r\n      return \"hello \" + name;\r\n   }\r\n}<\/pre>\n<p>This will produce a simple &#8220;hello&#8221; output on the \/hello URL and a short greeting with a name on \/hello\/greeting\/&lt;name&gt; .<\/p>\n<h2>Something useful<\/h2>\n<p>But now we look at a little more interesting example. Lets do a simple user registration. Something that is missing in our Coolstuff store.<\/p>\n<p>Lets start with the Class Person.java:<\/p>\n<pre>package com.sample;\r\n \r\nimport java.util.Objects;\r\n \r\npublic class Person {\r\n \r\n    String name;\r\n    String surname;\r\n \r\n    public Person( ) {  }\r\n \r\n    public String getName() {\r\n        return name;\r\n    }\r\n    public String getSurname() {  return surname;  }\r\n    public void setName(String name) {\r\n        this.name = name;\r\n    }\r\n    public void setSurname(String surname) {\r\n        this.surname = surname;\r\n    }\r\n    @Override\r\n    public String toString() {\r\n        return \"Person{\" +\r\n                \"name='\" + name + '\\'' +\r\n                \", surname='\" + surname + '\\'' +\r\n                '}';\r\n    }\r\n \r\n    @Override\r\n    public boolean equals(Object o) {\r\n        if (this == o) return true;\r\n        if (o == null || getClass() != o.getClass()) return false;\r\n        Person person = (Person) o;\r\n        return Objects.equals(name, person.name) &amp;&amp;\r\n                Objects.equals(surname, person.surname);\r\n    }\r\n \r\n    @Override\r\n    public int hashCode() {\r\n        return Objects.hash(name, surname);\r\n    }\r\n}<\/pre>\n<p>And an endpoint to safe this into a collection. RESTEndpoint.java:<\/p>\n<pre>package com.sample;\r\n \r\nimport java.util.Collections;\r\nimport java.util.LinkedHashMap;\r\nimport java.util.Set;\r\n \r\nimport javax.ws.rs.Consumes;\r\nimport javax.ws.rs.DELETE;\r\nimport javax.ws.rs.GET;\r\nimport javax.ws.rs.POST;\r\nimport javax.ws.rs.Path;\r\nimport javax.ws.rs.Produces;\r\nimport javax.ws.rs.core.MediaType;\r\n \r\n@Path(\"\/persons\")\r\n@Produces(MediaType.APPLICATION_JSON)\r\n@Consumes(MediaType.APPLICATION_JSON)\r\npublic class RESTEndpoint {\r\n \r\n    private Set&lt;Person&gt; persons = Collections.newSetFromMap(Collections.synchronizedMap(new LinkedHashMap&lt;&gt;()));\r\n \r\n    @GET\r\n    public Set&lt;Person&gt; list() {\r\n        return persons;\r\n    }\r\n \r\n    @POST\r\n    public Set&lt;Person&gt; add(Person person) {\r\n        System.out.println(\"Saving: \" +person);\r\n        persons.add(person);\r\n        return persons;\r\n    }\r\n \r\n}<\/pre>\n<p>Note: The files are already in the repository. This is just for a review.<\/p>\n<h2>\u00a0Adding an UI<\/h2>\n<p>Adding HTML UI is also quite easy with quarkus. Lets turn to the src\/main\/resources\/META-INF\/resources\/ directory. Here you can find the following HTML code (with AngularJS for displaying the data) in the index.html:<\/p>\n<pre>\u00a0&lt;!doctype html&gt;\r\n&lt;html&gt;\r\n&lt;head&gt;\r\n    &lt;meta charset=\"utf-8\"\/&gt;\r\n    &lt;title&gt;Quarkus REST service&lt;\/title&gt;\r\n    &lt;link rel=\"stylesheet\" href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/wingcss\/0.1.8\/wing.min.css\"\/&gt;\r\n    &lt;!-- Load AngularJS --&gt;\r\n    &lt;script src=\"\/\/ajax.googleapis.com\/ajax\/libs\/angularjs\/1.4.8\/angular.min.js\"&gt;&lt;\/script&gt;\r\n    &lt;script type=\"text\/javascript\"&gt;\r\n      var app = angular.module(\"PersonManagement\", []);\r\n \r\n      \/\/Controller Part\r\n      app.controller(\"PersonManagementController\", function ($scope, $http) {\r\n \r\n        \/\/Initialize page with empty data\r\n        $scope.persons = [];\r\n \r\n        $scope.form = {\r\n          name: \"\",\r\n          surname: \"\"\r\n        };\r\n \r\n        \/\/Now load the data from server\r\n        _refreshPageData();\r\n \r\n        \/\/HTTP POST methods for add data\r\n        $scope.add = function () {\r\n          var data = { \"name\": $scope.form.name, \"surname\": $scope.form.surname };\r\n \r\n          $http({\r\n            method: \"POST\",\r\n            url: '\/persons',\r\n            data: angular.toJson(data),\r\n            headers: {\r\n              'Content-Type': 'application\/json'\r\n            }\r\n          }).then(_success, _error);\r\n        };\r\n \r\n \r\n        \/\/HTTP GET- get all persons collection\r\n        function _refreshPageData() {\r\n          $http({\r\n            method: 'GET',\r\n            url: '\/persons'\r\n          }).then(function successCallback(response) {\r\n            $scope.persons = response.data;\r\n          }, function errorCallback(response) {\r\n            console.log(response.statusText);\r\n          });\r\n        }\r\n \r\n        function _success(response) {\r\n          _refreshPageData();\r\n          _clearForm();\r\n        }\r\n \r\n        function _error(response) {\r\n          alert(response.data.message || response.statusText);\r\n        }\r\n \r\n        \/\/Clear the form\r\n        function _clearForm() {\r\n          $scope.form.name = \"\";\r\n          $scope.form.surname = \"\";\r\n        }\r\n      });\r\n    &lt;\/script&gt;\r\n&lt;\/head&gt;\r\n&lt;body ng-app=\"PersonManagement\" ng-controller=\"PersonManagementController\"&gt;\r\n \r\n&lt;div class=\"container\"&gt;\r\n    &lt;h1&gt;Quarkus REST Service&lt;\/h1&gt;\r\n \r\n    &lt;form ng-submit=\"add()\"&gt;\r\n        &lt;div class=\"row\"&gt;\r\n            &lt;div class=\"col-6\"&gt;&lt;input type=\"text\" placeholder=\"Name\" ng-model=\"form.name\" size=\"60\"\/&gt;&lt;\/div&gt;\r\n        &lt;\/div&gt;\r\n        &lt;div class=\"row\"&gt;\r\n            &lt;div class=\"col-6\"&gt;&lt;input type=\"text\" placeholder=\"Surname\" ng-model=\"form.surname\" size=\"60\"\/&gt;&lt;\/div&gt;\r\n        &lt;\/div&gt;\r\n        &lt;input type=\"submit\" value=\"Save\"\/&gt;\r\n    &lt;\/form&gt;\r\n \r\n    &lt;h3&gt;Person List&lt;\/h3&gt;\r\n    &lt;div class=\"row\"&gt;\r\n        &lt;div class=\"col-4\"&gt;Name&lt;\/div&gt;\r\n        &lt;div class=\"col-8\"&gt;Surname&lt;\/div&gt;\r\n    &lt;\/div&gt;\r\n    &lt;div class=\"row\" ng-repeat=\"person in persons\"&gt;\r\n        &lt;div class=\"col-4\"&gt;{{ person.name }}&lt;\/div&gt;\r\n        &lt;div class=\"col-8\"&gt;{{ person.surname }}&lt;\/div&gt;\r\n    &lt;\/div&gt;\r\n&lt;\/div&gt;\r\n \r\n&lt;\/body&gt;\r\n&lt;\/html&gt;<\/pre>\n<p>Now its time to get it running!<\/p>\n<h2>Let OpenShift do its work!<\/h2>\n<p>First of all, make sure that you are still logged in with the right user into OpenShift:<\/p>\n<pre>$ oc whoami\r\nuser1<\/pre>\n<p>Now check that you are in the right project<\/p>\n<pre>$ oc project\r\ncoolstore-01<\/pre>\n<p>(If you trying this outside the cloud native workshop, simple make sure that you have a project and are logged in into OpenShift).<\/p>\n<p>The Quarkus S2i (Source-to-image) builder image is not yet a part of OpenShift by default, but it is available as a builder image on quay.io and needs to be called directly:<\/p>\n<pre>$ oc new-app quay.io\/quarkus\/centos-quarkus-native-s2i~https:\/\/github.com\/iboernig\/quarkus-demo.git --name=quarkdemo<\/pre>\n<p>Now OpenShift starts a build process and you can follow the progress here:<\/p>\n<pre>$ oc logs -f bc\/quarkdemo<\/pre>\n<p>This is progressing quite slow and in the normal workshop scenario it finally stops and fails. Can you tell why?<\/p>\n<p>Compiling to native needs a lot of resources and each project has limits. Either increase the limit yourself (if you are a cluster-admin) or talk to you instructor. He can help with increasing or removing the limit.<\/p>\n<p>Ok, lets go for the next try. Restart the build:<\/p>\n<pre>$ oc start-build bc\/quarkdemo<\/pre>\n<p>Now it progresses much faster and the ready built image is pushed to the local registry.<\/p>\n<p>look for the upcoming pods:<\/p>\n<pre>$ oc get pods \r\nNAME                READY     STATUS      RESTARTS   AGE\r\nquarkdemo-1-626xd   1\/1       Running     0          2s\r\nquarkdemo-1-build   0\/1       OOMKilled   0          5m\r\nquarkdemo-2-build   0\/1       Completed   0          5s<\/pre>\n<p>Its running! And you also can see, why the first build failed!<\/p>\n<p>Now expose the service and get a route:<\/p>\n<pre>$ oc expose svc quarkdemo\r\nroute.route.openshift.io\/quarkdemo exposed\r\n$ oc get routes\r\nNAME        HOST\/PORT                                                   PATH      SERVICES    PORT       TERMINATION   WILDCARD\r\nquarkdemo   quarkdemo-&lt;project&gt;-&lt;serverurl&gt;   quarkdemo  8080-tcp                 None<\/pre>\n<p>Now you can look at the URL in your browser:<\/p>\n<p><img loading=\"lazy\" class=\"aligncenter size-full wp-image-74\" src=\"http:\/\/boernig.de\/wordpress\/wp-content\/uploads\/2019\/04\/Screenshot-from-2019-04-08-19-15-53.png\" alt=\"\" width=\"851\" height=\"506\" srcset=\"http:\/\/192.168.178.2\/wordpress\/wp-content\/uploads\/2019\/04\/Screenshot-from-2019-04-08-19-15-53.png 851w, http:\/\/192.168.178.2\/wordpress\/wp-content\/uploads\/2019\/04\/Screenshot-from-2019-04-08-19-15-53-300x178.png 300w, http:\/\/192.168.178.2\/wordpress\/wp-content\/uploads\/2019\/04\/Screenshot-from-2019-04-08-19-15-53-768x457.png 768w\" sizes=\"(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/p>\n<p>You can add names ad see what happens!<\/p>\n<p>Also the simple rest service works with this binary:<\/p>\n<pre>$ curl http:\/\/&lt;route&gt;\/hello\r\nhello\r\n$ curl http:\/\/&lt;route&gt;\/hello\/greeting\/ingo\r\nhello ingo<\/pre>\n<p>Very nice. But what makes Quarkus so special?<\/p>\n<p><img loading=\"lazy\" class=\"aligncenter size-large wp-image-75\" src=\"http:\/\/boernig.de\/wordpress\/wp-content\/uploads\/2019\/04\/Screenshot-from-2019-04-08-19-21-57-1024x213.png\" alt=\"\" width=\"840\" height=\"175\" srcset=\"http:\/\/192.168.178.2\/wordpress\/wp-content\/uploads\/2019\/04\/Screenshot-from-2019-04-08-19-21-57-1024x213.png 1024w, http:\/\/192.168.178.2\/wordpress\/wp-content\/uploads\/2019\/04\/Screenshot-from-2019-04-08-19-21-57-300x62.png 300w, http:\/\/192.168.178.2\/wordpress\/wp-content\/uploads\/2019\/04\/Screenshot-from-2019-04-08-19-21-57-768x160.png 768w, http:\/\/192.168.178.2\/wordpress\/wp-content\/uploads\/2019\/04\/Screenshot-from-2019-04-08-19-21-57-1200x250.png 1200w, http:\/\/192.168.178.2\/wordpress\/wp-content\/uploads\/2019\/04\/Screenshot-from-2019-04-08-19-21-57.png 1672w\" sizes=\"(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/p>\n<p>Its very small. Only 19 MB. And it doesn&#8217;t use a JVM to run: its just a simple binary:<\/p>\n<pre>$ oc get pods\r\nNAME                READY     STATUS      RESTARTS   AGE\r\nquarkdemo-1-626xd   1\/1       Running     0          2h\r\nquarkdemo-1-build   0\/1       OOMKilled   0          3h\r\nquarkdemo-2-build   0\/1       Completed   0          2h\r\n$ oc rsh quarkdemo-1-626xd\r\nsh-4.2$ ls -lh\r\ntotal 22M\r\n-rwxr-xr-x. 1 quarkus    root 22M Apr  8 14:38 quarkus-quickstart-1.0-SNAPSHOT-runner\r\n-rw-r--r--. 1 1000660000 root 337 Apr  8 14:39 quarkus.log<\/pre>\n<p>All in one 22 MB binary. Also impressive:<\/p>\n<pre>sh-4.2$ cat quarkus.log \r\n2019-04-08 14:39:02,410 quarkdemo-1-626xd quarkus-quickstart-1.0-SNAPSHOT-runner[7] INFO  [io.quarkus] (main) Quarkus 0.12.0 started in 0.009s. Listening on: http:\/\/[::]:8080\r\n2019-04-08 14:39:02,410 quarkdemo-1-626xd quarkus-quickstart-1.0-SNAPSHOT-runner[7] INFO  [io.quarkus] (main) Installed features: [cdi, resteasy, resteasy-jsonb]<\/pre>\n<p>That&#8217;s a startup time in the ms range! So it makes difference!\u00a0 Hope you enjoyed that lab!<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Quarkus (https:\/\/quarkus.io\/) &#8211; A Supersonic Subatomic Java has recently been announced from some Red Hat Developers. The idea is to build a Java runtime that is native to Kubernetes and small and fast so it can also used in serverless scenarios or on very small edge devices. With Quarkus it is possible to create a &hellip; <a href=\"http:\/\/boernig.de\/wordpress\/2019\/04\/08\/quarkus-openshift-quickstart-a-bonuslab-for-the-cloud-native-developer-workshop\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Quarkus OpenShift Quickstart &#8211; A Bonuslab for the Cloud Native Developer Workshop&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[8,12,5,4,10,13],"tags":[],"_links":{"self":[{"href":"http:\/\/boernig.de\/wordpress\/wp-json\/wp\/v2\/posts\/71"}],"collection":[{"href":"http:\/\/boernig.de\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/boernig.de\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/boernig.de\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/boernig.de\/wordpress\/wp-json\/wp\/v2\/comments?post=71"}],"version-history":[{"count":3,"href":"http:\/\/boernig.de\/wordpress\/wp-json\/wp\/v2\/posts\/71\/revisions"}],"predecessor-version":[{"id":76,"href":"http:\/\/boernig.de\/wordpress\/wp-json\/wp\/v2\/posts\/71\/revisions\/76"}],"wp:attachment":[{"href":"http:\/\/boernig.de\/wordpress\/wp-json\/wp\/v2\/media?parent=71"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/boernig.de\/wordpress\/wp-json\/wp\/v2\/categories?post=71"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/boernig.de\/wordpress\/wp-json\/wp\/v2\/tags?post=71"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}