Fixes some ipaddress api changes, adds puppet host validation endpoint with test

This commit is contained in:
nikolark committed 2015-10-03 14:28:37 +02:00
1 parent a7b3e577b3
commit 33c6d61d2d
9 files changed
+2849 -23

No files matched your search

+46
View File
@@ -0,0 +1,46 @@
from mdb.models import Host, Interface, Domain
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
class HostValidateSecretSerializer(serializers.Serializer):
certname = serializers.CharField(required=True)
pxe_key = serializers.CharField(required=True)
host = None
def _host_in_domain_exists(self, attrs):
fqdn = attrs['certname']
host_name = fqdn.split('.')[0]
domain = '.'.join(fqdn.split('.')[1:])
try:
return Host.objects.get(hostname=host_name, interface__domain__domain_name=domain)
except (Host.DoesNotExist, Host.MultipleObjectsReturned):
return None
def validate(self, attrs):
# custom validation
host = self._host_in_domain_exists(attrs)
if host is None:
raise ValidationError('Host \'{}\' not found'.format(attrs['certname']))
if not host.pxe_installable:
raise ValidationError('Host is marked as not installable via PXE.')
if not host.pxe_key:
raise ValidationError('Host has no pxe_key.')
if host.pxe_key != attrs['pxe_key']:
raise ValidationError('Supplied pxe_key does not match host.')
self.host = host
return attrs
def create(self, validated_data):
# Make requests kind of idempotent by only allowing 1 request / installation
self.host.pxe_installable = False
self.host.save()
return self.host
+16 -3
View File
@@ -1,7 +1,20 @@
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from mdb.api.serializers import HostValidateSecretSerializer
class ValidatePuppetHostSecret(APIView):
def post(self):
# TODO
pass
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request):
serializer = HostValidateSecretSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save() # sets pxe_installable=False
return Response({'valid_secret': True})
File diff suppressed because it is too large. Load diff
+24
View File
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('mdb', '0006_auto_20151001_2235'),
]
operations = [
migrations.AddField(
model_name='host',
name='pxe_installable',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='host',
name='pxe_key',
field=models.CharField(max_length=254, blank=True),
),
]
+14 -12
View File
@@ -346,19 +346,19 @@ class Ip4Subnet(models.Model):
def num_addresses(self):
subnet = ipaddress.IPv4Network(self.network + "/" + self.netmask)
return subnet.numhosts
return subnet.num_addresses
def broadcast_address(self):
subnet = ipaddress.IPv4Network(self.network + "/" + self.netmask)
return subnet.broadcast
return subnet.broadcast_address
def first_address(self):
subnet = ipaddress.IPv4Network(self.network + "/" + self.netmask)
return subnet.iterhosts().next()
return next(subnet.hosts())
def last_address(self):
subnet = ipaddress.IPv4Network(self.network + "/" + self.netmask)
for curr in subnet.iterhosts():
for curr in subnet.hosts():
pass # horribly inefficient
return curr
@@ -448,15 +448,14 @@ class Ip4Address(models.Model):
ping_avg_rtt = models.FloatField(null=True, blank=True)
def __str__(self):
# FIXME: generates 2 SQL queries
_if = self.interface_set.all()
if not _if:
# FIXME: generates 2 SQL queries?
if not hasattr(self, 'interface'):
return self.address
return "{} ({})".format(self.address, _if.get().host.hostname)
return "{} ({})".format(self.address, self.interface.host.hostname)
def assigned_to_host(self):
return self.interface_set.get().host
return self.interface.host
assigned_to_host.short_description = "Assigned to Host"
@@ -523,12 +522,15 @@ class Host(models.Model):
kerberos_principal_name = models.CharField(max_length=256, editable=False)
kerberos_principal_created_date = models.DateTimeField(null=True, blank=True, editable=False)
pxe_key = models.CharField(max_length=254, blank=True)
pxe_installable = models.BooleanField(default=False)
def __str__(self):
return self.hostname
def in_domain(self):
domains = self.interface_set.values_list('domain__domain_name', flat=True)
return u",".join(domains)
return ",".join(domains)
in_domain.short_description = "in domains"
@@ -562,7 +564,7 @@ class Interface(models.Model):
return "%s (%s on %s)" % (self.macaddr, self.name, self.host.hostname)
def ipv6_enabled(self):
return self.ip6address_set.count() > 0
return self.ip6address_set.exists()
@python_2_unicode_compatible
@@ -624,7 +626,7 @@ def create_ips_for_subnet(sender, instance, created, **kwargs):
subnet = ipaddress.IPv4Network(instance.network + "/" + instance.netmask)
for addr in subnet.iterhosts():
for addr in subnet.hosts():
address = Ip4Address(address=str(addr), subnet=instance)
address.save()
+21 -5
View File
@@ -1,12 +1,28 @@
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from mdb.models import Host
from rest_framework.authtoken.models import Token
from rest_framework.test import APITestCase
class APITestCases(APITestCase):
class MyAPITestCases(APITestCase):
fixtures = ['test_data']
def create_user_with_token(self):
self.user = User.objects.create(username='admin')
self.token = Token.objects.create(user=self.user)
self.host = Host.objects.first()
def setUp(self):
self.create_user_with_token()
return super(MyAPITestCases, self).setUp()
def test_validate_puppet_host_by_secret(self):
data = {
'secret': 'something',
'certname': 'arnold.neuf.no'
'pxe_key': self.host.pxe_key,
'certname': '{}.{}'.format(self.host.hostname, self.host.interface_set.first().domain.domain_name)
}
res = self.client.post(reverse('api-validate-host-secret'), data)
self.fail()
self.client.credentials(HTTP_AUTHORIZATION='Token {}'.format(self.token.key))
res = self.client.post(reverse('api-validate-host-secret'), data, format='json')
self.assertEquals(res.status_code, 200, res.content)
self.assertFalse(Host.objects.get(pk=self.host.pk).pxe_installable)
+3 -2
View File
@@ -1,3 +1,4 @@
from __future__ import unicode_literals
from django.core.validators import RegexValidator
import re
@@ -5,11 +6,11 @@ import re
hostname_re = re.compile(r'^(?!-)[-a-z0-9]+(?<!-)$', re.IGNORECASE)
validate_hostname = RegexValidator(
regex=hostname_re,
message=u'Enter a valid hostname',
message='Enter a valid hostname',
code='invalid')
macaddr_re = re.compile(r'^([0-9A-F]{2}[:]){5}([0-9A-F]{2})$', re.IGNORECASE)
validate_macaddr = RegexValidator(
regex=macaddr_re,
message=u'Enter a valid MAC address',
message='Enter a valid MAC address',
code='invalid')
+1
View File
@@ -75,6 +75,7 @@ INSTALLED_APPS = (
'suit',
'django.contrib.admin',
'rest_framework',
'rest_framework.authtoken',
'mdb',
)
+2 -1
View File
@@ -1,6 +1,7 @@
Django>=1.8,<1.9
django-auth-ldap
django-suit==0.2.15
django-extensions==1.5.7
djangorestframework==3.2.4
django-suit==0.2.15
ipaddress==1.0.14
mysqlclient==1.3.6