# Copyright 2015 Cisco Systems, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import print_function
from future import standard_library
standard_library.install_aliases()
from builtins import str
from builtins import range
from builtins import object

import http.client
import os
import pytest
import random
import string
import time
import xml.etree.ElementTree as ET
import logging

from cobra.internal.codec.jsoncodec import toJSONStr, fromJSONStr
from cobra.internal.codec.xmlcodec import toXMLStr, fromXMLStr

import cobra.mit.access
import cobra.mit.request
import cobra.mit.session
cobra = pytest.importorskip("cobra")
cobra.model = pytest.importorskip("cobra.model")
cobra.model.fv = pytest.importorskip("cobra.model.fv")
import cobra.model.pol
import cobra.model.infra
import cobra.services

pytestmark = pytest.mark.skipif(pytest.config.getvalue('apic') == [],
                                reason="You must specify at least one --apic " +
                                       "option on the CLI")
slow = pytest.mark.slow

http.client.HTTPConnection.debuglevel = 1
logging.basicConfig(level=logging.DEBUG)

fakeDevicePackageZip = 'Archive.zip'
realDevicePackageZip = 'asa-device-pkg.zip'


@pytest.fixture(params=pytest.config.getvalue('apic'))
def moDir(request):
    url, user, password, secure = request.param
    secure = False if secure == 'False' else True
    session = cobra.mit.session.LoginSession(url, user, password,
                                             secure=secure)
    md = cobra.mit.access.MoDirectory(session)
    md.login()
    return md


class Test_rest_configrequest(object):

    def test_createtenant(self, moDir, tenantname):
        """
        create a tenant and commit it
        """
        dcid = str(time.time()).replace('.', '')

        polUni = cobra.model.pol.Uni('')
        tenant = cobra.model.fv.Tenant(polUni, tenantname[0])

        configRequest = cobra.mit.request.ConfigRequest()
        configRequest.addMo(tenant)
        configRequest.subtree = 'full'
        configRequest.id = dcid

        mos = moDir.commit(configRequest)
        assert mos

        mo = mos[0]
        assert len(mos) > 0
        assert str(mo.dn) == str(tenant.dn)
        assert len(list(mo.children)) >= 1

    def test_lookupcreatedtenant(self, moDir, tenantname):

        tenant = moDir.lookupByDn('uni/tn-{0}'.format(tenantname[0]))
        assert tenant

    def test_deletetenant(self, moDir, tenantname):
        tenant = moDir.lookupByDn('uni/tn-{0}'.format(tenantname[0]))

        tenant.delete()
        configRequest = cobra.mit.request.ConfigRequest()
        configRequest.addMo(tenant)

        r = moDir.commit(configRequest)
        assert r == []

        tenant = moDir.lookupByDn('uni/tn-{0}'.format(tenantname[0]))
        assert not tenant


