diff --git a/.gitignore b/.gitignore index c8d604a148e..4812accc5fb 100644 --- a/.gitignore +++ b/.gitignore @@ -47,7 +47,7 @@ m4/lt~obsolete.m4 /build* configs/records.yaml.default -configs/storage.yaml.default +configs/storage.config.default doc/_build doc/docbuild/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 49831bfebf9..a3fee3bf47d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) endif() cmake_minimum_required(VERSION 3.20..3.27) -project(ats VERSION 11.0.0) +project(ats VERSION 10.2.0) set(TS_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) set(TS_VERSION_MINOR ${PROJECT_VERSION_MINOR}) @@ -541,8 +541,6 @@ check_symbol_exists(SSL_error_description "openssl/ssl.h" HAVE_SSL_ERROR_DESCRIP check_symbol_exists(SSL_CTX_set_ciphersuites "openssl/ssl.h" TS_USE_TLS_SET_CIPHERSUITES) check_symbol_exists(SSL_CTX_set_keylog_callback "openssl/ssl.h" TS_HAS_TLS_KEYLOGGING) check_symbol_exists(SSL_CTX_set_tlsext_ticket_key_cb "openssl/ssl.h" HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_CB) -check_symbol_exists(SSL_CTX_add_cert_compression_alg "openssl/ssl.h" HAVE_SSL_CTX_ADD_CERT_COMPRESSION_ALG) -check_symbol_exists(SSL_CTX_set1_cert_comp_preference "openssl/ssl.h" HAVE_SSL_CTX_SET1_CERT_COMP_PREFERENCE) check_symbol_exists(SSL_get_all_async_fds openssl/ssl.h TS_USE_TLS_ASYNC) check_symbol_exists(OSSL_PARAM_construct_end "openssl/params.h" HAVE_OSSL_PARAM_CONSTRUCT_END) check_symbol_exists(TLS1_3_VERSION "openssl/ssl.h" TS_USE_TLS13) @@ -761,14 +759,13 @@ file(REMOVE "${PROJECT_SOURCE_DIR}/include/tscore/ink_config.h") file(REMOVE "${PROJECT_SOURCE_DIR}/include/ts/apidefs.h") file(REMOVE "${PROJECT_SOURCE_DIR}/include/ink_autoconf.h") -configure_file(configs/storage.yaml.default.in configs/storage.yaml.default) +configure_file(configs/storage.config.default.in configs/storage.config.default) configure_file(configs/records.yaml.default.in configs/records.yaml.default) configure_file(include/tscore/ink_config.h.cmake.in include/tscore/ink_config.h) configure_file(include/ts/apidefs.h.in include/ts/apidefs.h) add_subdirectory(src/tscpp/api) add_subdirectory(src/tsutil) -add_subdirectory(src/config) add_subdirectory(src/tscore) add_subdirectory(src/records) add_subdirectory(src/iocore) diff --git a/cmake/Findbrotli.cmake b/cmake/Findbrotli.cmake index bf12a0ab7ed..8a2c63b4a80 100644 --- a/cmake/Findbrotli.cmake +++ b/cmake/Findbrotli.cmake @@ -21,28 +21,23 @@ # # brotli_FOUND # brotlicommon_LIBRARY -# brotlidec_LIBRARY # brotlienc_LIBRARY # brotli_INCLUDE_DIRS # # and the following imported targets # # brotli::brotlicommon -# brotli::brotlidec # brotli::brotlienc # find_library(brotlicommon_LIBRARY NAMES brotlicommon) -find_library(brotlidec_LIBRARY NAMES brotlidec) find_library(brotlienc_LIBRARY NAMES brotlienc) find_path(brotli_INCLUDE_DIR NAMES brotli/encode.h) -mark_as_advanced(brotli_FOUND brotlicommon_LIBRARY brotlidec_LIBRARY brotlienc_LIBRARY brotli_INCLUDE_DIR) +mark_as_advanced(brotli_FOUND brotlicommon_LIBRARY brotlienc_LIBRARY brotli_INCLUDE_DIR) include(FindPackageHandleStandardArgs) -find_package_handle_standard_args( - brotli REQUIRED_VARS brotlicommon_LIBRARY brotlidec_LIBRARY brotlienc_LIBRARY brotli_INCLUDE_DIR -) +find_package_handle_standard_args(brotli REQUIRED_VARS brotlicommon_LIBRARY brotlienc_LIBRARY brotli_INCLUDE_DIR) if(brotli_FOUND) set(brotli_INCLUDE_DIRS "${brotli_INCLUDE_DIR}") @@ -54,12 +49,6 @@ if(brotli_FOUND AND NOT TARGET brotli::brotlicommon) target_link_libraries(brotli::brotlicommon INTERFACE "${brotlicommon_LIBRARY}") endif() -if(brotli_FOUND AND NOT TARGET brotli::brotlidec) - add_library(brotli::brotlidec INTERFACE IMPORTED) - target_include_directories(brotli::brotlidec INTERFACE ${brotli_INCLUDE_DIRS}) - target_link_libraries(brotli::brotlidec INTERFACE brotli::brotlicommon "${brotlidec_LIBRARY}") -endif() - if(brotli_FOUND AND NOT TARGET brotli::brotlienc) add_library(brotli::brotlienc INTERFACE IMPORTED) target_include_directories(brotli::brotlienc INTERFACE ${brotli_INCLUDE_DIRS}) diff --git a/configs/records.yaml.default.in b/configs/records.yaml.default.in index bf9e51d3869..225ae802399 100644 --- a/configs/records.yaml.default.in +++ b/configs/records.yaml.default.in @@ -25,7 +25,7 @@ records: ############################################################################## # RAM and disk cache configurations. Docs: # https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#ram-cache -# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/storage.yaml.en.html +# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/storage.config.en.html ############################################################################## ram_cache: size: -1 @@ -199,7 +199,7 @@ records: ############################################################################## # SSL Termination. Docs: # https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#client-related-configuration -# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/ssl_multicert.yaml.en.html +# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/ssl_multicert.config.en.html ############################################################################## verify: server: diff --git a/configs/ssl_multicert.yaml.default b/configs/ssl_multicert.config.default similarity index 56% rename from configs/ssl_multicert.yaml.default rename to configs/ssl_multicert.config.default index c5eac93a629..43eb43e4cb5 100644 --- a/configs/ssl_multicert.yaml.default +++ b/configs/ssl_multicert.config.default @@ -1,12 +1,12 @@ # -# ssl_multicert.yaml +# ssl_multicert.config # # Documentation: -# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/ssl_multicert.yaml.en.html +# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/ssl_multicert.config.en.html # # Allows a TLS certificate and private key to be tied to a specific # hostname or IP address. At load time, the certificate is parsed to -# extract the subject CN and all the DNS subjectAltNames. The +# extract the subject CN and all the DNS subjectAltNames. The # certificate will be presented for connections requesting any of the # hostnames found in the certificate. Wildcard names in the certificates # are supported, but only of the form '*.domain.com', ie. where '*' @@ -18,11 +18,7 @@ # # Fields: # -# ssl_cert_name: FILENAME -# The name of the file containing the TLS certificate. This is the -# only field that is required to be present (unless action is tunnel). -# -# dest_ip: ADDRESS +# dest_ip=ADDRESS # The IP (v4 or v6) address that the certificate should be presented # on. This is now only used as a fallback in the case that the TLS # ServerNameIndication extension is not supported. If ADDRESS is @@ -34,58 +30,37 @@ # accepted on the specified port. IPv6 addresses must be enclosed by # square brackets if they have a port, eg, [::1]:80. # -# ssl_key_name: FILENAME +# ssl_key_name=FILENAME # The name of the file containing the private key for this certificate. # If the key is contained in the certificate file, this field can be # omitted. # -# ssl_ca_name: FILENAME +# ssl_ca_name=FILENAME # If your certificates have different Certificate Authorities, you # can optionally specify the corresponding file here. # -# ssl_ocsp_name: FILENAME -# The name of the file containing the OCSP response for stapling. +# ssl_cert_name=FILENAME +# The name of the file containing the TLS certificate. This is the +# only field that is required to be present. # -# ssl_key_dialog: builtin|exec:/path/to/program +# ssl_key_dialog=[builtin|exec:/path/to/program] # Method used to provide a pass phrase for encrypted private keys. # Two options are supported: builtin and exec # builtin - Requests passphrase via stdin/stdout. Useful for debugging. # exec: - Executes a program and uses the stdout output for the pass # phrase. # -# ssl_ticket_enabled: 0|1 -# Enable or disable session tickets for this certificate. -# -# ssl_ticket_number: NUMBER -# Number of session tickets to issue for new TLSv1.3 connections. -# -# action: tunnel -# If the connection matches this entry, traffic server will not participate -# in the handshake. Instead, it will blind tunnel the SSL connection. -# -# Example configuration: -# -# ssl_multicert: -# - ssl_cert_name: server.pem -# -# - ssl_cert_name: bar.pem -# ssl_key_name: barKey.pem -# dest_ip: "*" -# -# - ssl_cert_name: server.pem -# ssl_key_name: serverKey.pem -# dest_ip: "209.131.48.79" -# -# - ssl_cert_name: port99.pem -# dest_ip: "10.0.0.1:99" -# -# - ssl_cert_name: foo.pem -# ssl_key_dialog: "exec:/usr/bin/mypass foo 'ba r'" -# -# - action: tunnel -# dest_ip: "192.168.1.1" -# -# - ssl_cert_name: wildcardcert.pem -# ssl_key_name: privkey.pem - -ssl_multicert: [] +# action=[tunnel] +# If the tunnel matches this line, traffic server will not participate +# in the handshake. But rather it will blind tunnel the SSL connection. +# If the connection is identified by server name, an openSSL patch must +# be applied to enable this functionality. See TS-3006 for details. +# +# Examples: +# ssl_cert_name=foo.pem +# dest_ip=* ssl_cert_name=bar.pem ssl_key_name=barKey.pem +# dest_ip=209.131.48.79 ssl_cert_name=server.pem ssl_key_name=serverKey.pem +# dest_ip=10.0.0.1:99 ssl_cert_name=port99.pem +# ssl_cert_name=foo.pem ssl_key_dialog="exec:/usr/bin/mypass foo 'ba r'" +# ssl_cert_name=foo.pem action=tunnel +# ssl_cert_name=wildcardcert.pem ssl_key_name=privkey.pem diff --git a/configs/storage.config.default.in b/configs/storage.config.default.in new file mode 100644 index 00000000000..0bd93848111 --- /dev/null +++ b/configs/storage.config.default.in @@ -0,0 +1,53 @@ +# +# storage.config - Storage Configuration file +# +# Documentation: +# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/storage.config.en.html +# +# The storage configuration is a list of all the storage to +# be used by the cache. +# +# +############################################################# +# Using a file for the cache storage +# +# +# +# Where 'pathname' is full path to the directory where you want +# the cache-file to live and 'size' is size in bytes +# +# Example: 128MB cache file(@exp_cachedir@/cache.db) +# @exp_cachedir@ 128M +# +# Example: 144MB cache file(@exp_cachedir@/cache.db) +# assuming prefix of '@prefix@' +# @rel_cachedir@ 150994944 +# +# Example: 512MB cache file(@exp_cachedir@/cache.db) +# assuming prefix of '@prefix@' +# @rel_cachedir@ 512M +# +# +############################################################# +## O_DIRECT Specific Configuration ## +############################################################# +# +# Examples: Using O_DIRECT on disks (Linux kernel >= 2.6.3, +# FreeBSD > 5.3) +# +# /dev/disc/by-id/[Insert_ID_Here_12345] # Linux +# /dev/disc/by-path/[Insert-Path-Here:12:34:56-1.0.0.0] # Linux +# +# /dev/ada1 # FreeBSD +# +# Note that disks are identified by id or path. This is to prevent changes +# by the kernel (which could occur if a disk was simply described as /dev/sda, sdb, etc.). +# +# Also note that when using these raw devices in O_DIRECT mode, you +# do not need to specify the partition size. It's automatically +# detected. +# +# A small default cache (256MB). This is set to allow for the regression test to succeed +# most likely you'll want to use a larger cache. And, we definitely recommend the use +# of raw devices for production caches. +@rel_cachedir@ 256M diff --git a/configs/storage.yaml.default.in b/configs/storage.yaml.default.in deleted file mode 100644 index e5b31cef7c3..00000000000 --- a/configs/storage.yaml.default.in +++ /dev/null @@ -1,32 +0,0 @@ -# -# storage.yaml - Span and Volume Configuration file -# -# Documentation: -# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/storage.yaml.en.html -# -# The storage configuration is a list of all the storage to be used by the cache and volume -# -# cache: # file level key -# spans: # -# - name: # name of the span -# path: # path to storage -# size: # size in bytes, required for file system storage, optional for raw device -# hash_seed: # optional, used to isolate lookup from path changes -# volumes: # optional -# - id: # identifier [1-255] -# size: # optional, size in bytes or percentage -# scheme: # optional, default to "http" -# ram_cache: # optional, default to "true" -# ram_cache_size: # optional, allocate a dedicated RAM cache pool for this volume -# ram_cache_cutoff: # optional, overrides proxy.config.cache.ram_cache_cutoff -# avg_obj_size: # optional, overrides proxy.config.cache.min_average_object_size -# fragment_size: # optional, overrides proxy.config.cache.target_fragment_size -# spans: # optional -# - use: # Span identifier -# size: # size allocated to this volume - -cache: - spans: - - name: disk-1 - path: @rel_cachedir@ - size: 256M diff --git a/configs/volume.config.default b/configs/volume.config.default new file mode 100644 index 00000000000..6eafd85aed8 --- /dev/null +++ b/configs/volume.config.default @@ -0,0 +1,65 @@ +# +# volume.config +# +# Documentation: +# https://docs.trafficserver.apache.org/en/latest/admin-guide/files/volume.config.en.html +# +# This file specifies the various volumes, their sizes and the +# protocol they belong to. Use this file in conjunction with the +# hosting.config file. +# +# Each line consists of a tag value pair. +# volume= scheme= size= +# +# volume_number can be any value between 1 and 255. +# This limits the maximum number of volumes to 255. +# Volume number 0 is reserved for the special free volume. +# Each line MUST have a distinct volume number. +# +# The only scheme currently supported is 'http. +# +# volume_size can either be specified in percentage of the total +# cache space or absolute value. It must be a multiple of 128 Megabytes, +# with 128 Megabytes being the smallest size possible. If specified in +# percentage, the size is rounded down to the closest multiple of +# 128 Megabytes. A volume can be as big as the whole cache. +# Each volume is striped across several disks to +# achieve parallel I/O. For example, if there are 4 disks, +# a 1 Gigabyte volume will have 256 Megabytes on each +# disk (assuming each disk has enough free space available). +# +# To create one volume of size 10% of the total cache space and +# another 1 Gig volume, +# volume=1 scheme=http size=10% +# volume=2 scheme=http size=1024 +# +# Additional optional parameters: +# +# ramcache=true/false +# Enable or disable RAM cache for this volume (default: true) +# +# ram_cache_size= +# Allocate a dedicated RAM cache pool for this volume (e.g., 512M, 2G) +# This amount is automatically subtracted from the global ram_cache.size +# setting, with the remainder shared among other volumes. +# +# ram_cache_cutoff= +# Override the global ram_cache_cutoff for this volume (e.g., 64K, 1M) +# Objects larger than this will not be stored in RAM cache. +# +# avg_obj_size= +# Override the global min_average_object_size for this volume +# +# fragment_size= +# Override the global target_fragment_size for this volume (max: 4MB) +# +# Advanced RAM cache configuration examples: +# +# Example 1: Volume with dedicated 2GB RAM cache +# volume=1 scheme=http size=40% ram_cache_size=2G +# +# Example 2: Small objects with custom cutoff and dedicated RAM +# volume=2 scheme=http size=20% ram_cache_size=512M ram_cache_cutoff=64K +# +# Example 3: Large media with higher cutoff (shares remaining RAM pool) +# volume=3 scheme=http size=40% ram_cache_cutoff=1M diff --git a/contrib/set_trafficserver.sh b/contrib/set_trafficserver.sh index 97d30dc20a9..d6486d11e12 100644 --- a/contrib/set_trafficserver.sh +++ b/contrib/set_trafficserver.sh @@ -29,7 +29,7 @@ # v1.0.3 - Check to force /mnt based cache.db REMAP_FILE=/usr/local/etc/trafficserver/remap.config -STORAGE_FILE=/usr/local/etc/trafficserver/storage.yaml +STORAGE_FILE=/usr/local/etc/trafficserver/storage.config EC2_CACHE_LOC=/mnt/trafficserver_cache # Base settings to use for testing and benchmarking diff --git a/doc/admin-guide/configuration/cache-basics.en.rst b/doc/admin-guide/configuration/cache-basics.en.rst index db5fdc8520b..b03d2bdae00 100644 --- a/doc/admin-guide/configuration/cache-basics.en.rst +++ b/doc/admin-guide/configuration/cache-basics.en.rst @@ -247,17 +247,17 @@ For example, an origin server might send:: Cache-Control: max-age=60 CDN-Cache-Control: max-age=3600 -By default, Traffic Server checks ``CDN-Cache-Control`` first (via -:ts:cv:`proxy.config.http.cache.targeted_cache_control_headers`) and uses -those directives instead of the standard ``Cache-Control`` directives for -caching decisions. The browser receiving the response will see both headers and -use the standard ``Cache-Control``, allowing the object to be cached for -60 seconds in the browser but 3600 seconds in the CDN. +When targeted cache control is enabled (via +:ts:cv:`proxy.config.http.cache.targeted_cache_control_headers`), Traffic +Server will use the ``CDN-Cache-Control`` directives instead of the standard +``Cache-Control`` directives for caching decisions. The browser receiving the +response will see both headers and use the standard ``Cache-Control``, allowing +the object to be cached for 60 seconds in the browser but 3600 seconds in the CDN. Configuration ~~~~~~~~~~~~~ -To customize targeted cache control, set +To enable targeted cache control, set :ts:cv:`proxy.config.http.cache.targeted_cache_control_headers` to a comma-separated list of header names to check in priority order:: @@ -268,10 +268,6 @@ Or with multiple targeted headers in priority order:: proxy.config.http.cache.targeted_cache_control_headers: ATS-Cache-Control,CDN-Cache-Control -To disable targeted cache control entirely, set the value to an empty string:: - - proxy.config.http.cache.targeted_cache_control_headers: "" - This configuration is overridable per-remap, allowing different rules for different origins:: diff --git a/doc/admin-guide/files/hosting.config.en.rst b/doc/admin-guide/files/hosting.config.en.rst index 4bdb851dabf..c83bfcec64b 100644 --- a/doc/admin-guide/files/hosting.config.en.rst +++ b/doc/admin-guide/files/hosting.config.en.rst @@ -30,7 +30,7 @@ refer to :ref:`partitioning-the-cache`. Before you can assign cache volumes to specific origin servers and/or domains, you must first partition your cache -according to size and protocol in the :file:`storage.yaml` +according to size and protocol in the :file:`volume.config` file. After you modify hosting.config, navigate to the Traffic Server bin @@ -52,7 +52,7 @@ want to store on a particular partition (for example, ``mydomain.com``); and ``NUMBERS`` is a comma-separated list of the partitions on which you want to store the content that belongs to the origin server or domain listed. The partition numbers must be valid numbers listed in the -:file:`storage.yaml`. +:file:`volume.config`. **Note:** To allocate more than one partition to an origin server or domain, you must enter the partitions in a comma-separated list on one diff --git a/doc/admin-guide/files/index.en.rst b/doc/admin-guide/files/index.en.rst index 38b1db9b41a..1ce15d62421 100644 --- a/doc/admin-guide/files/index.en.rst +++ b/doc/admin-guide/files/index.en.rst @@ -31,15 +31,14 @@ Configuration Files logging.yaml.en parent.config.en plugin.config.en - plugin.yaml.en records.yaml.en remap.config.en - remap.yaml.en splitdns.config.en - ssl_multicert.yaml.en + ssl_multicert.config.en sni.yaml.en - storage.yaml.en + storage.config.en strategies.yaml.en + volume.config.en jsonrpc.yaml.en :doc:`cache.config.en` @@ -64,35 +63,31 @@ Configuration Files Control runtime loadable plugins available to |TS|, as well as their configurations. -:doc:`plugin.yaml.en` - YAML-based alternative to :doc:`plugin.config.en` with support for - disabling plugins, explicit load ordering, and inline configuration. - :doc:`records.yaml.en` Contains many configuration variables affecting |TS| operation. :doc:`remap.config.en` Defines mapping rules used by |TS| to properly route all incoming requests. -:doc:`remap.yaml.en` - Defines mapping rules used by |TS| to properly route all incoming requests in YAML format - :doc:`splitdns.config.en` Configures DNS servers to use under specific conditions. -:doc:`ssl_multicert.yaml.en` +:doc:`ssl_multicert.config.en` Configures |TS| to use different server certificates for SSL termination when listening on multiple addresses or when clients employ SNI. :doc:`sni.yaml.en` Configures SNI based Layer 4 routing. -:doc:`storage.yaml.en` - Configures all storage devices and paths to be used for the |TS| cache and defines cache space usage by individual protocols. +:doc:`storage.config.en` + Configures all storage devices and paths to be used for the |TS| cache. :doc:`strategies.yaml.en` Configures NextHop strategies used with `remap.config` and replaces parent.config. +:doc:`volume.config.en` + Defines cache space usage by individual protocols. + :doc:`jsonrpc.yaml.en` Defines some of the configurable arguments of the jsonrpc endpoint. diff --git a/doc/admin-guide/files/plugin.config.en.rst b/doc/admin-guide/files/plugin.config.en.rst index c2e6919e331..b255a02b5b3 100644 --- a/doc/admin-guide/files/plugin.config.en.rst +++ b/doc/admin-guide/files/plugin.config.en.rst @@ -23,31 +23,6 @@ plugin.config .. configfile:: plugin.config -.. warning:: - - **Use** :file:`plugin.yaml` **instead.** The :file:`plugin.config` format is - maintained for backward compatibility but :file:`plugin.yaml` is the - recommended way to configure global plugins. It supports disabling plugins - without deleting lines, explicit load ordering, and inline configuration. - - If :file:`plugin.yaml` exists in the configuration directory, |TS| will load - plugins from it and ignore :file:`plugin.config` entirely. - - See :doc:`plugin.yaml.en` for the full reference. - - **Migrating:** use ``traffic_ctl`` to convert an existing file automatically: - - .. code-block:: bash - - # Preview the converted output on stdout - traffic_ctl config convert plugin_config plugin.config - - - # Write directly to plugin.yaml - traffic_ctl config convert plugin_config plugin.config plugin.yaml - - Commented-out lines in :file:`plugin.config` are converted to - ``enabled: false`` entries. Pass ``--skip-disabled`` to drop them instead. - Description =========== @@ -96,7 +71,6 @@ Examples See Also ======== -:doc:`plugin.yaml.en`, :manpage:`TSAPI(3ts)`, :manpage:`TSPluginInit(3ts)`, :manpage:`remap.config(5)` diff --git a/doc/admin-guide/files/plugin.yaml.en.rst b/doc/admin-guide/files/plugin.yaml.en.rst deleted file mode 100644 index 772e9e6a2dd..00000000000 --- a/doc/admin-guide/files/plugin.yaml.en.rst +++ /dev/null @@ -1,382 +0,0 @@ -.. Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - -.. include:: ../../common.defs - -=========== -plugin.yaml -=========== - -.. configfile:: plugin.yaml - -The :file:`plugin.yaml` file provides a YAML-based alternative to -:file:`plugin.config` for configuring global plugins available to |TS|. -Global plugins are loaded at startup and have global effect on all -transactions. This is in contrast to plugins specified in -:file:`remap.config` or :file:`remap.yaml`, whose effects are limited to -specific mapping rules. - -Configuration File Fallback -============================ - -|TS| will attempt to load :file:`plugin.yaml` first. If this file is -not found, it will fall back to loading :file:`plugin.config`. If both -files exist, only :file:`plugin.yaml` will be used. This allows for a -gradual migration from the legacy configuration format to YAML. - -Format -====== - -The :file:`plugin.yaml` file uses YAML syntax with a single required key: - -- ``plugins`` (required): A sequence of plugin entries. - -Each plugin entry is a YAML mapping with the following fields: - -``path`` --------- - -**Required.** Path to the ``.so`` file. This path can be absolute or -relative to the plugin directory (usually -``/usr/local/libexec/trafficserver``). - -``enabled`` ------------ - -**Optional.** Boolean. When set to ``false``, the plugin is skipped -entirely during startup — no ``dlopen``, no ``TSPluginInit``. The -configuration entry remains in the file for easy re-enabling. - -**Default:** ``true`` - -``params`` ----------- - -**Optional.** A YAML sequence of string arguments passed to the plugin's -``TSPluginInit`` function as ``argc/argv``. Arguments that begin -with ``$`` designate |TS| configuration variables and will be expanded -to their current value before the plugin is loaded. - -``config`` ----------- - -**Optional.** Inline configuration content specified as a YAML scalar. -The text is written to a temporary file at startup and the path is -passed to the plugin as an argument, so existing plugins work without -modification. - -.. tip:: - - Use a literal block scalar (``|``) to preserve exact text including - newlines and quoting -- this is important for plugins like - ``txn_box.so`` that assign special meaning to YAML quoting. - -.. note:: - - Structured YAML (mappings or sequences) is rejected because - re-serializing through a YAML emitter strips quoting semantics that - some plugins depend on. For example, ``txn_box.so`` distinguishes - ``"literal"`` (a quoted string) from ``extractor-name`` (an unquoted - reference), and that distinction would be lost after a round-trip - through ``YAML::Emitter``. Supporting structured YAML may be - revisited in the future. - -``load_order`` --------------- - -**Optional.** Integer. Provides explicit control over the order in which -plugins are loaded and therefore the order in which they are chained for -request processing. - -The loading rules are: - -1. Plugins **with** ``load_order`` are loaded first, sorted ascending by - value (lowest number loads first). -2. Among plugins with the **same** ``load_order`` value, their relative - order in the YAML file is preserved (stable sort). -3. Plugins **without** ``load_order`` are loaded after all ordered - plugins, in the order they appear in the YAML file. - -Most deployments do not need ``load_order`` — simply list plugins in the -desired order in the YAML file. Use ``load_order`` when the file is -managed by automation tools that may reorder entries, or when you want -to guarantee a specific plugin loads first regardless of where it -appears in the file. - -**Default:** Unset (YAML sequence order). - -Basic Structure -=============== - -.. code-block:: yaml - - plugins: - - path: stats_over_http.so - - - path: abuse.so - params: - - etc/trafficserver/abuse.config - - - path: header_rewrite.so - params: - - etc/trafficserver/header_rewrite.config - - - path: icx.so - params: - - etc/trafficserver/icx.config - - $proxy.config.http.connect_attempts_timeout - - - path: experimental_plugin.so - enabled: false - params: - - --verbose - -.. important:: - - **Loading order matters.** Plugins are loaded in the order they - appear in the YAML file, and this is the order in which they are - chained for request processing (hooks are called in load order). If - you need a plugin to run before another, place it earlier in the file - or assign it a lower ``load_order`` value. - -New Features Over plugin.config -================================ - -:file:`plugin.yaml` introduces several features not available in the -legacy :file:`plugin.config` format: - -* **Disable without deleting** — set ``enabled: false`` to skip a - plugin without removing or commenting out the line. -* **Explicit load ordering** — use ``load_order`` to control loading - priority independent of file position. -* **Inline configuration** — embed a plugin's config content directly - via the ``config`` field instead of maintaining a separate file. -* **Variable expansion** — ``$record`` references in ``params`` are - expanded to their current value at load time (same as - :file:`plugin.config`). -* **Startup logging** — each plugin produces a ``NOTE``-level log line - showing its load sequence number, path, and status. -* **Runtime introspection** — ``traffic_ctl plugin list`` shows the - loaded plugins and their status via JSONRPC. -* **Automated migration** — ``traffic_ctl config convert plugin_config`` - converts an existing :file:`plugin.config` to :file:`plugin.yaml`. - -Examples -======== - -Disabling a Plugin ------------------- - -.. code-block:: yaml - - plugins: - - path: debug_plugin.so - enabled: false - -The plugin entry remains in the configuration file but is not loaded. -Set ``enabled: true`` (or remove the field) to re-enable it. - -Plugin Loading Order ---------------------- - -By default, plugins load in the order they appear in the YAML file — -top to bottom. This is the same behavior as :file:`plugin.config` and -is sufficient for most deployments: - -.. code-block:: yaml - - plugins: - - path: certifier.so # loads 1st - - path: header_rewrite.so # loads 2nd - - path: stats_over_http.so # loads 3rd - -When ``load_order`` is set, it overrides the file order. Plugins with -``load_order`` always load before plugins without it: - -.. code-block:: yaml - - plugins: - - path: stats_over_http.so - load_order: 300 - - - path: certifier.so - load_order: 100 - - - path: header_rewrite.so - load_order: 200 - - - path: xdebug.so - -Despite the YAML sequence order, the actual load order is: - -1. ``certifier.so`` (load_order: 100) -2. ``header_rewrite.so`` (load_order: 200) -3. ``stats_over_http.so`` (load_order: 300) -4. ``xdebug.so`` (no load_order — loaded last, in file order) - -.. tip:: - - Use gaps between ``load_order`` values (e.g. 100, 200, 300) so new - plugins can be inserted later without renumbering. - -Inline Configuration --------------------- - -The ``config`` field lets you embed a plugin's configuration directly in -:file:`plugin.yaml` instead of maintaining a separate file. At startup, |TS| -writes the content to a temporary file in the configuration directory and passes -the path of that file to the plugin as an argument — exactly the same way a -``params`` entry pointing to an external file would work. The plugin reads the -file as usual; it has no knowledge the content was inlined. - -Use the YAML literal block scalar (``|``) to provide the content: - -.. code-block:: yaml - - plugins: - - path: header_rewrite.so - config: | - cond %{SEND_RESPONSE_HDR_HOOK} - set-header X-Debug "true" - -The text after ``|`` is preserved exactly (including newlines and -indentation). It is written to a temporary file named after the plugin -(e.g. ``/.header_rewrite_inline_1.conf``). Temporary files -from a previous run are removed automatically at startup. - -This works equally well for plugins that read YAML configuration files. -The block scalar preserves quoting and formatting that some YAML-consuming -plugins rely on: - -.. code-block:: yaml - - plugins: - - path: txn_box.so - config: | - txn_box: - when: proxy-rsp - do: - - proxy-rsp-field: "inline-config-active" - -.. note:: - - The ``config`` field and ``params`` can be used together. When both are - present, the temporary file path is inserted before the ``params`` entries - in the argument vector: - - .. code-block:: yaml - - plugins: - - path: header_rewrite.so - config: | - cond %{SEND_RESPONSE_HDR_HOOK} - set-header X-Source "inline" - params: - - --verbose - - The plugin receives ``argv = ["header_rewrite.so", - "/.header_rewrite_inline_1.conf", "--verbose"]``. - - The inline file path is always a bare positional argument at ``argv[1]``. - This works for plugins that take a config file as their first argument - (e.g., ``header_rewrite.so``, ``txn_box.so``). Plugins that require a - flag before the filename (e.g., ``--config ``) should use ``params`` - pointing to a separate file instead of ``config``. - -Configuration Variable Expansion ---------------------------------- - -.. code-block:: yaml - - plugins: - - path: icx.so - params: - - etc/trafficserver/icx.config - - $proxy.config.http.connect_attempts_timeout - -Arguments beginning with ``$`` are expanded to the current value of the -corresponding |TS| configuration variable before the plugin is loaded. - -Migration from plugin.config -============================== - -.. list-table:: - :header-rows: 1 - :widths: 50 50 - - * - plugin.config - - plugin.yaml - * - :: - - stats_over_http.so - - .. code-block:: yaml - - plugins: - - path: stats_over_http.so - - * - :: - - abuse.so etc/trafficserver/abuse.config - - .. code-block:: yaml - - plugins: - - path: abuse.so - params: - - etc/trafficserver/abuse.config - - * - :: - - icx.so etc/trafficserver/icx.config $proxy.config.http.connect_attempts_timeout - - .. code-block:: yaml - - plugins: - - path: icx.so - params: - - etc/trafficserver/icx.config - - $proxy.config.http.connect_attempts_timeout - - * - :: - - # header_rewrite.so etc/trafficserver/header_rewrite.config - - .. code-block:: yaml - - plugins: - - path: header_rewrite.so - params: - - etc/trafficserver/header_rewrite.config - enabled: false - -Startup Logging -=============== - -When plugins are loaded from :file:`plugin.yaml`, each plugin produces a -``NOTE``-level log line showing its load sequence number, path, and status: - -:: - - [NOTE] plugin #1 loading: certifier.so (load_order: 100) - [NOTE] plugin #2 loading: header_rewrite.so - [NOTE] plugin #3 skipped: experimental_plugin.so (enabled: false) - -See Also -======== - -:doc:`plugin.config.en`, -:manpage:`TSAPI(3ts)`, -:manpage:`TSPluginInit(3ts)`, -:doc:`remap.config.en`, -:doc:`remap.yaml.en` diff --git a/doc/admin-guide/files/records.yaml.en.rst b/doc/admin-guide/files/records.yaml.en.rst index b319c15b1b0..1f11f02ca3f 100644 --- a/doc/admin-guide/files/records.yaml.en.rst +++ b/doc/admin-guide/files/records.yaml.en.rst @@ -2545,16 +2545,15 @@ Cache Control ``Cache-Control: max-age``. ===== ====================================================================== -.. ts:cv:: CONFIG proxy.config.http.cache.targeted_cache_control_headers STRING "CDN-Cache-Control" +.. ts:cv:: CONFIG proxy.config.http.cache.targeted_cache_control_headers STRING "" :reloadable: :overridable: - Comma-separated list of targeted cache control header names to check in - priority order before falling back to the standard ``Cache-Control`` - header. This implements `RFC 9213 `_ - Targeted HTTP Cache Control. The default value is ``CDN-Cache-Control``. - Set this to an empty string to disable targeted cache control and use only - the standard ``Cache-Control`` header. + Comma-separated list of targeted cache control header names to check in priority + order before falling back to the standard ``Cache-Control`` header. This implements + `RFC 9213 `_ Targeted HTTP Cache Control. + When empty (the default), targeted cache control is disabled and only the standard + ``Cache-Control`` header is used. Example values: @@ -2874,7 +2873,7 @@ RAM Cache **20GB** (21474836480) This global setting can be overridden on a per-volume basis using the - ``ram_cache_size`` parameter in :file:`storage.yaml`. Per-volume + ``ram_cache_size`` parameter in :file:`volume.config`. Per-volume allocations are subtracted from the total RAM cache size before distributing the remainder among volumes without explicit settings. @@ -2886,7 +2885,7 @@ RAM Cache **4MB** (4194304) This global setting can be overridden on a per-volume basis using the - ``ram_cache_cutoff`` parameter in :file:`storage.yaml`. When set, + ``ram_cache_cutoff`` parameter in :file:`volume.config`. When set, the per-volume cutoff takes precedence over this global setting for that specific volume. @@ -4092,54 +4091,47 @@ SSL Termination ===== ====================================================================== -.. ts:cv:: CONFIG proxy.config.ssl.server.multicert.filename STRING ssl_multicert.yaml +.. ts:cv:: CONFIG proxy.config.ssl.server.multicert.filename STRING ssl_multicert.config :deprecated: - The location of the :file:`ssl_multicert.yaml` file, relative + The location of the :file:`ssl_multicert.config` file, relative to the |TS| configuration directory. In the following example, if the |TS| configuration directory is `/etc/trafficserver`, the |TS| SSL configuration file and the corresponding certificates are located in `/etc/trafficserver/ssl`:: - CONFIG proxy.config.ssl.server.multicert.filename STRING ssl/ssl_multicert.yaml + CONFIG proxy.config.ssl.server.multicert.filename STRING ssl/ssl_multicert.config CONFIG proxy.config.ssl.server.cert.path STRING etc/trafficserver/ssl CONFIG proxy.config.ssl.server.private_key.path STRING etc/trafficserver/ssl .. ts:cv:: CONFIG proxy.config.ssl.server.multicert.exit_on_load_fail INT 1 By default (``1``), |TS| will not start unless all the SSL certificates listed in the - :file:`ssl_multicert.yaml` file successfully load. If false (``0``), SSL certificate + :file:`ssl_multicert.config` file successfully load. If false (``0``), SSL certificate load failures will not prevent |TS| from starting. -.. ts:cv:: CONFIG proxy.config.ssl.server.multicert.concurrency INT 1 - - Controls how many threads are used to load SSL certificates from :file:`ssl_multicert.yaml` - during configuration reloads. On first startup, |TS| always uses all available CPU cores - regardless of this setting. Set to ``0`` to automatically use the number of hardware - threads. Default ``1`` (single-threaded reloads). - .. ts:cv:: CONFIG proxy.config.ssl.server.cert.path STRING /config The location of the SSL certificates and chains used for accepting and validation new SSL sessions. If this is a relative path, it is appended to the |TS| installation PREFIX. All certificates and certificate chains listed in - :file:`ssl_multicert.yaml` will be loaded relative to this path. + :file:`ssl_multicert.config` will be loaded relative to this path. .. ts:cv:: CONFIG proxy.config.ssl.server.private_key.path STRING NULL The location of the SSL certificate private keys. Change this variable only if the private key is not located in the SSL certificate file. All private keys listed in - :file:`ssl_multicert.yaml` will be loaded relative to this + :file:`ssl_multicert.config` will be loaded relative to this path. .. ts:cv:: CONFIG proxy.config.ssl.server.cert_chain.filename STRING NULL The name of a file containing a global certificate chain that should be used with every server certificate. This file is only - used if there are certificates defined in :file:`ssl_multicert.yaml`. + used if there are certificates defined in :file:`ssl_multicert.config`. Unless this is an absolute path, it is loaded relative to the path specified by :ts:cv:`proxy.config.ssl.server.cert.path`. @@ -4207,17 +4199,81 @@ SSL Termination Setting a value less than or equal to ``0`` effectively disables SSL session cache for the origin server. +.. ts:cv:: CONFIG proxy.config.ssl.session_cache.mode INT 2 + + Sets the SSL session cache mode: + + ===== ====================================================================== + Value Description + ===== ====================================================================== + ``0`` Disables the session cache entirely. + ``1`` Enables the session cache using OpenSSL's implementation. + ``2`` Default. Enables the session cache using |TS|'s implementation. This + implementation should perform much better than the OpenSSL + implementation. + ===== ====================================================================== + +.. ts:cv:: CONFIG proxy.config.ssl.session_cache.enabled INT 2 + + .. deprecated:: 10.1.0 + Use :ts:cv:`proxy.config.ssl.session_cache.mode` instead. + + This configuration exists for historical reasons and is deprecated in favor of + :ts:cv:`proxy.config.ssl.session_cache.mode`. It accepts the same values and + has identical behavior, so see that documentation for details. + +.. ts:cv:: CONFIG proxy.config.ssl.session_cache.timeout INT 0 + + This configuration specifies the lifetime of SSL session cache + entries in seconds. If it is ``0``, then the SSL library will use + a default value, typically 300 seconds. Note: This option has no affect + when using the |TS| session cache (option ``2`` in + ``proxy.config.ssl.session_cache.mode``) + + See :ref:`admin-performance-timeouts` for more discussion on |TS| timeouts. + +.. ts:cv:: CONFIG proxy.config.ssl.session_cache.auto_clear INT 1 + + This will set the OpenSSL auto clear flag. Auto clear is enabled by + default with ``1`` it can be disabled by changing this setting to ``0``. + +.. ts:cv:: CONFIG proxy.config.ssl.session_cache.size INT 102400 + + This configuration specifies the maximum number of entries + the SSL session cache may contain. + +.. ts:cv:: CONFIG proxy.config.ssl.session_cache.num_buckets INT 256 + + This configuration specifies the number of buckets to use with the + |TS| SSL session cache implementation. The TS implementation + is a fixed size hash map where each bucket is protected by a mutex. + +.. ts:cv:: CONFIG proxy.config.ssl.session_cache.skip_cache_on_bucket_contention INT 0 + + This configuration specifies the behavior of the |TS| SSL session + cache implementation during lock contention on each bucket: + + ===== ====================================================================== + Value Description + ===== ====================================================================== + ``0`` Default. Don't skip session caching when bucket lock is contented. + ``1`` Disable the SSL session cache for a connection during lock contention. + ===== ====================================================================== + .. ts:cv:: CONFIG proxy.config.ssl.server.session_ticket.enable INT 1 Set to 1 to enable Traffic Server to process TLS tickets for TLS session resumption. .. ts:cv:: CONFIG proxy.config.ssl.server.session_ticket.number INT 2 - This configuration controls the number of TLSv1.3 session tickets that are issued. - Setting the value to 0 will disable session ticket-based session resumption for TLSv1.3 + This configuration control the number of TLSv1.3 session tickets that are issued. + Take into account that setting the value to 0 will disable session caching for TLSv1.3 connections. - Increasing the number of tickets could be potentially beneficial for clients performing + Lowering this setting to ``1`` can be interesting when ``proxy.config.ssl.session_cache.mode`` is enabled because + otherwise for every new TLSv1.3 connection two session IDs will be inserted in the session cache. + On the other hand, if ``proxy.config.ssl.session_cache.mode`` is disabled, using the default value is recommended. + In those scenarios, increasing the number of tickets could be potentially beneficial for clients performing multiple requests over concurrent TLS connections as per RFC 8446 clients SHOULDN'T reuse TLS Tickets. This setting is applied at the SSL context level. BoringSSL does not support setting the @@ -4291,40 +4347,6 @@ SSL Termination ``1`` Enables the use of Kernel TLS.. ===== ====================================================================== -.. ts:cv:: CONFIG proxy.config.ssl.server.cert_compression.algorithms STRING - :reloadable: - - A comma-separated list of compression algorithms that |TS| is willing to - use for TLS Certificate Compression - (`RFC 8879 `_) when |TS| - acts as a TLS server (i.e. accepting connections from clients). When a - connecting client advertises support for one of these algorithms, |TS| will - send its certificate in compressed form, reducing handshake size. - - Supported values: ``zlib``, ``brotli``, ``zstd``. The order determines the - server's preference. An empty value (the default) disables certificate - compression. - - ``brotli`` and ``zstd`` are only available when |TS| is compiled with the - corresponding libraries. - - Example:: - - proxy.config.ssl.server.cert_compression.algorithms: zlib,brotli - -.. ts:cv:: CONFIG proxy.config.ssl.client.cert_compression.algorithms STRING - :reloadable: - - A comma-separated list of compression algorithms that |TS| advertises for - TLS Certificate Compression - (`RFC 8879 `_) when |TS| - acts as a TLS client (i.e. connecting to origin servers). When the origin - supports one of these algorithms, |TS| will accept and decompress the - certificate. - - Supported values: ``zlib``, ``brotli``, ``zstd``. An empty value (the - default) disables certificate compression. - Client-Related Configuration ---------------------------- @@ -4687,7 +4709,7 @@ OCSP Stapling Configuration The directory path of the prefetched OCSP stapling responses. Change this variable only if you intend to use and administratively maintain prefetched OCSP stapling responses. All stapling responses listed in - :file:`ssl_multicert.yaml` will be loaded relative to this + :file:`ssl_multicert.config` will be loaded relative to this path. HTTP/2 Configuration @@ -5758,11 +5780,11 @@ Sockets ``2`` Do not accept inbound connections until cache initialization has finished and been sufficiently successful that cache is enabled. This means at least one cache span is usable. If there are no spans in - :file:`storage.yaml` or none of the spans can be successfully parsed + :file:`storage.config` or none of the spans can be successfully parsed and initialized then |TS| will shut down. ``3`` Do not accept inbound connections until cache initialization has finished and been completely successful. This requires at least one - cache span in :file:`storage.yaml` and that every span specified is + cache span in :file:`storage.config` and that every span specified is valid and successfully initialized. Any error will cause |TS| to shut down. ===== ====================================================================== diff --git a/doc/admin-guide/files/remap.config.en.rst b/doc/admin-guide/files/remap.config.en.rst index 355e31f137d..73012823734 100644 --- a/doc/admin-guide/files/remap.config.en.rst +++ b/doc/admin-guide/files/remap.config.en.rst @@ -478,7 +478,7 @@ Where ```` can be either: - Multiple comma-separated volume numbers: ``@volume=3,4,5`` Volume numbers must be between 1 and 255 (volume 0 is reserved and not usable). -All specified volumes must be defined in :file:`storage.yaml`. +All specified volumes must be defined in :file:`volume.config`. Examples -------- diff --git a/doc/admin-guide/files/remap.yaml.en.rst b/doc/admin-guide/files/remap.yaml.en.rst deleted file mode 100644 index 398837aa1c1..00000000000 --- a/doc/admin-guide/files/remap.yaml.en.rst +++ /dev/null @@ -1,1212 +0,0 @@ -.. Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - -=========== -remap.yaml -=========== - -.. configfile:: remap.yaml - -.. include:: ../../common.defs - -.. toctree:: - :maxdepth: 2 - - -The :file:`remap.yaml` file (by default, located in -``/usr/local/etc/trafficserver/``) provides a YAML-based alternative to -:file:`remap.config` for configuring URL remapping rules. Traffic Server -uses these mapping rules to perform the following actions: - -- Map URL requests for a specific origin server to the appropriate - location on Traffic Server when Traffic Server acts as a reverse - proxy for that particular origin server -- Reverse-map server location headers so that when origin servers - respond to a request with a location header that redirects the client - to another location, the clients do not bypass Traffic Server -- Redirect HTTP requests permanently or temporarily without Traffic - Server having to contact any origin servers - -Refer to :ref:`reverse-proxy-and-http-redirects`, for information about -redirecting HTTP requests and using reverse proxy. - -After you modify the :file:`remap.yaml` run the -:option:`traffic_ctl config reload` to apply the changes. The current configuration is replaced -with the new configuration only if there are no errors in the file. Any syntax error will prevent -an update. Even if syntactically correct the file is considered valid only if it has at least :ts:cv:`proxy.config.url_remap.min_rules_required` -rules in it. This defaults to 0, but can be set higher if it is desirable to prevent loading an -empty or missing file. - -Configuration File Fallback -============================ - -Traffic Server will attempt to load :file:`remap.yaml` first. If this file is not found, -it will fall back to loading :file:`remap.config`. If both files exist, only -:file:`remap.yaml` will be used. This allows for a gradual migration from the legacy -configuration format to YAML. - -Format -====== - -The :file:`remap.yaml` file uses YAML syntax with two main sections: - -- ``acl_filters`` (optional): Global named filter definitions -- ``remap`` (required): Sequence of remapping rules - -Basic Structure ---------------- - -.. code-block:: yaml - - # Optional: Global named filter definitions - acl_filters: - filter_name: - # filter definition - - # Required: Remapping rules - remap: - - type: map - from: - url: http://example.com/foo - to: - url: http://backend.com/bar - - type: redirect - from: - url: old.example.com - to: - url: new.example.com - -Comments and Empty Lines -------------------------- - -Lines starting with ``#`` are comments and are ignored. Empty lines are also ignored. -Unlike :file:`remap.config`, YAML does not support line continuation with ``\``. - -Rule Structure -============== - -Each remap rule is a YAML mapping with the following fields: - -``type`` --------- - -Specifies the type of remapping rule. Required field. One of: - -- ``map`` -- translates an incoming request URL to the appropriate - origin server URL. - -- ``map_with_recv_port`` -- exactly like 'map' except that it uses the port at - which the request was received to perform the mapping instead of the port present - in the request. The ``regex_`` prefix can also be used for this type. When present, - 'map_with_recv_port' mappings are checked first. - -- ``map_with_referer`` -- extended version of 'map', which can be used to activate - "deep linking protection", where target URLs are only accessible when the Referer - header is set to a URL that is allowed to link to the target. - -- ``reverse_map`` -- translates the URL in origin server redirect - responses to point to the Traffic Server. - -- ``redirect`` -- redirects HTTP requests permanently without having - to contact the origin server. Permanent redirects notify the - browser of the URL change (by returning an HTTP status code 301) - so that the browser can update bookmarks. - -- ``redirect_temporary`` -- redirects HTTP requests temporarily - without having to contact the origin server. Temporary redirects - notify the browser of the URL change for the current request only - (by returning an HTTP status code 307). - -.. note:: use the ``regex_`` prefix for the type to indicate that the rule uses regular expressions. - -``from`` --------- - -Specifies the request ("from") URL as a YAML mapping. URL can be defined as a single URL or in components. -Single URL takes precedence over components: - -- ``url``: scheme://host:port/path - -URL components: - -- ``scheme`` (optional): ``http``, ``https``, ``ws``, ``wss``, or ``tunnel``. -- ``host`` (optional for forward maps with path, required otherwise): Hostname or IP address -- ``port`` (optional): Port number -- ``path`` (optional): Path prefix - -Example: - -.. code-block:: yaml - - from: - url: http://example.com:80/foo - from: - scheme: http - host: example.com - port: 80 - path: /foo - -.. note:: A remap rule for requests that upgrade from HTTP to WebSocket still require a remap rule with the ``ws`` or ``wss`` scheme. - -``to`` ------- - -Specifies the origin ("to") URL as a YAML mapping. Required field. - -URL components are the same as ``from``. - -Example: - -.. code-block:: yaml - - to: - url: http://backend.example.com:8080/bar - to: - scheme: http - host: backend.example.com - port: 8080 - path: /bar - -Optional Fields ---------------- - -``acl_filter`` -~~~~~~~~~~~~~~ - -Inline ACL filter definition for a single remap rule. See `ACL Filters`_ for details. - -.. code-block:: yaml - - acl_filter: - src_ip: - - 10.0.0.0/8 - method: - - GET - - POST - action: allow - -``plugins`` -~~~~~~~~~~~ - -Sequence of plugin configurations. Each plugin has: - -- ``name`` (required): Plugin filename -- ``params`` (optional): List of plugin parameters - -.. code-block:: yaml - - plugins: - - name: header_rewrite.so - params: - - config.txt - - param2 - - name: another_plugin.so - params: - - param1 - -``strategy`` -~~~~~~~~~~~~ - -NextHop selection strategy name. See :doc:`../configuration/hierarchical-caching.en` and :doc:`strategies.yaml.en`. - -.. code-block:: yaml - - strategy: my_strategy - -``redirect`` -~~~~~~~~~~~~ - -Used with ``map_with_referer`` type. Specifies redirect URL and allowed referer patterns. - -.. code-block:: yaml - - redirect: - url: http://example.com/denied # or "default" for default redirect URL - regex: - - "~https://trusted\\.com/.*" # regex pattern - - "~" # allow any referer (makes referer optional) - -Precedence -========== - -Remap rules in :file:`remap.yaml` follow the same precedence order as :file:`remap.config`: - -1. ``map_with_recv_port`` and ``regex_map_with_recv_port`` -#. ``map`` and ``regex_map`` and ``reverse_map`` -#. ``redirect`` and ``redirect_temporary`` -#. ``regex_redirect`` and ``regex_redirect_temporary`` - -For each precedence group the rules are checked in two phases. If the first phase fails to find a -match then the second phase is performed against the same group of rules. In the first phase the -rules are checked using the host name of the request. Only rules that specify a host name can match. -If there is no match in that phase, then the rules are checked again with no host name and only -rules without a host will match. The result is that rules with an explicit host take precedence over -rules without. - -Match-All -========= - -A map rule with only a path of ``/`` acts as a wildcard, it will match any -request. This should be use with care, and certainly only once at the -end of the remap section. E.g. - -.. code-block:: yaml - - remap: - - type: map - from: - url: / - to: - url: http://all.example.com - -:file:`remap.config` equivalent :: - - map / http://all.example.com - -Examples -======== - -The following sections show example mapping rules in the :file:`remap.yaml` file. - -Reverse Proxy Mapping Rules ----------------------------- - -The following example shows a map rule that does not specify a path -prefix in the target or replacement: - -.. code-block:: yaml - - remap: - - type: map - from: - url: http://www.x.com/ - to: - url: http://server.hoster.com/ - - type: reverse_map - from: - url: http://server.hoster.com/ - to: - url: http://www.x.com/ - -:file:`remap.config` equivalent :: - - map http://www.x.com/ http://server.hoster.com/ - reverse_map http://server.hoster.com/ http://www.x.com/ - -This rule results in the following translations: - -================================================ ======================================================== -Client Request Translated Request -================================================ ======================================================== -``http://www.x.com/Widgets/index.html`` ``http://server.hoster.com/Widgets/index.html`` -``http://www.x.com/cgi/form/submit.sh?arg=true`` ``http://server.hoster.com/cgi/form/submit.sh?arg=true`` -================================================ ======================================================== - -The following example shows a map rule with path prefixes specified in -the target: - -.. code-block:: yaml - - remap: - - type: map - from: - url: http://www.y.com/marketing/ - to: - url: http://marketing.y.com/ - - type: reverse_map - from: - url: http://marketing.y.com/ - to: - url: http://www.y.com/marketing/ - - type: map - from: - url: http://www.y.com/sales/ - to: - url: http://sales.y.com/ - - type: reverse_map - from: - url: http://sales.y.com/ - to: - url: http://www.y.com/sales/ - - type: map - from: - url: http://www.y.com/engineering/ - to: - url: http://engineering.y.com/ - - type: reverse_map - from: - url: http://engineering.y.com/ - to: - url: http://www.y.com/engineering/ - - type: map - from: - url: http://www.y.com/stuff/ - to: - url: http://info.y.com/ - - type: reverse_map - from: - url: http://info.y.com/ - to: - url: http://www.y.com/stuff/ - -:file:`remap.config` equivalent :: - - map http://www.y.com/marketing/ http://marketing.y.com/ - reverse_map http://marketing.y.com/ http://www.y.com/marketing/ - map http://www.y.com/sales/ http://sales.y.com/ - reverse_map http://sales.y.com/ http://www.y.com/sales/ - map http://www.y.com/engineering/ http://engineering.y.com/ - reverse_map http://engineering.y.com/ http://www.y.com/engineering/ - map http://www.y.com/stuff/ http://info.y.com/ - reverse_map http://info.y.com/ http://www.y.com/stuff/ - -These rules result in the following translations: - -=============================================================== ========================================================== -Client Request Translated Request -=============================================================== ========================================================== -``http://www.y.com/marketing/projects/manhattan/specs.html`` ``http://marketing.y.com/projects/manhattan/specs.html`` -``http://www.y.com/stuff/marketing/projects/boston/specs.html`` ``http://info.y.com/marketing/projects/boston/specs.html`` -=============================================================== ========================================================== - -The following example shows that the order of the rules matters: - -.. code-block:: yaml - - remap: - - type: map - from: - url: http://www.g.com/ - to: - url: http://external.g.com/ - - type: reverse_map - from: - url: http://external.g.com/ - to: - url: http://www.g.com/ - - type: map - from: - url: http://www.g.com/stuff/ - to: - url: http://stuff.g.com/ - - type: reverse_map - from: - url: http://stuff.g.com/ - to: - url: http://www.g.com/stuff/ - -:file:`remap.config` equivalent :: - - map http://www.g.com/ http://external.g.com/ - reverse_map http://external.g.com/ http://www.g.com/ - map http://www.g.com/stuff/ http://stuff.g.com/ - reverse_map http://stuff.g.com/ http://www.g.com/stuff/ - -These rules result in the following translation. - -================================ ===================================== -Client Request Translated Request -================================ ===================================== -``http://www.g.com/stuff/a.gif`` ``http://external.g.com/stuff/a.gif`` -================================ ===================================== - -In the above examples, the second rule is never applied because all URLs -that match the second rule also match the first rule. The first rule -takes precedence because it appears earlier in the :file:`remap.config` -file. - -This is different if one rule does not have a host. For example consider these rules using the `Match-All`_ rule - -.. code-block:: yaml - - remap: - - type: map - from: - url: / - to: - url: http://127.0.0.1:8001/ - - type: map - from: - url: http://example.com/dist_get_user - to: - url: http://127.0.0.1:8001/denied.html - -These rules are set up to redirect requests to another local process. Using them will result in - -==================================== ===================================== -Client Request Translated Request -==================================== ===================================== -``http://example.com/a.gif`` ``http://127.0.0.1:8001/a.gif`` -``http://example.com/dist_get_user`` ``http://127.0.0.1:8001/denied.html`` -==================================== ===================================== - -For the first request the second rule host matches but the path does not and so the second rule is -not selected. The first rule is then matched in the second phase when the rules are checked without -a host value. - -The second request is matched by the second rule even though the rules have the same base -precedence. Because the first rule does not have a host it will not match in the first phase. The -second rule does have a host that matches the host in the second request along with the other parts -of the URL and is therefore selected in the first phase. - -This will yield the same results if the rules are reversed because the rule selection happens in -different phases making the order irrelevant. - -.. code-block:: yaml - - remap: - - type: map - from: - url: http://example.com/dist_get_user - to: - url: http://127.0.0.1:8001/denied.html - - type: map - from: - url: / - to: - url: http://127.0.0.1:8001/ - -The following example shows a mapping with a path prefix specified in -the target and replacement - -.. code-block:: yaml - - remap: - - type: map - from: - url: http://www.h.com/a/b/ - to: - url: http://server.h.com/customers/x/y - - type: reverse_map - from: - url: http://server.h.com/customers/x/y/ - to: - url: http://www.h.com/a/b/ - -This rule results in the following translation. - -===================================== ================================================== -Client Request Translated Request -===================================== ================================================== -``http://www.h.com/a/b/c/d/doc.html`` ``http://server.h.com/customers/x/y/c/d/doc.html`` -``http://www.h.com/a/index.html`` ``Translation fails`` -===================================== ================================================== - -The following example shows reverse-map rules - -.. code-block:: yaml - - remap: - - type: map - from: - url: www.x.com - to: - url: http://server.hoster.com/x/ - - type: reverse_map - from: - url: http://server.hoster.com/x/ - to: - url: http://www.x.com/ - -These rules result in the following translations. - -================================ ===================================== -Client Request Translated Request -================================ ===================================== -``http://www.x.com/Widgets`` ``http://server.hoster.com/x/Widgets`` -================================ ===================================== - - - -================================ ======================================= ============================= -Client Request Origin Server Header Translated Request -================================ ======================================= ============================= -``http://www.x.com/Widgets`` ``http://server.hoster.com/x/Widgets/`` ``http://www.x.com/Widgets/`` -================================ ======================================= ============================= - -When acting as a reverse proxy for multiple servers, Traffic Server is -unable to route to URLs from older browsers that do not send the -``Host:`` header. As a solution, set the variable :ts:cv:`proxy.config.header.parse.no_host_url_redirect` -in the :file:`records.yaml` file to the URL to which Traffic Server will redirect -requests without host headers. - - -Redirect Mapping Rules ------------------------ - -The following rule permanently redirects all HTTP requests for -``www.company.com`` to ``www.company2.com``: - -.. code-block:: yaml - - remap: - - type: redirect - from: - url: http://www.company.com/ - to: - url: http://www.company2.com/ - -The following rule *temporarily* redirects all HTTP requests for -``www.company1.com`` to ``www.company2.com``: - -.. code-block:: yaml - - remap: - - type: redirect_temporary - from: - url: http://www.company1.com/ - to: - url: http://www.company2.com/ - -Regular Expression (regex) Remap Support -========================================= - -Regular expressions can be specified in remapping rules by using the ``regex_`` prefix -for the rule type, with the same limitations as :file:`remap.config`: - -- Only the ``host`` field can contain a regex; the ``scheme``, - ``port``, and other fields cannot. For path manipulation via regexes, - use the :ref:`admin-plugins-regex-remap`. -- The number of capturing subpatterns is limited to 9. This means that - ``$0`` through ``$9`` can be used as substitution placeholders (``$0`` - will be the entire input string). -- The number of substitutions in the expansion string is limited to 10. -- There is no ``regex_`` equivalent to ``reverse_remap``, so when using - ``regex_map`` you should make sure the reverse path is clear by - setting (:ts:cv:`proxy.config.url_remap.pristine_host_hdr`) - -Examples --------- - -.. code-block:: yaml - - remap: - - type: regex_map - from: - url: http://x([0-9]+).z.com/ - to: - url: http://real-x$1.z.com/ - - type: regex_redirect - from: - url: http://old.(.*).z.com - to: - url: http://new.$1.z.com - -map_with_recv_port -================== - -The ``map_with_recv_port`` type supports two special URL schemes, ``http+unix`` and ``https+unix``. -These are useful if you want to have different mapping rules or different plugin configuration for requests received via Unix Domain Socket. - -Examples --------- - -.. code-block:: yaml - - remap: - - type: map_with_recv_port - from: - url: http://foo.example.com:8000/ - to: - url: http://x.example.com/ - - type: map_with_recv_port - from: - url: http://foo.example.com:8888/ - to: - url: http://y.example.com/ - -Explanation: Requests received on port 8000 and 8888 are forwarded to different servers. - -.. code-block:: yaml - - remap: - - type: map - from: - url: http://foo.example.com/ - to: - url: http://x.example.com/ - plugins: - - name: plugin1.so - - type: map_with_recv_port - from: - url: http+unix://foo.example.com/ - to: - url: http://x.example.com/ - -Explanation: All requests are forwarded to the same server, but plugin1 does not run for requests received via Unix Domain Socket. - -map_with_referer -================ - -'redirect-URL' is a redirection URL specified according to RFC 2616 and can -contain special formatting instructions for run-time modifications of the -resulting redirection URL. All regexes Perl compatible regular expressions, -which describes the content of the "Referer" header which must be -verified. In case an actual request does not have "Referer" header or it -does not match with referer regular expression, the HTTP request will be -redirected to 'redirect-URL'. - -The ``map_with_referer`` type enables "deep linking protection" by validating -the Referer header against regular expressions. - -.. code-block:: yaml - - remap: - - type: map_with_referer - from: - url: client-URL - to: - url: origin-server-URL - redirect: - url: redirect-URL - regex: - - regex1 - - regex2 - -At least one regular expression must be specified. In order to enable the 'deep linking -protection' feature in Traffic Server, configure records.yaml with: - -.. code-block:: yaml - :linenos: - :emphasize-lines: 3 - - records: - http: - referer_filter: 1 - -In order to enable run-time formatting for redirect URL, configure: - -.. code-block:: yaml - :linenos: - :emphasize-lines: 3 - - records: - http: - referer_format_redirect: 1 - -When run-time formatting for redirect-URL is enabled the following format -symbols can be used:: - - %r - to substitute original "Referer" header string - %f - to substitute client-URL from 'map_with_referer' record - %t - to substitute origin-server-URL from 'map_with_referer' record - %o - to substitute request URL to origin server, which was created as - the result of a mapping operation - -Note: There is a special referer type "~*" that can be used to specify that the Referer header is optional in the request. If "~*" referer -was used in map_with_referer mapping, only requests with Referer header will -be verified for validity. If the "~" symbol was specified before a referer -regular expression, it means that the request with a matching referer header -will be redirected to redirectURL. It can be used to create a so-called -negative referer list. If "*" was used as a referer regular expression - -all referrers are allowed. - -Examples --------- - -.. code-block:: yaml - - remap: - - type: map_with_referer - from: - url: http://y.foo.bar.com/x/yy/ - to: - url: http://foo.bar.com/x/yy/ - redirect: - url: http://games.bar.com/new_games - regex: - - ".*\\.bar\\.com" - - "www.bar-friends.com" - -Explanation: Referer header must be in the request, only ".*\\.bar\\.com" and "www.bar-friends.com" are allowed. - -.. code-block:: yaml - - remap: - - type: map_with_referer - from: - url: http://y.foo.bar.com/x/yy/ - to: - url: http://foo.bar.com/x/yy/ - redirect: - url: http://games.bar.com/new_games - regex: - - "*" - - "~.*\\.evil\\.com" - -Explanation: Referer header must be in the request but all referrers are allowed except ".*\\.evil\\.com". - -.. code-block:: yaml - - remap: - - type: map_with_referer - from: - url: http://y.foo.bar.com/x/yy/ - to: - url: http://foo.bar.com/x/yy/ - redirect: - url: http://games.bar.com/error - regex: - - "~*" - - "*" - - "~.*\\.evil\\.com" - -Explanation: Referer header is optional. However, if Referer header exists, only request from ".*\\.evil\\.com" will be redirected to redirect-URL. - -Plugin Chaining -=============== - -Plugins can be configured to be evaluated in a specific order, passing -the results from one to the next (unless a plugin returns 0, then the -"chain" is broken). - -Examples --------- - -.. code-block:: yaml - - remap: - - type: map - from: - url: http://url/path - to: - url: http://url/path - plugins: - - name: /etc/traffic_server/config/plugins/plugin1.so - params: - - "1" - - "2" - - name: /etc/traffic_server/config/plugins/plugin2.so - params: - - "3" - -This will pass "1" and "2" to plugin1.so and "3" to plugin2.so. - -NextHop Selection Strategies -============================= - -You may configure Nexthop or Parent hierarchical caching rules by remap using the -``strategy`` field. See :doc:`../configuration/hierarchical-caching.en` and :doc:`strategies.yaml.en` -for configuration details and examples. - -.. code-block:: yaml - - remap: - - type: map - from: - url: htpp://example.com - to: - url: http://backend.com - strategy: my_strategy - -ACL Filters -=========== - -In-line Filter --------------- - -In-line filters can be created to control access of specific remap rules. The markup -is very similar to that of :file:`ip_allow.yaml`, structured as YAML mappings instead -of directive-based syntax. - -Actions -~~~~~~~ - -Each ACL filter takes one of a number of actions specified by the ``action`` field: - -- ``allow``: This behaves like the ``allow`` action in :file:`ip_allow.yaml` in which a list of allowed methods are - provided. Any request with a method in the list is allowed, while any request with a method not in the list is denied. - The exception to this is if :ts:cv:`proxy.config.url_remap.acl_behavior_policy` is set to ``0``. In this case, the - ``allow`` action is a synonym for ``add_allow``, described below. -- ``add_allow``: This action adds a list of allowed methods to whatever other methods are allowed in a subsequently - matched ACL filter or :file:`ip_allow.yaml` rule. -- ``deny``: This behaves like the ``deny`` action in :file:`ip_allow.yaml` in which a list of denied methods are - provided. Any request with a method in the list is denied, while any request with a method not in the list is allowed. - The exception to this is if :ts:cv:`proxy.config.url_remap.acl_behavior_policy` is set to ``0``. In this case, the - ``deny`` action is a synonym for ``add_deny``, described below. -- ``add_deny``: This action adds a list of denied methods to whatever other methods are denied in a subsequently matched - ACL filter or :file:`ip_allow.yaml` rule. - -Filter Fields -~~~~~~~~~~~~~ - -- ``src_ip`` -- source IP address or CIDR range (can be a list) -- ``src_ip_invert`` -- inverted source IP address or CIDR range (can be a list) -- ``src_ip_category`` -- source IP category name (string) -- ``src_ip_category_invert`` -- inverted source IP category name (string) -- ``in_ip`` -- incoming IP address or CIDR range (can be a list) -- ``in_ip_invert`` -- inverted incoming IP address or CIDR range (can be a list) -- ``method`` -- HTTP method or list of methods -- ``action`` -- action to take (allow, deny, add_allow, add_deny) -- ``internal`` -- boolean, matches internal requests only - -Examples -~~~~~~~~ - -.. code-block:: yaml - - remap: - - type: map - from: - url: http://foo.example.com/neverpost - to: - url: http://foo.example.com/neverpost - acl_filter: - action: deny - method: post - - - type: map - from: - url: http://foo.example.com/onlypost - to: - url: http://foo.example.com/onlypost - acl_filter: - action: allow - method: post - - - type: map - from: - url: http://foo.example.com/ - to: - url: http://foo.example.com/ - acl_filter: - action: deny - src_ip: 1.2.3.4 - - - type: map - from: - url: http://foo.example.com/ - to: - url: http://foo.example.com/ - acl_filter: - action: allow - src_ip: - - 10.5.2.1 - in_ip: - - 72.209.23.4 - - - type: map - from: - url: http://foo.example.com/ - to: - url: http://foo.example.com/ - acl_filter: - action: allow - src_ip: 127.0.0.1 - method: - - post - - get - - head - - - type: map - from: - url: http://foo.example.com/ - to: - url: http://foo.example.com/ - acl_filter: - action: allow - src_ip_category: ACME_INTERNAL - method: - - post - - get - - head - -Note that these ACL filters will return a 403 response if the resource is restricted. - -The difference between ``src_ip`` and ``in_ip`` is that ``src_ip`` is the client -IP and ``in_ip`` is the IP address the client is connecting to (the incoming address). -``src_ip_category`` functions like ``ip_category`` described in :file:`ip_allow.yaml`. -If no IP address is specified for ``src_ip``, ``src_ip_category``, or -``in_ip``, the filter will implicitly apply to all incoming IP addresses. This -can be explicitly stated with ``src_ip: all``. - -Named Filters -------------- - -Named filters can be defined globally in the ``acl_filters`` section and then activated -or deactivated for blocks of mappings using filter directives. - -Filter Directives -~~~~~~~~~~~~~~~~~ - -Filter directives are special entries in the ``remap`` sequence: - -- ``activate_filter: `` -- activates a named filter for subsequent rules -- ``deactivate_filter: `` -- deactivates a named filter -- ``delete_filter: `` -- removes a filter definition -- ``define_filter`` -- defines a new named filter inline - -The ``internal`` operator can be used to filter on whether a request -is generated by |TS| itself, usually by a plugin. This operator -is helpful for remapping internal requests without allowing access -to external users. By default both internal and external requests -are allowed. - -Examples -~~~~~~~~ - -.. code-block:: yaml - - acl_filters: - disable_delete_purge: - action: deny - method: - - delete - - purge - local_only: - action: allow - src_ip: - - 192.168.0.1-192.168.0.254 - - 10.0.0.1-10.0.0.254 - - remap: - - activate_filter: disable_delete_purge - - - type: map - from: - url: http://foo.example.com/ - to: - url: http://bar.example.com/ - - - activate_filter: local_only - - - type: map - from: - url: http://www.example.com/admin - to: - url: http://internal.example.com/admin - - - deactivate_filter: local_only - - - type: map - from: - url: http://www.example.com/ - to: - url: http://internal.example.com/ - - - type: map - from: - url: http://auth.example.com/ - to: - url: http://auth.internal.example.com/ - acl_filter: - action: allow - internal: true - -The filter ``disable_delete_purge`` will be applied to all of the -mapping rules after it is activated. (It is activated before any mappings -and is never deactivated.) The filter ``local_only`` will only be applied to -the ``www.example.com/admin`` mapping. - -Special Filter and ip_allow Named Filter ----------------------------------------- - -If :file:`ip_allow.yaml` has a "deny all" filter, it is treated as a special filter that is applied before remapping for -optimization. To control this for specific remap rules, a named filter called ``ip_allow`` is pre-defined. This named filter is -activated implicitly by default. To stop applying the special rule, disable the ``ip_allow`` filter as shown below. - -.. code-block:: yaml - - # ip_allow.yaml - ip_allow: - - apply: in - ip_addrs: 198.51.100.0/24 - action: deny - method: ALL - -.. code-block:: yaml - - # remap.yaml - remap: - - deactivate_filter: ip_allow - - type: map ... - - type: map ... - - activate_filter: ip_allow - -Note this entirely disables :file:`ip_allow.yaml` checks for those remap rules. - -Evaluation Order and Matching Policy ------------------------------------- - -|TS| evaluates multiple ACL filters in the following order: - -1. Special "deny all" filter in :file:`ip_allow.yaml` -2. In-line Filter in :file:`remap.yaml` -3. Named Filters in :file:`remap.yaml` -4. Filters in :file:`ip_allow.yaml` - -When a matching ACL filter is found, |TS| stops processing subsequent ACL filters. - -Note that step 1 happens at the start of the connection before any transactions are processed, unlike the other rules -here. This is an optimization: if literally all requests are denied for a source IP address via an -:file:`ip_allow.yaml` rule, then there is no need to process any content from that IP for the connection at all, so the -connection is simply denied at the start. - -.. note:: - - The ACL filter behavior in :file:`remap.yaml` is identical to that in :file:`remap.config`. - See :file:`remap.config` for details on ACL Action Behavior Changes for 10.x, Legacy Policy, - Modern Policy, and examples of ACL filter combinations. - -Including Additional Remap Files -================================= - -The ``include`` directive allows mapping rules to be spread across -multiple files. The argument to the ``include`` directive is a file path -or directory path. Unless the path is absolute, it is resolved relative to the -Traffic Server configuration directory. - -The effect of the ``include`` directive is as if the contents of -the included file(s) is included in the parent and parsing restarted -at the point of inclusion. This means that any filters defined in the -included files are global in scope, and that additional ``include`` -directives are allowed. - -.. note:: - - Included remap files are currently tracked by the configuration - subsystem. Changes to included remap files will be noticed - by online configuration changes applied by :option:`traffic_ctl config reload`. - -Examples --------- - -In a top-level :file:`remap.yaml` file: - -.. code-block:: yaml - - remap: - - include: filters.yaml - - include: one.example.com.yaml - - include: two.example.com.yaml - - include: /path/to/remap_fragments/ # directory - -The file ``filters.yaml`` contains: - -.. code-block:: yaml - - acl_filters: - deny_purge: - action: deny - method: purge - allow_purge: - action: allow - method: purge - -The file ``one.example.com.yaml`` contains: - -.. code-block:: yaml - - remap: - - activate_filter: deny_purge - - type: map - from: - url: http://one.example.com - to: - url: http://origin-one.example.com - - deactivate_filter: deny_purge - -The file ``two.example.com.yaml`` contains: - -.. code-block:: yaml - - remap: - - activate_filter: allow_purge - - type: map - from: - url: http://two.example.com - to: - url: http://origin-two.example.com - - deactivate_filter: allow_purge - -Migration from remap.config -============================ - -The :file:`remap.yaml` format provides equivalent functionality to :file:`remap.config` -with YAML structure. Here are some common patterns: - -remap.config to remap.yaml ---------------------------- - -.. list-table:: - :header-rows: 1 - :widths: 50 50 - - * - remap.config - - remap.yaml - * - :: - - map http://www.x.com/ http://server.com/ - - .. code-block:: yaml - - remap: - - type: map - from: - url: http://www.x.com/ - to: - url: http://server.com/ - - * - :: - - redirect http://old.com/ http://new.com/ - - .. code-block:: yaml - - remap: - - type: redirect - from: - url: http://old.com/ - to: - url: http://new.com/ - - * - :: - - map http://example.com/ http://backend.com/ \ - @plugin=plugin.so @pparam=arg1 - - - .. code-block:: yaml - - remap: - - type: map - from: - url: http://example.com/ - to: - url: http://backend.com/ - plugins: - - name: plugin.so - params: - - arg1 - - * - :: - - .definefilter my_filter @action=allow @src_ip=10.0.0.0/8 - .activatefilter my_filter - map http://example.com/ http://backend.com/ - - - .. code-block:: yaml - - acl_filters: - my_filter: - action: allow - src_ip: 10.0.0.0/8 - - remap: - - activate_filter: my_filter - - type: map - from: - url: http://example.com/ - to: - url: http://backend.com/ diff --git a/doc/admin-guide/files/ssl_multicert.yaml.en.rst b/doc/admin-guide/files/ssl_multicert.config.en.rst similarity index 76% rename from doc/admin-guide/files/ssl_multicert.yaml.en.rst rename to doc/admin-guide/files/ssl_multicert.config.en.rst index 2c27d7e4a98..d78bc75fc16 100644 --- a/doc/admin-guide/files/ssl_multicert.yaml.en.rst +++ b/doc/admin-guide/files/ssl_multicert.config.en.rst @@ -15,13 +15,13 @@ specific language governing permissions and limitations under the License. -================== -ssl_multicert.yaml -================== +==================== +ssl_multicert.config +==================== -.. configfile:: ssl_multicert.yaml +.. configfile:: ssl_multicert.config -The :file:`ssl_multicert.yaml` file lets you configure Traffic +The :file:`ssl_multicert.config` file lets you configure Traffic Server to use multiple SSL server certificates to terminate the SSL sessions. If you have a Traffic Server system with more than one IP address assigned to it, then you can assign a different SSL @@ -36,17 +36,17 @@ found in the certificate. Wildcard names are supported, but only of the form `*.domain.com`, ie. where `*` is the leftmost domain component. -Changes to :file:`ssl_multicert.yaml` can be applied to a running +Changes to :file:`ssl_multicert.config` can be applied to a running Traffic Server using :option:`traffic_ctl config reload`. Format ====== -The :file:`ssl_multicert.yaml` file uses YAML format with a top-level -``ssl_multicert`` key containing a sequence of certificate entries. -Each entry is a mapping with the following keys: +Each :file:`ssl_multicert.config` line consists of a sequence of +`key=value` fields that specify how Traffic Server should use a +particular SSL certificate. -ssl_cert_name: FILENAME[,FILENAME ...] +ssl_cert_name=FILENAME[,FILENAME ...] The name of the file containing the TLS certificate. *FILENAME* is located relative to the directory specified by the :ts:cv:`proxy.config.ssl.server.cert.path` configuration variable. @@ -68,10 +68,9 @@ ssl_cert_name: FILENAME[,FILENAME ...] You can also configure multiple leaf certificates in a same chain with OpenSSL 1.0.1. - This is the only field that is required to be present (unless - ``action`` is set to ``tunnel``). + This is the only field that is required to be present. -dest_ip: ADDRESS (optional) +dest_ip=ADDRESS (optional) The IP (v4 or v6) address that the certificate should be presented on. This is now only used as a fallback in the case that the TLS ServerNameIndication extension is not supported. If *ADDRESS* is @@ -82,19 +81,19 @@ dest_ip: ADDRESS (optional) IPv6 addresses must be enclosed by square brackets if they have a port, eg, [::1]:80. Care should be taken to make each ADDRESS unique. -ssl_key_name: FILENAME (optional) +ssl_key_name=FILENAME (optional) The name of the file containing the private key for this certificate. If the key is contained in the certificate file, this field can be omitted, otherwise *FILENAME* is resolved relative to the :ts:cv:`proxy.config.ssl.server.private_key.path` configuration variable. -ssl_ca_name: FILENAME (optional) +ssl_ca_name=FILENAME (optional) If the certificate is issued by an authority that is not in the system CA bundle, additional certificates may be needed to validate the certificate chain. *FILENAME* is resolved relative to the :ts:cv:`proxy.config.ssl.CA.cert.path` configuration variable. -ssl_ocsp_name: FILENAME (optional) +ssl_ocsp_name=FILENAME (optional) The name of the file containing the prefetched OCSP stapling response for this certificate. This field can be omitted to let trafficserver fetch OCSP responses dynamically. Otherwise, when included, the administrator is @@ -102,16 +101,16 @@ ssl_ocsp_name: FILENAME (optional) relative to the :ts:cv:`proxy.config.ssl.ocsp.response.path` configuration variable. -ssl_ticket_enabled: 1|0 (optional) +ssl_ticket_enabled=1|0 (optional) Enable RFC 5077 stateless TLS session tickets. To support this, OpenSSL should be upgraded to version 0.9.8f or higher. This option must be set to `0` to disable session ticket support. -ssl_ticket_number: INTEGER (optional) +ssl_ticket_number=INTEGER (optional) Specifies the number of TLSv1.3 session tickets that are issued. This defaults to 2 (the OpenSSL default) -ssl_key_dialog: builtin|"exec:/path/to/program [args]" (optional) +ssl_key_dialog=builtin|"exec:/path/to/program [args]" (optional) Method used to provide a pass phrase for encrypted private keys. If the pass phrase is incorrect, SSL negotiation for this dest_ip will fail for clients who attempt to connect. @@ -129,10 +128,6 @@ ssl_key_dialog: builtin|"exec:/path/to/program [args]" (optional) program runs a security check to ensure that the system is not compromised by an attacker before providing the pass phrase. -action: tunnel (optional) - If set to ``tunnel``, Traffic Server will not participate in the - TLS handshake and will blind tunnel the connection instead. - Certificate Selection ===================== @@ -168,27 +163,19 @@ are terminated with the ``default.pem`` certificate. Since the private key is included in the certificate files, no private key name is specified. -.. code-block:: yaml - - ssl_multicert: - - ssl_cert_name: server.pem - dest_ip: "111.11.11.1" - - - ssl_cert_name: server1.pem - dest_ip: "11.1.1.1" +:: - - ssl_cert_name: default.pem - dest_ip: "*" + dest_ip=111.11.11.1 ssl_cert_name=server.pem + dest_ip=11.1.1.1 ssl_cert_name=server1.pem + dest_ip=* ssl_cert_name=default.pem The following example configures Traffic Server to use the ECDSA certificate chain ``ecdsa.pem`` or RSA certificate chain ``rsa.pem`` for all requests. -.. code-block:: yaml +:: - ssl_multicert: - - ssl_cert_name: ecdsa.pem,rsa.pem - dest_ip: "*" + dest_ip=* ssl_cert_name=ecdsa.pem,rsa.pem The following example configures Traffic Server to use the ECDSA certificate chain ``ecdsa.pem`` or RSA certificate chain ``rsa.pem`` @@ -196,46 +183,35 @@ for all requests, the public key and private key are in separate PEM files. Note that the number of files in ssl_key_name must match the files in ssl_cert_name, and they should be presented in the same order. -.. code-block:: yaml +:: - ssl_multicert: - - ssl_cert_name: ecdsa_pub.pem,rsa_pub.pem - ssl_key_name: ecdsa_private.pem,rsa_private.pem - dest_ip: "*" + dest_ip=* ssl_cert_name=ecdsa_pub.pem,rsa_pub.pem ssl_key_name=ecdsa_private.pem,rsa_private.pem The following example configures Traffic Server to use the SSL certificate ``server.pem`` and the private key ``serverKey.pem`` for all requests to port 8443 on IP address 111.11.11.1. The ``general.pem`` certificate is used for server name matches. -.. code-block:: yaml +:: - ssl_multicert: - - ssl_cert_name: server.pem,general.pem - ssl_key_name: serverKey.pem - dest_ip: "111.11.11.1:8443" + dest_ip=111.11.11.1:8443 ssl_cert_name=server.pem ssl_key_name=serverKey.pem ssl_cert_name=general.pem The following example configures Traffic Server to use the SSL certificate ``server.pem`` for all requests to the IP address -111.11.11.1. Session tickets are enabled. +111.11.11.1. Session tickets are enabled with a persistent ticket +key. -.. code-block:: yaml +:: - ssl_multicert: - - ssl_cert_name: server.pem - dest_ip: "111.11.11.1" - ssl_ticket_enabled: 1 + dest_ip=111.11.11.1 ssl_cert_name=server.pem ssl_ticket_enabled=1 ticket_key_name=ticket.key The following example configures Traffic Server to use the SSL certificate ``server.pem`` and disable session tickets for all requests to the IP address 111.11.11.1. -.. code-block:: yaml +:: - ssl_multicert: - - ssl_cert_name: server.pem - dest_ip: "111.11.11.1" - ssl_ticket_enabled: 0 + dest_ip=111.11.11.1 ssl_cert_name=server.pem ssl_ticket_enabled=0 The following examples configure Traffic Server to use the SSL certificate ``server.pem`` which includes an encrypted private key. @@ -244,27 +220,7 @@ parameter (foo) in the first example, and with two parameters (foo) and (ba r) in the second example, the program (mypass) will return the pass phrase to decrypt the keys. -.. code-block:: yaml - - ssl_multicert: - - ssl_cert_name: server1.pem - ssl_key_dialog: "exec:/usr/bin/mypass foo" - - - ssl_cert_name: server2.pem - ssl_key_dialog: "exec:/usr/bin/mypass foo 'ba r'" - -Migration from ssl_multicert.config -=================================== - -The ``traffic_ctl`` command can be used to convert pre-11.x ``ssl_multicert.config`` -files to the new YAML format: - -.. code-block:: bash - - traffic_ctl config convert ssl_multicert ssl_multicert.config ssl_multicert.yaml - -To output to stdout instead of a file, use ``-`` as the output path: - -.. code-block:: bash +:: - traffic_ctl config convert ssl_multicert ssl_multicert.config - + ssl_cert_name=server1.pem ssl_key_dialog="exec:/usr/bin/mypass foo" + ssl_cert_name=server2.pem ssl_key_dialog="exec:/usr/bin/mypass foo 'ba r'" diff --git a/doc/admin-guide/files/storage.config.en.rst b/doc/admin-guide/files/storage.config.en.rst new file mode 100644 index 00000000000..2fd0a2d674c --- /dev/null +++ b/doc/admin-guide/files/storage.config.en.rst @@ -0,0 +1,186 @@ +.. Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +============== +storage.config +============== + +.. configfile:: storage.config + +The :file:`storage.config` file (by default, located in +``/usr/local/etc/trafficserver/``) lists all the files, directories, and/or +hard disk partitions that make up the Traffic Server cache. After you +modify the :file:`storage.config` file the new settings will not be effective until Traffic Server is restarted. + +Format +====== + +The format of the :file:`storage.config` file is a series of lines of the form + + *pathname* *size* [ ``volume=``\ *number* ] [ ``id=``\ *string* ] + +where :arg:`pathname` is the name of a partition, directory or file, :arg:`size` is the size of the +named partition, directory or file (in bytes), and :arg:`volume` is the volume number used in the +files :file:`volume.config` and :file:`hosting.config`. :arg:`id` is used for seeding the +:ref:`assignment-table`. You must specify a size for directories; size is optional for files and raw +partitions. :arg:`volume` and :arg:`id` are optional. + +.. note:: + + The :arg:`volume` option is independent of the :arg:`id` option and either can be used with or without the other, + and their ordering on the line is irrelevant. + +.. note:: + + If the :arg:`id` option is used every use must have a unique value for :arg:`string`. + +.. note:: + + Any change to this files can (and almost always will) invalidate the existing cache in its entirety. + +You can use any partition of any size. For best performance: + +- Use raw disk partitions. +- For each disk, make all partitions the same size. +- For each node, use the same number of partitions on all disks. +- Group similar kinds of storage into different volumes. For example + split out SSD's or RAM drives into their own volume. + +Specify pathnames according to your operating system requirements. See +the following examples. In the :file:`storage.config` file, a formatted or +raw disk must be at least 128 MB. + +When using raw disk or partitions, you should make sure the :ts:cv:`Traffic +Server user ` used by the Traffic Server process +has read and write privileges on the raw disk device or partition. One good +practice is to make sure the device file is set with 'g+rw' and the Traffic +Server user is in the group which owns the device file. However, some +operating systems have stronger requirements - see the following examples for +more information. + +As with standard ``records.yaml`` integers, human readable prefixes are also +supported. They include + + - ``K`` Kilobytes (1024 bytes) + - ``M`` Megabytes (1024^2 or 1,048,576 bytes) + - ``G`` Gigabytes (1024^3 or 1,073,741,824 bytes) + - ``T`` Terabytes (1024^4 or 1,099,511,627,776 bytes) + +.. _assignment-table: + +Assignment Table +---------------- + +Each storage element defined in :file:`storage.config` is divided in to :term:`stripes `. The +assignment table maps from an object URL to a specific stripe. The table is initialized based on a +pseudo-random process which is seeded by hashing a string for each stripe. This string is composed +of a base string, an offset (the start of the stripe on the storage element), and the length of the +stripe. By default the path for the storage is used as the base string. This ensures that each +stripe has a unique string for the assignment hash. This does make the assignment table very +sensitive to the path for the storage elements and changing even one can have a cascading effect +which will effectively clear most of the cache. This can be problem when drives fail and a system +reboot causes the path names to change. + +The :arg:`id` option can be used to create a fixed string that an administrator can use to keep the +assignment table consistent by maintaining the mapping from physical device to base string even in the presence of hardware changes and failures. + +Examples +======== + +The following basic example shows 128 MB of cache storage in the +``/big_dir`` directory:: + + /big_dir 134217728 + +You can use the ``.`` symbol for the current directory. Here is an +example for 64 MB of cache storage in the current directory:: + + . 134217728 + +As an alternative, using the human readable prefixes, you can express a 64GB +cache file with:: + + /really_big_dir 64G + + +.. note:: + When using on-filesystem cache disk storage, you can only have one such + directory specified. This will be addressed in a future version. + +Linux Example +------------- +.. note:: + Rather than refer to disk devices like ``/dev/sda``, ``/dev/sdb``, etc., + modern Linux supports `alternative symlinked names for disk devices + `_ in the ``/dev/disk`` + directory structure. As noted for the :ref:`assignment-table` the path used for the disk can effect + the cache if it changes. This can be ameliorated in some cases by using one of the alternate paths + in via ``/dev/disk``. Note that if the ``by-id`` or ``by-path`` style is used, replacing a failed drive will cause + that path to change because the new drive will have a different physical ID or path. The original hash string can + be kept by adding :arg:`id` or :arg:`path` with the original path to the storage line. + + If this is not sufficient then the :arg:`id` or :arg:`path` argument should be used to create a more permanent + assignment table. An example would be:: + + /dev/sde id=cache.disk.0 + /dev/sdg id=cache.disk.1 + +The following example will use an entire raw disk in the Linux operating +system:: + + /dev/disk/by-id/[DiskA_ID] volume=1 + /dev/disk/by-path/[DiskB_Path] volume=2 + +In order to make sure :program:`traffic_server` will have access to this disk +you can use :manpage:`udev(7)` to persistently set the right permissions. The +following rules are targeted for an Ubuntu system, and stored in +``/etc/udev/rules.d/51-cache-disk.rules``:: + + # Assign DiskA and DiskB to the tserver group + # make the assignment final, no later changes allowed to the group! + SUBSYSTEM=="block", KERNEL=="sd[ef]", GROUP:="tserver" + +In order to apply these settings, trigger a reload with :manpage:`udevadm(8)`::: + + udevadm trigger --subsystem-match=block + + +FreeBSD Example +--------------- + +Starting with 5.1 FreeBSD dropped support for explicit raw devices. All +devices on FreeBSD can be accessed raw now. + +The following example will use an entire raw disk in the FreeBSD +operating system:: + + /dev/ada1 + /dev/ada2 + +In order to make sure :program:`traffic_server` will have access to this disk +you can use :manpage:`devfs(8)` to persistently set the right permissions. The +following rules are stored in :manpage:`devfs.conf(5)`:: + + # Assign /dev/ada1 and /dev/ada2 to the tserver user + own ada[12] tserver:tserver + +Advanced +-------- + +Because relative paths in :file:`storage.config` are relative to the base prefix, when using customized runroot +it may be necessary to adjust such paths in :file:`storage.config` or adjust ``runroot.yaml`` itself. +Despite the name, the cachedir value is not used for this file. diff --git a/doc/admin-guide/files/storage.yaml.en.rst b/doc/admin-guide/files/storage.yaml.en.rst deleted file mode 100644 index cbbbde4a705..00000000000 --- a/doc/admin-guide/files/storage.yaml.en.rst +++ /dev/null @@ -1,561 +0,0 @@ -.. Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - -.. include:: ../../common.defs - -============== -storage.yaml -============== - -.. configfile:: storage.yaml - -The :file:`storage.yaml` file (by default, located in -``/usr/local/etc/trafficserver/``) lists all the files, directories, and/or -hard disk partitions that make up the Traffic Server cache. After you -modify the :file:`storage.yaml` file the new settings will not be effective until Traffic Server is restarted. - -Format -====== - -The format of the :file:`storage.yaml` file is a series of lines of the form - -.. code-block:: yaml - - cache: # file level key - spans: # - - name: # name of the span - path: # path to storage - size: # size in bytes, required for file system storage, optional for raw device - hash_seed: # optional, used to isolate lookup from path changes - volumes: # optional - - id: # identifier [1-255] - size: # optional, size in bytes or percentage - scheme: # optional, default to "http" - ram_cache: # optional, default to "true" - avg_obj_size: # optional, overrides proxy.config.cache.min_average_object_size - fragment_size: # optional, overrides proxy.config.cache.target_fragment_size - spans: # optional - - use: # Span identifier - size: # size allocated to this volume - -:code:`spans` lists the raw storage used for the cache. :code:`volumes` organizes the storage into locations for -storing cached objects. This is very similar to operating system partitions and file systems. - -For :code:`spans` the keys are - -+---------------+-------------+-------------------------------------------------------------+ -| Key | Type | Meaning | -+===============+=============+=============================================================+ -| name | string | Name of the span. | -+---------------+-------------+-------------------------------------------------------------+ -| path | string | File system of the storage. This must be a block device or | -| | | directory. | -+---------------+-------------+-------------------------------------------------------------+ -| size | bytes | Size in bytes. This is optional for devices but required | -| | | for directories. | -+---------------+-------------+-------------------------------------------------------------+ -| hash_seed | string | Hashing for object location uses a seed to randomize the | -| | | hash. By default this is the path for the span. | -+---------------+-------------+-------------------------------------------------------------+ - -For :code:`volumes` the keys are - -+------------------+-------------+---------------------------------------------------------------------------------------------------------+ -| Key | Type | Meaning | -+==================+=============+=========================================================================================================+ -| id | integer | Id of the volume. Range is [1-255]. This id can be referred | -| | | from :file:`hosting.config`. Each volume must have a distinct id; volume number 0 is reserved | -| | | for internal use. | -+------------------+-------------+---------------------------------------------------------------------------------------------------------+ -| size | bytes | Target size of the entire volume. This can be an absolute | -| | _or_ | number of bytes or a percentage. | -| | percentage | | -+------------------+-------------+---------------------------------------------------------------------------------------------------------+ -| scheme | enumeration | Protocol scheme, defaults to "http". Preserved for future | -| | string | use. | -+------------------+-------------+---------------------------------------------------------------------------------------------------------+ -| ram_cache | boolean | Control of ram caching for this volume. Default is ``true``. This may be desirable if you are using | -| | | something like ramdisks, to avoid wasting RAM and CPU time on double caching objects. | -+------------------+-------------+---------------------------------------------------------------------------------------------------------+ -| ram_cache_size | bytes | Allocate a dedicated RAM cache pool for this volume. | -| | | The specified amount is automatically subtracted from the global RAM cache pool; the remainder is | -| | | shared among volumes without a private allocation. Setting to ``0`` disables the RAM cache for this | -| | | volume (equivalent to ``ram_cache: false``). If the sum of all per-volume allocations exceeds the | -| | | global limit, Traffic Server will fail to start with a fatal error. If ``ram_cache: false`` is also | -| | | set, this value is ignored with a warning. Only takes effect when the global size is a positive value | -| | | (not ``-1`` for automatic sizing). | -+------------------+-------------+---------------------------------------------------------------------------------------------------------+ -| ram_cache_cutoff | bytes | Overrides the global :ts:cv:`proxy.config.cache.ram_cache_cutoff` configuration for this volume. | -| | | Objects larger than this size will not be placed in the RAM cache for this volume. Use a larger | -| | | cutoff for volumes serving frequently accessed large objects, a smaller cutoff for volumes with many | -| | | small objects to maximize RAM cache hits, or a very low cutoff to effectively disable RAM caching for | -| | | large objects on that volume. | -+------------------+-------------+---------------------------------------------------------------------------------------------------------+ -| avg_obj_size | integer | Overrides the global :ts:cv:`proxy.config.cache.min_average_object_size` configuration for this volume. | -| | | This is useful if you have a volume that is dedicated for say very small objects, and you need a lot of | -| | | directory entries to store them. | -+------------------+-------------+---------------------------------------------------------------------------------------------------------+ -| fragment_size | integer | Overrides the global :ts:cv:`proxy.config.cache.target_fragment_size` configuration for this volume. | -| | | This allows for a smaller, or larger, fragment size for a particular volume. This may be useful | -| | | together with ``avg_obj_size`` as well, since a larger fragment size could reduce the number of | -| | | directory entries needed for a large object. Note that this setting has a maximmum value of 4MB. | -+------------------+-------------+---------------------------------------------------------------------------------------------------------+ -| spans | list | Spans that provide storage for this volume. Defaults to | -| | | all spans. | -+------------------+-------------+---------------------------------------------------------------------------------------------------------+ - -For :code:`volumes:spans` the keys are - -+---------------+-------------+-------------------------------------------------------------+ -| Key | Type | Meaning | -+===============+=============+=============================================================+ -| use | string | Name of the span to use. | -+---------------+-------------+-------------------------------------------------------------+ -| size | bytes | Amount of the span to use. The total across all uses of | -| | _or_ | this specific span must be less than 100% and less than the | -| | percentage | total size of the span. | -+---------------+-------------+-------------------------------------------------------------+ - -Each volume is striped across several disks to achieve parallel I/O. For -example: if there are four disks, then a 1-GB volume will have 256 MB on -each disk (assuming each disk has enough free space available). If you -do not allocate all the disk space in the cache, then the extra disk -space is not used. You can use the extra space later to create new -volumes without deleting and clearing the existing volumes. - -.. important:: - - Any change to this file can (and almost always will) invalidate the existing cache in its entirety. - -You can use any partition of any size. For best performance: - -- Use raw disk partitions. -- For each disk, make all partitions the same size. -- Group similar kinds of storage into different volumes. For example - split out SSD's or RAM drives into their own volume. - -Specify pathnames according to your operating system requirements. See -the following examples. In the :file:`storage.yaml` file, a formatted or -raw disk must be at least 128 MB. - -When using raw disk or partitions, you should make sure the :ts:cv:`Traffic -Server user ` used by the Traffic Server process -has read and write privileges on the raw disk device or partition. One good -practice is to make sure the device file is set with 'g+rw' and the Traffic -Server user is in the group which owns the device file. However, some -operating systems have stronger requirements - see the following examples for -more information. - -As with standard ``records.yaml`` integers, human readable prefixes are also -supported. They include - - - ``K`` Kilobytes (1024 bytes) - - ``M`` Megabytes (1024^2 or 1,048,576 bytes) - - ``G`` Gigabytes (1024^3 or 1,073,741,824 bytes) - - ``T`` Terabytes (1024^4 or 1,099,511,627,776 bytes) - -Storage Allocation ------------------- - -Allocation of span storage to volumes is done in stages. Storage is always allocated in multiples of 128 megabytes, -rounded down. - -* Explicitly sized span storage (:code:`cache:volumes:spans:size`) is allocated to volumes. It is an error if the total allocated is larger than the span size. - * Absolute sizes are allocated first. - * Percentages are allocated from remaining space. - * Remaining storage from spans that are used without an explicit size is divided evenly among the volumes that use the span. -* Span storage is allocated to volumes by the :code:`cache:volumes:size` values. - * Absolute sizes are allocated first. - * Percentages are applied to remaining space. - * Remaining storage is divided evenly among volumes without an explicit size. - -.. _storage-assignment-table: - -Assignment Table ----------------- - -Each storage element defined in :file:`storage.yaml` is divided into :term:`stripes `. The -assignment table maps from an object URL to a specific stripe. The table is initialized based on a -pseudo-random process which is seeded by hashing a string for each stripe. This string is composed -of a base string, an offset (the start of the stripe on the storage element), and the length of the -stripe. By default the path for the storage is used as the base string. This ensures that each -stripe has a unique string for the assignment hash. This does make the assignment table very -sensitive to the path for the storage elements and changing even one can have a cascading effect -which will effectively clear most of the cache. This can be a problem when drives fail and a system -reboot causes the path names to change. - -The :arg:`name` option can be used to create a fixed string that an administrator can use to keep the -assignment table consistent by maintaining the mapping from physical device to base string even in the presence of hardware changes and failures. - -Backwards Compatibility ------------------------ - -In previous versions of |TS| it was possible to have "exclusive" spans which were used by only one volume. This is -now done by specifying the span in the volume and using a size of "100%". - -.. important:: - - In the old volume.config, absolute sizes were specified in **megabytes** (e.g., ``size=512`` - meant 512 MB). In :file:`storage.yaml`, sizes are specified in **bytes** with optional human-readable - suffixes (e.g., ``size: 512M`` or ``size: 536870912``). A bare number without a suffix is interpreted - as bytes, not megabytes. - -E.g. old configuration like :: - - /dev/disk2 volume=3 # storage.config - volume=3 scheme=http size=512 # volume.config - -The corresponding configuration would be - -.. code-block:: yaml - - cache: - spans: - - name: disk.2 - path: /dev/disk2 - volumes: - - id: 3 - spans: - - use: disk.2 - size: 512M - -Because volume sizes that are percentages are computed on span storage not already explicitly allocated, this will -leave none of "disk.2" for such allocation and therefore "disk.2" will be used only by volume "1". Note this -configuration is more flexible. If it was useful to have two linear volumes, each using exclusively half of the -span, this would be - -.. code-block:: yaml - - cache: - spans: - - name: disk.2 - path: /dev/disk2 - volumes: - - id: 1 - spans: - - use: disk.2 - size: 50% - - id: 2 - spans: - - use: disk.2 - size: 50% - -.. important:: - - If a span is explicitly used by any volume its storage will be allocated to only volumes that explicitly use that span. - -Examples -======== - -The following basic example shows 128 MB of cache storage in the "/big_dir" directory - -.. code-block:: yaml - - cache: - spans: - - name: store - path: /big_dir - size: 134217728 - -As an alternative, using the human readable prefixes, you can express a 64GB cache file with - -.. code-block:: yaml - - cache: - spans: - - name: store - path: /really_big_dir - size: 64G - -By default a volume uses all spans, therefore a volume uses all of span "store" because there are no other -volumes. It would be equivalent to using the spans explicitly, e.g. - -.. code-block:: yaml - - cache: - spans: - - name: store - path: /big_dir - size: 128M - volumes: - - id: 1 - size: 100% - spans: - - use: store - -You can use the ``.`` symbol for the current directory. Here is an example for 128 MB of cache storage in the current directory - -.. code-block:: yaml - - cache: - spans: - - name: store - path: "." - size: 128M - -.. note:: - When using on-filesystem cache disk storage, you can only have one such - directory specified. This will be addressed in a future version. - -Linux Example -------------- -.. note:: - - Rather than refer to disk devices like ``/dev/sda``, ``/dev/sdb``, etc., - modern Linux supports `alternative symlinked names for disk devices - `_ in the ``/dev/disk`` - directory structure. As noted for the :ref:`storage-assignment-table` the path used for the disk can affect - the cache if it changes. This can be ameliorated in some cases by using one of the alternate paths - in via ``/dev/disk``. Note that if the ``by-id`` or ``by-path`` style is used, replacing a failed drive will cause - that path to change because the new drive will have a different physical ID or path. - - If this is not sufficient then the :arg:`hash_seed` key should be used to create a more permanent - assignment table. An example would be - - .. code-block:: yaml - - cache: - spans: - - name: "span.0" - path: "/dev/sde" - hash_seed: "cache.disk.0" - - name: "span.1" - path: "/dev/sdg" - hash_seed: "cache.disk.1" - -The following example will use an entire raw disk in the Linux operating -system - -.. code-block:: yaml - - cache: - spans: - - name: a - path: "/dev/disk/by-id/disk-A-id" - - name: b - path: "/dev/disk/by-id/disk-B-id" - volumes: - - id: 1 - spans: - - use: a - size: 100% - - id: 2 - spans: - - use: b - size: 100% - -In order to make sure :program:`traffic_server` will have access to this disk -you can use :manpage:`udev(7)` to persistently set the right permissions. The -following rules are targeted for an Ubuntu system, and stored in -``/etc/udev/rules.d/51-cache-disk.rules``:: - - # Assign DiskA and DiskB to the tserver group - # make the assignment final, no later changes allowed to the group! - SUBSYSTEM=="block", KERNEL=="sd[ef]", GROUP:="tserver" - -In order to apply these settings, trigger a reload with :manpage:`udevadm(8)`:: - - udevadm trigger --subsystem-match=block - - -FreeBSD Example ---------------- - -Starting with 5.1 FreeBSD dropped support for explicit raw devices. All -devices on FreeBSD can be accessed raw now. - -The following example will use an entire raw disk in the FreeBSD -operating system - -.. code-block:: yaml - - cache: - spans: - - name: ada.1 - path: "/dev/ada1" - - name: ada.2 - path: "/dev/ada2" - volumes: - - id: 1 - size: 100% - -In order to make sure :program:`traffic_server` will have access to this disk -you can use :manpage:`devfs(8)` to persistently set the right permissions. The -following rules are stored in :manpage:`devfs.conf(5)`:: - - # Assign /dev/ada1 and /dev/ada2 to the tserver user - own ada[12] tserver:tserver - -Advanced --------- - -Because relative paths in :file:`storage.yaml` are relative to the base prefix, when using customized runroot -it may be necessary to adjust such paths in :file:`storage.yaml` or adjust ``runroot.yaml`` itself. -Despite the name, the cachedir value is not used for this file. - -Examples -======== - -The following example partitions the cache across 5 volumes to decrease single-lock pressure for a -machine with few drives. The last volume being an example of one that might be composed of purely -ramdisks so that the ram cache has been disabled. - -.. code-block:: yaml - - cache: - spans: - - name: disk - path: "/dev/sdb" - volumes: - - id: 1 - size: 20% - - id: 2 - size: 20% - - id: 3 - size: 20% - - id: 4 - size: 20% - avg_obj_size: 4096 - - id: 5 - size: 20% - ram_cache: false - -This can be simplified by depending on the default allocation which splits unallocated span storage across volumes. - -.. code-block:: yaml - - cache: - spans: - - name: disk - path: "/dev/sdb" - volumes: - - id: 1 - - id: 2 - - id: 3 - - id: 4 - - id: 5 - ram_cache: false - -For a host with a physical disk and two ram disks, where the ram disks should be split between two volumes, with a third -volume that uses the physical disk. - -This depends on defaults. The spans "ram.1" and "ram.2" are split evenly between volume "1" and volume "2" because no -sizes are specified. Span "disk" is not used for volume "1" nor volume "2" because it is not listed in the ``spans``. -Volume "3" therefore gets all of span "disk". - -.. code-block:: yaml - - cache: - spans: - - name: disk - path: "/dev/sdb" - - name: ram.1 - path: "/dev/ram.1" - - name: ram.2 - path: "/dev/ram.2" - volumes: - - id: 1 - spans: - - use: ram.1 - - use: ram.2 - - id: 2 - spans: - - use: ram.1 - - use: ram.2 - - id: 3 - -If one of the ram disk based volumes should be larger, this could be done as follows by making volume "1" roughly twice -as large as volume "2". - -.. code-block:: yaml - - cache: - spans: - - name: disk - path: "/dev/sdb" - - name: ram.1 - path: "/dev/ram.1" - - name: ram.2 - path: "/dev/ram.2" - volumes: - - id: 1 - spans: - - use: ram.1 - size: 66% - - use: ram.2 - size: 66% - - id: 2 - spans: - - use: ram.1 - - use: ram.2 - - id: 3 - -Instead, suppose the physical spans ("disk.1" and "disk.2") should be split across volumes. This can be done by adding volumes -with only defaults, as the physical spans will be divided evenly among four volumes (3 - 6), each volume allocated 25% of -"disk.1" and 25% of "disk.2". - -OTOH, the ram spans ("ram.1" and "ram.2") will be divided evenly among volume 1 and 2. - - -.. code-block:: yaml - - cache: - spans: - - name: disk.1 - path: "/dev/sdb" - - name: disk.2 - path: "/dev/sde" - - name: ram.1 - path: "/dev/ram.1" - - name: ram.2 - path: "/dev/ram.2" - volumes: - - id: 1 - spans: - - use: ram.1 - - use: ram.2 - - id: 2 - spans: - - use: ram.1 - - use: ram.2 - - id: 3 - - id: 4 - - id: 5 - - id: 6 - -The following example shows advanced RAM cache configuration with dedicated allocations and custom -cutoffs. Assuming a global :ts:cv:`proxy.config.cache.ram_cache.size` of 4GB, volume 1 receives a -dedicated 2GB RAM cache pool, volume 2 gets 512MB and only caches objects up to 64KB, and volume 3 -shares the remaining 1.5GB (4GB - 2GB - 512MB) and caches objects up to 1MB. - -.. code-block:: yaml - - cache: - spans: - - name: disk - path: "/dev/sdb" - volumes: - - id: 1 - size: 40% - ram_cache_size: 2G - - id: 2 - size: 20% - ram_cache_size: 512M - ram_cache_cutoff: 64K - - id: 3 - size: 40% - ram_cache_cutoff: 1M diff --git a/doc/admin-guide/files/volume.config.en.rst b/doc/admin-guide/files/volume.config.en.rst new file mode 100644 index 00000000000..f26a122eefb --- /dev/null +++ b/doc/admin-guide/files/volume.config.en.rst @@ -0,0 +1,196 @@ +.. Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +============= +volume.config +============= + +.. configfile:: volume.config + +The :file:`volume.config` file enables you to manage your cache space more +efficiently and restrict disk usage by creating cache volumes of +different sizes. By distributing the cache across multiple volumes, +you can help decrease single-lock pressure when there are not many hard drives +present. You can further configure these volumes to store data from certain +origin servers and/or domains in the :file:`hosting.config` file. + +Format +====== + +For each volume you want to create, enter a line with the following +format: :: + + volume=volume_number scheme=protocol_type size=volume_size + +where ``volume_number`` is a number between 1 and 255 (the maximum +number of volumes is 255) and ``protocol_type`` is ``http``. Traffic +Server supports ``http`` for HTTP volume types; ``volume_size`` is the +amount of cache space allocated to the volume. This value can be either +a percentage of the total cache space or an absolute value. The absolute +value must be a multiple of 128 MB, where 128 MB is the smallest value. +If you specify a percentage, then the size is rounded down to the +closest multiple of 128 MB. + +Each volume is striped across several disks to achieve parallel I/O. For +example: if there are four disks, then a 1-GB volume will have 256 MB on +each disk (assuming each disk has enough free space available). If you +do not allocate all the disk space in the cache, then the extra disk +space is not used. You can use the extra space later to create new +volumes without deleting and clearing the existing volumes. + +.. important:: + + Changing this file to add, remove or modify volumes effectively invalidates + the cache. + + +Optional ramcache setting +------------------------- + +You can also add an option ``ramcache=true/false`` to the volume configuration +line. True is the default setting and so not needed unless you want to explicitly +set it. Setting ``ramcache=false`` will disable the ramcache that normally +sits in front of a volume. This may be desirable if you are using something like +ramdisks, to avoid wasting RAM and cpu time on double caching objects. + + +Optional directory entry sizing +------------------------------- + +You can also add an option ``avg_obj_size=`` to the volume configuration +line. This overrides the global :ts:cv:`proxy.config.cache.min_average_object_size` +configuration for this volume. The size supports multipliers (K, M, G, T) for +convenience (e.g., ``avg_obj_size=64K`` or ``avg_obj_size=1M``). This is useful +if you have a volume that is dedicated for say very small objects, and you need +a lot of directory entries to store them. + +Optional fragment size setting +------------------------------ + +You can also add an option ``fragment_size=`` to the volume configuration +line. This overrides the global :ts:cv:`proxy.config.cache.target_fragment_size` +configuration for this volume. The size supports multipliers (K, M, G, T) for +convenience (e.g., ``fragment_size=512K`` or ``fragment_size=2M``). This allows +for a smaller, or larger, fragment size for a particular volume. This may be +useful together with ``avg_obj_size`` as well, since a larger fragment size could +reduce the number of directory entries needed for a large object. + +Note that this setting has a maximum value of 4MB. + +Optional RAM cache size allocation +----------------------------------- + +You can add an option ``ram_cache_size=`` to the volume configuration line +to allocate a dedicated RAM cache pool for this volume. The size supports +multipliers (K, M, G, T) for convenience (e.g., ``ram_cache_size=512M`` or +``ram_cache_size=2G``). Setting ``ram_cache_size=0`` disables the RAM cache +for this volume, which is equivalent to ``ramcache=false``. + +When ``ram_cache_size`` is specified for a volume, that amount is **automatically +subtracted** from the global :ts:cv:`proxy.config.cache.ram_cache.size` setting, +and the remainder is shared among volumes without private allocations. This ensures +total RAM cache usage never exceeds the configured global limit. + +For example, if the global RAM cache size is 4GB and you allocate 1GB to volume 1 +and 512MB to volume 2, the remaining 2.5GB will be distributed among other volumes +using the normal proportional allocation based on disk space. + +**Important notes:** + +* If the sum of all ``ram_cache_size`` allocations exceeds the global RAM cache size, + Traffic Server will fail to start with a fatal error. Increase + :ts:cv:`proxy.config.cache.ram_cache.size` or reduce the per-volume allocations. +* If ``ramcache=false`` is set alongside ``ram_cache_size``, the ``ram_cache_size`` + is ignored (with a warning) since the RAM cache is disabled for that volume. +* This setting only takes effect when :ts:cv:`proxy.config.cache.ram_cache.size` + is set to a positive value (not ``-1`` for automatic sizing). + +Optional RAM cache cutoff override +----------------------------------- + +You can add an option ``ram_cache_cutoff=`` to the volume configuration line +to override the global :ts:cv:`proxy.config.cache.ram_cache_cutoff` setting for +this specific volume. The size supports multipliers (K, M, G, T) for convenience +(e.g., ``ram_cache_cutoff=64K`` or ``ram_cache_cutoff=1M``). + +This cutoff determines the maximum object size that will be stored in the RAM cache. +Objects larger than this size will only be stored on disk. Setting different cutoffs +per volume allows you to: + +* Use larger cutoffs for volumes serving frequently accessed large objects +* Use smaller cutoffs for volumes with many small objects to maximize RAM cache hits +* Disable RAM caching entirely for certain objects by setting a very low cutoff + +Exclusive spans and volume sizes +================================ + +In the following sample configuration 2 spans `/dev/disk1` and `/dev/disk2` are defined +in :file:`storage.config`, where span `/dev/disk2` is assigned to `volume 3` exclusively +(`volume 3` is forced to an "exclusive" span `/dev/disk2`). +In :file:`volume.config` there are 3 volumes defined, where `volume 1` and `volume 2` +occupy span `/dev/disk1` taking each 50% of its space and `volume 3` takes 100% of span +`/dev/disk2` exclusively. + +storage.config:: + + /dev/disk1 + /dev/disk2 volume=3 # <- exclusive span + +volume.config:: + + volume=1 scheme=http size=50% + volume=2 scheme=http size=50% + volume=3 scheme=http size=512 # <- volume forced to a specific exclusive span + +It is important to note that when percentages are used to specify volume sizes +and "exclusive" spans are assigned (forced) to a particular volume (in this case `volume 3`), +the "exclusive" spans (in this case `/dev/disk2`) are excluded from the total cache +space when the "non-forced" volumes sizes are calculated (in this case `volume 1` and `volume 2`). + + +Examples +======== + +The following example partitions the cache across 5 volumes to decreasing +single-lock pressure for a machine with few drives. The last volume being +an example of one that might be composed of purely ramdisks so that the +ramcache has been disabled.:: + + volume=1 scheme=http size=20% + volume=2 scheme=http size=20% + volume=3 scheme=http size=20% + volume=4 scheme=http size=20% avg_obj_size=4K + volume=5 scheme=http size=20% ramcache=false fragment_size=512K + +The following example shows advanced RAM cache configuration with dedicated +allocations and custom cutoffs:: + + # Volume 1: General content with 2GB dedicated RAM cache + volume=1 scheme=http size=40% ram_cache_size=2G + + # Volume 2: Small API responses with custom cutoff and 512MB RAM cache + volume=2 scheme=http size=20% ram_cache_size=512M ram_cache_cutoff=64K + + # Volume 3: Large media with higher cutoff for thumbnails + volume=3 scheme=http size=40% ram_cache_cutoff=1M + +In this example, assuming a global ``proxy.config.cache.ram_cache.size`` of 4GB: + +* Volume 1 gets a dedicated 2GB RAM cache allocation +* Volume 2 gets a dedicated 512MB RAM cache allocation and only caches objects up to 64KB +* Volume 3 shares from the remaining 1.5GB pool (4GB - 2GB - 512MB) and caches objects up to 1MB +* The automatic subtraction ensures total RAM usage stays within the 4GB limit diff --git a/doc/admin-guide/monitoring/error-messages.en.rst b/doc/admin-guide/monitoring/error-messages.en.rst index 72f706d0814..eb472272fbb 100644 --- a/doc/admin-guide/monitoring/error-messages.en.rst +++ b/doc/admin-guide/monitoring/error-messages.en.rst @@ -70,10 +70,10 @@ Process Warnings too many errors. The disk might be corrupt and might have to be replaced. -``No cache disks specified in storage.yaml file: cache disabled`` - The Traffic Server :file:`storage.yaml` file does not list any cache +``No cache disks specified in storage.config file: cache disabled`` + The Traffic Server :file:`storage.config` file does not list any cache disks; Traffic Server is running in proxy-only mode. You must add - the disks you want to use for the cache to :file:`storage.yaml`. + the disks you want to use for the cache to :file:`storage.config`. .. _body-factory: diff --git a/doc/admin-guide/monitoring/statistics/core/ssl.en.rst b/doc/admin-guide/monitoring/statistics/core/ssl.en.rst index c3dc1bb7994..efef309c222 100644 --- a/doc/admin-guide/monitoring/statistics/core/ssl.en.rst +++ b/doc/admin-guide/monitoring/statistics/core/ssl.en.rst @@ -389,69 +389,3 @@ Stats for Pre-warming TLS Tunnel is registered dynamically. The ``POOL`` in belo :type: counter Represents the total number of pre-warming retry. - -.. ts:stat:: global proxy.process.ssl.cert_compress.zlib integer - :type: counter - - The number of times a server certificate was compressed with zlib during a - TLS handshake. - -.. ts:stat:: global proxy.process.ssl.cert_compress.zlib_failure integer - :type: counter - - The number of times zlib compression of a server certificate failed. - -.. ts:stat:: global proxy.process.ssl.cert_decompress.zlib integer - :type: counter - - The number of times a certificate received from an origin server was - decompressed with zlib. - -.. ts:stat:: global proxy.process.ssl.cert_decompress.zlib_failure integer - :type: counter - - The number of times zlib decompression of a certificate failed. - -.. ts:stat:: global proxy.process.ssl.cert_compress.brotli integer - :type: counter - - The number of times a server certificate was compressed with Brotli during a - TLS handshake. - -.. ts:stat:: global proxy.process.ssl.cert_compress.brotli_failure integer - :type: counter - - The number of times Brotli compression of a server certificate failed. - -.. ts:stat:: global proxy.process.ssl.cert_decompress.brotli integer - :type: counter - - The number of times a certificate received from an origin server was - decompressed with Brotli. - -.. ts:stat:: global proxy.process.ssl.cert_decompress.brotli_failure integer - :type: counter - - The number of times Brotli decompression of a certificate failed. - -.. ts:stat:: global proxy.process.ssl.cert_compress.zstd integer - :type: counter - - The number of times a server certificate was compressed with zstd during a - TLS handshake. - -.. ts:stat:: global proxy.process.ssl.cert_compress.zstd_failure integer - :type: counter - - The number of times zstd compression of a server certificate failed. - -.. ts:stat:: global proxy.process.ssl.cert_decompress.zstd integer - :type: counter - - The number of times a certificate received from an origin server was - decompressed with zstd. - -.. ts:stat:: global proxy.process.ssl.cert_decompress.zstd_failure integer - :type: counter - - The number of times zstd decompression of a certificate failed. diff --git a/doc/admin-guide/performance/index.en.rst b/doc/admin-guide/performance/index.en.rst index 33dccc4f390..461c6a9d844 100644 --- a/doc/admin-guide/performance/index.en.rst +++ b/doc/admin-guide/performance/index.en.rst @@ -405,6 +405,9 @@ will persist, specified in seconds using the remote OCSP responders, in seconds, with :ts:cv:`proxy.config.ssl.ocsp.request_timeout`. +Lastly, you can control the number of seconds for which SSL sessions will be +cached in |TS| using :ts:cv:`proxy.config.ssl.session_cache.timeout`. + .. code-block:: yaml records: @@ -413,6 +416,8 @@ the remote OCSP responders, in seconds, with ocsp: cache_timeout: 3600 request_timeout: 10 + session_cache: + timeout: 0 Transaction Activity Timeouts @@ -523,6 +528,8 @@ SSL-Specific Options ~~~~~~~~~~~~~~~~~~~~ :ts:cv:`proxy.config.ssl.max_record_size` +:ts:cv:`proxy.config.ssl.session_cache.mode` +:ts:cv:`proxy.config.ssl.session_cache.size` Thread Types ------------ diff --git a/doc/admin-guide/security/index.en.rst b/doc/admin-guide/security/index.en.rst index cec71bca1b4..829392e2425 100644 --- a/doc/admin-guide/security/index.en.rst +++ b/doc/admin-guide/security/index.en.rst @@ -121,7 +121,7 @@ Client/|TS| connections, you must do the following: private_key: path: /opt/ts/etc/ssl/keys -#. Add an entry to :file:`ssl_multicert.yaml` for each certificate and key +#. Add an entry to :file:`ssl_multicert.config` for each certificate and key which your |TS| system will be using to terminate SSL connections with clients. :: @@ -302,7 +302,7 @@ a ticket key file as a reverse queue in 48-byte chunks. #. *Optional*: Delete the last ticket key from the ticket key file. -#. Touch :file:`ssl_multicert.yaml` to indicate that the SSL configuration is stale. +#. Touch :file:`ssl_multicert.config` to indicate that the SSL configuration is stale. #. Run the command :option:`traffic_ctl config reload` to apply the new ticket key. @@ -335,7 +335,7 @@ Authority Information Access field of the signed certificate. For example:: CA Issuers - URI:http://cacerts.digicert.com/DigiCertSHA2SecureServerCA.crt |TS| can also use prefetched OCSP stapling responses if ssl_ocsp_name parameter -is used in :file:`ssl_multicert.yaml`. Take into account that when using prefetched +is used in :file:`ssl_multicert.config`. Take into account that when using prefetched OCSP stapling responses, |TS| will not refresh them and it should be done externally. This can be done using OpenSSL:: diff --git a/doc/admin-guide/security/mtls.en.rst b/doc/admin-guide/security/mtls.en.rst index e602d3d1cf2..6a850459730 100644 --- a/doc/admin-guide/security/mtls.en.rst +++ b/doc/admin-guide/security/mtls.en.rst @@ -161,6 +161,6 @@ From there you can verify via your origins that the updated certificates are bei traffic_ctl config reload -If the contents of the certificate files change but the names of the files do not, you may need to touch ssl_multicert.yaml +If the contents of the certificate files change but the names of the files do not, you may need to touch ssl_multicert.config (for server certs) and sni.yaml (for client certs). diff --git a/doc/admin-guide/storage/index.en.rst b/doc/admin-guide/storage/index.en.rst index f68808b1016..d6f650e89c1 100644 --- a/doc/admin-guide/storage/index.en.rst +++ b/doc/admin-guide/storage/index.en.rst @@ -143,8 +143,8 @@ Disabling the RAM Cache ----------------------- It is possible to disable the RAM cache. If you have configured your -storage using the :file:`storage.yaml` you can add an optional directive -of ``ram_cache: false`` to whichever volumes you wish to have it disabled on. +storage using the :file:`volume.config` you can add an optional directive +of ``ramcache=false`` to whichever volumes you wish to have it disabled on. This may be desirable for volumes composed of storage like RAM disks where you may want to avoid double RAM caching. @@ -166,7 +166,7 @@ existing disks, or to add new disks to a Traffic Server node: #. Add hardware, if necessary. -#. Edit :file:`storage.yaml` to increase the amount of disk space allocated +#. Edit :file:`storage.config` to increase the amount of disk space allocated to the cache on existing disks or describe the new hardware you are adding. #. Restart Traffic Server. @@ -181,12 +181,12 @@ existing disk, or to remove disks from a Traffic Server node: #. Remove hardware, if necessary. -#. Edit :file:`storage.yaml` to reduce the amount of disk space allocated +#. Edit :file:`storage.config` to reduce the amount of disk space allocated to the cache on existing disks or delete the reference to the hardware you're removing. #. Restart Traffic Server. -.. important:: In :file:`storage.yaml`, a formatted or raw disk must be at least 128 MB. +.. important:: In :file:`storage.config`, a formatted or raw disk must be at least 128 MB. .. _partitioning-the-cache: diff --git a/doc/admin-guide/tools/converting-records-to-yaml.en.rst b/doc/admin-guide/tools/converting-records-to-yaml.en.rst index dcc7af4cf1a..bb57ed2f035 100644 --- a/doc/admin-guide/tools/converting-records-to-yaml.en.rst +++ b/doc/admin-guide/tools/converting-records-to-yaml.en.rst @@ -150,20 +150,21 @@ Converting a file with a detailed output. $ python3 convert2yaml.py -f records.config -o records.yaml [████████████████████████████████████████] 494/494 - ┌■ 7 Renamed records: + ┌■ 8 Renamed records: └┬──» #1 : proxy.config.output.logfile -> proxy.config.output.logfile.name ├──» #2 : proxy.config.exec_thread.autoconfig -> proxy.config.exec_thread.autoconfig.enabled ├──» #3 : proxy.config.hostdb -> proxy.config.hostdb.enabled ├──» #4 : proxy.config.tunnel.prewarm -> proxy.config.tunnel.prewarm.enabled ├──» #5 : proxy.config.ssl.TLSv1_3 -> proxy.config.ssl.TLSv1_3.enabled ├──» #6 : proxy.config.ssl.client.TLSv1_3 -> proxy.config.ssl.client.TLSv1_3.enabled - └──» #7 : proxy.config.ssl.origin_session_cache -> proxy.config.ssl.origin_session_cache.enabled + ├──» #7 : proxy.config.ssl.origin_session_cache -> proxy.config.ssl.origin_session_cache.enabled + └──» #8 : proxy.config.ssl.session_cache -> proxy.config.ssl.session_cache.mode There are a few things to note here: Line 2. A total of ``494`` from ``494`` records were converted. -Line 4. A total of ``7`` records were renamed. +Line 4. A total of ``8`` records were renamed. Example 2 --------- diff --git a/doc/appendices/command-line/traffic_cache_tool.en.rst b/doc/appendices/command-line/traffic_cache_tool.en.rst index 87e3ab8adde..40d8822251a 100644 --- a/doc/appendices/command-line/traffic_cache_tool.en.rst +++ b/doc/appendices/command-line/traffic_cache_tool.en.rst @@ -48,12 +48,12 @@ be abbreviated to any unique initial substring (e.g. "--sp" for "--span"). .. option:: --spans Specify the span (storage) configuration. This can be a device, a cache directory, or a - configuration file in the form of :file:`storage.yaml`. In the latter case all devices listed + configuration file in the form of :file:`storage.config`. In the latter case all devices listed in the configuration file become active. .. option:: --volumes - Specify the volume configuration file in the format of :file:`storage.yaml`. This is important + Specify the volume configuration file in the format of :file:`volume.config`. This is important primarily for allocation operations where having the volume configuration is needed in order to properly allocate storage in spans to specific volumes. @@ -124,40 +124,46 @@ Examples List the basic span data.:: - traffic_cache_tool --spans=/usr/local/etc/trafficserver/storage.yaml list + traffic_cache_tool --spans=/usr/local/etc/trafficserver/storage.config list Allocate unused storage space.:: traffic_cache_tool \ - --spans=/usr/local/etc/trafficserver/storage.yaml \ + --spans=/usr/local/etc/trafficserver/storage.config \ + --volumes=/usr/local/etc/trafficserver/volume.config \ alloc free Clear all spans.:: traffic_cache_tool \ - --spans=/usr/local/etc/trafficserver/storage.yaml \ + --spans=/usr/local/etc/trafficserver/storage.config \ + --volumes=/usr/local/etc/trafficserver/volume.config \ clear Clear a single span.:: traffic_cache_tool \ - --span /opt/etc/trafficserver/storage.yaml \ + --span /opt/etc/trafficserver/storage.config \ + --volume /opt/etc/trafficserver/volume.config \ clear span --device "/dev/sdb3" --write Initialize a new span.:: traffic_cache_tool \ - --span /opt/etc/trafficserver/storage.yaml \ + --span /opt/etc/trafficserver/storage.config \ + --volume /opt/etc/trafficserver/volume.config \ init --input "/dev/sdb3" --write Find Stripe Assignment.:: traffic_cache_tool \ - --span /opt/etc/trafficserver/storage.yaml \ + --span /opt/etc/trafficserver/storage.config \ + --volume /opt/etc/trafficserver/volume.config \ init --input "/home/user/urls.txt" ======== See also ======== -:manpage:`storage.yaml(5)` +:manpage:`storage.config(5)` +:manpage:`volume.config(5)`, diff --git a/doc/appendices/command-line/traffic_ctl.en.rst b/doc/appendices/command-line/traffic_ctl.en.rst index 5fff291cf19..d116401d327 100644 --- a/doc/appendices/command-line/traffic_ctl.en.rst +++ b/doc/appendices/command-line/traffic_ctl.en.rst @@ -211,326 +211,8 @@ Display the current value of a configuration record. configuration after any configuration file modification. If no configuration files have been modified since the previous configuration load, this command is a no-op. - The reload is **asynchronous**: the command sends a JSONRPC request to |TS| and returns - immediately — it does not block until every config handler finishes. The actual reload work - runs on background threads (``ET_TASK``), where each registered config handler loads its - configuration and reports success or failure. - - Every reload is assigned a **token** — a unique identifier for the reload operation. The token - is the handle you use to track, monitor, or query the reload after it starts. If no token is - provided via ``--token``, the server generates one automatically using a timestamp (e.g. - ``rldtk-1739808000000``). You can supply a custom token via ``--token`` (e.g. - ``-t deploy-v2.1``) to tag reloads with meaningful labels for CI pipelines, deploy scripts, or - post-mortem analysis. - - Use the token to: - - - **Monitor** a reload in real-time: ``traffic_ctl config reload -t -m`` - - **Query** the final status: ``traffic_ctl config status -t `` - - **Get detailed logs**: ``traffic_ctl config status -t `` - The timestamp of the last reconfiguration event (in seconds since epoch) is published in the - ``proxy.process.proxy.reconfigure_time`` metric. - - **Behavior without options:** - - When called without ``--monitor`` or ``--show-details``, the command sends the reload request - and immediately prints the assigned token along with suggested next-step commands: - - .. code-block:: bash - - $ traffic_ctl config reload - ✔ Reload scheduled [rldtk-1739808000000] - - Monitor : traffic_ctl config reload -t rldtk-1739808000000 -m - Details : traffic_ctl config reload -t rldtk-1739808000000 -s -l - - **When a reload is already in progress:** - - Only one reload can be active at a time. If a reload is already running, the command does - **not** start a new one. Instead, it reports the in-progress reload's token and provides - options to monitor it or force a new one: - - .. code-block:: bash - - $ traffic_ctl config reload - ⟳ Reload in progress [rldtk-1739808000000] - - Monitor : traffic_ctl config reload -t rldtk-1739808000000 -m - Details : traffic_ctl config status -t rldtk-1739808000000 - Force : traffic_ctl config reload --force (may conflict with the running reload) - - With ``--monitor``, it automatically switches to monitoring the in-progress reload instead of - failing. With ``--show-details``, it displays the current status of the in-progress reload. - - **When a token already exists:** - - If the token provided via ``--token`` was already used by a previous reload (even a completed - one), the command refuses to start a new reload to prevent ambiguity. Choose a different token - or omit ``--token`` to let the server generate a unique one: - - .. code-block:: bash - - $ traffic_ctl config reload -t my-deploy - ✗ Token 'my-deploy' already in use - - Status : traffic_ctl config status -t my-deploy - Retry : traffic_ctl config reload - - Supports the following options: - - .. option:: --token, -t - - Assign a custom token to this reload. Tokens must be unique across the reload history — if - a reload (active or completed) already has this token, the command is rejected. When omitted, - the server generates a unique token automatically. - - Custom tokens are useful for tagging reloads with deployment identifiers, ticket numbers, - or other meaningful labels that make it easier to query status later. - - .. code-block:: bash - - $ traffic_ctl config reload -t deploy-v2.1 - ✔ Reload scheduled [deploy-v2.1] - - .. option:: --monitor, -m - - Start the reload and monitor its progress with a live progress bar until completion. The - progress bar updates in real-time showing the number of completed handlers, overall status, - and elapsed time. - - Polls the server at regular intervals controlled by ``--refresh-int`` (default: every 0.5 - seconds). Before the first poll, waits briefly (see ``--initial-wait``) to allow the server - time to dispatch work to all handlers. - - If a reload is already in progress, ``--monitor`` automatically attaches to that reload and - monitors it instead of failing. - - If both ``--monitor`` and ``--show-details`` are specified, ``--monitor`` is ignored and - ``--show-details`` takes precedence. - - .. code-block:: bash - - $ traffic_ctl config reload -t deploy-v2.1 -m - ✔ Reload scheduled [deploy-v2.1] - ✔ [deploy-v2.1] ████████████████████ 11/11 success (245ms) - - Failed reload: - - .. code-block:: bash - - $ traffic_ctl config reload -t hotfix-cert -m - ✔ Reload scheduled [hotfix-cert] - ✗ [hotfix-cert] ██████████████░░░░░░ 9/11 fail (310ms) - - Details : traffic_ctl config status -t hotfix-cert - - .. note:: - - During a reload, all subtasks are pre-registered shortly after file processing - completes. However, the first file (typically ``records.yaml``) may briefly show as - ``1/1 success`` before record-triggered handlers are reserved by the flush. The - ``--initial-wait`` option (default: 2s) delays the first poll to reduce the chance - of observing this transient state. - - .. option:: --show-details, -s - - Start the reload and display a detailed status report. The command sends the reload request, - waits for the configured initial wait (see ``--initial-wait``, default: 2 seconds) to allow - handlers to start, then fetches and prints the full task tree with per-handler status and - durations. - - If a reload is already in progress, shows the status of that reload immediately. - - Combine with ``--include-logs`` to also show per-handler log messages. - - .. code-block:: bash - - $ traffic_ctl config reload -s -l - ✔ Reload scheduled [rldtk-1739808000000]. Waiting for details... - - ✔ Reload [success] — rldtk-1739808000000 - Started : 2025 Feb 17 12:00:00.123 - Duration: 245ms - - Tasks: - ✔ ip_allow.yaml ·························· 18ms - ✔ logging.yaml ··························· 120ms - ... - - .. option:: --include-logs, -l - - Include per-handler log messages in the output. Only meaningful together with - ``--show-details``. Log messages are set by handlers via ``ctx.log()`` and - ``ctx.fail()`` during the reload. - - .. option:: --data, -d - - Supply inline YAML configuration content for the reload. The content is passed directly to - config handlers at runtime and is **not persisted to disk** — a server restart will revert - to the file-based configuration. A warning is printed after a successful inline reload to - remind the operator. - - Accepts the following formats: - - - ``@file.yaml`` — read content from a file - - ``@-`` — read content from stdin - - ``"yaml: content"`` — inline YAML string - - Multiple ``-d`` arguments can be provided — their content is merged, with later values - overriding earlier ones for the same key - - The YAML content uses **registry keys** (e.g. ``ip_allow``, ``sni``) as top-level keys. - Each key maps to the full configuration content that the handler normally reads from its - config file. A single file can target multiple handlers: - - .. code-block:: yaml - - # reload_rules.yaml - # Each top-level key is a registry key. - # The value is the config content (inner data, not the file's top-level wrapper). - ip_allow: - - apply: in - ip_addrs: 0.0.0.0/0 - action: allow - sni: - - fqdn: "*.example.com" - verify_client: NONE - - .. code-block:: bash - - # Reload from file - $ traffic_ctl config reload -d @reload_rules.yaml -t update-rules -m - - # Reload from stdin - $ cat rules.yaml | traffic_ctl config reload -d @- -m - - When used with ``-d``, only the handlers for the keys present in the YAML content are - invoked — other config handlers are not triggered. - - .. note:: - - Inline YAML reload requires the target config handler to support - ``ConfigSource::FileAndRpc``. Handlers that only support ``ConfigSource::FileOnly`` - will return an error for the corresponding key. The JSONRPC response will contain - per-key error details. - - .. option:: --directive, -D - - Pass a reload directive to a specific config handler. Directives are operational parameters - that modify how the handler performs the reload — for example, scoping a reload to a single - entry or enabling a dry-run mode. They are distinct from config content (``-d``). - - The format is ``config_key.directive_key=value``, parsed by splitting on the first ``.`` - and the first ``=``: - - - ``config_key`` — the registry key (e.g. ``ip_allow``, ``sni``) - - ``directive_key`` — the directive name understood by that handler - - ``value`` — the directive value (always passed as a string on the wire) - - Multiple directives are passed as space-separated values after a single ``-D``: - - .. code-block:: bash - - # Single directive - $ traffic_ctl config reload -D myconfig.id=foo - - # Multiple directives for the same handler - $ traffic_ctl config reload -D myconfig.id=foo myconfig.dry_run=true - - # Directives for different handlers in the same reload - $ traffic_ctl config reload -D myconfig.id=foo sni.fqdn=example.com - - On the wire, ``-D myconfig.id=foo`` translates to: - - .. code-block:: json - - { "configs": { "myconfig": { "_reload": { "id": "foo" } } } } - - For complex or nested directive values, use ``-d`` with full YAML instead: - - .. code-block:: bash - - $ traffic_ctl config reload -d 'myconfig: { _reload: { id: foo, options: { strict: true } } }' - - .. note:: - - ``-D`` uses variable-argument parsing and must appear as the **last option** - on the command line. Any flags placed after ``-D`` will be consumed as directive - values. ``-D`` and ``-d`` cannot be combined in the same invocation due to this - same constraint. Use ``-d`` with full YAML when you need both directives and - inline content in a single reload request. - - .. note:: - - Available directives depend on the handler — consult each config's documentation for - supported directive keys. Directive values are strings on the wire; handlers use - yaml-cpp's ``as()`` to interpret them as needed. - - .. option:: --force, -F - - Force a new reload even if one is already in progress. Without this flag, the server rejects - a new reload when one is active. - - .. warning:: - - ``--force`` does **not** stop or cancel the running reload. It starts a second reload - alongside the first one. Handlers from both reloads may execute concurrently on separate - ``ET_TASK`` threads. This can lead to unpredictable behavior if handlers are not designed - for concurrent execution. Use this flag only for debugging or recovery situations. - - .. option:: --refresh-int, -r - - Set the polling interval in seconds used with ``--monitor``. Accepts fractional values - (e.g. ``0.5`` for 500ms). Controls how often ``traffic_ctl`` queries the server for - updated reload status. Default: ``0.5``. - - .. option:: --initial-wait, -w - - Initial wait in seconds before the first status poll. After scheduling a reload, the - server needs a brief moment to dispatch work to all handlers. This delay avoids polling - before any handler has started, which would show an empty or incomplete task tree. - Accepts fractional values (e.g. ``1.5``). Default: ``2``. - - .. option:: --timeout, -T - - Maximum time to wait for reload completion when using ``--monitor``. Accepts duration - strings (e.g. ``30s``, ``1m``, ``500ms``). If the reload does not reach a terminal state - within the specified duration, ``traffic_ctl`` stops monitoring and exits with - ``EX_TEMPFAIL`` (75). The reload itself continues on the server — use - ``traffic_ctl config status -t `` to check its final status later. - Default: ``0`` (no timeout — wait indefinitely until the reload completes or is - interrupted). Requires ``--monitor``. - - **Exit codes for config reload:** - - The ``config reload`` command sets the process exit code to reflect the outcome of the - reload operation: - - ``0`` - Success. The reload was scheduled (without ``--monitor``) or completed successfully - (with ``--monitor``). - - ``2`` - Error. The reload reached a terminal failure state (``fail`` or ``timeout``), or an - RPC communication error occurred. - - ``75`` - Temporary failure (``EX_TEMPFAIL`` from ``sysexits.h``). A reload is already in - progress and the command could not start a new one, monitoring was interrupted - (e.g. Ctrl+C) before the reload reached a terminal state, or the ``--timeout`` - duration was exceeded. The caller is invited to retry or monitor the operation later. - - Example usage in scripts: - - .. code-block:: bash - - traffic_ctl config reload -m - rc=$? - case $rc in - 0) echo "Reload completed successfully" ;; - 2) echo "Reload failed" ;; - 75) echo "Reload still in progress, retry later" ;; - esac + `proxy.process.proxy.reconfigure_time` metric. .. program:: traffic_ctl config .. option:: set RECORD VALUE @@ -669,192 +351,11 @@ Display the current value of a configuration record. .. program:: traffic_ctl config .. option:: status - :ref:`get_reload_config_status` - - Display the status of configuration reloads. This is a read-only command — it does not trigger - a reload, it only queries the server for information about past or in-progress reloads. - - **Behavior without options:** - - When called without ``--token`` or ``--count``, shows the most recent reload: - - .. code-block:: bash - - $ traffic_ctl config status - ✔ Reload [success] — rldtk-1739808000000 - Started : 2025 Feb 17 12:00:00.123 - Finished: 2025 Feb 17 12:00:00.368 - Duration: 245ms - - ✔ 11 success ◌ 0 in-progress ✗ 0 failed (11 total) - - Tasks: - ✔ logging.yaml ··························· 120ms - ✔ ip_allow.yaml ·························· 18ms - ... - - **When no reloads have occurred:** - - If the server has not performed any reloads since startup, the command reports that no reload - tasks were found. - - **Querying a specific reload:** - - Use ``--token`` to look up a specific reload by its token. If the token does not exist in - the history, an error is returned: - - .. code-block:: bash - - $ traffic_ctl config status -t nonexistent - ✗ Token 'nonexistent' not found - - **Failed reload report:** - - When a reload has failed handlers, the output shows which handlers succeeded and which failed, - along with durations and per-handler log entries. Log entries carry severity tags when a - severity level was recorded: - - .. code-block:: bash - - $ traffic_ctl config status -t hotfix-cert - ✗ Reload [fail] — hotfix-cert - Started : 2025 Feb 17 14:30:10.500 - Finished: 2025 Feb 17 14:30:10.810 - Duration: 310ms - - ✔ 9 success ◌ 0 in-progress ✗ 2 failed (11 total) - - Tasks: - ✔ ip_allow.yaml ·························· 18ms - ✗ ssl_client_coordinator ················· 85ms ✗ FAIL - │ [Note] SSL configs reloaded - ├─ ✔ SSLConfig ·························· 10ms - │ [Note] SSLConfig loading ... - │ [Note] SSLConfig reloaded - ├─ ✗ SNIConfig ·························· 12ms ✗ FAIL - │ [Note] sni.yaml loading ... - │ [Err] sni.yaml failed to load: yaml-cpp error ... - └─ ✔ SSLCertificateConfig ·············· 13ms - [Note] (ssl) ssl_multicert.yaml loading ... - [Note] (ssl) ssl_multicert.yaml finished loading - ... - - Supports the following options: - - .. option:: --token, -t - - Show the status of a specific reload identified by its token. The token was either assigned - by the server (e.g. ``rldtk-``) or provided by the operator via - ``traffic_ctl config reload --token``. - - Returns an error if the token is not found in the reload history. - - .. option:: --count, -c - - Show the last ``N`` reload records from the history. Use ``all`` to display every reload - the server has recorded (up to the internal history limit). - - When ``--count`` is provided, ``--token`` is ignored. - - .. code-block:: bash - - # Show full history - $ traffic_ctl config status -c all - - # Show last 5 reloads - $ traffic_ctl config status -c 5 - - .. option:: --min-level - - Filter task log entries by minimum severity level. Only entries at or above the specified - level are displayed. State-transition messages carry implicit severity: - ``in_progress()`` and ``complete()`` produce ``[Note]`` entries, ``fail()`` produces - ``[Err]`` entries. Entries without a severity (``DL_Undefined``) — typically those logged - via the one-argument ``ctx.log(text)`` — are always shown regardless of this filter. - - Valid levels (case-insensitive): ``debug``, ``note``, ``warning``, ``error``. - - .. code-block:: bash - - # Show only warnings and errors - $ traffic_ctl config status -t my-token --min-level warning - - # Show only errors - $ traffic_ctl config status -t my-token --min-level error - - **Example — all logs (no filter):** - - .. code-block:: text - - ✗ ssl_client_coordinator ······················· 2ms ✗ FAIL - │ [Note] SSL configs reloaded - ├─ ✔ SSLConfig ································· 1ms - │ [Note] SSLConfig loading ... - │ [Note] SSLConfig reloaded - ├─ ✗ SNIConfig ································· 1ms ✗ FAIL - │ [Note] sni.yaml loading ... - │ [Err] sni.yaml failed to load - └─ ✔ SSLCertificateConfig ······················ 0ms - [Note] (ssl) ssl_multicert.yaml loading ... - [Warn] Cannot open SSL certificate configuration "ssl_multicert.yaml" - No such file or directory - [Note] (ssl) ssl_multicert.yaml finished loading - - **Example — --min-level warning (note and debug entries filtered out):** - - .. code-block:: text - - ✗ ssl_client_coordinator ······················· 2ms ✗ FAIL - ├─ ✗ SNIConfig ································· 1ms ✗ FAIL - │ [Err] sni.yaml failed to load - └─ ✔ SSLCertificateConfig ······················ 0ms - [Warn] Cannot open SSL certificate configuration "ssl_multicert.yaml" - No such file or directory - - All entries from state transitions and ``CfgLoad*`` macros carry a severity tag - (e.g. ``[Dbg]``, ``[Note]``, ``[Warn]``, ``[Err]``). Entries without a tag are - "unleveled" (from the one-argument ``ctx.log(text)``) and always pass the filter. - - .. tip:: - - For deeper investigation beyond what ``traffic_ctl config status`` shows, enable the - ``config.reload`` debug tag. This writes a full dump of every subtask and its log entries - (with severity tags) to ``diags.log`` after each reload completes. - See :ref:`config-reload-diags-log` in the developer guide for details and examples. - - Enable at runtime without restarting: - - .. code-block:: bash - - $ traffic_ctl server debug enable --tags "config.reload" - - Or persistently in ``records.yaml``: - - .. code-block:: yaml - - records: - diags: - debug: - enabled: 1 - tags: config.reload - - **JSON output:** - - All ``config status`` commands support the global ``--format json`` option to output the raw - JSONRPC response as JSON instead of the human-readable format. This is useful for automation, - CI pipelines, monitoring tools, or any system that consumes structured output directly: - - .. code-block:: bash + :ref:`admin_lookup_records` - $ traffic_ctl config status -t deploy-v2.1 --format json - { - "tasks": [ - { - "config_token": "deploy-v2.1", - "status": "success", - "description": "Main reload task - 2025 Feb 17 12:00:00", - "sub_tasks": [ ...] - } - ] - } + Display detailed status about the Traffic Server configuration system. This includes version + information, whether the internal configuration store is current and whether any daemon processes + should be restarted. .. program:: traffic_ctl config .. option:: registry @@ -864,76 +365,6 @@ Display the current value of a configuration record. Display information about the registered files in |TS|. This includes the full file path, config record name, parent config (if any) if needs root access and if the file is required in |TS|. -.. program:: traffic_ctl config -.. option:: convert - - Convert a legacy configuration file to its YAML equivalent. The conversion - runs locally — no running :program:`traffic_server` is required. - - Supported types: - - ``ssl_multicert`` - Convert ``ssl_multicert.config`` to :file:`ssl_multicert.yaml`. - - .. code-block:: bash - - traffic_ctl config convert ssl_multicert ssl_multicert.config ssl_multicert.yaml - - ``storage`` - Convert ``storage.config`` and ``volume.config`` to - :file:`storage.yaml`. - - .. code-block:: bash - - traffic_ctl config convert storage storage.config volume.config storage.yaml - - ``plugin_config`` - Convert :file:`plugin.config` to :file:`plugin.yaml`. - Commented-out lines are converted to ``enabled: false`` entries by - default. Pass ``--skip-disabled`` to omit them entirely. - - .. code-block:: bash - - # Write to a file - traffic_ctl config convert plugin_config plugin.config plugin.yaml - - # Preview on stdout - traffic_ctl config convert plugin_config plugin.config - - - # Drop commented-out entries - traffic_ctl config convert plugin_config plugin.config plugin.yaml --skip-disabled - - For all types, use ``-`` as the output file to write to stdout. - -.. program:: traffic_ctl config -.. option:: ssl-multicert show [--yaml | --json] - - Display the current ``ssl_multicert.yaml`` configuration. By default, output is in YAML format. - Use ``--json`` or ``-j`` to output in JSON format. - - .. option:: --yaml, -y - - Output in YAML format (default). - - .. option:: --json, -j - - Output in JSON format. - - Example: - - .. code-block:: bash - - $ traffic_ctl config ssl-multicert show - ssl_multicert: - - ssl_cert_name: server.pem - dest_ip: "*" - ssl_key_name: server.key - - .. code-block:: bash - - $ traffic_ctl config ssl-multicert show --json - {"ssl_multicert": [{"ssl_cert_name": "server.pem", "dest_ip": "*", "ssl_key_name": "server.key"}]} - .. _traffic-control-command-metric: traffic_ctl metric @@ -1148,7 +579,7 @@ traffic_ctl storage :ref:`admin_storage_set_device_offline` Mark a cache storage device as offline. The storage is identified by :arg:`PATH` which must match - exactly a path specified in :file:`storage.yaml`. This removes the storage from the cache and + exactly a path specified in :file:`storage.config`. This removes the storage from the cache and redirects requests that would have used this storage to other storage. This has exactly the same effect as a disk failure for that storage. This does not persist across restarts of the :program:`traffic_server` process. @@ -1166,36 +597,6 @@ traffic_ctl plugin ------------------- .. program:: traffic_ctl plugin -.. option:: list - - Display the globally loaded plugins and their status. The output includes - the configuration source, each plugin's sequence index, path, - ``load_order`` (when any plugin has one), and status. - - Example (with ``load_order``): - - .. code-block:: bash - - $ traffic_ctl plugin list - source: plugin.yaml - # plugin load_order status - 1 certifier.so 100 loaded - 2 header_rewrite.so -- loaded - 3 debug_plugin.so -- disabled - - Example (without ``load_order``): - - .. code-block:: bash - - $ traffic_ctl plugin list - source: plugin.yaml - # plugin status - 1 stats_over_http.so loaded - 2 header_rewrite.so loaded - - This command requires a running :program:`traffic_server` instance — it - communicates via JSONRPC. - .. option:: msg TAG DATA :ref:`admin_plugin_send_basic_msg` @@ -1496,30 +897,10 @@ Runroot needs to be configured in order to let `traffic_ctl` know where to find and there is no change you have to do to interact with it, but make sure that you are not overriding the `dump_runroot=False` when creating the ATS Process, otherwise the `runroot.yaml` will not be set. -Exit Codes -========== - -:program:`traffic_ctl` uses the following exit codes: - -``0`` - Success. The requested operation completed successfully. - -``2`` - Error. The operation failed. This may be returned when: - - - The RPC communication with :program:`traffic_server` failed (e.g. socket not found or connection refused). - - The server response contains an error (e.g. invalid record name, malformed request). - -``3`` - Unimplemented. The requested command is not yet implemented. - -``75`` - Temporary failure (aligned with ``EX_TEMPFAIL`` from ``sysexits.h``). The caller is invited to retry later. - See also ======== :manpage:`records.yaml(5)`, -:manpage:`storage.yaml(5)`, +:manpage:`storage.config(5)`, :ref:`admin-jsonrpc-configuration`, :ref:`jsonrpc-protocol` diff --git a/doc/appendices/command-line/traffic_layout.en.rst b/doc/appendices/command-line/traffic_layout.en.rst index 3f90400e1c8..2ad1a3be3a8 100644 --- a/doc/appendices/command-line/traffic_layout.en.rst +++ b/doc/appendices/command-line/traffic_layout.en.rst @@ -99,8 +99,8 @@ If ``traffic_layout init --layout="custom.yml"`` is executed, a runroot followin .. Note:: - :file:`storage.yaml` does not use the cachedir value, but makes its relatives paths relative to the base prefix. - So please update the directory for cache in ``storage.yaml`` according to the customized runroot. + :file:`storage.config` does not use the cachedir value, but makes its relatives paths relative to the base prefix. + So please update the directory for cache in ``storage.config`` according to the customized runroot. remove ------ diff --git a/doc/appendices/glossary.en.rst b/doc/appendices/glossary.en.rst index 9afd4ffb9c8..913bde210bf 100644 --- a/doc/appendices/glossary.en.rst +++ b/doc/appendices/glossary.en.rst @@ -63,7 +63,7 @@ Glossary cache volume A user defined unit of persistent storage for the cache. Cache volumes - are defined in :file:`storage.yaml`. A cache volume is by default spread + are defined in :file:`volume.config`. A cache volume is by default spread across :term:`cache span`\ s to increase robustness. Each section of a cache volume on a specific cache span is a :term:`cache stripe`. @@ -74,8 +74,8 @@ Glossary smallest independent unit of storage. cache span - The physical storage described by a single entry in - :file:`storage.yaml`. + The physical storage described by a single line in + :file:`storage.config`. cache key A byte sequence that is a globally unique identifier for an diff --git a/doc/developer-guide/api/functions/TSSslContext.en.rst b/doc/developer-guide/api/functions/TSSslContext.en.rst index 22f5ca99826..08373657c12 100644 --- a/doc/developer-guide/api/functions/TSSslContext.en.rst +++ b/doc/developer-guide/api/functions/TSSslContext.en.rst @@ -38,11 +38,11 @@ Description =========== :func:`TSSslContextFindByName` searches for a SSL server context -created from :file:`ssl_multicert.yaml`, matching against the +created from :file:`ssl_multicert.config`, matching against the server :arg:`name`. :func:`TSSslContextFindByAddr` searches for a SSL server context -created from :file:`ssl_multicert.yaml` matching against the server +created from :file:`ssl_multicert.config` matching against the server :arg:`address`. diff --git a/doc/developer-guide/api/functions/TSSslServerCertUpdate.en.rst b/doc/developer-guide/api/functions/TSSslServerCertUpdate.en.rst index 15847d9b1ca..6ee1f654241 100644 --- a/doc/developer-guide/api/functions/TSSslServerCertUpdate.en.rst +++ b/doc/developer-guide/api/functions/TSSslServerCertUpdate.en.rst @@ -36,7 +36,7 @@ Description =========== :func:`TSSslServerCertUpdate` updates existing server certificates configured in -:file:`ssl_multicert.yaml` based on the common name in :arg:`cert_path`. if :arg:`key_path` is set +:file:`ssl_multicert.config` based on the common name in :arg:`cert_path`. if :arg:`key_path` is set to nullptr, the function will use :arg:`cert_path` for both certificate and private key. :func:`TSSslServerCertUpdate` returns `TS_SUCCESS` only if there exists such a mapping, :arg:`cert_path` is a valid cert, and the context is updated to use that cert. diff --git a/doc/developer-guide/api/functions/TSSslSession.en.rst b/doc/developer-guide/api/functions/TSSslSession.en.rst index 2a2ed6290c1..07a29d633e5 100644 --- a/doc/developer-guide/api/functions/TSSslSession.en.rst +++ b/doc/developer-guide/api/functions/TSSslSession.en.rst @@ -18,8 +18,8 @@ .. include:: ../../../common.defs .. default-domain:: cpp -TSSslTicketKeyUpdate -******************** +TSSslSession +************ Synopsis ======== @@ -28,20 +28,47 @@ Synopsis #include -.. function:: TSReturnCode TSSslTicketKeyUpdate(char * ticketData, int ticketDataLength) +.. function:: TSSslSession TSSslSessionGet(const TSSslSessionID * sessionid) +.. function:: int TSSslSessionGetBuffer(const TSSslSessionID * sessionid, char * buffer, int * len_ptr) +.. function:: TSReturnCode TSSslSessionInsert(const TSSslSessionID * sessionid, TSSslSession addSession, TSSslConnection ssl_conn) +.. function:: TSReturnCode TSSslSessionRemove(const TSSslSessionID * sessionid) +.. function:: void TSSslTicketKeyUpdate(char * ticketData, int ticketDataLength) Description =========== -.. note:: +These functions work with the internal ATS session cache. These functions are only useful if the ATS internal +session cache is enabled by setting :ts:cv:`proxy.config.ssl.session_cache.mode` has been set to 2. - The session ID-based session cache and its associated APIs (``TSSslSessionGet``, ``TSSslSessionGetBuffer``, - ``TSSslSessionInsert``, ``TSSslSessionRemove``) were removed in ATS 11.x. - TLS session resumption is now only supported via session tickets. +These functions tend to be used with the :enumerator:`TS_SSL_SESSION_HOOK`. -:func:`TSSslTicketKeyUpdate` updates the running ATS process to use a new set of Session Ticket Encryption keys. -This behaves the same way as updating the session ticket encrypt key file with new data and reloading the -current ATS process. However, this API does not require writing session ticket encryption keys to disk. +The functions work with the :type:`TSSslSessionID` object to identify sessions to retrieve, insert, or delete. -If both the ticket key files and :func:`TSSslTicketKeyUpdate` are used to update session ticket encryption keys, -ATS will use the most recent update regardless of whether it was made by file and configuration reload or API. +The functions also work with the :type:`TSSslSession` object which can be cast to a pointer to the OpenSSL SSL_SESSION object. + +These functions perform the appropriate locking on the session cache to avoid errors. + +The :func:`TSSslSessionGet` and :func:`TSSslSessionGetBuffer` functions retrieve the :type:`TSSslSession` object that is identified by the +:type:`TSSslSessionID` object. If there is no matching session object, :func:`TSSslSessionGet` returns nullptr and :func:`TSSslSessionGetBuffer` +returns 0. + +:func:`TSSslSessionGetBuffer` returns the session information serialized in a buffer that can be shared between processes. +When the function is called len_ptr should point to the amount of space +available in the buffer parameter. The function returns the amount of data really needed to encode the session. len_ptr is +updated with the amount of data actually stored in the buffer. +:func:`TSSslSessionGetBuffer` will not overrun the provided buffer, but the caller should ensure that the data's size was not larger +than the buffer by comparing the returned value with the value of len_ptr. If the returned value is larger than the buffer size, +then the session data did not fit in the buffer and the session data stored in the buffer output variable should not be used. + + +:func:`TSSslSessionInsert` inserts the session specified by the addSession parameter into the ATS session cache under the sessionid key. +If there is already an entry in the cache for the session id key, it is first removed before the new entry is added. + +:func:`TSSslSessionRemove` removes the session entry from the session cache that is keyed by sessionid. + +:func:`TSSslTicketKeyUpdate` updates the running ATS process to use a new set of Session Ticket Encryption keys. This behaves the same way as +updating the session ticket encrypt key file with new data and reloading the current ATS process. However, this API does not +require writing session ticket encryption keys to disk. + +If both the ticket key files and :func:`TSSslTicketKeyUpdate` are used to update session ticket encryption keys, ATS will use the +most recent update regardless if whether it was made by file and configuration reload or API. diff --git a/doc/developer-guide/cache-architecture/architecture.en.rst b/doc/developer-guide/cache-architecture/architecture.en.rst index df7914b31a4..9bc27dd54a2 100644 --- a/doc/developer-guide/cache-architecture/architecture.en.rst +++ b/doc/developer-guide/cache-architecture/architecture.en.rst @@ -59,8 +59,8 @@ out the set of bytes to be used. Cache storage ------------- -The raw storage for the |TS| cache is configured in :file:`storage.yaml`. Each -entry in the file defines a :term:`cache span` which is treated as a uniform +The raw storage for the |TS| cache is configured in :file:`storage.config`. Each +line in the file defines a :term:`cache span` which is treated as a uniform persistent store. .. figure:: images/cache-spans.png @@ -69,7 +69,7 @@ persistent store. Two cache spans This storage is organized into a set of administrative units which are called :term:`cache volumes -`. These are defined in :file:`storage.yaml`. Cache volumes can be assigned +`. These are defined in :file:`volume.config`. Cache volumes can be assigned different properties in :file:`hosting.config`. Cache volumes can be defined by a percentage of the total storage or as an absolute amount of @@ -99,7 +99,7 @@ cache volumes. It's also possible to control which cache spans (and hence, which cache stripes) are contained in a specific cache volume. The layout and structure of the cache spans, the cache volumes, and the cache -stripes that compose them are derived entirely from :file:`storage.yaml` and +stripes that compose them are derived entirely from :file:`storage.config` and :file:`cache.config` and is recomputed from scratch when the :program:`traffic_server` is started. Therefore, any change to those files can (and almost always will) invalidate the existing cache in its entirety. diff --git a/doc/developer-guide/cache-architecture/cache-initialization.en.rst b/doc/developer-guide/cache-architecture/cache-initialization.en.rst index 973309d1d86..6660404dc0f 100644 --- a/doc/developer-guide/cache-architecture/cache-initialization.en.rst +++ b/doc/developer-guide/cache-architecture/cache-initialization.en.rst @@ -23,7 +23,7 @@ Cache Initialization ******************** Initialization starts with an instance of :cpp:class:`Store` reading the storage -configuration file, by default :file:`storage.yaml`. For each valid element in +configuration file, by default :file:`storage.config`. For each valid element in the file an instance of :cpp:class:`Span` is created. These are of basically four types: diff --git a/doc/developer-guide/cache-architecture/consistency.en.rst b/doc/developer-guide/cache-architecture/consistency.en.rst index b6e1872a811..45f5221f24e 100644 --- a/doc/developer-guide/cache-architecture/consistency.en.rst +++ b/doc/developer-guide/cache-architecture/consistency.en.rst @@ -54,9 +54,9 @@ Volume Tagging Currently, :term:`cache volumes ` are allocated somewhat arbitrarily from storage elements. `This enhancement `__ -allows :file:`storage.yaml` to assign :term:`storage units ` to +allows :file:`storage.config` to assign :term:`storage units ` to specific :term:`volumes ` although the volumes must still be -listed in :file:`storage.yaml` in general and in particular to map domains to +listed in :file:`volume.config` in general and in particular to map domains to specific volumes. A primary use case for this is to be able to map specific types of content to different storage elements. This can be employed to have different storage devices for various types of content (SSD vs. rotational). diff --git a/doc/developer-guide/cache-architecture/core-cache-functions.en.rst b/doc/developer-guide/cache-architecture/core-cache-functions.en.rst index 1d24abe7a09..bcba1e35f2e 100644 --- a/doc/developer-guide/cache-architecture/core-cache-functions.en.rst +++ b/doc/developer-guide/cache-architecture/core-cache-functions.en.rst @@ -87,7 +87,7 @@ Core Cache Types .. function:: char * read_config() - Read :file:`storage.yaml` and initialize the base state of the instance. The return value is :code:`nullptr` on success and a nul-terminated error string on error. + Read :file:`storage.config` and initialize the base state of the instance. The return value is :code:`nullptr` on success and a nul-terminated error string on error. .. cpp:class:: CacheDisk diff --git a/doc/developer-guide/cache-architecture/data-structures.en.rst b/doc/developer-guide/cache-architecture/data-structures.en.rst index d3c5a65b7af..a511d1c6a49 100644 --- a/doc/developer-guide/cache-architecture/data-structures.en.rst +++ b/doc/developer-guide/cache-architecture/data-structures.en.rst @@ -400,7 +400,7 @@ Data Structures .. class:: CacheVol - A :term:`cache volume` as described in :file:`storage.yaml`. This class represents a single volume. :class:`CacheVol` comprises of stripes spread across Spans(disks) + A :term:`cache volume` as described in :file:`volume.config`. This class represents a single volume. :class:`CacheVol` comprises of stripes spread across Spans(disks) .. member:: int volume_number @@ -453,11 +453,11 @@ Data Structures .. member:: int num_volumes - Total number of volumes specified in storage.yaml + Total number of volumes specified in volume.config .. member:: int num_http_volumes - Total number of volumes specified in storage.yaml for HTTP scheme + Total number of volumes specified in volume.config for HTTP scheme .. member:: Queue cp_queue diff --git a/doc/developer-guide/cache-architecture/tiered-storage.en.rst b/doc/developer-guide/cache-architecture/tiered-storage.en.rst index fa9443fb4d2..18399f7d353 100644 --- a/doc/developer-guide/cache-architecture/tiered-storage.en.rst +++ b/doc/developer-guide/cache-architecture/tiered-storage.en.rst @@ -46,7 +46,7 @@ copied if evicted from a higher tier. Configuration ============= -Each :term:`storage unit` in :file:`storage.yaml` can be marked with a +Each :term:`storage unit` in :file:`storage.config` can be marked with a *quality* value which is 32 bit number. Storage units that are not marked are all assigned the same value which is guaranteed to be distinct from all explicit values. The quality value is arbitrary from the point of view of this design, diff --git a/doc/developer-guide/config-reload-framework.en.rst b/doc/developer-guide/config-reload-framework.en.rst deleted file mode 100644 index 549512fec22..00000000000 --- a/doc/developer-guide/config-reload-framework.en.rst +++ /dev/null @@ -1,1028 +0,0 @@ -.. Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - -.. include:: ../common.defs - -.. _config-reload-framework: - -Configuration Reload Framework -****************************** - -This guide explains how to integrate a configuration module with the |TS| reload framework. -It covers registering handlers, reporting progress through ``ConfigContext``, and the rules -every handler must follow. - -Overview -======== - -When a reload is requested (via :program:`traffic_ctl config reload` or the -:ref:`admin_config_reload` JSONRPC API), the server does not block the caller. Instead, it: - -1. **Assigns a token** to the reload — either auto-generated (e.g. ``rldtk-``) or - user-supplied via ``-t``. -2. **Schedules the reload** on background threads (``ET_TASK``). Each registered config handler - runs, reports its status (``in_progress`` → ``success`` or ``fail``), and results are - aggregated into a task tree. -3. **Returns the token** immediately so the caller can track progress via - :option:`traffic_ctl config status` or :ref:`get_reload_config_status`. - -The **token** is the unique identifier for a reload operation — it is the handle used to monitor -progress, query final status, and retrieve per-handler logs. - -``ConfigRegistry`` is a centralized singleton that manages all configuration files, their reload -handlers, trigger records, and file dependencies. It coordinates execution, tracks progress per -handler, and records the result in a queryable history. - -Key capabilities: - -- **Traceability** — every reload gets a token. Each handler reports its status and the results - are aggregated into a task tree with per-handler timings and logs. -- **Centralized registration** — one place for config files, filename records, trigger records, and - handlers. -- **Inline YAML injection** — handlers that opt in can receive YAML content directly via the RPC, - without writing to disk. -- **Coordinated reload sessions** — concurrency control, timeout detection, and history. - - -Registration API -================ - -All registration calls are made during module startup, typically from a ``startup()`` method. - -register_config ---------------- - -Register a file-based configuration handler. - -.. code-block:: cpp - - void ConfigRegistry::register_config( - const std::string &key, // unique registry key (e.g. "ip_allow") - const std::string &default_filename, // default filename (e.g. "ip_allow.yaml") - const std::string &filename_record, // record holding the filename, or "" if fixed - ConfigReloadHandler handler, // reload callback - ConfigSource source, // content source (FileOnly, FileAndRpc) - std::initializer_list triggers = {}, // records that trigger reload (optional) - bool is_required = false // whether the file must exist on disk - ); - -This is the primary registration method. It: - -1. Adds the entry to the registry. -2. Registers the file with ``FileManager`` for mtime-based change detection. -3. Wires ``RecRegisterConfigUpdateCb`` callbacks for each trigger record. - -**Example — ip_allow:** - -.. code-block:: cpp - - config::ConfigRegistry::Get_Instance().register_config( - "ip_allow", // registry key - ts::filename::IP_ALLOW, // default filename - "proxy.config.cache.ip_allow.filename", // record holding the filename - [](ConfigContext ctx) { IpAllow::reconfigure(ctx); }, // handler - config::ConfigSource::FileOnly, // no inline content - {"proxy.config.cache.ip_allow.filename"}); // trigger records - - -register_record_config ----------------------- - -Register a handler that has no config file — it only reacts to record changes. - -.. code-block:: cpp - - void ConfigRegistry::register_record_config( - const std::string &key, // unique registry key - ConfigReloadHandler handler, // reload callback - std::initializer_list triggers // records that trigger reload - ); - -Use this for modules like ``SSLTicketKeyConfig`` that are reloaded via record changes and need -visibility in the reload infrastructure, or for pure coordinator entries that own child file -dependencies. - -**Example — ssl_client_coordinator (pure coordinator):** - -.. code-block:: cpp - - config::ConfigRegistry::Get_Instance().register_record_config( - "ssl_client_coordinator", - [](ConfigContext ctx) { SSLClientCoordinator::reconfigure(ctx); }, - {"proxy.config.ssl.client.cert.path", - "proxy.config.ssl.client.cert.filename", - "proxy.config.ssl.server.session_ticket.enable"}); - -.. note:: - - When a config key has multiple trigger records, a change to **any** of them invokes the - handler **once** per reload cycle — not once per record. The framework deduplicates - internally: the first trigger creates a subtask for the config key; subsequent triggers - for the same key in the same cycle are skipped. Handlers do not need to guard against - duplicate invocations. - - -register_static_file --------------------- - -Register a non-reloadable config file for inventory purposes. Static files have no reload handler -and no trigger records. This allows the registry to serve as the single source of truth for all -known configuration files, so that RPC endpoints (e.g. ``filemanager.get_files_registry``) can -expose this information. - -.. code-block:: cpp - - void ConfigRegistry::register_static_file( - const std::string &key, // unique registry key (e.g. "storage") - const std::string &default_filename, // default filename (e.g. "storage.yaml") - const std::string &filename_record = {}, // record holding the filename (optional) - bool is_required = false // whether the file must exist on disk - ); - -Internally this delegates to ``register_config()`` with a ``nullptr`` handler, no trigger records, -and ``ConfigSource::FileOnly``. The file is registered with ``FileManager`` for mtime tracking -but no reload callback is wired. - -**Example — startup-only files:** - -.. code-block:: cpp - - auto ® = config::ConfigRegistry::Get_Instance(); - reg.register_static_file("storage", ts::filename::STORAGE, {}, true); - reg.register_static_file("socks", ts::filename::SOCKS, "proxy.config.socks.socks_config_file"); - reg.register_static_file("plugin", ts::filename::PLUGIN); - reg.register_static_file("jsonrpc", ts::filename::JSONRPC, "proxy.config.jsonrpc.filename"); - - -attach ------- - -Add an additional trigger record to an existing config entry. Can be called from any module at -any time after the entry has been registered. - -.. code-block:: cpp - - int ConfigRegistry::attach(const std::string &key, const char *record_name); - -Returns ``0`` on success, ``-1`` if the key is not found. - -**Example:** - -.. code-block:: cpp - - config::ConfigRegistry::Get_Instance().attach("ip_allow", "proxy.config.some.extra.record"); - - -add_file_dependency -------------------- - -Register an auxiliary file that a config module depends on. When this file changes on disk, -the parent config's handler is invoked. - -.. code-block:: cpp - - int ConfigRegistry::add_file_dependency( - const std::string &key, // parent config key (must exist) - const char *filename_record, // record holding the filename - const char *default_filename, // default filename - bool is_required // whether the file must exist - ); - -**Example — ip_categories as a dependency of ip_allow:** - -.. code-block:: cpp - - config::ConfigRegistry::Get_Instance().add_file_dependency( - "ip_allow", - "proxy.config.cache.ip_categories.filename", - ts::filename::IP_CATEGORIES, - false); - - -add_file_and_node_dependency ----------------------------- - -Like ``add_file_dependency()``, but also registers a **dependency key** so the RPC handler can -route inline YAML content to the parent entry's handler. - -.. code-block:: cpp - - int ConfigRegistry::add_file_and_node_dependency( - const std::string &key, // parent config key (must exist) - const std::string &dep_key, // unique dependency key for RPC routing - const char *filename_record, // record holding the filename - const char *default_filename, // default filename - bool is_required // whether the file must exist - ); - -**Example — sni.yaml as a dependency of ssl_client_coordinator:** - -.. code-block:: cpp - - config::ConfigRegistry::Get_Instance().add_file_and_node_dependency( - "ssl_client_coordinator", "sni", - "proxy.config.ssl.servername.filename", - ts::filename::SNI, false); - - -ConfigContext API -================= - -``ConfigContext`` is a lightweight value type passed to reload handlers. It provides methods to -report progress and access inline YAML content. - -``ConfigContext`` is copyable (cheap — holds a ``weak_ptr`` and a ref-counted ``YAML::Node``). -Move is intentionally suppressed: ``std::move(ctx)`` silently copies, keeping the original valid. - -in_progress(text) - Mark the task as in-progress. Accepts an optional message. - -log(text) - Append a log message to the task. These appear in - ``traffic_ctl config status -l`` output - and in :ref:`get_reload_config_status` JSONRPC responses. - -complete(text) - Mark the task as successfully completed. - -fail(reason) / fail(errata, summary) - Mark the task as failed. Accepts a plain string or a ``swoc::Errata`` with a summary. - -supplied_yaml() - Returns the YAML node supplied via the RPC ``-d`` flag or ``configs`` parameter. If no inline - content was provided, the returned node is undefined (``operator bool()`` returns ``false``). - - The framework strips the reserved ``_reload`` key from the supplied YAML before delivering it - to the handler, so ``supplied_yaml()`` always contains pure config data. - -reload_directives() - Returns the YAML map extracted from the ``_reload`` key in the RPC-supplied content. If no - directives were provided, the returned node is Undefined (``operator bool()`` returns ``false``). - - Directives are operational parameters that modify **how** the handler performs the reload — - they are distinct from config **content**. Common uses include scoping a reload to a single - entry, enabling a dry-run mode, or passing a version constraint. - - On the wire, directives are nested under ``_reload`` inside the handler's ``configs`` node: - - .. code-block:: json - - { - "configs": { - "myconfig": { - "_reload": { "id": "foo", "dry_run": "true" }, - "rules": ["rule1", "rule2"] - } - } - } - - The framework extracts ``_reload`` before the handler runs, so: - - - ``reload_directives()`` returns ``{ "id": "foo", "dry_run": "true" }`` - - ``supplied_yaml()`` returns the remaining content (without ``_reload``) - - If ``_reload`` was the only key, ``supplied_yaml()`` is undefined - - Directives and content can coexist. The handler decides how to combine them — the framework - delivers both without interpretation. - - **Recommended handler pattern:** - - .. code-block:: cpp - - void MyConfig::reconfigure(ConfigContext ctx) { - ctx.in_progress(); - - if (auto directives = ctx.reload_directives()) { - if (auto id_node = directives["id"]; id_node.IsDefined()) { - std::string id = id_node.as(); - if (!reload_single_entry(id)) { - ctx.fail("Unknown entry: " + id); - return; - } - ctx.complete("Reloaded entry: " + id); - return; - } - } - - if (auto yaml = ctx.supplied_yaml()) { - if (!load_from_yaml(yaml)) { - ctx.fail("Invalid inline content"); - return; - } - ctx.complete("Loaded from inline content"); - return; - } - - if (!load_from_file(config_filename)) { - ctx.fail("Failed to parse " + config_filename); - return; - } - ctx.complete("Loaded from file"); - } - - From :program:`traffic_ctl`, directives are passed via ``--directive`` (``-D``): - - .. code-block:: bash - - $ traffic_ctl config reload -D myconfig.id=foo - - See the ``--directive`` option in :ref:`traffic_ctl ` for details. - - .. note:: - - Directive values are strings on the wire (the JSONRPC transport serializes all values as - double-quoted strings). Handlers use yaml-cpp's ``as()`` to interpret them as needed. - -add_dependent_ctx(description) - Create a child sub-task. The parent aggregates status from all its children. - Child contexts inherit both ``supplied_yaml()`` and ``reload_directives()`` from the parent. - -All methods support ``swoc::bwprint`` format strings: - -.. code-block:: cpp - - ctx.in_progress("Parsing {} rules", count); - ctx.fail(errata, "Failed to load {}", filename); - - -.. _config-context-terminal-state: - -Terminal State Rule -=================== - -.. warning:: - - **Every** ``ConfigContext`` **must reach a terminal state** — either ``complete()`` or ``fail()`` - — **before the handler returns.** This is the single most important rule of the framework. - -The entire tracing model depends on handlers reaching a terminal state. If a handler returns without -calling ``complete()`` or ``fail()``: - -- The task stays **IN_PROGRESS** indefinitely. -- The parent task (and the entire reload) cannot finish. -- ``traffic_ctl config status`` will show the reload as stuck. -- Eventually, the timeout checker will mark the task as **TIMEOUT** - (configurable via ``proxy.config.admin.reload.timeout``, default: 1 hour — - see :ref:`reload-framework-records` below). - -**Correct handler pattern:** - -.. code-block:: cpp - - void MyConfig::reconfigure(ConfigContext ctx) { - ctx.in_progress("Loading myconfig"); - - auto [errata, config] = load_my_config(); - if (!errata.is_ok()) { - ctx.fail(errata, "Failed to load myconfig"); - return; // always return after fail - } - - // ... apply config ... - - ctx.complete("Loaded successfully"); - } - -**Every code path must end in** ``complete()`` **or** ``fail()`` — including error paths, early -returns, and exception handlers. - -**Child contexts follow the same rule.** If you call ``add_dependent_ctx()``, every child must -also reach a terminal state: - -.. code-block:: cpp - - void SSLClientCoordinator::reconfigure(ConfigContext ctx) { - ctx.in_progress(); - - SSLConfig::reconfigure(ctx.add_dependent_ctx("SSLConfig")); - SNIConfig::reconfigure(ctx.add_dependent_ctx("SNIConfig")); - - ctx.complete("SSL configs reloaded"); - } - -**Deferred handlers** — some handlers schedule work on other threads and return before completion. -The ``ConfigContext`` they hold remains valid across threads. They must call ``ctx.complete()`` or -``ctx.fail()`` from whatever thread finishes the work. If they don't, the timeout checker will mark -the task as ``TIMEOUT``. - -.. note:: - - ``ctx.complete()`` and ``ctx.fail()`` are **thread-safe**. The underlying - ``ConfigReloadTask`` guards all state transitions with a ``std::shared_mutex``. Once a task - reaches a terminal state, subsequent calls are rejected (a warning is logged). This means - calling ``complete()`` or ``fail()`` from any thread — including a different ``ET_TASK`` - thread or a callback — is safe. - -After ``ConfigRegistry::execute_reload()`` calls the handler, it checks whether the context reached -a terminal state and emits a warning if not: - -.. code-block:: cpp - - entry_copy.handler(ctx); - if (!ctx.is_terminal()) { - Warning("Config '%s' handler returned without reaching a terminal state. " - "If the handler deferred work to another thread, ensure ctx.complete() or " - "ctx.fail() is called when processing finishes.", - entry_copy.key.c_str()); - } - - -Parent Status Aggregation -------------------------- - -Parent tasks derive their status from their children: - -- **Any child failed or timed out** → parent is ``FAIL`` -- **Any child still in progress** → parent stays ``IN_PROGRESS`` -- **All children succeeded** → parent is ``SUCCESS`` - -This aggregation is recursive. A parent's ``complete()`` call sets its own status, but if any child -later fails, the parent status will be downgraded accordingly. - -.. note:: - - During a file-based reload, subtasks are discovered in two phases: file-based handlers - complete synchronously inside ``rereadConfig()``, while record-triggered handlers are activated - by record callbacks. To ensure all subtasks are registered before the reload executor returns, - ``RecFlushConfigUpdateCbs()`` is called immediately after ``rereadConfig()``. This synchronously - fires all pending record callbacks, and each ``on_record_change()`` calls ``reserve_subtask()`` - to pre-register a ``CREATED`` subtask on the main task. The total task count is therefore stable - from the first status poll. - - As a safety net, ``add_sub_task()`` also calls ``aggregate_status()`` when the parent has - already reached ``SUCCESS``, reverting it to ``IN_PROGRESS``. This handles edge cases where a - subtask is registered after all previously known work has completed. - - -ConfigSource -============ - -``ConfigSource`` declares what content sources a handler supports: - -``FileOnly`` - The handler only reloads from its file on disk. This is the default for most configs. - Inline YAML via the RPC (:ref:`admin_config_reload`) is rejected. - -``RecordOnly`` - The handler only reacts to record changes. It has no config file and no RPC content. - Used by ``register_record_config()`` implicitly. - -``FileAndRpc`` - The handler can reload from file **or** from YAML content supplied via the RPC. The handler - checks ``ctx.supplied_yaml()`` to determine the source at runtime. - - -ConfigType -========== - -``ConfigType`` identifies the file format. It is **auto-detected** from the filename extension -during registration: - -- ``.yaml``, ``.yml`` → ``ConfigType::YAML`` -- All others → ``ConfigType::LEGACY`` - -You do not set this manually — ``register_config()`` infers it from the ``default_filename``. - - -Adding a New Config Module -========================== - -Step-by-step guide for adding a new configuration file to the reload framework. - -Step 1: Choose a Registry Key ------------------------------- - -Pick a short, lowercase, underscore-separated name that identifies the config. This key is used -in ``traffic_ctl config status`` output, JSONRPC APIs, and inline YAML reload files. - -Examples: ``ip_allow``, ``logging``, ``cache_control``, ``ssl_ticket_key``, ``ssl_client_coordinator`` - -For record-only configs (registered via ``register_record_config()``), the key identifies a group -of records that share a handler — e.g. ``ssl_client_coordinator``. - -.. note:: - - Not all records support runtime reload. Records declared with ``RECU_DYNAMIC`` in - ``RecordsConfig.cc`` can trigger a handler at runtime. Records marked ``RECU_RESTART_TS`` - require a server restart and are **not** affected by the reload framework. Only register - records that are ``RECU_DYNAMIC`` as trigger records for your handler. - -Step 2: Accept a ``ConfigContext`` Parameter --------------------------------------------- - -Your handler function must accept a ``ConfigContext`` parameter. Use a default value so the -handler can also be called at startup without a reload context: - -.. code-block:: cpp - - // In the header — any function name is fine, "reconfigure" is the common convention: - static void reconfigure(ConfigContext ctx = {}); - -A default-constructed ``ConfigContext{}`` is a **no-op context**: all status calls -(``in_progress()``, ``complete()``, ``fail()``, ``log()``) are safe no-ops. This means the -same handler works at startup (no active reload) and during a reload (with tracking). - -Step 3: Report Progress ------------------------ - -Inside the handler, report progress through the context: - -.. code-block:: cpp - - void MyConfig::reconfigure(ConfigContext ctx) { - ctx.in_progress(); - - // ... load and parse config ... - - if (error) { - ctx.fail(errata, "Failed to load myconfig.yaml"); - return; - } - - ctx.log("Loaded {} rules", rule_count); - ctx.complete("Finished loading"); - } - -.. warning:: - - Remember the :ref:`terminal state rule `: - every code path must end with ``complete()`` or ``fail()``. - -Step 4: Register with ``ConfigRegistry`` ------------------------------------------ - -Call ``register_config()`` (or ``register_record_config()``) during your module's initialization -— typically in a function you call at server startup. The function name is up to you; the -convention in existing code is ``startup()``, but any name works. - -.. code-block:: cpp - - void MyConfig::startup() { // or init(), or any name - config::ConfigRegistry::Get_Instance().register_config( - "myconfig", // registry key - "myconfig.yaml", // default filename - "proxy.config.mymodule.filename", // record holding filename - [](ConfigContext ctx) { MyConfig::reconfigure(ctx); }, // handler - config::ConfigSource::FileOnly, // content source - {"proxy.config.mymodule.filename"}); // triggers - - // Initial load — ConfigContext{} is a no-op, so all ctx calls are safe - reconfigure(); - } - -Step 5: Add File Dependencies (if needed) ------------------------------------------- - -If your config depends on auxiliary files, register them: - -.. code-block:: cpp - - config::ConfigRegistry::Get_Instance().add_file_dependency( - "myconfig", - "proxy.config.mymodule.aux_filename", - "myconfig_aux.yaml", - false); // not required - -Step 6: Support Inline YAML (optional) ---------------------------------------- - -To accept YAML content via the RPC (``traffic_ctl config reload -d`` / -:ref:`admin_config_reload` with ``configs``): - -1. Change the source to ``ConfigSource::FileAndRpc`` in the registration call. -2. Check ``ctx.supplied_yaml()`` in the handler: - -.. code-block:: cpp - - void MyConfig::reconfigure(ConfigContext ctx) { - ctx.in_progress(); - - YAML::Node root; - if (auto yaml = ctx.supplied_yaml()) { - // Inline mode: content from RPC. Not persisted to disk. - root = yaml; - } else { - // File mode: read from disk. - root = YAML::LoadFile(config_filename); - } - - // ... parse and apply ... - - ctx.complete("Loaded successfully"); - } - - -Composite Configs -================= - -Some config modules coordinate multiple sub-configs. For example, ``SSLClientCoordinator`` owns -``sni.yaml`` and ``ssl_multicert.config`` as children. - -Pattern: - -1. Register with ``register_record_config()`` (no primary file). -2. Add file dependencies with ``add_file_and_node_dependency()`` for each child. -3. In the handler, create child contexts with ``add_dependent_ctx()``. - -.. code-block:: cpp - - void SSLClientCoordinator::startup() { - config::ConfigRegistry::Get_Instance().register_record_config( - "ssl_client_coordinator", - [](ConfigContext ctx) { SSLClientCoordinator::reconfigure(ctx); }, - {"proxy.config.ssl.client.cert.path", - "proxy.config.ssl.server.session_ticket.enable"}); - - config::ConfigRegistry::Get_Instance().add_file_and_node_dependency( - "ssl_client_coordinator", "sni", - "proxy.config.ssl.servername.filename", "sni.yaml", false); - - config::ConfigRegistry::Get_Instance().add_file_and_node_dependency( - "ssl_client_coordinator", "ssl_multicert", - "proxy.config.ssl.server.multicert.filename", "ssl_multicert.config", false); - } - - void SSLClientCoordinator::reconfigure(ConfigContext ctx) { - ctx.in_progress(); - SSLConfig::reconfigure(ctx.add_dependent_ctx("SSLConfig")); - SNIConfig::reconfigure(ctx.add_dependent_ctx("SNIConfig")); - SSLCertificateConfig::reconfigure(ctx.add_dependent_ctx("SSLCertificateConfig")); - ctx.complete("SSL configs reloaded"); - } - -In :option:`traffic_ctl config status`, this renders as a tree: - -.. code-block:: text - - ✔ ssl_client_coordinator ················· 35ms - ├─ ✔ SSLConfig ·························· 10ms - ├─ ✔ SNIConfig ·························· 12ms - └─ ✔ SSLCertificateConfig ·············· 13ms - - -Startup vs. Reload -================== - -A common pattern is to call the same handler at startup (initial config load) and during runtime -reloads, but this is not mandatory — it is up to the developer. The only requirement is that the -handler exposed to ``ConfigRegistry`` accepts a ``ConfigContext`` parameter. - -At startup there is no active reload task, so all ``ConfigContext`` methods are **safe no-ops** — -they check the internal weak pointer and return immediately. - -This means the same handler code works in both cases without branching: - -.. code-block:: cpp - - void MyConfig::reconfigure(ConfigContext ctx) { - ctx.in_progress(); // no-op at startup, tracks progress during reload - // ... load config ... - ctx.complete(); // no-op at startup, marks task as SUCCESS during reload - } - - -Thread Model -============ - -All reload work runs on **ET_TASK** threads — never on the RPC thread or event-loop threads. - -1. **RPC thread** — receives the JSONRPC request (:ref:`admin_config_reload`), creates the reload - token and task via ``ReloadCoordinator::prepare_reload()``, schedules the actual work on - ``ET_TASK``, and returns immediately. The RPC response is sent back before any handler runs. - -2. **ET_TASK — file-based reload** — ``ReloadWorkContinuation`` fires on ``ET_TASK``. It calls - ``FileManager::rereadConfig()``, which walks every registered file and invokes - ``ConfigRegistry::execute_reload()`` for each changed config. Each handler runs synchronously. - -3. **ET_TASK — inline (RPC) reload** — ``ScheduledReloadContinuation`` fires on ``ET_TASK``. - It calls ``ConfigRegistry::execute_reload()`` directly for the targeted config key(s). - -4. **Deferred handlers** — some handlers schedule work on other threads and return before - completion. The ``ConfigContext`` remains valid across threads. The handler must call - ``ctx.complete()`` or ``ctx.fail()`` from whatever thread finishes the work. - -5. **Timeout checker** — ``ConfigReloadProgress`` is a per-reload continuation on ``ET_TASK`` - that polls periodically and marks stuck tasks as ``TIMEOUT``. - -Handlers block ``ET_TASK`` while they run. A slow handler delays all subsequent handlers in the -same reload cycle. - - -Naming Conventions -================== - -- **Registry keys** — lowercase, underscore-separated: ``ip_allow``, ``cache_control``, - ``ssl_ticket_key``, ``ssl_client_coordinator``. -- **Filename records** — follow the existing ``proxy.config..filename`` convention. -- **Trigger records** — any ``proxy.config.*`` record that should cause a reload when changed. - - -What NOT to Register -==================== - -Not every config file needs a **reload handler**. Startup-only configs that are never reloaded at -runtime (e.g. ``storage.yaml``, ``plugin.config``) should be registered via -``register_static_file()`` — this gives them visibility in the registry and RPC endpoints, but -does not wire any reload handler or trigger records. Do not use ``register_config()`` for files -that have no runtime reload support. - - -Logging Best Practices -====================== - -- Use ``ctx.log()`` for operational messages that appear in - ``traffic_ctl config status`` and :ref:`get_reload_config_status` responses. -- Use ``ctx.fail(errata, summary)`` when you have a ``swoc::Errata`` with detailed error context. -- Use ``ctx.fail(reason)`` for simple error strings. -- Keep log messages concise — they are stored in memory and included in JSONRPC responses. - -See the :ref:`get_reload_config_status` response examples for how log messages appear in the -task tree output. - - -.. _config-reload-unified-macros: - -Unified Diagnostic Macros (``CfgLoad*``) -========================================= - -Config handlers often need the same message in two places: the ATS diagnostic log -(``diags.log`` / ``error.log``) **and** the reload task log (visible via -:option:`traffic_ctl config status`). The ``CfgLoad*`` macros in -``mgmt/config/ConfigContextDiags.h`` format the message once and dispatch to both destinations. - -Include the header in any source file that uses these macros: - -.. code-block:: cpp - - #include "mgmt/config/ConfigContextDiags.h" - -Quick Reference ---------------- - -.. list-table:: - :header-rows: 1 - :widths: 15 15 40 - - * - Want in diags? - - Want in task log? - - Use - * - Note - - yes + in_progress - - ``CfgLoadInProgress(ctx, ...)`` (subtasks) - * - Note - - yes + complete - - ``CfgLoadComplete(ctx, ...)`` - * - Error - - yes + fail - - ``CfgLoadFail(ctx, ...)`` - * - Error + Errata - - yes + fail - - ``CfgLoadFailWithErrata(ctx, errata, ...)`` - * - Note / Warning - - yes (no state change) - - ``CfgLoadLog(ctx, DL_Note|DL_Warning, ...)`` - * - Dbg (conditional on tag) - - yes - - ``CfgLoadDbg(ctx, ctl, ...)`` - * - no - - yes - - ``ctx.log(...)`` - * - no - - yes + state - - ``ctx.complete()`` / ``ctx.fail()`` - * - yes - - no - - ``Note()`` / ``Warning()`` / ``Error()`` / ``Dbg()`` directly - -Macro Details -------------- - -``CfgLoadInProgress(ctx, fmt, ...)`` - Emits a ``Note()`` to ``diags.log`` and calls ``ctx.in_progress(msg)``. The framework - sets ``IN_PROGRESS`` on handler tasks automatically, so this macro is primarily useful - for subtasks created via ``add_dependent_ctx()``: - - .. code-block:: cpp - - CfgLoadInProgress(ctx, "%s loading ...", filename); - -``CfgLoadComplete(ctx, fmt, ...)`` - Emits a ``Note()`` to ``diags.log`` and calls ``ctx.complete(msg)``. Use when a config - operation finishes successfully: - - .. code-block:: cpp - - CfgLoadComplete(ctx, "%s finished loading", filename); - -``CfgLoadFail(ctx, fmt, ...)`` - Emits an ``Error()`` to ``diags.log`` and the task log, then marks the task as FAIL. - Fail always implies ``DL_Error`` — if the condition is merely degraded (not fatal to - the load), use ``CfgLoadLog(ctx, DL_Warning, ...)`` + ``CfgLoadComplete()`` instead: - - .. code-block:: cpp - - CfgLoadFail(ctx, "%s failed to load", filename); - -``CfgLoadFailWithErrata(ctx, errata, fmt, ...)`` - Like ``CfgLoadFail`` but also appends ``swoc::Errata`` annotations to the task log. - Combines ``CfgLoadFail`` + ``ctx.fail(errata)`` in one call — see - :ref:`config-reload-errata-handling` below. - -``CfgLoadLog(ctx, level, fmt, ...)`` - Emits at the given ``DiagsLevel`` and calls ``ctx.log(level, msg)`` **without changing - task state**. Use for intermediate informational messages: - - .. code-block:: cpp - - CfgLoadLog(ctx, DL_Warning, "ControlMatcher - Cannot open config file: %s", path); - CfgLoadLog(ctx, DL_Note, "loaded %d categories from %s", count, filename); - -``CfgLoadDbg(ctx, dbg_ctl, fmt, ...)`` - Emits via ``Dbg()`` (conditional on the tag being enabled) and always adds to the task log - at ``DL_Debug``. Use for debug-level messages that should also appear in reload status: - - .. code-block:: cpp - - CfgLoadDbg(ctx, dbg_ctl_ssl, "Reload SNI file"); - -.. _config-reload-errata-handling: - -Errata Handling ---------------- - -For failures with ``swoc::Errata`` detail, use ``CfgLoadFailWithErrata`` to combine -the diags summary, errata detail, and state change in a single call: - -.. code-block:: cpp - - CfgLoadFailWithErrata(ctx, errata, "%s failed to load", filename); - -This logs the formatted message to ``diags.log`` at ``DL_Error``, appends it to -the task log, then calls ``ctx.fail(errata)`` which stores each errata annotation -(with its own severity) in the task log and marks the task as FAIL. - -For errors that should not change state, pair ``CfgLoadLog`` with ``ctx.log(errata)``: - -.. code-block:: cpp - - CfgLoadLog(ctx, DL_Error, "Cannot open %s", path); - ctx.log(errata); // errata detail -> task log only - -When NOT to Use Macros ------------------------ - -- **Task-log-only messages** — use ``ctx.log()`` directly when the message is only useful in - ``traffic_ctl`` output and should not appear in ``diags.log``. -- **State-only transitions** — use ``ctx.in_progress()`` / ``ctx.complete()`` / ``ctx.fail()`` - directly when there is no message to emit to ``diags.log``. -- **Fatal errors** — ``Fatal()`` terminates the process; reload status is irrelevant. - Call ``Fatal()`` directly. - - -Severity-Aware Task Logs -========================= - -Each task log entry carries a ``DiagsLevel`` severity. State-transition methods carry implicit -severity: ``in_progress(text)`` and ``complete(text)`` store ``DL_Note``, ``fail(text)`` stores -``DL_Error``. The ``CfgLoad*`` macros and ``ctx.log(level, text)`` store the caller-specified -level. Only the one-argument ``ctx.log(text)`` (no level) stores ``DL_Undefined`` — these -entries are always shown regardless of ``--min-level`` filtering. - -In :option:`traffic_ctl config status` output, entries with a defined severity are prefixed -with a tag: - -.. code-block:: text - - ✗ ssl_client_coordinator ······················· 2ms ✗ FAIL - │ [Note] SSL configs reloaded - ├─ ✔ SSLConfig ································· 1ms - │ [Note] SSLConfig loading ... - │ [Note] SSLConfig reloaded - ├─ ✗ SNIConfig ································· 1ms ✗ FAIL - │ [Note] sni.yaml loading ... - │ [Err] sni.yaml failed to load - └─ ✔ SSLCertificateConfig ······················ 0ms - [Note] (ssl) ssl_multicert.yaml loading ... - [Warn] Cannot open SSL certificate configuration "ssl_multicert.yaml" - No such file or directory - [Note] (ssl) ssl_multicert.yaml finished loading - -The ``--min-level`` option on :option:`traffic_ctl config status` filters log entries -by severity — see :option:`traffic_ctl config status` for details. - -The severity is also available in JSON output (``--format json``) as an integer ``level`` -field on each log entry, where the value maps to the ``DiagsLevel`` enum (e.g. ``1`` = Debug, -``3`` = Note, ``4`` = Warning, ``5`` = Error). - - -.. _config-reload-diags-log: - -Reload Summary in ``diags.log`` -================================ - -After a reload reaches a terminal state (confirmed after a 5-second grace period), a summary -line is logged to ``diags.log``: - -**Success:** - -.. code-block:: text - - NOTE: Config reload [my-token] completed: 3/3 tasks succeeded - -**Failure:** - -.. code-block:: text - - WARNING: Config reload [my-token] finished with failures: 1 succeeded, 1 failed (3 total) — run: traffic_ctl config status -t my-token - -When the ``config.reload`` debug tag is enabled, a detailed dump of all subtasks and their -log entries is written to ``traffic.out`` / ``diags.log``: - -.. code-block:: text - - DIAG: (config.reload) [fail] ssl_client_coordinator - DIAG: (config.reload) [Note] SSL configs reloaded - DIAG: (config.reload) [success] SSLConfig - DIAG: (config.reload) [Note] SSLConfig loading ... - DIAG: (config.reload) [Note] SSLConfig reloaded - DIAG: (config.reload) [fail] SNIConfig - DIAG: (config.reload) [Note] sni.yaml loading ... - DIAG: (config.reload) [Err] sni.yaml failed to load - DIAG: (config.reload) [success] ssl_ticket_key - DIAG: (config.reload) [Note] SSL ticket key loading ... - DIAG: (config.reload) [Note] SSL ticket key reloaded - -Enable this tag for troubleshooting: - -.. code-block:: yaml - - records: - diags: - debug: - enabled: 1 - tags: config.reload - - -Testing -======== - -After registering a new handler: - -1. Start |TS| and verify your handler runs at startup (check logs for your config file). -2. Modify the config file on disk and run :option:`traffic_ctl config reload` ``-m`` to observe the - live progress bar. -3. Run :option:`traffic_ctl config status` to verify the handler appears in the task tree with - the correct status. -4. Introduce a parse error in the config file and reload — verify the handler reports ``FAIL``. -5. Check that severity tags (``[Dbg]``, ``[Err]``, etc.) appear correctly in - :option:`traffic_ctl config status` output and that ``--min-level`` filtering works. -6. Enable the ``config.reload`` debug tag and verify the detailed dump appears in ``diags.log``. -7. Use :option:`traffic_ctl config status` ``--format json`` to inspect the raw - :ref:`get_reload_config_status` response for automation testing. - -**Autests** — the project includes autest helpers for config reload testing. Use -``AddJsonRPCClientRequest`` with ``Request.admin_config_reload()`` to trigger reloads, and -``Testers.CustomJSONRPCResponse`` to validate responses programmatically. See the existing tests -for examples: - -- ``tests/gold_tests/jsonrpc/config_reload_tracking.test.py`` — token generation, status - queries, history, force reload, duplicate token rejection. -- ``tests/gold_tests/jsonrpc/config_reload_rpc.test.py`` — inline reload, multiple configs, - ``FileOnly`` rejection, large payloads. -- ``tests/gold_tests/jsonrpc/config_reload_failures.test.py`` — error handling, broken configs, - handler failure reporting. - - -.. _reload-framework-records: - -Configuration Records -===================== - -The reload framework uses the following configuration records: - -.. ts:cv:: CONFIG proxy.config.admin.reload.timeout STRING 1h - :reloadable: - - Maximum time a reload task can run before being marked as ``TIMEOUT``. - Supports duration strings: ``30s``, ``5min``, ``1h``. Set to ``0`` to disable. - Default: ``1h``. - -.. ts:cv:: CONFIG proxy.config.admin.reload.check_interval STRING 2s - :reloadable: - - How often the progress checker polls for stuck tasks (minimum: ``1s``). - Supports duration strings: ``1s``, ``5s``, ``30s``. - Default: ``2s``. diff --git a/doc/developer-guide/index.en.rst b/doc/developer-guide/index.en.rst index e4f16a9ea7f..7a900585cae 100644 --- a/doc/developer-guide/index.en.rst +++ b/doc/developer-guide/index.en.rst @@ -49,7 +49,6 @@ duplicate bugs is encouraged, but not required. plugins/index.en cripts/index.en config-vars.en - config-reload-framework.en api/index.en continuous-integration/index.en documentation/index.en diff --git a/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst b/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst index d0084b7eaaf..e232a09e395 100644 --- a/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst +++ b/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst @@ -922,67 +922,27 @@ admin_config_reload Description ~~~~~~~~~~~ -Initiate a configuration reload. This method returns immediately — the actual reload runs -asynchronously on background threads (``ET_TASK``). The response contains a **token** that the -caller uses to track the reload's progress and query its final status via -:ref:`get_reload_config_status`. - -This method supports two modes: - -- **File-based reload** (default) — re-reads all registered configuration files from disk and invokes - their handlers for any files whose modification time has changed. -- **Inline reload** — when the ``configs`` parameter is present, the supplied YAML content is passed - directly to the targeted handler(s) at runtime. Inline content is **not persisted to disk** — a - server restart will revert to the file-based configuration. - -Each reload is assigned a **token** that can be used to query its status via -:ref:`get_reload_config_status`. If no token is provided, the server generates one automatically. - -Only one reload can be active at a time. If a reload is already in progress, the request is rejected -unless ``force`` is set to ``true``. - -For more information about the configuration reload framework, see -:ref:`config-reload-framework`. +Instruct |TS| to start the reloading process. You can find more information about config reload here(add link TBC) Parameters ~~~~~~~~~~ -All parameters are optional. +* ``params``: Omitted -=================== ============= ================================================================================================================ -Field Type Description -=================== ============= ================================================================================================================ -``token`` |str| Custom token for this reload. Must be unique. If omitted, the server generates one (e.g. ``rldtk-``). -``force`` ``bool`` Force a new reload even if one is in progress. Default: ``false``. Use with caution. -``configs`` ``object`` YAML content for inline reload. Each key is a registry key (e.g. ``ip_allow``, ``sni``), and each value is the - full configuration content for that handler. When present, triggers an inline reload instead of a file-based one. -=================== ============= ================================================================================================================ +.. note:: + There is no need to add any parameters here. Result ~~~~~~ -On success, the response contains: - -=================== ============= ================================================================================================================ -Field Type Description -=================== ============= ================================================================================================================ -``token`` |str| The token assigned to this reload (server-generated or the one provided in the request). -``message`` ``array`` Human-readable status messages. -``created_time`` |str| Timestamp when the reload task was created. -=================== ============= ================================================================================================================ - -If a reload is already in progress (and ``force`` is not set), the response includes an ``errors`` array -with code ``RELOAD_IN_PROGRESS`` and the ``tasks`` array with the current reload's status. - +A |str| with the success message indicating that the command was acknowledged by the server. Examples ~~~~~~~~ -**File-based reload (default):** - Request: .. code-block:: json @@ -991,264 +951,18 @@ Request: { "id": "89fc5aea-0740-11eb-82c0-001fc69cc946", "jsonrpc": "2.0", - "method": "admin_config_reload", - "params": { - "token": "deploy-v2.1" - } + "method": "admin_config_reload" } Response: -.. code-block:: json - :linenos: - - { - "jsonrpc": "2.0", - "result": { - "token": "deploy-v2.1", - "message": ["Reload task scheduled"], - "created_time": "2025 Feb 17 12:00:00" - }, - "id": "89fc5aea-0740-11eb-82c0-001fc69cc946" - } - - -**Inline reload (with ``configs``):** - -The ``configs`` parameter is a map where each key is a **registry key** (e.g. ``ip_allow``, ``sni``) -and each value is the **config content** — the inner data, not the full file structure. For example, -``ip_allow.yaml`` on disk wraps rules under an ``ip_allow:`` top-level key, but when injecting via -RPC, you pass the rules array directly. The handler receives this content via -``ctx.supplied_yaml()`` and is responsible for using it without the file's wrapper key. - -Request: - -.. code-block:: json - :linenos: - - { - "id": "b1c2d3e4-0001-0001-0001-000000000001", - "jsonrpc": "2.0", - "method": "admin_config_reload", - "params": { - "token": "update-ip-and-sni", - "configs": { - "ip_allow": [ - { - "apply": "in", - "ip_addrs": "0.0.0.0/0", - "action": "allow", - "methods": "ALL" - } - ], - "sni": [ - { - "fqdn": "*.example.com", - "verify_client": "NONE" - } - ] - } - } - } - -Response: - -.. code-block:: json - :linenos: - - { - "jsonrpc": "2.0", - "result": { - "token": "update-ip-and-sni", - "message": ["Inline reload scheduled"], - "created_time": "2025 Feb 17 14:30:10" - }, - "id": "b1c2d3e4-0001-0001-0001-000000000001" - } - -.. note:: - - Inline reload requires the target config handler to support ``ConfigSource::FileAndRpc``. - Handlers that only support ``ConfigSource::FileOnly`` will return an error for the - corresponding key. +The response will contain the default `success_response` or a proper rpc error, check :ref:`jsonrpc-node-errors` for mode details. -**Reload already in progress:** - -.. code-block:: json - :linenos: - - { - "jsonrpc": "2.0", - "result": { - "errors": [ - { - "message": "Reload ongoing with token 'deploy-v2.1'", - "code": 1 - } - ], - "tasks": [] - }, - "id": "89fc5aea-0740-11eb-82c0-001fc69cc946" - } - - -.. _get_reload_config_status: - -get_reload_config_status ------------------------- - -|method| - -Description -~~~~~~~~~~~ +Validation: -Query the status of configuration reloads. Can retrieve a specific reload by token, or the last -``N`` reloads from the history. - -Each reload status includes an overall result, a task tree with per-handler status, durations, -and optional log messages. - - -Parameters -~~~~~~~~~~ - -All parameters are optional. If neither is provided, returns the most recent reload. - -=================== ============= ================================================================================================================ -Field Type Description -=================== ============= ================================================================================================================ -``token`` |str| Token of the reload to query. Takes precedence over ``count`` if both are provided. -``count`` ``number`` Number of recent reloads to return. Use ``0`` to return the full history. Default: ``1``. -=================== ============= ================================================================================================================ - -``traffic_ctl`` maps ``--count all`` to ``count: 0`` in the RPC request. The server keeps a -rolling history of the last 100 reloads — when the limit is reached, the oldest entry is evicted -to make room for new ones. ``count: 0`` returns the full history, most recent first. - - -Result -~~~~~~ - -The response contains a ``tasks`` array. Each element represents a reload operation with the following -structure: - -======================= ============= =================================================================== -Field Type Description -======================= ============= =================================================================== -``config_token`` |str| The reload token. -``status`` |str| Overall status: ``success``, ``fail``, ``in_progress``, ``timeout``. -``description`` |str| Human-readable description. -``config_key`` |str| Registry key used for deduplication (empty for main tasks). -``filename`` |str| Associated filename (empty for the main task). -``meta`` ``object`` Metadata: ``created_time_ms``, ``last_updated_time_ms``, ``main_task``. -``log`` ``array`` Log messages from the handler. -``sub_tasks`` ``array`` Nested sub-tasks (same structure, recursive). -======================= ============= =================================================================== - - -Examples -~~~~~~~~ - - -**Query a specific reload by token:** - -Request: - -.. code-block:: json - :linenos: - - { - "id": "f1e2d3c4-0001-0001-0001-000000000001", - "jsonrpc": "2.0", - "method": "get_reload_config_status", - "params": { - "token": "deploy-v2.1" - } - } - -Response: - -.. code-block:: json - :linenos: - - { - "jsonrpc": "2.0", - "result": { - "tasks": [ - { - "config_token": "deploy-v2.1", - "status": "success", - "description": "Main reload task - 2025 Feb 17 12:00:00", - "config_key": "", - "filename": "", - "meta": { - "created_time_ms": "1739808000123", - "last_updated_time_ms": "1739808000368", - "main_task": "true" - }, - "logs": [], - "sub_tasks": [ - { - "config_token": "deploy-v2.1", - "status": "success", - "description": "ip_allow", - "config_key": "ip_allow", - "filename": "/opt/ats/etc/trafficserver/ip_allow.yaml", - "meta": { - "created_time_ms": "1739808000200", - "last_updated_time_ms": "1739808000218", - "main_task": "false" - }, - "logs": ["Finished loading"], - "sub_tasks": [] - } - ] - } - ] - }, - "id": "f1e2d3c4-0001-0001-0001-000000000001" - } - - -**Query reload history (last 3):** - -Request: - -.. code-block:: json - :linenos: - - { - "id": "a1b2c3d4-0001-0001-0001-000000000002", - "jsonrpc": "2.0", - "method": "get_reload_config_status", - "params": { - "count": 3 - } - } - -Response contains up to 3 entries in the ``tasks`` array. - - -**Token not found:** - -.. code-block:: json - :linenos: - - { - "jsonrpc": "2.0", - "result": { - "errors": [ - { - "message": "Token 'nonexistent' not found", - "code": 4 - } - ], - "token": "nonexistent" - }, - "id": "f1e2d3c4-0001-0001-0001-000000000001" - } +You can request for the record `proxy.process.proxy.reconfigure_time` which will be updated with the time of the requested update. Host @@ -1592,7 +1306,7 @@ Parameters ~~~~~~~~~~ A list of |str| names for the specific storage we want to interact with. The storage identification used in the param list should match -exactly a path specified in :file:`storage.yaml`. +exactly a path specified in :file:`storage.config`. Result ~~~~~~ @@ -1603,7 +1317,7 @@ cachedisk Field Type Description ======================= ============= ============================================================================================= ``path`` |str| Storage identification. The storage is identified by :arg:`path` which must match exactly a - path specified in :file:`storage.yaml`. + path specified in :file:`storage.config`. ``status`` |str| Disk status. ``online`` or ``offline`` ``error_count`` |str| Number of errors on the particular disk. ======================= ============= ============================================================================================= @@ -1664,7 +1378,7 @@ Description ~~~~~~~~~~~ Mark a cache storage device as ``offline``. The storage is identified by :arg:`path` which must match exactly a path specified in -:file:`storage.yaml`. This removes the storage from the cache and redirects requests that would have used this storage to +:file:`storage.config`. This removes the storage from the cache and redirects requests that would have used this storage to other storage. This has exactly the same effect as a disk failure for that storage. This does not persist across restarts of the :program:`traffic_server` process. @@ -1672,7 +1386,7 @@ Parameters ~~~~~~~~~~ A list of |str| names for the specific storage we want to interact with. The storage identification used in the param list should match -exactly a path specified in :file:`storage.yaml`. +exactly a path specified in :file:`storage.config`. Result ~~~~~~ @@ -1684,7 +1398,7 @@ A list of |object| which the following fields: Field Type Description =========================== ============= ============================================================================================= ``path`` |str| Storage identification. The storage is identified by :arg:`path` which must match exactly a - path specified in :file:`storage.yaml`. + path specified in :file:`storage.config`. ``has_online_storage_left`` |str| A flag indicating if there is any online storage left after this operation. =========================== ============= ============================================================================================= @@ -1902,7 +1616,7 @@ Field Type Description ``parent_config`` |str| Parent's configuration file name. e.g. If a top level remap.config includes additional mapping files, then the top level file will be set in this field. ``root_access_needed`` |str| Elevated access needed. -``is_required`` |str| If it's required by |TS|. This specifies if |TS| treat this file as required to start the system(e.g. storage.yaml) +``is_required`` |str| If it's required by |TS|. This specifies if |TS| treat this file as required to start the system(e.g. storage.config) ======================= ============= =========================================== @@ -1938,7 +1652,7 @@ Response: "is_required":"false" }, { - "file_path":"/home/xyz/ats/etc/trafficserver/storage.yaml", + "file_path":"/home/xyz/ats/etc/trafficserver/storage.config", "config_record_name":"", "parent_config":"N/A", "root_access_needed":"false", diff --git a/doc/developer-guide/plugins/hooks-and-transactions/ssl-hooks.en.rst b/doc/developer-guide/plugins/hooks-and-transactions/ssl-hooks.en.rst index 65fd1996abd..e3cdd5a2ded 100644 --- a/doc/developer-guide/plugins/hooks-and-transactions/ssl-hooks.en.rst +++ b/doc/developer-guide/plugins/hooks-and-transactions/ssl-hooks.en.rst @@ -84,7 +84,7 @@ TS_SSL_SERVERNAME_HOOK This hook is called if the client provides SNI information in the SSL handshake. If called it will always be called after TS_VCONN_START_HOOK. -The Traffic Server core first evaluates the settings in the ssl_multicert.yaml file based on the +The Traffic Server core first evaluates the settings in the ssl_multicert.config file based on the server name. Then the core SNI callback executes the plugin registered SNI callback code. The plugin callback can access the servername by calling the OpenSSL function SSL_get_servername(). diff --git a/doc/developer-guide/plugins/hooks-and-transactions/ssl-session-api.en.rst b/doc/developer-guide/plugins/hooks-and-transactions/ssl-session-api.en.rst index 51641d7c88c..f85ed866080 100644 --- a/doc/developer-guide/plugins/hooks-and-transactions/ssl-session-api.en.rst +++ b/doc/developer-guide/plugins/hooks-and-transactions/ssl-session-api.en.rst @@ -21,28 +21,62 @@ .. default-domain:: cpp -TLS Session Ticket Key Plugin API -********************************** +TLS Session Plugin API +********************** -This interface enables a plugin to update the session ticket encryption keys used for TLS session resumption. +These interfaces enable a plugin to hook into operations on the ATS TLS session cache. ATS also provides API's +to enable the plugin to update the session cache based on outside information, e.g. peer servers. -.. note:: +.. enumerator:: TS_SSL_SESSION_HOOK - The session ID-based session cache and its associated APIs (``TSSslSessionGet``, ``TSSslSessionGetBuffer``, - ``TSSslSessionInsert``, ``TSSslSessionRemove``, and ``TS_SSL_SESSION_HOOK``) were removed in ATS 11.x. - TLS session resumption is now only supported via session tickets. +This hook is invoked when a change has been made to the ATS session cache or a session has been accessed +from ATS via OpenSSL. These hooks are only activated if the ATS implementation of the session cache is in +use. This means :ts:cv:`proxy.config.ssl.session_cache.mode` has been set to 2. + +The hook callback has the following signature + +.. function:: int SSL_session_callback(TSCont contp, TSEvent event, void * edata) + +The edata parameter is a pointer to a :type:`TSSslSessionID`. + +This callback in synchronous since the underlying OpenSSL callback is unable to pause processing. + +The following events can be sent to this callback + +.. c:enumerator:: TS_EVENT_SSL_SESSION_NEW + + Sent after a new session has been inserted into the SSL session cache. The plugin can call :func:`TSSslSessionGet` to retrieve the actual session object. The plugin could communicate information about the new session to other processes or update additional logging or statistics. + +.. c:enumerator:: TS_EVENT_SSL_SESSION_GET + + Sent after a session has been fetched from the SSL session cache by a client request. The plugin could update additional logging and statistics. + +.. c:enumerator:: TS_EVENT_SSL_SESSION_REMOVE + + Sent after a session has been removed from the SSL session cache. The plugin could communication information about the session removal to other processes or update additional logging and statistics. Utility Functions -***************** +****************** +A number of API functions will likely be used with this hook. + +* :func:`TSSslSessionGet` +* :func:`TSSslSessionGetBuffer` +* :func:`TSSslSessionInsert` +* :func:`TSSslSessionRemove` * :func:`TSSslTicketKeyUpdate` Example Use Case **************** -Consider deploying a set of ATS servers as a farm behind a layer 4 load balancer. To enable TLS session -ticket-based resumption across all servers, they need to share the same session ticket encryption keys. +Consider deploying a set of ATS servers as a farm behind a layer 4 load balancer. The load balancer does not +guarantee that all the requests from a single client are directed to the same ATS box. Therefore, to maximize TLS session +reuse, the servers should share session state via some external communication library like redis or rabbitmq. + +To do this, they write a plugin that sets the :enumerator:`TS_SSL_SESSION_HOOK`. When the hook is triggered, the plugin function sends the +updated session state to the other ATS servers via the communication library. + +The plugin also has thread that listens for updates and calls :func:`TSSslSessionInsert` and :func:`TSSslSessionRemove` to update the local session cache accordingly. -A plugin can engage in a protocol to periodically update the session ticket encryption key and communicate -the new key to its peers. The plugin calls :func:`TSSslTicketKeyUpdate` to update the local ATS process -with the newest keys and the last N keys. +The plugin can also engage in a protocol to periodically update the session ticket encryption key and communicate the new key to its +peers. The plugin calls :func:`TSSslTicketKeyUpdate` to update the local ATS process with the newest keys and the last N keys. diff --git a/doc/getting-started/index.en.rst b/doc/getting-started/index.en.rst index dade2d8b99d..c9e8026fec9 100644 --- a/doc/getting-started/index.en.rst +++ b/doc/getting-started/index.en.rst @@ -342,15 +342,11 @@ rules like:: map https://www.acme.com/ http://localhost:8080/ map https://static.acme.com/ https://origin-static.acme.com/ -This will require installing a certificate, and adding an entry to -:file:`ssl_multicert.yaml`. Assuming the cert has the static.acme.com alternate -name, and that cert should be presented by default: +This will require installing a certificate, and adding a line to +:file:`ssl_multicert.config`. Assuming the cert has the static.acme.com alternate +name, and that cert should be presented by default:: -.. code-block:: yaml - - ssl_multicert: - - ssl_cert_name: /path/to/secret/privatekey/acme.rsa - dest_ip: "*" + dest_ip=* ssl_cert_name=/path/to/secret/privatekey/acme.rsa Further information about configuring |TS| for TLS can be found :ref:`admin-ssl-termination` section of the documentation. @@ -361,18 +357,12 @@ Adjust Cache Parameters The default |TS| configuration will provide a 256 MB disk cache, located in ``var/trafficserver/`` underneath your install prefix. You may wish to adjust either or both of the size and location of this cache. This is done with the -:file:`storage.yaml` configuration file. In our example, |AW| has dedicated +:file:`storage.config` configuration file. In our example, |AW| has dedicated a large storage pool on their cache server which is mounted at ``/cache``. To use this, and to disable the default cache storage setting, the following will -be the sole entry in :file:`storage.yaml`: - -.. code-block:: yaml +be the sole entry in :file:`storage.config`:: - cache: - spans: - - name: cache - path: /cache/trafficserver - size: 500G + /cache/trafficserver 500G .. note:: Changes to the cache configuration require a restart of |TS|. @@ -411,22 +401,13 @@ entries: map http://static.acme.com/ http://origin-static.acme.com/ map https://static.acme.com/ https://origin-static.acme.com/ -:file:`storage.yaml`: +:file:`storage.config`:: -.. code-block:: yaml - - cache: - spans: - - name: cache - path: /cache/trafficserver - size: 500G + /cache/trafficserver 500G -:file:`ssl_multicert.yaml`: - -.. code-block:: yaml +:file:`ssl_multicert.config`:: - ssl_multicert: - - ssl_cert_name: /path/to/secret/acme.rsa + ssl_cert_name=/path/to/secret/acme.rsa Configuring A Forward Proxy --------------------------- diff --git a/doc/manpages.cmake.in.py b/doc/manpages.cmake.in.py index 790a0a73b3b..7be62e7db8c 100644 --- a/doc/manpages.cmake.in.py +++ b/doc/manpages.cmake.in.py @@ -54,9 +54,11 @@ ('admin-guide/files/sni.yaml.en', 'sni.yaml', u'Traffic Server sni rules configuration file', None, '5'), ('admin-guide/files/splitdns.config.en', 'splitdns.config', u'Traffic Server split DNS configuration file', None, '5'), ( - 'admin-guide/files/ssl_multicert.yaml.en', 'ssl_multicert.yaml', u'Traffic Server SSL certificate configuration file', None, - '5'), + 'admin-guide/files/ssl_multicert.config.en', 'ssl_multicert.config', u'Traffic Server SSL certificate configuration file', + None, '5'), + ('admin-guide/files/storage.config.en', 'storage.config', u'Traffic Server cache storage configuration file', None, '5'), ('admin-guide/files/strategies.yaml.en', 'strategies.yaml', u'Traffic Server cache hierarchy configuration file', None, '5'), + ('admin-guide/files/volume.config.en', 'volume.config', u'Traffic Server cache volume configuration file', None, '5'), ] if __name__ == '__main__': diff --git a/doc/manpages.py b/doc/manpages.py index f1b5562a853..6ad2d24311e 100644 --- a/doc/manpages.py +++ b/doc/manpages.py @@ -52,8 +52,8 @@ ('admin-guide/files/sni.yaml.en', 'sni.yaml', u'Traffic Server sni rules configuration file', None, '5'), ('admin-guide/files/splitdns.config.en', 'splitdns.config', u'Traffic Server split DNS configuration file', None, '5'), ( - 'admin-guide/files/ssl_multicert.yaml.en', 'ssl_multicert.yaml', u'Traffic Server SSL certificate configuration file', None, - '5'), + 'admin-guide/files/ssl_multicert.config.en', 'ssl_multicert.config', u'Traffic Server SSL certificate configuration file', + None, '5'), ('admin-guide/files/storage.config.en', 'storage.config', u'Traffic Server cache storage configuration file', None, '5'), ('admin-guide/files/strategies.yaml.en', 'strategies.yaml', u'Traffic Server cache hierarchy configuration file', None, '5'), ('admin-guide/files/volume.config.en', 'volume.config', u'Traffic Server cache volume configuration file', None, '5'), diff --git a/doc/release-notes/upgrading.en.rst b/doc/release-notes/upgrading.en.rst index 4fde3d3c7fa..5fc96c58186 100644 --- a/doc/release-notes/upgrading.en.rst +++ b/doc/release-notes/upgrading.en.rst @@ -173,6 +173,7 @@ The following :file:`records.yaml` changes have been made: - The records.yaml entry ``proxy.config.exec_thread.autoconfig`` has been renamed to :ts:cv:`proxy.config.exec_thread.autoconfig.enabled`. - The records.yaml entry ``proxy.config.tunnel.prewarm`` has been renamed to :ts:cv:`proxy.config.tunnel.prewarm.enabled`. - The records.yaml entry ``proxy.config.ssl.origin_session_cache`` has been renamed to :ts:cv:`proxy.config.ssl.origin_session_cache.enabled`. +- The records.yaml entry ``proxy.config.ssl.session_cache`` has been renamed to :ts:cv:`proxy.config.ssl.session_cache.mode`. - The records.yaml entry ``proxy.config.ssl.TLSv1_3`` has been renamed to :ts:cv:`proxy.config.ssl.TLSv1_3.enabled`. - The records.yaml entry ``proxy.config.ssl.client.TLSv1_3`` has been renamed to :ts:cv:`proxy.config.ssl.client.TLSv1_3.enabled`. - The records.yaml entry :ts:cv:`proxy.config.allocator.iobuf_chunk_sizes` has been added @@ -182,18 +183,6 @@ The following :file:`records.yaml` changes have been made: system. - The records.yaml entry ``proxy.config.plugin.compiler_path`` has been added to specify an optional compiler tool path for compiling plugins. -The ``ssl_multicert.config`` file has been replaced with -:file:`ssl_multicert.yaml`. The new file uses YAML format with a top-level -``ssl_multicert`` key containing a sequence of certificate entries. Use -``traffic_ctl config convert ssl_multicert`` to convert existing -configuration files: - -.. code-block:: bash - - traffic_ctl config convert ssl_multicert ssl_multicert.config ssl_multicert.yaml - -See :file:`ssl_multicert.yaml` for the new format documentation. - The following changes have been made to the :file:`sni.yaml` file: - ``disable_h2`` has been removed. Use ``http2`` with :code:`off` instead. diff --git a/doc/release-notes/whats-new.en.rst b/doc/release-notes/whats-new.en.rst index 2c3da9f1a14..0c64321ac8f 100644 --- a/doc/release-notes/whats-new.en.rst +++ b/doc/release-notes/whats-new.en.rst @@ -76,14 +76,6 @@ TS API Features -------- -* Add :file:`plugin.yaml`, a YAML-based replacement for :file:`plugin.config`. - New features include disabling plugins without deleting lines - (``enabled: false``), explicit ``load_order``, inline ``config`` content, and - startup logging. See :doc:`../admin-guide/files/plugin.yaml.en`. -* traffic_ctl: Add ``plugin list`` to show loaded plugins and their status via - JSONRPC. -* traffic_ctl: Add ``config convert plugin_config`` to migrate - :file:`plugin.config` to :file:`plugin.yaml`. * Add the ``cqssg`` log field for TLS group name logging * traffic_ctl: Add a new :ref:`server ` command to show some basic internal information @@ -102,9 +94,9 @@ Features * Added option for :ts:cv:`proxy.config.http.auth_server_session_private` to only mark the connection private if ``Proxy-Authorization`` or ``Www-Authenticate`` headers are present * It is now an ``ERROR`` if a remap ACL has more than one ``@action`` parameter. This was an error in ATS 10.0.x -* Add a ``fragment_size`` option in storage.yaml to control the fragment size +* Add a ``fragment-size`` option in volume.config to control the fragment size of the volume. -* Add an optional ``avg_obj_size`` to ``storage.yaml`` to control the directory +* Add an optional ``avg_obj_size`` to ``volume.config`` to control the directory entry sizing. * The ``proxy.config.http.cache.post_method`` is now an overridable config. * Defer deleting the copied plugin shared object file to startup to make it @@ -116,11 +108,17 @@ Configuration * Added :ts:cv:`proxy.config.http.negative_revalidating_list` to configure the list of status codes that apply to the negative revalidating feature -* :ts:cv:`proxy.config.http.cache.targeted_cache_control_headers` now defaults - to ``CDN-Cache-Control``. Set it to an empty string to disable targeted cache - control. -* The ``ssl_multicert.config`` file has been replaced with :file:`ssl_multicert.yaml`. - Use ``traffic_ctl config convert ssl_multicert`` to convert existing configuration files. +* Added :ts:cv:`proxy.config.ssl.session_cache.mode` to control TLS session caching. + This is intended to replace the legacy :ts:cv:`proxy.config.ssl.session_cache.enabled` and + ``proxy.config.ssl.session_cache.value`` configurations. The + :ts:cv:`proxy.config.ssl.session_cache.enabled` setting was documented but + never implemented, while ``proxy.config.ssl.session_cache.value`` was + implemented but not documented. The new :ts:cv:`proxy.config.ssl.session_cache.mode` + functions just like the legacy ``proxy.config.ssl.session_cache.value`` did + in the ealier 10.0 release. The :ts:cv:`proxy.config.ssl.session_cache.mode` + setting provides a clear and consistent interface going forward. For backward + compatibility, ``.enabled`` is now implemented, but both ``.enabled`` and + ``.value`` will be removed in ATS 11.x. @@ -236,3 +234,4 @@ HTTP UI Removed The stats and cache inspector pages were unmaintained and removed in this release. + diff --git a/include/config/config_result.h b/include/config/config_result.h deleted file mode 100644 index c4fd2c51adc..00000000000 --- a/include/config/config_result.h +++ /dev/null @@ -1,78 +0,0 @@ -/** @file - - Generic configuration parsing result type. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#pragma once - -#include "swoc/Errata.h" - -namespace config -{ - -/** - * Result of a configuration parse operation. - * - * This template is the standard return type for all configuration parsers in - * the config library. It bundles a parsed configuration value with an errata - * that may contain warnings or errors encountered during parsing. - * - * Design rationale: - * - Parsers can return partial results (value populated) even when warnings - * are present, allowing callers to decide how to handle degraded configs. - * - The ok() method checks whether parsing succeeded without errors, but - * callers should also inspect the errata for warnings. - * - This type is reused across all configuration file types (ssl_multicert, - * etc.) to provide a consistent API. - * - * Example usage: - * @code - * config::SSLMultiCertParser parser; - * auto result = parser.parse("/path/to/ssl_multicert.yaml"); - * if (!result.ok()) { - * // Handle error - * return; - * } - * for (const auto& entry : result.value) { - * // Use parsed entries - * } - * @endcode - * - * @tparam T The configuration type (e.g., SSLMultiCertConfig). - */ -template struct ConfigResult { - T value; ///< The parsed configuration value. - swoc::Errata errata; ///< Errors or warnings from parsing. - bool file_not_found = false; ///< True if the file was not found (ENOENT). - - /** - * Check if parsing succeeded without errors. - * - * @return true if no errors occurred, false otherwise. - */ - bool - ok() const - { - return errata.is_ok(); - } -}; - -} // namespace config diff --git a/include/config/plugin_config.h b/include/config/plugin_config.h deleted file mode 100644 index db131cba074..00000000000 --- a/include/config/plugin_config.h +++ /dev/null @@ -1,57 +0,0 @@ -/** @file - - Plugin configuration parsing and marshalling. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#pragma once - -#include -#include - -#include "config/config_result.h" - -namespace config -{ - -struct PluginConfigEntry { - std::string path; - std::vector args; - bool enabled{true}; -}; - -using PluginConfigData = std::vector; - -class PluginConfigParser -{ -public: - ConfigResult parse(std::string const &filename); - -private: - ConfigResult parse_legacy(std::string_view content); -}; - -class PluginConfigMarshaller -{ -public: - std::string to_yaml(PluginConfigData const &config); -}; - -} // namespace config diff --git a/include/config/ssl_multicert.h b/include/config/ssl_multicert.h deleted file mode 100644 index b7984eba301..00000000000 --- a/include/config/ssl_multicert.h +++ /dev/null @@ -1,128 +0,0 @@ -/** @file - - SSL Multi-Certificate configuration parsing and marshalling. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#pragma once - -#include -#include -#include -#include - -#include "config/config_result.h" - -namespace config -{ - -/** - * Represents a single certificate entry in ssl_multicert configuration. - */ -struct SSLMultiCertEntry { - std::string ssl_cert_name; ///< Certificate file name (required unless action is tunnel). - std::string dest_ip{"*"}; ///< IP address to match (default "*"). - std::string ssl_key_name; ///< Private key file name (optional). - std::string ssl_ca_name; ///< CA certificate file name (optional). - std::string ssl_ocsp_name; ///< OCSP response file name (optional). - std::string ssl_key_dialog; ///< Passphrase dialog method (optional). - std::string dest_fqdn; ///< Destination FQDN (optional). - std::string action; ///< Action (e.g., "tunnel"). - std::optional ssl_ticket_enabled; ///< Session ticket enabled (optional). - std::optional ssl_ticket_number; ///< Number of session tickets (optional). -}; - -/// A configuration is a vector of certificate entries. -using SSLMultiCertConfig = std::vector; - -/** - * Parser for ssl_multicert configuration files. - * - * Supports both YAML (.yaml) and legacy (.config) formats with automatic - * format detection. - */ -class SSLMultiCertParser -{ -public: - /** - * Parse an ssl_multicert configuration file. - * - * The format is auto-detected based on file extension and content. - * - * @param[in] filename Path to the configuration file. - * @return ConfigResult containing the parsed entries or errors. - */ - ConfigResult parse(std::string const &filename); - -private: - enum class Format { YAML, Legacy }; - - /** - * Detect the configuration format from content and filename. - * - * @param[in] content The configuration content. - * @param[in] filename The filename (used for extension-based detection). - * @return The detected format. - */ - Format detect_format(std::string_view content, std::string const &filename); - - /** - * Parse YAML-formatted configuration content. - * - * @param[in] content The YAML content. - * @return ConfigResult containing the parsed entries or errors. - */ - ConfigResult parse_yaml(std::string_view content); - - /** - * Parse legacy (.config) formatted configuration content. - * - * @param[in] content The legacy config content. - * @return ConfigResult containing the parsed entries or errors. - */ - ConfigResult parse_legacy(std::string_view content); -}; - -/** - * Marshaller for ssl_multicert configuration. - * - * Serializes configuration to YAML or JSON format. - */ -class SSLMultiCertMarshaller -{ -public: - /** - * Serialize configuration to YAML format. - * - * @param[in] config The configuration to serialize. - * @return YAML string representation. - */ - std::string to_yaml(SSLMultiCertConfig const &config); - - /** - * Serialize configuration to JSON format. - * - * @param[in] config The configuration to serialize. - * @return JSON string representation. - */ - std::string to_json(SSLMultiCertConfig const &config); -}; - -} // namespace config diff --git a/include/config/storage.h b/include/config/storage.h deleted file mode 100644 index 8ce49b85f16..00000000000 --- a/include/config/storage.h +++ /dev/null @@ -1,202 +0,0 @@ -/** @file - - Storage configuration parsing and marshalling. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#pragma once - -#include -#include -#include -#include - -#include "config/config_result.h" - -namespace config -{ - -/** - * Represents a single storage span entry in storage.yaml. - */ -struct StorageSpanEntry { - std::string name; - std::string path; - std::string hash_seed; ///< optional - int64_t size = 0; ///< optional for raw device -}; - -/** - * Represents a single volume entry in storage.yaml. - */ -struct StorageVolumeEntry { - struct Size { - int64_t absolute_value = 0; - bool in_percent = false; - int percent = 0; - - bool - is_empty() const - { - return !in_percent && absolute_value == 0; - } - }; - - struct SpanRef { - std::string use; - Size size; - }; - - int id = 0; - std::string scheme = "http"; - Size size; - bool ram_cache = true; - int64_t ram_cache_size = -1; - int64_t ram_cache_cutoff = -1; - int avg_obj_size = -1; - int fragment_size = -1; - std::vector spans; -}; - -/** - * Top-level storage configuration. - */ -struct StorageConfig { - std::vector spans; - std::vector volumes; -}; - -/** - * Parser for storage.yaml / storage.config configuration files. - * - * Supports both the current YAML format (storage.yaml) and the legacy - * line-based format (storage.config). The format is auto-detected by - * file extension when parse() is used: files ending in ".yaml" are - * parsed as YAML; all others are parsed as legacy storage.config. - */ -class StorageParser -{ -public: - /** - * Parse a storage configuration file. - * - * Files ending in ".yaml" are parsed as YAML (storage.yaml); all other - * files are parsed using the legacy storage.config line-based format. - * - * @param[in] filename Path to the configuration file. - * @return ConfigResult containing the parsed configuration or errors. - */ - ConfigResult parse(std::string const &filename); - - /** - * Parse storage.yaml content from a string (useful for unit tests). - * - * @param[in] content The YAML content to parse. - * @return ConfigResult containing the parsed configuration or errors. - */ - ConfigResult parse_content(std::string_view content); - - /** - * Parse legacy storage.config content from a string. - * - * Each non-blank, non-comment line has the form: - * pathname [size_bytes] [id=hash_seed] [volume=N] - * - * Spans whose lines carry a "volume=N" annotation produce a - * StorageVolumeEntry with id=N and a span-ref to the span. - * - * @param[in] content The legacy storage.config content to parse. - * @return ConfigResult containing the parsed configuration or errors. - */ - ConfigResult parse_legacy_storage_content(std::string_view content); -}; - -/** - * Parser for the legacy volume.config configuration file. - * - * Each non-blank, non-comment line has the form: - * volume=N scheme=http size=V[%] [avg_obj_size=V] [fragment_size=V] - * [ramcache=true|false] - * [ram_cache_size=V] [ram_cache_cutoff=V] - * - * Absolute sizes are in megabytes; percent sizes are written as "N%". - */ -class VolumeParser -{ -public: - /** - * Parse a volume.config file. - * - * @param[in] filename Path to the configuration file. - * @return ConfigResult whose value has only the volumes field populated. - */ - ConfigResult parse(std::string const &filename); - - /** - * Parse legacy volume.config content from a string (useful for unit tests). - * - * @param[in] content The legacy volume.config content to parse. - * @return ConfigResult whose value has only the volumes field populated. - */ - ConfigResult parse_content(std::string_view content); -}; - -/** - * Merge a legacy storage.config result with a legacy volume.config result. - * - * The spans come from @a storage (the storage.config parse result). - * If @a volumes contains any volume entries (the volume.config parse result), - * they replace the minimal volume entries produced by storage.config - * "volume=N" annotations, while preserving the span-ref associations that - * those annotations recorded. If @a volumes is empty, the partial volume - * entries from @a storage are kept as-is. - * - * @param[in] storage Result of StorageParser::parse_legacy_storage_content(). - * @param[in] volumes Result of VolumeParser::parse_content() (may be empty). - * @return Merged StorageConfig ready for marshalling. - */ -StorageConfig merge_legacy_storage_configs(StorageConfig const &storage, StorageConfig const &volumes); - -/** - * Marshaller for storage configuration. - * - * Serializes configuration to YAML or JSON format. - */ -class StorageMarshaller -{ -public: - /** - * Serialize configuration to YAML format. - * - * @param[in] config The configuration to serialize. - * @return YAML string representation. - */ - std::string to_yaml(StorageConfig const &config); - - /** - * Serialize configuration to JSON format. - * - * @param[in] config The configuration to serialize. - * @return JSON string representation. - */ - std::string to_json(StorageConfig const &config); -}; - -} // namespace config diff --git a/include/iocore/cache/Store.h b/include/iocore/cache/Store.h index d6c7b0f23b9..1f92ef22768 100644 --- a/include/iocore/cache/Store.h +++ b/include/iocore/cache/Store.h @@ -79,10 +79,10 @@ struct Span { unsigned hw_sector_size = DEFAULT_HW_SECTOR_SIZE; unsigned alignment = 0; span_diskid_t disk_id; - bool file_pathname = false; // the pathname is a file + int forced_volume_num = -1; ///< Force span in to specific volume. + bool file_pathname = false; // the pathname is a file // v- used as a magic location for copy constructor. // we memcpy everything before this member and do explicit assignment for the rest. - ats_scoped_str name; ats_scoped_str pathname; ats_scoped_str hash_base_string; ///< Used to seed the stripe assignment hash. @@ -100,7 +100,7 @@ struct Span { return offset + blocks; } - const char *init(const char *name, const char *path, int64_t size); + const char *init(const char *n, int64_t size); /// Set the hash seed string. void hash_base_string_set(const char *s); @@ -121,9 +121,6 @@ struct Span { * explicit assignment for every member, which has its own problems. */ memcpy(static_cast(this), &that, reinterpret_cast(&(static_cast(nullptr)->pathname))); - if (that.name) { - name = ats_strdup(that.name); - } if (that.pathname) { pathname = ats_strdup(that.pathname); } @@ -158,13 +155,17 @@ struct Store { Store(){}; ~Store(); - unsigned n_spans_in_config = 0; ///< The number of disks/paths defined in storage.yaml + unsigned n_spans_in_config = 0; ///< The number of disks/paths defined in storage.config unsigned n_spans = 0; ///< The number of disks/paths we could actually read and parse Span **spans = nullptr; Result read_config(); int write_config_data(int fd) const; + + /// Additional configuration key values. + static const char VOLUME_KEY[]; + static const char HASH_BASE_STRING_KEY[]; }; // store either free or in the cache, can be stolen for reconfiguration diff --git a/include/iocore/dns/SplitDNSProcessor.h b/include/iocore/dns/SplitDNSProcessor.h index caa7b8e95d4..6097a109b9a 100644 --- a/include/iocore/dns/SplitDNSProcessor.h +++ b/include/iocore/dns/SplitDNSProcessor.h @@ -46,13 +46,14 @@ struct SplitDNSConfig { static bool isSplitDNSEnabled(); - static void reconfigure(ConfigContext ctx = {}); + static void reconfigure(); static SplitDNS *acquire(); static void release(SplitDNS *params); static void print(); - static int m_id; - static Ptr dnsHandler_mutex; + static int m_id; + static Ptr dnsHandler_mutex; + static ConfigUpdateHandler *splitDNSUpdate; static int gsplit_dns_enabled; }; diff --git a/include/iocore/eventsystem/ConfigProcessor.h b/include/iocore/eventsystem/ConfigProcessor.h index 14804a5ffe6..80b1e0b7865 100644 --- a/include/iocore/eventsystem/ConfigProcessor.h +++ b/include/iocore/eventsystem/ConfigProcessor.h @@ -70,4 +70,50 @@ class ConfigProcessor std::atomic ninfos{0}; }; +// A Continuation wrapper that calls the static reconfigure() method of the given class. +template struct ConfigUpdateContinuation : public Continuation { + int + update(int /* etype */, void * /* data */) + { + UpdateClass::reconfigure(); + delete this; + return EVENT_DONE; + } + + ConfigUpdateContinuation(Ptr &m) : Continuation(m.get()) { SET_HANDLER(&ConfigUpdateContinuation::update); } +}; + +template +int +ConfigScheduleUpdate(Ptr &mutex) +{ + eventProcessor.schedule_imm(new ConfigUpdateContinuation(mutex), ET_TASK); + return 0; +} + +template struct ConfigUpdateHandler { + ConfigUpdateHandler() : mutex(new_ProxyMutex()) {} + // The mutex member is ref-counted so should not explicitly free it + ~ConfigUpdateHandler() {} + int + attach(const char *name) + { + return RecRegisterConfigUpdateCb(name, ConfigUpdateHandler::update, this); + } + +private: + static int + update(const char *name, RecDataT /* data_type ATS_UNUSED */, RecData /* data ATS_UNUSED */, void *cookie) + { + ConfigUpdateHandler *self = static_cast(cookie); + + Dbg(_dbg_ctl, "%s(%s)", __PRETTY_FUNCTION__, name); + return ConfigScheduleUpdate(self->mutex); + } + + Ptr mutex; + + inline static DbgCtl _dbg_ctl{"config"}; +}; + extern ConfigProcessor configProcessor; diff --git a/include/iocore/net/QUICMultiCertConfigLoader.h b/include/iocore/net/QUICMultiCertConfigLoader.h index 4dad3313a63..c2bfec87a9d 100644 --- a/include/iocore/net/QUICMultiCertConfigLoader.h +++ b/include/iocore/net/QUICMultiCertConfigLoader.h @@ -30,7 +30,7 @@ class QUICCertConfig { public: static void startup(); - static void reconfigure(ConfigContext ctx = {}); + static void reconfigure(); static SSLCertLookup *acquire(); static void release(SSLCertLookup *lookup); diff --git a/include/iocore/net/SSLMultiCertConfigLoader.h b/include/iocore/net/SSLMultiCertConfigLoader.h index c12594cb0f6..42187b86269 100644 --- a/include/iocore/net/SSLMultiCertConfigLoader.h +++ b/include/iocore/net/SSLMultiCertConfigLoader.h @@ -25,12 +25,10 @@ #include "iocore/net/SSLTypes.h" #include "tsutil/DbgCtl.h" -#include "config/ssl_multicert.h" #include #include -#include #include #include #include @@ -41,7 +39,7 @@ struct SSLMultiCertConfigParams; struct SSLLoadingContext; /** - @brief Load SSL certificates from ssl_multicert.yaml and setup SSLCertLookup for SSLCertificateConfig + @brief Load SSL certificates from ssl_multicert.config and setup SSLCertLookup for SSLCertificateConfig */ class SSLMultiCertConfigLoader { @@ -53,7 +51,7 @@ class SSLMultiCertConfigLoader SSLMultiCertConfigLoader(const SSLConfigParams *p) : _params(p) {} virtual ~SSLMultiCertConfigLoader(){}; - swoc::Errata load(SSLCertLookup *lookup, bool firstLoad = false); + swoc::Errata load(SSLCertLookup *lookup); virtual SSL_CTX *default_server_ssl_ctx(); @@ -90,12 +88,6 @@ class SSLMultiCertConfigLoader virtual bool _store_ssl_ctx(SSLCertLookup *lookup, const shared_SSLMultiCertConfigParams &ssl_multi_cert_params); bool _prep_ssl_ctx(const shared_SSLMultiCertConfigParams &sslMultCertSettings, SSLMultiCertConfigLoader::CertLoadData &data, std::set &common_names, std::unordered_map> &unique_names); - - void _load_items(SSLCertLookup *lookup, config::SSLMultiCertConfig::const_iterator begin, - config::SSLMultiCertConfig::const_iterator end, int base_index, swoc::Errata &errata); - - std::mutex _loader_mutex; - virtual void _set_handshake_callbacks(SSL_CTX *ctx); virtual bool _setup_session_cache(SSL_CTX *ctx); virtual bool _setup_dialog(SSL_CTX *ctx, const SSLMultiCertConfigParams *sslMultCertSettings); @@ -109,7 +101,6 @@ class SSLMultiCertConfigLoader virtual bool _set_npn_callback(SSL_CTX *ctx); virtual bool _set_alpn_callback(SSL_CTX *ctx); virtual bool _set_keylog_callback(SSL_CTX *ctx); - virtual bool _enable_cert_compression(SSL_CTX *ctx); virtual bool _enable_ktls(SSL_CTX *ctx); virtual bool _enable_early_data(SSL_CTX *ctx); }; diff --git a/include/iocore/net/SSLSNIConfig.h b/include/iocore/net/SSLSNIConfig.h index 64dd23a7c5d..4e2612cad8c 100644 --- a/include/iocore/net/SSLSNIConfig.h +++ b/include/iocore/net/SSLSNIConfig.h @@ -43,7 +43,6 @@ #include "iocore/eventsystem/ConfigProcessor.h" #include "iocore/net/SNIActionItem.h" #include "iocore/net/YamlSNIConfig.h" -#include "mgmt/config/ConfigContext.h" #include @@ -91,8 +90,8 @@ class SNIConfigParams : public ConfigInfo ~SNIConfigParams() override; const NextHopProperty *get_property_config(const std::string &servername) const; - bool initialize(ConfigContext ctx = {}); - bool initialize(const std::string &sni_filename, ConfigContext ctx = {}); + bool initialize(); + bool initialize(const std::string &sni_filename); /** Walk sni.yaml config and populate sni_action_list @return 0 for success, 1 is failure */ @@ -118,7 +117,7 @@ class SNIConfig /** Loads sni.yaml and swap into place if successful @return 0 for success, 1 is failure */ - static int reconfigure(ConfigContext ctx = {}); + static int reconfigure(); static SNIConfigParams *acquire(); static void release(SNIConfigParams *params); diff --git a/include/iocore/net/TLSSessionResumptionSupport.h b/include/iocore/net/TLSSessionResumptionSupport.h index 38eb5d4a5c1..80c25d96d60 100644 --- a/include/iocore/net/TLSSessionResumptionSupport.h +++ b/include/iocore/net/TLSSessionResumptionSupport.h @@ -77,6 +77,19 @@ class TLSSessionResumptionSupport // --------------------------------------------------------------------------- // TLS Session Resumption Support Via Server Session Caching // --------------------------------------------------------------------------- + + /** Retrieves a cached SSL session from the session cache. + * + * This function is used to retrieve a cached SSL session from the session cache. + * + * @param[in] ssl The SSL connection object. + * @param[in] id The session ID to lookup. + * @param[in] len The length of the session ID. + * @param[out] copy Pointer to an integer indicating if the session ID should be copied. + * @return A pointer to the cached SSL session, or nullptr if not found. + */ + SSL_SESSION *getSession(SSL *ssl, const unsigned char *id, int len, int *copy); + /** * @brief Retrieves a cached SSL session from the origin session cache. * diff --git a/include/iocore/net/quic/QUICConfig.h b/include/iocore/net/quic/QUICConfig.h index 1ebec4f6aa0..c0f28786f3a 100644 --- a/include/iocore/net/quic/QUICConfig.h +++ b/include/iocore/net/quic/QUICConfig.h @@ -28,7 +28,6 @@ #include "iocore/eventsystem/ConfigProcessor.h" #include "iocore/net/SSLTypes.h" -#include "mgmt/config/ConfigContext.h" class QUICConfigParams : public ConfigInfo { @@ -36,7 +35,7 @@ class QUICConfigParams : public ConfigInfo QUICConfigParams(){}; ~QUICConfigParams(); - void initialize(ConfigContext ctx = {}); + void initialize(); uint32_t instance_id() const; uint32_t stateless_retry() const; @@ -152,7 +151,7 @@ class QUICConfig { public: static void startup(); - static void reconfigure(ConfigContext ctx = {}); + static void reconfigure(); static QUICConfigParams *acquire(); static void release(QUICConfigParams *params); diff --git a/include/mgmt/config/ConfigContext.h b/include/mgmt/config/ConfigContext.h deleted file mode 100644 index 725b0a2be49..00000000000 --- a/include/mgmt/config/ConfigContext.h +++ /dev/null @@ -1,214 +0,0 @@ -/** @file - - ConfigContext - Context for configuration loading/reloading operations - - Provides: - - Status tracking (in_progress, complete, fail, log) - - Inline content support for YAML configs (via -d flag or RPC API) - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#pragma once - -#include -#include -#include - -#include "swoc/Errata.h" -#include "swoc/BufferWriter.h" -#include "tsutil/ts_diag_levels.h" -#include "yaml-cpp/node/node.h" - -// Forward declarations -class ConfigReloadTask; -class ReloadCoordinator; -namespace config -{ -class ConfigRegistry; -} - -/// -/// @brief Context passed to config handlers during load/reload operations. -/// -/// This object is passed to reconfigure() methods to: -/// 1. Track progress/status of the operation (in_progress, complete, fail, log) -/// 2. Provide RPC-supplied YAML content (for -d flag (traffic_ctl) or direct JSONRPC calls) -/// -/// For file-based reloads, handlers read from their own registered filename. -/// For RPC reloads, handlers use supplied_yaml() to get the content. -/// -/// @note This context is also used during **startup** configuration loading. -/// At startup there is no active reload task, so all status operations -/// (in_progress, complete, fail, log) are safe **no-ops**. To keep the -/// existing code logic for loading/reloading this design aims to avoid -/// having two separate code paths for startup vs. reload — handlers -/// can use the same API in both cases. -/// -/// Usage: -/// @code -/// void MyConfig::reconfigure(ConfigContext ctx) { -/// ctx.in_progress(); -/// -/// YAML::Node root; -/// if (auto yaml = ctx.supplied_yaml()) { -/// // RPC mode: content provided via -d flag or RPC. -/// // YAML::Node has explicit operator bool() → true when IsDefined(). -/// // Copy is cheap (internally reference-counted). -/// root = yaml; -/// } else { -/// // File mode: read from registered filename. -/// root = YAML::LoadFile(my_config_filename); -/// } -/// -/// // ... process config ... -/// -/// ctx.complete("Loaded successfully"); -/// } -/// @endcode -/// -class ConfigContext -{ -public: - ConfigContext(); - - explicit ConfigContext(std::shared_ptr t, std::string_view description = "", std::string_view filename = ""); - - ~ConfigContext(); - - // Copy only — move is intentionally suppressed. - // ConfigContext holds a weak_ptr (cheap to copy) and a YAML::Node (ref-counted). - // Suppressing move ensures that std::move(ctx) silently copies, keeping the - // original valid. This is critical for execute_reload()'s post-handler check: - // if a handler defers work (e.g. LogConfig), the original ctx must remain - // valid so is_terminal() can detect the non-terminal state and emit a warning. - ConfigContext(ConfigContext const &); - ConfigContext &operator=(ConfigContext const &); - - /// Check if this context wraps a live task. - /// Returns false for default-constructed or dedup-rejected contexts. - explicit - operator bool() const - { - return !_task.expired(); - } - - void in_progress(std::string_view text = ""); - template - void - in_progress(swoc::TextView fmt, Args &&...args) - { - std::string buf; - in_progress(swoc::bwprint(buf, fmt, std::forward(args)...)); - } - - void log(std::string_view text); - void log(DiagsLevel level, std::string_view text); - void log(swoc::Errata const &errata); - template - void - log(swoc::TextView fmt, Args &&...args) - { - std::string buf; - log(swoc::bwprint(buf, fmt, std::forward(args)...)); - } - - /// Mark operation as successfully completed - void complete(std::string_view text = ""); - template - void - complete(swoc::TextView fmt, Args &&...args) - { - std::string buf; - complete(swoc::bwprint(buf, fmt, std::forward(args)...)); - } - - /// Mark operation as failed. - void fail(swoc::Errata const &errata, std::string_view summary = ""); - void fail(std::string_view reason = ""); - template - void - fail(swoc::TextView fmt, Args &&...args) - { - std::string buf; - fail(swoc::bwprint(buf, fmt, std::forward(args)...)); - } - /// Eg: fail(errata, "Failed to load config: {}", filename); - template - void - fail(swoc::Errata const &errata, swoc::TextView fmt, Args &&...args) - { - std::string buf; - fail(errata, swoc::bwprint(buf, fmt, std::forward(args)...)); - } - - /// Check if the task has reached a terminal state (SUCCESS, FAIL, TIMEOUT). - [[nodiscard]] bool is_terminal() const; - - /// Get the description associated with this context's task. - /// For registered configs this is the registration key (e.g., "sni", "ssl"). - /// For dependent contexts it is the label passed to add_dependent_ctx(). - [[nodiscard]] std::string get_description() const; - - /// Create a dependent sub-task that tracks progress independently under this parent. - /// Each dependent reports its own status (in_progress/complete/fail) and the parent - /// task aggregates them. The dependent context also inherits the parent's supplied YAML node. - /// - [[nodiscard]] ConfigContext add_dependent_ctx(std::string_view description = ""); - - /// Get supplied YAML node (for RPC-based reloads). - /// Returns Undefined when no content was provided (operator bool() == false). - /// @code - /// if (auto yaml = ctx.supplied_yaml()) { /* use yaml node */ } - /// @endcode - /// @return copy of the supplied YAML node (cheap — YAML::Node is internally reference-counted). - [[nodiscard]] YAML::Node supplied_yaml() const; - - /// Get reload directives extracted from the _reload key. - /// Directives are operational parameters that modify how the handler performs - /// the reload (e.g. scope to a single entry, dry-run) — distinct from config content. - /// The framework extracts _reload from the supplied node before passing content - /// to the handler, so supplied_yaml() never contains _reload. - /// Returns Undefined when no directives were provided (operator bool() == false). - /// @code - /// if (auto directives = ctx.reload_directives()) { /* use directives */ } - /// @endcode - /// @return copy of the directives YAML node (cheap — YAML::Node is internally reference-counted). - [[nodiscard]] YAML::Node reload_directives() const; - -private: - /// Set supplied YAML node. Only ConfigRegistry should call this during reload setup. - void set_supplied_yaml(YAML::Node node); - - /// Set reload directives. Only ConfigRegistry should call this during reload setup. - void set_reload_directives(YAML::Node node); - - std::weak_ptr _task; - YAML::Node _supplied_yaml{YAML::NodeType::Undefined}; - YAML::Node _reload_directives{YAML::NodeType::Undefined}; - - friend class ReloadCoordinator; - friend class config::ConfigRegistry; -}; - -namespace config -{ -/// Create a ConfigContext for use in reconfigure handlers -ConfigContext make_config_reload_context(std::string_view description, std::string_view filename = ""); -} // namespace config diff --git a/include/mgmt/config/ConfigContextDiags.h b/include/mgmt/config/ConfigContextDiags.h deleted file mode 100644 index 96c09c5df5a..00000000000 --- a/include/mgmt/config/ConfigContextDiags.h +++ /dev/null @@ -1,185 +0,0 @@ -/** @file - - ConfigContextDiags.h — Convenience macros for config handler logging. - - These macros combine diags output (Note/Warning/Error) with ConfigContext - task tracking in a single call. They format the message once and send it - to both destinations: - 1. The ATS diagnostics system (diags.log / error.log) - 2. The ConfigContext reload task log (visible via traffic_ctl config status) - - This ensures operators see the same information whether they look at - diags.log or query reload status via traffic_ctl / JSONRPC. - - @section when When to use these macros - - Use a macro when you want the message in BOTH diags and the reload task log. - This is the common case for operational messages in config handlers: - - @code - CfgLoadInProgress(ctx, "%s loading ...", filename); // subtasks - CfgLoadLog(ctx, DL_Note, "%s loading ...", filename); // top-level handlers - CfgLoadComplete(ctx, "%s finished loading", filename); - CfgLoadFail(ctx, "%s failed to load", filename); - @endcode - - Use ctx methods directly when you only want the reload task log (no diags): - - @code - ctx.log("parsed %d rules", count); // task log only - ctx.in_progress(); // state change only, no message - ctx.complete(); // state change only, no message - @endcode - - Use Dbg() directly when you only want debug output (no task log): - - @code - Dbg(dbg_ctl_ssl, "internal detail ..."); // diags only, not in reload status - @endcode - - Use CfgLoadDbg when you want BOTH debug output and the task log: - - @code - CfgLoadDbg(ctx, dbg_ctl_ssl, "Reload SNI file"); - @endcode - - @section summary Quick reference - - | Want diags? | Want task log? | Use | - |-------------|----------------|-----------------------------------| - | Note | yes + in_progress| CfgLoadInProgress(ctx, ...) | - | Note | yes + complete | CfgLoadComplete(ctx, ...) | - | Error | yes + fail | CfgLoadFail(ctx, ...) | - | Err + Errata| yes + fail | CfgLoadFailWithErrata(...) | - | Note/Warn | yes (no state) | CfgLoadLog(ctx, DL_xxx, ...) | - | Dbg(tag) | yes | CfgLoadDbg(ctx, ctl, ...) | - | no | yes | ctx.log(...) | - | no | yes + state | ctx.complete() / ctx.fail() | - | yes | no | Note/Warning/Error/Dbg directly | - - @section errata Errata handling - - For failures with swoc::Errata detail, use CfgLoadFailWithErrata to - combine the diags summary, errata detail, and state change in one call: - - @code - CfgLoadFailWithErrata(ctx, errata, "%s failed to load", filename); - @endcode - - This logs the formatted message to diags and the task log at the given - severity, appends each errata annotation (with its own severity) to the - task log, and marks the task as FAIL. - - @section fatal Fatal errors - - Fatal/Emergency terminate the process — reload status is irrelevant. - Call Fatal() directly; do not use these macros for it. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#pragma once - -#include "mgmt/config/ConfigContext.h" -#include "tscore/Diags.h" - -/// Log a Note and mark the context as IN_PROGRESS. -/// The framework sets IN_PROGRESS on handler tasks automatically, so -/// CfgLoadLog(ctx, DL_Note, ...) is preferred for top-level "loading..." -/// messages. Use this macro for subtasks created via add_dependent_ctx(). -/// -/// CfgLoadInProgress(ctx, "%s loading ...", ts::filename::IP_ALLOW); -/// -#define CfgLoadInProgress(CTX, FMT, ...) \ - do { \ - char _cfgctx_buf[1024]; \ - snprintf(_cfgctx_buf, sizeof(_cfgctx_buf), FMT, ##__VA_ARGS__); \ - Note("%s", _cfgctx_buf); \ - (CTX).in_progress(_cfgctx_buf); \ - } while (false) - -/// Log a Note and mark the context as SUCCESS. -/// Use when a config load/reload operation finishes successfully. -/// -/// CfgLoadComplete(ctx, "%s finished loading", ts::filename::IP_ALLOW); -/// -#define CfgLoadComplete(CTX, FMT, ...) \ - do { \ - char _cfgctx_buf[1024]; \ - snprintf(_cfgctx_buf, sizeof(_cfgctx_buf), FMT, ##__VA_ARGS__); \ - Note("%s", _cfgctx_buf); \ - (CTX).complete(_cfgctx_buf); \ - } while (false) - -/// Log an Error and mark the context as FAIL. -/// Use when a config load/reload operation fails. Fail always implies -/// DL_Error — if the condition is merely degraded (not fatal to the load), -/// use CfgLoadLog(ctx, DL_Warning, ...) + CfgLoadComplete() instead. -/// -/// CfgLoadFail(ctx, "%s failed to load", ts::filename::IP_ALLOW); -/// -#define CfgLoadFail(CTX, FMT, ...) \ - do { \ - char _cfgctx_buf[1024]; \ - snprintf(_cfgctx_buf, sizeof(_cfgctx_buf), FMT, ##__VA_ARGS__); \ - DiagsError(DL_Error, "%s", _cfgctx_buf); \ - (CTX).log(DL_Error, _cfgctx_buf); \ - (CTX).fail(); \ - } while (false) - -/// Log an Error, append errata detail to the task log, and mark the context -/// as FAIL. Combines CfgLoadFail + ctx.fail(errata) in one call. -/// -/// CfgLoadFailWithErrata(ctx, errata, "%s failed to load", filename); -/// -#define CfgLoadFailWithErrata(CTX, ERRATA, FMT, ...) \ - do { \ - char _cfgctx_buf[1024]; \ - snprintf(_cfgctx_buf, sizeof(_cfgctx_buf), FMT, ##__VA_ARGS__); \ - DiagsError(DL_Error, "%s", _cfgctx_buf); \ - (CTX).log(DL_Error, _cfgctx_buf); \ - (CTX).fail(ERRATA); \ - } while (false) - -/// Log at the given DiagsLevel and add to the task log, without changing state. -/// Use for intermediate informational messages during load/reload. -/// -/// CfgLoadLog(ctx, DL_Note, "loaded %d categories from %s", count, filename); -/// -#define CfgLoadLog(CTX, LEVEL, FMT, ...) \ - do { \ - char _cfgctx_buf[1024]; \ - snprintf(_cfgctx_buf, sizeof(_cfgctx_buf), FMT, ##__VA_ARGS__); \ - DiagsError(LEVEL, "%s", _cfgctx_buf); \ - (CTX).log(LEVEL, _cfgctx_buf); \ - } while (false) - -/// Log via a DbgCtl (debug-level, conditional on the tag) and add to the task -/// log. The debug output only appears when the tag is enabled; the task log -/// always receives the message. -/// -/// CfgLoadDbg(ctx, dbg_ctl_ssl, "Reload SNI file"); -/// -#define CfgLoadDbg(CTX, CTL, FMT, ...) \ - do { \ - char _cfgctx_buf[1024]; \ - snprintf(_cfgctx_buf, sizeof(_cfgctx_buf), FMT, ##__VA_ARGS__); \ - Dbg((CTL), "%s", _cfgctx_buf); \ - (CTX).log(DL_Debug, _cfgctx_buf); \ - } while (false) diff --git a/include/mgmt/config/ConfigRegistry.h b/include/mgmt/config/ConfigRegistry.h deleted file mode 100644 index 2ff1a8380f4..00000000000 --- a/include/mgmt/config/ConfigRegistry.h +++ /dev/null @@ -1,344 +0,0 @@ -/** @file - * - * Config Registry - Centralized configuration management - * - * Provides: - * - Registration of config handlers by key - * - Flexible trigger attachment (at registration or later) - * - RPC reload support (YAML content supplied via RPC) - * - Runtime lookup for RPC handlers - * - * @section license License - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include - -#include "iocore/eventsystem/Lock.h" // For Ptr -#include "mgmt/config/ConfigContext.h" -#include "swoc/Errata.h" - -namespace config -{ - -/// Type of configuration file -enum class ConfigType { - YAML, ///< Modern YAML config (ip_allow.yaml, sni.yaml, etc.) - LEGACY ///< Legacy .config files (remap.config, etc.) -}; - -/// Declares what content sources a config handler supports. -/// @note If more sources are needed (e.g., Plugin, Env), consider -/// converting to bitwise flags instead of adding combinatorial values. -enum class ConfigSource { - FileOnly, ///< Handler only reloads from file on disk - RecordOnly, ///< Handler only reacts to record changes (no file, no RPC content) - FileAndRpc ///< Handler can also process YAML content supplied via RPC -}; - -/// Handler signature for config reload - receives ConfigContext -/// Handler can check ctx.supplied_yaml() for rpc-supplied content -using ConfigReloadHandler = std::function; - -/// -/// @brief Central registry for configuration files -/// -/// Singleton that maps config keys to their handlers, supporting: -/// - YAML and legacy .config file types -/// - Multiple trigger records per config -/// - RPC reload with supplied YAML content (not for legacy .config) -/// - Runtime lookup by string key -/// -/// Usage: -/// @code -/// // Register with filename record (allows runtime filename changes) -/// ConfigRegistry::Get_Instance().register_yaml( -/// "ip_allow", // key -/// "ip_allow.yaml", // default filename -/// "proxy.config.cache.ip_allow.filename", // record holding filename -/// [](ConfigContext s) { IpAllow::reconfigure(s); }, -/// {"proxy.config.cache.ip_allow.filename"} // triggers -/// ); -/// -/// // Later, if needed, add another trigger from a different module -/// ConfigRegistry::Get_Instance().attach("ip_allow", "proxy.config.plugin.extra"); -/// -/// // RPC reload with supplied content: -/// // 1. Store content: registry.set_passed_config("ip_allow", yaml_node); -/// // 2. Schedule: registry.schedule_reload("ip_allow"); -/// @endcode -/// -class ConfigRegistry -{ -public: - /// - /// @brief Configuration entry - /// - struct Entry { - std::string key; ///< Registry key (e.g., "ip_allow") - std::string default_filename; ///< Default filename if record not set (e.g., "ip_allow.yaml") - std::string filename_record; ///< Record containing filename (e.g., "proxy.config.cache.ip_allow.filename") - ConfigType type; ///< YAML or LEGACY - we set that based on the filename extension. - ConfigSource source{ConfigSource::FileOnly}; ///< What content sources this handler supports - ConfigReloadHandler handler; ///< Handler function (empty = static file/not reloadable) - std::vector trigger_records; ///< Records that trigger reload - bool is_required{false}; ///< Whether the file must exist on disk - - /// Resolve the actual filename (reads from record, falls back to default) - std::string resolve_filename() const; - - /// Whether this entry has a reload handler (false for static/non-reloadable files). - bool - has_handler() const - { - return static_cast(handler); - } - }; - - /// - /// @brief Get singleton instance - /// - static ConfigRegistry &Get_Instance(); - - /// - /// @brief Register a config file - /// - /// Type (YAML/LEGACY) is inferred from filename extension: - /// - .yaml, .yml → YAML (supports rpc reload via ctx.supplied_yaml()) - /// - others → LEGACY (file-based only) - /// - /// @param key Registry key (e.g., "ip_allow") - /// @param default_filename Default filename (e.g., "ip_allow.yaml") - /// @param filename_record Record that holds the filename (e.g., "proxy.config.cache.ip_allow.filename") - /// If empty, default_filename is always used. - /// @param handler Handler that receives ConfigContext - /// @param trigger_records Records that trigger reload (optional) - /// @param is_required Whether the file must exist on disk (default false) - /// - void register_config(const std::string &key, const std::string &default_filename, const std::string &filename_record, - ConfigReloadHandler handler, ConfigSource source, std::initializer_list trigger_records = {}, - bool is_required = false); - - /// @brief Register a record-only config handler (no file). - /// - /// Convenience method for modules that have no config file but need their - /// reload handler to participate in the config tracking system (tracing, - /// status reporting, traffic_ctl config reload). - /// - /// This is NOT for arbitrary record-change callbacks — use RecRegisterConfigUpdateCb - /// for that. This is for config modules like SSLTicketKeyConfig that are reloaded - /// via record changes and need visibility in the reload infrastructure. - /// - /// Internally uses ConfigSource::RecordOnly. - /// - /// @param key Registry key (e.g., "ssl_ticket_key") - /// @param handler Handler that receives ConfigContext - /// @param trigger_records Records that trigger reload - /// - void register_record_config(const std::string &key, ConfigReloadHandler handler, - std::initializer_list trigger_records); - - /// @brief Register a non-reloadable config file (startup files). - /// - /// Static files are registered for informational purposes only — no reload - /// handler and no trigger records. This allows the registry to serve as the - /// single source of truth for all known configuration files, so that RPC - /// endpoints can gather and expose this information. - /// - /// @param key Registry key (e.g., "storage") - /// @param default_filename Default filename (e.g., "storage.config") - /// @param filename_record Record holding the filename path (optional) - /// @param is_required Whether the file must exist on disk - /// - void register_static_file(const std::string &key, const std::string &default_filename, const std::string &filename_record = {}, - bool is_required = false); - - /// - /// @brief Attach a trigger record to an existing config - /// - /// Can be called from any module to add additional triggers. - /// - /// @note Attaches an additional record trigger to an existing config entry. - /// - /// @param key The registered config key - /// @param record_name The record that triggers reload - /// @return 0 on success, -1 if key not found - /// - int attach(const std::string &key, const char *record_name); - - /// - /// @brief Add a file dependency to an existing config - /// - /// Registers an additional file with FileManager for mtime-based change detection - /// and sets up a record callback to trigger the config's reload handler when the - /// file changes on disk or the record value is modified. - /// - /// This is for auxiliary/companion files that a config module depends on but that - /// are not the primary config file. For example, ip_allow depends on ip_categories. - /// - /// - /// @param key The registered config key (must already exist) - /// @param filename_record Record holding the filename (e.g., "proxy.config.cache.ip_categories.filename") - /// @param default_filename Default filename when record value is empty (e.g., "ip_categories.yaml") - /// @param is_required Whether the file is required to exist - /// @return 0 on success, -1 if key not found - /// - int add_file_dependency(const std::string &key, const char *filename_record, const char *default_filename, bool is_required); - - /// - /// @brief Add a file dependency with an RPC-routable node key - /// - /// Like add_file_dependency(), but also registers @p dep_key as a routable key - /// so the RPC handler can route inline YAML content to the parent entry's handler. - /// - /// When an RPC reload request specifies @p dep_key, resolve() maps it to the parent - /// entry, and the content is grouped under the dep_key in the YAML node passed to - /// the handler. The handler (or its sub-modules) can then check for their key: - /// @code - /// if (auto yaml = ctx.supplied_yaml(); yaml && yaml["sni"]) { - /// // parse from yaml["sni"] instead of file - /// } - /// @endcode - /// - /// @param key The registered parent config key (must already exist) - /// @param dep_key Key for RPC routing (e.g., "sni"). Must be unique across all entries and dependencies. - /// @param filename_record Record holding the filename - /// @param default_filename Default filename when record value is empty - /// @param is_required Whether the file is required to exist - /// @return 0 on success, -1 if key not found or dep_key already exists - /// - int add_file_and_node_dependency(const std::string &key, const std::string &dep_key, const char *filename_record, - const char *default_filename, bool is_required); - - /// - /// @brief Resolve a key to its entry, handling both direct entries and dependency keys - /// - /// Looks up @p key first in the direct entry map, then in the dependency key map. - /// For direct entries, returns {key, entry}. For dependency keys, returns {parent_key, parent_entry}. - /// Returns {"", nullptr} if the key is not found. - /// - /// Used by the RPC handler to route inline content to the correct parent handler, - /// grouping multiple dependency keys under the same parent into a single reload. - /// - /// @param key The key to look up (can be a direct entry key or a dependency key) - /// @return pair of {resolved_parent_key, entry_pointer} - /// - std::pair resolve(const std::string &key) const; - - /// - /// @brief Store passed config content for a key (internal RPC use only) - /// - /// Stores YAML content passed via RPC for rpc reload. - /// Called by RPC handlers in Configuration.cc before schedule_reload(). - /// Content is kept for future reference/debugging. - /// Thread-safe. - /// - /// @note Not intended for external use. Will be moved to a restricted interface - /// when the plugin config registration API is introduced. - /// - /// @param key The registered config key - /// @param content YAML::Node content to store - /// - void set_passed_config(const std::string &key, YAML::Node content); - - /// - /// @brief Schedule async reload for a config key - /// - /// Creates a ScheduledReloadContinuation on ET_TASK. - /// If content was set via set_passed_config(), it will be used (rpc reload). - /// Otherwise, handler reads from file. - /// - /// @param key The registered config key - /// - void schedule_reload(const std::string &key); - - /// - /// @brief Execute reload for a key (called by ScheduledReloadContinuation) - /// - /// Checks for rpc-supplied content, creates context, calls handler. - /// Internal use - called from continuation on ET_TASK thread. - /// - /// @param key The config key to reload - /// - void execute_reload(const std::string &key); - - /// look up. - bool contains(const std::string &key) const; - Entry const *find(const std::string &key) const; - - /// Callback context for RecRegisterConfigUpdateCb (public for callback access) - struct TriggerContext { - std::string config_key; - Ptr mutex; - }; - -private: - ConfigRegistry() = default; - ~ConfigRegistry() = default; - - // Non-copyable - ConfigRegistry(ConfigRegistry const &) = delete; - ConfigRegistry &operator=(ConfigRegistry const &) = delete; - - /// Internal: common registration logic - void do_register(Entry entry); - - /// Internal: setup trigger callbacks for an entry - void setup_triggers(Entry &entry); - - /// Internal: wire a record callback to fire on_record_change for a config key. - /// Does NOT modify trigger_records — callers decide whether to store the record. - int wire_record_callback(const char *record_name, const std::string &config_key); - - /// Hash for lookup. - struct StringHash { - using is_transparent = void; - size_t - operator()(std::string_view sv) const - { - return std::hash{}(sv); - } - size_t - operator()(std::string const &s) const - { - return std::hash{}(s); - } - }; - - mutable std::shared_mutex _mutex; - std::unordered_map> _entries; - std::unordered_map> _passed_configs; - /// Maps dependency keys to their parent entry's key. - /// - /// When a coordinator entry manages multiple configuration files, each file can - /// be given a dependency key via add_file_and_node_dependency(). This allows - /// resolve() to route RPC-supplied content for a dependency key back to the - /// parent coordinator's handler, so a single reload fires for all related files. - std::unordered_map> _dep_key_to_parent; -}; - -} // namespace config diff --git a/include/mgmt/config/ConfigReloadErrors.h b/include/mgmt/config/ConfigReloadErrors.h deleted file mode 100644 index 7b1fdcdfcdc..00000000000 --- a/include/mgmt/config/ConfigReloadErrors.h +++ /dev/null @@ -1,53 +0,0 @@ -/** @file - - Config reload error codes — shared between server (Configuration.cc) and client (CtrlCommands.cc). - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#pragma once - -namespace config::reload::errors -{ -/// Error codes for config reload RPC operations. -/// Used in the YAML error nodes exchanged between traffic_server and traffic_ctl. -/// -/// Range 6001–6099: general reload lifecycle errors -/// Range 6010–6019: per-config validation errors -enum class ConfigReloadError : int { - // --- General reload errors --- - TOKEN_NOT_FOUND = 6001, ///< Requested token does not exist in history - TOKEN_ALREADY_EXISTS = 6002, ///< Token name already in use - RELOAD_TASK_FAILED = 6003, ///< Failed to create or kick off reload task - RELOAD_IN_PROGRESS = 6004, ///< A reload is already running (use --force to override) - NO_RELOAD_TASKS = 6005, ///< No reload tasks found in history - - // --- Per-config validation errors --- - CONFIG_NOT_REGISTERED = 6010, ///< Config key not found in ConfigRegistry - RPC_SOURCE_NOT_SUPPORTED = 6011, ///< Config does not support RPC as a content source - CONFIG_NO_HANDLER = 6012, ///< Config is registered but has no reload handler -}; - -/// Helper to convert enum to int for YAML node construction -constexpr int -to_int(ConfigReloadError e) -{ - return static_cast(e); -} -} // namespace config::reload::errors diff --git a/include/mgmt/config/ConfigReloadExecutor.h b/include/mgmt/config/ConfigReloadExecutor.h deleted file mode 100644 index 9a146ea4e0c..00000000000 --- a/include/mgmt/config/ConfigReloadExecutor.h +++ /dev/null @@ -1,43 +0,0 @@ -/** @file - - Config reload execution logic - schedules async reload work on ET_TASK. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#pragma once - -#include - -namespace config -{ - -/// Schedule the config reload work to be executed on the ET_TASK thread. -/// -/// This function schedules the actual reload work which includes: -/// - Calling FileManager::rereadConfig() to detect and reload changed files -/// - Calling FileManager::invokeConfigPluginCallbacks() to notify registered plugins -/// -/// The reload status is tracked via ReloadCoordinator which must have been -/// initialized with a task before calling this function. -/// -/// @param delay Delay before executing the reload (default: 10ms) -void schedule_reload_work(std::chrono::milliseconds delay = std::chrono::milliseconds{10}); - -} // namespace config diff --git a/include/mgmt/config/ConfigReloadTrace.h b/include/mgmt/config/ConfigReloadTrace.h deleted file mode 100644 index 7d9dadbca07..00000000000 --- a/include/mgmt/config/ConfigReloadTrace.h +++ /dev/null @@ -1,383 +0,0 @@ -/** @file - * - * ConfigReloadTrace - Reload task tracking and progress reporting - * - * @section license License - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include "iocore/eventsystem/Continuation.h" -#include "iocore/eventsystem/EventSystem.h" -#include "iocore/eventsystem/Event.h" -#include "iocore/eventsystem/Tasks.h" -#include "tscore/Diags.h" - -class ConfigReloadTask; -class ConfigContext; - -namespace YAML -{ -template struct convert; -} // namespace YAML - -using ConfigReloadTaskPtr = std::shared_ptr; - -/// -/// @brief Progress checker for reload tasks — detects stuck/hanging tasks. -/// -/// Created per-reload by ConfigReloadTask::start_progress_checker(), which is called -/// only for main tasks in the IN_PROGRESS state. Each instance is bound to a single -/// ConfigReloadTask and holds a shared_ptr to it. -/// -/// Lifecycle: -/// - Scheduled on ET_TASK when a reload starts. -/// - check_progress() runs periodically (every check_interval). -/// - Self-terminates (returns EVENT_DONE) when: -/// * The task reaches a terminal state (SUCCESS, FAIL, TIMEOUT). -/// * The task exceeds the configured timeout (marked as TIMEOUT, then stops). -/// * The _reload pointer is null (defensive). -/// - Reschedules itself (EVENT_CONT) only while the task is still non-terminal. -/// - No idle polling — when no reload is in progress, no checker exists. -/// -/// Configurable via records: -/// - proxy.config.admin.reload.timeout: Duration string (default: "1h") -/// Supports: "30s", "5min", "1h", "1 hour 30min", "0" (disabled) -/// - proxy.config.admin.reload.check_interval: Duration string (default: "2s") -/// Minimum: 1s (enforced). How often to check task progress. -/// -/// If timeout is 0 or empty, timeout is disabled. Tasks can hang forever (BAD). -/// Use --force (traffic_ctl / RPC API) flag to bypass the in-progress guard and start a new reload. -/// Note: --force only marks the existing tracking task as stale; it does not cancel the actual work. -/// -struct ConfigReloadProgress : public Continuation { - /// Record names for configuration - static constexpr std::string_view RECORD_TIMEOUT = "proxy.config.admin.reload.timeout"; - static constexpr std::string_view RECORD_CHECK_INTERVAL = "proxy.config.admin.reload.check_interval"; - - /// Defaults - static constexpr std::string_view DEFAULT_TIMEOUT = "1h"; ///< 1 hour - static constexpr std::string_view DEFAULT_CHECK_INTERVAL = "2s"; ///< 2 seconds - static constexpr int64_t MIN_CHECK_INTERVAL_MS = 1000; ///< 1 second minimum - - ConfigReloadProgress(ConfigReloadTaskPtr reload); - int check_progress(int /* etype */, void * /* data */); - - /// Read timeout value from records, returns 0ms if disabled or invalid - [[nodiscard]] static std::chrono::milliseconds get_configured_timeout(); - - /// Read check interval from records, enforces minimum of 1s - [[nodiscard]] static std::chrono::milliseconds get_configured_check_interval(); - - /// Get the check interval for this instance - [[nodiscard]] std::chrono::milliseconds - get_check_interval() const - { - return _every; - } - -private: - /// Grace period before accepting a terminal state as final. A late reserve_subtask() - /// can pull the parent back from SUCCESS to IN_PROGRESS; this delay lets the checker - /// detect that transition instead of stopping prematurely. - static constexpr std::chrono::milliseconds TERMINAL_CONFIRMATION_DELAY{5000}; - ConfigReloadTaskPtr _reload{nullptr}; - std::chrono::milliseconds _every{std::chrono::seconds{2}}; ///< Set from config in constructor - bool _awaiting_terminal_confirmation{false}; ///< true after first terminal observation -}; - -/// -/// @brief Tracks the status and progress of a single config reload operation. -/// -/// Represents either a top-level (main) reload task or a sub-task for an individual -/// config module. Tasks form a tree: the main task has sub-tasks for each config, -/// and sub-tasks can themselves have children (e.g., SSLClientCoordinator → SSLConfig, SNIConfig). -/// -/// State flows: CREATED → IN_PROGRESS → SUCCESS / FAIL / TIMEOUT -/// Parent tasks aggregate state from their children automatically. -/// -/// Serialized to YAML via YAML::convert for RPC responses. -/// -class ConfigReloadTask : public std::enable_shared_from_this -{ -public: - enum class State { - INVALID = -1, - CREATED, ///< Initial state — task exists but not started - IN_PROGRESS, ///< Work is actively happening - SUCCESS, ///< Terminal: completed successfully - FAIL, ///< Terminal: error occurred - TIMEOUT ///< Terminal: task exceeded time limit - }; - - /// Check if a state represents a terminal (final) state - [[nodiscard]] static constexpr bool - is_terminal(State s) noexcept - { - return s == State::SUCCESS || s == State::FAIL || s == State::TIMEOUT; - } - - /// Convert State enum to string - [[nodiscard]] static constexpr std::string_view - state_to_string(State s) noexcept - { - switch (s) { - case State::INVALID: - return "invalid"; - case State::CREATED: - return "created"; - case State::IN_PROGRESS: - return "in_progress"; - case State::SUCCESS: - return "success"; - case State::FAIL: - return "fail"; - case State::TIMEOUT: - return "timeout"; - } - return "unknown"; - } - - /// Helper to get current time in milliseconds since epoch - [[nodiscard]] static int64_t - now_ms() - { - return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - } - - /// A single log entry with optional severity. - /// Entries from log(level, text) carry the supplied DiagsLevel. State-change - /// convenience methods attach an implicit level: in_progress() and complete() - /// log at DL_Note, fail() logs at DL_Error. The one-argument log(text) form - /// stores DL_Undefined and is always displayed (never filtered by --min-level). - struct LogEntry { - DiagsLevel level{DL_Undefined}; ///< DL_Undefined = always shown (filter bypass) - std::string text; - }; - - struct Info { - friend class ConfigReloadTask; - /// Grant friendship to the specific YAML::convert specialization. - friend struct YAML::convert; - Info() = default; - Info(State p_state, std::string_view p_token, std::string_view p_description, bool p_main_task) - : state(p_state), token(p_token), description(p_description), main_task(p_main_task) - { - } - - protected: - int64_t created_time_ms{now_ms()}; ///< milliseconds since epoch - int64_t last_updated_time_ms{now_ms()}; ///< last time this task was updated (ms) - std::vector logs; ///< log messages from handler - State state{State::CREATED}; - std::string token; - std::string description; - std::string config_key; ///< registry key for dedup (empty for main/child tasks) - std::string filename; ///< source file, if applicable - std::vector sub_tasks; ///< child tasks (if any) - bool main_task{false}; ///< true for the top-level reload task - }; - - using self_type = ConfigReloadTask; - ConfigReloadTask() = default; - ConfigReloadTask(std::string_view token, std::string_view description, bool main_task, ConfigReloadTaskPtr parent) - : _info(State::CREATED, token, description, main_task), _parent{parent} - { - if (_info.main_task) { - _info.state = State::IN_PROGRESS; - } - } - - /// Start the periodic progress checker (ConfigReloadProgress). - /// Only runs once, and only for main tasks. The checker detects stuck tasks - /// and marks them as TIMEOUT if they exceed the configured time limit. - void start_progress_checker(); - - /// Create a child sub-task and return a ConfigContext wrapping it. - /// The child inherits the parent's token and if passed, the supplied YAML content. - [[nodiscard]] ConfigContext add_child(std::string_view description = ""); - - self_type &log(std::string const &text); - self_type &log(DiagsLevel level, std::string const &text); - void set_completed(); - void set_failed(); - void set_in_progress(); - - void - set_description(std::string_view description) - { - std::unique_lock lock(_mutex); - _info.description = description; - } - - [[nodiscard]] std::string - get_description() const - { - std::shared_lock lock(_mutex); - return _info.description; - } - - void - set_filename(std::string_view filename) - { - std::unique_lock lock(_mutex); - _info.filename = filename; - } - - [[nodiscard]] std::string - get_filename() const - { - std::shared_lock lock(_mutex); - return _info.filename; - } - - void - set_config_key(std::string_view key) - { - std::unique_lock lock(_mutex); - _info.config_key = key; - } - - /// Check if any immediate subtask has the given config_key. - /// Used by ReloadCoordinator to prevent duplicate subtasks within a single reload cycle. - [[nodiscard]] bool has_subtask_for_key(std::string_view key) const; - - /// Find a subtask by its config_key. Returns nullptr if not found. - [[nodiscard]] ConfigReloadTaskPtr find_subtask_by_key(std::string_view key) const; - - /// Debug utility: dump task tree to an output stream. - /// Recursively prints this task and all sub-tasks with indentation. - - [[nodiscard]] bool - contains_dependents() const - { - std::shared_lock lock(_mutex); - return !_info.sub_tasks.empty(); - } - - /// Get created time in seconds (for Date formatting and metrics). - /// No lock needed — created_time_ms is immutable after construction. - [[nodiscard]] std::time_t - get_created_time() const - { - return static_cast( - std::chrono::duration_cast(std::chrono::milliseconds{_info.created_time_ms}).count()); - } - - /// Get created time in milliseconds since epoch. - /// No lock needed — created_time_ms is immutable after construction. - [[nodiscard]] int64_t - get_created_time_ms() const - { - return _info.created_time_ms; - } - - [[nodiscard]] State - get_state() const - { - std::shared_lock lock(_mutex); - return _info.state; - } - - /// Mark task as TIMEOUT with an optional reason logged - void mark_as_bad_state(std::string_view reason = ""); - - [[nodiscard]] std::vector - get_logs() const - { - std::shared_lock lock(_mutex); - return _info.logs; - } - - [[nodiscard]] std::string - get_token() const - { - std::shared_lock lock(_mutex); - return _info.token; - } - - [[nodiscard]] bool - is_main_task() const - { - std::shared_lock lock(_mutex); - return _info.main_task; - } - - /// Create a copy of the current task info - [[nodiscard]] Info - get_info() const - { - std::shared_lock lock(_mutex); - Info copy = _info; - copy.last_updated_time_ms = _atomic_last_updated_ms.load(std::memory_order_acquire); - return copy; - } - - /// Get last updated time in seconds (considers subtasks) - [[nodiscard]] std::time_t get_last_updated_time() const; - - /// Get last updated time in milliseconds (considers subtasks) - [[nodiscard]] int64_t get_last_updated_time_ms() const; - - void - update_last_updated_time() - { - _atomic_last_updated_ms.store(now_ms(), std::memory_order_release); - } - - /// Read the last updated time for this task only (no subtask traversal, lock-free) - [[nodiscard]] int64_t - get_own_last_updated_time_ms() const - { - return _atomic_last_updated_ms.load(std::memory_order_acquire); - } - -private: - /// Add a pre-created sub-task to this task's children list. - /// Called by ReloadCoordinator::create_config_context(). - void add_sub_task(ConfigReloadTaskPtr sub_task); - void on_sub_task_update(State state); - void aggregate_status(); - void notify_parent(); - void set_state_and_notify(State state); - - friend struct ConfigReloadProgress; - void log_reload_summary(State final_state); - static void dump_subtask_tree(const std::vector &tasks, int indent); - - mutable std::shared_mutex _mutex; - bool _reload_progress_checker_started{false}; - bool _summary_logged{false}; - Info _info; - ConfigReloadTaskPtr _parent; ///< parent task, if any - - std::atomic _atomic_last_updated_ms{now_ms()}; - - friend class ReloadCoordinator; -}; diff --git a/include/mgmt/config/FileManager.h b/include/mgmt/config/FileManager.h index 0d44cc4714d..57b115321bd 100644 --- a/include/mgmt/config/FileManager.h +++ b/include/mgmt/config/FileManager.h @@ -36,6 +36,8 @@ #include +class ConfigUpdateCbTable; + class FileManager { public: @@ -140,7 +142,7 @@ class FileManager bool isConfigStale(); void configFileChild(const char *parent, const char *child); - void registerConfigPluginCallbacks(std::function cb); + void registerConfigPluginCallbacks(ConfigUpdateCbTable *cblist); void invokeConfigPluginCallbacks(); static FileManager & @@ -153,8 +155,8 @@ class FileManager private: FileManager(); - ink_mutex accessLock; // Protects bindings hashtable - std::function _pluginCallback; + ink_mutex accessLock; // Protects bindings hashtable + ConfigUpdateCbTable *_pluginCallbackList{nullptr}; std::mutex _callbacksMutex; std::mutex _accessMutex; @@ -167,3 +169,5 @@ class FileManager /// JSONRPC endpoint swoc::Rv get_files_registry_rpc_endpoint(std::string_view const &id, YAML::Node const ¶ms); }; + +void initializeRegistry(); // implemented in AddConfigFilesHere.cc diff --git a/include/mgmt/config/ReloadCoordinator.h b/include/mgmt/config/ReloadCoordinator.h deleted file mode 100644 index 8dd1adfa269..00000000000 --- a/include/mgmt/config/ReloadCoordinator.h +++ /dev/null @@ -1,124 +0,0 @@ -/** @file - * - * ReloadCoordinator - Manages config reload lifecycle and concurrency - * - * @section license License - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -#include -#include -#include -#include -#include "tscore/Diags.h" -#include "mgmt/config/ConfigReloadTrace.h" -#include "mgmt/config/ConfigContext.h" - -class ReloadCoordinator -{ -public: - /// Initialize a new reload session. Generates a token, checks for in-progress reloads, - /// and creates the main tracking task. Use --force to bypass the in-progress guard. - [[nodiscard]] swoc::Errata prepare_reload(std::string &token_name, const char *token_prefix = "rldtk-", bool force = false); - - /// Return snapshots of the last N reload tasks (0 = all). Most recent first. - [[nodiscard]] std::vector get_all(std::size_t N = 0) const; - - /// Look up a reload task by its token. Returns {found, info_snapshot}. - [[nodiscard]] std::pair find_by_token(std::string_view token) const; - - /// Singleton access - static ReloadCoordinator & - Get_Instance() - { - static ReloadCoordinator instance; - return instance; - } - - [[nodiscard]] std::shared_ptr - get_current_task() const - { - std::shared_lock lock(_mutex); - return _current_task; - } - - /// Create a sub-task context for the given config key. - /// - /// Deduplication: if a subtask with the same @a config_key already exists on the - /// current main task, returns an empty ConfigContext (operator bool() == false). - /// This prevents the same handler from running multiple times in a single reload - /// cycle when multiple trigger records map to the same config key. - /// - /// @see ConfigRegistry::setup_triggers() — N trigger records produce N callbacks. - [[nodiscard]] ConfigContext create_config_context(std::string_view config_key, std::string_view description = "", - std::string_view filename = ""); - - [[nodiscard]] bool is_reload_in_progress() const; - - [[nodiscard]] bool - has_token(std::string_view token_name) const - { - std::shared_lock lock(_mutex); - return std::any_of(_history.begin(), _history.end(), - [&token_name](const std::shared_ptr &task) { return task->get_token() == token_name; }); - } - - /// - /// @brief Mark a reload task as stale/superseded - /// - /// Used internally when --force flag (jsonrpc or traffic_ctl) is specified to mark the current - /// in-progress task as stale before starting a new one. - /// - /// Note: This does NOT stop running handlers. Any handlers actively - /// processing will continue. This only updates the task tracking status - /// so we can spawn another reload task. Use with caution. - /// - /// @param token The token of the task to mark stale (empty = current task) - /// @param reason Reason for marking stale (will be logged) - /// @return true if task was found and marked, false otherwise - /// - bool mark_task_as_stale(std::string_view token = "", std::string_view reason = "Superseded by new reload"); - - /// Pre-register a CREATED subtask for the given config key on the current main task. - /// Called by on_record_change() during the synchronous RecExecConfigUpdateCbs flush - /// to reserve a placeholder before the handler continuation is scheduled on ET_TASK. - /// If no reload is active or the subtask already exists, this is a no-op. - void reserve_subtask(std::string_view config_key); - -private: - static constexpr size_t MAX_HISTORY_SIZE = 100; ///< Maximum number of reload tasks to keep in history. TODO: maybe configurable? - - /// Create main task (caller must hold unique lock) - void create_main_config_task(std::string_view token, std::string description); - - std::string generate_token_name(const char *prefix) const; - - std::vector> _history; - std::shared_ptr _current_task; - mutable std::shared_mutex _mutex; - - // Prevent construction/copying from outside - ReloadCoordinator() = default; - ReloadCoordinator(const ReloadCoordinator &) = delete; - ReloadCoordinator &operator=(const ReloadCoordinator &) = delete; -}; diff --git a/include/mgmt/rpc/handlers/config/Configuration.h b/include/mgmt/rpc/handlers/config/Configuration.h index 29cfee5e867..e6f00b5aeaa 100644 --- a/include/mgmt/rpc/handlers/config/Configuration.h +++ b/include/mgmt/rpc/handlers/config/Configuration.h @@ -24,40 +24,7 @@ namespace rpc::handlers::config { -/// Only records.yaml config. swoc::Rv set_config_records(std::string_view const &id, YAML::Node const ¶ms); - -/** - * @brief Unified config reload handler — supports file source and RPC source modes. - * - * File source (default): - * Reloads all changed config files from disk (on-disk configuration). - * Params: - * token: optional custom reload token - * force: force reload even if one is in progress - * - * RPC source (when "configs" param present): - * Reloads specific configs using YAML content injected through the RPC call, - * bypassing on-disk files. - * Params: - * token: optional custom reload token - * configs: map of config_key -> yaml_content - * e.g.: - * configs: - * ip_allow: - * - apply: in - * ip_addrs: 0.0.0.0/0 - * sni: - * - fqdn: '*.example.com' - * - * Response: - * token: reload task token - * created_time: task creation timestamp - * message: status message - * errors: array of errors (if any) - */ swoc::Rv reload_config(std::string_view const &id, YAML::Node const ¶ms); -swoc::Rv get_reload_config_status(std::string_view const &id, YAML::Node const ¶ms); - } // namespace rpc::handlers::config diff --git a/include/mgmt/rpc/handlers/plugins/Plugins.h b/include/mgmt/rpc/handlers/plugins/Plugins.h index b07d3ed0391..250b90d4e6a 100644 --- a/include/mgmt/rpc/handlers/plugins/Plugins.h +++ b/include/mgmt/rpc/handlers/plugins/Plugins.h @@ -25,5 +25,4 @@ namespace rpc::handlers::plugins { swoc::Rv plugin_send_basic_msg(std::string_view const &id, YAML::Node const ¶ms); -swoc::Rv get_plugin_list(std::string_view const &id, YAML::Node const ¶ms); } // namespace rpc::handlers::plugins diff --git a/include/proxy/CacheControl.h b/include/proxy/CacheControl.h index 15ff1e60e6a..fcd789634b3 100644 --- a/include/proxy/CacheControl.h +++ b/include/proxy/CacheControl.h @@ -33,7 +33,6 @@ #include "iocore/eventsystem/EventSystem.h" #include "proxy/ControlBase.h" #include "tscore/Result.h" -#include "mgmt/config/ConfigContext.h" struct RequestData; @@ -128,4 +127,4 @@ bool host_rule_in_CacheControlTable(); bool ip_rule_in_CacheControlTable(); void initCacheControl(); -void reloadCacheControl(ConfigContext ctx); +void reloadCacheControl(); diff --git a/include/proxy/ControlMatcher.h b/include/proxy/ControlMatcher.h index 505f09e343d..96e9c36e966 100644 --- a/include/proxy/ControlMatcher.h +++ b/include/proxy/ControlMatcher.h @@ -99,8 +99,6 @@ #include -#include "mgmt/config/ConfigContext.h" - #ifdef HAVE_CTYPE_H #include #endif @@ -304,12 +302,11 @@ template class ControlMatcher public: // Parameter name must not be deallocated before this object is ControlMatcher(const char *file_var, const char *name, const matcher_tags *tags, - int flags_in = (ALLOW_HOST_TABLE | ALLOW_IP_TABLE | ALLOW_REGEX_TABLE | ALLOW_HOST_REGEX_TABLE | ALLOW_URL_TABLE), - ConfigContext ctx = {}); + int flags_in = (ALLOW_HOST_TABLE | ALLOW_IP_TABLE | ALLOW_REGEX_TABLE | ALLOW_HOST_REGEX_TABLE | ALLOW_URL_TABLE)); ~ControlMatcher(); - int BuildTable(ConfigContext ctx = {}); - int BuildTableFromString(char *str, ConfigContext ctx = {}); + int BuildTable(); + int BuildTableFromString(char *str); void Match(RequestData *rdata, MatchResult *result) const; void Print() const; diff --git a/include/proxy/IPAllow.h b/include/proxy/IPAllow.h index f354c1faaa1..9d707247a0e 100644 --- a/include/proxy/IPAllow.h +++ b/include/proxy/IPAllow.h @@ -40,6 +40,8 @@ #include "swoc/swoc_ip.h" #include "swoc/Errata.h" +// forward declare in name only so it can be a friend. +struct IpAllowUpdate; namespace YAML { class Node; @@ -204,7 +206,7 @@ class IpAllow : public ConfigInfo static ACL match(sockaddr const *sa, match_key_t key); static void startup(); - static void reconfigure(ConfigContext ctx = {}); + static void reconfigure(); /// @return The global instance. static IpAllow *acquire(); /// Release the configuration. diff --git a/include/proxy/ParentSelection.h b/include/proxy/ParentSelection.h index 7102844d38e..8bfd556fcbe 100644 --- a/include/proxy/ParentSelection.h +++ b/include/proxy/ParentSelection.h @@ -445,7 +445,7 @@ class HttpRequestData; struct ParentConfig { public: static void startup(); - static void reconfigure(ConfigContext ctx = {}); + static void reconfigure(); static void print(); static void set_parent_table(P_table *pTable, ParentRecord *rec, int num_elements); diff --git a/include/proxy/Plugin.h b/include/proxy/Plugin.h index d7af6ef5bb3..f86f2cbc040 100644 --- a/include/proxy/Plugin.h +++ b/include/proxy/Plugin.h @@ -24,8 +24,6 @@ #pragma once #include -#include -#include "config/config_result.h" #include "tscore/List.h" enum class PluginDynamicReloadMode { OFF, ON, COUNT }; @@ -35,30 +33,6 @@ void parsePluginConfig(); bool isPluginDynamicReloadEnabled(); -struct PluginYAMLEntry { - std::string path; - bool enabled{true}; - int load_order{-1}; - std::vector params; - std::string config_literal; -}; - -using PluginYAMLEntries = std::vector; - -struct PluginLoadSummary { - struct Entry { - std::string path; - int load_order{-1}; - bool enabled{true}; - bool loaded{false}; - int index{0}; - }; - std::string source; - std::vector entries; -}; - -const PluginLoadSummary &get_plugin_load_summary(); - struct PluginRegInfo { PluginRegInfo(); ~PluginRegInfo(); @@ -80,13 +54,8 @@ extern DLL plugin_reg_list; extern PluginRegInfo *plugin_reg_current; bool plugin_init(bool validateOnly = false); -bool plugin_yaml_init(bool validateOnly = false); bool plugin_dso_load(const char *path, void *&handle, void *&init, std::string &error); -/// Parse plugin.yaml and return sorted entries. -/// Exposed (non-static) for unit testing. -config::ConfigResult parse_plugin_yaml(const char *yaml_path); - /** Abstract interface class for plugin based continuations. The primary intended use of this is for logging so that continuations diff --git a/include/proxy/ReverseProxy.h b/include/proxy/ReverseProxy.h index 201327d8aa2..c24748b7967 100644 --- a/include/proxy/ReverseProxy.h +++ b/include/proxy/ReverseProxy.h @@ -41,7 +41,6 @@ #include "proxy/http/remap/RemapPluginInfo.h" #include "proxy/http/remap/UrlRewrite.h" #include "proxy/http/remap/UrlMapping.h" -#include "mgmt/config/ConfigContext.h" #define EMPTY_PORT_MAPPING (int32_t) ~0 @@ -57,7 +56,7 @@ mapping_type request_url_remap_redirect(HTTPHdr *request_header, URL *redirect_u bool response_url_remap(HTTPHdr *response_header, UrlRewrite *table); // Reload Functions -bool reloadUrlRewrite(ConfigContext ctx); +bool reloadUrlRewrite(); bool urlRewriteVerify(); void init_remap_volume_host_records(); diff --git a/include/proxy/http/HttpUserAgent.h b/include/proxy/http/HttpUserAgent.h index 4761e4a30e4..6043d079002 100644 --- a/include/proxy/http/HttpUserAgent.h +++ b/include/proxy/http/HttpUserAgent.h @@ -32,7 +32,6 @@ #include "records/RecHttp.h" #include "iocore/net/TLSBasicSupport.h" #include "iocore/net/TLSSessionResumptionSupport.h" -#include "tscore/ink_assert.h" #include @@ -46,7 +45,6 @@ struct ClientConnectionInfo { bool tcp_reused{false}; bool ssl_reused{false}; bool connection_is_ssl{false}; - int ssl_resumption_type{0}; // 0=no resumption, 1=session cache, 2=session ticket char const *protocol{"-"}; char const *sec_protocol{"-"}; @@ -81,8 +79,6 @@ class HttpUserAgent bool get_client_ssl_reused() const; - int get_client_ssl_resumption_type() const; - bool get_client_connection_is_ssl() const; char const *get_client_protocol() const; @@ -194,20 +190,6 @@ HttpUserAgent::set_txn(ProxyTransaction *txn, TransactionMilestones &milestones) if (auto tsrs = netvc->get_service()) { m_conn_info.ssl_reused = tsrs->getIsResumedSSLSession(); - - if (m_conn_info.ssl_reused) { - if (tsrs->getIsResumedFromSessionCache()) { - m_conn_info.ssl_resumption_type = 1; - } else if (tsrs->getIsResumedFromSessionTicket()) { - m_conn_info.ssl_resumption_type = 2; - } else { - // This should not happen if ssl_reused is true. - ink_assert(!"ssl_resumption_type should be set for an SSL reused session"); - m_conn_info.ssl_resumption_type = 0; - } - } else { - m_conn_info.ssl_resumption_type = 0; - } } if (auto protocol_str{txn->get_protocol_string()}; protocol_str) { @@ -253,12 +235,6 @@ HttpUserAgent::get_client_ssl_reused() const return m_conn_info.ssl_reused; } -inline int -HttpUserAgent::get_client_ssl_resumption_type() const -{ - return m_conn_info.ssl_resumption_type; -} - inline bool HttpUserAgent::get_client_connection_is_ssl() const { diff --git a/include/proxy/http/PreWarmConfig.h b/include/proxy/http/PreWarmConfig.h index 24675e0e145..2ff2535b952 100644 --- a/include/proxy/http/PreWarmConfig.h +++ b/include/proxy/http/PreWarmConfig.h @@ -43,13 +43,14 @@ class PreWarmConfig static void startup(); - // ConfigRegistry reload handler - static void reconfigure(ConfigContext ctx = {}); + // ConfigUpdateContinuation interface + static void reconfigure(); // ConfigProcessor::scoped_config interface static PreWarmConfigParams *acquire(); static void release(PreWarmConfigParams *params); private: - inline static int _config_id = 0; + inline static int _config_id = 0; + inline static std::unique_ptr> _config_update_handler; }; diff --git a/include/proxy/http/remap/AclFiltering.h b/include/proxy/http/remap/AclFiltering.h index f830a16665a..32fe3c0c7a5 100644 --- a/include/proxy/http/remap/AclFiltering.h +++ b/include/proxy/http/remap/AclFiltering.h @@ -31,7 +31,6 @@ #include #include #include -#include // =============================================================================== // ACL like filtering defs (per one remap rule) @@ -109,9 +108,8 @@ class acl_filter_rule internal : 1; // filter internal HTTP requests // we need arguments as string array for directive processing - int argc = 0; // argument counter (only for filter defs) - char *argv[ACL_FILTER_MAX_ARGV]; // argument strings (only for filter defs) - YAML::Node node; // argument node (only for filter defs) + int argc = 0; // argument counter (only for filter defs) + char *argv[ACL_FILTER_MAX_ARGV]; // argument strings (only for filter defs) // methods bool method_restriction_enabled; @@ -135,7 +133,6 @@ class acl_filter_rule ~acl_filter_rule(); void name(const char *_name = nullptr); int add_argv(int _argc, char *_argv[]); - void add_node(const YAML::Node &_node); void print(); /** Return a description of the action. diff --git a/include/proxy/http/remap/RemapConfig.h b/include/proxy/http/remap/RemapConfig.h index d745a4d93b0..559604e5602 100644 --- a/include/proxy/http/remap/RemapConfig.h +++ b/include/proxy/http/remap/RemapConfig.h @@ -23,7 +23,6 @@ #pragma once -#include "mgmt/config/ConfigContext.h" #include "proxy/http/remap/AclFiltering.h" class UrlRewrite; @@ -66,7 +65,7 @@ struct BUILD_TABLE_INFO { bool ip_allow_check_enabled_p = true; bool accept_check_p = true; - acl_filter_rule *rules_list = nullptr; // all rules defined in config files + acl_filter_rule *rules_list = nullptr; // all rules defined in config files as .define_filter foobar @src_ip=..... UrlRewrite *rewrite = nullptr; // Pointer to the UrlRewrite object we are parsing for. // Clear the argument vector. @@ -81,7 +80,7 @@ struct BUILD_TABLE_INFO { }; const char *remap_parse_directive(BUILD_TABLE_INFO *bti, char *errbuf, size_t errbufsize); -bool remap_parse_config_bti(const char *path, BUILD_TABLE_INFO *bti, ConfigContext ctx = {}); +bool remap_parse_config_bti(const char *path, BUILD_TABLE_INFO *bti); const char *remap_validate_filter_args(acl_filter_rule **rule_pp, const char *const *argv, int argc, char *errStrBuf, size_t errStrBufSize, ACLBehaviorPolicy behavior_policy); @@ -89,13 +88,8 @@ const char *remap_validate_filter_args(acl_filter_rule **rule_pp, const char *co unsigned long remap_check_option(const char *const *argv, int argc, unsigned long findmode = 0, int *_ret_idx = nullptr, const char **argptr = nullptr); -bool remap_parse_config(const char *path, UrlRewrite *rewrite, ConfigContext ctx = {}); +bool remap_parse_config(const char *path, UrlRewrite *rewrite); using load_remap_file_func = void (*)(const char *, const char *); extern load_remap_file_func load_remap_file_cb; - -// Helper functions shared between RemapConfig.cc and RemapYamlConfig.cc -bool is_inkeylist(const char *key, ...); -void free_directory_list(int n_entries, struct dirent **entrylist); -const char *is_valid_scheme(std::string_view fromScheme, std::string_view toScheme); diff --git a/include/proxy/http/remap/RemapYamlConfig.h b/include/proxy/http/remap/RemapYamlConfig.h deleted file mode 100644 index a93ee9d8b10..00000000000 --- a/include/proxy/http/remap/RemapYamlConfig.h +++ /dev/null @@ -1,74 +0,0 @@ -/** @file - * - * YAML remap configuration file parsing. - * - * @section license License - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -#include - -#include "swoc/Errata.h" -#include "mgmt/config/ConfigContext.h" -#include "proxy/http/remap/RemapConfig.h" -#include "proxy/hdrs/URL.h" - -class acl_filter_rule; -class UrlRewrite; -class url_mapping; - -// Parse URL from YAML node into URL object -swoc::Errata parse_yaml_url(const YAML::Node &node, URL &url, bool host_check, std::string_view &url_str); - -// Parse and validate ACL filters from YAML node -swoc::Errata remap_validate_yaml_filter_args(acl_filter_rule **rule_pp, const YAML::Node &node, ACLBehaviorPolicy behavior_policy); - -// Parse map_with_referer YAML node -swoc::Errata parse_map_referer(const YAML::Node &node, url_mapping *url_mapping); - -// Parse plugin YAML node -swoc::Errata parse_yaml_plugins(const YAML::Node &node, url_mapping *url_mapping, BUILD_TABLE_INFO *bti); - -// Parse filter directive YAML nodes (activate_filter|deactivate_filter|delete_filter|define_filter) -swoc::Errata parse_yaml_filter_directive(const YAML::Node &node, BUILD_TABLE_INFO *bti); - -// Parse define filter directice YAML node (for inline and global filters) -swoc::Errata parse_yaml_define_directive(const YAML::Node &node, BUILD_TABLE_INFO *bti); - -// Parse remap filter YAML node -swoc::Errata process_yaml_filter_opt(url_mapping *mp, const YAML::Node &node, const BUILD_TABLE_INFO *bti); - -// Parse yaml subpath -swoc::Errata parse_yaml_remap_fragment(const char *path, BUILD_TABLE_INFO *bti); - -// Parse include directive for remap subpaths -swoc::Errata parse_yaml_include_directive(const std::string &include_path, BUILD_TABLE_INFO *bti); - -// Parse for single remap rule YAML node -swoc::Errata parse_yaml_remap_rule(const YAML::Node &node, BUILD_TABLE_INFO *bti); - -// Parse remap YAML node -bool remap_parse_yaml_bti(const char *path, BUILD_TABLE_INFO *bti, ConfigContext ctx = {}); - -bool remap_parse_yaml(const char *path, UrlRewrite *rewrite, ConfigContext ctx = {}); diff --git a/include/proxy/http/remap/UrlRewrite.h b/include/proxy/http/remap/UrlRewrite.h index 2ef85b9ed70..dcc767ebe44 100644 --- a/include/proxy/http/remap/UrlRewrite.h +++ b/include/proxy/http/remap/UrlRewrite.h @@ -25,7 +25,6 @@ #pragma once #include "iocore/eventsystem/Freer.h" -#include "mgmt/config/ConfigContext.h" #include "proxy/http/remap/UrlMapping.h" #include "proxy/http/remap/UrlMappingPathIndex.h" #include "proxy/http/HttpTransact.h" @@ -78,15 +77,14 @@ class UrlRewrite : public RefCountObjInHeap * * @return @c true if the instance state is valid, @c false if not. */ - bool load(ConfigContext ctx = {}); + bool load(); /** Build the internal url write tables. * * @param path Path to configuration file. - * @param ctx ConfigContext for reload status tracking. * @return 0 on success, non-zero error code on failure. */ - int BuildTable(const char *path, ConfigContext ctx = {}); + int BuildTable(const char *path); mapping_type Remap_redirect(HTTPHdr *request_header, URL *redirect_url); bool ReverseMap(HTTPHdr *response_header); @@ -119,18 +117,6 @@ class UrlRewrite : public RefCountObjInHeap return _valid; }; - bool - is_remap_yaml() const - { - return _remap_yaml; - }; - - void - set_remap_yaml(bool yaml) - { - _remap_yaml = yaml; - }; - /// @return Number of rules defined. int rule_count() const @@ -248,7 +234,6 @@ class UrlRewrite : public RefCountObjInHeap private: bool _valid = false; - bool _remap_yaml = false; ACLBehaviorPolicy _acl_behavior_policy = ACLBehaviorPolicy::ACL_BEHAVIOR_LEGACY; bool _mappingLookup(MappingsStore &mappings, URL *request_url, int request_port, const char *request_host, int request_host_len, @@ -266,7 +251,3 @@ class UrlRewrite : public RefCountObjInHeap }; void url_rewrite_remap_request(const UrlMappingContainer &mapping_container, URL *request_url, int scheme = -1); - -mapping_type get_mapping_type(const char *type_str, BUILD_TABLE_INFO *bti); - -bool process_regex_mapping_config(const char *from_host_lower, url_mapping *new_mapping, UrlRewrite::RegexMapping *reg_map); diff --git a/include/proxy/logging/LogConfig.h b/include/proxy/logging/LogConfig.h index d891cf0b237..47a83d76939 100644 --- a/include/proxy/logging/LogConfig.h +++ b/include/proxy/logging/LogConfig.h @@ -34,7 +34,6 @@ #include "proxy/logging/RolledLogDeleter.h" #include "swoc/MemSpan.h" #include "tsutil/Metrics.h" -#include "mgmt/config/ReloadCoordinator.h" using ts::Metrics; @@ -104,14 +103,11 @@ class LogConfig : public ConfigInfo void display(FILE *fd = stdout); void setup_log_objects(); - // static int reconfigure(const char *name, RecDataT data_type, RecData data, void *cookie); - static void reconfigure(ConfigContext ctx = {}); // ConfigRegistry reload handler + static int reconfigure(const char *name, RecDataT data_type, RecData data, void *cookie); static void register_config_callbacks(); static void register_stat_callbacks(); - ConfigContext reload_ctx; ///< Tracks reload status; - bool space_to_write(int64_t bytes_to_write) const; bool diff --git a/include/records/RecCore.h b/include/records/RecCore.h index e997f779b5e..a355bd39ccb 100644 --- a/include/records/RecCore.h +++ b/include/records/RecCore.h @@ -31,7 +31,6 @@ #include "tscore/Diags.h" #include "records/RecDefs.h" -#include "mgmt/config/ConfigContext.h" #include "swoc/MemSpan.h" struct RecRecord; @@ -272,17 +271,9 @@ RecErrT RecGetRecordPersistenceType(const char *name, RecPersistT *persist_type, RecErrT RecGetRecordSource(const char *name, RecSourceT *source, bool lock = true); /// Generate a warning if any configuration name/value is not registered. -// void RecConfigWarnIfUnregistered(); -/// Generate a warning if any configuration name/value is not registered. -void RecConfigWarnIfUnregistered(ConfigContext ctx = {}); +void RecConfigWarnIfUnregistered(); //------------------------------------------------------------------------ // Set RecRecord attributes //------------------------------------------------------------------------ RecErrT RecSetSyncRequired(const char *name, bool lock = true); - -/// Flush pending record config-update callbacks (those marked sync-required). -/// This forces immediate execution of RecConfigUpdateCb callbacks for dirty records, -/// rather than waiting for the next config_update_cont timer tick (~3s). -/// After this call the sync-required flag on those records is cleared. -void RecFlushConfigUpdateCbs(); diff --git a/include/records/YAMLConfigReloadTaskEncoder.h b/include/records/YAMLConfigReloadTaskEncoder.h deleted file mode 100644 index a86b863ba9d..00000000000 --- a/include/records/YAMLConfigReloadTaskEncoder.h +++ /dev/null @@ -1,72 +0,0 @@ -/** @file - - YAML encoder for ConfigReloadTask::Info — serializes reload task snapshots to YAML nodes - for JSONRPC responses. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#pragma once -#include -#include -#include -#include -#include -#include -#include - -#include "mgmt/config/ReloadCoordinator.h" - -namespace YAML -{ -template <> struct convert { - static Node - encode(const ConfigReloadTask::Info &info) - { - Node node; - node["config_token"] = info.token; - node["status"] = std::string(ConfigReloadTask::state_to_string(info.state)); - node["description"] = info.description; - node["config_key"] = info.config_key; - node["filename"] = info.filename; - auto meta = YAML::Node(YAML::NodeType::Map); - meta["created_time_ms"] = info.created_time_ms; - meta["last_updated_time_ms"] = info.last_updated_time_ms; - meta["main_task"] = info.main_task ? "true" : "false"; - - node["meta"] = meta; - - node["logs"] = YAML::Node(YAML::NodeType::Sequence); - // if no logs, it will be empty sequence. - for (const auto &entry : info.logs) { - YAML::Node log_node; - log_node["level"] = static_cast(entry.level); - log_node["text"] = entry.text; - node["logs"].push_back(log_node); - } - - node["sub_tasks"] = YAML::Node(YAML::NodeType::Sequence); - for (const auto &sub_task : info.sub_tasks) { - node["sub_tasks"].push_back(sub_task->get_info()); - } - return node; - } -}; - -} // namespace YAML diff --git a/include/shared/rpc/yaml_codecs.h b/include/shared/rpc/yaml_codecs.h index d719973f312..1e61742a515 100644 --- a/include/shared/rpc/yaml_codecs.h +++ b/include/shared/rpc/yaml_codecs.h @@ -33,7 +33,7 @@ namespace helper // traffic_ctl display something. template inline auto -try_extract(YAML::Node const &node, const char *name, bool throwOnFail = false, T def = T{}) -> T +try_extract(YAML::Node const &node, const char *name, bool throwOnFail = false) { try { if (auto n = node[name]) { @@ -44,9 +44,8 @@ try_extract(YAML::Node const &node, const char *name, bool throwOnFail = false, throw ex; } } - return def; + return T{}; } - } // namespace helper /** * YAML namespace. All json rpc request codecs can be placed here. It will read all the definitions from "requests.h" diff --git a/include/tscore/Filenames.h b/include/tscore/Filenames.h index b36e282aebe..90ae0485df9 100644 --- a/include/tscore/Filenames.h +++ b/include/tscore/Filenames.h @@ -25,10 +25,10 @@ namespace ts { namespace filename { - constexpr const char *STORAGE = "storage.yaml"; - constexpr const char *RECORDS = "records.yaml"; - constexpr const char *PLUGIN = "plugin.config"; - constexpr const char *PLUGIN_YAML = "plugin.yaml"; + constexpr const char *STORAGE = "storage.config"; + constexpr const char *RECORDS = "records.yaml"; + constexpr const char *VOLUME = "volume.config"; + constexpr const char *PLUGIN = "plugin.config"; // These still need to have their corresponding records.yaml settings removed. constexpr const char *LOGGING = "logging.yaml"; @@ -39,8 +39,7 @@ namespace filename constexpr const char *SOCKS = "socks.config"; constexpr const char *PARENT = "parent.config"; constexpr const char *REMAP = "remap.config"; - constexpr const char *REMAP_YAML = "remap.yaml"; - constexpr const char *SSL_MULTICERT = "ssl_multicert.yaml"; + constexpr const char *SSL_MULTICERT = "ssl_multicert.config"; constexpr const char *SPLITDNS = "splitdns.config"; constexpr const char *SNI = "sni.yaml"; constexpr const char *JSONRPC = "jsonrpc.yaml"; diff --git a/include/tscore/ink_config.h.cmake.in b/include/tscore/ink_config.h.cmake.in index 73c8b860fb9..bf012cefecd 100644 --- a/include/tscore/ink_config.h.cmake.in +++ b/include/tscore/ink_config.h.cmake.in @@ -186,8 +186,6 @@ const int DEFAULT_STACKSIZE = @DEFAULT_STACK_SIZE@; #cmakedefine01 HAVE_SSL_ERROR_DESCRIPTION #cmakedefine01 HAVE_OSSL_PARAM_CONSTRUCT_END #cmakedefine01 TS_USE_TLS_SET_CIPHERSUITES -#cmakedefine01 HAVE_SSL_CTX_ADD_CERT_COMPRESSION_ALG -#cmakedefine01 HAVE_SSL_CTX_SET1_CERT_COMP_PREFERENCE #define TS_BUILD_CANONICAL_HOST "@CMAKE_HOST@" diff --git a/plugins/experimental/maxmind_acl/mmdb.cc b/plugins/experimental/maxmind_acl/mmdb.cc index 8600172f050..c62dcc1a5b0 100644 --- a/plugins/experimental/maxmind_acl/mmdb.cc +++ b/plugins/experimental/maxmind_acl/mmdb.cc @@ -78,17 +78,13 @@ Acl::init(char const *filename) return status; } - // Associate our config file with remap.config or .yaml if possible to be able to initiate reloads + // Associate our config file with remap.config if possible to be able to initiate reloads TSMgmtString result; - const char *var_name = "proxy.config.url_remap_yaml.filename"; - if (TS_SUCCESS != TSMgmtStringGet(var_name, &result) || TS_SUCCESS != TSMgmtConfigFileAdd(result, configloc.c_str())) { - // Fall back to remap.config - var_name = "proxy.config.url_remap.filename"; - if (TS_SUCCESS != TSMgmtStringGet(var_name, &result)) { - TSWarning("[%s] Could not retrieve remap filename", PLUGIN_NAME); - } else if (TS_SUCCESS != TSMgmtConfigFileAdd(result, configloc.c_str())) { - TSWarning("[%s] Error adding mgmt config file", PLUGIN_NAME); - } + const char *var_name = "proxy.config.url_remap.filename"; + if (TS_SUCCESS != TSMgmtStringGet(var_name, &result)) { + TSWarning("[%s] Could not retrieve remap filename", PLUGIN_NAME); + } else if (TS_SUCCESS != TSMgmtConfigFileAdd(result, configloc.c_str())) { + TSWarning("[%s] Error adding mgmt config file", PLUGIN_NAME); } // Find our database name and convert to full path as needed diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc index c5f80b6ef16..f61c87e4730 100644 --- a/src/api/InkAPI.cc +++ b/src/api/InkAPI.cc @@ -139,6 +139,8 @@ extern ClassAllocator FetchSMAllocator; /* From proxy/http/HttpProxyServerMain.c: */ extern bool ssl_register_protocol(const char *, Continuation *); +extern SSLSessionCache *session_cache; // declared extern in P_SSLConfig.h + // External converters. extern MgmtConverter const &HttpDownServerCacheTimeConv; @@ -8496,6 +8498,61 @@ TSVConnPPInfoIntGet(TSVConn vconn, uint16_t key, TSMgmtInt *value) return TS_SUCCESS; } +TSSslSession +TSSslSessionGet(const TSSslSessionID *session_id) +{ + SSL_SESSION *session = nullptr; + if (session_id && session_cache) { + session_cache->getSession(reinterpret_cast(*session_id), &session, nullptr); + } + return reinterpret_cast(session); +} + +int +TSSslSessionGetBuffer(const TSSslSessionID *session_id, char *buffer, int *len_ptr) +{ + int true_len = 0; + // Don't get if there is no session id or the cache is not yet set up + if (session_id && session_cache && len_ptr) { + true_len = session_cache->getSessionBuffer(reinterpret_cast(*session_id), buffer, *len_ptr); + } + return true_len; +} + +TSReturnCode +TSSslSessionInsert(const TSSslSessionID *session_id, TSSslSession add_session, TSSslConnection ssl_conn) +{ + // Don't insert if there is no session id or the cache is not yet set up + if (session_id && session_cache) { + if (dbg_ctl_ssl_session_cache_insert.on()) { + const SSLSessionID *sid = reinterpret_cast(session_id); + char buf[sid->len * 2 + 1]; + sid->toString(buf, sizeof(buf)); + DbgPrint(dbg_ctl_ssl_session_cache_insert, "TSSslSessionInsert: Inserting session '%s' ", buf); + } + SSL_SESSION *session = reinterpret_cast(add_session); + SSL *ssl = reinterpret_cast(ssl_conn); + session_cache->insertSession(reinterpret_cast(*session_id), session, ssl); + // insertSession returns void, assume all went well + return TS_SUCCESS; + } else { + return TS_ERROR; + } +} + +TSReturnCode +TSSslSessionRemove(const TSSslSessionID *session_id) +{ + // Don't remove if there is no session id or the cache is not yet set up + if (session_id && session_cache) { + session_cache->removeSession(reinterpret_cast(*session_id)); + // removeSession returns void, assume all went well + return TS_SUCCESS; + } else { + return TS_ERROR; + } +} + // APIs for managing and using UUIDs. TSUuid TSUuidCreate() diff --git a/src/config/CMakeLists.txt b/src/config/CMakeLists.txt deleted file mode 100644 index 78730f13cb7..00000000000 --- a/src/config/CMakeLists.txt +++ /dev/null @@ -1,58 +0,0 @@ -####################### -# -# Licensed to the Apache Software Foundation (ASF) under one or more contributor license -# agreements. See the NOTICE file distributed with this work for additional information regarding -# copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed under the License -# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing permissions and limitations under -# the License. -# -####################### - -set(CONFIG_PUBLIC_HEADERS - ${PROJECT_SOURCE_DIR}/include/config/config_result.h ${PROJECT_SOURCE_DIR}/include/config/ssl_multicert.h - ${PROJECT_SOURCE_DIR}/include/config/storage.h ${PROJECT_SOURCE_DIR}/include/config/plugin_config.h -) - -add_library(tsconfig ssl_multicert.cc storage.cc plugin_config.cc) - -add_library(ts::config ALIAS tsconfig) - -set_target_properties(tsconfig PROPERTIES POSITION_INDEPENDENT_CODE TRUE PUBLIC_HEADER "${CONFIG_PUBLIC_HEADERS}") - -target_include_directories(tsconfig PUBLIC ${PROJECT_SOURCE_DIR}/include) - -target_link_libraries( - tsconfig - PUBLIC libswoc::libswoc yaml-cpp::yaml-cpp - PRIVATE ts::tscore -) - -if(BUILD_SHARED_LIBS) - install( - TARGETS tsconfig - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/config - ) -else() - install(FILES ${CONFIG_PUBLIC_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/config) -endif() - -clang_tidy_check(tsconfig) - -if(BUILD_TESTING) - add_executable( - test_tsconfig unit_tests/test_ssl_multicert.cc unit_tests/test_storage.cc unit_tests/test_plugin_config.cc - ) - - target_link_libraries(test_tsconfig PRIVATE tsconfig ts::tscore Catch2::Catch2WithMain) - - add_catch2_test(NAME test_tsconfig COMMAND $) -endif() diff --git a/src/config/README.md b/src/config/README.md deleted file mode 100644 index 9360062df20..00000000000 --- a/src/config/README.md +++ /dev/null @@ -1,122 +0,0 @@ -# Configuration Parsing Library - -This directory contains a shared configuration parsing and marshalling framework -used by various ATS components including `traffic_server` and `traffic_ctl`. - -## Architecture Overview - -Each configuration file type (ssl_multicert, etc.) follows the same pattern: - -``` -include/config/.h - Header with data types, parser, marshaller -src/config/.cc - Implementation -``` - -### Components - -For each configuration type, there are three main components: - -1. **Entry struct** - A POD (plain old data) type representing a single - configuration entry (e.g., `SSLMultiCertEntry`). - -2. **Parser class** - Reads configuration files and returns parsed entries. - Supports both YAML and legacy formats with automatic format detection. - -3. **Marshaller class** - Serializes configuration to YAML or JSON format. - -### Example: ssl_multicert - -``` -include/config/ssl_multicert.h -├── SSLMultiCertEntry - Single certificate entry -├── SSLMultiCertConfig - Vector of entries (type alias) -├── ConfigResult - Parse result with error handling -├── SSLMultiCertParser - Parser for .yaml and .config formats -└── SSLMultiCertMarshaller - Serializer to YAML/JSON -``` - -## Usage - -### Parsing a Configuration File - -```cpp -#include "config/ssl_multicert.h" - -config::SSLMultiCertParser parser; -auto result = parser.parse("/path/to/ssl_multicert.yaml"); - -if (!result.ok()) { - // Handle error - result.errata contains error details - return; -} - -for (const auto& entry : result.value) { - // Use entry.ssl_cert_name, entry.dest_ip, etc. -} -``` - -### Marshalling to YAML/JSON - -```cpp -config::SSLMultiCertMarshaller marshaller; -std::string yaml = marshaller.to_yaml(result.value); -std::string json = marshaller.to_json(result.value); -``` - -## Format Detection - -The parser automatically detects the configuration format: - -1. **By file extension**: `.yaml`/`.yml` → YAML, `.config` → Legacy -2. **By content inspection**: Looks for `ssl_multicert:` (YAML) vs `key=value` (Legacy) - -## Adding a New Configuration Type - -To add support for a new configuration (maybe, `logging.yaml`): - -1. Create `include/config/logging.h`: - - Define `LoggingEntry` struct with fields matching the config - - Define `LoggingConfig = std::vector` - - Declare `LoggingParser` and `LoggingMarshaller` classes - -2. Create `src/config/logging.cc`: - - Implement YAML parsing using yaml-cpp - - Implement legacy format parsing if applicable - - Implement YAML and JSON marshalling - -3. Update `src/config/CMakeLists.txt`: - - Add `logging.cc` to the library sources - - Add header to `CONFIG_PUBLIC_HEADERS` - -4. Integrate with consumers (traffic_ctl, traffic_server, etc.) - -## Error Handling - -All parsers return `ConfigResult` which contains: - -- `value` - The parsed configuration (valid even on partial failure) -- `errata` - Error/warning information using `swoc::Errata` -- `ok()` - Returns true if no errors occurred - -```cpp -if (!result.ok()) { - if (!result.errata.empty()) { - std::cerr << result.errata.front().text() << std::endl; - } -} -``` - -## Dependencies - -- **yaml-cpp** - YAML parsing -- **libswoc** - Error handling (`swoc::Errata`) - -## Running Unit Tests - -Build with testing enabled and run: - -```bash -cmake -B build -DBUILD_TESTING=ON ... -cmake --build build --target test_tsconfig -ctest --test-dir build -R test_tsconfig -``` diff --git a/src/config/plugin_config.cc b/src/config/plugin_config.cc deleted file mode 100644 index 417e0c212d6..00000000000 --- a/src/config/plugin_config.cc +++ /dev/null @@ -1,190 +0,0 @@ -/** @file - - Plugin configuration parsing and marshalling. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include "config/plugin_config.h" - -#include -#include - -#include - -namespace -{ - -constexpr char KEY_PLUGINS[] = "plugins"; -constexpr swoc::Errata::Severity INFO_SEVERITY{0}; - -std::vector -tokenize_plugin_line(std::string_view line) -{ - std::vector tokens; - std::size_t i = 0; - - while (i < line.size()) { - while (i < line.size() && (line[i] == ' ' || line[i] == '\t')) { - ++i; - } - if (i >= line.size() || line[i] == '#') { - break; - } - - if (line[i] == '"') { - ++i; - std::size_t start = i; - while (i < line.size() && line[i] != '"') { - ++i; - } - tokens.emplace_back(line.substr(start, i - start)); - if (i < line.size()) { - ++i; - } - } else { - std::size_t start = i; - while (i < line.size() && line[i] != ' ' && line[i] != '\t' && line[i] != '#') { - ++i; - } - tokens.emplace_back(line.substr(start, i - start)); - } - } - return tokens; -} - -void -emit_entry(YAML::Emitter &yaml, config::PluginConfigEntry const &entry) -{ - yaml << YAML::BeginMap; - yaml << YAML::Key << "path" << YAML::Value << entry.path; - - if (!entry.enabled) { - yaml << YAML::Key << "enabled" << YAML::Value << false; - } - - if (!entry.args.empty()) { - yaml << YAML::Key << "params" << YAML::Value << YAML::BeginSeq; - for (auto const &arg : entry.args) { - yaml << arg; - } - yaml << YAML::EndSeq; - } - - yaml << YAML::EndMap; -} - -} // namespace - -namespace config -{ - -ConfigResult -PluginConfigParser::parse(std::string const &filename) -{ - std::ifstream file(filename); - - if (!file.is_open()) { - ConfigResult result; - result.file_not_found = true; - result.errata.note("unable to open '{}'", filename); - return result; - } - - std::ostringstream ss; - ss << file.rdbuf(); - return parse_legacy(ss.str()); -} - -ConfigResult -PluginConfigParser::parse_legacy(std::string_view content) -{ - ConfigResult result; - std::istringstream stream{std::string{content}}; - std::string line; - int line_no = 0; - - while (std::getline(stream, line)) { - ++line_no; - std::string_view sv{line}; - - std::size_t start = 0; - while (start < sv.size() && (sv[start] == ' ' || sv[start] == '\t')) { - ++start; - } - sv = sv.substr(start); - - if (sv.empty()) { - continue; - } - - bool commented = false; - if (sv[0] == '#') { - commented = true; - sv = sv.substr(1); - start = 0; - while (start < sv.size() && (sv[start] == ' ' || sv[start] == '\t')) { - ++start; - } - sv = sv.substr(start); - } - - if (sv.empty()) { - continue; - } - - auto tokens = tokenize_plugin_line(sv); - if (tokens.empty()) { - continue; - } - - if (tokens[0].find(".so") == std::string::npos) { - result.errata.note(INFO_SEVERITY, "skipping line {}: '{}' does not look like a plugin path (no .so)", line_no, tokens[0]); - continue; - } - - PluginConfigEntry entry; - entry.path = std::move(tokens[0]); - entry.enabled = !commented; - for (std::size_t i = 1; i < tokens.size(); ++i) { - entry.args.emplace_back(std::move(tokens[i])); - } - result.value.emplace_back(std::move(entry)); - } - - return result; -} - -std::string -PluginConfigMarshaller::to_yaml(PluginConfigData const &config) -{ - YAML::Emitter yaml; - - yaml << YAML::BeginMap; - yaml << YAML::Key << KEY_PLUGINS << YAML::Value << YAML::BeginSeq; - - for (auto const &entry : config) { - emit_entry(yaml, entry); - } - - yaml << YAML::EndSeq << YAML::EndMap; - return yaml.c_str(); -} - -} // namespace config diff --git a/src/config/ssl_multicert.cc b/src/config/ssl_multicert.cc deleted file mode 100644 index 5dfdf486e15..00000000000 --- a/src/config/ssl_multicert.cc +++ /dev/null @@ -1,374 +0,0 @@ -/** @file - - SSL Multi-Certificate configuration parsing and marshalling implementation. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include "config/ssl_multicert.h" - -#include -#include -#include -#include - -#include - -#include "swoc/swoc_file.h" -#include "swoc/TextView.h" -#include "tsutil/ts_diag_levels.h" - -namespace -{ - -constexpr swoc::Errata::Severity ERRATA_NOTE_SEV{static_cast(DL_Note)}; -constexpr swoc::Errata::Severity ERRATA_WARN_SEV{static_cast(DL_Warning)}; -constexpr swoc::Errata::Severity ERRATA_ERROR_SEV{static_cast(DL_Error)}; - -// YAML key names. -constexpr char KEY_SSL_CERT_NAME[] = "ssl_cert_name"; -constexpr char KEY_DEST_IP[] = "dest_ip"; -constexpr char KEY_SSL_KEY_NAME[] = "ssl_key_name"; -constexpr char KEY_SSL_CA_NAME[] = "ssl_ca_name"; -constexpr char KEY_SSL_OCSP_NAME[] = "ssl_ocsp_name"; -constexpr char KEY_SSL_KEY_DIALOG[] = "ssl_key_dialog"; -constexpr char KEY_DEST_FQDN[] = "dest_fqdn"; -constexpr char KEY_SSL_TICKET_ENABLED[] = "ssl_ticket_enabled"; -constexpr char KEY_SSL_TICKET_NUMBER[] = "ssl_ticket_number"; -constexpr char KEY_ACTION[] = "action"; -constexpr char KEY_SSL_MULTICERT[] = "ssl_multicert"; - -std::set const valid_keys = { - KEY_SSL_CERT_NAME, KEY_DEST_IP, KEY_SSL_KEY_NAME, KEY_SSL_CA_NAME, KEY_SSL_OCSP_NAME, - KEY_SSL_KEY_DIALOG, KEY_DEST_FQDN, KEY_SSL_TICKET_ENABLED, KEY_SSL_TICKET_NUMBER, KEY_ACTION, -}; - -/** - * Parse a line in legacy key=value format, handling quoted values. - * - * Tokenizes on whitespace but respects quoted strings. Each token should be - * key=value format. - */ -std::vector> -parse_legacy_line(swoc::TextView line) -{ - std::vector> result; - - while (!line.ltrim_if(isspace).empty()) { - swoc::TextView key = line.split_prefix_at('='); - if (key.empty()) { - // No '=' found, skip this malformed token by consuming to next whitespace. - line.take_prefix_if(isspace); - continue; - } - key.trim_if(isspace); - - swoc::TextView value; - if (!line.empty() && (line.front() == '"' || line.front() == '\'')) { - // Quoted value: extract until closing quote. - char const quote = line.front(); - line.remove_prefix(1); - value = line.take_prefix_at(quote); - } else { - // Unquoted value: extract until whitespace. - value = line.take_prefix_if(isspace); - } - value.trim_if(isspace); - - if (!key.empty()) { - result.emplace_back(std::string{key}, std::string{value}); - } - } - - return result; -} - -/// Emit a single SSLMultiCertEntry to a YAML::Emitter. -void -emit_entry(YAML::Emitter &emitter, config::SSLMultiCertEntry const &entry) -{ - emitter << YAML::BeginMap; - - auto write_field = [&](char const *key, std::string const &value) { - if (!value.empty()) { - emitter << YAML::Key << key << YAML::Value << value; - } - }; - - auto write_int_field = [&](char const *key, std::optional const &value) { - if (value.has_value()) { - emitter << YAML::Key << key << YAML::Value << value.value(); - } - }; - - write_field(KEY_SSL_CERT_NAME, entry.ssl_cert_name); - write_field(KEY_DEST_IP, entry.dest_ip); - write_field(KEY_SSL_KEY_NAME, entry.ssl_key_name); - write_field(KEY_SSL_CA_NAME, entry.ssl_ca_name); - write_field(KEY_SSL_OCSP_NAME, entry.ssl_ocsp_name); - write_field(KEY_SSL_KEY_DIALOG, entry.ssl_key_dialog); - write_field(KEY_DEST_FQDN, entry.dest_fqdn); - write_field(KEY_ACTION, entry.action); - write_int_field(KEY_SSL_TICKET_ENABLED, entry.ssl_ticket_enabled); - write_int_field(KEY_SSL_TICKET_NUMBER, entry.ssl_ticket_number); - - emitter << YAML::EndMap; -} - -} // namespace - -namespace YAML -{ -template <> struct convert { - static bool - decode(Node const &node, config::SSLMultiCertEntry &entry) - { - if (node[KEY_SSL_CERT_NAME]) { - entry.ssl_cert_name = node[KEY_SSL_CERT_NAME].as(); - } - - if (node[KEY_DEST_IP]) { - entry.dest_ip = node[KEY_DEST_IP].as(); - } - - if (node[KEY_SSL_KEY_NAME]) { - entry.ssl_key_name = node[KEY_SSL_KEY_NAME].as(); - } - - if (node[KEY_SSL_CA_NAME]) { - entry.ssl_ca_name = node[KEY_SSL_CA_NAME].as(); - } - - if (node[KEY_SSL_OCSP_NAME]) { - entry.ssl_ocsp_name = node[KEY_SSL_OCSP_NAME].as(); - } - - if (node[KEY_SSL_KEY_DIALOG]) { - entry.ssl_key_dialog = node[KEY_SSL_KEY_DIALOG].as(); - } - - if (node[KEY_DEST_FQDN]) { - entry.dest_fqdn = node[KEY_DEST_FQDN].as(); - } - - if (node[KEY_SSL_TICKET_ENABLED]) { - entry.ssl_ticket_enabled = node[KEY_SSL_TICKET_ENABLED].as(); - } - - if (node[KEY_SSL_TICKET_NUMBER]) { - entry.ssl_ticket_number = node[KEY_SSL_TICKET_NUMBER].as(); - } - - if (node[KEY_ACTION]) { - entry.action = node[KEY_ACTION].as(); - } - - return true; - } -}; -} // namespace YAML - -namespace config -{ - -ConfigResult -SSLMultiCertParser::parse(std::string const &filename) -{ - std::error_code ec; - std::string content = swoc::file::load(filename, ec); - if (ec) { - // Missing ssl_multicert.* is an acceptable runtime state. - if (ec.value() == ENOENT) { - return {{}, swoc::Errata(ERRATA_WARN_SEV, "Cannot open SSL certificate configuration \"{}\" - {}", filename, ec)}; - } - return {{}, swoc::Errata(ERRATA_ERROR_SEV, "Failed to read SSL certificate configuration from \"{}\" - {}", filename, ec)}; - } - - if (content.empty()) { - return {{}, {}}; - } - - Format const format = detect_format(content, filename); - if (format == Format::YAML) { - return parse_yaml(content); - } - return parse_legacy(content); -} - -SSLMultiCertParser::Format -SSLMultiCertParser::detect_format(std::string_view content, std::string const &filename) -{ - swoc::TextView const fn{filename}; - - // Check file extension first. - if (fn.ends_with(".yaml") || fn.ends_with(".yml")) { - return Format::YAML; - } - if (fn.ends_with(".config")) { - return Format::Legacy; - } - - // Fall back to content inspection. - if (content.find("ssl_multicert:") != std::string_view::npos) { - return Format::YAML; - } - - // Legacy format uses key=value. - if (content.find('=') != std::string_view::npos) { - return Format::Legacy; - } - - // Default to YAML as that's the preferred format. - return Format::YAML; -} - -ConfigResult -SSLMultiCertParser::parse_yaml(std::string_view content) -{ - SSLMultiCertConfig result; - swoc::Errata errata; - std::set unknown_keys; - - try { - YAML::Node config = YAML::Load(std::string(content)); - if (config.IsNull()) { - return {result, std::move(errata)}; - } - - if (!config[KEY_SSL_MULTICERT]) { - return {result, swoc::Errata("expected a toplevel 'ssl_multicert' node")}; - } - - YAML::Node entries = config[KEY_SSL_MULTICERT]; - if (!entries.IsSequence()) { - return {result, swoc::Errata("expected 'ssl_multicert' to be a sequence")}; - } - - for (auto const &entry_node : entries) { - auto const mark = entry_node.Mark(); - if (!entry_node.IsMap()) { - return {result, swoc::Errata(ERRATA_ERROR_SEV, "Expected ssl_multicert entries to be maps at line {}, column {}", mark.line, - mark.column)}; - } - - for (auto const &field : entry_node) { - std::string key = field.first.as(); - if (valid_keys.find(key) == valid_keys.end() && unknown_keys.insert(key).second) { - errata.note(ERRATA_NOTE_SEV, "Ignoring unknown ssl_multicert key '{}' at line {}, column {}", key, mark.line, - mark.column); - } - } - - result.push_back(entry_node.as()); - } - } catch (std::exception const &ex) { - return {result, swoc::Errata("YAML parse error: {}", ex.what())}; - } - - return {result, std::move(errata)}; -} - -ConfigResult -SSLMultiCertParser::parse_legacy(std::string_view content) -{ - SSLMultiCertConfig result; - swoc::Errata errata; - std::set unknown_keys; - swoc::TextView src{content}; - - while (!src.empty()) { - swoc::TextView line = src.take_prefix_at('\n'); - line.trim_if(isspace); - - // Skip empty lines and comments. - if (line.empty() || line.front() == '#') { - continue; - } - - auto const kv_pairs = parse_legacy_line(line); - if (kv_pairs.empty()) { - continue; - } - - SSLMultiCertEntry entry; - - for (auto const &[key, value] : kv_pairs) { - if (key == KEY_SSL_CERT_NAME) { - entry.ssl_cert_name = value; - } else if (key == KEY_DEST_IP) { - entry.dest_ip = value; - } else if (key == KEY_SSL_KEY_NAME) { - entry.ssl_key_name = value; - } else if (key == KEY_SSL_CA_NAME) { - entry.ssl_ca_name = value; - } else if (key == KEY_SSL_OCSP_NAME) { - entry.ssl_ocsp_name = value; - } else if (key == KEY_SSL_KEY_DIALOG) { - entry.ssl_key_dialog = value; - } else if (key == KEY_DEST_FQDN) { - entry.dest_fqdn = value; - } else if (key == KEY_ACTION) { - entry.action = value; - } else if (key == KEY_SSL_TICKET_ENABLED) { - entry.ssl_ticket_enabled = swoc::svtoi(value); - } else if (key == KEY_SSL_TICKET_NUMBER) { - entry.ssl_ticket_number = swoc::svtoi(value); - } else if (unknown_keys.insert(key).second) { - errata.note(ERRATA_NOTE_SEV, "Ignoring unknown ssl_multicert key '{}' in legacy format", key); - } - } - - result.push_back(std::move(entry)); - } - - return {result, std::move(errata)}; -} - -std::string -SSLMultiCertMarshaller::to_yaml(SSLMultiCertConfig const &config) -{ - YAML::Emitter yaml; - yaml << YAML::BeginMap; - yaml << YAML::Key << KEY_SSL_MULTICERT << YAML::Value << YAML::BeginSeq; - - for (auto const &entry : config) { - emit_entry(yaml, entry); - } - - yaml << YAML::EndSeq << YAML::EndMap; - return yaml.c_str(); -} - -std::string -SSLMultiCertMarshaller::to_json(SSLMultiCertConfig const &config) -{ - YAML::Emitter json; - json << YAML::DoubleQuoted << YAML::Flow; - json << YAML::BeginMap; - json << YAML::Key << KEY_SSL_MULTICERT << YAML::Value << YAML::BeginSeq; - - for (auto const &entry : config) { - emit_entry(json, entry); - } - - json << YAML::EndSeq << YAML::EndMap; - return json.c_str(); -} - -} // namespace config diff --git a/src/config/storage.cc b/src/config/storage.cc deleted file mode 100644 index 42b2273f61f..00000000000 --- a/src/config/storage.cc +++ /dev/null @@ -1,897 +0,0 @@ -/** @file - - Storage configuration parsing and marshalling implementation. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include "config/storage.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "swoc/swoc_file.h" -#include "tscore/ParseRules.h" -#include "tsutil/ts_diag_levels.h" - -namespace -{ - -constexpr swoc::Errata::Severity ERRATA_NOTE_SEV{static_cast(DL_Note)}; -constexpr swoc::Errata::Severity ERRATA_ERROR_SEV{static_cast(DL_Error)}; - -// YAML key names - top-level -constexpr char KEY_CACHE[] = "cache"; -constexpr char KEY_SPANS[] = "spans"; -constexpr char KEY_VOLUMES[] = "volumes"; - -// YAML key names - spans -constexpr char KEY_NAME[] = "name"; -constexpr char KEY_PATH[] = "path"; -constexpr char KEY_SIZE[] = "size"; -constexpr char KEY_HASH_SEED[] = "hash_seed"; - -// YAML key names - volumes -constexpr char KEY_ID[] = "id"; -constexpr char KEY_SCHEME[] = "scheme"; -constexpr char KEY_RAM_CACHE[] = "ram_cache"; -constexpr char KEY_RAM_CACHE_SIZE[] = "ram_cache_size"; -constexpr char KEY_RAM_CACHE_CUTOFF[] = "ram_cache_cutoff"; -constexpr char KEY_AVG_OBJ_SIZE[] = "avg_obj_size"; -constexpr char KEY_FRAGMENT_SIZE[] = "fragment_size"; - -// YAML key names - volume span refs -constexpr char KEY_USE[] = "use"; - -constexpr int MAX_VOLUME_IDX = 255; - -std::set const valid_cache_keys = {KEY_SPANS, KEY_VOLUMES}; -std::set const valid_span_keys = {KEY_NAME, KEY_PATH, KEY_SIZE, KEY_HASH_SEED}; -std::set const valid_volume_keys = {KEY_ID, KEY_SCHEME, KEY_SIZE, - KEY_RAM_CACHE, KEY_RAM_CACHE_SIZE, KEY_RAM_CACHE_CUTOFF, - KEY_AVG_OBJ_SIZE, KEY_FRAGMENT_SIZE, KEY_SPANS}; -std::set const valid_spanref_keys = {KEY_USE, KEY_SIZE}; - -/** - * Validate that all keys in @a node are members of @a valid_keys. - * Returns warnings for unknown keys. - */ -swoc::Errata -validate_map(YAML::Node const &node, std::set const &valid_keys) -{ - swoc::Errata errata; - std::set unknown_keys; - - if (!node.IsMap()) { - errata.note(ERRATA_ERROR_SEV, "expected a map node"); - return errata; - } - - for (auto const &item : node) { - std::string key = item.first.as(); - if (valid_keys.find(key) == valid_keys.end() && unknown_keys.insert(key).second) { - errata.note(ERRATA_NOTE_SEV, "ignoring unknown key '{}' at line {}", key, item.first.Mark().line + 1); - } - } - - return errata; -} - -/** - * Parse a human-readable size string into bytes (raw, no MB conversion). - * Supports K/M/G/T suffixes. Returns -1 if the string does not start with a digit. - */ -int64_t -parse_byte_size(std::string const &s) -{ - if (s.empty() || !std::isdigit(static_cast(s[0]))) { - return -1; - } - return ink_atoi64(s.c_str()); -} - -/** - * Parse a human-readable size string into bytes. - * Supports K/M/G/T suffixes and percent values. - * Returns the absolute byte value (not converted to MB — callers must do that). - * - * @return false if parsing failed. - */ -bool -parse_size_string(config::StorageVolumeEntry::Size &size, std::string const &s) -{ - if (s.empty() || !std::isdigit(static_cast(s[0]))) { - return false; - } - - if (s.back() == '%') { - int value = std::stoi(s.substr(0, s.size() - 1)); - if (value > 100) { - return false; - } - size.in_percent = true; - size.percent = value; - } else { - size.in_percent = false; - // Convert raw bytes to megabytes for block calculation (matching original logic). - int64_t bytes = parse_byte_size(s); - if (bytes < 0) { - return false; - } - size.absolute_value = bytes / 1024 / 1024; - } - - return true; -} - -/** - * Parse a single token from @a rest (whitespace-delimited). - * Returns the token and advances @a rest past it and any trailing whitespace. - */ -std::string -next_token(std::string_view &rest) -{ - size_t start = rest.find_first_not_of(" \t"); - if (start == std::string_view::npos) { - rest = {}; - return {}; - } - rest = rest.substr(start); - size_t end = rest.find_first_of(" \t"); - if (end == std::string_view::npos) { - std::string tok{rest}; - rest = {}; - return tok; - } - std::string tok{rest.substr(0, end)}; - rest = rest.substr(end); - return tok; -} - -/** - * Parse legacy storage.config content. - * - * Format per line: pathname [size_bytes] [id=hash_seed] [volume=N] - */ -config::ConfigResult -parse_legacy_storage_config(std::string_view content) -{ - config::StorageConfig result; - swoc::Errata errata; - - // volume_num -> list of span names (path used as name) - std::map> vol_span_map; - - std::istringstream ss{std::string{content}}; - std::string line; - int line_num = 0; - - while (std::getline(ss, line)) { - ++line_num; - - // Normalize whitespace so simple whitespace splitting works. - for (char &c : line) { - if (std::isspace(static_cast(c))) { - c = ' '; - } - } - - std::string_view rest{line}; - std::string path_tok = next_token(rest); - - if (path_tok.empty() || path_tok[0] == '#') { - continue; - } - - int64_t size = 0; - std::string hash_seed; - int volume_num = -1; - bool err = false; - - while (true) { - std::string tok = next_token(rest); - if (tok.empty() || tok[0] == '#') { - break; - } - - if (tok.rfind("id=", 0) == 0) { - hash_seed = tok.substr(3); - } else if (tok.rfind("volume=", 0) == 0) { - std::string num_str = tok.substr(7); - if (num_str.empty() || !ParseRules::is_digit(num_str[0])) { - errata.note(ERRATA_ERROR_SEV, "invalid volume number '{}' at line {}", num_str, line_num); - err = true; - break; - } - volume_num = std::stoi(num_str); - if (volume_num < 1 || volume_num > MAX_VOLUME_IDX) { - errata.note(ERRATA_ERROR_SEV, "volume number {} out of range at line {}", volume_num, line_num); - err = true; - break; - } - } else if (ParseRules::is_digit(tok[0])) { - const char *end_ptr = nullptr; - int64_t v = ink_atoi64(tok.c_str(), &end_ptr); - if (v <= 0 || (end_ptr && *end_ptr != '\0')) { - errata.note(ERRATA_ERROR_SEV, "invalid size '{}' at line {}", tok, line_num); - err = true; - break; - } - size = v; - } else { - errata.note(ERRATA_NOTE_SEV, "ignoring unknown token '{}' at line {}", tok, line_num); - } - } - - if (err) { - continue; - } - - config::StorageSpanEntry span; - span.name = path_tok; - span.path = path_tok; - span.size = size; - span.hash_seed = std::move(hash_seed); - result.spans.push_back(std::move(span)); - - if (volume_num > 0) { - vol_span_map[volume_num].push_back(std::move(path_tok)); - } - } - - // Build volume entries from span assignments. - for (auto const &[vol_id, span_names] : vol_span_map) { - config::StorageVolumeEntry vol; - vol.id = vol_id; - vol.scheme = "http"; - for (auto const &sname : span_names) { - config::StorageVolumeEntry::SpanRef ref; - ref.use = sname; - vol.spans.push_back(std::move(ref)); - } - result.volumes.push_back(std::move(vol)); - } - - return {std::move(result), std::move(errata)}; -} - -/** - * Parse legacy volume.config content. - * - * Format per line: - * volume=N scheme=http size=V[%] [avg_obj_size=V] [fragment_size=V] - * [ramcache=true|false] - * [ram_cache_size=V] [ram_cache_cutoff=V] - * - * Absolute sizes are in megabytes. - */ -config::ConfigResult -parse_legacy_volume_config(std::string_view content) -{ - config::StorageConfig result; - swoc::Errata errata; - - std::istringstream ss{std::string{content}}; - std::string line; - int line_num = 0; - int total_percent = 0; - std::set seen_ids; - - while (std::getline(ss, line)) { - ++line_num; - - std::string_view rest{line}; - - // Skip leading whitespace. - size_t start = rest.find_first_not_of(" \t\r"); - if (start == std::string_view::npos || rest[start] == '#') { - continue; - } - rest = rest.substr(start); - - int volume_number = 0; - std::string scheme; - int size = 0; - bool in_percent = false; - bool ramcache_enabled = true; - int64_t ram_cache_size = -1; - int64_t ram_cache_cutoff = -1; - int avg_obj_size = -1; - int fragment_size = -1; - bool parse_error = false; - - while (true) { - std::string tok = next_token(rest); - if (tok.empty() || tok[0] == '#') { - break; - } - - size_t eq = tok.find('='); - if (eq == std::string::npos) { - errata.note(ERRATA_ERROR_SEV, "unexpected token '{}' at line {} (expected key=value)", tok, line_num); - parse_error = true; - break; - } - - std::string key{tok.substr(0, eq)}; - std::string val{tok.substr(eq + 1)}; - - if (strcasecmp(key.c_str(), "volume") == 0) { - if (val.empty() || !ParseRules::is_digit(val[0])) { - errata.note(ERRATA_ERROR_SEV, "invalid volume number '{}' at line {}", val, line_num); - parse_error = true; - break; - } - volume_number = std::stoi(val); - if (volume_number < 1 || volume_number > MAX_VOLUME_IDX) { - errata.note(ERRATA_ERROR_SEV, "volume number {} out of range [1,255] at line {}", volume_number, line_num); - parse_error = true; - break; - } - } else if (strcasecmp(key.c_str(), "scheme") == 0) { - if (strcasecmp(val.c_str(), "http") == 0) { - scheme = "http"; - } else { - errata.note(ERRATA_ERROR_SEV, "unsupported scheme '{}' at line {}", val, line_num); - parse_error = true; - break; - } - } else if (strcasecmp(key.c_str(), "size") == 0) { - if (!val.empty() && val.back() == '%') { - size = std::stoi(val.substr(0, val.size() - 1)); - if (size < 0 || size > 100) { - errata.note(ERRATA_ERROR_SEV, "size percentage {} out of range at line {}", size, line_num); - parse_error = true; - break; - } - in_percent = true; - total_percent += size; - if (total_percent > 100) { - errata.note(ERRATA_ERROR_SEV, "total volume size exceeds 100%% at line {}", line_num); - parse_error = true; - break; - } - } else { - if (val.empty() || !ParseRules::is_digit(val[0])) { - errata.note(ERRATA_ERROR_SEV, "invalid size '{}' at line {}", val, line_num); - parse_error = true; - break; - } - size = std::stoi(val); - in_percent = false; - } - } else if (strcasecmp(key.c_str(), "avg_obj_size") == 0) { - avg_obj_size = static_cast(ink_atoi64(val.c_str())); - } else if (strcasecmp(key.c_str(), "fragment_size") == 0) { - fragment_size = static_cast(ink_atoi64(val.c_str())); - } else if (strcasecmp(key.c_str(), "ramcache") == 0) { - if (strcasecmp(val.c_str(), "false") == 0) { - ramcache_enabled = false; - } else if (strcasecmp(val.c_str(), "true") == 0) { - ramcache_enabled = true; - } else { - errata.note(ERRATA_ERROR_SEV, "invalid ramcache value '{}' at line {}", val, line_num); - parse_error = true; - break; - } - } else if (strcasecmp(key.c_str(), "ram_cache_size") == 0) { - ram_cache_size = ink_atoi64(val.c_str()); - } else if (strcasecmp(key.c_str(), "ram_cache_cutoff") == 0) { - ram_cache_cutoff = ink_atoi64(val.c_str()); - } else { - errata.note(ERRATA_NOTE_SEV, "ignoring unknown key '{}' at line {}", key, line_num); - } - } - - if (parse_error || volume_number == 0) { - continue; - } - - if (seen_ids.count(volume_number)) { - errata.note(ERRATA_ERROR_SEV, "duplicate volume number {} at line {}", volume_number, line_num); - continue; - } - seen_ids.insert(volume_number); - - config::StorageVolumeEntry vol; - vol.id = volume_number; - vol.scheme = scheme.empty() ? "http" : scheme; - vol.ram_cache = ramcache_enabled; - vol.ram_cache_size = ram_cache_size; - vol.ram_cache_cutoff = ram_cache_cutoff; - vol.avg_obj_size = avg_obj_size; - vol.fragment_size = fragment_size; - if (in_percent) { - vol.size.in_percent = true; - vol.size.percent = size; - } else { - vol.size.in_percent = false; - vol.size.absolute_value = size; // megabytes - } - result.volumes.push_back(std::move(vol)); - } - - return {std::move(result), std::move(errata)}; -} - -} // namespace - -namespace YAML -{ - -template <> struct convert { - static bool - decode(Node const &node, config::StorageSpanEntry &span) - { - if (!node[KEY_NAME]) { - throw ParserException(node.Mark(), "missing 'name' argument in cache.spans[]"); - } - span.name = node[KEY_NAME].as(); - - if (!node[KEY_PATH]) { - throw ParserException(node.Mark(), "missing 'path' argument in cache.spans[]"); - } - span.path = node[KEY_PATH].as(); - - if (node[KEY_SIZE]) { - std::string s = node[KEY_SIZE].as(); - int64_t v = parse_byte_size(s); - if (v < 0) { - throw ParserException(node.Mark(), "invalid 'size' value in cache.spans[]: " + s); - } - span.size = v; - } - - if (node[KEY_HASH_SEED]) { - span.hash_seed = node[KEY_HASH_SEED].as(); - } - - return true; - } -}; - -template <> struct convert { - static bool - decode(Node const &node, config::StorageVolumeEntry::Size &size) - { - std::string s = node.as(); - if (!parse_size_string(size, s)) { - throw YAML::Exception(node.Mark(), "invalid size value: " + s); - } - return true; - } -}; - -template <> struct convert { - static bool - decode(Node const &node, config::StorageVolumeEntry::SpanRef &ref) - { - if (!node[KEY_USE]) { - throw ParserException(node.Mark(), "missing 'use' argument in cache.volumes[].spans[]"); - } - ref.use = node[KEY_USE].as(); - - if (node[KEY_SIZE]) { - ref.size = node[KEY_SIZE].as(); - } - - return true; - } -}; - -template <> struct convert { - static bool - decode(Node const &node, config::StorageVolumeEntry &vol) - { - if (!node[KEY_ID]) { - throw ParserException(node.Mark(), "missing 'id' argument in cache.volumes[]"); - } - vol.id = node[KEY_ID].as(); - if (vol.id < 1 || MAX_VOLUME_IDX < vol.id) { - throw ParserException(node.Mark(), "volume id out of range [1, 255]: " + node[KEY_ID].as()); - } - - if (node[KEY_SCHEME]) { - std::string s = node[KEY_SCHEME].as(); - if (s != "http") { - throw ParserException(node.Mark(), "unsupported scheme '" + s + "' in cache.volumes[]"); - } - vol.scheme = std::move(s); - } - - if (node[KEY_SIZE]) { - vol.size = node[KEY_SIZE].as(); - } - - if (node[KEY_RAM_CACHE]) { - vol.ram_cache = node[KEY_RAM_CACHE].as(); - } - - if (node[KEY_RAM_CACHE_SIZE]) { - std::string s = node[KEY_RAM_CACHE_SIZE].as(); - int64_t v = parse_byte_size(s); - if (v < 0) { - throw ParserException(node.Mark(), "invalid 'ram_cache_size' value: " + s); - } - vol.ram_cache_size = v; - } - - if (node[KEY_RAM_CACHE_CUTOFF]) { - std::string s = node[KEY_RAM_CACHE_CUTOFF].as(); - int64_t v = parse_byte_size(s); - if (v < 0) { - throw ParserException(node.Mark(), "invalid 'ram_cache_cutoff' value: " + s); - } - vol.ram_cache_cutoff = v; - } - - if (node[KEY_AVG_OBJ_SIZE]) { - std::string s = node[KEY_AVG_OBJ_SIZE].as(); - int64_t v = parse_byte_size(s); - if (v < 0) { - throw ParserException(node.Mark(), "invalid 'avg_obj_size' value: " + s); - } - vol.avg_obj_size = static_cast(v); - } - - if (node[KEY_FRAGMENT_SIZE]) { - std::string s = node[KEY_FRAGMENT_SIZE].as(); - int64_t v = parse_byte_size(s); - if (v < 0) { - throw ParserException(node.Mark(), "invalid 'fragment_size' value: " + s); - } - vol.fragment_size = static_cast(v); - } - - if (node[KEY_SPANS]) { - YAML::Node spans_node = node[KEY_SPANS]; - if (!spans_node.IsSequence()) { - throw ParserException(node.Mark(), "expected sequence for 'spans' in cache.volumes[]"); - } - for (auto const &s : spans_node) { - vol.spans.push_back(s.as()); - } - } - - return true; - } -}; - -} // namespace YAML - -namespace config -{ - -static void -emit_size(YAML::Emitter &out, StorageVolumeEntry::Size const &size) -{ - if (size.in_percent) { - out << std::to_string(size.percent) + "%"; - } else { - out << std::to_string(size.absolute_value * 1024 * 1024); - } -} - -ConfigResult -StorageParser::parse(std::string const &filename) -{ - std::error_code ec; - std::string content = swoc::file::load(filename, ec); - if (ec) { - if (ec.value() == ENOENT) { - return {{}, swoc::Errata(ERRATA_ERROR_SEV, "Cannot open storage configuration \"{}\" - {}", filename, ec)}; - } - return {{}, swoc::Errata(ERRATA_ERROR_SEV, "Failed to read storage configuration from \"{}\" - {}", filename, ec)}; - } - - bool use_yaml = filename.size() >= 5 && filename.substr(filename.size() - 5) == ".yaml"; - if (use_yaml) { - return parse_content(content); - } - return parse_legacy_storage_content(content); -} - -ConfigResult -StorageParser::parse_content(std::string_view content) -{ - StorageConfig result; - swoc::Errata errata; - - try { - YAML::Node root = YAML::Load(std::string(content)); - if (root.IsNull()) { - return {result, std::move(errata)}; - } - - if (!root[KEY_CACHE]) { - return {result, swoc::Errata(ERRATA_ERROR_SEV, "expected a top-level 'cache' node in storage.yaml")}; - } - - YAML::Node cache = root[KEY_CACHE]; - - // Warn about unknown keys under 'cache'. - errata.note(validate_map(cache, valid_cache_keys)); - if (!errata.is_ok()) { - return {result, std::move(errata)}; - } - - // Parse spans. - if (cache[KEY_SPANS]) { - YAML::Node spans_node = cache[KEY_SPANS]; - if (!spans_node.IsSequence()) { - return {result, swoc::Errata(ERRATA_ERROR_SEV, "expected sequence for 'cache.spans'")}; - } - for (auto const &s : spans_node) { - // Warn about unknown keys in each span. - errata.note(validate_map(s, valid_span_keys)); - result.spans.push_back(s.as()); - } - } - - // Parse volumes. - if (cache[KEY_VOLUMES]) { - YAML::Node vols_node = cache[KEY_VOLUMES]; - if (!vols_node.IsSequence()) { - return {result, swoc::Errata(ERRATA_ERROR_SEV, "expected sequence for 'cache.volumes'")}; - } - - int total_percent = 0; - std::set seen_ids; - - for (auto const &v : vols_node) { - // Warn about unknown keys in each volume. - errata.note(validate_map(v, valid_volume_keys)); - - // Warn about unknown keys in each volume's span refs. - if (v[KEY_SPANS] && v[KEY_SPANS].IsSequence()) { - for (auto const &sr : v[KEY_SPANS]) { - errata.note(validate_map(sr, valid_spanref_keys)); - } - } - - StorageVolumeEntry vol = v.as(); - - if (seen_ids.count(vol.id)) { - return {result, swoc::Errata(ERRATA_ERROR_SEV, "duplicate volume id {} at line {}", vol.id, v.Mark().line + 1)}; - } - seen_ids.insert(vol.id); - - if (vol.size.in_percent) { - total_percent += vol.size.percent; - if (total_percent > 100) { - return {result, swoc::Errata(ERRATA_ERROR_SEV, "total volume size exceeds 100%%")}; - } - } - - result.volumes.push_back(std::move(vol)); - } - } - - } catch (std::exception const &ex) { - return {std::move(result), swoc::Errata(ERRATA_ERROR_SEV, "YAML parse error: {}", ex.what())}; - } - - return {std::move(result), std::move(errata)}; -} - -ConfigResult -StorageParser::parse_legacy_storage_content(std::string_view content) -{ - return parse_legacy_storage_config(content); -} - -ConfigResult -VolumeParser::parse(std::string const &filename) -{ - std::error_code ec; - std::string content = swoc::file::load(filename, ec); - if (ec) { - if (ec.value() == ENOENT) { - ConfigResult r; - r.file_not_found = true; - r.errata.note(ERRATA_ERROR_SEV, "Cannot open volume configuration \"{}\" - {}", filename, ec); - return r; - } - return {{}, swoc::Errata(ERRATA_ERROR_SEV, "Failed to read volume configuration from \"{}\" - {}", filename, ec)}; - } - - return parse_content(content); -} - -ConfigResult -VolumeParser::parse_content(std::string_view content) -{ - return parse_legacy_volume_config(content); -} - -StorageConfig -merge_legacy_storage_configs(StorageConfig const &storage, StorageConfig const &volumes) -{ - StorageConfig merged = storage; - - if (volumes.volumes.empty()) { - return merged; - } - - // Build a lookup of volume id -> span refs gathered from storage.config. - std::map> span_ref_map; - for (auto const &vol : merged.volumes) { - span_ref_map[vol.id] = vol.spans; - } - - merged.volumes.clear(); - for (auto vol : volumes.volumes) { - auto it = span_ref_map.find(vol.id); - if (it != span_ref_map.end()) { - vol.spans = it->second; - } - merged.volumes.push_back(std::move(vol)); - } - - return merged; -} - -std::string -StorageMarshaller::to_yaml(StorageConfig const &config) -{ - YAML::Emitter out; - out << YAML::BeginMap; - out << YAML::Key << KEY_CACHE << YAML::Value << YAML::BeginMap; - - // Spans. - out << YAML::Key << KEY_SPANS << YAML::Value << YAML::BeginSeq; - for (auto const &span : config.spans) { - out << YAML::BeginMap; - out << YAML::Key << KEY_NAME << YAML::Value << span.name; - out << YAML::Key << KEY_PATH << YAML::Value << span.path; - if (span.size > 0) { - out << YAML::Key << KEY_SIZE << YAML::Value << std::to_string(span.size); - } - if (!span.hash_seed.empty()) { - out << YAML::Key << KEY_HASH_SEED << YAML::Value << span.hash_seed; - } - out << YAML::EndMap; - } - out << YAML::EndSeq; - - // Volumes. - if (!config.volumes.empty()) { - out << YAML::Key << KEY_VOLUMES << YAML::Value << YAML::BeginSeq; - for (auto const &vol : config.volumes) { - out << YAML::BeginMap; - out << YAML::Key << KEY_ID << YAML::Value << vol.id; - out << YAML::Key << KEY_SCHEME << YAML::Value << vol.scheme; - if (!vol.size.is_empty()) { - out << YAML::Key << KEY_SIZE << YAML::Value; - emit_size(out, vol.size); - } - out << YAML::Key << KEY_RAM_CACHE << YAML::Value << vol.ram_cache; - if (vol.ram_cache_size >= 0) { - out << YAML::Key << KEY_RAM_CACHE_SIZE << YAML::Value << std::to_string(vol.ram_cache_size); - } - if (vol.ram_cache_cutoff >= 0) { - out << YAML::Key << KEY_RAM_CACHE_CUTOFF << YAML::Value << std::to_string(vol.ram_cache_cutoff); - } - if (vol.avg_obj_size >= 0) { - out << YAML::Key << KEY_AVG_OBJ_SIZE << YAML::Value << std::to_string(vol.avg_obj_size); - } - if (vol.fragment_size >= 0) { - out << YAML::Key << KEY_FRAGMENT_SIZE << YAML::Value << std::to_string(vol.fragment_size); - } - if (!vol.spans.empty()) { - out << YAML::Key << KEY_SPANS << YAML::Value << YAML::BeginSeq; - for (auto const &sr : vol.spans) { - out << YAML::BeginMap; - out << YAML::Key << KEY_USE << YAML::Value << sr.use; - if (!sr.size.is_empty()) { - out << YAML::Key << KEY_SIZE << YAML::Value; - emit_size(out, sr.size); - } - out << YAML::EndMap; - } - out << YAML::EndSeq; - } - out << YAML::EndMap; - } - out << YAML::EndSeq; - } - - out << YAML::EndMap << YAML::EndMap; - return out.c_str(); -} - -std::string -StorageMarshaller::to_json(StorageConfig const &config) -{ - YAML::Emitter out; - out << YAML::DoubleQuoted << YAML::Flow; - out << YAML::BeginMap; - out << YAML::Key << KEY_CACHE << YAML::Value << YAML::BeginMap; - - // Spans. - out << YAML::Key << KEY_SPANS << YAML::Value << YAML::BeginSeq; - for (auto const &span : config.spans) { - out << YAML::BeginMap; - out << YAML::Key << KEY_NAME << YAML::Value << span.name; - out << YAML::Key << KEY_PATH << YAML::Value << span.path; - if (span.size > 0) { - out << YAML::Key << KEY_SIZE << YAML::Value << std::to_string(span.size); - } - if (!span.hash_seed.empty()) { - out << YAML::Key << KEY_HASH_SEED << YAML::Value << span.hash_seed; - } - out << YAML::EndMap; - } - out << YAML::EndSeq; - - // Volumes. - if (!config.volumes.empty()) { - out << YAML::Key << KEY_VOLUMES << YAML::Value << YAML::BeginSeq; - for (auto const &vol : config.volumes) { - out << YAML::BeginMap; - out << YAML::Key << KEY_ID << YAML::Value << vol.id; - out << YAML::Key << KEY_SCHEME << YAML::Value << vol.scheme; - if (!vol.size.is_empty()) { - out << YAML::Key << KEY_SIZE << YAML::Value; - emit_size(out, vol.size); - } - out << YAML::Key << KEY_RAM_CACHE << YAML::Value << vol.ram_cache; - if (vol.ram_cache_size >= 0) { - out << YAML::Key << KEY_RAM_CACHE_SIZE << YAML::Value << std::to_string(vol.ram_cache_size); - } - if (vol.ram_cache_cutoff >= 0) { - out << YAML::Key << KEY_RAM_CACHE_CUTOFF << YAML::Value << std::to_string(vol.ram_cache_cutoff); - } - if (vol.avg_obj_size >= 0) { - out << YAML::Key << KEY_AVG_OBJ_SIZE << YAML::Value << std::to_string(vol.avg_obj_size); - } - if (vol.fragment_size >= 0) { - out << YAML::Key << KEY_FRAGMENT_SIZE << YAML::Value << std::to_string(vol.fragment_size); - } - if (!vol.spans.empty()) { - out << YAML::Key << KEY_SPANS << YAML::Value << YAML::BeginSeq; - for (auto const &sr : vol.spans) { - out << YAML::BeginMap; - out << YAML::Key << KEY_USE << YAML::Value << sr.use; - if (!sr.size.is_empty()) { - out << YAML::Key << KEY_SIZE << YAML::Value; - emit_size(out, sr.size); - } - out << YAML::EndMap; - } - out << YAML::EndSeq; - } - out << YAML::EndMap; - } - out << YAML::EndSeq; - } - - out << YAML::EndMap << YAML::EndMap; - return out.c_str(); -} - -} // namespace config diff --git a/src/config/unit_tests/test_plugin_config.cc b/src/config/unit_tests/test_plugin_config.cc deleted file mode 100644 index 69b32c94d4b..00000000000 --- a/src/config/unit_tests/test_plugin_config.cc +++ /dev/null @@ -1,206 +0,0 @@ -/** @file - - Unit tests for plugin.config parser and marshaller. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include -#include -#include - -#include - -#include "config/plugin_config.h" - -namespace -{ - -class TempFile -{ -public: - TempFile(std::string const &filename, std::string const &content) - { - _path = std::filesystem::temp_directory_path() / filename; - std::ofstream ofs(_path); - ofs << content; - } - - ~TempFile() { std::filesystem::remove(_path); } - - std::string - path() const - { - return _path.string(); - } - -private: - std::filesystem::path _path; -}; - -} // namespace - -TEST_CASE("plugin_config parser - basic active plugins", "[plugin_config]") -{ - TempFile tf("plugin_basic.config", "stats_over_http.so _stats\nheader_rewrite.so /etc/trafficserver/rewrite.conf\n"); - auto result = config::PluginConfigParser{}.parse(tf.path()); - - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 2); - - CHECK(result.value[0].path == "stats_over_http.so"); - CHECK(result.value[0].enabled == true); - REQUIRE(result.value[0].args.size() == 1); - CHECK(result.value[0].args[0] == "_stats"); - - CHECK(result.value[1].path == "header_rewrite.so"); - CHECK(result.value[1].enabled == true); - REQUIRE(result.value[1].args.size() == 1); - CHECK(result.value[1].args[0] == "/etc/trafficserver/rewrite.conf"); -} - -TEST_CASE("plugin_config parser - commented lines become disabled", "[plugin_config]") -{ - TempFile tf("plugin_commented.config", "stats_over_http.so\n# cache_promote.so --policy=lru\nxdebug.so\n"); - auto result = config::PluginConfigParser{}.parse(tf.path()); - - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 3); - - CHECK(result.value[0].path == "stats_over_http.so"); - CHECK(result.value[0].enabled == true); - - CHECK(result.value[1].path == "cache_promote.so"); - CHECK(result.value[1].enabled == false); - REQUIRE(result.value[1].args.size() == 1); - CHECK(result.value[1].args[0] == "--policy=lru"); - - CHECK(result.value[2].path == "xdebug.so"); - CHECK(result.value[2].enabled == true); -} - -TEST_CASE("plugin_config parser - pure comment lines are skipped", "[plugin_config]") -{ - TempFile tf("plugin_pure_comments.config", "# This is just a comment\n# Another comment line\nstats_over_http.so\n"); - auto result = config::PluginConfigParser{}.parse(tf.path()); - - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 1); - CHECK(result.value[0].path == "stats_over_http.so"); -} - -TEST_CASE("plugin_config parser - blank lines", "[plugin_config]") -{ - TempFile tf("plugin_blanks.config", "\n\nstats_over_http.so\n\nxdebug.so\n\n"); - auto result = config::PluginConfigParser{}.parse(tf.path()); - - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 2); -} - -TEST_CASE("plugin_config parser - quoted arguments", "[plugin_config]") -{ - TempFile tf("plugin_quoted.config", "my_plugin.so \"arg with spaces\" second_arg\n"); - auto result = config::PluginConfigParser{}.parse(tf.path()); - - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 1); - REQUIRE(result.value[0].args.size() == 2); - CHECK(result.value[0].args[0] == "arg with spaces"); - CHECK(result.value[0].args[1] == "second_arg"); -} - -TEST_CASE("plugin_config parser - plugin with no args", "[plugin_config]") -{ - TempFile tf("plugin_noargs.config", "xdebug.so\n"); - auto result = config::PluginConfigParser{}.parse(tf.path()); - - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 1); - CHECK(result.value[0].path == "xdebug.so"); - CHECK(result.value[0].args.empty()); -} - -TEST_CASE("plugin_config parser - dollar record references preserved", "[plugin_config]") -{ - TempFile tf("plugin_dollar.config", "my_plugin.so $proxy.config.http.server_ports\n"); - auto result = config::PluginConfigParser{}.parse(tf.path()); - - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 1); - REQUIRE(result.value[0].args.size() == 1); - CHECK(result.value[0].args[0] == "$proxy.config.http.server_ports"); -} - -TEST_CASE("plugin_config parser - nonexistent file", "[plugin_config]") -{ - auto result = config::PluginConfigParser{}.parse("/nonexistent/path/plugin.config"); - - CHECK(!result.ok()); - CHECK(result.file_not_found == true); -} - -TEST_CASE("plugin_config marshaller - basic output", "[plugin_config]") -{ - config::PluginConfigData data = { - {"stats_over_http.so", {"_stats"}, true }, - {"cache_promote.so", {"--policy=lru", "--buckets=100"}, false}, - {"xdebug.so", {}, true }, - }; - - auto yaml = config::PluginConfigMarshaller{}.to_yaml(data); - - YAML::Node root = YAML::Load(yaml); - REQUIRE(root["plugins"]); - REQUIRE(root["plugins"].IsSequence()); - REQUIRE(root["plugins"].size() == 3); - - CHECK(root["plugins"][0]["path"].as() == "stats_over_http.so"); - CHECK_FALSE(root["plugins"][0]["enabled"]); - - CHECK(root["plugins"][1]["path"].as() == "cache_promote.so"); - CHECK(root["plugins"][1]["enabled"].as() == false); - CHECK(root["plugins"][1]["params"].size() == 2); - - CHECK(root["plugins"][2]["path"].as() == "xdebug.so"); - CHECK_FALSE(root["plugins"][2]["params"]); -} - -TEST_CASE("plugin_config round-trip parse then marshal", "[plugin_config]") -{ - TempFile tf("plugin_roundtrip.config", - "stats_over_http.so _stats\n# cache_promote.so --policy=lru\nheader_rewrite.so rewrite.conf\n"); - auto result = config::PluginConfigParser{}.parse(tf.path()); - - REQUIRE(result.ok()); - - auto yaml = config::PluginConfigMarshaller{}.to_yaml(result.value); - - YAML::Node root = YAML::Load(yaml); - REQUIRE(root["plugins"].size() == 3); - - CHECK(root["plugins"][0]["path"].as() == "stats_over_http.so"); - CHECK_FALSE(root["plugins"][0]["enabled"]); - - CHECK(root["plugins"][1]["path"].as() == "cache_promote.so"); - CHECK(root["plugins"][1]["enabled"].as() == false); - - CHECK(root["plugins"][2]["path"].as() == "header_rewrite.so"); - CHECK_FALSE(root["plugins"][2]["enabled"]); -} diff --git a/src/config/unit_tests/test_ssl_multicert.cc b/src/config/unit_tests/test_ssl_multicert.cc deleted file mode 100644 index 7baae9563bc..00000000000 --- a/src/config/unit_tests/test_ssl_multicert.cc +++ /dev/null @@ -1,482 +0,0 @@ -/** @file - - Unit tests for ssl_multicert configuration parsing and marshalling. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include "config/ssl_multicert.h" - -#include -#include - -#include -#include - -using namespace config; - -namespace -{ -// Helper to create a temporary file with content. -class TempFile -{ -public: - TempFile(std::string const &filename, std::string const &content) - { - _path = std::filesystem::temp_directory_path() / filename; - std::ofstream ofs(_path); - ofs << content; - } - - ~TempFile() { std::filesystem::remove(_path); } - - std::string - path() const - { - return _path.string(); - } - -private: - std::filesystem::path _path; -}; - -// Helper to parse content via temp file. -ConfigResult -parse_content(std::string const &content, std::string const &filename) -{ - TempFile file(filename, content); - SSLMultiCertParser parser; - return parser.parse(file.path()); -} -} // namespace - -// Sample legacy config content. -static constexpr char LEGACY_CONFIG[] = R"(# Comment line -ssl_cert_name=server.pem ssl_key_name=server.key dest_ip=* -ssl_cert_name=another.pem dest_ip="[::1]:8443" ssl_ticket_enabled=1 -ssl_cert_name=quoted.pem ssl_key_dialog="exec:/usr/bin/getpass arg1 'arg 2'" -)"; - -// Sample YAML config content. -static constexpr char YAML_CONFIG[] = R"(ssl_multicert: - - ssl_cert_name: server.pem - ssl_key_name: server.key - dest_ip: "*" - - ssl_cert_name: another.pem - dest_ip: "[::1]:8443" - ssl_ticket_enabled: 1 - - ssl_cert_name: quoted.pem - ssl_key_dialog: "exec:/usr/bin/getpass arg1 'arg 2'" -)"; - -TEST_CASE("SSLMultiCertParser parses legacy config format", "[ssl_multicert][parser][legacy]") -{ - auto result = parse_content(LEGACY_CONFIG, "ssl_multicert.config"); - - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 3); - - SECTION("First entry") - { - auto const &entry = result.value[0]; - CHECK(entry.ssl_cert_name == "server.pem"); - CHECK(entry.ssl_key_name == "server.key"); - CHECK(entry.dest_ip == "*"); - CHECK_FALSE(entry.ssl_ticket_enabled.has_value()); - } - - SECTION("Second entry with IPv6 and ticket enabled") - { - auto const &entry = result.value[1]; - CHECK(entry.ssl_cert_name == "another.pem"); - CHECK(entry.dest_ip == "[::1]:8443"); - REQUIRE(entry.ssl_ticket_enabled.has_value()); - CHECK(entry.ssl_ticket_enabled.value() == 1); - } - - SECTION("Third entry with quoted dialog") - { - auto const &entry = result.value[2]; - CHECK(entry.ssl_cert_name == "quoted.pem"); - CHECK(entry.ssl_key_dialog == "exec:/usr/bin/getpass arg1 'arg 2'"); - } -} - -TEST_CASE("SSLMultiCertParser parses YAML config format", "[ssl_multicert][parser][yaml]") -{ - auto result = parse_content(YAML_CONFIG, "ssl_multicert.yaml"); - - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 3); - - SECTION("First entry") - { - auto const &entry = result.value[0]; - CHECK(entry.ssl_cert_name == "server.pem"); - CHECK(entry.ssl_key_name == "server.key"); - CHECK(entry.dest_ip == "*"); - } - - SECTION("Second entry with IPv6 and ticket enabled") - { - auto const &entry = result.value[1]; - CHECK(entry.ssl_cert_name == "another.pem"); - CHECK(entry.dest_ip == "[::1]:8443"); - REQUIRE(entry.ssl_ticket_enabled.has_value()); - CHECK(entry.ssl_ticket_enabled.value() == 1); - } - - SECTION("Third entry with quoted dialog") - { - auto const &entry = result.value[2]; - CHECK(entry.ssl_cert_name == "quoted.pem"); - CHECK(entry.ssl_key_dialog == "exec:/usr/bin/getpass arg1 'arg 2'"); - } -} - -TEST_CASE("SSLMultiCertParser auto-detects format from filename", "[ssl_multicert][parser][detection]") -{ - auto [description, content, filename] = GENERATE(table({ - {"YAML from .yaml extension", YAML_CONFIG, "config.yaml" }, - {"YAML from .yml extension", YAML_CONFIG, "config.yml" }, - {"legacy from .config extension", LEGACY_CONFIG, "ssl_multicert.config"}, - {"YAML from content (no extension)", YAML_CONFIG, "config" }, - {"legacy from content (no extension)", LEGACY_CONFIG, "config" }, - })); - - CAPTURE(description, filename); - - auto result = parse_content(content, filename); - REQUIRE(result.ok()); - CHECK(result.value.size() == 3); -} - -TEST_CASE("SSLMultiCertParser returns empty config for empty input", "[ssl_multicert][parser][edge]") -{ - auto [description, content, filename] = GENERATE(table({ - {"empty YAML content", "", "config.yaml" }, - {"empty legacy content", "", "config.config"}, - {"comments only in legacy", "# Just a comment\n# Another comment\n", "config.config"}, - {"whitespace only", " \n\t\n ", "config.config"}, - {"empty ssl_multicert in YAML", "ssl_multicert: []\n", "config.yaml" }, - })); - - CAPTURE(description); - - auto result = parse_content(content, filename); - REQUIRE(result.ok()); - CHECK(result.value.empty()); -} - -TEST_CASE("SSLMultiCertParser returns error for invalid input", "[ssl_multicert][parser][edge]") -{ - auto [description, content, filename] = GENERATE(table({ - {"invalid YAML syntax", "ssl_multicert: [not: valid: yaml", "config.yaml"}, - {"missing ssl_multicert key", "other_key:\n - value: 1\n", "config.yaml"}, - })); - - CAPTURE(description); - - auto result = parse_content(content, filename); - CHECK_FALSE(result.ok()); -} - -TEST_CASE("SSLMultiCertParser handles all YAML entry fields", "[ssl_multicert][parser][fields]") -{ - static constexpr char FULL_YAML[] = R"(ssl_multicert: - - ssl_cert_name: cert.pem - dest_ip: "192.168.1.1" - ssl_key_name: key.pem - ssl_ca_name: ca.pem - ssl_ocsp_name: ocsp.der - ssl_key_dialog: "builtin" - dest_fqdn: "example.com" - action: tunnel - ssl_ticket_enabled: 1 - ssl_ticket_number: 5 -)"; - - auto result = parse_content(FULL_YAML, "config.yaml"); - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 1); - - auto const &entry = result.value[0]; - CHECK(entry.ssl_cert_name == "cert.pem"); - CHECK(entry.dest_ip == "192.168.1.1"); - CHECK(entry.ssl_key_name == "key.pem"); - CHECK(entry.ssl_ca_name == "ca.pem"); - CHECK(entry.ssl_ocsp_name == "ocsp.der"); - CHECK(entry.ssl_key_dialog == "builtin"); - CHECK(entry.dest_fqdn == "example.com"); - CHECK(entry.action == "tunnel"); - REQUIRE(entry.ssl_ticket_enabled.has_value()); - CHECK(entry.ssl_ticket_enabled.value() == 1); - REQUIRE(entry.ssl_ticket_number.has_value()); - CHECK(entry.ssl_ticket_number.value() == 5); -} - -TEST_CASE("SSLMultiCertMarshaller produces valid YAML", "[ssl_multicert][marshaller][yaml]") -{ - SSLMultiCertConfig config; - - SSLMultiCertEntry entry1; - entry1.ssl_cert_name = "server.pem"; - entry1.dest_ip = "*"; - entry1.ssl_key_name = "server.key"; - config.push_back(entry1); - - SSLMultiCertEntry entry2; - entry2.ssl_cert_name = "another.pem"; - entry2.dest_ip = "[::1]:8443"; - entry2.ssl_ticket_enabled = 1; - config.push_back(entry2); - - SSLMultiCertMarshaller marshaller; - std::string yaml = marshaller.to_yaml(config); - - SECTION("YAML contains expected structure") - { - CHECK(yaml.find("ssl_multicert:") != std::string::npos); - CHECK(yaml.find("ssl_cert_name: server.pem") != std::string::npos); - CHECK(yaml.find("ssl_key_name: server.key") != std::string::npos); - CHECK(yaml.find("ssl_cert_name: another.pem") != std::string::npos); - CHECK(yaml.find("ssl_ticket_enabled: 1") != std::string::npos); - } - - SECTION("YAML can be re-parsed") - { - auto result = parse_content(yaml, "config.yaml"); - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 2); - CHECK(result.value[0].ssl_cert_name == "server.pem"); - CHECK(result.value[1].ssl_cert_name == "another.pem"); - } -} - -TEST_CASE("SSLMultiCertMarshaller produces valid JSON", "[ssl_multicert][marshaller][json]") -{ - SSLMultiCertConfig config; - - SSLMultiCertEntry entry1; - entry1.ssl_cert_name = "server.pem"; - entry1.dest_ip = "*"; - config.push_back(entry1); - - SSLMultiCertEntry entry2; - entry2.ssl_cert_name = "another.pem"; - entry2.dest_ip = "[::1]:8443"; - entry2.ssl_ticket_enabled = 1; - entry2.ssl_ticket_number = 5; - config.push_back(entry2); - - SSLMultiCertMarshaller marshaller; - std::string json = marshaller.to_json(config); - - CHECK(json.find("\"ssl_multicert\"") != std::string::npos); - CHECK(json.find("\"ssl_cert_name\": \"server.pem\"") != std::string::npos); - CHECK(json.find("\"ssl_cert_name\": \"another.pem\"") != std::string::npos); - CHECK(json.find("\"ssl_ticket_enabled\": 1") != std::string::npos); - CHECK(json.find("\"ssl_ticket_number\": 5") != std::string::npos); - CHECK(json.find('[') != std::string::npos); - CHECK(json.find(']') != std::string::npos); -} - -TEST_CASE("SSLMultiCertMarshaller handles special characters", "[ssl_multicert][marshaller][escaping]") -{ - SSLMultiCertConfig config; - - SSLMultiCertEntry entry; - entry.ssl_cert_name = "server.pem"; - entry.dest_ip = "*"; - entry.ssl_key_dialog = "exec:/path/to/script \"with quotes\""; - config.push_back(entry); - - SSLMultiCertMarshaller marshaller; - - SECTION("YAML output contains the field and can be re-parsed") - { - std::string yaml = marshaller.to_yaml(config); - CHECK(yaml.find("ssl_key_dialog:") != std::string::npos); - - // Verify round-trip preserves the value. - auto result = parse_content(yaml, "test.yaml"); - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 1); - CHECK(result.value[0].ssl_key_dialog == "exec:/path/to/script \"with quotes\""); - } - - SECTION("JSON escapes quotes") - { - std::string json = marshaller.to_json(config); - CHECK(json.find("\\\"with quotes\\\"") != std::string::npos); - } -} - -TEST_CASE("Round-trip: legacy -> parse -> yaml -> parse", "[ssl_multicert][roundtrip]") -{ - SSLMultiCertMarshaller marshaller; - - // Parse legacy format. - auto legacy_result = parse_content(LEGACY_CONFIG, "ssl_multicert.config"); - REQUIRE(legacy_result.ok()); - - // Marshal to YAML. - std::string yaml = marshaller.to_yaml(legacy_result.value); - - // Re-parse YAML. - auto yaml_result = parse_content(yaml, "ssl_multicert.yaml"); - REQUIRE(yaml_result.ok()); - - // Verify same number of entries. - REQUIRE(legacy_result.value.size() == yaml_result.value.size()); - - // Verify entries match. - for (size_t i = 0; i < legacy_result.value.size(); ++i) { - CHECK(legacy_result.value[i].ssl_cert_name == yaml_result.value[i].ssl_cert_name); - CHECK(legacy_result.value[i].ssl_key_name == yaml_result.value[i].ssl_key_name); - CHECK(legacy_result.value[i].ssl_key_dialog == yaml_result.value[i].ssl_key_dialog); - } -} - -TEST_CASE("SSLMultiCertParser::parse returns error for missing file", "[ssl_multicert][parser][file]") -{ - SSLMultiCertParser parser; - - auto result = parser.parse("/nonexistent/path/to/ssl_multicert.yaml"); - CHECK_FALSE(result.ok()); -} - -// ============================================================================ -// Legacy format edge cases (parameterized) -// ============================================================================ - -TEST_CASE("Legacy parser handles whitespace variations", "[ssl_multicert][parser][legacy][whitespace]") -{ - auto [description, config, expected_cert, expected_key] = GENERATE(table({ - {"multiple spaces between pairs", "ssl_cert_name=a.pem ssl_key_name=a.key", "a.pem", "a.key"}, - {"tabs between pairs", "ssl_cert_name=a.pem\tssl_key_name=a.key", "a.pem", "a.key"}, - {"leading whitespace", " ssl_cert_name=a.pem ssl_key_name=a.key", "a.pem", "a.key"}, - {"trailing whitespace", "ssl_cert_name=a.pem ssl_key_name=a.key ", "a.pem", "a.key"}, - {"leading tabs", "\t\tssl_cert_name=a.pem ssl_key_name=a.key", "a.pem", "a.key"}, - {"mixed leading whitespace", " \t ssl_cert_name=a.pem ssl_key_name=a.key", "a.pem", "a.key"}, - })); - - CAPTURE(description, config); - - auto result = parse_content(config, "test.config"); - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 1); - CHECK(result.value[0].ssl_cert_name == expected_cert); - CHECK(result.value[0].ssl_key_name == expected_key); -} - -TEST_CASE("Legacy parser handles quoted values", "[ssl_multicert][parser][legacy][quotes]") -{ - auto [description, config, expected_field, expected_value] = - GENERATE(table({ - {"double-quoted with spaces", R"(ssl_cert_name="path with spaces.pem")", "ssl_cert_name", "path with spaces.pem"}, - {"single-quoted with spaces", R"(ssl_cert_name='path with spaces.pem')", "ssl_cert_name", "path with spaces.pem"}, - {"quoted followed by unquoted", R"(ssl_key_dialog="exec:/bin/script arg" ssl_cert_name=c.pem)", "ssl_key_dialog", - "exec:/bin/script arg" }, - {"IPv6 in quotes", R"(dest_ip="[::1]:443" ssl_cert_name=cert.pem)", "dest_ip", "[::1]:443" }, - {"equals inside quotes", R"(ssl_cert_name="value=with=equals")", "ssl_cert_name", "value=with=equals" }, - })); - - CAPTURE(description, config); - - auto result = parse_content(config, "test.config"); - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 1); - - auto const &entry = result.value[0]; - if (expected_field == std::string("ssl_cert_name")) { - CHECK(entry.ssl_cert_name == expected_value); - } else if (expected_field == std::string("ssl_key_dialog")) { - CHECK(entry.ssl_key_dialog == expected_value); - } else if (expected_field == std::string("dest_ip")) { - CHECK(entry.dest_ip == expected_value); - } -} - -TEST_CASE("Legacy parser handles multiline content", "[ssl_multicert][parser][legacy][multiline]") -{ - auto [description, config, expected_count] = GENERATE(table({ - {"three entries", "ssl_cert_name=first.pem\nssl_cert_name=second.pem\nssl_cert_name=third.pem", 3}, - {"with comments and blanks", "# Header\nssl_cert_name=first.pem\n\n# Comment\nssl_cert_name=second.pem\n", 2}, - {"Windows CRLF line endings", "ssl_cert_name=first.pem\r\nssl_cert_name=second.pem\r\n", 2}, - {"single line no newline", "ssl_cert_name=only.pem", 1}, - {"single line with newline", "ssl_cert_name=only.pem\n", 1}, - })); - - CAPTURE(description); - - auto result = parse_content(config, "test.config"); - REQUIRE(result.ok()); - CHECK(result.value.size() == expected_count); -} - -TEST_CASE("Legacy parser handles all field types", "[ssl_multicert][parser][legacy][fields]") -{ - static constexpr char FULL_LEGACY[] = "ssl_cert_name=cert.pem dest_ip=192.168.1.1 ssl_key_name=key.pem ssl_ca_name=ca.pem " - "ssl_ocsp_name=ocsp.der ssl_key_dialog=builtin dest_fqdn=example.com action=tunnel " - "ssl_ticket_enabled=1 ssl_ticket_number=5"; - - auto result = parse_content(FULL_LEGACY, "test.config"); - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 1); - - auto const &entry = result.value[0]; - CHECK(entry.ssl_cert_name == "cert.pem"); - CHECK(entry.dest_ip == "192.168.1.1"); - CHECK(entry.ssl_key_name == "key.pem"); - CHECK(entry.ssl_ca_name == "ca.pem"); - CHECK(entry.ssl_ocsp_name == "ocsp.der"); - CHECK(entry.ssl_key_dialog == "builtin"); - CHECK(entry.dest_fqdn == "example.com"); - CHECK(entry.action == "tunnel"); - REQUIRE(entry.ssl_ticket_enabled.has_value()); - CHECK(entry.ssl_ticket_enabled.value() == 1); - REQUIRE(entry.ssl_ticket_number.has_value()); - CHECK(entry.ssl_ticket_number.value() == 5); -} - -TEST_CASE("Legacy parser handles dual certificates", "[ssl_multicert][parser][legacy][dual-cert]") -{ - auto result = - parse_content("ssl_cert_name=server-ec.pem,server-rsa.pem ssl_key_name=server-ec.key,server-rsa.key", "test.config"); - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 1); - CHECK(result.value[0].ssl_cert_name == "server-ec.pem,server-rsa.pem"); - CHECK(result.value[0].ssl_key_name == "server-ec.key,server-rsa.key"); -} - -TEST_CASE("Legacy parser skips malformed entries", "[ssl_multicert][parser][legacy][malformed]") -{ - auto [description, config, expected_count] = GENERATE(table({ - {"line without equals", "ssl_cert_name=valid.pem\nmalformed_no_equals\nssl_cert_name=another.pem", 2}, - {"blank line between entries", "ssl_cert_name=first.pem\n\nssl_cert_name=second.pem", 2}, - {"comment before valid entry", "# only comment\nssl_cert_name=valid.pem", 1}, - })); - - CAPTURE(description); - - auto result = parse_content(config, "test.config"); - REQUIRE(result.ok()); - CHECK(result.value.size() == expected_count); -} diff --git a/src/config/unit_tests/test_storage.cc b/src/config/unit_tests/test_storage.cc deleted file mode 100644 index 4125583b8a9..00000000000 --- a/src/config/unit_tests/test_storage.cc +++ /dev/null @@ -1,870 +0,0 @@ -/** @file - - Unit tests for storage configuration parsing and marshalling. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include "config/storage.h" - -#include -#include -#include - -#include - -#include "swoc/Errata.h" -#include "tsutil/ts_diag_levels.h" - -using namespace config; - -namespace -{ - -class TempFile -{ -public: - TempFile(std::string const &filename, std::string const &content) - { - _path = std::filesystem::temp_directory_path() / filename; - std::ofstream ofs(_path); - ofs << content; - } - - ~TempFile() { std::filesystem::remove(_path); } - - std::string - path() const - { - return _path.string(); - } - -private: - std::filesystem::path _path; -}; - -ConfigResult -parse_file(std::string const &content, std::string const &filename = "storage.yaml") -{ - TempFile file(filename, content); - StorageParser parser; - return parser.parse(file.path()); -} - -} // namespace - -// ============================================================================ -// Spans-only configuration -// ============================================================================ - -static constexpr char SPANS_ONLY_YAML[] = R"(cache: - spans: - - name: span-1 - path: /var/cache/span1 - size: 10G - - name: span-2 - path: /var/cache/span2 - hash_seed: myseed -)"; - -TEST_CASE("StorageParser parses spans-only config", "[storage][parser][spans]") -{ - auto result = parse_file(SPANS_ONLY_YAML); - - REQUIRE(result.ok()); - REQUIRE(result.value.spans.size() == 2); - CHECK(result.value.volumes.empty()); - - SECTION("First span") - { - auto const &span = result.value.spans[0]; - CHECK(span.name == "span-1"); - CHECK(span.path == "/var/cache/span1"); - CHECK(span.size == 10LL * 1024 * 1024 * 1024); - CHECK(span.hash_seed.empty()); - } - - SECTION("Second span with hash_seed") - { - auto const &span = result.value.spans[1]; - CHECK(span.name == "span-2"); - CHECK(span.path == "/var/cache/span2"); - CHECK(span.hash_seed == "myseed"); - } -} - -// ============================================================================ -// Volumes-only configuration -// ============================================================================ - -static constexpr char VOLUMES_ONLY_YAML[] = R"(cache: - spans: - - name: span-1 - path: /dev/sdb - volumes: - - id: 1 - scheme: http - size: 60% - - id: 2 - size: 40% -)"; - -TEST_CASE("StorageParser parses volumes config", "[storage][parser][volumes]") -{ - auto result = parse_file(VOLUMES_ONLY_YAML); - - REQUIRE(result.ok()); - REQUIRE(result.value.volumes.size() == 2); - - SECTION("First volume") - { - auto const &vol = result.value.volumes[0]; - CHECK(vol.id == 1); - CHECK(vol.scheme == "http"); - CHECK(vol.size.in_percent); - CHECK(vol.size.percent == 60); - CHECK(vol.ram_cache); - } - - SECTION("Second volume") - { - auto const &vol = result.value.volumes[1]; - CHECK(vol.id == 2); - CHECK(vol.size.in_percent); - CHECK(vol.size.percent == 40); - } -} - -// ============================================================================ -// Full configuration (spans + volumes with span refs) -// ============================================================================ - -static constexpr char FULL_YAML[] = R"(cache: - spans: - - name: span-1 - path: /dev/sdb - - name: span-2 - path: /dev/sdc - hash_seed: abc123 - volumes: - - id: 1 - scheme: http - size: 50% - ram_cache: true - ram_cache_size: 1G - ram_cache_cutoff: 256K - avg_obj_size: 8K - fragment_size: 512K - - id: 2 - spans: - - use: span-1 - size: 75% - - use: span-2 -)"; - -TEST_CASE("StorageParser parses full spans+volumes config", "[storage][parser][full]") -{ - auto result = parse_file(FULL_YAML); - - REQUIRE(result.ok()); - REQUIRE(result.value.spans.size() == 2); - REQUIRE(result.value.volumes.size() == 2); - - SECTION("Span with hash_seed") - { - CHECK(result.value.spans[1].hash_seed == "abc123"); - } - - SECTION("Volume 1 fields") - { - auto const &vol = result.value.volumes[0]; - CHECK(vol.id == 1); - CHECK(vol.size.in_percent); - CHECK(vol.size.percent == 50); - CHECK(vol.ram_cache); - CHECK(vol.ram_cache_size == 1LL * 1024 * 1024 * 1024); - CHECK(vol.ram_cache_cutoff == 256LL * 1024); - CHECK(vol.avg_obj_size == 8 * 1024); - CHECK(vol.fragment_size == 512 * 1024); - } - - SECTION("Volume 2 with span refs") - { - auto const &vol = result.value.volumes[1]; - CHECK(vol.id == 2); - REQUIRE(vol.spans.size() == 2); - CHECK(vol.spans[0].use == "span-1"); - CHECK(vol.spans[0].size.in_percent); - CHECK(vol.spans[0].size.percent == 75); - CHECK(vol.spans[1].use == "span-2"); - } -} - -// ============================================================================ -// Error cases -// ============================================================================ - -TEST_CASE("StorageParser returns error for duplicate volume id", "[storage][parser][error]") -{ - static constexpr char YAML[] = R"(cache: - spans: - - name: span-1 - path: /dev/sdb - volumes: - - id: 1 - - id: 1 -)"; - auto result = parse_file(YAML); - CHECK_FALSE(result.ok()); -} - -TEST_CASE("StorageParser returns error when percent total exceeds 100", "[storage][parser][error]") -{ - static constexpr char YAML[] = R"(cache: - spans: - - name: span-1 - path: /dev/sdb - volumes: - - id: 1 - size: 70% - - id: 2 - size: 40% -)"; - auto result = parse_file(YAML); - CHECK_FALSE(result.ok()); -} - -TEST_CASE("StorageParser returns error for missing file", "[storage][parser][error]") -{ - StorageParser parser; - auto result = parser.parse("/nonexistent/path/to/storage.yaml"); - CHECK_FALSE(result.ok()); -} - -TEST_CASE("StorageParser returns error for missing top-level cache key", "[storage][parser][error]") -{ - auto result = parse_file("spans:\n - name: x\n path: /tmp\n"); - CHECK_FALSE(result.ok()); -} - -TEST_CASE("StorageParser returns error for invalid YAML syntax", "[storage][parser][error]") -{ - auto result = parse_file("cache: [not: valid: yaml"); - CHECK_FALSE(result.ok()); -} - -TEST_CASE("StorageParser warns on unknown keys", "[storage][parser][warning]") -{ - // Set FAILURE_SEVERITY to match ATS production (DL_Warning), so DL_Note - // annotations do not make is_ok() return false. - swoc::Errata::FAILURE_SEVERITY = swoc::Errata::Severity{static_cast(DL_Warning)}; - - static constexpr char YAML[] = R"(cache: - spans: - - name: span-1 - path: /dev/sdb - unknown_key: value -)"; - auto result = parse_file(YAML); - // Parse succeeds (unknown key is a note not an error). - CHECK(result.ok()); - CHECK(result.value.spans.size() == 1); - // Errata should contain a note about the unknown key. - CHECK_FALSE(result.errata.empty()); - - // Restore default so other tests are unaffected. - swoc::Errata::FAILURE_SEVERITY = swoc::Errata::Severity{2}; -} - -// ============================================================================ -// parse_content (in-memory) -// ============================================================================ - -TEST_CASE("StorageParser::parse_content works without file I/O", "[storage][parser][content]") -{ - StorageParser parser; - auto result = parser.parse_content(SPANS_ONLY_YAML); - - REQUIRE(result.ok()); - REQUIRE(result.value.spans.size() == 2); -} - -// ============================================================================ -// Marshaller -// ============================================================================ - -// ============================================================================ -// Legacy storage.config parser -// ============================================================================ - -TEST_CASE("StorageParser parses legacy storage.config", "[storage][legacy][storage_config]") -{ - static constexpr char STORAGE_CONFIG[] = "# comment line\n" - "/dev/sda\n" - "/dev/sdb 10737418240\n" // 10 GB in bytes - "/var/cache/disk3 id=myseed\n" - "/dev/sdc 5368709120 volume=2\n" // 5 GB, assigned to volume 2 - "\n" - "# another comment\n" - "/dev/sdd volume=1\n"; - - TempFile file("storage.config", STORAGE_CONFIG); - StorageParser parser; - auto result = parser.parse(file.path()); - - REQUIRE(result.ok()); - REQUIRE(result.value.spans.size() == 5); - - SECTION("plain path uses path as name") - { - CHECK(result.value.spans[0].name == "/dev/sda"); - CHECK(result.value.spans[0].path == "/dev/sda"); - CHECK(result.value.spans[0].size == 0); - CHECK(result.value.spans[0].hash_seed.empty()); - } - - SECTION("path with size") - { - CHECK(result.value.spans[1].path == "/dev/sdb"); - CHECK(result.value.spans[1].size == 10737418240LL); - } - - SECTION("path with hash seed") - { - CHECK(result.value.spans[2].path == "/var/cache/disk3"); - CHECK(result.value.spans[2].hash_seed == "myseed"); - } - - SECTION("spans with volume annotations produce volume entries") - { - REQUIRE(result.value.volumes.size() == 2); - - // volumes are stored in map-order (id=1 before id=2) - bool found_vol1 = false; - bool found_vol2 = false; - for (auto const &vol : result.value.volumes) { - if (vol.id == 1) { - found_vol1 = true; - REQUIRE(vol.spans.size() == 1); - CHECK(vol.spans[0].use == "/dev/sdd"); - } else if (vol.id == 2) { - found_vol2 = true; - REQUIRE(vol.spans.size() == 1); - CHECK(vol.spans[0].use == "/dev/sdc"); - } - } - CHECK(found_vol1); - CHECK(found_vol2); - } -} - -TEST_CASE("StorageParser::parse_legacy_storage_content parses basic lines", "[storage][legacy][storage_config][content]") -{ - static constexpr char CONTENT[] = "/path/to/disk1\n" - "/path/to/disk2 1073741824\n"; // 1 GB - - StorageParser parser; - auto result = parser.parse_legacy_storage_content(CONTENT); - - REQUIRE(result.ok()); - REQUIRE(result.value.spans.size() == 2); - CHECK(result.value.spans[0].path == "/path/to/disk1"); - CHECK(result.value.spans[1].size == 1073741824LL); -} - -TEST_CASE("StorageParser: single span exclusively assigned to one volume", "[storage][legacy][storage_config][volume]") -{ - static constexpr char CONTENT[] = "/dev/sda volume=1\n"; - - StorageParser parser; - auto result = parser.parse_legacy_storage_content(CONTENT); - - REQUIRE(result.ok()); - REQUIRE(result.value.spans.size() == 1); - REQUIRE(result.value.volumes.size() == 1); - CHECK(result.value.volumes[0].id == 1); - REQUIRE(result.value.volumes[0].spans.size() == 1); - CHECK(result.value.volumes[0].spans[0].use == "/dev/sda"); -} - -TEST_CASE("StorageParser: multiple spans exclusively assigned to different volumes", "[storage][legacy][storage_config][volume]") -{ - static constexpr char CONTENT[] = "/dev/sda volume=1\n" - "/dev/sdb volume=2\n" - "/dev/sdc volume=1\n"; // second span for volume 1 - - StorageParser parser; - auto result = parser.parse_legacy_storage_content(CONTENT); - - REQUIRE(result.ok()); - REQUIRE(result.value.spans.size() == 3); - REQUIRE(result.value.volumes.size() == 2); - - // volumes are in map order (id=1 before id=2) - auto const &vol1 = result.value.volumes[0]; - REQUIRE(vol1.id == 1); - REQUIRE(vol1.spans.size() == 2); - CHECK(vol1.spans[0].use == "/dev/sda"); - CHECK(vol1.spans[1].use == "/dev/sdc"); - - auto const &vol2 = result.value.volumes[1]; - REQUIRE(vol2.id == 2); - REQUIRE(vol2.spans.size() == 1); - CHECK(vol2.spans[0].use == "/dev/sdb"); -} - -TEST_CASE("StorageParser: unassigned spans produce no volume entries", "[storage][legacy][storage_config][volume]") -{ - // No volume= annotations anywhere - no volumes should be created. - static constexpr char CONTENT[] = "/dev/sda\n" - "/dev/sdb 10737418240\n"; - - StorageParser parser; - auto result = parser.parse_legacy_storage_content(CONTENT); - - REQUIRE(result.ok()); - CHECK(result.value.spans.size() == 2); - CHECK(result.value.volumes.empty()); -} - -TEST_CASE("StorageParser: mix of assigned and unassigned spans", "[storage][legacy][storage_config][volume]") -{ - // /dev/sda and /dev/sdc are not assigned to any volume. - // /dev/sdb is exclusively assigned to volume 3. - static constexpr char CONTENT[] = "/dev/sda\n" - "/dev/sdb volume=3\n" - "/dev/sdc\n"; - - StorageParser parser; - auto result = parser.parse_legacy_storage_content(CONTENT); - - REQUIRE(result.ok()); - REQUIRE(result.value.spans.size() == 3); - REQUIRE(result.value.volumes.size() == 1); - CHECK(result.value.volumes[0].id == 3); - REQUIRE(result.value.volumes[0].spans.size() == 1); - CHECK(result.value.volumes[0].spans[0].use == "/dev/sdb"); -} - -TEST_CASE("StorageParser: volume=N with size and id= on the same line", "[storage][legacy][storage_config][volume]") -{ - static constexpr char CONTENT[] = "/dev/sda 5368709120 id=myseed volume=2\n"; - - StorageParser parser; - auto result = parser.parse_legacy_storage_content(CONTENT); - - REQUIRE(result.ok()); - REQUIRE(result.value.spans.size() == 1); - CHECK(result.value.spans[0].size == 5368709120LL); - CHECK(result.value.spans[0].hash_seed == "myseed"); - REQUIRE(result.value.volumes.size() == 1); - CHECK(result.value.volumes[0].id == 2); - CHECK(result.value.volumes[0].spans[0].use == "/dev/sda"); -} - -TEST_CASE("StorageParser: volume= id and volume= annotations in any order", "[storage][legacy][storage_config][volume]") -{ - // volume= before id= - order on the line should not matter. - static constexpr char CONTENT[] = "/dev/sda volume=5 id=seed1\n"; - - StorageParser parser; - auto result = parser.parse_legacy_storage_content(CONTENT); - - REQUIRE(result.ok()); - REQUIRE(result.value.spans.size() == 1); - CHECK(result.value.spans[0].hash_seed == "seed1"); - REQUIRE(result.value.volumes.size() == 1); - CHECK(result.value.volumes[0].id == 5); -} - -// ============================================================================ -// merge_legacy_storage_configs -// ============================================================================ - -TEST_CASE("merge_legacy_storage_configs: volume=N spans get size/scheme from volume.config", "[storage][legacy][merge]") -{ - // storage.config: two spans, each exclusively assigned to a volume. - static constexpr char STORAGE[] = "/dev/sda volume=1\n" - "/dev/sdb volume=2\n"; - - // volume.config: two volumes with explicit size and scheme. - static constexpr char VOLUMES[] = "volume=1 scheme=http size=60%\n" - "volume=2 scheme=http size=40%\n"; - - StorageParser storage_parser; - auto storage_result = storage_parser.parse_legacy_storage_content(STORAGE); - REQUIRE(storage_result.ok()); - - VolumeParser volume_parser; - auto volume_result = volume_parser.parse_content(VOLUMES); - REQUIRE(volume_result.ok()); - - StorageConfig merged = merge_legacy_storage_configs(storage_result.value, volume_result.value); - - REQUIRE(merged.spans.size() == 2); - REQUIRE(merged.volumes.size() == 2); - - // Volume 1: size from volume.config, span ref from storage.config. - auto const &vol1 = merged.volumes[0]; - CHECK(vol1.id == 1); - CHECK(vol1.size.in_percent); - CHECK(vol1.size.percent == 60); - REQUIRE(vol1.spans.size() == 1); - CHECK(vol1.spans[0].use == "/dev/sda"); - - // Volume 2: size from volume.config, span ref from storage.config. - auto const &vol2 = merged.volumes[1]; - CHECK(vol2.id == 2); - CHECK(vol2.size.in_percent); - CHECK(vol2.size.percent == 40); - REQUIRE(vol2.spans.size() == 1); - CHECK(vol2.spans[0].use == "/dev/sdb"); -} - -TEST_CASE("merge_legacy_storage_configs: multiple spans assigned to the same volume", "[storage][legacy][merge]") -{ - // /dev/sda and /dev/sdc both go to volume 1. - static constexpr char STORAGE[] = "/dev/sda volume=1\n" - "/dev/sdb volume=2\n" - "/dev/sdc volume=1\n"; - - static constexpr char VOLUMES[] = "volume=1 scheme=http size=60%\n" - "volume=2 scheme=http size=40%\n"; - - StorageParser storage_parser; - auto storage_result = storage_parser.parse_legacy_storage_content(STORAGE); - REQUIRE(storage_result.ok()); - - VolumeParser volume_parser; - auto volume_result = volume_parser.parse_content(VOLUMES); - REQUIRE(volume_result.ok()); - - StorageConfig merged = merge_legacy_storage_configs(storage_result.value, volume_result.value); - - REQUIRE(merged.volumes.size() == 2); - - auto const &vol1 = merged.volumes[0]; - CHECK(vol1.id == 1); - REQUIRE(vol1.spans.size() == 2); - CHECK(vol1.spans[0].use == "/dev/sda"); - CHECK(vol1.spans[1].use == "/dev/sdc"); - - auto const &vol2 = merged.volumes[1]; - CHECK(vol2.id == 2); - REQUIRE(vol2.spans.size() == 1); - CHECK(vol2.spans[0].use == "/dev/sdb"); -} - -TEST_CASE("merge_legacy_storage_configs: unassigned spans are not attached to any volume", "[storage][legacy][merge]") -{ - // /dev/sdb has no volume= annotation. - static constexpr char STORAGE[] = "/dev/sda volume=1\n" - "/dev/sdb\n"; - - static constexpr char VOLUMES[] = "volume=1 scheme=http size=100%\n"; - - StorageParser storage_parser; - auto storage_result = storage_parser.parse_legacy_storage_content(STORAGE); - REQUIRE(storage_result.ok()); - - VolumeParser volume_parser; - auto volume_result = volume_parser.parse_content(VOLUMES); - REQUIRE(volume_result.ok()); - - StorageConfig merged = merge_legacy_storage_configs(storage_result.value, volume_result.value); - - REQUIRE(merged.spans.size() == 2); - REQUIRE(merged.volumes.size() == 1); - - auto const &vol1 = merged.volumes[0]; - CHECK(vol1.id == 1); - REQUIRE(vol1.spans.size() == 1); - CHECK(vol1.spans[0].use == "/dev/sda"); -} - -TEST_CASE("merge_legacy_storage_configs: volume.config volume without matching storage.config annotation has no span refs", - "[storage][legacy][merge]") -{ - // storage.config has no volume= lines; volume.config defines a volume. - // The merged volume should have the size/scheme but empty spans. - static constexpr char STORAGE[] = "/dev/sda\n" - "/dev/sdb\n"; - - static constexpr char VOLUMES[] = "volume=1 scheme=http size=50%\n"; - - StorageParser storage_parser; - auto storage_result = storage_parser.parse_legacy_storage_content(STORAGE); - REQUIRE(storage_result.ok()); - - VolumeParser volume_parser; - auto volume_result = volume_parser.parse_content(VOLUMES); - REQUIRE(volume_result.ok()); - - StorageConfig merged = merge_legacy_storage_configs(storage_result.value, volume_result.value); - - REQUIRE(merged.spans.size() == 2); - REQUIRE(merged.volumes.size() == 1); - CHECK(merged.volumes[0].id == 1); - CHECK(merged.volumes[0].size.percent == 50); - CHECK(merged.volumes[0].spans.empty()); -} - -TEST_CASE("merge_legacy_storage_configs: empty volume.config preserves storage.config partial volumes", "[storage][legacy][merge]") -{ - // When volume.config is absent (empty), keep the partial volumes from storage.config. - static constexpr char STORAGE[] = "/dev/sda volume=1\n"; - - StorageParser storage_parser; - auto storage_result = storage_parser.parse_legacy_storage_content(STORAGE); - REQUIRE(storage_result.ok()); - - StorageConfig empty_volumes; - StorageConfig merged = merge_legacy_storage_configs(storage_result.value, empty_volumes); - - REQUIRE(merged.volumes.size() == 1); - CHECK(merged.volumes[0].id == 1); - REQUIRE(merged.volumes[0].spans.size() == 1); - CHECK(merged.volumes[0].spans[0].use == "/dev/sda"); -} - -TEST_CASE("merge_legacy_storage_configs: volume attributes from volume.config are preserved", "[storage][legacy][merge]") -{ - static constexpr char STORAGE[] = "/dev/sda volume=3\n"; - - static constexpr char VOLUMES[] = "volume=3 scheme=http size=512 " - "avg_obj_size=8192 fragment_size=524288 ramcache=false " - "ram_cache_size=1073741824 ram_cache_cutoff=262144\n"; - - StorageParser storage_parser; - auto storage_result = storage_parser.parse_legacy_storage_content(STORAGE); - REQUIRE(storage_result.ok()); - - VolumeParser volume_parser; - auto volume_result = volume_parser.parse_content(VOLUMES); - REQUIRE(volume_result.ok()); - - StorageConfig merged = merge_legacy_storage_configs(storage_result.value, volume_result.value); - - REQUIRE(merged.volumes.size() == 1); - auto const &vol = merged.volumes[0]; - CHECK(vol.id == 3); - CHECK(vol.size.absolute_value == 512); - CHECK_FALSE(vol.ram_cache); - CHECK(vol.avg_obj_size == 8192); - CHECK(vol.fragment_size == 524288); - CHECK(vol.ram_cache_size == 1073741824LL); - CHECK(vol.ram_cache_cutoff == 262144LL); - REQUIRE(vol.spans.size() == 1); - CHECK(vol.spans[0].use == "/dev/sda"); -} - -// ============================================================================ -// Legacy volume.config parser -// ============================================================================ - -TEST_CASE("VolumeParser parses legacy volume.config", "[storage][legacy][volume_config]") -{ - static constexpr char VOLUME_CONFIG[] = "# comment\n" - "volume=1 scheme=http size=60%\n" - "volume=2 scheme=http size=40%\n"; - - TempFile file("volume.config", VOLUME_CONFIG); - VolumeParser parser; - auto result = parser.parse(file.path()); - - REQUIRE(result.ok()); - REQUIRE(result.value.spans.empty()); - REQUIRE(result.value.volumes.size() == 2); - - SECTION("first volume") - { - auto const &vol = result.value.volumes[0]; - CHECK(vol.id == 1); - CHECK(vol.scheme == "http"); - CHECK(vol.size.in_percent); - CHECK(vol.size.percent == 60); - CHECK(vol.ram_cache); - } - - SECTION("second volume") - { - auto const &vol = result.value.volumes[1]; - CHECK(vol.id == 2); - CHECK(vol.size.in_percent); - CHECK(vol.size.percent == 40); - } -} - -TEST_CASE("VolumeParser::parse_content parses absolute size in MB", "[storage][legacy][volume_config][content]") -{ - static constexpr char CONTENT[] = "volume=1 scheme=http size=1024\n"; // 1024 MB - - VolumeParser parser; - auto result = parser.parse_content(CONTENT); - - REQUIRE(result.ok()); - REQUIRE(result.value.volumes.size() == 1); - CHECK_FALSE(result.value.volumes[0].size.in_percent); - CHECK(result.value.volumes[0].size.absolute_value == 1024); -} - -TEST_CASE("VolumeParser::parse_content parses all fields", "[storage][legacy][volume_config][content]") -{ - static constexpr char CONTENT[] = "volume=3 scheme=http size=512 " - "avg_obj_size=8192 fragment_size=524288 " - "ramcache=false ram_cache_size=1073741824 ram_cache_cutoff=262144\n"; - - VolumeParser parser; - auto result = parser.parse_content(CONTENT); - - REQUIRE(result.ok()); - REQUIRE(result.value.volumes.size() == 1); - - auto const &vol = result.value.volumes[0]; - CHECK(vol.id == 3); - CHECK(vol.size.absolute_value == 512); - CHECK_FALSE(vol.ram_cache); - CHECK(vol.avg_obj_size == 8192); - CHECK(vol.fragment_size == 524288); - CHECK(vol.ram_cache_size == 1073741824LL); - CHECK(vol.ram_cache_cutoff == 262144LL); -} - -TEST_CASE("VolumeParser returns error for duplicate volume number", "[storage][legacy][volume_config][error]") -{ - static constexpr char CONTENT[] = "volume=1 scheme=http size=50%\n" - "volume=1 scheme=http size=30%\n"; - - VolumeParser parser; - auto result = parser.parse_content(CONTENT); - - // Second entry with duplicate id should be skipped; first parsed OK. - // The errata records the duplicate. - CHECK(result.value.volumes.size() == 1); - CHECK_FALSE(result.errata.empty()); -} - -TEST_CASE("VolumeParser returns error when percent total exceeds 100", "[storage][legacy][volume_config][error]") -{ - static constexpr char CONTENT[] = "volume=1 scheme=http size=70%\n" - "volume=2 scheme=http size=40%\n"; - - VolumeParser parser; - auto result = parser.parse_content(CONTENT); - - CHECK_FALSE(result.errata.empty()); -} - -TEST_CASE("VolumeParser returns error for missing file", "[storage][legacy][volume_config][error]") -{ - VolumeParser parser; - auto result = parser.parse("/nonexistent/volume.config"); - CHECK_FALSE(result.ok()); -} - -TEST_CASE("StorageMarshaller produces valid YAML", "[storage][marshaller][yaml]") -{ - StorageConfig config; - - StorageSpanEntry span1; - span1.name = "span-1"; - span1.path = "/var/cache/span1"; - span1.size = 10LL * 1024 * 1024 * 1024; - config.spans.push_back(std::move(span1)); - - StorageVolumeEntry vol1; - vol1.id = 1; - vol1.scheme = "http"; - vol1.size.in_percent = true; - vol1.size.percent = 100; - config.volumes.push_back(std::move(vol1)); - - StorageMarshaller marshaller; - std::string yaml = marshaller.to_yaml(config); - - SECTION("YAML contains expected fields") - { - CHECK(yaml.find("cache:") != std::string::npos); - CHECK(yaml.find("span-1") != std::string::npos); - CHECK(yaml.find("/var/cache/span1") != std::string::npos); - CHECK(yaml.find("id: 1") != std::string::npos); - CHECK(yaml.find("http") != std::string::npos); - } - - SECTION("YAML can be re-parsed") - { - StorageParser parser; - auto result = parser.parse_content(yaml); - REQUIRE(result.ok()); - REQUIRE(result.value.spans.size() == 1); - REQUIRE(result.value.volumes.size() == 1); - CHECK(result.value.spans[0].name == "span-1"); - CHECK(result.value.volumes[0].id == 1); - } -} - -TEST_CASE("StorageMarshaller produces valid JSON", "[storage][marshaller][json]") -{ - StorageConfig config; - - StorageSpanEntry span1; - span1.name = "span-1"; - span1.path = "/var/cache/span1"; - config.spans.push_back(std::move(span1)); - - StorageVolumeEntry vol1; - vol1.id = 1; - config.volumes.push_back(std::move(vol1)); - - StorageMarshaller marshaller; - std::string json = marshaller.to_json(config); - - CHECK(json.find("\"cache\"") != std::string::npos); - CHECK(json.find("\"span-1\"") != std::string::npos); - CHECK(json.find("\"id\"") != std::string::npos); - CHECK(json.find('{') != std::string::npos); - CHECK(json.find('}') != std::string::npos); -} - -TEST_CASE("Round-trip: parse -> marshal -> parse", "[storage][roundtrip]") -{ - StorageParser parser; - StorageMarshaller marshaller; - - auto initial = parser.parse_content(FULL_YAML); - REQUIRE(initial.ok()); - - std::string yaml = marshaller.to_yaml(initial.value); - auto round_trip = parser.parse_content(yaml); - REQUIRE(round_trip.ok()); - - REQUIRE(initial.value.spans.size() == round_trip.value.spans.size()); - REQUIRE(initial.value.volumes.size() == round_trip.value.volumes.size()); - - for (size_t i = 0; i < initial.value.spans.size(); ++i) { - CHECK(initial.value.spans[i].name == round_trip.value.spans[i].name); - CHECK(initial.value.spans[i].path == round_trip.value.spans[i].path); - CHECK(initial.value.spans[i].hash_seed == round_trip.value.spans[i].hash_seed); - } - - for (size_t i = 0; i < initial.value.volumes.size(); ++i) { - CHECK(initial.value.volumes[i].id == round_trip.value.volumes[i].id); - CHECK(initial.value.volumes[i].scheme == round_trip.value.volumes[i].scheme); - } -} diff --git a/src/cripts/CMakeLists.txt b/src/cripts/CMakeLists.txt index 1f08eae2d28..42fc019ce02 100644 --- a/src/cripts/CMakeLists.txt +++ b/src/cripts/CMakeLists.txt @@ -61,7 +61,7 @@ set(CRIPTS_BUNDLE_HEADERS add_library(cripts SHARED ${CPP_FILES}) add_library(ts::cripts ALIAS cripts) -target_link_libraries(cripts PUBLIC libswoc::libswoc OpenSSL::Crypto fmt::fmt PkgConfig::PCRE2 yaml-cpp::yaml-cpp) +target_link_libraries(cripts PUBLIC libswoc::libswoc OpenSSL::Crypto fmt::fmt PkgConfig::PCRE2) set_target_properties(cripts PROPERTIES PUBLIC_HEADER "${CRIPTS_PUBLIC_HEADERS}") set_target_properties( diff --git a/src/iocore/aio/CMakeLists.txt b/src/iocore/aio/CMakeLists.txt index 8040a311189..e6f342b1430 100644 --- a/src/iocore/aio/CMakeLists.txt +++ b/src/iocore/aio/CMakeLists.txt @@ -27,7 +27,7 @@ endif() if(BUILD_TESTING) add_executable(test_AIO test_AIO.cc) - target_link_libraries(test_AIO ts::aio configmanager) + target_link_libraries(test_AIO ts::aio) add_test( NAME test_AIO COMMAND $ diff --git a/src/iocore/cache/CMakeLists.txt b/src/iocore/cache/CMakeLists.txt index f8b3817f722..2775cef4e35 100644 --- a/src/iocore/cache/CMakeLists.txt +++ b/src/iocore/cache/CMakeLists.txt @@ -49,7 +49,7 @@ target_include_directories(inkcache PRIVATE ${CMAKE_SOURCE_DIR}/lib) target_link_libraries( inkcache PUBLIC ts::aio ts::hdrs ts::inkevent ts::tscore - PRIVATE ts::config ts::tsapibackend fastlz ZLIB::ZLIB + PRIVATE ts::tsapibackend fastlz ZLIB::ZLIB ) if(HAVE_LZMA_H) @@ -57,7 +57,6 @@ if(HAVE_LZMA_H) endif() if(BUILD_TESTING) - # Unit Tests with unit_tests/main.cc macro(add_cache_test name) add_executable(${name} unit_tests/main.cc unit_tests/stub.cc unit_tests/CacheTestHandler.cc ${ARGN}) target_link_libraries(${name} PRIVATE ts::inkcache Catch2::Catch2WithMain) @@ -92,11 +91,6 @@ if(BUILD_TESTING) add_cache_test(CacheStripe unit_tests/test_Stripe.cc) add_cache_test(CacheAggregateWriteBuffer unit_tests/test_AggregateWriteBuffer.cc) - # Unit Tests without unit_tests/main.cc - add_executable(test_ConfigVolumes unit_tests/test_ConfigVolumes.cc) - target_include_directories(test_ConfigVolumes PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") - target_link_libraries(test_ConfigVolumes ts::inkcache ts::config Catch2::Catch2WithMain) - add_test(NAME test_ConfigVolumes COMMAND test_ConfigVolumes) endif() clang_tidy_check(inkcache) diff --git a/src/iocore/cache/Cache.cc b/src/iocore/cache/Cache.cc index 69692d890d9..14855e98fab 100644 --- a/src/iocore/cache/Cache.cc +++ b/src/iocore/cache/Cache.cc @@ -31,8 +31,6 @@ #include "Stripe.h" #include "StripeSM.h" #include "iocore/cache/Cache.h" -#include "mgmt/config/ConfigContextDiags.h" -#include "mgmt/config/ConfigRegistry.h" #include "tscore/Filenames.h" #include "tscore/InkErrno.h" #include "tscore/Layout.h" @@ -231,25 +229,7 @@ Cache::open_done() { CacheHostTable *hosttable_raw = new CacheHostTable(this, scheme); hosttable.reset(hosttable_raw); - - auto *ppt = &this->hosttable; - config::ConfigRegistry::Get_Instance().register_config( // late config registration - "cache_hosting", // registry key - ts::filename::HOSTING, // default filename - "proxy.config.cache.hosting_filename", // record holding the filename - [ppt](ConfigContext ctx) { // reload handler - CacheType type = CacheType::HTTP; - Cache *cache = nullptr; - { - ReplaceablePtr::ScopedReader ht(ppt); - type = ht->getType(); - cache = ht->getCache(); - } - ppt->reset(new CacheHostTable(cache, type, ctx)); - CfgLoadComplete(ctx, "%s finished loading", ts::filename::HOSTING); - }, - config::ConfigSource::FileOnly, // no RPC content source. Legacy for now. - {"proxy.config.cache.hosting_filename"}); // trigger records + hosttable_raw->register_config_callback(&hosttable); } ReplaceablePtr::ScopedReader hosttable(&this->hosttable); diff --git a/src/iocore/cache/CacheHosting.cc b/src/iocore/cache/CacheHosting.cc index ce4f9a990bf..9f24fb1a91b 100644 --- a/src/iocore/cache/CacheHosting.cc +++ b/src/iocore/cache/CacheHosting.cc @@ -24,7 +24,7 @@ #include "P_CacheHosting.h" #include "Stripe.h" #include "iocore/cache/CacheDefs.h" -#include "mgmt/config/ConfigContextDiags.h" +#include "iocore/eventsystem/Tasks.h" #include "swoc/swoc_file.h" #include "tscore/HostLookup.h" @@ -32,14 +32,13 @@ #include "tscore/Filenames.h" #include "tsutil/DbgCtl.h" -#include - namespace { DbgCtl dbg_ctl_cache_hosting{"cache_hosting"}; DbgCtl dbg_ctl_matcher{"matcher"}; +constexpr static int MAX_VOLUME_IDX = 255; } // end anonymous namespace /************************************************************* @@ -189,7 +188,7 @@ CacheHostMatcher::NewEntry(matcher_line *line_info) * End class HostMatcher *************************************************************/ -CacheHostTable::CacheHostTable(Cache *c, CacheType typ, ConfigContext ctx) +CacheHostTable::CacheHostTable(Cache *c, CacheType typ) { ats_scoped_str config_path; @@ -200,7 +199,7 @@ CacheHostTable::CacheHostTable(Cache *c, CacheType typ, ConfigContext ctx) config_path = RecConfigReadConfigPath("proxy.config.cache.hosting_filename"); ink_release_assert(config_path); - m_numEntries = this->BuildTable(config_path, ctx); + m_numEntries = this->BuildTable(config_path); } CacheHostTable::~CacheHostTable() {} @@ -229,6 +228,15 @@ CacheHostTable::Match(std::string_view rdata, CacheHostResult *result) const hostMatch->Match(rdata, result); } +int +CacheHostTable::config_callback(const char * /* name ATS_UNUSED */, RecDataT /* data_type ATS_UNUSED */, + RecData /* data ATS_UNUSED */, void *cookie) +{ + ReplaceablePtr *ppt = static_cast *>(cookie); + eventProcessor.schedule_imm(new CacheHostTableConfig(ppt), ET_TASK); + return 0; +} + int fstat_wrapper(int fd, struct stat *s); // int ControlMatcher::BuildTable() { @@ -237,9 +245,9 @@ int fstat_wrapper(int fd, struct stat *s); // from it // int -CacheHostTable::BuildTableFromString(const char *config_file_path, char *file_buf, ConfigContext ctx) +CacheHostTable::BuildTableFromString(const char *config_file_path, char *file_buf) { - CfgLoadLog(ctx, DL_Note, "%s loading ...", ts::filename::HOSTING); + Note("%s loading ...", ts::filename::HOSTING); // Table build locals Tokenizer bufTok("\n"); @@ -261,7 +269,7 @@ CacheHostTable::BuildTableFromString(const char *config_file_path, char *file_bu /* no hosting customers -- put all the volumes in the generic table */ if (gen_host_rec.Init(type)) { - CfgLoadLog(ctx, DL_Warning, "Problems encountered while initializing the Generic Volume"); + Warning("Problems encountered while initializing the Generic Volume"); } return 0; } @@ -281,7 +289,7 @@ CacheHostTable::BuildTableFromString(const char *config_file_path, char *file_bu errPtr = parseConfigLine(const_cast(tmp), current, &config_tags); if (errPtr != nullptr) { - CfgLoadLog(ctx, DL_Warning, "%s discarding %s entry at line %d : %s", matcher_name, config_file_path, line_num, errPtr); + Warning("%s discarding %s entry at line %d : %s", matcher_name, config_file_path, line_num, errPtr); ats_free(current); } else { // Line parsed ok. Figure out what the destination @@ -318,9 +326,9 @@ CacheHostTable::BuildTableFromString(const char *config_file_path, char *file_bu generic table */ if (gen_host_rec.Init(type)) { - CfgLoadLog(ctx, DL_Warning, "Problems encountered while initializing the Generic Volume"); + Warning("Problems encountered while initializing the Generic Volume"); } - CfgLoadLog(ctx, DL_Note, "%s finished loading", ts::filename::HOSTING); + Note("%s finished loading", ts::filename::HOSTING); return 0; } @@ -346,22 +354,21 @@ CacheHostTable::BuildTableFromString(const char *config_file_path, char *file_bu if (current->dest_entry < MATCHER_MAX_TOKENS) { current->line[0][current->dest_entry] = nullptr; } else { - CfgLoadLog(ctx, DL_Warning, "Problems encountered while initializing the Generic Volume"); + Warning("Problems encountered while initializing the Generic Volume"); } current->num_el--; if (!gen_host_rec.Init(current, type)) { generic_rec_initd = 1; } else { - CfgLoadLog(ctx, DL_Warning, "Problems encountered while initializing the Generic Volume"); + Warning("Problems encountered while initializing the Generic Volume"); } } else { hostMatch->NewEntry(current); } } else { - CfgLoadLog(ctx, DL_Warning, "%s discarding %s entry with unknown type at line %d", matcher_name, config_file_path, - current->line_num); + Warning("%s discarding %s entry with unknown type at line %d", matcher_name, config_file_path, current->line_num); } // Deallocate the parsing structure @@ -370,12 +377,11 @@ CacheHostTable::BuildTableFromString(const char *config_file_path, char *file_bu ats_free(last); } - CfgLoadLog(ctx, DL_Note, "%s finished loading", ts::filename::HOSTING); + Note("%s finished loading", ts::filename::HOSTING); if (!generic_rec_initd) { const char *cache_type = (type == CacheType::HTTP) ? "http" : "mixt"; - CfgLoadLog(ctx, DL_Warning, "No Volumes specified for Generic Hostnames for %s documents: %s cache will be disabled", - cache_type, cache_type); + Warning("No Volumes specified for Generic Hostnames for %s documents: %s cache will be disabled", cache_type, cache_type); } ink_assert(second_pass == numEntries); @@ -387,7 +393,7 @@ CacheHostTable::BuildTableFromString(const char *config_file_path, char *file_bu } int -CacheHostTable::BuildTable(const char *config_file_path, ConfigContext ctx) +CacheHostTable::BuildTable(const char *config_file_path) { std::error_code ec; std::string content{swoc::file::load(swoc::file::path{config_file_path}, ec)}; @@ -395,16 +401,16 @@ CacheHostTable::BuildTable(const char *config_file_path, ConfigContext ctx) if (ec) { switch (ec.value()) { case ENOENT: - CfgLoadLog(ctx, DL_Warning, "Cannot open the config file: %s - %s", config_file_path, strerror(ec.value())); + Warning("Cannot open the config file: %s - %s", config_file_path, strerror(ec.value())); break; default: - CfgLoadFail(ctx, "%s failed to load: %s", config_file_path, strerror(ec.value())); + Error("%s failed to load: %s", config_file_path, strerror(ec.value())); gen_host_rec.Init(type); return 0; } } - return BuildTableFromString(config_file_path, content.data(), ctx); + return BuildTableFromString(config_file_path, content.data()); } int @@ -579,6 +585,285 @@ CacheHostRecord::Print() const { } +void +ConfigVolumes::read_config_file() +{ + ats_scoped_str config_path; + + config_path = RecConfigReadConfigPath("proxy.config.cache.volume_filename"); + ink_release_assert(config_path); + + Note("%s loading ...", ts::filename::VOLUME); + + std::error_code ec; + std::string content{swoc::file::load(swoc::file::path{config_path}, ec)}; + + if (ec) { + switch (ec.value()) { + case ENOENT: + Warning("Cannot open the config file: %s - %s", config_path.get(), strerror(ec.value())); + break; + default: + Error("%s failed to load: %s", config_path.get(), strerror(ec.value())); + return; + } + } + + BuildListFromString(config_path, content.data()); + Note("volume.config finished loading"); + + return; +} + +void +ConfigVolumes::BuildListFromString(char *config_file_path, char *file_buf) +{ + // Table build locals + Tokenizer bufTok("\n"); + tok_iter_state i_state; + const char *tmp; + int line_num = 0; + int total = 0; // added by YTS Team, yamsat for bug id 59632 + + char volume_seen[256]; + const char *matcher_name = "[CacheVolition]"; + + memset(volume_seen, 0, sizeof(volume_seen)); + num_volumes = 0; + num_http_volumes = 0; + + if (bufTok.Initialize(file_buf, SHARE_TOKS | ALLOW_EMPTY_TOKS) == 0) { + // We have an empty file + /* no volumes */ + return; + } + + // First get the number of entries + tmp = bufTok.iterFirst(&i_state); + while (tmp != nullptr) { + line_num++; + + char *end; + char *line_end = nullptr; + const char *err = nullptr; + int volume_number = 0; + CacheType scheme = CacheType::NONE; + int size = 0; + int in_percent = 0; + bool ramcache_enabled = true; + int avg_obj_size = -1; // Defaults + int fragment_size = -1; + int64_t ram_cache_size = -1; // -1 means use shared allocation + int64_t ram_cache_cutoff = -1; // -1 means use global cutoff + + while (true) { + // skip all blank spaces at beginning of line + while (*tmp && isspace(*tmp)) { + tmp++; + } + + if (*tmp == '\0' || *tmp == '#') { + break; + } else if (!(*tmp)) { + err = "Unexpected end of line"; + break; + } + + end = const_cast(tmp); + while (*end && !isspace(*end)) { + end++; + } + + if (!(*end)) { + line_end = end; + } else { + line_end = end + 1; + *end = '\0'; + } + char *eq_sign; + + eq_sign = const_cast(strchr(tmp, '=')); + if (!eq_sign) { + err = "Unexpected end of line"; + break; + } else { + *eq_sign = '\0'; + } + + if (strcasecmp(tmp, "volume") == 0) { // match volume + tmp += 7; // size of string volume including null + volume_number = atoi(tmp); + + if (volume_seen[volume_number]) { + err = "Volume Already Specified"; + break; + } + + if (volume_number < 1 || volume_number > MAX_VOLUME_IDX) { + err = "Bad Volume Number"; + break; + } + + volume_seen[volume_number] = 1; + while (ParseRules::is_digit(*tmp)) { + tmp++; + } + } else if (strcasecmp(tmp, "scheme") == 0) { // match scheme + tmp += 7; // size of string scheme including null + + if (!strcasecmp(tmp, "http")) { + tmp += 4; + scheme = CacheType::HTTP; + } else if (!strcasecmp(tmp, "mixt")) { + tmp += 4; + scheme = CacheType::RTSP; + } else { + err = "Unexpected end of line"; + break; + } + } else if (strcasecmp(tmp, "size") == 0) { // match size + tmp += 5; + size = atoi(tmp); + + while (ParseRules::is_digit(*tmp)) { + tmp++; + } + + if (*tmp == '%') { + // added by YTS Team, yamsat for bug id 59632 + total += size; + if (size > 100 || total > 100) { + err = "Total volume size added up to more than 100 percent, No volumes created"; + break; + } + // ends here + in_percent = 1; + tmp++; + } else { + in_percent = 0; + } + } else if (strcasecmp(tmp, "avg_obj_size") == 0) { // match avg_obj_size + tmp += 13; + if (!ParseRules::is_digit(*tmp)) { + err = "Invalid avg_obj_size value (must start with a number, e.g., 64K)"; + break; + } + avg_obj_size = static_cast(ink_atoi64(tmp)); + + if (avg_obj_size < 0) { + err = "Invalid avg_obj_size value (must be >= 0)"; + break; + } + while (*tmp && (ParseRules::is_digit(*tmp) || strchr("KMGT", *tmp))) { + tmp++; + } + } else if (strcasecmp(tmp, "fragment_size") == 0) { // match fragment_size + tmp += 14; + if (!ParseRules::is_digit(*tmp)) { + err = "Invalid fragment_size value (must start with a number, e.g., 1M)"; + break; + } + fragment_size = static_cast(ink_atoi64(tmp)); + + if (fragment_size < 0) { + err = "Invalid fragment_size value (must be >= 0)"; + break; + } + while (*tmp && (ParseRules::is_digit(*tmp) || strchr("KMGT", *tmp))) { + tmp++; + } + } else if (strcasecmp(tmp, "ramcache") == 0) { // match ramcache + tmp += 9; + if (!strcasecmp(tmp, "false")) { + tmp += 5; + ramcache_enabled = false; + } else if (!strcasecmp(tmp, "true")) { + tmp += 4; + ramcache_enabled = true; + } else { + err = "Unexpected end of line"; + break; + } + } else if (strcasecmp(tmp, "ram_cache_size") == 0) { // match ram_cache_size + tmp += 15; + if (!ParseRules::is_digit(*tmp)) { + err = "Invalid ram_cache_size value (must start with a number, e.g., 10G)"; + break; + } + ram_cache_size = ink_atoi64(tmp); + + if (ram_cache_size < 0) { + err = "Invalid ram_cache_size value (must be >= 0)"; + break; + } + // Note: ram_cache_size=0 disables RAM cache for this volume, same as ramcache=false + while (*tmp && (ParseRules::is_digit(*tmp) || strchr("KMGT", *tmp))) { + tmp++; + } + } else if (strcasecmp(tmp, "ram_cache_cutoff") == 0) { // match ram_cache_cutoff + tmp += 17; + if (!ParseRules::is_digit(*tmp)) { + err = "Invalid ram_cache_cutoff value (must start with a number, e.g., 5M)"; + break; + } + ram_cache_cutoff = ink_atoi64(tmp); + + if (ram_cache_cutoff < 0) { + err = "Invalid ram_cache_cutoff value (must be >= 0)"; + break; + } + while (*tmp && (ParseRules::is_digit(*tmp) || strchr("KMGT", *tmp))) { + tmp++; + } + } + + // ends here + if (end < line_end) { + tmp = line_end; + } + } + + if (err) { + Warning("%s discarding %s entry at line %d : %s", matcher_name, config_file_path, line_num, err); + } else if (volume_number && size && scheme != CacheType::NONE) { + /* add the config */ + + ConfigVol *configp = new ConfigVol(); + + configp->number = volume_number; + if (in_percent) { + configp->percent = size; + configp->in_percent = true; + } else { + configp->in_percent = false; + } + configp->scheme = scheme; + configp->size = size; + configp->avg_obj_size = avg_obj_size; + configp->fragment_size = fragment_size; + configp->ram_cache_size = ram_cache_size; + configp->ram_cache_cutoff = ram_cache_cutoff; + configp->cachep = nullptr; + configp->ramcache_enabled = ramcache_enabled; + cp_queue.enqueue(configp); + num_volumes++; + if (scheme == CacheType::HTTP) { + num_http_volumes++; + } else { + ink_release_assert(!"Unexpected non-HTTP cache volume"); + } + Dbg(dbg_ctl_cache_hosting, + "added volume=%d, scheme=%d, size=%d percent=%d, ramcache enabled=%d, " + "ram_cache_size=%" PRId64 ", ram_cache_cutoff=%" PRId64, + volume_number, static_cast(scheme), size, in_percent, ramcache_enabled, ram_cache_size, ram_cache_cutoff); + } + + tmp = bufTok.iterNext(&i_state); + } + + return; +} + // Wrapper function for deleting CacheHostRecord from outside the cache module. void destroyCacheHostRecord(CacheHostRecord *rec) @@ -624,87 +909,3 @@ createCacheHostRecord(const char *volume_str, char *errbuf, size_t errbufsize) return host_rec; } - -bool -ConfigVol::Size::is_empty() const -{ - return absolute_value == 0 && !in_percent && percent == 0; -} - -/** - Complement missing volumes[].size and volumes[].spans[].size if there - */ -void -ConfigVolumes::complement() -{ - struct SizeTracker { - int empty_node = 0; - int remaining_size = 100; ///< in percentage - }; - - SizeTracker volume_tracker; - std::unordered_map span_size_map; - - // Find missing size and remaining size in percentage - for (ConfigVol *conf = cp_queue.head; conf != nullptr; conf = cp_queue.next(conf)) { - if (!conf->spans.empty()) { - for (const auto &span : conf->spans) { - SizeTracker span_size; - - if (auto it = span_size_map.find(span.use); it != span_size_map.end()) { - span_size = it->second; - } - - if (span.size.is_empty()) { - span_size.empty_node++; - } else if (span.size.in_percent) { - if (span_size.remaining_size < span.size.percent) { - Error("Total volume size (in percent) exceeded 100%% - span.use=%s", span.use.c_str()); - continue; - } - - span_size.remaining_size -= span.size.percent; - } - - span_size_map.insert_or_assign(span.use, span_size); - } - } else { - if (conf->size.is_empty()) { - ++volume_tracker.empty_node; - } else if (conf->size.in_percent) { - if (volume_tracker.remaining_size < conf->size.percent) { - Error("Total volume size (in percent) exceeded 100%%"); - continue; - } - volume_tracker.remaining_size -= conf->size.percent; - } - } - } - - // If there're no missing size, do nothing - if (volume_tracker.empty_node == 0 && - std::all_of(span_size_map.cbegin(), span_size_map.cend(), [](auto &it) { return it.second.empty_node == 0; })) { - return; - } - - // Set size - for (ConfigVol *conf = cp_queue.head; conf != nullptr; conf = cp_queue.next(conf)) { - if (!conf->spans.empty()) { - for (auto &span : conf->spans) { - if (span.size.is_empty()) { - const SizeTracker &tracker = span_size_map[span.use]; - if (tracker.empty_node == 0) { - continue; - } - span.size.in_percent = true; - span.size.percent = tracker.remaining_size / tracker.empty_node; - } - } - } else { - if (conf->size.is_empty() && volume_tracker.empty_node != 0) { - conf->size.in_percent = true; - conf->size.percent = volume_tracker.remaining_size / volume_tracker.empty_node; - } - } - } -} diff --git a/src/iocore/cache/CacheProcessor.cc b/src/iocore/cache/CacheProcessor.cc index ffff468ef78..fe5b9cb77e9 100644 --- a/src/iocore/cache/CacheProcessor.cc +++ b/src/iocore/cache/CacheProcessor.cc @@ -199,6 +199,8 @@ CacheProcessor::start_internal(int flags) gndisks = 0; ink_aio_set_err_callback(new AIO_failure_handler()); + config_volumes.read_config_file(); + /* create CacheDisk objects for each span in the configuration file and store in gdisks */ @@ -277,28 +279,7 @@ CacheProcessor::start_internal(int flags) if (check) { cache_disk->read_only_p = true; } - - cache_disk->span_name = ats_strdup(span->name); - - // Find exclusive span - // A span is exclusive when ConfigVolumes::complement() has assigned 100% of it to a - // single volume (either because the legacy storage.config used "volume=N" on that span, - // or because the storage.yaml volumes[].spans[] entry carries size=100%). Exclusive - // spans are tracked via forced_volume_num so the allocator can skip them from the - // shared pool calculation. - for (ConfigVol *vol_config = config_volumes.cp_queue.head; vol_config; vol_config = vol_config->link.next) { - for (auto &span_config : vol_config->spans) { - if (strcmp(span->name, span_config.use.c_str()) == 0 && span_config.size.in_percent && - span_config.size.percent == 100) { - cache_disk->forced_volume_num = vol_config->number; - Dbg(dbg_ctl_cache_init, "cache disk span_name=%s forced volume num=%d", cache_disk->span_name.get(), - vol_config->number); - - break; - } - } - } - + cache_disk->forced_volume_num = span->forced_volume_num; if (span->hash_base_string) { cache_disk->hash_base_string = ats_strdup(span->hash_base_string); } @@ -821,7 +802,7 @@ CacheProcessor::diskInitialized() theCache->open(clear, fix); return; } - if (config_volumes.num_volumes != 0) { + if (config_volumes.num_http_volumes != 0) { theCache = new Cache(); theCache->scheme = CacheType::HTTP; theCache->open(clear, fix); @@ -831,6 +812,11 @@ CacheProcessor::diskInitialized() int cplist_reconfigure() { + int64_t size; + int volume_number; + off_t size_in_blocks; + ConfigVol *config_vol; + gnstripes = 0; if (config_volumes.num_volumes == 0) { /* only the http cache */ @@ -887,32 +873,13 @@ cplist_reconfigure() // in such a way forced volumes will not impact volume percentage calculations. if (-1 == gdisks[i]->forced_volume_num) { tot_space_in_blks += (gdisks[i]->num_usable_blocks / blocks_per_vol) * blocks_per_vol; - } else { - // exclusive span - for (ConfigVol *config_vol = config_volumes.cp_queue.head; config_vol; config_vol = config_vol->link.next) { - for (auto &vol_span_config : config_vol->spans) { - // Convert relative exclusive span size into absolute size - if (strlen(gdisks[i]->span_name) == vol_span_config.use.size() && - strncmp(gdisks[i]->span_name, vol_span_config.use.c_str(), vol_span_config.use.size()) == 0 && - vol_span_config.size.in_percent) { - int64_t space_in_blks = - (gdisks[i]->num_usable_blocks / blocks_per_vol) * blocks_per_vol * vol_span_config.size.percent / 100; - - space_in_blks = space_in_blks >> (20 - STORE_BLOCK_SHIFT); - // round down to 128 megabyte multiple - space_in_blks = (space_in_blks >> 7) << 7; - vol_span_config.size.absolute_value = space_in_blks; - } - } - } } } - // Convert relative volume size into absolute size double percent_remaining = 100.00; - for (ConfigVol *config_vol = config_volumes.cp_queue.head; config_vol; config_vol = config_vol->link.next) { - if (config_vol->size.in_percent) { - if (config_vol->size.percent > percent_remaining) { + for (config_vol = config_volumes.cp_queue.head; config_vol; config_vol = config_vol->link.next) { + if (config_vol->in_percent) { + if (config_vol->percent > percent_remaining) { Warning("total volume sizes added up to more than 100%%!"); Warning("no volumes created"); return -1; @@ -931,7 +898,7 @@ cplist_reconfigure() int64_t space_in_blks = 0; if (0 == tot_forced_space_in_blks) { // Calculate the space as percentage of total space in blocks. - space_in_blks = static_cast(((config_vol->size.percent / percent_remaining)) * tot_space_in_blks); + space_in_blks = static_cast(((config_vol->percent / percent_remaining)) * tot_space_in_blks); } else { // Forced volumes take all disk space, so no percentage calculations here. space_in_blks = tot_forced_space_in_blks; @@ -939,56 +906,34 @@ cplist_reconfigure() space_in_blks = space_in_blks >> (20 - STORE_BLOCK_SHIFT); /* round down to 128 megabyte multiple */ - space_in_blks = (space_in_blks >> 7) << 7; - config_vol->size.absolute_value = space_in_blks; + space_in_blks = (space_in_blks >> 7) << 7; + config_vol->size = space_in_blks; if (0 == tot_forced_space_in_blks) { tot_space_in_blks -= space_in_blks << (20 - STORE_BLOCK_SHIFT); - percent_remaining -= (config_vol->size.absolute_value < 128) ? 0 : config_vol->size.percent; + percent_remaining -= (config_vol->size < 128) ? 0 : config_vol->percent; } } - - if (!config_vol->size.is_empty() && config_vol->size.absolute_value < 128) { + if (config_vol->size < 128) { Warning("the size of volume %d (%" PRId64 ") is less than the minimum required volume size %d", config_vol->number, - static_cast(config_vol->size.absolute_value), 128); + static_cast(config_vol->size), 128); Warning("volume %d is not created", config_vol->number); } - - if (dbg_ctl_cache_hosting.on()) { - Dbg(dbg_ctl_cache_hosting, "volume: %d ramcache: %d", config_vol->number, config_vol->ramcache_enabled); - - if (config_vol->size.absolute_value) { - Dbg(dbg_ctl_cache_hosting, " size: %" PRId64, config_vol->size.absolute_value); - } else { - for (const auto &config_span : config_vol->spans) { - Dbg(dbg_ctl_cache_hosting, " using span: %s size: %" PRId64, config_span.use.c_str(), config_span.size.absolute_value); - } - } - } + Dbg(dbg_ctl_cache_hosting, "Volume: %d Size: %" PRId64 " Ramcache: %d", config_vol->number, + static_cast(config_vol->size), config_vol->ramcache_enabled); } cplist_update(); /* go through volume config and grow and create volumes */ - for (ConfigVol *config_vol = config_volumes.cp_queue.head; config_vol; config_vol = config_vol->link.next) { - Dbg(dbg_ctl_cache_init, "volume id=%d", config_vol->number); - - int64_t size = 0; - - if (!config_vol->size.is_empty()) { - size = config_vol->size.absolute_value; - } else { - for (const auto &config_span : config_vol->spans) { - size += config_span.size.absolute_value; - } - } - + for (config_vol = config_volumes.cp_queue.head; config_vol; config_vol = config_vol->link.next) { + size = config_vol->size; if (size < 128) { continue; } - int volume_number = config_vol->number; + volume_number = config_vol->number; - off_t size_in_blocks = (static_cast(size) * 1024 * 1024) / STORE_BLOCK_SIZE; + size_in_blocks = (static_cast(size) * 1024 * 1024) / STORE_BLOCK_SIZE; if (config_vol->cachep && config_vol->cachep->num_vols > 0) { gnstripes += config_vol->cachep->num_vols; diff --git a/src/iocore/cache/P_CacheDisk.h b/src/iocore/cache/P_CacheDisk.h index 1630ea7794f..0c056367971 100644 --- a/src/iocore/cache/P_CacheDisk.h +++ b/src/iocore/cache/P_CacheDisk.h @@ -94,7 +94,6 @@ struct CacheDisk : public Continuation { // Extra configuration values int forced_volume_num = -1; ///< Volume number for this disk. - ats_scoped_str span_name; ///< Span name ats_scoped_str hash_base_string; ///< Base string for hash seed. CacheDisk() : Continuation(new_ProxyMutex()) {} diff --git a/src/iocore/cache/P_CacheHosting.h b/src/iocore/cache/P_CacheHosting.h index 3605ff95b68..d36821c51e3 100644 --- a/src/iocore/cache/P_CacheHosting.h +++ b/src/iocore/cache/P_CacheHosting.h @@ -23,16 +23,12 @@ #pragma once #include "iocore/cache/CacheDefs.h" -#include "mgmt/config/ConfigContext.h" #include "records/RecCore.h" #include "tscore/MatcherUtils.h" #include "tscore/HostLookup.h" #include "tsutil/Bravo.h" -#include "tscore/Filenames.h" #include -#include -#include #define CACHE_MEM_FREE_TIMEOUT HRTIME_SECONDS(1) @@ -226,11 +222,11 @@ class CacheHostTable public: // Parameter name must not be deallocated before this // object is - CacheHostTable(Cache *c, CacheType typ, ConfigContext ctx = {}); + CacheHostTable(Cache *c, CacheType typ); ~CacheHostTable(); - int BuildTable(const char *config_file_path, ConfigContext ctx = {}); - int BuildTableFromString(const char *config_file_path, char *str, ConfigContext ctx = {}); + int BuildTable(const char *config_file_path); + int BuildTableFromString(const char *config_file_path, char *str); void Match(std::string_view rdata, CacheHostResult *result) const; void Print() const; @@ -272,6 +268,12 @@ class CacheHostTable return cache; } + void + register_config_callback(ReplaceablePtr *p) + { + RecRegisterConfigUpdateCb("proxy.config.cache.hosting_filename", CacheHostTable::config_callback, (void *)p); + } + private: static int config_callback(const char *, RecDataT, RecData, void *); @@ -284,85 +286,67 @@ class CacheHostTable const char *matcher_name = "unknown"; // Used for Debug/Warning/Error messages }; -/** - List of volumes in the storage.yaml - */ -struct ConfigVol { - struct Size { - int64_t absolute_value = 0; - bool in_percent = false; - int percent = 0; - - bool is_empty() const; - }; - - struct Span { - std::string use{}; - Size size{}; - }; - using Spans = std::vector; - - int number = 0; - CacheType scheme = CacheType::NONE; - bool ramcache_enabled = true; - Size size{}; - Spans spans{}; - - int avg_obj_size = -1; - int fragment_size = -1; - int64_t ram_cache_size = -1; // Per-volume RAM cache size (-1 = use shared allocation) - int64_t ram_cache_cutoff = -1; // Per-volume RAM cache cutoff (-1 = use global cutoff) - - CacheVol *cachep = nullptr; - LINK(ConfigVol, link); -}; - -struct ConfigVolumes { - ConfigVolumes() = default; - ~ConfigVolumes() { clear_all(); } - - // Move constructor - ConfigVolumes(ConfigVolumes &&other) noexcept : num_volumes(other.num_volumes), cp_queue(std::move(other.cp_queue)) +struct CacheHostTableConfig; +using CacheHostTabHandler = int (CacheHostTableConfig::*)(int, void *); +struct CacheHostTableConfig : public Continuation { + CacheHostTableConfig(ReplaceablePtr *appt) : Continuation(nullptr), ppt(appt) { - // Reset the source object to prevent double deletion - other.num_volumes = 0; - other.cp_queue.clear(); + SET_HANDLER(&CacheHostTableConfig::mainEvent); } - // Move assignment operator - ConfigVolumes & - operator=(ConfigVolumes &&other) noexcept + ~CacheHostTableConfig() {} + + int + mainEvent(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */) { - if (this != &other) { - // Clear current contents - clear_all(); - // Move from other - num_volumes = other.num_volumes; - cp_queue = std::move(other.cp_queue); - // Reset the source object to prevent double deletion - other.num_volumes = 0; - other.cp_queue.clear(); + CacheType type = CacheType::HTTP; + Cache *cache = nullptr; + { + ReplaceablePtr::ScopedReader hosttable(ppt); + type = hosttable->getType(); + cache = hosttable->getCache(); } - return *this; + ppt->reset(new CacheHostTable(cache, type)); + delete this; + return EVENT_DONE; } - // Delete copy constructor and copy assignment to prevent accidental copying - ConfigVolumes(const ConfigVolumes &) = delete; - ConfigVolumes &operator=(const ConfigVolumes &) = delete; +private: + ReplaceablePtr *ppt; +}; - int num_volumes = 0; - Queue cp_queue{}; +/* list of volumes in the volume.config file */ +struct ConfigVol { + int number; + CacheType scheme; + off_t size; + bool in_percent; + bool ramcache_enabled; + int percent; + int avg_obj_size; + int fragment_size; + int64_t ram_cache_size; // Per-volume RAM cache size (-1 = use shared allocation) + int64_t ram_cache_cutoff; // Per-volume RAM cache cutoff (-1 = use global cutoff) + CacheVol *cachep; + LINK(ConfigVol, link); +}; + +struct ConfigVolumes { + int num_volumes; + int num_http_volumes; + Queue cp_queue; + void read_config_file(); + void BuildListFromString(char *config_file_path, char *file_buf); - void complement(); void clear_all() { // remove all the volumes from the queue for (int i = 0; i < num_volumes; i++) { - ConfigVol *v = cp_queue.pop(); - delete v; + cp_queue.pop(); } // reset count variables - num_volumes = 0; + num_volumes = 0; + num_http_volumes = 0; } }; diff --git a/src/iocore/cache/Store.cc b/src/iocore/cache/Store.cc index c0896dea3b8..956ee5b4fcb 100644 --- a/src/iocore/cache/Store.cc +++ b/src/iocore/cache/Store.cc @@ -21,17 +21,14 @@ limitations under the License. */ -#include "P_CacheHosting.h" - -#include "config/storage.h" #include "iocore/cache/Store.h" #include "records/RecCore.h" #include "tscore/Diags.h" -#include "tscore/ink_memory.h" #include "tscore/ink_platform.h" #include "tscore/Layout.h" #include "tscore/Filenames.h" #include "tscore/ink_file.h" +#include "tscore/SimpleTokenizer.h" #include "tsutil/DbgCtl.h" #if defined(__linux__) @@ -41,6 +38,8 @@ // // Store // +const char Store::VOLUME_KEY[] = "volume"; +const char Store::HASH_BASE_STRING_KEY[] = "id"; namespace { @@ -49,8 +48,6 @@ DbgCtl dbg_ctl_cache_init{"cache_init"}; } // end anonymous namespace -extern ConfigVolumes config_volumes; - static span_error_t make_span_error(int error) { @@ -190,6 +187,12 @@ Span::hash_base_string_set(const char *s) hash_base_string = s ? ats_strdup(s) : nullptr; } +void +Span::volume_number_set(int n) +{ + forced_volume_num = n; +} + void Store::delete_all() { @@ -218,119 +221,124 @@ Span::~Span() Result Store::read_config() { + int n_dsstore = 0; + int i = 0; + const char *err = nullptr; + Span *sd = nullptr, *cur = nullptr; + Span *ns; + ats_scoped_fd fd; ats_scoped_str storage_path(RecConfigReadConfigPath(nullptr, ts::filename::STORAGE)); Note("%s loading ...", ts::filename::STORAGE); + Dbg(dbg_ctl_cache_init, "Store::read_config, fd = -1, \"%s\"", (const char *)storage_path); + fd = ::open(storage_path, O_RDONLY); + if (fd < 0) { + Error("%s failed to load", ts::filename::STORAGE); + return Result::failure("open %s: %s", storage_path.get(), strerror(errno)); + } - config::StorageParser parser; - auto parse_result = parser.parse(storage_path.get()); - if (!parse_result.ok()) { - std::string msg; - for (auto const &annotation : parse_result.errata) { - if (!msg.empty()) { - msg += "; "; + // For each line + + char line[1024]; + int len; + while ((len = ink_file_fd_readline(fd, sizeof(line), line)) > 0) { + const char *path; + const char *seed = nullptr; + // Because the SimpleTokenizer is a bit too simple, we have to normalize whitespace. + for (char *spot = line, *limit = line + len; spot < limit; ++spot) { + if (ParseRules::is_space(*spot)) { + *spot = ' '; // force whitespace to literal space. } - msg += std::string(annotation.text()); } - return Result::failure("failed to load %s: %s", ts::filename::STORAGE, msg.c_str()); - } + SimpleTokenizer tokens(line, ' ', SimpleTokenizer::OVERWRITE_INPUT_STRING); - // Convert StorageVolumeEntry -> ConfigVol and populate config_volumes. - for (auto const &vv : parse_result.value.volumes) { - auto *vol = new ConfigVol(); - vol->number = vv.id; - vol->scheme = (vv.scheme == "http") ? CacheType::HTTP : CacheType::NONE; - vol->ramcache_enabled = vv.ram_cache; - vol->size.in_percent = vv.size.in_percent; - vol->size.percent = vv.size.percent; - vol->size.absolute_value = vv.size.absolute_value; - vol->ram_cache_size = vv.ram_cache_size; - vol->ram_cache_cutoff = vv.ram_cache_cutoff; - vol->avg_obj_size = vv.avg_obj_size; - vol->fragment_size = vv.fragment_size; - for (auto const &sr : vv.spans) { - ConfigVol::Span span; - span.use = sr.use; - span.size.in_percent = sr.size.in_percent; - span.size.percent = sr.size.percent; - span.size.absolute_value = sr.size.absolute_value; - vol->spans.push_back(std::move(span)); + // skip comments and blank lines + path = tokens.getNext(); + if (nullptr == path || '#' == path[0]) { + continue; } - config_volumes.cp_queue.enqueue(vol); - config_volumes.num_volumes++; - } - config_volumes.complement(); - - auto const &storage_spans = parse_result.value.spans; - this->n_spans_in_config = storage_spans.size(); - - int n_dsstore = 0; - Span *prev = nullptr; - Span *head = nullptr; - for (const auto &it : storage_spans) { - Span *span = new Span(); + // parse + Dbg(dbg_ctl_cache_init, "Store::read_config: \"%s\"", path); + ++n_spans_in_config; + + int64_t size = -1; + int volume_num = -1; + const char *e; + while (nullptr != (e = tokens.getNext())) { + if (ParseRules::is_digit(*e)) { + const char *end; + if ((size = ink_atoi64(e, &end)) <= 0 || *end != '\0') { + delete sd; + Error("%s failed to load", ts::filename::STORAGE); + return Result::failure("failed to parse size '%s'", e); + } + } else if (0 == strncasecmp(HASH_BASE_STRING_KEY, e, sizeof(HASH_BASE_STRING_KEY) - 1)) { + e += sizeof(HASH_BASE_STRING_KEY) - 1; + if ('=' == *e) { + ++e; + } + if (*e && !ParseRules::is_space(*e)) { + seed = e; + } + } else if (0 == strncasecmp(VOLUME_KEY, e, sizeof(VOLUME_KEY) - 1)) { + e += sizeof(VOLUME_KEY) - 1; + if ('=' == *e) { + ++e; + } + if (!*e || !ParseRules::is_digit(*e) || 0 >= (volume_num = ink_atoi(e))) { + delete sd; + Error("%s failed to load", ts::filename::STORAGE); + return Result::failure("failed to parse volume number '%s'", e); + } + } + } - Dbg(dbg_ctl_cache_init, "Span name=\"%s\" path=\"%s\" size=%" PRId64 " hash_seed=%s", it.name.c_str(), it.path.c_str(), it.size, - it.hash_seed.c_str()); + std::string pp = Layout::get()->relative(path); - std::string pp = Layout::get()->relative(it.path); - const char *err = span->init(it.name.c_str(), pp.c_str(), it.size); - if (err) { + ns = new Span; + Dbg(dbg_ctl_cache_init, "Store::read_config - ns = new Span; ns->init(\"%s\",%" PRId64 "), forced volume=%d%s%s", pp.c_str(), + size, volume_num, seed ? " id=" : "", seed ? seed : ""); + if ((err = ns->init(pp.c_str(), size))) { Dbg(dbg_ctl_cache_init, "Store::read_config - could not initialize storage \"%s\" [%s]", pp.c_str(), err); - delete span; + delete ns; continue; } n_dsstore++; // Set side values if present. - if (!it.hash_seed.empty()) { - span->hash_base_string_set(it.hash_seed.c_str()); + if (seed) { + ns->hash_base_string_set(seed); + } + if (volume_num > 0) { + ns->volume_number_set(volume_num); } - // link prev and current Span - if (prev == nullptr) { - head = span; - } else { - prev->link.next = span; + // new Span + { + Span *prev = cur; + cur = ns; + if (!sd) { + sd = cur; + } else { + prev->link.next = cur; + } } - prev = span; } // count the number of disks extend(n_dsstore); - Span *s = head; - for (int i = 0; i < n_dsstore; ++i) { - Span *next = s->link.next; - s->link.next = nullptr; - spans[i] = s; - s = next; + cur = sd; + while (cur) { + Span *next = cur->link.next; + cur->link.next = nullptr; + spans[i++] = cur; + cur = next; } + sd = nullptr; // these are all used. sort(); - // print read config_volumes - if (dbg_ctl_cache_init.on()) { - for (ConfigVol *conf = config_volumes.cp_queue.head; conf != nullptr; conf = config_volumes.cp_queue.next(conf)) { - if (conf->spans.empty()) { - if (conf->size.in_percent) { - Dbg(dbg_ctl_cache_init, "Volume number=%d size=%d%%", conf->number, conf->size.percent); - } else { - Dbg(dbg_ctl_cache_init, "Volume number=%d size=%" PRId64, conf->number, conf->size.absolute_value); - } - } else { - Dbg(dbg_ctl_cache_init, "Volume number=%d", conf->number); - for (auto &span_conf : conf->spans) { - if (span_conf.size.in_percent) { - Dbg(dbg_ctl_cache_init, " using span id=%s size=%d%%", span_conf.use.c_str(), span_conf.size.percent); - } else { - Dbg(dbg_ctl_cache_init, " using span id=%s size=%" PRId64, span_conf.use.c_str(), span_conf.size.absolute_value); - } - } - } - } - } - Note("%s finished loading", ts::filename::STORAGE); return Result::ok(); @@ -352,10 +360,8 @@ Store::write_config_data(int fd) const } const char * -Span::init(const char *name, const char *path, int64_t size) +Span::init(const char *path, int64_t size) { - this->name = ats_strdup(name); - struct stat sbuf; struct statvfs vbuf; span_error_t serr; diff --git a/src/iocore/cache/unit_tests/etc/storage.yaml b/src/iocore/cache/unit_tests/etc/storage.yaml deleted file mode 100644 index dc388367ac7..00000000000 --- a/src/iocore/cache/unit_tests/etc/storage.yaml +++ /dev/null @@ -1,5 +0,0 @@ -cache: - spans: - - name: disk-1 - path: var/trafficserver - size: 256M diff --git a/src/iocore/cache/unit_tests/main.cc b/src/iocore/cache/unit_tests/main.cc index 6b76c504d23..369b0572b48 100644 --- a/src/iocore/cache/unit_tests/main.cc +++ b/src/iocore/cache/unit_tests/main.cc @@ -159,7 +159,7 @@ struct EventProcessorListener : Catch::EventListenerBase { thread->set_tail_handler(&uring_handler); #endif - std::string src_dir = std::string(TS_ABS_TOP_SRCDIR) + "/src/iocore/cache/unit_tests/etc/"; + std::string src_dir = std::string(TS_ABS_TOP_SRCDIR) + "/src/iocore/cache/unit_tests"; Layout::get()->sysconfdir = std::move(src_dir); } }; diff --git a/src/iocore/cache/unit_tests/storage.config b/src/iocore/cache/unit_tests/storage.config new file mode 100644 index 00000000000..c10a663b1db --- /dev/null +++ b/src/iocore/cache/unit_tests/storage.config @@ -0,0 +1,2 @@ +var/trafficserver 256M +var/trafficserver2 32M diff --git a/src/iocore/cache/unit_tests/test_CacheVol.cc b/src/iocore/cache/unit_tests/test_CacheVol.cc index 2805f7de5a2..187be472d55 100644 --- a/src/iocore/cache/unit_tests/test_CacheVol.cc +++ b/src/iocore/cache/unit_tests/test_CacheVol.cc @@ -88,14 +88,15 @@ create_config(int num) if (vol_num > 255) { break; } - ConfigVol *cp = new ConfigVol(); - cp->number = vol_num++; - cp->scheme = CacheType::HTTP; - cp->size.absolute_value = 128; - cp->size.in_percent = false; - cp->cachep = nullptr; + ConfigVol *cp = new ConfigVol(); + cp->number = vol_num++; + cp->scheme = CacheType::HTTP; + cp->size = 128; + cp->in_percent = false; + cp->cachep = nullptr; config_volumes.cp_queue.enqueue(cp); config_volumes.num_volumes++; + config_volumes.num_http_volumes++; } } Dbg(dbg_ctl_cache_vol_test, "%d 128 Megabyte Volumes", vol_num - 1); @@ -126,15 +127,16 @@ create_config(int num) vol_num = 1; Dbg(dbg_ctl_cache_vol_test, "Cleared disk"); for (i = 0; i < 10; i++) { - ConfigVol *cp = new ConfigVol(); - cp->number = vol_num++; - cp->scheme = CacheType::HTTP; - cp->size.absolute_value = 10; - cp->size.percent = 10; - cp->size.in_percent = true; - cp->cachep = nullptr; + ConfigVol *cp = new ConfigVol(); + cp->number = vol_num++; + cp->scheme = CacheType::HTTP; + cp->size = 10; + cp->percent = 10; + cp->in_percent = true; + cp->cachep = nullptr; config_volumes.cp_queue.enqueue(cp); config_volumes.num_volumes++; + config_volumes.num_http_volumes++; } Dbg(dbg_ctl_cache_vol_test, "10 volume, 10 percent each"); } break; @@ -191,14 +193,20 @@ create_config(int num) ConfigVol *cp = new ConfigVol(); - cp->number = vol_num++; - cp->scheme = scheme; - cp->size.absolute_value = random_size >> 20; - cp->size.percent = 0; - cp->size.in_percent = false; - cp->cachep = nullptr; + cp->number = vol_num++; + cp->scheme = scheme; + cp->size = random_size >> 20; + cp->percent = 0; + cp->in_percent = false; + cp->cachep = nullptr; config_volumes.cp_queue.enqueue(cp); config_volumes.num_volumes++; + if (cp->scheme == CacheType::HTTP) { + config_volumes.num_http_volumes++; + Dbg(dbg_ctl_cache_vol_test, "volume=%d scheme=http size=%zd", cp->number, static_cast(cp->size)); + } else { + // ToDo: Assert ? + } } } break; @@ -238,7 +246,7 @@ execute_and_verify() if (cachep->vol_number == cp->number) { // Configuration and Actual volumes should match REQUIRE(cachep->scheme == cp->scheme); - REQUIRE(cachep->size == (cp->size.absolute_value << (20 - STORE_BLOCK_SHIFT))); + REQUIRE(cachep->size == (cp->size << (20 - STORE_BLOCK_SHIFT))); REQUIRE(cachep == cp->cachep); /* check that the number of volumes match the ones @@ -304,7 +312,8 @@ ClearConfigVol(ConfigVolumes *configp) Warning("failed"); return 0; } - configp->num_volumes = 0; + configp->num_volumes = 0; + configp->num_http_volumes = 0; return 1; } @@ -336,18 +345,10 @@ save_state() { saved_cp_list = cp_list; saved_cp_list_len = cp_list_len; - - // Properly save ConfigVolumes by moving the queue contents - saved_config_volumes.num_volumes = config_volumes.num_volumes; - saved_config_volumes.cp_queue = config_volumes.cp_queue; - + memcpy(&saved_config_volumes, &config_volumes, sizeof(ConfigVolumes)); saved_gnstripes = gnstripes; memset(static_cast(&cp_list), 0, sizeof(Queue)); - - // Clear config_volumes properly without calling destructor on moved data - config_volumes.num_volumes = 0; - config_volumes.cp_queue.clear(); - + memset(static_cast(&config_volumes), 0, sizeof(ConfigVolumes)); gnstripes = 0; } @@ -356,15 +357,7 @@ restore_state() { cp_list = saved_cp_list; cp_list_len = saved_cp_list_len; - - // Properly restore ConfigVolumes by moving the queue contents back - config_volumes.num_volumes = saved_config_volumes.num_volumes; - config_volumes.cp_queue = saved_config_volumes.cp_queue; - - // Clear the saved state to avoid double ownership - saved_config_volumes.num_volumes = 0; - saved_config_volumes.cp_queue.clear(); - + memcpy(&config_volumes, &saved_config_volumes, sizeof(ConfigVolumes)); gnstripes = saved_gnstripes; } } // end anonymous namespace diff --git a/src/iocore/cache/unit_tests/test_ConfigVolumes.cc b/src/iocore/cache/unit_tests/test_ConfigVolumes.cc deleted file mode 100644 index a05ffa182da..00000000000 --- a/src/iocore/cache/unit_tests/test_ConfigVolumes.cc +++ /dev/null @@ -1,370 +0,0 @@ -/** @file - - Unit tests for ConfigVolumes - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#include - -#include "P_CacheHosting.h" - -#include "config/storage.h" - -namespace -{ - -/** - * Helper: parse a bare 'volumes:' YAML snippet into ConfigVolumes. - * - * The test YAML only contains a top-level 'volumes:' key. Wrap it in the - * 'cache:' envelope that StorageParser expects, then convert the resulting - * StorageVolumeEntry list into ConfigVol objects. - */ -bool -load_volumes(ConfigVolumes &v_config, std::string const &yaml) -{ - // Wrap the bare 'volumes:' snippet in a 'cache:' block. - std::string wrapped = "cache:\n"; - - // Indent every line by 2 spaces under 'cache:'. - std::string indented; - std::string::size_type pos = 0; - indented.reserve(yaml.size() + 64); - while (pos < yaml.size()) { - auto next = yaml.find('\n', pos); - indented += " "; - if (next == std::string::npos) { - indented += yaml.substr(pos); - break; - } - indented += yaml.substr(pos, next - pos + 1); - pos = next + 1; - } - wrapped += indented; - - config::StorageParser parser; - auto result = parser.parse_content(wrapped); - if (!result.ok()) { - return false; - } - - for (auto const &vv : result.value.volumes) { - auto *vol = new ConfigVol(); - vol->number = vv.id; - vol->scheme = (vv.scheme == "http") ? CacheType::HTTP : CacheType::NONE; - vol->ramcache_enabled = vv.ram_cache; - vol->size.in_percent = vv.size.in_percent; - vol->size.percent = vv.size.percent; - vol->size.absolute_value = vv.size.absolute_value; - vol->ram_cache_size = vv.ram_cache_size; - vol->ram_cache_cutoff = vv.ram_cache_cutoff; - vol->avg_obj_size = vv.avg_obj_size; - vol->fragment_size = vv.fragment_size; - for (auto const &sr : vv.spans) { - ConfigVol::Span span; - span.use = sr.use; - span.size.in_percent = sr.size.in_percent; - span.size.percent = sr.size.percent; - span.size.absolute_value = sr.size.absolute_value; - vol->spans.push_back(std::move(span)); - } - v_config.cp_queue.enqueue(vol); - v_config.num_volumes++; - } - v_config.complement(); - return true; -} - -} // namespace - -/** - Unit Test to complement omitted size. - */ -TEST_CASE("ConfigVolumes::complement") -{ - SECTION("simple 2 volumes") - { - ConfigVolumes config; - - std::string in = R"EOF( - volumes: - - id: 1 - - id: 2 - )EOF"; - - load_volumes(config, in); - - // Expected - // volumes: - // - id: 1 - // size: 50% - // - id: 2 - // size: 50% - for (ConfigVol *c = config.cp_queue.head; c != nullptr; c = config.cp_queue.next(c)) { - CHECK(c->size.in_percent); - CHECK(c->size.percent == 50); - } - } - - SECTION("one-third volume") - { - ConfigVolumes config; - - std::string in = R"EOF( - volumes: - - id: 1 - size: 66% - - id: 2 - )EOF"; - - load_volumes(config, in); - - // Expected - // volumes: - // - id: 1 - // size: 66% - // - id: 2 - // size: 34% - ConfigVol *v1 = config.cp_queue.head; - REQUIRE(v1 != nullptr); - CHECK(v1->size.percent == 66); - - ConfigVol *v2 = config.cp_queue.next(v1); - REQUIRE(v2 != nullptr); - CHECK(v2->size.percent == 34); - } - - SECTION("simple exclusive span") - { - ConfigVolumes config; - - std::string in = R"EOF( - volumes: - - id: 1 - spans: - - use: "span-1" - )EOF"; - - load_volumes(config, in); - - // Expected - // volumes: - // - id: 1 - // spans: - // - use: span-1 - // size: 100% - ConfigVol *v1 = config.cp_queue.head; - - REQUIRE(v1 != nullptr); - CHECK(v1->size.is_empty()); - REQUIRE(v1->spans.size() == 1); - CHECK(v1->spans[0].size.in_percent); - CHECK(v1->spans[0].size.percent == 100); - } - - SECTION("simple shared span") - { - ConfigVolumes config; - - std::string in = R"EOF( - volumes: - - id: 1 - spans: - - use: span-1 - - id: 2 - spans: - - use: span-1 - )EOF"; - - load_volumes(config, in); - - // Expected - // volumes: - // - id: 1 - // spans: - // - use: span-1 - // size: 50% - // - id: 2 - // spans: - // - use: span-1 - // size: 50% - for (ConfigVol *c = config.cp_queue.head; c != nullptr; c = config.cp_queue.next(c)) { - CHECK(c->size.is_empty()); - REQUIRE(c->spans.size() == 1); - CHECK(c->spans[0].size.in_percent); - CHECK(c->spans[0].size.percent == 50); - } - } - - SECTION("shared span") - { - ConfigVolumes config; - - std::string in = R"EOF( - volumes: - - id: 1 - spans: - - use: span-1 - size: 10% - - id: 2 - spans: - - use: span-1 - size: 20% - - id: 3 - spans: - - use: span-1 - )EOF"; - - load_volumes(config, in); - - // Expected - // volumes: - // - id: 1 - // spans: - // - use: span-1 - // size: 10% - // - id: 2 - // spans: - // - use: span-1 - // size: 20% - // - id: 3 - // spans: - // - use: span-1 - // size: 70% - ConfigVol *v1 = config.cp_queue.head; - - REQUIRE(v1 != nullptr); - CHECK(v1->size.is_empty()); - CHECK(v1->spans[0].size.percent == 10); - - ConfigVol *v2 = config.cp_queue.next(v1); - - REQUIRE(v2 != nullptr); - CHECK(v2->size.is_empty()); - CHECK(v2->spans[0].size.percent == 20); - - ConfigVol *v3 = config.cp_queue.next(v2); - - REQUIRE(v3 != nullptr); - CHECK(v3->size.is_empty()); - CHECK(v3->spans[0].size.percent == 70); - } - - SECTION("two shared spans") - { - ConfigVolumes config; - - std::string in = R"EOF( - volumes: - - id: 1 - spans: - - use: span-1 - size: 66% - - use: span-2 - size: 66% - - id: 2 - spans: - - use: span-1 - - use: span-2 - - id: 3 - spans: - - use: span-1 - - use: span-2 - )EOF"; - - load_volumes(config, in); - - // Expected - // volumes: - // - id: 1 - // - spans: - // - use: span-1 - // size: 66% - // - use: ram.2 - // size: 66% - // - id: 2 - // - spans: - // - use: span-1 - // size: 17% - // - use: ram.2 - // size: 17% - // - id: 3 - // - spans: - // - use: span-1 - // size: 17% - // - use: ram.2 - // size: 17% - ConfigVol *v1 = config.cp_queue.head; - - REQUIRE(v1 != nullptr); - CHECK(v1->size.is_empty()); - REQUIRE(v1->spans.size() == 2); - - CHECK(v1->spans[0].size.percent == 66); - CHECK(v1->spans[1].size.percent == 66); - - ConfigVol *v2 = config.cp_queue.next(v1); - - CHECK(v2->spans[0].size.percent == 17); - CHECK(v2->spans[1].size.percent == 17); - - ConfigVol *v3 = config.cp_queue.next(v2); - - CHECK(v3->spans[0].size.percent == 17); - CHECK(v3->spans[1].size.percent == 17); - } - - SECTION("mixed volumes") - { - ConfigVolumes config; - - std::string in = R"EOF( - volumes: - - id: 1 - spans: - - use: span-1 - - id: 2 - )EOF"; - - load_volumes(config, in); - - // Expected - // volumes: - // - id: 1 - // spans: - // - use: span-1 - // size: 100% - // - id: 2 - // size: 100% - ConfigVol *v1 = config.cp_queue.head; - - REQUIRE(v1 != nullptr); - CHECK(v1->size.is_empty()); - REQUIRE(v1->spans.size() == 1); - CHECK(v1->spans[0].size.in_percent); - CHECK(v1->spans[0].size.percent == 100); - - ConfigVol *v2 = config.cp_queue.next(v1); - - REQUIRE(v2 != nullptr); - CHECK(v2->size.in_percent); - CHECK(v2->size.percent == 100); - } -} diff --git a/src/iocore/dns/SplitDNS.cc b/src/iocore/dns/SplitDNS.cc index 1302b587ac5..7542143e1c6 100644 --- a/src/iocore/dns/SplitDNS.cc +++ b/src/iocore/dns/SplitDNS.cc @@ -31,8 +31,6 @@ #include "P_SplitDNSProcessor.h" #include "tscore/Tokenizer.h" #include "tscore/Filenames.h" -#include "mgmt/config/ConfigContextDiags.h" -#include "mgmt/config/ConfigRegistry.h" #include #include "P_SplitDNS.h" @@ -48,6 +46,8 @@ -------------------------------------------------------------- */ static const char modulePrefix[] = "[SplitDNS]"; +ConfigUpdateHandler *SplitDNSConfig::splitDNSUpdate = nullptr; + static ClassAllocator DNSReqAllocator("DNSRequestDataAllocator"); /* -------------------------------------------------------------- @@ -115,41 +115,33 @@ SplitDNSConfig::release(SplitDNS *params) void SplitDNSConfig::startup() { - gsplit_dns_enabled = RecGetRecordInt("proxy.config.dns.splitDNS.enabled").value_or(0); - - config::ConfigRegistry::Get_Instance().register_config( - "split_dns", // registry key - ts::filename::SPLITDNS, // default filename - "proxy.config.dns.splitdns.filename", // record holding the filename - [](ConfigContext ctx) { SplitDNSConfig::reconfigure(ctx); }, // reload handler - config::ConfigSource::FileOnly, // no RPC content - {"proxy.config.dns.splitdns.filename"}); // trigger records + // startup just check gsplit_dns_enabled + gsplit_dns_enabled = RecGetRecordInt("proxy.config.dns.splitDNS.enabled").value_or(0); + SplitDNSConfig::splitDNSUpdate = new ConfigUpdateHandler(); + SplitDNSConfig::splitDNSUpdate->attach("proxy.config.cache.splitdns.filename"); } /* -------------------------------------------------------------- SplitDNSConfig::reconfigure() -------------------------------------------------------------- */ void -SplitDNSConfig::reconfigure(ConfigContext ctx) +SplitDNSConfig::reconfigure() { if (0 == gsplit_dns_enabled) { - CfgLoadComplete(ctx, "SplitDNS disabled, skipping reload"); return; } - CfgLoadLog(ctx, DL_Note, "%s loading ...", ts::filename::SPLITDNS); + Note("%s loading ...", ts::filename::SPLITDNS); SplitDNS *params = new SplitDNS; params->m_SplitDNSlEnable = gsplit_dns_enabled; - params->m_DNSSrvrTable = std::make_unique( - "proxy.config.dns.splitdns.filename", modulePrefix, &sdns_dest_tags, - ALLOW_HOST_TABLE | ALLOW_IP_TABLE | ALLOW_REGEX_TABLE | ALLOW_HOST_REGEX_TABLE | ALLOW_URL_TABLE, ctx); + params->m_DNSSrvrTable = std::make_unique("proxy.config.dns.splitdns.filename", modulePrefix, &sdns_dest_tags); if (nullptr == params->m_DNSSrvrTable || (0 == params->m_DNSSrvrTable->getEntryCount())) { + Warning("Failed to load %s - No NAMEDs provided! Disabling SplitDNS", ts::filename::SPLITDNS); gsplit_dns_enabled = 0; delete params; - CfgLoadFail(ctx, "Failed to load %s - No NAMEDs provided! Disabling SplitDNS", ts::filename::SPLITDNS); return; } params->m_numEle = params->m_DNSSrvrTable->getEntryCount(); @@ -167,7 +159,7 @@ SplitDNSConfig::reconfigure(ConfigContext ctx) SplitDNSConfig::print(); } - CfgLoadComplete(ctx, "%s finished loading", ts::filename::SPLITDNS); + Note("%s finished loading", ts::filename::SPLITDNS); } /* -------------------------------------------------------------- diff --git a/src/iocore/eventsystem/CMakeLists.txt b/src/iocore/eventsystem/CMakeLists.txt index e3130eb7452..1eaf5cd29d8 100644 --- a/src/iocore/eventsystem/CMakeLists.txt +++ b/src/iocore/eventsystem/CMakeLists.txt @@ -53,9 +53,9 @@ endif() if(BUILD_TESTING) add_executable(test_EventSystem unit_tests/test_EventSystem.cc) - target_link_libraries(test_EventSystem ts::inkevent configmanager Catch2::Catch2WithMain) + target_link_libraries(test_EventSystem ts::inkevent Catch2::Catch2WithMain) add_executable(test_IOBuffer unit_tests/test_IOBuffer.cc) - target_link_libraries(test_IOBuffer ts::inkevent configmanager Catch2::Catch2WithMain) + target_link_libraries(test_IOBuffer ts::inkevent Catch2::Catch2WithMain) add_executable(test_MIOBufferWriter unit_tests/test_MIOBufferWriter.cc) target_link_libraries(test_MIOBufferWriter libswoc::libswoc Catch2::Catch2WithMain) diff --git a/src/iocore/eventsystem/RecProcess.cc b/src/iocore/eventsystem/RecProcess.cc index 8a27eeb69a2..128154cb3ec 100644 --- a/src/iocore/eventsystem/RecProcess.cc +++ b/src/iocore/eventsystem/RecProcess.cc @@ -45,7 +45,6 @@ static Event *config_update_cont_event; static Event *sync_cont_event; static DbgCtl dbg_ctl_statsproc{"statsproc"}; -static DbgCtl dbg_ctl_configproc{"configproc"}; //------------------------------------------------------------------------- // Simple setters for the intervals to decouple this from the proxy @@ -108,7 +107,7 @@ struct config_update_cont : public Continuation { exec_callbacks(int /* event */, Event * /* e */) { RecExecConfigUpdateCbs(REC_PROCESS_UPDATE_REQUIRED); - Dbg(dbg_ctl_configproc, "config_update_cont() processed"); + Dbg(dbg_ctl_statsproc, "config_update_cont() processed"); return EVENT_CONT; } diff --git a/src/iocore/net/CMakeLists.txt b/src/iocore/net/CMakeLists.txt index c5b66d8468c..b4ade3d9c60 100644 --- a/src/iocore/net/CMakeLists.txt +++ b/src/iocore/net/CMakeLists.txt @@ -61,7 +61,6 @@ add_library( TLSSessionResumptionSupport.cc TLSSNISupport.cc TLSTunnelSupport.cc - TLSCertCompression.cc UDPEventIO.cc UDPIOEvent.cc UnixConnection.cc @@ -109,7 +108,7 @@ target_link_libraries( OpenSSL::Crypto OpenSSL::SSL ts::tsapibackend - PRIVATE ts::config ts::tsutil yaml-cpp::yaml-cpp + PRIVATE ts::tsutil yaml-cpp::yaml-cpp ) # Is this necessary? @@ -117,21 +116,6 @@ if(TS_USE_LINUX_IO_URING) target_link_libraries(inknet PUBLIC ts::inkuring) endif() -# Link cert compression libraries after OpenSSL so that OpenSSL include -# directories appear first in the search order, preventing broad system -# include paths (e.g. from Homebrew's zstd) from shadowing them. -if(HAVE_SSL_CTX_ADD_CERT_COMPRESSION_ALG) - target_sources(inknet PRIVATE TLSCertCompression_zlib.cc) - if(HAVE_BROTLI_ENCODE_H) - target_sources(inknet PRIVATE TLSCertCompression_brotli.cc) - target_link_libraries(inknet PRIVATE brotli::brotlienc brotli::brotlidec) - endif() - if(HAVE_ZSTD_H) - target_sources(inknet PRIVATE TLSCertCompression_zstd.cc) - target_link_libraries(inknet PRIVATE zstd::zstd) - endif() -endif() - if(BUILD_TESTING) # libinknet_stub.cc is need because GNU ld is sensitive to the order of static libraries on the command line, and we have a cyclic dependency between inknet and proxy add_executable( @@ -152,6 +136,7 @@ if(BUILD_TESTING) ts::overridable_txn_vars ts::http ts::http_remap + ts::configmanager ) if(TS_USE_QUIC) list(APPEND LINK_GROUP_LIBS quic http3) diff --git a/src/iocore/net/P_SSLCertLookup.h b/src/iocore/net/P_SSLCertLookup.h index b40baa74c30..da31f58086d 100644 --- a/src/iocore/net/P_SSLCertLookup.h +++ b/src/iocore/net/P_SSLCertLookup.h @@ -46,7 +46,7 @@ enum class SSLCertContextOption { }; /** - @brief Gather user provided settings from ssl_multicert.yaml in to this single struct + @brief Gather user provided settings from ssl_multicert.config in to this single struct */ struct SSLMultiCertConfigParams { SSLMultiCertConfigParams() : opt(SSLCertContextOption::OPT_NONE) diff --git a/src/iocore/net/P_SSLClientCoordinator.h b/src/iocore/net/P_SSLClientCoordinator.h index cb2f64f278d..51899cd0c52 100644 --- a/src/iocore/net/P_SSLClientCoordinator.h +++ b/src/iocore/net/P_SSLClientCoordinator.h @@ -21,13 +21,11 @@ limitations under the License. */ -#include "mgmt/config/ConfigContext.h" - -// A class to coordinate the loading of SSL related configs (SSLConfig, SNIConfig, -// SSLCertificateConfig). All are reloaded together when any of the trigger records change. +// A class to pass the ConfigUpdateHandler, so both SSLConfig and SNIConfig get updated +// when the relevant files/configs get updated. class SSLClientCoordinator { public: static void startup(); - static void reconfigure(ConfigContext ctx = {}); + static void reconfigure(); }; diff --git a/src/iocore/net/P_SSLConfig.h b/src/iocore/net/P_SSLConfig.h index 62f3baf4a6a..960ec8edd04 100644 --- a/src/iocore/net/P_SSLConfig.h +++ b/src/iocore/net/P_SSLConfig.h @@ -34,7 +34,6 @@ #include "SSLSessionCache.h" #include "iocore/eventsystem/ConfigProcessor.h" #include "iocore/net/YamlSNIConfig.h" -#include "mgmt/config/ConfigContext.h" #include #include @@ -54,6 +53,12 @@ using init_ssl_ctx_func = void (*)(void *, bool); using load_ssl_file_func = void (*)(const char *); struct SSLConfigParams : public ConfigInfo { + enum SSL_SESSION_CACHE_MODE { + SSL_SESSION_CACHE_MODE_OFF = 0, + SSL_SESSION_CACHE_MODE_SERVER_OPENSSL_IMPL = 1, + SSL_SESSION_CACHE_MODE_SERVER_ATS_IMPL = 2 + }; + SSLConfigParams(); ~SSLConfigParams() override; @@ -67,11 +72,16 @@ struct SSLConfigParams : public ConfigInfo { char *cipherSuite; char *client_cipherSuite; int configExitOnLoadError; - int configLoadConcurrency; int clientCertLevel; int verify_depth; - int ssl_origin_session_cache{0}; - int ssl_origin_session_cache_size{0}; + int ssl_origin_session_cache; + int ssl_origin_session_cache_size; + int ssl_session_cache; // SSL_SESSION_CACHE_MODE + int ssl_session_cache_size; + int ssl_session_cache_num_buckets; + int ssl_session_cache_skip_on_contention; + int ssl_session_cache_timeout; + int ssl_session_cache_auto_clear; char *clientCertPath; char *clientCertPathOnly; @@ -89,9 +99,6 @@ struct SSLConfigParams : public ConfigInfo { unsigned char alpn_protocols_array[MAX_ALPN_STRING]; int alpn_protocols_array_size = 0; - char *server_cert_compression_algorithms; - char *client_cert_compression_algorithms; - char *server_tls13_cipher_suites; char *client_tls13_cipher_suites; char *server_groups_list; @@ -124,6 +131,9 @@ struct SSLConfigParams : public ConfigInfo { static int origin_session_cache; static size_t origin_session_cache_size; + static size_t session_cache_number_buckets; + static size_t session_cache_max_bucket_size; + static bool session_cache_skip_on_lock_contention; static swoc::IPRangeSet *proxy_protocol_ip_addrs; @@ -156,7 +166,7 @@ struct SSLConfigParams : public ConfigInfo { void cleanupCTXTable(); - void initialize(ConfigContext ctx = {}); + void initialize(); void cleanup(); void reset(); void SSLConfigInit(swoc::IPRangeSet *global); @@ -177,7 +187,7 @@ struct SSLConfigParams : public ConfigInfo { struct SSLConfig { static void startup(); - static void reconfigure(ConfigContext ctx = {}); + static void reconfigure(); static SSLConfigParams *acquire(); static SSLConfigParams *load_acquire(); static void release(SSLConfigParams *params); @@ -199,7 +209,7 @@ struct SSLConfig { struct SSLCertificateConfig { static bool startup(); - static bool reconfigure(ConfigContext ctx = {}); + static bool reconfigure(); static SSLCertLookup *acquire(); static void release(SSLCertLookup *params); @@ -213,7 +223,7 @@ struct SSLTicketParams : public ConfigInfo { ssl_ticket_key_block *default_global_keyblock = nullptr; time_t load_time = 0; char *ticket_key_filename = nullptr; - bool LoadTicket(bool &nochange, ConfigContext ctx = {}); + bool LoadTicket(bool &nochange); bool LoadTicketData(char *ticket_data, int ticket_data_len); void cleanup(); @@ -222,7 +232,7 @@ struct SSLTicketParams : public ConfigInfo { struct SSLTicketKeyConfig { static void startup(); - static bool reconfigure(ConfigContext ctx = {}); + static bool reconfigure(); static bool reconfigure_data(char *ticket_data, int ticket_data_len); static SSLTicketParams * @@ -245,4 +255,5 @@ struct SSLTicketKeyConfig { static int configid; }; +extern SSLSessionCache *session_cache; extern SSLOriginSessionCache *origin_sess_cache; diff --git a/src/iocore/net/QUICMultiCertConfigLoader.cc b/src/iocore/net/QUICMultiCertConfigLoader.cc index 2e7049643e8..314285d40a3 100644 --- a/src/iocore/net/QUICMultiCertConfigLoader.cc +++ b/src/iocore/net/QUICMultiCertConfigLoader.cc @@ -25,7 +25,6 @@ #include "P_SSLConfig.h" #include "iocore/net/QUICMultiCertConfigLoader.h" #include "iocore/net/quic/QUICConfig.h" -#include "mgmt/config/ConfigContextDiags.h" int QUICCertConfig::_config_id = 0; @@ -39,16 +38,14 @@ QUICCertConfig::startup() } void -QUICCertConfig::reconfigure(ConfigContext ctx) +QUICCertConfig::reconfigure() { bool retStatus = true; SSLConfig::scoped_config params; SSLCertLookup *lookup = new SSLCertLookup(); - CfgLoadInProgress(ctx, "(quic) %s loading ...", params->configFilePath); - QUICMultiCertConfigLoader loader(params); - auto errata = loader.load(lookup, _config_id == 0); + auto errata = loader.load(lookup); if (!lookup->is_valid || (errata.has_severity() && errata.severity() >= ERRATA_ERROR)) { retStatus = false; } @@ -60,13 +57,17 @@ QUICCertConfig::reconfigure(ConfigContext ctx) } if (!errata.empty()) { - ctx.log(errata); + errata.assign_annotation_glue_text("\n "); + errata.assign_severity_glue_text(" -> \n "); + bwprint(ts::bw_dbg, "\n{}", errata); + } else { + ts::bw_dbg = ""; } if (retStatus) { - CfgLoadComplete(ctx, "(quic) %s finished loading", params->configFilePath); + Note("(quic) %s finished loading%s", params->configFilePath, ts::bw_dbg.c_str()); } else { - CfgLoadFail(ctx, "(quic) %s failed to load", params->configFilePath); + Error("(quic) %s failed to load%s", params->configFilePath, ts::bw_dbg.c_str()); } } diff --git a/src/iocore/net/SSLClientCoordinator.cc b/src/iocore/net/SSLClientCoordinator.cc index e4af562498f..43b2e0c0a8e 100644 --- a/src/iocore/net/SSLClientCoordinator.cc +++ b/src/iocore/net/SSLClientCoordinator.cc @@ -24,52 +24,44 @@ #include "P_SSLClientCoordinator.h" #include "P_SSLConfig.h" #include "iocore/net/SSLSNIConfig.h" -#include "mgmt/config/ConfigRegistry.h" -#include "tscore/Filenames.h" #if TS_USE_QUIC == 1 #include "iocore/net/QUICMultiCertConfigLoader.h" #endif +std::unique_ptr> sslClientUpdate; + void -SSLClientCoordinator::reconfigure(ConfigContext reconf_ctx) +SSLClientCoordinator::reconfigure() { // The SSLConfig must have its configuration loaded before the SNIConfig. // The SSLConfig owns the client cert context storage and the SNIConfig will load // into it. - SSLConfig::reconfigure(reconf_ctx.add_dependent_ctx("SSLConfig")); - SNIConfig::reconfigure(reconf_ctx.add_dependent_ctx("SNIConfig")); - SSLCertificateConfig::reconfigure(reconf_ctx.add_dependent_ctx("SSLCertificateConfig")); + SSLConfig::reconfigure(); + SNIConfig::reconfigure(); + SSLCertificateConfig::reconfigure(); #if TS_USE_QUIC == 1 - QUICCertConfig::reconfigure(reconf_ctx.add_dependent_ctx("QUICCertConfig")); + QUICCertConfig::reconfigure(); #endif - reconf_ctx.complete("SSL configs reloaded"); } void SSLClientCoordinator::startup() { - // Register with ConfigRegistry — no primary file, this is a pure coordinator. - // File dependencies (sni.yaml, ssl_multicert.config) are tracked via add_file_and_node_dependency - // so (when enabled) the RPC handler can route injected YAML content to the coordinator's handler. - config::ConfigRegistry::Get_Instance().register_record_config( - "ssl_client_coordinator", // registry key - [](ConfigContext ctx) { SSLClientCoordinator::reconfigure(ctx); }, // reload handler - {"proxy.config.ssl.client.cert.path", // trigger records - "proxy.config.ssl.client.cert.filename", "proxy.config.ssl.client.private_key.path", - "proxy.config.ssl.client.private_key.filename", "proxy.config.ssl.keylog_file", "proxy.config.ssl.server.cert.path", - "proxy.config.ssl.server.private_key.path", "proxy.config.ssl.server.cert_chain.filename", - "proxy.config.ssl.server.session_ticket.enable"}); - - // Track sni.yaml — FileManager watches for mtime changes, record wired to trigger reload. - // When enabled, the "sni" dep_key makes this routable for RPC inline content. - config::ConfigRegistry::Get_Instance().add_file_and_node_dependency( - "ssl_client_coordinator", "sni", "proxy.config.ssl.servername.filename", ts::filename::SNI, false); - - // Track ssl_multicert.config — same pattern. - config::ConfigRegistry::Get_Instance().add_file_and_node_dependency( - "ssl_client_coordinator", "ssl_multicert", "proxy.config.ssl.server.multicert.filename", ts::filename::SSL_MULTICERT, false); - - // Sub-module initialization (order matters: SSLConfig before SNIConfig) + // The SSLConfig must have its configuration loaded before the SNIConfig. + // The SSLConfig owns the client cert context storage and the SNIConfig will load + // into it. + sslClientUpdate.reset(new ConfigUpdateHandler()); + sslClientUpdate->attach("proxy.config.ssl.client.cert.path"); + sslClientUpdate->attach("proxy.config.ssl.client.cert.filename"); + sslClientUpdate->attach("proxy.config.ssl.client.private_key.path"); + sslClientUpdate->attach("proxy.config.ssl.client.private_key.filename"); + sslClientUpdate->attach("proxy.config.ssl.keylog_file"); SSLConfig::startup(); + sslClientUpdate->attach("proxy.config.ssl.servername.filename"); SNIConfig::startup(); + sslClientUpdate->attach("proxy.config.ssl.server.multicert.filename"); + sslClientUpdate->attach("proxy.config.ssl.server.cert.path"); + sslClientUpdate->attach("proxy.config.ssl.server.private_key.path"); + sslClientUpdate->attach("proxy.config.ssl.server.cert_chain.filename"); + sslClientUpdate->attach("proxy.config.ssl.server.session_ticket.enable"); } diff --git a/src/iocore/net/SSLClientUtils.cc b/src/iocore/net/SSLClientUtils.cc index 453b971dd79..fe08b43a4f5 100644 --- a/src/iocore/net/SSLClientUtils.cc +++ b/src/iocore/net/SSLClientUtils.cc @@ -24,11 +24,9 @@ #include "P_SSLNetVConnection.h" #include "P_TLSKeyLogger.h" #include "SSLSessionCache.h" -#include "TLSCertCompression.h" #include "iocore/net/YamlSNIConfig.h" #include "iocore/net/SSLDiags.h" #include "tscore/ink_config.h" -#include "tscore/SimpleTokenizer.h" #include "tscore/Filenames.h" #include "tscore/X509HostnameValidator.h" @@ -249,18 +247,6 @@ SSLInitClientContext(const SSLConfigParams *params) } #endif - if (params->client_cert_compression_algorithms) { - std::vector algs; - SimpleTokenizer tok(params->client_cert_compression_algorithms, ','); - for (const char *token = tok.getNext(); token; token = tok.getNext()) { - algs.emplace_back(token); - } - if (register_certificate_compression_preference(client_ctx, algs) != 1) { - SSLError("invalid client certificate compression algorithm list in %s", ts::filename::RECORDS); - goto fail; - } - } - SSL_CTX_set_verify_depth(client_ctx, params->client_verify_depth); if (SSLConfigParams::init_ssl_ctx_cb) { SSLConfigParams::init_ssl_ctx_cb(client_ctx, false); diff --git a/src/iocore/net/SSLConfig.cc b/src/iocore/net/SSLConfig.cc index 95ab59891f9..b1b3c981067 100644 --- a/src/iocore/net/SSLConfig.cc +++ b/src/iocore/net/SSLConfig.cc @@ -43,37 +43,36 @@ #include "tscore/Layout.h" #include "records/RecHttp.h" #include "records/RecCore.h" -#include "mgmt/config/ConfigContextDiags.h" -#include "mgmt/config/ConfigRegistry.h" #include -#include #include #include #include -#include #include -int SSLConfig::config_index = 0; -int SSLConfig::configids[] = {0, 0}; -int SSLCertificateConfig::configid = 0; -int SSLTicketKeyConfig::configid = 0; -int SSLConfigParams::ssl_maxrecord = 0; -int SSLConfigParams::ssl_misc_max_iobuffer_size_index = 8; -bool SSLConfigParams::ssl_allow_client_renegotiation = false; -bool SSLConfigParams::ssl_ocsp_enabled = false; -int SSLConfigParams::ssl_ocsp_cache_timeout = 3600; -bool SSLConfigParams::ssl_ocsp_request_mode = false; -int SSLConfigParams::ssl_ocsp_request_timeout = 10; -int SSLConfigParams::ssl_ocsp_update_period = 60; -char *SSLConfigParams::ssl_ocsp_user_agent = nullptr; -int SSLConfigParams::ssl_handshake_timeout_in = 0; -int SSLConfigParams::origin_session_cache = 1; -size_t SSLConfigParams::origin_session_cache_size = 10240; -init_ssl_ctx_func SSLConfigParams::init_ssl_ctx_cb = nullptr; -load_ssl_file_func SSLConfigParams::load_ssl_file_cb = nullptr; -swoc::IPRangeSet *SSLConfigParams::proxy_protocol_ip_addrs = nullptr; -bool SSLConfigParams::ssl_ktls_enabled = false; +int SSLConfig::config_index = 0; +int SSLConfig::configids[] = {0, 0}; +int SSLCertificateConfig::configid = 0; +int SSLTicketKeyConfig::configid = 0; +int SSLConfigParams::ssl_maxrecord = 0; +int SSLConfigParams::ssl_misc_max_iobuffer_size_index = 8; +bool SSLConfigParams::ssl_allow_client_renegotiation = false; +bool SSLConfigParams::ssl_ocsp_enabled = false; +int SSLConfigParams::ssl_ocsp_cache_timeout = 3600; +bool SSLConfigParams::ssl_ocsp_request_mode = false; +int SSLConfigParams::ssl_ocsp_request_timeout = 10; +int SSLConfigParams::ssl_ocsp_update_period = 60; +char *SSLConfigParams::ssl_ocsp_user_agent = nullptr; +int SSLConfigParams::ssl_handshake_timeout_in = 0; +int SSLConfigParams::origin_session_cache = 1; +size_t SSLConfigParams::origin_session_cache_size = 10240; +size_t SSLConfigParams::session_cache_number_buckets = 1024; +bool SSLConfigParams::session_cache_skip_on_lock_contention = false; +size_t SSLConfigParams::session_cache_max_bucket_size = 100; +init_ssl_ctx_func SSLConfigParams::init_ssl_ctx_cb = nullptr; +load_ssl_file_func SSLConfigParams::load_ssl_file_cb = nullptr; +swoc::IPRangeSet *SSLConfigParams::proxy_protocol_ip_addrs = nullptr; +bool SSLConfigParams::ssl_ktls_enabled = false; const uint32_t EARLY_DATA_DEFAULT_SIZE = 16384; uint32_t SSLConfigParams::server_max_early_data = 0; @@ -85,12 +84,95 @@ char *SSLConfigParams::engine_conf_file = nullptr; namespace { +std::unique_ptr> sslTicketKey; + DbgCtl dbg_ctl_ssl_load{"ssl_load"}; DbgCtl dbg_ctl_ssl_config_updateCTX{"ssl_config_updateCTX"}; DbgCtl dbg_ctl_ssl_client_ctx{"ssl_client_ctx"}; } // end anonymous namespace +/** Determines the SSL session cache configuration value using a priority-based selection scheme. + * + * This function resolves the SSL session cache configuration by evaluating multiple potential + * configuration sources and selecting the one with the highest priority. The priority calculation + * combines two factors: + * + * Configuration Name Priority (base priority): + * - `proxy.config.ssl.session_cache.mode`: 3 (highest preference) + * - `proxy.config.ssl.session_cache.value`: 2 (medium preference) + * - `proxy.config.ssl.session_cache.enabled`: 1 (lowest preference) + * + * Configuration Source Priority (added to base priority): + * - Environment variable (`REC_SOURCE_ENV`): +0x30 (highest precedence) + * - Explicit configuration (`REC_SOURCE_EXPLICIT`): +0x20 (config file, API) + * - Plugin default (`REC_SOURCE_PLUGIN`): +0x10 (plugin changed the default value via TSMgmtIntCreate) + * - Built-in default (`REC_SOURCE_DEFAULT`): +0x00 (lowest precedence) + * + * Priority Calculation: + * `total_priority = base_priority + source_priority` + * + * Examples: + * - `mode` set via environment variable: 3 + 0x30 = 0x33 (highest possible) + * - `mode` set explicitly in config: 3 + 0x20 = 0x23 + * - `value` set via environment variable: 2 + 0x30 = 0x32 + * - `enabled` set explicitly in config: 1 + 0x20 = 0x21 + * + * The configuration with the highest total priority is selected. This ensures that: + * 1. Environment variables always override other sources. + * 2. Among configurations from the same source, `mode` > `value` > `enabled`. + * 3. Explicit configuration overrides plugin defaults and built-in defaults. + * + * @return The SSL session cache mode value. + */ +static int +get_ssl_session_cache_config() +{ + // + // TODO: in 11.x, we can simply remove this function and use only proxy.config.ssl.session_cache.mode. + // + + struct ConfigOption { + const char *name; ///< Configuration parameter name (e.g., "proxy.config.ssl.session_cache.mode"). + int value; ///< The configured value if explicitly set. + int priority; ///< The inherit priority of the config name, higher is more preferred. + }; + + /// The priority of the source. Higher is more preferred. + std::unordered_map source_priorities = { + {REC_SOURCE_ENV, 0x30}, + {REC_SOURCE_EXPLICIT, 0x20}, + {REC_SOURCE_PLUGIN, 0x10}, + {REC_SOURCE_DEFAULT, 0x0 }, + {REC_SOURCE_NULL, 0x0 }, // For completeness, no record should have this set. + }; + + std::array configs = { + { + {"proxy.config.ssl.session_cache.mode", 0, 0x3}, + {"proxy.config.ssl.session_cache.value", 0, 0x2}, + {"proxy.config.ssl.session_cache.enabled", 0, 0x1}, + } + }; + + // Loop over the config names, updating their priority score per their source. + auto *highest_priority_config = &configs[0]; + for (auto &config : configs) { + RecSourceT source; + if (RecGetRecordSource(config.name, &source) == REC_ERR_OKAY) { + config.priority += source_priorities[source]; + config.value = RecGetRecordInt(config.name).value_or(0); + if (config.priority > highest_priority_config->priority) { + highest_priority_config = &config; + } + } else { + // We need to update our logic here if any of these configs are removed. + ink_release_assert(false); + } + } + return highest_priority_config->value; +} + SSLConfigParams::SSLConfigParams() { ink_mutex_init(&ctxMapLock); @@ -116,8 +198,6 @@ SSLConfigParams::reset() clientKeyPath = clientCACertFilename = clientCACertPath = cipherSuite = client_cipherSuite = dhparamsFile = serverKeyPathOnly = clientKeyPathOnly = clientCertPathOnly = nullptr; ssl_ocsp_response_path_only = nullptr; - server_cert_compression_algorithms = nullptr; - client_cert_compression_algorithms = nullptr; server_tls13_cipher_suites = nullptr; client_tls13_cipher_suites = nullptr; server_groups_list = nullptr; @@ -129,8 +209,14 @@ SSLConfigParams::reset() verifyServerProperties = YamlSNIConfig::Property::NONE; ssl_ctx_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; ssl_client_ctx_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; - configExitOnLoadError = 1; - configLoadConcurrency = 1; + ssl_session_cache = SSL_SESSION_CACHE_MODE_SERVER_ATS_IMPL; + ssl_session_cache_size = 1024 * 100; + ssl_session_cache_num_buckets = 1024; // Sessions per bucket is ceil(ssl_session_cache_size / ssl_session_cache_num_buckets) + ssl_session_cache_skip_on_contention = 0; + ssl_session_cache_timeout = 0; + ssl_session_cache_auto_clear = 1; + configExitOnLoadError = 1; + clientCertExitOnLoadError = 0; } void @@ -154,13 +240,11 @@ SSLConfigParams::cleanup() ssl_ocsp_response_path_only = static_cast(ats_free_null(ssl_ocsp_response_path_only)); - server_cert_compression_algorithms = static_cast(ats_free_null(server_cert_compression_algorithms)); - client_cert_compression_algorithms = static_cast(ats_free_null(client_cert_compression_algorithms)); - server_tls13_cipher_suites = static_cast(ats_free_null(server_tls13_cipher_suites)); - client_tls13_cipher_suites = static_cast(ats_free_null(client_tls13_cipher_suites)); - server_groups_list = static_cast(ats_free_null(server_groups_list)); - client_groups_list = static_cast(ats_free_null(client_groups_list)); - keylog_file = static_cast(ats_free_null(keylog_file)); + server_tls13_cipher_suites = static_cast(ats_free_null(server_tls13_cipher_suites)); + client_tls13_cipher_suites = static_cast(ats_free_null(client_tls13_cipher_suites)); + server_groups_list = static_cast(ats_free_null(server_groups_list)); + client_groups_list = static_cast(ats_free_null(client_groups_list)); + keylog_file = static_cast(ats_free_null(keylog_file)); cleanupCTXTable(); reset(); @@ -256,7 +340,7 @@ SSLConfigParams::SetServerPolicy(const char *verify_server) } void -SSLConfigParams::initialize(ConfigContext ctx) +SSLConfigParams::initialize() { cleanup(); @@ -439,10 +523,6 @@ SSLConfigParams::initialize(ConfigContext ctx) configFilePath = ats_stringdup(RecConfigReadConfigPath("proxy.config.ssl.server.multicert.filename")); configExitOnLoadError = RecGetRecordInt("proxy.config.ssl.server.multicert.exit_on_load_fail").value_or(0); - configLoadConcurrency = RecGetRecordInt("proxy.config.ssl.server.multicert.concurrency").value_or(1); - if (configLoadConcurrency == 0) { - configLoadConcurrency = std::clamp(static_cast(std::thread::hardware_concurrency()), 1, 256); - } { auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.server.private_key.path")}; @@ -461,9 +541,25 @@ SSLConfigParams::initialize(ConfigContext ctx) // SSL session cache configurations ssl_origin_session_cache = RecGetRecordInt("proxy.config.ssl.origin_session_cache.enabled").value_or(0); ssl_origin_session_cache_size = RecGetRecordInt("proxy.config.ssl.origin_session_cache.size").value_or(0); + ssl_session_cache = get_ssl_session_cache_config(); + + ssl_session_cache_size = RecGetRecordInt("proxy.config.ssl.session_cache.size").value_or(0); + ssl_session_cache_num_buckets = RecGetRecordInt("proxy.config.ssl.session_cache.num_buckets").value_or(0); + ssl_session_cache_skip_on_contention = + RecGetRecordInt("proxy.config.ssl.session_cache.skip_cache_on_bucket_contention").value_or(0); + ssl_session_cache_timeout = RecGetRecordInt("proxy.config.ssl.session_cache.timeout").value_or(0); + ssl_session_cache_auto_clear = RecGetRecordInt("proxy.config.ssl.session_cache.auto_clear").value_or(0); SSLConfigParams::origin_session_cache = ssl_origin_session_cache; SSLConfigParams::origin_session_cache_size = ssl_origin_session_cache_size; + SSLConfigParams::session_cache_max_bucket_size = + static_cast(ceil(static_cast(ssl_session_cache_size) / ssl_session_cache_num_buckets)); + SSLConfigParams::session_cache_skip_on_lock_contention = ssl_session_cache_skip_on_contention; + SSLConfigParams::session_cache_number_buckets = ssl_session_cache_num_buckets; + + if (ssl_session_cache == SSL_SESSION_CACHE_MODE_SERVER_ATS_IMPL) { + session_cache = new SSLSessionCache(); + } if (ssl_origin_session_cache == 1 && ssl_origin_session_cache_size > 0 && origin_sess_cache == nullptr) { origin_sess_cache = new SSLOriginSessionCache(); @@ -499,13 +595,6 @@ SSLConfigParams::initialize(ConfigContext ctx) server_groups_list = ats_stringdup(rec_str); } - if (auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.server.cert_compression.algorithms")}; rec_str) { - server_cert_compression_algorithms = ats_stringdup(rec_str); - } - if (auto rec_str{RecGetRecordStringAlloc("proxy.config.ssl.client.cert_compression.algorithms")}; rec_str) { - client_cert_compression_algorithms = ats_stringdup(rec_str); - } - // ++++++++++++++++++++++++ Client part ++++++++++++++++++++ client_verify_depth = 7; @@ -565,7 +654,7 @@ SSLConfigParams::initialize(ConfigContext ctx) ssl_ktls_enabled = RecGetRecordInt("proxy.config.ssl.ktls.enabled").value_or(0); #ifndef SSL_OP_ENABLE_KTLS if (ssl_ktls_enabled) { - CfgLoadLog(ctx, DL_Error, "kTLS configured but not supported by OpenSSL library"); + Error("kTLS configured but not supported by OpenSSL library"); } #endif @@ -585,7 +674,6 @@ SSLConfigParams::initialize(ConfigContext ctx) Emergency("Can't initialize the SSL client, HTTPS in remap rules will not function"); } else { SSLError("Can't initialize the SSL client, HTTPS in remap rules will not function"); - ctx.log(DL_Warning, "Can't initialize the SSL client, HTTPS in remap rules will not function"); } } @@ -626,19 +714,17 @@ SSLConfig::startup() } void -SSLConfig::reconfigure(ConfigContext ctx) +SSLConfig::reconfigure() { - CfgLoadInProgress(ctx, "SSLConfig loading ..."); - - SSLConfigParams *params = new SSLConfigParams; + Dbg(dbg_ctl_ssl_load, "Reload SSLConfig"); + SSLConfigParams *params; + params = new SSLConfigParams; // start loading the next config - int loading_config_index = get_loading_config_index(); - + int loading_config_index = get_loading_config_index(); configids[loading_config_index] = configProcessor.set(configids[loading_config_index], params); - params->initialize(ctx); // re-read configuration + params->initialize(); // re-read configuration // Make the new config available for use. commit_config_id(); - ctx.complete("SSLConfig reloaded"); } SSLConfigParams * @@ -679,14 +765,12 @@ SSLCertificateConfig::startup() } bool -SSLCertificateConfig::reconfigure(ConfigContext ctx) +SSLCertificateConfig::reconfigure() { bool retStatus = true; SSLConfig::scoped_config params; SSLCertLookup *lookup = new SSLCertLookup(); - CfgLoadInProgress(ctx, "(ssl) %s loading ...", params->configFilePath); - // Test SSL certificate loading startup. With large numbers of certificates, reloading can take time, so delay // twice the healthcheck period to simulate a loading a large certificate set. if (is_action_tag_set("test.multicert.delay")) { @@ -695,7 +779,7 @@ SSLCertificateConfig::reconfigure(ConfigContext ctx) ink_hrtime_sleep(HRTIME_SECONDS(secs)); } - auto errata = SSLMultiCertConfigLoader(params).load(lookup, configid == 0); + auto errata = SSLMultiCertConfigLoader(params).load(lookup); if (!lookup->is_valid || (errata.has_severity() && errata.severity() >= ERRATA_ERROR)) { retStatus = false; } @@ -709,13 +793,17 @@ SSLCertificateConfig::reconfigure(ConfigContext ctx) } if (!errata.empty()) { - ctx.log(errata); + errata.assign_annotation_glue_text("\n "); + errata.assign_severity_glue_text(" -> \n "); + bwprint(ts::bw_dbg, "\n{}", errata); + } else { + ts::bw_dbg = ""; } if (retStatus) { - CfgLoadComplete(ctx, "(ssl) %s finished loading", params->configFilePath); + Note("(ssl) %s finished loading%s", params->configFilePath, ts::bw_dbg.c_str()); } else { - CfgLoadFail(ctx, "(ssl) %s failed to load", params->configFilePath); + Error("(ssl) %s failed to load%s", params->configFilePath, ts::bw_dbg.c_str()); } return retStatus; @@ -737,7 +825,7 @@ SSLCertificateConfig::release(SSLCertLookup *lookup) } bool -SSLTicketParams::LoadTicket(bool &nochange, ConfigContext ctx) +SSLTicketParams::LoadTicket(bool &nochange) { cleanup(); nochange = true; @@ -783,13 +871,13 @@ SSLTicketParams::LoadTicket(bool &nochange, ConfigContext ctx) return true; } if (!keyblock) { - CfgLoadFail(ctx, "Could not load ticket key from %s", ticket_key_filename); + Error("Could not load ticket key from %s", ticket_key_filename); return false; } default_global_keyblock = keyblock; load_time = time(nullptr); - CfgLoadDbg(ctx, dbg_ctl_ssl_load, "ticket key reloaded from %s", ticket_key_filename); + Dbg(dbg_ctl_ssl_load, "ticket key reloaded from %s", ticket_key_filename); #endif return true; } @@ -816,17 +904,9 @@ SSLTicketParams::LoadTicketData(char *ticket_data, int ticket_data_len) void SSLTicketKeyConfig::startup() { - config::ConfigRegistry::Get_Instance().register_record_config("ssl_ticket_key", // key - [](ConfigContext ctx) { // handler callback - CfgLoadLog(ctx, DL_Note, "SSL ticket key loading ..."); - if (SSLTicketKeyConfig::reconfigure(ctx)) { - ctx.complete("SSL ticket key reloaded"); - } else { - ctx.fail("Failed to reload SSL ticket key"); - } - }, - {"proxy.config.ssl.server.ticket_key.filename"}); + sslTicketKey.reset(new ConfigUpdateHandler()); + sslTicketKey->attach("proxy.config.ssl.server.ticket_key.filename"); SSLConfig::scoped_config params; if (!reconfigure() && params->configExitOnLoadError) { Fatal("Failed to load SSL ticket key file"); @@ -834,13 +914,13 @@ SSLTicketKeyConfig::startup() } bool -SSLTicketKeyConfig::reconfigure(ConfigContext ctx) +SSLTicketKeyConfig::reconfigure() { SSLTicketParams *ticketKey = new SSLTicketParams(); if (ticketKey) { bool nochange = false; - if (!ticketKey->LoadTicket(nochange, ctx)) { + if (!ticketKey->LoadTicket(nochange)) { delete ticketKey; return false; } diff --git a/src/iocore/net/SSLSNIConfig.cc b/src/iocore/net/SSLSNIConfig.cc index ce72e9d4bac..060f654774c 100644 --- a/src/iocore/net/SSLSNIConfig.cc +++ b/src/iocore/net/SSLSNIConfig.cc @@ -33,7 +33,6 @@ #include "P_SSLConfig.h" #include "iocore/net/SSLSNIConfig.h" #include "iocore/net/SNIActionItem.h" -#include "mgmt/config/ConfigContextDiags.h" #include "tscore/Diags.h" #include "tscore/Layout.h" #include "tscore/TSSystemState.h" @@ -263,20 +262,22 @@ SNIConfigParams::get(std::string_view servername, in_port_t dest_incoming_port) } bool -SNIConfigParams::initialize(ConfigContext ctx) +SNIConfigParams::initialize() { std::string sni_filename = RecConfigReadConfigPath("proxy.config.ssl.servername.filename"); - return initialize(sni_filename, ctx); + return initialize(sni_filename); } bool -SNIConfigParams::initialize(std::string const &sni_filename, ConfigContext ctx) +SNIConfigParams::initialize(std::string const &sni_filename) { - CfgLoadInProgress(ctx, "%s loading ...", sni_filename.c_str()); + Note("%s loading ...", sni_filename.c_str()); struct stat sbuf; if (stat(sni_filename.c_str(), &sbuf) == -1 && errno == ENOENT) { - CfgLoadLog(ctx, DL_Warning, "Loading SNI configuration - %s doesn't exist", sni_filename.c_str()); + Note("%s failed to load", sni_filename.c_str()); + Warning("Loading SNI configuration - filename: %s doesn't exist", sni_filename.c_str()); + return true; } @@ -288,7 +289,7 @@ SNIConfigParams::initialize(std::string const &sni_filename, ConfigContext ctx) if (TSSystemState::is_initializing()) { Emergency("%s failed to load: %s", sni_filename.c_str(), errMsg.str().c_str()); } else { - CfgLoadFail(ctx, "%s failed to load: %s", sni_filename.c_str(), errMsg.str().c_str()); + Error("%s failed to load: %s", sni_filename.c_str(), errMsg.str().c_str()); } return false; } @@ -318,22 +319,26 @@ SNIConfig::startup() } int -SNIConfig::reconfigure(ConfigContext ctx) +SNIConfig::reconfigure() { + Dbg(dbg_ctl_ssl, "Reload SNI file"); SNIConfigParams *params = new SNIConfigParams; - std::string sni_filename = RecConfigReadConfigPath("proxy.config.ssl.servername.filename"); - bool retStatus = params->initialize(sni_filename, ctx); - + bool retStatus = params->initialize(); if (retStatus) { _configid = configProcessor.set(_configid, params); if (SNIConfig::on_reconfigure) { SNIConfig::on_reconfigure(); } - CfgLoadComplete(ctx, "%s finished loading", sni_filename.c_str()); } else { delete params; - CfgLoadFail(ctx, "%s failed to load", sni_filename.c_str()); + } + + std::string sni_filename = RecConfigReadConfigPath("proxy.config.ssl.servername.filename"); + if (retStatus || TSSystemState::is_initializing()) { + Note("%s finished loading", sni_filename.c_str()); + } else { + Error("%s failed to load", sni_filename.c_str()); } return retStatus ? 1 : 0; diff --git a/src/iocore/net/SSLSessionCache.cc b/src/iocore/net/SSLSessionCache.cc index 2a68f268683..bf343593435 100644 --- a/src/iocore/net/SSLSessionCache.cc +++ b/src/iocore/net/SSLSessionCache.cc @@ -29,11 +29,302 @@ #include #include +#define SSLSESSIONCACHE_STRINGIFY0(x) #x +#define SSLSESSIONCACHE_STRINGIFY(x) SSLSESSIONCACHE_STRINGIFY0(x) +#define SSLSESSIONCACHE_LINENO SSLSESSIONCACHE_STRINGIFY(__LINE__) + +#ifdef DEBUG +#define PRINT_BUCKET(x) this->print(x " at " __FILE__ ":" SSLSESSIONCACHE_LINENO); +#else +#define PRINT_BUCKET(x) +#endif + namespace { DbgCtl dbg_ctl_ssl_origin_session_cache{"ssl.origin_session_cache"}; +DbgCtl dbg_ctl_ssl_session_cache{"ssl.session_cache"}; +DbgCtl dbg_ctl_ssl_session_cache_bucket{"ssl.session_cache.bucket"}; +DbgCtl dbg_ctl_ssl_session_cache_get{"ssl.session_cache.get"}; +DbgCtl dbg_ctl_ssl_session_cache_insert{"ssl.session_cache.insert"}; +DbgCtl dbg_ctl_ssl_session_cache_remove{"ssl.session_cache.remove"}; + } // end anonymous namespace +/* Session Cache */ +SSLSessionCache::SSLSessionCache() : nbuckets(SSLConfigParams::session_cache_number_buckets) +{ + Dbg(dbg_ctl_ssl_session_cache, "Created new ssl session cache %p with %zu buckets each with size max size %zu", this, nbuckets, + SSLConfigParams::session_cache_max_bucket_size); + + session_bucket = new SSLSessionBucket[nbuckets]; +} + +SSLSessionCache::~SSLSessionCache() +{ + delete[] session_bucket; +} + +int +SSLSessionCache::getSessionBuffer(const SSLSessionID &sid, char *buffer, int &len) const +{ + uint64_t hash = sid.hash(); + uint64_t target_bucket = hash % nbuckets; + SSLSessionBucket *bucket = &session_bucket[target_bucket]; + + return bucket->getSessionBuffer(sid, buffer, len); +} + +bool +SSLSessionCache::getSession(const SSLSessionID &sid, SSL_SESSION **sess, ssl_session_cache_exdata **data) const +{ + uint64_t hash = sid.hash(); + uint64_t target_bucket = hash % nbuckets; + SSLSessionBucket *bucket = &session_bucket[target_bucket]; + + if (dbg_ctl_ssl_session_cache.on()) { + char buf[sid.len * 2 + 1]; + sid.toString(buf, sizeof(buf)); + DbgPrint(dbg_ctl_ssl_session_cache_get, "SessionCache looking in bucket %" PRId64 " (%p) for session '%s' (hash: %" PRIX64 ").", + target_bucket, bucket, buf, hash); + } + + return bucket->getSession(sid, sess, data); +} + +void +SSLSessionCache::removeSession(const SSLSessionID &sid) +{ + uint64_t hash = sid.hash(); + uint64_t target_bucket = hash % nbuckets; + SSLSessionBucket *bucket = &session_bucket[target_bucket]; + + if (dbg_ctl_ssl_session_cache_remove.on()) { + char buf[sid.len * 2 + 1]; + sid.toString(buf, sizeof(buf)); + DbgPrint(dbg_ctl_ssl_session_cache_remove, + "SessionCache using bucket %" PRId64 " (%p): Removing session '%s' (hash: %" PRIX64 ").", target_bucket, bucket, buf, + hash); + } + Metrics::Counter::increment(ssl_rsb.session_cache_eviction); + + bucket->removeSession(sid); +} + +void +SSLSessionCache::insertSession(const SSLSessionID &sid, SSL_SESSION *sess, SSL *ssl) +{ + uint64_t hash = sid.hash(); + uint64_t target_bucket = hash % nbuckets; + SSLSessionBucket *bucket = &session_bucket[target_bucket]; + + if (dbg_ctl_ssl_session_cache_insert.on()) { + char buf[sid.len * 2 + 1]; + sid.toString(buf, sizeof(buf)); + DbgPrint(dbg_ctl_ssl_session_cache_insert, + "SessionCache using bucket %" PRId64 " (%p): Inserting session '%s' (hash: %" PRIX64 ").", target_bucket, bucket, buf, + hash); + } + + bucket->insertSession(sid, sess, ssl); +} + +void +SSLSessionBucket::insertSession(const SSLSessionID &id, SSL_SESSION *sess, SSL *ssl) +{ + std::shared_lock r_lock(mutex, std::try_to_lock); + if (!r_lock.owns_lock()) { + Metrics::Counter::increment(ssl_rsb.session_cache_lock_contention); + if (SSLConfigParams::session_cache_skip_on_lock_contention) { + return; + } + r_lock.lock(); + } + + // Don't insert if it is already there + if (bucket_map.find(id) != bucket_map.end()) { + return; + } + + r_lock.unlock(); + + size_t len = i2d_SSL_SESSION(sess, nullptr); // make sure we're not going to need more than SSL_MAX_SESSION_SIZE bytes + /* do not cache a session that's too big. */ + if (len > static_cast(SSL_MAX_SESSION_SIZE)) { + Dbg(dbg_ctl_ssl_session_cache, "Unable to save SSL session because size of %zd exceeds the max of %d", len, + SSL_MAX_SESSION_SIZE); + return; + } + + if (dbg_ctl_ssl_session_cache.on()) { + char buf[id.len * 2 + 1]; + id.toString(buf, sizeof(buf)); + DbgPrint(dbg_ctl_ssl_session_cache, "Inserting session '%s' to bucket %p.", buf, this); + } + + Ptr buf; + Ptr buf_exdata; + size_t len_exdata = sizeof(ssl_session_cache_exdata); + buf = new_IOBufferData(buffer_size_to_index(len, MAX_BUFFER_SIZE_INDEX), MEMALIGNED); + ink_release_assert(static_cast(buf->block_size()) >= len); + unsigned char *loc = reinterpret_cast(buf->data()); + i2d_SSL_SESSION(sess, &loc); + buf_exdata = new_IOBufferData(buffer_size_to_index(len, MAX_BUFFER_SIZE_INDEX), MEMALIGNED); + ink_release_assert(static_cast(buf_exdata->block_size()) >= len_exdata); + ssl_session_cache_exdata *exdata = reinterpret_cast(buf_exdata->data()); + // This could be moved to a function in charge of populating exdata + exdata->curve = (ssl == nullptr) ? 0 : SSLGetCurveNID(ssl); + + if (ssl == nullptr) { + exdata->group_name[0] = '\0'; + } else { + std::string_view group_name = SSLGetGroupName(ssl); + ink_release_assert(group_name.size() < sizeof(exdata->group_name)); + memcpy(exdata->group_name, group_name.data(), group_name.size()); + exdata->group_name[group_name.size()] = '\0'; + } + + std::unique_ptr ssl_session(new SSLSession(id, buf, len, buf_exdata)); + + std::unique_lock w_lock(mutex, std::try_to_lock); + if (!w_lock.owns_lock()) { + Metrics::Counter::increment(ssl_rsb.session_cache_lock_contention); + if (SSLConfigParams::session_cache_skip_on_lock_contention) { + return; + } + w_lock.lock(); + } + + PRINT_BUCKET("insertSession before") + if (bucket_map.size() >= SSLConfigParams::session_cache_max_bucket_size) { + Metrics::Counter::increment(ssl_rsb.session_cache_eviction); + removeOldestSession(w_lock); + } + + /* do the actual insert */ + auto node = ssl_session.release(); + bucket_que.enqueue(node); + bucket_map[id] = node; + + PRINT_BUCKET("insertSession after") +} + +int +SSLSessionBucket::getSessionBuffer(const SSLSessionID &id, char *buffer, int &len) +{ + int true_len = 0; + std::shared_lock lock(mutex, std::try_to_lock); + if (!lock.owns_lock()) { + Metrics::Counter::increment(ssl_rsb.session_cache_lock_contention); + if (SSLConfigParams::session_cache_skip_on_lock_contention) { + return true_len; + } + lock.lock(); + } + + auto entry = bucket_map.find(id); + if (buffer && entry != bucket_map.end()) { + true_len = entry->second->len_asn1_data; + const unsigned char *loc = reinterpret_cast(entry->second->asn1_data->data()); + if (true_len < len) { + len = true_len; + } + memcpy(buffer, loc, len); + return true_len; + } + return 0; +} + +bool +SSLSessionBucket::getSession(const SSLSessionID &id, SSL_SESSION **sess, ssl_session_cache_exdata **data) +{ + char buf[id.len * 2 + 1]; + buf[0] = '\0'; // just to be safe. + if (dbg_ctl_ssl_session_cache.on()) { + id.toString(buf, sizeof(buf)); + } + + Dbg(dbg_ctl_ssl_session_cache, "Looking for session with id '%s' in bucket %p", buf, this); + + std::shared_lock lock(mutex, std::try_to_lock); + if (!lock.owns_lock()) { + Metrics::Counter::increment(ssl_rsb.session_cache_lock_contention); + if (SSLConfigParams::session_cache_skip_on_lock_contention) { + return false; + } + lock.lock(); + } + + PRINT_BUCKET("getSession") + + auto entry = bucket_map.find(id); + if (entry == bucket_map.end()) { + Dbg(dbg_ctl_ssl_session_cache, "Session with id '%s' not found in bucket %p.", buf, this); + return false; + } + const unsigned char *loc = reinterpret_cast(entry->second->asn1_data->data()); + *sess = d2i_SSL_SESSION(nullptr, &loc, entry->second->len_asn1_data); + if (data != nullptr) { + ssl_session_cache_exdata *exdata = reinterpret_cast(entry->second->extra_data->data()); + *data = exdata; + } + return true; +} + +void inline SSLSessionBucket::print(const char *ref_str) const +{ + /* NOTE: This method assumes you're already holding the bucket lock */ + if (!dbg_ctl_ssl_session_cache_bucket.on()) { + return; + } + + fprintf(stderr, "-------------- BUCKET %p (%s) ----------------\n", this, ref_str); + fprintf(stderr, "Current Size: %ld, Max Size: %zd\n", bucket_map.size(), SSLConfigParams::session_cache_max_bucket_size); + fprintf(stderr, "Bucket: \n"); + + for (auto &x : bucket_map) { + char s_buf[2 * x.second->session_id.len + 1]; + x.second->session_id.toString(s_buf, sizeof(s_buf)); + fprintf(stderr, " %s\n", s_buf); + } +} + +void inline SSLSessionBucket::removeOldestSession(const std::unique_lock &lock) +{ + // Caller must hold the bucket shared_mutex with unique_lock. + ink_assert(lock.owns_lock()); + + PRINT_BUCKET("removeOldestSession before") + + while (bucket_que.head && bucket_que.size >= static_cast(SSLConfigParams::session_cache_max_bucket_size)) { + auto node = bucket_que.pop(); + bucket_map.erase(node->session_id); + delete node; + } + + PRINT_BUCKET("removeOldestSession after") +} + +void +SSLSessionBucket::removeSession(const SSLSessionID &id) +{ + // We can't bail on contention here because this session MUST be removed. + std::unique_lock lock(mutex); + + PRINT_BUCKET("removeSession before") + + auto entry = bucket_map.find(id); + if (entry != bucket_map.end()) { + auto node = entry->second; + bucket_que.remove(node); + bucket_map.erase(entry); + delete node; + } + + PRINT_BUCKET("removeSession after") + + return; +} + // Custom deleter for shared origin sessions void SSLSessDeleter(SSL_SESSION *_p) @@ -41,6 +332,11 @@ SSLSessDeleter(SSL_SESSION *_p) SSL_SESSION_free(_p); } +/* Session Bucket */ +SSLSessionBucket::SSLSessionBucket() {} + +SSLSessionBucket::~SSLSessionBucket() {} + SSLOriginSessionCache::SSLOriginSessionCache() {} SSLOriginSessionCache::~SSLOriginSessionCache() diff --git a/src/iocore/net/SSLSessionCache.h b/src/iocore/net/SSLSessionCache.h index ff78e8f0e1f..769c20adf1c 100644 --- a/src/iocore/net/SSLSessionCache.h +++ b/src/iocore/net/SSLSessionCache.h @@ -51,6 +51,155 @@ struct ssl_session_cache_exdata { char group_name[SSL_MAX_GROUP_NAME_SIZE] = {'\0'}; }; +inline void +hash_combine(uint64_t &seed, uint64_t hash) +{ + // using boost's version of hash combine, substituting magic number with a 64bit version + // https://www.boost.org/doc/libs/1_43_0/doc/html/hash/reference.html#boost.hash_combine + seed ^= hash + 0x9E3779B97F4A7C15 + (seed << 6) + (seed >> 2); +} + +struct SSLSessionID : public TSSslSessionID { + SSLSessionID(const unsigned char *s, size_t l) + { + len = l; + ink_release_assert(l <= sizeof(bytes)); + memcpy(bytes, s, l); + hash(); + } + + SSLSessionID(const SSLSessionID &other) + { + if (other.len) { + memcpy(bytes, other.bytes, other.len); + } + + len = other.len; + hash(); + } + + bool + operator<(const SSLSessionID &other) const + { + if (len != other.len) { + return len < other.len; + } + + return (memcmp(bytes, other.bytes, len) < 0); + } + + SSLSessionID & + operator=(const SSLSessionID &other) + { + if (other.len) { + memcpy(bytes, other.bytes, other.len); + } + + len = other.len; + return *this; + } + + bool + operator==(const SSLSessionID &other) const + { + if (len != other.len) { + return false; + } + + // memcmp returns 0 on equal + return (memcmp(bytes, other.bytes, len) == 0); + } + + const char * + toString(char *buf, size_t buflen) const + { + char *cur_pos = buf; + for (size_t i = 0; i < len && buflen > 0; ++i) { + if (buflen > 2) { // we have enough space for 3 bytes, 2 hex and 1 null terminator + snprintf(cur_pos, 3 /* including a null terminator */, "%02hhX", static_cast(bytes[i])); + cur_pos += 2; + buflen -= 2; + } else { // not enough space for any more hex bytes, just null terminate + *cur_pos = '\0'; + break; + } + } + return buf; + } + + uint64_t + hash() const + { + // because the session ids should be uniformly random, we can treat the bits as a hash value + // however we need to combine them if the length is longer than 64bits + if (len >= sizeof(uint64_t)) { + uint64_t seed = 0; + for (uint64_t i = 0; i < len; i += sizeof(uint64_t)) { + hash_combine(seed, static_cast(bytes[i])); + } + return seed; + } else if (len) { + return static_cast(bytes[0]); + } else { + return 0; + } + } +}; + +class SSLSession +{ +public: + SSLSessionID session_id; + Ptr asn1_data; /* this is the ASN1 representation of the SSL_CTX */ + size_t len_asn1_data; + Ptr extra_data; + + SSLSession(const SSLSessionID &id, const Ptr &ssl_asn1_data, size_t len_asn1, Ptr &exdata) + : session_id(id), asn1_data(ssl_asn1_data), len_asn1_data(len_asn1), extra_data(exdata) + { + } + + LINK(SSLSession, link); +}; + +class SSLSessionBucket +{ +public: + SSLSessionBucket(); + ~SSLSessionBucket(); + void insertSession(const SSLSessionID &sid, SSL_SESSION *sess, SSL *ssl); + bool getSession(const SSLSessionID &sid, SSL_SESSION **sess, ssl_session_cache_exdata **data); + int getSessionBuffer(const SSLSessionID &sid, char *buffer, int &len); + void removeSession(const SSLSessionID &sid); + +private: + /* these method must be used while hold the lock */ + void print(const char *) const; + void removeOldestSession(const std::unique_lock &lock); + + mutable ts::shared_mutex mutex; + CountQueue bucket_que; + std::map bucket_map; +}; + +class SSLSessionCache +{ +public: + bool getSession(const SSLSessionID &sid, SSL_SESSION **sess, ssl_session_cache_exdata **data) const; + int getSessionBuffer(const SSLSessionID &sid, char *buffer, int &len) const; + void insertSession(const SSLSessionID &sid, SSL_SESSION *sess, SSL *ssl); + void removeSession(const SSLSessionID &sid); + SSLSessionCache(); + ~SSLSessionCache(); + + SSLSessionCache(const SSLSessionCache &) = delete; + SSLSessionCache &operator=(const SSLSessionCache &) = delete; + +private: + SSLSessionBucket *session_bucket = nullptr; + size_t nbuckets; +}; + class SSLOriginSession { public: diff --git a/src/iocore/net/SSLStats.cc b/src/iocore/net/SSLStats.cc index 60e3b41d77e..fdec94b1e46 100644 --- a/src/iocore/net/SSLStats.cc +++ b/src/iocore/net/SSLStats.cc @@ -125,6 +125,7 @@ void SSLPeriodicMetricsUpdate() { SSLCertificateConfig::scoped_config certLookup; + SSLConfig::scoped_config sslConfig; int64_t sessions = 0; int64_t hits = 0; @@ -132,6 +133,43 @@ SSLPeriodicMetricsUpdate() int64_t timeouts = 0; Dbg(dbg_ctl_ssl, "Starting to update the new session metrics"); + + // Check if we're using the ATS session cache implementation rather than the + // OpenSSL internal cache. + bool const using_ats_session_cache = + sslConfig && sslConfig->ssl_session_cache == SSLConfigParams::SSL_SESSION_CACHE_MODE_SERVER_ATS_IMPL; + + if (using_ats_session_cache) { + // Most of the SSL_CTX_sess_*() metrics are inclusive of OpenSSL's + // "internal" cache *and* the ATS "external" cache. The exception is the + // SSL_CTX_sess_misses() metric, which curiously only counts OpenSSL + // internal misses. Therefore, to make that metric accurate for the + // situation where ATS manages sessions via its own cache, which is the + // default configuration (see proxy.config.ssl.session_cache.value), we + // have to add in the misses we've counted in the + // TLSSessionResumptionSupport.cc callback hooks. + + // We count timeouts as misses in TLSSessionResumptionSupport.cc for + // session_cache_miss, whereas OpenSSL tracks them separately and our + // user_agent_session_miss follows suit. + int64_t session_cache_timeouts = 0; + if (ssl_rsb.session_cache_timeout) { + session_cache_timeouts = Metrics::Counter::load(ssl_rsb.session_cache_timeout); + } +#if defined(OPENSSL_IS_BORINGSSL) + // On BoringSSL, all SSL_CTX_sess_*() functions always return 0 for the ATS + // external cache, making them unusable for monitoring. We currently address + // hits and misses because they are the most relevant metrics for session + // cache performance monitoring and should be treated as a pair. + if (ssl_rsb.session_cache_hit) { + hits = Metrics::Counter::load(ssl_rsb.session_cache_hit); + } +#endif + if (ssl_rsb.session_cache_miss) { + misses = Metrics::Counter::load(ssl_rsb.session_cache_miss); + misses -= (session_cache_timeouts > misses) ? 0 : session_cache_timeouts; + } + } if (certLookup) { const unsigned ctxCount = certLookup->count(); for (size_t i = 0; i < ctxCount; i++) { @@ -175,18 +213,6 @@ SSLInitializeStatistics() // For now, register with the librecords global sync. RecRegNewSyncStatSync(SSLPeriodicMetricsUpdate); - ssl_rsb.cert_compress_zlib = Metrics::Counter::createPtr("proxy.process.ssl.cert_compress.zlib"); - ssl_rsb.cert_compress_zlib_failure = Metrics::Counter::createPtr("proxy.process.ssl.cert_compress.zlib_failure"); - ssl_rsb.cert_decompress_zlib = Metrics::Counter::createPtr("proxy.process.ssl.cert_decompress.zlib"); - ssl_rsb.cert_decompress_zlib_failure = Metrics::Counter::createPtr("proxy.process.ssl.cert_decompress.zlib_failure"); - ssl_rsb.cert_compress_brotli = Metrics::Counter::createPtr("proxy.process.ssl.cert_compress.brotli"); - ssl_rsb.cert_compress_brotli_failure = Metrics::Counter::createPtr("proxy.process.ssl.cert_compress.brotli_failure"); - ssl_rsb.cert_decompress_brotli = Metrics::Counter::createPtr("proxy.process.ssl.cert_decompress.brotli"); - ssl_rsb.cert_decompress_brotli_failure = Metrics::Counter::createPtr("proxy.process.ssl.cert_decompress.brotli_failure"); - ssl_rsb.cert_compress_zstd = Metrics::Counter::createPtr("proxy.process.ssl.cert_compress.zstd"); - ssl_rsb.cert_compress_zstd_failure = Metrics::Counter::createPtr("proxy.process.ssl.cert_compress.zstd_failure"); - ssl_rsb.cert_decompress_zstd = Metrics::Counter::createPtr("proxy.process.ssl.cert_decompress.zstd"); - ssl_rsb.cert_decompress_zstd_failure = Metrics::Counter::createPtr("proxy.process.ssl.cert_decompress.zstd_failure"); ssl_rsb.early_data_received_count = Metrics::Counter::createPtr("proxy.process.ssl.early_data_received"); ssl_rsb.error_async = Metrics::Counter::createPtr("proxy.process.ssl.ssl_error_async"); ssl_rsb.error_ssl = Metrics::Counter::createPtr("proxy.process.ssl.ssl_error_ssl"); @@ -214,6 +240,7 @@ SSLInitializeStatistics() ssl_rsb.session_cache_lock_contention = Metrics::Counter::createPtr("proxy.process.ssl.ssl_session_cache_lock_contention"); ssl_rsb.session_cache_miss = Metrics::Counter::createPtr("proxy.process.ssl.ssl_session_cache_miss"); ssl_rsb.session_cache_new_session = Metrics::Counter::createPtr("proxy.process.ssl.ssl_session_cache_new_session"); + ssl_rsb.session_cache_timeout = Metrics::Counter::createPtr("proxy.process.ssl.ssl_session_cache_timeout"); ssl_rsb.total_attempts_handshake_count_in = Metrics::Counter::createPtr("proxy.process.ssl.total_attempts_handshake_count_in"); ssl_rsb.total_attempts_handshake_count_out = Metrics::Counter::createPtr("proxy.process.ssl.total_attempts_handshake_count_out"); ssl_rsb.total_dyn_def_tls_record_count = Metrics::Counter::createPtr("proxy.process.ssl.default_record_size_count"); diff --git a/src/iocore/net/SSLStats.h b/src/iocore/net/SSLStats.h index 50ae6f89dee..82b84445302 100644 --- a/src/iocore/net/SSLStats.h +++ b/src/iocore/net/SSLStats.h @@ -37,18 +37,6 @@ using ts::Metrics; // for ssl_rsb.total_ticket_keys_renewed needs this initialization, but lets be // consistent at least. struct SSLStatsBlock { - Metrics::Counter::AtomicType *cert_compress_zlib = nullptr; - Metrics::Counter::AtomicType *cert_compress_zlib_failure = nullptr; - Metrics::Counter::AtomicType *cert_decompress_zlib = nullptr; - Metrics::Counter::AtomicType *cert_decompress_zlib_failure = nullptr; - Metrics::Counter::AtomicType *cert_compress_brotli = nullptr; - Metrics::Counter::AtomicType *cert_compress_brotli_failure = nullptr; - Metrics::Counter::AtomicType *cert_decompress_brotli = nullptr; - Metrics::Counter::AtomicType *cert_decompress_brotli_failure = nullptr; - Metrics::Counter::AtomicType *cert_compress_zstd = nullptr; - Metrics::Counter::AtomicType *cert_compress_zstd_failure = nullptr; - Metrics::Counter::AtomicType *cert_decompress_zstd = nullptr; - Metrics::Counter::AtomicType *cert_decompress_zstd_failure = nullptr; Metrics::Counter::AtomicType *early_data_received_count = nullptr; Metrics::Counter::AtomicType *error_async = nullptr; Metrics::Counter::AtomicType *error_ssl = nullptr; @@ -74,6 +62,7 @@ struct SSLStatsBlock { Metrics::Counter::AtomicType *session_cache_hit = nullptr; Metrics::Counter::AtomicType *session_cache_lock_contention = nullptr; Metrics::Counter::AtomicType *session_cache_miss = nullptr; + Metrics::Counter::AtomicType *session_cache_timeout = nullptr; Metrics::Counter::AtomicType *session_cache_new_session = nullptr; Metrics::Counter::AtomicType *sni_name_set_failure = nullptr; Metrics::Counter::AtomicType *total_attempts_handshake_count_in = nullptr; @@ -111,11 +100,11 @@ struct SSLStatsBlock { Metrics::Counter::AtomicType *user_agent_version_too_low = nullptr; Metrics::Counter::AtomicType *user_agent_wrong_version = nullptr; - // Note: The following user_agent_session_* metrics are implemented as Gauge - // types even though they semantically represent cumulative counters. This is - // because they are periodically synchronized from external counter sources - // (OpenSSL's built-in session cache) and need to be "set" to specific values - // rather than incremented. From a monitoring perspective, these should be + // Note: The following user_agent_session_* metrics are implemented as Gauge types + // even though they semantically represent cumulative counters. This is because + // they are periodically synchronized from external counter sources (OpenSSL's + // built-in session cache or ATS's session cache) and need to be "set" to specific + // values rather than incremented. From a monitoring perspective, these should be // treated as counters for calculating rates. Metrics::Gauge::AtomicType *user_agent_session_hit = nullptr; Metrics::Gauge::AtomicType *user_agent_session_miss = nullptr; diff --git a/src/iocore/net/SSLUtils.cc b/src/iocore/net/SSLUtils.cc index 397ca0b0b8c..6d262c1ca6b 100644 --- a/src/iocore/net/SSLUtils.cc +++ b/src/iocore/net/SSLUtils.cc @@ -30,10 +30,8 @@ #include "SSLSessionCache.h" #include "SSLSessionTicket.h" #include "SSLDynlock.h" // IWYU pragma: keep - for ssl_dyn_* -#include "TLSCertCompression.h" #include "iocore/net/SSLMultiCertConfigLoader.h" -#include "config/ssl_multicert.h" #include "iocore/net/SSLAPIHooks.h" #include "iocore/net/SSLDiags.h" #include "iocore/net/TLSSessionResumptionSupport.h" @@ -70,8 +68,6 @@ #include #endif -#include -#include #include #include #include @@ -80,7 +76,19 @@ using namespace std::literals; -static constexpr char SSL_CERT_SEPARATE_DELIM = ','; +// ssl_multicert.config field names: +static constexpr std::string_view SSL_IP_TAG("dest_ip"sv); +static constexpr std::string_view SSL_CERT_TAG("ssl_cert_name"sv); +static constexpr std::string_view SSL_PRIVATE_KEY_TAG("ssl_key_name"sv); +static constexpr std::string_view SSL_OCSP_RESPONSE_TAG("ssl_ocsp_name"sv); +static constexpr std::string_view SSL_CA_TAG("ssl_ca_name"sv); +static constexpr std::string_view SSL_ACTION_TAG("action"sv); +static constexpr std::string_view SSL_ACTION_TUNNEL_TAG("tunnel"sv); +static constexpr std::string_view SSL_SESSION_TICKET_ENABLED("ssl_ticket_enabled"sv); +static constexpr std::string_view SSL_SESSION_TICKET_NUMBER("ssl_ticket_number"sv); +static constexpr std::string_view SSL_KEY_DIALOG("ssl_key_dialog"sv); +static constexpr std::string_view SSL_SERVERNAME("dest_fqdn"sv); +static constexpr char SSL_CERT_SEPARATE_DELIM = ','; #ifndef evp_md_func #ifdef OPENSSL_NO_SHA256 @@ -90,6 +98,8 @@ static constexpr char SSL_CERT_SEPARATE_DELIM = ','; #endif #endif +SSLSessionCache *session_cache; // declared extern in P_SSLConfig.h + static int ssl_vc_index = -1; static ink_mutex *mutex_buf = nullptr; @@ -100,12 +110,6 @@ static DbgCtl dbg_ctl_ssl_session_cache{"ssl.session_cache"}; static DbgCtl dbg_ctl_ssl_error{"ssl.error"}; static DbgCtl dbg_ctl_ssl_verify{"ssl_verify"}; -#if TS_HAS_TLS_SESSION_TICKET -static bool ssl_context_enable_ticket_callback(SSL_CTX *ctx); -static bool ssl_apply_sni_session_ticket_properties(SSL *ssl); -static bool ssl_set_session_ticket_number(SSL *ssl, size_t num_tickets); -#endif - /* Using pthread thread ID and mutex functions directly, instead of * ATS this_ethread / ProxyMutex, so that other linked libraries * may use pthreads and openssl without confusing us here. (TS-2271). @@ -180,6 +184,92 @@ SSL_CTX_add_extra_chain_cert_file(SSL_CTX *ctx, const char *chainfile) return SSL_CTX_add_extra_chain_cert_bio(ctx, bio.get()); } +static SSL_SESSION * +#if defined(LIBRESSL_VERSION_NUMBER) +ssl_get_cached_session(SSL *ssl, unsigned char *id, int len, int *copy) +#else +ssl_get_cached_session(SSL *ssl, const unsigned char *id, int len, int *copy) +#endif +{ + TLSSessionResumptionSupport *srs = TLSSessionResumptionSupport::getInstance(ssl); + + ink_assert(srs); + if (srs) { + return srs->getSession(ssl, id, len, copy); + } + + return nullptr; +} + +static int +ssl_new_cached_session(SSL *ssl, SSL_SESSION *sess) +{ +#ifdef TLS1_3_VERSION + if (SSL_SESSION_get_protocol_version(sess) == TLS1_3_VERSION) { + return 0; + } +#endif + + unsigned int len = 0; + const unsigned char *id = SSL_SESSION_get_id(sess, &len); + + SSLSessionID sid(id, len); + + if (diags()->on()) { + static DbgCtl dbg_ctl("ssl_session_cache.insert"); + if (dbg_ctl.tag_on()) { + char printable_buf[(len * 2) + 1]; + + sid.toString(printable_buf, sizeof(printable_buf)); + DbgPrint(dbg_ctl, "ssl_new_cached_session session '%s' and context %p", printable_buf, SSL_get_SSL_CTX(ssl)); + } + } + + Metrics::Counter::increment(ssl_rsb.session_cache_new_session); + session_cache->insertSession(sid, sess, ssl); + + // Call hook after new session is created + APIHook *hook = SSLAPIHooks::instance()->get(TSSslHookInternalID(TS_SSL_SESSION_HOOK)); + while (hook) { + hook->invoke(TS_EVENT_SSL_SESSION_NEW, &sid); + hook = hook->m_link.next; + } + + return 0; +} + +static void +ssl_rm_cached_session(SSL_CTX * /* ctx ATS_UNUSED */, SSL_SESSION *sess) +{ +#ifdef TLS1_3_VERSION + if (SSL_SESSION_get_protocol_version(sess) == TLS1_3_VERSION) { + return; + } +#endif + + unsigned int len = 0; + const unsigned char *id = SSL_SESSION_get_id(sess, &len); + SSLSessionID sid(id, len); + + // Call hook before session is removed + APIHook *hook = SSLAPIHooks::instance()->get(TSSslHookInternalID(TS_SSL_SESSION_HOOK)); + while (hook) { + hook->invoke(TS_EVENT_SSL_SESSION_REMOVE, &sid); + hook = hook->m_link.next; + } + + if (diags()->on()) { + static DbgCtl dbg_ctl("ssl_session_cache.remove"); + if (dbg_ctl.tag_on()) { + char printable_buf[(len * 2) + 1]; + sid.toString(printable_buf, sizeof(printable_buf)); + DbgPrint(dbg_ctl, "ssl_rm_cached_session cached session '%s'", printable_buf); + } + } + + session_cache->removeSession(sid); +} + // Callback function for verifying client certificate static int ssl_verify_client_callback(int preverify_ok, X509_STORE_CTX *ctx) @@ -313,9 +403,13 @@ ssl_cert_callback(SSL *ssl, [[maybe_unused]] void *arg) setClientCertCACerts(ssl, sslnetvc->get_ca_cert_file(), sslnetvc->get_ca_cert_dir()); } - if (!ssl_apply_sni_session_ticket_properties(ssl)) { - retval = 0; - } + // Reset the ticket callback if needed + SSL_CTX *ctx = SSL_get_SSL_CTX(ssl); +#ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB + SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx, ssl_callback_session_ticket); +#else + SSL_CTX_set_tlsext_ticket_key_cb(ctx, ssl_callback_session_ticket); +#endif } #endif @@ -436,26 +530,6 @@ DH_get_2048_256() } #endif -bool -SSLMultiCertConfigLoader::_enable_cert_compression(SSL_CTX *ctx) -{ - std::vector algs; - - if (this->_params->server_cert_compression_algorithms) { - SimpleTokenizer tok(this->_params->server_cert_compression_algorithms, ','); - for (const char *token = tok.getNext(); token; token = tok.getNext()) { - algs.emplace_back(token); - } - } - - if (register_certificate_compression_preference(ctx, algs) == 1) { - return true; - } else { - SSLError("Failed to enable certificate compression"); - return false; - } -} - bool SSLMultiCertConfigLoader::_enable_ktls([[maybe_unused]] SSL_CTX *ctx) { @@ -515,77 +589,6 @@ ssl_context_enable_dhe(const char *dhparams_file, SSL_CTX *ctx) return ctx; } -#if TS_HAS_TLS_SESSION_TICKET -static bool -ssl_context_enable_ticket_callback(SSL_CTX *ctx) -{ -#ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB - if (SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx, ssl_callback_session_ticket) == 0) { -#else - if (SSL_CTX_set_tlsext_ticket_key_cb(ctx, ssl_callback_session_ticket) == 0) { -#endif - Error("failed to set session ticket callback"); - return false; - } - return true; -} - -static bool -ssl_set_session_ticket_number(SSL *ssl, size_t num_tickets) -{ -#if defined(OPENSSL_IS_BORINGSSL) - // BoringSSL only exposes SSL_CTX_set_num_tickets(), so the per-connection - // sni.yaml override is not available here. - (void)ssl; - (void)num_tickets; - return true; -#else - return SSL_set_num_tickets(ssl, num_tickets) == 1; -#endif -} - -static bool -ssl_apply_sni_session_ticket_properties(SSL *ssl) -{ - auto snis = TLSSNISupport::getInstance(ssl); - if (snis == nullptr) { - return true; - } - - auto const &hints = snis->hints_from_sni; - if (!hints.ssl_ticket_enabled.has_value() && !hints.ssl_ticket_number.has_value()) { - return true; - } - - std::optional num_tickets; - - if (hints.ssl_ticket_enabled.has_value()) { - if (hints.ssl_ticket_enabled.value() != 0) { - SSL_clear_options(ssl, SSL_OP_NO_TICKET); - Dbg(dbg_ctl_ssl_load, "Enabled session tickets due to sni.yaml override"); - } else { - SSL_set_options(ssl, SSL_OP_NO_TICKET); - num_tickets = 0; - Dbg(dbg_ctl_ssl_load, "Disabled session tickets due to sni.yaml override"); - } - } - - if ((!hints.ssl_ticket_enabled.has_value() || hints.ssl_ticket_enabled.value() != 0) && hints.ssl_ticket_number.has_value()) { - num_tickets = hints.ssl_ticket_number.value() > 0 ? static_cast(hints.ssl_ticket_number.value()) : 0; - } - - if (num_tickets.has_value()) { - if (!ssl_set_session_ticket_number(ssl, num_tickets.value())) { - Error("failed to set session ticket number from sni.yaml"); - return false; - } - Dbg(dbg_ctl_ssl_load, "Set session ticket number from sni.yaml to %zu", num_tickets.value()); - } - - return true; -} -#endif - static ssl_ticket_key_block * ssl_context_enable_tickets(SSL_CTX *ctx, const char *ticket_key_path) { @@ -599,10 +602,15 @@ ssl_context_enable_tickets(SSL_CTX *ctx, const char *ticket_key_path) Metrics::Counter::increment(ssl_rsb.total_ticket_keys_renewed); } - // Setting the callback can only fail if OpenSSL does not recognize the - // SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB constant. we set the callback first - // so that we don't leave a ticket_key pointer attached if it fails. - if (!ssl_context_enable_ticket_callback(ctx)) { +// Setting the callback can only fail if OpenSSL does not recognize the +// SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB constant. we set the callback first +// so that we don't leave a ticket_key pointer attached if it fails. +#ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB + if (SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx, ssl_callback_session_ticket) == 0) { +#else + if (SSL_CTX_set_tlsext_ticket_key_cb(ctx, ssl_callback_session_ticket) == 0) { +#endif + Error("failed to set session ticket callback"); ticket_block_free(keyblock); return nullptr; } @@ -1236,7 +1244,9 @@ SSLMultiCertConfigLoader::init_server_ssl_ctx(CertLoadData const &data, const SS SSL_CTX_set_max_proto_version(ctx, ver); } - SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF | SSL_SESS_CACHE_NO_INTERNAL); + if (!this->_setup_session_cache(ctx)) { + goto fail; + } #ifdef SSL_MODE_RELEASE_BUFFERS Dbg(dbg_ctl_ssl_load, "enabling SSL_MODE_RELEASE_BUFFERS"); @@ -1267,12 +1277,6 @@ SSLMultiCertConfigLoader::init_server_ssl_ctx(CertLoadData const &data, const SS } } -#if TS_HAS_TLS_SESSION_TICKET - if (!ssl_context_enable_ticket_callback(ctx)) { - goto fail; - } -#endif - if (!this->_setup_client_cert_verification(ctx)) { goto fail; } @@ -1293,10 +1297,6 @@ SSLMultiCertConfigLoader::init_server_ssl_ctx(CertLoadData const &data, const SS goto fail; } - if (!this->_enable_cert_compression(ctx)) { - goto fail; - } - if (!this->_enable_ktls(ctx)) { goto fail; } @@ -1349,8 +1349,46 @@ SSLMultiCertConfigLoader::init_server_ssl_ctx(CertLoadData const &data, const SS } bool -SSLMultiCertConfigLoader::_setup_session_cache(SSL_CTX * /* ctx ATS_UNUSED */) +SSLMultiCertConfigLoader::_setup_session_cache(SSL_CTX *ctx) { + const SSLConfigParams *params = this->_params; + + Dbg(dbg_ctl_ssl_session_cache, + "ssl context=%p: using session cache options, enabled=%d, size=%d, num_buckets=%d, " + "skip_on_contention=%d, timeout=%d, auto_clear=%d", + ctx, params->ssl_session_cache, params->ssl_session_cache_size, params->ssl_session_cache_num_buckets, + params->ssl_session_cache_skip_on_contention, params->ssl_session_cache_timeout, params->ssl_session_cache_auto_clear); + + if (params->ssl_session_cache_timeout) { + SSL_CTX_set_timeout(ctx, params->ssl_session_cache_timeout); + } + + int additional_cache_flags = 0; + additional_cache_flags |= (params->ssl_session_cache_auto_clear == 0) ? SSL_SESS_CACHE_NO_AUTO_CLEAR : 0; + + switch (params->ssl_session_cache) { + case SSLConfigParams::SSL_SESSION_CACHE_MODE_OFF: + Dbg(dbg_ctl_ssl_session_cache, "disabling SSL session cache"); + + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF | SSL_SESS_CACHE_NO_INTERNAL); + break; + case SSLConfigParams::SSL_SESSION_CACHE_MODE_SERVER_OPENSSL_IMPL: + Dbg(dbg_ctl_ssl_session_cache, "enabling SSL session cache with OpenSSL implementation"); + + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER | additional_cache_flags); + SSL_CTX_sess_set_cache_size(ctx, params->ssl_session_cache_size); + break; + case SSLConfigParams::SSL_SESSION_CACHE_MODE_SERVER_ATS_IMPL: { + Dbg(dbg_ctl_ssl_session_cache, "enabling SSL session cache with ATS implementation"); + /* Add all the OpenSSL callbacks */ + SSL_CTX_sess_set_new_cb(ctx, ssl_new_cached_session); + SSL_CTX_sess_set_remove_cb(ctx, ssl_rm_cached_session); + SSL_CTX_sess_set_get_cb(ctx, ssl_get_cached_session); + + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER | SSL_SESS_CACHE_NO_INTERNAL | additional_cache_flags); + break; + } + } return true; } @@ -1372,7 +1410,7 @@ SSLMultiCertConfigLoader::_setup_dialog(SSL_CTX *ctx, const SSLMultiCertConfigPa } else if (strcmp(sslMultCertSettings->dialog, "builtin") == 0) { passwd_cb = ssl_private_key_passphrase_callback_builtin; } else { // unknown config - SSLError("unknown ssl_key_dialog configuration value '%s'", (const char *)sslMultCertSettings->dialog); + SSLError("unknown %s configuration value '%s'", SSL_KEY_DIALOG.data(), (const char *)sslMultCertSettings->dialog); return false; } SSL_CTX_set_default_passwd_cb(ctx, passwd_cb); @@ -1388,7 +1426,7 @@ SSLMultiCertConfigLoader::_set_verify_path(SSL_CTX *ctx, const SSLMultiCertConfi // serverCACertFilename if that is not nullptr. Otherwise, it uses the hashed // symlinks in serverCACertPath. // - // if ssl_ca_name is NOT configured for this cert in ssl_multicert.yaml + // if ssl_ca_name is NOT configured for this cert in ssl_multicert.config // AND // if proxy.config.ssl.CA.cert.filename and proxy.config.ssl.CA.cert.path // are configured @@ -1626,20 +1664,11 @@ SSLMultiCertConfigLoader::_store_ssl_ctx(SSLCertLookup *lookup, const shared_SSL SSLMultiCertConfigLoader::CertLoadData data; if (!this->_prep_ssl_ctx(sslMultCertSettings, data, common_names, unique_names)) { - { - std::lock_guard lock(_loader_mutex); - lookup->is_valid = false; - } + lookup->is_valid = false; return false; } std::vector ctxs = this->init_server_ssl_ctx(data, sslMultCertSettings.get()); - - // Serialize all mutations to the shared SSLCertLookup. - // The expensive work above (_prep_ssl_ctx + init_server_ssl_ctx) runs - // without the lock, allowing parallel cert loading across threads. - std::lock_guard lock(_loader_mutex); - for (const auto &loadingctx : ctxs) { if (!sslMultCertSettings || !this->_store_single_ssl_ctx(lookup, sslMultCertSettings, shared_SSL_CTX{loadingctx.ctx, SSL_CTX_free}, loadingctx.ctx_type, @@ -1817,139 +1846,158 @@ SSLMultiCertConfigLoader::_store_single_ssl_ctx(SSLCertLookup *lookup, const sha return ctx.get(); } -swoc::Errata -SSLMultiCertConfigLoader::load(SSLCertLookup *lookup, bool firstLoad) +static bool +ssl_extract_certificate(const matcher_line *line_info, SSLMultiCertConfigParams *sslMultCertSettings) { - const SSLConfigParams *params = this->_params; + for (int i = 0; i < MATCHER_MAX_TOKENS; ++i) { + const char *label; + const char *value; - Note("(%s) %s loading ...", this->_debug_tag(), ts::filename::SSL_MULTICERT); + label = line_info->line[0][i]; + value = line_info->line[1][i]; - // Optionally elevate/allow file access to read root-only - // certificates. The destructor will drop privilege for us. - uint32_t elevate_setting = 0; - elevate_setting = RecGetRecordInt("proxy.config.ssl.cert.load_elevated").value_or(0); - ElevateAccess elevate_access(elevate_setting ? ElevateAccess::FILE_PRIVILEGE : 0); + if (label == nullptr) { + continue; + } + Dbg(dbg_ctl_ssl_load, "Extracting certificate label: %s, value: %s", label, value); - // Guard against nullptr configFilePath which can happen if records aren't initialized. - if (params->configFilePath == nullptr) { - return swoc::Errata(ERRATA_WARN, "No SSL certificate configuration file path configured"); - } + if (strcasecmp(label, SSL_IP_TAG) == 0) { + sslMultCertSettings->addr = ats_strdup(value); + } - config::SSLMultiCertParser parser; - config::ConfigResult parse_result = parser.parse(params->configFilePath); - if (!parse_result.ok()) { - return std::move(parse_result.errata); - } + if (strcasecmp(label, SSL_CERT_TAG) == 0) { + sslMultCertSettings->cert = ats_strdup(value); + } - swoc::Errata errata(ERRATA_NOTE); + if (strcasecmp(label, SSL_CA_TAG) == 0) { + sslMultCertSettings->ca = ats_strdup(value); + } - static constexpr int MAX_LOAD_THREADS = 256; + if (strcasecmp(label, SSL_PRIVATE_KEY_TAG) == 0) { + sslMultCertSettings->key = ats_strdup(value); + } - int num_threads = params->configLoadConcurrency; - if (firstLoad) { - num_threads = std::clamp(static_cast(std::thread::hardware_concurrency()), 1, MAX_LOAD_THREADS); - } - num_threads = std::min(num_threads, static_cast(parse_result.value.size())); + if (strcasecmp(label, SSL_OCSP_RESPONSE_TAG) == 0) { + sslMultCertSettings->ocsp_response = ats_strdup(value); + } - if (num_threads > 1 && parse_result.value.size() > 1) { - std::size_t bucket_size = parse_result.value.size() / num_threads; - std::size_t remainder = parse_result.value.size() % num_threads; - auto current = parse_result.value.cbegin(); + if (strcasecmp(label, SSL_SESSION_TICKET_ENABLED) == 0) { + sslMultCertSettings->session_ticket_enabled = atoi(value); + } - std::vector threads; - Note("(%s) loading %zu certs with %d threads", this->_debug_tag(), parse_result.value.size(), num_threads); + if (strcasecmp(label, SSL_SESSION_TICKET_NUMBER) == 0) { + sslMultCertSettings->session_ticket_number = atoi(value); + } - for (int t = 0; t < num_threads; ++t) { - std::size_t this_bucket = bucket_size + (static_cast(t) < remainder ? 1 : 0); - auto end = current + this_bucket; - int base_index = static_cast(std::distance(parse_result.value.cbegin(), current)); - threads.emplace_back(&SSLMultiCertConfigLoader::_load_items, this, lookup, current, end, base_index, std::ref(errata)); - current = end; + if (strcasecmp(label, SSL_KEY_DIALOG) == 0) { + sslMultCertSettings->dialog = ats_strdup(value); } - for (auto &th : threads) { - th.join(); + if (strcasecmp(label, SSL_SERVERNAME) == 0) { + sslMultCertSettings->servername = ats_strdup(value); } - Note("(%s) loaded %zu certs in %d threads", this->_debug_tag(), parse_result.value.size(), num_threads); - } else { - _load_items(lookup, parse_result.value.cbegin(), parse_result.value.cend(), 0, errata); - Note("(%s) loaded %zu certs (single-threaded)", this->_debug_tag(), parse_result.value.size()); + if (strcasecmp(label, SSL_ACTION_TAG) == 0) { + if (strcasecmp(SSL_ACTION_TUNNEL_TAG, value) == 0) { + sslMultCertSettings->opt = SSLCertContextOption::OPT_TUNNEL; + } else { + Error("Unrecognized action for %s", SSL_ACTION_TAG.data()); + return false; + } + } } - - // We *must* have a default context even if it can't possibly work. The default context is used to - // bootstrap the SSL handshake so that we can subsequently do the SNI lookup to switch to the real - // context. - if (lookup->ssl_default == nullptr) { - shared_SSLMultiCertConfigParams sslMultiCertSettings(new SSLMultiCertConfigParams); - sslMultiCertSettings->addr = ats_strdup("*"); - if (!this->_store_ssl_ctx(lookup, sslMultiCertSettings)) { - errata.note(ERRATA_ERROR, "failed set default context"); + // TS-4679: It is ok to be missing the cert. At least if the action is set to tunnel + if (sslMultCertSettings->cert) { + SimpleTokenizer cert_tok(sslMultCertSettings->cert, SSL_CERT_SEPARATE_DELIM); + const char *first_cert = cert_tok.getNext(); + if (first_cert) { + sslMultCertSettings->first_cert = ats_strdup(first_cert); } } - return errata; + return true; } -void -SSLMultiCertConfigLoader::_load_items(SSLCertLookup *lookup, config::SSLMultiCertConfig::const_iterator begin, - config::SSLMultiCertConfig::const_iterator end, int base_index, swoc::Errata &errata) +swoc::Errata +SSLMultiCertConfigLoader::load(SSLCertLookup *lookup) { - // Each thread needs its own elevated privileges since POSIX capabilities are per-thread + const SSLConfigParams *params = this->_params; + + char *tok_state = nullptr; + char *line = nullptr; + unsigned line_num = 0; + matcher_line line_info; + + const matcher_tags sslCertTags = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, false}; + + Note("(%s) %s loading ...", this->_debug_tag(), ts::filename::SSL_MULTICERT); + + std::error_code ec; + std::string content{swoc::file::load(swoc::file::path{params->configFilePath}, ec)}; + if (ec) { + switch (ec.value()) { + case ENOENT: + // missing config file is an acceptable runtime state + return swoc::Errata(ERRATA_WARN, "Cannot open SSL certificate configuration \"{}\" - {}", params->configFilePath, ec); + default: + return swoc::Errata(ERRATA_ERROR, "Failed to read SSL certificate configuration from \"{}\" - {}", params->configFilePath, + ec); + } + } + + // Optionally elevate/allow file access to read root-only + // certificates. The destructor will drop privilege for us. uint32_t elevate_setting = 0; elevate_setting = RecGetRecordInt("proxy.config.ssl.cert.load_elevated").value_or(0); ElevateAccess elevate_access(elevate_setting ? ElevateAccess::FILE_PRIVILEGE : 0); - int item_num = base_index; - for (auto it = begin; it != end; ++it) { - item_num++; - const auto &item = *it; - - shared_SSLMultiCertConfigParams sslMultiCertSettings = std::make_shared(); + line = tokLine(content.data(), &tok_state); + swoc::Errata errata(ERRATA_NOTE); + while (line != nullptr) { + line_num++; - if (!item.ssl_cert_name.empty()) { - sslMultiCertSettings->cert = ats_strdup(item.ssl_cert_name.c_str()); - } - if (!item.dest_ip.empty()) { - sslMultiCertSettings->addr = ats_strdup(item.dest_ip.c_str()); - } - if (!item.ssl_key_name.empty()) { - sslMultiCertSettings->key = ats_strdup(item.ssl_key_name.c_str()); - } - if (!item.ssl_ca_name.empty()) { - sslMultiCertSettings->ca = ats_strdup(item.ssl_ca_name.c_str()); - } - if (!item.ssl_ocsp_name.empty()) { - sslMultiCertSettings->ocsp_response = ats_strdup(item.ssl_ocsp_name.c_str()); - } - if (!item.ssl_key_dialog.empty()) { - sslMultiCertSettings->dialog = ats_strdup(item.ssl_key_dialog.c_str()); - } - if (!item.dest_fqdn.empty()) { - sslMultiCertSettings->servername = ats_strdup(item.dest_fqdn.c_str()); - } - if (item.ssl_ticket_enabled.has_value()) { - sslMultiCertSettings->session_ticket_enabled = item.ssl_ticket_enabled.value(); - } - if (item.ssl_ticket_number.has_value()) { - sslMultiCertSettings->session_ticket_number = item.ssl_ticket_number.value(); - } - if (item.action == "tunnel") { - sslMultiCertSettings->opt = SSLCertContextOption::OPT_TUNNEL; + // Skip all blank spaces at beginning of line. + while (*line && isspace(*line)) { + line++; } - // There must be a certificate specified unless the tunnel action is set. - if (sslMultiCertSettings->cert || sslMultiCertSettings->opt == SSLCertContextOption::OPT_TUNNEL) { - if (!this->_store_ssl_ctx(lookup, sslMultiCertSettings)) { - std::lock_guard lock(_loader_mutex); - errata.note(ERRATA_ERROR, "Failed to load certificate '{}' at item {}", - sslMultiCertSettings->cert ? sslMultiCertSettings->cert : "(unnamed)", item_num); + if (*line != '\0' && *line != '#') { + shared_SSLMultiCertConfigParams sslMultiCertSettings = std::make_shared(); + const char *errPtr; + + errPtr = parseConfigLine(line, &line_info, &sslCertTags); + Dbg(dbg_ctl_ssl_load, "currently parsing %s at line %d from config file: %s", line, line_num, params->configFilePath); + if (errPtr != nullptr) { + Warning("%s: discarding %s entry at line %d: %s", __func__, params->configFilePath, line_num, errPtr); + } else { + if (ssl_extract_certificate(&line_info, sslMultiCertSettings.get())) { + // There must be a certificate specified unless the tunnel action is set + if (sslMultiCertSettings->cert || sslMultiCertSettings->opt != SSLCertContextOption::OPT_TUNNEL) { + if (!this->_store_ssl_ctx(lookup, sslMultiCertSettings)) { + errata.note(ERRATA_ERROR, "Failed to load certificate on line {}", line_num); + } + } else { + errata.note(ERRATA_WARN, "No ssl_cert_name specified and no tunnel action set on line {}", line_num); + } + } } - } else { - std::lock_guard lock(_loader_mutex); - errata.note(ERRATA_WARN, "No ssl_cert_name specified and no tunnel action set at item {}", item_num); } + + line = tokLine(nullptr, &tok_state); } + + // We *must* have a default context even if it can't possibly work. The default context is used to + // bootstrap the SSL handshake so that we can subsequently do the SNI lookup to switch to the real + // context. + if (lookup->ssl_default == nullptr) { + shared_SSLMultiCertConfigParams sslMultiCertSettings(new SSLMultiCertConfigParams); + sslMultiCertSettings->addr = ats_strdup("*"); + if (!this->_store_ssl_ctx(lookup, sslMultiCertSettings)) { + errata.note(ERRATA_ERROR, "failed set default context"); + } + } + + return errata; } // Release SSL_CTX and the associated data. This works for both diff --git a/src/iocore/net/TLSCertCompression.cc b/src/iocore/net/TLSCertCompression.cc deleted file mode 100644 index 3b31d3e1be3..00000000000 --- a/src/iocore/net/TLSCertCompression.cc +++ /dev/null @@ -1,128 +0,0 @@ -/** @file - - Functions for Certificate Compression - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#include - -#include "tscore/Diags.h" -#include "TLSCertCompression.h" - -namespace -{ -DbgCtl dbg_ctl_ssl_cert_compress{"ssl_cert_compress"}; -} - -constexpr unsigned int N_ALGORITHMS = 3; - -#if HAVE_SSL_CTX_ADD_CERT_COMPRESSION_ALG -#include "TLSCertCompression_zlib.h" - -#if HAVE_BROTLI_ENCODE_H -#include "TLSCertCompression_brotli.h" -#endif - -#if HAVE_ZSTD_H -#include "TLSCertCompression_zstd.h" -#endif -#endif - -struct alg_info { - const char *name; - int32_t number; -#if HAVE_SSL_CTX_ADD_CERT_COMPRESSION_ALG - ssl_cert_compression_func_t compress_func; - ssl_cert_decompression_func_t decompress_func; -#endif -} supported_algs[] = { - {"zlib", 1, -#if HAVE_SSL_CTX_ADD_CERT_COMPRESSION_ALG - compression_func_zlib, decompression_func_zlib -#endif - }, -#if HAVE_BROTLI_ENCODE_H - {"brotli", 2, -#if HAVE_SSL_CTX_ADD_CERT_COMPRESSION_ALG - compression_func_brotli, decompression_func_brotli -#endif - }, -#endif -#if HAVE_ZSTD_H - {"zstd", 3, -#if HAVE_SSL_CTX_ADD_CERT_COMPRESSION_ALG - compression_func_zstd, decompression_func_zstd -#endif - }, -#endif -}; - -int -register_certificate_compression_preference(SSL_CTX *ctx, const std::vector &specified_algs) -{ - ink_assert(ctx != nullptr); - if (specified_algs.size() > N_ALGORITHMS) { - return 0; - } - - if (specified_algs.empty()) { - return 1; - } - -#if HAVE_SSL_CTX_ADD_CERT_COMPRESSION_ALG - for (auto &&alg : specified_algs) { - struct alg_info *info = nullptr; - - for (unsigned int i = 0; i < countof(supported_algs); ++i) { - if (strcmp(alg.c_str(), supported_algs[i].name) == 0) { - info = &supported_algs[i]; - } - } - if (info != nullptr) { - if (SSL_CTX_add_cert_compression_alg(ctx, info->number, info->compress_func, info->decompress_func) == 0) { - return 0; - } - Dbg(dbg_ctl_ssl_cert_compress, "Enabled %s", info->name); - } else { - Dbg(dbg_ctl_ssl_cert_compress, "Unrecognized algorithm: %s", alg.c_str()); - return 0; - } - } - return 1; -#elif HAVE_SSL_CTX_SET1_CERT_COMP_PREFERENCE - int algs[N_ALGORITHMS]; - int n = 0; - - for (unsigned int i = 0; i < specified_algs.size(); ++i) { - for (unsigned int j = 0; j < countof(supported_algs); ++j) { - if (strcmp(specified_algs[i].c_str(), supported_algs[j].name) == 0) { - algs[n++] = supported_algs[j].number; - Dbg(dbg_ctl_ssl_cert_compress, "Enabled %s", supported_algs[j].name); - } - } - } - return SSL_CTX_set1_cert_comp_preference(ctx, algs, n); -#else - // If Certificate Compression is unsupported there's nothing to do. - // No need to raise an error since handshake would be done successfully without compression. - Dbg(dbg_ctl_ssl_cert_compress, "Certificate Compression is unsupported"); - return 1; -#endif -} diff --git a/src/iocore/net/TLSCertCompression.h b/src/iocore/net/TLSCertCompression.h deleted file mode 100644 index fa7bbc6ee56..00000000000 --- a/src/iocore/net/TLSCertCompression.h +++ /dev/null @@ -1,43 +0,0 @@ -/** @file - - Functions for Certificate Compression - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#pragma once - -#include -#include -#include - -// RFC 8879 uses uint24 for uncompressed_length, allowing up to ~16 MB. -// Real certificate chains rarely exceed 10-30 KB even with large RSA -// keys and multiple intermediates — 128 KB gives ample headroom while -// preventing excessive memory allocation from a malicious peer. -constexpr size_t MAX_CERT_UNCOMPRESSED_LEN = 128 * 1024; - -/** - * Common function to set certificate compression preference - * - * @param[in] ctx SSL_CTX - * @param[in] algs A vector that contains compression algorithm names ("zlib", "brotli", or "zstd") - * @return 1 on success - */ -int register_certificate_compression_preference(SSL_CTX *ctx, const std::vector &algs); diff --git a/src/iocore/net/TLSCertCompression_brotli.cc b/src/iocore/net/TLSCertCompression_brotli.cc deleted file mode 100644 index 52cf22dada6..00000000000 --- a/src/iocore/net/TLSCertCompression_brotli.cc +++ /dev/null @@ -1,84 +0,0 @@ -/** @file - - Functions for brotli compression/decompression - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#include "TLSCertCompression_brotli.h" -#include "TLSCertCompression.h" -#include "SSLStats.h" -#include -#include -#include - -int -compression_func_brotli(SSL * /* ssl */, CBB *out, const uint8_t *in, size_t in_len) -{ - // TODO Need a cache mechanism inside this function for better performance. - - uint8_t *buf; - unsigned long buf_len = BrotliEncoderMaxCompressedSize(in_len); - - if (CBB_reserve(out, &buf, buf_len) != 1) { - Metrics::Counter::increment(ssl_rsb.cert_compress_brotli_failure); - return 0; - } - - if (BrotliEncoderCompress(BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, in_len, in, &buf_len, buf) == - BROTLI_TRUE) { - CBB_did_write(out, buf_len); - Metrics::Counter::increment(ssl_rsb.cert_compress_brotli); - return 1; - } else { - CBB_did_write(out, 0); - Metrics::Counter::increment(ssl_rsb.cert_compress_brotli_failure); - return 0; - } -} - -int -decompression_func_brotli(SSL * /* ssl */, CRYPTO_BUFFER **out, size_t uncompressed_len, const uint8_t *in, size_t in_len) -{ - if (uncompressed_len > MAX_CERT_UNCOMPRESSED_LEN) { - *out = nullptr; - Metrics::Counter::increment(ssl_rsb.cert_decompress_brotli_failure); - return 0; - } - - uint8_t *buf; - - *out = CRYPTO_BUFFER_alloc(&buf, uncompressed_len); - if (*out == nullptr) { - Metrics::Counter::increment(ssl_rsb.cert_decompress_brotli_failure); - return 0; - } - - size_t dest_len = uncompressed_len; - - if (BrotliDecoderDecompress(in_len, in, &dest_len, buf) != BROTLI_DECODER_RESULT_SUCCESS || dest_len != uncompressed_len) { - CRYPTO_BUFFER_free(*out); - *out = nullptr; - Metrics::Counter::increment(ssl_rsb.cert_decompress_brotli_failure); - return 0; - } - - Metrics::Counter::increment(ssl_rsb.cert_decompress_brotli); - return 1; -} diff --git a/src/iocore/net/TLSCertCompression_brotli.h b/src/iocore/net/TLSCertCompression_brotli.h deleted file mode 100644 index 7026f4ff70f..00000000000 --- a/src/iocore/net/TLSCertCompression_brotli.h +++ /dev/null @@ -1,30 +0,0 @@ -/** @file - - Functions for brotli compression/decompression - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#pragma once - -#include -#include - -int compression_func_brotli(SSL *ssl, CBB *out, const uint8_t *in, size_t in_len); -int decompression_func_brotli(SSL *ssl, CRYPTO_BUFFER **out, size_t uncompressed_len, const uint8_t *in, size_t in_len); diff --git a/src/iocore/net/TLSCertCompression_zlib.cc b/src/iocore/net/TLSCertCompression_zlib.cc deleted file mode 100644 index 1d724ef541a..00000000000 --- a/src/iocore/net/TLSCertCompression_zlib.cc +++ /dev/null @@ -1,82 +0,0 @@ -/** @file - - Functions for zlib compression/decompression - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#include "TLSCertCompression_zlib.h" -#include "TLSCertCompression.h" -#include "SSLStats.h" -#include -#include - -int -compression_func_zlib(SSL * /* ssl */, CBB *out, const uint8_t *in, size_t in_len) -{ - // TODO Need a cache mechanism inside this function for better performance. - - uint8_t *buf; - unsigned long buf_len = compressBound(in_len); - - if (CBB_reserve(out, &buf, buf_len) != 1) { - Metrics::Counter::increment(ssl_rsb.cert_compress_zlib_failure); - return 0; - } - - if (compress(buf, &buf_len, in, in_len) == Z_OK) { - CBB_did_write(out, buf_len); - Metrics::Counter::increment(ssl_rsb.cert_compress_zlib); - return 1; - } else { - CBB_did_write(out, 0); - Metrics::Counter::increment(ssl_rsb.cert_compress_zlib_failure); - return 0; - } -} - -int -decompression_func_zlib(SSL * /* ssl */, CRYPTO_BUFFER **out, size_t uncompressed_len, const uint8_t *in, size_t in_len) -{ - if (uncompressed_len > MAX_CERT_UNCOMPRESSED_LEN) { - *out = nullptr; - Metrics::Counter::increment(ssl_rsb.cert_decompress_zlib_failure); - return 0; - } - - uint8_t *buf; - - *out = CRYPTO_BUFFER_alloc(&buf, uncompressed_len); - if (*out == nullptr) { - Metrics::Counter::increment(ssl_rsb.cert_decompress_zlib_failure); - return 0; - } - - unsigned long dest_len = uncompressed_len; - - if (uncompress(buf, &dest_len, in, in_len) != Z_OK || dest_len != uncompressed_len) { - CRYPTO_BUFFER_free(*out); - *out = nullptr; - Metrics::Counter::increment(ssl_rsb.cert_decompress_zlib_failure); - return 0; - } - - Metrics::Counter::increment(ssl_rsb.cert_decompress_zlib); - return 1; -} diff --git a/src/iocore/net/TLSCertCompression_zlib.h b/src/iocore/net/TLSCertCompression_zlib.h deleted file mode 100644 index 622f8efb5a4..00000000000 --- a/src/iocore/net/TLSCertCompression_zlib.h +++ /dev/null @@ -1,30 +0,0 @@ -/** @file - - Functions for zlib compression/decompression - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#pragma once - -#include -#include - -int compression_func_zlib(SSL *ssl, CBB *out, const uint8_t *in, size_t in_len); -int decompression_func_zlib(SSL *ssl, CRYPTO_BUFFER **out, size_t uncompressed_len, const uint8_t *in, size_t in_len); diff --git a/src/iocore/net/TLSCertCompression_zstd.cc b/src/iocore/net/TLSCertCompression_zstd.cc deleted file mode 100644 index 85cf2a94f90..00000000000 --- a/src/iocore/net/TLSCertCompression_zstd.cc +++ /dev/null @@ -1,89 +0,0 @@ -/** @file - - Functions for zstd compression/decompression - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#include "TLSCertCompression_zstd.h" -#include "TLSCertCompression.h" -#include "SSLStats.h" -#include -#include - -int -compression_func_zstd(SSL * /* ssl */, CBB *out, const uint8_t *in, size_t in_len) -{ - // TODO Need a cache mechanism inside this function for better performance. - - uint8_t *buf; - unsigned long buf_len = ZSTD_compressBound(in_len); - - if (ZSTD_isError(buf_len) == 1) { - Metrics::Counter::increment(ssl_rsb.cert_compress_zstd_failure); - return 0; - } - - if (CBB_reserve(out, &buf, buf_len) != 1) { - Metrics::Counter::increment(ssl_rsb.cert_compress_zstd_failure); - return 0; - } - - // For better performance ZSTD_compressCCtx, which reuses a context object, should be used. - // One context object need to be made for each thread. - size_t ret = ZSTD_compress(buf, buf_len, in, in_len, ZSTD_CLEVEL_DEFAULT); - if (ZSTD_isError(ret) == 1) { - Metrics::Counter::increment(ssl_rsb.cert_compress_zstd_failure); - return 0; - } else { - CBB_did_write(out, ret); - Metrics::Counter::increment(ssl_rsb.cert_compress_zstd); - return 1; - } -} - -int -decompression_func_zstd(SSL * /* ssl */, CRYPTO_BUFFER **out, size_t uncompressed_len, const uint8_t *in, size_t in_len) -{ - if (uncompressed_len > MAX_CERT_UNCOMPRESSED_LEN) { - *out = nullptr; - Metrics::Counter::increment(ssl_rsb.cert_decompress_zstd_failure); - return 0; - } - - uint8_t *buf; - - *out = CRYPTO_BUFFER_alloc(&buf, uncompressed_len); - if (*out == nullptr) { - Metrics::Counter::increment(ssl_rsb.cert_decompress_zstd_failure); - return 0; - } - - size_t ret = ZSTD_decompress(buf, uncompressed_len, in, in_len); - - if (ZSTD_isError(ret) || ret != uncompressed_len) { - CRYPTO_BUFFER_free(*out); - *out = nullptr; - Metrics::Counter::increment(ssl_rsb.cert_decompress_zstd_failure); - return 0; - } - - Metrics::Counter::increment(ssl_rsb.cert_decompress_zstd); - return 1; -} diff --git a/src/iocore/net/TLSCertCompression_zstd.h b/src/iocore/net/TLSCertCompression_zstd.h deleted file mode 100644 index bde6ef6d7b3..00000000000 --- a/src/iocore/net/TLSCertCompression_zstd.h +++ /dev/null @@ -1,30 +0,0 @@ -/** @file - - Functions for zstd compression/decompression - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#pragma once - -#include -#include - -int compression_func_zstd(SSL *ssl, CBB *out, const uint8_t *in, size_t in_len); -int decompression_func_zstd(SSL *ssl, CRYPTO_BUFFER **out, size_t uncompressed_len, const uint8_t *in, size_t in_len); diff --git a/src/iocore/net/TLSSessionResumptionSupport.cc b/src/iocore/net/TLSSessionResumptionSupport.cc index 1bd93bcc644..1fac030a7c4 100644 --- a/src/iocore/net/TLSSessionResumptionSupport.cc +++ b/src/iocore/net/TLSSessionResumptionSupport.cc @@ -164,6 +164,56 @@ TLSSessionResumptionSupport::getSSLGroupName() const return this->_sslGroupName; } +SSL_SESSION * +TLSSessionResumptionSupport::getSession(SSL *ssl, const unsigned char *id, int len, int *copy) +{ + SSLSessionID sid(id, len); + + *copy = 0; + if (diags()->on()) { + static DbgCtl dbg_ctl("ssl.session_cache.get"); + if (dbg_ctl.tag_on()) { + char printable_buf[(len * 2) + 1]; + sid.toString(printable_buf, sizeof(printable_buf)); + DbgPrint(dbg_ctl, "ssl_get_cached_session cached session '%s' context %p", printable_buf, SSL_get_SSL_CTX(ssl)); + } + } + + APIHook *hook = SSLAPIHooks::instance()->get(TSSslHookInternalID(TS_SSL_SESSION_HOOK)); + while (hook) { + hook->invoke(TS_EVENT_SSL_SESSION_GET, &sid); + hook = hook->m_link.next; + } + + SSL_SESSION *session = nullptr; + ssl_session_cache_exdata *exdata = nullptr; + if (session_cache->getSession(sid, &session, &exdata)) { + ink_assert(session); + ink_assert(exdata); + + // Double check the timeout + if (is_ssl_session_timed_out(session)) { + Metrics::Counter::increment(ssl_rsb.session_cache_miss); + Metrics::Counter::increment(ssl_rsb.session_cache_timeout); +// Due to bug in openssl, the timeout is checked, but only removed +// from the openssl built-in hash table. The external remove cb is not called +#if 0 // This is currently eliminated, since it breaks things in odd ways (see TS-3710) + ssl_rm_cached_session(SSL_get_SSL_CTX(ssl), session); +#endif + SSL_SESSION_free(session); + session = nullptr; + } else { + Metrics::Counter::increment(ssl_rsb.session_cache_hit); + this->_setResumptionType(ResumptionType::RESUMED_FROM_SESSION_CACHE, !IS_RESUMED_ORIGIN_SESSION); + this->_setSSLCurveNID(exdata->curve); + this->_setSSLGroupName(exdata->group_name); + } + } else { + Metrics::Counter::increment(ssl_rsb.session_cache_miss); + } + return session; +} + std::shared_ptr TLSSessionResumptionSupport::getOriginSession(const std::string &lookup_key) { diff --git a/src/iocore/net/quic/QUICConfig.cc b/src/iocore/net/quic/QUICConfig.cc index 9905ae06889..a97baf8ace2 100644 --- a/src/iocore/net/quic/QUICConfig.cc +++ b/src/iocore/net/quic/QUICConfig.cc @@ -25,7 +25,6 @@ #include -#include "mgmt/config/ConfigContextDiags.h" #include "records/RecHttp.h" #include "../P_SSLConfig.h" @@ -58,7 +57,7 @@ quic_new_ssl_ctx() ALPN and SNI should be set to SSL object with NETVC_OPTIONS **/ static shared_SSL_CTX -quic_init_client_ssl_ctx(const QUICConfigParams *params, [[maybe_unused]] ConfigContext ctx) +quic_init_client_ssl_ctx(const QUICConfigParams *params) { std::unique_ptr ssl_ctx(nullptr, &SSL_CTX_free); ssl_ctx.reset(quic_new_ssl_ctx()); @@ -70,7 +69,7 @@ quic_init_client_ssl_ctx(const QUICConfigParams *params, [[maybe_unused]] Config #else if (SSL_CTX_set1_curves_list(ssl_ctx.get(), params->client_supported_groups()) != 1) { #endif - CfgLoadLog(ctx, DL_Error, "SSL_CTX_set1_groups_list failed"); + Error("SSL_CTX_set1_groups_list failed"); } } #endif @@ -100,7 +99,7 @@ QUICConfigParams::~QUICConfigParams() }; void -QUICConfigParams::initialize(ConfigContext ctx) +QUICConfigParams::initialize() { RecEstablishStaticConfigUInt32(this->_instance_id, "proxy.config.quic.instance_id"); RecEstablishStaticConfigInt32(this->_connection_table_size, "proxy.config.quic.connection_table.size"); @@ -172,7 +171,7 @@ QUICConfigParams::initialize(ConfigContext ctx) RecEstablishStaticConfigUInt32(this->_disable_http_0_9, "proxy.config.quic.disable_http_0_9"); RecEstablishStaticConfigUInt32(this->_cc_algorithm, "proxy.config.quic.cc_algorithm"); - this->_client_ssl_ctx = quic_init_client_ssl_ctx(this, ctx); + this->_client_ssl_ctx = quic_init_client_ssl_ctx(this); } uint32_t @@ -452,16 +451,15 @@ QUICConfig::startup() } void -QUICConfig::reconfigure(ConfigContext ctx) +QUICConfig::reconfigure() { QUICConfigParams *params; params = new QUICConfigParams; // re-read configuration - params->initialize(ctx); + params->initialize(); _config_id = configProcessor.set(_config_id, params); QUICConnectionId::SCID_LEN = params->scid_len(); - ctx.complete("QUICConfig reloaded"); } QUICConfigParams * diff --git a/src/mgmt/config/AddConfigFilesHere.cc b/src/mgmt/config/AddConfigFilesHere.cc new file mode 100644 index 00000000000..38ff8abad96 --- /dev/null +++ b/src/mgmt/config/AddConfigFilesHere.cc @@ -0,0 +1,81 @@ +/** @file + + A brief file description + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include "tscore/ink_platform.h" +#include "tscore/Filenames.h" +#include "../../records/P_RecCore.h" +#include "tscore/Diags.h" +#include "mgmt/config/FileManager.h" + +static constexpr bool REQUIRED{true}; +static constexpr bool NOT_REQUIRED{false}; +/**************************************************************************** + * + * AddConfigFilesHere.cc - Structs for config files and + * + * + ****************************************************************************/ +void +registerFile(const char *configName, const char *defaultName, bool isRequired) +{ + auto fname{RecGetRecordStringAlloc(configName)}; + FileManager::instance().addFile(fname ? ats_as_c_str(fname) : defaultName, configName, false, isRequired); +} + +// +// initializeRegistry() +// +// Code to initialize of registry of objects that represent +// Web Editable configuration files +// +// thread-safe: NO! - Should only be executed once from the main +// web interface thread, before any child +// threads have been spawned +void +initializeRegistry() +{ + static int run_already = 0; + + if (run_already == 0) { + run_already = 1; + } else { + ink_assert(!"Configuration Object Registry Initialized More than Once"); + } + + registerFile("proxy.config.log.config.filename", ts::filename::LOGGING, NOT_REQUIRED); + registerFile("", ts::filename::STORAGE, REQUIRED); + registerFile("proxy.config.socks.socks_config_file", ts::filename::SOCKS, NOT_REQUIRED); + registerFile(ts::filename::RECORDS, ts::filename::RECORDS, NOT_REQUIRED); + registerFile("proxy.config.cache.control.filename", ts::filename::CACHE, NOT_REQUIRED); + registerFile("proxy.config.cache.ip_allow.filename", ts::filename::IP_ALLOW, NOT_REQUIRED); + registerFile("proxy.config.cache.ip_categories.filename", ts::filename::IP_CATEGORIES, NOT_REQUIRED); + registerFile("proxy.config.http.parent_proxy.file", ts::filename::PARENT, NOT_REQUIRED); + registerFile("proxy.config.url_remap.filename", ts::filename::REMAP, NOT_REQUIRED); + registerFile("", ts::filename::VOLUME, NOT_REQUIRED); + registerFile("proxy.config.cache.hosting_filename", ts::filename::HOSTING, NOT_REQUIRED); + registerFile("", ts::filename::PLUGIN, NOT_REQUIRED); + registerFile("proxy.config.dns.splitdns.filename", ts::filename::SPLITDNS, NOT_REQUIRED); + registerFile("proxy.config.ssl.server.multicert.filename", ts::filename::SSL_MULTICERT, NOT_REQUIRED); + registerFile("proxy.config.ssl.servername.filename", ts::filename::SNI, NOT_REQUIRED); + registerFile("proxy.config.jsonrpc.filename", ts::filename::JSONRPC, NOT_REQUIRED); +} diff --git a/src/mgmt/config/CMakeLists.txt b/src/mgmt/config/CMakeLists.txt index 6107734f63e..e4e106ff929 100644 --- a/src/mgmt/config/CMakeLists.txt +++ b/src/mgmt/config/CMakeLists.txt @@ -15,13 +15,13 @@ # ####################### -add_library(configmanager STATIC FileManager.cc ConfigReloadExecutor.cc ConfigRegistry.cc) +add_library(configmanager STATIC FileManager.cc AddConfigFilesHere.cc) add_library(ts::configmanager ALIAS configmanager) target_link_libraries( configmanager - PUBLIC ts::tscore ts::records - PRIVATE ts::inkevent ts::jsonrpc_protocol yaml-cpp::yaml-cpp + PUBLIC ts::tscore + PRIVATE ts::proxy ) clang_tidy_check(configmanager) diff --git a/src/mgmt/config/ConfigContext.cc b/src/mgmt/config/ConfigContext.cc deleted file mode 100644 index 4604911b5b1..00000000000 --- a/src/mgmt/config/ConfigContext.cc +++ /dev/null @@ -1,190 +0,0 @@ -/** @file - - ConfigContext implementation - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#include "mgmt/config/ConfigContext.h" -#include "mgmt/config/ConfigReloadTrace.h" -#include "mgmt/config/ReloadCoordinator.h" - -#include - -// Defined here (not = default in header) so that YAML::Node ctor/dtor/copy -// symbols are only emitted in this TU (part of librecords, which links yaml-cpp). -// Otherwise every consumer of RecCore.h would need yaml-cpp at link time. -ConfigContext::ConfigContext() = default; -ConfigContext::~ConfigContext() = default; -ConfigContext::ConfigContext(ConfigContext const &) = default; -ConfigContext &ConfigContext::operator=(ConfigContext const &) = default; - -ConfigContext::ConfigContext(std::shared_ptr t, std::string_view description, std::string_view filename) - : _task(t) -{ - if (auto p = _task.lock()) { - if (!description.empty()) { - p->set_description(description); - } - if (!filename.empty()) { - p->set_filename(filename); - } - } -} - -bool -ConfigContext::is_terminal() const -{ - if (auto p = _task.lock()) { - return ConfigReloadTask::is_terminal(p->get_state()); - } - return true; // expired task is supposed to be terminal -} - -void -ConfigContext::in_progress(std::string_view text) -{ - if (auto p = _task.lock()) { - p->set_in_progress(); - if (!text.empty()) { - p->log(DL_Note, std::string{text}); - } - } -} - -void -ConfigContext::log(std::string_view text) -{ - if (auto p = _task.lock()) { - p->log(std::string{text}); - } -} - -void -ConfigContext::log(DiagsLevel level, std::string_view text) -{ - if (auto p = _task.lock()) { - p->log(level, std::string{text}); - } -} - -void -ConfigContext::log(swoc::Errata const &errata) -{ - if (auto p = _task.lock()) { - for (auto const &annotation : errata) { - p->log(static_cast(static_cast(annotation.severity())), std::string{annotation.text()}); - } - } -} - -void -ConfigContext::complete(std::string_view text) -{ - if (auto p = _task.lock()) { - p->set_completed(); - if (!text.empty()) { - p->log(DL_Note, std::string{text}); - } - } -} - -void -ConfigContext::fail(std::string_view reason) -{ - if (auto p = _task.lock()) { - p->set_failed(); - if (!reason.empty()) { - p->log(DL_Error, std::string{reason}); - } - } -} - -void -ConfigContext::fail(swoc::Errata const &errata, std::string_view summary) -{ - if (auto p = _task.lock()) { - p->set_failed(); - // Log the summary first - if (!summary.empty()) { - p->log(DL_Error, std::string{summary}); - } - // Log each error from the errata - for (auto const &err : errata) { - p->log(static_cast(static_cast(err.severity())), std::string{err.text()}); - } - } -} - -std::string -ConfigContext::get_description() const -{ - if (auto p = _task.lock()) { - return p->get_description(); - } - return {}; -} - -ConfigContext -ConfigContext::add_dependent_ctx(std::string_view description) -{ - if (auto p = _task.lock()) { - auto child = p->add_child(description); - // child task will get the full content of the parent task - // TODO: eventually we can have a "key" passed so child module - // only gets their node of interest. - child._supplied_yaml = _supplied_yaml; - child._reload_directives = _reload_directives; - return child; - } - return {}; -} - -void -ConfigContext::set_supplied_yaml(YAML::Node node) -{ - _supplied_yaml = node; // YAML::Node has no move semantics; copy is cheap (ref-counted). -} - -YAML::Node -ConfigContext::supplied_yaml() const -{ - return _supplied_yaml; -} - -void -ConfigContext::set_reload_directives(YAML::Node node) -{ - _reload_directives = node; -} - -YAML::Node -ConfigContext::reload_directives() const -{ - return _reload_directives; -} - -namespace config -{ -ConfigContext -make_config_reload_context(std::string_view description, std::string_view filename) -{ - return ReloadCoordinator::Get_Instance().create_config_context({}, description, filename); -} -} // namespace config diff --git a/src/mgmt/config/ConfigRegistry.cc b/src/mgmt/config/ConfigRegistry.cc deleted file mode 100644 index 776fa265d2f..00000000000 --- a/src/mgmt/config/ConfigRegistry.cc +++ /dev/null @@ -1,511 +0,0 @@ -/** @file - * - * Config Registry implementation - * - * @section license License - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "mgmt/config/ConfigRegistry.h" - -#include "iocore/eventsystem/Continuation.h" -#include "iocore/eventsystem/EventProcessor.h" -#include "iocore/eventsystem/Tasks.h" -#include "records/RecCore.h" -#include "mgmt/config/ConfigContext.h" -#include "mgmt/config/FileManager.h" -#include "mgmt/config/ReloadCoordinator.h" -#include "tscore/Diags.h" -#include "tscore/ink_assert.h" -#include "tscore/Layout.h" -#include "tsutil/ts_errata.h" -#include "swoc/TextView.h" - -#include - -namespace -{ -DbgCtl dbg_ctl{"config.reload"}; - -/// Infer ConfigType from the filename extension. -config::ConfigType -infer_config_type(swoc::TextView filename) -{ - return (filename.ends_with(".yaml") || filename.ends_with(".yml")) ? config::ConfigType::YAML : config::ConfigType::LEGACY; -} - -// Resolve a config filename: read the current value from the named record, -// fallback to default_filename if the record is empty or absent. -// Returns the bare filename (no sysconfdir prefix) — suitable for FileManager::addFile(). -std::string -resolve_config_filename(const char *record_name, const std::string &default_filename) -{ - if (record_name && record_name[0] != '\0') { - if (auto val = RecGetRecordStringAlloc(record_name); val && !val->empty()) { - return std::string{*val}; - } - } - return default_filename; -} - -/// -// Continuation that executes config reload on ET_TASK thread -// Used by ConfigRegistry::schedule_reload() for async rpc reloads (content supplied via RPC) or file reloads -// -class ScheduledReloadContinuation : public Continuation -{ -public: - ScheduledReloadContinuation(Ptr &m, std::string key) : Continuation(m.get()), _config_key(std::move(key)) - { - SET_HANDLER(&ScheduledReloadContinuation::execute); - } - - int - execute(int /* event */, Event * /* e */) - { - Dbg(dbg_ctl, "ScheduledReloadContinuation: executing reload for config '%s'", _config_key.c_str()); - config::ConfigRegistry::Get_Instance().execute_reload(_config_key); - delete this; - return EVENT_DONE; - } - -private: - std::string _config_key; -}; - -/// -// Continuation used by record-triggered reloads (via on_record_change callback) -// This is separate from ScheduledReloadContinuation as it always reloads from file -// -class RecordTriggeredReloadContinuation : public Continuation -{ -public: - RecordTriggeredReloadContinuation(Ptr &m, std::string key) : Continuation(m.get()), _config_key(std::move(key)) - { - SET_HANDLER(&RecordTriggeredReloadContinuation::execute); - } - - int - execute(int /* event */, Event * /* e */) - { - Dbg(dbg_ctl, "RecordTriggeredReloadContinuation: executing reload for config '%s'", _config_key.c_str()); - - auto const *entry = config::ConfigRegistry::Get_Instance().find(_config_key); - - if (entry == nullptr) { - Warning("Config key '%s' not found in registry", _config_key.c_str()); - } else if (!entry->has_handler()) { - Warning("Config '%s' has no handler", _config_key.c_str()); - } else { - auto ctx = ReloadCoordinator::Get_Instance().create_config_context(_config_key, _config_key, entry->resolve_filename()); - if (!ctx) { - if (ReloadCoordinator::Get_Instance().is_reload_in_progress()) { - // True duplicate — same config key already handled in this reload cycle - Dbg(dbg_ctl, "Config '%s' reload skipped (duplicate in this reload cycle)", _config_key.c_str()); - } else { - // Standalone record change (no active reload) — run handler directly - Dbg(dbg_ctl, "Config '%s' standalone record-triggered reload (no active reload task)", _config_key.c_str()); - entry->handler(ctx); - } - } else { - ctx.in_progress(); - entry->handler(ctx); - Dbg(dbg_ctl, "Config '%s' file reload completed", _config_key.c_str()); - } - } - - delete this; - return EVENT_DONE; - } - -private: - std::string _config_key; -}; - -/// -/// Callback invoked by the Records system when a trigger record changes. -/// Only fires for records registered with ConfigRegistry (via trigger_records -/// in register_config()/register_record_config(), or via add_file_dependency()). -/// -/// Record-triggered reload: fan-in deduplication -/// ────────────────────────────────────────────── -/// When a config key has N trigger records (e.g., ssl_client_coordinator has 11), -/// setup_triggers() registers an independent on_record_change callback for each. -/// The Records system (RecExecConfigUpdateCbs) fires all record -/// callbacks synchronously in one pass — N records produce N calls here, each -/// scheduling its own RecordTriggeredReloadContinuation on ET_TASK. -/// -/// All N continuations carry the same config_key and would invoke the same handler. -/// The handler doesn't know which specific record triggered it — trigger records are -/// an OR-set meaning "any of these changed → reconfigure this subsystem." -/// -/// If different records need different handlers, register them under separate config keys. -/// -int -on_record_change(const char *name, RecDataT /* data_type */, RecData /* data */, void *cookie) -{ - auto *ctx = static_cast(cookie); - - Dbg(dbg_ctl, "Record '%s' changed, scheduling reload for config '%s'", name, ctx->config_key.c_str()); - - // Pre-register a CREATED subtask so the main task knows work is pending. - // Without this, aggregate_status() can reach SUCCESS before the continuation - // runs and creates the subtask. The continuation will activate the reserved - // subtask instead of creating a new one. - ReloadCoordinator::Get_Instance().reserve_subtask(ctx->config_key); - - // Schedule file reload on ET_TASK thread (always file-based, no rpc-supplied content) - eventProcessor.schedule_imm(new RecordTriggeredReloadContinuation(ctx->mutex, ctx->config_key), ET_TASK); - - return 0; -} - -} // anonymous namespace - -namespace config -{ - -ConfigRegistry & -ConfigRegistry::Get_Instance() -{ - static ConfigRegistry _instance; - return _instance; -} - -std::string -ConfigRegistry::Entry::resolve_filename() const -{ - auto fname = resolve_config_filename(filename_record.empty() ? nullptr : filename_record.c_str(), default_filename); - - // Build full path if not already absolute - if (!fname.empty() && fname[0] != '/') { - return Layout::get()->sysconfdir + "/" + fname; - } - return fname; -} - -void -ConfigRegistry::do_register(Entry entry) -{ - const char *type_str = (entry.type == ConfigType::YAML) ? "YAML" : "legacy"; - - Dbg(dbg_ctl, "Registering %s config '%s' (default: %s, record: %s, triggers: %zu)", type_str, entry.key.c_str(), - entry.default_filename.c_str(), entry.filename_record.empty() ? "" : entry.filename_record.c_str(), - entry.trigger_records.size()); - - std::unique_lock lock(_mutex); - auto [it, inserted] = _entries.emplace(entry.key, std::move(entry)); - - if (inserted) { - setup_triggers(it->second); - - // Register with FileManager for mtime-based file change detection. - // This replaces the manual registerFile() call in AddConfigFilesHere.cc. - // When rereadConfig() detects the file changed, it calls RecSetSyncRequired() - // on the filename_record, which eventually triggers our on_record_change callback. - if (!it->second.default_filename.empty()) { - auto resolved = resolve_config_filename(it->second.filename_record.empty() ? nullptr : it->second.filename_record.c_str(), - it->second.default_filename); - FileManager::instance().addFile(resolved.c_str(), it->second.filename_record.c_str(), false, it->second.is_required); - } - } else { - Warning("Config '%s' already registered, ignoring", it->first.c_str()); - } -} - -void -ConfigRegistry::register_config(const std::string &key, const std::string &default_filename, const std::string &filename_record, - ConfigReloadHandler handler, ConfigSource source, - std::initializer_list trigger_records, bool is_required) -{ - Entry entry; - entry.key = key; - entry.default_filename = default_filename; - entry.filename_record = filename_record; - entry.handler = std::move(handler); - entry.source = source; - entry.is_required = is_required; - entry.type = infer_config_type(default_filename); - - for (auto const *record : trigger_records) { - entry.trigger_records.emplace_back(record); - } - - do_register(std::move(entry)); -} - -void -ConfigRegistry::register_record_config(const std::string &key, ConfigReloadHandler handler, - std::initializer_list trigger_records) -{ - register_config(key, "", "", std::move(handler), ConfigSource::RecordOnly, trigger_records); -} - -void -ConfigRegistry::register_static_file(const std::string &key, const std::string &default_filename, - const std::string &filename_record, bool is_required) -{ - // Delegate — no handler, no trigger records, FileOnly source. - register_config(key, default_filename, filename_record, nullptr, ConfigSource::FileOnly, {}, is_required); -} - -void -ConfigRegistry::setup_triggers(Entry &entry) -{ - for (auto const &record : entry.trigger_records) { - wire_record_callback(record.c_str(), entry.key); - } -} - -int -ConfigRegistry::wire_record_callback(const char *record_name, const std::string &config_key) -{ - // TriggerContext lives for the lifetime of the process — intentionally not deleted - // as RecRegisterConfigUpdateCb stores the pointer and may invoke the callback at any time. - // This is a small, bounded allocation (one per trigger record). - auto *ctx = new TriggerContext(); - ctx->config_key = config_key; - ctx->mutex = new_ProxyMutex(); - - Dbg(dbg_ctl, "Wiring record callback '%s' to config '%s'", record_name, config_key.c_str()); - - int result = RecRegisterConfigUpdateCb(record_name, on_record_change, ctx); - if (result != 0) { - Warning("Failed to wire callback for record '%s' on config '%s'", record_name, config_key.c_str()); - delete ctx; - return -1; - } - return 0; -} - -int -ConfigRegistry::attach(const std::string &key, const char *record_name) -{ - std::string config_key; - - // Single lock for check-and-modify. - { - std::unique_lock lock(_mutex); - auto it = _entries.find(key); - if (it == _entries.end()) { - Warning("Cannot attach trigger to unknown config: %s", key.c_str()); - return -1; - } - - // Store record in entry — owned trigger - it->second.trigger_records.emplace_back(record_name); - config_key = it->second.key; - } - // Lock released before external call to RecRegisterConfigUpdateCb - - Dbg(dbg_ctl, "Attaching trigger '%s' to config '%s'", record_name, key.c_str()); - return wire_record_callback(record_name, config_key); -} - -int -ConfigRegistry::add_file_dependency(const std::string &key, const char *filename_record, const char *default_filename, - bool is_required) -{ - std::string config_key; - - { - std::shared_lock lock(_mutex); - auto it = _entries.find(key); - if (it == _entries.end()) { - Warning("Cannot add file dependency to unknown config: %s", key.c_str()); - return -1; - } - config_key = it->second.key; - } - - auto resolved = resolve_config_filename(filename_record, default_filename); - - Dbg(dbg_ctl, "Adding file dependency '%s' (resolved: %s) to config '%s'", filename_record, resolved.c_str(), key.c_str()); - - // Register with FileManager for mtime-based change detection. - // When rereadConfig() detects the file changed, it calls RecSetSyncRequired() - // on the filename_record, which triggers on_record_change below. - FileManager::instance().addFile(resolved.c_str(), filename_record, false, is_required); - - // Wire callback — dependency trigger, not stored in trigger_records. - return wire_record_callback(filename_record, config_key); -} - -int -ConfigRegistry::add_file_and_node_dependency(const std::string &key, const std::string &dep_key, const char *filename_record, - const char *default_filename, bool is_required) -{ - // Do the normal file dependency work (FileManager registration + record callback wiring) - int ret = add_file_dependency(key, filename_record, default_filename, is_required); - if (ret != 0) { - return ret; - } - - // Register the dep_key -> parent mapping for RPC routing - std::unique_lock lock(_mutex); - if (_entries.count(dep_key)) { - Warning("ConfigRegistry: dep_key '%s' collides with an existing entry key, ignoring", dep_key.c_str()); - return -1; - } - if (_dep_key_to_parent.count(dep_key)) { - Warning("ConfigRegistry: dep_key '%s' already registered, ignoring", dep_key.c_str()); - return -1; - } - _dep_key_to_parent[dep_key] = key; - Dbg(dbg_ctl, "Dependency key '%s' routes to parent '%s'", dep_key.c_str(), key.c_str()); - return 0; -} - -std::pair -ConfigRegistry::resolve(const std::string &key) const -{ - std::shared_lock lock(_mutex); - - // Direct entry lookup - auto it = _entries.find(key); - if (it != _entries.end()) { - return {key, &it->second}; - } - - // Dependency key lookup - auto dep_it = _dep_key_to_parent.find(key); - if (dep_it != _dep_key_to_parent.end()) { - auto parent_it = _entries.find(dep_it->second); - if (parent_it != _entries.end()) { - return {dep_it->second, &parent_it->second}; - } - } - - return {{}, nullptr}; -} - -bool -ConfigRegistry::contains(const std::string &key) const -{ - std::shared_lock lock(_mutex); - return _entries.find(key) != _entries.end(); -} - -ConfigRegistry::Entry const * -ConfigRegistry::find(const std::string &key) const -{ - std::shared_lock lock(_mutex); - auto it = _entries.find(key); - return it != _entries.end() ? &it->second : nullptr; -} - -void -ConfigRegistry::set_passed_config(const std::string &key, YAML::Node content) -{ - std::unique_lock lock(_mutex); - _passed_configs[key] = std::move(content); - Dbg(dbg_ctl, "Stored passed config for '%s'", key.c_str()); -} - -void -ConfigRegistry::schedule_reload(const std::string &key) -{ - Dbg(dbg_ctl, "Scheduling async reload for config '%s'", key.c_str()); - - Ptr mutex(new_ProxyMutex()); - eventProcessor.schedule_imm(new ScheduledReloadContinuation(mutex, key), ET_TASK); -} - -void -ConfigRegistry::execute_reload(const std::string &key) -{ - Dbg(dbg_ctl, "Executing reload for config '%s'", key.c_str()); - - YAML::Node passed_config; - bool has_passed_config{false}; - Entry entry_copy; - { - std::unique_lock lock(_mutex); - - if (auto pc_it = _passed_configs.find(key); pc_it != _passed_configs.end()) { - passed_config = pc_it->second; - has_passed_config = true; - _passed_configs.erase(pc_it); - Dbg(dbg_ctl, "Retrieved and consumed passed config for '%s'", key.c_str()); - } - - if (auto it = _entries.find(key); it != _entries.end()) { - entry_copy = it->second; - } else { - Warning("Config '%s' not found in registry during execute_reload", key.c_str()); - return; - } - } - - ink_release_assert(entry_copy.has_handler()); - - // Create context with subtask tracking - // For rpc reload: use key as description, no filename (source: rpc) - // For file reload: use key as description, filename indicates source: file - std::string filename = has_passed_config ? "" : entry_copy.resolve_filename(); - auto ctx = ReloadCoordinator::Get_Instance().create_config_context(entry_copy.key, entry_copy.key, filename); - ctx.in_progress(); - - if (has_passed_config) { - Dbg(dbg_ctl, "Config '%s' reloading from rpc-supplied content", entry_copy.key.c_str()); - - // Extract _reload directives before passing content to the handler. - // This keeps supplied_yaml() clean (pure config data) and provides - // reload_directives() as a separate accessor for operational parameters. - if (passed_config.IsMap() && passed_config["_reload"]) { - auto directives = passed_config["_reload"]; - if (!directives.IsMap()) { - Warning("Config '%s': _reload must be a YAML map, ignoring directives", entry_copy.key.c_str()); - } else { - Dbg(dbg_ctl, "Config '%s' has reload directives", entry_copy.key.c_str()); - ctx.set_reload_directives(directives); - } - passed_config.remove("_reload"); - } - - // After stripping _reload, pass remaining content (if any) as supplied_yaml - if (passed_config.size() > 0) { - ctx.set_supplied_yaml(passed_config); - } - } else { - Dbg(dbg_ctl, "Config '%s' reloading from file '%s'", entry_copy.key.c_str(), filename.c_str()); - } - - // Handler checks ctx.supplied_yaml() for rpc-supplied content, otherwise reads from the - // module's known filename. - try { - entry_copy.handler(ctx); - if (!ctx.is_terminal()) { // handler did not call ctx.complete() or ctx.fail(). It may have deferred work to another thread. - Warning("Config '%s' handler returned without reaching a terminal state. " - "If the handler deferred work to another thread, ensure ctx.complete() or ctx.fail() " - "is called when processing finishes; otherwise the task will remain in progress " - "until the timeout checker marks it as TIMEOUT.", - entry_copy.key.c_str()); - } - Dbg(dbg_ctl, "Config '%s' reload completed", entry_copy.key.c_str()); - // TODO: For future diff/etc support, snapshot the config content here. - // For RPC reloads: serialize passed_config to string. - // For file reloads: read the file content at reload time. - // Store in ConfigReloadTask::Info for history-based diffing. - } catch (std::exception const &ex) { - ctx.fail(ex.what()); - Warning("Config '%s' reload failed: %s", entry_copy.key.c_str(), ex.what()); - } -} - -} // namespace config diff --git a/src/mgmt/config/ConfigReloadExecutor.cc b/src/mgmt/config/ConfigReloadExecutor.cc deleted file mode 100644 index 86a1d2714cc..00000000000 --- a/src/mgmt/config/ConfigReloadExecutor.cc +++ /dev/null @@ -1,102 +0,0 @@ -/** @file - - Config reload execution logic - schedules async reload work on ET_TASK. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#include "mgmt/config/ConfigReloadExecutor.h" -#include "mgmt/config/FileManager.h" -#include "mgmt/config/ReloadCoordinator.h" - -#include "iocore/eventsystem/Continuation.h" -#include "iocore/eventsystem/Tasks.h" -#include "iocore/eventsystem/EventProcessor.h" - -#include "records/RecCore.h" - -#include "tscore/Diags.h" - -namespace -{ -DbgCtl dbg_ctl_config{"config.reload"}; - -/** - * Continuation that executes the actual config reload work. - * This runs on ET_TASK thread to avoid blocking the main RPC thread. - */ -struct ReloadWorkContinuation : public Continuation { - int - handleEvent(int /* etype */, void * /* data */) - { - bool failed{false}; - auto current_task = ReloadCoordinator::Get_Instance().get_current_task(); - - Dbg(dbg_ctl_config, "Executing config reload work"); - - if (current_task) { - Dbg(dbg_ctl_config, "Reload task token: %s", current_task->get_token().c_str()); - } - - // This will tell each changed file to reread itself. If some module is waiting - // for a record to be reloaded, it will be notified and the file update will happen - // at each module's logic. - // Each module will get a ConfigContext object which will be used to track the reload progress. - if (auto err = FileManager::instance().rereadConfig(); !err.empty()) { - Dbg(dbg_ctl_config, "rereadConfig failed"); - failed = true; - } - - // Force-flush pending record callbacks. rereadConfig() marks records as - // sync-required via RecSetSyncRequired(), but the actual on_record_change - // callbacks normally wait for the next config_update_cont tick (~3s). - // Flushing here fires those callbacks immediately so record-triggered - // handlers can reserve their subtasks before handleEvent() returns. - // RecExecConfigUpdateCbs clears the sync-required flag so the next - // config_update_cont tick is a no-op for these records. - RecFlushConfigUpdateCbs(); - Dbg(dbg_ctl_config, "Flushed pending record update callbacks"); - - Dbg(dbg_ctl_config, "Invoking plugin callbacks"); - // If any callback was registered (TSMgmtUpdateRegister) for config notifications, - // then it will eventually be notified. - FileManager::instance().invokeConfigPluginCallbacks(); - - Dbg(dbg_ctl_config, "Reload work completed, failed=%s", failed ? "true" : "false"); - - delete this; - return failed ? EVENT_ERROR : EVENT_DONE; - } - - ReloadWorkContinuation() : Continuation(new_ProxyMutex()) { SET_HANDLER(&ReloadWorkContinuation::handleEvent); } -}; - -} // namespace - -namespace config -{ - -void -schedule_reload_work(std::chrono::milliseconds delay) -{ - Dbg(dbg_ctl_config, "Scheduling reload work with %lldms delay", static_cast(delay.count())); - eventProcessor.schedule_in(new ReloadWorkContinuation(), HRTIME_MSECONDS(delay.count()), ET_TASK); -} - -} // namespace config diff --git a/src/mgmt/config/ConfigReloadTrace.cc b/src/mgmt/config/ConfigReloadTrace.cc deleted file mode 100644 index bf26a6489f8..00000000000 --- a/src/mgmt/config/ConfigReloadTrace.cc +++ /dev/null @@ -1,475 +0,0 @@ -/** @file - - ConfigReloadTrace — reload progress checker and task timeout detection. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#include "mgmt/config/ConfigReloadTrace.h" -#include "mgmt/config/ConfigContext.h" -#include "records/RecCore.h" -#include "tsutil/ts_diag_levels.h" - -#include -#include "tsutil/Metrics.h" -#include "tsutil/ts_time_parser.h" - -namespace -{ -DbgCtl dbg_ctl_config{"config.reload"}; - -/// Helper to read a time duration from records configuration. -[[nodiscard]] std::chrono::milliseconds -read_time_record(std::string_view record_name, std::string_view default_value, std::chrono::milliseconds fallback, - std::chrono::milliseconds minimum = std::chrono::milliseconds{0}) -{ - // record_name / default_value are compile-time string_view constants, always null-terminated. - char str[128] = {0}; - - auto result = RecGetRecordString(record_name.data(), str, sizeof(str)); - std::string_view value = (result.has_value() && !result->empty()) ? result.value() : default_value; - - auto [duration, errata] = ts::time_parser(value); - if (!errata.is_ok()) { - Dbg(dbg_ctl_config, "Failed to parse '%.*s' value '%.*s': using fallback", static_cast(record_name.size()), - record_name.data(), static_cast(value.size()), value.data()); - return fallback; - } - - auto ms = std::chrono::duration_cast(duration); - - // Enforce minimum if specified - if (minimum.count() > 0 && ms < minimum) { - Dbg(dbg_ctl_config, "'%.*s' value %lldms below minimum, using %lldms", static_cast(record_name.size()), record_name.data(), - static_cast(ms.count()), static_cast(minimum.count())); - return minimum; - } - - return ms; -} -} // namespace - -std::chrono::milliseconds -ConfigReloadProgress::get_configured_timeout() -{ - return read_time_record(RECORD_TIMEOUT, DEFAULT_TIMEOUT, std::chrono::hours{1}); -} - -std::chrono::milliseconds -ConfigReloadProgress::get_configured_check_interval() -{ - return read_time_record(RECORD_CHECK_INTERVAL, DEFAULT_CHECK_INTERVAL, std::chrono::seconds{2}, - std::chrono::milliseconds{MIN_CHECK_INTERVAL_MS}); -} - -ConfigContext -ConfigReloadTask::add_child(std::string_view description) -{ - std::unique_lock lock(_mutex); - // Read token directly - can't call get_token() as it would deadlock (tries to acquire shared_lock on same mutex) - auto trace = std::make_shared(_info.token, description, false, shared_from_this()); - _info.sub_tasks.push_back(trace); - return ConfigContext{trace, description}; -} - -ConfigReloadTask & -ConfigReloadTask::log(std::string const &text) -{ - std::unique_lock lock(_mutex); - _info.logs.push_back({DL_Undefined, text}); - return *this; -} - -ConfigReloadTask & -ConfigReloadTask::log(DiagsLevel level, std::string const &text) -{ - std::unique_lock lock(_mutex); - _info.logs.push_back({level, text}); - return *this; -} - -void -ConfigReloadTask::add_sub_task(ConfigReloadTaskPtr sub_task) -{ - { - std::unique_lock lock(_mutex); - Dbg(dbg_ctl_config, "Adding subtask %s to task %s", sub_task->get_description().c_str(), _info.description.c_str()); - _info.sub_tasks.push_back(sub_task); - } - // Re-aggregate only if the parent prematurely reached SUCCESS. Record-triggered - // handlers register asynchronously (via config_update_cont, ~3s timer), so a - // new CREATED subtask can arrive after all previously known subtasks completed. - // Guarding on SUCCESS avoids redundant work when the parent is still IN_PROGRESS - // and avoids overwriting a TIMEOUT set by the progress checker. - if (get_state() == State::SUCCESS) { - aggregate_status(); - } -} - -bool -ConfigReloadTask::has_subtask_for_key(std::string_view key) const -{ - std::shared_lock lock(_mutex); - return std::any_of(_info.sub_tasks.begin(), _info.sub_tasks.end(), - [&key](const auto &t) { return t->_info.config_key == key; }); // config_key is immutable once in sub_tasks -} - -ConfigReloadTaskPtr -ConfigReloadTask::find_subtask_by_key(std::string_view key) const -{ - std::shared_lock lock(_mutex); - auto it = - std::find_if(_info.sub_tasks.begin(), _info.sub_tasks.end(), [&key](const auto &t) { return t->_info.config_key == key; }); - return it != _info.sub_tasks.end() ? *it : nullptr; -} - -void -ConfigReloadTask::set_in_progress() -{ - this->set_state_and_notify(State::IN_PROGRESS); -} - -void -ConfigReloadTask::set_completed() -{ - this->set_state_and_notify(State::SUCCESS); -} - -void -ConfigReloadTask::set_failed() -{ - this->set_state_and_notify(State::FAIL); -} - -/// @note Main-task only. Does not call notify_parent() — do not call on subtasks. -void -ConfigReloadTask::mark_as_bad_state(std::string_view reason) -{ - std::unique_lock lock(_mutex); - // Once a task reaches SUCCESS, FAIL, or TIMEOUT, reject further transitions. - if (is_terminal(_info.state)) { - Warning("ConfigReloadTask '%s': ignoring mark_as_bad_state from %.*s — already terminal.", _info.description.c_str(), - static_cast(state_to_string(_info.state).size()), state_to_string(_info.state).data()); - return; - } - _info.state = State::TIMEOUT; - _atomic_last_updated_ms.store(now_ms(), std::memory_order_release); - if (!reason.empty()) { - // Push directly to avoid deadlock (log() would try to acquire same mutex) - _info.logs.push_back({DL_Undefined, std::string{reason}}); - } -} - -void -ConfigReloadTask::notify_parent() -{ - Dbg(dbg_ctl_config, "parent null =%s , parent main task? %s", _parent ? "false" : "true", - (_parent && _parent->is_main_task()) ? "true" : "false"); - - if (_parent) { - _parent->aggregate_status(); - } -} - -void -ConfigReloadTask::set_state_and_notify(State state) -{ - { - std::unique_lock lock(_mutex); - if (_info.state == state) { - return; - } - // Once a task reaches a terminal state, reject further transitions. - if (is_terminal(_info.state)) { - auto const cur_str = state_to_string(_info.state); - auto const new_str = state_to_string(state); - Dbg(dbg_ctl_config, "ConfigReloadTask '%s': ignoring transition from %.*s to %.*s — already terminal.", - _info.description.c_str(), static_cast(cur_str.size()), cur_str.data(), static_cast(new_str.size()), - new_str.data()); - return; - } - Dbg(dbg_ctl_config, "State changed to %.*s for task %s", static_cast(state_to_string(state).size()), - state_to_string(state).data(), _info.description.c_str()); - _info.state = state; - _atomic_last_updated_ms.store(now_ms(), std::memory_order_release); - } - - // Now that the lock is released, we can safely notify the parent. - this->notify_parent(); -} - -void -ConfigReloadTask::aggregate_status() -{ - // Use unique_lock throughout to avoid TOCTOU race and data races - std::unique_lock lock(_mutex); - - if (_info.sub_tasks.empty()) { - // No subtasks - keep current state (don't change to CREATED) - return; - } - - bool any_failed = false; - bool any_in_progress = false; - bool all_success = true; - bool all_created = true; - - for (const auto &sub_task : _info.sub_tasks) { - State sub_state = sub_task->get_state(); - switch (sub_state) { - case State::FAIL: - case State::TIMEOUT: // Treat TIMEOUT as failure - any_failed = true; - all_success = false; - all_created = false; - break; - case State::IN_PROGRESS: // Handle IN_PROGRESS explicitly! - any_in_progress = true; - all_success = false; - all_created = false; - break; - case State::SUCCESS: - all_created = false; - break; - case State::CREATED: - all_success = false; - break; - case State::INVALID: - default: - // Unknown state - treat as not success, not created - all_success = false; - all_created = false; - break; - } - } - - // Determine new parent state based on children - // Priority: FAIL/TIMEOUT > IN_PROGRESS > SUCCESS > CREATED - State new_state; - if (any_failed) { - new_state = State::FAIL; - } else if (any_in_progress) { - // If any subtask is still working, parent is IN_PROGRESS - new_state = State::IN_PROGRESS; - } else if (all_success) { - Dbg(dbg_ctl_config, "Setting %s task '%s' to SUCCESS (all subtasks succeeded)", _info.main_task ? "main" : "sub", - _info.description.c_str()); - new_state = State::SUCCESS; - } else if (all_created && !_info.main_task) { - Dbg(dbg_ctl_config, "Setting %s task '%s' to CREATED (all subtasks created)", _info.main_task ? "main" : "sub", - _info.description.c_str()); - new_state = State::CREATED; - } else { - // Mixed state or main task with created subtasks - keep as IN_PROGRESS - Dbg(dbg_ctl_config, "Setting %s task '%s' to IN_PROGRESS (mixed state)", _info.main_task ? "main" : "sub", - _info.description.c_str()); - new_state = State::IN_PROGRESS; - } - - // Only update if state actually changed - if (_info.state != new_state) { - _info.state = new_state; - _atomic_last_updated_ms.store(now_ms(), std::memory_order_release); - } - - // Release lock before notifying parent to avoid potential deadlock - lock.unlock(); - - if (_parent) { - _parent->aggregate_status(); - } -} - -void -ConfigReloadTask::log_reload_summary(State final_state) -{ - // Snapshot token and sub_tasks under the lock to avoid races with RPC readers - // that also consult _info via get_info()/get_state() on other threads. - std::string token; - std::vector sub_tasks_snapshot; - { - std::unique_lock lock(_mutex); - if (!_info.main_task || !is_terminal(final_state) || _summary_logged) { - return; - } - _summary_logged = true; - token = _info.token; - sub_tasks_snapshot = _info.sub_tasks; - } - - int success_count{0}, fail_count{0}, total{0}; - for (const auto &sub : sub_tasks_snapshot) { - State st = sub->get_state(); - if (st == State::SUCCESS) { - success_count++; - } else if (st == State::FAIL || st == State::TIMEOUT) { - fail_count++; - } - total++; - } - - if (final_state == State::SUCCESS) { - Note("Config reload [%s] completed: %d/%d tasks succeeded", token.c_str(), success_count, total); - } else { - Warning("Config reload [%s] finished with failures: %d succeeded, %d failed (%d total) " - "— run: traffic_ctl config status -t %s", - token.c_str(), success_count, fail_count, total, token.c_str()); - } - - dump_subtask_tree(sub_tasks_snapshot, 2); -} - -void -ConfigReloadTask::dump_subtask_tree(const std::vector &tasks, int indent) -{ - static constexpr int MAX_DEPTH = 10; - if (indent / 2 > MAX_DEPTH) { - return; - } - for (const auto &sub : tasks) { - auto sub_state = sub->get_state(); - auto sub_info = sub->get_info(); - Dbg(dbg_ctl_config, "%*s[%.*s] %s", indent, "", static_cast(state_to_string(sub_state).size()), - state_to_string(sub_state).data(), sub_info.description.c_str()); - for (const auto &entry : sub_info.logs) { - if (entry.level != DL_Undefined) { - static constexpr const char *tags[] = { - "[Diag] ", "[Dbg] ", "[Stat] ", "[Note] ", "[Warn] ", "[Err] ", "[Fatal] ", "[Alert] ", "[Emrg] ", - }; - int idx = std::min(std::max(static_cast(entry.level), 0), static_cast(DL_Emergency)); - Dbg(dbg_ctl_config, "%*s %s%s", indent, "", tags[idx], entry.text.c_str()); - } else { - Dbg(dbg_ctl_config, "%*s %s", indent, "", entry.text.c_str()); - } - } - if (!sub_info.sub_tasks.empty()) { - dump_subtask_tree(sub_info.sub_tasks, indent + 2); - } - } -} - -int64_t -ConfigReloadTask::get_last_updated_time_ms() const -{ - int64_t last_time_ms = _atomic_last_updated_ms.load(std::memory_order_acquire); - - std::shared_lock lock(_mutex); - for (const auto &sub_task : _info.sub_tasks) { - int64_t sub_time_ms = sub_task->get_own_last_updated_time_ms(); - if (sub_time_ms > last_time_ms) { - last_time_ms = sub_time_ms; - } - } - return last_time_ms; -} - -std::time_t -ConfigReloadTask::get_last_updated_time() const -{ - return static_cast( - std::chrono::duration_cast(std::chrono::milliseconds{get_last_updated_time_ms()}).count()); -} -void -ConfigReloadTask::start_progress_checker() -{ - std::unique_lock lock(_mutex); - if (!_reload_progress_checker_started && _info.main_task && _info.state == State::IN_PROGRESS) { // can only start once - auto *checker = new ConfigReloadProgress(shared_from_this()); - eventProcessor.schedule_in(checker, HRTIME_MSECONDS(checker->get_check_interval().count()), ET_TASK); - _reload_progress_checker_started = true; - } -} - -// reload progress checker -int -ConfigReloadProgress::check_progress(int /* etype */, void * /* data */) -{ - Dbg(dbg_ctl_config, "Checking progress for reload task %s - descr: %s", _reload ? _reload->get_token().c_str() : "null", - _reload ? _reload->get_description().c_str() : "null"); - if (_reload == nullptr) { - return EVENT_DONE; - } - - auto const current_state = _reload->get_state(); - if (ConfigReloadTask::is_terminal(current_state)) { - auto const state_str = ConfigReloadTask::state_to_string(current_state); - if (_awaiting_terminal_confirmation) { - // Confirmed terminal — safe to stop. - Dbg(dbg_ctl_config, "Reload task %s confirmed %.*s after grace period, stopping progress check.", - _reload->get_token().c_str(), static_cast(state_str.size()), state_str.data()); - _reload->log_reload_summary(current_state); - return EVENT_DONE; - } - // First observation of terminal state — reschedule once more to confirm - // it isn't pulled back to IN_PROGRESS by a late reserve_subtask(). - _awaiting_terminal_confirmation = true; - Dbg(dbg_ctl_config, "Reload task %s reached %.*s, scheduling confirmation in %lldms.", _reload->get_token().c_str(), - static_cast(state_str.size()), state_str.data(), static_cast(TERMINAL_CONFIRMATION_DELAY.count())); - eventProcessor.schedule_in(this, HRTIME_MSECONDS(TERMINAL_CONFIRMATION_DELAY.count()), ET_TASK); - return EVENT_CONT; - } - // State was pulled back from terminal (e.g., SUCCESS -> IN_PROGRESS) — reset and continue. - _awaiting_terminal_confirmation = false; - - // Get configured timeout (read dynamically to allow runtime changes) - // Returns 0ms if disabled (timeout string is "0" or empty) - auto max_running_time = get_configured_timeout(); - - // Check if timeout is disabled (0ms means disabled) - if (max_running_time.count() == 0) { - Dbg(dbg_ctl_config, "Timeout disabled - task %s will run indefinitely until completion or manual cancellation", - _reload->get_token().c_str()); - // Still reschedule to detect completion, but don't timeout - eventProcessor.schedule_in(this, HRTIME_MSECONDS(_every.count()), ET_TASK); - return EVENT_CONT; - } - - // ok, it's running, should we keep it running? - auto ct = std::chrono::system_clock::from_time_t(_reload->get_created_time()); - auto lut = std::chrono::system_clock::from_time_t(_reload->get_last_updated_time()); - std::string buf; - if (lut + max_running_time < std::chrono::system_clock::now()) { - if (_reload->contains_dependents()) { - swoc::bwprint(buf, "Task {} timed out after {}ms with no reload action (no config to reload). Last state: {}", - _reload->get_token(), max_running_time.count(), ConfigReloadTask::state_to_string(current_state)); - } else { - swoc::bwprint(buf, "Reload task {} timed out after {}ms. Previous state: {}.", _reload->get_token(), max_running_time.count(), - ConfigReloadTask::state_to_string(current_state)); - } - _reload->mark_as_bad_state(buf); - Dbg(dbg_ctl_config, "%s", buf.c_str()); - return EVENT_DONE; - } - - swoc::bwprint(buf, - "Reload task {} ongoing with state {}, created at {} and last update at {}. Timeout in {}ms. Will check again.", - _reload->get_token(), ConfigReloadTask::state_to_string(current_state), - swoc::bwf::Date(std::chrono::system_clock::to_time_t(ct)), - swoc::bwf::Date(std::chrono::system_clock::to_time_t(lut)), max_running_time.count()); - Dbg(dbg_ctl_config, "%s", buf.c_str()); - - eventProcessor.schedule_in(this, HRTIME_MSECONDS(_every.count()), ET_TASK); - return EVENT_CONT; -} - -ConfigReloadProgress::ConfigReloadProgress(ConfigReloadTaskPtr reload) - : Continuation(new_ProxyMutex()), _reload{reload}, _every{get_configured_check_interval()} -{ - SET_HANDLER(&ConfigReloadProgress::check_progress); -} diff --git a/src/mgmt/config/FileManager.cc b/src/mgmt/config/FileManager.cc index 6928b40cc5e..31a7834d0f3 100644 --- a/src/mgmt/config/FileManager.cc +++ b/src/mgmt/config/FileManager.cc @@ -25,13 +25,14 @@ #include #include +#include "api/InkAPIInternal.h" // TODO: this brings a lot of dependencies, double check this. + #include "tscore/ink_platform.h" #include "tscore/ink_file.h" #include "../../records/P_RecCore.h" #include "tscore/Diags.h" #include "tscore/Filenames.h" #include "tscore/Layout.h" -#include "mgmt/config/ConfigRegistry.h" #if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC #define TS_ARCHIVE_STAT_MTIME(t) ((t).st_mtime * 1000000000 + (t).st_mtimespec.tv_nsec) @@ -51,12 +52,13 @@ process_config_update(std::string const &fileName, std::string const &configName Dbg(dbg_ctl, "Config update requested for '%s'. [%s]", fileName.empty() ? "Unknown" : fileName.c_str(), configName.empty() ? "No config record associated" : configName.c_str()); swoc::Errata ret; - // records.yaml reload is now handled by its ConfigRegistry handler - // (registered in register_config_files() in traffic_server.cc). - // Delegate to ConfigRegistry::execute_reload("records") so the reload - // is traced and status-reported like every other config. + // TODO: make sure records holds the name after change, if not we should change it. if (fileName == ts::filename::RECORDS) { - config::ConfigRegistry::Get_Instance().execute_reload("records"); + if (auto zret = RecReadYamlConfigFile(); zret) { + RecConfigWarnIfUnregistered(); + } else { + ret.note("Error reading {}", fileName).note(zret); + } } else if (!configName.empty()) { // Could be the case we have a child file to reload with no related config record. RecT rec_type; if (auto r = RecGetRecordType(configName.c_str(), &rec_type); r == REC_ERR_OKAY && rec_type == RECT_CONFIG) { @@ -70,13 +72,13 @@ process_config_update(std::string const &fileName, std::string const &configName } // JSONRPC endpoint defs. -constexpr const char *CONFIG_REGISTRY_KEY_STR{"config_registry"}; -constexpr const char *FILE_PATH_KEY_STR{"file_path"}; -constexpr const char *RECORD_NAME_KEY_STR{"config_record_name"}; -constexpr const char *PARENT_CONFIG_KEY_STR{"parent_config"}; -constexpr const char *ROOT_ACCESS_NEEDED_KEY_STR{"root_access_needed"}; -constexpr const char *IS_REQUIRED_KEY_STR{"is_required"}; -constexpr const char *NA_STR{"N/A"}; +const std::string CONFIG_REGISTRY_KEY_STR{"config_registry"}; +const std::string FILE_PATH_KEY_STR{"file_path"}; +const std::string RECORD_NAME_KEY_STR{"config_record_name"}; +const std::string PARENT_CONFIG_KEY_STR{"parent_config"}; +const std::string ROOT_ACCESS_NEEDED_KEY_STR{"root_access_needed"}; +const std::string IS_REQUIRED_KEY_STR{"is_required"}; +const std::string NA_STR{"N/A"}; } // namespace @@ -151,18 +153,21 @@ FileManager::fileChanged(std::string const &fileName, std::string const &configN return ret; } +// TODO: To do the following here, we have to pull up a lot of dependencies we don't really +// need, #include "InkAPIInternal.h" brings plenty of them. Double check this approach. RPC will +// also be able to pass messages to plugins, once that's designed it can also cover this. void -FileManager::registerConfigPluginCallbacks(std::function cb) +FileManager::registerConfigPluginCallbacks(ConfigUpdateCbTable *cblist) { - _pluginCallback = std::move(cb); + _pluginCallbackList = cblist; } void FileManager::invokeConfigPluginCallbacks() { Dbg(dbg_ctl, "invoke plugin callbacks"); - if (_pluginCallback) { - _pluginCallback(); + if (_pluginCallbackList) { + _pluginCallbackList->invoke(); } } @@ -189,8 +194,7 @@ FileManager::rereadConfig() // ToDo: rb->isVersions() was always true before, because numberBackups was always >= 1. So ROLLBACK_CHECK_ONLY could not // happen at all... if (rb->checkForUserUpdate(FileManager::ROLLBACK_CHECK_AND_UPDATE)) { - Dbg(dbg_ctl, "File %s changed. Has a parent=%s, ", it.first.c_str(), - rb->getParentConfig() ? rb->getParentConfig()->getFileName() : "none"); + Dbg(dbg_ctl, "File %s changed.", it.first.c_str()); if (auto const &r = fileChanged(rb->getFileName(), rb->getConfigName()); !r) { ret.note(r); } @@ -381,16 +385,6 @@ FileManager::ConfigManager::checkForUserUpdate(FileManager::RollBackCheckType ho ink_mutex_acquire(&fileAccessLock); if (this->statFile(&fileInfo) < 0) { - // File doesn't exist. If it previously existed (fileLastModified > 0), - // treat the deletion as a change so the reload handler can fall back - // to an alternative config file (e.g. remap.yaml -> remap.config). - if (fileLastModified > 0) { - if (how == FileManager::ROLLBACK_CHECK_AND_UPDATE) { - fileLastModified = 0; - } - ink_mutex_release(&fileAccessLock); - return true; - } ink_mutex_release(&fileAccessLock); return false; } diff --git a/src/mgmt/config/ReloadCoordinator.cc b/src/mgmt/config/ReloadCoordinator.cc deleted file mode 100644 index 202a7806af1..00000000000 --- a/src/mgmt/config/ReloadCoordinator.cc +++ /dev/null @@ -1,270 +0,0 @@ -/** @file - - ReloadCoordinator - tracks config reload sessions and status. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -#include "mgmt/config/ReloadCoordinator.h" - -#include -#include -#include - -#include "tscore/Diags.h" -#include "tsutil/Metrics.h" -#include "swoc/Errata.h" - -namespace -{ -DbgCtl dbg_ctl{"config.reload"}; -} // namespace - -swoc::Errata -ReloadCoordinator::prepare_reload(std::string &token_name, const char *token_prefix, bool force) -{ - std::unique_lock lock(_mutex); - - if (token_name.empty()) { - token_name = generate_token_name(token_prefix); - } - - Dbg(dbg_ctl, "Preparing reload task for token: %s (force=%s)", token_name.c_str(), force ? "true" : "false"); - - // Check if a reload is already in progress. - if (_current_task != nullptr) { - auto state = _current_task->get_state(); - if (!ConfigReloadTask::is_terminal(state) && state != ConfigReloadTask::State::INVALID) { - if (force) { - Dbg(dbg_ctl, "Force mode: marking existing reload as stale to start new one"); - _current_task->mark_as_bad_state("Superseded by forced reload"); - } else { - return swoc::Errata("Reload already in progress for token: {}", token_name); - } - } - } - - Dbg(dbg_ctl, "No reload in progress detected"); - - // Create the main task for tracking (status tracking only) - // The actual reload work is scheduled separately via config::schedule_reload_work() - create_main_config_task(token_name, "Main reload task"); - - Dbg(dbg_ctl, "Reload task created with token: %s", token_name.c_str()); - return {}; // Success — caller will schedule the actual work -} - -void -ReloadCoordinator::create_main_config_task(std::string_view token, std::string description) -{ - _current_task = std::make_shared(token, description, true /*root*/, nullptr); - _current_task->start_progress_checker(); - - std::string txt; - swoc::bwprint(txt, "{} - {}", description, swoc::bwf::Date(_current_task->get_created_time())); - _current_task->set_description(txt); - - // Enforce history size limit — remove oldest when full - if (_history.size() >= MAX_HISTORY_SIZE) { - _history.erase(_history.begin()); - } - _history.push_back(_current_task); - - ts::Metrics &metrics = ts::Metrics::instance(); - static auto reconf_time = metrics.lookup("proxy.process.proxy.reconfigure_time"); - metrics[reconf_time].store( - _current_task->get_created_time()); // This may be different from the actual task reload time. Ok for now. -} - -std::string -ReloadCoordinator::generate_token_name(const char *prefix) const -{ - auto now = std::chrono::system_clock::now(); - auto time = std::chrono::duration_cast(now.time_since_epoch()).count(); - return std::string(prefix) + std::to_string(time); -} - -void -ReloadCoordinator::reserve_subtask(std::string_view config_key) -{ - std::unique_lock lock(_mutex); - if (!_current_task) { - return; - } - - // Block only hard-terminal states (FAIL, TIMEOUT). SUCCESS is intentionally - // allowed: records.yaml is always the first file processed by rereadConfig(), - // so the main task reaches SUCCESS (from the "records" subtask) before any - // other file-based or record-triggered reservation runs. Adding a CREATED - // subtask to a SUCCESS parent causes add_sub_task() → aggregate_status() to - // pull the parent back to IN_PROGRESS. - auto state = _current_task->get_state(); - if (state == ConfigReloadTask::State::FAIL || state == ConfigReloadTask::State::TIMEOUT) { - return; - } - - // Already reserved or handled — nothing to do. - if (_current_task->has_subtask_for_key(config_key)) { - return; - } - - auto task = std::make_shared(_current_task->get_token(), config_key, false, _current_task); - task->set_config_key(config_key); - _current_task->add_sub_task(task); - - Dbg(dbg_ctl, "Reserved subtask for config '%.*s'", static_cast(config_key.size()), config_key.data()); -} - -ConfigContext -ReloadCoordinator::create_config_context(std::string_view config_key, std::string_view description, std::string_view filename) -{ - std::unique_lock lock(_mutex); - if (!_current_task) { - // No active reload — return empty context (safe no-op for startup path) - return ConfigContext{}; - } - - // Check for an existing subtask with this config key. - // - // Two cases: - // 1. Reserved (CREATED) — pre-registered by reserve_subtask() in on_record_change. - // Return a ConfigContext wrapping it so the handler can activate and complete it. - // 2. Already active/done — true duplicate from N trigger records mapping to one config key. - // Return empty to skip. - if (!config_key.empty() && !ConfigReloadTask::is_terminal(_current_task->get_state())) { - auto existing = _current_task->find_subtask_by_key(config_key); - if (existing) { - if (existing->get_state() == ConfigReloadTask::State::CREATED) { - // Activate the reserved subtask - Dbg(dbg_ctl, "Activating reserved subtask for config '%.*s'", static_cast(config_key.size()), config_key.data()); - return ConfigContext{existing, description, filename}; - } - // Already handled — true duplicate - Dbg(dbg_ctl, "Duplicate reload for config '%.*s' — subtask already exists, skipping", static_cast(config_key.size()), - config_key.data()); - return ConfigContext{}; - } - } - - // No existing subtask — create a new one - auto task = - std::make_shared(_current_task->get_token(), description, false /*not a main reload job*/, _current_task); - task->set_config_key(config_key); - _current_task->add_sub_task(task); - - ConfigContext ctx{task, description, filename}; - return ctx; -} - -bool -ReloadCoordinator::is_reload_in_progress() const -{ - std::shared_lock lock(_mutex); - - if (_current_task == nullptr) { - Dbg(dbg_ctl, "No current task found, reload not in progress."); - return false; - } - - auto state = _current_task->get_state(); - if (!ConfigReloadTask::is_terminal(state) && state != ConfigReloadTask::State::INVALID) { - Dbg(dbg_ctl, "Found reload in progress for task: %s", _current_task->get_token().c_str()); - return true; - } else { - auto state_str = ConfigReloadTask::state_to_string(state); - Dbg(dbg_ctl, "Current task is not running, state: %s", state_str.data()); - } - - return false; -} - -std::pair -ReloadCoordinator::find_by_token(std::string_view token_name) const -{ - std::shared_lock lock(_mutex); - Dbg(dbg_ctl, "Search %s, history size=%d", token_name.data(), static_cast(_history.size())); - - auto it = - std::find_if(_history.begin(), _history.end(), [&token_name](auto const &task) { return task->get_token() == token_name; }); - - if (it != _history.end()) { - return {true, (*it)->get_info()}; - } - return {false, ConfigReloadTask::Info{}}; -} - -std::vector -ReloadCoordinator::get_all(std::size_t N) const -{ - std::shared_lock lock(_mutex); - std::vector result; - if (N == 0) { - N = _history.size(); - } - - result.reserve(std::min(N, _history.size())); - - auto start_it = _history.begin(); - if (_history.size() > N) { - start_it = _history.end() - N; - } - - std::transform(start_it, _history.end(), std::back_inserter(result), - [](const std::shared_ptr &task) { return task->get_info(); }); - - return result; -} - -bool -ReloadCoordinator::mark_task_as_stale(std::string_view token, std::string_view reason) -{ - std::unique_lock lock(_mutex); - - std::shared_ptr task_to_mark; - - if (token.empty()) { - task_to_mark = _current_task; - } else { - auto it = std::find_if(_history.begin(), _history.end(), [&token](auto const &task) { return task->get_token() == token; }); - if (it != _history.end()) { - task_to_mark = *it; - } - } - - if (!task_to_mark) { - Dbg(dbg_ctl, "No task found to mark stale (token: %.*s)", static_cast(token.size()), - token.empty() ? "" : token.data()); - return false; - } - - auto state = task_to_mark->get_state(); - if (ConfigReloadTask::is_terminal(state)) { - auto const state_str = ConfigReloadTask::state_to_string(state); - Dbg(dbg_ctl, "Task %s already in terminal state (%.*s), cannot mark stale", task_to_mark->get_token().c_str(), - static_cast(state_str.size()), state_str.data()); - return false; - } - - auto state_str = ConfigReloadTask::state_to_string(state); - Dbg(dbg_ctl, "Marking task %s as stale (state: %s) - reason: %s", task_to_mark->get_token().c_str(), state_str.data(), - reason.data()); - - task_to_mark->mark_as_bad_state(reason); - return true; -} diff --git a/src/mgmt/rpc/CMakeLists.txt b/src/mgmt/rpc/CMakeLists.txt index 8bd738c48c0..6ab29e3dbdf 100644 --- a/src/mgmt/rpc/CMakeLists.txt +++ b/src/mgmt/rpc/CMakeLists.txt @@ -68,9 +68,7 @@ if(BUILD_TESTING) add_executable( test_jsonrpcserver server/unit_tests/test_rpcserver.cc ${CMAKE_SOURCE_DIR}/src/shared/rpc/IPCSocketClient.cc ) - target_link_libraries( - test_jsonrpcserver Catch2::Catch2WithMain ts::jsonrpc_server ts::inkevent libswoc::libswoc configmanager - ) + target_link_libraries(test_jsonrpcserver Catch2::Catch2WithMain ts::jsonrpc_server ts::inkevent libswoc::libswoc) add_catch2_test(NAME test_jsonrpcserver COMMAND test_jsonrpcserver) add_executable(test_record_yaml handlers/common/unit_tests/test_record_yaml.cc) diff --git a/src/mgmt/rpc/handlers/config/Configuration.cc b/src/mgmt/rpc/handlers/config/Configuration.cc index 9f901bbb62e..afe1c538913 100644 --- a/src/mgmt/rpc/handlers/config/Configuration.cc +++ b/src/mgmt/rpc/handlers/config/Configuration.cc @@ -27,19 +27,11 @@ #include "tscore/Diags.h" #include "mgmt/config/FileManager.h" -#include "mgmt/config/ConfigReloadExecutor.h" -#include "mgmt/config/ConfigRegistry.h" #include "../common/RecordsUtils.h" #include "tsutil/Metrics.h" -#include "mgmt/config/ReloadCoordinator.h" -#include "mgmt/config/ConfigReloadErrors.h" -#include "records/YAMLConfigReloadTaskEncoder.h" - -namespace utils = rpc::handlers::records::utils; -using ConfigError = config::reload::errors::ConfigReloadError; -constexpr auto errc = config::reload::errors::to_int; +namespace utils = rpc::handlers::records::utils; namespace { @@ -49,12 +41,11 @@ struct SetRecordCmdInfo { std::string value; }; -DbgCtl dbg_ctl_RPC{"rpc"}; +DbgCtl dbg_ctl_RPC{"RPC"}; } // namespace namespace YAML { - template <> struct convert { static bool decode(Node const &node, SetRecordCmdInfo &info) @@ -226,197 +217,25 @@ set_config_records(std::string_view const & /* id ATS_UNUSED */, YAML::Node cons return resp; } -/// -// Unified config reload handler - supports file source and RPC source modes -// RPC source is detected by presence of "configs" parameter -// swoc::Rv -reload_config(std::string_view const & /* id ATS_UNUSED */, YAML::Node const ¶ms) +reload_config(std::string_view const & /* id ATS_UNUSED */, YAML::Node const & /* params ATS_UNUSED */) { - std::string token = params["token"] ? params["token"].as() : std::string{}; - bool const force = params["force"] ? params["force"].as() : false; - std::string buf; + ts::Metrics &metrics = ts::Metrics::instance(); + static auto reconf_time = metrics.lookup("proxy.process.proxy.reconfigure_time"); + static auto reconf_req = metrics.lookup("proxy.process.proxy.reconfigure_required"); swoc::Rv resp; - - auto make_error = [&](std::string_view msg, int code) -> YAML::Node { - YAML::Node err; - err["message"] = msg; - err["code"] = code; - return err; - }; - - // Check if reload is already in progress - if (!force && ReloadCoordinator::Get_Instance().is_reload_in_progress()) { - resp.result()["errors"].push_back(make_error( - swoc::bwprint(buf, "Reload ongoing with token '{}'", ReloadCoordinator::Get_Instance().get_current_task()->get_token()), - errc(ConfigError::RELOAD_IN_PROGRESS))); - resp.result()["tasks"].push_back(ReloadCoordinator::Get_Instance().get_current_task()->get_info()); - return resp; - } - - // Validate token doesn't already exist - if (!token.empty() && ReloadCoordinator::Get_Instance().has_token(token)) { - resp.result()["errors"].push_back( - make_error(swoc::bwprint(buf, "Token '{}' already exists.", token), errc(ConfigError::TOKEN_ALREADY_EXISTS))); - return resp; - } - - /// - // RPC source: detected by presence of "configs" parameter - // Expected format: - // configs: - // ip_allow: - // - apply: in - // ... - // sni: - // - fqdn: '*.example.com' - // ... - // - if (params["configs"] && params["configs"].IsMap()) { - auto const &configs = params["configs"]; - auto ®istry = ::config::ConfigRegistry::Get_Instance(); - - // Dependency keys (registered via add_file_and_node_dependency) are resolved to their - // parent entry. Multiple dependency keys for the same parent are merged into a single - // YAML node so the parent handler fires only once. - struct ResolvedConfig { - std::string parent_key; - std::string original_key; - YAML::Node content; - }; - std::vector valid_configs; - - for (auto it = configs.begin(); it != configs.end(); ++it) { - std::string key = it->first.as(); - - auto [parent_key, entry] = registry.resolve(key); - if (!entry) { - resp.result()["errors"].push_back( - make_error(swoc::bwprint(buf, "Config '{}' not registered", key), errc(ConfigError::CONFIG_NOT_REGISTERED))); - continue; - } - - if (entry->source != ::config::ConfigSource::FileAndRpc) { - resp.result()["errors"].push_back(make_error(swoc::bwprint(buf, "Config '{}' does not support direct RPC content", key), - errc(ConfigError::RPC_SOURCE_NOT_SUPPORTED))); - continue; - } - - if (!entry->handler) { - resp.result()["errors"].push_back( - make_error(swoc::bwprint(buf, "Config '{}' has no handler", key), errc(ConfigError::CONFIG_NO_HANDLER))); - continue; - } - - valid_configs.push_back({parent_key, key, it->second}); - } - - // If no valid configs, return early without creating a task - if (valid_configs.empty()) { - resp.result()["message"].push_back("No configs were scheduled for reload"); - return resp; - } - - // Create reload task only if we have valid configs - std::string token_prefix = token.empty() ? "rpc-" : ""; - if (auto ret = ReloadCoordinator::Get_Instance().prepare_reload(token, token_prefix.c_str(), force); !ret.is_ok()) { - resp.result()["errors"].push_back( - make_error(swoc::bwprint(buf, "Failed to create reload task: {}", ret), errc(ConfigError::RELOAD_TASK_FAILED))); - return resp; - } - - // - Direct entries (key == parent_key): content passed as-is (existing behavior). - // - Dependency keys (key != parent_key): content merged under original keys, - // so the handler can check yaml["sni"], yaml["ssl_multicert"], etc. - std::unordered_map>> by_parent; - - for (auto &vc : valid_configs) { - by_parent[vc.parent_key].emplace_back(vc.original_key, std::move(vc.content)); - } - - for (auto &[parent_key, items] : by_parent) { - if (items.size() == 1 && items[0].first == parent_key) { - // Single direct entry — pass content as-is (preserves existing behavior) - registry.set_passed_config(parent_key, items[0].second); - } else { - // Dependency key(s) or multiple items — merge under original keys - YAML::Node merged; - for (auto &[orig_key, content] : items) { - merged[orig_key] = content; - } - registry.set_passed_config(parent_key, merged); - } - Dbg(dbg_ctl_RPC, "Scheduling reload for '%s' (%zu config(s))", parent_key.c_str(), items.size()); - registry.schedule_reload(parent_key); - } - - // Build response - resp.result()["token"] = token; - resp.result()["created_time"] = - swoc::bwprint(buf, "{}", swoc::bwf::Date(ReloadCoordinator::Get_Instance().get_current_task()->get_created_time())); - resp.result()["message"].push_back("Inline reload scheduled"); - - return resp; + Dbg(dbg_ctl_RPC, "invoke plugin callbacks"); + // if there is any error, report it back. + if (auto err = FileManager::instance().rereadConfig(); !err.empty()) { + resp.note(err); } + // If any callback was register(TSMgmtUpdateRegister) for config notifications, then it will be eventually notify. + FileManager::instance().invokeConfigPluginCallbacks(); - /// - // File source: default when no "configs" param - // - if (auto ret = ReloadCoordinator::Get_Instance().prepare_reload(token, "rldtk-", force); !ret.is_ok()) { - resp.result()["errors"].push_back(make_error(swoc::bwprint(buf, "Failed to prepare reload for token '{}': {}", token, ret), - errc(ConfigError::RELOAD_TASK_FAILED))); - return resp; - } - - // Schedule the actual reload work asynchronously on ET_TASK - ::config::schedule_reload_work(); - - resp.result()["created_time"] = - swoc::bwprint(buf, "{}", swoc::bwf::Date(ReloadCoordinator::Get_Instance().get_current_task()->get_created_time())); - resp.result()["message"].push_back("Reload task scheduled"); - resp.result()["token"] = token; + metrics[reconf_time].store(time(nullptr)); + metrics[reconf_req].store(0); return resp; } -swoc::Rv -get_reload_config_status(std::string_view const & /* id ATS_UNUSED */, YAML::Node const ¶ms) -{ - swoc::Rv resp; - - auto make_error = [&](std::string const &msg, int code) -> YAML::Node { - YAML::Node err; - err["message"] = msg; - err["code"] = code; - return err; - }; - - const std::string token = params["token"] ? params["token"].as() : ""; - - if (!token.empty()) { - if (auto [found, info] = ReloadCoordinator::Get_Instance().find_by_token(token); !found) { - std::string text; - Dbg(dbg_ctl_RPC, "No reload task found with token: %s", token.c_str()); - resp.result()["errors"].push_back( - make_error(swoc::bwprint(text, "Token '{}' not found", token), errc(ConfigError::TOKEN_NOT_FOUND))); - resp.result()["token"] = token; - } else { - resp.result()["tasks"].push_back(info); - } - } else { - const int count = params["count"] ? params["count"].as() : 1; - Dbg(dbg_ctl_RPC, "No token provided, count=%d", count); - // no token provided and no count, get last one. - auto infos = ReloadCoordinator::Get_Instance().get_all(count); - if (infos.empty()) { - resp.result()["errors"].push_back(make_error("No reload tasks found", errc(ConfigError::NO_RELOAD_TASKS))); - } else { - for (const auto &info : infos) { - resp.result()["tasks"].push_back(info); - } - } - } - - return resp; -} } // namespace rpc::handlers::config diff --git a/src/mgmt/rpc/handlers/plugins/Plugins.cc b/src/mgmt/rpc/handlers/plugins/Plugins.cc index 58e43f26ebd..f4459ea7d2c 100644 --- a/src/mgmt/rpc/handlers/plugins/Plugins.cc +++ b/src/mgmt/rpc/handlers/plugins/Plugins.cc @@ -22,7 +22,6 @@ #include "mgmt/rpc/handlers/common/ErrorUtils.h" #include "api/LifecycleAPIHooks.h" -#include "proxy/Plugin.h" namespace { @@ -90,35 +89,4 @@ plugin_send_basic_msg(std::string_view const & /* id ATS_UNUSED */, YAML::Node c return resp; } -swoc::Rv -get_plugin_list(std::string_view const & /* id ATS_UNUSED */, YAML::Node const & /* params ATS_UNUSED */) -{ - swoc::Rv resp; - try { - const auto &summary = get_plugin_load_summary(); - YAML::Node data; - - data["source"] = summary.source; - - YAML::Node plugins; - for (const auto &e : summary.entries) { - YAML::Node plugin; - - plugin["path"] = e.path; - plugin["enabled"] = e.enabled; - plugin["status"] = e.enabled ? "loaded" : "disabled"; - plugin["index"] = e.index; - if (e.load_order >= 0) { - plugin["load_order"] = e.load_order; - } - plugins.push_back(plugin); - } - data["plugins"] = plugins; - - resp.result()["data"] = data; - } catch (std::exception const &ex) { - resp.errata().assign(std::error_code{errors::Codes::PLUGIN}).note("Error calling get_plugin_list: {}", ex.what()); - } - return resp; -} } // namespace rpc::handlers::plugins diff --git a/src/proxy/CMakeLists.txt b/src/proxy/CMakeLists.txt index 34874f380f1..93c16697845 100644 --- a/src/proxy/CMakeLists.txt +++ b/src/proxy/CMakeLists.txt @@ -41,8 +41,8 @@ add_library(ts::proxy ALIAS proxy) target_link_libraries( proxy - PUBLIC ts::inkcache ts::inkevent ts::tsutil ts::tscore ts::inknet ts::http - PRIVATE ts::http ts::rpcpublichandlers ts::jsonrpc_protocol ts::inkutils ts::tsapibackend ts::configmanager + PUBLIC ts::inkcache ts::inkevent ts::tsutil ts::tscore ts::inknet + PRIVATE ts::rpcpublichandlers ts::jsonrpc_protocol ts::inkutils ts::tsapibackend ) add_subdirectory(hdrs) diff --git a/src/proxy/CacheControl.cc b/src/proxy/CacheControl.cc index 12fff112537..ce5bb64985f 100644 --- a/src/proxy/CacheControl.cc +++ b/src/proxy/CacheControl.cc @@ -33,8 +33,7 @@ #include "tscore/Filenames.h" #include "proxy/CacheControl.h" #include "proxy/ControlMatcher.h" -#include "mgmt/config/ConfigContextDiags.h" -#include "mgmt/config/ConfigRegistry.h" +#include "iocore/eventsystem/ConfigProcessor.h" #include "proxy/http/HttpConfig.h" namespace { @@ -86,6 +85,30 @@ struct CC_FreerContinuation : public Continuation { CC_FreerContinuation(CC_table *ap) : Continuation(nullptr), p(ap) { SET_HANDLER(&CC_FreerContinuation::freeEvent); } }; +// struct CC_UpdateContinuation +// +// Used to read the cache.conf file after the manager signals +// a change +// +struct CC_UpdateContinuation : public Continuation { + int + file_update_handler(int /* etype ATS_UNUSED */, void * /* data ATS_UNUSED */) + { + reloadCacheControl(); + delete this; + return EVENT_DONE; + } + CC_UpdateContinuation(Ptr &m) : Continuation(m) { SET_HANDLER(&CC_UpdateContinuation::file_update_handler); } +}; + +int +cacheControlFile_CB(const char * /* name ATS_UNUSED */, RecDataT /* data_type ATS_UNUSED */, RecData /* data ATS_UNUSED */, + void * /* cookie ATS_UNUSED */) +{ + eventProcessor.schedule_imm(new CC_UpdateContinuation(reconfig_mutex), ET_CALL); + return 0; +} + // // Begin API functions // @@ -107,14 +130,7 @@ initCacheControl() ink_assert(CacheControlTable == nullptr); reconfig_mutex = new_ProxyMutex(); CacheControlTable = new CC_table("proxy.config.cache.control.filename", modulePrefix, &http_dest_tags); - - config::ConfigRegistry::Get_Instance().register_config( // File registration. - "cache_control", // registry key - ts::filename::CACHE, // default filename - "proxy.config.cache.control.filename", // record holding the filename - [](ConfigContext ctx) { reloadCacheControl(ctx); }, // reload handler - config::ConfigSource::FileOnly, // no RPC content source - {"proxy.config.cache.control.filename"}); // trigger records + RecRegisterConfigUpdateCb("proxy.config.cache.control.filename", cacheControlFile_CB, nullptr); } // void reloadCacheControl() @@ -124,18 +140,18 @@ initCacheControl() // lock acquire is also blocking // void -reloadCacheControl(ConfigContext ctx) +reloadCacheControl() { - CfgLoadLog(ctx, DL_Note, "%s loading ...", ts::filename::CACHE); - Dbg(dbg_ctl_cache_control, "%s updated, reloading", ts::filename::CACHE); + Note("%s loading ...", ts::filename::CACHE); + CC_table *newTable; + + Dbg(dbg_ctl_cache_control, "%s updated, reloading", ts::filename::CACHE); eventProcessor.schedule_in(new CC_FreerContinuation(CacheControlTable), CACHE_CONTROL_TIMEOUT, ET_CALL); - CC_table *newTable = - new CC_table("proxy.config.cache.control.filename", modulePrefix, &http_dest_tags, - ALLOW_HOST_TABLE | ALLOW_IP_TABLE | ALLOW_REGEX_TABLE | ALLOW_HOST_REGEX_TABLE | ALLOW_URL_TABLE, ctx); + newTable = new CC_table("proxy.config.cache.control.filename", modulePrefix, &http_dest_tags); ink_atomic_swap(&CacheControlTable, newTable); - CfgLoadComplete(ctx, "%s finished loading", ts::filename::CACHE); + Note("%s finished loading", ts::filename::CACHE); } void diff --git a/src/proxy/ControlMatcher.cc b/src/proxy/ControlMatcher.cc index 4be7cf1880a..8e6b6b7ee5e 100644 --- a/src/proxy/ControlMatcher.cc +++ b/src/proxy/ControlMatcher.cc @@ -31,7 +31,6 @@ #include "swoc/bwf_ip.h" #include "swoc/swoc_file.h" -#include "mgmt/config/ConfigContextDiags.h" #include "tscore/MatcherUtils.h" #include "tscore/Tokenizer.h" #include "proxy/ControlMatcher.h" @@ -640,8 +639,7 @@ IpMatcher::Print() const } template -ControlMatcher::ControlMatcher(const char *file_var, const char *name, const matcher_tags *tags, int flags_in, - ConfigContext ctx) +ControlMatcher::ControlMatcher(const char *file_var, const char *name, const matcher_tags *tags, int flags_in) { flags = flags_in; ink_assert(flags & (ALLOW_HOST_TABLE | ALLOW_REGEX_TABLE | ALLOW_URL_TABLE | ALLOW_IP_TABLE)); @@ -666,7 +664,7 @@ ControlMatcher::ControlMatcher(const char *file_var, const ch hrMatch = nullptr; if (!(flags & DONT_BUILD_TABLE)) { - m_numEntries = this->BuildTable(ctx); + m_numEntries = this->BuildTable(); } else { m_numEntries = 0; } @@ -733,7 +731,7 @@ ControlMatcher::Match(RequestData *rdata, MatchResult *result // template int -ControlMatcher::BuildTableFromString(char *file_buf, ConfigContext ctx) +ControlMatcher::BuildTableFromString(char *file_buf) { // Table build locals Tokenizer bufTok("\n"); @@ -778,7 +776,7 @@ ControlMatcher::BuildTableFromString(char *file_buf, ConfigCo if (config_tags != &socks_server_tags) { Result error = Result::failure("%s discarding %s entry at line %d : %s", matcher_name, config_file_path, line_num, errptr); - CfgLoadLog(ctx, DL_Error, "%s", error.message()); + Error("%s", error.message()); } ats_free(current); } else { @@ -876,7 +874,7 @@ ControlMatcher::BuildTableFromString(char *file_buf, ConfigCo // Check to see if there was an error in creating the NewEntry if (error.failed()) { - CfgLoadLog(ctx, DL_Error, "%s", error.message()); + Error("%s", error.message()); } // Deallocate the parsing structure @@ -895,23 +893,22 @@ ControlMatcher::BuildTableFromString(char *file_buf, ConfigCo template int -ControlMatcher::BuildTable(ConfigContext ctx) +ControlMatcher::BuildTable() { std::error_code ec; std::string content{swoc::file::load(swoc::file::path{config_file_path}, ec)}; - if (ec) { switch (ec.value()) { case ENOENT: - CfgLoadLog(ctx, DL_Warning, "ControlMatcher - Cannot open config file: %s - %s", config_file_path, strerror(ec.value())); + Warning("ControlMatcher - Cannot open config file: %s - %s", config_file_path, strerror(ec.value())); break; default: - CfgLoadFail(ctx, "ControlMatcher - %s failed to load: %s", config_file_path, strerror(ec.value())); + Error("ControlMatcher - %s failed to load: %s", config_file_path, strerror(ec.value())); return 1; } } - return BuildTableFromString(content.data(), ctx); + return BuildTableFromString(content.data()); } /**************************************************************** diff --git a/src/proxy/IPAllow.cc b/src/proxy/IPAllow.cc index 6659fb2b69e..554a5098c5b 100644 --- a/src/proxy/IPAllow.cc +++ b/src/proxy/IPAllow.cc @@ -27,8 +27,6 @@ #include #include "proxy/IPAllow.h" -#include "mgmt/config/ConfigContextDiags.h" -#include "mgmt/config/ConfigRegistry.h" #include "records/RecCore.h" #include "swoc/Errata.h" #include "swoc/TextView.h" @@ -75,6 +73,8 @@ size_t IpAllow::configid = 0; bool IpAllow::accept_check_p = true; // initializing global flag for fast deny uint8_t IpAllow::subjects[Subject::MAX_SUBJECTS]; +static ConfigUpdateHandler *ipAllowUpdate; + // // Begin API functions // @@ -93,23 +93,9 @@ IpAllow::startup() // Should not have been initialized before ink_assert(IpAllow::configid == 0); - config::ConfigRegistry::Get_Instance().register_config( - "ip_allow", // registry key - ts::filename::IP_ALLOW, // default filename - "proxy.config.cache.ip_allow.filename", // record holding the filename - [](ConfigContext ctx) { IpAllow::reconfigure(ctx); }, // reload handler - config::ConfigSource::FileOnly, // no RPC content source. Change to FileAndRpc if we want to support RPC. - // if supplied, YAML can be sourced by calling ctx.supplied_yaml() - {"proxy.config.cache.ip_allow.filename"}); // trigger records - - // ip_categories is an auxiliary data file loaded by ip_allow (see BuildCategories()). - // Track it with FileManager for mtime detection and register a record callback - // so that changes to the file or the record trigger an ip_allow reload. - config::ConfigRegistry::Get_Instance().add_file_dependency( - "ip_allow", // config key to attach to - "proxy.config.cache.ip_categories.filename", // record holding the filename - ts::filename::IP_CATEGORIES, // default filename (used when record is "") - false); // not required + ipAllowUpdate = new ConfigUpdateHandler(); + ipAllowUpdate->attach("proxy.config.cache.ip_allow.filename"); + ipAllowUpdate->attach("proxy.config.cache.ip_categories.filename"); reconfigure(); @@ -122,30 +108,34 @@ IpAllow::startup() } void -IpAllow::reconfigure(ConfigContext ctx) +IpAllow::reconfigure() { - CfgLoadLog(ctx, DL_Note, "%s loading ...", ts::filename::IP_ALLOW); + self_type *new_table; - auto *new_table = new self_type("proxy.config.cache.ip_allow.filename", "proxy.config.cache.ip_categories.filename"); + Note("%s loading ...", ts::filename::IP_ALLOW); + new_table = new self_type("proxy.config.cache.ip_allow.filename", "proxy.config.cache.ip_categories.filename"); // IP rules need categories, so load them first (if they exist). if (auto errata = new_table->BuildCategories(); !errata.is_ok()) { - CfgLoadFailWithErrata(ctx, errata, "%s failed to load", new_table->ip_categories_config_file.c_str()); + std::string text; + swoc::bwprint(text, "{} failed to load\n{}", new_table->ip_categories_config_file, errata); + Error("%s", text.c_str()); delete new_table; return; } if (auto errata = new_table->BuildTable(); !errata.is_ok()) { - if (errata.severity() > ERRATA_ERROR) { - std::string errata_text; - swoc::bwprint(errata_text, "{}", errata); - Fatal("%s failed to load\n%s", ts::filename::IP_ALLOW, errata_text.c_str()); + std::string text; + swoc::bwprint(text, "{} failed to load\n{}", ts::filename::IP_ALLOW, errata); + if (errata.severity() <= ERRATA_ERROR) { + Error("%s", text.c_str()); + } else { + Fatal("%s", text.c_str()); } - CfgLoadFailWithErrata(ctx, errata, "%s failed to load", ts::filename::IP_ALLOW); delete new_table; return; } configid = configProcessor.set(configid, new_table); - CfgLoadComplete(ctx, "%s finished loading", ts::filename::IP_ALLOW); + Note("%s finished loading", ts::filename::IP_ALLOW); } IpAllow * diff --git a/src/proxy/ParentSelection.cc b/src/proxy/ParentSelection.cc index 25a246bc64b..7d983d71d5a 100644 --- a/src/proxy/ParentSelection.cc +++ b/src/proxy/ParentSelection.cc @@ -24,8 +24,7 @@ #include "proxy/ParentConsistentHash.h" #include "proxy/ParentRoundRobin.h" #include "proxy/ControlMatcher.h" -#include "mgmt/config/ConfigContextDiags.h" -#include "mgmt/config/ConfigRegistry.h" +#include "iocore/eventsystem/ConfigProcessor.h" #include "proxy/HostStatus.h" #include "proxy/hdrs/HTTP.h" #include "proxy/http/HttpTransact.h" @@ -44,8 +43,9 @@ using namespace std::literals; using P_table = ControlMatcher; // Global Vars for Parent Selection -static const char modulePrefix[] = "[ParentSelection]"; -static int self_detect = 2; +static const char modulePrefix[] = "[ParentSelection]"; +static ConfigUpdateHandler *parentConfigUpdate = nullptr; +static int self_detect = 2; // Config var names static const char *file_var = "proxy.config.http.parent_proxy.file"; @@ -289,28 +289,33 @@ int ParentConfig::m_id = 0; void ParentConfig::startup() { - config::ConfigRegistry::Get_Instance().register_config( - "parent_proxy", // registry key - ts::filename::PARENT, // default filename - file_var, // record holding the filename - [](ConfigContext ctx) { ParentConfig::reconfigure(ctx); }, // reload handler - config::ConfigSource::FileOnly, // file-based only - {file_var, default_var, retry_var, threshold_var}); // trigger records + parentConfigUpdate = new ConfigUpdateHandler(); // Load the initial configuration reconfigure(); + + // Setup the callbacks for reconfiuration + // parent table + parentConfigUpdate->attach(file_var); + // default parent + parentConfigUpdate->attach(default_var); + // Retry time + parentConfigUpdate->attach(retry_var); + // Fail Threshold + parentConfigUpdate->attach(threshold_var); } void -ParentConfig::reconfigure(ConfigContext ctx) +ParentConfig::reconfigure() { - CfgLoadLog(ctx, DL_Note, "%s loading ...", ts::filename::PARENT); + Note("%s loading ...", ts::filename::PARENT); + + ParentConfigParams *params = nullptr; // Allocate parent table - P_table *pTable = - new P_table(file_var, modulePrefix, &http_dest_tags, - ALLOW_HOST_TABLE | ALLOW_IP_TABLE | ALLOW_REGEX_TABLE | ALLOW_HOST_REGEX_TABLE | ALLOW_URL_TABLE, ctx); - ParentConfigParams *params = new ParentConfigParams(pTable); + P_table *pTable = new P_table(file_var, modulePrefix, &http_dest_tags); + + params = new ParentConfigParams(pTable); ink_assert(params != nullptr); m_id = configProcessor.set(m_id, params); @@ -319,7 +324,7 @@ ParentConfig::reconfigure(ConfigContext ctx) ParentConfig::print(); } - CfgLoadComplete(ctx, "%s finished loading", ts::filename::PARENT); + Note("%s finished loading", ts::filename::PARENT); } void diff --git a/src/proxy/Plugin.cc b/src/proxy/Plugin.cc index 0cdb620460d..5e2d1e6bb70 100644 --- a/src/proxy/Plugin.cc +++ b/src/proxy/Plugin.cc @@ -22,9 +22,6 @@ */ #include -#include -#include -#include #include "tscore/ink_platform.h" #include "tscore/ink_file.h" #include "tscore/ParseRules.h" @@ -33,7 +30,6 @@ #include "proxy/Plugin.h" #include "tscore/ink_cap.h" #include "tscore/Filenames.h" -#include #define MAX_PLUGIN_ARGS 64 @@ -81,27 +77,8 @@ parsePluginConfig() static const char *plugin_dir = "."; -static void -plugin_dir_init() -{ - static bool once = true; - - if (once) { - plugin_dir = ats_stringdup(RecConfigReadPluginDir()); - once = false; - } -} - using init_func_t = void (*)(int, char **); -static PluginLoadSummary s_plugin_load_summary; - -const PluginLoadSummary & -get_plugin_load_summary() -{ - return s_plugin_load_summary; -} - // Plugin registration vars // // plugin_reg_list has an entry for each plugin @@ -157,7 +134,7 @@ plugin_dso_load(const char *path, void *&handle, void *&init, std::string &error return true; } -bool +static bool single_plugin_init(int argc, char *argv[], bool validateOnly) { char path[PATH_NAME_MAX]; @@ -221,7 +198,7 @@ single_plugin_init(int argc, char *argv[], bool validateOnly) if (plugin_reg_current->plugin_registered) { plugin_reg_list.push(plugin_reg_current); } else { - Fatal("plugin '%s' not registered by calling TSPluginRegister", path); + Fatal("plugin not registered by calling TSPluginRegister"); return false; // this line won't get called since Fatal brings down ATS } @@ -231,7 +208,7 @@ single_plugin_init(int argc, char *argv[], bool validateOnly) } static char * -plugin_expand(char *arg, const char *source) +plugin_expand(char *arg) { RecDataT data_type; char *str = nullptr; @@ -291,7 +268,7 @@ plugin_expand(char *arg, const char *source) } not_found: - Warning("%s: unable to find parameter %s", source, arg); + Warning("%s: unable to find parameter %s", ts::filename::PLUGIN, arg); return nullptr; } @@ -305,13 +282,13 @@ plugin_init(bool validateOnly) int argc; int fd; int i; - bool retVal = true; - int load_index = 0; - - plugin_dir_init(); + bool retVal = true; + static bool INIT_ONCE = true; - s_plugin_load_summary.source = ts::filename::PLUGIN; - s_plugin_load_summary.entries.clear(); + if (INIT_ONCE) { + plugin_dir = ats_stringdup(RecConfigReadPluginDir()); + INIT_ONCE = false; + } Note("%s loading ...", ts::filename::PLUGIN); path = RecConfigReadConfigPath(nullptr, ts::filename::PLUGIN); @@ -373,7 +350,7 @@ plugin_init(bool validateOnly) } for (i = 0; i < argc; i++) { - vars[i] = plugin_expand(argv[i], ts::filename::PLUGIN); + vars[i] = plugin_expand(argv[i]); if (vars[i]) { argv[i] = vars[i]; } @@ -384,14 +361,8 @@ plugin_init(bool validateOnly) } else { argv[MAX_PLUGIN_ARGS - 1] = nullptr; } - - ++load_index; - std::string plugin_name = (argc > 0) ? argv[0] : "unknown"; - retVal = single_plugin_init(argc, argv, validateOnly); - s_plugin_load_summary.entries.push_back({plugin_name, -1, true, retVal, load_index}); - for (i = 0; i < argc; i++) { ats_free(vars[i]); } @@ -405,267 +376,3 @@ plugin_init(bool validateOnly) } return retVal; } - -config::ConfigResult -parse_plugin_yaml(const char *yaml_path) -{ - config::ConfigResult result; - YAML::Node root; - - try { - root = YAML::LoadFile(yaml_path); - } catch (const YAML::Exception &e) { - result.errata.note("failed to parse: {}", e.what()); - return result; - } - - if (!root["plugins"] || !root["plugins"].IsSequence()) { - result.errata.note("missing or invalid 'plugins' sequence"); - return result; - } - - struct IndexedEntry { - int seq_idx; - PluginYAMLEntry entry; - }; - - std::vector indexed; - int seq_idx = 0; - - for (const auto &node : root["plugins"]) { - PluginYAMLEntry entry; - - if (!node["path"]) { - result.errata.note("plugin entry #{} missing required 'path' field", seq_idx + 1); - return result; - } - entry.path = node["path"].as(); - - if (auto n = node["enabled"]; n) { - entry.enabled = n.as(); - } - if (auto n = node["load_order"]; n) { - entry.load_order = n.as(); - } - if (auto n = node["params"]; n && n.IsSequence()) { - for (const auto &p : n) { - entry.params.emplace_back(p.as()); - } - } - if (auto n = node["config"]; n) { - if (n.IsScalar()) { - entry.config_literal = n.as(); - } else { - result.errata.note("plugin '{}': 'config' must be a scalar (use literal block '|' for multi-line content)", entry.path); - return result; - } - } - - indexed.push_back({seq_idx++, std::move(entry)}); - } - - std::stable_sort(indexed.begin(), indexed.end(), [](const IndexedEntry &a, const IndexedEntry &b) { - const bool a_has = a.entry.load_order >= 0; - const bool b_has = b.entry.load_order >= 0; - - if (a_has && b_has) { - return a.entry.load_order < b.entry.load_order; - } - return a_has && !b_has; - }); - - result.value.reserve(indexed.size()); - for (auto &[_, entry] : indexed) { - result.value.emplace_back(std::move(entry)); - } - - return result; -} - -/// Write inline config content to a temp file, returning the path on success. -static std::optional -write_inline_config(const PluginYAMLEntry &entry, int index) -{ - char tmp_path[PATH_NAME_MAX]; - - std::string_view stem{entry.path}; - if (auto pos = stem.rfind('/'); pos != std::string_view::npos) { - stem = stem.substr(pos + 1); - } - if (auto pos = stem.rfind('.'); pos != std::string_view::npos) { - stem = stem.substr(0, pos); - } - - snprintf(tmp_path, sizeof(tmp_path), "%s/.%.*s_inline_%d.conf", RecConfigReadConfigDir().c_str(), static_cast(stem.size()), - stem.data(), index); - - int fd = open(tmp_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (fd < 0) { - Error("%s: failed to create temp config for %s: %s", ts::filename::PLUGIN_YAML, entry.path.c_str(), strerror(errno)); - return std::nullopt; - } - - auto n = write(fd, entry.config_literal.data(), entry.config_literal.size()); - close(fd); - - if (n < 0 || static_cast(n) != entry.config_literal.size()) { - Error("%s: failed to write inline config for %s", ts::filename::PLUGIN_YAML, entry.path.c_str()); - return std::nullopt; - } - - return std::string(tmp_path); -} - -/// Build the argv for a single plugin: [path, inline_config_path?, params..., $record expansions]. -static std::optional> -build_plugin_args(const PluginYAMLEntry &entry, int index) -{ - std::vector args; - args.emplace_back(entry.path); - - if (!entry.config_literal.empty()) { - if (auto path = write_inline_config(entry, index); path) { - args.emplace_back(std::move(*path)); - } else { - return std::nullopt; - } - } - - for (const auto &p : entry.params) { - args.emplace_back(p); - } - - return args; -} - -static void -log_plugin_load_summary(int loaded, int disabled) -{ - Note("%s: %d plugins loaded, %d disabled", ts::filename::PLUGIN_YAML, loaded, disabled); - - for (const auto &e : s_plugin_load_summary.entries) { - if (e.enabled) { - if (e.load_order >= 0) { - Note(" #%d %-30s load_order: %-5d loaded", e.index, e.path.c_str(), e.load_order); - } else { - Note(" #%d %-30s loaded", e.index, e.path.c_str()); - } - } else { - Note(" -- %-30s disabled", e.path.c_str()); - } - } -} - -static void -cleanup_inline_configs() -{ - std::string config_dir = RecConfigReadConfigDir(); - std::error_code ec; - - try { - for (const auto &entry : std::filesystem::directory_iterator(config_dir, ec)) { - if (!entry.is_regular_file()) { - continue; - } - auto name = entry.path().filename().string(); - if (name.front() == '.' && name.find("_inline_") != std::string::npos && name.ends_with(".conf")) { - std::filesystem::remove(entry.path(), ec); - } - } - } catch (const std::exception &e) { - Error("%s: error cleaning up inline config files: %s", ts::filename::PLUGIN_YAML, e.what()); - } -} - -bool -plugin_yaml_init(bool validateOnly) -{ - plugin_dir_init(); - - ats_scoped_str yaml_path; - - if (!validateOnly) { - cleanup_inline_configs(); - } - - yaml_path = RecConfigReadConfigPath(nullptr, ts::filename::PLUGIN_YAML); - if (access(yaml_path, R_OK) != 0) { - if (errno != ENOENT) { - Error("%s: %s", ts::filename::PLUGIN_YAML, strerror(errno)); - return false; - } - return plugin_init(validateOnly); - } - - Note("%s loading ...", ts::filename::PLUGIN_YAML); - - auto result = parse_plugin_yaml(yaml_path.get()); - if (!result.ok()) { - Error("%s: %s", ts::filename::PLUGIN_YAML, std::string(result.errata.front().text()).c_str()); - return false; - } - - s_plugin_load_summary.source = ts::filename::PLUGIN_YAML; - s_plugin_load_summary.entries.clear(); - - bool retVal = true; - int index = 0; - int loaded = 0; - int disabled = 0; - - for (const auto &entry : result.value) { - ++index; - - if (!entry.enabled) { - Note("plugin #%d skipped: %s (enabled: false)", index, entry.path.c_str()); - s_plugin_load_summary.entries.push_back({entry.path, entry.load_order, false, false, index}); - ++disabled; - continue; - } - - auto args = build_plugin_args(entry, index); - if (!args) { - return false; - } - - if (args->size() > MAX_PLUGIN_ARGS) { - Warning("%s: plugin '%s' has %zu args, exceeds typical max (%d)", ts::filename::PLUGIN_YAML, entry.path.c_str(), args->size(), - MAX_PLUGIN_ARGS); - } - - std::vector argv_ptrs; - std::vector expanded; - - for (auto &a : *args) { - char *var = plugin_expand(a.data(), ts::filename::PLUGIN_YAML); - expanded.emplace_back(var); - argv_ptrs.emplace_back(var ? var : a.data()); - } - argv_ptrs.emplace_back(nullptr); - - if (entry.load_order >= 0) { - Note("plugin #%d loading: %s (load_order: %d)", index, entry.path.c_str(), entry.load_order); - } else { - Note("plugin #%d loading: %s", index, entry.path.c_str()); - } - - retVal = single_plugin_init(static_cast(args->size()), argv_ptrs.data(), validateOnly); - s_plugin_load_summary.entries.push_back({entry.path, entry.load_order, true, retVal, index}); - ++loaded; - - for (auto *v : expanded) { - ats_free(v); - } - - if (!retVal) { - break; - } - } - - if (retVal) { - log_plugin_load_summary(loaded, disabled); - } else { - Error("%s failed to load", ts::filename::PLUGIN_YAML); - } - return retVal; -} diff --git a/src/proxy/ReverseProxy.cc b/src/proxy/ReverseProxy.cc index 893f421d7c0..8deb9d00162 100644 --- a/src/proxy/ReverseProxy.cc +++ b/src/proxy/ReverseProxy.cc @@ -30,10 +30,8 @@ #include "tscore/ink_platform.h" #include "tscore/Filenames.h" #include -#include "iocore/cache/Cache.h" +#include "iocore/eventsystem/ConfigProcessor.h" #include "proxy/ReverseProxy.h" -#include "mgmt/config/ConfigContextDiags.h" -#include "mgmt/config/ConfigRegistry.h" #include "tscore/MatcherUtils.h" #include "tscore/Tokenizer.h" #include "ts/remap.h" @@ -63,8 +61,6 @@ thread_local PluginThreadContext *pluginThreadContext = nullptr; #define URL_REMAP_MODE_CHANGED 8 #define HTTP_DEFAULT_REDIRECT_CHANGED 9 -static void init_table_volume_host_records(UrlRewrite &table); - // // Begin API Functions // @@ -72,53 +68,21 @@ int init_reverse_proxy() { ink_assert(rewrite_table.load() == nullptr); - reconfig_mutex = new_ProxyMutex(); - auto *initial_table = new UrlRewrite(); - auto &config_reg = config::ConfigRegistry::Get_Instance(); - - // Register with ConfigRegistry BEFORE load() so that remap.config and remap.yaml are in - // FileManager's bindings when .include directives call configFileChild() - // to register child files (e.g. test.inc). - config_reg.register_config( - "remap", // registry key - ts::filename::REMAP, // default filename - "proxy.config.url_remap.filename", // record holding the filename - [](ConfigContext ctx) { reloadUrlRewrite(ctx); }, // reload handler - config::ConfigSource::FileOnly); // file-based only - - config_reg.register_config( - "remap_yaml", // registry key - ts::filename::REMAP_YAML, // default filename - "proxy.config.url_remap_yaml.filename", // record holding the filename - [](ConfigContext ctx) { reloadUrlRewrite(ctx); }, // reload handler - config::ConfigSource::FileOnly); // file-based only - - initial_table->acquire(); - Note("%s loading (checking first) ...", ts::filename::REMAP_YAML); - Note("%s loading ...", ts::filename::REMAP); - bool status = initial_table->load(); - bool is_yaml = initial_table->is_remap_yaml(); + reconfig_mutex = new_ProxyMutex(); + rewrite_table.store(new UrlRewrite()); - if (!status) { - Emergency("%s failed to load", is_yaml ? ts::filename::REMAP_YAML : ts::filename::REMAP); + rewrite_table.load()->acquire(); + Note("%s loading ...", ts::filename::REMAP); + if (!rewrite_table.load()->load()) { + Emergency("%s failed to load", ts::filename::REMAP); } else { - Note("%s finished loading", is_yaml ? ts::filename::REMAP_YAML : ts::filename::REMAP); + Note("%s finished loading", ts::filename::REMAP); } - if (initial_table->is_valid() && CacheProcessor::IsCacheEnabled() == CacheInitState::INITIALIZED) { - // Initialize deferred @volume= mappings before publishing so startup-only - // remap walks cannot race a reload. - init_table_volume_host_records(*initial_table); - } - - rewrite_table.store(initial_table, std::memory_order_release); - ink_assert(0 == config_reg.attach("remap", "proxy.config.url_remap.filename")); - ink_assert(0 == config_reg.attach("remap", "proxy.config.proxy_name")); - ink_assert(0 == config_reg.attach("remap", "proxy.config.http.referer_default_redirect")); - ink_assert(0 == config_reg.attach("remap_yaml", "proxy.config.url_remap_yaml.filename")); - ink_assert(0 == config_reg.attach("remap_yaml", "proxy.config.proxy_name")); - ink_assert(0 == config_reg.attach("remap_yaml", "proxy.config.http.referer_default_redirect")); + RecRegisterConfigUpdateCb("proxy.config.url_remap.filename", url_rewrite_CB, (void *)FILE_CHANGED); + RecRegisterConfigUpdateCb("proxy.config.proxy_name", url_rewrite_CB, (void *)TSNAME_CHANGED); RecRegisterConfigUpdateCb("proxy.config.reverse_proxy.enabled", url_rewrite_CB, (void *)REVERSE_CHANGED); + RecRegisterConfigUpdateCb("proxy.config.http.referer_default_redirect", url_rewrite_CB, (void *)HTTP_DEFAULT_REDIRECT_CHANGED); return 0; } @@ -144,6 +108,18 @@ response_url_remap(HTTPHdr *response_header, UrlRewrite *table) // End API Functions // +/** Used to read the remap.config file after the manager signals a change. */ +struct UR_UpdateContinuation : public Continuation { + int + file_update_handler(int /* etype ATS_UNUSED */, void * /* data ATS_UNUSED */) + { + static_cast(reloadUrlRewrite()); + delete this; + return EVENT_DONE; + } + UR_UpdateContinuation(Ptr &m) : Continuation(m) { SET_HANDLER(&UR_UpdateContinuation::file_update_handler); } +}; + bool urlRewriteVerify() { @@ -151,30 +127,21 @@ urlRewriteVerify() } /** - Called when the remap.config or remap.yaml file changes. Since it called infrequently, + Called when the remap.config file changes. Since it called infrequently, we do the load of new file as blocking I/O and lock acquire is also blocking. */ bool -reloadUrlRewrite(ConfigContext ctx) +reloadUrlRewrite() { - std::string msg_buffer; - - msg_buffer.reserve(1024); UrlRewrite *newTable, *oldTable; - CfgLoadLog(ctx, DL_Note, "%s loading (checking first) ...", ts::filename::REMAP_YAML); - CfgLoadLog(ctx, DL_Note, "%s loading ...", ts::filename::REMAP); - Dbg(dbg_ctl_url_rewrite, "%s updated, reloading...", ts::filename::REMAP_YAML); + Note("%s loading ...", ts::filename::REMAP); Dbg(dbg_ctl_url_rewrite, "%s updated, reloading...", ts::filename::REMAP); newTable = new UrlRewrite(); - - bool status = newTable->load(ctx); - bool is_yaml = (newTable->is_remap_yaml()); - - if (status) { - swoc::bwprint(msg_buffer, "{} finished loading", is_yaml ? ts::filename::REMAP_YAML : ts::filename::REMAP); + if (newTable->load()) { + static const char *msg_format = "%s finished loading"; // Hold at least one lease, until we reload the configuration newTable->acquire(); @@ -187,15 +154,15 @@ reloadUrlRewrite(ConfigContext ctx) // Release the old one oldTable->release(); - Dbg(dbg_ctl_url_rewrite, "%s", msg_buffer.c_str()); - CfgLoadComplete(ctx, "%s finished loading", is_yaml ? ts::filename::REMAP_YAML : ts::filename::REMAP); + Dbg(dbg_ctl_url_rewrite, msg_format, ts::filename::REMAP); + Note(msg_format, ts::filename::REMAP); return true; } else { - swoc::bwprint(msg_buffer, "{} failed to load", is_yaml ? ts::filename::REMAP_YAML : ts::filename::REMAP); + static const char *msg_format = "%s failed to load"; delete newTable; - Dbg(dbg_ctl_url_rewrite, "%s", msg_buffer.c_str()); - CfgLoadFail(ctx, "%s failed to load", is_yaml ? ts::filename::REMAP_YAML : ts::filename::REMAP); + Dbg(dbg_ctl_url_rewrite, msg_format, ts::filename::REMAP); + Error(msg_format, ts::filename::REMAP); return false; } } @@ -234,27 +201,11 @@ init_store_volume_host_records(UrlRewrite::MappingsStore &store) } } -static void -init_table_volume_host_records(UrlRewrite &table) -{ - Dbg(dbg_ctl_url_rewrite, "Initializing volume_host_rec for all remap rules after cache init"); - - init_store_volume_host_records(table.forward_mappings); - init_store_volume_host_records(table.reverse_mappings); - init_store_volume_host_records(table.permanent_redirects); - init_store_volume_host_records(table.temporary_redirects); - init_store_volume_host_records(table.forward_mappings_with_recv_port); -} - // This is called after the cache is initialized, since we may need the volume_host_records. // Must only be called during startup before any remap reload can occur. void init_remap_volume_host_records() { - if (CacheProcessor::IsCacheEnabled() != CacheInitState::INITIALIZED) { - return; - } - UrlRewrite *table = rewrite_table.load(std::memory_order_acquire); if (!table) { @@ -263,17 +214,42 @@ init_remap_volume_host_records() table->acquire(); - if (table->is_valid()) { - init_table_volume_host_records(*table); - } + Dbg(dbg_ctl_url_rewrite, "Initializing volume_host_rec for all remap rules after cache init"); + + // Initialize for all mapping stores + init_store_volume_host_records(table->forward_mappings); + init_store_volume_host_records(table->reverse_mappings); + init_store_volume_host_records(table->permanent_redirects); + init_store_volume_host_records(table->temporary_redirects); + init_store_volume_host_records(table->forward_mappings_with_recv_port); table->release(); } int -url_rewrite_CB(const char * /* name ATS_UNUSED */, RecDataT /* data_type ATS_UNUSED */, RecData data, - void * /* cookie ATS_UNUSED */) +url_rewrite_CB(const char * /* name ATS_UNUSED */, RecDataT /* data_type ATS_UNUSED */, RecData data, void *cookie) { - rewrite_table.load()->SetReverseFlag(data.rec_int); + int my_token = static_cast((long)cookie); + + switch (my_token) { + case REVERSE_CHANGED: + rewrite_table.load()->SetReverseFlag(data.rec_int); + break; + + case TSNAME_CHANGED: + case FILE_CHANGED: + case HTTP_DEFAULT_REDIRECT_CHANGED: + eventProcessor.schedule_imm(new UR_UpdateContinuation(reconfig_mutex), ET_TASK); + break; + + case URL_REMAP_MODE_CHANGED: + // You need to restart TS. + break; + + default: + ink_assert(0); + break; + } + return 0; } diff --git a/src/proxy/hdrs/CMakeLists.txt b/src/proxy/hdrs/CMakeLists.txt index 45a084dd6ee..09dfc6f8b6c 100644 --- a/src/proxy/hdrs/CMakeLists.txt +++ b/src/proxy/hdrs/CMakeLists.txt @@ -55,14 +55,7 @@ if(BUILD_TESTING) unit_tests/unit_test_main.cc ) target_link_libraries( - test_proxy_hdrs - PRIVATE ts::hdrs - ts::tscore - ts::inkevent - libswoc::libswoc - Catch2::Catch2WithMain - lshpack - configmanager + test_proxy_hdrs PRIVATE ts::hdrs ts::tscore ts::inkevent libswoc::libswoc Catch2::Catch2WithMain lshpack ) add_catch2_test(NAME test_proxy_hdrs COMMAND test_proxy_hdrs) diff --git a/src/proxy/hdrs/MIME.cc b/src/proxy/hdrs/MIME.cc index f8e25de040b..df32250d545 100644 --- a/src/proxy/hdrs/MIME.cc +++ b/src/proxy/hdrs/MIME.cc @@ -3770,9 +3770,6 @@ MIMEHdrImpl::recompute_cooked_stuff(MIMEField *changing_field_or_null, const std for (s = csv_iter.get_first(field, &len); s != nullptr; s = csv_iter.get_next(&len)) { e = s + len; - // Store set mask bits from this CSV value so we can clear them if needed. - uint32_t csv_value_mask = 0; - for (c = s; (c < e) && (ParseRules::is_token(*c)); c++) { ; } @@ -3787,7 +3784,6 @@ MIMEHdrImpl::recompute_cooked_stuff(MIMEField *changing_field_or_null, const std HdrTokenHeapPrefix *p = hdrtoken_wks_to_prefix(token_wks); mask = p->wks_type_specific.u.cache_control.cc_mask; m_cooked_stuff.m_cache_control.m_mask |= mask; - csv_value_mask |= mask; #if TRACK_COOKING Dbg(dbg_ctl_http, " set mask 0x%0X", mask); @@ -3796,72 +3792,27 @@ MIMEHdrImpl::recompute_cooked_stuff(MIMEField *changing_field_or_null, const std if (mask & (MIME_COOKED_MASK_CC_MAX_AGE | MIME_COOKED_MASK_CC_S_MAXAGE | MIME_COOKED_MASK_CC_MAX_STALE | MIME_COOKED_MASK_CC_MIN_FRESH)) { int value; - // Per RFC 7230 Section 3.2.3, there should be no whitespace around '='. - const char *value_start = c; - - // Check if the next character is '=' (no space allowed before '='). - if (c < e && *c == '=') { - ++c; // Move past the '=' - - // Again: no whitespace after the '=' either. Keep in mind that values can be negative. - bool valid_syntax = (c < e) && (is_digit(*c) || *c == '-'); - if (valid_syntax) { - // Reset to value_start to let mime_parse_integer do its work. - c = value_start; - if (mime_parse_integer(c, e, &value)) { + if (mime_parse_integer(c, e, &value)) { #if TRACK_COOKING - Dbg(dbg_ctl_http, " set integer value %d", value); + Dbg(dbg_ctl_http, " set integer value %d", value); #endif - if (token_wks == MIME_VALUE_MAX_AGE.c_str()) { - m_cooked_stuff.m_cache_control.m_secs_max_age = value; - } else if (token_wks == MIME_VALUE_MIN_FRESH.c_str()) { - m_cooked_stuff.m_cache_control.m_secs_min_fresh = value; - } else if (token_wks == MIME_VALUE_MAX_STALE.c_str()) { - m_cooked_stuff.m_cache_control.m_secs_max_stale = value; - } else if (token_wks == MIME_VALUE_S_MAXAGE.c_str()) { - m_cooked_stuff.m_cache_control.m_secs_s_maxage = value; - } - } else { + if (token_wks == MIME_VALUE_MAX_AGE.c_str()) { + m_cooked_stuff.m_cache_control.m_secs_max_age = value; + } else if (token_wks == MIME_VALUE_MIN_FRESH.c_str()) { + m_cooked_stuff.m_cache_control.m_secs_min_fresh = value; + } else if (token_wks == MIME_VALUE_MAX_STALE.c_str()) { + m_cooked_stuff.m_cache_control.m_secs_max_stale = value; + } else if (token_wks == MIME_VALUE_S_MAXAGE.c_str()) { + m_cooked_stuff.m_cache_control.m_secs_s_maxage = value; + } + } else { #if TRACK_COOKING - Dbg(dbg_ctl_http, " set integer value %d", INT_MAX); + Dbg(dbg_ctl_http, " set integer value %d", INT_MAX); #endif - if (token_wks == MIME_VALUE_MAX_STALE.c_str()) { - m_cooked_stuff.m_cache_control.m_secs_max_stale = INT_MAX; - } - } - } else { - // Syntax is malformed (e.g., whitespace after '=', quotes around value, or no value). - // Treat this as unrecognized and clear the mask. - csv_value_mask = 0; - m_cooked_stuff.m_cache_control.m_mask &= ~mask; + if (token_wks == MIME_VALUE_MAX_STALE.c_str()) { + m_cooked_stuff.m_cache_control.m_secs_max_stale = INT_MAX; } - } else { - // No '=' found, or whitespace before '='. This is malformed. - // For directives that require values, this is an error. - // Clear the mask for this directive. - csv_value_mask = 0; - m_cooked_stuff.m_cache_control.m_mask &= ~mask; - } - } - - // Detect whether there is any more non-whitespace content after the - // directive. This indicates an unrecognized or malformed directive. - // This can happen, for instance, if the host uses semicolons - // instead of commas as separators which is against RFC 7234 (see - // issue #12029). Regardless of the cause, this means we need to - // ignore the directive and clear any mask bits we set from it. - while (c < e && ParseRules::is_ws(*c)) { - ++c; - } - if (c < e) { - // There's non-whitespace content that wasn't parsed. This means - // that we cannot really understand what this directive is. - // Per RFC 7234 Section 5.2: "A cache MUST ignore unrecognized cache - // directives." - if (csv_value_mask != 0) { - // Reverse the mask that we set above. - m_cooked_stuff.m_cache_control.m_mask &= ~csv_value_mask; } } } diff --git a/src/proxy/hdrs/unit_tests/test_HdrUtils.cc b/src/proxy/hdrs/unit_tests/test_HdrUtils.cc index befd37b5502..119a4bb4958 100644 --- a/src/proxy/hdrs/unit_tests/test_HdrUtils.cc +++ b/src/proxy/hdrs/unit_tests/test_HdrUtils.cc @@ -24,110 +24,33 @@ #include #include #include -#include #include -#include -#include #include "proxy/hdrs/HdrHeap.h" #include "proxy/hdrs/MIME.h" #include "proxy/hdrs/HdrUtils.h" -// Parameterized test for HdrCsvIter parsing. -TEST_CASE("HdrCsvIter", "[proxy][hdrutils]") +TEST_CASE("HdrUtils", "[proxy][hdrutils]") { - constexpr bool COMBINE_DUPLICATES = true; - - // Structure for parameterized HdrCsvIter tests. - struct CsvIterTestCase { - const char *description; - const char *header_text; - const char *field_name; - std::vector expected_values; - bool combine_dups; // Parameter for get_first() - }; - - // Test cases for HdrCsvIter parsing. - // clang-format off - static const std::vector csv_iter_test_cases = { - // Basic CSV parsing tests - {"single value", - "One: alpha\r\n\r\n", - "One", - {"alpha"}, - COMBINE_DUPLICATES}, - - {"two values", - "Two: alpha, bravo\r\n\r\n", - "Two", - {"alpha", "bravo"}, - COMBINE_DUPLICATES}, - - {"quoted values and escaping", - "Three: zwoop, \"A,B\" , , phil , \"unterminated\r\n\r\n", - "Three", - {"zwoop", "A,B", "phil", "unterminated"}, - COMBINE_DUPLICATES}, - - {"escaped quotes passed through", - "Four: itchi, \"ni, \\\"san\" , \"\" , \"\r\n\r\n", - "Four", - {"itchi", "ni, \\\"san"}, - COMBINE_DUPLICATES}, - - {"duplicate fields combined", - "Five: alpha, bravo, charlie\r\nFive: delta, echo\r\n\r\n", - "Five", - {"alpha", "bravo", "charlie", "delta", "echo"}, - COMBINE_DUPLICATES}, - - {"duplicate fields not combined", - "Five: alpha, bravo, charlie\r\nFive: delta, echo\r\n\r\n", - "Five", - {"alpha", "bravo", "charlie"}, - !COMBINE_DUPLICATES}, - - // Cache-Control specific tests - {"Cache-Control: basic max-age and public", - "Cache-Control: max-age=30, public\r\n\r\n", - "Cache-Control", - {"max-age=30", "public"}, - COMBINE_DUPLICATES}, - - {"Cache-Control: extension directives with values", - "Cache-Control: stale-if-error=1, stale-while-revalidate=60, no-cache\r\n\r\n", - "Cache-Control", - {"stale-if-error=1", "stale-while-revalidate=60", "no-cache"}, - COMBINE_DUPLICATES}, - - {"Cache-Control: mixed directives", - "Cache-Control: public, max-age=300, s-maxage=600\r\n\r\n", - "Cache-Control", - {"public", "max-age=300", "s-maxage=600"}, - COMBINE_DUPLICATES}, - - {"Cache-Control: semicolon separator treated as single value", - "Cache-Control: public; max-age=30\r\n\r\n", - "Cache-Control", - {"public; max-age=30"}, - COMBINE_DUPLICATES}, - - {"Cache-Control: empty value", - "Cache-Control: \r\n\r\n", - "Cache-Control", - {}, - COMBINE_DUPLICATES}, - }; - // clang-format on - auto test_case = GENERATE(from_range(csv_iter_test_cases)); - - CAPTURE(test_case.description, test_case.header_text); + static constexpr swoc::TextView text{"One: alpha\r\n" + "Two: alpha, bravo\r\n" + "Three: zwoop, \"A,B\" , , phil , \"unterminated\r\n" + "Five: alpha, bravo, charlie\r\n" + "Four: itchi, \"ni, \\\"san\" , \"\" , \"\r\n" + "Five: delta, echo\r\n" + "\r\n"}; + + static constexpr std::string_view ONE_TAG{"One"}; + static constexpr std::string_view TWO_TAG{"Two"}; + static constexpr std::string_view THREE_TAG{"Three"}; + static constexpr std::string_view FOUR_TAG{"Four"}; + static constexpr std::string_view FIVE_TAG{"Five"}; HdrHeap *heap = new_HdrHeap(HdrHeap::DEFAULT_SIZE + 64); MIMEParser parser; - char const *real_s = test_case.header_text; - char const *real_e = test_case.header_text + strlen(test_case.header_text); + char const *real_s = text.data(); + char const *real_e = text.data_end(); MIMEHdr mime; mime.create(heap); @@ -137,26 +60,65 @@ TEST_CASE("HdrCsvIter", "[proxy][hdrutils]") REQUIRE(ParseResult::DONE == result); HdrCsvIter iter; - MIMEField *field = mime.field_find(test_case.field_name); - REQUIRE(field != nullptr); - - if (test_case.expected_values.empty()) { - auto value = iter.get_first(field, test_case.combine_dups); - REQUIRE(value.empty()); - } else { - auto value = iter.get_first(field, test_case.combine_dups); - REQUIRE(value == test_case.expected_values[0]); - for (size_t i = 1; i < test_case.expected_values.size(); ++i) { - value = iter.get_next(); - REQUIRE(value == test_case.expected_values[i]); - } - - // After all expected values, the next should be empty. - value = iter.get_next(); - REQUIRE(value.empty()); - } + MIMEField *field{mime.field_find(ONE_TAG)}; + REQUIRE(field != nullptr); + auto value = iter.get_first(field); + REQUIRE(value == "alpha"); + + field = mime.field_find(TWO_TAG); + value = iter.get_first(field); + REQUIRE(value == "alpha"); + value = iter.get_next(); + REQUIRE(value == "bravo"); + value = iter.get_next(); + REQUIRE(value.empty()); + + field = mime.field_find(THREE_TAG); + value = iter.get_first(field); + REQUIRE(value == "zwoop"); + value = iter.get_next(); + REQUIRE(value == "A,B"); // quotes escape separator, and are stripped. + value = iter.get_next(); + REQUIRE(value == "phil"); + value = iter.get_next(); + REQUIRE(value == "unterminated"); + value = iter.get_next(); + REQUIRE(value.empty()); + + field = mime.field_find(FOUR_TAG); + value = iter.get_first(field); + REQUIRE(value == "itchi"); + value = iter.get_next(); + REQUIRE(value == "ni, \\\"san"); // verify escaped quotes are passed through. + value = iter.get_next(); + REQUIRE(value.empty()); + + // Check that duplicates are handled correctly. + field = mime.field_find(FIVE_TAG); + value = iter.get_first(field); + REQUIRE(value == "alpha"); + value = iter.get_next(); + REQUIRE(value == "bravo"); + value = iter.get_next(); + REQUIRE(value == "charlie"); + value = iter.get_next(); + REQUIRE(value == "delta"); + value = iter.get_next(); + REQUIRE(value == "echo"); + value = iter.get_next(); + REQUIRE(value.empty()); + + field = mime.field_find(FIVE_TAG); + value = iter.get_first(field, false); + REQUIRE(value == "alpha"); + value = iter.get_next(); + REQUIRE(value == "bravo"); + value = iter.get_next(); + REQUIRE(value == "charlie"); + value = iter.get_next(); + REQUIRE(value.empty()); heap->destroy(); } @@ -245,320 +207,3 @@ TEST_CASE("HdrUtils 3", "[proxy][hdrutils]") REQUIRE(0 == memcmp(swoc::TextView(buff, idx), text)); heap->destroy(); }; - -// Test that malformed Cache-Control directives are properly ignored during cooking. -// All malformed directives should result in mask == 0. -TEST_CASE("Cache-Control Malformed Cooking", "[proxy][hdrutils]") -{ - struct MalformedCCTestCase { - const char *description; - const char *header_text; - }; - - // clang-format off - // These tests align with cache-tests.fyi/#cc-parse - static const std::vector malformed_cc_test_cases = { - // Separator issues - {"semicolon separator (should be comma)", - "Cache-Control: public; max-age=30\r\n\r\n"}, - - // Space around equals (cc-parse: max-age with space before/after =) - {"space before equals sign", - "Cache-Control: max-age =300\r\n\r\n"}, - - {"space after equals sign", - "Cache-Control: max-age= 300\r\n\r\n"}, - - {"space both before and after equals sign", - "Cache-Control: max-age = 300\r\n\r\n"}, - - // Quoted values (cc-parse: single-quoted max-age) - {"single quotes around value", - "Cache-Control: max-age='300'\r\n\r\n"}, - - {"double quotes around value", - "Cache-Control: max-age=\"300\"\r\n\r\n"}, - - // s-maxage variants - {"s-maxage with space before equals", - "Cache-Control: s-maxage =600\r\n\r\n"}, - - {"s-maxage with space after equals", - "Cache-Control: s-maxage= 600\r\n\r\n"}, - - // Invalid numeric values (cc-parse: decimal max-age) - {"decimal value in max-age (1.5)", - "Cache-Control: max-age=1.5\r\n\r\n"}, - - {"decimal value in max-age (3600.0)", - "Cache-Control: max-age=3600.0\r\n\r\n"}, - - {"decimal value starting with dot (.5)", - "Cache-Control: max-age=.5\r\n\r\n"}, - - {"decimal value in s-maxage", - "Cache-Control: s-maxage=1.5\r\n\r\n"}, - - // Leading and trailing alpha characters - {"leading alpha in max-age value", - "Cache-Control: max-age=a300\r\n\r\n"}, - - {"trailing alpha in max-age value", - "Cache-Control: max-age=300a\r\n\r\n"}, - - {"leading alpha in s-maxage value", - "Cache-Control: s-maxage=a600\r\n\r\n"}, - - {"trailing alpha in s-maxage value", - "Cache-Control: s-maxage=600a\r\n\r\n"}, - - // Empty and missing values - {"empty max-age value alone", - "Cache-Control: max-age=\r\n\r\n"}, - }; - // clang-format on - - auto test_case = GENERATE(from_range(malformed_cc_test_cases)); - - CAPTURE(test_case.description, test_case.header_text); - - HdrHeap *heap = new_HdrHeap(HdrHeap::DEFAULT_SIZE + 64); - MIMEParser parser; - char const *real_s = test_case.header_text; - char const *real_e = test_case.header_text + strlen(test_case.header_text); - MIMEHdr mime; - - mime.create(heap); - mime_parser_init(&parser); - - auto result = mime_parser_parse(&parser, heap, mime.m_mime, &real_s, real_e, false, true, false); - REQUIRE(ParseResult::DONE == result); - - mime.m_mime->recompute_cooked_stuff(); - - // All malformed directives should result in mask == 0. - auto mask = mime.get_cooked_cc_mask(); - REQUIRE(mask == 0); - - heap->destroy(); -} - -// Test that properly formed Cache-Control directives are correctly cooked. -TEST_CASE("Cache-Control Valid Cooking", "[proxy][hdrutils]") -{ - struct ValidCCTestCase { - const char *description; - const char *header_text; - uint32_t expected_mask; - int32_t expected_max_age; - int32_t expected_s_maxage; - int32_t expected_max_stale; - int32_t expected_min_fresh; - }; - - // Use 0 to indicate "don't care" for integer values (mask determines which are valid). - // clang-format off - static const std::vector valid_cc_test_cases = { - // Basic directives without values - {"public only", - "Cache-Control: public\r\n\r\n", - MIME_COOKED_MASK_CC_PUBLIC, - 0, 0, 0, 0}, - - {"private only", - "Cache-Control: private\r\n\r\n", - MIME_COOKED_MASK_CC_PRIVATE, - 0, 0, 0, 0}, - - {"no-cache only", - "Cache-Control: no-cache\r\n\r\n", - MIME_COOKED_MASK_CC_NO_CACHE, - 0, 0, 0, 0}, - - {"no-store only", - "Cache-Control: no-store\r\n\r\n", - MIME_COOKED_MASK_CC_NO_STORE, - 0, 0, 0, 0}, - - {"no-transform only", - "Cache-Control: no-transform\r\n\r\n", - MIME_COOKED_MASK_CC_NO_TRANSFORM, - 0, 0, 0, 0}, - - {"must-revalidate only", - "Cache-Control: must-revalidate\r\n\r\n", - MIME_COOKED_MASK_CC_MUST_REVALIDATE, - 0, 0, 0, 0}, - - {"proxy-revalidate only", - "Cache-Control: proxy-revalidate\r\n\r\n", - MIME_COOKED_MASK_CC_PROXY_REVALIDATE, - 0, 0, 0, 0}, - - {"only-if-cached only", - "Cache-Control: only-if-cached\r\n\r\n", - MIME_COOKED_MASK_CC_ONLY_IF_CACHED, - 0, 0, 0, 0}, - - // Directives with values - {"max-age=0", - "Cache-Control: max-age=0\r\n\r\n", - MIME_COOKED_MASK_CC_MAX_AGE, - 0, 0, 0, 0}, - - {"max-age=300", - "Cache-Control: max-age=300\r\n\r\n", - MIME_COOKED_MASK_CC_MAX_AGE, - 300, 0, 0, 0}, - - {"max-age=86400", - "Cache-Control: max-age=86400\r\n\r\n", - MIME_COOKED_MASK_CC_MAX_AGE, - 86400, 0, 0, 0}, - - {"s-maxage=600", - "Cache-Control: s-maxage=600\r\n\r\n", - MIME_COOKED_MASK_CC_S_MAXAGE, - 0, 600, 0, 0}, - - {"max-stale=100", - "Cache-Control: max-stale=100\r\n\r\n", - MIME_COOKED_MASK_CC_MAX_STALE, - 0, 0, 100, 0}, - - {"min-fresh=60", - "Cache-Control: min-fresh=60\r\n\r\n", - MIME_COOKED_MASK_CC_MIN_FRESH, - 0, 0, 0, 60}, - - // Multiple directives - {"max-age and public", - "Cache-Control: max-age=300, public\r\n\r\n", - MIME_COOKED_MASK_CC_MAX_AGE | MIME_COOKED_MASK_CC_PUBLIC, - 300, 0, 0, 0}, - - {"public and max-age (reversed order)", - "Cache-Control: public, max-age=300\r\n\r\n", - MIME_COOKED_MASK_CC_MAX_AGE | MIME_COOKED_MASK_CC_PUBLIC, - 300, 0, 0, 0}, - - {"max-age and s-maxage", - "Cache-Control: max-age=300, s-maxage=600\r\n\r\n", - MIME_COOKED_MASK_CC_MAX_AGE | MIME_COOKED_MASK_CC_S_MAXAGE, - 300, 600, 0, 0}, - - {"private and no-cache", - "Cache-Control: private, no-cache\r\n\r\n", - MIME_COOKED_MASK_CC_PRIVATE | MIME_COOKED_MASK_CC_NO_CACHE, - 0, 0, 0, 0}, - - {"no-store and no-cache", - "Cache-Control: no-store, no-cache\r\n\r\n", - MIME_COOKED_MASK_CC_NO_STORE | MIME_COOKED_MASK_CC_NO_CACHE, - 0, 0, 0, 0}, - - {"must-revalidate and proxy-revalidate", - "Cache-Control: must-revalidate, proxy-revalidate\r\n\r\n", - MIME_COOKED_MASK_CC_MUST_REVALIDATE | MIME_COOKED_MASK_CC_PROXY_REVALIDATE, - 0, 0, 0, 0}, - - {"complex: public, max-age, s-maxage, must-revalidate", - "Cache-Control: public, max-age=300, s-maxage=600, must-revalidate\r\n\r\n", - MIME_COOKED_MASK_CC_PUBLIC | MIME_COOKED_MASK_CC_MAX_AGE | - MIME_COOKED_MASK_CC_S_MAXAGE | MIME_COOKED_MASK_CC_MUST_REVALIDATE, - 300, 600, 0, 0}, - - {"all request directives: max-age, max-stale, min-fresh, no-cache, no-store, no-transform, only-if-cached", - "Cache-Control: max-age=100, max-stale=200, min-fresh=50, no-cache, no-store, no-transform, only-if-cached\r\n\r\n", - MIME_COOKED_MASK_CC_MAX_AGE | MIME_COOKED_MASK_CC_MAX_STALE | MIME_COOKED_MASK_CC_MIN_FRESH | - MIME_COOKED_MASK_CC_NO_CACHE | MIME_COOKED_MASK_CC_NO_STORE | - MIME_COOKED_MASK_CC_NO_TRANSFORM | MIME_COOKED_MASK_CC_ONLY_IF_CACHED, - 100, 0, 200, 50}, - - // Edge cases - whitespace - {"extra whitespace around directive", - "Cache-Control: max-age=300 \r\n\r\n", - MIME_COOKED_MASK_CC_MAX_AGE, - 300, 0, 0, 0}, - - {"extra whitespace between directives", - "Cache-Control: max-age=300 , public\r\n\r\n", - MIME_COOKED_MASK_CC_MAX_AGE | MIME_COOKED_MASK_CC_PUBLIC, - 300, 0, 0, 0}, - - {"tab character in header value", - "Cache-Control:\tmax-age=300\r\n\r\n", - MIME_COOKED_MASK_CC_MAX_AGE, - 300, 0, 0, 0}, - - // Edge cases - unknown directives - {"unknown directive ignored, known directive parsed", - "Cache-Control: unknown-directive, max-age=300\r\n\r\n", - MIME_COOKED_MASK_CC_MAX_AGE, - 300, 0, 0, 0}, - - {"unknown directive with value ignored", - "Cache-Control: unknown=value, public\r\n\r\n", - MIME_COOKED_MASK_CC_PUBLIC, - 0, 0, 0, 0}, - - // Edge cases - numeric values (cc-parse: 0000 max-age, large max-age) - {"max-age with leading zeros (cc-parse: 0000 max-age)", - "Cache-Control: max-age=0000\r\n\r\n", - MIME_COOKED_MASK_CC_MAX_AGE, - 0, 0, 0, 0}, - - {"max-age with leading zeros and value", - "Cache-Control: max-age=00300\r\n\r\n", - MIME_COOKED_MASK_CC_MAX_AGE, - 300, 0, 0, 0}, - - {"large max-age value", - "Cache-Control: max-age=999999999\r\n\r\n", - MIME_COOKED_MASK_CC_MAX_AGE, - 999999999, 0, 0, 0}, - - // Edge cases - negative values should be parsed (behavior per implementation) - {"negative max-age value", - "Cache-Control: max-age=-1\r\n\r\n", - MIME_COOKED_MASK_CC_MAX_AGE, - -1, 0, 0, 0}, - }; - // clang-format on - - auto test_case = GENERATE(from_range(valid_cc_test_cases)); - - CAPTURE(test_case.description, test_case.header_text); - - HdrHeap *heap = new_HdrHeap(HdrHeap::DEFAULT_SIZE + 64); - MIMEParser parser; - char const *real_s = test_case.header_text; - char const *real_e = test_case.header_text + strlen(test_case.header_text); - MIMEHdr mime; - - mime.create(heap); - mime_parser_init(&parser); - - auto result = mime_parser_parse(&parser, heap, mime.m_mime, &real_s, real_e, false, true, false); - REQUIRE(ParseResult::DONE == result); - - mime.m_mime->recompute_cooked_stuff(); - - auto mask = mime.get_cooked_cc_mask(); - REQUIRE(mask == test_case.expected_mask); - - if (test_case.expected_mask & MIME_COOKED_MASK_CC_MAX_AGE) { - REQUIRE(mime.get_cooked_cc_max_age() == test_case.expected_max_age); - } - if (test_case.expected_mask & MIME_COOKED_MASK_CC_S_MAXAGE) { - REQUIRE(mime.get_cooked_cc_s_maxage() == test_case.expected_s_maxage); - } - if (test_case.expected_mask & MIME_COOKED_MASK_CC_MAX_STALE) { - REQUIRE(mime.get_cooked_cc_max_stale() == test_case.expected_max_stale); - } - if (test_case.expected_mask & MIME_COOKED_MASK_CC_MIN_FRESH) { - REQUIRE(mime.get_cooked_cc_min_fresh() == test_case.expected_min_fresh); - } - - heap->destroy(); -} diff --git a/src/proxy/http/PreWarmConfig.cc b/src/proxy/http/PreWarmConfig.cc index dbf79ce1021..bfe1582518c 100644 --- a/src/proxy/http/PreWarmConfig.cc +++ b/src/proxy/http/PreWarmConfig.cc @@ -23,7 +23,6 @@ #include "proxy/http/PreWarmConfig.h" #include "proxy/http/PreWarmManager.h" -#include "mgmt/config/ConfigRegistry.h" //// // PreWarmConfigParams @@ -44,21 +43,22 @@ PreWarmConfigParams::PreWarmConfigParams() void PreWarmConfig::startup() { - config::ConfigRegistry::Get_Instance().register_record_config( - "prewarm", [](ConfigContext ctx) { PreWarmConfig::reconfigure(ctx); }, - {"proxy.config.tunnel.prewarm.event_period", "proxy.config.tunnel.prewarm.algorithm"}); + _config_update_handler = std::make_unique>(); + + // dynamic configs + _config_update_handler->attach("proxy.config.tunnel.prewarm.event_period"); + _config_update_handler->attach("proxy.config.tunnel.prewarm.algorithm"); reconfigure(); } void -PreWarmConfig::reconfigure(ConfigContext ctx) +PreWarmConfig::reconfigure() { PreWarmConfigParams *params = new PreWarmConfigParams(); _config_id = configProcessor.set(_config_id, params); prewarmManager.reconfigure(); - ctx.complete("PreWarm config published"); } PreWarmConfigParams * diff --git a/src/proxy/http/remap/AclFiltering.cc b/src/proxy/http/remap/AclFiltering.cc index 44ecde767de..db48d09d32c 100644 --- a/src/proxy/http/remap/AclFiltering.cc +++ b/src/proxy/http/remap/AclFiltering.cc @@ -96,12 +96,6 @@ acl_filter_rule::add_argv(int _argc, char *_argv[]) return real_cnt; } -void -acl_filter_rule::add_node(const YAML::Node &_node) -{ - node = _node; -} - void acl_filter_rule::name(const char *_name) { diff --git a/src/proxy/http/remap/CMakeLists.txt b/src/proxy/http/remap/CMakeLists.txt index e6d4771049b..ff0f63da03e 100644 --- a/src/proxy/http/remap/CMakeLists.txt +++ b/src/proxy/http/remap/CMakeLists.txt @@ -24,7 +24,6 @@ add_library( NextHopRoundRobin.cc NextHopStrategyFactory.cc RemapConfig.cc - RemapYamlConfig.cc RemapPluginInfo.cc PluginDso.cc PluginFactory.cc diff --git a/src/proxy/http/remap/RemapConfig.cc b/src/proxy/http/remap/RemapConfig.cc index bac5253a675..23f859fb60d 100644 --- a/src/proxy/http/remap/RemapConfig.cc +++ b/src/proxy/http/remap/RemapConfig.cc @@ -24,7 +24,6 @@ #include "proxy/http/remap/AclFiltering.h" #include "swoc/swoc_file.h" -#include "mgmt/config/ConfigContextDiags.h" #include "proxy/http/remap/RemapConfig.h" #include "proxy/http/remap/UrlRewrite.h" #include "proxy/ReverseProxy.h" @@ -81,31 +80,6 @@ UrlWhack(char *toWhack, int *origLength) return length; } -const char * -is_valid_scheme(std::string_view fromScheme, std::string_view toScheme) -{ - const char *errStr = nullptr; - // Include support for HTTPS scheme - // includes support for FILE scheme - if ((fromScheme != std::string_view{URL_SCHEME_HTTP} && fromScheme != std::string_view{URL_SCHEME_HTTPS} && - fromScheme != std::string_view{URL_SCHEME_FILE} && fromScheme != std::string_view{URL_SCHEME_TUNNEL} && - fromScheme != std::string_view{URL_SCHEME_WS} && fromScheme != std::string_view{URL_SCHEME_WSS} && - fromScheme != std::string_view{URL_SCHEME_HTTP_UDS} && fromScheme != std::string_view{URL_SCHEME_HTTPS_UDS}) || - (toScheme != std::string_view{URL_SCHEME_HTTP} && toScheme != std::string_view{URL_SCHEME_HTTPS} && - toScheme != std::string_view{URL_SCHEME_TUNNEL} && toScheme != std::string_view{URL_SCHEME_WS} && - toScheme != std::string_view{URL_SCHEME_WSS})) { - errStr = "only http, https, http+unix, https+unix, ws, wss, and tunnel remappings are supported"; - return errStr; - } - - // If mapping from WS or WSS we must map out to WS or WSS - if ((fromScheme == std::string_view{URL_SCHEME_WSS} || fromScheme == std::string_view{URL_SCHEME_WS}) && - (toScheme != std::string_view{URL_SCHEME_WSS} && toScheme != std::string_view{URL_SCHEME_WS})) { - errStr = "WS or WSS can only be mapped out to WS or WSS."; - } - return errStr; -} - /** Cleanup *char[] array - each item in array must be allocated via ats_malloc or similar "x..." function. @@ -205,7 +179,7 @@ process_filter_opt(url_mapping *mp, const BUILD_TABLE_INFO *bti, char *errStrBuf return errStr; } -bool +static bool is_inkeylist(const char *key, ...) { va_list ap; @@ -333,7 +307,7 @@ parse_deactivate_directive(const char *directive, BUILD_TABLE_INFO *bti, char *e return nullptr; } -void +static void free_directory_list(int n_entries, struct dirent **entrylist) { for (int i = 0; i < n_entries; ++i) { @@ -1006,7 +980,7 @@ remap_load_plugin(const char *const *argv, int argc, url_mapping *mp, char *errb output argument reg_map. It assumes existing data in reg_map is inconsequential and will be perfunctorily null-ed; */ -bool +static bool process_regex_mapping_config(const char *from_host_lower, url_mapping *new_mapping, UrlRewrite::RegexMapping *reg_map) { std::string_view to_host{}; @@ -1076,7 +1050,7 @@ process_regex_mapping_config(const char *from_host_lower, url_mapping *new_mappi } bool -remap_parse_config_bti(const char *path, BUILD_TABLE_INFO *bti, ConfigContext ctx) +remap_parse_config_bti(const char *path, BUILD_TABLE_INFO *bti) { char errBuf[1024]; char errStrBuf[1024]; @@ -1115,7 +1089,7 @@ remap_parse_config_bti(const char *path, BUILD_TABLE_INFO *bti, ConfigContext ct return true; } if (ec.value()) { - CfgLoadLog(ctx, DL_Warning, "Failed to open remapping configuration file %s - %s", path, strerror(ec.value())); + Warning("Failed to open remapping configuration file %s - %s", path, strerror(ec.value())); return false; } @@ -1123,7 +1097,7 @@ remap_parse_config_bti(const char *path, BUILD_TABLE_INFO *bti, ConfigContext ct ACLBehaviorPolicy behavior_policy = ACLBehaviorPolicy::ACL_BEHAVIOR_LEGACY; if (!UrlRewrite::get_acl_behavior_policy(behavior_policy)) { - CfgLoadLog(ctx, DL_Warning, "Failed to get ACL matching policy."); + Warning("Failed to get ACL matching policy."); return false; } bti->behavior_policy = behavior_policy; @@ -1198,8 +1172,28 @@ remap_parse_config_bti(const char *path, BUILD_TABLE_INFO *bti, ConfigContext ct type_id_str = is_cur_mapping_regex ? (bti->paramv[0] + 6) : bti->paramv[0]; // Check to see whether is a reverse or forward mapping - maptype = get_mapping_type(type_id_str, bti); - if (maptype == mapping_type::NONE) { + if (!strcasecmp("reverse_map", type_id_str)) { + Dbg(dbg_ctl_url_rewrite, "[BuildTable] - mapping_type::REVERSE_MAP"); + maptype = mapping_type::REVERSE_MAP; + } else if (!strcasecmp("map", type_id_str)) { + Dbg(dbg_ctl_url_rewrite, "[BuildTable] - %s", + ((bti->remap_optflg & REMAP_OPTFLG_MAP_WITH_REFERER) == 0) ? "mapping_type::FORWARD_MAP" : + "mapping_type::FORWARD_MAP_REFERER"); + maptype = + ((bti->remap_optflg & REMAP_OPTFLG_MAP_WITH_REFERER) == 0) ? mapping_type::FORWARD_MAP : mapping_type::FORWARD_MAP_REFERER; + } else if (!strcasecmp("redirect", type_id_str)) { + Dbg(dbg_ctl_url_rewrite, "[BuildTable] - mapping_type::PERMANENT_REDIRECT"); + maptype = mapping_type::PERMANENT_REDIRECT; + } else if (!strcasecmp("redirect_temporary", type_id_str)) { + Dbg(dbg_ctl_url_rewrite, "[BuildTable] - mapping_type::TEMPORARY_REDIRECT"); + maptype = mapping_type::TEMPORARY_REDIRECT; + } else if (!strcasecmp("map_with_referer", type_id_str)) { + Dbg(dbg_ctl_url_rewrite, "[BuildTable] - mapping_type::FORWARD_MAP_REFERER"); + maptype = mapping_type::FORWARD_MAP_REFERER; + } else if (!strcasecmp("map_with_recv_port", type_id_str)) { + Dbg(dbg_ctl_url_rewrite, "[BuildTable] - mapping_type::FORWARD_MAP_WITH_RECV_PORT"); + maptype = mapping_type::FORWARD_MAP_WITH_RECV_PORT; + } else { snprintf(errStrBuf, sizeof(errStrBuf), "unknown mapping type at line %d", cln + 1); errStr = errStrBuf; goto MAP_ERROR; @@ -1336,8 +1330,23 @@ remap_parse_config_bti(const char *path, BUILD_TABLE_INFO *bti, ConfigContext ct } toScheme = new_mapping->toURL.scheme_get(); - errStr = is_valid_scheme(fromScheme, toScheme); - if (errStr != nullptr) { + // Include support for HTTPS scheme + // includes support for FILE scheme + if ((fromScheme != std::string_view{URL_SCHEME_HTTP} && fromScheme != std::string_view{URL_SCHEME_HTTPS} && + fromScheme != std::string_view{URL_SCHEME_FILE} && fromScheme != std::string_view{URL_SCHEME_TUNNEL} && + fromScheme != std::string_view{URL_SCHEME_WS} && fromScheme != std::string_view{URL_SCHEME_WSS} && + fromScheme != std::string_view{URL_SCHEME_HTTP_UDS} && fromScheme != std::string_view{URL_SCHEME_HTTPS_UDS}) || + (toScheme != std::string_view{URL_SCHEME_HTTP} && toScheme != std::string_view{URL_SCHEME_HTTPS} && + toScheme != std::string_view{URL_SCHEME_TUNNEL} && toScheme != std::string_view{URL_SCHEME_WS} && + toScheme != std::string_view{URL_SCHEME_WSS})) { + errStr = "only http, https, http+unix, https+unix, ws, wss, and tunnel remappings are supported"; + goto MAP_ERROR; + } + + // If mapping from WS or WSS we must map out to WS or WSS + if ((fromScheme == std::string_view{URL_SCHEME_WSS} || fromScheme == std::string_view{URL_SCHEME_WS}) && + (toScheme != std::string_view{URL_SCHEME_WSS} && toScheme != std::string_view{URL_SCHEME_WS})) { + errStr = "WS or WSS can only be mapped out to WS or WSS."; goto MAP_ERROR; } @@ -1544,7 +1553,7 @@ remap_parse_config_bti(const char *path, BUILD_TABLE_INFO *bti, ConfigContext ct MAP_ERROR: snprintf(errBuf, sizeof(errBuf), "%s failed to add remap rule at %s line %d: %s", modulePrefix, path, cln + 1, errStr); - CfgLoadLog(ctx, DL_Error, "%s", errBuf); + Error("%s", errBuf); delete reg_map; delete new_mapping; @@ -1556,7 +1565,7 @@ remap_parse_config_bti(const char *path, BUILD_TABLE_INFO *bti, ConfigContext ct } bool -remap_parse_config(const char *path, UrlRewrite *rewrite, ConfigContext ctx) +remap_parse_config(const char *path, UrlRewrite *rewrite) { BUILD_TABLE_INFO bti; @@ -1565,7 +1574,7 @@ remap_parse_config(const char *path, UrlRewrite *rewrite, ConfigContext ctx) rewrite->pluginFactory.indicatePreReload(); bti.rewrite = rewrite; - bool status = remap_parse_config_bti(path, &bti, ctx); + bool status = remap_parse_config_bti(path, &bti); /* Now after we parsed the configuration and (re)loaded plugins and plugin instances * accordingly notify all plugins that we are done */ diff --git a/src/proxy/http/remap/RemapYamlConfig.cc b/src/proxy/http/remap/RemapYamlConfig.cc deleted file mode 100644 index 7a441434463..00000000000 --- a/src/proxy/http/remap/RemapYamlConfig.cc +++ /dev/null @@ -1,1048 +0,0 @@ -/** @file - * - * YAML remap configuration file parsing implementation. - * - * @section license License - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "proxy/http/remap/RemapYamlConfig.h" - -#include "mgmt/config/ConfigContextDiags.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "tscore/Diags.h" -#include "tscore/ink_string.h" -#include "tsutil/ts_errata.h" -#include "tsutil/PostScript.h" -#include "swoc/bwf_base.h" -#include "swoc/bwf_ex.h" -#include "swoc/swoc_file.h" - -#include "proxy/http/remap/UrlRewrite.h" -#include "proxy/http/remap/UrlMapping.h" -#include "proxy/http/remap/RemapConfig.h" -#include "proxy/http/remap/AclFiltering.h" -#include "records/RecCore.h" - -namespace -{ -DbgCtl dbg_ctl_remap_yaml{"remap_yaml"}; -DbgCtl dbg_ctl_url_rewrite{"url_rewrite"}; -} // end anonymous namespace - -swoc::Errata -parse_yaml_url(const YAML::Node &node, URL &url, bool host_check, std::string_view &url_str) -{ - if (!node || !node.IsMap()) { - return swoc::Errata("URL must be a map"); - } - url.create(nullptr); - - // Use url first if defined - ParseResult rparse; - if (node["url"]) { - url_str = node["url"].as(); - if (host_check) { - rparse = url.parse_regex(url_str); - } else { - rparse = url.parse_no_host_check(url_str); - } - if (rparse != ParseResult::DONE) { - return swoc::Errata("malformed URL: {}", url_str); - } - - return {}; - } - - // Build URL string from components - if (node["scheme"]) { - url.scheme_set(node["scheme"].as()); - } - - if (node["host"]) { - url.host_set(node["host"].as()); - } - - if (node["port"]) { - url.port_set(node["port"].as()); - } - - if (node["path"]) { - url.path_set(node["path"].as()); - } - - return {}; -} - -swoc::Errata -remap_validate_yaml_filter_args(acl_filter_rule **rule_pp, const YAML::Node &node, ACLBehaviorPolicy behavior_policy) -{ - acl_filter_rule *rule; - int j; - bool new_rule_flg = false; - - if (!rule_pp) { - Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Invalid argument(s)"); - return swoc::Errata("Invalid argument(s)"); - } - - Dbg(dbg_ctl_url_rewrite, "validate_filter_args: "); - for (const auto &rule : node) { - Dbg(dbg_ctl_url_rewrite, "\"%s\" \n", rule.first.as().c_str()); - } - - ts::PostScript free_rule([&]() -> void { - if (new_rule_flg) { - delete rule; - *rule_pp = nullptr; - } - }); - - if ((rule = *rule_pp) == nullptr) { - rule = new acl_filter_rule(); - if (unlikely((*rule_pp = rule) == nullptr)) { - Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Memory allocation error"); - return swoc::Errata("Memory allocation Error"); - } - new_rule_flg = true; - Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] new acl_filter_rule class was created during remap rule processing"); - } - - if (!node || !node.IsMap()) { - return swoc::Errata("filters must be a map"); - } - - // Parse method - auto parse_method = [&](const std::string &method_str) { - int m = hdrtoken_tokenize(method_str.c_str(), method_str.length(), nullptr) - HTTP_WKSIDX_CONNECT; - - if (m >= 0 && m < HTTP_WKSIDX_METHODS_CNT) { - rule->standard_method_lookup[m] = true; - } else { - Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Using nonstandard method [%s]", method_str.c_str()); - rule->nonstandard_methods.insert(method_str); - } - rule->method_restriction_enabled = true; - }; - - if (node["method"]) { - if (node["method"].IsSequence()) { - for (const auto &method : node["method"]) { - parse_method(method.as()); - } - } else { - parse_method(node["method"].as()); - } - } - - // Parse src_ip (and src_ip_invert) - auto parse_src_ip = [&](const std::string &ip_str, bool invert) -> swoc::Errata { - if (rule->src_ip_cnt >= ACL_FILTER_MAX_SRC_IP) { - Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Too many \"src_ip=\" filters"); - return swoc::Errata("Defined more than {} src_ip filters", ACL_FILTER_MAX_SRC_IP); - } - - src_ip_info_t *ipi = &rule->src_ip_array[rule->src_ip_cnt]; - if (invert) { - ipi->invert = true; - } - std::string_view arg{ip_str}; - if (arg == "all") { - ipi->match_all_addresses = true; - } else if (ats_ip_range_parse(arg, ipi->start, ipi->end) != 0) { - Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Unable to parse IP value in %s", ip_str.c_str()); - return swoc::Errata("Unable to parse IP value: {}", ip_str); - } - for (j = 0; j < rule->src_ip_cnt; j++) { - if (rule->src_ip_array[j].start == ipi->start && rule->src_ip_array[j].end == ipi->end) { - ipi->reset(); - return {}; - } - } - if (ipi) { - rule->src_ip_cnt++; - rule->src_ip_valid = 1; - } - return {}; - }; - - if (node["src_ip"]) { - if (node["src_ip"].IsSequence()) { - for (const auto &src_ip : node["src_ip"]) { - auto errata = parse_src_ip(src_ip.as(), false); - if (!errata.is_ok()) { - return errata; - } - } - } else { - auto errata = parse_src_ip(node["src_ip"].as(), false); - if (!errata.is_ok()) { - return errata; - } - } - } - - if (node["src_ip_invert"]) { - if (node["src_ip_invert"].IsSequence()) { - for (const auto &src_ip_invert : node["src_ip_invert"]) { - auto errata = parse_src_ip(src_ip_invert.as(), true); - if (!errata.is_ok()) { - return errata; - } - } - } else { - auto errata = parse_src_ip(node["src_ip_invert"].as(), true); - if (!errata.is_ok()) { - return errata; - } - } - } - - // Parse src_ip_category (and src_ip_category_invert) - auto parse_src_ip_category = [&](const std::string &ip_category, bool invert) -> swoc::Errata { - if (rule->src_ip_category_cnt >= ACL_FILTER_MAX_SRC_IP) { - Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Too many \"src_ip_category=\" filters"); - return swoc::Errata("Defined more than {} src_ip_category filters", ACL_FILTER_MAX_SRC_IP); - } - src_ip_category_info_t *ipi = &rule->src_ip_category_array[rule->src_ip_category_cnt]; - ipi->category.assign(ip_category); - if (invert) { - ipi->invert = true; - } - for (j = 0; j < rule->src_ip_category_cnt; j++) { - if (rule->src_ip_category_array[j].category == ipi->category) { - ipi->reset(); - return {}; - } - } - if (ipi) { - rule->src_ip_category_cnt++; - rule->src_ip_category_valid = 1; - } - return {}; - }; - - if (node["src_ip_category"]) { - auto errata = parse_src_ip_category(node["src_ip_category"].as(), false); - if (!errata.is_ok()) { - return errata; - } - } - - if (node["src_ip_category_invert"]) { - auto errata = parse_src_ip_category(node["src_ip_category_invert"].as(), true); - if (!errata.is_ok()) { - return errata; - } - } - - // Parse in_ip (and in_ip_invert) - auto parse_in_ip = [&](const std::string &in_ip, bool invert) -> swoc::Errata { - if (rule->in_ip_cnt >= ACL_FILTER_MAX_IN_IP) { - Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Too many \"in_ip=\" filters"); - return swoc::Errata("Defined more than {} in_ip filters", ACL_FILTER_MAX_IN_IP); - } - src_ip_info_t *ipi = &rule->in_ip_array[rule->in_ip_cnt]; - if (invert) { - ipi->invert = true; - } - // important! use copy of argument - std::string_view arg{in_ip}; - if (arg == "all") { - ipi->match_all_addresses = true; - } else if (ats_ip_range_parse(arg, ipi->start, ipi->end) != 0) { - Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Unable to parse IP value in %s", in_ip.c_str()); - return swoc::Errata("Unable to parse IP value: {}", in_ip); - } - for (j = 0; j < rule->in_ip_cnt; j++) { - if (rule->in_ip_array[j].start == ipi->start && rule->in_ip_array[j].end == ipi->end) { - ipi->reset(); - return {}; - } - } - if (ipi) { - rule->in_ip_cnt++; - rule->in_ip_valid = 1; - } - return {}; - }; - - if (node["in_ip"]) { - if (node["in_ip"].IsSequence()) { - for (const auto &in_ip : node["in_ip"]) { - auto errata = parse_in_ip(in_ip.as(), false); - if (!errata.is_ok()) { - return errata; - } - } - } else { - auto errata = parse_in_ip(node["in_ip"].as(), false); - if (!errata.is_ok()) { - return errata; - } - } - } - - if (node["in_ip_invert"]) { - if (node["in_ip_invert"].IsSequence()) { - for (const auto &in_ip_invert : node["in_ip_invert"]) { - auto errata = parse_in_ip(in_ip_invert.as(), true); - if (!errata.is_ok()) { - return errata; - } - } - } else { - auto errata = parse_in_ip(node["in_ip_invert"].as(), true); - if (!errata.is_ok()) { - return errata; - } - } - } - - // Parse action - if (node["action"]) { - if (node["action"].IsSequence()) { - Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Only one action is allowed per remap ACL"); - return swoc::Errata("Only one action is allowed per remap ACL"); - } - std::string action_str = node["action"].as(); - if (behavior_policy == ACLBehaviorPolicy::ACL_BEHAVIOR_MODERN) { - // With the new matching policy, we don't allow the legacy "allow" and - // "deny" actions. Users must transition to either add_allow/add_deny or - // set_allow/set_deny. - if (is_inkeylist(action_str.c_str(), "allow", "deny", nullptr)) { - Dbg(dbg_ctl_url_rewrite, - R"([validate_filter_args] "allow" and "deny" are no longer valid. Use add_allow/add_deny or set_allow/set_deny: "%s"")", - action_str.c_str()); - return swoc::Errata("\"allow\" and \"deny\" are no longer valid. Use add_allow/add_deny or set_allow/set_deny: {}", - action_str.c_str()); - } - } - if (is_inkeylist(action_str.c_str(), "add_allow", "add_deny", nullptr)) { - rule->add_flag = 1; - } else { - rule->add_flag = 0; - } - // Remove "deny" from this list when MATCH_ON_IP_AND_METHOD is removed in 11.x. - if (is_inkeylist(action_str.c_str(), "0", "off", "deny", "set_deny", "add_deny", "disable", nullptr)) { - rule->allow_flag = 0; - // Remove "allow" from this list when MATCH_ON_IP_AND_METHOD is removed in 11.x. - } else if (is_inkeylist(action_str.c_str(), "1", "on", "allow", "set_allow", "add_allow", "enable", nullptr)) { - rule->allow_flag = 1; - } else { - Dbg(dbg_ctl_url_rewrite, "[validate_filter_args] Unknown argument \"%s\"", action_str.c_str()); - return swoc::Errata("Unknown action: {}", action_str); - } - } - - // Parse internal - if (node["internal"] && node["internal"].as()) { - rule->internal = 1; - } - - if (dbg_ctl_url_rewrite.on()) { - rule->print(); - } - - free_rule.release(); - return {}; -} - -swoc::Errata -parse_map_referer(const YAML::Node &node, url_mapping *url_mapping) -{ - if (!node || !node.IsMap()) { - return swoc::Errata("redirect must be a map"); - } - - if (!node["url"]) { - return swoc::Errata("Missing 'url' field in redirect map-with-referer"); - } - std::string url = node["url"].as(); - url_mapping->filter_redirect_url = ats_strdup(url.c_str()); - if (!strcasecmp(url.c_str(), "") || !strcasecmp(url.c_str(), "default") || - !strcasecmp(url.c_str(), "") || !strcasecmp(url.c_str(), "default_redirect_url")) { - url_mapping->default_redirect_url = true; - } - url_mapping->redir_chunk_list = redirect_tag_str::parse_format_redirect_url(ats_strdup(url.c_str())); - - if (!node["regex"] || !node["regex"].IsSequence()) { - return swoc::Errata("'regex' field must be sequence"); - } - - referer_info *ri; - for (const auto &rule : node["regex"]) { - char refinfo_error_buf[1024]; - bool refinfo_error = false; - std::string regex = rule.as(); - - ri = new referer_info(regex.c_str(), &refinfo_error, refinfo_error_buf, sizeof(refinfo_error_buf)); - if (refinfo_error) { - delete ri; - ri = nullptr; - return swoc::Errata("Incorrect Referer regular expression \"{}\" - {}", regex.c_str(), refinfo_error_buf); - } - - if (ri && ri->negative) { - if (ri->any) { - url_mapping->optional_referer = true; /* referer header is optional */ - delete ri; - ri = nullptr; - } else { - url_mapping->negative_referer = true; /* we have negative referer in list */ - } - } - if (ri) { - ri->next = url_mapping->referer_list; - url_mapping->referer_list = ri; - } - } - return {}; -} - -swoc::Errata -parse_yaml_plugins(const YAML::Node &node, url_mapping *url_mapping, BUILD_TABLE_INFO *bti) -{ - char *err; - - if (!node["name"]) { - return swoc::Errata("plugin missing 'name' field"); - } - - std::string plugin_name = node["name"].as(); - Dbg(dbg_ctl_remap_yaml, "Loading plugin: %s", plugin_name.c_str()); - - std::vector plugin_params; - - /* Prepare remap plugin parameters from the config */ - if ((err = url_mapping->fromURL.string_get(nullptr)) == nullptr) { - return swoc::Errata("Can't load fromURL from URL class"); - } - plugin_params.emplace_back(err); - ats_free(err); - - if ((err = url_mapping->toURL.string_get(nullptr)) == nullptr) { - return swoc::Errata("Can't load toURL from URL class"); - } - plugin_params.emplace_back(err); - ats_free(err); - - // Add plugin parameters - if (node["params"] && node["params"].IsSequence()) { - for (const auto ¶m : node["params"]) { - plugin_params.push_back(param.as()); - Dbg(dbg_ctl_remap_yaml, " Plugin param: %s", plugin_params.back().c_str()); - } - } - - std::vector pargv; - for (auto ¶m : plugin_params) { - pargv.push_back(param.data()); - } - - RemapPluginInst *pi = nullptr; - std::string error; - { - uint32_t elevate_access = 0; - elevate_access = RecGetRecordInt("proxy.config.plugin.load_elevated").value_or(0); - ElevateAccess access(elevate_access ? ElevateAccess::FILE_PRIVILEGE : 0); - - pi = bti->rewrite->pluginFactory.getRemapPlugin(swoc::file::path(plugin_name), pargv.size(), pargv.data(), error, - isPluginDynamicReloadEnabled()); - } // done elevating access - - if (nullptr == pi) { - return swoc::Errata("failed to instantiate plugin ({}) to remap rule: {}", plugin_name.c_str(), error.c_str()); - } - - url_mapping->add_plugin_instance(pi); - return {}; -} - -swoc::Errata -parse_yaml_filter_directive(const YAML::Node &node, BUILD_TABLE_INFO *bti) -{ - acl_filter_rule *rp; - - // Check for activate_filters directive - if (node["activate_filter"]) { - std::string filter_name = node["activate_filter"].as(); - - // Check if for ip_allow filter - if (filter_name == "ip_allow") { - bti->ip_allow_check_enabled_p = true; - return {}; - } - - if ((rp = acl_filter_rule::find_byname(bti->rules_list, filter_name.c_str())) == nullptr) { - Dbg(dbg_ctl_url_rewrite, "(Undefined filter '%s' in activate_filter directive)", filter_name.c_str()); - return swoc::Errata("(Undefined filter '{}' in activate_filter directive)", filter_name.c_str()); - } - - acl_filter_rule::requeue_in_active_list(&bti->rules_list, rp); - return {}; - } - - // Check for deactivate_filters directive - if (node["deactivate_filter"]) { - std::string filter_name = node["deactivate_filter"].as(); - - // Check if for ip_allow filter - if (strcmp(filter_name.c_str(), "ip_allow") == 0) { - bti->ip_allow_check_enabled_p = false; - return {}; - } - - if ((rp = acl_filter_rule::find_byname(bti->rules_list, filter_name.c_str())) == nullptr) { - Dbg(dbg_ctl_url_rewrite, "(Undefined filter '%s' in deactivate_filter directive)", filter_name.c_str()); - return swoc::Errata("(Undefined filter '{}' in deactivate_filter directive)", filter_name.c_str()); - } - - acl_filter_rule::requeue_in_passive_list(&bti->rules_list, rp); - return {}; - } - - // Check for delete_filters directive - if (node["delete_filter"]) { - std::string filter_name = node["delete_filter"].as(); - - acl_filter_rule::delete_byname(&bti->rules_list, filter_name.c_str()); - return {}; - } - - // Check for define_filter directive - if (node["define_filter"]) { - return parse_yaml_define_directive(node["define_filter"], bti); - } - - return swoc::Errata("Not a filter directive"); -} - -swoc::Errata -parse_yaml_define_directive(const YAML::Node &node, BUILD_TABLE_INFO *bti) -{ - bool flg; - acl_filter_rule *rp; - swoc::Errata errata; - - if (!node || !node.IsMap()) { - return swoc::Errata("named filters must be a map"); - } - - // When iterating over a YAML map, each element is a key-value pair - // We expect a single-entry map here - auto it = node.begin(); - std::string filter_name = it->first.as(); - const YAML::Node filter_spec = it->second; - - flg = ((rp = acl_filter_rule::find_byname(bti->rules_list, filter_name.c_str())) == nullptr) ? true : false; - // coverity[alloc_arg] - if ((errata = remap_validate_yaml_filter_args(&rp, filter_spec, bti->behavior_policy)).is_ok() && rp) { - if (flg) { // new filter - add to list - acl_filter_rule **rpp = nullptr; - Dbg(dbg_ctl_url_rewrite, "[parse_directive] new rule \"%s\" was created", filter_name.c_str()); - for (rpp = &bti->rules_list; *rpp; rpp = &((*rpp)->next)) { - ; - } - (*rpp = rp)->name(filter_name.c_str()); - } - Dbg(dbg_ctl_url_rewrite, "[parse_directive] %zu argument(s) were added to rule \"%s\"", filter_spec.size(), - filter_name.c_str()); - rp->add_node(filter_spec); // store string arguments for future processing - } - return errata; -} - -swoc::Errata -process_yaml_filter_opt(url_mapping *mp, const YAML::Node &node, const BUILD_TABLE_INFO *bti) -{ - acl_filter_rule *rp, **rpp; - swoc::Errata errata; - - if (unlikely(!mp || !bti)) { - Dbg(dbg_ctl_url_rewrite, "[process_yaml_filter_opt] Invalid argument(s)"); - return swoc::Errata("[process_yaml_filter_opt] Invalid argument(s)"); - } - // ACLs are processed in this order: - // 1. A remap.config ACL line for an individual remap rule. - // 2. All named ACLs in remap.config. - // 3. Rules as specified in ip_allow.yaml. - if (node["acl_filter"]) { - Dbg(dbg_ctl_url_rewrite, "[process_yaml_filter_opt] Add per remap filter"); - for (rpp = &mp->filter; *rpp; rpp = &((*rpp)->next)) { - ; - } - errata = remap_validate_yaml_filter_args(rpp, node["acl_filter"], bti->behavior_policy); - } - - for (rp = bti->rules_list; rp; rp = rp->next) { - for (rpp = &mp->filter; *rpp; rpp = &((*rpp)->next)) { - ; - } - if (rp->active_queue_flag) { - Dbg(dbg_ctl_url_rewrite, "[process_yaml_filter_opt] Add active main filter \"%s\"", - rp->filter_name ? rp->filter_name : ""); - for (rpp = &mp->filter; *rpp; rpp = &((*rpp)->next)) { - ; - } - errata = remap_validate_yaml_filter_args(rpp, rp->node, bti->behavior_policy); - if (!errata.is_ok()) { - break; - } - if (auto rule = *rpp; rule) { - // If no IP addresses are listed, treat that like `@src_ip=all`. - if (rule->src_ip_valid == 0 && rule->src_ip_cnt == 0) { - src_ip_info_t *ipi = &rule->src_ip_array[rule->src_ip_cnt]; - ipi->match_all_addresses = true; - rule->src_ip_cnt++; - rule->src_ip_valid = 1; - } - } - } - } - - // Set the ip allow flag for this rule to the current ip allow flag state - mp->ip_allow_check_enabled_p = bti->ip_allow_check_enabled_p; - - return errata; -} - -swoc::Errata -parse_yaml_remap_fragment(const char *path, BUILD_TABLE_INFO *bti) -{ - // We need to create a new bti so that we don't clobber any state in the parent parse, but we want - // to keep the ACL rules from the parent because ACLs must be global across the full set of config - // files. - BUILD_TABLE_INFO nbti; - bool success; - - if (access(path, R_OK) == -1) { - return swoc::Errata("{}: {}", path, strerror(errno)); - } - - nbti.rules_list = bti->rules_list; - nbti.rewrite = bti->rewrite; - - Dbg(dbg_ctl_url_rewrite, "[%s] including remap configuration from %s", __func__, path); - success = remap_parse_yaml_bti(path, &nbti); - - // The sub-parse might have updated the rules list, so push it up to the parent parse. - bti->rules_list = nbti.rules_list; - - if (success) { - // register the included file with the management subsystem so that we can correctly - // reload them when they change - load_remap_file_cb(ts::filename::REMAP_YAML, path); - } else { - return swoc::Errata("failed to parse included file {}", path); - } - - return {}; -} - -swoc::Errata -parse_yaml_include_directive(const std::string &include_path, BUILD_TABLE_INFO *bti) -{ - ats_scoped_str path; - swoc::Errata errata; - - // The included path is relative to SYSCONFDIR - path = RecConfigReadConfigPath(nullptr, include_path.c_str()); - - if (ink_file_is_directory(path)) { - struct dirent **entrylist; - int n_entries; - - n_entries = scandir(path, &entrylist, nullptr, alphasort); - if (n_entries == -1) { - return swoc::Errata("failed to open {}: {}", path.get(), strerror(errno)); - } - - for (int j = 0; j < n_entries; ++j) { - ats_scoped_str subpath; - - if (isdot(entrylist[j]->d_name) || isdotdot(entrylist[j]->d_name)) { - continue; - } - - subpath = Layout::relative_to(path.get(), entrylist[j]->d_name); - - if (ink_file_is_directory(subpath)) { - continue; - } - - errata = parse_yaml_remap_fragment(subpath, bti); - if (!errata.is_ok()) { - break; - } - } - - free_directory_list(n_entries, entrylist); - - } else { - errata = parse_yaml_remap_fragment(path, bti); - } - - return errata; -} - -swoc::Errata -parse_yaml_remap_rule(const YAML::Node &node, BUILD_TABLE_INFO *bti) -{ - std::string errStr; - - std::string_view fromScheme{}, toScheme{}; - std::string_view fromHost{}, toHost{}; - std::string_view fromUrl{}, toUrl{}; - std::string_view fromPath; - char *fromHost_lower = nullptr; - char *fromHost_lower_ptr = nullptr; - char fromHost_lower_buf[1024]; - mapping_type maptype; - bool is_cur_mapping_regex; - const char *type_id_str; - - swoc::Errata errata; - const char *valid_scheme = nullptr; - std::unique_ptr new_mapping; - std::unique_ptr reg_map; - - if (!node || !node.IsMap()) { - return swoc::Errata("remap rule must be a map"); - } - - // Parse for include directive first - if (node["include"]) { - return parse_yaml_include_directive(node["include"].as(), bti); - } - - // Parse for filter directives (activate/deactivate/delete/define) - if (node["activate_filter"] || node["deactivate_filter"] || node["delete_filter"] || node["define_filter"]) { - return parse_yaml_filter_directive(node, bti); - } - - // Parse rule type - if (!node["type"]) { - return swoc::Errata("remap rule missing 'type' field"); - } - std::string type_str = node["type"].as(); - - is_cur_mapping_regex = (strncasecmp("regex_", type_str.c_str(), 6) == 0); - type_id_str = is_cur_mapping_regex ? (type_str.c_str() + 6) : type_str.c_str(); - - // Check to see whether is a reverse or forward mapping - maptype = get_mapping_type(type_id_str, bti); - if (maptype == mapping_type::NONE) { - return swoc::Errata("unknown mapping type: {}", type_str); - } - - new_mapping = std::make_unique(); - - // apply filter rules if we have to - errata = process_yaml_filter_opt(new_mapping.get(), node, bti); - if (!errata.is_ok()) { - swoc::bwprint(errStr, "Failed to process filter: {}", errata); - goto MAP_ERROR; - } - - // update sticky flag - bti->accept_check_p = bti->accept_check_p && bti->ip_allow_check_enabled_p; - - new_mapping->map_id = 0; - if (node["mapid"]) { - new_mapping->map_id = node["mapid"].as(); - } - - // Parse from URL - if (!node["from"]) { - errStr = "remap rule missing 'from' field"; - goto MAP_ERROR; - } - - errata = parse_yaml_url(node["from"], new_mapping->fromURL, true, fromUrl); - if (!errata.is_ok()) { - swoc::bwprint(errStr, "malformed From URL: {}", errata); - goto MAP_ERROR; - } - - // Parse to URL - if (!node["to"]) { - errStr = "remap rule missing 'to' field"; - goto MAP_ERROR; - } - - errata = parse_yaml_url(node["to"], new_mapping->toURL, false, toUrl); - if (!errata.is_ok()) { - swoc::bwprint(errStr, "malformed To URL: {}", errata); - goto MAP_ERROR; - } - - // Check if valid schemes - fromScheme = new_mapping->fromURL.scheme_get(); - toScheme = new_mapping->toURL.scheme_get(); - if (fromScheme.empty()) { - new_mapping->fromURL.scheme_set(std::string_view{URL_SCHEME_HTTP}); - new_mapping->wildcard_from_scheme = true; - fromScheme = new_mapping->fromURL.scheme_get(); - } - valid_scheme = is_valid_scheme(fromScheme, toScheme); - if (valid_scheme != nullptr) { - errStr = valid_scheme; - goto MAP_ERROR; - } - - // Check if map_with_referer is used - if (node["redirect"] && maptype == mapping_type::FORWARD_MAP_REFERER) { - errata = parse_map_referer(node["redirect"], new_mapping.get()); - if (!errata.is_ok()) { - swoc::bwprint(errStr, "invalid map_with_referer: {}", errata); - goto MAP_ERROR; - } - } - - // Check to see the fromHost remapping is a relative one - fromHost = new_mapping->fromURL.host_get(); - if (fromHost.empty()) { - if (maptype == mapping_type::FORWARD_MAP || maptype == mapping_type::FORWARD_MAP_REFERER || - maptype == mapping_type::FORWARD_MAP_WITH_RECV_PORT) { - fromPath = new_mapping->fromURL.path_get(); - if ((fromPath.empty() || fromPath[0] != '/') && (fromUrl.empty() || fromUrl[0] != '/')) { - errStr = "relative remappings must begin with a /"; - goto MAP_ERROR; - } else { - fromHost = ""sv; - } - } else { - errStr = "remap source in reverse mappings requires a hostname"; - goto MAP_ERROR; - } - } - - toHost = new_mapping->toURL.host_get(); - if (toHost.empty()) { - errStr = "The remap destinations require a hostname"; - goto MAP_ERROR; - } - // Get rid of trailing slashes since they interfere - // with our ability to send redirects - - // You might be tempted to remove these lines but the new - // optimized header system will introduce problems. You - // might get two slashes occasionally instead of one because - // the rest of the system assumes that trailing slashes have - // been removed. - - if (unlikely(fromHost.length() >= sizeof(fromHost_lower_buf))) { - fromHost_lower = (fromHost_lower_ptr = static_cast(ats_malloc(fromHost.length() + 1))); - } else { - fromHost_lower = &fromHost_lower_buf[0]; - } - // Canonicalize the hostname by making it lower case - memcpy(fromHost_lower, fromHost.data(), fromHost.length()); - fromHost_lower[fromHost.length()] = 0; - LowerCaseStr(fromHost_lower); - - // set the normalized string so nobody else has to normalize this - new_mapping->fromURL.host_set({fromHost_lower, fromHost.length()}); - - if (is_cur_mapping_regex) { - reg_map = std::make_unique(); - if (!process_regex_mapping_config(fromHost_lower, new_mapping.get(), reg_map.get())) { - errStr = "could not process regex mapping config line"; - goto MAP_ERROR; - } - Dbg(dbg_ctl_url_rewrite, "Configured regex rule for host [%s]", fromHost_lower); - } - - // If a TS receives a request on a port which is set to tunnel mode - // (ie, blind forwarding) and a client connects directly to the TS, - // then the TS will use its IPv4 address and remap rules given - // to send the request to its proper destination. - // See HttpTransact::HandleBlindTunnel(). - // Therefore, for a remap with "type: map" and "scheme: tunnel", - // we also needs to convert hostname to its IPv4 addr - // and gives a new remap rule with the IPv4 addr. - if ((maptype == mapping_type::FORWARD_MAP || maptype == mapping_type::FORWARD_MAP_REFERER || - maptype == mapping_type::FORWARD_MAP_WITH_RECV_PORT) && - fromScheme == std::string_view{URL_SCHEME_TUNNEL} && (fromHost_lower[0] < '0' || fromHost_lower[0] > '9')) { - addrinfo *ai_records; // returned records. - ip_text_buffer ipb; // buffer for address string conversion. - if (0 == getaddrinfo(fromHost_lower, nullptr, nullptr, &ai_records)) { - for (addrinfo *ai_spot = ai_records; ai_spot; ai_spot = ai_spot->ai_next) { - if (ats_is_ip(ai_spot->ai_addr) && !ats_is_ip_any(ai_spot->ai_addr) && ai_spot->ai_protocol == IPPROTO_TCP) { - url_mapping *u_mapping; - - ats_ip_ntop(ai_spot->ai_addr, ipb, sizeof ipb); - u_mapping = new url_mapping; - u_mapping->fromURL.create(nullptr); - u_mapping->fromURL.copy(&new_mapping->fromURL); - u_mapping->fromURL.host_set({ipb}); - u_mapping->toURL.create(nullptr); - u_mapping->toURL.copy(&new_mapping->toURL); - - if (!bti->rewrite->InsertForwardMapping(maptype, u_mapping, ipb)) { - errStr = "unable to add mapping rule to lookup table"; - freeaddrinfo(ai_records); - goto MAP_ERROR; - } - } - } - - freeaddrinfo(ai_records); - } - } - - // check for a 'strategy' and if wire it up if one exists. - if (node["strategy"] && (maptype == mapping_type::FORWARD_MAP || maptype == mapping_type::FORWARD_MAP_REFERER || - maptype == mapping_type::FORWARD_MAP_WITH_RECV_PORT)) { - std::string strategy_name = node["strategy"].as(); - new_mapping->strategy = bti->rewrite->strategyFactory->strategyInstance(strategy_name.c_str()); - if (new_mapping->strategy == nullptr) { - errStr = "missing 'strategy' name argument, unable to add mapping rule"; - goto MAP_ERROR; - } - Dbg(dbg_ctl_url_rewrite, "mapped the 'strategy' named %s", strategy_name.c_str()); - } - - // Check "remap" plugin options and load .so object - if (node["plugins"] && (maptype == mapping_type::FORWARD_MAP || maptype == mapping_type::FORWARD_MAP_REFERER || - maptype == mapping_type::FORWARD_MAP_WITH_RECV_PORT)) { - if (!node["plugins"] || !node["plugins"].IsSequence()) { - errStr = "plugins must be a sequence"; - goto MAP_ERROR; - } - - for (const auto &plugin : node["plugins"]) { - errata = parse_yaml_plugins(plugin, new_mapping.get(), bti); - if (!errata.is_ok()) { - swoc::bwprint(errStr, "{}", errata); - goto MAP_ERROR; - } - } - } - - // Now add the mapping to appropriate container - if (!bti->rewrite->InsertMapping(maptype, new_mapping.release(), reg_map.release(), fromHost_lower, is_cur_mapping_regex)) { - errStr = "unable to add mapping rule to lookup table"; - goto MAP_ERROR; - } - - ats_free_null(fromHost_lower_ptr); - - Dbg(dbg_ctl_remap_yaml, "Successfully added mapping rule"); - return {}; - -// Deal with error / warning scenarios -MAP_ERROR: - - Error("%s", errStr.c_str()); - return swoc::Errata(errStr); -} - -bool -remap_parse_yaml_bti(const char *path, BUILD_TABLE_INFO *bti, ConfigContext ctx) -{ - try { - Dbg(dbg_ctl_remap_yaml, "Parsing YAML config file: %s", path); - - YAML::Node config = YAML::LoadFile(path); - - if (config.IsNull()) { - Dbg(dbg_ctl_remap_yaml, "Empty YAML config file"); - return true; // a missing file is ok - treat as empty, no rules. - } - - Dbg(dbg_ctl_url_rewrite, "[BuildTable] UrlRewrite::BuildTable()"); - - ACLBehaviorPolicy behavior_policy = ACLBehaviorPolicy::ACL_BEHAVIOR_LEGACY; - if (!UrlRewrite::get_acl_behavior_policy(behavior_policy)) { - CfgLoadLog(ctx, DL_Warning, "Failed to get ACL matching policy."); - return false; - } - bti->behavior_policy = behavior_policy; - - // Parse global filters section (optional) - if (config["acl_filters"] && config["acl_filters"].IsMap()) { - for (const auto &filter_def : config["acl_filters"]) { - auto errata = parse_yaml_define_directive(filter_def, bti); - if (!errata.is_ok()) { - CfgLoadLog(ctx, DL_Error, "Failed to parse acl_filters section"); - return false; - } - } - } - - if (config["remap"].IsNull() || !config["remap"].IsSequence()) { - CfgLoadLog(ctx, DL_Error, "Expected toplevel 'remap' key to be a sequence"); - return false; - } - - // Parse each remap rule - for (const auto &rule : config["remap"]) { - // Reset bti state for each rule (but keep rules_list for named filters) - bti->reset(); - - auto errata = parse_yaml_remap_rule(rule, bti); - if (!errata.is_ok()) { - CfgLoadLog(ctx, DL_Error, "Failed to parse remap rule"); - return false; - } - } - - IpAllow::enableAcceptCheck(bti->accept_check_p); - - Dbg(dbg_ctl_remap_yaml, "Successfully parsed remap.yaml config"); - return true; - - } catch (YAML::Exception &ex) { - CfgLoadLog(ctx, DL_Error, "YAML parsing error in %s: %s", path, ex.what()); - } catch (std::exception &ex) { - CfgLoadLog(ctx, DL_Error, "Exception parsing YAML config %s: %s", path, ex.what()); - } - return false; -} - -bool -remap_parse_yaml(const char *path, UrlRewrite *rewrite, ConfigContext ctx) -{ - BUILD_TABLE_INFO bti; - - /* If this happens to be a config reload, the list of loaded remap plugins is non-empty, and we - * can signal all these plugins that a reload has begun. */ - rewrite->pluginFactory.indicatePreReload(); - - bti.rewrite = rewrite; - bool status = remap_parse_yaml_bti(path, &bti, ctx); - - /* Now after we parsed the configuration and (re)loaded plugins and plugin instances - * accordingly notify all plugins that we are done */ - rewrite->pluginFactory.indicatePostReload(status); - - bti.clear_acl_rules_list(); - - return status; -} diff --git a/src/proxy/http/remap/UrlRewrite.cc b/src/proxy/http/remap/UrlRewrite.cc index fbda217462b..8d8f40d4e86 100644 --- a/src/proxy/http/remap/UrlRewrite.cc +++ b/src/proxy/http/remap/UrlRewrite.cc @@ -23,9 +23,7 @@ */ #include "proxy/http/remap/UrlRewrite.h" -#include "proxy/http/remap/RemapYamlConfig.h" #include "iocore/eventsystem/ConfigProcessor.h" -#include "mgmt/config/ConfigContextDiags.h" #include "proxy/ReverseProxy.h" #include "tscore/Layout.h" #include "tscore/Filenames.h" @@ -77,23 +75,14 @@ UrlRewrite::get_acl_behavior_policy(ACLBehaviorPolicy &policy) } bool -UrlRewrite::load(ConfigContext ctx) +UrlRewrite::load() { ats_scoped_str config_file_path; - // Try remap.yaml first - config_file_path = RecConfigReadConfigPath("proxy.config.url_remap_yaml.filename", ts::filename::REMAP_YAML); - this->_remap_yaml = true; - - if (!config_file_path || !swoc::file::exists(swoc::file::path(config_file_path))) { - CfgLoadLog(ctx, DL_Note, "%s failed to load, fall back to %s", ts::filename::REMAP_YAML, ts::filename::REMAP); - // Fall back to remap.config if remap.yaml not found - this->_remap_yaml = false; - config_file_path = RecConfigReadConfigPath("proxy.config.url_remap.filename", ts::filename::REMAP); - if (!config_file_path) { - CfgLoadLog(ctx, DL_Warning, "%s Unable to locate %s. No remappings in effect", modulePrefix, ts::filename::REMAP); - return false; - } + config_file_path = RecConfigReadConfigPath("proxy.config.url_remap.filename", ts::filename::REMAP); + if (!config_file_path) { + Warning("%s Unable to locate %s. No remappings in effect", modulePrefix, ts::filename::REMAP); + return false; } this->ts_name = nullptr; @@ -101,7 +90,7 @@ UrlRewrite::load(ConfigContext ctx) this->ts_name = ats_stringdup(rec_str); } if (this->ts_name == nullptr) { - CfgLoadLog(ctx, DL_Warning, "%s Unable to determine proxy name. Incorrect redirects could be generated", modulePrefix); + Warning("%s Unable to determine proxy name. Incorrect redirects could be generated", modulePrefix); this->ts_name = ats_strdup(""); } @@ -110,7 +99,7 @@ UrlRewrite::load(ConfigContext ctx) this->http_default_redirect_url = ats_stringdup(rec_str); } if (this->http_default_redirect_url == nullptr) { - CfgLoadLog(ctx, DL_Warning, "%s Unable to determine default redirect url for \"referer\" filter.", modulePrefix); + Warning("%s Unable to determine default redirect url for \"referer\" filter.", modulePrefix); this->http_default_redirect_url = ats_strdup("http://www.apache.org"); } @@ -130,7 +119,7 @@ UrlRewrite::load(ConfigContext ctx) fs::file_status status = fs::status(compilerPath, ec); if (ec || !swoc::file::is_regular_file(status)) { - CfgLoadFail(ctx, "Configured plugin compiler path '%s' is not a regular file", buf); + Error("Configured plugin compiler path '%s' is not a regular file", buf); return false; } else { // This also adds the configuration directory (etc/trafficserver) to find Cripts etc. @@ -143,22 +132,20 @@ UrlRewrite::load(ConfigContext ctx) Dbg(dbg_ctl_url_rewrite_regex, "strategyFactory file: %s", sf.c_str()); strategyFactory = new NextHopStrategyFactory(sf.c_str()); - if (TS_SUCCESS == this->BuildTable(config_file_path, ctx)) { + if (TS_SUCCESS == this->BuildTable(config_file_path)) { int n_rules = this->rule_count(); // Minimum # of rules to be considered a valid configuration. int required_rules; required_rules = RecGetRecordInt("proxy.config.url_remap.min_rules_required").value_or(0); - Dbg(dbg_ctl_url_rewrite, "n_rules: %d, required_rules: %d", n_rules, required_rules); if (n_rules >= required_rules) { _valid = true; if (dbg_ctl_url_rewrite.on()) { Print(); } } else { - CfgLoadLog(ctx, DL_Warning, "%s %d rules defined but %d rules required, configuration is invalid.", modulePrefix, n_rules, - required_rules); + Warning("%s %d rules defined but %d rules required, configuration is invalid.", modulePrefix, n_rules, required_rules); } } else { - CfgLoadLog(ctx, DL_Warning, "something failed during BuildTable() -- check your remap plugins!"); + Warning("something failed during BuildTable() -- check your remap plugins!"); } // ACL Matching Policy @@ -817,7 +804,7 @@ UrlRewrite::InsertForwardMapping(mapping_type maptype, url_mapping *mapping, con */ int -UrlRewrite::BuildTable(const char *path, ConfigContext ctx) +UrlRewrite::BuildTable(const char *path) { ink_assert(forward_mappings.empty()); ink_assert(reverse_mappings.empty()); @@ -836,14 +823,7 @@ UrlRewrite::BuildTable(const char *path, ConfigContext ctx) temporary_redirects.hash_lookup.reset(new URLTable); forward_mappings_with_recv_port.hash_lookup.reset(new URLTable); - bool parse_success; - if (is_remap_yaml()) { - parse_success = remap_parse_yaml(path, this, ctx); - } else { - parse_success = remap_parse_config(path, this, ctx); - } - - if (!parse_success) { + if (!remap_parse_config(path, this)) { return TS_ERROR; } @@ -1100,35 +1080,3 @@ UrlRewrite::_destroyList(RegexMappingList &mappings) } mappings.clear(); } - -/** - * Convert a YAML rule type string to a mapping_type enum. - */ -mapping_type -get_mapping_type(const char *type_str, BUILD_TABLE_INFO *bti) -{ - // Check to see whether is a reverse or forward mapping - if (!strcasecmp("reverse_map", type_str)) { - Dbg(dbg_ctl_url_rewrite, "[BuildTable] - mapping_type::REVERSE_MAP"); - return mapping_type::REVERSE_MAP; - } else if (!strcasecmp("map", type_str)) { - Dbg(dbg_ctl_url_rewrite, "[BuildTable] - %s", - ((bti->remap_optflg & REMAP_OPTFLG_MAP_WITH_REFERER) == 0) ? "mapping_type::FORWARD_MAP" : - "mapping_type::FORWARD_MAP_REFERER"); - return ((bti->remap_optflg & REMAP_OPTFLG_MAP_WITH_REFERER) == 0) ? mapping_type::FORWARD_MAP : - mapping_type::FORWARD_MAP_REFERER; - } else if (!strcasecmp("redirect", type_str)) { - Dbg(dbg_ctl_url_rewrite, "[BuildTable] - mapping_type::PERMANENT_REDIRECT"); - return mapping_type::PERMANENT_REDIRECT; - } else if (!strcasecmp("redirect_temporary", type_str)) { - Dbg(dbg_ctl_url_rewrite, "[BuildTable] - mapping_type::TEMPORARY_REDIRECT"); - return mapping_type::TEMPORARY_REDIRECT; - } else if (!strcasecmp("map_with_referer", type_str)) { - Dbg(dbg_ctl_url_rewrite, "[BuildTable] - mapping_type::FORWARD_MAP_REFERER"); - return mapping_type::FORWARD_MAP_REFERER; - } else if (!strcasecmp("map_with_recv_port", type_str)) { - Dbg(dbg_ctl_url_rewrite, "[BuildTable] - mapping_type::FORWARD_MAP_WITH_RECV_PORT"); - return mapping_type::FORWARD_MAP_WITH_RECV_PORT; - } - return mapping_type::NONE; -} diff --git a/src/proxy/http/remap/unit-tests/CMakeLists.txt b/src/proxy/http/remap/unit-tests/CMakeLists.txt index 6b8769721a7..4de87e8f08d 100644 --- a/src/proxy/http/remap/unit-tests/CMakeLists.txt +++ b/src/proxy/http/remap/unit-tests/CMakeLists.txt @@ -201,20 +201,10 @@ target_compile_definitions( target_include_directories(test_NextHopStrategyFactory PRIVATE ${PROJECT_SOURCE_DIR}/tests/include) target_link_libraries( - test_NextHopStrategyFactory - PRIVATE Catch2::Catch2WithMain - ts::hdrs - ts::inkutils - tscore - libswoc::libswoc - yaml-cpp::yaml-cpp - configmanager + test_NextHopStrategyFactory PRIVATE Catch2::Catch2WithMain ts::hdrs ts::inkutils tscore libswoc::libswoc + yaml-cpp::yaml-cpp ) -if(NOT APPLE) - target_link_options(test_NextHopStrategyFactory PRIVATE -Wl,--allow-multiple-definition) -endif() - add_catch2_test(NAME test_NextHopStrategyFactory COMMAND $) ### test_NextHopRoundRobin ######################################################################## @@ -244,13 +234,8 @@ target_link_libraries( tscore libswoc::libswoc yaml-cpp::yaml-cpp - configmanager ) -if(NOT APPLE) - target_link_options(test_NextHopRoundRobin PRIVATE -Wl,--allow-multiple-definition) -endif() - add_catch2_test(NAME test_NextHopRoundRobin COMMAND $) ### test_NextHopConsistentHash ######################################################################## @@ -282,13 +267,8 @@ target_link_libraries( ts::inkutils libswoc::libswoc yaml-cpp::yaml-cpp - configmanager ) -if(NOT APPLE) - target_link_options(test_NextHopConsistentHash PRIVATE -Wl,--allow-multiple-definition) -endif() - add_catch2_test(NAME test_NextHopConsistentHash COMMAND $) ### test_RemapRules ######################################################################## @@ -308,21 +288,3 @@ target_link_libraries( ) add_catch2_test(NAME test_RemapRules COMMAND $) - -### test_RemapRulesYaml ######################################################################## -add_executable(test_RemapRulesYaml "${PROJECT_SOURCE_DIR}/src/iocore/cache/unit_tests/stub.cc" test_RemapRulesYaml.cc) - -target_link_libraries( - test_RemapRulesYaml - PRIVATE Catch2::Catch2WithMain - ts::http - ts::hdrs # transitive - logging # transitive - ts::http_remap # transitive - ts::proxy - inkdns # transitive - ts::inknet - ts::jsonrpc_protocol -) - -add_catch2_test(NAME test_RemapRulesYaml COMMAND $) diff --git a/src/proxy/http/remap/unit-tests/test_RemapRulesYaml.cc b/src/proxy/http/remap/unit-tests/test_RemapRulesYaml.cc deleted file mode 100644 index 513982370c4..00000000000 --- a/src/proxy/http/remap/unit-tests/test_RemapRulesYaml.cc +++ /dev/null @@ -1,262 +0,0 @@ -/** @file - - Unit tests for a class that deals with remap rules - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - @section details Details - - Implements code necessary for Reverse Proxy which mostly consists of - general purpose hostname substitution in URLs. - - */ - -#include "proxy/hdrs/HdrHeap.h" -#include "proxy/http/remap/RemapYamlConfig.h" -#include "proxy/http/remap/UrlMapping.h" -#include "proxy/http/remap/UrlRewrite.h" -#include "records/RecordsConfig.h" -#include "swoc/swoc_file.h" -#include "ts/apidefs.h" -#include "tscore/BaseLogFile.h" -#include "tsutil/PostScript.h" - -#include -#include -#include - -#include /* catch unit-test framework */ -#include -#include -#include - -struct TestListener : Catch::EventListenerBase { - using EventListenerBase::EventListenerBase; - - void - testRunStarting(Catch::TestRunInfo const & /* testRunInfo ATS_UNUSED */) override - { - Thread *main_thread = new EThread(); - main_thread->set_specific(); - - DiagsPtr::set(new Diags("test_RemapRulesYaml", "*", "", new BaseLogFile("stderr"))); - diags()->show_location = SHOW_LOCATION_DEBUG; - - url_init(); - mime_init(); - http_init(); - Layout::create(); - RecProcessInit(diags()); - LibRecordsConfigInit(); - } -}; - -CATCH_REGISTER_LISTENER(TestListener); - -swoc::file::path -write_test_remap(const std::string &config, const std::string &tag) -{ - auto tmpdir = swoc::file::temp_directory_path(); - auto path = tmpdir / swoc::file::path(tag + ".yaml"); - - std::ofstream f(path.c_str(), std::ios::trunc); - f.write(config.data(), config.size()); - f.close(); - - return path; -} - -SCENARIO("Parsing ACL named filters", "[proxy][remap]") -{ - GIVEN("Named filter definitions with multiple actions") - { - BUILD_TABLE_INFO bti{}; - ts::PostScript acl_rules_defer([&]() -> void { bti.clear_acl_rules_list(); }); - UrlRewrite rewrite{}; - - bti.rewrite = &rewrite; - - WHEN("filter rule definition has multiple @action") - { - std::string config = R"RMCFG( - remap: - - define_filter: - deny_methods: - action: [deny, allow] - method: [CONNECT, PUT, DELETE] - )RMCFG"; - auto cpath = write_test_remap(config, "test2"); - ts::PostScript file_cleanup([&]() -> void { std::filesystem::remove(cpath.c_str()); }); - THEN("The remap parse fails with an error") - { - REQUIRE(remap_parse_yaml_bti(cpath.c_str(), &bti) == false); - } - } - - WHEN("filter rule redefine has multiple @action") - { - std::string config = R"RMCFG( - remap: - - define_filter: - deny_methods: - action: deny - method: CONNECT - deny_methods: - action: allow - method: [PUT, DELETE] - )RMCFG"; - auto cpath = write_test_remap(config, "test2"); - ts::PostScript file_cleanup([&]() -> void { std::filesystem::remove(cpath.c_str()); }); - - THEN("The rule uses the first action specified") - { - REQUIRE(remap_parse_yaml_bti(cpath.c_str(), &bti) == true); - REQUIRE((bti.rules_list != nullptr && bti.rules_list->next == nullptr)); - REQUIRE((bti.rules_list != nullptr && bti.rules_list->allow_flag == false)); - ; - } - } - } -} - -struct EasyURL { - URL url; - HdrHeap *heap; - - EasyURL(std::string_view s) - { - heap = new_HdrHeap(); - url.create(heap); - url.parse(s); - } - ~EasyURL() { heap->destroy(); } -}; - -SCENARIO("Parsing UrlRewrite", "[proxy][remap]") -{ - GIVEN("A named remap rule without ips") - { - std::unique_ptr urlrw = std::make_unique(); - urlrw->set_remap_yaml(true); - - std::string config = R"RMCFG( - remap: - - define_filter: - deny_methods: - action: deny - method: [CONNECT, PUT, DELETE] - - activate_filter: deny_methods - - type: map - from: - url: https://h1.example.com - to: - url: https://h2.example.com - - deactivate_filter: deny_methods - )RMCFG"; - - auto cpath = write_test_remap(config, "test1"); - ts::PostScript file_cleanup([&]() -> void { std::filesystem::remove(cpath.c_str()); }); - - printf("wrote config to path: %s\n", cpath.c_str()); - int rc = urlrw->BuildTable(cpath.c_str()); - EasyURL url("https://h1.example.com"); - const char *host = "h1.example.com"; - - THEN("the remap rules has an ip=all") - { - REQUIRE(rc == TS_SUCCESS); - REQUIRE(urlrw->rule_count() == 1); - UrlMappingContainer urlmap; - - REQUIRE(urlrw->forwardMappingLookup(&url.url, 443, host, strlen(host), urlmap)); - REQUIRE(urlmap.getMapping()->filter); - REQUIRE(urlmap.getMapping()->filter->src_ip_cnt == 1); - REQUIRE(urlmap.getMapping()->filter->src_ip_valid); - REQUIRE(urlmap.getMapping()->filter->src_ip_array[0].match_all_addresses); - } - } - GIVEN("map_with_recv_port keyword with a special URL scheme for Unix Domain Socket") - { - std::unique_ptr urlrw = std::make_unique(); - urlrw->set_remap_yaml(true); - - std::string config = R"RMCFG( - remap: - - type: map_with_recv_port - from: - url: http+unix://front.example.com - to: - url: http://origin.example.com - )RMCFG"; - - auto cpath = write_test_remap(config, "unix-scheme"); - ts::PostScript file_cleanup([&]() -> void { std::filesystem::remove(cpath.c_str()); }); - printf("wrote config to path: %s\n", cpath.c_str()); - int rc = urlrw->BuildTable(cpath.c_str()); - EasyURL url("http+unix://front.example.com"); - const char *host = "front.example.com"; - - THEN("only requests via unix domain socket matches") - { - // Checck if the rule is loaded - REQUIRE(rc == TS_SUCCESS); - REQUIRE(urlrw->rule_count() == 1); - UrlMappingContainer urlmap; - - // The rule must not match if a port number is available (the request is made on IP interface) - REQUIRE(urlrw->forwardMappingWithRecvPortLookup(&url.url, 80, host, strlen(host), urlmap) == false); - // The rule must match if a port number is unavailable (the request is made on Unix Domain Socket) - REQUIRE(urlrw->forwardMappingWithRecvPortLookup(&url.url, 0, host, strlen(host), urlmap) == true); - } - } - GIVEN("map_with_recv_port keyword with a regular URL scheme") - { - std::unique_ptr urlrw = std::make_unique(); - urlrw->set_remap_yaml(true); - - std::string config = R"RMCFG( - remap: - - type: map_with_recv_port - from: - url: http://front.example.com - to: - url: http://origin.example.com - )RMCFG"; - - auto cpath = write_test_remap(config, "regular-scheme"); - ts::PostScript file_cleanup([&]() -> void { std::filesystem::remove(cpath.c_str()); }); - printf("wrote config to path: %s\n", cpath.c_str()); - int rc = urlrw->BuildTable(cpath.c_str()); - EasyURL url("http://front.example.com"); - const char *host = "front.example.com"; - - THEN("only request via IP interface matches") - { - // Checck if the rule is loaded - REQUIRE(rc == TS_SUCCESS); - REQUIRE(urlrw->rule_count() == 1); - UrlMappingContainer urlmap; - - // The rule must match if a port number is available (the request is made on IP interface) - REQUIRE(urlrw->forwardMappingWithRecvPortLookup(&url.url, 80, host, strlen(host), urlmap) == true); - // The rule must not match if a port number is unavailable (the request is made on Unix Domain Socket) - REQUIRE(urlrw->forwardMappingWithRecvPortLookup(&url.url, 0, host, strlen(host), urlmap) == false); - } - } -} diff --git a/src/proxy/http2/CMakeLists.txt b/src/proxy/http2/CMakeLists.txt index b302a0dcbec..e769fe36225 100644 --- a/src/proxy/http2/CMakeLists.txt +++ b/src/proxy/http2/CMakeLists.txt @@ -49,7 +49,7 @@ if(BUILD_TESTING) unit_tests/test_Http2Frame.cc unit_tests/test_HpackIndexingTable.cc ) - target_link_libraries(test_http2 PRIVATE Catch2::Catch2WithMain records tscore hdrs inkevent configmanager) + target_link_libraries(test_http2 PRIVATE Catch2::Catch2WithMain records tscore hdrs inkevent) add_catch2_test(NAME test_http2 COMMAND test_http2) add_executable(test_Http2DependencyTree unit_tests/test_Http2DependencyTree.cc) @@ -57,7 +57,7 @@ if(BUILD_TESTING) add_catch2_test(NAME test_Http2DependencyTree COMMAND test_Http2DependencyTree) add_executable(test_HPACK test_HPACK.cc HPACK.cc) - target_link_libraries(test_HPACK PRIVATE tscore hdrs inkevent configmanager) + target_link_libraries(test_HPACK PRIVATE tscore hdrs inkevent) add_test(NAME test_HPACK COMMAND test_HPACK -i ${CMAKE_CURRENT_SOURCE_DIR}/hpack-tests -o ./results) endif() diff --git a/src/proxy/logging/LogConfig.cc b/src/proxy/logging/LogConfig.cc index 0aa16221db1..dfa20f7d512 100644 --- a/src/proxy/logging/LogConfig.cc +++ b/src/proxy/logging/LogConfig.cc @@ -48,8 +48,6 @@ using namespace std::literals; #include "tscore/SimpleTokenizer.h" #include "proxy/logging/YamlLogConfig.h" -#include "mgmt/config/ConfigContextDiags.h" -#include "mgmt/config/ConfigRegistry.h" #define DISK_IS_CONFIG_FULL_MESSAGE \ "Access logging to local log directory suspended - " \ @@ -288,13 +286,6 @@ LogConfig::init(LogConfig *prev_config) ink_assert(!initialized); - // Inherit the reload context so evaluate_config() can log parse details - // and access RPC-supplied YAML content. At startup prev_config is nullptr, - // so reload_ctx stays default-constructed (all calls are safe no-ops). - if (prev_config) { - reload_ctx = prev_config->reload_ctx; - } - update_space_used(); // create log objects @@ -420,13 +411,13 @@ LogConfig::setup_log_objects() function from the logging thread. -------------------------------------------------------------------------*/ -void -LogConfig::reconfigure(ConfigContext ctx) +int +LogConfig::reconfigure(const char * /* name ATS_UNUSED */, RecDataT /* data_type ATS_UNUSED */, RecData /* data ATS_UNUSED */, + void * /* cookie ATS_UNUSED */) { - CfgLoadDbg(ctx, dbg_ctl_log_config, "Reconfiguration request accepted"); - + Dbg(dbg_ctl_log_config, "Reconfiguration request accepted"); Log::config->reconfiguration_needed = true; - Log::config->reload_ctx = ctx; + return 0; } /*------------------------------------------------------------------------- @@ -464,13 +455,8 @@ LogConfig::register_config_callbacks() "proxy.config.diags.debug.throttling_interval_msec", }; - auto ®istry = config::ConfigRegistry::Get_Instance(); - registry.register_config( - "logging", ts::filename::LOGGING, "proxy.config.log.config.filename", [](ConfigContext ctx) { LogConfig::reconfigure(ctx); }, - config::ConfigSource::FileOnly); - for (unsigned i = 0; i < countof(names); ++i) { - registry.attach("logging", names[i]); + RecRegisterConfigUpdateCb(names[i], &LogConfig::reconfigure, nullptr); } } @@ -775,8 +761,7 @@ LogConfig::evaluate_config() ats_scoped_str path(RecConfigReadConfigPath("proxy.config.log.config.filename", ts::filename::LOGGING)); struct stat sbuf; if (stat(path.get(), &sbuf) == -1 && errno == ENOENT) { - // File doesn't exist — not a failure; ATS uses default logging. - CfgLoadComplete(reload_ctx, "logging configuration '%s' doesn't exist, using defaults", path.get()); + Warning("logging configuration '%s' doesn't exist", path.get()); return false; } @@ -785,9 +770,9 @@ LogConfig::evaluate_config() bool zret = y.parse(path.get()); if (zret) { - CfgLoadComplete(reload_ctx, "%s finished loading", path.get()); + Note("%s finished loading", path.get()); } else { - CfgLoadFail(reload_ctx, "%s failed to load", path.get()); + Note("%s failed to load", path.get()); } return zret; diff --git a/src/proxy/logging/YamlLogConfig.cc b/src/proxy/logging/YamlLogConfig.cc index ee88b2f724c..e554e8f9666 100644 --- a/src/proxy/logging/YamlLogConfig.cc +++ b/src/proxy/logging/YamlLogConfig.cc @@ -22,7 +22,6 @@ #include "proxy/logging/YamlLogConfig.h" #include "proxy/logging/YamlLogConfigDecoders.h" -#include "mgmt/config/ConfigContextDiags.h" #include "proxy/logging/LogConfig.h" #include "proxy/logging/LogObject.h" @@ -46,7 +45,7 @@ YamlLogConfig::parse(const char *cfgFilename) try { result = loadLogConfig(cfgFilename); } catch (std::exception &ex) { - CfgLoadLog(cfg->reload_ctx, DL_Error, "%s", ex.what()); + Error("%s", ex.what()); result = false; } return result; @@ -62,14 +61,14 @@ YamlLogConfig::loadLogConfig(const char *cfgFilename) } if (!config.IsMap()) { - CfgLoadLog(cfg->reload_ctx, DL_Error, "malformed %s file; expected a map", cfgFilename); + Error("malformed %s file; expected a map", cfgFilename); return false; } if (config["logging"]) { config = config["logging"]; } else { - CfgLoadLog(cfg->reload_ctx, DL_Error, "malformed %s file; expected a toplevel 'logging' node", cfgFilename); + Error("malformed %s file; expected a toplevel 'logging' node", cfgFilename); return false; } diff --git a/src/proxy/unit_tests/CMakeLists.txt b/src/proxy/unit_tests/CMakeLists.txt index 72afd432f98..b21fb327155 100644 --- a/src/proxy/unit_tests/CMakeLists.txt +++ b/src/proxy/unit_tests/CMakeLists.txt @@ -16,8 +16,7 @@ ####################### add_executable( - test_proxy main.cc test_ParentHashConfig.cc test_PluginYAML.cc - "${PROJECT_SOURCE_DIR}/src/iocore/net/libinknet_stub.cc" stub.cc + test_proxy main.cc test_ParentHashConfig.cc "${PROJECT_SOURCE_DIR}/src/iocore/net/libinknet_stub.cc" stub.cc ) target_link_libraries(test_proxy PRIVATE Catch2::Catch2WithMain ts::http ts::proxy ts::tscore ts::records ts::inkevent) diff --git a/src/proxy/unit_tests/test_PluginYAML.cc b/src/proxy/unit_tests/test_PluginYAML.cc deleted file mode 100644 index 4df57fa4964..00000000000 --- a/src/proxy/unit_tests/test_PluginYAML.cc +++ /dev/null @@ -1,324 +0,0 @@ -/** @file - - Unit tests for plugin.yaml parsing - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include -#include -#include -#include - -#include "proxy/Plugin.h" - -namespace -{ -class TempYAML -{ -public: - explicit TempYAML(const std::string &content) - { - _path = std::filesystem::temp_directory_path() / "test_plugin_yaml.yaml"; - std::ofstream f(_path); - f << content; - } - - ~TempYAML() { std::filesystem::remove(_path); } - - const char * - path() const - { - return _path.c_str(); - } - -private: - std::filesystem::path _path; -}; -} // namespace - -TEST_CASE("parse_plugin_yaml - minimal valid config", "[plugin_yaml]") -{ - TempYAML yaml(R"( -plugins: - - path: stats_over_http.so -)"); - - auto result = parse_plugin_yaml(yaml.path()); - - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 1); - CHECK(result.value[0].path == "stats_over_http.so"); - CHECK(result.value[0].enabled == true); - CHECK(result.value[0].load_order == -1); - CHECK(result.value[0].params.empty()); - CHECK(result.value[0].config_literal.empty()); -} - -TEST_CASE("parse_plugin_yaml - all fields populated", "[plugin_yaml]") -{ - TempYAML yaml(R"( -plugins: - - path: abuse.so - enabled: true - load_order: 100 - params: - - etc/trafficserver/abuse.config - - --verbose - - --debug -)"); - - auto result = parse_plugin_yaml(yaml.path()); - - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 1); - CHECK(result.value[0].path == "abuse.so"); - CHECK(result.value[0].enabled == true); - CHECK(result.value[0].load_order == 100); - REQUIRE(result.value[0].params.size() == 3); - CHECK(result.value[0].params[0] == "etc/trafficserver/abuse.config"); - CHECK(result.value[0].params[1] == "--verbose"); - CHECK(result.value[0].params[2] == "--debug"); -} - -TEST_CASE("parse_plugin_yaml - enabled false", "[plugin_yaml]") -{ - TempYAML yaml(R"( -plugins: - - path: debug_plugin.so - enabled: false - - path: stats_over_http.so -)"); - - auto result = parse_plugin_yaml(yaml.path()); - - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 2); - CHECK(result.value[0].enabled == false); - CHECK(result.value[1].enabled == true); -} - -TEST_CASE("parse_plugin_yaml - load_order sorting", "[plugin_yaml]") -{ - TempYAML yaml(R"( -plugins: - - path: third.so - load_order: 300 - - path: first.so - load_order: 100 - - path: second.so - load_order: 200 -)"); - - auto result = parse_plugin_yaml(yaml.path()); - - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 3); - CHECK(result.value[0].path == "first.so"); - CHECK(result.value[1].path == "second.so"); - CHECK(result.value[2].path == "third.so"); -} - -TEST_CASE("parse_plugin_yaml - ordered before unordered", "[plugin_yaml]") -{ - TempYAML yaml(R"( -plugins: - - path: unordered_first.so - - path: unordered_second.so - - path: ordered.so - load_order: 50 -)"); - - auto result = parse_plugin_yaml(yaml.path()); - - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 3); - CHECK(result.value[0].path == "ordered.so"); - CHECK(result.value[1].path == "unordered_first.so"); - CHECK(result.value[2].path == "unordered_second.so"); -} - -TEST_CASE("parse_plugin_yaml - stable sort preserves sequence order on ties", "[plugin_yaml]") -{ - TempYAML yaml(R"( -plugins: - - path: b.so - load_order: 100 - - path: a.so - load_order: 100 - - path: c.so - load_order: 100 -)"); - - auto result = parse_plugin_yaml(yaml.path()); - - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 3); - CHECK(result.value[0].path == "b.so"); - CHECK(result.value[1].path == "a.so"); - CHECK(result.value[2].path == "c.so"); -} - -TEST_CASE("parse_plugin_yaml - inline config literal (scalar)", "[plugin_yaml]") -{ - TempYAML yaml(R"( -plugins: - - path: header_rewrite.so - config: | - cond %{SEND_RESPONSE_HDR_HOOK} - set-header X-Debug "true" -)"); - - auto result = parse_plugin_yaml(yaml.path()); - - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 1); - CHECK(result.value[0].path == "header_rewrite.so"); - CHECK(result.value[0].config_literal.find("set-header X-Debug") != std::string::npos); -} - -TEST_CASE("parse_plugin_yaml - inline config rejects structured YAML mapping", "[plugin_yaml]") -{ - TempYAML yaml(R"( -plugins: - - path: txn_box.so - config: - when: proxy-req - do: - - set-header: - name: X-Forwarded-For - value: inbound-addr-remote -)"); - - auto result = parse_plugin_yaml(yaml.path()); - - REQUIRE_FALSE(result.ok()); -} - -TEST_CASE("parse_plugin_yaml - inline config rejects structured YAML sequence", "[plugin_yaml]") -{ - TempYAML yaml(R"( -plugins: - - path: custom.so - config: - - rule1 - - rule2 - - rule3 -)"); - - auto result = parse_plugin_yaml(yaml.path()); - - REQUIRE_FALSE(result.ok()); -} - -TEST_CASE("parse_plugin_yaml - missing path field", "[plugin_yaml]") -{ - TempYAML yaml(R"( -plugins: - - enabled: true -)"); - - auto result = parse_plugin_yaml(yaml.path()); - - REQUIRE_FALSE(result.ok()); - CHECK(std::string(result.errata.front().text()).find("missing required 'path' field") != std::string::npos); -} - -TEST_CASE("parse_plugin_yaml - missing plugins key", "[plugin_yaml]") -{ - TempYAML yaml(R"( -something_else: - - path: foo.so -)"); - - auto result = parse_plugin_yaml(yaml.path()); - - REQUIRE_FALSE(result.ok()); - CHECK(std::string(result.errata.front().text()).find("missing or invalid 'plugins' sequence") != std::string::npos); -} - -TEST_CASE("parse_plugin_yaml - invalid YAML syntax", "[plugin_yaml]") -{ - TempYAML yaml("plugins:\n - path: foo.so\n bad indent here\n"); - - auto result = parse_plugin_yaml(yaml.path()); - - REQUIRE_FALSE(result.ok()); - CHECK(std::string(result.errata.front().text()).find("failed to parse") != std::string::npos); -} - -TEST_CASE("parse_plugin_yaml - empty plugins list", "[plugin_yaml]") -{ - TempYAML yaml(R"( -plugins: [] -)"); - - auto result = parse_plugin_yaml(yaml.path()); - - REQUIRE(result.ok()); - CHECK(result.value.empty()); -} - -TEST_CASE("parse_plugin_yaml - multiple plugins mixed features", "[plugin_yaml]") -{ - TempYAML yaml(R"( -plugins: - - path: stats_over_http.so - - - path: abuse.so - params: - - etc/trafficserver/abuse.config - - - path: header_rewrite.so - params: - - etc/trafficserver/header_rewrite.config - - - path: experimental.so - enabled: false - params: - - --verbose -)"); - - auto result = parse_plugin_yaml(yaml.path()); - - REQUIRE(result.ok()); - REQUIRE(result.value.size() == 4); - - CHECK(result.value[0].path == "stats_over_http.so"); - CHECK(result.value[0].params.empty()); - - CHECK(result.value[1].path == "abuse.so"); - REQUIRE(result.value[1].params.size() == 1); - CHECK(result.value[1].params[0] == "etc/trafficserver/abuse.config"); - - CHECK(result.value[2].path == "header_rewrite.so"); - REQUIRE(result.value[2].params.size() == 1); - CHECK(result.value[2].params[0] == "etc/trafficserver/header_rewrite.config"); - - CHECK(result.value[3].path == "experimental.so"); - CHECK(result.value[3].enabled == false); -} - -TEST_CASE("parse_plugin_yaml - nonexistent file", "[plugin_yaml]") -{ - auto result = parse_plugin_yaml("/tmp/nonexistent_plugin_yaml_test.yaml"); - - REQUIRE_FALSE(result.ok()); - CHECK(std::string(result.errata.front().text()).find("failed to parse") != std::string::npos); -} diff --git a/src/records/CMakeLists.txt b/src/records/CMakeLists.txt index ac93f681737..7cdabdbee20 100644 --- a/src/records/CMakeLists.txt +++ b/src/records/CMakeLists.txt @@ -30,32 +30,21 @@ add_library( RecordsConfig.cc RecordsConfigUtils.cc RecRawStats.cc - # Reload infrastructure - ${CMAKE_SOURCE_DIR}/src/mgmt/config/ReloadCoordinator.cc - ${CMAKE_SOURCE_DIR}/src/mgmt/config/ConfigReloadTrace.cc - ${CMAKE_SOURCE_DIR}/src/mgmt/config/ConfigContext.cc ) add_library(ts::records ALIAS records) target_link_libraries( records - PUBLIC ts::tscore yaml-cpp::yaml-cpp ts::inkevent + PUBLIC ts::tscore yaml-cpp::yaml-cpp PRIVATE ts::tsutil ) if(BUILD_TESTING) add_executable( - test_records - unit_tests/unit_test_main.cc - unit_tests/test_RecHttp.cc - unit_tests/test_RecUtils.cc - unit_tests/test_RecRegister.cc - unit_tests/test_ConfigReloadTask.cc - unit_tests/test_ConfigRegistry.cc - unit_tests/test_ReloadDirectives.cc - unit_tests/test_RecDumpRecords.cc + test_records unit_tests/unit_test_main.cc unit_tests/test_RecHttp.cc unit_tests/test_RecUtils.cc + unit_tests/test_RecRegister.cc unit_tests/test_RecDumpRecords.cc ) - target_link_libraries(test_records PRIVATE records configmanager inkevent Catch2::Catch2 ts::tscore libswoc::libswoc) + target_link_libraries(test_records PRIVATE records Catch2::Catch2 ts::tscore libswoc::libswoc ts::inkevent) add_catch2_test(NAME test_records COMMAND test_records) endif() diff --git a/src/records/P_RecCore.cc b/src/records/P_RecCore.cc index 40e8de7e87b..ebc071d284d 100644 --- a/src/records/P_RecCore.cc +++ b/src/records/P_RecCore.cc @@ -544,9 +544,3 @@ RecSetSyncRequired(const char *name, bool lock) return err; } - -void -RecFlushConfigUpdateCbs() -{ - RecExecConfigUpdateCbs(REC_PROCESS_UPDATE_REQUIRED); -} diff --git a/src/records/RecCore.cc b/src/records/RecCore.cc index c5daa449bf6..65cb7e578e8 100644 --- a/src/records/RecCore.cc +++ b/src/records/RecCore.cc @@ -1079,20 +1079,16 @@ RecConfigReadPersistentStatsPath() //------------------------------------------------------------------------- /// Generate a warning if the record is a configuration name/value but is not registered. void -RecConfigWarnIfUnregistered(ConfigContext ctx) +RecConfigWarnIfUnregistered() { RecDumpRecords( RECT_CONFIG, - [](RecT, void *edata, int registered_p, const char *name, int, RecData *) -> void { + [](RecT, void *, int registered_p, const char *name, int, RecData *) -> void { if (!registered_p) { - std::string err; - swoc::bwprint(err, "Unrecognized configuration value '{}'", name); - Warning("%s", err.c_str()); - auto *ctx_ptr = static_cast(edata); - ctx_ptr->log(err); + Warning("Unrecognized configuration value '%s'", name); } }, - &ctx); + nullptr); } //------------------------------------------------------------------------- diff --git a/src/records/RecordsConfig.cc b/src/records/RecordsConfig.cc index 4586f039a2d..83e2c90f52e 100644 --- a/src/records/RecordsConfig.cc +++ b/src/records/RecordsConfig.cc @@ -263,12 +263,6 @@ static constexpr RecordElement RecordsConfig[] = //############################################################################## {RECT_CONFIG, "proxy.config.admin.user_id", RECD_STRING, TS_PKGSYSUSER, RECU_NULL, RR_REQUIRED, RECC_NULL, nullptr, RECA_READ_ONLY} , - //# Config reload timeout - supports duration strings: "30s", "5min", "1h", "0" (disabled) - {RECT_CONFIG, "proxy.config.admin.reload.timeout", RECD_STRING, "1h", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} - , - //# Config reload check interval - how often to check task progress (min: 1s) - {RECT_CONFIG, "proxy.config.admin.reload.check_interval", RECD_STRING, "2s", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} - , //############################################################################## //# //# UDP configuration stuff: hidden variables @@ -653,7 +647,7 @@ static constexpr RecordElement RecordsConfig[] = , {RECT_CONFIG, "proxy.config.http.cache.range.write", RECD_INT, "0", RECU_NULL, RR_NULL, RECC_NULL, nullptr, RECA_NULL} , - {RECT_CONFIG, "proxy.config.http.cache.targeted_cache_control_headers", RECD_STRING, "CDN-Cache-Control", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} + {RECT_CONFIG, "proxy.config.http.cache.targeted_cache_control_headers", RECD_STRING, "", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} , // ######################## @@ -852,6 +846,8 @@ static constexpr RecordElement RecordsConfig[] = , {RECT_CONFIG, "proxy.config.cache.hosting_filename", RECD_STRING, ts::filename::HOSTING, RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} , + {RECT_CONFIG, "proxy.config.cache.volume_filename", RECD_STRING, ts::filename::VOLUME, RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL} + , {RECT_CONFIG, "proxy.config.cache.permit.pinning", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL} , // # default the ram cache size to AUTO_SIZE (-1) @@ -1111,8 +1107,6 @@ static constexpr RecordElement RecordsConfig[] = , {RECT_CONFIG, "proxy.config.url_remap.filename", RECD_STRING, ts::filename::REMAP, RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} , - {RECT_CONFIG, "proxy.config.url_remap_yaml.filename", RECD_STRING, ts::filename::REMAP_YAML, RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} - , {RECT_CONFIG, "proxy.config.url_remap.strategies.filename", RECD_STRING, "strategies.yaml", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} , {RECT_CONFIG, "proxy.config.url_remap.remap_required", RECD_INT, "1", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL} @@ -1180,9 +1174,7 @@ static constexpr RecordElement RecordsConfig[] = {RECT_CONFIG, "proxy.config.ssl.server.multicert.filename", RECD_STRING, ts::filename::SSL_MULTICERT, RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL} , {RECT_CONFIG, "proxy.config.ssl.server.multicert.exit_on_load_fail", RECD_INT, "1", RECU_RESTART_TS, RR_NULL, RECC_INT, "[0-1]", RECA_NULL} - , - {RECT_CONFIG, "proxy.config.ssl.server.multicert.concurrency", RECD_INT, "1", RECU_RESTART_TS, RR_NULL, RECC_INT, "[0-256]", RECA_NULL} - , +, {RECT_CONFIG, "proxy.config.ssl.servername.filename", RECD_STRING, ts::filename::SNI, RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL} , {RECT_CONFIG, "proxy.config.ssl.server.ticket_key.filename", RECD_STRING, nullptr, RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} @@ -1219,8 +1211,24 @@ static constexpr RecordElement RecordsConfig[] = , {RECT_CONFIG, "proxy.config.ssl.origin_session_cache.size", RECD_INT, "10240", RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL} , + {RECT_CONFIG, "proxy.config.ssl.session_cache.mode", RECD_INT, "2", RECU_RESTART_TS, RR_NULL, RECC_INT, "[0-2]", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.ssl.session_cache.enabled", RECD_INT, "2", RECU_RESTART_TS, RR_NULL, RECC_INT, "[0-2]", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.ssl.session_cache.value", RECD_INT, "2", RECU_RESTART_TS, RR_NULL, RECC_INT, "[0-2]", RECA_NULL} + , + {RECT_CONFIG, "proxy.config.ssl.session_cache.size", RECD_INT, "102400", RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL} + , + {RECT_CONFIG, "proxy.config.ssl.session_cache.num_buckets", RECD_INT, "256", RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL} + , + {RECT_CONFIG, "proxy.config.ssl.session_cache.skip_cache_on_bucket_contention", RECD_INT, "0", RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL} + , {RECT_CONFIG, "proxy.config.ssl.max_record_size", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-16383]", RECA_NULL} , + {RECT_CONFIG, "proxy.config.ssl.session_cache.timeout", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} + , + {RECT_CONFIG, "proxy.config.ssl.session_cache.auto_clear", RECD_INT, "1", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} + , {RECT_CONFIG, "proxy.config.ssl.hsts_max_age", RECD_INT, "-1", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL} , {RECT_CONFIG, "proxy.config.ssl.hsts_include_subdomains", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL} @@ -1245,10 +1253,6 @@ static constexpr RecordElement RecordsConfig[] = , {RECT_CONFIG, "proxy.config.ssl.ktls.enabled", RECD_INT, "0", RECU_RESTART_TS, RR_NULL, RECC_INT, "[0-1]", RECA_NULL} , - {RECT_CONFIG, "proxy.config.ssl.server.cert_compression.algorithms", RECD_STRING, nullptr, RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} - , - {RECT_CONFIG, "proxy.config.ssl.client.cert_compression.algorithms", RECD_STRING, nullptr, RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL} - , //############################################################################## //# //# OCSP (Online Certificate Status Protocol) Stapling Configuration diff --git a/src/records/unit_tests/test_ConfigRegistry.cc b/src/records/unit_tests/test_ConfigRegistry.cc deleted file mode 100644 index 29e26de9c40..00000000000 --- a/src/records/unit_tests/test_ConfigRegistry.cc +++ /dev/null @@ -1,207 +0,0 @@ -/** @file - - Unit tests for ConfigRegistry: resolve(), add_file_and_node_dependency(), dependency key routing. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include - -#include "mgmt/config/ConfigRegistry.h" -#include "records/RecCore.h" - -using config::ConfigRegistry; -using config::ConfigSource; - -// Shared no-op handler for test registrations -static config::ConfigReloadHandler noop_handler = [](ConfigContext) {}; - -namespace -{ -// Register test-specific records so RecRegisterConfigUpdateCb succeeds. -// Called once; RecRegisterConfigUpdateCb requires the record to exist in g_records_ht. -void -ensure_test_records() -{ - static bool done = false; - if (done) { - return; - } - done = true; - RecRegisterConfigString(RECT_CONFIG, "test.registry.dep.filename1", const_cast("test_sni.yaml"), RECU_NULL, RECC_NULL, - nullptr, REC_SOURCE_DEFAULT); - RecRegisterConfigString(RECT_CONFIG, "test.registry.dep.filename2", const_cast("test_multicert.config"), RECU_NULL, - RECC_NULL, nullptr, REC_SOURCE_DEFAULT); - RecRegisterConfigString(RECT_CONFIG, "test.registry.dep.filename3", const_cast("test_child_a.yaml"), RECU_NULL, RECC_NULL, - nullptr, REC_SOURCE_DEFAULT); - RecRegisterConfigString(RECT_CONFIG, "test.registry.dep.filename4", const_cast("test_child_b.config"), RECU_NULL, - RECC_NULL, nullptr, REC_SOURCE_DEFAULT); - RecRegisterConfigString(RECT_CONFIG, "test.registry.dep.filename5", const_cast("test_dep_for_b.yaml"), RECU_NULL, - RECC_NULL, nullptr, REC_SOURCE_DEFAULT); - RecRegisterConfigString(RECT_CONFIG, "test.registry.dep.dup", const_cast("dup.yaml"), RECU_NULL, RECC_NULL, nullptr, - REC_SOURCE_DEFAULT); -} -} // namespace - -// ─── Direct entry resolution (no Records/FileManager needed) ────────────────── - -TEST_CASE("ConfigRegistry resolve() with direct entries", "[config][registry][resolve]") -{ - auto ® = ConfigRegistry::Get_Instance(); - - // No file, no triggers — pure map operation - reg.register_config("test_direct_resolve", "", "", noop_handler, ConfigSource::FileOnly, {}); - - SECTION("Direct entry found") - { - auto [parent_key, entry] = reg.resolve("test_direct_resolve"); - REQUIRE(entry != nullptr); - REQUIRE(parent_key == "test_direct_resolve"); - REQUIRE(entry->key == "test_direct_resolve"); - } - - SECTION("Unknown key returns nullptr") - { - auto [parent_key, entry] = reg.resolve("nonexistent_key_xyz"); - REQUIRE(entry == nullptr); - REQUIRE(parent_key.empty()); - } -} - -// ─── add_file_and_node_dependency: basic ────────────────────────────────────── - -TEST_CASE("ConfigRegistry add_file_and_node_dependency resolves to parent", "[config][registry][dependency]") -{ - ensure_test_records(); - auto ® = ConfigRegistry::Get_Instance(); - - reg.register_config("test_coordinator", "", "", noop_handler, ConfigSource::FileAndRpc, {}); - - int ret = - reg.add_file_and_node_dependency("test_coordinator", "test_dep_sni", "test.registry.dep.filename1", "test_sni.yaml", false); - REQUIRE(ret == 0); - - // The dep_key resolves to the parent entry - auto [parent_key, entry] = reg.resolve("test_dep_sni"); - REQUIRE(entry != nullptr); - REQUIRE(parent_key == "test_coordinator"); - REQUIRE(entry->key == "test_coordinator"); - REQUIRE(entry->source == ConfigSource::FileAndRpc); - - // find() and contains() should NOT find dep_keys — only resolve() does - REQUIRE(reg.find("test_dep_sni") == nullptr); - REQUIRE_FALSE(reg.contains("test_dep_sni")); -} - -// ─── add_file_and_node_dependency: rejection cases ──────────────────────────── - -TEST_CASE("ConfigRegistry add_file_and_node_dependency rejects duplicates", "[config][registry][dependency]") -{ - ensure_test_records(); - auto ® = ConfigRegistry::Get_Instance(); - - reg.register_config("test_coord_dup", "", "", noop_handler, ConfigSource::FileAndRpc, {}); - - int ret1 = reg.add_file_and_node_dependency("test_coord_dup", "test_dup_dep", "test.registry.dep.dup", "dup.yaml", false); - REQUIRE(ret1 == 0); - // Same dep_key again should fail - int ret2 = reg.add_file_and_node_dependency("test_coord_dup", "test_dup_dep", "test.registry.dep.dup", "dup.yaml", false); - REQUIRE(ret2 == -1); -} - -TEST_CASE("ConfigRegistry add_file_and_node_dependency rejects dep colliding with entry", "[config][registry][dependency]") -{ - ensure_test_records(); - auto ® = ConfigRegistry::Get_Instance(); - - reg.register_config("test_coord_coll", "", "", noop_handler, ConfigSource::FileAndRpc, {}); - reg.register_config("test_collision_entry", "", "", noop_handler, ConfigSource::FileOnly, {}); - - // Dep_key same name as existing entry should fail - int ret = reg.add_file_and_node_dependency("test_coord_coll", "test_collision_entry", "test.registry.dep.filename2", - "test_multicert.config", false); - REQUIRE(ret == -1); -} - -TEST_CASE("ConfigRegistry add_file_and_node_dependency rejects unknown parent", "[config][registry][dependency]") -{ - ensure_test_records(); - auto ® = ConfigRegistry::Get_Instance(); - - int ret = reg.add_file_and_node_dependency("nonexistent_parent", "test_orphan_dep", "test.registry.dep.filename1", - "test_sni.yaml", false); - REQUIRE(ret == -1); -} - -// ─── Multiple dep_keys for same parent ──────────────────────────────────────── - -TEST_CASE("ConfigRegistry multiple dep_keys resolve to same parent", "[config][registry][dependency][grouping]") -{ - ensure_test_records(); - auto ® = ConfigRegistry::Get_Instance(); - - reg.register_config("test_multi_parent", "", "", noop_handler, ConfigSource::FileAndRpc, {}); - - int ret1 = - reg.add_file_and_node_dependency("test_multi_parent", "test_child_a", "test.registry.dep.filename3", "child_a.yaml", false); - int ret2 = - reg.add_file_and_node_dependency("test_multi_parent", "test_child_b", "test.registry.dep.filename4", "child_b.config", false); - REQUIRE(ret1 == 0); - REQUIRE(ret2 == 0); - - // Both dep_keys resolve to the same parent - auto [key_a, entry_a] = reg.resolve("test_child_a"); - auto [key_b, entry_b] = reg.resolve("test_child_b"); - - REQUIRE(entry_a != nullptr); - REQUIRE(entry_b != nullptr); - REQUIRE(key_a == "test_multi_parent"); - REQUIRE(key_b == "test_multi_parent"); - REQUIRE(entry_a == entry_b); // same Entry* - - // Parent itself still resolves directly - auto [parent_key, entry] = reg.resolve("test_multi_parent"); - REQUIRE(entry != nullptr); - REQUIRE(parent_key == "test_multi_parent"); -} - -// ─── resolve() with mixed entries and deps ──────────────────────────────────── - -TEST_CASE("ConfigRegistry resolve() does not confuse entries and deps", "[config][registry][resolve]") -{ - ensure_test_records(); - auto ® = ConfigRegistry::Get_Instance(); - - reg.register_config("test_entry_a", "", "", noop_handler, ConfigSource::FileOnly, {}); - reg.register_config("test_entry_b", "", "", noop_handler, ConfigSource::FileAndRpc, {}); - - int ret = reg.add_file_and_node_dependency("test_entry_b", "test_dep_for_b", "test.registry.dep.filename5", "dep_b.yaml", false); - REQUIRE(ret == 0); - - // Direct entry resolves to itself - auto [key_a, entry_a] = reg.resolve("test_entry_a"); - REQUIRE(entry_a != nullptr); - REQUIRE(key_a == "test_entry_a"); - - // Dep key resolves to its parent, not other entries - auto [key_b, entry_b] = reg.resolve("test_dep_for_b"); - REQUIRE(entry_b != nullptr); - REQUIRE(key_b == "test_entry_b"); - REQUIRE(entry_b->source == ConfigSource::FileAndRpc); -} diff --git a/src/records/unit_tests/test_ConfigReloadTask.cc b/src/records/unit_tests/test_ConfigReloadTask.cc deleted file mode 100644 index 9cc1acdb107..00000000000 --- a/src/records/unit_tests/test_ConfigReloadTask.cc +++ /dev/null @@ -1,148 +0,0 @@ -/** @file - - Unit tests for ConfigReloadProgress timeout configuration and ReloadCoordinator::cancel_reload - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include - -#include "mgmt/config/ConfigReloadTrace.h" -#include "mgmt/config/ReloadCoordinator.h" - -// Note: These tests verify the default values and basic logic. -// Full integration testing with records is done via autest. - -TEST_CASE("ConfigReloadProgress default timeout", "[config][reload][timeout]") -{ - SECTION("Default timeout is 1 hour") - { - // Default should be "1h" which equals 3600000ms - REQUIRE(std::string(ConfigReloadProgress::DEFAULT_TIMEOUT) == "1h"); - } -} - -TEST_CASE("ConfigReloadProgress constants", "[config][reload][timeout]") -{ - SECTION("Record names are correct") - { - REQUIRE(std::string(ConfigReloadProgress::RECORD_TIMEOUT) == "proxy.config.admin.reload.timeout"); - REQUIRE(std::string(ConfigReloadProgress::RECORD_CHECK_INTERVAL) == "proxy.config.admin.reload.check_interval"); - } - - SECTION("Default values are sensible") - { - // Default timeout should be "1h" - REQUIRE(std::string(ConfigReloadProgress::DEFAULT_TIMEOUT) == "1h"); - // Default check interval should be "2s" - REQUIRE(std::string(ConfigReloadProgress::DEFAULT_CHECK_INTERVAL) == "2s"); - // Minimum check interval should be 1 second - REQUIRE(ConfigReloadProgress::MIN_CHECK_INTERVAL_MS == 1000); - } -} - -TEST_CASE("ReloadCoordinator mark_task_as_stale with no task", "[config][reload][stale]") -{ - auto &coord = ReloadCoordinator::Get_Instance(); - - SECTION("mark_task_as_stale returns false when no current task") - { - // Ensure no task is running (might have leftover from previous tests) - // Note: In a real scenario, we'd wait for any existing task to complete - - // Try to mark stale with non-existent token - bool marked = coord.mark_task_as_stale("nonexistent-token-xyz", "Test stale"); - REQUIRE(marked == false); - } -} - -TEST_CASE("ConfigReloadTask state transitions", "[config][reload][state]") -{ - SECTION("Task can be marked as timeout (bad state)") - { - auto task = std::make_shared("test-token", "test task", false, nullptr); - - // Initial state should be CREATED - REQUIRE(task->get_state() == ConfigReloadTask::State::CREATED); - - // Mark as in progress first - task->set_in_progress(); - REQUIRE(task->get_state() == ConfigReloadTask::State::IN_PROGRESS); - - // Now mark as bad state (timeout) - task->mark_as_bad_state("Test timeout"); - REQUIRE(task->get_state() == ConfigReloadTask::State::TIMEOUT); - - // Verify logs contain the reason - auto logs = task->get_logs(); - REQUIRE(!logs.empty()); - REQUIRE(logs.back().text.find("Test timeout") != std::string::npos); - } - - SECTION("Terminal states cannot be changed via mark_as_bad_state") - { - auto task = std::make_shared("test-token-2", "test task 2", false, nullptr); - - // Set to SUCCESS (terminal state) - task->set_completed(); - REQUIRE(task->get_state() == ConfigReloadTask::State::SUCCESS); - - // Try to mark as timeout — terminal guard rejects the transition - task->mark_as_bad_state("Should not apply"); - REQUIRE(task->get_state() == ConfigReloadTask::State::SUCCESS); - - // Verify the rejected reason was NOT added to logs - auto logs = task->get_logs(); - for (const auto &entry : logs) { - REQUIRE(entry.text.find("Should not apply") == std::string::npos); - } - } - - SECTION("Terminal states cannot be changed via set_state_and_notify") - { - auto task = std::make_shared("test-token-3", "test task 3", false, nullptr); - - // Set to FAIL (terminal state) - task->set_failed(); - REQUIRE(task->get_state() == ConfigReloadTask::State::FAIL); - - // Try to transition to SUCCESS — rejected - task->set_completed(); - REQUIRE(task->get_state() == ConfigReloadTask::State::FAIL); - - // Try to transition to IN_PROGRESS — rejected - task->set_in_progress(); - REQUIRE(task->get_state() == ConfigReloadTask::State::FAIL); - } -} - -TEST_CASE("State to string conversion", "[config][reload][state]") -{ - // Runtime checks - REQUIRE(ConfigReloadTask::state_to_string(ConfigReloadTask::State::INVALID) == "invalid"); - REQUIRE(ConfigReloadTask::state_to_string(ConfigReloadTask::State::CREATED) == "created"); - REQUIRE(ConfigReloadTask::state_to_string(ConfigReloadTask::State::IN_PROGRESS) == "in_progress"); - REQUIRE(ConfigReloadTask::state_to_string(ConfigReloadTask::State::SUCCESS) == "success"); - REQUIRE(ConfigReloadTask::state_to_string(ConfigReloadTask::State::FAIL) == "fail"); - REQUIRE(ConfigReloadTask::state_to_string(ConfigReloadTask::State::TIMEOUT) == "timeout"); - - // Compile-time verification (constexpr) - static_assert(ConfigReloadTask::state_to_string(ConfigReloadTask::State::SUCCESS) == "success"); - static_assert(ConfigReloadTask::state_to_string(ConfigReloadTask::State::FAIL) == "fail"); -} diff --git a/src/records/unit_tests/test_ReloadDirectives.cc b/src/records/unit_tests/test_ReloadDirectives.cc deleted file mode 100644 index 5b90b607081..00000000000 --- a/src/records/unit_tests/test_ReloadDirectives.cc +++ /dev/null @@ -1,319 +0,0 @@ -/** @file - - Unit tests for reload directives: ConfigContext directive accessors, - framework extraction logic, and CLI parse_directive() format. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include - -#include "mgmt/config/ConfigContext.h" -#include "mgmt/config/ConfigReloadTrace.h" - -#include -#include -#include - -// ─── parse_directive: standalone copy of the traffic_ctl parsing logic ──────── -// -// The actual function lives in an anonymous namespace in CtrlCommands.cc. -// We reproduce the identical logic here so we can unit-test the format parsing -// without pulling in the full traffic_ctl binary and its dependencies. -namespace -{ -bool -parse_directive(std::string_view dir, YAML::Node &configs, std::string &error_out) -{ - auto dot = dir.find('.'); - if (dot == std::string_view::npos || dot == 0) { - error_out = "Invalid directive format '" + std::string(dir) + "'. Expected: config_key.directive_key=value"; - return false; - } - - auto eq = dir.find('=', dot + 1); - if (eq == std::string_view::npos || eq == dot + 1) { - error_out = "Invalid directive format '" + std::string(dir) + "'. Expected: config_key.directive_key=value"; - return false; - } - - std::string config_key{dir.substr(0, dot)}; - std::string directive_key{dir.substr(dot + 1, eq - dot - 1)}; - std::string value{dir.substr(eq + 1)}; - - configs[config_key]["_reload"][directive_key] = value; - return true; -} -} // namespace - -// ─── parse_directive format tests ───────────────────────────────────────────── - -TEST_CASE("parse_directive: valid single directive", "[config][directive][parse]") -{ - YAML::Node configs; - std::string err; - - REQUIRE(parse_directive("myconfig.id=foo", configs, err)); - REQUIRE(configs["myconfig"]["_reload"]["id"].as() == "foo"); -} - -TEST_CASE("parse_directive: value with equals signs", "[config][directive][parse]") -{ - YAML::Node configs; - std::string err; - - REQUIRE(parse_directive("plugin.url=http://x.com/a=b", configs, err)); - REQUIRE(configs["plugin"]["_reload"]["url"].as() == "http://x.com/a=b"); -} - -TEST_CASE("parse_directive: value with dots", "[config][directive][parse]") -{ - YAML::Node configs; - std::string err; - - REQUIRE(parse_directive("plugin.fqdn=foo.example.com", configs, err)); - REQUIRE(configs["plugin"]["_reload"]["fqdn"].as() == "foo.example.com"); -} - -TEST_CASE("parse_directive: empty value is allowed", "[config][directive][parse]") -{ - YAML::Node configs; - std::string err; - - REQUIRE(parse_directive("myconfig.flag=", configs, err)); - REQUIRE(configs["myconfig"]["_reload"]["flag"].as() == ""); -} - -TEST_CASE("parse_directive: multiple directives for same config", "[config][directive][parse]") -{ - YAML::Node configs; - std::string err; - - REQUIRE(parse_directive("myconfig.id=foo", configs, err)); - REQUIRE(parse_directive("myconfig.dry_run=true", configs, err)); - - REQUIRE(configs["myconfig"]["_reload"]["id"].as() == "foo"); - REQUIRE(configs["myconfig"]["_reload"]["dry_run"].as() == "true"); -} - -TEST_CASE("parse_directive: multiple directives for different configs", "[config][directive][parse]") -{ - YAML::Node configs; - std::string err; - - REQUIRE(parse_directive("myconfig.id=foo", configs, err)); - REQUIRE(parse_directive("sni.fqdn=example.com", configs, err)); - - REQUIRE(configs["myconfig"]["_reload"]["id"].as() == "foo"); - REQUIRE(configs["sni"]["_reload"]["fqdn"].as() == "example.com"); -} - -TEST_CASE("parse_directive: rejects missing dot", "[config][directive][parse]") -{ - YAML::Node configs; - std::string err; - - REQUIRE_FALSE(parse_directive("nodot", configs, err)); - REQUIRE(err.find("Invalid directive format") != std::string::npos); -} - -TEST_CASE("parse_directive: rejects leading dot", "[config][directive][parse]") -{ - YAML::Node configs; - std::string err; - - REQUIRE_FALSE(parse_directive(".key=value", configs, err)); - REQUIRE(err.find("Invalid directive format") != std::string::npos); -} - -TEST_CASE("parse_directive: rejects missing equals", "[config][directive][parse]") -{ - YAML::Node configs; - std::string err; - - REQUIRE_FALSE(parse_directive("config.key", configs, err)); - REQUIRE(err.find("Invalid directive format") != std::string::npos); -} - -TEST_CASE("parse_directive: rejects empty directive key", "[config][directive][parse]") -{ - YAML::Node configs; - std::string err; - - REQUIRE_FALSE(parse_directive("config.=value", configs, err)); - REQUIRE(err.find("Invalid directive format") != std::string::npos); -} - -// ─── ConfigContext directive accessor tests ─────────────────────────────────── - -TEST_CASE("ConfigContext: reload_directives on default context has no keys", "[config][context][directive]") -{ - ConfigContext ctx; - - // Members are initialized as Undefined, so operator bool() is false. - YAML::Node const directives = ctx.reload_directives(); - REQUIRE_FALSE(directives.IsDefined()); - REQUIRE_FALSE(directives); - REQUIRE_FALSE(directives["id"].IsDefined()); -} - -TEST_CASE("ConfigContext: supplied_yaml on default context has no content", "[config][context][directive]") -{ - ConfigContext ctx; - - auto yaml = ctx.supplied_yaml(); - REQUIRE_FALSE(yaml.IsDefined()); - REQUIRE_FALSE(yaml); - REQUIRE_FALSE(yaml.IsMap()); - REQUIRE_FALSE(yaml.IsSequence()); -} - -TEST_CASE("ConfigContext: reload_directives round-trip via task", "[config][context][directive]") -{ - auto task = std::make_shared("test-dir-1", "test", false, nullptr); - ConfigContext ctx(task, "test_handler"); - - YAML::Node directives; - directives["id"] = "foo"; - directives["dry_run"] = "true"; - - // Use the private setter via a ConfigContext that has a live task. - // Since set_reload_directives is private and friend-accessible only from - // ConfigRegistry/ReloadCoordinator, we test through the public interface - // after setting up the state that execute_reload would create. - - // Simulate what ConfigRegistry::execute_reload() does: - // Build a passed_config with _reload, then manually extract - YAML::Node passed_config; - passed_config["_reload"]["id"] = "foo"; - passed_config["_reload"]["dry_run"] = "true"; - passed_config["data"] = "some content"; - - // Extract _reload (same logic as execute_reload) - if (passed_config.IsMap() && passed_config["_reload"]) { - auto dir = passed_config["_reload"]; - if (dir.IsMap()) { - // We can't call set_reload_directives directly (private). - // But we can verify the extraction logic works on the YAML node. - REQUIRE(dir["id"].as() == "foo"); - REQUIRE(dir["dry_run"].as() == "true"); - passed_config.remove("_reload"); - } - } - - // After extraction, passed_config should only have "data" - REQUIRE_FALSE(passed_config["_reload"].IsDefined()); - REQUIRE(passed_config["data"].as() == "some content"); - REQUIRE(passed_config.size() == 1); -} - -TEST_CASE("ConfigContext: _reload extraction with directives only", "[config][context][directive]") -{ - YAML::Node passed_config; - passed_config["_reload"]["id"] = "bar"; - - // Extract - YAML::Node directives; - if (passed_config.IsMap() && passed_config["_reload"]) { - directives = passed_config["_reload"]; - passed_config.remove("_reload"); - } - - REQUIRE(directives.IsDefined()); - REQUIRE(directives["id"].as() == "bar"); - - // After extraction with only _reload, the map should be empty - REQUIRE(passed_config.size() == 0); -} - -TEST_CASE("ConfigContext: _reload extraction with content only", "[config][context][directive]") -{ - YAML::Node passed_config; - passed_config["rules"].push_back("rule1"); - passed_config["rules"].push_back("rule2"); - - bool extracted = false; - if (passed_config.IsMap() && passed_config["_reload"]) { - extracted = true; - passed_config.remove("_reload"); - } - - // No _reload key present — extraction did not fire - REQUIRE_FALSE(extracted); - - // Content untouched - REQUIRE(passed_config["rules"].size() == 2); -} - -TEST_CASE("ConfigContext: _reload non-map is rejected", "[config][context][directive]") -{ - YAML::Node passed_config; - passed_config["_reload"] = "scalar_value"; - passed_config["data"] = "content"; - - bool extracted = false; - bool rejected = false; - if (passed_config.IsMap() && passed_config["_reload"]) { - auto dir = passed_config["_reload"]; - if (!dir.IsMap()) { - rejected = true; - } else { - extracted = true; - } - passed_config.remove("_reload"); - } - - REQUIRE(rejected); - REQUIRE_FALSE(extracted); - // _reload is still removed even when rejected - REQUIRE_FALSE(passed_config["_reload"].IsDefined()); - REQUIRE(passed_config["data"].as() == "content"); -} - -// ─── Wire format integration: -D flag produces correct YAML structure ───────── - -TEST_CASE("Wire format: -D produces _reload nested under config key", "[config][directive][wire]") -{ - YAML::Node configs; - std::string err; - - parse_directive("myconfig.id=foo", configs, err); - - // Verify the structure matches what the server expects - REQUIRE(configs.IsMap()); - REQUIRE(configs["myconfig"].IsMap()); - REQUIRE(configs["myconfig"]["_reload"].IsMap()); - REQUIRE(configs["myconfig"]["_reload"]["id"].as() == "foo"); -} - -TEST_CASE("Wire format: -D combined with -d content", "[config][directive][wire]") -{ - YAML::Node configs; - - // Simulate -d providing content - configs["myconfig"]["rules"].push_back("rule1"); - - // Then -D adding directives - std::string err; - parse_directive("myconfig.id=foo", configs, err); - - // Both coexist under the same config key - REQUIRE(configs["myconfig"]["rules"].size() == 1); - REQUIRE(configs["myconfig"]["_reload"]["id"].as() == "foo"); -} diff --git a/src/traffic_ctl/CMakeLists.txt b/src/traffic_ctl/CMakeLists.txt index c967d42e9c9..671789b852b 100644 --- a/src/traffic_ctl/CMakeLists.txt +++ b/src/traffic_ctl/CMakeLists.txt @@ -16,18 +16,11 @@ ####################### add_executable( - traffic_ctl - traffic_ctl.cc - ConvertConfigCommand.cc - CtrlCommands.cc - CtrlPrinters.cc - FileConfigCommand.cc - PrintUtils.cc - SSLMultiCertCommand.cc - ${CMAKE_SOURCE_DIR}/src/shared/rpc/IPCSocketClient.cc + traffic_ctl traffic_ctl.cc CtrlCommands.cc CtrlPrinters.cc FileConfigCommand.cc PrintUtils.cc + ${CMAKE_SOURCE_DIR}/src/shared/rpc/IPCSocketClient.cc ) -target_link_libraries(traffic_ctl ts::tscore ts::config libswoc::libswoc yaml-cpp::yaml-cpp ts::tsutil) +target_link_libraries(traffic_ctl ts::tscore libswoc::libswoc yaml-cpp::yaml-cpp ts::tsutil) install(TARGETS traffic_ctl) diff --git a/src/traffic_ctl/ConvertConfigCommand.cc b/src/traffic_ctl/ConvertConfigCommand.cc deleted file mode 100644 index 80bcf3eb139..00000000000 --- a/src/traffic_ctl/ConvertConfigCommand.cc +++ /dev/null @@ -1,194 +0,0 @@ -/** @file - - Configuration format conversion command for traffic_ctl. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include "ConvertConfigCommand.h" -#include "config/ssl_multicert.h" -#include "config/storage.h" -#include "config/plugin_config.h" - -#include -#include - -ConvertConfigCommand::ConvertConfigCommand(ts::Arguments *args) : CtrlCommand(args) -{ - BasePrinter::Options print_opts{parse_print_opts(args)}; - _printer = std::make_unique(print_opts); - - if (args->get("ssl_multicert")) { - auto const &convert_args = args->get("ssl_multicert"); - if (convert_args.size() < 2) { - throw std::invalid_argument("ssl_multicert requires "); - } - _input_file = convert_args[0]; - _output_file = convert_args[1]; - _invoked_func = [this]() { convert_ssl_multicert(); }; - } else if (args->get("storage")) { - auto const &convert_args = args->get("storage"); - if (convert_args.size() < 3) { - throw std::invalid_argument("storage requires "); - } - _input_file = convert_args[0]; - _volume_config_file = convert_args[1]; - _output_file = convert_args[2]; - _invoked_func = [this]() { convert_storage(); }; - } else if (args->get("plugin_config")) { - auto const &convert_args = args->get("plugin_config"); - if (convert_args.size() < 2) { - throw std::invalid_argument("plugin_config requires "); - } - _input_file = convert_args[0]; - _output_file = convert_args[1]; - _skip_disabled = args->get("skip-disabled"); - _invoked_func = [this]() { convert_plugin_config(); }; - } else { - throw std::invalid_argument("Unsupported config type for conversion"); - } -} - -void -ConvertConfigCommand::convert_ssl_multicert() -{ - config::SSLMultiCertParser parser; - config::ConfigResult result = parser.parse(_input_file); - - if (!result.ok()) { - std::string error_msg = "Failed to parse input file '" + _input_file + "'"; - if (!result.errata.empty()) { - error_msg += ": "; - error_msg += std::string(result.errata.front().text()); - } - _printer->write_output(error_msg); - return; - } - - config::SSLMultiCertMarshaller marshaller; - std::string const yaml_output = marshaller.to_yaml(result.value); - - // Write to output file or stdout if output is "-". - if (_output_file == "-") { - std::cout << yaml_output << '\n'; - } else { - std::ofstream out(_output_file); - if (!out) { - _printer->write_output("Failed to open output file '" + _output_file + "' for writing"); - return; - } - out << yaml_output << '\n'; - out.close(); - _printer->write_output("Converted " + _input_file + " -> " + _output_file); - } -} - -void -ConvertConfigCommand::convert_storage() -{ - // Parse legacy storage.config. - config::StorageParser storage_parser; - config::ConfigResult storage_result = storage_parser.parse(_input_file); - - if (!storage_result.ok()) { - std::string error_msg = "Failed to parse storage config '" + _input_file + "'"; - if (!storage_result.errata.empty()) { - error_msg += ": "; - error_msg += std::string(storage_result.errata.front().text()); - } - _printer->write_output(error_msg); - return; - } - - // Parse legacy volume.config. - config::VolumeParser volume_parser; - config::ConfigResult volume_result = volume_parser.parse(_volume_config_file); - - // A missing volume.config is treated as "no volumes configured" rather than - // a hard error, since the file was optional in the legacy setup. - if (!volume_result.ok()) { - if (!volume_result.file_not_found) { - std::string error_msg = "Failed to parse volume config '" + _volume_config_file + "'"; - if (!volume_result.errata.empty()) { - error_msg += ": "; - error_msg += std::string(volume_result.errata.front().text()); - } - _printer->write_output(error_msg); - return; - } - } - - // Merge: volumes from volume.config replace the minimal volumes produced by - // storage.config span annotations. Span refs recorded by the storage.config - // parser (volume=N lines) are preserved in the merged result. - config::StorageConfig const merged = config::merge_legacy_storage_configs(storage_result.value, volume_result.value); - - config::StorageMarshaller marshaller; - std::string const yaml_output = marshaller.to_yaml(merged); - - if (_output_file == "-") { - std::cout << yaml_output << '\n'; - } else { - std::ofstream out(_output_file); - if (!out) { - _printer->write_output("Failed to open output file '" + _output_file + "' for writing"); - return; - } - out << yaml_output << '\n'; - out.close(); - _printer->write_output("Converted " + _input_file + " + " + _volume_config_file + " -> " + _output_file); - } -} - -void -ConvertConfigCommand::convert_plugin_config() -{ - config::PluginConfigParser parser; - config::ConfigResult result = parser.parse(_input_file); - - if (!result.ok()) { - std::string error_msg = "Failed to parse input file '" + _input_file + "'"; - if (!result.errata.empty()) { - error_msg += ": "; - error_msg += std::string(result.errata.front().text()); - } - _printer->write_output(error_msg); - return; - } - - if (_skip_disabled) { - std::erase_if(result.value, [](const config::PluginConfigEntry &e) { return !e.enabled; }); - } - - config::PluginConfigMarshaller marshaller; - std::string const serialized = marshaller.to_yaml(result.value); - - if (_output_file == "-") { - std::cout << serialized << '\n'; - } else { - std::ofstream out(_output_file); - if (!out) { - _printer->write_output("Failed to open output file '" + _output_file + "' for writing"); - return; - } - out << serialized << '\n'; - out.close(); - _printer->write_output("Converted " + _input_file + " -> " + _output_file); - } -} diff --git a/src/traffic_ctl/ConvertConfigCommand.h b/src/traffic_ctl/ConvertConfigCommand.h deleted file mode 100644 index 8ee4386e87e..00000000000 --- a/src/traffic_ctl/ConvertConfigCommand.h +++ /dev/null @@ -1,57 +0,0 @@ -/** @file - - Configuration format conversion command for traffic_ctl. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#pragma once - -#include "CtrlCommands.h" - -/** - * Command handler for configuration format conversion. - * - * Converts configuration files from legacy formats to YAML. - * Supports: ssl_multicert, storage, plugin_config - */ -class ConvertConfigCommand : public CtrlCommand -{ -public: - /** - * Construct the command from parsed arguments. - * - * @param[in] args Parsed command line arguments. - */ - ConvertConfigCommand(ts::Arguments *args); - -private: - void convert_ssl_multicert(); - void convert_storage(); - void convert_plugin_config(); - - std::string _input_file; - std::string _output_file; - - // For storage conversion only: optional volume.config path. - std::string _volume_config_file; - - // For plugin_config conversion: drop disabled entries from output. - bool _skip_disabled{false}; -}; diff --git a/src/traffic_ctl/CtrlCommands.cc b/src/traffic_ctl/CtrlCommands.cc index 1f64bd45db9..6bd568492ed 100644 --- a/src/traffic_ctl/CtrlCommands.cc +++ b/src/traffic_ctl/CtrlCommands.cc @@ -20,12 +20,9 @@ */ #include "CtrlCommands.h" -#include -#include #include #include #include -#include #include #include #include @@ -37,8 +34,6 @@ #include "jsonrpc/CtrlRPCRequests.h" #include "jsonrpc/ctrl_yaml_codecs.h" -#include "mgmt/config/ConfigReloadErrors.h" -#include "tsutil/ts_time_parser.h" #include "TrafficCtlStatus.h" namespace { @@ -65,49 +60,6 @@ yaml_to_record_name(std::string_view path) } return std::string{path}; } - -void -display_errors(BasePrinter *printer, std::vector const &errors) -{ - std::string text; - if (auto iter = std::begin(errors); iter != std::end(errors)) { - auto print_error = [&](auto &&e) { printer->write_output(swoc::bwprint(text, "Message: {}, Code: {}", e.message, e.code)); }; - printer->write_output("------------ Errors ----------"); - print_error(*iter); - ++iter; - for (; iter != std::end(errors); ++iter) { - printer->write_output("--"); - print_error(*iter); - } - } -} - -/// Parse a single --directive (-D) argument "config_key.directive_key=value" -/// and inject into configs[config_key]["_reload"][directive_key]. -/// Returns true on success, sets error_out on parse failure. -bool -parse_directive(std::string_view dir, YAML::Node &configs, std::string &error_out) -{ - auto dot = dir.find('.'); - if (dot == std::string_view::npos || dot == 0) { - error_out = "Invalid directive format '" + std::string(dir) + "'. Expected: config_key.directive_key=value"; - return false; - } - - auto eq = dir.find('=', dot + 1); - if (eq == std::string_view::npos || eq == dot + 1) { - error_out = "Invalid directive format '" + std::string(dir) + "'. Expected: config_key.directive_key=value"; - return false; - } - - std::string config_key{dir.substr(0, dot)}; - std::string directive_key{dir.substr(dot + 1, eq - dot - 1)}; - std::string value{dir.substr(eq + 1)}; - - configs[config_key]["_reload"][directive_key] = value; - return true; -} - } // namespace BasePrinter::Options::FormatFlags @@ -209,7 +161,7 @@ ConfigCommand::ConfigCommand(ts::Arguments *args) : RecordCommand(args) _printer = std::make_unique(printOpts); _invoked_func = [&]() { config_reset(); }; } else if (args->get(STATUS_STR)) { - _printer = std::make_unique(printOpts); + _printer = std::make_unique(printOpts); _invoked_func = [&]() { config_status(); }; } else if (args->get(RELOAD_STR)) { _printer = std::make_unique(printOpts); @@ -286,52 +238,9 @@ ConfigCommand::config_diff() void ConfigCommand::config_status() { - std::string token = get_parsed_arguments()->get("token").value(); - std::string count = get_parsed_arguments()->get("count").value(); - std::string min_level = get_parsed_arguments()->get("min-level").value(); - - if (!count.empty() && !token.empty()) { - // can't use both. - if (!_printer->is_json_format()) { - _printer->write_output("You can't use both --token and --count options together. Ignoring --count"); - } - count = ""; // server will ignore this if token is set anyways. - } - - if (!min_level.empty()) { - static const std::unordered_map level_map = { - {"debug", DL_Debug }, - {"note", DL_Note }, - {"warning", DL_Warning}, - {"error", DL_Error }, - }; - - std::string lowered{min_level}; - std::transform(lowered.begin(), lowered.end(), lowered.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); - - if (auto it = level_map.find(lowered); it != level_map.end()) { - _printer->as()->set_min_level(it->second); - } else { - _printer->write_output("Invalid --min-level value. Use: debug, note, warning, error"); - App_Exit_Status_Code = CTRL_EX_ERROR; - return; - } - } - - auto resp = fetch_config_reload(token, count); - - if (resp.error.size()) { - display_errors(_printer.get(), resp.error); - App_Exit_Status_Code = CTRL_EX_ERROR; - return; - } - - if (resp.tasks.size() > 0) { - for (const auto &task : resp.tasks) { - _printer->as()->print_reload_report(task, true); - } - } + ConfigStatusRequest request; + shared::rpc::JSONRPCResponse response = invoke_rpc(request); + _printer->write_output(response); } void @@ -346,134 +255,6 @@ ConfigCommand::config_set() _printer->write_output(response); } -ConfigReloadResponse -ConfigCommand::fetch_config_reload(std::string const &token, std::string const &count) -{ - // traffic_ctl config status [--token ] [--count ] - - FetchConfigReloadStatusRequest request{ - FetchConfigReloadStatusRequest::Params{token, count} - }; - - auto response = invoke_rpc(request); // server will handle if token is empty or not. - - _printer->write_output(response); // in case of errors. - return response.result.as(); -} - -void -ConfigCommand::track_config_reload_progress(std::string const &token, std::chrono::milliseconds refresh_interval, - std::chrono::milliseconds timeout, std::string const &timeout_str) -{ - FetchConfigReloadStatusRequest request{ - FetchConfigReloadStatusRequest::Params{token, "1" /* last reload if any*/} - }; - auto resp = invoke_rpc(request); - - if (resp.is_error()) { - _printer->write_output(resp); - return; - } - - auto start_time = std::chrono::steady_clock::now(); - - while (!Signal_Flagged.load()) { - auto decoded_response = resp.result.as(); - - _printer->write_output(resp); - if (decoded_response.tasks.empty()) { - _printer->write_output(resp); - App_Exit_Status_Code = CTRL_EX_ERROR; - return; - } - - ConfigReloadResponse::ReloadInfo current_task = decoded_response.tasks[0]; - _printer->as()->write_progress_line(current_task); - - // Check if reload has reached a terminal state - if (current_task.status == "success" || current_task.status == "fail" || current_task.status == "timeout") { - std::cout << "\n"; - if (current_task.status != "success") { - App_Exit_Status_Code = CTRL_EX_ERROR; - std::string hint; - _printer->write_output(swoc::bwprint(hint, "\n Details : traffic_ctl config status -t {}", current_task.config_token)); - } - return; - } - - // Check timeout (0 means no timeout) - if (timeout.count() > 0) { - auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - start_time); - if (elapsed >= timeout) { - std::cout << "\n"; - std::string text; - _printer->write_output(swoc::bwprint(text, "Monitor timed out after {} waiting for reload completion.", timeout_str)); - _printer->write_output(swoc::bwprint(text, " Details : traffic_ctl config status -t {}", token)); - App_Exit_Status_Code = CTRL_EX_TEMPFAIL; - return; - } - } - - std::this_thread::sleep_for(refresh_interval); - - request = FetchConfigReloadStatusRequest{ - FetchConfigReloadStatusRequest::Params{token, "1" /* last reload if any*/} - }; - resp = invoke_rpc(request); - if (resp.is_error()) { - _printer->write_output(resp); - App_Exit_Status_Code = CTRL_EX_ERROR; - return; - } - } - - // Signal received (e.g. Ctrl+C) — assume reload still in progress. - if (Signal_Flagged.load()) { - App_Exit_Status_Code = CTRL_EX_TEMPFAIL; - } -} - -std::string -ConfigCommand::read_data_input(std::string const &data_arg) -{ - if (data_arg.empty()) { - return {}; - } - - // @- means stdin - if (data_arg == "@-") { - std::istreambuf_iterator begin(std::cin), end; - return std::string(begin, end); - } - - // @filename means read from file - if (data_arg[0] == '@') { - std::string filename = data_arg.substr(1); - std::ifstream file(filename); - if (!file) { - _printer->write_output("Error: Cannot open file '" + filename + "'"); - App_Exit_Status_Code = CTRL_EX_ERROR; - return {}; - } - std::istreambuf_iterator begin(file), end; - return std::string(begin, end); - } - - // Otherwise, treat as inline YAML string - return data_arg; -} - -ConfigReloadResponse -ConfigCommand::config_reload(std::string const &token, bool force, YAML::Node const &configs) -{ - auto resp = invoke_rpc(ConfigReloadRequest{ - ConfigReloadRequest::Params{token, force, configs} - }); - // base class method will handle error and json output if needed. - _printer->write_output(resp); - return resp.result.as(); -} - void ConfigCommand::config_reset() { @@ -520,216 +301,8 @@ ConfigCommand::config_reset() void ConfigCommand::config_reload() { - std::string token = get_parsed_arguments()->get("token").value(); - bool force = get_parsed_arguments()->get("force") ? true : false; - auto data_args = get_parsed_arguments()->get("data"); - - bool show_details = get_parsed_arguments()->get("show-details") ? true : false; - bool monitor = get_parsed_arguments()->get("monitor") ? true : false; - - float refresh_secs = std::stof(get_parsed_arguments()->get("refresh-int").value()); - float initial_wait_secs = std::stof(get_parsed_arguments()->get("initial-wait").value()); - // Parse timeout using ts::time_parser (supports: 30s, 1m, 500ms, etc.) - std::chrono::milliseconds timeout_ms{0}; - std::string timeout_str = get_parsed_arguments()->get("timeout").value(); - if (timeout_str != "0") { - auto &&[ns, errata] = ts::time_parser(swoc::TextView{timeout_str}); - if (!errata.is_ok()) { - _printer->write_output("Error: Invalid timeout value '" + timeout_str + "'. Use duration format: 30s, 1m, 500ms, etc."); - App_Exit_Status_Code = CTRL_EX_ERROR; - return; - } - timeout_ms = std::chrono::duration_cast(ns); - } - - if (monitor && show_details) { - // ignore monitor if details is set. - monitor = false; - } - - // Warn about --force behavior - if (force) { - _printer->write_output("Warning: --force does not stop running handlers."); - _printer->write_output(" If a reload is actively processing, handlers may run in parallel."); - _printer->write_output(""); - } - - // Parse inline config data if provided (supports multiple -d arguments) - YAML::Node configs; - for (auto const &data_arg : data_args) { - if (data_arg.empty()) { - continue; - } - - std::string data_content = read_data_input(data_arg); - if (data_content.empty() && App_Exit_Status_Code == CTRL_EX_ERROR) { - return; // Error already reported by read_data_input - } - - try { - YAML::Node parsed = YAML::Load(data_content); - if (!parsed.IsMap()) { - _printer->write_output("Error: Data must be a YAML map with config keys (e.g., ip_allow, sni)"); - App_Exit_Status_Code = CTRL_EX_ERROR; - return; - } - // Merge parsed content into configs (later files override earlier ones) - for (auto const &kv : parsed) { - configs[kv.first.as()] = kv.second; - } - } catch (YAML::Exception const &ex) { - _printer->write_output(std::string("Error: Invalid YAML data in '") + data_arg + "': " + ex.what()); - App_Exit_Status_Code = CTRL_EX_ERROR; - return; - } - } - - // Parse --directive (-D) arguments into configs[key]["_reload"][directive] = value - auto dir_args = get_parsed_arguments()->get("directive"); - for (auto const &dir : dir_args) { - if (dir.empty()) { - continue; - } - if (dir[0] == '-') { - _printer->write_output("Error: '" + dir + - "' looks like a flag, not a directive. " - "Place -D as the last option on the command line."); - App_Exit_Status_Code = CTRL_EX_ERROR; - return; - } - std::string err; - if (!parse_directive(dir, configs, err)) { - _printer->write_output("Error: " + err); - App_Exit_Status_Code = CTRL_EX_ERROR; - return; - } - } - - using ConfigError = config::reload::errors::ConfigReloadError; - - auto contains_error = [](std::vector const &errors, ConfigError error) -> bool { - const int code = static_cast(error); - for (auto const &n : errors) { - if (n.code == code) { - return true; - } - } - return false; - }; - - std::string text; - bool token_exist{false}; - bool in_progress{false}; - - if (show_details) { - bool include_logs = get_parsed_arguments()->get("include-logs") ? true : false; - - ConfigReloadResponse resp = config_reload(token, force, configs); - if (contains_error(resp.error, ConfigError::RELOAD_IN_PROGRESS)) { - App_Exit_Status_Code = CTRL_EX_TEMPFAIL; - if (resp.tasks.size() > 0) { - const auto &task = resp.tasks[0]; - _printer->write_output(swoc::bwprint(text, "\xe2\x9f\xb3 Reload in progress [{}]", task.config_token)); - _printer->as()->print_reload_report(task, include_logs); - } - return; - } else if (contains_error(resp.error, ConfigError::TOKEN_ALREADY_EXISTS)) { - App_Exit_Status_Code = CTRL_EX_ERROR; - token_exist = true; - } else if (resp.error.size()) { - display_errors(_printer.get(), resp.error); - App_Exit_Status_Code = CTRL_EX_ERROR; - return; - } - - if (token_exist) { - _printer->write_output(swoc::bwprint(text, "\xe2\x9c\x97 Token '{}' already in use", token)); - } else { - _printer->write_output(swoc::bwprint(text, "\xe2\x9c\x94 Reload scheduled [{}]. Waiting for details...", resp.config_token)); - std::this_thread::sleep_for(std::chrono::milliseconds(static_cast(initial_wait_secs * 1000))); - } - - resp = fetch_config_reload(token); - if (resp.error.size()) { - display_errors(_printer.get(), resp.error); - App_Exit_Status_Code = CTRL_EX_ERROR; - return; - } - - if (resp.tasks.size() > 0) { - const auto &task = resp.tasks[0]; - // _printer->as()->print_basic_ri_line(task, true, true, 0); - _printer->as()->print_reload_report(task, include_logs); - } - } else if (monitor) { - _printer->disable_json_format(); // monitor output is not json. - ConfigReloadResponse resp = config_reload(token, force, configs); - - if (contains_error(resp.error, ConfigError::RELOAD_IN_PROGRESS)) { - in_progress = true; - if (!resp.tasks.empty()) { - _printer->write_output(swoc::bwprint(text, "\xe2\x9f\xb3 Reload in progress [{}]", resp.tasks[0].config_token)); - } - } else if (contains_error(resp.error, ConfigError::TOKEN_ALREADY_EXISTS)) { - App_Exit_Status_Code = CTRL_EX_ERROR; - _printer->write_output(swoc::bwprint(text, "\xe2\x9c\x97 Token '{}' already in use\n", token)); - _printer->write_output(swoc::bwprint(text, " Status : traffic_ctl config status -t {}", token)); - _printer->write_output(" Retry : traffic_ctl config reload"); - return; - } else if (resp.error.size()) { - display_errors(_printer.get(), resp.error); - App_Exit_Status_Code = CTRL_EX_ERROR; - return; - } else { - _printer->write_output(swoc::bwprint(text, "\xe2\x9c\x94 Reload scheduled [{}]", resp.config_token)); - } - - if (!in_progress) { - std::this_thread::sleep_for(std::chrono::milliseconds(static_cast(initial_wait_secs * 1000))); // wait before first poll - } // else no need to wait, we can start fetching right away. - - track_config_reload_progress(resp.config_token, std::chrono::milliseconds(static_cast(refresh_secs * 1000)), timeout_ms, - timeout_str); - } else { - ConfigReloadResponse resp = config_reload(token, force, configs); - if (contains_error(resp.error, ConfigError::RELOAD_IN_PROGRESS)) { - App_Exit_Status_Code = CTRL_EX_TEMPFAIL; - if (!resp.tasks.empty()) { - std::string tk = resp.tasks[0].config_token; - _printer->write_output(swoc::bwprint(text, "\xe2\x9f\xb3 Reload in progress [{}]\n", tk)); - _printer->write_output(swoc::bwprint(text, " Monitor : traffic_ctl config reload -t {} -m", tk)); - _printer->write_output(swoc::bwprint(text, " Details : traffic_ctl config status -t {}", tk)); - _printer->write_output(" Force : traffic_ctl config reload --force (may conflict with the running reload)"); - } - } else if (contains_error(resp.error, ConfigError::TOKEN_ALREADY_EXISTS)) { - App_Exit_Status_Code = CTRL_EX_ERROR; - _printer->write_output(swoc::bwprint(text, "\xe2\x9c\x97 Token '{}' already in use\n", token)); - _printer->write_output(swoc::bwprint(text, " Status : traffic_ctl config status -t {}", token)); - _printer->write_output(" Retry : traffic_ctl config reload"); - } else if (resp.error.size()) { - display_errors(_printer.get(), resp.error); - App_Exit_Status_Code = CTRL_EX_ERROR; - return; - } else { - _printer->write_output(swoc::bwprint(text, "\xe2\x9c\x94 Reload scheduled [{}]\n", resp.config_token)); - _printer->write_output(swoc::bwprint(text, " Monitor : traffic_ctl config reload -t {} -m", resp.config_token)); - _printer->write_output(swoc::bwprint(text, " Details : traffic_ctl config reload -t {} -s -l", resp.config_token)); - } - - if (resp.tasks.size() > 0) { - const auto &task = resp.tasks[0]; - _printer->as()->print_reload_report(task); - } - } - - // Show warning for inline config (not persisted to disk) - if (configs.size() > 0 && App_Exit_Status_Code != CTRL_EX_ERROR) { - _printer->write_output(""); - _printer->write_output("Note: Inline configuration is NOT persisted to disk."); - _printer->write_output(" Server restart will revert to file-based configuration."); - } + _printer->write_output(invoke_rpc(ConfigReloadRequest{})); } - void ConfigCommand::config_show_file_registry() { @@ -817,7 +390,7 @@ MetricCommand::metric_monitor() }; std::unordered_map summary; - _printer->disable_json_format(); // monitor is not json. + while (!Signal_Flagged.load()) { // Request will hold all metrics in a single message. shared::rpc::JSONRPCResponse const &resp = record_fetch(arg, shared::rpc::NOT_REGEX, RecordQueryType::METRIC); @@ -953,8 +526,6 @@ PluginCommand::PluginCommand(ts::Arguments *args) : CtrlCommand(args) { if (get_parsed_arguments()->get(MSG_STR)) { _invoked_func = [&]() { plugin_msg(); }; - } else if (get_parsed_arguments()->get(LIST_STR)) { - _invoked_func = [&]() { plugin_list(); }; } _printer = std::make_unique(parse_print_opts(args)); } @@ -973,52 +544,6 @@ PluginCommand::plugin_msg() auto response = invoke_rpc(request); _printer->write_output(response); } - -void -PluginCommand::plugin_list() -{ - GetPluginListRequest request; - auto response = invoke_rpc(request); - - if (response.is_error()) { - _printer->write_output(response); - return; - } - - auto info = response.result.as(); - - std::cout << "source: " << info.source << '\n'; - - bool has_load_order = false; - for (const auto &p : info.plugins) { - if (p.load_order >= 0) { - has_load_order = true; - break; - } - } - - if (has_load_order) { - std::cout << " # plugin load_order status\n"; - } else { - std::cout << " # plugin status\n"; - } - - for (const auto &p : info.plugins) { - std::cout << " " << std::right << std::setw(2) << p.index << " " << std::left << std::setw(30) << p.path; - - if (has_load_order) { - char order_buf[12]; - if (p.load_order >= 0) { - snprintf(order_buf, sizeof(order_buf), "%d", p.load_order); - } else { - snprintf(order_buf, sizeof(order_buf), "--"); - } - std::cout << " " << std::left << std::setw(11) << order_buf; - } - - std::cout << " " << p.status << '\n'; - } -} //------------------------------------------------------------------------------------------------------------------------------------ DirectRPCCommand::DirectRPCCommand(ts::Arguments *args) : CtrlCommand(args) { @@ -1060,7 +585,6 @@ DirectRPCCommand::from_file_request() { // TODO: remove all the output messages from here if possible auto filenames = get_parsed_arguments()->get(FILE_STR); - for (auto &&filename : filenames) { std::string text; // run some basic validation on the passed files, they should @@ -1076,7 +600,7 @@ DirectRPCCommand::from_file_request() std::string const &response = invoke_rpc(content); if (_printer->is_json_format()) { // as we have the raw json in here, we cna just directly print it - _printer->write_debug(response); + _printer->write_output(response); } else { _printer->write_output(swoc::bwprint(text, "\n[ {} ]\n --> \n{}\n", filename, content)); _printer->write_output(swoc::bwprint(text, "<--\n{}\n", response)); diff --git a/src/traffic_ctl/CtrlCommands.h b/src/traffic_ctl/CtrlCommands.h index eb6913109c5..8d5fc2b9316 100644 --- a/src/traffic_ctl/CtrlCommands.h +++ b/src/traffic_ctl/CtrlCommands.h @@ -147,15 +147,6 @@ class ConfigCommand : public RecordCommand void config_reload(); void config_show_file_registry(); - // Helper functions for config reload - ConfigReloadResponse fetch_config_reload(std::string const &token, std::string const &count = "1"); - void track_config_reload_progress(std::string const &token, std::chrono::milliseconds refresh_interval, - std::chrono::milliseconds timeout, std::string const &timeout_str); - ConfigReloadResponse config_reload(std::string const &token, bool force, YAML::Node const &configs); - - // Helper to read data from file, stdin, or inline string - std::string read_data_input(std::string const &data_arg); - public: ConfigCommand(ts::Arguments *args); }; @@ -207,9 +198,7 @@ class PluginCommand : public CtrlCommand private: static inline const std::string MSG_STR{"msg"}; - static inline const std::string LIST_STR{"list"}; void plugin_msg(); - void plugin_list(); }; // ----------------------------------------------------------------------------------------------------------------------------------- class DirectRPCCommand : public CtrlCommand diff --git a/src/traffic_ctl/CtrlPrinters.cc b/src/traffic_ctl/CtrlPrinters.cc index 22b934179df..166f6b9c4de 100644 --- a/src/traffic_ctl/CtrlPrinters.cc +++ b/src/traffic_ctl/CtrlPrinters.cc @@ -85,11 +85,6 @@ BasePrinter::write_output(shared::rpc::JSONRPCResponse const &response) void BasePrinter::write_output(std::string_view output) const { - if (is_json_format()) { - // if json format, no other output is expected to avoid mixing formats. - // Specially if you consume the json output with a tool. - return; - } std::cout << output << '\n'; } @@ -104,7 +99,7 @@ BasePrinter::write_output_json(YAML::Node const &node) const YAML::Emitter out; out << YAML::DoubleQuoted << YAML::Flow; out << node; - std::cout << out.c_str() << '\n'; + write_output(std::string_view{out.c_str()}); } //------------------------------------------------------------------------------------------------------------------------------------ void @@ -179,298 +174,7 @@ DiffConfigPrinter::write_output(YAML::Node const &result) void ConfigReloadPrinter::write_output([[maybe_unused]] YAML::Node const &result) { - // no op, ctrl command will handle the output directly. - // BasePrinter will handle the error and the json output if needed. -} -namespace -{ -void -group_files(const ConfigReloadResponse::ReloadInfo &info, std::vector &files) -{ - if (!info.meta.is_main_task) { - files.push_back(&info); - } - for (const auto &sub : info.sub_tasks) { - group_files(sub, files); - } -} - -// Calculate duration in milliseconds from ms-since-epoch timestamps -inline int -duration_ms(int64_t start_ms, int64_t end_ms) -{ - return (end_ms >= start_ms) ? static_cast(end_ms - start_ms) : -1; -} - -// Format millisecond timestamp as human-readable date with milliseconds -// Output format: "YYYY Mon DD HH:MM:SS.mmm" -std::string -format_time_ms(int64_t ms_timestamp) -{ - if (ms_timestamp <= 0) { - return "-"; - } - std::time_t seconds = ms_timestamp / 1000; - int millis = ms_timestamp % 1000; - - std::string buf; - swoc::bwprint(buf, "{}.{:03d}", swoc::bwf::Date(seconds), millis); - return buf; -} - -// Build a UTF-8 progress bar. @a width = number of visual characters. -std::string -build_progress_bar(int done, int total, int width = 20) -{ - int filled = total > 0 ? (done * width / total) : 0; - std::string bar; - bar.reserve(width * 3); - for (int i = 0; i < width; ++i) { - bar += (i < filled) ? "\xe2\x96\x88" : "\xe2\x96\x91"; // █ or ░ - } - return bar; -} - -// Human-readable duration string from milliseconds. -std::string -format_duration(int ms) -{ - if (ms < 0) { - return "-"; - } - if (ms < 1000) { - return std::to_string(ms) + "ms"; - } - if (ms < 60000) { - return std::to_string(ms / 1000) + "." + std::to_string((ms % 1000) / 100) + "s"; - } - return std::to_string(ms / 60000) + "m " + std::to_string((ms % 60000) / 1000) + "s"; -} - -// Map task status string to a single-character icon for compact display. -const char * -status_icon(const std::string &status) -{ - if (status == "success") { - return "\xe2\x9c\x94"; // ✔ - } - if (status == "fail") { - return "\xe2\x9c\x97"; // ✗ - } - if (status == "in_progress" || status == "created") { - return "\xe2\x97\x8c"; // ◌ - } - if (status == "timeout") { - return "\xe2\x9f\xb3"; // ⟳ - } - return "?"; -} - -// Approximate visual width of a UTF-8 string (each code point counts as 1 column). -int -visual_width(const std::string &s) -{ - int w = 0; - for (size_t i = 0; i < s.size();) { - auto c = static_cast(s[i]); - if (c < 0x80) { - ++i; - } else if (c < 0xE0) { - i += 2; - } else if (c < 0xF0) { - i += 3; - } else { - i += 4; - } - ++w; - } - return w; -} - -// Build a dot-leader string: " ···· " of the given visual width (min 2). -std::string -dot_fill(int width) -{ - if (width < 2) { - width = 2; - } - std::string out(" "); - for (int i = 1; i < width - 1; ++i) { - out += "\xc2\xb7"; // · (middle dot U+00B7) - } - out += ' '; - return out; -} - -// Recursively print a task and its children using tree-drawing characters. -// @param prefix characters printed before this task's icon (tree connectors from parent) -// @param child_prefix base prefix for this task's log lines and its children's connectors -// @param content_width visual columns available for icon+name+dots+duration (shrinks per nesting) -void -print_task_tree(const ConfigReloadResponse::ReloadInfo &f, bool full_report, const std::string &prefix, - const std::string &child_prefix, DiagsLevel min_level = DL_Undefined, int content_width = 55) -{ - std::string fname; - if (f.filename.empty() || f.filename == "") { - fname = f.description; - } else { - fname = f.filename; - } - - int dur_ms = duration_ms(f.meta.created_time_ms, f.meta.last_updated_time_ms); - - // Build label and right-aligned duration - std::string label = std::string(status_icon(f.status)) + " " + fname; - std::string dur_str = format_duration(dur_ms); - - // Right-pad duration to fixed width so values align - constexpr int DUR_COL = 6; - while (static_cast(dur_str.size()) < DUR_COL) { - dur_str = " " + dur_str; - } - - // Dot fill between label and duration - int label_vw = visual_width(label); - int gap = content_width - label_vw - DUR_COL; - - std::cout << prefix << label << dot_fill(gap) << dur_str; - - // Annotate non-success terminal states so failures stand out - if (f.status == "fail") { - std::cout << " \xe2\x9c\x97 FAIL"; - } else if (f.status == "timeout") { - std::cout << " \xe2\x9f\xb3 TIMEOUT"; - } - std::cout << "\n"; - - bool has_children = !f.sub_tasks.empty(); - - // Log lines: indented under the task, with tree continuation line if children follow. - if (full_report && !f.logs.empty()) { - std::string log_pfx = has_children ? (child_prefix + "\xe2\x94\x82 ") : (child_prefix + " "); - for (const auto &entry : f.logs) { - if (min_level != DL_Undefined && entry.level != DL_Undefined && entry.level < min_level) { - continue; - } - std::cout << log_pfx; - if (entry.level != DL_Undefined) { - // Indexed by DiagsLevel enum. In practice only [Dbg], [Note], [Warn], [Err] appear - // in task logs — Fatal/Alert/Emergency terminate the process before any task completes. - static constexpr const char *severity_tags[] = { - "[Diag] ", "[Dbg] ", "[Stat] ", "[Note] ", "[Warn] ", "[Err] ", "[Fatal] ", "[Alert] ", "[Emrg] ", - }; - int idx = std::min(std::max(static_cast(entry.level), 0), static_cast(DL_Emergency)); - std::cout << severity_tags[idx]; - } - std::cout << entry.text << '\n'; - } - } - - // Children: draw tree connectors. Each nesting level eats 3 visual columns. - for (size_t i = 0; i < f.sub_tasks.size(); ++i) { - bool is_last = (i == f.sub_tasks.size() - 1); - std::string sub_prefix = child_prefix + (is_last ? "\xe2\x94\x94\xe2\x94\x80 " : "\xe2\x94\x9c\xe2\x94\x80 "); - std::string sub_child_prefix = child_prefix + (is_last ? " " : "\xe2\x94\x82 "); - print_task_tree(f.sub_tasks[i], full_report, sub_prefix, sub_child_prefix, min_level, content_width - 3); - } -} - -} // namespace -void -ConfigReloadPrinter::write_progress_line(const ConfigReloadResponse::ReloadInfo &info) -{ - if (this->is_json_format()) { - return; - } - - int done{0}, total{0}; - - auto count_tasks = [&](auto &&self, const ConfigReloadResponse::ReloadInfo &ri) -> void { - if (ri.sub_tasks.empty()) { - if (ri.status == "success" || ri.status == "fail") { - done++; - } - total++; - } - for (const auto &sub : ri.sub_tasks) { - self(self, sub); - } - }; - count_tasks(count_tasks, info); - - bool terminal = (info.status == "success" || info.status == "fail" || info.status == "timeout"); - - int dur_ms = duration_ms(info.meta.created_time_ms, info.meta.last_updated_time_ms); - - std::string bar = build_progress_bar(done, total); - - // \r + ANSI clear-to-EOL overwrites the previous line in place. - std::cout << "\r\033[K" << status_icon(info.status) << " [" << info.config_token << "] " << bar << " " << done << "/" << total - << " " << info.status; - if (terminal) { - std::cout << " (" << format_duration(dur_ms) << ")"; - } - std::cout << std::flush; -} - -void -ConfigReloadPrinter::print_reload_report(const ConfigReloadResponse::ReloadInfo &info, bool full_report) -{ - if (this->is_json_format()) { - return; - } - - int overall_duration = duration_ms(info.meta.created_time_ms, info.meta.last_updated_time_ms); - - int total{0}, completed{0}, failed{0}, created{0}, in_progress{0}; - - auto calculate_summary = [&](auto &&self, const ConfigReloadResponse::ReloadInfo &ri) -> void { - if (ri.sub_tasks.empty()) { - if (ri.status == "success") { - completed++; - } else if (ri.status == "fail") { - failed++; - } else if (ri.status == "created") { - created++; - } else if (ri.status == "in_progress") { - in_progress++; - } - total++; - } - if (!ri.sub_tasks.empty()) { - for (const auto &sub : ri.sub_tasks) { - self(self, sub); - } - } - }; - - std::vector files; - group_files(info, files); - calculate_summary(calculate_summary, info); - - std::string start_time_str = format_time_ms(info.meta.created_time_ms); - std::string end_time_str = format_time_ms(info.meta.last_updated_time_ms); - - // ── Header ── - std::cout << status_icon(info.status) << " Reload [" << info.status << "] \xe2\x80\x94 " << info.config_token << "\n"; - std::cout << " Started : " << start_time_str << '\n'; - std::cout << " Finished: " << end_time_str << '\n'; - std::cout << " Duration: " << format_duration(overall_duration) << "\n\n"; - - // ── Summary ── - std::cout << " \xe2\x9c\x94 " << completed << " success \xe2\x97\x8c " << in_progress << " in-progress \xe2\x9c\x97 " << failed - << " failed (" << total << " total)\n"; - - // ── Task tree ── - if (!files.empty()) { - std::cout << "\n Tasks:\n"; - } - const std::string base_prefix(" "); - for (const auto &sub : info.sub_tasks) { - print_task_tree(sub, full_report, base_prefix, base_prefix, _min_level); - } } - //------------------------------------------------------------------------------------------------------------------------------------ void ConfigShowFileRegistryPrinter::write_output(YAML::Node const &result) diff --git a/src/traffic_ctl/CtrlPrinters.h b/src/traffic_ctl/CtrlPrinters.h index dcae30837f8..0c724942d84 100644 --- a/src/traffic_ctl/CtrlPrinters.h +++ b/src/traffic_ctl/CtrlPrinters.h @@ -24,8 +24,6 @@ #include #include -#include "jsonrpc/CtrlRPCRequests.h" -#include "jsonrpc/ctrl_yaml_codecs.h" #include "shared/rpc/RPCRequests.h" #include @@ -114,16 +112,8 @@ class BasePrinter Options::FormatFlags get_format() const; bool print_rpc_message() const; bool is_json_format() const; - void disable_json_format(); - - bool is_records_format() const; - bool should_include_default() const; - - /// In case a derived class needs to call derived class functions. Ugly but works. - /// Note: CRTP may worth a try. - template // TODO, move it down the file. - const Derived *as() const; - template Derived *as(); + bool is_records_format() const; + bool should_include_default() const; protected: void write_output_json(YAML::Node const &node) const; @@ -166,12 +156,6 @@ BasePrinter::is_json_format() const return _printOpt._format & Options::FormatFlags::JSON; } -inline void -BasePrinter::disable_json_format() -{ - _printOpt._format = static_cast(_printOpt._format & ~Options::FormatFlags::JSON); -} - inline bool BasePrinter::is_records_format() const { @@ -225,24 +209,8 @@ class ConfigReloadPrinter : public BasePrinter { void write_output(YAML::Node const &result) override; - DiagsLevel _min_level{DL_Undefined}; - public: ConfigReloadPrinter(BasePrinter::Options opt) : BasePrinter(opt) {} - - void - set_min_level(DiagsLevel level) - { - _min_level = level; - } - DiagsLevel - min_level() const - { - return _min_level; - } - - void print_reload_report(const ConfigReloadResponse::ReloadInfo &info, bool full_report = false); - void write_progress_line(const ConfigReloadResponse::ReloadInfo &info); }; //------------------------------------------------------------------------------------------------------------------------------------ class ConfigShowFileRegistryPrinter : public BasePrinter @@ -325,6 +293,7 @@ class RPCAPIPrinter : public BasePrinter RPCAPIPrinter(BasePrinter::Options opt) : BasePrinter(opt) {} }; //------------------------------------------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------------------------------------------ class ServerStatusPrinter : public BasePrinter { void write_output(YAML::Node const &result) override; @@ -333,26 +302,3 @@ class ServerStatusPrinter : public BasePrinter ServerStatusPrinter(BasePrinter::Options opt) : BasePrinter(opt) {} }; //------------------------------------------------------------------------------------------------------------------------------------ - -/// In case a derived class needs to call derived class functions. Ugly but works. -/// Note: CRTP may worth a try. -template // TODO, move it down the file. -const Derived * -BasePrinter::as() const -{ - auto r = dynamic_cast(this); - if (!r) { - throw std::runtime_error("Internal error: Couldn't get Derived instance"); - } - return r; -} -template -Derived * -BasePrinter::as() -{ - auto r = dynamic_cast(this); - if (!r) { - throw std::runtime_error("Internal error: Couldn't get Derived instance"); - } - return r; -} diff --git a/src/traffic_ctl/SSLMultiCertCommand.cc b/src/traffic_ctl/SSLMultiCertCommand.cc deleted file mode 100644 index ebc5d00f357..00000000000 --- a/src/traffic_ctl/SSLMultiCertCommand.cc +++ /dev/null @@ -1,84 +0,0 @@ -/** @file - - SSL Multi-Certificate configuration command for traffic_ctl. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include "SSLMultiCertCommand.h" -#include "config/ssl_multicert.h" -#include "tscore/Layout.h" -#include "tscore/Filenames.h" - -#include - -namespace -{ - -/// Get the default ssl_multicert.yaml file path. -std::string -get_default_ssl_multicert_path() -{ - std::string sysconfdir; - if (char const *env = getenv("PROXY_CONFIG_CONFIG_DIR")) { - sysconfdir = Layout::get()->relative(env); - } else { - sysconfdir = Layout::get()->sysconfdir; - } - return Layout::get()->relative_to(sysconfdir, ts::filename::SSL_MULTICERT); -} - -} // namespace - -SSLMultiCertCommand::SSLMultiCertCommand(ts::Arguments *args) : CtrlCommand(args) -{ - BasePrinter::Options print_opts{parse_print_opts(args)}; - _printer = std::make_unique(print_opts); - - if (args->get("show")) { - // Default to YAML; use JSON only if explicitly requested. - _output_json = args->get("json"); - _invoked_func = [this]() { show_config(); }; - } else { - throw std::invalid_argument("Unsupported ssl-multicert subcommand"); - } -} - -void -SSLMultiCertCommand::show_config() -{ - std::string const filename = get_default_ssl_multicert_path(); - - config::SSLMultiCertParser parser; - config::ConfigResult result = parser.parse(filename); - - if (!result.ok()) { - std::string error_msg = "Failed to parse ssl_multicert config"; - if (!result.errata.empty()) { - error_msg += ": "; - error_msg += std::string(result.errata.front().text()); - } - _printer->write_output(error_msg); - return; - } - - config::SSLMultiCertMarshaller marshaller; - std::string const output = _output_json ? marshaller.to_json(result.value) : marshaller.to_yaml(result.value); - _printer->write_output(output); -} diff --git a/src/traffic_ctl/SSLMultiCertCommand.h b/src/traffic_ctl/SSLMultiCertCommand.h deleted file mode 100644 index d796d1d5b2c..00000000000 --- a/src/traffic_ctl/SSLMultiCertCommand.h +++ /dev/null @@ -1,47 +0,0 @@ -/** @file - - SSL Multi-Certificate configuration command for traffic_ctl. - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#pragma once - -#include "CtrlCommands.h" - -/** - * Command handler for ssl-multicert configuration operations. - * - * Supports reading and displaying ssl_multicert configuration in JSON or YAML format. - */ -class SSLMultiCertCommand : public CtrlCommand -{ -public: - /** - * Construct the command from parsed arguments. - * - * @param[in] args Parsed command line arguments. - */ - SSLMultiCertCommand(ts::Arguments *args); - -private: - void show_config(); - - bool _output_json = false; -}; diff --git a/src/traffic_ctl/TrafficCtlStatus.h b/src/traffic_ctl/TrafficCtlStatus.h index 61e84a80542..b7d758fd25c 100644 --- a/src/traffic_ctl/TrafficCtlStatus.h +++ b/src/traffic_ctl/TrafficCtlStatus.h @@ -24,6 +24,5 @@ constexpr int CTRL_EX_OK = 0; // EXIT_FAILURE can also be used. constexpr int CTRL_EX_ERROR = 2; constexpr int CTRL_EX_UNIMPLEMENTED = 3; -constexpr int CTRL_EX_TEMPFAIL = 75; ///< Temporary failure — operation in progress, retry later (EX_TEMPFAIL from sysexits.h). extern int App_Exit_Status_Code; //!< Global variable to store the exit status code of the application. diff --git a/src/traffic_ctl/jsonrpc/CtrlRPCRequests.h b/src/traffic_ctl/jsonrpc/CtrlRPCRequests.h index f3e51b77ce6..7e11670856e 100644 --- a/src/traffic_ctl/jsonrpc/CtrlRPCRequests.h +++ b/src/traffic_ctl/jsonrpc/CtrlRPCRequests.h @@ -23,9 +23,6 @@ // We base on the common client types. #include "shared/rpc/RPCRequests.h" -#include "tsutil/ts_diag_levels.h" - -#include /// This file defines all the traffic_ctl API client request and responses objects needed to model the jsonrpc messages used in the /// TS JSONRPC Node API. @@ -42,72 +39,15 @@ struct GetAllRecordsRequest : shared::rpc::RecordLookupRequest { }; //------------------------------------------------------------------------------------------------------------------------------------ /// -/// @brief Models the config reload request. Supports both file-based and rpc-supplied modes. -/// rpc-supplied mode is triggered when configs is present. +/// @brief Models the config reload request. No params are needed. /// struct ConfigReloadRequest : shared::rpc::ClientRequest { - struct Params { - std::string token; - bool force{false}; - YAML::Node configs; // Optional: if present, triggers inline mode - }; - ConfigReloadRequest(Params p) { super::params = std::move(p); } std::string get_method() const override { return "admin_config_reload"; } }; - -// Full list of reload tasks, could be nested. -struct ConfigReloadResponse { - // Existing reload task info, could be nested. - struct LogEntry { - DiagsLevel level{DL_Undefined}; ///< DL_Undefined for state-change messages - std::string text; - }; - - struct ReloadInfo { - std::string config_token; - std::string status; - std::string description; - std::string filename; - std::vector logs; - std::vector sub_tasks; - struct Meta { // internal info. - int64_t created_time_ms{0}; - int64_t last_updated_time_ms{0}; - bool is_main_task{false}; - } meta; - }; - - struct Error { - int code; - std::string message; - }; - std::vector error; ///< Error list, if any. - - // when requesting existing tasks. - std::vector tasks; - - std::string created_time; - std::vector messages; - std::string config_token; -}; - -struct FetchConfigReloadStatusRequest : shared::rpc::ClientRequest { - struct Params { - std::string token; - std::string count{"1"}; // number of latest reloads to return, 0 means all. - }; - FetchConfigReloadStatusRequest(Params p) { super::params = std::move(p); } - std::string - get_method() const override - { - return "get_reload_config_status"; - } -}; - //------------------------------------------------------------------------------------------------------------------------------------ /// /// @brief To fetch config file registry from the RPC node. @@ -198,28 +138,6 @@ struct HostDBGetStatusRequest : shared::rpc::ClientRequest { return "get_hostdb_status"; } }; -//------------------------------------------------------------------------------------------------------------------------------------ -struct GetPluginListRequest : shared::rpc::ClientRequest { - using super = shared::rpc::ClientRequest; - std::string - get_method() const override - { - return "admin_plugin_get_list"; - } -}; - -struct PluginListResponse { - struct PluginInfo { - std::string path; - bool enabled{false}; - std::string status; - int index{0}; - int load_order{-1}; - }; - std::string source; - std::vector plugins; -}; - //------------------------------------------------------------------------------------------------------------------------------------ struct BasicPluginMessageRequest : shared::rpc::ClientRequest { using super = BasicPluginMessageRequest; diff --git a/src/traffic_ctl/jsonrpc/ctrl_yaml_codecs.h b/src/traffic_ctl/jsonrpc/ctrl_yaml_codecs.h index 3f9ff1016fd..1bf7ebac52c 100644 --- a/src/traffic_ctl/jsonrpc/ctrl_yaml_codecs.h +++ b/src/traffic_ctl/jsonrpc/ctrl_yaml_codecs.h @@ -41,114 +41,6 @@ template <> struct convert { return node; } }; -//------------------------------------------------------------------------------------------------------------------------------------ - -template <> struct convert { - static Node - encode(ConfigReloadRequest::Params const ¶ms) - { - Node node; - if (!params.token.empty()) { - node["token"] = params.token; - } - - if (params.force) { - node["force"] = params.force; - } - - // Include configs if present (triggers inline mode on server) - if (params.configs && params.configs.IsMap() && params.configs.size() > 0) { - node["configs"] = params.configs; - } - - return node; - } -}; - -template <> struct convert { - static Node - encode(FetchConfigReloadStatusRequest::Params const ¶ms) - { - Node node; - auto is_number = [](const std::string &s) { - return !s.empty() && std::find_if(s.begin(), s.end(), [](unsigned char c) { return !std::isdigit(c); }) == s.end(); - }; - // either passed values or defaults. - node["token"] = params.token; - - if (!params.count.empty() && (!is_number(params.count) && params.count != "all")) { - throw std::invalid_argument("Invalid 'count' value, must be numeric or 'all'"); - } - - if (!params.count.empty()) { - if (params.count == "all") { - node["count"] = 0; // 0 means all. - } else { - node["count"] = std::stoi(params.count); - } - } - - return node; - } -}; - -template <> struct convert { - static bool - decode(Node const &node, ConfigReloadResponse &out) - { - auto get_info = [](auto &&self, YAML::Node const &from) -> ConfigReloadResponse::ReloadInfo { - ConfigReloadResponse::ReloadInfo info; - info.config_token = helper::try_extract(from, "config_token"); - info.status = helper::try_extract(from, "status"); - info.description = helper::try_extract(from, "description", false, std::string{""}); - info.filename = helper::try_extract(from, "filename", false, std::string{""}); - for (auto &&log : from["logs"]) { - if (log.IsMap()) { - ConfigReloadResponse::LogEntry entry; - entry.level = static_cast(log["level"].as(DL_Undefined)); - entry.text = log["text"].as(); - info.logs.push_back(entry); - } else { - info.logs.push_back({DL_Undefined, log.as()}); - } - } - - for (auto &&sub : from["sub_tasks"]) { - info.sub_tasks.push_back(self(self, sub)); - } - - if (auto meta = from["meta"]) { - info.meta.created_time_ms = helper::try_extract(meta, "created_time_ms"); - info.meta.last_updated_time_ms = helper::try_extract(meta, "last_updated_time_ms"); - info.meta.is_main_task = helper::try_extract(meta, "main_task"); - } - return info; - }; - - // Server sends "errors" (plural) - if (node["errors"]) { - for (auto &&err : node["errors"]) { - ConfigReloadResponse::Error e; - e.code = helper::try_extract(err, "code"); - e.message = helper::try_extract(err, "message"); - out.error.push_back(std::move(e)); - } - } - out.created_time = helper::try_extract(node, "created_time"); - for (auto &&msg : node["message"]) { - out.messages.push_back(msg.as()); - } - out.config_token = helper::try_extract(node, "token"); - - for (auto &&element : node["tasks"]) { - ConfigReloadResponse::ReloadInfo task = get_info(get_info, element); - out.tasks.push_back(std::move(task)); - } - - return true; - } -}; - //------------------------------------------------------------------------------------------------------------------------------------ template <> struct convert { static Node @@ -189,31 +81,6 @@ template <> struct convert { } }; //------------------------------------------------------------------------------------------------------------------------------------ -template <> struct convert { - static bool - decode(Node const &node, PluginListResponse &out) - { - if (auto data = node["data"]; data) { - out.source = helper::try_extract(data, "source"); - for (const auto &p : data["plugins"]) { - PluginListResponse::PluginInfo info; - - info.path = helper::try_extract(p, "path"); - info.enabled = helper::try_extract(p, "enabled"); - info.status = helper::try_extract(p, "status"); - if (p["index"]) { - info.index = p["index"].as(); - } - if (p["load_order"]) { - info.load_order = p["load_order"].as(); - } - out.plugins.emplace_back(std::move(info)); - } - } - return true; - } -}; -//------------------------------------------------------------------------------------------------------------------------------------ template <> struct convert { static Node encode(BasicPluginMessageRequest::Params const ¶ms) diff --git a/src/traffic_ctl/traffic_ctl.cc b/src/traffic_ctl/traffic_ctl.cc index 9697aaa05da..1f6bc416867 100644 --- a/src/traffic_ctl/traffic_ctl.cc +++ b/src/traffic_ctl/traffic_ctl.cc @@ -31,9 +31,7 @@ #include "tscore/signals.h" #include "CtrlCommands.h" -#include "ConvertConfigCommand.h" #include "FileConfigCommand.h" -#include "SSLMultiCertCommand.h" #include "TrafficCtlStatus.h" // Define the global variable @@ -90,8 +88,7 @@ main([[maybe_unused]] int argc, const char **argv) .add_option("--run-root", "", "using TS_RUNROOT as sandbox", "TS_RUNROOT", 1) .add_option("--format", "-f", "Use a specific output format {json|rpc}", "", 1, "", "format") .add_option("--read-timeout-ms", "", "Read timeout for RPC (in milliseconds)", "", 1, "10000", "read-timeout") - .add_option("--read-attempts", "", "Read attempts for RPC", "", 1, "100", "read-attempts") - .add_option("--watch", "-w", "Execute a program periodically. Watch interval(in seconds) can be passed.", "", 1, "-1", "watch"); + .add_option("--read-attempts", "", "Read attempts for RPC", "", 1, "100", "read-attempts"); auto &config_command = parser.add_command("config", "Manipulate configuration records").require_commands(); auto &metric_command = parser.add_command("metric", "Manipulate performance metrics").require_commands(); @@ -123,63 +120,9 @@ main([[maybe_unused]] int argc, const char **argv) .add_example_usage("traffic_ctl config match [OPTIONS] REGEX [REGEX ...]") .add_option("--records", "", "Emit output in YAML format") .add_option("--default", "", "Include the default value"); - - // - // Start a new reload. If used without any extra options, it will start a new reload - // or show the details of the current reload if one is in progress. - // A new token will be assigned by the server if no token is provided. - config_command.add_command("reload", "Request a configuration reload", [&]() { command->execute(); }) - .add_example_usage("traffic_ctl config reload") - // - // Start a new reload with a specific token. If no token is provided, the server will assign one. - // If a reload is already in progress, it will try to show the details of the current reload. - // If token already exists, you must use another token, or let the server assign one. - .add_option("--token", "-t", "Configuration token to reload.", "", 1, "") - // - // Start a new reload and monitor its progress until completion. - // Polls the server at regular intervals (see --refresh-int). - // If a reload is already in progress, monitors that one instead. - .add_option("--monitor", "-m", "Monitor reload progress until completion") - // - // Start a new reload. if one in progress it will show de details of the current reload. - // if no reload in progress, it will start a new one and it will show the details of it. - // This cannot be used with --monitor, if both are set, --show-details will be ignored. - .add_option("--show-details", "-s", "Show detailed information of the reload.") - .add_option("--include-logs", "-l", "include logs in the details. only work together with --show-details") - - // - // Refresh interval in seconds used with --monitor. - // Controls how often to poll the server for reload status. - .add_option("--refresh-int", "-r", "Refresh interval in seconds (used with --monitor). Accepts fractional values (e.g. 0.5)", - "", 1, "0.5") - // - // The server will not let you start two reload at the same time. This option will force a new reload - // even if there is one in progress. Use with caution as this may have unexpected results. - // This is mostly for debugging and testing purposes. note: Should we keep it here? - .add_option("--force", "-F", "Force reload even if there are unsaved changes") - // - // Pass inline config data for reload. Like curl's -d flag: - // -d @file.yaml - read config from file - // -d @file1.yaml @file2.yaml - read multiple files - // -d @- - read config from stdin - // -d "yaml: content" - inline yaml string - .add_option("--data", "-d", "Inline config data (@file, @- for stdin, or yaml string)", "", MORE_THAN_ZERO_ARG_N, "") - .add_option("--directive", "-D", "Pass a reload directive to a config handler (format: config_key.directive_key=value)", "", - MORE_THAN_ZERO_ARG_N, "") - .add_option( - "--initial-wait", "-w", - "Initial wait before first poll, giving the server time to schedule all handlers (seconds). Accepts fractional values", "", 1, - "2") - .add_option("--timeout", "-T", - "Maximum time to wait for reload completion (used with --monitor). " - "Accepts duration units: 30s, 1m, 500ms, etc. 0 means no timeout", - "", 1, "0") - .with_required("--monitor"); - - config_command.add_command("status", "Check the configuration status", [&]() { command->execute(); }) - .add_option("--token", "-t", "Configuration token to check status.", "", 1, "") - .add_option("--count", "-c", "Number of status records to return. Use numeric or 'all' to get the full history", "", 1, "") - .add_option("--min-level", "", "Minimum severity level for log entries: debug, note, warning, error", "", 1, "") + config_command.add_command("reload", "Request a configuration reload", Command_Execute) + .add_example_usage("traffic_ctl config reload"); + config_command.add_command("status", "Check the configuration status", Command_Execute) .add_example_usage("traffic_ctl config status"); config_command.add_command("set", "Set a configuration value", "", 2, Command_Execute) .add_option("--cold", "-c", @@ -200,34 +143,6 @@ main([[maybe_unused]] int argc, const char **argv) config_command.add_command("registry", "Show configuration file registry", Command_Execute) .add_example_usage("traffic_ctl config registry"); - - // ssl-multicert subcommand - auto &ssl_multicert_command = - config_command.add_command("ssl-multicert", "Manage ssl_multicert configuration").require_commands(); - auto &ssl_multicert_show = ssl_multicert_command.add_command("show", "Show the ssl_multicert configuration", Command_Execute) - .add_example_usage("traffic_ctl config ssl-multicert show") - .add_example_usage("traffic_ctl config ssl-multicert show --yaml") - .add_example_usage("traffic_ctl config ssl-multicert show --json"); - ssl_multicert_show.add_mutex_group("format", false, "Output format"); - ssl_multicert_show.add_option_to_group("format", "--yaml", "-y", "Output in YAML format (default)"); - ssl_multicert_show.add_option_to_group("format", "--json", "-j", "Output in JSON format"); - - // convert subcommand - convert config files between formats - auto &convert_command = config_command.add_command("convert", "Convert configuration files to YAML format").require_commands(); - convert_command.add_command("ssl_multicert", "Convert ssl_multicert.config to ssl_multicert.yaml", "", 2, Command_Execute) - .add_example_usage("traffic_ctl config convert ssl_multicert ") - .add_example_usage("traffic_ctl config convert ssl_multicert ssl_multicert.config ssl_multicert.yaml") - .add_example_usage("traffic_ctl config convert ssl_multicert ssl_multicert.config - # output to stdout"); - convert_command.add_command("storage", "Convert storage.config + volume.config to storage.yaml", "", 3, Command_Execute) - .add_example_usage("traffic_ctl config convert storage ") - .add_example_usage("traffic_ctl config convert storage storage.config volume.config storage.yaml") - .add_example_usage("traffic_ctl config convert storage storage.config volume.config - # output to stdout"); - convert_command.add_command("plugin_config", "Convert plugin.config to plugin.yaml", "", 2, Command_Execute) - .add_example_usage("traffic_ctl config convert plugin_config ") - .add_example_usage("traffic_ctl config convert plugin_config plugin.config plugin.yaml") - .add_example_usage("traffic_ctl config convert plugin_config plugin.config - # output to stdout") - .add_option("--skip-disabled", "", "Omit commented-out (disabled) plugins from the output"); - // host commands host_command.add_command("status", "Get one or more host statuses", "", MORE_THAN_ZERO_ARG_N, Command_Execute) .add_example_usage("traffic_ctl host status HOST [HOST ...]"); @@ -264,8 +179,6 @@ main([[maybe_unused]] int argc, const char **argv) plugin_command .add_command("msg", "Send message to plugins - a TAG and the message DATA(optional)", "", MORE_THAN_ONE_ARG_N, Command_Execute) .add_example_usage("traffic_ctl plugin msg TAG DATA"); - plugin_command.add_command("list", "Show globally loaded plugins and their status", "", 0, Command_Execute) - .add_example_usage("traffic_ctl plugin list"); // server commands server_command.add_command("backtrace", "Show a full stack trace of the traffic_server process", @@ -317,12 +230,6 @@ main([[maybe_unused]] int argc, const char **argv) auto create_command = [](ts::Arguments &args) -> std::unique_ptr { if (args.get("config")) { - if (args.get("convert")) { - return std::make_unique(&args); - } - if (args.get("ssl-multicert")) { - return std::make_unique(&args); - } if (args.get("cold")) { return std::make_unique(&args); } diff --git a/src/traffic_layout/info.cc b/src/traffic_layout/info.cc index dfdbc6915a0..0a4f44491c0 100644 --- a/src/traffic_layout/info.cc +++ b/src/traffic_layout/info.cc @@ -100,11 +100,6 @@ produce_features(bool json) #else print_feature("TS_HAS_ZSTD", 0, json); #endif -#if HAVE_SSL_CTX_ADD_CERT_COMPRESSION_ALG || HAVE_SSL_CTX_SET1_CERT_COMP_PREFERENCE - print_feature("TS_HAS_CERT_COMPRESSION", 1, json); -#else - print_feature("TS_HAS_CERT_COMPRESSION", 0, json); -#endif #ifdef F_GETPIPE_SZ print_feature("TS_HAS_PIPE_BUFFER_SIZE_CONFIG", 1, json); #else @@ -178,6 +173,7 @@ produce_layout(bool json) print_var(ts::filename::SSL_MULTICERT, RecConfigReadConfigPath("proxy.config.ssl.server.multicert.filename"), json); print_var(ts::filename::STORAGE, RecConfigReadConfigPath(nullptr, ts::filename::STORAGE), json); print_var(ts::filename::HOSTING, RecConfigReadConfigPath("proxy.config.cache.hosting_filename"), json); + print_var(ts::filename::VOLUME, RecConfigReadConfigPath("proxy.config.cache.volume_filename"), json); print_var(ts::filename::IP_ALLOW, RecConfigReadConfigPath("proxy.config.cache.ip_allow.filename"), json, true); if (json) { printf("}\n"); diff --git a/src/traffic_logstats/CMakeLists.txt b/src/traffic_logstats/CMakeLists.txt index c51118454d8..f14d2b5381a 100644 --- a/src/traffic_logstats/CMakeLists.txt +++ b/src/traffic_logstats/CMakeLists.txt @@ -16,7 +16,7 @@ ####################### add_executable(traffic_logstats logstats.cc) -target_link_libraries(traffic_logstats PRIVATE ts::logging ts::tscore ts::diagsconfig ts::records ts::configmanager) +target_link_libraries(traffic_logstats PRIVATE ts::logging ts::tscore ts::diagsconfig ts::configmanager) install(TARGETS traffic_logstats) diff --git a/src/traffic_server/CMakeLists.txt b/src/traffic_server/CMakeLists.txt index f50ff2ef824..8bd5b8f2847 100644 --- a/src/traffic_server/CMakeLists.txt +++ b/src/traffic_server/CMakeLists.txt @@ -27,6 +27,7 @@ target_link_libraries( ts::http2 ts::logging ts::hdrs + ts::configmanager ts::diagsconfig ts::inkutils ts::inkdns @@ -41,7 +42,6 @@ target_link_libraries( ts::jsonrpc_protocol ts::jsonrpc_server ts::rpcpublichandlers - ts::configmanager ) if(NOT APPLE) # Skipping apple because macOS doesn't seem to provide an equivalent option diff --git a/src/traffic_server/RpcAdminPubHandlers.cc b/src/traffic_server/RpcAdminPubHandlers.cc index 1e053db966b..4ab2706f444 100644 --- a/src/traffic_server/RpcAdminPubHandlers.cc +++ b/src/traffic_server/RpcAdminPubHandlers.cc @@ -38,10 +38,7 @@ register_admin_jsonrpc_handlers() using namespace rpc::handlers::config; rpc::add_method_handler("admin_config_set_records", &set_config_records, &core_ats_rpc_service_provider_handle, {{rpc::RESTRICTED_API}}); - // Unified reload handler - supports both file-based and rpc-supplied modes. rpc::add_method_handler("admin_config_reload", &reload_config, &core_ats_rpc_service_provider_handle, {{rpc::RESTRICTED_API}}); - rpc::add_method_handler("get_reload_config_status", &get_reload_config_status, &core_ats_rpc_service_provider_handle, - {{rpc::RESTRICTED_API}}); // HostDB using namespace rpc::handlers::hostdb; @@ -56,8 +53,6 @@ register_admin_jsonrpc_handlers() using namespace rpc::handlers::plugins; rpc::add_method_handler("admin_plugin_send_basic_msg", &plugin_send_basic_msg, &core_ats_rpc_service_provider_handle, {{rpc::RESTRICTED_API}}); - rpc::add_method_handler("admin_plugin_get_list", &get_plugin_list, &core_ats_rpc_service_provider_handle, - {{rpc::NON_RESTRICTED_API}}); // server using namespace rpc::handlers::server; diff --git a/src/traffic_server/traffic_server.cc b/src/traffic_server/traffic_server.cc index a9ea60fcab1..c04e0ef3116 100644 --- a/src/traffic_server/traffic_server.cc +++ b/src/traffic_server/traffic_server.cc @@ -92,8 +92,6 @@ extern "C" int plock(int); #include "iocore/eventsystem/RecProcess.h" #include "proxy/Transform.h" #include "iocore/eventsystem/ConfigProcessor.h" -#include "mgmt/config/ConfigContextDiags.h" -#include "mgmt/config/ConfigRegistry.h" #include "proxy/http/HttpProxyServerMain.h" #include "proxy/http/HttpBodyFactory.h" #include "proxy/ProxySession.h" @@ -141,6 +139,8 @@ extern void load_config_file_callback(const char *parent_file, const char *remap extern HttpBodyFactory *body_factory; +extern void initializeRegistry(); + extern void Initialize_Errata_Settings(); namespace @@ -265,7 +265,7 @@ DbgCtl dbg_ctl_diags{"diags"}; DbgCtl dbg_ctl_hugepages{"hugepages"}; DbgCtl dbg_ctl_rpc_init{"rpc.init"}; DbgCtl dbg_ctl_statsproc{"statsproc"}; -DbgCtl dbg_ctl_conf_reload{"confreload"}; + struct AutoStopCont : public Continuation { int mainEvent(int /* event */, Event * /* e */) @@ -736,58 +736,10 @@ initialize_records() ts::Metrics::StaticString::createString("proxy.process.version.server.build_person", version.build_person()); } -// register_config_files -// -// Registration point for records.yaml and static (non-reloadable) config files. -// -// Most reloadable config files (ip_allow, sni, logging, etc.) register -// themselves via ConfigRegistry::register_config() in their own modules -// (IPAllow.cc, SSLClientCoordinator.cc, LogConfig.cc, etc.). -// -// records.yaml is special: -// - It is first read at startup inside RecCoreInit() (src/records/RecCore.cc), -// which is called through RecProcessInit() → initialize_records() well before -// this function. -// - On reload (file change detected by FileManager), process_config_update() in -// FileManager.cc delegates to ConfigRegistry::execute_reload("records"), which -// invokes the handler below. -// - The handler calls RecReadYamlConfigFile() (src/records/P_RecCore.cc), the same -// function used at startup, to re-parse the file. -// -// Static/non-reloadable files (storage.config, socks.config, volume.config, -// plugin.config, jsonrpc.yaml) are registered via register_static_file() for -// inventory purposes (filemanager.get_files_registry RPC endpoint and future work). -// void -register_config_files() +initialize_file_manager() { - using namespace config; - auto ® = ConfigRegistry::Get_Instance(); - - // records.yaml — reloadable. - // First read happens at startup in RecCoreInit() (src/records/RecCore.cc:244). - // This handler is only invoked on runtime reload via ConfigRegistry::execute_reload("records"). - reg.register_config( - "records", ts::filename::RECORDS, ts::filename::RECORDS, - [](ConfigContext ctx) { - if (auto zret = RecReadYamlConfigFile(); zret) { - RecConfigWarnIfUnregistered(ctx); - } else { - ctx.log("{}", zret); - if (zret.severity() >= ERRATA_ERROR) { - CfgLoadFail(ctx, "Failed to reload %s", ts::filename::RECORDS); - return; - } - } - CfgLoadComplete(ctx, "%s finished loading", ts::filename::RECORDS); - }, - ConfigSource::FileOnly); - - // Static (non-reloadable) files only. - reg.register_static_file("storage", ts::filename::STORAGE, {}, true); - reg.register_static_file("socks", ts::filename::SOCKS, "proxy.config.socks.socks_config_file"); - reg.register_static_file("plugin", ts::filename::PLUGIN); - reg.register_static_file("jsonrpc", ts::filename::JSONRPC, "proxy.config.jsonrpc.filename"); + initializeRegistry(); } std::tuple @@ -1017,11 +969,11 @@ cmd_verify(char * /* cmd ATS_UNUSED */) } api_init(); - if (!plugin_yaml_init(true)) { + if (!plugin_init(true)) { exitStatus |= (1 << 2); - fprintf(stderr, "ERROR: Failed to load plugins, exitStatus %d\n\n", exitStatus); + fprintf(stderr, "ERROR: Failed to load %s, exitStatus %d\n\n", ts::filename::PLUGIN, exitStatus); } else { - fprintf(stderr, "INFO: Successfully loaded plugins\n\n"); + fprintf(stderr, "INFO: Successfully loaded %s\n\n", ts::filename::PLUGIN); } if (!urlRewriteVerify()) { @@ -2002,8 +1954,8 @@ main(int /* argc ATS_UNUSED */, const char **argv) // Records init initialize_records(); - // Register non reloadable config files and records.yaml. - register_config_files(); + // Initialize file manager for TS. + initialize_file_manager(); // Set the core limit for the process init_core_size(); @@ -2357,9 +2309,7 @@ main(int /* argc ATS_UNUSED */, const char **argv) // Init plugins as soon as logging is ready. api_init(); - if (!plugin_yaml_init()) { - Warning("plugin initialization failed"); - } + (void)plugin_init(); // plugin.config { std::unique_lock lock(pluginInitMutex); @@ -2388,7 +2338,7 @@ main(int /* argc ATS_UNUSED */, const char **argv) #if TS_USE_QUIC == 1 quic_NetProcessor.start(-1, stacksize); #endif - FileManager::instance().registerConfigPluginCallbacks([&]() { global_config_cbs->invoke(); }); + FileManager::instance().registerConfigPluginCallbacks(global_config_cbs); cacheProcessor.afterInitCallbackSet(&CB_After_Cache_Init); cacheProcessor.start(); diff --git a/tests/README.md b/tests/README.md index cbeda199083..57127a45a46 100644 --- a/tests/README.md +++ b/tests/README.md @@ -146,7 +146,8 @@ A number of file objects are defined to help with adding values to a given confi * socks.config * splitdns.config * ssl_multicert.config - * storage.yaml + * storage.config + * volume.config #### Examples diff --git a/tests/autest.sh b/tests/autest.sh index 938fcb6a954..30e28d4d95a 100755 --- a/tests/autest.sh +++ b/tests/autest.sh @@ -30,7 +30,7 @@ cd "$SCRIPT_DIR" ./prepare_proxy_verifier.sh || fail "Failed to install Proxy Verifier." export PYTHONPATH=$(pwd):$PYTHONPATH -export PYTHONPATH=$(pwd)/gold_tests/remap:$(pwd)/gold_tests/remap_yaml:$PYTHONPATH +export PYTHONPATH=$(pwd)/gold_tests/remap:$PYTHONPATH ./test-env-check.sh || fail "Failed Python environment checks." hash nc 2>/dev/null || fail "Netcat is not installed." # this is for rhel or centos systems diff --git a/tests/autest.sh.in b/tests/autest.sh.in index 67a50fe7ee1..b4edfff9ea4 100755 --- a/tests/autest.sh.in +++ b/tests/autest.sh.in @@ -8,7 +8,7 @@ # export LD_LIBRARY_PATH=${CMAKE_INSTALL_PREFIX}/lib -export PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}/gold_tests/remap:${CMAKE_CURRENT_SOURCE_DIR}/gold_tests/remap_yaml:${CMAKE_CURRENT_SOURCE_DIR}/gold_tests/lib:$PYTHONPATH +export PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}/gold_tests/remap:${CMAKE_CURRENT_SOURCE_DIR}/gold_tests/lib:$PYTHONPATH # Move test directories that require features not supported by the current curl # build. This must happen before the parallel runner dispatches because it diff --git a/tests/gold_tests/autest-site/ats_replay.test.ext b/tests/gold_tests/autest-site/ats_replay.test.ext index d6379025b20..c19ff589674 100644 --- a/tests/gold_tests/autest-site/ats_replay.test.ext +++ b/tests/gold_tests/autest-site/ats_replay.test.ext @@ -43,11 +43,11 @@ def configure_ats(obj: 'TestRun', server: 'Process', ats_config: dict, dns: Opti # TLS configs enable_tls = process_config.get('enable_tls', False) if enable_tls: - # Configure ssl_multicert.yaml if specified. - ssl_multicert_yaml = ats_config.get('ssl_multicert_yaml', []) + # Configure ssl_multicert.config if specified. + ssl_multicert_config = ats_config.get('ssl_multicert_config', []) - # setup default cert and key if ssl_multicert_yaml is empty - if ssl_multicert_yaml == []: + # setup default cert and key ssl_multicert_config is empty + if ssl_multicert_config == []: ts.addDefaultSSLFiles() ts.Disk.records_config.update( @@ -56,15 +56,10 @@ def configure_ats(obj: 'TestRun', server: 'Process', ats_config: dict, dns: Opti 'proxy.config.ssl.server.private_key.path': ts.Variables.SSLDir, }) - ssl_multicert_yaml = [ - "ssl_multicert:", - " - ssl_cert_name: server.pem", - " ssl_key_name: server.key", - ' dest_ip: "*"', - ] + ssl_multicert_config = ["dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key"] - for line in ssl_multicert_yaml: - ts.Disk.ssl_multicert_yaml.AddLine(line) + for line in ssl_multicert_config: + ts.Disk.ssl_multicert_config.AddLine(line) # Configure sni.yaml if specified. sni_yaml = ats_config.get('sni_yaml') @@ -86,10 +81,14 @@ def configure_ats(obj: 'TestRun', server: 'Process', ats_config: dict, dns: Opti if logging_yaml != None: ts.Disk.logging_yaml.AddLines(yaml.dump(logging_yaml).split('\n')) - # Configure storage.yaml if specified. - storage_yaml = ats_config.get('storage_yaml') - if storage_yaml is not None: - ts.Disk.storage_yaml.AddLines(yaml.dump(storage_yaml).split('\n')) + # Configure volume.config if specified. + volume_config = ats_config.get('volume_config', []) + for vol in volume_config: + parts = [f"volume={vol['volume']}", f"scheme={vol['scheme']}", f"size={vol['size']}"] + for opt_key in ('ramcache', 'ram_cache_size', 'ram_cache_cutoff', 'avg_obj_size', 'fragment_size'): + if opt_key in vol: + parts.append(f"{opt_key}={vol[opt_key]}") + ts.Disk.volume_config.AddLine(' '.join(parts)) remap_config = ats_config.get('remap_config', []) for remap_entry in remap_config: diff --git a/tests/gold_tests/autest-site/min_cfg/readme.txt b/tests/gold_tests/autest-site/min_cfg/readme.txt index 822dc894c9b..369a4dbf2af 100644 --- a/tests/gold_tests/autest-site/min_cfg/readme.txt +++ b/tests/gold_tests/autest-site/min_cfg/readme.txt @@ -1,4 +1,4 @@ Contains the minimum set of config file and setting to allow trafficserver to start in a usable way. The goal is to remove the need for any of these files to exist. ip_allow.yaml is needed to allow tests the minimum access to traffic_server. -storage.yaml needs to exist for now. +storage.config needs to exist for now. diff --git a/tests/gold_tests/autest-site/min_cfg/storage.yaml b/tests/gold_tests/autest-site/min_cfg/storage.config similarity index 64% rename from tests/gold_tests/autest-site/min_cfg/storage.yaml rename to tests/gold_tests/autest-site/min_cfg/storage.config index 0422436e23d..d05db253019 100644 --- a/tests/gold_tests/autest-site/min_cfg/storage.yaml +++ b/tests/gold_tests/autest-site/min_cfg/storage.config @@ -1,8 +1,4 @@ # seems good enough for doing something for playing with. # not good for production # File must exist and must have this value in it -cache: - spans: - - name: disk-1 - path: storage - size: 256M +storage 256M diff --git a/tests/gold_tests/autest-site/traffic_replay.test.ext b/tests/gold_tests/autest-site/traffic_replay.test.ext index c276fb2e445..f173033560b 100644 --- a/tests/gold_tests/autest-site/traffic_replay.test.ext +++ b/tests/gold_tests/autest-site/traffic_replay.test.ext @@ -29,13 +29,7 @@ def Replay(obj, name, replay_dir, key=None, cert=None, conn_type='mixed', option ts.addSSLfile(os.path.join(obj.Variables["AtsTestToolsDir"], "microserver", "ssl", "server.pem")) ts.addSSLfile(os.path.join(obj.Variables["AtsTestToolsDir"], "microserver", "ssl", "server.crt")) - ts.Disk.ssl_multicert_yaml.AddLines( - """ - ssl_multicert: - - ssl_cert_name: server.pem - ssl_key_name: server.pem - dest_ip: "*" - """.split("\n")) + ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.pem') # MicroServer setup - NOTE: expand to multiple microserver in future? server = obj.MakeOriginServer("server", both=True, lookup_key='{%uuid}') diff --git a/tests/gold_tests/autest-site/trafficserver.test.ext b/tests/gold_tests/autest-site/trafficserver.test.ext index 4f38cf8dd69..a41f8948ced 100755 --- a/tests/gold_tests/autest-site/trafficserver.test.ext +++ b/tests/gold_tests/autest-site/trafficserver.test.ext @@ -295,10 +295,6 @@ def MakeATSProcess( tmpname = os.path.join(config_dir, fname) p.Disk.File(tmpname, id=make_id(fname), typename="ats:config") - fname = "remap.yaml" - tmpname = os.path.join(config_dir, fname) - p.Disk.File(tmpname, id=make_id(fname), typename="ats:config") - fname = "socks.config" tmpname = os.path.join(config_dir, fname) p.Disk.File(tmpname, id=make_id(fname), typename="ats:config") @@ -307,7 +303,7 @@ def MakeATSProcess( tmpname = os.path.join(config_dir, fname) p.Disk.File(tmpname, id=make_id(fname), typename="ats:config") - fname = "ssl_multicert.yaml" + fname = "ssl_multicert.config" tmpname = os.path.join(config_dir, fname) p.Disk.File(tmpname, id=make_id(fname), typename="ats:config") @@ -315,7 +311,11 @@ def MakeATSProcess( tmpname = os.path.join(config_dir, fname) p.Disk.File(tmpname, id=make_id(fname), typename="ats:config") - fname = "storage.yaml" + fname = "storage.config" + tmpname = os.path.join(config_dir, fname) + p.Disk.File(tmpname, id=make_id(fname), typename="ats:config") + + fname = "volume.config" tmpname = os.path.join(config_dir, fname) p.Disk.File(tmpname, id=make_id(fname), typename="ats:config") diff --git a/tests/gold_tests/bigobj/bigobj.test.py b/tests/gold_tests/bigobj/bigobj.test.py index d9430750721..55bb5c7403d 100644 --- a/tests/gold_tests/bigobj/bigobj.test.py +++ b/tests/gold_tests/bigobj/bigobj.test.py @@ -41,19 +41,13 @@ 'proxy.config.url_remap.remap_required': 0 }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine(f'map https://localhost:{ts.Variables.ssl_port} http://localhost:{ts.Variables.port}') ts.Disk.remap_config.AddLine(f'map https://localhost:{ts.Variables.ssl_portv6} http://localhost:{ts.Variables.port}') # Size of object to get. (NOTE: If you increase this significantly you may also have to increase cache -# capacity in tests/gold_tests/autest-size/min_cfg/storage.yaml. Also, for very large objects, if +# capacity in tests/gold_tests/autest-size/min_cfg/storage.config. Also, for very large objects, if # proxy.config.diags.debug.enabled is 1, the PUSH request will timeout and fail.) # obj_kilobytes = 10 * 1024 @@ -126,13 +120,7 @@ def create_pushfile(): 'proxy.config.url_remap.remap_required': 0 }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine(f'map https://localhost:{ts.Variables.ssl_port} http://localhost:{ts.Variables.port}') ts.Disk.remap_config.AddLine(f'map https://localhost:{ts.Variables.ssl_portv6} http://localhost:{ts.Variables.port}') diff --git a/tests/gold_tests/cache/background_fill.test.py b/tests/gold_tests/cache/background_fill.test.py index c9f5024f520..552add221da 100644 --- a/tests/gold_tests/cache/background_fill.test.py +++ b/tests/gold_tests/cache/background_fill.test.py @@ -50,13 +50,7 @@ def __setupTS(self, ts_names=['default']): self.ts[name] = Test.MakeATSProcess(name, select_ports=True, enable_tls=True, enable_cache=True) self.ts[name].addDefaultSSLFiles() - self.ts[name].Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self.ts[name].Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key") self.ts[name].Disk.records_config.update( { diff --git a/tests/gold_tests/cache/cache_config_reload.test.py b/tests/gold_tests/cache/cache_config_reload.test.py deleted file mode 100644 index 21bb7998287..00000000000 --- a/tests/gold_tests/cache/cache_config_reload.test.py +++ /dev/null @@ -1,72 +0,0 @@ -''' -Test cache.config and hosting.config reload via ConfigRegistry. - -Verifies that: -1. cache.config reload works after file touch -2. hosting.config reload works after file touch (requires cache to be initialized) -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os - -Test.Summary = ''' -Test cache.config and hosting.config reload via ConfigRegistry. -''' - -Test.ContinueOnFail = True - -# Create ATS with cache enabled (needed for hosting.config registration in open_done) -ts = Test.MakeATSProcess("ts", enable_cache=True) -ts.Disk.records_config.update({ - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'rpc|config', -}) - -# Set up initial cache.config with a caching rule -ts.Disk.cache_config.AddLine('dest_domain=example.com ttl-in-cache=30d') - -config_dir = ts.Variables.CONFIGDIR - -# --- Test 1: Touch cache.config and reload --- - -tr = Test.AddTestRun("Touch cache.config to trigger change detection") -tr.Processes.Default.StartBefore(ts) -tr.Processes.Default.Command = f"touch {os.path.join(config_dir, 'cache.config')} && sleep 2" -tr.Processes.Default.ReturnCode = 0 -tr.StillRunningAfter = ts - -tr = Test.AddTestRun("Reload after cache.config touch") -tr.Processes.Default.Env = ts.Env -tr.Processes.Default.Command = 'traffic_ctl config reload --show-details --token reload_cache_test' -tr.Processes.Default.ReturnCode = Any(0, 2) -tr.StillRunningAfter = ts -tr.Processes.Default.Streams.stdout = Testers.ContainsExpression("cache.config", "Reload output should reference cache.config") - -# --- Test 2: Touch hosting.config and reload --- - -tr = Test.AddTestRun("Touch hosting.config to trigger change detection") -tr.DelayStart = 3 -tr.Processes.Default.Command = f"touch {os.path.join(config_dir, 'hosting.config')} && sleep 2" -tr.Processes.Default.ReturnCode = 0 -tr.StillRunningAfter = ts - -tr = Test.AddTestRun("Reload after hosting.config touch") -tr.Processes.Default.Env = ts.Env -tr.Processes.Default.Command = 'traffic_ctl config reload --show-details --token reload_hosting_test' -tr.Processes.Default.ReturnCode = Any(0, 2) -tr.StillRunningAfter = ts -tr.Processes.Default.Streams.stdout = Testers.ContainsExpression("hosting.config", "Reload output should reference hosting.config") diff --git a/tests/gold_tests/cache/cache_volume_defaults.replay.yaml b/tests/gold_tests/cache/cache_volume_defaults.replay.yaml index a828f8efd4a..c0d66e6b2ad 100644 --- a/tests/gold_tests/cache/cache_volume_defaults.replay.yaml +++ b/tests/gold_tests/cache/cache_volume_defaults.replay.yaml @@ -43,16 +43,11 @@ autest: # Set default volumes to volume 1 proxy.config.cache.default_volumes: "1" - storage_yaml: - cache: - spans: - - name: "disk-1" - path: "storage" - size: "256M" - volumes: - - id: 1 - scheme: "http" - size: "100%" + volume_config: + # Single volume — multi-param line proves parsing works + - volume: 1 + scheme: "http" + size: "100%" remap_config: # Default volume selection (uses default_volumes -> volume 1) diff --git a/tests/gold_tests/cache/cache_volume_features.replay.yaml b/tests/gold_tests/cache/cache_volume_features.replay.yaml index 3ea0f5a3425..6c7c8154271 100644 --- a/tests/gold_tests/cache/cache_volume_features.replay.yaml +++ b/tests/gold_tests/cache/cache_volume_features.replay.yaml @@ -17,21 +17,16 @@ meta: version: "1.0" -# This test proves that multi-parameter storage.yaml volume entries are parsed -# correctly. The generated storage.yaml contains: +# This test proves that multi-parameter volume.config lines are parsed +# correctly. The generated volume.config lines are: # -# cache: -# volumes: -# - id: 1 -# scheme: http -# size: 50% -# ram_cache_size: 32M -# ram_cache_cutoff: 8K -# - id: 2 -# scheme: http -# size: 50% +# volume=1 scheme=http size=50% ram_cache_size=32M ram_cache_cutoff=8K +# volume=2 scheme=http size=50% # -# All volume parameters must be parsed correctly from the YAML format. +# All 5 key=value pairs on line 1 must be parsed from the single line. +# If the inner loop's "tmp = line_end" advancement were broken (as claimed +# in a PR review), only "volume=1" would be parsed, scheme and size would +# be missing, and the volume would be rejected -- disabling the cache. autest: description: "Test cache volume features: per-volume RAM cache and @volume= directive" @@ -55,30 +50,24 @@ autest: proxy.config.http.insert_response_via_str: 0 proxy.config.cache.enable_read_while_writer: 0 - storage_yaml: - cache: - spans: - - name: "disk-1" - path: "storage" - size: "256M" - volumes: - # Volume 1 with all new parameters - - id: 1 - scheme: "http" - size: "50%" - ram_cache_size: "32M" - ram_cache_cutoff: "8K" - # Volume 2 — simple volume, no extra params - - id: 2 - scheme: "http" - size: "50%" + volume_config: + # Volume 1 with all new parameters — 5 key=value pairs on one line + - volume: 1 + scheme: "http" + size: "50%" + ram_cache_size: "32M" + ram_cache_cutoff: "8K" + # Volume 2 — simple line, no extra params + - volume: 2 + scheme: "http" + size: "50%" log_validation: traffic_out: excludes: - # No storage.yaml volume entries should be discarded + # No volume.config lines should be discarded - expression: "discarding.*entry at line" - description: "No storage.yaml volume entries should be discarded" + description: "No volume.config entries should be discarded" remap_config: - from: "http://volume1.example.com/" diff --git a/tests/gold_tests/cache/cache_volume_features.test.py b/tests/gold_tests/cache/cache_volume_features.test.py index b6aa8ec13d6..c52575ef564 100644 --- a/tests/gold_tests/cache/cache_volume_features.test.py +++ b/tests/gold_tests/cache/cache_volume_features.test.py @@ -20,6 +20,6 @@ - @volume= directive in remap.config for volume selection - Integration between both features ''' -# TODO: hosting.config + @volume= priority interaction test requires ats_replay.test.ext to support storage_yaml/hosting_config. +# TODO: hosting.config + @volume= priority interaction test requires ats_replay.test.ext to support volume_config/hosting_config. Test.ATSReplayTest(replay_file="cache_volume_features.replay.yaml") diff --git a/tests/gold_tests/cache/gold/storage_metrics_3_stdout.gold b/tests/gold_tests/cache/gold/storage_metrics_3_stdout.gold deleted file mode 100644 index 9325e46d067..00000000000 --- a/tests/gold_tests/cache/gold/storage_metrics_3_stdout.gold +++ /dev/null @@ -1,9 +0,0 @@ -`` -proxy.process.cache.stripes 2 -`` -proxy.process.cache.span.online 1 -`` -proxy.process.cache.volume_1.stripes 1 -`` -proxy.process.cache.volume_2.stripes 1 -`` diff --git a/tests/gold_tests/cache/negative-caching.test.py b/tests/gold_tests/cache/negative-caching.test.py index 853624e6348..0e09ae17bb4 100644 --- a/tests/gold_tests/cache/negative-caching.test.py +++ b/tests/gold_tests/cache/negative-caching.test.py @@ -132,6 +132,3 @@ p.StartBefore(dns) p.StartBefore(server) p.StartBefore(ts) - -# Test malformed Cache-Control header with semicolons instead of commas (issue #12029) -Test.ATSReplayTest(replay_file="replay/negative-caching-malformed-cc.replay.yaml") diff --git a/tests/gold_tests/cache/replay/negative-caching-malformed-cc.replay.yaml b/tests/gold_tests/cache/replay/negative-caching-malformed-cc.replay.yaml deleted file mode 100644 index dccee65e2b4..00000000000 --- a/tests/gold_tests/cache/replay/negative-caching-malformed-cc.replay.yaml +++ /dev/null @@ -1,320 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# -# This replay file tests negative caching with malformed Cache-Control headers -# that use semicolons instead of commas as separators (issue #12029). -# - -meta: - version: "1.0" - -# Configuration section for autest integration -autest: - description: 'Test malformed Cache-Control header with semicolons instead of commas (issue #12029)' - - # Server configuration - server: - name: 'server-malformed-cc' - - # Client configuration - client: - name: 'client-malformed-cc' - - # ATS configuration - ats: - name: 'ts-malformed-cc' - - # ATS records.config settings - records_config: - proxy.config.diags.debug.enabled: 1 - proxy.config.diags.debug.tags: 'http' - proxy.config.http.insert_age_in_response: 0 - proxy.config.http.negative_caching_enabled: 0 - - # Remap configuration - remap_config: - - from: "/" - to: "http://127.0.0.1:{SERVER_HTTP_PORT}/" - -sessions: -- transactions: - - # - # Phase 1: Initial requests to populate the cache. - # - # These requests are sent to the origin server and the responses are cached - # (or not cached, depending on the Cache-Control header). - # - - # First, verify that a 400 response with a proper Cache-Control header is cached. - - client-request: - method: "GET" - version: "1.1" - scheme: "http" - url: /path/400_proper_cc - headers: - fields: - - [ Host, example.com ] - - [ uuid, proper_cc ] - - server-response: - status: 400 - reason: "Bad Request" - headers: - fields: - - [ Content-Length, 0 ] - - [ Cache-Control, max-age=300 ] - - proxy-response: - status: 400 - - # Test: Verify that a 400 response with malformed Cache-Control using - # semicolons is not cached. The header "Cache-Control: public; max-age=30" - # uses semicolons instead of commas as separators, violating RFC 7234. - # Per RFC 7234 Section 5.2, caches must ignore unrecognized directives. - # Since the directive is malformed, it should be ignored, and the response - # should not be cached (no valid Cache-Control + negative_caching_enabled=0). - - client-request: - method: "GET" - version: "1.1" - scheme: "http" - url: /path/400_malformed_cc_semicolon - headers: - fields: - - [ Host, example.com ] - - [ uuid, semicolon ] - - server-response: - status: 400 - reason: "Bad Request" - headers: - fields: - - [ Content-Length, 0 ] - # Note: Using semicolon instead of comma - this is malformed per RFC 7234 - - [ Cache-Control, "public; max-age=30" ] - - proxy-response: - status: 400 - - # Test: Verify that a 400 response with space before = in max-age is not cached. - # The header "Cache-Control: max-age =300" is malformed per RFC 7230. - # Per RFC 7234 Section 5.2, caches must ignore unrecognized directives. - - client-request: - method: "GET" - version: "1.1" - scheme: "http" - url: /path/400_malformed_cc_space_before_equals - headers: - fields: - - [ Host, example.com ] - - [ uuid, space_before_equals ] - - server-response: - status: 400 - reason: "Bad Request" - headers: - fields: - - [ Content-Length, 0 ] - # Note: Space before = is malformed per RFC 7230 - - [ Cache-Control, "max-age =300" ] - - proxy-response: - status: 400 - - # Test: Verify that a 400 response with space after = in max-age is not cached. - # The header "Cache-Control: max-age= 300" is malformed per RFC 7230. - # Per RFC 7234 Section 5.2, caches must ignore unrecognized directives. - - client-request: - method: "GET" - version: "1.1" - scheme: "http" - url: /path/400_malformed_cc_space_after_equals - headers: - fields: - - [ Host, example.com ] - - [ uuid, space_after_equals ] - - server-response: - status: 400 - reason: "Bad Request" - headers: - fields: - - [ Content-Length, 0 ] - # Note: Space after = is malformed per RFC 7230 - - [ Cache-Control, "max-age= 300" ] - - proxy-response: - status: 400 - - # Test: Verify that a 400 response with single-quoted max-age value is not cached. - # The header "Cache-Control: max-age='300'" is malformed per RFC 7230. - # Per RFC 7234 Section 5.2, caches must ignore unrecognized directives. - - client-request: - method: "GET" - version: "1.1" - scheme: "http" - url: /path/400_malformed_cc_single_quotes - headers: - fields: - - [ Host, example.com ] - - [ uuid, single_quotes ] - - server-response: - status: 400 - reason: "Bad Request" - headers: - fields: - - [ Content-Length, 0 ] - # Note: Single quotes around value are malformed per RFC 7230 - - [ Cache-Control, "max-age='300'" ] - - proxy-response: - status: 400 - - # - # Phase 2: Verification requests to check if responses were cached. - # - # These requests should be served from the cache (if cached) or from the - # origin server (if not cached). - # - - # Second request to /path/400_proper_cc should be served from cache. - - client-request: - - # Add delay to ensure time for cache IO processing. - delay: 100ms - - method: "GET" - version: "1.1" - scheme: "http" - url: /path/400_proper_cc - headers: - fields: - - [ Host, example.com ] - - [ uuid, proper_cc_verify ] - - proxy-request: - expect: absent - - # The server should not receive this request because it should be served from cache. - server-response: - status: 200 - reason: "OK" - headers: - fields: - - [ Content-Length, 0 ] - - [ Cache-Control, max-age=300 ] - - proxy-response: - status: 400 - - # Second request to /path/400_malformed_cc_semicolon should NOT be served from cache. - - client-request: - method: "GET" - version: "1.1" - scheme: "http" - url: /path/400_malformed_cc_semicolon - headers: - fields: - - [ Host, example.com ] - - [ uuid, semicolon_verify ] - - # Since the initial CC was malformed, the response should not be cached. - server-response: - status: 200 - reason: OK - headers: - fields: - - [ Content-Length, 16 ] - - [ Cache-Control, max-age=300 ] - - # Expect the origin's 200 response. - proxy-response: - status: 200 - - # Second request to /path/400_malformed_cc_space_before_equals should NOT be served from cache. - - client-request: - method: "GET" - version: "1.1" - scheme: "http" - url: /path/400_malformed_cc_space_before_equals - headers: - fields: - - [ Host, example.com ] - - [ uuid, space_before_equals_verify ] - - # Since the initial CC was malformed, the response should not be cached. - server-response: - status: 200 - reason: OK - headers: - fields: - - [ Content-Length, 16 ] - - [ Cache-Control, max-age=300 ] - - # Expect the origin's 200 response. - proxy-response: - status: 200 - - # Second request to /path/400_malformed_cc_space_after_equals should NOT be served from cache. - - client-request: - method: "GET" - version: "1.1" - scheme: "http" - url: /path/400_malformed_cc_space_after_equals - headers: - fields: - - [ Host, example.com ] - - [ uuid, space_after_equals_verify ] - - # Since the initial CC was malformed, the response should not be cached. - server-response: - status: 200 - reason: OK - headers: - fields: - - [ Content-Length, 16 ] - - [ Cache-Control, max-age=300 ] - - # Expect the origin's 200 response. - proxy-response: - status: 200 - - # Second request to /path/400_malformed_cc_single_quotes should NOT be served from cache. - - client-request: - method: "GET" - version: "1.1" - scheme: "http" - url: /path/400_malformed_cc_single_quotes - headers: - fields: - - [ Host, example.com ] - - [ uuid, single_quotes_verify ] - - # Since the initial CC was malformed, the response should not be cached. - server-response: - status: 200 - reason: OK - headers: - fields: - - [ Content-Length, 16 ] - - [ Cache-Control, max-age=300 ] - - # Expect the origin's 200 response. - proxy-response: - status: 200 - diff --git a/tests/gold_tests/cache/replay/targeted-cache-control.replay.yaml b/tests/gold_tests/cache/replay/targeted-cache-control.replay.yaml index bd51379e898..90300f79e30 100644 --- a/tests/gold_tests/cache/replay/targeted-cache-control.replay.yaml +++ b/tests/gold_tests/cache/replay/targeted-cache-control.replay.yaml @@ -42,14 +42,11 @@ autest: proxy.config.diags.debug.tags: 'http|cache' proxy.config.http.cache.http: 1 proxy.config.http.cache.required_headers: 0 + proxy.config.http.cache.targeted_cache_control_headers: 'ATS-Cache-Control,CDN-Cache-Control' remap_config: - from: "http://example.com/" to: "http://backend.example.com:{SERVER_HTTP_PORT}/" - plugins: - - name: "conf_remap.so" - args: - - "proxy.config.http.cache.targeted_cache_control_headers=ATS-Cache-Control,CDN-Cache-Control" - from: "http://acme.com/" to: "http://backend.acme.com:{SERVER_HTTP_PORT}/" @@ -58,41 +55,9 @@ autest: args: - "proxy.config.http.cache.targeted_cache_control_headers=ACME-Cache-Control" - - from: "http://default.com/" - to: "http://backend.default.com:{SERVER_HTTP_PORT}/" - sessions: - transactions: - ############################################################################# - # Test 0: default config checks CDN-Cache-Control by default. - ############################################################################# - - client-request: - method: GET - url: /default/test1 - version: '1.1' - headers: - fields: - - [Host, default.com] - - [uuid, default-test1-request1] - - server-response: - status: 200 - reason: OK - headers: - fields: - - [Content-Type, text/plain] - - [Content-Length, "14"] - - [Cache-Control, "no-store"] - - [CDN-Cache-Control, "max-age=30"] # Should be used by default. - - [Connection, close] - - proxy-response: - status: 200 - headers: - fields: - - [ Cache-Control, { value: "no-store", as: equal } ] - - [ CDN-Cache-Control, { value: "max-age=30", as: equal } ] ############################################################################# # Test 1: CDN-Cache-Control with higher max-age overrides Cache-Control @@ -284,31 +249,6 @@ sessions: ############################################################################# # Now verify the correct cache behavior from above. ############################################################################# - - client-request: - method: GET - url: /default/test1 - version: '1.1' - headers: - fields: - - [Host, default.com] - - [uuid, default-test1-request2] - - proxy-request: - expect: absent - - # Should not reach the origin. - server-response: - status: 404 - reason: Not Found - - # Expect the cached 200 response due to default CDN-Cache-Control handling. - proxy-response: - status: 200 - headers: - fields: - - [ Cache-Control, { value: "no-store", as: equal } ] - - [ CDN-Cache-Control, { value: "max-age=30", as: equal } ] - - client-request: # Delay to exceed Cache-Control but not CDN-Cache-Control. delay: 2s @@ -321,9 +261,6 @@ sessions: - [Host, example.com] - [uuid, targeted-test1-request2] - proxy-request: - expect: absent - # Should not reach the origin. server-response: status: 404 @@ -346,9 +283,6 @@ sessions: - [Host, example.com] - [uuid, targeted-test2-request2] - proxy-request: - expect: absent - # Should not reach the origin. server-response: status: 404 @@ -394,9 +328,6 @@ sessions: - [Host, example.com] - [uuid, targeted-test4-request2] - proxy-request: - expect: absent - # Should not reach the origin. server-response: status: 404 @@ -418,9 +349,6 @@ sessions: - [Host, acme.com] - [uuid, acme-test1-request2] - proxy-request: - expect: absent - # The origin should not receive the request since ATS will reply out of cache. server-response: status: 404 diff --git a/tests/gold_tests/cache/storage-metrics.test.py b/tests/gold_tests/cache/storage-metrics.test.py index 006edcbc3e9..27a293f3790 100644 --- a/tests/gold_tests/cache/storage-metrics.test.py +++ b/tests/gold_tests/cache/storage-metrics.test.py @@ -27,75 +27,43 @@ "case": 0, "description": "default config", "storage": ''' -cache: - spans: - - name: disk.0 - path: storage - size: 256M +storage 256M +''', + "volume": ''' +# empty ''' - }, - { + }, { "case": 1, - "description": "four equally divided volumes", - "storage": + "description": "four equally devided volumes", + "storage": ''' +storage 1G +''', + "volume": ''' -cache: - spans: - - name: disk.0 - path: storage - size: 1G - volumes: - - id: 1 - size: 25% - - id: 2 - size: 25% - - id: 3 - size: 25% - - id: 4 - size: 25% +volume=1 scheme=http size=25% +volume=2 scheme=http size=25% +volume=3 scheme=http size=25% +volume=4 scheme=http size=25% ''' - }, - { + }, { "case": 2, "description": "exclusive span", - "storage": - ''' -cache: - spans: - - name: disk.0 - path: storage - size: 256M - volumes: - - id: 1 - spans: - - use: disk.0 - size: 100% + "storage": ''' +storage 256M volume=1 +''', + "volume": ''' +volume=1 scheme=http size=262144 ''', "hosting": ''' hostname=* volume=1 ''' - }, - { - "case": 3, - "description": "two equally divided volumes without size option", - "storage": - ''' -cache: - spans: - - name: disk.0 - path: storage - size: 512M - volumes: - - id: 1 - - id: 2 -''' - }, + } ] class StorageMetricsTest: """ - Test loading storage.yaml + Test loading storage.config and volume.config 1. Spawn TS process with configs in test_cases 2. Get 'proxy.process.cache.*' metrics @@ -106,11 +74,10 @@ def run(self): for config in test_cases: i = config["case"] ts = Test.MakeATSProcess(f"ts_{i}") - ts.Disk.storage_yaml.AddLine(config["storage"]) - + ts.Disk.storage_config.AddLine(config["storage"]) + ts.Disk.volume_config.AddLine(config["volume"]) if "hosting" in config: ts.Disk.hosting_config.AddLine(config["hosting"]) - ts.Disk.records_config.update({ 'proxy.config.diags.debug.enabled': 1, 'proxy.config.diags.debug.tags': 'cache', diff --git a/tests/gold_tests/cache/targeted-cache-control.test.py b/tests/gold_tests/cache/targeted-cache-control.test.py index 4c6efc174f5..3bcd84047b8 100644 --- a/tests/gold_tests/cache/targeted-cache-control.test.py +++ b/tests/gold_tests/cache/targeted-cache-control.test.py @@ -18,8 +18,8 @@ Test.Summary = ''' Test targeted cache control headers per RFC 9213. -Verifies that CDN-Cache-Control is applied by default, and that remap-level -targeted header overrides still work. +Verifies that CDN-Cache-Control and other targeted headers can override +standard Cache-Control when properly configured. ''' Test.ATSReplayTest(replay_file="replay/targeted-cache-control.replay.yaml") diff --git a/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py b/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py index 46638f315aa..5b63acf2d01 100644 --- a/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py +++ b/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py @@ -96,13 +96,7 @@ def setupTS(self): "proxy.config.ssl.server.private_key.path": f'{self.ts.Variables.SSLDir}', "proxy.config.ssl.client.verify.server.policy": 'PERMISSIVE', }) - self.ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self.ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') self.ts.Disk.remap_config.AddLine(f"map / http://127.0.0.1:{self.server.Variables.http_port}/",) def runChunkedTraffic(self): @@ -158,13 +152,7 @@ def setupTS(self): "proxy.config.ssl.server.private_key.path": f'{self.ts.Variables.SSLDir}', "proxy.config.ssl.client.verify.server.policy": 'PERMISSIVE', }) - self.ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self.ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') self.ts.Disk.remap_config.AddLine(f"map / http://127.0.0.1:{self.server.Variables.http_port}/",) self.ts.Disk.traffic_out.Content += Testers.ContainsExpression( "user agent post chunk decoding error", "Verify that ATS detected a problem parsing a chunk.") diff --git a/tests/gold_tests/chunked_encoding/chunked_encoding.test.py b/tests/gold_tests/chunked_encoding/chunked_encoding.test.py index 14513a097a2..f4b0b129fc4 100644 --- a/tests/gold_tests/chunked_encoding/chunked_encoding.test.py +++ b/tests/gold_tests/chunked_encoding/chunked_encoding.test.py @@ -90,13 +90,7 @@ 'map https://www.anotherexample.com https://127.0.0.1:{0}'.format(server2.Variables.SSL_Port, ts.Variables.ssl_port)) ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(Test.Variables.upstream_port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # smuggle-client is built via `make`. Here we copy the built binary down to the # test directory so that the test runs in this file can use it. diff --git a/tests/gold_tests/chunked_encoding/chunked_encoding_disabled.test.py b/tests/gold_tests/chunked_encoding/chunked_encoding_disabled.test.py index f584b906335..8b7dc7cfed7 100644 --- a/tests/gold_tests/chunked_encoding/chunked_encoding_disabled.test.py +++ b/tests/gold_tests/chunked_encoding/chunked_encoding_disabled.test.py @@ -44,13 +44,7 @@ def setupTS(self): # Never respond with chunked encoding. "proxy.config.http.chunking_enabled": 0, }) - self.ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self.ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') self.ts.Disk.remap_config.AddLines( [ f"map /for/http http://127.0.0.1:{self.server.Variables.http_port}/", diff --git a/tests/gold_tests/chunked_encoding/chunked_encoding_h2.test.py b/tests/gold_tests/chunked_encoding/chunked_encoding_h2.test.py index cbf63581782..004267c2d60 100644 --- a/tests/gold_tests/chunked_encoding/chunked_encoding_h2.test.py +++ b/tests/gold_tests/chunked_encoding/chunked_encoding_h2.test.py @@ -56,13 +56,7 @@ ts.Disk.remap_config.AddLine('map /post-full http://127.0.0.1:{0}'.format(Test.Variables.upstream_port2)) ts.Disk.remap_config.AddLine('map /post-chunked http://127.0.0.1:{0}'.format(Test.Variables.upstream_port3)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Using netcat as a cheap origin server in case 1 so we can insert a delay in sending back the response. # Replaced microserver for cases 2 and 3 as well because I was getting python exceptions when running diff --git a/tests/gold_tests/client_connection/per_client_connection_max.test.py b/tests/gold_tests/client_connection/per_client_connection_max.test.py index 4bec83d487b..aa887d0768b 100644 --- a/tests/gold_tests/client_connection/per_client_connection_max.test.py +++ b/tests/gold_tests/client_connection/per_client_connection_max.test.py @@ -125,13 +125,7 @@ def _configure_trafficserver(self) -> None: name = f'ts{self._process_counter}' self._ts = Test.MakeATSProcess(name, enable_cache=False, enable_tls=True) self._ts.addDefaultSSLFiles() - self._ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self._ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') if self._protocol == Protocol.HTTP: server_port = self._server.Variables.http_port scheme = 'http' diff --git a/tests/gold_tests/connect/connect.test.py b/tests/gold_tests/connect/connect.test.py index 838ba3543f8..a1674e4a34c 100644 --- a/tests/gold_tests/connect/connect.test.py +++ b/tests/gold_tests/connect/connect.test.py @@ -221,13 +221,7 @@ def setupTS(self): }) self.ts.addDefaultSSLFiles() - self.ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self.ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') self.ts.Disk.remap_config.AddLines([ f"map / http://127.0.0.1:{self.server.Variables.http_port}/", diff --git a/tests/gold_tests/connect/h2_malformed_request_logging.test.py b/tests/gold_tests/connect/h2_malformed_request_logging.test.py index e51902892c7..5862d67af8f 100644 --- a/tests/gold_tests/connect/h2_malformed_request_logging.test.py +++ b/tests/gold_tests/connect/h2_malformed_request_logging.test.py @@ -92,13 +92,7 @@ def _setup_ts(self): typename='ats:config', ) self._ts.Disk.storage_config.AddLine('') - self._ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split('\n')) + self._ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') self._ts.Disk.records_config.update( { diff --git a/tests/gold_tests/continuations/double_h2.test.py b/tests/gold_tests/continuations/double_h2.test.py index 8ff4fa0acfa..cb81c2d83a7 100644 --- a/tests/gold_tests/continuations/double_h2.test.py +++ b/tests/gold_tests/continuations/double_h2.test.py @@ -48,13 +48,7 @@ # add port and remap rule ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.records_config.update( { diff --git a/tests/gold_tests/continuations/openclose_h2.test.py b/tests/gold_tests/continuations/openclose_h2.test.py index 987636e8845..8cbeb010553 100644 --- a/tests/gold_tests/continuations/openclose_h2.test.py +++ b/tests/gold_tests/continuations/openclose_h2.test.py @@ -59,13 +59,7 @@ }) ts.Disk.remap_config.AddLine('map https://oc.test:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') cmd = '-k --resolve oc.test:{0}:127.0.0.1 --http2 https://oc.test:{0}'.format(ts.Variables.ssl_port) numberOfRequests = 100 diff --git a/tests/gold_tests/continuations/session_id.test.py b/tests/gold_tests/continuations/session_id.test.py index a10e45b3c2c..5c20d025587 100644 --- a/tests/gold_tests/continuations/session_id.test.py +++ b/tests/gold_tests/continuations/session_id.test.py @@ -53,13 +53,7 @@ ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # # Run some HTTP/1 traffic. diff --git a/tests/gold_tests/cripts/cripts.test.py b/tests/gold_tests/cripts/cripts.test.py index bffe3c4c872..ce46154ee59 100644 --- a/tests/gold_tests/cripts/cripts.test.py +++ b/tests/gold_tests/cripts/cripts.test.py @@ -50,13 +50,7 @@ def setUpTS(self): self.ts = Test.MakeATSProcess("ts_in", enable_tls=True, enable_cache=False, enable_cripts=True) self.ts.addDefaultSSLFiles() - self.ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self.ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key") self.ts.Setup.Copy('files/basic.cript', self.ts.Variables.CONFIGDIR) diff --git a/tests/gold_tests/dns/splitdns_reload.test.py b/tests/gold_tests/dns/splitdns_reload.test.py deleted file mode 100644 index aa058675305..00000000000 --- a/tests/gold_tests/dns/splitdns_reload.test.py +++ /dev/null @@ -1,90 +0,0 @@ -''' -Test splitdns.config reload via ConfigRegistry. - -Verifies that: -1. splitdns.config reload works after file touch -2. The reload handler is invoked (diags log check) -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os - -Test.Summary = 'Test splitdns.config reload via ConfigRegistry.' -Test.ContinueOnFail = True - - -class SplitDNSReloadTest: - - def __init__(self): - self.setupDNSServer() - self.setupOriginServer() - self.setupTS() - - def setupDNSServer(self): - self.dns = Test.MakeDNServer("dns") - self.dns.addRecords(records={'foo.ts.a.o.': ['127.0.0.1']}) - - def setupOriginServer(self): - self.origin_server = Test.MakeOriginServer("origin_server") - self.origin_server.addResponse( - "sessionlog.json", {"headers": "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"}, - {"headers": "HTTP/1.1 200 OK\r\nServer: microserver\r\nConnection: close\r\n\r\n"}) - - def setupTS(self): - self.ts = Test.MakeATSProcess("ts", enable_cache=False) - self.ts.Disk.records_config.update( - { - 'proxy.config.dns.splitDNS.enabled': 1, - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'splitdns|config', - }) - self.ts.Disk.splitdns_config.AddLine(f"dest_domain=foo.ts.a.o named=127.0.0.1:{self.dns.Variables.Port}") - self.ts.Disk.remap_config.AddLine(f"map /foo/ http://foo.ts.a.o:{self.origin_server.Variables.Port}/") - - def run(self): - config_dir = self.ts.Variables.CONFIGDIR - - # Test 1: Verify basic SplitDNS works (startup loads config) - tr = Test.AddTestRun("Verify SplitDNS works at startup") - tr.MakeCurlCommand(f"-v http://localhost:{self.ts.Variables.port}/foo/", ts=self.ts) - tr.Processes.Default.ReturnCode = 0 - tr.Processes.Default.StartBefore(self.dns) - tr.Processes.Default.StartBefore(self.origin_server) - tr.Processes.Default.StartBefore(self.ts) - tr.StillRunningAfter = self.ts - - # Test 2: Touch splitdns.config -> reload -> handler fires - tr = Test.AddTestRun("Touch splitdns.config") - tr.Processes.Default.Command = (f"touch {os.path.join(config_dir, 'splitdns.config')} && sleep 1") - tr.Processes.Default.ReturnCode = 0 - tr.StillRunningAfter = self.ts - - tr = Test.AddTestRun("Reload after splitdns.config touch") - p = tr.Processes.Process("reload-1") - p.Command = 'traffic_ctl config reload; sleep 30' - p.Env = self.ts.Env - p.ReturnCode = Any(0, -2) - # Wait for 2nd "finished loading" (1st is startup) - p.Ready = When.FileContains(self.ts.Disk.diags_log.Name, "splitdns.config finished loading", 2) - p.Timeout = 20 - tr.Processes.Default.StartBefore(p) - tr.Processes.Default.Command = ('echo "waiting for splitdns.config reload"') - tr.TimeOut = 25 - tr.StillRunningAfter = self.ts - - -SplitDNSReloadTest().run() diff --git a/tests/gold_tests/early_hints/early_hints.test.py b/tests/gold_tests/early_hints/early_hints.test.py index 0f646624692..84c7a760edf 100644 --- a/tests/gold_tests/early_hints/early_hints.test.py +++ b/tests/gold_tests/early_hints/early_hints.test.py @@ -91,13 +91,7 @@ def _configure_ts(self, tr: 'TestRun'): self._ts = ts ts.Disk.remap_config.AddLine(f'map / http://backend.server.com:{self._server.Variables.http_port}') ts.addDefaultSSLFiles() - ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.records_config.update( { 'proxy.config.ssl.server.cert.path': ts.Variables.SSLDir, diff --git a/tests/gold_tests/forward_proxy/forward_proxy.test.py b/tests/gold_tests/forward_proxy/forward_proxy.test.py index 6566414aec8..49dd714bed8 100644 --- a/tests/gold_tests/forward_proxy/forward_proxy.test.py +++ b/tests/gold_tests/forward_proxy/forward_proxy.test.py @@ -59,13 +59,7 @@ def setupTS(self): self.ts = Test.MakeATSProcess(proc_name, enable_tls=True, enable_cache=False) ForwardProxyTest._ts_counter += 1 self.ts.addDefaultSSLFiles() - self.ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self.ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key") self.ts.Disk.remap_config.AddLine(f"map / http://127.0.0.1:{self.server.Variables.http_port}/") self.ts.Disk.records_config.update( diff --git a/tests/gold_tests/h2/grpc/grpc.test.py b/tests/gold_tests/h2/grpc/grpc.test.py index 4c0c19fe97c..72c52498102 100644 --- a/tests/gold_tests/h2/grpc/grpc.test.py +++ b/tests/gold_tests/h2/grpc/grpc.test.py @@ -53,13 +53,7 @@ def _configure_traffic_server(self, tr: 'TestRun', dns_port: int, server_port: i self._ts = tr.MakeATSProcess("ts", enable_tls=True, enable_cache=False) self._ts.addDefaultSSLFiles() - self._ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self._ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key") self._ts.Disk.remap_config.AddLine(f"map / https://example.com:{server_port}/") diff --git a/tests/gold_tests/h2/h2disable.test.py b/tests/gold_tests/h2/h2disable.test.py index 7946228fabc..9e645419e08 100644 --- a/tests/gold_tests/h2/h2disable.test.py +++ b/tests/gold_tests/h2/h2disable.test.py @@ -35,13 +35,7 @@ ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all diff --git a/tests/gold_tests/h2/h2disable_no_accept_threads.test.py b/tests/gold_tests/h2/h2disable_no_accept_threads.test.py index e431858b908..3fc0a1d5020 100644 --- a/tests/gold_tests/h2/h2disable_no_accept_threads.test.py +++ b/tests/gold_tests/h2/h2disable_no_accept_threads.test.py @@ -35,13 +35,7 @@ ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all diff --git a/tests/gold_tests/h2/h2enable.test.py b/tests/gold_tests/h2/h2enable.test.py index 1aaf0168555..36eb2fc7618 100644 --- a/tests/gold_tests/h2/h2enable.test.py +++ b/tests/gold_tests/h2/h2enable.test.py @@ -35,13 +35,7 @@ ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Set up port 4444 with HTTP1 only, no HTTP/2 ts.Disk.records_config.update( diff --git a/tests/gold_tests/h2/h2enable_no_accept_threads.test.py b/tests/gold_tests/h2/h2enable_no_accept_threads.test.py index c6818be4a4d..c07aceec66b 100644 --- a/tests/gold_tests/h2/h2enable_no_accept_threads.test.py +++ b/tests/gold_tests/h2/h2enable_no_accept_threads.test.py @@ -35,13 +35,7 @@ ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Set up port 4444 with HTTP1 only, no HTTP/2 ts.Disk.records_config.update( diff --git a/tests/gold_tests/h2/h2get_with_body.test.py b/tests/gold_tests/h2/h2get_with_body.test.py index 89661b6362f..f97aa04b996 100644 --- a/tests/gold_tests/h2/h2get_with_body.test.py +++ b/tests/gold_tests/h2/h2get_with_body.test.py @@ -29,13 +29,7 @@ ts = Test.MakeATSProcess('ts', select_ports=True, enable_tls=True, enable_cache=True) ts.addDefaultSSLFiles() -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key") ts.Disk.records_config.update( { "proxy.config.http.server_ports": f"{ts.Variables.port} {ts.Variables.ssl_port}:ssl", diff --git a/tests/gold_tests/h2/h2origin.test.py b/tests/gold_tests/h2/h2origin.test.py index 6b0211a8d00..24e92b553e9 100644 --- a/tests/gold_tests/h2/h2origin.test.py +++ b/tests/gold_tests/h2/h2origin.test.py @@ -51,13 +51,7 @@ ts.Disk.remap_config.AddLines( [f'map /expect http://127.0.0.1:{server_expect.Variables.http_port}', f'map / https://127.0.0.1:{server.Variables.https_port}']) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.logging_yaml.AddLines( ''' diff --git a/tests/gold_tests/h2/h2origin_single_thread.test.py b/tests/gold_tests/h2/h2origin_single_thread.test.py index ba0b3389537..d8d5e468570 100644 --- a/tests/gold_tests/h2/h2origin_single_thread.test.py +++ b/tests/gold_tests/h2/h2origin_single_thread.test.py @@ -49,13 +49,7 @@ }) ts.Disk.remap_config.AddLine('map / https://127.0.0.1:{0}'.format(server.Variables.https_port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.logging_yaml.AddLines( ''' diff --git a/tests/gold_tests/h2/h2spec.test.py b/tests/gold_tests/h2/h2spec.test.py index ba841153980..fee2dd53300 100644 --- a/tests/gold_tests/h2/h2spec.test.py +++ b/tests/gold_tests/h2/h2spec.test.py @@ -38,13 +38,7 @@ ts.addDefaultSSLFiles() ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(httpbin.Variables.Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.records_config.update( { 'proxy.config.http.insert_request_via_str': 1, diff --git a/tests/gold_tests/h2/http2.test.py b/tests/gold_tests/h2/http2.test.py index f6dfcf87973..1635615ddaa 100644 --- a/tests/gold_tests/h2/http2.test.py +++ b/tests/gold_tests/h2/http2.test.py @@ -139,13 +139,7 @@ ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.records_config.update( { 'proxy.config.diags.debug.enabled': 1, diff --git a/tests/gold_tests/h2/http2_close_connection.test.py b/tests/gold_tests/h2/http2_close_connection.test.py index 5a16bd4cd0a..7ad60d81609 100644 --- a/tests/gold_tests/h2/http2_close_connection.test.py +++ b/tests/gold_tests/h2/http2_close_connection.test.py @@ -27,13 +27,7 @@ ts = Test.MakeATSProcess('ts', select_ports=True, enable_tls=True) ts.addDefaultSSLFiles() -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key") ts.Disk.records_config.update( { "proxy.config.http.server_ports": f"{ts.Variables.port} {ts.Variables.ssl_port}:ssl", diff --git a/tests/gold_tests/h2/http2_concurrent_streams.test.py b/tests/gold_tests/h2/http2_concurrent_streams.test.py index 79e6b9d088d..7510b8e777b 100644 --- a/tests/gold_tests/h2/http2_concurrent_streams.test.py +++ b/tests/gold_tests/h2/http2_concurrent_streams.test.py @@ -43,13 +43,7 @@ def __setupTS(self): 'proxy.config.http.insert_response_via_str': 2, }) self._ts.Disk.remap_config.AddLine(f"map / http://127.0.0.1:{self._server.Variables.http_port}") - self._ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self._ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') def run(self): tr = Test.AddTestRun() diff --git a/tests/gold_tests/h2/http2_empty_data_frame.test.py b/tests/gold_tests/h2/http2_empty_data_frame.test.py index ae2caf7cadc..d0e90389116 100644 --- a/tests/gold_tests/h2/http2_empty_data_frame.test.py +++ b/tests/gold_tests/h2/http2_empty_data_frame.test.py @@ -45,13 +45,7 @@ def __setupTS(self): 'proxy.config.http2.stream_error_rate_threshold': 0.1 # default }) self._ts.Disk.remap_config.AddLine(f"map / http://127.0.0.1:{self._server.Variables.Port}") - self._ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self._ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') def __setupClient(self): self._ts.Setup.CopyAs("clients/h2empty_data_frame.py", Test.RunDirectory) diff --git a/tests/gold_tests/h2/http2_flow_control.test.py b/tests/gold_tests/h2/http2_flow_control.test.py index bbcd949b682..f09429f7d48 100644 --- a/tests/gold_tests/h2/http2_flow_control.test.py +++ b/tests/gold_tests/h2/http2_flow_control.test.py @@ -166,13 +166,7 @@ def _configure_trafficserver(self, tr: 'TestRun', is_outbound: bool, server_type configuration: self._max_concurrent_streams, }) - ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') if self._server is not None: ts.Disk.remap_config.AddLine(f'map / https://127.0.0.1:{self._server.Variables.https_port}') diff --git a/tests/gold_tests/h2/http2_priority.test.py b/tests/gold_tests/h2/http2_priority.test.py index 8b8168ca550..64b58c3fdb8 100644 --- a/tests/gold_tests/h2/http2_priority.test.py +++ b/tests/gold_tests/h2/http2_priority.test.py @@ -52,13 +52,7 @@ ts.addDefaultSSLFiles() ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.records_config.update( { 'proxy.config.http2.stream_priority_enabled': 1, diff --git a/tests/gold_tests/h2/http2_rst_stream.test.py b/tests/gold_tests/h2/http2_rst_stream.test.py index 339e0dec60b..caf2a711412 100644 --- a/tests/gold_tests/h2/http2_rst_stream.test.py +++ b/tests/gold_tests/h2/http2_rst_stream.test.py @@ -45,13 +45,7 @@ 'proxy.config.http.server_session_sharing.match': 'ip,sni,cert', }) ts.Disk.remap_config.AddLine(f'map / https://127.0.0.1:{server.Variables.https_port}') -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') tr = Test.AddTestRun('Client sends RST_STREAM after DATA frame') tr.Processes.Default.StartBefore(server) @@ -95,13 +89,7 @@ 'proxy.config.http.server_session_sharing.match': 'ip,sni,cert', }) ts.Disk.remap_config.AddLine(f'map / https://127.0.0.1:{server.Variables.https_port}') -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') tr = Test.AddTestRun('Client sends RST_STREAM after HEADERS frame') tr.Processes.Default.StartBefore(server) @@ -145,13 +133,7 @@ 'proxy.config.http.server_session_sharing.match': 'ip,sni,cert', }) ts.Disk.remap_config.AddLine(f'map / https://127.0.0.1:{server.Variables.https_port}') -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') tr = Test.AddTestRun('Server sends RST_STREAM after HEADERS frame') tr.Processes.Default.StartBefore(server) diff --git a/tests/gold_tests/h2/http2_write_threshold.test.py b/tests/gold_tests/h2/http2_write_threshold.test.py index 53bfcae865e..694037a7a56 100644 --- a/tests/gold_tests/h2/http2_write_threshold.test.py +++ b/tests/gold_tests/h2/http2_write_threshold.test.py @@ -89,13 +89,7 @@ def _configure_traffic_server( self._ts = tr.MakeATSProcess("ts", enable_tls=True, enable_cache=False) self._ts.addDefaultSSLFiles() - self._ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self._ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key") self._ts.Disk.remap_config.AddLine(f"map / https://example.com:{server_port}/") diff --git a/tests/gold_tests/h2/httpbin.test.py b/tests/gold_tests/h2/httpbin.test.py index 4af4c0bf7d9..14b5812aa65 100644 --- a/tests/gold_tests/h2/httpbin.test.py +++ b/tests/gold_tests/h2/httpbin.test.py @@ -46,13 +46,7 @@ ts.addDefaultSSLFiles() ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(httpbin.Variables.Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.records_config.update( { 'proxy.config.http.insert_request_via_str': 1, diff --git a/tests/gold_tests/h2/nghttp.test.py b/tests/gold_tests/h2/nghttp.test.py index b465e2766fc..071643a3498 100644 --- a/tests/gold_tests/h2/nghttp.test.py +++ b/tests/gold_tests/h2/nghttp.test.py @@ -52,13 +52,7 @@ httpbin.Variables.Port, Test.RunDirectory) ]) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.records_config.update( { diff --git a/tests/gold_tests/h3/h3_sni_check.test.py b/tests/gold_tests/h3/h3_sni_check.test.py index 185ef70236d..4ac57c66206 100644 --- a/tests/gold_tests/h3/h3_sni_check.test.py +++ b/tests/gold_tests/h3/h3_sni_check.test.py @@ -67,13 +67,7 @@ def _configure_traffic_server(self, tr: 'TestRun'): self._ts = ts # Configure TLS for Traffic Server. self._ts.addDefaultSSLFiles() - self._ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self._ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') self._ts.Disk.records_config.update( { 'proxy.config.diags.debug.enabled': 1, diff --git a/tests/gold_tests/headers/cachedIMSRange.test.py b/tests/gold_tests/headers/cachedIMSRange.test.py index 6682053931a..847be0dad09 100644 --- a/tests/gold_tests/headers/cachedIMSRange.test.py +++ b/tests/gold_tests/headers/cachedIMSRange.test.py @@ -23,4 +23,263 @@ Test.ContinueOnFail = True -Test.ATSReplayTest(replay_file="replays/cached_ims_range.replay.yaml") +# Set up Origin server +# request_header is from ATS to origin; response from Origin to ATS +# lookup_key is to make unique response in origin for header "UID" that will pass in ATS request +server = Test.MakeOriginServer("server", lookup_key="{%UID}") +# Initial request +request_header = { + "headers": "GET / HTTP/1.1\r\nHost: www.example.com\r\nUID: Fill\r\n\r\n", + "timestamp": "1469733493.993", + "body": "" +} +response_header = { + "headers": + "HTTP/1.1 200 OK\r\nConnection: close\r\nLast-Modified: Tue, 08 May 2018 15:49:41 GMT\r\nCache-Control: max-age=1\r\n\r\n", + "timestamp": "1469733493.993", + "body": "xxx" +} +server.addResponse("sessionlog.json", request_header, response_header) +# IMS revalidation request +request_IMS_header = { + "headers": "GET / HTTP/1.1\r\nUID: IMS\r\nIf-Modified-Since: Tue, 08 May 2018 15:49:41 GMT\r\nHost: www.example.com\r\n\r\n", + "timestamp": "1469733493.993", + "body": "" +} +response_IMS_header = { + "headers": "HTTP/1.1 304 Not Modified\r\nConnection: close\r\nCache-Control: max-age=1\r\n\r\n", + "timestamp": "1469733493.993", + "body": None +} +server.addResponse("sessionlog.json", request_IMS_header, response_IMS_header) + +# EtagFill +request_etagfill_header = { + "headers": "GET /etag HTTP/1.1\r\nHost: www.example.com\r\nUID: EtagFill\r\n\r\n", + "timestamp": "1469733493.993", + "body": None +} +response_etagfill_header = { + "headers": "HTTP/1.1 200 OK\r\nETag: myetag\r\nConnection: close\r\nCache-Control: max-age=1\r\n\r\n", + "timestamp": "1469733493.993", + "body": "xxx" +} +server.addResponse("sessionlog.json", request_etagfill_header, response_etagfill_header) +# INM revalidation +request_INM_header = { + "headers": "GET /etag HTTP/1.1\r\nUID: INM\r\nIf-None-Match: myetag\r\nHost: www.example.com\r\n\r\n", + "timestamp": "1469733493.993", + "body": None +} +response_INM_header = { + "headers": "HTTP/1.1 304 Not Modified\r\nConnection: close\r\nETag: myetag\r\nCache-Control: max-age=1\r\n\r\n", + "timestamp": "1469733493.993", + "body": None +} +server.addResponse("sessionlog.json", request_INM_header, response_INM_header) + +# object changed to 0 byte +request_noBody_header = { + "headers": "GET / HTTP/1.1\r\nUID: noBody\r\nHost: www.example.com\r\n\r\n", + "timestamp": "1469733493.993", + "body": "" +} +response_noBody_header = { + "headers": "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\nCache-Control: max-age=3\r\n\r\n", + "timestamp": "1469733493.993", + "body": "" +} +server.addResponse("sessionlog.json", request_noBody_header, response_noBody_header) + +# etag object now is a 404. Yeah, 404s don't usually have Cache-Control, but, ATS's default is to cache 404s for a while. +request_etagfill_header = { + "headers": "GET /etag HTTP/1.1\r\nHost: www.example.com\r\nUID: EtagError\r\n\r\n", + "timestamp": "1469733493.993", + "body": None +} +response_etagfill_header = { + "headers": "HTTP/1.1 404 Not Found\r\nConnection: close\r\nContent-Length: 0\r\nCache-Control: max-age=3\r\n\r\n", + "timestamp": "1469733493.993", + "body": "" +} +server.addResponse("sessionlog.json", request_etagfill_header, response_etagfill_header) + +# ATS Configuration +ts = Test.MakeATSProcess("ts", enable_tls=True) +ts.Disk.plugin_config.AddLine('xdebug.so --enable=x-cache,x-cache-key,via') +ts.addDefaultSSLFiles() +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') +ts.Disk.records_config.update( + { + 'proxy.config.diags.debug.enabled': 1, + 'proxy.config.diags.debug.tags': 'http', + 'proxy.config.http.response_via_str': 3, + 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), + }) + +default_304_host = 'www.default304.test' +regex_remap_conf_file = "maps.reg" +ts.Disk.remap_config.AddLines( + [ + f'map https://{default_304_host}/ http://127.0.0.1:{server.Variables.Port}/ ' + f'@plugin=regex_remap.so @pparam={regex_remap_conf_file} @pparam=no-query-string @pparam=host', + f'map http://{default_304_host}/ http://127.0.0.1:{server.Variables.Port}/ ' + f'@plugin=regex_remap.so @pparam={regex_remap_conf_file} @pparam=no-query-string @pparam=host', + f'map / http://127.0.0.1:{server.Variables.Port}', + ]) + +ts.Disk.MakeConfigFile(regex_remap_conf_file).AddLine(f'//.*/ http://127.0.0.1:{server.Variables.Port} @status=304') + +ipv4flag = "" +if not Condition.CurlUsingUnixDomainSocket(): + ipv4flag = "--ipv4" + +# Test 0 - Fill a 3 byte object with Last-Modified time into cache. +tr = Test.AddTestRun() +tr.Processes.Default.StartBefore(server) +tr.Processes.Default.StartBefore(ts) +tr.MakeCurlCommand( + '-s -D - -v {0} --http1.1 -H"UID: Fill" -H "x-debug: x-cache,x-cache-key,via" -H "Host: www.example.com" http://localhost:{1}/' + .format(ipv4flag, ts.Variables.port), + ts=ts) +tr.Processes.Default.ReturnCode = 0 +tr.Processes.Default.Streams.stdout = "cache_and_req_body-miss.gold" +tr.StillRunningAfter = ts +tr.StillRunningAfter = server + +# Test 1 - Once it goes stale, fetch it again. We expect Origin to get IMS +# request, and serve a 304. We expect ATS to refresh the object, and give +# a 200 to user +tr = Test.AddTestRun() +tr.DelayStart = 2 +tr.MakeCurlCommand( + '-s -D - -v {0} --http1.1 -H"UID: IMS" -H "x-debug: x-cache,x-cache-key,via" -H "Host: www.example.com" http://localhost:{1}/' + .format(ipv4flag, ts.Variables.port), + ts=ts) +tr.Processes.Default.ReturnCode = 0 +tr.Processes.Default.Streams.stdout = "cache_and_req_body-hit-stale.gold" +tr.StillRunningAfter = ts +tr.StillRunningAfter = server + +# Test 2 - Once it goes stale, fetch it via a range request. We expect +# Origin to get IMS request, and serve a 304. We expect ATS to refresh the +# object, and give a 206 to user +tr = Test.AddTestRun() +tr.DelayStart = 2 +tr.MakeCurlCommand( + '--range 0-1 -s -D - -v {0} --http1.1 -H"UID: IMS" -H "x-debug: x-cache,x-cache-key,via" -H "Host: www.example.com" http://localhost:{1}/' + .format(ipv4flag, ts.Variables.port), + ts=ts) +tr.Processes.Default.ReturnCode = 0 +tr.Processes.Default.Streams.stdout = "cache_and_req_body-hit-stale-206.gold" +tr.StillRunningAfter = ts +tr.StillRunningAfter = server + +# Test 3 - Test 304 response served from a regex-remap rule with HTTP. +tr = Test.AddTestRun() +tr.MakeCurlCommand(f'-vs http://127.0.0.1:{ts.Variables.port}/ -H "Host: {default_304_host}"', ts=ts) +tr.Processes.Default.ReturnCode = 0 +tr.Processes.Default.Streams.All = Testers.GoldFile("gold/http1_304.gold", case_insensitive=True) +tr.StillRunningAfter = server + +if not Condition.CurlUsingUnixDomainSocket(): + # Test 4 - Test 304 response served from a regex-remap rule with HTTPS. + tr = Test.AddTestRun() + tr.MakeCurlCommand(f'-vs -k https://127.0.0.1:{ts.Variables.ssl_port}/ -H "Host: {default_304_host}"', ts=ts) + tr.Processes.Default.ReturnCode = 0 + tr.Processes.Default.Streams.All = Testers.GoldFile("gold/http1_304.gold", case_insensitive=True) + tr.StillRunningAfter = server + + # Test 5 - Test 304 response served from a regex-remap rule with HTTP/2. + tr = Test.AddTestRun() + tr.MakeCurlCommand(f'-vs -k --http2 https://127.0.0.1:{ts.Variables.ssl_port}/ -H "Host: {default_304_host}"', ts=ts) + tr.Processes.Default.ReturnCode = 0 + tr.Processes.Default.Streams.All = Testers.GoldFile("gold/http2_304.gold", case_insensitive=True) + tr.StillRunningAfter = server + +# Test 6 - Fill a new object with an Etag. Not checking the output here. +tr = Test.AddTestRun() +tr.MakeCurlCommand( + '-s -D - -v {0} --http1.1 -H"UID: EtagFill" -H "x-debug: x-cache,x-cache-key,via" -H "Host: www.example.com" http://localhost:{1}/etag' + .format(ipv4flag, ts.Variables.port), + ts=ts) +tr.Processes.Default.ReturnCode = 0 +tr.StillRunningAfter = ts +tr.StillRunningAfter = server + +# Test 7 - Once the etag object goes stale, fetch it again. We expect +# Origin to get INM request, and serve a 304. We expect ATS to refresh the +# object, and give a 200 to user +tr = Test.AddTestRun() +tr.DelayStart = 2 +tr.MakeCurlCommand( + '-s -D - -v {0} --http1.1 -H"UID: INM" -H "x-debug: x-cache,x-cache-key,via" -H "Host: www.example.com" http://localhost:{1}/etag' + .format(ipv4flag, ts.Variables.port), + ts=ts) +tr.Processes.Default.ReturnCode = 0 +tr.Processes.Default.Streams.stdout = "cache_and_req_body-hit-stale-INM.gold" +tr.StillRunningAfter = ts +tr.StillRunningAfter = server + +# Test 8 - Once the etag object goes stale, fetch it via a range request. +# We expect Origin to get INM request, and serve a 304. We expect ATS to +# refresh the object, and give a 206 to user +tr = Test.AddTestRun() +tr.DelayStart = 2 +tr.MakeCurlCommand( + '--range 0-1 -s -D - -v {0} --http1.1 -H"UID: INM" -H "x-debug: x-cache,x-cache-key,via" -H "Host: www.example.com" http://localhost:{1}/etag' + .format(ipv4flag, ts.Variables.port), + ts=ts) +tr.Processes.Default.ReturnCode = 0 +tr.Processes.Default.Streams.stdout = "cache_and_req_body-hit-stale-206-etag.gold" +tr.StillRunningAfter = ts +tr.StillRunningAfter = server + +# Test 9 - The origin changes the initial LMT object to 0 byte. We expect ATS to fetch and serve the new 0 byte object. +tr = Test.AddTestRun() +tr.DelayStart = 3 +tr.MakeCurlCommand( + '-s -D - -v {0} --http1.1 -H"UID: noBody" -H "x-debug: x-cache,x-cache-key,via" -H "Host: www.example.com" http://localhost:{1}/' + .format(ipv4flag, ts.Variables.port), + ts=ts) +tr.Processes.Default.ReturnCode = 0 +tr.Processes.Default.Streams.stdout = "cache_and_req_nobody-hit-stale.gold" +tr.StillRunningAfter = ts +tr.StillRunningAfter = server + +# Test 10 - Fetch the new 0 byte object again when fresh in cache to ensure its still a 0 byte object. +tr = Test.AddTestRun() +tr.DelayStart = 3 +tr.MakeCurlCommand( + '-s -D - -v {0} --http1.1 -H"UID: noBody" -H "x-debug: x-cache,x-cache-key,via" -H "Host: www.example.com" http://localhost:{1}/' + .format(ipv4flag, ts.Variables.port), + ts=ts) +tr.Processes.Default.ReturnCode = 0 +tr.Processes.Default.Streams.stdout = "cache_and_req_nobody-hit-stale.gold" +tr.StillRunningAfter = ts +tr.StillRunningAfter = server + +# Test 11 - The origin changes the etag object to 0 byte 404. We expect ATS to fetch and serve the 404 0 byte object. +tr = Test.AddTestRun() +tr.DelayStart = 2 +tr.MakeCurlCommand( + '-s -D - -v {0} --http1.1 -H"UID: EtagError" -H "x-debug: x-cache,x-cache-key,via" -H "Host: www.example.com" http://localhost:{1}/etag' + .format(ipv4flag, ts.Variables.port), + ts=ts) +tr.Processes.Default.ReturnCode = 0 +tr.Processes.Default.Streams.stdout = "cache_and_error_nobody.gold" +tr.StillRunningAfter = ts +tr.StillRunningAfter = server + +# Test 12 - Fetch the 0 byte etag object again when fresh in cache to ensure its still a 0 byte object +tr = Test.AddTestRun() +tr.DelayStart = 2 +tr.MakeCurlCommand( + '-s -D - -v {0} --http1.1 -H"UID: EtagError" -H "x-debug: x-cache,x-cache-key,via" -H "Host: www.example.com" http://localhost:{1}/etag' + .format(ipv4flag, ts.Variables.port), + ts=ts) +tr.Processes.Default.ReturnCode = 0 +tr.Processes.Default.Streams.stdout = "cache_and_error_nobody.gold" +tr.StillRunningAfter = ts +tr.StillRunningAfter = server diff --git a/tests/gold_tests/headers/forwarded.test.py b/tests/gold_tests/headers/forwarded.test.py index 958e27a7c06..8add15e404a 100644 --- a/tests/gold_tests/headers/forwarded.test.py +++ b/tests/gold_tests/headers/forwarded.test.py @@ -99,13 +99,7 @@ def baselineTsSetup(ts): 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir) }) - ts.Disk.ssl_multicert_yaml.AddLines( - """ - ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key - """.split("\n")) + ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine('map http://www.no-oride.com http://127.0.0.1:{0}'.format(server.Variables.Port)) diff --git a/tests/gold_tests/headers/gold/http1_304.gold b/tests/gold_tests/headers/gold/http1_304.gold new file mode 100644 index 00000000000..3740c2395f9 --- /dev/null +++ b/tests/gold_tests/headers/gold/http1_304.gold @@ -0,0 +1,10 @@ +`` +> GET / HTTP/`` +> Host: www.default304.test +> User-Agent: curl/`` +> Accept: */* +`` +< HTTP/`` 304`` +< date: `` +< server: ATS/`` +`` diff --git a/tests/gold_tests/headers/gold/http2_304.gold b/tests/gold_tests/headers/gold/http2_304.gold new file mode 100644 index 00000000000..f5c3599828b --- /dev/null +++ b/tests/gold_tests/headers/gold/http2_304.gold @@ -0,0 +1,10 @@ +`` +> GET / HTTP/2 +> Host: www.default304.test +> User-Agent: curl/`` +> Accept: */* +`` +< HTTP/2 304`` +< date: `` +< server: ATS/`` +`` diff --git a/tests/gold_tests/headers/via.test.py b/tests/gold_tests/headers/via.test.py index 841736340ec..3ab495a2e6a 100644 --- a/tests/gold_tests/headers/via.test.py +++ b/tests/gold_tests/headers/via.test.py @@ -57,13 +57,7 @@ ts.Disk.remap_config.AddLine( 'map https://www.example.com http://127.0.0.1:{0}'.format(server.Variables.Port, ts.Variables.ssl_port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Set up to check the output after the tests have run. via_log_id = Test.Disk.File("via.log") diff --git a/tests/gold_tests/ip_allow/ip_allow.test.py b/tests/gold_tests/ip_allow/ip_allow.test.py index 0020e62d6b0..0454a4e373f 100644 --- a/tests/gold_tests/ip_allow/ip_allow.test.py +++ b/tests/gold_tests/ip_allow/ip_allow.test.py @@ -80,13 +80,7 @@ # Configure TLS for Traffic Server for HTTP/2. ts.addDefaultSSLFiles() -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.records_config.update( { @@ -254,13 +248,7 @@ def _configure_traffic_server(self, tr: 'TestRun'): self._ts = ts # Configure TLS for Traffic Server. self._ts.addDefaultSSLFiles() - self._ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self._ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') self._ts.Disk.records_config.update( { 'proxy.config.diags.debug.enabled': 1, diff --git a/tests/gold_tests/ip_allow/ip_allow_reload_triggered.test.py b/tests/gold_tests/ip_allow/ip_allow_reload_triggered.test.py deleted file mode 100644 index 3466e9de066..00000000000 --- a/tests/gold_tests/ip_allow/ip_allow_reload_triggered.test.py +++ /dev/null @@ -1,256 +0,0 @@ -''' -Test ip_allow and ip_categories reload via ConfigRegistry. - -Verifies that: -1. ip_allow.yaml touch triggers ip_allow reload -2. ip_categories touch triggers ip_allow reload (via add_file_dependency) -3. Unrelated config touch (hosting.config) does NOT trigger ip_allow reload -4. ip_categories content change causes actual behavior change after reload -5. Changing ip_categories record value (traffic_ctl config set) triggers ip_allow reload -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import shutil - -Test.Summary = ''' -Test ip_allow and ip_categories reload via ConfigRegistry add_file_dependency. -''' - -Test.ContinueOnFail = True - -# --- Setup: origin server --- -server = Test.MakeOriginServer("server", ssl=False) -request = {"headers": "GET /test HTTP/1.1\r\nHost: www.example.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""} -response = { - "headers": "HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\n", - "timestamp": "1469733493.993", - "body": "ok" -} -server.addResponse("sessionlog.json", request, response) - -# --- Setup: ip_categories file variants --- - -# Version A: 127.0.0.1 is INTERNAL (requests allowed) -categories_allow = os.path.join(Test.RunDirectory, 'categories_allow.yaml') -with open(categories_allow, 'w') as f: - f.write('ip_categories:\n - name: INTERNAL\n ip_addrs: 127.0.0.1\n') - -# Version B: 127.0.0.1 is NOT INTERNAL (GET denied, only HEAD allowed by catch-all) -categories_deny = os.path.join(Test.RunDirectory, 'categories_deny.yaml') -with open(categories_deny, 'w') as f: - f.write('ip_categories:\n - name: INTERNAL\n ip_addrs: 1.2.3.4\n') - -# Version C: 127.0.0.1 back in INTERNAL (for record value change test) -categories_restore = os.path.join(Test.RunDirectory, 'categories_restore.yaml') -with open(categories_restore, 'w') as f: - f.write('ip_categories:\n - name: INTERNAL\n ip_addrs: 127.0.0.1\n') - -# Active ip_categories file that the record points to (start with allow version) -categories_file = os.path.join(Test.RunDirectory, 'ip_categories.yaml') -shutil.copy(categories_allow, categories_file) - -# --- Setup: ATS --- -ts = Test.MakeATSProcess("ts", enable_cache=True) - -ts.Disk.records_config.update( - { - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'ip_allow|config', - 'proxy.config.cache.ip_categories.filename': categories_file, - }) - -# ip_allow config: -# Rule 1: INTERNAL category → allow ALL methods -# Rule 2: catch-all 0/0 → allow only HEAD -# -# Effect: -# 127.0.0.1 in INTERNAL → GET /test → 200 (rule 1 matches) -# 127.0.0.1 NOT in INTERNAL → GET /test → 403 (rule 2 matches, GET not allowed) -ts.Disk.ip_allow_yaml.AddLines( - '''ip_allow: - - apply: in - ip_categories: INTERNAL - action: allow - methods: ALL - - apply: in - ip_addrs: 0/0 - action: allow - methods: - - HEAD -'''.split("\n")) - -ts.Disk.remap_config.AddLine(f'map / http://127.0.0.1:{server.Variables.Port}') - -config_dir = ts.Variables.CONFIGDIR -reload_counter = 0 - -# ================================================================ -# Test 1: Touch ip_allow.yaml → reload → ip_allow reloads (count 2) -# ================================================================ - -tr = Test.AddTestRun("Touch ip_allow.yaml") -tr.Processes.Default.StartBefore(server, ready=When.PortOpen(server.Variables.Port)) -tr.Processes.Default.StartBefore(ts) -tr.Processes.Default.Command = f"sleep 3 && touch {os.path.join(config_dir, 'ip_allow.yaml')} && sleep 1" -tr.Processes.Default.ReturnCode = 0 -tr.StillRunningAfter = ts -tr.StillRunningAfter = server - -reload_counter += 1 -tr = Test.AddTestRun("Reload after ip_allow.yaml touch") -p = tr.Processes.Process(f"reload-{reload_counter}") -p.Command = 'traffic_ctl config reload; sleep 30' -p.Env = ts.Env -p.ReturnCode = Any(0, -2) -p.Ready = When.FileContains(ts.Disk.diags_log.Name, "ip_allow.yaml finished loading", 1 + reload_counter) -p.Timeout = 20 -tr.Processes.Default.StartBefore(p) -tr.Processes.Default.Command = 'echo "waiting for ip_allow reload after ip_allow.yaml touch"' -tr.TimeOut = 25 -tr.StillRunningAfter = ts - -# ================================================================ -# Test 2: Touch ip_categories → reload → ip_allow reloads (count 3) -# Verifies add_file_dependency() correctly wired ip_categories -# to trigger ip_allow reload via FileManager mtime detection. -# ================================================================ - -tr = Test.AddTestRun("Touch ip_categories") -tr.Processes.Default.Command = f"touch {categories_file} && sleep 1" -tr.Processes.Default.ReturnCode = 0 -tr.StillRunningAfter = ts - -reload_counter += 1 -tr = Test.AddTestRun("Reload after ip_categories touch") -p = tr.Processes.Process(f"reload-{reload_counter}") -p.Command = 'traffic_ctl config reload; sleep 30' -p.Env = ts.Env -p.ReturnCode = Any(0, -2) -p.Ready = When.FileContains(ts.Disk.diags_log.Name, "ip_allow.yaml finished loading", 1 + reload_counter) -p.Timeout = 20 -tr.Processes.Default.StartBefore(p) -tr.Processes.Default.Command = 'echo "waiting for ip_allow reload after ip_categories touch"' -tr.TimeOut = 25 -tr.StillRunningAfter = ts - -# ================================================================ -# Test 3: Touch hosting.config → reload → ip_allow NOT triggered -# Verifies the fix for the false trigger bug where changing -# any file in the config directory spuriously triggered -# ip_allow reload (due to FileManager watching the directory -# instead of ip_categories.yaml when the record was ""). -# ================================================================ - -tr = Test.AddTestRun("Touch hosting.config and reload (should NOT trigger ip_allow)") -tr.Processes.Default.Command = ( - f"touch {os.path.join(config_dir, 'hosting.config')} && " - f"sleep 1 && " - f"traffic_ctl config reload && " - f"sleep 5") -tr.Processes.Default.Env = ts.Env -tr.Processes.Default.ReturnCode = Any(0, -2) -tr.Processes.Default.Timeout = 15 -tr.StillRunningAfter = ts - -tr = Test.AddTestRun("Verify ip_allow loaded exactly 3 times (no false trigger)") -tr.DelayStart = 3 -tr.Processes.Default.Command = (f"grep -c 'ip_allow.yaml finished loading' {ts.Disk.diags_log.Name} " - f"| grep -qx 3") -tr.Processes.Default.ReturnCode = 0 -tr.StillRunningAfter = ts - -# ================================================================ -# Test 4: Functional — change ip_categories content, verify behavior -# Proves that add_file_dependency() not only triggers the -# reload but that the handler actually re-reads the updated -# ip_categories file. -# ================================================================ - -# 4a: Verify initial state: GET → 200 (127.0.0.1 is in INTERNAL) -tr = Test.AddTestRun("GET should succeed (127.0.0.1 in INTERNAL category)") -tr.Processes.Default.Command = (f"curl -s -o /dev/null -w '%{{http_code}}' " - f"http://127.0.0.1:{ts.Variables.port}/test") -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stdout = Testers.ContainsExpression("200", "Should get 200 when 127.0.0.1 is in INTERNAL category") -tr.StillRunningAfter = ts -tr.StillRunningAfter = server - -# 4b: Swap ip_categories to deny version (127.0.0.1 NOT in INTERNAL) -tr = Test.AddTestRun("Change ip_categories to deny 127.0.0.1") -tr.Processes.Default.Command = f"cp {categories_deny} {categories_file}" -tr.Processes.Default.ReturnCode = 0 -tr.StillRunningAfter = ts - -# 4c: Reload and wait for ip_allow to pick up the change -reload_counter += 1 -tr = Test.AddTestRun("Reload after ip_categories content change") -p = tr.Processes.Process(f"reload-{reload_counter}") -p.Command = 'traffic_ctl config reload; sleep 30' -p.Env = ts.Env -p.ReturnCode = Any(0, -2) -p.Ready = When.FileContains(ts.Disk.diags_log.Name, "ip_allow.yaml finished loading", 1 + reload_counter) -p.Timeout = 20 -tr.Processes.Default.StartBefore(p) -tr.Processes.Default.Command = 'echo "waiting for reload after ip_categories content change"' -tr.TimeOut = 25 -tr.StillRunningAfter = ts - -# 4d: GET should now be denied (falls to catch-all: only HEAD allowed) -tr = Test.AddTestRun("GET should be denied (127.0.0.1 NOT in INTERNAL)") -tr.Processes.Default.Command = (f"curl -s -o /dev/null -w '%{{http_code}}' " - f"http://127.0.0.1:{ts.Variables.port}/test") -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stdout = Testers.ContainsExpression("403", "Should get 403 when 127.0.0.1 is not in INTERNAL category") -tr.StillRunningAfter = ts -tr.StillRunningAfter = server - -# ================================================================ -# Test 5: Record value change triggers ip_allow reload -# Changes the ip_categories filename record via traffic_ctl -# config set (no explicit config reload). The callback -# registered by add_file_dependency() fires when the record -# value changes, triggering ip_allow reload with the new file. -# ================================================================ - -# 5a: Change the ip_categories filename record to point to categories_restore -# (which has 127.0.0.1 back in INTERNAL). -# No traffic_ctl config reload — the RecRegisterConfigUpdateCb fires -# automatically via config_update_cont when the record value changes. -reload_counter += 1 -tr = Test.AddTestRun("Change ip_categories record value to new file") -p = tr.Processes.Process(f"reload-{reload_counter}") -p.Command = (f"traffic_ctl config set proxy.config.cache.ip_categories.filename " - f"'{categories_restore}'; sleep 30") -p.Env = ts.Env -p.ReturnCode = Any(0, -2) -p.Ready = When.FileContains(ts.Disk.diags_log.Name, "ip_allow.yaml finished loading", 1 + reload_counter) -p.Timeout = 20 -tr.Processes.Default.StartBefore(p) -tr.Processes.Default.Command = 'echo "waiting for ip_allow reload after record value change"' -tr.TimeOut = 25 -tr.StillRunningAfter = ts - -# 5b: GET should succeed again (new file has 127.0.0.1 in INTERNAL) -tr = Test.AddTestRun("GET should succeed after record value change") -tr.Processes.Default.Command = (f"curl -s -o /dev/null -w '%{{http_code}}' " - f"http://127.0.0.1:{ts.Variables.port}/test") -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stdout = Testers.ContainsExpression( - "200", "Should get 200 after restoring INTERNAL category via record change") -tr.StillRunningAfter = ts -tr.StillRunningAfter = server diff --git a/tests/gold_tests/ip_allow/ip_category.test.py b/tests/gold_tests/ip_allow/ip_category.test.py index 13a9899e807..76a8c1b7735 100644 --- a/tests/gold_tests/ip_allow/ip_category.test.py +++ b/tests/gold_tests/ip_allow/ip_category.test.py @@ -218,17 +218,11 @@ def _configure_traffic_server(self) -> None: Test_ip_category._ts = ts ts.addDefaultSSLFiles() - ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.records_config.update( { 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'http|ip_allow|config.reload', + 'proxy.config.diags.debug.tags': 'http|ip_allow', 'proxy.config.cache.ip_categories.filename': Test_ip_category._categories_filename, 'proxy.config.http.push_method_enabled': 1, 'proxy.config.ssl.server.cert.path': ts.Variables.SSLDir, diff --git a/tests/gold_tests/jsonrpc/config_reload_dedup.test.py b/tests/gold_tests/jsonrpc/config_reload_dedup.test.py deleted file mode 100644 index 1426aed481b..00000000000 --- a/tests/gold_tests/jsonrpc/config_reload_dedup.test.py +++ /dev/null @@ -1,160 +0,0 @@ -''' -Test that a trigger-record value change in records.yaml is handled correctly -during a reload, including deduplication when the 3s config_update_cont timer -fires the same on_record_change callback again. - -Scenario: - 1. Change proxy.config.ssl.server.session_ticket.enable in records.yaml - via --cold. This is a trigger record for ssl_client_coordinator - (registered via register_record_config with 11 trigger records). - - 2. Touch sni.yaml — this also triggers ssl_client_coordinator via - add_file_and_node_dependency (proxy.config.ssl.servername.filename). - So ssl_client_coordinator gets hit from TWO paths: the value change - callback AND the file-mtime callback. - - 3. Touch other config files (ip_allow, logging) for broader coverage. - - 4. Trigger a reload and wait for completion. - - 5. Wait long enough (>6s) for the 3s config_update_cont timer to fire - at least once during or after the reload. When it fires: - a. If the reload is still in progress — create_config_context() - finds the existing subtask and logs "Duplicate reload …skipping". - b. If the reload already completed (SUCCESS) — create_config_context() - may create a new subtask, the handler runs again, and the task - re-settles to SUCCESS. - Either way, no crash, no stuck tasks, no state-transition conflicts. - - 6. Verify: - - "Reserved subtask" messages present (reserve_subtask worked) - - ssl_client_coordinator subtask completed successfully - - No "ignoring transition from" messages (no state corruption) - - Final status is success (even if briefly flickered) -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = 'Verify trigger-record value change + file-mtime dedup during reload' -Test.ContinueOnFail = True - -# --- Setup --- -ts = Test.MakeATSProcess("ts", enable_cache=True) -ts.StartupTimeout = 30 - -ts.Disk.records_config.update( - { - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'rpc|config|config.reload|configproc', - }) - -# Provide valid content for config files whose handlers reject empty input. -ts.Disk.ip_allow_yaml.AddLines([ - 'ip_allow:', - '- apply: in', - ' ip_addrs: 0/0', - ' action: allow', - ' methods: ALL', -]) -ts.Disk.logging_yaml.AddLines([ - 'logging:', - ' formats:', - ' - name: dedup_test', - ' format: "%"', -]) -ts.Disk.sni_yaml.AddLines([ - 'sni:', - '- fqdn: "*.example.com"', - ' verify_client: NONE', -]) - -# Files to touch — sni.yaml is key because it also triggers -# ssl_client_coordinator via add_file_and_node_dependency. -files_to_touch = [ - ts.Disk.ip_allow_yaml, - ts.Disk.logging_yaml, - ts.Disk.sni_yaml, -] -touch_cmd = "touch " + " ".join([f.AbsRunTimePath for f in files_to_touch]) - -# ============================================================================ -# Test 1: Change a trigger-record VALUE in records.yaml via --cold -# -# proxy.config.ssl.server.session_ticket.enable is a trigger record for -# ssl_client_coordinator. Toggling it from 1→0 modifies records.yaml on -# disk. When the reload fires, rereadConfig → RecReadYamlConfigFile → -# RecSetRecord sets update_required on this record. RecFlushConfigUpdateCbs -# fires the on_record_change callback. Later the 3s timer may fire it again. -# ============================================================================ -tr = Test.AddTestRun("Change trigger record value in records.yaml via --cold") -tr.Processes.Default.StartBefore(ts) -tr.Processes.Default.Command = ('sleep 3 && traffic_ctl config set proxy.config.ssl.server.session_ticket.enable 0 --cold') -tr.Processes.Default.Env = ts.Env -tr.Processes.Default.ReturnCode = 0 -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 2: Touch config files — sni.yaml is the critical one -# -# sni.yaml's mtime change triggers RecSetSyncRequired on -# proxy.config.ssl.servername.filename, which is wired to -# ssl_client_coordinator. So ssl_client_coordinator gets hit from two paths: -# 1. proxy.config.ssl.server.session_ticket.enable value change (from records.yaml) -# 2. proxy.config.ssl.servername.filename sync-required (from sni.yaml mtime) -# This exercises the fan-in deduplication in on_record_change / reserve_subtask. -# ============================================================================ -tr = Test.AddTestRun("Touch config files including sni.yaml") -tr.Processes.Default.Command = touch_cmd -tr.Processes.Default.ReturnCode = 0 -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 3: Trigger reload -# ============================================================================ -tr = Test.AddTestRun("Trigger reload with named token") -tr.Processes.Default.Command = "traffic_ctl config reload -t dedup_test" -tr.Processes.Default.Env = ts.Env -tr.Processes.Default.ReturnCode = 0 -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 4: Wait long enough for the 3s timer to fire at least twice -# -# The config_update_cont timer fires every 3s. We wait 15s to ensure: -# - All subtasks complete (ip_allow, logging, remap, ssl, etc.) -# - The 3s timer fires at least 4 times during/after the reload -# - Any duplicate on_record_change callbacks have been processed -# ============================================================================ -tr = Test.AddTestRun("Verify reload completed — no tasks stuck in progress") -tr.DelayStart = 15 -tr.Processes.Default.Command = "traffic_ctl config status -t dedup_test" -tr.Processes.Default.Env = ts.Env -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stdout += Testers.ExcludesExpression( - "in_progress", "No task should remain in progress after 15s delay") -# ssl_client_coordinator must be present in the output (it was triggered) -tr.Processes.Default.Streams.stdout += Testers.ContainsExpression( - "ssl_client_coordinator", "ssl_client_coordinator subtask must appear in status") -tr.Processes.Default.Streams.stdout += Testers.ContainsExpression("success", "Final status must be success") -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 5: No state-transition conflicts anywhere in the logs -# ============================================================================ -ts.Disk.traffic_out.Content += Testers.ExcludesExpression( - "ignoring transition from", "No state-transition conflicts should appear in traffic.out") -ts.Disk.traffic_out.Content += Testers.ContainsExpression( - "Reserved subtask", "reserve_subtask() must log pre-registration messages") diff --git a/tests/gold_tests/jsonrpc/config_reload_failures.test.py b/tests/gold_tests/jsonrpc/config_reload_failures.test.py deleted file mode 100644 index b000176f4dc..00000000000 --- a/tests/gold_tests/jsonrpc/config_reload_failures.test.py +++ /dev/null @@ -1,429 +0,0 @@ -''' -Test config reload failure scenarios. - -Tests: -1. Failed tasks (invalid config content) -2. Failed subtasks (invalid SSL certificates) -3. Incomplete subtasks (timeout scenarios) -4. Status propagation when subtasks fail -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from jsonrpc import Request, Response -import os - -Test.Summary = 'Test config reload failure scenarios and status propagation' -Test.ContinueOnFail = True - -ts = Test.MakeATSProcess('ts', dump_runroot=True, enable_tls=True) - -Test.testName = 'config_reload_failures' - -# Enable debugging -ts.Disk.records_config.update( - { - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'config|ssl|ip_allow', - 'proxy.config.ssl.server.cert.path': ts.Variables.SSLDir, - 'proxy.config.ssl.server.private_key.path': ts.Variables.SSLDir, - }) - -# Add valid SSL certs for baseline -ts.addDefaultSSLFiles() - -ts.Disk.ssl_multicert_yaml.AddLines( - [ - 'ssl_multicert:', - ' - dest_ip: "*"', - ' ssl_cert_name: server.pem', - ' ssl_key_name: server.key', - ]) - -# Override default diags check — this test intentionally triggers SSL errors -ts.Disk.diags_log.Content = Testers.ContainsExpression("ERROR", "Expected errors from invalid SSL cert injection") - -# ============================================================================ -# Test 1: Baseline - successful reload -# ============================================================================ -tr = Test.AddTestRun("Baseline - successful reload") -tr.Processes.Default.StartBefore(ts) -tr.DelayStart = 2 -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload(force=True)) - - -def validate_baseline(resp: Response): - '''Verify baseline reload succeeds''' - if resp.is_error(): - return (False, f"Baseline failed: {resp.error_as_str()}") - - result = resp.result - token = result.get('token', '') - return (True, f"Baseline succeeded: token={token}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_baseline) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 2: Wait and check baseline completed successfully -# ============================================================================ -tr = Test.AddTestRun("Verify baseline completed with success status") -tr.DelayStart = 3 -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload()) - - -def validate_baseline_status(resp: Response): - '''Check baseline reload status''' - if resp.is_error(): - error = resp.error_as_str() - if 'in progress' in error.lower(): - return (True, f"Still in progress: {error}") - return (True, f"Query result: {error}") - - result = resp.result - tasks = result.get('tasks', []) - - # Check for any failed tasks - def find_failures(task_list): - failures = [] - for t in task_list: - if t.get('status') == 'fail': - failures.append(t.get('description', 'unknown')) - failures.extend(find_failures(t.get('sub_tasks', []))) - return failures - - failures = find_failures(tasks) - if failures: - return (True, f"Found failures (may be expected): {failures}") - - return (True, f"Baseline status OK, {len(tasks)} tasks") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_baseline_status) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 3: Inject invalid ip_allow config (should fail) -# ============================================================================ -tr = Test.AddTestRun("Inject invalid ip_allow config") -tr.DelayStart = 2 - -# Invalid ip_allow YAML - missing required fields -invalid_ip_allow = """ip_allow: - - apply: invalid_value - action: not_a_valid_action -""" - -# This should trigger a validation error in IpAllow -# Note: The actual behavior depends on how strict the parser is -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload(force=True)) - - -def validate_after_invalid_config(resp: Response): - '''Check reload after invalid config injection''' - if resp.is_error(): - return (True, f"Reload error (may be expected): {resp.error_as_str()}") - - result = resp.result - token = result.get('token', '') - errors = result.get('error', []) - - if errors: - return (True, f"Reload reported errors: {errors}") - - return (True, f"Reload started: {token}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_after_invalid_config) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 4: Add invalid SSL cert reference (subtask failure) -# ============================================================================ -tr = Test.AddTestRun("Configure invalid SSL cert path") -tr.DelayStart = 2 - -# Add a bad cert reference to ssl_multicert.yaml -# This should cause the SSL subtask to fail -sslcertpath = ts.Disk.ssl_multicert_yaml.AbsPath -tr.Disk.File(sslcertpath, id="ssl_multicert_yaml", typename="ats:config") -tr.Disk.ssl_multicert_yaml.AddLines( - [ - 'ssl_multicert:', - ' - dest_ip: "*"', - ' ssl_cert_name: server.pem', - ' ssl_key_name: server.key', - ' - dest_ip: 1.2.3.4', - ' ssl_cert_name: /nonexistent/bad.pem', - ' ssl_key_name: /nonexistent/bad.key', - ]) - -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload(force=True)) - - -def validate_ssl_failure(resp: Response): - '''Check reload with bad SSL config''' - if resp.is_error(): - return (True, f"SSL reload error: {resp.error_as_str()}") - - result = resp.result - token = result.get('token', '') - return (True, f"SSL reload started: {token}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_ssl_failure) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 5: Check for failed subtasks after SSL reload -# ============================================================================ -tr = Test.AddTestRun("Check for failed SSL subtasks") -tr.DelayStart = 3 -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload()) - - -def validate_failed_subtasks(resp: Response): - '''Check if SSL subtasks show failure''' - if resp.is_error(): - error = resp.error_as_str() - if 'in progress' in error.lower(): - return (True, f"Still in progress: {error}") - return (True, f"Query: {error}") - - result = resp.result - tasks = result.get('tasks', []) - - def analyze_tasks(task_list, depth=0): - analysis = [] - for t in task_list: - desc = t.get('description', 'unknown') - status = t.get('status', 'unknown') - logs = t.get('logs', []) - - info = f"{' '*depth}{desc}: {status}" - if status == 'fail' and logs: - info += f" - {logs[0][:50]}..." - - analysis.append(info) - - # Recurse into subtasks - analysis.extend(analyze_tasks(t.get('sub_tasks', []), depth + 1)) - - return analysis - - task_info = analyze_tasks(tasks) - return (True, f"Task analysis:\n" + "\n".join(task_info[:10])) - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_failed_subtasks) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 6: Verify parent status reflects subtask failure -# ============================================================================ -tr = Test.AddTestRun("Verify status propagation from subtask to parent") -tr.DelayStart = 1 -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload()) - - -def validate_status_propagation(resp: Response): - '''Verify failed subtask propagates to parent''' - if resp.is_error(): - error = resp.error_as_str() - if 'in progress' in error.lower(): - return (True, f"In progress: {error}") - return (True, f"Query: {error}") - - result = resp.result - tasks = result.get('tasks', []) - - def check_propagation(task_list): - """ - For each task with subtasks, verify: - - If any subtask is 'fail', parent should be 'fail' - - If any subtask is 'in_progress', parent should be 'in_progress' - - If all subtasks are 'success', parent should be 'success' - """ - issues = [] - for t in task_list: - sub_tasks = t.get('sub_tasks', []) - if not sub_tasks: - continue - - parent_status = t.get('status', '') - sub_statuses = [st.get('status', '') for st in sub_tasks] - - if 'fail' in sub_statuses and parent_status != 'fail': - issues.append(f"{t.get('description')}: has failed subtask but parent is '{parent_status}'") - - if 'in_progress' in sub_statuses and parent_status != 'in_progress': - issues.append(f"{t.get('description')}: has in_progress subtask but parent is '{parent_status}'") - - # Recurse - issues.extend(check_propagation(sub_tasks)) - - return issues - - issues = check_propagation(tasks) - if issues: - return (True, f"Propagation issues found: {issues}") - - return (True, "Status propagation verified correctly") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_status_propagation) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 7: Check task logs contain error details -# ============================================================================ -tr = Test.AddTestRun("Check failed tasks have error logs") -tr.DelayStart = 1 -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload()) - - -def validate_error_logs(resp: Response): - '''Verify failed tasks have descriptive logs''' - if resp.is_error(): - return (True, f"Query: {resp.error_as_str()}") - - result = resp.result - tasks = result.get('tasks', []) - - def find_failed_with_logs(task_list): - results = [] - for t in task_list: - if t.get('status') == 'fail': - logs = t.get('logs', []) - desc = t.get('description', 'unknown') - if logs: - results.append(f"{desc}: {logs}") - else: - results.append(f"{desc}: NO LOGS (should have error details)") - - results.extend(find_failed_with_logs(t.get('sub_tasks', []))) - return results - - failed_info = find_failed_with_logs(tasks) - if failed_info: - return (True, f"Failed tasks with logs: {failed_info}") - - return (True, "No failed tasks found (baseline may have recovered)") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_error_logs) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 8: Force reload to reset state -# ============================================================================ -tr = Test.AddTestRun("Force reload to reset") -tr.DelayStart = 2 - -# Reset ssl_multicert.yaml to valid state -sslcertpath = ts.Disk.ssl_multicert_yaml.AbsPath -tr.Disk.File(sslcertpath, id="ssl_multicert_yaml", typename="ats:config") -tr.Disk.ssl_multicert_yaml.AddLines( - [ - 'ssl_multicert:', - ' - dest_ip: "*"', - ' ssl_cert_name: server.pem', - ' ssl_key_name: server.key', - ]) - -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload(force=True)) - - -def validate_reset(resp: Response): - '''Reset to clean state''' - if resp.is_error(): - return (True, f"Reset: {resp.error_as_str()}") - - result = resp.result - token = result.get('token', '') - return (True, f"Reset reload started: {token}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_reset) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 9: Verify clean state after reset -# ============================================================================ -tr = Test.AddTestRun("Verify clean state after reset") -tr.DelayStart = 3 -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload()) - - -def validate_clean_state(resp: Response): - '''Verify we're back to clean state''' - if resp.is_error(): - error = resp.error_as_str() - if 'in progress' in error.lower(): - return (True, f"In progress: {error}") - return (True, f"Query: {error}") - - result = resp.result - tasks = result.get('tasks', []) - - # Count failures - def count_failures(task_list): - count = 0 - for t in task_list: - if t.get('status') == 'fail': - count += 1 - count += count_failures(t.get('sub_tasks', [])) - return count - - failures = count_failures(tasks) - if failures > 0: - return (True, f"Still have {failures} failed tasks (may need more time)") - - return (True, "Clean state - no failures") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_clean_state) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 10: Summary -# ============================================================================ -tr = Test.AddTestRun("Final summary") -tr.DelayStart = 1 -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload()) - - -def validate_summary(resp: Response): - '''Final summary of failure testing''' - if resp.is_error(): - return (True, f"Final: {resp.error_as_str()}") - - result = resp.result - - summary = """ - Config Reload Failure Testing Summary: - - Failed tasks: Detected when config validation fails - - Failed subtasks: SSL cert failures propagate to parent - - Status propagation: Parent status reflects worst subtask status - - Error logs: Failed tasks should include error details - """ - - return (True, f"Test complete. Token: {result.get('token', 'none')}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_summary) -tr.StillRunningAfter = ts diff --git a/tests/gold_tests/jsonrpc/config_reload_full_smoke.test.py b/tests/gold_tests/jsonrpc/config_reload_full_smoke.test.py deleted file mode 100644 index e1788b3a15d..00000000000 --- a/tests/gold_tests/jsonrpc/config_reload_full_smoke.test.py +++ /dev/null @@ -1,165 +0,0 @@ -''' -Full reload smoke test. - -Verifies that ALL registered config handlers complete properly by: - Part A: Touching every registered config file, triggering a reload with a - named token, and verifying all deferred handlers reach a terminal - state (no in_progress after a delay). - Part B: Changing one record per module via traffic_ctl config set, waiting, - then verifying no terminal-state conflicts appear in diags.log. - -Registered configs at time of writing: - Files: ip_allow.yaml, parent.config, cache.config, hosting.config, - splitdns.config, logging.yaml, sni.yaml, ssl_multicert.yaml - Record-only: ssl_ticket_key (proxy.config.ssl.server.ticket_key.filename) - -The key assertion is that diags.log does NOT contain: - "ignoring transition from" — means two code paths disagree about task outcome -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = 'Full reload smoke test: all config files + record triggers' -Test.ContinueOnFail = True - -# --- Setup --- -ts = Test.MakeATSProcess("ts", enable_cache=True) -ts.Disk.records_config.update({ - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'rpc|config|reload', -}) - -# ============================================================================ -# Part A: File-based full reload -# -# ATS starts with valid config content (written via AddLines before start). -# We then touch every file to bump mtime and trigger a reload — this exercises -# the full parse path of each handler. -# ============================================================================ - -# Provide valid content for files whose handlers reject empty input. -ts.Disk.ip_allow_yaml.AddLines([ - 'ip_allow:', - '- apply: in', - ' ip_addrs: 0/0', - ' action: allow', - ' methods: ALL', -]) -ts.Disk.logging_yaml.AddLines([ - 'logging:', - ' formats:', - ' - name: smoke', - ' format: "%"', -]) -ts.Disk.sni_yaml.AddLines([ - 'sni:', - '- fqdn: "*.example.com"', - ' verify_client: NONE', -]) -# parent.config, cache.config, hosting.config, splitdns.config, -# ssl_multicert.yaml are fine empty — handlers accept empty/comment-only files. - -# All registered config files whose mtime we'll bump to trigger reload. -files_to_touch = [ - ts.Disk.ip_allow_yaml, - ts.Disk.parent_config, - ts.Disk.cache_config, - ts.Disk.hosting_config, - ts.Disk.splitdns_config, - ts.Disk.logging_yaml, - ts.Disk.sni_yaml, - ts.Disk.ssl_multicert_yaml, -] -touch_cmd = "touch " + " ".join([f.AbsRunTimePath for f in files_to_touch]) -# Modify records.yaml via traffic_ctl --cold to trigger a real records reload. -records_cmd = 'traffic_ctl config set proxy.config.diags.debug.tags "rpc|config|reload|upd" --cold' - -# Test 1: Start ATS, wait for it to settle, update records.yaml on disk -tr = Test.AddTestRun("Update records.yaml via --cold") -tr.Processes.Default.StartBefore(ts) -tr.Processes.Default.Command = f"sleep 3 && {records_cmd}" -tr.Processes.Default.Env = ts.Env -tr.Processes.Default.ReturnCode = 0 -tr.StillRunningAfter = ts - -# Test 2: Touch all other config files to bump mtime -tr = Test.AddTestRun("Touch all registered config files") -tr.Processes.Default.Command = touch_cmd -tr.Processes.Default.ReturnCode = 0 -tr.StillRunningAfter = ts - -# Test 3: Reload with token — all handlers re-read from disk -tr = Test.AddTestRun("Reload with token - show details") -tr.Processes.Default.Command = "traffic_ctl config reload -t full_reload_smoke" -tr.Processes.Default.Env = ts.Env -tr.Processes.Default.ReturnCode = 0 -tr.StillRunningAfter = ts - -# Test 4: Query status after delay — all deferred handlers (e.g. logging) should have -# reached a terminal state by now. -tr = Test.AddTestRun("Verify no tasks stuck in progress after delay") -tr.DelayStart = 15 -tr.Processes.Default.Command = "traffic_ctl config status -t full_reload_smoke" -tr.Processes.Default.Env = ts.Env -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stdout += Testers.ExcludesExpression("in_progress", "No task should remain in progress after 15s") -tr.StillRunningAfter = ts - -# ============================================================================ -# Part B: Record-triggered reloads (tracing ENABLED — default) -# -# Change one record per module to exercise the RecordTriggeredReloadContinuation path. -# With proxy.config.admin.reload.trace_record_triggers=1 (default), each -# record-triggered reload creates a "rec-" parent task visible in status/history. -# ============================================================================ - -# One safe record per module: -records_to_change = [ - # (record_name, new_value) — pick values that won't break ATS - ("proxy.config.log.sampling_frequency", "2"), # logging - ("proxy.config.ssl.server.session_ticket.enable", "0"), # ssl_client_coordinator -] - -for record_name, new_value in records_to_change: - tr = Test.AddTestRun(f"Set {record_name}={new_value}") - tr.DelayStart = 2 - tr.Processes.Default.Command = f"traffic_ctl config set {record_name} {new_value}" - tr.Processes.Default.Env = ts.Env - tr.Processes.Default.ReturnCode = 0 - tr.StillRunningAfter = ts - -# Wait for record-triggered reloads to complete -tr = Test.AddTestRun("Wait for record-triggered reloads") -tr.DelayStart = 10 -tr.Processes.Default.Command = "echo 'Waiting for record-triggered reloads to settle'" -tr.Processes.Default.ReturnCode = 0 -tr.StillRunningAfter = ts - -# Final dump of all reload history -tr = Test.AddTestRun("Fetch all reload history") -tr.Processes.Default.Command = "traffic_ctl config status -c all" -tr.Processes.Default.Env = ts.Env -tr.Processes.Default.ReturnCode = 0 -tr.StillRunningAfter = ts - -# ============================================================================ -# Global assertions on diags.log -# ============================================================================ - -# No handler should have conflicting terminal state transitions. -# This catches bugs where e.g. evaluate_config() calls fail() and then -# change_configuration() calls complete() — the guard rejects the second call. -ts.Disk.diags_log.Content = Testers.ExcludesExpression("ignoring transition from", "No handler should fight over terminal states") diff --git a/tests/gold_tests/jsonrpc/config_reload_reserve_subtask.test.py b/tests/gold_tests/jsonrpc/config_reload_reserve_subtask.test.py deleted file mode 100644 index d4b5b17c39f..00000000000 --- a/tests/gold_tests/jsonrpc/config_reload_reserve_subtask.test.py +++ /dev/null @@ -1,134 +0,0 @@ -''' -Test that reserve_subtask correctly pre-registers subtasks during a reload. - -When records.yaml is changed alongside other config files, the "records" -subtask completes first (records.yaml is always processed first in -rereadConfig). This pushes the main task to SUCCESS before other -file-based or record-triggered handlers have a chance to register their -subtasks. reserve_subtask() must accept a SUCCESS parent and pull it back -to IN_PROGRESS by adding a CREATED child. - -This test: - 1. Modifies records.yaml on disk (--cold) to change a trigger record so - that a record-triggered handler fires during the next reload. - 2. Touches additional config files so file-based handlers also fire. - 3. Triggers a reload with a named token. - 4. Verifies that traffic.out contains "Reserved subtask" messages — proving - reserve_subtask() succeeded despite the parent being SUCCESS. - 5. Verifies no "ignoring transition" warnings (no state conflicts). - 6. Verifies the reload reaches a terminal state with all subtasks tracked. -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = 'Verify reserve_subtask pre-registers subtasks when records.yaml is first' -Test.ContinueOnFail = True - -# --- Setup --- -ts = Test.MakeATSProcess("ts", enable_cache=True) -# Allow extra startup time — cache clearing can take ~10s in some environments. -ts.StartupTimeout = 30 -ts.Disk.records_config.update( - { - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'rpc|config|config.reload|filemanager', - }) - -# Provide valid content for files whose handlers reject empty input. -ts.Disk.ip_allow_yaml.AddLines([ - 'ip_allow:', - '- apply: in', - ' ip_addrs: 0/0', - ' action: allow', - ' methods: ALL', -]) -ts.Disk.logging_yaml.AddLines([ - 'logging:', - ' formats:', - ' - name: reserve_test', - ' format: "%"', -]) -ts.Disk.sni_yaml.AddLines([ - 'sni:', - '- fqdn: "*.example.com"', - ' verify_client: NONE', -]) - -# Files to touch to trigger file-based handlers during reload. -files_to_touch = [ - ts.Disk.ip_allow_yaml, - ts.Disk.logging_yaml, - ts.Disk.sni_yaml, - ts.Disk.cache_config, -] -touch_cmd = "touch " + " ".join([f.AbsRunTimePath for f in files_to_touch]) - -# ============================================================================ -# Test 1: Start ATS, let it settle, then modify records.yaml on disk -# ============================================================================ -tr = Test.AddTestRun("Modify records.yaml via --cold to change a trigger record") -tr.Processes.Default.StartBefore(ts) -# Change debug tags — this touches records.yaml on disk so rereadConfig -# detects it as changed. The --cold flag modifies the file without -# notifying the running process (the reload will pick it up). -tr.Processes.Default.Command = ( - 'sleep 3 && traffic_ctl config set proxy.config.diags.debug.tags ' - '"rpc|config|config.reload|filemanager|upd" --cold') -tr.Processes.Default.Env = ts.Env -tr.Processes.Default.ReturnCode = 0 -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 2: Touch additional config files to bump mtime -# ============================================================================ -tr = Test.AddTestRun("Touch config files to trigger file-based handlers") -tr.Processes.Default.Command = touch_cmd -tr.Processes.Default.ReturnCode = 0 -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 3: Trigger reload with a named token -# ============================================================================ -tr = Test.AddTestRun("Trigger reload with named token") -tr.Processes.Default.Command = "traffic_ctl config reload -t reserve_subtask_test" -tr.Processes.Default.Env = ts.Env -tr.Processes.Default.ReturnCode = 0 -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 4: Wait for all handlers to complete, then query status -# ============================================================================ -tr = Test.AddTestRun("Verify reload completed — no tasks stuck in progress") -tr.DelayStart = 15 -tr.Processes.Default.Command = "traffic_ctl config status -t reserve_subtask_test" -tr.Processes.Default.Env = ts.Env -tr.Processes.Default.ReturnCode = 0 -# No subtask should still be in_progress after 15s. -tr.Processes.Default.Streams.stdout += Testers.ExcludesExpression( - "in_progress", "No task should remain in progress after 15s delay") -tr.Processes.Default.Streams.stdout += Testers.ContainsExpression("success", "Final reload status must be success") -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 5: Verify no state-transition conflicts in traffic.out -# -# "ignoring transition from" means two code paths disagree about a task's -# outcome. This must never happen. -# ============================================================================ -ts.Disk.traffic_out.Content += Testers.ExcludesExpression( - "ignoring transition from", "No state-transition conflicts should appear in traffic.out") -ts.Disk.traffic_out.Content += Testers.ContainsExpression( - "Reserved subtask", "reserve_subtask() must log pre-registration messages") diff --git a/tests/gold_tests/jsonrpc/config_reload_rpc.test.py b/tests/gold_tests/jsonrpc/config_reload_rpc.test.py deleted file mode 100644 index 55d38103452..00000000000 --- a/tests/gold_tests/jsonrpc/config_reload_rpc.test.py +++ /dev/null @@ -1,491 +0,0 @@ -''' -Test inline config reload functionality via unified admin_config_reload RPC method. - -Inline mode is triggered by passing the "configs" parameter. -Tests the following features: -1. Basic inline reload with single config -2. Multiple configs in single request -3. File-based vs inline mode detection -4. Unknown config key error handling -5. Invalid YAML content handling -6. Reload while another is in progress -7. Verify config is actually applied -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from jsonrpc import Request, Response - -Test.Summary = 'Test inline config reload via RPC' -Test.ContinueOnFail = True - -ts = Test.MakeATSProcess('ts', dump_runroot=True) - -Test.testName = 'config_reload_rpc' - -# Initial configuration -ts.Disk.records_config.update({ - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'rpc|config', -}) - -# ============================================================================ -# Test 1: File-based reload (no configs parameter) -# ============================================================================ -tr = Test.AddTestRun("File-based reload without configs parameter") -tr.Processes.Default.StartBefore(ts) -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload()) - - -def validate_file_based(resp: Response): - '''Verify file-based reload works when configs is not provided''' - result = resp.result - token = result.get('token', '') - message = result.get('message', []) - - if token: - return (True, f"File-based reload started: token={token}") - - errors = result.get('errors', []) - if errors: - return (True, f"File-based reload response: {errors}") - - return (True, f"Response: {result}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_file_based) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 2: Empty configs map (should trigger inline mode but process 0 configs) -# ============================================================================ -tr = Test.AddTestRun("Empty configs map") -tr.DelayStart = 2 -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload(configs={})) - - -def validate_empty_configs(resp: Response): - '''Verify behavior with empty configs''' - result = resp.result - - # Empty configs should succeed but with 0 changes - success = result.get('success', -1) - failed = result.get('failed', -1) - - if success == 0 and failed == 0: - return (True, f"Empty configs handled: success={success}, failed={failed}") - - # Or it might be an error - errors = result.get('errors', []) - if errors: - return (True, f"Empty configs rejected: {errors}") - - return (True, f"Result: {result}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_empty_configs) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 3: Unknown config key (error code 6010) -# ============================================================================ -tr = Test.AddTestRun("Unknown config key should error with code 6010") -tr.DelayStart = 2 -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload(configs={"unknown_config_key": {"some": "data"}})) - - -def validate_unknown_key(resp: Response): - '''Verify error for unknown config key - should return error code 6010''' - result = resp.result - errors = result.get('errors', []) - - if not errors: - return (False, f"Expected error for unknown key, got: {result}") - - error_str = str(errors) - if '6010' in error_str or 'not registered' in error_str: - return (True, f"Unknown key rejected with code 6010: {errors}") - return (False, f"Expected error 6010, got: {errors}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_unknown_key) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 3b: Unregistered config rejected (error code 6010) -# Note: remap.config is not registered in ConfigRegistry -# ============================================================================ -tr = Test.AddTestRun("Unregistered config should error with code 6010") -tr.DelayStart = 1 -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload(configs={"remap.config": {"some": "data"}})) - - -def validate_legacy_not_supported(resp: Response): - '''Verify unregistered config returns error code 6010''' - result = resp.result - errors = result.get('errors', []) - - if not errors: - return (False, f"Expected rejection for unregistered config, got: {result}") - - error_str = str(errors) - if '6010' in error_str or 'not registered' in error_str: - return (True, f"Unregistered config correctly rejected: {errors}") - return (False, f"Expected error 6010, got: {errors}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_legacy_not_supported) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 4: RPC-injected content rejected for FileOnly config (ip_allow) -# ip_allow is registered with ConfigSource::FileOnly — RPC content must be rejected -# ============================================================================ -tr = Test.AddTestRun("RPC-injected content rejected for FileOnly config (ip_allow)") -tr.DelayStart = 2 -tr.AddJsonRPCClientRequest( - ts, - Request.admin_config_reload( - configs={"ip_allow": [{ - "apply": "in", - "ip_addrs": "127.0.0.1", - "action": "allow", - "methods": ["GET", "HEAD"] - }]})) - - -def validate_rpc_inject_rejected(resp: Response): - '''ip_allow is registered as FileOnly — RPC-injected content must be rejected with 6011''' - result = resp.result - errors = result.get('errors', []) - - if not errors: - return (False, f"Expected rejection for FileOnly config, got: {result}") - - error_str = str(errors) - if '6011' in error_str or 'does not support RPC' in error_str: - return (True, f"FileOnly config correctly rejected RPC injection: {errors}") - return (False, f"Expected error 6011, got: {errors}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_rpc_inject_rejected) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 5: Multiple configs in single request -# ============================================================================ -tr = Test.AddTestRun("Multiple configs in single request") -tr.DelayStart = 2 -tr.AddJsonRPCClientRequest( - ts, - Request.admin_config_reload( - configs={ - "ip_allow": [{ - "apply": "in", - "ip_addrs": "0.0.0.0/0", - "action": "allow" - }], - "sni": [{ - "fqdn": "*.test.com", - "verify_client": "NONE" - }], - "records": { - "diags": { - "debug": { - "enabled": 1 - } - } - } - })) - - -def validate_multiple_configs(resp: Response): - '''All configs should be rejected — none support RPC content source at this stage''' - result = resp.result - errors = result.get('errors', []) - - if not errors: - return (False, f"Expected rejections for all configs, got: {result}") - - # Each config should produce an error (6010=not registered, 6011=RPC source not supported) - error_str = str(errors) - if '6010' in error_str or '6011' in error_str: - return (True, f"All configs rejected as expected: {len(errors)} errors") - return (False, f"Unexpected errors: {errors}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_multiple_configs) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 6: Reload while another is in progress -# ============================================================================ -tr = Test.AddTestRun("First reload request") -tr.DelayStart = 1 -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload()) - - -def validate_first_reload(resp: Response): - '''Start a regular reload''' - result = resp.result - token = result.get('token', '') - return (True, f"First reload started: token={token}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_first_reload) -tr.StillRunningAfter = ts - -# Immediately try inline reload -tr = Test.AddTestRun("Inline reload while regular reload in progress") -tr.DelayStart = 0 # No delay - immediately after -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload(configs={"ip_allow": [{"apply": "in", "ip_addrs": "10.0.0.0/8"}]})) - - -def validate_in_progress_rejection(resp: Response): - '''Should be rejected for RPC source not supported or reload in progress''' - result = resp.result - errors = result.get('errors', []) - - if not errors: - return (False, f"Expected rejection, got: {result}") - - error_str = str(errors) - # Either 6011 (RPC source not supported) or 6004 (reload in progress) - if '6011' in error_str or '6004' in error_str: - return (True, f"Correctly rejected: {errors}") - return (False, f"Unexpected error: {errors}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_in_progress_rejection) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 7: Verify token is returned with inline- prefix -# ============================================================================ -tr = Test.AddTestRun("Verify inline token prefix") -tr.DelayStart = 3 # Wait for previous reloads to complete -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload(configs={"unknown_for_token_test": {"data": "value"}})) - - -def validate_inline_token(resp: Response): - '''Verify token has inline- prefix''' - result = resp.result - token = result.get('token', '') - - if token and token.startswith('inline-'): - return (True, f"Token has correct prefix: {token}") - - if not token: - # Check if there's an error (which is fine) - errors = result.get('errors', []) - if errors: - return (True, f"No token (error case): {errors}") - - return (True, f"Token result: {token}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_inline_token) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 8: Nested YAML structure (records.diags.debug) -# ============================================================================ -tr = Test.AddTestRun("Nested YAML structure") -tr.DelayStart = 2 -tr.AddJsonRPCClientRequest( - ts, - Request.admin_config_reload( - configs={"records": { - "diags": { - "debug": { - "enabled": 1, - "tags": "http|rpc|test" - } - }, - "http": { - "cache": { - "http": 1 - } - } - }})) - - -def validate_nested_yaml(resp: Response): - '''Verify nested YAML handling''' - result = resp.result - success = result.get('success', 0) - failed = result.get('failed', 0) - errors = result.get('errors', []) - - return (True, f"Nested YAML: success={success}, failed={failed}, errors={errors}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_nested_yaml) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 9: Query status after inline reload -# ============================================================================ -tr = Test.AddTestRun("Query status after inline reload") -tr.DelayStart = 2 -tr.AddJsonRPCClientRequest(ts, Request.get_reload_config_status()) - - -def validate_status_after_inline(resp: Response): - '''Check status includes inline reload info''' - if resp.is_error(): - return (True, f"Status query error (may be expected): {resp.error_as_str()}") - - result = resp.result - tasks = result.get('tasks', []) - - if tasks: - # Check if any task has inline- prefix - for task in tasks: - token = task.get('token', '') - if token.startswith('inline-'): - return (True, f"Found inline reload in status: {token}") - - return (True, f"Status result: {result}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_status_after_inline) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 10: Large config content -# ============================================================================ -tr = Test.AddTestRun("Large config content") -tr.DelayStart = 2 - -# Generate a larger config -large_ip_allow = [] -for i in range(50): - large_ip_allow.append({"apply": "in", "ip_addrs": f"10.{i}.0.0/16", "action": "allow"}) - -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload(configs={"ip_allow": large_ip_allow})) - - -def validate_large_config(resp: Response): - '''Large ip_allow config should also be rejected (FileOnly)''' - result = resp.result - errors = result.get('errors', []) - - if not errors: - return (False, f"Expected rejection for FileOnly config, got: {result}") - - error_str = str(errors) - if '6011' in error_str: - return (True, f"Large config correctly rejected: {errors}") - return (False, f"Expected error 6011, got: {errors}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_large_config) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 11: Reload directive for registered FileOnly config (sni) -# Directive-only request — sni is FileOnly, so the RPC handler rejects with 6011. -# Verifies the _reload structure is handled gracefully through the RPC stack. -# ============================================================================ -tr = Test.AddTestRun("Reload directive for FileOnly config (sni)") -tr.DelayStart = 2 -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload(configs={"sni": {"_reload": {"fqdn": "*.example.com"}}})) - - -def validate_directive_fileonly(resp: Response): - '''sni is FileOnly — directive-only request rejected with 6011''' - result = resp.result - errors = result.get('errors', []) - - if not errors: - return (False, f"Expected rejection for FileOnly config, got: {result}") - - error_str = str(errors) - if '6011' in error_str: - return (True, f"Directive-only correctly rejected for FileOnly config: {errors}") - return (False, f"Expected error 6011, got: {errors}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_directive_fileonly) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 12: Reload directive for unregistered config (virtualhost) -# virtualhost is not registered yet — should get 6010. -# This is the intended use case once the virtualhost handler is registered. -# ============================================================================ -tr = Test.AddTestRun("Reload directive for unregistered config (virtualhost)") -tr.DelayStart = 2 -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload(configs={"virtualhost": {"_reload": {"id": "myhost.example.com"}}})) - - -def validate_directive_unregistered(resp: Response): - '''virtualhost is not registered — rejected with 6010''' - result = resp.result - errors = result.get('errors', []) - - if not errors: - return (False, f"Expected error for unregistered config, got: {result}") - - error_str = str(errors) - if '6010' in error_str or 'not registered' in error_str: - return (True, f"Directive for unregistered config rejected: {errors}") - return (False, f"Expected error 6010, got: {errors}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_directive_unregistered) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 13: Directives mixed with content for FileOnly config (ip_allow) -# _reload directives alongside actual config content — still rejected with 6011. -# ============================================================================ -tr = Test.AddTestRun("Directives mixed with content for FileOnly config") -tr.DelayStart = 2 -tr.AddJsonRPCClientRequest( - ts, - Request.admin_config_reload( - configs={ - "ip_allow": { - "_reload": { - "validate_only": "true" - }, - "rules": [{ - "apply": "in", - "ip_addrs": "0/0", - "action": "allow" - }] - } - })) - - -def validate_directive_mixed(resp: Response): - '''ip_allow is FileOnly — mixed directive+content rejected with 6011''' - result = resp.result - errors = result.get('errors', []) - - if not errors: - return (False, f"Expected rejection, got: {result}") - - error_str = str(errors) - if '6011' in error_str: - return (True, f"Mixed directive+content correctly rejected: {errors}") - return (False, f"Expected error 6011, got: {errors}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_directive_mixed) -tr.StillRunningAfter = ts diff --git a/tests/gold_tests/jsonrpc/config_reload_tracking.test.py b/tests/gold_tests/jsonrpc/config_reload_tracking.test.py deleted file mode 100644 index 272a0e7d599..00000000000 --- a/tests/gold_tests/jsonrpc/config_reload_tracking.test.py +++ /dev/null @@ -1,304 +0,0 @@ -''' -Test config reload tracking functionality. - -Tests the following features: -1. Basic reload with token generation -2. Querying reload status while in progress -3. Reload history tracking -4. Force reload while one is in progress -5. Custom token names -6. Duplicate token prevention -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from jsonrpc import Request, Response -import time - -Test.Summary = 'Test config reload tracking with tokens and status' -Test.ContinueOnFail = True - -ts = Test.MakeATSProcess('ts', dump_runroot=True) - -Test.testName = 'config_reload_tracking' - -# Initial configuration -ts.Disk.records_config.update({ - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'rpc|config', -}) - -# Store tokens for later tests -stored_tokens = [] - -# ============================================================================ -# Test 1: Basic reload - verify token is returned -# ============================================================================ -tr = Test.AddTestRun("Basic reload with auto-generated token") -tr.Processes.Default.StartBefore(ts) -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload()) - - -def validate_basic_reload(resp: Response): - '''Verify reload returns a token''' - if resp.is_error(): - return (False, f"Error: {resp.error_as_str()}") - - result = resp.result - token = result.get('token', '') - created_time = result.get('created_time', '') - messages = result.get('message', []) - - if not token: - return (False, "No token returned") - - if not token.startswith('rldtk-'): - return (False, f"Token should start with 'rldtk-', got: {token}") - - # Store for later tests - stored_tokens.append(token) - - return (True, f"Reload started: token={token}, created={created_time}, messages={messages}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_basic_reload) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 2: Query status of completed reload -# ============================================================================ -tr = Test.AddTestRun("Query status of completed reload") -tr.DelayStart = 2 # Give time for reload to complete -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload_status()) - - -def validate_status_query(resp: Response): - '''Check reload status after completion''' - if resp.is_error(): - # If method doesn't exist, that's OK - we're testing the main reload - return (True, f"Status query: {resp.error_as_str()}") - - result = resp.result - return (True, f"Reload status: {result}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_status_query) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 3: Reload with custom token -# ============================================================================ -tr = Test.AddTestRun("Reload with custom token") -tr.DelayStart = 1 -custom_token = f"my-custom-token-{int(time.time())}" -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload(token=custom_token)) - - -def validate_custom_token(resp: Response): - '''Verify custom token is accepted''' - if resp.is_error(): - # Check if it's a "reload in progress" error - error_str = resp.error_as_str() - if 'in progress' in error_str.lower(): - return (True, f"Reload in progress (expected): {error_str}") - return (False, f"Error: {error_str}") - - result = resp.result - token = result.get('token', '') - - if token != custom_token: - return (False, f"Expected custom token '{custom_token}', got '{token}'") - - stored_tokens.append(token) - return (True, f"Custom token accepted: {token}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_custom_token) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 4: Force reload while previous might still be processing -# ============================================================================ -tr = Test.AddTestRun("Force reload") -tr.DelayStart = 1 -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload(force=True)) - - -def validate_force_reload(resp: Response): - '''Verify force reload works''' - if resp.is_error(): - return (False, f"Force reload failed: {resp.error_as_str()}") - - result = resp.result - token = result.get('token', '') - if token: - stored_tokens.append(token) - - return (True, f"Force reload succeeded: token={token}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_force_reload) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 5: Try duplicate token (should fail) -# ============================================================================ -tr = Test.AddTestRun("Duplicate token rejection") -tr.DelayStart = 2 # Wait for previous reload to complete - - -def make_duplicate_test(): - # Use the first token we stored - if stored_tokens: - return Request.admin_config_reload(token=stored_tokens[0]) - return Request.admin_config_reload(token="rldtk-duplicate-test") - - -tr.AddJsonRPCClientRequest(ts, make_duplicate_test()) - - -def validate_duplicate_rejection(resp: Response): - '''Verify duplicate tokens are rejected''' - if resp.is_error(): - return (True, f"Duplicate rejected (expected): {resp.error_as_str()}") - - result = resp.result - errors = result.get('error', []) - if errors: - return (True, f"Duplicate token rejected: {errors}") - - # If no error, check if token was actually reused - token = result.get('token', '') - return (True, f"Reload result: token={token}, errors={errors}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_duplicate_rejection) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 6: Rapid succession reloads -# ============================================================================ -tr = Test.AddTestRun("Rapid succession reloads - first") -tr.DelayStart = 1 -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload()) - - -def validate_rapid_first(resp: Response): - '''First rapid reload''' - if resp.is_error(): - return (True, f"First rapid: {resp.error_as_str()}") - - result = resp.result - token = result.get('token', '') - return (True, f"First rapid reload: {token}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_rapid_first) -tr.StillRunningAfter = ts - -# Second rapid reload (should see in-progress or succeed) -tr = Test.AddTestRun("Rapid succession reloads - second") -tr.DelayStart = 0 # No delay - immediately after first -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload()) - - -def validate_rapid_second(resp: Response): - '''Second rapid reload - may see in-progress''' - if resp.is_error(): - return (True, f"Second rapid (may be in progress): {resp.error_as_str()}") - - result = resp.result - token = result.get('token', '') - error = result.get('error', []) - - if error: - # In-progress is expected - return (True, f"Second rapid - in progress or error: {error}") - - return (True, f"Second rapid reload: {token}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_rapid_second) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 7: Get reload history -# ============================================================================ -tr = Test.AddTestRun("Get reload history") -tr.DelayStart = 2 -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload_history()) - - -def validate_history(resp: Response): - '''Check reload history''' - if resp.is_error(): - # Method may not exist - return (True, f"History query: {resp.error_as_str()}") - - result = resp.result - history = result.get('history', []) - return (True, f"Reload history: {len(history)} entries") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_history) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 8: Trigger reload and verify new config is loaded -# ============================================================================ -tr = Test.AddTestRun("Reload after config change") -tr.DelayStart = 1 -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload(force=True)) - - -def validate_reload_after_change(resp: Response): - '''Verify reload after config change''' - if resp.is_error(): - return (False, f"Reload after change failed: {resp.error_as_str()}") - - result = resp.result - token = result.get('token', '') - return (True, f"Reload after config change: token={token}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_reload_after_change) -tr.StillRunningAfter = ts - -# ============================================================================ -# Test 10: Final status check -# ============================================================================ -tr = Test.AddTestRun("Final reload status") -tr.DelayStart = 2 -tr.AddJsonRPCClientRequest(ts, Request.admin_config_reload()) - - -def validate_final_status(resp: Response): - '''Final status verification''' - if resp.is_error(): - error_str = resp.error_as_str() - if 'in progress' in error_str.lower(): - return (True, f"Reload still in progress: {error_str}") - return (True, f"Final status: {error_str}") - - result = resp.result - token = result.get('token', '') - created = result.get('created_time', '') - - return (True, f"Final reload: token={token}, created={created}") - - -tr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(validate_final_status) -tr.StillRunningAfter = ts diff --git a/tests/gold_tests/jsonrpc/json/admin_detached_config_reload_req.json b/tests/gold_tests/jsonrpc/json/admin_config_reload_req.json similarity index 100% rename from tests/gold_tests/jsonrpc/json/admin_detached_config_reload_req.json rename to tests/gold_tests/jsonrpc/json/admin_config_reload_req.json diff --git a/tests/gold_tests/jsonrpc/jsonrpc_api_schema.test.py b/tests/gold_tests/jsonrpc/jsonrpc_api_schema.test.py index 0b83fef35a1..e6532e59478 100644 --- a/tests/gold_tests/jsonrpc/jsonrpc_api_schema.test.py +++ b/tests/gold_tests/jsonrpc/jsonrpc_api_schema.test.py @@ -99,13 +99,7 @@ def add_testrun_for_jsonrpc_request( # One of the API's will be checking the storage. Need this to get a response with content. storage_path = os.path.join(Test.RunDirectory, "ts", "storage") -ts.Disk.storage_yaml.AddLine(f''' -cache: - spans: - - name: disk-1 - path: {storage_path} - size: 512M -''') +ts.Disk.storage_config.AddLine(f"{storage_path} 512M") # The following tests will only validate the jsonrpc message, it will not run any validation on the content of the 'result' or 'params' # of the jsonrpc message. This should be added once the schemas are available. @@ -154,11 +148,10 @@ def add_testrun_for_jsonrpc_request( }) # admin_config_reload -# We will wait for this to have a stable schema. I think we may need to adjust the response a bit. -# add_testrun_for_jsonrpc_request( -# "Test admin_config_reload", -# request_file_name='json/admin_config_reload_req.json', -# result_schema_file_name=success_schema_file_name_name) +add_testrun_for_jsonrpc_request( + "Test admin_config_reload", + request_file_name='json/admin_config_reload_req.json', + result_schema_file_name=success_schema_file_name_name) # admin_host_set_status add_testrun_for_jsonrpc_request( diff --git a/tests/gold_tests/logging/new_log_flds.test.py b/tests/gold_tests/logging/new_log_flds.test.py index 38be642b3da..91e91d1fa73 100644 --- a/tests/gold_tests/logging/new_log_flds.test.py +++ b/tests/gold_tests/logging/new_log_flds.test.py @@ -53,13 +53,7 @@ ts.Disk.remap_config.AddLine( 'map https://reallyreallyreallyreallylong.com http://127.0.0.1:{1}/ip'.format(ts.Variables.ssl_port, httpbin.Variables.Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.logging_yaml.AddLines( ''' diff --git a/tests/gold_tests/logging/qlog_quiche.test.py b/tests/gold_tests/logging/qlog_quiche.test.py index 6d5a393407f..cb86cf17310 100644 --- a/tests/gold_tests/logging/qlog_quiche.test.py +++ b/tests/gold_tests/logging/qlog_quiche.test.py @@ -83,13 +83,8 @@ def _configure_traffic_server(self, tr: 'TestRun'): qlog: file_base: log/test_qlog # we expect to have log/test_qlog-.sqlog ''') - self._ts.Disk.ssl_multicert_yaml.AddLines( - f""" -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: {ts.Variables.SSLDir}/server.pem - ssl_key_name: {ts.Variables.SSLDir}/server.key -""".split("\n")) + self._ts.Disk.ssl_multicert_config.AddLine( + f'dest_ip=* ssl_cert_name={ts.Variables.SSLDir}/server.pem ssl_key_name={ts.Variables.SSLDir}/server.key') self._ts.Disk.remap_config.AddLine(f'map / http://127.0.0.1:{self._server.Variables.http_port}') diff --git a/tests/gold_tests/parent_config/parent_config_reload.test.py b/tests/gold_tests/parent_config/parent_config_reload.test.py deleted file mode 100644 index acdf31a5cb4..00000000000 --- a/tests/gold_tests/parent_config/parent_config_reload.test.py +++ /dev/null @@ -1,86 +0,0 @@ -''' -Test parent.config reload via ConfigRegistry. - -Verifies that: -1. parent.config reload works after file touch -2. Record value change (retry_time) triggers parent reload -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os - -Test.Summary = ''' -Test parent.config reload via ConfigRegistry. -''' - -Test.ContinueOnFail = True - -ts = Test.MakeATSProcess("ts") -ts.Disk.records_config.update({ - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'parent_select|config', -}) - -# Initial parent.config with a simple rule -ts.Disk.parent_config.AddLine('dest_domain=example.com parent="origin.example.com:80"') - -config_dir = ts.Variables.CONFIGDIR - -# ================================================================ -# Test 1: Touch parent.config → reload → handler fires -# ================================================================ - -tr = Test.AddTestRun("Touch parent.config") -tr.Processes.Default.StartBefore(ts) -tr.Processes.Default.Command = f"sleep 3 && touch {os.path.join(config_dir, 'parent.config')} && sleep 1" -tr.Processes.Default.ReturnCode = 0 -tr.StillRunningAfter = ts - -tr = Test.AddTestRun("Reload after parent.config touch") -p = tr.Processes.Process("reload-1") -p.Command = 'traffic_ctl config reload; sleep 30' -p.Env = ts.Env -p.ReturnCode = Any(0, -2) -# Wait for the 2nd "finished loading" (1st is startup) -p.Ready = When.FileContains(ts.Disk.diags_log.Name, "parent.config finished loading", 2) -p.Timeout = 20 -tr.Processes.Default.StartBefore(p) -tr.Processes.Default.Command = 'echo "waiting for parent.config reload after file touch"' -tr.TimeOut = 25 -tr.StillRunningAfter = ts - -# ================================================================ -# Test 2: Change retry_time record value → triggers parent reload -# No file touch, no explicit config reload — the -# RecRegisterConfigUpdateCb fires automatically. -# ================================================================ - -tr = Test.AddTestRun("Change parent retry_time record value") -p = tr.Processes.Process("reload-2") -p.Command = ("traffic_ctl config set proxy.config.http.parent_proxy.retry_time 60; " - "sleep 30") -p.Env = ts.Env -p.ReturnCode = Any(0, -2) -# Wait for the 3rd "finished loading" -p.Ready = When.FileContains(ts.Disk.diags_log.Name, "parent.config finished loading", 3) -p.Timeout = 20 -tr.Processes.Default.StartBefore(p) -## TODO: we should have an extension like When.ReloadCompleted(token, success) to validate this instead of parsing -## diags. -tr.Processes.Default.Command = 'echo "waiting for parent.config reload after record change"' -tr.TimeOut = 25 -tr.StillRunningAfter = ts diff --git a/tests/gold_tests/pluginTest/access_control/access_control.test.py b/tests/gold_tests/pluginTest/access_control/access_control.test.py index 65ed2b3035d..8e1f7878170 100644 --- a/tests/gold_tests/pluginTest/access_control/access_control.test.py +++ b/tests/gold_tests/pluginTest/access_control/access_control.test.py @@ -39,13 +39,7 @@ def setupOriginServer(self): def setupTS(self): self.ts = Test.MakeATSProcess("ts", enable_tls=True) self.ts.addDefaultSSLFiles() - self.ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self.ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key") self.ts.Disk.records_config.update( { "proxy.config.diags.debug.enabled": 1, diff --git a/tests/gold_tests/pluginTest/cert_update/cert_update.test.py b/tests/gold_tests/pluginTest/cert_update/cert_update.test.py index bbbaa31aa02..3d2766a4add 100644 --- a/tests/gold_tests/pluginTest/cert_update/cert_update.test.py +++ b/tests/gold_tests/pluginTest/cert_update/cert_update.test.py @@ -57,13 +57,7 @@ 'proxy.config.url_remap.pristine_host_hdr': 1 }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server1.pem - ssl_key_name: server1.pem -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server1.pem ssl_key_name=server1.pem') ts.Disk.remap_config.AddLines( [ diff --git a/tests/gold_tests/pluginTest/certifier/certifier.test.py b/tests/gold_tests/pluginTest/certifier/certifier.test.py index 33e444f35ee..cb397fd1fd6 100644 --- a/tests/gold_tests/pluginTest/certifier/certifier.test.py +++ b/tests/gold_tests/pluginTest/certifier/certifier.test.py @@ -53,13 +53,7 @@ def setupTS(self): "proxy.config.ssl.server.cert.path": f'{self.ts.Variables.SSLDir}', "proxy.config.ssl.server.private_key.path": f'{self.ts.Variables.SSLDir}', }) - self.ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self.ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') self.ts.Disk.remap_config.AddLine(f"map / http://127.0.0.1:{self.server.Variables.http_port}/",) self.ts.Disk.plugin_config.AddLine( f'certifier.so -s {os.path.join(self.certPathDest, "store")} -m 1000 -c {os.path.join(self.certPathDest, "ca.cert")} -k {os.path.join(self.certPathDest, "ca.key")} -r {os.path.join(self.certPathDest, "ca-serial.txt")}' @@ -134,13 +128,7 @@ def setupTS(self): "proxy.config.ssl.server.cert.path": f'{self.ts.Variables.SSLDir}', "proxy.config.ssl.server.private_key.path": f'{self.ts.Variables.SSLDir}', }) - self.ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self.ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') self.ts.Disk.remap_config.AddLine(f"map / http://127.0.0.1:{self.server.Variables.http_port}/",) self.ts.Disk.plugin_config.AddLine( f'certifier.so -s {os.path.join(self.certPathDest, "store")} -m 1000 -c {os.path.join(self.certPathDest, "ca.cert")} -k {os.path.join(self.certPathDest, "ca.key")} -r {os.path.join(self.certPathDest, "ca-serial.txt")}' diff --git a/tests/gold_tests/pluginTest/client_context_dump/client_context_dump.test.py b/tests/gold_tests/pluginTest/client_context_dump/client_context_dump.test.py index 7645684e339..195caf5679b 100644 --- a/tests/gold_tests/pluginTest/client_context_dump/client_context_dump.test.py +++ b/tests/gold_tests/pluginTest/client_context_dump/client_context_dump.test.py @@ -40,13 +40,7 @@ 'proxy.config.ssl.client.private_key.path': '{}'.format(ts.Variables.SSLDir), }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: one.com.pem - ssl_key_name: one.com.pem -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=one.com.pem ssl_key_name=one.com.pem') ts.Disk.sni_yaml.AddLines( [ diff --git a/tests/gold_tests/pluginTest/healthchecks/healthchecks.test.py b/tests/gold_tests/pluginTest/healthchecks/healthchecks.test.py index 28479e079c2..bbf1a96f4cf 100644 --- a/tests/gold_tests/pluginTest/healthchecks/healthchecks.test.py +++ b/tests/gold_tests/pluginTest/healthchecks/healthchecks.test.py @@ -72,13 +72,7 @@ def _configure_global_ts(self) -> None: "proxy.config.ssl.server.private_key.path": f'{ts.Variables.SSLDir}', "proxy.config.ssl.client.verify.server.policy": 'PERMISSIVE', }) - ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Other configuration. ts.Disk.records_config.update({ diff --git a/tests/gold_tests/pluginTest/ja3_fingerprint/ja3_fingerprint.test.py b/tests/gold_tests/pluginTest/ja3_fingerprint/ja3_fingerprint.test.py index 354c30c5aea..89eb57ee882 100644 --- a/tests/gold_tests/pluginTest/ja3_fingerprint/ja3_fingerprint.test.py +++ b/tests/gold_tests/pluginTest/ja3_fingerprint/ja3_fingerprint.test.py @@ -98,13 +98,7 @@ def _configure_trafficserver(self) -> None: self._ts = Test.MakeATSProcess(name, enable_cache=False, enable_tls=True) JA3FingerprintTest._ts_counter += 1 self._ts.addDefaultSSLFiles() - self._ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self._ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') server_port = self._server.Variables.https_port self._ts.Disk.remap_config.AddLine(f'map https://https.server.com https://https.backend.com:{server_port}') diff --git a/tests/gold_tests/pluginTest/ja4_fingerprint/ja4_fingerprint.test.py b/tests/gold_tests/pluginTest/ja4_fingerprint/ja4_fingerprint.test.py index f9de1f2d80c..2545c9e40d4 100644 --- a/tests/gold_tests/pluginTest/ja4_fingerprint/ja4_fingerprint.test.py +++ b/tests/gold_tests/pluginTest/ja4_fingerprint/ja4_fingerprint.test.py @@ -127,13 +127,7 @@ def _configure_traffic_server(self, server_one: 'Process'): ts.Disk.remap_config.AddLine(f'map / http://localhost:{server_one.Variables.http_port}') - ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + ts.Disk.ssl_multicert_config.AddLine(f'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') plugin_args = 'ja4_fingerprint.so' if self.use_preserve: diff --git a/tests/gold_tests/pluginTest/jax_fingerprint/jax_fingerprint.test.py b/tests/gold_tests/pluginTest/jax_fingerprint/jax_fingerprint.test.py index 5620feac29f..5cb83896183 100644 --- a/tests/gold_tests/pluginTest/jax_fingerprint/jax_fingerprint.test.py +++ b/tests/gold_tests/pluginTest/jax_fingerprint/jax_fingerprint.test.py @@ -200,13 +200,7 @@ def _configure_trafficserver(self) -> None: JaxFingerprintTest._ts_counter += 1 self._ts.addDefaultSSLFiles() - self._ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self._ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') if self._needs_tls: server_port = self._server.Variables.https_port @@ -431,13 +425,7 @@ def _configure_trafficserver(self) -> None: AllMethodsTest._ts_counter += 1 self._ts.addDefaultSSLFiles() - self._ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self._ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') server_port = self._server.Variables.https_port diff --git a/tests/gold_tests/pluginTest/multiplexer/multiplexer.test.py b/tests/gold_tests/pluginTest/multiplexer/multiplexer.test.py index 3468e6bc69c..c4ffe1d4797 100644 --- a/tests/gold_tests/pluginTest/multiplexer/multiplexer.test.py +++ b/tests/gold_tests/pluginTest/multiplexer/multiplexer.test.py @@ -130,13 +130,7 @@ def setupTS(self, skip_post): 'proxy.config.dns.nameservers': f'127.0.0.1:{self.dns.Variables.Port}', 'proxy.config.dns.resolv_conf': 'NULL', }) - self.ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self.ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') skip_remap_param = '' if skip_post: skip_remap_param = ' @pparam=proxy.config.multiplexer.skip_post_put=1' diff --git a/tests/gold_tests/pluginTest/plugin_yaml/plugin_yaml.test.py b/tests/gold_tests/pluginTest/plugin_yaml/plugin_yaml.test.py deleted file mode 100644 index 34623850ff3..00000000000 --- a/tests/gold_tests/pluginTest/plugin_yaml/plugin_yaml.test.py +++ /dev/null @@ -1,90 +0,0 @@ -''' -Test plugin.yaml loading with inline config and enabled/disabled plugins. -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = ''' -Test that plugin.yaml is loaded instead of plugin.config, that inline config -works with header_rewrite.so, and that enabled: false skips a plugin. -''' - -Test.ContinueOnFail = True - -server = Test.MakeOriginServer("server") - -request_header = {"headers": "GET /test HTTP/1.1\r\nHost: example.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""} -response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""} -server.addResponse("sessionlog.json", request_header, response_header) - -ts = Test.MakeATSProcess("ts", enable_cache=False) - -ts.Disk.records_config.update( - { - 'proxy.config.url_remap.remap_required': 0, - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'header_rewrite|plugin', - }) - -ts.Disk.remap_config.AddLine("map http://example.com http://127.0.0.1:{0}".format(server.Variables.Port)) - -# Write plugin.yaml into the config directory. ATS will prefer this over -# plugin.config. Uses inline config (scalar literal) with header_rewrite.so -# to set a custom response header. xdebug.so is listed but disabled. -ts.Disk.MakeConfigFile("plugin.yaml").update( - { - "plugins": - [ - { - "path": "header_rewrite.so", - "config": "cond %{SEND_RESPONSE_HDR_HOOK}\n set-header X-Plugin-YAML \"loaded-from-inline\"\n", - }, - { - "path": "xdebug.so", - "enabled": False, - "params": ["--enable=x-cache"], - }, - ] - }) - -# Test 1: Verify header_rewrite loaded via plugin.yaml sets the response header. -tr = Test.AddTestRun("Verify inline config sets response header") -tr.Processes.Default.StartBefore(server, ready=When.PortOpen(server.Variables.Port)) -tr.Processes.Default.StartBefore(ts) -tr.MakeCurlCommand('-s -D- -o /dev/null -H "Host: example.com" http://127.0.0.1:{0}/test'.format(ts.Variables.port), ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stdout = Testers.ContainsExpression( - "X-Plugin-YAML: loaded-from-inline", "Response should contain X-Plugin-YAML header set by inline config") -tr.StillRunningAfter = server -tr.StillRunningAfter = ts - -# Test 2: Verify xdebug is NOT loaded (enabled: false). Send the X-Debug -# header and confirm the X-Cache header is absent from the response. -tr = Test.AddTestRun("Verify disabled plugin is not loaded") -tr.MakeCurlCommand( - '-s -D- -o /dev/null -H "Host: example.com" -H "X-Debug: x-cache" http://127.0.0.1:{0}/test'.format(ts.Variables.port), ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stdout = Testers.ExcludesExpression( - "X-Cache:", "Response should NOT contain X-Cache header since xdebug is disabled") -tr.StillRunningAfter = server -tr.StillRunningAfter = ts - -# Test 3: Verify the diags.log shows plugin.yaml was used. -tr = Test.AddTestRun("Verify plugin.yaml loading logged") -tr.Processes.Default.Command = "echo check diags.log" -tr.Processes.Default.ReturnCode = 0 -ts.Disk.diags_log.Content += Testers.ContainsExpression("plugin.yaml loading", "diags.log should indicate plugin.yaml was loaded") -ts.Disk.diags_log.Content += Testers.ContainsExpression("skipped", "diags.log should indicate a plugin was skipped") diff --git a/tests/gold_tests/pluginTest/server_push_preload/server_push_preload.test.py b/tests/gold_tests/pluginTest/server_push_preload/server_push_preload.test.py index 9ca134705db..af8dc4d940f 100644 --- a/tests/gold_tests/pluginTest/server_push_preload/server_push_preload.test.py +++ b/tests/gold_tests/pluginTest/server_push_preload/server_push_preload.test.py @@ -71,13 +71,7 @@ ts.addDefaultSSLFiles() -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}/ @plugin=server_push_preload.so'.format(microserver.Variables.Port)) diff --git a/tests/gold_tests/pluginTest/sslheaders/sslheaders.test.py b/tests/gold_tests/pluginTest/sslheaders/sslheaders.test.py index b5cdf9b2cc5..85c12b8c5d1 100644 --- a/tests/gold_tests/pluginTest/sslheaders/sslheaders.test.py +++ b/tests/gold_tests/pluginTest/sslheaders/sslheaders.test.py @@ -55,13 +55,7 @@ ts.Variables.port, ts.Variables.ssl_port)), }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine('map http://bar.com http://127.0.0.1:{0}'.format(server.Variables.Port)) ts.Disk.remap_config.AddLine('map https://bar.com http://127.0.0.1:{0}'.format(server.Variables.Port)) diff --git a/tests/gold_tests/pluginTest/stale_response/stale_response_no_default.replay.yaml b/tests/gold_tests/pluginTest/stale_response/stale_response_no_default.replay.yaml index d0961053b55..35d72c5973e 100644 --- a/tests/gold_tests/pluginTest/stale_response/stale_response_no_default.replay.yaml +++ b/tests/gold_tests/pluginTest/stale_response/stale_response_no_default.replay.yaml @@ -135,7 +135,7 @@ sessions: - [ Content-Type, image/jpeg ] - [ Content-Length, 100 ] - [ Connection, keep-alive ] - - [ Cache-Control, "max-age=1, stale-if-error=30" ] + - [ Cache-Control, max-age=1 stale-if-error=30 ] - [ X-Response, fourth-response ] # We better have gone back to the origin and gotten second-response. diff --git a/tests/gold_tests/pluginTest/stale_response/stale_response_with_force_sie.replay.yaml b/tests/gold_tests/pluginTest/stale_response/stale_response_with_force_sie.replay.yaml index 5837d68384f..10a8cd68f2b 100644 --- a/tests/gold_tests/pluginTest/stale_response/stale_response_with_force_sie.replay.yaml +++ b/tests/gold_tests/pluginTest/stale_response/stale_response_with_force_sie.replay.yaml @@ -43,7 +43,7 @@ sessions: - [ Content-Length, 100 ] - [ Connection, keep-alive ] # Configure a small stale-if-error. - - [ Cache-Control, "max-age=1, stale-if-error=1" ] + - [ Cache-Control, max-age=1 stale-if-error=1 ] - [ X-Response, first-response ] proxy-response: diff --git a/tests/gold_tests/pluginTest/stale_response/stale_response_with_force_swr.replay.yaml b/tests/gold_tests/pluginTest/stale_response/stale_response_with_force_swr.replay.yaml index 7a54ee94b36..a980bb5010e 100644 --- a/tests/gold_tests/pluginTest/stale_response/stale_response_with_force_swr.replay.yaml +++ b/tests/gold_tests/pluginTest/stale_response/stale_response_with_force_swr.replay.yaml @@ -42,14 +42,14 @@ sessions: - [ Connection, keep-alive ] # The low stale-while-revalidate should be overridden by # --force-stale-while-revalidate. - - [ Cache-Control, "max-age=1, stale-while-revalidate=1" ] + - [ Cache-Control, max-age=1 stale-while-revalidate=1 ] - [ X-Response, first-response ] proxy-response: status: 200 headers: fields: - - [ Cache-Control, { value: "max-age=1, stale-while-revalidate=1", as: equal } ] + - [ Cache-Control, { value: "max-age=1 stale-while-revalidate=1", as: equal } ] - [ X-Response, { value: first-response, as: equal } ] - client-request: diff --git a/tests/gold_tests/pluginTest/stek_share/stek_share.test.py b/tests/gold_tests/pluginTest/stek_share/stek_share.test.py index 8d2fb94d745..a3359aed050 100644 --- a/tests/gold_tests/pluginTest/stek_share/stek_share.test.py +++ b/tests/gold_tests/pluginTest/stek_share/stek_share.test.py @@ -164,18 +164,16 @@ 'proxy.config.exec_thread.limit': 4, 'proxy.config.ssl.server.cert.path': '{0}'.format(Test.RunDirectory), 'proxy.config.ssl.server.private_key.path': '{0}'.format(Test.RunDirectory), + 'proxy.config.ssl.session_cache.mode': 2, + 'proxy.config.ssl.session_cache.size': 1024, + 'proxy.config.ssl.session_cache.timeout': 7200, + 'proxy.config.ssl.session_cache.num_buckets': 16, 'proxy.config.ssl.server.session_ticket.enable': 1, 'proxy.config.ssl.server.cipher_suite': 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA' }) ts1.Disk.plugin_config.AddLine('stek_share.so {0}'.format(stek_share_conf_path_1)) -ts1.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: self_signed.crt - ssl_key_name: self_signed.key -""".split("\n")) +ts1.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=self_signed.crt ssl_key_name=self_signed.key') ts1.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) ts2.Disk.records_config.update( @@ -186,18 +184,16 @@ 'proxy.config.exec_thread.limit': 4, 'proxy.config.ssl.server.cert.path': '{0}'.format(Test.RunDirectory), 'proxy.config.ssl.server.private_key.path': '{0}'.format(Test.RunDirectory), + 'proxy.config.ssl.session_cache.mode': 2, + 'proxy.config.ssl.session_cache.size': 1024, + 'proxy.config.ssl.session_cache.timeout': 7200, + 'proxy.config.ssl.session_cache.num_buckets': 16, 'proxy.config.ssl.server.session_ticket.enable': 1, 'proxy.config.ssl.server.cipher_suite': 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA' }) ts2.Disk.plugin_config.AddLine('stek_share.so {0}'.format(stek_share_conf_path_2)) -ts2.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: self_signed.crt - ssl_key_name: self_signed.key -""".split("\n")) +ts2.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=self_signed.crt ssl_key_name=self_signed.key') ts2.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) ts3.Disk.records_config.update( @@ -208,18 +204,16 @@ 'proxy.config.exec_thread.limit': 4, 'proxy.config.ssl.server.cert.path': '{0}'.format(Test.RunDirectory), 'proxy.config.ssl.server.private_key.path': '{0}'.format(Test.RunDirectory), + 'proxy.config.ssl.session_cache.mode': 2, + 'proxy.config.ssl.session_cache.size': 1024, + 'proxy.config.ssl.session_cache.timeout': 7200, + 'proxy.config.ssl.session_cache.num_buckets': 16, 'proxy.config.ssl.server.session_ticket.enable': 1, 'proxy.config.ssl.server.cipher_suite': 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA' }) ts3.Disk.plugin_config.AddLine('stek_share.so {0}'.format(stek_share_conf_path_3)) -ts3.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: self_signed.crt - ssl_key_name: self_signed.key -""".split("\n")) +ts3.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=self_signed.crt ssl_key_name=self_signed.key') ts3.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) ts4.Disk.records_config.update( @@ -230,18 +224,16 @@ 'proxy.config.exec_thread.limit': 4, 'proxy.config.ssl.server.cert.path': '{0}'.format(Test.RunDirectory), 'proxy.config.ssl.server.private_key.path': '{0}'.format(Test.RunDirectory), + 'proxy.config.ssl.session_cache.mode': 2, + 'proxy.config.ssl.session_cache.size': 1024, + 'proxy.config.ssl.session_cache.timeout': 7200, + 'proxy.config.ssl.session_cache.num_buckets': 16, 'proxy.config.ssl.server.session_ticket.enable': 1, 'proxy.config.ssl.server.cipher_suite': 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA' }) ts4.Disk.plugin_config.AddLine('stek_share.so {0}'.format(stek_share_conf_path_4)) -ts4.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: self_signed.crt - ssl_key_name: self_signed.key -""".split("\n")) +ts4.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=self_signed.crt ssl_key_name=self_signed.key') ts4.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) ts5.Disk.records_config.update( @@ -252,18 +244,16 @@ 'proxy.config.exec_thread.limit': 4, 'proxy.config.ssl.server.cert.path': '{0}'.format(Test.RunDirectory), 'proxy.config.ssl.server.private_key.path': '{0}'.format(Test.RunDirectory), + 'proxy.config.ssl.session_cache.mode': 2, + 'proxy.config.ssl.session_cache.size': 1024, + 'proxy.config.ssl.session_cache.timeout': 7200, + 'proxy.config.ssl.session_cache.num_buckets': 16, 'proxy.config.ssl.server.session_ticket.enable': 1, 'proxy.config.ssl.server.cipher_suite': 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA' }) ts5.Disk.plugin_config.AddLine('stek_share.so {0}'.format(stek_share_conf_path_5)) -ts5.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: self_signed.crt - ssl_key_name: self_signed.key -""".split("\n")) +ts5.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=self_signed.crt ssl_key_name=self_signed.key') ts5.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) diff --git a/tests/gold_tests/pluginTest/test_hooks/test_hooks.test.py b/tests/gold_tests/pluginTest/test_hooks/test_hooks.test.py index b0cb46daf7d..20ca7fbb1d3 100644 --- a/tests/gold_tests/pluginTest/test_hooks/test_hooks.test.py +++ b/tests/gold_tests/pluginTest/test_hooks/test_hooks.test.py @@ -49,13 +49,7 @@ 'proxy.config.diags.debug.tags': 'http|test_hooks', }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') Test.PrepareTestPlugin(os.path.join(Test.Variables.AtsTestPluginsDir, 'test_hooks.so'), ts) diff --git a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py index cedaafb8c8c..7a0eafb8429 100644 --- a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py +++ b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py @@ -60,13 +60,7 @@ 'proxy.config.http.connect_ports': f"{server.Variables.http_port}", }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLines( [ diff --git a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_http3.test.py b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_http3.test.py index 758b469979e..5c8bfa6255d 100644 --- a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_http3.test.py +++ b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_http3.test.py @@ -60,13 +60,7 @@ 'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE', }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine(f'map https://www.client_only_tls.com/ http://127.0.0.1:{server.Variables.http_port}') ts.Disk.remap_config.AddLine(f'map https://www.tls.com/ https://127.0.0.1:{server.Variables.https_port}') diff --git a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_response_body.test.py b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_response_body.test.py index 1b296b5e6a0..f816b63948e 100644 --- a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_response_body.test.py +++ b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_response_body.test.py @@ -52,13 +52,7 @@ 'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE', }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLines([ f'map / http://127.0.0.1:{server.Variables.http_port}', diff --git a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_sni_filter.test.py b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_sni_filter.test.py index b3ff3ca00e0..710d2c9742d 100644 --- a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_sni_filter.test.py +++ b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_sni_filter.test.py @@ -53,13 +53,7 @@ 'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE', }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine(f'map / https://127.0.0.1:{server.Variables.https_port}') diff --git a/tests/gold_tests/pluginTest/transform/transaction_data_sink.test.py b/tests/gold_tests/pluginTest/transform/transaction_data_sink.test.py index a632521da61..46d71b84d90 100644 --- a/tests/gold_tests/pluginTest/transform/transaction_data_sink.test.py +++ b/tests/gold_tests/pluginTest/transform/transaction_data_sink.test.py @@ -51,13 +51,7 @@ def _setupTS(self): }) self.ts.addDefaultSSLFiles() self.ts.Disk.remap_config.AddLine(f'map / http://localhost:{self.server.Variables.http_port}/') - self.ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self.ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') self.ts.Disk.plugin_config.AddLine('txn_data_sink.so') # All of the bodies that contained "not_dumped" were not configured to diff --git a/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo.test.py b/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo.test.py index 2685ee3ef78..3d1dad1a2f3 100644 --- a/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo.test.py +++ b/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo.test.py @@ -49,13 +49,7 @@ ts.Disk.remap_config.AddLines(['map /httpbin/ http://127.0.0.1:{0}/'.format(httpbin.Variables.Port)]) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') Test.PrepareTestPlugin( os.path.join(Test.Variables.AtsBuildGoldTestsDir, 'pluginTest', 'tsapi', '.libs', 'test_TSHttpSsnInfo.so'), ts) diff --git a/tests/gold_tests/pluginTest/tsapi/test_TSVConnPPInfo.test.py b/tests/gold_tests/pluginTest/tsapi/test_TSVConnPPInfo.test.py index 3adea661f33..0c45109d971 100644 --- a/tests/gold_tests/pluginTest/tsapi/test_TSVConnPPInfo.test.py +++ b/tests/gold_tests/pluginTest/tsapi/test_TSVConnPPInfo.test.py @@ -49,13 +49,7 @@ ts.Disk.remap_config.AddLines(['map /httpbin/ http://127.0.0.1:{0}/'.format(httpbin.Variables.Port)]) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') Test.PrepareTestPlugin( os.path.join(Test.Variables.AtsBuildGoldTestsDir, 'pluginTest', 'tsapi', '.libs', 'test_TSVConnPPInfo.so'), ts) diff --git a/tests/gold_tests/pluginTest/tsapi/tsapi.test.py b/tests/gold_tests/pluginTest/tsapi/tsapi.test.py index 2b4e2e278f5..84b5ba4be6e 100644 --- a/tests/gold_tests/pluginTest/tsapi/tsapi.test.py +++ b/tests/gold_tests/pluginTest/tsapi/tsapi.test.py @@ -55,13 +55,7 @@ 'proxy.config.diags.debug.tags': f'http|{plugin_name}', }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') rp = os.path.join(Test.Variables.AtsBuildGoldTestsDir, 'pluginTest', 'tsapi', '.libs', f'{plugin_name}.so') ts.Setup.Copy(rp, ts.Env['PROXY_CONFIG_PLUGIN_PLUGIN_DIR']) diff --git a/tests/gold_tests/pluginTest/txn_box/basic/txn_box_tls-cert.test.py b/tests/gold_tests/pluginTest/txn_box/basic/txn_box_tls-cert.test.py index 496e5f08954..00181e4da68 100644 --- a/tests/gold_tests/pluginTest/txn_box/basic/txn_box_tls-cert.test.py +++ b/tests/gold_tests/pluginTest/txn_box/basic/txn_box_tls-cert.test.py @@ -58,10 +58,4 @@ 'proxy.config.ssl.client.cert.path': ts.Variables.SSLDir, 'proxy.config.ssl.client.cert.filename': "bravo-signed.cert" }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') diff --git a/tests/gold_tests/pluginTest/txn_box/basic/txn_box_tls.test.py b/tests/gold_tests/pluginTest/txn_box/basic/txn_box_tls.test.py index e23165d4ce5..080753460e8 100644 --- a/tests/gold_tests/pluginTest/txn_box/basic/txn_box_tls.test.py +++ b/tests/gold_tests/pluginTest/txn_box/basic/txn_box_tls.test.py @@ -53,10 +53,4 @@ 'proxy.config.http.server_ports': '{0} {1}:ssl'.format(ts.Variables.port, ts.Variables.ssl_port), 'proxy.config.ssl.client.verify.server.policy': 'DISABLED' }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') diff --git a/tests/gold_tests/pluginTest/txn_box/ct_header/txn_box_ct_header.test.py b/tests/gold_tests/pluginTest/txn_box/ct_header/txn_box_ct_header.test.py index 07cae3ca260..c5fc5e9762e 100644 --- a/tests/gold_tests/pluginTest/txn_box/ct_header/txn_box_ct_header.test.py +++ b/tests/gold_tests/pluginTest/txn_box/ct_header/txn_box_ct_header.test.py @@ -56,10 +56,4 @@ 'proxy.config.http.server_ports': '{0} {1}:ssl'.format(ts.Variables.port, ts.Variables.ssl_port), 'proxy.config.ssl.client.verify.server.policy': 'DISABLED' }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') diff --git a/tests/gold_tests/pluginTest/txn_box/prod/txn_box_mTLS.test.py b/tests/gold_tests/pluginTest/txn_box/prod/txn_box_mTLS.test.py index bfb76b22c5a..c76d0fab7e1 100644 --- a/tests/gold_tests/pluginTest/txn_box/prod/txn_box_mTLS.test.py +++ b/tests/gold_tests/pluginTest/txn_box/prod/txn_box_mTLS.test.py @@ -67,10 +67,4 @@ 'proxy.config.ssl.client.certification_level': 2, 'proxy.config.http.server_ports': '{0} {1}:ssl'.format(ts.Variables.port, ts.Variables.ssl_port) }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') diff --git a/tests/gold_tests/pluginTest/txn_box/ramp/multi_ramp_common.py b/tests/gold_tests/pluginTest/txn_box/ramp/multi_ramp_common.py index 9feddac59de..b7322dc8e88 100644 --- a/tests/gold_tests/pluginTest/txn_box/ramp/multi_ramp_common.py +++ b/tests/gold_tests/pluginTest/txn_box/ramp/multi_ramp_common.py @@ -97,13 +97,7 @@ def ramp_test_fixup(tr): 'proxy.config.ssl.server.cipher_suite': 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:RC4-SHA:RC4-MD5:AES128-SHA:AES256-SHA:DES-CBC3-SHA!SRP:!DSS:!PSK:!aNULL:!eNULL:!SSLv2' }) - ts.Disk.ssl_multicert_yaml.AddLines( - """ - ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key - """.split("\n")) + ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') pv_client = tr.Variables.CLIENT diff --git a/tests/gold_tests/pluginTest/txn_box/remap/txn_box_remap-base.test.py b/tests/gold_tests/pluginTest/txn_box/remap/txn_box_remap-base.test.py index 6e16c73cdec..04b08f7dcb4 100644 --- a/tests/gold_tests/pluginTest/txn_box/remap/txn_box_remap-base.test.py +++ b/tests/gold_tests/pluginTest/txn_box/remap/txn_box_remap-base.test.py @@ -60,10 +60,4 @@ 'proxy.config.ssl.client.verify.server.policy': "disabled" }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') diff --git a/tests/gold_tests/pluginTest/url_sig/url_sig.test.py b/tests/gold_tests/pluginTest/url_sig/url_sig.test.py index 16c2927ba07..373255cd633 100644 --- a/tests/gold_tests/pluginTest/url_sig/url_sig.test.py +++ b/tests/gold_tests/pluginTest/url_sig/url_sig.test.py @@ -96,13 +96,7 @@ 'proxy.config.ssl.server.private_key.path': ts.Variables.SSLDir, }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Use unchanged incoming URL. # diff --git a/tests/gold_tests/post/post-continue.test.py b/tests/gold_tests/post/post-continue.test.py index b170f0e374e..5dfd75044c1 100644 --- a/tests/gold_tests/post/post-continue.test.py +++ b/tests/gold_tests/post/post-continue.test.py @@ -47,13 +47,7 @@ ts2.addDefaultSSLFiles() ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.http_port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.records_config.update( { 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), @@ -62,13 +56,7 @@ 'proxy.config.diags.debug.tags': 'http', }) ts2.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.http_port)) -ts2.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts2.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts2.Disk.records_config.update( { 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), diff --git a/tests/gold_tests/post/post-early-return.test.py b/tests/gold_tests/post/post-early-return.test.py index 054fd08fb5a..a8e85e2c06c 100644 --- a/tests/gold_tests/post/post-early-return.test.py +++ b/tests/gold_tests/post/post-early-return.test.py @@ -51,13 +51,7 @@ 'map /five http://127.0.0.1:{0}'.format(Test.Variables.upstream_port5), 'map /six http://127.0.0.1:{0}'.format(Test.Variables.upstream_port6), ]) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.records_config.update( { 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), diff --git a/tests/gold_tests/post_slow_server/post_slow_server.test.py b/tests/gold_tests/post_slow_server/post_slow_server.test.py index 1f4612b43dd..4447eb938bc 100644 --- a/tests/gold_tests/post_slow_server/post_slow_server.test.py +++ b/tests/gold_tests/post_slow_server/post_slow_server.test.py @@ -41,13 +41,7 @@ 'proxy.config.http2.no_activity_timeout_in': 150, }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') Test.GetTcpPort("server_port") diff --git a/tests/gold_tests/proxy_protocol/proxy_protocol.test.py b/tests/gold_tests/proxy_protocol/proxy_protocol.test.py index e2abc9f7138..fc675b9f424 100644 --- a/tests/gold_tests/proxy_protocol/proxy_protocol.test.py +++ b/tests/gold_tests/proxy_protocol/proxy_protocol.test.py @@ -44,13 +44,7 @@ def setupTS(self, name, enable_cp): enable_proxy_protocol_cp_src=enable_cp) self.ts.addDefaultSSLFiles() - self.ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self.ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key") self.ts.Disk.remap_config.AddLine(f"map / http://127.0.0.1:{self.server.Variables.http_port}/") @@ -153,13 +147,7 @@ def setupTS(self, tr: 'TestRun') -> None: self._ts = tr.MakeATSProcess(process_name, enable_tls=True, enable_cache=False) self._ts.addDefaultSSLFiles() - self._ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self._ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key") scheme = 'https' if self._is_tls_to_origin else 'http' server_port = self._server.Variables.https_port if self._is_tls_to_origin else self._server.Variables.http_port self._ts.Disk.remap_config.AddLine(f"map / {scheme}://backend.pp.origin.com:{server_port}/") diff --git a/tests/gold_tests/remap/remap_https.test.py b/tests/gold_tests/remap/remap_https.test.py index 0c53fa2f4c9..19f74ae8309 100644 --- a/tests/gold_tests/remap/remap_https.test.py +++ b/tests/gold_tests/remap/remap_https.test.py @@ -57,13 +57,7 @@ ts.Disk.remap_config.AddLine( 'map https://www.anotherexample.com https://127.0.0.1:{0}'.format(server2.Variables.SSL_Port, ts.Variables.ssl_port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # call localhost straight tr = Test.AddTestRun() diff --git a/tests/gold_tests/remap/remap_reload.test.py b/tests/gold_tests/remap/remap_reload.test.py index a0337fc9444..21a4d2328e0 100644 --- a/tests/gold_tests/remap/remap_reload.test.py +++ b/tests/gold_tests/remap/remap_reload.test.py @@ -58,9 +58,7 @@ def update_remap_config(path: str, lines: list) -> None: tm.Disk.records_config.update( { 'proxy.config.dns.nameservers': f"127.0.0.1:{nameserver.Variables.Port}", - 'proxy.config.dns.resolv_conf': 'NULL', - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'remap|config|file|rpc', + 'proxy.config.dns.resolv_conf': 'NULL' }) tr = Test.AddTestRun("verify load") diff --git a/tests/gold_tests/remap/remap_ws.test.py b/tests/gold_tests/remap/remap_ws.test.py index 3e122446415..5d677a0f9cc 100644 --- a/tests/gold_tests/remap/remap_ws.test.py +++ b/tests/gold_tests/remap/remap_ws.test.py @@ -51,13 +51,7 @@ 'map wss://www.example.com:{1} ws://127.0.0.1:{0}'.format(server.Variables.Port, ts.Variables.ssl_port), ]) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') if not Condition.CurlUsingUnixDomainSocket(): # wss mapping diff --git a/tests/gold_tests/remap_yaml/all_acl_combinations_yaml.py b/tests/gold_tests/remap_yaml/all_acl_combinations_yaml.py deleted file mode 100644 index 8fe1e1d8296..00000000000 --- a/tests/gold_tests/remap_yaml/all_acl_combinations_yaml.py +++ /dev/null @@ -1,162 +0,0 @@ -''' -Verify remap.yaml acl behavior. -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -ALLOW_GET_AND_POST = f''' -ip_allow: - - apply: in - ip_addrs: [0/0, ::/0] - action: set_allow - methods: [GET, POST] -''' - -ALLOW_GET = f''' -ip_allow: - - apply: in - ip_addrs: [0/0, ::/0] - action: set_allow - methods: [GET] -''' - -DENY_GET = f''' -ip_allow: - - apply: in - ip_addrs: [0/0, ::/0] - action: set_deny - methods: [GET] -''' - -DENY_GET_AND_POST = f''' -ip_allow: - - apply: in - ip_addrs: [0/0, ::/0] - action: set_deny - methods: [GET, POST] -''' - -# yapf: disable -keys = ["index", "policy", "inline", "named_acl", "ip_allow", "GET response", "POST response"] -all_acl_combinations_yaml = [ - [ 0, "legacy", [], [], ALLOW_GET_AND_POST, 200, 200, ], - [ 1, "legacy", [], [], ALLOW_GET, 200, 403, ], - [ 2, "legacy", [], [], DENY_GET, 403, 200, ], - [ 3, "legacy", [], [], DENY_GET_AND_POST, 403, 403, ], - [ 4, "legacy", [], ['action: allow', 'method: GET'], ALLOW_GET_AND_POST, 200, 403, ], - [ 5, "legacy", [], ['action: allow', 'method: GET'], ALLOW_GET, 200, 403, ], - [ 6, "legacy", [], ['action: allow', 'method: GET'], DENY_GET, 403, 403, ], - [ 7, "legacy", [], ['action: allow', 'method: GET'], DENY_GET_AND_POST, 403, 403, ], - [ 8, "legacy", [], ['action: deny', 'method: GET'], ALLOW_GET_AND_POST, 403, 200, ], - [ 9, "legacy", [], ['action: deny', 'method: GET'], ALLOW_GET, 403, 403, ], - [ 10, "legacy", [], ['action: deny', 'method: GET'], DENY_GET, 403, 200, ], - [ 11, "legacy", [], ['action: deny', 'method: GET'], DENY_GET_AND_POST, 403, 403, ], - [ 12, "legacy", ['action: allow', 'method: GET'], [], ALLOW_GET_AND_POST, 200, 403, ], - [ 13, "legacy", ['action: allow', 'method: GET'], [], ALLOW_GET, 200, 403, ], - [ 14, "legacy", ['action: allow', 'method: GET'], [], DENY_GET, 403, 403, ], - [ 15, "legacy", ['action: allow', 'method: GET'], [], DENY_GET_AND_POST, 403, 403, ], - [ 16, "legacy", ['action: allow', 'method: GET'], ['action: allow', 'method: GET'], ALLOW_GET_AND_POST, 200, 403, ], - [ 17, "legacy", ['action: allow', 'method: GET'], ['action: allow', 'method: GET'], ALLOW_GET, 200, 403, ], - [ 18, "legacy", ['action: allow', 'method: GET'], ['action: allow', 'method: GET'], DENY_GET, 403, 403, ], - [ 19, "legacy", ['action: allow', 'method: GET'], ['action: allow', 'method: GET'], DENY_GET_AND_POST, 403, 403, ], - [ 20, "legacy", ['action: allow', 'method: GET'], ['action: deny', 'method: GET'], ALLOW_GET_AND_POST, 403, 403, ], - [ 21, "legacy", ['action: allow', 'method: GET'], ['action: deny', 'method: GET'], ALLOW_GET, 403, 403, ], - [ 22, "legacy", ['action: allow', 'method: GET'], ['action: deny', 'method: GET'], DENY_GET, 403, 403, ], - [ 23, "legacy", ['action: allow', 'method: GET'], ['action: deny', 'method: GET'], DENY_GET_AND_POST, 403, 403, ], - [ 24, "legacy", ['action: allow', 'method: GET'], ['action: allow', 'method: POST'], ALLOW_GET_AND_POST, 403, 403, ], - [ 25, "legacy", ['action: allow', 'method: GET'], ['action: allow', 'method: POST'], ALLOW_GET, 403, 403, ], - [ 26, "legacy", ['action: allow', 'method: GET'], ['action: allow', 'method: POST'], DENY_GET, 403, 403, ], - [ 27, "legacy", ['action: allow', 'method: GET'], ['action: allow', 'method: POST'], DENY_GET_AND_POST, 403, 403, ], - [ 28, "legacy", ['action: allow', 'method: GET'], ['action: deny', 'method: POST'], ALLOW_GET_AND_POST, 200, 403, ], - [ 29, "legacy", ['action: allow', 'method: GET'], ['action: deny', 'method: POST'], ALLOW_GET, 200, 403, ], - [ 30, "legacy", ['action: allow', 'method: GET'], ['action: deny', 'method: POST'], DENY_GET, 403, 403, ], - [ 31, "legacy", ['action: allow', 'method: GET'], ['action: deny', 'method: POST'], DENY_GET_AND_POST, 403, 403, ], - [ 32, "legacy", ['action: deny', 'method: GET'], [], ALLOW_GET_AND_POST, 403, 200, ], - [ 33, "legacy", ['action: deny', 'method: GET'], [], ALLOW_GET, 403, 403, ], - [ 34, "legacy", ['action: deny', 'method: GET'], [], DENY_GET, 403, 200, ], - [ 35, "legacy", ['action: deny', 'method: GET'], [], DENY_GET_AND_POST, 403, 403, ], - [ 36, "legacy", ['action: deny', 'method: GET'], ['action: allow', 'method: GET'], ALLOW_GET_AND_POST, 403, 403, ], - [ 37, "legacy", ['action: deny', 'method: GET'], ['action: allow', 'method: GET'], ALLOW_GET, 403, 403, ], - [ 38, "legacy", ['action: deny', 'method: GET'], ['action: allow', 'method: GET'], DENY_GET, 403, 403, ], - [ 39, "legacy", ['action: deny', 'method: GET'], ['action: allow', 'method: GET'], DENY_GET_AND_POST, 403, 403, ], - [ 40, "legacy", ['action: deny', 'method: GET'], ['action: deny', 'method: GET'], ALLOW_GET_AND_POST, 403, 200, ], - [ 41, "legacy", ['action: deny', 'method: GET'], ['action: deny', 'method: GET'], ALLOW_GET, 403, 403, ], - [ 42, "legacy", ['action: deny', 'method: GET'], ['action: deny', 'method: GET'], DENY_GET, 403, 200, ], - [ 43, "legacy", ['action: deny', 'method: GET'], ['action: deny', 'method: GET'], DENY_GET_AND_POST, 403, 403, ], - [ 44, "legacy", ['action: deny', 'method: GET'], ['action: allow', 'method: POST'], ALLOW_GET_AND_POST, 403, 200, ], - [ 45, "legacy", ['action: deny', 'method: GET'], ['action: allow', 'method: POST'], ALLOW_GET, 403, 403, ], - [ 46, "legacy", ['action: deny', 'method: GET'], ['action: allow', 'method: POST'], DENY_GET, 403, 200, ], - [ 47, "legacy", ['action: deny', 'method: GET'], ['action: allow', 'method: POST'], DENY_GET_AND_POST, 403, 403, ], - [ 48, "legacy", ['action: deny', 'method: GET'], ['action: deny', 'method: POST'], ALLOW_GET_AND_POST, 403, 403, ], - [ 49, "legacy", ['action: deny', 'method: GET'], ['action: deny', 'method: POST'], ALLOW_GET, 403, 403, ], - [ 50, "legacy", ['action: deny', 'method: GET'], ['action: deny', 'method: POST'], DENY_GET, 403, 403, ], - [ 51, "legacy", ['action: deny', 'method: GET'], ['action: deny', 'method: POST'], DENY_GET_AND_POST, 403, 403, ], - [ 52, "modern", [], [], ALLOW_GET_AND_POST, 200, 200, ], - [ 53, "modern", [], [], ALLOW_GET, 200, 403, ], - [ 54, "modern", [], [], DENY_GET, 403, 200, ], - [ 55, "modern", [], [], DENY_GET_AND_POST, 403, 403, ], - [ 56, "modern", [], ['action: set_allow', 'method: GET'], ALLOW_GET_AND_POST, 200, 403, ], - [ 57, "modern", [], ['action: set_allow', 'method: GET'], ALLOW_GET, 200, 403, ], - [ 58, "modern", [], ['action: set_allow', 'method: GET'], DENY_GET, 200, 403, ], - [ 59, "modern", [], ['action: set_allow', 'method: GET'], DENY_GET_AND_POST, 200, 403, ], - [ 60, "modern", [], ['action: set_deny', 'method: GET'], ALLOW_GET_AND_POST, 403, 200, ], - [ 61, "modern", [], ['action: set_deny', 'method: GET'], ALLOW_GET, 403, 200, ], - [ 62, "modern", [], ['action: set_deny', 'method: GET'], DENY_GET, 403, 200, ], - [ 63, "modern", [], ['action: set_deny', 'method: GET'], DENY_GET_AND_POST, 403, 200, ], - [ 64, "modern", ['action: set_allow', 'method: GET'], [], ALLOW_GET_AND_POST, 200, 403, ], - [ 65, "modern", ['action: set_allow', 'method: GET'], [], ALLOW_GET, 200, 403, ], - [ 66, "modern", ['action: set_allow', 'method: GET'], [], DENY_GET, 200, 403, ], - [ 67, "modern", ['action: set_allow', 'method: GET'], [], DENY_GET_AND_POST, 200, 403, ], - [ 68, "modern", ['action: set_allow', 'method: GET'], ['action: set_allow', 'method: GET'], ALLOW_GET_AND_POST, 200, 403, ], - [ 69, "modern", ['action: set_allow', 'method: GET'], ['action: set_allow', 'method: GET'], ALLOW_GET, 200, 403, ], - [ 70, "modern", ['action: set_allow', 'method: GET'], ['action: set_allow', 'method: GET'], DENY_GET, 200, 403, ], - [ 71, "modern", ['action: set_allow', 'method: GET'], ['action: set_allow', 'method: GET'], DENY_GET_AND_POST, 200, 403, ], - [ 72, "modern", ['action: set_allow', 'method: GET'], ['action: set_deny', 'method: GET'], ALLOW_GET_AND_POST, 200, 403, ], - [ 73, "modern", ['action: set_allow', 'method: GET'], ['action: set_deny', 'method: GET'], ALLOW_GET, 200, 403, ], - [ 74, "modern", ['action: set_allow', 'method: GET'], ['action: set_deny', 'method: GET'], DENY_GET, 200, 403, ], - [ 75, "modern", ['action: set_allow', 'method: GET'], ['action: set_deny', 'method: GET'], DENY_GET_AND_POST, 200, 403, ], - [ 76, "modern", ['action: set_deny', 'method: GET'], [], ALLOW_GET_AND_POST, 403, 200, ], - [ 77, "modern", ['action: set_deny', 'method: GET'], [], ALLOW_GET, 403, 200, ], - [ 78, "modern", ['action: set_deny', 'method: GET'], [], DENY_GET, 403, 200, ], - [ 79, "modern", ['action: set_deny', 'method: GET'], [], DENY_GET_AND_POST, 403, 200, ], - [ 80, "modern", ['action: set_deny', 'method: GET'], ['action: set_allow', 'method: GET'], ALLOW_GET_AND_POST, 403, 200, ], - [ 81, "modern", ['action: set_deny', 'method: GET'], ['action: set_allow', 'method: GET'], ALLOW_GET, 403, 200, ], - [ 82, "modern", ['action: set_deny', 'method: GET'], ['action: set_allow', 'method: GET'], DENY_GET, 403, 200, ], - [ 83, "modern", ['action: set_deny', 'method: GET'], ['action: set_allow', 'method: GET'], DENY_GET_AND_POST, 403, 200, ], - [ 84, "modern", ['action: set_deny', 'method: GET'], ['action: set_deny', 'method: GET'], ALLOW_GET_AND_POST, 403, 200, ], - [ 85, "modern", ['action: set_deny', 'method: GET'], ['action: set_deny', 'method: GET'], ALLOW_GET, 403, 200, ], - [ 86, "modern", ['action: set_deny', 'method: GET'], ['action: set_deny', 'method: GET'], DENY_GET, 403, 200, ], - [ 87, "modern", ['action: set_deny', 'method: GET'], ['action: set_deny', 'method: GET'], DENY_GET_AND_POST, 403, 200, ], - [ 88, "legacy", ['action: allow', 'src_ip: 127.0.0.1'], [], ALLOW_GET_AND_POST, 200, 200, ], - [ 89, "legacy", ['action: allow', 'src_ip: 127.0.0.1'], [], ALLOW_GET, 200, 403, ], - [ 90, "legacy", ['action: allow', 'src_ip: 127.0.0.1'], [], DENY_GET, 403, 200, ], - [ 91, "legacy", ['action: allow', 'src_ip: 127.0.0.1'], [], DENY_GET_AND_POST, 403, 403, ], - [ 92, "legacy", ['action: deny', 'src_ip: 127.0.0.1'], [], ALLOW_GET_AND_POST, 403, 403, ], - [ 93, "legacy", ['action: deny', 'src_ip: 127.0.0.1'], [], ALLOW_GET, 403, 403, ], - [ 94, "legacy", ['action: deny', 'src_ip: 127.0.0.1'], [], DENY_GET, 403, 403, ], - [ 95, "legacy", ['action: deny', 'src_ip: 127.0.0.1'], [], DENY_GET_AND_POST, 403, 403, ], - [ 96, "legacy", ['action: allow', 'src_ip: 192.0.2.1/24'], [], ALLOW_GET_AND_POST, 403, 403, ], - [ 97, "legacy", ['action: allow', 'src_ip: 192.0.2.1/24'], [], ALLOW_GET, 403, 403, ], - [ 98, "legacy", ['action: allow', 'src_ip: 192.0.2.0/24'], [], DENY_GET, 403, 403, ], - [ 99, "legacy", ['action: allow', 'src_ip: 192.0.2.0/24'], [], DENY_GET_AND_POST, 403, 403, ], - [100, "legacy", ['action: deny', 'src_ip: 192.0.2.1/24'], [], ALLOW_GET_AND_POST, 200, 200, ], - [101, "legacy", ['action: deny', 'src_ip: 192.0.2.1/24'], [], ALLOW_GET, 200, 403, ], - [102, "legacy", ['action: deny', 'src_ip: 192.0.2.0/24'], [], DENY_GET, 403, 200, ], - [103, "legacy", ['action: deny', 'src_ip: 192.0.2.0/24'], [], DENY_GET_AND_POST, 403, 403, ], -] -# yapf: enable - -all_acl_combination_tests_yaml = [dict(zip(keys, test)) for test in all_acl_combinations_yaml] diff --git a/tests/gold_tests/remap_yaml/base.replay.yaml b/tests/gold_tests/remap_yaml/base.replay.yaml deleted file mode 100644 index 2f4ba5da8ff..00000000000 --- a/tests/gold_tests/remap_yaml/base.replay.yaml +++ /dev/null @@ -1,66 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This expects a remap.config that denies HEAD and POST, but allows all other -# methods. - -meta: - version: '1.0' - -sessions: -- protocol: - stack: http - transactions: - - - client-request: - method: GET - version: '1.1' - url: /test/ip_allow/test_get - headers: - fields: - - [ Content-Length, 0 ] - - [ uuid, get ] - - [ X-Request, get ] - - proxy-response: - status: 200 - - server-response: - status: 200 - reason: OK - headers: - fields: - - [ Content-Length, 20 ] - - - client-request: - method: POST - version: '1.1' - url: /test/ip_allow/test_post - headers: - fields: - - [ Content-Length, 10 ] - - [ uuid, post ] - - [ X-Request, post ] - - proxy-response: - status: 200 - - server-response: - status: 200 - reason: OK - headers: - fields: - - [ Content-Length, 20 ] diff --git a/tests/gold_tests/remap_yaml/basic_conf_remap_yaml_yaml.test.py b/tests/gold_tests/remap_yaml/basic_conf_remap_yaml_yaml.test.py deleted file mode 100644 index 5cff98416c1..00000000000 --- a/tests/gold_tests/remap_yaml/basic_conf_remap_yaml_yaml.test.py +++ /dev/null @@ -1,203 +0,0 @@ -''' -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = ''' -Test conf_remap using a yaml file. -''' - -Test.ContinueOnFail = True - - -class conf_remap_yaml_load_test: - """Test conf_remap using a yaml file.""" - - client_counter: int = 0 - ts_counter: int = 0 - server_counter: int = 0 - - def __init__(self, name: str, gold_file="", remap_filename="", remap_content=""): - """Initialize the test. - :param name: The name of the test. - :param gold_file: Gold file to be checked. - :param remap_filename: Remap yaml filename. - :param remap_content: remap yaml file content. - """ - self.name = name - self.gold_file = gold_file - self._remap_filename = remap_filename - self._remap_content = remap_content - - def _configure_server(self, tr: 'TestRun'): - """Configure the server. - - :param tr: The TestRun object to associate the server process with. - """ - server = Test.MakeOriginServer(f"server-{conf_remap_yaml_load_test.ts_counter}", lookup_key="{%Host}{PATH}") - request_header2 = { - "headers": "GET /test HTTP/1.1\r\nHost: www.testexample.com\r\n\r\n", - "timestamp": "1469733493.993", - "body": "" - } - response_header2 = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""} - - server.addResponse("sessionfile.log", request_header2, response_header2) - conf_remap_yaml_load_test.server_counter += 1 - self._server = server - - def _configure_traffic_server(self, tr: 'TestRun'): - """Configure Traffic Server. - - :param tr: The TestRun object to associate the ts process with. - """ - ts = Test.MakeATSProcess(f"ts-{conf_remap_yaml_load_test.ts_counter}") - - conf_remap_yaml_load_test.ts_counter += 1 - ts.Disk.records_config.update( - ''' - diags: - debug: - enabled: 1 - tags: conf_remap - dns: - resolv_conf: NULL - http: - referer_filter: 1 - url_remap: - pristine_host_hdr: 0 # make sure is 0 - - ''') - self._ts = ts - - def run(self, diags_fail_exp="", ts_retcode=0): - """Run the test. - :param diags_fail_exp: Text to be included to validate the error. - :param ts_retcode: Expected return code from TS. - """ - tr = Test.AddTestRun(self.name) - self._configure_server(tr) - self._configure_traffic_server(tr) - - tr.Processes.Default.StartBefore(self._server) - tr.Processes.Default.StartBefore(self._ts) - - self._ts.ReturnCode = ts_retcode - - if ts_retcode > 0: # we could have errors logged and yet, we still want to move on. - self._ts.Ready = 0 - - if diags_fail_exp != "": - # some error logs will be written to the diags. - self._ts.Disk.diags_log.Content = Testers.IncludesExpression(diags_fail_exp, "Have a look.") - else: - tr.Processes.Default.ReturnCode = 0 - - if self.gold_file: - tr.Processes.Default.Streams.stderr = self.gold_file - - if self._remap_filename != "" and self._remap_content != "": - self._ts.Disk.MakeConfigFile(self._remap_filename).update(self._remap_content) - self._ts.Disk.remap_yaml.AddLines( - f''' -remap: - - type: map - from: - url: http://www.testexample.com/ - to: - url: http://127.0.0.1:{self._server.Variables.Port} - plugins: - - name: conf_remap.so - params: - - {self._remap_filename} - '''.split("\n")) - if Condition.CurlUsingUnixDomainSocket(): - tr.MakeCurlCommand( - '-H "Host: www.testexample.com" "http://127.0.0.1:{0}/test" --verbose'.format(self._ts.Variables.port), ts=self._ts) - else: - tr.MakeCurlCommand( - '--proxy 127.0.0.1:{0} "http://www.testexample.com/test" -H "Host: www.testexample.com" --verbose'.format( - self._ts.Variables.port), - ts=self._ts) - conf_remap_yaml_load_test.client_counter += 1 - - -gold_file = "gold/200OK_test.gold" -if Condition.CurlUsingUnixDomainSocket(): - gold_file = "gold/200OK_test_uds.gold" - -test0 = conf_remap_yaml_load_test( - "Test success", - gold_file=gold_file, - remap_filename="testexample_remap.yaml", - remap_content=''' - records: - url_remap: - pristine_host_hdr: 1 - ''') -test0.run() - -test1 = conf_remap_yaml_load_test( - "Test mismatch type", - remap_filename="mismatch_field_type_remap.yaml", - remap_content=''' - records: - url_remap: - pristine_host_hdr: !!float '1' - ''') -test1.run(diags_fail_exp="'proxy.config.url_remap.pristine_host_hdr' variable type mismatch", ts_retcode=33) - -test2 = conf_remap_yaml_load_test( - "Test invalid variable", - remap_filename="invalid1_field_type_remap.yaml", - remap_content=''' - records: - plugin: - dynamic_reload_mode: 1 - ''') - -test2.run( - diags_fail_exp="'proxy.config.plugin.dynamic_reload_mode' is not a configuration variable or cannot be overridden", - ts_retcode=33) - -# We let the conf_remap parse two fields, only one is valid, we expect ATS to start and the invalid fields ignored. -test3 = conf_remap_yaml_load_test( - "Test success", - gold_file=gold_file, - remap_filename="testexample2_remap.yaml", - remap_content=''' - records: - plugin: - dynamic_reload_mode: 1 - - url_remap: - pristine_host_hdr: 1 - ''') -test3.run(diags_fail_exp="'proxy.config.plugin.dynamic_reload_mode' is not a configuration variable or cannot be overridden") - -# Check null values -test4 = conf_remap_yaml_load_test( - "Test success - with NULL variable", - gold_file=gold_file, - remap_filename="testexample_remap.yaml", - remap_content=''' - records: - url_remap: - pristine_host_hdr: 1 - hostdb: - ip_resolve: "NULL" # We want to make sure this gets read as it should. "NULL" could be the value of this field. - ''') -test4.run() diff --git a/tests/gold_tests/remap_yaml/conf_remap_float_yaml.test.py b/tests/gold_tests/remap_yaml/conf_remap_float_yaml.test.py deleted file mode 100644 index bf0edbc3108..00000000000 --- a/tests/gold_tests/remap_yaml/conf_remap_float_yaml.test.py +++ /dev/null @@ -1,52 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = ''' -Test command: traffic_ctl config describe proxy.config.http.background_fill_completed_threshold (YTSATS-3309) -''' -Test.testName = 'Float in conf_remap Config Test' - -ts = Test.MakeATSProcess("ts") - -ts.Disk.MakeConfigFile('conf_remap.yaml').update(''' -records: - http: - background_fill_completed_threshold: !!float '0.5' -''') - -ts.Disk.remap_yaml.AddLines( - f''' -remap: - - type: map - from: - url: http://cdn.example.com/ - to: - url: http://origin.example.com/ - plugins: - - name: conf_remap.so - params: - - {Test.RunDirectory}/ts/config/conf_remap.yaml - '''.split("\n")) - -tr = Test.AddTestRun("traffic_ctl command") -tr.Env = ts.Env -tr.TimeOut = 5 -tr.StillRunningAfter = ts - -p = tr.Processes.Default -p.Command = f"traffic_ctl config describe proxy.config.http.background_fill_completed_threshold" -p.ReturnCode = 0 -p.StartBefore(Test.Processes.ts) diff --git a/tests/gold_tests/remap_yaml/deactivate_ip_allow_yaml.py b/tests/gold_tests/remap_yaml/deactivate_ip_allow_yaml.py deleted file mode 100644 index 5c710abc568..00000000000 --- a/tests/gold_tests/remap_yaml/deactivate_ip_allow_yaml.py +++ /dev/null @@ -1,126 +0,0 @@ -''' -Verify remap.yaml acl behavior. -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -ALLOW_GET_AND_POST = f''' -ip_allow: - - apply: in - ip_addrs: [0/0, ::/0] - action: allow - methods: [GET, POST] -''' - -ALLOW_GET = f''' -ip_allow: - - apply: in - ip_addrs: [0/0, ::/0] - action: allow - methods: [GET] -''' - -DENY_GET = f''' -ip_allow: - - apply: in - ip_addrs: [0/0, ::/0] - action: deny - methods: [GET] -''' - -DENY_GET_AND_POST = f''' -ip_allow: - - apply: in - ip_addrs: [0/0, ::/0] - action: deny - methods: [GET, POST] -''' - -# Optimized ACL filter on accept -DENY_ALL = f''' -ip_allow: - - apply: in - ip_addrs: [0/0, ::/0] - action: deny - methods: ALL -''' - -# yapf: disable -keys = ["index", "policy", "inline", "named_acl", "deactivate_ip_allow", "ip_allow", "GET response", "POST response"] -deactivate_ip_allow_combinations_yaml = [ - [ 0, "legacy", [], [], False, ALLOW_GET_AND_POST, 200, 200, ], - [ 1, "legacy", [], [], False, ALLOW_GET, 200, 403, ], - [ 2, "legacy", [], [], False, DENY_GET, 403, 200, ], - [ 3, "legacy", [], [], False, DENY_GET_AND_POST, 403, 403, ], - [ 4, "legacy", [], [], False, DENY_ALL, None, None, ], - [ 5, "legacy", [], [], True, ALLOW_GET_AND_POST, 200, 200, ], - [ 6, "legacy", [], [], True, ALLOW_GET, 200, 200, ], - [ 7, "legacy", [], [], True, DENY_GET, 200, 200, ], - [ 8, "legacy", [], [], True, DENY_GET_AND_POST, 200, 200, ], - [ 9, "legacy", [], [], True, DENY_ALL, 200, 200, ], - [ 10, "legacy", ["action: allow", "method: GET"], [], False, ALLOW_GET_AND_POST, 200, 403, ], - [ 11, "legacy", ["action: allow", "method: GET"], [], False, ALLOW_GET, 200, 403, ], - [ 12, "legacy", ["action: allow", "method: GET"], [], False, DENY_GET, 403, 403, ], - [ 13, "legacy", ["action: allow", "method: GET"], [], False, DENY_GET_AND_POST, 403, 403, ], - [ 14, "legacy", ["action: allow", "method: GET"], [], False, DENY_ALL, None, None, ], - [ 15, "legacy", ["action: allow", "method: GET"], [], True, ALLOW_GET_AND_POST, 200, 403, ], - [ 16, "legacy", ["action: allow", "method: GET"], [], True, ALLOW_GET, 200, 403, ], - [ 17, "legacy", ["action: allow", "method: GET"], [], True, DENY_GET, 200, 403, ], - [ 18, "legacy", ["action: allow", "method: GET"], [], True, DENY_GET_AND_POST, 200, 403, ], - [ 19, "legacy", ["action: allow", "method: GET"], [], True, DENY_ALL, 200, 403, ], - [ 20, "legacy", ["action: deny", "method: GET"], [], False, ALLOW_GET_AND_POST, 403, 200, ], - [ 21, "legacy", ["action: deny", "method: GET"], [], False, ALLOW_GET, 403, 403, ], - [ 22, "legacy", ["action: deny", "method: GET"], [], False, DENY_GET, 403, 200, ], - [ 23, "legacy", ["action: deny", "method: GET"], [], False, DENY_GET_AND_POST, 403, 403, ], - [ 24, "legacy", ["action: deny", "method: GET"], [], False, DENY_ALL, None, None, ], - [ 25, "legacy", ["action: deny", "method: GET"], [], True, ALLOW_GET_AND_POST, 403, 200, ], - [ 26, "legacy", ["action: deny", "method: GET"], [], True, ALLOW_GET, 403, 200, ], - [ 27, "legacy", ["action: deny", "method: GET"], [], True, DENY_GET, 403, 200, ], - [ 28, "legacy", ["action: deny", "method: GET"], [], True, DENY_GET_AND_POST, 403, 200, ], - [ 29, "legacy", ["action: deny", "method: GET"], [], True, DENY_ALL, 403, 200, ], - [ 30, "legacy", ["action: allow", "src_ip: 127.0.0.1"], [], False, DENY_ALL, None, None, ], - [ 31, "legacy", ["action: allow", "src_ip: 127.0.0.1"], [], True, DENY_ALL, 200, 200, ], - [ 32, "legacy", ["action: deny", "src_ip: 127.0.0.1"], [], False, DENY_ALL, None, None, ], - [ 33, "legacy", ["action: deny", "src_ip: 127.0.0.1"], [], True, DENY_ALL, 403, 403, ], - [ 34, "legacy", ["action: allow", "src_ip: 192.0.2.1/24"], [], False, DENY_ALL, None, None, ], - [ 35, "legacy", ["action: allow", "src_ip: 192.0.2.1/24"], [], True, DENY_ALL, 403, 403, ], - [ 36, "legacy", ["action: deny", "src_ip: 192.0.2.0/24"], [], False, DENY_ALL, None, None, ], - [ 37, "legacy", ["action: deny", "src_ip: 192.0.2.0/24"], [], True, DENY_ALL, 200, 200, ], - - # Verify in legacy mode that add_allow acts just like allow, and add_deny acts just like deny. - [ 38, "legacy", ["action: add_allow", "method: GET"], [], False, ALLOW_GET_AND_POST, 200, 403, ], - [ 39, "legacy", ["action: add_allow", "method: GET"], [], False, ALLOW_GET, 200, 403, ], - [ 40, "legacy", ["action: add_allow", "method: GET"], [], False, DENY_GET, 403, 403, ], - [ 41, "legacy", ["action: add_allow", "method: GET"], [], False, DENY_GET_AND_POST, 403, 403, ], - [ 42, "legacy", ["action: add_allow", "method: GET"], [], False, DENY_ALL, None, None, ], - [ 43, "legacy", ["action: add_allow", "method: GET"], [], True, ALLOW_GET_AND_POST, 200, 403, ], - [ 44, "legacy", ["action: add_allow", "method: GET"], [], True, ALLOW_GET, 200, 403, ], - [ 45, "legacy", ["action: add_allow", "method: GET"], [], True, DENY_GET, 200, 403, ], - [ 46, "legacy", ["action: add_allow", "method: GET"], [], True, DENY_GET_AND_POST, 200, 403, ], - [ 47, "legacy", ["action: add_allow", "method: GET"], [], True, DENY_ALL, 200, 403, ], - [ 48, "legacy", ["action: add_deny", "method: GET"], [], False, ALLOW_GET_AND_POST, 403, 200, ], - [ 49, "legacy", ["action: add_deny", "method: GET"], [], False, ALLOW_GET, 403, 403, ], - [ 50, "legacy", ["action: add_deny", "method: GET"], [], False, DENY_GET, 403, 200, ], - [ 51, "legacy", ["action: add_deny", "method: GET"], [], False, DENY_GET_AND_POST, 403, 403, ], - [ 52, "legacy", ["action: add_deny", "method: GET"], [], False, DENY_ALL, None, None, ], - [ 53, "legacy", ["action: add_deny", "method: GET"], [], True, ALLOW_GET_AND_POST, 403, 200, ], - [ 54, "legacy", ["action: add_deny", "method: GET"], [], True, ALLOW_GET, 403, 200, ], - [ 55, "legacy", ["action: add_deny", "method: GET"], [], True, DENY_GET, 403, 200, ], - [ 56, "legacy", ["action: add_deny", "method: GET"], [], True, DENY_GET_AND_POST, 403, 200, ], - [ 57, "legacy", ["action: add_deny", "method: GET"], [], True, DENY_ALL, 403, 200, ], -] -all_deactivate_ip_allow_tests_yaml = [dict(zip(keys, test)) for test in deactivate_ip_allow_combinations_yaml] -# yapf: enable diff --git a/tests/gold_tests/remap_yaml/deny_head_post.replay.yaml b/tests/gold_tests/remap_yaml/deny_head_post.replay.yaml deleted file mode 100644 index e149fa73eac..00000000000 --- a/tests/gold_tests/remap_yaml/deny_head_post.replay.yaml +++ /dev/null @@ -1,97 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This expects a remap.config that denies HEAD and POST, but allows all other -# methods. - -meta: - version: "1.0" - - blocks: - - standard_response: &standard_response - server-response: - status: 200 - reason: OK - headers: - fields: - - [ Content-Length, 20 ] - -sessions: -- protocol: - stack: http - transactions: - - - client-request: - method: "GET" - version: "1.1" - url: /test/ip_allow/test_get - headers: - fields: - - [ Content-Length, 0 ] - - [ uuid, get ] - - [ X-Request, get ] - - <<: *standard_response - - proxy-response: - status: 200 - - - client-request: - method: "HEAD" - version: "1.1" - url: /test/ip_allow/test_head - headers: - fields: - - [ Content-Length, 0 ] - - [ uuid, head ] - - [ X-Request, head ] - - <<: *standard_response - - proxy-response: - status: 403 - - # POST rejected - - client-request: - method: "POST" - version: "1.1" - url: /test/ip_allow/test_post - headers: - fields: - - [Content-Length, 10] - - [ uuid, post ] - - [ X-Request, post ] - - <<: *standard_response - - proxy-response: - status: 403 - - - client-request: - method: "DELETE" - version: "1.1" - url: /test/ip_allow/test_delete - headers: - fields: - - [ Host, example.com ] - - [ uuid, delete ] - - [ X-Request, delete ] - - [ Content-Length, 0 ] - - <<: *standard_response - - proxy-response: - status: 200 diff --git a/tests/gold_tests/remap_yaml/gold/200OK_test.gold b/tests/gold_tests/remap_yaml/gold/200OK_test.gold deleted file mode 100644 index 887d5fb3ac0..00000000000 --- a/tests/gold_tests/remap_yaml/gold/200OK_test.gold +++ /dev/null @@ -1,13 +0,0 @@ -`` -> GET http://www.testexample.com/test`` -> Host: www.testexample.com`` -> User-Agent: curl/`` -> Accept: */* -`` -< HTTP/1.1 200 OK -< Date: `` -< Age: `` -< Transfer-Encoding: chunked -< Proxy-Connection: keep-alive -< Server: ATS/`` -`` diff --git a/tests/gold_tests/remap_yaml/gold/200OK_test_uds.gold b/tests/gold_tests/remap_yaml/gold/200OK_test_uds.gold deleted file mode 100644 index 5d588e039e5..00000000000 --- a/tests/gold_tests/remap_yaml/gold/200OK_test_uds.gold +++ /dev/null @@ -1,13 +0,0 @@ -`` -> GET /test`` -> Host: www.testexample.com`` -> User-Agent: curl/`` -> Accept: */* -`` -< HTTP/1.1 200 OK -< Date: `` -< Age: `` -< Transfer-Encoding: chunked -< Connection: keep-alive -< Server: ATS/`` -`` diff --git a/tests/gold_tests/remap_yaml/gold/lookupTest.gold b/tests/gold_tests/remap_yaml/gold/lookupTest.gold deleted file mode 100644 index e6f1707580e..00000000000 --- a/tests/gold_tests/remap_yaml/gold/lookupTest.gold +++ /dev/null @@ -1,14 +0,0 @@ -`` -> GET ``/test`` -> Host: www.testexample.com`` -> User-Agent: curl/`` -> Accept: */* -`` -< HTTP/1.1 200 OK -< Date: `` -< Age: `` -< Transfer-Encoding: chunked -< Proxy-Connection: keep-alive -< Server: ATS/`` -< -`` diff --git a/tests/gold_tests/remap_yaml/gold/map-with-recv-port-ip.gold b/tests/gold_tests/remap_yaml/gold/map-with-recv-port-ip.gold deleted file mode 100644 index b7949ccc68c..00000000000 --- a/tests/gold_tests/remap_yaml/gold/map-with-recv-port-ip.gold +++ /dev/null @@ -1,13 +0,0 @@ -`` -> GET /`` -> Host: test.example.com`` -> User-Agent: curl/`` -> Accept: */* -`` -< HTTP/1.1 200 OK -< Content-Length: 2 -< Date: `` -< Age: `` -< Server: ATS/`` -< -`` diff --git a/tests/gold_tests/remap_yaml/gold/map-with-recv-port-unix.gold b/tests/gold_tests/remap_yaml/gold/map-with-recv-port-unix.gold deleted file mode 100644 index a83cd71adee..00000000000 --- a/tests/gold_tests/remap_yaml/gold/map-with-recv-port-unix.gold +++ /dev/null @@ -1,13 +0,0 @@ -`` -> GET /`` -> Host: test.example.com`` -> User-Agent: curl/`` -> Accept: */* -`` -< HTTP/1.1 200 OK -< Content-Length: 4 -< Date: `` -< Age: `` -< Server: ATS/`` -< -`` diff --git a/tests/gold_tests/remap_yaml/gold/remap-200.gold b/tests/gold_tests/remap_yaml/gold/remap-200.gold deleted file mode 100644 index d44a26ad06c..00000000000 --- a/tests/gold_tests/remap_yaml/gold/remap-200.gold +++ /dev/null @@ -1,14 +0,0 @@ -`` -> GET `` -> Host: www.example.com`` -> User-Agent: curl/`` -> Accept: */* -`` -< HTTP/1.1 200 OK -< Date: `` -< Age: `` -< Transfer-Encoding: chunked -< Proxy-Connection: keep-alive -< Server: ATS/`` -< -`` diff --git a/tests/gold_tests/remap_yaml/gold/remap-404.gold b/tests/gold_tests/remap_yaml/gold/remap-404.gold deleted file mode 100644 index 133518e2d02..00000000000 --- a/tests/gold_tests/remap_yaml/gold/remap-404.gold +++ /dev/null @@ -1,12 +0,0 @@ -`` -> GET `` HTTP/1.1 -> Host: `` -> User-Agent: curl/`` -`` -< HTTP/1.1 404 Not Found`` -< Date: `` -< Proxy-Connection: keep-alive -< Server: ATS/`` -`` -< Content-Type: text/html -`` diff --git a/tests/gold_tests/remap_yaml/gold/remap-DNS-200.gold b/tests/gold_tests/remap_yaml/gold/remap-DNS-200.gold deleted file mode 100644 index 9fafc8ee1d6..00000000000 --- a/tests/gold_tests/remap_yaml/gold/remap-DNS-200.gold +++ /dev/null @@ -1,14 +0,0 @@ -`` -> GET `` -> Host: testDNS.com`` -> User-Agent: curl/`` -> Accept: */* -`` -< HTTP/1.1 200 OK -< Date: `` -< Age: `` -< Transfer-Encoding: chunked -< Proxy-Connection: keep-alive -< Server: ATS/`` -< -`` diff --git a/tests/gold_tests/remap_yaml/gold/remap-DNS-ipv6-200.gold b/tests/gold_tests/remap_yaml/gold/remap-DNS-ipv6-200.gold deleted file mode 100644 index f926c5b93fb..00000000000 --- a/tests/gold_tests/remap_yaml/gold/remap-DNS-ipv6-200.gold +++ /dev/null @@ -1,14 +0,0 @@ -`` -> GET `` -> Host: testDNS2.com`` -> User-Agent: curl/`` -> Accept: */* -`` -< HTTP/1.1 200 OK -< Date: `` -< Age: `` -< Transfer-Encoding: chunked -< Proxy-Connection: keep-alive -< Server: ATS/`` -< -`` diff --git a/tests/gold_tests/remap_yaml/gold/remap-hitATS-404.gold b/tests/gold_tests/remap_yaml/gold/remap-hitATS-404.gold deleted file mode 100644 index 67a81bc4f9a..00000000000 --- a/tests/gold_tests/remap_yaml/gold/remap-hitATS-404.gold +++ /dev/null @@ -1,11 +0,0 @@ -`` -> GET / HTTP/1.1 -> Host: `` -> User-Agent: curl/`` -`` -< HTTP/1.1 404 Not Found on Accelerator -< Date: `` -< Connection: `` -< Server: ATS/`` -< Content-Type: text/html -`` diff --git a/tests/gold_tests/remap_yaml/gold/remap-https-200.gold b/tests/gold_tests/remap_yaml/gold/remap-https-200.gold deleted file mode 100644 index 9cd42fc43a0..00000000000 --- a/tests/gold_tests/remap_yaml/gold/remap-https-200.gold +++ /dev/null @@ -1,13 +0,0 @@ -`` -> GET / HTTP/1.1 -> Host: www.example.com`` -> User-Agent: curl/`` -`` -< HTTP/1.1 200 OK -< Date: `` -< Age: `` -< Transfer-Encoding: chunked -< Connection: keep-alive -< Server: ATS/`` -< -`` diff --git a/tests/gold_tests/remap_yaml/gold/remap-https-200_2.gold b/tests/gold_tests/remap_yaml/gold/remap-https-200_2.gold deleted file mode 100644 index 4bb24ea4e34..00000000000 --- a/tests/gold_tests/remap_yaml/gold/remap-https-200_2.gold +++ /dev/null @@ -1,13 +0,0 @@ -`` -> GET / HTTP/1.1 -> Host: www.anotherexample.com`` -> User-Agent: curl/`` -`` -< HTTP/1.1 200 OK -< Date: `` -< Age: `` -< Transfer-Encoding: chunked -< Connection: keep-alive -< Server: ATS/`` -< -`` diff --git a/tests/gold_tests/remap_yaml/gold/remap-https-200_3.gold b/tests/gold_tests/remap_yaml/gold/remap-https-200_3.gold deleted file mode 100644 index f5a6798af88..00000000000 --- a/tests/gold_tests/remap_yaml/gold/remap-https-200_3.gold +++ /dev/null @@ -1,13 +0,0 @@ -`` -> GET / HTTP/1.1 -> Host: www.example3.com`` -> User-Agent: curl/`` -`` -< HTTP/1.1 200 OK -< Date: `` -< Age: `` -< Transfer-Encoding: chunked -< Connection: keep-alive -< Server: ATS/`` -< -`` diff --git a/tests/gold_tests/remap_yaml/gold/remap-ip-resolve.gold b/tests/gold_tests/remap_yaml/gold/remap-ip-resolve.gold deleted file mode 100644 index c83df3f4ca3..00000000000 --- a/tests/gold_tests/remap_yaml/gold/remap-ip-resolve.gold +++ /dev/null @@ -1,14 +0,0 @@ -`` -> GET http://testDNS.com/`` -> Host: testDNS.com`` -> User-Agent: curl/`` -> Accept: */* -`` -< HTTP/1.1 200 OK -< Server: ATS/`` -< Date: `` -< Age: `` -< Transfer-Encoding: chunked -< Proxy-Connection: keep-alive -< -`` diff --git a/tests/gold_tests/remap_yaml/gold/remap-redirect.gold b/tests/gold_tests/remap_yaml/gold/remap-redirect.gold deleted file mode 100644 index 27203b00d45..00000000000 --- a/tests/gold_tests/remap_yaml/gold/remap-redirect.gold +++ /dev/null @@ -1,15 +0,0 @@ -`` -> GET `` -> Host: test3.com`` -> User-Agent: curl/`` -> Accept: */* -`` -< HTTP/1.1 301 Redirect -< Date: `` -< Proxy-Connection: `` -< Server: ATS/`` -< Cache-Control: `` -< Location: http://httpbin.org/ -`` -< Content-Length: `` -`` diff --git a/tests/gold_tests/remap_yaml/gold/remap-referer-hit.gold b/tests/gold_tests/remap_yaml/gold/remap-referer-hit.gold deleted file mode 100644 index 17e39666026..00000000000 --- a/tests/gold_tests/remap_yaml/gold/remap-referer-hit.gold +++ /dev/null @@ -1,14 +0,0 @@ -`` -> GET `` -> Host: test4.com`` -> User-Agent: curl/`` -> Accept: */* -`` -< HTTP/1.1 200 OK -< Date: `` -< Age: `` -< Transfer-Encoding: chunked -< Proxy-Connection: keep-alive -< Server: ATS/`` -< -`` diff --git a/tests/gold_tests/remap_yaml/gold/remap-referer-miss.gold b/tests/gold_tests/remap_yaml/gold/remap-referer-miss.gold deleted file mode 100644 index 0a99becbda1..00000000000 --- a/tests/gold_tests/remap_yaml/gold/remap-referer-miss.gold +++ /dev/null @@ -1,15 +0,0 @@ -`` -> GET `` -> Host: test4.com`` -> User-Agent: curl/`` -> Accept: */* -`` -< HTTP/1.1 302 Redirect -< Date: `` -< Proxy-Connection: `` -< Server: ATS/`` -< Cache-Control: `` -< Location: http://httpbin.org -`` -< Content-Length: `` -`` diff --git a/tests/gold_tests/remap_yaml/gold/remap-ws-metrics-uds.gold b/tests/gold_tests/remap_yaml/gold/remap-ws-metrics-uds.gold deleted file mode 100644 index d88a74be335..00000000000 --- a/tests/gold_tests/remap_yaml/gold/remap-ws-metrics-uds.gold +++ /dev/null @@ -1,21 +0,0 @@ -proxy.process.http.total_incoming_connections 2 -proxy.process.http.total_client_connections 2 -proxy.process.http.total_client_connections_ipv4 0 -proxy.process.http.total_client_connections_ipv6 0 -proxy.process.http.total_server_connections 1 -proxy.process.http2.total_client_connections 0 -proxy.process.http.connect_requests 0 -proxy.process.tunnel.total_client_connections_blind_tcp 1 -proxy.process.tunnel.current_client_connections_blind_tcp 0 -proxy.process.tunnel.total_server_connections_blind_tcp 1 -proxy.process.tunnel.current_server_connections_blind_tcp 0 -proxy.process.tunnel.total_client_connections_tls_tunnel 0 -proxy.process.tunnel.current_client_connections_tls_tunnel 0 -proxy.process.tunnel.total_client_connections_tls_forward 0 -proxy.process.tunnel.current_client_connections_tls_forward 0 -proxy.process.tunnel.total_client_connections_tls_partial_blind 0 -proxy.process.tunnel.current_client_connections_tls_partial_blind 0 -proxy.process.tunnel.total_client_connections_tls_http 0 -proxy.process.tunnel.current_client_connections_tls_http 0 -proxy.process.tunnel.total_server_connections_tls 0 -proxy.process.tunnel.current_server_connections_tls 0 diff --git a/tests/gold_tests/remap_yaml/gold/remap-ws-metrics.gold b/tests/gold_tests/remap_yaml/gold/remap-ws-metrics.gold deleted file mode 100644 index cf39c20a91c..00000000000 --- a/tests/gold_tests/remap_yaml/gold/remap-ws-metrics.gold +++ /dev/null @@ -1,21 +0,0 @@ -proxy.process.http.total_incoming_connections 3 -proxy.process.http.total_client_connections 3 -proxy.process.http.total_client_connections_ipv4 3 -proxy.process.http.total_client_connections_ipv6 0 -proxy.process.http.total_server_connections 2 -proxy.process.http2.total_client_connections 0 -proxy.process.http.connect_requests 0 -proxy.process.tunnel.total_client_connections_blind_tcp 1 -proxy.process.tunnel.current_client_connections_blind_tcp 0 -proxy.process.tunnel.total_server_connections_blind_tcp 2 -proxy.process.tunnel.current_server_connections_blind_tcp 0 -proxy.process.tunnel.total_client_connections_tls_tunnel 0 -proxy.process.tunnel.current_client_connections_tls_tunnel 0 -proxy.process.tunnel.total_client_connections_tls_forward 0 -proxy.process.tunnel.current_client_connections_tls_forward 0 -proxy.process.tunnel.total_client_connections_tls_partial_blind 0 -proxy.process.tunnel.current_client_connections_tls_partial_blind 0 -proxy.process.tunnel.total_client_connections_tls_http 1 -proxy.process.tunnel.current_client_connections_tls_http 0 -proxy.process.tunnel.total_server_connections_tls 0 -proxy.process.tunnel.current_server_connections_tls 0 diff --git a/tests/gold_tests/remap_yaml/gold/remap-ws-upgrade-400.gold b/tests/gold_tests/remap_yaml/gold/remap-ws-upgrade-400.gold deleted file mode 100644 index a46a2490de7..00000000000 --- a/tests/gold_tests/remap_yaml/gold/remap-ws-upgrade-400.gold +++ /dev/null @@ -1,7 +0,0 @@ -`` -> GET /chat HTTP/1.1 -> Host: `` -> User-Agent: curl/`` -`` -< HTTP/1.1 400 Invalid Upgrade Request -`` diff --git a/tests/gold_tests/remap_yaml/gold/remap-ws-upgrade.gold b/tests/gold_tests/remap_yaml/gold/remap-ws-upgrade.gold deleted file mode 100644 index 317fee1f53c..00000000000 --- a/tests/gold_tests/remap_yaml/gold/remap-ws-upgrade.gold +++ /dev/null @@ -1,11 +0,0 @@ -`` -> GET /chat HTTP/1.1 -> Host: `` -> User-Agent: curl/`` -`` -< HTTP/1.1 101 Switching Protocols -< Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= -< Date: `` -< Connection: Upgrade -< Upgrade: websocket -`` diff --git a/tests/gold_tests/remap_yaml/gold/remap-zero-200.gold b/tests/gold_tests/remap_yaml/gold/remap-zero-200.gold deleted file mode 100644 index bcad7913071..00000000000 --- a/tests/gold_tests/remap_yaml/gold/remap-zero-200.gold +++ /dev/null @@ -1,7 +0,0 @@ -`` -> GET / HTTP/1.1 -> Host: zero.one.two.three.com -`` -< HTTP/1.1 200 OK -< Date: `` -`` diff --git a/tests/gold_tests/remap_yaml/gold/remap2-200.gold b/tests/gold_tests/remap_yaml/gold/remap2-200.gold deleted file mode 100644 index 19ec0035312..00000000000 --- a/tests/gold_tests/remap_yaml/gold/remap2-200.gold +++ /dev/null @@ -1,14 +0,0 @@ -`` -> GET `` -> Host: www.example2.com`` -> User-Agent: curl/`` -> Accept: */* -`` -< HTTP/1.1 200 OK -< Date: `` -< Age: `` -< Transfer-Encoding: chunked -< Proxy-Connection: keep-alive -< Server: ATS/`` -< -`` diff --git a/tests/gold_tests/remap_yaml/map_with_recv_port_yaml.test.py b/tests/gold_tests/remap_yaml/map_with_recv_port_yaml.test.py deleted file mode 100644 index 5513533a1f2..00000000000 --- a/tests/gold_tests/remap_yaml/map_with_recv_port_yaml.test.py +++ /dev/null @@ -1,87 +0,0 @@ -''' -Verify correct behavior of regex_map in remap.config. -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = ''' -Verify correct behavior of map_with_recv_port in remap.config. -''' - -Test.ContinueOnFail = True -ts = Test.MakeATSProcess("ts") -server = Test.MakeOriginServer("server") -dns = Test.MakeDNServer("dns", default='127.0.0.1') - -Test.testName = "" -request_header_ip = {"headers": "GET /ip HTTP/1.1\r\nHost: origin.example.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""} -response_header_ip = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": "ip"} -request_header_unix = { - "headers": "GET /unix HTTP/1.1\r\nHost: origin.example.com\r\n\r\n", - "timestamp": "1469733493.993", - "body": "" -} -response_header_unix = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": "unix"} -request_header_error = { - "headers": "GET /error HTTP/1.1\r\nHost: origin.example.com\r\n\r\n", - "timestamp": "1469733493.993", - "body": "" -} -response_header_error = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": "error"} -server.addResponse("sessionfile.log", request_header_ip, response_header_ip) -server.addResponse("sessionfile.log", request_header_unix, response_header_unix) -server.addResponse("sessionfile.log", request_header_error, response_header_error) - -ts.Disk.records_config.update( - { - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'http|dns', - 'proxy.config.dns.nameservers': '127.0.0.1:{0}'.format(dns.Variables.Port), - 'proxy.config.dns.resolv_conf': 'NULL' - }) - -# This map rule should not match -ts.Disk.remap_yaml.AddLines( - f''' -remap: - - type: map - from: - url: http://test.example.com - to: - url: http://origin.example.com:{server.Variables.Port}/error - - type: map_with_recv_port - from: - url: http://test.example.com:{ts.Variables.port}/ - to: - url: http://origin.example.com:{server.Variables.Port}/ip - - type: map_with_recv_port - from: - url: http+unix://test.example.com - to: - url: http://origin.example.com:{server.Variables.Port}/unix - '''.split("\n")) - -tr = Test.AddTestRun() -tr.MakeCurlCommand('-H"Host: test.example.com" http://127.0.0.1:{0}/ --verbose'.format(ts.Variables.port), ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.StartBefore(server) -tr.Processes.Default.StartBefore(dns) -tr.Processes.Default.StartBefore(Test.Processes.ts) -if Condition.CurlUsingUnixDomainSocket(): - tr.Processes.Default.Streams.stderr = "gold/map-with-recv-port-unix.gold" -else: - tr.Processes.Default.Streams.stderr = "gold/map-with-recv-port-ip.gold" -tr.StillRunningAfter = server diff --git a/tests/gold_tests/remap_yaml/regex_map_yaml.test.py b/tests/gold_tests/remap_yaml/regex_map_yaml.test.py deleted file mode 100644 index abecde2ee3d..00000000000 --- a/tests/gold_tests/remap_yaml/regex_map_yaml.test.py +++ /dev/null @@ -1,65 +0,0 @@ -''' -Verify correct behavior of regex_map in remap.yaml. -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = ''' -Verify correct behavior of regex_map in remap.config. -''' - -Test.ContinueOnFail = True -ts = Test.MakeATSProcess("ts") -server = Test.MakeOriginServer("server") -dns = Test.MakeDNServer("dns", default='127.0.0.1') - -Test.testName = "" -request_header = {"headers": "GET / HTTP/1.1\r\nHost: zero.one.two.three.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""} -response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""} -server.addResponse("sessionfile.log", request_header, response_header) - -ts.Disk.records_config.update( - { - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'http.*|dns|conf_remap', - 'proxy.config.http.referer_filter': 1, - 'proxy.config.dns.nameservers': '127.0.0.1:{0}'.format(dns.Variables.Port), - 'proxy.config.dns.resolv_conf': 'NULL' - }) - -ts.Disk.remap_yaml.AddLines( - rf''' -remap: - - type: regex_map - from: - url: http://(.*)?one\.two\.three\.com/ - to: - url: http://$1reactivate.four.five.six.com:{server.Variables.Port}/ - - type: regex_map - from: - url: https://\b(?!(.*one|two|three|four|five|six)).+\b\.seven\.eight\.nine\.com/blah12345.html - to: - url: https://www.example.com:{server.Variables.Port}/one/two/three/blah12345.html - '''.split("\n")) - -tr = Test.AddTestRun() -tr.MakeCurlCommand('-H"Host: zero.one.two.three.com" http://127.0.0.1:{0}/ --verbose'.format(ts.Variables.port), ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.StartBefore(server) -tr.Processes.Default.StartBefore(dns) -tr.Processes.Default.StartBefore(Test.Processes.ts) -tr.Processes.Default.Streams.stderr = "gold/remap-zero-200.gold" -tr.StillRunningAfter = server diff --git a/tests/gold_tests/remap_yaml/reload_1.replay.yaml b/tests/gold_tests/remap_yaml/reload_1.replay.yaml deleted file mode 100644 index a65574262e2..00000000000 --- a/tests/gold_tests/remap_yaml/reload_1.replay.yaml +++ /dev/null @@ -1,39 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# -# This replay file assumes that caching is enabled and -# proxy.config.http.cache.ignore_client_cc_max_age is set to 0 so that we can -# test max-age in the client requests. -# - -meta: - version: "1.0" - -sessions: -- transactions: - - all: { headers: { fields: [[ uuid, success ]]}} - client-request: - method: "GET" - version: "1.1" - scheme: "http" - url: /index.html - headers: - fields: - - [ Host, charlie.ex ] - - proxy-response: - status: 200 diff --git a/tests/gold_tests/remap_yaml/reload_2.replay.yaml b/tests/gold_tests/remap_yaml/reload_2.replay.yaml deleted file mode 100644 index a65574262e2..00000000000 --- a/tests/gold_tests/remap_yaml/reload_2.replay.yaml +++ /dev/null @@ -1,39 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# -# This replay file assumes that caching is enabled and -# proxy.config.http.cache.ignore_client_cc_max_age is set to 0 so that we can -# test max-age in the client requests. -# - -meta: - version: "1.0" - -sessions: -- transactions: - - all: { headers: { fields: [[ uuid, success ]]}} - client-request: - method: "GET" - version: "1.1" - scheme: "http" - url: /index.html - headers: - fields: - - [ Host, charlie.ex ] - - proxy-response: - status: 200 diff --git a/tests/gold_tests/remap_yaml/reload_3.replay.yaml b/tests/gold_tests/remap_yaml/reload_3.replay.yaml deleted file mode 100644 index f4ca866e6ff..00000000000 --- a/tests/gold_tests/remap_yaml/reload_3.replay.yaml +++ /dev/null @@ -1,42 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# -# This replay file assumes that caching is enabled and -# proxy.config.http.cache.ignore_client_cc_max_age is set to 0 so that we can -# test max-age in the client requests. -# - -meta: - version: "1.0" - -sessions: -- transactions: - - all: { headers: { fields: [[ uuid, success ]]}} - client-request: - method: "GET" - version: "1.1" - scheme: "http" - url: /index.html - headers: - fields: - - [ Host, charlie.ex ] - - proxy-request: - expect: absent - - proxy-response: - status: 404 diff --git a/tests/gold_tests/remap_yaml/reload_4.replay.yaml b/tests/gold_tests/remap_yaml/reload_4.replay.yaml deleted file mode 100644 index fc1e00261de..00000000000 --- a/tests/gold_tests/remap_yaml/reload_4.replay.yaml +++ /dev/null @@ -1,39 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# -# This replay file assumes that caching is enabled and -# proxy.config.http.cache.ignore_client_cc_max_age is set to 0 so that we can -# test max-age in the client requests. -# - -meta: - version: "1.0" - -sessions: -- transactions: - - all: { headers: { fields: [[ uuid, success ]]}} - client-request: - method: "GET" - version: "1.1" - scheme: "http" - url: /index.html - headers: - fields: - - [ Host, golf.ex ] - - proxy-response: - status: 200 diff --git a/tests/gold_tests/remap_yaml/reload_server.replay.yaml b/tests/gold_tests/remap_yaml/reload_server.replay.yaml deleted file mode 100644 index c7045e63fb2..00000000000 --- a/tests/gold_tests/remap_yaml/reload_server.replay.yaml +++ /dev/null @@ -1,51 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# -# This replay file assumes that caching is enabled and -# proxy.config.http.cache.ignore_client_cc_max_age is set to 0 so that we can -# test max-age in the client requests. -# - -meta: - version: "1.0" - - blocks: - - 200_ok_response: &200_ok_response - server-response: - status: 200 - reason: OK - headers: - fields: - - [ Content-Length, 16 ] - - [ Cache-Control, max-age=300 ] - -sessions: -- transactions: - - all: { headers: { fields: [[ uuid, success ]]}} - client-request: - method: "GET" - version: "1.1" - scheme: "http" - url: /index.html - headers: - fields: - - [ Host, charlie.ex ] - - <<: *200_ok_response - - proxy-response: - status: 200 diff --git a/tests/gold_tests/remap_yaml/remap_acl_all_allowed.replay.yaml b/tests/gold_tests/remap_yaml/remap_acl_all_allowed.replay.yaml deleted file mode 100644 index 952b348de94..00000000000 --- a/tests/gold_tests/remap_yaml/remap_acl_all_allowed.replay.yaml +++ /dev/null @@ -1,119 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This expects a remap.config to allow all requests. - -meta: - version: "1.0" - - blocks: - - standard_response: &standard_response - server-response: - status: 200 - reason: OK - headers: - fields: - - [ Content-Length, 20 ] - -sessions: -- protocol: - stack: http - transactions: - - - client-request: - method: "GET" - version: "1.1" - url: /test/ip_allow/test_get - headers: - fields: - - [ Content-Length, 0 ] - - [ uuid, get ] - - [ X-Request, get ] - - <<: *standard_response - - proxy-response: - status: 200 - - - client-request: - method: "POST" - version: "1.1" - url: /test/ip_allow/test_post - headers: - fields: - - [Content-Length, 10] - - [ uuid, post ] - - [ X-Request, post ] - - <<: *standard_response - - proxy-response: - status: 200 - - - client-request: - method: "PUT" - version: "1.1" - url: /test/ip_allow/test_put - headers: - fields: - - [ Host, example.com ] - - [ uuid, put ] - - [ X-Request, put ] - - [ Content-Length, 113 ] - content: - encoding: plain - data: "HTTP/1.1 200 OK\nServer: ATS/10.0.0\nAccept-Ranges: bytes\nContent-Length: 6\nCache-Control: public,max-age=2\n\nCACHED" - - # Not received. - <<: *standard_response - - proxy-response: - status: 200 - - - client-request: - method: "DELETE" - version: "1.1" - url: /test/ip_allow/test_delete - headers: - fields: - - [ Host, example.com ] - - [ uuid, delete ] - - [ X-Request, delete ] - - [ Content-Length, 0 ] - - <<: *standard_response - - proxy-response: - status: 200 - - - client-request: - method: "PUSH" - version: "1.1" - url: /test/ip_allow/test_push - headers: - fields: - - [ Host, example.com ] - - [ uuid, push ] - - [ X-Request, push ] - - [ Content-Length, 113 ] - content: - encoding: plain - data: "HTTP/1.1 200 OK\nServer: ATS/10.0.0\nAccept-Ranges: bytes\nContent-Length: 6\nCache-Control: public,max-age=2\n\nCACHED" - - <<: *standard_response - - proxy-response: - status: 400 diff --git a/tests/gold_tests/remap_yaml/remap_acl_all_denied.replay.yaml b/tests/gold_tests/remap_yaml/remap_acl_all_denied.replay.yaml deleted file mode 100644 index 6c0f6186a1e..00000000000 --- a/tests/gold_tests/remap_yaml/remap_acl_all_denied.replay.yaml +++ /dev/null @@ -1,123 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This expects a remap.config that denies all request methods. - -meta: - version: "1.0" - - blocks: - - standard_response: &standard_response - server-response: - status: 200 - reason: OK - headers: - fields: - - [ Content-Length, 20 ] - -sessions: -- protocol: - stack: http - transactions: - - - client-request: - method: "GET" - version: "1.1" - url: /test/ip_allow/test_get - headers: - fields: - - [ Content-Length, 0 ] - - [ uuid, get ] - - [ X-Request, get ] - - # Not received. - <<: *standard_response - - proxy-response: - status: 403 - - - client-request: - method: "POST" - version: "1.1" - url: /test/ip_allow/test_post - headers: - fields: - - [Content-Length, 10] - - [ uuid, post ] - - [ X-Request, post ] - - # Not received. - <<: *standard_response - - proxy-response: - status: 403 - - - client-request: - method: "PUT" - version: "1.1" - url: /test/ip_allow/test_put - headers: - fields: - - [ Host, example.com ] - - [ uuid, put ] - - [ X-Request, put ] - - [ Content-Length, 113 ] - content: - encoding: plain - data: "HTTP/1.1 200 OK\nServer: ATS/10.0.0\nAccept-Ranges: bytes\nContent-Length: 6\nCache-Control: public,max-age=2\n\nCACHED" - - # Not received. - <<: *standard_response - - proxy-response: - status: 403 - - # DELETE rejected - - client-request: - method: "DELETE" - version: "1.1" - url: /test/ip_allow/test_delete - headers: - fields: - - [ Host, example.com ] - - [ uuid, delete ] - - [ X-Request, delete ] - - [ Content-Length, 0 ] - - <<: *standard_response - - proxy-response: - status: 403 - - # PUSH rejected - - client-request: - method: "PUSH" - version: "1.1" - url: /test/ip_allow/test_push - headers: - fields: - - [ Host, example.com ] - - [ uuid, push ] - - [ X-Request, push ] - - [ Content-Length, 113 ] - content: - encoding: plain - data: "HTTP/1.1 200 OK\nServer: ATS/10.0.0\nAccept-Ranges: bytes\nContent-Length: 6\nCache-Control: public,max-age=2\n\nCACHED" - - <<: *standard_response - - proxy-response: - status: 403 diff --git a/tests/gold_tests/remap_yaml/remap_acl_get_allowed.replay.yaml b/tests/gold_tests/remap_yaml/remap_acl_get_allowed.replay.yaml deleted file mode 100644 index 087e5b3f66b..00000000000 --- a/tests/gold_tests/remap_yaml/remap_acl_get_allowed.replay.yaml +++ /dev/null @@ -1,127 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This expects a remap.config that allows GET, but denies all other -# methods. - -meta: - version: "1.0" - - blocks: - - standard_response: &standard_response - server-response: - status: 200 - reason: OK - headers: - fields: - - [ Content-Length, 20 ] - -sessions: -- protocol: - stack: http - transactions: - - - client-request: - method: "GET" - version: "1.1" - url: /test/ip_allow/test_get - headers: - fields: - - [ Content-Length, 0 ] - - [ uuid, get ] - - [ X-Request, get ] - - <<: *standard_response - - proxy-response: - status: 200 - - # POST rejected - - client-request: - method: "POST" - version: "1.1" - url: /test/ip_allow/test_post - headers: - fields: - - [Content-Length, 10] - - [ uuid, post ] - - [ X-Request, post ] - - <<: *standard_response - - proxy-response: - status: 403 - - # PUT rejected - - client-request: - method: "PUT" - version: "1.1" - url: /test/ip_allow/test_put - headers: - fields: - - [ Host, example.com ] - - [ uuid, put ] - - [ X-Request, put ] - - [ Content-Length, 113 ] - content: - encoding: plain - data: "HTTP/1.1 200 OK\nServer: ATS/10.0.0\nAccept-Ranges: bytes\nContent-Length: 6\nCache-Control: public,max-age=2\n\nCACHED" - - # Not received. - <<: *standard_response - - # Verify that ATS rejected the PUSH. - proxy-response: - status: 403 - - # DELETE rejected - - client-request: - method: "DELETE" - version: "1.1" - url: /test/ip_allow/test_delete - headers: - fields: - - [ Host, example.com ] - - [ uuid, delete ] - - [ X-Request, delete ] - - [ Content-Length, 0 ] - - <<: *standard_response - - # Verify that ATS rejects the DELETE. - proxy-response: - status: 403 - - # PUSH rejected - - client-request: - method: "PUSH" - version: "1.1" - url: /test/ip_allow/test_push - headers: - fields: - - [ Host, example.com ] - - [ uuid, push ] - - [ X-Request, push ] - - [ Content-Length, 113 ] - content: - encoding: plain - data: "HTTP/1.1 200 OK\nServer: ATS/10.0.0\nAccept-Ranges: bytes\nContent-Length: 6\nCache-Control: public,max-age=2\n\nCACHED" - - <<: *standard_response - - # Verify that ATS rejected the PUSH. - proxy-response: - status: 403 diff --git a/tests/gold_tests/remap_yaml/remap_acl_get_post_allowed.replay.yaml b/tests/gold_tests/remap_yaml/remap_acl_get_post_allowed.replay.yaml deleted file mode 100644 index 2dbcb636341..00000000000 --- a/tests/gold_tests/remap_yaml/remap_acl_get_post_allowed.replay.yaml +++ /dev/null @@ -1,127 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This expects a remap.config that allows GET and POST, but denies all other -# methods. - -meta: - version: "1.0" - - blocks: - - standard_response: &standard_response - server-response: - status: 200 - reason: OK - headers: - fields: - - [ Content-Length, 20 ] - -sessions: -- protocol: - stack: http - transactions: - - - client-request: - method: "GET" - version: "1.1" - url: /test/ip_allow/test_get - headers: - fields: - - [ Content-Length, 0 ] - - [ uuid, get ] - - [ X-Request, get ] - - <<: *standard_response - - proxy-response: - status: 200 - - # POST also is in the allow list. - - client-request: - method: "POST" - version: "1.1" - url: /test/ip_allow/test_post - headers: - fields: - - [Content-Length, 10] - - [ uuid, post ] - - [ X-Request, post ] - - <<: *standard_response - - proxy-response: - status: 200 - - # PUT rejected - - client-request: - method: "PUT" - version: "1.1" - url: /test/ip_allow/test_put - headers: - fields: - - [ Host, example.com ] - - [ uuid, put ] - - [ X-Request, put ] - - [ Content-Length, 113 ] - content: - encoding: plain - data: "HTTP/1.1 200 OK\nServer: ATS/10.0.0\nAccept-Ranges: bytes\nContent-Length: 6\nCache-Control: public,max-age=2\n\nCACHED" - - # Not received. - <<: *standard_response - - # Verify that ATS rejected the PUSH. - proxy-response: - status: 403 - - # DELETE rejected - - client-request: - method: "DELETE" - version: "1.1" - url: /test/ip_allow/test_delete - headers: - fields: - - [ Host, example.com ] - - [ uuid, delete ] - - [ X-Request, delete ] - - [ Content-Length, 0 ] - - <<: *standard_response - - # Verify that ATS rejects the DELETE. - proxy-response: - status: 403 - - # PUSH rejected - - client-request: - method: "PUSH" - version: "1.1" - url: /test/ip_allow/test_push - headers: - fields: - - [ Host, example.com ] - - [ uuid, push ] - - [ X-Request, push ] - - [ Content-Length, 113 ] - content: - encoding: plain - data: "HTTP/1.1 200 OK\nServer: ATS/10.0.0\nAccept-Ranges: bytes\nContent-Length: 6\nCache-Control: public,max-age=2\n\nCACHED" - - <<: *standard_response - - # Verify that ATS rejected the PUSH. - proxy-response: - status: 403 diff --git a/tests/gold_tests/remap_yaml/remap_acl_get_post_allowed_pp.replay.yaml b/tests/gold_tests/remap_yaml/remap_acl_get_post_allowed_pp.replay.yaml deleted file mode 100644 index 91907bb6ed0..00000000000 --- a/tests/gold_tests/remap_yaml/remap_acl_get_post_allowed_pp.replay.yaml +++ /dev/null @@ -1,131 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This expects a remap.config that allows GET and POST, but denies all other -# methods. - -meta: - version: "1.0" - - blocks: - - standard_response: &standard_response - server-response: - status: 200 - reason: OK - headers: - fields: - - [ Content-Length, 20 ] - -sessions: -- protocol: - stack: http - proxy-protocol: - version: 2 - src-addr: "1.2.3.4:1111" - dst-addr: "5.6.7.8:2222" - transactions: - - - client-request: - method: "GET" - version: "1.1" - url: /test/ip_allow/test_get - headers: - fields: - - [ Content-Length, 0 ] - - [ uuid, get ] - - [ X-Request, get ] - - <<: *standard_response - - proxy-response: - status: 200 - - # POST also is in the allow list. - - client-request: - method: "POST" - version: "1.1" - url: /test/ip_allow/test_post - headers: - fields: - - [Content-Length, 10] - - [ uuid, post ] - - [ X-Request, post ] - - <<: *standard_response - - proxy-response: - status: 200 - - # PUT rejected - - client-request: - method: "PUT" - version: "1.1" - url: /test/ip_allow/test_put - headers: - fields: - - [ Host, example.com ] - - [ uuid, put ] - - [ X-Request, put ] - - [ Content-Length, 113 ] - content: - encoding: plain - data: "HTTP/1.1 200 OK\nServer: ATS/10.0.0\nAccept-Ranges: bytes\nContent-Length: 6\nCache-Control: public,max-age=2\n\nCACHED" - - # Not received. - <<: *standard_response - - # Verify that ATS rejected the PUSH. - proxy-response: - status: 403 - - # DELETE rejected - - client-request: - method: "DELETE" - version: "1.1" - url: /test/ip_allow/test_delete - headers: - fields: - - [ Host, example.com ] - - [ uuid, delete ] - - [ X-Request, delete ] - - [ Content-Length, 0 ] - - <<: *standard_response - - # Verify that ATS rejects the DELETE. - proxy-response: - status: 403 - - # PUSH rejected - - client-request: - method: "PUSH" - version: "1.1" - url: /test/ip_allow/test_push - headers: - fields: - - [ Host, example.com ] - - [ uuid, push ] - - [ X-Request, push ] - - [ Content-Length, 113 ] - content: - encoding: plain - data: "HTTP/1.1 200 OK\nServer: ATS/10.0.0\nAccept-Ranges: bytes\nContent-Length: 6\nCache-Control: public,max-age=2\n\nCACHED" - - <<: *standard_response - - # Verify that ATS rejected the PUSH. - proxy-response: - status: 403 diff --git a/tests/gold_tests/remap_yaml/remap_acl_get_post_denied.replay.yaml b/tests/gold_tests/remap_yaml/remap_acl_get_post_denied.replay.yaml deleted file mode 100644 index cb2db9766d9..00000000000 --- a/tests/gold_tests/remap_yaml/remap_acl_get_post_denied.replay.yaml +++ /dev/null @@ -1,121 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This expects a remap.config that denies GET and POST, but allows all other -# methods. - -meta: - version: "1.0" - - blocks: - - standard_response: &standard_response - server-response: - status: 200 - reason: OK - headers: - fields: - - [ Content-Length, 20 ] - -sessions: -- protocol: - stack: http - transactions: - - - client-request: - method: "GET" - version: "1.1" - url: /test/ip_allow/test_get - headers: - fields: - - [ Content-Length, 0 ] - - [ uuid, get ] - - [ X-Request, get ] - - <<: *standard_response - - proxy-response: - status: 403 - - - client-request: - method: "POST" - version: "1.1" - url: /test/ip_allow/test_post - headers: - fields: - - [Content-Length, 10] - - [ uuid, post ] - - [ X-Request, post ] - - <<: *standard_response - - proxy-response: - status: 403 - - # All other methods are allowed. - - client-request: - method: "PUT" - version: "1.1" - url: /test/ip_allow/test_put - headers: - fields: - - [ Host, example.com ] - - [ uuid, put ] - - [ X-Request, put ] - - [ Content-Length, 113 ] - content: - encoding: plain - data: "HTTP/1.1 200 OK\nServer: ATS/10.0.0\nAccept-Ranges: bytes\nContent-Length: 6\nCache-Control: public,max-age=2\n\nCACHED" - - <<: *standard_response - - proxy-response: - status: 200 - - - client-request: - method: "DELETE" - version: "1.1" - url: /test/ip_allow/test_delete - headers: - fields: - - [ Host, example.com ] - - [ uuid, delete ] - - [ X-Request, delete ] - - [ Content-Length, 0 ] - - <<: *standard_response - - proxy-response: - status: 200 - - - client-request: - method: "PUSH" - version: "1.1" - url: /test/ip_allow/test_push - headers: - fields: - - [ Host, example.com ] - - [ uuid, push ] - - [ X-Request, push ] - - [ Content-Length, 113 ] - content: - encoding: plain - data: "HTTP/1.1 200 OK\nServer: ATS/10.0.0\nAccept-Ranges: bytes\nContent-Length: 6\nCache-Control: public,max-age=2\n\nCACHED" - - <<: *standard_response - - # ATS will allow the PUSH, but issue a 400 saying that it can't cache it. - proxy-response: - status: 400 diff --git a/tests/gold_tests/remap_yaml/remap_acl_yaml.test.py b/tests/gold_tests/remap_yaml/remap_acl_yaml.test.py deleted file mode 100644 index a200fa229a7..00000000000 --- a/tests/gold_tests/remap_yaml/remap_acl_yaml.test.py +++ /dev/null @@ -1,662 +0,0 @@ -''' -Verify remap.yaml acl behavior. -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# # http://www.apache.org/licenses/LICENSE-2.0 # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import io -import re -import inspect -import tempfile -from yaml import load, dump -from yaml import CLoader as Loader -from typing import List, Tuple - -from ports import get_port - -Test.Summary = ''' -Verify remap.yaml acl behavior. -''' - - -def update_config_file(path1: str, content1: str, path2: str, content2: str) -> None: - """Update two config files. - - This is used for some of the updates to the config files between test runs. - - :param path1: The path to the first config file. - :param content1: The content to write to the first config file. - :param path2: The path to the second config file. - :param content2: The content to write to the second config file. - """ - with open(path1, 'w') as f: - f.write(content1 + '\n') - with open(path2, 'w') as f: - f.write(content2 + '\n') - - -class Test_remap_acl: - """Configure a test to verify remap.config acl behavior.""" - - _ts: 'TestProcess' = None - _ts_reload_counter: int = 0 - _ts_is_started: bool = False - - _server_counter: int = 0 - _client_counter: int = 0 - - def __init__( - self, name: str, replay_file: str, ip_allow_content: str, deactivate_ip_allow: bool, acl_behavior_policy: int, - acl_configuration: List[str], named_acls: List[Tuple[str, - List[str]]], expected_responses: List[int], proxy_protocol: bool): - """Initialize the test. - - :param name: The name of the test. - :param replay_file: The replay file to be used. - :param ip_allow_content: The ip_allow configuration to be used. - :param deactivate_ip_allow: Whether to deactivate the ip_allow filter. - :param acl_configuration: The ACL configuration to be used. - :param named_acls: The set of named ACLs to configure and use. - :param expect_responses: The in-order expected responses from the proxy. - """ - self._replay_file = replay_file - self._ip_allow_lines = ip_allow_content.split("\n") - self._deactivate_ip_allow = deactivate_ip_allow - self._acl_behavior_policy = acl_behavior_policy - self._acl_configuration = acl_configuration - self._named_acls = named_acls - self._expected_responses = expected_responses - - # Usually we configure the server first and use the server port to - # configure ATS to remap to it. In this case, though, we want a - # long-lived ATS process that spans TestRuns. So we let ATS choose an - # arbitrary availble server port, and then tell the TestRun-specific - # server to use that port. - server_port = self._configure_traffic_server() - tr = Test.AddTestRun(name) - self._configure_server(tr, server_port) - self._configure_client(tr, proxy_protocol) - - def _configure_server(self, tr: 'TestRun', server_port: int) -> None: - """Configure the server. - """ - name = f"server-{Test_remap_acl._server_counter}" - server = tr.AddVerifierServerProcess(name, self._replay_file, http_ports=[server_port]) - Test_remap_acl._server_counter += 1 - self._server = server - - def _configure_traffic_server(self) -> int: - """Configure Traffic Server. - - :return: The listening port that the server should use. - """ - - call_reload: bool = False - if Test_remap_acl._ts is not None: - ts = Test_remap_acl._ts - call_reload = True - else: - ts = Test.MakeATSProcess("ts", enable_cache=False, enable_proxy_protocol=True, enable_uds=False) - Test_remap_acl._ts = ts - self._ts = ts - port_name = f'ServerPort-{Test_remap_acl._ts_reload_counter}' - server_port: int = get_port(ts, port_name) - - remap_yaml_lines = ['remap:'] - if self._deactivate_ip_allow: - remap_yaml_lines.append('- deactivate_filter: ip_allow') - - # First, define the name ACLs (filters). - defined_filters = [] - for name, definition in self._named_acls: - if len(definition) > 0: - remap_yaml_lines.append('- define_filter:') - remap_yaml_lines.append(f' {name}:') - for d in definition: - remap_yaml_lines.append(f' {d}') - defined_filters.append(name) - # Now activate them. - for name in defined_filters: - remap_yaml_lines.append(f'- activate_filter: {name}') - - remap_yaml_lines.append('- type: map') - remap_yaml_lines.append(' from:') - remap_yaml_lines.append(' url: /') - remap_yaml_lines.append(' to:') - remap_yaml_lines.append(f' url: http://127.0.0.1:{server_port}') - - if len(self._acl_configuration) > 0: - remap_yaml_lines.append(' acl_filter:') - for f in self._acl_configuration: - remap_yaml_lines.append(f' {f}') - - if call_reload: - # - # Update the ATS configuration. - # - tr = Test.AddTestRun("Change the ATS configuration") - p = tr.Processes.Default - p.Command = ( - f'traffic_ctl config set proxy.config.http.connect_ports {server_port} && ' - f'traffic_ctl config set proxy.config.url_remap.acl_behavior_policy {self._acl_behavior_policy}') - - p.Env = ts.Env - tr.StillRunningAfter = ts - - remap_yaml_path = os.path.join(ts.Variables.CONFIGDIR, 'remap.yaml') - ip_allow_path = os.path.join(ts.Variables.CONFIGDIR, 'ip_allow.yaml') - p.Setup.Lambda( - lambda: update_config_file( - remap_yaml_path, '\n'.join(remap_yaml_lines), ip_allow_path, '\n'.join(self._ip_allow_lines))) - - # - # Kick off the ATS config reload. - # - tr = Test.AddTestRun("Reload the ATS configuration") - p = tr.Processes.Default - p.Command = 'traffic_ctl config reload' - p.Env = ts.Env - tr.StillRunningAfter = ts - - # - # Await the config reload to finish. - # - tr = Test.AddTestRun("Await config reload") - p = tr.Processes.Default - p.Command = 'echo awaiting config reload' - p.Env = ts.Env - Test_remap_acl._ts_reload_counter += 1 - count = Test_remap_acl._ts_reload_counter - await_config_reload = tr.Processes.Process(f'config_reload_succeeded_{count}', 'sleep 30') - await_config_reload.Ready = When.FileContains(ts.Disk.diags_log.Name, "remap.yaml finished loading", count) - p.StartBefore(await_config_reload) - - else: - record_config = { - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'http|url|remap|ip_allow|proxyprotocol', - 'proxy.config.http.push_method_enabled': 1, - 'proxy.config.http.connect_ports': server_port, - 'proxy.config.url_remap.acl_behavior_policy': self._acl_behavior_policy, - 'proxy.config.acl.subjects': 'PROXY,PEER', - } - - ts.Disk.records_config.update(record_config) - ts.Disk.remap_yaml.AddLines(remap_yaml_lines) - ts.Disk.ip_allow_yaml.AddLines(self._ip_allow_lines) - - return server_port - - def _configure_client(self, tr: 'TestRun', proxy_protocol: bool) -> None: - """Run the test. - - :param tr: The TestRun object to associate the client process with. - """ - - name = f"client-{Test_remap_acl._client_counter}" - ts = Test_remap_acl._ts - port = ts.Variables.port if proxy_protocol == False else ts.Variables.proxy_protocol_port - p = tr.AddVerifierClientProcess(name, self._replay_file, http_ports=[port]) - Test_remap_acl._client_counter += 1 - p.StartBefore(self._server) - if not Test_remap_acl._ts_is_started: - p.StartBefore(ts) - Test_remap_acl._ts_is_started = True - - if self._expected_responses == [None, None]: - # If there are no expected responses, expect the Warning about the rejected ip. - self._ts.Disk.diags_log.Content += Testers.ContainsExpression( - "client '127.0.0.1' prohibited by ip-allow policy", "Verify the client rejection warning message.") - - # Also, the client will complain about the broken connections. - p.ReturnCode = 1 - - else: - codes = [str(code) for code in self._expected_responses] - p.Streams.stdout += Testers.ContainsExpression( - '.*'.join(codes), "Verifying the expected order of responses", reflags=re.DOTALL | re.MULTILINE) - - -class Test_old_action: - _ts_counter: int = 0 - - def __init__(self, name: str, acl_filter: List[str], ip_allow_content: str) -> None: - '''Test that ATS fails with a FATAL message if an old action is used with modern ACL filter policy. - - :param name: The name of the test run. - :param acl_filter: The ACL filter to use. - :param ip_allow_content: The ip_allow configuration to use. - ''' - - tr = Test.AddTestRun(name) - ts = self._configure_traffic_server(tr, acl_filter, ip_allow_content) - - def _configure_traffic_server(self, tr: 'TestRun', acl_filter: List[str], ip_allow_content: str) -> 'Process': - '''Configure Traffic Server process - - :param tr: The TestRun object to associate the Traffic Server process with. - :param acl_filter: The ACL filter to configure in remap.config. - :param ip_allow_content: The ip_allow configuration to use. - :return: The Traffic Server process. - ''' - name = f"ts-old-action-{Test_old_action._ts_counter}" - Test_old_action._ts_counter += 1 - ts = tr.MakeATSProcess(name, enable_uds=False) - self._ts = ts - - ts.Disk.records_config.update( - { - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'http|url|remap|ip_allow', - 'proxy.config.url_remap.acl_behavior_policy': 1, - }) - - ts.Disk.remap_yaml.AddLines( - f''' -remap: - - type: map - from: - url: / - to: - url: http://127.0.0.1:8080 - acl_filter: '''.split("\n")) - - for f in acl_filter: - ts.Disk.remap_yaml.AddLine(f' {f}') - - if ip_allow_content: - ts.Disk.ip_allow_yaml.AddLines(ip_allow_content.split("\n")) - - if len(acl_filter) > 0: - expected_error = '"allow" and "deny" are no longer valid.' - else: - expected_error = 'Legacy action name of' - - # We have to wait upon TS to emit the expected log message, but it cannot be - # the ts Ready criteria because autest might detect the process going away - # before it detects the log message. So we add a separate process that waits - # upon the log message. - watcher = tr.Processes.Process("watcher") - watcher.Command = "sleep 10" - watcher.Ready = When.FileContains(ts.Disk.diags_log.Name, expected_error) - watcher.StartBefore(ts) - - tr.Processes.Default.Command = 'printf "Fatal Shutdown Test"' - tr.Processes.Default.ReturnCode = 0 - tr.Processes.Default.StartBefore(watcher) - - tr.Timeout = 5 - ts.ReturnCode = Any(33, 70) - ts.Ready = 0 - ts.Disk.diags_log.Content = Testers.IncludesExpression(expected_error, 'ATS should fatal with the old actions.') - - return ts - - -IP_ALLOW_OLD_ACTION = f''' -ip_categories: - - name: ACME_LOCAL - ip_addrs: 127.0.0.1 - - name: ACME_EXTERNAL - ip_addrs: 5.6.7.8 - -ip_allow: - - apply: in - ip_addrs: 0/0 - action: allow - methods: - - GET -''' - -IP_ALLOW_CONTENT = f''' -ip_categories: - - name: ACME_LOCAL - ip_addrs: 127.0.0.1 - - name: ACME_EXTERNAL - ip_addrs: 5.6.7.8 - -ip_allow: - - apply: in - ip_addrs: 0/0 - action: set_allow - methods: - - GET -''' - -Test_old_action("Verify allow is reject in modern policy", ['action: allow', 'method: GET'], IP_ALLOW_CONTENT) -Test_old_action("Verify deny is reject in modern policy", ['action: deny', 'method: GET'], IP_ALLOW_CONTENT) -Test_old_action("Verify deny is reject in modern policy", [], IP_ALLOW_OLD_ACTION) - -test_ip_allow_optional_methods = Test_remap_acl( - "Verify non-allowed methods are blocked.", - replay_file='remap_acl_get_post_allowed.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - acl_configuration=['action: set_allow', 'src_ip: 127.0.0.1', 'method: [GET, POST]'], - named_acls=[], - expected_responses=[200, 200, 403, 403, 403], - proxy_protocol=False) - -test_ip_allow_optional_methods_pp = Test_remap_acl( - "Verify non-allowed methods are blocked (PP).", - replay_file='remap_acl_get_post_allowed_pp.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - acl_configuration=['action: set_allow', 'src_ip: 1.2.3.4', 'method: [GET, POST]'], - named_acls=[], - expected_responses=[200, 200, 403, 403, 403], - proxy_protocol=True) - -test_ip_allow_optional_methods = Test_remap_acl( - "Verify add_allow adds an allowed method.", - replay_file='remap_acl_get_post_allowed.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - acl_configuration=['action: add_allow', 'src_ip: 127.0.0.1', 'method: POST'], - named_acls=[], - expected_responses=[200, 200, 403, 403, 403], - proxy_protocol=False) - -test_ip_allow_optional_methods = Test_remap_acl( - "Verify add_allow adds allowed methods.", - replay_file='remap_acl_get_post_allowed.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - acl_configuration=['action: add_allow', 'src_ip: 127.0.0.1', 'method: [GET, POST]'], - named_acls=[], - expected_responses=[200, 200, 403, 403, 403], - proxy_protocol=False) - -test_ip_allow_optional_methods = Test_remap_acl( - "Verify if no ACLs match, ip_allow.yaml is used.", - replay_file='remap_acl_get_allowed.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - acl_configuration=['action: set_allow', 'src_ip: 1.2.3.4', 'method: [GET, POST]'], - named_acls=[], - expected_responses=[200, 403, 403, 403, 403], - proxy_protocol=False) - -test_ip_allow_optional_methods = Test_remap_acl( - "Verify @src_ip=all works.", - replay_file='remap_acl_get_post_allowed.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - acl_configuration=['action: set_allow', 'src_ip: all', 'method: [GET, POST]'], - named_acls=[], - expected_responses=[200, 200, 403, 403, 403], - proxy_protocol=False) - -test_ip_allow_optional_methods = Test_remap_acl( - "Verify @src_ip_category works.", - replay_file='remap_acl_get_post_allowed.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - acl_configuration=['action: set_allow', 'src_ip_category: ACME_LOCAL', 'method: [GET, POST]'], - named_acls=[], - expected_responses=[200, 200, 403, 403, 403], - proxy_protocol=False) - -test_ip_allow_optional_methods = Test_remap_acl( - "Verify no @src_ip implies all IP addresses.", - replay_file='remap_acl_get_post_allowed.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - acl_configuration=['action: set_allow', 'method: [GET, POST]'], - named_acls=[], - expected_responses=[200, 200, 403, 403, 403], - proxy_protocol=False) - -test_ip_allow_optional_methods = Test_remap_acl( - "Verify denied methods are blocked.", - replay_file='remap_acl_get_post_denied.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - acl_configuration=['action: set_deny', 'src_ip: 127.0.0.1', 'method: [GET, POST]'], - named_acls=[], - expected_responses=[403, 403, 200, 200, 400], - proxy_protocol=False) - -test_ip_allow_optional_methods = Test_remap_acl( - "Verify add_deny adds blocked methods.", - replay_file='remap_acl_all_denied.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - acl_configuration=['action: add_deny', 'src_ip: 127.0.0.1', 'method: GET'], - named_acls=[], - expected_responses=[403, 403, 403, 403, 403], - proxy_protocol=False) - -test_ip_allow_optional_methods = Test_remap_acl( - "Verify a default deny filter rule works.", - replay_file='remap_acl_all_denied.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - acl_configuration=['action: set_allow', 'src_ip: 1.2.3.4', 'method: [GET, POST]'], - named_acls=[('deny', ['action: set_deny'])], - expected_responses=[403, 403, 403, 403, 403], - proxy_protocol=False) - -test_ip_allow_optional_methods = Test_remap_acl( - "Verify inverting @src_ip works.", - replay_file='remap_acl_all_denied.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - acl_configuration=['action: set_allow', 'src_ip_invert: 127.0.0.1', 'method: [GET, POST]'], - named_acls=[('deny', ['action: set_deny'])], - expected_responses=[403, 403, 403, 403, 403], - proxy_protocol=False) - -test_ip_allow_optional_methods = Test_remap_acl( - "Verify inverting @src_ip works with the rule matching.", - replay_file='remap_acl_get_post_allowed.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - acl_configuration=['action: set_allow', 'src_ip_invert: 3.4.5.6', 'method: [GET, POST]'], - named_acls=[('deny', ['action: set_deny'])], - expected_responses=[200, 200, 403, 403, 403], - proxy_protocol=False) - -test_ip_allow_optional_methods = Test_remap_acl( - "Verify inverting @src_ip_category works.", - replay_file='remap_acl_all_denied.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - acl_configuration=['action: set_allow', 'src_ip_category_invert: ACME_LOCAL', 'method: [GET, POST]'], - named_acls=[('deny', ['action: set_deny'])], - expected_responses=[403, 403, 403, 403, 403], - proxy_protocol=False) - -test_ip_allow_optional_methods = Test_remap_acl( - "Verify inverting @src_ip_category works with the rule matching.", - replay_file='remap_acl_get_post_allowed.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - acl_configuration=['action: set_allow', 'src_ip_category_invert: ACME_EXTERNAL', 'method: [GET, POST]'], - named_acls=[('deny', ['action: set_deny'])], - expected_responses=[200, 200, 403, 403, 403], - proxy_protocol=False) - -test_ip_allow_optional_methods = Test_remap_acl( - "Verify @src_ip and @src_ip_category AND together.", - replay_file='remap_acl_all_denied.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - # The rule will not match because, while @src_ip matches, @src_ip_category does not. - acl_configuration=['action: set_allow', 'src_ip: 127.0.0.1', 'src_ip_category: ACME_EXTERNAL', 'method: [GET, POST]'], - # Therefore, this named deny filter will block. - named_acls=[('deny', ['action: set_deny'])], - expected_responses=[403, 403, 403, 403, 403], - proxy_protocol=False) - -test_ip_allow_optional_methods = Test_remap_acl( - "Verify defined in-line ACLS are evaluated before named ones.", - replay_file='remap_acl_get_post_allowed.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - acl_configuration=['action: set_allow', 'src_ip: 127.0.0.1', 'method: [GET, POST]'], - named_acls=[('deny', ['action: set_deny'])], - expected_responses=[200, 200, 403, 403, 403], - proxy_protocol=False) - -test_ip_allow_optional_methods = Test_remap_acl( - "Verify remap.config line overrides ip_allow rule.", - replay_file='remap_acl_get_post_allowed.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - acl_configuration=['action: set_allow', 'src_ip: 127.0.0.1', 'method: [GET, POST]'], - named_acls=[], - expected_responses=[200, 200, 403, 403, 403], - proxy_protocol=False) - -test_ip_allow_optional_methods = Test_remap_acl( - "Verify we can deactivate the ip_allow filter.", - replay_file='remap_acl_all_allowed.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=True, - acl_behavior_policy=1, - # This won't match, so nothing will match since ip_allow.yaml is off. - acl_configuration=['action: set_allow', 'src_ip: 1.2.3.4', 'method: [GET, POST]'], - named_acls=[], - # Nothing will block the request since ip_allow.yaml is off. - expected_responses=[200, 200, 200, 200, 400], - proxy_protocol=False) - -test_ip_allow_optional_methods = Test_remap_acl( - "Verify in_ip matches on IP as expected.", - replay_file='remap_acl_get_post_allowed.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - acl_configuration=['action: set_allow', 'in_ip: 127.0.0.1', 'method: [GET, POST]'], - named_acls=[], - expected_responses=[200, 200, 403, 403, 403], - proxy_protocol=False) - -test_ip_allow_optional_methods = Test_remap_acl( - "Verify in_ip rules do not match on other IPs.", - replay_file='remap_acl_get_allowed.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - acl_configuration=['action: set_allow', 'in_ip: 3.4.5.6', 'method: [GET, POST]'], - named_acls=[], - expected_responses=[200, 403, 403, 403, 403], - proxy_protocol=False) - -test_named_acl_deny = Test_remap_acl( - "Verify a named ACL is applied if an in-line ACL is absent.", - replay_file='deny_head_post.replay.yaml', - ip_allow_content=IP_ALLOW_CONTENT, - deactivate_ip_allow=False, - acl_behavior_policy=1, - acl_configuration=[], - named_acls=[('deny', ['action: set_deny', 'method: [HEAD, POST]'])], - expected_responses=[200, 403, 403, 403], - proxy_protocol=False) - - -def replay_proxy_response(filename, replay_file, get_proxy_response, post_proxy_response): - """ - replay_proxy_response writes the given replay file (which expects a single GET & POST client-request) - with the given proxy_response value. This is only used to support the tests in the combination table. - """ - - current_dir = os.path.dirname(inspect.getfile(inspect.currentframe())) - path = os.path.join(current_dir, filename) - data = None - with open(path) as f: - data = load(f, Loader=Loader) - for session in data["sessions"]: - for transaction in session["transactions"]: - method = transaction["client-request"]["method"] - if method == "GET": - transaction["proxy-response"]["status"] = 403 if get_proxy_response == None else get_proxy_response - elif method == "POST": - transaction["proxy-response"]["status"] = 403 if post_proxy_response == None else post_proxy_response - else: - raise Exception("Expected to find GET or POST request, found %s", method) - with open(replay_file, "w") as f: - f.write(dump(data)) - - -from deactivate_ip_allow_yaml import all_deactivate_ip_allow_tests_yaml -from all_acl_combinations_yaml import all_acl_combination_tests_yaml -""" -Test all acl combinations -""" -for idx, test in enumerate(all_acl_combination_tests_yaml): - (_, replay_file_name) = tempfile.mkstemp(suffix="acl_table_test_{}.replay".format(idx)) - replay_proxy_response( - "base.replay.yaml", - replay_file_name, - test["GET response"], - test["POST response"], - ) - Test_remap_acl( - "allcombo-{0} {1} {2} {3}".format(idx, test["inline"], test["named_acl"], test["ip_allow"]), - replay_file=replay_file_name, - ip_allow_content=test["ip_allow"], - deactivate_ip_allow=False, - acl_behavior_policy=0 if test["policy"] == "legacy" else 1, - acl_configuration=test["inline"], - named_acls=[("acl", test["named_acl"])] if test["named_acl"] != "" else [], - expected_responses=[test["GET response"], test["POST response"]], - proxy_protocol=False, - ) -""" -Test all ACL combinations -""" -for idx, test in enumerate(all_deactivate_ip_allow_tests_yaml): - try: - test["deactivate_ip_allow"] - except: - print(test) - (_, replay_file_name) = tempfile.mkstemp(suffix="deactivate_ip_allow_table_test_{}.replay".format(idx)) - replay_proxy_response( - "base.replay.yaml", - replay_file_name, - test["GET response"], - test["POST response"], - ) - Test_remap_acl( - "ipallow-{0} {1} {2} {3}".format(idx, test["inline"], test["named_acl"], test["ip_allow"]), - replay_file=replay_file_name, - ip_allow_content=test["ip_allow"], - deactivate_ip_allow=test["deactivate_ip_allow"], - acl_behavior_policy=0 if test["policy"] == "legacy" else 1, - acl_configuration=test["inline"], - named_acls=[("acl", test["named_acl"])] if test["named_acl"] != "" else [], - expected_responses=[test["GET response"], test["POST response"]], - proxy_protocol=False, - ) diff --git a/tests/gold_tests/remap_yaml/remap_http_yaml.test.py b/tests/gold_tests/remap_yaml/remap_http_yaml.test.py deleted file mode 100644 index 204246fae99..00000000000 --- a/tests/gold_tests/remap_yaml/remap_http_yaml.test.py +++ /dev/null @@ -1,193 +0,0 @@ -''' -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = ''' -Test a basic remap of a http connection -''' - -Test.ContinueOnFail = True -# Define default ATS -ts = Test.MakeATSProcess("ts") -server = Test.MakeOriginServer("server") -server2 = Test.MakeOriginServer("server2", lookup_key="{%Host}{PATH}") -dns = Test.MakeDNServer("dns") - -Test.testName = "" -request_header = {"headers": "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""} -# expected response from the origin server -response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""} - -request_header2 = {"headers": "GET /test HTTP/1.1\r\nHost: www.testexample.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""} -# expected response from the origin server -response_header2 = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""} - -# add response to the server dictionary -server.addResponse("sessionfile.log", request_header, response_header) -server2.addResponse("sessionfile.log", request_header2, response_header2) -ts.Disk.records_config.update( - { - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'http.*|dns|conf_remap|remap_yaml', - 'proxy.config.http.referer_filter': 1, - 'proxy.config.dns.nameservers': '127.0.0.1:{0}'.format(dns.Variables.Port), - 'proxy.config.dns.resolv_conf': 'NULL' - }) - -ts.Disk.remap_yaml.AddLines( - f''' -remap: - - type: map - from: - url: http://www.example.com - to: - url: http://127.0.0.1:{server.Variables.Port} - - type: map_with_recv_port - from: - url: http://www.example2.com:{ts.Variables.port} - to: - url: http://127.0.0.1:{server.Variables.Port} - - type: map - from: - url: http://www.example.com:8080 - to: - url: http://127.0.0.1:{server.Variables.Port} - - type: redirect - from: - url: http://test3.com - to: - url: http://httpbin.org - - type: map_with_referer - from: - url: http://test4.com - to: - url: http://127.0.0.1:{server.Variables.Port} - redirect: - url: http://httpbin.org - regex: - - (.*[.])?persia[.]com - - type: map - from: - url: http://testDNS.com - to: - url: http://audrey.hepburn.com:{server.Variables.Port} - - type: map - from: - url: http://www.testexample.com - to: - url: http://127.0.0.1:{server2.Variables.Port} - plugins: - - name: conf_remap.so - params: - - proxy.config.url_remap.pristine_host_hdr=1 - '''.split("\n")) - -dns.addRecords(records={"audrey.hepburn.com.": ["127.0.0.1"]}) -dns.addRecords(records={"whatever.com.": ["127.0.0.1"]}) - -# call localhost straight -tr = Test.AddTestRun() -tr.MakeCurlCommand('"http://127.0.0.1:{0}/" --verbose'.format(ts.Variables.port), ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.StartBefore(server) -tr.Processes.Default.StartBefore(dns) -tr.Processes.Default.StartBefore(Test.Processes.ts) -tr.Processes.Default.Streams.stderr = "gold/remap-hitATS-404.gold" -tr.StillRunningAfter = server - -# www.example.com host -tr = Test.AddTestRun() -tr.MakeCurlCommand( - '--proxy 127.0.0.1:{0} "http://www.example.com" -H "Proxy-Connection: keep-alive" --verbose'.format(ts.Variables.port), ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stderr = "gold/remap-200.gold" - -# www.example2.com host (match on receive port) -# map_with_recv_port doesn't work with UDS -if not Condition.CurlUsingUnixDomainSocket(): - tr = Test.AddTestRun() - tr.MakeCurlCommand( - '--proxy 127.0.0.1:{0} "http://www.example2.com" -H "Proxy-Connection: keep-alive" --verbose'.format(ts.Variables.port), - ts=ts) - tr.Processes.Default.ReturnCode = 0 - tr.Processes.Default.Streams.stderr = "gold/remap2-200.gold" - -# www.example.com:80 host -tr = Test.AddTestRun() -tr.MakeCurlCommand( - ' --proxy 127.0.0.1:{0} "http://www.example.com:80/" -H "Proxy-Connection: keep-alive" --verbose'.format(ts.Variables.port), - ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stderr = "gold/remap-200.gold" - -# www.example.com:8080 host -tr = Test.AddTestRun() -tr.MakeCurlCommand( - ' --proxy 127.0.0.1:{0} "http://www.example.com:8080" -H "Proxy-Connection: keep-alive" --verbose'.format(ts.Variables.port), - ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stderr = "gold/remap-200.gold" - -# no rule for this -tr = Test.AddTestRun() -tr.MakeCurlCommand( - ' --proxy 127.0.0.1:{0} "http://www.test.com/" -H "Proxy-Connection: keep-alive" --verbose'.format(ts.Variables.port), ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stderr = "gold/remap-404.gold" - -# redirect result -tr = Test.AddTestRun() -tr.MakeCurlCommand(' --proxy 127.0.0.1:{0} "http://test3.com" --verbose'.format(ts.Variables.port), ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stderr = "gold/remap-redirect.gold" - -# referer hit -tr = Test.AddTestRun() -tr.MakeCurlCommand( - ' --proxy 127.0.0.1:{0} "http://test4.com" --header "Referer: persia.com" --verbose'.format(ts.Variables.port), ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stderr = "gold/remap-referer-hit.gold" - -# referer miss -tr = Test.AddTestRun() -tr.MakeCurlCommand( - ' --proxy 127.0.0.1:{0} "http://test4.com" --header "Referer: monkey.com" --verbose'.format(ts.Variables.port), ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stderr = "gold/remap-referer-miss.gold" - -# referer hit -tr = Test.AddTestRun() -tr.MakeCurlCommand( - ' --proxy 127.0.0.1:{0} "http://test4.com" --header "Referer: www.persia.com" --verbose'.format(ts.Variables.port), ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stderr = "gold/remap-referer-hit.gold" - -# DNS test -tr = Test.AddTestRun() -tr.MakeCurlCommand(' --proxy 127.0.0.1:{0} "http://testDNS.com" --verbose'.format(ts.Variables.port), ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stderr = "gold/remap-DNS-200.gold" - -# microserver lookup test -tr = Test.AddTestRun() -tr.MakeCurlCommand( - '--proxy 127.0.0.1:{0} "http://www.testexample.com/test" -H "Host: www.testexample.com" --verbose'.format(ts.Variables.port), - ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.StartBefore(server2) -tr.Processes.Default.Streams.stderr = "gold/lookupTest.gold" -tr.StillRunningAfter = server2 diff --git a/tests/gold_tests/remap_yaml/remap_https_yaml.test.py b/tests/gold_tests/remap_yaml/remap_https_yaml.test.py deleted file mode 100644 index 57a579aa5bb..00000000000 --- a/tests/gold_tests/remap_yaml/remap_https_yaml.test.py +++ /dev/null @@ -1,142 +0,0 @@ -''' -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = ''' -Test a basic remap of a http connection -''' - -Test.SkipIf(Condition.CurlUsingUnixDomainSocket()) -Test.ContinueOnFail = True -# Define default ATS -ts = Test.MakeATSProcess("ts", enable_tls=True) -server = Test.MakeOriginServer("server") -server2 = Test.MakeOriginServer("server2", ssl=True) - -# **testname is required** -testName = "" -request_header = {"headers": "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""} -# desired response form the origin server -response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""} -server.addResponse("sessionlog.json", request_header, response_header) -server2.addResponse("sessionlog.json", request_header, response_header) - -# add ssl materials like key, certificates for the server -ts.addDefaultSSLFiles() - -ts.Disk.records_config.update( - { - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'lm|ssl', - 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), - 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), - # enable ssl port - 'proxy.config.http.server_ports': '{0} {1}:proto=http2;http:ssl'.format(ts.Variables.port, ts.Variables.ssl_port), - 'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE', - }) - -ts.Disk.remap_yaml.AddLines( - f''' -remap: - - type: map - from: - url: https://www.example.com - to: - url: http://127.0.0.1:{server.Variables.Port} - - type: map - from: - url: https://www.example.com:{ts.Variables.ssl_port} - to: - url: http://127.0.0.1:{server.Variables.Port} - - type: map_with_recv_port - from: - url: https://www.example3.com:{ts.Variables.ssl_port} - to: - url: http://127.0.0.1:{server.Variables.Port} - - type: map - from: - url: https://www.anotherexample.com - to: - url: https://127.0.0.1:{server2.Variables.SSL_Port} - '''.split("\n")) - -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) - -# call localhost straight -tr = Test.AddTestRun() -tr.MakeCurlCommand('--http1.1 -k https://127.0.0.1:{0} --verbose'.format(ts.Variables.ssl_port), ts=ts) -tr.Processes.Default.ReturnCode = 0 - -tr.Processes.Default.StartBefore(server) -tr.Processes.Default.StartBefore(server2) -tr.Processes.Default.StartBefore(Test.Processes.ts) -tr.Processes.Default.Streams.stderr = "gold/remap-hitATS-404.gold" -tr.StillRunningAfter = server -tr.StillRunningAfter = ts - -# www.example.com host -tr = Test.AddTestRun() -tr.MakeCurlCommand('--http1.1 -k https://127.0.0.1:{0} -H "Host: www.example.com" --verbose'.format(ts.Variables.ssl_port), ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stderr = "gold/remap-https-200.gold" - -# www.example.com:80 host -tr = Test.AddTestRun() -tr.MakeCurlCommand( - '--http1.1 -k https://127.0.0.1:{0} -H "Host: www.example.com:443" --verbose'.format(ts.Variables.ssl_port), ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stderr = "gold/remap-https-200.gold" - -# www.example.com:8080 host -tr = Test.AddTestRun() -tr.MakeCurlCommand( - '--http1.1 -k https://127.0.0.1:{0} -H "Host: www.example.com:{0}" --verbose'.format(ts.Variables.ssl_port), ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stderr = "gold/remap-https-200.gold" - -# www.example3.com (match on receive port) -tr = Test.AddTestRun() -tr.MakeCurlCommand('--http1.1 -k https://127.0.0.1:{0} -H "Host: www.example3.com" --verbose'.format(ts.Variables.ssl_port), ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stderr = "gold/remap-https-200_3.gold" - -# no rule for this -tr = Test.AddTestRun() -tr.MakeCurlCommand('--http1.1 -k https://127.0.0.1:{0} -H "Host: www.test.com" --verbose'.format(ts.Variables.ssl_port), ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stderr = "gold/remap-hitATS-404.gold" - -# bad port -tr = Test.AddTestRun() -tr.MakeCurlCommand( - '--http1.1 -k https://127.0.0.1:{0} -H "Host: www.example.com:1234" --verbose'.format(ts.Variables.ssl_port), ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stderr = "gold/remap-hitATS-404.gold" - -# map www.anotherexample.com to https://.com -tr = Test.AddTestRun() -tr.MakeCurlCommand( - '--http1.1 -k https://127.0.0.1:{0} -H "Host: www.anotherexample.com" --verbose'.format(ts.Variables.ssl_port), ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stderr = "gold/remap-https-200_2.gold" -tr.StillRunningAfter = server2 diff --git a/tests/gold_tests/remap_yaml/remap_ip_resolve_yaml.test.py b/tests/gold_tests/remap_yaml/remap_ip_resolve_yaml.test.py deleted file mode 100644 index 4cdc3c0122d..00000000000 --- a/tests/gold_tests/remap_yaml/remap_ip_resolve_yaml.test.py +++ /dev/null @@ -1,89 +0,0 @@ -''' -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = ''' -Test a basic ip_resolve override using an ipv6 server -''' - -Test.ContinueOnFail = True -# Define default ATS -ts = Test.MakeATSProcess("ts") -server = Test.MakeOriginServer("server") -server_v6 = Test.MakeOriginServer("server_v6", None, None, '::1', 0) - -dns = Test.MakeDNServer("dns") - -Test.testName = "" -request_header = {"headers": "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""} -# expected response from the origin server -response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""} - -# add response to the server dictionary -server.addResponse("sessionfile.log", request_header, response_header) -server_v6.addResponse("sessionfile.log", request_header, response_header) -ts.Disk.records_config.update( - { - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'http.*|dns|conf_remap', - 'proxy.config.http.referer_filter': 1, - 'proxy.config.dns.nameservers': '127.0.0.1:{0}'.format(dns.Variables.Port), - 'proxy.config.dns.resolv_conf': 'NULL', - 'proxy.config.hostdb.ip_resolve': 'ipv4' - }) - -ts.Disk.remap_yaml.AddLines( - f''' -remap: - - type: map - from: - url: http://testDNS.com - to: - url: http://test.ipv4.only.com:{server.Variables.Port} - plugins: - - name: conf_remap.so - params: - - proxy.config.hostdb.ip_resolve=ipv6;ipv4;client - - type: map - from: - url: http://testDNS2.com - to: - url: http://test.ipv6.only.com:{server_v6.Variables.Port} - plugins: - - name: conf_remap.so - params: - - proxy.config.hostdb.ip_resolve=ipv6;only - '''.split("\n")) - -dns.addRecords(records={"test.ipv4.only.com.": ["127.0.0.1"]}) -dns.addRecords(records={"test.ipv6.only.com": ["127.0.0.1", "::1"]}) - -tr = Test.AddTestRun() -tr.MakeCurlCommand(' --proxy 127.0.0.1:{0} "http://testDNS.com" --verbose'.format(ts.Variables.port), ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.StartBefore(server) -tr.Processes.Default.StartBefore(dns) -tr.Processes.Default.StartBefore(Test.Processes.ts) -tr.Processes.Default.Streams.stderr = "gold/remap-DNS-200.gold" -tr.StillRunningAfter = server - -tr = Test.AddTestRun() -tr.MakeCurlCommand(' --proxy 127.0.0.1:{0} "http://testDNS2.com" --verbose'.format(ts.Variables.port), ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.StartBefore(server_v6) -tr.Processes.Default.Streams.stderr = "gold/remap-DNS-ipv6-200.gold" -tr.StillRunningAfter = server_v6 diff --git a/tests/gold_tests/remap_yaml/remap_load_empty_failure_yaml.test.py b/tests/gold_tests/remap_yaml/remap_load_empty_failure_yaml.test.py deleted file mode 100644 index 03991886426..00000000000 --- a/tests/gold_tests/remap_yaml/remap_load_empty_failure_yaml.test.py +++ /dev/null @@ -1,42 +0,0 @@ -''' -Verify correct behavior of regex_map in remap.yaml. -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = ''' -Test minimum rules on load - fail on missing file. -''' -ts = Test.MakeATSProcess("ts") -ts.Disk.remap_yaml.AddLine(f"") # empty file -ts.Disk.records_config.update({'proxy.config.url_remap.min_rules_required': 1}) -ts.ReturnCode = 33 # expect to Emergency fail due to empty "remap.config". -ts.Ready = 0 - -tr = Test.AddTestRun("test") - -# We have to wait upon TS to emit the expected log message, but it cannot be -# the ts Ready criteria because autest might detect the process going away -# before it detects the log message. So we add a separate process that waits -# upon the log message. -watcher = Test.Processes.Process("watcher") -watcher.Command = "sleep 10" -watcher.Ready = When.FileContains(ts.Disk.diags_log.Name, "remap.yaml failed to load") -watcher.StartBefore(ts) - -tr.Processes.Default.Command = "echo howdy" -tr.TimeOut = 5 -tr.Processes.Default.StartBefore(watcher) diff --git a/tests/gold_tests/remap_yaml/remap_load_empty_success_yaml.test.py b/tests/gold_tests/remap_yaml/remap_load_empty_success_yaml.test.py deleted file mode 100644 index 596973bde61..00000000000 --- a/tests/gold_tests/remap_yaml/remap_load_empty_success_yaml.test.py +++ /dev/null @@ -1,33 +0,0 @@ -''' -Verify correct behavior of regex_map in remap.yaml. -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = ''' -Test minimum rules on load - succeed on empty file. -''' - -ts = Test.MakeATSProcess("ts") -ts.Disk.remap_yaml.AddLine(f"") # empty file -ts.Disk.records_config.update({f'proxy.config.url_remap.min_rules_required': 0}) - -tr = Test.AddTestRun("startup") -p = tr.Processes.Default -p.Command = "sh -c echo" -p.ReturnCode = 0 -p.StartBefore(Test.Processes.ts) -tr.StillRunningAfter = ts diff --git a/tests/gold_tests/remap_yaml/remap_load_missing_failure_yaml.test.py b/tests/gold_tests/remap_yaml/remap_load_missing_failure_yaml.test.py deleted file mode 100644 index 8f38e7644e2..00000000000 --- a/tests/gold_tests/remap_yaml/remap_load_missing_failure_yaml.test.py +++ /dev/null @@ -1,29 +0,0 @@ -''' -Verify correct behavior of regex_map in remap.yaml. -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = ''' -Test minimum rules on load - fail on missing file. -''' -ts = Test.MakeATSProcess("ts") -ts.Disk.records_config.update({'proxy.config.url_remap.min_rules_required': 1}) -ts.ReturnCode = 33 # expect to Emergency fail due to empty "remap.config". - -tr = Test.AddTestRun("test") -tr.Processes.Default.Command = "echo" -tr.Processes.Default.StartAfter(ts, ready=When.FileExists(ts.Disk.diags_log)) diff --git a/tests/gold_tests/remap_yaml/remap_load_missing_success_yaml.test.py b/tests/gold_tests/remap_yaml/remap_load_missing_success_yaml.test.py deleted file mode 100644 index 1b2b519c645..00000000000 --- a/tests/gold_tests/remap_yaml/remap_load_missing_success_yaml.test.py +++ /dev/null @@ -1,33 +0,0 @@ -''' -Verify correct behavior of regex_map in remap.yaml. -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = ''' -Test minimum rules on load - succeed on missing file. -''' - -ts = Test.MakeATSProcess("ts") - -ts.Disk.records_config.update({f'proxy.config.url_remap.min_rules_required': 0}) - -tr = Test.AddTestRun("startup") -p = tr.Processes.Default -p.Command = "sh -c echo" -p.ReturnCode = 0 -p.StartBefore(Test.Processes.ts) -tr.StillRunningAfter = ts diff --git a/tests/gold_tests/remap_yaml/remap_reload_yaml.test.py b/tests/gold_tests/remap_yaml/remap_reload_yaml.test.py deleted file mode 100644 index cd006903cc3..00000000000 --- a/tests/gold_tests/remap_yaml/remap_reload_yaml.test.py +++ /dev/null @@ -1,165 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import os - - -def update_remap_yaml(path: str, lines: list) -> None: - """Update the remap.yaml file. - - This is used to update the config file between test runs without - triggering framework warnings about overriding file objects. - - :param path: The path to the remap.config file. - :param lines: The list of lines to write to the file. - """ - with open(path, 'w') as f: - f.write('\n'.join(lines) + '\n') - - -Test.Summary = ''' -Test remap reloading -''' -Test.testName = 'remap_reload' - -replay_file_1 = "reload_1.replay.yaml" -replay_file_2 = "reload_2.replay.yaml" -replay_file_3 = "reload_3.replay.yaml" -replay_file_4 = "reload_4.replay.yaml" - -tm = Test.MakeATSProcess("ts") -tm.Disk.diags_log.Content = Testers.ContainsExpression("remap.yaml failed to load", "Remap should fail to load") -remap_cfg_path = os.path.join(tm.Variables.CONFIGDIR, 'remap.yaml') - -pv = Test.MakeVerifierServerProcess("pv", "reload_server.replay.yaml") -pv_port = pv.Variables.http_port - -tm.Disk.remap_yaml.AddLines( - f''' -remap: - - type: map - from: - url: http://alpha.ex - to: - url: http://alpha.ex:{pv_port} - - type: map - from: - url: http://bravo.ex - to: - url: http://bravo.ex:{pv_port} - - type: map - from: - url: http://charlie.ex - to: - url: http://charlie.ex:{pv_port} - - type: map - from: - url: http://delta.ex - to: - url: http://delta.ex:{pv_port} - '''.split("\n")) - -tm.Disk.records_config.update({'proxy.config.url_remap.min_rules_required': 3}) - -nameserver = Test.MakeDNServer("dns", default='127.0.0.1') -tm.Disk.records_config.update( - { - 'proxy.config.dns.nameservers': f"127.0.0.1:{nameserver.Variables.Port}", - 'proxy.config.dns.resolv_conf': 'NULL' - }) - -tr = Test.AddTestRun("verify load") -tr.Processes.Default.StartBefore(pv) -tr.Processes.Default.StartBefore(nameserver) -tr.Processes.Default.StartBefore(tm) -tr.AddVerifierClientProcess("client", replay_file_1, http_ports=[tm.Variables.port]) - -tr = Test.AddTestRun("Change remap.yaml to have only two remap rules") -p = tr.Processes.Default -p.Env = tm.Env -p.Command = 'echo "Change remap.yaml, two lines"' -p.Setup.Lambda( - lambda: update_remap_yaml( - remap_cfg_path, f''' -remap: - - type: map - from: - url: http://alpha.ex - to: - url: http://alpha.ex:{pv_port} - - type: map - from: - url: http://bravo.ex - to: - url: http://bravo.ex:{pv_port} - '''.split("\n"))) - -tr = Test.AddTestRun("remap_yaml reload, fails") -tr.Processes.Default.Env = tm.Env -tr.Processes.Default.Command = 'sleep 2; traffic_ctl config reload' - -tr = Test.AddTestRun("after first reload") -await_config_reload = tr.Processes.Process('config_reload_failed', 'sleep 30') -await_config_reload.Ready = When.FileContains(tm.Disk.diags_log.Name, "configuration is invalid") -tr.AddVerifierClientProcess("client_2", replay_file_2, http_ports=[tm.Variables.port]) -tr.Processes.Default.StartBefore(await_config_reload) - -tr = Test.AddTestRun("Change remap.yaml to have more than three remap rules") -p = tr.Processes.Default -p.Env = tm.Env -p.Command = 'echo "Change remap.yaml, more than three lines"' -p.Setup.Lambda( - lambda: update_remap_yaml( - remap_cfg_path, f''' -remap: - - type: map - from: - url: http://echo.ex - to: - url: http://echo.ex:{pv_port} - - type: map - from: - url: http://foxtrot.ex - to: - url: http://foxtrot.ex:{pv_port} - - type: map - from: - url: http://golf.ex - to: - url: http://golf.ex:{pv_port} - - type: map - from: - url: http://hotel.ex - to: - url: http://hotel.ex:{pv_port} - - type: map - from: - url: http://india.ex - to: - url: http://india.ex:{pv_port} - '''.split("\n"))) - -tr = Test.AddTestRun("remap_yaml reload, succeeds") -tr.Processes.Default.Env = tm.Env -tr.Processes.Default.Command = 'sleep 2; traffic_ctl config reload' - -tr = Test.AddTestRun("post update charlie") -await_config_reload = tr.Processes.Process('config_reload_succeeded', 'sleep 30') -await_config_reload.Ready = When.FileContains(tm.Disk.diags_log.Name, "remap.yaml finished loading", 2) -tr.Processes.Default.StartBefore(await_config_reload) -tr.AddVerifierClientProcess("client_3", replay_file_3, http_ports=[tm.Variables.port]) - -tr = Test.AddTestRun("post update golf") -tr.AddVerifierClientProcess("client_4", replay_file_4, http_ports=[tm.Variables.port]) diff --git a/tests/gold_tests/remap_yaml/remap_ws_yaml.test.py b/tests/gold_tests/remap_yaml/remap_ws_yaml.test.py deleted file mode 100644 index c80312a062f..00000000000 --- a/tests/gold_tests/remap_yaml/remap_ws_yaml.test.py +++ /dev/null @@ -1,135 +0,0 @@ -''' -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = ''' -Test a basic remap of a websocket connections -''' - -Test.ContinueOnFail = True - -ts = Test.MakeATSProcess("ts", enable_tls=True) -server = Test.MakeOriginServer("server") - -testName = "Test WebSocket Remaps" -request_header = { - "headers": "GET /chat HTTP/1.1\r\nHost: www.example.com\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n\r\n", - "body": None -} -response_header = { - "headers": - "HTTP/1.1 101 OK\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n", - "body": None -} -server.addResponse("sessionlog.json", request_header, response_header) - -ts.addDefaultSSLFiles() - -ts.Disk.records_config.update( - { - 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), - 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), - }) - -ts.Disk.remap_yaml.AddLines( - f''' -remap: - - type: map - from: - url: ws://www.example.com:{ts.Variables.port} - to: - url: ws://127.0.0.1:{server.Variables.Port} - - type: map - from: - url: wss://www.example.com:{ts.Variables.ssl_port} - to: - url: ws://127.0.0.1:{server.Variables.Port} - '''.split("\n")) - -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) - -if not Condition.CurlUsingUnixDomainSocket(): - # wss mapping - tr = Test.AddTestRun() - tr.Processes.Default.StartBefore(server) - tr.Processes.Default.StartBefore(Test.Processes.ts, ready=1) - tr.MakeCurlCommand( - '--max-time 2 -v -s -q -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" -H "Sec-WebSocket-Version: 13" --http1.1 --resolve www.example.com:{0}:127.0.0.1 -k https://www.example.com:{0}/chat' - .format(ts.Variables.ssl_port), - ts=ts) - tr.Processes.Default.ReturnCode = 28 - tr.Processes.Default.Streams.stderr = "gold/remap-ws-upgrade.gold" - tr.StillRunningAfter = server - tr.StillRunningAfter = ts - -# ws mapping -tr = Test.AddTestRun() -if Condition.CurlUsingUnixDomainSocket(): - tr.Processes.Default.StartBefore(server) - tr.Processes.Default.StartBefore(Test.Processes.ts, ready=1) -tr.MakeCurlCommand( - '--max-time 2 -v -s -q -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" -H "Sec-WebSocket-Version: 13" --http1.1 --resolve www.example.com:{0}:127.0.0.1 -k http://www.example.com:{0}/chat' - .format(ts.Variables.port), - ts=ts) -tr.Processes.Default.ReturnCode = 28 -tr.Processes.Default.Streams.stderr = "gold/remap-ws-upgrade.gold" -tr.StillRunningAfter = server -tr.StillRunningAfter = ts - -# Missing required headers (should result in 400) -tr = Test.AddTestRun() -tr.MakeCurlCommand( - '--max-time 2 -v -s -q -H "Connection: Upgrade" -H "Upgrade: websocket" --http1.1 --resolve www.example.com:{0}:127.0.0.1 -k http://www.example.com:{0}/chat' - .format(ts.Variables.port), - ts=ts) -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.stderr = "gold/remap-ws-upgrade-400.gold" -tr.StillRunningAfter = server -tr.StillRunningAfter = ts - -# Test metrics -tr = Test.AddTestRun() -if Condition.CurlUsingUnixDomainSocket(): - metrics_gold_file = 'remap-ws-metrics-uds.gold' -else: - metrics_gold_file = 'remap-ws-metrics.gold' -tr.Processes.Default.Command = ( - f"{Test.Variables.AtsTestToolsDir}/stdout_wait" + " 'traffic_ctl metric get" + - " proxy.process.http.total_incoming_connections" + " proxy.process.http.total_client_connections" + - " proxy.process.http.total_client_connections_ipv4" + " proxy.process.http.total_client_connections_ipv6" + - " proxy.process.http.total_server_connections" + " proxy.process.http2.total_client_connections" + - " proxy.process.http.connect_requests" + " proxy.process.tunnel.total_client_connections_blind_tcp" + - " proxy.process.tunnel.current_client_connections_blind_tcp" + " proxy.process.tunnel.total_server_connections_blind_tcp" + - " proxy.process.tunnel.current_server_connections_blind_tcp" + " proxy.process.tunnel.total_client_connections_tls_tunnel" + - " proxy.process.tunnel.current_client_connections_tls_tunnel" + " proxy.process.tunnel.total_client_connections_tls_forward" + - " proxy.process.tunnel.current_client_connections_tls_forward" + - " proxy.process.tunnel.total_client_connections_tls_partial_blind" + - " proxy.process.tunnel.current_client_connections_tls_partial_blind" + - " proxy.process.tunnel.total_client_connections_tls_http" + " proxy.process.tunnel.current_client_connections_tls_http" + - " proxy.process.tunnel.total_server_connections_tls" + " proxy.process.tunnel.current_server_connections_tls'" + - f" {Test.TestDirectory}/gold/{metrics_gold_file}") -# Need to copy over the environment so traffic_ctl knows where to find the unix domain socket -tr.Processes.Default.Env = ts.Env -tr.Processes.Default.ReturnCode = 0 -tr.StillRunningAfter = server -tr.StillRunningAfter = ts diff --git a/tests/gold_tests/slow_post/server_abort.test.py b/tests/gold_tests/slow_post/server_abort.test.py index 394e7b350e3..05867859839 100644 --- a/tests/gold_tests/slow_post/server_abort.test.py +++ b/tests/gold_tests/slow_post/server_abort.test.py @@ -30,13 +30,7 @@ # on the origin server so that it aborts the connection upon receiving a # request 'map / https://127.0.0.1:{0}'.format(server.Variables.Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: aaa-signed.pem - ssl_key_name: aaa-signed.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=aaa-signed.pem ssl_key_name=aaa-signed.key') ts.Disk.records_config.update( { 'proxy.config.diags.debug.tags': 'http|dns', diff --git a/tests/gold_tests/timeout/accept_timeout.test.py b/tests/gold_tests/timeout/accept_timeout.test.py index 80a7d44dcce..1521a129a58 100644 --- a/tests/gold_tests/timeout/accept_timeout.test.py +++ b/tests/gold_tests/timeout/accept_timeout.test.py @@ -38,13 +38,7 @@ 'proxy.config.net.defer_accept': 0 # Must turn off defer accept to test the raw TCP case }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # case 1 TLS with no data tr = Test.AddTestRun("tr") diff --git a/tests/gold_tests/timeout/active_timeout.test.py b/tests/gold_tests/timeout/active_timeout.test.py index 5227cc0e93c..5f5d1d52ec9 100644 --- a/tests/gold_tests/timeout/active_timeout.test.py +++ b/tests/gold_tests/timeout/active_timeout.test.py @@ -43,13 +43,7 @@ ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}/'.format(server.Variables.Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') tr = Test.AddTestRun("tr") tr.Processes.Default.StartBefore(server) diff --git a/tests/gold_tests/timeout/http2_no_activity_timeout.test.py b/tests/gold_tests/timeout/http2_no_activity_timeout.test.py index ab910e0732c..bab22021048 100644 --- a/tests/gold_tests/timeout/http2_no_activity_timeout.test.py +++ b/tests/gold_tests/timeout/http2_no_activity_timeout.test.py @@ -75,13 +75,9 @@ def _configure_traffic_server(self, tr: 'TestRun'): self._ts.addSSLfile("ssl/cert.crt") self._ts.addSSLfile("ssl/private-key.key") - self._ts.Disk.ssl_multicert_yaml.AddLines( - f""" -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: {self._ts.Variables.SSLDir}/cert.crt - ssl_key_name: {self._ts.Variables.SSLDir}/private-key.key -""".split("\n")) + self._ts.Disk.ssl_multicert_config.AddLine( + f'dest_ip=* ssl_cert_name={self._ts.Variables.SSLDir}/cert.crt ' + f'ssl_key_name={self._ts.Variables.SSLDir}/private-key.key') self._ts.Disk.records_config.update( { diff --git a/tests/gold_tests/timeout/inactive_client_timeout.test.py b/tests/gold_tests/timeout/inactive_client_timeout.test.py index 84d3de4adf6..6c0a06f3f2c 100644 --- a/tests/gold_tests/timeout/inactive_client_timeout.test.py +++ b/tests/gold_tests/timeout/inactive_client_timeout.test.py @@ -46,13 +46,7 @@ 'map / http://127.0.0.1:{0}'.format(server.Variables.http_port), ]) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # # Test 1: Verify that server delay does not trigger client activity timeout. diff --git a/tests/gold_tests/timeout/inactive_timeout.test.py b/tests/gold_tests/timeout/inactive_timeout.test.py index 2265763eff7..c1fc3d8ed0b 100644 --- a/tests/gold_tests/timeout/inactive_timeout.test.py +++ b/tests/gold_tests/timeout/inactive_timeout.test.py @@ -40,13 +40,7 @@ ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}/'.format(server.Variables.Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') tr = Test.AddTestRun("tr") tr.Processes.Default.StartBefore(server) diff --git a/tests/gold_tests/timeout/quic_no_activity_timeout.test.py b/tests/gold_tests/timeout/quic_no_activity_timeout.test.py index f50146f5bea..0b688c8b0a5 100644 --- a/tests/gold_tests/timeout/quic_no_activity_timeout.test.py +++ b/tests/gold_tests/timeout/quic_no_activity_timeout.test.py @@ -74,13 +74,9 @@ def _configure_traffic_server(self, tr: 'TestRun'): self._ts.Disk.records_config.update(self.extra_recs) self._ts.Disk.remap_config.AddLine(f'map / http://127.0.0.1:{self._server.Variables.http_port}') - self._ts.Disk.ssl_multicert_yaml.AddLines( - f""" -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: {self._ts.Variables.SSLDir}/cert.crt - ssl_key_name: {self._ts.Variables.SSLDir}/private-key.key -""".split("\n")) + self._ts.Disk.ssl_multicert_config.AddLine( + f'dest_ip=* ssl_cert_name={self._ts.Variables.SSLDir}/cert.crt ssl_key_name={self._ts.Variables.SSLDir}/private-key.key' + ) def run(self, check_for_max_idle_timeout=False): """Run the test.""" diff --git a/tests/gold_tests/timeout/tunnel_active_timeout.test.py b/tests/gold_tests/timeout/tunnel_active_timeout.test.py index e8882f4dbbd..80c5840acea 100644 --- a/tests/gold_tests/timeout/tunnel_active_timeout.test.py +++ b/tests/gold_tests/timeout/tunnel_active_timeout.test.py @@ -34,13 +34,7 @@ ts.addDefaultSSLFiles() -ts.Disk.ssl_multicert_yaml.AddLines( - f""" -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split('\n')) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.records_config.update( { diff --git a/tests/gold_tests/tls/allow-plain.test.py b/tests/gold_tests/tls/allow-plain.test.py index 0e2802b868d..d4791383f4f 100644 --- a/tests/gold_tests/tls/allow-plain.test.py +++ b/tests/gold_tests/tls/allow-plain.test.py @@ -53,13 +53,7 @@ 'map /post http://127.0.0.1:{0}/post'.format(server.Variables.http_port), ]) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') big_post_body = "0123456789" * 50000 big_post_body_file = open(os.path.join(Test.RunDirectory, "big_post_body"), "w") diff --git a/tests/gold_tests/tls/exit_on_cert_load_fail.test.py b/tests/gold_tests/tls/exit_on_cert_load_fail.test.py index cbac56e6672..c075e1a357c 100644 --- a/tests/gold_tests/tls/exit_on_cert_load_fail.test.py +++ b/tests/gold_tests/tls/exit_on_cert_load_fail.test.py @@ -58,13 +58,7 @@ def _configure_traffic_server(self, tr: 'TestRun'): # Also setup the server certs so that issues are limited to client # cert loading. self._ts.addDefaultSSLFiles() - self._ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self._ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') self._ts.Disk.records_config.update( { 'proxy.config.ssl.server.cert.path': f'{ts.Variables.SSLDir}', diff --git a/tests/gold_tests/tls/replay/tls_cert_compression.replay.yaml b/tests/gold_tests/tls/replay/tls_cert_compression.replay.yaml deleted file mode 100644 index bc05706ec25..00000000000 --- a/tests/gold_tests/tls/replay/tls_cert_compression.replay.yaml +++ /dev/null @@ -1,41 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -meta: - version: "1.0" - -sessions: -- transactions: - - - client-request: - method: GET - url: /cert-compression-test - version: '1.1' - headers: - fields: - - [Host, example.com] - - [uuid, cert-compression-request] - - server-response: - status: 200 - reason: OK - headers: - fields: - - [Content-Length, "0"] - - [X-Response, cert-compression-response] - - proxy-response: - status: 200 diff --git a/tests/gold_tests/tls/ssl_key_dialog.test.py b/tests/gold_tests/tls/ssl_key_dialog.test.py index 107e4d722fe..dd9438f371c 100644 --- a/tests/gold_tests/tls/ssl_key_dialog.test.py +++ b/tests/gold_tests/tls/ssl_key_dialog.test.py @@ -39,14 +39,10 @@ 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: passphrase.pem - ssl_key_name: passphrase.key - ssl_key_dialog: "exec:/bin/bash -c 'echo -n passphrase'" -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLines( + [ + 'dest_ip=* ssl_cert_name=passphrase.pem ssl_key_name=passphrase.key ssl_key_dialog="exec:/bin/bash -c \'echo -n passphrase\'"', + ]) request_header = {"headers": "GET / HTTP/1.1\r\nHost: bogus\r\n\r\n", "timestamp": "1469733493.993", "body": ""} response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": "success!"} @@ -67,16 +63,12 @@ tr2 = Test.AddTestRun("Update config files") # Update the multicert config -sslcertpath = ts.Disk.ssl_multicert_yaml.AbsPath +sslcertpath = ts.Disk.ssl_multicert_config.AbsPath tr2.Disk.File(sslcertpath, id="ssl_multicert_config", typename="ats:config"), tr2.Disk.ssl_multicert_config.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: passphrase2.pem - ssl_key_name: passphrase2.key - ssl_key_dialog: "exec:/bin/bash -c 'echo -n passphrase'" -""".split("\n")) + [ + 'dest_ip=* ssl_cert_name=passphrase2.pem ssl_key_name=passphrase2.key ssl_key_dialog="exec:/bin/bash -c \'echo -n passphrase\'"', + ]) tr2.StillRunningAfter = ts tr2.StillRunningAfter = server tr2.Processes.Default.Command = 'echo Updated configs' @@ -98,7 +90,7 @@ p.Env = ts.Env p.ReturnCode = 0 await_config_reload = tr.Processes.Process(f'config_reload_succeeded', 'sleep 30') -await_config_reload.Ready = When.FileContains(ts.Disk.diags_log.Name, "ssl_multicert.yaml finished loading", 2) +await_config_reload.Ready = When.FileContains(ts.Disk.diags_log.Name, "ssl_multicert.config finished loading", 2) p.StartBefore(await_config_reload) tr3 = Test.AddTestRun("use a key with passphrase") diff --git a/tests/gold_tests/tls/ssl_multicert_loader.test.py b/tests/gold_tests/tls/ssl_multicert_loader.test.py index 5eb95a7f536..60834b1dbc6 100644 --- a/tests/gold_tests/tls/ssl_multicert_loader.test.py +++ b/tests/gold_tests/tls/ssl_multicert_loader.test.py @@ -15,14 +15,14 @@ # limitations under the License. Test.Summary = ''' -Test reloading ssl_multicert.yaml with errors and keeping around the old ssl config structure +Test reloading ssl_multicert.config with errors and keeping around the old ssl config structure ''' sni_domain = 'example.com' ts = Test.MakeATSProcess("ts", enable_tls=True) server = Test.MakeOriginServer("server") -server2 = Test.MakeOriginServer("server2") +server2 = Test.MakeOriginServer("server3") request_header = {"headers": f"GET / HTTP/1.1\r\nHost: {sni_domain}\r\n\r\n", "timestamp": "1469733493.993", "body": ""} response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""} @@ -39,13 +39,7 @@ ts.Disk.remap_config.AddLine(f'map / http://127.0.0.1:{server.Variables.Port}') -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') tr = Test.AddTestRun("ensure we can connect for SNI $sni_domain") tr.Processes.Default.StartBefore(Test.Processes.ts) @@ -59,19 +53,15 @@ tr.Processes.Default.Streams.stderr = Testers.IncludesExpression(f"CN={sni_domain}", "Check response") tr2 = Test.AddTestRun("Update config files") -# Update the configs - overwrite the ssl_multicert.yaml file with an invalid config -sslcertpath = ts.Disk.ssl_multicert_yaml.AbsPath - -tr2.Disk.File(sslcertpath, id="ssl_multicert_update", typename="ats:config") -tr2.Disk.ssl_multicert_update.AddLines( - """ -ssl_multicert: - - ssl_cert_name: server_does_not_exist.pem - ssl_key_name: server_does_not_exist.key - - dest_ip: "*" - ssl_cert_name: server.pem_doesnotexist - ssl_key_name: server.key -""".split("\n")) +# Update the configs +sslcertpath = ts.Disk.ssl_multicert_config.AbsPath + +tr2.Disk.File(sslcertpath, id="ssl_multicert_config", typename="ats:config") +tr2.Disk.ssl_multicert_config.AddLines( + [ + 'ssl_cert_name=server_does_not_exist.pem ssl_key_name=server_does_not_exist.key', + 'dest_ip=* ssl_cert_name=server.pem_doesnotexist ssl_key_name=server.key', + ]) tr2.StillRunningAfter = ts tr2.StillRunningAfter = server tr2.Processes.Default.Command = 'echo Updated configs' @@ -86,7 +76,7 @@ tr2reload.Processes.Default.ReturnCode = 0 ts.Disk.diags_log.Content = Testers.ContainsExpression('ERROR: ', 'ERROR') -# Reload of ssl_multicert.yaml should fail, BUT the old config structure +# Reload of ssl_multicert.config should fail, BUT the old config structure # should be in place to successfully answer for the test domain tr3 = Test.AddTestRun("Make request again for $sni_domain") # Wait for the reload to complete @@ -105,13 +95,9 @@ # to catch if the current default (1) changes in the future ts2 = Test.MakeATSProcess("ts2", enable_tls=True) -ts2.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem_doesnotexist - ssl_key_name: server.key -""".split("\n")) +ts2.Disk.ssl_multicert_config.AddLines([ + 'dest_ip=* ssl_cert_name=server.pem_doesnotexist ssl_key_name=server.key', +]) tr4 = Test.AddTestRun() tr4.Processes.Default.Command = 'echo Waiting' @@ -123,45 +109,3 @@ ts2.Disk.traffic_out.Content = Testers.ExcludesExpression( 'Traffic Server is fully initialized', 'process should fail when invalid certificate specified') ts2.Disk.diags_log.Content = Testers.IncludesExpression('EMERGENCY: failed to load SSL certificate file', 'check diags.log"') - -########################################################################## -# Verify parallel cert loading on startup (firstLoad uses hardware_concurrency, -# not the configured concurrency value, so the thread count is host-dependent) - -ts3 = Test.MakeATSProcess("ts3", enable_tls=True) -server3 = Test.MakeOriginServer("server3") -server3.addResponse("sessionlog.json", request_header, response_header) - -ts3.Disk.records_config.update( - { - 'proxy.config.ssl.server.cert.path': f'{ts3.Variables.SSLDir}', - 'proxy.config.ssl.server.private_key.path': f'{ts3.Variables.SSLDir}', - }) - -ts3.addDefaultSSLFiles() - -ts3.Disk.remap_config.AddLine(f'map / http://127.0.0.1:{server3.Variables.Port}') - -# Need at least 2 certs for multi-threading to kick in -ts3.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key - - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) - -tr5 = Test.AddTestRun("Verify parallel cert loading") -tr5.Processes.Default.StartBefore(ts3) -tr5.Processes.Default.StartBefore(server3) -tr5.StillRunningAfter = ts3 -tr5.StillRunningAfter = server3 -tr5.MakeCurlCommand( - f"-q -s -v -k --resolve '{sni_domain}:{ts3.Variables.ssl_port}:127.0.0.1' https://{sni_domain}:{ts3.Variables.ssl_port}", - ts=ts3) -tr5.Processes.Default.ReturnCode = 0 -tr5.Processes.Default.Streams.stdout = Testers.ExcludesExpression("Could Not Connect", "Check response") -tr5.Processes.Default.Streams.stderr = Testers.IncludesExpression(f"CN={sni_domain}", "Check response") -ts3.Disk.diags_log.Content = Testers.IncludesExpression('loaded 2 certs', 'verify certs were loaded successfully') diff --git a/tests/gold_tests/tls/tls.test.py b/tests/gold_tests/tls/tls.test.py index 5ac2951446d..60789376ca0 100644 --- a/tests/gold_tests/tls/tls.test.py +++ b/tests/gold_tests/tls/tls.test.py @@ -65,13 +65,7 @@ ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.records_config.update( { 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), diff --git a/tests/gold_tests/tls/tls_0rtt_server.test.py b/tests/gold_tests/tls/tls_0rtt_server.test.py index 49e6d5f6cd3..081644a918d 100644 --- a/tests/gold_tests/tls/tls_0rtt_server.test.py +++ b/tests/gold_tests/tls/tls_0rtt_server.test.py @@ -104,6 +104,10 @@ 'proxy.config.exec_thread.limit': 8, 'proxy.config.ssl.server.cert.path': '{0}'.format(ts1.Variables.SSLDir), 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts1.Variables.SSLDir), + 'proxy.config.ssl.session_cache.mode': 2, + 'proxy.config.ssl.session_cache.size': 512000, + 'proxy.config.ssl.session_cache.timeout': 7200, + 'proxy.config.ssl.session_cache.num_buckets': 32768, 'proxy.config.ssl.server.session_ticket.enable': 1, 'proxy.config.ssl.server.max_early_data': 16384, 'proxy.config.ssl.server.allow_early_data_params': 0, @@ -111,13 +115,7 @@ 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA' }) -ts1.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts1.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts1.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) @@ -135,6 +133,10 @@ 'proxy.config.exec_thread.limit': 8, 'proxy.config.ssl.server.cert.path': '{0}'.format(ts1.Variables.SSLDir), 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts1.Variables.SSLDir), + 'proxy.config.ssl.session_cache.mode': 2, + 'proxy.config.ssl.session_cache.size': 512000, + 'proxy.config.ssl.session_cache.timeout': 7200, + 'proxy.config.ssl.session_cache.num_buckets': 32768, 'proxy.config.ssl.server.session_ticket.enable': 1, 'proxy.config.ssl.server.max_early_data': 0, 'proxy.config.ssl.server.allow_early_data_params': 0, @@ -142,13 +144,7 @@ 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA' }) -ts2.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts2.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts2.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) diff --git a/tests/gold_tests/tls/tls_bad_alpn.test.py b/tests/gold_tests/tls/tls_bad_alpn.test.py index 513aba7e22f..027eed148df 100644 --- a/tests/gold_tests/tls/tls_bad_alpn.test.py +++ b/tests/gold_tests/tls/tls_bad_alpn.test.py @@ -34,13 +34,7 @@ ts.addSSLfile("ssl/server.key") # Make sure the TS server certs are different from the origin certs -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all diff --git a/tests/gold_tests/tls/tls_cert_comp.test.py b/tests/gold_tests/tls/tls_cert_comp.test.py deleted file mode 100644 index 53d87c670b1..00000000000 --- a/tests/gold_tests/tls/tls_cert_comp.test.py +++ /dev/null @@ -1,175 +0,0 @@ -''' -Verify TLS Certificate Compression (RFC 8879) between two ATS processes. -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = ''' -Verify TLS Certificate Compression (RFC 8879) works between two ATS -instances. An edge ATS (client) connects via HTTPS to a mid ATS (server) -with cert compression enabled. The test verifies compression and -decompression succeed by checking the ssl cert compression metrics. -''' - -Test.SkipUnless(Condition.HasATSFeature('TS_HAS_CERT_COMPRESSION')) - -REPLAY_FILE = 'replay/tls_cert_compression.replay.yaml' - - -class TestCertCompression: - server_counter: int = 0 - ts_counter: int = 0 - client_counter: int = 0 - - def __init__(self, algorithm: str) -> None: - self._algorithm = algorithm - self._server = self._configure_server() - self._ts_mid = self._configure_ts_mid() - self._ts_edge = self._configure_ts_edge() - - def _configure_server(self) -> 'Process': - name = f'server-{TestCertCompression.server_counter}' - TestCertCompression.server_counter += 1 - server = Test.MakeVerifierServerProcess(name, REPLAY_FILE) - return server - - def _configure_ts_mid(self) -> 'Process': - """Mid-tier ATS that terminates TLS and forwards to origin.""" - name = f'm{TestCertCompression.ts_counter}' - TestCertCompression.ts_counter += 1 - ts = Test.MakeATSProcess(name, enable_tls=True, enable_cache=False) - - ts.addDefaultSSLFiles() - ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) - - ts.Disk.remap_config.AddLine(f'map / http://127.0.0.1:{self._server.Variables.http_port}/') - - ts.Disk.records_config.update( - { - 'proxy.config.ssl.server.cert.path': ts.Variables.SSLDir, - 'proxy.config.ssl.server.private_key.path': ts.Variables.SSLDir, - 'proxy.config.ssl.server.cert_compression.algorithms': self._algorithm, - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'ssl_cert_compress', - }) - - return ts - - def _configure_ts_edge(self) -> 'Process': - """Edge ATS that connects to mid-tier via HTTPS.""" - name = f'e{TestCertCompression.ts_counter}' - TestCertCompression.ts_counter += 1 - ts = Test.MakeATSProcess(name, enable_tls=True, enable_cache=False) - - ts.addDefaultSSLFiles() - ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) - - ts.Disk.remap_config.AddLine(f'map / https://127.0.0.1:{self._ts_mid.Variables.ssl_port}/') - - ts.Disk.records_config.update( - { - 'proxy.config.ssl.server.cert.path': ts.Variables.SSLDir, - 'proxy.config.ssl.server.private_key.path': ts.Variables.SSLDir, - 'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE', - 'proxy.config.ssl.client.cert_compression.algorithms': self._algorithm, - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'ssl_cert_compress', - }) - - return ts - - def run(self) -> None: - # Test run 1: Send traffic through the proxy chain. - tr = Test.AddTestRun(f'Send request through edge->mid with {self._algorithm} cert compression') - tr.Processes.Default.StartBefore(self._server) - tr.Processes.Default.StartBefore(self._ts_mid) - tr.Processes.Default.StartBefore(self._ts_edge) - - name = f'client-{TestCertCompression.client_counter}' - TestCertCompression.client_counter += 1 - tr.AddVerifierClientProcess(name, REPLAY_FILE, http_ports=[self._ts_edge.Variables.port]) - - # Test run 2: Check compression metric on the mid-tier (server side). - tr = Test.AddTestRun(f'Verify {self._algorithm} compression metric on mid-tier') - tr.Processes.Default.Command = (f'traffic_ctl metric get' - f' proxy.process.ssl.cert_compress.{self._algorithm}') - tr.Processes.Default.Env = self._ts_mid.Env - tr.Processes.Default.ReturnCode = 0 - tr.Processes.Default.Streams.All = Testers.ContainsExpression( - f'proxy.process.ssl.cert_compress.{self._algorithm} 1', - f'Certificate should have been compressed with {self._algorithm}') - tr.StillRunningAfter = self._ts_mid - tr.StillRunningAfter = self._ts_edge - tr.StillRunningAfter = self._server - - # Test run 3: Check decompression metric on the edge (client side). - tr = Test.AddTestRun(f'Verify {self._algorithm} decompression metric on edge') - tr.Processes.Default.Command = (f'traffic_ctl metric get' - f' proxy.process.ssl.cert_decompress.{self._algorithm}') - tr.Processes.Default.Env = self._ts_edge.Env - tr.Processes.Default.ReturnCode = 0 - tr.Processes.Default.Streams.All = Testers.ContainsExpression( - f'proxy.process.ssl.cert_decompress.{self._algorithm} 1', - f'Certificate should have been decompressed with {self._algorithm}') - tr.StillRunningAfter = self._ts_mid - tr.StillRunningAfter = self._ts_edge - tr.StillRunningAfter = self._server - - # Test run 4: Verify no failures on either side. - tr = Test.AddTestRun(f'Verify no {self._algorithm} compression failures on mid-tier') - tr.Processes.Default.Command = (f'traffic_ctl metric get' - f' proxy.process.ssl.cert_compress.{self._algorithm}_failure') - tr.Processes.Default.Env = self._ts_mid.Env - tr.Processes.Default.ReturnCode = 0 - tr.Processes.Default.Streams.All = Testers.ContainsExpression( - f'proxy.process.ssl.cert_compress.{self._algorithm}_failure 0', - f'There should be no {self._algorithm} compression failures') - tr.StillRunningAfter = self._ts_mid - tr.StillRunningAfter = self._ts_edge - tr.StillRunningAfter = self._server - - tr = Test.AddTestRun(f'Verify no {self._algorithm} decompression failures on edge') - tr.Processes.Default.Command = (f'traffic_ctl metric get' - f' proxy.process.ssl.cert_decompress.{self._algorithm}_failure') - tr.Processes.Default.Env = self._ts_edge.Env - tr.Processes.Default.ReturnCode = 0 - tr.Processes.Default.Streams.All = Testers.ContainsExpression( - f'proxy.process.ssl.cert_decompress.{self._algorithm}_failure 0', - f'There should be no {self._algorithm} decompression failures') - tr.StillRunningAfter = self._ts_mid - tr.StillRunningAfter = self._ts_edge - tr.StillRunningAfter = self._server - - -algorithms = ['zlib'] -if Condition.HasATSFeature('TS_HAS_BROTLI'): - algorithms.append('brotli') -if Condition.HasATSFeature('TS_HAS_ZSTD'): - algorithms.append('zstd') -for algorithm in algorithms: - TestCertCompression(algorithm).run() diff --git a/tests/gold_tests/tls/tls_check_cert_select_plugin.test.py b/tests/gold_tests/tls/tls_check_cert_select_plugin.test.py index 6cc069ea03e..b4b4d8f5870 100644 --- a/tests/gold_tests/tls/tls_check_cert_select_plugin.test.py +++ b/tests/gold_tests/tls/tls_check_cert_select_plugin.test.py @@ -44,18 +44,12 @@ ts.Disk.remap_config.AddLine('map / https://foo.com:{1}'.format(ts.Variables.ssl_port, server.Variables.SSL_Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: 127.0.0.1 - ssl_cert_name: signed-foo.pem - ssl_key_name: signed-foo.key - - ssl_cert_name: signed2-bar.pem - ssl_key_name: signed-bar.key - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLines( + [ + 'dest_ip=127.0.0.1 ssl_cert_name=signed-foo.pem ssl_key_name=signed-foo.key', + 'ssl_cert_name=signed2-bar.pem ssl_key_name=signed-bar.key', + 'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key', + ]) Test.PrepareTestPlugin(os.path.join(Test.Variables.AtsTestPluginsDir, 'ssl_secret_load_test.so'), ts) diff --git a/tests/gold_tests/tls/tls_check_cert_selection.test.py b/tests/gold_tests/tls/tls_check_cert_selection.test.py index bc3006193a6..d5c18867f07 100644 --- a/tests/gold_tests/tls/tls_check_cert_selection.test.py +++ b/tests/gold_tests/tls/tls_check_cert_selection.test.py @@ -41,17 +41,12 @@ ts.Disk.remap_config.AddLine('map / https://foo.com:{1}'.format(ts.Variables.ssl_port, server.Variables.SSL_Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "127.0.0.1" - ssl_cert_name: signed-foo.pem - ssl_key_name: signed-foo.key - - ssl_cert_name: signed2-bar.pem - ssl_key_name: signed-bar.key - - dest_ip: "*" - ssl_cert_name: combo.pem -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLines( + [ + 'dest_ip=127.0.0.1 ssl_cert_name=signed-foo.pem ssl_key_name=signed-foo.key', + 'ssl_cert_name=signed2-bar.pem ssl_key_name=signed-bar.key', + 'dest_ip=* ssl_cert_name=combo.pem', + ]) # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all diff --git a/tests/gold_tests/tls/tls_check_cert_selection_reload.test.py b/tests/gold_tests/tls/tls_check_cert_selection_reload.test.py index 84d526bf4fd..51d092a844e 100644 --- a/tests/gold_tests/tls/tls_check_cert_selection_reload.test.py +++ b/tests/gold_tests/tls/tls_check_cert_selection_reload.test.py @@ -39,14 +39,11 @@ ts.Disk.remap_config.AddLine('map /stuff https://foo.com:{1}'.format(ts.Variables.ssl_port, server.Variables.SSL_Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - ssl_cert_name: signed-bar.pem - ssl_key_name: signed-bar.key - - dest_ip: "*" - ssl_cert_name: combo.pem -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLines( + [ + 'ssl_cert_name=signed-bar.pem ssl_key_name=signed-bar.key', + 'dest_ip=* ssl_cert_name=combo.pem', + ]) # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all @@ -109,7 +106,8 @@ tr = Test.AddTestRun("Try with signer 1 again") # Wait for the reload to complete -tr.Processes.Default.StartBefore(server3, ready=When.FileContains(ts.Disk.diags_log.Name, 'ssl_multicert.yaml finished loading', 2)) +tr.Processes.Default.StartBefore( + server3, ready=When.FileContains(ts.Disk.diags_log.Name, 'ssl_multicert.config finished loading', 2)) tr.StillRunningAfter = ts tr.StillRunningAfter = server tr.MakeCurlCommand( diff --git a/tests/gold_tests/tls/tls_check_dual_cert_selection.test.py b/tests/gold_tests/tls/tls_check_dual_cert_selection.test.py index 5fd3c2ad71c..cff5c542803 100644 --- a/tests/gold_tests/tls/tls_check_dual_cert_selection.test.py +++ b/tests/gold_tests/tls/tls_check_dual_cert_selection.test.py @@ -48,17 +48,12 @@ ts.Disk.remap_config.AddLine('map / https://foo.com:{1}'.format(ts.Variables.ssl_port, server.Variables.SSL_Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - ssl_cert_name: signed-foo-ec.pem,signed-foo.pem - ssl_key_name: signed-foo-ec.key,signed-foo.key - - ssl_cert_name: signed-san-ec.pem,signed-san.pem - ssl_key_name: signed-san-ec.key,signed-san.key - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLines( + [ + 'ssl_cert_name=signed-foo-ec.pem,signed-foo.pem ssl_key_name=signed-foo-ec.key,signed-foo.key', + 'ssl_cert_name=signed-san-ec.pem,signed-san.pem ssl_key_name=signed-san-ec.key,signed-san.key', + 'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key', + ]) # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all diff --git a/tests/gold_tests/tls/tls_check_dual_cert_selection2.test.py b/tests/gold_tests/tls/tls_check_dual_cert_selection2.test.py index b443564c0c2..94b01e8ded3 100644 --- a/tests/gold_tests/tls/tls_check_dual_cert_selection2.test.py +++ b/tests/gold_tests/tls/tls_check_dual_cert_selection2.test.py @@ -49,14 +49,12 @@ ts.Disk.remap_config.AddLine('map / https://foo.com:{1}'.format(ts.Variables.ssl_port, server.Variables.SSL_Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - ssl_cert_name: combined-ec.pem,combined.pem - - ssl_cert_name: signed-foo-ec.pem,signed-foo.pem - - dest_ip: "*" - ssl_cert_name: signed-san-ec.pem,signed-san.pem -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLines( + [ + 'ssl_cert_name=combined-ec.pem,combined.pem', + 'ssl_cert_name=signed-foo-ec.pem,signed-foo.pem', + 'dest_ip=* ssl_cert_name=signed-san-ec.pem,signed-san.pem', + ]) # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all diff --git a/tests/gold_tests/tls/tls_check_dual_cert_selection_plugin.test.py b/tests/gold_tests/tls/tls_check_dual_cert_selection_plugin.test.py index fbc1dea836e..14b3b2fe1cc 100644 --- a/tests/gold_tests/tls/tls_check_dual_cert_selection_plugin.test.py +++ b/tests/gold_tests/tls/tls_check_dual_cert_selection_plugin.test.py @@ -50,17 +50,12 @@ ts.Disk.remap_config.AddLine('map / https://foo.com:{1}'.format(ts.Variables.ssl_port, server.Variables.SSL_Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - ssl_cert_name: signed-foo-ec.pem,signed-foo.pem - ssl_key_name: signed-foo-ec.key,signed-foo.key - - ssl_cert_name: signed-san-ec.pem,signed-san.pem - ssl_key_name: signed-san-ec.key,signed-san.key - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLines( + [ + 'ssl_cert_name=signed-foo-ec.pem,signed-foo.pem ssl_key_name=signed-foo-ec.key,signed-foo.key', + 'ssl_cert_name=signed-san-ec.pem,signed-san.pem ssl_key_name=signed-san-ec.key,signed-san.key', + 'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key', + ]) Test.PrepareTestPlugin(os.path.join(Test.Variables.AtsTestPluginsDir, 'ssl_secret_load_test.so'), ts) diff --git a/tests/gold_tests/tls/tls_client_alpn_configuration.test.py b/tests/gold_tests/tls/tls_client_alpn_configuration.test.py index 1bd5cffcd26..dc57b3ae38d 100644 --- a/tests/gold_tests/tls/tls_client_alpn_configuration.test.py +++ b/tests/gold_tests/tls/tls_client_alpn_configuration.test.py @@ -107,13 +107,7 @@ def _configure_trafficserver( 'proxy.config.ssl.client.alpn_protocols': records_config_alpn, }) - ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') conf_remap_specification = '' if conf_remap_alpn is not None: diff --git a/tests/gold_tests/tls/tls_client_cert.test.py b/tests/gold_tests/tls/tls_client_cert.test.py index 605ad843c12..c848c735faa 100644 --- a/tests/gold_tests/tls/tls_client_cert.test.py +++ b/tests/gold_tests/tls/tls_client_cert.test.py @@ -91,13 +91,7 @@ 'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE', }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine('map /case1 https://127.0.0.1:{0}/'.format(server.Variables.SSL_Port)) ts.Disk.remap_config.AddLine('map /case2 https://127.0.0.1:{0}/'.format(server2.Variables.SSL_Port)) diff --git a/tests/gold_tests/tls/tls_client_cert2.test.py b/tests/gold_tests/tls/tls_client_cert2.test.py index 4645c048483..1e6970e7462 100644 --- a/tests/gold_tests/tls/tls_client_cert2.test.py +++ b/tests/gold_tests/tls/tls_client_cert2.test.py @@ -86,13 +86,7 @@ 'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE', }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine('map /case1 https://127.0.0.1:{0}/'.format(server.Variables.SSL_Port)) ts.Disk.remap_config.AddLine('map /case2 https://127.0.0.1:{0}/'.format(server2.Variables.SSL_Port)) diff --git a/tests/gold_tests/tls/tls_client_cert2_plugin.test.py b/tests/gold_tests/tls/tls_client_cert2_plugin.test.py index c3dade36eb8..3f7924d7773 100644 --- a/tests/gold_tests/tls/tls_client_cert2_plugin.test.py +++ b/tests/gold_tests/tls/tls_client_cert2_plugin.test.py @@ -95,13 +95,7 @@ 'proxy.config.url_remap.pristine_host_hdr': 1, }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine('map /case1 https://127.0.0.1:{0}/'.format(server.Variables.SSL_Port)) ts.Disk.remap_config.AddLine('map /case2 https://127.0.0.1:{0}/'.format(server2.Variables.SSL_Port)) diff --git a/tests/gold_tests/tls/tls_client_cert_override.test.py b/tests/gold_tests/tls/tls_client_cert_override.test.py index 466eaf9eba2..38f951d5705 100644 --- a/tests/gold_tests/tls/tls_client_cert_override.test.py +++ b/tests/gold_tests/tls/tls_client_cert_override.test.py @@ -88,13 +88,7 @@ 'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE', }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map /case1 https://127.0.0.1:{0}/ @plugin=conf_remap.so @pparam=proxy.config.ssl.client.cert.filename={1} plugin=conf_remap.so @pparam=proxy.config.ssl.client.private_key.filename={2}' diff --git a/tests/gold_tests/tls/tls_client_cert_override_plugin.test.py b/tests/gold_tests/tls/tls_client_cert_override_plugin.test.py index 3b7b8a47d0e..c9af9e2cca3 100644 --- a/tests/gold_tests/tls/tls_client_cert_override_plugin.test.py +++ b/tests/gold_tests/tls/tls_client_cert_override_plugin.test.py @@ -107,13 +107,7 @@ 'proxy.config.diags.debug.tags': 'ssl_secret_load|http|ssl', }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map /case1 https://127.0.0.1:{0}/ @plugin=conf_remap.so @pparam=proxy.config.ssl.client.cert.filename={1} plugin=conf_remap.so @pparam=proxy.config.ssl.client.private_key.filename={2}' diff --git a/tests/gold_tests/tls/tls_client_cert_plugin.test.py b/tests/gold_tests/tls/tls_client_cert_plugin.test.py index 6061b46cc6b..7b8d39d91ee 100644 --- a/tests/gold_tests/tls/tls_client_cert_plugin.test.py +++ b/tests/gold_tests/tls/tls_client_cert_plugin.test.py @@ -98,13 +98,7 @@ 'proxy.config.url_remap.pristine_host_hdr': 1, }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine('map /case1 https://127.0.0.1:{0}/'.format(server.Variables.SSL_Port)) ts.Disk.remap_config.AddLine('map /case2 https://127.0.0.1:{0}/'.format(server2.Variables.SSL_Port)) @@ -197,7 +191,7 @@ # Should succeed tr3bar = Test.AddTestRun("Make request with other bar cert to first server") # Wait for the reload to complete -tr3bar.Processes.Default.StartBefore(server3, ready=When.FileContains(ts.Disk.diags_log.Name, 'sni.yaml finished loading', 2)) +tr3bar.Processes.Default.StartBefore(server3, ready=When.FileContains(ts.Disk.diags_log.Name, 'sni.yaml finished loading', 3)) tr3bar.StillRunningAfter = ts tr3bar.StillRunningAfter = server tr3bar.StillRunningAfter = server2 diff --git a/tests/gold_tests/tls/tls_client_verify.test.py b/tests/gold_tests/tls/tls_client_verify.test.py index 710a2353eae..917e48842a8 100644 --- a/tests/gold_tests/tls/tls_client_verify.test.py +++ b/tests/gold_tests/tls/tls_client_verify.test.py @@ -49,13 +49,7 @@ 'proxy.config.ssl.TLSv1_3.enabled': 0 }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Just map everything through to origin. This test is concentrating on the user-agent side ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}/'.format(server.Variables.Port)) diff --git a/tests/gold_tests/tls/tls_client_verify2.test.py b/tests/gold_tests/tls/tls_client_verify2.test.py index d8b00d36d93..657d8ebe05f 100644 --- a/tests/gold_tests/tls/tls_client_verify2.test.py +++ b/tests/gold_tests/tls/tls_client_verify2.test.py @@ -48,13 +48,7 @@ 'proxy.config.ssl.CA.cert.filename': '{0}/signer.pem'.format(ts.Variables.SSLDir) }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Just map everything through to origin. This test is concentrating on the user-agent side ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}/'.format(server.Variables.Port)) diff --git a/tests/gold_tests/tls/tls_client_verify3.test.py b/tests/gold_tests/tls/tls_client_verify3.test.py index acda0e1cb2e..27fd741b8f5 100644 --- a/tests/gold_tests/tls/tls_client_verify3.test.py +++ b/tests/gold_tests/tls/tls_client_verify3.test.py @@ -50,19 +50,8 @@ 'proxy.config.ssl.TLSv1_3.enabled': 0 }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - ssl_cert_name: bbb-signed.pem - ssl_key_name: bbb-signed.key -""".split("\n")) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('ssl_cert_name=bbb-signed.pem ssl_key_name=bbb-signed.key') +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Just map everything through to origin. This test is concentrating on the user-agent side. ts.Disk.remap_config.AddLine(f'map / http://127.0.0.1:{server.Variables.Port}/') diff --git a/tests/gold_tests/tls/tls_client_versions.test.py b/tests/gold_tests/tls/tls_client_versions.test.py index 2aed6bc2c40..134124eabfb 100644 --- a/tests/gold_tests/tls/tls_client_versions.test.py +++ b/tests/gold_tests/tls/tls_client_versions.test.py @@ -42,13 +42,7 @@ # Need no remap rules. Everything should be processed by sni # Make sure the TS server certs are different from the origin certs -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') cipher_suite = 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:RC4-SHA:RC4-MD5:AES128-SHA:AES256-SHA:DES-CBC3-SHA!SRP:!DSS:!PSK:!aNULL:!eNULL:!SSLv2' if Condition.HasOpenSSLVersion("3.0.0"): diff --git a/tests/gold_tests/tls/tls_client_versions_minmax.test.py b/tests/gold_tests/tls/tls_client_versions_minmax.test.py index e84324c459d..4c0d1742e86 100644 --- a/tests/gold_tests/tls/tls_client_versions_minmax.test.py +++ b/tests/gold_tests/tls/tls_client_versions_minmax.test.py @@ -43,13 +43,7 @@ # Need no remap rules. Everything should be processed by sni # Make sure the TS server certs are different from the origin certs -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.records_config.update( { diff --git a/tests/gold_tests/tls/tls_engine.test.py b/tests/gold_tests/tls/tls_engine.test.py index eadc8e87c76..623f4305389 100644 --- a/tests/gold_tests/tls/tls_engine.test.py +++ b/tests/gold_tests/tls/tls_engine.test.py @@ -55,13 +55,7 @@ ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.records_config.update( { 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), diff --git a/tests/gold_tests/tls/tls_forward_nonhttp.test.py b/tests/gold_tests/tls/tls_forward_nonhttp.test.py index 23c6846438b..e2ba698ede0 100644 --- a/tests/gold_tests/tls/tls_forward_nonhttp.test.py +++ b/tests/gold_tests/tls/tls_forward_nonhttp.test.py @@ -38,13 +38,7 @@ # Need no remap rules. Everything should be processed by sni # Make sure the TS server certs are different from the origin certs -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all diff --git a/tests/gold_tests/tls/tls_hooks_client_verify.test.py b/tests/gold_tests/tls/tls_hooks_client_verify.test.py index 33c8b21ec81..f25e80b4890 100644 --- a/tests/gold_tests/tls/tls_hooks_client_verify.test.py +++ b/tests/gold_tests/tls/tls_hooks_client_verify.test.py @@ -48,13 +48,7 @@ 'proxy.config.url_remap.pristine_host_hdr': 1 }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://foo.com:{1}/ https://127.0.0.1:{0}'.format(server.Variables.SSL_Port, ts.Variables.ssl_port)) diff --git a/tests/gold_tests/tls/tls_hooks_verify.test.py b/tests/gold_tests/tls/tls_hooks_verify.test.py index 8507e55e59c..dd33443f05d 100644 --- a/tests/gold_tests/tls/tls_hooks_verify.test.py +++ b/tests/gold_tests/tls/tls_hooks_verify.test.py @@ -44,13 +44,7 @@ 'proxy.config.url_remap.pristine_host_hdr': 1 }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://foo.com:{1}/ https://127.0.0.1:{0}'.format(server.Variables.SSL_Port, ts.Variables.ssl_port)) diff --git a/tests/gold_tests/tls/tls_keepalive.test.py b/tests/gold_tests/tls/tls_keepalive.test.py index 7e393ed2260..3022e4f0594 100644 --- a/tests/gold_tests/tls/tls_keepalive.test.py +++ b/tests/gold_tests/tls/tls_keepalive.test.py @@ -44,13 +44,7 @@ 'proxy.config.log.max_secs_per_buffer': 1 }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port)) diff --git a/tests/gold_tests/tls/tls_ocsp.test.py b/tests/gold_tests/tls/tls_ocsp.test.py index 8585b35f006..5a77f84aa97 100644 --- a/tests/gold_tests/tls/tls_ocsp.test.py +++ b/tests/gold_tests/tls/tls_ocsp.test.py @@ -42,20 +42,8 @@ ts.Disk.remap_config.AddLine( 'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ - - -ssl_multicert: - - - - dest_ip: "*" - ssl_cert_name: server.ocsp.pem - ssl_key_name: server.ocsp.key - ssl_ocsp_name: ocsp_response.der - - -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine( + 'dest_ip=* ssl_cert_name=server.ocsp.pem ssl_key_name=server.ocsp.key ssl_ocsp_name=ocsp_response.der') # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all diff --git a/tests/gold_tests/tls/tls_origin_session_reuse.test.py b/tests/gold_tests/tls/tls_origin_session_reuse.test.py index 6889535c7b3..5825ae64d5d 100644 --- a/tests/gold_tests/tls/tls_origin_session_reuse.test.py +++ b/tests/gold_tests/tls/tls_origin_session_reuse.test.py @@ -53,34 +53,10 @@ ts3.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) ts4.Disk.remap_config.AddLine('map / https://127.0.0.1:{0}'.format(ts3.Variables.ssl_port)) -ts1.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) -ts2.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) -ts3.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) -ts4.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts1.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') +ts2.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') +ts3.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') +ts4.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts1.Disk.records_config.update( { @@ -88,6 +64,12 @@ 'proxy.config.ssl.server.cert.path': '{0}'.format(ts1.Variables.SSLDir), 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts1.Variables.SSLDir), 'proxy.config.exec_thread.autoconfig.scale': 1.0, + 'proxy.config.ssl.session_cache.mode': 2, + 'proxy.config.ssl.session_cache.size': 4096, + 'proxy.config.ssl.session_cache.num_buckets': 256, + 'proxy.config.ssl.session_cache.skip_cache_on_bucket_contention': 0, + 'proxy.config.ssl.session_cache.timeout': 0, + 'proxy.config.ssl.session_cache.auto_clear': 1, 'proxy.config.ssl.server.session_ticket.enable': 1, 'proxy.config.ssl.origin_session_cache.enabled': 1, 'proxy.config.ssl.origin_session_cache.size': 1, @@ -101,6 +83,12 @@ 'proxy.config.ssl.server.cert.path': '{0}'.format(ts2.Variables.SSLDir), 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts2.Variables.SSLDir), 'proxy.config.exec_thread.autoconfig.scale': 1.0, + 'proxy.config.ssl.session_cache.mode': 2, + 'proxy.config.ssl.session_cache.size': 4096, + 'proxy.config.ssl.session_cache.num_buckets': 256, + 'proxy.config.ssl.session_cache.skip_cache_on_bucket_contention': 0, + 'proxy.config.ssl.session_cache.timeout': 0, + 'proxy.config.ssl.session_cache.auto_clear': 1, 'proxy.config.ssl.server.session_ticket.enable': 1, 'proxy.config.ssl.origin_session_cache.enabled': 1, 'proxy.config.ssl.origin_session_cache.size': 1, @@ -112,6 +100,12 @@ 'proxy.config.ssl.server.cert.path': '{0}'.format(ts3.Variables.SSLDir), 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts3.Variables.SSLDir), 'proxy.config.exec_thread.autoconfig.scale': 1.0, + 'proxy.config.ssl.session_cache.mode': 2, + 'proxy.config.ssl.session_cache.size': 4096, + 'proxy.config.ssl.session_cache.num_buckets': 256, + 'proxy.config.ssl.session_cache.skip_cache_on_bucket_contention': 0, + 'proxy.config.ssl.session_cache.timeout': 0, + 'proxy.config.ssl.session_cache.auto_clear': 1, 'proxy.config.ssl.server.session_ticket.enable': 1, 'proxy.config.ssl.origin_session_cache.enabled': 1, 'proxy.config.ssl.origin_session_cache.size': 1, @@ -125,6 +119,12 @@ 'proxy.config.ssl.server.cert.path': '{0}'.format(ts4.Variables.SSLDir), 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts4.Variables.SSLDir), 'proxy.config.exec_thread.autoconfig.scale': 1.0, + 'proxy.config.ssl.session_cache.mode': 2, + 'proxy.config.ssl.session_cache.size': 4096, + 'proxy.config.ssl.session_cache.num_buckets': 256, + 'proxy.config.ssl.session_cache.skip_cache_on_bucket_contention': 0, + 'proxy.config.ssl.session_cache.timeout': 0, + 'proxy.config.ssl.session_cache.auto_clear': 1, 'proxy.config.ssl.server.session_ticket.enable': 1, 'proxy.config.ssl.origin_session_cache.enabled': 0, 'proxy.config.ssl.origin_session_cache.size': 1, diff --git a/tests/gold_tests/tls/tls_partial_blind_tunnel.test.py b/tests/gold_tests/tls/tls_partial_blind_tunnel.test.py index 2a9dee38aa1..eab7b9fca10 100644 --- a/tests/gold_tests/tls/tls_partial_blind_tunnel.test.py +++ b/tests/gold_tests/tls/tls_partial_blind_tunnel.test.py @@ -36,13 +36,7 @@ # Need no remap rules. Everything should be processed by sni # Make sure the TS server certs are different from the origin certs -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: signed-foo.pem - ssl_key_name: signed-foo.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=signed-foo.pem ssl_key_name=signed-foo.key') # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all diff --git a/tests/gold_tests/tls/tls_session_key_logging.test.py b/tests/gold_tests/tls/tls_session_key_logging.test.py index ecb269d6d6a..7ace4a076aa 100644 --- a/tests/gold_tests/tls/tls_session_key_logging.test.py +++ b/tests/gold_tests/tls/tls_session_key_logging.test.py @@ -55,13 +55,7 @@ def setupTS(self, enable_secrets_logging): 'proxy.config.diags.debug.enabled': 1, 'proxy.config.diags.debug.tags': 'ssl_keylog' }) - self.ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + self.ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') self.ts.Disk.remap_config.AddLine(f'map / https://127.0.0.1:{self.server.Variables.https_port}') keylog_file = os.path.join(self.ts.Variables.LOGDIR, "tls_secrets.txt") diff --git a/tests/gold_tests/tls/tls_session_reuse.test.py b/tests/gold_tests/tls/tls_session_reuse.test.py index 3529be510fa..2d368483137 100644 --- a/tests/gold_tests/tls/tls_session_reuse.test.py +++ b/tests/gold_tests/tls/tls_session_reuse.test.py @@ -26,6 +26,7 @@ # Define default ATS ts1 = Test.MakeATSProcess("ts1", enable_tls=True) ts2 = Test.MakeATSProcess("ts2", enable_tls=True) +ts3 = Test.MakeATSProcess("ts3", enable_tls=True) server = Test.MakeOriginServer("server") # Add info the origin server responses @@ -38,123 +39,124 @@ ts1.addSSLfile("ssl/server.key") ts2.addSSLfile("ssl/server.pem") ts2.addSSLfile("ssl/server.key") +ts3.addSSLfile("ssl/server.pem") +ts3.addSSLfile("ssl/server.key") ts1.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) ts2.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) +ts3.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) -ts1.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) -ts2.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts1.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') +ts2.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') +ts3.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts1.Disk.records_config.update( { - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'ssl', 'proxy.config.ssl.server.cert.path': '{0}'.format(ts1.Variables.SSLDir), 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts1.Variables.SSLDir), 'proxy.config.ssl.server.cipher_suite': 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA', 'proxy.config.exec_thread.autoconfig.scale': 1.0, - 'proxy.config.ssl.server.session_ticket.enable': 1, - 'proxy.config.ssl.server.session_ticket.number': 2, + 'proxy.config.ssl.session_cache.mode': 2, + 'proxy.config.ssl.session_cache.size': 4096, + 'proxy.config.ssl.session_cache.num_buckets': 256, + 'proxy.config.ssl.session_cache.skip_cache_on_bucket_contention': 0, + 'proxy.config.ssl.session_cache.timeout': 0, + 'proxy.config.ssl.session_cache.auto_clear': 1, + 'proxy.config.ssl.server.session_ticket.enable': 0, }) ts2.Disk.records_config.update( { - 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'ssl', 'proxy.config.ssl.server.cert.path': '{0}'.format(ts2.Variables.SSLDir), 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts2.Variables.SSLDir), 'proxy.config.ssl.server.cipher_suite': 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA', 'proxy.config.exec_thread.autoconfig.scale': 1.0, - 'proxy.config.ssl.server.session_ticket.enable': 0, - 'proxy.config.ssl.server.session_ticket.number': 0, + 'proxy.config.ssl.session_cache.mode': 2, + 'proxy.config.ssl.session_cache.size': 4096, + 'proxy.config.ssl.session_cache.num_buckets': 256, + 'proxy.config.ssl.session_cache.skip_cache_on_bucket_contention': 0, + 'proxy.config.ssl.session_cache.timeout': 0, + 'proxy.config.ssl.session_cache.auto_clear': 1, + 'proxy.config.ssl.server.session_ticket.enable': 1, + }) +ts3.Disk.records_config.update( + { + 'proxy.config.ssl.server.cert.path': '{0}'.format(ts3.Variables.SSLDir), + 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts3.Variables.SSLDir), + 'proxy.config.ssl.server.cipher_suite': + 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA', + 'proxy.config.exec_thread.autoconfig.scale': 1.0, + 'proxy.config.ssl.session_cache.mode': 0, + 'proxy.config.ssl.session_cache.size': 4096, + 'proxy.config.ssl.session_cache.num_buckets': 256, + 'proxy.config.ssl.session_cache.skip_cache_on_bucket_contention': 0, + 'proxy.config.ssl.session_cache.timeout': 0, + 'proxy.config.ssl.session_cache.auto_clear': 1, + 'proxy.config.ssl.server.session_ticket.enable': 1, }) -def check_session(output_path, tls_ver, reuse_count): +def check_session(ev, test): retval = False - f = open(output_path, 'r') + f = open(test.GetContent(ev), 'r') + err = "Session ids match" if not f: - err = "Failed to open {0}".format(output_path) - return (retval, "Check session is reused", err) + err = "Failed to open {0}".format(openssl_output) + return (retval, "Check that session ids match", err) content = f.read() - match = re.findall(f'Reused, {tls_ver}', content) - if len(match) == reuse_count: - retval = True - err = "Reused successfully {0} times".format(len(match)) + match = re.findall('Session-ID: ([0-9A-F]+)', content) + + if match: + if all(i == j for i, j in zip(match, match[1:])): + err = "{0} reused successfully {1} times".format(match[0], len(match) - 1) + retval = True + else: + err = "Session is not being reused as expected" else: - err = "Session is not being reused as expected" - f.close() - return (retval, "Check session is reused", err) - - -tr1 = Test.AddTestRun("TLSv1.2 Session Resumption Enabled") + err = "Didn't find session id" + return (retval, "Check that session ids match", err) + + +tr = Test.AddTestRun("TLSv1.2 Session ID") +tr.Command = \ + 'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_out {1} && ' \ + 'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_in {1} && ' \ + 'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_in {1} && ' \ + 'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_in {1} && ' \ + 'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_in {1} && ' \ + 'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_in {1}' \ + .format(ts1.Variables.ssl_port, os.path.join(Test.RunDirectory, 'sess.dat')) +tr.ReturnCode = 0 +tr.Processes.Default.StartBefore(server) +tr.Processes.Default.StartBefore(ts1) +tr.Processes.Default.Streams.All.Content = Testers.Lambda(check_session) +tr.StillRunningAfter = server + +tr1 = Test.AddTestRun("TLSv1.2 Session Ticket") tr1.Command = \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_out {1} -tls1_2 && ' \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in {1} -tls1_2 && ' \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in {1} -tls1_2 && ' \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in {1} -tls1_2 && ' \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in {1} -tls1_2 && ' \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in {1} -tls1_2' \ - .format(ts1.Variables.ssl_port, os.path.join(Test.RunDirectory, 'sess1.dat')) + 'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -sess_out {1} && ' \ + 'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -sess_in {1} && ' \ + 'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -sess_in {1} && ' \ + 'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -sess_in {1} && ' \ + 'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -sess_in {1} && ' \ + 'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -sess_in {1}' \ + .format(ts2.Variables.ssl_port, os.path.join(Test.RunDirectory, 'sess.dat')) tr1.ReturnCode = 0 -tr1.Processes.Default.StartBefore(server) -tr1.Processes.Default.StartBefore(ts1) -tr1.Processes.Default.Streams.All.Content = Testers.Lambda( - lambda info, tester: check_session(tr1.Processes.Default.Streams.All.AbsPath, 'TLSv1.2', 5)) -tr1.StillRunningAfter += server -tr1.StillRunningAfter += ts1 - -tr2 = Test.AddTestRun("TLSv1.3 Session Resumption Enabled") +tr1.Processes.Default.StartBefore(ts2) +tr1.Processes.Default.Streams.All.Content = Testers.Lambda(check_session) +tr1.StillRunningAfter = server + +tr2 = Test.AddTestRun("Disabled Session Cache") tr2.Command = \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_out {1} -tls1_2 && ' \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in {1} -tls1_2 && ' \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in {1} -tls1_2 && ' \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in {1} -tls1_2 && ' \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in {1} -tls1_2 && ' \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in {1} -tls1_2' \ - .format(ts1.Variables.ssl_port, os.path.join(Test.RunDirectory, 'sess2.dat')) + 'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_out {1} && ' \ + 'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_in {1} && ' \ + 'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_in {1} && ' \ + 'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_in {1} && ' \ + 'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_in {1} && ' \ + 'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_in {1}' \ + .format(ts3.Variables.ssl_port, os.path.join(Test.RunDirectory, 'sess.dat')) tr2.ReturnCode = 0 -tr2.Processes.Default.Streams.All.Content = Testers.Lambda( - lambda info, tester: check_session(tr2.Processes.Default.Streams.All.AbsPath, 'TLSv1.2', 5)) -tr2.StillRunningAfter += server - -tr3 = Test.AddTestRun("TLSv1.2 Session Resumption Disabled") -tr3.Command = \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_out {1} -tls1_2 && ' \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in {1} -tls1_2 && ' \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in {1} -tls1_2 && ' \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in {1} -tls1_2 && ' \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in {1} -tls1_2 && ' \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in {1} -tls1_2' \ - .format(ts2.Variables.ssl_port, os.path.join(Test.RunDirectory, 'sess3.dat')) -tr3.Processes.Default.StartBefore(ts2) -tr3.Processes.Default.Streams.All = Testers.ExcludesExpression('Reused', '') -tr3.Processes.Default.Streams.All += Testers.ContainsExpression('TLSv1.2', '') -tr3.StillRunningAfter += server -tr3.StillRunningAfter += ts2 - -tr4 = Test.AddTestRun("TLSv1.3 Session Resumption Disabled") -tr4.Command = \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_out {1} -tls1_3 && ' \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in {1} -tls1_3 && ' \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in {1} -tls1_3 && ' \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in {1} -tls1_3 && ' \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in {1} -tls1_3 && ' \ - 'printf "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in {1} -tls1_3' \ - .format(ts2.Variables.ssl_port, os.path.join(Test.RunDirectory, 'sess4.dat')) -tr4.Processes.Default.Streams.All = Testers.ExcludesExpression('Reused', '') -tr4.Processes.Default.Streams.All += Testers.ContainsExpression('TLSv1.3', '') +tr2.Processes.Default.StartBefore(ts3) +tr2.Processes.Default.Streams.All = Testers.ExcludesExpression('Reused', '') diff --git a/tests/gold_tests/tls/tls_sni_groups.test.py b/tests/gold_tests/tls/tls_sni_groups.test.py index 79e9b208288..16c1cce280a 100644 --- a/tests/gold_tests/tls/tls_sni_groups.test.py +++ b/tests/gold_tests/tls/tls_sni_groups.test.py @@ -37,13 +37,7 @@ # Need no remap rules. Everything should be processed by sni # Make sure the TS server certs are different from the origin certs -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.records_config.update( { diff --git a/tests/gold_tests/tls/tls_sni_host_policy.test.py b/tests/gold_tests/tls/tls_sni_host_policy.test.py index 0f711a96df7..6a2e7477e61 100644 --- a/tests/gold_tests/tls/tls_sni_host_policy.test.py +++ b/tests/gold_tests/tls/tls_sni_host_policy.test.py @@ -52,13 +52,7 @@ 'proxy.config.diags.debug.tags': 'ssl', }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Just map everything through to origin. This test is concentrating on the user-agent side ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}/'.format(server.Variables.Port)) diff --git a/tests/gold_tests/tls/tls_sni_ip_allow.test.py b/tests/gold_tests/tls/tls_sni_ip_allow.test.py index fa23342639e..84cb9f6b9f3 100644 --- a/tests/gold_tests/tls/tls_sni_ip_allow.test.py +++ b/tests/gold_tests/tls/tls_sni_ip_allow.test.py @@ -121,13 +121,7 @@ def _configure_trafficserver(self, tr: 'TestRun', connect_type: int, dns: 'Proce ' ip_allow: 1.2.3.4', ]) ts.addDefaultSSLFiles() - ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine(f'map / http://remapped.backend.server.com:{server.Variables.http_port}/') ts.Disk.records_config.update( { diff --git a/tests/gold_tests/tls/tls_sni_ticket.test.py b/tests/gold_tests/tls/tls_sni_ticket.test.py index d9d6d62abb2..e85f0bb2cd0 100644 --- a/tests/gold_tests/tls/tls_sni_ticket.test.py +++ b/tests/gold_tests/tls/tls_sni_ticket.test.py @@ -84,13 +84,7 @@ def setupTS( ts.addSSLfile('ssl/server.pem') ts.addSSLfile('ssl/server.key') ts.Disk.remap_config.AddLine(f'map / http://127.0.0.1:{self.server.Variables.Port}') - ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.records_config.update( { diff --git a/tests/gold_tests/tls/tls_sni_with_port.test.py b/tests/gold_tests/tls/tls_sni_with_port.test.py index cadda0c677b..3f107d6765a 100644 --- a/tests/gold_tests/tls/tls_sni_with_port.test.py +++ b/tests/gold_tests/tls/tls_sni_with_port.test.py @@ -134,13 +134,7 @@ def _configure_traffic_server(self, tr: "TestRun", server_one: "Process", server f" tunnel_route: localhost:{server_two.Variables.https_port}", ]) - ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) + ts.Disk.ssl_multicert_config.AddLine(f"dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key") self._ts = ts diff --git a/tests/gold_tests/tls/tls_sni_yaml_reload.test.py b/tests/gold_tests/tls/tls_sni_yaml_reload.test.py index 133ecaf8964..8e2e0b2772b 100644 --- a/tests/gold_tests/tls/tls_sni_yaml_reload.test.py +++ b/tests/gold_tests/tls/tls_sni_yaml_reload.test.py @@ -45,21 +45,9 @@ ts.Disk.remap_config.AddLine(f'map / http://127.0.0.1:{server.Variables.Port}') -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.sni_yaml.AddLines( f""" diff --git a/tests/gold_tests/tls/tls_ticket.test.py b/tests/gold_tests/tls/tls_ticket.test.py index 28eec527bce..fa0e65d7546 100644 --- a/tests/gold_tests/tls/tls_ticket.test.py +++ b/tests/gold_tests/tls/tls_ticket.test.py @@ -41,20 +41,8 @@ ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) ts2.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) -ts2.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') +ts2.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.records_config.update( { diff --git a/tests/gold_tests/tls/tls_tunnel.test.py b/tests/gold_tests/tls/tls_tunnel.test.py index ab020181112..e2de6524af6 100644 --- a/tests/gold_tests/tls/tls_tunnel.test.py +++ b/tests/gold_tests/tls/tls_tunnel.test.py @@ -74,13 +74,7 @@ # Need no remap rules. Everything should be processed by sni # Make sure the TS server certs are different from the origin certs -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: signed-foo.pem - ssl_key_name: signed-foo.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=signed-foo.pem ssl_key_name=signed-foo.key') # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all diff --git a/tests/gold_tests/tls/tls_tunnel_forward.test.py b/tests/gold_tests/tls/tls_tunnel_forward.test.py index dc9e4a427ae..472797d10ae 100644 --- a/tests/gold_tests/tls/tls_tunnel_forward.test.py +++ b/tests/gold_tests/tls/tls_tunnel_forward.test.py @@ -54,13 +54,7 @@ # Need no remap rules. Everything should be processed by sni # Make sure the TS server certs are different from the origin certs -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: signed-foo.pem - ssl_key_name: signed-foo.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=signed-foo.pem ssl_key_name=signed-foo.key') # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all diff --git a/tests/gold_tests/tls/tls_verify.test.py b/tests/gold_tests/tls/tls_verify.test.py index 890bae18ce6..64b32407f20 100644 --- a/tests/gold_tests/tls/tls_verify.test.py +++ b/tests/gold_tests/tls/tls_verify.test.py @@ -76,13 +76,7 @@ ts.Disk.remap_config.AddLine('map https://foo.wild.com/ https://127.0.0.1:{0}'.format(server_wild.Variables.SSL_Port)) ts.Disk.remap_config.AddLine('map https://foo_bar.wild.com/ https://127.0.0.1:{0}'.format(server_wild.Variables.SSL_Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all diff --git a/tests/gold_tests/tls/tls_verify2.test.py b/tests/gold_tests/tls/tls_verify2.test.py index 72f261eab0e..db54aebba9d 100644 --- a/tests/gold_tests/tls/tls_verify2.test.py +++ b/tests/gold_tests/tls/tls_verify2.test.py @@ -64,13 +64,7 @@ ts.Disk.remap_config.AddLine('map https://bad_bar.com/ https://127.0.0.1:{0}'.format(server_bar.Variables.SSL_Port)) ts.Disk.remap_config.AddLine('map / https://127.0.0.1:{0}'.format(server.Variables.SSL_Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all diff --git a/tests/gold_tests/tls/tls_verify3.test.py b/tests/gold_tests/tls/tls_verify3.test.py index 0603499aa34..08920ffc70c 100644 --- a/tests/gold_tests/tls/tls_verify3.test.py +++ b/tests/gold_tests/tls/tls_verify3.test.py @@ -68,13 +68,7 @@ 'map https://bob.bar.com:{1}/ https://127.0.0.1:{0}'.format(server_bar.Variables.SSL_Port, ts.Variables.ssl_port)) ts.Disk.remap_config.AddLine('map / https://127.0.0.1:{0}'.format(server.Variables.SSL_Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all diff --git a/tests/gold_tests/tls/tls_verify4.test.py b/tests/gold_tests/tls/tls_verify4.test.py index 437d582bd58..bab09093229 100644 --- a/tests/gold_tests/tls/tls_verify4.test.py +++ b/tests/gold_tests/tls/tls_verify4.test.py @@ -65,13 +65,7 @@ ts.Disk.remap_config.AddLine('map https://bad_bar.com/ https://127.0.0.1:{0}'.format(server_bar.Variables.SSL_Port)) ts.Disk.remap_config.AddLine('map / https://127.0.0.1:{0}'.format(server.Variables.SSL_Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all diff --git a/tests/gold_tests/tls/tls_verify_base.test.py b/tests/gold_tests/tls/tls_verify_base.test.py index 7617bee1c14..7526cfa2dc0 100644 --- a/tests/gold_tests/tls/tls_verify_base.test.py +++ b/tests/gold_tests/tls/tls_verify_base.test.py @@ -64,13 +64,7 @@ ts.Disk.remap_config.AddLine('map https://bar.com/ https://127.0.0.1:{0}'.format(server_bar.Variables.SSL_Port)) ts.Disk.remap_config.AddLine('map https://bad_bar.com/ https://127.0.0.1:{0}'.format(server_bar.Variables.SSL_Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all diff --git a/tests/gold_tests/tls/tls_verify_ca_override.test.py b/tests/gold_tests/tls/tls_verify_ca_override.test.py index 610bc016031..2de7f424d79 100644 --- a/tests/gold_tests/tls/tls_verify_ca_override.test.py +++ b/tests/gold_tests/tls/tls_verify_ca_override.test.py @@ -71,13 +71,7 @@ 'map /badcase2 https://127.0.0.1:{0}/ @plugin=conf_remap.so @pparam=proxy.config.ssl.client.CA.cert.filename={1}/{2}'.format( server2.Variables.SSL_Port, ts.Variables.SSLDir, "signer.pem")) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all diff --git a/tests/gold_tests/tls/tls_verify_not_pristine.test.py b/tests/gold_tests/tls/tls_verify_not_pristine.test.py index 2d7723d7038..2429f256208 100644 --- a/tests/gold_tests/tls/tls_verify_not_pristine.test.py +++ b/tests/gold_tests/tls/tls_verify_not_pristine.test.py @@ -53,13 +53,7 @@ ts.Disk.remap_config.AddLine( 'map https://foo.com:{0}/ https://bar.com:{1}'.format(ts.Variables.ssl_port, server_foo.Variables.SSL_Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all diff --git a/tests/gold_tests/tls/tls_verify_override.test.py b/tests/gold_tests/tls/tls_verify_override.test.py index 5fc323da3cf..747a4d804cd 100644 --- a/tests/gold_tests/tls/tls_verify_override.test.py +++ b/tests/gold_tests/tls/tls_verify_override.test.py @@ -101,13 +101,7 @@ 'map /snipolicybarhost https://bar.com:{0} @plugin=conf_remap.so @pparam=proxy.config.ssl.client.verify.server.properties=NAME @plugin=conf_remap.so @pparam=proxy.config.ssl.client.verify.server.policy=ENFORCED @plugin=conf_remap.so @pparam=proxy.config.ssl.client.sni_policy=host' .format(server_bar.Variables.SSL_Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all diff --git a/tests/gold_tests/tls/tls_verify_override_base.test.py b/tests/gold_tests/tls/tls_verify_override_base.test.py index ab07dbaeed8..be8179ddacf 100644 --- a/tests/gold_tests/tls/tls_verify_override_base.test.py +++ b/tests/gold_tests/tls/tls_verify_override_base.test.py @@ -100,13 +100,7 @@ 'map /snipolicybarservername https://bar.com:{0} @plugin=conf_remap.so @pparam=proxy.config.ssl.client.verify.server.properties=NAME @plugin=conf_remap.so @pparam=proxy.config.ssl.client.verify.server.policy=ENFORCED @plugin=conf_remap.so @pparam=proxy.config.ssl.client.sni_policy=server_name' .format(server_bar.Variables.SSL_Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # Case 1, global config policy=permissive properties=signature # override for foo.com policy=enforced properties=all diff --git a/tests/gold_tests/tls/tls_verify_override_sni.test.py b/tests/gold_tests/tls/tls_verify_override_sni.test.py index f671d44bd5e..97391eb876d 100644 --- a/tests/gold_tests/tls/tls_verify_override_sni.test.py +++ b/tests/gold_tests/tls/tls_verify_override_sni.test.py @@ -83,13 +83,7 @@ 'map http://foo.com/overrideproperties https://bar.com:{0} @plugin=conf_remap.so @pparam=proxy.config.ssl.client.verify.server.properties=SIGNATURE' .format(server_foo.Variables.SSL_Port)) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') # global config policy=permissive properties=all ts.Disk.records_config.update( diff --git a/tests/gold_tests/tls_hooks/tls_hooks.test.py b/tests/gold_tests/tls_hooks/tls_hooks.test.py index db73db2a786..ffb20503269 100644 --- a/tests/gold_tests/tls_hooks/tls_hooks.test.py +++ b/tests/gold_tests/tls_hooks/tls_hooks.test.py @@ -43,13 +43,7 @@ 'proxy.config.ssl.TLSv1_3.enabled': 0, }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port)) diff --git a/tests/gold_tests/tls_hooks/tls_hooks10.test.py b/tests/gold_tests/tls_hooks/tls_hooks10.test.py index 0c32f87d231..fb52206ed28 100644 --- a/tests/gold_tests/tls_hooks/tls_hooks10.test.py +++ b/tests/gold_tests/tls_hooks/tls_hooks10.test.py @@ -41,13 +41,7 @@ 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port)) diff --git a/tests/gold_tests/tls_hooks/tls_hooks11.test.py b/tests/gold_tests/tls_hooks/tls_hooks11.test.py index 2c5a6b72d3f..f92b7dc5931 100644 --- a/tests/gold_tests/tls_hooks/tls_hooks11.test.py +++ b/tests/gold_tests/tls_hooks/tls_hooks11.test.py @@ -42,13 +42,7 @@ 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port)) diff --git a/tests/gold_tests/tls_hooks/tls_hooks12.test.py b/tests/gold_tests/tls_hooks/tls_hooks12.test.py index aed35b57768..78f369691f7 100644 --- a/tests/gold_tests/tls_hooks/tls_hooks12.test.py +++ b/tests/gold_tests/tls_hooks/tls_hooks12.test.py @@ -41,13 +41,7 @@ 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port)) diff --git a/tests/gold_tests/tls_hooks/tls_hooks13.test.py b/tests/gold_tests/tls_hooks/tls_hooks13.test.py index 515fd584936..d9820afe05d 100644 --- a/tests/gold_tests/tls_hooks/tls_hooks13.test.py +++ b/tests/gold_tests/tls_hooks/tls_hooks13.test.py @@ -41,13 +41,7 @@ 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://example.com:{0} https://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.SSL_Port)) diff --git a/tests/gold_tests/tls_hooks/tls_hooks14.test.py b/tests/gold_tests/tls_hooks/tls_hooks14.test.py index 4128b58497c..8364fac306b 100644 --- a/tests/gold_tests/tls_hooks/tls_hooks14.test.py +++ b/tests/gold_tests/tls_hooks/tls_hooks14.test.py @@ -42,13 +42,7 @@ 'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE', }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://example.com:{0} https://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.SSL_Port)) diff --git a/tests/gold_tests/tls_hooks/tls_hooks15.test.py b/tests/gold_tests/tls_hooks/tls_hooks15.test.py index e43a26f883a..fd2aa270b5c 100644 --- a/tests/gold_tests/tls_hooks/tls_hooks15.test.py +++ b/tests/gold_tests/tls_hooks/tls_hooks15.test.py @@ -41,13 +41,7 @@ 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://example.com:{0} https://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.SSL_Port)) diff --git a/tests/gold_tests/tls_hooks/tls_hooks16.test.py b/tests/gold_tests/tls_hooks/tls_hooks16.test.py index 9a8a083be88..78042926341 100644 --- a/tests/gold_tests/tls_hooks/tls_hooks16.test.py +++ b/tests/gold_tests/tls_hooks/tls_hooks16.test.py @@ -44,13 +44,7 @@ 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://example.com:{1} http://127.0.0.1:{0}'.format(server.Variables.Port, ts.Variables.ssl_port)) diff --git a/tests/gold_tests/tls_hooks/tls_hooks17.test.py b/tests/gold_tests/tls_hooks/tls_hooks17.test.py index 0b54d7a9e45..34a48e98d96 100644 --- a/tests/gold_tests/tls_hooks/tls_hooks17.test.py +++ b/tests/gold_tests/tls_hooks/tls_hooks17.test.py @@ -44,13 +44,7 @@ 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://example.com:{1} http://127.0.0.1:{0}'.format(server.Variables.Port, ts.Variables.ssl_port)) diff --git a/tests/gold_tests/tls_hooks/tls_hooks18.test.py b/tests/gold_tests/tls_hooks/tls_hooks18.test.py index c7e03faeb6d..64e7b3b089a 100644 --- a/tests/gold_tests/tls_hooks/tls_hooks18.test.py +++ b/tests/gold_tests/tls_hooks/tls_hooks18.test.py @@ -44,13 +44,7 @@ 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://example.com:{1} http://127.0.0.1:{0}'.format(server.Variables.Port, ts.Variables.ssl_port)) diff --git a/tests/gold_tests/tls_hooks/tls_hooks2.test.py b/tests/gold_tests/tls_hooks/tls_hooks2.test.py index 0323002f1c9..66afffac595 100644 --- a/tests/gold_tests/tls_hooks/tls_hooks2.test.py +++ b/tests/gold_tests/tls_hooks/tls_hooks2.test.py @@ -42,13 +42,7 @@ 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port)) diff --git a/tests/gold_tests/tls_hooks/tls_hooks3.test.py b/tests/gold_tests/tls_hooks/tls_hooks3.test.py index 23aeaa9797b..006eecb479f 100644 --- a/tests/gold_tests/tls_hooks/tls_hooks3.test.py +++ b/tests/gold_tests/tls_hooks/tls_hooks3.test.py @@ -42,13 +42,7 @@ 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port)) diff --git a/tests/gold_tests/tls_hooks/tls_hooks4.test.py b/tests/gold_tests/tls_hooks/tls_hooks4.test.py index 7068c838e4f..f7e0742baaf 100644 --- a/tests/gold_tests/tls_hooks/tls_hooks4.test.py +++ b/tests/gold_tests/tls_hooks/tls_hooks4.test.py @@ -42,13 +42,7 @@ 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port)) diff --git a/tests/gold_tests/tls_hooks/tls_hooks6.test.py b/tests/gold_tests/tls_hooks/tls_hooks6.test.py index 1b2635f4349..8e943fd3a99 100644 --- a/tests/gold_tests/tls_hooks/tls_hooks6.test.py +++ b/tests/gold_tests/tls_hooks/tls_hooks6.test.py @@ -42,13 +42,7 @@ 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port)) diff --git a/tests/gold_tests/tls_hooks/tls_hooks7.test.py b/tests/gold_tests/tls_hooks/tls_hooks7.test.py index e664b717daa..29a4490e281 100644 --- a/tests/gold_tests/tls_hooks/tls_hooks7.test.py +++ b/tests/gold_tests/tls_hooks/tls_hooks7.test.py @@ -42,13 +42,7 @@ 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port)) diff --git a/tests/gold_tests/tls_hooks/tls_hooks8.test.py b/tests/gold_tests/tls_hooks/tls_hooks8.test.py index be08cc17371..721ce3630b4 100644 --- a/tests/gold_tests/tls_hooks/tls_hooks8.test.py +++ b/tests/gold_tests/tls_hooks/tls_hooks8.test.py @@ -42,13 +42,7 @@ 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port)) diff --git a/tests/gold_tests/tls_hooks/tls_hooks9.test.py b/tests/gold_tests/tls_hooks/tls_hooks9.test.py index 343c29f54ae..cf77135203d 100644 --- a/tests/gold_tests/tls_hooks/tls_hooks9.test.py +++ b/tests/gold_tests/tls_hooks/tls_hooks9.test.py @@ -42,13 +42,7 @@ 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port)) diff --git a/tests/gold_tests/traffic_ctl/convert_plugin_config/convert_plugin_config.test.py b/tests/gold_tests/traffic_ctl/convert_plugin_config/convert_plugin_config.test.py deleted file mode 100644 index 4e691730a98..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_plugin_config/convert_plugin_config.test.py +++ /dev/null @@ -1,68 +0,0 @@ -''' -Test the traffic_ctl config convert plugin_config command. -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = 'Test traffic_ctl config convert plugin_config command.' - -ts = Test.MakeATSProcess("ts", enable_cache=False) - -# Test 1: Basic plugin.config conversion. -tr = Test.AddTestRun("Test basic plugin.config conversion") -tr.Setup.Copy('legacy_config/basic.config') -tr.Processes.Default.Command = 'traffic_ctl config convert plugin_config basic.config -' -tr.Processes.Default.Streams.stdout = "gold/basic.yaml" -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Env = ts.Env -tr.Processes.Default.StartBefore(ts) -tr.StillRunningAfter = ts - -# Test 2: Commented-out lines become enabled: false. -tr = Test.AddTestRun("Test commented lines converted to disabled entries") -tr.Setup.Copy('legacy_config/commented.config') -tr.Processes.Default.Command = 'traffic_ctl config convert plugin_config commented.config -' -tr.Processes.Default.Streams.stdout = "gold/commented.yaml" -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Env = ts.Env -tr.StillRunningAfter = ts - -# Test 3: Quoted arguments. -tr = Test.AddTestRun("Test plugin.config with quoted arguments") -tr.Setup.Copy('legacy_config/quoted.config') -tr.Processes.Default.Command = 'traffic_ctl config convert plugin_config quoted.config -' -tr.Processes.Default.Streams.stdout = "gold/quoted.yaml" -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Env = ts.Env -tr.StillRunningAfter = ts - -# Test 4: Output to file instead of stdout. -tr = Test.AddTestRun("Test output to file") -tr.Setup.Copy('legacy_config/basic.config') -tr.Processes.Default.Command = 'traffic_ctl config convert plugin_config basic.config generated.yaml > /dev/null && cat generated.yaml' -tr.Processes.Default.Streams.stdout = "gold/basic.yaml" -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Env = ts.Env -tr.StillRunningAfter = ts - -# Test 5: --skip-disabled omits commented-out plugins from output. -tr = Test.AddTestRun("Test --skip-disabled drops disabled entries") -tr.Setup.Copy('legacy_config/commented.config') -tr.Processes.Default.Command = 'traffic_ctl config convert plugin_config --skip-disabled commented.config -' -tr.Processes.Default.Streams.stdout = "gold/skip_disabled.yaml" -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Env = ts.Env -tr.StillRunningAfter = ts diff --git a/tests/gold_tests/traffic_ctl/convert_plugin_config/gold/basic.yaml b/tests/gold_tests/traffic_ctl/convert_plugin_config/gold/basic.yaml deleted file mode 100644 index 9b7c6467781..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_plugin_config/gold/basic.yaml +++ /dev/null @@ -1,10 +0,0 @@ -plugins: - - path: stats_over_http.so - params: - - _stats - - path: header_rewrite.so - params: - - /etc/trafficserver/rewrite.conf - - path: xdebug.so - params: - - --enable=x-cache diff --git a/tests/gold_tests/traffic_ctl/convert_plugin_config/gold/commented.yaml b/tests/gold_tests/traffic_ctl/convert_plugin_config/gold/commented.yaml deleted file mode 100644 index 9dfecffd241..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_plugin_config/gold/commented.yaml +++ /dev/null @@ -1,17 +0,0 @@ -plugins: - - path: stats_over_http.so - params: - - _stats - - path: cache_promote.so - enabled: false - params: - - --policy=lru - - --buckets=100 - - path: header_rewrite.so - params: - - /etc/trafficserver/rewrite.conf - - path: slice.so - enabled: false - params: - - --blockbytes=1048576 - - path: xdebug.so diff --git a/tests/gold_tests/traffic_ctl/convert_plugin_config/gold/quoted.yaml b/tests/gold_tests/traffic_ctl/convert_plugin_config/gold/quoted.yaml deleted file mode 100644 index 28fbf1a0231..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_plugin_config/gold/quoted.yaml +++ /dev/null @@ -1,8 +0,0 @@ -plugins: - - path: regex_remap.so - params: - - maps_https.config - - --no-query - - path: header_rewrite.so - params: - - /etc/trafficserver/my rules.conf diff --git a/tests/gold_tests/traffic_ctl/convert_plugin_config/gold/skip_disabled.yaml b/tests/gold_tests/traffic_ctl/convert_plugin_config/gold/skip_disabled.yaml deleted file mode 100644 index ecb17a980b4..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_plugin_config/gold/skip_disabled.yaml +++ /dev/null @@ -1,8 +0,0 @@ -plugins: - - path: stats_over_http.so - params: - - _stats - - path: header_rewrite.so - params: - - /etc/trafficserver/rewrite.conf - - path: xdebug.so diff --git a/tests/gold_tests/traffic_ctl/convert_plugin_config/legacy_config/basic.config b/tests/gold_tests/traffic_ctl/convert_plugin_config/legacy_config/basic.config deleted file mode 100644 index 6f590c26f35..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_plugin_config/legacy_config/basic.config +++ /dev/null @@ -1,3 +0,0 @@ -stats_over_http.so _stats -header_rewrite.so /etc/trafficserver/rewrite.conf -xdebug.so --enable=x-cache diff --git a/tests/gold_tests/traffic_ctl/convert_plugin_config/legacy_config/commented.config b/tests/gold_tests/traffic_ctl/convert_plugin_config/legacy_config/commented.config deleted file mode 100644 index 10499b53eb4..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_plugin_config/legacy_config/commented.config +++ /dev/null @@ -1,5 +0,0 @@ -stats_over_http.so _stats -# cache_promote.so --policy=lru --buckets=100 -header_rewrite.so /etc/trafficserver/rewrite.conf -# slice.so --blockbytes=1048576 -xdebug.so diff --git a/tests/gold_tests/traffic_ctl/convert_plugin_config/legacy_config/quoted.config b/tests/gold_tests/traffic_ctl/convert_plugin_config/legacy_config/quoted.config deleted file mode 100644 index 429923813d8..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_plugin_config/legacy_config/quoted.config +++ /dev/null @@ -1,2 +0,0 @@ -regex_remap.so "maps_https.config" --no-query -header_rewrite.so "/etc/trafficserver/my rules.conf" diff --git a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/convert_ssl_multicert.test.py b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/convert_ssl_multicert.test.py deleted file mode 100644 index c0ffbcfb1d8..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/convert_ssl_multicert.test.py +++ /dev/null @@ -1,60 +0,0 @@ -''' -Test the traffic_ctl config convert ssl_multicert command. -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = 'Test traffic_ctl config convert ssl_multicert command.' - -# Create an ATS process to get the environment with PATH set correctly. -ts = Test.MakeATSProcess("ts", enable_cache=False) - -# Test 1: Basic config conversion. -tr = Test.AddTestRun("Test basic ssl_multicert.config conversion") -tr.Setup.Copy('legacy_config/basic.config') -tr.Processes.Default.Command = 'traffic_ctl config convert ssl_multicert basic.config -' -tr.Processes.Default.Streams.stdout = "gold/basic.yaml" -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Env = ts.Env -tr.Processes.Default.StartBefore(ts) -tr.StillRunningAfter = ts - -# Test 2: Full config with all options. -tr = Test.AddTestRun("Test full ssl_multicert.config conversion with all options") -tr.Setup.Copy('legacy_config/full.config') -tr.Processes.Default.Command = 'traffic_ctl config convert ssl_multicert full.config -' -tr.Processes.Default.Streams.stdout = "gold/full.yaml" -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Env = ts.Env -tr.StillRunningAfter = ts - -# Test 3: Config with quoted values. -tr = Test.AddTestRun("Test ssl_multicert.config with quoted values") -tr.Setup.Copy('legacy_config/quoted.config') -tr.Processes.Default.Command = 'traffic_ctl config convert ssl_multicert quoted.config -' -tr.Processes.Default.Streams.stdout = "gold/quoted.yaml" -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Env = ts.Env -tr.StillRunningAfter = ts - -# Test 4: Output to file. -tr = Test.AddTestRun("Test output to file") -tr.Setup.Copy('legacy_config/basic.config') -tr.Processes.Default.Command = 'traffic_ctl config convert ssl_multicert basic.config generated.yaml > /dev/null && cat generated.yaml' -tr.Processes.Default.Streams.stdout = "gold/basic.yaml" -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Env = ts.Env -tr.StillRunningAfter = ts diff --git a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/gold/basic.yaml b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/gold/basic.yaml deleted file mode 100644 index 8cd4076bee3..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/gold/basic.yaml +++ /dev/null @@ -1,6 +0,0 @@ -ssl_multicert: - - ssl_cert_name: server.pem - dest_ip: "*" - ssl_key_name: server.key - - ssl_cert_name: example.pem - dest_ip: 192.168.1.1 diff --git a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/gold/full.yaml b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/gold/full.yaml deleted file mode 100644 index 917ad6297e0..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/gold/full.yaml +++ /dev/null @@ -1,20 +0,0 @@ -ssl_multicert: - - ssl_cert_name: server.pem - dest_ip: "*" - ssl_key_name: server.key - ssl_ticket_enabled: 1 - ssl_ticket_number: 3 - - ssl_cert_name: ecdsa.pem,rsa.pem - dest_ip: 10.0.0.1:443 - ssl_key_name: ecdsa.key,rsa.key - ssl_ca_name: ca.pem - ssl_ocsp_name: ocsp.der - - ssl_cert_name: encrypted.pem - dest_ip: "*" - ssl_key_dialog: exec:/usr/bin/getpass foo - - dest_ip: 192.168.1.1 - action: tunnel - - ssl_cert_name: wildcard.pem - dest_ip: "*" - dest_fqdn: "*.example.com" - ssl_ticket_enabled: 0 diff --git a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/gold/quoted.yaml b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/gold/quoted.yaml deleted file mode 100644 index 0567cfca3cc..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/gold/quoted.yaml +++ /dev/null @@ -1,6 +0,0 @@ -ssl_multicert: - - ssl_cert_name: server.pem - dest_ip: "*" - ssl_key_dialog: exec:/usr/bin/getpass arg1 'arg 2' - - ssl_cert_name: another.pem - dest_ip: "[::1]:8443" diff --git a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/legacy_config/basic.config b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/legacy_config/basic.config deleted file mode 100644 index fb686c14704..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/legacy_config/basic.config +++ /dev/null @@ -1,3 +0,0 @@ -# Basic ssl_multicert.config example -ssl_cert_name=server.pem ssl_key_name=server.key dest_ip=* -ssl_cert_name=example.pem dest_ip=192.168.1.1 diff --git a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/legacy_config/full.config b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/legacy_config/full.config deleted file mode 100644 index b7cd4f504e3..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/legacy_config/full.config +++ /dev/null @@ -1,7 +0,0 @@ -# Full ssl_multicert.config with all options -# This exercises all supported configuration keys -ssl_cert_name=server.pem ssl_key_name=server.key dest_ip=* ssl_ticket_enabled=1 ssl_ticket_number=3 -ssl_cert_name=ecdsa.pem,rsa.pem ssl_key_name=ecdsa.key,rsa.key dest_ip=10.0.0.1:443 ssl_ca_name=ca.pem ssl_ocsp_name=ocsp.der -ssl_cert_name=encrypted.pem ssl_key_dialog="exec:/usr/bin/getpass foo" -dest_ip=192.168.1.1 action=tunnel -ssl_cert_name=wildcard.pem dest_fqdn=*.example.com ssl_ticket_enabled=0 diff --git a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/legacy_config/quoted.config b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/legacy_config/quoted.config deleted file mode 100644 index af1bb0923c7..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/legacy_config/quoted.config +++ /dev/null @@ -1,3 +0,0 @@ -# Config with quoted values -ssl_cert_name=server.pem ssl_key_dialog="exec:/usr/bin/getpass arg1 'arg 2'" -ssl_cert_name=another.pem dest_ip="[::1]:8443" diff --git a/tests/gold_tests/traffic_ctl/convert_storage/convert_storage.test.py b/tests/gold_tests/traffic_ctl/convert_storage/convert_storage.test.py deleted file mode 100644 index cf437c9a64a..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_storage/convert_storage.test.py +++ /dev/null @@ -1,68 +0,0 @@ -''' -Test the traffic_ctl config convert storage command. -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = 'Test traffic_ctl config convert storage command.' - -# Create an ATS process to get the environment with PATH set correctly. -ts = Test.MakeATSProcess("ts", enable_cache=False) - -# Test 1: Basic conversion (spans only, no volume=N annotations). -tr = Test.AddTestRun("Test basic storage.config + volume.config conversion") -tr.Setup.Copy('legacy_config/basic.storage.config') -tr.Setup.Copy('legacy_config/basic.volume.config') -tr.Processes.Default.Command = \ - 'traffic_ctl config convert storage basic.storage.config basic.volume.config -' -tr.Processes.Default.Streams.stdout = "gold/basic.yaml" -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Env = ts.Env -tr.Processes.Default.StartBefore(ts) -tr.StillRunningAfter = ts - -# Test 2: Exclusive volume assignments (volume=N per span line). -tr = Test.AddTestRun("Test storage.config with exclusive volume=N span assignments") -tr.Setup.Copy('legacy_config/exclusive.storage.config') -tr.Setup.Copy('legacy_config/exclusive.volume.config') -tr.Processes.Default.Command = \ - 'traffic_ctl config convert storage exclusive.storage.config exclusive.volume.config -' -tr.Processes.Default.Streams.stdout = "gold/exclusive.yaml" -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Env = ts.Env -tr.StillRunningAfter = ts - -# Test 3: Spans only, no volume.config (missing volume.config is treated as empty). -tr = Test.AddTestRun("Test storage.config with no volume.config (spans only)") -tr.Setup.Copy('legacy_config/no_volumes.storage.config') -tr.Processes.Default.Command = \ - 'traffic_ctl config convert storage no_volumes.storage.config /nonexistent/volume.config -' -tr.Processes.Default.Streams.stdout = "gold/no_volumes.yaml" -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Env = ts.Env -tr.StillRunningAfter = ts - -# Test 4: Output to file. -tr = Test.AddTestRun("Test output to file") -tr.Setup.Copy('legacy_config/basic.storage.config') -tr.Setup.Copy('legacy_config/basic.volume.config') -tr.Processes.Default.Command = \ - 'traffic_ctl config convert storage basic.storage.config basic.volume.config generated.yaml' \ - ' > /dev/null && cat generated.yaml' -tr.Processes.Default.Streams.stdout = "gold/basic.yaml" -tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Env = ts.Env -tr.StillRunningAfter = ts diff --git a/tests/gold_tests/traffic_ctl/convert_storage/gold/basic.yaml b/tests/gold_tests/traffic_ctl/convert_storage/gold/basic.yaml deleted file mode 100644 index ce9a9c5f5bb..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_storage/gold/basic.yaml +++ /dev/null @@ -1,19 +0,0 @@ -cache: - spans: - - name: /dev/sda - path: /dev/sda - - name: /dev/sdb - path: /dev/sdb - size: 10737418240 - - name: /var/cache/disk3 - path: /var/cache/disk3 - hash_seed: myseed - volumes: - - id: 1 - scheme: http - size: 60% - ram_cache: true - - id: 2 - scheme: http - size: 40% - ram_cache: true diff --git a/tests/gold_tests/traffic_ctl/convert_storage/gold/exclusive.yaml b/tests/gold_tests/traffic_ctl/convert_storage/gold/exclusive.yaml deleted file mode 100644 index 5fc07a680d8..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_storage/gold/exclusive.yaml +++ /dev/null @@ -1,28 +0,0 @@ -cache: - spans: - - name: /dev/sda - path: /dev/sda - - name: /dev/sdb - path: /dev/sdb - - name: /dev/sdc - path: /dev/sdc - - name: /dev/sdd - path: /dev/sdd - volumes: - - id: 1 - scheme: http - size: 60% - ram_cache: true - spans: - - use: /dev/sda - - use: /dev/sdc - - id: 2 - scheme: http - size: 40% - ram_cache: false - ram_cache_size: 1073741824 - ram_cache_cutoff: 262144 - avg_obj_size: 8192 - fragment_size: 524288 - spans: - - use: /dev/sdb diff --git a/tests/gold_tests/traffic_ctl/convert_storage/gold/no_volumes.yaml b/tests/gold_tests/traffic_ctl/convert_storage/gold/no_volumes.yaml deleted file mode 100644 index 49e622dfa3f..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_storage/gold/no_volumes.yaml +++ /dev/null @@ -1,10 +0,0 @@ -cache: - spans: - - name: /dev/sda - path: /dev/sda - - name: /dev/sdb - path: /dev/sdb - size: 5368709120 - - name: /var/cache/disk - path: /var/cache/disk - hash_seed: my_hash_seed diff --git a/tests/gold_tests/traffic_ctl/convert_storage/legacy_config/basic.storage.config b/tests/gold_tests/traffic_ctl/convert_storage/legacy_config/basic.storage.config deleted file mode 100644 index 25f2732a70c..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_storage/legacy_config/basic.storage.config +++ /dev/null @@ -1,4 +0,0 @@ -# Basic storage.config example -/dev/sda -/dev/sdb 10737418240 -/var/cache/disk3 id=myseed diff --git a/tests/gold_tests/traffic_ctl/convert_storage/legacy_config/basic.volume.config b/tests/gold_tests/traffic_ctl/convert_storage/legacy_config/basic.volume.config deleted file mode 100644 index 457a7f1a95d..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_storage/legacy_config/basic.volume.config +++ /dev/null @@ -1,3 +0,0 @@ -# Basic volume.config example -volume=1 scheme=http size=60% -volume=2 scheme=http size=40% diff --git a/tests/gold_tests/traffic_ctl/convert_storage/legacy_config/exclusive.storage.config b/tests/gold_tests/traffic_ctl/convert_storage/legacy_config/exclusive.storage.config deleted file mode 100644 index 213007d53d7..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_storage/legacy_config/exclusive.storage.config +++ /dev/null @@ -1,5 +0,0 @@ -# storage.config with exclusive volume assignments (volume=N per span) -/dev/sda volume=1 -/dev/sdb volume=2 -/dev/sdc volume=1 -/dev/sdd diff --git a/tests/gold_tests/traffic_ctl/convert_storage/legacy_config/exclusive.volume.config b/tests/gold_tests/traffic_ctl/convert_storage/legacy_config/exclusive.volume.config deleted file mode 100644 index def3e0eb0b9..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_storage/legacy_config/exclusive.volume.config +++ /dev/null @@ -1,3 +0,0 @@ -# volume.config with all supported fields -volume=1 scheme=http size=60% -volume=2 scheme=http size=40% avg_obj_size=8192 fragment_size=524288 ramcache=false ram_cache_size=1073741824 ram_cache_cutoff=262144 diff --git a/tests/gold_tests/traffic_ctl/convert_storage/legacy_config/no_volumes.storage.config b/tests/gold_tests/traffic_ctl/convert_storage/legacy_config/no_volumes.storage.config deleted file mode 100644 index 86330433962..00000000000 --- a/tests/gold_tests/traffic_ctl/convert_storage/legacy_config/no_volumes.storage.config +++ /dev/null @@ -1,4 +0,0 @@ -# storage.config with no volume assignments (spans only) -/dev/sda -/dev/sdb 5368709120 -/var/cache/disk id=my_hash_seed diff --git a/tests/gold_tests/traffic_ctl/show_ssl_multicert/gold/show_json.gold b/tests/gold_tests/traffic_ctl/show_ssl_multicert/gold/show_json.gold deleted file mode 100644 index 5fff803226e..00000000000 --- a/tests/gold_tests/traffic_ctl/show_ssl_multicert/gold/show_json.gold +++ /dev/null @@ -1 +0,0 @@ -{"ssl_multicert": [{"ssl_cert_name": "server.pem", "dest_ip": "*", "ssl_key_name": "server.key"}]} diff --git a/tests/gold_tests/traffic_ctl/show_ssl_multicert/gold/show_yaml.gold b/tests/gold_tests/traffic_ctl/show_ssl_multicert/gold/show_yaml.gold deleted file mode 100644 index 5c558469ae2..00000000000 --- a/tests/gold_tests/traffic_ctl/show_ssl_multicert/gold/show_yaml.gold +++ /dev/null @@ -1,4 +0,0 @@ -ssl_multicert: - - ssl_cert_name: server.pem - dest_ip: "*" - ssl_key_name: server.key diff --git a/tests/gold_tests/traffic_ctl/show_ssl_multicert/show_ssl_multicert.test.py b/tests/gold_tests/traffic_ctl/show_ssl_multicert/show_ssl_multicert.test.py deleted file mode 100644 index 984df9b7f18..00000000000 --- a/tests/gold_tests/traffic_ctl/show_ssl_multicert/show_ssl_multicert.test.py +++ /dev/null @@ -1,91 +0,0 @@ -''' -Test the traffic_ctl config ssl-multicert show command. -''' -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -Test.Summary = 'Test traffic_ctl config ssl-multicert show command.' - - -class ShowSSLMulticert: - - def __init__(self): - self.setup_ts() - self.setup_show_default() - self.setup_show_json() - self.setup_show_yaml() - - def setup_ts(self): - self._ts = Test.MakeATSProcess("ts", enable_cache=False, enable_tls=True) - self._ts.addDefaultSSLFiles() - self._ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - ssl_cert_name: server.pem - dest_ip: "*" - ssl_key_name: server.key -""".split("\n")) - self._ts.Disk.records_config.update( - { - 'proxy.config.ssl.server.cert.path': f'{self._ts.Variables.SSLDir}', - 'proxy.config.ssl.server.private_key.path': f'{self._ts.Variables.SSLDir}', - }) - - def setup_show_default(self): - tr = Test.AddTestRun("Test ssl-multicert show (default YAML format)") - tr.Processes.Default.Command = 'traffic_ctl config ssl-multicert show' - tr.Processes.Default.Streams.stdout = "gold/show_yaml.gold" - tr.Processes.Default.ReturnCode = 0 - tr.Processes.Default.Env = self._ts.Env - tr.Processes.Default.StartBefore(self._ts) - tr.StillRunningAfter = self._ts - - def setup_show_json(self): - # Test with explicit --json flag. - tr = Test.AddTestRun("Test ssl-multicert show --json") - tr.Processes.Default.Command = 'traffic_ctl config ssl-multicert show --json' - tr.Processes.Default.Streams.stdout = "gold/show_json.gold" - tr.Processes.Default.ReturnCode = 0 - tr.Processes.Default.Env = self._ts.Env - tr.StillRunningAfter = self._ts - - # Test with short -j flag. - tr = Test.AddTestRun("Test ssl-multicert show -j") - tr.Processes.Default.Command = 'traffic_ctl config ssl-multicert show -j' - tr.Processes.Default.Streams.stdout = "gold/show_json.gold" - tr.Processes.Default.ReturnCode = 0 - tr.Processes.Default.Env = self._ts.Env - tr.StillRunningAfter = self._ts - - def setup_show_yaml(self): - # Test with --yaml flag. - tr = Test.AddTestRun("Test ssl-multicert show --yaml") - tr.Processes.Default.Command = 'traffic_ctl config ssl-multicert show --yaml' - tr.Processes.Default.Streams.stdout = "gold/show_yaml.gold" - tr.Processes.Default.ReturnCode = 0 - tr.Processes.Default.Env = self._ts.Env - tr.StillRunningAfter = self._ts - - # Test with short -y flag. - tr = Test.AddTestRun("Test ssl-multicert show -y") - tr.Processes.Default.Command = 'traffic_ctl config ssl-multicert show -y' - tr.Processes.Default.Streams.stdout = "gold/show_yaml.gold" - tr.Processes.Default.ReturnCode = 0 - tr.Processes.Default.Env = self._ts.Env - tr.StillRunningAfter = self._ts - - -ShowSSLMulticert() diff --git a/tests/gold_tests/traffic_ctl/traffic_ctl_config_reload.test.py b/tests/gold_tests/traffic_ctl/traffic_ctl_config_reload.test.py deleted file mode 100644 index 59f440b7223..00000000000 --- a/tests/gold_tests/traffic_ctl/traffic_ctl_config_reload.test.py +++ /dev/null @@ -1,192 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import sys -import os - -# To include util classes -sys.path.insert(0, f'{Test.TestDirectory}') - -from traffic_ctl_test_utils import Make_traffic_ctl -# import ruamel.yaml Uncomment only when GoldFilePathFor is used. - - -def touch(fname, times=None): - with open(fname, 'a'): - os.utime(fname, times) - - -Test.Summary = ''' -Test traffic_ctl config reload. -''' - -Test.ContinueOnFail = True - -records_config = ''' - udp: - threads: 1 - diags: - debug: - enabled: 1 - tags: rpc|config - throttling_interval_msec: 0 - ''' - -traffic_ctl = Make_traffic_ctl(Test, records_config, Any(0, 2)) -# todo: we need to get the status just in json format - -#### CONFIG STATUS - -# Config status with no token, no reloads exist, should return error. -traffic_ctl.config().status().validate_with_text(""" -`` -Message: No reload tasks found, Code: 6005 -`` -""") - -traffic_ctl.config().status().token("test1").validate_with_text(""" -`` -Message: Token 'test1' not found, Code: 6001 -`` -""") - -traffic_ctl.config().status().count("all").validate_with_text(""" -`` -Message: No reload tasks found, Code: 6005 -`` -""") - -traffic_ctl.config().status().token("test1").count("all").validate_with_text( - """ -`` -You can't use both --token and --count options together. Ignoring --count -`` -Message: Token 'test1' not found, Code: 6001 -`` -""") -##### CONFIG RELOAD - -# basic reload, no params. no existing reload in progress, we expect this to start a new reload. -traffic_ctl.config().reload().validate_with_text( - "\u2714 Reload scheduled [``]\n\n Monitor : traffic_ctl config reload -t `` -m\n Details : traffic_ctl config reload -t `` -s -l" -) - -# basic reload, but traffic_ctl should create and wait for the details, showing the newly created -# reload and some details. -traffic_ctl.config().reload().show_details().validate_contains_all("Reload scheduled", "Waiting for details", "Reload [success]") - -# Now we try with a token, this should start a new reload with the given token. -token = "testtoken_1234" -traffic_ctl.config().reload().token(token).validate_with_text( - f"\u2714 Reload scheduled [{token}]\n\n Monitor : traffic_ctl config reload -t {token} -m\n Details : traffic_ctl config reload -t {token} -s -l" -) - -# traffic_ctl config status should show the last reload, same as the above. -traffic_ctl.config().status().token(token).validate_contains_all("success", "testtoken_1234") - -# Now we try again, with same token, this should fail as the token already exists. -traffic_ctl.config().reload().token(token).validate_with_text( - f"\u2717 Token '{token}' already in use\n\n Status : traffic_ctl config status -t {token}\n Retry : traffic_ctl config reload" -) - -# Modify ip_allow.yaml and validate the reload status. - -tr = Test.AddTestRun("touch file to trigger ip_allow reload") -tr.Processes.Default.Command = f"touch {os.path.join(traffic_ctl._ts.Variables.CONFIGDIR, 'ip_allow.yaml')} && sleep 1" -tr.Processes.Default.ReturnCode = 0 - -traffic_ctl.config().reload().token("reload_ip_allow").show_details().validate_contains_all( - "reload_ip_allow", "success", "ip_allow.yaml") - -##### FORCE RELOAD - -# Force reload should work even if we just did a reload -traffic_ctl.config().reload().force().validate_contains_all("Reload scheduled") - -##### INLINE DATA RELOAD - -# Test inline data with -d flag (config not registered, expect error but no stuck task) -# Use --force to avoid "reload in progress" conflict -tr = Test.AddTestRun("Inline data reload with unregistered config") -tr.DelayStart = 5 # Wait for previous reload to complete -tr.Processes.Default.Command = f'traffic_ctl config reload --force -d "unknown_cfg: {{foo: bar}}"' -tr.Processes.Default.Env = traffic_ctl._ts.Env -tr.Processes.Default.ReturnCode = Any(0, 1, 2) -tr.StillRunningAfter = traffic_ctl._ts -tr.Processes.Default.Streams.All.Content = Testers.ContainsExpression( - r'not registered|No configs were scheduled', "Should report config not registered") - -# Verify no stuck task - new reload should work immediately after -traffic_ctl.config().reload().token("after_inline_test").validate_with_text( - "\u2714 Reload scheduled [after_inline_test]\n\n Monitor : traffic_ctl config reload -t after_inline_test -m\n Details : traffic_ctl config reload -t after_inline_test -s -l" -) - -##### MULTI-KEY FILE RELOAD - -# Create a multi-key config file -tr = Test.AddTestRun("Create multi-key config file") -multi_config_path = os.path.join(traffic_ctl._ts.Variables.CONFIGDIR, 'multi_test.yaml') -tr.Processes.Default.Command = f'''cat > {multi_config_path} << 'EOF' -# Multiple config keys in one file -config_a: - foo: bar -config_b: - baz: qux -EOF''' -tr.Processes.Default.Env = traffic_ctl._ts.Env -tr.Processes.Default.ReturnCode = 0 -tr.StillRunningAfter = traffic_ctl._ts - -# Test reload with multi-key file using data_file() -tr = Test.AddTestRun("Multi-key file reload") -tr.DelayStart = 5 # Wait for previous reload to complete -tr.Processes.Default.Command = f'traffic_ctl config reload --force --data @{multi_config_path}' -tr.Processes.Default.Env = traffic_ctl._ts.Env -tr.Processes.Default.ReturnCode = Any(0, 1, 2) -tr.StillRunningAfter = traffic_ctl._ts -tr.Processes.Default.Streams.All.Content = Testers.ContainsExpression( - r'not registered|No configs were scheduled|error', "Should process multi-key file") - -##### FORCE WITH INLINE DATA - -# Force reload with inline data -tr = Test.AddTestRun("Force reload with inline data") -tr.DelayStart = 1 -tr.Processes.Default.Command = f'traffic_ctl config reload --force --data "test_config: {{key: value}}"' -tr.Processes.Default.Env = traffic_ctl._ts.Env -tr.Processes.Default.ReturnCode = Any(0, 1, 2) -tr.StillRunningAfter = traffic_ctl._ts -tr.Processes.Default.Streams.All.Content = Testers.ContainsExpression( - r'not registered|No configs were scheduled|scheduled', "Should handle force with inline data") - -##### EXIT CODE TESTS -# Exit codes: 0 = success, 2 = error, 75 = temporary failure / in-progress (EX_TEMPFAIL from sysexits.h) - -# Test: Successful reload should return exit code 0 -traffic_ctl.config().reload().token("exit_code_ok").validate_with_exit_code(0) - -# Test: Successful reload with --monitor should return exit code 0 -traffic_ctl.config().reload().token("exit_code_monitor_ok").monitor().initial_wait(0.5).validate_with_exit_code(0) - -# Test: Token already in use should return exit code 2 (CTRL_EX_ERROR) -traffic_ctl.config().reload().token("exit_code_ok").validate_with_exit_code(2) - -# NOTE: Exit code 75 (CTRL_EX_TEMPFAIL / RELOAD_IN_PROGRESS) is not tested here -# because the reload completes almost instantly in the test environment, making it -# impossible to reliably trigger the "in progress" window with a second concurrent -# request. Testing this path would require a test plugin that artificially delays -# reload processing. The exit code 75 paths are validated by code review: -# 1. Server returns RELOAD_IN_PROGRESS when a second reload is attempted while one is active. -# 2. Ctrl+C during --monitor sets CTRL_EX_TEMPFAIL when the reload hasn't finished. diff --git a/tests/gold_tests/traffic_ctl/traffic_ctl_test_utils.py b/tests/gold_tests/traffic_ctl/traffic_ctl_test_utils.py index 29275c9ffd9..b4c2012238f 100644 --- a/tests/gold_tests/traffic_ctl/traffic_ctl_test_utils.py +++ b/tests/gold_tests/traffic_ctl/traffic_ctl_test_utils.py @@ -166,127 +166,6 @@ def validate_json_contains(self, **field_checks): return self -class ConfigReload(Common): - """ - Handy class to map traffic_ctl config reload options. - - Options (in command order): - --token, -t Configuration token - --monitor, -m Monitor reload progress until completion - --show-details, -s Show detailed information of the reload - --include-logs, -l Include logs (with --show-details) - --refresh-int, -r Refresh interval in seconds (with --monitor). Accepts fractional values - --force, -F Force reload even if one in progress - --data, -d Inline config data (@file1 @file2, @- for stdin, or yaml string) - --initial-wait, -w Initial wait before first poll (seconds). Accepts fractional values - """ - - def __init__(self, dir, tr, tn): - super().__init__(tr) - self._cmd = "traffic_ctl config reload" - self._tr = tr - self._dir = dir - self._tn = tn - - def __finish(self): - """ - Sets the command to the test. Make sure this gets called after - validation is set. Without this call the test will fail. - """ - self._tr.Processes.Default.Command = self._cmd - - # --- Options in command order --- - - def token(self, token: str): - """Set a custom token for the reload (--token, -t)""" - self._cmd = f'{self._cmd} --token {token} ' - return self - - def monitor(self): - """Monitor reload progress until completion (--monitor, -m)""" - self._cmd = f'{self._cmd} --monitor ' - return self - - def show_details(self): - """Show detailed information of the reload (--show-details, -s)""" - self._cmd = f'{self._cmd} --show-details ' - return self - - def include_logs(self): - """Include logs in details (--include-logs, -l). Use with show_details()""" - self._cmd = f'{self._cmd} --include-logs ' - return self - - def refresh_int(self, seconds: float): - """Set refresh interval in seconds (--refresh-int, -r). Use with monitor(). Accepts fractional values (e.g. 0.5)""" - self._cmd = f'{self._cmd} --refresh-int {seconds} ' - return self - - def force(self): - """Force reload even if one in progress (--force, -F)""" - self._cmd = f'{self._cmd} --force ' - return self - - def data(self, data_arg: str): - """Set inline YAML data string (--data, -d)""" - self._cmd = f'{self._cmd} --data \'{data_arg}\' ' - return self - - def data_file(self, filepath: str): - """Set file-based inline data (--data @filepath, -d @filepath)""" - self._cmd = f'{self._cmd} --data @{filepath} ' - return self - - def data_files(self, filepaths: list): - """Set multiple file-based inline data (--data @file1 @file2 ...)""" - files_str = ' '.join([f'@{fp}' for fp in filepaths]) - self._cmd = f'{self._cmd} --data {files_str} ' - return self - - def initial_wait(self, seconds: float): - """Set initial wait before first poll (--initial-wait, -w). Use with monitor() or show_details()""" - self._cmd = f'{self._cmd} --initial-wait {seconds} ' - return self - - # --- Validation --- - - def validate_with_text(self, text: str): - self._tr.Processes.Default.Streams.stdout = MakeGoldFileWithText(text, self._dir, self._tn) - self.__finish() - - -class ConfigStatus(Common): - """ - Handy class to map traffic_ctl config status. - """ - - def __init__(self, dir, tr, tn): - super().__init__(tr) - self._cmd = "traffic_ctl config status" - self._tr = tr - self._dir = dir - self._tn = tn - - def __finish(self): - """ - Sets the command to the test. Make sure this gets called after - validation is set. Without this call the test will fail. - """ - self._tr.Processes.Default.Command = self._cmd - - def token(self, token: str): - self._cmd = f'{self._cmd} --token {token} ' - return self - - def count(self, count: str): - self._cmd = f'{self._cmd} --count {count}' - return self - - def validate_with_text(self, text: str): - self._tr.Processes.Default.Streams.stdout = MakeGoldFileWithText(text, self._dir, self._tn) - self.__finish() - - class Config(Common): """ Handy class to map traffic_ctl config options. @@ -348,12 +227,6 @@ def reset(self, *paths): self._cmd = f'{self._cmd} reset {paths_str}' return self - def reload(self): - return ConfigReload(self._dir, self._tr, self._tn) - - def status(self): - return ConfigStatus(self._dir, self._tr, self._tn) - def as_records(self): self._cmd = f'{self._cmd} --records' return self @@ -494,10 +367,10 @@ class TrafficCtl(Config, Server): Every time a config() is called, a new test is created. """ - def __init__(self, test, records_yaml=None, retcode=0): + def __init__(self, test, records_yaml=None): self._testNumber = 0 self._current_test_number = self._testNumber - self._retcode = retcode + self._Test = test self._ts = self._Test.MakeATSProcess(f"ts_{self._testNumber}") if records_yaml != None: @@ -516,7 +389,7 @@ def add_test(self): tr.Processes.Default.Env = self._ts.Env tr.DelayStart = 3 - tr.Processes.Default.ReturnCode = self._retcode + tr.Processes.Default.ReturnCode = 0 tr.StillRunningAfter = self._ts self._tests.insert(self.__get_index(), tr) @@ -535,6 +408,6 @@ def rpc(self): return RPC(self._Test.TestDirectory, self._tests[self.__get_index()], self._testNumber) -def Make_traffic_ctl(test, records_yaml=None, retcode=0): - tctl = TrafficCtl(test, records_yaml, retcode) +def Make_traffic_ctl(test, records_yaml=None): + tctl = TrafficCtl(test, records_yaml) return tctl diff --git a/tests/gold_tests/tunnel/tunnel_transform.test.py b/tests/gold_tests/tunnel/tunnel_transform.test.py index ed169c453f8..dabb0692b44 100644 --- a/tests/gold_tests/tunnel/tunnel_transform.test.py +++ b/tests/gold_tests/tunnel/tunnel_transform.test.py @@ -58,13 +58,7 @@ 'proxy.config.http.connect_ports': '{0}'.format(server.Variables.SSL_Port) }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.sni_yaml.AddLines([ 'sni:', diff --git a/tests/gold_tests/tunnel/txn_type.test.py b/tests/gold_tests/tunnel/txn_type.test.py index a52933f5ca1..8c79e0a6798 100644 --- a/tests/gold_tests/tunnel/txn_type.test.py +++ b/tests/gold_tests/tunnel/txn_type.test.py @@ -63,13 +63,7 @@ 'proxy.config.http.connect_ports': '{0}'.format(server.Variables.SSL_Port) }) -ts.Disk.ssl_multicert_yaml.AddLines( - """ -ssl_multicert: - - dest_ip: "*" - ssl_cert_name: server.pem - ssl_key_name: server.key -""".split("\n")) +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') ts.Disk.remap_config.AddLine( 'map https://http-test:{0}/ https://127.0.0.1:{1}/'.format(ts.Variables.ssl_port, server.Variables.SSL_Port)) diff --git a/tools/cmake-format.sh b/tools/cmake-format.sh index 07a1a32e12a..01036eb2666 100755 --- a/tools/cmake-format.sh +++ b/tools/cmake-format.sh @@ -49,16 +49,30 @@ function main() { tmp_dir=$(mktemp -d -t tracked-git-files.XXXXXXXXXX) files=${tmp_dir}/git_files.txt files_filtered=${tmp_dir}/git_files_filtered.txt + files_deleted=${tmp_dir}/git_files_deleted.txt git ls-tree -r HEAD --name-only ${DIR} | grep -E 'CMakeLists.txt|.cmake$' | grep -vE "lib/(Catch2|fastlz|ls-hpack|swoc|yamlcpp)" > ${files} # Add to the above any newly added staged files. git diff --cached --name-only --diff-filter=A >> ${files} # But probably not all the new staged files are CMakeLists.txt files: grep -E 'CMakeLists.txt|.cmake$' ${files} > ${files_filtered} + # Drop files that are staged for deletion or otherwise missing from the + # working tree -- formatting a file that doesn't exist on disk fails. + git diff --cached --name-only --diff-filter=D > ${files_deleted} + if [ -s ${files_deleted} ]; then + grep -vxF -f ${files_deleted} ${files_filtered} > ${files_filtered}.tmp || true + mv ${files_filtered}.tmp ${files_filtered} + fi # Prepend the filenames with "./" to make the modified file output consistent # with the clang-format target output. sed -i'.bak' 's:^:\./:' ${files_filtered} rm -f ${files_filtered}.bak + # If after filtering there's nothing to format, we're done. + if [ ! -s ${files_filtered} ]; then + rm -rf ${tmp_dir} + return 0 + fi + # Efficiently retrieving modification timestamps in a platform # independent way is challenging. We use find's -newer argument, which # seems to be broadly supported. The following file is created and has a