# Fetches topology representations from other

import time
import StringIO

from twisted.python import log
from twisted.internet import defer, task
from twisted.application import service

from opennsa.protocols.shared import httpclient
from opennsa.topology import nmlxml


LOG_SYSTEM = 'topology.Fetcher'

FETCH_INTERVAL = 30 # seconds, increase for production :-)



class FetcherService(service.Service):

    def __init__(self, peering_entries, topology):
        # peering entries is a list of two-tuples, where each tuple contains
        # a network name and the url of the network topology
        #for network, topo_url in peering_pairs:
        for pe in peering_entries:
            assert len(pe) is 2, 'Peering entry %s is not two-tuple' % pp
            network, topo_url = pe
            assert topo_url.startswith('http'), 'Topology URL %s does not start with http' % topo_url

        self.peering_entries = peering_entries
        self.topology = topology

        self.blacklist = {}

        self.call = task.LoopingCall(self.fetchTopologies)


    def startService(self):
        self.call.start(FETCH_INTERVAL)
        service.Service.startService(self)


    def stopService(self):
        self.call.stop()
        service.Service.stopService(self)


    def blacklistNetwork(self, network_name, seconds=600):
        # blacklist a network for a certain amount of time
        expire_time = int(time.time() + seconds)
        self.blacklist[network_name] = expire_time
        log.msg('Network %s blacklisted from topology retrieval for %i seconds' % (network_name, seconds), system=LOG_SYSTEM)


    def getPeeringEntries(self):
        # filters out the blacklisted sites - returns non-blacklisted entries
        now = time.time()
        active_peering_entries = []

        for network_name, topology_url in self.peering_entries:
            try:
                if self.blacklist[network_name] > now:
                    continue
                else:
                    # blacklist has expired
                    self.blacklist.pop(network_name)
            except KeyError:
                pass
            active_peering_entries.append( (network_name, topology_url) )

        return active_peering_entries


    def fetchTopologies(self):
        log.msg('Fetching topologies. %i sources, %i possibly blacklisted' % (len(self.peering_entries), len(self.blacklist)), system=LOG_SYSTEM)

        defs = []
        for network_name, topology_url in self.getPeeringEntries():
            log.msg('Fetchin topology for network %s from %s' % (network_name, topology_url), system=LOG_SYSTEM)
            d = httpclient.httpRequest(topology_url, '', {}, 'GET', timeout=10) # https should perhaps be added sometime
            ca = (network_name, topology_url)
            d.addCallbacks(self.gotTopology, self.retrievalFailed, callbackArgs=ca, errbackArgs=ca)
            defs.append(d)

        if defs:
            return defer.DeferredList(defs)


    def gotTopology(self, result, network_name, topology_url):
        log.msg('Got topology for %s (%i bytes)' % (network_name, len(result)), system=LOG_SYSTEM)
        try:
            # here we should let the parser know that it should not go outside the network name when parsing - later man...
            nsi_agent, nml_network = nmlxml.parseNSITopology(StringIO.StringIO(result))
            # here we could do some version checking first
            self.topology.updateNetwork(nml_network, nsi_agent)
            log.msg('Topology for network %s updated' % nml_network.name, system=LOG_SYSTEM)
        except error.TopologyError as e:
            log.msg('Error parsing topology for network %s, url %s. Reason %s' % (network_name, topology_url, str(e)), system=LOG_SYSTEM)
            self.blacklistNetwork(network_name)


    def retrievalFailed(self, result, network_name, topology_url):
        log.msg('Topology retrieval for network %s from URL %s failed. Reason %s' % (network_name, topology_url, result.getErrorMessage), system=LOG_SYSTEM)
        self.blacklistNetwork(network_name)


@@ -44,20 +44,8 @@
 er%0A%0A
-import time%0A
 impo
@@ -128,16 +128,25 @@
 er, task
+, reactor
 %0Afrom tw
@@ -1221,16 +1221,103 @@
 .stop()%0A
+        for delayed_call in self.blacklist.values():%0A            delayed_call.cancel()%0A
         
@@ -1482,47 +1482,82 @@
     
-expire_time = int(time.time() + seconds
+delayed_call = reactor.callLater(seconds, self.blacklist.pop, network_name
 )%0A  
@@ -1597,19 +1597,20 @@
 %5D = 
-expire_time
+delayed_call
 %0A   
@@ -1857,509 +1857,90 @@
     
-now = time.time()%0A        active_peering_entries = %5B%5D%0A%0A        for network_name, topology_url in self.peering_entries:%0A            try:%0A                if self.blacklist%5Bnetwork_name%5D %3E now:%0A                    continue%0A                else:%0A                    # blacklist has expired%0A                    self.blacklist.pop(network_name)%0A            except KeyError:%0A                pass%0A            active_peering_entries.append( (network_name, topology_url) )%0A%0A        return active_peering_entries
+return %5B (nw, tu) for nw, tu in self.peering_entries if nw not in self.blacklist %5D
 %0A%0A%0A 
@@ -2026,17 +2026,8 @@
  %25i 
-possibly 
 blac

simplify fetcher by using delayed call, also makes blacklisting info accurate