class Test_rest_classquery(object):

    def test_classquery_shorthand_filter(self, moDir):
        """
        check that lookupByClass is able to lookup tenant common and only one
        item is returned
        """
        commonTn = moDir.lookupByClass(
            'fvTenant', propFilter='eq(fvTenant.name, "common")')
        assert len(commonTn) == 1
        commonTn = commonTn[0]
        assert str(commonTn.dn) == 'uni/tn-common'

    def test_classquery_normal(self, moDir):
        """
        check that a class query with no special  properties succeeds
        we should get at least three tenants (infra, mgmt, common)
        """
        classQuery = cobra.mit.request.ClassQuery('fvTenant')
        commonTn = moDir.query(classQuery)

        def findtn(tnlist, tnname):
            for tn in tnlist:
                if tn.name == tnname:
                    return True
            return False

        assert findtn(commonTn, 'common')
        assert findtn(commonTn, 'infra')
        assert findtn(commonTn, 'mgmt')

    def test_classquery_filter(self, moDir):
        """
        check that a class query with a property filter works
        """
        classQuery = cobra.mit.request.ClassQuery('fvTenant')
        classQuery.propFilter = 'eq(fvTenant.name, "common")'
        commonTn = moDir.query(classQuery)
        commonTn = commonTn[0]
        assert str(commonTn.dn) == 'uni/tn-common'

    def test_classquery_subtree(self, moDir):
        """
        check that a class query with a subtree response
        """
        classQuery = cobra.mit.request.ClassQuery('fvTenant')
        classQuery.subtree = 'full'
        classQuery.propFilter = 'eq(fvTenant.name, "common")'
        commonTn = moDir.query(classQuery)
        commonTn = commonTn[0]
        assert str(commonTn.dn) == 'uni/tn-common'
        # expect at least 3 child objects
        assert len(list(commonTn.children)) >= 3
        assert str(commonTn.BD['default'].dn) == 'uni/tn-common/BD-default'

    @pytest.mark.parametrize("cls,subtree", [
        pytest.mark.skipif(pytest.config.getvalue('apic') == [],
                           reason="no --apic")(('fvTenant', 'full')),
        pytest.mark.skipif(pytest.config.getvalue('apic') == [],
                           reason="no --apic")(('infraInfra', 'no')),
        pytest.mark.skipif(pytest.config.getvalue('apic') == [],
                           reason="no --apic")(('fvAEPg', 'full')),
        pytest.mark.skipif(pytest.config.getvalue('apic') == [],
                           reason="no --apic")(('infraFuncP', 'full')),
        pytest.mark.skipif(pytest.config.getvalue('apic') == [],
                           reason="no --apic")(('fabricNode', 'no')),
        pytest.mark.skipif(pytest.config.getvalue('apic') == [],
                           reason="no --apic")(('topSystem', 'full')),
    ])
    def test_classquery_many(self, moDir, cls, subtree):
        classQuery = cobra.mit.request.ClassQuery(cls)
        classQuery.subtree = subtree
        # classQuery.propFilter='eq(fvTenant.name, "common")'
        mos = moDir.query(classQuery)
        assert len(mos) > 0

    def test_classquery_verifyxml(self, moDir):
        """
        verify that the XML returned by lookupByClass is valid
        """
        commonTn = moDir.lookupByClass(
            'fvTenant', propFilter='eq(fvTenant.name, "common")')
        commonTn = commonTn[0]
        xml = ET.fromstring(toXMLStr(commonTn))
        assert xml.tag == 'fvTenant'

    def test_classquery_negative(self, moDir):
        """
        generate a random tenant name and ensure that we dont find a match for it
        """
        tenantName = ''.join(random.choice(string.ascii_lowercase)
                             for i in range(64))
        tenant = moDir.lookupByClass(
            'fvTenant', propFilter='eq(fvTenant.name, "{0}")'.format(tenantName))
        assert len(tenant) == 0


class Test_rest_dnquery(object):

    def test_dnquery_normal(self, moDir, dn):

        dnQuery = cobra.mit.request.DnQuery(dn)
        dnQuery.subtree = 'full'

        commonTn = moDir.query(dnQuery)
        assert len(commonTn) == 1
        commonTn = commonTn[0]
        assert str(commonTn.dn) == str(dn)
        # expect at least 3 child objects
        assert len(list(commonTn.children)) >= 3
        assert str(commonTn.BD['default'].dn) == 'uni/tn-common/BD-default'

    def test_dnquery_shorthand(self, moDir, dn):
        commonTn = moDir.lookupByDn(dn)
        assert str(commonTn.dn) == str(dn)


class Test_rest_getLoginDomains(object):

    def test_getDomains(self, apic):
        """Verify that the getLoginDomains() method works.
        """
        url, user, password, secure = apic
        secure = False if secure == 'False' else True
        session = cobra.mit.session.LoginSession(url, user, password,
                                                 secure=secure)
        session.getLoginDomains()
        assert session.domains != []

    def test_loginDomains_setting(self, apic):
        """Verify that the loginDomain can be set."""
        url, user, password, secure = apic
        secure = False if secure == 'False' else True
        session = cobra.mit.session.LoginSession(url, user, password,
                                                 secure=secure)
        session.getLoginDomains()
        session.loginDomain = session.domains[0]
        assert session.loginDomain == session.domains[0]


