exptToHv.py 7.68 KB
Newer Older
1 2
#
# Copyright (c) 2004 University of Utah and the Flux Group.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
# 
# {{{EMULAB-LICENSE
# 
# This file is part of the Emulab network testbed software.
# 
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
# 
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
# License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this file.  If not, see <http://www.gnu.org/licenses/>.
# 
# }}}
22
#
Mike Hibler's avatar
Mike Hibler committed
23 24

# exptToHv - Get an experiment topology via xmlrpc, and write a HyperViewer .hyp file.
25 26
import sets
import string
27
import fnmatch
Russ Fish's avatar
Russ Fish committed
28
import os
Russ Fish's avatar
Russ Fish committed
29
import os.path
30 31 32 33 34 35 36 37

from sshxmlrpc import *
from emulabclient import *

## Server constants.
PACKAGE_VERSION = 0.1                   # The package version number
XMLRPC_SERVER   = "boss.emulab.net"     # Default server
xmlrpc_server   = XMLRPC_SERVER         # User supplied server name.
Russ Fish's avatar
Russ Fish committed
38 39 40
if os.environ.has_key("EMULAB_USER"):    # Emulab user login ID to use.
    login_id    = os.environ["EMULAB_USER"]
elif os.environ.has_key("USER"):          # User login ID to use.
Russ Fish's avatar
Russ Fish committed
41 42 43 44 45
    login_id    = os.environ["USER"]      # Unix shells.
elif os.environ.has_key("USERNAME"):
    login_id    = os.environ["USERNAME"]  # Windows.
else:
    login_id    = "guest"
46 47 48 49 50 51 52 53
module          = "experiment"          # The default RPC module to invoke.
path            = None

## initServer - Get a handle on the server.
#
server = None
def initServer():
    global server
54 55
    uri = "ssh://" + login_id + "@" + xmlrpc_server + "/xmlrpc/" + module
    ###uri = "ssh://" + login_id + "@" + xmlrpc_server + "/" + module
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
    ##print uri
    server = SSHServerProxy(uri, path=path)
    pass

## intfcHost - An interface name is host:port .  Get just the host part.
#
def intfcHost(intfcName):
    return intfcName[0:string.index(intfcName,':')]

## AddConnect - Represent a topology graph as a dictionary of nodes with sets
## of their connected nodes.
#
def addConnection(graph, h1, h2):

    # Connect the first host in the link to the host at the other end.
    if h1 in graph:
        graph[h1].add(h2)
    else:
        graph[h1] = sets.Set([h2])
Russ Fish's avatar
Russ Fish committed
75
        pass
76 77 78 79 80 81

    # Make back-connections as well for an undirected (bi-directed) graph.
    if h2 in graph:
        graph[h2].add(h1)
    else:
        graph[h2] = sets.Set([h1])
Russ Fish's avatar
Russ Fish committed
82 83
        pass
    pass
84

85
## getExperiment - Make the request from the server.  Reconstitute a topology graph
86 87 88 89 90 91
## from the host interface list, and traverse it to write HyperViewer .hyp file.
#
# Args are the project and experiment names, and optionally the root of the topology.
# If no root is given, the first lan or the first host is the default root node.
#
# An experiment.hyp file is written in /tmp.
Russ Fish's avatar
Russ Fish committed
92
# Return is the .hyp file name, or an error list in case of failure.
93 94 95 96 97 98 99 100 101 102 103
#
def getExperiment(project, experiment, root=""):
    if not server:
        initServer()
    meth_args = [PACKAGE_VERSION, {'proj':project, 'exp':experiment, 'aspect':'links'}]
    response = None
    try:
        meth = getattr(server, "info")
        response = apply(meth, meth_args)
        pass
    except xmlrpclib.Fault, e:
Russ Fish's avatar
Russ Fish committed
104 105 106 107 108 109 110 111 112
        err = "XMLRPC-lib error --"
        #print err,  e.faultString
        return [3, err,  e.faultString, ""]
    except BadResponse, e:
        err = "SSH-XMLRPC error --"
        err2 = "Make sure you have a valid SSH key in ssh-agent or PuTTY/pageant." 
        #print err, e
        #print err2
        return [4, err, e, err2]
113
    if response["code"] != RESPONSE_SUCCESS:
