<reponame>planetlab/sfa<filename>sfa/plc/network.py
from __future__ import with_statement
import sys
import re
import socket
from StringIO import StringIO
from lxml import etree
from xmlbuilder import XMLBuilder

from sfa.util.faults import *
from sfa.util.xrn import get_authority
from sfa.util.plxrn import hrn_to_pl_slicename, hostname_to_urn

class Sliver:
    def __init__(self, node):
        self.node = node
        self.network = node.network
        self.slice = node.network.slice
        
    def toxml(self, xml):
        with xml.sliver:
            self.slice.tags_to_xml(xml, self.node)

        
class Iface:
    def __init__(self, network, iface):
        self.network = network
        self.id = iface['interface_id']
        self.idtag = "i%s" % self.id
        self.ipv4 = iface['ip']
        self.bwlimit = iface['bwlimit']
        self.hostname = iface['hostname']
        self.primary = iface['is_primary']

    def toxml(self, xml):
        """
        Just print out bwlimit right now
        """
        if self.bwlimit:
            with xml.bw_limit(units="kbps"):
                xml << str(self.bwlimit / 1000)


class Node:
    def __init__(self, network, node):
        self.network = network
        self.id = node['node_id']
        self.idtag = "n%s" % self.id
        self.hostname = node['hostname']
        self.site_id = node['site_id']
        self.iface_ids = node['interface_ids']
        self.sliver = None
        self.whitelist = node['slice_ids_whitelist']
        auth = self.network.api.hrn
        login_base = self.get_site().idtag
        self.urn = hostname_to_urn(auth, login_base, self.hostname)

    def get_primary_iface(self):
        for id in self.iface_ids:
            iface = self.network.lookupIface(id)
            if iface.primary:
                return iface
        return None
        
    def get_site(self):
        return self.network.lookupSite(self.site_id)
    
    def add_sliver(self):
        self.sliver = Sliver(self)

    def toxml(self, xml):
        slice = self.network.slice
        if self.whitelist and not self.sliver:
            if not slice or slice.id not in self.whitelist:
                return

        with xml.node(id = self.idtag):
            with xml.hostname:
                xml << self.hostname
            with xml.urn:
                xml << self.urn
            iface = self.get_primary_iface()
            if iface:
                iface.toxml(xml)
            if self.sliver:
                self.sliver.toxml(xml)
    

class Site:
    def __init__(self, network, site):
        self.network = network
        self.id = site['site_id']
        self.node_ids = site['node_ids']
        self.node_ids.sort()
        self.name = site['abbreviated_name']
        self.tag = site['login_base']
        self.idtag = site['login_base']
        self.public = site['is_public']
        self.enabled = site['enabled']
        self.links = set()
        self.whitelist = False

    def get_sitenodes(self):
        n = []
        for i in self.node_ids:
            n.append(self.network.lookupNode(i))
        return n
    
    def toxml(self, xml):
        if not (self.public and self.enabled and self.node_ids):
            return
        with xml.site(id = self.idtag):
            with xml.name:
                xml << self.name
            for node in self.get_sitenodes():
                node.toxml(xml)
   
    
