diff --git a/CHANGELOG.md b/CHANGELOG.md index 45d40e5d9..41a9bffc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to the ZSS package will be documented in this file. +## `3.5.0` +- Enhancement: ZSS can now use eureka registration to APIML, providing improved high availability over prior static registration. Static registration is still available via new property `components.zss.agent.mediationLayer.static: true` and remains default for non-HA setups. [(#811)](https://github.com/zowe/zss/pull/811) + ## `3.4.0` - Bugfix: Fixed hostname to IP address lookup for "bind-test" program. [(#801)](https://github.com/zowe/zss/pull/801) @@ -9,8 +12,8 @@ All notable changes to the ZSS package will be documented in this file. - Enhancement: Utility "zis-test" is now used to ensure that ZIS is running and accessible by Zowe before starting ZSS. (zowe/zss#764) - Enhancement: Utility "bind-test" is now available in Zowe and used to validate if each Zowe server can succeed in binding to the user requested TCPIP port at each Zowe startup. (zowe/zss#764) - Enhancement: zss handles the setting "components.apiml.enabled: true" as an alternative to enabling "gateway", "discovery", and "caching-service" components. [(#787)](https://github.com/zowe/zss/pull/787) -- Bugfix: JWK logic for single-sign-on to the APIML Gateway had the potential for a high-cpu loop when AT-TLS was enabled and the destination had read errors ([#772](https://github.com/zowe/zss/pull/772)) -- Bugfix: Stop aborting when Zowe starts on new zOS version([#781](https://github.com/zowe/zss/pull/781)) +- Bugfix: JWK logic for single-sign-on to the APIML Gateway had the potential for a high-cpu loop when AT-TLS was enabled and the destination had read errors [(#772)](https://github.com/zowe/zss/pull/772) +- Bugfix: Stop aborting when Zowe starts on new zOS version[(#781)](https://github.com/zowe/zss/pull/781) ## `3.2.0` - Enhancement: include the stub version in the generated HLASM stub (#743) diff --git a/bin/configure.sh b/bin/configure.sh index 61af36b10..e88d667c3 100755 --- a/bin/configure.sh +++ b/bin/configure.sh @@ -28,3 +28,23 @@ if [ "${ZWE_components_app_server_enabled}" != "true" ]; then _CEE_RUNOPTS="XPLINK(ON),HEAPPOOLS(OFF),HEAPPOOLS64(OFF)" ${ZWE_zowe_runtimeDirectory}/bin/utils/configmgr -script "${ZWE_zowe_runtimeDirectory}/components/zss/bin/plugins-init.js" fi +# Register ZSS as a static APIML service when components.zss.agent.mediationLayer.static is enabled. +# ZWE_components_zss_agent_mediationLayer_static defaults to false; only +# proceed when it is explicitly set to true. +if [ "${ZWE_components_zss_agent_mediationLayer_static}" = "true" ]; then + if [ -n "${ZWE_STATIC_DEFINITIONS_DIR}" ]; then + apiml_static_def="${ZWE_STATIC_DEFINITIONS_DIR}/zss.apiml_static_reg_yaml_template.${ZWE_CLI_PARAMETER_HA_INSTANCE}.yml" + apiml_static_src="${COMPONENT_HOME}/apiml-static-reg.yaml.template" + parsed_def=$( ( echo "cat <&1) + echo "${parsed_def}" > "${apiml_static_def}" + chmod 770 "${apiml_static_def}" + fi +else + if [ -n "${ZWE_STATIC_DEFINITIONS_DIR}" ]; then + apiml_static_def="${ZWE_STATIC_DEFINITIONS_DIR}/zss.apiml_static_reg_yaml_template.${ZWE_CLI_PARAMETER_HA_INSTANCE}.yml" + if [ -f "${apiml_static_def}" ]; then + rm -f "${apiml_static_def}" + fi + fi +fi + diff --git a/build/build_zss.sh b/build/build_zss.sh index 394123121..e52155eec 100755 --- a/build/build_zss.sh +++ b/build/build_zss.sh @@ -253,6 +253,7 @@ xlc \ ${ZSS}/c/passTicketService.c \ ${ZSS}/c/jwk.c \ ${ZSS}/c/safIdtService.c \ + ${ZSS}/c/eurekaClient.c \ ${GSKLIB} #then diff --git a/build/build_zss64.sh b/build/build_zss64.sh index f07d63221..b7c7558f9 100755 --- a/build/build_zss64.sh +++ b/build/build_zss64.sh @@ -252,6 +252,7 @@ if ! c89 \ ${ZSS}/c/passTicketService.c \ ${ZSS}/c/jwk.c \ ${ZSS}/c/safIdtService.c \ + ${ZSS}/c/eurekaClient.c \ ${GSKLIB} ; then extattr +p ${ZSS}/bin/zssServer64 diff --git a/c/eurekaClient.c b/c/eurekaClient.c new file mode 100644 index 000000000..abee3fdc2 --- /dev/null +++ b/c/eurekaClient.c @@ -0,0 +1,778 @@ +/* + This program and the accompanying materials are + made available under the terms of the Eclipse Public License v2.0 which accompanies + this distribution, and is available at https://www.eclipse.org/legal/epl-v20.html + + SPDX-License-Identifier: EPL-2.0 + + Copyright Contributors to the Zowe Project. +*/ + +/* + * eurekaClient.c + * + * Implements Eureka (API ML Discovery) client registration for ZSS. + * + * Registration flow (runs inside a dedicated RLETask): + * + * 1. POST /eureka/apps/ -- initial service registration + * 2. PUT /eureka/apps// -- heartbeat every 30 s + * + * On receipt of HTTP 404 from a heartbeat the task automatically re-registers. + * On any other network error the task sleeps retryIntervalSecs and tries again. + * + * All strings sent over HTTP are native EBCDIC; httpClientSessionSetRequestBody + * is called with bodyIsNativeText=TRUE so that the library translates them to + * ASCII before writing to the socket. + */ + +#ifdef METTLE +#error Metal C is not supported +#endif + +#include +#include +#include +#include +#include +#include + +#include "zowetypes.h" +#include "alloc.h" +#include "utils.h" +#include "bpxnet.h" +#include "collections.h" +#include "socketmgmt.h" +#include "le.h" +#include "logging.h" +#include "scheduling.h" +#include "json.h" +#include "httpclient.h" +#include "configmgr.h" +#ifdef USE_ZOWE_TLS +#include "tls.h" +#endif +#include "zssLogging.h" +#include "zss.h" +#include "eurekaClient.h" + +/* ------------------------------------------------------------------------- + * IPv6 helpers + * ------------------------------------------------------------------------- */ + +/* Returns true when addr is an IPv6 address (contains a colon). */ +static bool isIPv6Address(const char *addr) { + return addr && strchr(addr, ':') != NULL; +} + +/* Returns a safeMalloc'd copy of addr with every ':' replaced by '_'. + * Used to produce a legal Eureka instanceId component from an IPv6 address. */ +static char *ipv6ColonsToUnderscores(const char *addr) { + int len = strlen(addr); + char *dst = (char *)safeMalloc(len + 1, "eureka_ipv6id"); + if (!dst) return NULL; + for (int i = 0; i <= len; i++) { + dst[i] = (addr[i] == ':') ? '_' : addr[i]; + } + return dst; +} + +/* ------------------------------------------------------------------------- + * Internal context held by the RLETask + * ------------------------------------------------------------------------- */ +typedef struct EurekaClientContext_tag { + EurekaClientSettings *settings; +} EurekaClientContext; + +/* ------------------------------------------------------------------------- + * Return-code constants used internally + * ------------------------------------------------------------------------- */ +#define EUREKA_RC_OK 0 +#define EUREKA_RC_CTX_ERROR 1 +#define EUREKA_RC_SESSION_ERROR 2 +#define EUREKA_RC_STAGE_ERROR 3 +#define EUREKA_RC_SEND_ERROR 4 +#define EUREKA_RC_RECV_ERROR 5 +#define EUREKA_RC_HTTP_ERROR 6 /* unexpected HTTP status code */ +#define EUREKA_RC_NOT_FOUND 7 /* HTTP 404 -- instance not registered */ + +/* ------------------------------------------------------------------------- + * Eureka REST API defaults + * ------------------------------------------------------------------------- */ +#define EUREKA_HEARTBEAT_INTERVAL_SECS 30 +#define EUREKA_RETRY_INTERVAL_SECS 30 +#define EUREKA_RECV_TIMEOUT_SECS 10 +#define EUREKA_APPS_PATH_PREFIX "/eureka/apps/" + +/* Size of the static buffer used to build the JSON registration body. + * Eureka bodies are typically well under 4 KB. */ +#define EUREKA_BODY_BUFSIZE 4096 + +/* ------------------------------------------------------------------------- + * Forward declarations + * ------------------------------------------------------------------------- */ +static int eurekaClientTaskMain(RLETask *task); +static int doRegistration(EurekaClientSettings *settings, int *httpStatusOut); +static int doHeartbeat(EurekaClientSettings *settings, int *httpStatusOut); +static int sendEurekaRequest(EurekaClientSettings *settings, + const char *method, + const char *urlPath, + const char *body, /* NULL for heartbeat */ + int bodyLen, + int *httpStatusOut); +static int buildBodyPath(EurekaClientSettings *settings, + char *buf, int bufLen); +static int buildInstancePath(EurekaClientSettings *settings, + char *buf, int bufLen); +static int buildRegistrationBody(EurekaClientSettings *settings, + char *buf, int bufLen); + + +/* ========================================================================= + * Public API + * ========================================================================= */ + +/* + * makeEurekaClientSettings + * + * Reads from the Zowe YAML: + * - zowe.externalDomains[0] -> discoveryHost / hostName for ZSS + * - components.discovery.port -> discoveryPort + * - zssPort (passed by caller) -> port + * + * Constructs the registration URL fragments from these values. + */ +EurekaClientSettings *makeEurekaClientSettings(ShortLivedHeap *slh, + ConfigManager *configmgr, + const char *zssAddress, + int zssPort, + bool zssSecure, + const char *zssVersion +#ifdef USE_ZOWE_TLS + , TlsEnvironment *tlsEnv +#endif + ) +{ + /* ---- discovery or apiml enabled? -------------------------------- */ + bool discoveryEnabled = false; + cfgGetBooleanC(configmgr, ZSS_CFGNAME, &discoveryEnabled, + 3, "components", "discovery", "enabled"); + + bool apimlEnabled = false; + cfgGetBooleanC(configmgr, ZSS_CFGNAME, &apimlEnabled, + 3, "components", "apiml", "enabled"); + + if (!discoveryEnabled && !apimlEnabled) { + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_DEBUG, + "Eureka registration skipped: neither components.discovery.enabled " + "nor components.apiml.enabled is true\n"); + return NULL; + } + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_DEBUG, + "Eureka registration enabled (discovery=%d, apiml=%d)\n", + discoveryEnabled, apimlEnabled); + + /* ---- discovery port -------------------------------------------- */ + int discoveryPort = 0; + int portStatus = cfgGetIntC(configmgr, ZSS_CFGNAME, &discoveryPort, + 3, "components", "discovery", "port"); + if (portStatus != ZCFG_SUCCESS || discoveryPort <= 0) { + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_DEBUG, + "Eureka registration skipped: components.discovery.port not found " + "(status=%d, port=%d)\n", portStatus, discoveryPort); + return NULL; + } + + /* ---- external hostname (used both as discovery host and as this + * service's advertised hostname) ---- */ + char *externalDomain = NULL; + int hostStatus = cfgGetStringC(configmgr, ZSS_CFGNAME, &externalDomain, + 3, "zowe", "externalDomains", 0); + if (hostStatus != ZCFG_SUCCESS || externalDomain == NULL) { + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_SEVERE, + ZSS_LOG_EUREKA_NO_DOMAIN_MSG, hostStatus); + return NULL; + } + + /* ---- IPv6: prepare bracketed form for URLs and underscore form for instanceId ---- */ + bool ipv6 = isIPv6Address(externalDomain); + + /* hostForUrl: "[addr]" for IPv6, plain addr otherwise */ + char *hostForUrl; + if (ipv6) { + int bracketLen = strlen(externalDomain) + 3; /* '[' + addr + ']' + NUL */ + hostForUrl = (char *)safeMalloc(bracketLen, "eurekaHostForUrl"); + snprintf(hostForUrl, bracketLen, "[%s]", externalDomain); + } else { + hostForUrl = externalDomain; /* no transformation needed for IPv4 or hostname */ + } + + /* hostForInstanceId: colons become underscores for IPv6 */ + char *hostForInstanceId = ipv6 + ? ipv6ColonsToUnderscores(externalDomain) + : externalDomain; + + /* ---- allocate and populate settings ---- */ + EurekaClientSettings *eurekaSettings = + (EurekaClientSettings *)safeMalloc(sizeof(EurekaClientSettings), + "EurekaClientSettings"); + if (!eurekaSettings) { + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_SEVERE, + ZSS_LOG_EUREKA_ALLOC_SETTINGS_MSG); + return NULL; + } + memset(eurekaSettings, 0, sizeof(EurekaClientSettings)); + + /* discovery coordinates */ + eurekaSettings->discoveryHost = externalDomain; + eurekaSettings->discoveryPort = discoveryPort; + + /* service identity */ + eurekaSettings->serviceId = "zss"; + eurekaSettings->hostName = externalDomain; + eurekaSettings->ipAddr = zssAddress; + eurekaSettings->port = zssPort; + eurekaSettings->securePortEnabled = zssSecure; + + /* instanceId: :: (IPv6 colons -> underscores) */ + { + int idLen = strlen(hostForInstanceId) + 1 /* : */ + 3 /* ZSS */ + 1 + 10 + 1; + char *iid = (char *)safeMalloc(idLen, "EurekaInstanceId"); + snprintf(iid, idLen, "%s:zss:%d", hostForInstanceId, zssPort); + eurekaSettings->instanceId = iid; + } + + /* well-known URLs (IPv6 host already bracketed in hostForUrl) */ + { + const char *scheme = zssSecure ? "https" : "http"; + int urlBase = strlen(scheme) + 3 /* :// */ + strlen(hostForUrl) + 1 + 10 + 32; + + char *homeUrl = (char *)safeMalloc(urlBase, "EurekaHomeUrl"); + char *statusUrl = (char *)safeMalloc(urlBase, "EurekaStatusUrl"); + char *healthUrl = (char *)safeMalloc(urlBase, "EurekaHealthUrl"); + + snprintf(homeUrl, urlBase, "%s://%s:%d/", scheme, hostForUrl, zssPort); + snprintf(statusUrl, urlBase, "%s://%s:%d/info", scheme, hostForUrl, zssPort); + snprintf(healthUrl, urlBase, "%s://%s:%d/health", scheme, hostForUrl, zssPort); + + eurekaSettings->homePageUrl = homeUrl; + eurekaSettings->statusPageUrl = statusUrl; + eurekaSettings->healthCheckUrl = healthUrl; + } + + /* swagger URL: uses app-server port from components.app-server.port */ + { + int appServerPort = 0; + cfgGetIntC(configmgr, ZSS_CFGNAME, &appServerPort, + 3, "components", "app-server", "port"); + if (appServerPort > 0) { + const char *scheme = zssSecure ? "https" : "http"; + int swaggerUrlLen = strlen(scheme) + 3 + strlen(hostForUrl) + 1 + 10 + + sizeof("/api-docs/agent"); + char *swaggerUrl = (char *)safeMalloc(swaggerUrlLen, "EurekaSwaggerUrl"); + snprintf(swaggerUrl, swaggerUrlLen, "%s://%s:%d/api-docs/agent", + scheme, hostForUrl, appServerPort); + eurekaSettings->swaggerUrl = swaggerUrl; + } else { + eurekaSettings->swaggerUrl = NULL; + } + } + + eurekaSettings->version = zssVersion; + + /* timing: read heartbeatIntervalSeconds from YAML, fall back to compile-time default */ + int heartbeatIntervalSeconds = EUREKA_HEARTBEAT_INTERVAL_SECS; + cfgGetIntC(configmgr, ZSS_CFGNAME, &heartbeatIntervalSeconds, + 5, "components", "zss", "agent", "mediationLayer", "heartbeatIntervalSeconds"); + if (heartbeatIntervalSeconds <= 0) { + heartbeatIntervalSeconds = EUREKA_HEARTBEAT_INTERVAL_SECS; + } + + eurekaSettings->heartbeatIntervalSeconds = heartbeatIntervalSeconds; + + /* timing: read retryIntervalSeconds from YAML, fall back to compile-time default */ + int retryIntervalSeconds = EUREKA_RETRY_INTERVAL_SECS; + cfgGetIntC(configmgr, ZSS_CFGNAME, &retryIntervalSeconds, + 5, "components", "zss", "agent", "mediationLayer", "retryIntervalSeconds"); + if (retryIntervalSeconds <= 0) { + retryIntervalSeconds = EUREKA_RETRY_INTERVAL_SECS; + } + + eurekaSettings->retryIntervalSeconds = retryIntervalSeconds; + eurekaSettings->maxRetries = 0; /* retry indefinitely */ + +#ifdef USE_ZOWE_TLS + eurekaSettings->tlsEnv = tlsEnv; +#endif + + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_INFO, + ZSS_LOG_EUREKA_SETTINGS_MSG, + eurekaSettings->discoveryHost, eurekaSettings->discoveryPort, eurekaSettings->instanceId); + + return eurekaSettings; +} + + +/* + * startEurekaClient + * + * Spawns the background RLETask. The task pointer (EurekaClientContext) is + * attached via task->userPointer and is owned by the task. + */ +void startEurekaClient(HttpServer *server, EurekaClientSettings *settings) { + if (!settings) { + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_DEBUG, + "startEurekaClient: settings is NULL, skipping\n"); + return; + } + + EurekaClientContext *ctx = + (EurekaClientContext *)safeMalloc(sizeof(EurekaClientContext), + "EurekaClientContext"); + if (!ctx) { + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_SEVERE, + ZSS_LOG_EUREKA_ALLOC_CTX_MSG); + return; + } + ctx->settings = settings; + + RLETask *task = makeRLETask(server->base->rleAnchor, + RLE_TASK_TCB_CAPABLE | RLE_TASK_DISPOSABLE, + eurekaClientTaskMain); + if (!task) { + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_SEVERE, + ZSS_LOG_EUREKA_CREATE_TASK_MSG); + safeFree((char *)ctx, sizeof(EurekaClientContext)); + return; + } + + task->userPointer = ctx; + startRLETask(task, NULL); + + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_INFO, + ZSS_LOG_EUREKA_TASK_STARTED_MSG, + settings->discoveryHost, settings->discoveryPort, + settings->instanceId); +} + + +/* ========================================================================= + * RLETask entry point + * ========================================================================= */ + +static int eurekaClientTaskMain(RLETask *task) { + EurekaClientContext *ctx = (EurekaClientContext *)task->userPointer; + EurekaClientSettings *settings = ctx->settings; + + const int heartbeatSecs = (settings->heartbeatIntervalSeconds > 0) + ? settings->heartbeatIntervalSeconds + : EUREKA_HEARTBEAT_INTERVAL_SECS; + const int retrySecs = (settings->retryIntervalSeconds > 0) + ? settings->retryIntervalSeconds + : EUREKA_RETRY_INTERVAL_SECS; + const int warnEvery = 10; /* log a warning every N successive failures */ + + bool registered = false; + int httpStatus = 0; + int rc = EUREKA_RC_OK; + int failCount = 0; + + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_INFO, + ZSS_LOG_EUREKA_REG_LOOP_MSG); + + /* ----------------------------------------------------------------------- + * Outer loop: keep trying to (re)register and heartbeat forever. + * --------------------------------------------------------------------- */ + while (true) { + + /* ---- registration phase ---- */ + if (!registered) { + rc = doRegistration(settings, &httpStatus); + if (rc == EUREKA_RC_OK && (httpStatus == 204 || httpStatus == 200)) { + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_INFO, + ZSS_LOG_EUREKA_REGISTERED_MSG, httpStatus); + registered = true; + failCount = 0; + } else { + failCount++; + if (failCount == 1 || failCount % warnEvery == 0) { + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_WARNING, + ZSS_LOG_EUREKA_REG_FAILED_MSG, + rc, httpStatus, retrySecs, failCount); + } + sleep(retrySecs); + continue; + } + } + + /* ---- heartbeat phase ---- */ + sleep(heartbeatSecs); + + rc = doHeartbeat(settings, &httpStatus); + + if (rc == EUREKA_RC_OK && httpStatus == 200) { + /* heartbeat accepted -- all is well */ + failCount = 0; + } else if (rc == EUREKA_RC_NOT_FOUND || httpStatus == 404) { + /* Instance was evicted; force re-registration on next iteration */ + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_WARNING, + ZSS_LOG_EUREKA_HB_404_MSG); + registered = false; + } else { + failCount++; + if (failCount == 1 || failCount % warnEvery == 0) { + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_WARNING, + ZSS_LOG_EUREKA_HB_FAILED_MSG, + rc, httpStatus, failCount); + } + } + } + + /* unreachable -- task runs for the lifetime of the process */ + return 0; +} + + +/* ========================================================================= + * Registration / heartbeat helpers + * ========================================================================= */ + +/* + * doRegistration -- POST /eureka/apps/ + */ +static int doRegistration(EurekaClientSettings *settings, int *httpStatusOut) { + char urlPath[256]; + if (buildBodyPath(settings, urlPath, sizeof(urlPath)) != 0) { + return EUREKA_RC_STAGE_ERROR; + } + + char body[EUREKA_BODY_BUFSIZE]; + int bodyLen = buildRegistrationBody(settings, body, sizeof(body)); + if (bodyLen <= 0) { + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_WARNING, + ZSS_LOG_EUREKA_BODY_OVERFLOW_MSG); + return EUREKA_RC_STAGE_ERROR; + } + + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_DEBUG, + "Eureka registration body: %s\n", body); + + return sendEurekaRequest(settings, "POST", urlPath, body, bodyLen, + httpStatusOut); +} + +/* + * doHeartbeat -- PUT /eureka/apps// + */ +static int doHeartbeat(EurekaClientSettings *settings, int *httpStatusOut) { + char urlPath[512]; + if (buildInstancePath(settings, urlPath, sizeof(urlPath)) != 0) { + return EUREKA_RC_STAGE_ERROR; + } + + return sendEurekaRequest(settings, "PUT", urlPath, NULL, 0, httpStatusOut); +} + + +/* ========================================================================= + * Low-level HTTP helper + * ========================================================================= */ + +/* + * sendEurekaRequest + * + * Opens a new HTTP(S) client session, stages and sends a single request, + * reads the response, and returns. Each call creates and destroys its own + * HttpClientContext + HttpClientSession so that the task never holds an + * open socket between heartbeats. + * + * body == NULL means no request body (used for heartbeats). + */ +static int sendEurekaRequest(EurekaClientSettings *settings, + const char *method, + const char *urlPath, + const char *body, + int bodyLen, + int *httpStatusOut) { + int rc = EUREKA_RC_OK; + *httpStatusOut = 0; + + HttpClientSettings clientSettings; + memset(&clientSettings, 0, sizeof(clientSettings)); + clientSettings.host = settings->discoveryHost; + clientSettings.port = settings->discoveryPort; + clientSettings.recvTimeoutSeconds = EUREKA_RECV_TIMEOUT_SECS; + + LoggingContext *logCtx = makeLoggingContext(); + HttpClientContext *httpCtx = NULL; + HttpClientSession *session = NULL; + + do { + /* initialise HTTP context (with or without TLS) */ + int initRc = 0; +#ifdef USE_ZOWE_TLS + if (settings->tlsEnv) { + initRc = httpClientContextInitSecure(&clientSettings, logCtx, + settings->tlsEnv, &httpCtx); + } else { + initRc = httpClientContextInit(&clientSettings, logCtx, &httpCtx); + } +#else + initRc = httpClientContextInit(&clientSettings, logCtx, &httpCtx); +#endif + if (initRc != 0) { + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_DEBUG, + "Eureka %s %s: httpClientContextInit rc=%d\n", + method, urlPath, initRc); + rc = EUREKA_RC_CTX_ERROR; + break; + } + + /* open TCP (or TLS) connection */ + int sessionRc = httpClientSessionInit(httpCtx, &session); + if (sessionRc != 0) { + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_DEBUG, + "Eureka %s %s: httpClientSessionInit rc=%d\n", + method, urlPath, sessionRc); + rc = EUREKA_RC_SESSION_ERROR; + break; + } + + /* stage the request line and Host header */ + int stageRc = httpClientSessionStageRequest(httpCtx, session, + (char *)method, + (char *)urlPath, + NULL, NULL, /* no basic auth */ + NULL, 0); /* no inline body */ + if (stageRc != 0) { + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_DEBUG, + "Eureka %s %s: stageRequest rc=%d\n", method, urlPath, stageRc); + rc = EUREKA_RC_STAGE_ERROR; + break; + } + + /* add required HTTP headers */ + requestStringHeader(session->request, TRUE, "Content-Type", + "application/json"); + requestStringHeader(session->request, TRUE, "Accept", + "application/json"); + + /* attach request body when provided (POST only) */ + if (body != NULL && bodyLen > 0) { + int bodyRc = httpClientSessionSetRequestBody(httpCtx, session, + (char *)body, bodyLen, + TRUE /* bodyIsNativeText */); + if (bodyRc != 0) { + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_DEBUG, + "Eureka %s %s: setRequestBody rc=%d\n", + method, urlPath, bodyRc); + rc = EUREKA_RC_STAGE_ERROR; + break; + } + } + + /* send the request */ + int sendRc = httpClientSessionSend(httpCtx, session); + if (sendRc != 0) { + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_DEBUG, + "Eureka %s %s: send rc=%d\n", method, urlPath, sendRc); + rc = EUREKA_RC_SEND_ERROR; + break; + } + + /* receive and parse the response */ + int recvRc = httpClientSessionReceiveNativeLoop(httpCtx, session); + if (recvRc != 0) { + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_DEBUG, + "Eureka %s %s: receive rc=%d\n", method, urlPath, recvRc); + rc = EUREKA_RC_RECV_ERROR; + break; + } + + if (session->response == NULL) { + rc = EUREKA_RC_RECV_ERROR; + break; + } + + *httpStatusOut = session->response->statusCode; + + /* 404 is a special signal to the caller to re-register */ + if (*httpStatusOut == 404) { + rc = EUREKA_RC_NOT_FOUND; + } + + zowelog(NULL, LOG_COMP_ID_EUREKA, ZOWE_LOG_DEBUG, + "Eureka %s %s: HTTP %d\n", method, urlPath, *httpStatusOut); + + } while (0); + + /* clean up */ + if (session) { + httpClientSessionDestroy(session); + } + if (httpCtx) { + httpClientContextDestroy(httpCtx); + } + + return rc; +} + + +/* ========================================================================= + * URL path builders + * ========================================================================= */ + +/* + * buildBodyPath -- /eureka/apps/ + * Used for the initial POST registration. + */ +static int buildBodyPath(EurekaClientSettings *settings, + char *buf, int bufLen) { + int n = snprintf(buf, bufLen, "%s%s", + EUREKA_APPS_PATH_PREFIX, settings->serviceId); + return (n > 0 && n < bufLen) ? 0 : -1; +} + +/* + * buildInstancePath -- /eureka/apps// + * Used for PUT heartbeats. + */ +static int buildInstancePath(EurekaClientSettings *settings, + char *buf, int bufLen) { + int n = snprintf(buf, bufLen, "%s%s/%s", + EUREKA_APPS_PATH_PREFIX, + settings->serviceId, + settings->instanceId); + return (n > 0 && n < bufLen) ? 0 : -1; +} + + +/* ========================================================================= + * JSON body builder + * ========================================================================= */ + +/* + * buildRegistrationBody + * + * Produces the Eureka instance registration JSON as a native (EBCDIC on + * z/OS) null-terminated string. Returns the number of bytes written + * (excluding the NUL), or -1 if the buffer was too small. + * + * Example output (pretty-printed for readability): + * + * { + * "instance": { + * "instanceId": "myhost:zss:7557", + * "app": "zss", + * "hostName": "myhost", + * "ipAddr": "1.2.3.4", + * "vipAddress": "zss", + * "secureVipAddress":"zss", + * "status": "UP", + * "port": { "$": 7557, "@enabled": "false" }, + * "securePort": { "$": 7557, "@enabled": "true" }, + * "homePageUrl": "https://myhost:7557/", + * "statusPageUrl": "https://myhost:7557/info", + * "healthCheckUrl": "https://myhost:7557/health", + * "dataCenterInfo": { + * "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", + * "name": "MyOwn" + * }, + * "leaseInfo": { + * "renewalIntervalInSecs": 30, + * "durationInSecs": 90 + * }, + * "metadata": { + * "apiml.service.title": "Zowe ZSS", + * "apiml.service.description": "Zowe ZSS Service (z/OS Systems Services)" + * } + * } + * } + */ +static int buildRegistrationBody(EurekaClientSettings *settings, + char *buf, int bufLen) { + const char *portEnabled = settings->securePortEnabled ? "false" : "true"; + const char *securePortEnabled = settings->securePortEnabled ? "true" : "false"; + const char *version = (settings->version && settings->version[0]) ? settings->version : "1"; + int renewalSecs = (settings->heartbeatIntervalSeconds > 0) + ? settings->heartbeatIntervalSeconds + : EUREKA_HEARTBEAT_INTERVAL_SECS; + int durationSecs = renewalSecs * 3; /* Eureka convention: 3x renewal */ + + int n = snprintf(buf, bufLen, + "{" + "\"instance\":{" + "\"instanceId\":\"%s\"," + "\"app\":\"%s\"," + "\"hostName\":\"%s\"," + "\"ipAddr\":\"%s\"," + "\"vipAddress\":\"zss\"," + "\"secureVipAddress\":\"zss\"," + "\"status\":\"UP\"," + "\"port\":{\"$\":%d,\"@enabled\":\"%s\"}," + "\"securePort\":{\"$\":%d,\"@enabled\":\"%s\"}," + "\"homePageUrl\":\"%s\"," + "\"statusPageUrl\":\"%s\"," + "\"healthCheckUrl\":\"%s\"," + "\"dataCenterInfo\":{" + "\"@class\":\"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo\"," + "\"name\":\"MyOwn\"" + "}," + "\"leaseInfo\":{" + "\"renewalIntervalInSecs\":%d," + "\"durationInSecs\":%d" + "}," + "\"metadata\":{" + "\"apiml.routes.api_v1.gatewayUrl\": \"/api/v1\"," + "\"apiml.routes.api_v1.serviceUrl\": \"/\"," + "\"apiml.routes.ws_v1.gatewayUrl\": \"/ws/v1\"," + "\"apiml.routes.ws_v1.serviceUrl\": \"/\"," + "\"apiml.apiInfo.0.apiId\": \"org.zowe.zss\"," + "\"apiml.apiInfo.0.gatewayUrl\": \"api/v1\"," + "\"apiml.apiInfo.0.swaggerUrl\": \"%s\"," + "\"apiml.apiInfo.0.version\": \"%s\"," + "\"apiml.catalog.tile.id\": \"zss\"," + "\"apiml.catalog.tile.title\": \"Zowe System Services (ZSS)\"," + "\"apiml.catalog.tile.description\": \"Zowe System Services is an HTTPS and Websocket server that makes it easy to have secure, powerful web APIs backed by low-level z/OS constructs. It contains services for essential z/OS abilities such as working with files, datasets, and ESMs, but is also extensible by REST and Websocket Dataservices which are optionally present in App Framework Plugins.\"," + "\"apiml.catalog.tile.version\": \"%s\"," + "\"apiml.authentication.sso\": \"true\"," + "\"apiml.authentication.scheme\": \"zoweJwt\"," + "\"apiml.service.description\":\"Zowe System Services (ZSS)\"," + "\"apiml.service.title\":\"Zowe System Services (ZSS)\"," + "\"apiml.service.version\":\"%s\"" + "}" + "}" + "}", + settings->instanceId, + settings->serviceId, + settings->hostName, + settings->ipAddr, + settings->port, portEnabled, + settings->port, securePortEnabled, + settings->homePageUrl, + settings->statusPageUrl, + settings->healthCheckUrl, + renewalSecs, + durationSecs, + settings->swaggerUrl ? settings->swaggerUrl : "", + version, + version, + version + ); + + if (n <= 0 || n >= bufLen) { + return -1; + } + return n; +} + + +/* + This program and the accompanying materials are + made available under the terms of the Eclipse Public License v2.0 which accompanies + this distribution, and is available at https://www.eclipse.org/legal/epl-v20.html + + SPDX-License-Identifier: EPL-2.0 + + Copyright Contributors to the Zowe Project. +*/ diff --git a/c/zss.c b/c/zss.c index c4b855f5d..2e1642c46 100644 --- a/c/zss.c +++ b/c/zss.c @@ -86,6 +86,7 @@ #include "storageApiml.h" #include "passTicketService.h" #include "jwk.h" +#include "eurekaClient.h" #include "zss.h" #define PRODUCT "ZLUX" @@ -1073,6 +1074,7 @@ static void initLoggingComponents(void) { logConfigureComponent(NULL, LOG_COMP_ID_CTDS, "CT/DS", LOG_DEST_PRINTF_STDOUT, ZOWE_LOG_INFO); logConfigureComponent(NULL, LOG_COMP_ID_APIML_STORAGE, "APIML Storage", LOG_DEST_PRINTF_STDOUT, ZOWE_LOG_INFO); logConfigureComponent(NULL, LOG_COMP_ID_JWK, "JWK", LOG_DEST_PRINTF_STDOUT, ZOWE_LOG_INFO); + logConfigureComponent(NULL, LOG_COMP_ID_EUREKA, "Eureka Client", LOG_DEST_PRINTF_STDOUT, ZOWE_LOG_INFO); zowelog(NULL, LOG_COMP_ID_MVD_SERVER, ZOWE_LOG_INFO, ZSS_LOG_ZSS_START_VER_MSG, productVersion); } @@ -1872,6 +1874,14 @@ int main(int argc, char **argv){ loadWebServerConfigV2(server, configmgr, htUsers, htGroups, defaultSeconds); readWebPluginDefinitions(server, slh, pluginsDir, configmgr, apimlStorageSettings); configureJwt(server, jwkSettings); + EurekaClientSettings *eurekaSettings = + makeEurekaClientSettings(slh, configmgr, address, port, isHttpsConfigured, + productVersion +#ifdef USE_ZOWE_TLS + , tlsEnv +#endif + ); + startEurekaClient(server, eurekaSettings); installUserMappingService(server); installUnixFileContentsService(server); installUnixFileRenameService(server); diff --git a/defaults.yaml b/defaults.yaml index 83dc4a752..1725977be 100644 --- a/defaults.yaml +++ b/defaults.yaml @@ -37,4 +37,7 @@ components: enabled: ${{ components['caching-service']?.enabled || components.apiml?.enabled }} enabled: ${{ components.discovery?.enabled || components.apiml?.enabled }} serviceName: "zss" + static: ${{ (haInstances && Object.keys(haInstances).length > 1) ? false : true }} + heartbeatIntervalSeconds: 30 + retryIntervalSeconds: 10 handshakeTimeout: 30000 diff --git a/deps/zowe-common-c b/deps/zowe-common-c index 8cf887c8c..37f289699 160000 --- a/deps/zowe-common-c +++ b/deps/zowe-common-c @@ -1 +1 @@ -Subproject commit 8cf887c8c9959e3e6a0bd2544f87159dc98b4c91 +Subproject commit 37f28969948674f574065534300ce869bb152559 diff --git a/h/eurekaClient.h b/h/eurekaClient.h new file mode 100644 index 000000000..fba97afa3 --- /dev/null +++ b/h/eurekaClient.h @@ -0,0 +1,137 @@ +/* + This program and the accompanying materials are + made available under the terms of the Eclipse Public License v2.0 which accompanies + this distribution, and is available at https://www.eclipse.org/legal/epl-v20.html + + SPDX-License-Identifier: EPL-2.0 + + Copyright Contributors to the Zowe Project. +*/ + +/* + * eurekaClient.h + * + * Registers ZSS as a service with the Zowe API ML Discovery server (Eureka) + * and maintains the registration via periodic heartbeats. All network I/O + * runs inside a dedicated RLETask so the main HTTP server loop is not blocked. + * + * Typical call sequence from zss.c: + * + * EurekaClientSettings *eurekaSettings = + * makeEurekaClientSettings(slh, configmgr, address, port, isHttps, tlsEnv); + * startEurekaClient(server, eurekaSettings); + */ + +#ifndef ZSS_EUREKACLIENT_H_ +#define ZSS_EUREKACLIENT_H_ + +#ifdef METTLE +#error Metal C is not supported by eurekaClient +#endif + +#include +#include "zowetypes.h" +#include "alloc.h" +#include "httpserver.h" +#include "configmgr.h" +#ifdef USE_ZOWE_TLS +#include "tls.h" +#endif + +/* --------------------------------------------------------------------------- + * EurekaClientSettings + * + * All char* fields are expected to remain valid for the lifetime of the + * background task; the recommended approach is to allocate them from the + * server's ShortLivedHeap (slh) or from safeMalloc. + * --------------------------------------------------------------------------- */ +typedef struct EurekaClientSettings_tag { + + /* ---- Discovery server (API ML Discovery) coordinates ---- */ + char *discoveryHost; /* hostname / IP of the discovery service */ + int discoveryPort; /* TCP port of the discovery service */ + + /* ---- This service's Eureka identity ---- */ + char *serviceId; /* Eureka appId (upper-case), e.g. "ZSS" */ + char *instanceId; /* :: */ + char *hostName; /* hostname this service is reachable at */ + const char *ipAddr; /* IP address this service is reachable at */ + int port; /* TCP port this service listens on */ + bool securePortEnabled; /* true when TLS is in use for this service */ + + /* ---- Well-known URL suffixes for Eureka metadata ---- */ + char *homePageUrl; /* e.g. "https://:/" */ + char *statusPageUrl; /* e.g. "https://:/info" */ + char *healthCheckUrl; /* e.g. "https://:/health" */ + char *swaggerUrl; /* e.g. "https://:/api-docs/agent" */ + + /* ---- Service version reported in Eureka metadata ---- */ + const char *version; /* e.g. "2.17.0+20260101" */ + + /* ---- Timing ---- */ + int heartbeatIntervalSeconds; /* PUT heartbeat interval (default: 30 s) */ + int retryIntervalSeconds; /* retry after failed req (default: 30 s) */ + int maxRetries; /* 0 means retry indefinitely */ + +#ifdef USE_ZOWE_TLS + /* TLS environment used to connect to the discovery server */ + TlsEnvironment *tlsEnv; +#endif + +} EurekaClientSettings; + +/* --------------------------------------------------------------------------- + * makeEurekaClientSettings + * + * Reads the Zowe YAML via configmgr to determine the discovery server + * host/port, then constructs an EurekaClientSettings that describes this + * ZSS instance. + * + * Parameters: + * slh - short-lived heap used for string allocations + * configmgr - the live ConfigManager instance + * zssAddress - the IP/hostname ZSS is bound to (from agent settings) + * zssPort - the TCP port ZSS is listening on + * zssSecure - true when ZSS is serving HTTPS + * zssVersion - product version string (e.g. productVersion from zss.c) + * tlsEnv - TLS environment for outbound connections (may be NULL) + * + * Returns NULL when the discovery component is not enabled or required + * configuration values are absent. + * --------------------------------------------------------------------------- */ +EurekaClientSettings *makeEurekaClientSettings(ShortLivedHeap *slh, + ConfigManager *configmgr, + const char *zssAddress, + int zssPort, + bool zssSecure, + const char *zssVersion +#ifdef USE_ZOWE_TLS + , TlsEnvironment *tlsEnv +#endif + ); + +/* --------------------------------------------------------------------------- + * startEurekaClient + * + * Creates a disposable RLETask that: + * 1. Posts a registration request to /eureka/apps/. + * 2. Sends periodic PUT heartbeats to keep the registration alive. + * + * The task retries on transient network errors according to + * settings->retryIntervalSecs. It runs until the process exits. + * + * Safe to call with settings == NULL (no-op). + * --------------------------------------------------------------------------- */ +void startEurekaClient(HttpServer *server, EurekaClientSettings *settings); + +#endif /* ZSS_EUREKACLIENT_H_ */ + +/* + This program and the accompanying materials are + made available under the terms of the Eclipse Public License v2.0 which accompanies + this distribution, and is available at https://www.eclipse.org/legal/epl-v20.html + + SPDX-License-Identifier: EPL-2.0 + + Copyright Contributors to the Zowe Project. +*/ diff --git a/h/zssLogging.h b/h/zssLogging.h index 74a0f400f..14b644341 100644 --- a/h/zssLogging.h +++ b/h/zssLogging.h @@ -42,6 +42,7 @@ #define LOG_COMP_ID_DATASERVICE 0x008F000300050000 #define LOG_COMP_ID_APIML_STORAGE 0x008F000300060000 #define LOG_COMP_ID_JWK 0x008F000300070000 +#define LOG_COMP_ID_EUREKA 0x008F000300080000 #define ZSS_LOGGING_COMPONENTS_MAP(zssLogComponents)\ static const LogComponentsMap zssLogComponents[] = {\ @@ -52,6 +53,7 @@ {LOG_COMP_ID_DATASERVICE, "dataservice"},\ {LOG_COMP_ID_APIML_STORAGE, "apimlstorage"},\ {LOG_COMP_ID_JWK, "jwk"},\ + {LOG_COMP_ID_EUREKA, "eureka"},\ {0, NULL}\ }; @@ -527,6 +529,80 @@ bool isLogLevelValid(int level); #define ZSS_LOG_JWK_RETRY_MSG_TEXT "Failed to get JWK. rc=%s (%d), rsn=%s (%d). Retry in %d seconds\n" #define ZSS_LOG_JWK_RETRY_MSG ZSS_LOG_JWK_RETRY_MSG_ID" "ZSS_LOG_JWK_RETRY_MSG_TEXT +/* Eureka client */ + +#ifndef ZSS_LOG_EUREKA_SETTINGS_MSG_ID +#define ZSS_LOG_EUREKA_SETTINGS_MSG_ID ZSS_LOG_MSG_PRFX"1700I" +#endif +#define ZSS_LOG_EUREKA_SETTINGS_MSG_TEXT "Eureka settings: discovery=%s:%d instanceId=%s\n" +#define ZSS_LOG_EUREKA_SETTINGS_MSG ZSS_LOG_EUREKA_SETTINGS_MSG_ID" "ZSS_LOG_EUREKA_SETTINGS_MSG_TEXT + +#ifndef ZSS_LOG_EUREKA_TASK_STARTED_MSG_ID +#define ZSS_LOG_EUREKA_TASK_STARTED_MSG_ID ZSS_LOG_MSG_PRFX"1701I" +#endif +#define ZSS_LOG_EUREKA_TASK_STARTED_MSG_TEXT "Eureka client task started (discovery=%s:%d, instanceId=%s)\n" +#define ZSS_LOG_EUREKA_TASK_STARTED_MSG ZSS_LOG_EUREKA_TASK_STARTED_MSG_ID" "ZSS_LOG_EUREKA_TASK_STARTED_MSG_TEXT + +#ifndef ZSS_LOG_EUREKA_REG_LOOP_MSG_ID +#define ZSS_LOG_EUREKA_REG_LOOP_MSG_ID ZSS_LOG_MSG_PRFX"1702I" +#endif +#define ZSS_LOG_EUREKA_REG_LOOP_MSG_TEXT "Eureka client task: beginning registration loop\n" +#define ZSS_LOG_EUREKA_REG_LOOP_MSG ZSS_LOG_EUREKA_REG_LOOP_MSG_ID" "ZSS_LOG_EUREKA_REG_LOOP_MSG_TEXT + +#ifndef ZSS_LOG_EUREKA_REGISTERED_MSG_ID +#define ZSS_LOG_EUREKA_REGISTERED_MSG_ID ZSS_LOG_MSG_PRFX"1703I" +#endif +#define ZSS_LOG_EUREKA_REGISTERED_MSG_TEXT "Eureka: registered successfully (HTTP %d)\n" +#define ZSS_LOG_EUREKA_REGISTERED_MSG ZSS_LOG_EUREKA_REGISTERED_MSG_ID" "ZSS_LOG_EUREKA_REGISTERED_MSG_TEXT + +#ifndef ZSS_LOG_EUREKA_ALLOC_SETTINGS_MSG_ID +#define ZSS_LOG_EUREKA_ALLOC_SETTINGS_MSG_ID ZSS_LOG_MSG_PRFX"1704E" +#endif +#define ZSS_LOG_EUREKA_ALLOC_SETTINGS_MSG_TEXT "Eureka: failed to allocate settings\n" +#define ZSS_LOG_EUREKA_ALLOC_SETTINGS_MSG ZSS_LOG_EUREKA_ALLOC_SETTINGS_MSG_ID" "ZSS_LOG_EUREKA_ALLOC_SETTINGS_MSG_TEXT + +#ifndef ZSS_LOG_EUREKA_ALLOC_CTX_MSG_ID +#define ZSS_LOG_EUREKA_ALLOC_CTX_MSG_ID ZSS_LOG_MSG_PRFX"1705E" +#endif +#define ZSS_LOG_EUREKA_ALLOC_CTX_MSG_TEXT "Eureka: failed to allocate client context\n" +#define ZSS_LOG_EUREKA_ALLOC_CTX_MSG ZSS_LOG_EUREKA_ALLOC_CTX_MSG_ID" "ZSS_LOG_EUREKA_ALLOC_CTX_MSG_TEXT + +#ifndef ZSS_LOG_EUREKA_CREATE_TASK_MSG_ID +#define ZSS_LOG_EUREKA_CREATE_TASK_MSG_ID ZSS_LOG_MSG_PRFX"1706E" +#endif +#define ZSS_LOG_EUREKA_CREATE_TASK_MSG_TEXT "Eureka: failed to create background RLETask\n" +#define ZSS_LOG_EUREKA_CREATE_TASK_MSG ZSS_LOG_EUREKA_CREATE_TASK_MSG_ID" "ZSS_LOG_EUREKA_CREATE_TASK_MSG_TEXT + +#ifndef ZSS_LOG_EUREKA_REG_FAILED_MSG_ID +#define ZSS_LOG_EUREKA_REG_FAILED_MSG_ID ZSS_LOG_MSG_PRFX"1707W" +#endif +#define ZSS_LOG_EUREKA_REG_FAILED_MSG_TEXT "Eureka: registration failed (rc=%d, HTTP %d), retry in %d s (attempt %d)\n" +#define ZSS_LOG_EUREKA_REG_FAILED_MSG ZSS_LOG_EUREKA_REG_FAILED_MSG_ID" "ZSS_LOG_EUREKA_REG_FAILED_MSG_TEXT + +#ifndef ZSS_LOG_EUREKA_HB_404_MSG_ID +#define ZSS_LOG_EUREKA_HB_404_MSG_ID ZSS_LOG_MSG_PRFX"1708W" +#endif +#define ZSS_LOG_EUREKA_HB_404_MSG_TEXT "Eureka: heartbeat returned 404; will re-register\n" +#define ZSS_LOG_EUREKA_HB_404_MSG ZSS_LOG_EUREKA_HB_404_MSG_ID" "ZSS_LOG_EUREKA_HB_404_MSG_TEXT + +#ifndef ZSS_LOG_EUREKA_HB_FAILED_MSG_ID +#define ZSS_LOG_EUREKA_HB_FAILED_MSG_ID ZSS_LOG_MSG_PRFX"1709W" +#endif +#define ZSS_LOG_EUREKA_HB_FAILED_MSG_TEXT "Eureka: heartbeat failed (rc=%d, HTTP %d), attempt %d\n" +#define ZSS_LOG_EUREKA_HB_FAILED_MSG ZSS_LOG_EUREKA_HB_FAILED_MSG_ID" "ZSS_LOG_EUREKA_HB_FAILED_MSG_TEXT + +#ifndef ZSS_LOG_EUREKA_BODY_OVERFLOW_MSG_ID +#define ZSS_LOG_EUREKA_BODY_OVERFLOW_MSG_ID ZSS_LOG_MSG_PRFX"1710W" +#endif +#define ZSS_LOG_EUREKA_BODY_OVERFLOW_MSG_TEXT "Eureka: registration body buffer too small\n" +#define ZSS_LOG_EUREKA_BODY_OVERFLOW_MSG ZSS_LOG_EUREKA_BODY_OVERFLOW_MSG_ID" "ZSS_LOG_EUREKA_BODY_OVERFLOW_MSG_TEXT + +#ifndef ZSS_LOG_EUREKA_NO_DOMAIN_MSG_ID +#define ZSS_LOG_EUREKA_NO_DOMAIN_MSG_ID ZSS_LOG_MSG_PRFX"1711E" +#endif +#define ZSS_LOG_EUREKA_NO_DOMAIN_MSG_TEXT "Eureka registration skipped: zowe.externalDomains[0] not found (status=%d)\n" +#define ZSS_LOG_EUREKA_NO_DOMAIN_MSG ZSS_LOG_EUREKA_NO_DOMAIN_MSG_ID" "ZSS_LOG_EUREKA_NO_DOMAIN_MSG_TEXT + #endif /* MVD_H_ZSSLOGGING_H_ */ diff --git a/manifest.template.yaml b/manifest.template.yaml index 1227158e0..cd836da4d 100644 --- a/manifest.template.yaml +++ b/manifest.template.yaml @@ -30,8 +30,4 @@ commands: install: bin/internal-install.sh start: bin/start.sh configure: bin/configure.sh - validate: bin/validate.sh -# we do not specify encoding here because its already tagged ascii -apimlServices: - static: - - file: apiml-static-reg.yaml.template + validate: bin/validate.sh \ No newline at end of file diff --git a/schemas/zss-config.json b/schemas/zss-config.json index 70ee28155..0295ab5f3 100644 --- a/schemas/zss-config.json +++ b/schemas/zss-config.json @@ -86,6 +86,21 @@ "description": "Controls whether the app-server storage API can store in the caching service" } } + }, + "heartbeatIntervalSeconds": { + "type": "number", + "default": 30, + "description": "Interval in seconds between Eureka heartbeat renewal requests sent to the API Mediation Layer Discovery service" + }, + "retryIntervalSeconds": { + "type": "number", + "default": 10, + "description": "Interval in seconds to wait before retrying a failed Eureka registration or heartbeat request" + }, + "static": { + "type": "boolean", + "default": false, + "description": "When true, ZSS uses APIML static registration instead of APIML eureka registration." } } }, @@ -384,6 +399,10 @@ "description": "Controls logging of JWT library functions.", "$ref": "#/$defs/logLevel" }, + "_zss.eureka": { + "description": "Controls logging of APIML eureka registration.", + "$ref": "#/$defs/logLevel" + }, "^.*$": { "$ref": "#/$defs/logLevel" }