Russ Fish's avatar
Russ Fish committed
114 115 116 117 118 119
        err = "XMLRPC failure, code"
        e = response["code"]
        err2 = "There is no experiment " + project + "/" + experiment
        #print err, e
        #print err2        
        return [2, err, e, err2]
120 121 122 123 124 125 126 127
    links = response["value"]

    # Figure out the nodes from the experiment links (interfaces) from the virt_lans table.
    # These are "link ends", which are members of either inter-host links or lans.
    # Lan nodes are are implicitly represented as links with over two interfaces as members.
    linksByName = {}	# Regroup by link name into sets of members, to find lans.
    for member, link in links.items():	# The links dict is keyed by interface (member) name.
        linkName = link['name']
Russ Fish's avatar
Russ Fish committed
128
        #print linkName
129 130 131
        if not linksByName.has_key(linkName):
            linksByName[linkName] = sets.Set()
        linksByName[linkName].add(member)	# Each link/lan connects a set of interfaces.
132 133
        pass
    
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
    # Build the connection graph as a dictionary of nodes with sets of connected nodes.
    hosts = sets.Set()	# Collect unique node names (hosts and lans.)
    lans = sets.Set()
    graph = {}
    for link, intfcs in linksByName.items():
        if len(intfcs) == 2:
            # Two interfaces connected indicates a host-to-host link.
            h1, h2 = [intfcHost(intfc) for intfc in intfcs]
            hosts.add(h1)
            hosts.add(h2)
            addConnection(graph, h1, h2)
        else:
            # Lan nodes are are links with more than two interfaces as members.
            lans.add(link)
            for intfc in intfcs:
                addConnection(graph, link, intfcHost(intfc))
150 151 152
                pass
            pass
        pass
153 154 155 156 157 158 159

    # Use the first lan or the first host as the default root node.
    if root == "":
        if len(lans) > 0:
            root = lans.copy().pop()
        else:
            root = hosts.copy().pop()
160 161
            pass
        pass
162

163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
    # The root may be a glob expression, in which case we make up a root node and put
    # the matching nodes under it.  We could add a regexp option as well...
    rootNodes = []
    if '*' in root or '?' in root or '[' in root:
        glob = root
        rootNodes = fnmatch.filter(hosts, glob) + fnmatch.filter(lans, glob)
        if len(rootNodes) > 1:
            # Find a new root name that isn't already in the hosts or lans lists.
            for newroot in ['root','Root','ROOT','RoOt']:
                if not newroot in hosts and not newroot in lans:
                    root = newroot
                    ##print "Connecting", root, "to", rootNodes
                    for node in rootNodes:
                        addConnection(graph, root, node)
                        pass
                    pass
                    break
                pass
        pass
    
183 184 185 186 187 188 189 190
    # Walk the graph structure in depth-first order to generate the .hyp file.
    # Make a second copy of the graph as we go to avoid repeating nodes due to back-links.
    def walkNodes(graph, graph2, node, level, outfile):
        ##print level, node, 1, ['host','lan'][node in lans]
        outfile.write(str(level)+" "+node+" 1 "+['host','lan'][node in lans]+'\n')

        # Recursively traverse the nodes connected to this one.
        for conn in graph[node]:
191 192 193
            if (not (node in graph2 and conn in graph2[node])
                # rootNodes are fanned-out from the root only.
                and (level == 0 or not conn in rootNodes)):
194 195 196 197 198
                addConnection(graph2, node, conn)
                walkNodes(graph, graph2, conn, level+1, outfile)
                pass
            pass
        pass
199
    
Russ Fish's avatar
Russ Fish committed
200 201 202 203 204
    if os.name == "nt":
        tmpdir = "C:\\temp\\"
    else:
        tmpdir = "/tmp/"
        pass
Russ Fish's avatar
Russ Fish committed
205 206
    if not os.path.exists(tmpdir):
        tmpdir = ""                     # Default to the current directory.
Russ Fish's avatar
Russ Fish committed
207 208
    hypfile = tmpdir + experiment + '.hyp'
    outfile = file(hypfile, 'w')
209 210 211 212 213
    graph2 = {}
    walkNodes(graph, graph2, root, 0, outfile)
    outfile.close()

    return hypfile