class Slice:
    def __init__(self, network, hrn, slice):
        self.hrn = hrn
        self.network = network
        self.id = slice['slice_id']
        self.name = slice['name']
        self.peer_id = slice['peer_id']
        self.node_ids = set(slice['node_ids'])
        self.slice_tag_ids = slice['slice_tag_ids']
    
    """
    Use with tags that can have more than one instance
    """
    def get_multi_tag(self, tagname, node = None):
        tags = []
        for i in self.slice_tag_ids:
            try: 
                tag = self.network.lookupSliceTag(i)                 
                if tag.tagname == tagname:
                    if node:
                        if node.id == tag.node_id:
                            tags.append(tag)
                    elif not tag.node_id:
                        tags.append(tag)
            except InvalidRSpec, e: 
                # As they're not needed, we ignore some tag types from 
                # GetSliceTags call. See Slicetag.ignore_tags 
                pass 
        return tags
        
    """
    Use with tags that have only one instance
    """
    def get_tag(self, tagname, node = None):
        for i in self.slice_tag_ids:
            try:
                tag = self.network.lookupSliceTag(i) 
                if tag.tagname == tagname: 
                    if node:
                        if node.id == tag.node_id:
                            return tag
                    elif not tag.node_id:
                        return tag 
            except InvalidRSpec, e:
                # As they're not needed, we ignore some tag types from
                # GetSliceTags call. See Slicetag.ignore_tags
                pass
        return None
        
    def get_nodes(self):
        n = []
        for id in self.node_ids:
            if id in self.network.nodes:
                n.append(self.network.nodes[id])
        return n

    # Add a new slice tag   
    def add_tag(self, tagname, value, node = None, role = "user"):
        tt = self.network.lookupTagType(tagname)
        if not tt.permit_update(role):
            raise InvalidRSpec("permission denied to modify '%s' tag" % tagname)
        tag = Slicetag()
        tag.initialize(tagname, value, node, self.network)
        self.network.tags[tag.id] = tag
        self.slice_tag_ids.append(tag.id)
        return tag
    
    # Update a slice tag if it exists, else add it             
    def update_tag(self, tagname, value, node = None, role = "user"):
        tag = self.get_tag(tagname, node)
        if tag and tag.value == value:
            return tag

        tt = self.network.lookupTagType(tagname)
        if not tt.permit_update(role):
            raise InvalidRSpec("permission denied to modify '%s' tag" % tagname)

        if tag:
            tag.change(value)
        else:
            tag = self.add_tag(tagname, value, node, role)
        return tag
            
    def update_multi_tag(self, tagname, value, node = None, role = "user"):
        tags = self.get_multi_tag(tagname, node)
        for tag in tags:
            if tag and tag.value == value:
                break
        else:
            tag = self.add_tag(tagname, value, node, role)
        return tag
            
    def tags_to_xml(self, xml, node = None):
        tagtypes = self.network.getTagTypes()
        for tt in tagtypes:
            if tt.in_rspec:
                if tt.multi:
                    tags = self.get_multi_tag(tt.tagname, node)
                    for tag in tags:
                        if not tag.was_deleted():  ### Debugging
                            xml << (tag.tagname, tag.value)
                else:
                    tag = self.get_tag(tt.tagname, node)
                    if tag:
                        if not tag.was_deleted():   ### Debugging
                            xml << (tag.tagname, tag.value)

    def toxml(self, xml):
        with xml.sliver_defaults:
            self.tags_to_xml(xml)


class Slicetag:
    newid = -1 
    filter_fields = ['slice_tag_id','slice_id','tagname','value','node_id','category'] 
    ignore_tags = ['hmac','ssh_key']
    def __init__(self, tag = None):
        if not tag:
            return
        self.id = tag['slice_tag_id']
        self.slice_id = tag['slice_id']
        self.tagname = tag['tagname']
        self.value = tag['value']
        self.node_id = tag['node_id']
        self.category = tag['category']
        self.status = None

    # Create a new slicetag that will be written to the DB later
    def initialize(self, tagname, value, node, network):
        tt = network.lookupTagType(tagname)
        self.id = Slicetag.newid
        Slicetag.newid -=1
        self.slice_id = network.slice.id
        self.tagname = tagname
        self.value = value
        if node:
            self.node_id = node.id
        else:
            self.node_id = None
        self.category = tt.category
        self.status = "new"

    def change(self, value):
        if self.value != value:
            self.value = value
            self.status = "change"
        else:
            self.status = "updated"
        
    # Mark a tag as deleted
    def delete(self):
        self.status = "delete"

    def was_added(self):
        return (self.id < 0)

    def was_changed(self):
        return (self.status == "change")

    def was_deleted(self):
        return (self.status == "delete")

    def was_updated(self):
        return (self.status != None)
    
    def write(self, api):
        if self.was_added():
            api.plshell.AddSliceTag(api.plauth, self.slice_id, 
                                    self.tagname, self.value, self.node_id)
        elif self.was_changed():
            api.plshell.UpdateSliceTag(api.plauth, self.id, self.value)
        elif self.was_deleted():
            api.plshell.DeleteSliceTag(api.plauth, self.id)


class TagType:
    ignore_tags = ['hmac','ssh_key']
    def __init__(self, tagtype):
        self.id = tagtype['tag_type_id']
        self.category = tagtype['category']
        self.tagname = tagtype['tagname']
        self.roles = tagtype['roles']
        self.multi = False
        self.in_rspec = False
        if self.category == 'slice/rspec':
            self.in_rspec = True
        if self.tagname in ['codemux', 'ip_addresses', 'vsys']:
            self.multi = True

    def permit_update(self, role):
        if role in self.roles:
            return True
        return False
        

