GIR3.2_LAMP: lamp-sendmanifest.py

File lamp-sendmanifest.py, 19.7 KB (added by lnevers@bbn.com, 8 years ago)
Line 
1#!/usr/bin/env python
2
3import os
4import sys
5import getopt
6
7import xml.dom.minidom as dom
8from httplib import HTTPConnection, HTTPSConnection
9
10cert_file = os.environ['HOME'] + "/.ssl/encrypted.pem"
11key_file = os.environ['HOME'] + "/.ssl/encrypted.pem"
12
13if "HTTPS_CERT_FILE" in os.environ:
14    cert_file = os.environ["HTTPS_CERT_FILE"]
15
16if "HTTPS_KEY_FILE" in os.environ:
17    key_file = os.environ["HTTPS_KEY_FILE"]
18
19class SimpleClient:
20    """
21    Very simple client to send SOAP requests to perfSONAR service
22    """
23
24    def __init__(self, host, port, uri, cert=None, key=None):
25        self.host = host
26        self.port = port
27        self.uri  = uri
28        self.cert = cert
29        self.key = key
30   
31    def soapifyMessage(self, message):
32        headerString = """<SOAP-ENV:Envelope
33 xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
34 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
35 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
36 xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
37<SOAP-ENV:Header/>
38<SOAP-ENV:Body>
39%s
40</SOAP-ENV:Body>
41</SOAP-ENV:Envelope>
42""" % message
43        return headerString
44
45    def send_request(self, message, useSSL=False, issoap=False):
46        if useSSL:
47            conn = HTTPSConnection(self.host, self.port, self.key, self.cert)
48        else:
49            conn = HTTPConnection(self.host, self.port)
50           
51        conn.connect()
52        headers = {'SOAPAction':'', 'Content-Type': 'text/xml'}
53        if issoap == False:
54            message = self.soapifyMessage(message)
55        conn.request('POST', self.uri, message, headers)
56        resp = conn.getresponse()
57        response = resp.read()
58        conn.close()
59        return response
60
61
62# pS Namespaces
63UNIS_NS = "http://ogf.org/schema/network/topology/unis/20100528/"
64PSCONFIG_NS = "http://ogf.org/schema/network/topology/psconfig/20100716/"
65PROTOGENI_NS ="http://ogf.org/schema/network/topology/protogeni/20100716/"
66TS_EVENTTYPE = "http://ggf.org/ns/nmwg/topology/20070809"
67XQUERY_EVENTTYPE = "http://ggf.org/ns/nmwg/tools/org/perfsonar/service/lookup/xquery/1.0"
68
69# RSpec Namespaces
70RSPEC_NS = "http://protogeni.net/resources/rspec/0.2"
71LAMP_NS = "http://protogeni.net/resources/rspec/0.2/ext/lamp/1"
72
73host = "blackseal.damsl.cis.udel.edu"
74port = 8012
75uri = "/perfSONAR_PS/services/unis"
76
77if "UNIS_ADDRESS" in os.environ:
78    host = os.environ["UNIS_ADDRESS"]
79
80
81class RSpecXMLParsingException(Exception):
82    def __init__(self, msg):
83        self.msg = msg
84   
85    def __str__(self):
86        return self.msg
87
88   
89# TODO: This method needs refactoring.
90def manifest_to_unis(manifest, slice_id):
91    unis_dom = dom.getDOMImplementation().createDocument(UNIS_NS, "topology", None)
92    topology = unis_dom.documentElement
93    topology.setAttribute('id', 'genitopo')
94   
95    slice_id = slice_id.replace("urn:publicid:IDN+", "")
96    domain = unis_dom.createElementNS(UNIS_NS, "domain")
97    domain.setAttribute('id', create_urn(domain=slice_id))
98    topology.appendChild(domain)
99   
100    parsed = dict()
101    rspec = manifest.documentElement
102    rsnodes = rspec.getElementsByTagNameNS(RSPEC_NS, "node")
103    for rsnode in rsnodes:
104        node_virtual_id = rsnode.getAttribute('virtual_id')
105        unis_id = create_urn(domain=slice_id, node=node_virtual_id)
106        if unis_id in parsed:
107            raise RSpecXMLParsingException, \
108               "Two node elements found with same id (%s)" % node_virtual_id
109       
110        unis_node = unis_dom.createElementNS(UNIS_NS, "node")
111        unis_node.setAttribute('id', unis_id)
112       
113        parsed[unis_id] = unis_node
114         
115        # Add node's hostname as a unis:address of type 'hostname'
116        hostname = rsnode.getAttribute('hostname')
117        unis_address = create_text_element(unis_dom, UNIS_NS, "address", hostname)
118        unis_address.setAttribute('type', 'dns')
119        unis_node.appendChild(unis_address)
120       
121        unis_node_properties_bag = unis_dom.createElementNS(UNIS_NS, "nodePropertiesBag")
122        unis_node.appendChild(unis_node_properties_bag)
123       
124        unis_node_pgeni_properties = unis_dom.createElementNS(PROTOGENI_NS, "nodeProperties")
125        unis_node_properties_bag.appendChild(unis_node_pgeni_properties)
126       
127        # Most RSpec node attributes become attributes of the pgeni properties
128        # element, but we ignore a few because they're represented elsewhere
129        NODE_IGNORED_ATTRS = ('virtual_id', 'hostname', 'sshdport')
130        for i in range(rsnode.attributes.length):
131            attr = rsnode.attributes.item(i)
132            if attr.name in NODE_IGNORED_ATTRS:
133                continue
134            unis_node_pgeni_properties.setAttribute(attr.name, attr.value)
135       
136        # Now we process the child elements that we recognize
137        rsinterfaces = rsnode.getElementsByTagNameNS(RSPEC_NS, "interface")
138        for rsiface in rsinterfaces:
139            iface_virtual_id = rsiface.getAttribute('virtual_id')
140            unis_id = create_urn(domain=slice_id, node=node_virtual_id,
141                                 port=iface_virtual_id)
142            if unis_id in parsed:
143                raise RSpecXMLParsingException, \
144                   "Two interface elements found with same id (%s) in node %s" \
145                        % iface_virtual_id, node_virtual_id
146           
147            unis_port = unis_dom.createElementNS(UNIS_NS, "port")
148            unis_port.setAttribute('id', unis_id)
149           
150            parsed[unis_id] = unis_port
151           
152            iface_component_id = rsiface.getAttribute('component_id')
153            unis_port.appendChild(
154                create_text_element(unis_dom, UNIS_NS, "name", iface_component_id))
155           
156            unis_port_properties_bag = unis_dom.createElementNS(UNIS_NS, "portPropertiesBag")
157            unis_port.appendChild(unis_port_properties_bag)
158       
159            unis_port_pgeni_properties = unis_dom.createElementNS(PROTOGENI_NS, "portProperties")
160            unis_port_properties_bag.appendChild(unis_port_pgeni_properties)
161           
162            INTERFACE_IGNORED_ATTRS = ('virtual_id',)
163            for i in range(rsiface.attributes.length):
164                attr = rsiface.attributes.item(i)
165                if attr.name in INTERFACE_IGNORED_ATTRS:
166                    continue
167                unis_port_pgeni_properties.setAttribute(attr.name, attr.value)
168             
169            unis_node.appendChild(unis_port)
170           
171        # We expect a single lamp:config element and we basically just change
172        # the namespace to psconfig and clone the root element as a nodeProperties
173        rslamp_config = get_unique_xml_child(rsnode, LAMP_NS, 'config')
174        if rslamp_config:
175            unis_node_psconfig_properties = unis_dom.createElementNS(PSCONFIG_NS,
176                                                                     "nodeProperties")
177            unis_node_properties_bag.appendChild(unis_node_psconfig_properties)
178           
179            clone_children(rslamp_config, unis_node_psconfig_properties)
180       
181        # All the other child nodes of rspec node we clone into the pgeni extension
182        NODE_IGNORED_CHILDREN = ((LAMP_NS, 'config'), (RSPEC_NS, 'interface'))
183        clone_children(rsnode, unis_node_pgeni_properties, NODE_IGNORED_CHILDREN)
184       
185        domain.appendChild(unis_node)
186   
187    rslinks = rspec.getElementsByTagNameNS(RSPEC_NS, "link")
188    for rslink in rslinks:
189        link_virtual_id = rslink.getAttribute('virtual_id')
190        unis_id = create_urn(domain=slice_id, link=link_virtual_id)
191        if unis_id in parsed:
192            raise RSpecXMLParsingException, \
193               "Found two link elements with same id (%s)" % link_virtual_id
194       
195        # XXX: Note that if this RSpec link has more than two interface_refs
196        # it should actually be a UNIS network element. But we don't deal
197        # with this case yet (it will raise an error below).
198        unis_link = unis_dom.createElementNS(UNIS_NS, "link")
199        unis_link.setAttribute('id', unis_id)
200       
201        parsed[unis_id] = unis_link
202         
203        # XXX: We run away a little from the current NML consensus regarding
204        #   links. We basically add an attribute 'directed' set to 'false'
205        #   to identify the link as a bidirectional link, and make two
206        #   relation elements of type 'endPoint'.
207        unis_link.setAttribute('directed', 'false')
208       
209        # Add link's link_type element as unis:type'
210        link_type = get_unique_xml_child(rslink, RSPEC_NS, 'link_type')
211        if link_type:
212            unis_link.appendChild(
213                create_text_element(unis_dom, UNIS_NS, 'type',
214                                    link_type.getAttribute('type_name')))
215       
216        unis_link_properties_bag = unis_dom.createElementNS(UNIS_NS, "linkPropertiesBag")
217        unis_link.appendChild(unis_link_properties_bag)
218       
219        unis_link_pgeni_properties = unis_dom.createElementNS(PROTOGENI_NS, "linkProperties")
220        unis_link_properties_bag.appendChild(unis_link_pgeni_properties)
221       
222        LINK_IGNORED_ATTRS = ('virtual_id',)
223        for i in range(rslink.attributes.length):
224            attr = rslink.attributes.item(i)
225            if attr.name in LINK_IGNORED_ATTRS:
226                continue
227            unis_link_pgeni_properties.setAttribute(attr.name, attr.value)
228       
229       
230        interface_refs = rslink.getElementsByTagNameNS(RSPEC_NS, "interface_ref")
231        if len(interface_refs) != 2:
232            raise RSpecXMLParsingException, \
233                "Unsupported number of interface_refs in link %s" % link_virtual_id
234       
235        for iface_ref in interface_refs:
236            port_id_ref = create_urn(domain=slice_id,
237                                     node=iface_ref.getAttribute('virtual_node_id'),
238                                     port=iface_ref.getAttribute('virtual_interface_id'))
239           
240            if port_id_ref not in parsed:
241                raise RSpecXMLParsingException, \
242                    "Link %s references nonexistent interface" % link_virtual_id
243           
244            # This part is bizarre. RSpec puts the sliver, component urn,
245            # MAC and IP addresses of an iface as part of the interface_ref
246            unis_port = parsed[port_id_ref]
247           
248            mac = iface_ref.getAttribute('MAC')
249            if mac:
250                unis_port.appendChild(
251                    create_text_element(unis_dom, UNIS_NS, 'address', mac,
252                                        attributes=(('type', 'mac'),) ))
253           
254            ip = iface_ref.getAttribute('IP')
255            if ip:
256                unis_port.appendChild(
257                    create_text_element(unis_dom, UNIS_NS, 'address', ip,
258                                        attributes=(('type', 'ipv4'),) ))
259           
260            unis_port_pgeni_properties = \
261                unis_port.getElementsByTagNameNS(PROTOGENI_NS, "portProperties")
262            assert len(unis_port_pgeni_properties) == 1
263            unis_port_pgeni_properties = unis_port_pgeni_properties[0]
264           
265            IFACEREF_IGNORED_ATTRS = ('virtual_node_id', 'MAC', 'IP',
266                                      'virtual_interface_id')
267            for i in range(iface_ref.attributes.length):
268                attr = iface_ref.attributes.item(i)
269                if attr.name in IFACEREF_IGNORED_ATTRS:
270                    continue
271                unis_port_pgeni_properties.setAttribute(attr.name, attr.value)
272           
273            # Finally we can create the actual relation for this iface_ref
274            unis_relation = unis_dom.createElementNS(UNIS_NS, "relation")
275            unis_relation.setAttribute('type', 'endPoint')
276            unis_relation.appendChild(
277                create_text_element(unis_dom, UNIS_NS, 'portIdRef', port_id_ref))
278            unis_link.appendChild(unis_relation)
279       
280        # We clone everything else that's left
281        LINK_IGNORED_CHILDREN = ((RSPEC_NS, 'interface_ref'), (RSPEC_NS, 'link_type'))
282        clone_children(rslink, unis_link_pgeni_properties, LINK_IGNORED_CHILDREN)
283       
284        domain.appendChild(unis_link)
285   
286    # Alright, seems like we're done. Now we change all the namespaces to the
287    # appropriate UNIS based namespace and make sure tags are correct. This
288    # could have been done while processing the elements, but it's simpler to
289    # do here (we don't have to worry about the n levels of cloned children).
290    for e in domain.getElementsByTagNameNS(PROTOGENI_NS, "*"):
291        e.tagName = 'pgeni:' + e.localName
292   
293    for e in domain.getElementsByTagNameNS(RSPEC_NS, "*"):
294        e.namespaceURI = PROTOGENI_NS
295        e.tagName = 'pgeni:' + e.localName
296       
297    for e in domain.getElementsByTagNameNS(PSCONFIG_NS, "*"):
298        e.tagName = 'psconfig:' + e.localName
299           
300    for e in domain.getElementsByTagNameNS(LAMP_NS, "*"):
301        e.namespaceURI = PSCONFIG_NS
302        e.tagName = 'psconfig:' + e.localName
303       
304    # Now set the namespace globally in the document, minidom doesn't
305    # do this for us so we must do it manually. UNIS_NS is the default.
306    topology.setAttribute("xmlns", UNIS_NS)
307    topology.setAttribute("xmlns:pgeni", PROTOGENI_NS)
308    topology.setAttribute("xmlns:psconfig", PSCONFIG_NS)
309   
310    return unis_dom
311   
312   
313class Usage(Exception):
314    def __init__(self, msg):
315        self.msg = msg
316
317def main(argv=None):
318    if argv is None:
319        argv = sys.argv
320    try:
321        try:
322            opts, args = getopt.getopt(argv[1:], "h", ["help"])
323            if opts or (len(args) != 2 and len(args) != 3):
324                raise Usage('Not enough arguments')
325           
326            manifest_xml = args[0]
327            slice_id = args[1]
328           
329            try:
330                open(manifest_xml, 'r')
331            except IOError, msg:
332                raise Usage('Cannot open manifest: ' + msg)
333           
334            if not slice_id.startswith("urn:publicid:IDN+"):
335                raise Usage('Invalid slice urn')
336           
337            credential_xml = None
338            if len(args) == 3:
339                try:
340                    open(args[2], 'r')
341                    credential_xml = args[2]
342                except IOError, msg:
343                    raise Usage('Cannot open credential: ' + msg)
344
345        except getopt.error, msg:
346            raise Usage(msg)
347       
348        manifest_dom = dom.parse(manifest_xml)
349        unis_dom = manifest_to_unis(manifest_dom, slice_id)
350        print unis_dom
351       
352        # Clean spurious empty lines in message (toprettyxml() abuses them)
353        unis_str = ""
354        for line in unis_dom.toprettyxml().split("\n"):
355            if line.strip() != '' and not line.lstrip().startswith('<?xml '):
356                unis_str += line + "\n"
357       
358        credential = None
359        if credential_xml:
360            credential = get_slice_cred(credential_xml)
361       
362
363        # Don't do the above on messages with credentials!
364        message = make_UNISTSReplace_message(unis_str, credential)
365        print message
366        print "\n\n"
367        try:
368            client = SimpleClient(host=host, port=port, uri=uri, cert=cert_file, key=key_file)
369            response = client.send_request(message, useSSL=True)
370        except Exception, err:
371            print "Error contacting UNIS: " + str(err)
372            return
373       
374        print "Received:\n"
375        print response
376       
377    except Usage, err:
378        print >>sys.stderr, err.msg
379        print >>sys.stderr, "Usage: <manifest> <slice_urn> [credential]"
380        return 2
381   
382##################################
383# Utility methods
384##################################
385
386def get_slice_cred(fname):
387    f = open(fname, 'r')
388    return f.read()
389
390def make_credential_metadata(cred, metadata_id, metadata_idref):
391    return """
392  <nmwg:metadata id="%s">
393    <nmwg:subject metadataIdRef="%s">
394%s
395    </nmwg:subject>
396    <nmwg:eventType>http://perfsonar.net/ns/protogeni/auth/credential/1</nmwg:eventType>
397  </nmwg:metadata>
398""" % (metadata_id, metadata_idref, cred)
399
400
401def make_UNIS_request(type, eventType, subject="", data_content="", cred=None):
402    metadata_id = 'meta0'
403   
404    cred_metadata = ""
405    if cred:
406        cred_metadata = make_credential_metadata(cred, 'cred0', metadata_id)
407        metadata_id = 'cred0'
408
409    msg="""
410<nmwg:message type="%s" xmlns:nmwg="http://ggf.org/ns/nmwg/base/2.0/">
411  <nmwg:metadata id="meta0">
412%s
413    <nmwg:eventType>%s</nmwg:eventType>
414  </nmwg:metadata>
415%s
416  <nmwg:data id="data0" metadataIdRef="%s">
417%s
418  </nmwg:data>
419</nmwg:message>
420"""
421    return msg % (type, subject, eventType, cred_metadata, metadata_id, data_content)   
422   
423def make_UNISTSQueryAll_message(cred=None):
424    return make_UNIS_request("TSQueryRequest", TS_EVENTTYPE, cred=cred)
425
426def make_UNISTSAdd_message(topology, cred=None):
427    return make_UNIS_request("TSAddRequest", TS_EVENTTYPE,
428                             data_content=topology, cred=cred)
429
430def make_UNISTSReplace_message(topology, cred=None):
431    return make_UNIS_request("TSReplaceRequest", TS_EVENTTYPE,
432                             data_content=topology, cred=cred)
433
434def make_UNISLSQuery_message(xquery=None, cred=None):
435    if xquery is None:
436        xquery = """
437declare namespace nmwg="http://ggf.org/ns/nmwg/base/2.0/";
438/nmwg:store[@type="LSStore"]
439"""
440   
441    subject = """
442    <xquery:subject id="sub1" xmlns:xquery="http://ggf.org/ns/nmwg/tools/org/perfsonar/service/lookup/xquery/1.0/">
443%s
444    </xquery:subject>
445""" % xquery
446   
447    return make_UNIS_request("LSQueryRequest", XQUERY_EVENTTYPE,
448                             subject=subject, cred=cred)
449
450def create_urn(domain, node=None, port=None, link=None, service=None):
451    """
452    Create UNIS URN.
453   
454    Example if domain is udel.edu then the URN is
455    'urn:ogf:network:domain=udel.edu'
456    And if domain is udel.edu and node is stout then the URN is
457    'urn:ogf:network:domain=udel.edu:node=stout'
458    """
459    assert domain, "URN must be fully qualified; no domain provided"
460    urn = "urn:ogf:network:domain=%s" % domain
461   
462    if node != None:
463        urn = urn + ":node=%s" % node
464       
465    if port != None:
466        assert node != None, "URN must be fully qualified; no node given for port"
467        urn = urn + ":port=%s" % port
468   
469    if link != None:
470        assert node == None, "URN must be fully qualified; invalid link urn"
471        urn = urn + ":link=%s" % link
472   
473    if service != None:
474        assert node != None and port == None, "URN must be fully qualified; invalid service urn"
475        urn = urn + ":service=%s" % service
476   
477    return urn
478
479
480# As bizarre as it sounds, (mini)DOM doesn't provide a
481# getElementsByTag that gets from only the immediate children.
482def get_qualified_xml_children(element, ns, name):
483    elements = []
484    for child in element.childNodes:
485        if isinstance(child, dom.Element):
486            if child.namespaceURI == ns and child.localName == name:
487                elements.append(child)
488    return elements
489
490def get_unique_xml_child(element, ns, name):
491    # XXX: Maybe namespace could be None?
492    assert element != None and ns != None and name != None
493   
494    children = get_qualified_xml_children(element, ns, name)
495    if len(children) > 1:
496        raise Exception, element.localName + ": has more than one " + \
497                         name + " element!"
498   
499    if len(children) == 1:
500        return children[0]
501    return None
502
503def create_text_element(dom, ns, name, data, attributes=()):
504    if ns:
505        e = dom.createElementNS(ns, name)
506    else:
507        e = dom.createElement(name)
508   
509    e.appendChild(dom.createTextNode(data))
510   
511    for (attr_name, attr_value) in attributes:
512        e.setAttribute(attr_name, attr_value)
513       
514    return e
515
516def clone_children(node_from, node_to, ignore_list=()):
517    for child in node_from.childNodes:
518        if (child.namespaceURI, child.localName) in ignore_list:
519            continue
520        node_to.appendChild( child.cloneNode(True) )
521
522
523if __name__ == "__main__":
524    sys.exit(main())