Change format of dhcp_subnets to be a dict

Top level key is now subnet CIDR, rather than a list containing dicts,
where subnet is a sub-key, so better structured

- Update to pass more modern ansible-lint
- Fix galaxy info
- Format filter plugin w/black
- BSD fixes - paths, listening IP for tftpd server

Change-Id: I490331bc2998f2afbef135545500d1fa07b626ff
diff --git a/.reuse/dep5 b/.reuse/dep5
index 36ce305..40fef72 100644
--- a/.reuse/dep5
+++ b/.reuse/dep5
@@ -1,5 +1,5 @@
 Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
 
-Files: .cookiecutter_params.json VERSION .gitreview
+Files: .cookiecutter_params.json VERSION .gitreview README.md
 Copyright: 2020 Open Networking Foundation
 License: Apache-2.0
diff --git a/Makefile b/Makefile
index 9ce7e2e..e797d50 100644
--- a/Makefile
+++ b/Makefile
@@ -26,10 +26,13 @@
     -d "{extends: default, rules: {line-length: {max: 99}}}" \
     -s $(YAML_FILES)
 
+# List of molecule files, not including base molecule.yml which isn't ansible format
+MOLECULE_FILES ?= $(shell find molecule -type f -name '*.yml' \! -name 'molecule.yml' -print )
+
 ansiblelint: ## lint check with ansible-lint
 	ansible-lint --version
 	ansible-lint -v .
