Skip to content

Commit

Permalink
Implement pypiserver and pypi proxy cache
Browse files Browse the repository at this point in the history
This patch implements nginx as a reverse proxy for python
packages. The initial query will be to a local deployment
of pypiserver in order to serve any locally built packages,
but if the package is not available locally it will retry
the query against pypi and cache the response.

Depends-On: Id20a43fed833d53ca0f147f517deafba6587352d
Change-Id: Ic4fd64f4dc82121a65088f3d7f4ae53f373df608
Implements: blueprint python-build-install-simplification
Signed-off-by: Jesse Pretorius <jesse.pretorius@rackspace.co.uk>
  • Loading branch information
Jesse Pretorius committed Nov 24, 2017
1 parent 956a942 commit 6320c00
Show file tree
Hide file tree
Showing 10 changed files with 194 additions and 5 deletions.
45 changes: 44 additions & 1 deletion defaults/main.yml
Expand Up @@ -13,16 +13,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.

## Verbosity Options
debug: False

## Cap the maximum number of threads / workers when a user value is unspecified.
repo_nginx_threads_max: 16
repo_nginx_threads: "{{ [[ansible_processor_vcpus|default(2) // 2, 1] | max, repo_nginx_threads_max] | min }}"

## APT Cache Options
cache_timeout: 600

# Set the package install state for distribution packages
# Set the package install state for distribution and pip packages
# Options are 'present' and 'latest'
repo_server_package_state: "latest"
repo_server_pip_package_state: "latest"

repo_worker_connections: 1024
repo_server_name: openstack-slushee
Expand Down Expand Up @@ -61,3 +65,42 @@ repo_pkg_cache_group: apt-cacher-ng

# Set the log directory
repo_service_log_dir: /var/log/apt-cacher-ng

# Required packages to install on the host
repo_requires_pip_packages:
- virtualenv
- virtualenv-tools

# Set the list of packages for the pypiserver
repo_pypiserver_pip_packages:
- "pypiserver==1.2.0"

# Set the path to place all built python wheels
# This is used by pypiserver to serve them
repo_pypiserver_package_path: "{{ repo_service_home_folder }}/repo/python_packages"

# Path to the pypiserver python virtualenv binaries
repo_pypiserver_bin: "/openstack/venvs/pypiserver-1.2.0/bin"

# Path to the pypiserver working directory
repo_pypiserver_working_dir: "{{ repo_service_home_folder }}/pypiserver"

# pypiserver service start options
repo_pypiserver_start_options: >-
-i localhost
-p 8080
--log-file /var/log/pypiserver/pypiserver.log
--disable-fallback
{{ (debug | bool) | ternary('-vv', '-v') }}
{{ repo_pypiserver_package_path }}
# config override var for systemd init file
repo_pypiserver_init_overrides: {}

# Set the options for the nginx proxy_cache_path directive.
# The proxy cache is used for data downloaded from pypi.
# The default is set to cache up to 1G worth of packages
# for up to 1 month
repo_nginx_proxy_cache_path: >-
/var/lib/nginx/pypi levels=1:2 keys_zone=pypi:16m inactive=1M max_size=1G
11 changes: 11 additions & 0 deletions handlers/main.yml
Expand Up @@ -79,3 +79,14 @@
retries: 5
delay: 2

- name: reload pypiserver
service:
name: "pypiserver"
enabled: yes
state: restarted
daemon_reload: "{{ (ansible_service_mgr == 'systemd') | ternary('yes', omit) }}"
register: _restart
until: _restart | success
retries: 5
delay: 2

1 change: 1 addition & 0 deletions meta/main.yml
Expand Up @@ -38,3 +38,4 @@ dependencies:
- role: apt_package_pinning
when:
- ansible_pkg_mgr == 'apt'
- role: pip_install
@@ -0,0 +1,8 @@
---
features:
- |
The repo server now implements nginx as a reverse proxy for python
packages sourced from pypi. The initial query will be to a local
deployment of pypiserver in order to serve any locally built packages,
but if the package is not available locally it will retry
the query against pypi and cache the response.
35 changes: 31 additions & 4 deletions tasks/repo_install.yml
Expand Up @@ -13,15 +13,42 @@
# See the License for the specific language governing permissions and
# limitations under the License.

- name: Install repo server packages
- name: Install distro packages
package:
name: "{{ repo_server_distro_packages }}"
state: "{{ repo_server_package_state }}"
update_cache: "{{ (ansible_pkg_mgr == 'apt') | ternary('yes', omit) }}"
cache_valid_time: "{{ (ansible_pkg_mgr == 'apt') | ternary(cache_timeout, omit) }}"
register: install_packages
until: install_packages|success
until: install_packages | success
retries: 5
delay: 5
tags:
- repo-packages

- name: Install required pip packages
pip:
name: "{{ repo_requires_pip_packages }}"
state: "{{ repo_server_pip_package_state }}"
extra_args: >-
{{ (pip_install_upper_constraints is defined) | ternary('--constraint ' + pip_install_upper_constraints | default(''),'') }}
{{ pip_install_options | default('') }}
register: install_packages
until: install_packages | success
retries: 5
delay: 2

