summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/LogserverContainerCluster.java3
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryCollector.java28
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryConfigGenerator.java16
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2Builder.java16
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java2
-rw-r--r--configdefinitions/src/vespa/CMakeLists.txt2
-rw-r--r--configdefinitions/src/vespa/open-telemetry.def5
-rw-r--r--logforwarder/CMakeLists.txt1
-rw-r--r--logforwarder/src/apps/vespa-otelcol-start/.gitignore1
-rw-r--r--logforwarder/src/apps/vespa-otelcol-start/CMakeLists.txt15
-rw-r--r--logforwarder/src/apps/vespa-otelcol-start/cf-handler.cpp67
-rw-r--r--logforwarder/src/apps/vespa-otelcol-start/cf-handler.h25
-rw-r--r--logforwarder/src/apps/vespa-otelcol-start/child-handler.cpp88
-rw-r--r--logforwarder/src/apps/vespa-otelcol-start/child-handler.h18
-rw-r--r--logforwarder/src/apps/vespa-otelcol-start/file-watcher.cpp37
-rw-r--r--logforwarder/src/apps/vespa-otelcol-start/file-watcher.h15
-rw-r--r--logforwarder/src/apps/vespa-otelcol-start/main.cpp43
-rw-r--r--logforwarder/src/apps/vespa-otelcol-start/wrapper.cpp76
-rw-r--r--logforwarder/src/apps/vespa-otelcol-start/wrapper.h18
19 files changed, 460 insertions, 16 deletions
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/LogserverContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/LogserverContainerCluster.java
index 629ce54d9dd..654c81f0519 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/LogserverContainerCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/LogserverContainerCluster.java
@@ -5,8 +5,6 @@ import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.producer.TreeConfigProducer;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.search.config.QrStartConfig;
-import com.yahoo.vespa.model.admin.otel.OpenTelemetryCollector;
-import com.yahoo.vespa.model.admin.otel.OpenTelemetryConfigGenerator;
import com.yahoo.vespa.model.container.ContainerCluster;
import com.yahoo.vespa.model.container.PlatformBundles;
import com.yahoo.vespa.model.container.component.Handler;
@@ -31,7 +29,6 @@ public class LogserverContainerCluster extends ContainerCluster<LogserverContain
setJvmGCOptions(deployState.getProperties().jvmGCOptions(Optional.of(ClusterSpec.Type.admin)));
if (isHostedVespa())
addAccessLog(getName());
-
}
@Override
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryCollector.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryCollector.java
index 71e2a670034..73275a36804 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryCollector.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryCollector.java
@@ -2,21 +2,22 @@
package com.yahoo.vespa.model.admin.otel;
import com.yahoo.cloud.config.OpenTelemetryConfig;
+import com.yahoo.config.model.ApplicationConfigProducerRoot;
import com.yahoo.config.model.producer.TreeConfigProducer;
import com.yahoo.vespa.model.AbstractService;
import com.yahoo.vespa.model.PortAllocBridge;
-import java.util.Optional;
+import com.yahoo.cloud.config.ModelConfig;
+import com.yahoo.config.model.producer.AnyConfigProducer;
-public class OpenTelemetryCollector extends AbstractService implements OpenTelemetryConfig.Producer {
+import java.util.Optional;
- private final String config;
+public class OpenTelemetryCollector extends AbstractService implements OpenTelemetryConfig.Producer {
- public OpenTelemetryCollector(TreeConfigProducer<?> parent, String config) {
+ public OpenTelemetryCollector(TreeConfigProducer<?> parent) {
super(parent, "otelcol");
setProp("clustertype", "admin");
setProp("clustername", "admin");
- this.config = config;
}
/**
@@ -24,10 +25,9 @@ public class OpenTelemetryCollector extends AbstractService implements OpenTelem
*/
@Override
public Optional<String> getStartupCommand() {
- return Optional.of("exec $ROOT/bin/vespa-otelcol-start");
+ return Optional.of("exec $ROOT/bin/vespa-otelcol-start -c " + getConfigId());
}
-
@Override
public void allocatePorts(int start, PortAllocBridge from) {}
@@ -38,6 +38,18 @@ public class OpenTelemetryCollector extends AbstractService implements OpenTelem
@Override
public void getConfig(OpenTelemetryConfig.Builder builder) {
- builder.config(config);
+ var generator = new OpenTelemetryConfigGenerator();
+ AnyConfigProducer pp = this;
+ AnyConfigProducer p = pp.getParent();
+ while (p != null && p != pp) {
+ if (pp instanceof ApplicationConfigProducerRoot root) {
+ generator.addStatePorts(root.getStatePorts());
+ break;
+ }
+ pp = p;
+ p = pp.getParent();
+ }
+ builder.yaml(generator.generate());
+ builder.refPaths(generator.referencedPaths());
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryConfigGenerator.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryConfigGenerator.java
index 7d18cf36f58..8e36d86911c 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryConfigGenerator.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/otel/OpenTelemetryConfigGenerator.java
@@ -1,6 +1,9 @@
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.admin.otel;
+import com.yahoo.config.model.ApplicationConfigProducerRoot.StatePortInfo;
+import java.util.List;
+
/**
* @author olaa
*/
@@ -13,7 +16,7 @@ public class OpenTelemetryConfigGenerator {
2. Processing with mapping/filtering from metric sets
3. Exporter to correct endpoint (alternatively amended)
*/
- public static String generate() {
+ public String generate() {
return """
receivers:
@@ -24,7 +27,7 @@ public class OpenTelemetryConfigGenerator {
params:
format: 'prometheus'
tls:
- ca_file: '/opt/vespa/var/vespa//trust-store.pem'
+ ca_file: '/opt/vespa/var/vespa/trust-store.pem'
cert_file: '/var/lib/sia/certs/vespa.external.cd.tenant.cert.pem'
insecure_skip_verify: true
key_file: '/var/lib/sia/keys/vespa.external.cd.tenant.key.pem'
@@ -43,4 +46,13 @@ public class OpenTelemetryConfigGenerator {
exporters: [ file ]
""";
}
+
+ void addStatePorts(List<StatePortInfo> portList) {
+ // XXX not used yet
+ }
+
+ List<String> referencedPaths() {
+ return List.of("/var/lib/sia/certs/vespa.external.cd.tenant.cert.pem",
+ "/var/lib/sia/keys/vespa.external.cd.tenant.key.pem");
+ }
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2Builder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2Builder.java
index 7f02ecacc18..692de1769d3 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2Builder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2Builder.java
@@ -7,6 +7,7 @@ import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.producer.AnyConfigProducer;
import com.yahoo.config.model.producer.TreeConfigProducer;
import com.yahoo.text.XML;
+import com.yahoo.vespa.model.HostResource;
import com.yahoo.vespa.model.SimpleConfigProducer;
import com.yahoo.vespa.model.admin.Admin;
import com.yahoo.vespa.model.admin.Configserver;
@@ -15,6 +16,8 @@ import com.yahoo.vespa.model.admin.Slobrok;
import com.yahoo.vespa.model.admin.clustercontroller.ClusterControllerCluster;
import com.yahoo.vespa.model.admin.clustercontroller.ClusterControllerContainer;
import com.yahoo.vespa.model.admin.clustercontroller.ClusterControllerContainerCluster;
+import com.yahoo.vespa.model.admin.otel.OpenTelemetryCollector;
+import com.yahoo.vespa.model.admin.otel.OpenTelemetryConfigGenerator;
import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder.DomConfigProducerBuilderBase;
import com.yahoo.vespa.model.container.Container;
import org.w3c.dom.Element;
@@ -38,10 +41,21 @@ public class DomAdminV2Builder extends DomAdminBuilderBase {
super(applicationType, multitenant, configServerSpecs);
}
+ private void addOtelcol(TreeConfigProducer<?> parent, DeployState deployState, HostResource hostResource) {
+ var otelcol = new OpenTelemetryCollector(parent);
+ otelcol.setHostResource(hostResource);
+ otelcol.initService(deployState);
+ }
+
@Override
protected void doBuildAdmin(DeployState deployState, Admin admin, Element adminE) {
List<Configserver> configservers = parseConfigservers(deployState, admin, adminE);
- admin.setLogserver(parseLogserver(deployState, admin, adminE));
+ var logserver = parseLogserver(deployState, admin, adminE);
+ admin.setLogserver(logserver);
+ if (deployState.featureFlags().logserverOtelCol()) {
+ // for manual testing
+ addOtelcol(admin, deployState, logserver.getHostResource());
+ }
admin.addConfigservers(configservers);
admin.addSlobroks(getSlobroks(deployState, admin, XML.getChild(adminE, "slobroks")));
if ( ! admin.multitenant())
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java
index 79866980170..23a46b3e065 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java
@@ -123,7 +123,7 @@ public class DomAdminV4Builder extends DomAdminBuilderBase {
private void addOtelcol(TreeConfigProducer<?> parent, DeployState deployState, HostResource hostResource) {
- var otelcol = new OpenTelemetryCollector(parent, OpenTelemetryConfigGenerator.generate());
+ var otelcol = new OpenTelemetryCollector(parent);
otelcol.setHostResource(hostResource);
otelcol.initService(deployState);
}
diff --git a/configdefinitions/src/vespa/CMakeLists.txt b/configdefinitions/src/vespa/CMakeLists.txt
index b80a7ac73e3..81e587fcace 100644
--- a/configdefinitions/src/vespa/CMakeLists.txt
+++ b/configdefinitions/src/vespa/CMakeLists.txt
@@ -36,6 +36,8 @@ vespa_generate_config(configdefinitions load-type.def)
install_config_definition(load-type.def vespa.config.content.load-type.def)
vespa_generate_config(configdefinitions logforwarder.def)
install_config_definition(logforwarder.def cloud.config.logforwarder.def)
+vespa_generate_config(configdefinitions open-telemetry.def)
+install_config_definition(open-telemetry.def cloud.config.open-telemetry.def)
vespa_generate_config(configdefinitions messagetyperouteselectorpolicy.def)
install_config_definition(messagetyperouteselectorpolicy.def vespa.config.content.messagetyperouteselectorpolicy.def)
vespa_generate_config(configdefinitions model.def)
diff --git a/configdefinitions/src/vespa/open-telemetry.def b/configdefinitions/src/vespa/open-telemetry.def
index 15a0e92b32c..3b379f6ca16 100644
--- a/configdefinitions/src/vespa/open-telemetry.def
+++ b/configdefinitions/src/vespa/open-telemetry.def
@@ -2,4 +2,7 @@
namespace=cloud.config
# For now - store entire config in one string
-config string
+yaml string
+
+# Referenced paths
+refPaths[] string
diff --git a/logforwarder/CMakeLists.txt b/logforwarder/CMakeLists.txt
index 85406cba5de..b8f7fb3c416 100644
--- a/logforwarder/CMakeLists.txt
+++ b/logforwarder/CMakeLists.txt
@@ -7,4 +7,5 @@ vespa_define_module(
APPS
src/apps/vespa-logforwarder-start
+ src/apps/vespa-otelcol-start
)
diff --git a/logforwarder/src/apps/vespa-otelcol-start/.gitignore b/logforwarder/src/apps/vespa-otelcol-start/.gitignore
new file mode 100644
index 00000000000..f2e64aa95d6
--- /dev/null
+++ b/logforwarder/src/apps/vespa-otelcol-start/.gitignore
@@ -0,0 +1 @@
+vespa-otelcol-start
diff --git a/logforwarder/src/apps/vespa-otelcol-start/CMakeLists.txt b/logforwarder/src/apps/vespa-otelcol-start/CMakeLists.txt
new file mode 100644
index 00000000000..d95ce0584c9
--- /dev/null
+++ b/logforwarder/src/apps/vespa-otelcol-start/CMakeLists.txt
@@ -0,0 +1,15 @@
+# Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(otelcol_start_app
+ SOURCES
+ main.cpp
+ cf-handler.cpp
+ child-handler.cpp
+ file-watcher.cpp
+ wrapper.cpp
+ OUTPUT_NAME vespa-otelcol-start
+ INSTALL bin
+ DEPENDS
+ config_cloudconfig
+ configdefinitions
+ vespalib
+)
diff --git a/logforwarder/src/apps/vespa-otelcol-start/cf-handler.cpp b/logforwarder/src/apps/vespa-otelcol-start/cf-handler.cpp
new file mode 100644
index 00000000000..9579271f88a
--- /dev/null
+++ b/logforwarder/src/apps/vespa-otelcol-start/cf-handler.cpp
@@ -0,0 +1,67 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "cf-handler.h"
+#include <vespa/config/common/configcontext.h>
+#include <vespa/config/common/configsystem.h>
+#include <vespa/config/common/exceptions.h>
+#include <vespa/config/helper/legacy.h>
+#include <vespa/config/subscription/configsubscriber.hpp>
+
+#include <unistd.h>
+
+#include <vespa/log/log.h>
+LOG_SETUP(".cf-handler");
+
+CfHandler::CfHandler(const std::string & configId)
+ : _subscriber(std::make_shared<config::ConfigContext>(*config::legacyConfigId2Spec(configId)))
+{}
+
+CfHandler::~CfHandler() = default;
+
+void CfHandler::subscribe(const std::string & configId, std::chrono::milliseconds timeout) {
+ LOG(info, "subscribe with config id: %s", configId.c_str());
+ std::string cfgId(config::legacyConfigId2ConfigId(configId));
+ _handle = _subscriber.subscribe<OpenTelemetryConfig>(cfgId, timeout);
+}
+
+void CfHandler::doConfigure() {
+ auto curConfig = _handle->getConfig();
+ if (_lastConfig && *curConfig == *_lastConfig) {
+ LOG(info, "same config as last");
+ return;
+ }
+ LOG(info, "new config, trigger restart");
+ _lastConfig = std::move(curConfig);
+ const OpenTelemetryConfig& config(*_lastConfig);
+ LOG(info, "watch %zu files", config.refPaths.size());
+ _fileWatcher.init(config.refPaths);
+ gotConfig(config);
+}
+
+void CfHandler::checkConfig() {
+ if (_subscriber.nextConfigNow()) {
+ doConfigure();
+ } else if (_fileWatcher.anyChanged()) {
+ LOG(info, "watched file updated, trigger restart");
+ const OpenTelemetryConfig& config(*_lastConfig);
+ gotConfig(config);
+ }
+}
+
+constexpr std::chrono::milliseconds CONFIG_TIMEOUT_MS(30 * 1000);
+
+void CfHandler::start(const std::string &configId) {
+ LOG(debug, "Reading configuration with id '%s'", configId.c_str());
+ try {
+ subscribe(configId, CONFIG_TIMEOUT_MS);
+ } catch (config::ConfigTimeoutException & ex) {
+ LOG(warning, "Timout getting config, please check your setup. Will exit and restart: %s", ex.getMessage().c_str());
+ std::_Exit(EXIT_FAILURE);
+ } catch (config::InvalidConfigException& ex) {
+ LOG(error, "Fatal: Invalid configuration, please check your setup: %s", ex.getMessage().c_str());
+ std::_Exit(EXIT_FAILURE);
+ } catch (config::ConfigRuntimeException& ex) {
+ LOG(error, "Fatal: Could not get config, please check your setup: %s", ex.getMessage().c_str());
+ std::_Exit(EXIT_FAILURE);
+ }
+}
diff --git a/logforwarder/src/apps/vespa-otelcol-start/cf-handler.h b/logforwarder/src/apps/vespa-otelcol-start/cf-handler.h
new file mode 100644
index 00000000000..7e8699caf85
--- /dev/null
+++ b/logforwarder/src/apps/vespa-otelcol-start/cf-handler.h
@@ -0,0 +1,25 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "file-watcher.h"
+#include <vespa/config-open-telemetry.h>
+#include <vespa/config/subscription/configsubscriber.h>
+
+using cloud::config::OpenTelemetryConfig;
+
+class CfHandler {
+private:
+ FileWatcher _fileWatcher;
+ config::ConfigSubscriber _subscriber;
+ config::ConfigHandle<OpenTelemetryConfig>::UP _handle = {};
+ std::unique_ptr<OpenTelemetryConfig> _lastConfig = {};
+ time_t _lastCertFileChange = 0;
+ void subscribe(const std::string & configId, std::chrono::milliseconds timeout);
+ void doConfigure();
+public:
+ CfHandler(const std::string &configId);
+ virtual ~CfHandler();
+ void start(const std::string &configId);
+ void checkConfig();
+ virtual void gotConfig(const OpenTelemetryConfig&) = 0;
+};
diff --git a/logforwarder/src/apps/vespa-otelcol-start/child-handler.cpp b/logforwarder/src/apps/vespa-otelcol-start/child-handler.cpp
new file mode 100644
index 00000000000..46ac3bea60e
--- /dev/null
+++ b/logforwarder/src/apps/vespa-otelcol-start/child-handler.cpp
@@ -0,0 +1,88 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "child-handler.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <vector>
+#include <string>
+#include <cstdlib>
+
+#include <vespa/log/log.h>
+LOG_SETUP(".child-handler");
+
+ChildHandler::ChildHandler() : _childRunning(false), _childPid(0) {}
+
+ChildHandler::~ChildHandler() = default;
+
+bool ChildHandler::checkChild() {
+ if (! _childRunning) return true;
+ int waitStatus = 0;
+ int r = waitpid(_childPid, &waitStatus, WNOHANG);
+ if (r == 0) {
+ return false;
+ }
+ if (r < 0) {
+ perror("waitpid");
+ // XXX how to handle?
+ return false;
+ }
+ _childRunning = false;
+ if (WIFEXITED(waitStatus) && WEXITSTATUS(waitStatus) == 0) {
+ // all OK
+ LOG(info, "child ran ok, exit status 0");
+ } else if (WIFEXITED(waitStatus)) {
+ LOG(warning, "child failed (exit status %d)", WEXITSTATUS(waitStatus));
+ } else if (WIFSIGNALED(waitStatus)) {
+ if (_terminating) {
+ LOG(info, "child terminated (using signal %d)", WTERMSIG(waitStatus));
+ } else {
+ LOG(warning, "child failed (exit on signal %d)", WTERMSIG(waitStatus));
+ }
+ } else {
+ LOG(warning, "child failed (abnormal exit status %d)", waitStatus);
+ }
+ return true;
+}
+
+void ChildHandler::startChild(const std::string &progPath, const std::string &cfPath) {
+ _terminating = false;
+ LOG(info, "startChild '%s' '%s'", progPath.c_str(), cfPath.c_str());
+ pid_t child = fork();
+ if (child == -1) {
+ perror("fork()");
+ return;
+ }
+ if (child == 0) {
+ std::string cfArg{"--config=file:" + cfPath};
+ const char *cargv[] = { progPath.c_str(), cfArg.c_str(), nullptr };
+ execv(progPath.c_str(), const_cast<char **>(cargv));
+ // if execv fails:
+ perror(progPath.c_str());
+ std::_Exit(1);
+ }
+ LOG(info, "child running with pid %d", (int)child);
+ _childRunning = true;
+ _childPid = child;
+}
+
+void ChildHandler::stopChild() {
+ if (! _childRunning) return;
+ LOG(info, "stopChild");
+ _terminating = true;
+ kill(_childPid, SIGTERM);
+ for (int retry = 0; retry < 10; ++retry) {
+ if (checkChild()) return;
+ usleep(12500 + retry * 20000);
+ }
+ kill(_childPid, SIGKILL);
+ for (int retry = 0; retry < 10; ++retry) {
+ if (checkChild()) return;
+ usleep(12500 + retry * 20000);
+ }
+ LOG(error, "Could not terminete child process %d", _childPid);
+}
diff --git a/logforwarder/src/apps/vespa-otelcol-start/child-handler.h b/logforwarder/src/apps/vespa-otelcol-start/child-handler.h
new file mode 100644
index 00000000000..4fd72fc7682
--- /dev/null
+++ b/logforwarder/src/apps/vespa-otelcol-start/child-handler.h
@@ -0,0 +1,18 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "cf-handler.h"
+#include <string>
+
+class ChildHandler {
+private:
+ bool _childRunning = false;
+ bool _terminating = false;
+ int _childPid = 0;
+public:
+ ChildHandler();
+ ~ChildHandler();
+ void startChild(const std::string &progPath, const std::string &cfFile);
+ void stopChild();
+ bool checkChild();
+};
diff --git a/logforwarder/src/apps/vespa-otelcol-start/file-watcher.cpp b/logforwarder/src/apps/vespa-otelcol-start/file-watcher.cpp
new file mode 100644
index 00000000000..f56a1d9d169
--- /dev/null
+++ b/logforwarder/src/apps/vespa-otelcol-start/file-watcher.cpp
@@ -0,0 +1,37 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "file-watcher.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+namespace {
+
+time_t lastModTime(const vespalib::string &fn) {
+ if (fn.empty()) return 0;
+ struct stat info;
+ if (stat(fn.c_str(), &info) != 0) return 0;
+ return info.st_mtime;
+}
+
+} // namespace
+
+bool FileWatcher::anyChanged() {
+ bool result = false;
+ for (auto &entry : watchedFiles) {
+ time_t updated = lastModTime(entry.pathName);
+ if (updated != entry.seenModTime) {
+ result = true;
+ entry.seenModTime = updated;
+ }
+ }
+ return result;
+}
+
+void FileWatcher::init(const config::StringVector &pathList) {
+ watchedFiles.clear();
+ for (const auto& path : pathList) {
+ watchedFiles.emplace_back(path, lastModTime(path));
+ }
+}
diff --git a/logforwarder/src/apps/vespa-otelcol-start/file-watcher.h b/logforwarder/src/apps/vespa-otelcol-start/file-watcher.h
new file mode 100644
index 00000000000..0f50f6d90f7
--- /dev/null
+++ b/logforwarder/src/apps/vespa-otelcol-start/file-watcher.h
@@ -0,0 +1,15 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/config-open-telemetry.h>
+
+class FileWatcher {
+ struct FileInfo {
+ vespalib::string pathName;
+ time_t seenModTime;
+ };
+ std::vector<FileInfo> watchedFiles;
+public:
+ bool anyChanged();
+ void init(const config::StringVector &pathList);
+};
diff --git a/logforwarder/src/apps/vespa-otelcol-start/main.cpp b/logforwarder/src/apps/vespa-otelcol-start/main.cpp
new file mode 100644
index 00000000000..2e3e0659707
--- /dev/null
+++ b/logforwarder/src/apps/vespa-otelcol-start/main.cpp
@@ -0,0 +1,43 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "wrapper.h"
+#include <csignal>
+#include <unistd.h>
+#include <vespa/vespalib/util/sig_catch.h>
+
+#include <vespa/defaults.h>
+#include <vespa/log/log.h>
+LOG_SETUP("vespa-otelcol-start");
+
+static void run(const char *configId) {
+ vespalib::SigCatch catcher;
+ Wrapper handler(configId);
+ handler.start(configId);
+ while (! catcher.receivedStopSignal()) {
+ handler.check();
+ usleep(125000); // Avoid busy looping;
+ }
+ handler.stop();
+};
+
+int main(int argc, char** argv) {
+ vespa::Defaults::bootstrap(argv[0]);
+ int c = -1;
+ const char *cfid = nullptr;
+ while ((c = getopt(argc, argv, "c:")) != -1) {
+ switch (c) {
+ case 'c':
+ cfid = optarg;
+ break;
+ default:
+ cfid = nullptr;
+ break;
+ }
+ }
+ if (cfid == nullptr) {
+ LOG(error, "Usage: %s -c <config-id>", argv[0]);
+ return EXIT_FAILURE;
+ }
+ run(cfid);
+ return 0;
+}
diff --git a/logforwarder/src/apps/vespa-otelcol-start/wrapper.cpp b/logforwarder/src/apps/vespa-otelcol-start/wrapper.cpp
new file mode 100644
index 00000000000..7228a3ea921
--- /dev/null
+++ b/logforwarder/src/apps/vespa-otelcol-start/wrapper.cpp
@@ -0,0 +1,76 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "wrapper.h"
+#include "child-handler.h"
+
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <vespa/defaults.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".wrapper");
+
+namespace {
+
+vespalib::string fixDir(const vespalib::string &parent, const vespalib::string &subdir) {
+ auto dirname = parent + "/" + subdir;
+ DIR *dp = opendir(dirname.c_str());
+ if (dp == NULL) {
+ if (errno != ENOENT || mkdir(dirname.c_str(), 0755) != 0) {
+ LOG(warning, "Could not create directory '%s'", dirname.c_str());
+ perror(dirname.c_str());
+ }
+ } else {
+ closedir(dp);
+ }
+ return dirname;
+}
+
+vespalib::string cfFilePath() {
+ vespalib::string path = vespa::Defaults::underVespaHome("var/db/vespa");
+ path = fixDir(path, "otelcol");
+ return path + "/" + "config.yaml";
+}
+
+void writeConfig(const vespalib::string &config, const vespalib::string &path) {
+ LOG(info, "got config, writing %s", path.c_str());
+ vespalib::string tmpPath = path + ".new";
+ FILE *fp = fopen(tmpPath.c_str(), "w");
+ if (fp == NULL) {
+ LOG(warning, "could not open '%s' for write", tmpPath.c_str());
+ return;
+ }
+ fprintf(fp, "%s\n", config.c_str());
+ fclose(fp);
+ rename(tmpPath.c_str(), path.c_str());
+}
+
+} // namespace <unnamed>
+
+Wrapper::Wrapper(const std::string &configId)
+ : CfHandler(configId),
+ _childHandler()
+{}
+
+Wrapper::~Wrapper() = default;
+
+void Wrapper::stop() {
+ _childHandler.stopChild();
+}
+
+void Wrapper::check() {
+ checkConfig();
+ if (_childHandler.checkChild()) {
+ LOG(error, "Fatal: child process died unexpectedly");
+ std::_Exit(EXIT_FAILURE);
+ }
+}
+
+void Wrapper::gotConfig(const OpenTelemetryConfig& config) {
+ _childHandler.stopChild();
+ std::string progPath = vespa::Defaults::underVespaHome("sbin/otelcol-contrib");
+ std::string cfPath = cfFilePath();
+ writeConfig(config.yaml, cfPath);
+ _childHandler.startChild(progPath, cfPath);
+}
diff --git a/logforwarder/src/apps/vespa-otelcol-start/wrapper.h b/logforwarder/src/apps/vespa-otelcol-start/wrapper.h
new file mode 100644
index 00000000000..d6ff06a4f0d
--- /dev/null
+++ b/logforwarder/src/apps/vespa-otelcol-start/wrapper.h
@@ -0,0 +1,18 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "cf-handler.h"
+#include "child-handler.h"
+
+#include <string>
+
+class Wrapper : public CfHandler {
+private:
+ ChildHandler _childHandler;
+public:
+ Wrapper(const std::string &configId);
+ ~Wrapper();
+ void check();
+ void stop();
+ void gotConfig(const OpenTelemetryConfig& config) override;
+};