├── .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 |
--------------------------------------------------------------------------------