예제 #1
0
    def test_happy_path_first_node(self, zk, tmp_path):
        cluster_state_file = tmp_path / "cluster_state_file.txt"
        cluster_nodes_file = tmp_path / "cluster_nodes_file.txt"
        etcdctl_path = tmp_path / "etcdctl_path"

        detectip_mock = mock.Mock(return_value="1.2.3.4")
        etcdctl_result = subprocess.CompletedProcess(
            args=["foo", "bar"],
            returncode=0,
            stdout="foo stdout",
            stderr="foo stderr",
        )
        subprocess_mock = mock.MagicMock(return_value=etcdctl_result)
        args = Namespace(
            etcd_data_dir=str(etcdctl_path),
            zk_addr="127.0.0.1:2181",
            cluster_state_file=str(cluster_state_file),
            cluster_nodes_file=str(cluster_nodes_file),
            etcdctl_path=str(etcdctl_path),
            secure=False,
            ca_cert="",
            etcd_client_tls_cert="",
            etcd_client_tls_key="",
        )

        with contextlib.ExitStack() as stack:
            stack.enter_context(
                mock.patch('etcd_discovery.etcd_discovery.detect_ip',
                           detectip_mock))
            stack.enter_context(mock.patch('subprocess.run', subprocess_mock))
            etd.join_cluster(args)

        detectip_mock.assert_called_once()
        subprocess_mock.assert_not_called()
예제 #2
0
    def test_etcd_already_initialized(self, tmp_path):
        d = tmp_path / "default.etcd"
        d.mkdir()

        args = Namespace(etcd_data_dir=str(d))

        etd.join_cluster(args)
예제 #3
0
    def test_happy_path_first_node(self, zk, tmp_path, args):
        detectip_mock = mock.Mock(return_value="1.2.3.4")
        subprocess_mock = mock.MagicMock()

        with contextlib.ExitStack() as stack:
            stack.enter_context(
                mock.patch('etcd_discovery.etcd_discovery.detect_ip',
                           detectip_mock))
            stack.enter_context(mock.patch('subprocess.run', subprocess_mock))
            etd.join_cluster(args)

        detectip_mock.assert_called_once()
        subprocess_mock.assert_not_called()
예제 #4
0
    def test_happy_path_subsequent_node(self, zk, tmp_path,
                                        subprocess_sideeffect, call_data,
                                        args):
        this_node = "1.2.3.4"
        active_node = "1.1.1.1"

        zk.set('/etcd/nodes',
               bytes('{{"nodes":["{}"]}}'.format(active_node), "utf8"))

        # Adjust the IP we are expecting
        for k in call_data:
            call_data[k]["args"][2] = "http://{}:2379".format(active_node)
            call_data[k]["action"].args[2] = "http://{}:2379".format(
                active_node)

        detectip_mock = mock.Mock(return_value=this_node)
        subprocess_mock = mock.MagicMock(side_effect=subprocess_sideeffect)

        with contextlib.ExitStack() as stack:
            stack.enter_context(
                mock.patch('etcd_discovery.etcd_discovery.detect_ip',
                           detectip_mock))
            stack.enter_context(mock.patch('subprocess.run', subprocess_mock))
            etd.join_cluster(args)

        detectip_mock.assert_called_once()
        subprocess_mock.assert_has_calls([
            mock.call(call_data["member_list"]["args"],
                      stdout=subprocess.PIPE,
                      stderr=subprocess.PIPE),
            mock.call(call_data["member_add"]["args"],
                      stdout=subprocess.PIPE,
                      stderr=subprocess.PIPE),
            mock.call(call_data["endpoint_health"]["args"],
                      stdout=subprocess.PIPE,
                      stderr=subprocess.PIPE),
        ],
                                         any_order=True)  # noqa: E126
        assert subprocess_mock.call_count == 3
