diff --git a/README-host.md b/README-host.md
index 7ff7afa7a836fc712cf81a843c87061b68bf97d1..77ca098d1cd2a0c4315fb7758308e62da4458b39 100644
--- a/README-host.md
+++ b/README-host.md
@@ -41,7 +41,7 @@ ipaserver.test.local
 ```
 
 
-Example playbook to add hosts:
+Example playbook to ensure host presence:
 
 ```yaml
 ---
@@ -67,7 +67,7 @@ Example playbook to add hosts:
 ```
 
 
-Example playbook to create host without DNS:
+Example playbook to ensure host presence without DNS:
 
 ```yaml
 ---
@@ -85,28 +85,118 @@ Example playbook to create host without DNS:
 ```
 
 
-Example playbook to initiate the generation of a random password to be used in bulk enrollment:
+Example playbook to ensure host presence with a random password:
 
 ```yaml
 ---
-- name: Playbook to handle hosts
+- name: Ensure host with random password
   hosts: ipaserver
   become: true
 
   tasks:
-  # Generate a random password for bulk enrollment
-  - ipahost:
+  - name: Host host01.example.com present with random password
+    ipahost:
       ipaadmin_password: MyPassword123
       name: host01.example.com
-      description: Example host
-      ip_address: 192.168.0.123
       random: yes
