4 # Thierry Parmentelat - INRIA
8 from types import StringTypes
9 from urlparse import urlparse
11 from PLC.Faults import *
12 from PLC.Parameter import Parameter, Mixed
13 from PLC.Filter import Filter
14 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.SliceTags import SliceTag, SliceTags
23 from PLC.Slices import Slice, Slices
27 Stores the list of peering PLCs in the peers table.
28 See the Row class for more details
32 primary_key = 'peer_id'
33 join_tables = ['peer_site', 'peer_person', 'peer_key', 'peer_node', 'peer_slice']
35 'peer_id': Parameter (int, "Peer identifier"),
36 'peername': Parameter (str, "Peer name"),
37 'peer_url': Parameter (str, "Peer API URL"),
38 'key': Parameter(str, "Peer GPG public key"),
39 'cacert': Parameter(str, "Peer SSL public certificate"),
40 'shortname' : Parameter(str, "Peer short name"),
41 'hrn_root' : Parameter(str, "Root of this peer in a hierarchical naming space"),
43 'site_ids': Parameter([int], "List of sites for which this peer is authoritative"),
44 'person_ids': Parameter([int], "List of users for which this peer is authoritative"),
45 'key_ids': Parameter([int], "List of keys for which this peer is authoritative"),
46 'node_ids': Parameter([int], "List of nodes for which this peer is authoritative"),
47 'slice_ids': Parameter([int], "List of slices for which this peer is authoritative"),
50 def validate_peername(self, peername):
52 raise PLCInvalidArgument, "Peer name must be specified"
54 conflicts = Peers(self.api, [peername])
55 for peer in conflicts:
56 if 'peer_id' not in self or self['peer_id'] != peer['peer_id']:
57 raise PLCInvalidArgument, "Peer name already in use"
61 def validate_peer_url(self, url):
63 Validate URL. Must be HTTPS.
66 (scheme, netloc, path, params, query, fragment) = urlparse(url)
68 raise PLCInvalidArgument, "Peer URL scheme must be https"
70 raise PLCInvalidArgument, "Peer URL should end with /"
74 def delete(self, commit = True):
76 Deletes this peer and all related entities.
79 assert 'peer_id' in self
81 # Remove all related entities
83 Slices(self.api, self['slice_ids']) + \
84 Keys(self.api, self['key_ids']) + \
85 Persons(self.api, self['person_ids']) + \
86 Nodes(self.api, self['node_ids']) + \
87 Sites(self.api, self['site_ids']):
88 assert obj['peer_id'] == self['peer_id']
89 obj.delete(commit = False)
92 self['deleted'] = True
95 def add_site(self, site, peer_site_id, commit = True):
97 Associate a local site entry with this peer.
100 add = Row.add_object(Site, 'peer_site')
102 {'peer_id': self['peer_id'],
103 'site_id': site['site_id'],
104 'peer_site_id': peer_site_id},
107 def remove_site(self, site, commit = True):
109 Unassociate a site with this peer.
112 remove = Row.remove_object(Site, 'peer_site')
113 remove(self, site, commit)
115 def add_person(self, person, peer_person_id, commit = True):
117 Associate a local user entry with this peer.
120 add = Row.add_object(Person, 'peer_person')
122 {'peer_id': self['peer_id'],
123 'person_id': person['person_id'],
124 'peer_person_id': peer_person_id},
127 def remove_person(self, person, commit = True):
129 Unassociate a site with this peer.
132 remove = Row.remove_object(Person, 'peer_person')
133 remove(self, person, commit)
135 def add_key(self, key, peer_key_id, commit = True):
137 Associate a local key entry with this peer.
140 add = Row.add_object(Key, 'peer_key')
142 {'peer_id': self['peer_id'],
143 'key_id': key['key_id'],
144 'peer_key_id': peer_key_id},
147 def remove_key(self, key, commit = True):
149 Unassociate a key with this peer.
152 remove = Row.remove_object(Key, 'peer_key')
153 remove(self, key, commit)
155 def add_node(self, node, peer_node_id, commit = True):
157 Associate a local node entry with this peer.
160 add = Row.add_object(Node, 'peer_node')
162 {'peer_id': self['peer_id'],
163 'node_id': node['node_id'],
164 'peer_node_id': peer_node_id},
167 # calling UpdateNode with the node's hostname
168 # will force the 'hrn' tag to be updated with the
170 from PLC.Methods.UpdateNode import UpdateNode
171 UpdateNode.__call__(UpdateNode(self.api), auth, node['node_id'], {'hostname': node['hostname']})
173 def remove_node(self, node, commit = True):
175 Unassociate a node with this peer.
178 remove = Row.remove_object(Node, 'peer_node')
179 remove(self, node, commit)
181 # calling UpdateNode with the node's hostname
182 # will force the 'hrn' tag to be updated with the
184 from PLC.Methods.UpdateNode import UpdateNode
185 UpdateNode.__call__(UpdateNode(self.api), auth, node['node_id'], {'hostname': node['hostname']})
187 def add_slice(self, slice, peer_slice_id, commit = True):
189 Associate a local slice entry with this peer.
192 add = Row.add_object(Slice, 'peer_slice')
194 {'peer_id': self['peer_id'],
195 'slice_id': slice['slice_id'],
196 'peer_slice_id': peer_slice_id},
199 def remove_slice(self, slice, commit = True):
201 Unassociate a slice with this peer.
204 remove = Row.remove_object(Slice, 'peer_slice')
205 remove(self, slice, commit)
207 def connect(self, **kwds):
209 Connect to this peer via XML-RPC.
213 from PLC.PyCurl import PyCurlTransport
214 self.server = xmlrpclib.ServerProxy(self['peer_url'],
215 PyCurlTransport(self['peer_url'], self['cacert']),
216 allow_none = 1, **kwds)
218 def add_auth(self, function, methodname, **kwds):
220 Sign the specified XML-RPC call and add an auth struct as the
221 first argument of the call.
224 def wrapper(*args, **kwds):
225 from PLC.GPG import gpg_sign
226 signature = gpg_sign(args,
227 self.api.config.PLC_ROOT_GPG_KEY,
228 self.api.config.PLC_ROOT_GPG_KEY_PUB,
231 auth = {'AuthMethod': "gpg",
232 'name': self.api.config.PLC_NAME,
233 'signature': signature}
235 # Automagically add auth struct to every call
236 args = (auth,) + args
238 return function(*args)
242 def __getattr__(self, attr):
244 Returns a callable API function if attr is the name of a
245 PLCAPI function; otherwise, returns the specified attribute.
249 # Figure out if the specified attribute is the name of a
250 # PLCAPI function. If so and the function requires an
251 # authentication structure as its first argument, return a
252 # callable that automagically adds an auth struct to the
255 api_function = self.api.callable(methodname)
256 if api_function.accepts and \
257 (isinstance(api_function.accepts[0], PLC.Auth.Auth) or \
258 (isinstance(api_function.accepts[0], Mixed) and \
259 filter(lambda param: isinstance(param, Auth), api_function.accepts[0]))):
260 function = getattr(self.server, methodname)
261 return self.add_auth(function, methodname)
262 except Exception, err:
265 if hasattr(self, attr):
266 return getattr(self, attr)
268 raise AttributeError, "type object 'Peer' has no attribute '%s'" % attr
272 Maps to the peers table in the database
275 def __init__ (self, api, peer_filter = None, columns = None):
276 Table.__init__(self, api, Peer, columns)
278 sql = "SELECT %s FROM view_peers WHERE deleted IS False" % \
279 ", ".join(self.columns)
281 if peer_filter is not None:
282 if isinstance(peer_filter, (list, tuple, set)):
283 # Separate the list into integers and strings
284 ints = filter(lambda x: isinstance(x, (int, long)), peer_filter)
285 strs = filter(lambda x: isinstance(x, StringTypes), peer_filter)
286 peer_filter = Filter(Peer.fields, {'peer_id': ints, 'peername': strs})
287 sql += " AND (%s) %s" % peer_filter.sql(api, "OR")
288 elif isinstance(peer_filter, dict):
289 peer_filter = Filter(Peer.fields, peer_filter)
290 sql += " AND (%s) %s" % peer_filter.sql(api, "AND")