EMS: Applied changes from main repo

Change-Id: I91c7cd475c0a1139f73ff76b71b90ab3aae1445b
This commit is contained in:
ipatini 2024-03-15 12:13:09 +02:00 committed by Radosław Piliszek
parent c773bc1328
commit 8212cd655d
112 changed files with 4993 additions and 537 deletions

View File

@ -45,11 +45,12 @@
</dependency>
<!-- https://mvnrepository.com/artifact/org.rauschig/jarchivelib -->
<!--XXX: Commented a not used feature with dependency with vulnerability
<dependency>
<groupId>org.rauschig</groupId>
<artifactId>jarchivelib</artifactId>
<version>1.2.0</version>
</dependency>
</dependency>-->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml -->
<dependency>
@ -61,6 +62,34 @@
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<!-- Fabric8 Kubernetes client -->
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
<version>6.10.0</version>
<exclusions>
<exclusion>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>com.squareup.okio</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
<version>3.8.0</version>
</dependency>
</dependencies>
</project>

View File

@ -9,30 +9,32 @@
package gr.iccs.imu.ems.baguette.client.install;
import gr.iccs.imu.ems.baguette.client.install.installer.K8sClientInstaller;
import gr.iccs.imu.ems.baguette.client.install.installer.SshClientInstaller;
import gr.iccs.imu.ems.baguette.client.install.installer.SshJsClientInstaller;
import gr.iccs.imu.ems.baguette.server.BaguetteServer;
import gr.iccs.imu.ems.baguette.server.ClientShellCommand;
import gr.iccs.imu.ems.baguette.server.NodeRegistryEntry;
import gr.iccs.imu.ems.brokercep.BrokerCepService;
import gr.iccs.imu.ems.common.plugin.PluginManager;
import lombok.NoArgsConstructor;
import gr.iccs.imu.ems.util.ConfigWriteService;
import gr.iccs.imu.ems.util.PasswordUtil;
import jakarta.jms.JMSException;
import jakarta.validation.constraints.NotNull;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import jakarta.jms.JMSException;
import jakarta.validation.constraints.NotNull;
import java.io.Serializable;
import java.time.Instant;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiFunction;
import static gr.iccs.imu.ems.baguette.client.install.ClientInstallationTask.TASK_TYPE;
@ -41,18 +43,18 @@ import static gr.iccs.imu.ems.baguette.client.install.ClientInstallationTask.TAS
*/
@Slf4j
@Service
@NoArgsConstructor
@RequiredArgsConstructor
public class ClientInstaller implements InitializingBean {
private static ClientInstaller singleton;
@Autowired
private ClientInstallationProperties properties;
@Autowired
private BrokerCepService brokerCepService;
@Autowired
private BaguetteServer baguetteServer;
@Autowired
private PluginManager pluginManager;
private final ClientInstallationProperties properties;
private final BrokerCepService brokerCepService;
private final BaguetteServer baguetteServer;
private final PluginManager pluginManager;
private final List<ClientInstallerPlugin> clientInstallerPluginList;
private final ConfigWriteService configWriteService;
private final PasswordUtil passwordUtil;
private final AtomicLong taskCounter = new AtomicLong();
private ExecutorService executorService;
@ -130,6 +132,12 @@ public class ClientInstaller implements InitializingBean {
return executeVmOrBaremetalTask(task, taskCounter);
} else
if ("K8S".equalsIgnoreCase(task.getType())) {
if (baguetteServer.getNodeRegistry().getCoordinator()==null)
throw new IllegalStateException("Baguette Server Coordinator has not yet been initialized");
return executeKubernetesTask(task, taskCounter);
} else
//if ("DIAGNOSTICS".equalsIgnoreCase(task.getType())) {
if (task.getTaskType()==TASK_TYPE.DIAGNOSTICS) {
return executeDiagnosticsTask(task, taskCounter);
@ -139,7 +147,10 @@ public class ClientInstaller implements InitializingBean {
return false;
}
private boolean executeVmOrBaremetalTask(ClientInstallationTask task, long taskCounter) {
private boolean executeInstaller(ClientInstallationTask task, long taskCounter,
BiFunction<ClientInstallationTask,Long,Boolean> condition,
BiFunction<ClientInstallationTask,Long,Boolean> installerExecution)
{
NodeRegistryEntry entry = baguetteServer.getNodeRegistry().getNodeByAddress(task.getAddress());
if (task.isNodeMustBeInRegistry() && entry==null)
throw new IllegalStateException("Node entry has been removed from Node Registry before installation: Node IP address: "+ task.getAddress());
@ -156,7 +167,7 @@ public class ClientInstaller implements InitializingBean {
}
boolean success;
if (! task.getInstructionSets().isEmpty()) {
if (condition==null || condition.apply(task, taskCounter)) {
// Starting installation
entry.nodeInstalling(task);
@ -166,7 +177,7 @@ public class ClientInstaller implements InitializingBean {
.forEach(plugin -> ((InstallationContextProcessorPlugin) plugin).processBeforeInstallation(task, taskCounter));
log.debug("ClientInstaller: INSTALLATION: Executing installation task: task-counter={}, task={}", taskCounter, task);
success = executeVmTask(task, taskCounter);
success = installerExecution.apply(task, taskCounter);
log.debug("ClientInstaller: NODE_REGISTRY_ENTRY after installation execution: \n{}", task.getNodeRegistryEntry());
if (entry.getState() == NodeRegistryEntry.STATE.INSTALLING) {
@ -179,7 +190,7 @@ public class ClientInstaller implements InitializingBean {
pluginManager.getActivePlugins(InstallationContextProcessorPlugin.class)
.forEach(plugin -> ((InstallationContextProcessorPlugin) plugin).processAfterInstallation(task, taskCounter, success));
} else {
log.debug("ClientInstaller: SKIP INSTALLATION: Task has no instructions sets. Skipping execution: Node IP address: "+ task.getAddress());
log.debug("ClientInstaller: SKIP INSTALLATION: due to condition. Skipping execution: Node IP address: "+ task.getAddress());
success = true;
}
@ -203,6 +214,27 @@ public class ClientInstaller implements InitializingBean {
return success;
}
private boolean executeVmOrBaremetalTask(ClientInstallationTask task, long taskCounter) {
return executeInstaller(task, taskCounter, (t,c) -> ! t.getInstructionSets().isEmpty(), this::executeVmTask);
}
private boolean executeKubernetesTask(ClientInstallationTask task, long taskCounter) {
return executeInstaller(task, taskCounter, (t,c) -> true, (t,c) -> {
boolean result;
log.info("ClientInstaller: Using K8sClientInstaller for task #{}", taskCounter);
result = K8sClientInstaller.builder()
.task(task)
.taskCounter(taskCounter)
.properties(properties)
.configWriteService(configWriteService)
.passwordUtil(passwordUtil)
.build()
.execute();
log.info("ClientInstaller: Task execution result #{}: success={}", taskCounter, result);
return result;
});
}
private boolean executeDiagnosticsTask(ClientInstallationTask task, long taskCounter) {
log.debug("ClientInstaller: DIAGNOSTICS: Executing diagnostics task: task-counter={}, task={}", taskCounter, task);
boolean success = executeVmTask(task, taskCounter);

View File

@ -23,15 +23,14 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringSubstitutor;
import org.apache.tomcat.util.net.SSLHostConfig;
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
import org.rauschig.jarchivelib.Archiver;
import org.rauschig.jarchivelib.ArchiverFactory;
//import org.rauschig.jarchivelib.Archiver;
//import org.rauschig.jarchivelib.ArchiverFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
import org.springframework.context.ApplicationListener;
import org.springframework.core.io.FileSystemResource;
import org.springframework.stereotype.Service;
import java.io.*;
import java.nio.file.*;
@ -43,31 +42,24 @@ import java.util.stream.Collectors;
* Baguette Client installation helper
*/
@Slf4j
@Service
public abstract class AbstractInstallationHelper implements InitializingBean, ApplicationListener<WebServerInitializedEvent>, InstallationHelper {
protected final static String LINUX_OS_FAMILY = "LINUX";
protected final static String WINDOWS_OS_FAMILY = "WINDOWS";
protected static AbstractInstallationHelper instance = null;
@Autowired
@Getter @Setter
protected ClientInstallationProperties properties;
@Autowired
protected PasswordUtil passwordUtil;
protected String archiveBase64;
//XXX: Commented a not used feature with dependency with vulnerability
// protected String archiveBase64;
protected boolean isServerSecure;
protected String serverCert;
public synchronized static AbstractInstallationHelper getInstance() {
return instance;
}
@Override
public void afterPropertiesSet() {
log.debug("AbstractInstallationHelper.afterPropertiesSet(): class={}: configuration: {}", getClass().getName(), properties);
AbstractInstallationHelper.instance = this;
}
@Override
@ -115,7 +107,7 @@ public abstract class AbstractInstallationHelper implements InitializingBean, Ap
String certPem = KeystoreUtil.exportCertificateAsPEM(c);
log.debug("AbstractInstallationHelper.initServerCertificate(): SSL certificate[{}]: {}: \n{}", n, m, certPem);
// Append PEM certificate to 'sb'
sb.append(certPem).append(System.getProperty("line.separator"));
sb.append(certPem).append(System.lineSeparator());
m++;
}
// The first entry is used as the server certificate
@ -182,6 +174,7 @@ public abstract class AbstractInstallationHelper implements InitializingBean, Ap
}
// Create baguette client configuration archive
/*XXX: Commented a not used feature with dependency with vulnerability
Archiver archiver = ArchiverFactory.createArchiver(archiveFile);
String tempFileName = "archive_" + System.currentTimeMillis();
log.debug("AbstractInstallationHelper: Temp. archive name: {}", tempFileName);
@ -198,6 +191,7 @@ public abstract class AbstractInstallationHelper implements InitializingBean, Ap
byte[] archiveBytes = Files.readAllBytes(archiveFile.toPath());
this.archiveBase64 = Base64.getEncoder().encodeToString(archiveBytes);
log.debug("AbstractInstallationHelper: Archive Base64 encoded: {}", archiveBase64);
*/
}
private String getResourceAsString(String resourcePath) throws IOException {

View File

@ -39,8 +39,12 @@ public class InstallationHelperFactory implements InitializingBean {
public InstallationHelper createInstallationHelper(NodeRegistryEntry entry) {
String nodeType = entry.getPreregistration().get("type");
log.debug("InstallationHelperFactory: Node type: {}", nodeType);
if ("VM".equalsIgnoreCase(nodeType) || "baremetal".equalsIgnoreCase(nodeType)) {
return createVmInstallationHelper(entry);
} else
if ("K8S".equalsIgnoreCase(nodeType)) {
return createK8sInstallationHelper(entry);
}
throw new IllegalArgumentException("Unsupported or missing Node type: "+nodeType);
}
@ -58,6 +62,12 @@ public class InstallationHelperFactory implements InitializingBean {
}
private InstallationHelper createVmInstallationHelper(NodeRegistryEntry entry) {
log.debug("InstallationHelperFactory: Returning VmInstallationHelper");
return VmInstallationHelper.getInstance();
}
private InstallationHelper createK8sInstallationHelper(NodeRegistryEntry entry) {
log.debug("InstallationHelperFactory: Returning K8sInstallationHelper");
return K8sInstallationHelper.getInstance();
}
}

View File

@ -0,0 +1,118 @@
/*
* Copyright (C) 2017-2025 Institute of Communication and Computer Systems (imu.iccs.gr)
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v2.0, unless
* Esper library is used, in which case it is subject to the terms of General Public License v2.0.
* If a copy of the MPL was not distributed with this file, you can obtain one at
* https://www.mozilla.org/en-US/MPL/2.0/
*/
package gr.iccs.imu.ems.baguette.client.install.helper;
import gr.iccs.imu.ems.baguette.client.install.ClientInstallationTask;
import gr.iccs.imu.ems.baguette.client.install.instruction.InstructionsSet;
import gr.iccs.imu.ems.baguette.server.NodeRegistryEntry;
import gr.iccs.imu.ems.translate.TranslationContext;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* Baguette Client installation helper for Kubernetes
*/
@Slf4j
@Service
public class K8sInstallationHelper extends AbstractInstallationHelper {
private static K8sInstallationHelper instance;
public static AbstractInstallationHelper getInstance() {
return instance;
}
@Override
public void afterPropertiesSet() {
log.debug("K8sInstallationHelper.afterPropertiesSet(): configuration: {}", properties);
instance = this;
}
@Override
public ClientInstallationTask createClientInstallationTask(NodeRegistryEntry entry, TranslationContext translationContext) throws IOException {
return createClientTask(ClientInstallationTask.TASK_TYPE.INSTALL, entry, translationContext);
}
@Override
public ClientInstallationTask createClientReinstallTask(NodeRegistryEntry entry, TranslationContext translationContext) throws IOException {
return createClientTask(ClientInstallationTask.TASK_TYPE.REINSTALL, entry, translationContext);
}
@Override
public ClientInstallationTask createClientUninstallTask(NodeRegistryEntry entry, TranslationContext translationContext) throws Exception {
return createClientTask(ClientInstallationTask.TASK_TYPE.UNINSTALL, entry, translationContext);
}
private ClientInstallationTask createClientTask(@NonNull ClientInstallationTask.TASK_TYPE taskType,
NodeRegistryEntry entry,
TranslationContext translationContext)
{
Map<String, String> nodeMap = initializeNodeMap(entry);
String baseUrl = nodeMap.get("BASE_URL");
String clientId = nodeMap.get("CLIENT_ID");
String ipSetting = nodeMap.get("IP_SETTING");
String requestId = nodeMap.get("requestId");
// Extract node identification and type information
String nodeId = nodeMap.get("id");
String nodeAddress = nodeMap.get("address");
String nodeType = nodeMap.get("type");
String nodeName = nodeMap.get("name");
String nodeProvider = nodeMap.get("provider");
if (StringUtils.isBlank(nodeType)) nodeType = "K8S";
// Create Installation Task for VM node
ClientInstallationTask task = ClientInstallationTask.builder()
.id(clientId)
.taskType(taskType)
.nodeId(nodeId)
.requestId(requestId)
.name(nodeName)
.address(nodeAddress)
.type(nodeType)
.provider(nodeProvider)
.nodeRegistryEntry(entry)
.translationContext(translationContext)
.build();
log.debug("K8sInstallationHelper.createClientTask(): Created client task: {}", task);
return task;
}
private Map<String, String> initializeNodeMap(NodeRegistryEntry entry) {
return entry.getPreregistration();
}
@Override
public List<InstructionsSet> prepareInstallationInstructionsForWin(NodeRegistryEntry entry) {
return null;
}
@Override
public List<InstructionsSet> prepareInstallationInstructionsForLinux(NodeRegistryEntry entry) throws IOException {
return null;
}
@Override
public List<InstructionsSet> prepareUninstallInstructionsForWin(NodeRegistryEntry entry) {
return null;
}
@Override
public List<InstructionsSet> prepareUninstallInstructionsForLinux(NodeRegistryEntry entry) throws IOException {
return null;
}
}

View File

@ -23,11 +23,10 @@ import gr.iccs.imu.ems.translate.TranslationContext;
import gr.iccs.imu.ems.util.CredentialsMap;
import gr.iccs.imu.ems.util.NetUtil;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringSubstitutor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;
import java.io.IOException;
@ -44,6 +43,7 @@ import java.util.stream.Collectors;
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class VmInstallationHelper extends AbstractInstallationHelper {
private final static SimpleDateFormat tsW3C = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
private final static SimpleDateFormat tsUTC = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
@ -54,10 +54,19 @@ public class VmInstallationHelper extends AbstractInstallationHelper {
tsFile.setTimeZone(TimeZone.getDefault());
}
@Autowired
private ResourceLoader resourceLoader;
@Autowired
private ClientInstallationProperties clientInstallationProperties;
private static VmInstallationHelper instance;
private final ClientInstallationProperties clientInstallationProperties;
public static AbstractInstallationHelper getInstance() {
return instance;
}
@Override
public void afterPropertiesSet() {
log.debug("VmInstallationHelper.afterPropertiesSet(): configuration: {}", properties);
instance = this;
}
@Override
public ClientInstallationTask createClientInstallationTask(NodeRegistryEntry entry, TranslationContext translationContext) throws IOException {

View File

@ -0,0 +1,269 @@
/*
* Copyright (C) 2017-2025 Institute of Communication and Computer Systems (imu.iccs.gr)
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v2.0, unless
* Esper library is used, in which case it is subject to the terms of General Public License v2.0.
* If a copy of the MPL was not distributed with this file, you can obtain one at
* https://www.mozilla.org/en-US/MPL/2.0/
*/
package gr.iccs.imu.ems.baguette.client.install.installer;
import gr.iccs.imu.ems.baguette.client.install.ClientInstallationProperties;
import gr.iccs.imu.ems.baguette.client.install.ClientInstallationTask;
import gr.iccs.imu.ems.baguette.client.install.ClientInstallerPlugin;
import gr.iccs.imu.ems.util.ConfigWriteService;
import gr.iccs.imu.ems.util.EmsConstant;
import gr.iccs.imu.ems.util.EmsRelease;
import gr.iccs.imu.ems.util.PasswordUtil;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.api.model.apps.DaemonSet;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.fabric8.kubernetes.client.dsl.Resource;
import lombok.Builder;
import lombok.Getter;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringSubstitutor;
import org.springframework.util.StreamUtils;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* EMS client installer on a Kubernetes cluster
*/
@Slf4j
@Getter
public class K8sClientInstaller implements ClientInstallerPlugin {
private static final String K8S_SERVICE_ACCOUNT_SECRETS_PATH_DEFAULT = "/var/run/secrets/kubernetes.io/serviceaccount";
private static final String APP_CONFIG_MAP_NAME_DEFAULT = "monitoring-configmap";
private static final String EMS_CLIENT_CONFIG_MAP_NAME_DEFAULT = "ems-client-configmap";
private static final String EMS_CLIENT_DAEMONSET_SPECIFICATION_FILE_DEFAULT = "/ems-client-daemonset.yaml";
private static final String EMS_CLIENT_DAEMONSET_NAME_DEFAULT = "ems-client-daemonset";
private static final String EMS_CLIENT_DAEMONSET_IMAGE_REPOSITORY_DEFAULT = "registry.gitlab.com/nebulous-project/ems-main/ems-client";
private static final String EMS_CLIENT_DAEMONSET_IMAGE_TAG_DEFAULT = EmsRelease.EMS_VERSION;
private static final String EMS_CLIENT_DAEMONSET_IMAGE_PULL_POLICY_DEFAULT = "Always";
private final ClientInstallationTask task;
private final long taskCounter;
private final ClientInstallationProperties properties;
private final ConfigWriteService configWriteService;
private final PasswordUtil passwordUtil;
private String additionalCredentials; // Those specified in EMS_CLIENT_ADDITIONAL_BROKER_CREDENTIALS, plus one generated
private String brokerUsername;
private String brokerPassword;
@Builder
public K8sClientInstaller(ClientInstallationTask task, long taskCounter, ClientInstallationProperties properties,
ConfigWriteService configWriteService, PasswordUtil passwordUtil)
{
this.task = task;
this.taskCounter = taskCounter;
this.properties = properties;
this.configWriteService = configWriteService;
this.passwordUtil = passwordUtil;
initializeAdditionalCredentials();
}
private void initializeAdditionalCredentials() {
brokerUsername = getConfig("EMS_CLIENT_BROKER_USERNAME", "user-" + RandomStringUtils.randomAlphanumeric(32));
brokerPassword = getConfig("EMS_CLIENT_BROKER_PASSWORD", RandomStringUtils.randomAlphanumeric(32));
StringBuilder sb = new StringBuilder(getConfig("EMS_CLIENT_ADDITIONAL_BROKER_CREDENTIALS", ""));
if (StringUtils.isNotBlank(sb))
sb.append(", ");
sb.append(brokerUsername).append("/").append(brokerPassword);
additionalCredentials = sb.toString();
}
private String getConfig(@NonNull String key, String defaultValue) {
String value = System.getenv(key);
return value==null ? defaultValue : value;
}
@Override
public boolean executeTask() {
boolean success = true;
try {
deployOnCluster();
} catch (Exception ex) {
log.error("K8sClientInstaller: Failed executing installation instructions for task #{}, Exception: ", taskCounter, ex);
success = false;
}
if (success) log.info("K8sClientInstaller: Task completed successfully #{}", taskCounter);
else log.info("K8sClientInstaller: Error occurred while executing task #{}", taskCounter);
return true;
}
private void deployOnCluster() throws IOException {
boolean dryRun = Boolean.parseBoolean( getConfig("EMS_CLIENT_DEPLOYMENT_DRY_RUN", "false") );
if (dryRun)
log.warn("K8sClientInstaller: NOTE: Dry Run set!! Will not make any changes to cluster");
deployOnCluster(dryRun);
}
private void deployOnCluster(boolean dryRun) throws IOException {
String serviceAccountPath = getConfig("K8S_SERVICE_ACCOUNT_SECRETS_PATH", K8S_SERVICE_ACCOUNT_SECRETS_PATH_DEFAULT);
String masterUrl = getConfig("KUBERNETES_SERVICE_HOST", null);
String caCert = Files.readString(Paths.get(serviceAccountPath, "ca.crt"));
String token = Files.readString(Paths.get(serviceAccountPath, "token"));
String namespace = Files.readString(Paths.get(serviceAccountPath, "namespace"));
log.debug("""
K8sClientInstaller:
Master URL: {}
CA cert.:
{}
Token: {}
Namespace: {}""",
masterUrl, caCert.trim(), passwordUtil.encodePassword(token), namespace);
// Configure and start Kubernetes API client
Config config = new ConfigBuilder()
.withMasterUrl(masterUrl)
.withCaCertData(caCert)
.withOauthToken(token)
.build();
try (KubernetesClient client = new KubernetesClientBuilder().withConfig(config).build()) {
// Prepare ems client config map
createEmsClientConfigMap(dryRun, client, namespace);
// Prepare application config map
createAppConfigMap(dryRun, client, namespace);
// Deploy ems client daemonset
createEmsClientDaemonSet(dryRun, client, namespace);
task.getNodeRegistryEntry().nodeInstallationComplete(null);
}
}
private void createEmsClientConfigMap(boolean dryRun, KubernetesClient client, String namespace) {
log.debug("K8sClientInstaller.createEmsClientConfigMap: BEGIN: dry-run={}, namespace={}", dryRun, namespace);
// Get ems client configmap name
String configMapName = getConfig("EMS_CLIENT_CONFIG_MAP_NAME", EMS_CLIENT_CONFIG_MAP_NAME_DEFAULT);
log.debug("K8sClientInstaller: configMap: name: {}", configMapName);
// Get ems client configuration
Map<String, String> configMapMap = configWriteService
.getConfigFile(EmsConstant.EMS_CLIENT_K8S_CONFIG_MAP_FILE).getContentMap();
log.debug("K8sClientInstaller: configMap: data:\n{}", configMapMap);
// Create ems client configmap
Resource<ConfigMap> configMapResource = client.configMaps()
.inNamespace(namespace)
.resource(new ConfigMapBuilder()
.withNewMetadata().withName(configMapName).endMetadata()
.addToData("creationTimestamp", Long.toString(Instant.now().getEpochSecond()))
.addToData(configMapMap)
.build());
log.trace("K8sClientInstaller: ConfigMap to create: {}", configMapResource);
if (!dryRun) {
ConfigMap configMap = configMapResource.serverSideApply();
log.debug("K8sClientInstaller: ConfigMap created: {}", configMap);
} else {
log.warn("K8sClientInstaller: DRY-RUN: Didn't create ems client configmap");
}
}
private void createAppConfigMap(boolean dryRun, KubernetesClient client, String namespace) {
log.debug("K8sClientInstaller.createAppConfigMap: BEGIN: dry-run={}, namespace={}", dryRun, namespace);
// Get ems client configmap name
String configMapName = getConfig("APP_CONFIG_MAP_NAME", APP_CONFIG_MAP_NAME_DEFAULT);
log.debug("K8sClientInstaller: App configMap: name: {}", configMapName);
// Get App ems-client-related configuration
Map<String, String> configMapMap = new LinkedHashMap<>();
configMapMap.put("BROKER_USERNAME", brokerUsername);
configMapMap.put("BROKER_PASSWORD", brokerPassword);
log.debug("K8sClientInstaller: App configMap: data:\n{}", configMapMap);
// Create ems client configmap
Resource<ConfigMap> configMapResource = client.configMaps()
.inNamespace(namespace)
.resource(new ConfigMapBuilder()
.withNewMetadata().withName(configMapName).endMetadata()
.addToData("creationTimestamp", Long.toString(Instant.now().getEpochSecond()))
.addToData(configMapMap)
.build());
log.trace("K8sClientInstaller: App ConfigMap to create: {}", configMapResource);
if (!dryRun) {
ConfigMap configMap = configMapResource.serverSideApply();
log.debug("K8sClientInstaller: App ConfigMap created: {}", configMap);
} else {
log.warn("K8sClientInstaller: DRY-RUN: Didn't create App ems client configmap");
}
}
private void createEmsClientDaemonSet(boolean dryRun, KubernetesClient client, String namespace) throws IOException {
log.debug("K8sClientInstaller.createEmsClientDaemonSet: BEGIN: dry-run={}, namespace={}", dryRun, namespace);
String resourceName = getConfig("EMS_CLIENT_DAEMONSET_SPECIFICATION_FILE", EMS_CLIENT_DAEMONSET_SPECIFICATION_FILE_DEFAULT);
Map<String,String> values = Map.of(
"EMS_CLIENT_DAEMONSET_NAME", getConfig("EMS_CLIENT_DAEMONSET_NAME", EMS_CLIENT_DAEMONSET_NAME_DEFAULT),
"EMS_CLIENT_DAEMONSET_IMAGE_REPOSITORY", getConfig("EMS_CLIENT_DAEMONSET_IMAGE_REPOSITORY", EMS_CLIENT_DAEMONSET_IMAGE_REPOSITORY_DEFAULT),
"EMS_CLIENT_DAEMONSET_IMAGE_TAG", getConfig("EMS_CLIENT_DAEMONSET_IMAGE_TAG", EMS_CLIENT_DAEMONSET_IMAGE_TAG_DEFAULT),
"EMS_CLIENT_DAEMONSET_IMAGE_PULL_POLICY", getConfig("EMS_CLIENT_DAEMONSET_IMAGE_PULL_POLICY", EMS_CLIENT_DAEMONSET_IMAGE_PULL_POLICY_DEFAULT),
"EMS_CLIENT_CONFIG_MAP_NAME", getConfig("EMS_CLIENT_CONFIG_MAP_NAME", EMS_CLIENT_CONFIG_MAP_NAME_DEFAULT),
"EMS_CLIENT_ADDITIONAL_BROKER_CREDENTIALS", additionalCredentials,
"EMS_CLIENT_KEYSTORE_SECRET", getConfig("EMS_CLIENT_KEYSTORE_SECRET", ""),
"EMS_CLIENT_TRUSTSTORE_SECRET", getConfig("EMS_CLIENT_TRUSTSTORE_SECRET", "")
);
log.debug("K8sClientInstaller: resourceName: {}", namespace);
log.debug("K8sClientInstaller: values: {}", values);
String spec;
try (InputStream inputStream = K8sClientInstaller.class.getResourceAsStream(resourceName)) {
spec = StreamUtils.copyToString(inputStream, Charset.defaultCharset());
log.trace("K8sClientInstaller: Ems client daemonset spec BEFORE:\n{}", spec);
spec = StringSubstitutor.replace(spec, values);
log.trace("K8sClientInstaller: Ems client daemonset spec AFTER :\n{}", spec);
}
if (StringUtils.isNotBlank(spec)) {
try (InputStream stream = new ByteArrayInputStream(spec.getBytes(StandardCharsets.UTF_8))) {
// Load a DaemonSet object
DaemonSet daemonSet = client.apps().daemonSets().load(stream).item();
log.debug("K8sClientInstaller: DaemonSet to create: {} :: {}", daemonSet.hashCode(), daemonSet.getMetadata().getName());
// Deploy the DaemonSet
if (!dryRun) {
DaemonSet ds = client.apps().daemonSets().inNamespace(namespace).resource(daemonSet).create();
log.debug("K8sClientInstaller: DaemonSet created: {} :: {}", ds.hashCode(), ds.getMetadata().getName());
} else {
log.warn("K8sClientInstaller: DRY-RUN: Didn't create ems client daemonset");
}
}
} else {
log.warn("K8sClientInstaller: ERROR: Ems client daemonset spec is empty");
}
}
@Override
public void preProcessTask() {
// Throw exception to prevent task exception, if task data have problem
}
@Override
public boolean postProcessTask() {
return true;
}
}

View File

@ -7,8 +7,9 @@
* https://www.mozilla.org/en-US/MPL/2.0/
*/
package gr.iccs.imu.ems.baguette.client.install;
package gr.iccs.imu.ems.baguette.client.install.installer;
import gr.iccs.imu.ems.baguette.client.install.*;
import gr.iccs.imu.ems.baguette.client.install.instruction.INSTRUCTION_RESULT;
import gr.iccs.imu.ems.baguette.client.install.instruction.Instruction;
import gr.iccs.imu.ems.baguette.client.install.instruction.InstructionsService;
@ -32,6 +33,9 @@ import org.apache.sshd.mina.MinaServiceFactoryFactory;
import org.apache.sshd.scp.client.DefaultScpClientCreator;
import org.apache.sshd.scp.client.ScpClient;
import org.apache.sshd.scp.client.ScpClientCreator;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
@ -212,10 +216,12 @@ public class SshClientInstaller implements ClientInstallerPlugin {
.verify(connectTimeout)
.getSession();
if (StringUtils.isNotBlank(privateKey)) {
PrivateKey privKey = getPrivateKey(privateKey);
/*PrivateKey privKey = getPrivateKey(privateKey);
//PublicKey pubKey = getPublicKey(publicKeyStr);
PublicKey pubKey = getPublicKey(privKey);
KeyPair keyPair = new KeyPair(pubKey, privKey);
*/
KeyPair keyPair = getKeyPairFromPEM(privateKey);
session.addPublicKeyIdentity(keyPair);
}
if (StringUtils.isNotBlank(password)) {
@ -249,6 +255,28 @@ public class SshClientInstaller implements ClientInstallerPlugin {
//PrivateKey privKey = kf.generatePrivate(keySpecPKCS8);
}
private KeyPair getKeyPairFromPEM(String pemStr) throws IOException {
// Load the PEM file containing the private key
log.trace("SshClientInstaller: getKeyPairFromPEM: pemStr: {}", pemStr);
pemStr = pemStr.replace("\\n", "\n");
try (StringReader keyReader = new StringReader(pemStr); PEMParser pemParser = new PEMParser(keyReader)) {
// Parse the PEM encoded private key
Object pemObject = pemParser.readObject();
log.trace("SshClientInstaller: getKeyPairFromPEM: pemObject: {}", pemObject);
// Convert the PEM object to a KeyPair
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
if (pemObject instanceof PEMKeyPair pemKeyPair) {
KeyPair keyPair = converter.getKeyPair(pemKeyPair);
log.trace("SshClientInstaller: getKeyPairFromPEM: keyPair: {}", keyPair);
return keyPair;
} else {
log.warn("SshClientInstaller: getKeyPairFromPEM: Failed to parse PEM private key");
throw new RuntimeException("Failed to parse PEM private key");
}
}
}
private PublicKey getPublicKey(PrivateKey privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory factory = KeyFactory.getInstance(privateKey.getAlgorithm());
PKCS8EncodedKeySpec pubKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded());

View File

@ -7,8 +7,10 @@
* https://www.mozilla.org/en-US/MPL/2.0/
*/
package gr.iccs.imu.ems.baguette.client.install;
package gr.iccs.imu.ems.baguette.client.install.installer;
import gr.iccs.imu.ems.baguette.client.install.ClientInstallationProperties;
import gr.iccs.imu.ems.baguette.client.install.ClientInstallationTask;
import gr.iccs.imu.ems.baguette.client.install.instruction.INSTRUCTION_RESULT;
import gr.iccs.imu.ems.baguette.client.install.instruction.InstructionsSet;
import lombok.Builder;

View File

@ -7,7 +7,7 @@
* https://www.mozilla.org/en-US/MPL/2.0/
*/
package gr.iccs.imu.ems.baguette.client.install;
package gr.iccs.imu.ems.baguette.client.install.installer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

View File

@ -9,6 +9,9 @@
package gr.iccs.imu.ems.baguette.client.install.plugin;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import gr.iccs.imu.ems.baguette.client.install.ClientInstallationTask;
import gr.iccs.imu.ems.baguette.client.install.InstallationContextProcessorPlugin;
import gr.iccs.imu.ems.translate.model.Monitor;
@ -23,8 +26,8 @@ import java.util.*;
/**
* Installation context processor plugin for generating 'allowed-topics' setting
* used in baguette-client[.yml/.properties] config. file.
* It set the 'COLLECTOR_ALLOWED_TOPICS' variable in pre-registration context.
* used in baguette-client[.yml/.properties] configuration file.
* It sets the 'COLLECTOR_ALLOWED_TOPICS' variable in pre-registration context.
*/
@Slf4j
@Data
@ -37,6 +40,7 @@ public class AllowedTopicsProcessorPlugin implements InstallationContextProcesso
StringBuilder sbAllowedTopics = new StringBuilder();
Set<String> addedTopicsSet = new HashSet<>();
Map<String, List<Object>> collectorConfigs = new LinkedHashMap<>();
boolean first = true;
for (Monitor monitor : task.getTranslationContext().getMON()) {
@ -53,7 +57,7 @@ public class AllowedTopicsProcessorPlugin implements InstallationContextProcesso
}
// Get sensor configuration
Map<String,Object> sensorConfig = monitor.getSensor().getConfiguration();;
Map<String,Object> sensorConfig = monitor.getSensor().getConfiguration();
// Process Destination aliases, if specified in configuration
if (sensorConfig!=null) {
@ -72,6 +76,14 @@ public class AllowedTopicsProcessorPlugin implements InstallationContextProcesso
}
}
}
if (monitor.getSensor().isPullSensor()) {
if (sensorConfig.get("type") instanceof String type && StringUtils.isNotBlank(type)) {
collectorConfigs
.computeIfAbsent(type, key->new LinkedList<>())
.add(monitor.getSensor());
}
}
}
log.trace("AllowedTopicsProcessorPlugin: Task #{}: MONITOR: metric={}, allowed-topics={}",
@ -86,7 +98,28 @@ public class AllowedTopicsProcessorPlugin implements InstallationContextProcesso
String allowedTopics = sbAllowedTopics.toString();
log.debug("AllowedTopicsProcessorPlugin: Task #{}: Allowed-Topics configuration for collectors: \n{}", taskCounter, allowedTopics);
String collectorConfigsStr = null;
try {
if (! collectorConfigs.isEmpty()) {
log.debug("AllowedTopicsProcessorPlugin: Task #{}: Pull-Sensor collector configurations: \n{}", taskCounter, collectorConfigs);
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
/*mapper.setFilterProvider(new SimpleFilterProvider().addFilter("customerFilter",
SimpleBeanPropertyFilter.serializeAllExcept("@objectClass")));*/
collectorConfigsStr = mapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(collectorConfigs);
if (StringUtils.isBlank(collectorConfigsStr))
collectorConfigsStr = null;
}
} catch (JsonProcessingException e) {
log.error("AllowedTopicsProcessorPlugin: Task #{}: EXCEPTION while processing sensor configs. Skipping them.\n",
taskCounter, e);
}
log.debug("AllowedTopicsProcessorPlugin: Task #{}: Pull-Sensor collector configurations String: \n{}", taskCounter, collectorConfigsStr);
task.getNodeRegistryEntry().getPreregistration().put(EmsConstant.COLLECTOR_ALLOWED_TOPICS_VAR, allowedTopics);
task.getNodeRegistryEntry().getPreregistration().put(EmsConstant.COLLECTOR_CONFIGURATIONS_VAR, collectorConfigsStr);
log.debug("AllowedTopicsProcessorPlugin: Task #{}: processBeforeInstallation: END", taskCounter);
}

View File

@ -83,7 +83,7 @@ public class PrometheusProcessorPlugin implements InstallationContextProcessorPl
// Get monitor interval
Interval interval = monitor.getSensor().pullSensor().getInterval();
if (interval != null) {
int period = interval.getPeriod();
long period = interval.getPeriod();
TimeUnit unit = TimeUnit.SECONDS;
if (interval.getUnit() != null) {
unit = TimeUnit.valueOf( interval.getUnit().name() );

View File

@ -0,0 +1,188 @@
/*
* Copyright (C) 2017-2025 Institute of Communication and Computer Systems (imu.iccs.gr)
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v2.0, unless
* Esper library is used, in which case it is subject to the terms of General Public License v2.0.
* If a copy of the MPL was not distributed with this file, you can obtain one at
* https://www.mozilla.org/en-US/MPL/2.0/
*/
package gr.iccs.imu.ems.baguette.client.install.watch;
import gr.iccs.imu.ems.baguette.server.NodeRegistry;
import gr.iccs.imu.ems.baguette.server.NodeRegistryEntry;
import gr.iccs.imu.ems.util.PasswordUtil;
import io.fabric8.kubernetes.api.model.Node;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.fabric8.kubernetes.client.dsl.Resource;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Service;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;
/**
* Kubernetes cluster pods watcher service
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class K8sPodWatcher implements InitializingBean {
private static final String K8S_SERVICE_ACCOUNT_SECRETS_PATH_DEFAULT = "/var/run/secrets/kubernetes.io/serviceaccount";
private final TaskScheduler taskScheduler;
private final PasswordUtil passwordUtil;
private final NodeRegistry nodeRegistry;
@Override
public void afterPropertiesSet() throws Exception {
if (Boolean.parseBoolean(getConfig("K8S_WATCHER_ENABLED", "true"))) {
taskScheduler.scheduleWithFixedDelay(this::doWatch, Instant.now().plusSeconds(20), Duration.ofSeconds(10));
} else {
log.warn("K8sPodWatcher: Disabled (set K8S_WATCHER_ENABLED=true to enable)");
}
}
private String getConfig(@NonNull String key, String defaultValue) {
String value = System.getenv(key);
return value==null ? defaultValue : value;
}
private void doWatch() {
Map<String, NodeEntry> addressToNodeMap;
Map<String, Set<PodEntry>> addressToPodMap;
try {
log.debug("K8sPodWatcher: BEGIN: doWatch");
String serviceAccountPath = getConfig("K8S_SERVICE_ACCOUNT_SECRETS_PATH", K8S_SERVICE_ACCOUNT_SECRETS_PATH_DEFAULT);
String masterUrl = getConfig("KUBERNETES_SERVICE_HOST", null);
String caCert = Files.readString(Paths.get(serviceAccountPath, "ca.crt"));
String token = Files.readString(Paths.get(serviceAccountPath, "token"));
String namespace = Files.readString(Paths.get(serviceAccountPath, "namespace"));
log.trace("""
K8sPodWatcher:
Master URL: {}
CA cert.:
{}
Token: {}
Namespace: {}""",
masterUrl, caCert.trim(), passwordUtil.encodePassword(token), namespace);
// Configure and start Kubernetes API client
Config config = new ConfigBuilder()
.withMasterUrl(masterUrl)
.withCaCertData(caCert)
.withOauthToken(token)
.build();
try (KubernetesClient client = new KubernetesClientBuilder().withConfig(config).build()) {
log.debug("K8sPodWatcher: Retrieving active Kubernetes cluster nodes and pods");
// Get Kubernetes cluster nodes (Hosts)
addressToNodeMap = new HashMap<>();
Map<String, NodeEntry> uidToNodeMap = new HashMap<>();
client.nodes()
.resources()
.map(Resource::item)
.forEach(node -> {
NodeEntry entry = uidToNodeMap.computeIfAbsent(
node.getMetadata().getUid(), s -> new NodeEntry(node));
node.getStatus().getAddresses().stream()
.filter(address -> ! "Hostname".equalsIgnoreCase(address.getType()))
.forEach(address -> addressToNodeMap.putIfAbsent(address.getAddress(), entry));
});
log.trace("K8sPodWatcher: Address-to-Nodes: {}", addressToNodeMap);
// Get Kubernetes cluster pods
addressToPodMap = new HashMap<>();
Map<String, PodEntry> uidToPodMap = new HashMap<>();
client.pods()
.inAnyNamespace()
// .withLabel("nebulous.application")
.resources()
.map(Resource::item)
.filter(pod-> "Running".equalsIgnoreCase(pod.getStatus().getPhase()))
.forEach(pod -> {
PodEntry entry = uidToPodMap.computeIfAbsent(
pod.getMetadata().getUid(), s -> new PodEntry(pod));
pod.getStatus().getPodIPs()
.forEach(address ->
addressToPodMap.computeIfAbsent(address.getIp(), s -> new HashSet<>()).add(entry)
);
});
log.trace("K8sPodWatcher: Address-to-Pods: {}", addressToPodMap);
} // End of try-with-resources
// Update Node Registry
log.debug("K8sPodWatcher: Updating Node Registry");
Map<String, NodeRegistryEntry> addressToNodeEntryMap = nodeRegistry.getNodes().stream()
.collect(Collectors.toMap(NodeRegistryEntry::getIpAddress, entry -> entry));
// New Pods
HashMap<String, Set<PodEntry>> newPods = new HashMap<>(addressToPodMap);
newPods.keySet().removeAll(addressToNodeEntryMap.keySet());
if (! newPods.isEmpty()) {
log.trace("K8sPodWatcher: New Pods found: {}", newPods);
/*newPods.forEach((address, podSet) -> {
if (podSet.size()==1)
nodeRegistry.addNode(null, podSet.iterator().next().getPodUid());
});*/
} else {
log.trace("K8sPodWatcher: No new Pods");
}
// Node Entries to be removed
HashMap<String, NodeRegistryEntry> oldEntries = new HashMap<>(addressToNodeEntryMap);
oldEntries.keySet().removeAll(addressToPodMap.keySet());
if (! oldEntries.isEmpty()) {
log.trace("K8sPodWatcher: Node entries to be removed: {}", oldEntries);
} else {
log.trace("K8sPodWatcher: No node entries to remove");
}
} catch (Exception e) {
log.warn("K8sPodWatcher: Error while running doWatch: ", e);
}
}
@Data
private static class NodeEntry {
private final String nodeUid;
private final String nodeName;
public NodeEntry(Node node) {
nodeUid = node.getMetadata().getUid();
nodeName = node.getMetadata().getName();
}
}
@Data
private static class PodEntry {
private final String podUid;
private final String podIP;
private final String podName;
private final String hostIP;
private final Map<String, String> labels;
public PodEntry(Pod pod) {
podUid = pod.getMetadata().getUid();
podIP = pod.getStatus().getPodIP();
podName = pod.getMetadata().getName();
hostIP = pod.getStatus().getHostIP();
labels = Collections.unmodifiableMap(pod.getMetadata().getLabels());
}
}
}

View File

@ -11,7 +11,7 @@ package gr.iccs.imu.ems.baguette.client.selfhealing;
import gr.iccs.imu.ems.baguette.client.install.ClientInstallationProperties;
import gr.iccs.imu.ems.baguette.client.install.ClientInstallationTask;
import gr.iccs.imu.ems.baguette.client.install.SshClientInstaller;
import gr.iccs.imu.ems.baguette.client.install.installer.SshClientInstaller;
import gr.iccs.imu.ems.baguette.client.install.helper.InstallationHelperFactory;
import gr.iccs.imu.ems.baguette.server.BaguetteServer;
import gr.iccs.imu.ems.baguette.server.ClientShellCommand;

View File

@ -0,0 +1,156 @@
#
# Copyright (C) 2017-2025 Institute of Communication and Computer Systems (imu.iccs.gr)
#
# This Source Code Form is subject to the terms of the Mozilla Public License, v2.0, unless
# Esper library is used, in which case it is subject to the terms of General Public License v2.0.
# If a copy of the MPL was not distributed with this file, you can obtain one at
# https://www.mozilla.org/en-US/MPL/2.0/
#
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: ${EMS_CLIENT_DAEMONSET_NAME}
labels:
app.kubernetes.io/name: ${EMS_CLIENT_DAEMONSET_NAME}
spec:
selector:
matchLabels:
app.kubernetes.io/name: ${EMS_CLIENT_DAEMONSET_NAME}
template:
metadata:
labels:
app.kubernetes.io/name: ${EMS_CLIENT_DAEMONSET_NAME}
spec:
hostNetwork: true
terminationGracePeriodSeconds: 10
containers:
- name: "${EMS_CLIENT_DAEMONSET_NAME}"
image: "${EMS_CLIENT_DAEMONSET_IMAGE_REPOSITORY}:${EMS_CLIENT_DAEMONSET_IMAGE_TAG}"
imagePullPolicy: "${EMS_CLIENT_DAEMONSET_IMAGE_PULL_POLICY}"
env:
#
# K8S cluster node info
#
- name: K8S_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: K8S_NODE_ADDRESS
valueFrom:
fieldRef:
fieldPath: status.hostIP
#
# Pod info
#
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_ADDRESS
valueFrom:
fieldRef:
fieldPath: status.podIP
#
# EMS client settings
#
- name: IP_SETTING
value: 'DEFAULT_IP'
- name: BAGUETTE_CLIENT_BASE_DIR
value: '/opt/baguette-client'
- name: BAGUETTE_CLIENT_ID
valueFrom:
fieldRef:
fieldPath: metadata.uid
- name: NODE_CLIENT_ID
valueFrom:
fieldRef:
fieldPath: metadata.uid
- name: NODE_ADDRESS
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: SELF_HEALING_ENABLED
value: "false"
- name: COLLECTOR_NETDATA_ENABLE
value: "true"
- name: COLLECTOR_NETDATA_URL
value: 'http://${K8S_NODE_ADDRESS}:19999/api/v1/allmetrics?format=json'
- name: COLLECTOR_PROMETHEUS_ENABLE
value: "false"
- name: COLLECTOR_ALLOWED_TOPICS
valueFrom:
configMapKeyRef:
name: "${EMS_CLIENT_CONFIG_MAP_NAME}"
key: COLLECTOR_ALLOWED_TOPICS
optional: true
- name: EMS_KEYSTORE_PASSWORD
value: "${EMS_CLIENT_KEYSTORE_SECRET}"
- name: EMS_TRUSTSTORE_PASSWORD
value: "${EMS_CLIENT_TRUSTSTORE_SECRET}"
#
# EMS client Broker settings
#
- name: BROKER_URL_ADDRESS_INSECURE
value: "0.0.0.0"
- name: BROKERCEP_ADDITIONAL_BROKER_CREDENTIALS
value: "${EMS_CLIENT_ADDITIONAL_BROKER_CREDENTIALS}"
#
# Baguette Server connection info. Can be retrieved from EMS server:
# https://ems-server:8111/baguette/connectionInfo
#
- name: BAGUETTE_SERVER_ADDRESS
valueFrom:
configMapKeyRef:
name: "${EMS_CLIENT_CONFIG_MAP_NAME}"
key: BAGUETTE_SERVER_ADDRESS
- name: BAGUETTE_SERVER_PORT
valueFrom:
configMapKeyRef:
name: "${EMS_CLIENT_CONFIG_MAP_NAME}"
key: BAGUETTE_SERVER_PORT
- name: BAGUETTE_SERVER_PUBKEY
valueFrom:
configMapKeyRef:
name: "${EMS_CLIENT_CONFIG_MAP_NAME}"
key: BAGUETTE_SERVER_PUBKEY
- name: BAGUETTE_SERVER_PUBKEY_FINGERPRINT
valueFrom:
configMapKeyRef:
name: "${EMS_CLIENT_CONFIG_MAP_NAME}"
key: BAGUETTE_SERVER_PUBKEY_FINGERPRINT
- name: BAGUETTE_SERVER_PUBKEY_FORMAT
valueFrom:
configMapKeyRef:
name: "${EMS_CLIENT_CONFIG_MAP_NAME}"
key: BAGUETTE_SERVER_PUBKEY_FORMAT
- name: BAGUETTE_SERVER_PUBKEY_ALGORITHM
valueFrom:
configMapKeyRef:
name: "${EMS_CLIENT_CONFIG_MAP_NAME}"
key: BAGUETTE_SERVER_PUBKEY_ALGORITHM
- name: BAGUETTE_SERVER_USERNAME
valueFrom:
configMapKeyRef:
name: "${EMS_CLIENT_CONFIG_MAP_NAME}"
key: BAGUETTE_SERVER_USERNAME
- name: BAGUETTE_SERVER_PASSWORD
valueFrom:
configMapKeyRef:
name: "${EMS_CLIENT_CONFIG_MAP_NAME}"
key: BAGUETTE_SERVER_PASSWORD
ports:
- name: openwire
containerPort: 61616
protocol: TCP
- name: openwire-tls
containerPort: 61617
protocol: TCP
- name: stomp
containerPort: 61610
protocol: TCP

View File

@ -0,0 +1,47 @@
#
# Copyright (C) 2017-2023 Institute of Communication and Computer Systems (imu.iccs.gr)
#
# This Source Code Form is subject to the terms of the Mozilla Public License, v2.0, unless
# Esper library is used, in which case it is subject to the terms of General Public License v2.0.
# If a copy of the MPL was not distributed with this file, you can obtain one at
# https://www.mozilla.org/en-US/MPL/2.0/
#
ARG RUN_IMAGE=eclipse-temurin
ARG RUN_IMAGE_TAG=21.0.1_12-jre
# ----------------- Run image -----------------
FROM $RUN_IMAGE:$RUN_IMAGE_TAG
# Install required and optional packages
RUN apt-get update \
&& apt-get install -y vim iputils-ping \
&& rm -rf /var/lib/apt/lists/*
# Add an EMS user
ARG EMS_USER=emsuser
ARG EMS_HOME=/opt/baguette-client
RUN mkdir -p ${EMS_HOME} && \
addgroup ${EMS_USER} && \
adduser --home ${EMS_HOME} --no-create-home --ingroup ${EMS_USER} --disabled-password ${EMS_USER} && \
chown ${EMS_USER}:${EMS_USER} ${EMS_HOME}
USER ${EMS_USER}
WORKDIR ${EMS_HOME}
# Setup environment
ARG EMS_CONFIG_DIR=${EMS_HOME}/conf
ARG JAVA_HOME=/opt/java/openjdk
ARG PATH=$JAVA_HOME/bin:$PATH
ARG INSTALLATION_PACKAGE=baguette-client-installation-package.tgz
# Copy Baguette Client files
COPY --chown=${EMS_USER}:${EMS_USER} target/$INSTALLATION_PACKAGE /tmp
RUN tar zxvf /tmp/$INSTALLATION_PACKAGE -C /opt && rm -f /tmp/$INSTALLATION_PACKAGE
COPY --chown=${EMS_USER}:${EMS_USER} conf/* ${EMS_HOME}/conf/
EXPOSE 61610
EXPOSE 61616
EXPOSE 61617
ENTRYPOINT ["/bin/sh", "-c", "/opt/baguette-client/bin/run.sh && tail -f /opt/baguette-client/logs/output.txt"]

View File

@ -41,9 +41,9 @@ date -Iseconds
# Common variables
DOWNLOAD_URL=$BASE_URL/baguette-client.tgz
DOWNLOAD_URL_MD5=$BASE_URL/baguette-client.tgz.md5
DOWNLOAD_URL_SHA256=$BASE_URL/baguette-client.tgz.sha256
INSTALL_PACKAGE=/opt/baguette-client/baguette-client.tgz
INSTALL_PACKAGE_MD5=/opt/baguette-client/baguette-client.tgz.md5
INSTALL_PACKAGE_SHA256=/opt/baguette-client/baguette-client.tgz.sha256
INSTALL_DIR=/opt/
STARTUP_SCRIPT=$BIN_DIRECTORY/baguette-client
SERVICE_NAME=baguette-client
@ -86,34 +86,34 @@ fi
date -Iseconds
echo "Download installation package...ok"
# Download installation package MD5 checksum
# Download installation package SHA256 checksum
echo ""
echo "Download installation package MD5 checksum..."
echo "Download installation package SHA256 checksum..."
date -Iseconds
wget $SERVER_CERT $DOWNLOAD_URL_MD5 -O $INSTALL_PACKAGE_MD5
wget $SERVER_CERT $DOWNLOAD_URL_SHA256 -O $INSTALL_PACKAGE_SHA256
if [ $? != 0 ]; then
echo "Failed to download installation package ($?)"
echo "Aborting installation..."
date -Iseconds
echo "ABORT: download MD5: `date -Iseconds`" >> $INSTALL_LOG
echo "ABORT: download SHA256: `date -Iseconds`" >> $INSTALL_LOG
exit 1
fi
date -Iseconds
echo "Download installation package MD5 checksum...ok"
echo "Download installation package SHA256 checksum...ok"
# Check MD5 checksum
PACKAGE_MD5=`cat $INSTALL_PACKAGE_MD5`
PACKAGE_CHECKSUM=`md5sum $INSTALL_PACKAGE |cut -d " " -f 1`
# Check SHA256 checksum
PACKAGE_SHA256=`cat $INSTALL_PACKAGE_SHA256`
PACKAGE_CHECKSUM=`sha256sum $INSTALL_PACKAGE |cut -d " " -f 1`
echo ""
echo "Checksum MD5: $PACKAGE_MD5"
echo "Checksum SHA256: $PACKAGE_SHA256"
echo "Checksum calc: $PACKAGE_CHECKSUM"
if [ $PACKAGE_CHECKSUM == $PACKAGE_MD5 ]; then
if [ $PACKAGE_CHECKSUM == $PACKAGE_SHA256 ]; then
echo "Checksum: ok"
else
echo "Checksum: wrong"
echo "Aborting installation..."
date -Iseconds
echo "ABORT: wrong MD5: `date -Iseconds`" >> $INSTALL_LOG
echo "ABORT: wrong SHA256: `date -Iseconds`" >> $INSTALL_LOG
exit 1
fi

View File

@ -0,0 +1,24 @@
#!/usr/bin/env bash
arch=$(uname -m)
bits=$(getconf LONG_BIT)
echo "Architecture: $arch, Bits: $bits"
[[ "$bits" == "32" ]] && [[ "${arch,,}" = arm* ]] && TARGET="ARM-32"
[[ "$bits" == "64" ]] && [[ "${arch,,}" = arm* ]] && TARGET="ARM-64"
[[ "$bits" == "64" ]] && [[ "${arch,,}" = amd* ]] && TARGET="x86-64"
[[ "$bits" == "64" ]] && [[ "${arch,,}" = x86* ]] && TARGET="x86-64"
DOWNLOAD_URL="https://download.bell-sw.com/java/21.0.2+14"
[[ "$TARGET" == "ARM-32" ]] && PACKAGE="bellsoft-jre21.0.2+14-linux-arm32-vfp-hflt-full.tar.gz"
[[ "$TARGET" == "ARM-64" ]] && PACKAGE="bellsoft-jre21.0.2+14-linux-aarch64-full.tar.gz"
[[ "$TARGET" == "x86-64" ]] && PACKAGE="bellsoft-jre21.0.2+14-linux-amd64-full.tar.gz"
if [[ "$TARGET" == "" ]]; then
echo "Target device not supported"
exit 1
else
echo "Target device: $TARGET"
curl -k $DOWNLOAD_URL/$PACKAGE -o /tmp/jre.tar.gz
ls -l
fi

View File

@ -12,32 +12,46 @@ setlocal
set PWD=%~dp0
cd %PWD%..
set BASEDIR=%cd%
IF NOT DEFINED EMS_CONFIG_DIR set EMS_CONFIG_DIR=%BASEDIR%\conf
IF NOT DEFINED PAASAGE_CONFIG_DIR set PAASAGE_CONFIG_DIR=%BASEDIR%\conf
:: IF NOT DEFINED PAASAGE_CONFIG_DIR set PAASAGE_CONFIG_DIR=%BASEDIR%\conf
IF NOT DEFINED EMS_CONFIG_LOCATION set EMS_CONFIG_LOCATION=optional:file:%EMS_CONFIG_DIR%\ems-client.yml,optional:file:%EMS_CONFIG_DIR%\ems-client.properties,optional:file:%EMS_CONFIG_DIR%\baguette-client.yml,optional:file:%EMS_CONFIG_DIR%\baguette-client.properties
IF NOT DEFINED JASYPT_PASSWORD set JASYPT_PASSWORD=password
set JAVA_HOME=%BASEDIR%/jre
set LOG_FILE=%BASEDIR%/logs/output.txt
:: IF NOT DEFINED JASYPT_PASSWORD set JASYPT_PASSWORD=password
IF NOT DEFINED JAVA_HOME IF EXIST %BASEDIR%\jre\ set JAVA_HOME=%BASEDIR%/jre
:: Update path
set PATH=%JAVA_HOME%\bin;%PATH%
:: Source external environment variables file
if DEFINED EMS_EXTRA_ENV_VARS_FILE CALL %EMS_EXTRA_ENV_VARS_FILE%
:: Copy dependencies if missing
if exist pom.xml (
if not exist %BASEDIR%\target\dependency cmd /C "mvn dependency:copy-dependencies"
)
:: Run Baguette Client
set JAVA_OPTS= -Djavax.net.ssl.trustStore=%EMS_CONFIG_DIR%\client-broker-truststore.p12 ^
-Djavax.net.ssl.trustStorePassword=melodic ^
-Djavax.net.ssl.trustStoreType=pkcs12 ^
-Djasypt.encryptor.password=%JASYPT_PASSWORD% ^
--add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED
:: Set JAVA_OPTS
::set JAVA_OPTS= -Djavax.net.ssl.trustStore=%EMS_CONFIG_DIR%\client-broker-truststore.p12 ^
:: -Djavax.net.ssl.trustStorePassword=melodic ^
:: -Djavax.net.ssl.trustStoreType=pkcs12 ^
::set JAVA_OPTS=-Djavax.net.debug=all %JAVA_OPTS%
::set JAVA_OPTS=-Dlogging.level.gr.iccs.imu.ems=TRACE %JAVA_OPTS%
set JAVA_OPTS=%JAVA_OPTS% -Djasypt.encryptor.password=%JASYPT_PASSWORD%
set JAVA_OPTS=%JAVA_OPTS% --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED
:: Print settings
echo Starting baguette client...
echo EMS_CONFIG_DIR=%EMS_CONFIG_DIR%
echo EMS_CONFIG_LOCATION=%EMS_CONFIG_LOCATION%
echo Starting baguette client...
echo LOG_FILE=%LOG_FILE%
echo Starting baguette client... >> %LOG_FILE%
echo EMS_CONFIG_DIR=%EMS_CONFIG_DIR% >> %LOG_FILE%
echo EMS_CONFIG_LOCATION=%EMS_CONFIG_LOCATION% >> %LOG_FILE%
echo LOG_FILE=%LOG_FILE% >> %LOG_FILE%
:: Run Baguette Client
java %JAVA_OPTS% -classpath "%EMS_CONFIG_DIR%;%BASEDIR%\jars\*;%BASEDIR%\target\classes;%BASEDIR%\target\dependency\*" gr.iccs.imu.ems.baguette.client.BaguetteClient "--spring.config.location=%EMS_CONFIG_LOCATION%" "--logging.config=file:%EMS_CONFIG_DIR%\logback-spring.xml" %*
cd %PWD%

View File

@ -12,18 +12,28 @@
PREVWORKDIR=`pwd`
BASEDIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )
cd ${BASEDIR}
EMS_CONFIG_DIR=${BASEDIR}/conf
PAASAGE_CONFIG_DIR=${BASEDIR}/conf
#PAASAGE_CONFIG_DIR=${BASEDIR}/conf
EMS_CONFIG_LOCATION=optional:file:$EMS_CONFIG_DIR/ems-client.yml,optional:file:$EMS_CONFIG_DIR/ems-client.properties,optional:file:$EMS_CONFIG_DIR/baguette-client.yml,optional:file:$EMS_CONFIG_DIR/baguette-client.properties
LOG_FILE=${BASEDIR}/logs/output.txt
TEE_FILE=${BASEDIR}/logs/tee.txt
JASYPT_PASSWORD=password
JAVA_HOME=${BASEDIR}/jre
export EMS_CONFIG_DIR PAASAGE_CONFIG_DIR LOG_FILE JASYPT_PASSWORD JAVA_HOME
#JASYPT_PASSWORD=password
export HOST_IP=1.2.3.4
[ -z "${JAVA_HOME}" ] && [ -d "${BASEDIR}/jre" ] && JAVA_HOME=${BASEDIR}/jre
#export EMS_CONFIG_DIR PAASAGE_CONFIG_DIR LOG_FILE JASYPT_PASSWORD JAVA_HOME
export EMS_CONFIG_DIR LOG_FILE JAVA_HOME
# Update path
PATH=${JAVA_HOME}/bin:$PATH
# Source external environment variables file
if [ "$EMS_EXTRA_ENV_VARS_FILE" != "" ]; then
echo "Sourcing $EMS_EXTRA_ENV_VARS_FILE..."
source $EMS_EXTRA_ENV_VARS_FILE
fi
# Check if baguette client is already running
#PID=`jps | grep BaguetteClient | cut -d " " -f 1`
PID=`ps -ef |grep java |grep BaguetteClient | cut -c 10-14`
@ -40,14 +50,15 @@ if [ -f pom.xml ]; then
fi
fi
# Run Baguette client
JAVA_OPTS=-Djavax.net.ssl.trustStore=${EMS_CONFIG_DIR}/client-broker-truststore.p12
JAVA_OPTS="${JAVA_OPTS} -Djavax.net.ssl.trustStorePassword=melodic -Djavax.net.ssl.trustStoreType=pkcs12"
JAVA_OPTS="${JAVA_OPTS} -Djasypt.encryptor.password=$JASYPT_PASSWORD"
# Set JAVA_OPTS
#JAVA_OPTS=-Djavax.net.ssl.trustStore=${EMS_CONFIG_DIR}/client-broker-truststore.p12
#JAVA_OPTS="${JAVA_OPTS} -Djavax.net.ssl.trustStorePassword=melodic -Djavax.net.ssl.trustStoreType=pkcs12"
#JAVA_OPTS="-Djavax.net.debug=all ${JAVA_OPTS}"
#JAVA_OPTS="-Dlogging.level.gr.iccs.imu.ems=TRACE ${JAVA_OPTS}"
JAVA_OPTS="${JAVA_OPTS} -Djasypt.encryptor.password=$JASYPT_PASSWORD"
JAVA_OPTS="${JAVA_OPTS} --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED"
# Print settings
echo "Starting baguette client..."
echo "EMS_CONFIG_DIR=${EMS_CONFIG_DIR}"
echo "EMS_CONFIG_LOCATION=${EMS_CONFIG_LOCATION}"
@ -58,14 +69,18 @@ echo "EMS_CONFIG_DIR=${EMS_CONFIG_DIR}" &>> ${LOG_FILE}
echo "EMS_CONFIG_LOCATION=${EMS_CONFIG_LOCATION}" &>> ${LOG_FILE}
echo "LOG_FILE=${LOG_FILE}" &>> ${LOG_FILE}
# Run Baguette Client
if [ "$1" == "--i" ]; then
echo "Baguette client running in Interactive mode"
java ${JAVA_OPTS} -classpath "conf:jars/*:target/classes:target/dependency/*" gr.iccs.imu.ems.baguette.client.BaguetteClient "--spring.config.location=${EMS_CONFIG_LOCATION}" "--logging.config=file:${EMS_CONFIG_DIR}/logback-spring.xml" $* $* 2>&1 | tee ${TEE_FILE}
java ${JAVA_OPTS} -classpath "conf:jars/*:target/classes:target/dependency/*" gr.iccs.imu.ems.baguette.client.BaguetteClient "--spring.config.location=${EMS_CONFIG_LOCATION}" "--logging.config=file:${EMS_CONFIG_DIR}/logback-spring.xml" $* 2>&1 | tee ${TEE_FILE}
else
java ${JAVA_OPTS} -classpath "conf:jars/*:target/classes:target/dependency/*" gr.iccs.imu.ems.baguette.client.BaguetteClient "--spring.config.location=${EMS_CONFIG_LOCATION}" "--logging.config=file:${EMS_CONFIG_DIR}/logback-spring.xml" $* &>> ${LOG_FILE} &
PID=`jps | grep BaguetteClient | cut -d " " -f 1`
PID=`ps -ef |grep java |grep BaguetteClient | cut -c 10-14`
echo "Baguette client PID: $PID"
if command -v jps
then
PID=`jps | grep BaguetteClient | cut -d " " -f 1`
PID=`ps -ef |grep java |grep BaguetteClient | cut -c 10-14`
echo "Baguette client PID: $PID"
fi
fi
cd $PREVWORKDIR

View File

@ -11,12 +11,20 @@
### EMS - Baguette Client properties ###
################################################################################
#password-encoder-class = password.gr.iccs.imu.ems.util.AsterisksPasswordEncoder
#password-encoder-class = password.gr.iccs.imu.ems.util.IdentityPasswordEncoder
#password-encoder-class = password.gr.iccs.imu.ems.util.PresentPasswordEncoder
#password-encoder-class = gr.iccs.imu.ems.util.password.AsterisksPasswordEncoder
#password-encoder-class = gr.iccs.imu.ems.util.password.IdentityPasswordEncoder
#password-encoder-class = gr.iccs.imu.ems.util.password.PresentPasswordEncoder
### Jasypt encryptor settings (using old settings until encrypted texts are updated)
jasypt.encryptor.algorithm = PBEWithMD5AndDES
jasypt.encryptor.ivGeneratorClassname = org.jasypt.iv.NoIvGenerator
# Baguette Client configuration
baseDir = ${BAGUETTE_CLIENT_BASE_DIR}
connection-retry-enabled = true
connection-retry-delay = 10000
connection-retry-limit = -1
auth-timeout = 60000
exec-timeout = 120000
#retry-period = 60000
@ -38,7 +46,9 @@ client-id = ${BAGUETTE_CLIENT_ID}
server-address = ${BAGUETTE_SERVER_ADDRESS}
server-port = ${BAGUETTE_SERVER_PORT}
server-pubkey = ${BAGUETTE_SERVER_PUBKEY}
server-fingerprint = ${BAGUETTE_SERVER_PUBKEY_FINGERPRINT}
server-pubkey-fingerprint = ${BAGUETTE_SERVER_PUBKEY_FINGERPRINT}
server-pubkey-algorithm = ${BAGUETTE_SERVER_PUBKEY_ALGORITHM}
server-pubkey-format = ${BAGUETTE_SERVER_PUBKEY_FORMAT}
server-username = ${BAGUETTE_SERVER_USERNAME}
server-password = ${BAGUETTE_SERVER_PASSWORD}
@ -58,7 +68,7 @@ server-password = ${BAGUETTE_SERVER_PASSWORD}
# Collectors settings
# -----------------------------------------------------------------------------
#collector-classes = netdata.collector.gr.iccs.imu.ems.baguette.client.NetdataCollector
#collector-classes = gr.iccs.imu.ems.baguette.client.collector.netdata.NetdataCollector
collector.netdata.enable = true
collector.netdata.delay = 10000
@ -141,8 +151,8 @@ brokercep.broker-protocol = ssl
BROKER_URL_PROPERTIES = transport.daemon=true&transport.trace=false&transport.useKeepAlive=true&transport.useInactivityMonitor=false&transport.needClientAuth=${CLIENT_AUTH_REQUIRED}&transport.verifyHostName=true&transport.connectionTimeout=0&transport.keepAlive=true
CLIENT_AUTH_REQUIRED = false
brokercep.broker-url[0] = ${brokercep.broker-protocol}://0.0.0.0:${brokercep.broker-port}?${BROKER_URL_PROPERTIES}
brokercep.broker-url[1] = tcp://127.0.0.1:61616?${BROKER_URL_PROPERTIES}
brokercep.broker-url[2] =
brokercep.broker-url[1] = tcp://${BROKER_URL_ADDRESS_INSECURE:127.0.0.1}:61616?${BROKER_URL_PROPERTIES}
brokercep.broker-url[2] = stomp://${BROKER_URL_ADDRESS_INSECURE:127.0.0.1}:61610?${BROKER_URL_PROPERTIES}
CLIENT_URL_PROPERTIES=daemon=true&trace=false&useInactivityMonitor=false&connectionTimeout=0&keepAlive=true
brokercep.broker-url-for-consumer = tcp://127.0.0.1:61616?${CLIENT_URL_PROPERTIES}
@ -153,12 +163,14 @@ brokercep.broker-url-for-clients = ${brokercep.broker-protocol}://${EMS_CLIENT_A
brokercep.ssl.keystore-file = ${EMS_CONFIG_DIR}/client-broker-keystore.p12
brokercep.ssl.keystore-type = PKCS12
#brokercep.ssl.keystore-password = melodic
brokercep.ssl.keystore-password = ENC(ISMbn01HVPbtRPkqm2Lslg==)
#brokercep.ssl.keystore-password = ENC(ISMbn01HVPbtRPkqm2Lslg==)
brokercep.ssl.keystore-password = ${EMS_KEYSTORE_PASSWORD}
# Trust store
brokercep.ssl.truststore-file = ${EMS_CONFIG_DIR}/client-broker-truststore.p12
brokercep.ssl.truststore-type = PKCS12
#brokercep.ssl.truststore-password = melodic
brokercep.ssl.truststore-password = ENC(ISMbn01HVPbtRPkqm2Lslg==)
#brokercep.ssl.truststore-password = ENC(ISMbn01HVPbtRPkqm2Lslg==)
brokercep.ssl.truststore-password = ${EMS_TRUSTSTORE_PASSWORD}
# Certificate
brokercep.ssl.certificate-file = ${EMS_CONFIG_DIR}/client-broker.crt
# Key-and-Cert data
@ -170,7 +182,8 @@ brokercep.ssl.key-entry-ext-san = dns:localhost,ip:127.0.0.1,ip:${DEFAULT_IP},ip
# Authentication and Authorization settings
brokercep.authentication-enabled = true
#brokercep.additional-broker-credentials = aaa/111, bbb/222, morphemic/morphemic
brokercep.additional-broker-credentials = ENC(axeJUxNHajYfBffUwvuT3kwTgLTpRliDMz/ZQ9hROZ3BNOv0Idw72NJsawzIZRuZ)
#brokercep.additional-broker-credentials = ENC(axeJUxNHajYfBffUwvuT3kwTgLTpRliDMz/ZQ9hROZ3BNOv0Idw72NJsawzIZRuZ)
brokercep.additional-broker-credentials = ${EMS_CLIENT_ADDITIONAL_BROKER_CREDENTIALS}
brokercep.authorization-enabled = false
# Broker instance settings
@ -184,14 +197,14 @@ brokercep.broker-using-shutdown-hook = false
# Message interceptors
brokercep.message-interceptors[0].destination = >
brokercep.message-interceptors[0].className = interceptor.broker.gr.iccs.imu.ems.brokercep.SequentialCompositeInterceptor
brokercep.message-interceptors[0].className = gr.iccs.imu.ems.brokercep.broker.interceptor.SequentialCompositeInterceptor
brokercep.message-interceptors[0].params[0] = #SourceAddressMessageUpdateInterceptor
brokercep.message-interceptors[0].params[1] = #MessageForwarderInterceptor
brokercep.message-interceptors[0].params[2] = #NodePropertiesMessageUpdateInterceptor
brokercep.message-interceptors-specs.SourceAddressMessageUpdateInterceptor.className = interceptor.broker.gr.iccs.imu.ems.brokercep.SourceAddressMessageUpdateInterceptor
brokercep.message-interceptors-specs.MessageForwarderInterceptor.className = interceptor.broker.gr.iccs.imu.ems.brokercep.MessageForwarderInterceptor
brokercep.message-interceptors-specs.NodePropertiesMessageUpdateInterceptor.className = interceptor.broker.gr.iccs.imu.ems.brokercep.NodePropertiesMessageUpdateInterceptor
brokercep.message-interceptors-specs.SourceAddressMessageUpdateInterceptor.className = gr.iccs.imu.ems.brokercep.broker.interceptor.SourceAddressMessageUpdateInterceptor
brokercep.message-interceptors-specs.MessageForwarderInterceptor.className = gr.iccs.imu.ems.brokercep.broker.interceptor.MessageForwarderInterceptor
brokercep.message-interceptors-specs.NodePropertiesMessageUpdateInterceptor.className = gr.iccs.imu.ems.brokercep.broker.interceptor.NodePropertiesMessageUpdateInterceptor
# Message forward destinations (MessageForwarderInterceptor must be included in 'message-interceptors' property)
#brokercep.message-forward-destinations[0].connection-string = tcp://localhost:51515

View File

@ -11,12 +11,22 @@
### EMS - Baguette Client properties ###
################################################################################
#password-encoder-class: password.gr.iccs.imu.ems.util.AsterisksPasswordEncoder
#password-encoder-class: password.gr.iccs.imu.ems.util.IdentityPasswordEncoder
#password-encoder-class: password.gr.iccs.imu.ems.util.PresentPasswordEncoder
#password-encoder-class: gr.iccs.imu.ems.util.password.AsterisksPasswordEncoder
#password-encoder-class: gr.iccs.imu.ems.util.password.IdentityPasswordEncoder
#password-encoder-class: gr.iccs.imu.ems.util.password.PresentPasswordEncoder
### Jasypt encryptor settings (using old settings until encrypted texts are updated)
jasypt:
encryptor:
algorithm: PBEWithMD5AndDES
ivGeneratorClassname: org.jasypt.iv.NoIvGenerator
# Baguette Client configuration
baseDir: ${BAGUETTE_CLIENT_BASE_DIR}
connection-retry-enabled: true
connection-retry-delay: 10000
connection-retry-limit: -1
auth-timeout: 60000
exec-timeout: 120000
#retry-period: 60000
@ -46,7 +56,9 @@ client-id: ${BAGUETTE_CLIENT_ID}
server-address: ${BAGUETTE_SERVER_ADDRESS}
server-port: ${BAGUETTE_SERVER_PORT}
server-pubkey: ${BAGUETTE_SERVER_PUBKEY}
server-fingerprint: ${BAGUETTE_SERVER_PUBKEY_FINGERPRINT}
server-pubkey-fingerprint: ${BAGUETTE_SERVER_PUBKEY_FINGERPRINT}
server-pubkey-algorithm: ${BAGUETTE_SERVER_PUBKEY_ALGORITHM}
server-pubkey-format: ${BAGUETTE_SERVER_PUBKEY_FORMAT}
server-username: ${BAGUETTE_SERVER_USERNAME}
server-password: ${BAGUETTE_SERVER_PASSWORD}
@ -69,7 +81,9 @@ server-password: ${BAGUETTE_SERVER_PASSWORD}
# Collectors settings
# -----------------------------------------------------------------------------
#collector-classes: netdata.collector.gr.iccs.imu.ems.baguette.client.NetdataCollector
#collector-classes: gr.iccs.imu.ems.baguette.client.collector.netdata.NetdataCollector
collector-configurations: ${COLLECTOR_CONFIGURATIONS}
collector:
netdata:
@ -162,7 +176,8 @@ brokercep:
# Broker connectors
broker-url:
- ${brokercep.broker-protocol}://0.0.0.0:${brokercep.broker-port}?${BROKER_URL_PROPERTIES}
- tcp://127.0.0.1:61616?${BROKER_URL_PROPERTIES}
- tcp://${BROKER_URL_ADDRESS_INSECURE:127.0.0.1}:61616?${BROKER_URL_PROPERTIES}
- stomp://${BROKER_URL_ADDRESS_INSECURE:127.0.0.1}:61610?${BROKER_URL_PROPERTIES}
# Broker URLs for (EMS) consumer and clients
broker-url-for-consumer: tcp://127.0.0.1:61616?${CLIENT_URL_PROPERTIES}
@ -173,12 +188,14 @@ brokercep:
# Key store settings
keystore-file: ${EMS_CONFIG_DIR}/client-broker-keystore.p12
keystore-type: PKCS12
keystore-password: 'ENC(ISMbn01HVPbtRPkqm2Lslg==)' # melodic
#keystore-password: 'ENC(ISMbn01HVPbtRPkqm2Lslg==)' # melodic
keystore-password: ${EMS_KEYSTORE_PASSWORD:${random.value}}
# Trust store settings
truststore-file: ${EMS_CONFIG_DIR}/client-broker-truststore.p12
truststore-type: PKCS12
truststore-password: 'ENC(ISMbn01HVPbtRPkqm2Lslg==)' # melodic
#truststore-password: 'ENC(ISMbn01HVPbtRPkqm2Lslg==)' # melodic
truststore-password: ${EMS_TRUSTSTORE_PASSWORD:${random.value}}
# Certificate settings
certificate-file: ${EMS_CONFIG_DIR}/client-broker.crt
@ -192,7 +209,8 @@ brokercep:
# Authentication and Authorization settings
authentication-enabled: true
#additional-broker-credentials: aaa/111, bbb/222, morphemic/morphemic
additional-broker-credentials: 'ENC(axeJUxNHajYfBffUwvuT3kwTgLTpRliDMz/ZQ9hROZ3BNOv0Idw72NJsawzIZRuZ)'
#additional-broker-credentials: 'ENC(axeJUxNHajYfBffUwvuT3kwTgLTpRliDMz/ZQ9hROZ3BNOv0Idw72NJsawzIZRuZ)'
additional-broker-credentials: ${EMS_CLIENT_ADDITIONAL_BROKER_CREDENTIALS}
authorization-enabled: false
# Broker instance settings
@ -207,7 +225,7 @@ brokercep:
# Message interceptors
message-interceptors:
- destination: '>'
className: 'interceptor.broker.gr.iccs.imu.ems.brokercep.SequentialCompositeInterceptor'
className: 'gr.iccs.imu.ems.brokercep.broker.interceptor.SequentialCompositeInterceptor'
params:
- '#SourceAddressMessageUpdateInterceptor'
- '#MessageForwarderInterceptor'
@ -215,11 +233,11 @@ brokercep:
message-interceptors-specs:
SourceAddressMessageUpdateInterceptor:
className: interceptor.broker.gr.iccs.imu.ems.brokercep.SourceAddressMessageUpdateInterceptor
className: gr.iccs.imu.ems.brokercep.broker.interceptor.SourceAddressMessageUpdateInterceptor
MessageForwarderInterceptor:
className: interceptor.broker.gr.iccs.imu.ems.brokercep.MessageForwarderInterceptor
className: gr.iccs.imu.ems.brokercep.broker.interceptor.MessageForwarderInterceptor
NodePropertiesMessageUpdateInterceptor:
className: interceptor.broker.gr.iccs.imu.ems.brokercep.NodePropertiesMessageUpdateInterceptor
className: gr.iccs.imu.ems.brokercep.broker.interceptor.NodePropertiesMessageUpdateInterceptor
# Message forward destinations (MessageForwarderInterceptor must be included in 'message-interceptors' property)
#message-forward-destinations:

View File

@ -0,0 +1,56 @@
#
# Copyright (C) 2017-2025 Institute of Communication and Computer Systems (imu.iccs.gr)
#
# This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# https://www.mozilla.org/en-US/MPL/2.0/
#
services:
ems-client:
image: ems-client:2024-feb-nebulous
environment:
# Logging settings
# - LOGGING_LEVEL_GR_ICCS_IMU_EMS_BAGUETTE_CLIENT=TRACE
# Mode of operation - Common settings
- IP_SETTING=DEFAULT_IP
- SELF_HEALING_ENABLED=false
#- JASYPT_PASSWORD=
#- BAGUETTE_CLIENT_BASE_DIR=/opt/baguette-client
# This Node info
- NODE_CLIENT_ID=localhost
- BAGUETTE_CLIENT_ID=localhost
- EMS_CLIENT_ADDRESS=localhost
- NODE_ADDRESS=localhost
- NODE_ADDRESS_PUBLIC=localhost
- NODE_ADDRESS_PRIVATE=localhost
- zone-id=
- provider=
# EMS server SSH info and credentials
- BAGUETTE_SERVER_ADDRESS=host.docker.internal
- BAGUETTE_SERVER_PORT=2222
- BAGUETTE_SERVER_PUBKEY=-----BEGIN PUBLIC KEY-----\nMIICRjCCAbkGByqGSM49AgEwggGsAgEBME0GByqGSM49AQECQgH/////////////\n////////////////////////////////////////////////////////////////\n/////////zCBiARCAf//////////////////////////////////////////////\n///////////////////////////////////////8BEIAUZU+uWGOHJofkpohoLaF\nQO6i2nJbmbMV87i0iZGO8QnhVhk5Uex+k3sWUsC9O7G/BzVz34g9LDTx70Uf1GtQ\nPwAEgYUEAMaFjga3BATpzZ4+y2YjlbRCnGSBOQU/tSH4KK9ga009uqFLXnfv51ko\n/h3BJ6L/qN4zSLPBhWpCm/l+fjHC5b1mARg5KWp4mjvABFyKX7QsfRvZmPVESVeb\nRGgXr70XJz5mLJfucple9CZAxVC5AT+tB2E1PHCGonLCQIi+lHaf0WZQAkIB////\n///////////////////////////////////////6UYaHg78vlmt/zAFI9wml0Du1\nybiJnEeuu2+3HpE4ZAkCAQEDgYYABAHedQ6pZnbUFjw+/5B2MQGl/WQsUcRFS0Dy\n7RmSTOsfBlRnEaga7KWPT/lGaSwJfi3OdEMwZYdAN/I7A1HsvqemBgEYUuN96ENP\nGskWCX2qNmfDjxsDpPPXr7lsR9pzafXFpP7PLIvQSFb5UnlzI7edbF6UNVjfjkSY\nY8KnManEvzaMeQ\=\=\n-----END PUBLIC KEY-----
- BAGUETTE_SERVER_PUBKEY_FINGERPRINT=SHA256\:GPn9rx9WWPr+JXlUw0cq8I8tvYLiyadVZswfCevzpN0
- BAGUETTE_SERVER_PUBKEY_ALGORITHM=EC
- BAGUETTE_SERVER_PUBKEY_FORMAT=X.509
- BAGUETTE_SERVER_USERNAME=user-45cb8e46-c6bf-4a4b-8e7a-354f7b6d8fc1
- BAGUETTE_SERVER_PASSWORD=tTUjicVJfrfCurbjecGfBOTxdshA9dOLLjIEo
# Collectors settings
- COLLECTOR_NETDATA_ENABLE=false
- COLLECTOR_PROMETHEUS_ENABLE=false
- COLLECTOR_ALLOWED_TOPICS=
# AMQ broker settings
- EMS_KEYSTORE_PASSWORD=
- EMS_TRUSTSTORE_PASSWORD=
- EMS_CLIENT_ADDITIONAL_BROKER_CREDENTIALS=
ports:
- 11016:61616
- 11017:61617
- 11010:61610
# - 1099:19999

View File

@ -21,6 +21,12 @@
<properties>
<atomix.version>3.1.12</atomix.version>
<!-- io.fabricat8 docker-maven-plugin properties -->
<docker-maven-plugin.version>0.43.2</docker-maven-plugin.version>
<docker.image.name>ems-client</docker.image.name>
<docker.user>emsuser</docker.user>
<docker.user.home>/opt/baguette-client</docker.user.home>
</properties>
<dependencies>
@ -74,6 +80,10 @@
<groupId>org.springframework</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@ -171,5 +181,121 @@
</plugins>
</build>
<profiles>
<profile>
<id>dev-local-docker-image-build</id>
<activation>
<file>
<exists>../.dev-local-docker-image-build</exists>
</file>
</activation>
<build>
<plugins>
<!-- Read docker plugin settings from properties file -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>properties-maven-plugin</artifactId>
<version>1.2.0</version>
<executions>
<execution>
<id>read-docker-image-properties</id>
<phase>validate</phase>
<goals>
<goal>read-project-properties</goal>
</goals>
</execution>
</executions>
<configuration>
<files>
<file>../.dev-local-docker-image-build</file>
</files>
<outputFile/>
<properties/>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>build-docker-image</id>
<activation>
<file><exists>.</exists></file>
</activation>
<build>
<plugins>
<!-- Set docker image properties -->
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>set-docker-properties</id>
<phase>validate</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<exportAntProperties>true</exportAntProperties>
<target unless="docker.image.tag">
<property name="docker.image.tag" value="${project.version}"/>
<property name="build.description" value=""/>
</target>
</configuration>
</execution>
<!--<execution>
<id>print-docker-properties</id>
<phase>install</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<echo>Print Docker image dev properties</echo>
<echo>Image Tag: "${docker.image.tag}"</echo>
<echo>Description: "${build.description}"</echo>
</target>
</configuration>
</execution>-->
</executions>
</plugin>
<!-- Build docker image using docker-context folder -->
<!--<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>${docker-maven-plugin.version}</version>
<configuration>
<verbose>true</verbose>
<useColor>true</useColor>
<images>
<image>
<name>${docker.image.name}:${docker.image.tag}</name>
<build>
<dockerFile>${project.basedir}/Dockerfile</dockerFile>
<args>
<EMS_USER>${docker.user}</EMS_USER>
<EMS_HOME>${docker.user.home}</EMS_HOME>
</args>
</build>
</image>
</images>
</configuration>
<executions>
<execution>
<id>docker-image-build</id>
<phase>install</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>-->
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -140,6 +140,13 @@ public class BaguetteClient implements ApplicationRunner {
try {
log.debug("BaguetteClient: Starting collector: {}...", collectorClass.getName());
Collector collector = applicationContext.getBean(collectorClass);
log.debug("BaguetteClient: Starting collector: {}: instance={}", collectorClass.getName(), collector);
if (baguetteClientProperties.getCollectorConfigurations()!=null) {
Object config = baguetteClientProperties.getCollectorConfigurations().get(collector.getName());
log.debug("BaguetteClient: Starting collector: {}: collector-config={}", collectorClass.getName(), collector);
if (config!=null)
collector.setConfiguration(config);
}
collector.start();
collectorsList.add(collector);
log.debug("BaguetteClient: Starting collector: {}...ok", collectorClass.getName());

View File

@ -18,6 +18,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import java.util.List;
import java.util.Map;
@Data
@ToString(callSuper = true)
@ -41,6 +42,7 @@ public class BaguetteClientProperties extends SshClientProperties {
private int killDelay = 5;
private List<Class<? extends Collector>> collectorClasses;
private Map<String,List<Map<String,Object>>> collectorConfigurations;
private String debugFakeIpAddress;

View File

@ -12,5 +12,7 @@ package gr.iccs.imu.ems.baguette.client;
import gr.iccs.imu.ems.util.Plugin;
public interface Collector extends Plugin {
String getName();
void setConfiguration(Object config);
void activeGroupingChanged(String oldGrouping, String newGrouping);
}

View File

@ -371,10 +371,14 @@ public class CommandExecutor {
double upper = Double.parseDouble(args[4].trim());
if (eventGenerators.get(destination) == null) {
/*
EventGenerator generator = applicationContext.getBean(EventGenerator.class);
generator.setBrokerUrl(brokerCepService.getBrokerCepProperties().getBrokerUrlForClients());
generator.setBrokerUsername(brokerCepService.getBrokerUsername());
generator.setBrokerPassword(brokerCepService.getBrokerPassword());
*/
EventGenerator generator = new EventGenerator((destinationName, event) ->
sendEvent(null, destinationName, event)==CollectorContext.PUBLISH_RESULT.SENT);
generator.setDestinationName(destination);
generator.setLevel(1);
generator.setInterval(interval);

View File

@ -0,0 +1,382 @@
/*
* Copyright (C) 2017-2023 Institute of Communication and Computer Systems (imu.iccs.gr)
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v2.0, unless
* Esper library is used, in which case it is subject to the terms of General Public License v2.0.
* If a copy of the MPL was not distributed with this file, you can obtain one at
* https://www.mozilla.org/en-US/MPL/2.0/
*/
package gr.iccs.imu.ems.baguette.client.collector.netdata;
import gr.iccs.imu.ems.baguette.client.Collector;
import gr.iccs.imu.ems.baguette.client.collector.ClientCollectorContext;
import gr.iccs.imu.ems.brokercep.event.EventMap;
import gr.iccs.imu.ems.common.collector.CollectorContext;
import gr.iccs.imu.ems.common.collector.netdata.NetdataCollectorProperties;
import gr.iccs.imu.ems.util.EmsConstant;
import gr.iccs.imu.ems.util.EventBus;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Collects measurements from Netdata agents in a Kubernetes cluster
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class K8sNetdataCollector implements Collector, InitializingBean {
protected final static Set<String> SENSOR_CONFIG_KEYS_EXCLUDED = Set.of("endpoint", "type", "_containerName");
protected final static String NETDATA_DATA_API_V1_PATH = "/api/v1/data";
protected final static String NETDATA_DATA_API_V2_PATH = "/api/v2/data";
protected final static String DEFAULT_NETDATA_DATA_API_PATH = NETDATA_DATA_API_V2_PATH;
private final NetdataCollectorProperties properties;
private final CollectorContext collectorContext;
private final TaskScheduler taskScheduler;
private final EventBus<String, Object, Object> eventBus;
private final RestClient restClient = RestClient.create();
private final List<ScheduledFuture<?>> scheduledFuturesList = new LinkedList<>();
private boolean started;
private List<Map<String, Object>> configuration;
@Override
public void afterPropertiesSet() throws Exception {
if (!(collectorContext instanceof ClientCollectorContext))
throw new IllegalArgumentException("Invalid CollectorContext provided. Expected: ClientCollectorContext, but got "+collectorContext.getClass().getName());
}
@Override
public String getName() {
return "netdata";
}
@Override
public void setConfiguration(Object config) {
if (config instanceof List sensorConfigList) {
configuration = sensorConfigList.stream()
.filter(o -> o instanceof Map)
.filter(map -> ((Map)map).keySet().stream().allMatch(k->k instanceof String))
.toList();
log.debug("K8sNetdataCollector: setConfiguration: {}", configuration);
// If configuration changes while collector running we need to restart it
if (started) {
log.debug("K8sNetdataCollector: setConfiguration: Restarting collector");
stop();
start();
log.info("K8sNetdataCollector: setConfiguration: Restarted collector");
}
} else
log.warn("K8sNetdataCollector: setConfiguration: Ignoring unsupported configuration object: {}", config);
}
@Override
public void start() {
if (started) return;
if (configuration!=null)
doStart();
started = true;
log.debug("K8sNetdataCollector: Started");
}
@Override
public void stop() {
if (!started) return;
started = false;
doStop();
log.debug("K8sNetdataCollector: Stopped");
}
private synchronized void doStart() {
log.debug("K8sNetdataCollector: doStart(): BEGIN: configuration={}", configuration);
log.trace("K8sNetdataCollector: doStart(): BEGIN: scheduledFuturesList={}", scheduledFuturesList);
// Get Netdata agent address and port from env. vars
String netdataAddress = null;
if (StringUtils.isBlank(netdataAddress)) netdataAddress = System.getenv("NETDATA_ADDRESS");
if (StringUtils.isBlank(netdataAddress)) netdataAddress = System.getenv("NETDATA_IP");
if (StringUtils.isBlank(netdataAddress)) netdataAddress = System.getenv("HOST_IP");
if (StringUtils.isBlank(netdataAddress)) netdataAddress = "127.0.0.1";
log.trace("K8sNetdataCollector: doStart(): netdataAddress={}", netdataAddress);
int netdataPort = Integer.parseInt(
StringUtils.defaultIfBlank(System.getenv("NETDATA_PORT"), "19999").trim());
final String baseUrl = String.format("http://%s:%d", netdataAddress.trim(), netdataPort);
log.trace("K8sNetdataCollector: doStart(): baseUrl={}", baseUrl);
// Process each sensor configuration
AtomicInteger sensorNum = new AtomicInteger(0);
configuration.forEach(map -> {
log.debug("K8sNetdataCollector: doStart(): Sensor-{}: map={}", sensorNum.incrementAndGet(), map);
// Check if it is a Pull sensor. (Push sensors are ignored)
if ("true".equalsIgnoreCase( get(map, "pushSensor", "false") )) {
log.debug("K8sNetdataCollector: doStart(): Sensor-{}: It is a Push sensor. Ignoring this sensor", sensorNum.get());
return;
}
// else it is a Pull sensor
// Get destination (topic) and component name
String destinationName = get(map, "name", null);
log.trace("K8sNetdataCollector: doStart(): Sensor-{}: destination={}", sensorNum.get(), destinationName);
if (StringUtils.isBlank(destinationName)) {
log.warn("K8sNetdataCollector: doStart(): Sensor-{}: No destination found in sensor config: {}", sensorNum.get(), map);
return;
}
// Get metric URL
int apiVer;
String url = null;
String component = null;
String context = null;
String dimensions = "*";
if (map.get("configuration") instanceof Map cfgMap) {
log.trace("K8sNetdataCollector: doStart(): Sensor-{}: cfgMap={}", sensorNum.get(), cfgMap);
// Get component name
component = get(cfgMap, "_containerName", null);
log.trace("K8sNetdataCollector: doStart(): Sensor-{}: component={}", sensorNum.get(), component);
// Process 'configuration' map entries, to build metric URL
Map<String, Object> sensorConfig = (Map<String, Object>) cfgMap;
String endpoint = get(sensorConfig, "endpoint", DEFAULT_NETDATA_DATA_API_PATH);
log.trace("K8sNetdataCollector: doStart(): Sensor-{}: endpoint={}", sensorNum.get(), endpoint);
if (NETDATA_DATA_API_V1_PATH.equalsIgnoreCase(endpoint)) {
apiVer = 1;
// If expanded by a shorthand expression
context = get(sensorConfig, EmsConstant.NETDATA_METRIC_KEY, null);
if (StringUtils.isNotBlank(context))
addEntryIfMissingOrBlank(sensorConfig, "context", context);
// Else check sensor config for 'context' key
context = get(sensorConfig, "context", null);
addEntryIfMissingOrBlank(sensorConfig, "dimension", "*");
addEntryIfMissingOrBlank(sensorConfig, "after", "-1");
addEntryIfMissingOrBlank(sensorConfig, "group", "average");
addEntryIfMissingOrBlank(sensorConfig, "format", "json2");
} else
if (NETDATA_DATA_API_V2_PATH.equalsIgnoreCase(endpoint)) {
apiVer = 2;
// If expanded by a shorthand expression
context = get(sensorConfig, EmsConstant.NETDATA_METRIC_KEY, null);
if (StringUtils.isNotBlank(context))
addEntryIfMissingOrBlank(sensorConfig, "scope_contexts", context);
// Else check sensor config for 'scope_contexts' or 'context' key
context = get(sensorConfig, "scope_contexts", null);
if (StringUtils.isBlank(context))
context = get(sensorConfig, "context", null);
boolean isK8s = StringUtils.startsWithIgnoreCase(context, "k8s");
if (isK8s) {
addEntryIfMissingOrBlank(sensorConfig, "group_by", "label");
addEntryIfMissingOrBlank(sensorConfig, "group_by_label", "k8s_pod_name");
}
addEntryIfMissingOrBlank(sensorConfig, "dimension", "*");
addEntryIfMissingOrBlank(sensorConfig, "after", "-1");
addEntryIfMissingOrBlank(sensorConfig, "time_group", "average");
addEntryIfMissingOrBlank(sensorConfig, "format", "ssv");
} else {
log.warn("K8sNetdataCollector: doStart(): Sensor-{}: Invalid Netdata endpoint found in sensor config: {}", sensorNum.get(), map);
return;
}
dimensions = get(sensorConfig, "dimension", dimensions);
StringBuilder sb = new StringBuilder(endpoint);
final AtomicBoolean first = new AtomicBoolean(true);
sensorConfig.forEach((key, value) -> {
if (StringUtils.isNotBlank(key) && ! SENSOR_CONFIG_KEYS_EXCLUDED.contains(key)) {
if (value instanceof String valueStr) {
sb.append(first.get() ? "?" : "&").append(key).append("=").append(valueStr);
first.set(false);
}
}
});
if (StringUtils.isNotBlank(context)) {
url = baseUrl + sb;
} else {
log.warn("K8sNetdataCollector: doStart(): Sensor-{}: No 'context' found in sensor configuration: {}", sensorNum.get(), map);
return;
}
} else {
log.warn("K8sNetdataCollector: doStart(): Sensor-{}: No sensor configuration found is spec: {}", sensorNum.get(), map);
return;
}
log.trace("K8sNetdataCollector: doStart(): Sensor-{}: Metric url={}", sensorNum.get(), url);
// Get interval and configure scheduler
long period = 60;
TimeUnit unit = TimeUnit.SECONDS;
Duration duration = null;
if (map.get("interval") instanceof Map intervalMap) {
log.trace("K8sNetdataCollector: doStart(): Sensor-{}: intervalMap={}", sensorNum.get(), intervalMap);
period = Long.parseLong(get(intervalMap, "period", Long.toString(period)));
if (period>0) {
String unitStr = get(intervalMap, "unit", unit.name());
unit = StringUtils.isNotBlank(unitStr)
? TimeUnit.valueOf(unitStr.toUpperCase().trim()) : TimeUnit.SECONDS;
duration = Duration.of(period, unit.toChronoUnit());
}
}
log.trace("K8sNetdataCollector: doStart(): Sensor-{}: duration-from-spec={}", sensorNum.get(), duration);
if (duration==null) {
duration = Duration.of(period, unit.toChronoUnit());
}
log.trace("K8sNetdataCollector: doStart(): Sensor-{}: duration={}", sensorNum.get(), duration);
final int apiVer1 = apiVer;
final String url1 = url;
final String component1 = component;
scheduledFuturesList.add( taskScheduler.scheduleAtFixedRate(() -> {
collectData(apiVer1, url1, destinationName, component1);
}, duration) );
log.debug("K8sNetdataCollector: doStart(): Sensor-{}: destination={}, component={}, interval={}, url={}",
sensorNum.get(), destinationName, component, duration, url);
log.info("K8sNetdataCollector: Collecting Netdata metric '{}.{}' into '{}', every {} {}",
context, dimensions, destinationName, period, unit.name().toLowerCase());
});
log.trace("K8sNetdataCollector: doStart(): scheduledFuturesList={}", scheduledFuturesList);
log.debug("K8sNetdataCollector: doStart(): END");
}
private String get(Map<String,Object> map, String key, String defaultValue) {
Object valObj;
if (!map.containsKey(key) || (valObj = map.get(key))==null) return defaultValue;
String value = valObj.toString();
if (StringUtils.isBlank(value)) return defaultValue;
return value;
}
private void addEntryIfMissingOrBlank(Map<String, Object> map, String key, Object value) {
if (map.containsKey(key) && map.get(key)!=null)
if (map.get(key) instanceof String s && StringUtils.isNotBlank(s))
return;
map.put(key, value);
}
private void collectData(int apiVer, String url, String destination, String component) {
long startTm = System.currentTimeMillis();
log.debug("K8sNetdataCollector: collectData(): BEGIN: apiVer={}, url={}, destination={}, component={}",
apiVer, url, destination, component);
Map<String,Double> resultsMap = new HashMap<>();
long timestamp = -1L;
if (apiVer==1) {
Map response = restClient.get()
.uri(url)
.retrieve()
.body(Map.class);
// Need to call for each pod separately
// or get the total
timestamp = Long.parseLong( response.get("before").toString() );
List<String> chart_ids = (List) response.get("chart_ids");
List<String> dimension_ids = (List) response.get("dimension_ids");
List<String> latest_values = (List) response.get("view_latest_value");
for (int i=0, n=chart_ids.size(); i<n; i++) {
String id = chart_ids.get(i) + "|" + dimension_ids.get(i);
try {
// double v = values.get(i).doubleValue();
double v = Double.parseDouble(latest_values.get(i));
resultsMap.put(id, v);
} catch (Exception e) {
log.warn("K8sNetdataCollector: collectData(): ERROR at index #{}: id={}, value={}, Exception: ",
i, id, latest_values.get(i), e);
resultsMap.put(id, 0.0);
}
}
} else
if (apiVer==2) {
log.warn("K8sNetdataCollector: collectData(): Calling Netdata: apiVer={}, url={}", apiVer, url);
Map response = restClient.get()
.uri(url)
.retrieve()
.body(Map.class);
log.trace("K8sNetdataCollector: collectData(): apiVer={}, response={}", apiVer, response);
double result = Double.parseDouble( response.get("result").toString() );
Map view = (Map) response.get("view");
long after = Long.parseLong( view.get("after").toString() );
long before = Long.parseLong( view.get("before").toString() );
timestamp = before;
log.trace("K8sNetdataCollector: collectData(): result={}, after={}, before={}", result, after, before);
Map dimensions = (Map) view.get("dimensions");
List<String> ids = (List<String>) dimensions.get("ids");
List<String> units = (List<String>) dimensions.get("units");
List<Number> values = (List<Number>) ((Map)dimensions.get("sts")).get("avg");
log.trace("K8sNetdataCollector: collectData(): ids={}", ids);
log.trace("K8sNetdataCollector: collectData(): units={}", units);
log.trace("K8sNetdataCollector: collectData(): values={}", values);
for (int i=0, n=ids.size(); i<n; i++) {
try {
double v = values.get(i).doubleValue();
resultsMap.put(ids.get(i), v);
} catch (Exception e) {
log.warn("K8sNetdataCollector: collectData(): ERROR at index #{}: id={}, value={}, Exception: ",
i, ids.get(i), values.get(i), e);
resultsMap.put(ids.get(i), 0.0);
}
}
//resultsMap.put("result", result);
}
log.warn("K8sNetdataCollector: collectData(): Data collected: timestamp={}, results={}", timestamp, resultsMap);
// Publish collected data to destination
final long timestamp1 = timestamp;
Map<String, CollectorContext.PUBLISH_RESULT> publishResults = new LinkedHashMap<>();
resultsMap.forEach((k,v) -> {
publishResults.put( k+"="+v, publishMetricEvent(destination, k, v, timestamp1, null) );
});
log.debug("K8sNetdataCollector: collectData(): Events published: results={}", publishResults);
long endTm = System.currentTimeMillis();
log.warn("K8sNetdataCollector: collectData(): END: duration={}ms", endTm-startTm);
}
private synchronized void doStop() {
log.debug("K8sNetdataCollector: doStop(): BEGIN");
log.trace("K8sNetdataCollector: doStop(): BEGIN: scheduledFuturesList={}", scheduledFuturesList);
// Cancel all task scheduler futures
scheduledFuturesList.forEach(future -> future.cancel(true));
scheduledFuturesList.clear();
log.debug("K8sNetdataCollector: doStop(): END");
}
public synchronized void activeGroupingChanged(String oldGrouping, String newGrouping) {
log.debug("K8sNetdataCollector: activeGroupingChanged: {} --> {}", oldGrouping, newGrouping);
}
protected CollectorContext.PUBLISH_RESULT publishMetricEvent(String metricName, String key, double metricValue, long timestamp, String nodeAddress) {
EventMap event = new EventMap(metricValue, 1, timestamp);
return sendEvent(metricName, metricName, key, event, null, true);
}
private CollectorContext.PUBLISH_RESULT sendEvent(String metricName, String originalTopic, String key, EventMap event, String nodeAddress, boolean createDestination) {
event.setEventProperty(EmsConstant.EVENT_PROPERTY_SOURCE_ADDRESS, nodeAddress);
event.getEventProperties().put(EmsConstant.EVENT_PROPERTY_EFFECTIVE_DESTINATION, metricName);
event.getEventProperties().put(EmsConstant.EVENT_PROPERTY_ORIGINAL_DESTINATION, originalTopic);
event.getEventProperties().put(EmsConstant.EVENT_PROPERTY_KEY, key);
log.debug("K8sNetdataCollector: Publishing metric: {}: {}", metricName, event.getMetricValue());
CollectorContext.PUBLISH_RESULT result = collectorContext.sendEvent(null, metricName, event, createDestination);
log.trace("K8sNetdataCollector: Publishing metric: {}: {} -> result: {}", metricName, event.getMetricValue(), result);
return result;
}
}

View File

@ -21,11 +21,9 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Collects measurements from Netdata http server
@ -33,6 +31,8 @@ import java.util.stream.Collectors;
@Slf4j
@Component
public class NetdataCollector extends gr.iccs.imu.ems.common.collector.netdata.NetdataCollector implements Collector {
private List<Map<String,Object>> configuration;
public NetdataCollector(@NonNull NetdataCollectorProperties properties,
@NonNull CollectorContext collectorContext,
@NonNull TaskScheduler taskScheduler,
@ -43,6 +43,22 @@ public class NetdataCollector extends gr.iccs.imu.ems.common.collector.netdata.N
throw new IllegalArgumentException("Invalid CollectorContext provided. Expected: ClientCollectorContext, but got "+collectorContext.getClass().getName());
}
@Override
public String getName() {
return "netdata";
}
@Override
public void setConfiguration(Object config) {
if (config instanceof List sensorConfigList) {
configuration = sensorConfigList.stream()
.filter(o -> o instanceof Map)
.filter(map -> ((Map)map).keySet().stream().allMatch(k->k instanceof String))
.toList();
log.info("Collectors::Netdata: setConfiguration: {}", configuration);
}
}
public synchronized void activeGroupingChanged(String oldGrouping, String newGrouping) {
HashSet<String> topics = new HashSet<>();
for (String g : GROUPING.getNames()) {

View File

@ -21,11 +21,9 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Collects measurements from Prometheus exporter
@ -33,6 +31,8 @@ import java.util.stream.Collectors;
@Slf4j
@Component
public class PrometheusCollector extends gr.iccs.imu.ems.common.collector.prometheus.PrometheusCollector implements Collector {
private List<Map<String,Object>> configuration;
public PrometheusCollector(@NonNull PrometheusCollectorProperties properties,
@NonNull CollectorContext collectorContext,
@NonNull TaskScheduler taskScheduler,
@ -43,6 +43,22 @@ public class PrometheusCollector extends gr.iccs.imu.ems.common.collector.promet
throw new IllegalArgumentException("Invalid CollectorContext provided. Expected: ClientCollectorContext, but got "+collectorContext.getClass().getName());
}
@Override
public String getName() {
return "prometheus";
}
@Override
public void setConfiguration(Object config) {
if (config instanceof List sensorConfigList) {
configuration = sensorConfigList.stream()
.filter(o -> o instanceof Map)
.filter(map -> ((Map)map).keySet().stream().allMatch(k->k instanceof String))
.toList();
log.info("Collectors::Prometheus: setConfiguration: {}", configuration);
}
}
public synchronized void activeGroupingChanged(String oldGrouping, String newGrouping) {
HashSet<String> topics = new HashSet<>();
for (String g : GROUPING.getNames()) {

View File

@ -63,6 +63,10 @@
<groupId>org.springframework</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>

View File

@ -49,6 +49,7 @@ public class BaguetteServer implements InitializingBean, EventBus.EventConsumer<
@Getter
private final SelfHealingManager<NodeRegistryEntry> selfHealingManager;
private final TaskScheduler taskScheduler;
private final ConfigWriteService configWriteService;
private Sshd server;
@ -147,6 +148,8 @@ public class BaguetteServer implements InitializingBean, EventBus.EventConsumer<
public BrokerCepService getBrokerCepService() { return brokerCepService; }
public Map<String, String> getServerConnectionInfo() { return server.getServerConnectionInfo(); }
public String getServerPubkey() { return server.getPublicKey(); }
public String getServerPubkeyFingerprint() { return server.getPublicKeyFingerprint(); }
@ -162,13 +165,26 @@ public class BaguetteServer implements InitializingBean, EventBus.EventConsumer<
if (server == null) {
eventBus.subscribe(RecoveryConstant.SELF_HEALING_RECOVERY_GIVE_UP, this);
// Start SSH server
log.info("BaguetteServer.startServer(): Starting SSH server...");
nodeRegistry.setCoordinator(coordinator);
Sshd server = new Sshd();
server.start(config, coordinator, eventBus, nodeRegistry);
server.setNodeRegistry(getNodeRegistry());
//server.setNodeRegistry(getNodeRegistry());
this.server = server;
log.info("BaguetteServer.startServer(): Starting SSH server... done");
// Store Baguette Server connection info in a properties file
try {
configWriteService
.getOrCreateConfigFile(
EmsConstant.EMS_CLIENT_K8S_CONFIG_MAP_FILE,
EmsConstant.EMS_CLIENT_K8S_CONFIG_MAP_FORMAT)
.putAll(server.getServerConnectionInfo());
} catch (Exception e) {
log.error("BaguetteServer.startServer(): Failed to store connection info in ems-client-config-map: {}, Exception: ",
EmsConstant.EMS_CLIENT_K8S_CONFIG_MAP_FILE, e);
}
} else {
log.info("BaguetteServer.startServer(): SSH server is already running");
}

View File

@ -164,9 +164,23 @@ public class ClientShellCommand implements Command, Runnable, ServerSessionAware
if (entry!=null) {
setNodeRegistryEntry(entry);
} else {
log.error("{}--> initNodeRegistryEntry: No node registry entry found for client: address={}", id, address);
log.error("{}--> initNodeRegistryEntry: Marked client session for immediate close: address={}", id, address);
setCloseConnection(true);
log.info("{}--> initNodeRegistryEntry: No node registry entry found for client: address={}", id, address);
if (coordinator.allowNotPreregisteredNode(this)) {
try {
log.info("{}--> initNodeRegistryEntry: Preregistering new client: address={}", id, address);
HashMap<String, String> nodeInfo = new HashMap<>();
nodeInfo.put("address", address);
entry = coordinator.getServer().registerClient(nodeInfo);
setNodeRegistryEntry(entry);
} catch (Exception e) {
log.error("{}--> initNodeRegistryEntry: Exception while registering new client: address={}, EXCEPTION: ", id, address, e);
log.error("{}--> initNodeRegistryEntry: Marked client session for immediate close: address={}", id, address);
setCloseConnection(true);
}
} else {
log.error("{}--> initNodeRegistryEntry: Marked client session for immediate close: address={}", id, address);
setCloseConnection(true);
}
}
}

View File

@ -38,17 +38,22 @@ public class NodeRegistry {
// Get IP address from provided hostname or address
Throwable errorObj = null;
try {
log.debug("NodeRegistry.addNode(): Resolving IP address from provided hostname/address: {}", hostnameOrAddress);
InetAddress host = InetAddress.getByName(hostnameOrAddress);
log.trace("NodeRegistry.addNode(): InetAddress for provided hostname/address: {}, InetAddress: {}", hostnameOrAddress, host);
String resolvedIpAddress = host.getHostAddress();
log.info("NodeRegistry.addNode(): Provided-Address={}, Resolved-IP-Address={}", hostnameOrAddress, resolvedIpAddress);
ipAddress = resolvedIpAddress;
} catch (UnknownHostException e) {
log.error("NodeRegistry.addNode(): EXCEPTION while resolving IP address from provided hostname/address: {}\n", ipAddress, e);
errorObj = e;
//throw e;
if (StringUtils.isNotBlank(ipAddress)) {
try {
log.debug("NodeRegistry.addNode(): Resolving IP address from provided hostname/address: {}", hostnameOrAddress);
InetAddress host = InetAddress.getByName(hostnameOrAddress);
log.trace("NodeRegistry.addNode(): InetAddress for provided hostname/address: {}, InetAddress: {}", hostnameOrAddress, host);
String resolvedIpAddress = host.getHostAddress();
log.info("NodeRegistry.addNode(): Provided-Address={}, Resolved-IP-Address={}", hostnameOrAddress, resolvedIpAddress);
ipAddress = resolvedIpAddress;
} catch (UnknownHostException e) {
log.error("NodeRegistry.addNode(): EXCEPTION while resolving IP address from provided hostname/address: {}\n", ipAddress, e);
errorObj = e;
//throw e;
}
} else {
ipAddress = "unknown-address-"+System.currentTimeMillis(); // We don't know POD address
nodeInfo.put("address", ipAddress);
}
nodeInfo.put("original-address", hostnameOrAddress);
nodeInfo.put("address", ipAddress);

View File

@ -142,6 +142,20 @@ public class Sshd {
log.info("SSH server: Stopped");
}
public Map<String,String> getServerConnectionInfo() {
Map.Entry<String, String> credentials = configuration.getCredentials().getPreferredPair();
return Map.of(
"BAGUETTE_SERVER_ADDRESS", configuration.getServerAddress(),
"BAGUETTE_SERVER_PORT", Integer.toString(configuration.getServerPort()),
"BAGUETTE_SERVER_PUBKEY", StringEscapeUtils.unescapeJson(serverPubkey),
"BAGUETTE_SERVER_PUBKEY_FINGERPRINT", serverPubkeyFingerprint,
"BAGUETTE_SERVER_PUBKEY_ALGORITHM", serverPubkeyAlgorithm,
"BAGUETTE_SERVER_PUBKEY_FORMAT", serverPubkeyFormat,
"BAGUETTE_SERVER_USERNAME", credentials.getKey(),
"BAGUETTE_SERVER_PASSWORD", credentials.getValue()
);
}
public void startHeartbeat(long period) {
heartbeatOn = true;
Thread heartbeat = new Thread(

View File

@ -97,7 +97,7 @@ public class NoopCoordinator implements ServerCoordinator {
if (!checkStarted && started) {
log.warn("{}: {}(): Coordinator is already running{}", className, methodName, str);
} else {
log.info("{}: {}(): Method invoked{}", className, methodName, str);
log.debug("{}: {}(): Method invoked{}", className, methodName, str);
}
return started;
}

View File

@ -27,6 +27,10 @@ public class ClusterSelfHealing {
// Server-side self-healing methods
// ------------------------------------------------------------------------
public boolean isEnabled() {
return selfHealingManager.isEnabled();
}
List<NodeRegistryEntry> getAggregatorCapableNodesInZone(IClusterZone zone) {
// Get the normal nodes in the zone that can be Aggregators (i.e. Aggregator and candidates)
List<NodeRegistryEntry> aggregatorCapableNodes = zone.findAggregatorCapableNodes();

View File

@ -89,8 +89,12 @@ public class ClusteringCoordinator extends NoopCoordinator {
topLevelGrouping, aggregatorGrouping, lastLevelGrouping);
// Configure Self-Healing manager
clusterSelfHealing = new ClusterSelfHealing(server.getSelfHealingManager());
server.getSelfHealingManager().setMode(SelfHealingManager.MODE.INCLUDED);
if (server.getSelfHealingManager().isEnabled()) {
clusterSelfHealing = new ClusterSelfHealing(server.getSelfHealingManager());
server.getSelfHealingManager().setMode(SelfHealingManager.MODE.INCLUDED);
} else {
clusterSelfHealing = null;
}
}
@SneakyThrows
@ -336,9 +340,11 @@ public class ClusteringCoordinator extends NoopCoordinator {
log.trace("addNodeInTopology: CSC is in zone: client={}, address={}, zone={}", csc.getId(), csc.getClientIpAddress(), zone.getId());
// Self-healing-related actions
List<NodeRegistryEntry> aggregatorCapableNodes = clusterSelfHealing.getAggregatorCapableNodesInZone(zone);
clusterSelfHealing.updateNodesSelfHealingMonitoring(zone, aggregatorCapableNodes);
clusterSelfHealing.removeResourceLimitedNodeSelfHealingMonitoring(zone, aggregatorCapableNodes);
if (clusterSelfHealing!=null && clusterSelfHealing.isEnabled()) {
List<NodeRegistryEntry> aggregatorCapableNodes = clusterSelfHealing.getAggregatorCapableNodesInZone(zone);
clusterSelfHealing.updateNodesSelfHealingMonitoring(zone, aggregatorCapableNodes);
clusterSelfHealing.removeResourceLimitedNodeSelfHealingMonitoring(zone, aggregatorCapableNodes);
}
}
}
@ -371,11 +377,13 @@ public class ClusteringCoordinator extends NoopCoordinator {
}
// Self-healing-related actions
List<NodeRegistryEntry> aggregatorCapableNodes = clusterSelfHealing.getAggregatorCapableNodesInZone(zone);
clusterSelfHealing.updateNodesSelfHealingMonitoring(zone, aggregatorCapableNodes);
if (aggregatorCapableNodes.isEmpty())
; //XXX: TODO: ??Reconfigure non-candidate nodes to forward their events to EMS server??
clusterSelfHealing.addResourceLimitedNodeSelfHealingMonitoring(zone, aggregatorCapableNodes);
if (clusterSelfHealing!=null && clusterSelfHealing.isEnabled()) {
List<NodeRegistryEntry> aggregatorCapableNodes = clusterSelfHealing.getAggregatorCapableNodesInZone(zone);
clusterSelfHealing.updateNodesSelfHealingMonitoring(zone, aggregatorCapableNodes);
if (aggregatorCapableNodes.isEmpty())
; //XXX: TODO: ??Reconfigure non-candidate nodes to forward their events to EMS server??
clusterSelfHealing.addResourceLimitedNodeSelfHealingMonitoring(zone, aggregatorCapableNodes);
}
}
}

View File

@ -76,6 +76,8 @@ public class BaguetteServerProperties implements InitializingBean {
private String keyFile = "hostkey.pem";
public String getServerKeyFile() { return keyFile; }
private String connectionInfoFile = "baguette-server-connection-info.json";
private boolean heartbeatEnabled;
@Min(-1)
private long heartbeatPeriod = 60000;

View File

@ -0,0 +1,34 @@
#!/usr/bin/env bash
#
# Copyright (C) 2017-2025 Institute of Communication and Computer Systems (imu.iccs.gr)
#
# This Source Code Form is subject to the terms of the Mozilla Public License, v2.0, unless
# Esper library is used, in which case it is subject to the terms of General Public License v2.0.
# If a copy of the MPL was not distributed with this file, you can obtain one at
# https://www.mozilla.org/en-US/MPL/2.0/
#
EMS_CLIENT_K8S_CONFIG_MAP_NAME=ems-client-configmap
EMS_CLIENT_K8S_CONFIG_MAP_FILE=ems-client-configmap.json
K8S_OUTPUT_DIR=$EMS_CONFIG_DIR/k8s
K8S_OUTPUT_FILE=$K8S_OUTPUT_DIR/cfgmap_output.json
# Check if baguette server connection info file exists
[ ! -f $EMS_CLIENT_K8S_CONFIG_MAP_FILE ] && exit 1
# Read baguette server connection info file into a variable
BSCI=$( tr -d "\r\n" < $EMS_CLIENT_K8S_CONFIG_MAP_FILE )
#echo $BSCI
# Write baguette server connection info into ems-client-configmap
mkdir -p $K8S_OUTPUT_DIR/
echo "/* Date: $(date) */" > $K8S_OUTPUT_FILE
sec=/var/run/secrets/kubernetes.io/serviceaccount
curl -sS \
-H "Authorization: Bearer $(cat $sec/token)" \
-H "Content-Type: application/json-patch+json" \
--cacert $sec/ca.crt \
--request PATCH \
--data "[{\"op\": \"replace\", \"path\": \"/data\", \"value\": $BSCI}]" \
https://$KUBERNETES_SERVICE_HOST/api/v1/namespaces/$(cat $sec/namespace)/configmaps/$EMS_CLIENT_K8S_CONFIG_MAP_NAME \
&>> $K8S_OUTPUT_FILE

View File

@ -13,7 +13,7 @@ set PWD=%~dp0
cd %PWD%..
set BASEDIR=%cd%
IF NOT DEFINED EMS_CONFIG_DIR set EMS_CONFIG_DIR=%BASEDIR%\config-files
IF NOT DEFINED PAASAGE_CONFIG_DIR set PAASAGE_CONFIG_DIR=%BASEDIR%\config-files
:: IF NOT DEFINED PAASAGE_CONFIG_DIR set PAASAGE_CONFIG_DIR=%BASEDIR%\config-files
IF NOT DEFINED JARS_DIR set JARS_DIR=%BASEDIR%\control-service\target
IF NOT DEFINED LOGS_DIR set LOGS_DIR=%BASEDIR%\logs
IF NOT DEFINED PUBLIC_DIR set PUBLIC_DIR=%BASEDIR%\public_resources
@ -31,7 +31,7 @@ if "%EMS_SECRETS_FILE%"=="" (
set EMS_SECRETS_FILE=%EMS_CONFIG_DIR%\secrets.properties
)
if "%EMS_CONFIG_LOCATION%"=="" (
set EMS_CONFIG_LOCATION=classpath:rule-templates.yml,optional:file:%EMS_CONFIG_DIR%\ems-server.yml,optional:file:%EMS_CONFIG_DIR%\ems-server.properties,optional:file:%EMS_CONFIG_DIR%\ems.yml,optional:file:%EMS_CONFIG_DIR%\ems.properties,optional:file:%EMS_SECRETS_FILE%
set EMS_CONFIG_LOCATION=optional:classpath:rule-templates.yml,optional:file:%EMS_CONFIG_DIR%\ems-server.yml,optional:file:%EMS_CONFIG_DIR%\ems-server.properties,optional:file:%EMS_CONFIG_DIR%\ems.yml,optional:file:%EMS_CONFIG_DIR%\ems.properties,optional:file:%EMS_SECRETS_FILE%
)
:: Check logger configuration
@ -46,6 +46,9 @@ if "%LOG_FILE%"=="" (
:: Set shell encoding to UTF-8 (in order to display banner correctly)
chcp 65001
:: Create default models directory
mkdir %BASEDIR%\models
:: Run EMS server
rem Uncomment next line to set JAVA runtime options
rem set JAVA_OPTS=-Djavax.net.debug=all

View File

@ -13,7 +13,7 @@ PREVWORKDIR=`pwd`
BASEDIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )
cd ${BASEDIR}
if [[ -z $EMS_CONFIG_DIR ]]; then EMS_CONFIG_DIR=$BASEDIR/config-files; export EMS_CONFIG_DIR; fi
if [[ -z $PAASAGE_CONFIG_DIR ]]; then PAASAGE_CONFIG_DIR=$BASEDIR/config-files; export PAASAGE_CONFIG_DIR; fi
#if [[ -z $PAASAGE_CONFIG_DIR ]]; then PAASAGE_CONFIG_DIR=$BASEDIR/config-files; export PAASAGE_CONFIG_DIR; fi
if [[ -z $JARS_DIR ]]; then JARS_DIR=$BASEDIR/control-service/target; export JARS_DIR; fi
if [[ -z $LOGS_DIR ]]; then LOGS_DIR=$BASEDIR/logs; export LOGS_DIR; fi
if [[ -z $PUBLIC_DIR ]]; then PUBLIC_DIR=$BASEDIR/public_resources; export PUBLIC_DIR; fi
@ -34,7 +34,7 @@ if [[ -z "$EMS_SECRETS_FILE" ]]; then
EMS_SECRETS_FILE=$EMS_CONFIG_DIR/secrets.properties
fi
if [[ -z "$EMS_CONFIG_LOCATION" ]]; then
EMS_CONFIG_LOCATION=classpath:rule-templates.yml,optional:file:$EMS_CONFIG_DIR/ems-server.yml,optional:file:$EMS_CONFIG_DIR/ems-server.properties,optional:file:$EMS_CONFIG_DIR/ems.yml,optional:file:$EMS_CONFIG_DIR/ems.properties,optional:file:$EMS_SECRETS_FILE
EMS_CONFIG_LOCATION=optional:classpath:rule-templates.yml,optional:file:$EMS_CONFIG_DIR/ems-server.yml,optional:file:$EMS_CONFIG_DIR/ems-server.properties,optional:file:$EMS_CONFIG_DIR/ems.yml,optional:file:$EMS_CONFIG_DIR/ems.properties,optional:file:$EMS_SECRETS_FILE
fi
# Check logger configuration
@ -52,6 +52,9 @@ export LANG=C.UTF-8
# Setup TERM & INT signal handler
trap 'echo "Signaling EMS to exit"; kill -TERM "${emsPid}"; wait "${emsPid}"; ' SIGTERM SIGINT
# Create default models directory
mkdir -p ${BASEDIR}/models
# Run EMS server
# Uncomment next line to set JAVA runtime options
#JAVA_OPTS=-Djavax.net.debug=all

View File

@ -14,6 +14,8 @@ import gr.iccs.imu.ems.brokercep.cep.CepService;
import gr.iccs.imu.ems.brokercep.event.EventMap;
import gr.iccs.imu.ems.brokercep.properties.BrokerCepProperties;
import gr.iccs.imu.ems.util.StrUtil;
import jakarta.jms.*;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.activemq.broker.BrokerService;
@ -27,11 +29,12 @@ import org.springframework.context.event.ContextClosedEvent;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Service;
import jakarta.jms.*;
import java.time.Instant;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
@ -59,6 +62,9 @@ public class BrokerCepConsumer implements MessageListener, InitializingBean, App
private final EventCache eventCache;
@Getter
private final List<MessageListener> listeners = new LinkedList<>();
@Override
public void afterPropertiesSet() {
initialize();
@ -123,36 +129,28 @@ public class BrokerCepConsumer implements MessageListener, InitializingBean, App
}
public synchronized void addQueue(String queueName) {
log.debug("BrokerCepConsumer.addQueue(): Adding queue: {}", queueName);
if (addedDestinations.containsKey(queueName)) {
log.debug("BrokerCepConsumer.addQueue(): Queue already added: {}", queueName);
return;
}
try {
Queue queue = session.createQueue(queueName);
MessageConsumer consumer = session.createConsumer(queue);
consumer.setMessageListener(this);
addedDestinations.put(queueName, consumer);
log.debug("BrokerCepConsumer.addQueue(): Added queue: {}", queueName);
} catch (Exception ex) {
log.error("BrokerCepConsumer.addQueue(): EXCEPTION: ", ex);
}
addDestinationAndListener(queueName, false, this);
}
public synchronized void addTopic(String topicName) {
log.debug("BrokerCepConsumer.addTopic(): Adding topic: {}", topicName);
if (addedDestinations.containsKey(topicName)) {
log.debug("BrokerCepConsumer.addTopic(): Topic already added: {}", topicName);
addDestinationAndListener(topicName, true, this);
}
private synchronized void addDestinationAndListener(String destinationName, boolean isTopic, MessageListener listener) {
log.debug("BrokerCepConsumer.addDestinationAndListener(): Adding destination: {}", destinationName);
if (addedDestinations.containsKey(destinationName)) {
log.debug("BrokerCepConsumer.addDestinationAndListener(): Destination already added: {}", destinationName);
return;
}
try {
Topic topic = session.createTopic(topicName);
MessageConsumer consumer = session.createConsumer(topic);
consumer.setMessageListener(this);
addedDestinations.put(topicName, consumer);
log.debug("BrokerCepConsumer.addTopic(): Added topic: {}", topicName);
Destination destination = isTopic
? session.createTopic(destinationName) : session.createQueue(destinationName);
MessageConsumer consumer = session.createConsumer(destination);
consumer.setMessageListener(listener);
addedDestinations.put(destinationName, consumer);
log.debug("BrokerCepConsumer.addDestinationAndListener(): Added destination: {}", destinationName);
} catch (Exception ex) {
log.error("BrokerCepConsumer.addTopic(): EXCEPTION: ", ex);
log.error("BrokerCepConsumer.addDestinationAndListener(): EXCEPTION: ", ex);
}
}
@ -224,6 +222,10 @@ public class BrokerCepConsumer implements MessageListener, InitializingBean, App
log.warn("BrokerCepConsumer.onMessage(): Message ignored: type={}", message.getClass().getName());
}
eventCounter.incrementAndGet();
// Notify listeners
listeners.forEach(l -> l.onMessage(message));
} catch (Exception ex) {
log.error("BrokerCepConsumer.onMessage(): EXCEPTION: ", ex);
eventFailuresCounter.incrementAndGet();

View File

@ -300,7 +300,7 @@ public class BrokerConfig implements InitializingBean {
List<BrokerPlugin> plugins = new ArrayList<>();
if (getBrokerAuthenticationPlugin()!=null) plugins.add(getBrokerAuthenticationPlugin());
if (getBrokerAuthorizationPlugin()!=null) plugins.add(getBrokerAuthorizationPlugin());
if (plugins.size() > 0) {
if (!plugins.isEmpty()) {
brokerService.setPlugins(plugins.toArray(new BrokerPlugin[0]));
}

View File

@ -46,8 +46,9 @@ public class SourceAddressMessageUpdateInterceptor extends AbstractMessageInterc
boolean isLocal = StringUtils.isBlank(address) || NetUtil.isLocalAddress(address.trim());
if (isLocal) {
log.trace("SourceAddressMessageUpdateInterceptor: Producer host is local. Getting our public IP address");
address = NetUtil.getPublicIpAddress();
log.trace("SourceAddressMessageUpdateInterceptor: Producer host (public): {}", address);
address = NetUtil.getIpSettingAddress();
log.trace("SourceAddressMessageUpdateInterceptor: Producer host ({}): {}",
NetUtil.isUsePublic() ? "public" : "default", address);
} else {
log.trace("SourceAddressMessageUpdateInterceptor: Producer host is not local. Ok");
}

View File

@ -26,6 +26,7 @@ import java.util.stream.Collectors;
public class MathUtil {
static {
License.iConfirmNonCommercialUse("EMS-"+ NetUtil.getIpAddress());
mXparser.disableImpliedMultiplicationMode();
}
private static final Map<String, Function> functions = new HashMap<>();
private static final Map<String, Constant> constants = new HashMap<>();
@ -118,8 +119,20 @@ public class MathUtil {
// Get token names
List<Token> initTokens = e.getCopyOfInitialTokens();
log.debug("MathUtil: initial-tokens={}", initTokens);
if (log.isTraceEnabled()) {
log.trace("MathUtil: initial-tokens={}", initTokens.stream()
.map(token -> Map.of(
"tokenId", token.tokenId,
"tokenType", token.tokenTypeId,
"tokenStr", token.tokenStr,
"tokenValue", token.tokenValue,
"looksLike", token.looksLike,
"keyWord", token.keyWord,
"tokenLevel", token.tokenLevel,
"is-identifier", token.isIdentifier()
)
).toList()
);
mXparser.consolePrintTokens(initTokens);
}

View File

@ -10,6 +10,7 @@
package gr.iccs.imu.ems.brokercep.event;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import gr.iccs.imu.ems.util.StrUtil;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -29,7 +30,7 @@ import java.util.stream.Collectors;
@EqualsAndHashCode(callSuper = false)
public class EventMap extends LinkedHashMap<String, Object> implements Serializable {
private static Gson gson;
private static Gson gson = new GsonBuilder().create();
private static AtomicLong eventIdSequence = new AtomicLong(0);
// Standard/Known Event fields configuration

View File

@ -31,6 +31,7 @@ import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
@ -98,9 +99,9 @@ public class BrokerClientApp {
String url = processUrlArg( args[aa++] );
String topic = args[aa++];
String type = args[aa].startsWith("-T") ? args[aa++].substring(2) : "text";
boolean processPlaceholders = !args[aa].startsWith("-PP") || Boolean.parseBoolean(args[aa++].substring(3));
String payload = args[aa++];
payload = payload
.replaceAll("%TIMESTAMP%|%TS%", ""+System.currentTimeMillis());
payload = getPayload(payload, processPlaceholders);
EventMap event = gson.fromJson(payload, EventMap.class);
sendEvent(url, username, password, topic, type, event, collectProperties(args, aa));
} else
@ -108,9 +109,9 @@ public class BrokerClientApp {
String url = processUrlArg( args[aa++] );
String topic = args[aa++];
String type = args[aa].startsWith("-T") ? args[aa++].substring(2) : "text";
boolean processPlaceholders = !args[aa].startsWith("-PP") || Boolean.parseBoolean(args[aa++].substring(3));
String payload = args[aa++];
payload = payload
.replaceAll("%TIMESTAMP%|%TS%", ""+System.currentTimeMillis());
payload = getPayload(payload, processPlaceholders);
Map<String, String> properties = collectProperties(args, aa);
if ("map".equalsIgnoreCase(type)) {
EventMap event = gson.fromJson(payload, EventMap.class);
@ -224,6 +225,24 @@ public class BrokerClientApp {
}
}
private static String getPayload(String payload, boolean processPlaceholders) throws IOException {
if (payload==null) return null;
String payloadTrim = payload.trim();
if (StringUtils.startsWith(payloadTrim, "@")) {
payload = Files.readString(Paths.get(StringUtils.substring(payloadTrim, 1)));
}
if ("-".equals(payloadTrim)) {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
payload = reader.lines().collect(Collectors.joining("\n"));
}
if (processPlaceholders) {
payload = payload
.replaceAll("%TIMESTAMP%|%TS%", ""+System.currentTimeMillis());
}
return payload;
}
private static Map<String, String> collectProperties(String[] args, int aa) {
return Arrays.stream(args, aa, args.length)
.map(s->s.split("[=:]",2))
@ -741,9 +760,11 @@ public class BrokerClientApp {
log.info("BrokerClientApp: Usage: ");
log.info("BrokerClientApp: client list [-U<USERNAME> [-P<PASSWORD]] <URL> ");
log.info("BrokerClientApp: client publish [ -U<USERNAME> [-P<PASSWORD]] <URL> <TOPIC> [-T<MSG-TYPE>] <VALUE> <LEVEL> [<PROPERTY>]*");
log.info("BrokerClientApp: client publish2 [-U<USERNAME> [-P<PASSWORD]] <URL> <TOPIC> [-T<MSG-TYPE>] <JSON-PAYLOAD> [<PROPERTY>]*");
log.info("BrokerClientApp: client publish3 [-U<USERNAME> [-P<PASSWORD]] <URL> <TOPIC> [-T<MSG-TYPE>] <TEXT-PAYLOAD> [<PROPERTY>]*");
log.info("BrokerClientApp: client publish2 [-U<USERNAME> [-P<PASSWORD]] <URL> <TOPIC> [-T<MSG-TYPE>] [-PP<true|false>] <JSON-PAYLOAD|-|@file> [<PROPERTY>]*");
log.info("BrokerClientApp: client publish3 [-U<USERNAME> [-P<PASSWORD]] <URL> <TOPIC> [-T<MSG-TYPE>] [-PP<true|false>] <TEXT-PAYLOAD|-|@file> [<PROPERTY>]*");
log.info("BrokerClientApp: <MSG-TYPE>: text, object, bytes, map");
log.info("BrokerClientApp: -PP: Process placeholders (default 'true')");
log.info("BrokerClientApp: '-' | '@file': Read payload from STDIN | Read payload from file 'file' ");
log.info("BrokerClientApp: <PROPERTY>: <Property name>=<Property value> (use quotes if needed)");
log.info("BrokerClientApp: client receive [-U<USERNAME> [-P<PASSWORD]] <URL> <TOPIC> ");
log.info("BrokerClientApp: client subscribe [-U<USERNAME> [-P<PASSWORD]] <URL> <TOPIC> ");

View File

@ -12,12 +12,14 @@ package gr.iccs.imu.ems.brokerclient.event;
import gr.iccs.imu.ems.brokerclient.BrokerClient;
import jakarta.annotation.PostConstruct;
import lombok.Data;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiFunction;
@Slf4j
@Data
@ -25,6 +27,7 @@ import java.util.concurrent.atomic.AtomicLong;
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class EventGenerator implements Runnable {
private final static AtomicLong counter = new AtomicLong();
private final BiFunction<String,EventMap,Boolean> eventPublisher;
private final BrokerClient client;
private String brokerUrl;
private String brokerUsername;
@ -38,6 +41,16 @@ public class EventGenerator implements Runnable {
private transient boolean keepRunning;
public EventGenerator(@NonNull BiFunction<String,EventMap,Boolean> eventPublisher) {
this.eventPublisher = eventPublisher;
this.client = null;
}
public EventGenerator(@NonNull BrokerClient brokerClient) {
this.eventPublisher = null;
this.client = brokerClient;
}
@PostConstruct
public void printCounter() {
log.info("New EventGenerator with instance number: {}", counter.getAndIncrement());
@ -65,7 +78,10 @@ public class EventGenerator implements Runnable {
double newValue = Math.random() * valueRangeWidth + lowerValue;
EventMap event = new EventMap(newValue, level, System.currentTimeMillis());
log.info("EventGenerator.run(): Sending event #{}: {}", countSent + 1, event);
client.publishEventWithCredentials(brokerUrl, brokerUsername, brokerPassword, destinationName, event);
if (eventPublisher!=null)
eventPublisher.apply(destinationName, event);
else
client.publishEventWithCredentials(brokerUrl, brokerUsername, brokerPassword, destinationName, event);
countSent++;
if (countSent == howMany) keepRunning = false;
log.info("EventGenerator.run(): Event sent #{}: {}", countSent, event);

View File

@ -181,7 +181,7 @@ public abstract class AbstractEndpointCollector<T> implements InitializingBean,
// collect data from local node
if (! properties.isSkipLocal()) {
log.debug/*info*/("Collectors::{}: Collecting metrics from local node...", collectorId);
log.debug("Collectors::{}: Collecting metrics from local node...", collectorId);
collectAndPublishData("");
} else {
log.debug("Collectors::{}: Collection from local node is disabled", collectorId);
@ -191,8 +191,8 @@ public abstract class AbstractEndpointCollector<T> implements InitializingBean,
log.trace("Collectors::{}: Nodes without clients in Zone: {}", collectorId, collectorContext.getNodesWithoutClient());
log.trace("Collectors::{}: Is Aggregator: {}", collectorId, collectorContext.isAggregator());
if (collectorContext.isAggregator()) {
if (collectorContext.getNodesWithoutClient().size()>0) {
log.debug/*info*/("Collectors::{}: Collecting metrics from remote nodes (without EMS client): {}", collectorId,
if (! collectorContext.getNodesWithoutClient().isEmpty()) {
log.debug("Collectors::{}: Collecting metrics from remote nodes (without EMS client): {}", collectorId,
collectorContext.getNodesWithoutClient());
for (Object nodeAddress : collectorContext.getNodesWithoutClient()) {
// collect data from remote node
@ -253,7 +253,7 @@ public abstract class AbstractEndpointCollector<T> implements InitializingBean,
private COLLECTION_RESULT collectAndPublishData(@NonNull String nodeAddress) {
if (ignoredNodes.containsKey(nodeAddress)) {
log.debug/*info*/("Collectors::{}: Node is in ignore list: {}", collectorId, nodeAddress);
log.debug("Collectors::{}: Node is in ignore list: {}", collectorId, nodeAddress);
return COLLECTION_RESULT.IGNORED;
}
@ -322,7 +322,7 @@ public abstract class AbstractEndpointCollector<T> implements InitializingBean,
// Remote node data collection URL
url = String.format(properties.getUrlOfNodesWithoutClient(), nodeAddress);
}
log.debug/*info*/("Collectors::{}: Collecting data from url: {}", collectorId, url);
log.debug("Collectors::{}: Collecting data from url: {}", collectorId, url);
log.debug("Collectors::{}: Collecting data: {}...", collectorId, url);
long startTm = System.currentTimeMillis();
@ -342,8 +342,8 @@ public abstract class AbstractEndpointCollector<T> implements InitializingBean,
log.debug("Collectors::{}: Collecting data...ok", collectorId);
//log.info("Collectors::{}: Metrics: extracted={}, published={}, failed={}", collectorId,
// stats.countSuccess + stats.countErrors, stats.countSuccess, stats.countErrors);
if (log.isInfoEnabled())
log.debug/*info*/("Collectors::{}: Publish statistics: {}", collectorId, stats);
if (log.isDebugEnabled())
log.debug("Collectors::{}: Publish statistics: {}", collectorId, stats);
log.debug("Collectors::{}: Durations: rest-call={}, extract+publish={}, total={}", collectorId,
callEndTm-startTm, endTm-callEndTm, endTm-startTm);
} else {

View File

@ -53,17 +53,17 @@ instructions:
executable: false
exitCode: 0
match: false
- description: Upload installation package MD5 checksum
- description: Upload installation package SHA256 checksum
taskType: COPY
fileName: /tmp/baguette-client.tgz.md5
localFileName: '${EMS_PUBLIC_DIR}/resources/baguette-client.tgz.md5'
fileName: /tmp/baguette-client.tgz.sha256
localFileName: '${EMS_PUBLIC_DIR}/resources/baguette-client.tgz.sha256'
executable: false
exitCode: 0
match: false
- description: Check MD5 checksum of installation package
- description: Check SHA256 checksum of installation package
taskType: CHECK
command: >-
[[ `cat /tmp/baguette-client.tgz.md5` != `md5sum /tmp/baguette-client.tgz | cut -d ' ' -f 1 ` ]] && exit 99
[[ `cat /tmp/baguette-client.tgz.sha256` != `sha256sum /tmp/baguette-client.tgz | cut -d ' ' -f 1 ` ]] && exit 99
executable: false
exitCode: 99
match: true

View File

@ -48,18 +48,18 @@
"match": false
},
{
"description": "Upload installation package MD5 checksum",
"description": "Upload installation package SHA256 checksum",
"taskType": "COPY",
"fileName": "/tmp/baguette-client.tgz.md5",
"localFileName": "${EMS_PUBLIC_DIR}/resources/baguette-client.tgz.md5",
"fileName": "/tmp/baguette-client.tgz.sha256",
"localFileName": "${EMS_PUBLIC_DIR}/resources/baguette-client.tgz.sha256",
"executable": false,
"exitCode": 0,
"match": false
},
{
"description": "Check MD5 checksum of installation package",
"description": "Check SHA256 checksum of installation package",
"taskType": "CHECK",
"command": "[[ `cat /tmp/baguette-client.tgz.md5` != `md5sum /tmp/baguette-client.tgz | cut -d ' ' -f 1 ` ]] && exit 99",
"command": "[[ `cat /tmp/baguette-client.tgz.sha256` != `sha256sum /tmp/baguette-client.tgz | cut -d ' ' -f 1 ` ]] && exit 99",
"executable": false,
"exitCode": 99,
"match": true

View File

@ -11,9 +11,9 @@
### EMS - Baguette Client properties ###
################################################################################
#password-encoder-class = password.gr.iccs.imu.ems.util.AsterisksPasswordEncoder
#password-encoder-class = password.gr.iccs.imu.ems.util.IdentityPasswordEncoder
#password-encoder-class = password.gr.iccs.imu.ems.util.PresentPasswordEncoder
#password-encoder-class = gr.iccs.imu.ems.util.password.AsterisksPasswordEncoder
#password-encoder-class = gr.iccs.imu.ems.util.password.IdentityPasswordEncoder
#password-encoder-class = gr.iccs.imu.ems.util.password.PresentPasswordEncoder
### Jasypt encryptor settings (using old settings until encrypted texts are updated)
jasypt.encryptor.algorithm = PBEWithMD5AndDES
@ -68,7 +68,7 @@ server-password = ${BAGUETTE_SERVER_PASSWORD}
# Collectors settings
# -----------------------------------------------------------------------------
#collector-classes = netdata.collector.gr.iccs.imu.ems.baguette.client.NetdataCollector
#collector-classes = gr.iccs.imu.ems.baguette.client.collector.netdata.NetdataCollector
collector.netdata.enable = true
collector.netdata.delay = 10000
@ -80,7 +80,7 @@ collector.netdata.allowed-topics = ${COLLECTOR_ALLOWED_TOPICS}
collector.netdata.error-limit = 3
collector.netdata.pause-period = 60
collector.prometheus.enable = false
collector.prometheus.enable = true
collector.prometheus.delay = 10000
collector.prometheus.url = http://127.0.0.1:9090/metrics
collector.prometheus.urlOfNodesWithoutClient = http://%s:9090/metrics
@ -151,8 +151,8 @@ brokercep.broker-protocol = ssl
BROKER_URL_PROPERTIES = transport.daemon=true&transport.trace=false&transport.useKeepAlive=true&transport.useInactivityMonitor=false&transport.needClientAuth=${CLIENT_AUTH_REQUIRED}&transport.verifyHostName=true&transport.connectionTimeout=0&transport.keepAlive=true
CLIENT_AUTH_REQUIRED = false
brokercep.broker-url[0] = ${brokercep.broker-protocol}://0.0.0.0:${brokercep.broker-port}?${BROKER_URL_PROPERTIES}
brokercep.broker-url[1] = tcp://127.0.0.1:61616?${BROKER_URL_PROPERTIES}
brokercep.broker-url[2] =
brokercep.broker-url[1] = tcp://${BROKER_URL_ADDRESS_INSECURE:127.0.0.1}:61616?${BROKER_URL_PROPERTIES}
brokercep.broker-url[2] = stomp://${BROKER_URL_ADDRESS_INSECURE:127.0.0.1}:61610?${BROKER_URL_PROPERTIES}
CLIENT_URL_PROPERTIES=daemon=true&trace=false&useInactivityMonitor=false&connectionTimeout=0&keepAlive=true
brokercep.broker-url-for-consumer = tcp://127.0.0.1:61616?${CLIENT_URL_PROPERTIES}
@ -163,16 +163,18 @@ brokercep.broker-url-for-clients = ${brokercep.broker-protocol}://${EMS_CLIENT_A
brokercep.ssl.keystore-file = ${EMS_CONFIG_DIR}/client-broker-keystore.p12
brokercep.ssl.keystore-type = PKCS12
#brokercep.ssl.keystore-password = melodic
brokercep.ssl.keystore-password = ENC(ISMbn01HVPbtRPkqm2Lslg==)
#brokercep.ssl.keystore-password = ENC(ISMbn01HVPbtRPkqm2Lslg==)
brokercep.ssl.keystore-password = ${EMS_KEYSTORE_PASSWORD}
# Trust store
brokercep.ssl.truststore-file = ${EMS_CONFIG_DIR}/client-broker-truststore.p12
brokercep.ssl.truststore-type = PKCS12
#brokercep.ssl.truststore-password = melodic
brokercep.ssl.truststore-password = ENC(ISMbn01HVPbtRPkqm2Lslg==)
#brokercep.ssl.truststore-password = ENC(ISMbn01HVPbtRPkqm2Lslg==)
brokercep.ssl.truststore-password = ${EMS_TRUSTSTORE_PASSWORD}
# Certificate
brokercep.ssl.certificate-file = ${EMS_CONFIG_DIR}/client-broker.crt
# Key-and-Cert data
brokercep.ssl.key-entry-generate = IF-IP-CHANGED
brokercep.ssl.key-entry-generate = ALWAYS
brokercep.ssl.key-entry-name = ${EMS_CLIENT_ADDRESS}
brokercep.ssl.key-entry-dname = CN=${EMS_CLIENT_ADDRESS},OU=Information Management Unit (IMU),O=Institute of Communication and Computer Systems (ICCS),L=Athens,ST=Attika,C=GR
brokercep.ssl.key-entry-ext-san = dns:localhost,ip:127.0.0.1,ip:${DEFAULT_IP},ip:${PUBLIC_IP}
@ -180,7 +182,8 @@ brokercep.ssl.key-entry-ext-san = dns:localhost,ip:127.0.0.1,ip:${DEFAULT_IP},ip
# Authentication and Authorization settings
brokercep.authentication-enabled = true
#brokercep.additional-broker-credentials = aaa/111, bbb/222, morphemic/morphemic
brokercep.additional-broker-credentials = ENC(axeJUxNHajYfBffUwvuT3kwTgLTpRliDMz/ZQ9hROZ3BNOv0Idw72NJsawzIZRuZ)
#brokercep.additional-broker-credentials = ENC(axeJUxNHajYfBffUwvuT3kwTgLTpRliDMz/ZQ9hROZ3BNOv0Idw72NJsawzIZRuZ)
brokercep.additional-broker-credentials = ${EMS_CLIENT_ADDITIONAL_BROKER_CREDENTIALS}
brokercep.authorization-enabled = false
# Broker instance settings
@ -194,14 +197,14 @@ brokercep.broker-using-shutdown-hook = false
# Message interceptors
brokercep.message-interceptors[0].destination = >
brokercep.message-interceptors[0].className = interceptor.broker.gr.iccs.imu.ems.brokercep.SequentialCompositeInterceptor
brokercep.message-interceptors[0].className = gr.iccs.imu.ems.brokercep.broker.interceptor.SequentialCompositeInterceptor
brokercep.message-interceptors[0].params[0] = #SourceAddressMessageUpdateInterceptor
brokercep.message-interceptors[0].params[1] = #MessageForwarderInterceptor
brokercep.message-interceptors[0].params[2] = #NodePropertiesMessageUpdateInterceptor
brokercep.message-interceptors-specs.SourceAddressMessageUpdateInterceptor.className = interceptor.broker.gr.iccs.imu.ems.brokercep.SourceAddressMessageUpdateInterceptor
brokercep.message-interceptors-specs.MessageForwarderInterceptor.className = interceptor.broker.gr.iccs.imu.ems.brokercep.MessageForwarderInterceptor
brokercep.message-interceptors-specs.NodePropertiesMessageUpdateInterceptor.className = interceptor.broker.gr.iccs.imu.ems.brokercep.NodePropertiesMessageUpdateInterceptor
brokercep.message-interceptors-specs.SourceAddressMessageUpdateInterceptor.className = gr.iccs.imu.ems.brokercep.broker.interceptor.SourceAddressMessageUpdateInterceptor
brokercep.message-interceptors-specs.MessageForwarderInterceptor.className = gr.iccs.imu.ems.brokercep.broker.interceptor.MessageForwarderInterceptor
brokercep.message-interceptors-specs.NodePropertiesMessageUpdateInterceptor.className = gr.iccs.imu.ems.brokercep.broker.interceptor.NodePropertiesMessageUpdateInterceptor
# Message forward destinations (MessageForwarderInterceptor must be included in 'message-interceptors' property)
#brokercep.message-forward-destinations[0].connection-string = tcp://localhost:51515

View File

@ -83,6 +83,8 @@ server-password: ${BAGUETTE_SERVER_PASSWORD}
#collector-classes: gr.iccs.imu.ems.baguette.client.collector.netdata.NetdataCollector
collector-configurations: ${COLLECTOR_CONFIGURATIONS}
collector:
netdata:
enable: true
@ -95,7 +97,7 @@ collector:
error-limit: 3
pause-period: 60
prometheus:
enable: false
enable: true
delay: 10000
url: http://127.0.0.1:9090/metrics
urlOfNodesWithoutClient: http://%s:9090/metrics
@ -174,8 +176,8 @@ brokercep:
# Broker connectors
broker-url:
- ${brokercep.broker-protocol}://0.0.0.0:${brokercep.broker-port}?${BROKER_URL_PROPERTIES}
- tcp://127.0.0.1:61616?${BROKER_URL_PROPERTIES}
- stomp://127.0.0.1:61610?${BROKER_URL_PROPERTIES}
- tcp://${BROKER_URL_ADDRESS_INSECURE:127.0.0.1}:61616?${BROKER_URL_PROPERTIES}
- stomp://${BROKER_URL_ADDRESS_INSECURE:127.0.0.1}:61610?${BROKER_URL_PROPERTIES}
# Broker URLs for (EMS) consumer and clients
broker-url-for-consumer: tcp://127.0.0.1:61616?${CLIENT_URL_PROPERTIES}
@ -186,18 +188,20 @@ brokercep:
# Key store settings
keystore-file: ${EMS_CONFIG_DIR}/client-broker-keystore.p12
keystore-type: PKCS12
keystore-password: 'ENC(ISMbn01HVPbtRPkqm2Lslg==)' # melodic
#keystore-password: 'ENC(ISMbn01HVPbtRPkqm2Lslg==)' # melodic
keystore-password: ${EMS_KEYSTORE_PASSWORD:${random.value}}
# Trust store settings
truststore-file: ${EMS_CONFIG_DIR}/client-broker-truststore.p12
truststore-type: PKCS12
truststore-password: 'ENC(ISMbn01HVPbtRPkqm2Lslg==)' # melodic
#truststore-password: 'ENC(ISMbn01HVPbtRPkqm2Lslg==)' # melodic
truststore-password: ${EMS_TRUSTSTORE_PASSWORD:${random.value}}
# Certificate settings
certificate-file: ${EMS_CONFIG_DIR}/client-broker.crt
# key generation settings
key-entry-generate: IF-IP-CHANGED
key-entry-generate: ALWAYS
key-entry-name: ${EMS_CLIENT_ADDRESS}
key-entry-dname: 'CN=${EMS_CLIENT_ADDRESS},OU=Information Management Unit (IMU),O=Institute of Communication and Computer Systems (ICCS),L=Athens,ST=Attika,C=GR'
key-entry-ext-san: 'dns:localhost,ip:127.0.0.1,ip:${DEFAULT_IP},ip:${PUBLIC_IP}'
@ -205,7 +209,8 @@ brokercep:
# Authentication and Authorization settings
authentication-enabled: true
#additional-broker-credentials: aaa/111, bbb/222, morphemic/morphemic
additional-broker-credentials: 'ENC(axeJUxNHajYfBffUwvuT3kwTgLTpRliDMz/ZQ9hROZ3BNOv0Idw72NJsawzIZRuZ)'
#additional-broker-credentials: 'ENC(axeJUxNHajYfBffUwvuT3kwTgLTpRliDMz/ZQ9hROZ3BNOv0Idw72NJsawzIZRuZ)'
additional-broker-credentials: ${EMS_CLIENT_ADDITIONAL_BROKER_CREDENTIALS}
authorization-enabled: false
# Broker instance settings

View File

@ -11,8 +11,9 @@
### Global settings
################################################################################
### Don't touch the next line!!
EMS_SERVER_ADDRESS=${${control.IP_SETTING}}
### Don't touch the next lines!!
EMS_IP_SETTING=${P_EMS_IP_SETTING:PUBLIC_IP}
EMS_SERVER_ADDRESS=${${EMS_IP_SETTING}}
DOLLAR=$
### Password Encoder settings
@ -111,9 +112,9 @@ control.log-requests = ${EMS_LOG_REQUESTS:false}
#control.skip-broker-cep = true
#control.skip-baguette = true
#control.skip-collectors = true
#control.skip-metasolver = true
#control.skip-notification = true
control.upperware-grouping = GLOBAL
control.skip-metasolver = true
control.skip-notification = true
#control.upperware-grouping = GLOBAL
### Debug settings - Load/Save translation results
control.tc-load-file = ${EMS_TC_LOAD_FILE:${EMS_TC_FILE:${LOGS_DIR:${EMS_CONFIG_DIR}/../logs}/_TC.json}}
@ -319,7 +320,7 @@ brokercep.broker-url[1] = tcp://0.0.0.0:61616?${BROKER_URL_PROPERTIES}
brokercep.broker-url[2] = stomp://0.0.0.0:61610
# Broker URLs for (EMS) consumer and clients
brokercep.broker-url-for-consumer = tcp://${EMS_SERVER_ADDRESS}:61616?${CLIENT_URL_PROPERTIES}
brokercep.broker-url-for-consumer = tcp://127.0.0.1:61616?${CLIENT_URL_PROPERTIES}
brokercep.broker-url-for-clients = ${brokercep.broker-protocol}://${EMS_SERVER_ADDRESS}:${brokercep.broker-port}?${CLIENT_URL_PROPERTIES}
# Must be a public IP address
@ -527,9 +528,9 @@ baguette.client.install.sessionRecordingDir = ${LOGS_DIR:${EMS_CONFIG_DIR}/../lo
#baguette.client.install.parameters.SKIP_JRE_INSTALLATION=true
#baguette.client.install.parameters.SKIP_START=true
baguette.client.install.parameters.BAGUETTE_INSTALLATION_MIN_PROCESSORS=2
baguette.client.install.parameters.BAGUETTE_INSTALLATION_MIN_RAM=2*1024*1024
baguette.client.install.parameters.BAGUETTE_INSTALLATION_MIN_DISK_FREE=1024*1024
#baguette.client.install.parameters.BAGUETTE_INSTALLATION_MIN_PROCESSORS=2
#baguette.client.install.parameters.BAGUETTE_INSTALLATION_MIN_RAM=2*1024*1024
#baguette.client.install.parameters.BAGUETTE_INSTALLATION_MIN_DISK_FREE=1024*1024
### Settings for resolving Node state after baguette client installation
#baguette.client.install.clientInstallVarName=__EMS_CLIENT_INSTALL__

View File

@ -11,8 +11,9 @@
### Global settings
################################################################################
### Don't touch the next line!!
EMS_SERVER_ADDRESS: ${${control.IP_SETTING}}
### Don't touch the next lines!!
EMS_IP_SETTING: ${P_EMS_IP_SETTING:PUBLIC_IP}
EMS_SERVER_ADDRESS: ${${EMS_IP_SETTING}}
DOLLAR: '$'
### Password Encoder settings
@ -114,9 +115,9 @@ control:
#skip-broker-cep: true
#skip-baguette: true
#skip-collectors: true
#skip-metasolver: true
#skip-notification: true
upperware-grouping: GLOBAL
skip-metasolver: true
skip-notification: true
#upperware-grouping: GLOBAL
### Debug settings - Load/Save translation results
tc-load-file: ${EMS_TC_LOAD_FILE:${EMS_TC_FILE:${LOGS_DIR:${EMS_CONFIG_DIR}/../logs}/_TC.json}}
@ -329,7 +330,7 @@ brokercep:
- stomp://0.0.0.0:61610
# Broker URLs for (EMS) consumer and clients
broker-url-for-consumer: tcp://${EMS_SERVER_ADDRESS}:61616?${CLIENT_URL_PROPERTIES}
broker-url-for-consumer: tcp://127.0.0.1:61616?${CLIENT_URL_PROPERTIES}
broker-url-for-clients: ${brokercep.broker-protocol}://${EMS_SERVER_ADDRESS}:${brokercep.broker-port}?${CLIENT_URL_PROPERTIES}
# Must be a public IP address
@ -569,7 +570,7 @@ baguette.client.install:
sessionRecordingDir: ${LOGS_DIR:${EMS_CONFIG_DIR}/../logs}
### Baguette and Netdata installation parameters (for condition checking)
parameters:
#parameters:
#SKIP_IGNORE_CHECK: true
#SKIP_DETECTION: true
@ -578,9 +579,9 @@ baguette.client.install:
#SKIP_JRE_INSTALLATION: true
#SKIP_START: true
BAGUETTE_INSTALLATION_MIN_PROCESSORS: 2
BAGUETTE_INSTALLATION_MIN_RAM: 2*1024*1024
BAGUETTE_INSTALLATION_MIN_DISK_FREE: 1024*1024
#BAGUETTE_INSTALLATION_MIN_PROCESSORS: 2
#BAGUETTE_INSTALLATION_MIN_RAM: 2*1024*1024
#BAGUETTE_INSTALLATION_MIN_DISK_FREE: 1024*1024
### Settings for resolving Node state after baguette client installation
#clientInstallVarName: '__EMS_CLIENT_INSTALL__'

View File

@ -21,8 +21,3 @@ control.ssl.truststore-password=ENC(ISMbn01HVPbtRPkqm2Lslg==)
### Additional Baguette Server SSH username/passwords: aa/xx, bb/yy
#baguette.server.credentials.aa=xx
#baguette.server.credentials.bb=yy
### Other settings
control.IP_SETTING=DEFAULT_IP
control.esb-url=
control.metasolver-configuration-url=

View File

@ -20,11 +20,11 @@
<name>EMS - Control Service</name>
<properties>
<!--<hawtio.version>3.0-M8</hawtio.version>-->
<spring.boot.admin.version>3.1.3</spring.boot.admin.version>
<micrometer.registry.prometheus.version>1.11.2</micrometer.registry.prometheus.version>
<springdoc.version>2.1.0</springdoc.version>
<jjwt.version>0.11.5</jjwt.version>
<hawtio.version>4.0-M4</hawtio.version>
<spring.boot.admin.version>3.2.2</spring.boot.admin.version>
<micrometer.registry.prometheus.version>1.12.4</micrometer.registry.prometheus.version>
<springdoc.version>2.3.0</springdoc.version>
<jjwt.version>0.12.5</jjwt.version>
<!-- Build timestamp -->
<timestamp>${maven.build.timestamp}</timestamp>
<maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss</maven.build.timestamp.format>
@ -33,10 +33,12 @@
<docker.esperJar>esper-${esper.version}.jar</docker.esperJar>
<!-- io.fabricat8 docker-maven-plugin properties -->
<docker-maven-plugin.version>0.43.2</docker-maven-plugin.version>
<docker-maven-plugin.version>0.44.0</docker-maven-plugin.version>
<docker.image.name>ems-server</docker.image.name>
<!--<docker.image.tag>${revision}</docker.image.tag>
<build.description></build.description>-->
<docker.user>emsuser</docker.user>
<docker.user.home>/opt/ems-server</docker.user.home>
<!-- EMS server main class -->
<start-class>gr.iccs.imu.ems.control.ControlServiceApplication</start-class>
@ -176,13 +178,11 @@
</dependency>-->
<!-- Hawtio console dependency (https://hawt.io/docs/get-started/) -->
<!-- XXX: Causes 'BeanPostProcessorChecker' warnings -->
<!--XXX: TODO: Temporarily disabled because Jolokia does not follow Spring Boot 3 changes (see https://adevait.com/java/spring-boot-3-0)
<dependency>
<groupId>io.hawt</groupId>
<artifactId>hawtio-springboot</artifactId>
<version>${hawtio.version}</version>
</dependency>-->
</dependency>
<!-- Springdoc: OpenAPI UI and Swagger -->
<dependency>
@ -208,43 +208,6 @@
</exclusions>
</dependency>
<!-- Maven plugin dependencies -->
<!-- https://mvnrepository.com/artifact/net.nicoulaj.maven.plugins/checksum-maven-plugin -->
<dependency>
<groupId>net.nicoulaj.maven.plugins</groupId>
<artifactId>checksum-maven-plugin</artifactId>
<version>1.11</version>
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
<exclusion>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.15.1</version>
</dependency>
</dependencies>
<build>
@ -291,7 +254,7 @@
<plugin>
<groupId>io.github.git-commit-id</groupId>
<artifactId>git-commit-id-maven-plugin</artifactId>
<version>6.0.0</version>
<version>7.0.0</version>
<!--<executions>
<execution>
<id>get-the-git-infos</id>
@ -419,7 +382,7 @@
</fileSet>
</fileSets>
<algorithms>
<algorithm>MD5</algorithm>
<algorithm>SHA256</algorithm>
</algorithms>
<individualFiles>true</individualFiles>
<failOnError>true</failOnError>
@ -477,13 +440,17 @@
<destinationFile>../public_resources/resources/baguette-client.tgz</destinationFile>
</fileSet>
<fileSet>
<sourceFile>${project.build.directory}/baguette-client-installation-package.tgz.md5</sourceFile>
<destinationFile>../public_resources/resources/baguette-client.tgz.md5</destinationFile>
<sourceFile>${project.build.directory}/baguette-client-installation-package.tgz.sha256</sourceFile>
<destinationFile>../public_resources/resources/baguette-client.tgz.sha256</destinationFile>
</fileSet>
<fileSet>
<sourceFile>../baguette-client/bin/install.sh</sourceFile>
<destinationFile>../public_resources/resources/install.sh</destinationFile>
</fileSet>
<fileSet>
<sourceFile>../baguette-client/bin/jre-install.sh</sourceFile>
<destinationFile>../public_resources/resources/jre-install.sh</destinationFile>
</fileSet>
</fileSets>
<ignoreFileNotFoundOnIncremental>true</ignoreFileNotFoundOnIncremental>
<overWrite>true</overWrite>
@ -700,6 +667,10 @@
<name>${docker.image.name}:${docker.image.tag}</name>
<build>
<contextDir>${project.build.directory}/docker-context</contextDir>
<args>
<EMS_USER>${docker.user}</EMS_USER>
<EMS_HOME>${docker.user.home}</EMS_HOME>
</args>
</build>
</image>
</images>
@ -739,12 +710,43 @@
<name>docker.image.tag</name>
<value>${docker.image.tag}</value>
</property>
<property>
<name>docker.user</name>
<value>${docker.user}</value>
</property>
<property>
<name>docker.user.home</name>
<value>${docker.user.home}</value>
</property>
</properties>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<goals>
<goal>install-file</goal>
</goals>
<phase>install</phase>
<configuration>
<file>${project.build.directory}/docker-image.properties</file>
<artifactId>${project.artifactId}</artifactId>
<groupId>${project.groupId}</groupId>
<version>${project.version}</version>
<classifier>docker-image</classifier>
<packaging>pom</packaging>
<generatePom>false</generatePom>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>

View File

@ -22,16 +22,6 @@ RUN java -Djarmode=layertools -jar control-service.jar extract
# ----------------- Run image -----------------
FROM $RUN_IMAGE:$RUN_IMAGE_TAG
# Setup environment
ENV BASEDIR /opt/ems-server
ENV EMS_HOME ${BASEDIR}
ENV EMS_CONFIG_DIR ${BASEDIR}/config
ENV BIN_DIR ${BASEDIR}/bin
ENV CONFIG_DIR ${BASEDIR}/config
ENV LOGS_DIR ${BASEDIR}/logs
ENV PUBLIC_DIR ${BASEDIR}/public_resources
# Install required and optional packages
RUN wget --progress=dot:giga -O /usr/local/bin/dumb-init \
https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64 && \
@ -42,13 +32,23 @@ RUN wget --progress=dot:giga -O /usr/local/bin/dumb-init \
# Add an EMS user
ARG EMS_USER=emsuser
ARG EMS_HOME=/opt/ems-server
RUN mkdir ${EMS_HOME} && \
addgroup ${EMS_USER} && \
adduser --home ${EMS_HOME} --no-create-home --ingroup ${EMS_USER} --disabled-password ${EMS_USER} && \
chown ${EMS_USER}:${EMS_USER} ${EMS_HOME}
USER ${EMS_USER}
WORKDIR ${BASEDIR}
WORKDIR ${EMS_HOME}
# Setup environment
ENV BASEDIR ${EMS_HOME}
ENV EMS_CONFIG_DIR ${BASEDIR}/config
ENV BIN_DIR ${BASEDIR}/bin
ENV CONFIG_DIR ${BASEDIR}/config
ENV LOGS_DIR ${BASEDIR}/logs
ENV PUBLIC_DIR ${BASEDIR}/public_resources
# Download a JRE suitable for running EMS clients, and
# offer it for download
@ -61,8 +61,10 @@ COPY --chown=${EMS_USER}:${EMS_USER} bin ${BIN_DIR}
COPY --chown=${EMS_USER}:${EMS_USER} config ${CONFIG_DIR}
COPY --chown=${EMS_USER}:${EMS_USER} public_resources ${PUBLIC_DIR}
# Create 'logs', and 'models' directories. Make bin/*.sh scripts executable
RUN mkdir ${LOGS_DIR} && \
chmod +rx ${BIN_DIR}/*.sh
chmod +rx ${BIN_DIR}/*.sh && \
mkdir -p ${EMS_HOME}/models
# Copy files from builder container
COPY --chown=${EMS_USER}:${EMS_USER} --from=ems-server-builder /app/dependencies ${BASEDIR}

View File

@ -7,22 +7,42 @@
# https://www.mozilla.org/en-US/MPL/2.0/
#
ARG BUILDER_IMAGE=eclipse-temurin:21.0.1_12-jre-alpine
ARG RUN_IMAGE=eclipse-temurin:21.0.1_12-jre-alpine
ARG BUILDER_IMAGE=eclipse-temurin
ARG BUILDER_IMAGE_TAG=21.0.1_12-jre
ARG RUN_IMAGE=eclipse-temurin
ARG RUN_IMAGE_TAG=21.0.1_12-jre
# ----------------- Builder image -----------------
FROM $BUILDER_IMAGE as ems-server-builder
FROM $BUILDER_IMAGE:$BUILDER_IMAGE_TAG as ems-server-builder
#FROM vegardit/graalvm-maven:latest-java17
WORKDIR /app
COPY jars/control-service.jar .
RUN java -Djarmode=layertools -jar control-service.jar extract
# ----------------- Run image -----------------
FROM $RUN_IMAGE
FROM $RUN_IMAGE:$RUN_IMAGE_TAG
# Install required and optional packages
RUN wget --progress=dot:giga -O /usr/local/bin/dumb-init \
https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64 && \
chmod +x /usr/local/bin/dumb-init
#RUN apk update && \
# apk add bash netcat-openbsd vim iputils-ping
#
# Add an EMS user
ARG EMS_USER=emsuser
ARG EMS_HOME=/opt/ems-server
RUN mkdir ${EMS_HOME} && \
addgroup ${EMS_USER} && \
adduser --home ${EMS_HOME} --no-create-home --ingroup ${EMS_USER} --disabled-password ${EMS_USER} && \
chown ${EMS_USER}:${EMS_USER} ${EMS_HOME}
USER ${EMS_USER}
WORKDIR ${EMS_HOME}
# Setup environment
ENV BASEDIR /opt/ems-server
ENV EMS_HOME ${BASEDIR}
ENV BASEDIR ${EMS_HOME}
ENV EMS_CONFIG_DIR ${BASEDIR}/config
ENV BIN_DIR ${BASEDIR}/bin
@ -30,36 +50,21 @@ ENV CONFIG_DIR ${BASEDIR}/config
ENV LOGS_DIR ${BASEDIR}/logs
ENV PUBLIC_DIR ${BASEDIR}/public_resources
# Install required and optional packages
RUN apk update && \
apk add curl bash netcat-openbsd vim iputils-ping
RUN wget -O /usr/local/bin/dumb-init \
https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64 && \
chmod +x /usr/local/bin/dumb-init
# Add an EMS user
ARG EMS_USER=emsuser
RUN mkdir ${EMS_HOME} && \
addgroup ${EMS_USER} && \
adduser --home ${EMS_HOME} --no-create-home --ingroup ${EMS_USER} --disabled-password ${EMS_USER} && \
chown ${EMS_USER}:${EMS_USER} ${EMS_HOME}
USER ${EMS_USER}
WORKDIR ${BASEDIR}
# Download a JRE suitable for running EMS clients, and
# offer it for download
ENV JRE_LINUX_PACKAGE zulu21.30.15-ca-jre21.0.1-linux_x64.tar.gz
RUN mkdir -p ${PUBLIC_DIR}/resources && \
curl https://cdn.azul.com/zulu/bin/${JRE_LINUX_PACKAGE} --output ${PUBLIC_DIR}/resources/${JRE_LINUX_PACKAGE}
wget --progress=dot:giga -O ${PUBLIC_DIR}/resources/${JRE_LINUX_PACKAGE} https://cdn.azul.com/zulu/bin/${JRE_LINUX_PACKAGE}
# Copy resource files to image
ADD --chown=${EMS_USER}:${EMS_USER} bin ${BIN_DIR}
ADD --chown=${EMS_USER}:${EMS_USER} config ${CONFIG_DIR}
ADD --chown=${EMS_USER}:${EMS_USER} public_resources ${PUBLIC_DIR}
COPY --chown=${EMS_USER}:${EMS_USER} bin ${BIN_DIR}
COPY --chown=${EMS_USER}:${EMS_USER} config ${CONFIG_DIR}
COPY --chown=${EMS_USER}:${EMS_USER} public_resources ${PUBLIC_DIR}
RUN mkdir ${LOGS_DIR}
RUN chmod +rx ${BIN_DIR}/*.sh
# Create 'logs', and 'models' directories. Make bin/*.sh scripts executable
RUN mkdir ${LOGS_DIR} && \
chmod +rx ${BIN_DIR}/*.sh && \
mkdir -p ${EMS_HOME}/models
# Copy files from builder container
COPY --chown=${EMS_USER}:${EMS_USER} --from=ems-server-builder /app/dependencies ${BASEDIR}

View File

@ -12,10 +12,7 @@ package gr.iccs.imu.ems.control;
import com.ulisesbocchio.jasyptspringboot.environment.StandardEncryptableEnvironment;
import gr.iccs.imu.ems.control.controller.ControlServiceCoordinator;
import gr.iccs.imu.ems.control.properties.ControlServiceProperties;
import gr.iccs.imu.ems.util.EventBus;
import gr.iccs.imu.ems.util.KeystoreUtil;
import gr.iccs.imu.ems.util.PasswordUtil;
import gr.iccs.imu.ems.util.StrUtil;
import gr.iccs.imu.ems.util.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.Connector;
@ -62,6 +59,8 @@ public class ControlServiceApplication {
private final PasswordUtil passwordUtil;
public static void main(String[] args) {
System.out.println(EmsRelease.EMS_DESCRIPTION);
long initStartTime = System.currentTimeMillis();
// Start EMS server

View File

@ -69,8 +69,17 @@ public class ControlServiceController {
throw new RestControllerException(400, "Request does not contain an application id");
}
// Get applicationId (if provided)
String applicationId = Optional.ofNullable(jObj.get("applicationId")).map(je -> stripQuotes(je.toString())).orElse(null);
if (StringUtils.isBlank(appExecModelId)) {
applicationId = appModelId;
log.warn("ControlServiceController.newAppModel(): No 'applicationId' found. Using App model id instead: {}", applicationId);
} else {
log.info("ControlServiceController.newAppModel(): Found 'applicationId': {}", applicationId);
}
// Start translation and component reconfiguration in a worker thread
coordinator.processAppModel(appModelId, appExecModelId, ControlServiceRequestInfo.create(null, null, jwtToken));
coordinator.processAppModel(appModelId, appExecModelId, ControlServiceRequestInfo.create(applicationId, null, null, jwtToken, null));
log.debug("ControlServiceController.newAppModel(): Model translation dispatched to a worker thread");
return "OK";

View File

@ -13,10 +13,12 @@ import com.google.gson.GsonBuilder;
import gr.iccs.imu.ems.baguette.server.BaguetteServer;
import gr.iccs.imu.ems.baguette.server.NodeRegistry;
import gr.iccs.imu.ems.baguette.server.ServerCoordinator;
import gr.iccs.imu.ems.baguette.server.coordinator.NoopCoordinator;
import gr.iccs.imu.ems.brokercep.BrokerCepService;
import gr.iccs.imu.ems.brokercep.BrokerCepStatementSubscriber;
import gr.iccs.imu.ems.brokercep.event.EventMap;
import gr.iccs.imu.ems.control.collector.netdata.ServerNetdataCollector;
import gr.iccs.imu.ems.control.plugin.AppModelPlugin;
import gr.iccs.imu.ems.control.plugin.MetasolverPlugin;
import gr.iccs.imu.ems.control.plugin.PostTranslationPlugin;
import gr.iccs.imu.ems.control.plugin.TranslationContextPlugin;
@ -73,6 +75,8 @@ public class ControlServiceCoordinator implements InitializingBean {
private final PasswordUtil passwordUtil;
private final EventBus<String,Object,Object> eventBus;
private final List<AppModelPlugin> appModelPluginList;
private final List<Translator> translatorImplementations;
private Translator translator; // Will be populated in 'afterPropertiesSet()'
private final List<PostTranslationPlugin> postTranslationPlugins;
@ -106,8 +110,11 @@ public class ControlServiceCoordinator implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
setCurrentEmsState(EMS_STATE.INITIALIZING, "Starting ControlServiceCoordinator...");
initTranslator();
initMvvService();
startBaguetteServer();
// Run configuration checks and throw exceptions early (before actually using EMS)
if (properties.isSkipTranslation()) {
@ -119,6 +126,8 @@ public class ControlServiceCoordinator implements InitializingBean {
log.debug("ControlServiceCoordinator.afterPropertiesSet(): Post-translation plugins: {}", postTranslationPlugins);
log.debug("ControlServiceCoordinator.afterPropertiesSet(): TranslationContext plugins: {}", translationContextPlugins);
log.debug("ControlServiceCoordinator.afterPropertiesSet(): MetaSolver plugins: {}", metasolverPlugins);
setCurrentEmsState(EMS_STATE.IDLE, "ControlServiceCoordinator started");
}
private void initMvvService() {
@ -142,7 +151,7 @@ public class ControlServiceCoordinator implements InitializingBean {
private void initTranslator() {
log.debug("ControlServiceCoordinator.initTranslator(): Translator implementations: {}", translatorImplementations);
if (translatorImplementations.size() == 1) {
translator = translatorImplementations.get(0);
translator = translatorImplementations.getFirst();
} else if (translatorImplementations.isEmpty()) {
throw new IllegalArgumentException("No Translator implementations found");
} else {
@ -156,6 +165,15 @@ public class ControlServiceCoordinator implements InitializingBean {
log.info("ControlServiceCoordinator.initTranslator(): Effective translator: {}", translator.getClass().getName());
}
private void startBaguetteServer() {
log.debug("ControlServiceCoordinator.startBaguetteServer(): Starting Baguette Server...");
try {
baguetteServer.startServer(new NoopCoordinator());
} catch (Exception ex) {
log.error("ControlServiceCoordinator.startBaguetteServer(): EXCEPTION while starting Baguette server: ", ex);
}
}
// ------------------------------------------------------------------------------------------------------------
public String getAppModelId() {
@ -247,10 +265,17 @@ public class ControlServiceCoordinator implements InitializingBean {
return;
}
// Execute callback after acquiring lock
try {
callback.run();
} catch (Exception ex) {
setCurrentEmsState(EMS_STATE.ERROR, ex.getMessage());
StringBuilder sb = new StringBuilder(ex.getClass().getName()).append(": ").append(ex.getMessage());
Throwable t = ex;
while (t.getCause()!=null) {
t = t.getCause();
sb.append(", caused by: ").append(t.getClass().getName()).append(": ").append(t.getMessage());
}
setCurrentEmsState(EMS_STATE.ERROR, sb.toString());
String mesg = "ControlServiceCoordinator."+caller+": EXCEPTION: " + ex;
log.error(mesg, ex);
@ -263,6 +288,15 @@ public class ControlServiceCoordinator implements InitializingBean {
// Release lock of this coordinator
inUse.compareAndSet(true, false);
}
// Invoke requestInfo callback if provided
if (requestInfo.getCallback()!=null) {
requestInfo.getCallback().accept(Map.of(
"ems-state", StringUtils.defaultIfBlank(getCurrentEmsState().name(), "UNKNOWN"),
"ems-state-message", StringUtils.defaultIfBlank(getCurrentEmsStateMessage(), ""),
"ems-state-change-timestamp", getCurrentEmsStateChangeTimestamp()
));
}
}
// ------------------------------------------------------------------------------------------------------------
@ -270,17 +304,28 @@ public class ControlServiceCoordinator implements InitializingBean {
protected void _processAppModels(String appModelId, String appExecModelId, ControlServiceRequestInfo requestInfo) {
log.info("ControlServiceCoordinator._processAppModel(): BEGIN: app-model-id={}, app-exec-model-id={}, request-info={}", appModelId, appExecModelId, requestInfo);
// Run pre-processing plugins
log.debug("ControlServiceCoordinator._processAppModel(): appModelPluginList: {}", appModelPluginList);
if (appModelPluginList!=null) {
for (AppModelPlugin plugin : appModelPluginList) {
if (plugin!=null) {
log.debug("ControlServiceCoordinator._processAppModel(): Calling preProcessingNewAppModel on plugin: {}", plugin);
plugin.preProcessingNewAppModel(appModelId, requestInfo);
}
}
}
// Translate model into Translation Context (with EPL rules etc.)
TranslationContext _TC;
if (!properties.isSkipTranslation()) {
_TC = translateAppModelAndStore(appModelId);
_TC = translateAppModelAndStore(appModelId, requestInfo.getApplicationId());
} else {
log.warn("ControlServiceCoordinator._processAppModel(): Skipping translation due to configuration");
_TC = loadStoredTranslationContext(appModelId);
}
// Run TranslationContext plugins
if (translationContextPlugins!=null && translationContextPlugins.size()>0) {
if (translationContextPlugins!=null && !translationContextPlugins.isEmpty()) {
log.info("ControlServiceCoordinator._processAppModel(): Running {} TranslationContext plugins", translationContextPlugins.size());
translationContextPlugins.stream().filter(Objects::nonNull).forEach(plugin -> {
log.debug("ControlServiceCoordinator._processAppModel(): Calling TranslationContext plugin: {}", plugin.getClass().getName());
@ -356,6 +401,17 @@ public class ControlServiceCoordinator implements InitializingBean {
log.warn("ControlServiceCoordinator._processAppModel(): Skipping notification due to configuration");
}
// Run post-processing plugins
log.debug("ControlServiceCoordinator._processAppModel(): appModelPluginList: {}", appModelPluginList);
if (appModelPluginList!=null) {
for (AppModelPlugin plugin : appModelPluginList) {
if (plugin!=null) {
log.debug("ControlServiceCoordinator._processAppModel(): Calling postProcessingNewAppModel on plugin: {}", plugin);
plugin.postProcessingNewAppModel(appModelId, requestInfo, _TC);
}
}
}
this.currentTC = _TC;
log.info("ControlServiceCoordinator._processAppModel(): END: app-model-id={}", appModelId);
@ -418,13 +474,13 @@ public class ControlServiceCoordinator implements InitializingBean {
setCurrentEmsState(EMS_STATE.READY, null);
}
private TranslationContext translateAppModelAndStore(String appModelId) {
private TranslationContext translateAppModelAndStore(String appModelId, String applicationId) {
final TranslationContext _TC;
setCurrentEmsState(EMS_STATE.INITIALIZING, "Retrieving and translating model");
// Translate application model into a TranslationContext object
log.info("ControlServiceCoordinator.translateAppModelAndStore(): Model translation: model-id={}", appModelId);
_TC = translator.translate(appModelId);
_TC = translator.translate(appModelId, applicationId);
_TC.populateTopLevelMetricNames();
log.debug("ControlServiceCoordinator.translateAppModelAndStore(): Model translation: RESULTS: {}", _TC);
@ -548,7 +604,7 @@ public class ControlServiceCoordinator implements InitializingBean {
log.debug("ControlServiceCoordinator.configureBrokerCep(): Broker-CEP: Upperware grouping: {}", upperwareGrouping);
Set<String> eventTypeNames = _TC.getG2T().get(upperwareGrouping);
log.debug("ControlServiceCoordinator.configureBrokerCep(): Broker-CEP: Configuration of Event Types: {}", eventTypeNames);
if (eventTypeNames == null || eventTypeNames.size() == 0)
if (eventTypeNames == null || eventTypeNames.isEmpty())
throw new RuntimeException("Broker-CEP: No event types for GLOBAL grouping");
// Clear any previous event types, statements or function definitions and register the new ones

View File

@ -25,11 +25,28 @@ public class ControlServiceRequestInfo {
@ToString.Exclude
private final String jwtToken;
private final String applicationId;
private final java.util.function.Consumer<Object> callback;
public static ControlServiceRequestInfo create(String notificationUri, String requestUuid, String jwtToken) {
return ControlServiceRequestInfo.builder()
.notificationUri(notificationUri)
.requestUuid(requestUuid)
.jwtToken(jwtToken)
.applicationId(null)
.callback(null)
.build();
}
public static ControlServiceRequestInfo create(String applicationId, String notificationUri, String requestUuid,
String jwtToken, java.util.function.Consumer<Object> callback)
{
return ControlServiceRequestInfo.builder()
.notificationUri(notificationUri)
.requestUuid(requestUuid)
.jwtToken(jwtToken)
.applicationId(applicationId)
.callback(callback)
.build();
}
}

View File

@ -65,25 +65,45 @@ public class CredentialsController {
response.put("password", brokerPassword);
response.put("certificate", brokerCertificatePem);
HttpEntity<Map> entity = coordinator.createHttpEntity(Map.class, response, jwtToken);
log.info("CredentialsController.getBrokerCredentials(): Response: {}", response);
log.debug("CredentialsController.getBrokerCredentials(): Response: {}",
encodeMapFields(response, "password", "certificate"));
//return response;
return entity;
}
// @PreAuthorize(ROLES_ALLOWED_JWT_TOKEN_OR_API_KEY)
@GetMapping(value = "/baguette/connectionInfo")
public HttpEntity<Map> getBaguetteConnectionInfo(
@RequestHeader(name = HttpHeaders.AUTHORIZATION, required = false) String jwtToken)
{
log.info("CredentialsController.getBaguetteConnectionInfo(): BEGIN");
log.trace("CredentialsController.getBaguetteConnectionInfo(): JWT token: {}", jwtToken);
// Retrieve sensor information
Map<String,String> response = coordinator.getBaguetteServer().getServerConnectionInfo();
// Prepare response
HttpEntity<Map> entity = coordinator.createHttpEntity(Map.class, response, jwtToken);
log.debug("CredentialsController.getBaguetteConnectionInfo(): Response: {}",
encodeMapFields(response, "BAGUETTE_SERVER_PASSWORD", "BAGUETTE_SERVER_PUBKEY"));
return entity;
}
// @PreAuthorize(ROLES_ALLOWED_JWT_TOKEN_OR_API_KEY)
@GetMapping(value = "/baguette/ref/{ref}", produces = MediaType.APPLICATION_JSON_VALUE)
public HttpEntity<Map> getNodeCredentials(@PathVariable String optRef,
public HttpEntity<Map> getNodeCredentials(@PathVariable String ref,
@RequestHeader(name = HttpHeaders.AUTHORIZATION, required = false) String jwtToken)
{
log.info("CredentialsController.getNodeCredentials(): BEGIN: ref={}", optRef);
log.info("CredentialsController.getNodeCredentials(): BEGIN: ref={}", ref);
log.trace("CredentialsController.getNodeCredentials(): JWT token: {}", jwtToken);
if (StringUtils.isBlank(optRef))
if (StringUtils.isBlank(ref))
throw new IllegalArgumentException("The 'ref' parameter is mandatory");
// Check if it is EMS server ref
if (credentialsCoordinator.getReference().equals(optRef)) {
if (credentialsCoordinator.getReference().equals(ref)) {
if (coordinator.getBaguetteServer()==null || !coordinator.getBaguetteServer().isServerRunning()) {
log.warn("CredentialsController.getNodeCredentials(): Baguette Server is not started");
return null;
@ -101,7 +121,7 @@ public class CredentialsController {
}
String key = coordinator.getBaguetteServer().getServerPubkey();
log.debug("CredentialsController.getNodeCredentials(): Retrieved EMS server connection info by reference: ref={}", optRef);
log.debug("CredentialsController.getNodeCredentials(): Retrieved EMS server connection info by reference: ref={}", ref);
// Prepare response
Map<String,String> response = new HashMap<>();
@ -117,11 +137,11 @@ public class CredentialsController {
}
// Retrieve node credentials
NodeRegistryEntry entry = coordinator.getBaguetteServer().getNodeRegistry().getNodeByReference(optRef);
NodeRegistryEntry entry = coordinator.getBaguetteServer().getNodeRegistry().getNodeByReference(ref);
if (entry==null) {
throw new IllegalArgumentException("Not found Node with reference: "+optRef);
throw new IllegalArgumentException("Not found Node with reference: "+ref);
}
log.debug("CredentialsController.getNodeCredentials(): Retrieved node by reference: ref={}", optRef);
log.debug("CredentialsController.getNodeCredentials(): Retrieved node by reference: ref={}", ref);
// Prepare response
Map<String,String> response = new HashMap<>();
@ -131,11 +151,20 @@ public class CredentialsController {
response.put("password", entry.getPreregistration().get("ssh.password"));
response.put("private-key", entry.getPreregistration().get("ssh.key"));
HttpEntity<Map> entity = coordinator.createHttpEntity(Map.class, response, jwtToken);
log.debug("CredentialsController.getNodeCredentials(): Response: ** Not shown because it contains credentials **");
//log.debug("CredentialsController.getNodeCredentials(): Response: ** Not shown because it contains credentials **");
log.debug("CredentialsController.getNodeCredentials(): Response: {}", encodeMapFields(response, "password", "private-key"));
return entity;
}
private HashMap<String, String> encodeMapFields(Map<String, String> response, String...keys) {
HashMap<String, String> map = new HashMap<>(response);
for (String k : keys) {
map.put(k, passwordUtil.encodePassword(map.getOrDefault(k, "")));
}
return map;
}
// ------------------------------------------------------------------------------------------------------------
// EMS One-Time-Password (OTP) endpoints

View File

@ -0,0 +1,22 @@
/*
* Copyright (C) 2017-2023 Institute of Communication and Computer Systems (imu.iccs.gr)
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v2.0, unless
* Esper library is used, in which case it is subject to the terms of General Public License v2.0.
* If a copy of the MPL was not distributed with this file, you can obtain one at
* https://www.mozilla.org/en-US/MPL/2.0/
*/
package gr.iccs.imu.ems.control.plugin;
import gr.iccs.imu.ems.control.controller.ControlServiceRequestInfo;
import gr.iccs.imu.ems.translate.TranslationContext;
import gr.iccs.imu.ems.util.Plugin;
/**
* Executed before/after Application Model processing by ControlServiceCoordinator
*/
public interface AppModelPlugin extends Plugin {
default void preProcessingNewAppModel(String appModelId, ControlServiceRequestInfo requestInfo) { }
default void postProcessingNewAppModel(String appModelId, ControlServiceRequestInfo requestInfo, TranslationContext translationContext) { }
}

View File

@ -16,5 +16,6 @@ import gr.iccs.imu.ems.util.Plugin;
* TopicBeacon plugin
*/
public interface BeaconPlugin extends Plugin {
default void init(TopicBeacon.BeaconContext context) { }
void transmit(TopicBeacon.BeaconContext context);
}

View File

@ -9,6 +9,7 @@
package gr.iccs.imu.ems.control.properties;
import gr.iccs.imu.ems.util.GROUPING;
import gr.iccs.imu.ems.util.KeystoreAndCertificateProperties;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@ -48,7 +49,7 @@ public class ControlServiceProperties {
private IpSetting ipSetting = IpSetting.PUBLIC_IP;
private ExecutionWare executionware = ExecutionWare.PROACTIVE;
private String upperwareGrouping;
private String upperwareGrouping = GROUPING.GLOBAL.name();
private String metasolverConfigurationUrl;
private String notificationUrl;

View File

@ -66,6 +66,16 @@ public class TopicBeacon implements InitializingBean {
// initialize a Gson instance
initializeGson();
// initialize plugins
beaconPlugins.stream().filter(Objects::nonNull).forEach(plugin -> {
try {
log.debug("Topic Beacon: initializing Beacon plugin: {}", plugin.getClass().getName());
plugin.init(beaconContext);
} catch (Throwable t) {
log.error("Topic Beacon: EXCEPTION while initializing Beacon plugin: {}\n", plugin.getClass().getName(), t);
}
});
// configure and start scheduler
Date startTime = new Date(System.currentTimeMillis() + properties.getInitialDelay());
log.debug("Topic Beacon settings: init-delay={}, delay={}, heartbeat-topics={}, threshold-topics={}, instance-topics={}",
@ -141,6 +151,8 @@ public class TopicBeacon implements InitializingBean {
}
public String toJson(Object o) {
if (gson==null)
initializeGson();
return gson.toJson(o);
}
@ -183,7 +195,7 @@ public class TopicBeacon implements InitializingBean {
coordinator.getTranslationContextOfAppModel(coordinator.getCurrentAppModelId())
.getMetricConstraints()
.forEach(c -> {
String message = gson.toJson(c);
String message = toJson(c);
log.debug("Topic Beacon: Transmitting Metric Constraint threshold info: message={}, topics={}",
message, properties.getThresholdTopics());
try {
@ -206,7 +218,7 @@ public class TopicBeacon implements InitializingBean {
String nodeName = node.getPreregistration().getOrDefault("name", "");
String nodeIp = node.getIpAddress();
//String nodeIp = node.getPreregistration().getOrDefault("ip","");
// String message = gson.toJson(node);
// String message = toJson(node);
log.debug("Topic Beacon: Transmitting Instance info for: instance={}, ip-address={}, message={}, topics={}",
nodeName, nodeIp, node, properties.getInstanceTopics());
sendEventToTopics(node, properties.getInstanceTopics());
@ -271,7 +283,7 @@ public class TopicBeacon implements InitializingBean {
private void sendEventToTopics(Object message, Set<String> topics) throws JMSException {
EventMap event = new EventMap(-1);
event.put("message", message);
String s = gson.toJson(event);
String s = toJson(event);
log.trace("Topic Beacon: Converted event to JSON string: {}", s);
sendMessageToTopics(s, topics);
}
@ -280,7 +292,7 @@ public class TopicBeacon implements InitializingBean {
for (String topicName : topics) {
log.trace("Topic Beacon: Sending event to topic: event={}, topic={}", event, topicName);
brokerCepService.publishSerializable(
brokerCepService.getBrokerCepProperties().getBrokerUrlForClients(),
brokerCepService.getBrokerCepProperties().getBrokerUrlForConsumer(),
brokerCepService.getBrokerUsername(),
brokerCepService.getBrokerPassword(),
topicName,

View File

@ -11,7 +11,6 @@ package gr.iccs.imu.ems.control.util.jwt;
import gr.iccs.imu.ems.util.PasswordUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
@ -21,8 +20,12 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import javax.crypto.SecretKey;
import java.security.Key;
import java.util.*;
import java.util.Base64;
import java.util.Date;
import java.util.InvalidPropertiesFormatException;
import java.util.UUID;
@Slf4j
@Service
@ -50,10 +53,10 @@ public class JwtTokenService {
}
@SneakyThrows
protected Key getKeyFromProperties() {
protected SecretKey getKeyFromProperties() {
if (StringUtils.isBlank(jwtTokenProperties.getSecret()))
throw new InvalidPropertiesFormatException("JWT token secret key is blank. Check 'jwt.secret' property.");
Key key = Keys.hmacShaKeyFor(Base64.getDecoder().decode(jwtTokenProperties.getSecret()));
SecretKey key = Keys.hmacShaKeyFor(Base64.getDecoder().decode(jwtTokenProperties.getSecret()));
log.debug("JwtTokenService.getKeyFromProperties(): algorithm={}, format={}, key-size={}, base64-encoded-key={}",
key.getAlgorithm(), key.getFormat(), key.getEncoded().length, passwordUtil.encodePassword(keyToString(key)));
return key;
@ -68,11 +71,11 @@ public class JwtTokenService {
// ------------------------------------------------------------------------
public Claims parseToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(getKeyFromProperties())
return Jwts.parser()
.verifyWith(getKeyFromProperties())
.build()
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.getBody();
.parseSignedClaims(token.replace(TOKEN_PREFIX, ""))
.getPayload();
}
public String createToken(String userName) {
@ -81,22 +84,20 @@ public class JwtTokenService {
public String createToken(String userName, Key key) {
return Jwts.builder()
.setSubject(userName)
.setAudience(AUDIENCE_UPPERWARE)
.setExpiration(new Date(System.currentTimeMillis() + jwtTokenProperties.getExpirationTime()))
.subject(userName)
.audience().add(AUDIENCE_UPPERWARE).and()
.expiration(new Date(System.currentTimeMillis() + jwtTokenProperties.getExpirationTime()))
.signWith(key)
.compact();
}
public String createRefreshToken(String userName) {
Map<String, Object> header = new HashMap<>();
header.put(Header.CONTENT_TYPE, REFRESH_HEADER_STRING);
return Jwts.builder()
.setSubject(userName)
.setHeader(header)
.setAudience(AUDIENCE_JWT)
.setId(UUID.randomUUID().toString())
.setExpiration(new Date(System.currentTimeMillis() + jwtTokenProperties.getRefreshTokenExpirationTime()))
.subject(userName)
.header().contentType(REFRESH_HEADER_STRING).and()
.audience().add(AUDIENCE_JWT).and()
.id(UUID.randomUUID().toString())
.expiration(new Date(System.currentTimeMillis() + jwtTokenProperties.getRefreshTokenExpirationTime()))
.signWith(getKeyFromProperties())
.compact();
}

View File

@ -46,10 +46,7 @@ import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.security.InvalidParameterException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
@Slf4j
@Order(1)
@ -400,11 +397,11 @@ public class WebSecurityConfig implements InitializingBean {
log.debug("jwtAuthorizationFilter: Parsing Authorization header...");
Claims claims = jwtTokenService.parseToken(jwtValue);
String user = claims.getSubject();
String audience = claims.getAudience();
Set<String> audience = claims.getAudience();
log.debug("jwtAuthorizationFilter: Authorization header --> user: {}", user);
log.debug("jwtAuthorizationFilter: Authorization header --> audience: {}", audience);
if (user!=null && audience!=null) {
if (JwtTokenService.AUDIENCE_UPPERWARE.equals(audience)) {
if (audience.contains(JwtTokenService.AUDIENCE_UPPERWARE)) {
log.debug("jwtAuthorizationFilter: JWT token is valid");
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(user, null,

View File

@ -12,6 +12,6 @@ ${AnsiColor.046} :: Spring Boot :: ${AnsiColor.87} ${spring
${AnsiColor.046} :: Java (TM) :: ${AnsiColor.87} (${java.version})
${AnsiColor.046} :: Build Num. :: ${AnsiColor.226}@buildNumber@
${AnsiColor.046} :: Build Date :: ${AnsiColor.226}@timestamp@
${AnsiColor.046} :: SCM Branch :: ${AnsiColor.226}@git.branch@
${AnsiColor.046} :: SCM Branch :: ${AnsiColor.226}@git.branch@, at Repos.: @git.remote.origin.url@
${AnsiColor.046} :: Image Tag :: ${AnsiColor.226}@docker.image.name@:@docker.image.tag@
${AnsiColor.046} :: Description :: ${AnsiColor.226}@build.description@ ${AnsiColor.DEFAULT}${AnsiStyle.NORMAL}

View File

@ -432,7 +432,7 @@
<!--<li><a href="/resources/gr.iccs.imu.ems.brokerclient.properties">Broker Client properties</a></li>-->
<br/>
<li><a href="/resources/baguette-client.tgz">Baguette Client TGZ</a></li>
<li><a href="/resources/baguette-client.tgz.md5">Baguette Client MD5</a></li>
<li><a href="/resources/baguette-client.tgz.sha256">Baguette Client SHA256</a></li>
</ul>
</td>
</tr>

View File

@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.1</version>
<version>3.2.3</version>
<relativePath></relativePath>
</parent>
@ -36,10 +36,10 @@
<maven.compiler.release>21</maven.compiler.release>
<!-- Versions for common Maven plugins -->
<source-plugin.version>2.4</source-plugin.version>
<maven-compiler.version>3.11.0</maven-compiler.version>
<javadoc-plugin.version>2.9.1</javadoc-plugin.version>
<maven-assembly-plugin.version>2.5.3</maven-assembly-plugin.version>
<source-plugin.version>3.3.0</source-plugin.version>
<maven-compiler.version>3.12.1</maven-compiler.version>
<javadoc-plugin.version>3.6.3</javadoc-plugin.version>
<maven-assembly-plugin.version>3.7.0</maven-assembly-plugin.version>
<!-- Gson version -->
<gson.version>2.10.1</gson.version>
@ -58,18 +58,18 @@
<!-- Jasypt version -->
<jasypt.starter.version>3.0.5</jasypt.starter.version>
<!-- Apache SSHD version -->
<apache-sshd.version>2.11.0</apache-sshd.version>
<apache-sshd.version>2.12.1</apache-sshd.version>
<!-- Bouncy Castle version -->
<bouncy-castle.version>1.77</bouncy-castle.version>
<!-- Guava version -->
<guava.version>32.1.3-jre</guava.version>
<guava.version>33.0.0-jre</guava.version>
<!-- Apache Commons-CSV -->
<commons-csv.version>1.10.0</commons-csv.version>
<!-- Cryptacular -->
<cryptacular.version>1.2.6</cryptacular.version>
<!-- Jackson and Snakeyaml - Used in baguette-client-install -->
<jackson.version>2.16.0</jackson.version>
<jackson.version>2.16.2</jackson.version>
<snakeyaml.version>2.2</snakeyaml.version>
</properties>

View File

@ -11,4 +11,7 @@ package gr.iccs.imu.ems.translate;
public interface Translator {
TranslationContext translate(String modelPath);
default TranslationContext translate(String modelPath, String applicationId) {
return translate(modelPath);
}
}

View File

@ -25,5 +25,5 @@ public class Interval extends AbstractInterfaceRootObject {
public enum UnitType { DAYS, HOURS, MINUTES, SECONDS, MILLISECONDS, MICROSECONDS, NANOSECONDS }
private UnitType unit;
private int period;
private long period;
}

View File

@ -0,0 +1,105 @@
/*
* Copyright (C) 2017-2023 Institute of Communication and Computer Systems (imu.iccs.gr)
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v2.0, unless
* Esper library is used, in which case it is subject to the terms of General Public License v2.0.
* If a copy of the MPL was not distributed with this file, you can obtain one at
* https://www.mozilla.org/en-US/MPL/2.0/
*/
package gr.iccs.imu.ems.util;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
@Slf4j
@Service
@RequiredArgsConstructor
public class ConfigWriteService {
private final Map<String,Configuration> configurations = new HashMap<>();
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
public Configuration createConfigFile(@NonNull String fileName, String format) {
if (configurations.containsKey(fileName))
throw new IllegalArgumentException("Config. file already exists: "+fileName);
return getOrCreateConfigFile(fileName, format);
}
public Configuration getConfigFile(@NonNull String fileName) {
return configurations.get(fileName);
}
public Configuration getOrCreateConfigFile(@NonNull String fileName, String format) {
if (StringUtils.isBlank(format) && StringUtils.endsWithIgnoreCase(fileName, ".json")) format = "json";
final Format fmt = EnumUtils.getEnumIgnoreCase(Format.class, format, Format.PROPERTIES);
return configurations.computeIfAbsent(fileName, s -> new Configuration(Paths.get(fileName), fmt));
}
public boolean removeConfigFile(@NonNull String fileName, boolean alsoRemoveFile) {
Configuration c = configurations.remove(fileName);
if (c!=null) {
if (! c.getConfigPath().toFile().delete()) {
log.warn("removeConfigFile: Failed to remove config. file from the disk: {}", c.getConfigPath());
}
return true;
}
return false;
}
enum Format { PROPERTIES, JSON }
@Data
@RequiredArgsConstructor
public class Configuration {
@NonNull private final Path configPath;
private final Format format;
private final Map<String,String> contentMap = new LinkedHashMap<>();
public void put(@NonNull String key, String value) throws IOException {
contentMap.put(key, value);
write();
}
public void putAll(@NonNull Map<String,String> map) throws IOException {
contentMap.putAll(map);
write();
}
public void write() throws IOException {
String content;
if (format==Format.JSON) content = asJson();
else content = asProperties();
Files.writeString(configPath, content);
}
private String asProperties() throws IOException {
try (StringWriter writer = new StringWriter()) {
Properties p = new Properties();
p.putAll(contentMap);
p.store(writer, null);
return writer.toString();
}
}
private String asJson() {
return gson.toJson(contentMap);
}
}
}

View File

@ -13,12 +13,18 @@ package gr.iccs.imu.ems.util;
* EMS constant
*/
public class EmsConstant {
public final static String EMS_PROPERTIES_PREFIX = ""; //""ems.";
public final static String EMS_PROPERTIES_PREFIX = ""; //"ems.";
public final static String EVENT_PROPERTY_SOURCE_ADDRESS = "producer-host";
public final static String EVENT_PROPERTY_ORIGINAL_DESTINATION = "original-destination";
public final static String EVENT_PROPERTY_EFFECTIVE_DESTINATION = "effective-destination";
public final static String EVENT_PROPERTY_KEY = "destination-key";
public final static String NETDATA_METRIC_KEY = "_netdata_metric";
public final static String COLLECTOR_DESTINATION_ALIASES = "destination-aliases";
public final static String COLLECTOR_DESTINATION_ALIASES_DELIMITERS = "[,;: \t\r\n]+";
public final static String COLLECTOR_ALLOWED_TOPICS_VAR = "COLLECTOR_ALLOWED_TOPICS";
public static final String COLLECTOR_CONFIGURATIONS_VAR = "COLLECTOR_CONFIGURATIONS";
public final static String EMS_CLIENT_K8S_CONFIG_MAP_FILE = "ems-client-configmap.json";
public final static String EMS_CLIENT_K8S_CONFIG_MAP_FORMAT = "json";
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (C) 2017-2023 Institute of Communication and Computer Systems (imu.iccs.gr)
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v2.0, unless
* Esper library is used, in which case it is subject to the terms of General Public License v2.0.
* If a copy of the MPL was not distributed with this file, you can obtain one at
* https://www.mozilla.org/en-US/MPL/2.0/
*/
package gr.iccs.imu.ems.util;
/**
* EMS Release info
*/
public class EmsRelease {
public final static String EMS_ID = "ems";
public final static String EMS_NAME = "Event Management System";
public final static String EMS_VERSION = EmsRelease.class.getPackage().getImplementationVersion();
public final static String EMS_COPYRIGHT =
"Copyright (C) 2017-2025 Institute of Communication and Computer Systems (imu.iccs.gr)";
public final static String EMS_LICENSE = "Mozilla Public License, v2.0";
public final static String EMS_DESCRIPTION = String.format("\n%s (%s), v.%s, %s\n%s\n",
EMS_NAME, EMS_ID, EMS_VERSION, EMS_LICENSE, EMS_COPYRIGHT);
}

View File

@ -30,6 +30,7 @@ import java.math.BigInteger;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.Certificate;
@ -473,11 +474,26 @@ public class KeystoreUtil {
log.debug(" Entry SAN: {}", properties.getKeyEntryExtSAN());
log.debug(" Entry Gen.: {}", properties.getKeyEntryGenerate());
IKeystoreAndCertificateProperties.KEY_ENTRY_GENERATE keyGen = properties.getKeyEntryGenerate();
boolean gen = (keyGen==IKeystoreAndCertificateProperties.KEY_ENTRY_GENERATE.YES || keyGen==IKeystoreAndCertificateProperties.KEY_ENTRY_GENERATE.ALWAYS);
IKeystoreAndCertificateProperties.KEY_ENTRY_GENERATE keyGenerationStrategy = properties.getKeyEntryGenerate();
boolean generateKey = (keyGenerationStrategy==IKeystoreAndCertificateProperties.KEY_ENTRY_GENERATE.YES
|| keyGenerationStrategy==IKeystoreAndCertificateProperties.KEY_ENTRY_GENERATE.ALWAYS);
// If ALWAYS then remove previous keystore and truststore files
if (generateKey) {
Path oldKeystoreFile = Path.of(properties.getKeystoreFile());
if (Files.exists(oldKeystoreFile))
Files.deleteIfExists(oldKeystoreFile);
Path oldTruststoreFile = Path.of(properties.getTruststoreFile());
if (Files.exists(oldTruststoreFile))
Files.deleteIfExists(oldTruststoreFile);
Path oldCertificateFile = Path.of(properties.getCertificateFile());
if (Files.exists(oldCertificateFile))
Files.deleteIfExists(oldCertificateFile);
log.debug(" Removed old Keystore, Truststore and Certificate files");
}
// Check if key entry is missing
if (keyGen==IKeystoreAndCertificateProperties.KEY_ENTRY_GENERATE.IF_MISSING) {
if (keyGenerationStrategy==IKeystoreAndCertificateProperties.KEY_ENTRY_GENERATE.IF_MISSING) {
// Check if keystore and truststore files exist (and create if they don't)
KeystoreUtil
.getKeystore(properties.getKeystoreFile(), properties.getKeystoreType(), properties.getKeystorePassword())
@ -497,12 +513,12 @@ public class KeystoreUtil {
log.debug(" Keystore already contains entry: {}", properties.getKeyEntryName());
} else {
log.debug(" Keystore does not contain entry: {}", properties.getKeyEntryName());
gen = true;
generateKey = true;
}
}
// Check if IP address is in subject CN or SAN list
if (keyGen==IKeystoreAndCertificateProperties.KEY_ENTRY_GENERATE.IF_IP_CHANGED) {
if (keyGenerationStrategy==IKeystoreAndCertificateProperties.KEY_ENTRY_GENERATE.IF_IP_CHANGED) {
// Check if keystore and truststore files exist (and create if they don't)
KeystoreUtil
.getKeystore(properties.getKeystoreFile(), properties.getKeystoreType(), properties.getKeystorePassword())
@ -527,13 +543,13 @@ public class KeystoreUtil {
// check if Default and Public IP addresses are contained in 'addrList'
boolean defaultFound = addrList.stream().anyMatch(s -> s.equals(defaultIp));
boolean publicFound = addrList.stream().anyMatch(s -> s.equals(publicIp));
gen = !defaultFound || !publicFound;
generateKey = !defaultFound || !publicFound;
log.debug(" Address has changed: {} (default-ip-found={}, public-ip-found={})",
gen, defaultFound, publicFound);
generateKey, defaultFound, publicFound);
}
// Generate new key pair and certificate, and update keystore and trust store
if (gen) {
if (generateKey) {
log.debug(" Generating new Key pair and Certificate for: {}", properties.getKeyEntryName());
KeystoreUtil ksUtil = KeystoreUtil

View File

@ -9,6 +9,7 @@
package gr.iccs.imu.ems.util;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@ -30,6 +31,11 @@ public class NetUtil {
private final static String[][] PUBLIC_ADDRESS_DISCOVERY_SERVICES;
@Getter
private final static boolean usePublic;
@Getter
private final static boolean useDefault;
static {
// Configure Address Filters
String filtersStr = System.getenv("NET_UTIL_ADDRESS_FILTERS");
@ -69,6 +75,12 @@ public class NetUtil {
servicesList.add(Arrays.asList("WhatIsMyIpAddress", "http://bot.whatismyipaddress.com/").toArray(new String[0]));
}
PUBLIC_ADDRESS_DISCOVERY_SERVICES = servicesList.toArray(new String[0][]);
// Configure IP address setting
String s = System.getenv("IP_SETTING");
s = s!=null ? s.trim() : "";
useDefault = "DEFAULT_IP".equalsIgnoreCase(s);
usePublic = ! useDefault;
}
// ------------------------------------------------------------------------
@ -272,6 +284,12 @@ public class NetUtil {
// ------------------------------------------------------------------------
public static String getIpSettingAddress() {
return usePublic ? getPublicIpAddress() : getDefaultIpAddress();
}
// ------------------------------------------------------------------------
public static boolean isLocalAddress(String addr) throws UnknownHostException {
return isLocalAddress(InetAddress.getByName(addr));
}

View File

@ -45,10 +45,11 @@ export const FORM_TYPE_OPTIONS = [
'text': 'Credentials',
'priority': 1001,
'options': [
{ 'id': 'get-cred', 'text': 'EMS server Broker credentials', 'url': '/broker/credentials', 'method': 'GET', 'form': '', 'priority': 1 },
{ 'id': 'get-ref', 'text': 'VM credentials by Ref', 'url': '/baguette/ref/{ref}', 'method': 'GET', 'form': 'ref-form', 'priority': 2 },
{ 'id': 'new-otp', 'text': 'New OTP', 'url': '/ems/otp/new', 'method': 'GET', 'form': '', 'priority': 3 },
{ 'id': 'del-otp', 'text': 'Delete OTP', 'url': '/ems/otp/remove/{otp}', 'method': 'GET', 'form': 'otp-form', 'priority': 4 },
{ 'id': 'get-bc-cred', 'text': 'EMS server Broker credentials', 'url': '/broker/credentials', 'method': 'GET', 'form': '', 'priority': 1 },
{ 'id': 'get-bs-cred', 'text': 'Baguette Server connection info', 'url': '/baguette/connectionInfo', 'method': 'GET', 'form': '', 'priority': 2 },
{ 'id': 'get-ref', 'text': 'VM credentials by Ref', 'url': '/baguette/ref/{ref}', 'method': 'GET', 'form': 'ref-form', 'priority': 3 },
{ 'id': 'new-otp', 'text': 'New OTP', 'url': '/ems/otp/new', 'method': 'GET', 'form': '', 'priority': 4 },
{ 'id': 'del-otp', 'text': 'Delete OTP', 'url': '/ems/otp/remove/{otp}', 'method': 'GET', 'form': 'otp-form', 'priority': 5 },
]
},

View File

@ -54,7 +54,7 @@
<a class="dropdown-item" href="/resources/client.sh" target="_new">Broker Client .sh</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="/resources/baguette-client.tgz" target="_new">Baguette Client</a>
<a class="dropdown-item" href="/resources/baguette-client.tgz.md5" target="_new">Baguette Client MD5</a>
<a class="dropdown-item" href="/resources/baguette-client.tgz.sha256" target="_new">Baguette Client SHA256</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item disabled" href="#">Details...</a>
</div>

View File

@ -77,10 +77,10 @@
</a>
</li>
<li class="nav-item">
<a href="/resources/baguette-client.tgz.md5" class="nav-link" target="_new">
<a href="/resources/baguette-client.tgz.sha256" class="nav-link" target="_new">
<i class="fas fa fa-font nav-icon"></i>
<p>
Baguette Client MD5
Baguette Client SHA256
<i class="fas right fa-arrow-alt-circle-down"></i>
</p>
</a>

View File

@ -15,8 +15,8 @@
<artifactId>ems-nebulous-plugin</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Nebulous-EMS plugin</name>
<description>Nebulous-EMS plugin provides metric model translator and MVV service</description>
<name>Nebulous EMS plugin</name>
<description>Nebulous EMS plugin providing the metric model translator and MVV service</description>
<properties>
<java.version>21</java.version>
@ -28,20 +28,24 @@
<ems.version>7.0.0-SNAPSHOT</ems.version>
<!-- Spring Boot versions -->
<spring.version>6.1.2</spring.version>
<spring-boot.version>3.2.1</spring-boot.version>
<spring.version>6.1.4</spring.version>
<spring-boot.version>3.2.3</spring-boot.version>
<snakeyaml.version>2.2</snakeyaml.version>
<lombok.version>1.18.30</lombok.version>
<!-- Nebulous-EMS extension dependency versions -->
<jackson.version>2.16.0</jackson.version>
<json-path.version>2.8.0</json-path.version>
<jackson.version>2.16.2</jackson.version>
<json-path.version>2.9.0</json-path.version>
<thymeleaf.version>3.1.2.RELEASE</thymeleaf.version>
<schematron.version>7.1.3</schematron.version>
<schematron.version>8.0.0</schematron.version>
<!-- io.fabricat8 docker-maven-plugin properties -->
<docker-image-properties-file>../ems-core/control-service/target/docker-image.properties</docker-image-properties-file>
<docker-maven-plugin.version>0.43.2</docker-maven-plugin.version>
<!-- EMS Nebulous Docker image properties -->
<docker-image-properties-file>docker-image.properties</docker-image-properties-file>
<docker.image.tag-nebulous>${docker.image.tag}-nebulous</docker.image.tag-nebulous>
<build.description>EMS Nebulous Docker Image is based on the EMS Core Docker Image</build.description>
</properties>
<dependencyManagement>
@ -117,6 +121,12 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>eu.nebulouscloud</groupId>
<artifactId>exn-connector-java</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- ===================================================================== -->
<!-- Compile time dependencies - Specific to Nebulous EMS extension -->
@ -137,6 +147,15 @@
<version>${jackson.version}</version>
</dependency>
<!-- ANTLR4 dependency -->
<!--XXX: Using version 4.7 required by Esper 7.1.0 (used in Broker-CEP)
XXX: Using latest ANTLR4 version results in conflict!
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
<version>4.13.1</version>
</dependency>-->
<!-- Thymeleaf dependencies -->
<dependency>
<groupId>org.thymeleaf</groupId>
@ -168,8 +187,109 @@
</dependencies>
<repositories>
<repository>
<id>nexus-nebulous</id>
<url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>
</repository>
</repositories>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<targetPath>${project.build.directory}</targetPath>
<filtering>true</filtering>
<includes>
<include>banner.txt</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<includes>
<include>*</include>
</includes>
<excludes>
<exclude>banner.txt</exclude>
</excludes>
</resource>
</resources>
<plugins>
<!-- Plugins for getting Buildnumber and Git info -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>buildnumber-create</id>
<phase>validate</phase>
<goals>
<goal>create</goal>
</goals>
</execution>
<execution>
<id>buildnumber-create-metadata</id>
<phase>validate</phase>
<goals>
<goal>create-metadata</goal>
</goals>
</execution>
</executions>
<configuration>
<outputDirectory>${project.build.directory}</outputDirectory>
<!--<format>{0,number,integer}</format>-->
<timestampFormat>yyyy-MM-dd HH:mm:ss.SSSZ</timestampFormat>
<revisionOnScmFailure>${project.version}</revisionOnScmFailure>
<!--<revisionOnScmFailure>unknownbuild</revisionOnScmFailure>-->
<items>
<item>buildNumber</item>
</items>
<doCheck>false</doCheck>
<doUpdate>false</doUpdate>
</configuration>
</plugin>
<plugin>
<groupId>io.github.git-commit-id</groupId>
<artifactId>git-commit-id-maven-plugin</artifactId>
<version>7.0.0</version>
<executions>
<execution>
<id>get-git-info</id>
<goals>
<goal>revision</goal>
</goals>
<phase>initialize</phase>
</execution>
</executions>
<configuration>
<generateGitPropertiesFile>true</generateGitPropertiesFile>
<generateGitPropertiesFilename>${project.build.directory}/git.properties</generateGitPropertiesFilename>
<commitIdGenerationMode>full</commitIdGenerationMode>
<failOnNoGitDirectory>false</failOnNoGitDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.antlr</groupId>
<artifactId>antlr4-maven-plugin</artifactId>
<!--<version>4.13.1</version>-->
<version>4.7</version>
<executions>
<execution>
<goals>
<goal>antlr4</goal>
</goals>
</execution>
</executions>
<configuration>
<listener>true</listener>
<visitor>true</visitor>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
@ -206,6 +326,10 @@
tofile="${project.build.directory}/${project.artifactId}-${project.version}-jar-with-dependencies.jar"
force="true"/>
<delete file="${project.build.directory}/temp-jar-with-dependencies.jar" />
<!--XXX:TODO: Used during development -->
<!--<copy file="${project.build.directory}/${project.artifactId}-${project.version}-jar-with-dependencies.jar"
todir="${project.basedir}/../../tests/plugins/"
force="true"/>-->
</target>
</configuration>
<goals>
@ -215,6 +339,32 @@
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>get-ems-core-docker-image-properties</id>
<phase>validate</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>gr.iccs.imu.ems</groupId>
<artifactId>control-service</artifactId>
<version>${ems.version}</version>
<classifier>docker-image</classifier>
<type>properties</type>
<outputDirectory>${project.build.directory}</outputDirectory>
<destFileName>${docker-image-properties-file}</destFileName>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<!-- Read docker image properties from file -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
@ -232,7 +382,7 @@
<configuration>
<!--<keyPrefix>dev-</keyPrefix>-->
<files>
<file>${docker-image-properties-file}</file>
<file>${project.build.directory}/${docker-image-properties-file}</file>
</files>
<outputFile/>
<properties/>
@ -260,57 +410,14 @@
</executions>
</plugin>-->
<!-- Build docker image using docker-context folder -->
<!--<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>${docker-maven-plugin.version}</version>
<configuration>
<verbose>true</verbose>
<useColor>true</useColor>
<images>
<image>
<name>${docker.image.name}:${docker.image.tag}-nebulous</name>
<build>
<from>${docker.image.name}:${docker.image.tag}</from>
<labels>
<artifactId>${project.artifactId}</artifactId>
<artifactId>${project.groupId}</artifactId>
<version>${project.version}</version>
</labels>
<env>
<EXTRA_LOADER_PATHS>/plugins/*</EXTRA_LOADER_PATHS>
<SCAN_PACKAGES>eu.nebulous.ems</SCAN_PACKAGES>
</env>
<assembly>
<targetDir>/plugin</targetDir>
<inline>
<files>
<file>
&lt;!&ndash; Path to the file you want to copy &ndash;&gt;
<source>${project.build.directory}/${project.artifactId}-${project.version}-jar-with-dependencies.jar</source>
&lt;!&ndash; Destination path within the container &ndash;&gt;
<outputDirectory></outputDirectory>
</file>
</files>
</inline>
</assembly>
</build>
</image>
</images>
</configuration>
<executions>
<execution>
<id>docker-image-build</id>
<phase>install</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>-->
</plugins>
</build>
<scm>
<connection>scm:git:http://127.0.0.1/dummy</connection>
<developerConnection>scm:git:https://127.0.0.1/dummy</developerConnection>
<tag>HEAD</tag>
<url>http://127.0.0.1/dummy</url>
</scm>
</project>

View File

@ -0,0 +1,82 @@
/*
* Copyright (C) 2023-2025 Institute of Communication and Computer Systems (imu.iccs.gr)
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
* If a copy of the MPL was not distributed with this file, you can obtain one at
* https://www.mozilla.org/en-US/MPL/2.0/
*/
grammar Constraints;
constraintExpression
: orConstraint EOF ;
orConstraint
: andConstraint ( OR andConstraint )*
;
andConstraint
: constraint ( AND constraint )*
;
constraint
: PARENTHESES_OPEN orConstraint PARENTHESES_CLOSE
| metricConstraint
| notConstraint
| conditionalConstraint
;
metricConstraint
: ID comparisonOperator NUM
| NUM comparisonOperator ID
;
comparisonOperator
: '=' | '==' | '<>'
| '<' | '<=' | '=<'
| '>' | '>=' | '=>'
;
notConstraint
: NOT constraint
;
/*logicalOperator
: AND | OR
;*/
conditionalConstraint
: IF orConstraint THEN orConstraint ( ELSE orConstraint )?
;
PARENTHESES_OPEN: '(' ;
PARENTHESES_CLOSE: ')' ;
NOT: N O T ;
AND: A N D ;
OR: O R ;
IF: I F ;
THEN: T H E N ;
ELSE: E L S E ;
fragment A: 'a' | 'A' ;
fragment D: 'd' | 'D' ;
fragment E: 'e' | 'E' ;
fragment F: 'f' | 'F' ;
fragment H: 'h' | 'H' ;
fragment I: 'i' | 'I' ;
fragment L: 'l' | 'L' ;
fragment N: 'n' | 'N' ;
fragment O: 'o' | 'O' ;
fragment R: 'r' | 'R' ;
fragment S: 's' | 'S' ;
fragment T: 't' | 'T' ;
ID: [a-zA-Z] [a-zA-Z0-9_-]* ;
NUM: ('-' | '+')? [0-9]+ ('.' [0-9]+)?
| ('-' | '+')? '.' [0-9]+
;
WS: [ \r\n\t]+ -> skip ;

View File

@ -0,0 +1,90 @@
/*
* Copyright (C) 2017-2023 Institute of Communication and Computer Systems (imu.iccs.gr)
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v2.0, unless
* Esper library is used, in which case it is subject to the terms of General Public License v2.0.
* If a copy of the MPL was not distributed with this file, you can obtain one at
* https://www.mozilla.org/en-US/MPL/2.0/
*/
package eu.nebulous.ems.controller;
import eu.nebulous.ems.translate.NebulousEmsTranslatorProperties;
import gr.iccs.imu.ems.control.controller.ControlServiceCoordinator;
import gr.iccs.imu.ems.control.controller.ControlServiceRequestInfo;
import gr.iccs.imu.ems.control.controller.RestControllerException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
@Slf4j
@RestController
@RequiredArgsConstructor
public class ModelController {
private final NebulousEmsTranslatorProperties translatorProperties;
private final ControlServiceCoordinator coordinator;
@RequestMapping(value = "/loadAppModel", method = POST)
public String loadAppModel(@RequestBody Map<String,String> request,
@RequestHeader(name = HttpHeaders.AUTHORIZATION, required = false) String jwtToken) throws IOException {
log.debug("ModelController.loadAppModel(): Received request: {}", request);
log.trace("ModelController.loadAppModel(): JWT token: {}", jwtToken);
// Get information from request
String applicationId = request.getOrDefault("application-id", "model-"+System.currentTimeMillis());
String applicationName = request.getOrDefault("application-name", applicationId);
String modelString = request.getOrDefault("model", null);
if (StringUtils.isBlank(modelString))
modelString = request.getOrDefault("body", null);
String modelFile = request.getOrDefault("model-path", "").toString();
log.info("ModelController.loadAppModel(): Request info: app-id={}, app-name={}, model-path={}, model={}",
applicationId, applicationName, modelFile, modelString);
// Check parameters
if (StringUtils.isBlank(applicationId)) {
log.warn("ModelController.loadAppModel(): Request does not contain an application id");
throw new RestControllerException(400, "Request does not contain an application id");
}
if (StringUtils.isBlank(modelString)) {
log.warn("ModelController.loadAppModel(): Request does not contain a model");
throw new RestControllerException(400, "Request does not contain a model");
}
// Store model in the disk
if (StringUtils.isNotBlank(modelString)) {
modelFile = StringUtils.isBlank(modelFile) ? getModelFile(applicationId) : modelFile;
storeModel(modelFile, modelString);
}
// Start translation and reconfiguration in a worker thread
coordinator.processAppModel(modelFile, null,
ControlServiceRequestInfo.create(applicationId, null, null, jwtToken, null));
log.debug("ModelController.loadAppModel(): Model translation dispatched to a worker thread");
return "OK";
}
private String getModelFile(String appId) {
return String.format("model-%s--%d.yml", appId, System.currentTimeMillis());
}
private void storeModel(String fileName, String modelStr) throws IOException {
Path path = Paths.get(translatorProperties.getModelsDir(), fileName);
Files.writeString(path, modelStr);
log.info("ModelController.storeModel(): Stored metric model in file: {}", path);
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (C) 2017-2023 Institute of Communication and Computer Systems (imu.iccs.gr)
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v2.0, unless
* Esper library is used, in which case it is subject to the terms of General Public License v2.0.
* If a copy of the MPL was not distributed with this file, you can obtain one at
* https://www.mozilla.org/en-US/MPL/2.0/
*/
package eu.nebulous.ems.plugins;
import gr.iccs.imu.ems.baguette.client.install.ClientInstallationTask;
import gr.iccs.imu.ems.baguette.client.install.plugin.AllowedTopicsProcessorPlugin;
import gr.iccs.imu.ems.baguette.server.NodeRegistryEntry;
import gr.iccs.imu.ems.control.controller.ControlServiceRequestInfo;
import gr.iccs.imu.ems.control.plugin.AppModelPlugin;
import gr.iccs.imu.ems.translate.TranslationContext;
import gr.iccs.imu.ems.util.ConfigWriteService;
import gr.iccs.imu.ems.util.EmsConstant;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.io.IOException;
@Slf4j
@Service
@RequiredArgsConstructor
public class NebulousAppModelPlugin implements AppModelPlugin {
private final AllowedTopicsProcessorPlugin allowedTopicsProcessorPlugin;
private final ConfigWriteService configWriteService;
@Override
public void preProcessingNewAppModel(String appModelId, ControlServiceRequestInfo requestInfo) {
log.debug("NebulousAppModelPlugin: Nothing to do. Args: appModelId={}, requestInfo={}", appModelId, requestInfo);
}
@Override
public void postProcessingNewAppModel(String appModelId, ControlServiceRequestInfo requestInfo, TranslationContext translationContext) {
log.debug("NebulousAppModelPlugin: BEGIN: appModelId={}, requestInfo={}", appModelId, requestInfo);
// Get collector allowed topics
NodeRegistryEntry entry = new NodeRegistryEntry(null, null, null);
ClientInstallationTask task = ClientInstallationTask.builder()
.nodeRegistryEntry(entry)
.translationContext(translationContext)
.build();
allowedTopicsProcessorPlugin.processBeforeInstallation(task, -1);
String allowedTopics = task.getNodeRegistryEntry().getPreregistration().get(EmsConstant.COLLECTOR_ALLOWED_TOPICS_VAR);
log.debug("NebulousAppModelPlugin: collector-allowed-topics: {}", allowedTopics);
if (StringUtils.isBlank(allowedTopics)) {
log.debug("NebulousAppModelPlugin: END: No value for 'collector-allowed-topics' setting: appModelId={}, requestInfo={}", appModelId, requestInfo);
return;
}
// Append collector-allowed-topics in ems-client-configmap file
try {
configWriteService
.getOrCreateConfigFile(
EmsConstant.EMS_CLIENT_K8S_CONFIG_MAP_FILE,
EmsConstant.EMS_CLIENT_K8S_CONFIG_MAP_FORMAT)
.put(EmsConstant.COLLECTOR_ALLOWED_TOPICS_VAR, allowedTopics);
log.debug("NebulousAppModelPlugin: END: Updated ems-client-configmap file: {}", EmsConstant.EMS_CLIENT_K8S_CONFIG_MAP_FILE);
} catch (IOException e) {
log.error("NebulousAppModelPlugin: EXCEPTION while updating ems-client-configmap file, during post-processing of new App Model: appModelId={}, requestInfo={}\nException: ",
appModelId, requestInfo, e);
}
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2017-2023 Institute of Communication and Computer Systems (imu.iccs.gr)
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v2.0, unless
* Esper library is used, in which case it is subject to the terms of General Public License v2.0.
* If a copy of the MPL was not distributed with this file, you can obtain one at
* https://www.mozilla.org/en-US/MPL/2.0/
*/
package eu.nebulous.ems.plugins;
import gr.iccs.imu.ems.control.plugin.WebAdminPlugin;
import org.springframework.stereotype.Service;
@Service
public class NebulousWebAdminPlugin implements WebAdminPlugin {
public RestCallCommandGroup restCallCommands() {
RestCallForm form = RestCallForm.builder()
.id("load-app-model-form")
.field( RestCallFormField.builder().name("application-id").text("Application Id").build() )
.field( RestCallFormField.builder().name("application-name").text("Application Name").build() )
.field( RestCallFormField.builder().name("model-path").text("Model Name").build() )
.field( RestCallFormField.builder().name("model").text("Model").build() )
.build();
return RestCallCommandGroup.builder()
.id("nebulous-group")
.text("Nebulous-related API")
.priority(0)
.command(RestCallCommand.builder()
.id("load-model").text("Load App. Model")
.method("POST").url("/loadAppModel")
.form(form)
.priority(10).build())
.build();
}
}

View File

@ -9,25 +9,46 @@
package eu.nebulous.ems.plugins;
import eu.nebulous.ems.service.ExternalBrokerPublisherService;
import eu.nebulous.ems.service.ExternalBrokerServiceProperties;
import gr.iccs.imu.ems.control.plugin.BeaconPlugin;
import gr.iccs.imu.ems.control.properties.TopicBeaconProperties;
import gr.iccs.imu.ems.control.util.TopicBeacon;
import gr.iccs.imu.ems.translate.TranslationContext;
import jakarta.jms.JMSException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import jakarta.jms.JMSException;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
@Slf4j
@Service
@RequiredArgsConstructor
public class PredictionInfoBeaconPlugin implements BeaconPlugin {
private final ExternalBrokerServiceProperties properties;
private final ExternalBrokerPublisherService externalBrokerPublisherService;
private final String externalBrokerMetricsToPredictTopic = ExternalBrokerServiceProperties.BASE_TOPIC_PREFIX + "metric_list";
private final String externalBrokerSloViolationDetectorTopics = ExternalBrokerServiceProperties.BASE_TOPIC_PREFIX + "slo.new";
public void init(TopicBeacon.BeaconContext context) {
log.debug("PredictionInfoBeaconPlugin.init(): Invoked");
if (properties.isEnabled()) {
externalBrokerPublisherService.addAdditionalTopic(externalBrokerMetricsToPredictTopic, externalBrokerMetricsToPredictTopic);
externalBrokerPublisherService.addAdditionalTopic(externalBrokerSloViolationDetectorTopics, externalBrokerSloViolationDetectorTopics);
log.debug("PredictionInfoBeaconPlugin.init(): Initialized ExternalBrokerService");
} else
log.debug("PredictionInfoBeaconPlugin.init(): ExternalBrokerService is disabled due to configuration");
}
public void transmit(TopicBeacon.BeaconContext context) {
log.debug("PredictionInfoBeaconPlugin.transmit(): Invoked");
transmitPredictionInfo(context);
transmitSloViolatorInfo(context);
log.trace("PredictionInfoBeaconPlugin.transmit(): Transmitted ");
}
private <T>Set<T> emptyIfNull(Set<T> s) {
@ -54,6 +75,8 @@ public class PredictionInfoBeaconPlugin implements BeaconPlugin {
// Get metric contexts of top-level DAG nodes
String metricContexts = translationContext.getAdditionalResultsAs(
PredictionsPostTranslationPlugin.PREDICTION_TOP_LEVEL_NODES_METRICS, String.class);
Map metricContextsMap = translationContext.getAdditionalResultsAs(
PredictionsPostTranslationPlugin.PREDICTION_TOP_LEVEL_NODES_METRICS_MAP, Map.class);
log.debug("Topic Beacon: transmitPredictionInfo: Metric Contexts for prediction: {}", metricContexts);
// Skip event sending if payload is empty
@ -66,6 +89,8 @@ public class PredictionInfoBeaconPlugin implements BeaconPlugin {
log.debug("Topic Beacon: Transmitting Prediction info: event={}, topics={}", metricContexts, properties.getPredictionTopics());
try {
context.sendMessageToTopics(metricContexts, properties.getPredictionTopics());
if (properties.isEnabled())
externalBrokerPublisherService.publishMessage(externalBrokerMetricsToPredictTopic, metricContextsMap);
} catch (JMSException e) {
log.error("Topic Beacon: EXCEPTION while transmitting Prediction info: event={}, topics={}, exception: ",
metricContexts, properties.getPredictionTopics(), e);
@ -88,6 +113,8 @@ public class PredictionInfoBeaconPlugin implements BeaconPlugin {
// Get SLO metric decompositions (String) from TranslationContext
String sloMetricDecompositions = translationContext.getAdditionalResultsAs(
PredictionsPostTranslationPlugin.PREDICTION_SLO_METRIC_DECOMPOSITION, String.class);
Map sloMetricDecompositionsMap = translationContext.getAdditionalResultsAs(
PredictionsPostTranslationPlugin.PREDICTION_SLO_METRIC_DECOMPOSITION_MAP, Map.class);
log.debug("Topic Beacon: transmitSloViolatorInfo: SLO metric decompositions: {}", sloMetricDecompositions);
if (StringUtils.isBlank(sloMetricDecompositions)) {
@ -99,6 +126,8 @@ public class PredictionInfoBeaconPlugin implements BeaconPlugin {
log.debug("Topic Beacon: Transmitting SLO Violator info: event={}, topics={}", sloMetricDecompositions, properties.getSloViolationDetectorTopics());
try {
context.sendMessageToTopics(sloMetricDecompositions, properties.getSloViolationDetectorTopics());
if (properties.isEnabled())
externalBrokerPublisherService.publishMessage(externalBrokerSloViolationDetectorTopics, sloMetricDecompositionsMap);
} catch (JMSException e) {
log.error("Topic Beacon: EXCEPTION while transmitting SLO Violator info: event={}, topics={}, exception: ",
sloMetricDecompositions, properties.getSloViolationDetectorTopics(), e);

View File

@ -11,7 +11,6 @@ package eu.nebulous.ems.plugins;
import eu.nebulous.ems.translate.NebulousEmsTranslator;
import gr.iccs.imu.ems.control.plugin.PostTranslationPlugin;
import gr.iccs.imu.ems.control.properties.TopicBeaconProperties;
import gr.iccs.imu.ems.control.util.TopicBeacon;
import gr.iccs.imu.ems.translate.TranslationContext;
import gr.iccs.imu.ems.translate.dag.DAGNode;
@ -22,7 +21,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import jakarta.annotation.PostConstruct;
import java.time.temporal.ValueRange;
import java.util.*;
import java.util.stream.Collectors;
@ -30,7 +29,9 @@ import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class PredictionsPostTranslationPlugin implements PostTranslationPlugin {
public final static String PREDICTION_SLO_METRIC_DECOMPOSITION_MAP = "NEBULOUS_PREDICTION_SLO_METRIC_DECOMPOSITION_MAP";
public final static String PREDICTION_SLO_METRIC_DECOMPOSITION = "NEBULOUS_PREDICTION_SLO_METRIC_DECOMPOSITION";
public final static String PREDICTION_TOP_LEVEL_NODES_METRICS_MAP = "NEBULOUS_PREDICTION_TOP_LEVEL_NODES_METRICS_MAP";
public final static String PREDICTION_TOP_LEVEL_NODES_METRICS = "NEBULOUS_PREDICTION_TOP_LEVEL_NODES_METRICS";
@PostConstruct
@ -42,15 +43,27 @@ public class PredictionsPostTranslationPlugin implements PostTranslationPlugin {
public void processTranslationResults(TranslationContext translationContext, TopicBeacon topicBeacon) {
log.debug("PredictionsPostTranslationPlugin.processTranslationResults(): BEGIN");
String sloMetricDecompositions = getSLOMetricDecompositionPayload(translationContext, topicBeacon);
translationContext.getAdditionalResults().put(PREDICTION_SLO_METRIC_DECOMPOSITION, sloMetricDecompositions);
log.debug("PredictionsPostTranslationPlugin.processTranslationResults(): SLO metrics decompositions: model={}, decompositions={}",
translationContext.getModelName(), sloMetricDecompositions);
// PREDICTION_SLO_METRIC_DECOMPOSITION
Map<String, Object> sloMetricDecompositionsMap = getSLOMetricDecompositionPayload(translationContext, topicBeacon);
translationContext.getAdditionalResults().put(PREDICTION_SLO_METRIC_DECOMPOSITION_MAP, sloMetricDecompositionsMap);
log.debug("PredictionsPostTranslationPlugin.processTranslationResults(): SLO metrics decompositions: model={}, decompositions-map={}",
translationContext.getModelName(), sloMetricDecompositionsMap);
String metricsOfTopLevelNodes = getMetricsForPredictionPayload(translationContext, topicBeacon);
translationContext.getAdditionalResults().put(PREDICTION_TOP_LEVEL_NODES_METRICS, metricsOfTopLevelNodes);
String sloMetricDecompositionsStr = topicBeacon.toJson(sloMetricDecompositionsMap);
translationContext.getAdditionalResults().put(PREDICTION_SLO_METRIC_DECOMPOSITION, sloMetricDecompositionsStr);
log.debug("PredictionsPostTranslationPlugin.processTranslationResults(): SLO metrics decompositions: model={}, decompositions={}",
translationContext.getModelName(), sloMetricDecompositionsStr);
// PREDICTION_TOP_LEVEL_NODES_METRICS
HashMap<String, Object> metricsOfTopLevelNodesMap = getMetricsForPredictionPayload(translationContext, topicBeacon);
translationContext.getAdditionalResults().put(PREDICTION_TOP_LEVEL_NODES_METRICS_MAP, metricsOfTopLevelNodesMap);
log.debug("PredictionsPostTranslationPlugin.processTranslationResults(): Metrics of Top-Level nodes of model: model={}, metrics-map={}",
translationContext.getModelName(), metricsOfTopLevelNodesMap);
String metricsOfTopLevelNodesStr = topicBeacon.toJson(metricsOfTopLevelNodesMap);
translationContext.getAdditionalResults().put(PREDICTION_TOP_LEVEL_NODES_METRICS, metricsOfTopLevelNodesStr);
log.debug("PredictionsPostTranslationPlugin.processTranslationResults(): Metrics of Top-Level nodes of model: model={}, metrics={}",
translationContext.getModelName(), metricsOfTopLevelNodes);
translationContext.getModelName(), metricsOfTopLevelNodesMap);
log.debug("PredictionsPostTranslationPlugin.processTranslationResults(): END");
}
@ -75,7 +88,7 @@ public class PredictionsPostTranslationPlugin implements PostTranslationPlugin {
.collect(Collectors.toSet());
}*/
public String getSLOMetricDecompositionPayload(TranslationContext translationContext, TopicBeacon topicBeacon) {
public Map<String, Object> getSLOMetricDecompositionPayload(TranslationContext translationContext, TopicBeacon topicBeacon) {
List<Object> slos = _getSLOMetricDecomposition(translationContext);
if (slos.isEmpty()) {
return null;
@ -87,7 +100,7 @@ public class PredictionsPostTranslationPlugin implements PostTranslationPlugin {
result.put("constraints", slos);
result.put("version", topicBeacon.getModelVersion());
return topicBeacon.toJson(result);
return result;
}
private @NonNull List<Object> _getSLOMetricDecomposition(TranslationContext translationContext) {
@ -133,7 +146,7 @@ public class PredictionsPostTranslationPlugin implements PostTranslationPlugin {
MetricConstraint mc = mcMap.get(elementName);
return Map.of(
"name", NebulousEmsTranslator.nameNormalization.apply(mc.getName()),
"comparisonOperator", mc.getComparisonOperator(),
"operator", mc.getComparisonOperator().getOperator(),
"threshold", mc.getThreshold());
} else
if (constraintNode instanceof LogicalConstraint) {
@ -175,7 +188,7 @@ public class PredictionsPostTranslationPlugin implements PostTranslationPlugin {
// ------------------------------------------------------------------------
private String getMetricsForPredictionPayload(@NonNull TranslationContext _TC, TopicBeacon topicBeacon) {
private HashMap<String, Object> getMetricsForPredictionPayload(@NonNull TranslationContext _TC, TopicBeacon topicBeacon) {
HashSet<MetricContext> metricsOfTopLevelNodes = getMetricsForPrediction(_TC);
if (metricsOfTopLevelNodes.isEmpty()) {
return null;
@ -240,8 +253,7 @@ public class PredictionsPostTranslationPlugin implements PostTranslationPlugin {
.filter(Objects::nonNull)
.toList() );
// Serialize payload
return topicBeacon.toJson(payload);
return payload;
}
private @NonNull HashSet<MetricContext> getMetricsForPrediction(@NonNull TranslationContext _TC) {

View File

@ -0,0 +1,101 @@
/*
* Copyright (C) 2023-2025 Institute of Communication and Computer Systems (imu.iccs.gr)
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
* If a copy of the MPL was not distributed with this file, you can obtain one at
* https://www.mozilla.org/en-US/MPL/2.0/
*/
package eu.nebulous.ems.service;
import eu.nebulouscloud.exn.Connector;
import eu.nebulouscloud.exn.core.Consumer;
import eu.nebulouscloud.exn.core.Context;
import eu.nebulouscloud.exn.core.Publisher;
import eu.nebulouscloud.exn.handlers.ConnectorHandler;
import eu.nebulouscloud.exn.settings.StaticExnConfig;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.TaskScheduler;
import java.time.Instant;
import java.util.List;
@Slf4j
public abstract class AbstractExternalBrokerService {
protected final ExternalBrokerServiceProperties properties;
protected final TaskScheduler taskScheduler;
protected Connector connector;
protected AbstractExternalBrokerService(ExternalBrokerServiceProperties properties, TaskScheduler taskScheduler) {
this.properties = properties;
this.taskScheduler = taskScheduler;
}
protected boolean checkProperties() {
return properties!=null
&& StringUtils.isNotBlank(properties.getBrokerAddress())
&& (properties.getBrokerPort() > 0 && properties.getBrokerPort() <= 65535);
}
protected void connectToBroker(List<Publisher> publishers, List<Consumer> consumers) {
try {
log.debug("AbstractExternalBrokerService: Trying to connect to broker: {}@{}:{}",
properties.getBrokerUsername(), properties.getBrokerAddress(), properties.getBrokerPort());
Connector connector = createConnector(publishers, consumers);
connector.start();
log.info("AbstractExternalBrokerService: Connected to broker");
Connector old_connector = this.connector;
this.connector = connector;
/*XXX:TODO: Report bug!!!
if (old_connector!=null) {
log.info("AbstractExternalBrokerService: Stopping old connector");
old_connector.stop();
}*/
} catch (Exception e) {
log.error("AbstractExternalBrokerService: Could not connect to broker: ", e);
this.connector = null;
if (properties.getRetryDelay()>0) {
log.error("AbstractExternalBrokerService: Next attempt to connect to broker in {}s", properties.getRetryDelay());
taskScheduler.schedule(() -> connectToBroker(publishers, consumers), Instant.now().plusSeconds(properties.getRetryDelay()));
} else {
log.error("AbstractExternalBrokerService: Will not retry to connect to broker (delay={})", properties.getRetryDelay());
}
}
}
@NonNull
private Connector createConnector(List<Publisher> publishers, List<Consumer> consumers) {
return new Connector(
ExternalBrokerServiceProperties.COMPONENT_NAME,
new ConnectorHandler() {
@Override
public void onReady(Context context) {
log.debug("AbstractExternalBrokerService: onReady: connected to: {}@{}:{}",
properties.getBrokerUsername(), properties.getBrokerAddress(), properties.getBrokerPort());
//((StatePublisher)context.getPublisher("state")).ready();
// ALSO SET 'enableState=true' AT LINE 83: 'false, false,' --> 'true, false,'
/*
if(context.hasPublisher("state")){
StatePublisher sp = (StatePublisher) context.getPublisher("state");
sp.starting();
sp.started();
sp.custom("forecasting");
sp.stopping();
sp.stopped();
}*/
}
},
publishers, consumers,
false, false,
new StaticExnConfig(
properties.getBrokerAddress(), properties.getBrokerPort(),
properties.getBrokerUsername(), properties.getBrokerPassword(),
properties.getHealthTimeout())
);
}
}

View File

@ -0,0 +1,99 @@
/*
* Copyright (C) 2023-2025 Institute of Communication and Computer Systems (imu.iccs.gr)
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
* If a copy of the MPL was not distributed with this file, you can obtain one at
* https://www.mozilla.org/en-US/MPL/2.0/
*/
package eu.nebulous.ems.service;
import eu.nebulouscloud.exn.core.Consumer;
import eu.nebulouscloud.exn.core.Context;
import eu.nebulouscloud.exn.core.Handler;
import eu.nebulouscloud.exn.core.Publisher;
import gr.iccs.imu.ems.util.NetUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.qpid.protonj2.client.Message;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
public class EmsBootInitializer extends AbstractExternalBrokerService implements ApplicationListener<ApplicationReadyEvent> {
private final ExternalBrokerListenerService listener;
private Consumer consumer;
private Publisher publisher;
public EmsBootInitializer(ExternalBrokerServiceProperties properties,
ExternalBrokerListenerService listener,
TaskScheduler scheduler)
{
super(properties, scheduler);
this.listener = listener;
}
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
if (! properties.isEnabled()) {
log.warn("===================> EMS is ready -- EMS Boot disabled due to configuration");
return;
}
log.info("===================> EMS is ready -- Scheduling EMS Boot message");
// Start connector used for EMS Booting
startConnector();
// Schedule sending EMS Boot message
taskScheduler.schedule(this::sendEmsBootReadyEvent, Instant.now().plusSeconds(1));
}
private void startConnector() {
Handler messageHandler = new Handler() {
@Override
public void onMessage(String key, String address, Map body, Message message, Context context) {
log.debug("EmsBootInitializer: Received a new EMS Boot Response message: key={}, address={}, body={}, message={}",
key, address, body, message);
processEmsBootResponseMessage(body);
}
};
consumer = new Consumer(properties.getEmsBootResponseTopic(), properties.getEmsBootResponseTopic(), messageHandler, null, true, true);
publisher = new Publisher(properties.getEmsBootTopic(), properties.getEmsBootTopic(), true, true);
connectToBroker(List.of(publisher), List.of(consumer));
}
protected void sendEmsBootReadyEvent() {
//XXX:TODO: Work in PROGRESS...
Map<String, String> message = Map.of(
"internal-address", NetUtil.getDefaultIpAddress(),
"public-address", NetUtil.getPublicIpAddress(),
"address", NetUtil.getIpAddress()
);
log.debug("ExternalBrokerPublisherService: Sending message to EMS Boot: {}", message);
publisher.send(message, null,true);
log.debug("ExternalBrokerPublisherService: Sent message to EMS Boot");
}
protected void processEmsBootResponseMessage(Map body) {
try {
// Process EMS Boot Response message
String appId = body.get("application").toString();
String modelStr = body.get("yaml").toString();
log.info("EmsBootInitializer: Received a new EMS Boot Response: App-Id: {}, Model:\n{}", appId, modelStr);
try {
listener.processMetricModel(appId, modelStr, null);
} catch (Exception e) {
log.warn("EmsBootInitializer: EXCEPTION while processing Metric Model for: app-id={} -- Exception: ", appId, e);
}
} catch (Exception e) {
log.warn("EmsBootInitializer: EXCEPTION while processing EMS Boot Response message: ", e);
}
}
}

View File

@ -0,0 +1,303 @@
/*
* Copyright (C) 2023-2025 Institute of Communication and Computer Systems (imu.iccs.gr)
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
* If a copy of the MPL was not distributed with this file, you can obtain one at
* https://www.mozilla.org/en-US/MPL/2.0/
*/
package eu.nebulous.ems.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.nebulous.ems.translate.NebulousEmsTranslatorProperties;
import eu.nebulouscloud.exn.core.Consumer;
import eu.nebulouscloud.exn.core.Context;
import eu.nebulouscloud.exn.core.Handler;
import eu.nebulouscloud.exn.core.Publisher;
import gr.iccs.imu.ems.control.controller.ControlServiceCoordinator;
import gr.iccs.imu.ems.control.controller.ControlServiceRequestInfo;
import gr.iccs.imu.ems.control.controller.NodeRegistrationCoordinator;
import gr.iccs.imu.ems.control.plugin.PostTranslationPlugin;
import gr.iccs.imu.ems.control.util.TopicBeacon;
import gr.iccs.imu.ems.translate.TranslationContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.qpid.protonj2.client.Message;
import org.apache.qpid.protonj2.client.exceptions.ClientException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
@Slf4j
@Service
public class ExternalBrokerListenerService extends AbstractExternalBrokerService
implements PostTranslationPlugin, InitializingBean
{
private final NebulousEmsTranslatorProperties translatorProperties;
private final ApplicationContext applicationContext;
private final ArrayBlockingQueue<Command> commandQueue = new ArrayBlockingQueue<>(100);
private final ObjectMapper objectMapper;
private List<Consumer> consumers;
private Publisher commandsResponsePublisher;
private Publisher modelsResponsePublisher;
private String applicationId = System.getenv("APPLICATION_ID");
record Command(String key, String address, Map body, Message message, Context context) {
}
public ExternalBrokerListenerService(ApplicationContext applicationContext,
ExternalBrokerServiceProperties properties,
TaskScheduler taskScheduler,
ObjectMapper objectMapper,
NebulousEmsTranslatorProperties translatorProperties)
{
super(properties, taskScheduler);
this.applicationContext = applicationContext;
this.objectMapper = objectMapper;
this.translatorProperties = translatorProperties;
}
@Override
public void afterPropertiesSet() throws Exception {
if (!properties.isEnabled()) {
log.info("ExternalBrokerListenerService: Disabled due to configuration");
return;
}
log.info("ExternalBrokerListenerService: Application Id (from Env.): {}", applicationId);
if (checkProperties()) {
initializeConsumers();
initializePublishers();
startCommandProcessor();
connectToBroker(List.of(commandsResponsePublisher, modelsResponsePublisher), consumers);
log.info("ExternalBrokerListenerService: Initialized listeners and publishers");
} else {
log.warn("ExternalBrokerListenerService: Not configured or misconfigured. Will not initialize");
}
}
@Override
public void processTranslationResults(TranslationContext translationContext, TopicBeacon topicBeacon) {
if (!properties.isEnabled()) {
log.info("ExternalBrokerListenerService: Disabled due to configuration");
return;
}
this.applicationId = translationContext.getAppId();
log.info("ExternalBrokerListenerService: Set applicationId to: {}", applicationId);
// Call control-service to deploy EMS clients
if (properties.isDeployEmsClientsOnKubernetesEnabled()) {
try {
log.info("ExternalBrokerListenerService: Start deploying EMS clients...");
String id = "dummy-" + System.currentTimeMillis();
Map<String, Object> nodeInfo = new HashMap<>(Map.of(
"id", id,
"name", id,
"type", "K8S",
"provider", "Kubernetes",
"zone-id", ""
));
applicationContext.getBean(NodeRegistrationCoordinator.class)
.registerNode("", nodeInfo, translationContext);
log.debug("ExternalBrokerListenerService: EMS clients deployment started");
} catch (Exception e) {
log.warn("ExternalBrokerListenerService: EXCEPTION while starting EMS client deployment: ", e);
}
} else
log.info("ExternalBrokerListenerService: EMS clients deployment is disabled");
}
private void initializeConsumers() {
/*if (StringUtils.isBlank(applicationId)) {
log.warn("ExternalBrokerListenerService: Call to initializeConsumers with blank applicationId. Will not change anything.");
return;
}*/
// Create message handler
Handler messageHandler = new Handler() {
@Override
public void onMessage(String key, String address, Map body, Message message, Context context) {
try {
log.info("ExternalBrokerListenerService: messageHandler: Got new message: key={}, address={}, body={}, message={}",
key, address, body, message);
super.onMessage(key, address, body, message, context);
commandQueue.add(new Command(key, address, body, message, context));
} catch (IllegalStateException e) {
log.warn("ExternalBrokerListenerService: Commands queue is full. Dropping command: queue-size={}", commandQueue.size());
} catch (Exception e) {
log.warn("ExternalBrokerListenerService: Error while processing message: ", e);
}
}
};
// Create consumers for each topic of interest
consumers = List.of(
new Consumer(properties.getCommandsTopic(), properties.getCommandsTopic(), messageHandler, null, true, true),
new Consumer(properties.getModelsTopic(), properties.getModelsTopic(), messageHandler, null, true, true)
);
log.info("ExternalBrokerListenerService: created subscribers");
}
private void initializePublishers() {
commandsResponsePublisher = new Publisher(properties.getCommandsResponseTopic(), properties.getCommandsResponseTopic(), true, true);
modelsResponsePublisher = new Publisher(properties.getModelsResponseTopic(), properties.getModelsResponseTopic(), true, true);
log.info("ExternalBrokerListenerService: created publishers");
}
private void startCommandProcessor() {
taskScheduler.schedule(()->{
while (true) {
try {
Command command = commandQueue.take();
processMessage(command);
} catch (InterruptedException e) {
log.warn("ExternalBrokerListenerService: Command processor interrupted. Exiting process loop");
break;
} catch (Exception e) {
log.warn("ExternalBrokerListenerService: Exception while processing command: {}\n", commandQueue, e);
}
}
}, Instant.now());
}
private void processMessage(Command command) throws ClientException, IOException {
log.debug("ExternalBrokerListenerService: Command: {}", command);
log.debug("ExternalBrokerListenerService: Command: message: {}", command.message);
log.debug("ExternalBrokerListenerService: Command: body: {}", command.message.body());
command.message.forEachProperty((s, o) ->
log.debug("ExternalBrokerListenerService: Command: --- property: {} = {}", s, o));
if (properties.getCommandsTopic().equals(command.address)) {
// Process command
log.info("ExternalBrokerListenerService: Received a command from external broker: {}", command.body);
processCommandMessage(command);
} else
if (properties.getModelsTopic().equals(command.address)) {
// Process metric model message
log.info("ExternalBrokerListenerService: Received a new Metric Model message from external broker: {}", command.body);
processMetricModelMessage(command);
}
}
private void processCommandMessage(Command command) throws ClientException {
// Get application id
String appId = getAppId(command, commandsResponsePublisher);
if (appId == null) return;
// Get command string
String commandStr = command.body.getOrDefault("command", "").toString();
log.debug("ExternalBrokerListenerService: Command: {}", commandStr);
sendResponse(commandsResponsePublisher, appId, "ERROR: ---NOT YET IMPLEMENTED---: "+ command.body);
}
private void processMetricModelMessage(Command command) throws ClientException, IOException {
// Get application id
String appId = getAppId(command, modelsResponsePublisher);
if (appId == null) return;
// Get model string and/or model file
Object modelObj = command.body.getOrDefault("model", null);
if (modelObj==null) {
modelObj = command.body.getOrDefault("yaml", null);
}
if (modelObj==null) {
modelObj = command.body.getOrDefault("body", null);
}
String modelFile = command.body.getOrDefault("model-path", "").toString();
// Check if 'model' or 'model-path' is provided
if (modelObj==null && StringUtils.isBlank(modelFile)) {
log.warn("ExternalBrokerListenerService: No model found in Metric Model message: {}", command.body);
sendResponse(modelsResponsePublisher, appId, "ERROR: No model found in Metric Model message: "+ command.body);
return;
}
String modelStr = null;
if (modelObj!=null) {
log.debug("ExternalBrokerListenerService: modelObj: class={}, object={}", modelObj.getClass(), modelObj);
modelStr = (modelObj instanceof String) ? (String) modelObj : modelObj.toString();
if (modelObj instanceof String) {
modelStr = (String) modelObj;
}
if (modelObj instanceof Map) {
modelStr = objectMapper.writeValueAsString(modelObj);
}
}
processMetricModel(appId, modelStr, modelFile);
}
void processMetricModel(String appId, String modelStr, String modelFile) throws IOException {
// If 'model' string is provided, store it in a file
if (StringUtils.isNotBlank(modelStr)) {
modelFile = StringUtils.isBlank(modelFile) ? getModelFile(appId) : modelFile;
storeModel(modelFile, modelStr);
} else if (StringUtils.isNotBlank(modelStr)) {
log.warn("ExternalBrokerListenerService: Parameter 'modelStr' is blank. Trying modelFile: {}", modelFile);
} else {
log.warn("ExternalBrokerListenerService: Parameters 'modelStr' and 'modelFile' are both blank");
throw new IllegalArgumentException("Parameters 'modelStr' and 'modelFile' are both blank");
}
// Call control-service to process model, also pass a callback to get the result
applicationContext.getBean(ControlServiceCoordinator.class).processAppModel(modelFile, null,
ControlServiceRequestInfo.create(appId, null, null, null,
(result) -> {
// Send message with the processing result
log.info("ExternalBrokerListenerService: Metric model processing result: {}", result);
sendResponse(modelsResponsePublisher, appId, result);
}));
}
private String getAppId(Command command, Publisher publisher) throws ClientException {
// Check if 'applicationId' is provided in message properties
Object propApp = command.message.property(properties.getApplicationIdPropertyName());
String appId = propApp != null ? propApp.toString() : null;
if (StringUtils.isNotBlank(appId)) {
log.debug("ExternalBrokerListenerService: Application Id found in message properties: {}", appId);
return appId;
}
log.debug("ExternalBrokerListenerService: No Application Id found in message properties: {}", command.body);
// Check if 'applicationId' is provided in message body
appId = command.body.getOrDefault("application", "").toString();
if (StringUtils.isNotBlank(appId)) {
log.debug("ExternalBrokerListenerService: Application Id found in message body: {}", appId);
return appId;
}
log.debug("ExternalBrokerListenerService: No Application Id found in message body: {}", command.body);
// Not found 'applicationId'
log.warn("ExternalBrokerListenerService: No Application Id found in message: {}", command.body);
sendResponse(modelsResponsePublisher, null, "ERROR: No Application Id found in message: "+ command.body);
return null;
}
private String getModelFile(String appId) {
return String.format("model-%s--%d.yml", appId, System.currentTimeMillis());
}
private void storeModel(String fileName, String modelStr) throws IOException {
Path path = Paths.get(translatorProperties.getModelsDir(), fileName);
Files.writeString(path, modelStr);
log.info("ExternalBrokerListenerService: Stored metric model in file: {}", path);
}
private void sendResponse(Publisher publisher, String appId, Object response) {
publisher.send(Map.of(
"response", response
), appId);
}
}

View File

@ -0,0 +1,197 @@
/*
* Copyright (C) 2023-2025 Institute of Communication and Computer Systems (imu.iccs.gr)
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
* If a copy of the MPL was not distributed with this file, you can obtain one at
* https://www.mozilla.org/en-US/MPL/2.0/
*/
package eu.nebulous.ems.service;
import com.google.gson.Gson;
import eu.nebulous.ems.translate.NebulousEmsTranslator;
import eu.nebulouscloud.exn.core.Publisher;
import gr.iccs.imu.ems.brokercep.BrokerCepService;
import gr.iccs.imu.ems.brokercep.event.EventMap;
import gr.iccs.imu.ems.control.plugin.PostTranslationPlugin;
import gr.iccs.imu.ems.control.util.TopicBeacon;
import gr.iccs.imu.ems.translate.Grouping;
import gr.iccs.imu.ems.translate.TranslationContext;
import jakarta.jms.JMSException;
import jakarta.jms.Message;
import jakarta.jms.MessageListener;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.activemq.command.ActiveMQMapMessage;
import org.apache.activemq.command.ActiveMQMessage;
import org.apache.activemq.command.ActiveMQObjectMessage;
import org.apache.activemq.command.ActiveMQTextMessage;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j
@Service
public class ExternalBrokerPublisherService extends AbstractExternalBrokerService
implements PostTranslationPlugin, MessageListener
{
private static final String COMBINED_SLO_PUBLISHER_KEY = "COMBINED_SLO_PUBLISHER_KEY_" + System.currentTimeMillis();
private final BrokerCepService brokerCepService;
private final Map<String, String> additionalTopicsMap = new HashMap<>();
private final Gson gson = new Gson();
private Map<String, Publisher> publishersMap;
private String applicationId;
private Set<String> sloSet;
protected ExternalBrokerPublisherService(ExternalBrokerServiceProperties properties,
TaskScheduler taskScheduler, BrokerCepService brokerCepService)
{
super(properties, taskScheduler);
this.brokerCepService = brokerCepService;
}
public void addAdditionalTopic(@NonNull String topic, @NonNull String externalBrokerTopic) {
if (StringUtils.isNotBlank(topic) && StringUtils.isNotBlank(externalBrokerTopic))
additionalTopicsMap.put(topic.trim(), externalBrokerTopic.trim());
else
log.warn("ExternalBrokerPublisherService: Ignoring call to 'addAdditionalTopic' with blank argument(s): topic={}, externalBrokerTopic={}",
topic, externalBrokerTopic);
}
@Override
public void processTranslationResults(TranslationContext translationContext, TopicBeacon topicBeacon) {
if (!properties.isEnabled()) {
log.info("ExternalBrokerPublisherService: Disabled due to configuration");
return;
}
log.debug("ExternalBrokerPublisherService: Initializing...");
// Get application id
applicationId = translationContext.getAppId();
// Get Top-Level topics (i.e. those at GLOBAL grouping)
Set<String> topLevelTopics = translationContext.getG2T().entrySet().stream()
.filter(e -> e.getKey().equalsIgnoreCase(Grouping.GLOBAL.name()))
.findAny().orElseThrow()
.getValue();
log.debug("ExternalBrokerPublisherService: Top-Level topics: {}", topLevelTopics);
if (topLevelTopics.isEmpty()) {
log.warn("ExternalBrokerPublisherService: No top-level topics found.");
return;
}
// Find top-level topics that correspond to SLOs (or other requirements)
log.trace("ExternalBrokerPublisherService: SLOs-BEFORE: {}", translationContext.getSLO());
sloSet = translationContext.getSLO().stream()
.map(NebulousEmsTranslator.nameNormalization)
.collect(Collectors.toSet());
log.trace("ExternalBrokerPublisherService: SLOs-AFTER: {}", sloSet);
// Prepare publishers
publishersMap = topLevelTopics.stream().collect(Collectors.toMap(
t -> t, t -> {
/*if (sloSet.contains(t)) {
// Send SLO violations to combined SLO topic
return new Publisher(t, properties.getCombinedSloTopic(), true, true);
} else {
// Send non-SLO events to their corresponding Nebulous topics
return new Publisher(t, properties.getMetricsTopicPrefix() + t, true, true);
}*/
return new Publisher(t, properties.getMetricsTopicPrefix() + t, true, true);
},
(publisher, publisher2) -> publisher, HashMap::new
));
// Also add publisher for Event Type VI messages
publishersMap.put(COMBINED_SLO_PUBLISHER_KEY,
new Publisher(COMBINED_SLO_PUBLISHER_KEY, properties.getCombinedSloTopic(), true, true));
// Also prepare additional publishers
log.debug("ExternalBrokerPublisherService: additionalTopicsMap: {}", additionalTopicsMap);
if (! additionalTopicsMap.isEmpty()) {
additionalTopicsMap.forEach((topic, externalBrokerTopic) -> {
log.trace("ExternalBrokerPublisherService: additionalTopicsMap: Additional publisher for: {} --> {}", topic, externalBrokerTopic);
publishersMap.put(topic, new Publisher(topic, externalBrokerTopic, true, true));
});
}
// Create connector to external broker
connectToBroker(publishersMap.values().stream().toList(), List.of());
// Register for EMS broker events
brokerCepService.getBrokerCepBridge().getListeners().add(this);
log.info("ExternalBrokerPublisherService: initialized publishers");
}
@Override
public void onMessage(Message message) {
if (message instanceof ActiveMQMessage amqMessage) {
String topic = amqMessage.getDestination().getPhysicalName();
Publisher publisher = publishersMap.get(topic);
if (publisher!=null) {
log.trace("ExternalBrokerPublisherService: Sending message to external broker: topic: {}, message: {}", topic, amqMessage);
try {
// Send metric event
Map body = getMessageAsMap(amqMessage);
if (body!=null) {
publishMessage(publisher, body);
log.trace("ExternalBrokerPublisherService: Sent message to external broker: topic: {}, message: {}", topic, body);
// If an SLO, also send an Event Type VI event to combined SLO topics
if (sloSet.contains(topic)) {
publishMessage(publishersMap.get(COMBINED_SLO_PUBLISHER_KEY), Map.of(
"severity", 0.5,
"predictionTime", Instant.now().toEpochMilli(),
"probability", 1.0
));
log.trace("ExternalBrokerPublisherService: Also sent message to combined SLO topic: topic: {}, message: {}", topic, body);
}
} else
log.warn("ExternalBrokerPublisherService: Could not get body from internal broker message: topic: {}, message: {}", topic, amqMessage);
} catch (JMSException e) {
log.warn("ExternalBrokerPublisherService: Error while sending message: {}, Exception: ", topic, e);
}
} else
log.warn("ExternalBrokerPublisherService: No publisher found for topic: {}, message: {}", topic, message);
} else
log.trace("ExternalBrokerPublisherService: Ignoring non-ActiveMQ message from internal broker: {}", message);
}
private Map getMessageAsMap(ActiveMQMessage amqMessage) throws JMSException {
return switch (amqMessage) {
case ActiveMQTextMessage textMessage -> EventMap.parseEventMap(textMessage.getText());
case ActiveMQObjectMessage objectMessage -> EventMap.parseEventMap(objectMessage.getObject().toString());
case ActiveMQMapMessage mapMessage -> mapMessage.getContentMap();
case null, default -> null;
};
}
public boolean publishMessage(String topic, String bodyStr) {
if (! properties.isEnabled()) return false;
Publisher publisher = publishersMap.get(topic);
if (publisher!=null)
publishMessage(publisher, gson.fromJson(bodyStr, Map.class));
return publisher!=null;
}
public boolean publishMessage(String topic, Map body) {
if (! properties.isEnabled()) return false;
Publisher publisher = publishersMap.get(topic);
if (publisher!=null)
publishMessage(publisher, body);
return publisher!=null;
}
private void publishMessage(Publisher publisher, Map body) {
publisher.send(body, applicationId, Map.of(/*"prop1", "zz", "prop2", "cc"*/));
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (C) 2023-2025 Institute of Communication and Computer Systems (imu.iccs.gr)
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
* If a copy of the MPL was not distributed with this file, you can obtain one at
* https://www.mozilla.org/en-US/MPL/2.0/
*/
package eu.nebulous.ems.service;
import lombok.Data;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;
import static gr.iccs.imu.ems.util.EmsConstant.EMS_PROPERTIES_PREFIX;
@Slf4j
@Data
@Validated
@Configuration
@ConfigurationProperties(prefix = EMS_PROPERTIES_PREFIX + "external")
public class ExternalBrokerServiceProperties implements InitializingBean {
public final static String NEBULOUS_TOPIC_PREFIX = "eu.nebulouscloud.";
public final static String COMPONENT_NAME = "monitoring";
public final static String BASE_TOPIC_PREFIX = NEBULOUS_TOPIC_PREFIX + COMPONENT_NAME + ".";
private boolean enabled = true;
private String brokerAddress;
private int brokerPort;
private String brokerUsername;
@ToString.Exclude
private String brokerPassword;
private int healthTimeout = 60;
private int retryDelay = 10;
private String applicationIdPropertyName = "application";
private String commandsTopic = BASE_TOPIC_PREFIX + "commands";
private String commandsResponseTopic = BASE_TOPIC_PREFIX + "commands.reply";
private String modelsTopic = NEBULOUS_TOPIC_PREFIX + "ui.dsl.metric_model";
private String modelsResponseTopic = NEBULOUS_TOPIC_PREFIX + "ui.dsl.metric_model.reply";
private String metricsTopicPrefix = BASE_TOPIC_PREFIX + "realtime.";
private String combinedSloTopic = BASE_TOPIC_PREFIX + "slo.severity_value";
private String emsBootTopic = NEBULOUS_TOPIC_PREFIX + "ems.boot";
private String emsBootResponseTopic = NEBULOUS_TOPIC_PREFIX + "ems.boot.reply";
private boolean deployEmsClientsOnKubernetesEnabled = true;
@Override
public void afterPropertiesSet() {
log.debug("ExternalBrokerServiceProperties: {}", this);
}
}

View File

@ -53,6 +53,11 @@ public class NebulousEmsTranslator implements Translator, InitializingBean {
@Override
public TranslationContext translate(String metricModelPath) {
return translate(metricModelPath, null);
}
@Override
public TranslationContext translate(String metricModelPath, String applicationId) {
if (StringUtils.isBlank(metricModelPath)) {
log.error("NebulousEmsTranslator: No metric model specified");
throw new NebulousEmsTranslationException("No metric model specified");
@ -72,7 +77,7 @@ public class NebulousEmsTranslator implements Translator, InitializingBean {
// -- Translate model ---------------------------------------------
log.info("NebulousEmsTranslator: Translating metric model: {}", metricModelPath);
TranslationContext _TC = translate(modelObj, metricModelPath);
TranslationContext _TC = translate(modelObj, metricModelPath, applicationId);
log.info("NebulousEmsTranslator: Translating metric model completed: {}", metricModelPath);
return _TC;
@ -85,7 +90,7 @@ public class NebulousEmsTranslator implements Translator, InitializingBean {
// ================================================================================================================
// Private methods
private TranslationContext translate(Object modelObj, String modelFileName) throws Exception {
private TranslationContext translate(Object modelObj, String modelFileName, String appId) throws Exception {
log.debug("NebulousEmsTranslator.translate(): BEGIN: metric-model={}", modelObj);
// Get model name
@ -93,6 +98,7 @@ public class NebulousEmsTranslator implements Translator, InitializingBean {
// Initialize data structures
TranslationContext _TC = new TranslationContext(modelName, modelFileName);
_TC.setAppId(appId);
// -- Expand shorthand expressions ------------------------------------
log.debug("NebulousEmsTranslator.translate(): Expanding shorthand expressions: {}", modelName);

View File

@ -32,7 +32,7 @@ public class NebulousEmsTranslatorProperties implements InitializingBean {
private boolean skipModelValidation;
// Translator parameters
private String modelsDir = "/models";
private String modelsDir = "models";
// Sensor processing parameters
private long sensorMinInterval;

View File

@ -29,6 +29,7 @@ public class AnalysisUtils {
List.of("<", "<=", "=<", ">", ">=", "=<", "=", "<>", "!=");
final static List<String> LOGICAL_OPERATORS =
List.of("and", "or");
final static String NOT_OPERATOR = "not";
// ------------------------------------------------------------------------
// Exceptions and Casting method
@ -102,6 +103,31 @@ public class AnalysisUtils {
return val;
}
static Object getSpecFieldAsObject(Object o, String field) {
return getSpecFieldAsObject(o, field, "Block '%s' is not String or Map: ");
}
static Object getSpecFieldAsObject(Object o, String field, String exceptionMessage) {
try {
Map<String, Object> spec = asMap(o);
Object oValue = spec.get(field);
if (oValue == null)
return null;
if (oValue instanceof String || oValue instanceof Map)
return oValue;
throw createException(exceptionMessage.formatted(field) + spec);
} catch (Exception e) {
throw createException(exceptionMessage.formatted(field) + o, e);
}
}
static Object getMandatorySpecFieldAsObject(Object o, String field, String exceptionMessage) {
Object val = getSpecFieldAsObject(o, field, exceptionMessage);
if (val==null)
throw createException(exceptionMessage.formatted(field) + o);
return val;
}
static String getSpecName(Object o) {
return getSpecField(o, "name");
}
@ -219,4 +245,8 @@ public class AnalysisUtils {
return LOGICAL_OPERATORS.contains(s);
}
static boolean isNotOperator(String s) {
return NOT_OPERATOR.equalsIgnoreCase(s);
}
}

Some files were not shown because too many files have changed in this diff Show More