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