- name: Install pip packages
pip:
name: "{{ repo_pypiserver_pip_packages }}"
state: "{{ repo_server_pip_package_state }}"
virtualenv: "{{ repo_pypiserver_bin | dirname }}"
virtualenv_site_packages: "no"
extra_args: >-
{{ (pip_install_upper_constraints is defined) | ternary('--constraint ' + pip_install_upper_constraints | default(''),'') }}
{{ pip_install_options | default('') }}
register: install_packages
until: install_packages | success
retries: 5
delay: 2
notify:
- reload pypiserver

15 changes: 15 additions & 0 deletions tasks/repo_post_install.yml
Expand Up @@ -39,6 +39,8 @@
dest: "/etc/rsyncd.conf"
- src: "openstack-slushee.vhost.j2"
dest: "/etc/nginx/sites-available/openstack-slushee.vhost"
- src: "nginx-pypi.conf.j2"
dest: "/etc/nginx/conf.d/pypi.conf"
notify:
- reload nginx

Expand All @@ -64,3 +66,16 @@
dest: "{{ systemd_utils_prefix }}/system/git.socket"
notify:
- reload git socket

- name: Place the pypiserver systemd init script
config_template:
src: "pypiserver-systemd-init.j2"
dest: "/etc/systemd/system/pypiserver.service"
mode: "0644"
owner: "root"
group: "root"
config_overrides: "{{ repo_pypiserver_init_overrides }}"
config_type: "ini"
notify:
- reload pypiserver

6 changes: 6 additions & 0 deletions tasks/repo_pre_install.yml
Expand Up @@ -62,6 +62,10 @@
- path: "{{ repo_service_home_folder }}/repo/venvs"
- path: "/var/log/nginx"
mode: "0775"
- path: "/var/log/pypiserver"
mode: "0775"
- path: "{{ repo_pypiserver_working_dir }}"
mode: "0775"

- name: Drop repo pre/post command script
template:
Expand Down Expand Up @@ -96,6 +100,8 @@
- path: "/var/log/lsyncd"
- path: "/etc/nginx/sites-enabled/default"
state: "absent"
- path: "/etc/nginx/conf.d"
- path: "/etc/nginx/sites-available"
- path: "/etc/nginx/sites-enabled"
- path: "{{ repo_pypiserver_package_path }}"

12 changes: 12 additions & 0 deletions templates/nginx-pypi.conf.j2
@@ -0,0 +1,12 @@
# {{ ansible_managed }}

proxy_cache_path {{ repo_nginx_proxy_cache_path }};

upstream pypiserver {
server localhost:8080;
}

upstream pypi {
server pypi.python.org:443;
keepalive 16;
}
34 changes: 34 additions & 0 deletions templates/openstack-slushee.vhost.j2
Expand Up @@ -6,6 +6,40 @@ server {
access_log /var/log/nginx/{{ repo_server_name }}.access.log gzip buffer=32k;
error_log /var/log/nginx/{{ repo_server_name }}.error.log notice;

# Allow cached content to be used even when the upstream source is not available.
proxy_cache pypi;
proxy_cache_key $uri;
proxy_cache_lock on;
proxy_cache_revalidate on;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;

proxy_http_version 1.1;
proxy_set_header Host $host:$server_port;
proxy_set_header Connection "";
proxy_set_header Accept-Encoding "";

# Rewrite any http redirects to use relative to proxy
proxy_redirect ~https?://pypi.python.org(.*) $1;

# Fallback mechanism from:
# http://linuxplayer.org/2013/06/nginx-try-files-on-multiple-named-location-or-server
location @pypi {
proxy_set_header Host pypi.python.org;
proxy_pass https://pypi;
}

location /simple {
proxy_intercept_errors on;
proxy_pass http://pypiserver;
error_page 404 = @pypi;
}

location /packages {
proxy_intercept_errors on;
proxy_pass http://pypiserver;
error_page 404 = @pypi;
}

location / {
root {{ repo_service_home_folder }}/repo/;
autoindex on;
Expand Down
32 changes: 32 additions & 0 deletions templates/pypiserver-systemd-init.j2
@@ -0,0 +1,32 @@
# {{ ansible_managed }}

[Unit]
Description=pypiserver
After=network.target

[Service]
Type=simple
User={{ repo_service_user_name }}
Group={{ repo_service_group_name }}

ExecStart={{ repo_pypiserver_bin }}/pypi-server {{ repo_pypiserver_start_options }}
ExecStop=/bin/kill -TERM $MAINPID
WorkingDirectory={{ repo_pypiserver_working_dir }}

# Give a reasonable amount of time for the server to start up/shut down
TimeoutSec=120
TimeoutStartSec=3
Restart=on-failure
RestartSec=2

# This creates a specific slice which all services will operate from
# The accounting options give us the ability to see resource usage through
# the `systemd-cgtop` command.
Slice=pypiserver.slice
CPUAccounting=true
BlockIOAccounting=true
MemoryAccounting=false
TasksAccounting=true

[Install]
WantedBy=multi-user.target

0 comments on commit 6320c00

Please sign in to comment.