- NULL out invalid creator_person_ids
[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(methodname, args,
170                                  self.api.config.PLC_ROOT_GPG_KEY,
171                                  self.api.config.PLC_ROOT_GPG_KEY_PUB)
172
173             auth = {'AuthMethod': "gpg",
174                     'name': self.api.config.PLC_NAME,
175                     'signature': signature}
176
177             # Automagically add auth struct to every call
178             args = (auth,) + args
179
180             return function(*args)
181
182         return wrapper
183
184     def __getattr__(self, attr):
185         """
186         Returns a callable API function if attr is the name of a
187         PLCAPI function; otherwise, returns the specified attribute.
188         """
189
190         try:
191             # Figure out if the specified attribute is the name of a
192             # PLCAPI function. If so and the function requires an
193             # authentication structure as its first argument, return a
194             # callable that automagically adds an auth struct to the
195             # call.
196             methodname = attr
197             api_function = self.api.callable(methodname)
198             if api_function.accepts and \
199                (isinstance(api_function.accepts[0], PLC.Auth.Auth) or \
200                 (isinstance(api_function.accepts[0], Mixed) and \
201                  filter(lambda param: isinstance(param, Auth), api_function.accepts[0]))):
202                 function = getattr(self.server, methodname)
203                 return self.add_auth(function, methodname)
204         except Exception, err:
205             pass
206
207         if hasattr(self, attr):
208             return getattr(self, attr)
209         else:
210             raise AttributeError, "type object 'Peer' has no attribute '%s'" % attr
211
212 class Peers (Table):
213     """ 
214     Maps to the peers table in the database
215     """
216     
217     def __init__ (self, api, peer_filter = None, columns = None):
218         Table.__init__(self, api, Peer, columns)
219
220         sql = "SELECT %s FROM view_peers WHERE deleted IS False" % \
221               ", ".join(self.columns)
222
223         if peer_filter is not None:
224             if isinstance(peer_filter, (list, tuple, set)):
225                 # Separate the list into integers and strings
226                 ints = filter(lambda x: isinstance(x, (int, long)), peer_filter)
227                 strs = filter(lambda x: isinstance(x, StringTypes), peer_filter)
228                 peer_filter = Filter(Peer.fields, {'peer_id': ints, 'peername': strs})
229                 sql += " AND (%s)" % peer_filter.sql(api, "OR")
230             elif isinstance(peer_filter, dict):
231                 peer_filter = Filter(Peer.fields, peer_filter)
232                 sql += " AND (%s)" % peer_filter.sql(api, "AND")
233
234         self.selectall(sql)