Add a Remote DAV Cache to staging.

-- Use nginx as the server to store and retrieve files.
-- A python script enforces a maximum cache size using
   the inotify subsystem. Whenever a file is added to
   the cache it computes the total cache size and if it
   exceeds 400GiB, it deletes ~80GiB of the least recently
   used files.

Change-Id: Ib01c48fa957fa3e4d69b7c2442579c5614584fe6
diff --git a/gce/setup-remote-cache.sh b/gce/setup-remote-cache.sh
new file mode 100755
index 0000000..39c15ba
--- /dev/null
+++ b/gce/setup-remote-cache.sh
@@ -0,0 +1,125 @@
+#!/usr/bin/env bash
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+# Setup scripts for the remote cache
+
+cat > /root/nginx.conf <<'EOF'
+
+user ci;
+worker_processes auto;
+pid /run/nginx.pid;
+
+events {
+  worker_connections 10000;
+  multi_accept on;
+}
+
+http {
+  sendfile on;
+  tcp_nopush on;
+  tcp_nodelay on;
+  types_hash_max_size 2048;
+  default_type application/octet-stream;
+
+  access_log /var/log/nginx/access.log;
+  error_log /var/log/nginx/error.log;
+
+  server {
+    listen 8080;
+    client_max_body_size 0;
+
+    location / {
+      root /var/cas/cache;
+      client_body_temp_path /var/cas/temp;
+      dav_methods PUT;
+      create_full_put_path off;
+      dav_access user:rw group:rw all:r;
+    }
+  }
+}
+
+EOF
+
+cat > /root/purge-cache.py <<'EOF'
+
+from inotify_simple import INotify, flags
+from os import close, listdir, unlink, stat
+from re import findall
+from subprocess import check_output
+from sys import argv
+from time import time
+
+def folder_size(path):
+  stdout = check_output(["du", "-bs", path])
+  res = findall("^[0-9]+", stdout)
+  return int(res[0])
+
+def purge_files(dir_path, trigger_size, purge_target=0.7):
+  """Watch dir_path for files to be added. If the folder size
+  exceeds trigger_size, delete files until its size is lte
+  trigger_size * purge_target.
+
+  Arguments:
+  dir_path -- path to directory to monitor.
+  trigger_size -- directory size in bytes that when exceeded
+                  triggers purging.
+  purge_target -- to what fraction of size the directory
+                  contents should be reduced (default 0.7)
+  """
+  inotify = INotify()
+  watch_flags = flags.CREATE | flags.MOVED_TO
+  wd = inotify.add_watch(dir_path, watch_flags)
+  try:
+    while True:
+      actual_size_bytes = folder_size(dir_path)
+      if actual_size_bytes > trigger_size:
+        target_size_bytes = trigger_size * purge_target;
+
+        files = [dir_path + "/" + name for name in listdir(dir_path)]
+        files_stat = [(f, stat(f)) for f in files]
+
+        deleted_size_bytes = actual_size_bytes
+        deleted_files_count = 0
+        start = time()
+        for filepath, st in sorted(files_stat, lambda _,t: int(t[1].st_atime)):
+         try:
+            unlink(filepath)
+            deleted_files_count += 1
+            deleted_size_bytes -= st.st_size
+            if deleted_size_bytes < target_size_bytes:
+              break
+         except OSError:
+            print "Failed to delete file {}".format(filepath)
+        end = time()
+
+        print "Deleted {} files, totalling {} bytes in {} seconds".format(deleted_files_count,
+                                                                          actual_size_bytes - deleted_size_bytes,
+                                                                          end - start)
+
+      # Block until one or more files were created/moved to the folder. We are not
+      # interested in the particular file(s).
+      inotify.read()
+  except KeyboardInterrupt:
+    inotify.close()
+
+if __name__ == "__main__":
+  purge_files(argv[1], int(argv[2]))
+
+EOF
+
+apt-get -y update
+apt-get -y install nginx python-pip
+pip install inotify_simple enum34
+
diff --git a/gce/start-remote-cache.sh b/gce/start-remote-cache.sh
new file mode 100755
index 0000000..c169fc7
--- /dev/null
+++ b/gce/start-remote-cache.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+# Startup scripts for the remote cache
+
+rm -rf /var/cas
+
+mkdir -p /var/cas/temp
+mkdir -p /var/cas/cache
+chown -R ci:ci /var/cas
+
+nginx -s quit || true
+nginx -c /root/nginx.conf
+
+max_cache_size=$((400 * 1024 * 1024 * 1024))
+echo "python /root/purge-cache.py /var/cas/cache ${max_cache_size}" | batch
+
diff --git a/gce/vm.sh b/gce/vm.sh
index 9a0555e..21c29fd 100755
--- a/gce/vm.sh
+++ b/gce/vm.sh
@@ -95,6 +95,8 @@
     "freebsd-12-slave-staging https://www.googleapis.com/compute/v1/projects/freebsd-org-cloud-dev/global/images/freebsd-12-0-current-amd64-2017-07-04 freebsd-12-staging europe-west1-d staging startup-script=jenkins-slave.sh freebsd-slave.sh freebsd-ci-homedir.sh"
     # Fow Windows, we use a custom image with pre-installed MSVC.
     "windows-slave-staging windows-server-2012-r2-dc-v20160112-vs2015-cpp-python-msys windows-x86_64-staging europe-west1-d staging windows-startup-script-ps1=jenkins-slave-windows.ps1"
+    # Remote Cache
+    "remote-cache-staging ubuntu-1604-lts remote-cache-staging europe-west1-d staging startup-script=start-remote-cache.sh setup-remote-cache.sh"
 )
 STAGING_MASTER=(
     # VM name