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