2 # Thierry Parmentelat - INRIA
6 from types import StringTypes
8 from urlparse import urlparse
11 from PLC.Debug import log
12 from PLC.Faults import *
13 from PLC.Namespace import hostname_to_hrn
14 from PLC.Parameter import Parameter, Mixed
15 from PLC.Filter import Filter
16 from PLC.Table import Row, Table
17 from PLC.Sites import Site, Sites
18 from PLC.Persons import Person, Persons
19 from PLC.Keys import Key, Keys
20 from PLC.Nodes import Node, Nodes
21 from PLC.TagTypes import TagType, TagTypes
22 from PLC.NodeTags import NodeTag, NodeTags
23 from PLC.SliceTags import SliceTag, SliceTags
24 from PLC.Slices import Slice, Slices
28 Stores the list of peering PLCs in the peers table.
29 See the Row class for more details
33 primary_key = 'peer_id'
34 join_tables = ['peer_site', 'peer_person', 'peer_key', 'peer_node', 'peer_slice']
36 'peer_id': Parameter (int, "Peer identifier"),
37 'peername': Parameter (str, "Peer name"),
38 'peer_url': Parameter (str, "Peer API URL"),
39 'key': Parameter(str, "Peer GPG public key"),
40 'cacert': Parameter(str, "Peer SSL public certificate"),
41 'shortname' : Parameter(str, "Peer short name"),
42 'hrn_root' : Parameter(str, "Root of this peer in a hierarchical naming space"),
44 'site_ids': Parameter([int], "List of sites for which this peer is authoritative"),
45 'person_ids': Parameter([int], "List of users for which this peer is authoritative"),
46 'key_ids': Parameter([int], "List of keys for which this peer is authoritative"),
47 'node_ids': Parameter([int], "List of nodes for which this peer is authoritative"),
48 'slice_ids': Parameter([int], "List of slices for which this peer is authoritative"),
51 def validate_peername(self, peername):
53 raise PLCInvalidArgument, "Peer name must be specified"
55 conflicts = Peers(self.api, [peername])
56 for peer in conflicts:
57 if 'peer_id' not in self or self['peer_id'] != peer['peer_id']:
58 raise PLCInvalidArgument, "Peer name already in use"
62 def validate_peer_url(self, url):
64 Validate URL. Must be HTTPS.
67 (scheme, netloc, path, params, query, fragment) = urlparse(url)
69 raise PLCInvalidArgument, "Peer URL scheme must be https"
71 raise PLCInvalidArgument, "Peer URL should end with /"
75 def delete(self, commit = True):
77 Deletes this peer and all related entities.
80 assert 'peer_id' in self
82 # Remove all related entities
84 Slices(self.api, self['slice_ids']) + \
85 Keys(self.api, self['key_ids']) + \
86 Persons(self.api, self['person_ids']) + \
87 Nodes(self.api, self['node_ids']) + \
88 Sites(self.api, self['site_ids']):
89 assert obj['peer_id'] == self['peer_id']
90 obj.delete(commit = False)
93 self['deleted'] = True
96 def add_site(self, site, peer_site_id, commit = True):
98 Associate a local site entry with this peer.
101 add = Row.add_object(Site, 'peer_site')
103 {'peer_id': self['peer_id'],
104 'site_id': site['site_id'],
105 'peer_site_id': peer_site_id},
108 def remove_site(self, site, commit = True):
110 Unassociate a site with this peer.
113 remove = Row.remove_object(Site, 'peer_site')
114 remove(self, site, commit)
116 def add_person(self, person, peer_person_id, commit = True):
118 Associate a local user entry with this peer.
121 add = Row.add_object(Person, 'peer_person')
123 {'peer_id': self['peer_id'],
124 'person_id': person['person_id'],
125 'peer_person_id': peer_person_id},
128 def remove_person(self, person, commit = True):
130 Unassociate a site with this peer.
133 remove = Row.remove_object(Person, 'peer_person')
134 remove(self, person, commit)
136 def add_key(self, key, peer_key_id, commit = True):
138 Associate a local key entry with this peer.
141 add = Row.add_object(Key, 'peer_key')
143 {'peer_id': self['peer_id'],
144 'key_id': key['key_id'],
145 'peer_key_id': peer_key_id},
148 def remove_key(self, key, commit = True):
150 Unassociate a key with this peer.
153 remove = Row.remove_object(Key, 'peer_key')
154 remove(self, key, commit)
156 def add_node(self, node, peer_node_id, commit = True):
158 Associate a local node entry with this peer.
161 add = Row.add_object(Node, 'peer_node')
163 {'peer_id': self['peer_id'],
164 'node_id': node['node_id'],
165 'peer_node_id': peer_node_id},
168 sites = Sites(self.api, node['site_id'], ['login_base'])
170 login_base = site['login_base']
172 # attempt to manually update the 'hrn' tag with the remote prefix
173 hrn_root = self['hrn_root']
174 hrn = hostname_to_hrn(hrn_root, login_base, node['hostname'])
176 Node(self.api, node).update_tags(tags)
178 print >>log, "WARNING: (beg) could not find out hrn on hostname=%s"%node['hostname']
179 traceback.print_exc(5,log)
180 print >>log, "WARNING: (end) could not find out hrn on hostname=%s"%node['hostname']
182 def remove_node(self, node, commit = True):
184 Unassociate a node with this peer.
187 remove = Row.remove_object(Node, 'peer_node')
188 remove(self, node, commit)
189 # attempt to manually update the 'hrn' tag now that the node is local
190 root_auth = self.api.config.PLC_HRN_ROOT
191 sites = Sites(self.api, node['site_id'], ['login_base'])
193 login_base = site['login_base']
194 hrn = hostname_to_hrn(root_auth, login_base, node['hostname'])
196 Node(self.api, node).update_tags(tags)
198 def add_slice(self, slice, peer_slice_id, commit = True):
200 Associate a local slice entry with this peer.
203 add = Row.add_object(Slice, 'peer_slice')
205 {'peer_id': self['peer_id'],
206 'slice_id': slice['slice_id'],
207 'peer_slice_id': peer_slice_id},
210 def remove_slice(self, slice, commit = True):
212 Unassociate a slice with this peer.
215 remove = Row.remove_object(Slice, 'peer_slice')
216 remove(self, slice, commit)
218 def connect(self, **kwds):
220 Connect to this peer via XML-RPC.
224 from PLC.PyCurl import PyCurlTransport
225 self.server = xmlrpclib.ServerProxy(self['peer_url'],
226 PyCurlTransport(self['peer_url'], self['cacert']),
227 allow_none = 1, **kwds)
229 def add_auth(self, function, methodname, **kwds):
231 Sign the specified XML-RPC call and add an auth struct as the
232 first argument of the call.
235 def wrapper(*args, **kwds):
236 from PLC.GPG import gpg_sign
237 signature = gpg_sign(args,
238 self.api.config.PLC_ROOT_GPG_KEY,
239 self.api.config.PLC_ROOT_GPG_KEY_PUB,
242 auth = {'AuthMethod': "gpg",
243 'name': self.api.config.PLC_NAME,
244 'signature': signature}
246 # Automagically add auth struct to every call
247 args = (auth,) + args
249 return function(*args)
253 def __getattr__(self, attr):
255 Returns a callable API function if attr is the name of a
256 PLCAPI function; otherwise, returns the specified attribute.
260 # Figure out if the specified attribute is the name of a
261 # PLCAPI function. If so and the function requires an
262 # authentication structure as its first argument, return a
263 # callable that automagically adds an auth struct to the
266 api_function = self.api.callable(methodname)
267 if api_function.accepts and \
268 (isinstance(api_function.accepts[0], PLC.Auth.Auth) or \
269 (isinstance(api_function.accepts[0], Mixed) and \
270 filter(lambda param: isinstance(param, Auth), api_function.accepts[0]))):
271 function = getattr(self.server, methodname)
272 return self.add_auth(function, methodname)
273 except Exception, err:
276 if hasattr(self, attr):
277 return getattr(self, attr)
279 raise AttributeError, "type object 'Peer' has no attribute '%s'" % attr
283 Maps to the peers table in the database
286 def __init__ (self, api, peer_filter = None, columns = None):
287 Table.__init__(self, api, Peer, columns)
289 sql = "SELECT %s FROM view_peers WHERE deleted IS False" % \
290 ", ".join(self.columns)
292 if peer_filter is not None:
293 if isinstance(peer_filter, (list, tuple, set)):
294 # Separate the list into integers and strings
295 ints = filter(lambda x: isinstance(x, (int, long)), peer_filter)
296 strs = filter(lambda x: isinstance(x, StringTypes), peer_filter)
297 peer_filter = Filter(Peer.fields, {'peer_id': ints, 'peername': strs})
298 sql += " AND (%s) %s" % peer_filter.sql(api, "OR")
299 elif isinstance(peer_filter, dict):
300 peer_filter = Filter(Peer.fields, peer_filter)
301 sql += " AND (%s) %s" % peer_filter.sql(api, "AND")
302 elif isinstance(peer_filter, (int, long)):
303 peer_filter = Filter(Peer.fields, {'peer_id': peer_filter})
304 sql += " AND (%s) %s" % peer_filter.sql(api, "AND")
305 elif isinstance(peer_filter, StringTypes):
306 peer_filter = Filter(Peer.fields, {'peername': peer_filter})
307 sql += " AND (%s) %s" % peer_filter.sql(api, "AND")
309 raise PLCInvalidArgument, "Wrong peer filter %r"%peer_filter