← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~ilasc/content-cache-charm:enable-follow-redirects into content-cache-charm:master

 

Ioana Lasc has proposed merging ~ilasc/content-cache-charm:enable-follow-redirects into content-cache-charm:master.

Commit message:
Add ability to follow redirections

Requested reviews:
  Canonical IS Reviewers (canonical-is-reviewers)
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~ilasc/content-cache-charm/+git/content-cache-charm/+merge/433376
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~ilasc/content-cache-charm:enable-follow-redirects into content-cache-charm:master.
diff --git a/.gitignore b/.gitignore
index 3b821e7..a885ad6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@ deps/
 htmlcov/
 repo-info
 revision
+.idea/
diff --git a/config.yaml b/config.yaml
index cc17d36..0fecb0b 100644
--- a/config.yaml
+++ b/config.yaml
@@ -109,6 +109,16 @@ options:
       Use the SO_REUSEPORT socket option, allowing a better load balance on the nginx workers.
       Can result in performance drop due to increased latency on heavy workload.
       See https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/.
+  upstream_name:
+    default: ""
+    type: string
+    description: >
+      A name to use for the upstream server.
+  upstream_address:
+    default: ""
+    type: string
+    description: >
+      IP and port for the upstream server, e.g. 10.26.60.1:5000.
   sites:
     default: ""
     type: string
diff --git a/lib/nginx.py b/lib/nginx.py
index 261597c..6007050 100644
--- a/lib/nginx.py
+++ b/lib/nginx.py
@@ -104,6 +104,8 @@ class NginxConf:
         for location, loc_conf in locations.items():
             conf[location] = deepcopy(loc_conf)
             lc = conf[location]
+            if lc.get('proxy_intercept_errors'):
+                lc['proxy_intercept_errors'] = 'on'
             backend_port = lc.get('backend_port')
             if backend_port:
                 backend_path = lc.get('backend-path')
@@ -177,6 +179,8 @@ class NginxConf:
             'reuseport': conf['reuseport'],
             'site': conf['site'],
             'site_name': conf['site_name'],
+            'upstream_name': conf['upstream_name'],
+            'upstream_address': conf['upstream_address'],
         }
         template = self.jinja_env.get_template('templates/nginx_cfg.tmpl')
         return template.render(data)
diff --git a/reactive/content_cache.py b/reactive/content_cache.py
index 9e7e0ad..2ed420b 100644
--- a/reactive/content_cache.py
+++ b/reactive/content_cache.py
@@ -186,6 +186,9 @@ def configure_nginx(conf_path=None):
     # access.
     conf['listen_address'] = '127.0.0.1'
     conf['reuseport'] = config['reuseport']
+    conf['upstream_name'] = config['upstream_name']
+    conf['upstream_address'] = config['upstream_address']
+
     changed = False
     for site, site_conf in sites.items():
         conf['site'] = site
diff --git a/templates/nginx_cfg.tmpl b/templates/nginx_cfg.tmpl
index 0cda5be..a8bb066 100644
--- a/templates/nginx_cfg.tmpl
+++ b/templates/nginx_cfg.tmpl
@@ -1,5 +1,11 @@
 proxy_cache_path {{cache_path}}/{{site}} use_temp_path=off levels=1:2 keys_zone={{keys_zone}}:10m {% if cache_inactive_time %}inactive={{cache_inactive_time}} {% endif %}max_size={{cache_max_size}};
 
