| #!/bin/bash |
| # |
| # lib/apache |
| # Functions to control configuration and operation of apache web server |
| |
| # Dependencies: |
| # |
| # - ``functions`` file |
| # - ``STACK_USER`` must be defined |
| # |
| # lib/apache exports the following functions: |
| # |
| # - install_apache_wsgi |
| # - apache_site_config_for |
| # - enable_apache_site |
| # - disable_apache_site |
| # - start_apache_server |
| # - stop_apache_server |
| # - restart_apache_server |
| |
| # Save trace setting |
| _XTRACE_LIB_APACHE=$(set +o | grep xtrace) |
| set +o xtrace |
| |
| # Allow overriding the default Apache user and group, default to |
| # current user and his default group. |
| APACHE_USER=${APACHE_USER:-$STACK_USER} |
| APACHE_GROUP=${APACHE_GROUP:-$(id -gn $APACHE_USER)} |
| |
| APACHE_LOCAL_HOST=$SERVICE_LOCAL_HOST |
| if [[ "$SERVICE_IP_VERSION" == 6 ]]; then |
| APACHE_LOCAL_HOST=[$APACHE_LOCAL_HOST] |
| fi |
| |
| |
| # Set up apache name and configuration directory |
| # Note that APACHE_CONF_DIR is really more accurately apache's vhost |
| # configuration dir but we can't just change this because public interfaces. |
| if is_ubuntu; then |
| APACHE_NAME=apache2 |
| APACHE_CONF_DIR=${APACHE_CONF_DIR:-/etc/$APACHE_NAME/sites-available} |
| APACHE_SETTINGS_DIR=${APACHE_SETTINGS_DIR:-/etc/$APACHE_NAME/conf-enabled} |
| elif is_fedora; then |
| APACHE_NAME=httpd |
| APACHE_CONF_DIR=${APACHE_CONF_DIR:-/etc/$APACHE_NAME/conf.d} |
| APACHE_SETTINGS_DIR=${APACHE_SETTINGS_DIR:-/etc/$APACHE_NAME/conf.d} |
| fi |
| APACHE_LOG_DIR="/var/log/${APACHE_NAME}" |
| |
| # Functions |
| # --------- |
| |
| # Enable apache mod and restart apache if it isn't already enabled. |
| function enable_apache_mod { |
| local mod=$1 |
| local should_restart=$2 |
| # Apache installation, because we mark it NOPRIME |
| if is_ubuntu; then |
| # Skip mod_version as it is not a valid mod to enable |
| # on debuntu, instead it is built in. |
| if [[ "$mod" != "version" ]] && ! a2query -m $mod ; then |
| sudo a2enmod $mod |
| if [[ "$should_restart" != "norestart" ]] ; then |
| restart_apache_server |
| fi |
| fi |
| elif is_fedora; then |
| # pass |
| true |
| else |
| exit_distro_not_supported "apache enable mod" |
| fi |
| } |
| |
| # NOTE(sdague): Install uwsgi including apache module, we need to get |
| # to 2.0.6+ to get a working mod_proxy_uwsgi. We can probably build a |
| # check for that and do it differently for different platforms. |
| function install_apache_uwsgi { |
| local apxs="apxs2" |
| if is_fedora; then |
| apxs="apxs" |
| fi |
| |
| if is_ubuntu; then |
| local pkg_list="uwsgi uwsgi-plugin-python3 libapache2-mod-proxy-uwsgi" |
| install_package ${pkg_list} |
| # NOTE(ianw) 2022-02-03 : Fedora 35 needs to skip this and fall |
| # into the install-from-source because the upstream packages |
| # didn't fix Python 3.10 compatibility before release. Should be |
| # fixed in uwsgi 4.9.0; can remove this when packages available |
| # or we drop this release |
| elif is_fedora && ! is_openeuler && ! [[ $DISTRO =~ f36 ]]; then |
| # Note httpd comes with mod_proxy_uwsgi and it is loaded by |
| # default; the mod_proxy_uwsgi package actually conflicts now. |
| # See: |
| # https://bugzilla.redhat.com/show_bug.cgi?id=1574335 |
| # |
| # Thus there is nothing else to do after this install |
| install_package uwsgi \ |
| uwsgi-plugin-python3 |
| else |
| # Compile uwsgi from source. |
| local dir |
| dir=$(mktemp -d) |
| pushd $dir |
| pip_install uwsgi |
| pip download uwsgi -c $REQUIREMENTS_DIR/upper-constraints.txt |
| local uwsgi |
| uwsgi=$(ls uwsgi*) |
| tar xvf $uwsgi |
| cd uwsgi*/apache2 |
| sudo $apxs -i -c mod_proxy_uwsgi.c |
| popd |
| # delete the temp directory |
| sudo rm -rf $dir |
| fi |
| |
| if is_ubuntu; then |
| if ! a2query -m proxy || ! a2query -m proxy_uwsgi ; then |
| # we've got to enable proxy and proxy_uwsgi for this to work |
| sudo a2enmod proxy |
| sudo a2enmod proxy_uwsgi |
| restart_apache_server |
| fi |
| fi |
| } |
| |
| # install_apache_wsgi() - Install Apache server and wsgi module |
| function install_apache_wsgi { |
| # Apache installation, because we mark it NOPRIME |
| if is_ubuntu; then |
| # Install apache2, which is NOPRIME'd |
| install_package apache2 |
| if is_package_installed libapache2-mod-wsgi; then |
| uninstall_package libapache2-mod-wsgi |
| fi |
| install_package libapache2-mod-wsgi-py3 |
| elif is_fedora; then |
| sudo rm -f /etc/httpd/conf.d/000-* |
| install_package httpd python3-mod_wsgi |
| # rpm distros dont enable httpd by default so enable it to support reboots. |
| sudo systemctl enable httpd |
| # For consistency with Ubuntu, switch to the worker mpm, as |
| # the default is event |
| sudo sed -i '/mod_mpm_prefork.so/s/^/#/g' /etc/httpd/conf.modules.d/00-mpm.conf |
| sudo sed -i '/mod_mpm_event.so/s/^/#/g' /etc/httpd/conf.modules.d/00-mpm.conf |
| sudo sed -i '/mod_mpm_worker.so/s/^#//g' /etc/httpd/conf.modules.d/00-mpm.conf |
| else |
| exit_distro_not_supported "apache wsgi installation" |
| fi |
| # WSGI isn't enabled by default, enable it |
| enable_apache_mod wsgi |
| } |
| |
| # apache_site_config_for() - The filename of the site's configuration file. |
| # This function uses the global variables APACHE_NAME and APACHE_CONF_DIR. |
| # |
| # On Ubuntu 14.04+, the site configuration file must have a .conf suffix for a2ensite and a2dissite to |
| # recognise it. a2ensite and a2dissite ignore the .conf suffix used as parameter. The default sites' |
| # files are 000-default.conf and default-ssl.conf. |
| # |
| # On Fedora, any file in /etc/httpd/conf.d/ whose name ends with .conf is enabled. |
| # |
| # On RHEL and CentOS, things should hopefully work as in Fedora. |
| # |
| # The table below summarizes what should happen on each distribution: |
| # +----------------------+--------------------+--------------------------+--------------------------+ |
| # | Distribution | File name | Site enabling command | Site disabling command | |
| # +----------------------+--------------------+--------------------------+--------------------------+ |
| # | Ubuntu 14.04 | site.conf | a2ensite site | a2dissite site | |
| # | Fedora, RHEL, CentOS | site.conf.disabled | mv site.conf{.disabled,} | mv site.conf{,.disabled} | |
| # +----------------------+--------------------+--------------------------+--------------------------+ |
| function apache_site_config_for { |
| local site=$@ |
| if is_ubuntu; then |
| # Ubuntu 14.04 - Apache 2.4 |
| echo $APACHE_CONF_DIR/${site}.conf |
| elif is_fedora; then |
| # fedora conf.d is only imported if it ends with .conf so this is approx the same |
| local enabled_site_file="$APACHE_CONF_DIR/${site}.conf" |
| if [ -f $enabled_site_file ]; then |
| echo ${enabled_site_file} |
| else |
| echo ${enabled_site_file}.disabled |
| fi |
| fi |
| } |
| |
| # enable_apache_site() - Enable a particular apache site |
| function enable_apache_site { |
| local site=$@ |
| # Many of our sites use mod version. Just enable it. |
| enable_apache_mod version |
| if is_ubuntu; then |
| sudo a2ensite ${site} |
| elif is_fedora; then |
| local enabled_site_file="$APACHE_CONF_DIR/${site}.conf" |
| # Do nothing if site already enabled or no site config exists |
| if [[ -f ${enabled_site_file}.disabled ]] && [[ ! -f ${enabled_site_file} ]]; then |
| sudo mv ${enabled_site_file}.disabled ${enabled_site_file} |
| fi |
| fi |
| } |
| |
| # disable_apache_site() - Disable a particular apache site |
| function disable_apache_site { |
| local site=$@ |
| if is_ubuntu; then |
| sudo a2dissite ${site} || true |
| elif is_fedora; then |
| local enabled_site_file="$APACHE_CONF_DIR/${site}.conf" |
| # Do nothing if no site config exists |
| if [[ -f ${enabled_site_file} ]]; then |
| sudo mv ${enabled_site_file} ${enabled_site_file}.disabled |
| fi |
| fi |
| } |
| |
| # start_apache_server() - Start running apache server |
| function start_apache_server { |
| start_service $APACHE_NAME |
| } |
| |
| # stop_apache_server() - Stop running apache server |
| function stop_apache_server { |
| if [ -n "$APACHE_NAME" ]; then |
| stop_service $APACHE_NAME |
| else |
| exit_distro_not_supported "apache configuration" |
| fi |
| } |
| |
| # restart_apache_server |
| function restart_apache_server { |
| # Apache can be slow to stop, doing an explicit stop, sleep, start helps |
| # to mitigate issues where apache will claim a port it's listening on is |
| # still in use and fail to start. |
| restart_service $APACHE_NAME |
| } |
| |
| # write_uwsgi_config() - Create a new uWSGI config file |
| function write_uwsgi_config { |
| local conf=$1 |
| local wsgi=$2 |
| local url=$3 |
| local http=$4 |
| local name=$5 |
| |
| if [ -z "$name" ]; then |
| name=$(basename $wsgi) |
| fi |
| |
| # create a home for the sockets; note don't use /tmp -- apache has |
| # a private view of it on some platforms. |
| local socket_dir='/var/run/uwsgi' |
| |
| # /var/run will be empty on ubuntu after reboot, so we can use systemd-temptiles |
| # to automatically create $socket_dir. |
| sudo mkdir -p /etc/tmpfiles.d/ |
| echo "d $socket_dir 0755 $STACK_USER root" | sudo tee /etc/tmpfiles.d/uwsgi.conf |
| sudo systemd-tmpfiles --create /etc/tmpfiles.d/uwsgi.conf |
| |
| local socket="$socket_dir/${name}.socket" |
| |
| # always cleanup given that we are using iniset here |
| rm -rf $conf |
| # Set either the module path or wsgi script path depending on what we've |
| # been given. Note that the regex isn't exhaustive - neither Python modules |
| # nor Python variables can start with a number - but it's "good enough" |
| if [[ "$wsgi" =~ ^[a-zA-Z0-9_.]+:[a-zA-Z0-9_]+$ ]]; then |
| iniset "$conf" uwsgi module "$wsgi" |
| else |
| deprecated 'Configuring uWSGI with a WSGI file is deprecated, use module paths instead' |
| iniset "$conf" uwsgi wsgi-file "$wsgi" |
| fi |
| iniset "$conf" uwsgi processes $API_WORKERS |
| # This is running standalone |
| iniset "$conf" uwsgi master true |
| # Set die-on-term & exit-on-reload so that uwsgi shuts down |
| iniset "$conf" uwsgi die-on-term true |
| iniset "$conf" uwsgi exit-on-reload false |
| # Set worker-reload-mercy so that worker will not exit till the time |
| # configured after graceful shutdown |
| iniset "$conf" uwsgi worker-reload-mercy $WORKER_TIMEOUT |
| iniset "$conf" uwsgi enable-threads true |
| iniset "$conf" uwsgi plugins http,python3 |
| # uwsgi recommends this to prevent thundering herd on accept. |
| iniset "$conf" uwsgi thunder-lock true |
| # Set hook to trigger graceful shutdown on SIGTERM |
| iniset "$conf" uwsgi hook-master-start "unix_signal:15 gracefully_kill_them_all" |
| # Override the default size for headers from the 4k default. |
| iniset "$conf" uwsgi buffer-size 65535 |
| # Make sure the client doesn't try to re-use the connection. |
| iniset "$conf" uwsgi add-header "Connection: close" |
| # This ensures that file descriptors aren't shared between processes. |
| iniset "$conf" uwsgi lazy-apps true |
| # Starting time of the WSGi server |
| iniset "$conf" uwsgi start-time %t |
| |
| # If we said bind directly to http, then do that and don't start the apache proxy |
| if [[ -n "$http" ]]; then |
| iniset "$conf" uwsgi http $http |
| else |
| local apache_conf="" |
| apache_conf=$(apache_site_config_for $name) |
| iniset "$conf" uwsgi socket "$socket" |
| iniset "$conf" uwsgi chmod-socket 666 |
| echo "ProxyPass \"${url}\" \"unix:${socket}|uwsgi://uwsgi-uds-${name}\" retry=0 acquire=1 " | sudo tee -a $apache_conf |
| enable_apache_site $name |
| restart_apache_server |
| fi |
| } |
| |
| # For services using chunked encoding, the only services known to use this |
| # currently are Glance and Swift, we need to use an http proxy instead of |
| # mod_proxy_uwsgi because the chunked encoding gets dropped. See: |
| # https://github.com/unbit/uwsgi/issues/1540. |
| function write_local_uwsgi_http_config { |
| local conf=$1 |
| local wsgi=$2 |
| local url=$3 |
| local name=$4 |
| |
| if [ -z "$name" ]; then |
| name=$(basename $wsgi) |
| fi |
| |
| # create a home for the sockets; note don't use /tmp -- apache has |
| # a private view of it on some platforms. |
| |
| # always cleanup given that we are using iniset here |
| rm -rf $conf |
| # Set either the module path or wsgi script path depending on what we've |
| # been given |
| if [[ "$wsgi" =~ ^[a-zA-Z0-9_.]+:[a-zA-Z0-9_]+$ ]]; then |
| iniset "$conf" uwsgi module "$wsgi" |
| else |
| deprecated 'Configuring uWSGI with a WSGI file is deprecated, use module paths instead' |
| iniset "$conf" uwsgi wsgi-file "$wsgi" |
| fi |
| port=$(get_random_port) |
| iniset "$conf" uwsgi http-socket "$APACHE_LOCAL_HOST:$port" |
| iniset "$conf" uwsgi processes $API_WORKERS |
| # This is running standalone |
| iniset "$conf" uwsgi master true |
| # Set die-on-term & exit-on-reload so that uwsgi shuts down |
| iniset "$conf" uwsgi die-on-term true |
| iniset "$conf" uwsgi exit-on-reload false |
| # Set worker-reload-mercy so that worker will not exit till the time |
| # configured after graceful shutdown |
| iniset "$conf" uwsgi worker-reload-mercy $WORKER_TIMEOUT |
| iniset "$conf" uwsgi enable-threads true |
| iniset "$conf" uwsgi plugins http,python3 |
| # uwsgi recommends this to prevent thundering herd on accept. |
| iniset "$conf" uwsgi thunder-lock true |
| # Set hook to trigger graceful shutdown on SIGTERM |
| iniset "$conf" uwsgi hook-master-start "unix_signal:15 gracefully_kill_them_all" |
| # Override the default size for headers from the 4k default. |
| iniset "$conf" uwsgi buffer-size 65535 |
| # Make sure the client doesn't try to re-use the connection. |
| iniset "$conf" uwsgi add-header "Connection: close" |
| # This ensures that file descriptors aren't shared between processes. |
| iniset "$conf" uwsgi lazy-apps true |
| iniset "$conf" uwsgi chmod-socket 666 |
| iniset "$conf" uwsgi http-raw-body true |
| iniset "$conf" uwsgi http-chunked-input true |
| iniset "$conf" uwsgi http-auto-chunked true |
| iniset "$conf" uwsgi http-keepalive false |
| # Increase socket timeout for slow chunked uploads |
| iniset "$conf" uwsgi socket-timeout 30 |
| # Starting time of the WSGi server |
| iniset "$conf" uwsgi start-time %t |
| |
| enable_apache_mod proxy |
| enable_apache_mod proxy_http |
| local apache_conf="" |
| apache_conf=$(apache_site_config_for $name) |
| echo "KeepAlive Off" | sudo tee $apache_conf |
| echo "SetEnv proxy-sendchunked 1" | sudo tee -a $apache_conf |
| echo "ProxyPass \"${url}\" \"http://$APACHE_LOCAL_HOST:$port\" retry=0 acquire=1 " | sudo tee -a $apache_conf |
| enable_apache_site $name |
| restart_apache_server |
| } |
| |
| # Write a straight-through proxy for a service that runs locally and just needs |
| # to be reachable via the main http proxy at $loc |
| function write_local_proxy_http_config { |
| local name=$1 |
| local url=$2 |
| local loc=$3 |
| local apache_conf |
| apache_conf=$(apache_site_config_for $name) |
| |
| enable_apache_mod proxy |
| enable_apache_mod proxy_http |
| |
| echo "KeepAlive Off" | sudo tee $apache_conf |
| echo "SetEnv proxy-sendchunked 1" | sudo tee -a $apache_conf |
| echo "ProxyPass \"${loc}\" \"$url\" retry=0 acquire=1 " | sudo tee -a $apache_conf |
| enable_apache_site $name |
| restart_apache_server |
| } |
| |
| function remove_uwsgi_config { |
| local conf=$1 |
| local wsgi=$2 |
| local name="" |
| # TODO(stephenfin): Remove this call when everyone is using module path |
| # configuration instead of file path configuration |
| name=$(basename $wsgi) |
| |
| if [[ "$wsgi" = /* ]]; then |
| deprecated "Passing a wsgi script to remove_uwsgi_config is deprecated, pass an application name instead" |
| fi |
| |
| rm -rf $conf |
| disable_apache_site $name |
| } |
| |
| # Restore xtrace |
| $_XTRACE_LIB_APACHE |
| |
| # Tell emacs to use shell-script-mode |
| ## Local variables: |
| ## mode: shell-script |
| ## End: |