diff --git a/nebulous/ems-core/baguette-client-install/pom.xml b/nebulous/ems-core/baguette-client-install/pom.xml index 66e1895..b7f2946 100644 --- a/nebulous/ems-core/baguette-client-install/pom.xml +++ b/nebulous/ems-core/baguette-client-install/pom.xml @@ -45,11 +45,12 @@ + @@ -61,6 +62,34 @@ org.yaml snakeyaml + + + + io.fabric8 + kubernetes-client + 6.10.0 + + + com.squareup.okhttp3 + * + + + com.squareup.okio + * + + + + + com.squareup.okhttp3 + okhttp + 4.12.0 + + + com.squareup.okio + okio + 3.8.0 + + diff --git a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/ClientInstaller.java b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/ClientInstaller.java index 9b3a931..0c23ab6 100644 --- a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/ClientInstaller.java +++ b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/ClientInstaller.java @@ -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 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 condition, + BiFunction 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); diff --git a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/helper/AbstractInstallationHelper.java b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/helper/AbstractInstallationHelper.java index e4711ee..8845e1d 100644 --- a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/helper/AbstractInstallationHelper.java +++ b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/helper/AbstractInstallationHelper.java @@ -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, 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 { diff --git a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/helper/InstallationHelperFactory.java b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/helper/InstallationHelperFactory.java index c3ab4df..7f2d727 100644 --- a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/helper/InstallationHelperFactory.java +++ b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/helper/InstallationHelperFactory.java @@ -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(); + } } diff --git a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/helper/K8sInstallationHelper.java b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/helper/K8sInstallationHelper.java new file mode 100644 index 0000000..296994b --- /dev/null +++ b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/helper/K8sInstallationHelper.java @@ -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 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 initializeNodeMap(NodeRegistryEntry entry) { + return entry.getPreregistration(); + } + + @Override + public List prepareInstallationInstructionsForWin(NodeRegistryEntry entry) { + return null; + } + + @Override + public List prepareInstallationInstructionsForLinux(NodeRegistryEntry entry) throws IOException { + return null; + } + + @Override + public List prepareUninstallInstructionsForWin(NodeRegistryEntry entry) { + return null; + } + + @Override + public List prepareUninstallInstructionsForLinux(NodeRegistryEntry entry) throws IOException { + return null; + } +} diff --git a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/helper/VmInstallationHelper.java b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/helper/VmInstallationHelper.java index dae0db0..0ac0110 100644 --- a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/helper/VmInstallationHelper.java +++ b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/helper/VmInstallationHelper.java @@ -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 { diff --git a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/installer/K8sClientInstaller.java b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/installer/K8sClientInstaller.java new file mode 100644 index 0000000..d90a27e --- /dev/null +++ b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/installer/K8sClientInstaller.java @@ -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 configMapMap = configWriteService + .getConfigFile(EmsConstant.EMS_CLIENT_K8S_CONFIG_MAP_FILE).getContentMap(); + log.debug("K8sClientInstaller: configMap: data:\n{}", configMapMap); + + // Create ems client configmap + Resource 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 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 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 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; + } +} diff --git a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/SshClientInstaller.java b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/installer/SshClientInstaller.java similarity index 97% rename from nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/SshClientInstaller.java rename to nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/installer/SshClientInstaller.java index b066c18..53d956f 100644 --- a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/SshClientInstaller.java +++ b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/installer/SshClientInstaller.java @@ -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()); diff --git a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/SshJsClientInstaller.java b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/installer/SshJsClientInstaller.java similarity index 97% rename from nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/SshJsClientInstaller.java rename to nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/installer/SshJsClientInstaller.java index 80fb38f..2189796 100644 --- a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/SshJsClientInstaller.java +++ b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/installer/SshJsClientInstaller.java @@ -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; diff --git a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/StreamLogger.java b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/installer/StreamLogger.java similarity index 98% rename from nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/StreamLogger.java rename to nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/installer/StreamLogger.java index 101717f..1732f89 100644 --- a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/StreamLogger.java +++ b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/installer/StreamLogger.java @@ -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; diff --git a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/plugin/AllowedTopicsProcessorPlugin.java b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/plugin/AllowedTopicsProcessorPlugin.java index c82f9d7..7caffd1 100644 --- a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/plugin/AllowedTopicsProcessorPlugin.java +++ b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/plugin/AllowedTopicsProcessorPlugin.java @@ -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 addedTopicsSet = new HashSet<>(); + Map> 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 sensorConfig = monitor.getSensor().getConfiguration();; + Map 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); } diff --git a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/plugin/PrometheusProcessorPlugin.java b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/plugin/PrometheusProcessorPlugin.java index 1fe58d4..8b9200e 100644 --- a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/plugin/PrometheusProcessorPlugin.java +++ b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/plugin/PrometheusProcessorPlugin.java @@ -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() ); diff --git a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/watch/K8sPodWatcher.java b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/watch/K8sPodWatcher.java new file mode 100644 index 0000000..041e889 --- /dev/null +++ b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/install/watch/K8sPodWatcher.java @@ -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 addressToNodeMap; + Map> 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 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 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 addressToNodeEntryMap = nodeRegistry.getNodes().stream() + .collect(Collectors.toMap(NodeRegistryEntry::getIpAddress, entry -> entry)); + + // New Pods + HashMap> 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 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 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()); + } + } +} diff --git a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/selfhealing/ClientRecoveryPlugin.java b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/selfhealing/ClientRecoveryPlugin.java index 4cf113b..7c314a2 100644 --- a/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/selfhealing/ClientRecoveryPlugin.java +++ b/nebulous/ems-core/baguette-client-install/src/main/java/gr/iccs/imu/ems/baguette/client/selfhealing/ClientRecoveryPlugin.java @@ -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; diff --git a/nebulous/ems-core/baguette-client-install/src/main/resources/ems-client-daemonset.yaml b/nebulous/ems-core/baguette-client-install/src/main/resources/ems-client-daemonset.yaml new file mode 100644 index 0000000..5a65c10 --- /dev/null +++ b/nebulous/ems-core/baguette-client-install/src/main/resources/ems-client-daemonset.yaml @@ -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 \ No newline at end of file diff --git a/nebulous/ems-core/baguette-client/Dockerfile b/nebulous/ems-core/baguette-client/Dockerfile new file mode 100644 index 0000000..4990b0c --- /dev/null +++ b/nebulous/ems-core/baguette-client/Dockerfile @@ -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"] \ No newline at end of file diff --git a/nebulous/ems-core/baguette-client/bin/install.sh b/nebulous/ems-core/baguette-client/bin/install.sh index 9c841dd..8e4d3fd 100644 --- a/nebulous/ems-core/baguette-client/bin/install.sh +++ b/nebulous/ems-core/baguette-client/bin/install.sh @@ -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 diff --git a/nebulous/ems-core/baguette-client/bin/jre-install.sh b/nebulous/ems-core/baguette-client/bin/jre-install.sh new file mode 100644 index 0000000..247c1f8 --- /dev/null +++ b/nebulous/ems-core/baguette-client/bin/jre-install.sh @@ -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 diff --git a/nebulous/ems-core/baguette-client/bin/run.bat b/nebulous/ems-core/baguette-client/bin/run.bat index a38a299..fc015e4 100644 --- a/nebulous/ems-core/baguette-client/bin/run.bat +++ b/nebulous/ems-core/baguette-client/bin/run.bat @@ -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% diff --git a/nebulous/ems-core/baguette-client/bin/run.sh b/nebulous/ems-core/baguette-client/bin/run.sh index e9cd9fc..df84fce 100644 --- a/nebulous/ems-core/baguette-client/bin/run.sh +++ b/nebulous/ems-core/baguette-client/bin/run.sh @@ -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 \ No newline at end of file diff --git a/nebulous/ems-core/baguette-client/conf/baguette-client.properties.sample b/nebulous/ems-core/baguette-client/conf/baguette-client.properties.sample index ab88d1c..1a1b7f8 100644 --- a/nebulous/ems-core/baguette-client/conf/baguette-client.properties.sample +++ b/nebulous/ems-core/baguette-client/conf/baguette-client.properties.sample @@ -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 diff --git a/nebulous/ems-core/baguette-client/conf/baguette-client.yml b/nebulous/ems-core/baguette-client/conf/baguette-client.yml index 0920f57..76602ef 100644 --- a/nebulous/ems-core/baguette-client/conf/baguette-client.yml +++ b/nebulous/ems-core/baguette-client/conf/baguette-client.yml @@ -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: diff --git a/nebulous/ems-core/baguette-client/docker-compose.yml b/nebulous/ems-core/baguette-client/docker-compose.yml new file mode 100644 index 0000000..3cb0348 --- /dev/null +++ b/nebulous/ems-core/baguette-client/docker-compose.yml @@ -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 \ No newline at end of file diff --git a/nebulous/ems-core/baguette-client/pom.xml b/nebulous/ems-core/baguette-client/pom.xml index 0127385..a08c2ce 100644 --- a/nebulous/ems-core/baguette-client/pom.xml +++ b/nebulous/ems-core/baguette-client/pom.xml @@ -21,6 +21,12 @@ 3.1.12 + + + 0.43.2 + ems-client + emsuser + /opt/baguette-client @@ -74,6 +80,10 @@ org.springframework * + + org.eclipse.jgit + org.eclipse.jgit + @@ -171,5 +181,121 @@ - + + + + dev-local-docker-image-build + + + ../.dev-local-docker-image-build + + + + + + + org.codehaus.mojo + properties-maven-plugin + 1.2.0 + + + read-docker-image-properties + validate + + read-project-properties + + + + + + ../.dev-local-docker-image-build + + + + + + + + + + + build-docker-image + + . + + + + + + maven-antrun-plugin + 3.1.0 + + + set-docker-properties + validate + + run + + + true + + + + + + + + + + + + + + + + + + diff --git a/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/BaguetteClient.java b/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/BaguetteClient.java index 3856ab5..c8e2e90 100644 --- a/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/BaguetteClient.java +++ b/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/BaguetteClient.java @@ -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()); diff --git a/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/BaguetteClientProperties.java b/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/BaguetteClientProperties.java index b2f6037..38ef8bb 100644 --- a/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/BaguetteClientProperties.java +++ b/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/BaguetteClientProperties.java @@ -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> collectorClasses; + private Map>> collectorConfigurations; private String debugFakeIpAddress; diff --git a/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/Collector.java b/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/Collector.java index a1a5cbd..84e655d 100644 --- a/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/Collector.java +++ b/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/Collector.java @@ -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); } diff --git a/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/CommandExecutor.java b/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/CommandExecutor.java index 3f64ed3..9831472 100644 --- a/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/CommandExecutor.java +++ b/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/CommandExecutor.java @@ -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); diff --git a/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/collector/netdata/K8sNetdataCollector.java b/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/collector/netdata/K8sNetdataCollector.java new file mode 100644 index 0000000..0c0cfb1 --- /dev/null +++ b/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/collector/netdata/K8sNetdataCollector.java @@ -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 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 eventBus; + private final RestClient restClient = RestClient.create(); + private final List> scheduledFuturesList = new LinkedList<>(); + private boolean started; + private List> 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 sensorConfig = (Map) 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 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 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 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 chart_ids = (List) response.get("chart_ids"); + List dimension_ids = (List) response.get("dimension_ids"); + List latest_values = (List) response.get("view_latest_value"); + for (int i=0, n=chart_ids.size(); i ids = (List) dimensions.get("ids"); + List units = (List) dimensions.get("units"); + List values = (List) ((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 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; + } +} diff --git a/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/collector/netdata/NetdataCollector.java b/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/collector/netdata/NetdataCollector.java index 8d3db8a..ef7be39 100644 --- a/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/collector/netdata/NetdataCollector.java +++ b/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/collector/netdata/NetdataCollector.java @@ -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> 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 topics = new HashSet<>(); for (String g : GROUPING.getNames()) { diff --git a/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/collector/prometheus/PrometheusCollector.java b/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/collector/prometheus/PrometheusCollector.java index cea61df..1a088e8 100644 --- a/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/collector/prometheus/PrometheusCollector.java +++ b/nebulous/ems-core/baguette-client/src/main/java/gr/iccs/imu/ems/baguette/client/collector/prometheus/PrometheusCollector.java @@ -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> 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 topics = new HashSet<>(); for (String g : GROUPING.getNames()) { diff --git a/nebulous/ems-core/baguette-server/pom.xml b/nebulous/ems-core/baguette-server/pom.xml index 12a70aa..fe03368 100644 --- a/nebulous/ems-core/baguette-server/pom.xml +++ b/nebulous/ems-core/baguette-server/pom.xml @@ -63,6 +63,10 @@ org.springframework * + + org.eclipse.jgit + org.eclipse.jgit + diff --git a/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/BaguetteServer.java b/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/BaguetteServer.java index 510d636..1efb26b 100644 --- a/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/BaguetteServer.java +++ b/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/BaguetteServer.java @@ -49,6 +49,7 @@ public class BaguetteServer implements InitializingBean, EventBus.EventConsumer< @Getter private final SelfHealingManager 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 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"); } diff --git a/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/ClientShellCommand.java b/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/ClientShellCommand.java index 7a9de39..672f112 100644 --- a/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/ClientShellCommand.java +++ b/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/ClientShellCommand.java @@ -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 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); + } } } diff --git a/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/NodeRegistry.java b/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/NodeRegistry.java index 05a7b8e..b7bc1f4 100644 --- a/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/NodeRegistry.java +++ b/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/NodeRegistry.java @@ -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); diff --git a/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/Sshd.java b/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/Sshd.java index 5c5da67..a5bccb6 100644 --- a/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/Sshd.java +++ b/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/Sshd.java @@ -142,6 +142,20 @@ public class Sshd { log.info("SSH server: Stopped"); } + public Map getServerConnectionInfo() { + Map.Entry 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( diff --git a/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/coordinator/NoopCoordinator.java b/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/coordinator/NoopCoordinator.java index 4f78422..a070d35 100644 --- a/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/coordinator/NoopCoordinator.java +++ b/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/coordinator/NoopCoordinator.java @@ -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; } diff --git a/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/coordinator/cluster/ClusterSelfHealing.java b/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/coordinator/cluster/ClusterSelfHealing.java index 5480a3d..d845d8a 100644 --- a/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/coordinator/cluster/ClusterSelfHealing.java +++ b/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/coordinator/cluster/ClusterSelfHealing.java @@ -27,6 +27,10 @@ public class ClusterSelfHealing { // Server-side self-healing methods // ------------------------------------------------------------------------ + public boolean isEnabled() { + return selfHealingManager.isEnabled(); + } + List getAggregatorCapableNodesInZone(IClusterZone zone) { // Get the normal nodes in the zone that can be Aggregators (i.e. Aggregator and candidates) List aggregatorCapableNodes = zone.findAggregatorCapableNodes(); diff --git a/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/coordinator/cluster/ClusteringCoordinator.java b/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/coordinator/cluster/ClusteringCoordinator.java index 7490e4b..0354b10 100644 --- a/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/coordinator/cluster/ClusteringCoordinator.java +++ b/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/coordinator/cluster/ClusteringCoordinator.java @@ -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 aggregatorCapableNodes = clusterSelfHealing.getAggregatorCapableNodesInZone(zone); - clusterSelfHealing.updateNodesSelfHealingMonitoring(zone, aggregatorCapableNodes); - clusterSelfHealing.removeResourceLimitedNodeSelfHealingMonitoring(zone, aggregatorCapableNodes); + if (clusterSelfHealing!=null && clusterSelfHealing.isEnabled()) { + List 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 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 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); + } } } diff --git a/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/properties/BaguetteServerProperties.java b/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/properties/BaguetteServerProperties.java index d8dd180..a42ddb9 100644 --- a/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/properties/BaguetteServerProperties.java +++ b/nebulous/ems-core/baguette-server/src/main/java/gr/iccs/imu/ems/baguette/server/properties/BaguetteServerProperties.java @@ -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; diff --git a/nebulous/ems-core/bin/k8s-helper.sh b/nebulous/ems-core/bin/k8s-helper.sh new file mode 100644 index 0000000..ea6ed0f --- /dev/null +++ b/nebulous/ems-core/bin/k8s-helper.sh @@ -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 diff --git a/nebulous/ems-core/bin/run.bat b/nebulous/ems-core/bin/run.bat index a65bb98..ebebd1d 100644 --- a/nebulous/ems-core/bin/run.bat +++ b/nebulous/ems-core/bin/run.bat @@ -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 diff --git a/nebulous/ems-core/bin/run.sh b/nebulous/ems-core/bin/run.sh index 9ded56e..4715598 100644 --- a/nebulous/ems-core/bin/run.sh +++ b/nebulous/ems-core/bin/run.sh @@ -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 diff --git a/nebulous/ems-core/broker-cep/src/main/java/gr/iccs/imu/ems/brokercep/BrokerCepConsumer.java b/nebulous/ems-core/broker-cep/src/main/java/gr/iccs/imu/ems/brokercep/BrokerCepConsumer.java index 8608ef1..b5e1081 100644 --- a/nebulous/ems-core/broker-cep/src/main/java/gr/iccs/imu/ems/brokercep/BrokerCepConsumer.java +++ b/nebulous/ems-core/broker-cep/src/main/java/gr/iccs/imu/ems/brokercep/BrokerCepConsumer.java @@ -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 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(); diff --git a/nebulous/ems-core/broker-cep/src/main/java/gr/iccs/imu/ems/brokercep/broker/BrokerConfig.java b/nebulous/ems-core/broker-cep/src/main/java/gr/iccs/imu/ems/brokercep/broker/BrokerConfig.java index 98e089c..287b0f7 100644 --- a/nebulous/ems-core/broker-cep/src/main/java/gr/iccs/imu/ems/brokercep/broker/BrokerConfig.java +++ b/nebulous/ems-core/broker-cep/src/main/java/gr/iccs/imu/ems/brokercep/broker/BrokerConfig.java @@ -300,7 +300,7 @@ public class BrokerConfig implements InitializingBean { List 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])); } diff --git a/nebulous/ems-core/broker-cep/src/main/java/gr/iccs/imu/ems/brokercep/broker/interceptor/SourceAddressMessageUpdateInterceptor.java b/nebulous/ems-core/broker-cep/src/main/java/gr/iccs/imu/ems/brokercep/broker/interceptor/SourceAddressMessageUpdateInterceptor.java index 0005d12..0418ede 100644 --- a/nebulous/ems-core/broker-cep/src/main/java/gr/iccs/imu/ems/brokercep/broker/interceptor/SourceAddressMessageUpdateInterceptor.java +++ b/nebulous/ems-core/broker-cep/src/main/java/gr/iccs/imu/ems/brokercep/broker/interceptor/SourceAddressMessageUpdateInterceptor.java @@ -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"); } diff --git a/nebulous/ems-core/broker-cep/src/main/java/gr/iccs/imu/ems/brokercep/cep/MathUtil.java b/nebulous/ems-core/broker-cep/src/main/java/gr/iccs/imu/ems/brokercep/cep/MathUtil.java index 09e777d..17dd828 100644 --- a/nebulous/ems-core/broker-cep/src/main/java/gr/iccs/imu/ems/brokercep/cep/MathUtil.java +++ b/nebulous/ems-core/broker-cep/src/main/java/gr/iccs/imu/ems/brokercep/cep/MathUtil.java @@ -26,6 +26,7 @@ import java.util.stream.Collectors; public class MathUtil { static { License.iConfirmNonCommercialUse("EMS-"+ NetUtil.getIpAddress()); + mXparser.disableImpliedMultiplicationMode(); } private static final Map functions = new HashMap<>(); private static final Map constants = new HashMap<>(); @@ -118,8 +119,20 @@ public class MathUtil { // Get token names List 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); } diff --git a/nebulous/ems-core/broker-cep/src/main/java/gr/iccs/imu/ems/brokercep/event/EventMap.java b/nebulous/ems-core/broker-cep/src/main/java/gr/iccs/imu/ems/brokercep/event/EventMap.java index cdb680b..eec4bdc 100644 --- a/nebulous/ems-core/broker-cep/src/main/java/gr/iccs/imu/ems/brokercep/event/EventMap.java +++ b/nebulous/ems-core/broker-cep/src/main/java/gr/iccs/imu/ems/brokercep/event/EventMap.java @@ -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 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 diff --git a/nebulous/ems-core/broker-client/src/main/java/gr/iccs/imu/ems/brokerclient/BrokerClientApp.java b/nebulous/ems-core/broker-client/src/main/java/gr/iccs/imu/ems/brokerclient/BrokerClientApp.java index a5545fd..f78bd25 100644 --- a/nebulous/ems-core/broker-client/src/main/java/gr/iccs/imu/ems/brokerclient/BrokerClientApp.java +++ b/nebulous/ems-core/broker-client/src/main/java/gr/iccs/imu/ems/brokerclient/BrokerClientApp.java @@ -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 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 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 [-P "); log.info("BrokerClientApp: client publish [ -U [-P [-T] []*"); - log.info("BrokerClientApp: client publish2 [-U [-P [-T] []*"); - log.info("BrokerClientApp: client publish3 [-U [-P [-T] []*"); + log.info("BrokerClientApp: client publish2 [-U [-P [-T] [-PP] []*"); + log.info("BrokerClientApp: client publish3 [-U [-P [-T] [-PP] []*"); log.info("BrokerClientApp: : 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: : = (use quotes if needed)"); log.info("BrokerClientApp: client receive [-U [-P "); log.info("BrokerClientApp: client subscribe [-U [-P "); diff --git a/nebulous/ems-core/broker-client/src/main/java/gr/iccs/imu/ems/brokerclient/event/EventGenerator.java b/nebulous/ems-core/broker-client/src/main/java/gr/iccs/imu/ems/brokerclient/event/EventGenerator.java index 48f384c..18c33ec 100644 --- a/nebulous/ems-core/broker-client/src/main/java/gr/iccs/imu/ems/brokerclient/event/EventGenerator.java +++ b/nebulous/ems-core/broker-client/src/main/java/gr/iccs/imu/ems/brokerclient/event/EventGenerator.java @@ -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 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 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); diff --git a/nebulous/ems-core/common/src/main/java/gr/iccs/imu/ems/common/collector/AbstractEndpointCollector.java b/nebulous/ems-core/common/src/main/java/gr/iccs/imu/ems/common/collector/AbstractEndpointCollector.java index 106bec1..12ce9fd 100644 --- a/nebulous/ems-core/common/src/main/java/gr/iccs/imu/ems/common/collector/AbstractEndpointCollector.java +++ b/nebulous/ems-core/common/src/main/java/gr/iccs/imu/ems/common/collector/AbstractEndpointCollector.java @@ -181,7 +181,7 @@ public abstract class AbstractEndpointCollector 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 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 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 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 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 { diff --git a/nebulous/ems-core/config-files/baguette-client-install/linux-yaml/baguette.yml b/nebulous/ems-core/config-files/baguette-client-install/linux-yaml/baguette.yml index 0caa8cc..0d740be 100644 --- a/nebulous/ems-core/config-files/baguette-client-install/linux-yaml/baguette.yml +++ b/nebulous/ems-core/config-files/baguette-client-install/linux-yaml/baguette.yml @@ -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 diff --git a/nebulous/ems-core/config-files/baguette-client-install/linux/baguette.json b/nebulous/ems-core/config-files/baguette-client-install/linux/baguette.json index 5c1905d..b650193 100644 --- a/nebulous/ems-core/config-files/baguette-client-install/linux/baguette.json +++ b/nebulous/ems-core/config-files/baguette-client-install/linux/baguette.json @@ -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 diff --git a/nebulous/ems-core/config-files/baguette-client/conf/baguette-client.properties.sample b/nebulous/ems-core/config-files/baguette-client/conf/baguette-client.properties.sample index b493650..a70398c 100644 --- a/nebulous/ems-core/config-files/baguette-client/conf/baguette-client.properties.sample +++ b/nebulous/ems-core/config-files/baguette-client/conf/baguette-client.properties.sample @@ -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 diff --git a/nebulous/ems-core/config-files/baguette-client/conf/baguette-client.yml b/nebulous/ems-core/config-files/baguette-client/conf/baguette-client.yml index d9d4237..62aa123 100644 --- a/nebulous/ems-core/config-files/baguette-client/conf/baguette-client.yml +++ b/nebulous/ems-core/config-files/baguette-client/conf/baguette-client.yml @@ -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 diff --git a/nebulous/ems-core/config-files/ems-server.properties.sample b/nebulous/ems-core/config-files/ems-server.properties.sample index e3b0bec..625819a 100644 --- a/nebulous/ems-core/config-files/ems-server.properties.sample +++ b/nebulous/ems-core/config-files/ems-server.properties.sample @@ -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__ diff --git a/nebulous/ems-core/config-files/ems-server.yml b/nebulous/ems-core/config-files/ems-server.yml index c8f3503..bd1d427 100644 --- a/nebulous/ems-core/config-files/ems-server.yml +++ b/nebulous/ems-core/config-files/ems-server.yml @@ -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__' diff --git a/nebulous/ems-core/config-files/secrets.properties b/nebulous/ems-core/config-files/secrets.properties index 1184da4..aac86f6 100644 --- a/nebulous/ems-core/config-files/secrets.properties +++ b/nebulous/ems-core/config-files/secrets.properties @@ -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= diff --git a/nebulous/ems-core/control-service/pom.xml b/nebulous/ems-core/control-service/pom.xml index 22bad01..6e581fe 100644 --- a/nebulous/ems-core/control-service/pom.xml +++ b/nebulous/ems-core/control-service/pom.xml @@ -20,11 +20,11 @@ EMS - Control Service - - 3.1.3 - 1.11.2 - 2.1.0 - 0.11.5 + 4.0-M4 + 3.2.2 + 1.12.4 + 2.3.0 + 0.12.5 ${maven.build.timestamp} yyyy-MM-dd HH:mm:ss @@ -33,10 +33,12 @@ esper-${esper.version}.jar - 0.43.2 + 0.44.0 ems-server + emsuser + /opt/ems-server gr.iccs.imu.ems.control.ControlServiceApplication @@ -176,13 +178,11 @@ --> - - + @@ -208,43 +208,6 @@ - - - - - net.nicoulaj.maven.plugins - checksum-maven-plugin - 1.11 - - - org.bouncycastle - * - - - org.codehaus.plexus - plexus-utils - - - com.google.guava - guava - - - commons-io - commons-io - - - - - org.codehaus.plexus - plexus-utils - 4.0.0 - - - commons-io - commons-io - 2.15.1 - - @@ -291,7 +254,7 @@ io.github.git-commit-id git-commit-id-maven-plugin - 6.0.0 + 7.0.0 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, diff --git a/nebulous/ems-core/control-service/src/main/resources/banner.txt b/nebulous/ems-core/control-service/src/main/resources/banner.txt index d246d52..0f8f260 100644 --- a/nebulous/ems-core/control-service/src/main/resources/banner.txt +++ b/nebulous/ems-core/control-service/src/main/resources/banner.txt @@ -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} \ No newline at end of file diff --git a/nebulous/ems-core/control-service/src/main/resources/public/index.html b/nebulous/ems-core/control-service/src/main/resources/public/index.html index 8e74e5f..921fc8c 100644 --- a/nebulous/ems-core/control-service/src/main/resources/public/index.html +++ b/nebulous/ems-core/control-service/src/main/resources/public/index.html @@ -432,7 +432,7 @@ Baguette Client TGZ - Baguette Client MD5 + Baguette Client SHA256 diff --git a/nebulous/ems-core/pom.xml b/nebulous/ems-core/pom.xml index 4f4a6a1..eb1b6e7 100644 --- a/nebulous/ems-core/pom.xml +++ b/nebulous/ems-core/pom.xml @@ -14,7 +14,7 @@ org.springframework.boot spring-boot-starter-parent - 3.2.1 + 3.2.3 @@ -36,10 +36,10 @@ 21 - 2.4 - 3.11.0 - 2.9.1 - 2.5.3 + 3.3.0 + 3.12.1 + 3.6.3 + 3.7.0 2.10.1 @@ -58,18 +58,18 @@ 3.0.5 - 2.11.0 + 2.12.1 1.77 - 32.1.3-jre + 33.0.0-jre 1.10.0 1.2.6 - 2.16.0 + 2.16.2 2.2 diff --git a/nebulous/ems-core/translator/src/main/java/gr/iccs/imu/ems/translate/Translator.java b/nebulous/ems-core/translator/src/main/java/gr/iccs/imu/ems/translate/Translator.java index 159646e..6b7d814 100644 --- a/nebulous/ems-core/translator/src/main/java/gr/iccs/imu/ems/translate/Translator.java +++ b/nebulous/ems-core/translator/src/main/java/gr/iccs/imu/ems/translate/Translator.java @@ -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); + } } \ No newline at end of file diff --git a/nebulous/ems-core/translator/src/main/java/gr/iccs/imu/ems/translate/model/Interval.java b/nebulous/ems-core/translator/src/main/java/gr/iccs/imu/ems/translate/model/Interval.java index cb88ddc..b62aced 100644 --- a/nebulous/ems-core/translator/src/main/java/gr/iccs/imu/ems/translate/model/Interval.java +++ b/nebulous/ems-core/translator/src/main/java/gr/iccs/imu/ems/translate/model/Interval.java @@ -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; } diff --git a/nebulous/ems-core/util/src/main/java/gr/iccs/imu/ems/util/ConfigWriteService.java b/nebulous/ems-core/util/src/main/java/gr/iccs/imu/ems/util/ConfigWriteService.java new file mode 100644 index 0000000..3d28166 --- /dev/null +++ b/nebulous/ems-core/util/src/main/java/gr/iccs/imu/ems/util/ConfigWriteService.java @@ -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 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 contentMap = new LinkedHashMap<>(); + + public void put(@NonNull String key, String value) throws IOException { + contentMap.put(key, value); + write(); + } + + public void putAll(@NonNull Map 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); + } + } +} diff --git a/nebulous/ems-core/util/src/main/java/gr/iccs/imu/ems/util/EmsConstant.java b/nebulous/ems-core/util/src/main/java/gr/iccs/imu/ems/util/EmsConstant.java index af6f6b6..9323434 100644 --- a/nebulous/ems-core/util/src/main/java/gr/iccs/imu/ems/util/EmsConstant.java +++ b/nebulous/ems-core/util/src/main/java/gr/iccs/imu/ems/util/EmsConstant.java @@ -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"; } \ No newline at end of file diff --git a/nebulous/ems-core/util/src/main/java/gr/iccs/imu/ems/util/EmsRelease.java b/nebulous/ems-core/util/src/main/java/gr/iccs/imu/ems/util/EmsRelease.java new file mode 100644 index 0000000..8f25910 --- /dev/null +++ b/nebulous/ems-core/util/src/main/java/gr/iccs/imu/ems/util/EmsRelease.java @@ -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); +} \ No newline at end of file diff --git a/nebulous/ems-core/util/src/main/java/gr/iccs/imu/ems/util/KeystoreUtil.java b/nebulous/ems-core/util/src/main/java/gr/iccs/imu/ems/util/KeystoreUtil.java index d812686..a894418 100644 --- a/nebulous/ems-core/util/src/main/java/gr/iccs/imu/ems/util/KeystoreUtil.java +++ b/nebulous/ems-core/util/src/main/java/gr/iccs/imu/ems/util/KeystoreUtil.java @@ -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 diff --git a/nebulous/ems-core/util/src/main/java/gr/iccs/imu/ems/util/NetUtil.java b/nebulous/ems-core/util/src/main/java/gr/iccs/imu/ems/util/NetUtil.java index 73d0564..ebda88b 100644 --- a/nebulous/ems-core/util/src/main/java/gr/iccs/imu/ems/util/NetUtil.java +++ b/nebulous/ems-core/util/src/main/java/gr/iccs/imu/ems/util/NetUtil.java @@ -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)); } diff --git a/nebulous/ems-core/web-admin/src/views/admin/widgets/rest-call-forms.js b/nebulous/ems-core/web-admin/src/views/admin/widgets/rest-call-forms.js index e322996..9867977 100644 --- a/nebulous/ems-core/web-admin/src/views/admin/widgets/rest-call-forms.js +++ b/nebulous/ems-core/web-admin/src/views/admin/widgets/rest-call-forms.js @@ -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 }, ] }, diff --git a/nebulous/ems-core/web-admin/src/views/common/header/header.html b/nebulous/ems-core/web-admin/src/views/common/header/header.html index 982acc5..31714e5 100644 --- a/nebulous/ems-core/web-admin/src/views/common/header/header.html +++ b/nebulous/ems-core/web-admin/src/views/common/header/header.html @@ -54,7 +54,7 @@ Broker Client .sh Baguette Client - Baguette Client MD5 + Baguette Client SHA256 Details... diff --git a/nebulous/ems-core/web-admin/src/views/common/menu-sidebar/menu-sidebar.html b/nebulous/ems-core/web-admin/src/views/common/menu-sidebar/menu-sidebar.html index 8cf700e..25e1d9c 100644 --- a/nebulous/ems-core/web-admin/src/views/common/menu-sidebar/menu-sidebar.html +++ b/nebulous/ems-core/web-admin/src/views/common/menu-sidebar/menu-sidebar.html @@ -77,10 +77,10 @@ - + - Baguette Client MD5 + Baguette Client SHA256 diff --git a/nebulous/ems-nebulous/pom.xml b/nebulous/ems-nebulous/pom.xml index d99fe65..e4e169c 100644 --- a/nebulous/ems-nebulous/pom.xml +++ b/nebulous/ems-nebulous/pom.xml @@ -15,8 +15,8 @@ ems-nebulous-plugin 1.0.0-SNAPSHOT jar - Nebulous-EMS plugin - Nebulous-EMS plugin provides metric model translator and MVV service + Nebulous EMS plugin + Nebulous EMS plugin providing the metric model translator and MVV service 21 @@ -28,20 +28,24 @@ 7.0.0-SNAPSHOT - 6.1.2 - 3.2.1 + 6.1.4 + 3.2.3 2.2 1.18.30 - 2.16.0 - 2.8.0 + 2.16.2 + 2.9.0 3.1.2.RELEASE - 7.1.3 + 8.0.0 - ../ems-core/control-service/target/docker-image.properties 0.43.2 + + + docker-image.properties + ${docker.image.tag}-nebulous + EMS Nebulous Docker Image is based on the EMS Core Docker Image @@ -117,6 +121,12 @@ provided + + eu.nebulouscloud + exn-connector-java + 1.0-SNAPSHOT + + @@ -137,6 +147,15 @@ ${jackson.version} + + + org.thymeleaf @@ -168,8 +187,109 @@ + + + nexus-nebulous + https://s01.oss.sonatype.org/content/repositories/snapshots/ + + + + + + src/main/resources + ${project.build.directory} + true + + banner.txt + + + + src/main/resources + false + + * + + + banner.txt + + + + + + + org.codehaus.mojo + buildnumber-maven-plugin + 3.2.0 + + + buildnumber-create + validate + + create + + + + buildnumber-create-metadata + validate + + create-metadata + + + + + ${project.build.directory} + + yyyy-MM-dd HH:mm:ss.SSSZ + ${project.version} + + + buildNumber + + false + false + + + + io.github.git-commit-id + git-commit-id-maven-plugin + 7.0.0 + + + get-git-info + + revision + + initialize + + + + true + ${project.build.directory}/git.properties + full + false + + + + + org.antlr + antlr4-maven-plugin + + 4.7 + + + + antlr4 + + + + + true + true + + + org.apache.maven.plugins maven-assembly-plugin @@ -206,6 +326,10 @@ tofile="${project.build.directory}/${project.artifactId}-${project.version}-jar-with-dependencies.jar" force="true"/> + + @@ -215,6 +339,32 @@ + + org.apache.maven.plugins + maven-dependency-plugin + + + get-ems-core-docker-image-properties + validate + + copy + + + + + gr.iccs.imu.ems + control-service + ${ems.version} + docker-image + properties + ${project.build.directory} + ${docker-image-properties-file} + + + + + + org.codehaus.mojo @@ -232,7 +382,7 @@ - ${docker-image-properties-file} + ${project.build.directory}/${docker-image-properties-file} @@ -260,57 +410,14 @@ --> - - - + + scm:git:http://127.0.0.1/dummy + scm:git:https://127.0.0.1/dummy + HEAD + http://127.0.0.1/dummy + + \ No newline at end of file diff --git a/nebulous/ems-nebulous/src/main/antlr4/eu/nebulous/ems/translate/analyze/antlr4/Constraints.g4 b/nebulous/ems-nebulous/src/main/antlr4/eu/nebulous/ems/translate/analyze/antlr4/Constraints.g4 new file mode 100644 index 0000000..10dad56 --- /dev/null +++ b/nebulous/ems-nebulous/src/main/antlr4/eu/nebulous/ems/translate/analyze/antlr4/Constraints.g4 @@ -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 ; diff --git a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/controller/ModelController.java b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/controller/ModelController.java new file mode 100644 index 0000000..a8f1932 --- /dev/null +++ b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/controller/ModelController.java @@ -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 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); + } +} diff --git a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/plugins/NebulousAppModelPlugin.java b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/plugins/NebulousAppModelPlugin.java new file mode 100644 index 0000000..830f1ca --- /dev/null +++ b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/plugins/NebulousAppModelPlugin.java @@ -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); + } + } +} diff --git a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/plugins/NebulousWebAdminPlugin.java b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/plugins/NebulousWebAdminPlugin.java new file mode 100644 index 0000000..ad7eb68 --- /dev/null +++ b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/plugins/NebulousWebAdminPlugin.java @@ -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(); + } +} diff --git a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/plugins/PredictionInfoBeaconPlugin.java b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/plugins/PredictionInfoBeaconPlugin.java index fc32f83..e417558 100644 --- a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/plugins/PredictionInfoBeaconPlugin.java +++ b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/plugins/PredictionInfoBeaconPlugin.java @@ -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 Set emptyIfNull(Set 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); diff --git a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/plugins/PredictionsPostTranslationPlugin.java b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/plugins/PredictionsPostTranslationPlugin.java index d46771d..16c207a 100644 --- a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/plugins/PredictionsPostTranslationPlugin.java +++ b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/plugins/PredictionsPostTranslationPlugin.java @@ -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 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 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 getSLOMetricDecompositionPayload(TranslationContext translationContext, TopicBeacon topicBeacon) { List 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 _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 getMetricsForPredictionPayload(@NonNull TranslationContext _TC, TopicBeacon topicBeacon) { HashSet 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 getMetricsForPrediction(@NonNull TranslationContext _TC) { diff --git a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/service/AbstractExternalBrokerService.java b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/service/AbstractExternalBrokerService.java new file mode 100644 index 0000000..1605dc7 --- /dev/null +++ b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/service/AbstractExternalBrokerService.java @@ -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 publishers, List 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 publishers, List 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()) + ); + } +} \ No newline at end of file diff --git a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/service/EmsBootInitializer.java b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/service/EmsBootInitializer.java new file mode 100644 index 0000000..6250827 --- /dev/null +++ b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/service/EmsBootInitializer.java @@ -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 { + 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 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); + } + } +} \ No newline at end of file diff --git a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/service/ExternalBrokerListenerService.java b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/service/ExternalBrokerListenerService.java new file mode 100644 index 0000000..1b681a8 --- /dev/null +++ b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/service/ExternalBrokerListenerService.java @@ -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 commandQueue = new ArrayBlockingQueue<>(100); + private final ObjectMapper objectMapper; + private List 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 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); + } +} \ No newline at end of file diff --git a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/service/ExternalBrokerPublisherService.java b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/service/ExternalBrokerPublisherService.java new file mode 100644 index 0000000..e3ec335 --- /dev/null +++ b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/service/ExternalBrokerPublisherService.java @@ -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 additionalTopicsMap = new HashMap<>(); + private final Gson gson = new Gson(); + private Map publishersMap; + private String applicationId; + private Set 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 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"*/)); + } +} \ No newline at end of file diff --git a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/service/ExternalBrokerServiceProperties.java b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/service/ExternalBrokerServiceProperties.java new file mode 100644 index 0000000..79ea1c9 --- /dev/null +++ b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/service/ExternalBrokerServiceProperties.java @@ -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); + } +} \ No newline at end of file diff --git a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/NebulousEmsTranslator.java b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/NebulousEmsTranslator.java index 3ae0450..46c4203 100644 --- a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/NebulousEmsTranslator.java +++ b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/NebulousEmsTranslator.java @@ -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); diff --git a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/NebulousEmsTranslatorProperties.java b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/NebulousEmsTranslatorProperties.java index c232c80..463145c 100644 --- a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/NebulousEmsTranslatorProperties.java +++ b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/NebulousEmsTranslatorProperties.java @@ -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; diff --git a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/AnalysisUtils.java b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/AnalysisUtils.java index b324ee3..5618d27 100644 --- a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/AnalysisUtils.java +++ b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/AnalysisUtils.java @@ -29,6 +29,7 @@ public class AnalysisUtils { List.of("<", "<=", "=<", ">", ">=", "=<", "=", "<>", "!="); final static List 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 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); + } + } \ No newline at end of file diff --git a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/ConstraintsHelper.java b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/ConstraintsHelper.java index 18f9f05..6a5f882 100644 --- a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/ConstraintsHelper.java +++ b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/ConstraintsHelper.java @@ -136,7 +136,7 @@ class ConstraintsHelper extends AbstractHelper { NamesKey constraintNamesKey = createNamesKey(parentNamesKey, constraintName); // Further field checks - if (! isLogicalOperator(logicalOperator)) + if (! isLogicalOperator(logicalOperator) && ! isNotOperator(logicalOperator)) throw createException("Unknown logical operator '"+logicalOperator+"' in metric constraint: "+ constraintSpec); if (composingConstraints==null || composingConstraints.isEmpty()) throw createException("At least one composing constraint must be provided in logical constraint: "+ constraintSpec); @@ -152,8 +152,7 @@ class ConstraintsHelper extends AbstractHelper { // Decompose composing constraints List composingConstraintsList = new ArrayList<>(); for (Object cc : composingConstraints) { - String sloName = cc.toString(); - Constraint childConstraint = decomposeComposingConstraint(_TC, sloName, parentNamesKey, logicalConstraint); + Constraint childConstraint = decomposeComposingConstraint(_TC, cc, parentNamesKey, logicalConstraint); composingConstraintsList.add(childConstraint); } logicalConstraint.setConstraints(composingConstraintsList); @@ -173,9 +172,13 @@ class ConstraintsHelper extends AbstractHelper { NamesKey constraintNamesKey = createNamesKey(parentNamesKey, constraintName); // Get the referenced constraints names (their SLOs' actually) - String ifSloName = getMandatorySpecField(constraintSpec, "if", "Unspecified IF part in conditional constraint '"+constraintNamesKey.name()+"': "+ constraintSpec); - String thenSloName = getMandatorySpecField(constraintSpec, "then", "Unspecified THEN part in conditional constraint '"+constraintNamesKey.name()+"': "+ constraintSpec); - String elseSloName = getSpecField(constraintSpec, "else"); +// String ifSloName = getMandatorySpecField(constraintSpec, "if", "Unspecified IF part in conditional constraint '"+constraintNamesKey.name()+"': "+ constraintSpec); +// String thenSloName = getMandatorySpecField(constraintSpec, "then", "Unspecified THEN part in conditional constraint '"+constraintNamesKey.name()+"': "+ constraintSpec); +// String elseSloName = getSpecField(constraintSpec, "else"); + // Get the constraints (spec or referenced by name (their SLOs' actually)) + Object ifSlo = getMandatorySpecFieldAsObject(constraintSpec, "if", "Unspecified IF part in conditional constraint '" + constraintNamesKey.name() + "': " + constraintSpec); + Object thenSlo = getMandatorySpecFieldAsObject(constraintSpec, "then", "Unspecified THEN part in conditional constraint '" + constraintNamesKey.name() + "': " + constraintSpec); + Object elseSlo = getSpecFieldAsObject(constraintSpec, "else"); // Update TC IfThenConstraint conditionalConstraint = IfThenConstraint.builder() @@ -185,10 +188,10 @@ class ConstraintsHelper extends AbstractHelper { _TC.getDAG().addNode(parent, conditionalConstraint); // Decompose the composing metrics - Constraint ifConstraint = decomposeComposingConstraint(_TC, ifSloName, parentNamesKey, conditionalConstraint); - Constraint thenConstraint = decomposeComposingConstraint(_TC, thenSloName, parentNamesKey, conditionalConstraint); - Constraint elseConstraint = StringUtils.isNotBlank(elseSloName) - ? decomposeComposingConstraint(_TC, elseSloName, parentNamesKey, conditionalConstraint) : null; + Constraint ifConstraint = decomposeComposingConstraint(_TC, ifSlo, parentNamesKey, conditionalConstraint); + Constraint thenConstraint = decomposeComposingConstraint(_TC, thenSlo, parentNamesKey, conditionalConstraint); + Constraint elseConstraint = elseSlo!=null + ? decomposeComposingConstraint(_TC, elseSlo, parentNamesKey, conditionalConstraint) : null; // Update the conditional (main) constraint conditionalConstraint.setIfConstraint(ifConstraint); @@ -202,6 +205,14 @@ class ConstraintsHelper extends AbstractHelper { return conditionalConstraint; } + private Constraint decomposeComposingConstraint(TranslationContext _TC, @NonNull Object o, NamesKey parentNamesKey, Constraint parentConstraint) { + if (o instanceof String sloName) + return decomposeComposingConstraint(_TC, sloName, parentNamesKey, parentConstraint); + if (o instanceof Map spec) + return decomposeComposingConstraintSpec(_TC, spec, parentNamesKey, parentConstraint); + throw createException("Invalid 'composing constraint' type: "+o.getClass()+" -- cc: "+o); + } + private Constraint decomposeComposingConstraint(TranslationContext _TC, String sloName, NamesKey parentNamesKey, Constraint parentConstraint) { // Get referenced constraint spec (its SLO spec actually) Object sloSpec = $$(_TC).allSLOs.get(NamesKey.create(parentNamesKey.parent, sloName)); @@ -215,4 +226,13 @@ class ConstraintsHelper extends AbstractHelper { // Decompose composing constraint return decomposeConstraint(_TC, constraintSpec, sloNamesKey, parentConstraint); } + + private Constraint decomposeComposingConstraintSpec(TranslationContext _TC, Map spec, NamesKey parentNamesKey, Constraint parentConstraint) { + // Construct SLO namesKey + String name = "random-"+System.currentTimeMillis(); + NamesKey namesKey = NamesKey.create(getContainerName(spec), name); + + // Decompose composing constraint + return decomposeConstraint(_TC, spec, namesKey, parentConstraint); + } } \ No newline at end of file diff --git a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/MetricsHelper.java b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/MetricsHelper.java index cc117b4..6c74434 100644 --- a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/MetricsHelper.java +++ b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/MetricsHelper.java @@ -139,9 +139,9 @@ class MetricsHelper extends AbstractHelper { _TC.getDAG().addNode(parent, rawMetric); // Process child blocks - Sensor sensor = sensorsHelper.processSensor(_TC, sensorSpec, metricNamesKey, rawMetric); Schedule schedule = outputSpec!=null ? processSchedule(outputSpec, metricNamesKey) : null; + Sensor sensor = sensorsHelper.processSensor(_TC, sensorSpec, metricNamesKey, rawMetric, schedule); // Complete TC update rawMetric.setSensor(sensor); @@ -192,8 +192,13 @@ class MetricsHelper extends AbstractHelper { List childMetricsList = new ArrayList<>(); for (String childMetricName : formulaArgs) { NamesKey childMetricNamesKey = getNamesKey(metricSpec, childMetricName); + Map childMetricSpec = asMap($$(_TC).allMetrics.get(childMetricNamesKey)); + log.trace("decomposeCompositeMetric: {}: Checking formula arg={} -- key: {}, spec: {}", metricNamesKey.name(), childMetricName, childMetricNamesKey, childMetricSpec); + if (childMetricSpec==null) + throw createException("Formula argument in metric '" + metricName + "' not found: "+childMetricName+", formula: " + formula); MetricContext childMetric = decomposeMetric( - _TC, asMap($$(_TC).allMetrics.get(childMetricNamesKey)), metricNamesKey, compositeMetric); + _TC, childMetricSpec, metricNamesKey, compositeMetric); + log.trace("decomposeCompositeMetric: {}: Checking formula arg={} -- childMetric: {}", metricNamesKey.name(), childMetricName, childMetric); if (childMetric!=null) childMetricsList.add(childMetric); } diff --git a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/NodeUpdatingHelper.java b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/NodeUpdatingHelper.java index 346b763..61b0ad3 100644 --- a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/NodeUpdatingHelper.java +++ b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/NodeUpdatingHelper.java @@ -126,7 +126,8 @@ class NodeUpdatingHelper extends AbstractHelper { Set components = $$(_TC).slosToComponentsMap.get(NamesKey.create(slo.getName())); log.trace("Updating DAG node components: slo-name={}, slo-components={}", slo.getName(), components); tlNode.getProperties().put("components", components); - updateComponents(_TC, tlNode); + if (components!=null) + updateComponents(_TC, tlNode); } else if (tlNode.getElement() instanceof BusyStatusMetricVariable bsMv) { if (log.isTraceEnabled()) log.trace("Updating DAG node components: BS-name={}, BS-components={}, BS-metric={}", diff --git a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/SensorsHelper.java b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/SensorsHelper.java index 4687299..27b3300 100644 --- a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/SensorsHelper.java +++ b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/SensorsHelper.java @@ -64,7 +64,7 @@ class SensorsHelper extends AbstractHelper implements InitializingBean { log.debug("SensorsHelper: Sensor Type Registry: {}", sensorTypePluginsRegistry); } - Sensor processSensor(TranslationContext _TC, Map sensorSpec, NamesKey parentNamesKey, NamedElement parent) { + Sensor processSensor(TranslationContext _TC, Map sensorSpec, NamesKey parentNamesKey, NamedElement parent, Schedule schedule) { // Get needed fields String sensorName = getSpecName(sensorSpec); String sensorType = getSpecField(sensorSpec, "type"); @@ -100,7 +100,7 @@ class SensorsHelper extends AbstractHelper implements InitializingBean { // Create pull or push sensor Sensor sensor; if (isPull) { - sensor = createPullSensor(sensorSpec, sensorName, sensorNamesKey, configuration); + sensor = createPullSensor(sensorSpec, sensorName, sensorNamesKey, configuration, schedule); } else { sensor = createPushSensor(sensorSpec, sensorName, sensorNamesKey, configuration); } @@ -125,7 +125,7 @@ class SensorsHelper extends AbstractHelper implements InitializingBean { return sensor; } - private Sensor createPullSensor(Map sensorSpec, String sensorName, NamesKey sensorNamesKey, LinkedHashMap configuration) { + private Sensor createPullSensor(Map sensorSpec, String sensorName, NamesKey sensorNamesKey, LinkedHashMap configuration, Schedule schedule) { // Convert Map to Map Map cfgMapWithStr = configuration.entrySet().stream() .filter(e -> e.getValue() != null) @@ -137,12 +137,16 @@ class SensorsHelper extends AbstractHelper implements InitializingBean { String intervalUnitStr = StrUtil.getWithVariations(cfgMapWithStr, "intervalUnit", "").trim(); // Get interval - int period = StrUtil.strToInt(intervalPeriodStr, - (int)properties.getSensorDefaultInterval(), (i)->i>=properties.getSensorMinInterval(), false, + long period = StrUtil.strToLong(intervalPeriodStr, + schedule!=null ? schedule.getInterval() : properties.getSensorDefaultInterval(), + (i) -> i>=properties.getSensorMinInterval(), + false, String.format(" createPullSensor(): Invalid interval period in configuration: sensor=%s, configuration=%s\n", sensorName, cfgMapWithStr)); Interval.UnitType periodUnit = StrUtil.strToEnum(intervalUnitStr, - Interval.UnitType.class, Interval.UnitType.SECONDS, false, + Interval.UnitType.class, + schedule!=null ? Interval.UnitType.valueOf(schedule.getTimeUnit().toUpperCase()) : Interval.UnitType.SECONDS, + false, String.format(" createPullSensor(): Invalid interval unit in configuration: sensor=%s, configuration=%s\n", sensorName, cfgMapWithStr)); Interval interval = Interval.builder() diff --git a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/ShorthandsExpansionHelper.java b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/ShorthandsExpansionHelper.java index 5cd4aeb..9a1f199 100644 --- a/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/ShorthandsExpansionHelper.java +++ b/nebulous/ems-nebulous/src/main/java/eu/nebulous/ems/translate/analyze/ShorthandsExpansionHelper.java @@ -12,15 +12,29 @@ import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.DocumentContext; import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.ParseContext; +import eu.nebulous.ems.translate.analyze.antlr4.ConstraintsBaseVisitor; +import eu.nebulous.ems.translate.analyze.antlr4.ConstraintsLexer; +import eu.nebulous.ems.translate.analyze.antlr4.ConstraintsParser; +import gr.iccs.imu.ems.translate.model.MetricTemplate; +import gr.iccs.imu.ems.translate.model.ValueType; +import gr.iccs.imu.ems.util.EmsConstant; +import lombok.Getter; +import lombok.NonNull; import lombok.RequiredArgsConstructor; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CodePointCharStream; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ParseTree; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import static eu.nebulous.ems.translate.analyze.AnalysisUtils.*; @@ -28,8 +42,8 @@ import static eu.nebulous.ems.translate.analyze.AnalysisUtils.*; @Service @RequiredArgsConstructor public class ShorthandsExpansionHelper { - private final static Pattern METRIC_CONSTRAINT_PATTERN = - Pattern.compile("^([^<>=!]+)([<>]=|=[<>]|<>|!=|[=><])(.+)$"); + /*private final static Pattern METRIC_CONSTRAINT_PATTERN = + Pattern.compile("^([^<>=!]+)([<>]=|=[<>]|<>|!=|[=><])(.+)$");*/ private final static Pattern METRIC_WINDOW_PATTERN = Pattern.compile("^\\s*(\\w+)\\s+(\\d+(?:\\.\\d*)?|\\.\\d+)\\s*(?:(\\w+)\\s*)?"); private final static Pattern METRIC_WINDOW_SIZE_PATTERN = @@ -39,7 +53,7 @@ public class ShorthandsExpansionHelper { private final static Pattern METRIC_OUTPUT_SCHEDULE_PATTERN = Pattern.compile("^\\s*(\\d+(?:\\.\\d*)?|\\.\\d+)\\s*(\\w+)\\s*"); private final static Pattern METRIC_SENSOR_PATTERN = - Pattern.compile("^\\s*(\\w+)\\s+(\\w+)\\s*"); + Pattern.compile("^\\s*(\\w+)\\s+(\\w[\\w\\.]+\\w)\\s*"); // ------------------------------------------------------------------------ // Methods for expanding shorthand expressions @@ -57,10 +71,25 @@ public class ShorthandsExpansionHelper { List expandedConstraints = asList(ctx .read("$.spec.*.*.requirements.*[?(@.constraint)]", List.class)).stream() .filter(item -> JsonPath.read(item, "$.constraint") instanceof String) - .peek(this::expandConstraint) +// .peek(this::expandConstraint) + .peek(this::expandConstraintExpression) .toList(); log.debug("ShorthandsExpansionHelper: Constraints expanded: {}", expandedConstraints); + // ----- Read Metric templates ----- + Map templateSpecs = new LinkedHashMap<>(); + templateSpecs.putAll( readMetricTemplate("$.templates.*", ctx) ); + templateSpecs.putAll( readMetricTemplate("$.spec.templates.*", ctx) ); + log.debug("ShorthandsExpansionHelper: Metric Templates found: {}", templateSpecs); + + // ----- Expand Metric templates in Metric specifications ----- + List expandedTemplates = asList(ctx + .read("$.spec.*.*.metrics.*[?(@.template)]", List.class)).stream() + .filter(item -> JsonPath.read(item, "$.template") instanceof String) + .peek(item -> expandTemplate(item, templateSpecs)) + .toList(); + log.debug("ShorthandsExpansionHelper: Templates expanded: {}", expandedTemplates); + // ----- Expand Metric windows ----- List expandedWindows = asList(ctx .read("$.spec.*.*.metrics.*[?(@.window)]", List.class)).stream() @@ -100,6 +129,56 @@ public class ShorthandsExpansionHelper { log.debug("ShorthandsExpansionHelper: Sensors expanded: {}", expandedSensors); } + private static Map readMetricTemplate(@NonNull String path, @NonNull DocumentContext ctx) { + try { + return asList(ctx + .read(path, List.class)).stream() + .filter(x -> x instanceof Map) + .map(x -> (Map) x) + .filter(x -> x.get("id") != null) + .collect(Collectors.toMap(x -> x.get("id").toString(), x -> x)); + } catch (Exception e) { + log.debug("ShorthandsExpansionHelper.readMetricTemplate: Not found metric templates in path: {}", path); + return Collections.emptyMap(); + } + } + + private void expandTemplate(Object spec, Map templateSpecs) { + log.debug("ShorthandsExpansionHelper.expandTemplate: {}", spec); + String templateId = JsonPath.read(spec, "$.template").toString().trim(); + Object tplSpec = templateSpecs.get(templateId); + if (tplSpec!=null) { + asMap(spec).put("template", tplSpec); + } else { + List parts = Arrays.asList(templateId.split("[ \t\r\n]+")); + if (parts.size()>=4) { + MetricTemplate newTemplate; + if (StringUtils.equalsAnyIgnoreCase(parts.get(0), "double", "float")) { + asMap(spec).put("template", MetricTemplate.builder() + .valueType(ValueType.DOUBLE_TYPE) + .lowerBound("-inf".equalsIgnoreCase(parts.get(1)) + ? Double.NEGATIVE_INFINITY : Double.parseDouble(parts.get(1))) + .upperBound(StringUtils.equalsAnyIgnoreCase(parts.get(2), "inf", "+inf") + ? Double.POSITIVE_INFINITY : Double.parseDouble(parts.get(2))) + .unit(parts.get(3)) + .build()); + } else + if (StringUtils.equalsAnyIgnoreCase(parts.get(0), "int", "integer")) { + asMap(spec).put("template", MetricTemplate.builder() + .valueType(ValueType.INT_TYPE) + .lowerBound("-inf".equalsIgnoreCase(parts.get(1)) + ? Integer.MIN_VALUE : Integer.parseInt(parts.get(1))) + .upperBound(StringUtils.equalsAnyIgnoreCase(parts.get(2), "inf", "+inf") + ? Integer.MAX_VALUE : Integer.parseInt(parts.get(2))) + .unit(parts.get(3)) + .build()); + } else + throw createException("Invalid Metric template shorthand expression: " + templateId); + } else + throw createException("Metric template id not found: " + templateId); + } + } + private void expandWindow(Object spec) { log.debug("ShorthandsExpansionHelper.expandWindow: {}", spec); String constraintStr = JsonPath.read(spec, "$.window").toString().trim(); @@ -163,15 +242,20 @@ public class ShorthandsExpansionHelper { if (matcher.matches()) { asMap(spec).put("sensor", Map.of( "type", matcher.group(1), - "config", Map.of("mapping", matcher.group(2)) + "config", Map.of( + EmsConstant.NETDATA_METRIC_KEY, matcher.group(2) + ) )); } else throw createException("Invalid metric sensor shorthand expression: "+spec); } - private void expandConstraint(Object spec) { + /*private void expandConstraint(Object spec) { log.debug("ShorthandsExpansionHelper.expandConstraint: {}", spec); String constraintStr = JsonPath.read(spec, "$.constraint").toString().trim(); + log.trace("ShorthandsExpansionHelper.expandConstraint: BEFORE removeOuterBrackets: {}", constraintStr); + constraintStr = removeOuterBrackets(constraintStr); + log.trace("ShorthandsExpansionHelper.expandConstraint: AFTER removeOuterBrackets: {}", constraintStr); Matcher matcher = METRIC_CONSTRAINT_PATTERN.matcher(constraintStr); if (matcher.matches()) { String g1 = matcher.group(1); @@ -205,4 +289,289 @@ public class ShorthandsExpansionHelper { } else throw createException("Invalid metric constraint shorthand expression: "+spec); } + + private String removeOuterBrackets(String s) { + if (s==null) return null; + s = s.trim(); + if (s.isEmpty()) return s; + while (s.startsWith("(") && s.endsWith(")") + || s.startsWith("[") && s.endsWith("]") + || s.startsWith("{") && s.endsWith("}")) + { + s = s.substring(1, s.length() - 1).trim(); + if (s.isEmpty()) return s; + } + return s; + }*/ + + public void expandConstraintExpression(Object spec) { + log.debug("ShorthandsExpansionHelper.expandConstraintExpression: {}", spec); + + // Get constraint string + String constraintStr = JsonPath.read(spec, "$.constraint").toString().trim(); + log.debug("ShorthandsExpansionHelper.expandConstraintExpression: constraint-expression: {}", constraintStr); + + // Create a CharStream that reads from standard input + CodePointCharStream input = CharStreams.fromString(constraintStr); + + // create a lexer that feeds off of input CharStream + ConstraintsLexer lexer = new ConstraintsLexer(input); + + // create a buffer of tokens pulled from the lexer + CommonTokenStream tokens = new CommonTokenStream(lexer); + + // create a parser that feeds off the tokens buffer + ConstraintsParser parser = new ConstraintsParser(tokens); + + ParseTree tree = parser.constraintExpression(); + if (log.isTraceEnabled()) + // print LISP-style tree + log.trace("ShorthandsExpansionHelper.expandConstraintExpression: parse-tree: {}", tree.toStringTree(parser)); + + // Create expanded constraint specification + ConstraintVisitor visitor = new ConstraintVisitor(); + Map map = visitor.visit(tree); + log.trace("ShorthandsExpansionHelper.expandConstraintExpression: resulting-map: {}", map); + + asMap(spec).put("constraint", map); + log.trace("ShorthandsExpansionHelper.expandConstraintExpression: Spec AFTER update: {}", spec); + } + + @Getter + @Setter + private static class ConstraintVisitor extends ConstraintsBaseVisitor> { + private boolean optimize = true; + private boolean logAnyway = false; + + @Override + public Map visitConstraintExpression(ConstraintsParser.ConstraintExpressionContext ctx) { + log(ctx, "ConstraintExpression: {}", ctx.children.size()); + return visitOrConstraint(ctx.orConstraint()); + } + + @Override + public Map visitOrConstraint(ConstraintsParser.OrConstraintContext ctx) { + if (optimize && ctx.andConstraint().size()==1) + return visitAndConstraint(ctx.andConstraint(0)); + log(ctx, "OrConstraint: {} -- and: {}", ctx.children.size(), ctx.andConstraint().size()); + ArrayList> childMapList = new ArrayList<>(); + for (ConstraintsParser.AndConstraintContext constraintContext : ctx.andConstraint()) { + Map childMap = visitAndConstraint(constraintContext); + childMapList.add(childMap); + } + return makeMap(ctx, + "type", "logical", + "operator", "or", + "constraints", childMapList + ); + } + + @Override + public Map visitAndConstraint(ConstraintsParser.AndConstraintContext ctx) { + if (optimize && ctx.constraint().size()==1) + return visitConstraint(ctx.constraint(0)); + log(ctx, "AndConstraint: {} -- cons: {}", ctx.children.size(), ctx.constraint().size()); + ArrayList> childMapList = new ArrayList<>(); + for (ConstraintsParser.ConstraintContext constraintContext : ctx.constraint()) { + Map childMap = visitConstraint(constraintContext); + childMapList.add(childMap); + } + return makeMap(ctx, + "type", "logical", + "operator", "and", + "constraints", childMapList + ); + } + + @Override + public Map visitConstraint(ConstraintsParser.ConstraintContext ctx) { + if (ctx.PARENTHESES_OPEN()!=null) { + if (!optimize) log(ctx, "Constraint: PARENTHESES"); + return visitOrConstraint(ctx.orConstraint()); + } + + if (ctx.metricConstraint()!=null) { + if (!optimize) log(ctx, "Constraint: METRIC CONSTRAINT"); + return visitMetricConstraint(ctx.metricConstraint()); + } + if (ctx.notConstraint()!=null) { + if (!optimize) log(ctx, "Constraint: NOT CONSTRAINT"); + return visitNotConstraint(ctx.notConstraint()); + } + if (ctx.conditionalConstraint()!=null) { + if (!optimize) log(ctx, "Constraint: CONDITIONAL CONSTRAINT"); + return visitConditionalConstraint(ctx.conditionalConstraint()); + } + + // error + log(ctx, true, "Constraint: ERROR: "); + for (ParseTree child : ctx.children) { + log(ctx, true, "Constraint: ERROR: --> {}", child.getText()); + } + throw new IllegalArgumentException("Unexpected constraint type encountered: "+ctx.getText()); + } + + @Override + public Map visitMetricConstraint(ConstraintsParser.MetricConstraintContext ctx) { + log(ctx, "MetricConstraint: {}", ctx.children.size()); + String metric = ctx.ID().getText(); + Double threshold = Double.parseDouble( ctx.NUM().getText() ); + String operator = ctx.comparisonOperator().getText(); + + // Check if first child is NOT the 'metric' (i.e. NUM op METRIC) + if (! ctx.getChild(0).getText().equals(metric)) { + // Inverse operator (implies METRIC inverted-op NUM) + if (! "<>".equals(operator)) { + operator = operator.contains("<") + ? operator.replace("<", ">") + : operator.replace(">", "<"); + } + } + + return makeMap(ctx, + "type", "metric", + "metric", metric, + "threshold", threshold, + "operator", operator + ); + } + + @Override + public Map visitNotConstraint(ConstraintsParser.NotConstraintContext ctx) { + log(ctx, "NotConstraint: {}", ctx.children.size()); + Map childMap = visitConstraint(ctx.constraint()); + log(ctx, "NotConstraint: --> Constraint to be NEGATED: {}", childMap); + /*return makeMap(ctx, + "type", "logical", + "operator", "not", + "constraints", List.of( childMap ) + );*/ + + childMap = negateConstraint(childMap); + log(ctx, "NotConstraint: --> Constraint NEGATED: {}", childMap); + return childMap; + } + + private Map negateConstraint(Map map) { + log.trace("ConstraintVisitor.negateConstraint: BEGIN: {}", map); + if (map==null) return null; + + String type = map.get("type").toString(); + log.trace("ConstraintVisitor.negateConstraint: type: {}", type); + if (StringUtils.isBlank(type)) + throw new IllegalArgumentException("Missing constraint type: map: "+map); + + if ("metric".equalsIgnoreCase(type)) { + String operator = map.get("operator").toString(); + log.error("ConstraintVisitor.negateConstraint: METRIC-CONSTRAINT: operator-BEFORE: {}", operator); + + // Negate comparison operator + if (StringUtils.equalsAny(operator, "=", "==")) operator = "<>"; + else if (StringUtils.equalsAny(operator, "<>")) operator = "="; + else if (StringUtils.equalsAny(operator, "<")) operator = ">="; + else if (StringUtils.equalsAny(operator, "<=", "=<")) operator = ">"; + else if (StringUtils.equalsAny(operator, ">")) operator = "<="; + else if (StringUtils.equalsAny(operator, ">=", "=>")) operator = "<"; + else + throw new IllegalArgumentException("Invalid comparison operator: "+operator); + + log.error("ConstraintVisitor.negateConstraint: METRIC-CONS: operator-AFTER: {}", operator); + map.put("operator", operator); + return map; + } else + if ("logical".equalsIgnoreCase(type)) { + String operator = map.get("operator").toString(); + log.trace("ConstraintVisitor.negateConstraint: LOGICAL-CONSTRAINT: operator-BEFORE: {}", operator); + + // Negate (second) Not-constraint + if ("not".equalsIgnoreCase(operator)) { + Map result = (Map) asList(map.get("constraints")).get(0); + log.trace("ConstraintVisitor.negateConstraint: NOT-CONSTRAINT: END: {}", result); + return result; + } + + // Negate and/or constraint + if ("and".equalsIgnoreCase(operator)) + operator = "or"; + if ("or".equalsIgnoreCase(operator)) + operator = "and"; + log.trace("ConstraintVisitor.negateConstraint: LOGICAL-CONSTRAINT: operator-AFTER: {}", operator); + map.put("operator", operator); + asList(map.get("constraints")).forEach(c -> negateConstraint((Map)c)); + return map; + } else + if ("conditional".equalsIgnoreCase(type)) { + log.trace("ConstraintVisitor.negateConstraint: CONDITIONAL-CONSTRAINT: "); + + // IF-THEN <==> IF AND THEN --- Negation: IF AND NOT(THEN) (See: https://www.math.toronto.edu/preparing-for-calculus/3_logic/we_3_negation.html) + // IF-ELSE <==> NOT(IF) AND ELSE --- Negation: NOT(IF) AND NOT(ELSE) + // IF-THEN-ELSE <==> IF AND THEN XOR NOT(IF) AND ELSE --- Negation: IF AND NOT(THEN) XOR NOT(IF) AND NOT(ELSE) ????? + + Map ifConstraint = asMap(map.get("if")); + Map thenConstraint = asMap(map.get("then")); + Map elseConstraint = asMap(map.get("else")); + ////ifConstraint = negateConstraint(ifConstraint); + thenConstraint = negateConstraint(thenConstraint); + elseConstraint = negateConstraint(elseConstraint); + map.put("if", ifConstraint); + map.put("then", thenConstraint); + map.put("else", elseConstraint); + return map; + } else + throw new IllegalArgumentException("Invalid constraint type: "+type); + } + + @Override + public Map visitConditionalConstraint(ConstraintsParser.ConditionalConstraintContext ctx) { + log(ctx, "ConditionalConstraint: {}", ctx.children.size()); + Map ifMap = visitOrConstraint(ctx.orConstraint(0)); + Map thenMap = visitOrConstraint(ctx.orConstraint(1)); + Map elseMap = ctx.orConstraint().size()>2 + ? visitOrConstraint(ctx.orConstraint(2)) : null; + + return elseMap!=null + ? makeMap(ctx, "type", "conditional", + "if", ifMap, + "then", thenMap, + "else", elseMap) + : makeMap(ctx, "type", "conditional", + "if", ifMap, + "then", thenMap); + } + + // -------------------------------------------------------------------- + + private void log(ParserRuleContext ctx, String formatter, Object...args) { + log(ctx, false, formatter, args); + } + + private void log(ParserRuleContext ctx, boolean isError, String formatter, Object...args) { + if (isError || logAnyway || log.isDebugEnabled()) { + String indent = StringUtils.repeat(' ', 2 * (ctx.depth() - 1)); + if (isError) { + log.error("ConstraintVisitor: " + indent + formatter, args); + } else if (logAnyway) { + log.warn("ConstraintVisitor: " + indent + formatter + " [::] " + ctx.getText(), args); + } else { + log.debug("ConstraintVisitor: " + indent + formatter + " [::] " + ctx.getText(), args); + } + } + } + + private Map makeMap(ParserRuleContext ctx, Object...args) { + if (args.length%2==1) + throw new IllegalArgumentException("makeMap argument number is not even: "+args.length); + LinkedHashMap map = new LinkedHashMap<>(); + for (int i=0, n=args.length; i result: {}", map); + return map; + } + } } \ No newline at end of file diff --git a/nebulous/ems-nebulous/src/main/resources/banner.txt b/nebulous/ems-nebulous/src/main/resources/banner.txt new file mode 100644 index 0000000..36bde25 --- /dev/null +++ b/nebulous/ems-nebulous/src/main/resources/banner.txt @@ -0,0 +1,7 @@ + +${AnsiColor.009} ========== EMS Nebulous Docker Image details ========== +${AnsiColor.013} :: Build Num. :: ${AnsiColor.12}@buildNumber@ +${AnsiColor.013} :: Build Date :: ${AnsiColor.12}@timestamp@ +${AnsiColor.013} :: SCM Branch :: ${AnsiColor.12}@git.branch@, at Repos.: @git.remote.origin.url@ +${AnsiColor.013} :: Image Tag :: ${AnsiColor.12}@docker.image.name@:@docker.image.tag-nebulous@ +${AnsiColor.013} :: Description :: ${AnsiColor.12}@build.description@ ${AnsiColor.DEFAULT}${AnsiStyle.NORMAL} diff --git a/nebulous/ems-nebulous/src/test/java/eu/nebulous/ems/translate/analyze/ShorthandsExpansionHelperTest.java b/nebulous/ems-nebulous/src/test/java/eu/nebulous/ems/translate/analyze/ShorthandsExpansionHelperTest.java new file mode 100644 index 0000000..3cd2a76 --- /dev/null +++ b/nebulous/ems-nebulous/src/test/java/eu/nebulous/ems/translate/analyze/ShorthandsExpansionHelperTest.java @@ -0,0 +1,32 @@ +/* + * 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.translate.analyze; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +class ShorthandsExpansionHelperTest { + @Test + void testExpandConstraint() throws IOException { + Object spec = new HashMap<>(Map.of( +// "constraint", "ZZ < 7 AND ( IF NOT XX <= 5 THEN 4 > YY Else XX <> 7 ) AND BBBB <> 8 OR CCC == 1" + "constraint", "(mean_job < 7878 AND NOT (mean_job_process_time < 12) AND\n" + + " job_process_time_instance < 1 AND time > 10 AND NOT (tstee <\n" + + " 312432))" + )); + ShorthandsExpansionHelper helper = new ShorthandsExpansionHelper(); + helper.expandConstraintExpression(spec); + log.warn(">>>> ShorthandsExpansionHelperTest:\n{}", spec); + } +} \ No newline at end of file diff --git a/nebulous/metric-model/nebulous-metric-model-schema.json b/nebulous/metric-model/nebulous-metric-model-schema.json index 98912ec..c28c4d2 100644 --- a/nebulous/metric-model/nebulous-metric-model-schema.json +++ b/nebulous/metric-model/nebulous-metric-model-schema.json @@ -1,8 +1,8 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://nebulouscloud.eu/nebulous-metric-model-v1.0.0.schema.json", + "$id": "https://nebulouscloud.eu/nebulous-metric-model-v1.0.1.schema.json", "title": "Nebulous Metric Model", - "description": "Nebulous Metric Model is a YAML file defining the monitoring requirements and details of a Nebulous multi-container application. Current version is v.1.0.0, 2023-10", + "description": "Nebulous Metric Model is a YAML file defining the monitoring requirements and details of a Nebulous multi-container application. Current version is v.1.0.1, 2023-10", "type": "object", "properties": { @@ -60,7 +60,7 @@ "id": "#/$defs/name", "description": "Element name", "type": "string", - "pattern": "[A-Za-z_][A-Za-z0-9_]*" + "pattern": "[A-Za-z_][A-Za-z0-9_\\-]*" }, "ref": { @@ -158,7 +158,7 @@ "oneOf": [ { "const": "*" - }, + }, { "type": "array", "items": { @@ -205,8 +205,8 @@ { "description": "SLO constraint in shorthand form. Only metric constraints are allowed in shorthand form. E.g. ", "type": "string", - "pattern": "^\\s*[A-Za-z_][A-Za-z0-9_]*\\s*[<>=]{1,2}\\s*\\-?\\d+(\\.\\d*)?\\s*$" - }, + "pattern": "^\\s*[A-Za-z_][A-Za-z0-9_\\-]*\\s*[<>=]{1,2}\\s*\\-?\\d+(\\.\\d*)?\\s*$" + }, { "description": "SLO metric constraint in detail form", "type": "object", @@ -291,10 +291,10 @@ "oneOf": [ { "$ref": "#/$defs/metric-raw" - }, + }, { "$ref": "#/$defs/metric-composite" - }, + }, { "$ref": "#/$defs/metric-constant" }, @@ -407,7 +407,7 @@ "oneOf": [ { "type": "string" - }, + }, { "type": "number" } @@ -455,7 +455,7 @@ "prefixItems": [ { "type": "number" - }, + }, { "type": "number" } @@ -479,8 +479,8 @@ { "description": "Sensor specification in shorthand form. Only 'netdata' sensors allowed. Form: 'netdata ', e.g. 'netdata netdata__system__cpu__user'", "type": "string", - "pattern": "^\\s*netdata\\s*[A-Za-z0-9_]+\\s*$" - }, + "pattern": "^\\s*netdata\\s*[A-Za-z_][A-Za-z0-9_\\-]*\\s*$" + }, { "description": "Sensor specification in detail form", "type": "object", @@ -518,7 +518,7 @@ "description": "Window specification in shorthand form. Form: , e.g. 'sliding 5 minutes'", "type": "string", "pattern": "^\\s*(sliding|batch)\\s+\\d+(\\.\\d*)?\\s*[AZ=a-z]+\\s*$" - }, + }, { "description": "Window specification in detail form", "type": "object", @@ -617,7 +617,7 @@ "description": "Output specification in shorthand form. Form: , e.g. 'all 30 seconds'", "type": "string", "pattern": "^\\s*((all|first|last|snapshot)\\s)?\\s*\\d+(\\.\\d*)?\\s*[A-Za-z]*\\s*$" - }, + }, { "description": "Output specification in detail form", "type": "object", @@ -634,7 +634,7 @@ "description": "Output schedule in shorthand form. Form: , e.g. '30 seconds'", "type": "string", "pattern": "^\\s*\\d+(\\.\\d*)?\\s*[A-Za-z]*\\s*$" - }, + }, { "description": "Output schedule in detail form", "$ref": "#/$defs/cep_size" @@ -666,7 +666,7 @@ "description": "Function arguments. They MUST match to those used in 'expression' both in number and names.", "type": "array", "items": { - "description": "Function argument. MUST be used in the corresponing function expression.", + "description": "Function argument. MUST be used in the corresponding function expression.", "$ref": "#/$defs/name" } } diff --git a/nebulous/metric-model/nebulous-metric-model-v1.0.1-schema.json b/nebulous/metric-model/nebulous-metric-model-v1.0.1-schema.json new file mode 100644 index 0000000..c28c4d2 --- /dev/null +++ b/nebulous/metric-model/nebulous-metric-model-v1.0.1-schema.json @@ -0,0 +1,679 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://nebulouscloud.eu/nebulous-metric-model-v1.0.1.schema.json", + "title": "Nebulous Metric Model", + "description": "Nebulous Metric Model is a YAML file defining the monitoring requirements and details of a Nebulous multi-container application. Current version is v.1.0.1, 2023-10", + + "type": "object", + "properties": { + "apiVersion": { + "const": "nebulous/v1" + }, + "kind": { + "const": "MetricModel" + }, + "metadata": { + "type": "object", + "properties": { + "name": { + "description": "The display name of the model", + "type": "string" + } + }, + "additionalProperties": true + }, + "spec": { + "description": "Lists of component and scope with metrics and/or requirements", + "type": "object", + "properties": { + "components": { + "description": "Components list", + "type": "array", + "items": { + "$ref": "#/$defs/component" + } + }, + "scopes": { + "description": "Scopes list", + "type": "array", + "items": { + "$ref": "#/$defs/scope" + } + } + }, + "additionalProperties": false + }, + "functions": { + "description": "Function definitions used in composite metric formulas", + "type": "array", + "items": { + "$ref": "#/$defs/function" + } + } + }, + "required": [ "apiVersion", "kind", "spec" ], + "additionalProperties": true, + + "$defs": { + + "name": { + "id": "#/$defs/name", + "description": "Element name", + "type": "string", + "pattern": "[A-Za-z_][A-Za-z0-9_\\-]*" + }, + + "ref": { + "id": "#/$defs/ref", + "description": "Metric reference", + "type": "string", + "pattern": "^\\s*([A-Za-z_][A-Za-z0-9_\\-]*|\\[[A-Za-z_][A-Za-z0-9_\\-]*\\])(\\s*\\.\\s*([A-Za-z_][A-Za-z0-9_\\-]*|\\[[A-Za-z_][A-Za-z0-9_\\-]*\\]))*\\s*$" + }, + + "grouping": { + "id": "#/$defs/grouping", + "description": "Metrics computation grouping", + "enum": [ + "per_instance", "instance", "PER_INSTANCE", "INSTANCE", + "per_host", "host", "PER_HOST", "HOST", + "per_zone", "zone", "PER_ZONE", "ZONE", + "per_region", "region", "PER_REGION", "REGION", + "per_cloud", "cloud", "PER_CLOUD", "CLOUD", + "global", "GLOBAL" + ] + }, + + "unit": { + "id": "#/$defs/name", + "description": "Units (empty means 'seconds')", + "enum": [ + "event", "events", + "ms", "msec", "milli", "millis", + "s", "sec", "second", "seconds", + "min", "minute", "minutes", + "hr", "hour", "hours", + "d", "day", "days", + "w", "week", "weeks", + "y", "year", "years" + ], + "default": "sec" + }, + + "cep_size": { + "description": "Window size or Schedule", + "type": "object", + "properties": { + "value": { + "description": "Size value (a positive number)", + "type": "number", + "exclusiveMinimum": 0 + }, + "unit": { + "$ref": "#/$defs/unit" + } + }, + "required": [ "value" ], + "additionalProperties": false + }, + + "component": { + "id": "#/$defs/component", + "description": "Component-specific metrics and requirements", + "type": "object", + "properties": { + "name": { + "$ref": "#/$defs/name" + }, + "requirements": { + "description": "Component requirements list", + "type": "array", + "items": { + "type": "object", + "$ref": "#/$defs/requirement" + } + }, + "metrics": { + "description": "Component metrics list", + "type": "array", + "items": { + "type": "object", + "$ref": "#/$defs/metric" + } + } + }, + "required": [ "name" ], + "additionalProperties": false + }, + + "scope": { + "id": "#/$defs/scope", + "description": "Scope-specific metrics and requirements", + "type": "object", + "properties": { + "name": { + "$ref": "#/$defs/name" + }, + "components": { + "description": "Components partitipating in scope ('*' means all components)", + "oneOf": [ + { + "const": "*" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/name" + } + } + ] + }, + "requirements": { + "description": "Scope requirements list", + "type": "array", + "items": { + "type": "object", + "$ref": "#/$defs/requirement" + } + }, + "metrics": { + "description": "Scope metrics list", + "type": "array", + "items": { + "type": "object", + "$ref": "#/$defs/metric" + } + } + }, + "required": [ "name" ], + "additionalProperties": false + }, + + "requirement": { + "id": "#/$defs/requirement", + "description": "Requirement (SLO)", + "type": "object", + "properties": { + "name": { + "$ref": "#/$defs/name" + }, + "type": { + "description": "Requirement type. Currently only SLO's are supported", + "const": "slo" + }, + "constraint": { + "oneOf": [ + { + "description": "SLO constraint in shorthand form. Only metric constraints are allowed in shorthand form. E.g. ", + "type": "string", + "pattern": "^\\s*[A-Za-z_][A-Za-z0-9_\\-]*\\s*[<>=]{1,2}\\s*\\-?\\d+(\\.\\d*)?\\s*$" + }, + { + "description": "SLO metric constraint in detail form", + "type": "object", + "properties": { + "type": { + "description": "Constraint type. Currently only 'metric' constraints are supported", + "const": "metric" + }, + "metric": { + "description": "Name of constraint metric. Must be defined in the metrics list of this component or scope", + "$ref": "#/$defs/name" + }, + "operator": { + "description": "Metric constraint comparison operator", + "enum": [ "=", "<>", "<", "<=", "=<", ">", ">=", "=>" ] + }, + "threshold": { + "description": "Metric constraint threshold (a real number)", + "type": "number" + } + }, + "required": [ "metric", "operator", "threshold" ], + "additionalProperties": false + }, + { + "description": "SLO logical constraint in detail form", + "type": "object", + "properties": { + "type": { + "description": "Constraint type. Currently only 'metric' constraints are supported", + "const": "logical" + }, + "operator": { + "description": "Logical constraint operator", + "enum": [ "and", "or" ] + }, + "constraints": { + "description": "Logical constraint sub-constraints", + "type": "array", + "items": { + "$ref": "#/$defs/name" + } + } + }, + "required": [ "type", "operator", "constraints" ], + "additionalProperties": false + }, + { + "description": "SLO conditional constraint in detail form", + "type": "object", + "properties": { + "type": { + "description": "Constraint type", + "const": "conditional" + }, + "if": { + "description": "Conditional constraint IF clause", + "$ref": "#/$defs/name" + }, + "then": { + "description": "Conditional constraint THEN clause", + "$ref": "#/$defs/name" + }, + "else": { + "description": "Conditional constraint ELSE clause", + "$ref": "#/$defs/name" + } + }, + "required": [ "type", "if", "then" ], + "additionalProperties": false + } + ] + } + }, + "required": [ "name", "constraint" ], + "additionalProperties": false + }, + + "metric": { + "id": "#/$defs/metric", + "description": "Metric related to this component or scope", + "oneOf": [ + { + "$ref": "#/$defs/metric-raw" + }, + { + "$ref": "#/$defs/metric-composite" + }, + { + "$ref": "#/$defs/metric-constant" + }, + { + "$ref": "#/$defs/metric-ref" + } + ] + }, + + "metric-raw": { + "id": "#/$defs/metric-raw", + "description": "Raw metric related to this component or scope", + "type": "object", + "properties": { + "name": { + "description": "Raw metric name", + "$ref": "#/$defs/name" + }, + "type": { + "description": "Raw metric type (always 'raw'. Can be omitted)", + "const": "raw" + }, + "template": { + "description": "Raw metric template (if omitted means any real number)", + "$ref": "#/$defs/template" + }, + "sensor": { + "description": "Raw metric sensor specification", + "$ref": "#/$defs/sensor" + }, + "output": { + "description": "Raw metric output schedule", + "$ref": "#/$defs/output" + }, + "busy-status": { + "description": "is busy-status metric?", + "type": "boolean" + } + }, + "required": [ "name", "sensor" ], + "additionalProperties": false + }, + + "metric-composite": { + "id": "#/$defs/metric-composite", + "description": "Composite related to this component or scope", + "type": "object", + "properties": { + "name": { + "description": "Composite metric name", + "$ref": "#/$defs/name" + }, + "type": { + "description": "Composite metric type (always 'composite'. Can be omitted)", + "const": "composite" + }, + "template": { + "description": "Composite metric template (if omitted means any real number)", + "$ref": "#/$defs/template" + }, + "formula": { + "description": "Composite metric formula. MUST comply with 'mathXParser' syntax", + "type": "string", + "minLength": 1 + }, + "window": { + "description": "Composite metric window specification", + "$ref": "#/$defs/window" + }, + "output": { + "description": "Composite metric output schedule", + "$ref": "#/$defs/output" + }, + "level": { + "description": "Composite metric computation level/grouping", + "$ref": "#/$defs/grouping" + }, + "grouping": { + "description": "Composite metric computation level/grouping", + "$ref": "#/$defs/grouping" + }, + "busy-status": { + "description": "is busy-status metric?", + "type": "boolean" + } + }, + "required": [ "name", "formula" ], + "additionalProperties": false + }, + + "metric-constant": { + "id": "#/$defs/metric-constant", + "description": "Constant related to this component or scope", + "type": "object", + "properties": { + "name": { + "description": "Constant name", + "$ref": "#/$defs/name" + }, + "type": { + "description": "Constant type (always 'constant'. CANNOT BE OMITTED)", + "const": "constant" + }, + "template": { + "description": "Constant metric template (if omitted means any real number)", + "$ref": "#/$defs/template" + }, + "initial": { + "description": "Constant's initial value. NOTE: If omitted errors may occur at runtime until a value is provided", + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + } + }, + "required": [ "name", "type" ], + "additionalProperties": false + }, + + "metric-ref": { + "id": "#/$defs/metric-ref", + "description": "Reference to another metric in this or another component or scope", + "type": "object", + "properties": { + "name": { + "description": "Metric name", + "$ref": "#/$defs/name" + }, + "ref": { + "description": "Reference to another metric. Form: '.', COMPONET_NAME/SCOPE_NAME can be omitted if referenced metric is in the same component or scope", + "$ref": "#/$defs/ref" + } + }, + "required": [ "name", "ref" ], + "additionalProperties": false + }, + + "template": { + "id": "#/$defs/template", + "description": "Metric template for metrics and constants. If omitted, the metric or constant value range is assumed to be any real number", + "type": "object", + "properties": { + "id": { + "description": "Template Id. Reserved for future use.", + "type": "string" + }, + "type": { + "description": "Metric template value type", + "enum": [ "int", "integer", "double", "float", "real" ] + }, + "range": { + "description": "Metric template value range. Range bounds must be of the type specified in 'type' property", + "type": "array", + "prefixItems": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "minItems": 2, + "maxItems": 2 + }, + "unit": { + "description": "Template unit (can be omitted)", + "type": "string" + } + }, + "required": [ "type" ], + "additionalProperties": false + }, + + "sensor": { + "id": "#/$defs/sensor", + "description": "Sensor specification for a raw metric", + "oneOf": [ + { + "description": "Sensor specification in shorthand form. Only 'netdata' sensors allowed. Form: 'netdata ', e.g. 'netdata netdata__system__cpu__user'", + "type": "string", + "pattern": "^\\s*netdata\\s*[A-Za-z_][A-Za-z0-9_\\-]*\\s*$" + }, + { + "description": "Sensor specification in detail form", + "type": "object", + "properties": { + "type": { + "description": "Sensor type. If omitted 'netdata' sensor is assumed", + "type": "string", + "minLength": 1 + }, + "affinity": { + "description": "Netdata sensors only. The EMS_NETDATA_TOPIC. E.g. ''netdata__system__cpu__user'", + "type": "string", + "minLength": 1 + }, + "config": { + "description": "Sensor specific configuration. Typically needed for custom sensors", + "type": "object" + }, + "install": { + "description": "Sensor installation scripts. NOTE: Reserved for future use", + "type": "object" + } + }, + "required": [ "type" ], + "additionalProperties": false + } + ] + }, + + "window": { + "id": "#/$defs/window", + "description": "Composite metric window. Windows are memory buffers retaining incoming events until a time or size condition is met.", + "oneOf": [ + { + "description": "Window specification in shorthand form. Form: , e.g. 'sliding 5 minutes'", + "type": "string", + "pattern": "^\\s*(sliding|batch)\\s+\\d+(\\.\\d*)?\\s*[AZ=a-z]+\\s*$" + }, + { + "description": "Window specification in detail form", + "type": "object", + "properties": { + "type": { + "description": "Window type: sliding or batch", + "enum": [ "batch", "sliding" ] + }, + "size": { + "description": "Window size", + "oneOf": [ + { + "description": "Window size in shorthand form. Form: , e.g. '30 seconds'", + "type": "string", + "pattern": "^\\s*\\d+(\\.\\d*)?\\s*[A-Za-z]*\\s*$" + }, + { + "description": "Window size in detail form", + "$ref": "#/$defs/cep_size" + } + ] + }, + "processing": { + "description": "Window processings list", + "type": "array", + "items": { + "$ref": "#/$defs/processing" + } + } + }, + "required": [ "type", "size" ], + "additionalProperties": false + } + ] + }, + + "processing": { + "id": "#/$defs/processing", + "description": "Window processing (i.e. group and sort, or rank)", + "properties": { + "type": { + "description": "Window processing type (group, sort, rank)", + "enum": [ "group", "sort", "rank" ] + }, + "function": { + "description": "Window processing EPL function", + "type": "string", + "minLength": 1 + }, + "criteria": { + "description": "Window processing criteria list. At least one must be provided", + "type": "array", + "items": { + "$ref": "#/$defs/criteria", + "minItems": 1 + }, + "uniqueItems": true + } + }, + "required": [ "type", "criteria" ], + "additionalProperties": false + }, + + "criteria": { + "id": "#/$defs/criteria", + "description": "Window processing criterion (for grouping, or sorting)", + "oneOf": [ + { + "$ref": "#/$defs/grouping" + }, + { + "enum": [ "timestamp", "TIMESTAMP" ] + }, + { + "type": "object", + "properties": { + "type": { + "enum": [ "custom", "CUSTOM" ] + }, + "custom": { + "type": "string", + "minLength": 1 + } + }, + "required": [ "custom" ], + "additionalProperties": false + } + ] + }, + + "output": { + "id": "#/$defs/output", + "description": "Output schedule for metrics. Controls how often the metric values are published to EMS (meanwhile they are cached in memory)", + "oneOf": [ + { + "description": "Output specification in shorthand form. Form: , e.g. 'all 30 seconds'", + "type": "string", + "pattern": "^\\s*((all|first|last|snapshot)\\s)?\\s*\\d+(\\.\\d*)?\\s*[A-Za-z]*\\s*$" + }, + { + "description": "Output specification in detail form", + "type": "object", + "properties": { + "type": { + "description": "Output type. Controls which (cached) metric values will be published to EMS (all, first, last, snapshot)", + "enum": [ "all", "first", "last", "snapshot" ], + "default": "all" + }, + "schedule": { + "description": "Output schedule", + "oneOf": [ + { + "description": "Output schedule in shorthand form. Form: , e.g. '30 seconds'", + "type": "string", + "pattern": "^\\s*\\d+(\\.\\d*)?\\s*[A-Za-z]*\\s*$" + }, + { + "description": "Output schedule in detail form", + "$ref": "#/$defs/cep_size" + } + ] + } + }, + "required": [ "schedule" ], + "additionalProperties": false + } + ] + }, + + "function": { + "id": "#/$defs/function", + "description": "Function definition. Functions can be used in composite metric formulas.", + "type": "object", + "properties": { + "name": { + "description": "Function name. MUST BE UNIQUE. It can be used in composite metric formulas.", + "$ref": "#/$defs/name" + }, + "expression": { + "description": "Function expression. MUST comply with 'mathXParser' syntax", + "type": "string", + "minLength": 1 + }, + "arguments": { + "description": "Function arguments. They MUST match to those used in 'expression' both in number and names.", + "type": "array", + "items": { + "description": "Function argument. MUST be used in the corresponding function expression.", + "$ref": "#/$defs/name" + } + } + }, + "required": [ "name", "expression", "arguments" ], + "additionalProperties": false + } + + } +} diff --git a/nebulous/pom.xml b/nebulous/pom.xml index 9512f9d..567ae67 100644 --- a/nebulous/pom.xml +++ b/nebulous/pom.xml @@ -14,7 +14,7 @@ Event Management System for Nebulous gr.iccs.imu.ems ems-nebublous - 7.8.9 + 1.0.0-SNAPSHOT pom diff --git a/playbooks/ulimit/run_setup.yaml b/playbooks/ulimit/run_setup.yaml new file mode 100644 index 0000000..1f12e33 --- /dev/null +++ b/playbooks/ulimit/run_setup.yaml @@ -0,0 +1,13 @@ +- name: Update containers.conf configuration + hosts: all + tasks: + - name: Add default_ulimits configuration to containers.conf + ansible.builtin.blockinfile: + path: "{{ ansible_user_dir }}/.config/containers/containers.conf" + block: | + [containers] + default_ulimits = [ + "nofile=5000:10000", + ] + marker: "# {mark} ANSIBLE MANAGED BLOCK for default_ulimits" + create: yes diff --git a/zuul.d/jobs.yaml b/zuul.d/jobs.yaml index e54763f..8b022bc 100644 --- a/zuul.d/jobs.yaml +++ b/zuul.d/jobs.yaml @@ -1,6 +1,7 @@ - job: name: nebulous-monitoring-build-container-images parent: nebulous-build-container-images + pre-run: playbooks/ulimit/run_setup.yaml dependencies: - name: opendev-buildset-registry soft: false @@ -22,6 +23,7 @@ - job: name: nebulous-monitoring-upload-container-images parent: nebulous-upload-container-images + pre-run: playbooks/ulimit/run_setup.yaml dependencies: - name: opendev-buildset-registry soft: false
- Baguette Client MD5 + Baguette Client SHA256