summaryrefslogtreecommitdiffstats
path: root/ipatests/test_integration/test_server_del.py
blob: f5738a3a594b32200e6e2cacff0763a1464c62a3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
#
# Copyright (C) 2016  FreeIPA Contributors see COPYING for license
#

from itertools import permutations

from ipatests.test_integration.base import IntegrationTest
from ipatests.pytest_plugins.integration import tasks
from ipalib.constants import DOMAIN_LEVEL_1, DOMAIN_SUFFIX_NAME, CA_SUFFIX_NAME

REMOVAL_ERR_TEMPLATE = ("Removal of '{hostname}' leads to disconnected "
                        "topology in suffix '{suffix}'")


def check_master_removal(host, hostname_to_remove,
                         force=False,
                         ignore_topology_disconnect=False,
                         ignore_last_of_role=False):
    result = tasks.run_server_del(
        host,
        hostname_to_remove,
        force=force,
        ignore_topology_disconnect=ignore_topology_disconnect,
        ignore_last_of_role=ignore_last_of_role)

    assert result.returncode == 0
    if force:
        assert ("Forcing removal of {hostname}".format(
            hostname=hostname_to_remove) in result.stderr_text)

    if ignore_topology_disconnect:
        assert "Ignoring topology connectivity errors." in result.stderr_text

    if ignore_last_of_role:
        assert ("Ignoring these warnings and proceeding with removal" in
                result.stderr_text)

    tasks.assert_error(
        host.run_command(
            ['ipa', 'server-show', hostname_to_remove], raiseonerr=False
        ),
        "{}: server not found".format(hostname_to_remove),
        returncode=2
    )


def check_removal_disconnects_topology(
        host, hostname_to_remove,
        affected_suffixes=(DOMAIN_SUFFIX_NAME,)):
    result = tasks.run_server_del(host, hostname_to_remove)
    assert len(affected_suffixes) <= 2

    err_messages_by_suffix = {
        CA_SUFFIX_NAME: REMOVAL_ERR_TEMPLATE.format(
            hostname=hostname_to_remove,
            suffix=CA_SUFFIX_NAME
        ),
        DOMAIN_SUFFIX_NAME: REMOVAL_ERR_TEMPLATE.format(
            hostname=hostname_to_remove,
            suffix=DOMAIN_SUFFIX_NAME
        )
    }

    for suffix in err_messages_by_suffix:
        if suffix in affected_suffixes:
            tasks.assert_error(
                result, err_messages_by_suffix[suffix], returncode=1)
        else:
            assert err_messages_by_suffix[suffix] not in result.stderr_text


class ServerDelBase(IntegrationTest):
    num_replicas = 2
    num_clients = 1
    domain_level = DOMAIN_LEVEL_1
    topology = 'star'

    @classmethod
    def install(cls, mh):
        super(ServerDelBase, cls).install(mh)

        cls.client = cls.clients[0]
        cls.replica1 = cls.replicas[0]
        cls.replica2 = cls.replicas[1]


