mod_dslice.py.in 10.9 KB
Newer Older
1
# -*- python -*-
Kirk Webb's avatar
Kirk Webb committed
2 3 4 5 6
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2003 University of Utah and the Flux Group.
# All rights reserved.
#
7 8 9 10 11 12 13 14 15 16 17 18 19 20

import sys
sys.path.append("@prefix@/lib")
sys.path.append("@prefix@/lib/dslice")
sys.path.append("@prefix@/lib/dslice/dslice")
sys.path.append("@prefix@/lib/dslice/HTMLgen")

import agent, agentproxy
import nodemgr, nodemgrproxy
import lease
import cPickle

from libtestbed import *

Kirk Webb's avatar
Kirk Webb committed
21 22 23 24 25 26
#
# output control vars
#
verbose = 0
debug = 0

27 28 29
#
# Constants
#
Kirk Webb's avatar
Kirk Webb committed
30
LEASELEN = 14*24*60*60   # Two weeks (maximum lease length)
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
AGENTIP = "dslice.planet-lab.org"
ELABPUBKEYFILE = "/root/.ssh/identity.pub"

# XXX: Create agent dictionary - one entry for each slice.
class mod_dslice:
    def __init__(self, keyfile = DEFAULT_DATA_PATH + "key.pem",
                 pubkeyfile = DEFAULT_DATA_PATH + "pubkey.pem",
                 certfile = DEFAULT_DATA_PATH + "cert.pem",
                 cacertfile = DEFAULT_DATA_PATH + "cacert.pem"):
        for file in (keyfile, pubkeyfile, certfile, cacertfile):
            if not os.path.exists(file):
                raise RuntimeError, "Key or cert %s does not exist" % file
        self.keyfile, self.pubkeyfile, self.certfile, self.cacertfile = \
                      keyfile, pubkeyfile, certfile, cacertfile
        self.__agentProxy = None
        self.__nodemgrProxies = {}
        self.modname = "mod_dslice"
        pass
    
Kirk Webb's avatar
Kirk Webb committed
50
    def createSlice(self, slice):
51
        privkey, pubkey = self.__genKeypair()
Kirk Webb's avatar
Kirk Webb committed
52
        return (1, cPickle.dumps((privkey, pubkey)), None)
53

Kirk Webb's avatar
Kirk Webb committed
54 55 56 57 58
    def deleteSlice(self, slice):
        return

    def renewSlice(self, slice):
        return (0, slice.slicemeta, None)
59 60 61 62 63 64 65 66 67 68

    def createNode(self, node):
        # Get a ticket, and redeem it for a vm lease
        privkey, pubkey = cPickle.loads(node.slice.slicemeta)
        agent = self.__createAgentProxy()
        tickets = tryXmlrpcCmd(agent.newtickets,
                               (node.slice.slicename,
                                1, LEASELEN, (node.IP,)))
        assert (len(tickets) == 1), "%d tickets returned" % len(tickets)
        ticketdata = tickets[0]
Kirk Webb's avatar
Kirk Webb committed
69
        if debug:
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
            print "Obtained ticket:"
            print ticketdata
            pass
        
        nodemgr = self.__createNodemgrProxy(node.IP)
        self.leasedata = None
        
        tries = DEF_TRIES
        while 1:
            TIMESTAMP("createnode %s try %d started." % (node.nodeid,
                                                         DEF_TRIES-tries+1))
            try:
                self.leasedata = tryXmlrpcCmd(nodemgr.newleasevm,
                                              (ticketdata,
                                               privkey,
                                               pubkey),
                                              inittries = tries,
                                              raisefault = True)
                pass
            
            # We may have actually gotten the lease/vm even though
            # the xmlrpc call appeared to fail.  We check for this
            # condition here, which will show up on subsequent allocation
            # attempts.
            except xmlrpclib.Fault, e:
                if e.faultString.find("already exists") != -1:
                    print "Lease for %s already exists; deleting." % \
                          node.nodeid
                    if self.freeNode(node):
                        raise RuntimeError, "Could not delete lingering " \
                              "lease for slice %s on %s" % \
                              (node.slice.slicename, node.nodeid)
                    pass
                
                if e.triesleft > 0:
                    tries = e.triesleft
                else:
                    raise
                
                pass
            
            # success
            else:
                break
            pass
        
Kirk Webb's avatar
Kirk Webb committed
116
        if debug:
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
            print "Obtained lease/vm:"
            print leasedata
            pass

        lease = lease.lease(leasedata)
        self.__addKey(node, ELABPUBKEYFILE)
        return (1, cPickle.dumps((ticketdata, leasedata)), lease.end_time)

    def freeNode(self, node):
        # Get node manager handle
        nodemgr = self.__createNodemgrProxy(node.IP)
        deleted = 0
        
        tries = DEF_TRIES
        while 1:
            TIMESTAMP("freenode %s try %d started." % (node.nodeid,
                                                       DEF_TRIES-tries+1))
            try:
                tryXmlrpcCmd(nodemgr.deletelease, node.slice.slicename,
                             inittries = tries, raisefault = 1)
                pass
            except xmlrpclib.Fault, e:
                if e.faultString.find("does not exist") != -1:
                    print "Lease for %s did not exist on node" % node.nodeid
                    deleted = 1
                    break
                elif e.triesleft > 0:
                    tries = e.triesleft
                else:
                    break
                pass
            except:
                print "Warning: couldn't delete the lease for %s on %s" % \
                      (node.slice.slicename, node.nodeid)
                tbstr = "".join(traceback.format_exception(*sys.exc_info()))
                SENDMAIL(TBOPS, "Sliver lease deletion failed on %s, "
                         "dslice %s" % (node.nodeid, node.slice.slicename),
                         "Sliver lease deletion failed:\n\n%s" % tbstr, TBOPS)
                break
            else:
                deleted = 1
                break
            pass
        return deleted

    def __addKey(self, node, identityfile):
        """
        Adds an ssh public key to the node.  Note that identityfile must
        be the path of the public key.  This must be done before any
        calls to becomeEmulba, addtoGroup, or unpackTgz, because those
        commands rely on ssh'ing into the node.  Note also that this
        should be one of the keys that ssh naturally knows about, or
        those commands will fail.
        """
