2 # Thierry Parmentelat - INRIA
7 from urllib.parse import urlparse
10 from PLC.Logger import logger
11 from PLC.Faults import *
12 from PLC.Namespace import hostname_to_hrn
13 from PLC.Parameter import Parameter, Mixed
14 from PLC.Filter import Filter
15 from PLC.Table import Row, Table
16 from PLC.Sites import Site, Sites
17 from PLC.Persons import Person, Persons
18 from PLC.Keys import Key, Keys
19 from PLC.Nodes import Node, Nodes
20 from PLC.TagTypes import TagType, TagTypes
21 from PLC.NodeTags import NodeTag, NodeTags
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 sites = Sites(self.api, node['site_id'], ['login_base'])
169 login_base = site['login_base']
171 # attempt to manually update the 'hrn' tag with the remote prefix
172 hrn_root = self['hrn_root']
173 hrn = hostname_to_hrn(hrn_root, login_base, node['hostname'])
175 Node(self.api, node).update_tags(tags)
177 logger.exception("Could not find out hrn on hostname=%s"%node['hostname'])
179 def remove_node(self, node, commit = True):
181 Unassociate a node with this peer.
184 remove = Row.remove_object(Node, 'peer_node')
185 remove(self, node, commit)
186 # attempt to manually update the 'hrn' tag now that the node is local
187 root_auth = self.api.config.PLC_HRN_ROOT
188 sites = Sites(self.api, node['site_id'], ['login_base'])
190 login_base = site['login_base']
191 hrn = hostname_to_hrn(root_auth, login_base, node['hostname'])
193 Node(self.api, node).update_tags(tags)
195 def add_slice(self, slice, peer_slice_id, commit = True):
197 Associate a local slice entry with this peer.
200 add = Row.add_object(Slice, 'peer_slice')
202 {'peer_id': self['peer_id'],
203 'slice_id': slice['slice_id'],
204 'peer_slice_id': peer_slice_id},
207 def remove_slice(self, slice, commit = True):
209 Unassociate a slice with this peer.
212 remove = Row.remove_object(Slice, 'peer_slice')
213 remove(self, slice, commit)
215 def connect(self, **kwds):
217 Connect to this peer via XML-RPC.
221 from PLC.PyCurl import PyCurlTransport
222 self.server = xmlrpc.client.ServerProxy(self['peer_url'],
223 PyCurlTransport(self['peer_url'], self['cacert']),
224 allow_none = 1, **kwds)
226 def add_auth(self, function, methodname, **kwds):
228 Sign the specified XML-RPC call and add an auth struct as the
229 first argument of the call.
232 def wrapper(*args, **kwds):
233 from PLC.GPG import gpg_sign
234 signature = gpg_sign(args,
235 self.api.config.PLC_ROOT_GPG_KEY,
236 self.api.config.PLC_ROOT_GPG_KEY_PUB,
239 auth = {'AuthMethod': "gpg",
240 'name': self.api.config.PLC_NAME,
241 'signature': signature}
243 # Automagically add auth struct to every call
244 args = (auth,) + args
246 return function(*args)
250 def __getattr__(self, attr):
252 Returns a callable API function if attr is the name of a
253 PLCAPI function; otherwise, returns the specified attribute.
257 # Figure out if the specified attribute is the name of a
258 # PLCAPI function. If so and the function requires an
259 # authentication structure as its first argument, return a
260 # callable that automagically adds an auth struct to the
263 api_function = self.api.callable(methodname)
264 if api_function.accepts and \
265 (isinstance(api_function.accepts[0], PLC.Auth.Auth) or \
266 (isinstance(api_function.accepts[0], Mixed) and \
267 [param for param in api_function.accepts[0] if isinstance(param, Auth)])):
268 function = getattr(self.server, methodname)
269 return self.add_auth(function, methodname)
270 except Exception as err:
273 if hasattr(self, attr):
274 return getattr(self, attr)
276 raise AttributeError("type object 'Peer' has no attribute '%s'" % attr)
280 Maps to the peers table in the database
283 def __init__ (self, api, peer_filter = None, columns = None):
284 Table.__init__(self, api, Peer, columns)
286 sql = "SELECT %s FROM view_peers WHERE deleted IS False" % \
287 ", ".join(self.columns)
289 if peer_filter is not None:
290 if isinstance(peer_filter, (list, tuple, set)):
291 # Separate the list into integers and strings
292 ints = [x for x in peer_filter if isinstance(x, int)]
293 strs = [x for x in peer_filter if isinstance(x, str)]
294 peer_filter = Filter(Peer.fields, {'peer_id': ints, 'peername': strs})
295 sql += " AND (%s) %s" % peer_filter.sql(api, "OR")
296 elif isinstance(peer_filter, dict):
297 peer_filter = Filter(Peer.fields, peer_filter)
298 sql += " AND (%s) %s" % peer_filter.sql(api, "AND")
299 elif isinstance(peer_filter, int):
300 peer_filter = Filter(Peer.fields, {'peer_id': peer_filter})
301 sql += " AND (%s) %s" % peer_filter.sql(api, "AND")
302 elif isinstance(peer_filter, str):
303 peer_filter = Filter(Peer.fields, {'peername': peer_filter})
304 sql += " AND (%s) %s" % peer_filter.sql(api, "AND")
306 raise PLCInvalidArgument("Wrong peer filter %r"%peer_filter)