Jude's latest changes to syndicate models and admin
diff --git a/planetstack/syndicate/admin.py b/planetstack/syndicate/admin.py
index 85c3ebd..fd306a2 100644
--- a/planetstack/syndicate/admin.py
+++ b/planetstack/syndicate/admin.py
@@ -14,6 +14,7 @@
from suit.widgets import LinkedSelect
from bitfield import BitField
from bitfield.forms import BitFieldCheckboxSelectMultiple
+from django.core.exceptions import ValidationError, ObjectDoesNotExist
class SyndicateServiceAdmin(SingletonAdmin,ReadOnlyAwareAdmin):
model = SyndicateService
@@ -31,6 +32,7 @@
('serviceattrs','Additional Attributes'),
)
+
class VolumeAccessRightForUserROInline(ReadOnlyTabularInline):
model = VolumeAccessRight
extra = 0
@@ -47,40 +49,9 @@
model = VolumeAccessRight
extra = 0
suit_classes = 'suit-tab suit-tab-volumeAccessRights'
-
-class VolumeAccessRightAdmin(ReadOnlyAwareAdmin):
- model = VolumeAccessRight
-
- formfield_overrides = { BitField: {'widget': BitFieldCheckboxSelectMultiple},}
- list_display = ['owner_id', 'volume']
- user_readonly_fields = ['owner_id','volume','gateway_caps']
- user_readonly_inlines = []
-
-class VolumeAccessRequestForUserROInline(ReadOnlyTabularInline):
- model = VolumeAccessRequest
- extra = 0
- suit_classes = 'suit-tab suit-tab-volumeAccessRequests'
- fields = ['volume', 'message']
-
-class VolumeAccessRequestROInline(ReadOnlyTabularInline):
- model = VolumeAccessRequest
- extra = 0
- suit_classes = 'suit-tab suit-tab-volumeAccessRequests'
- fields = ['owner_id', 'message']
-
-class VolumeAccessRequestInline(PlStackTabularInline):
- model = VolumeAccessRequest
- extra = 0
- suit_classes = 'suit-tab suit-tab-volumeAccessRequests'
- fields = ['owner_id', 'message']
-
-class VolumeAccessRequestAdmin(ReadOnlyAwareAdmin):
- model = VolumeAccessRequest
-
- formfield_overrides = { BitField: {'widget': BitFieldCheckboxSelectMultiple},}
- list_display = ['owner_id', 'volume', 'message']
- user_readonly_fields = ['volume','owner_id','message','message', 'gateway_caps']
- user_readonly_inlines = []
+ formfield_overrides = {
+ BitField: {'widget': BitFieldCheckboxSelectMultiple}
+ }
class VolumeInline(PlStackTabularInline):
model = Volume
@@ -94,56 +65,123 @@
suit_classes = 'suit-tab suit-tab-volumes'
fields = ['name', 'owner_id']
+
+class VolumeSliceFormSet( forms.models.BaseInlineFormSet ):
+ # verify that our VolumeSlice is valid
+
+ @classmethod
+ def verify_unchanged( cls, volume_pk, slice_pk, field_name, new_value ):
+ vs = None
+ try:
+ vs = VolumeSlice.objects.get( volume_id=volume_pk, slice_id=slice_pk )
+ except ObjectDoesNotExist, dne:
+ return True, None
+
+ old_value = getattr( vs, field_name )
+ if old_value != new_value:
+ return False, old_value
+ else:
+ return True, None
+
+
+ def clean( self ):
+ for form in self.forms:
+ # check each inline's cleaned data, if it's valid
+ cleaned_data = None
+ try:
+ if form.cleaned_data:
+ cleaned_data = form.cleaned_data
+ except AttributeError:
+ continue
+
+ # verify that the ports haven't changed
+ volume_pk = cleaned_data['volume_id'].pk
+ slice_pk = cleaned_data['slice_id'].pk
+
+ if not cleaned_data.has_key('peer_portnum'):
+ raise ValidationError("Missing client peer-to-peer cache port number")
+
+ if not cleaned_data.has_key('replicate_portnum'):
+ raise ValidationError("Missing replication service port number")
+
+ rc1, old_peer_port = VolumeSliceFormSet.verify_unchanged( volume_pk, slice_pk, 'peer_portnum', cleaned_data['peer_portnum'] )
+ rc2, old_replicate_port = VolumeSliceFormSet.verify_unchanged( volume_pk, slice_pk, 'replicate_portnum', cleaned_data['replicate_portnum'] )
+
+ err1str = ""
+ err2str = ""
+ if not rc1:
+ err1str = "change %s back to %s" % (cleaned_data['peer_portnum'], old_peer_port)
+ if not rc2:
+ err2str = " and change %s back to %s" % (cleaned_data['replicate_portnum'], old_replicate_port )
+
+ if not rc1 or not rc2:
+ raise ValidationError("Port numbers cannot be changed once they are set. Please %s %s" % (err1str, err2str))
+
+
+
+class VolumeSliceInline(PlStackTabularInline):
+ model = VolumeSlice
+ extra = 0
+ suit_classes = 'suit-tab suit-tab-volumeSlices'
+ fields = ['volume_id', 'slice_id', 'gateway_caps', 'peer_portnum', 'replicate_portnum']
+ formfield_overrides = { BitField: {'widget': BitFieldCheckboxSelectMultiple},}
+
+ formset = VolumeSliceFormSet
+
+ readonly_fields = ['credentials_blob']
+
+
+class VolumeSliceROInline(ReadOnlyTabularInline):
+ model = VolumeSlice
+ extra = 0
+ suit_classes = 'suit-tab suit-tab-volumeSlices'
+ fields = ['volume_id', 'slice_id', 'gateway_caps', 'peer_portnum', 'replicate_portnum']
+ formfield_overrides = { BitField: {'widget': BitFieldCheckboxSelectMultiple},}
+
+ formset = VolumeSliceFormSet
+
+ readonly_fields = ['credentials_blob']
+
+
class VolumeAdmin(ReadOnlyAwareAdmin):
model = Volume
- read_only_fields = ['blockSize']
+
+ def get_readonly_fields(self, request, obj=None ):
+ always_readonly = []
+ if obj == None:
+ # all fields are editable on add
+ return always_readonly
+
+ else:
+ # can't change owner, slice id, or block size on update
+ return ['blocksize', 'owner_id'] + always_readonly
+
+
list_display = ['name', 'owner_id']
formfield_overrides = { BitField: {'widget': BitFieldCheckboxSelectMultiple},}
- detailsFieldList = ['name', 'owner_id', 'description','file_quota','blocksize', 'private','archive', 'default_gateway_caps' ]
- keyList = ['metadata_public_key','metadata_private_key','api_public_key']
-
+ #detailsFieldList = ['name', 'owner_id', 'description','file_quota','blocksize', 'private','archive', 'default_gateway_caps' ]
+ detailsFieldList = ['name', 'owner_id', 'description','blocksize', 'private','archive', 'default_gateway_caps' ]
+
fieldsets = [
(None, {'fields': detailsFieldList, 'classes':['suit-tab suit-tab-general']}),
- (None, {'fields': keyList, 'classes':['suit-tab suit-tab-volumeKeys']}),
+ #(None, {'fields': keyList, 'classes':['suit-tab suit-tab-volumeKeys']}),
]
- inlines = [VolumeAccessRightInline, VolumeAccessRequestInline]
+ inlines = [VolumeAccessRightInline, VolumeSliceInline]
- user_readonly_fields = ['name','owner_id','description','blocksize','private','metadata_public_key','metadata_private_key','api_public_key','file_quota','default_gateway_caps']
- user_readonly_inlines = [VolumeAccessRightROInline, VolumeAccessRequestROInline]
+ user_readonly_fields = ['name','owner_id','description','blocksize','private','default_gateway_caps']
+
+ user_readonly_inlines = [VolumeAccessRightROInline, VolumeSliceROInline]
suit_form_tabs =(('general', 'Volume Details'),
- ('volumeKeys', 'Access Keys'),
- ('volumeAccessRequests', 'Volume Access Requests'),
+ #('volumeKeys', 'Access Keys'),
+ ('volumeSlices', 'Slices'),
('volumeAccessRights', 'Volume Access Rights'),
)
-
-class SyndicateUserAdmin(ReadOnlyAwareAdmin):
- model = SyndicateUser
- verbose_name = "Users"
- verbose_name = "Users"
- list_display = ['user','is_admin', 'max_volumes']
- inlines = [VolumeInline,VolumeAccessRequestInline,VolumeAccessRightInline]
- user_readonly_fields = ['user','is_admin','max_volumes','max_UGs','max_RGs','max_AGs']
- user_readonly_inlines = [VolumeROInline,VolumeAccessRequestForUserROInline,VolumeAccessRightForUserROInline]
-
- fieldsets = [
- (None, {'fields': ['user','is_admin','max_volumes','max_UGs','max_RGs','max_AGs'], 'classes':['suit-tab suit-tab-general']}),
- ]
-
- suit_form_tabs =(('general', 'Volume Details'),
- ('volumes', 'Volumes'),
- ('volumeAccessRequests', 'Volume Access Requests'),
- ('volumeAccessRights', 'Volume Access Rights'),
- )
-
+# left panel:
admin.site.register(SyndicateService, SyndicateServiceAdmin)
-admin.site.register(VolumeAccessRight, VolumeAccessRightAdmin)
-admin.site.register(VolumeAccessRequest, VolumeAccessRequestAdmin)
admin.site.register(Volume, VolumeAdmin)
-admin.site.register(SyndicateUser, SyndicateUserAdmin)
-
diff --git a/planetstack/syndicate/models.py b/planetstack/syndicate/models.py
index efc08c6..656e881 100644
--- a/planetstack/syndicate/models.py
+++ b/planetstack/syndicate/models.py
@@ -1,8 +1,9 @@
-from core.models import User,Site,Service,SingletonModel,PlCoreBase
+from core.models import User,Site,Service,SingletonModel,PlCoreBase,Slice
import os
from django.db import models
from django.forms.models import model_to_dict
from bitfield import BitField
+from django.core.exceptions import ValidationError
# Create your models here.
@@ -14,51 +15,79 @@
def __unicode__(self): return u'Syndicate Service'
-class SyndicateUser(models.Model):
- user = models.ForeignKey(User)
- is_admin = models.BooleanField(default=False, help_text="Indicates this user has Administrative purposes for the Syndicate Service")
- max_volumes = models.PositiveIntegerField(help_text="Maximum number of Volumes this user may create.", default=1)
- max_UGs = models.PositiveIntegerField(help_text="Maximum number of User Gateways this user may create.", default=500)
- max_RGs = models.PositiveIntegerField(help_text="Maximum number of Replica Gateways this user may create.", default=500)
- max_AGs = models.PositiveIntegerField(help_text="Maximum number of Aquisition Gateways this user may create.", default=10)
-
- def __unicode__(self): return self.user.email
-
-class Volume(models.Model):
+class SyndicatePrincipal(PlCoreBase):
+ class Meta:
+ app_label = "syndicate"
+
+ # for now, this is a user email address
+ principal_id = models.TextField()
+ public_key_pem = models.TextField()
+ sealed_private_key = models.TextField()
+
+ def __unicode__self(self): return "%s" % self.principal_id
+
+
+class Volume(PlCoreBase):
+ class Meta:
+ app_label = "syndicate"
+
name = models.CharField(max_length=64, help_text="Human-readable, searchable name of the Volume")
- owner_id = models.ForeignKey(SyndicateUser, verbose_name='Owner')
+
+ owner_id = models.ForeignKey(User, verbose_name='Owner')
+
description = models.TextField(null=True, blank=True,max_length=130, help_text="Human-readable description of what this Volume is used for.")
blocksize = models.PositiveIntegerField(help_text="Number of bytes per block.")
private = models.BooleanField(default=True, help_text="Indicates if the Volume is visible to users other than the Volume Owner and Syndicate Administrators.")
- archive = models.BooleanField(default=True, help_text="Indicates if this Volume is read-only, and only an Aquisition Gateway owned by the Volume owner (or Syndicate admin) can write to it.")
- metadata_public_key = models.TextField(null=True, blank=True, max_length=1024, help_text="Public key Gateways will use to verify the authenticity of metadata from this Volume")
- metadata_private_key = models.TextField(null=True, blank=True, max_length=1024, help_text="Private key the Volume should use to sign metadata served to Gateways")
- api_public_key = models.TextField(null=True, blank=True, max_length=1024, help_text="Public key used to verify writes to these fields from Volume owner")
+ archive = models.BooleanField(default=False, help_text="Indicates if this Volume is read-only, and only an Aquisition Gateway owned by the Volume owner (or Syndicate admin) can write to it.")
- file_quota = models.IntegerField(help_text='Maximum number of files and directories allowed in this Volume (-1 means "unlimited")')
-
- default_gateway_caps = BitField(flags=('GATEWAY_CAP_READ_DATA','GATEWAY_CAP_READ_METADATA', 'GATEWAY_CAP_WRITE_DATA', 'GATEWAY_CAP_WRITE_METADATA', 'GATEWAY_CAP_COORDINATE'), verbose_name='Default Gateway Capabilities')
- #default_gateway_caps = models.PositiveIntegerField(verbose_name='Default Gateway Capabilities')
- #default_gateway_caps2 = models.CharField(max_length=32,null=True,default = "readonly", verbose_name='Default Gateway Capabilities')
+ CAP_READ_DATA = 1
+ CAP_WRITE_DATA = 2
+ CAP_HOST_DATA = 4
+
+ # NOTE: preserve order of capabilities here...
+ default_gateway_caps = BitField(flags=("read data", "write data", "host files"), verbose_name='Default User Capabilities')
def __unicode__(self): return self.name
-class VolumeAccessRight(models.Model):
- owner_id = models.ForeignKey(SyndicateUser, verbose_name='user')
+
+class VolumeAccessRight(PlCoreBase):
+ class Meta:
+ app_label = "syndicate"
+
+ owner_id = models.ForeignKey(User, verbose_name='user')
+
volume = models.ForeignKey(Volume)
- gateway_caps = BitField(flags=('GATEWAY_CAP_READ_DATA','GATEWAY_CAP_READ_METADATA', 'GATEWAY_CAP_WRITE_DATA', 'GATEWAY_CAP_WRITE_METADATA', 'GATEWAY_CAP_COORDINATE'), verbose_name='Gateway Capabilities')
- #gateway_caps = models.PositiveIntegerField(verbose_name='Gateway Capabilities')
- #gateway_caps2 = models.CharField(max_length=32, default='readonly',null=True,verbose_name='Default Gateway Capabilities')
+ gateway_caps = BitField(flags=("read data", "write data", "host files"), verbose_name="User Capabilities")
- def __unicode__(self): return self.owner_id.user.email
+ def __unicode__(self): return "%s-%s" % (self.owner_id.email, self.volume.name)
-class VolumeAccessRequest(models.Model):
- owner_id = models.ForeignKey(SyndicateUser, verbose_name='user')
- volume = models.ForeignKey(Volume)
- message = models.TextField(null=True, blank=True, max_length=1024, help_text="Description of why the user wants access to the volume.")
- gateway_caps = BitField(flags=('GATEWAY_CAP_READ_DATA','GATEWAY_CAP_READ_METADATA', 'GATEWAY_CAP_WRITE_DATA', 'GATEWAY_CAP_WRITE_METADATA', 'GATEWAY_CAP_COORDINATE'), verbose_name='Gateway Capabilities')
- #gateway_caps = models.PositiveIntegerField(verbose_name='Gateway Capabilities')
- #gateway_caps2 = models.CharField(max_length=32,default='readonly',null=True,verbose_name='Default Gateway Capabilities')
- def __unicode__(self): return self.owner_id.user.email
+class VolumeSlice(PlCoreBase):
+ class Meta:
+ app_label = "syndicate"
+
+ volume_id = models.ForeignKey(Volume, verbose_name="Volume")
+ slice_id = models.ForeignKey(Slice, verbose_name="Slice")
+ gateway_caps = BitField(flags=("read data", "write data", "host files"), verbose_name="Slice Capabilities")
+
+ peer_portnum = models.PositiveIntegerField(help_text="User Gateway port", verbose_name="Client peer-to-peer cache port")
+ replicate_portnum = models.PositiveIntegerField(help_text="Replica Gateway port", verbose_name="Replication service port")
+
+ credentials_blob = models.TextField(null=True, blank=True, help_text="Encrypted slice credentials")
+
+ def __unicode__(self): return "%s-%s" % (self.volume_id.name, self.slice_id.name)
+
+ def clean(self):
+ """
+ Verify that our fields are in order:
+ * peer_portnum and replicate_portnum have to be valid port numbers between 1025 and 65534
+ * peer_portnum and replicate_portnum cannot be changed once set.
+ """
+
+ if self.peer_portnum < 1025 or self.peer_portnum > 65534:
+ raise ValidationError( "Client peer-to-peer cache port number must be between 1025 and 65534" )
+
+ if self.replicate_portnum < 1025 or self.replicate_portnum > 65534:
+ raise ValidationError( "Replication service port number must be between 1025 and 65534" )
+