Ejemplo n.º 1
0
def test_create_a_new_host_with_missing_groups(basic_host_configuration):
    """
    Tests when any of the provided groups doesn't exists
    """
    host, _, interfaces, kwargs, _ = basic_host_configuration
    groups = ["Testing Group", "Missing Group"]

    ret = {
        "changes": {},
        "comment": "Invalid group Missing Group",
        "name": "new_host",
        "result": False,
    }

    hostgroup_get_output = [
        [{"groupid": "16", "name": "Testing Group", "internal": "0", "flags": "0"}],
        False,
    ]
    host_exists_output = False
    host_create_output = "31337"

    mock_hostgroup_get = MagicMock(side_effect=hostgroup_get_output)
    mock_host_exists = MagicMock(return_value=host_exists_output)
    mock_host_create = MagicMock(return_value=host_create_output)
    with patch.dict(
        zabbix_host.__salt__,
        {
            "zabbix.hostgroup_get": mock_hostgroup_get,
            "zabbix.host_exists": mock_host_exists,
            "zabbix.host_create": mock_host_create,
        },
    ):
        assert zabbix_host.present(host, groups, interfaces, **kwargs) == ret
        assert not mock_host_create.called, "host_create should not be called"
Ejemplo n.º 2
0
def test_change_a_host_group(basic_host_configuration,
                             existing_host_responses):
    """
    Tests if the group of a host is changed when solicited
    """
    host, groups, interfaces, kwargs, _ = basic_host_configuration
    (
        host_get_output,
        hostgroup_get_output_up,
        hostinterface_get_output,
        host_inventory_get_output,
    ) = existing_host_responses

    hostgroup_get_output = [
        hostgroup_get_output_up,
        [{
            "groupid": "17",
            "name": "Actual Group",
            "internal": "0",
            "flags": "0"
        }],
    ]
    host_exists_output = True
    host_update_output = "31337"

    ret = {
        "changes": {
            "groups": "[16]"
        },
        "comment": "Host new_host updated.",
        "name": "new_host",
        "result": True,
    }

    mock_hostgroup_get = MagicMock(side_effect=hostgroup_get_output)
    mock_host_exists = MagicMock(return_value=host_exists_output)
    mock_host_get = MagicMock(return_value=host_get_output)
    mock_hostinterface_get = MagicMock(return_value=hostinterface_get_output)
    mock_host_inventory_get = MagicMock(return_value=host_inventory_get_output)
    mock_host_update = MagicMock(return_value=host_update_output)
    with patch.dict(
            zabbix_host.__salt__,
        {
            "zabbix.hostgroup_get": mock_hostgroup_get,
            "zabbix.host_exists": mock_host_exists,
            "zabbix.host_get": mock_host_get,
            "zabbix.hostinterface_get": mock_hostinterface_get,
            "zabbix.host_inventory_get": mock_host_inventory_get,
            "zabbix.host_update": mock_host_update,
        },
    ):
        assert zabbix_host.present(host, groups, interfaces, **kwargs) == ret
        mock_host_update.assert_called_with(
            "31337",
            groups=[16],
            _connection_password="******",
            _connection_url="http://XXXXXXXXX/zabbix/api_jsonrpc.php",
            _connection_user="******",
        )
