summaryrefslogtreecommitdiff
path: root/mirrors/models.py
diff options
context:
space:
mode:
Diffstat (limited to 'mirrors/models.py')
-rw-r--r--mirrors/models.py165
1 files changed, 138 insertions, 27 deletions
diff --git a/mirrors/models.py b/mirrors/models.py
index 85423303..9743d177 100644
--- a/mirrors/models.py
+++ b/mirrors/models.py
@@ -1,78 +1,189 @@
+from datetime import timedelta
+import socket
+from urlparse import urlparse
+
+from django.core.exceptions import ValidationError
from django.db import models
+from django.db.models.signals import pre_save
+from django_countries.fields import CountryField
+
+from .fields import IPNetworkField
+from main.utils import set_created_field
-TIER_CHOICES = (
- (0, 'Tier 0'),
- (1, 'Tier 1'),
- (2, 'Tier 2'),
- (-1, 'Untiered'),
-)
class Mirror(models.Model):
- name = models.CharField(max_length=255)
+ TIER_CHOICES = (
+ (0, 'Tier 0'),
+ (1, 'Tier 1'),
+ (2, 'Tier 2'),
+ (-1, 'Untiered'),
+ )
+
+ name = models.CharField(max_length=255, unique=True)
tier = models.SmallIntegerField(default=2, choices=TIER_CHOICES)
- upstream = models.ForeignKey('self', null=True)
- country = models.CharField(max_length=255, db_index=True)
+ upstream = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
admin_email = models.EmailField(max_length=255, blank=True)
+ alternate_email = models.EmailField(max_length=255, blank=True)
public = models.BooleanField(default=True)
active = models.BooleanField(default=True)
- isos = models.BooleanField(default=True)
+ isos = models.BooleanField("ISOs", default=True)
rsync_user = models.CharField(max_length=50, blank=True, default='')
rsync_password = models.CharField(max_length=50, blank=True, default='')
+ bug = models.PositiveIntegerField("Flyspray bug", null=True, blank=True)
notes = models.TextField(blank=True)
+ created = models.DateTimeField(editable=False)
+ last_modified = models.DateTimeField(editable=False)
class Meta:
- ordering = ('country', 'name')
+ ordering = ('name',)
def __unicode__(self):
return self.name
- def supported_protocols(self):
- protocols = MirrorProtocol.objects.filter(
- urls__mirror=self).order_by('protocol').distinct()
- return ", ".join([p.protocol for p in protocols])
-
def downstream(self):
return Mirror.objects.filter(upstream=self).order_by('name')
def get_absolute_url(self):
return '/mirrors/%s/' % self.name
+
class MirrorProtocol(models.Model):
protocol = models.CharField(max_length=10, unique=True)
+ is_download = models.BooleanField(default=True,
+ help_text="Is protocol useful for end-users, e.g. HTTP")
+ default = models.BooleanField(default=True,
+ help_text="Included by default when building mirror list?")
+ created = models.DateTimeField(editable=False)
+
def __unicode__(self):
return self.protocol
+
class Meta:
- verbose_name = 'Mirror Protocol'
+ ordering = ('protocol',)
+
class MirrorUrl(models.Model):
- url = models.CharField(max_length=255)
- protocol = models.ForeignKey(MirrorProtocol, related_name="urls")
+ url = models.CharField("URL", max_length=255, unique=True)
+ protocol = models.ForeignKey(MirrorProtocol, related_name="urls",
+ editable=False, on_delete=models.PROTECT)
mirror = models.ForeignKey(Mirror, related_name="urls")
+ country = CountryField(blank=True, db_index=True)
+ has_ipv4 = models.BooleanField("IPv4 capable", default=True,
+ editable=False)
+ has_ipv6 = models.BooleanField("IPv6 capable", default=False,
+ editable=False)
+ active = models.BooleanField(default=True)
+ bandwidth = models.FloatField("bandwidth (mbits)", null=True, blank=True)
+ created = models.DateTimeField(editable=False)
+
+ class Meta:
+ verbose_name = 'mirror URL'
+
def __unicode__(self):
return self.url
- class Meta:
- verbose_name = 'Mirror URL'
+
+ def address_families(self):
+ hostname = urlparse(self.url).hostname
+ info = socket.getaddrinfo(hostname, None, 0, socket.SOCK_STREAM)
+ families = [x[0] for x in info]
+ return families
+
+ @property
+ def hostname(self):
+ return urlparse(self.url).hostname
+
+ def clean(self):
+ try:
+ # Auto-map the protocol field by looking at the URL
+ protocol = urlparse(self.url).scheme
+ self.protocol = MirrorProtocol.objects.get(protocol=protocol)
+ except Exception as e:
+ raise ValidationError(e)
+ try:
+ families = self.address_families()
+ self.has_ipv4 = socket.AF_INET in families
+ self.has_ipv6 = socket.AF_INET6 in families
+ except socket.error:
+ # We don't fail in this case; we'll just set both to False
+ self.has_ipv4 = False
+ self.has_ipv6 = False
+
+ def get_absolute_url(self):
+ return '/mirrors/%s/%d/' % (self.mirror.name, self.pk)
+
class MirrorRsync(models.Model):
- ip = models.CharField(max_length=24)
+ # max length is 40 chars for full-form IPv6 addr + subnet
+ ip = IPNetworkField("IP")
mirror = models.ForeignKey(Mirror, related_name="rsync_ips")
+ created = models.DateTimeField(editable=False)
+
def __unicode__(self):
- return "%s" % (self.ip)
+ return unicode(self.ip)
+
+ class Meta:
+ verbose_name = 'mirror rsync IP'
+ ordering = ('ip',)
+
+
+class CheckLocation(models.Model):
+ hostname = models.CharField(max_length=255)
+ source_ip = models.GenericIPAddressField('source IP',
+ unpack_ipv4=True, unique=True)
+ country = CountryField()
+ created = models.DateTimeField(editable=False)
+
class Meta:
- verbose_name = 'Mirror Rsync IP'
+ ordering = ('hostname', 'source_ip')
+
+ def __unicode__(self):
+ return self.hostname
+
+ @property
+ def family(self):
+ info = socket.getaddrinfo(self.source_ip, None, 0, 0, 0,
+ socket.AI_NUMERICHOST)
+ families = [x[0] for x in info]
+ return families[0]
+
+ @property
+ def ip_version(self):
+ '''Returns integer '4' or '6'.'''
+ if self.family == socket.AF_INET6:
+ return 6
+ if self.family == socket.AF_INET:
+ return 4
+ return None
+
class MirrorLog(models.Model):
url = models.ForeignKey(MirrorUrl, related_name="logs")
+ location = models.ForeignKey(CheckLocation, related_name="logs", null=True)
check_time = models.DateTimeField(db_index=True)
last_sync = models.DateTimeField(null=True)
duration = models.FloatField(null=True)
is_success = models.BooleanField(default=True)
- error = models.CharField(max_length=255, blank=True, default='')
+ error = models.TextField(blank=True, default='')
+
+ @property
+ def delay(self):
+ if self.last_sync is None:
+ return None
+ # sanity check, this shouldn't happen
+ if self.check_time < self.last_sync:
+ return timedelta()
+ return self.check_time - self.last_sync
def __unicode__(self):
- return "Check of %s at %s" % (url.url, check_time)
+ return "Check of %s at %s" % (self.url.url, self.check_time)
class Meta:
- verbose_name = 'Mirror Check Log'
+ verbose_name = 'mirror check log'
+ get_latest_by = 'check_time'
+
+
+for model in (Mirror, MirrorProtocol, MirrorUrl, MirrorRsync, CheckLocation):
+ pre_save.connect(set_created_field, sender=model,
+ dispatch_uid="mirrors.models")
# vim: set ts=4 sw=4 et: