Merge remote-tracking branch 'origin/pycurl' into planetlab-4_0-branch
[plcapi.git] / PLC / Slices.py
index 42da1d4..1a1786c 100644 (file)
@@ -3,14 +3,14 @@ import time
 import re
 
 from PLC.Faults import *
 import re
 
 from PLC.Faults import *
-from PLC.Parameter import Parameter
+from PLC.Parameter import Parameter, Mixed
 from PLC.Filter import Filter
 from PLC.Debug import profile
 from PLC.Table import Row, Table
 from PLC.Filter import Filter
 from PLC.Debug import profile
 from PLC.Table import Row, Table
-from PLC.SliceInstantiations import SliceInstantiations
-from PLC.Nodes import Node, Nodes
-from PLC.ForeignNodes import ForeignNode, ForeignNodes
-import PLC.Persons
+from PLC.SliceInstantiations import SliceInstantiation, SliceInstantiations
+from PLC.Nodes import Node
+from PLC.Persons import Person, Persons
+from PLC.SliceAttributes import SliceAttribute
 
 class Slice(Row):
     """
 
 class Slice(Row):
     """
@@ -22,6 +22,7 @@ class Slice(Row):
 
     table_name = 'slices'
     primary_key = 'slice_id'
 
     table_name = 'slices'
     primary_key = 'slice_id'
+    join_tables = ['slice_node', 'slice_person', 'slice_attribute', 'peer_slice', 'node_slice_whitelist']
     fields = {
         'slice_id': Parameter(int, "Slice identifier"),
         'site_id': Parameter(int, "Identifier of the site to which this slice belongs"),
     fields = {
         'slice_id': Parameter(int, "Slice identifier"),
         'site_id': Parameter(int, "Identifier of the site to which this slice belongs"),
@@ -36,7 +37,27 @@ class Slice(Row):
         'node_ids': Parameter([int], "List of nodes in this slice", ro = True),
         'person_ids': Parameter([int], "List of accounts that can use this slice", ro = True),
         'slice_attribute_ids': Parameter([int], "List of slice attributes", ro = True),
         'node_ids': Parameter([int], "List of nodes in this slice", ro = True),
         'person_ids': Parameter([int], "List of accounts that can use this slice", ro = True),
         'slice_attribute_ids': Parameter([int], "List of slice attributes", ro = True),
+        'peer_id': Parameter(int, "Peer to which this slice belongs", nullok = True),
+        'peer_slice_id': Parameter(int, "Foreign slice identifier at peer", nullok = True),
         }
         }
+    related_fields = {
+       'persons': [Mixed(Parameter(int, "Person identifier"),
+                         Parameter(str, "Email address"))],
+       'nodes': [Mixed(Parameter(int, "Node identifier"),
+                       Parameter(str, "Fully qualified hostname"))]
+       }
+    # for Cache
+    class_key = 'name'
+    foreign_fields = ['instantiation', 'url', 'description', 'max_nodes', 'expires']
+    foreign_xrefs = [
+        {'field': 'node_ids' ,         'class': 'Node',   'table': 'slice_node' },
+       {'field': 'person_ids',        'class': 'Person', 'table': 'slice_person'},
+       {'field': 'creator_person_id', 'class': 'Person', 'table': 'unused-on-direct-refs'},
+        {'field': 'site_id',           'class': 'Site',   'table': 'unused-on-direct-refs'},
+    ]
+    # forget about this one, it is read-only anyway
+    # handling it causes Cache to re-sync all over again 
+    # 'created'
 
     def validate_name(self, name):
         # N.B.: Responsibility of the caller to ensure that login_base
 
     def validate_name(self, name):
         # N.B.: Responsibility of the caller to ensure that login_base
@@ -44,10 +65,10 @@ class Slice(Row):
         # desired.
 
         # 1. Lowercase.
         # desired.
 
         # 1. Lowercase.
-        # 2. Begins with login_base (only letters).
+        # 2. Begins with login_base (letters or numbers).
         # 3. Then single underscore after login_base.
         # 4. Then letters, numbers, or underscores.
         # 3. Then single underscore after login_base.
         # 4. Then letters, numbers, or underscores.
-        good_name = r'^[a-z]+_[a-z0-9_]+$'
+        good_name = r'^[a-z0-9]+_[a-zA-Z0-9_]+$'
         if not name or \
            not re.match(good_name, name):
             raise PLCInvalidArgument, "Invalid slice name"
         if not name or \
            not re.match(good_name, name):
             raise PLCInvalidArgument, "Invalid slice name"
@@ -55,7 +76,7 @@ class Slice(Row):
         conflicts = Slices(self.api, [name])
         for slice in conflicts:
             if 'slice_id' not in self or self['slice_id'] != slice['slice_id']:
         conflicts = Slices(self.api, [name])
         for slice in conflicts:
             if 'slice_id' not in self or self['slice_id'] != slice['slice_id']:
-                raise PLCInvalidArgument, "Slice name already in use"
+                raise PLCInvalidArgument, "Slice name already in use, %s"%name
 
         return name
 
 
         return name
 
@@ -66,116 +87,144 @@ class Slice(Row):
 
         return instantiation
 
 
         return instantiation
 
+    validate_created = Row.validate_timestamp
+
     def validate_expires(self, expires):
         # N.B.: Responsibility of the caller to ensure that expires is
         # not too far into the future.
     def validate_expires(self, expires):
         # N.B.: Responsibility of the caller to ensure that expires is
         # not too far into the future.
-        if expires < time.time():
-            raise PLCInvalidArgument, "Expiration date must be in the future"
-
-        return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(expires))
-
-    def add_person(self, person, commit = True):
-        """
-        Add person to existing slice.
-        """
-
-        assert 'slice_id' in self
-        assert isinstance(person, PLC.Persons.Person)
-        assert 'person_id' in person
-
-        slice_id = self['slice_id']
-        person_id = person['person_id']
-
-        if person_id not in self['person_ids']:
-            assert slice_id not in person['slice_ids']
-
-            self.api.db.do("INSERT INTO slice_person (person_id, slice_id)" \
-                           " VALUES(%(person_id)d, %(slice_id)d)",
-                           locals())
+        check_future = not ('is_deleted' in self and self['is_deleted'])
+        return Row.validate_timestamp(self, expires, check_future = check_future)
 
 
-            if commit:
-                self.api.db.commit()
+    add_person = Row.add_object(Person, 'slice_person')
+    remove_person = Row.remove_object(Person, 'slice_person')
 
 
-            self['person_ids'].append(person_id)
-            person['slice_ids'].append(slice_id)
+    add_node = Row.add_object(Node, 'slice_node')
+    remove_node = Row.remove_object(Node, 'slice_node')
 
 
-    def remove_person(self, person, commit = True):
-        """
-        Remove person from existing slice.
-        """
-
-        assert 'slice_id' in self
-        assert isinstance(person, PLC.Persons.Person)
-        assert 'person_id' in person
-
-        slice_id = self['slice_id']
-        person_id = person['person_id']
+    add_to_node_whitelist = Row.add_object(Node, 'node_slice_whitelist')
+    delete_from_node_whitelist = Row.remove_object(Node, 'node_slice_whitelist')
 
 
-        if person_id in self['person_ids']:
-            assert slice_id in person['slice_ids']
-
-            self.api.db.do("DELETE FROM slice_person" \
-                           " WHERE person_id = %(person_id)d" \
-                           " AND slice_id = %(slice_id)d",
-                           locals())
-
-            if commit:
-                self.api.db.commit()
-
-            self['person_ids'].remove(person_id)
-            person['slice_ids'].remove(slice_id)
-
-    def add_node(self, node, commit = True):
+    def associate_persons(self, auth, field, value):
         """
         """
-        Add node to existing slice.
+        Adds persons found in value list to this slice (using AddPersonToSlice).
+       Deletes persons not found in value list from this slice (using DeletePersonFromSlice).
         """
         """
