~andrewjbeach/juju-ci-tools/make-local-patcher

983.1.1 by Martin Packman
Add new Remote class for accessing juju machines
1
"""Tests for remote access to juju machines."""
2
3
import logging
4
from mock import patch
994.4.1 by Martin Packman
Implement copy function for winrm remote
5
import os
983.1.1 by Martin Packman
Add new Remote class for accessing juju machines
6
from StringIO import StringIO
7
import subprocess
8
import unittest
9
994.3.1 by Martin Packman
Make Remote an abstract class and implement winrm based version
10
import winrm
11
983.1.1 by Martin Packman
Add new Remote class for accessing juju machines
12
from jujupy import (
13
    EnvJujuClient,
14
    SimpleEnvironment,
15
    Status,
16
)
17
from remote import (
994.3.1 by Martin Packman
Make Remote an abstract class and implement winrm based version
18
    remote_from_address,
19
    remote_from_unit,
994.4.1 by Martin Packman
Implement copy function for winrm remote
20
    WinRmRemote,
21
)
22
from utility import (
23
    temp_dir,
983.1.1 by Martin Packman
Add new Remote class for accessing juju machines
24
)
25
26
27
class TestRemote(unittest.TestCase):
28
994.3.1 by Martin Packman
Make Remote an abstract class and implement winrm based version
29
    precise_status_output = """\
30
    machines:
31
        "1":
32
            series: precise
33
    services:
34
        a-service:
35
            units:
36
                a-service/0:
37
                    machine: "1"
38
                    public-address: 10.55.60.1
39
    """
40
41
    win2012hvr2_status_output = """\
42
    machines:
43
        "2":
44
            series: win2012hvr2
45
    services:
46
        a-service:
47
            units:
48
                a-service/0:
49
                    machine: "2"
50
                    public-address: 10.55.60.2
51
    """
52
983.1.1 by Martin Packman
Add new Remote class for accessing juju machines
53
    def setUp(self):
54
        log = logging.getLogger()
55
        self.addCleanup(setattr, log, "handlers", log.handlers)
56
        log.handlers = []
57
        self.log_stream = StringIO()
58
        handler = logging.StreamHandler(self.log_stream)
59
        handler.setFormatter(logging.Formatter("%(levelname)s %(message)s"))
60
        log.addHandler(handler)
61
994.3.1 by Martin Packman
Make Remote an abstract class and implement winrm based version
62
    def test_remote_from_unit(self):
63
        env = SimpleEnvironment("an-env", {"type": "nonlocal"})
64
        client = EnvJujuClient(env, None, None)
65
        unit = "a-service/0"
66
        with patch.object(client, "get_status", autospec=True) as st:
67
            st.return_value = Status.from_text(self.precise_status_output)
68
            remote = remote_from_unit(client, unit)
69
        self.assertEqual(
70
            repr(remote),
71
            "<SSHRemote env='an-env' unit='a-service/0'>")
72
        self.assertIs(False, remote.is_windows())
73
74
    def test_remote_from_unit_with_series(self):
75
        env = SimpleEnvironment("an-env", {"type": "nonlocal"})
76
        client = EnvJujuClient(env, None, None)
77
        unit = "a-service/0"
78
        remote = remote_from_unit(client, unit, series="trusty")
79
        self.assertEqual(
80
            repr(remote),
81
            "<SSHRemote env='an-env' unit='a-service/0'>")
82
        self.assertIs(False, remote.is_windows())
83
84
    def test_remote_from_unit_with_status(self):
85
        env = SimpleEnvironment("an-env", {"type": "nonlocal"})
86
        client = EnvJujuClient(env, None, None)
87
        unit = "a-service/0"
88
        status = Status.from_text(self.win2012hvr2_status_output)
89
        remote = remote_from_unit(client, unit, status=status)
90
        self.assertEqual(
91
            repr(remote),
994.4.4 by Martin Packman
Update with extra tests and addresses review comments by sinzui
92
            "<WinRmRemote env='an-env' unit='a-service/0' addr='10.55.60.2'>")
994.3.1 by Martin Packman
Make Remote an abstract class and implement winrm based version
93
        self.assertIs(True, remote.is_windows())
94
95
    def test_remote_from_address(self):
96
        remote = remote_from_address("10.55.60.1")
97
        self.assertEqual(repr(remote), "<SSHRemote addr='10.55.60.1'>")
98
        self.assertIs(None, remote.is_windows())
99
100
    def test_remote_from_address_and_series(self):
101
        remote = remote_from_address("10.55.60.2", series="trusty")
102
        self.assertEqual(repr(remote), "<SSHRemote addr='10.55.60.2'>")
103
        self.assertIs(False, remote.is_windows())
104
105
    def test_remote_from_address_and_win_series(self):
