2 # Thierry Parmentelat - INRIA
6 from types import StringTypes
8 from urllib.parse import urlparse
11 from PLC.Logger import logger
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 logger.exception("Could not find out hrn on hostname=%s"%node['hostname'])
180 def remove_node(self, node, commit = True):
182 Unassociate a node with this peer.
185 remove = Row.remove_object(Node, 'peer_node')
186 remove(self, node, commit)
187 # attempt to manually update the 'hrn' tag now that the node is local
188 root_auth = self.api.config.PLC_HRN_ROOT
189 sites = Sites(self.api, node['site_id'], ['login_base'])
191 login_base = site['login_base']
192 hrn = hostname_to_hrn(root_auth, login_base, node['hostname'])
194 Node(self.api, node).update_tags(tags)
196 def add_slice(self, slice, peer_slice_id, commit = True):
198 Associate a local slice entry with this peer.
201 add = Row.add_object(Slice, 'peer_slice')
203 {'peer_id': self['peer_id'],
204 'slice_id': slice['slice_id'],
205 'peer_slice_id': peer_slice_id},
208 def remove_slice(self, slice, commit = True):
210 Unassociate a slice with this peer.
213 remove = Row.remove_object(Slice, 'peer_slice')
214 remove(self, slice, commit)
216 def connect(self, **kwds):
218 Connect to this peer via XML-RPC.
222 from PLC.PyCurl import PyCurlTransport
223 self.server = xmlrpc.client.ServerProxy(self['peer_url'],
224 PyCurlTransport(self['peer_url'], self['cacert']),
225 allow_none = 1, **kwds)
227 def add_auth(self, function, methodname, **kwds):
229 Sign the specified XML-RPC call and add an auth struct as the
230 first argument of the call.
233 def wrapper(*args, **kwds):
234 from PLC.GPG import gpg_sign
235 signature = gpg_sign(args,
236 self.api.config.PLC_ROOT_GPG_KEY,
237 self.api.config.PLC_ROOT_GPG_KEY_PUB,
240 auth = {'AuthMethod': "gpg",
241 'name': self.api.config.PLC_NAME,
242 'signature': signature}
244 # Automagically add auth struct to every call
245 args = (auth,) + args
247 return function(*args)
251 def __getattr__(self, attr):
253 Returns a callable API function if attr is the name of a
254 PLCAPI function; otherwise, returns the specified attribute.
258 # Figure out if the specified attribute is the name of a
259 # PLCAPI function. If so and the function requires an
260 # authentication structure as its first argument, return a
261 # callable that automagically adds an auth struct to the
264 api_function = self.api.callable(methodname)
265 if api_function.accepts and \
266 (isinstance(api_function.accepts[0], PLC.Auth.Auth) or \
267 (isinstance(api_function.accepts[0], Mixed) and \
268 [param for param in api_function.accepts[0] if isinstance(param, Auth)])):
269 function = getattr(self.server, methodname)
270 return self.add_auth(function, methodname)
271 except Exception as err:
274 if hasattr(self, attr):
275 return getattr(self, attr)
277 raise AttributeError("type object 'Peer' has no attribute '%s'" % attr)
281 Maps to the peers table in the database
284 def __init__ (self, api, peer_filter = None, columns = None):
285 Table.__init__(self, api, Peer, columns)
287 sql = "SELECT %s FROM view_peers WHERE deleted IS False" % \
288 ", ".join(self.columns)
290 if peer_filter is not None:
291 if isinstance(peer_filter, (list, tuple, set)):
292 # Separate the list into integers and strings
293 ints = [x for x in peer_filter if isinstance(x, int)]
294 strs = [x for x in peer_filter if isinstance(x, StringTypes)]
295 peer_filter = Filter(Peer.fields, {'peer_id': ints, 'peername': strs})
296 sql += " AND (%s) %s" % peer_filter.sql(api, "OR")
297 elif isinstance(peer_filter, dict):
298 peer_filter = Filter(Peer.fields, peer_filter)
299 sql += " AND (%s) %s" % peer_filter.sql(api, "AND")
300 elif isinstance(peer_filter, int):
301 peer_filter = Filter(Peer.fields, {'peer_id': peer_filter})
302 sql += " AND (%s) %s" % peer_filter.sql(api, "AND")
303 elif isinstance(peer_filter, StringTypes):
304 peer_filter = Filter(Peer.fields, {'peername': peer_filter})
305 sql += " AND (%s) %s" % peer_filter.sql(api, "AND")
307 raise PLCInvalidArgument("Wrong peer filter %r"%peer_filter)