Peer object now has hrn_root
[plcapi.git] / PLC / Peers.py
1 # $Id$
2 #
3 # Thierry Parmentelat - INRIA
4
5
6 import re
7 from types import StringTypes
8 from urlparse import urlparse
9
10 from PLC.Faults import *
11 from PLC.Parameter import Parameter, Mixed
12 from PLC.Filter import Filter
13 from PLC.Table import Row, Table
14 import PLC.Auth
15
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.SliceTags import SliceTag, SliceTags
22 from PLC.Slices import Slice, Slices
23
24 class Peer(Row):
25     """
26     Stores the list of peering PLCs in the peers table. 
27     See the Row class for more details
28     """
29
30     table_name = 'peers'
31     primary_key = 'peer_id'
32     join_tables = ['peer_site', 'peer_person', 'peer_key', 'peer_node', 'peer_slice']
33     fields = {
34         'peer_id': Parameter (int, "Peer identifier"),
35         'peername': Parameter (str, "Peer name"),
36         'peer_url': Parameter (str, "Peer API URL"),
37         'key': Parameter(str, "Peer GPG public key"),
38         'cacert': Parameter(str, "Peer SSL public certificate"),
39         'shortname' : Parameter(str, "Peer short name"),
40         'hrn_root' : Parameter(str, "Root of this peer in a hierarchical naming space"),
41         ### cross refs
42         'site_ids': Parameter([int], "List of sites for which this peer is authoritative"),
43         'person_ids': Parameter([int], "List of users for which this peer is authoritative"),
44         'key_ids': Parameter([int], "List of keys for which this peer is authoritative"),
45         'node_ids': Parameter([int], "List of nodes for which this peer is authoritative"),
46         'slice_ids': Parameter([int], "List of slices for which this peer is authoritative"),
47         }
48
49     def validate_peername(self, peername):
50         if not len(peername):
51             raise PLCInvalidArgument, "Peer name must be specified"
52
53         conflicts = Peers(self.api, [peername])
54         for peer in conflicts:
55             if 'peer_id' not in self or self['peer_id'] != peer['peer_id']:
56                 raise PLCInvalidArgument, "Peer name already in use"
57
58         return peername
59
60     def validate_peer_url(self, url):
61         """
62         Validate URL. Must be HTTPS.
63         """
64
65         (scheme, netloc, path, params, query, fragment) = urlparse(url)
66         if scheme != "https":
67             raise PLCInvalidArgument, "Peer URL scheme must be https"
68
69         return url
70
71     def delete(self, commit = True):
72         """
73         Deletes this peer and all related entities.
74         """
75
76         assert 'peer_id' in self
77
78         # Remove all related entities
79         for obj in \
80             Slices(self.api, self['slice_ids']) + \
81             Keys(self.api, self['key_ids']) + \
82             Persons(self.api, self['person_ids']) + \
83             Nodes(self.api, self['node_ids']) + \
84             Sites(self.api, self['site_ids']):
85             assert obj['peer_id'] == self['peer_id']
86             obj.delete(commit = False)
87
88         # Mark as deleted
89         self['deleted'] = True
90         self.sync(commit)
91
92     def add_site(self, site, peer_site_id, commit = True):
93         """
94         Associate a local site entry with this peer.
95         """
96
97         add = Row.add_object(Site, 'peer_site')
98         add(self, site,
99             {'peer_id': self['peer_id'],
100              'site_id': site['site_id'],
101              'peer_site_id': peer_site_id},
102             commit = commit)
103
104     def add_person(self, person, peer_person_id, commit = True):
105         """
106         Associate a local user entry with this peer.
107         """
108
109         add = Row.add_object(Person, 'peer_person')
110         add(self, person,
111             {'peer_id': self['peer_id'],
112              'person_id': person['person_id'],
113              'peer_person_id': peer_person_id},
114             commit = commit)
115
116     def add_key(self, key, peer_key_id, commit = True):
117         """
118         Associate a local key entry with this peer.
119         """
120
121         add = Row.add_object(Key, 'peer_key')
122         add(self, key,
123             {'peer_id': self['peer_id'],
124              'key_id': key['key_id'],
125              'peer_key_id': peer_key_id},
126             commit = commit)
127
128     def add_node(self, node, peer_node_id, commit = True):
129         """
130         Associate a local node entry with this peer.
131         """
132
133         add = Row.add_object(Node, 'peer_node')
134         add(self, node,
135             {'peer_id': self['peer_id'],
136              'node_id': node['node_id'],
137              'peer_node_id': peer_node_id},
138             commit = commit)
139
140     def add_slice(self, slice, peer_slice_id, commit = True):
141         """
142         Associate a local slice entry with this peer.
143         """
144
145         add = Row.add_object(Slice, 'peer_slice')
146         add(self, slice,
147             {'peer_id': self['peer_id'],
148              'slice_id': slice['slice_id'],
149              'peer_slice_id': peer_slice_id},
150             commit = commit)
151
152     def connect(self, **kwds):
153         """
154         Connect to this peer via XML-RPC.
155         """
156
157         import xmlrpclib
158         from PLC.PyCurl import PyCurlTransport
159         self.server = xmlrpclib.ServerProxy(self['peer_url'],
160                                             PyCurlTransport(self['peer_url'], self['cacert']),
161                                             allow_none = 1, **kwds)
162
163     def add_auth(self, function, methodname, **kwds):
164         """
165         Sign the specified XML-RPC call and add an auth struct as the
166         first argument of the call.
167         """
168
169         def wrapper(*args, **kwds):
170             from PLC.GPG import gpg_sign
171             signature = gpg_sign(args,
172                                  self.api.config.PLC_ROOT_GPG_KEY,
173                                  self.api.config.PLC_ROOT_GPG_KEY_PUB,
174                                  methodname)
175
176             auth = {'AuthMethod': "gpg",
177                     'name': self.api.config.PLC_NAME,
178                     'signature': signature}
179
180             # Automagically add auth struct to every call
181             args = (auth,) + args
182
183             return function(*args)
184
185         return wrapper
186
187     def __getattr__(self, attr):
188         """
189         Returns a callable API function if attr is the name of a
190         PLCAPI function; otherwise, returns the specified attribute.
191         """
192
193         try:
194             # Figure out if the specified attribute is the name of a
195             # PLCAPI function. If so and the function requires an
196             # authentication structure as its first argument, return a
197             # callable that automagically adds an auth struct to the
198             # call.
199             methodname = attr
200             api_function = self.api.callable(methodname)
201             if api_function.accepts and \
202                (isinstance(api_function.accepts[0], PLC.Auth.Auth) or \
203                 (isinstance(api_function.accepts[0], Mixed) and \
204                  filter(lambda param: isinstance(param, Auth), api_function.accepts[0]))):
205                 function = getattr(self.server, methodname)
206                 return self.add_auth(function, methodname)
207         except Exception, err:
208             pass
209
210         if hasattr(self, attr):
211             return getattr(self, attr)
212         else:
213             raise AttributeError, "type object 'Peer' has no attribute '%s'" % attr
214
215 class Peers (Table):
216     """ 
217     Maps to the peers table in the database
218     """
219     
220     def __init__ (self, api, peer_filter = None, columns = None):
221         Table.__init__(self, api, Peer, columns)
222
223         sql = "SELECT %s FROM view_peers WHERE deleted IS False" % \
224               ", ".join(self.columns)
225
226         if peer_filter is not None:
227             if isinstance(peer_filter, (list, tuple, set)):
228                 # Separate the list into integers and strings
229                 ints = filter(lambda x: isinstance(x, (int, long)), peer_filter)
230                 strs = filter(lambda x: isinstance(x, StringTypes), peer_filter)
231                 peer_filter = Filter(Peer.fields, {'peer_id': ints, 'peername': strs})
232                 sql += " AND (%s) %s" % peer_filter.sql(api, "OR")
233             elif isinstance(peer_filter, dict):
234                 peer_filter = Filter(Peer.fields, peer_filter)
235                 sql += " AND (%s) %s" % peer_filter.sql(api, "AND")
236
237         self.selectall(sql)