make read-only a Parameter attribute
[plcapi.git] / PLC / Slices.py
1 from types import StringTypes
2 import time
3
4 from PLC.Faults import *
5 from PLC.Parameter import Parameter
6 from PLC.Debug import profile
7 from PLC.Table import Row, Table
8 from PLC.SliceInstantiations import SliceInstantiations
9 import PLC.Persons
10
11 class Slice(Row):
12     """
13     Representation of a row in the slices table. To use, optionally
14     instantiate with a dict of values. Update as you would a
15     dict. Commit to the database with sync().To use, instantiate
16     with a dict of values.
17     """
18
19     fields = {
20         'slice_id': Parameter(int, "Slice type"),
21         'site_id': Parameter(int, "Identifier of the site to which this slice belongs"),
22         'name': Parameter(str, "Slice name", max = 32),
23         'instantiation': Parameter(str, "Slice instantiation state"),
24         'url': Parameter(str, "URL further describing this slice", max = 254),
25         'description': Parameter(str, "Slice description", max = 2048),
26         'max_nodes': Parameter(int, "Maximum number of nodes that can be assigned to this slice"),
27         'creator_person_id': Parameter(int, "Identifier of the account that created this slice"),
28         'created': Parameter(int, "Date and time when slice was created, in seconds since UNIX epoch", ro = True),
29         'expires': Parameter(int, "Date and time when slice expires, in seconds since UNIX epoch"),
30         'node_ids': Parameter([int], "List of nodes in this slice", ro = True),
31         'person_ids': Parameter([int], "List of accounts that can use this slice", ro = True),
32         'attribute_ids': Parameter([int], "List of slice attributes", ro = True),
33         }
34
35     def __init__(self, api, fields):
36         Row.__init__(self, fields)
37         self.api = api
38
39     def validate_name(self, name):
40         # N.B.: Responsibility of the caller to ensure that login_base
41         # portion of the slice name corresponds to a valid site, if
42         # desired.
43         conflicts = Slices(self.api, [name])
44         for slice_id, slice in conflicts.iteritems():
45             if 'slice_id' not in self or self['slice_id'] != slice_id:
46                 raise PLCInvalidArgument, "Slice name already in use"
47
48         return name
49
50     def validate_instantiation(self, instantiation):
51         instantiations = SliceInstantiations(self.api)
52         if instantiation not in instantiations:
53             raise PLCInvalidArgument, "No such instantiation state"
54
55         return state
56
57     def validate_expires(self, expires):
58         # N.B.: Responsibility of the caller to ensure that expires is
59         # not too far into the future.
60         if expires < time.time():
61             raise PLCInvalidArgument, "Expiration date must be in the future"
62
63         return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(expires))
64
65     def validate_creator_person_id(self, person_id):
66         persons = PLC.Persons.Persons(self.api, [person_id])
67         if not persons:
68             raise PLCInvalidArgument, "Invalid creator"
69
70         return person_id
71
72     def add_person(self, person, commit = True):
73         """
74         Add person to existing slice.
75         """
76
77         assert 'slice_id' in self
78         assert isinstance(person, PLC.Persons.Person)
79         assert 'person_id' in person
80
81         slice_id = self['slice_id']
82         person_id = person['person_id']
83         self.api.db.do("INSERT INTO slice_person (person_id, slice_id)" \
84                        " VALUES(%(person_id)d, %(slice_id)d)",
85                        locals())
86
87         if commit:
88             self.api.db.commit()
89
90         if 'person_ids' in self and person_id not in self['person_ids']:
91             self['person_ids'].append(person_id)
92
93         if 'slice_ids' in person and slice_id not in person['slice_ids']:
94             person['slice_ids'].append(slice_id)
95
96     def remove_person(self, person, commit = True):
97         """
98         Remove person from existing slice.
99         """
100
101         assert 'slice_id' in self
102         assert isinstance(person, PLC.Persons.Person)
103         assert 'person_id' in person
104
105         slice_id = self['slice_id']
106         person_id = person['person_id']
107         self.api.db.do("DELETE FROM slice_person" \
108                        " WHERE person_id = %(person_id)d" \
109                        " AND slice_id = %(slice_id)d",
110                        locals())
111
112         if commit:
113             self.api.db.commit()
114
115         if 'person_ids' in self and person_id in self['person_ids']:
116             self['person_ids'].remove(person_id)
117
118         if 'slice_ids' in person and slice_id in person['slice_ids']:
119             person['slice_ids'].remove(slice_id)
120
121     def add_node(self, node, commit = True):
122         """
123         Add node to existing slice.
124         """
125
126         assert 'slice_id' in self
127         assert isinstance(node, PLC.Nodes.Node)
128         assert 'node_id' in node
129
130         slice_id = self['slice_id']
131         node_id = node['node_id']
132         self.api.db.do("INSERT INTO slice_node (node_id, slice_id)" \
133                        " VALUES(%(node_id)d, %(slice_id)d)",
134                        locals())
135
136         if commit:
137             self.api.db.commit()
138
139         if 'node_ids' in self and node_id not in self['node_ids']:
140             self['node_ids'].append(node_id)
141
142         if 'slice_ids' in node and slice_id not in node['slice_ids']:
143             node['slice_ids'].append(slice_id)
144
145     def remove_node(self, node, commit = True):
146         """
147         Remove node from existing slice.
148         """
149
150         assert 'slice_id' in self
151         assert isinstance(node, PLC.Nodes.Node)
152         assert 'node_id' in node
153
154         slice_id = self['slice_id']
155         node_id = node['node_id']
156         self.api.db.do("DELETE FROM slice_node" \
157                        " WHERE node_id = %(node_id)d" \
158                        " AND slice_id = %(slice_id)d",
159                        locals())
160
161         if commit:
162             self.api.db.commit()
163
164         if 'node_ids' in self and node_id in self['node_ids']:
165             self['node_ids'].remove(node_id)
166
167         if 'slice_ids' in node and slice_id in node['slice_ids']:
168             node['slice_ids'].remove(slice_id)
169
170     def sync(self, commit = True):
171         """
172         Flush changes back to the database.
173         """
174
175         try:
176             if not self['name']:
177                 raise KeyError
178         except KeyError:
179             raise PLCInvalidArgument, "Slice name must be specified"
180
181         self.validate()
182
183         # Fetch a new slice_id if necessary
184         if 'slice_id' not in self:
185             # N.B.: Responsibility of the caller to ensure that
186             # max_slices is not exceeded.
187             rows = self.api.db.selectall("SELECT NEXTVAL('slices_slice_id_seq') AS slice_id")
188             if not rows:
189                 raise PLCDBError, "Unable to fetch new slice_id"
190             self['slice_id'] = rows[0]['slice_id']
191             insert = True
192         else:
193             insert = False
194
195         # Filter out fields that cannot be set or updated directly
196         slices_fields = self.api.db.fields('slices')
197         fields = dict(filter(lambda (key, value): \
198                              key in slices_fields and \
199                              (key not in self.fields or not self.fields[key].ro),
200                              self.items()))
201
202         # Parameterize for safety
203         keys = fields.keys()
204         values = [self.api.db.param(key, value) for (key, value) in fields.items()]
205
206         if insert:
207             # Insert new row in slices table
208             sql = "INSERT INTO slices (%s) VALUES (%s)" % \
209                   (", ".join(keys), ", ".join(values))
210         else:
211             # Update existing row in slices table
212             columns = ["%s = %s" % (key, value) for (key, value) in zip(keys, values)]
213             sql = "UPDATE slices SET " + \
214                   ", ".join(columns) + \
215                   " WHERE slice_id = %(slice_id)d"
216
217         self.api.db.do(sql, fields)
218
219         if commit:
220             self.api.db.commit()
221
222     def delete(self, commit = True):
223         """
224         Delete existing slice.
225         """
226
227         assert 'slice_id' in self
228
229         # Clean up miscellaneous join tables
230         for table in ['slice_node', 'slice_person', 'slice_attribute']:
231             self.api.db.do("DELETE FROM %s" \
232                            " WHERE slice_id = %d" % \
233                            (table, self['slice_id']), self)
234
235         # Mark as deleted
236         self['is_deleted'] = True
237         self.sync(commit)
238
239 class Slices(Table):
240     """
241     Representation of row(s) from the slices table in the
242     database.
243     """
244
245     def __init__(self, api, slice_id_or_name_list = None, fields = Slice.fields):
246         self.api = api
247
248         sql = "SELECT %s FROM view_slices WHERE is_deleted IS False" % \
249               ", ".join(fields)
250
251         if slice_id_or_name_list:
252             # Separate the list into integers and strings
253             slice_ids = filter(lambda slice_id: isinstance(slice_id, (int, long)),
254                                slice_id_or_name_list)
255             names = filter(lambda name: isinstance(name, StringTypes),
256                            slice_id_or_name_list)
257             sql += " AND (False"
258             if slice_ids:
259                 sql += " OR slice_id IN (%s)" % ", ".join(map(str, slice_ids))
260             if names:
261                 sql += " OR name IN (%s)" % ", ".join(api.db.quote(names))
262             sql += ")"
263
264         rows = self.api.db.selectall(sql)
265
266         for row in rows:
267             self[row['slice_id']] = slice = Slice(api, row)
268             for aggregate in 'person_ids', 'slice_ids', 'attribute_ids':
269                 if not slice.has_key(aggregate) or slice[aggregate] is None:
270                     slice[aggregate] = []
271                 else:
272                     slice[aggregate] = map(int, slice[aggregate].split(','))