-
-        assert 'slice_id' in self
-        assert isinstance(node, (Node,ForeignNode))
-        assert 'node_id' in node
-
-        slice_id = self['slice_id']
-        node_id = node['node_id']
-
-        if node_id not in self['node_ids']:
-            assert slice_id not in node['slice_ids']
-
-            self.api.db.do("INSERT INTO slice_node (node_id, slice_id)" \
-                           " VALUES(%(node_id)d, %(slice_id)d)",
-                           locals())
-
-            if commit:
-                self.api.db.commit()
-
-            self['node_ids'].append(node_id)
-            node['slice_ids'].append(slice_id)
-
-    def remove_node(self, node, commit = True):
-        """
-        Remove node from existing slice.
-        """
-
-        assert 'slice_id' in self
-        assert isinstance(node, (Node,ForeignNode))
-        assert 'node_id' in node
-
-        slice_id = self['slice_id']
-        node_id = node['node_id']
-
-        if node_id in self['node_ids']:
-            assert slice_id in node['slice_ids']
-
-            self.api.db.do("DELETE FROM slice_node" \
-                           " WHERE node_id = %(node_id)d" \
-                           " AND slice_id = %(slice_id)d",
-                           locals())
-
-            if commit:
-                self.api.db.commit()
-
-            self['node_ids'].remove(node_id)
-            node['slice_ids'].remove(slice_id)
-
+       
+       assert 'person_ids' in self
+       assert 'slice_id' in self
+        assert isinstance(value, list)
+
+       (person_ids, emails) = self.separate_types(value)[0:2]
+
+       # Translate emails into person_ids      
+       if emails:
+           persons = Persons(self.api, emails, ['person_id']).dict('person_id')
+           person_ids += persons.keys()
+       
+       # Add new ids, remove stale ids
+        if self['person_ids'] != person_ids:
+            from PLC.Methods.AddPersonToSlice import AddPersonToSlice
+            from PLC.Methods.DeletePersonFromSlice import DeletePersonFromSlice
+            new_persons = set(person_ids).difference(self['person_ids'])
+            stale_persons = set(self['person_ids']).difference(person_ids)
+
+            for new_person in new_persons:
+                AddPersonToSlice.__call__(AddPersonToSlice(self.api), auth, new_person, self['slice_id'])
+            for stale_person in stale_persons:
+                DeletePersonFromSlice.__call__(DeletePersonFromSlice(self.api), auth, stale_person, self['slice_id'])
+
+    def associate_nodes(self, auth, field, value):
+       """
+       Adds nodes found in value list to this slice (using AddSliceToNodes).
+       Deletes nodes not found in value list from this slice (using DeleteSliceFromNodes).
+       """
+
+        from PLC.Nodes import Nodes
+
+       assert 'node_ids' in self
+       assert 'slice_id' in self
+       assert isinstance(value, list)
+       
+       (node_ids, hostnames) = self.separate_types(value)[0:2]
+       
+       # Translate hostnames into node_ids
+       if hostnames:
+           nodes = Nodes(self.api, hostnames, ['node_id']).dict('node_id')
+           node_ids += nodes.keys()
+       
+       # Add new ids, remove stale ids
+       if self['node_ids'] != node_ids:
+           from PLC.Methods.AddSliceToNodes import AddSliceToNodes
+           from PLC.Methods.DeleteSliceFromNodes import DeleteSliceFromNodes
+           new_nodes = set(node_ids).difference(self['node_ids'])
+           stale_nodes = set(self['node_ids']).difference(node_ids)
+           
+           if new_nodes:
+               AddSliceToNodes.__call__(AddSliceToNodes(self.api), auth, self['slice_id'], list(new_nodes))
+           if stale_nodes:
+               DeleteSliceFromNodes.__call__(DeleteSliceFromNodes(self.api), auth, self['slice_id'], list(stale_nodes))                        
+    def associate_slice_attributes(self, auth, fields, value):
+       """
+       Deletes slice_attribute_ids not found in value list (using DeleteSliceAttribute). 
+       Adds slice_attributes if slice_fields w/o slice_id is found (using AddSliceAttribute).
+       Updates slice_attribute if slice_fields w/ slice_id is found (using UpdateSlceiAttribute).  
+       """
+       
+       assert 'slice_attribute_ids' in self
+       assert isinstance(value, list)
+
+       (attribute_ids, blank, attributes) = self.separate_types(value)
+       
+       # There is no way to add attributes by id. They are
+       # associated with a slice when they are created.
+       # So we are only looking to delete here 
+       if self['slice_attribute_ids'] != attribute_ids:
+           from PLC.Methods.DeleteSliceAttribute import DeleteSliceAttribute
+           stale_attributes = set(self['slice_attribute_ids']).difference(attribute_ids)
+       
+           for stale_attribute in stale_attributes:
+               DeleteSliceAttribute.__call__(DeleteSliceAttribute(self.api), auth, stale_attribute['slice_attribute_id'])              
+       
+       # If dictionary exists, we are either adding new
+        # attributes or updating existing ones.
+        if attributes:
+            from PLC.Methods.AddSliceAttribute import AddSliceAttribute
+            from PLC.Methods.UpdateSliceAttribute import UpdateSliceAttribute
+       
+           added_attributes = filter(lambda x: 'slice_attribute_id' not in x, attributes)
+           updated_attributes = filter(lambda x: 'slice_attribute_id' in x, attributes)
+
+           for added_attribute in added_attributes:
+               if 'attribute_type' in added_attribute:
+                   type = added_attribute['attribute_type']
+               elif 'attribute_type_id' in added_attribute:
+                   type = added_attribute['attribute_type_id']
+               else:
+                   raise PLCInvalidArgument, "Must specify attribute_type or attribute_type_id"
+
+               if 'value' in added_attribute:
+                   value = added_attribute['value']
+               else:
+                   raise PLCInvalidArgument, "Must specify a value"
+               
+               if 'node_id' in added_attribute:
+                   node_id = added_attribute['node_id']
+               else:
+                   node_id = None
+
+               if 'nodegroup_id' in added_attribute:
+                   nodegroup_id = added_attribute['nodegroup_id']
+               else:
+                   nodegroup_id = None 
+               AddSliceAttribute.__call__(AddSliceAttribute(self.api), auth, self['slice_id'], type, value, node_id, nodegroup_id)
+           for updated_attribute in updated_attributes:
+               attribute_id = updated_attribute.pop('slice_attribute_id')
+               if attribute_id not in self['slice_attribute_ids']:
+                   raise PLCInvalidArgument, "Attribute doesnt belong to this slice" 
+               else:
+                   UpdateSliceAttribute.__call__(UpdateSliceAttribute(self.api), auth, attribute_id, updated_attribute)                 
+       
     def sync(self, commit = True):
         """
         Add or update a slice.
     def sync(self, commit = True):
         """
         Add or update a slice.
