Charmed-Kubernetes/nrpe/mod/charmhelpers/tests/contrib/unison/test_unison.py

483 lines
19 KiB
Python

from mock import call, patch, MagicMock, sentinel
from testtools import TestCase
from tests.helpers import patch_open, FakeRelation
from charmhelpers.contrib import unison
FAKE_RELATION_ENV = {
'cluster:0': ['cluster/0', 'cluster/1']
}
TO_PATCH = [
'log', 'check_call', 'check_output', 'relation_ids',
'related_units', 'relation_get', 'relation_set',
'hook_name', 'unit_private_ip',
]
FAKE_LOCAL_UNIT = 'test_host'
FAKE_RELATION = {
'cluster:0': {
'cluster/0': {
'private-address': 'cluster0.local',
'ssh_authorized_hosts': 'someotherhost:test_host'
},
'clsuter/1': {
'private-address': 'cluster1.local',
'ssh_authorized_hosts': 'someotherhost'
},
'clsuter/3': {
'private-address': 'cluster2.local',
'ssh_authorized_hosts': 'someotherthirdhost'
},
},
}
class UnisonHelperTests(TestCase):
def setUp(self):
super(UnisonHelperTests, self).setUp()
for m in TO_PATCH:
setattr(self, m, self._patch(m))
self.fake_relation = FakeRelation(FAKE_RELATION)
self.unit_private_ip.return_value = FAKE_LOCAL_UNIT
self.relation_get.side_effect = self.fake_relation.get
self.relation_ids.side_effect = self.fake_relation.relation_ids
self.related_units.side_effect = self.fake_relation.related_units
def _patch(self, method):
_m = patch('charmhelpers.contrib.unison.' + method)
mock = _m.start()
self.addCleanup(_m.stop)
return mock
@patch('pwd.getpwnam')
def test_get_homedir(self, pwnam):
fake_user = MagicMock()
fake_user.pw_dir = '/home/foo'
pwnam.return_value = fake_user
self.assertEquals(unison.get_homedir('foo'),
'/home/foo')
@patch('pwd.getpwnam')
def test_get_homedir_no_user(self, pwnam):
e = KeyError
pwnam.side_effect = e
self.assertRaises(Exception, unison.get_homedir, user='foo')
def _ensure_calls_in(self, calls):
for _call in calls:
self.assertIn(call(_call), self.check_call.call_args_list)
@patch('os.chmod')
@patch('os.chown')
@patch('os.path.isfile')
@patch('pwd.getpwnam')
def test_create_private_key_rsa(self, pwnam, isfile, chown, chmod):
fake_user = MagicMock()
fake_user.pw_uid = 3133
pwnam.return_value = fake_user
create_cmd = [
'ssh-keygen', '-q', '-N', '', '-t', 'rsa', '-b', '2048',
'-f', '/home/foo/.ssh/id_rsa']
def _ensure_perms():
chown.assert_called_with('/home/foo/.ssh/id_rsa', 3133, -1)
chmod.assert_called_with('/home/foo/.ssh/id_rsa', 0o600)
isfile.return_value = False
unison.create_private_key(
user='foo', priv_key_path='/home/foo/.ssh/id_rsa')
self.assertIn(call(create_cmd), self.check_call.call_args_list)
_ensure_perms()
self.check_call.call_args_list = []
chown.reset_mock()
chmod.reset_mock()
isfile.return_value = True
unison.create_private_key(
user='foo', priv_key_path='/home/foo/.ssh/id_rsa')
self.assertNotIn(call(create_cmd), self.check_call.call_args_list)
_ensure_perms()
@patch('os.chmod')
@patch('os.chown')
@patch('os.path.isfile')
@patch('pwd.getpwnam')
def test_create_private_key_ecdsa(self, pwnam, isfile, chown, chmod):
fake_user = MagicMock()
fake_user.pw_uid = 3133
pwnam.return_value = fake_user
create_cmd = [
'ssh-keygen', '-q', '-N', '', '-t', 'ecdsa', '-b', '521',
'-f', '/home/foo/.ssh/id_ecdsa']
def _ensure_perms():
chown.assert_called_with('/home/foo/.ssh/id_ecdsa', 3133, -1)
chmod.assert_called_with('/home/foo/.ssh/id_ecdsa', 0o600)
isfile.return_value = False
unison.create_private_key(
user='foo',
priv_key_path='/home/foo/.ssh/id_ecdsa',
key_type='ecdsa')
self.assertIn(call(create_cmd), self.check_call.call_args_list)
_ensure_perms()
self.check_call.call_args_list = []
chown.reset_mock()
chmod.reset_mock()
isfile.return_value = True
unison.create_private_key(
user='foo',
priv_key_path='/home/foo/.ssh/id_ecdsa',
key_type='ecdsa')
self.assertNotIn(call(create_cmd), self.check_call.call_args_list)
_ensure_perms()
@patch('os.chown')
@patch('os.path.isfile')
@patch('pwd.getpwnam')
def test_create_public_key(self, pwnam, isfile, chown):
fake_user = MagicMock()
fake_user.pw_uid = 3133
pwnam.return_value = fake_user
create_cmd = ['ssh-keygen', '-y', '-f', '/home/foo/.ssh/id_rsa']
def _ensure_perms():
chown.assert_called_with('/home/foo/.ssh/id_rsa.pub', 3133, -1)
isfile.return_value = True
unison.create_public_key(
user='foo', priv_key_path='/home/foo/.ssh/id_rsa',
pub_key_path='/home/foo/.ssh/id_rsa.pub')
self.assertNotIn(call(create_cmd), self.check_output.call_args_list)
_ensure_perms()
isfile.return_value = False
with patch_open() as (_open, _file):
self.check_output.return_value = b'fookey'
unison.create_public_key(
user='foo', priv_key_path='/home/foo/.ssh/id_rsa',
pub_key_path='/home/foo/.ssh/id_rsa.pub')
self.assertIn(call(create_cmd), self.check_output.call_args_list)
_ensure_perms()
_open.assert_called_with('/home/foo/.ssh/id_rsa.pub', 'wb')
_file.write.assert_called_with(b'fookey')
@patch('os.mkdir')
@patch('os.path.isdir')
@patch.object(unison, 'get_homedir')
@patch.multiple(unison, create_private_key=MagicMock(),
create_public_key=MagicMock())
def test_get_keypair(self, get_homedir, isdir, mkdir):
get_homedir.return_value = '/home/foo'
isdir.return_value = False
with patch_open() as (_open, _file):
_file.read.side_effect = [
'foopriv', 'foopub'
]
priv, pub = unison.get_keypair('adam')
for f in ['/home/foo/.ssh/id_rsa',
'/home/foo/.ssh/id_rsa.pub']:
self.assertIn(call(f, 'r'), _open.call_args_list)
self.assertEquals(priv, 'foopriv')
self.assertEquals(pub, 'foopub')
@patch.object(unison, 'get_homedir')
@patch('os.chown')
@patch('pwd.getpwnam')
def test_write_auth_keys(self, pwnam, chown, get_homedir):
fake_user = MagicMock()
fake_user.pw_uid = 3133
pwnam.return_value = fake_user
get_homedir.return_value = '/home/foo'
keys = [
'ssh-rsa AAAB3Nz adam',
'ssh-rsa ALKJFz adam@whereschuck.org',
]
def _ensure_perms():
chown.assert_called_with('/home/foo/.ssh/authorized_keys', 3133, -1)
with patch_open() as (_open, _file):
unison.write_authorized_keys('foo', keys)
_open.assert_called_with('/home/foo/.ssh/authorized_keys', 'w')
for k in keys:
self.assertIn(call('%s\n' % k), _file.write.call_args_list)
_ensure_perms()
@patch.object(unison, 'get_homedir')
@patch('os.chown')
@patch('pwd.getpwnam')
def test_write_known_hosts(self, pwnam, chown, get_homedir):
fake_user = MagicMock()
fake_user.pw_uid = 3133
pwnam.return_value = fake_user
get_homedir.return_value = '/home/foo'
keys = [
'10.0.0.1 ssh-rsa KJDSJF=',
'10.0.0.2 ssh-rsa KJDSJF=',
]
self.check_output.side_effect = keys
def _ensure_perms():
chown.assert_called_with('/home/foo/.ssh/known_hosts', 3133, -1)
with patch_open() as (_open, _file):
unison.write_known_hosts('foo', ['10.0.0.1', '10.0.0.2'])
_open.assert_called_with('/home/foo/.ssh/known_hosts', 'w')
for k in keys:
self.assertIn(call('%s\n' % k), _file.write.call_args_list)
_ensure_perms()
@patch.object(unison, 'remove_password_expiry')
@patch.object(unison, 'pwgen')
@patch.object(unison, 'add_user_to_group')
@patch.object(unison, 'adduser')
def test_ensure_user(self, adduser, to_group, pwgen,
remove_password_expiry):
pwgen.return_value = sentinel.password
unison.ensure_user('foo', group='foobar')
adduser.assert_called_with('foo', sentinel.password)
to_group.assert_called_with('foo', 'foobar')
remove_password_expiry.assert_called_with('foo')
@patch.object(unison, '_run_as_user')
def test_run_as_user(self, _run):
with patch.object(unison, '_run_as_user') as _run:
fake_preexec = MagicMock()
_run.return_value = fake_preexec
unison.run_as_user('foo', ['echo', 'foo'])
self.check_output.assert_called_with(
['echo', 'foo'], preexec_fn=fake_preexec, cwd='/')
@patch('pwd.getpwnam')
def test_run_user_not_found(self, getpwnam):
e = KeyError
getpwnam.side_effect = e
self.assertRaises(Exception, unison._run_as_user, 'nouser')
@patch('os.setuid')
@patch('os.setgid')
@patch('os.environ', spec=dict)
@patch('pwd.getpwnam')
def test_run_as_user_preexec(self, pwnam, environ, setgid, setuid):
fake_env = {'HOME': '/root'}
environ.__getitem__ = MagicMock()
environ.__setitem__ = MagicMock()
environ.__setitem__.side_effect = fake_env.__setitem__
environ.__getitem__.side_effect = fake_env.__getitem__
fake_user = MagicMock()
fake_user.pw_uid = 1010
fake_user.pw_gid = 1011
fake_user.pw_dir = '/home/foo'
pwnam.return_value = fake_user
inner = unison._run_as_user('foo')
self.assertEquals(fake_env['HOME'], '/home/foo')
inner()
setgid.assert_called_with(1011)
setuid.assert_called_with(1010)
@patch('os.setuid')
@patch('os.setgid')
@patch('os.environ', spec=dict)
@patch('pwd.getpwnam')
def test_run_as_user_preexec_with_group(self, pwnam, environ, setgid, setuid):
fake_env = {'HOME': '/root'}
environ.__getitem__ = MagicMock()
environ.__setitem__ = MagicMock()
environ.__setitem__.side_effect = fake_env.__setitem__
environ.__getitem__.side_effect = fake_env.__getitem__
fake_user = MagicMock()
fake_user.pw_uid = 1010
fake_user.pw_gid = 1011
fake_user.pw_dir = '/home/foo'
fake_group_id = 2000
pwnam.return_value = fake_user
inner = unison._run_as_user('foo', gid=fake_group_id)
self.assertEquals(fake_env['HOME'], '/home/foo')
inner()
setgid.assert_called_with(2000)
setuid.assert_called_with(1010)
@patch.object(unison, 'get_keypair')
@patch.object(unison, 'ensure_user')
def test_ssh_auth_peer_joined(self, ensure_user, get_keypair):
get_keypair.return_value = ('privkey', 'pubkey')
self.hook_name.return_value = 'cluster-relation-joined'
unison.ssh_authorized_peers(peer_interface='cluster',
user='foo', group='foo',
ensure_local_user=True)
self.relation_set.assert_called_with(ssh_pub_key='pubkey')
self.assertFalse(self.relation_get.called)
ensure_user.assert_called_with('foo', 'foo')
get_keypair.assert_called_with('foo')
@patch.object(unison, 'write_known_hosts')
@patch.object(unison, 'write_authorized_keys')
@patch.object(unison, 'get_keypair')
@patch.object(unison, 'ensure_user')
def test_ssh_auth_peer_changed(self, ensure_user, get_keypair,
write_keys, write_hosts):
get_keypair.return_value = ('privkey', 'pubkey')
self.hook_name.return_value = 'cluster-relation-changed'
self.relation_get.side_effect = [
'key1',
'host1',
'key2',
'host2',
'', ''
]
unison.ssh_authorized_peers(peer_interface='cluster',
user='foo', group='foo',
ensure_local_user=True)
ensure_user.assert_called_with('foo', 'foo')
get_keypair.assert_called_with('foo')
write_keys.assert_called_with('foo', ['key1', 'key2'])
write_hosts.assert_called_with('foo', ['host1', 'host2'])
self.relation_set.assert_called_with(ssh_authorized_hosts='host1:host2')
@patch.object(unison, 'write_known_hosts')
@patch.object(unison, 'write_authorized_keys')
@patch.object(unison, 'get_keypair')
@patch.object(unison, 'ensure_user')
def test_ssh_auth_peer_departed(self, ensure_user, get_keypair,
write_keys, write_hosts):
get_keypair.return_value = ('privkey', 'pubkey')
self.hook_name.return_value = 'cluster-relation-departed'
self.relation_get.side_effect = [
'key1',
'host1',
'key2',
'host2',
'', ''
]
unison.ssh_authorized_peers(peer_interface='cluster',
user='foo', group='foo',
ensure_local_user=True)
ensure_user.assert_called_with('foo', 'foo')
get_keypair.assert_called_with('foo')
write_keys.assert_called_with('foo', ['key1', 'key2'])
write_hosts.assert_called_with('foo', ['host1', 'host2'])
self.relation_set.assert_called_with(ssh_authorized_hosts='host1:host2')
def test_collect_authed_hosts(self):
# only one of the hosts in fake environment has auth'd
# the local peer
hosts = unison.collect_authed_hosts('cluster')
self.assertEquals(hosts, ['cluster0.local'])
def test_collect_authed_hosts_none_authed(self):
with patch.object(unison, 'relation_get') as relation_get:
relation_get.return_value = ''
hosts = unison.collect_authed_hosts('cluster')
self.assertEquals(hosts, [])
@patch.object(unison, 'run_as_user')
def test_sync_path_to_host(self, run_as_user, verbose=True, gid=None):
for path in ['/tmp/foo', '/tmp/foo/']:
unison.sync_path_to_host(path=path, host='clusterhost1',
user='foo', verbose=verbose, gid=gid)
ex_cmd = ['unison', '-auto', '-batch=true',
'-confirmbigdel=false', '-fastcheck=true',
'-group=false', '-owner=false',
'-prefer=newer', '-times=true']
if not verbose:
ex_cmd.append('-silent')
ex_cmd += ['/tmp/foo', 'ssh://foo@clusterhost1//tmp/foo']
run_as_user.assert_called_with('foo', ex_cmd, gid)
@patch.object(unison, 'run_as_user')
def test_sync_path_to_host_error(self, run_as_user):
for i, path in enumerate(['/tmp/foo', '/tmp/foo/']):
run_as_user.side_effect = Exception
if i == 0:
unison.sync_path_to_host(path=path, host='clusterhost1',
user='foo', verbose=True, gid=None)
else:
self.assertRaises(Exception, unison.sync_path_to_host,
path=path, host='clusterhost1',
user='foo', verbose=True, gid=None,
fatal=True)
ex_cmd = ['unison', '-auto', '-batch=true',
'-confirmbigdel=false', '-fastcheck=true',
'-group=false', '-owner=false',
'-prefer=newer', '-times=true',
'/tmp/foo', 'ssh://foo@clusterhost1//tmp/foo']
run_as_user.assert_called_with('foo', ex_cmd, None)
def test_sync_path_to_host_non_verbose(self):
return self.test_sync_path_to_host(verbose=False)
def test_sync_path_to_host_with_gid(self):
return self.test_sync_path_to_host(gid=111)
@patch.object(unison, 'sync_path_to_host')
def test_sync_to_peer(self, sync_path_to_host):
paths = ['/tmp/foo1', '/tmp/foo2']
host = 'host1'
unison.sync_to_peer(host, 'foouser', paths, True)
calls = [call('/tmp/foo1', host, 'foouser', True, None, None, False),
call('/tmp/foo2', host, 'foouser', True, None, None, False)]
sync_path_to_host.assert_has_calls(calls)
@patch.object(unison, 'sync_path_to_host')
def test_sync_to_peer_with_gid(self, sync_path_to_host):
paths = ['/tmp/foo1', '/tmp/foo2']
host = 'host1'
unison.sync_to_peer(host, 'foouser', paths, True, gid=111)
calls = [call('/tmp/foo1', host, 'foouser', True, None, 111, False),
call('/tmp/foo2', host, 'foouser', True, None, 111, False)]
sync_path_to_host.assert_has_calls(calls)
@patch.object(unison, 'collect_authed_hosts')
@patch.object(unison, 'sync_to_peer')
def test_sync_to_peers(self, sync_to_peer, collect_hosts):
collect_hosts.return_value = ['host1', 'host2', 'host3']
paths = ['/tmp/foo']
unison.sync_to_peers(peer_interface='cluster', user='foouser',
paths=paths, verbose=True)
calls = [call('host1', 'foouser', ['/tmp/foo'], True, None, None, False),
call('host2', 'foouser', ['/tmp/foo'], True, None, None, False),
call('host3', 'foouser', ['/tmp/foo'], True, None, None, False)]
sync_to_peer.assert_has_calls(calls)
@patch.object(unison, 'collect_authed_hosts')
@patch.object(unison, 'sync_to_peer')
def test_sync_to_peers_with_gid(self, sync_to_peer, collect_hosts):
collect_hosts.return_value = ['host1', 'host2', 'host3']
paths = ['/tmp/foo']
unison.sync_to_peers(peer_interface='cluster', user='foouser',
paths=paths, verbose=True, gid=111)
calls = [call('host1', 'foouser', ['/tmp/foo'], True, None, 111, False),
call('host2', 'foouser', ['/tmp/foo'], True, None, 111, False),
call('host3', 'foouser', ['/tmp/foo'], True, None, 111, False)]
sync_to_peer.assert_has_calls(calls)
@patch.object(unison, 'collect_authed_hosts')
@patch.object(unison, 'sync_to_peer')
def test_sync_to_peers_with_cmd(self, sync_to_peer, collect_hosts):
collect_hosts.return_value = ['host1', 'host2', 'host3']
paths = ['/tmp/foo']
cmd = ['dummy_cmd']
unison.sync_to_peers(peer_interface='cluster', user='foouser',
paths=paths, verbose=True, cmd=cmd, gid=111)
calls = [call('host1', 'foouser', ['/tmp/foo'], True, cmd, 111, False),
call('host2', 'foouser', ['/tmp/foo'], True, cmd, 111, False),
call('host3', 'foouser', ['/tmp/foo'], True, cmd, 111, False)]
sync_to_peer.assert_has_calls(calls)