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