class Network:
    """
    A Network is a compound object consisting of:
    * a dictionary mapping site IDs to Site objects
    * a dictionary mapping node IDs to Node objects
    * a dictionary mapping interface IDs to Iface objects
    """
    def __init__(self, api, type = "SFA"):
        self.api = api
        self.type = type
        self.sites = self.get_sites(api)
        self.nodes = self.get_nodes(api)
        self.ifaces = self.get_ifaces(api)
        self.tags = self.get_slice_tags(api)
        self.tagtypes = self.get_tag_types(api)
        self.slice = None
        self.sitemap = {}
        for s in self.sites:
            site = self.sites[s]
            self.sitemap[site.idtag] = site.id

    def lookupSiteIdtag(self, name):
        """ Lookup site id from name """
        val = None
        try:
            val = self.sitemap[name]
        except:
            raise InvalidRSpec("site name '%s' not found" % name)
        return val
    
    def lookupSite(self, id):
        """ Lookup site based on id or idtag value """
        val = None
        if isinstance(id, basestring):
            id = self.lookupSiteIdtag(id)
        try:
            val = self.sites[id]
        except:
            self.api.logger.error("Invalid RSpec: site ID %s not found" % id )
            raise InvalidRSpec("site ID %s not found" % id)
        return val
    
    def getSites(self):
        sites = []
        for s in self.sites:
            sites.append(self.sites[s])
        return sites
        
    def lookupNode(self, id):
        """ Lookup node based on id or idtag value """
        val = None
        if isinstance(id, basestring):
            id = int(id.lstrip('n'))
        try:
            val = self.nodes[id]
        except:
            raise InvalidRSpec("node ID %s not found" % id)
        return val
    
    def getNodes(self):
        nodes = []
        for n in self.nodes:
            nodes.append(self.nodes[n])
        return nodes
    
    def lookupIface(self, id):
        """ Lookup iface based on id or idtag value """
        val = None
        if isinstance(id, basestring):
            id = int(id.lstrip('i'))
        try:
            val = self.ifaces[id]
        except:
            raise InvalidRSpec("interface ID %s not found" % id)
        return val
    
    def getIfaces(self):
        ifaces = []
        for i in self.ifaces:
            ifaces.append(self.ifaces[i])
        return ifaces
    
    def nodesWithSlivers(self):
        nodes = []
        for n in self.nodes:
            node = self.nodes[n]
            if node.sliver:
                nodes.append(node)
        return nodes
            
    def lookupSliceTag(self, id):
        val = None
        try:
            val = self.tags[id]
        except:
            raise InvalidRSpec("slicetag ID %s not found" % id)
        return val
    
    def getSliceTags(self):
        tags = []
        for t in self.tags:
            tags.append(self.tags[t])
        return tags
    
    def lookupTagType(self, name):
        val = None
        try:
            val = self.tagtypes[name]
        except:
            raise InvalidRSpec("tag %s not found" % name)
        return val
    
    def getTagTypes(self):
        tags = []
        for t in self.tagtypes:
            tags.append(self.tagtypes[t])
        return tags
    
    def __process_attributes(self, element, node=None):
        """
        Process the elements under <sliver_defaults> or <sliver>
        """
        if element is None:
            return 

        tagtypes = self.getTagTypes()
        for tt in tagtypes:
            if tt.in_rspec:
                if tt.multi:
                    for e in element.iterfind("./" + tt.tagname):
                        self.slice.update_multi_tag(tt.tagname, e.text, node)
                else:
                    e = element.find("./" + tt.tagname)
                    if e is not None:
                        self.slice.update_tag(tt.tagname, e.text, node)

    def addRSpec(self, xml, schema=None):
        """
        Annotate the objects in the Network with information from the RSpec
        """
        try:
            tree = etree.parse(StringIO(xml))
        except etree.XMLSyntaxError:
            message = str(sys.exc_info()[1])
            raise InvalidRSpec(message)

        # Filter out stuff that's not for us
        rspec = tree.getroot()
        for network in rspec.iterfind("./network"):
            if network.get("name") != self.api.hrn:
                rspec.remove(network)
        for request in rspec.iterfind("./request"):
            if request.get("name") != self.api.hrn:
                rspec.remove(request)

        if schema:
            # Validate the incoming request against the RelaxNG schema
            relaxng_doc = etree.parse(schema)
            relaxng = etree.RelaxNG(relaxng_doc)
        
            if not relaxng(tree):
                error = relaxng.error_log.last_error
                message = "%s (line %s)" % (error.message, error.line)
                self.api.logger.error("failed to validate rspec %r"%message)
                self.api.logger.debug("---------- XML input BEG")
                self.api.logger.debug(xml)
                self.api.logger.debug("---------- XML input END")
                raise InvalidRSpec(message)

        self.rspec = rspec

        defaults = rspec.find(".//sliver_defaults")
        self.__process_attributes(defaults)

        # Find slivers under node elements
        for sliver in rspec.iterfind("./network/site/node/sliver"):
            elem = sliver.getparent()
            try:
                node = self.lookupNode(elem.get("id"))
            except:
                # Don't worry about nodes from other aggregates
                pass
            else:
                node.add_sliver()
                self.__process_attributes(sliver, node)

        # Find slivers that specify nodeid
        for sliver in rspec.iterfind("./request/sliver[@nodeid]"):
            try:
                node = self.lookupNode(sliver.get("nodeid"))
            except:
                # Don't worry about nodes from other aggregates
                pass
            else:
                node.add_sliver()
                self.__process_attributes(sliver, node)

        return

    def addSlice(self):
        """
        Annotate the objects in the Network with information from the slice
        """
        slice = self.slice
        if not slice:
            raise InvalidRSpec("no slice associated with network")

        for node in slice.get_nodes():
            node.add_sliver()

    def updateSliceTags(self):
        """
        Write any slice tags that have been added or modified back to the DB
        """
        for tag in self.getSliceTags():
            if tag.category == 'slice/rspec' and not tag.was_updated():
                tt = self.lookupTagType(tag.tagname)
                if tt.permit_update("user"):
                    tag.delete()

        # Update slice tags in database
        for tag in self.getSliceTags():
            if tag.slice_id == self.slice.id:
                tag.write(self.api) 

    def toxml(self):
        """
        Produce XML directly from the topology specification.
        """
        xml = XMLBuilder(format = True, tab_step = "  ")
        with xml.RSpec(type=self.type):
            if self.slice:
                element = xml.network(name=self.api.hrn, slice=self.slice.hrn)
            else:
                element = xml.network(name=self.api.hrn)
                
            with element:
                if self.slice:
                    self.slice.toxml(xml)
                for site in self.getSites():
                    site.toxml(xml)

        header = '<?xml version="1.0"?>\n'
        return header + str(xml)

    def get_sites(self, api):
        """
        Create a dictionary of site objects keyed by site ID
        """
        tmp = []
        for site in api.plshell.GetSites(api.plauth, {'peer_id': None}):
            t = site['site_id'], Site(self, site)
            tmp.append(t)
        return dict(tmp)


    def get_nodes(self, api):
        """
        Create a dictionary of node objects keyed by node ID
        """
        tmp = []
        for node in api.plshell.GetNodes(api.plauth, {'peer_id': None}):
            try:
                t = node['node_id'], Node(self, node)
                tmp.append(t)
            except:
                self.api.logger.error("Failed to add node %s (%s) to RSpec" % (node['hostname'], node['node_id']))
                 
        return dict(tmp)

    def get_ifaces(self, api):
        """
        Create a dictionary of node objects keyed by node ID
        """
        tmp = []
        for iface in api.plshell.GetInterfaces(api.plauth):
            t = iface['interface_id'], Iface(self, iface)
            tmp.append(t)
        return dict(tmp)

    def get_slice_tags(self, api):
        """
        Create a dictionary of slicetag objects keyed by slice tag ID
        """
        tmp = []
        for tag in api.plshell.GetSliceTags(api.plauth, {'~tagname':Slicetag.ignore_tags}, Slicetag.filter_fields): 
            t = tag['slice_tag_id'], Slicetag(tag)
            tmp.append(t)
        return dict(tmp)
    
    def get_tag_types(self, api):
        """
        Create a list of tagtype obects keyed by tag name
        """
        tmp = []
        for tag in api.plshell.GetTagTypes(api.plauth, {'~tagname':TagType.ignore_tags}):
            t = tag['tagname'], TagType(tag)
            tmp.append(t)
        return dict(tmp)
    
    def get_slice(self, api, hrn):
        """
        Return a Slice object for a single slice
        """
        slicename = hrn_to_pl_slicename(hrn)
        slice = api.plshell.GetSlices(api.plauth, [slicename])
        if len(slice):
            self.slice = Slice(self, slicename, slice[0])
            return self.slice
        else:
            return None
    


