From 9ff22d634fdd506cde851b80305ba4da9bfa8a48 Mon Sep 17 00:00:00 2001 From: Nikolai Kristiansen Date: Thu, 1 Oct 2015 22:54:50 +0200 Subject: [PATCH 01/11] Bump everything (python3 ftw), starts verify api --- .gitignore | 4 +- README | 17 -- README.md | 15 ++ manage.py | 7 +- __init__.py => mdb/api/__init__.py | 0 wsgi/__init__.py => mdb/api/serializers.py | 0 mdb/api/views.py | 6 + mdb/management/commands/dhcp-sync.py | 14 +- mdb/management/commands/zone-sync.py | 14 +- mdb/migrations/0006_auto_20151001_2235.py | 85 +++++++++ mdb/models.py | 100 ++++++----- mdb/settings-sample.py | 191 --------------------- mdb/tests.py | 24 ++- mdbsite/settings.py | 128 ++++++++++++++ urls.py => mdbsite/urls.py | 9 +- {wsgi => mdbsite}/wsgi.py | 0 requirements.txt | 9 +- 17 files changed, 336 insertions(+), 287 deletions(-) delete mode 100644 README create mode 100644 README.md rename __init__.py => mdb/api/__init__.py (100%) rename wsgi/__init__.py => mdb/api/serializers.py (100%) create mode 100644 mdb/api/views.py create mode 100644 mdb/migrations/0006_auto_20151001_2235.py delete mode 100644 mdb/settings-sample.py create mode 100644 mdbsite/settings.py rename urls.py => mdbsite/urls.py (59%) rename {wsgi => mdbsite}/wsgi.py (100%) diff --git a/.gitignore b/.gitignore index c91623e..8f3eedc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ *.swp temp *.log -settings.py -dns_mdb.db +local_settings.py +db.sqlite3 /.idea /venv diff --git a/README b/README deleted file mode 100644 index 510496c..0000000 --- a/README +++ /dev/null @@ -1,17 +0,0 @@ -A Django app for managing computer equipment. -Generates DHCP configs and zone files. - -Dependencies: - sudo apt-get install libmysqlclient-dev python-dev libldap2-dev libsasl2-dev bind9utils - -Setup environment: - mdb$ virtualenv venv - mdb$ . venv/bin/activate - (venv)mdb$ pip install -r requirements.txt - -Create a config file: - mdb$ cp mdb/settings-sample.py mdb/settings.py - # Edit settings.py with your favorite editor - -Run the server: - (venv)mdb$ python manage.py runserver diff --git a/README.md b/README.md new file mode 100644 index 0000000..d4f3fa3 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +A Django app for managing computer equipment. + +## Installation + sudo apt-get install libmysqlclient-dev python-dev libldap2-dev libsasl2-dev bind9utils + virtualenv venv + . venv/bin/activate + pip install -r requirements.txt + python manage.py migrate + python manage.py runserver + +## Features + +* Creates DHCPD configuration files +* Creates DNS zone files for BIND +* TODO: Creates PXE config files (with random secret for use with Puppet autosign) \ No newline at end of file diff --git a/manage.py b/manage.py index e59d7d2..f6ab478 100755 --- a/manage.py +++ b/manage.py @@ -1,9 +1,8 @@ #!/usr/bin/env python -import os, sys +import os +import sys if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mdb.settings") - + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mdbsite.settings") from django.core.management import execute_from_command_line - execute_from_command_line(sys.argv) diff --git a/__init__.py b/mdb/api/__init__.py similarity index 100% rename from __init__.py rename to mdb/api/__init__.py diff --git a/wsgi/__init__.py b/mdb/api/serializers.py similarity index 100% rename from wsgi/__init__.py rename to mdb/api/serializers.py diff --git a/mdb/api/views.py b/mdb/api/views.py new file mode 100644 index 0000000..6053112 --- /dev/null +++ b/mdb/api/views.py @@ -0,0 +1,6 @@ +from rest_framework.views import APIView + + +class ValidatePuppetHostSecret(APIView): + # TODO + pass diff --git a/mdb/management/commands/dhcp-sync.py b/mdb/management/commands/dhcp-sync.py index d12124e..0f62474 100644 --- a/mdb/management/commands/dhcp-sync.py +++ b/mdb/management/commands/dhcp-sync.py @@ -6,7 +6,11 @@ from difflib import context_diff from django.core.management.base import BaseCommand from django.core.mail import mail_admins from optparse import make_option -from commands import getstatusoutput +try: + from subprocess import getstatusoutput +except ImportError: + from commands import getstatusoutput + from mdb.models import DhcpConfig @@ -42,13 +46,13 @@ class Command(BaseCommand): # do we need to do anything? if config.serial == config.active_serial and not force: - print "Nothing to do, serial is the same: %s" % config.serial + print("Nothing to do, serial is the same: %s" % config.serial) sys.exit(0) # write to stdout if output == sys.stdout: - print config.dhcpd_configuration() - print 'Not writing to file, serial not updated' + print(config.dhcpd_configuration()) + print('Not writing to file, serial not updated') return # read current file contents @@ -71,7 +75,7 @@ class Command(BaseCommand): tofile=config.serial, lineterm='')) - print diff + print(diff) # update serial old_serial = config.active_serial diff --git a/mdb/management/commands/zone-sync.py b/mdb/management/commands/zone-sync.py index 6b1bc4e..ea57037 100644 --- a/mdb/management/commands/zone-sync.py +++ b/mdb/management/commands/zone-sync.py @@ -7,8 +7,12 @@ from difflib import context_diff from django.core.management.base import BaseCommand from django.core.mail import mail_admins from optparse import make_option -from commands import getstatusoutput -from mdb.models import * +try: + from subprocess import getstatusoutput +except ImportError: + from commands import getstatusoutput + +from mdb.models import Domain, Ip4Subnet, Ip6Subnet class Command(BaseCommand): @@ -43,7 +47,7 @@ class Command(BaseCommand): # do the binaries exist? for f in (self.checkzone_bin, self.bind_bin): if not os.path.isfile(f): - print "ERROR: no such file %s, exiting..." % f + print("ERROR: no such file %s, exiting..." % f) if not debug: sys.exit(1) @@ -106,11 +110,11 @@ class Command(BaseCommand): 'diff': None, } - print "updating %s %s [%d -> %d]" % ( + print("updating %s %s [%d -> %d]" % ( zonetype, zone.domain_name, zone.domain_active_serial, - zone.domain_serial) + zone.domain_serial)) # generate zone file contents new = zone.zone_file_contents() diff --git a/mdb/migrations/0006_auto_20151001_2235.py b/mdb/migrations/0006_auto_20151001_2235.py new file mode 100644 index 0000000..bb38709 --- /dev/null +++ b/mdb/migrations/0006_auto_20151001_2235.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import django.core.validators +import re + + +class Migration(migrations.Migration): + + dependencies = [ + ('mdb', '0005_auto_20150822_2204'), + ] + + operations = [ + migrations.AlterModelOptions( + name='dhcpcustomfield', + options={'verbose_name': 'DHCP custom field'}, + ), + migrations.AlterField( + model_name='domain', + name='domain_admin', + field=models.EmailField(max_length=254), + ), + migrations.AlterField( + model_name='domain', + name='domain_ipaddr', + field=models.GenericIPAddressField(protocol='IPv4'), + ), + migrations.AlterField( + model_name='host', + name='hostname', + field=models.CharField(validators=[django.core.validators.RegexValidator(code='invalid', message='Enter a valid hostname', regex=re.compile('^(?!-)[-a-z0-9]+(? 0 +@python_2_unicode_compatible class Ip6Address(models.Model): subnet = models.ForeignKey(Ip6Subnet) address = models.CharField(max_length=64) @@ -556,7 +574,7 @@ class Ip6Address(models.Model): def full_address(self): return self.subnet.network + self.address - def __unicode__(self): + def __str__(self): return "%s (%s on %s)" % ( self.full_address(), self.interface.name, @@ -604,7 +622,7 @@ def create_ips_for_subnet(sender, instance, created, **kwargs): if not created: return - subnet = ipaddr.IPv4Network(instance.network + "/" + instance.netmask) + subnet = ipaddress.IPv4Network(instance.network + "/" + instance.netmask) for addr in subnet.iterhosts(): address = Ip4Address(address=str(addr), subnet=instance) @@ -642,7 +660,7 @@ def set_domain_name_for_ipv6_subnet(sender, instance, **kwargs): if len(instance.domain_name) > 0: return - network = ipaddr.IPv6Address("%s::" % instance.network) + network = ipaddress.IPv6Address("%s::" % instance.network) instance.domain_name = ".".join(network.exploded.replace(":", "")[:16])[::-1] + ".ip6.arpa" diff --git a/mdb/settings-sample.py b/mdb/settings-sample.py deleted file mode 100644 index 32b2658..0000000 --- a/mdb/settings-sample.py +++ /dev/null @@ -1,191 +0,0 @@ -# Django settings for dns_mdb project. - -DEBUG = True -TEMPLATE_DEBUG = DEBUG - -ADMINS = ( - ('System Administrator', 'sysadmin@example.com'), -) - -MANAGERS = ADMINS - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. - 'NAME': 'mdb.db', # Or path to database file if using sqlite3. - 'USER': 'mdb', # Not used with sqlite3. - 'PASSWORD': 'mdb', # Not used with sqlite3. - 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. - 'PORT': '3306', # Set to empty string for default. Not used with sqlite3. - } -} - -# Local time zone for this installation. Choices can be found here: -# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -# although not all choices may be available on all operating systems. -# On Unix systems, a value of None will cause Django to use the same -# timezone as the operating system. -# If running in a Windows environment this must be set to the same as your -# system time zone. -TIME_ZONE = 'Europe/Oslo' - -# Language code for this installation. All choices can be found here: -# http://www.i18nguy.com/unicode/language-identifiers.html -LANGUAGE_CODE = 'en-us' - -SITE_ID = 1 - -# If you set this to False, Django will make some optimizations so as not -# to load the internationalization machinery. -USE_I18N = True - -# If you set this to False, Django will not format dates, numbers and -# calendars according to the current locale -USE_L10N = True - -# Absolute filesystem path to the directory that will hold user-uploaded files. -# Example: "/home/media/media.lawrence.com/media/" -MEDIA_ROOT = '' - -# URL that handles the media served from MEDIA_ROOT. Make sure to use a -# trailing slash. -# Examples: "http://media.lawrence.com/media/", "http://example.com/media/" -MEDIA_URL = '' - -# Absolute path to the directory static files should be collected to. -# Don't put anything in this directory yourself; store your static files -# in apps' "static/" subdirectories and in STATICFILES_DIRS. -# Example: "/home/media/media.lawrence.com/static/" -STATIC_ROOT = '' - -# URL prefix for static files. -# Example: "http://media.lawrence.com/static/" -STATIC_URL = '/static/' - -# URL prefix for admin static files -- CSS, JavaScript and images. -# Make sure to use a trailing slash. -# Examples: "http://foo.com/static/admin/", "/static/admin/". -ADMIN_MEDIA_PREFIX = '/media/' - -# Additional locations of static files -STATICFILES_DIRS = ( - # Put strings here, like "/home/html/static" or "C:/www/django/static". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. -) - -# List of finder classes that know how to find static files in -# various locations. -STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', -# 'django.contrib.staticfiles.finders.DefaultStorageFinder', -) - -# Make this unique, and don't share it with anybody. -SECRET_KEY = '@4ps=ebofcpv8sb#y7gm896=l&l*u0u-pgo7bcdj%+(k+2lqnl' - -# List of callables that know how to import templates from various sources. -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', -# 'django.template.loaders.eggs.Loader', -) - -MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', -) - -ROOT_URLCONF = 'urls' - -TEMPLATE_DIRS = ( - # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. -) - -INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'mdb', - 'suit', - 'django.contrib.admin', -) - -# Defines a test runner -TEST_RUNNER = 'django.test.runner.DiscoverRunner' - -# A sample logging configuration. The only tangible logging -# performed by this configuration is to send an email to -# the site admins on every HTTP 500 error. -# See http://docs.djangoproject.com/en/dev/topics/logging for -# more details on how to customize your logging configuration. -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'handlers': { - 'mail_admins': { - 'level': 'ERROR', - 'class': 'django.utils.log.AdminEmailHandler' - } - }, - 'loggers': { - 'django.request': { - 'handlers': ['mail_admins'], - 'level': 'ERROR', - 'propagate': True, - }, - } -} - -EMAIL_SUBJECT_PREFIX = '[MDB] ' -EMAIL_HOST = 'smtp.example.com' -EMAIL_PORT = 25 - - -# for django-suit -from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS as TCP - -TEMPLATE_CONTEXT_PROCESSORS = TCP + ( - 'django.core.context_processors.request', -) - -# django-suit configuration -SUIT_CONFIG = { - # header - 'ADMIN_NAME': 'MDB', - 'HEADER_DATE_FORMAT': 'l, Y-m-d', - 'HEADER_TIME_FORMAT': 'H:i', - - # forms - # 'SHOW_REQUIRED_ASTERISK': True, # Default True - # 'CONFIRM_UNSAVED_CHANGES': True, # Default True - - # menu - 'SEARCH_URL': '/admin/mdb/host/', - # 'MENU_ICONS': { - # 'sites': 'icon-leaf', - # 'auth': 'icon-lock', - # }, - # 'MENU_OPEN_FIRST_CHILD': True, # Default True - # 'MENU_EXCLUDE': ('auth.group',), - 'MENU': ( - {'app': 'mdb', 'label': 'MDB', 'url': 'mdb.host'}, - { - 'label': 'Settings', - 'icon': 'icon-cog', - 'models': ('auth.user', 'auth.group') - }, - ), - - # misc - 'LIST_PER_PAGE': 30 -} diff --git a/mdb/tests.py b/mdb/tests.py index 501deb7..f699e78 100644 --- a/mdb/tests.py +++ b/mdb/tests.py @@ -1,16 +1,12 @@ -""" -This file demonstrates writing tests using the unittest module. These will pass -when you run "manage.py test". - -Replace this with more appropriate tests for your application. -""" - -from django.test import TestCase +from django.core.urlresolvers import reverse +from rest_framework.test import APITestCase -class SimpleTest(TestCase): - def test_basic_addition(self): - """ - Tests that 1 + 1 always equals 2. - """ - self.assertEqual(1 + 1, 2) +class APITestCases(APITestCase): + def test_validate_puppet_host_by_secret(self): + data = { + 'secret': 'something', + 'certname': 'arnold.neuf.no' + } + res = self.client.post(reverse('api-validate-host-secret'), data) + self.fail() diff --git a/mdbsite/settings.py b/mdbsite/settings.py new file mode 100644 index 0000000..6da84b1 --- /dev/null +++ b/mdbsite/settings.py @@ -0,0 +1,128 @@ +import os +from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS as TCP + +BASE_DIR = os.path.dirname(os.path.dirname(__file__)) + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +ADMINS = ( + ('System Administrator', 'sysadmin@example.com'), +) + +MANAGERS = ADMINS + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': 'db.sqlite3', + } +} +TIME_ZONE = 'Europe/Oslo' +LANGUAGE_CODE = 'en-us' + +SITE_ID = 1 + +USE_I18N = True +USE_L10N = True + +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') +MEDIA_URL = '/media/' +ADMIN_MEDIA_PREFIX = '/media/' + +STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') +STATIC_URL = '/static/' + + +# Additional locations of static files +STATICFILES_DIRS = ( +) + +# List of finder classes that know how to find static files in +# various locations. +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', +# 'django.contrib.staticfiles.finders.DefaultStorageFinder', +) + +# Make this unique, and don't share it with anybody. +SECRET_KEY = '@4ps=ebofcpv8sb#y7gm896=l&l*u0u-pgo7bcdj%+(k+2lqnl' + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', +) + +ROOT_URLCONF = 'mdbsite.urls' + +TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'mdb', + 'suit', + 'django.contrib.admin', +) + +# Defines a test runner +TEST_RUNNER = 'django.test.runner.DiscoverRunner' + +EMAIL_SUBJECT_PREFIX = '[MDB] ' + +TEMPLATE_CONTEXT_PROCESSORS = TCP + ( + 'django.core.context_processors.request', # for django-suit +) + +# django-suit configuration +SUIT_CONFIG = { + # header + 'ADMIN_NAME': 'MDB', + 'HEADER_DATE_FORMAT': 'l, Y-m-d', + 'HEADER_TIME_FORMAT': 'H:i', + + # forms + # 'SHOW_REQUIRED_ASTERISK': True, # Default True + # 'CONFIRM_UNSAVED_CHANGES': True, # Default True + + # menu + 'SEARCH_URL': '/admin/mdb/host/', + # 'MENU_ICONS': { + # 'sites': 'icon-leaf', + # 'auth': 'icon-lock', + # }, + # 'MENU_OPEN_FIRST_CHILD': True, # Default True + # 'MENU_EXCLUDE': ('auth.group',), + 'MENU': ( + {'app': 'mdb', 'label': 'MDB', 'url': 'mdb.host'}, + { + 'label': 'Settings', + 'icon': 'icon-cog', + 'models': ('auth.user', 'auth.group') + }, + ), + + # misc + 'LIST_PER_PAGE': 30 +} +REST_FRAMEWORK = { + 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAuthenticated',), + 'PAGINATE_BY': 10 +} + +try: + from local_settings import * +except ImportError: + pass diff --git a/urls.py b/mdbsite/urls.py similarity index 59% rename from urls.py rename to mdbsite/urls.py index 4d30b2a..8fab6a9 100644 --- a/urls.py +++ b/mdbsite/urls.py @@ -1,14 +1,15 @@ -from django.conf.urls import patterns, include, url +from django.conf.urls import include, url from django.contrib import admin from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.views.generic.base import RedirectView +from mdb.api.views import ValidatePuppetHostSecret admin.autodiscover() -urlpatterns = patterns( - '', +urlpatterns = [ url(r'^$', RedirectView.as_view(url='admin/')), url(r'^admin/', include(admin.site.urls)), -) + url(r'^api/validate-host-secret', ValidatePuppetHostSecret.as_view(), name='api-validate-host-secret') +] urlpatterns += staticfiles_urlpatterns() diff --git a/wsgi/wsgi.py b/mdbsite/wsgi.py similarity index 100% rename from wsgi/wsgi.py rename to mdbsite/wsgi.py diff --git a/requirements.txt b/requirements.txt index 9bf7dda..af04a5d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ -Django>=1.7,<1.8 -MySQL-python==1.2.5 -ipaddr==2.1.11 +Django>=1.8,<1.9 django-auth-ldap -django-suit +django-suit==0.2.15 +djangorestframework==3.2.4 +ipaddress==1.0.14 +mysqlclient==1.3.6 From a7b3e577b32dd035fc0cc31db1caf03552790318 Mon Sep 17 00:00:00 2001 From: Nikolai Kristiansen Date: Thu, 1 Oct 2015 22:59:38 +0200 Subject: [PATCH 02/11] Minor --- mdb/api/views.py | 5 +++-- mdbsite/settings.py | 7 +++---- mdbsite/urls.py | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/mdb/api/views.py b/mdb/api/views.py index 6053112..c8c2bee 100644 --- a/mdb/api/views.py +++ b/mdb/api/views.py @@ -2,5 +2,6 @@ from rest_framework.views import APIView class ValidatePuppetHostSecret(APIView): - # TODO - pass + def post(self): + # TODO + pass diff --git a/mdbsite/settings.py b/mdbsite/settings.py index 6da84b1..ec02482 100644 --- a/mdbsite/settings.py +++ b/mdbsite/settings.py @@ -72,13 +72,12 @@ INSTALLED_APPS = ( 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', - 'mdb', 'suit', 'django.contrib.admin', -) + 'rest_framework', -# Defines a test runner -TEST_RUNNER = 'django.test.runner.DiscoverRunner' + 'mdb', +) EMAIL_SUBJECT_PREFIX = '[MDB] ' diff --git a/mdbsite/urls.py b/mdbsite/urls.py index 8fab6a9..ce82912 100644 --- a/mdbsite/urls.py +++ b/mdbsite/urls.py @@ -7,9 +7,9 @@ from mdb.api.views import ValidatePuppetHostSecret admin.autodiscover() urlpatterns = [ - url(r'^$', RedirectView.as_view(url='admin/')), + url(r'^$', RedirectView.as_view(url='admin/', permanent=True)), url(r'^admin/', include(admin.site.urls)), - url(r'^api/validate-host-secret', ValidatePuppetHostSecret.as_view(), name='api-validate-host-secret') + url(r'^api/validate-host-secret/', ValidatePuppetHostSecret.as_view(), name='api-validate-host-secret') ] urlpatterns += staticfiles_urlpatterns() From 33c6d61d2d58548e01a4df6114d41cac4c4759b4 Mon Sep 17 00:00:00 2001 From: Nikolai Kristiansen Date: Sat, 3 Oct 2015 14:28:37 +0200 Subject: [PATCH 03/11] Fixes some ipaddress api changes, adds puppet host validation endpoint with test --- mdb/api/serializers.py | 46 + mdb/api/views.py | 19 +- mdb/fixtures/test_data.json | 2722 +++++++++++++++++++++ mdb/migrations/0007_auto_20151003_1313.py | 24 + mdb/models.py | 26 +- mdb/tests.py | 26 +- mdb/validators.py | 5 +- mdbsite/settings.py | 1 + requirements.txt | 3 +- 9 files changed, 2849 insertions(+), 23 deletions(-) create mode 100644 mdb/fixtures/test_data.json create mode 100644 mdb/migrations/0007_auto_20151003_1313.py diff --git a/mdb/api/serializers.py b/mdb/api/serializers.py index e69de29..17a1fec 100644 --- a/mdb/api/serializers.py +++ b/mdb/api/serializers.py @@ -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 diff --git a/mdb/api/views.py b/mdb/api/views.py index c8c2bee..8b2b1f8 100644 --- a/mdb/api/views.py +++ b/mdb/api/views.py @@ -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}) diff --git a/mdb/fixtures/test_data.json b/mdb/fixtures/test_data.json new file mode 100644 index 0000000..0ef9f9a --- /dev/null +++ b/mdb/fixtures/test_data.json @@ -0,0 +1,2722 @@ +[ +{ + "pk": 1, + "fields": { + "hostname": "sega.neuf.no" + }, + "model": "mdb.nameserver" +}, +{ + "pk": 2, + "fields": { + "hostname": "atari.neuf.no" + }, + "model": "mdb.nameserver" +}, +{ + "pk": 1, + "fields": { + "priority": 10, + "hostname": "mx.neuf.no" + }, + "model": "mdb.mailexchange" +}, +{ + "pk": 1, + "fields": { + "domain_admin": "sysadmin@neuf.no", + "created_date": "2015-10-03T13:02:53.581", + "domain_ipaddr": "127.0.0.1", + "domain_retry": 7200, + "domain_active_serial": 0, + "domain_nameservers": [ + 1, + 2 + ], + "domain_mailexchanges": [ + 1 + ], + "domain_serial": 2015100301, + "domain_refresh": 28800, + "domain_name": "edb.technology", + "domain_filename": "/tmp/mdb/bind/edb.technology", + "domain_minimum_ttl": 86400, + "domain_expire": 604800, + "domain_soa": "sega.neuf.no", + "domain_ttl": 3600 + }, + "model": "mdb.domain" +}, +{ + "pk": 1, + "fields": { + "log_facility": "local7", + "name": "default", + "max_lease_time": 7200, + "default_lease_time": 600, + "authoritative": false, + "ddns_update_style": "none", + "active_serial": 1, + "serial": 2015100302 + }, + "model": "mdb.dhcpconfig" +}, +{ + "pk": 1, + "fields": { + "created_date": "2015-10-03T13:08:17.765", + "network": "10.0.3.0", + "name": "clients", + "domain_retry": 7200, + "domain_active_serial": 0, + "domain_nameservers": [ + 1, + 2 + ], + "dhcp_dynamic_end": "10.0.3.254", + "domain_serial": 2015100301, + "dhcp_config": 1, + "domain_refresh": 28800, + "domain_name": "3.0.10.in-addr.arpa", + "netmask": "255.255.255.0", + "dhcp_dynamic_start": "10.0.3.2", + "domain_admin": "sysadmin@neuf.no", + "domain_filename": "/tmp/mdb/bind/3.0.10.in-addr.arpa", + "domain_minimum_ttl": 86400, + "domain_expire": 604800, + "dhcp_dynamic": true, + "domain_soa": "sega.neuf.no", + "domain_ttl": 3600 + }, + "model": "mdb.ip4subnet" +}, +{ + "pk": 1, + "fields": { + "ip4subnet": 1, + "key": "domain-name", + "value": "\"edb.technology\"" + }, + "model": "mdb.dhcpoption" +}, +{ + "pk": 2, + "fields": { + "ip4subnet": 1, + "key": "domain-name-servers", + "value": "158.36.190.194, 158.36.190.199, 8.8.8.8" + }, + "model": "mdb.dhcpoption" +}, +{ + "pk": 3, + "fields": { + "ip4subnet": 1, + "key": "routers", + "value": "158.36.186.1" + }, + "model": "mdb.dhcpoption" +}, +{ + "pk": 1, + "fields": { + "ip4subnet": 1, + "value": "deny unknown-clients" + }, + "model": "mdb.dhcpcustomfield" +}, +{ + "pk": 1, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.1" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 2, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.2" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 3, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.3" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 4, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.4" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 5, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.5" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 6, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.6" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 7, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.7" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 8, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.8" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 9, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.9" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 10, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.10" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 11, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.11" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 12, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.12" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 13, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.13" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 14, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.14" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 15, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.15" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 16, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.16" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 17, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.17" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 18, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.18" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 19, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.19" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 20, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.20" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 21, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.21" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 22, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.22" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 23, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.23" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 24, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.24" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 25, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.25" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 26, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.26" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 27, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.27" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 28, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.28" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 29, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.29" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 30, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.30" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 31, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.31" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 32, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.32" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 33, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.33" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 34, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.34" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 35, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.35" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 36, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.36" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 37, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.37" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 38, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.38" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 39, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.39" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 40, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.40" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 41, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.41" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 42, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.42" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 43, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.43" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 44, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.44" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 45, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.45" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 46, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.46" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 47, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.47" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 48, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.48" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 49, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.49" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 50, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.50" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 51, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.51" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 52, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.52" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 53, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.53" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 54, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.54" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 55, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.55" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 56, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.56" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 57, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.57" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 58, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.58" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 59, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.59" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 60, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.60" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 61, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.61" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 62, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.62" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 63, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.63" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 64, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.64" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 65, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.65" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 66, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.66" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 67, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.67" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 68, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.68" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 69, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.69" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 70, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.70" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 71, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.71" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 72, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.72" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 73, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.73" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 74, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.74" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 75, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.75" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 76, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.76" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 77, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.77" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 78, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.78" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 79, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.79" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 80, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.80" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 81, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.81" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 82, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.82" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 83, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.83" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 84, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.84" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 85, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.85" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 86, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.86" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 87, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.87" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 88, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.88" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 89, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.89" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 90, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.90" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 91, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.91" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 92, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.92" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 93, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.93" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 94, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.94" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 95, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.95" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 96, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.96" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 97, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.97" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 98, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.98" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 99, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.99" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 100, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.100" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 101, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.101" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 102, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.102" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 103, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.103" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 104, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.104" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 105, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.105" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 106, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.106" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 107, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.107" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 108, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.108" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 109, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.109" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 110, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.110" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 111, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.111" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 112, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.112" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 113, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.113" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 114, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.114" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 115, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.115" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 116, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.116" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 117, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.117" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 118, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.118" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 119, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.119" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 120, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.120" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 121, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.121" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 122, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.122" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 123, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.123" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 124, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.124" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 125, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.125" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 126, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.126" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 127, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.127" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 128, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.128" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 129, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.129" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 130, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.130" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 131, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.131" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 132, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.132" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 133, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.133" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 134, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.134" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 135, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.135" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 136, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.136" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 137, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.137" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 138, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.138" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 139, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.139" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 140, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.140" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 141, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.141" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 142, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.142" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 143, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.143" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 144, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.144" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 145, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.145" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 146, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.146" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 147, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.147" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 148, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.148" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 149, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.149" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 150, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.150" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 151, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.151" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 152, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.152" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 153, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.153" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 154, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.154" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 155, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.155" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 156, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.156" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 157, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.157" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 158, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.158" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 159, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.159" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 160, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.160" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 161, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.161" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 162, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.162" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 163, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.163" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 164, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.164" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 165, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.165" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 166, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.166" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 167, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.167" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 168, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.168" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 169, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.169" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 170, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.170" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 171, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.171" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 172, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.172" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 173, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.173" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 174, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.174" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 175, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.175" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 176, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.176" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 177, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.177" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 178, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.178" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 179, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.179" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 180, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.180" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 181, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.181" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 182, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.182" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 183, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.183" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 184, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.184" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 185, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.185" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 186, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.186" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 187, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.187" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 188, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.188" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 189, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.189" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 190, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.190" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 191, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.191" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 192, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.192" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 193, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.193" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 194, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.194" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 195, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.195" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 196, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.196" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 197, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.197" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 198, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.198" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 199, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.199" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 200, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.200" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 201, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.201" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 202, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.202" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 203, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.203" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 204, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.204" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 205, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.205" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 206, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.206" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 207, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.207" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 208, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.208" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 209, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.209" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 210, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.210" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 211, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.211" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 212, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.212" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 213, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.213" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 214, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.214" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 215, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.215" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 216, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.216" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 217, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.217" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 218, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.218" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 219, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.219" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 220, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.220" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 221, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.221" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 222, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.222" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 223, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.223" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 224, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.224" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 225, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.225" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 226, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.226" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 227, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.227" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 228, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.228" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 229, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.229" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 230, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.230" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 231, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.231" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 232, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.232" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 233, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.233" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 234, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.234" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 235, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.235" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 236, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.236" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 237, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.237" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 238, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.238" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 239, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.239" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 240, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.240" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 241, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.241" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 242, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.242" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 243, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.243" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 244, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.244" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 245, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.245" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 246, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.246" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 247, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.247" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 248, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.248" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 249, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.249" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 250, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.250" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 251, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.251" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 252, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.252" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 253, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.253" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 254, + "fields": { + "ping_avg_rtt": null, + "subnet": 1, + "last_contact": null, + "address": "10.0.3.254" + }, + "model": "mdb.ip4address" +}, +{ + "pk": 1, + "fields": { + "description": "Client machine (puppet controlled)", + "host_type": "client" + }, + "model": "mdb.hosttype" +}, +{ + "pk": 1, + "fields": { + "name": "ubuntu", + "arch": "x86_64", + "version": "14.04" + }, + "model": "mdb.operatingsystem" +}, +{ + "pk": 1, + "fields": { + "created_date": "2015-10-03T13:23:37.061", + "owner": "edb", + "serial_number": "12345678", + "kerberos_principal_created": false, + "model": "OptiPlex 760", + "pxe_key": "snop", + "kerberos_principal_name": "", + "kerberos_principal_created_date": null, + "pxe_installable": true, + "description": "testclient", + "host_type": 1, + "location": "kak", + "operating_system": 1, + "virtual": false, + "brand": "Dell", + "request_kerberos_principal": false, + "hostname": "tut" + }, + "model": "mdb.host" +}, +{ + "pk": 1, + "fields": { + "created_date": "2015-10-03T13:23:37.063", + "name": "eth0", + "ip4address": 2, + "host": 1, + "domain": 1, + "pxe_filename": "pxelinux.0", + "macaddr": "f0:de:f1:58:be:1a", + "dhcp_client": true + }, + "model": "mdb.interface" +} +] diff --git a/mdb/migrations/0007_auto_20151003_1313.py b/mdb/migrations/0007_auto_20151003_1313.py new file mode 100644 index 0000000..27db5c3 --- /dev/null +++ b/mdb/migrations/0007_auto_20151003_1313.py @@ -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), + ), + ] diff --git a/mdb/models.py b/mdb/models.py index de4904f..d55a720 100644 --- a/mdb/models.py +++ b/mdb/models.py @@ -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() diff --git a/mdb/tests.py b/mdb/tests.py index f699e78..bfd4846 100644 --- a/mdb/tests.py +++ b/mdb/tests.py @@ -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) diff --git a/mdb/validators.py b/mdb/validators.py index 58bfb1b..5959f1b 100644 --- a/mdb/validators.py +++ b/mdb/validators.py @@ -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]+(?=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 From 836a4d136effffaeafdf67abaf937768c7f434b5 Mon Sep 17 00:00:00 2001 From: Nikolai Kristiansen Date: Sat, 3 Oct 2015 15:17:05 +0200 Subject: [PATCH 04/11] Adds pxe field to host in admin, minor refactor --- mdb/admin.py | 8 ++++---- mdb/api/serializers.py | 16 ++++++++-------- mdb/api/views.py | 10 +++++----- mdbsite/urls.py | 4 ++-- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/mdb/admin.py b/mdb/admin.py index 6ababb0..ee3ce25 100644 --- a/mdb/admin.py +++ b/mdb/admin.py @@ -20,17 +20,17 @@ class HostAdmin(admin.ModelAdmin): ordering = ('hostname',) inlines = [InterfaceInline] list_display = ['hostname', 'owner', 'host_type', 'location', 'mac_addresses', 'ip_addresses', 'in_domain', - 'ipv6_enabled'] - list_filter = ['host_type', 'owner', 'location'] + 'pxe_installable', 'ipv6_enabled'] + list_filter = ['host_type', 'owner', 'location', 'pxe_installable'] readonly_fields = ['kerberos_principal_name', 'kerberos_principal_created_date', 'kerberos_principal_created'] search_fields = ['hostname', 'location', 'interface__macaddr', 'interface__ip4address__address'] fieldsets = ( ('Owner Information', { - 'fields': ('owner', 'location', 'description' ) + 'fields': ('owner', 'location', 'description') }), ('Hardware and Software Information', { - 'fields': (('brand', 'model', 'serial_number'), ('hostname', 'host_type'), ('operating_system', 'virtual')) + 'fields': (('hostname', 'host_type'), ('pxe_key', 'pxe_installable'), ('brand', 'model'), 'serial_number', ('operating_system', 'virtual')) }), # FIXME: Not in use # ('Domain and Kerberos Information', { diff --git a/mdb/api/serializers.py b/mdb/api/serializers.py index 17a1fec..eb5e7d7 100644 --- a/mdb/api/serializers.py +++ b/mdb/api/serializers.py @@ -1,14 +1,14 @@ -from mdb.models import Host, Interface, Domain +from mdb.models import Host from rest_framework import serializers from rest_framework.exceptions import ValidationError -class HostValidateSecretSerializer(serializers.Serializer): +class HostPXEValidateSerializer(serializers.Serializer): certname = serializers.CharField(required=True) pxe_key = serializers.CharField(required=True) host = None - def _host_in_domain_exists(self, attrs): + def _get_host_by_fqdn(self, attrs): fqdn = attrs['certname'] host_name = fqdn.split('.')[0] domain = '.'.join(fqdn.split('.')[1:]) @@ -20,19 +20,19 @@ class HostValidateSecretSerializer(serializers.Serializer): def validate(self, attrs): # custom validation - host = self._host_in_domain_exists(attrs) + host = self._get_host_by_fqdn(attrs) if host is None: - raise ValidationError('Host \'{}\' not found'.format(attrs['certname'])) + raise ValidationError('Host \'{}\' not found.'.format(attrs['certname'])) if not host.pxe_installable: - raise ValidationError('Host is marked as not installable via PXE.') + raise ValidationError('Host \'{}\' is marked as not installable via PXE.'.format(attrs['certname'])) if not host.pxe_key: - raise ValidationError('Host has no pxe_key.') + raise ValidationError('Host \'{}\' has no pxe_key.'.format(attrs['certname'])) if host.pxe_key != attrs['pxe_key']: - raise ValidationError('Supplied pxe_key does not match host.') + raise ValidationError('Supplied pxe_key does not match host \'{}\'.'.format(attrs['certname'])) self.host = host diff --git a/mdb/api/views.py b/mdb/api/views.py index 8b2b1f8..dd9ec89 100644 --- a/mdb/api/views.py +++ b/mdb/api/views.py @@ -3,18 +3,18 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView -from mdb.api.serializers import HostValidateSecretSerializer +from mdb.api.serializers import HostPXEValidateSerializer -class ValidatePuppetHostSecret(APIView): +class HostPXEValidate(APIView): authentication_classes = [TokenAuthentication] permission_classes = [IsAuthenticated] def post(self, request): - serializer = HostValidateSecretSerializer(data=request.data) + serializer = HostPXEValidateSerializer(data=request.data) serializer.is_valid(raise_exception=True) - serializer.save() # sets pxe_installable=False + host = serializer.save() # sets Host.pxe_installable=False - return Response({'valid_secret': True}) + return Response({'valid_pxe_key': True}) diff --git a/mdbsite/urls.py b/mdbsite/urls.py index ce82912..4deb08c 100644 --- a/mdbsite/urls.py +++ b/mdbsite/urls.py @@ -2,14 +2,14 @@ from django.conf.urls import include, url from django.contrib import admin from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.views.generic.base import RedirectView -from mdb.api.views import ValidatePuppetHostSecret +from mdb.api.views import HostPXEValidate admin.autodiscover() urlpatterns = [ url(r'^$', RedirectView.as_view(url='admin/', permanent=True)), url(r'^admin/', include(admin.site.urls)), - url(r'^api/validate-host-secret/', ValidatePuppetHostSecret.as_view(), name='api-validate-host-secret') + url(r'^api/validate-host-secret/', HostPXEValidate.as_view(), name='api-validate-host-secret') ] urlpatterns += staticfiles_urlpatterns() From 8d537dfba78f7bdb6ca3ce41de136cbe08556443 Mon Sep 17 00:00:00 2001 From: Nikolai Kristiansen Date: Sat, 3 Oct 2015 17:05:34 +0200 Subject: [PATCH 05/11] Write pxeconfig files per host - Autogenerate pxe_key if missing - Adds AppConfig - Move signals to own file - Fixes local_setting simport on python 3 --- mdb/__init__.py | 1 + mdb/apps.py | 10 +++ mdb/models.py | 130 ++----------------------------------- mdb/signals.py | 121 ++++++++++++++++++++++++++++++++++ mdb/templates/pxelinux.cfg | 4 ++ mdb/tests.py | 12 +++- mdb/utils.py | 82 +++++++++++++++++++++++ mdbsite/settings.py | 5 +- 8 files changed, 237 insertions(+), 128 deletions(-) create mode 100644 mdb/apps.py create mode 100644 mdb/signals.py create mode 100644 mdb/templates/pxelinux.cfg create mode 100644 mdb/utils.py diff --git a/mdb/__init__.py b/mdb/__init__.py index e69de29..d600276 100644 --- a/mdb/__init__.py +++ b/mdb/__init__.py @@ -0,0 +1 @@ +default_app_config = 'mdb.apps.MdbAppConfig' diff --git a/mdb/apps.py b/mdb/apps.py new file mode 100644 index 0000000..159d9aa --- /dev/null +++ b/mdb/apps.py @@ -0,0 +1,10 @@ +from django.apps import AppConfig + + +class MdbAppConfig(AppConfig): + name = 'mdb' + verbose_name = 'Machine Database' + + def ready(self): + # all models are loaded, now attach signals + import mdb.signals \ No newline at end of file diff --git a/mdb/models.py b/mdb/models.py index d55a720..26b1bc3 100644 --- a/mdb/models.py +++ b/mdb/models.py @@ -1,17 +1,14 @@ from django.db import models from django.db.models import Count -from django.db.models.signals import post_save, pre_save, pre_delete -from django.dispatch import receiver from django.utils.encoding import python_2_unicode_compatible from django.utils.six import u from django.utils.translation import ugettext_lazy as _ - +from mdb.utils import host_as_pxe_files from mdb.validators import validate_hostname, validate_macaddr import ipaddress import datetime -import re @python_2_unicode_compatible @@ -548,6 +545,9 @@ class Host(models.Model): addresses = self.interface_set.filter(ip4address__isnull=False).values_list('ip4address__address', flat=True) return ",".join(addresses) + def as_pxe_files(self): + return host_as_pxe_files(self) + @python_2_unicode_compatible class Interface(models.Model): @@ -585,125 +585,3 @@ class Ip6Address(models.Model): class Meta: verbose_name = 'IPv6 address' verbose_name_plural = 'IPv6 addresses' - - -def format_domain_serial_and_add_one(serial): - today = datetime.datetime.now() - res = re.findall("^%4d%02d%02d(\d\d)$" % ( - today.year, today.month, today.day), str(serial), re.DOTALL) - - if len(res) == 0: - """ This probably means that the serial is malformed - or the date is wrong. We assume that if the date is wrong, - it is in the past. Just create a new serial starting from 1.""" - return "%4d%02d%02d%02d" % \ - (today.year, today.month, today.day, 1) - elif len(res) == 1: - """ The serial contains todays date, just update it. """ - try: - number = int(res[0]) - except: - number = 1 - if number >= 99: - """ This is bad... Just keep the number on 99. - We also send a mail to sysadmins telling them that - something is wrong...""" - - else: - number += 1 - return "%4d%02d%02d%02d" % ( - today.year, today.month, today.day, number) - else: - """ Just return the first serial for today. """ - return "%4d%02d%02d%02d" % \ - (today.year, today.month, today.day, 1) - - -@receiver(post_save, sender=Ip4Subnet) -def create_ips_for_subnet(sender, instance, created, **kwargs): - if not created: - return - - subnet = ipaddress.IPv4Network(instance.network + "/" + instance.netmask) - - for addr in subnet.hosts(): - address = Ip4Address(address=str(addr), subnet=instance) - address.save() - - -@receiver(pre_delete, sender=Ip4Subnet) -def delete_ips_for_subnet(sender, instance, **kwargs): - for addr in instance.ip4address_set.all(): - addr.delete() - - -@receiver(pre_save, sender=Ip4Subnet) -def set_domain_name_for_subnet(sender, instance, **kwargs): - # we assume that the reverse domain_name does not change - if len(instance.domain_name) == 0: - ipspl = instance.network.split(".") - rev = "%s.%s.%s" % (ipspl[2], ipspl[1], ipspl[0]) - instance.domain_name = "%s.in-addr.arpa" % rev - - # update it's own serial - # if instance.domain_serial is not None: - # instance.domain_serial = format_domain_serial_and_add_one(instance.domain_serial) - - # lets update the serial of the dhcp config - # when the subnet is changed - if instance.dhcp_config: - # instance.dhcp_config.serial = instance.dhcp_config.serial + 1 - instance.dhcp_config.serial = format_domain_serial_and_add_one(instance.dhcp_config.serial) - instance.dhcp_config.save() - - -@receiver(pre_save, sender=Ip6Subnet) -def set_domain_name_for_ipv6_subnet(sender, instance, **kwargs): - if len(instance.domain_name) > 0: - return - - network = ipaddress.IPv6Address("%s::" % instance.network) - instance.domain_name = ".".join(network.exploded.replace(":", "")[:16])[::-1] + ".ip6.arpa" - - -@receiver(post_save, sender=Interface) -def update_domain_serial_when_change_to_interface(sender, instance, created, **kwargs): - if instance.domain is not None: - domain = instance.domain - domain.domain_serial = format_domain_serial_and_add_one(domain.domain_serial) - domain.save() - - if instance.ip4address is not None: - subnet = instance.ip4address.subnet - subnet.domain_serial = format_domain_serial_and_add_one(subnet.domain_serial) - subnet.save() - - -@receiver(post_save, sender=Host) -def update_domain_serial_when_change_to_host(sender, instance, created, **kwargs): - for interface in instance.interface_set.all(): - if interface.domain is not None: - domain = interface.domain - domain.domain_serial = format_domain_serial_and_add_one(domain.domain_serial) - domain.save() - if interface.ip4address is not None: - subnet = interface.ip4address.subnet - subnet.domain_serial = format_domain_serial_and_add_one(subnet.domain_serial) - subnet.save() - - -@receiver(pre_delete, sender=Interface) -def update_domain_serial_when_interface_deleted(sender, instance, **kwargs): - if instance.domain is not None: - domain = instance.domain - domain.domain_serial += 1 - domain.save() - - if instance.ip4address is not None: - subnet = instance.ip4address.subnet - subnet.domain_serial += 1 - subnet.save() - -# @receiver(pre_save, sender=Domain) -# def update_domain_serial_when_domain_is_saved(sender, instance, **kwargs): -# instance.domain_serial = format_domain_serial_and_add_one(instance.domain_serial) diff --git a/mdb/signals.py b/mdb/signals.py new file mode 100644 index 0000000..d18e467 --- /dev/null +++ b/mdb/signals.py @@ -0,0 +1,121 @@ +import logging +import os +import uuid +from django.conf import settings +from django.db.models.signals import pre_delete, post_save, pre_save +from django.dispatch import receiver +import ipaddress + +from mdb.models import Interface, Host, Ip6Subnet, Ip4Subnet, Ip4Address +from mdb.utils import format_domain_serial_and_add_one + +logger = logging.getLogger(__name__) + + +@receiver(post_save, sender=Ip4Subnet) +def create_ips_for_subnet(sender, instance, created, **kwargs): + if not created: + return + + subnet = ipaddress.IPv4Network(instance.network + "/" + instance.netmask) + + for addr in subnet.hosts(): + address = Ip4Address(address=str(addr), subnet=instance) + address.save() + + +@receiver(pre_delete, sender=Ip4Subnet) +def delete_ips_for_subnet(sender, instance, **kwargs): + for addr in instance.ip4address_set.all(): + addr.delete() + + +@receiver(pre_save, sender=Ip4Subnet) +def set_domain_name_for_subnet(sender, instance, **kwargs): + # we assume that the reverse domain_name does not change + if len(instance.domain_name) == 0: + ipspl = instance.network.split(".") + rev = "%s.%s.%s" % (ipspl[2], ipspl[1], ipspl[0]) + instance.domain_name = "%s.in-addr.arpa" % rev + + # update it's own serial + # if instance.domain_serial is not None: + # instance.domain_serial = format_domain_serial_and_add_one(instance.domain_serial) + + # lets update the serial of the dhcp config + # when the subnet is changed + if instance.dhcp_config: + # instance.dhcp_config.serial = instance.dhcp_config.serial + 1 + instance.dhcp_config.serial = format_domain_serial_and_add_one(instance.dhcp_config.serial) + instance.dhcp_config.save() + + +@receiver(pre_save, sender=Ip6Subnet) +def set_domain_name_for_ipv6_subnet(sender, instance, **kwargs): + if len(instance.domain_name) > 0: + return + + network = ipaddress.IPv6Address("%s::" % instance.network) + instance.domain_name = ".".join(network.exploded.replace(":", "")[:16])[::-1] + ".ip6.arpa" + + +@receiver(post_save, sender=Interface) +def update_domain_serial_when_change_to_interface(sender, instance, created, **kwargs): + if instance.domain is not None: + domain = instance.domain + domain.domain_serial = format_domain_serial_and_add_one(domain.domain_serial) + domain.save() + + if instance.ip4address is not None: + subnet = instance.ip4address.subnet + subnet.domain_serial = format_domain_serial_and_add_one(subnet.domain_serial) + subnet.save() + + +@receiver(post_save, sender=Host) +def update_domain_serial_when_change_to_host(sender, instance, created, **kwargs): + for interface in instance.interface_set.all(): + if interface.domain is not None: + domain = interface.domain + domain.domain_serial = format_domain_serial_and_add_one(domain.domain_serial) + domain.save() + if interface.ip4address is not None: + subnet = interface.ip4address.subnet + subnet.domain_serial = format_domain_serial_and_add_one(subnet.domain_serial) + subnet.save() + + +@receiver(post_save, sender=Host) +def create_pxe_key_and_write_pxe_files_when_host_changes(sender, instance, created, **kwargs): + if not instance.pxe_key: + # generate key (prevent infinite recursion by using update) + pxe_key = uuid.uuid4().hex + Host.objects.filter(pk=instance.pk).update(pxe_key=pxe_key) + instance.pxe_key = pxe_key + + for pxe_file_name, pxe_file in instance.as_pxe_files(): + path = os.path.join(settings.MDB_PXE_TFTP_ROOT, pxe_file_name) + if instance.pxe_installable: + with open(path, 'w+') as f: + f.write(pxe_file) + logger.info("Created or updated {}".format(path)) + else: + os.unlink(path) + logger.info("deleted {}".format(path)) + + +@receiver(pre_delete, sender=Interface) +def update_domain_serial_when_interface_deleted(sender, instance, **kwargs): + if instance.domain is not None: + domain = instance.domain + domain.domain_serial += 1 + domain.save() + + if instance.ip4address is not None: + subnet = instance.ip4address.subnet + subnet.domain_serial += 1 + subnet.save() + +# @receiver(pre_save, sender=Domain) +# def update_domain_serial_when_domain_is_saved(sender, instance, **kwargs): +# instance.domain_serial = format_domain_serial_and_add_one(instance.domain_serial) diff --git a/mdb/templates/pxelinux.cfg b/mdb/templates/pxelinux.cfg new file mode 100644 index 0000000..b333dd3 --- /dev/null +++ b/mdb/templates/pxelinux.cfg @@ -0,0 +1,4 @@ +kernel {{kernel}} +append ramdisk_size=14984 locale=en_US console-setup/ask_detect=false keyboard-configuration/layoutcode=no netcfg/wireless_wep= netcfg_choose_interface=eth0 netcfg/get_hostname= preseed/url={{preseed_config_url}} vga=normal initrd={{initrd}} -- snop={{host.pxe_key}} +prompt 0 +timeout 0 \ No newline at end of file diff --git a/mdb/tests.py b/mdb/tests.py index bfd4846..6c975cf 100644 --- a/mdb/tests.py +++ b/mdb/tests.py @@ -1,9 +1,11 @@ from django.contrib.auth.models import User from django.core.urlresolvers import reverse -from mdb.models import Host +from django.test import TestCase from rest_framework.authtoken.models import Token from rest_framework.test import APITestCase +from mdb.models import Host + class MyAPITestCases(APITestCase): fixtures = ['test_data'] @@ -26,3 +28,11 @@ class MyAPITestCases(APITestCase): 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) + + +class UnitTests(TestCase): + fixtures = ['test_data'] + + def test_host_as_pxe_files(self): + host = Host.objects.first() + self.assertEqual(len(host.as_pxe_files()), 1) diff --git a/mdb/utils.py b/mdb/utils.py new file mode 100644 index 0000000..19e734b --- /dev/null +++ b/mdb/utils.py @@ -0,0 +1,82 @@ +import datetime +from django.conf import settings +from django.template.loader import render_to_string +import ipaddress +import re + + +def format_domain_serial_and_add_one(serial): + today = datetime.datetime.now() + res = re.findall("^%4d%02d%02d(\d\d)$" % ( + today.year, today.month, today.day), str(serial), re.DOTALL) + + if len(res) == 0: + """ This probably means that the serial is malformed + or the date is wrong. We assume that if the date is wrong, + it is in the past. Just create a new serial starting from 1.""" + return "%4d%02d%02d%02d" % \ + (today.year, today.month, today.day, 1) + elif len(res) == 1: + """ The serial contains todays date, just update it. """ + try: + number = int(res[0]) + except: + number = 1 + if number >= 99: + """ This is bad... Just keep the number on 99. + We also send a mail to sysadmins telling them that + something is wrong...""" + + else: + number += 1 + return "%4d%02d%02d%02d" % ( + today.year, today.month, today.day, number) + else: + """ Just return the first serial for today. """ + return "%4d%02d%02d%02d" % \ + (today.year, today.month, today.day, 1) + + +def render_pxelinux_cfg(_if, host): + # TODO add fields to os model and make dynamic per host + distro = 'ubuntu/14_04/amd64/alternate/' + kernel = distro + 'linux' + initrd = distro + 'initrd.gz' + preseed_config_url = settings.MDB_PXE_PRESEED_URL + context = { + 'kernel': kernel, + 'initrd': initrd, + 'preseed_config_url': preseed_config_url, + 'host': host, + 'interface': _if + } + return render_to_string('pxelinux.cfg', context=context) + + +def get_pxe_filename(interface, filename_format='mac_addr'): + """ + Mac or IP address based PXE filename + Ref: http://www.syslinux.org/wiki/index.php/PXELINUX#Examples + """ + ip_addr_in_hex = '{:02X}'.format(int(ipaddress.IPv4Address(interface.ip4address.address))) + formats = { + 'ip_addr': ip_addr_in_hex, + 'mac_addr': '01-{}'.format(interface.macaddr.lower().replace(':', '-')) + } + + return formats.get(filename_format, formats['mac_addr']) + + +def host_as_pxe_files(host): + """ Returns a list of filename,content for each host """ + from mdb.models import Host + assert isinstance(host, Host) + + pxe_files = [] + ifs = host.interface_set.filter(dhcp_client=True) + for _if in ifs: + filename = get_pxe_filename(_if) + content = render_pxelinux_cfg(_if, host) + pxe_files.append((filename, content)) + + return pxe_files diff --git a/mdbsite/settings.py b/mdbsite/settings.py index 3a0bf85..1e0549c 100644 --- a/mdbsite/settings.py +++ b/mdbsite/settings.py @@ -122,7 +122,10 @@ REST_FRAMEWORK = { 'PAGINATE_BY': 10 } +MDB_PXE_TFTP_ROOT = '/var/lib/tftpboot/pxelinux/pxelinux.cfg/' +MDB_PXE_PRESEED_URL = 'http://158.36.190.194/ubuntu/preseed_1404.cfg' + try: - from local_settings import * + from .local_settings import * except ImportError: pass From c332f7bc4046ecfbb2a91a1d048a8b9f500c7ce1 Mon Sep 17 00:00:00 2001 From: Nikolai Kristiansen Date: Sun, 4 Oct 2015 12:45:41 +0200 Subject: [PATCH 06/11] Sync scripts work again with ipv4 --- README.md | 2 +- mdb/fixtures/test_data.json | 4 ++-- mdb/models.py | 40 ++++++++++++++----------------------- mdb/tests.py | 20 ++++++++++++++++++- 4 files changed, 37 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index d4f3fa3..aaa5f68 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ A Django app for managing computer equipment. ## Installation - sudo apt-get install libmysqlclient-dev python-dev libldap2-dev libsasl2-dev bind9utils + sudo apt install libmysqlclient-dev python-dev libldap2-dev libsasl2-dev bind9utils virtualenv venv . venv/bin/activate pip install -r requirements.txt diff --git a/mdb/fixtures/test_data.json b/mdb/fixtures/test_data.json index 0ef9f9a..5b2e24f 100644 --- a/mdb/fixtures/test_data.json +++ b/mdb/fixtures/test_data.json @@ -39,7 +39,7 @@ "domain_serial": 2015100301, "domain_refresh": 28800, "domain_name": "edb.technology", - "domain_filename": "/tmp/mdb/bind/edb.technology", + "domain_filename": "/tmp/bind-db.edb.technology", "domain_minimum_ttl": 86400, "domain_expire": 604800, "domain_soa": "sega.neuf.no", @@ -81,7 +81,7 @@ "netmask": "255.255.255.0", "dhcp_dynamic_start": "10.0.3.2", "domain_admin": "sysadmin@neuf.no", - "domain_filename": "/tmp/mdb/bind/3.0.10.in-addr.arpa", + "domain_filename": "/tmp/bind-db.3.0.10.in-addr.arpa", "domain_minimum_ttl": 86400, "domain_expire": 604800, "dhcp_dynamic": true, diff --git a/mdb/models.py b/mdb/models.py index 26b1bc3..0a8ed52 100644 --- a/mdb/models.py +++ b/mdb/models.py @@ -222,17 +222,15 @@ class DhcpConfig(models.Model): # time to write host definitions for subnet in self.ip4subnet_set.all(): for ip4address in subnet.ip4address_set.all(): - if ip4address.interface_set.count() == 0: + if not hasattr(ip4address, 'interface') or not ip4address.interface.dhcp_client: continue - interface = ip4address.interface_set.get() - if not interface.dhcp_client: - continue - content += "\nhost %s {\n" % interface.host.hostname - content += "\thardware ethernet %s;\n" % interface.macaddr + _if = ip4address.interface + content += "\nhost %s {\n" % _if.host.hostname + content += "\thardware ethernet %s;\n" % _if.macaddr content += "\tfixed-address %s.%s;\n" % \ - (interface.host.hostname, interface.domain.domain_name) - if len(interface.pxe_filename) > 0: - content += "\tfilename \"%s\";\n" % interface.pxe_filename + (_if.host.hostname, _if.domain.domain_name) + if len(_if.pxe_filename) > 0: + content += "\tfilename \"%s\";\n" % _if.pxe_filename content += "}\n" return content @@ -388,26 +386,18 @@ class Ip4Subnet(models.Model): content += ";\n" for addr in self.ip4address_set.all(): - if addr.interface_set.count() == 0 and generate_unassigned: + """ Get PTR records for each v4 address in subnet """ + if hasattr(addr, 'interface') and addr.interface.domain: + hostname = "%s.%s" % ( + addr.interface.host.hostname, + addr.interface.domain.domain_name) + content += "%-20s\tIN\tPTR\t%s.\n" % \ + (addr.address.split(".")[3], hostname) + elif not hasattr(addr, 'interface') and generate_unassigned: content += "%s\tIN\tPTR\t%s.%s\n" % ( addr.address, addr.address.split(".")[3], "dhcp.neuf.no.") - continue - - for interface in addr.interface_set.all(): - if interface.domain is None and generate_unassigned: - content += "%s\tIN\tPTR\t%s.%s.\n" % ( - addr.address, - addr.address.split(".")[3], - "dhcp.neuf.no") - - else: - hostname = "%s.%s" % ( - interface.host.hostname, - interface.domain.domain_name) - content += "%-20s\tIN\tPTR\t%s.\n" % \ - (addr.address.split(".")[3], hostname) return content diff --git a/mdb/tests.py b/mdb/tests.py index 6c975cf..7581525 100644 --- a/mdb/tests.py +++ b/mdb/tests.py @@ -1,6 +1,8 @@ from django.contrib.auth.models import User +from django.core.management import call_command from django.core.urlresolvers import reverse -from django.test import TestCase +from django.test import TestCase, LiveServerTestCase +from django.utils import six from rest_framework.authtoken.models import Token from rest_framework.test import APITestCase @@ -36,3 +38,19 @@ class UnitTests(TestCase): def test_host_as_pxe_files(self): host = Host.objects.first() self.assertEqual(len(host.as_pxe_files()), 1) + + +class RunManagementCommands(TestCase): + fixtures = ['test_data'] + + def test_zone_sync(self): + expected_zone_file = '' + with six.BytesIO() as f: + call_command('zone-sync', debug=True, stdout=f) + # TODO check expected_zone_file + + def test_dhcp_sync(self): + expected_dhcp_file = '' + with six.BytesIO() as f: + call_command('dhcp-sync', ('--debug',), stdout=f) + # TODO check expected_dhcp_file From a07edd160b539034d3aacba9f3f8aeea40af383b Mon Sep 17 00:00:00 2001 From: Nikolai Kristiansen Date: Sun, 4 Oct 2015 13:14:51 +0200 Subject: [PATCH 07/11] Use IPv6-field and adds ipv6 test data --- README.md | 8 +- mdb/fixtures/test_data.json | 2738 +++++++++++---------- mdb/migrations/0008_auto_20151004_1258.py | 19 + mdb/models.py | 7 +- 4 files changed, 1414 insertions(+), 1358 deletions(-) create mode 100644 mdb/migrations/0008_auto_20151004_1258.py diff --git a/README.md b/README.md index aaa5f68..7b94cf1 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,14 @@ A Django app for managing computer equipment. python manage.py migrate python manage.py runserver +## Development tasks +Dump test data: + + python manage.py dumpdata mdb --natural-foreign --indent=4 > mdb/fixtures/test_data.json + + ## Features * Creates DHCPD configuration files * Creates DNS zone files for BIND -* TODO: Creates PXE config files (with random secret for use with Puppet autosign) \ No newline at end of file +* TODO: Creates PXE config files (with random secret for use with Puppet autosign) diff --git a/mdb/fixtures/test_data.json b/mdb/fixtures/test_data.json index 5b2e24f..1da19d9 100644 --- a/mdb/fixtures/test_data.json +++ b/mdb/fixtures/test_data.json @@ -1,2722 +1,2756 @@ [ { - "pk": 1, + "model": "mdb.nameserver", "fields": { "hostname": "sega.neuf.no" }, - "model": "mdb.nameserver" + "pk": 1 }, { - "pk": 2, + "model": "mdb.nameserver", "fields": { "hostname": "atari.neuf.no" }, - "model": "mdb.nameserver" + "pk": 2 }, { - "pk": 1, + "model": "mdb.mailexchange", "fields": { "priority": 10, "hostname": "mx.neuf.no" }, - "model": "mdb.mailexchange" + "pk": 1 }, { - "pk": 1, + "model": "mdb.domain", "fields": { - "domain_admin": "sysadmin@neuf.no", - "created_date": "2015-10-03T13:02:53.581", - "domain_ipaddr": "127.0.0.1", - "domain_retry": 7200, "domain_active_serial": 0, + "domain_soa": "sega.neuf.no", + "domain_retry": 7200, + "domain_expire": 604800, + "domain_ipaddr": "127.0.0.1", + "domain_mailexchanges": [ + 1 + ], + "domain_minimum_ttl": 86400, + "domain_ttl": 3600, + "domain_refresh": 28800, + "domain_admin": "sysadmin@neuf.no", + "domain_serial": 2015100313, "domain_nameservers": [ 1, 2 ], - "domain_mailexchanges": [ - 1 - ], - "domain_serial": 2015100301, - "domain_refresh": 28800, - "domain_name": "edb.technology", "domain_filename": "/tmp/bind-db.edb.technology", - "domain_minimum_ttl": 86400, - "domain_expire": 604800, - "domain_soa": "sega.neuf.no", - "domain_ttl": 3600 + "created_date": "2015-10-03T13:02:53.581", + "domain_name": "edb.technology" }, - "model": "mdb.domain" + "pk": 1 }, { - "pk": 1, + "model": "mdb.dhcpconfig", "fields": { "log_facility": "local7", - "name": "default", "max_lease_time": 7200, - "default_lease_time": 600, - "authoritative": false, "ddns_update_style": "none", + "authoritative": false, + "serial": 2015100401, "active_serial": 1, - "serial": 2015100302 + "name": "default", + "default_lease_time": 600 }, - "model": "mdb.dhcpconfig" + "pk": 1 }, { - "pk": 1, + "model": "mdb.ip6subnet", + "fields": { + "created_date": "2015-10-04T12:50:12.248", + "domain_soa": "sega.neuf.no", + "domain_retry": 7200, + "domain_expire": 604800, + "domain_ttl": 3600, + "domain_minimum_ttl": 86400, + "network": "2001:16d8:eeb6:3", + "domain_serial": 1, + "domain_refresh": 28800, + "domain_admin": "sysadmin@neuf.no", + "domain_active_serial": 0, + "netmask": 64, + "domain_nameservers": [ + 1, + 2 + ], + "domain_filename": "/tmp/bind-reverse-v6-2001-16d8-eeb6-3-64.ip6.arpa", + "name": "client-ipv6", + "domain_name": "3.0.0.0.6.b.e.e.8.d.6.1.1.0.0.2.ip6.arpa" + }, + "pk": 1 +}, +{ + "model": "mdb.ip4subnet", "fields": { "created_date": "2015-10-03T13:08:17.765", - "network": "10.0.3.0", - "name": "clients", + "domain_soa": "sega.neuf.no", + "dhcp_config": 1, "domain_retry": 7200, - "domain_active_serial": 0, + "dhcp_dynamic_start": "10.0.3.2", + "domain_expire": 604800, + "domain_ttl": 3600, "domain_nameservers": [ 1, 2 ], "dhcp_dynamic_end": "10.0.3.254", - "domain_serial": 2015100301, - "dhcp_config": 1, - "domain_refresh": 28800, - "domain_name": "3.0.10.in-addr.arpa", - "netmask": "255.255.255.0", - "dhcp_dynamic_start": "10.0.3.2", - "domain_admin": "sysadmin@neuf.no", - "domain_filename": "/tmp/bind-db.3.0.10.in-addr.arpa", "domain_minimum_ttl": 86400, - "domain_expire": 604800, + "network": "10.0.3.0", + "domain_serial": 2015100313, + "domain_refresh": 28800, + "domain_admin": "sysadmin@neuf.no", + "domain_active_serial": 0, + "netmask": "255.255.255.0", "dhcp_dynamic": true, - "domain_soa": "sega.neuf.no", - "domain_ttl": 3600 + "domain_filename": "/tmp/bind-db.3.0.10.in-addr.arpa", + "name": "clients", + "domain_name": "3.0.10.in-addr.arpa" }, - "model": "mdb.ip4subnet" + "pk": 1 }, { - "pk": 1, + "model": "mdb.dhcpoption", "fields": { - "ip4subnet": 1, "key": "domain-name", + "ip4subnet": 1, "value": "\"edb.technology\"" }, - "model": "mdb.dhcpoption" + "pk": 1 }, { - "pk": 2, + "model": "mdb.dhcpoption", "fields": { - "ip4subnet": 1, "key": "domain-name-servers", + "ip4subnet": 1, "value": "158.36.190.194, 158.36.190.199, 8.8.8.8" }, - "model": "mdb.dhcpoption" + "pk": 2 }, { - "pk": 3, + "model": "mdb.dhcpoption", "fields": { - "ip4subnet": 1, "key": "routers", + "ip4subnet": 1, "value": "158.36.186.1" }, - "model": "mdb.dhcpoption" + "pk": 3 }, { - "pk": 1, + "model": "mdb.dhcpcustomfield", "fields": { "ip4subnet": 1, "value": "deny unknown-clients" }, - "model": "mdb.dhcpcustomfield" + "pk": 1 }, { - "pk": 1, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.1" + "ping_avg_rtt": null, + "address": "10.0.3.1", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 1 }, { - "pk": 2, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.2" + "ping_avg_rtt": null, + "address": "10.0.3.2", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 2 }, { - "pk": 3, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.3" + "ping_avg_rtt": null, + "address": "10.0.3.3", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 3 }, { - "pk": 4, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.4" + "ping_avg_rtt": null, + "address": "10.0.3.4", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 4 }, { - "pk": 5, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.5" + "ping_avg_rtt": null, + "address": "10.0.3.5", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 5 }, { - "pk": 6, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.6" + "ping_avg_rtt": null, + "address": "10.0.3.6", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 6 }, { - "pk": 7, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.7" + "ping_avg_rtt": null, + "address": "10.0.3.7", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 7 }, { - "pk": 8, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.8" + "ping_avg_rtt": null, + "address": "10.0.3.8", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 8 }, { - "pk": 9, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.9" + "ping_avg_rtt": null, + "address": "10.0.3.9", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 9 }, { - "pk": 10, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.10" + "ping_avg_rtt": null, + "address": "10.0.3.10", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 10 }, { - "pk": 11, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.11" + "ping_avg_rtt": null, + "address": "10.0.3.11", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 11 }, { - "pk": 12, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.12" + "ping_avg_rtt": null, + "address": "10.0.3.12", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 12 }, { - "pk": 13, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.13" + "ping_avg_rtt": null, + "address": "10.0.3.13", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 13 }, { - "pk": 14, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.14" + "ping_avg_rtt": null, + "address": "10.0.3.14", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 14 }, { - "pk": 15, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.15" + "ping_avg_rtt": null, + "address": "10.0.3.15", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 15 }, { - "pk": 16, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.16" + "ping_avg_rtt": null, + "address": "10.0.3.16", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 16 }, { - "pk": 17, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.17" + "ping_avg_rtt": null, + "address": "10.0.3.17", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 17 }, { - "pk": 18, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.18" + "ping_avg_rtt": null, + "address": "10.0.3.18", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 18 }, { - "pk": 19, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.19" + "ping_avg_rtt": null, + "address": "10.0.3.19", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 19 }, { - "pk": 20, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.20" + "ping_avg_rtt": null, + "address": "10.0.3.20", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 20 }, { - "pk": 21, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.21" + "ping_avg_rtt": null, + "address": "10.0.3.21", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 21 }, { - "pk": 22, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.22" + "ping_avg_rtt": null, + "address": "10.0.3.22", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 22 }, { - "pk": 23, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.23" + "ping_avg_rtt": null, + "address": "10.0.3.23", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 23 }, { - "pk": 24, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.24" + "ping_avg_rtt": null, + "address": "10.0.3.24", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 24 }, { - "pk": 25, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.25" + "ping_avg_rtt": null, + "address": "10.0.3.25", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 25 }, { - "pk": 26, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.26" + "ping_avg_rtt": null, + "address": "10.0.3.26", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 26 }, { - "pk": 27, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.27" + "ping_avg_rtt": null, + "address": "10.0.3.27", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 27 }, { - "pk": 28, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.28" + "ping_avg_rtt": null, + "address": "10.0.3.28", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 28 }, { - "pk": 29, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.29" + "ping_avg_rtt": null, + "address": "10.0.3.29", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 29 }, { - "pk": 30, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.30" + "ping_avg_rtt": null, + "address": "10.0.3.30", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 30 }, { - "pk": 31, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.31" + "ping_avg_rtt": null, + "address": "10.0.3.31", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 31 }, { - "pk": 32, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.32" + "ping_avg_rtt": null, + "address": "10.0.3.32", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 32 }, { - "pk": 33, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.33" + "ping_avg_rtt": null, + "address": "10.0.3.33", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 33 }, { - "pk": 34, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.34" + "ping_avg_rtt": null, + "address": "10.0.3.34", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 34 }, { - "pk": 35, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.35" + "ping_avg_rtt": null, + "address": "10.0.3.35", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 35 }, { - "pk": 36, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.36" + "ping_avg_rtt": null, + "address": "10.0.3.36", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 36 }, { - "pk": 37, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.37" + "ping_avg_rtt": null, + "address": "10.0.3.37", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 37 }, { - "pk": 38, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.38" + "ping_avg_rtt": null, + "address": "10.0.3.38", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 38 }, { - "pk": 39, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.39" + "ping_avg_rtt": null, + "address": "10.0.3.39", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 39 }, { - "pk": 40, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.40" + "ping_avg_rtt": null, + "address": "10.0.3.40", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 40 }, { - "pk": 41, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.41" + "ping_avg_rtt": null, + "address": "10.0.3.41", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 41 }, { - "pk": 42, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.42" + "ping_avg_rtt": null, + "address": "10.0.3.42", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 42 }, { - "pk": 43, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.43" + "ping_avg_rtt": null, + "address": "10.0.3.43", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 43 }, { - "pk": 44, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.44" + "ping_avg_rtt": null, + "address": "10.0.3.44", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 44 }, { - "pk": 45, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.45" + "ping_avg_rtt": null, + "address": "10.0.3.45", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 45 }, { - "pk": 46, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.46" + "ping_avg_rtt": null, + "address": "10.0.3.46", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 46 }, { - "pk": 47, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.47" + "ping_avg_rtt": null, + "address": "10.0.3.47", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 47 }, { - "pk": 48, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.48" + "ping_avg_rtt": null, + "address": "10.0.3.48", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 48 }, { - "pk": 49, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.49" + "ping_avg_rtt": null, + "address": "10.0.3.49", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 49 }, { - "pk": 50, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.50" + "ping_avg_rtt": null, + "address": "10.0.3.50", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 50 }, { - "pk": 51, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.51" + "ping_avg_rtt": null, + "address": "10.0.3.51", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 51 }, { - "pk": 52, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.52" + "ping_avg_rtt": null, + "address": "10.0.3.52", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 52 }, { - "pk": 53, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.53" + "ping_avg_rtt": null, + "address": "10.0.3.53", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 53 }, { - "pk": 54, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.54" + "ping_avg_rtt": null, + "address": "10.0.3.54", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 54 }, { - "pk": 55, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.55" + "ping_avg_rtt": null, + "address": "10.0.3.55", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 55 }, { - "pk": 56, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.56" + "ping_avg_rtt": null, + "address": "10.0.3.56", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 56 }, { - "pk": 57, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.57" + "ping_avg_rtt": null, + "address": "10.0.3.57", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 57 }, { - "pk": 58, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.58" + "ping_avg_rtt": null, + "address": "10.0.3.58", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 58 }, { - "pk": 59, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.59" + "ping_avg_rtt": null, + "address": "10.0.3.59", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 59 }, { - "pk": 60, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.60" + "ping_avg_rtt": null, + "address": "10.0.3.60", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 60 }, { - "pk": 61, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.61" + "ping_avg_rtt": null, + "address": "10.0.3.61", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 61 }, { - "pk": 62, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.62" + "ping_avg_rtt": null, + "address": "10.0.3.62", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 62 }, { - "pk": 63, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.63" + "ping_avg_rtt": null, + "address": "10.0.3.63", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 63 }, { - "pk": 64, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.64" + "ping_avg_rtt": null, + "address": "10.0.3.64", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 64 }, { - "pk": 65, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.65" + "ping_avg_rtt": null, + "address": "10.0.3.65", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 65 }, { - "pk": 66, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.66" + "ping_avg_rtt": null, + "address": "10.0.3.66", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 66 }, { - "pk": 67, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.67" + "ping_avg_rtt": null, + "address": "10.0.3.67", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 67 }, { - "pk": 68, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.68" + "ping_avg_rtt": null, + "address": "10.0.3.68", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 68 }, { - "pk": 69, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.69" + "ping_avg_rtt": null, + "address": "10.0.3.69", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 69 }, { - "pk": 70, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.70" + "ping_avg_rtt": null, + "address": "10.0.3.70", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 70 }, { - "pk": 71, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.71" + "ping_avg_rtt": null, + "address": "10.0.3.71", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 71 }, { - "pk": 72, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.72" + "ping_avg_rtt": null, + "address": "10.0.3.72", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 72 }, { - "pk": 73, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.73" + "ping_avg_rtt": null, + "address": "10.0.3.73", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 73 }, { - "pk": 74, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.74" + "ping_avg_rtt": null, + "address": "10.0.3.74", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 74 }, { - "pk": 75, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.75" + "ping_avg_rtt": null, + "address": "10.0.3.75", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 75 }, { - "pk": 76, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.76" + "ping_avg_rtt": null, + "address": "10.0.3.76", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 76 }, { - "pk": 77, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.77" + "ping_avg_rtt": null, + "address": "10.0.3.77", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 77 }, { - "pk": 78, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.78" + "ping_avg_rtt": null, + "address": "10.0.3.78", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 78 }, { - "pk": 79, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.79" + "ping_avg_rtt": null, + "address": "10.0.3.79", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 79 }, { - "pk": 80, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.80" + "ping_avg_rtt": null, + "address": "10.0.3.80", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 80 }, { - "pk": 81, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.81" + "ping_avg_rtt": null, + "address": "10.0.3.81", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 81 }, { - "pk": 82, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.82" + "ping_avg_rtt": null, + "address": "10.0.3.82", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 82 }, { - "pk": 83, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.83" + "ping_avg_rtt": null, + "address": "10.0.3.83", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 83 }, { - "pk": 84, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.84" + "ping_avg_rtt": null, + "address": "10.0.3.84", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 84 }, { - "pk": 85, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.85" + "ping_avg_rtt": null, + "address": "10.0.3.85", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 85 }, { - "pk": 86, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.86" + "ping_avg_rtt": null, + "address": "10.0.3.86", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 86 }, { - "pk": 87, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.87" + "ping_avg_rtt": null, + "address": "10.0.3.87", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 87 }, { - "pk": 88, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.88" + "ping_avg_rtt": null, + "address": "10.0.3.88", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 88 }, { - "pk": 89, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.89" + "ping_avg_rtt": null, + "address": "10.0.3.89", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 89 }, { - "pk": 90, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.90" + "ping_avg_rtt": null, + "address": "10.0.3.90", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 90 }, { - "pk": 91, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.91" + "ping_avg_rtt": null, + "address": "10.0.3.91", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 91 }, { - "pk": 92, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.92" + "ping_avg_rtt": null, + "address": "10.0.3.92", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 92 }, { - "pk": 93, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.93" + "ping_avg_rtt": null, + "address": "10.0.3.93", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 93 }, { - "pk": 94, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.94" + "ping_avg_rtt": null, + "address": "10.0.3.94", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 94 }, { - "pk": 95, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.95" + "ping_avg_rtt": null, + "address": "10.0.3.95", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 95 }, { - "pk": 96, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.96" + "ping_avg_rtt": null, + "address": "10.0.3.96", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 96 }, { - "pk": 97, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.97" + "ping_avg_rtt": null, + "address": "10.0.3.97", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 97 }, { - "pk": 98, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.98" + "ping_avg_rtt": null, + "address": "10.0.3.98", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 98 }, { - "pk": 99, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.99" + "ping_avg_rtt": null, + "address": "10.0.3.99", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 99 }, { - "pk": 100, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.100" + "ping_avg_rtt": null, + "address": "10.0.3.100", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 100 }, { - "pk": 101, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.101" + "ping_avg_rtt": null, + "address": "10.0.3.101", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 101 }, { - "pk": 102, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.102" + "ping_avg_rtt": null, + "address": "10.0.3.102", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 102 }, { - "pk": 103, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.103" + "ping_avg_rtt": null, + "address": "10.0.3.103", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 103 }, { - "pk": 104, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.104" + "ping_avg_rtt": null, + "address": "10.0.3.104", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 104 }, { - "pk": 105, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.105" + "ping_avg_rtt": null, + "address": "10.0.3.105", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 105 }, { - "pk": 106, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.106" + "ping_avg_rtt": null, + "address": "10.0.3.106", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 106 }, { - "pk": 107, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.107" + "ping_avg_rtt": null, + "address": "10.0.3.107", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 107 }, { - "pk": 108, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.108" + "ping_avg_rtt": null, + "address": "10.0.3.108", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 108 }, { - "pk": 109, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.109" + "ping_avg_rtt": null, + "address": "10.0.3.109", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 109 }, { - "pk": 110, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.110" + "ping_avg_rtt": null, + "address": "10.0.3.110", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 110 }, { - "pk": 111, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.111" + "ping_avg_rtt": null, + "address": "10.0.3.111", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 111 }, { - "pk": 112, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.112" + "ping_avg_rtt": null, + "address": "10.0.3.112", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 112 }, { - "pk": 113, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.113" + "ping_avg_rtt": null, + "address": "10.0.3.113", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 113 }, { - "pk": 114, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.114" + "ping_avg_rtt": null, + "address": "10.0.3.114", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 114 }, { - "pk": 115, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.115" + "ping_avg_rtt": null, + "address": "10.0.3.115", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 115 }, { - "pk": 116, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.116" + "ping_avg_rtt": null, + "address": "10.0.3.116", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 116 }, { - "pk": 117, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.117" + "ping_avg_rtt": null, + "address": "10.0.3.117", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 117 }, { - "pk": 118, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.118" + "ping_avg_rtt": null, + "address": "10.0.3.118", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 118 }, { - "pk": 119, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.119" + "ping_avg_rtt": null, + "address": "10.0.3.119", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 119 }, { - "pk": 120, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.120" + "ping_avg_rtt": null, + "address": "10.0.3.120", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 120 }, { - "pk": 121, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.121" + "ping_avg_rtt": null, + "address": "10.0.3.121", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 121 }, { - "pk": 122, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.122" + "ping_avg_rtt": null, + "address": "10.0.3.122", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 122 }, { - "pk": 123, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.123" + "ping_avg_rtt": null, + "address": "10.0.3.123", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 123 }, { - "pk": 124, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.124" + "ping_avg_rtt": null, + "address": "10.0.3.124", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 124 }, { - "pk": 125, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.125" + "ping_avg_rtt": null, + "address": "10.0.3.125", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 125 }, { - "pk": 126, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.126" + "ping_avg_rtt": null, + "address": "10.0.3.126", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 126 }, { - "pk": 127, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.127" + "ping_avg_rtt": null, + "address": "10.0.3.127", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 127 }, { - "pk": 128, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.128" + "ping_avg_rtt": null, + "address": "10.0.3.128", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 128 }, { - "pk": 129, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.129" + "ping_avg_rtt": null, + "address": "10.0.3.129", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 129 }, { - "pk": 130, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.130" + "ping_avg_rtt": null, + "address": "10.0.3.130", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 130 }, { - "pk": 131, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.131" + "ping_avg_rtt": null, + "address": "10.0.3.131", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 131 }, { - "pk": 132, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.132" + "ping_avg_rtt": null, + "address": "10.0.3.132", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 132 }, { - "pk": 133, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.133" + "ping_avg_rtt": null, + "address": "10.0.3.133", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 133 }, { - "pk": 134, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.134" + "ping_avg_rtt": null, + "address": "10.0.3.134", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 134 }, { - "pk": 135, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.135" + "ping_avg_rtt": null, + "address": "10.0.3.135", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 135 }, { - "pk": 136, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.136" + "ping_avg_rtt": null, + "address": "10.0.3.136", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 136 }, { - "pk": 137, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.137" + "ping_avg_rtt": null, + "address": "10.0.3.137", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 137 }, { - "pk": 138, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.138" + "ping_avg_rtt": null, + "address": "10.0.3.138", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 138 }, { - "pk": 139, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.139" + "ping_avg_rtt": null, + "address": "10.0.3.139", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 139 }, { - "pk": 140, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.140" + "ping_avg_rtt": null, + "address": "10.0.3.140", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 140 }, { - "pk": 141, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.141" + "ping_avg_rtt": null, + "address": "10.0.3.141", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 141 }, { - "pk": 142, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.142" + "ping_avg_rtt": null, + "address": "10.0.3.142", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 142 }, { - "pk": 143, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.143" + "ping_avg_rtt": null, + "address": "10.0.3.143", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 143 }, { - "pk": 144, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.144" + "ping_avg_rtt": null, + "address": "10.0.3.144", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 144 }, { - "pk": 145, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.145" + "ping_avg_rtt": null, + "address": "10.0.3.145", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 145 }, { - "pk": 146, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.146" + "ping_avg_rtt": null, + "address": "10.0.3.146", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 146 }, { - "pk": 147, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.147" + "ping_avg_rtt": null, + "address": "10.0.3.147", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 147 }, { - "pk": 148, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.148" + "ping_avg_rtt": null, + "address": "10.0.3.148", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 148 }, { - "pk": 149, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.149" + "ping_avg_rtt": null, + "address": "10.0.3.149", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 149 }, { - "pk": 150, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.150" + "ping_avg_rtt": null, + "address": "10.0.3.150", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 150 }, { - "pk": 151, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.151" + "ping_avg_rtt": null, + "address": "10.0.3.151", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 151 }, { - "pk": 152, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.152" + "ping_avg_rtt": null, + "address": "10.0.3.152", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 152 }, { - "pk": 153, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.153" + "ping_avg_rtt": null, + "address": "10.0.3.153", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 153 }, { - "pk": 154, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.154" + "ping_avg_rtt": null, + "address": "10.0.3.154", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 154 }, { - "pk": 155, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.155" + "ping_avg_rtt": null, + "address": "10.0.3.155", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 155 }, { - "pk": 156, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.156" + "ping_avg_rtt": null, + "address": "10.0.3.156", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 156 }, { - "pk": 157, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.157" + "ping_avg_rtt": null, + "address": "10.0.3.157", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 157 }, { - "pk": 158, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.158" + "ping_avg_rtt": null, + "address": "10.0.3.158", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 158 }, { - "pk": 159, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.159" + "ping_avg_rtt": null, + "address": "10.0.3.159", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 159 }, { - "pk": 160, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.160" + "ping_avg_rtt": null, + "address": "10.0.3.160", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 160 }, { - "pk": 161, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.161" + "ping_avg_rtt": null, + "address": "10.0.3.161", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 161 }, { - "pk": 162, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.162" + "ping_avg_rtt": null, + "address": "10.0.3.162", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 162 }, { - "pk": 163, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.163" + "ping_avg_rtt": null, + "address": "10.0.3.163", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 163 }, { - "pk": 164, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.164" + "ping_avg_rtt": null, + "address": "10.0.3.164", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 164 }, { - "pk": 165, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.165" + "ping_avg_rtt": null, + "address": "10.0.3.165", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 165 }, { - "pk": 166, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.166" + "ping_avg_rtt": null, + "address": "10.0.3.166", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 166 }, { - "pk": 167, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.167" + "ping_avg_rtt": null, + "address": "10.0.3.167", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 167 }, { - "pk": 168, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.168" + "ping_avg_rtt": null, + "address": "10.0.3.168", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 168 }, { - "pk": 169, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.169" + "ping_avg_rtt": null, + "address": "10.0.3.169", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 169 }, { - "pk": 170, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.170" + "ping_avg_rtt": null, + "address": "10.0.3.170", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 170 }, { - "pk": 171, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.171" + "ping_avg_rtt": null, + "address": "10.0.3.171", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 171 }, { - "pk": 172, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.172" + "ping_avg_rtt": null, + "address": "10.0.3.172", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 172 }, { - "pk": 173, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.173" + "ping_avg_rtt": null, + "address": "10.0.3.173", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 173 }, { - "pk": 174, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.174" + "ping_avg_rtt": null, + "address": "10.0.3.174", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 174 }, { - "pk": 175, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.175" + "ping_avg_rtt": null, + "address": "10.0.3.175", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 175 }, { - "pk": 176, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.176" + "ping_avg_rtt": null, + "address": "10.0.3.176", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 176 }, { - "pk": 177, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.177" + "ping_avg_rtt": null, + "address": "10.0.3.177", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 177 }, { - "pk": 178, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.178" + "ping_avg_rtt": null, + "address": "10.0.3.178", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 178 }, { - "pk": 179, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.179" + "ping_avg_rtt": null, + "address": "10.0.3.179", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 179 }, { - "pk": 180, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.180" + "ping_avg_rtt": null, + "address": "10.0.3.180", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 180 }, { - "pk": 181, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.181" + "ping_avg_rtt": null, + "address": "10.0.3.181", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 181 }, { - "pk": 182, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.182" + "ping_avg_rtt": null, + "address": "10.0.3.182", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 182 }, { - "pk": 183, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.183" + "ping_avg_rtt": null, + "address": "10.0.3.183", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 183 }, { - "pk": 184, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.184" + "ping_avg_rtt": null, + "address": "10.0.3.184", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 184 }, { - "pk": 185, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.185" + "ping_avg_rtt": null, + "address": "10.0.3.185", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 185 }, { - "pk": 186, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.186" + "ping_avg_rtt": null, + "address": "10.0.3.186", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 186 }, { - "pk": 187, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.187" + "ping_avg_rtt": null, + "address": "10.0.3.187", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 187 }, { - "pk": 188, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.188" + "ping_avg_rtt": null, + "address": "10.0.3.188", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 188 }, { - "pk": 189, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.189" + "ping_avg_rtt": null, + "address": "10.0.3.189", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 189 }, { - "pk": 190, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.190" + "ping_avg_rtt": null, + "address": "10.0.3.190", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 190 }, { - "pk": 191, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.191" + "ping_avg_rtt": null, + "address": "10.0.3.191", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 191 }, { - "pk": 192, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.192" + "ping_avg_rtt": null, + "address": "10.0.3.192", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 192 }, { - "pk": 193, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.193" + "ping_avg_rtt": null, + "address": "10.0.3.193", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 193 }, { - "pk": 194, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.194" + "ping_avg_rtt": null, + "address": "10.0.3.194", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 194 }, { - "pk": 195, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.195" + "ping_avg_rtt": null, + "address": "10.0.3.195", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 195 }, { - "pk": 196, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.196" + "ping_avg_rtt": null, + "address": "10.0.3.196", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 196 }, { - "pk": 197, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.197" + "ping_avg_rtt": null, + "address": "10.0.3.197", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 197 }, { - "pk": 198, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.198" + "ping_avg_rtt": null, + "address": "10.0.3.198", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 198 }, { - "pk": 199, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.199" + "ping_avg_rtt": null, + "address": "10.0.3.199", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 199 }, { - "pk": 200, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.200" + "ping_avg_rtt": null, + "address": "10.0.3.200", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 200 }, { - "pk": 201, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.201" + "ping_avg_rtt": null, + "address": "10.0.3.201", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 201 }, { - "pk": 202, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.202" + "ping_avg_rtt": null, + "address": "10.0.3.202", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 202 }, { - "pk": 203, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.203" + "ping_avg_rtt": null, + "address": "10.0.3.203", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 203 }, { - "pk": 204, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.204" + "ping_avg_rtt": null, + "address": "10.0.3.204", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 204 }, { - "pk": 205, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.205" + "ping_avg_rtt": null, + "address": "10.0.3.205", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 205 }, { - "pk": 206, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.206" + "ping_avg_rtt": null, + "address": "10.0.3.206", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 206 }, { - "pk": 207, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.207" + "ping_avg_rtt": null, + "address": "10.0.3.207", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 207 }, { - "pk": 208, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.208" + "ping_avg_rtt": null, + "address": "10.0.3.208", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 208 }, { - "pk": 209, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.209" + "ping_avg_rtt": null, + "address": "10.0.3.209", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 209 }, { - "pk": 210, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.210" + "ping_avg_rtt": null, + "address": "10.0.3.210", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 210 }, { - "pk": 211, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.211" + "ping_avg_rtt": null, + "address": "10.0.3.211", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 211 }, { - "pk": 212, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.212" + "ping_avg_rtt": null, + "address": "10.0.3.212", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 212 }, { - "pk": 213, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.213" + "ping_avg_rtt": null, + "address": "10.0.3.213", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 213 }, { - "pk": 214, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.214" + "ping_avg_rtt": null, + "address": "10.0.3.214", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 214 }, { - "pk": 215, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.215" + "ping_avg_rtt": null, + "address": "10.0.3.215", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 215 }, { - "pk": 216, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.216" + "ping_avg_rtt": null, + "address": "10.0.3.216", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 216 }, { - "pk": 217, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.217" + "ping_avg_rtt": null, + "address": "10.0.3.217", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 217 }, { - "pk": 218, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.218" + "ping_avg_rtt": null, + "address": "10.0.3.218", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 218 }, { - "pk": 219, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.219" + "ping_avg_rtt": null, + "address": "10.0.3.219", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 219 }, { - "pk": 220, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.220" + "ping_avg_rtt": null, + "address": "10.0.3.220", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 220 }, { - "pk": 221, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.221" + "ping_avg_rtt": null, + "address": "10.0.3.221", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 221 }, { - "pk": 222, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.222" + "ping_avg_rtt": null, + "address": "10.0.3.222", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 222 }, { - "pk": 223, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.223" + "ping_avg_rtt": null, + "address": "10.0.3.223", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 223 }, { - "pk": 224, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.224" + "ping_avg_rtt": null, + "address": "10.0.3.224", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 224 }, { - "pk": 225, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.225" + "ping_avg_rtt": null, + "address": "10.0.3.225", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 225 }, { - "pk": 226, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.226" + "ping_avg_rtt": null, + "address": "10.0.3.226", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 226 }, { - "pk": 227, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.227" + "ping_avg_rtt": null, + "address": "10.0.3.227", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 227 }, { - "pk": 228, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.228" + "ping_avg_rtt": null, + "address": "10.0.3.228", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 228 }, { - "pk": 229, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.229" + "ping_avg_rtt": null, + "address": "10.0.3.229", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 229 }, { - "pk": 230, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.230" + "ping_avg_rtt": null, + "address": "10.0.3.230", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 230 }, { - "pk": 231, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.231" + "ping_avg_rtt": null, + "address": "10.0.3.231", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 231 }, { - "pk": 232, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.232" + "ping_avg_rtt": null, + "address": "10.0.3.232", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 232 }, { - "pk": 233, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.233" + "ping_avg_rtt": null, + "address": "10.0.3.233", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 233 }, { - "pk": 234, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.234" + "ping_avg_rtt": null, + "address": "10.0.3.234", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 234 }, { - "pk": 235, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.235" + "ping_avg_rtt": null, + "address": "10.0.3.235", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 235 }, { - "pk": 236, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.236" + "ping_avg_rtt": null, + "address": "10.0.3.236", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 236 }, { - "pk": 237, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.237" + "ping_avg_rtt": null, + "address": "10.0.3.237", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 237 }, { - "pk": 238, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.238" + "ping_avg_rtt": null, + "address": "10.0.3.238", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 238 }, { - "pk": 239, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.239" + "ping_avg_rtt": null, + "address": "10.0.3.239", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 239 }, { - "pk": 240, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.240" + "ping_avg_rtt": null, + "address": "10.0.3.240", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 240 }, { - "pk": 241, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.241" + "ping_avg_rtt": null, + "address": "10.0.3.241", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 241 }, { - "pk": 242, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.242" + "ping_avg_rtt": null, + "address": "10.0.3.242", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 242 }, { - "pk": 243, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.243" + "ping_avg_rtt": null, + "address": "10.0.3.243", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 243 }, { - "pk": 244, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.244" + "ping_avg_rtt": null, + "address": "10.0.3.244", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 244 }, { - "pk": 245, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.245" + "ping_avg_rtt": null, + "address": "10.0.3.245", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 245 }, { - "pk": 246, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.246" + "ping_avg_rtt": null, + "address": "10.0.3.246", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 246 }, { - "pk": 247, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.247" + "ping_avg_rtt": null, + "address": "10.0.3.247", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 247 }, { - "pk": 248, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.248" + "ping_avg_rtt": null, + "address": "10.0.3.248", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 248 }, { - "pk": 249, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.249" + "ping_avg_rtt": null, + "address": "10.0.3.249", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 249 }, { - "pk": 250, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.250" + "ping_avg_rtt": null, + "address": "10.0.3.250", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 250 }, { - "pk": 251, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.251" + "ping_avg_rtt": null, + "address": "10.0.3.251", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 251 }, { - "pk": 252, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.252" + "ping_avg_rtt": null, + "address": "10.0.3.252", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 252 }, { - "pk": 253, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.253" + "ping_avg_rtt": null, + "address": "10.0.3.253", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 253 }, { - "pk": 254, + "model": "mdb.ip4address", "fields": { - "ping_avg_rtt": null, "subnet": 1, - "last_contact": null, - "address": "10.0.3.254" + "ping_avg_rtt": null, + "address": "10.0.3.254", + "last_contact": null }, - "model": "mdb.ip4address" + "pk": 254 }, { - "pk": 1, + "model": "mdb.hosttype", "fields": { "description": "Client machine (puppet controlled)", "host_type": "client" }, - "model": "mdb.hosttype" + "pk": 1 }, { - "pk": 1, + "model": "mdb.operatingsystem", "fields": { - "name": "ubuntu", "arch": "x86_64", - "version": "14.04" + "version": "14.04", + "name": "ubuntu" }, - "model": "mdb.operatingsystem" + "pk": 1 }, { - "pk": 1, + "model": "mdb.host", "fields": { - "created_date": "2015-10-03T13:23:37.061", - "owner": "edb", - "serial_number": "12345678", "kerberos_principal_created": false, "model": "OptiPlex 760", - "pxe_key": "snop", - "kerberos_principal_name": "", - "kerberos_principal_created_date": null, - "pxe_installable": true, - "description": "testclient", - "host_type": 1, "location": "kak", - "operating_system": 1, + "kerberos_principal_created_date": null, + "owner": "edb", + "created_date": "2015-10-03T13:23:37.061", + "hostname": "tut", + "host_type": 1, + "serial_number": "12345678", "virtual": false, "brand": "Dell", - "request_kerberos_principal": false, - "hostname": "tut" + "pxe_installable": true, + "kerberos_principal_name": "", + "description": "testclient", + "pxe_key": "e17b532276e247fe80b92540bef71007", + "operating_system": 1, + "request_kerberos_principal": false }, - "model": "mdb.host" + "pk": 1 }, { - "pk": 1, + "model": "mdb.interface", "fields": { "created_date": "2015-10-03T13:23:37.063", - "name": "eth0", - "ip4address": 2, - "host": 1, "domain": 1, - "pxe_filename": "pxelinux.0", "macaddr": "f0:de:f1:58:be:1a", - "dhcp_client": true + "pxe_filename": "pxelinux.0", + "dhcp_client": true, + "ip4address": 2, + "name": "eth0", + "host": 1 }, - "model": "mdb.interface" + "pk": 1 +}, +{ + "model": "mdb.ip6address", + "fields": { + "subnet": 1, + "interface": 1, + "address": "::2" + }, + "pk": 1 } ] diff --git a/mdb/migrations/0008_auto_20151004_1258.py b/mdb/migrations/0008_auto_20151004_1258.py new file mode 100644 index 0000000..5fb79ad --- /dev/null +++ b/mdb/migrations/0008_auto_20151004_1258.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('mdb', '0007_auto_20151003_1313'), + ] + + operations = [ + migrations.AlterField( + model_name='ip6address', + name='address', + field=models.GenericIPAddressField(protocol='IPv6'), + ), + ] diff --git a/mdb/models.py b/mdb/models.py index 0a8ed52..b23f135 100644 --- a/mdb/models.py +++ b/mdb/models.py @@ -353,10 +353,7 @@ class Ip4Subnet(models.Model): def last_address(self): subnet = ipaddress.IPv4Network(self.network + "/" + self.netmask) - for curr in subnet.hosts(): - pass # horribly inefficient - - return curr + return list(subnet.hosts())[-1] broadcast_address.short_description = 'broadcast' num_addresses.short_description = '#addresses' @@ -560,7 +557,7 @@ class Interface(models.Model): @python_2_unicode_compatible class Ip6Address(models.Model): subnet = models.ForeignKey(Ip6Subnet) - address = models.CharField(max_length=64) + address = models.GenericIPAddressField(protocol='IPv6') interface = models.ForeignKey(Interface) def full_address(self): From 2a959c47afb387e375d3dc75f21056253f0860bc Mon Sep 17 00:00:00 2001 From: Nikolai Kristiansen Date: Sun, 4 Oct 2015 14:13:53 +0200 Subject: [PATCH 08/11] Make it easier setting multiple hosts as pxe installable, ++ --- mdb/admin.py | 27 +++++++++++++++++++++++++-- mdb/models.py | 14 +++++++------- mdb/signals.py | 33 ++++++++++++++++++++++++++------- mdb/utils.py | 5 ++--- 4 files changed, 60 insertions(+), 19 deletions(-) diff --git a/mdb/admin.py b/mdb/admin.py index ee3ce25..e025f20 100644 --- a/mdb/admin.py +++ b/mdb/admin.py @@ -1,5 +1,8 @@ -from mdb.models import * -from django.contrib import admin +from django.contrib import admin, messages + +from mdb.models import Ip6Address, Interface, Ip4Address, DhcpOption, DhcpCustomField, DomainSrvRecord, DomainTxtRecord, \ + DomainCnameRecord, DomainARecord, Domain, Host, Ip4Subnet, Ip6Subnet, Nameserver, MailExchange, OperatingSystem, \ + HostType, DhcpConfig class Ip6AddressInline(admin.TabularInline): @@ -40,6 +43,26 @@ class HostAdmin(admin.ModelAdmin): # ('kerberos_principal_name', 'kerberos_principal_created_date')) # }), ) + actions = ['set_installable'] + + def _get_host_warning_message(self, host, ifs): + ifs_str = ', '.join([_if.name for _if in ifs]) + return 'Host {} might not be installable since interface(s) {} has no IP set.'.format(host, ifs_str) + + def set_installable(self, request, queryset): + invalid_ifs_per_host = [] + + for obj in queryset: + invalid_ifs = obj.interface_set.filter(ip4address__isnull=True) + if invalid_ifs.exists(): + invalid_ifs_per_host.append(self._get_host_warning_message(obj, invalid_ifs)) + obj.pxe_installable = True + obj.save() # triggers post save signal + + if invalid_ifs_per_host: + self.message_user(request, message='\n'.join(invalid_ifs_per_host), level=messages.WARNING) + + set_installable.short_description = "Mark selected hosts as PXE installable" def get_queryset(self, request): return super(HostAdmin, self).get_queryset(request).select_related() diff --git a/mdb/models.py b/mdb/models.py index b23f135..de99e47 100644 --- a/mdb/models.py +++ b/mdb/models.py @@ -53,12 +53,12 @@ class Domain(models.Model): def num_records(self): host_a_records = Host.objects.filter(interface__domain=self).count() size = { - "cname": self.domaincnamerecord_set.count(), - "srv": self.domainsrvrecord_set.count(), - "txt": self.domaintxtrecord_set.count(), - "a": self.domainarecord_set.count() + host_a_records + "CNAME": self.domaincnamerecord_set.count(), + "SRV": self.domainsrvrecord_set.count(), + "TXT": self.domaintxtrecord_set.count(), + "A": self.domainarecord_set.count() + host_a_records } - return size + return ', '.join(['{}: {}'.format(k, v) for k, v in size.items()]) num_records.short_description = "Num Records" @@ -526,11 +526,11 @@ class Host(models.Model): def mac_addresses(self): addresses = self.interface_set.filter(macaddr__isnull=False).values_list('macaddr', flat=True) - return ",".join(addresses) + return ", ".join(addresses) def ip_addresses(self): addresses = self.interface_set.filter(ip4address__isnull=False).values_list('ip4address__address', flat=True) - return ",".join(addresses) + return ", ".join(addresses) def as_pxe_files(self): return host_as_pxe_files(self) diff --git a/mdb/signals.py b/mdb/signals.py index d18e467..bd91bbc 100644 --- a/mdb/signals.py +++ b/mdb/signals.py @@ -87,21 +87,40 @@ def update_domain_serial_when_change_to_host(sender, instance, created, **kwargs @receiver(post_save, sender=Host) def create_pxe_key_and_write_pxe_files_when_host_changes(sender, instance, created, **kwargs): - if not instance.pxe_key: + host = instance + + if not host.pxe_key: # generate key (prevent infinite recursion by using update) pxe_key = uuid.uuid4().hex - Host.objects.filter(pk=instance.pk).update(pxe_key=pxe_key) - instance.pxe_key = pxe_key + Host.objects.filter(pk=host.pk).update(pxe_key=pxe_key) + host.pxe_key = pxe_key - for pxe_file_name, pxe_file in instance.as_pxe_files(): + for pxe_file_name, pxe_file in host.as_pxe_files(): path = os.path.join(settings.MDB_PXE_TFTP_ROOT, pxe_file_name) - if instance.pxe_installable: + if host.pxe_installable: with open(path, 'w+') as f: f.write(pxe_file) logger.info("Created or updated {}".format(path)) + + # Also set necessary fields for pxe installation + interfaces = host.interface_set.exclude(ip4address__isnull=True) + + for _if in interfaces: + changed = False + if not _if.pxe_filename: + _if.pxe_filename = 'pxelinux.0' # don't overwrite + changed = True + if not _if.dhcp_client: + _if.dhcp_client = True + changed = True + + if changed: + _if.save() + else: - os.unlink(path) - logger.info("deleted {}".format(path)) + if os.path.exists(path): + os.unlink(path) + logger.info("deleted {}".format(path)) @receiver(pre_delete, sender=Interface) diff --git a/mdb/utils.py b/mdb/utils.py index 19e734b..766d5c1 100644 --- a/mdb/utils.py +++ b/mdb/utils.py @@ -68,13 +68,12 @@ def get_pxe_filename(interface, filename_format='mac_addr'): def host_as_pxe_files(host): - """ Returns a list of filename,content for each host """ + """ Returns a list of filename,content for each host interface with an IPv4 address """ from mdb.models import Host assert isinstance(host, Host) pxe_files = [] - ifs = host.interface_set.filter(dhcp_client=True) - for _if in ifs: + for _if in host.interface_set.exclude(ip4address__isnull=True): filename = get_pxe_filename(_if) content = render_pxelinux_cfg(_if, host) pxe_files.append((filename, content)) From 35c0afbb3e78db9e9ac0dc4c536352b33690f3b3 Mon Sep 17 00:00:00 2001 From: Nikolai Kristiansen Date: Sun, 4 Oct 2015 14:21:43 +0200 Subject: [PATCH 09/11] Keep python 2 working --- mdb/models.py | 1 - mdb/tests.py | 8 ++++---- mdbsite/__init__.py | 0 3 files changed, 4 insertions(+), 5 deletions(-) create mode 100644 mdbsite/__init__.py diff --git a/mdb/models.py b/mdb/models.py index de99e47..332505f 100644 --- a/mdb/models.py +++ b/mdb/models.py @@ -415,7 +415,6 @@ class DhcpOption(models.Model): verbose_name = 'DHCP option' -@python_2_unicode_compatible class DhcpCustomField(models.Model): value = models.CharField(max_length=255) ip4subnet = models.ForeignKey(Ip4Subnet) diff --git a/mdb/tests.py b/mdb/tests.py index 7581525..98f16ae 100644 --- a/mdb/tests.py +++ b/mdb/tests.py @@ -1,8 +1,8 @@ +import io from django.contrib.auth.models import User from django.core.management import call_command from django.core.urlresolvers import reverse -from django.test import TestCase, LiveServerTestCase -from django.utils import six +from django.test import TestCase from rest_framework.authtoken.models import Token from rest_framework.test import APITestCase @@ -45,12 +45,12 @@ class RunManagementCommands(TestCase): def test_zone_sync(self): expected_zone_file = '' - with six.BytesIO() as f: + with io.BytesIO() as f: call_command('zone-sync', debug=True, stdout=f) # TODO check expected_zone_file def test_dhcp_sync(self): expected_dhcp_file = '' - with six.BytesIO() as f: + with io.BytesIO() as f: call_command('dhcp-sync', ('--debug',), stdout=f) # TODO check expected_dhcp_file diff --git a/mdbsite/__init__.py b/mdbsite/__init__.py new file mode 100644 index 0000000..e69de29 From 0693cff76a98394d518e25104dfa066bc943e168 Mon Sep 17 00:00:00 2001 From: Nikolai Kristiansen Date: Sun, 4 Oct 2015 16:36:09 +0200 Subject: [PATCH 10/11] Fixes DNS record generation on py3 --- mdb/models.py | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/mdb/models.py b/mdb/models.py index 332505f..bb404cc 100644 --- a/mdb/models.py +++ b/mdb/models.py @@ -1,7 +1,6 @@ from django.db import models from django.db.models import Count from django.utils.encoding import python_2_unicode_compatible -from django.utils.six import u from django.utils.translation import ugettext_lazy as _ from mdb.utils import host_as_pxe_files @@ -96,12 +95,12 @@ class Domain(models.Model): content += "; SRV records\n" for srv in self.domainsrvrecord_set.all(): - content += u(srv) + "\n" + content += srv.as_record() + "\n" content += "; A records\n" for a in self.domainarecord_set.all(): - content += u(a) + "\n" + content += a.as_record() + "\n" content += "; CNAME records \n" @@ -111,7 +110,7 @@ class Domain(models.Model): content += "; TXT records \n" for txt in self.domaintxtrecord_set.all(): - content += u(txt) + "\n" + content += txt.as_record() + "\n" content += "; HOST records\n" @@ -141,10 +140,20 @@ class DomainSrvRecord(models.Model): domain = models.ForeignKey(Domain) created_date = models.DateTimeField(auto_now_add=True) + def as_record(self): + return '{}.{}.{}. IN SRV {} {} {} {}.'.format( + self.srvce, + self.prot, + self.name, + + self.priority, + self.weight, + self.port, + self.target + ) + def __str__(self): - return self.srvce + "." + self.prot + "." + self.name + ". IN SRV " \ - + str(self.priority) + " " + str(self.weight) + " " + \ - str(self.port) + " " + self.target + "." + return self.as_record() @python_2_unicode_compatible @@ -154,8 +163,11 @@ class DomainTxtRecord(models.Model): domain = models.ForeignKey(Domain) created_date = models.DateTimeField(auto_now_add=True) + def as_record(self): + return '{} TXT {}'.format(self.name, self.target) + def __str__(self): - return self.name + " TXT " + self.target + return self.as_record() @python_2_unicode_compatible @@ -165,8 +177,11 @@ class DomainCnameRecord(models.Model): domain = models.ForeignKey(Domain) created_date = models.DateTimeField(auto_now_add=True) + def as_record(self): + return '{} IN CNAME {}'.format(self.name, self.target) + def __str__(self): - return self.name + " IN CNAME " + self.target + return self.as_record() @python_2_unicode_compatible @@ -176,8 +191,11 @@ class DomainARecord(models.Model): domain = models.ForeignKey(Domain) created_date = models.DateTimeField(auto_now_add=True) + def as_record(self): + return '{} IN A {}'.format(self.name, self.target) + def __str__(self): - return self.name + " IN A " + self.target + return self.as_record() @python_2_unicode_compatible From b7826993116b81baa3da41a6c7956062d0664b6a Mon Sep 17 00:00:00 2001 From: Nikolai Kristiansen Date: Sun, 4 Oct 2015 16:38:11 +0200 Subject: [PATCH 11/11] Minor README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7b94cf1..d6956ad 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ Dump test data: python manage.py dumpdata mdb --natural-foreign --indent=4 > mdb/fixtures/test_data.json - ## Features * Creates DHCPD configuration files * Creates DNS zone files for BIND -* TODO: Creates PXE config files (with random secret for use with Puppet autosign) +* Creates PXE config files (with random secret for use with Puppet autosign) +* Partial IPv6 support