├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── spring ├── src │ └── main │ │ ├── resources │ │ └── application.yml │ │ └── java │ │ ├── io │ │ └── k8spatterns │ │ │ └── examples │ │ │ ├── IoUtils.java │ │ │ ├── HealthToggleIndicator.java │ │ │ └── RandomGeneratorApplication.java │ │ └── RandomRunner.java ├── Dockerfile └── pom.xml ├── LICENSE ├── .gitignore ├── Dockerfile ├── pom.xml └── README.adoc /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k8spatterns/random-generator/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /spring/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | # Overall version 2 | version: @image.tag@ 3 | 4 | # Always show details for the health checks 5 | management: 6 | endpoint: 7 | health: 8 | show-details: always -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This work is licensed under the Creative Commons Attribution 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /spring/src/main/java/io/k8spatterns/examples/IoUtils.java: -------------------------------------------------------------------------------- 1 | package io.k8spatterns.examples; 2 | 3 | import java.io.FileOutputStream; 4 | import java.io.IOException; 5 | import java.nio.channels.FileLock; 6 | 7 | // Some share utility methods 8 | public class IoUtils { 9 | 10 | 11 | // Append a line to a given file with locking 12 | public static void appendLineWithLocking(String file, String line) throws IOException { 13 | FileOutputStream out = new FileOutputStream(file, true); 14 | try { 15 | FileLock lock = out.getChannel().lock(); 16 | try { 17 | out.write(line.getBytes()); 18 | } finally { 19 | lock.release(); 20 | } 21 | } finally { 22 | out.close(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Multistage Dockerfile for building random-generator 2 | FROM eclipse-temurin:17-jdk as BUILD 3 | ARG VERSION=v1 4 | 5 | RUN apt-get update -y \ 6 | && apt-get install -y maven 7 | 8 | # Copy over source into the container 9 | COPY spring /opt/random-generator/spring 10 | COPY pom.xml /opt/random-generator 11 | 12 | WORKDIR /opt/random-generator 13 | # Build jar files 14 | RUN mvn install -P ${VERSION} -f spring/pom.xml 15 | 16 | # -------------------------------- 17 | # Runtime image 18 | FROM eclipse-temurin:17-jre 19 | 20 | # Copy over artifacts 21 | COPY --from=BUILD /opt/random-generator/spring/target/random-generator*jar /opt/random-generator.jar 22 | COPY --from=BUILD /opt/random-generator/spring/target/classes/RandomRunner.class /opt 23 | COPY random-generator-runner /usr/local/bin 24 | RUN chmod a+x /usr/local/bin/random-generator-runner 25 | 26 | # Setup env 27 | WORKDIR /opt 28 | EXPOSE 8080 29 | 30 | # We need root for some examples that write to PVs. However, if possible 31 | # you should avoid this 32 | USER 0 33 | 34 | # Execute jar file 35 | ENTRYPOINT [ "java", "-jar", "/opt/random-generator.jar" ] 36 | -------------------------------------------------------------------------------- /spring/Dockerfile: -------------------------------------------------------------------------------- 1 | # Super simple Dockerfile for starting up our app. 2 | # All configuration comes from the outside (beside the default values in application.yaml) 3 | FROM openjdk:11 4 | 5 | # Port to access the random service 6 | EXPOSE 8080 7 | 8 | # Install dig for network checks 9 | RUN apt-get update \ 10 | && apt-get install -y dnsutils jq \ 11 | && apt-get clean 12 | 13 | # Fat jar are copied over. Ignore version number. 14 | COPY target/random-generator*jar /opt/random-generator.jar 15 | 16 | # This runner is supposed to be called with java RandomRunner for calling the batch mode 17 | COPY target/classes/RandomRunner.class /opt/ 18 | 19 | # Make it running on OpenShift, too 20 | RUN chown -R 451900 /opt \ 21 | && chgrp -R 0 /opt \ 22 | && chmod -R g=u /opt 23 | 24 | # Arbitrary user id. This does currently not work with every example. 25 | # We are trying to get this running, but especially in combination with 26 | # persitent volumes (like the _Stateful Service_ example), there are issues. 27 | # So we leave it better to user root for now. 28 | # USER 451900 29 | 30 | # Fire up Spring Boot's fat jar 31 | WORKDIR /opt 32 | 33 | # Don't use shell form, or you will have issue with shuttind down on SIGTERM as 34 | # this is not propagated to the JVM process 35 | CMD [ "java", "-jar", "random-generator.jar" ] 36 | -------------------------------------------------------------------------------- /spring/src/main/java/io/k8spatterns/examples/HealthToggleIndicator.java: -------------------------------------------------------------------------------- 1 | package io.k8spatterns.examples; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.boot.actuate.health.Health; 5 | import org.springframework.boot.actuate.health.HealthIndicator; 6 | import org.springframework.stereotype.Component; 7 | 8 | // Simple toggle to switch on/off the health check 9 | @Component 10 | public class HealthToggleIndicator implements HealthIndicator { 11 | 12 | @Value("${healthy:true}") 13 | private boolean healthy; 14 | 15 | @Override 16 | public Health health() { 17 | Health.Builder builder = healthy ? Health.up() : Health.down(); 18 | Runtime r = Runtime.getRuntime(); 19 | return builder 20 | .withDetail("toggle", healthy) 21 | .withDetail("usedMemory", format(r.totalMemory() - r.freeMemory())) 22 | .withDetail("totalMemory", format(r.totalMemory())) 23 | .withDetail("maxMemory", format(r.maxMemory())) 24 | .withDetail("freeMemory", format(r.freeMemory())) 25 | .withDetail("availableProcessors", r.availableProcessors()) 26 | .build(); 27 | } 28 | 29 | public void toggle() { 30 | this.healthy = !this.healthy; 31 | } 32 | 33 | public void setHealthy(boolean healthy) { 34 | this.healthy = healthy; 35 | } 36 | 37 | public static String format(long v) { 38 | if (v < 1024) return v + " B"; 39 | int z = (63 - Long.numberOfLeadingZeros(v)) / 10; 40 | return String.format("%.1f %sB", 41 | (double)v / (1L << (z*10)), 42 | " KMGTPE".charAt(z)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | examples.k8spatterns.io 7 | random-generator-parent 8 | 1.0 9 | pom 10 | 11 | ${project.artifactId} 12 | Random Generator :: Parent 13 | 14 | 15 | UTF-8 16 | UTF-8 17 | 1.8 18 | k8spatterns 19 | ${project.version} 20 | 21 | ${image.repo}/random-generator:${image.tag}${image.tag.extension} 22 | true 23 | 0.42.0 24 | 25 | 26 | 27 | spring 28 | 29 | 30 | 31 | 32 | 33 | maven-compiler-plugin 34 | 35 | 1.8 36 | 1.8 37 | -Xlint:deprecation 38 | true 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | v2 47 | 48 | 2.0 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /spring/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | random-generator-spring 7 | jar 8 | 9 | Random Generator :: Spring 10 | 11 | 2.7.9 12 | -spring 13 | 14 | 15 | 16 | examples.k8spatterns.io 17 | random-generator-parent 18 | 1.0 19 | .. 20 | 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-web 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-actuator 31 | 32 | 33 | 34 | 35 | 36 | 37 | src/main/resources 38 | true 39 | 40 | 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-maven-plugin 46 | ${version.spring-boot} 47 | 48 | 49 | 50 | repackage 51 | 52 | 53 | 54 | 55 | 56 | 57 | io.fabric8 58 | docker-maven-plugin 59 | ${version.docker-maven-plugin} 60 | 61 | 62 | 63 | ${docker.name} 64 | 65 | ${project.basedir} 66 | 67 | ${image.tag} 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | org.springframework.boot 82 | spring-boot-dependencies 83 | ${version.spring-boot} 84 | pom 85 | import 86 | 87 | 88 | 89 | 90 | 91 | 92 | v1 93 | 94 | 1.0 95 | 96 | 97 | 98 | v2 99 | 100 | 2.0 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /spring/src/main/java/RandomRunner.java: -------------------------------------------------------------------------------- 1 | import java.io.FileOutputStream; 2 | import java.io.IOException; 3 | import java.nio.channels.FileLock; 4 | import java.time.Instant; 5 | import java.time.ZoneOffset; 6 | import java.time.format.DateTimeFormatter; 7 | import java.util.Random; 8 | import java.util.UUID; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | /** 12 | * Simple Batch runner for creating a certain amount of entries in a log 13 | * holding log files. This is a standalone class used in the Batch Job and Scheduled Job patterns 14 | */ 15 | public class RandomRunner { 16 | 17 | // Simples possible way to create a random number. 18 | private static Random random = new Random(); 19 | 20 | // Simple UUID to identify this server instance 21 | private static final UUID id = UUID.randomUUID(); 22 | 23 | public static void main(String[] args) throws IOException { 24 | if (args.length > 5) { 25 | System.err.println("Usage: java -jar ... " + 26 | RandomRunner.class.getCanonicalName() + 27 | " [--seed ] [path-to-file] [number-lines-to-create] [seconds-to-sleep]"); 28 | System.exit(1); 29 | } 30 | int idx = 0; 31 | if (idx < args.length && "--seed".equalsIgnoreCase(args[idx])) { 32 | long seed = Long.parseLong(args[++idx]); 33 | random = new Random(seed); 34 | idx++; 35 | } 36 | String file = idx < args.length ? args[idx++] : "-"; 37 | int nrLines = Integer.parseInt(idx < args.length ? args[idx++] : "10000"); 38 | int sleep = 0; 39 | if (idx < args.length) { 40 | sleep = Integer.parseInt(args[idx]); 41 | } 42 | while (true) { 43 | long overallStart = System.nanoTime(); 44 | System.out.println("Starting to create " + nrLines + " random numbers"); 45 | for (int i = 0; i < nrLines; i++) { 46 | long start = System.nanoTime(); 47 | String date = DateTimeFormatter.ofPattern("HH:mm:ss.SSS") 48 | .withZone(ZoneOffset.UTC) 49 | .format(Instant.now()); 50 | int randomValue = random.nextInt(); 51 | String line = date + "," + id + "," + (System.nanoTime() - start) + "," + randomValue + "\n"; 52 | 53 | appendLineWithLocking(file, line); 54 | } 55 | System.out.println("Finished after " + ((System.nanoTime() - overallStart) / 1_000_000) + " ms"); 56 | if (sleep == 0) { 57 | return; 58 | } 59 | try { 60 | TimeUnit.SECONDS.sleep(sleep); 61 | } catch (InterruptedException e) { 62 | System.out.println("Thread interrupted, exiting"); 63 | System.exit(1); 64 | } 65 | } 66 | } 67 | 68 | private static void appendLineWithLocking(String file, String line) throws IOException { 69 | if ("-".equalsIgnoreCase(file)) { 70 | System.out.print(line); 71 | return; 72 | } 73 | try (FileOutputStream out = new FileOutputStream(file, true)) { 74 | FileLock lock = out.getChannel().lock(); 75 | try { 76 | out.write(line.getBytes()); 77 | } finally { 78 | lock.release(); 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | ## Random Number Generator Service 2 | 3 | This repository holds a sample REST service used in most of the https://github.com/k8spatterns/examples/[examples] for the book "Kubernetes Patterns" by https://github.com/bibryam[Bilgin Ibryam] and https://github.com/rhuss[Roland Huß]. 4 | 5 | It's a simple Spring Boot service with a handful of endpoints used to demonstrate Kubernetes concepts. 6 | 7 | You can start it locally with with `./mvnw -pl spring spring-boot:run` footnote:[Instead of the Maven wrapper `mvnw` you can also use a locally installed `mvn`] and get a random number: 8 | 9 | [source, bash] 10 | ---- 11 | curl -sL http://localhost:8080/ | jq . 12 | { 13 | "random": 838265052, 14 | "id": "58da4c5b-ba06-438e-84f8-1a30581baeb8" 15 | } 16 | ---- 17 | 18 | In order to create a Docker image, just use `./mvnw -pl spring package docker:build`. 19 | You can easly change the Docker repository and push it to your own account with `-Dimage.repo`: 20 | 21 | [source, bash] 22 | ---- 23 | ./mvnw -Dimage.repo="rhuss" package docker:build push 24 | ---- 25 | 26 | ### Configuration 27 | 28 | [cols="1,1,5,2", options="header"] 29 | |=== 30 | | Property | Env Var | Description | Pattern 31 | 32 | | version 33 | | --- 34 | | Version of this application, created during build 35 | | Declarative Deployment 36 | 37 | | log.file 38 | | LOG_FILE 39 | | Path to a log file. If set, the write out the generated response values into a CSV file 40 | | many 41 | 42 | | log.url 43 | | LOG_URL 44 | | If given, send out an HTTP request with the results of a random number generation 45 | | Ambassador 46 | 47 | | build.type 48 | | BUILD_TYPE 49 | | Add some informations about how the image has been built 50 | | Image Builder 51 | 52 | | pattern 53 | | PATTERN 54 | | Descriptive label used in environment variables 55 | | Envvar Configuration, Configuration Resource 56 | 57 | | seed 58 | | SEED 59 | | Seed number to initialize the random number generator. Must be a long. 60 | | Configuration Resource 61 | 62 | |=== 63 | 64 | ### Endpoints 65 | 66 | [cols="1,5,2", options="header"] 67 | |=== 68 | | Endpoint | Description | Pattern 69 | 70 | | / 71 | | Return a random number and a unique id of this service 72 | | many 73 | 74 | | /info 75 | | Return information about configuration and system 76 | | many 77 | 78 | | /memory-eater 79 | | Allocate an array for eating up heap memory. Use parameter "mb" to specify the size in MegaByte 80 | | Predictable Demands 81 | 82 | | /toggle-live 83 | | Toggle the status of the Sprint Boot's actuator health check between `UP` and `DOWN` 84 | | Health Probe 85 | 86 | | /toggle-ready 87 | | Add or remove the local file which indicates readiness of this service 88 | | Health Probe 89 | 90 | | /shutdown 91 | | Shutdown the Spring Boot service 92 | | Managed Lifecycle 93 | 94 | | /logs 95 | | Return the content of the logs 96 | | Stateful Service, Init Container 97 | |=== 98 | 99 | ### Batch 100 | 101 | There is also a single class `RandomRunner` included which creates a list of random numbers into a file: 102 | 103 | [source, bash] 104 | ---- 105 | # Compile stuff first 106 | ./mvnw -pl spring compile 107 | 108 | # Create a file with 100000 lines in /tmp/random_numbers.txt 109 | java -cp target/classes RandomRunner /tmp/random_numbers.txt 100000 110 | 111 | # Write 100000 lines to /dev/random_seed, wait 30s, and then repeat endlessly. 112 | java -c target/classes RandomRunner /dev/random_seed 100000 30 113 | ---- 114 | 115 | This command is used in the _Batch Job_ and _Periodic Job_ pattern example. 116 | The last example is used in _Daemon Service_ pattern example. 117 | 118 | ### Building 119 | 120 | Refer to the _Image Builder_ pattern how this example is build from within a Kubernetes cluster. 121 | 122 | For creating a tag `2.0` just use the profile `-Pv2`. This is used for creating images to be used the _Declarative Deployment_ pattern. 123 | 124 | ### Crossbuild the images 125 | 126 | In order to compile the images for multiple architectures, use: 127 | 128 | [source, bash] 129 | ---- 130 | docker buildx create --use 131 | docker buildx build --build-arg VERSION="v1" --platform linux/arm64/v8,linux/amd64 -t k8spatterns/random-generator:1.0 . 132 | docker buildx build --build-arg VERSION="v2" --platform linux/arm64/v8,linux/amd64 -t k8spatterns/random-generator:2.0 . 133 | ---- 134 | -------------------------------------------------------------------------------- /spring/src/main/java/io/k8spatterns/examples/RandomGeneratorApplication.java: -------------------------------------------------------------------------------- 1 | package io.k8spatterns.examples; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.io.OutputStream; 7 | import java.net.HttpURLConnection; 8 | import java.net.URL; 9 | import java.nio.file.Files; 10 | import java.nio.file.Paths; 11 | import java.time.Instant; 12 | import java.time.ZoneOffset; 13 | import java.time.format.DateTimeFormatter; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | import java.util.Random; 17 | import java.util.UUID; 18 | 19 | import org.apache.commons.logging.Log; 20 | import org.apache.commons.logging.LogFactory; 21 | import org.springframework.beans.BeansException; 22 | import org.springframework.beans.factory.annotation.Value; 23 | import org.springframework.boot.SpringApplication; 24 | import org.springframework.boot.autoconfigure.SpringBootApplication; 25 | import org.springframework.boot.context.event.ApplicationReadyEvent; 26 | import org.springframework.context.ApplicationContext; 27 | import org.springframework.context.ApplicationContextAware; 28 | import org.springframework.context.event.EventListener; 29 | import org.springframework.web.bind.annotation.RequestMapping; 30 | import org.springframework.web.bind.annotation.RequestParam; 31 | import org.springframework.web.bind.annotation.RestController; 32 | import sun.misc.Signal; 33 | 34 | @SpringBootApplication 35 | @RestController 36 | public class RandomGeneratorApplication implements ApplicationContextAware { 37 | 38 | // Writable directory for printing out log files and marker filers 39 | private static File WRITE_DIR = new File("/tmp"); 40 | 41 | // File to indicate readiness 42 | private static File READY_FILE = new File(WRITE_DIR,"random-generator-ready"); 43 | 44 | // Simples possible way to create a random number. 45 | // Could be replaced by access to a hardware number generator if 46 | // you want to take this service seriously :) 47 | private static Random random = new Random(); 48 | 49 | // Simple UUID to identify this server instance 50 | private static UUID id = UUID.randomUUID(); 51 | 52 | // Injected from the application properties 53 | @Value("${version}") 54 | private String version; 55 | 56 | // Optional path to store data in (env: LOG_FILE) 57 | @Value("${log.file:#{null}}") 58 | private String logFile; 59 | 60 | // Optional url to log the data to(env: LOG_URL) 61 | @Value("${log.url:#{null}}") 62 | private String logUrl; 63 | 64 | // Build type (env: BUILD_TYPE) 65 | @Value("${build.type:#{null}}") 66 | private String buildType; 67 | 68 | // Pattern name where this service is used 69 | @Value("${pattern:None}") 70 | private String patternName; 71 | 72 | // Another env var for the pattern name 73 | @Value("${random_pattern:None}") 74 | private String randomPatternName; 75 | 76 | // Seed to use for initializing the random number generator 77 | @Value("${seed:0}") 78 | private long seed; 79 | 80 | // Used to allocate memory, testing resource limits 81 | private byte[] memoryHole; 82 | 83 | // Some logging 84 | private static Log log = LogFactory.getLog(RandomGeneratorApplication.class); 85 | 86 | // Health toggle 87 | private HealthToggleIndicator healthToggle; 88 | 89 | // Application context used for shutdown the app 90 | private ApplicationContext applicationContext; 91 | 92 | 93 | public RandomGeneratorApplication(HealthToggleIndicator healthToggle) { 94 | this.healthToggle = healthToggle; 95 | } 96 | 97 | // ====================================================================== 98 | public static void main(String[] args) throws InterruptedException, IOException { 99 | 100 | // Check for a postStart generated file and log if it found one 101 | waitForPostStart(); 102 | 103 | // shutdownHook & signalHandler for dealing with signals 104 | addShutdownHook(); 105 | 106 | // Ensure that the ready flag is not enabled 107 | ready(false); 108 | 109 | // Delay startup if a certain envvar is set 110 | delayIfRequested(); 111 | 112 | SpringApplication.run(RandomGeneratorApplication.class, args); 113 | } 114 | 115 | // Dump out some information 116 | @EventListener(ApplicationReadyEvent.class) 117 | public void dumpInfo() throws IOException { 118 | 119 | Map info = getSysinfo(); 120 | 121 | log.info("=== Max Heap Memory: " + info.get("memory.max") + " MB"); 122 | log.info("=== Used Heap Memory: " + info.get("memory.used") + " MB"); 123 | log.info("=== Free Memory: " + info.get("memory.free") + " MB"); 124 | log.info("=== Processors: " + info.get("cpu.procs")); 125 | } 126 | 127 | 128 | // Indicate, that our application is up and running 129 | @EventListener(ApplicationReadyEvent.class) 130 | public void createReadyFile() { 131 | try { 132 | ready(true); 133 | } catch (IOException exp) { 134 | log.warn("Can't create 'ready' file " + READY_FILE + 135 | " used in readiness check. Possibly running locally, so ignoring it"); 136 | } 137 | } 138 | 139 | // Init seed if configured 140 | @EventListener(ApplicationReadyEvent.class) 141 | public void initSeed() { 142 | if (seed != 0) { 143 | random = new Random(seed); 144 | } 145 | } 146 | 147 | /** 148 | * Endpoint returning a JSON document with a random number. It maps to the root context 149 | * of this web application 150 | * 151 | * @return map which gets convert to JSON when returned to the client 152 | */ 153 | @RequestMapping(value = "/", produces = "application/json") 154 | public Map getRandomNumber(@RequestParam(name = "burn", required = false) Long burn) throws IOException { 155 | long start = System.nanoTime(); 156 | Map ret = new HashMap<>(); 157 | 158 | // Exercise the CPU a bit if requested 159 | burnCpuTimeIfRequested(burn); 160 | 161 | // The very random number. 162 | int randomValue = random.nextInt(); 163 | 164 | // Create return value 165 | ret.put("random", randomValue); 166 | ret.put("id", id.toString()); 167 | ret.put("version", version); 168 | if (patternName != null && !"None".equalsIgnoreCase(patternName)) { 169 | ret.put("pattern", patternName); 170 | } 171 | 172 | // Write out the value to a file 173 | logRandomValue(randomValue, System.nanoTime() - start); 174 | return ret; 175 | } 176 | 177 | /** 178 | * Reserve some memory to simulate memory heap usage 179 | * 180 | * @param megaBytes how many MB to reserve 181 | */ 182 | @RequestMapping(value = "/memory-eater") 183 | public void getEatUpMemory(@RequestParam(name = "mb") int megaBytes) { 184 | memoryHole = new byte[megaBytes * 1024 * 1024]; 185 | random.nextBytes(memoryHole); 186 | } 187 | 188 | /** 189 | * Toggle the overall health of the system 190 | */ 191 | @RequestMapping(value = "/toggle-live") 192 | public void toggleLiveness() { 193 | healthToggle.toggle(); 194 | } 195 | 196 | /** 197 | * Toggle the overall health of the system 198 | */ 199 | @RequestMapping(value = "/toggle-ready") 200 | public void toggleReadiness() throws IOException { 201 | ready(!READY_FILE.exists()); 202 | } 203 | 204 | 205 | /** 206 | * Get some info 207 | * @return overall information including version, and id 208 | */ 209 | @RequestMapping(value = "/info", produces = "application/json") 210 | public Map info() throws IOException { 211 | return getSysinfo(); 212 | } 213 | 214 | /** 215 | * Shutdown called by preStop hook in the Lifecycle Conformance pattern example 216 | * Log and stop the Spring Boot container 217 | */ 218 | @RequestMapping(value = "/shutdown") 219 | public void shutdown() { 220 | log.info("SHUTDOWN NOW"); 221 | SpringApplication.exit(applicationContext); 222 | } 223 | 224 | /** 225 | * Get all log data that has been already written 226 | */ 227 | @RequestMapping(value = "/tmp/logs", produces = "text/plain") 228 | public String logs() throws IOException { 229 | return getLog(); 230 | } 231 | 232 | // ================================================================================================ 233 | // Burn down some CPU time, which can be used to increase the CPU load on 234 | // the system 235 | private void burnCpuTimeIfRequested(Long burn) { 236 | if (burn != null) { 237 | for (int i = 0; i < (burn < 10_000 ? burn : 10_000) * 1_000 * 1_000; i++) { 238 | random.nextInt(); 239 | } 240 | } 241 | } 242 | 243 | // Write out the random value to a given path (if configured) and/or an external URL 244 | private void logRandomValue(int randomValue, long duration) throws IOException { 245 | if (logFile != null) { 246 | logToFile(logFile, randomValue, duration); 247 | } 248 | 249 | if (logUrl != null) { 250 | logToUrl(new URL(logUrl), randomValue, duration); 251 | } 252 | } 253 | 254 | private void logToFile(String file, int randomValue, long duration) throws IOException { 255 | String date = DateTimeFormatter.ofPattern("HH:mm:ss.SSS") 256 | .withZone(ZoneOffset.UTC) 257 | .format(Instant.now()); 258 | String line = date + "," + id + "," + duration + "," + randomValue + "\n"; 259 | IoUtils.appendLineWithLocking(file, line); 260 | } 261 | 262 | private void logToUrl(URL url, int randomValue, long duration) throws IOException { 263 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 264 | connection.setRequestMethod("POST"); 265 | 266 | String output = String.format( 267 | "{ \"id\": \"%s\", \"random\": \"%d\", \"duration\": \"%d\" }", 268 | id, randomValue, duration); 269 | 270 | log.info("Sending log to " + url); 271 | connection.setDoOutput(true); 272 | OutputStream os = connection.getOutputStream(); 273 | os.write(output.getBytes()); 274 | os.flush(); 275 | os.close(); 276 | 277 | int responseCode = connection.getResponseCode(); 278 | log.info("Log delegate response: " + responseCode); 279 | } 280 | 281 | 282 | // If the given environment variable is set, sleep a bit 283 | private static void delayIfRequested() throws InterruptedException { 284 | int sleep = Integer.parseInt(System.getenv().getOrDefault("DELAY_STARTUP", "0")); 285 | if (sleep > 0) { 286 | Thread.sleep(sleep * 1000); 287 | } 288 | } 289 | 290 | // Get the content of the log file 291 | private String getLog() throws IOException { 292 | if (logFile == null) { 293 | return ""; 294 | } 295 | return String.join("\n", Files.readAllLines(Paths.get(logFile))); 296 | } 297 | 298 | // Get some sysinfo 299 | private Map getSysinfo() throws IOException { 300 | Map ret = new HashMap(); 301 | Runtime rt = Runtime.getRuntime(); 302 | int mb = 1024 * 1024; 303 | ret.put("memory.max", rt.maxMemory() / mb); 304 | ret.put("memory.used", rt.totalMemory() / mb); 305 | ret.put("memory.free", rt.freeMemory() / mb); 306 | ret.put("cpu.procs", rt.availableProcessors()); 307 | ret.put("id", id); 308 | ret.put("version", version); 309 | ret.put("pattern", !"None".equals(randomPatternName) ? randomPatternName : patternName); 310 | 311 | if (logFile != null) { 312 | ret.put("logFile", logFile); 313 | } 314 | if (logUrl != null) { 315 | ret.put("logUrl", logUrl); 316 | } 317 | if (seed != 0) { 318 | ret.put("seed", seed); 319 | } 320 | if (buildType != null) { 321 | ret.put("build-type", buildType); 322 | } 323 | 324 | // From Downward API environment 325 | Map env = System.getenv(); 326 | for (String key : new String[] { "POD_IP", "NODE_NAME"}) { 327 | if (env.containsKey(key)) { 328 | ret.put(key, env.get(key)); 329 | } 330 | } 331 | 332 | // From Downward API mount 333 | File podInfoDir = new File("/pod-info"); 334 | if (podInfoDir.exists() && podInfoDir.isDirectory()) { 335 | for (String meta : new String[] { "labels", "annotations"}) { 336 | File file = new File(podInfoDir, meta); 337 | if (file.exists()) { 338 | byte[] encoded = Files.readAllBytes(file.toPath()); 339 | ret.put(file.getName(), new String(encoded)); 340 | } 341 | } 342 | } 343 | 344 | // Add environment 345 | ret.put("env", System.getenv()); 346 | 347 | return ret; 348 | } 349 | 350 | // Set the ready flag file 351 | static private void ready(boolean create) throws IOException { 352 | if (create) { 353 | new FileOutputStream(READY_FILE).close(); 354 | } else { 355 | if (READY_FILE.exists()) { 356 | READY_FILE.delete(); 357 | } 358 | } 359 | } 360 | 361 | // Check for a file which has supposedly be created as part of postStart Hook 362 | private static void waitForPostStart() throws IOException, InterruptedException { 363 | if ("true".equals(System.getenv("WAIT_FOR_POST_START"))) { 364 | File postStartFile = new File(WRITE_DIR, "postStart-done"); 365 | while (!postStartFile.exists()) { 366 | log.info("Waiting for postStart to be finished ...."); 367 | Thread.sleep(10_000); 368 | } 369 | log.info("postStart Message: " + new String(Files.readAllBytes(postStartFile.toPath()))); 370 | } else { 371 | log.info("No WAIT_FOR_POST_START configured"); 372 | } 373 | } 374 | 375 | // Simple shutdown hook to catch some signals. If you have another idea to catch a SIGTERM, please 376 | // let us know by opening an issue in this repo or send in a pull request. 377 | private static void addShutdownHook() { 378 | Runtime.getRuntime().addShutdownHook( 379 | new Thread( 380 | () -> log.info(">>>> SHUTDOWN HOOK called. Possibly because of a SIGTERM from Kubernetes"))); 381 | } 382 | 383 | @Override 384 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 385 | this.applicationContext = applicationContext; 386 | } 387 | } 388 | --------------------------------------------------------------------------------