Merge from trunk
[plcapi.git] / trunk / PLC / Methods / SliceGetTicket.py
diff --git a/trunk/PLC/Methods/SliceGetTicket.py b/trunk/PLC/Methods/SliceGetTicket.py
new file mode 100644 (file)
index 0000000..64413c9
--- /dev/null
@@ -0,0 +1,249 @@
+import os
+import sys
+from subprocess import Popen, PIPE, call
+from tempfile import NamedTemporaryFile
+from xml.sax.saxutils import escape, quoteattr, XMLGenerator
+
+from PLC.Faults import *
+from PLC.Slices import Slice, Slices
+from PLC.Nodes import Node, Nodes
+from PLC.Persons import Person, Persons
+from PLC.SliceAttributes import SliceAttribute, SliceAttributes
+
+from PLC.Methods.GetSliceTicket import GetSliceTicket
+
+class PrettyXMLGenerator(XMLGenerator):
+    """
+    Adds indentation to the beginning and newlines to the end of
+    opening and closing tags.
+    """
+
+    def __init__(self, out = sys.stdout, encoding = "utf-8", indent = "", addindent = "", newl = ""):
+        XMLGenerator.__init__(self, out, encoding)
+        # XMLGenerator does not export _write()
+        self.write = self.ignorableWhitespace
+        self.indents = [indent]
+        self.addindent = addindent
+        self.newl = newl
+
+    def startDocument(self):
+        XMLGenerator.startDocument(self)
+
+    def startElement(self, name, attrs, indent = True, newl = True):
+        if indent:
+            self.ignorableWhitespace("".join(self.indents))
+        self.indents.append(self.addindent)
+
+        XMLGenerator.startElement(self, name, attrs)
+
+        if newl:
+            self.ignorableWhitespace(self.newl)
+
+    def characters(self, content):
+        # " to "
+        # ' to '
+        self.write(escape(content, {
+            '"': '"',
+            "'": ''',
+            }))
+
+    def endElement(self, name, indent = True, newl = True):
+        self.indents.pop()
+        if indent:
+            self.ignorableWhitespace("".join(self.indents))
+
+        XMLGenerator.endElement(self, name)
+
+        if newl:
+            self.ignorableWhitespace(self.newl)
+
+    def simpleElement(self, name, attrs = {}, indent = True, newl = True):
+        if indent:
+            self.ignorableWhitespace("".join(self.indents))
+
+        self.write('<' + name)
+        for (name, value) in attrs.items():
+            self.write(' %s=%s' % (name, quoteattr(value)))
+        self.write('/>')
+
+        if newl:
+            self.ignorableWhitespace(self.newl)
+
+class SliceGetTicket(GetSliceTicket):
+    """
+    Deprecated. See GetSliceTicket.
+
+    Warning: This function exists solely for backward compatibility
+    with the old public PlanetLab 3.0 Node Manager, which will be
+    removed from service by 2007. This call is not intended to be used
+    by any other PLC except the public PlanetLab.
+    """
+
+    status = "deprecated"
+
+    def call(self, auth, slice_id_or_name):
+        slices = Slices(self.api, [slice_id_or_name])
+        if not slices:
+            raise PLCInvalidArgument, "No such slice"
+        slice = slices[0]
+
+        # Allow peers to obtain tickets for their own slices
+        if slice['peer_id'] is not None:
+            raise PLCInvalidArgument, "Not a local slice"
+
+        if slice['instantiation'] != 'delegated':
+            raise PLCInvalidArgument, "Not in delegated state"
+
+        nodes = Nodes(self.api, slice['node_ids']).dict()
+        persons = Persons(self.api, slice['person_ids']).dict()
+        slice_attributes = SliceAttributes(self.api, slice['slice_attribute_ids']).dict()
+
+        ticket = NamedTemporaryFile()
+
+        xml = PrettyXMLGenerator(out = ticket, encoding = self.api.encoding, indent = "", addindent = "  ", newl = "\n")
+        xml.startDocument()
+
+        # <ticket>
+        xml.startElement('ticket', {})
+
+        # <slice name="site_slice" id="12345" expiry="1138712648">
+        xml.startElement('slice',
+                         {'id': str(slice['slice_id']),
+                          'name': unicode(slice['name']),
+                          'expiry': unicode(int(slice['expires']))})
+        
+        # <nodes>
+        xml.startElement('nodes', {})
+        for node_id in slice['node_ids']:
+            if not nodes.has_key(node_id):
+                continue
+            node = nodes[node_id]
+            # <node id="12345" hostname="node.site.domain"/>
+            xml.simpleElement('node',
+                              {'id': str(node['node_id']),
+                               'hostname': unicode(node['hostname'])})
+        # </nodes>
+        xml.endElement('nodes')
+
+        # <users>
+        xml.startElement('users', {})
+        for person_id in slice['person_ids']:
+            if not persons.has_key(person_id):
+                continue
+            user = persons[person_id]
+            # <user person_id="12345" email="user@site.domain"/>
+            xml.simpleElement('user',
+                              {'person_id': unicode(user['person_id']),
+                               'email': unicode(user['email'])})
+        # </users>
+        xml.endElement('users')
+
+        # <rspec>
+        xml.startElement('rspec', {})
+        for slice_attribute_id in slice['slice_attribute_ids']:
+            if not slice_attributes.has_key(slice_attribute_id):
+                continue
+            slice_attribute = slice_attributes[slice_attribute_id]
+
+            name = slice_attribute['name']
+            value = slice_attribute['value']
+
+            def kbps_to_bps(kbps):
+                bps = int(kbps) * 1000
+                return bps
+
+            def max_kbyte_to_bps(max_kbyte):
+                bps = int(max_kbyte) * 1000 * 8 / 24 / 60 / 60
+                return bps
+
+            # XXX Used to support multiple named values for each attribute type
+            name_type_cast = {
+                'cpu_share': ('nm_cpu_share', 'cpu_share', 'integer', int),
+
+                'net_share': ('nm_net_share', 'rate', 'integer', int),
+                'net_min_rate': ('nm_net_min_rate', 'rate', 'integer', int),
+                'net_max_rate': ('nm_net_max_rate', 'rate', 'integer', int),
+                'net_max_kbyte': ('nm_net_avg_rate', 'rate', 'integer', max_kbyte_to_bps),
+
+                'net_i2_share': ('nm_net_exempt_share', 'rate', 'integer', int),
+                'net_i2_min_rate': ('nm_net_exempt_min_rate', 'rate', 'integer', kbps_to_bps),
+                'net_i2_max_rate': ('nm_net_exempt_max_rate', 'rate', 'integer', kbps_to_bps),
+                'net_i2_max_kbyte': ('nm_net_exempt_avg_rate', 'rate', 'integer', max_kbyte_to_bps),
+
+                'disk_max': ('nm_disk_quota', 'quota', 'integer', int),
+                'plc_agent_version': ('plc_agent_version', 'version', 'string', str),
+                'plc_slice_type': ('plc_slice_type', 'type', 'string', str),
+                'plc_ticket_pubkey': ('plc_ticket_pubkey', 'key', 'string', str),
+                }
+
+            if name == 'initscript':
+                (attribute_name, value_name, type) = ('initscript', 'initscript_id', 'integer')
+                value = slice_attribute['slice_attribute_id']
+            elif name in name_type_cast:
+                (attribute_name, value_name, type, cast) = name_type_cast[name]
+                value = cast(value)
+            else:
+                attribute_name = value_name = name
+                type = "string"
+
+            # <resource name="slice_attribute_type">
+            xml.startElement('resource', {'name': unicode(attribute_name)})
+
+            # <value name="element_name" type="element_type">
+            xml.startElement('value',
+                             {'name': unicode(value_name),
+                              'type': type},
+                             newl = False)
+            # element value
+            xml.characters(unicode(value))
+            # </value>
+            xml.endElement('value', indent = False)
+
+            # </resource>
+            xml.endElement('resource')
+        # </rspec>
+        xml.endElement('rspec')
+
+        # </slice>
+        xml.endElement('slice')
+
+        # Add signature template
+        xml.startElement('Signature', {'xmlns': "http://www.w3.org/2000/09/xmldsig#"})
+        xml.startElement('SignedInfo', {})
+        xml.simpleElement('CanonicalizationMethod', {'Algorithm': "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"})
+        xml.simpleElement('SignatureMethod', {'Algorithm': "http://www.w3.org/2000/09/xmldsig#rsa-sha1"})
+        xml.startElement('Reference', {'URI': ""})
+        xml.startElement('Transforms', {})
+        xml.simpleElement('Transform', {'Algorithm': "http://www.w3.org/2000/09/xmldsig#enveloped-signature"})
+        xml.endElement('Transforms')
+        xml.simpleElement('DigestMethod', {'Algorithm': "http://www.w3.org/2000/09/xmldsig#sha1"})
+        xml.simpleElement('DigestValue', {})
+        xml.endElement('Reference')
+        xml.endElement('SignedInfo')
+        xml.simpleElement('SignatureValue', {})
+        xml.endElement('Signature')
+
+        xml.endElement('ticket')
+        xml.endDocument()
+
+        if not hasattr(self.api.config, 'PLC_API_TICKET_KEY') or \
+           not os.path.exists(self.api.config.PLC_API_TICKET_KEY):
+            raise PLCAPIError, "Slice ticket signing key not found"
+
+        ticket.flush()
+
+        # Sign the ticket
+        p = Popen(["xmlsec1", "--sign",
+                   "--privkey-pem", self.api.config.PLC_API_TICKET_KEY,
+                   ticket.name],
+                  stdin = PIPE, stdout = PIPE, stderr = PIPE, close_fds = True)
+        signed_ticket = p.stdout.read()
+        err = p.stderr.read()
+        rc = p.wait()
+
+        ticket.close()
+
+        if rc:
+            raise PLCAPIError, err
+
+        return signed_ticket