106
        remote = remote_from_address("10.55.60.3", series="win2012hvr2")
107
        self.assertEqual(repr(remote), "<WinRmRemote addr='10.55.60.3'>")
108
        self.assertIs(True, remote.is_windows())
983.1.1 by Martin Packman
Add new Remote class for accessing juju machines
109
110
    def test_run_with_unit(self):
111
        env = SimpleEnvironment("an-env", {"type": "nonlocal"})
112
        client = EnvJujuClient(env, None, None)
113
        unit = "a-service/0"
994.3.1 by Martin Packman
Make Remote an abstract class and implement winrm based version
114
        remote = remote_from_unit(client, unit, series="trusty")
983.1.1 by Martin Packman
Add new Remote class for accessing juju machines
115
        with patch.object(client, "get_juju_output") as mock_cmd:
116
            mock_cmd.return_value = "contents of /a/file"
117
            output = remote.run("cat /a/file")
118
            self.assertEqual(output, "contents of /a/file")
119
        mock_cmd.assert_called_once_with("ssh", unit, "cat /a/file")
120
121
    def test_run_with_unit_fallback(self):
122
        env = SimpleEnvironment("an-env", {"type": "nonlocal"})
123
        client = EnvJujuClient(env, None, None)
124
        unit = "a-service/0"
994.3.1 by Martin Packman
Make Remote an abstract class and implement winrm based version
125
        with patch.object(client, "get_status") as st:
126
            st.return_value = Status.from_text(self.precise_status_output)
127
            remote = remote_from_unit(client, unit)
128
            with patch.object(client, "get_juju_output") as mock_cmd:
129
                mock_cmd.side_effect = subprocess.CalledProcessError(1, "ssh")
983.1.1 by Martin Packman
Add new Remote class for accessing juju machines
130
                with patch.object(remote, "_run_subprocess") as mock_run:
131
                    mock_run.return_value = "contents of /a/file"
132
                    output = remote.run("cat /a/file")
133
                    self.assertEqual(output, "contents of /a/file")
134
        mock_cmd.assert_called_once_with("ssh", unit, "cat /a/file")
135
        mock_run.assert_called_once_with([
136
            "ssh",
137
            "-o", "User ubuntu",
138
            "-o", "UserKnownHostsFile /dev/null",
139
            "-o", "StrictHostKeyChecking no",
994.3.1 by Martin Packman
Make Remote an abstract class and implement winrm based version
140
            "10.55.60.1",
983.1.1 by Martin Packman
Add new Remote class for accessing juju machines
141
            "cat /a/file",
142
        ])
143
        self.assertRegexpMatches(
144
            self.log_stream.getvalue(),
145
            "(?m)^WARNING juju ssh to 'a-service/0' failed: .*")
146
147
    def test_run_with_address(self):
994.3.1 by Martin Packman
Make Remote an abstract class and implement winrm based version
148
        remote = remote_from_address("10.55.60.1")
983.1.1 by Martin Packman
Add new Remote class for accessing juju machines
149
        with patch.object(remote, "_run_subprocess") as mock_run:
150
            mock_run.return_value = "contents of /a/file"
151
            output = remote.run("cat /a/file")
152
            self.assertEqual(output, "contents of /a/file")
153
        mock_run.assert_called_once_with([
154
            "ssh",
155
            "-o", "User ubuntu",
156
            "-o", "UserKnownHostsFile /dev/null",
157
            "-o", "StrictHostKeyChecking no",
158
            "10.55.60.1",
159
            "cat /a/file",
160
        ])
161
994.3.1 by Martin Packman
Make Remote an abstract class and implement winrm based version
162
    def test_cat(self):
163
        remote = remote_from_address("10.55.60.1")
164
        with patch.object(remote, "_run_subprocess") as mock_run:
165
            remote.cat("/a/file")
166
        mock_run.assert_called_once_with([
167
            "ssh",
168
            "-o", "User ubuntu",
169
            "-o", "UserKnownHostsFile /dev/null",
170
            "-o", "StrictHostKeyChecking no",
171
            "10.55.60.1",
172
            "cat /a/file",
173
        ])
174
175
    def test_cat_on_windows(self):
176
        env = SimpleEnvironment("an-env", {"type": "nonlocal"})
177
        client = EnvJujuClient(env, None, None)
178
        unit = "a-service/0"
179
        with patch.object(client, "get_status", autospec=True) as st:
180
            st.return_value = Status.from_text(self.win2012hvr2_status_output)
181
            response = winrm.Response(("contents of /a/file", "",  0))
182
            remote = remote_from_unit(client, unit)
183
            with patch.object(remote.session, "run_cmd", autospec=True,
184
                              return_value=response) as mock_run:
185
                output = remote.cat("/a/file")