from __future__ import with_statement
import sys
import re
import socket
from StringIO import StringIO
from lxml import etree
from xmlbuilder import XMLBuilder

from sfa.util.faults import *
from sfa.util.xrn import get_authority
from sfa.util.plxrn import hrn_to_pl_slicename, hostname_to_urn

class Sliver:
    def __init__(self, node):
        self.node = node
        self.network = node.network
        self.slice = node.network.slice
        
    def toxml(self, xml):
        with xml.sliver:
            self.slice.tags_to_xml(xml, self.node)

        
class Iface:
    def __init__(self, network, iface):
        self.network = network
        self.id = iface['interface_id']
        self.idtag = "i%s" % self.id
        self.ipv4 = iface['ip']
        self.bwlimit = iface['bwlimit']
        self.hostname = iface['hostname']
        self.primary = iface['is_primary']

    def toxml(self, xml):
        """
        Just print out bwlimit right now
        """
        if self.bwlimit:
            with xml.bw_limit(units="kbps"):
                xml << str(self.bwlimit / 1000)


class Node:
    def __init__(self, network, node):
        self.network = network
        self.id = node['node_id']
        self.idtag = "n%s" % self.id
        self.hostname = node['hostname']
        self.site_id = node['site_id']
        self.iface_ids = node['interface_ids']
        self.sliver = None
        self.whitelist = node['slice_ids_whitelist']
        auth = self.network.api.hrn
        login_base = self.get_site().idtag
        self.urn = hostname_to_urn(auth, login_base, self.hostname)

    def get_primary_iface(self):
        for id in self.iface_ids:
            iface = self.network.lookupIface(id)
            if iface.primary:
                return iface
        return None
        
    def get_site(self):
        return self.network.lookupSite(self.site_id)
    
    def add_sliver(self):
        self.sliver = Sliver(self)

    def toxml(self, xml):
        slice = self.network.slice
        if self.whitelist and not self.sliver:
            if not slice or slice.id not in self.whitelist:
                return

        with xml.node(id = self.idtag):
            with xml.hostname:
                xml << self.hostname
            with xml.urn:
                xml << self.urn
            iface = self.get_primary_iface()
            if iface:
                iface.toxml(xml)
            if self.sliver:
                self.sliver.toxml(xml)
    