Kirk Webb's avatar
Kirk Webb committed
171
        if verbose:
172 173 174 175 176 177 178 179
            print "Adding pubkey to node %s" % node.nodeid
            pass
        if not identityfile.endswith(".pub"):
            raise RuntimeError, "File %s doesn't look like a pubkey" % \
                  identityfile
        pubkey = file(identityfile, "rb").read().strip()
        nodemgr = self.__createNodemgrProxy(node.IP)
        ret = tryXmlrpcCmd(nodemgr.addkey, (node.slice.slicename, pubkey))
Kirk Webb's avatar
Kirk Webb committed
180
        if debug:
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
            print "Added key: %s" % `ret`
            pass
        return ret

    def renewNode(self, node, length = 0):
        """
        Renew the lease for node belonging to this instance.  Don't
        call this directly; instead, use Node.renew()
        """
        print "Renewing lease on Plab node %s." % node.nodeid
        ticketdata, leasedata = cPickle.loads(node.nodemeta)
        nodemgr = self.__createNodemgrProxy(node.IP)

        tries = DEF_TRIES
        while 1:
            TIMESTAMP("renewnode %s try %d started." % (node.nodeid,
                                                        DEF_TRIES-tries+1))
            try:
                self.leasedata = tryXmlrpcCmd(nodemgr.renewlease,
                                              node.slice.slicename,
                                              inittries = tries,
                                              raisefault = True)
            except xmlrpclib.Fault, e:
                if e.faultString.find("does not exist") != -1:
                    print "No lease found on %s for slice %s" % \
                          (node.nodeid, node.slice.slicename)
                    return 1
                elif e.triesleft > 0:
                    tries = e.triesleft
                else:
                    raise
            else:
                break

Kirk Webb's avatar
Kirk Webb committed
215
        if debug:
216 217 218 219 220 221 222 223 224 225 226 227 228 229
            print "Obtained new lease:"
            print self.leasedata
        self.lease = lease.lease(self.leasedata)
        return (1, cPickle.dumps((ticketdata, leasedata)), lease.end_time)

    def __genKeypair(self):
        """
        Generates a passphrase-less RSA keypair and returns the PEM
        format data to be stored in the slice's identity and
        identity.pub files.
        """
        # XXX This is a workaround for a bug in M2Crypto
        import tempfile

Kirk Webb's avatar
Kirk Webb committed
230
        if verbose:
231 232 233
            print "Generating slice keypair"
        # pdssi = Plab Dynamic Slice SSH Identity
        fname = tempfile.mktemp("pdssi%d" % os.getpid())
Kirk Webb's avatar
Kirk Webb committed
234
        if debug:
235 236 237 238 239 240 241 242 243 244 245 246 247 248
            print "Writing keypair to %s(.pub)" % fname
        if os.spawnlp(os.P_WAIT, "ssh-keygen",
                      "ssh-keygen", "-t", "rsa", "-b", "1024", "-P", "",
                      "-f", fname, "-q"):
            raise RuntimeError, "Error generating slice keypair"
        
        privkey = file(fname, "rb").read()
        pubkey = file(fname + ".pub", "rb").read()
        map(os.unlink, (fname, fname + ".pub"))
        
        return privkey, pubkey
        
        
        # Below here is the way it _should_ be done
Kirk Webb's avatar
Kirk Webb committed
249
        if verbose:
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
            print "Generating slice keypair"
        key = RSA.gen_key(1024, 35)    # OpenSSH ssh-keygen uses 35 for e

        privkeyio = cStringIO.StringIO()
        # Due to a current bug in M2Crypto, None cannot be passed as the
        # cipher; therefore, passphraseless keys cannot be generated
        key.save_key_bio(BIO.File(privkeyio), None)
        privkey = privkeyio.getvalue()

        pubkeyio = cStringIO.StringIO()
        key.save_pub_key_bio(BIO.File(pubkeyio))
        pubkey = pubkeyio.getvalue()

        return privkey, pubkey

    def __createAgentProxy(self, insecure = False):
        """
        Creates an agent proxy connected to the Plab central agent.
        Also caches the agent for later reuse.
        """
        if not self.__agentProxy:
Kirk Webb's avatar
Kirk Webb committed
271
            if verbose:
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
                print "Connecting to agent %s" % AGENTIP
            if insecure:
                args = (AGENTIP, agent.PORT)
            else:
                args = (AGENTIP, agent.PORT, agent.SSLPORT,
                        self.keyfile, self.certfile, self.cacertfile)
                self.__agentProxy = agentproxy.agentproxy(*args)
        return self.__agentProxy

    def __createNodemgrProxy(self, IP):
        """
        Creates a node manager proxy connected to this node's node
        manager.  Also caches the nodemgr for later reuse.
        """
        if not self.__nodemgrProxies[IP]:
Kirk Webb's avatar
Kirk Webb committed
287
            if verbose:
288 289 290 291 292 293 294 295 296 297 298
                print "Connecting to nodemgr on %s" % IP
            self.__nodemgrProxies[IP] = \
                                      nodemgrproxy.nodemgrproxy(IP,
                                                                nodemgr.PORT,
                                                                nodemgr.SSLPORT,
                                                                self.keyfile,
                                                                self.certfile,
                                                                self.cacertfile)
        return self.__nodemgrProxies[IP]