Ejemplo n.º 3
0
def test_update_an_existent_host_proxy(basic_host_configuration,
                                       existing_host_responses):
    """
    Tests if the proxy of a host is updated to a new one.
    This also tests if a proxy can be added, as a host without a proxy
    have the proxy_hostid property equals zero.
    """
    host, groups, interfaces, kwargs, _ = basic_host_configuration
    (
        host_get_output,
        hostgroup_get_output,
        hostinterface_get_output,
        host_inventory_get_output,
    ) = existing_host_responses

    kwargs["proxy_host"] = 10356
    host_exists_output = True
    host_update_output = "31337"
    run_query_output = [{"proxyid": "10356"}]

    ret = {
        "changes": {
            "proxy_hostid": "10356"
        },
        "comment": "Host new_host updated.",
        "name": "new_host",
        "result": True,
    }

    mock_hostgroup_get = MagicMock(return_value=hostgroup_get_output)
    mock_host_exists = MagicMock(return_value=host_exists_output)
    mock_host_get = MagicMock(return_value=host_get_output)
    mock_hostinterface_get = MagicMock(return_value=hostinterface_get_output)
    mock_host_inventory_get = MagicMock(return_value=host_inventory_get_output)
    mock_host_update = MagicMock(return_value=host_update_output)
    mock_run_query = MagicMock(return_value=run_query_output)
    with patch.dict(
            zabbix_host.__salt__,
        {
            "zabbix.hostgroup_get": mock_hostgroup_get,
            "zabbix.host_exists": mock_host_exists,
            "zabbix.host_get": mock_host_get,
            "zabbix.hostinterface_get": mock_hostinterface_get,
            "zabbix.host_inventory_get": mock_host_inventory_get,
            "zabbix.host_update": mock_host_update,
            "zabbix.run_query": mock_run_query,
        },
    ):
        assert zabbix_host.present(host, groups, interfaces, **kwargs) == ret
        mock_host_update.assert_called_with(
            "31337",
            proxy_hostid="10356",
            _connection_password="******",
            _connection_url="http://XXXXXXXXX/zabbix/api_jsonrpc.php",
            _connection_user="******",
        )
Ejemplo n.º 4
0
def test_create_a_new_host_with_additional_parameters(
        basic_host_configuration):
    """
    Tests if additional parameters, like "description" or "inventory_mode"
    are being properly passed to host_create. Also, checks if invalid
    parameters are filtered out.
    """
    host, groups, interfaces, kwargs, ret = basic_host_configuration
    kwargs["visible_name"] = "Visible Name"
    kwargs["description"] = "An amazing test host entry"
    kwargs["not_valid_property"] = "This should be removed"
    kwargs["inventory_mode"] = "0"

    hostgroup_get_output = [{
        "groupid": "16",
        "name": "Testing Group",
        "internal": "0",
        "flags": "0"
    }]
    host_exists_output = False
    host_create_output = "31337"

    mock_hostgroup_get = MagicMock(return_value=hostgroup_get_output)
    mock_host_exists = MagicMock(return_value=host_exists_output)
    mock_host_create = MagicMock(return_value=host_create_output)
    with patch.dict(
            zabbix_host.__salt__,
        {
            "zabbix.hostgroup_get": mock_hostgroup_get,
            "zabbix.host_exists": mock_host_exists,
            "zabbix.host_create": mock_host_create,
        },
    ):
        assert zabbix_host.present(host, groups, interfaces, **kwargs) == ret
        mock_host_create.assert_called_with(
            "new_host",
            [16],
            [{
                "type": "1",
                "main": "1",
                "useip": "1",
                "ip": "127.0.0.1",
                "dns": "basic_interface",
                "port": "10050",
                "details": [],
            }],
            _connection_password="******",
            _connection_url="http://XXXXXXXXX/zabbix/api_jsonrpc.php",
            _connection_user="******",
            description="An amazing test host entry",
            inventory={},
            inventory_mode="0",
            proxy_hostid="0",
            visible_name="Visible Name",
        )
Ejemplo n.º 5
0
def test_create_a_new_host_with_multiple_groups(basic_host_configuration):
    """
    This test creates a host with multiple groups, mixing names and IDs.
    """
    host, _, interfaces, kwargs, ret = basic_host_configuration
    groups = ["Testing Group", 15, "Tested Group"]

    hostgroup_get_output = [
        [{
            "groupid": "16",
            "name": "Testing Group",
            "internal": "0",
            "flags": "0"
        }],
        [{
            "groupid": "17",
            "name": "Tested Group",
            "internal": "0",
            "flags": "0"
        }],
    ]
    host_exists_output = False
    host_create_output = "31337"

    mock_hostgroup_get = MagicMock(side_effect=hostgroup_get_output)
    mock_host_exists = MagicMock(return_value=host_exists_output)
    mock_host_create = MagicMock(return_value=host_create_output)
    with patch.dict(
            zabbix_host.__salt__,
        {
            "zabbix.hostgroup_get": mock_hostgroup_get,
            "zabbix.host_exists": mock_host_exists,
            "zabbix.host_create": mock_host_create,
        },
    ):
        assert zabbix_host.present(host, groups, interfaces, **kwargs) == ret
        mock_host_create.assert_called_with(
            "new_host",
            [16, 15, 17],
            [{
                "type": "1",
                "main": "1",
                "useip": "1",
                "ip": "127.0.0.1",
                "dns": "basic_interface",
                "port": "10050",
                "details": [],
            }],
            _connection_password="******",
            _connection_url="http://XXXXXXXXX/zabbix/api_jsonrpc.php",
            _connection_user="******",
            inventory={},
            proxy_hostid="0",
            visible_name=None,
        )
Ejemplo n.º 6
0
def test_ensure_nothing_happens_when_host_is_in_desired_state(
        basic_host_configuration, existing_host_responses):
    """
    Test to ensure that nothing happens when the state applied
    already corresponds to the host actual configuration.
    """
    host, groups, interfaces, kwargs, _ = basic_host_configuration
    (
        host_get_output,
        hostgroup_get_output_up,
        hostinterface_get_output,
        host_inventory_get_output,
    ) = existing_host_responses

    hostgroup_get_output = [
        [{
            "groupid": "16",
            "name": "Testing Group",
            "internal": "0",
            "flags": "0"
        }],
        hostgroup_get_output_up,
    ]
    host_exists_output = True
    host_create_output = "31337"

    ret = {
        "changes": {},
        "comment": "Host new_host already exists.",
        "name": "new_host",
        "result": True,
    }

    mock_hostgroup_get = MagicMock(side_effect=hostgroup_get_output)
    mock_host_exists = MagicMock(return_value=host_exists_output)
    mock_host_get = MagicMock(return_value=host_get_output)
    mock_hostinterface_get = MagicMock(return_value=hostinterface_get_output)
    mock_host_inventory_get = MagicMock(return_value=host_inventory_get_output)
    mock_host_update = MagicMock(return_value=False)
    with patch.dict(
            zabbix_host.__salt__,
        {
            "zabbix.hostgroup_get": mock_hostgroup_get,
            "zabbix.host_exists": mock_host_exists,
            "zabbix.host_get": mock_host_get,
            "zabbix.hostinterface_get": mock_hostinterface_get,
            "zabbix.host_inventory_get": mock_host_inventory_get,
            "zabbix.host_update": mock_host_update,
        },
    ):
        assert zabbix_host.present(host, groups, interfaces, **kwargs) == ret
        assert not mock_host_update.called, "host_update should not be called"
Ejemplo n.º 7
0
def test_create_a_basic_new_host(basic_host_configuration):
    """
    This test creates a host with the minimum required parameters:

    host, groups, interfaces and _connection_args

    The "groups" should be converted to their numeric IDs and the
    hidden mandatory fields of interfaces should be populated.
    """
    host, groups, interfaces, kwargs, ret = basic_host_configuration

    hostgroup_get_output = [{
        "groupid": "16",
        "name": "Testing Group",
        "internal": "0",
        "flags": "0"
    }]
    host_exists_output = False
    host_create_output = "31337"

    mock_hostgroup_get = MagicMock(return_value=hostgroup_get_output)
    mock_host_exists = MagicMock(return_value=host_exists_output)
    mock_host_create = MagicMock(return_value=host_create_output)
    with patch.dict(
            zabbix_host.__salt__,
        {
            "zabbix.hostgroup_get": mock_hostgroup_get,
            "zabbix.host_exists": mock_host_exists,
            "zabbix.host_create": mock_host_create,
        },
    ):
        assert zabbix_host.present(host, groups, interfaces, **kwargs) == ret
        mock_host_create.assert_called_with(
            "new_host",
            [16],
            [{
                "type": "1",
                "main": "1",
                "useip": "1",
                "ip": "127.0.0.1",
                "dns": "basic_interface",
                "port": "10050",
                "details": [],
            }],
            _connection_password="******",
            _connection_url="http://XXXXXXXXX/zabbix/api_jsonrpc.php",
            _connection_user="******",
            inventory={},
            proxy_hostid="0",
            visible_name=None,
        )