class Site:
    def __init__(self, network, site):
        self.network = network
        self.id = site['site_id']
        self.node_ids = site['node_ids']
        self.node_ids.sort()
        self.name = site['abbreviated_name']
        self.tag = site['login_base']
        self.idtag = site['login_base']
        self.public = site['is_public']
        self.enabled = site['enabled']
        self.links = set()
        self.whitelist = False

    def get_sitenodes(self):
        n = []
        for i in self.node_ids:
            n.append(self.network.lookupNode(i))
        return n
    
    def toxml(self, xml):
        if not (self.public and self.enabled and self.node_ids):
            return
        with xml.site(id = self.idtag):
            with xml.name:
                xml << self.name
            for node in self.get_sitenodes():
                node.toxml(xml)
   
    
class Slice:
    def __init__(self, network, hrn, slice):
        self.hrn = hrn
        self.network = network
        self.id = slice['slice_id']
        self.name = slice['name']
        self.peer_id = slice['peer_id']
        self.node_ids = set(slice['node_ids'])
        self.slice_tag_ids = slice['slice_tag_ids']
    
    """
    Use with tags that can have more than one instance
    """
    def get_multi_tag(self, tagname, node = None):
        tags = []
        for i in self.slice_tag_ids:
            try: 
                tag = self.network.lookupSliceTag(i)                 
                if tag.tagname == tagname:
                    if node:
                        if node.id == tag.node_id:
                            tags.append(tag)
                    elif not tag.node_id:
                        tags.append(tag)
            except InvalidRSpec, e: 
                # As they're not needed, we ignore some tag types from 
                # GetSliceTags call. See Slicetag.ignore_tags 
                pass 
        return tags
        
    """
    Use with tags that have only one instance
    """
    def get_tag(self, tagname, node = None):
        for i in self.slice_tag_ids:
            try:
                tag = self.network.lookupSliceTag(i) 
                if tag.tagname == tagname: 
                    if node:
                        if node.id == tag.node_id:
                            return tag
                    elif not tag.node_id:
                        return tag 
            except InvalidRSpec, e:
                # As they're not needed, we ignore some tag types from
                # GetSliceTags call. See Slicetag.ignore_tags
                pass
        return None
        
    def get_nodes(self):
        n = []
        for id in self.node_ids:
            if id in self.network.nodes:
                n.append(self.network.nodes[id])
        return n

    # Add a new slice tag   
    def add_tag(self, tagname, value, node = None, role = "user"):
        tt = self.network.lookupTagType(tagname)
        if not tt.permit_update(role):
            raise InvalidRSpec("permission denied to modify '%s' tag" % tagname)
        tag = Slicetag()
        tag.initialize(tagname, value, node, self.network)
        self.network.tags[tag.id] = tag
        self.slice_tag_ids.append(tag.id)
        return tag
    
    # Update a slice tag if it exists, else add it             
    def update_tag(self, tagname, value, node = None, role = "user"):
        tag = self.get_tag(tagname, node)
        if tag and tag.value == value:
            return tag

        tt = self.network.lookupTagType(tagname)
        if not tt.permit_update(role):
            raise InvalidRSpec("permission denied to modify '%s' tag" % tagname)

        if tag:
            tag.change(value)
        else:
            tag = self.add_tag(tagname, value, node, role)
        return tag
            
    def update_multi_tag(self, tagname, value, node = None, role = "user"):
        tags = self.get_multi_tag(tagname, node)
        for tag in tags:
            if tag and tag.value == value:
                break
        else:
            tag = self.add_tag(tagname, value, node, role)
        return tag
            
    def tags_to_xml(self, xml, node = None):
        tagtypes = self.network.getTagTypes()
        for tt in tagtypes:
            if tt.in_rspec:
                if tt.multi:
                    tags = self.get_multi_tag(tt.tagname, node)
                    for tag in tags:
                        if not tag.was_deleted():  ### Debugging
                            xml << (tag.tagname, tag.value)
                else:
                    tag = self.get_tag(tt.tagname, node)
                    if tag:
                        if not tag.was_deleted():   ### Debugging
                            xml << (tag.tagname, tag.value)

    def toxml(self, xml):
        with xml.sliver_defaults:
            self.tags_to_xml(xml)


