Add node_id to added_fields
[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):
113         assert 'slice_id' in self
114         nodes = Nodes(self.api, node_filter)
115         for node in nodes:
116             slice_node = SliceNode(self.api, {'slice_id': self['slice_id'],
117                                               'node_id': node['node_id']})
118             slice_node.sync()
119  
120     def remove_node(self, node_filter):
121         assert 'slice_id' in self
122         nodes = Nodes(self.api, node_filter)
123         for node in nodes:
124             slice_node = SliceNode(self.api, {'slice_id': self['slice_id'],
125                                               'node_id': node['node_id']})
126             slice_node.delete()
127
128     #add_to_node_whitelist = Row.add_object(Node, 'node_slice_whitelist')
129     #delete_from_node_whitelist = Row.remove_object(Node, 'node_slice_whitelist')
130
131     def sync(self, commit = True, validate=True):
132         """
133         Add or update a slice.
134         """
135         # sync the nova record and the plc record
136         AlchemyObj.sync(self, commit=commit, validate=validate)
137         # create the nova record
138         nova_fields = ['enabled', 'description']
139         nova_can_update = lambda (field, value): field in nova_fields
140         nova_slice = dict(filter(nova_can_update, self.items()))
141         nova_slice['tenant_name'] = self['name']
142         if 'slice_id' not in self:
143             now = datetime.now()
144             # Before a new slice is added, delete expired slices
145             #expired = Slices(self.api, expires = -int(time.time()))
146             #for slice in expired:
147             #    slice.delete(commit)
148             self.object = self.api.client_shell.keystone.tenants.create(**nova_slice)
149             self['tenant_id'] = self.object.id
150             self['created'] = now
151             self['expires'] = now + timedelta(days=14)
152             AlchemyObj.insert(self, dict(self))
153             slice = AlchemyObj.select(self, filter={'tenant_id': self['tenant_id']})[0]
154             self['slice_id'] = slice.slice_id
155         else:
156             self.object = self.api.client_shell.keystone.tenants.update(self['tenant_id'], **nova_slice) 
157             AlchemyObj.update(self, {'slice_id': self['slice_id']}, dict(self)) 
158
159     def delete(self, commit = True):
160         """
161         Delete existing slice.
162         """
163         assert 'slice_id' in self
164         assert 'tenant_id' in self
165
166         # delete the nova object
167         tenant = self.api.client_shell.keystone.tenants.find(id=self['tenant_id'])
168         self.api.client_shell.keystone.tenants.delete(tenant)
169
170         # delete relationships
171         for slice_person in SlicePerson().select(filter={'slice_id': self['slice_id']}):
172             slice_person.delete()
173         for slice_node in SliceNode().select(filter={'slice_id': self['slice_id']}):
174             slice_node.delete()
175         for slice_tag in SliceTag().select(filter={'slice_id': self['slice_id']}):
176             slice_tag.delete()
177         
178         # delete slice 
179         AlchemyObj.delete(self, filter={'slice_id': self['slice_id']})
180
181 class Slices(list):
182     """
183     Representation of row(s) from the slices table in the
184     database.
185     """
186
187     def __init__(self, api, slice_filter = None, columns = None, expires = int(time.time())):
188          
189         # the view that we're selecting upon: start with view_slices
190         if not slice_filter:
191             slices = Slice().select()
192         elif isinstance (slice_filter, StringTypes):
193             slices = Slice().select(filter={'name': slice_filter})
194         elif isinstance(slice_filter, dict):
195             slices = Slice().select(filter=slice_filter)
196         elif isinstance(slice_filter, (list, tuple, set)):
197             slices = Slice().select()
198             slices = [slice for slice in slices if slice.slice_id in slice_filter or slice.name in slice_filter]
199         else:
200             raise PLCInvalidArgument, "Wrong slice filter %r"%slice_filter
201
202         for slice in slices:
203             slice = Slice(api, object=slice)
204             if not columns or 'person_ids' in columns:
205                 slice_persons = SlicePerson().select(filter={'slice_id': slice['slice_id']})
206                 slice['person_ids'] = [rec.person_id for rec in slice_persons] 
207                 
208             if not columns or 'node_ids' in columns:
209                 slice_nodes = SliceNode().select(filter={'slice_id': slice['slice_id']})
210                 slice['node_ids'] = [rec.node_id for rec in slice_nodes]
211
212             if not columns or 'slice_tag_ids' in columns:
213                 slice_tags = SliceTag().select(filter={'slice_id': slice['slice_id']})
214                 slice['slice_tag_ids'] = [rec.slice_tag_id for rec in slice_tags]
215                 
216             self.append(slice)
217
218     def refresh(self, api):
219         """
220         Import tenants from keystone.
221         """
222         # get current slices
223         slices = Slice().select()
224         slice_names = [slice.name for slice in slices]
225
226         # get current tenants
227         tenants = api.client_shell.keystone.tenants.list()
228
229         # add tenants that dont already exist
230         for tenant in tenants:
231             # site tenants should not contain '_'
232             if '_' in tenant.name and tenant.name not in slice_names:
233                 description = tenant.description
234                 if not description: description  = tenant.name
235                 slice = Slice(api, {'name': tenant.name,
236                                     'tenant_id': tenant.id,
237                                     'enabled': tenant.enabled,
238                                     'description': description
239                                     'is_public': True})
240                 try:
241                     slice.sync()
242                 except:
243                     # slice may have a login base prefix that doesn't exist yet.
244                     pass