exptToHv.py 7 KB
Newer Older
1 2 3 4 5
#
# EMULAB-COPYRIGHT
# Copyright (c) 2004 University of Utah and the Flux Group.
# All rights reserved.
#
Mike Hibler's avatar
Mike Hibler committed
6 7

# exptToHv - Get an experiment topology via xmlrpc, and write a HyperViewer .hyp file.
8 9
import sets
import string
10
import fnmatch
Russ Fish's avatar
Russ Fish committed
11
import os
Russ Fish's avatar
Russ Fish committed
12
import os.path
13 14 15 16 17 18 19 20

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
21 22 23
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
24 25 26 27 28
    login_id    = os.environ["USER"]      # Unix shells.
elif os.environ.has_key("USERNAME"):
    login_id    = os.environ["USERNAME"]  # Windows.
else:
    login_id    = "guest"
29 30 31 32 33 34 35 36
module          = "experiment"          # The default RPC module to invoke.
path            = None

## initServer - Get a handle on the server.
#
server = None
def initServer():
    global server
37 38
    uri = "ssh://" + login_id + "@" + xmlrpc_server + "/xmlrpc/" + module
    ###uri = "ssh://" + login_id + "@" + xmlrpc_server + "/" + module
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
    ##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
58
        pass
59 60 61 62 63 64

    # 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
65 66
        pass
    pass
67

68
## getExperiment - Make the request from the server.  Reconstitute a topology graph
69 70 71 72 73 74
## 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
75
# Return is the .hyp file name, or an error list in case of failure.
76 77 78 79 80 81 82 83 84 85 86
#
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
87 88 89 90 91 92 93 94 95
        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]
96
    if response["code"] != RESPONSE_SUCCESS:
Russ Fish's avatar
Russ Fish committed
97 98 99 100 101 102
        err = "XMLRPC failure, code"
        e = response["code"]
        err2 = "There is no experiment " + project + "/" + experiment
        #print err, e
        #print err2        
        return [2, err, e, err2]
103 104 105 106 107 108 109 110
    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
111
        #print linkName
112 113 114
        if not linksByName.has_key(linkName):
            linksByName[linkName] = sets.Set()
        linksByName[linkName].add(member)	# Each link/lan connects a set of interfaces.
115 116
        pass
    
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
    # 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))
133 134 135
                pass
            pass
        pass
136 137 138 139 140 141 142

    # 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()
143 144
            pass
        pass
145

146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
    # 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
    
166 167 168 169 170 171 172 173
    # 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]:
174 175 176
            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)):
177 178 179 180 181
                addConnection(graph2, node, conn)
                walkNodes(graph, graph2, conn, level+1, outfile)
                pass
            pass
        pass
182
    
Russ Fish's avatar
Russ Fish committed
183 184 185 186 187
    if os.name == "nt":
        tmpdir = "C:\\temp\\"
    else:
        tmpdir = "/tmp/"
        pass
Russ Fish's avatar
Russ Fish committed
188 189
    if not os.path.exists(tmpdir):
        tmpdir = ""                     # Default to the current directory.
Russ Fish's avatar
Russ Fish committed
190 191
    hypfile = tmpdir + experiment + '.hyp'
    outfile = file(hypfile, 'w')
192 193 194 195 196
    graph2 = {}
    walkNodes(graph, graph2, root, 0, outfile)
    outfile.close()

    return hypfile