import pytest
from pr2test.context_manager import make_test_matrix
from pr2test.marks import require_root
from pr2test.tools import interface_exists
from utils import require_user

from pyroute2 import NDB

pytestmark = [require_root()]
test_matrix = make_test_matrix(
    targets=['local', 'netns'], dbs=['sqlite3/:memory:', 'postgres/pr2test']
)


def test_multiple_sources(context):
    '''
    NDB should work with multiple netlink sources

    Check that it actually works:
    * with multiple sources of different kind
    * without the default "localhost" RTNL source
    '''
    nsname = context.new_nsname

    #
    # NB: no 'localhost' record -- important !
    sources = [
        {'target': 'localhost0', 'kind': 'local'},
        {'target': 'localhost1', 'kind': 'netns', 'netns': nsname},
        {'target': 'localhost2', 'kind': 'local'},
    ]
    ndb = None
    #
    # check that all the view has length > 0
    # that means that the sources are working
    with NDB(sources=sources) as ndb:
        assert len(list(ndb.interfaces.dump()))
        assert len(list(ndb.neighbours.dump()))
        assert len(list(ndb.addresses.dump()))
        assert len(list(ndb.routes.dump()))
    # here NDB() gets closed
    #

    #
    # the `ndb` variable still references the closed
    # NDB() object from the code block above, check
    # that all the sources are closed too
    for source in ndb.sources:
        assert ndb.sources[source].nl.closed


@pytest.mark.parametrize('context', test_matrix, indirect=True)
def test_source_localhost_restart(context):
    '''
    The database must be operational after a complete
    restart of any source.
    '''
    require_user('root')
    ifname1 = context.new_ifname
    ifname2 = context.new_ifname
    ndb = context.ndb

    #
    # check that there are existing interfaces
    # loaded into the DB
    assert len(list(ndb.interfaces.dump()))
    #
    # create a dummy interface to prove the
    # source working
    (ndb.interfaces.create(ifname=ifname1, kind='dummy', state='up').commit())
    #
    # an external check
    assert interface_exists(context.netns, ifname=ifname1, state='up')
    #
    # internal checks
    assert ifname1 in ndb.interfaces
    assert ndb.interfaces[ifname1]['state'] == 'up'
    #
    # now restart the source
    # the reason should be visible in the log
    ndb.sources['localhost'].restart(reason='test')
    #
    # the interface must be in the DB (after the
    # source restart)
    assert ifname1 in ndb.interfaces
    #
    # create another one
    (
        ndb.interfaces.create(
            ifname=ifname2, kind='dummy', state='down'
        ).commit()
    )
    #
    # check the interface both externally and internally
    assert interface_exists(context.netns, ifname=ifname2, state='down')
    assert ifname2 in ndb.interfaces
    assert ndb.interfaces[ifname2]['state'] == 'down'
    #
    # cleanup
    ndb.interfaces[ifname1].remove().commit()
    ndb.interfaces[ifname2].remove().commit()
    #
    # check
    assert not interface_exists(ifname=ifname1)
    assert not interface_exists(ifname=ifname2)


@pytest.mark.parametrize('context', test_matrix, indirect=True)
def test_source_netns_restart(context):
    '''
    Netns sources should be operational after restart as well
    '''
    require_user('root')
    nsname = context.new_nsname
    #
    # simple `context.new_ifname` returns ifname only for the main
    # netns, if we want to register the name in a netns, we should
    # use `context.register(netns=...)`
    ifname = context.register(netns=nsname)
    ndb = context.ndb

    #
    # add a netns source, the netns will be created automatically
    ndb.sources.add(netns=nsname)
    #
    # check the interfaces from the netns are loaded into the DB
    assert len(list(ndb.interfaces.dump().filter(target=nsname)))
    #
    # restart the DB
    ndb.sources[nsname].restart(reason='test')
    #
    # check the netns interfaces again
    assert len(list(ndb.interfaces.dump().filter(target=nsname)))
    #
    # create an interface in the netns
    (
        ndb.interfaces.create(
            target=nsname, ifname=ifname, kind='dummy', state='up'
        ).commit()
    )
    #
    # check the interface
    assert interface_exists(nsname, ifname=ifname)
    assert (
        ndb.interfaces[{'target': nsname, 'ifname': ifname}]['state'] == 'up'
    )
    #
    # netns will be remove automatically by the fixture as well
    # as interfaces inside the netns


def count_interfaces(ndb, target):
    return (
        ndb.schema.fetchone(
            '''
                      SELECT count(*) FROM interfaces
                      WHERE
                          f_target = '%s' AND
                          f_index != 0
                      '''
            % target
        )
    )[0]


@pytest.mark.parametrize('context', test_matrix, indirect=True)
def test_disconnect_localhost(context):
    '''
    Disconnecting the `localhost` source should not break the DB
    '''
    require_user('root')
    nsname = context.new_nsname
    localhost_ifnum = 0
    nsname_ifnum = 0
    total_ifnum = 0

    #
    # attach the NetNS source
    context.ndb.sources.add(netns=nsname)

    #
    # lock the DB
    with context.ndb.schema.readonly:
        total_ifnum = context.ndb.interfaces.dump().count()
        localhost_ifnum = (
            context.ndb.interfaces.dump().filter(target='localhost').count()
        )
        nsname_ifnum = (
            context.ndb.interfaces.dump().filter(target=nsname).count()
        )

        assert localhost_ifnum == count_interfaces(context.ndb, 'localhost')
        assert nsname_ifnum == count_interfaces(context.ndb, nsname)
        assert 0 < count_interfaces(context.ndb, 'localhost') < total_ifnum
        assert 0 < count_interfaces(context.ndb, nsname) < total_ifnum

    context.ndb.sources.remove('localhost')

    with context.ndb.schema.readonly:
        #
        # the number of 'localhost' interfaces must be 0 here
        s = len(list(context.ndb.interfaces.dump().filter(target='localhost')))
        assert s == 0
        assert count_interfaces(context.ndb, 'localhost') == 0
        #
        # the number of `nsname` interfaces must remain the same as before
        s = len(list(context.ndb.interfaces.dump().filter(target=nsname)))
        assert s > 0
        assert count_interfaces(context.ndb, nsname) == s
