Commit 33c6d61d authored by Nikolai Kristiansen's avatar Nikolai Kristiansen
Browse files

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

parent a7b3e577
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:])
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.') = host
return attrs
def create(self, validated_data):
# Make requests kind of idempotent by only allowing 1 request / installation = False
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):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request):
serializer = HostValidateSecretSerializer(
serializer.is_valid(raise_exception=True) # sets pxe_installable=False
return Response({'valid_secret': True})
This diff is collapsed.
# -*- 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 = [
field=models.CharField(max_length=254, blank=True),
......@@ -346,19 +346,19 @@ class Ip4Subnet(models.Model):
def num_addresses(self):
subnet = ipaddress.IPv4Network( + "/" + self.netmask)
return subnet.numhosts
return subnet.num_addresses
def broadcast_address(self):
subnet = ipaddress.IPv4Network( + "/" + self.netmask)
return subnet.broadcast
return subnet.broadcast_address
def first_address(self):
subnet = ipaddress.IPv4Network( + "/" + self.netmask)
return subnet.iterhosts().next()
return next(subnet.hosts())
def last_address(self):
subnet = ipaddress.IPv4Network( + "/" + 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,
def assigned_to_host(self):
return self.interface_set.get().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,,
def ipv6_enabled(self):
return self.ip6address_set.count() > 0
return self.ip6address_set.exists()
......@@ -624,7 +626,7 @@ def create_ips_for_subnet(sender, instance, created, **kwargs):
subnet = ipaddress.IPv4Network( + "/" + instance.netmask)
for addr in subnet.iterhosts():
for addr in subnet.hosts():
address = Ip4Address(address=str(addr), subnet=instance)
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) = Host.objects.first()
def setUp(self):
return super(MyAPITestCases, self).setUp()
def test_validate_puppet_host_by_secret(self):
data = {
'secret': 'something',
'certname': ''
'certname': '{}.{}'.format(,
res ='api-validate-host-secret'), data)
self.client.credentials(HTTP_AUTHORIZATION='Token {}'.format(self.token.key))
res ='api-validate-host-secret'), data, format='json')
self.assertEquals(res.status_code, 200, res.content)
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(
message=u'Enter a valid hostname',
message='Enter a valid hostname',
macaddr_re = re.compile(r'^([0-9A-F]{2}[:]){5}([0-9A-F]{2})$', re.IGNORECASE)
validate_macaddr = RegexValidator(
message=u'Enter a valid MAC address',
message='Enter a valid MAC address',
......@@ -75,6 +75,7 @@ INSTALLED_APPS = (
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment