pytest-netconf


Namepytest-netconf JSON
Version 0.1.0 PyPI version JSON
download
home_pageNone
SummaryA pytest plugin that provides a mock NETCONF (RFC6241/RFC6242) server for local testing.
upload_time2024-08-08 09:55:04
maintainerNone
docs_urlNone
authorAdam Kirchberger
requires_python<4.0,>=3.8
licenseApache-2.0
keywords netconf network automation network engineering network testing
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # pytest-netconf

![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/nomios-opensource/pytest-netconf/publish.yml)
![Codecov](https://img.shields.io/codecov/c/github/nomios-opensource/pytest-netconf)  
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pytest-netconf)
![PyPI - Downloads](https://img.shields.io/pypi/dm/pytest-netconf)
![GitHub License](https://img.shields.io/github/license/nomios-opensource/pytest-netconf)

A pytest plugin that provides a mock NETCONF (RFC6241/RFC6242) server for local testing. 

`pytest-netconf` is authored by [Adam Kirchberger](https://github.com/adamkirchberger), governed as a [benevolent dictatorship](CODE_OF_CONDUCT.md), and distributed under [license](LICENSE).

## Introduction

Testing NETCONF devices has traditionally required maintaining labs with multiple vendor devices which can be complex and resource-intensive. Additionally, spinning up virtual devices for testing purposes is often time-consuming and too slow for CICD pipelines. This plugin provides a convenient way to mock the behavior and responses of these NETCONF devices.

## Features

- **NETCONF server**, a real SSH server is run locally which enables testing using actual network connections instead of patching.
- **Predefined requests and responses**, define specific NETCONF requests and responses to meet your testing needs.
- **Capability testing**, define specific capabilities you want the server to support and test their responses.
- **Authentication testing**, test error handling for authentication issues (supports password or key auth).
- **Connection testing**, test error handling when tearing down connections unexpectedly.

## NETCONF Clients

The clients below have been tested

- `ncclient` :white_check_mark:
- `netconf-client` :white_check_mark:
- `scrapli-netconf` :white_check_mark:

## Quickstart

The plugin will install a pytest fixture named `netconf_server`, which will start an SSH server with settings you provide, and **only** reply to requests which you define with corresponding responses.

For more use cases see [examples](#examples)


```python
# Configure server settings
netconf_server.username = None  # allow any username
netconf_server.password = None  # allow any password
netconf_server.port = 8830  # default value

# Configure a request and response
netconf_server.expect_request(
        '<?xml version="1.0" encoding="UTF-8"?>'
        '<nc:rpc xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="{message_id}">'
        "<nc:get-config><nc:source><nc:running/></nc:source></nc:get-config>"
        "</nc:rpc>"
    ).respond_with(
        """
        <?xml version="1.0" encoding="UTF-8"?>
        <rpc-reply message-id="{message_id}"
          xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
            <data>
                <interfaces>
                    <interface>
                        <name>eth0</name>
                    </interface>
                </interfaces>
            </data>
        </rpc-reply>
        """
    )
```

## Examples

<details>
<summary>Get Config</summary>
<br>

```python
from pytest_netconf import NetconfServer
from ncclient import manager


def test_netconf_get_config(
    netconf_server: NetconfServer,
):
    # GIVEN server request and response
    netconf_server.expect_request(
        '<?xml version="1.0" encoding="UTF-8"?>'
        '<nc:rpc xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="{message_id}">'
        "<nc:get-config><nc:source><nc:running/></nc:source></nc:get-config>"
        "</nc:rpc>"
    ).respond_with(
        """
        <?xml version="1.0" encoding="UTF-8"?>
        <rpc-reply message-id="{message_id}"
          xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
            <data>
                <interfaces>
                    <interface>
                        <name>eth0</name>
                    </interface>
                </interfaces>
            </data>
        </rpc-reply>"""
    )

    # WHEN fetching rpc response from server
    with manager.connect(
        host="localhost",
        port=8830,
        username="admin",
        password="admin",
        hostkey_verify=False,
    ) as m:
        response = m.get_config(source="running").data_xml

    # THEN expect response
    assert (
        """
                <interfaces>
                    <interface>
                        <name>eth0</name>
                    </interface>
                </interfaces>
        """
        in response
    )
```
</details>

<details>
<summary>Authentication Fail</summary>
<br>

```python
from pytest_netconf import NetconfServer
from ncclient import manager
from ncclient.transport.errors import AuthenticationError


def test_netconf_auth_fail(
    netconf_server: NetconfServer,
):
    # GIVEN username and password have been defined
    netconf_server.username = "admin"
    netconf_server.password = "password"

    # WHEN connecting using wrong credentials
    with pytest.raises(AuthenticationError) as error:
        with manager.connect(
            host="localhost",
            port=8830,
            username="foo",
            password="bar",
            hostkey_verify=False,
        ):
            ...

    # THEN expect error
    assert error
```
</details>

<details>
<summary>Custom Capabilities</summary>
<br>

```python
from pytest_netconf import NetconfServer
from ncclient import manager


def test_netconf_capabilities(
    netconf_server: NetconfServer,
):
    # GIVEN extra capabilities
    netconf_server.capabilities.append("urn:ietf:params:netconf:capability:foo:1.1")
    netconf_server.capabilities.append("urn:ietf:params:netconf:capability:bar:1.1")

    # WHEN receiving server capabilities
    with manager.connect(
        host="localhost",
        port=8830,
        username="admin",
        password="admin",
        hostkey_verify=False,
    ) as m:
        server_capabilities = m.server_capabilities

    # THEN expect to see capabilities
    assert "urn:ietf:params:netconf:capability:foo:1.1" in server_capabilities
    assert "urn:ietf:params:netconf:capability:bar:1.1" in server_capabilities
```
</details>

<details>
<summary>Server Disconnect</summary>
<br>

```python
from pytest_netconf import NetconfServer
from ncclient import manager
from ncclient.transport.errors import TransportError


def test_netconf_server_disconnect(
    netconf_server: NetconfServer,
):
    # GIVEN netconf connection
    with pytest.raises(TransportError) as error:
        with manager.connect(
            host="localhost",
            port=8830,
            username="admin",
            password="admin",
            hostkey_verify=False,
        ) as m:
            pass
            # WHEN server stops
            netconf_server.stop()

    # THEN expect error
    assert str(error.value) == "Not connected to NETCONF server"
```
</details>

<details>
<summary>Key Auth</summary>
<br>

```python
from pytest_netconf import NetconfServer
from ncclient import manager


def test_netconf_key_auth(
    netconf_server: NetconfServer,
):
    # GIVEN SSH username and authorized key
    netconf_server.username = "admin"
    netconf_server.authorized_key = "ssh-rsa AAAAB3NzaC1yc..."

    # WHEN connecting using key credentials
    with manager.connect(
        host="localhost",
        port=8830,
        username="admin",
        key_filename=key_filepath,
        hostkey_verify=False,
    ) as m:
        # THEN expect to be connected
        assert m.connected
```
</details>


## Versioning

Releases will follow semantic versioning (major.minor.patch). Before 1.0.0 breaking changes can be included in a minor release, therefore we highly recommend pinning this package.

## Contributing

Suggest a [feature]() or report a [bug](). Read our developer [guide](CONTRIBUTING.md).

## License

pytest-netconf is distributed under the Apache 2.0 [license](LICENSE).


            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "pytest-netconf",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.8",
    "maintainer_email": null,
    "keywords": "Netconf, Network automation, Network engineering, Network testing",
    "author": "Adam Kirchberger",
    "author_email": "adam.kirchberger@nomios.co.uk",
    "download_url": null,
    "platform": null,
    "description": "# pytest-netconf\n\n![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/nomios-opensource/pytest-netconf/publish.yml)\n![Codecov](https://img.shields.io/codecov/c/github/nomios-opensource/pytest-netconf)  \n![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pytest-netconf)\n![PyPI - Downloads](https://img.shields.io/pypi/dm/pytest-netconf)\n![GitHub License](https://img.shields.io/github/license/nomios-opensource/pytest-netconf)\n\nA pytest plugin that provides a mock NETCONF (RFC6241/RFC6242) server for local testing. \n\n`pytest-netconf` is authored by [Adam Kirchberger](https://github.com/adamkirchberger), governed as a [benevolent dictatorship](CODE_OF_CONDUCT.md), and distributed under [license](LICENSE).\n\n## Introduction\n\nTesting NETCONF devices has traditionally required maintaining labs with multiple vendor devices which can be complex and resource-intensive. Additionally, spinning up virtual devices for testing purposes is often time-consuming and too slow for CICD pipelines. This plugin provides a convenient way to mock the behavior and responses of these NETCONF devices.\n\n## Features\n\n- **NETCONF server**, a real SSH server is run locally which enables testing using actual network connections instead of patching.\n- **Predefined requests and responses**, define specific NETCONF requests and responses to meet your testing needs.\n- **Capability testing**, define specific capabilities you want the server to support and test their responses.\n- **Authentication testing**, test error handling for authentication issues (supports password or key auth).\n- **Connection testing**, test error handling when tearing down connections unexpectedly.\n\n## NETCONF Clients\n\nThe clients below have been tested\n\n- `ncclient` :white_check_mark:\n- `netconf-client` :white_check_mark:\n- `scrapli-netconf` :white_check_mark:\n\n## Quickstart\n\nThe plugin will install a pytest fixture named `netconf_server`, which will start an SSH server with settings you provide, and **only** reply to requests which you define with corresponding responses.\n\nFor more use cases see [examples](#examples)\n\n\n```python\n#\u00a0Configure server settings\nnetconf_server.username = None  #\u00a0allow any username\nnetconf_server.password = None  # allow any password\nnetconf_server.port = 8830  #\u00a0default value\n\n#\u00a0Configure a request and response\nnetconf_server.expect_request(\n        '<?xml version=\"1.0\" encoding=\"UTF-8\"?>'\n        '<nc:rpc xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\" message-id=\"{message_id}\">'\n        \"<nc:get-config><nc:source><nc:running/></nc:source></nc:get-config>\"\n        \"</nc:rpc>\"\n    ).respond_with(\n        \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <rpc-reply message-id=\"{message_id}\"\n          xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n            <data>\n                <interfaces>\n                    <interface>\n                        <name>eth0</name>\n                    </interface>\n                </interfaces>\n            </data>\n        </rpc-reply>\n        \"\"\"\n    )\n```\n\n## Examples\n\n<details>\n<summary>Get Config</summary>\n<br>\n\n```python\nfrom pytest_netconf import NetconfServer\nfrom ncclient import manager\n\n\ndef test_netconf_get_config(\n    netconf_server: NetconfServer,\n):\n    # GIVEN server request and response\n    netconf_server.expect_request(\n        '<?xml version=\"1.0\" encoding=\"UTF-8\"?>'\n        '<nc:rpc xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\" message-id=\"{message_id}\">'\n        \"<nc:get-config><nc:source><nc:running/></nc:source></nc:get-config>\"\n        \"</nc:rpc>\"\n    ).respond_with(\n        \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <rpc-reply message-id=\"{message_id}\"\n          xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n            <data>\n                <interfaces>\n                    <interface>\n                        <name>eth0</name>\n                    </interface>\n                </interfaces>\n            </data>\n        </rpc-reply>\"\"\"\n    )\n\n    # WHEN fetching rpc response from server\n    with manager.connect(\n        host=\"localhost\",\n        port=8830,\n        username=\"admin\",\n        password=\"admin\",\n        hostkey_verify=False,\n    ) as m:\n        response = m.get_config(source=\"running\").data_xml\n\n    # THEN expect response\n    assert (\n        \"\"\"\n                <interfaces>\n                    <interface>\n                        <name>eth0</name>\n                    </interface>\n                </interfaces>\n        \"\"\"\n        in response\n    )\n```\n</details>\n\n<details>\n<summary>Authentication Fail</summary>\n<br>\n\n```python\nfrom pytest_netconf import NetconfServer\nfrom ncclient import manager\nfrom ncclient.transport.errors import AuthenticationError\n\n\ndef test_netconf_auth_fail(\n    netconf_server: NetconfServer,\n):\n    # GIVEN username and password have been defined\n    netconf_server.username = \"admin\"\n    netconf_server.password = \"password\"\n\n    # WHEN connecting using wrong credentials\n    with pytest.raises(AuthenticationError) as error:\n        with manager.connect(\n            host=\"localhost\",\n            port=8830,\n            username=\"foo\",\n            password=\"bar\",\n            hostkey_verify=False,\n        ):\n            ...\n\n    # THEN expect error\n    assert error\n```\n</details>\n\n<details>\n<summary>Custom Capabilities</summary>\n<br>\n\n```python\nfrom pytest_netconf import NetconfServer\nfrom ncclient import manager\n\n\ndef test_netconf_capabilities(\n    netconf_server: NetconfServer,\n):\n    # GIVEN extra capabilities\n    netconf_server.capabilities.append(\"urn:ietf:params:netconf:capability:foo:1.1\")\n    netconf_server.capabilities.append(\"urn:ietf:params:netconf:capability:bar:1.1\")\n\n    # WHEN receiving server capabilities\n    with manager.connect(\n        host=\"localhost\",\n        port=8830,\n        username=\"admin\",\n        password=\"admin\",\n        hostkey_verify=False,\n    ) as m:\n        server_capabilities = m.server_capabilities\n\n    # THEN expect to see capabilities\n    assert \"urn:ietf:params:netconf:capability:foo:1.1\" in server_capabilities\n    assert \"urn:ietf:params:netconf:capability:bar:1.1\" in server_capabilities\n```\n</details>\n\n<details>\n<summary>Server Disconnect</summary>\n<br>\n\n```python\nfrom pytest_netconf import NetconfServer\nfrom ncclient import manager\nfrom ncclient.transport.errors import TransportError\n\n\ndef test_netconf_server_disconnect(\n    netconf_server: NetconfServer,\n):\n    # GIVEN netconf connection\n    with pytest.raises(TransportError) as error:\n        with manager.connect(\n            host=\"localhost\",\n            port=8830,\n            username=\"admin\",\n            password=\"admin\",\n            hostkey_verify=False,\n        ) as m:\n            pass\n            # WHEN server stops\n            netconf_server.stop()\n\n    # THEN expect error\n    assert str(error.value) == \"Not connected to NETCONF server\"\n```\n</details>\n\n<details>\n<summary>Key Auth</summary>\n<br>\n\n```python\nfrom pytest_netconf import NetconfServer\nfrom ncclient import manager\n\n\ndef test_netconf_key_auth(\n    netconf_server: NetconfServer,\n):\n    # GIVEN SSH username and authorized key\n    netconf_server.username = \"admin\"\n    netconf_server.authorized_key = \"ssh-rsa AAAAB3NzaC1yc...\"\n\n    # WHEN connecting using key credentials\n    with manager.connect(\n        host=\"localhost\",\n        port=8830,\n        username=\"admin\",\n        key_filename=key_filepath,\n        hostkey_verify=False,\n    ) as m:\n        # THEN expect to be connected\n        assert m.connected\n```\n</details>\n\n\n## Versioning\n\nReleases will follow semantic versioning (major.minor.patch). Before 1.0.0 breaking changes can be included in a minor release, therefore we highly recommend pinning this package.\n\n## Contributing\n\nSuggest a [feature]() or report a [bug](). Read our developer [guide](CONTRIBUTING.md).\n\n## License\n\npytest-netconf is distributed under the Apache 2.0 [license](LICENSE).\n\n",
    "bugtrack_url": null,
    "license": "Apache-2.0",
    "summary": "A pytest plugin that provides a mock NETCONF (RFC6241/RFC6242) server for local testing.",
    "version": "0.1.0",
    "project_urls": null,
    "split_keywords": [
        "netconf",
        " network automation",
        " network engineering",
        " network testing"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "139d04ff62a17289763b87957ff6dc08bbc79930c12493df964e78f490999cf3",
                "md5": "7888f0ee91adad543aad75e287e0f558",
                "sha256": "8b91ce02b17b6058dfbb17e3d5532677e55b16fb01f40ff6a988a72d00f487ba"
            },
            "downloads": -1,
            "filename": "pytest_netconf-0.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "7888f0ee91adad543aad75e287e0f558",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.8",
            "size": 17813,
            "upload_time": "2024-08-08T09:55:04",
            "upload_time_iso_8601": "2024-08-08T09:55:04.278533Z",
            "url": "https://files.pythonhosted.org/packages/13/9d/04ff62a17289763b87957ff6dc08bbc79930c12493df964e78f490999cf3/pytest_netconf-0.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-08-08 09:55:04",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "pytest-netconf"
}
        
Elapsed time: 1.60461s