4 # Thierry Parmentelat - INRIA
8 from types import StringTypes
9 from urlparse import urlparse
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 # attempt to manually update the 'hrn' tag
169 root_auth = self['hrn_root']
170 sites = Sites(self.api, node['site_id'], ['login_base'])
172 login_base = site['login_base']
173 hrn = hostname_to_hrn(root_auth, login_base, node['hostname'])
175 Node(self.api, node).update_tags(tags)
177 def remove_node(self, node, commit = True):
179 Unassociate a node with this peer.
182 remove = Row.remove_object(Node, 'peer_node')
183 remove(self, node, commit)
184 # attempt to manually update the 'hrn' tag
185 root_auth = self.api.config.PLC_HRN_ROOT
186 sites = Sites(self.api, node['site_id'], ['login_base'])
188 login_base = site['login_base']
189 hrn = hostname_to_hrn(root_auth, login_base, node['hostname'])
191 Node(self.api, node).update_tags(tags)
193 def add_slice(self, slice, peer_slice_id, commit = True):
195 Associate a local slice entry with this peer.
198 add = Row.add_object(Slice, 'peer_slice')
200 {'peer_id': self['peer_id'],
201 'slice_id': slice['slice_id'],
202 'peer_slice_id': peer_slice_id},
205 def remove_slice(self, slice, commit = True):
207 Unassociate a slice with this peer.
210 remove = Row.remove_object(Slice, 'peer_slice')
211 remove(self, slice, commit)
213 def connect(self, **kwds):
215 Connect to this peer via XML-RPC.
219 from PLC.PyCurl import PyCurlTransport
220 self.server = xmlrpclib.ServerProxy(self['peer_url'],
221 PyCurlTransport(self['peer_url'], self['cacert']),
222 allow_none = 1, **kwds)
224 def add_auth(self, function, methodname, **kwds):
226 Sign the specified XML-RPC call and add an auth struct as the
227 first argument of the call.
230 def wrapper(*args, **kwds):
231 from PLC.GPG import gpg_sign
232 signature = gpg_sign(args,
233 self.api.config.PLC_ROOT_GPG_KEY,
234 self.api.config.PLC_ROOT_GPG_KEY_PUB,
237 auth = {'AuthMethod': "gpg",
238 'name': self.api.config.PLC_NAME,
239 'signature': signature}
241 # Automagically add auth struct to every call
242 args = (auth,) + args
244 return function(*args)
248 def __getattr__(self, attr):
250 Returns a callable API function if attr is the name of a
251 PLCAPI function; otherwise, returns the specified attribute.
255 # Figure out if the specified attribute is the name of a
256 # PLCAPI function. If so and the function requires an
257 # authentication structure as its first argument, return a
258 # callable that automagically adds an auth struct to the
261 api_function = self.api.callable(methodname)
262 if api_function.accepts and \
263 (isinstance(api_function.accepts[0], PLC.Auth.Auth) or \
264 (isinstance(api_function.accepts[0], Mixed) and \
265 filter(lambda param: isinstance(param, Auth), api_function.accepts[0]))):
266 function = getattr(self.server, methodname)
267 return self.add_auth(function, methodname)
268 except Exception, err:
271 if hasattr(self, attr):
272 return getattr(self, attr)
274 raise AttributeError, "type object 'Peer' has no attribute '%s'" % attr
278 Maps to the peers table in the database
281 def __init__ (self, api, peer_filter = None, columns = None):
282 Table.__init__(self, api, Peer, columns)
284 sql = "SELECT %s FROM view_peers WHERE deleted IS False" % \
285 ", ".join(self.columns)
287 if peer_filter is not None:
288 if isinstance(peer_filter, (list, tuple, set)):
289 # Separate the list into integers and strings
290 ints = filter(lambda x: isinstance(x, (int, long)), peer_filter)
291 strs = filter(lambda x: isinstance(x, StringTypes), peer_filter)
292 peer_filter = Filter(Peer.fields, {'peer_id': ints, 'peername': strs})
293 sql += " AND (%s) %s" % peer_filter.sql(api, "OR")
294 elif isinstance(peer_filter, dict):
295 peer_filter = Filter(Peer.fields, peer_filter)
296 sql += " AND (%s) %s" % peer_filter.sql(api, "AND")