Initial commit with basic Molecule

Change-Id: If55c8b62cb219e575857ba5395a48ddac5973e01
diff --git a/roles/kubernetes/tasks/bootstrap-cluster.yml b/roles/kubernetes/tasks/bootstrap-cluster.yml
new file mode 100644
index 0000000..8231267
--- /dev/null
+++ b/roles/kubernetes/tasks/bootstrap-cluster.yml
@@ -0,0 +1,57 @@
+# Copyright (c) 2022 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+- name: Determine node to use for bootstrapping cluster
+  block:
+    - name: Check if any control plane is bootstrapped
+      ansible.builtin.stat:
+        path: /etc/kubernetes/admin.conf
+      register: _kubernetes_stat
+      loop: "{{ groups[kubernetes_control_plane_group] }}"
+      delegate_to: "{{ item }}"
+      delegate_facts: True
+
+- name: Pick node from pre-existing cluster
+  ansible.builtin.set_fact:
+    _kubernetes_bootstrap_node: "{{ _kubernetes_stat.results | selectattr('stat.exists', 'equalto', true) | map(attribute='item') | first }}"
+  when: _kubernetes_stat.results | selectattr('stat.exists', 'equalto', true) | length > 0
+
+- name: Select first node to initialize cluster
+  ansible.builtin.set_fact:
+    _kubernetes_bootstrap_node: "{{ groups[kubernetes_control_plane_group] | first }}"
+  when: _kubernetes_stat.results | selectattr('stat.exists', 'equalto', true) | length == 0
+
+- name: Print selected bootstrap node
+  ansible.builtin.debug:
+    msg: "{{ _kubernetes_bootstrap_node }}"
+
+- name: Upload cluster configuration for bootstrap node
+  ansible.builtin.template:
+    src: kubeadm.yaml.j2
+    dest: /etc/kubernetes/kubeadm.yaml
+  when: inventory_hostname == _kubernetes_bootstrap_node
+
+- name: Initialize cluster
+  throttle: 1
+  ansible.builtin.shell: |
+    kubeadm init --config /etc/kubernetes/kubeadm.yaml --upload-certs
+  args:
+    creates: /etc/kubernetes/admin.conf
+  environment:
+    PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+  when: inventory_hostname == _kubernetes_bootstrap_node
+
+- name: Join cluster
+  ansible.builtin.include_tasks: join-cluster.yml
+  when: inventory_hostname != _kubernetes_bootstrap_node
diff --git a/roles/kubernetes/tasks/control-plane.yml b/roles/kubernetes/tasks/control-plane.yml
new file mode 100644
index 0000000..cbb8752
--- /dev/null
+++ b/roles/kubernetes/tasks/control-plane.yml
@@ -0,0 +1,89 @@
+# Copyright (c) 2022 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+- name: Upload configuration for Keepalived
+  block:
+    - name: Create folder
+      ansible.builtin.file:
+        dest: /etc/keepalived
+        state: directory
+    - name: Upload configuration
+      ansible.builtin.template:
+        src: keepalived.conf.j2
+        dest: /etc/keepalived/keepalived.conf
+    - name: Upload health check
+      ansible.builtin.template:
+        src: check_apiserver.sh.j2
+        dest: /etc/keepalived/check_apiserver.sh
+        mode: 0755
+    - name: Upload Kubernetes manifest
+      ansible.builtin.copy:
+        src: keepalived.yaml
+        dest: /etc/kubernetes/manifests/keepalived.yaml
+
+- name: Upload configuration for HAproxy
+  block:
+    - name: Create folder
+      ansible.builtin.file:
+        dest: /etc/haproxy
+        state: directory
+    - name: Upload configuration
+      ansible.builtin.template:
+        src: haproxy.cfg.j2
+        dest: /etc/haproxy/haproxy.cfg
+    - name: Upload Kubernetes manifest
+      ansible.builtin.copy:
+        src: haproxy.yaml
+        dest: /etc/kubernetes/manifests/haproxy.yaml
+
+- name: Bootstrap cluster
+  include_tasks: bootstrap-cluster.yml
+
+- name: create folder for admin configuration
+  ansible.builtin.file:
+    path: /root/.kube
+    state: directory
+
+- name: copy admin configuration file
+  ansible.builtin.copy:
+    src: /etc/kubernetes/admin.conf
+    dest: /root/.kube/config
+    mode: 0600
+    remote_src: true
+
+- name: install pip
+  ansible.builtin.apt:
+    name: python3-pip
+    install_recommends: false
+
+- name: install kubernetes python package
+  ansible.builtin.pip:
+    name: kubernetes
+
+- name: Allow workloads on control plane nodes
+  run_once: true
+  ansible.builtin.shell: |
+    kubectl taint nodes --all node-role.kubernetes.io/master-
+  ignore_errors: true
+  changed_when: false
+
+- name: Add labels to control plane nodes
+  kubernetes.core.k8s:
+    state: patched
+    kind: Node
+    name: "{{ inventory_hostname_short }}"
+    definition:
+      metadata:
+        labels:
+          openstack-control-plane: enabled
diff --git a/roles/kubernetes/tasks/join-cluster.yml b/roles/kubernetes/tasks/join-cluster.yml
new file mode 100644
index 0000000..b65e347
--- /dev/null
+++ b/roles/kubernetes/tasks/join-cluster.yml
@@ -0,0 +1,63 @@
+# Copyright (c) 2022 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+- name: Check if the node is already part of the cluster
+  ansible.builtin.stat:
+    path: /etc/kubernetes/kubelet.conf
+  register: _stat_etc_kubernetes_kubelet_conf
+
+- name: Generate control-plane certificates for joining cluster
+  run_once: true
+  delegate_to: "{{ _kubernetes_bootstrap_node | default(groups[kubernetes_control_plane_group][0]) }}"
+  ansible.builtin.shell: |
+    kubeadm init phase upload-certs --upload-certs 2>/dev/null | grep -v upload-certs
+  changed_when: false
+  register: _kubeadm_init_upload_certs
+  when:
+    - not _stat_etc_kubernetes_kubelet_conf.stat.exists
+    - inventory_hostname in groups[kubernetes_control_plane_group]
+
+- name: Retrieve SHA256 certificate hash
+  run_once: true
+  delegate_to: "{{ _kubernetes_bootstrap_node | default(groups[kubernetes_control_plane_group][0]) }}"
+  community.crypto.x509_certificate_info:
+    path: /etc/kubernetes/pki/ca.crt
+  register: _kubeadm_certificate_info
+  when:
+    - not _stat_etc_kubernetes_kubelet_conf.stat.exists
+
+- name: Generate token for joining cluster
+  run_once: true
+  delegate_to: "{{ _kubernetes_bootstrap_node | default(groups[kubernetes_control_plane_group][0]) }}"
+  ansible.builtin.shell: |
+    kubeadm token create
+  register: _kubeadm_token_create
+  when:
+    - not _stat_etc_kubernetes_kubelet_conf.stat.exists
+
+- name: Upload kubeadm configuration
+  ansible.builtin.template:
+    src: kubeadm.yaml.j2
+    dest: /etc/kubernetes/kubeadm.yaml
+  when:
+    - not _stat_etc_kubernetes_kubelet_conf.stat.exists
+
+- name: Join cluster
+  ansible.builtin.shell: |
+    kubeadm join --config /etc/kubernetes/kubeadm.yaml \
+                 --ignore-preflight-errors=DirAvailable--etc-kubernetes-manifests
+  environment:
+    PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+  args:
+    creates: /etc/kubernetes/kubelet.conf
diff --git a/roles/kubernetes/tasks/main.yml b/roles/kubernetes/tasks/main.yml
new file mode 100644
index 0000000..383ce1f
--- /dev/null
+++ b/roles/kubernetes/tasks/main.yml
@@ -0,0 +1,124 @@
+# Copyright (c) 2022 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+- name: Add repository keys
+  ansible.builtin.copy:
+    src: apt-key.gpg
+    dest: /usr/share/keyrings/kubernetes-archive-keyring.gpg
+  when:
+    - kubernetes_repo_url == _kubernetes_upstream_apt_repository
+
+- name: Add repository
+  ansible.builtin.apt_repository:
+    repo: "deb {% if kubernetes_repo_url == _kubernetes_upstream_apt_repository %}[signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg]{% endif %} {{ kubernetes_repo_url }} kubernetes-xenial main"
+    state: present
+
+- name: Setup version pins
+  ansible.builtin.template:
+    src: apt-preferences.j2
+    dest: /etc/apt/preferences.d/kubernetes
+    mode: 0644
+
+- name: Install packages
+  ansible.builtin.apt:
+    name:
+      - "containerd"
+      - "kubeadm={{ kubernetes_version }}-00"
+      - "kubectl={{ kubernetes_version }}-00"
+      - "kubelet={{ kubernetes_version }}-00"
+    state: present
+
+- name: Enable kernel modules on-boot
+  ansible.builtin.template:
+    src: modules-load.conf.j2
+    dest: /etc/modules-load.d/k8s.conf
+
+- name: Enable kernel modules in runtime
+  community.general.modprobe:
+    name: "{{ item }}"
+    state: present
+  loop: "{{ kubernetes_kernel_modules }}"
+
+- name: Configure sysctl values
+  ansible.posix.sysctl:
+    name: "{{ item.name }}"
+    value: "{{ item.value }}"
+    state: present
+  loop: "{{ kubernetes_sysctls }}"
+
+- name: Check swap status
+  ansible.builtin.command: /sbin/swapon -s
+  changed_when: false
+  register: _swapon
+
+- name: Disable swap
+  ansible.builtin.command: /sbin/swapoff -a
+  ignore_errors: "{{ ansible_check_mode }}"
+  when:
+    - _swapon.stdout
+
+- name: Remove swapfile from /etc/fstab
+  ansible.posix.mount:
+    name: "{{ item }}"
+    fstype: swap
+    state: absent
+  with_items:
+    - swap
+    - none
+
+- name: Configure short hostname
+  ansible.builtin.hostname:
+    name: "{{ inventory_hostname_short }}"
+
+- name: Ensure hostname inside hosts file
+  ansible.builtin.lineinfile:
+    path: /etc/hosts
+    regexp: '^127\.0\.1\.1'
+    line: 127.0.1.1 {{ inventory_hostname }} {{ inventory_hostname_short }}
+
+- name: Setup control plane
+  when: inventory_hostname in groups[kubernetes_control_plane_group]
+  ansible.builtin.include_tasks: control-plane.yml
+
+- name: Setup nodes
+  when: inventory_hostname not in groups[kubernetes_control_plane_group]
+  ansible.builtin.include_tasks: nodes.yml
+
+- name: Add labels to control plane nodes
+  delegate_to: "{{ groups[kubernetes_control_plane_group][0] }}"
+  kubernetes.core.k8s:
+    state: patched
+    kind: Node
+    name: "{{ inventory_hostname_short }}"
+    definition:
+      metadata:
+        labels:
+          openstack-control-plane: enabled
+          openvswitch: enabled
+  when:
+    - inventory_hostname in groups['controllers']
+
+- name: Add labels to compute nodes
+  delegate_to: "{{ groups[kubernetes_control_plane_group][0] }}"
+  kubernetes.core.k8s:
+    state: patched
+    kind: Node
+    name: "{{ inventory_hostname_short }}"
+    definition:
+      metadata:
+        labels:
+          openstack-compute-node: enabled
+          openvswitch: enabled
+  when:
+    - inventory_hostname in groups['computes']
diff --git a/roles/kubernetes/tasks/nodes.yml b/roles/kubernetes/tasks/nodes.yml
new file mode 100644
index 0000000..bc11ac5
--- /dev/null
+++ b/roles/kubernetes/tasks/nodes.yml
@@ -0,0 +1,22 @@
+# Copyright (c) 2022 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+- name: Check if Kubernetes is already deployed
+  stat:
+    path: /etc/kubernetes/kubelet.conf
+  register: _kubernetes_kubelet
+
+- name: Join cluster
+  when: not _kubernetes_kubelet.stat.exists
+  ansible.builtin.include_tasks: join-cluster.yml