-	ansible-lint -v molecule/*/*
+	ansible-lint -v $(MOLECULE_FILES)
 
 license: ## Check license with the reuse tool
 	reuse --version
diff --git a/README.md b/README.md
index bacd957..289c0d6 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,3 @@
-<!--
-SPDX-FileCopyrightText: © 2020 Open Networking Foundation <support@opennetworking.org>
-SPDX-License-Identifier: Apache-2.0
---!>
 # dhcpd
 
 Installs/configure a DHCP server and TFTP server
@@ -53,7 +49,7 @@
     dhcpd_interfaces:
       - eth0
     dhcpd_subnets:
-      - subnet: "192.168.0.1/24"
+      "192.168.0.1/24":
         range: "192.168.0.128/25"
         dns_servers:
           - "192.168.0.1"
@@ -71,7 +67,7 @@
     - dhcpd
 
 ```
-### Todo
+### ToDo
 
 Add classless static route support for OpenBSD - see dhcp-options(5) on that
 system.
diff --git a/defaults/main.yml b/defaults/main.yml
index 41d87ff..cd96479 100644
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -10,7 +10,7 @@
 dhcpd_interfaces: []
 
 # subnets to serve DHCP on
-dhcpd_subnets: []
+dhcpd_subnets: {}
 
 # default PXE filename
 dhcpd_pxe_filename: "undionly.kpxe"
diff --git a/filter_plugins/rfc3442_words.py b/filter_plugins/rfc3442_words.py
index 96756f5..8655622 100644
--- a/filter_plugins/rfc3442_words.py
+++ b/filter_plugins/rfc3442_words.py
@@ -73,8 +73,34 @@
 
     # verify correct functionality
     assert words == [
-        8, 10, 192, 168, 1, 10,
-        16, 172, 16, 192, 168, 1, 10,
-        22, 172, 17, 0, 192, 168, 1, 10,
-        25, 172, 31, 10, 0, 192, 168, 1, 10,
+        8,
+        10,
+        192,
+        168,
+        1,
+        10,
+        16,
+        172,
+        16,
+        192,
+        168,
+        1,
+        10,
+        22,
+        172,
+        17,
+        0,
+        192,
+        168,
+        1,
+        10,
+        25,
+        172,
+        31,
+        10,
+        0,
+        192,
+        168,
+        1,
+        10,
     ]
diff --git a/meta/main.yml b/meta/main.yml
index 72453d3..f53f389 100644
--- a/meta/main.yml
+++ b/meta/main.yml
@@ -5,23 +5,31 @@
 # SPDX-License-Identifier: Apache-2.0
 
 galaxy_info:
+  role_name: dhcpd
+
   author: Open Networking Foundation
-  description: DHCP server and optional TFTP server
+  description: Configures a ISC-style dhcpd server and optional TFTP server
   company: Open Networking Foundation
 
   issue_tracker_url: https://jira.opennetworking.org/
 
   license: Apache-2.0
 
-  min_ansible_version: 2.9.5
+  min_ansible_version: 2.10.17
 
   platforms:
-    - name: Ubuntu
+    - name: ubuntu
       versions:
-        - "16.04"
-        - "18.04"
+        - bionic
+    - name: Debian
+      versions:
+        - bullseye
+    - name: OpenBSD
+      versions:
+        - 7.0
 
   galaxy_tags:
     - dhcpd
+    - tftpd
 
 dependencies: []
diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml
index 13c2065..cd0cfb9 100644
--- a/molecule/default/converge.yml
+++ b/molecule/default/converge.yml
@@ -10,7 +10,7 @@
     dhcpd_interfaces:
       - bridge0
     dhcpd_subnets:
-      - subnet: "192.168.0.1/24"
+      "192.168.0.1/24":
         range: "192.168.0.128/25"
         dns_servers:
           - "192.168.0.1"
diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml
index 9deb838..8c53aea 100644
--- a/molecule/default/molecule.yml
+++ b/molecule/default/molecule.yml
@@ -14,7 +14,18 @@
     privileged: true
     volumes:
       - "/sys/fs/cgroup:/sys/fs/cgroup:ro"
+  - name: "debian-11-priv"
+    image: "onfinfra/molecule-systemd:debian-11"
+    privileged: true
+    volumes:
+      - "/sys/fs/cgroup:/sys/fs/cgroup:ro"
 provisioner:
   name: ansible
+  playbooks:
+    prepare: prepare.yml
+  inventory:
+    host_vars:
+      debian-11-priv:
+        ansible_python_interpreter: /usr/bin/python3
 verifier:
   name: ansible
diff --git a/molecule/default/prepare.yml b/molecule/default/prepare.yml
index 68ec77e..e6b92bf 100644
--- a/molecule/default/prepare.yml
+++ b/molecule/default/prepare.yml
@@ -12,6 +12,12 @@
       apt:
         update_cache: true
 
+    - name: Install ps command for Debian init.d script to function
+      apt:
+        name:
+          - "procps"
+        state: "present"
+
     - name: Create a bridge to nowhere so dhcpd can start during testing
       when: "'bridge0' not in ansible_interfaces"
       command:
diff --git a/templates/dhcpd.conf.j2 b/templates/dhcpd.conf.j2
index 3951fe5..56fafb5 100644
--- a/templates/dhcpd.conf.j2
+++ b/templates/dhcpd.conf.j2
@@ -13,15 +13,15 @@
 option rfc3442-classless-static-routes code 121 = array of integer 8;
 {% endif %}
 
-{% for subnet in dhcpd_subnets %}
-subnet {{ subnet.subnet | ipaddr('network') }} netmask {{ subnet.subnet | ipaddr('netmask') }} {
+{% for subnet, subdata in dhcpd_subnets.items() %}
+subnet {{ subnet | ipaddr('network') }} netmask {{ subnet | ipaddr('netmask') }} {
 
   # routing
-{% if subnet.routers is defined %}
+{% if subdata.routers is defined %}
   # custom router IP set
-  option routers {{ subnet.routers | map(attribute="ip") | join (",") }};
+  option routers {{ subdata.routers | map(attribute="ip") | join (",") }};
 {% set r3442ns = namespace(r3442list = []) %}
-{% for rtr in subnet.routers %}
+{% for rtr in subdata.routers %}
 {% if "rfc3442routes" in rtr %}
 {% set r3442ns.r3442list = r3442ns.r3442list + (rtr | rfc3442_words() ) %}
 {% endif %}
@@ -31,35 +31,35 @@
 {% endif %}
 {% else %}
   # first IP address in range used as router
-  option routers {{ subnet.subnet | ipaddr('next_usable') }};
+  option routers {{ subnet | ipaddr('next_usable') }};
 {% endif %}
 
   # DNS/naming options
-  option domain-name-servers {{ subnet.dns_servers | join(", ") }};
-  option domain-name "{{ subnet.dns_search [0] }}";
-  option domain-search "{{ subnet.dns_search | join('", "') }}";
+  option domain-name-servers {{ subdata.dns_servers | join(", ") }};
+  option domain-name "{{ subdata.dns_search [0] }}";
+  option domain-search "{{ subdata.dns_search | join('", "') }}";
 
-{% if subnet.tftpd_server is defined %}
+{% if subdata.tftpd_server is defined and subdata.tftpd_server %}
   # tftpd options
-  filename "{{ subnet.pxe_filename | default(dhcpd_pxe_filename) }}";
-  next-server {{ subnet.tftpd_server }};
+  filename "{{ subdata.pxe_filename | default(dhcpd_pxe_filename) }}";
+  next-server {{ subdata.tftpd_server }};
 
 {% endif %}
-{% if subnet.ntp_servers is defined %}
+{% if subdata.ntp_servers is defined and subdata.ntp_servers %}
   # ntp options
-  option ntp-servers {{ subnet.ntp_servers | join('", "') }};
+  option ntp-servers {{ subdata.ntp_servers | join('", "') }};
 
 {% endif %}
-{% if subnet.range is defined %}
+{% if subdata.range is defined %}
   # dynamically assignable range
-  range {{ subnet.range | ipaddr('next_usable') }} {{ subnet.range | ipaddr('last_usable') }};
+  range {{ subdata.range | ipaddr('next_usable') }} {{ subdata.range | ipaddr('last_usable') }};
 {% endif %}
 }
 
-{% if subnet.hosts is defined %}
-# hosts for subnet: {{ subnet.dns_search [0] }}
-{% for host in subnet.hosts %}
-host {{ host.name }}.{{ subnet.dns_search [0] }} {
+{% if subdata.hosts is defined %}
+# hosts for subnet: {{ subdata.dns_search [0] }}
+{% for host in subdata.hosts %}
+host {{ host.name }}.{{ subdata.dns_search [0] }} {
   option host-name "{{ host.name }}";
   fixed-address {{ host.ip_addr }};
   hardware ethernet {{ host.mac_addr | hwaddr('linux') }};
diff --git a/vars/OpenBSD.yml b/vars/OpenBSD.yml
index d1ffefc..aa86f49 100644
--- a/vars/OpenBSD.yml
+++ b/vars/OpenBSD.yml
@@ -13,4 +13,4 @@
 
 tftpd_service: "tftpd"
 tftpd_groupname: "_tftpd"
-tftpd_boot_dir: "/var/tftpboot"
+tftpd_boot_dir: "/var/tftpd"