#!/usr/local/bin/python
import os,sys
from BaseHTTPServer import BaseHTTPRequestHandler
from ZSI import *
from M2Crypto import SSL
from M2Crypto.m2xmlrpclib import SSL_Transport
from M2Crypto.SSL.SSLServer import SSLServer
import M2Crypto.httpslib
import xmlrpclib
import re
import random
import string
import subprocess
import tempfile
from fedd_services import *
from fedd_internal_services import *
from fedd_util import *
import parse_detail
from service_error import *
import logging
# Configure loggers to dump to /dev/null which avoids errors if calling classes
# don't configure them.
class nullHandler(logging.Handler):
def emit(self, record): pass
fl = logging.getLogger("fedd.allocate.local")
fl.addHandler(nullHandler())
fl = logging.getLogger("fedd.allocate.remote")
fl.addHandler(nullHandler())
class fedd_allocate_project_local:
"""
Allocate projects on this machine in response to an access request.
"""
def __init__(self, dp=False, url=None, certs=None):
"""
Initializer. Parses a configuration if one is given.
"""
self.dynamic_projects = dp
self.wap = '/usr/testbed/sbin/wap'
self.newproj = '/usr/testbed/sbin/newproj'
self.mkproj = '/usr/testbed/sbin/mkproj'
self.grantnodetype = '/usr/testbed/sbin/grantnodetype'
self.log = logging.getLogger("fedd.allocate.local")
# Internal services are SOAP only
self.soap_services = {\
"AllocateProject": make_soap_handler(\
AllocateProjectRequestMessage.typecode,\
self.dynamic_project, AllocateProjectResponseMessage,\
"AllocateProjectResponseBody")\
}
self.xmlrpc_services = { }
def random_string(self, s, n=3):
"""Append n random ASCII characters to s and return the string"""
rv = s
for i in range(0,n):
rv += random.choice(string.ascii_letters)
return rv
def write_attr_xml(self, file, root, lines):
"""
Write an emulab config file for a dynamic project.
Format is lines[1]
"""
# Convert a pair to an attribute line
out_attr = lambda a,v : \
'%s' % (a, v)
f = os.fdopen(file, "w")
f.write("<%s>\n" % root)
f.write("\n".join([out_attr(*l) for l in lines]))
f.write("%s>\n" % root)
f.close()
def dynamic_project(self, req, fedid=None):
"""
Create a dynamic project with ssh access
Req includes the project and resources as a dictionary
"""
# tempfiles for the parameter files
uf, userfile = tempfile.mkstemp(prefix="usr", suffix=".xml",
dir="/tmp")
pf, projfile = tempfile.mkstemp(prefix="proj", suffix=".xml",
dir="/tmp")
if req.has_key('AllocateProjectRequestBody') and \
req['AllocateProjectRequestBody'].has_key('project'):
proj = req['AllocateProjectRequestBody']['project']
else:
raise service_error(service_error.req,
"Badly formed allocation request")
# Take the first user and ssh key
name = proj.get('name', None) or self.random_string("proj",4)
user = proj.get('user', None)
if user != None:
user = user[0] # User is a list, take the first entry
if not user.has_key("userID"):
uname = self.random_string("user", 3)
else:
uid = proj['userID']
# XXX: fedid
uname = uid.get('localname', None) or \
uid.get('kerberosUsername', None) or \
uid.get('uri', None)
if uname == None:
raise fedd_proj.service_error(fedd_proj.service_error.req,
"No ID for user");
access = user.get('access', None)
if access != None:
ssh = access[0].get('sshPubkey', None)
if ssh == None:
raise fedd_proj.service_error(fedd_proj.service_error.req,
"No ssh key for user");
else:
raise fedd_proj.service_error(fedd_proj.service_error.req,
"No access information for project");
# uname, name and ssh are set
user_fields = [
("name", "Federation User %s" % uname),
("email", "%s-fed@isi.deterlab.net" % uname),
("password", self.random_string("", 8)),
("login", uname),
("address", "4676 Admiralty"),
("city", "Marina del Rey"),
("state", "CA"),
("zip", "90292"),
("country", "USA"),
("phone", "310-448-9190"),
("title", "None"),
("affiliation", "USC/ISI"),
("pubkey", ssh)
]
proj_fields = [
("name", name),
("short description", "dynamic federated project"),
("URL", "http://www.isi.edu/~faber"),
("funders", "USC/USU"),
("long description", "Federation access control"),
("public", "1"),
("num_pcs", "100"),
("linkedtous", "1"),
("newuser_xml", userfile)
]
# Write out the files
self.write_attr_xml(uf, "user", user_fields)
self.write_attr_xml(pf, "project", proj_fields)
# Generate the commands (only grantnodetype's are dynamic)
cmds = [
(self.wap, self.newproj, projfile),
(self.wap, self.mkproj, name)
]
# Add commands to grant access to any resources in the request.
for nt in [ h for r in req.get('resources', []) \
if r.has_key('node') and r['node'].has_key('hardware')\
for h in r['node']['hardware'] ] :
cmds.append((self.wap, self.grantnodetype, '-p', name, nt))
# Create the projects
rc = 0
for cmd in cmds:
self.log.debug("[dynamic_project]: %s" % ' '.join(cmd))
if self.dynamic_projects:
try:
rc = subprocess.call(cmd)
except OSerror, e:
raise fedd_proj.service_error(\
fedd_proj.service_error.internal,
"Dynamic project subprocess creation error "+ \
"[%s] (%s)" % (cmd[1], e.strerror))
if rc != 0:
raise fedd_proj.service_error(\
fedd_proj.service_error.internal,
"Dynamic project subprocess error " +\
"[%s] (%d)" % (cmd[1], rc))
# Clean up tempfiles
os.unlink(userfile)
os.unlink(projfile)
rv = {\
'project': {\
'name': { 'localname': name },
'user' : [ {\
'userID': { 'localname' : uname },
'access': [ { 'sshPubkey' : ssh } ],
} ]\
}\
}
return rv
class fedd_allocate_project_remote:
"""
Allocate projects on a remote machine using the internal SOAP interface
"""
def __init__(self, dp=False, url=None, certs=None):
"""
Initializer. Parses a configuration if one is given.
"""
self.dynamic_projects = dp
self.url = url
if certs != None and isinstance(certs, type(tuple())):
self.cert_file, self.trusted_certs, self.cert_pwd = certs
else:
self.cert_file, self.trusted_certs, self.cert_pwd = \
(None, None, None)
self.soap_services = { }
self.xmlrpc_services = { }
def dynamic_project(self, req, fedid=None):
"""
Send req on to a remote project instantiator.
Req is just the projectAllocType object. This function re-wraps it.
It also rethrows any faults.
"""
# No retry loop here. Proxy servers must correctly authenticate
# themselves without help
try:
ctx = fedd_ssl_context(self.cert_file, self.trusted_certs,
password=self.cert_pwd)
except SSL.SSLError:
raise service_error(service_error.server_config,
"Server certificates misconfigured")
loc = feddInternalServiceLocator();
port = loc.getfeddInternalPortType(self.url,
transport=M2Crypto.httpslib.HTTPSConnection,
transdict={ 'ssl_context' : ctx })
if req.has_key('AllocateProjectRequestBody'):
req = req['AllocateProjectRequestBody']
else:
raise service_error(service_error.req, "Bad formated request");
# Reconstruct the full request message
msg = AllocateProjectRequestMessage()
msg.set_element_AllocateProjectRequestBody(
pack_soap(msg, "AllocateProjectRequestBody", req))
try:
resp = port.AllocateProject(msg)
except ZSI.ParseException, e:
raise service_error(service_error.proxy,
"Bad format message (XMLRPC??): %s" % str(e))
r = unpack_soap(resp)
if r.has_key('AllocateProjectResponseBody'):
return r['AllocateProjectResponseBody']
else:
raise service_error(service_error.proxy, "Bad proxy response")