Ejemplo n.º 8
0
def test_create_a_new_host_with_proxy_by_id(basic_host_configuration):
    """
    Test the handling of proxy_host parameter when it is a proxyid
    """
    host, groups, interfaces, kwargs, ret = basic_host_configuration
    kwargs["proxy_host"] = 10356

    hostgroup_get_output = [{
        "groupid": "16",
        "name": "Testing Group",
        "internal": "0",
        "flags": "0"
    }]
    host_exists_output = False
    host_create_output = "31337"
    run_query_output = [{"proxyid": "10356"}]

    mock_hostgroup_get = MagicMock(return_value=hostgroup_get_output)
    mock_host_exists = MagicMock(return_value=host_exists_output)
    mock_host_create = MagicMock(return_value=host_create_output)
    mock_run_query = MagicMock(return_value=run_query_output)
    with patch.dict(
            zabbix_host.__salt__,
        {
            "zabbix.hostgroup_get": mock_hostgroup_get,
            "zabbix.host_exists": mock_host_exists,
            "zabbix.host_create": mock_host_create,
            "zabbix.run_query": mock_run_query,
        },
    ):
        assert zabbix_host.present(host, groups, interfaces, **kwargs) == ret
        mock_host_create.assert_called_with(
            "new_host",
            [16],
            [{
                "type": "1",
                "main": "1",
                "useip": "1",
                "ip": "127.0.0.1",
                "dns": "basic_interface",
                "port": "10050",
                "details": [],
            }],
            _connection_password="******",
            _connection_url="http://XXXXXXXXX/zabbix/api_jsonrpc.php",
            _connection_user="******",
            inventory={},
            proxy_hostid="10356",
            visible_name=None,
        )
Ejemplo n.º 9
0
def test_create_a_new_host_with_missing_proxy(basic_host_configuration):
    """
    Tests when the given proxy_host doesn't exists
    """
    host, groups, interfaces, kwargs, _ = basic_host_configuration
    kwargs["proxy_host"] = 10356

    ret = {
        "changes": {},
        "comment": "Invalid proxy_host 10356",
        "name": "new_host",
        "result": False,
    }

    hostgroup_get_output = [{
        "groupid": "16",
        "name": "Testing Group",
        "internal": "0",
        "flags": "0"
    }]
    host_exists_output = False
    host_create_output = "31337"
    run_query_output = False

    mock_hostgroup_get = MagicMock(return_value=hostgroup_get_output)
    mock_host_exists = MagicMock(return_value=host_exists_output)
    mock_run_query = MagicMock(return_value=run_query_output)
    mock_host_create = MagicMock(return_value=host_create_output)
    with patch.dict(
            zabbix_host.__salt__,
        {
            "zabbix.hostgroup_get": mock_hostgroup_get,
            "zabbix.host_exists": mock_host_exists,
            "zabbix.run_query": mock_run_query,
            "zabbix.host_create": mock_host_create,
        },
    ):
        assert zabbix_host.present(host, groups, interfaces, **kwargs) == ret
        assert not mock_host_create.called, "host_create should not be called"
