this version uses the first release of Cache.py
[plcapi.git] / PLC / Slices.py
1 from types import StringTypes
2 import time
3 import re
4
5 from PLC.Faults import *
6 from PLC.Parameter import Parameter
7 from PLC.Filter import Filter
8 from PLC.Debug import profile
9 from PLC.Table import Row, Table
10 from PLC.SliceInstantiations import SliceInstantiations
11 from PLC.Nodes import Node, Nodes
12 import PLC.Persons
13
14 class Slice(Row):
15     """
16     Representation of a row in the slices table. To use, optionally
17     instantiate with a dict of values. Update as you would a
18     dict. Commit to the database with sync().To use, instantiate
19     with a dict of values.
20     """
21
22     table_name = 'slices'
23     primary_key = 'slice_id'
24     fields = {
25         'slice_id': Parameter(int, "Slice identifier"),
26         'site_id': Parameter(int, "Identifier of the site to which this slice belongs"),
27         'peer_id': Parameter(int, "Peer at which this slice was created", nullok = True),
28         'name': Parameter(str, "Slice name", max = 32),
29         'instantiation': Parameter(str, "Slice instantiation state"),
30         'url': Parameter(str, "URL further describing this slice", max = 254, nullok = True),
31         'description': Parameter(str, "Slice description", max = 2048, nullok = True),
32         'max_nodes': Parameter(int, "Maximum number of nodes that can be assigned to this slice"),
33         'creator_person_id': Parameter(int, "Identifier of the account that created this slice"),
34         'created': Parameter(int, "Date and time when slice was created, in seconds since UNIX epoch", ro = True),
35         'expires': Parameter(int, "Date and time when slice expires, in seconds since UNIX epoch"),
36         'node_ids': Parameter([int], "List of nodes in this slice", ro = True),
37         'person_ids': Parameter([int], "List of accounts that can use this slice", ro = True),
38         'slice_attribute_ids': Parameter([int], "List of slice attributes", ro = True),
39         }
40     # for Cache
41     class_id = 'slice_id'
42     class_key = 'name'
43     foreign_fields = ['instantiation', 'url', 'description',
44                          'max_nodes', 'created', 'expires']
45     foreign_xrefs = { 'Node' : { 'field' : 'node_ids' ,
46                                  'table': 'slice_node' } }
47
48     def validate_name(self, name):
49         # N.B.: Responsibility of the caller to ensure that login_base
50         # portion of the slice name corresponds to a valid site, if
51         # desired.
52
53         # 1. Lowercase.
54         # 2. Begins with login_base (only letters).
55         # 3. Then single underscore after login_base.
56         # 4. Then letters, numbers, or underscores.
57         good_name = r'^[a-z]+_[a-z0-9_]+$'
58         if not name or \
59            not re.match(good_name, name):
60             raise PLCInvalidArgument, "Invalid slice name"
61
62         conflicts = Slices(self.api, [name])
63         for slice in conflicts:
64             if 'slice_id' not in self or self['slice_id'] != slice['slice_id']:
65                 raise PLCInvalidArgument, "Slice name already in use, %s"%name
66
67         return name
68
69     def validate_instantiation(self, instantiation):
70         instantiations = [row['instantiation'] for row in SliceInstantiations(self.api)]
71         if instantiation not in instantiations:
72             raise PLCInvalidArgument, "No such instantiation state"
73
74         return instantiation
75
76     def validate_expires(self, expires):
77         # N.B.: Responsibility of the caller to ensure that expires is
78         # not too far into the future.
79         if expires < time.time():
80             raise PLCInvalidArgument, "Expiration date must be in the future"
81
82         return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(expires))
83
84     def add_person(self, person, commit = True):
85         """
86         Add person to existing slice.
87         """
88
89         assert 'slice_id' in self
90         assert isinstance(person, PLC.Persons.Person)
91         assert 'person_id' in person
92
93         slice_id = self['slice_id']
94         person_id = person['person_id']
95
96         if person_id not in self['person_ids']:
97             assert slice_id not in person['slice_ids']
98
99             self.api.db.do("INSERT INTO slice_person (person_id, slice_id)" \
100                            " VALUES(%(person_id)d, %(slice_id)d)",
101                            locals())
102
103             if commit:
104                 self.api.db.commit()
105
106             self['person_ids'].append(person_id)
107             person['slice_ids'].append(slice_id)
108
109     def remove_person(self, person, commit = True):
110         """
111         Remove person from existing slice.
112         """
113
114         assert 'slice_id' in self
115         assert isinstance(person, PLC.Persons.Person)
116         assert 'person_id' in person
117
118         slice_id = self['slice_id']
119         person_id = person['person_id']
120
121         if person_id in self['person_ids']:
122             assert slice_id in person['slice_ids']
123
124             self.api.db.do("DELETE FROM slice_person" \
125                            " WHERE person_id = %(person_id)d" \
126                            " AND slice_id = %(slice_id)d",
127                            locals())
128
129             if commit:
130                 self.api.db.commit()
131
132             self['person_ids'].remove(person_id)
133             person['slice_ids'].remove(slice_id)
134
135     def add_node(self, node, commit = True):
136         """
137         Add node to existing slice.
138         """
139
140         assert 'slice_id' in self
141         assert isinstance(node, Node)
142         assert 'node_id' in node
143
144         slice_id = self['slice_id']
145         node_id = node['node_id']
146
147         if node_id not in self['node_ids']:
148             assert slice_id not in node['slice_ids']
149
150             self.api.db.do("INSERT INTO slice_node (node_id, slice_id)" \
151                            " VALUES(%(node_id)d, %(slice_id)d)",
152                            locals())
153
154             if commit:
155                 self.api.db.commit()
156
157             self['node_ids'].append(node_id)
158             node['slice_ids'].append(slice_id)
159
160     def remove_node(self, node, commit = True):
161         """
162         Remove node from existing slice.
163         """
164
165         assert 'slice_id' in self
166         assert isinstance(node, Node)
167         assert 'node_id' in node
168
169         slice_id = self['slice_id']
170         node_id = node['node_id']
171
172         if node_id in self['node_ids']:
173             assert slice_id in node['slice_ids']
174
175             self.api.db.do("DELETE FROM slice_node" \
176                            " WHERE node_id = %(node_id)d" \
177                            " AND slice_id = %(slice_id)d",
178                            locals())
179
180             if commit:
181                 self.api.db.commit()
182
183             self['node_ids'].remove(node_id)
184             node['slice_ids'].remove(slice_id)
185
186     ##########
187     def sync(self, commit = True):
188         """
189         Add or update a slice.
190         """
191
192         # Before a new slice is added, delete expired slices
193         if 'slice_id' not in self:
194             expired = Slices(self.api, expires = -int(time.time()))
195             for slice in expired:
196                 slice.delete(commit)
197
198         Row.sync(self, commit)
199
200     def delete(self, commit = True):
201         """
202         Delete existing slice.
203         """
204
205         assert 'slice_id' in self
206
207         # Clean up miscellaneous join tables
208         for table in ['slice_node', 'slice_person', 'slice_attribute']:
209             self.api.db.do("DELETE FROM %s" \
210                            " WHERE slice_id = %d" % \
211                            (table, self['slice_id']), self)
212
213         # Mark as deleted
214         self['is_deleted'] = True
215         self.sync(commit)
216
217 class Slices(Table):
218     """
219     Representation of row(s) from the slices table in the
220     database.
221     """
222
223     def __init__(self, api, slice_filter = None, columns = None, expires = int(time.time())):
224         Table.__init__(self, api, Slice, columns)
225
226         sql = "SELECT %s FROM view_slices WHERE is_deleted IS False" % \
227               ", ".join(self.columns)
228
229         if expires is not None:
230             if expires >= 0:
231                 sql += " AND expires > %(expires)d"
232             else:
233                 expires = -expires
234                 sql += " AND expires < %(expires)d"
235
236         if slice_filter is not None:
237             if isinstance(slice_filter, (list, tuple, set)):
238                 # Separate the list into integers and strings
239                 ints = filter(lambda x: isinstance(x, (int, long)), slice_filter)
240                 strs = filter(lambda x: isinstance(x, StringTypes), slice_filter)
241                 slice_filter = Filter(Slice.fields, {'slice_id': ints, 'name': strs})
242                 sql += " AND (%s)" % slice_filter.sql(api, "OR")
243             elif isinstance(slice_filter, dict):
244                 slice_filter = Filter(Slice.fields, slice_filter)
245                 sql += " AND (%s)" % slice_filter.sql(api, "AND")
246
247         self.selectall(sql, locals())