Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions lib/nameserver/store/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,7 @@ class NameserverRepoFile extends NameserverBase {
if ([null, undefined].includes(r[f])) r[f] = ''
}

// Ensure the nested export object is always present and well-formed.
// Unlike MySQL (which joins nt_nameserver_export_type), TOML stores the
// type name inline, so no translation is needed.
if (!r.export || typeof r.export !== 'object') r.export = {}
if ([null, undefined].includes(r.export.type)) r.export.type = ''
if ([null, undefined].includes(r.export.interval)) r.export.interval = 0
if ([null, undefined].includes(r.export.status)) r.export.status = ''
r.export.serials = Boolean(r.export.serials)
Expand Down
36 changes: 22 additions & 14 deletions lib/nameserver/store/mysql.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,7 @@ class Nameserver extends NameserverBase {
if (g.length === 1) return g[0].id
}

if (args.export.type) {
args = JSON.parse(JSON.stringify(args))
const rows = await Mysql.execute(
...Mysql.select('SELECT id FROM nt_nameserver_export_type', {
name: args.export.type,
}),
)
args.export_type_id = rows[0].id
delete args.export.type
}
args = await resolveType(args)

return await Mysql.execute(...Mysql.insert(`nt_nameserver`, mapToDbColumn(objectToDb(args), nsDbMap)))
}
Expand Down Expand Up @@ -58,13 +49,12 @@ class Nameserver extends NameserverBase {
, ns.export_interval
, ns.export_serials
, ns.export_status
, ns.engine
, ns.listen
, ns.publisher
, ns.transport
, ns.dnssec
, ns.deleted
, t.name AS export_type
, t.name AS type
FROM nt_nameserver ns
JOIN nt_nameserver_export_type t ON ns.export_type_id=t.id`,
mapToDbColumn(args, nsDbMap),
Expand All @@ -81,6 +71,7 @@ class Nameserver extends NameserverBase {

async put(args) {
if (!args.id) return false
args = await resolveType(args)
const id = args.id
delete args.id
// Mysql.debug(1)
Expand Down Expand Up @@ -111,6 +102,24 @@ class Nameserver extends NameserverBase {

export default Nameserver

/**
* `type` is stored as the export_type_id foreign key, so a write has to resolve
* the name to its id.
*/
async function resolveType(argsIn) {
if (argsIn.type === undefined) return argsIn

const args = JSON.parse(JSON.stringify(argsIn))
const rows = await Mysql.execute(
...Mysql.select('SELECT id FROM nt_nameserver_export_type', { name: args.type }),
)
if (!rows.length) throw new Error(`unknown nameserver type: ${args.type}`)

args.export_type_id = rows[0].id
delete args.type
return args
}

function dbToObject(rows) {
for (const row of rows) {
for (const f of ['description', 'address6', 'remote_login', 'datadir', 'logdir', 'export_status']) {
Expand All @@ -128,9 +137,8 @@ function dbToObject(rows) {
}
if (row[f] === null || row[f] === undefined) delete row[f]
}
if ([null, undefined, ''].includes(row.engine)) delete row.engine
for (const f of ['export']) {
for (const p of ['type', 'interval', 'serials', 'status']) {
for (const p of ['interval', 'serials', 'status']) {
if (![null, undefined].includes(row[`${f}_${p}`])) {
if (row[f] === undefined) row[f] = {}
row[f][p] = row[`${f}_${p}`]
Expand Down
4 changes: 2 additions & 2 deletions lib/nameserver/test/nameserver.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
"remote_login": "nsd",
"logdir": "/foo",
"datadir": "/bar",
"type": "nsd",
"export": {
"interval": 0,
"serials": true,
"status": "last run:03-05 15:25<br>last cp :09-20 12:59",
"type": "nsd"
"status": "last run:03-05 15:25<br>last cp :09-20 12:59"
},
"ttl": 3600
}
33 changes: 23 additions & 10 deletions lib/nameserver/test/runtime.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// The nameserver record carries the v3 runtime configuration the supervisor
// consumes (engine, listen, publisher, transport, dnssec). It has to survive a
// consumes (type, listen, publisher, transport, dnssec). It has to survive a
// round-trip through whichever store is active: nested structures in the file
// stores, JSON columns under MySQL.
// stores, JSON columns under MySQL, and `type` through the export_type_id
// foreign key.
import assert from 'node:assert/strict'
import { describe, it, after, before } from 'node:test'

Expand All @@ -13,8 +14,8 @@ const runtimeCase = {
name: 'rt.ns.example.com.',
address: '1.2.3.9',
ttl: 3600,
export: { type: 'nsd', interval: 0, serials: true, status: '' },
engine: 'native',
export: { interval: 0, serials: true, status: '' },
type: 'native',
listen: [
{ address: '127.0.0.1', port: 5353, proto: 'udp' },
{ address: '127.0.0.1', port: 5353, proto: 'tcp' },
Expand All @@ -41,33 +42,45 @@ describe('nameserver runtime config', () => {
assert.deepEqual(ns.listen, runtimeCase.listen)
})

it('round-trips engine, publisher, transport and dnssec', async () => {
it('round-trips type, publisher, transport and dnssec', async () => {
const [ns] = await Nameserver.get({ id: runtimeCase.id })

assert.equal(ns.engine, 'native')
assert.equal(ns.type, 'native')
assert.deepEqual(ns.publisher, runtimeCase.publisher)
assert.deepEqual(ns.transport, runtimeCase.transport)
assert.deepEqual(ns.dnssec, runtimeCase.dnssec)
})

it('updates runtime config in place', async () => {
const listen = [{ address: '127.0.0.1', port: 5354, proto: 'udp' }]
assert.ok(await Nameserver.put({ id: runtimeCase.id, listen, engine: 'bind' }))
assert.ok(await Nameserver.put({ id: runtimeCase.id, listen, type: 'bind' }))

const [ns] = await Nameserver.get({ id: runtimeCase.id })
assert.deepEqual(ns.listen, listen)
assert.equal(ns.engine, 'bind')
assert.equal(ns.type, 'bind')
})

// dynect and bind-nsupdate are 2.x export types with no v3 nameserver. An
// adopted record must still round-trip; refusing it would strand the record.
it('stores a type nothing here can build', async () => {
const legacy = { ...runtimeCase, id: 4244, name: 'dyn.ns.example.com.', type: 'dynect' }
await Nameserver.destroy({ id: legacy.id })
await Nameserver.create(legacy)

const [ns] = await Nameserver.get({ id: legacy.id })
assert.equal(ns.type, 'dynect')
await Nameserver.destroy({ id: legacy.id })
})

it('omits runtime keys entirely for a legacy record that has none', async () => {
const legacy = { ...runtimeCase, id: 4243, name: 'legacy.ns.example.com.' }
for (const k of ['engine', 'listen', 'publisher', 'transport', 'dnssec']) delete legacy[k]
for (const k of ['listen', 'publisher', 'transport', 'dnssec']) delete legacy[k]

await Nameserver.destroy({ id: legacy.id })
await Nameserver.create(legacy)
const [ns] = await Nameserver.get({ id: legacy.id })

for (const k of ['engine', 'listen', 'publisher', 'transport', 'dnssec']) {
for (const k of ['listen', 'publisher', 'transport', 'dnssec']) {
assert.equal(ns[k], undefined, `${k} should be absent, not null`)
}
await Nameserver.destroy({ id: legacy.id })
Expand Down
4 changes: 2 additions & 2 deletions routes/test/nameserver.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
"remote_login": "nsd",
"logdir": "/foo",
"datadir": "/bar",
"type": "nsd",
"export": {
"interval": 0,
"serials": true,
"status": "last run:03-05 15:25<br>last cp :09-20 12:59",
"type": "nsd"
"status": "last run:03-05 15:25<br>last cp :09-20 12:59"
},
"ttl": 3600,
"deleted": false
Expand Down
10 changes: 7 additions & 3 deletions sql/01_nt_group.sql
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ CREATE TABLE IF NOT EXISTS nt_group_subgroups(
INSERT IGNORE INTO `nt_group` (`nt_group_id`, `parent_group_id`, `name`)
VALUES
(1,0,'NicTool');
INSERT IGNORE INTO nt_group_log(nt_group_id, nt_user_id, action, timestamp, modified_group_id, parent_group_id)
VALUES
(1, 1, 'added', UNIX_TIMESTAMP(), 1, 0);

# The log row carries no explicit id and nt_group_log has no unique key over
# these columns, guard on the row already being there.
INSERT INTO nt_group_log(nt_group_id, nt_user_id, action, timestamp, modified_group_id, parent_group_id)
SELECT 1, 1, 'added', UNIX_TIMESTAMP(), 1, 0
FROM DUAL WHERE NOT EXISTS
(SELECT 1 FROM nt_group_log WHERE modified_group_id=1 AND action='added');
48 changes: 32 additions & 16 deletions sql/04_nt_nameserver.sql
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ CREATE TABLE IF NOT EXISTS nt_nameserver(
export_serials tinyint(1) UNSIGNED NOT NULL DEFAULT '1',
export_status varchar(255) NULL DEFAULT NULL,
-- v3 runtime configuration, driving the nameserver supervisor. Stored as
-- JSON because the shape is engine-specific and evolves with the engines.
engine VARCHAR(32) NULL DEFAULT NULL,
-- JSON because the shape varies by nameserver type and evolves with it.
-- Which software this is stays in export_type_id: one fact, one column.
listen JSON NULL DEFAULT NULL,
publisher JSON NULL DEFAULT NULL,
transport JSON NULL DEFAULT NULL,
Expand Down Expand Up @@ -72,21 +72,37 @@ VALUES (1,'djbdns','djbdns (tinydns & axfrdns)','cr.yp.to/djbdns.html'),
(5,'bind-nsupdate','BIND (nsupdate protocol)',''),
(6,'nsd','Name Server Daemon (NSD)','www.nlnetlabs.nl/projects/nsd/'),
(7,'dynect','DynECT Standard DNS','dyn.com/managed-dns/'),
(8,'knot','Knot DNS','www.knot-dns.cz');
(8,'knot','Knot DNS','www.knot-dns.cz'),
(9,'coredns','CoreDNS','coredns.io'),
(10,'native','NicTool (in-process)','nictool.com');

INSERT IGNORE INTO nt_nameserver(nt_group_id, name, ttl, description, address,
export_type_id, logdir, datadir, export_interval) values (1,'ns1.example.com.',86400,'ns east',
'198.93.97.188','1','/etc/tinydns-ns1/log/main/',
'/etc/tinydns-ns1/root/',120);
INSERT IGNORE INTO nt_nameserver(nt_group_id, name, ttl, description, address,
export_type_id, logdir, datadir, export_interval) values (1,'ns2.example.com.',86400,'ns west',
'216.133.235.6','1','/etc/tinydns-ns2/log/main/','/etc/tinydns-ns2/root/',120);
INSERT IGNORE INTO nt_nameserver(nt_group_id, name, ttl, description, address,
export_type_id, logdir, datadir, export_interval) values (1,'ns3.example.com.',86400,'ns test',
'127.0.0.1','2','/var/log', '/etc/namedb/master/',120);
INSERT IGNORE INTO nt_nameserver_log(nt_group_id,nt_user_id, action, timestamp, nt_nameserver_id) VALUES (1,1,'added',UNIX_TIMESTAMP(), 1);
INSERT IGNORE INTO nt_nameserver_log(nt_group_id,nt_user_id, action, timestamp, nt_nameserver_id) VALUES (1,1,'added',UNIX_TIMESTAMP(), 2);
INSERT IGNORE INTO nt_nameserver_log(nt_group_id,nt_user_id, action, timestamp, nt_nameserver_id) VALUES (1,1,'added',UNIX_TIMESTAMP(), 3);
INSERT INTO nt_nameserver(nt_group_id, name, ttl, description, address,
export_type_id, logdir, datadir, export_interval)
SELECT 1,'ns1.example.com.',86400,'ns east','198.93.97.188','1',
'/etc/tinydns-ns1/log/main/','/etc/tinydns-ns1/root/',120
FROM DUAL WHERE NOT EXISTS
(SELECT 1 FROM nt_nameserver WHERE name='ns1.example.com.');

INSERT INTO nt_nameserver(nt_group_id, name, ttl, description, address,
export_type_id, logdir, datadir, export_interval)
SELECT 1,'ns2.example.com.',86400,'ns west','216.133.235.6','1',
'/etc/tinydns-ns2/log/main/','/etc/tinydns-ns2/root/',120
FROM DUAL WHERE NOT EXISTS
(SELECT 1 FROM nt_nameserver WHERE name='ns2.example.com.');

INSERT INTO nt_nameserver(nt_group_id, name, ttl, description, address,
export_type_id, logdir, datadir, export_interval)
SELECT 1,'ns3.example.com.',86400,'ns test','127.0.0.1','2',
'/var/log','/etc/namedb/master/',120
FROM DUAL WHERE NOT EXISTS
(SELECT 1 FROM nt_nameserver WHERE name='ns3.example.com.');

INSERT INTO nt_nameserver_log(nt_group_id, nt_user_id, action, timestamp, nt_nameserver_id)
SELECT 1,1,'added',UNIX_TIMESTAMP(),ns.nt_nameserver_id
FROM nt_nameserver ns
WHERE ns.name IN ('ns1.example.com.','ns2.example.com.','ns3.example.com.')
AND NOT EXISTS (SELECT 1 FROM nt_nameserver_log l
WHERE l.nt_nameserver_id = ns.nt_nameserver_id AND l.action = 'added');

CREATE TABLE IF NOT EXISTS nt_nameserver_export_log(
nt_nameserver_export_log_id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
Expand Down
2 changes: 1 addition & 1 deletion sql/06_resource_records.sql
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ VALUES
(2,'NS','Name Server',1,1,0),
(5,'CNAME','Canonical Name',1,1,0),
(6,'SOA','Start Of Authority',0,0,0),
(12,'PTR','Pointer',1,0,0),
(12,'PTR','Pointer',1,1,0),
(13,'HINFO','Host Info',0,0,1),
(15,'MX','Mail Exchanger',0,1,0),
(16,'TXT','Text',1,1,0),
Expand Down
1 change: 0 additions & 1 deletion sql/upgrade/03_nameserver_runtime_columns.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
# errors with ER_DUP_FIELDNAME. That is safe to ignore.

ALTER TABLE nt_nameserver
ADD COLUMN engine VARCHAR(32) NULL DEFAULT NULL,
ADD COLUMN listen JSON NULL DEFAULT NULL,
ADD COLUMN publisher JSON NULL DEFAULT NULL,
ADD COLUMN transport JSON NULL DEFAULT NULL,
Expand Down
Loading