Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
696cb74740
|
|||
59f267426c
|
|||
608a9d18de
|
|||
d2c00402df
|
@@ -5,8 +5,6 @@ on:
|
||||
- '*'
|
||||
jobs:
|
||||
build:
|
||||
env:
|
||||
RUNNER_TOOL_CACHE: /toolcache
|
||||
runs-on: hostinger
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
@@ -19,49 +17,53 @@ jobs:
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
- name: Execute Gradle build
|
||||
env:
|
||||
PUBLISHER_TOKEN: ${{ secrets.PUBLISHER_TOKEN }}
|
||||
run: ./gradlew build
|
||||
- name: Publish artifacts
|
||||
env:
|
||||
PUBLISHER_TOKEN: ${{ secrets.PUBLISHER_TOKEN }}
|
||||
run: ./gradlew publish
|
||||
build-docker:
|
||||
name: "Build Docker images"
|
||||
runs-on: hostinger
|
||||
steps:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.4.0
|
||||
- name: Prepare Docker image build
|
||||
run: ./gradlew prepareDockerBuild
|
||||
- name: Get project version
|
||||
id: retrieve-version
|
||||
run: ./gradlew -q version >> "$GITHUB_OUTPUT"
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
driver: docker-container
|
||||
-
|
||||
name: Login to Gitea container registry
|
||||
- name: Login to Gitea container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: gitea.woggioni.net
|
||||
username: woggioni
|
||||
password: ${{ secrets.PUBLISHER_TOKEN }}
|
||||
-
|
||||
name: Build and push gbcs images
|
||||
uses: docker/build-push-action@v6
|
||||
name: Build gbcs Docker image
|
||||
uses: docker/build-push-action@v5.3.0
|
||||
with:
|
||||
context: "docker/build/docker"
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
pull: true
|
||||
tags: |
|
||||
"gitea.woggioni.net/woggioni/gbcs:slim"
|
||||
cache-from: type=registry,ref=gitea.woggioni.net/woggioni/gbcs:buildx
|
||||
cache-to: type=registry,mode=max,compression=zstd,image-manifest=true,oci-mediatypes=true,ref=gitea.woggioni.net/woggioni/gbcs:buildx
|
||||
gitea.woggioni.net/woggioni/gbcs:latest
|
||||
gitea.woggioni.net/woggioni/gbcs:${{ steps.retrieve-version.outputs.VERSION }}
|
||||
target: release
|
||||
cache-from: type=registry,ref=gitea.woggioni.net/woggioni/gbcs:buildx
|
||||
-
|
||||
name: Build and push gbcs memcached image
|
||||
uses: docker/build-push-action@v6
|
||||
name: Build gbcs memcached Docker image
|
||||
uses: docker/build-push-action@v5.3.0
|
||||
with:
|
||||
context: "docker/build/docker"
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
pull: true
|
||||
tags: |
|
||||
"gitea.woggioni.net/woggioni/gbcs:latest"
|
||||
"gitea.woggioni.net/woggioni/gbcs:memcached"
|
||||
gitea.woggioni.net/woggioni/gbcs:memcached
|
||||
gitea.woggioni.net/woggioni/gbcs:memcached-${{ steps.retrieve-version.outputs.VERSION }}
|
||||
target: release-memcached
|
||||
cache-from: type=registry,ref=gitea.woggioni.net/woggioni/gbcs:buildx
|
||||
cache-to: type=registry,mode=max,compression=zstd,image-manifest=true,oci-mediatypes=true,ref=gitea.woggioni.net/woggioni/gbcs:buildx
|
||||
target: release-memcached
|
||||
- name: Publish artifacts
|
||||
env:
|
||||
PUBLISHER_TOKEN: ${{ secrets.PUBLISHER_TOKEN }}
|
||||
run: ./gradlew publish
|
||||
|
||||
|
46
Dockerfile
46
Dockerfile
@@ -1,46 +1,2 @@
|
||||
FROM container-registry.oracle.com/graalvm/native-image:21 AS oracle
|
||||
|
||||
FROM ubuntu:24.04 AS build
|
||||
COPY --from=oracle /usr/lib64/graalvm/ /usr/lib64/graalvm/
|
||||
ENV JAVA_HOME=/usr/lib64/graalvm/graalvm-java21
|
||||
USER ubuntu
|
||||
WORKDIR /home/ubuntu
|
||||
|
||||
RUN mkdir gbcs
|
||||
WORKDIR /home/ubuntu/gbcs
|
||||
|
||||
COPY --chown=ubuntu:users .git .git
|
||||
COPY --chown=ubuntu:users gbcs-base gbcs-base
|
||||
COPY --chown=ubuntu:users gbcs-api gbcs-api
|
||||
COPY --chown=ubuntu:users gbcs-memcached gbcs-memcached
|
||||
COPY --chown=ubuntu:users gbcs-cli gbcs-cli
|
||||
COPY --chown=ubuntu:users src src
|
||||
COPY --chown=ubuntu:users settings.gradle settings.gradle
|
||||
COPY --chown=ubuntu:users build.gradle build.gradle
|
||||
COPY --chown=ubuntu:users gradle.properties gradle.properties
|
||||
COPY --chown=ubuntu:users gradle gradle
|
||||
COPY --chown=ubuntu:users gradlew gradlew
|
||||
|
||||
RUN --mount=type=cache,target=/home/ubuntu/.gradle,uid=1000,gid=1000 ./gradlew --no-daemon assemble
|
||||
|
||||
FROM alpine:latest AS base-release
|
||||
RUN --mount=type=cache,target=/var/cache/apk apk update
|
||||
RUN --mount=type=cache,target=/var/cache/apk apk add openjdk21-jre
|
||||
RUN adduser -D luser
|
||||
USER luser
|
||||
WORKDIR /home/luser
|
||||
|
||||
FROM base-release AS release
|
||||
RUN --mount=type=bind,from=build,source=/home/ubuntu/gbcs/gbcs-cli/build,target=/home/luser/build cp build/libs/gbcs-cli-envelope-*.jar gbcs.jar
|
||||
ENTRYPOINT ["java", "-jar", "/home/luser/gbcs.jar"]
|
||||
|
||||
FROM base-release AS release-memcached
|
||||
RUN --mount=type=bind,from=build,source=/home/ubuntu/gbcs/gbcs-cli/build,target=/home/luser/build cp build/libs/gbcs-cli-envelope-*.jar gbcs.jar
|
||||
RUN mkdir plugins
|
||||
WORKDIR /home/luser/plugins
|
||||
RUN --mount=type=bind,from=build,source=/home/ubuntu/gbcs/gbcs-memcached/build/distributions,target=/build/distributions tar -xf /build/distributions/gbcs-memcached*.tar
|
||||
WORKDIR /home/luser
|
||||
ENTRYPOINT ["java", "-jar", "/home/luser/gbcs.jar"]
|
||||
|
||||
FROM release-memcached as compose
|
||||
FROM gitea.woggioni.net/woggioni/gbcs:memcached
|
||||
COPY --chown=luser:luser conf/gbcs-memcached.xml /home/luser/.config/gbcs/gbcs.xml
|
26
benchmark/build.gradle
Normal file
26
benchmark/build.gradle
Normal file
@@ -0,0 +1,26 @@
|
||||
plugins {
|
||||
alias catalog.plugins.gradle.jmh
|
||||
alias catalog.plugins.lombok
|
||||
}
|
||||
|
||||
import me.champeau.jmh.JMHTask
|
||||
|
||||
dependencies {
|
||||
implementation rootProject
|
||||
|
||||
implementation catalog.jwo
|
||||
implementation catalog.xz
|
||||
implementation catalog.jackson.databind
|
||||
|
||||
jmhAnnotationProcessor catalog.lombok
|
||||
}
|
||||
|
||||
jmh {
|
||||
threads = 4
|
||||
iterations = 2
|
||||
fork = 1
|
||||
warmupIterations = 1
|
||||
warmupForks = 0
|
||||
resultFormat = 'JSON'
|
||||
}
|
||||
|
170
benchmark/src/jmh/java/net/woggioni/gbcs/benchmark/Main.java
Normal file
170
benchmark/src/jmh/java/net/woggioni/gbcs/benchmark/Main.java
Normal file
@@ -0,0 +1,170 @@
|
||||
package net.woggioni.gbcs.benchmark;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Level;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.Setup;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class Main {
|
||||
|
||||
@SneakyThrows
|
||||
private static Properties loadProperties() {
|
||||
Properties properties = new Properties();
|
||||
try (final var is = Main.class.getResourceAsStream("/benchmark.properties")) {
|
||||
properties.load(is);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
private static final Properties properties = loadProperties();
|
||||
|
||||
@State(Scope.Thread)
|
||||
public static class ExecutionPlan {
|
||||
private final Random random = new Random(101325);
|
||||
|
||||
@Getter
|
||||
private final HttpClient client = HttpClient.newHttpClient();
|
||||
|
||||
private final Map<String, byte[]> entries = new HashMap<>();
|
||||
|
||||
public final Map<String, byte[]> getEntries() {
|
||||
return Collections.unmodifiableMap(entries);
|
||||
}
|
||||
|
||||
public Map.Entry<String, byte[]> newEntry() {
|
||||
final var keyBuffer = new byte[0x20];
|
||||
random.nextBytes(keyBuffer);
|
||||
final var key = Base64.getUrlEncoder().encodeToString(keyBuffer);
|
||||
final var value = new byte[0x1000];
|
||||
random.nextBytes(value);
|
||||
return Map.entry(key, value);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public HttpRequest.Builder newRequestBuilder(String key) {
|
||||
final var requestBuilder = HttpRequest.newBuilder()
|
||||
.uri(getServerURI().resolve(key));
|
||||
String user = getUser();
|
||||
if (user != null) {
|
||||
requestBuilder.header("Authorization", buildAuthorizationHeader(user, getPassword()));
|
||||
}
|
||||
return requestBuilder;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public URI getServerURI() {
|
||||
return new URI(properties.getProperty("gbcs.server.url"));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public String getUser() {
|
||||
return Optional.ofNullable(properties.getProperty("gbcs.server.username"))
|
||||
.filter(Predicate.not(String::isEmpty))
|
||||
.orElse(null);
|
||||
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public String getPassword() {
|
||||
return Optional.ofNullable(properties.getProperty("gbcs.server.password"))
|
||||
.filter(Predicate.not(String::isEmpty))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private String buildAuthorizationHeader(String user, String password) {
|
||||
final var b64 = Base64.getEncoder().encode(String.format("%s:%s", user, password).getBytes(StandardCharsets.UTF_8));
|
||||
return "Basic " + new String(b64);
|
||||
}
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
@Setup(Level.Trial)
|
||||
public void setUp() {
|
||||
try (final var client = HttpClient.newHttpClient()) {
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
final var pair = newEntry();
|
||||
final var requestBuilder = newRequestBuilder(pair.getKey())
|
||||
.header("Content-Type", "application/octet-stream")
|
||||
.PUT(HttpRequest.BodyPublishers.ofByteArray(pair.getValue()));
|
||||
final var response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
|
||||
if (201 != response.statusCode()) {
|
||||
throw new IllegalStateException(Integer.toString(response.statusCode()));
|
||||
} else {
|
||||
entries.put(pair.getKey(), pair.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Iterator<Map.Entry<String, byte[]>> it = null;
|
||||
|
||||
private Map.Entry<String, byte[]> nextEntry() {
|
||||
if (it == null || !it.hasNext()) {
|
||||
it = getEntries().entrySet().iterator();
|
||||
}
|
||||
return it.next();
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.Throughput)
|
||||
@OutputTimeUnit(TimeUnit.SECONDS)
|
||||
public void get(ExecutionPlan plan) {
|
||||
final var client = plan.getClient();
|
||||
final var entry = plan.nextEntry();
|
||||
final var requestBuilder = plan.newRequestBuilder(entry.getKey())
|
||||
.header("Accept", "application/octet-stream")
|
||||
.GET();
|
||||
final var response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray());
|
||||
if (200 != response.statusCode()) {
|
||||
throw new IllegalStateException(Integer.toString(response.statusCode()));
|
||||
} else {
|
||||
if (!Arrays.equals(entry.getValue(), response.body())) {
|
||||
throw new IllegalStateException("Retrieved unexpected value");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.Throughput)
|
||||
@OutputTimeUnit(TimeUnit.SECONDS)
|
||||
public void put(Main.ExecutionPlan plan) {
|
||||
final var client = plan.getClient();
|
||||
final var entry = plan.nextEntry();
|
||||
|
||||
final var requestBuilder = plan.newRequestBuilder(entry.getKey())
|
||||
.header("Content-Type", "application/octet-stream")
|
||||
.PUT(HttpRequest.BodyPublishers.ofByteArray(entry.getValue()));
|
||||
|
||||
final var response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray());
|
||||
if (201 != response.statusCode()) {
|
||||
throw new IllegalStateException(Integer.toString(response.statusCode()));
|
||||
}
|
||||
}
|
||||
}
|
1
benchmark/src/jmh/resources/benchmark.properties
Normal file
1
benchmark/src/jmh/resources/benchmark.properties
Normal file
@@ -0,0 +1 @@
|
||||
gbcs.server.url= http://localhost:8080
|
@@ -130,3 +130,9 @@ publishing {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('version') {
|
||||
doLast {
|
||||
println("VERSION=$version")
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<gbcs:server useVirtualThreads="false" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
|
||||
<gbcs:server useVirtualThreads="true" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:gbcs="urn:net.woggioni.gbcs"
|
||||
xmlns:gbcs-memcached="urn:net.woggioni.gbcs-memcached"
|
||||
xs:schemaLocation="urn:net.woggioni.gbcs-memcached jpms://net.woggioni.gbcs.memcached/net/woggioni/gbcs/memcached/schema/gbcs-memcached.xsd urn:net.woggioni.gbcs jpms://net.woggioni.gbcs/net/woggioni/gbcs/schema/gbcs.xsd">
|
||||
|
21
conf/logback.xml
Normal file
21
conf/logback.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE configuration>
|
||||
|
||||
<configuration>
|
||||
<import class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"/>
|
||||
<import class="ch.qos.logback.core.ConsoleAppender"/>
|
||||
|
||||
<appender name="console" class="ConsoleAppender">
|
||||
<target>System.err</target>
|
||||
<encoder class="PatternLayoutEncoder">
|
||||
<pattern>%d [%highlight(%-5level)] \(%thread\) %logger{36} -%kvp- %msg %n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="console"/>
|
||||
</root>
|
||||
<logger name="io.netty" level="debug"/>
|
||||
<logger name="com.google.code.yanf4j" level="warn"/>
|
||||
<logger name="net.rubyeye.xmemcached" level="warn"/>
|
||||
</configuration>
|
@@ -11,7 +11,6 @@ services:
|
||||
gbcs:
|
||||
build:
|
||||
context: .
|
||||
target: compose
|
||||
container_name: gbcs
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
|
21
docker/Dockerfile
Normal file
21
docker/Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
||||
FROM alpine:latest AS base-release
|
||||
RUN --mount=type=cache,target=/var/cache/apk apk update
|
||||
RUN --mount=type=cache,target=/var/cache/apk apk add openjdk21-jre
|
||||
RUN adduser -D luser
|
||||
USER luser
|
||||
WORKDIR /home/luser
|
||||
|
||||
FROM base-release AS release
|
||||
ADD gbcs-cli-envelope-*.jar gbcs.jar
|
||||
ENTRYPOINT ["java", "-jar", "/home/luser/gbcs.jar"]
|
||||
|
||||
FROM base-release AS release-memcached
|
||||
ADD --chown=luser:luser gbcs-cli-envelope-*.jar gbcs.jar
|
||||
RUN mkdir plugins
|
||||
WORKDIR /home/luser/plugins
|
||||
RUN --mount=type=bind,source=.,target=/build/distributions tar -xf /build/distributions/gbcs-memcached*.tar
|
||||
WORKDIR /home/luser
|
||||
ENTRYPOINT ["java", "-jar", "/home/luser/gbcs.jar"]
|
||||
|
||||
FROM release-memcached as compose
|
||||
COPY --chown=luser:luser conf/gbcs-memcached.xml /home/luser/.config/gbcs/gbcs.xml
|
67
docker/build.gradle
Normal file
67
docker/build.gradle
Normal file
@@ -0,0 +1,67 @@
|
||||
plugins {
|
||||
id 'base'
|
||||
alias(catalog.plugins.gradle.docker)
|
||||
}
|
||||
|
||||
import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage
|
||||
import com.bmuschko.gradle.docker.tasks.image.DockerPushImage
|
||||
import com.bmuschko.gradle.docker.tasks.image.DockerTagImage
|
||||
|
||||
|
||||
configurations {
|
||||
docker {
|
||||
canBeResolved = true
|
||||
transitive = false
|
||||
visible = false
|
||||
canBeConsumed = false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
docker project(path: ':gbcs-cli', configuration: 'release')
|
||||
docker project(path: ':gbcs-memcached', configuration: 'release')
|
||||
}
|
||||
|
||||
Provider<Task> cleanTaskProvider = tasks.named(BasePlugin.CLEAN_TASK_NAME) {}
|
||||
|
||||
Provider<Copy> prepareDockerBuild = tasks.register('prepareDockerBuild', Copy) {
|
||||
dependsOn cleanTaskProvider
|
||||
group = 'docker'
|
||||
into project.layout.buildDirectory.file('docker')
|
||||
from(configurations.docker)
|
||||
from(file('Dockerfile'))
|
||||
}
|
||||
|
||||
Provider<DockerBuildImage> dockerBuild = tasks.register('dockerBuildImage', DockerBuildImage) {
|
||||
group = 'docker'
|
||||
dependsOn prepareDockerBuild
|
||||
images.add('gitea.woggioni.net/woggioni/gbcs:latest')
|
||||
images.add("gitea.woggioni.net/woggioni/gbcs:${version}")
|
||||
}
|
||||
|
||||
Provider<DockerTagImage> dockerTag = tasks.register('dockerTagImage', DockerTagImage) {
|
||||
group = 'docker'
|
||||
repository = 'gitea.woggioni.net/woggioni/gbcs'
|
||||
imageId = 'gitea.woggioni.net/woggioni/gbcs:latest'
|
||||
tag = version
|
||||
}
|
||||
|
||||
Provider<DockerTagImage> dockerTagMemcached = tasks.register('dockerTagMemcachedImage', DockerTagImage) {
|
||||
group = 'docker'
|
||||
repository = 'gitea.woggioni.net/woggioni/gbcs'
|
||||
imageId = 'gitea.woggioni.net/woggioni/gbcs:memcached'
|
||||
tag = "${version}-memcached"
|
||||
}
|
||||
|
||||
Provider<DockerPushImage> dockerPush = tasks.register('dockerPushImage', DockerPushImage) {
|
||||
group = 'docker'
|
||||
dependsOn dockerTag, dockerTagMemcached
|
||||
registryCredentials {
|
||||
url = getProperty('docker.registry.url')
|
||||
username = 'woggioni'
|
||||
password = System.getenv().get("PUBLISHER_TOKEN")
|
||||
}
|
||||
images = [dockerTag.flatMap{ it.tag }, dockerTagMemcached.flatMap{ it.tag }]
|
||||
}
|
||||
|
||||
|
@@ -21,6 +21,15 @@ tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) {
|
||||
options.javaModuleMainClass = mainClassName
|
||||
}
|
||||
|
||||
configurations {
|
||||
release {
|
||||
transitive = false
|
||||
canBeConsumed = true
|
||||
canBeResolved = true
|
||||
visible = true
|
||||
}
|
||||
}
|
||||
|
||||
envelopeJar {
|
||||
mainModule = 'net.woggioni.gbcs.cli'
|
||||
mainClass = mainClassName
|
||||
@@ -56,6 +65,10 @@ tasks.named(NativeImagePlugin.NATIVE_IMAGE_TASK_NAME, NativeImageTask) {
|
||||
buildStaticImage = true
|
||||
}
|
||||
|
||||
artifacts {
|
||||
release(envelopeJarTaskProvider)
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
@@ -64,3 +77,4 @@ publishing {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -92,7 +92,8 @@ class GradleBuildCacheServerCli(application : Application, private val log : Log
|
||||
"Server configuration:\n${String(it.toByteArray())}"
|
||||
}
|
||||
}
|
||||
GradleBuildCacheServer(configuration).run().use {
|
||||
val server = GradleBuildCacheServer(configuration)
|
||||
server.run().use {
|
||||
}
|
||||
}
|
||||
}
|
@@ -22,7 +22,15 @@ configurations {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
release {
|
||||
transitive = false
|
||||
canBeConsumed = true
|
||||
canBeResolved = true
|
||||
visible = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly project(':gbcs-base')
|
||||
compileOnly project(':gbcs-api')
|
||||
@@ -40,6 +48,10 @@ tasks.named(BasePlugin.ASSEMBLE_TASK_NAME) {
|
||||
dependsOn(bundleTask)
|
||||
}
|
||||
|
||||
artifacts {
|
||||
release(bundleTask)
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
|
@@ -4,6 +4,7 @@ org.gradle.caching=true
|
||||
|
||||
gbcs.version = 0.0.1
|
||||
|
||||
lys.version = 2025.01.09
|
||||
lys.version = 2025.01.10
|
||||
|
||||
gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven
|
||||
docker.registry.url=gitea.woggioni.net
|
||||
|
@@ -30,5 +30,6 @@ include 'gbcs-api'
|
||||
include 'gbcs-base'
|
||||
include 'gbcs-memcached'
|
||||
include 'gbcs-cli'
|
||||
|
||||
include 'docker'
|
||||
include 'benchmark'
|
||||
|
||||
|
@@ -12,7 +12,6 @@ import io.netty.channel.ChannelInitializer
|
||||
import io.netty.channel.ChannelOption
|
||||
import io.netty.channel.ChannelPromise
|
||||
import io.netty.channel.DefaultFileRegion
|
||||
import io.netty.channel.EventLoopGroup
|
||||
import io.netty.channel.SimpleChannelInboundHandler
|
||||
import io.netty.channel.nio.NioEventLoopGroup
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel
|
||||
@@ -55,6 +54,7 @@ import net.woggioni.gbcs.base.PasswordSecurity.decodePasswordHash
|
||||
import net.woggioni.gbcs.base.PasswordSecurity.hashPassword
|
||||
import net.woggioni.gbcs.base.Xml
|
||||
import net.woggioni.gbcs.base.contextLogger
|
||||
import net.woggioni.gbcs.base.info
|
||||
import net.woggioni.gbcs.configuration.Parser
|
||||
import net.woggioni.gbcs.configuration.Serializer
|
||||
import net.woggioni.jwo.JWO
|
||||
@@ -69,7 +69,6 @@ import java.security.PrivateKey
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.Arrays
|
||||
import java.util.Base64
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
import javax.naming.ldap.LdapName
|
||||
@@ -178,8 +177,12 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
}
|
||||
}
|
||||
|
||||
private class ServerInitializer(private val cfg: Configuration) : ChannelInitializer<Channel>() {
|
||||
private class ServerInitializer(
|
||||
private val cfg: Configuration,
|
||||
private val eventExecutorGroup: EventExecutorGroup
|
||||
) : ChannelInitializer<Channel>() {
|
||||
|
||||
companion object {
|
||||
private fun createSslCtx(tls: Configuration.Tls): SslContext {
|
||||
val keyStore = tls.keyStore
|
||||
return if (keyStore == null) {
|
||||
@@ -187,7 +190,7 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
} else {
|
||||
val javaKeyStore = loadKeystore(keyStore.file, keyStore.password)
|
||||
val serverKey = javaKeyStore.getKey(
|
||||
keyStore.keyAlias, keyStore.keyPassword?.let(String::toCharArray)
|
||||
keyStore.keyAlias, (keyStore.keyPassword ?: "").let(String::toCharArray)
|
||||
) as PrivateKey
|
||||
val serverCert: Array<X509Certificate> =
|
||||
Arrays.stream(javaKeyStore.getCertificateChain(keyStore.keyAlias))
|
||||
@@ -208,11 +211,6 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
}
|
||||
}
|
||||
|
||||
private val sslContext: SslContext? = cfg.tls?.let(this::createSslCtx)
|
||||
private val group: EventExecutorGroup = DefaultEventExecutorGroup(Runtime.getRuntime().availableProcessors())
|
||||
|
||||
companion object {
|
||||
|
||||
fun loadKeystore(file: Path, password: String?): KeyStore {
|
||||
val ext = JWO.splitExtension(file)
|
||||
.map(Tuple2<String, String>::get_2)
|
||||
@@ -235,6 +233,8 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
}
|
||||
}
|
||||
|
||||
private val sslContext: SslContext? = cfg.tls?.let(Companion::createSslCtx)
|
||||
|
||||
private fun userExtractor(authentication: Configuration.ClientCertificateAuthentication) =
|
||||
authentication.userExtractor?.let { extractor ->
|
||||
val pattern = Pattern.compile(extractor.pattern)
|
||||
@@ -294,7 +294,7 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
}
|
||||
val cacheImplementation = cfg.cache.materialize()
|
||||
val prefix = Path.of("/").resolve(Path.of(cfg.serverPath ?: "/"))
|
||||
pipeline.addLast(group, ServerHandler(cacheImplementation, prefix))
|
||||
pipeline.addLast(eventExecutorGroup, ServerHandler(cacheImplementation, prefix))
|
||||
pipeline.addLast(ExceptionHandler())
|
||||
}
|
||||
}
|
||||
@@ -446,8 +446,7 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
|
||||
class ServerHandle(
|
||||
httpChannelFuture: ChannelFuture,
|
||||
private val bossGroup: EventLoopGroup,
|
||||
private val workerGroup: EventLoopGroup
|
||||
private val executorGroups : Iterable<EventExecutorGroup>
|
||||
) : AutoCloseable {
|
||||
private val httpChannel: Channel = httpChannelFuture.channel()
|
||||
|
||||
@@ -461,31 +460,35 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
try {
|
||||
closeFuture.sync()
|
||||
} finally {
|
||||
val fut1 = workerGroup.shutdownGracefully()
|
||||
val fut2 = if (bossGroup !== workerGroup) {
|
||||
bossGroup.shutdownGracefully()
|
||||
} else null
|
||||
fut1.sync()
|
||||
fut2?.sync()
|
||||
executorGroups.forEach {
|
||||
it.shutdownGracefully().sync()
|
||||
}
|
||||
}
|
||||
log.info {
|
||||
"GradleBuildCacheServer has been gracefully shut down"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun run(): ServerHandle {
|
||||
// Create the multithreaded event loops for the server
|
||||
val bossGroup = NioEventLoopGroup(0, Executors.defaultThreadFactory())
|
||||
val bossGroup = NioEventLoopGroup(0)
|
||||
val serverSocketChannel = NioServerSocketChannel::class.java
|
||||
val workerGroup = if (cfg.isUseVirtualThread) {
|
||||
NioEventLoopGroup(0, Executors.newVirtualThreadPerTaskExecutor())
|
||||
val workerGroup = bossGroup
|
||||
val eventExecutorGroup = run {
|
||||
val threadFactory = if(cfg.isUseVirtualThread) {
|
||||
Thread.ofVirtual().factory()
|
||||
} else {
|
||||
bossGroup
|
||||
null
|
||||
}
|
||||
DefaultEventExecutorGroup(Runtime.getRuntime().availableProcessors(), threadFactory)
|
||||
}
|
||||
// A helper class that simplifies server configuration
|
||||
val bootstrap = ServerBootstrap().apply {
|
||||
// Configure the server
|
||||
group(bossGroup, workerGroup)
|
||||
channel(serverSocketChannel)
|
||||
childHandler(ServerInitializer(cfg))
|
||||
childHandler(ServerInitializer(cfg, eventExecutorGroup))
|
||||
option(ChannelOption.SO_BACKLOG, 128)
|
||||
childOption(ChannelOption.SO_KEEPALIVE, true)
|
||||
}
|
||||
@@ -494,7 +497,10 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
// Bind and start to accept incoming connections.
|
||||
val bindAddress = InetSocketAddress(cfg.host, cfg.port)
|
||||
val httpChannel = bootstrap.bind(bindAddress).sync()
|
||||
return ServerHandle(httpChannel, bossGroup, workerGroup)
|
||||
log.info {
|
||||
"GradleBuildCacheServer is listening on ${cfg.host}:${cfg.port}"
|
||||
}
|
||||
return ServerHandle(httpChannel, setOf(bossGroup, workerGroup, eventExecutorGroup))
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -511,5 +517,7 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
fun dumpConfiguration(conf: Configuration, outputStream: OutputStream) {
|
||||
Xml.write(Serializer.serialize(conf), outputStream)
|
||||
}
|
||||
|
||||
private val log = contextLogger()
|
||||
}
|
||||
}
|
||||
|
@@ -32,7 +32,7 @@ object Parser {
|
||||
val serverPath = root.getAttribute("path")
|
||||
val useVirtualThread = root.getAttribute("useVirtualThreads")
|
||||
.takeIf(String::isNotEmpty)
|
||||
?.let(String::toBoolean) ?: false
|
||||
?.let(String::toBoolean) ?: true
|
||||
var authentication: Authentication? = null
|
||||
for (child in root.asIterable()) {
|
||||
when (child.localName) {
|
||||
|
@@ -10,9 +10,6 @@
|
||||
<xs:sequence minOccurs="0">
|
||||
<xs:element name="bind" type="gbcs:bindType" maxOccurs="1"/>
|
||||
<xs:element name="cache" type="gbcs:cacheType" maxOccurs="1"/>
|
||||
<!-- <xs:choice>-->
|
||||
<!-- <xs:element name="fileSystemCache" type="fileSystemCacheType"/>-->
|
||||
<!-- </xs:choice>-->
|
||||
<xs:element name="authorization" type="gbcs:authorizationType" minOccurs="0">
|
||||
<xs:key name="userId">
|
||||
<xs:selector xpath="users/user"/>
|
||||
@@ -28,7 +25,7 @@
|
||||
<xs:element name="tls" type="gbcs:tlsType" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="path" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="useVirtualThreads" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute name="useVirtualThreads" type="xs:boolean" use="optional" default="true"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="bindType">
|
||||
|
@@ -105,4 +105,28 @@ class NoAuthServerTest : AbstractServerTest() {
|
||||
val response: HttpResponse<ByteArray> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
|
||||
Assertions.assertEquals(HttpResponseStatus.NOT_FOUND.code(), response.statusCode())
|
||||
}
|
||||
|
||||
// @Test
|
||||
// @Order(4)
|
||||
// fun manyRequestsTest() {
|
||||
// val client: HttpClient = HttpClient.newHttpClient()
|
||||
//
|
||||
// for(i in 0 until 100000) {
|
||||
//
|
||||
// val newEntry = random.nextBoolean()
|
||||
// val (key, _) = if(newEntry) {
|
||||
// newEntry(random)
|
||||
// } else {
|
||||
// keyValuePair
|
||||
// }
|
||||
// val requestBuilder = newRequestBuilder(key).GET()
|
||||
//
|
||||
// val response: HttpResponse<ByteArray> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
|
||||
// if(newEntry) {
|
||||
// Assertions.assertEquals(HttpResponseStatus.NOT_FOUND.code(), response.statusCode())
|
||||
// } else {
|
||||
// Assertions.assertEquals(HttpResponseStatus.OK.code(), response.statusCode())
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
21
src/test/resources/logback.xml
Normal file
21
src/test/resources/logback.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE configuration>
|
||||
|
||||
<configuration>
|
||||
<import class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"/>
|
||||
<import class="ch.qos.logback.core.ConsoleAppender"/>
|
||||
|
||||
<appender name="console" class="ConsoleAppender">
|
||||
<target>System.err</target>
|
||||
<encoder class="PatternLayoutEncoder">
|
||||
<pattern>%d [%highlight(%-5level)] \(%thread\) %logger{36} -%kvp- %msg %n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="console"/>
|
||||
</root>
|
||||
<logger name="io.netty" level="debug"/>
|
||||
<logger name="com.google.code.yanf4j" level="warn"/>
|
||||
<logger name="net.rubyeye.xmemcached" level="warn"/>
|
||||
</configuration>
|
Reference in New Issue
Block a user