Merge branch 'master' of ssh://git.planet-lab.org/git/plstackapi
[plstackapi.git] / planetstack / syndicate_storage / models.py
1 from core.models import User,Site,Service,SingletonModel,PlCoreBase,Slice,SlicePrivilege
2 import os
3 from django.db import models
4 from django.db.models import Q
5 from django.forms.models import model_to_dict
6 from django.core.exceptions import ValidationError, ObjectDoesNotExist
7
8 # Create your models here.
9
10 class SyndicateService(SingletonModel,Service):
11     class Meta:
12         app_label = "syndicate_storage"
13         verbose_name = "Syndicate Service"
14         verbose_name_plural = "Syndicate Service"
15
16     def __unicode__(self):  return u'Syndicate Service'
17
18
19 class SyndicatePrincipal(PlCoreBase):
20     class Meta:
21         app_label = "syndicate_storage"
22
23     # for now, this is a user email address 
24     principal_id = models.TextField(unique=True)
25     public_key_pem = models.TextField()
26     sealed_private_key = models.TextField()
27
28     def __unicode__self(self):  return "%s" % self.principal_id
29
30
31 class Volume(PlCoreBase):
32     class Meta:
33         app_label = "syndicate_storage"
34
35     name = models.CharField(max_length=64, help_text="Human-readable, searchable name of the Volume")
36     
37     owner_id = models.ForeignKey(User, verbose_name='Owner')
38
39     description = models.TextField(null=True, blank=True,max_length=130, help_text="Human-readable description of what this Volume is used for.")
40     blocksize = models.PositiveIntegerField(help_text="Number of bytes per block.")
41     private = models.BooleanField(default=True, help_text="Indicates if the Volume is visible to users other than the Volume Owner and Syndicate Administrators.")
42     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.")
43
44     cap_read_data = models.BooleanField(default=True, help_text="VM can read Volume data")
45     cap_write_data = models.BooleanField(default=True, help_text="VM can write Volume data")
46     cap_host_data = models.BooleanField(default=True, help_text="VM can host Volume data")
47     
48     slice_id = models.ManyToManyField(Slice, through="VolumeSlice")
49     
50     def __unicode__(self):  return self.name
51  
52     
53     @staticmethod
54     def select_by_user(user):
55         """
56         Only return Volumes accessible by the user.
57         Admin users can see everything.
58         """
59         if user.is_admin:
60             qs = Volume.objects.all()
61         else:
62             qs = Volume.objects.filter( Q(owner_id=user) | Q(private=False) )
63             
64         return qs
65
66
67 class VolumeAccessRight(PlCoreBase):
68     class Meta:
69         app_label = "syndicate_storage"
70
71     owner_id = models.ForeignKey(User, verbose_name='user')
72     
73     volume = models.ForeignKey(Volume)
74
75     cap_read_data = models.BooleanField(default=True, help_text="VM can read Volume data")
76     cap_write_data = models.BooleanField(default=True, help_text="VM can write Volume data")
77     cap_host_data = models.BooleanField(default=True, help_text="VM can host Volume data")
78
79
80     def __unicode__(self):  return "%s-%s" % (self.owner_id.email, self.volume.name)
81
82
83 class ObserverSecretValue( models.TextField ):
84     class Meta:
85         app_label = "syndicate_storage"
86     
87     __metaclass__ = models.SubfieldBase
88     
89     MAGIC_PREFIX = "$SECRET$:"
90     
91     @classmethod 
92     def is_encrypted( cls, secret_str ):
93        # all encrypted secrets start with MAGIC_PREFIX, which is NOT base64-encoded
94        return secret_str.startswith( cls.MAGIC_PREFIX )
95     
96     @classmethod 
97     def unserialize( cls, serialized_ciphertext ):
98        # strip prefix and return ciphertext 
99        return serialized_ciphertext[len(cls.MAGIC_PREFIX):]
100     
101     @classmethod 
102     def serialize( cls, ciphertext ):
103        # prepend a magic prefix so we know it's encrypted 
104        return cls.MAGIC_PREFIX + ciphertext
105     
106     def to_python( self, secret_str ):
107        """
108        Decrypt the value with the Observer key
109        """
110        
111        # is this in the clear?
112        if not ObserverSecretValue.is_encrypted( secret_str ):
113           # nothing to do
114           return secret_str
115        
116        # otherwise, decrypt it
117        from syndicate_observer import syndicatelib
118        
119        # get observer private key
120        config = syndicatelib.get_config()
121        
122        try:
123           observer_pkey_path = config.SYNDICATE_PRIVATE_KEY
124           observer_pkey_pem = syndicatelib.get_private_key_pem( observer_pkey_path )
125        except:
126           raise syndicatelib.SyndicateObserverError( "Internal Syndicate Observer error: failed to load Observer private key" )
127        
128        # deserialize 
129        secret_str = ObserverSecretValue.unserialize( secret_str )
130        
131        # decrypt
132        if secret_str is not None and len(secret_str) > 0:
133           
134           slice_secret = syndicatelib.decrypt_slice_secret( observer_pkey_pem, secret_str )
135           
136           if slice_secret is not None:
137              return slice_secret 
138           
139           else:
140              raise syndicatelib.SyndicateObserverError( "Internal Syndicate Observer error: failed to decrypt slice secret value" )
141        else:
142           return None
143        
144        
145     def pre_save( self, model_inst, add ):
146        """
147        Encrypt the value with the Observer key
148        """
149        
150        from syndicate_observer import syndicatelib 
151        
152        # get observer private key
153        config = syndicatelib.get_config()
154        
155        try:
156           observer_pkey_path = config.SYNDICATE_PRIVATE_KEY
157           observer_pkey_pem = syndicatelib.get_private_key_pem( observer_pkey_path )
158        except:
159           raise syndicatelib.SyndicateObserverError( "Internal Syndicate Observer error: failed to load Observer private key" )
160        
161        slice_secret = getattr(model_inst, self.attname )
162        
163        if slice_secret is not None:
164           
165           # encrypt it 
166           sealed_slice_secret = syndicatelib.encrypt_slice_secret( observer_pkey_pem, slice_secret )
167           
168           return ObserverSecretValue.serialize( sealed_slice_secret )
169        
170        else:
171           raise syndicatelib.SyndicateObserverError( "Internal Syndicate Observer error: No slice secret generated" )
172                                                     
173
174 class SliceSecret(models.Model):        # NOTE: not a PlCoreBase
175     class Meta:
176        app_label = "syndicate_storage"
177     
178     slice_id = models.ForeignKey(Slice)
179     secret = ObserverSecretValue(blank=True, help_text="Shared secret between OpenCloud and this slice's Syndicate daemons.")
180     
181     def __unicode__(self):  return self.slice_id.name
182  
183     @staticmethod
184     def select_by_user(user):
185         """
186         Only return slice secrets for slices where this user has 'admin' role.
187         Admin users can see everything.
188         """
189         if user.is_admin:
190             qs = SliceSecret.objects.all()
191         else:
192             visible_slice_ids = [sp.slice.id for sp in SlicePrivilege.objects.filter(user=user,role__role='admin')]
193             qs = SliceSecret.objects.filter(slice_id__id__in=visible_slice_ids)
194             
195         return qs
196  
197
198 class VolumeSlice(PlCoreBase):
199     class Meta:
200         app_label = "syndicate_storage"
201
202     volume_id = models.ForeignKey(Volume, verbose_name="Volume")
203     slice_id = models.ForeignKey(Slice, verbose_name="Slice")
204     
205     cap_read_data = models.BooleanField(default=True, help_text="VM can read Volume data")
206     cap_write_data = models.BooleanField(default=True, help_text="VM can write Volume data")
207     cap_host_data = models.BooleanField(default=True, help_text="VM can host Volume data")
208     
209     UG_portnum = models.PositiveIntegerField(help_text="User Gateway port.  Any port above 1024 will work, but it must be available slice-wide.", verbose_name="UG port")
210     RG_portnum = models.PositiveIntegerField(help_text="Replica Gateway port.  Any port above 1024 will work, but it must be available slice-wide.", verbose_name="RG port")
211     
212     credentials_blob = models.TextField(null=True, blank=True, help_text="Encrypted slice credentials, sealed with the slice secret.")
213  
214     def __unicode__(self):  return "%s-%s" % (self.volume_id.name, self.slice_id.name)
215
216     def clean(self):
217         """
218         Verify that our fields are in order:
219             * UG_portnum and RG_portnum have to be valid port numbers between 1025 and 65534
220             * UG_portnum and RG_portnum cannot be changed once set.
221             * UG_portnum and RG_portnum are unique
222         """
223
224         if self.UG_portnum == self.RG_portnum:
225             raise ValidationError( "UG and RG ports must be unique" )
226          
227         if self.UG_portnum < 1025 or self.UG_portnum > 65534:
228             raise ValidationError( "UG port number must be between 1025 and 65534" )
229
230         if self.RG_portnum < 1025 or self.RG_portnum > 65534:
231             raise ValidationError( "RG port number must be between 1025 and 65534" )
232          
233          
234     def save(self, *args, **kw):
235        """
236        Make sure a SliceSecret exists for this slice
237        """
238        
239        from syndicate_observer import syndicatelib
240        
241        # get observer private key
242        config = syndicatelib.get_config()
243        
244        try:
245           observer_pkey_path = config.SYNDICATE_PRIVATE_KEY
246           observer_pkey_pem = syndicatelib.get_private_key_pem( observer_pkey_path )
247        except:
248           raise syndicatelib.SyndicateObserverError( "Internal Syndicate Observer error: failed to load Observer private key" )
249        
250        # get or create the slice secret 
251        slice_secret = syndicatelib.get_or_create_slice_secret( observer_pkey_pem, None, slice_fk=self.slice_id )
252        
253        if slice_secret is None:
254           raise SyndicateObserverError( "Failed to get or create slice secret for %s" % self.slice_id.name )
255        
256        super(VolumeSlice, self).save(*args, **kw)
257        
258
259