class Slicetag:
    newid = -1 
    filter_fields = ['slice_tag_id','slice_id','tagname','value','node_id','category'] 
    ignore_tags = ['hmac','ssh_key']
    def __init__(self, tag = None):
        if not tag:
            return
        self.id = tag['slice_tag_id']
        self.slice_id = tag['slice_id']
        self.tagname = tag['tagname']
        self.value = tag['value']
        self.node_id = tag['node_id']
        self.category = tag['category']
        self.status = None

    # Create a new slicetag that will be written to the DB later
    def initialize(self, tagname, value, node, network):
        tt = network.lookupTagType(tagname)
        self.id = Slicetag.newid
        Slicetag.newid -=1
        self.slice_id = network.slice.id
        self.tagname = tagname
        self.value = value
        if node:
            self.node_id = node.id
        else:
            self.node_id = None
        self.category = tt.category
        self.status = "new"

    def change(self, value):
        if self.value != value:
            self.value = value
            self.status = "change"
        else:
            self.status = "updated"
        
    # Mark a tag as deleted
    def delete(self):
        self.status = "delete"

    def was_added(self):
        return (self.id < 0)

    def was_changed(self):
        return (self.status == "change")

    def was_deleted(self):
        return (self.status == "delete")

    def was_updated(self):
        return (self.status != None)
    
    def write(self, api):
        if self.was_added():
            api.plshell.AddSliceTag(api.plauth, self.slice_id, 
                                    self.tagname, self.value, self.node_id)
        elif self.was_changed():
            api.plshell.UpdateSliceTag(api.plauth, self.id, self.value)
        elif self.was_deleted():
            api.plshell.DeleteSliceTag(api.plauth, self.id)


class TagType:
    ignore_tags = ['hmac','ssh_key']
    def __init__(self, tagtype):
        self.id = tagtype['tag_type_id']
        self.category = tagtype['category']
        self.tagname = tagtype['tagname']
        self.roles = tagtype['roles']
        self.multi = False
        self.in_rspec = False
        if self.category == 'slice/rspec':
            self.in_rspec = True
        if self.tagname in ['codemux', 'ip_addresses', 'vsys']:
            self.multi = True

    def permit_update(self, role):
        if role in self.roles:
            return True
        return False
        

