[28843] in Source-Commits

home help back first fref pref prev next nref lref last post

moira commit [debian]: Checkpoint.

daemon@ATHENA.MIT.EDU (Anders Kaseorg)
Wed Apr 25 23:50:10 2018

Date: Wed, 25 Apr 2018 23:49:58 -0400
From: Anders Kaseorg <andersk@mit.edu>
Message-Id: <201804260349.w3Q3nwBs025252@drugstore.mit.edu>
To: source-commits@mit.edu

https://github.com/mit-athena/moira/commit/fed0e9c5ee247306906b2d162929d5a37cceedb5
commit fed0e9c5ee247306906b2d162929d5a37cceedb5
Author: Garry Zacheiss <zacheiss@mit.edu>
Date:   Fri Sep 15 16:06:25 2017 -0400

    Checkpoint.

 moira/clients/stella/stella.c            |   20 +-
 moira/db/schema.sql                      |   10 +-
 moira/incremental/infoblox/infoblox.incr |  499 +++++++++++++++++++-----------
 moira/server/increment.pc                |    4 +-
 moira/server/qfollow.pc                  |   27 ++-
 moira/server/qsetup.pc                   |   67 ++++-
 6 files changed, 423 insertions(+), 204 deletions(-)

diff --git a/moira/clients/stella/stella.c b/moira/clients/stella/stella.c
index 5a9a12e..0f68bd5 100644
--- a/moira/clients/stella/stella.c
+++ b/moira/clients/stella/stella.c
@@ -940,18 +940,18 @@ int main(int argc, char **argv)
 
       while (q) {
 	char *args[3];
-	struct mrcl_addropt_type *addropt;
+	struct mrcl_netaddr_type *host_rr;
 
-	addropt = mrcl_parse_addropt(q->string);
-	if (!addropt)
+	host_rr = mrcl_parse_netaddr(q->string);
+	if (!host_rr)
 	  {
 	    com_err(whoami, 0, "Could not parse resource record specification while adding host resource record.");
 	    exit(1);
 	  }
 
 	args[0] = canonicalize_hostname(strdup(hostname));
-	args[1] = addropt->address;
-	args[2] = addropt->opt;
+	args[1] = host_rr->network;
+	args[2] = host_rr->address;
 
 	status = wrap_mr_query("add_host_resource_record", 3, args, NULL, NULL);
 	if (status) {
@@ -970,18 +970,18 @@ int main(int argc, char **argv)
 
       while (q) {
         char *args[3];
-        struct mrcl_addropt_type *addropt;
+        struct mrcl_netaddr_type *host_rr;
 
-        addropt = mrcl_parse_addropt(q->string);
-        if (!addropt)
+        host_rr = mrcl_parse_netaddr(q->string);
+        if (!host_rr)
           {
             com_err(whoami, 0, "Could not parse resource record specification while removing host resource record.");
             exit(1);
           }
 
 	args[0] = canonicalize_hostname(strdup(hostname));
-        args[1] = addropt->address;
-        args[2] = addropt->opt;
+        args[1] = host_rr->network;
+        args[2] = host_rr->address;
 
         status = wrap_mr_query("delete_host_resource_record", 3, args, NULL, NULL);
         if (status) {
diff --git a/moira/db/schema.sql b/moira/db/schema.sql
index d6a7869..2399b1c 100644
--- a/moira/db/schema.sql
+++ b/moira/db/schema.sql
@@ -226,7 +226,7 @@ create table servers
 
 create table serverhosts 
 (
-	service		VARCHAR(16) 	DEFAULT CHR(0)	NOT NULL,
+	service		VARCHAR(32) 	DEFAULT CHR(0)	NOT NULL,
 	mach_id		INTEGER		DEFAULT 0	NOT NULL,
 	success		INTEGER 	DEFAULT 0	NOT NULL,
 	enable		INTEGER 	DEFAULT 0	NOT NULL,
@@ -345,7 +345,7 @@ create table strings
 
 create table services 
 (
-	name		VARCHAR(16) 	DEFAULT CHR(0)	NOT NULL,
+	name		VARCHAR(32) 	DEFAULT CHR(0)	NOT NULL,
 	protocol	VARCHAR(8) 	DEFAULT CHR(0)	NOT NULL,
 	port		SMALLINT 	DEFAULT 0	NOT NULL,
 	description	VARCHAR(64) 	DEFAULT CHR(0)	NOT NULL,
@@ -417,7 +417,7 @@ create table numvalues
 
 create table tblstats 
 (
-	table_name	VARCHAR(16)	DEFAULT CHR(0)	NOT NULL,
+	table_name	VARCHAR(32)	DEFAULT CHR(0)	NOT NULL,
 	modtime		DATE    	DEFAULT SYSDATE	NOT NULL,
 	appends		INTEGER		DEFAULT 0	NOT NULL,
 	updates		INTEGER		DEFAULT 0	NOT NULL,
@@ -524,8 +524,8 @@ create table machidentifiermap
 
 create table incremental_queue
 (
-	table_name      VARCHAR(16)     DEFAULT CHR(0)  NOT NULL,
-	service         VARCHAR(16)     DEFAULT CHR(0)  NOT NULL,
+	table_name      VARCHAR(32)     DEFAULT CHR(0)  NOT NULL,
+	service         VARCHAR(32)     DEFAULT CHR(0)  NOT NULL,
 	beforec		INTEGER		DEFAUlT 0	NOT NULL,
 	afterc		INTEGER		DEFAULT 0	NOT NULL,
 	before		VARCHAR(4000)	DEFAULT CHR(0)	NOT NULL,
diff --git a/moira/incremental/infoblox/infoblox.incr b/moira/incremental/infoblox/infoblox.incr
index 0838d99..1fa4010 100755
--- a/moira/incremental/infoblox/infoblox.incr
+++ b/moira/incremental/infoblox/infoblox.incr
@@ -8,7 +8,9 @@ import os
 import requests
 import struct
 import sys
+import netaddr
 from netaddr import IPAddress
+from netaddr import EUI
 from subprocess import call
 from time import sleep, ctime
 
@@ -90,33 +92,36 @@ def get_moira_host_status(hostname):
     data, = domoira('get_host', (hostname, '*'))
     return int(data[9])
 
+def get_moira_host(hostname):
+    return domoira('get_host', (hostname, '*'))
+
 def mach_to_ib(record):
     extattrs = {
-        'moira_mach_id' : record[1],
-        'moira_vendor' : record[2],
-        'moira_model' : record[3],
-        'moira_os' : record[4],
-        'moira_location' : record[5],
-        'moira_contact' : record[6],
-        'moira_biling_contact' : record[7],
-        'moira_account_number' : record[8],
-        'moira_status' : record[9],
-        'moira_owner_type' : record[10],
-        'moira_owner_id' : record[11],
-        'moira_acomment' : record[12],
-        'moira_ocomment' : record[13],
+        'moira_mach_id' : { 'value' : record[1] },
+        'moira_vendor' : { 'value' : record[2] },
+        'moira_model' : { 'value' : record[3] },
+        'moira_os' : { 'value' : record[4] },
+        'moira_location' : { 'value' : record[5] },
+        'moira_contact' : { 'value' : record[6] },
+        'moira_billing_contact' : { 'value' : record[7] },
+        'moira_account_number' : { 'value' : record[8] },
+        'moira_status' : { 'value' : record[9] },
+        'moira_owner_type' : { 'value' : record[10] },
+        'moira_owner_id' : { 'value' : record[11] },
+        'moira_acomment' : { 'value' : record[12] },
+        'moira_ocomment' : { 'value' : record[13] },
         }
 
-    if extattrs['moira_owner_id'] == "0":
-        extattrs['moira_owner_id'] = "none";
+    if extattrs['moira_owner_id']['value'] == "0":
+        extattrs['moira_owner_id']['value'] = "none";
 
     for key, value in extattrs.items():
-        if not value:
-            extattrs[key] = '-'
+        if not value['value']:
+            extattrs[key]['value'] = '-'
         else:
-            extattrs[key] = value.lower()
+            extattrs[key]['value'] = value['value'].lower()
 
-    extattrs['moira_managed'] = 1;
+    extattrs['moira_managed'] = { 'value' : 1 }
     
     ib_host_record = {
         'name' : record[0].lower(),
@@ -175,10 +180,10 @@ def ib_search_by_name(name):
     return results_by_view
 
 # Add infoblox host record
-def ib_create_host(record):
+def ib_create_host(record, myviews=None):
     # Turn incoming record into something we can work with
     ib_record = mach_to_ib(record)
-    moira_mach_id = ib_record['extattrs']['moira_mach_id']
+    moira_mach_id = ib_record['extattrs']['moira_mach_id']['value']
     # Does this record already exist?
     mach_id_results = ib_search_by_mach_id(moira_mach_id)
     name_results = ib_search_by_name(ib_record['name'])
@@ -191,7 +196,14 @@ def ib_create_host(record):
         log('ib_search_by_name returned error, aborting')
         return
 
-    for view in views:
+    # For creation, use dummy IPs.
+    ib_record['ipv4addrs'] = [ { 'ipv4addr' : '0.0.0.0' } ]
+    ib_record['ipv6addrs'] = [ ]
+
+    if myviews == None:
+        myviews = views
+
+    for view in myviews:
         ib_record['view'] = view
         if len(name_results[view]) > 0:
             critical_log("Record already exists for name %s in view %s" % (ib_record['name'], view))
@@ -203,7 +215,7 @@ def ib_create_host(record):
 
             r = requests.post(ib_url, data=json.dumps(ib_record), headers=headers, auth=(ib_user, ib_passwd))
             if r.status_code != requests.codes.created:
-                critical_log('Infoblox API call failed in ib_create_host for host %s view %s' % (ib_record['name'], view))
+                critical_log('Infoblox API call failed in ib_create_host for host %s view %s with HTTP status %s, %s' % (ib_record['name'], view, r.status_code, r.text))
                 continue
 
             log('Created host record for %s with mach_id %s in view %s: %s' % (ib_record['name'], moira_mach_id, view, r.text))
@@ -211,7 +223,7 @@ def ib_create_host(record):
 # Delete infoblox host record
 def ib_delete_host(record):
     ib_record = mach_to_ib(record)
-    moira_mach_id = ib_record['extattrs']['moira_mach_id']
+    moira_mach_id = ib_record['extattrs']['moira_mach_id']['value']
     # Does this exist?
     mach_id_results = ib_search_by_mach_id(moira_mach_id)
 
@@ -223,8 +235,8 @@ def ib_delete_host(record):
         ib_record['view'] = view
         if len(mach_id_results[view]) == 0:
             log('No record exists for mach_id %s in view %s, not deleting' % (moira_mach_id, view))
-        elif mach_id_results[view]['extattrs']['moira_managed'] != "1":
-            log('Record for mach_id %s in view %s is not managed by moira, not deleting' % (moira_mach_id, view))
+        elif mach_id_results[view][0]['extattrs']['moira_managed']['value'] != 1:
+            log('Record for mach_id %s in view %s is not managed by moira, not deleting: moira_managed value is %s' % (moira_mach_id, view, mach_id_results[view][0]['extattrs']['moira_managed']['value']))
         else:
             ib_ref = mach_id_results[view][0]['_ref']
             ib_url = ib_base_url + '/' + ib_ref
@@ -241,11 +253,11 @@ def ib_delete_host(record):
 def ib_update_host(before, after):
     before_record = mach_to_ib(before)
     after_record = mach_to_ib(after)
-    moira_mach_id = before_record['extattrs']['moira_mach_id']
+    moira_mach_id = before_record['extattrs']['moira_mach_id']['value']
 
     # This shouldn't ever change.
-    if moira_mach_id != after_record['extattrs']['moira_mach_id']:
-        critical_log('Moira mach ID changed from %s to %s in an update, this cannot happen!' % (moira_mach_id, after_record['extattrs']['moira_mach_id']))
+    if moira_mach_id != after_record['extattrs']['moira_mach_id']['value']:
+        critical_log('Moira mach ID changed from %s to %s in an update, this cannot happen!' % (moira_mach_id, after_record['extattrs']['moira_mach_id']['value']))
         return
 
     mach_id_results = ib_search_by_mach_id(moira_mach_id)
@@ -270,10 +282,10 @@ def ib_update_host(before, after):
             ib_create_host(after)
         elif len(mach_id_results[view]) > 1:
             critical_log('Mutiple records exist for mach_id %s in view %s, should not happen!' % (moira_mach_id, view))
-        elif mach_id_results[view]['extattrs']['moira_managed'] != "1":
+        elif mach_id_results[view][0]['extattrs']['moira_managed']['value'] != 1:
             log('Record for mach_id %s in view %s is not managed by moira, not updating' % (moira_mach_id, view))
         else:
-            ib_ref = mach_id_results[view]['_ref']
+            ib_ref = mach_id_results[view][0]['_ref']
             ib_url = ib_base_url + '/' + ib_ref
             headers = { 'Content-Type' : 'application/json' }
 
@@ -282,7 +294,7 @@ def ib_update_host(before, after):
                 critical_log('Infoblox API call failed in ib_update_host for host %s view %s' % (after_record['name'], view))
                 continue
 
-            log('Updated host record for %s with mach_id in view %s: %s' % (after_record['name'], moira_mach_id, view, r.text))
+            log('Updated host record for %s with mach_id %s in view %s: %s' % (after_record['name'], moira_mach_id, view, r.text))
 
 def address_to_ib(record):
     ib_address_record = {
@@ -409,7 +421,7 @@ def ib_search_by_name_and_cname(name, alias):
     object = '/record:cname'
     ib_search_url = ib_base_url + object + ib_common_args
     headers = { 'Content-Type' : 'application/json' }
-    payload = { 'canonical' : name.lower(), 'name' : alias.lowe() }
+    payload = { 'canonical' : name.lower(), 'name' : alias.lower() }
 
     results_by_view = {}
     for view in views:
@@ -419,7 +431,7 @@ def ib_search_by_name_and_cname(name, alias):
                
     r = requests.get(ib_search_url, data=data, headers=headers, auth=(ib_user, ib_passwd))
     if r.status_code != requests.codes.ok:
-        critical_log('Infoblox API call failed in ib_search_by_name_and_cname for URL %s, payload %s with HTTP status: %s' % (ib_search_url, data, r.status_code))
+        critical_log('Infoblox API call failed in ib_search_by_name_and_cname for URL %s, payload %s with HTTP status %s: %s' % (ib_search_url, data, r.status_code, r.text))
         return { 'error' : r.text }
 
     response = r.json()
@@ -438,7 +450,7 @@ def ib_add_cname(record):
         return
 
     # canonical name should be non-mit.edu if we got here.
-    if canonical_for_name(ib_record['canonical']) == True:
+    if canonical_for_hostname(ib_record['canonical']) == True:
         critical_log('Request to create explicit CNAME record for %s. alias %s, should not happen!' % (ib_record['canonical'], ib_record['name']))
         return
 
@@ -452,14 +464,14 @@ def ib_add_cname(record):
 
             r = requests.post(ib_url, data=json.dumps(ib_record), headers=headers, auth=(ib_user, ib_passwd))
             if r.status_code != requests.codes.created:
-                critical_log('Infoblox API call failed in ib_add_cname for name %s alias %s view %s' % (ib_record['canonical'], ib_record['name'], view))
+                critical_log('Infoblox API call failed in ib_add_cname for name %s alias %s view %s with HTTP status %s: %s' % (ib_record['canonical'], ib_record['name'], view, r.status_code, r.text))
                 continue
 
             log('Created CNAME record for name %s alias %s in view %s: %s' % (ib_record['canonical'], ib_record['name'], view, r.text))
             
 def ib_add_host_alias(record):
     ib_record = { 'name' : record[1].lower() }
-    alias = record[0]
+    alias = record[0].lower()
     search_results = ib_search_by_name(ib_record['name'])
 
     if 'error' in search_results:
@@ -469,20 +481,27 @@ def ib_add_host_alias(record):
     for view in views:
         ib_record['view'] = view
         if len(search_results[view]) == 0:
-            critical_log('No host record exists for %s while adding alias %s, skipping' % (ib_record['name'], alias))
+            critical_log('No host record exists for %s in view %s while adding alias %s, skipping' % (ib_record['name'], view, alias))
+        elif len(search_results[view]) > 1:
+            critical_log('Multiple host records exist for %s in view %s while adding alias %s, should not happen!' % (ib_record['name'], view, alias))
         else:
+            # exactly one result
+            search_result = search_results[view][0]
+            
             aliases = [ alias ]
-            for item in search_results[view][0]['aliases']:
-                aliases.append(item)
+            if 'aliases' in search_result:
+                for item in search_result['aliases']:
+                    aliases.append(item)
+
             ib_record['aliases'] = aliases
 
-            ib_ref = search_results[view][0]['_ref']
+            ib_ref = search_result['_ref']
             ib_url = ib_base_url + '/' + ib_ref
             headers = { 'Content-Type' : 'application/json' }
 
             r = requests.put(ib_url, data=json.dumps(ib_record), headers=headers, auth=(ib_user, ib_passwd))
             if r.status_code != requests.codes.ok:
-                critical_log('Infoblox API call failed in ib_add_host_alias for name %s alias %s view %s with HTTP return %s' % (ib_record['name'], alias, view, str(r.status_code)))
+                critical_log('Infoblox API call failed in ib_add_host_alias for name %s alias %s view %s with HTTP return %s: %s' % (ib_record['name'], alias, view, r.status_code, r.text))
                 continue
     
             log('Added alias %s to host %s in view %s: %s' % (alias, ib_record['name'], view, r.text))
@@ -507,26 +526,23 @@ def ib_add_host_duid(record):
         return
 
     for view in views:
+        if view == 'external':
+            continue
         ib_record['view'] = view
         if len(search_results[view]) == 0:
             critical_log('No host record exists for %s while adding %s %s in view %s, skipping' % (ib_record['name'], id_type, id_value, view))
         elif len(search_results[view]) > 1:
             critical_log('Multiple host records exist for %s while adding %s %s in view %s, should not happen' % (ib_record['name'], id_type, id_value, view))
         else:
-            # DUID is associated with ipv6addrs entry in host structure
-            if 'ipv6addrs' in search_results[view][0]:
-                ipv6addrs = search_results[view][0]['ipv6addrs']
-            else:
-                ipv6addrs = []
-                ipv6addrs[0] = {} 
+            search_result = search_results[view][0]
+
+            ipv6addrs = [ { 'ipv6addr' : search_result['ipv6addrs'][0]['ipv6addr'] } ]
             ipv6addrs[0]['duid'] = id_value
             ipv6addrs[0]['configure_for_dhcp'] = True
-            if not 'ipv6addr' in ipv6addrs[0]:
-                ipv6addrs[0]['ipv6addr'] = 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'
 
             ib_record['ipv6addrs'] = ipv6addrs
 
-            ib_ref = search_results[view][0]['_ref']
+            ib_ref = search_result['_ref']
             ib_url = ib_base_url + '/' + ib_ref
             headers = { 'Content-Type' : 'application/json' }
 
@@ -540,7 +556,7 @@ def ib_add_host_duid(record):
 def ib_add_host_mac(record):
     ib_record = { 'name' : record[0].lower() }
     id_type = record[1]
-    id_value = record[2]
+    id_value = str(EUI(record[2], dialect=netaddr.mac_unix_expanded))
     search_results = ib_search_by_name(ib_record['name'])
 
     if 'error' in search_results:
@@ -548,38 +564,45 @@ def ib_add_host_mac(record):
         return
 
     for view in views:
+        if view == 'external':
+            continue
         ib_record['view'] = view
         if len(search_results[view]) == 0:
             critical_log('No host record exists for %s while adding %s %s in view %s, skipping' % (ib_record['name'], id_type, id_value, view))
         elif len(search_results[view]) > 1:
             critical_log('Multiple host records exist for %s while adding %s %s in view %s, should not happen' % (ib_record['name'], id_type, id_value, view))
         else:
-            # MAC address associated with ipv4addrs entry in host structure
-            if 'ipv4addrs' in search_results[view][0]:
-                ipv4addrs = search_results[view][0]['ipv4addrs']
-            else:
-                ipv4addrs = []
-                ipv4addrs[0] = {}
+            search_result = search_results[view][0]
+
+            # preserve existing IP address
+            ipv4addrs = [ { 'ipv4addr' : search_result['ipv4addrs'][0]['ipv4addr'] } ]
             ipv4addrs[0]['mac'] = id_value
             ipv4addrs[0]['configure_for_dhcp'] = True
-            if not 'ipv4addr' in ipv4addrs[0]:
-                ipv4addrs[0]['ipv4addr'] = '255.255.255.255'
 
             ib_record['ipv4addrs'] = ipv4addrs
 
-            ib_ref = search_results[view][0]['_ref']
+            ib_ref = search_result['_ref']
             ib_url = ib_base_url + '/' + ib_ref
             headers = { 'Content-Type' : 'application/json' }
 
             r = requests.put(ib_url, data=json.dumps(ib_record), headers=headers, auth=(ib_user, ib_passwd))
             if r.status_code != requests.codes.ok:
-                critical_log('Infoblox AP call failed in ib_add_host_mac for name %s %s %s in view %s' % (ib_record['name'], id_type, id_value, view))
+                critical_log('Infoblox API call failed in ib_add_host_mac for name %s %s %s in view %s with HTTP return %s: %s' % (ib_record['name'], id_type, id_value, view, r.status_code, r.text))
                 continue
 
             log('Added %s %s to host %s in view %s: %s' % (id_type, id_value, ib_record['name'], view, r.text))
 
 def ib_add_host_ipv4_address(record):
     ib_record = { 'name' : record[0].lower() }
+
+    ttl = int(record[4])
+    if ttl == default_ttl:
+        ib_record['use_ttl'] = False
+        ib_record['ttl'] = default_ttl
+    else:
+        ib_record['use_ttl'] = True
+        ib_record['ttl'] = ttl
+
     address = record[3]
     search_results = ib_search_by_name(ib_record['name'])
 
@@ -594,30 +617,43 @@ def ib_add_host_ipv4_address(record):
             continue
 
         if len(search_results[view]) == 0:
-            critical_log('No host record exists for %s while adding IPv4 address %s, skipping' % (ib_record['name'], address))
+            critical_log('No host record exists for %s in view %s while adding IPv4 address %s, skipping' % (ib_record['name'], view, address))
+        elif len(search_results[view]) > 1:
+            critical_log('Multiple host records exist for %s in view %s while adding IPv4 address %s, should not happen!' % (ib_record['name'], view, address))            
         else:
-            if 'ipv4addrs' in search_results[view][0]:
-                ipv4addrs = search_results[view][0]['ipv4addrs']
-            else:
-                ipv4addrs = []
-                ipv4addrs[0] = {}
-            ipv4addrs[0]['ipv4addr'] = address
+            # exactly one result, as expected.
+            search_result = search_results[view][0]
+
+            ipv4addrs = [ { 'ipv4addr' : address } ]
+            # preserve MAC address if assigned.
+            if 'mac' in search_result['ipv4addrs'][0]:
+                ipv4addrs[0]['mac'] = search_result['ipv4addrs'][0]['mac']
+                ipv4addrs[0]['configure_for_dhcp'] = True
 
             ib_record['ipv4addrs'] = ipv4addrs
 
-            ib_ref = search_results[view][0]['_ref']
+            ib_ref = search_result['_ref']
             ib_url = ib_base_url + '/' + ib_ref
             headers = { 'Content-Type' : 'application/json' }
 
             r = requests.put(ib_url, data=json.dumps(ib_record), headers=headers, auth=(ib_user, ib_passwd))
             if r.status_code != requests.codes.ok:
-                critical_log('Infoblox API call failed in ib_add_host_ipv4_address for name %s address %s view %s with HTTP return %s' % (ib_record['name'], address, view, r.status_code))
+                critical_log('Infoblox API call failed in ib_add_host_ipv4_address for name %s address %s view %s with HTTP return %s: %s' % (ib_record['name'], address, view, r.status_code, r.text))
                 continue
 
             log('Added IPv4 address %s to host %s in view %s: %s' % (address, ib_record['name'], view, r.text))
 
 def ib_add_host_ipv6_address(record):
-    ib_record = { 'name' : record[0] }
+    ib_record = { 'name' : record[0].lower() }
+
+    ttl = int(record[4])
+    if ttl == default_ttl:
+        ib_record['use_ttl'] = False
+        ib_record['ttl'] = default_ttl
+    else:
+        ib_record['use_ttl'] = True
+        ib_record['ttl'] = ttl
+
     address = record[3]
     search_results = ib_search_by_name(ib_record['name'])
 
@@ -628,28 +664,32 @@ def ib_add_host_ipv6_address(record):
     for view in views:
         ib_record['view'] = view
         # No private addresses in external view
-        if view == 'external' and IPAddress(address).is_private == True:
+        if view == 'external' and IPAddress(address).is_private() == True:
             continue
 
         if len(search_results[view]) == 0:
-            critical_log('No host record exists for %s while adding IPv6 address %s, skipping' % (ib_record['name']. address))
+            critical_log('No host record exists for %s in view %s while adding IPv6 address %s, skipping' % (ib_record['name'], view, address))
+        elif len(search_results[view]) > 1:
+            critical_log('Multiple host records exist for %s in view %s while adding IPv6 address %s, should not happen!' % (ib_record['name'], view, address))
         else:
-            if 'ipv6addrs' in search_results[view][0]:
-                ipv6addrs = search_results[view][0]['ipv6addrs']
-            else:
-                ipv6addrs = []
-                ipv6addrs[0] = {}
-            ipv6addrs[0]['ipv6addr'] = address
+            # exactly one result, as expected.
+            search_result = search_results[view][0]
+
+            ipv6addrs = [ { 'ipv6addr' : address } ]
+            # preserve DUID if assigned.
+            if 'ipv6addrs' in search_result and 'duid' in search_result['ipv6addrs'][0]:
+                ipv6addrs[0]['duid'] = search_result['ipv6addrs'][0]['duid']
+                ipv6addrs[0]['configure_for_dhcp'] = True
 
             ib_record['ipv6addrs'] = ipv6addrs
 
-            ib_ref = search_results[view][0]['_ref']
+            ib_ref = search_result['_ref']
             ib_url = ib_base_url + '/' + ib_ref
             headers = { 'Content-Type' : 'application/json' }
 
             r = requests.put(ib_url, data=json.dumps(ib_record), headers=headers, auth=(ib_user, ib_passwd))
             if r.status_code != requests.codes.ok:
-                critical_log('Infoblox API call failed in ib_add_host_ipv6_address for name %s address %s view %s with HTTP return %s' % (ib_record['name'], address, view, r.status_code))
+                critical_log('Infoblox API call failed in ib_add_host_ipv6_address for name %s address %s view %s with HTTP return %s: %s' % (ib_record['name'], address, view, r.status_code, r.text))
                 continue
 
             log('Added IPv6 address %s to host %s in view %s: %s' % (address, ib_record['name'], view, r.text))
@@ -661,16 +701,31 @@ def ib_add_hostrecord(record):
         return
     elif rr_type == 'MX':
         ib_record = mx_to_ib(record)
+        if 'error' in ib_record:
+            critical_log(ib_record['error'])
+            return
+
         primary_rr_value = ib_record['mail_exchanger']
         object = '/record:mx'
+
     elif rr_type == 'SRV':
         ib_record = srv_to_ib(record)
+        if 'error' in ib_record:
+            critical_log(ib_record['error'])
+            return
+        
         primary_rr_value = ib_record['target']
         object = '/record:srv'
+
     elif rr_type == 'TXT':
         ib_record = txt_to_ib(record)
+        if 'error' in ib_record:
+            critical_log(ib_record['error'])
+            return
+
         primary_rr_value = ib_record['text']
         object = '/record:txt'
+
     else:
         critical_log('Unexpected RR type %s in ib_add_hostrecord' % (rr_type))
         return
@@ -691,10 +746,10 @@ def ib_add_hostrecord(record):
 
             r = requests.post(ib_url, data=json.dumps(ib_record), headers=headers, auth=(ib_user, ib_passwd))
             if r.status_code != requests.codes.created:
-                critical_log('Infoblox API call failed creating RR type %s for name %s view %s' % (rr_type, ib_record['name'], view))
+                critical_log('Infoblox API call failed creating RR type %s for name %s view %s with HTTP return %s: %s' % (rr_type, ib_record['name'], view, r.status_code, r.text))
                 continue
 
-            log('Created %s record for name %s value %s in view %s: %s' % (rr_type, ib_record['name'], primary_rr_value, view))
+            log('Created %s record for name %s value %s in view %s: %s' % (rr_type, ib_record['name'], primary_rr_value, view, r.text))
 
 def ib_search_hostrecord(name, rr_type, value):
     if rr_type == 'MX':
@@ -738,7 +793,7 @@ def ns_to_ib(record):
             'address' : address[3]
         }
 
-        ib.delegate_to.append(ib_delegate_element)
+        ib_delegate_to.append(ib_delegate_element)
 
     ib_ns_record = {
         'fqdn' : record[0].lower(),
@@ -790,46 +845,55 @@ def ib_add_delegated_zone(record):
     for view in views:
         if view == 'external':
             # Make a copy of ib_record['delegated_to'] since we might modify it.
-            delegated_to = list(ib_record['delegated_to'])
+            delegated_to = list(ib_record['delegate_to'])
             # Don't put private addresses in the external zone.
             for item in delegated_to:
                 if IPAddress(item['address']).is_private() == True:
                     delegated_to[:] = [ d for d in delegated_to if d.get('address') != item['address']]
-            ib_record['delegated_to'] = delegated_to
+            ib_record['delegate_to'] = delegated_to
 
         ib_record['view'] = view
 
         if len(search_results[view]) > 0:
-            for item in search_results[view]['delegated_to']:
-                ib_record['delegated_to'].append(item)
+            # zone already exists
+            for item in search_results[view]['delegate_to']:
+                if item not in ib_record['delegate_to']:
+                    ib_record['delegate_to'].append(item)
 
-            ib_ref = search_results[view][0]['_ref']
+            # Infoblox doesn't allow this for updates, even if it didn't change.
+            if 'fqdn' in ib_record:
+                del ib_record['fqdn']
+
+            ib_ref = search_results[view]['_ref']
             ib_url = ib_base_url + '/' + ib_ref
             headers = { 'Content-Type' : 'application/json' }
 
-            r = requests.put(ib_url, data=json.dumps(ib_record), headers=headers, auth=(ib_user, ib_passwd))
+            data = json.dumps(ib_record)
+            r = requests.put(ib_url, data=data, headers=headers, auth=(ib_user, ib_passwd))
 
             if r.status_code != requests.codes.ok:
-                critical_log('Infoblox API call failed updating delegated zone %s in view %s' % (ib_record['fqdn'], view))
+                critical_log('Infoblox API call failed updating delegated zone %s in view %s with HTTP return %s: %s' % (record[0].lower(), view, r.status_code, r.text))
                 continue
 
-            log('Updated delegated zone %s with additional delegation to %s in view %s: %s' % (ib_record['fqdn'], ib_record['delegated_to'][0]['name'], view, r.text))
+            log('Updated delegated zone %s with additional delegation to %s in view %s: %s' % (record[0].lower(), ib_record['delegate_to'][0]['name'], view, r.text))
         else:
+            # empty search results, create.
             ib_url = ib_base_url + '/zone_delegated' + ib_common_args
             headers = { 'Content-Type' : 'application/json' }
 
-            r = requests.post(ib_url, data=json.dumps(ib_record), headers=headers, auth=(ib_user, ib_passwd))
+            data = json.dumps(ib_record)
+            r = requests.post(ib_url, data=data, headers=headers, auth=(ib_user, ib_passwd))
 
             if r.status_code != requests.codes.created:
-                critical_log('Infoblox API call failed creating delegated zone %s in view %s' % (ib_record['fqdn'], view))
+                critical_log('Infoblox API call failed creating delegated zone %s in view %s with HTTP return %s: %s' % (ib_record['fqdn'], view, r.status_code, r.text))
                 continue
 
-            log('Created delegated zone %s with delegation to %s in view %s: %s' % (ib_record['fqdn'], ib_record['delegated_to'][0]['name'], view, r.text))
+            log('Created delegated zone %s with delegation to %s in view %s: %s' % (ib_record['fqdn'], ib_record['delegate_to'][0]['name'], view, r.text))
 
 def txt_to_ib(record):
     ib_txt_record = {
         'name' : record[0].lower(),
-        'text' : record[2].lower(),
+        'text' : '"' + record[2].lower() + '"',
         'disable' : False
         }
 
@@ -849,10 +913,12 @@ def srv_to_ib(record):
         'disable' : False
         }
 
+    if len(record[2].split()) != 4:
+        return { 'error' : 'Could not parse SRV record in srv_to_ib' }
+
     (priority, weight, port, target) = record[2].split()
     if not priority or not weight or not port or not target:
-        critical_log('Could not parse SRV record value in srv_to_ib')
-        return
+        return { 'error' : 'Could not parse SRV record in srv_to_ib' }
     ib_srv_record['priority'] = int(priority)
     ib_srv_record['weight'] = int(weight)
     ib_srv_record['port'] = int(port)
@@ -874,10 +940,12 @@ def mx_to_ib(record):
         'disable' : False
         }
 
+    if len(record[2].split()) != 2:
+        return { 'error' : 'Could not parse MX record value in mx_to_ib' }
+
     (weight, mxhost) = record[2].split()
     if not weight or not mxhost:
-        critical_log('Could not parse MX record value in mx_to_ib')
-        return
+        return { 'error' : 'Could not parse MX record value in mx_to_ib' }
     ib_mx_record['preference'] = int(weight)
     ib_mx_record['mail_exchanger'] = mxhost.lower()
 
@@ -961,8 +1029,8 @@ def ib_delete_cname(record):
             log('Deleted CNAME record for name %s alias %s in view %s: %s' % (ib_record['canonical'], ib_record['name'], view, r.text))
 
 def ib_delete_host_alias(record):
-    ib_record = { 'name' : record[1] }
-    alias = record[0]
+    ib_record = { 'name' : record[1].lower() }
+    alias = record[0].lower()
     search_results = ib_search_by_name(ib_record['name'])
 
     if 'error' in search_results:
@@ -972,18 +1040,22 @@ def ib_delete_host_alias(record):
     for view in views:
         ib_record['view'] = view
         if len(search_results[view]) == 0:
-            log('No host record exists for %s while deleting alias %s, skipping' % (ib_record['name'], alias))
+            log('No host record exists for %s in view %s while deleting alias %s, skipping' % (ib_record['name'], view, alias))
+        elif len(search_results[view]) > 1:
+            critical_log('Multiple host records exist for %s in view %s while deleting alias %s, should not happen!' % (ib_record['name'], view, alias))
         else:
-            aliases = [ x for x in search_results[view][0]['aliases'] if x != alias]
+            search_result = search_results[view][0]
+            
+            aliases = [ x for x in search_result['aliases'] if x != alias]
             ib_record['aliases'] = aliases
 
-            ib_ref = search_results[view][0]['_ref']
+            ib_ref = search_result['_ref']
             ib_url = ib_base_url + '/' + ib_ref
             headers = { 'Content-Type' : 'application/json' }
 
             r = requests.put(ib_url, data=json.dumps(ib_record), headers=headers, auth=(ib_user, ib_passwd))
             if r.status_code != requests.codes.ok:
-                critical_log('Infoblox API call failed in ib_delete_host_alias for name %s alias %s view %s' % (ib_record['name'], alias, view))
+                critical_log('Infoblox API call failed in ib_delete_host_alias for name %s alias %s view %s with HTTP return %s: %s' % (ib_record['name'], alias, view, r.status_code, r.text))
                 continue
 
             log('Deleted alias %s from host %s in view %s: %s' % (alias, ib_record['name'], view, r.text))
@@ -1000,7 +1072,8 @@ def ib_delete_host_identifier(record):
 def ib_delete_host_duid(record):
     ib_record = { 'name' : record[0].lower() }
     id_type = record[1]
-    id_value = record[2]
+    # Infoblox formats these things like MAC addresses, but longer, for some reason.
+    id_value = ':'.join(s.encode('hex') for s in record[2].decode('hex'))
     search_results = ib_search_by_name(ib_record['name'])
 
     if 'error' in search_results:
@@ -1008,27 +1081,35 @@ def ib_delete_host_duid(record):
         return
 
     for view in views:
+        if view == 'external':
+            continue
         ib_record['view'] = view
         if len(search_results[view]) == 0:
             log('No host record exists for %s while deleting %s %s in view %s, skipping' % (ib_record['name'], id_type, id_value, view))
+        elif len(search_results[view]) > 1:
+            critical_log('Multiple host records exist for %s in view %s while deleting %s %s, should not happen!' % (ib_record['name'], view, id_type, id_value))
         else:
-            ipv6addrs = []
-            for x in search_results[view][0]['ipv6addrs']:
-                if x['duid'] != id_value:
-                    ipv6addrs.append(x)
-                elif x['ipv6addr'] != 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff':
-                    del x['duid']
-                    ipv6addrs.append(x)
+            search_result = search_results[view][0]
+
+            ipv6addrs = [ { 'ipv6addr' : search_result['ipv6addrs'][0]['ipv6addr'] } ]
+
+            if 'duid' in search_result['ipv6addrs'][0]:
+                if search_result['ipv6addrs'][0]['duid'] == id_value:
+                    ipv6addrs[0]['duid'] = ''
+                    ipv6addrs[0]['configure_for_dhcp'] = False
+                else:
+                    critical_log('DUID %s does not match attempted delete of %s for host %s in view %s, not deleting' % (search_result['ipv6addrs'][0]['duid'], id_value, ib_record['name'], view))
+                    continue
 
             ib_record['ipv6addrs'] = ipv6addrs
 
-            ib_ref = search_results[view][0]['_ref']
+            ib_ref = search_result['_ref']
             ib_url = ib_base_url + '/' + ib_ref
             headers = { 'Content-Type' : 'application/json' }
 
             r = requests.put(ib_url, data=json.dumps(ib_record), headers=headers, auth=(ib_user, ib_passwd))
             if r.status_code != requests.codes.ok:
-                critical_log('Infoblox API call failed in ib_delete_host_duid for name %s %s %s view %s' % (ib_record['name'], id_type, id_value, view))
+                critical_log('Infoblox API call failed in ib_delete_host_duid for name %s %s %s view %s with HTTP return %s: %s' % (ib_record['name'], id_type, id_value, view, r.status_code, r.text))
                 continue
 
             log('Deleted %s %s for host %s in view %s: %s' % (id_type, id_value, ib_record['name'], view, r.text))
@@ -1036,7 +1117,7 @@ def ib_delete_host_duid(record):
 def ib_delete_host_mac(record):
     ib_record = { 'name' : record[0].lower() }
     id_type = record[1]
-    id_value = record[2]
+    id_value = str(EUI(record[2], dialect=netaddr.mac_unix_expanded))
     search_results = ib_search_by_name(ib_record['name'])
 
     if 'error' in search_results:
@@ -1044,27 +1125,36 @@ def ib_delete_host_mac(record):
         return
 
     for view in views:
+        if view == 'external':
+            continue
         ib_record['view'] = view
         if len(search_results[view]) == 0:
             log('No host record exists for %s while deleting %s %s in view %s, skipping' % (ib_record['name'], id_type, id_value, view))
+        elif len(search_results[view]) > 1:
+            critical_log('Multiple host records exist for %s in view %s while deleting %s %s, should not happen!' % (ib_record['name'],  view, id_type, id_value))
         else:
-            ipv4addrs = []
-            for x in search_results[view][0]['ipv4addrs']:
-                if x['mac'] != id_value:
-                    ipv4addrs.append(x)
-                elif x['ipv4addr'] != '255.255.255.255':
-                    del x['mac']
-                    ipv4addrs.append(x)
+            # exactly one result
+            search_result = search_results[view][0]
+
+            ipv4addrs = [ { 'ipv4addr' : search_result['ipv4addrs'][0]['ipv4addr'] } ]
+
+            if 'mac' in search_result['ipv4addrs'][0]:
+                if search_result['ipv4addrs'][0]['mac'] == id_value:
+                    ipv4addrs[0]['mac'] = ''
+                    ipv4addrs[0]['configure_for_dhcp'] = False
+                else:
+                    critical_log('MAC address %s does not match attempted delete of %s for host %s in view %s, not deleting' % (search_result['ipv4addrs'][0]['mac'], id_value, ib_record['name'], view))
+                    continue
 
             ib_record['ipv4addrs'] = ipv4addrs
 
-            ib_ref = search_results[view][0]['_ref']
+            ib_ref = search_result['_ref']
             ib_url = ib_base_url + '/' + ib_ref
             headers = { 'Content-Type' : 'application/json' }
 
             r = requests.put(ib_url, data=json.dumps(ib_record), headers=headers, auth=(ib_user, ib_passwd))
             if r.status_code != requests.codes.ok:
-                critical_log('Infoblox API call failed in ib_delete_host_mac for name %s %s %s view %s' % (ib_record['name'], id_type, id_value, view))
+                critical_log('Infoblox API call failed in ib_delete_host_mac for name %s %s %s view %s with HTTP return %s: %s' % (ib_record['name'], id_type, id_value, view, r.status_code, r.text))
                 continue
 
             log('Deleted %s %s for host %s in view %s: %s' % (id_type, id_value, ib_record['name'], view, r.text))
@@ -1081,25 +1171,28 @@ def ib_delete_host_ipv4_address(record):
     for view in views:
         ib_record['view'] = view
         if len(search_results[view]) == 0:
-            log('No host record exists for %s while deleting IPv4 address %s, skipping' % (ib_record['name'], address))
+            log('No host record exists for %s in view %s while deleting IPv4 address %s, skipping' % (ib_record['name'], view, address))
+        elif len(search_results[view]) > 1:
+            critical_log('Multiple host records exist for %s in view %s while deleting IPv4 address %s, should not happen!' % (ib_record['name'], view, address))
         else:
-            ipv4addrs = []
-            for x in search_results[view][0]['ipv4addrs']:
-                if x['ipv4addr'] != address:
-                    ipv4addrs.append(x)
-                elif 'mac' in x:
-                    x['ipv4addr'] = '255.255.255.255'
-                    ipv4addrs.append(x)
+            # exactly one result, as expected.
+            search_result = search_results[view][0]
+
+            ipv4addrs = [ { 'ipv4addr' : '0.0.0.0' } ]
+            # preserve MAC address if assigned.
+            if 'mac' in search_result['ipv4addrs'][0]:
+                ipv4addrs[0]['mac'] = search_result['ipv4addrs'][0]['mac']
+                ipv4addrs[0]['configure_for_dhcp'] = True
 
             ib_record['ipv4addrs'] = ipv4addrs
 
-            ib_ref = search_results[view][0]['_ref']
+            ib_ref = search_result['_ref']
             ib_url = ib_base_url + '/' + ib_ref
             headers = { 'Content-Type' : 'application/json' }
 
             r = requests.put(ib_url, data=json.dumps(ib_record), headers=headers, auth=(ib_user, ib_passwd))
             if r.status_code != requests.codes.ok:
-                critical_log('Infoblox API call failed in ib_delete_host_ipv4_address for name %s address %s view %s' % (ib_record['name'], address, view))
+                critical_log('Infoblox API call failed in ib_delete_host_ipv4_address for name %s address %s view %s with HTTP return %s: %s' % (ib_record['name'], address, view, r.status_code, r.text))
                 continue
 
             log('Deleted IPv4 address %s for host %s in view %s: %s' % (address, ib_record['name'], view, r.text))
@@ -1116,19 +1209,22 @@ def ib_delete_host_ipv6_address(record):
     for view in views:
         ib_record['view'] = view
         if len(search_results[view]) == 0:
-            log('No host record exists for %s while deleting IPv6 address %s, skipping' % (ib_record['name'], address))
+            log('No host record exists for %s in view %s while deleting IPv6 address %s, skipping' % (ib_record['name'], view, address))
+        elif len(search_results[view]) > 1:
+            critical_log('Multiple host records exist for %s in view %s while deleting IPv6 address %s, should not happen!' % (ib_record['name'], view, address))
         else:
-            ipv6addrs = []
-            for x in search_results[view][0]['ipv6addrs']:
-                if x['ipv6addr'] != address:
-                    ipv6addrs.append(x)
-                elif 'duid' in x:
-                    x['ipv6addr'] = 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'
-                    ipv6addrs.append(x)
-
+            # exactly one result, as expected.
+            search_result = search_results[view][0]
+
+            ipv6addrs = [ ]
+            # preserve DUID if assigned.
+            if 'ipv6addrs' in search_result and 'duid' in search_result['ipv6addrs'][0]:
+                ipv6addrs[0]['duid'] = search_result['ipv6addrs'][0]['duid']
+                ipv6addrs[0]['configure_for_dhcp'] = True
+                          
             ib_record['ipv6addrs'] = ipv6addrs
 
-            ib_ref = search_results[view][0]['_ref']
+            ib_ref = search_result['_ref']
             ib_url = ib_base_url + '/' + ib_ref
             headers = { 'Content-Type' : 'application/json' }
 
@@ -1146,12 +1242,20 @@ def ib_delete_hostrecord(record):
         return
     elif rr_type == 'MX':
         ib_record = mx_to_ib(record)
+        if 'error' in ib_record:
+            critical_log(ib_record['error'])
+            return
         primary_rr_value = ib_record['mail_exchanger']
     elif rr_type == 'SRV':
         ib_record = srv_to_ib(record)
+        if 'error' in ib_record:
+            critical_log(ib_record['error'])
+            return
         primary_rr_value = ib_record['target']
     elif rr_type == 'TXT':
         ib_record = txt_to_ib(record)
+        if 'error' in ib_record:
+            critical_log(ib_record['error'])
         primary_rr_value = ib_record['text']
     else:
         critical_log('Unexpected RR type %s in ib_delete_hostrecord' % (rr_type))
@@ -1165,18 +1269,23 @@ def ib_delete_hostrecord(record):
 
     for view in views:
         if len(search_results[view]) == 0:
+            if rr_type == 'TXT':
+                # search is stupid
+                search_results = ib_search_hostrecord(ib_record['name'], rr_type, primary_rr_value.strip('"'))
+        
+        if len(search_results[view]) == 0:
             log('No %s record exists for name %s value %s in view %s, not deleting' % (rr_type, ib_record['name'], primary_rr_value, view))
         else:
-            ib_ref = search_results[view][0]['_ref']
+            ib_ref = search_results[view]['_ref']
             ib_url = ib_base_url + '/' + ib_ref
             headers = { 'Content-Type' : 'application/json' }
 
             r = requests.delete(ib_url, headers=headers, auth=(ib_user, ib_passwd))
             if r.status_code != requests.codes.ok:
-                critical_log('Infoblox API call failed in ib_delete_hostrecord for RR type %s name %s view %s' % (rr_type, ib_record['name'], view))
+                critical_log('Infoblox API call failed in ib_delete_hostrecord for RR type %s name %s view %s with HTTP return %s: %s' % (rr_type, ib_record['name'], view, r.status_code, r.text))
                 continue
 
-            log('Deleted %s record for name %s value %s in view %s: %s' % (rr_type, ib_record['name'], primary_rr_value, view))
+            log('Deleted %s record for name %s value %s in view %s: %s' % (rr_type, ib_record['name'], primary_rr_value, view, r.text))
 
 def ib_delete_delegated_zone(record):
     ib_record = ns_to_ib(record)
@@ -1186,28 +1295,57 @@ def ib_delete_delegated_zone(record):
         log('ib_search_delegated_zone returned error, aborting')
         return
 
+    deleted_delegation = list(ib_record['delegate_to'])
+
     for view in views:
         if len(search_results[view]) == 0:
             log('No delegated zone record for %s exists in view %s, not deleting' % (ib_record['fqdn'], view))
         else:
-            ib_ref = search_results[view][0]['_ref']
+            ib_ref = search_results[view]['_ref']
             ib_url = ib_base_url + '/' + ib_ref
             headers = { 'Content-Type' : 'application/json' }
-            delegated_to = [ x for x in search_results[view][0]['delegated_to'] if x not in ib_record['delegated_to']]
-            if len(delegated_to) == 0:
+            new_delegated_to = [ x for x in search_results[view]['delegate_to'] if x not in deleted_delegation ]
+
+            if len(new_delegated_to) == 0:
                 r = requests.delete(ib_url, headers=headers, auth=(ib_user, ib_passwd))
                 if r.status_code != requests.codes.ok:
-                    critical_log('Infoblox API call failed for DELETE in ib_delete_delegated_zone of zone %s view %s' % (ib_record['fqdn'], view))
+                    critical_log('Infoblox API call failed for DELETE in ib_delete_delegated_zone of zone %s view %s with HTTP return %s: %s' % (record[0].lower(), view, r.status_code, r.text))
                     continue
-                log('Deleted delegated zone %s in view %s: %s' % (ib_record['fqdn'], view, r.text))
+                log('Deleted delegated zone %s in view %s: %s' % (record[0].lower(), view, r.text))
+
+                # Infoblox nuked our host record when we did this, but Moira might still expect it to exist.  Check and put it back if so.
+                # This case should only come up in a 'real' incremental, where we have the mach_id.
+                if len(record) == 5:
+                    name_results = ib_search_by_name(record[0].lower())
+
+                    if 'error' in name_results:
+                        log('ib_search_by_mach_id returned error, aborting')
+                        return
+
+                    if len(name_results[view]) > 0:
+                        # host record still exists for some reason, which is weird.
+                        critical_log('Infoblox host record for %s in view %s still exists after delegated zone deletion, should not happen!' % (record[0].lower(), view))
+                        return
+                    else:
+                        # No record exists, make one.
+                        hostinfo, = get_moira_host(record[0])
+                        # Reshuffle output of get_host into incremental argv, inserting mach_id.
+                        create_record = [ hostinfo[0], record[4], hostinfo[1], hostinfo[2], hostinfo[3], hostinfo[4], hostinfo[5], hostinfo[6], hostinfo[7],
+                                          hostinfo[9], hostinfo[11], hostinfo[12], hostinfo[13], hostinfo[14] ]
+                        # Since we're already in our view loop, only do this for the current view
+                        ib_create_host(create_record, myviews=[view])
             else:
-                ib_record['delegated_to'] = delegated_to
+                # Infoblox doesn't allow this for updates, even if it hasn't changed.
+                if 'fqdn' in ib_record:
+                    del ib_record['fqdn']
+                
+                ib_record['delegate_to'] = new_delegated_to
                 r = requests.put(ib_url, data=json.dumps(ib_record), headers=headers, auth=(ib_user, ib_passwd))
                 if r.status_code != requests.codes.ok:
-                    critical_log('Infoblox API call failed for PUT in ib_delete_delegated_zone of zone %s view %s' % (ib_record['fqdn'], view))
+                    critical_log('Infoblox API call failed for PUT in ib_delete_delegated_zone of zone %s view %s with HTTP return %s: %s' % (record[0].lower(), view, r.status_code, r.text))
                     continue
 
-                log('Updated delegated zone %s to remove delegation to %s in view %s: %s' % (ib_record['fqdn'], record[2], view, r.text))
+                log('Updated delegated zone %s to remove delegation to %s in view %s: %s' % (record[0].lower(), record[2], view, r.text))
         
 def ib_set_aaaa_record_ttl(record):
     ib_record = address_to_ib(record)
@@ -1218,7 +1356,6 @@ def ib_set_aaaa_record_ttl(record):
         return
 
     for view in views:
-        ib_record['view'] = view
         if len(search_results[view]) == 0:
             critical_log('No AAAA record exists for name %s address %s in view %s while updating TTL' % (ib_record['name'], ib_record['ipv6addr'], view))
         elif len(search_results[view]) > 1:
@@ -1230,7 +1367,7 @@ def ib_set_aaaa_record_ttl(record):
 
             r = requests.put(ib_url, data=json.dumps(ib_record), headers=headers, auth=(ib_user, ib_passwd))
             if r.status_code != requests.codes.ok:
-                critical_log('Infoblox API call failed in ib_set_aaaa_record_ttl for name %s address %s view %s' % (ib_record['name'], ib_record['ipv6addr'], view))
+                critical_log('Infoblox API call failed in ib_set_aaaa_record_ttl for name %s address %s view %s with HTTP return %s: %s' % (ib_record['name'], ib_record['ipv6addr'], view, r.status_code, r.text))
                 continue
 
             log('Updated TTL for AAAA record %s address %s in view %s to %s' % (ib_record['name'], ib_record['ipv6addr'], view, ib_record['ttl']))
@@ -1244,7 +1381,6 @@ def ib_set_a_record_ttl(record):
         return
 
     for view in views:
-        ib_record['view'] = view
         if len(search_results[view]) == 0:
             critical_log('No A record exists for name %s address %s in view %s while updating TTL' % (ib_record['name'], ib_record['ipv4addr'], view))
         elif len(search_results[view]) > 1:
@@ -1256,7 +1392,7 @@ def ib_set_a_record_ttl(record):
 
             r = requests.put(ib_url, data=json.dumps(ib_record), headers=headers, auth=(ib_user, ib_passwd))
             if r.status_code != requests.codes.ok:
-                critical_log('Infoblox API call failed in ib_set_a_record_ttl for name %s address %s view %s' % (ib_record['name'], ib_record['ipv4addr'], view))
+                critical_log('Infoblox API call failed in ib_set_a_record_ttl for name %s address %s view %s with HTTP return %s: %s' % (ib_record['name'], ib_record['ipv4addr'], view, r.status_code, r.text))
                 continue
 
             log('Updated TTL for A record %s address %s in view %s to %s' % (ib_record['name'], ib_record['ipv4addr'], view, ib_record['ttl']))
@@ -1317,16 +1453,14 @@ def ib_set_record_ttl(record):
         ib_record['view'] = view
         if len(search_results[view]) == 0:
             critical_log('No %s record exists for name %s value %s in view %s while setting TTL' % (rr_type, ib_record['name'], primary_rr_value, view))
-        elif len(search_results[view]) > 1:
-            critical_log('Multiple %s records exist for name %s value %s in view %s while setting TTL, should not happen' % (rr_type, ib_record['name'], primary_rr_value, view))
         else:
-            ib_ref = search_results[view][0]['_ref']
+            ib_ref = search_results[view]['_ref']
             ib_url = ib_base_url + '/' + ib_ref
             headers = { 'Content-Type' : 'application/json' }
 
             r = requests.put(ib_url, data=json.dumps(ib_record), headers=headers, auth=(ib_user, ib_passwd))
             if r.status_code != requests.codes.ok:
-                critical_log('Infoblox API call failed in ib_set_record_ttl for type %s name %s view %s' % (rr_type, ib_record['name'], view))
+                critical_log('Infoblox API call failed in ib_set_record_ttl for type %s name %s view %s with HTTP return %s: %s' % (rr_type, ib_record['name'], view, r.status_code, r.text))
                 continue
 
             log('Updated TTL for %s record %s value %s in view %s to %s' % (rr_type, ib_record['name'], primary_rr_value, view, ib_record['ttl']))
@@ -1590,13 +1724,23 @@ def do_hostaddress(before, after):
                         ib_set_aaaa_record_ttl(after)
         else:
             # ptr bit changed.
-            if before_ptr == 0 and after_ptr == 1:
-                ib_delete_a_record(before)
-                ib_add_host_ipv4_address(after)
-            elif before_ptr == 1 and after_ptr == 0:
-                ib_delete_host_ipv4_address(before)
-                ib_add_a_record(after)
-            
+            status = int(get_moira_host_status(hostname))
+            if status == 1:
+                if before_ptr == 0 and after_ptr == 1:
+                    if addr_type == 'IPV4':
+                        ib_delete_a_record(before)
+                        ib_add_host_ipv4_address(after)
+                    elif addr_type == 'IPV6':
+                        ib_delete_aaaa_record(before)
+                        ib_add_host_ipv6_address(after)
+                elif before_ptr == 1 and after_ptr == 0:
+                    if addr_type == 'IPV4':
+                        ib_delete_host_ipv4_address(before)
+                        ib_add_a_record(after)
+                    elif addr_type == 'IPV6':
+                        ib_delete_host_ipv6_address(before)
+                        ib_add_aaaa_record(after)
+
 # Handle hostrecord incremental
 def do_hostrecord(before, after):
     # Add
@@ -1615,7 +1759,7 @@ def do_hostrecord(before, after):
         rr_type = before[1]
         if canonical_for_hostname(hostname) == True:
             if rr_type in rr_types:
-                ib_delete_hostrecord(after)
+                ib_delete_hostrecord(before)
             else:
                 log('received incremental for unsupported DNS RR type %s' % (rr_type))
     # Update - only thing that can change is TTL
@@ -1671,22 +1815,15 @@ while True:
     beforev = incrargv[3:3+beforec]
     afterv = incrargv[3+beforec:3+beforec+afterc]
 
-    log('processing an incremental')
-
     if table == 'machine':
-        log('calling do_machine')
         do_machine(beforev, afterv)
     elif table == 'hostalias':
-        log('calling do_hostalias')
         do_hostalias(beforev, afterv)
     elif table == 'hostaddress':
-        log('calling do_hostaddress')
         do_hostaddress(beforev, afterv)
     elif table == 'hostrecord':
-        log('calling do_hostrecord')
         do_hostrecord(beforev, afterv)
     elif table == 'machidentifiermap':
-        log('calling do_machidentifier')
         do_machidentifier(beforev, afterv)
     else:
         error = 'Incremental received on unexpected table %s' % table
diff --git a/moira/server/increment.pc b/moira/server/increment.pc
index 9cf6896..9e054b3 100644
--- a/moira/server/increment.pc
+++ b/moira/server/increment.pc
@@ -171,7 +171,7 @@ void incremental_before(enum tables table, char *qual, char **argv)
       strcpy(before[1], argv[1]);
       strcpy(before[2], argv[2]);
       EXEC SQL SELECT ttl INTO :ttl FROM hostrecord
-	WHERE mach_id = :id AND rr_type = :argv[1] AND rr_value = :argv[2];
+	WHERE mach_id = :id AND rr_type = :argv[1] AND rr_value = UPPER(:argv[2]);
       sprintf(before[3], "%d", ttl);
       sprintf(before[4], "%d", id);
       beforec = 5;
@@ -448,7 +448,7 @@ void incremental_after(enum tables table, char *qual, char **argv)
       strcpy(after[1], argv[1]);
       strcpy(after[2], argv[2]);
       EXEC SQL SELECT ttl INTO :ttl FROM hostrecord
-	WHERE mach_id = :id AND rr_type = :argv[1] AND rr_value = :argv[2];
+	WHERE mach_id = :id AND rr_type = :argv[1] AND rr_value = UPPER(:argv[2]);
       sprintf(after[3], "%d", ttl);
       sprintf(after[4], "%d", id);
       afterc = 5;
diff --git a/moira/server/qfollow.pc b/moira/server/qfollow.pc
index b377b0b..d4004b8 100644
--- a/moira/server/qfollow.pc
+++ b/moira/server/qfollow.pc
@@ -864,6 +864,14 @@ int followup_ahad(struct query *q, char *argv[], client *cl)
 	ha.mach_id = :mid AND ha.snet_id = s.snet_id AND s.addr_type = :addr_type;
       if (cnt == 0)
 	ptr = 1;
+      else
+	{
+	  /* TTL should match to what's set for existing addresses of this type */
+	  EXEC SQL SELECT ttl INTO :ttl FROM hostaddress ha, subnet s WHERE
+	    ha.mach_id = :mid AND ha.snet_id = s.snet_id AND s.addr_type = :addr_type;
+	  if (dbms_errno)
+	    return mr_errcode;
+	}
 
       /* Is this address in use by any other hosts with reverse resolution? */
       EXEC SQL SELECT COUNT(*) INTO :cnt FROM hostaddress WHERE
@@ -1290,8 +1298,8 @@ int followup_srrt(struct query *q, char *argv[], client *cl)
 int followup_sttl(struct query *q, char *argv[], client *cl)
 {
   EXEC SQL BEGIN DECLARE SECTION;
-  char *address;
-  int mid, ttl, has_ptr;
+  char *address, addr_type[SUBNET_ADDR_TYPE_SIZE];
+  int mid, sid, ttl, has_ptr;
   EXEC SQL END DECLARE SECTION;
   int status;
 
@@ -1300,7 +1308,7 @@ int followup_sttl(struct query *q, char *argv[], client *cl)
   ttl = atoi(argv[2]);
 
   /* Did we update a record where has_ptr is set to 1? */
-  EXEC SQL SELECT ptr INTO :has_ptr FROM hostaddress
+  EXEC SQL SELECT snet_id, ptr INTO :sid, :has_ptr FROM hostaddress
     WHERE mach_id = :mid AND address = :address;
   if (dbms_errno)
     return mr_errcode;
@@ -1314,6 +1322,19 @@ int followup_sttl(struct query *q, char *argv[], client *cl)
 	return mr_errcode;
     }
 
+  /* Keep TTLs for all addresses of the same type in sync. */
+  EXEC SQL SELECT addr_type INTO :addr_type FROM subnet
+    WHERE snet_id = :sid;
+  if (dbms_errno)
+    return mr_errcode;
+  strmove(addr_type, strtrim(addr_type));
+
+  EXEC SQL UPDATE hostaddress SET ttl = :ttl WHERE
+    mach_id = :mid AND address != :address AND snet_id
+    IN (SELECT snet_id FROM subnet WHERE addr_type = :addr_type);
+  if (dbms_errno)
+    return mr_errcode;
+ 
   status = set_mach_modtime_by_id(q, argv, cl);
   if (status)
     return status;
diff --git a/moira/server/qsetup.pc b/moira/server/qsetup.pc
index ee6488a..5ce2f80 100644
--- a/moira/server/qsetup.pc
+++ b/moira/server/qsetup.pc
@@ -1358,6 +1358,17 @@ int setup_ahst(struct query *q, char **argv, client *cl)
 	return MR_EXISTS;
     }
 
+  /* Don't allow renaming at all if you have NS records */
+  if (row == 1)
+    {
+      EXEC SQL SELECT COUNT(*) INTO :cnt FROM hostrecord
+	WHERE mach_id = :id;
+      if (dbms_errno)
+	return mr_errcode;
+      if (cnt != 0)
+	return MR_EXISTS;
+    }
+
   /*
    * If this is an update_host query, we're done.
    */
@@ -1398,6 +1409,14 @@ int setup_ahal(struct query *q, char **argv, client *cl)
   if (cnt > 0)
     return MR_EXISTS;
 
+  /* No aliases if you have NS records / are a delegated zone */
+  EXEC SQL SELECT count(mach_id) INTO :cnt FROM hostrecord hr, machine m
+    WHERE m.name = UPPER(:name) AND m.mach_id = hr.mach_id AND hr.rr_type = 'NS';
+  if (dbms_errno)
+    return mr_errcode;
+  if (cnt > 0)
+    return MR_EXISTS;
+
   return MR_SUCCESS;
 }
 
@@ -1432,6 +1451,14 @@ int setup_ahad(struct query *q, char **argv, client *cl)
   if (status)
     return status;
 
+  /* Don't allow adding addresses for NS records / delegated zones */
+  EXEC SQL SELECT COUNT(*) INTO :cnt FROM hostrecord WHERE mach_id = :mid
+    AND rr_type = 'NS';
+  if (dbms_errno)
+    return mr_errcode;
+  if (cnt > 0)
+    return MR_EXISTS;
+
   /* Get address type of specified subnet */
   EXEC SQL SELECT addr_type, saddr, mask, low, high INTO :addr_type, :saddr, :mask, :low, :high
     FROM subnet WHERE snet_id = :sid;
@@ -1750,6 +1777,14 @@ int setup_ahid(struct query *q, char **argv, client *cl)
   if (status)
     return status;
 
+  /* Don't allow adding identifiers to anything with NS records */
+  EXEC SQL SELECT COUNT(*) INTO :count FROM hostrecord WHERE mach_id = :mid
+    AND rr_type = 'NS';
+  if (dbms_errno)
+    return mr_errcode;
+  if (count > 0)
+    return MR_EXISTS;
+
   /* Don't allow empty string */
   if (strlen(identifier) == 0)
     return MR_BAD_CHAR;
@@ -2178,13 +2213,14 @@ int setup_sttl(struct query *q, char *argv[], client *cl)
 int setup_ahrr(struct query *q, char *argv[], client *cl)
 {
   EXEC SQL BEGIN DECLARE SECTION;
-  int mid;
-  char mname[MACHINE_NAME_SIZE];
+  int mid, count;
+  char *rr_type, mname[MACHINE_NAME_SIZE];
   EXEC SQL END DECLARE SECTION;
   int status;
 
   mid = *(int *)argv[0];
-  
+  rr_type = argv[1];
+
   EXEC SQL SELECT name INTO :mname FROM machine WHERE mach_id = :mid;
   if (dbms_errno)
     return mr_errcode;
@@ -2195,6 +2231,31 @@ int setup_ahrr(struct query *q, char *argv[], client *cl)
   if (status)
     return status;
 
+  /* Don't allow adding NS records to anything that has other identifiers */
+  if (!strcmp(rr_type, "NS"))
+    {
+      EXEC SQL SELECT COUNT(*) INTO :count FROM hostaddress
+	WHERE mach_id = :mid;
+      if (dbms_errno)
+	return mr_errcode;
+      if (count > 0)
+	return MR_EXISTS;
+
+      EXEC SQL SELECT COUNT(*) INTO :count FROM hostalias
+	WHERE mach_id = :mid;
+      if (dbms_errno)
+	return mr_errcode;
+      if (count > 0)
+	return MR_EXISTS;
+
+      EXEC SQL SELECT COUNT(*) INTO :count FROM machidentifiermap
+	WHERE mach_id = :mid;
+      if (dbms_errno)
+	return mr_errcode;
+      if (count > 0)
+	return MR_EXISTS;
+    }
+
   return MR_SUCCESS;
 }
 

home help back first fref pref prev next nref lref last post