+      force: yes
     register: ipahost
 
   - name: Print generated random password
     debug:
       var: ipahost.host.randompassword
 ```
+Please remember that the `force` tag will also force the generation of a new random password even if the host already exists and if `update_password` is limited to `on_create`.
+
+
+Example playbook to ensure presence of several hosts with a random password:
+
+```yaml
+---
+- name: Ensure hosts with random password
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Hosts host01.example.com and host01.example.com present with random passwords
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: host01.example.com
+        random: yes
+        force: yes
+      - name: host02.example.com
+        random: yes
+        force: yes
+    register: ipahost
+
+  - name: Print generated random password for host01.example.com
+    debug:
+      var: ipahost.host["host01.example.com"].randompassword
+
+  - name: Print generated random password for host02.example.com
+    debug:
+      var: ipahost.host["host02.example.com"].randompassword
+```
+Please remember that the `force` tag will also force the generation of a new random password even if the host alreay exists and if `update_password` is limited to `on_create`.
+
+
+Example playbook to ensure presence of host member principal:
+
+```yaml
+---
+- name: Host present with principal
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Host host01.example.com present with principals host/testhost01.example.com and host/myhost01.example.com
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: host01.example.com
+      principal:
+      - host/testhost01.example.com
+      - host/myhost01.example.com
+      action: member
+```
+
+
+Example playbook to ensure presence of host member certificate:
+
+```yaml
+- name: Host present with certificate
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Host host01.example.com present with certificate
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: host01.example.com
+      certificate:
+      - MIIC/zCCAeegAwIBAg...
+      action: member
+```
+
+
+Example playbook to ensure presence of member managedby_host for serveral hosts:
+
+```yaml
+---
+- name: Host present with managedby_host
+  hosts: ipaserver
+  become: true
+
+  tasks:
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: host01.exmaple.com
+        managedby_host: server.exmaple.com
+      - name: host02.exmaple.com
+        managedby_host: server.exmaple.com
+      action: member
+```
 
 
 Example playbook to disable a host:
@@ -155,7 +245,20 @@ Variable | Description | Required
 -------- | ----------- | --------
 `ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
 `ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
-`name` \| `fqdn` | The list of host name strings. | yes
+`name` \| `fqdn` | The list of host name strings. `name` with *host variables* or `hosts` containing *host variables* need to be used. | no
+**Host variables** | Only used with `name` variable in the first level. | no
+`hosts` | The list of host dicts. Each `hosts` dict entry can contain **host variables**.<br>There is one required option in the `hosts` dict:| no
+&nbsp; | `name` \| `fqdn` - The user name string of the entry. | yes
+&nbsp; | **Host variables** | no
+`update_password` |  Set password for a host in present state only on creation or always. It can be one of `always` or `on_create` and defaults to `always`. | no
+`action` | Work on host or member level. It can be on of `member` or `host` and defaults to `host`. | no
+`state` | The state to ensure. It can be one of `present`, `absent` or `disabled`, default: `present`. | yes
+
+
+**Host Variables:**
+
+Variable | Description | Required
+-------- | ----------- | --------
 `description` | The host description. | no
 `locality` | Host locality (e.g. "Baltimore, MD"). | no
 `location` \| `ns_host_location` | Host location (e.g. "Lab 2"). | no
@@ -163,13 +266,28 @@ Variable | Description | Required
 `os` \| `ns_os_version` | Host operating system and version (e.g. "Fedora 9"). | no
 `password` \| `user_password` \| `userpassword` | Password used in bulk enrollment. | no
 `random` \| `random_password` |  Initiate the generation of a random password to be used in bulk enrollment. | no
+`certificate` \| `usercertificate` | List of base-64 encoded host certificates | no
+`managedby` \| `principalname` \| `krbprincipalname` | List of hosts that can manage this host | no
+`principal` \| `principalname` \| `krbprincipalname` | List of principal aliases for this host | no
+`allow_create_keytab_user` \| `ipaallowedtoperform_write_keys_user` | Users allowed to create a keytab of this host. <br>Options: | no
+`allow_create_keytab_group` \| `ipaallowedtoperform_write_keys_group` | Groups allowed to create a keytab of this host. <br>Options: | no
+`allow_create_keytab_host` \| `ipaallowedtoperform_write_keys_host` | Hosts allowed to create a keytab of this host. <br>Options: | no
+`allow_create_keytab_hostgroup` \| `ipaallowedtoperform_write_keys_hostgroup` | Host groups allowed to create a keytab of this host. <br>Options: | no
+`allow_retrieve_keytab_user` \| `ipaallowedtoperform_read_keys_user` | Users allowed to retieve a keytab of this host. <br>Options: | no
+`allow_retrieve_keytab_group` \| `ipaallowedtoperform_read_keys_group` | Groups allowed to retieve a keytab of this host. <br>Options: | no
+`allow_retrieve_keytab_host` \| `ipaallowedtoperform_read_keys_host` | Hosts allowed to retieve a keytab of this host. <br>Options: | no
+`allow_retrieve_keytab_hostgroup` \| `ipaallowedtoperform_read_keys_hostgroup` | Host groups allowed to retieve a keytab of this host. <br>Options: | no
 `mac_address` \| `macaddress` | List of hardware MAC addresses. | no
+`sshpubkey` \| `ipasshpubkey` | List of SSH public keys | no
+`userclass` \| `class` | Host category (semantics placed on this attribute are for local interpretation) | no
+`auth_ind` \| `krbprincipalauthind` | Defines a whitelist for Authentication Indicators. Use 'otp' to allow OTP-based 2FA authentications. Use 'radius' to allow RADIUS-based 2FA authentications. Other values may be used for custom configurations. choices: ["radius", "otp", "pkinit", "hardened"] | no
+`requires_pre_auth` \| `ipakrbrequirespreauth` | Pre-authentication is required for the service (bool) | no
+`ok_as_delegate` \| `ipakrbokasdelegate` | Client credentials may be delegated to the service (bool) | no
+`ok_to_auth_as_delegate` \| `ipakrboktoauthasdelegate` | The service is allowed to authenticate on behalf of a client (bool) | no
 `force` | Force host name even if not in DNS. | no
 `reverse` | Reverse DNS detection. | no
 `ip_address` \| `ipaddress` | The host IP address. | no
 `update_dns` | Update DNS entries. | no
-`update_password` |  Set password for a host in present state only on creation or always. It can be one of `always` or `on_create` and defaults to `always`. | no
-`state` | The state to ensure. It can be one of `present`, `absent` or `disabled`, default: `present`. | yes
 
 
 Return Values
diff --git a/playbooks/host/add-host.yml b/playbooks/host/add-host.yml
deleted file mode 100644
index 61b8a958e6152c2176b6964454eacc137cb6f1c3..0000000000000000000000000000000000000000
--- a/playbooks/host/add-host.yml
+++ /dev/null
@@ -1,20 +0,0 @@
----
-- name: Playbook to handle hosts
-  hosts: ipaserver
-  become: true
-
-  tasks:
-  - name: Ensure host is present
-    ipahost:
-      ipaadmin_password: MyPassword123
-      name: host01.example.com
-      description: Example host
-      ip_address: 192.168.0.123
-      locality: Lab
-      ns_host_location: Lab
-      ns_os_version: CentOS 7
-      ns_hardware_platform: Lenovo T61
-      mac_address:
-      - "08:00:27:E3:B1:2D"
-      - "52:54:00:BD:97:1E"
-      state: present
diff --git a/playbooks/host/add-host.yml b/playbooks/host/add-host.yml
new file mode 120000
index 0000000000000000000000000000000000000000..6d5bc9a91bc32800ece3c9e845e4634916afe699
--- /dev/null
+++ b/playbooks/host/add-host.yml
@@ -0,0 +1 @@
+host-present.yml
\ No newline at end of file
diff --git a/playbooks/host/host-member-allow_create_keytab-absent.yml b/playbooks/host/host-member-allow_create_keytab-absent.yml
new file mode 100644
index 0000000000000000000000000000000000000000..55e3110d852a2f82c287c8f220869abe18f03983
--- /dev/null
+++ b/playbooks/host/host-member-allow_create_keytab-absent.yml
@@ -0,0 +1,24 @@
+---
+- name: Host member allow_create_keytab absent
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Host host1.example.com members allow_create_keytab absent for users, groups, hosts and hostgroups
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: host01.exmaple.com
+      allow_create_keytab_user:
+      - user01
+      - user02
+      allow_create_keytab_group:
+      - group01
+      - group02
+      allow_create_keytab_host:
+      - host02.exmaple.com
+      - host03.exmaple.com
+      allow_create_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
+      state: absent
diff --git a/playbooks/host/host-member-allow_create_keytab-present.yml b/playbooks/host/host-member-allow_create_keytab-present.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f5865497faa1716693b550cf5090931b9ee0da65
--- /dev/null
+++ b/playbooks/host/host-member-allow_create_keytab-present.yml
@@ -0,0 +1,23 @@
+---
+- name: Host member allow_create_keytab present
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Host host1.example.com members allow_create_keytab present for users, groups, hosts and hostgroups
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: host01.exmaple.com
+      allow_create_keytab_user:
+      - user01
+      - user02
+      allow_create_keytab_group:
+      - group01
+      - group02
+      allow_create_keytab_host:
+      - host02.exmaple.com
+      - host03.exmaple.com
+      allow_create_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
diff --git a/playbooks/host/host-member-allow_retrieve_keytab-absent.yml b/playbooks/host/host-member-allow_retrieve_keytab-absent.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b8830f605171830b3bf3fd30953e0fa50d290358
--- /dev/null
+++ b/playbooks/host/host-member-allow_retrieve_keytab-absent.yml
@@ -0,0 +1,24 @@
+---
+- name: Host member allow_retrieve_keytab absent
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Host host1.example.com members allow_retrieve_keytab absent for users, groups, hosts and hostgroups
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: host01.exmaple.com
+      allow_retrieve_keytab_user:
+      - user01
+      - user02
+      allow_retrieve_keytab_group:
+      - group01
+      - group02
+      allow_retrieve_keytab_host:
+      - host02.exmaple.com
+      - host03.exmaple.com
+      allow_retrieve_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
+      state: absent
diff --git a/playbooks/host/host-member-allow_retrieve_keytab-present.yml b/playbooks/host/host-member-allow_retrieve_keytab-present.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fde116cce25c705364749a8dfbc9e2bb3a14cd6e
--- /dev/null
+++ b/playbooks/host/host-member-allow_retrieve_keytab-present.yml
@@ -0,0 +1,23 @@
+---
+- name: Host member allow_retrieve_keytab present
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Host host1.example.com members allow_retrieve_keytab present for users, groups, hosts and hostgroups
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: host01.exmaple.com
+      allow_retrieve_keytab_user:
+      - user01
+      - user02
+      allow_retrieve_keytab_group:
+      - group01
+      - group02
+      allow_retrieve_keytab_host:
+      - host02.exmaple.com
+      - host03.exmaple.com
+      allow_retrieve_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      action: member
diff --git a/playbooks/host/host-member-certificate-absent.yml b/playbooks/host/host-member-certificate-absent.yml
new file mode 100644
index 0000000000000000000000000000000000000000..918951f82d64a8f405a8f239a16ba93df2c542d3
--- /dev/null
+++ b/playbooks/host/host-member-certificate-absent.yml
@@ -0,0 +1,13 @@
+- name: Host member certificate absent
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Host host01.example.com member certificate absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: host01.example.com
+      certificate:
+      - MIIC/zCCAeegAwIBAgIUZGHLaSYg1myp6EI4VGWSC27vOrswDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0xOTEwMTQxNjI4MzVaFw0yMDEwMTMxNjI4MzVaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDER/lB8wUAmPTSwSc/NOXNlzdpPOQDSwrhKH6XsqZF4KpQoSY/nmCjAhJmOVpOUo4K2fGRZ0yAH9fkGv6yJP6c7IAFjLeec7GPHVwN4bZrP1DXfTAmfmXhcRQbCYkV+wmq8Puzw/+xA9EJrrodnJPPsE6E8HnSVLF6Ys9+cJMJ7HuwOI+wYt3gkmspsir1tccmf4x1PP+yHJWdcXyetlFRcmZ8gspjqOR2jb89xSQsh8gcyDW6rPNlSTzYZ2FmNtjES6ZhCsYL31fQbF2QglidlLGpAlvHUUS+xCigW73cvhFPMWXcfO51Mr15RcgYTckY+7QZ2nYqplRBoDlQl6DnAgMBAAGjUzBRMB0GA1UdDgQWBBTPG99XVRdxpOXMZo3Nhy+ldnf13TAfBgNVHSMEGDAWgBTPG99XVRdxpOXMZo3Nhy+ldnf13TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAjWTcnIl2mpNbfHAN8DB4Kk+RNRmhsH0y+r/47MXVTMMMToCfofeNY3Jeohu+2lIXMPQfTvXUbDTkNAGsGLv6LtQEUfSREqgk1eY7bT9BFfpH1uV2ZFhCO9jBA+E4bf55Kx7bgUNG31ykBshOsOblOJM1lS/0q4TWHAxrsU2PNwPi8X0ten+eGeB8aRshxS17Ij2cH0fdAMmSA+jMAvTIZl853Bxe0HuozauKwOFWL4qHm61c4O/j1mQCLqJKYfJ9mBDWFQLszd/tF+ePKiNhZCQly60F8Lumn2CDZj5UIkl8wk9Wls5n1BIQs+M8AN65NAdv7+js8jKUKCuyji8r3
+      action: member
+      state: absent
diff --git a/playbooks/host/host-member-certificate-present.yml b/playbooks/host/host-member-certificate-present.yml
new file mode 100644
index 0000000000000000000000000000000000000000..066ae0a975a38271d4a645135a4dd10aba342746
--- /dev/null
+++ b/playbooks/host/host-member-certificate-present.yml
@@ -0,0 +1,12 @@
+- name: Host member certificate present
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Host host01.example.com member certificate present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: host01.example.com
+      certificate:
+      - MIIC/zCCAeegAwIBAgIUZGHLaSYg1myp6EI4VGWSC27vOrswDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0xOTEwMTQxNjI4MzVaFw0yMDEwMTMxNjI4MzVaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDER/lB8wUAmPTSwSc/NOXNlzdpPOQDSwrhKH6XsqZF4KpQoSY/nmCjAhJmOVpOUo4K2fGRZ0yAH9fkGv6yJP6c7IAFjLeec7GPHVwN4bZrP1DXfTAmfmXhcRQbCYkV+wmq8Puzw/+xA9EJrrodnJPPsE6E8HnSVLF6Ys9+cJMJ7HuwOI+wYt3gkmspsir1tccmf4x1PP+yHJWdcXyetlFRcmZ8gspjqOR2jb89xSQsh8gcyDW6rPNlSTzYZ2FmNtjES6ZhCsYL31fQbF2QglidlLGpAlvHUUS+xCigW73cvhFPMWXcfO51Mr15RcgYTckY+7QZ2nYqplRBoDlQl6DnAgMBAAGjUzBRMB0GA1UdDgQWBBTPG99XVRdxpOXMZo3Nhy+ldnf13TAfBgNVHSMEGDAWgBTPG99XVRdxpOXMZo3Nhy+ldnf13TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAjWTcnIl2mpNbfHAN8DB4Kk+RNRmhsH0y+r/47MXVTMMMToCfofeNY3Jeohu+2lIXMPQfTvXUbDTkNAGsGLv6LtQEUfSREqgk1eY7bT9BFfpH1uV2ZFhCO9jBA+E4bf55Kx7bgUNG31ykBshOsOblOJM1lS/0q4TWHAxrsU2PNwPi8X0ten+eGeB8aRshxS17Ij2cH0fdAMmSA+jMAvTIZl853Bxe0HuozauKwOFWL4qHm61c4O/j1mQCLqJKYfJ9mBDWFQLszd/tF+ePKiNhZCQly60F8Lumn2CDZj5UIkl8wk9Wls5n1BIQs+M8AN65NAdv7+js8jKUKCuyji8r3
+      action: member
diff --git a/playbooks/host/host-member-managedby_host-absent.yml b/playbooks/host/host-member-managedby_host-absent.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f899a52fa6c6756b419aa256c1da1252476fc7e6
--- /dev/null
+++ b/playbooks/host/host-member-managedby_host-absent.yml
@@ -0,0 +1,12 @@
+---
+- name: Host member managedby_host absent
+  hosts: ipaserver
+  become: true
+
+  tasks:
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: host01.exmaple.com
+      managedby_host: server.exmaple.com
+      action: member
+      state: absent
diff --git a/playbooks/host/host-member-managedby_host-present.yml b/playbooks/host/host-member-managedby_host-present.yml
new file mode 100644
index 0000000000000000000000000000000000000000..073d81ad6335bd61f7d7e853ce72bd562e4927cb
--- /dev/null
+++ b/playbooks/host/host-member-managedby_host-present.yml
@@ -0,0 +1,11 @@
+---
+- name: Host member managedby_host present
+  hosts: ipaserver
+  become: true
+
+  tasks:
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: host01.exmaple.com
+      managedby_host: server.exmaple.com
+      action: member
diff --git a/playbooks/host/host-member-principal-absent.yml b/playbooks/host/host-member-principal-absent.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b2c3a8d88191f8a6c0fa0ff51a36d3315ea42d36
--- /dev/null
+++ b/playbooks/host/host-member-principal-absent.yml
@@ -0,0 +1,15 @@
+---
+- name: Host member principal absent
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Host host01.example.com principals host/testhost01.example.com and host/myhost01.example.com absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: host01.example.com
+      principal:
+      - host/testhost01.example.com
+      - host/myhost01.example.com
+      action: member
+      state: absent
diff --git a/playbooks/host/host-member-principal-present.yml b/playbooks/host/host-member-principal-present.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b56fd591276493536d9d35987c355fea957c02b7
--- /dev/null
+++ b/playbooks/host/host-member-principal-present.yml
@@ -0,0 +1,14 @@
+---
+- name: Host member principal present
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Host host01.example.com principals host/testhost01.example.com and host/myhost01.example.com present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: host01.example.com
+      principal:
+      - host/testhost01.example.com
+      - host/myhost01.example.com
+      action: member
diff --git a/playbooks/host/host-present-with-allow_create_keytab.yml b/playbooks/host/host-present-with-allow_create_keytab.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f7b9c6f43e8925c6e4520c0840b89211a64f43de
--- /dev/null
+++ b/playbooks/host/host-present-with-allow_create_keytab.yml
@@ -0,0 +1,23 @@
+---
+- name: Host present with allow_create_keytab
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Host host1.example.com present with allow_create_keytab for users, groups, hosts and hostgroups
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: host01.exmaple.com
+      allow_create_keytab_user:
+      - user01
+      - user02
+      allow_create_keytab_group:
+      - group01
+      - group02
+      allow_create_keytab_host:
+      - host02.exmaple.com
+      - host03.exmaple.com
+      allow_create_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      ip_address: 192.168.0.123
diff --git a/playbooks/host/host-present-with-allow_retrieve_keytab.yml b/playbooks/host/host-present-with-allow_retrieve_keytab.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5a9f3af5a9b2e2c88f7562f756ba80ee5558df96
--- /dev/null
+++ b/playbooks/host/host-present-with-allow_retrieve_keytab.yml
@@ -0,0 +1,23 @@
+---
+- name: Host present with allow_retrieve_keytab
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Host host1.example.com present with allow_retrieve_keytab for users, groups, hosts and hostgroups
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: host01.exmaple.com
+      allow_retrieve_keytab_user:
+      - user01
+      - user02
+      allow_retrieve_keytab_group:
+      - group01
+      - group02
+      allow_retrieve_keytab_host:
+      - host02.exmaple.com
+      - host03.exmaple.com
+      allow_retrieve_keytab_hostgroup:
+      - hostgroup01
+      - hostgroup02
+      ip_address: 192.168.0.123
diff --git a/playbooks/host/host-present-with-certificate.yml b/playbooks/host/host-present-with-certificate.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f5da46d72577ec2f19ccf795fcda30b88c6f589e
--- /dev/null
+++ b/playbooks/host/host-present-with-certificate.yml
@@ -0,0 +1,12 @@
+- name: Host present with certificate
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Host host01.example.com present with certificate
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: host01.example.com
+      certificate:
+      - MIIC/zCCAeegAwIBAgIUZGHLaSYg1myp6EI4VGWSC27vOrswDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0xOTEwMTQxNjI4MzVaFw0yMDEwMTMxNjI4MzVaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDER/lB8wUAmPTSwSc/NOXNlzdpPOQDSwrhKH6XsqZF4KpQoSY/nmCjAhJmOVpOUo4K2fGRZ0yAH9fkGv6yJP6c7IAFjLeec7GPHVwN4bZrP1DXfTAmfmXhcRQbCYkV+wmq8Puzw/+xA9EJrrodnJPPsE6E8HnSVLF6Ys9+cJMJ7HuwOI+wYt3gkmspsir1tccmf4x1PP+yHJWdcXyetlFRcmZ8gspjqOR2jb89xSQsh8gcyDW6rPNlSTzYZ2FmNtjES6ZhCsYL31fQbF2QglidlLGpAlvHUUS+xCigW73cvhFPMWXcfO51Mr15RcgYTckY+7QZ2nYqplRBoDlQl6DnAgMBAAGjUzBRMB0GA1UdDgQWBBTPG99XVRdxpOXMZo3Nhy+ldnf13TAfBgNVHSMEGDAWgBTPG99XVRdxpOXMZo3Nhy+ldnf13TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAjWTcnIl2mpNbfHAN8DB4Kk+RNRmhsH0y+r/47MXVTMMMToCfofeNY3Jeohu+2lIXMPQfTvXUbDTkNAGsGLv6LtQEUfSREqgk1eY7bT9BFfpH1uV2ZFhCO9jBA+E4bf55Kx7bgUNG31ykBshOsOblOJM1lS/0q4TWHAxrsU2PNwPi8X0ten+eGeB8aRshxS17Ij2cH0fdAMmSA+jMAvTIZl853Bxe0HuozauKwOFWL4qHm61c4O/j1mQCLqJKYfJ9mBDWFQLszd/tF+ePKiNhZCQly60F8Lumn2CDZj5UIkl8wk9Wls5n1BIQs+M8AN65NAdv7+js8jKUKCuyji8r3
+      force: yes
diff --git a/playbooks/host/host-present-with-managedby_host.yml b/playbooks/host/host-present-with-managedby_host.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b85f5e60ecadd5acf8c96805be7ecff22edcf69e
--- /dev/null
+++ b/playbooks/host/host-present-with-managedby_host.yml
@@ -0,0 +1,11 @@
+---
+- name: Host present with managedby_host
+  hosts: ipaserver
+  become: true
+
+  tasks:
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: host01.exmaple.com
+      managedby_host: server.exmaple.com
+      force: yes
diff --git a/playbooks/host/host-present-with-principal.yml b/playbooks/host/host-present-with-principal.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5b8ad4eefa8fe27baaad0249826f88f06fe64fab
--- /dev/null
+++ b/playbooks/host/host-present-with-principal.yml
@@ -0,0 +1,14 @@
+---
+- name: Host present with principal
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Host host01.example.com present with principals host/testhost01.example.com and host/myhost01.example.com
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: host01.example.com
+      principal:
+      - host/testhost01.example.com
+      - host/myhost01.example.com
+      force: yes
diff --git a/playbooks/host/host-present-with-randompassword.yml b/playbooks/host/host-present-with-randompassword.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9063c48061b6a9f3e26073a0bc2e7afc5f136c53
--- /dev/null
+++ b/playbooks/host/host-present-with-randompassword.yml
@@ -0,0 +1,17 @@
+---
+- name: Host present with random password
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Host host01.example.com present with random password
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: host01.example.com
+      random: yes
+      force: yes
+    register: ipahost
+
+  - name: Print generated random password
+    debug:
+      var: ipahost.host.randompassword
diff --git a/playbooks/host/host-present.yml b/playbooks/host/host-present.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d40c1ecf21e1281e7d6c8844b5499606ed4b3fec
--- /dev/null
+++ b/playbooks/host/host-present.yml
@@ -0,0 +1,20 @@
+---
+- name: Host present
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Ensure host is present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: host01.example.com
+      description: Example host
+      ip_address: 192.168.0.123
+      locality: Lab
+      ns_host_location: Lab
+      ns_os_version: CentOS 7
+      ns_hardware_platform: Lenovo T61
+      mac_address:
+      - "08:00:27:E3:B1:2D"
+      - "52:54:00:BD:97:1E"
+      state: present
diff --git a/playbooks/host/hosts-member-certificate-absent.yml b/playbooks/host/hosts-member-certificate-absent.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bb2d5b0393d5474a01bdfbd5bbe3bb30ca4fc383
--- /dev/null
+++ b/playbooks/host/hosts-member-certificate-absent.yml
@@ -0,0 +1,18 @@
+---
+- name: Hosts member certificate absent
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Hosts host01.example.com and host01.exmaple.com member certificate absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: host01.example.com
+        certificate:
+        - MIIC/zCCAeegAwIBAgIUZGHLaSYg1myp6EI4VGWSC27vOrswDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0xOTEwMTQxNjI4MzVaFw0yMDEwMTMxNjI4MzVaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDER/lB8wUAmPTSwSc/NOXNlzdpPOQDSwrhKH6XsqZF4KpQoSY/nmCjAhJmOVpOUo4K2fGRZ0yAH9fkGv6yJP6c7IAFjLeec7GPHVwN4bZrP1DXfTAmfmXhcRQbCYkV+wmq8Puzw/+xA9EJrrodnJPPsE6E8HnSVLF6Ys9+cJMJ7HuwOI+wYt3gkmspsir1tccmf4x1PP+yHJWdcXyetlFRcmZ8gspjqOR2jb89xSQsh8gcyDW6rPNlSTzYZ2FmNtjES6ZhCsYL31fQbF2QglidlLGpAlvHUUS+xCigW73cvhFPMWXcfO51Mr15RcgYTckY+7QZ2nYqplRBoDlQl6DnAgMBAAGjUzBRMB0GA1UdDgQWBBTPG99XVRdxpOXMZo3Nhy+ldnf13TAfBgNVHSMEGDAWgBTPG99XVRdxpOXMZo3Nhy+ldnf13TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAjWTcnIl2mpNbfHAN8DB4Kk+RNRmhsH0y+r/47MXVTMMMToCfofeNY3Jeohu+2lIXMPQfTvXUbDTkNAGsGLv6LtQEUfSREqgk1eY7bT9BFfpH1uV2ZFhCO9jBA+E4bf55Kx7bgUNG31ykBshOsOblOJM1lS/0q4TWHAxrsU2PNwPi8X0ten+eGeB8aRshxS17Ij2cH0fdAMmSA+jMAvTIZl853Bxe0HuozauKwOFWL4qHm61c4O/j1mQCLqJKYfJ9mBDWFQLszd/tF+ePKiNhZCQly60F8Lumn2CDZj5UIkl8wk9Wls5n1BIQs+M8AN65NAdv7+js8jKUKCuyji8r3
+      - name: host02.example.com
+        certificate:
+        - MIIC/zCCAeegAwIBAgIUAWE1vaA+mZd3nwZqwWH64EbHvR0wDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0xOTEwMTQxNjI4NDVaFw0yMDEwMTMxNjI4NDVaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCWzJibKtN8Zf7LgandINhFonx99AKi44iaZkrlMKEObE6Faf8NTUbUgK3VfJNYmCbA1baLVJ0YZJijJ7S/4o7h7eeqcJVXJkEhWNTimWXNW/YCzTHe3SSapnSYOKmdHHRClplysL8OyyEG7pbX/aB9iAfFb/+vUFCX5sMwFFrYxOimKJ9Pc/NRFtdv1wNw1rqWKF1ZzagWRlG4QgzRGwQ4quc7yO98TKikj2OPiIt7Zd46hbqQxmgGBtCkVOZIhxu77OmNrFsXmM4rZZpmqh0UdqcpwkRojVnGXmNqeMCd6dNTnLhr9wukUYw0KgE57zCDVr9Ix+p/dA5R1mG4RJ2XAgMBAAGjUzBRMB0GA1UdDgQWBBSbuiH2lNVrID3yt1SsFwtOFKOnpTAfBgNVHSMEGDAWgBSbuiH2lNVrID3yt1SsFwtOFKOnpTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBCVWd293wWyohFqMFMHRBBg97T2Uc1yeT0dMH4BpuOaCqQp4q5ep+uLcXEI6+3mEwm8pa/ULQCD8yLLdotIWlG3+h/4boFpdiPFcBDgT8kGe+0KOzB8Nt7E13QYOu12MNi10qwGrjKhdhu1xBe4fpY5VCetVU1OLyuTsUyucQsFrtZI0SR83h+blbyoMZ7IhMngCfGUe1bnYeWnLbpFbigKfPuVDWsMH2kgj05EAd5EgHkWbX8QA8hmcmDKfNT3YZM8kiGQwmFrnQdq8bN0uHR8Nz+24cbmdbHcD65wlDW6GmYxi8mW+V6bAqn9pir/J14r4YFnqMGgjmdt81tscJV
+      action: member
+      state: absent
diff --git a/playbooks/host/hosts-member-certificate-present.yml b/playbooks/host/hosts-member-certificate-present.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c402ef4a04cdc36452ec9c630c34281af02b9354
--- /dev/null
+++ b/playbooks/host/hosts-member-certificate-present.yml
@@ -0,0 +1,17 @@
+---
+- name: Hosts member certificate present
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Hosts host01.example.com and host01.exmaple.com member certificate present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: host01.example.com
+        certificate:
+        - MIIC/zCCAeegAwIBAgIUZGHLaSYg1myp6EI4VGWSC27vOrswDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0xOTEwMTQxNjI4MzVaFw0yMDEwMTMxNjI4MzVaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDER/lB8wUAmPTSwSc/NOXNlzdpPOQDSwrhKH6XsqZF4KpQoSY/nmCjAhJmOVpOUo4K2fGRZ0yAH9fkGv6yJP6c7IAFjLeec7GPHVwN4bZrP1DXfTAmfmXhcRQbCYkV+wmq8Puzw/+xA9EJrrodnJPPsE6E8HnSVLF6Ys9+cJMJ7HuwOI+wYt3gkmspsir1tccmf4x1PP+yHJWdcXyetlFRcmZ8gspjqOR2jb89xSQsh8gcyDW6rPNlSTzYZ2FmNtjES6ZhCsYL31fQbF2QglidlLGpAlvHUUS+xCigW73cvhFPMWXcfO51Mr15RcgYTckY+7QZ2nYqplRBoDlQl6DnAgMBAAGjUzBRMB0GA1UdDgQWBBTPG99XVRdxpOXMZo3Nhy+ldnf13TAfBgNVHSMEGDAWgBTPG99XVRdxpOXMZo3Nhy+ldnf13TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAjWTcnIl2mpNbfHAN8DB4Kk+RNRmhsH0y+r/47MXVTMMMToCfofeNY3Jeohu+2lIXMPQfTvXUbDTkNAGsGLv6LtQEUfSREqgk1eY7bT9BFfpH1uV2ZFhCO9jBA+E4bf55Kx7bgUNG31ykBshOsOblOJM1lS/0q4TWHAxrsU2PNwPi8X0ten+eGeB8aRshxS17Ij2cH0fdAMmSA+jMAvTIZl853Bxe0HuozauKwOFWL4qHm61c4O/j1mQCLqJKYfJ9mBDWFQLszd/tF+ePKiNhZCQly60F8Lumn2CDZj5UIkl8wk9Wls5n1BIQs+M8AN65NAdv7+js8jKUKCuyji8r3
+      - name: host02.example.com
+        certificate:
+        - MIIC/zCCAeegAwIBAgIUAWE1vaA+mZd3nwZqwWH64EbHvR0wDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0xOTEwMTQxNjI4NDVaFw0yMDEwMTMxNjI4NDVaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCWzJibKtN8Zf7LgandINhFonx99AKi44iaZkrlMKEObE6Faf8NTUbUgK3VfJNYmCbA1baLVJ0YZJijJ7S/4o7h7eeqcJVXJkEhWNTimWXNW/YCzTHe3SSapnSYOKmdHHRClplysL8OyyEG7pbX/aB9iAfFb/+vUFCX5sMwFFrYxOimKJ9Pc/NRFtdv1wNw1rqWKF1ZzagWRlG4QgzRGwQ4quc7yO98TKikj2OPiIt7Zd46hbqQxmgGBtCkVOZIhxu77OmNrFsXmM4rZZpmqh0UdqcpwkRojVnGXmNqeMCd6dNTnLhr9wukUYw0KgE57zCDVr9Ix+p/dA5R1mG4RJ2XAgMBAAGjUzBRMB0GA1UdDgQWBBSbuiH2lNVrID3yt1SsFwtOFKOnpTAfBgNVHSMEGDAWgBSbuiH2lNVrID3yt1SsFwtOFKOnpTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBCVWd293wWyohFqMFMHRBBg97T2Uc1yeT0dMH4BpuOaCqQp4q5ep+uLcXEI6+3mEwm8pa/ULQCD8yLLdotIWlG3+h/4boFpdiPFcBDgT8kGe+0KOzB8Nt7E13QYOu12MNi10qwGrjKhdhu1xBe4fpY5VCetVU1OLyuTsUyucQsFrtZI0SR83h+blbyoMZ7IhMngCfGUe1bnYeWnLbpFbigKfPuVDWsMH2kgj05EAd5EgHkWbX8QA8hmcmDKfNT3YZM8kiGQwmFrnQdq8bN0uHR8Nz+24cbmdbHcD65wlDW6GmYxi8mW+V6bAqn9pir/J14r4YFnqMGgjmdt81tscJV
+      action: member
diff --git a/playbooks/host/hosts-member-managedby_host-absent.yml b/playbooks/host/hosts-member-managedby_host-absent.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9b584e5165fb219e290d5e1471628d0fce30ed50
--- /dev/null
+++ b/playbooks/host/hosts-member-managedby_host-absent.yml
@@ -0,0 +1,15 @@
+---
+- name: Hosts member managedby_host absent
+  hosts: ipaserver
+  become: true
+
+  tasks:
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: host01.exmaple.com
+        managedby_host: server.exmaple.com
+      - name: host02.exmaple.com
+        managedby_host: server.exmaple.com
+      action: member
+      state: absent
diff --git a/playbooks/host/hosts-member-managedby_host-present.yml b/playbooks/host/hosts-member-managedby_host-present.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f8d4e2b79790dbb170fda08210a3131bc1c77745
--- /dev/null
+++ b/playbooks/host/hosts-member-managedby_host-present.yml
@@ -0,0 +1,14 @@
+---
+- name: Hosts member managedby_host present
+  hosts: ipaserver
+  become: true
+
+  tasks:
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: host01.exmaple.com
+        managedby_host: server.exmaple.com
+      - name: host02.exmaple.com
+        managedby_host: server.exmaple.com
+      action: member
diff --git a/playbooks/host/hosts-member-principal-absent.yml b/playbooks/host/hosts-member-principal-absent.yml
new file mode 100644
index 0000000000000000000000000000000000000000..edd33521ea89e6bba2981493e51ac121e7e2c914
--- /dev/null
+++ b/playbooks/host/hosts-member-principal-absent.yml
@@ -0,0 +1,18 @@
+---
+- name: Host member principal absent
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Hosts host01.exmaple.com and host02.exmaple.com member principals host/testhost0X.exmaple.com absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: host01.exmaple.com
+        principal:
+        - host/testhost01.exmaple.com
+      - name: host02.exmaple.com
+        principal:
+        - host/testhost02.exmaple.com
+      action: member
+      state: absent
diff --git a/playbooks/host/hosts-member-principal-present.yml b/playbooks/host/hosts-member-principal-present.yml
new file mode 100644
index 0000000000000000000000000000000000000000..54c9a8f6e467d91e3fb44c0ad6bb20792a9c772c
--- /dev/null
+++ b/playbooks/host/hosts-member-principal-present.yml
@@ -0,0 +1,17 @@
+---
+- name: Hosts member principal present
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Hosts host01.exmaple.com and host02.exmaple.com member principals host/testhost0X.exmaple.com present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: host01.exmaple.com
+        principal:
+        - host/testhost01.exmaple.com
+      - name: host02.exmaple.com
+        principal:
+        - host/testhost02.exmaple.com
+      action: member
diff --git a/playbooks/host/hosts-present-with-certificate.yml b/playbooks/host/hosts-present-with-certificate.yml
new file mode 100644
index 0000000000000000000000000000000000000000..34e402f741a355ade66d54f3865f1d8a871ff44e
--- /dev/null
+++ b/playbooks/host/hosts-present-with-certificate.yml
@@ -0,0 +1,17 @@
+---
+- name: Hosts present with certificate
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Hosts host01.example.com and host01.exmaple.com present with certificate
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: host01.example.com
+        certificate:
+        - MIIC/zCCAeegAwIBAgIUZGHLaSYg1myp6EI4VGWSC27vOrswDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0xOTEwMTQxNjI4MzVaFw0yMDEwMTMxNjI4MzVaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDER/lB8wUAmPTSwSc/NOXNlzdpPOQDSwrhKH6XsqZF4KpQoSY/nmCjAhJmOVpOUo4K2fGRZ0yAH9fkGv6yJP6c7IAFjLeec7GPHVwN4bZrP1DXfTAmfmXhcRQbCYkV+wmq8Puzw/+xA9EJrrodnJPPsE6E8HnSVLF6Ys9+cJMJ7HuwOI+wYt3gkmspsir1tccmf4x1PP+yHJWdcXyetlFRcmZ8gspjqOR2jb89xSQsh8gcyDW6rPNlSTzYZ2FmNtjES6ZhCsYL31fQbF2QglidlLGpAlvHUUS+xCigW73cvhFPMWXcfO51Mr15RcgYTckY+7QZ2nYqplRBoDlQl6DnAgMBAAGjUzBRMB0GA1UdDgQWBBTPG99XVRdxpOXMZo3Nhy+ldnf13TAfBgNVHSMEGDAWgBTPG99XVRdxpOXMZo3Nhy+ldnf13TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAjWTcnIl2mpNbfHAN8DB4Kk+RNRmhsH0y+r/47MXVTMMMToCfofeNY3Jeohu+2lIXMPQfTvXUbDTkNAGsGLv6LtQEUfSREqgk1eY7bT9BFfpH1uV2ZFhCO9jBA+E4bf55Kx7bgUNG31ykBshOsOblOJM1lS/0q4TWHAxrsU2PNwPi8X0ten+eGeB8aRshxS17Ij2cH0fdAMmSA+jMAvTIZl853Bxe0HuozauKwOFWL4qHm61c4O/j1mQCLqJKYfJ9mBDWFQLszd/tF+ePKiNhZCQly60F8Lumn2CDZj5UIkl8wk9Wls5n1BIQs+M8AN65NAdv7+js8jKUKCuyji8r3
+      - name: host02.example.com
+        certificate:
+        - MIIC/zCCAeegAwIBAgIUAWE1vaA+mZd3nwZqwWH64EbHvR0wDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0xOTEwMTQxNjI4NDVaFw0yMDEwMTMxNjI4NDVaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCWzJibKtN8Zf7LgandINhFonx99AKi44iaZkrlMKEObE6Faf8NTUbUgK3VfJNYmCbA1baLVJ0YZJijJ7S/4o7h7eeqcJVXJkEhWNTimWXNW/YCzTHe3SSapnSYOKmdHHRClplysL8OyyEG7pbX/aB9iAfFb/+vUFCX5sMwFFrYxOimKJ9Pc/NRFtdv1wNw1rqWKF1ZzagWRlG4QgzRGwQ4quc7yO98TKikj2OPiIt7Zd46hbqQxmgGBtCkVOZIhxu77OmNrFsXmM4rZZpmqh0UdqcpwkRojVnGXmNqeMCd6dNTnLhr9wukUYw0KgE57zCDVr9Ix+p/dA5R1mG4RJ2XAgMBAAGjUzBRMB0GA1UdDgQWBBSbuiH2lNVrID3yt1SsFwtOFKOnpTAfBgNVHSMEGDAWgBSbuiH2lNVrID3yt1SsFwtOFKOnpTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBCVWd293wWyohFqMFMHRBBg97T2Uc1yeT0dMH4BpuOaCqQp4q5ep+uLcXEI6+3mEwm8pa/ULQCD8yLLdotIWlG3+h/4boFpdiPFcBDgT8kGe+0KOzB8Nt7E13QYOu12MNi10qwGrjKhdhu1xBe4fpY5VCetVU1OLyuTsUyucQsFrtZI0SR83h+blbyoMZ7IhMngCfGUe1bnYeWnLbpFbigKfPuVDWsMH2kgj05EAd5EgHkWbX8QA8hmcmDKfNT3YZM8kiGQwmFrnQdq8bN0uHR8Nz+24cbmdbHcD65wlDW6GmYxi8mW+V6bAqn9pir/J14r4YFnqMGgjmdt81tscJV
+      force: yes
diff --git a/playbooks/host/hosts-present-with-managedby_host.yml b/playbooks/host/hosts-present-with-managedby_host.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5f3546b6fff68e37daaa719f9127663410ccccbf
--- /dev/null
+++ b/playbooks/host/hosts-present-with-managedby_host.yml
@@ -0,0 +1,15 @@
+---
+- name: Host present with managedby_host
+  hosts: ipaserver
+  become: true
+
+  tasks:
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: host01.exmaple.com
+        managedby_host: server.exmaple.com
+        force: yes
+      - name: host02.exmaple.com
+        managedby_host: server.exmaple.com
+        force: yes
diff --git a/playbooks/host/hosts-present-with-randompasswords.yml b/playbooks/host/hosts-present-with-randompasswords.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f747ca31a5d1f906110757a1678789c27bd9adb2
--- /dev/null
+++ b/playbooks/host/hosts-present-with-randompasswords.yml
@@ -0,0 +1,26 @@
+---
+- name: Hosts present with random passwords
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Hosts host01.example.com and host01.example.com present with random passwords
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: host01.example.com
+        random: yes
+        force: yes
+      - name: host02.example.com
+        random: yes
+        force: yes
+    register: ipahost
+
+  - name: Print generated random password for host01.example.com
+    debug:
+      var: ipahost.host["host01.example.com"].randompassword
+
+  - name: Print generated random password for host02.example.com
+    debug:
+      var: ipahost.host["host02.example.com"].randompassword
+
diff --git a/plugins/module_utils/ansible_freeipa_module.py b/plugins/module_utils/ansible_freeipa_module.py
index 78895c57084d58c57f25b6152aaf49e2b2172126..a8312bc5c2c0306129398fb6c91c226d57679167 100644
--- a/plugins/module_utils/ansible_freeipa_module.py
+++ b/plugins/module_utils/ansible_freeipa_module.py
@@ -38,6 +38,11 @@ from ipapython.ipautil import run
 from ipaplatform.paths import paths
 from ipalib.krb_utils import get_credentials_if_valid
 from ansible.module_utils._text import to_text
+try:
+    from ipalib.x509 import Encoding
+except ImportError:
+    from cryptography.hazmat.primitives.serialization import Encoding
+import base64
 import six
 
 
@@ -238,3 +243,28 @@ def module_params_get(module, name):
 
 def api_get_realm():
     return api.env.realm
+
+
+def gen_add_del_lists(user_list, res_list):
+    """
+    Generate the lists for the addition and removal of members using the
+    provided user and ipa settings
+    """
+    add_list = list(set(user_list or []) - set(res_list or []))
+    del_list = list(set(res_list or []) - set(user_list or []))
+
+    return add_list, del_list
+
+
+def encode_certificate(cert):
+    """
+    Encode a certificate using base64 with also taking FreeIPA and Python
+    versions into account
+    """
+    if isinstance(cert, str) or isinstance(cert, unicode):
+        encoded = base64.b64encode(cert)
+    else:
+        encoded = base64.b64encode(cert.public_bytes(Encoding.DER))
+    if not six.PY2:
+        encoded = encoded.decode('ascii')
+    return encoded
diff --git a/plugins/modules/ipahost.py b/plugins/modules/ipahost.py
index 4a240bdb00fd01b9a68235394fdb970f277e2f27..ec5e19630debc8989ed3264e3c1b1ef36a831a5d 100644
--- a/plugins/modules/ipahost.py
+++ b/plugins/modules/ipahost.py
@@ -42,6 +42,145 @@ options:
     description: The full qualified domain name.
     aliases: ["fqdn"]
     required: true
+
+  hosts:
+    description: The list of user host dicts
+    required: false
+    options:
+      name:
+        description: The host (internally uid).
+        aliases: ["fqdn"]
+        required: true
+      description:
+        description: The host description
+        required: false
+      locality:
+        description: Host locality (e.g. "Baltimore, MD")
+        required: false
+      location:
+        description: Host location (e.g. "Lab 2")
+        aliases: ["ns_host_location"]
+        required: false
+      platform:
+        description: Host hardware platform (e.g. "Lenovo T61")
+        aliases: ["ns_hardware_platform"]
+        required: false
+      os:
+        description: Host operating system and version (e.g. "Fedora 9")
+        aliases: ["ns_os_version"]
+        required: false
+      password:
+        description: Password used in bulk enrollment
+        aliases: ["user_password", "userpassword"]
+        required: false
+      random:
+        description:
+          Initiate the generation of a random password to be used in bulk
+          enrollment
+        aliases: ["random_password"]
+        required: false
+      certificate:
+        description: List of base-64 encoded host certificates
+        type: list
+        aliases: ["usercertificate"]
+        required: false
+      managedby_host:
+        description: List of hosts that can manage this host
+        type: list
+        aliases: ["principalname", "krbprincipalname"]
+        required: false
+      principal:
+        description: List of principal aliases for this host
+        type: list
+        aliases: ["principalname", "krbprincipalname"]
+        required: false
+      allow_create_keytab_user:
+        description: Users allowed to create a keytab of this host
+        aliases: ["ipaallowedtoperform_write_keys_user"]
+        required: false
+      allow_create_keytab_group:
+        description: Groups allowed to create a keytab of this host
+        aliases: ["ipaallowedtoperform_write_keys_group"]
+        required: false
+      allow_create_keytab_host:
+        description: Hosts allowed to create a keytab of this host
+        aliases: ["ipaallowedtoperform_write_keys_host"]
+        required: false
+      allow_create_keytab_hostgroup:
+        description: Hostgroups allowed to create a keytab of this host
+        aliases: ["ipaallowedtoperform_write_keys_hostgroup"]
+        required: false
+      allow_retrieve_keytab_user:
+        description: Users allowed to retrieve a keytab of this host
+        aliases: ["ipaallowedtoperform_read_keys_user"]
+        required: false
+      allow_retrieve_keytab_group:
+        description: Groups allowed to retrieve a keytab of this host
+        aliases: ["ipaallowedtoperform_read_keys_group"]
+        required: false
+      allow_retrieve_keytab_host:
+        description: Hosts allowed to retrieve a keytab of this host
+        aliases: ["ipaallowedtoperform_read_keys_host"]
+        required: false
+      allow_retrieve_keytab_hostgroup:
+        description: Hostgroups allowed to retrieve a keytab of this host
+        aliases: ["ipaallowedtoperform_read_keys_hostgroup"]
+        required: false
+      mac_address:
+        description: List of hardware MAC addresses.
+        type: list
+        aliases: ["macaddress"]
+        required: false
+      sshpubkey:
+        description: List of SSH public keys
+        type: list
+        aliases: ["ipasshpubkey"]
+        required: false
+      userclass:
+        description:
+          Host category (semantics placed on this attribute are for local
+          interpretation)
+        aliases: ["class"]
+        required: false
+      auth_ind:
+        description:
+          Defines a whitelist for Authentication Indicators. Use 'otp' to allow
+          OTP-based 2FA authentications. Use 'radius' to allow RADIUS-based 2FA
+          authentications. Other values may be used for custom configurations.
+        type: list
+        aliases: ["krbprincipalauthind"]
+        choices: ["radius", "otp", "pkinit", "hardened"]
+        required: false
+      requires_pre_auth:
+        description: Pre-authentication is required for the service
+        type: bool
+        aliases: ["ipakrbrequirespreauth"]
+        required: false
+      ok_as_delegate:
+        description: Client credentials may be delegated to the service
+        type: bool
+        aliases: ["ipakrbokasdelegate"]
+        required: false
+      ok_to_auth_as_delegate:
+        description:
+          The service is allowed to authenticate on behalf of a client
+        type: bool
+        aliases: ["ipakrboktoauthasdelegate"]
+        required: false
+      force:
+        description: Force host name even if not in DNS
+        required: false
+      reverse:
+        description: Reverse DNS detection
+        default: true
+        required: false
+      ip_address:
+        description: The host IP address
+        aliases: ["ipaddress"]
+        required: false
+      update_dns:
+        description: Update DNS entries
+        required: false
   description:
     description: The host description
     required: false
@@ -70,11 +209,93 @@ options:
       enrollment
     aliases: ["random_password"]
     required: false
+  certificate:
+    description: List of base-64 encoded host certificates
+    type: list
+    aliases: ["usercertificate"]
+    required: false
+  managedby_host:
+    description: List of hosts that can manage this host
+    type: list
+    aliases: ["principalname", "krbprincipalname"]
+    required: false
+  principal:
+    description: List of principal aliases for this host
+    type: list
+    aliases: ["principalname", "krbprincipalname"]
+    required: false
+  allow_create_keytab_user:
+    description: Users allowed to create a keytab of this host
+    aliases: ["ipaallowedtoperform_write_keys_user"]
+    required: false
+  allow_create_keytab_group:
+    description: Groups allowed to create a keytab of this host
+    aliases: ["ipaallowedtoperform_write_keys_group"]
+    required: false
+  allow_create_keytab_host:
+    description: Hosts allowed to create a keytab of this host
+    aliases: ["ipaallowedtoperform_write_keys_host"]
+    required: false
+  allow_create_keytab_hostgroup:
+    description: Hostgroups allowed to create a keytab of this host
+    aliases: ["ipaallowedtoperform_write_keys_hostgroup"]
+    required: false
+  allow_retrieve_keytab_user:
+    description: Users allowed to retrieve a keytab of this host
+    aliases: ["ipaallowedtoperform_read_keys_user"]
+    required: false
+  allow_retrieve_keytab_group:
+    description: Groups allowed to retrieve a keytab of this host
+    aliases: ["ipaallowedtoperform_read_keys_group"]
+    required: false
+  allow_retrieve_keytab_host:
+    description: Hosts allowed to retrieve a keytab of this host
+    aliases: ["ipaallowedtoperform_read_keys_host"]
+    required: false
+  allow_retrieve_keytab_hostgroup:
+    description: Hostgroups allowed to retrieve a keytab of this host
+    aliases: ["ipaallowedtoperform_read_keys_hostgroup"]
+    required: false
   mac_address:
     description: List of hardware MAC addresses.
     type: list
     aliases: ["macaddress"]
     required: false
+  sshpubkey:
+    description: List of SSH public keys
+    type: list
+    aliases: ["ipasshpubkey"]
+    required: false
+  userclass:
+    description:
+      Host category (semantics placed on this attribute are for local
+      interpretation)
+    aliases: ["class"]
+    required: false
+  auth_ind:
+    description:
+      Defines a whitelist for Authentication Indicators. Use 'otp' to allow
+      OTP-based 2FA authentications. Use 'radius' to allow RADIUS-based 2FA
+      authentications. Other values may be used for custom configurations.
+    type: list
+    aliases: ["krbprincipalauthind"]
+    choices: ["radius", "otp", "pkinit", "hardened"]
+    required: false
+  requires_pre_auth:
+    description: Pre-authentication is required for the service
+    type: bool
+    aliases: ["ipakrbrequirespreauth"]
+    required: false
+  ok_as_delegate:
+    description: Client credentials may be delegated to the service
+    type: bool
+    aliases: ["ipakrbokasdelegate"]
+    required: false
+  ok_to_auth_as_delegate:
+    description: The service is allowed to authenticate on behalf of a client
+    type: bool
+    aliases: ["ipakrboktoauthasdelegate"]
+    required: false
   force:
     description: Force host name even if not in DNS
     required: false
@@ -94,6 +315,10 @@ options:
       Set password for a host in present state only on creation or always
     default: 'always'
     choices: ["always", "on_create"]
+  action:
+    description: Work on host or member level
+    default: "host"
+    choices: ["member", "host"]
   state:
     description: State to ensure
     default: present
@@ -170,7 +395,13 @@ host:
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils._text import to_text
 from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
-    temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa
+    temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
+    module_params_get, gen_add_del_lists, encode_certificate, api_get_realm
+import six
+
+
+if six.PY3:
+    unicode = str
 
 
 def find_host(module, name):
@@ -185,7 +416,12 @@ def find_host(module, name):
         module.fail_json(
             msg="There is more than one host '%s'" % (name))
     elif len(_result["result"]) == 1:
-        return _result["result"][0]
+        _res = _result["result"][0]
+        certs = _res.get("usercertificate")
+        if certs is not None:
+            _res["usercertificate"] = [encode_certificate(cert) for
+                                       cert in certs]
+        return _res
     else:
         return None
 
@@ -195,13 +431,15 @@ def show_host(module, name):
     return _result["result"]
 
 
-def gen_args(description, force, locality, location, platform, os, password,
-             random, mac_address, ip_address, update_dns, reverse):
+def gen_args(description, locality, location, platform, os, password, random,
+             mac_address, sshpubkey, userclass, auth_ind, requires_pre_auth,
+             ok_as_delegate, ok_to_auth_as_delegate, force, reverse,
+             ip_address, update_dns):
+    # certificate, managedby_host, principal, create_keytab_* and
+    # allow_retrieve_keytab_* are not handled here
     _args = {}
     if description is not None:
         _args["description"] = description
-    if force is not None:
-        _args["force"] = force
     if locality is not None:
         _args["l"] = locality
     if location is not None:
@@ -216,17 +454,162 @@ def gen_args(description, force, locality, location, platform, os, password,
         _args["random"] = random
     if mac_address is not None:
         _args["macaddress"] = mac_address
+    if sshpubkey is not None:
+        _args["ipasshpubkey"] = sshpubkey
+    if userclass is not None:
+        _args["userclass"] = userclass
+    if auth_ind is not None:
+        _args["krbprincipalauthind"] = auth_ind
+    if requires_pre_auth is not None:
+        _args["ipakrbrequirespreauth"] = requires_pre_auth
+    if ok_as_delegate is not None:
+        _args["ipakrbokasdelegate"] = ok_as_delegate
+    if ok_to_auth_as_delegate is not None:
+        _args["ipakrboktoauthasdelegate"] = ok_to_auth_as_delegate
+    if force is not None:
+        _args["force"] = force
+    if reverse is not None:
+        _args["no_reverse"] = not reverse
     if ip_address is not None:
         _args["ip_address"] = ip_address
     if update_dns is not None:
         _args["updatedns"] = update_dns
-    if reverse is not None:
-        _args["no_reverse"] = not reverse
 
     return _args
 
 
+def check_parameters(
+        module, state, action,
+        description, locality, location, platform, os, password, random,
+        certificate, managedby_host, principal, allow_create_keytab_user,
+        allow_create_keytab_group, allow_create_keytab_host,
+        allow_create_keytab_hostgroup, allow_retrieve_keytab_user,
+        allow_retrieve_keytab_group, allow_retrieve_keytab_host,
+        allow_retrieve_keytab_hostgroup, mac_address, sshpubkey,
+        userclass, auth_ind, requires_pre_auth, ok_as_delegate,
+        ok_to_auth_as_delegate, force, reverse, ip_address, update_dns,
+        update_password):
+    if state == "present":
+        if action == "member":
+            # certificate, managedby_host, principal,
+            # allow_create_keytab_*, allow_retrieve_keytab_*,
+            invalid = ["description", "locality", "location", "platform",
+                       "os", "password", "random", "mac_address", "sshpubkey",
+                       "userclass", "auth_ind", "requires_pre_auth",
+                       "ok_as_delegate", "ok_to_auth_as_delegate", "force",
+                       "reverse", "ip_address", "update_dns",
+                       "update_password"]
+            for x in invalid:
+                if vars()[x] is not None:
+                    module.fail_json(
+                        msg="Argument '%s' can not be used with action "
+                        "'%s'" % (x, action))
+
+    if state == "absent":
+        invalid = ["description", "locality", "location", "platform", "os",
+                   "password", "random", "mac_address", "sshpubkey",
+                   "userclass", "auth_ind", "requires_pre_auth",
+                   "ok_as_delegate", "ok_to_auth_as_delegate", "force",
+                   "reverse", "ip_address", "update_password"]
+        if action == "host":
+            invalid.extend([
+                "certificate", "managedby_host", "principal",
+                "allow_create_keytab_user", "allow_create_keytab_group",
+                "allow_create_keytab_host", "allow_create_keytab_hostgroup",
+                "allow_retrieve_keytab_user", "allow_retrieve_keytab_group",
+                "allow_retrieve_keytab_host",
+                "allow_retrieve_keytab_hostgroup"])
+        for x in invalid:
+            if vars()[x] is not None:
+                module.fail_json(
+                    msg="Argument '%s' can not be used with state '%s'" %
+                    (x, state))
+
+
 def main():
+    host_spec = dict(
+        # present
+        description=dict(type="str", default=None),
+        locality=dict(type="str", default=None),
+        location=dict(type="str", aliases=["ns_host_location"],
+                      default=None),
+        platform=dict(type="str", aliases=["ns_hardware_platform"],
+                      default=None),
+        os=dict(type="str", aliases=["ns_os_version"], default=None),
+        password=dict(type="str",
+                      aliases=["user_password", "userpassword"],
+                      default=None, no_log=True),
+        random=dict(type="bool", aliases=["random_password"],
+                    default=None),
+
+
+
+        certificate=dict(type="list", aliases=["usercertificate"],
+                         default=None),
+        managedby_host=dict(type="list",
+                            default=None),
+        principal=dict(type="list", aliases=["krbprincipalname"],
+                       default=None),
+        allow_create_keytab_user=dict(
+            type="list",
+            aliases=["ipaallowedtoperform_write_keys_user"],
+            default=None),
+        allow_create_keytab_group=dict(
+            type="list",
+            aliases=["ipaallowedtoperform_write_keys_group"],
+            default=None),
+        allow_create_keytab_host=dict(
+            type="list",
+            aliases=["ipaallowedtoperform_write_keys_host"],
+            default=None),
+        allow_create_keytab_hostgroup=dict(
+            type="list",
+            aliases=["ipaallowedtoperform_write_keys_hostgroup"],
+            default=None),
+        allow_retrieve_keytab_user=dict(
+            type="list",
+            aliases=["ipaallowedtoperform_write_keys_user"],
+            default=None),
+        allow_retrieve_keytab_group=dict(
+            type="list",
+            aliases=["ipaallowedtoperform_write_keys_group"],
+            default=None),
+        allow_retrieve_keytab_host=dict(
+            type="list",
+            aliases=["ipaallowedtoperform_write_keys_host"],
+            default=None),
+        allow_retrieve_keytab_hostgroup=dict(
+            type="list",
+            aliases=["ipaallowedtoperform_write_keys_hostgroup"],
+            default=None),
+        mac_address=dict(type="list", aliases=["macaddress"],
+                         default=None),
+        sshpubkey=dict(type="str", aliases=["ipasshpubkey"],
+                       default=None),
+        userclass=dict(type="list", aliases=["class"],
+                       default=None),
+        auth_ind=dict(type='list', aliases=["krbprincipalauthind"],
+                      default=None,
+                      choices=['password', 'radius', 'otp']),
+        requires_pre_auth=dict(type="bool", aliases=["ipakrbrequirespreauth"],
+                               default=None),
+        ok_as_delegate=dict(type="bool", aliases=["ipakrbokasdelegate"],
+                            default=None),
+        ok_to_auth_as_delegate=dict(type="bool",
+                                    aliases=["ipakrboktoauthasdelegate"],
+                                    default=None),
+        force=dict(type='bool', default=None),
+        reverse=dict(type='bool', default=None),
+        ip_address=dict(type="str", aliases=["ipaddress"],
+                        default=None),
+        update_dns=dict(type="bool", aliases=["updatedns"],
+                        default=None),
+        # no_members
+
+        # for update:
+        # krbprincipalname
+    )
+
     ansible_module = AnsibleModule(
         argument_spec=dict(
             # general
@@ -234,51 +617,33 @@ def main():
             ipaadmin_password=dict(type="str", no_log=True),
 
             name=dict(type="list", aliases=["fqdn"], default=None,
-                      required=True),
-            # present
-            description=dict(type="str", default=None),
-            locality=dict(type="str", default=None),
-            location=dict(type="str", aliases=["ns_host_location"],
-                          default=None),
-            platform=dict(type="str", aliases=["ns_hardware_platform"],
-                          default=None),
-            os=dict(type="str", aliases=["ns_os_version"], default=None),
-            password=dict(type="str",
-                          aliases=["user_password", "userpassword"],
-                          default=None, no_log=True),
-            random=dict(type="bool", aliases=["random_password"],
-                        default=None),
-            # certificate (usercertificate)
-            mac_address=dict(type="list", aliases=["macaddress"],
-                             default=None),
-            # sshpubkey=dict(type="str", aliases=["ipasshpubkey"],
-            #                default=None),
-            # class
-            # auth_ind
-            # requires_pre_auth
-            # ok_as_delegate
-            # ok_to_auth_as_delegate
-            force=dict(type='bool', default=None),
-            reverse=dict(type='bool', default=True),
-            ip_address=dict(type="str", aliases=["ipaddress"],
-                            default=None),
-            # no_members
+                      required=False),
 
-            # for update:
-            # krbprincipalname
-            update_dns=dict(type="bool", aliases=["updatedns"],
-                            default=None),
+            hosts=dict(type="list", default=None,
+                       options=dict(
+                           # Here name is a simple string
+                           name=dict(type="str", aliases=["fqdn"],
+                                     required=True),
+                           # Add host specific parameters
+                           **host_spec
+                       ),
+                       elements='dict', required=False),
+
+            # mod
             update_password=dict(type='str', default=None,
                                  choices=['always', 'on_create']),
-            # absent
-            # continue
-
-            # disabled
 
-            # state
+            # general
+            action=dict(type="str", default="host",
+                        choices=["member", "host"]),
             state=dict(type="str", default="present",
                        choices=["present", "absent", "disabled"]),
+
+            # Add host specific parameters for simple use case
+            **host_spec
         ),
+        mutually_exclusive=[["name", "hosts"]],
+        required_one_of=[["name", "hosts"]],
         supports_check_mode=True,
     )
 
@@ -287,49 +652,82 @@ def main():
     # Get parameters
 
     # general
-    ipaadmin_principal = ansible_module.params.get("ipaadmin_principal")
-    ipaadmin_password = ansible_module.params.get("ipaadmin_password")
-    names = ansible_module.params.get("name")
+    ipaadmin_principal = module_params_get(ansible_module,
+                                           "ipaadmin_principal")
+    ipaadmin_password = module_params_get(ansible_module,
+                                          "ipaadmin_password")
+    names = module_params_get(ansible_module, "name")
+    hosts = module_params_get(ansible_module, "hosts")
 
     # present
-    description = ansible_module.params.get("description")
-    locality = ansible_module.params.get("locality")
-    location = ansible_module.params.get("location")
-    platform = ansible_module.params.get("platform")
-    os = ansible_module.params.get("os")
-    password = ansible_module.params.get("password")
-    random = ansible_module.params.get("random")
-    mac_address = ansible_module.params.get("mac_address")
-    force = ansible_module.params.get("force")
-    reverse = ansible_module.params.get("reverse")
-    ip_address = ansible_module.params.get("ip_address")
-    update_dns = ansible_module.params.get("update_dns")
-    update_password = ansible_module.params.get("update_password")
-    # absent
-    # disabled
-    # state
-    state = ansible_module.params.get("state")
+    description = module_params_get(ansible_module, "description")
+    locality = module_params_get(ansible_module, "locality")
+    location = module_params_get(ansible_module, "location")
+    platform = module_params_get(ansible_module, "platform")
+    os = module_params_get(ansible_module, "os")
+    password = module_params_get(ansible_module, "password")
+    random = module_params_get(ansible_module, "random")
+    certificate = module_params_get(ansible_module, "certificate")
+    managedby_host = module_params_get(ansible_module, "managedby_host")
+    principal = module_params_get(ansible_module, "principal")
+    allow_create_keytab_user = module_params_get(
+        ansible_module, "allow_create_keytab_user")
+    allow_create_keytab_group = module_params_get(
+        ansible_module, "allow_create_keytab_group")
+    allow_create_keytab_host = module_params_get(
+        ansible_module, "allow_create_keytab_host")
+    allow_create_keytab_hostgroup = module_params_get(
+        ansible_module, "allow_create_keytab_hostgroup")
+    allow_retrieve_keytab_user = module_params_get(
+        ansible_module, "allow_retrieve_keytab_user")
+    allow_retrieve_keytab_group = module_params_get(
+        ansible_module, "allow_retrieve_keytab_group")
+    allow_retrieve_keytab_host = module_params_get(
+        ansible_module, "allow_retrieve_keytab_host")
+    allow_retrieve_keytab_hostgroup = module_params_get(
+        ansible_module, "allow_retrieve_keytab_hostgroup")
+    mac_address = module_params_get(ansible_module, "mac_address")
+    sshpubkey = module_params_get(ansible_module, "sshpubkey")
+    userclass = module_params_get(ansible_module, "userclass")
+    auth_ind = module_params_get(ansible_module, "auth_ind")
+    requires_pre_auth = module_params_get(ansible_module, "requires_pre_auth")
+    ok_as_delegate = module_params_get(ansible_module, "ok_as_delegate")
+    ok_to_auth_as_delegate = module_params_get(ansible_module,
+                                               "ok_to_auth_as_delegate")
+    force = module_params_get(ansible_module, "force")
+    reverse = module_params_get(ansible_module, "reverse")
+    ip_address = module_params_get(ansible_module, "ip_address")
+    update_dns = module_params_get(ansible_module, "update_dns")
+    update_password = module_params_get(ansible_module, "update_password")
+    # general
+    action = module_params_get(ansible_module, "action")
+    state = module_params_get(ansible_module, "state")
 
     # Check parameters
 
+    if (names is None or len(names) < 1) and \
+       (hosts is None or len(hosts) < 1):
+        ansible_module.fail_json(msg="One of name and hosts is required")
+
     if state == "present":
-        if len(names) != 1:
+        if names is not None and len(names) != 1:
             ansible_module.fail_json(
                 msg="Only one host can be added at a time.")
 
-    if state == "absent":
-        if len(names) < 1:
-            ansible_module.fail_json(
-                msg="No name given.")
-        for x in ["description", "password", "random", "mac_address",
-                  "force", "ip_address", "update_password"]:
-            if vars()[x] is not None:
-                ansible_module.fail_json(
-                    msg="Argument '%s' can not be used with state '%s'" %
-                    (x, state))
+    check_parameters(
+        ansible_module, state, action,
+        description, locality, location, platform, os, password, random,
+        certificate, managedby_host, principal, allow_create_keytab_user,
+        allow_create_keytab_group, allow_create_keytab_host,
+        allow_create_keytab_hostgroup, allow_retrieve_keytab_user,
+        allow_retrieve_keytab_group, allow_retrieve_keytab_host,
+        allow_retrieve_keytab_hostgroup, mac_address, sshpubkey, userclass,
+        auth_ind, requires_pre_auth, ok_as_delegate, ok_to_auth_as_delegate,
+        force, reverse, ip_address, update_dns, update_password)
 
-    if update_password is None:
-        update_password = "always"
+    # Use hosts if names is None
+    if hosts is not None:
+        names = hosts
 
     # Init
 
@@ -343,9 +741,75 @@ def main():
                                                  ipaadmin_password)
         api_connect()
 
+        # Check version specific settings
+
+        server_realm = api_get_realm()
+
         commands = []
 
-        for name in names:
+        for host in names:
+            if isinstance(host, dict):
+                name = host.get("name")
+                description = host.get("description")
+                locality = host.get("locality")
+                location = host.get("location")
+                platform = host.get("platform")
+                os = host.get("os")
+                password = host.get("password")
+                random = host.get("random")
+                certificate = host.get("certificate")
+                managedby_host = host.get("managedby_host")
+                principal = host.get("principal")
+                allow_create_keytab_user = host.get(
+                    "allow_create_keytab_user")
+                allow_create_keytab_group = host.get(
+                    "allow_create_keytab_group")
+                allow_create_keytab_host = host.get(
+                    "allow_create_keytab_host")
+                allow_create_keytab_hostgroup = host.get(
+                    "allow_create_keytab_hostgroup")
+                allow_retrieve_keytab_user = host.get(
+                    "allow_retrieve_keytab_user")
+                allow_retrieve_keytab_group = host.get(
+                    "allow_retrieve_keytab_group")
+                allow_retrieve_keytab_host = host.get(
+                    "allow_retrieve_keytab_host")
+                allow_retrieve_keytab_hostgroup = host.get(
+                    "allow_retrieve_keytab_hostgroup")
+                mac_address = host.get("mac_address")
+                sshpubkey = host.get("sshpubkey")
+                userclass = host.get("userclass")
+                auth_ind = host.get("auth_ind")
+                requires_pre_auth = host.get("requires_pre_auth")
+                ok_as_delegate = host.get("ok_as_delegate")
+                ok_to_auth_as_delegate = host.get("ok_to_auth_as_delegate")
+                force = host.get("force")
+                reverse = host.get("reverse")
+                ip_address = host.get("ip_address")
+                update_dns = host.get("update_dns")
+                # update_password is not part of hosts structure
+                # action is not part of hosts structure
+                # state is not part of hosts structure
+
+                check_parameters(
+                    ansible_module, state, action,
+                    description, locality, location, platform, os, password,
+                    random, certificate, managedby_host, principal,
+                    allow_create_keytab_user, allow_create_keytab_group,
+                    allow_create_keytab_host, allow_create_keytab_hostgroup,
+                    allow_retrieve_keytab_user, allow_retrieve_keytab_group,
+                    allow_retrieve_keytab_host,
+                    allow_retrieve_keytab_hostgroup, mac_address, sshpubkey,
+                    userclass, auth_ind, requires_pre_auth, ok_as_delegate,
+                    ok_to_auth_as_delegate, force, reverse, ip_address,
+                    update_dns, update_password)
+
+            elif isinstance(host, str) or isinstance(host, unicode):
+                name = host
+            else:
+                ansible_module.fail_json(msg="Host '%s' is not valid" %
+                                         repr(host))
+
             # Make sure host exists
             res_find = find_host(ansible_module, name)
 
@@ -353,41 +817,392 @@ def main():
             if state == "present":
                 # Generate args
                 args = gen_args(
-                    description, force, locality, location, platform, os,
-                    password, random, mac_address, ip_address, update_dns,
-                    reverse)
+                    description, locality, location, platform, os, password,
+                    random, mac_address, sshpubkey, userclass, auth_ind,
+                    requires_pre_auth, ok_as_delegate, ok_to_auth_as_delegate,
+                    force, reverse, ip_address, update_dns)
 
-                # Found the host
-                if res_find is not None:
-                    # Ignore password with update_password == on_create
-                    if update_password == "on_create":
-                        if "userpassword" in args:
+                if action == "host":
+                    # Found the host
+                    if res_find is not None:
+                        # Ignore password with update_password == on_create
+                        if update_password == "on_create" and \
+                           "userpassword" in args:
                             del args["userpassword"]
-                        if "random" in args:
-                            del args["random"]
-
-                    # Ignore force, ip_address and no_reverse for mod
-                    for x in ["force", "ip_address", "no_reverse"]:
-                        if x in args:
-                            del args[x]
-
-                    # For all settings is args, check if there are
-                    # different settings in the find result.
-                    # If yes: modify
-                    if not compare_args_ipa(ansible_module, args, res_find):
-                        commands.append([name, "host_mod", args])
+
+                        # Ignore force, ip_address and no_reverse for mod
+                        for x in ["force", "ip_address", "no_reverse"]:
+                            if x in args:
+                                del args[x]
+
+                        # For all settings is args, check if there are
+                        # different settings in the find result.
+                        # If yes: modify
+                        if not compare_args_ipa(ansible_module, args,
+                                                res_find):
+                            commands.append([name, "host_mod", args])
+                        elif random and "userpassword" in res_find:
+                            # Host exists and random is set, return
+                            # userpassword
+                            if len(names) == 1:
+                                exit_args["userpassword"] = \
+                                    res_find["userpassword"]
+                            else:
+                                exit_args.setdefault("hosts", {})[name] = {
+                                    "userpassword": res_find["userpassword"]
+                                }
+
+                    else:
+                        # Remove update_dns as it is not supported by host_add
+                        if "updatedns" in args:
+                            del args["updatedns"]
+                        commands.append([name, "host_add", args])
+
+                    # Handle members: certificate, managedby_host, principal,
+                    # allow_create_keytab and allow_retrieve_keytab
+                    if res_find is not None:
+                        certificate_add, certificate_del = gen_add_del_lists(
+                            certificate, res_find.get("usercertificate"))
+                        managedby_host_add, managedby_host_del = \
+                            gen_add_del_lists(managedby_host,
+                                              res_find.get("managedby_host"))
+                        principal_add, principal_del = gen_add_del_lists(
+                            principal, res_find.get("principal"))
+                        # Principals are not returned as utf8 for IPA using
+                        # python2 using host_find, therefore we need to
+                        # convert the principals that we should remove.
+                        principal_del = [to_text(x) for x in principal_del]
+
+                        (allow_create_keytab_user_add,
+                         allow_create_keytab_user_del) = \
+                            gen_add_del_lists(
+                                allow_create_keytab_user,
+                                res_find.get(
+                                    "ipaallowedtoperform_write_keys_user"))
+                        (allow_create_keytab_group_add,
+                         allow_create_keytab_group_del) = \
+                            gen_add_del_lists(
+                                allow_create_keytab_group,
+                                res_find.get(
+                                    "ipaallowedtoperform_write_keys_group"))
+                        (allow_create_keytab_host_add,
+                         allow_create_keytab_host_del) = \
+                            gen_add_del_lists(
+                                allow_create_keytab_host,
+                                res_find.get(
+                                    "ipaallowedtoperform_write_keys_host"))
+                        (allow_create_keytab_hostgroup_add,
+                         allow_create_keytab_hostgroup_del) = \
+                            gen_add_del_lists(
+                                allow_create_keytab_hostgroup,
+                                res_find.get(
+                                    "ipaallowedtoperform_write_keys_"
+                                    "hostgroup"))
+                        (allow_retrieve_keytab_user_add,
+                         allow_retrieve_keytab_user_del) = \
+                            gen_add_del_lists(
+                                allow_retrieve_keytab_user,
+                                res_find.get(
+                                    "ipaallowedtoperform_read_keys_user"))
+                        (allow_retrieve_keytab_group_add,
+                         allow_retrieve_keytab_group_del) = \
+                            gen_add_del_lists(
+                                allow_retrieve_keytab_group,
+                                res_find.get(
+                                    "ipaallowedtoperform_read_keys_group"))
+                        (allow_retrieve_keytab_host_add,
+                         allow_retrieve_keytab_host_del) = \
+                            gen_add_del_lists(
+                                allow_retrieve_keytab_host,
+                                res_find.get(
+                                    "ipaallowedtoperform_read_keys_host"))
+                        (allow_retrieve_keytab_hostgroup_add,
+                         allow_retrieve_keytab_hostgroup_del) = \
+                            gen_add_del_lists(
+                                allow_retrieve_keytab_hostgroup,
+                                res_find.get(
+                                    "ipaallowedtoperform_read_keys_hostgroup"))
+
+                    else:
+                        certificate_add = certificate or []
+                        certificate_del = []
+                        managedby_host_add = managedby_host or []
+                        managedby_host_del = []
+                        principal_add = principal or []
+                        principal_del = []
+                        allow_create_keytab_user_add = \
+                            allow_create_keytab_user or []
+                        allow_create_keytab_user_del = []
+                        allow_create_keytab_group_add = \
+                            allow_create_keytab_group or []
+                        allow_create_keytab_group_del = []
+                        allow_create_keytab_host_add = \
+                            allow_create_keytab_host or []
+                        allow_create_keytab_host_del = []
+                        allow_create_keytab_hostgroup_add = \
+                            allow_create_keytab_hostgroup or []
+                        allow_create_keytab_hostgroup_del = []
+                        allow_retrieve_keytab_user_add = \
+                            allow_retrieve_keytab_user or []
+                        allow_retrieve_keytab_user_del = []
+                        allow_retrieve_keytab_group_add = \
+                            allow_retrieve_keytab_group or []
+                        allow_retrieve_keytab_group_del = []
+                        allow_retrieve_keytab_host_add = \
+                            allow_retrieve_keytab_host or []
+                        allow_retrieve_keytab_host_del = []
+                        allow_retrieve_keytab_hostgroup_add = \
+                            allow_retrieve_keytab_hostgroup or []
+                        allow_retrieve_keytab_hostgroup_del = []
+
                 else:
-                    commands.append([name, "host_add", args])
+                    certificate_add = certificate or []
+                    certificate_del = []
+                    managedby_host_add = managedby_host or []
+                    managedby_host_del = []
+                    principal_add = principal or []
+                    principal_del = []
+                    allow_create_keytab_user_add = \
+                        allow_create_keytab_user or []
+                    allow_create_keytab_user_del = []
+                    allow_create_keytab_group_add = \
+                        allow_create_keytab_group or []
+                    allow_create_keytab_group_del = []
+                    allow_create_keytab_host_add = \
+                        allow_create_keytab_host or []
+                    allow_create_keytab_host_del = []
+                    allow_create_keytab_hostgroup_add = \
+                        allow_create_keytab_hostgroup or []
+                    allow_create_keytab_hostgroup_del = []
+                    allow_retrieve_keytab_user_add = \
+                        allow_retrieve_keytab_user or []
+                    allow_retrieve_keytab_user_del = []
+                    allow_retrieve_keytab_group_add = \
+                        allow_retrieve_keytab_group or []
+                    allow_retrieve_keytab_group_del = []
+                    allow_retrieve_keytab_host_add = \
+                        allow_retrieve_keytab_host or []
+                    allow_retrieve_keytab_host_del = []
+                    allow_retrieve_keytab_hostgroup_add = \
+                        allow_retrieve_keytab_hostgroup or []
+                    allow_retrieve_keytab_hostgroup_del = []
+
+                # Remove canonical principal from principal_del
+                canonical_principal = "host/" + name + "@" + server_realm
+                if canonical_principal in principal_del and \
+                   action == "host" and (principal is not None or
+                                         canonical_principal not in principal):
+                    principal_del.remove(canonical_principal)
+
+                # Remove canonical managedby managedby_host_del for
+                # action host if managedby_host is set and the canonical
+                # managedby host is not in the managedby_host list.
+                canonical_managedby_host = name
+                if canonical_managedby_host in managedby_host_del and \
+                   action == "host" and (managedby_host is None or
+                                         canonical_managedby_host not in
+                                         managedby_host):
+                    managedby_host_del.remove(canonical_managedby_host)
+
+                # Certificates need to be added and removed one by one,
+                # because if entry already exists, the processing of
+                # the remaining enries is stopped. The same applies to
+                # the removal of non-existing entries.
+
+                # Add certificates
+                for _certificate in certificate_add:
+                    commands.append([name, "host_add_cert",
+                                     {
+                                         "usercertificate":
+                                         _certificate,
+                                     }])
+                # Remove certificates
+                for _certificate in certificate_del:
+                    commands.append([name, "host_remove_cert",
+                                     {
+                                         "usercertificate":
+                                         _certificate,
+                                     }])
+
+                # Managedby_Hosts need to be added and removed one by one,
+                # because if entry already exists, the processing of
+                # the remaining enries is stopped. The same applies to
+                # the removal of non-existing entries.
+
+                # Add managedby_hosts
+                for _managedby_host in managedby_host_add:
+                    commands.append([name, "host_add_managedby",
+                                     {
+                                         "host":
+                                         _managedby_host,
+                                     }])
+                # Remove managedby_hosts
+                for _managedby_host in managedby_host_del:
+                    commands.append([name, "host_remove_managedby",
+                                     {
+                                         "host":
+                                         _managedby_host,
+                                     }])
+
+                # Principals need to be added and removed one by one,
+                # because if entry already exists, the processing of
+                # the remaining enries is stopped. The same applies to
+                # the removal of non-existing entries.
+
+                # Add principals
+                for _principal in principal_add:
+                    commands.append([name, "host_add_principal",
+                                     {
+                                         "krbprincipalname":
+                                         _principal,
+                                     }])
+                # Remove principals
+                for _principal in principal_del:
+                    commands.append([name, "host_remove_principal",
+                                     {
+                                         "krbprincipalname":
+                                         _principal,
+                                     }])
+
+                # Allow create keytab
+                if len(allow_create_keytab_user_add) > 0 or \
+                   len(allow_create_keytab_group_add) > 0 or \
+                   len(allow_create_keytab_host_add) > 0 or \
+                   len(allow_create_keytab_hostgroup_add) > 0:
+                    commands.append(
+                        [name, "host_allow_create_keytab",
+                         {
+                             "user": allow_create_keytab_user_add,
+                             "group": allow_create_keytab_group_add,
+                             "host": allow_create_keytab_host_add,
+                             "hostgroup": allow_create_keytab_hostgroup_add,
+                         }])
+
+                # Disallow create keytab
+                if len(allow_create_keytab_user_del) > 0 or \
+                   len(allow_create_keytab_group_del) > 0 or \
+                   len(allow_create_keytab_host_del) > 0 or \
+                   len(allow_create_keytab_hostgroup_del) > 0:
+                    commands.append(
+                        [name, "host_disallow_create_keytab",
+                         {
+                             "user": allow_create_keytab_user_del,
+                             "group": allow_create_keytab_group_del,
+                             "host": allow_create_keytab_host_del,
+                             "hostgroup": allow_create_keytab_hostgroup_del,
+                         }])
+
+                # Allow retrieve keytab
+                if len(allow_retrieve_keytab_user_add) > 0 or \
+                   len(allow_retrieve_keytab_group_add) > 0 or \
+                   len(allow_retrieve_keytab_host_add) > 0 or \
+                   len(allow_retrieve_keytab_hostgroup_add) > 0:
+                    commands.append(
+                        [name, "host_allow_retrieve_keytab",
+                         {
+                             "user": allow_retrieve_keytab_user_add,
+                             "group": allow_retrieve_keytab_group_add,
+                             "host": allow_retrieve_keytab_host_add,
+                             "hostgroup": allow_retrieve_keytab_hostgroup_add,
+                         }])
+
+                # Disallow retrieve keytab
+                if len(allow_retrieve_keytab_user_del) > 0 or \
+                   len(allow_retrieve_keytab_group_del) > 0 or \
+                   len(allow_retrieve_keytab_host_del) > 0 or \
+                   len(allow_retrieve_keytab_hostgroup_del) > 0:
+                    commands.append(
+                        [name, "host_disallow_retrieve_keytab",
+                         {
+                             "user": allow_retrieve_keytab_user_del,
+                             "group": allow_retrieve_keytab_group_del,
+                             "host": allow_retrieve_keytab_host_del,
+                             "hostgroup": allow_retrieve_keytab_hostgroup_del,
+                         }])
 
             elif state == "absent":
-                if res_find is not None:
-                    commands.append([name, "host_del", {}])
+                if action == "host":
+
+                    if res_find is not None:
+                        args = {}
+                        if update_dns is not None:
+                            args["updatedns"] = update_dns
+                        commands.append([name, "host_del", args])
+                else:
+
+                    # Certificates need to be added and removed one by one,
+                    # because if entry already exists, the processing of
+                    # the remaining enries is stopped. The same applies to
+                    # the removal of non-existing entries.
+
+                    # Remove certificates
+                    if certificate is not None:
+                        for _certificate in certificate:
+                            commands.append([name, "host_remove_cert",
+                                             {
+                                                 "usercertificate":
+                                                 _certificate,
+                                             }])
+
+                    # Managedby_Hosts need to be added and removed one by one,
+                    # because if entry already exists, the processing of
+                    # the remaining enries is stopped. The same applies to
+                    # the removal of non-existing entries.
+
+                    # Remove managedby_hosts
+                    if managedby_host is not None:
+                        for _managedby_host in managedby_host:
+                            commands.append([name, "host_remove_managedby",
+                                             {
+                                                 "host":
+                                                 _managedby_host,
+                                             }])
+
+                    # Principals need to be added and removed one by one,
+                    # because if entry already exists, the processing of
+                    # the remaining enries is stopped. The same applies to
+                    # the removal of non-existing entries.
+
+                    # Remove principals
+                    if principal is not None:
+                        for _principal in principal:
+                            commands.append([name, "host_remove_principal",
+                                             {
+                                                 "krbprincipalname":
+                                                 _principal,
+                                             }])
+
+                    # Disallow create keytab
+                    if allow_create_keytab_user is not None or \
+                       allow_create_keytab_group is not None or \
+                       allow_create_keytab_host is not None or \
+                       allow_create_keytab_hostgroup is not None:
+                        commands.append(
+                            [name, "host_disallow_create_keytab",
+                             {
+                                 "user": allow_create_keytab_user,
+                                 "group": allow_create_keytab_group,
+                                 "host": allow_create_keytab_host,
+                                 "hostgroup": allow_create_keytab_hostgroup,
+                             }])
+
+                    # Disallow retrieve keytab
+                    if allow_retrieve_keytab_user is not None or \
+                       allow_retrieve_keytab_group is not None or \
+                       allow_retrieve_keytab_host is not None or \
+                       allow_retrieve_keytab_hostgroup is not None:
+                        commands.append(
+                            [name, "host_disallow_retrieve_keytab",
+                             {
+                                 "user": allow_retrieve_keytab_user,
+                                 "group": allow_retrieve_keytab_group,
+                                 "host": allow_retrieve_keytab_host,
+                                 "hostgroup": allow_retrieve_keytab_hostgroup,
+                             }])
 
             elif state == "disabled":
                 if res_find is not None:
-                    res_show = show_host(ansible_module, name)
-                    if res_show["has_keytab"]:
-                        commands.append([name, "host_disable", {}])
+                    commands.append([name, "host_disable", {}])
                 else:
                     raise ValueError("No host '%s'" % name)
 
@@ -395,11 +1210,17 @@ def main():
                 ansible_module.fail_json(msg="Unkown state '%s'" % state)
 
         # Execute commands
+
+        errors = []
         for name, command, args in commands:
             try:
                 result = api_command(ansible_module, command, to_text(name),
                                      args)
-                changed = True
+                if "completed" in result:
+                    if result["completed"] > 0:
+                        changed = True
+                else:
+                    changed = True
 
                 if "random" in args and command in ["host_add", "host_mod"] \
                    and "randompassword" in result["result"]:
@@ -411,8 +1232,37 @@ def main():
                             result["result"]["randompassword"]
 
             except Exception as e:
+                msg = str(e)
+                if "already contains" in msg \
+                   or "does not contain" in msg:
+                    continue
+
+                #  The canonical principal name may not be removed
+                if "equal to the canonical principal name must" in msg:
+                    continue
+
+                # Host is already disabled, ignore error
+                if "This entry is already disabled" in msg:
+                    continue
                 ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
-                                                             str(e)))
+                                                             msg))
+
+            # Get all errors
+            # All "already a member" and "not a member" failures in the
+            # result are ignored. All others are reported.
+            if "failed" in result and len(result["failed"]) > 0:
+                for item in result["failed"]:
+                    failed_item = result["failed"][item]
+                    for member_type in failed_item:
+                        for member, failure in failed_item[member_type]:
+                            if "already a member" in failure \
+                               or "not a member" in failure:
+                                continue
+                            errors.append("%s: %s %s: %s" % (
+                                command, member_type, member, failure))
+
+        if len(errors) > 0:
+            ansible_module.fail_json(msg=", ".join(errors))
 
     except Exception as e:
         ansible_module.fail_json(msg=str(e))
diff --git a/tests/host/certificate/cert1.der b/tests/host/certificate/cert1.der
new file mode 100644
index 0000000000000000000000000000000000000000..334511ff52b76c7611d9782e454275b06e93c93b
Binary files /dev/null and b/tests/host/certificate/cert1.der differ
diff --git a/tests/host/certificate/cert1.pem b/tests/host/certificate/cert1.pem
new file mode 100644
index 0000000000000000000000000000000000000000..6fb9d2e9d5e0c61afa9322a4321a87a6c375faf7
--- /dev/null
+++ b/tests/host/certificate/cert1.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAeegAwIBAgIUZGHLaSYg1myp6EI4VGWSC27vOrswDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAwwEdGVzdDAeFw0xOTEwMTQxNjI4MzVaFw0yMDEwMTMxNjI4
+MzVaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQDER/lB8wUAmPTSwSc/NOXNlzdpPOQDSwrhKH6XsqZF4KpQoSY/nmCjAhJm
+OVpOUo4K2fGRZ0yAH9fkGv6yJP6c7IAFjLeec7GPHVwN4bZrP1DXfTAmfmXhcRQb
+CYkV+wmq8Puzw/+xA9EJrrodnJPPsE6E8HnSVLF6Ys9+cJMJ7HuwOI+wYt3gkmsp
+sir1tccmf4x1PP+yHJWdcXyetlFRcmZ8gspjqOR2jb89xSQsh8gcyDW6rPNlSTzY
+Z2FmNtjES6ZhCsYL31fQbF2QglidlLGpAlvHUUS+xCigW73cvhFPMWXcfO51Mr15
+RcgYTckY+7QZ2nYqplRBoDlQl6DnAgMBAAGjUzBRMB0GA1UdDgQWBBTPG99XVRdx
+pOXMZo3Nhy+ldnf13TAfBgNVHSMEGDAWgBTPG99XVRdxpOXMZo3Nhy+ldnf13TAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAjWTcnIl2mpNbfHAN8
+DB4Kk+RNRmhsH0y+r/47MXVTMMMToCfofeNY3Jeohu+2lIXMPQfTvXUbDTkNAGsG
+Lv6LtQEUfSREqgk1eY7bT9BFfpH1uV2ZFhCO9jBA+E4bf55Kx7bgUNG31ykBshOs
+OblOJM1lS/0q4TWHAxrsU2PNwPi8X0ten+eGeB8aRshxS17Ij2cH0fdAMmSA+jMA
+vTIZl853Bxe0HuozauKwOFWL4qHm61c4O/j1mQCLqJKYfJ9mBDWFQLszd/tF+ePK
+iNhZCQly60F8Lumn2CDZj5UIkl8wk9Wls5n1BIQs+M8AN65NAdv7+js8jKUKCuyj
+i8r3
+-----END CERTIFICATE-----
diff --git a/tests/host/certificate/cert2.der b/tests/host/certificate/cert2.der
new file mode 100644
index 0000000000000000000000000000000000000000..a3ba5a575677e12a26c96ab8844724e733878ff7
Binary files /dev/null and b/tests/host/certificate/cert2.der differ
diff --git a/tests/host/certificate/cert2.pem b/tests/host/certificate/cert2.pem
new file mode 100644
index 0000000000000000000000000000000000000000..4a243b253fb2cb0984aa8e80d9ce0168180f7b0d
--- /dev/null
+++ b/tests/host/certificate/cert2.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAeegAwIBAgIUAWE1vaA+mZd3nwZqwWH64EbHvR0wDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAwwEdGVzdDAeFw0xOTEwMTQxNjI4NDVaFw0yMDEwMTMxNjI4
+NDVaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQCWzJibKtN8Zf7LgandINhFonx99AKi44iaZkrlMKEObE6Faf8NTUbUgK3V
+fJNYmCbA1baLVJ0YZJijJ7S/4o7h7eeqcJVXJkEhWNTimWXNW/YCzTHe3SSapnSY
+OKmdHHRClplysL8OyyEG7pbX/aB9iAfFb/+vUFCX5sMwFFrYxOimKJ9Pc/NRFtdv
+1wNw1rqWKF1ZzagWRlG4QgzRGwQ4quc7yO98TKikj2OPiIt7Zd46hbqQxmgGBtCk
+VOZIhxu77OmNrFsXmM4rZZpmqh0UdqcpwkRojVnGXmNqeMCd6dNTnLhr9wukUYw0
+KgE57zCDVr9Ix+p/dA5R1mG4RJ2XAgMBAAGjUzBRMB0GA1UdDgQWBBSbuiH2lNVr
+ID3yt1SsFwtOFKOnpTAfBgNVHSMEGDAWgBSbuiH2lNVrID3yt1SsFwtOFKOnpTAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBCVWd293wWyohFqMFM
+HRBBg97T2Uc1yeT0dMH4BpuOaCqQp4q5ep+uLcXEI6+3mEwm8pa/ULQCD8yLLdot
+IWlG3+h/4boFpdiPFcBDgT8kGe+0KOzB8Nt7E13QYOu12MNi10qwGrjKhdhu1xBe
+4fpY5VCetVU1OLyuTsUyucQsFrtZI0SR83h+blbyoMZ7IhMngCfGUe1bnYeWnLbp
+FbigKfPuVDWsMH2kgj05EAd5EgHkWbX8QA8hmcmDKfNT3YZM8kiGQwmFrnQdq8bN
+0uHR8Nz+24cbmdbHcD65wlDW6GmYxi8mW+V6bAqn9pir/J14r4YFnqMGgjmdt81t
+scJV
+-----END CERTIFICATE-----
diff --git a/tests/host/certificate/cert3.der b/tests/host/certificate/cert3.der
new file mode 100644
index 0000000000000000000000000000000000000000..783830ee5d9fb52cc41010a3b60047bd68667cd0
Binary files /dev/null and b/tests/host/certificate/cert3.der differ
diff --git a/tests/host/certificate/cert3.pem b/tests/host/certificate/cert3.pem
new file mode 100644
index 0000000000000000000000000000000000000000..b2a6a99c09ab9b4b2036c6124c373f45ca901f83
--- /dev/null
+++ b/tests/host/certificate/cert3.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAeegAwIBAgIUTC33WUoYGFoIVGMwgjbc5J6xCyowDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAwwEdGVzdDAeFw0xOTEwMTQxNjI4NTJaFw0yMDEwMTMxNjI4
+NTJaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQDCA+6P2eieXHaVJivtWif7SntjjkJm0juRKRRGsT3wt+zCZqoDe8zylTBN
+0mse/POWXdC+zXRMC2X/c4V10kgrvWbnNdFdUFfBUphiXSoqnUYHZ6Ta+b4UTzC2
+tECSUEnSCz9n1ofHnyqDyT9FELzVkRkQqexD+BFgZTF39R4q8BA4bWKQy94Kgvb+
+IP77+ou4fhkBLI1MX5nkWa3Oyu4TMzT/tqgPE70hk8wQzUU2aiwJ7IsmnWE6Ysk7
+c4DYMJQF/51bi2ByZWERNjyBY6L+ZV90aL4UFR9O+Pw9HatfHVBRdmzSkKJOr9iu
+4summWgH0QYDmbkdhGwYvup0EmEfAgMBAAGjUzBRMB0GA1UdDgQWBBSJCQ8ho0Pp
+e0khVhgiMqsvlgxIjzAfBgNVHSMEGDAWgBSJCQ8ho0Ppe0khVhgiMqsvlgxIjzAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAILLPnau32r/YoOVCV
+WQotGtySy36aFlHa3T8IkSpatNCPIf3U0FWS6TVYBwY0PBfdqWBkvCuJTupLh0OE
+P4TCsDa5pJGOK7blyfiAfcHajqyouACSVNlG63EPvB63h4H4F4HJnhDd4z7pVC/W
+PB8w5GTBJNjELmeWfH7nj7lu8UkOdLhzTKL40RPs0k4l09yYBmZqqExxGsSfvRBQ
+crwlAsvQ0E/cTNGbyzOKs3SbOM2WEHye6xNEsey01icYcjfjqvEd6mw3+WOUeJAu
+DH9/EOloFM2iz5Xp31Ig3WT0RVy+lMriG9GesPpFBs2xp9wQCXLNIkpbHKyYs3vo
+MyBH
+-----END CERTIFICATE-----
diff --git a/tests/host/certificate/private1.key b/tests/host/certificate/private1.key
new file mode 100644
index 0000000000000000000000000000000000000000..cb76298b850d378088c572ad8ab81abc1958a10c
--- /dev/null
+++ b/tests/host/certificate/private1.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDER/lB8wUAmPTS
+wSc/NOXNlzdpPOQDSwrhKH6XsqZF4KpQoSY/nmCjAhJmOVpOUo4K2fGRZ0yAH9fk
+Gv6yJP6c7IAFjLeec7GPHVwN4bZrP1DXfTAmfmXhcRQbCYkV+wmq8Puzw/+xA9EJ
+rrodnJPPsE6E8HnSVLF6Ys9+cJMJ7HuwOI+wYt3gkmspsir1tccmf4x1PP+yHJWd
+cXyetlFRcmZ8gspjqOR2jb89xSQsh8gcyDW6rPNlSTzYZ2FmNtjES6ZhCsYL31fQ
+bF2QglidlLGpAlvHUUS+xCigW73cvhFPMWXcfO51Mr15RcgYTckY+7QZ2nYqplRB
+oDlQl6DnAgMBAAECggEBALtKTj6urHxId3xPEKsQR6Noglgp4Qx/Y687W6hWsLAG
+051CV+PmtSF2DaZ7XX9U6PLTydzL68RqHjArzhKgmE+WoAYrot5QWQJNqpQYZ19o
+uDQW4YYpn/+BTgUKkUNnGm+BqTt8b5QyJxoNHsy4ppZMDnBtomCfrgYxGPr2YmfZ
+Ee7oEEf1xIU2maE5Nxv97lNR2Xvm2R4F8lzRcHmvejLYNiqZ2Ag4ijnKVTpoEMUy
+afl5LNSzGJETXsv+LtaJGEr6x8/IesVSCdyX2LZeAyQPLKwb3YQDkQree54vwS7p
+cVmQdx6fLTYV1tOSXUEC2ibInO188kGA198HSqSgHJkCgYEA7zhL+6tYZdXoMzTH
+hXHLYGHmQsQXxleH5uciz4q+en7do6BFB2DqIgLTpcD/H8XMDlg9WO7756H1zqvb
+6IOkqwsrro/fsgb6FrmjXl8zlkwT3pTNJfmBydRf7Qk2woCRPUoLZBRAumNL8RSx
+Xm1/DbPbTR3jjVNH9dPb3Efd0qUCgYEA0gyesMgwDzjsXpPUsuWTMBMziy0KRFNT
+lCMCI5DVpy/XnptyLdkY93jvmq+VWbily4KlOYbfYJ/16xeNZ7aNOMnC6z4z9p9+
+w3E9q5xKJcAJP5kN/WnjBwErveDK9r1YSj8RJpvapJFqjxA5WVTwADtyBhgNS4Og
+mXPPBleMC5sCgYEA0Yw/AvXVOV9nR3O0UvCbdpJLYbDkIpoKMfnGRIcE08jN3cdG
+sG/0qFZRj6C/2tUpKmehVYYCo6T77U4eFE88r5fZa9Ab45a4+68hrEk4py99ODyg
+d+NYDbQ7Uyf/D+IPV+DEmaYkDSFuJIA73ruL0DT8pVDJQ8LwBibPMObDKQECgYBa
+aUYxD6noE3diaj1GV5zYN5ubD2L47+jsvXjhOClOkkA8K+qko2qksrBno6YkfV8X
+zv8xWMVzgMbIT1X1S1VUGTxGJ3sUb6iPlYGXCWm9AAC7GDU2W8p1rGJYk5apR+zl
+4GmQdctRxKnaNICK3A2F/BBjYRzv4RNSmc+Fik9kewKBgHsCF3uEP7ONvmEjYLQ4
+7+6fZ+m4BXKeU/kKQoEXSjSFn0dBIHo+2yuafSUz04VJCVXUic3c47kHwVtgX5lu
+jEUL1jgK4aBbl9cvywupHBf3spAP89aocgFiC9uUJzp3u39U0LpgXY7Z+1lUsCL+
+VG2oGh0KVgazjUzmbTf9ZcLp
+-----END PRIVATE KEY-----
diff --git a/tests/host/certificate/private2.key b/tests/host/certificate/private2.key
new file mode 100644
index 0000000000000000000000000000000000000000..59d83b9f423b866c6db8f329d208d3b807e0d89c
--- /dev/null
+++ b/tests/host/certificate/private2.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCWzJibKtN8Zf7L
+gandINhFonx99AKi44iaZkrlMKEObE6Faf8NTUbUgK3VfJNYmCbA1baLVJ0YZJij
+J7S/4o7h7eeqcJVXJkEhWNTimWXNW/YCzTHe3SSapnSYOKmdHHRClplysL8OyyEG
+7pbX/aB9iAfFb/+vUFCX5sMwFFrYxOimKJ9Pc/NRFtdv1wNw1rqWKF1ZzagWRlG4
+QgzRGwQ4quc7yO98TKikj2OPiIt7Zd46hbqQxmgGBtCkVOZIhxu77OmNrFsXmM4r
+ZZpmqh0UdqcpwkRojVnGXmNqeMCd6dNTnLhr9wukUYw0KgE57zCDVr9Ix+p/dA5R
+1mG4RJ2XAgMBAAECggEAS5nXCDO4Qy1/R9eBqXLF+mMztpGWoMMhwQZ3ld+DXw+9
+bfVuAOU1FWRNwjHqTQg6pYJ/Oer5tzj3rRRC8dBLgckb078Nn9t125oFYHU3LHVm
+KJFm5yxHJaE94vLFVhbl0lxeIbmqj2gW7rq+tRpaU5TXEIzNyr6hKQZv5LLPuMx6
+MiBrSpkCwfPf9psv6k2GIGqE1JuY99dNqdEUi8UQryNMzV4pthUmVybO8NPxUY8M
+s/VAbG1Hy9tgInR3wRgTjEc2ejUJrTziiqiZarZtCp+JSZufYakDU9yZbu9v4Oz9
+ityPdApkW8CuZnJcUDAtdgtKMhWyBPnWcrUgkbV0AQKBgQDGY1saiI9M7VlleyDc
+QNVXpPCmOpDLso5X3hZrrHDgDIGkvXa026Q5ufkdxkybRYJeOCdYzIM/iXSJlgNe
+R2a+aoAsePfEVFAe96ZgzrLrBq7lGvcPXGpT6GTVl0d0CwN/vG1Tzk89Hq3xIBbh
+NTlM+j2ot66xgekIsE0v5Pi41wKBgQDCl14mgaui4DqYFYlI/ckI00r/X0/0HIhf
+kf/Ck/pkF89IeOAK+O4GOfVoMk3vi1gDYgiz6G7h+sUsFTOYKuP9io/vX0pIFNOA
+NPgaVtRKitiepNo4vwc+/PRmxvf2XXFXFRSiYf0jDzruvE3yDzWwX9P1nQFBQoPj
+r8g/6+7pQQKBgDXHnVzWBDLQbNmLxV6v3KXDutD1M2dk4h2DwQQzXO3/te1YxyNE
+H4LenV+q7/1vnGW6R0BVQIcq1gKuPf+Cz6Fy8Ygcyt3YFVgvvlSj8/CugR7ubmcl
+oFVavGsCdYZJrgsko2aCmQxykqi5EDrA2OW7OJfSI3NPSkLmuCXxplNFAoGBALHD
+D5pDqOTAzCY0vlY0qNrsEr4ZdvO8wQP1XtyEzB919MDy01CSuPZtKfeGxNWIyN1G
+SEb5lZnQuSCdOaXPwLjURMralQQmKlQbj26YVZTHJD5AwK1ILTloYWgmaUzhbfGs
+a04wD8xgVGjVEquHI3e9AueEBypztgJgiaGDSZxBAoGADpxUn3L6lJrPyOd3IJrj
+ypU/EfvY7Qd5pRTrJd9tObbi8zF1sWi/FcQNgoZP7oz/aklFfq8WWwJbe0fL1Wk/
+MeVHj8JEc/dh1ISgbHYdBgegvS6L30RcNRUJWANYcifEQPlSHTzYXviQ8tEOCq+S
+/TPqxnd2CkT6w3bSCJbxKVM=
+-----END PRIVATE KEY-----
diff --git a/tests/host/certificate/private3.key b/tests/host/certificate/private3.key
new file mode 100644
index 0000000000000000000000000000000000000000..f42616230794d18468f6e3d6074f35aa39a52805
--- /dev/null
+++ b/tests/host/certificate/private3.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDCA+6P2eieXHaV
+JivtWif7SntjjkJm0juRKRRGsT3wt+zCZqoDe8zylTBN0mse/POWXdC+zXRMC2X/
+c4V10kgrvWbnNdFdUFfBUphiXSoqnUYHZ6Ta+b4UTzC2tECSUEnSCz9n1ofHnyqD
+yT9FELzVkRkQqexD+BFgZTF39R4q8BA4bWKQy94Kgvb+IP77+ou4fhkBLI1MX5nk
+Wa3Oyu4TMzT/tqgPE70hk8wQzUU2aiwJ7IsmnWE6Ysk7c4DYMJQF/51bi2ByZWER
+NjyBY6L+ZV90aL4UFR9O+Pw9HatfHVBRdmzSkKJOr9iu4summWgH0QYDmbkdhGwY
+vup0EmEfAgMBAAECggEAdEgFAGSbHebPD79sDnq9gcf3QgjuVU/lcbAMPf5W4GJr
+3WvItAPMJwwxgkL9/vmeSN37kY/0BuvB+yPStnYM2WJQPX0s+V+A6RZGzJWIAzh1
+01RUIwYR3XxE9wv7s3W5eNFS9DpI8OS9h3TjndJVSy8Gtc0SFP6l839S8dGQfiyN
+eMqV16M+TM0LwyEogvGa79l4HjMIcorypCXg8NVcDaxNJYnAcegoRwdhGsb4jKG6
+kB+Z4dfAKLu7OFT6/20Q0QUdA/PrdBRAFt8KPhrrweKaAApVtrU0OHNs9ULFHXnu
+kSVKZ+UTCUGWxMd4lJw5XZQ4FqUdb8Sxt8TUuvRioQKBgQDy6zY1EiqsDZE+sJbd
+/jnUWn+I4/xR5y4KmbEx76dWL39TooyiHYKABJQ9BgvBIXi95AXaRodwn12DhaW7
+VW0m4RgJH/FNZoxc9xOE2+EPr2pQGn6bvJK9IjsITDoAmDbguzMZ+TCDGZqIYiIE
+GTcgeW7NOBYM2Qy8Ufqe9zfV0QKBgQDMdo426TPxdU6Gb8AVOFFuXlL6Py0Sxk0q
+pEAhyEzCKV9HM1eX8aDrJ5++lFiMwlhkYRrWVBENPyhPgWo9sMATJM0AIsBKTSyg
+rVuqlaU8e+Pqyl8ZMOZ6uMq3zLts/Vp1sX5yU8vw5FqMddMas6SMpIoPEIAiJlse
+CujyJ29T7wKBgQCiQnry+C+IvYdHWK1tm2MFdW27Ao6IJuOaMQ8rS+l6qD9kni9S
+GmQRHv3lxSQU3UbJkIZYRsQxdkIAmEUb3PQMBE8JyUxlZxpa/q8LD9RFpeZdm1T2
+sf9SVosX/9K+ku4VLvXzY4AEEhYnA2W1VyJ7jqF0cwJHkrPvFtNRW9DwAQKBgCRi
+6NYu1DahSLM2Cfn8xskccinkulHABpWTG3KnoblgAXu7UFhTAO84Yv5YihWqtG5Q
+taT02v//gF39yvlljhkaEH14sb3HVCzYDRsjfH9yENKE5z2lbS7j2fexsJ0pzUJq
+rvULopyhFtguU75Jv/vjgEpEBnmNV+PVzzTg/bfzAoGAGz11E33qpZjVw6becf9w
+U8qnPfncIqSCg0fWNnsYwD56vI9L2ExCZG//SOUZ54b8GW+RaTFDHOlIE8dRAhrF
+M5QnEjm2S+wVPJz7gKQ3cVART8EPi/Q6BT7YIgNIhemq+AwW5xMhZcBiA3vRI/Eu
+vi807exD569efFLa9uspI8o=
+-----END PRIVATE KEY-----
diff --git a/tests/host/certificate/test_host_certificate.yml b/tests/host/certificate/test_host_certificate.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7607006f1f0309abfdcde0ada0478ac7878f3854
--- /dev/null
+++ b/tests/host/certificate/test_host_certificate.yml
@@ -0,0 +1,110 @@
+#
+# Generate self-signed certificates using openssl:
+#
+#   openssl req -x509 -newkey rsa:2048 -days 365 -nodes -keyout private1.key -out cert1.pem -subj '/CN=test'
+#   openssl req -x509 -newkey rsa:2048 -days 365 -nodes -keyout private2.key -out cert2.pem -subj '/CN=test'
+#   openssl req -x509 -newkey rsa:2048 -days 365 -nodes -keyout private3.key -out cert3.pem -subj '/CN=test'
+#
+# Convert the certificate do DER for easier handling through CLI
+#
+#   openssl x509 -outform der -in cert1.pem -out cert1.der
+#   openssl x509 -outform der -in cert2.pem -out cert2.der
+#   openssl x509 -outform der -in cert3.pem -out cert3.der
+#
+# Use base64:
+#
+#  base64 cert1.der -w5000
+#  base64 cert2.der -w5000
+#  base64 cert3.der -w5000
+#
+---
+- name: Test host certificates
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  - name: Get Domain from server name
+    set_fact:
+      ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}"
+    when: ipaserver_domain is not defined
+
+  - name: Host test absent
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      name: "{{ 'test.' + ipaserver_domain }}"
+      state: absent
+
+  - name: Host test present
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      name: "{{ 'test.' + ipaserver_domain }}"
+      force: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Host test cert members present
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      name: "{{ 'test.' + ipaserver_domain }}"
+      certificate:
+      - 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
+      action: member
+    register: result
+    failed_when: not result.changed
+
+  - name: Host test cert members present again
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      name: "{{ 'test.' + ipaserver_domain }}"
+      certificate:
+      - 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
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Host test cert members absent
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      name: "{{ 'test.' + ipaserver_domain }}"
+      certificate:
+      - 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
+      state: absent
+      action: member
+    register: result
+    failed_when: not result.changed
+
+  - name: Host test cert members absent again
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      name: "{{ 'test.' + ipaserver_domain }}"
+      certificate:
+      - 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
+      state: absent
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Host test absent
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      name: "{{ 'test.' + ipaserver_domain }}"
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Host test absent again
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      name: "{{ 'test.' + ipaserver_domain }}"
+      state: absent
+    register: result
+    failed_when: result.changed
diff --git a/tests/host/certificate/test_hosts_certificate.yml b/tests/host/certificate/test_hosts_certificate.yml
new file mode 100644
index 0000000000000000000000000000000000000000..853762c6c649ca5e8d936728b81252bbcbebc0b9
--- /dev/null
+++ b/tests/host/certificate/test_hosts_certificate.yml
@@ -0,0 +1,109 @@
+#
+# Generate self-signed certificates using openssl:
+#
+#   openssl req -x509 -newkey rsa:2048 -days 365 -nodes -keyout private1.key -out cert1.pem -subj '/CN=test'
+#   openssl req -x509 -newkey rsa:2048 -days 365 -nodes -keyout private2.key -out cert2.pem -subj '/CN=test'
+#   openssl req -x509 -newkey rsa:2048 -days 365 -nodes -keyout private3.key -out cert3.pem -subj '/CN=test'
+#
+# Convert the certificate do DER for easier handling through CLI
+#
+#   openssl x509 -outform der -in cert1.pem -out cert1.der
+#   openssl x509 -outform der -in cert2.pem -out cert2.der
+#   openssl x509 -outform der -in cert3.pem -out cert3.der
+#
+# Use base64:
+#
+#  base64 cert1.der -w5000
+#  base64 cert2.der -w5000
+#  base64 cert3.der -w5000
+#
+---
+- name: Test host certificates
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  - name: Get Domain from server name
+    set_fact:
+      ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}"
+    when: ipaserver_domain is not defined
+
+  - name: Host test absent
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      hosts:
+      - name: "{{ 'test.' + ipaserver_domain }}"
+      state: absent
+
+  - name: Host test present
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      hosts:
+      - name: "{{ 'test.' + ipaserver_domain }}"
+        force: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Host test cert members present
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      hosts:
+      - name: "{{ 'test.' + ipaserver_domain }}"
+        certificate:
+        - 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
+      action: member
+    register: result
+    failed_when: not result.changed
+
+  - name: Host test cert members present again
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      hosts:
+      - name: "{{ 'test.' + ipaserver_domain }}"
+        certificate:
+        - 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
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Host test cert members absent
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      hosts:
+      - name: "{{ 'test.' + ipaserver_domain }}"
+        certificate:
+        - MIIC/zCCAeegAwIBAgIUZGHLaSYg1myp6EI4VGWSC27vOrswDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0xOTEwMTQxNjI4MzVaFw0yMDEwMTMxNjI4MzVaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDER/lB8wUAmPTSwSc/NOXNlzdpPOQDSwrhKH6XsqZF4KpQoSY/nmCjAhJmOVpOUo4K2fGRZ0yAH9fkGv6yJP6c7IAFjLeec7GPHVwN4bZrP1DXfTAmfmXhcRQbCYkV+wmq8Puzw/+xA9EJrrodnJPPsE6E8HnSVLF6Ys9+cJMJ7HuwOI+wYt3gkmspsir1tccmf4x1PP+yHJWdcXyetlFRcmZ8gspjqOR2jb89xSQsh8gcyDW6rPNlSTzYZ2FmNtjES6ZhCsYL31fQbF2QglidlLGpAlvHUUS+xCigW73cvhFPMWXcfO51Mr15RcgYTckY+7QZ2nYqplRBoDlQl6DnAgMBAAGjUzBRMB0GA1UdDgQWBBTPG99XVRdxpOXMZo3Nhy+ldnf13TAfBgNVHSMEGDAWgBTPG99XVRdxpOXMZo3Nhy+ldnf13TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAjWTcnIl2mpNbfHAN8DB4Kk+RNRmhsH0y+r/47MXVTMMMToCfofeNY3Jeohu+2lIXMPQfTvXUbDTkNAGsGLv6LtQEUfSREqgk1eY7bT9BFfpH1uV2ZFhCO9jBA+E4bf55Kx7bgUNG31ykBshOsOblOJM1lS/0q4TWHAxrsU2PNwPi8X0ten+eGeB8aRshxS17Ij2cH0fdAMmSA+jMAvTIZl853Bxe0HuozauKwOFWL4qHm61c4O/j1mQCLqJKYfJ9mBDWFQLszd/tF+ePKiNhZCQly60F8Lumn2CDZj5UIkl8wk9Wls5n1BIQs+M8AN65NAdv7+js8jKUKCuyji8r3
+        - MIIC/zCCAeegAwIBAgIUAWE1vaA+mZd3nwZqwWH64EbHvR0wDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0xOTEwMTQxNjI4NDVaFw0yMDEwMTMxNjI4NDVaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCWzJibKtN8Zf7LgandINhFonx99AKi44iaZkrlMKEObE6Faf8NTUbUgK3VfJNYmCbA1baLVJ0YZJijJ7S/4o7h7eeqcJVXJkEhWNTimWXNW/YCzTHe3SSapnSYOKmdHHRClplysL8OyyEG7pbX/aB9iAfFb/+vUFCX5sMwFFrYxOimKJ9Pc/NRFtdv1wNw1rqWKF1ZzagWRlG4QgzRGwQ4quc7yO98TKikj2OPiIt7Zd46hbqQxmgGBtCkVOZIhxu77OmNrFsXmM4rZZpmqh0UdqcpwkRojVnGXmNqeMCd6dNTnLhr9wukUYw0KgE57zCDVr9Ix+p/dA5R1mG4RJ2XAgMBAAGjUzBRMB0GA1UdDgQWBBSbuiH2lNVrID3yt1SsFwtOFKOnpTAfBgNVHSMEGDAWgBSbuiH2lNVrID3yt1SsFwtOFKOnpTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBCVWd293wWyohFqMFMHRBBg97T2Uc1yeT0dMH4BpuOaCqQp4q5ep+uLcXEI6+3mEwm8pa/ULQCD8yLLdotIWlG3+h/4boFpdiPFcBDgT8kGe+0KOzB8Nt7E13QYOu12MNi10qwGrjKhdhu1xBe4fpY5VCetVU1OLyuTsUyucQsFrtZI0SR83h+blbyoMZ7IhMngCfGUe1bnYeWnLbpFbigKfPuVDWsMH2kgj05EAd5EgHkWbX8QA8hmcmDKfNT3YZM8kiGQwmFrnQdq8bN0uHR8Nz+24cbmdbHcD65wlDW6GmYxi8mW+V6bAqn9pir/J14r4YFnqMGgjmdt81tscJV
+        - MIIC/zCCAeegAwIBAgIUTC33WUoYGFoIVGMwgjbc5J6xCyowDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0xOTEwMTQxNjI4NTJaFw0yMDEwMTMxNjI4NTJaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCA+6P2eieXHaVJivtWif7SntjjkJm0juRKRRGsT3wt+zCZqoDe8zylTBN0mse/POWXdC+zXRMC2X/c4V10kgrvWbnNdFdUFfBUphiXSoqnUYHZ6Ta+b4UTzC2tECSUEnSCz9n1ofHnyqDyT9FELzVkRkQqexD+BFgZTF39R4q8BA4bWKQy94Kgvb+IP77+ou4fhkBLI1MX5nkWa3Oyu4TMzT/tqgPE70hk8wQzUU2aiwJ7IsmnWE6Ysk7c4DYMJQF/51bi2ByZWERNjyBY6L+ZV90aL4UFR9O+Pw9HatfHVBRdmzSkKJOr9iu4summWgH0QYDmbkdhGwYvup0EmEfAgMBAAGjUzBRMB0GA1UdDgQWBBSJCQ8ho0Ppe0khVhgiMqsvlgxIjzAfBgNVHSMEGDAWgBSJCQ8ho0Ppe0khVhgiMqsvlgxIjzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAILLPnau32r/YoOVCVWQotGtySy36aFlHa3T8IkSpatNCPIf3U0FWS6TVYBwY0PBfdqWBkvCuJTupLh0OEP4TCsDa5pJGOK7blyfiAfcHajqyouACSVNlG63EPvB63h4H4F4HJnhDd4z7pVC/WPB8w5GTBJNjELmeWfH7nj7lu8UkOdLhzTKL40RPs0k4l09yYBmZqqExxGsSfvRBQcrwlAsvQ0E/cTNGbyzOKs3SbOM2WEHye6xNEsey01icYcjfjqvEd6mw3+WOUeJAuDH9/EOloFM2iz5Xp31Ig3WT0RVy+lMriG9GesPpFBs2xp9wQCXLNIkpbHKyYs3voMyBH
+      state: absent
+      action: member
+    #register: result
+    #failed_when: not result.changed
+
+  - name: Host test cert members absent again
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      hosts:
+      - name: "{{ 'test.' + ipaserver_domain }}"
+        certificate:
+        - MIIC/zCCAeegAwIBAgIUZGHLaSYg1myp6EI4VGWSC27vOrswDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0xOTEwMTQxNjI4MzVaFw0yMDEwMTMxNjI4MzVaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDER/lB8wUAmPTSwSc/NOXNlzdpPOQDSwrhKH6XsqZF4KpQoSY/nmCjAhJmOVpOUo4K2fGRZ0yAH9fkGv6yJP6c7IAFjLeec7GPHVwN4bZrP1DXfTAmfmXhcRQbCYkV+wmq8Puzw/+xA9EJrrodnJPPsE6E8HnSVLF6Ys9+cJMJ7HuwOI+wYt3gkmspsir1tccmf4x1PP+yHJWdcXyetlFRcmZ8gspjqOR2jb89xSQsh8gcyDW6rPNlSTzYZ2FmNtjES6ZhCsYL31fQbF2QglidlLGpAlvHUUS+xCigW73cvhFPMWXcfO51Mr15RcgYTckY+7QZ2nYqplRBoDlQl6DnAgMBAAGjUzBRMB0GA1UdDgQWBBTPG99XVRdxpOXMZo3Nhy+ldnf13TAfBgNVHSMEGDAWgBTPG99XVRdxpOXMZo3Nhy+ldnf13TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAjWTcnIl2mpNbfHAN8DB4Kk+RNRmhsH0y+r/47MXVTMMMToCfofeNY3Jeohu+2lIXMPQfTvXUbDTkNAGsGLv6LtQEUfSREqgk1eY7bT9BFfpH1uV2ZFhCO9jBA+E4bf55Kx7bgUNG31ykBshOsOblOJM1lS/0q4TWHAxrsU2PNwPi8X0ten+eGeB8aRshxS17Ij2cH0fdAMmSA+jMAvTIZl853Bxe0HuozauKwOFWL4qHm61c4O/j1mQCLqJKYfJ9mBDWFQLszd/tF+ePKiNhZCQly60F8Lumn2CDZj5UIkl8wk9Wls5n1BIQs+M8AN65NAdv7+js8jKUKCuyji8r3
+        - MIIC/zCCAeegAwIBAgIUAWE1vaA+mZd3nwZqwWH64EbHvR0wDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0xOTEwMTQxNjI4NDVaFw0yMDEwMTMxNjI4NDVaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCWzJibKtN8Zf7LgandINhFonx99AKi44iaZkrlMKEObE6Faf8NTUbUgK3VfJNYmCbA1baLVJ0YZJijJ7S/4o7h7eeqcJVXJkEhWNTimWXNW/YCzTHe3SSapnSYOKmdHHRClplysL8OyyEG7pbX/aB9iAfFb/+vUFCX5sMwFFrYxOimKJ9Pc/NRFtdv1wNw1rqWKF1ZzagWRlG4QgzRGwQ4quc7yO98TKikj2OPiIt7Zd46hbqQxmgGBtCkVOZIhxu77OmNrFsXmM4rZZpmqh0UdqcpwkRojVnGXmNqeMCd6dNTnLhr9wukUYw0KgE57zCDVr9Ix+p/dA5R1mG4RJ2XAgMBAAGjUzBRMB0GA1UdDgQWBBSbuiH2lNVrID3yt1SsFwtOFKOnpTAfBgNVHSMEGDAWgBSbuiH2lNVrID3yt1SsFwtOFKOnpTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBCVWd293wWyohFqMFMHRBBg97T2Uc1yeT0dMH4BpuOaCqQp4q5ep+uLcXEI6+3mEwm8pa/ULQCD8yLLdotIWlG3+h/4boFpdiPFcBDgT8kGe+0KOzB8Nt7E13QYOu12MNi10qwGrjKhdhu1xBe4fpY5VCetVU1OLyuTsUyucQsFrtZI0SR83h+blbyoMZ7IhMngCfGUe1bnYeWnLbpFbigKfPuVDWsMH2kgj05EAd5EgHkWbX8QA8hmcmDKfNT3YZM8kiGQwmFrnQdq8bN0uHR8Nz+24cbmdbHcD65wlDW6GmYxi8mW+V6bAqn9pir/J14r4YFnqMGgjmdt81tscJV
+        - MIIC/zCCAeegAwIBAgIUTC33WUoYGFoIVGMwgjbc5J6xCyowDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEdGVzdDAeFw0xOTEwMTQxNjI4NTJaFw0yMDEwMTMxNjI4NTJaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCA+6P2eieXHaVJivtWif7SntjjkJm0juRKRRGsT3wt+zCZqoDe8zylTBN0mse/POWXdC+zXRMC2X/c4V10kgrvWbnNdFdUFfBUphiXSoqnUYHZ6Ta+b4UTzC2tECSUEnSCz9n1ofHnyqDyT9FELzVkRkQqexD+BFgZTF39R4q8BA4bWKQy94Kgvb+IP77+ou4fhkBLI1MX5nkWa3Oyu4TMzT/tqgPE70hk8wQzUU2aiwJ7IsmnWE6Ysk7c4DYMJQF/51bi2ByZWERNjyBY6L+ZV90aL4UFR9O+Pw9HatfHVBRdmzSkKJOr9iu4summWgH0QYDmbkdhGwYvup0EmEfAgMBAAGjUzBRMB0GA1UdDgQWBBSJCQ8ho0Ppe0khVhgiMqsvlgxIjzAfBgNVHSMEGDAWgBSJCQ8ho0Ppe0khVhgiMqsvlgxIjzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAILLPnau32r/YoOVCVWQotGtySy36aFlHa3T8IkSpatNCPIf3U0FWS6TVYBwY0PBfdqWBkvCuJTupLh0OEP4TCsDa5pJGOK7blyfiAfcHajqyouACSVNlG63EPvB63h4H4F4HJnhDd4z7pVC/WPB8w5GTBJNjELmeWfH7nj7lu8UkOdLhzTKL40RPs0k4l09yYBmZqqExxGsSfvRBQcrwlAsvQ0E/cTNGbyzOKs3SbOM2WEHye6xNEsey01icYcjfjqvEd6mw3+WOUeJAuDH9/EOloFM2iz5Xp31Ig3WT0RVy+lMriG9GesPpFBs2xp9wQCXLNIkpbHKyYs3voMyBH
+      state: absent
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Host test absent
+    ipahost:
+      ipaadmin_password: SomeADMINpassword
+      hosts:
+      - name: "{{ 'test.' + ipaserver_domain }}"
+      state: absent
+    register: result
+    failed_when: not result.changed
diff --git a/tests/host/test_host.yml b/tests/host/test_host.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1a555a130de3c3b7425fe5fc61a9febb08f4799b
--- /dev/null
+++ b/tests/host/test_host.yml
@@ -0,0 +1,218 @@
+---
+- name: Test host
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Get Domain from server name
+    set_fact:
+      ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}"
+    when: ipaserver_domain is not defined
+
+  - name: Set host1_fqdn .. host6_fqdn
+    set_fact:
+      host1_fqdn: "{{ 'host1.' + ipaserver_domain }}"
+      host2_fqdn: "{{ 'host2.' + ipaserver_domain }}"
+      host3_fqdn: "{{ 'host3.' + ipaserver_domain }}"
+      host4_fqdn: "{{ 'host4.' + ipaserver_domain }}"
+      host5_fqdn: "{{ 'host5.' + ipaserver_domain }}"
+      host6_fqdn: "{{ 'host6.' + ipaserver_domain }}"
+
+  - name: Host absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      - "{{ host3_fqdn }}"
+      - "{{ host4_fqdn }}"
+      - "{{ host5_fqdn }}"
+      - "{{ host6_fqdn }}"
+      update_dns: yes
+      state: absent
+
+  - name: Get IPv4 address prefix from server node
+    set_fact:
+      ipv4_prefix: "{{ ansible_default_ipv4.address.split('.')[:-1] |
+                       join('.') }}"
+
+  - name: Host "{{ host1_fqdn }}" present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      ip_address: "{{ ipv4_prefix + '.201' }}"
+      update_dns: yes
+      reverse: no
+    register: result
+    failed_when: not result.changed
+
+  - name: Host "{{ host1_fqdn }}" present again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      ip_address: "{{ ipv4_prefix + '.201' }}"
+      update_dns: yes
+      reverse: no
+    register: result
+    failed_when: result.changed
+
+  - name: Host "{{ host2_fqdn }}" present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host2_fqdn }}"
+      ip_address: "{{ ipv4_prefix + '.202' }}"
+      update_dns: yes
+      reverse: no
+    register: result
+    failed_when: not result.changed
+
+  - name: Host "{{ host2_fqdn }}" present again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host2_fqdn }}"
+      ip_address: "{{ ipv4_prefix + '.202' }}"
+      update_dns: yes
+      reverse: no
+    register: result
+    failed_when: result.changed
+
+  - name: Host "{{ host3_fqdn }}" present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host3_fqdn }}"
+      ip_address: "{{ ipv4_prefix + '.203' }}"
+      update_dns: yes
+      reverse: no
+    register: result
+    failed_when: not result.changed
+
+  - name: Host "{{ host3_fqdn }}" present again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host3_fqdn }}"
+      ip_address: "{{ ipv4_prefix + '.203' }}"
+      update_dns: yes
+      reverse: no
+    register: result
+    failed_when: result.changed
+
+  - name: Host "{{ host4_fqdn }}" present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host4_fqdn }}"
+      ip_address: "{{ ipv4_prefix + '.204' }}"
+      update_dns: yes
+      reverse: no
+    register: result
+    failed_when: not result.changed
+
+  - name: Host "{{ host4_fqdn }}" present again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host4_fqdn }}"
+      ip_address: "{{ ipv4_prefix + '.204' }}"
+      update_dns: yes
+      reverse: no
+    register: result
+    failed_when: result.changed
+
+  - name: Host "{{ host5_fqdn }}" present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host5_fqdn }}"
+      ip_address: "{{ ipv4_prefix + '.205' }}"
+      update_dns: yes
+      reverse: no
+    register: result
+    failed_when: not result.changed
+
+  - name: Host "{{ host5_fqdn }}" present again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      ip_address: "{{ ipv4_prefix + '.205' }}"
+      update_dns: yes
+      reverse: no
+    register: result
+    failed_when: result.changed
+
+  - name: Host "{{ host6_fqdn }}" present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host6_fqdn }}"
+      ip_address: "{{ ipv4_prefix + '.206' }}"
+      update_dns: yes
+      reverse: no
+    register: result
+    failed_when: not result.changed
+
+  - name: Host "{{ host6_fqdn }}" present again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host6_fqdn }}"
+      ip_address: "{{ ipv4_prefix + '.206' }}"
+      update_dns: yes
+      reverse: no
+    register: result
+    failed_when: result.changed
+
+  # disabled can only be checked with enabled hosts, all hosts above are
+  # not enabled.
+  #- name: Hosts host1..host6 disabled
+  #  ipahost:
+  #    ipaadmin_password: MyPassword123
+  #    name:
+  #    - "{{ host1_fqdn }}"
+  #    - "{{ host2_fqdn }}"
+  #    - "{{ host3_fqdn }}"
+  #    - "{{ host4_fqdn }}"
+  #    - "{{ host5_fqdn }}"
+  #    - "{{ host6_fqdn }}"
+  #    state: disabled
+  #  register: result
+  #  failed_when: not result.changed
+  #
+  #- name: Hosts host1..host6 disabled again
+  #  ipahost:
+  #    ipaadmin_password: MyPassword123
+  #    name:
+  #    - "{{ host1_fqdn }}"
+  #    - "{{ host2_fqdn }}"
+  #    - "{{ host3_fqdn }}"
+  #    - "{{ host4_fqdn }}"
+  #    - "{{ host5_fqdn }}"
+  #    - "{{ host6_fqdn }}"
+  #    state: disabled
+  #  register: result
+  #  failed_when: result.changed
+
+  - name: Hosts host1..host6 absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      - "{{ host3_fqdn }}"
+      - "{{ host4_fqdn }}"
+      - "{{ host5_fqdn }}"
+      - "{{ host6_fqdn }}"
+      update_dns: yes
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Hosts host1..host6 absent again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      - "{{ host3_fqdn }}"
+      - "{{ host4_fqdn }}"
+      - "{{ host5_fqdn }}"
+      - "{{ host6_fqdn }}"
+      update_dns: yes
+      state: absent
+    register: result
+    failed_when: result.changed
+
diff --git a/tests/host/test_host_allow_create_keytab.yml b/tests/host/test_host_allow_create_keytab.yml
new file mode 100644
index 0000000000000000000000000000000000000000..eb7b776466c38da861d3e835fc0f3ad48a8fab28
--- /dev/null
+++ b/tests/host/test_host_allow_create_keytab.yml
@@ -0,0 +1,278 @@
+---
+- name: Test host allow_create_keytab
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Get Domain from server name
+    set_fact:
+      ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}"
+    when: ipaserver_domain is not defined
+
+  - name: Get Realm from server name
+    set_fact:
+      ipaserver_realm: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') | upper }}"
+    when: ipaserver_realm is not defined
+
+  - name: Set host1_fqdn .. host3_fqdn
+    set_fact:
+      host1_fqdn: "{{ 'host1.' + ipaserver_domain }}"
+      host2_fqdn: "{{ 'host2.' + ipaserver_domain }}"
+      host3_fqdn: "{{ 'host3.' + ipaserver_domain }}"
+
+  - name: Host host1..., host2... and host3... absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      - "{{ host3_fqdn }}"
+      state: absent
+
+  - name: Ensure host-groups hostgroup1 and hostgroup2 absent
+    ipahostgroup:
+      ipaadmin_password: MyPassword123
+      name: hostgroup1,hostgroup2
+      state: absent
+
+  - name: Ensure users user1 and user2 absent
+    ipauser:
+      ipaadmin_password: MyPassword123
+      users:
+      - name: user1
+      - name: user2
+      state: absent
+
+  - name: Ensure group1 and group2 absent
+    ipagroup:
+      ipaadmin_password: MyPassword123
+      name: group1,group2
+      state: absent
+
+  - name: Host host2... and host3... present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host2_fqdn }}"
+        force: yes
+      - name: "{{ host3_fqdn }}"
+        force: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure host-group hostgroup1 present
+    ipahostgroup:
+      ipaadmin_password: MyPassword123
+      name: hostgroup1
+      state: present
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure host-group hostgroup2 present
+    ipahostgroup:
+      ipaadmin_password: MyPassword123
+      name: hostgroup2
+      state: present
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure users user1 and user2 present
+    ipauser:
+      ipaadmin_password: MyPassword123
+      users:
+      - name: user1
+        first: First1
+        last: Last1
+      - name: user2
+        first: First2
+        last: Last2
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure group1 present
+    ipagroup:
+      ipaadmin_password: MyPassword123
+      name: group1
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure group2 present
+    ipagroup:
+      ipaadmin_password: MyPassword123
+      name: group2
+    register: result
+    failed_when: not result.changed
+
+  - name: Host host1... present with allow_create_keytab users,groups,hosts and hostgroups
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      allow_create_keytab_user:
+      - user1
+      - user2
+      allow_create_keytab_group:
+      - group1
+      - group2
+      allow_create_keytab_host:
+      - "{{ host2_fqdn }}"
+      - "{{ host3_fqdn }}"
+      allow_create_keytab_hostgroup:
+      - hostgroup1
+      - hostgroup2
+      force: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Host host1... present with allow_create_keytab users,groups,hosts and hostgroups again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      allow_create_keytab_user:
+      - user1
+      - user2
+      allow_create_keytab_group:
+      - group1
+      - group2
+      allow_create_keytab_host:
+      - "{{ host2_fqdn }}"
+      - "{{ host3_fqdn }}"
+      allow_create_keytab_hostgroup:
+      - hostgroup1
+      - hostgroup2
+      force: yes
+    register: result
+    failed_when: result.changed
+
+  - name: Host host1... absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name:
+      - "{{ host1_fqdn }}"
+      state: absent
+
+  - name: Host host1... present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      force: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Host host1... ensure allow_create_keytab users,groups,hosts and hostgroups present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      allow_create_keytab_user:
+      - user1
+      - user2
+      allow_create_keytab_group:
+      - group1
+      - group2
+      allow_create_keytab_host:
+      - "{{ host2_fqdn }}"
+      - "{{ host3_fqdn }}"
+      allow_create_keytab_hostgroup:
+      - hostgroup1
+      - hostgroup2
+      action: member
+    register: result
+    failed_when: not result.changed
+
+  - name: Host host1... ensure allow_create_keytab users,groups,hosts and hostgroups present again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      allow_create_keytab_user:
+      - user1
+      - user2
+      allow_create_keytab_group:
+      - group1
+      - group2
+      allow_create_keytab_host:
+      - "{{ host2_fqdn }}"
+      - "{{ host3_fqdn }}"
+      allow_create_keytab_hostgroup:
+      - hostgroup1
+      - hostgroup2
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Host host1... ensure allow_create_keytab users,groups,hosts and hostgroups absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      allow_create_keytab_user:
+      - user1
+      - user2
+      allow_create_keytab_group:
+      - group1
+      - group2
+      allow_create_keytab_host:
+      - "{{ host2_fqdn }}"
+      - "{{ host3_fqdn }}"
+      allow_create_keytab_hostgroup:
+      - hostgroup1
+      - hostgroup2
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Host host1... ensure allow_create_keytab users,groups,hosts and hostgroups absent again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      allow_create_keytab_user:
+      - user1
+      - user2
+      allow_create_keytab_group:
+      - group1
+      - group2
+      allow_create_keytab_host:
+      - "{{ host2_fqdn }}"
+      - "{{ host3_fqdn }}"
+      allow_create_keytab_hostgroup:
+      - hostgroup1
+      - hostgroup2
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  - name: Host host1..., host2... and host3... absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      - "{{ host3_fqdn }}"
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure host-groups hostgroup1 and hostgroup2 absent
+    ipahostgroup:
+      ipaadmin_password: MyPassword123
+      name: hostgroup1,hostgroup2
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure users user1 and user2 absent
+    ipauser:
+      ipaadmin_password: MyPassword123
+      users:
+      - name: user1
+      - name: user2
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure group1 and group2 absent
+    ipagroup:
+      ipaadmin_password: MyPassword123
+      name: group1,group2
+      state: absent
+    register: result
+    failed_when: not result.changed
diff --git a/tests/host/test_host_allow_retrieve_keytab.yml b/tests/host/test_host_allow_retrieve_keytab.yml
new file mode 100644
index 0000000000000000000000000000000000000000..65d86aa64777bac4ee83409478119c5458fed9f6
--- /dev/null
+++ b/tests/host/test_host_allow_retrieve_keytab.yml
@@ -0,0 +1,278 @@
+---
+- name: Test host allow_retrieve_keytab
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Get Domain from server name
+    set_fact:
+      ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}"
+    when: ipaserver_domain is not defined
+
+  - name: Get Realm from server name
+    set_fact:
+      ipaserver_realm: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') | upper }}"
+    when: ipaserver_realm is not defined
+
+  - name: Set host1_fqdn .. host3_fqdn
+    set_fact:
+      host1_fqdn: "{{ 'host1.' + ipaserver_domain }}"
+      host2_fqdn: "{{ 'host2.' + ipaserver_domain }}"
+      host3_fqdn: "{{ 'host3.' + ipaserver_domain }}"
+
+  - name: Host host1..., host2... and host3... absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      - "{{ host3_fqdn }}"
+      state: absent
+
+  - name: Ensure host-groups hostgroup1 and hostgroup2 absent
+    ipahostgroup:
+      ipaadmin_password: MyPassword123
+      name: hostgroup1,hostgroup2
+      state: absent
+
+  - name: Ensure users user1 and user2 absent
+    ipauser:
+      ipaadmin_password: MyPassword123
+      users:
+      - name: user1
+      - name: user2
+      state: absent
+
+  - name: Ensure group1 and group2 absent
+    ipagroup:
+      ipaadmin_password: MyPassword123
+      name: group1,group2
+      state: absent
+
+  - name: Host host2... and host3... present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host2_fqdn }}"
+        force: yes
+      - name: "{{ host3_fqdn }}"
+        force: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure host-group hostgroup1 present
+    ipahostgroup:
+      ipaadmin_password: MyPassword123
+      name: hostgroup1
+      state: present
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure host-group hostgroup2 present
+    ipahostgroup:
+      ipaadmin_password: MyPassword123
+      name: hostgroup2
+      state: present
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure users user1 and user2 present
+    ipauser:
+      ipaadmin_password: MyPassword123
+      users:
+      - name: user1
+        first: First1
+        last: Last1
+      - name: user2
+        first: First2
+        last: Last2
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure group1 present
+    ipagroup:
+      ipaadmin_password: MyPassword123
+      name: group1
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure group2 present
+    ipagroup:
+      ipaadmin_password: MyPassword123
+      name: group2
+    register: result
+    failed_when: not result.changed
+
+  - name: Host host1... present with allow_retrieve_keytab users,groups,hosts and hostgroups
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      allow_retrieve_keytab_user:
+      - user1
+      - user2
+      allow_retrieve_keytab_group:
+      - group1
+      - group2
+      allow_retrieve_keytab_host:
+      - "{{ host2_fqdn }}"
+      - "{{ host3_fqdn }}"
+      allow_retrieve_keytab_hostgroup:
+      - hostgroup1
+      - hostgroup2
+      force: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Host host1... present with allow_retrieve_keytab users,groups,hosts and hostgroups again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      allow_retrieve_keytab_user:
+      - user1
+      - user2
+      allow_retrieve_keytab_group:
+      - group1
+      - group2
+      allow_retrieve_keytab_host:
+      - "{{ host2_fqdn }}"
+      - "{{ host3_fqdn }}"
+      allow_retrieve_keytab_hostgroup:
+      - hostgroup1
+      - hostgroup2
+      force: yes
+    register: result
+    failed_when: result.changed
+
+  - name: Host host1... absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name:
+      - "{{ host1_fqdn }}"
+      state: absent
+
+  - name: Host host1... present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      force: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Host host1... ensure allow_retrieve_keytab users,groups,hosts and hostgroups present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      allow_retrieve_keytab_user:
+      - user1
+      - user2
+      allow_retrieve_keytab_group:
+      - group1
+      - group2
+      allow_retrieve_keytab_host:
+      - "{{ host2_fqdn }}"
+      - "{{ host3_fqdn }}"
+      allow_retrieve_keytab_hostgroup:
+      - hostgroup1
+      - hostgroup2
+      action: member
+    register: result
+    failed_when: not result.changed
+
+  - name: Host host1... ensure allow_retrieve_keytab users,groups,hosts and hostgroups present again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      allow_retrieve_keytab_user:
+      - user1
+      - user2
+      allow_retrieve_keytab_group:
+      - group1
+      - group2
+      allow_retrieve_keytab_host:
+      - "{{ host2_fqdn }}"
+      - "{{ host3_fqdn }}"
+      allow_retrieve_keytab_hostgroup:
+      - hostgroup1
+      - hostgroup2
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Host host1... ensure allow_retrieve_keytab users,groups,hosts and hostgroups absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      allow_retrieve_keytab_user:
+      - user1
+      - user2
+      allow_retrieve_keytab_group:
+      - group1
+      - group2
+      allow_retrieve_keytab_host:
+      - "{{ host2_fqdn }}"
+      - "{{ host3_fqdn }}"
+      allow_retrieve_keytab_hostgroup:
+      - hostgroup1
+      - hostgroup2
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Host host1... ensure allow_retrieve_keytab users,groups,hosts and hostgroups absent again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      allow_retrieve_keytab_user:
+      - user1
+      - user2
+      allow_retrieve_keytab_group:
+      - group1
+      - group2
+      allow_retrieve_keytab_host:
+      - "{{ host2_fqdn }}"
+      - "{{ host3_fqdn }}"
+      allow_retrieve_keytab_hostgroup:
+      - hostgroup1
+      - hostgroup2
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  - name: Host host1..., host2... and host3... absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      - "{{ host3_fqdn }}"
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure host-groups hostgroup1 and hostgroup2 absent
+    ipahostgroup:
+      ipaadmin_password: MyPassword123
+      name: hostgroup1,hostgroup2
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure users user1 and user2 absent
+    ipauser:
+      ipaadmin_password: MyPassword123
+      users:
+      - name: user1
+      - name: user2
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure group1 and group2 absent
+    ipagroup:
+      ipaadmin_password: MyPassword123
+      name: group1,group2
+      state: absent
+    register: result
+    failed_when: not result.changed
diff --git a/tests/host/test_host_managedby_host.yml b/tests/host/test_host_managedby_host.yml
new file mode 100644
index 0000000000000000000000000000000000000000..78c5a43d76fb7a355858a537718a97fb8c70ce15
--- /dev/null
+++ b/tests/host/test_host_managedby_host.yml
@@ -0,0 +1,125 @@
+---
+- name: Test host managedby_host
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Get Domain from server name
+    set_fact:
+      ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}"
+    when: ipaserver_domain is not defined
+
+  - name: Set host1_fqdn .. host2_fqdn
+    set_fact:
+      host1_fqdn: "{{ 'host1.' + ipaserver_domain }}"
+      host2_fqdn: "{{ 'host2.' + ipaserver_domain }}"
+
+  - name: Host absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      update_dns: yes
+      state: absent
+
+  - name: Host "{{ host1_fqdn }}" present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      force: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Host "{{ host2_fqdn }}" present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host2_fqdn }}"
+      force: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Host "{{ host1_fqdn }}" managed by "{{ 'host2.' + ipaserver_domain }}"
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      managedby_host: "{{ host2_fqdn }}"
+    register: result
+    failed_when: not result.changed
+
+  - name: Host "{{ host1_fqdn }}" managed by "{{ 'host2.' + ipaserver_domain }}" again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      managedby_host: "{{ host2_fqdn }}"
+    register: result
+    failed_when: result.changed
+
+  - name: Host "{{ host1_fqdn }}" managed by "{{ groups.ipaserver[0] }}"
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      managedby_host: "{{ groups.ipaserver[0] }}"
+      action: member
+    register: result
+    failed_when: not result.changed
+
+  - name: Host "{{ host1_fqdn }}" managed by "{{ groups.ipaserver[0] }}" again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      managedby_host: "{{ groups.ipaserver[0] }}"
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Host "{{ host1_fqdn }}" not managed by "{{ groups.ipaserver[0] }}"
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      managedby_host: "{{ groups.ipaserver[0] }}"
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Host "{{ host1_fqdn }}" not managed by "{{ groups.ipaserver[0] }}" again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      managedby_host: "{{ groups.ipaserver[0] }}"
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  - name: Host "{{ host1_fqdn }}" not managed by "{{ 'host2.' + ipaserver_domain }}"
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      managedby_host: "{{ host2_fqdn }}"
+      state: absent
+      action: member
+    register: result
+    failed_when: not result.changed
+
+  - name: Host "{{ host1_fqdn }}" not managed by "{{ 'host2.' + ipaserver_domain }}" again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      managedby_host: "{{ host2_fqdn }}"
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  - name: Host absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      update_dns: yes
+      state: absent
+    register: result
+    failed_when: not result.changed
diff --git a/tests/host/test_host_principal.yml b/tests/host/test_host_principal.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0dce400a32387ba79e8829bf64ac25a78858716f
--- /dev/null
+++ b/tests/host/test_host_principal.yml
@@ -0,0 +1,130 @@
+---
+- name: Test host principal
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Get Domain from server name
+    set_fact:
+      ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}"
+    when: ipaserver_domain is not defined
+
+  - name: Get Realm from server name
+    set_fact:
+      ipaserver_realm: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') | upper }}"
+    when: ipaserver_realm is not defined
+
+  - name: Set host1_fqdn
+    set_fact:
+      host1_fqdn: "{{ 'host1.' + ipaserver_domain }}"
+
+  - name: Host host1 absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name:
+      - "{{ host1_fqdn }}"
+      update_dns: yes
+      state: absent
+
+  - name: Host host1... present with principal host/testhost1...
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      principal:
+      - "{{ 'host/testhost1.' + ipaserver_domain + '@' + ipaserver_realm }}" 
+      force: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Host host1... principal host/host1... present (existing already)
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      principal:
+      - "{{ 'host/host1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Host host1... principal host/testhost1... present again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      principal: "{{ 'host/testhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Host host1... principal host/testhost1... absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      principal: "{{ 'host/testhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Host host1... principal host/testhost1... absent again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      principal: "{{ 'host/testhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  - name: Host host1... principal host/testhost1... and host/myhost1... present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      principal:
+      - "{{ 'host/testhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      - "{{ 'host/myhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      action: member
+    register: result
+    failed_when: not result.changed
+
+  - name: Host host1... principal host/testhost1... and host/myhost1... present again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      principal:
+      - "{{ 'host/testhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      - "{{ 'host/myhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Host host1... principal host/testhost1... and host/myhost1... absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      principal:
+      - "{{ 'host/testhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      - "{{ 'host/myhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Host host1... principal host/testhost1... and host/myhost1... absent again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host1_fqdn }}"
+      principal:
+      - "{{ 'host/testhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      - "{{ 'host/myhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  - name: Host host1... absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name:
+      - "{{ host1_fqdn }}"
+      update_dns: yes
+      state: absent
diff --git a/tests/host/test_host_random.yml b/tests/host/test_host_random.yml
index 0856ddc0c95077695ed52d0be1d62b2aae8da482..84893f9007280636d310d5801a9eb9d7adcb836e 100644
--- a/tests/host/test_host_random.yml
+++ b/tests/host/test_host_random.yml
@@ -9,33 +9,78 @@
       ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}"
     when: ipaserver_domain is not defined
 
+  - name: Set host1_fqdn and host2_fqdn
+    set_fact:
+      host1_fqdn: "{{ 'host1.' + ipaserver_domain }}"
+      host2_fqdn: "{{ 'host2.' + ipaserver_domain }}"
+
   - name: Test hosts absent
     ipahost:
       ipaadmin_password: MyPassword123
       name:
-      - "{{ 'host1.' + ipaserver_domain }}"
-      - "{{ 'host2.' + ipaserver_domain }}"
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
       update_dns: yes
       state: absent
 
-  - name: Host "{{ 'host1.' + ipaserver_domain }}" present with random password
+  - name: Host "{{ host1_fqdn }}" present with random password
     ipahost:
       ipaadmin_password: MyPassword123
-      name: "{{ 'host1.' + ipaserver_domain }}"
+      name: "{{ host1_fqdn }}"
       random: yes
       force: yes
       update_password: on_create
     register: ipahost
-    failed_when: not ipahost.changed or
-                 ipahost.host.randompassword is not defined
+    failed_when: not ipahost.changed
+
+  - assert:
+      that:
+      - ipahost.host.randompassword is defined
 
   - name: Print generated random password
     debug:
       var: ipahost.host.randompassword
 
-  - name: Host "{{ 'host1.' + ipaserver_domain }}" absent
+  - name: Host "{{ host1_fqdn }}" absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name:
+      - "{{ host1_fqdn }}"
+      state: absent
+
+  - name: Hosts "{{ host1_fqdn }}" and "{{ host2_fqdn }}" present with random password
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+        random: yes
+        force: yes
+      - name: "{{ host2_fqdn }}"
+        random: yes
+        force: yes
+      update_password: on_create
+    register: ipahost
+    failed_when: not ipahost.changed
+
+  - assert:
+      that:
+      - ipahost.host["{{host1_fqdn }}"].randompassword is
+        defined
+      - ipahost.host["{{host2_fqdn }}"].randompassword is
+        defined
+
+  - name: Print generated random password for "{{host1_fqdn }}"
+    debug:
+      var: ipahost.host["{{host1_fqdn }}"].randompassword
+
+  - name: Print generated random password for "{{host2_fqdn }}"
+    debug:
+      var: ipahost.host["{{host2_fqdn }}"].randompassword
+
+  - name: Hosts "{{ host1_fqdn }}" and "{{ host2_fqdn }}" absent
     ipahost:
       ipaadmin_password: MyPassword123
       name:
-      - "{{ 'host1.' + ipaserver_domain }}"
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
       state: absent
diff --git a/tests/host/test_hosts.yml b/tests/host/test_hosts.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8e92bf32b6d4b589665098bc7d75b6ca11a4045d
--- /dev/null
+++ b/tests/host/test_hosts.yml
@@ -0,0 +1,98 @@
+---
+- name: Test hosts
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  - name: Get Domain from server name
+    set_fact:
+      ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}"
+    when: ipaserver_domain is not defined
+
+  - name: Set host1_fqdn .. host6_fqdn
+    set_fact:
+      host1_fqdn: "{{ 'host1.' + ipaserver_domain }}"
+      host2_fqdn: "{{ 'host2.' + ipaserver_domain }}"
+      host3_fqdn: "{{ 'host3.' + ipaserver_domain }}"
+      host4_fqdn: "{{ 'host4.' + ipaserver_domain }}"
+      host5_fqdn: "{{ 'host5.' + ipaserver_domain }}"
+      host6_fqdn: "{{ 'host6.' + ipaserver_domain }}"
+
+  - name: Host host1..host6 absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+      - name: "{{ host2_fqdn }}"
+      - name: "{{ host3_fqdn }}"
+      - name: "{{ host4_fqdn }}"
+      - name: "{{ host5_fqdn }}"
+      - name: "{{ host6_fqdn }}"
+      state: absent
+
+  - name: Hosts host1..host6 present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+        force: yes
+      - name: "{{ host2_fqdn }}"
+        force: yes
+      - name: "{{ host3_fqdn }}"
+        force: yes
+      - name: "{{ host4_fqdn }}"
+        force: yes
+      - name: "{{ host5_fqdn }}"
+        force: yes
+      - name: "{{ host6_fqdn }}"
+        force: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Hosts host1..host6 present again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+        force: yes
+      - name: "{{ host2_fqdn }}"
+        force: yes
+      - name: "{{ host3_fqdn }}"
+        force: yes
+      - name: "{{ host4_fqdn }}"
+        force: yes
+      - name: "{{ host5_fqdn }}"
+        force: yes
+      - name: "{{ host6_fqdn }}"
+        force: yes
+    register: result
+    failed_when: result.changed
+
+  - name: Hosts host1..host6 absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+      - name: "{{ host2_fqdn }}"
+      - name: "{{ host3_fqdn }}"
+      - name: "{{ host4_fqdn }}"
+      - name: "{{ host5_fqdn }}"
+      - name: "{{ host6_fqdn }}"
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Hosts host1..host6 absent again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+      - name: "{{ host2_fqdn }}"
+      - name: "{{ host3_fqdn }}"
+      - name: "{{ host4_fqdn }}"
+      - name: "{{ host5_fqdn }}"
+      - name: "{{ host6_fqdn }}"
+      state: absent
+    register: result
+    failed_when: result.changed
diff --git a/tests/host/test_hosts_managedby_host.yml b/tests/host/test_hosts_managedby_host.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a692745a666a38d19a4de1804b1cd00186c2e688
--- /dev/null
+++ b/tests/host/test_hosts_managedby_host.yml
@@ -0,0 +1,151 @@
+---
+- name: Test hosts managedby_host
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Get Domain from server name
+    set_fact:
+      ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}"
+    when: ipaserver_domain is not defined
+
+  - name: Set host1_fqdn .. host5_fqdn
+    set_fact:
+      host1_fqdn: "{{ 'host1.' + ipaserver_domain }}"
+      host2_fqdn: "{{ 'host2.' + ipaserver_domain }}"
+      host3_fqdn: "{{ 'host3.' + ipaserver_domain }}"
+      host4_fqdn: "{{ 'host4.' + ipaserver_domain }}"
+      host5_fqdn: "{{ 'host5.' + ipaserver_domain }}"
+
+  - name: Host absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      - "{{ host3_fqdn }}"
+      - "{{ host4_fqdn }}"
+      - "{{ host5_fqdn }}"
+      update_dns: yes
+      state: absent
+
+  - name: Host "{{ host5_fqdn }}" present
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name: "{{ host5_fqdn }}"
+      force: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Hosts "{{ host1_fqdn }}" .. "{{ 'host5.' + ipaserver_domain }}" present and managed by "{{ 'host5.' + ipaserver_domain }}"
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+        force: yes
+      - name: "{{ host2_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+        force: yes
+      - name: "{{ host3_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+        force: yes
+      - name: "{{ host4_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+        force: yes
+      - name: "{{ host5_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+        force: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Hosts "{{ host1_fqdn }}" .. "{{ 'host5.' + ipaserver_domain }}" present and managed by "{{ 'host5.' + ipaserver_domain }}" again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+        force: yes
+      - name: "{{ host2_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+        force: yes
+      - name: "{{ host3_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+        force: yes
+      - name: "{{ host4_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+        force: yes
+      - name: "{{ host5_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+        force: yes
+    register: result
+    failed_when: result.changed
+
+  - name: Hosts "{{ host1_fqdn }}" .. "{{ 'host5.' + ipaserver_domain }}" managed by "{{ 'host5.' + ipaserver_domain }}"
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+      - name: "{{ host2_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+      - name: "{{ host3_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+      - name: "{{ host4_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+      - name: "{{ host5_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Hosts "{{ host1_fqdn }}" .. "{{ 'host5.' + ipaserver_domain }}" not managed by "{{ 'host5.' + ipaserver_domain }}"
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+      - name: "{{ host2_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+      - name: "{{ host3_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+      - name: "{{ host4_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+      - name: "{{ host5_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Hosts "{{ host1_fqdn }}" .. "{{ 'host5.' + ipaserver_domain }}" not managed by "{{ 'host5.' + ipaserver_domain }}" again
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+      - name: "{{ host2_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+      - name: "{{ host3_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+      - name: "{{ host4_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+      - name: "{{ host5_fqdn }}"
+        managedby_host: "{{ host5_fqdn }}"
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  - name: Hosts "{{ host1_fqdn }}" .. "{{ 'host5.' + ipaserver_domain }}" absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+      - name: "{{ host2_fqdn }}"
+      - name: "{{ host3_fqdn }}"
+      - name: "{{ host4_fqdn }}"
+      - name: "{{ host5_fqdn }}"
+      state: absent
+    register: result
+    failed_when: not result.changed
diff --git a/tests/host/test_hosts_principal.yml b/tests/host/test_hosts_principal.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5918d35d0fe7c7e707154f3ab80b64dfdbc18394
--- /dev/null
+++ b/tests/host/test_hosts_principal.yml
@@ -0,0 +1,179 @@
+---
+- name: Test hosts principal
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Get Domain from server name
+    set_fact:
+      ipaserver_domain: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') }}"
+    when: ipaserver_domain is not defined
+
+  - name: Get Realm from server name
+    set_fact:
+      ipaserver_realm: "{{ groups.ipaserver[0].split('.')[1:] | join ('.') | upper }}"
+    when: ipaserver_realm is not defined
+
+  - name: Set host1_fqdn .. host2_fqdn
+    set_fact:
+      host1_fqdn: "{{ 'host1.' + ipaserver_domain }}"
+      host2_fqdn: "{{ 'host2.' + ipaserver_domain }}"
+
+  - name: Host host1... and host2... absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      update_dns: yes
+      state: absent
+
+  - name: Host hostX... present with principal host/testhostX... X=[1,2]
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+        principal:
+        - "{{ 'host/testhost1.' + ipaserver_domain + '@' + ipaserver_realm }}" 
+        force: yes
+      - name: "{{ host2_fqdn }}"
+        principal:
+        - "{{ 'host/testhost2.' + ipaserver_domain + '@' + ipaserver_realm }}" 
+        force: yes
+    register: result
+    failed_when: not result.changed
+
+  - name: Host hostX... principal 'host/hostX... present (existing already) X=[1,2]
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+        principal:
+        - "{{ 'host/host1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      - name: "{{ host2_fqdn }}"
+        principal:
+        - "{{ 'host/host2.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Host hostX... principal host/testhostX... present again X=[1,2]
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+        principal:
+        - "{{ 'host/testhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      - name: "{{ host2_fqdn }}"
+        principal:
+        - "{{ 'host/testhost2.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Host hostX.. principal host/testhostX... absent X=[1,2]
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+        principal:
+        - "{{ 'host/testhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      - name: "{{ host2_fqdn }}"
+        principal:
+        - "{{ 'host/testhost2.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Host hostX... principal host/testhostX... absent again X=[1,2]
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+        principal:
+        - "{{ 'host/testhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      - name: "{{ host2_fqdn }}"
+        principal:
+        - "{{ 'host/testhost2.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  - name: Host hostX... principal host/testhostX... and host/myhostX... present X=[1,2]
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+        principal:
+        - "{{ 'host/testhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+        - "{{ 'host/myhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      - name: "{{ host2_fqdn }}"
+        principal:
+        - "{{ 'host/testhost2.' + ipaserver_domain + '@' + ipaserver_realm }}"
+        - "{{ 'host/myhost2.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      action: member
+    register: result
+    failed_when: not result.changed
+
+  - name: Host hostX... principal host/testhostX... and host/myhostX... present again X=[1,2]
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+        principal:
+        - "{{ 'host/testhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+        - "{{ 'host/myhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      - name: "{{ host2_fqdn }}"
+        principal:
+        - "{{ 'host/testhost2.' + ipaserver_domain + '@' + ipaserver_realm }}"
+        - "{{ 'host/myhost2.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Host hostX... principal host/testhostX... and host/myhostX... absent X=[1,2]
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+        principal:
+        - "{{ 'host/testhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+        - "{{ 'host/myhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      hosts:
+      - name: "{{ host2_fqdn }}"
+        principal:
+        - "{{ 'host/testhost2.' + ipaserver_domain + '@' + ipaserver_realm }}"
+        - "{{ 'host/myhost2.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Host hostX... principal host/testhostX... and host/myhostX... absent again X=[1,2]
+    ipahost:
+      ipaadmin_password: MyPassword123
+      hosts:
+      - name: "{{ host1_fqdn }}"
+        principal:
+        - "{{ 'host/testhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+        - "{{ 'host/myhost1.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      hosts:
+      - name: "{{ host2_fqdn }}"
+        principal:
+        - "{{ 'host/testhost2.' + ipaserver_domain + '@' + ipaserver_realm }}"
+        - "{{ 'host/myhost2.' + ipaserver_domain + '@' + ipaserver_realm }}"
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  - name: Hosts host1... and host2... absent
+    ipahost:
+      ipaadmin_password: MyPassword123
+      name:
+      - "{{ host1_fqdn }}"
+      - "{{ host2_fqdn }}"
+      update_dns: yes
+      state: absent