class Network:
    """
    A Network is a compound object consisting of:
    * a dictionary mapping site IDs to Site objects
    * a dictionary mapping node IDs to Node objects
    * a dictionary mapping interface IDs to Iface objects
    """
    def __init__(self, api, type = "SFA"):
        self.api = api
        self.type = type
        self.sites = self.get_sites(api)
        self.nodes = self.get_nodes(api)
        self.ifaces = self.get_ifaces(api)
        self.tags = self.get_slice_tags(api)
        self.tagtypes = self.get_tag_types(api)
        self.slice = None
        self.sitemap = {}
        for s in self.sites:
            site = self.sites[s]
            self.sitemap[site.idtag] = site.id

    def lookupSiteIdtag(self, name):
        """ Lookup site id from name """
        val = None
        try:
            val = self.sitemap[name]
        except:
            raise InvalidRSpec("site name '%s' not found" % name)
        return val
    
    def lookupSite(self, id):
        """ Lookup site based on id or idtag value """
        val = None
        if isinstance(id, basestring):
            id = self.lookupSiteIdtag(id)
        try:
            val = self.sites[id]
        except:
            self.api.logger.error("Invalid RSpec: site ID %s not found" % id )
            raise InvalidRSpec("site ID %s not found" % id)
        return val
    
    def getSites(self):
        sites = []
        for s in self.sites:
            sites.append(self.sites[s])
        return sites
        
    def lookupNode(self, id):
        """ Lookup node based on id or idtag value """
        val = None
        if isinstance(id, basestring):
            id = int(id.lstrip('n'))
        try:
            val = self.nodes[id]
        except:
            raise InvalidRSpec("node ID %s not found" % id)
        return val
    
    def getNodes(self):
        nodes = []
        for n in self.nodes:
            nodes.append(self.nodes[n])
        return nodes
    
    def lookupIface(self, id):
        """ Lookup iface based on id or idtag value """
        val = None
        if isinstance(id, basestring):
            id = int(id.lstrip('i'))
        try:
            val = self.ifaces[id]
        except:
            raise InvalidRSpec("interface ID %s not found" % id)
        return val
    
    def getIfaces(self):
        ifaces = []
        for i in self.ifaces:
            ifaces.append(self.ifaces[i])
        return ifaces
    
    def nodesWithSlivers(self):
        nodes = []
        for n in self.nodes:
            node = self.nodes[n]
            if node.sliver:
                nodes.append(node)
        return nodes
            
    def lookupSliceTag(self, id):
        val = None
        try:
            val = self.tags[id]
        except:
            raise InvalidRSpec("slicetag ID %s not found" % id)
        return val
    
    def getSliceTags(self):
        tags = []
        for t in self.tags:
            tags.append(self.tags[t])
        return tags
    
    def lookupTagType(self, name):
        val = None
        try:
            val = self.tagtypes[name]
        except:
            raise InvalidRSpec("tag %s not found" % name)
        return val
    
    def getTagTypes(self):
        tags = []
        for t in self.tagtypes:
            tags.append(self.tagtypes[t])
        return tags
    
    def __process_attributes(self, element, node=None):
        """
        Process the elements under <sliver_defaults> or <sliver>
        """
        if element is None:
            return 

        tagtypes = self.getTagTypes()
        for tt in tagtypes:
            if tt.in_rspec:
                if tt.multi:
                    for e in element.iterfind("./" + tt.tagname):
                        self.slice.update_multi_tag(tt.tagname, e.text, node)
                else:
                    e = element.find("./" + tt.tagname)
                    if e is not None:
                        self.slice.update_tag(tt.tagname, e.text, node)

    def addRSpec(self, xml, schema=None):
        """
        Annotate the objects in the Network with information from the RSpec
        """
        try:
            tree = etree.parse(StringIO(xml))
        except etree.XMLSyntaxError:
            message = str(sys.exc_info()[1])
            raise InvalidRSpec(message)

        # Filter out stuff that's not for us
        rspec = tree.getroot()
        for network in rspec.iterfind("./network"):
            if network.get("name") != self.api.hrn:
                rspec.remove(network)
        for request in rspec.iterfind("./request"):
            if request.get("name") != self.api.hrn:
                rspec.remove(request)

        if schema:
            # Validate the incoming request against the RelaxNG schema
            relaxng_doc = etree.parse(schema)
            relaxng = etree.RelaxNG(relaxng_doc)
        
            if not relaxng(tree):
                error = relaxng.error_log.last_error
                message = "%s (line %s)" % (error.message, error.line)
                self.api.logger.error("failed to validate rspec %r"%message)
                self.api.logger.debug("---------- XML input BEG")
                self.api.logger.debug(xml)
                self.api.logger.debug("---------- XML input END")
                raise InvalidRSpec(message)

        self.rspec = rspec

        defaults = rspec.find(".//sliver_defaults")
        self.__process_attributes(defaults)

        # Find slivers under node elements
        for sliver in rspec.iterfind("./network/site/node/sliver"):
            elem = sliver.getparent()
            try:
                node = self.lookupNode(elem.get("id"))
            except:
                # Don't worry about nodes from other aggregates
                pass
            else:
                node.add_sliver()
                self.__process_attributes(sliver, node)

        # Find slivers that specify nodeid
        for sliver in rspec.iterfind("./request/sliver[@nodeid]"):
            try:
                node = self.lookupNode(sliver.get("nodeid"))
            except:
                # Don't worry about nodes from other aggregates
                pass
            else:
                node.add_sliver()
                self.__process_attributes(sliver, node)

        return

    def addSlice(self):
        """
        Annotate the objects in the Network with information from the slice
        """
        slice = self.slice
        if not slice:
            raise InvalidRSpec("no slice associated with network")

        for node in slice.get_nodes():
            node.add_sliver()

    def updateSliceTags(self):
        """
        Write any slice tags that have been added or modified back to the DB
        """
        for tag in self.getSliceTags():
            if tag.category == 'slice/rspec' and not tag.was_updated():
                tt = self.lookupTagType(tag.tagname)
                if tt.permit_update("user"):
                    tag.delete()

        # Update slice tags in database
        for tag in self.getSliceTags():
            if tag.slice_id == self.slice.id:
                tag.write(self.api) 

    def toxml(self):
        """
        Produce XML directly from the topology specification.
        """
        xml = XMLBuilder(format = True, tab_step = "  ")
        with xml.RSpec(type=self.type):
            if self.slice:
                element = xml.network(name=self.api.hrn, slice=self.slice.hrn)
            else:
                element = xml.network(name=self.api.hrn)
                
            with element:
                if self.slice:
                    self.slice.toxml(xml)
                for site in self.getSites():
                    site.toxml(xml)

        header = '<?xml version="1.0"?>\n'
        return header + str(xml)

    def get_sites(self, api):
        """
        Create a dictionary of site objects keyed by site ID
        """
        tmp = []
        for site in api.plshell.GetSites(api.plauth, {'peer_id': None}):
            t = site['site_id'], Site(self, site)
            tmp.append(t)
        return dict(tmp)


    def get_nodes(self, api):
        """
        Create a dictionary of node objects keyed by node ID
        """
        tmp = []
        for node in api.plshell.GetNodes(api.plauth, {'peer_id': None}):
            try:
                t = node['node_id'], Node(self, node)
                tmp.append(t)
            except:
                self.api.logger.error("Failed to add node %s (%s) to RSpec" % (node['hostname'], node['node_id']))
                 
        return dict(tmp)

    def get_ifaces(self, api):
        """
        Create a dictionary of node objects keyed by node ID
        """
        tmp = []
        for iface in api.plshell.GetInterfaces(api.plauth):
            t = iface['interface_id'], Iface(self, iface)
            tmp.append(t)
        return dict(tmp)

    def get_slice_tags(self, api):
        """
        Create a dictionary of slicetag objects keyed by slice tag ID
        """
        tmp = []
        for tag in api.plshell.GetSliceTags(api.plauth, {'~tagname':Slicetag.ignore_tags}, Slicetag.filter_fields): 
            t = tag['slice_tag_id'], Slicetag(tag)
            tmp.append(t)
        return dict(tmp)
    
    def get_tag_types(self, api):
        """
        Create a list of tagtype obects keyed by tag name
        """
        tmp = []
        for tag in api.plshell.GetTagTypes(api.plauth, {'~tagname':TagType.ignore_tags}):
            t = tag['tagname'], TagType(tag)
            tmp.append(t)
        return dict(tmp)
    
    def get_slice(self, api, hrn):
        """
        Return a Slice object for a single slice
        """
        slicename = hrn_to_pl_slicename(hrn)
        slice = api.plshell.GetSlices(api.plauth, [slicename])
        if len(slice):
            self.slice = Slice(self, slicename, slice[0])
            return self.slice
        else:
            return None
