diff --git a/.zuul.yaml b/.zuul.yaml
index dcb7de8a1d..921a89857a 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -481,6 +481,35 @@
- testinfra/test_adns.py
- testinfra/test_ns.py
+- job:
+ name: system-config-run-mirror
+ parent: system-config-run
+ description: |
+ Run the playbook for a mirror node
+ nodeset:
+ nodes:
+ - name: bridge.openstack.org
+ label: ubuntu-bionic
+ - name: mirror01.region.provider.opendev.org
+ label: ubuntu-bionic
+ vars:
+ run_playbooks:
+ - playbooks/service-letsencrypt.yaml
+ - playbooks/service-mirror.yaml
+ files:
+ - .zuul.yaml
+ - roles/
+ - playbooks/roles/mirror/
+ - playbooks/roles/letsencrypt.*
+ - playbooks/service-letsencrypt.yaml
+ - playbooks/service-mirror.yaml
+ - testinfra/test_mirror.py
+
+ host-vars:
+ mirror.region.provider.opendev.org:
+ host_copy_output:
+ '/var/log/apache2/': logs
+
- job:
name: system-config-run-docker-registry
parent: system-config-run
@@ -615,6 +644,7 @@
- system-config-run-dns
- system-config-run-eavesdrop
- system-config-run-nodepool
+ - system-config-run-mirror
- system-config-run-docker-registry
- system-config-run-gitea:
dependencies:
diff --git a/inventory/groups.yaml b/inventory/groups.yaml
index 3944e5f4ac..fa18f4c3bb 100644
--- a/inventory/groups.yaml
+++ b/inventory/groups.yaml
@@ -57,6 +57,7 @@ groups:
- opendev-k8s*.opendev.org
letsencrypt:
- graphite01.opendev.org
+ - mirror[0-9]*.opendev.org
logstash:
- logstash[0-9]*.open*.org
logstash-worker:
@@ -65,7 +66,9 @@ groups:
- lists*.katacontainers.io
- lists*.open*.org
mirror:
- - mirror[0-9]*.open*.org
+ - mirror[0-9]*.openstack.org
+ mirror_opendev:
+ - mirror[0-9]*.opendev.org
nodepool:
- nb[0-9]*.open*.org
- nl[0-9]*.open*.org
@@ -110,7 +113,7 @@ groups:
- logstash-worker[0-9]*.open*.org
- logstash[0-9]*.open*.org
- mirror-update[0-9]*.open*.org
- - mirror[0-9]*.open*.org
+ - mirror[0-9]*.openstack.org
- nb[0-9]*.open*.org
- nl[0-9]*.open*.org
- openstackid-dev*.openstack.org
@@ -161,7 +164,7 @@ groups:
- logstash-worker[0-9]*.open*.org
- logstash[0-9]*.open*.org
- mirror-update[0-9]*.open*.org
- - ^mirror[0-9].*\..*\.(?!linaro|linaro-london|arm64ci).*\.open.*\.org
+ - ^mirror[0-9].*\..*\.(?!linaro|linaro-london|arm64ci).*\.openstack\.org
- ^nb(?!03)[0-9]*\.open.*\.org
- nl[0-9]*.open*.org
- openstackid[0-9]*.openstack.org
diff --git a/playbooks/group_vars/mirror_opendev.yaml b/playbooks/group_vars/mirror_opendev.yaml
new file mode 100644
index 0000000000..3f4cb5ba92
--- /dev/null
+++ b/playbooks/group_vars/mirror_opendev.yaml
@@ -0,0 +1,6 @@
+iptables_extra_public_tcp_ports:
+ - 80
+ - 443
+ - 8080
+ - 8081
+ - 8082
diff --git a/playbooks/roles/letsencrypt-create-certs/handlers/main.yaml b/playbooks/roles/letsencrypt-create-certs/handlers/main.yaml
index 24fb8a4eab..86c0760a3a 100644
--- a/playbooks/roles/letsencrypt-create-certs/handlers/main.yaml
+++ b/playbooks/roles/letsencrypt-create-certs/handlers/main.yaml
@@ -30,3 +30,6 @@
import_tasks: touch_file.yaml
vars:
touch_file: '/tmp/letsencrypt02-main-service.stamp'
+
+- name: letsencrypt updated mirror01-region-provider-opendev-org-main
+ import_tasks: restart_apache.yaml
diff --git a/playbooks/roles/letsencrypt-create-certs/handlers/restart_apache.yaml b/playbooks/roles/letsencrypt-create-certs/handlers/restart_apache.yaml
new file mode 100644
index 0000000000..6b643ad236
--- /dev/null
+++ b/playbooks/roles/letsencrypt-create-certs/handlers/restart_apache.yaml
@@ -0,0 +1,8 @@
+- name: Populate service facts
+ service_facts:
+
+- name: Restart apache
+ service:
+ name: apache2
+ state: restarted
+ when: "'apache2' in ansible_facts.services"
\ No newline at end of file
diff --git a/playbooks/roles/mirror/README.rst b/playbooks/roles/mirror/README.rst
new file mode 100644
index 0000000000..412267c32f
--- /dev/null
+++ b/playbooks/roles/mirror/README.rst
@@ -0,0 +1,6 @@
+Configure an opendev mirror
+
+This role installs and configures a mirror node
+
+**Role Variables**
+
diff --git a/playbooks/roles/mirror/defaults/main.yaml b/playbooks/roles/mirror/defaults/main.yaml
new file mode 100644
index 0000000000..8268337479
--- /dev/null
+++ b/playbooks/roles/mirror/defaults/main.yaml
@@ -0,0 +1,3 @@
+mirror_root: '/afs/openstack.org/mirror'
+www_base: '/var/www'
+www_root: '{{ www_base }}/mirror'
\ No newline at end of file
diff --git a/playbooks/roles/mirror/files/robots.txt b/playbooks/roles/mirror/files/robots.txt
new file mode 100644
index 0000000000..1f53798bb4
--- /dev/null
+++ b/playbooks/roles/mirror/files/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /
diff --git a/playbooks/roles/mirror/handlers/main.yaml b/playbooks/roles/mirror/handlers/main.yaml
new file mode 100644
index 0000000000..6edd3d3105
--- /dev/null
+++ b/playbooks/roles/mirror/handlers/main.yaml
@@ -0,0 +1,4 @@
+- name: restart apache2
+ service:
+ name: apache2
+ state: restarted
\ No newline at end of file
diff --git a/playbooks/roles/mirror/tasks/main.yaml b/playbooks/roles/mirror/tasks/main.yaml
new file mode 100644
index 0000000000..ef167f323d
--- /dev/null
+++ b/playbooks/roles/mirror/tasks/main.yaml
@@ -0,0 +1,151 @@
+- name: Check AFS mounted
+ stat:
+ path: "/afs/openstack.org/mirror"
+ register: afs_mirror
+- name: Sanity check AFS
+ assert:
+ that:
+ - afs_mirror.stat.exists
+
+- name: Install apache2
+ apt:
+ name:
+ - apache2
+ - apache2-utils
+ state: present
+
+- name: Rewrite module
+ apache2_module:
+ state: present
+ name: rewrite
+
+- name: Substitute module
+ apache2_module:
+ state: present
+ name: substitute
+
+- name: Cache module
+ apache2_module:
+ state: present
+ name: cache
+
+- name: Cache disk module
+ apache2_module:
+ state: present
+ name: cache_disk
+
+- name: Proxy module
+ apache2_module:
+ state: present
+ name: proxy
+
+- name: HTTP Proxy module
+ apache2_module:
+ state: present
+ name: proxy_http
+
+- name: Apache macro module
+ apache2_module:
+ state: present
+ name: macro
+
+- name: Apache 2 ssl module
+ apache2_module:
+ state: present
+ name: ssl
+
+- name: Apache webroot
+ file:
+ path: '{{ www_base }}'
+ state: directory
+ owner: root
+ group: root
+
+- name: Apache www root
+ file:
+ path: '{{ www_root }}'
+ state: directory
+ owner: root
+ group: root
+
+- name: AFS content symlinks
+ file:
+ src: '{{ mirror_root }}/{{ item }}'
+ dest: '{{ www_root }}/{{ item }}'
+ state: link
+ owner: root
+ group: root
+ with_items:
+ - centos
+ - ceph-deb-hammer
+ - ceph-deb-jewel
+ - ceph-deb-luminous
+ - ceph-deb-mimic
+ - deb-docker
+ - debian
+ - debian-security
+ - debian-openstack
+ - epel
+ - fedora
+ - opensuse
+ - ubuntu-ports
+ - ubuntu-cloud-archive
+ - wheel
+ - yum-puppetlabs
+
+- name: Install robots.txt
+ copy:
+ src: robots.txt
+ dest: '{{ www_root }}'
+ owner: root
+ group: root
+ mode: 0444
+
+- name: Apache proxy cache
+ file:
+ path: /var/cache/apache2/proxy
+ owner: www-data
+ group: www-data
+ mode: 0755
+ state: directory
+
+- name: Set mirror servername and alias
+ set_fact:
+ apache_server_name: '{{ inventory_hostname }}'
+ # Strip the numeric host value from mirror01.region.provider.o.o
+ # for the serveralias
+ apache_server_alias: '{{ inventory_hostname | regex_replace("^mirror\d\d\.", "mirror.") }}'
+
+- name: Create mirror virtual host
+ template:
+ src: mirror.vhost.j2
+ dest: /etc/apache2/sites-available/mirror.conf
+
+- name: Make sure default site disabled
+ command: a2dissite 000-default.conf
+ args:
+ removes: /etc/apache2/sites-enabled/000-default.conf
+
+- name: Enable mirror virtual host
+ command: a2ensite mirror
+ args:
+ creates: /etc/apache2/sites-enabled/mirror.conf
+ notify:
+ - restart apache2
+
+- name: Debug config
+ slurp:
+ src: /etc/apache2/sites-available/mirror.conf
+ register: http_config
+- name: Show config
+ debug:
+ msg: '{{ http_config["content"] | b64decode }}'
+
+# Clean apache cache once an hour, keep size down to 70GiB.
+- name: Proxy cleanup cron job
+ cron:
+ name: Apache cache cleanup
+ state: present
+ job: /usr/bin/flock -n /var/run/htcacheclean.lock /usr/bin/htcacheclean -n -p /var/cache/apache2/proxy -t -l 70200M > /dev/null
+ minute: '0'
+ hour: '*'
diff --git a/playbooks/roles/mirror/templates/mirror.vhost.j2 b/playbooks/roles/mirror/templates/mirror.vhost.j2
new file mode 100644
index 0000000000..68ce814309
--- /dev/null
+++ b/playbooks/roles/mirror/templates/mirror.vhost.j2
@@ -0,0 +1,404 @@
+NameVirtualHost *:80
+NameVirtualHost *:443
+
+# Dedicated port for proxy caching, as not to affect afs mirrors.
+Listen 8080
+NameVirtualHost *:8080
+
+Listen 8081
+NameVirtualHost *:8081
+
+Listen 8082
+NameVirtualHost *:8082
+
+LogFormat "%h %l %u %t \"%r\" %>s %b %{cache-status}e \"%{Referer}i\" \"%{User-agent}i\"" combined-cache
+
+
+
+ DocumentRoot /var/www/mirror
+
+ Options Indexes FollowSymLinks MultiViews
+ AllowOverride None
+ Order allow,deny
+ allow from all
+ Satisfy any
+ = 2.4>
+ Require all granted
+
+
+
+ # Caching reverse proxy for things that don't make sense in AFS
+ #
+ # General cache rules
+ CacheRoot "/var/cache/apache2/proxy"
+ CacheDirLevels 5
+ CacheDirLength 2
+ # SSL support
+ SSLProxyEngine on
+ # Prevent thundering herds.
+ CacheLock on
+ CacheLockPath "/tmp/mod_cache-lock"
+ CacheLockMaxAge 5
+ # 5GiB
+ CacheMaxFileSize 5368709120
+ CacheStoreExpired On
+ # Pip sets Cache-Control: max-age=0 on requests for pypi index pages.
+ # This means we don't use the cache for those requests. This setting
+ # should force the proxy to ignore cache-control on the request side
+ # but we should still cache things based on the cache-control responses
+ # from the backed servers.
+ CacheIgnoreCacheControl On
+
+ # Added Aug 2017 in an attempt to avoid occasional 502 errors (around
+ # 0.05% of requests) of the type:
+ #
+ # End of file found: ... AH01102: error reading status line from remote server ...
+ #
+ # Per [1]:
+ #
+ # This avoids the "proxy: error reading status line from remote
+ # server" error message caused by the race condition that the backend
+ # server closed the pooled connection after the connection check by the
+ # proxy and before data sent by the proxy reached the backend.
+ #
+ # [1] https://httpd.apache.org/docs/2.4/mod/mod_proxy_http.html
+ SetEnv proxy-initial-not-pooled 1
+
+ RewriteEngine On
+ # pypi
+ CacheEnable disk "/pypi"
+ ProxyPass "/pypi/" "https://pypi.org/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/pypi/" "https://pypi.org/
+
+ # files.pythonhosted.org
+ CacheEnable disk "/pypifiles"
+ ProxyPass "/pypifiles/" "https://files.pythonhosted.org/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/pypifiles/" "https://files.pythonhosted.org/"
+
+ # Rewrite the locations of the actual files
+
+ SetOutputFilter INFLATE;SUBSTITUTE;DEFLATE
+ Substitute "s|https://files.pythonhosted.org/|/pypifiles/|ni"
+
+
+ # Wheel URL's are:
+ # /wheel/{distro}-{distro-version}/a/a/a-etc.whl
+ # /wheel/{distro}-{distro-version}/a/abcd/abcd-etc.whl
+ # /wheel/{distro}-{distro-version}/a/abcde/abcde-etc.whl
+ RewriteCond %{REQUEST_URI} ^/wheel/([^/]+)/([^/])([^/]*)
+ RewriteCond %{DOCUMENT_ROOT}/wheel/$1/$2/$2$3 -d
+ RewriteRule ^/wheel/([^/]+)/([^/])([^/]*)(/.*)?$ /wheel/$1/$2/$2$3$4 [L]
+
+ # Special cases for openstack.nose_plugin & backports.*
+ RewriteCond %{REQUEST_URI} ^/wheel/
+ RewriteRule ^(.*)/openstack-nose-plugin(.*)$ $1/openstack.nose_plugin$2
+ RewriteCond %{REQUEST_URI} ^/wheel/
+ RewriteRule ^(.*)/backports-(.*)$ $1/backports.$2
+
+ # Try again but replacing -'s with .'s
+ RewriteCond %{REQUEST_URI} ^/wheel/
+ RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_URI} !-f
+ RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_URI} !-d
+ RewriteRule (.*)-(.*) $1.$2 [N]
+
+ ErrorLog /var/log/apache2/proxy_$port_error.log
+ LogLevel warn
+ CustomLog /var/log/apache2/proxy_$port_access.log combined-cache
+ ServerSignature Off
+
+
+
+
+ ServerName {{ apache_server_name }}
+ ServerAlias {{ apache_server_alias }}
+
+ Use BaseProxy 80
+
+
+
+ ServerName {{ apache_server_name }}
+ ServerAlias {{ apache_server_alias }}
+
+ SSLCertificateFile /etc/letsencrypt-certs/{{ apache_server_name }}/{{ apache_server_name }}.cer
+ SSLCertificateKeyFile /etc/letsencrypt-certs/{{ apache_server_name }}/{{ apache_server_name }}.key
+ SSLCertificateChainFile /etc/letsencrypt-certs/{{ apache_server_name }}/ca.cer
+ SSLProtocol All -SSLv2 -SSLv3
+ # Note: this list should ensure ciphers that provide forward secrecy
+ SSLCipherSuite ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:!AES256:!aNULL:!eNULL:!MD5:!DSS:!PSK:!SRP
+ SSLHonorCipherOrder on
+
+ Use BaseProxy 443
+
+
+
+ ServerName {{ apache_server_name }}:8080
+ ServerAlias {{ apache_server_alias }}:8080
+
+ # Disable directory listing by default.
+
+ Order Deny,Allow
+ Deny from all
+ Options None
+ AllowOverride None
+
+
+ ErrorLog /var/log/apache2/proxy_8080_error.log
+ LogLevel warn
+ CustomLog /var/log/apache2/proxy_8080_access.log combined-cache
+ ServerSignature Off
+
+ # Caching reverse proxy for things that don't make sense in AFS
+ #
+ # General cache rules
+ CacheRoot "/var/cache/apache2/proxy"
+ CacheDirLevels 5
+ CacheDirLength 2
+ # SSL support
+ SSLProxyEngine on
+ # Prevent thundering herds.
+ CacheLock on
+ CacheLockPath "/tmp/mod_cache-lock"
+ CacheLockMaxAge 5
+ # 5GiB
+ CacheMaxFileSize 5368709120
+ CacheStoreExpired On
+
+ # Added Aug 2017 in an attempt to avoid occasional 502 errors (around
+ # 0.05% of requests) of the type:
+ #
+ # End of file found: ... AH01102: error reading status line from remote server ...
+ #
+ # Per [1]:
+ #
+ # This avoids the "proxy: error reading status line from remote
+ # server" error message caused by the race condition that the backend
+ # server closed the pooled connection after the connection check by the
+ # proxy and before data sent by the proxy reached the backend.
+ #
+ # [1] https://httpd.apache.org/docs/2.4/mod/mod_proxy_http.html
+ SetEnv proxy-initial-not-pooled 1
+
+ # Per site caching reverse proxy rules
+ # Only cache specific backends, rely on afs cache otherwise.
+
+ # buildlogs.centos.org (302 redirects to buildlogs.cdn.centos.org)
+ CacheEnable disk "/buildlogs.centos"
+ ProxyPass "/buildlogs.centos/" "https://buildlogs.centos.org/" ttl=120 disablereuse=On retry=0
+ ProxyPassReverse "/buildlogs.centos/" "https://buildlogs.centos.org/"
+
+ # buildlogs.cdn.centos.org
+ CacheEnable disk "/buildlogs.cdn.centos"
+ ProxyPass "/buildlogs.cdn.centos/" "https://buildlogs.cdn.centos.org/" ttl=120 disablereuse=On retry=0
+ ProxyPassReverse "/buildlogs.cdn.centos/" "https://buildlogs.cdn.centos.org/"
+
+ # rdo
+ CacheEnable disk "/rdo"
+ ProxyPass "/rdo/" "https://trunk.rdoproject.org/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/rdo/" "https://trunk.rdoproject.org/"
+
+ # cbs.centos.org
+ CacheEnable disk "/cbs.centos"
+ ProxyPass "/cbs.centos/" "https://cbs.centos.org/repos/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/cbs.centos/" "https://cbs.centos.org/repos/"
+
+ # tarballs
+ CacheEnable disk "/tarballs"
+ ProxyPass "/tarballs/" "https://tarballs.openstack.org/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/tarballs/" "https://tarballs.openstack.org/"
+
+ # pypi
+ CacheEnable disk "/pypi"
+ ProxyPass "/pypi/" "https://pypi.org/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/pypi/" "https://pypi.org/
+
+ # files.pythonhosted.org
+ CacheEnable disk "/pypifiles"
+ ProxyPass "/pypifiles/" "https://files.pythonhosted.org/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/pypifiles/" "https://files.pythonhosted.org/"
+
+ # Rewrite the locations of the actual files
+
+ SetOutputFilter INFLATE;SUBSTITUTE;DEFLATE
+ Substitute "s|https://files.pythonhosted.org/|/pypifiles/|ni"
+
+
+ # images.linuxcontainers.org
+ CacheEnable disk "/images.linuxcontainers"
+ ProxyPass "/images.linuxcontainers/" "http://us.images.linuxcontainers.org/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/images.linuxcontainers/" "http://us.images.linuxcontainers.org/"
+
+ # registry.npmjs.org
+ CacheEnable disk "/registry.npmjs"
+ ProxyPass "/registry.npmjs/" "https://registry.npmjs.org/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/registry.npmjs/" "https://registry.npmjs.org/"
+
+ # api.rubygems.org
+ CacheEnable disk "/api.rubygems"
+ ProxyPass "/api.rubygems/" "https://api.rubygems.org/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/api.rubygems/" "https://api.rubygems.org/"
+
+ # rubygems.org
+ CacheEnable disk "/rubygems"
+ ProxyPass "/rubygems/" "https://rubygems.org/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/rubygems/" "https://rubygems.org/"
+
+ # opendaylight
+ CacheEnable disk "/opendaylight"
+ ProxyPass "/opendaylight/" "https://nexus.opendaylight.org/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/opendaylight/" "https://nexus.opendaylight.org/"
+
+ # elastico
+ CacheEnable disk "/elastic"
+ ProxyPass "/elastic/" "https://packages.elastic.co/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/elastic/" "https://packages.elastic.co/"
+
+ # grafana
+ CacheEnable disk "/grafana"
+ ProxyPass "/grafana" "https://packagecloud.io/grafana/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/grafana/" "https://packagecloud.io/grafana/"
+
+ # OracleLinux
+ CacheEnable disk "/oraclelinux"
+ ProxyPass "/oraclelinux/" "http://yum.oracle.com/repo/OracleLinux/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/oraclelinux/" "http://yum.oracle.com/repo/OracleLinux/"
+
+ # Percona
+ CacheEnable disk "/percona"
+ ProxyPass "/percona/" "https://repo.percona.com/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/percona/" "https://repo.percona.com/"
+
+ # MariaDB
+ CacheEnable disk "/MariaDB"
+ ProxyPass "/MariaDB/" "https://downloads.mariadb.com/MariaDB/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/MariaDB/" "https://downloads.mariadb.com/MariaDB/"
+
+ # Docker
+ CacheEnable disk "/docker"
+ ProxyPass "/docker/" "https://download.docker.com/linux/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/docker/" "https://download.docker.com/linux/"
+
+ # Alpine
+ CacheEnable disk "/alpine"
+ ProxyPass "/alpine/" "http://dl-cdn.alpinelinux.org/alpine/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/alpine/" "http://dl-cdn.alpinelinux.org/alpine/"
+
+ # LXC (copr)
+ CacheEnable disk "/copr-lxc2"
+ ProxyPass "/copr-lxc2/" "https://copr-be.cloud.fedoraproject.org/results/thm/lxc2.0/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/copr-lxc2/" "https://copr-be.cloud.fedoraproject.org/results/thm/lxc2.0/"
+
+
+
+# Docker registry v1 proxy.
+
+ ServerName {{ apache_server_name }}:8081
+ ServerAlias {{ apache_server_alias }}:8081
+
+ # Disable directory listing by default.
+
+ Order Deny,Allow
+ Deny from all
+ Options None
+ AllowOverride None
+
+
+ ErrorLog /var/log/apache2/proxy_8081_error.log
+ LogLevel warn
+ CustomLog /var/log/apache2/proxy_8081_access.log combined-cache
+ ServerSignature Off
+
+ # Caching reverse proxy for things that don't make sense in AFS
+ #
+ # General cache rules
+ CacheRoot "/var/cache/apache2/proxy"
+ CacheDirLevels 5
+ CacheDirLength 2
+ # SSL support
+ SSLProxyEngine on
+ # Prevent thundering herds.
+ CacheLock on
+ CacheLockPath "/tmp/mod_cache-lock"
+ CacheLockMaxAge 5
+ # 5GiB
+ CacheMaxFileSize 5368709120
+ # Ignore expire headers as the urls use sha256 hashes.
+ CacheIgnoreQueryString On
+ # NOTE(pabelanger): In the case of docker, if neither an expiry date nor
+ # last-modified date are provided default expire to 1 day. This is up from
+ # 1 hour.
+ CacheDefaultExpire 86400
+ CacheStoreExpired On
+
+ # registry-1.docker.io
+ CacheEnable disk "/registry-1.docker"
+ ProxyPass "/registry-1.docker/" "https://registry-1.docker.io/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/registry-1.docker/" "https://registry-1.docker.io/"
+
+ # dseasb33srnrn.cloudfront.net
+ CacheEnable disk "/cloudfront"
+ ProxyPass "/cloudfront/" "https://dseasb33srnrn.cloudfront.net/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/cloudfront/" "https://dseasb33srnrn.cloudfront.net/"
+
+ # production.cloudflare.docker.com
+ CacheEnable disk "/cloudflare"
+ ProxyPass "/cloudflare/" "https://production.cloudflare.docker.com/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/cloudflare/" "https://production.cloudflare.docker.com/"
+
+
+
+# Docker registry v2 proxy.
+
+ ServerName {{ apache_server_name }}:8082
+ ServerAlias {{ apache_server_alias }}:8082
+
+ # Disable directory listing by default.
+
+ Order Deny,Allow
+ Deny from all
+ Options None
+ AllowOverride None
+
+
+ ErrorLog /var/log/apache2/proxy_8082_error.log
+ LogLevel warn
+ CustomLog /var/log/apache2/proxy_8082_access.log combined-cache
+ ServerSignature Off
+
+ # Caching reverse proxy for things that don't make sense in AFS
+ #
+ # General cache rules
+ CacheRoot "/var/cache/apache2/proxy"
+ CacheDirLevels 5
+ CacheDirLength 2
+ # SSL support
+ SSLProxyEngine on
+ # Prevent thundering herds.
+ CacheLock on
+ CacheLockPath "/tmp/mod_cache-lock"
+ CacheLockMaxAge 5
+ # 5GiB
+ CacheMaxFileSize 5368709120
+ # Ignore expire headers as the urls use sha256 hashes.
+ CacheIgnoreQueryString On
+ # NOTE(pabelanger): In the case of docker, if neither an expiry date nor
+ # last-modified date are provided default expire to 1 day. This is up from
+ # 1 hour.
+ CacheDefaultExpire 86400
+ CacheStoreExpired On
+
+ # dseasb33srnrn.cloudfront.net
+ CacheEnable disk "/cloudfront"
+ ProxyPass "/cloudfront/" "https://dseasb33srnrn.cloudfront.net/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/cloudfront/" "https://dseasb33srnrn.cloudfront.net/"
+
+ # production.cloudflare.docker.com
+ CacheEnable disk "/cloudflare"
+ ProxyPass "/cloudflare/" "https://production.cloudflare.docker.com/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/cloudflare/" "https://production.cloudflare.docker.com/"
+
+ # NOTE(corvus): Ensure this stanza is last since it's the most
+ # greedy match.
+ CacheEnable disk "/"
+ ProxyPass "/" "https://registry-1.docker.io/" ttl=120 keepalive=On retry=0
+ ProxyPassReverse "/" "https://registry-1.docker.io/"
+
diff --git a/playbooks/service-mirror.yaml b/playbooks/service-mirror.yaml
new file mode 100644
index 0000000000..ba6f0c59d6
--- /dev/null
+++ b/playbooks/service-mirror.yaml
@@ -0,0 +1,11 @@
+- hosts: "mirror_opendev:!disabled"
+ name: "Configure per region opendev mirrors"
+ roles:
+ - role: kerberos-client
+ kerberos_realm: 'OPENSTACK.ORG'
+ kerberos_admin_server: 'kdc.openstack.org'
+ kerberos_kdcs:
+ - kdc03.openstack.org
+ - kdc04.openstack.org
+ - role: openafs-client
+ - role: mirror
\ No newline at end of file
diff --git a/playbooks/zuul/run-base.yaml b/playbooks/zuul/run-base.yaml
index 649dcb1e2f..5786816f70 100644
--- a/playbooks/zuul/run-base.yaml
+++ b/playbooks/zuul/run-base.yaml
@@ -81,6 +81,7 @@
- host_vars/bridge.openstack.org.yaml
- host_vars/letsencrypt01.opendev.org.yaml
- host_vars/letsencrypt02.opendev.org.yaml
+ - host_vars/mirror01.region.provider.opendev.org.yaml
- name: Display group membership
command: ansible localhost -m debug -a 'var=groups'
- name: Run base.yaml
diff --git a/playbooks/zuul/templates/gate-groups.yaml.j2 b/playbooks/zuul/templates/gate-groups.yaml.j2
index 206b4e6585..fbecbbc3e8 100644
--- a/playbooks/zuul/templates/gate-groups.yaml.j2
+++ b/playbooks/zuul/templates/gate-groups.yaml.j2
@@ -8,3 +8,4 @@ groups:
letsencrypt:
- letsencrypt01.opendev.org
- letsencrypt02.opendev.org
+ - mirror01.region.provider.opendev.org
diff --git a/playbooks/zuul/templates/host_vars/mirror01.region.provider.opendev.org.yaml.j2 b/playbooks/zuul/templates/host_vars/mirror01.region.provider.opendev.org.yaml.j2
new file mode 100644
index 0000000000..083952b24c
--- /dev/null
+++ b/playbooks/zuul/templates/host_vars/mirror01.region.provider.opendev.org.yaml.j2
@@ -0,0 +1,4 @@
+letsencrypt_certs:
+ mirror01-region-provider-opendev-org-main:
+ - mirror01.region.provider.opendev.org
+ - mirror.region.provider.opendev.org
diff --git a/run_all.sh b/run_all.sh
index 8647625b01..33f7927661 100755
--- a/run_all.sh
+++ b/run_all.sh
@@ -100,6 +100,10 @@ start_timer
timeout -k 2m 30m ansible-playbook -f 50 ${ANSIBLE_PLAYBOOKS}/service-nodepool.yaml
send_timer nodepool
+start_timer
+timeout -k 2m 30m ansible-playbook -f 50 ${ANSIBLE_PLAYBOOKS}/service-mirror.yaml
+send_timer nodepool
+
start_timer
timeout -k 2m 30m ansible-playbook -f 50 ${ANSIBLE_PLAYBOOKS}/service-registry.yaml
send_timer registry
diff --git a/testinfra/test_mirror.py b/testinfra/test_mirror.py
new file mode 100644
index 0000000000..404733f1fa
--- /dev/null
+++ b/testinfra/test_mirror.py
@@ -0,0 +1,32 @@
+# Copyright 2019 Red Hat, Inc.
+#
+# Licensed 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.
+
+
+testinfra_hosts = ['mirror01.region.provider.opendev.org']
+
+
+def test_apache(host):
+ apache = host.service('apache2')
+ assert apache.is_running
+
+def test_mirror_indexes(host):
+ cmd = host.run("wget --no-check-certificate -qO- https://localhost/")
+ assert '' in cmd.stdout
+
+ cmd = host.run("wget -qO- http://localhost/")
+ assert '' in cmd.stdout
+
+# NOTE(ianw): further testing idea for anyone interested; get the
+# actual IP address of the mirror node and connect via that, and then
+# also poke at the other proxy ports