+{%- if upstream_name %}
+upstream {{upstream_name}} {
+    server {{upstream_address}};
+}
+{% endif %}
+
 server {
     server_name {{site_name}};
     listen {% if address %}{{address}}:{% endif %}{{port}}{% if reuseport %} reuseport{% endif %};
@@ -20,7 +26,8 @@ server {
     location {% if conf['modifier'] %}{{conf['modifier']}} {% endif %}{{ location }} {
 {%- if conf['backend'] %}
         proxy_http_version 1.1;
-        proxy_pass {{conf['backend']}};
+        {%- if not upstream_name %}
+        proxy_pass {{conf['backend']}};{% endif %}
         proxy_set_header Host "{{site_name}}";
         # Removed the following headers to avoid cache poisoning.
         proxy_set_header Forwarded "";
@@ -43,6 +50,17 @@ server {
 {%- endif %}
 {%- endfor %}
         proxy_force_ranges {{conf['force_ranges']}};
+{%- else %}
+{%- if upstream_name %}
+{%- if location == "@handle_redirects" %}
+        set $saved_redirect_location '$upstream_http_location';
+        proxy_pass {{conf['proxy_pass']}};
+{%- else %}
+        proxy_pass {{conf['proxy_pass']}};
+        proxy_intercept_errors {{conf['proxy_intercept_errors']}};
+        error_page 301 302 303 307 = @handle_redirects;
+{%- endif %}
+{%- endif %}
 {%- endif %}
 
 {%- if conf['origin-headers'] %}
diff --git a/tests/unit/files/config_nginx_redirect.txt b/tests/unit/files/config_nginx_redirect.txt
new file mode 100644
index 0000000..5c27eb0
--- /dev/null
+++ b/tests/unit/files/config_nginx_redirect.txt
@@ -0,0 +1,16 @@
+upstream lp_archive:
+    server:
+        - 10.26.60.1:5000
+
+site1.local:
+    port: 8085
+
+    locations:
+        '/':
+            proxy_pass: http://lp_archive/file
+            proxy_intercept_errors: 'on'
+            error_page:
+                - 301 302 303 307 = @handle_redirects
+        '@handle_redirects':
+            set $saved_redirect_location: '$upstream_http_location'
+            proxy_pass: $saved_redirect_location
diff --git a/tests/unit/files/config_nginx_redirect_rendered.txt b/tests/unit/files/config_nginx_redirect_rendered.txt
new file mode 100644
index 0000000..dce9879
--- /dev/null
+++ b/tests/unit/files/config_nginx_redirect_rendered.txt
@@ -0,0 +1,29 @@
+proxy_cache_path /var/lib/nginx/proxy/site1.local use_temp_path=off levels=1:2 keys_zone=a5586980e57a-cache:10m inactive=2h max_size=1g;
+upstream lp_archive {
+    server 10.26.60.1:5000;
+}
+
+
+server {
+    server_name site1.local;
+    listen 6081;
+
+    port_in_redirect off;
+    absolute_redirect off;
+
+    location / {
+        proxy_pass http://lp_archive/file;
+        proxy_intercept_errors on;
+        error_page 301 302 303 307 = @handle_redirects;
+    }
+
+
+    location @handle_redirects {
+        set $saved_redirect_location '$upstream_http_location';
+        proxy_pass $saved_redirect_location;
+    }
+
+
+    access_log /var/log/nginx/site1.local-access.log content_cache;
+    error_log /var/log/nginx/site1.local-error.log;
+}
diff --git a/tests/unit/test_content_cache.py b/tests/unit/test_content_cache.py
index 5f03e07..9ee7d2c 100644
--- a/tests/unit/test_content_cache.py
+++ b/tests/unit/test_content_cache.py
@@ -223,6 +223,8 @@ class TestCharm(unittest.TestCase):
             'enable_cache_lock': True,
             'enable_prometheus_metrics': False,
             'reuseport': False,
+            'upstream_name': '',
+            'upstream_address': '',
             'sites': ngx_config,
             'worker_connections': 768,
             'worker_processes': 0,
@@ -302,6 +304,8 @@ site1.local:
             'cache_max_size': '1g',
             'cache_path': '/var/lib/nginx/proxy',
             'reuseport': False,
+            'upstream_name': '',
+            'upstream_address': '',
             'sites': config,
             'sites_secrets': secrets,
             'worker_connections': 768,
@@ -352,6 +356,8 @@ site1.local:
                 'cache_max_size': '1g',
                 'cache_path': '/var/lib/nginx/proxy',
                 'reuseport': False,
+                'upstream_name': '',
+                'upstream_address': '',
                 'sites': config,
                 'worker_connections': 768,
                 'worker_processes': 0,
@@ -370,6 +376,8 @@ site1.local:
                 'cache_max_size': '20g',
                 'cache_path': '/srv/cache',
                 'reuseport': False,
+                'upstream_name': '',
+                'upstream_address': '',
                 'sites': config,
                 'worker_connections': 768,
                 'worker_processes': 0,
@@ -389,6 +397,8 @@ site1.local:
                 'cache_max_size': '',
                 'cache_path': '/srv/cache',
                 'reuseport': False,
+                'upstream_name': '',
+                'upstream_address': '',
                 'sites': config,
                 'worker_connections': 768,
                 'worker_processes': 0,
@@ -1422,6 +1432,8 @@ site1.local:
             'enable_prometheus_metrics': True,
             'metrics_listen_address': '127.0.0.2',
             'reuseport': False,
+            'upstream_name': '',
+            'upstream_address': '',
             'sites': ngx_config,
             'worker_connections': 768,
             'worker_processes': 0,
@@ -1540,6 +1552,8 @@ site1.local:
             'enable_prometheus_metrics': True,
             'metrics_listen_address': '127.0.0.2',
             'reuseport': True,
+            'upstream_name': '',
+            'upstream_address': '',
             'sites': ngx_config,
             'worker_connections': 768,
             'worker_processes': 0,
diff --git a/tests/unit/test_nginx.py b/tests/unit/test_nginx.py
index f51aaf6..0a39929 100644
--- a/tests/unit/test_nginx.py
+++ b/tests/unit/test_nginx.py
@@ -52,6 +52,9 @@ class TestLibNginx(unittest.TestCase):
         conf['enable_prometheus_metrics'] = False
         conf['listen_address'] = '127.0.0.1'
         conf['reuseport'] = False
+        conf['upstream_name'] = ''
+        conf['upstream_address'] = ''
+
         # From the given YAML-formatted list of sites, check that each individual
         # Nginx config rendered matches what's in tests/unit/files.
         port = BASE_LISTEN_PORT - 1
@@ -79,6 +82,50 @@ class TestLibNginx(unittest.TestCase):
 
             self.assertEqual(ngx_conf.render(conf), output)
 
+    def test_nginx_config_redirect(self):
+        """Test parsing a YAML-formatted site for following redirect."""
+        ngx_conf = nginx.NginxConf(unit='mock-content-cache/0')
+
+        with open('tests/unit/files/config_nginx_redirect.txt', 'r', encoding='utf-8') as f:
+            sites = yaml.safe_load(f.read())
+            # 'configs' is special and used to host YAML anchors so let's remove it
+            sites.pop('configs', '')
+
+        conf = {}
+        conf['cache_path'] = '/var/lib/nginx/proxy'
+        conf['enable_prometheus_metrics'] = False
+        conf['listen_address'] = '127.0.0.1'
+        conf['reuseport'] = False
+        conf['upstream_name'] = 'lp_archive'
+        conf['upstream_address'] = '10.26.60.1:5000'
+
+        # From the given YAML-formatted list of sites, check that each individual
+        # Nginx config rendered matches what's in tests/unit/files.
+        port = BASE_LISTEN_PORT - 1
+        backend_port = BASE_BACKEND_PORT - 1
+        for site, site_conf in sites.items():
+            port += 1
+            conf['site'] = site
+            conf['site_name'] = site_conf.get('site-name') or site
+            conf['listen_port'] = port
+            conf['listen_address'] = site_conf.get('listen_address')
+            conf['upstream'] = site_conf.get('server', {})
+            conf['cache_inactive_time'] = site_conf.get('cache-inactive-time', '2h')
+            conf['cache_max_size'] = site_conf.get('cache-max-size', '1g')
+            conf['extra_configs'] = site_conf.get('extra-configs', [])
+            conf['locations'] = site_conf.get('locations', {})
+
+            for location, loc_conf in conf['locations'].items():
+                if loc_conf.get('backends'):
+                    backend_port += 1
+                    loc_conf['backend_port'] = backend_port
+
+        output_file = 'tests/unit/files/config_nginx_redirect_rendered.txt'
+        with open(output_file, 'r', encoding='utf-8') as f:
+            output = f.read()
+
+        self.assertEqual(ngx_conf.render(conf), output)
+
     def test_nginx_config_write_sites(self):
         """Test writing out sites to individual Nginx site config files."""
         ngx_conf = nginx.NginxConf(self.tmpdir)
@@ -143,6 +190,8 @@ class TestLibNginx(unittest.TestCase):
             'enable_prometheus_metrics': True,
             'listen_address': '127.0.0.1',
             'reuseport': False,
+            'upstream_name': '',
+            'upstream_address': '',
         }
         for site, site_conf in sites.items():
             conf['site'] = site

References