Just print out bwlimit right now Use with tags that can have more than one instance # As they're not needed, we ignore some tag types from # GetSliceTags call. See Slicetag.ignore_tags Use with tags that have only one instance # As they're not needed, we ignore some tag types from # GetSliceTags call. See Slicetag.ignore_tags # Add a new slice tag # Update a slice tag if it exists, else add it ### Debugging ### Debugging # Create a new slicetag that will be written to the DB later # Mark a tag as deleted A Network is a compound object consisting of:     * a dictionary mapping site IDs to Site objects     * a dictionary mapping node IDs to Node objects     * a dictionary mapping interface IDs to Iface objects Lookup site id from name Lookup site based on id or idtag value Lookup node based on id or idtag value Lookup iface based on id or idtag value Process the elements under <sliver_defaults> or <sliver> Annotate the objects in the Network with information from the RSpec # Filter out stuff that's not for us # Validate the incoming request against the RelaxNG schema # Find slivers under node elements # Don't worry about nodes from other aggregates # Find slivers that specify nodeid # Don't worry about nodes from other aggregates Annotate the objects in the Network with information from the slice Write any slice tags that have been added or modified back to the DB # Update slice tags in database Produce XML directly from the topology specification. Create a dictionary of site objects keyed by site ID Create a dictionary of node objects keyed by node ID Create a dictionary of node objects keyed by node ID Create a dictionary of slicetag objects keyed by slice tag ID Create a list of tagtype obects keyed by tag name Return a Slice object for a single slice