From dd4a7ac0fb422718584c801918fcd71327bd54cc Mon Sep 17 00:00:00 2001
From: Edward Hope-Morley <edward.hope-morley@canonical.com>
Date: Tue, 24 Nov 2015 13:35:26 +0000
Subject: [PATCH] [hopem,r=]

Add support for settting swift-storage unit rsyncd acls.
Partially-Closes-Bug: 1427361
---
 hooks/swift_hooks.py           | 62 ++++++++++++++++++++++++++++------
 hooks/upgrade-charm            |  1 +
 unit_tests/test_swift_hooks.py | 36 +++++++++++++++++++-
 3 files changed, 87 insertions(+), 12 deletions(-)
 create mode 120000 hooks/upgrade-charm

diff --git a/hooks/swift_hooks.py b/hooks/swift_hooks.py
index ec37af6..baf462f 100755
--- a/hooks/swift_hooks.py
+++ b/hooks/swift_hooks.py
@@ -2,6 +2,7 @@
 
 import os
 import sys
+import time
 
 from subprocess import (
     check_call,
@@ -192,6 +193,44 @@ def storage_joined():
         mark_www_rings_deleted()
 
 
+def get_host_ip(rid=None, unit=None):
+    addr = relation_get('private-address', rid=rid, unit=unit)
+    if config('prefer-ipv6'):
+        host_ip = format_ipv6_addr(addr)
+        if host_ip:
+            return host_ip
+        else:
+            msg = ("Did not get IPv6 address from storage relation "
+                   "(got=%s)" % (addr))
+            log(msg, level=WARNING)
+
+    return openstack.get_host_ip(addr)
+
+
+def update_rsync_acls():
+    """Get Host IP of each storage unit and broadcast acl to all units."""
+    hosts = []
+
+    if not is_elected_leader(SWIFT_HA_RES):
+        log("Skipping rsync acl update since not leader", level=DEBUG)
+        return
+
+    # Get all unit addresses
+    for rid in relation_ids('swift-storage'):
+        for unit in related_units(rid):
+            hosts.append(get_host_ip(rid=rid, unit=unit))
+
+    rsync_hosts = ' '.join(hosts)
+    log("Broadcasting acl '%s' to all storage units" % (rsync_hosts),
+        level=DEBUG)
+    # We add a timestamp so that the storage units know which is the newest
+    settings = {'rsync_allowed_hosts': rsync_hosts,
+                'timestamp': time.time()}
+    for rid in relation_ids('swift-storage'):
+        for unit in related_units(rid):
+            relation_set(unit=unit, relation_id=rid, **settings)
+
+
 @hooks.hook('swift-storage-relation-changed')
 @pause_aware_restart_on_change(restart_map())
 def storage_changed():
@@ -206,17 +245,13 @@ def storage_changed():
         return
 
     log("Leader established, updating ring builders", level=INFO)
-    addr = relation_get('private-address')
-    if config('prefer-ipv6'):
-        host_ip = format_ipv6_addr(addr)
-        if not host_ip:
-            msg = ("Did not get IPv6 address from storage relation "
-                   "(got=%s)" % (addr))
-            log(msg, level=WARNING)
-            host_ip = addr
-            return
-    else:
-        host_ip = openstack.get_host_ip(addr)
+    host_ip = get_host_ip()
+    if not host_ip:
+        log("No host ip found in storage relation - deferring storage "
+            "relation", level=WARNING)
+        return
+
+    update_rsync_acls()
 
     zone = get_zone(config('zone-assignment'))
     node_settings = {
@@ -540,6 +575,11 @@ def update_nrpe_config():
     nrpe_setup.write()
 
 
+@hooks.hook('upgrade-charm')
+def upgrade_charm():
+    update_rsync_acls()
+
+
 def main():
     try:
         hooks.execute(sys.argv)
diff --git a/hooks/upgrade-charm b/hooks/upgrade-charm
new file mode 120000
index 0000000..8623fba
--- /dev/null
+++ b/hooks/upgrade-charm
@@ -0,0 +1 @@
+swift_hooks.py
\ No newline at end of file
diff --git a/unit_tests/test_swift_hooks.py b/unit_tests/test_swift_hooks.py
index 84bf0bf..e16061f 100644
--- a/unit_tests/test_swift_hooks.py
+++ b/unit_tests/test_swift_hooks.py
@@ -1,8 +1,12 @@
-from mock import patch
 import sys
 import unittest
 import uuid
 
+from mock import (
+    call,
+    patch,
+)
+
 sys.path.append("hooks")
 with patch('charmhelpers.core.hookenv.log'):
     with patch('lib.swift_utils.is_paused') as is_paused:
@@ -129,3 +133,33 @@ class SwiftHooksTestCase(unittest.TestCase):
             requested_roles='Operator,Monitor',
             relation_id=None
         )
+
+    @patch.object(swift_hooks.time, 'time')
+    @patch.object(swift_hooks, 'get_host_ip')
+    @patch.object(swift_hooks, 'is_elected_leader')
+    @patch.object(swift_hooks, 'related_units')
+    @patch.object(swift_hooks, 'relation_ids')
+    @patch.object(swift_hooks, 'relation_set')
+    def test_update_rsync_acls(self, mock_rel_set, mock_rel_ids,
+                               mock_rel_units, mock_is_leader,
+                               mock_get_host_ip, mock_time):
+        mock_time.return_value = 1234
+        mock_is_leader.return_value = True
+        mock_rel_ids.return_value = ['storage:1']
+        mock_rel_units.return_value = ['unit/0', 'unit/1']
+
+        def fake_get_host_ip(rid, unit):
+            if unit == 'unit/0':
+                return '10.0.0.1'
+            elif unit == 'unit/1':
+                return '10.0.0.2'
+
+        mock_get_host_ip.side_effect = fake_get_host_ip
+        swift_hooks.update_rsync_acls()
+        calls = [call(rsync_allowed_hosts='10.0.0.1 10.0.0.2',
+                      relation_id='storage:1',
+                      unit='unit/0', timestamp=1234),
+                 call(rsync_allowed_hosts='10.0.0.1 10.0.0.2',
+                      relation_id='storage:1',
+                      unit='unit/1', timestamp=1234)]
+        mock_rel_set.assert_has_calls(calls)