186
                self.assertEqual(output, "contents of /a/file")
187
        st.assert_called_once_with()
188
        mock_run.assert_called_once_with("type", ["/a/file"])
189
983.1.1 by Martin Packman
Add new Remote class for accessing juju machines
190
    def test_copy(self):
994.3.1 by Martin Packman
Make Remote an abstract class and implement winrm based version
191
        remote = remote_from_address("10.55.60.1")
983.1.1 by Martin Packman
Add new Remote class for accessing juju machines
192
        dest = "/local/path"
193
        with patch.object(remote, "_run_subprocess") as mock_run:
194
            remote.copy(dest, ["/var/log/*", "~/.config"])
195
        mock_run.assert_called_once_with([
196
            "scp",
197
            "-C",
198
            "-o", "User ubuntu",
199
            "-o", "UserKnownHostsFile /dev/null",
200
            "-o", "StrictHostKeyChecking no",
201
            "10.55.60.1:/var/log/*",
202
            "10.55.60.1:~/.config",
203
            "/local/path",
204
        ])
994.3.1 by Martin Packman
Make Remote an abstract class and implement winrm based version
205
994.4.4 by Martin Packman
Update with extra tests and addresses review comments by sinzui
206
    def test_copy_on_windows(self):
207
        env = SimpleEnvironment("an-env", {"type": "nonlocal"})
208
        client = EnvJujuClient(env, None, None)
209
        unit = "a-service/0"
210
        dest = "/local/path"
211
        with patch.object(client, "get_status", autospec=True) as st:
212
            st.return_value = Status.from_text(self.win2012hvr2_status_output)
213
            response = winrm.Response(("fake output", "",  0))
214
            remote = remote_from_unit(client, unit)
215
            with patch.object(remote.session, "run_ps", autospec=True,
216
                              return_value=response) as mock_run:
217
                with patch.object(remote, "_encoded_copy_to_dir",
218
                                  autospec=True) as mock_cpdir:
219
                    remote.copy(dest, ["C:\\logs\\*", "%APPDATA%\\*.log"])
220
        mock_cpdir.assert_called_once_with(dest, "fake output")
221
        st.assert_called_once_with()
222
        self.assertEquals(mock_run.call_count, 1)
223
        self.assertRegexpMatches(
224
            mock_run.call_args[0][0],
225
            r'.*"C:\\logs\\[*]","%APPDATA%\\[*].log".*')
226
994.3.1 by Martin Packman
Make Remote an abstract class and implement winrm based version
227
    def test_run_cmd(self):
228
        env = SimpleEnvironment("an-env", {"type": "nonlocal"})
229
        client = EnvJujuClient(env, None, None)
230
        unit = "a-service/0"
231
        with patch.object(client, "get_status", autospec=True) as st:
232
            st.return_value = Status.from_text(self.win2012hvr2_status_output)
233
            response = winrm.Response(("some out", "some err",  0))
234
            remote = remote_from_unit(client, unit)
235
            with patch.object(remote.session, "run_cmd", autospec=True,
236
                              return_value=response) as mock_run:
237
                output = remote.run_cmd(
238
                    ["C:\\Program Files\\bin.exe", "/IN", "Bob's Stuff"])
239
                self.assertEqual(output, response)
240
        st.assert_called_once_with()
241
        mock_run.assert_called_once_with(
242
            '"C:\\Program Files\\bin.exe"', ['/IN "Bob\'s Stuff"'])
994.4.1 by Martin Packman
Implement copy function for winrm remote
243
244
    def test_encoded_copy_to_dir_one(self):
245
        output = "testfile|K0ktLuECAA==\r\n"
246
        with temp_dir() as dest:
247
            WinRmRemote._encoded_copy_to_dir(dest, output)
248
            with open(os.path.join(dest, "testfile")) as f:
249
                self.assertEqual(f.read(), "test\n")
250
251
    def test_encoded_copy_to_dir_many(self):
252
        output = "test one|K0ktLuECAA==\r\ntest two|K0ktLuECAA==\r\n\r\n"
253
        with temp_dir() as dest:
254
            WinRmRemote._encoded_copy_to_dir(dest, output)
994.4.2 by Martin Packman
Use winrm copy implementation to get logs from windows machines
255
            for name in ("test one", "test two"):
256
                with open(os.path.join(dest, name)) as f:
257
                    self.assertEqual(f.read(), "test\n")
994.4.4 by Martin Packman
Update with extra tests and addresses review comments by sinzui
258
259
    def test_encoded_copy_traversal_guard(self):
260
        output = "../../../etc/passwd|K0ktLuECAA==\r\n"
261
        with temp_dir() as dest:
262
            with self.assertRaises(ValueError):
263
                WinRmRemote._encoded_copy_to_dir(dest, output)