Fix slice tags
[plcapi.git] / PLC / Slices.py
1 from types import StringTypes
2 import time
3 import re
4 from datetime import datetime, timedelta
5
6 from PLC.Faults import *
7 from PLC.Parameter import Parameter, Mixed
8 from PLC.Debug import profile
9 from PLC.Roles import Role, Roles
10 from PLC.Nodes import Node
11 from PLC.Persons import Person, Persons
12 from PLC.SlicePersons import SlicePerson, SlicePersons
13 from PLC.SliceNodes import SliceNode, SliceNodes
14 from PLC.SliceTags import SliceTag, SliceTags
15 from PLC.Timestamp import Timestamp
16 from PLC.Storage.AlchemyObject import AlchemyObj
17
18 class Slice(AlchemyObj):
19     """
20     Representation of a row in the slices table. To use, optionally
21     instantiate with a dict of values. Update as you would a
22     dict. Commit to the database with sync().To use, instantiate
23     with a dict of values.
24     """
25
26     tablename = 'slices'
27  
28     fields = {
29         'slice_id': Parameter(int, "Slice identifier", primary_key=True),
30         'site_id': Parameter(int, "Identifier of the site to which this slice belongs"),
31         'tenant_id': Parameter(int, "Keystone tenant identifier"), 
32         'name': Parameter(str, "Slice name", max = 32),
33         'instantiation': Parameter(str, "Slice instantiation state", nullok=True),
34         'url': Parameter(str, "URL further describing this slice", max = 254, nullok = True),
35         'description': Parameter(str, "Slice description", max = 2048, nullok = True),
36         'max_nodes': Parameter(int, "Maximum number of nodes that can be assigned to this slice", default=100),
37         'creator_person_id': Parameter(str, "Identifier of the account that created this slice"),
38         'created': Parameter(datetime, "Date and time when slice was created, in seconds since UNIX epoch", ro = True),
39         'expires': Parameter(datetime, "Date and time when slice expires, in seconds since UNIX epoch"),
40         'node_ids': Parameter([str], "List of nodes in this slice", joined = True),
41         'person_ids': Parameter([str], "List of accounts that can use this slice", joined = True),
42         'slice_tag_ids': Parameter([int], "List of slice attributes", joined = True),
43         'peer_id': Parameter(int, "Peer to which this slice belongs", nullok = True),
44         'peer_slice_id': Parameter(int, "Foreign slice identifier at peer", nullok = True),
45         }
46     tags = {}
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 (letters or numbers).
55         # 3. Then single underscore after login_base.
56         # 4. Then letters, numbers, or underscores.
57         good_name = r'^[a-z0-9]+_[a-zA-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_expires(self, expires):
70         # N.B.: Responsibility of the caller to ensure that expires is
71         # not too far into the future.
72         check_future = not ('is_deleted' in self and self['is_deleted'])
73         return Timestamp.sql_validate( expires, check_future = check_future)
74
75     def add_person(self, person_filter, role_name=None):
76         assert 'slice_id' in self
77         assert 'tenant_id' in self
78         if not role_name:
79             role_name = 'user'
80         roles = Roles(self.api, role_name)
81         if not roles:
82             raise PLCInvalidArgument, "No such role %s" % role_name
83         role = roles[0]
84         tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id'])
85         persons = Persons(self.api, person_filter)
86         for person in persons:
87             keystone_user = self.api.client_shell.keystone.users.find(id=person['keystone_id'])
88             tenant.add_user(keystone_user, role.object)
89             slice_person = SlicePerson(self.api, {'slice_id': self['slice_id'],
90                                                   'person_id': person['person_id']})
91             slice_person.sync()
92
93     def remove_person(self, person_filter, role=None):
94         assert 'slice_id' in self
95         assert 'tenant_id' in self
96         if not role_name:
97             role_name = 'user'
98         roles = Roles(self.api, role_name)
99         if not roles:
100             raise PLCInvalidArgument, "No such role %s" % role_name
101         role = roles[0]
102         tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id'])
103         persons = Persons(self.api, person_filter)
104         for person in persons:
105             keystone_user = self.api.client_shell.keystone.users.find(id=person['keystone_id'])
106             tenant.remove_user(keystone_user, role.object)
107             slice_person = SlicePerson(self.api, {'slice_id': self['slice_id'],
108                                                   'person_id': person['person_id']})
109             slice_person.delete()
110  
111
112     def add_node(self, node_filter, commit=True):
113         from PLC.Nodes import Nodes
114         assert 'slice_id' in self
115         nodes = Nodes(self.api, node_filter)
116         for node in nodes:
117             slice_node = SliceNode(self.api, {'slice_id': self['slice_id'],
118                                               'node_id': node['node_id']})
119             slice_node.sync()
120  
121     def remove_node(self, node_filter, commit=True):
122         from PLC.Nodes import Nodes 
123         assert 'slice_id' in self
124         nodes = Nodes(self.api, node_filter)
125         for node in nodes:
126             slice_node = SliceNode(self.api, {'slice_id': self['slice_id'],
127                                               'node_id': node['node_id']})
128             slice_node.delete()
129
130     #add_to_node_whitelist = Row.add_object(Node, 'node_slice_whitelist')
131     #delete_from_node_whitelist = Row.remove_object(Node, 'node_slice_whitelist')
132
133     def sync(self, commit = True, validate=True):
134         """
135         Add or update a slice.
136         """
137         # sync the nova record and the plc record
138         AlchemyObj.sync(self, commit=commit, validate=validate)
139         # create the nova record
140         nova_fields = ['enabled', 'description']
141         nova_can_update = lambda (field, value): field in nova_fields
142         nova_slice = dict(filter(nova_can_update, self.items()))
143         nova_slice['tenant_name'] = self['name']
144         if 'slice_id' not in self:
145             now = datetime.now()
146             # Before a new slice is added, delete expired slices
147             #expired = Slices(self.api, expires = -int(time.time()))
148             #for slice in expired:
149             #    slice.delete(commit)
150             self.object = self.api.client_shell.keystone.tenants.create(**nova_slice)
151             self['tenant_id'] = self.object.id
152             self['created'] = now
153             self['expires'] = now + timedelta(days=14)
154             AlchemyObj.insert(self, dict(self))
155             slice = AlchemyObj.select(self, filter={'tenant_id': self['tenant_id']})[0]
156             self['slice_id'] = slice.slice_id
157         else:
158             self.object = self.api.client_shell.keystone.tenants.update(self['tenant_id'], **nova_slice) 
159             AlchemyObj.updatedb(self, {'slice_id': self['slice_id']}, dict(self)) 
160
161     def delete(self, commit = True):
162         """
163         Delete existing slice.
164         """
165         assert 'slice_id' in self
166         assert 'tenant_id' in self
167
168         # delete the nova object
169         tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id'])
170         self.api.client_shell.keystone.tenants.delete(tenant)
171
172         # delete relationships
173         for slice_person in SlicePerson().select(filter={'slice_id': self['slice_id']}):
174             slice_person.delete()
175         for slice_node in SliceNode().select(filter={'slice_id': self['slice_id']}):
176             slice_node.delete()
177         for slice_tag in SliceTag().select(filter={'slice_id': self['slice_id']}):
178             slice_tag.delete()
179         
180         # delete slice 
181         AlchemyObj.delete(self, filter={'slice_id': self['slice_id']})
182
183 class Slices(list):
184     """
185     Representation of row(s) from the slices table in the
186     database.
187     """
188
189     def __init__(self, api, slice_filter = None, columns = None, expires = int(time.time())):
190          
191         # the view that we're selecting upon: start with view_slices
192         if not slice_filter:
193             slices = Slice().select()
194         elif isinstance (slice_filter, StringTypes):
195             slices = Slice().select(filter={'name': slice_filter})
196         elif isinstance(slice_filter, dict):
197             slices = Slice().select(filter=slice_filter)
198         elif isinstance(slice_filter, (list, tuple, set)):
199             slices = Slice().select()
200             slices = [slice for slice in slices if slice.slice_id in slice_filter or slice.name in slice_filter]
201         else:
202             raise PLCInvalidArgument, "Wrong slice filter %r"%slice_filter
203
204         for slice in slices:
205             slice = Slice(api, object=slice)
206             if not columns or 'person_ids' in columns:
207                 slice_persons = SlicePerson().select(filter={'slice_id': slice['slice_id']})
208                 slice['person_ids'] = [rec.person_id for rec in slice_persons] 
209                 
210             if not columns or 'node_ids' in columns:
211                 slice_nodes = SliceNode().select(filter={'slice_id': slice['slice_id']})
212                 slice['node_ids'] = [rec.node_id for rec in slice_nodes]
213
214             if not columns or 'slice_tag_ids' in columns:
215                 slice_tags = SliceTag().select(filter={'slice_id': slice['slice_id']})
216                 slice['slice_tag_ids'] = [rec.slice_tag_id for rec in slice_tags]
217                 
218             self.append(slice)
219
220     def refresh(self, api):
221         """
222         Import tenants from keystone.
223         """
224         # get current slices
225         slices = Slice().select()
226         slice_names = [slice.name for slice in slices]
227
228         # get current tenants
229         tenants = api.client_shell.keystone.tenants.list()
230
231         # add tenants that dont already exist
232         for tenant in tenants:
233             # site tenants should not contain '_'
234             if '_' in tenant.name and tenant.name not in slice_names:
235                 description = tenant.description
236                 if not description: description  = tenant.name
237                 slice = Slice(api, {'name': tenant.name,
238                                     'tenant_id': tenant.id,
239                                     'enabled': tenant.enabled,
240                                     'description': description
241                                     'is_public': True})
242                 try:
243                     slice.sync()
244                 except:
245                     # slice may have a login base prefix that doesn't exist yet.
246                     pass