├── deploy ├── builds │ └── .gitkeep └── logs │ └── .gitkeep ├── src ├── hadjango │ ├── __init__.py │ ├── uwsgi │ │ ├── __init__.py │ │ ├── bootstrap.py │ │ └── wsgi.py │ └── management │ │ ├── __init__.py │ │ └── commands │ │ ├── __init__.py │ │ └── warmup.py ├── requirements │ ├── hadjango.txt │ └── prod_without_hash.txt └── local_settings.py ├── .gitconfig ├── docker ├── README.md ├── mysql.repo ├── hadjango.repo ├── epel.repo ├── epel-testing.repo ├── start-docker.sh ├── supervisor.conf ├── RPM-GPG-KEY-EPEL-7 ├── RPM-GPG-KEY-hadjango ├── start-nginx.sh └── RPM-GPG-KEY-mysql ├── .dockerignore ├── .gitignore ├── fabfile ├── README.md ├── test.py ├── build_port_convert.py ├── utils.py └── __init__.py ├── wsgi ├── stub.ini ├── uwsgi_status.ini ├── README.md ├── stub.py └── uwsgi_status.py ├── .gitmodules ├── conf └── uwsgi │ ├── vassal.skel │ └── zerg.skel ├── LICENSE.md ├── docker-compose.yml ├── Dockerfile └── README.md /deploy/builds/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deploy/logs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/hadjango/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/hadjango/uwsgi/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/hadjango/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/hadjango/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitconfig: -------------------------------------------------------------------------------- 1 | [user] 2 | email = nobody@nowhere.com 3 | name = Nobody 4 | -------------------------------------------------------------------------------- /src/requirements/hadjango.txt: -------------------------------------------------------------------------------- 1 | uWSGI==2.0.12 2 | futures==3.0.3 3 | requests-futures==0.9.5 4 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | These are files that are either copied into docker containers, or are 2 | executed as the entry points in `docker-compose.yml`. 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .npm 3 | deploy 4 | node_modules 5 | .bash_history 6 | .lesshst 7 | .npmrc 8 | .cache 9 | .ipython 10 | docker/artifacts 11 | *.pyc 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs/* 2 | .bash_history 3 | .lesshst 4 | .npmrc 5 | .cache 6 | deps/ 7 | *.pid 8 | .ipython 9 | assets/ 10 | deploy/ 11 | docker/artifacts 12 | *.pyc 13 | .npm 14 | -------------------------------------------------------------------------------- /fabfile/README.md: -------------------------------------------------------------------------------- 1 | Defines all of the commands available to fabric (run `fab --list` from the root 2 | of the repository to see a full list). 3 | 4 | All tasks are defined in `fabfile/__init__.py`. 5 | -------------------------------------------------------------------------------- /wsgi/stub.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | plugins = logfile,http,python 3 | master = true 4 | uid = olympia 5 | gid = olympia 6 | http = :1999 7 | processes = 1 8 | wsgi-file = /code/wsgi/stub.py 9 | procname = uwsgi stub 10 | logto = /code/deploy/logs/uwsgi-stub.log 11 | -------------------------------------------------------------------------------- /docker/mysql.repo: -------------------------------------------------------------------------------- 1 | [mysql56-community] 2 | name=MySQL 5.6 Community Server 3 | baseurl=https://s3-us-west-2.amazonaws.com/net-mozaws-prod-us-west-2-ops-rpmrepo-mirror/mysql/5.6/7/$basearch/ 4 | enabled=1 5 | gpgcheck=1 6 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-mysql 7 | -------------------------------------------------------------------------------- /src/requirements/prod_without_hash.txt: -------------------------------------------------------------------------------- 1 | # Pinned versions, equivalent to their editable git installs in 2 | # addons-server/requirements/prod_without_hash.txt, to allow wheel install with 3 | # --no-index 4 | jingo-minify==0.6.0 5 | django-cache-machine==0.9.1 6 | django-mobility==0.1 7 | -------------------------------------------------------------------------------- /wsgi/uwsgi_status.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | plugins = logfile,http,python 3 | master = true 4 | uid = olympia 5 | gid = olympia 6 | http = :9999 7 | processes = 1 8 | wsgi-file = /code/wsgi/uwsgi_status.py 9 | procname = uwsgi status 10 | logto = /code/deploy/logs/uwsgi-status.log 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "addons-server"] 2 | path = addons-server 3 | url = https://github.com/hadjango/addons-server.git 4 | [submodule "src/uwsgi-dashboard"] 5 | path = uwsgi-dashboard 6 | url = https://github.com/hadjango/uwsgi-dashboard.git 7 | [submodule "nginx"] 8 | path = nginx 9 | url = https://github.com/hadjango/addons-nginx.git 10 | -------------------------------------------------------------------------------- /conf/uwsgi/vassal.skel: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | plugins = logfile,http,python 3 | build = %c 4 | port = @(exec:///code/fabfile/build_port_convert.py %c) 5 | uid = olympia 6 | gid = olympia 7 | chmod-socket = 666 8 | chown-socket = olympia:olympia 9 | 10 | thunder-lock = true 11 | master = true 12 | stats = /var/run/uwsgi/%(build)-vassal.stats 13 | processes = 0 14 | http = :%(port) 15 | zerg-server = /var/run/uwsgi/%(build).sock 16 | logto = /code/deploy/logs/vassal.%(build).log 17 | 18 | ; Kill request after 15 seconds 19 | http-timeout = 15 20 | -------------------------------------------------------------------------------- /docker/hadjango.repo: -------------------------------------------------------------------------------- 1 | [hadjango] 2 | name=hadjango 3 | baseurl=https://hadjango.github.io/rpms/el/7/$basearch 4 | repo_gpgcheck=0 5 | gpgcheck=1 6 | enabled=1 7 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-hadjango 8 | sslverify=1 9 | sslcacert=/etc/pki/tls/certs/ca-bundle.crt 10 | metadata_expire=300 11 | 12 | [hadjango-source] 13 | name=hadjango-source 14 | baseurl=https://hadjango.github.io/rpms/el/7/SRPMS 15 | repo_gpgcheck=0 16 | gpgcheck=1 17 | enabled=1 18 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-hadjango 19 | sslverify=1 20 | sslcacert=/etc/pki/tls/certs/ca-bundle.crt 21 | metadata_expire=300 22 | -------------------------------------------------------------------------------- /wsgi/README.md: -------------------------------------------------------------------------------- 1 | Contains the uwsgi configuration files (`*.ini`) and wsgi files `*.py` for 2 | simple wsgi applications that start on load. 3 | 4 |
To continue, run:
38 |fab init
39 | from the root of the checked out repository.
40 |
41 | """
42 |
43 |
44 | def application(environ, start_response):
45 | status = '200 OK'
46 | headers = [
47 | ('Content-Type', 'text/html'),
48 | ('Content-Length', str(len(html))),
49 | ]
50 | start_response(status, headers)
51 | return [html]
52 |
--------------------------------------------------------------------------------
/fabfile/test.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from fabric.api import task
5 |
6 | from .utils import docker_exec, build_venv
7 |
8 |
9 | __all__ = ('run_all', 'es', 'failed', 'force_db', 'no_es', 'tdd')
10 |
11 |
12 | def run_tests(name="live", flags='', args=''):
13 | with build_venv(name):
14 | docker_exec("py.test %s src/olympia %s" % (flags, args))
15 |
16 |
17 | @task
18 | def tdd(name="live", args=''):
19 | """to run the entire test suite, but stop on the first error"""
20 | run_tests(name, "-x --pdb", args)
21 |
22 |
23 | @task
24 | def run_all(name="live", args=''):
25 | """to run the entire test suite"""
26 | run_tests(name, args=args)
27 |
28 |
29 | @task
30 | def es(name="live", args=''):
31 | """to run the ES tests"""
32 | run_tests(name, "-m es_tests", args)
33 |
34 |
35 | @task
36 | def failed(name="live", args=''):
37 | """to rerun the failed tests from the previous run"""
38 | run_tests(name, "--lf", args)
39 |
40 |
41 | @task
42 | def force_db(name="live", args=''):
43 | """to run the entire test suite with a new database"""
44 | run_tests(name, "--create-db", args)
45 |
46 |
47 | @task
48 | def no_es(name="live", args=''):
49 | """to run all but the ES tests"""
50 | run_tests(name, "-m 'no es_tests'", args)
51 |
--------------------------------------------------------------------------------
/src/hadjango/uwsgi/bootstrap.py:
--------------------------------------------------------------------------------
1 | """
2 | A script which preloads most modules in Django.
3 |
4 | This file is executed via execfile() from other files in this directory.
5 |
6 | It is assumed that the original file which is executing this file has set
7 | os.environ['DJANGO_SETTINGS_MODULE'] prior to calling execfile().
8 | """
9 | import os
10 | from importlib import import_module
11 |
12 |
13 | os.environ["CELERY_LOADER"] = "django"
14 |
15 |
16 | def run_mgmt_validate():
17 | import django.core.management
18 | utility = django.core.management.ManagementUtility()
19 | command = utility.fetch_command('runserver')
20 | command.validate(display_num_errors=True)
21 |
22 |
23 | def load_templatetags():
24 | import_module('django.template.base').get_templatetags_modules()
25 |
26 |
27 | def load_admin():
28 | import_module('django.contrib.admin').autodiscover()
29 |
30 |
31 | def load_i18n(lang_code):
32 | import_module('django.utils.translation').activate(lang_code)
33 |
34 |
35 | def load_urls():
36 | import_module('django.core.urlresolvers').resolve('/')
37 |
38 |
39 | def setup():
40 | import django
41 |
42 | django.setup()
43 |
44 | from django.conf import settings
45 |
46 | load_templatetags()
47 | if 'django.contrib.admin' in settings.INSTALLED_APPS:
48 | load_admin()
49 | load_i18n(settings.LANGUAGE_CODE)
50 |
51 | load_urls()
52 |
53 | run_mgmt_validate()
54 |
55 |
56 | setup()
57 |
--------------------------------------------------------------------------------
/docker/start-docker.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # If running within docker-machine, the current directory will be a mounted
3 | # volume, owned by the user running Docker on the host machine.
4 | #
5 | # In that case, we don't want to trample all over the contents of this
6 | # directory with files owned by root. So we create a new user with the same
7 | # UID, and drop privileges before running any commands.
8 | #
9 | # If we are not running in docker-machine, we create a user with uid 1000
10 | # and execute commands as that user.
11 |
12 | # Get the numeric user ID of the current directory.
13 | uid=$(ls -nd . | awk '{ print $3 }')
14 | gid=$(ls -nd . | awk '{ print $4 }')
15 |
16 | # If the current working directory is owned by root, that means we're running
17 | # with plain-old docker, not docker-machine.
18 | if [ "$uid" == "0" ]; then
19 | uid=1000
20 | gid=1000
21 | fi
22 |
23 | group_name=$(getent group $gid)
24 |
25 | groupadd -g 1000 olympia
26 |
27 | # Create an `olympia` user with that ID, and the current directory
28 | # as its home directory.
29 | if [ -z "$group_name" ]; then
30 | useradd -Md $(pwd) -u $uid -g $gid -G olympia olympia
31 | else
32 | useradd -Md $(pwd) -u $uid -g $gid olympia
33 | fi
34 |
35 | if [ ! -d /deps ]; then
36 | mkdir /deps
37 | chown $uid:$gid /deps
38 | fi
39 |
40 | mkdir -p /var/run/supervisor
41 | chown $uid:$gid /var/run/supervisor
42 | chown $uid:$gid /var/run/uwsgi
43 |
44 | # Switch to that user and execute our actual command.
45 | exec su root -c 'exec "$@"' sh -- "$@"
46 |
--------------------------------------------------------------------------------
/docker/supervisor.conf:
--------------------------------------------------------------------------------
1 | [supervisord]
2 | logfile = /code/deploy/logs/supervisord.log
3 | pidfile = /var/run/supervisor/supervisord.pid
4 |
5 | [program:uwsgi_status]
6 | command = /usr/sbin/uwsgi /code/wsgi/uwsgi_status.ini
7 | autorestart = true
8 | redirect_stderr = true
9 | stdout_logfile = /code/deploy/logs/supervisord.uwsgi_status.log
10 | stopsignal = INT
11 |
12 | [program:stub]
13 | command = /usr/sbin/uwsgi /code/wsgi/stub.ini
14 | autorestart = true
15 | redirect_stderr = true
16 | stdout_logfile = /code/deploy/logs/supervisord.stub.log
17 | stopsignal = INT
18 |
19 | [program:olympia_vassals]
20 | command = /usr/sbin/uwsgi --master --thunder-lock --emperor "/code/deploy/builds/[a-z]*/vassal.ini"
21 | autorestart = true
22 | redirect_stderr = true
23 | stdout_logfile = /code/deploy/logs/supervisord.vassals.log
24 | stopsignal = INT
25 | priority = 996
26 |
27 | [program:olympia_zergs]
28 | command = /usr/sbin/uwsgi --master --thunder-lock --emperor "/code/deploy/builds/[a-z]*/zerg.ini"
29 | redirect_stderr = true
30 | stdout_logfile = /code/deploy/logs/supervisord.zergs.log
31 | stopsignal = INT
32 | startsecs = 5
33 | priority = 997
34 |
35 | [group:olympia]
36 | programs = olympia_zergs,olympia_vassals
37 | priority = 998
38 |
39 | # The following sections enable supervisorctl.
40 |
41 | [unix_http_server]
42 | file = /var/run/supervisor/supervisor.sock
43 |
44 | [rpcinterface:supervisor]
45 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
46 |
47 | [supervisorctl]
48 | serverurl = unix:///var/run/supervisor/supervisor.sock
49 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | web:
5 | # build: .
6 | image: hadjango/djangocon-2016-demo
7 | entrypoint: ./docker/start-docker.sh
8 | command: supervisord -n -c /code/docker/supervisor.conf
9 | privileged: true
10 | expose:
11 | - "1999"
12 | - "2000"
13 | - "2010"
14 | - "2020"
15 | - "2030"
16 | - "2040"
17 | - "2050"
18 | - "2060"
19 | - "2070"
20 | - "2080"
21 | - "2090"
22 | - "2100"
23 | - "2110"
24 | - "2120"
25 | - "2130"
26 | - "2140"
27 | - "2150"
28 | - "2160"
29 | - "2170"
30 | - "2180"
31 | - "2190"
32 | - "2200"
33 | - "2210"
34 | - "2220"
35 | - "2230"
36 | - "2240"
37 | - "2250"
38 | - "9999"
39 | volumes:
40 | - .:/code
41 | links:
42 | - memcached
43 | - mysqld
44 | - elasticsearch
45 | - redis
46 | environment:
47 | - PYTHONUNBUFFERED=1
48 | - RECURSION_LIMIT=10000
49 | - TERM=xterm-256color
50 |
51 | nginx:
52 | # build: ./nginx
53 | image: hadjango/addons-nginx
54 | entrypoint: /code/docker/start-nginx.sh
55 | command: nginx -g 'daemon off;'
56 | ports:
57 | - "80"
58 | ports:
59 | - "80:80"
60 | volumes:
61 | - .:/code
62 | links:
63 | - web:web
64 |
65 | memcached:
66 | image: memcached:1.4
67 |
68 | mysqld:
69 | image: mysql:5.6
70 | environment:
71 | - MYSQL_ALLOW_EMPTY_PASSWORD=yes
72 | - MYSQL_DATABASE=olympia
73 |
74 | elasticsearch:
75 | image: elasticsearch:1.6
76 |
77 | redis:
78 | image: redis:2.8
79 |
--------------------------------------------------------------------------------
/docker/RPM-GPG-KEY-EPEL-7:
--------------------------------------------------------------------------------
1 | -----BEGIN PGP PUBLIC KEY BLOCK-----
2 | Version: GnuPG v1.4.11 (GNU/Linux)
3 |
4 | mQINBFKuaIQBEAC1UphXwMqCAarPUH/ZsOFslabeTVO2pDk5YnO96f+rgZB7xArB
5 | OSeQk7B90iqSJ85/c72OAn4OXYvT63gfCeXpJs5M7emXkPsNQWWSju99lW+AqSNm
6 | jYWhmRlLRGl0OO7gIwj776dIXvcMNFlzSPj00N2xAqjMbjlnV2n2abAE5gq6VpqP
7 | vFXVyfrVa/ualogDVmf6h2t4Rdpifq8qTHsHFU3xpCz+T6/dGWKGQ42ZQfTaLnDM
8 | jToAsmY0AyevkIbX6iZVtzGvanYpPcWW4X0RDPcpqfFNZk643xI4lsZ+Y2Er9Yu5
9 | S/8x0ly+tmmIokaE0wwbdUu740YTZjCesroYWiRg5zuQ2xfKxJoV5E+Eh+tYwGDJ
10 | n6HfWhRgnudRRwvuJ45ztYVtKulKw8QQpd2STWrcQQDJaRWmnMooX/PATTjCBExB
11 | 9dkz38Druvk7IkHMtsIqlkAOQMdsX1d3Tov6BE2XDjIG0zFxLduJGbVwc/6rIc95
12 | T055j36Ez0HrjxdpTGOOHxRqMK5m9flFbaxxtDnS7w77WqzW7HjFrD0VeTx2vnjj
13 | GqchHEQpfDpFOzb8LTFhgYidyRNUflQY35WLOzLNV+pV3eQ3Jg11UFwelSNLqfQf
14 | uFRGc+zcwkNjHh5yPvm9odR1BIfqJ6sKGPGbtPNXo7ERMRypWyRz0zi0twARAQAB
15 | tChGZWRvcmEgRVBFTCAoNykgPGVwZWxAZmVkb3JhcHJvamVjdC5vcmc+iQI4BBMB
16 | AgAiBQJSrmiEAhsPBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRBqL66iNSxk
17 | 5cfGD/4spqpsTjtDM7qpytKLHKruZtvuWiqt5RfvT9ww9GUUFMZ4ZZGX4nUXg49q
18 | ixDLayWR8ddG/s5kyOi3C0uX/6inzaYyRg+Bh70brqKUK14F1BrrPi29eaKfG+Gu
19 | MFtXdBG2a7OtPmw3yuKmq9Epv6B0mP6E5KSdvSRSqJWtGcA6wRS/wDzXJENHp5re
20 | 9Ism3CYydpy0GLRA5wo4fPB5uLdUhLEUDvh2KK//fMjja3o0L+SNz8N0aDZyn5Ax
21 | CU9RB3EHcTecFgoy5umRj99BZrebR1NO+4gBrivIfdvD4fJNfNBHXwhSH9ACGCNv
22 | HnXVjHQF9iHWApKkRIeh8Fr2n5dtfJEF7SEX8GbX7FbsWo29kXMrVgNqHNyDnfAB
23 | VoPubgQdtJZJkVZAkaHrMu8AytwT62Q4eNqmJI1aWbZQNI5jWYqc6RKuCK6/F99q
24 | thFT9gJO17+yRuL6Uv2/vgzVR1RGdwVLKwlUjGPAjYflpCQwWMAASxiv9uPyYPHc
25 | ErSrbRG0wjIfAR3vus1OSOx3xZHZpXFfmQTsDP7zVROLzV98R3JwFAxJ4/xqeON4
26 | vCPFU6OsT3lWQ8w7il5ohY95wmujfr6lk89kEzJdOTzcn7DBbUru33CQMGKZ3Evt
27 | RjsC7FDbL017qxS+ZVA/HGkyfiu4cpgV8VUnbql5eAZ+1Ll6Dw==
28 | =hdPa
29 | -----END PGP PUBLIC KEY BLOCK-----
30 |
--------------------------------------------------------------------------------
/conf/uwsgi/zerg.skel:
--------------------------------------------------------------------------------
1 | [uwsgi]
2 | plugins = cheaper_busyness,logfile,python
3 | uid = olympia
4 | gid = olympia
5 | chmod-socket = 666
6 | chown-socket = olympia:olympia
7 |
8 | ; a, b, etc...
9 | ; (this is based on the name of the current symlink's directory)
10 | build = %c
11 |
12 | log-x-forwarded-for = true
13 | log-format = [%(worker_id)] %(addr) [%(ltime)] "%(method) %(uri) %(proto)" %(status) %(size) "%(referer)" "%(uagent)" %(micros)
14 |
15 | master = true
16 | memory-report = true
17 | thunder-lock = true
18 |
19 | ; Initialize adaptive process spawning (aka the cheaper subsystem)
20 | ; maximum number of workers that can be spawned
21 | workers = 14
22 | ; Use the 'busyness' algorithm
23 | cheaper-algo = busyness
24 | ; tries to keep 4 idle workers
25 | cheaper = 4
26 | ; starts with minimal workers
27 | cheaper-initial = 4
28 | ; spawn at most 1 worker at a time
29 | cheaper-step = 1
30 | ; how many seconds between busyness checks
31 | cheaper-overload = 20
32 | cheaper-busyness-multiplier = 5
33 | ; how many requests are in backlog before quick response triggered
34 | cheaper-busyness-backlog-alert = 33
35 | cheaper-busyness-backlog-step = 1
36 |
37 | auto-procname = true
38 | procname-master = uwsgi master <%(build)>
39 | wsgi-file = /code/deploy/builds/%(build)/hadjango/uwsgi/wsgi.py
40 | python-path = /code/deploy/builds/%(build)
41 | home = /code/deploy/builds/%(build)
42 | buffer-size = 32768
43 | env = PYTHON_EGG_CACHE=/tmp/.%(build)-python-eggs
44 | env = DJANGO_SETTINGS_MODULE=settings
45 | req-logger = file:/code/deploy/logs/access.%(build).log
46 | logger = file:/code/deploy/logs/error.%(build).log
47 | stats = /var/run/uwsgi/%(build).stats
48 |
49 | lazy-apps = true
50 | touch-workers-reload = /code/deploy/builds/%(build)/hadjango/uwsgi/wsgi.py
51 |
52 | ; attach to zerg pool
53 | zerg = /var/run/uwsgi/%(build).sock
54 |
--------------------------------------------------------------------------------
/docker/RPM-GPG-KEY-hadjango:
--------------------------------------------------------------------------------
1 | -----BEGIN PGP PUBLIC KEY BLOCK-----
2 | Version: GnuPG v2.0.22 (GNU/Linux)
3 |
4 | mQENBFeVjuwBCADYR++dX1+50rszQV5OkYB0MTW2bAWypR384yFS4Mt44xtQOEdg
5 | vDZUzs0ky5ABjFcAgal+ET15sNs/g6l++Qqh1/vJ6BzySxNQoQXx+lUqmrOMxKlJ
6 | 3JWdsJ7Xj2L+YCHsNygbRBfFyFJpGy5ZYkMjxAQOojJy1rVYjTbwP1uzMmK1nRAg
7 | uecxPhkiHIE2z70+XNVd2S7pEppH1lhKx1tJMOr0tSP4ByPjw/49RUU19QCVTxOE
8 | CxioODX07O8EM+X+Z5pPHuxHUJv4KaWRmU+ey7XfJBvx7Bqhnx9cY34z+OubeDTJ
9 | hphUHidSxFtGXjvmuPFoftlFSFV7WEsTmLmRABEBAAG0JEZyYW5raWUgRGludGlu
10 | byA8ZmRpbnRpbm9AZ21haWwuY29tPokBOQQTAQIAIwUCV5WO7AIbAwcLCQgHAwIB
11 | BhUIAgkKCwQWAgMBAh4BAheAAAoJECT19zSLizZo6fMH/08laHE6YnijwGYScPmE
12 | HxTMNBN9XT46lvTKrqxzEM94OEGQe8RCa5eDg+JHzqPFdTZG8irUH8mZrfeRqyqj
13 | e/1JnoJkDaJqrKPXjcHYFq7388wzCkfzGIPlV40AcrqmGhsw0+cuXhLCLXsH6Zlp
14 | dOPfWoUQusME6wBwEIbABndeb47pRJV26F4U8cTUXYKlqV1dtjDcJWAkzpwJhVGa
15 | /nquN3OaoXDFt4b0IuBjQUxdw/uANVlmcJdqUxdpvq/n0Eka3+kDk9JF8ZKDZUCh
16 | J/LXJiNU4NsPp5xcqx5ySDMzvQFcpKhUbl2V+saTKdRCojaO2WrwIN0DfLEkI/lc
17 | BUe5AQ0EV5WO7AEIAOz58fp7w7436i9emah0ALr+FmpxlTgwEQg3VeOcUbT+hjvK
18 | yYom+5oPiPzK9R/tS+XUPXu3glYM6el+owR6rcCRQr/RPs+GeFmHxuikKPQqSNhX
19 | Owqw1PZKgaa0bb5DYW8EretmFOzr0tA2W3O6KCf2v9U/tZoruyE+eqPOWFya2bdl
20 | hQuu8ZuuivSSh6kbmWnpn9yv6JWYeDWU28SiOErcmrZdT16cQSXqHALqV38T7oRy
21 | jSjoVs2R+4CfgAE67WlUWDhgEscjjtq1exdXnw4pvNRBiESb6pCzDFk4Dp7yPdrL
22 | AUpZk4xKTrojTKmWSoddzTTzFVC9M++vRnbi6RsAEQEAAYkBHwQYAQIACQUCV5WO
23 | 7AIbDAAKCRAk9fc0i4s2aMG5CACFUgyqIe/PbmMNrtxesSpKf/j6D7G26hmCYPs9
24 | PEfF02/FOBzN4vdO8uXlEuhKwRQKS1qjDukmw9YBveu5+gKcZsKF1vPRfgtCymW+
25 | o4c8XYBBh7ePVeh1Jc3uIBvtn65MFMnx7ORN0o4UjFDlknvkw8GCebldGe5pcP5C
26 | OAxXnb+glleuYd1dc1d5RZidhIyLxGu1Zecpr4xfu9cBTpEMdNWjJ60uZywhoQmk
27 | TC7omOQ281wdHTsEHj8RRNBgmMbTepvg3NCc01qovQqu1qjE1qgfF/pEwJs6UphH
28 | s0u0cwa71u2c2wTe+weiCBacEZEhfLYDNoqHizfwuJ08fi9v
29 | =Oug9
30 | -----END PGP PUBLIC KEY BLOCK-----
31 |
--------------------------------------------------------------------------------
/docker/start-nginx.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # If running within docker-machine, the current directory will be a mounted
3 | # volume, owned by the user running Docker on the host machine.
4 | #
5 | # In that case, we don't want to trample all over the contents of this
6 | # directory with files owned by root. So we create a new user with the same
7 | # UID, and drop privileges before running any commands.
8 | #
9 | # If we are not running in docker-machine, we create a user with uid 1000
10 | # and execute commands as that user.
11 |
12 | # Get the numeric user ID of the current directory.
13 | uid=$(ls -nd /code | awk '{ print $3 }')
14 | gid=$(ls -nd /code | awk '{ print $4 }')
15 |
16 | # If the current working directory is owned by root, that means we're running
17 | # with plain-old docker, not docker-machine.
18 | if [ "$uid" == "0" ]; then
19 | uid=1000
20 | gid=1000
21 | fi
22 |
23 | group_name=$(getent group $gid)
24 |
25 | groupadd -g 1000 olympia 2>&1 2>/dev/null
26 |
27 | # Create an `olympia` user with that ID, and the current directory
28 | # as its home directory.
29 | if [ -z "$group_name" ]; then
30 | useradd -Md $(pwd) -u $uid -g $gid -G olympia olympia 2>&1 2>/dev/null
31 | else
32 | useradd -Md $(pwd) -u $uid -g $gid olympia 2>&1 2>/dev/null
33 | fi
34 |
35 | if [ ! -L /code/deploy/builds/_live ]; then
36 | mkdir -p /code/deploy/tmp/live
37 | mkdir -p /code/deploy/tmp/stage
38 | echo "upstream web { server web:1999; }" > /code/deploy/tmp/live/live.conf;
39 | echo "upstream webstage { server web:1999; }" > /code/deploy/tmp/stage/stage.conf;
40 |
41 | ln -sfvn /code/deploy/tmp/live /code/deploy/builds/_live
42 | ln -sfvn /code/deploy/tmp/stage /code/deploy/builds/_stage
43 | fi
44 |
45 | chown -R $uid:$gid /code/deploy/tmp
46 | chown $uid:$gid /code/deploy/builds/*
47 |
48 | exec su root -c 'exec "$@"' sh -- "$@"
49 |
--------------------------------------------------------------------------------
/src/local_settings.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from olympia.lib.settings_base import INSTALLED_APPS
4 |
5 |
6 | INSTALLED_APPS = ('hadjango', ) + INSTALLED_APPS + ('olympia.landfill', )
7 |
8 | BROKER_URL = 'amqp://olympia:olympia@rabbitmq/olympia'
9 | CELERY_RESULT_BACKEND = 'redis://redis:6379/1'
10 | REDIS_LOCATION = 'redis://redis:6379/0?socket_timeout=0.5'
11 | ES_HOSTS = ['elasticsearch:9200']
12 | ES_URLS = ['http://%s' % h for h in ES_HOSTS]
13 | SITE_DIR = 'http://olympia.dev'
14 |
15 | CACHES = {
16 | 'default': {
17 | 'BACKEND': 'caching.backends.memcached.MemcachedCache',
18 | 'LOCATION': 'memcached:11211',
19 | }
20 | }
21 |
22 |
23 | ROOT = os.path.realpath(os.path.dirname(__file__))
24 |
25 | BUILD_NAME = os.path.basename(ROOT)
26 | STATIC_ROOT = os.path.join(ROOT, "assets", "static")
27 | MEDIA_ROOT = os.path.join(ROOT, "assets", "media")
28 | STATIC_URL = "/static/%s/" % BUILD_NAME
29 |
30 | CELERY_ALWAYS_EAGER = True
31 |
32 | DATABASES = {
33 | 'default': {
34 | 'ENGINE': 'django.db.backends.mysql',
35 | 'NAME': 'olympia',
36 | 'USER': 'root',
37 | 'PASSWORD': '',
38 | 'HOST': 'mysqld',
39 | 'PORT': '',
40 | 'OPTIONS': {'sql_mode': 'STRICT_ALL_TABLES'},
41 | 'TEST_CHARSET': 'utf8',
42 | 'TEST_COLLATION': 'utf8_general_ci',
43 | # Run all views in a transaction unless they are decorated not to.
44 | 'ATOMIC_REQUESTS': True,
45 | # Pool our database connections up for 300 seconds
46 | 'CONN_MAX_AGE': 300,
47 | },
48 | }
49 |
50 | # A database to be used by the services scripts, which does not use Django.
51 | # The settings can be copied from DATABASES, but since its not a full Django
52 | # database connection, only some values are supported.
53 | SERVICES_DATABASE = {
54 | 'NAME': DATABASES['default']['NAME'],
55 | 'USER': DATABASES['default']['USER'],
56 | 'PASSWORD': DATABASES['default']['PASSWORD'],
57 | 'HOST': DATABASES['default']['HOST'],
58 | 'PORT': DATABASES['default']['PORT'],
59 | }
60 |
--------------------------------------------------------------------------------
/src/hadjango/uwsgi/wsgi.py:
--------------------------------------------------------------------------------
1 | import re
2 | import os
3 | import sys
4 | import site
5 | import uwsgi
6 |
7 |
8 | project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
9 |
10 | sys.path.insert(0, project_root)
11 | site.addsitedir(os.path.join(
12 | project_root,
13 | "lib",
14 | "python" + sys.version[0:3],
15 | "site-packages"))
16 |
17 |
18 | def get_site_name():
19 | settings_module = os.environ.get('DJANGO_SETTINGS_MODULE') or ''
20 | matches = re.match(r'settings\.(\w+)\.live', settings_module)
21 | if matches:
22 | site_name = matches.group(1)
23 | else:
24 | site_name = 'unknown'
25 | return site_name
26 |
27 |
28 | site_name = get_site_name()
29 |
30 |
31 | def set_uwsgi_proc_name():
32 | build_dir = os.path.basename(project_root)
33 | try:
34 | deploy_tag = open(os.path.join(project_root, '.DEPLOY_TAG')).read().strip()
35 | except IOError:
36 | deploy_tag = '?'
37 |
38 | os.environ['DEPLOY_TAG'] = deploy_tag
39 |
40 | uwsgi.setprocname("uwsgi worker %(worker_id)d <%(build)s> [%(tag)s]" % {
41 | 'worker_id': uwsgi.worker_id(),
42 | 'build': build_dir,
43 | 'tag': deploy_tag,
44 | })
45 |
46 |
47 | set_uwsgi_proc_name()
48 |
49 | execfile(os.path.join(os.path.dirname(__file__), 'bootstrap.py'))
50 |
51 | _application = None
52 |
53 |
54 | def application(environ, start_response):
55 | global _application
56 |
57 | uwsgi.set_logvar('worker_id', str(uwsgi.worker_id()))
58 |
59 | if not os.environ.get('DJANGO_SETTINGS_MODULE'):
60 | os.environ['DJANGO_SETTINGS_MODULE'] = environ.get('DJANGO_SETTINGS_MODULE', 'settings')
61 |
62 | if _application is None:
63 | try:
64 | from django.core.wsgi import get_wsgi_application
65 | except ImportError:
66 | import django.core.handlers.wsgi
67 | _application = django.core.handlers.wsgi.WSGIHandler()
68 | else:
69 | _application = get_wsgi_application()
70 |
71 | return _application(environ, start_response)
72 |
--------------------------------------------------------------------------------
/src/hadjango/management/commands/warmup.py:
--------------------------------------------------------------------------------
1 | import random
2 |
3 | from django.conf import settings
4 | from django.core.management.base import BaseCommand
5 |
6 | import requests
7 | from requests_futures.sessions import FuturesSession
8 |
9 | from hadjango.build_port_convert import build_to_port
10 |
11 |
12 | class Command(BaseCommand):
13 | """
14 | Command that primes processes/cache for a given build
15 | """
16 |
17 | help = 'Warms up processes/cache for a given host/port'
18 |
19 | def add_arguments(self, parser):
20 | parser.add_argument('--build', '-b', dest='build',
21 | default=settings.BUILD_NAME, help='Build to warm up: (a|b|c|...)')
22 | parser.add_argument('--concurrents', '-c', dest='concurrents',
23 | default='6', help='Number of concurrent requests to make')
24 |
25 | def handle(self, **options):
26 | server = "web"
27 | build = options['build']
28 | port = build_to_port(build)
29 | max_workers = int(options['concurrents'])
30 |
31 | session = FuturesSession(max_workers=max_workers)
32 |
33 | futures = []
34 | random_version = random.randint(0, 65535)
35 | for i in range(0, max_workers):
36 | version = '' if i == 0 else '?v=%d%d' % (i, random_version)
37 | futures.append(session.get('http://%(server)s:%(port)d/en-US/firefox/%(version)s' % {
38 | 'server': server,
39 | 'port': port,
40 | 'version': version,
41 | }))
42 |
43 | for i, future in enumerate(futures):
44 | # wait for the response to complete
45 | try:
46 | response = future.result()
47 | except requests.ConnectionError:
48 | status = 'XXX'
49 | time = 0
50 | else:
51 | status = response.status_code
52 | time = int(round(response.elapsed.total_seconds() * 1000.0))
53 | self.stdout.write("%(server)s: (%(status)s) %(time)6d ms: worker %(i)s\n" % {
54 | 'server': server,
55 | 'status': status,
56 | 'time': time,
57 | 'i': i + 1,
58 | })
59 |
60 | self.stdout.write("Successfully warmed up.")
61 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM centos:centos7
2 |
3 | # Allow scripts to detect we're running in our own container
4 | RUN touch /addons-server-centos7-container
5 |
6 | # Set the locale. This is mainly so that tests can write non-ascii files to
7 | # disk.
8 | ENV LANG en_US.UTF-8
9 | ENV LC_ALL en_US.UTF-8
10 |
11 | ADD docker/RPM-GPG-KEY-mysql /etc/pki/rpm-gpg/RPM-GPG-KEY-mysql
12 | ADD docker/RPM-GPG-KEY-hadjango /etc/pki/rpm-gpg/RPM-GPG-KEY-hadjango
13 | ADD docker/RPM-GPG-KEY-EPEL-7 /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7
14 |
15 | ADD docker/epel.repo /etc/yum.repos.d/epel.repo
16 | ADD docker/epel-testing.repo /etc/yum.repos.d/epel-testing.repo
17 | ADD docker/hadjango.repo /etc/yum.repos.d/hadjango.repo
18 |
19 | # For mysql-python dependencies
20 | ADD docker/mysql.repo /etc/yum.repos.d/mysql.repo
21 |
22 | RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-mysql \
23 | && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-hadjango \
24 | && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7 \
25 | && yum update -y \
26 | && yum install -y \
27 | # Supervisor is being used to start and keep our services running
28 | supervisor \
29 | # General (dev-) dependencies
30 | bash-completion \
31 | gcc-c++ \
32 | curl \
33 | make \
34 | libjpeg-devel \
35 | cyrus-sasl-devel \
36 | libxml2-devel \
37 | libxslt-devel \
38 | zlib-devel \
39 | libffi-devel \
40 | openssl-devel \
41 | python-devel \
42 | # Git, because we're using git-checkout dependencies
43 | git \
44 | # Nodejs for less, stylus, uglifyjs and others
45 | nodejs \
46 | # Dependencies for mysql-python
47 | mysql-community-devel \
48 | mysql-community-client \
49 | mysql-community-libs \
50 | epel-release \
51 | swig \
52 | python-uwsgidecorators \
53 | uwsgi-devel \
54 | uwsgi-logger-file \
55 | uwsgi-plugin-python \
56 | uwsgi-plugin-zergpool \
57 | uwsgi-router-http \
58 | uwsgi-router-raw \
59 | uwsgi-router-uwsgi \
60 | uwsgi-stats-pusher-file \
61 | uwsgi-stats-pusher-socket \
62 | uwsgi-plugin-cheaper-busyness \
63 | python-pip \
64 | python-setuptools \
65 | python-virtualenv \
66 | && yum clean all
67 |
68 | RUN pip install wheel pyOpenSSL ndg-httpsclient pyasn1 certifi urllib3 psutil supervisor fabric \
69 | && rm -rf /root/.cache
70 |
71 | COPY . /code
72 | WORKDIR /code
73 |
74 | ENV SWIG_FEATURES="-D__x86_64__"
75 |
76 | # Preserve bash history across image updates.
77 | # This works best when you link your local source code
78 | # as a volume.
79 | ENV HISTFILE /code/docker/artifacts/bash_history
80 |
81 | # Configure bash history.
82 | ENV HISTSIZE 50000
83 | ENV HISTIGNORE ls:exit:"cd .."
84 |
85 | # This prevents dupes but only in memory for the current session.
86 | ENV HISTCONTROL erasedups
87 |
--------------------------------------------------------------------------------
/fabfile/build_port_convert.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Convert a build name (e.g. 'a', 'b', etc.) to a port number, or vice-versa"""
3 | from __future__ import print_function
4 |
5 | import os
6 | import sys
7 |
8 |
9 | START_PORT = 2000
10 | PORT_INCREMENT = 10
11 | MAX_PORT = 65535
12 | MAX_PORT_NUMS = (MAX_PORT - START_PORT) // PORT_INCREMENT
13 |
14 | NUM_TO_CHAR = dict([((i), chr(i + ord('a'))) for i in range(0, 26)])
15 | CHAR_TO_NUM = dict(map(reversed, NUM_TO_CHAR.items()))
16 |
17 |
18 | def base26_decode(string):
19 | num = 0
20 | i = 0
21 | while i < len(string):
22 | char = string[i]
23 | offset = 0
24 | if i == 0:
25 | offset = 1
26 | power = len(string) - i - 1
27 | num += (26 ** power) * (CHAR_TO_NUM[char] + offset)
28 | if offset:
29 | while power > 1:
30 | power -= 1
31 | num += 26 ** power
32 | i += 1
33 | return num - 1
34 |
35 |
36 | def base26_encode(num):
37 | string = ''
38 | num += 1
39 | while num:
40 | num, val = divmod((num - 1), 26)
41 | string = "%s%s" % (NUM_TO_CHAR[val], string)
42 | return string
43 |
44 |
45 | def port_to_build(port):
46 | if port < START_PORT:
47 | usage("Port number must be greater than or equal to %d" % START_PORT)
48 | if port > MAX_PORT:
49 | usage("Port number must be less than %d" % MAX_PORT)
50 | num, index = divmod((port - START_PORT), PORT_INCREMENT)
51 | return (base26_encode(num), index)
52 |
53 |
54 | def build_to_port(name):
55 | if name[-1].isdigit():
56 | index = int(name[-1])
57 | name = name[:-1]
58 | else:
59 | index = 0
60 |
61 | num = base26_decode(name)
62 | port = START_PORT + (num * PORT_INCREMENT) + index
63 | return port
64 |
65 |
66 | USAGE = """%(error)s
67 | Usage: %(script_name)s [port|build_name [index]]
68 |
69 | build_name A string composed of lowercase letters, as part of the sequence
70 | a, b, c, ..., z, aa, ab, ac, ..., zz, aaa, ..., %(max_build_name)s
71 | index Optional (defaults to 0), an integer between 0 and %(max_index)s
72 | which is added to the port number for the build_name
73 | port An integer, greater than %(start)s, which is converted into a
74 | build_name and index.
75 | """.strip()
76 |
77 |
78 | def usage(error=""):
79 | if error:
80 | error = "ERROR: %s\n" % error
81 | script_name = os.path.basename(sys.argv[0])
82 | print(USAGE % {
83 | "script_name": script_name,
84 | "max_build_name": base26_encode(MAX_PORT_NUMS),
85 | "max_index": PORT_INCREMENT,
86 | "start": START_PORT,
87 | "error": error,
88 | })
89 | sys.exit(1)
90 |
91 |
92 | if __name__ == '__main__':
93 | if len(sys.argv) != 2:
94 | usage()
95 | arg = sys.argv[-1]
96 | if arg.isdigit():
97 | print("%s %d" % port_to_build(int(arg)))
98 | elif len(arg) > 3:
99 | usage()
100 | else:
101 | print(build_to_port(arg))
102 |
--------------------------------------------------------------------------------
/wsgi/uwsgi_status.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import sys
4 | import re
5 | import errno
6 | import glob
7 | import socket
8 | import json
9 | import logging
10 |
11 | import psutil
12 |
13 |
14 | logging.basicConfig()
15 | logger = logging.getLogger()
16 |
17 | re_procname = re.compile(r"(?i)"
18 | r"uwsgi (?P