class TestServerDel(ServerDelBase):

    @classmethod
    def install(cls, mh):
        super(TestServerDel, cls).install(mh)
        # prepare topologysegments for negative test cases
        # it should look like this for DOMAIN_SUFFIX_NAME:
        #             master
        #            /
        #           /
        #          /
        #   replica1------- replica2
        # and like this for CA_SUFFIX_NAME
        #             master
        #                  \
        #                   \
        #                    \
        #   replica1------- replica2

        tasks.create_segment(cls.client, cls.replica1, cls.replica2)
        tasks.create_segment(cls.client, cls.replica1, cls.replica2,
                             suffix=CA_SUFFIX_NAME)

        # try to delete all relevant segment connecting master and replica1/2
        segment_name_fmt = '{p[0].hostname}-to-{p[1].hostname}'
        for domain_pair in permutations((cls.master, cls.replica2)):
            tasks.destroy_segment(
                cls.client, segment_name_fmt.format(p=domain_pair))

        for ca_pair in permutations((cls.master, cls.replica1)):
            tasks.destroy_segment(
                cls.client, segment_name_fmt.format(p=ca_pair),
                suffix=CA_SUFFIX_NAME)

    def test_removal_of_nonexistent_master_raises_error(self):
        """
        tests that removal of non-existent master raises an error
        """
        hostname = u'bogus-master.bogus.domain'
        err_message = "{}: server not found".format(hostname)
        tasks.assert_error(
            tasks.run_server_del(self.client, hostname),
            err_message,
            returncode=2
        )

    def test_forced_removal_of_nonexistent_master(self):
        """
        tests that removal of non-existent master with '--force' does not raise
        an error
        """
        hostname = u'bogus-master.bogus.domain'
        result = tasks.run_server_del(self.client, hostname, force=True)
        assert result.returncode == 0
        assert ('Deleted IPA server "{}"'.format(hostname) in
                result.stdout_text)

        assert ("Server has already been deleted" in result.stderr_text)

    def test_removal_of_replica1_disconnects_domain_topology(self):
        """
        tests that given the used topology, attempted removal of replica1 fails
        with disconnected DOMAIN topology but not CA
        """

        check_removal_disconnects_topology(
            self.client,
            self.replica1.hostname,
            affected_suffixes=(DOMAIN_SUFFIX_NAME,)
        )

    def test_removal_of_replica2_disconnects_ca_topology(self):
        """
        tests that given the used topology, attempted removal of replica2 fails
        with disconnected CA topology but not DOMAIN
        """

        check_removal_disconnects_topology(
            self.client,
            self.replica2.hostname,
            affected_suffixes=(CA_SUFFIX_NAME,)
        )

    def test_ignore_topology_disconnect_replica1(self):
        """
        tests that removal of replica1 with '--ignore-topology-disconnect'
        destroys master for good
        """
        check_master_removal(
            self.client,
            self.replica1.hostname,
            ignore_topology_disconnect=True
        )

        # reinstall the replica
        tasks.uninstall_master(self.replica1)
        tasks.install_replica(self.master, self.replica1, setup_ca=True)

    def test_ignore_topology_disconnect_replica2(self):
        """
        tests that removal of replica2 with '--ignore-topology-disconnect'
        destroys master for good
        """
        check_master_removal(
            self.client,
            self.replica2.hostname,
            ignore_topology_disconnect=True
        )

        # reinstall the replica
        tasks.uninstall_master(self.replica2)
        tasks.install_replica(self.master, self.replica2, setup_ca=True)

    def test_removal_of_master_disconnects_both_topologies(self):
        """
        tests that master removal will now raise errors in both suffixes.
        """
        check_removal_disconnects_topology(
            self.client,
            self.master.hostname,
            affected_suffixes=(CA_SUFFIX_NAME, DOMAIN_SUFFIX_NAME)
        )

    def test_removal_of_replica1(self):
        """
        tests the removal of replica1 which should now pass without errors
        """
        check_master_removal(
            self.client,
            self.replica1.hostname
        )

    def test_removal_of_replica2(self):
        """
        tests the removal of replica2 which should now pass without errors
        """
        check_master_removal(
            self.client,
            self.replica2.hostname
        )


class TestLastServices(ServerDelBase):
    """
    Test the checks for last services during server-del and their bypassing
    using when forcing the removal
    """
    num_replicas = 1
    domain_level = DOMAIN_LEVEL_1
    topology = 'line'

    @classmethod
    def install(cls, mh):
        tasks.install_topo(
            cls.topology, cls.master, cls.replicas, [],
            domain_level=cls.domain_level, setup_replica_cas=False)

    def test_removal_of_master_raises_error_about_last_ca(self):
        """
        test that removal of master fails on the last
        """
        tasks.assert_error(
            tasks.run_server_del(self.replicas[0], self.master.hostname),
            "Deleting this server is not allowed as it would leave your "
            "installation without a CA.",
            1
        )

    def test_install_ca_on_replica1(self):
        """
        Install CA on replica so that we can test DNS-related checks
        """
        tasks.install_ca(self.replicas[0], domain_level=self.domain_level)

    def test_removal_of_master_raises_error_about_last_dns(self):
        """
        Now server-del should complain about the removal of last DNS server
        """
        tasks.assert_error(
            tasks.run_server_del(self.replicas[0], self.master.hostname),
            "Deleting this server will leave your installation "
            "without a DNS.",
            1
        )

    def test_install_dns_on_replica1_and_dnssec_on_master(self):
        """
        install DNS server on replica and DNSSec on master
        """
        tasks.install_dns(self.replicas[0])
        args = [
            "ipa-dns-install",
            "--dnssec-master",
            "--forwarder", self.master.config.dns_forwarder,
            "-U",
        ]
        self.master.run_command(args)

    def test_removal_of_master_raises_error_about_dnssec(self):
        tasks.assert_error(
            tasks.run_server_del(self.replicas[0], self.master.hostname),
            "Replica is active DNSSEC key master. Uninstall "
            "could break your DNS system. Please disable or replace "
            "DNSSEC key master first.",
            1
        )

    def test_forced_removal_of_master(self):
        """
        Tests that we can still force remove the master using
        '--ignore-last-of-role'
        """
        check_master_removal(
            self.replicas[0], self.master.hostname,
            ignore_last_of_role=True
        )