class Test_rest_login(object):

    def test_login_positive(self, apic):
        """
        verify that the login function works
        """
        url, user, password, secure = apic
        secure = False if secure == 'False' else True
        session = cobra.mit.session.LoginSession(url, user, password,
                                                 secure=secure)
        moDir = cobra.mit.access.MoDirectory(session)
        moDir.login()
        assert moDir._session

    def test_login_negative(self, apic):
        """
        verify that the invalid logins throw an exception
        """
        url, user, password, secure = apic
        secure = False if secure == 'False' else True
        session = cobra.mit.session.LoginSession(url, user, 'wrongpassword',
                                                 secure=secure)
        moDir = cobra.mit.access.MoDirectory(session)
        with pytest.raises(cobra.mit.session.LoginError):
            moDir.login()

    @slow
    def test_login_timeout(self, apic):
        """
        verify that the session times out properly
        """
        url, user, password, secure = apic
        secure = False if secure == 'False' else True
        session = cobra.mit.session.LoginSession(url, user, password,
                                                 secure=secure)
        moDir = cobra.mit.access.MoDirectory(session)
        moDir.login()
        start = time.time()

        pki = moDir.lookupByDn('uni/userext/pkiext/webtokendata')
        refreshTime = pki.webtokenTimeoutSeconds
        sleepTime = float(refreshTime) - (time.time() - start)
        sleepTime += 1.0  # one second buffer, for good measure

        time.sleep(sleepTime)

        with pytest.raises(cobra.mit.request.QueryError):
            moDir.lookupByClass('pkiWebTokenData')

    def test_login_get_timeout(self, apic):
        """
        verify that the session times out properly
        """
        url, user, password, secure = apic
        secure = False if secure == 'False' else True
        session = cobra.mit.session.LoginSession(url, user, password,
                                                 secure=secure)
        moDir = cobra.mit.access.MoDirectory(session)
        moDir.login()

        assert moDir._session.refreshTime > int(time.time())
        assert moDir._session.refreshTimeoutSeconds > 0

    def test_rest_login_reauth(self, apic):
        """Verify that the reauth call returns a different session cookie."""
        url, user, password, secure = apic
        secure = False if secure == 'False' else True
        session = cobra.mit.session.LoginSession(url, user, password,
                                                 secure=secure)
        moDir = cobra.mit.access.MoDirectory(session)
        moDir.login()
        orig_cookie = session.cookie
        # sleep for 5 seconds to ensure we get a different cookie.
        time.sleep(5)
        moDir.reauth()
        assert orig_cookie != session.cookie

class Test_rest_tracequery(object):

    @pytest.mark.parametrize("cls", [
        pytest.mark.skipif(pytest.config.getvalue('apic') == [],
                           reason="no --apic")('fvEpP'),
        pytest.mark.skipif(pytest.config.getvalue('apic') == [],
                           reason="no --apic")('vlanCktEp'),
        pytest.mark.skipif(pytest.config.getvalue('apic') == [],
                           reason="no --apic")('actrlRule'),
    ])
    def test_tracequery(self, moDir, cls):
        """
        Query every leaf in the fabric for some concrete objects and try to
        find at least one response. If we don't get that, we fail
        """
        traceResponse = 0
        nodes = moDir.lookupByClass(
            'fabricNode', propFilter='eq(fabricNode.role,"leaf"')
        assert len(nodes) > 0

        for node in nodes:
            a = cobra.mit.request.TraceQuery(node.dn, cls)
            print(a.getUrl(moDir._session))
            mos = moDir.query(a)
            for mo in mos:
                print(mo.dn)
            traceResponse += len(mos)
        assert traceResponse > 0


class Test_services_devicepackage(object):

    fakePackage = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                               fakeDevicePackageZip)
    realPackage = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                               realDevicePackageZip)

    def test_packagevalidate(self):
        """
        Make sure that invalid device packages throw an exception when validation
        is enabled
        """
        with pytest.raises(AttributeError):
            cobra.services.UploadPackage(self.fakePackage, validate=True)

    def test_packagedonotvalidate(self):
        """
        Make sure that if validation is not enabled, no exception is thrown
        """
        packageUpload = cobra.services.UploadPackage(self.fakePackage)
        assert packageUpload.devicePackagePath == self.fakePackage

    def test_uploadpackage(self, moDir):
        """
        ensure that the device package upload returns a 200
        """
        packageUpload = cobra.services.UploadPackage(self.realPackage,
                                                     validate=True)
        r = moDir.commit(packageUpload)
        assert r == []

    def test_validateupload(self, moDir):
        """
        make sure that the uploaded device package is found
        """
        uni = cobra.model.pol.Uni('')
        infra = cobra.model.infra.Infra(uni)
        vnsQuery = cobra.mit.request.DnQuery(infra.dn)
        vnsQuery.propFilter = 'eq(vnsMDev.vendor,"CISCO")'
        vnsQuery.queryTarget = 'subtree'
        vnsQuery.classFilter = 'vnsMDev'
        packages = moDir.query(vnsQuery)
        assert len(packages) > 0
        package = packages[0]
        assert package.vendor == 'CISCO'
        assert package.model == 'ASA'
        # for package in packages:
        #   print '\n'.join(['%s:\t%s' % (k,getattr(package,k)) for k in package.meta.props.names])
        #   print package.dn

