2cd48e7d3ec21beb043b78239073e7fac6b5d757
[plcapi.git] / PLC / Methods / SliceGetTicket.py
1 # $Id$
2 import os
3 import sys
4 from subprocess import Popen, PIPE, call
5 from tempfile import NamedTemporaryFile
6 from xml.sax.saxutils import escape, quoteattr, XMLGenerator
7
8 from PLC.Faults import *
9 from PLC.Slices import Slice, Slices
10 from PLC.Nodes import Node, Nodes
11 from PLC.Persons import Person, Persons
12 from PLC.SliceTags import SliceTag, SliceTags
13
14 from PLC.Methods.GetSliceTicket import GetSliceTicket
15
16 class PrettyXMLGenerator(XMLGenerator):
17     """
18     Adds indentation to the beginning and newlines to the end of
19     opening and closing tags.
20     """
21
22     def __init__(self, out = sys.stdout, encoding = "utf-8", indent = "", addindent = "", newl = ""):
23         XMLGenerator.__init__(self, out, encoding)
24         # XMLGenerator does not export _write()
25         self.write = self.ignorableWhitespace
26         self.indents = [indent]
27         self.addindent = addindent
28         self.newl = newl
29
30     def startDocument(self):
31         XMLGenerator.startDocument(self)
32
33     def startElement(self, name, attrs, indent = True, newl = True):
34         if indent:
35             self.ignorableWhitespace("".join(self.indents))
36         self.indents.append(self.addindent)
37
38         XMLGenerator.startElement(self, name, attrs)
39
40         if newl:
41             self.ignorableWhitespace(self.newl)
42
43     def characters(self, content):
44         # " to "
45         # ' to '
46         self.write(escape(content, {
47             '"': '"',
48             "'": ''',
49             }))
50
51     def endElement(self, name, indent = True, newl = True):
52         self.indents.pop()
53         if indent:
54             self.ignorableWhitespace("".join(self.indents))
55
56         XMLGenerator.endElement(self, name)
57
58         if newl:
59             self.ignorableWhitespace(self.newl)
60
61     def simpleElement(self, name, attrs = {}, indent = True, newl = True):
62         if indent:
63             self.ignorableWhitespace("".join(self.indents))
64
65         self.write('<' + name)
66         for (name, value) in attrs.items():
67             self.write(' %s=%s' % (name, quoteattr(value)))
68         self.write('/>')
69
70         if newl:
71             self.ignorableWhitespace(self.newl)
72
73 class SliceGetTicket(GetSliceTicket):
74     """
75     Deprecated. See GetSliceTicket.
76
77     Warning: This function exists solely for backward compatibility
78     with the old public PlanetLab 3.0 Node Manager, which will be
79     removed from service by 2007. This call is not intended to be used
80     by any other PLC except the public PlanetLab.
81     """
82
83     status = "deprecated"
84
85     def call(self, auth, slice_id_or_name):
86         slices = Slices(self.api, [slice_id_or_name])
87         if not slices:
88             raise PLCInvalidArgument, "No such slice"
89         slice = slices[0]
90
91         # Allow peers to obtain tickets for their own slices
92         if slice['peer_id'] is not None:
93             raise PLCInvalidArgument, "Not a local slice"
94
95         if slice['instantiation'] != 'delegated':
96             raise PLCInvalidArgument, "Not in delegated state"
97
98         nodes = Nodes(self.api, slice['node_ids']).dict()
99         persons = Persons(self.api, slice['person_ids']).dict()
100         slice_tags = SliceTags(self.api, slice['slice_tag_ids']).dict()
101
102         ticket = NamedTemporaryFile()
103
104         xml = PrettyXMLGenerator(out = ticket, encoding = self.api.encoding, indent = "", addindent = "  ", newl = "\n")
105         xml.startDocument()
106
107         # <ticket>
108         xml.startElement('ticket', {})
109
110         # <slice name="site_slice" id="12345" expiry="1138712648">
111         xml.startElement('slice',
112                          {'id': str(slice['slice_id']),
113                           'name': unicode(slice['name']),
114                           'expiry': unicode(int(slice['expires']))})
115         
116         # <nodes>
117         xml.startElement('nodes', {})
118         for node_id in slice['node_ids']:
119             if not nodes.has_key(node_id):
120                 continue
121             node = nodes[node_id]
122             # <node id="12345" hostname="node.site.domain"/>
123             xml.simpleElement('node',
124                               {'id': str(node['node_id']),
125                                'hostname': unicode(node['hostname'])})
126         # </nodes>
127         xml.endElement('nodes')
128
129         # <users>
130         xml.startElement('users', {})
131         for person_id in slice['person_ids']:
132             if not persons.has_key(person_id):
133                 continue
134             user = persons[person_id]
135             # <user person_id="12345" email="user@site.domain"/>
136             xml.simpleElement('user',
137                               {'person_id': unicode(user['person_id']),
138                                'email': unicode(user['email'])})
139         # </users>
140         xml.endElement('users')
141
142         # <rspec>
143         xml.startElement('rspec', {})
144         for slice_tag_id in slice['slice_tag_ids']:
145             if not slice_tags.has_key(slice_tag_id):
146                 continue
147             slice_tag = slice_tags[slice_tag_id]
148
149             name = slice_tag['name']
150             value = slice_tag['value']
151
152             def kbps_to_bps(kbps):
153                 bps = int(kbps) * 1000
154                 return bps
155
156             def max_kbyte_to_bps(max_kbyte):
157                 bps = int(max_kbyte) * 1000 * 8 / 24 / 60 / 60
158                 return bps
159
160             # XXX Used to support multiple named values for each attribute type
161             name_type_cast = {
162                 'cpu_share': ('nm_cpu_share', 'cpu_share', 'integer', int),
163
164                 'net_share': ('nm_net_share', 'rate', 'integer', int),
165                 'net_min_rate': ('nm_net_min_rate', 'rate', 'integer', int),
166                 'net_max_rate': ('nm_net_max_rate', 'rate', 'integer', int),
167                 'net_max_kbyte': ('nm_net_avg_rate', 'rate', 'integer', max_kbyte_to_bps),
168
169                 'net_i2_share': ('nm_net_exempt_share', 'rate', 'integer', int),
170                 'net_i2_min_rate': ('nm_net_exempt_min_rate', 'rate', 'integer', kbps_to_bps),
171                 'net_i2_max_rate': ('nm_net_exempt_max_rate', 'rate', 'integer', kbps_to_bps),
172                 'net_i2_max_kbyte': ('nm_net_exempt_avg_rate', 'rate', 'integer', max_kbyte_to_bps),
173
174                 'disk_max': ('nm_disk_quota', 'quota', 'integer', int),
175                 'plc_agent_version': ('plc_agent_version', 'version', 'string', str),
176                 'plc_slice_type': ('plc_slice_type', 'type', 'string', str),
177                 'plc_ticket_pubkey': ('plc_ticket_pubkey', 'key', 'string', str),
178                 }
179
180             if name == 'initscript':
181                 (attribute_name, value_name, type) = ('initscript', 'initscript_id', 'integer')
182                 value = slice_tag['slice_tag_id']
183             elif name in name_type_cast:
184                 (attribute_name, value_name, type, cast) = name_type_cast[name]
185                 value = cast(value)
186             else:
187                 attribute_name = value_name = name
188                 type = "string"
189
190             # <resource name="tag_type">
191             xml.startElement('resource', {'name': unicode(attribute_name)})
192
193             # <value name="element_name" type="element_type">
194             xml.startElement('value',
195                              {'name': unicode(value_name),
196                               'type': type},
197                              newl = False)
198             # element value
199             xml.characters(unicode(value))
200             # </value>
201             xml.endElement('value', indent = False)
202
203             # </resource>
204             xml.endElement('resource')
205         # </rspec>
206         xml.endElement('rspec')
207
208         # </slice>
209         xml.endElement('slice')
210
211         # Add signature template
212         xml.startElement('Signature', {'xmlns': "http://www.w3.org/2000/09/xmldsig#"})
213         xml.startElement('SignedInfo', {})
214         xml.simpleElement('CanonicalizationMethod', {'Algorithm': "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"})
215         xml.simpleElement('SignatureMethod', {'Algorithm': "http://www.w3.org/2000/09/xmldsig#rsa-sha1"})
216         xml.startElement('Reference', {'URI': ""})
217         xml.startElement('Transforms', {})
218         xml.simpleElement('Transform', {'Algorithm': "http://www.w3.org/2000/09/xmldsig#enveloped-signature"})
219         xml.endElement('Transforms')
220         xml.simpleElement('DigestMethod', {'Algorithm': "http://www.w3.org/2000/09/xmldsig#sha1"})
221         xml.simpleElement('DigestValue', {})
222         xml.endElement('Reference')
223         xml.endElement('SignedInfo')
224         xml.simpleElement('SignatureValue', {})
225         xml.endElement('Signature')
226
227         xml.endElement('ticket')
228         xml.endDocument()
229
230         if not hasattr(self.api.config, 'PLC_API_TICKET_KEY') or \
231            not os.path.exists(self.api.config.PLC_API_TICKET_KEY):
232             raise PLCAPIError, "Slice ticket signing key not found"
233
234         ticket.flush()
235
236         # Sign the ticket
237         p = Popen(["xmlsec1", "--sign",
238                    "--privkey-pem", self.api.config.PLC_API_TICKET_KEY,
239                    ticket.name],
240                   stdin = PIPE, stdout = PIPE, stderr = PIPE, close_fds = True)
241         signed_ticket = p.stdout.read()
242         err = p.stderr.read()
243         rc = p.wait()
244
245         ticket.close()
246
247         if rc:
248             raise PLCAPIError, err
249
250         return signed_ticket