예제 #5
0
    def test_subsequent_node_already_registered(self, zk, tmp_path,
                                                subprocess_sideeffect,
                                                call_data, args):
        this_node = "1.2.3.4"
        active_node = "1.1.1.1"

        zk.set(
            '/etcd/nodes',
            bytes('{{"nodes":["{}", "{}"]}}'.format(this_node, active_node),
                  "utf8"))

        # Adjust the IP we are expecting
        for k in call_data:
            call_data[k]["args"][2] = "http://{}:2379".format(active_node)
            call_data[k]["action"].args[2] = "http://{}:2379".format(
                active_node)

        detectip_mock = mock.Mock(return_value=this_node)
        subprocess_mock = mock.MagicMock(side_effect=subprocess_sideeffect)

        # designated node is choosen basing on the exit status of the
        # `endpoint health` command
        tmp = call_data.pop("endpoint_health")
        call_data["endpoint_health_this_node"] = {
            "args":
            tmp["args"].copy(),
            "action":
            subprocess.CompletedProcess(
                args=tmp["action"].args.copy(),
                returncode=1,
                stdout=b'',
                stderr=b'',
            )
        }
        call_data["endpoint_health_this_node"]["args"][
            2] = "http://{}:2379".format(this_node)
        call_data["endpoint_health_this_node"]["action"].args[
            2] = "http://{}:2379".format(this_node)
        call_data["endpoint_health_active_node"] = {
            "args":
            tmp["args"].copy(),
            "action":
            subprocess.CompletedProcess(
                args=tmp["action"].args.copy(),
                returncode=0,
                stdout=b'',
                stderr=b'',
            )
        }
        call_data["endpoint_health_active_node"]["args"][
            2] = "http://{}:2379".format(active_node)
        call_data["endpoint_health_active_node"]["action"].args[
            2] = "http://{}:2379".format(active_node)

        with contextlib.ExitStack() as stack:
            stack.enter_context(
                mock.patch('etcd_discovery.etcd_discovery.detect_ip',
                           detectip_mock))
            stack.enter_context(mock.patch('subprocess.run', subprocess_mock))
            etd.join_cluster(args)

        detectip_mock.assert_called_once()
        subprocess_mock.assert_has_calls([
            mock.call(call_data["member_list"]["args"],
                      stdout=subprocess.PIPE,
                      stderr=subprocess.PIPE),
            mock.call(call_data["member_add"]["args"],
                      stdout=subprocess.PIPE,
                      stderr=subprocess.PIPE),
            mock.call(call_data["endpoint_health_active_node"]["args"],
                      stdout=subprocess.PIPE,
                      stderr=subprocess.PIPE),
            mock.call(call_data["endpoint_health_this_node"]["args"],
                      stdout=subprocess.PIPE,
                      stderr=subprocess.PIPE),
        ],
                                         any_order=True)  # noqa: E126
        assert subprocess_mock.call_count == 5
예제 #6
0
    def test_etcd_already_initialized(self, etcdctl_path):
        etcdctl_path.mkdir()

        args = Namespace(etcd_data_dir=str(etcdctl_path))

        etd.join_cluster(args)
예제 #7
0
    def test_happy_path_subsequent_node(self, zk, tmp_path):
        cluster_state_file = tmp_path / "cluster_state_file.txt"
        cluster_nodes_file = tmp_path / "cluster_nodes_file.txt"
        etcdctl_path = tmp_path / "etcdctl_path"

        zk.set('/etcd/nodes', b'{"nodes":["1.1.1.1"]}')

        detectip_mock = mock.Mock(return_value="1.2.3.4")
        base_args = [
            str(etcdctl_path),
            "--endpoints",
            "http://1.1.1.1:2379",
        ]
        member_list_args = base_args + ["member", "list", "-w", "json"]
        member_add_args = base_args + [
            "member",
            "add",
            "etcd-1.2.3.4",
            "--peer-urls=https://1.2.3.4:2380",
        ]
        endpoint_health = base_args + ["endpoint", "health"]

        def subprocess_sideeffect(args, stdout, stderr):
            assert stdout is subprocess.PIPE
            assert stderr is subprocess.PIPE

            if args == member_list_args:
                return subprocess.CompletedProcess(
                    args=member_list_args,
                    returncode=0,
                    stdout='''{
                  "header": {
                    "cluster_id": 2953241377848662500,
                    "member_id": 4079294043104093700,
                    "raft_term": 17
                  },
                  "members": [
                    {
                      "ID": 1301204472537744000,
                      "name": "etcd-1.1.1.1",
                      "peerURLs": [
                        "https://1.1.1.1:2380"
                      ],
                      "clientURLs": [
                        "http://1.1.1.1:2379",
                        "http://localhost:2379"
                      ]
                    }
                  ]
                }
                ''',
                    stderr="foo stderr",
                )
            elif args == member_add_args:
                return subprocess.CompletedProcess(
                    args=member_add_args,
                    returncode=0,
                    stdout="foo stdout",
                    stderr="foo stderr",
                )
            elif args == endpoint_health:
                return subprocess.CompletedProcess(
                    args=member_add_args,
                    returncode=0,
                    stdout="foo stdout",
                    stderr="foo stderr",
                )
            else:
                pytest.fail(
                    "unhandled arguments have been passed: {}".format(args))

        subprocess_mock = mock.MagicMock(side_effect=subprocess_sideeffect)
        args = Namespace(
            etcd_data_dir=str(etcdctl_path),
            zk_addr="127.0.0.1:2181",
            cluster_state_file=str(cluster_state_file),
            cluster_nodes_file=str(cluster_nodes_file),
            etcdctl_path=str(etcdctl_path),
            secure=False,
            ca_cert="",
            etcd_client_tls_cert="",
            etcd_client_tls_key="",
        )

        with contextlib.ExitStack() as stack:
            stack.enter_context(
                mock.patch('etcd_discovery.etcd_discovery.detect_ip',
                           detectip_mock))
            stack.enter_context(mock.patch('subprocess.run', subprocess_mock))
            etd.join_cluster(args)

        detectip_mock.assert_called_once()
        subprocess_mock.assert_has_calls([
            mock.call(endpoint_health,
                      stdout=subprocess.PIPE,
                      stderr=subprocess.PIPE),
            mock.call(member_list_args,
                      stdout=subprocess.PIPE,
                      stderr=subprocess.PIPE),
            mock.call(member_add_args,
                      stdout=subprocess.PIPE,
                      stderr=subprocess.PIPE),
        ],
                                         any_order=False)  # noqa: E126
        assert subprocess_mock.call_count == 3