Huge changes in powerdns

 * Add .gitreview
 * Add support for sqlite backend
 * Move backends configuration from service state to multiple state files
 * Add packages.jinja for os_family package name mapping
 * Update README
 * Add debian/*
 * Add simple test

This split simplifies process of adding new types of backends.
To add new backend type we just need describe required steps in
particular state file, add backend config and specify the engine
in powerdns:server:backend pillar.

Related PROD: 11411

Change-Id: I758b3eea71ceaeb4788a94ea616ffc9240c9e594
diff --git a/.gitreview b/.gitreview
new file mode 100644
index 0000000..f33ac35
--- /dev/null
+++ b/.gitreview
@@ -0,0 +1,4 @@
+[gerrit]
+host=gerrit.mcp.mirantis.net
+port=29418
+project=salt-formulas/powerdns
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..2a1e9d7
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,26 @@
+DESTDIR=/
+SALTENVDIR=/usr/share/salt-formulas/env
+RECLASSDIR=/usr/share/salt-formulas/reclass
+FORMULANAME=$(shell awk '/^name:/ {print $$2}' metadata.yml)
+
+all:
+	@echo "make install - Install into DESTDIR"
+	@echo "make test    - Run tests"
+	@echo "make clean   - Cleanup after tests run"
+
+install:
+	# Formula
+	[ -d $(DESTDIR)/$(SALTENVDIR) ] || mkdir -p $(DESTDIR)/$(SALTENVDIR)
+	cp -a $(FORMULANAME) $(DESTDIR)/$(SALTENVDIR)/
+	[ ! -d _modules ] || cp -a _modules $(DESTDIR)/$(SALTENVDIR)/
+	[ ! -d _states ] || cp -a _states $(DESTDIR)/$(SALTENVDIR)/ || true
+	# Metadata
+	[ -d $(DESTDIR)/$(RECLASSDIR)/service/$(FORMULANAME) ] || mkdir -p $(DESTDIR)/$(RECLASSDIR)/service/$(FORMULANAME)
+	cp -a metadata/service/* $(DESTDIR)/$(RECLASSDIR)/service/$(FORMULANAME)
+
+test:
+	[ ! -d tests ] || (cd tests; ./run_tests.sh)
+
+clean:
+	[ ! -d tests/build ] || rm -rf tests/build
+	[ ! -d build ] || rm -rf build
diff --git a/README.rst b/README.rst
index 5a2da61..e85f36f 100644
--- a/README.rst
+++ b/README.rst
@@ -7,16 +7,37 @@
 
 PowerDNS server with MySQL backend
 
-	powedns:
-	  server:
-	    enabled: true
-	    backend:
-	      engine: mysql
-	      host: localhost
-	      port: 3306
-	      name: pdns
-	      user: pdns
-	      password: password
+.. code-block:: yaml
+  powedns:
+    server:
+      enabled: true
+      backend:
+        engine: mysql
+        host: localhost
+        port: 3306
+        name: pdns
+        user: pdns
+        password: password
+      bind:
+        address: 0.0.0.0
+        port: 53
+
+PowerDNS server with sqlite backend
+
+.. code-block:: yaml
+  powerdns:
+    server:
+      enabled: true
+      backend:
+        engine: sqlite
+        dbname: pdns.sqlite
+        dbpath: /var/lib/powerdns
+      bind:
+        address: 127.0.0.1
+        port: 55
+      default-soa-name: ns1.domain.tld
+      soa-minimum-ttl: 3600
+
 
 Read more
 =========
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..09e4837
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,5 @@
+salt-formula-powerdns (0.1) xenial; urgency=low
+
+  * Initial release
+
+ -- Ivan Suzdal <mos-linux@mirantis.com>  Wed, 31 May 2017 15:13:23 +0300
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..ec63514
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..190d433
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,14 @@
+Source: salt-formula-powerdns
+Maintainer: MOS Linux Team <mos-linux@mirantis.com>
+Section: admin
+Priority: optional
+Build-Depends: debhelper (>= 9), python-pip, python-virtualenv
+Standards-Version: 3.9.8
+Vcs-Browser: https://github.com/salt-formulas/salt-formula-powerdns
+Vcs-Git: https://github.com/salt-formulas/salt-formula-powerdns.git
+
+Package: salt-formula-powerdns
+Architecture: all
+Depends: ${misc:Depends}, salt-master
+Description: PowerDNS salt formula
+ Install and configure PowerDNS server.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..d03cdde
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,29 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: salt-formula-powerdns
+Source: https://github.com/salt-formulas/salt-formula-powerdns
+
+Files: debian/*
+Copyright:  2017, MOS Linux Team <mos-linux@mirantis.com>
+License: Apache-2
+
+Files: *
+Copyright: Filip Pytloun <fpytloun@mirantis.com>
+  Aleš Komárek <akomarek@mirantis.com>
+  MOS Linux Team <mos-linux@mirantis.com>
+License: Apache-2
+
+License: Apache-2
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ .
+    http://www.apache.org/licenses/LICENSE-2.0
+ .
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ .
+ On Debian-based systems the full text of the Apache version 2.0 license
+ can be found in `/usr/share/common-licenses/Apache-2.0'.
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..a1320b1
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1 @@
+README.rst
diff --git a/debian/rules b/debian/rules
new file mode 100644
index 0000000..2d33f6a
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,4 @@
+#!/usr/bin/make -f
+
+%:
+	dh $@
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..89ae9db
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (native)
diff --git a/metadata.yml b/metadata.yml
new file mode 100644
index 0000000..fa2148c
--- /dev/null
+++ b/metadata.yml
@@ -0,0 +1,3 @@
+name: powerdns
+version: "0.1"
+source: https://github.com/salt-formulas/salt-formula-powerdns.git
diff --git a/metadata/service/server/single.yml b/metadata/service/server/single.yml
index e69de29..14ff61f 100644
--- a/metadata/service/server/single.yml
+++ b/metadata/service/server/single.yml
@@ -0,0 +1,10 @@
+applications:
+- powerdns
+parameters:
+  powerdns:
+    server:
+      enabled: true
+      backend:
+        engine: sqlite
+        dbname: pdns.sqlite
+        dbpath: /var/lib/powerdns
diff --git a/powerdns/files/pdns.local.gmysql.conf b/powerdns/files/backends/mysql.conf
similarity index 60%
rename from powerdns/files/pdns.local.gmysql.conf
rename to powerdns/files/backends/mysql.conf
index d8f3a6b..9ff090e 100644
--- a/powerdns/files/pdns.local.gmysql.conf
+++ b/powerdns/files/backends/mysql.conf
@@ -2,13 +2,17 @@
 # MySQL Configuration
 #
 # Launch gmysql backend
-launch=gmysql
+launch+=gmysql
 
 # gmysql parameters
+{%- if server.backend.socket is defined %}
+gmysql-socket={{ server.backend.socket }}
+{%- else %}
 gmysql-host={{ server.backend.host }}
 gmysql-port={{ server.backend.port }}
+{%- endif %}
 gmysql-dbname={{ server.backend.name }}
 gmysql-user={{ server.backend.user }}
 gmysql-password={{ server.backend.password }}
-gmysql-dnssec=yes
-# gmysql-socket=
+gmysql-dnssec={{ server.backend.dnssec }}
+gmysql-timeout={{ server.backend.timeout }}
diff --git a/powerdns/files/backends/sqlite.conf b/powerdns/files/backends/sqlite.conf
new file mode 100644
index 0000000..4684992
--- /dev/null
+++ b/powerdns/files/backends/sqlite.conf
@@ -0,0 +1,15 @@
+{%- from "powerdns/map.jinja" import server with context -%}
+# Sqlite3 Configuration
+#
+# Launch gsqlite3 backend
+launch+=gsqlite3
+
+# gsqlite3 parameters
+gsqlite3-database={{ server.backend.dbpath }}/{{ server.backend.dbname }}
+gsqlite3-dnssec={{ server.backend.dnssec }}
+
+{%- if server.backend.pragma_synchronous is defined %}
+gsqlite3-pragma-synchronous={{ server.backend.pragma_synchronous }}
+{%- elif server.backend.pragma_foreign_keys is defined %}
+gsqlite3-pragma-foreign-keys={{ server.backend.pragma_foreign_keys }}
+{%- endif %}
diff --git a/powerdns/files/pdns.conf b/powerdns/files/pdns.conf
index 02970e5..7b48195 100644
--- a/powerdns/files/pdns.conf
+++ b/powerdns/files/pdns.conf
@@ -1,310 +1,21 @@
 {%- from "powerdns/map.jinja" import server with context %}
 # Autogenerated configuration file template
-#################################
-# allow-axfr-ips    If enabled, restrict zonetransfers to originate from these
-#                   IP addresses
-#
-# allow-axfr-ips=
-
-#################################
-# allow-recursion	List of netmasks that are allowed to recurse
-#
+launch=
 allow-recursion=127.0.0.1
-
-#################################
-# allow-recursion-override   Local data even about hosts that don't exist will
-#                            override the internet. (on/off)
-#
-# allow-recursion-override=
-
-#################################
-# cache-ttl	Seconds to store packets in the PacketCache
-#
-# cache-ttl=20
-
-#################################
-# chroot	If set, chroot to this directory for more security
-#
-# chroot=/var/spool/powerdns
-
-#################################
-# config-dir	Location of configuration directory (pdns.conf)
-#
 config-dir=/etc/powerdns
-
-#################################
-# config-name	Name of this virtual configuration - will rename the binary image
-#
-# config-name=
-
-#################################
-# control-console	Debugging switch - don't use
-#
-# control-console=no
-
-#################################
-# daemon	Operate as a daemon
-#
 daemon=yes
-
-#################################
-# default-soa-name	name to insert in the SOA record if none set in the backend
-#
-default-soa-name=a.very.best.power.dns.server
-
-#################################
-# disable-axfr	Disable zonetransfers but do allow TCP queries
-#
+default-soa-name={{ server.default_soa_name }}
+soa-minimum-ttl={{ server.soa_minimum_ttl }}
 disable-axfr=yes
-
-#################################
-# disable-tcp	Do not listen to TCP queries
-#
-# disable-tcp=no
-
-#################################
-# distributor-threads	Default number of Distributor (backend) threads to start
-#
-# distributor-threads=3
-
-#################################
-# fancy-records	Process URL and MBOXFW records
-#
-# fancy-records=no
-
-#################################
-# guardian	Run within a guardian process
-#
 guardian=yes
-
-#################################
-# launch	Which backends to launch and order to query them in
-#
-launch={% if server.backend.engine == 'mysql' %}gmysql{% else %}{{ server.backend.engine }}{% endif %}
-
-#################################
-# load-modules	Load this module - supply absolute or relative path
-#
-# load-modules=
-
-#################################
-# local-address	Local IP address to which we bind
-#
 local-address={{ server.bind.address }}
-
-#################################
-# local-ipv6	Local IP address to which we bind
-#
-# local-ipv6=
-
-#################################
-# local-port	The port on which we listen
-#
 local-port={{ server.bind.port }}
-
-#################################
-# log-dns-details	If PDNS should log failed update requests
-#
-# log-dns-details=
-
-#################################
-# log-failed-updates	If PDNS should log failed update requests
-#
-# log-failed-updates=
-
-#################################
-# logfile	Logfile to use
-#
-# logfile=/var/log/pdns.log
-
-#################################
-# logging-facility	Log under a specific facility
-#
-# logging-facility=
-
-#################################
-# loglevel	Amount of logging. Higher is more. Do not set below 3
-#
-# loglevel=4
-
-#################################
-# master	Act as a master
-#
-# master=yes
-
-#################################
-# max-queue-length	Maximum queuelength before considering situation lost
-#
-# max-queue-length=5000
-
-#################################
-# max-tcp-connections	Maximum number of TCP connections
-#
-# max-tcp-connections=10
-
-#################################
-# module-dir	Default directory for modules
-#
-# module-dir=/usr/lib/powerdns
-
-#################################
-# negquery-cache-ttl	Seconds to store packets in the PacketCache
-#
-# negquery-cache-ttl=60
-
-#################################
-# out-of-zone-additional-processing	Do out of zone additional processing
-#
-# out-of-zone-additional-processing=no
-
-#################################
-# query-cache-ttl	Seconds to store packets in the PacketCache
-#
-# query-cache-ttl=20
-
-#################################
-# query-logging	Hint backends that queries should be logged
-#
-# query-logging=no
-
-#################################
-# queue-limit	Maximum number of milliseconds to queue a query
-#
-# queue-limit=1500
-
-#################################
-# query-local-address   The IP address to use as a source address for sending
-#                       queries.
-# query-local-address=
-
-#################################
-# receiver-threads	Number of receiver threads to launch
-#
-# receiver-threads=1
-
-#################################
-# recursive-cache-ttl	Seconds to store packets in the PacketCache
-#
-# recursive-cache-ttl=10
-
-#################################
-# recursor	If recursion is desired, IP address of a recursing nameserver
-#
-# recursor=
-
-#################################
-# setgid	If set, change group id to this gid for more security
-#
 setgid=pdns
-
-#################################
-# setuid	If set, change user id to this uid for more security
-#
 setuid=pdns
-
-#################################
-# skip-cname	Do not perform CNAME indirection for each query
-#
-# skip-cname=no
-
-#################################
-# slave	Act as a slave
-#
-# slave=no
-
-#################################
-# slave-cycle-interval	Reschedule failed SOA serial checks once every .. seconds
-#
-# slave-cycle-interval=60
-
-#################################
-# smtpredirector	Our smtpredir MX host
-#
-# smtpredirector=a.misconfigured.powerdns.smtp.server
-
-#################################
-# soa-minimum-ttl	Default SOA mininum ttl
-#
-# soa-minimum-ttl=3600
-
-#################################
-# soa-refresh-default  Default SOA refresh
-#
-# soa-refresh-default=10800
-
-#################################
-# soa-retry-default    Default SOA retry
-#
-# soa-retry-default=3600
-
-#################################
-# soa-expire-default   Default SOA expire
-#
-# soa-expire-default=604800
-
-#################################
-# soa-serial-offset	Make sure that no SOA serial is less than this number
-#
-# soa-serial-offset=0
-
-#################################
-# socket-dir	Where the controlsocket will live
-#
 socket-dir=/var/run
-
-#################################
-# strict-rfc-axfrs	Perform strictly rfc compliant axfrs (very slow)
-#
-# strict-rfc-axfrs=no
-
-#################################
-# urlredirector	Where we send hosts to that need to be url redirected
-#
-# urlredirector=127.0.0.1
-
-#################################
-# use-logfile	Use a log file
-#
-# use-logfile=yes
-
-#################################
-# webserver	Start a webserver for monitoring
-#
 webserver=yes
-
-#################################
-# webserver-address	IP Address of webserver to listen on
-#
 webserver-address=127.0.0.1
-
-#################################
-# webserver-password	Password required for accessing the webserver
-#
 webserver-password=i.cannot.be.bad
-
-#################################
-# webserver-port	Port of webserver to listen on
-#
 webserver-port=8081
-
-#################################
-# webserver-print-arguments	If the webserver should print arguments
-#
-# webserver-print-arguments=no
-
-#################################
-# wildcard-url	Process URL and MBOXFW records
-#
-# wildcard-url=no
-
-#################################
-# wildcards	Honor wildcards in the database
-#
-# wildcards=
-
-#################################
-# version-string   What should PowerDNS return for version
-#                  allowed methods are anonymous / powerdns / full / custom
 version-string=powerdns
-
 include-dir=/etc/powerdns/pdns.d
diff --git a/powerdns/files/sqlite.sql b/powerdns/files/sqlite.sql
new file mode 100644
index 0000000..4748a8d
--- /dev/null
+++ b/powerdns/files/sqlite.sql
@@ -0,0 +1,92 @@
+PRAGMA foreign_keys = 1;
+
+CREATE TABLE domains (
+  id                    INTEGER PRIMARY KEY,
+  name                  VARCHAR(255) NOT NULL COLLATE NOCASE,
+  master                VARCHAR(128) DEFAULT NULL,
+  last_check            INTEGER DEFAULT NULL,
+  type                  VARCHAR(6) NOT NULL,
+  notified_serial       INTEGER DEFAULT NULL,
+  account               VARCHAR(40) DEFAULT NULL
+);
+
+CREATE UNIQUE INDEX name_index ON domains(name);
+
+
+CREATE TABLE records (
+  id                    INTEGER PRIMARY KEY,
+  domain_id             INTEGER DEFAULT NULL,
+  name                  VARCHAR(255) DEFAULT NULL,
+  type                  VARCHAR(10) DEFAULT NULL,
+  content               VARCHAR(65535) DEFAULT NULL,
+  ttl                   INTEGER DEFAULT NULL,
+  prio                  INTEGER DEFAULT NULL,
+  change_date           INTEGER DEFAULT NULL,
+  disabled              BOOLEAN DEFAULT 0,
+  ordername             VARCHAR(255),
+  auth                  BOOL DEFAULT 1,
+  FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE
+);
+
+CREATE INDEX rec_name_index ON records(name);
+CREATE INDEX nametype_index ON records(name,type);
+CREATE INDEX domain_id ON records(domain_id);
+CREATE INDEX orderindex ON records(ordername);
+
+
+CREATE TABLE supermasters (
+  ip                    VARCHAR(64) NOT NULL,
+  nameserver            VARCHAR(255) NOT NULL COLLATE NOCASE,
+  account               VARCHAR(40) NOT NULL
+);
+
+CREATE UNIQUE INDEX ip_nameserver_pk ON supermasters(ip, nameserver);
+
+
+CREATE TABLE comments (
+  id                    INTEGER PRIMARY KEY,
+  domain_id             INTEGER NOT NULL,
+  name                  VARCHAR(255) NOT NULL,
+  type                  VARCHAR(10) NOT NULL,
+  modified_at           INT NOT NULL,
+  account               VARCHAR(40) DEFAULT NULL,
+  comment               VARCHAR(65535) NOT NULL,
+  FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE
+);
+
+CREATE INDEX comments_domain_id_index ON comments (domain_id);
+CREATE INDEX comments_nametype_index ON comments (name, type);
+CREATE INDEX comments_order_idx ON comments (domain_id, modified_at);
+
+
+CREATE TABLE domainmetadata (
+ id                     INTEGER PRIMARY KEY,
+ domain_id              INT NOT NULL,
+ kind                   VARCHAR(32) COLLATE NOCASE,
+ content                TEXT,
+ FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE
+);
+
+CREATE INDEX domainmetaidindex ON domainmetadata(domain_id);
+
+
+CREATE TABLE cryptokeys (
+ id                     INTEGER PRIMARY KEY,
+ domain_id              INT NOT NULL,
+ flags                  INT NOT NULL,
+ active                 BOOL,
+ content                TEXT,
+ FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE
+);
+
+CREATE INDEX domainidindex ON cryptokeys(domain_id);
+
+
+CREATE TABLE tsigkeys (
+ id                     INTEGER PRIMARY KEY,
+ name                   VARCHAR(255) COLLATE NOCASE,
+ algorithm              VARCHAR(50) COLLATE NOCASE,
+ secret                 VARCHAR(255)
+);
+
+CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);
diff --git a/powerdns/map.jinja b/powerdns/map.jinja
index 0d74fa6..bdced8e 100644
--- a/powerdns/map.jinja
+++ b/powerdns/map.jinja
@@ -1,31 +1,45 @@
 
 {%- set server = salt['grains.filter_by']({
     'Debian': {
-        'pkgs': ['pdns-server'],
-        'mysql_pkgs': ['pdns-backend-mysql'],
         'service': 'pdns',
         'config': '/etc/powerdns/pdns.conf',
         'local_config': '/etc/powerdns/pdns.d/pdns.local.conf',
-        'bind': {
-            'address': '0.0.0.0',
-            'port': '53'
-        },
         'backend': {
             'engine': 'mysql',
-        }
+            'host': 'localhost',
+            'port': '3306',
+            'dbname': 'powerdns',
+            'user': 'mysql',
+            'password': 'mysql',
+            'timeout': 10,
+            'dnssec': 'on'
+        },
+        'bind': {
+            'address': '127.0.0.1',
+            'port': 53,
+        },
+        'default_soa_name': 'a.very.best.power.dns.server',
+        'soa_minimum_ttl': 3600
     },
     'RedHat': {
-        'pkgs': ['pdns-server'],
-        'mysql_pkgs': ['pdns-backend-mysql'],
         'service': 'pdns',
         'config': '/etc/powerdns/pdns.conf',
         'local_config': '/etc/powerdns/pdns.d/pdns.local.conf',
-        'bind': {
-            'address': '0.0.0.0',
-            'port': '53'
-        },
         'backend': {
             'engine': 'mysql',
-        }
+            'host': 'localhost',
+            'port': '3306',
+            'dbname': 'powerdns',
+            'user': 'mysql',
+            'password': 'mysql',
+            'timeout': 10,
+            'dnssec': 'on'
+        },
+        'bind': {
+            'address': '127.0.0.1',
+            'port': 53,
+        },
+        'default_soa_name': 'a.very.best.power.dns.server',
+        'soa_minimum_ttl': 3600
     },
 }, merge=salt['pillar.get']('powerdns:server')) %}
diff --git a/powerdns/server/backends/mysql.sls b/powerdns/server/backends/mysql.sls
new file mode 100644
index 0000000..755930a
--- /dev/null
+++ b/powerdns/server/backends/mysql.sls
@@ -0,0 +1,20 @@
+{%- from "powerdns/map.jinja" import server with context %}
+{%- from "powerdns/server/packages.jinja" import packages with context %}
+include:
+  - powerdns.server.service
+
+powerdns_mysql_packages:
+  pkg.installed:
+    - names: {{ packages.backends.mysql }}
+
+/etc/powerdns/pdns.d/pdns.local.gmysql.conf:
+  file.managed:
+  - source: salt://powerdns/files/backends/mysql.conf
+  - template: jinja
+  - user: root
+  - group: root
+  - mode: 640
+  - require:
+    - pkg: powerdns_mysql_packages
+  - watch_in:
+    - service: powerdns_service
diff --git a/powerdns/server/backends/sqlite.sls b/powerdns/server/backends/sqlite.sls
new file mode 100644
index 0000000..224bc99
--- /dev/null
+++ b/powerdns/server/backends/sqlite.sls
@@ -0,0 +1,44 @@
+{%- from "powerdns/map.jinja" import server with context %}
+{%- from "powerdns/server/packages.jinja" import packages with context %}
+include:
+  - powerdns.server.service
+
+powerdns_sqlite_packages:
+  pkg.installed:
+    - names: {{ packages.backends.sqlite }}
+
+/etc/powerdns/dbtemplate.sql:
+  file.managed:
+    - source: salt://powerdns/files/sqlite.sql
+    - require:
+      - pkg: powerdns_sqlite_packages
+
+{{ server.backend.dbpath }}:
+  file.directory:
+    - user: pdns
+    - group: pdns
+    - mode: 750
+    - makedirs: true
+
+init_sqlite_db:
+  cmd.run:
+    - name: sqlite3 {{ server.backend.dbpath }}/{{ server.backend.dbname }} < /etc/powerdns/dbtemplate.sql
+    - runas: pdns
+    - umask: 027
+    - require:
+      - file: /etc/powerdns/dbtemplate.sql
+      - file: {{ server.backend.dbpath }}
+    - creates: {{ server.backend.dbpath }}/{{ server.backend.dbname }}
+
+/etc/powerdns/pdns.d/pdns.local.gsqlite3.conf:
+  file.managed:
+  - source: salt://powerdns/files/backends/sqlite.conf
+  - template: jinja
+  - user: root
+  - group: root
+  - mode: 640
+  - require:
+    - pkg: powerdns_sqlite_packages
+  - watch_in:
+    - service: powerdns_service
+
diff --git a/powerdns/server/init.sls b/powerdns/server/init.sls
index 6a611f4..cc4f965 100644
--- a/powerdns/server/init.sls
+++ b/powerdns/server/init.sls
@@ -1,3 +1,10 @@
+{%- from "powerdns/map.jinja" import server with context %}
+{%- if server.backend is defined %}
+{%- if not server.backend.engine is defined %}
+{{ salt.test.exception('Server backend MUST be configured') }}
+{%- endif %}
+
 include:
-- powerdns.server.service
-- powerdns.server.zone
+  - powerdns.server.backends.{{ server.backend.engine }}
+  - powerdns.server.zone
+{%- endif %}
diff --git a/powerdns/server/packages.jinja b/powerdns/server/packages.jinja
new file mode 100644
index 0000000..ea93df0
--- /dev/null
+++ b/powerdns/server/packages.jinja
@@ -0,0 +1,27 @@
+{%- set packages = salt['grains.filter_by']({
+    'default': {
+        'backends': {
+            'mysql': ['pdns-backend-mysql'],
+            'geoip': ['pdns-backend-geoip'],
+            'ldap': ['pdns-backend-ldap'],
+            'lua': ['pdns-backend-lua'],
+            'mydns': ['pdns-backend-mydns'],
+            'remote': ['pdns-backend-remote'],
+            'tinydns': ['pdns-backend-tinydns'],
+        },
+    },
+    'Debian': {
+        'pkgs': ['pdns-server'],
+        'backends': {
+            'sqlite': ['pdns-backend-sqlite3'],
+            'pgsql': ['pdns-backend-pgsql'],
+        },
+    },
+    'RedHat': {
+        'pkgs': ['pdns'],
+        'backends': {
+            'sqlite': ['pdns-backend-sqlite'],
+            'pgsql': ['pdns-backend-postgresql'],
+        },
+    },
+}, grain='os_family') %}
diff --git a/powerdns/server/service.sls b/powerdns/server/service.sls
index c2046c8..9ff3a70 100644
--- a/powerdns/server/service.sls
+++ b/powerdns/server/service.sls
@@ -1,9 +1,10 @@
 {%- from "powerdns/map.jinja" import server with context %}
+{%- from "powerdns/server/packages.jinja" import packages with context %}
 {%- if server.enabled %}
 
 powerdns_packages:
   pkg.installed:
-  - names: {{ server.pkgs }}
+  - names: {{ packages.pkgs }}
 
 /etc/powerdns/pdns.conf:
   file.managed:
@@ -14,29 +15,8 @@
   - mode: 600
   - require:
     - pkg: powerdns_packages
-
-{%- if server.backend.engine == 'mysql' %}
-
-powerdns_mysql_packages:
-  pkg.installed:
-  - names: {{ server.mysql_pkgs }}
-
-/etc/powerdns/pdns.d/pdns.local.gmysql.conf:
-  file.managed:
-  - source: salt://powerdns/files/pdns.local.gmysql.conf
-  - template: jinja
-  - user: root
-  - group: root
-  - mode: 600
-  - require:
-    - pkg: powerdns_mysql_packages
-  - watch_in:
-    - service: powerdns_service
-
-/etc/powerdns/pdns.d/pdns.simplebind.conf:
-  file.absent
-
-{%- endif %}
+  - require_in:
+    - service: {{ server.service }}
 
 powerdns_service:
   service.running:
diff --git a/tests/pillar/server.sls b/tests/pillar/server.sls
new file mode 100644
index 0000000..e1aa1e8
--- /dev/null
+++ b/tests/pillar/server.sls
@@ -0,0 +1,10 @@
+powerdns:
+  server:
+    enabled: true
+    backend:
+      engine: sqlite
+      dbname: pdns.sqlite3
+      dbpath: /var/lib/powerdns
+    bind:
+      address: 127.0.0.1
+      port: 53
diff --git a/tests/run_tests.sh b/tests/run_tests.sh
new file mode 100755
index 0000000..8275235
--- /dev/null
+++ b/tests/run_tests.sh
@@ -0,0 +1,162 @@
+#!/usr/bin/env bash
+
+set -e
+[ -n "$DEBUG" ] && set -x
+
+CURDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+METADATA=${CURDIR}/../metadata.yml
+FORMULA_NAME="$(awk '/^name:/ {print $2}' ${METADATA})"
+
+## Overrideable parameters
+PILLARDIR=${PILLARDIR:-${CURDIR}/pillar}
+BUILDDIR=${BUILDDIR:-${CURDIR}/build}
+VENV_DIR=${VENV_DIR:-${BUILDDIR}/virtualenv}
+DEPSDIR=${BUILDDIR}/deps
+
+SALT_FILE_DIR=${SALT_FILE_DIR:-${BUILDDIR}/file_root}
+SALT_PILLAR_DIR=${SALT_PILLAR_DIR:-${BUILDDIR}/pillar_root}
+SALT_CONFIG_DIR=${SALT_CONFIG_DIR:-${BUILDDIR}/salt}
+SALT_CACHE_DIR=${SALT_CACHE_DIR:-${SALT_CONFIG_DIR}/cache}
+
+SALT_OPTS="${SALT_OPTS} --retcode-passthrough --local -c ${SALT_CONFIG_DIR}"
+
+if [ "x${SALT_VERSION}" != "x" ]; then
+    PIP_SALT_VERSION="==${SALT_VERSION}"
+fi
+
+## Functions
+log_info() {
+    echo "[INFO] $*"
+}
+
+log_err() {
+    echo "[ERROR] $*" >&2
+}
+
+setup_virtualenv() {
+    log_info "Setting up Python virtualenv"
+    virtualenv $VENV_DIR
+    source ${VENV_DIR}/bin/activate
+    pip install salt${PIP_SALT_VERSION}
+}
+
+setup_pillar() {
+    [ ! -d ${SALT_PILLAR_DIR} ] && mkdir -p ${SALT_PILLAR_DIR}
+    echo "base:" > ${SALT_PILLAR_DIR}/top.sls
+    for pillar in ${PILLARDIR}/*; do
+        state_name=$(basename ${pillar%.sls})
+        echo -e "  ${state_name}:\n    - ${state_name}" >> ${SALT_PILLAR_DIR}/top.sls
+    done
+}
+
+setup_salt() {
+    [ ! -d ${SALT_FILE_DIR} ] && mkdir -p ${SALT_FILE_DIR}
+    [ ! -d ${SALT_CONFIG_DIR} ] && mkdir -p ${SALT_CONFIG_DIR}
+    [ ! -d ${SALT_CACHE_DIR} ] && mkdir -p ${SALT_CACHE_DIR}
+
+    echo "base:" > ${SALT_FILE_DIR}/top.sls
+    for pillar in ${PILLARDIR}/*.sls; do
+        state_name=$(basename ${pillar%.sls})
+        echo -e "  ${state_name}:\n    - ${FORMULA_NAME}" >> ${SALT_FILE_DIR}/top.sls
+    done
+
+    cat << EOF > ${SALT_CONFIG_DIR}/minion
+file_client: local
+cachedir: ${SALT_CACHE_DIR}
+verify_env: False
+
+file_roots:
+  base:
+  - ${SALT_FILE_DIR}
+  - ${CURDIR}/..
+  - /usr/share/salt-formulas/env
+
+pillar_roots:
+  base:
+  - ${SALT_PILLAR_DIR}
+  - ${PILLARDIR}
+EOF
+}
+
+fetch_dependency() {
+    dep_name="$(echo $1|cut -d : -f 1)"
+    dep_source="$(echo $1|cut -d : -f 2-)"
+    dep_root="${DEPSDIR}/$(basename $dep_source .git)"
+    dep_metadata="${dep_root}/metadata.yml"
+
+    [ -d /usr/share/salt-formulas/env/${dep_name} ] && log_info "Dependency $dep_name already present in system-wide salt env" && return 0
+    [ -d ${dep_root} ] && log_info "Dependency ${dep_name} already fetched" && return 0
+
+    log_info "Fetching dependency $dep_name"
+    [ ! -d ${DEPSDIR} ] && mkdir -p ${DEPSDIR}
+    git clone $dep_source ${DEPSDIR}/$(basename $dep_source .git)
+    ln -s ${dep_root}/${dep_name} ${SALT_FILE_DIR}/${dep_name}
+
+    METADATA="${dep_metadata}" install_dependencies
+}
+
+install_dependencies() {
+    grep -E "^dependencies:" ${METADATA} >/dev/null || return 0
+    (python - | while read dep; do fetch_dependency "$dep"; done) << EOF
+import sys,yaml
+for dep in yaml.load(open('${METADATA}', 'ro'))['dependencies']:
+    print '%s:%s' % (dep["name"], dep["source"])
+EOF
+}
+
+clean() {
+    log_info "Cleaning up ${BUILDDIR}"
+    [ -d ${BUILDDIR} ] && rm -rf ${BUILDDIR} || exit 0
+}
+
+salt_run() {
+    [ -e ${VEN_DIR}/bin/activate ] && source ${VENV_DIR}/bin/activate
+    salt-call ${SALT_OPTS} $*
+}
+
+prepare() {
+    [ -d ${BUILDDIR} ] && mkdir -p ${BUILDDIR}
+
+    which salt-call || setup_virtualenv
+    setup_pillar
+    setup_salt
+    install_dependencies
+}
+
+run() {
+    for pillar in ${PILLARDIR}/*.sls; do
+        state_name=$(basename ${pillar%.sls})
+        salt_run --id=${state_name} state.show_sls ${FORMULA_NAME} || (log_err "Execution of ${FORMULA_NAME}.${state_name} failed"; exit 1)
+    done
+}
+
+_atexit() {
+    RETVAL=$?
+    trap true INT TERM EXIT
+
+    if [ $RETVAL -ne 0 ]; then
+        log_err "Execution failed"
+    else
+        log_info "Execution successful"
+    fi
+    return $RETVAL
+}
+
+## Main
+trap _atexit INT TERM EXIT
+
+case $1 in
+    clean)
+        clean
+        ;;
+    prepare)
+        prepare
+        ;;
+    run)
+        run
+        ;;
+    *)
+        prepare
+        run
+        ;;
+esac