[((17, 0, 17, 34), 'future.standard_library.install_aliases', 'standard_library.install_aliases', ({}, {}), '()', False, 'from future import standard_library\n'), ((37, 8, 37, 36), 'pytest.importorskip', 'pytest.importorskip', ({(37, 28, 37, 35): '"""cobra"""'}, {}), "('cobra')", False, 'import pytest\n'), ((38, 14, 38, 48), 'pytest.importorskip', 'pytest.importorskip', ({(38, 34, 38, 47): '"""cobra.model"""'}, {}), "('cobra.model')", False, 'import pytest\n'), ((39, 17, 39, 54), 'pytest.importorskip', 'pytest.importorskip', ({(39, 37, 39, 53): '"""cobra.model.fv"""'}, {}), "('cobra.model.fv')", False, 'import pytest\n'), ((50, 0, 50, 40), 'logging.basicConfig', 'logging.basicConfig', (), '', False, 'import logging\n'), ((44, 32, 44, 62), 'pytest.config.getvalue', 'pytest.config.getvalue', ({(44, 55, 44, 61): '"""apic"""'}, {}), "('apic')", False, 'import pytest\n'), ((56, 23, 56, 53), 'pytest.config.getvalue', 'pytest.config.getvalue', ({(56, 46, 56, 52): '"""apic"""'}, {}), "('apic')", False, 'import pytest\n'), ((287, 16, 287, 27), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((294, 8, 294, 29), 'time.sleep', 'time.sleep', ({(294, 19, 294, 28): 'sleepTime'}, {}), '(sleepTime)', False, 'import time\n'), ((323, 8, 323, 21), 'time.sleep', 'time.sleep', ({(323, 19, 323, 20): '(5)'}, {}), '(5)', False, 'import time\n'), ((88, 15, 88, 25), 'builtins.str', 'str', ({(88, 19, 88, 24): 'mo.dn'}, {}), '(mo.dn)', False, 'from builtins import str\n'), ((88, 29, 88, 43), 'builtins.str', 'str', ({(88, 33, 88, 42): 'tenant.dn'}, {}), '(tenant.dn)', False, 'from builtins import str\n'), ((121, 15, 121, 31), 'builtins.str', 'str', ({(121, 19, 121, 30): 'commonTn.dn'}, {}), '(commonTn.dn)', False, 'from builtins import str\n'), ((149, 15, 149, 31), 'builtins.str', 'str', ({(149, 19, 149, 30): 'commonTn.dn'}, {}), '(commonTn.dn)', False, 'from builtins import str\n'), ((160, 15, 160, 31), 'builtins.str', 'str', ({(160, 19, 160, 30): 'commonTn.dn'}, {}), '(commonTn.dn)', False, 'from builtins import str\n'), ((163, 15, 163, 45), 'builtins.str', 'str', ({(163, 19, 163, 44): "commonTn.BD['default'].dn"}, {}), "(commonTn.BD['default'].dn)", False, 'from builtins import str\n'), ((193, 28, 193, 46), 'cobra.internal.codec.xmlcodec.toXMLStr', 'toXMLStr', ({(193, 37, 193, 45): 'commonTn'}, {}), '(commonTn)', False, 'from cobra.internal.codec.xmlcodec import toXMLStr, fromXMLStr\n'), ((217, 15, 217, 31), 'builtins.str', 'str', ({(217, 19, 217, 30): 'commonTn.dn'}, {}), '(commonTn.dn)', False, 'from builtins import str\n'), ((217, 35, 217, 42), 'builtins.str', 'str', ({(217, 39, 217, 41): 'dn'}, {}), '(dn)', False, 'from builtins import str\n'), ((220, 15, 220, 45), 'builtins.str', 'str', ({(220, 19, 220, 44): "commonTn.BD['default'].dn"}, {}), "(commonTn.BD['default'].dn)", False, 'from builtins import str\n'), ((224, 15, 224, 31), 'builtins.str', 'str', ({(224, 19, 224, 30): 'commonTn.dn'}, {}), '(commonTn.dn)', False, 'from builtins import str\n'), ((224, 35, 224, 42), 'builtins.str', 'str', ({(224, 39, 224, 41): 'dn'}, {}), '(dn)', False, 'from builtins import str\n'), ((273, 13, 273, 56), 'pytest.raises', 'pytest.raises', ({(273, 27, 273, 55): 'cobra.mit.session.LoginError'}, {}), '(cobra.mit.session.LoginError)', False, 'import pytest\n'), ((296, 13, 296, 56), 'pytest.raises', 'pytest.raises', ({(296, 27, 296, 55): 'cobra.mit.request.QueryError'}, {}), '(cobra.mit.request.QueryError)', False, 'import pytest\n'), ((359, 47, 359, 73), 'os.path.realpath', 'os.path.realpath', ({(359, 64, 359, 72): '__file__'}, {}), '(__file__)', False, 'import os\n'), ((361, 47, 361, 73), 'os.path.realpath', 'os.path.realpath', ({(361, 64, 361, 72): '__file__'}, {}), '(__file__)', False, 'import os\n'), ((369, 13, 369, 42), 'pytest.raises', 'pytest.raises', ({(369, 27, 369, 41): 'AttributeError'}, {}), '(AttributeError)', False, 'import pytest\n'), ((200, 29, 200, 66), 'random.choice', 'random.choice', ({(200, 43, 200, 65): 'string.ascii_lowercase'}, {}), '(string.ascii_lowercase)', False, 'import random\n'), ((291, 42, 291, 53), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((310, 48, 310, 59), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((73, 19, 73, 30), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((201, 38, 201, 47), 'builtins.range', 'range', ({(201, 44, 201, 46): '64'}, {}), '(64)', False, 'from builtins import range\n'), ((166, 27, 166, 57), 'pytest.config.getvalue', 'pytest.config.getvalue', ({(166, 50, 166, 56): '"""apic"""'}, {}), "('apic')", False, 'import pytest\n'), ((168, 27, 168, 57), 'pytest.config.getvalue', 'pytest.config.getvalue', ({(168, 50, 168, 56): '"""apic"""'}, {}), "('apic')", False, 'import pytest\n'), ((170, 27, 170, 57), 'pytest.config.getvalue', 'pytest.config.getvalue', ({(170, 50, 170, 56): '"""apic"""'}, {}), "('apic')", False, 'import pytest\n'), ((172, 27, 172, 57), 'pytest.config.getvalue', 'pytest.config.getvalue', ({(172, 50, 172, 56): '"""apic"""'}, {}), "('apic')", False, 'import pytest\n'), ((174, 27, 174, 57), 'pytest.config.getvalue', 'pytest.config.getvalue', ({(174, 50, 174, 56): '"""apic"""'}, {}), "('apic')", False, 'import pytest\n'), ((176, 27, 176, 57), 'pytest.config.getvalue', 'pytest.config.getvalue', ({(176, 50, 176, 56): '"""apic"""'}, {}), "('apic')", False, 'import pytest\n'), ((330, 27, 330, 57), 'pytest.config.getvalue', 'pytest.config.getvalue', ({(330, 50, 330, 56): '"""apic"""'}, {}), "('apic')", False, 'import pytest\n'), ((332, 27, 332, 57), 'pytest.config.getvalue', 'pytest.config.getvalue', ({(332, 50, 332, 56): '"""apic"""'}, {}), "('apic')", False, 'import pytest\n'), ((334, 27, 334, 57), 'pytest.config.getvalue', 'pytest.config.getvalue', ({(334, 50, 334, 56): '"""apic"""'}, {}), "('apic')", False, 'import pytest\n')]