Ejemplo n.º 10
0
def test_add_a_new_hostinterface(basic_host_configuration, existing_host_responses):
    """
    Tests the update of a current and creation of a new hostinterface
    of a host.
    """
    host, groups, _, kwargs, _ = basic_host_configuration
    (
        host_get_output,
        hostgroup_get_output,
        hostinterface_get_output,
        host_inventory_get_output,
    ) = existing_host_responses

    interfaces = [
        OrderedDict(
            [
                (
                    "basic_interface",
                    [
                        OrderedDict([("dns", "new_host")]),
                        OrderedDict([("type", "agent")]),
                        OrderedDict([("useip", "0")]),
                    ],
                ),
                (
                    "ipmi_interface",
                    [
                        OrderedDict([("ip", "127.0.0.1")]),
                        OrderedDict([("dns", "new_host")]),
                        OrderedDict([("useip", False)]),
                        OrderedDict([("type", "ipmi")]),
                    ],
                ),
            ]
        )
    ]
    host_exists_output = True
    hostinterface_update_output = "29"
    hostinterface_create_output = "30"

    ret = {
        "changes": {
            "interfaces": (
                "[{'type': '1', 'main': '1', 'useip': '0', 'ip': '', 'dns': 'new_host',"
                " 'port': '10050', 'details': []}, {'type': '3', 'main': '1', 'useip':"
                " '0', 'ip': '127.0.0.1', 'dns': 'new_host', 'port': '623', 'details':"
                " []}]"
            )
        },
        "comment": "Host new_host updated.",
        "name": "new_host",
        "result": True,
    }

    mock_hostgroup_get = MagicMock(return_value=hostgroup_get_output)
    mock_host_exists = MagicMock(return_value=host_exists_output)
    mock_host_get = MagicMock(return_value=host_get_output)
    mock_hostinterface_get = MagicMock(return_value=hostinterface_get_output)
    mock_host_inventory_get = MagicMock(return_value=host_inventory_get_output)
    mock_hostinterface_update = MagicMock(return_value=hostinterface_update_output)
    mock_hostinterface_create = MagicMock(return_value=hostinterface_create_output)
    with patch.dict(
        zabbix_host.__salt__,
        {
            "zabbix.hostgroup_get": mock_hostgroup_get,
            "zabbix.host_exists": mock_host_exists,
            "zabbix.host_get": mock_host_get,
            "zabbix.hostinterface_get": mock_hostinterface_get,
            "zabbix.host_inventory_get": mock_host_inventory_get,
            "zabbix.hostinterface_update": mock_hostinterface_update,
            "zabbix.hostinterface_create": mock_hostinterface_create,
        },
    ):
        # Blame Python 3.5 support for all this black magic
        host_present_ret = zabbix_host.present(host, groups, interfaces, **kwargs)
        for interface in ast.literal_eval(host_present_ret["changes"]["interfaces"]):
            if interface["type"] == "1":
                assert interface == ast.literal_eval(ret["changes"]["interfaces"])[0]
            elif interface["type"] == "3":
                assert interface == ast.literal_eval(ret["changes"]["interfaces"])[1]
            else:
                assert interface["type"] == "Should be 1 or 3"
        assert host_present_ret["comment"] == "Host new_host updated."
        assert host_present_ret["name"] == "new_host"
        assert host_present_ret["result"] is True
        # When Python 3.5 is gone, the following line does the job:
        # assert zabbix_host.present(host, groups, interfaces, **kwargs) == ret
        mock_hostinterface_update.assert_called_with(
            interfaceid="29",
            ip="",
            dns="new_host",
            useip="0",
            type="1",
            main="1",
            port="10050",
            details=[],
            _connection_password="******",
            _connection_url="http://XXXXXXXXX/zabbix/api_jsonrpc.php",
            _connection_user="******",
        )
        mock_hostinterface_create.assert_called_with(
            "31337",
            "127.0.0.1",
            dns="new_host",
            useip="0",
            if_type="3",
            main="1",
            port="623",
            details=[],
            _connection_password="******",
            _connection_url="http://XXXXXXXXX/zabbix/api_jsonrpc.php",
            _connection_user="******",
        )
Ejemplo n.º 11
0
def test_update_a_host_with_additional_parameters(
    basic_host_configuration, existing_host_responses
):
    """
    This test checks if additional parameters can be added to an
    existing host
    """
    host, groups, interfaces, kwargs, _ = basic_host_configuration
    (
        host_get_output,
        hostgroup_get_output,
        hostinterface_get_output,
        host_inventory_get_output,
    ) = existing_host_responses

    kwargs["inventory_mode"] = 0
    kwargs["description"] = "An amazing test host entry"
    host_exists_output = True
    host_update_output = "31337"

    ret = {
        "changes": {
            "host": "{'description': 'An amazing test host entry', 'inventory_mode': 0}"
        },
        "comment": "Host new_host updated.",
        "name": "new_host",
        "result": True,
    }

    mock_hostgroup_get = MagicMock(return_value=hostgroup_get_output)
    mock_host_exists = MagicMock(return_value=host_exists_output)
    mock_host_get = MagicMock(return_value=host_get_output)
    mock_hostinterface_get = MagicMock(return_value=hostinterface_get_output)
    mock_host_inventory_get = MagicMock(return_value=host_inventory_get_output)
    mock_host_update = MagicMock(return_value=host_update_output)
    with patch.dict(
        zabbix_host.__salt__,
        {
            "zabbix.hostgroup_get": mock_hostgroup_get,
            "zabbix.host_exists": mock_host_exists,
            "zabbix.host_get": mock_host_get,
            "zabbix.hostinterface_get": mock_hostinterface_get,
            "zabbix.host_inventory_get": mock_host_inventory_get,
            "zabbix.host_update": mock_host_update,
        },
    ):
        # Blame Python 3.5 support for all this black magic
        host_present_ret = zabbix_host.present(host, groups, interfaces, **kwargs)
        host_present_changes = ast.literal_eval(host_present_ret["changes"]["host"])
        assert host_present_changes == ast.literal_eval(ret["changes"]["host"])
        assert host_present_ret["comment"] == "Host new_host updated."
        assert host_present_ret["name"] == "new_host"
        assert host_present_ret["result"] is True
        # When Python 3.5 is gone, the following line does the job:
        # assert zabbix_host.present(host, groups, interfaces, **kwargs) == ret
        mock_host_update.assert_called_with(
            "31337",
            _connection_password="******",
            _connection_url="http://XXXXXXXXX/zabbix/api_jsonrpc.php",
            _connection_user="******",
            description="An amazing test host entry",
            inventory_mode=0,
        )
