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