@@ -197,15 +246,15 @@ class Slice(Row):
         assert 'slice_id' in self
 
         # Clean up miscellaneous join tables
         assert 'slice_id' in self
 
         # Clean up miscellaneous join tables
-        for table in ['slice_node', 'slice_person', 'slice_attribute']:
-            self.api.db.do("DELETE FROM %s" \
-                           " WHERE slice_id = %d" % \
-                           (table, self['slice_id']), self)
+        for table in self.join_tables:
+            self.api.db.do("DELETE FROM %s WHERE slice_id = %d" % \
+                           (table, self['slice_id']))
 
         # Mark as deleted
         self['is_deleted'] = True
         self.sync(commit)
 
 
         # Mark as deleted
         self['is_deleted'] = True
         self.sync(commit)
 
+
 class Slices(Table):
     """
     Representation of row(s) from the slices table in the
 class Slices(Table):
     """
     Representation of row(s) from the slices table in the
@@ -220,10 +269,10 @@ class Slices(Table):
 
         if expires is not None:
             if expires >= 0:
 
         if expires is not None:
             if expires >= 0:
-                sql += " AND expires > %(expires)d"
+                sql += " AND expires > %d" % expires
             else:
                 expires = -expires
             else:
                 expires = -expires
-                sql += " AND expires < %(expires)d"
+                sql += " AND expires < %d" % expires
 
         if slice_filter is not None:
             if isinstance(slice_filter, (list, tuple, set)):
 
         if slice_filter is not None:
             if isinstance(slice_filter, (list, tuple, set)):
@@ -231,9 +280,17 @@ class Slices(Table):
                 ints = filter(lambda x: isinstance(x, (int, long)), slice_filter)
                 strs = filter(lambda x: isinstance(x, StringTypes), slice_filter)
                 slice_filter = Filter(Slice.fields, {'slice_id': ints, 'name': strs})
                 ints = filter(lambda x: isinstance(x, (int, long)), slice_filter)
                 strs = filter(lambda x: isinstance(x, StringTypes), slice_filter)
                 slice_filter = Filter(Slice.fields, {'slice_id': ints, 'name': strs})
-                sql += " AND (%s)" % slice_filter.sql(api, "OR")
+                sql += " AND (%s) %s" % slice_filter.sql(api, "OR")
             elif isinstance(slice_filter, dict):
                 slice_filter = Filter(Slice.fields, slice_filter)
             elif isinstance(slice_filter, dict):
                 slice_filter = Filter(Slice.fields, slice_filter)
-                sql += " AND (%s)" % slice_filter.sql(api, "AND")
+                sql += " AND (%s) %s" % slice_filter.sql(api, "AND")
+            elif isinstance (slice_filter, StringTypes):
+                slice_filter = Filter(Slice.fields, {'name':[slice_filter]})
+                sql += " AND (%s) %s" % slice_filter.sql(api, "AND")
+            elif isinstance (slice_filter, int):
+                slice_filter = Filter(Slice.fields, {'slice_id':[slice_filter]})
+                sql += " AND (%s) %s" % slice_filter.sql(api, "AND")
+            else:
+                raise PLCInvalidArgument, "Wrong slice filter %r"%slice_filter
 
 
-        self.selectall(sql, locals())
+        self.selectall(sql)