add new slice functions
authorMark Huang <mlhuang@cs.princeton.edu>
Mon, 2 Oct 2006 15:38:54 +0000 (15:38 +0000)
committerMark Huang <mlhuang@cs.princeton.edu>
Mon, 2 Oct 2006 15:38:54 +0000 (15:38 +0000)
PLC/Methods/AddSlice.py [new file with mode: 0644]
PLC/Methods/DeleteSlice.py [new file with mode: 0644]
PLC/Methods/GetSlices.py [new file with mode: 0644]
PLC/Methods/UpdateSlice.py [new file with mode: 0644]
PLC/Methods/__init__.py
PLC/__init__.py

diff --git a/PLC/Methods/AddSlice.py b/PLC/Methods/AddSlice.py
new file mode 100644 (file)
index 0000000..76dc24d
--- /dev/null
@@ -0,0 +1,74 @@
+import re
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Slices import Slice, Slices
+from PLC.Auth import PasswordAuth
+from PLC.Sites import Site, Sites
+
+class AddSlice(Method):
+    """
+    Adds a new slice. Any fields specified in optional_vals are used,
+    otherwise defaults are used.
+
+    Valid slice names are lowercase and begin with the login_base
+    (slice prefix) of a valid site, followed by a single
+    underscore. Thereafter, only letters, numbers, or additional
+    underscores may be used.
+
+    PIs may only add slices associated with their own sites (i.e.,
+    slice prefixes must always be the login_base of one of their
+    sites).
+
+    Returns the new slice_id (> 0) if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi']
+
+    can_update = lambda (field, value): field in \
+                 ['instantiation', 'url', 'description', 'max_nodes']
+    update_fields = dict(filter(can_update, Slice.fields.items()))
+
+    accepts = [
+        PasswordAuth(),
+        Slice.fields['name'],
+        update_fields
+        ]
+
+    returns = Parameter(int, 'New slice_id (> 0) if successful')
+
+    def call(self, auth, name, optional_vals = {}):
+        if filter(lambda field: field not in self.update_fields, optional_vals):
+            raise PLCInvalidArgument, "Invalid field specified"
+
+        # 1. Lowercase.
+        # 2. Begins with login_base (only letters).
+        # 3. Then single underscore after login_base.
+        # 4. Then letters, numbers, or underscores.
+        good_name = r'^[a-z]+_[a-z0-9_]+$'
+        if not name or \
+           not re.match(good_name, name):
+            raise PLCInvalidArgument, "Invalid slice name"
+
+        # Get associated site details
+        login_base = name.split("_")[0]
+        sites = Sites(self.api, [login_base]).values()
+        if not sites:
+            raise PLCInvalidArgument, "Invalid slice prefix"
+        site = sites[0]
+
+        if 'admin' not in self.caller['roles']:
+            if site['site_id'] not in self.caller['site_ids']:
+                raise PLCPermissionDenied, "Slice prefix must be the same as the login_base of one of your sites"
+
+        if len(site['slice_ids']) >= site['max_slices']:
+            raise PLCInvalidArgument, "Site has reached its maximum allowable slice count"
+
+        slice = Slice(self.api, optional_vals)
+        slice['creator_person_id'] = self.caller['person_id']
+        slice['name'] = name
+        slice['site_id'] = site['site_id']
+        slice.sync()
+
+        return slice['slice_id']
diff --git a/PLC/Methods/DeleteSlice.py b/PLC/Methods/DeleteSlice.py
new file mode 100644 (file)
index 0000000..c5b511e
--- /dev/null
@@ -0,0 +1,44 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Slices import Slice, Slices
+from PLC.Auth import PasswordAuth
+
+class DeleteSlice(Method):
+    """
+    Deletes the specified slice.
+
+    Users may only delete slices of which they are members. PIs may
+    delete any of the slices at their sites. Admins may delete any
+    slice.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi', 'user']
+
+    accepts = [
+        PasswordAuth(),
+        Mixed(Slice.fields['slice_id'],
+              Slice.fields['name']),
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, slice_id_or_name):
+        slices = Slices(self.api, [slice_id_or_name]).values()
+        if not slices:
+            raise PLCInvalidArgument, "No such slice"
+        slice = slices[0]
+
+        if 'admin' not in self.caller['roles']:
+            if self.caller['person_id'] in slice['person_ids']:
+                pass
+            elif 'pi' not in self.caller['roles']:
+                raise PLCPermissionDenied, "Not a member of the specified slice"
+            elif slice['site_id'] not in self.caller['site_ids']:
+                raise PLCPermissionDenied, "Specified slice not associated with any of your sites"
+
+        slice.delete()
+
+        return 1
diff --git a/PLC/Methods/GetSlices.py b/PLC/Methods/GetSlices.py
new file mode 100644 (file)
index 0000000..624a6bb
--- /dev/null
@@ -0,0 +1,54 @@
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import PasswordAuth
+from PLC.Slices import Slice, Slices
+
+class GetSlices(Method):
+    """
+    Return an array of structs containing details about slices. If
+    slice_id_or_name_list is specified, only the specified slices will
+    be queried.
+
+    Users may only query slices of which they are members. PIs may
+    query any of the slices at their sites. Admins may query any
+    slice. If a slice that cannot be queried is specified in
+    slice_id_or_name_list, details about that slice will not be
+    returned.
+    """
+
+    roles = ['admin', 'pi', 'user', 'tech']
+
+    accepts = [
+        PasswordAuth(),
+        [Mixed(Slice.fields['slice_id'],
+               Slice.fields['name'])],
+        Parameter([str], 'List of fields to return')
+        ]
+
+    # Filter out is_deleted field
+    can_return = lambda (field, value): field not in ['is_deleted']
+    return_fields = dict(filter(can_return, Slice.fields.items()))
+    returns = [return_fields]
+
+    def call(self, auth, slice_id_or_name_list = None):
+        # Get slice information
+        slices = Slices(self.api, slice_id_or_name_list).values()
+
+        # Filter out slices that are not viewable
+        if 'admin' not in self.caller['roles']:
+            member_of = lambda slice: self.caller['person_id'] in slice['person_ids']
+            if 'pi' in self.caller['roles']:
+                can_view = lambda slice: \
+                           member_of(slice) or \
+                           slice['site_id'] in self.caller['site_ids']
+            else:
+                can_view = member_of
+            slices = filter(can_view, slices)
+
+        # Filter out undesired or None fields (XML-RPC cannot marshal
+        # None) and turn each slice into a real dict.
+        valid_return_fields_only = lambda (key, value): value is not None
+        slices = [dict(filter(valid_return_fields_only, slice.items())) \
+                 for slice in slices]
+
+        return slices
diff --git a/PLC/Methods/UpdateSlice.py b/PLC/Methods/UpdateSlice.py
new file mode 100644 (file)
index 0000000..7846754
--- /dev/null
@@ -0,0 +1,88 @@
+import time
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Slices import Slice, Slices
+from PLC.Auth import PasswordAuth
+from PLC.Sites import Site, Sites
+
+class UpdateSlice(Method):
+    """
+    Updates the parameters of an existing slice with the values in
+    update_fields.
+
+    Users may only update slices of which they are members. PIs may
+    update any of the slices at their slices. Admins may update any
+    slice.
+
+    Only PIs and admins may update max_nodes. Slices cannot be renewed
+    (by updating the expires parameter) more than 8 weeks into the
+    future.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi', 'user']
+
+    can_update = lambda (field, value): field in \
+                 ['instantiation', 'url', 'description', 'max_nodes', 'expires']
+    update_fields = dict(filter(can_update, Slice.fields.items()))
+
+    accepts = [
+        PasswordAuth(),
+        Mixed(Slice.fields['slice_id'],
+              Slice.fields['name']),
+        update_fields
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, slice_id_or_name, update_fields):
+        if filter(lambda field: field not in self.update_fields, update_fields):
+            raise PLCInvalidArgument, "Invalid field specified"
+
+        slices = Slices(self.api, [slice_id_or_name]).values()
+        if not slices:
+            raise PLCInvalidArgument, "No such slice"
+        slice = slices[0]
+
+        if 'admin' not in self.caller['roles']:
+            if self.caller['person_id'] in slice['person_ids']:
+                pass
+            elif 'pi' not in self.caller['roles']:
+                raise PLCPermissionDenied, "Not a member of the specified slice"
+            elif slice['site_id'] not in self.caller['site_ids']:
+                raise PLCPermissionDenied, "Specified slice not associated with any of your sites"
+
+        # Renewing
+        if 'expires' in update_fields and update_fields['expires'] > slice['expires']:
+            sites = Sites(self.api, [slice['site_id']]).values()
+            assert sites
+            site = sites[0]
+
+            if site['max_slices'] < 0:
+                raise PLCInvalidArgument, "Slice creation and renewal have been disabled for the site"
+
+            # Maximum expiration date is 8 weeks from now
+            # XXX Make this configurable
+            max_expires = time.time() + (8 * 7 * 24 * 60 * 60)
+
+            if 'admin' not in self.caller['roles'] and update_fields['expires'] > max_expires:
+                raise PLCInvalidArgument, "Cannot renew a slice beyond 8 weeks from now"
+
+        if 'max_nodes' in update_fields and update_fields['max_nodes'] != slice['max_nodes']:
+            if 'admin' not in self.caller['roles'] and \
+               'pi' not in self.caller['roles']:
+                raise PLCInvalidArgument, "Only admins and PIs may update max_nodes"
+
+        slice.update(update_fields)
+
+        # XXX Make this a configurable policy
+        if slice['description'] is None or not slice['description'].strip() or \
+           slice['url'] is None or not slice['url'].strip():
+            raise PLCInvalidArgument, "Cannot renew a slice with an empty description or URL"
+
+        slice.sync()
+
+        return 1
index fb45aae..ed4de53 100644 (file)
@@ -1 +1 @@
-methods = 'AdmAddNode AdmAddPerson AdmAddPersonToSite AdmAddSite AdmAuthCheck AdmDeleteNode AdmDeletePerson AdmDeleteSite AdmGetAllRoles AdmGetNodes AdmGetPersonRoles AdmGetPersonSites AdmGetPersons AdmGetSites AdmGrantRoleToPerson AdmIsPersonInRole AdmRemovePersonFromSite AdmRevokeRoleFromPerson AdmSetPersonEnabled AdmSetPersonPrimarySite AdmUpdateNode AdmUpdatePerson  system.listMethods  system.methodHelp  system.methodSignature  system.multicall'.split()
+methods = 'AddSlice AdmAddNodeGroup AdmAddNodeNetwork AdmAddNode AdmAddNodeToNodeGroup AdmAddPerson AdmAddPersonToSite AdmAddSite AdmAuthCheck AdmDeleteNodeGroup AdmDeleteNodeNetwork AdmDeleteNode AdmDeletePerson AdmDeleteSite AdmGetAllNodeNetworkBandwidthLimits AdmGetAllNodeNetworks AdmGetAllRoles AdmGetNodeGroupNodes AdmGetNodeGroups AdmGetNodes AdmGetPersonRoles AdmGetPersonSites AdmGetPersons AdmGetSiteNodes AdmGetSitePersons AdmGetSites AdmGrantRoleToPerson AdmIsPersonInRole AdmRemoveNodeFromNodeGroup AdmRemovePersonFromSite AdmRevokeRoleFromPerson AdmSetPersonEnabled AdmSetPersonPrimarySite AdmUpdateNodeGroup AdmUpdateNodeNetwork AdmUpdateNode AdmUpdatePerson AdmUpdateSite DeleteSlice GetSlices UpdateSlice  system.listMethods  system.methodHelp  system.methodSignature  system.multicall'.split()
index f4ec5ca..0527953 100644 (file)
@@ -1 +1 @@
-all = 'Addresses AddressTypes API Auth BootStates Config Debug Faults Keys md5crypt Method NodeGroups NodeNetworks Nodes Parameter PCUs Persons PostgreSQL Roles Sites Slices Table'.split()
+all = 'Addresses AddressTypes API Auth BootStates Config Debug Faults Keys Method NodeGroups NodeNetworks Nodes Parameter PCUs Persons PostgreSQL Roles Sites SliceInstantiations Slices Slivers Table'.split()