Ejemplo n.º 12
0
def test_create_a_new_host_with_multiple_interfaces(basic_host_configuration):
    """
    Tests the creation of a host with multiple interfaces. This creates
    one interface of each type, which needs to have their default
    parameters filled. Also, tests the different dns, ip and useip
    combinations.
    """
    host, groups, _, kwargs, ret = basic_host_configuration
    interfaces = [
        OrderedDict(
            [
                (
                    "agent_interface",
                    [
                        OrderedDict([("dns", "new_host")]),
                        OrderedDict([("type", "agent")]),
                    ],
                ),
                (
                    "snmp_interface",
                    [
                        OrderedDict([("ip", "127.0.0.1")]),
                        OrderedDict([("dns", "new_host")]),
                        OrderedDict([("useip", False)]),
                        OrderedDict([("type", "snmp")]),
                    ],
                ),
                (
                    "ipmi_interface",
                    [
                        OrderedDict([("ip", "127.0.0.1")]),
                        OrderedDict([("dns", "new_host")]),
                        OrderedDict([("type", "ipmi")]),
                    ],
                ),
                (
                    "jmx_interface",
                    [
                        OrderedDict([("ip", "127.0.0.1")]),
                        OrderedDict([("dns", "new_host")]),
                        OrderedDict([("useip", True)]),
                        OrderedDict([("type", "jmx")]),
                    ],
                ),
            ]
        )
    ]

    hostgroup_get_output = [
        [{"groupid": "16", "name": "Testing Group", "internal": "0", "flags": "0"}],
        [{"groupid": "17", "name": "Tested Group", "internal": "0", "flags": "0"}],
    ]
    host_exists_output = False
    host_create_output = "31337"

    mock_hostgroup_get = MagicMock(side_effect=hostgroup_get_output)
    mock_host_exists = MagicMock(return_value=host_exists_output)
    mock_host_create = MagicMock(return_value=host_create_output)
    with patch.dict(
        zabbix_host.__salt__,
        {
            "zabbix.hostgroup_get": mock_hostgroup_get,
            "zabbix.host_exists": mock_host_exists,
            "zabbix.host_create": mock_host_create,
        },
    ):
        assert zabbix_host.present(host, groups, interfaces, **kwargs) == ret
        # Blame Python 3.5 for this:
        host_create_call = mock_host_create.call_args[0]
        assert host_create_call[0] == "new_host"
        assert host_create_call[1] == [16]
        for interface in host_create_call[2]:
            if interface["type"] == "1":
                assert interface == {
                    "type": "1",
                    "main": "1",
                    "useip": "1",
                    "ip": "",
                    "dns": "new_host",
                    "port": "10050",
                    "details": [],
                }
            elif interface["type"] == "2":
                assert interface == {
                    "type": "2",
                    "main": "1",
                    "useip": "0",
                    "ip": "127.0.0.1",
                    "dns": "new_host",
                    "port": "161",
                    "details": {
                        "version": "2",
                        "bulk": "1",
                        "community": "{$SNMP_COMMUNITY}",
                    },
                }
            elif interface["type"] == "3":
                assert interface == {
                    "type": "3",
                    "main": "1",
                    "useip": "1",
                    "ip": "127.0.0.1",
                    "dns": "new_host",
                    "port": "623",
                    "details": [],
                }
            elif interface["type"] == "4":
                assert interface == {
                    "type": "4",
                    "main": "1",
                    "useip": "1",
                    "ip": "127.0.0.1",
                    "dns": "new_host",
                    "port": "12345",
                    "details": [],
                }
            else:
                assert interface["type"] == "Should be 1, 2, 3 or 4"