Merge remote-tracking branch 'origin/pycurl' into planetlab-4_0-branch
[plcapi.git] / PLC / Peers.py
1 #
2 # Thierry Parmentelat - INRIA
3
4
5 import re
6 from types import StringTypes
7 from urlparse import urlparse
8
9 from PLC.Faults import *
10 from PLC.Parameter import Parameter, Mixed
11 from PLC.Filter import Filter
12 from PLC.Table import Row, Table
13 import PLC.Auth
14
15 from PLC.Sites import Site, Sites
16 from PLC.Persons import Person, Persons
17 from PLC.Keys import Key, Keys
18 from PLC.Nodes import Node, Nodes
19 from PLC.SliceAttributeTypes import SliceAttributeType, SliceAttributeTypes
20 from PLC.SliceAttributes import SliceAttribute, SliceAttributes
21 from PLC.Slices import Slice, Slices
22
23 class Peer(Row):
24     """
25     Stores the list of peering PLCs in the peers table. 
26     See the Row class for more details
27     """
28
29     table_name = 'peers'
30     primary_key = 'peer_id'
31     join_tables = ['peer_site', 'peer_person', 'peer_key', 'peer_node',
32                    'peer_slice_attribute_type', 'peer_slice_attribute', 'peer_slice']
33     fields = {
34         'peer_id': Parameter (int, "Peer identifier"),
35         'peername': Parameter (str, "Peer name"),
36         'peer_url': Parameter (str, "Peer API URL"),
37         'key': Parameter(str, "Peer GPG public key"),
38         'cacert': Parameter(str, "Peer SSL public certificate"),
39         ### cross refs
40         'site_ids': Parameter([int], "List of sites for which this peer is authoritative"),
41         'person_ids': Parameter([int], "List of users for which this peer is authoritative"),
42         'key_ids': Parameter([int], "List of keys for which this peer is authoritative"),
43         'node_ids': Parameter([int], "List of nodes for which this peer is authoritative"),
44         'slice_ids': Parameter([int], "List of slices for which this peer is authoritative"),
45         }
46
47     def validate_peername(self, peername):
48         if not len(peername):
49             raise PLCInvalidArgument, "Peer name must be specified"
50
51         conflicts = Peers(self.api, [peername])
52         for peer in conflicts:
53             if 'peer_id' not in self or self['peer_id'] != peer['peer_id']:
54                 raise PLCInvalidArgument, "Peer name already in use"
55
56         return peername
57
58     def validate_peer_url(self, url):
59         """
60         Validate URL. Must be HTTPS.
61         """
62
63         (scheme, netloc, path, params, query, fragment) = urlparse(url)
64         if scheme != "https":
65             raise PLCInvalidArgument, "Peer URL scheme must be https"
66
67         return url
68
69     def delete(self, commit = True):
70         """
71         Deletes this peer and all related entities.
72         """
73
74         assert 'peer_id' in self
75
76         # Remove all related entities
77         for obj in \
78             Slices(self.api, self['slice_ids']) + \
79             Keys(self.api, self['key_ids']) + \
80             Persons(self.api, self['person_ids']) + \
81             Nodes(self.api, self['node_ids']) + \
82             Sites(self.api, self['site_ids']):
83             assert obj['peer_id'] == self['peer_id']
84             obj.delete(commit = False)
85
86         # Mark as deleted
87         self['deleted'] = True
88         self.sync(commit)
89
90     def add_site(self, site, peer_site_id, commit = True):
91         """
92         Associate a local site entry with this peer.
93         """
94
95         add = Row.add_object(Site, 'peer_site')
96         add(self, site,
97             {'peer_id': self['peer_id'],
98              'site_id': site['site_id'],
99              'peer_site_id': peer_site_id},
100             commit = commit)
101
102     def add_person(self, person, peer_person_id, commit = True):
103         """
104         Associate a local user entry with this peer.
105         """
106
107         add = Row.add_object(Person, 'peer_person')
108         add(self, person,
109             {'peer_id': self['peer_id'],
110              'person_id': person['person_id'],
111              'peer_person_id': peer_person_id},
112             commit = commit)
113
114     def add_key(self, key, peer_key_id, commit = True):
115         """
116         Associate a local key entry with this peer.
117         """
118
119         add = Row.add_object(Key, 'peer_key')
120         add(self, key,
121             {'peer_id': self['peer_id'],
122              'key_id': key['key_id'],
123              'peer_key_id': peer_key_id},
124             commit = commit)
125
126     def add_node(self, node, peer_node_id, commit = True):
127         """
128         Associate a local node entry with this peer.
129         """
130
131         add = Row.add_object(Node, 'peer_node')
132         add(self, node,
133             {'peer_id': self['peer_id'],
134              'node_id': node['node_id'],
135              'peer_node_id': peer_node_id},
136             commit = commit)
137
138     def add_slice(self, slice, peer_slice_id, commit = True):
139         """
140         Associate a local slice entry with this peer.
141         """
142
143         add = Row.add_object(Slice, 'peer_slice')
144         add(self, slice,
145             {'peer_id': self['peer_id'],
146              'slice_id': slice['slice_id'],
147              'peer_slice_id': peer_slice_id},
148             commit = commit)
149
150     def connect(self, **kwds):
151         """
152         Connect to this peer via XML-RPC.
153         """
154
155         import xmlrpclib
156         from PLC.PyCurl import PyCurlTransport
157         self.server = xmlrpclib.ServerProxy(self['peer_url'],
158                                             PyCurlTransport(self['peer_url'], self['cacert']),
159                                             allow_none = 1, **kwds)
160
161     def add_auth(self, function, methodname, **kwds):
162         """
163         Sign the specified XML-RPC call and add an auth struct as the
164         first argument of the call.
165         """
166
167         def wrapper(*args, **kwds):
168             from PLC.GPG import gpg_sign
169             signature = gpg_sign(args,
170                                  self.api.config.PLC_ROOT_GPG_KEY,
171                                  self.api.config.PLC_ROOT_GPG_KEY_PUB,
172                                  methodname)
173
174             auth = {'AuthMethod': "gpg",
175                     'name': self.api.config.PLC_NAME,
176                     'signature': signature}
177
178             # Automagically add auth struct to every call
179             args = (auth,) + args
180
181             return function(*args)
182
183         return wrapper
184
185     def __getattr__(self, attr):
186         """
187         Returns a callable API function if attr is the name of a
188         PLCAPI function; otherwise, returns the specified attribute.
189         """
190
191         try:
192             # Figure out if the specified attribute is the name of a
193             # PLCAPI function. If so and the function requires an
194             # authentication structure as its first argument, return a
195             # callable that automagically adds an auth struct to the
196             # call.
197             methodname = attr
198             api_function = self.api.callable(methodname)
199             if api_function.accepts and \
200                (isinstance(api_function.accepts[0], PLC.Auth.Auth) or \
201                 (isinstance(api_function.accepts[0], Mixed) and \
202                  filter(lambda param: isinstance(param, Auth), api_function.accepts[0]))):
203                 function = getattr(self.server, methodname)
204                 return self.add_auth(function, methodname)
205         except Exception, err:
206             pass
207
208         if hasattr(self, attr):
209             return getattr(self, attr)
210         else:
211             raise AttributeError, "type object 'Peer' has no attribute '%s'" % attr
212
213 class Peers (Table):
214     """ 
215     Maps to the peers table in the database
216     """
217     
218     def __init__ (self, api, peer_filter = None, columns = None):
219         Table.__init__(self, api, Peer, columns)
220
221         sql = "SELECT %s FROM view_peers WHERE deleted IS False" % \
222               ", ".join(self.columns)
223
224         if peer_filter is not None:
225             if isinstance(peer_filter, (list, tuple, set)):
226                 # Separate the list into integers and strings
227                 ints = filter(lambda x: isinstance(x, (int, long)), peer_filter)
228                 strs = filter(lambda x: isinstance(x, StringTypes), peer_filter)
229                 peer_filter = Filter(Peer.fields, {'peer_id': ints, 'peername': strs})
230                 sql += " AND (%s) %s" % peer_filter.sql(api, "OR")
231             elif isinstance(peer_filter, dict):
232                 peer_filter = Filter(Peer.fields, peer_filter)
233                 sql += " AND (%s) %s" % peer_filter.sql(api, "AND")
234
235         self.selectall(sql)