Skip to content

Commit acd7a05

Browse files
committed
archives and event templates can use tagging, s3 objectmeta, or separate bucket for metadata
1 parent 662fdaa commit acd7a05

15 files changed

+1473
-209
lines changed

src/main/java/io/cryostat/ConfigProperties.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,25 @@
1616
package io.cryostat;
1717

1818
public class ConfigProperties {
19-
public static final String ARCHIVED_RECORDINGS_METADATA_STORAGE_MODE =
20-
"cryostat.archived-recordings.metadata.storage-mode";
19+
public static final String STORAGE_METADATA_STORAGE_MODE = "storage.metadata.storage-mode";
20+
public static final String STORAGE_METADATA_ARCHIVES_STORAGE_MODE =
21+
"storage.metadata.archives.storage-mode";
22+
public static final String STORAGE_METADATA_EVENT_TEMPLATES_STORAGE_MODE =
23+
"storage.metadata.event-templates.storage-mode";
24+
public static final String STORAGE_METADATA_PROBE_TEMPLATES_STORAGE_MODE =
25+
"storage.metadata.probe-templates.storage-mode";
2126
public static final String AWS_BUCKET_NAME_ARCHIVES = "storage.buckets.archives.name";
22-
public static final String AWS_BUCKET_NAME_ARCHIVES_META = "storage.buckets.archives-meta.name";
27+
public static final String AWS_BUCKET_NAME_METADATA = "storage.buckets.metadata.name";
2328
public static final String AWS_BUCKET_NAME_EVENT_TEMPLATES =
2429
"storage.buckets.event-templates.name";
2530
public static final String AWS_BUCKET_NAME_PROBE_TEMPLATES =
2631
"storage.buckets.probe-templates.name";
32+
public static final String AWS_METADATA_PREFIX_RECORDINGS =
33+
"storage.metadata.prefix.recordings";
34+
public static final String AWS_METADATA_PREFIX_EVENT_TEMPLATES =
35+
"storage.metadata.prefix.event-templates";
36+
public static final String AWS_METADATA_PREFIX_PROBE_TEMPLATES =
37+
"storage.metadata.prefix.probe-templates";
2738

2839
public static final String CONTAINERS_POLL_PERIOD = "cryostat.discovery.containers.poll-period";
2940
public static final String CONTAINERS_REQUEST_TIMEOUT =

src/main/java/io/cryostat/StorageBuckets.java

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
package io.cryostat;
1717

1818
import java.time.Duration;
19-
import java.util.Set;
20-
import java.util.concurrent.ConcurrentHashMap;
19+
import java.util.concurrent.ArrayBlockingQueue;
20+
import java.util.concurrent.BlockingQueue;
21+
import java.util.concurrent.ExecutorService;
2122
import java.util.concurrent.Executors;
2223
import java.util.concurrent.ScheduledExecutorService;
2324
import java.util.concurrent.TimeUnit;
@@ -44,10 +45,17 @@ public class StorageBuckets {
4445
@ConfigProperty(name = "storage.buckets.creation-retry.period")
4546
Duration creationRetryPeriod;
4647

47-
private final Set<String> buckets = ConcurrentHashMap.newKeySet();
48-
private final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor();
48+
private final BlockingQueue<String> buckets = new ArrayBlockingQueue<>(16);
49+
private final ScheduledExecutorService q = Executors.newSingleThreadScheduledExecutor();
50+
private final ExecutorService pool = Executors.newCachedThreadPool();
51+
private volatile boolean shutdown = false;
4952

5053
public void createIfNecessary(String bucket) {
54+
if (buckets.contains(bucket)) {
55+
logger.debugv("Bucket \"{0}\" already queued, skipping");
56+
return;
57+
}
58+
logger.debugv("Queueing bucket check/creation: \"{0}\"", bucket);
5159
buckets.add(bucket);
5260
}
5361

@@ -78,11 +86,15 @@ private boolean tryCreate(String bucket) {
7886
}
7987

8088
void onStart(@Observes StartupEvent evt) {
81-
worker.scheduleAtFixedRate(
89+
q.scheduleAtFixedRate(
8290
() -> {
83-
var it = buckets.iterator();
84-
while (it.hasNext()) {
85-
if (tryCreate(it.next())) it.remove();
91+
while (!shutdown) {
92+
try {
93+
String bucket = buckets.take();
94+
pool.execute(() -> tryCreate(bucket));
95+
} catch (InterruptedException e) {
96+
break;
97+
}
8698
}
8799
},
88100
0,
@@ -91,6 +103,7 @@ void onStart(@Observes StartupEvent evt) {
91103
}
92104

93105
void onStop(@Observes ShutdownEvent evt) {
94-
worker.shutdown();
106+
shutdown = true;
107+
q.shutdownNow();
95108
}
96109
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Copyright The Cryostat Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.cryostat.events;
17+
18+
import java.io.IOException;
19+
import java.util.List;
20+
import java.util.Objects;
21+
import java.util.Optional;
22+
23+
import io.cryostat.ConfigProperties;
24+
import io.cryostat.Producers;
25+
import io.cryostat.StorageBuckets;
26+
import io.cryostat.libcryostat.templates.Template;
27+
import io.cryostat.libcryostat.templates.TemplateType;
28+
import io.cryostat.recordings.ArchivedRecordingMetadataService;
29+
import io.cryostat.util.CRUDService;
30+
import io.cryostat.util.HttpMimeType;
31+
32+
import com.fasterxml.jackson.databind.ObjectMapper;
33+
import io.quarkus.arc.lookup.LookupIfProperty;
34+
import io.quarkus.runtime.StartupEvent;
35+
import io.smallrye.common.annotation.Identifier;
36+
import jakarta.enterprise.context.ApplicationScoped;
37+
import jakarta.enterprise.event.Observes;
38+
import jakarta.inject.Inject;
39+
import org.apache.commons.codec.binary.Base64;
40+
import org.eclipse.microprofile.config.inject.ConfigProperty;
41+
import org.jboss.logging.Logger;
42+
import software.amazon.awssdk.core.sync.RequestBody;
43+
import software.amazon.awssdk.services.s3.S3Client;
44+
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
45+
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
46+
import software.amazon.awssdk.services.s3.model.ListObjectsV2Request;
47+
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
48+
49+
@ApplicationScoped
50+
@LookupIfProperty(
51+
name = ConfigProperties.STORAGE_METADATA_EVENT_TEMPLATES_STORAGE_MODE,
52+
stringValue = ArchivedRecordingMetadataService.METADATA_STORAGE_MODE_BUCKET)
53+
class BucketedEventTemplateMetadataService implements CRUDService<String, Template, Template> {
54+
55+
@ConfigProperty(name = ConfigProperties.AWS_BUCKET_NAME_METADATA)
56+
String bucket;
57+
58+
@ConfigProperty(name = ConfigProperties.AWS_METADATA_PREFIX_EVENT_TEMPLATES)
59+
String prefix;
60+
61+
@Inject S3Client storage;
62+
@Inject StorageBuckets buckets;
63+
64+
@Inject
65+
@Identifier(Producers.BASE64_URL)
66+
Base64 base64Url;
67+
68+
@Inject ObjectMapper mapper;
69+
70+
@Inject Logger logger;
71+
72+
void onStart(@Observes StartupEvent evt) {
73+
buckets.createIfNecessary(bucket);
74+
}
75+
76+
@Override
77+
public List<Template> list() throws IOException {
78+
var builder = ListObjectsV2Request.builder().bucket(bucket).prefix(prefix);
79+
var objs = storage.listObjectsV2(builder.build()).contents();
80+
return objs.stream()
81+
.map(
82+
t -> {
83+
// TODO this entails a remote file read over the network and then some
84+
// minor processing of the received file. More time will be spent
85+
// retrieving the data than processing it, so this should be
86+
// parallelized.
87+
try {
88+
return read(t.key()).orElseThrow();
89+
} catch (IOException e) {
90+
logger.error(e);
91+
return null;
92+
}
93+
})
94+
.filter(Objects::nonNull)
95+
.toList();
96+
}
97+
98+
@Override
99+
public void create(String k, Template template) throws IOException {
100+
storage.putObject(
101+
PutObjectRequest.builder()
102+
.bucket(bucket)
103+
.key(prefix(k))
104+
.contentType(HttpMimeType.JFC.mime())
105+
.build(),
106+
RequestBody.fromBytes(mapper.writeValueAsBytes(TemplateMeta.from(template))));
107+
}
108+
109+
@Override
110+
public Optional<Template> read(String k) throws IOException {
111+
try (var stream =
112+
storage.getObject(
113+
GetObjectRequest.builder().bucket(bucket).key(prefix(k)).build())) {
114+
return Optional.of(mapper.readValue(stream, TemplateMeta.class))
115+
.map(TemplateMeta::asTemplate);
116+
}
117+
}
118+
119+
@Override
120+
public void delete(String k) throws IOException {
121+
storage.deleteObject(DeleteObjectRequest.builder().bucket(bucket).key(prefix(k)).build());
122+
}
123+
124+
private String prefix(String key) {
125+
return String.format("%s/%s", prefix, key);
126+
}
127+
128+
// just a thin serialization adapter. Jackson ObjectMapper complains about not being able to
129+
// instantiate the Template type directly.
130+
static record TemplateMeta(String label, String provider, String description) {
131+
static TemplateMeta from(Template template) {
132+
return new TemplateMeta(
133+
template.getName(), template.getProvider(), template.getDescription());
134+
}
135+
136+
Template asTemplate() {
137+
return new Template(label, description, provider, TemplateType.CUSTOM);
138+
}
139+
}
140+
}

0 commit comments

Comments
 (0)