OKD bare metal deployment automation

- Modified the ocloud role to import the ocloud_infra_baremetal role for
  bare metal hosts
- Modified the ocloud_platform_okd role to support generation of
  bare metal agent-based installer ISO
- Added ocloud_infra_baremetal role to handle virtual media and power
  control of bare metal hosts
- Minor changes to the ocloud_platform_o2ims role
- Added debug task in ocloud_platform_okd role to print agent-config.yml
  and install-config.yml in verbose mode
- Updated README.md with information about bare metal deployment,
  Stolostron, and the oran-o2ims operator

Issue-ID: INF-476
Change-Id: I2f6c538a4d2b3d462e1acf928201b53ac6c61079
Signed-off-by: Chris Wheeler <chwheele@redhat.com>
diff --git a/.gitignore b/.gitignore
index 4fdfd7a..f544220 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@
 /okd/fetched
 
 # ignored anywhere
+collections/
 fetched/
 tmp/
 temp/
diff --git a/okd/README.md b/okd/README.md
index 53af7c7..deb777a 100644
--- a/okd/README.md
+++ b/okd/README.md
@@ -8,6 +8,7 @@
 
 ## Infrastructure
 - KVM/libvirtd virtual machine
+- Bare metal (x86_64 architecture); see [Requirements for installing OpenShift on a single node](https://docs.okd.io/4.16/installing/installing_sno/install-sno-preparing-to-install-sno.html#install-sno-requirements-for-installing-on-a-single-node_install-sno-preparing) for hardware minimum resource requirements
 
 # Prerequisites
 The following prerequisites must be installed on the host where the playbook will be run (localhost, by default):
@@ -21,6 +22,8 @@
 
 In the case of all-in-one topology clusters, all addresses must resolve to the machine network IP assigned to the node.
 
+The okd/playbooks/deploy_dns.yml playbook can be used to deploy dnsmasq if a DNS server needs to be configured.
+
 ## Ansible
 
 Install Ansible per [Installing Ansible on specific operating systems](https://docs.ansible.com/ansible/latest/installation_guide/installation_distros.html) documentation.
@@ -57,7 +60,7 @@
 
 #### Optional
 The following variables can be set to override deployment defaults:
-- ocloud_infra [default="vm"]: infrastructure target
+- ocloud_infra [default="vm"]: infrastructure target (supported values: "vm", "baremetal")
 - ocloud_platform [default="okd"]: platform target
 - ocloud_topology [default="aio"]: O-Cloud cluster topology
 - ocloud_cluster_name [default="ocloud-{{ ocloud_infra }}-{{ ocloud_platform }}-{{ ocloud_topology }}"]: O-Cloud cluster name
@@ -66,6 +69,9 @@
 
 ### Infrastructure / VM
 
+#### Required
+- role: cluster role of the node (supported values: "master")
+
 #### Optional
 The following variables can be set to override defaults for deploying to a VM infrastructure target:
 
@@ -76,6 +82,31 @@
 - ocloud_net_name [default="ocloud"]: virtual network name
 - ocloud_net_bridge [default="ocloud-br"]: virtual network bridge name
 - ocloud_net_mac_prefix [default="52:54:00:01:23"]: virtual network MAC prefix
+- ocloud_dns_servers: list of DNS resolvers to configure (see okd/playbooks/deploy_dns.yml if a DNS server needs to be deployed)
+- ocloud_ntp_servers: list of NTP servers to configure (see okd/playbooks/deploy_ntp.yml if an NTP server needs to be deployed)
+
+### Infrastructure / Bare Metal
+#### Required
+The following variables must be set for deploying to a bare metal infrastructure target (see okd/inventory/host_vars/master-0-baremetal/ for example):
+- bmc_address: hostname/IP of the node's out-of-band management interface (e.g. BMC, iDRAC, iLO)
+- bmc_user: username used to authenticate to the node's out-of-band management interface
+- bmc_password: password used to authenticate to the node's out-of-band management interface
+- installation_disk_path: disk device to which OKD will be installed (reference the `deviceName` subfield in [About root device hints](https://docs.openshift.com/container-platform/4.16/installing/installing_with_agent_based_installer/preparing-to-install-with-agent-based-installer.html#root-device-hints_preparing-to-install-with-agent-based-installer)
+- mac_addresses: dictionary of interface names and corresponding MAC addresses
+- network_config: node network configuration in [nmstate](https://nmstate.io/) format
+- ocloud_dns_servers: list of DNS resolvers to configure (see okd/playbooks/deploy_dns.yml if a DNS server needs to be deployed)
+- ocloud_net_cidr: must be set to the subnet corresponding to the node's IP assigned in 'network_config'
+- ocloud_ntp_servers: list of NTP servers to configure (see okd/playbooks/deploy_ntp.yml if an NTP server needs to be deployed)
+- role: cluster role of the node (supported values: "master")
+
+#### Inventory
+The following inventory variables must be defined for the 'http_store' host (see okd/inventory/host_vars/http_store/ for example
+x inventory and okd/playbooks/deploy_http_store.yml if an HTTP store needs to be deployed):
+- ansible_host: hostname/IP of the HTTP store that will serve the agent-based installer ISO image
+- http_store_dir: document root on the HTTP store where thet agent-based installer ISO image will be copied 
+- http_port: port on which the HTTP store listens
+
+#### Optional
 
 ### Platform / OKD
 
@@ -98,8 +129,9 @@
 ansible-playbook -i inventory playbooks/ocloud.yml
 ```
 
-This will deploy the O-Cloud up through the bootstrap phase.
-Continue to monitor the cluster deployment through completion per the Validation section below.
+This will deploy the O-Cloud up through the bootstrap and installation phases, as well as deploying
+[Stolostron](https://github.com/stolostron/stolostron) and the [oran-o2ims](https://github.com/openshift-kni/oran-o2ims)
+operator.
 
 # Validation
 
@@ -111,8 +143,9 @@
 export KUBECONFIG=/tmp/ansible.6u4ydu5n/cfg/auth/kubeconfig
 ```
 
-Monitor the progress of the installation by running the 'oc get nodes', 'oc get clusteroperators', and
-'oc get clusterversion' commands until all nodes are ready and all cluster operators are available, for example:
+Verify the installation completion by running the 'oc get nodes', 'oc get clusteroperators',
+and 'oc get clusterversion' commands and confirm all nodes are ready and all cluster operators
+are available, for example:
 
 ```
 $ oc get nodes
@@ -160,6 +193,185 @@
 version   4.14.0-0.okd-2024-01-26-175629   True        False         83m     Cluster version is 4.14.0-0.okd-2024-01-26-175629
 ```
 
+## Stolostron
+
+Verify the Stolostron deployment by running the 'oc get all -n open-cluster-management'
+and 'oc get MultiClusterHub -n open-cluster-management' commands, for example:
+
+```
+$ oc get all -n open-cluster-management
+Warning: apps.openshift.io/v1 DeploymentConfig is deprecated in v4.14+, unavailable in v4.10000+
+NAME                                                                  READY   STATUS             RESTARTS        AGE
+pod/cluster-permission-6964454c7b-pt2pq                               1/1     Running            0               3h31m
+pod/console-chart-console-v2-7f5554f4bf-mpxkq                         1/1     Running            0               3h31m
+pod/console-chart-console-v2-7f5554f4bf-mt949                         1/1     Running            0               3h31m
+pod/grc-policy-addon-controller-8599cc9c55-qxhtq                      1/1     Running            0               3h31m
+pod/grc-policy-addon-controller-8599cc9c55-r2rpq                      1/1     Running            0               3h31m
+pod/grc-policy-propagator-78f57b57d8-f79b9                            2/2     Running            0               3h31m
+pod/grc-policy-propagator-78f57b57d8-j49cw                            2/2     Running            0               3h31m
+pod/insights-client-5445dbd97f-lfl4w                                  1/1     Running            0               3h31m
+pod/insights-metrics-7b568c78fd-zdk7t                                 2/2     Running            0               3h31m
+pod/klusterlet-addon-controller-v2-7b9c995cc5-kmtkb                   1/1     Running            0               3h31m
+pod/klusterlet-addon-controller-v2-7b9c995cc5-v8b6z                   1/1     Running            0               3h31m
+pod/multicluster-integrations-78bbdf889f-7nj27                        3/3     Running            1 (3h32m ago)   3h34m
+pod/multicluster-observability-operator-86dc5477cb-sbw5f              1/1     Running            0               3h31m
+pod/multicluster-operators-application-7995b449fb-2gssq               3/3     Running            2 (3h32m ago)   3h34m
+pod/multicluster-operators-channel-597d9ddc46-kvwk2                   1/1     Running            1 (3h33m ago)   3h34m
+pod/multicluster-operators-hub-subscription-58c97bf6cc-7rxqk          1/1     Running            1 (3h32m ago)   3h34m
+pod/multicluster-operators-standalone-subscription-6b548d9bb8-r9rhf   1/1     Running            0               3h34m
+pod/multicluster-operators-subscription-report-7bb6dfdcb6-nblvh       1/1     Running            0               3h34m
+pod/multiclusterhub-operator-66d8788b98-98m7l                         1/1     Running            1 (3h34m ago)   3h34m
+pod/multiclusterhub-operator-66d8788b98-bg5s2                         1/1     Running            0               3h34m
+pod/search-api-6cbd6557c8-ld687                                       1/1     Running            0               3h30m
+pod/search-collector-f68dbfc6-m955j                                   1/1     Running            0               3h30m
+pod/search-indexer-54b95db649-6mlpc                                   1/1     Running            0               3h30m
+pod/search-postgres-bb88bc4d4-ngff9                                   1/1     Running            0               3h30m
+pod/search-v2-operator-controller-manager-79cfcfdf5b-d4r42            2/2     Running            0               3h31m
+pod/submariner-addon-77b9fb5df8-27rbx                                 0/1     CrashLoopBackOff   46 (9s ago)     3h31m
+pod/volsync-addon-controller-546985f674-lj56q                         1/1     Running            0               3h31m
+
+NAME                                                            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
+service/channels-apps-open-cluster-management-webhook-svc       ClusterIP   172.30.186.153   <none>        443/TCP    3h32m
+service/console-chart-console-v2                                ClusterIP   172.30.136.138   <none>        3000/TCP   3h31m
+service/governance-policy-compliance-history-api                ClusterIP   172.30.194.57    <none>        8384/TCP   3h31m
+service/grc-policy-propagator-metrics                           ClusterIP   172.30.220.46    <none>        8443/TCP   3h31m
+service/hub-subscription-metrics                                ClusterIP   172.30.238.159   <none>        8381/TCP   3h34m
+service/insights-client                                         ClusterIP   172.30.176.80    <none>        3030/TCP   3h31m
+service/insights-metrics                                        ClusterIP   172.30.187.226   <none>        8443/TCP   3h31m
+service/multicluster-observability-webhook-service              ClusterIP   172.30.160.21    <none>        443/TCP    3h31m
+service/multicluster-operators-application-svc                  ClusterIP   172.30.104.75    <none>        443/TCP    3h33m
+service/multicluster-operators-subscription                     ClusterIP   172.30.24.51     <none>        8443/TCP   3h33m
+service/multiclusterhub-operator-metrics                        ClusterIP   172.30.250.141   <none>        8383/TCP   3h34m
+service/multiclusterhub-operator-webhook                        ClusterIP   172.30.155.20    <none>        443/TCP    3h34m
+service/propagator-webhook-service                              ClusterIP   172.30.186.176   <none>        443/TCP    3h31m
+service/search-indexer                                          ClusterIP   172.30.223.23    <none>        3010/TCP   3h30m
+service/search-postgres                                         ClusterIP   172.30.68.78     <none>        5432/TCP   3h30m
+service/search-search-api                                       ClusterIP   172.30.231.222   <none>        4010/TCP   3h30m
+service/search-v2-operator-controller-manager-metrics-service   ClusterIP   172.30.106.43    <none>        8443/TCP   3h31m
+service/standalone-subscription-metrics                         ClusterIP   172.30.89.117    <none>        8389/TCP   3h34m
+
+NAME                                                             READY   UP-TO-DATE   AVAILABLE   AGE
+deployment.apps/cluster-permission                               1/1     1            1           3h31m
+deployment.apps/console-chart-console-v2                         2/2     2            2           3h31m
+deployment.apps/grc-policy-addon-controller                      2/2     2            2           3h31m
+deployment.apps/grc-policy-propagator                            2/2     2            2           3h31m
+deployment.apps/insights-client                                  1/1     1            1           3h31m
+deployment.apps/insights-metrics                                 1/1     1            1           3h31m
+deployment.apps/klusterlet-addon-controller-v2                   2/2     2            2           3h31m
+deployment.apps/multicluster-integrations                        1/1     1            1           3h34m
+deployment.apps/multicluster-observability-operator              1/1     1            1           3h31m
+deployment.apps/multicluster-operators-application               1/1     1            1           3h34m
+deployment.apps/multicluster-operators-channel                   1/1     1            1           3h34m
+deployment.apps/multicluster-operators-hub-subscription          1/1     1            1           3h34m
+deployment.apps/multicluster-operators-standalone-subscription   1/1     1            1           3h34m
+deployment.apps/multicluster-operators-subscription-report       1/1     1            1           3h34m
+deployment.apps/multiclusterhub-operator                         2/2     2            2           3h34m
+deployment.apps/search-api                                       1/1     1            1           3h30m
+deployment.apps/search-collector                                 1/1     1            1           3h30m
+deployment.apps/search-indexer                                   1/1     1            1           3h30m
+deployment.apps/search-postgres                                  1/1     1            1           3h30m
+deployment.apps/search-v2-operator-controller-manager            1/1     1            1           3h31m
+deployment.apps/submariner-addon                                 0/1     1            0           3h31m
+deployment.apps/volsync-addon-controller                         1/1     1            1           3h31m
+
+NAME                                                                        DESIRED   CURRENT   READY   AGE
+replicaset.apps/cluster-permission-6964454c7b                               1         1         1       3h31m
+replicaset.apps/console-chart-console-v2-7f5554f4bf                         2         2         2       3h31m
+replicaset.apps/grc-policy-addon-controller-8599cc9c55                      2         2         2       3h31m
+replicaset.apps/grc-policy-propagator-78f57b57d8                            2         2         2       3h31m
+replicaset.apps/insights-client-5445dbd97f                                  1         1         1       3h31m
+replicaset.apps/insights-metrics-7b568c78fd                                 1         1         1       3h31m
+replicaset.apps/klusterlet-addon-controller-v2-7b9c995cc5                   2         2         2       3h31m
+replicaset.apps/multicluster-integrations-78bbdf889f                        1         1         1       3h34m
+replicaset.apps/multicluster-observability-operator-86dc5477cb              1         1         1       3h31m
+replicaset.apps/multicluster-operators-application-7995b449fb               1         1         1       3h34m
+replicaset.apps/multicluster-operators-channel-597d9ddc46                   1         1         1       3h34m
+replicaset.apps/multicluster-operators-hub-subscription-58c97bf6cc          1         1         1       3h34m
+replicaset.apps/multicluster-operators-standalone-subscription-6b548d9bb8   1         1         1       3h34m
+replicaset.apps/multicluster-operators-subscription-report-7bb6dfdcb6       1         1         1       3h34m
+replicaset.apps/multiclusterhub-operator-66d8788b98                         2         2         2       3h34m
+replicaset.apps/search-api-6cbd6557c8                                       1         1         1       3h30m
+replicaset.apps/search-collector-f68dbfc6                                   1         1         1       3h30m
+replicaset.apps/search-indexer-54b95db649                                   1         1         1       3h30m
+replicaset.apps/search-postgres-bb88bc4d4                                   1         1         1       3h30m
+replicaset.apps/search-v2-operator-controller-manager-79cfcfdf5b            1         1         1       3h31m
+replicaset.apps/submariner-addon-77b9fb5df8                                 1         1         0       3h31m
+replicaset.apps/volsync-addon-controller-546985f674                         1         1         1       3h31m
+
+NAME                                  HOST/PORT                                                                      PATH   SERVICES            PORT    TERMINATION   WILDCARD
+route.route.openshift.io/search-api   search-api-open-cluster-management.apps.ocloud-baremetal-okd-aio.example.com          search-search-api   <all>   reencrypt     None
+
+$ oc get MultiClusterHub -n open-cluster-management
+NAME              STATUS    AGE
+multiclusterhub   Running   3h34m
+```
+
+## oran-o2ims
+
+Verify the oran-o2ims operator deployment by running the 'oc get all -n oran-o2ims'
+command, for example:
+
+```
+$ oc get all -n oran-o2ims
+Warning: apps.openshift.io/v1 DeploymentConfig is deprecated in v4.14+, unavailable in v4.10000+
+NAME                                                READY   STATUS    RESTARTS         AGE
+pod/deployment-manager-server-658bc7cbdf-qxw4s      2/2     Running   0                3h34m
+pod/metadata-server-6ffb478f87-fk7hc                2/2     Running   0                3h34m
+pod/oran-o2ims-controller-manager-bf459859f-lrbks   2/2     Running   32 (7m12s ago)   3h34m
+pod/postgres-server-6f658dbfb9-gpp4b                1/1     Running   0                3h34m
+pod/resource-server-77ccdfdf6d-2fgn8                2/2     Running   0                3h34m
+
+NAME                                                    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
+service/alarms-server                                   ClusterIP   172.30.191.244   <none>        8000/TCP   3h34m
+service/deployment-manager-server                       ClusterIP   172.30.71.250    <none>        8000/TCP   3h34m
+service/metadata-server                                 ClusterIP   172.30.186.182   <none>        8000/TCP   3h34m
+service/oran-o2ims-controller-manager-metrics-service   ClusterIP   172.30.238.160   <none>        8443/TCP   3h34m
+service/postgres-server                                 ClusterIP   172.30.134.42    <none>        5432/TCP   3h34m
+service/resource-server                                 ClusterIP   172.30.151.16    <none>        8000/TCP   3h34m
+
+NAME                                            READY   UP-TO-DATE   AVAILABLE   AGE
+deployment.apps/deployment-manager-server       1/1     1            1           3h34m
+deployment.apps/metadata-server                 1/1     1            1           3h34m
+deployment.apps/oran-o2ims-controller-manager   1/1     1            1           3h34m
+deployment.apps/postgres-server                 1/1     1            1           3h34m
+deployment.apps/resource-server                 1/1     1            1           3h34m
+
+NAME                                                      DESIRED   CURRENT   READY   AGE
+replicaset.apps/deployment-manager-server-658bc7cbdf      1         1         1       3h34m
+replicaset.apps/metadata-server-6ffb478f87                1         1         1       3h34m
+replicaset.apps/oran-o2ims-controller-manager-bf459859f   1         1         1       3h34m
+replicaset.apps/postgres-server-6f658dbfb9                1         1         1       3h34m
+replicaset.apps/resource-server-77ccdfdf6d                1         1         1       3h34m
+
+NAME                                 HOST/PORT                                         PATH                                                   SERVICES                    PORT   TERMINATION          WILDCARD
+route.route.openshift.io/api-4h7lp   o2ims.apps.ocloud-baremetal-okd-aio.example.com   /o2ims-infrastructureInventory/v1/resourceTypes        resource-server             api    reencrypt/Redirect   None
+route.route.openshift.io/api-5x9tj   o2ims.apps.ocloud-baremetal-okd-aio.example.com   /o2ims-infrastructureMonitoring                        alarms-server               api    reencrypt/Redirect   None
+route.route.openshift.io/api-cg6wl   o2ims.apps.ocloud-baremetal-okd-aio.example.com   /                                                      metadata-server             api    reencrypt/Redirect   None
+route.route.openshift.io/api-dshdl   o2ims.apps.ocloud-baremetal-okd-aio.example.com   /o2ims-infrastructureInventory/v1/deploymentManagers   deployment-manager-server   api    reencrypt/Redirect   None
+route.route.openshift.io/api-jkcz4   o2ims.apps.ocloud-baremetal-okd-aio.example.com   /o2ims-infrastructureInventory/v1/resourcePools        resource-server             api    reencrypt/Redirect   None
+route.route.openshift.io/api-njwp2   o2ims.apps.ocloud-baremetal-okd-aio.example.com   /o2ims-infrastructureInventory/v1/subscriptions        resource-server             api    reencrypt/Redirect   None
+```
+
+Additionally, a service account can be created and a token generated for testing the O2 API endpoints:
+
+```
+$ oc apply -f https://raw.githubusercontent.com/openshift-kni/oran-o2ims/refs/heads/osc-k-release/config/testing/client-service-account-rbac.yaml
+serviceaccount/test-client created
+clusterrole.rbac.authorization.k8s.io/oran-o2ims-test-client-role created
+clusterrolebinding.rbac.authorization.k8s.io/oran-o2ims-test-client-binding created
+
+$ TOKEN=`oc create token -n oran-o2ims test-client`
+
+$ curl -H "Authorization: Bearer $TOKEN" -k https://o2ims.apps.ocloud-baremetal-okd-aio.example.com/o2ims-infrastructureInventory/v1
+{
+  "oCloudId": "c064e7b9-ee17-44eb-b689-90bb4d26274d",
+  "globalCloudId": "3ff52a9a-e4f3-52cc-8823-044b9af8558b",
+  "name": "OpenShift O-Cloud",
+  "description": "OpenShift O-Cloud",
+  "serviceUri": "https://o2ims.apps.ocloud-baremetal-okd-aio.example.com",
+  "extensions": {
+  }
+```
 # Troubleshooting
 
 ## OKD
diff --git a/okd/inventory/host_vars/http_store/vars.yml b/okd/inventory/host_vars/http_store/vars.yml
new file mode 100644
index 0000000..79eb11a
--- /dev/null
+++ b/okd/inventory/host_vars/http_store/vars.yml
@@ -0,0 +1,4 @@
+ansible_host: 192.168.89.10
+http_store_dir: /opt/http_store/data
+http_port: 80
+ansible_connection: local
diff --git a/okd/inventory/host_vars/master-0-baremetal/vars.yml b/okd/inventory/host_vars/master-0-baremetal/vars.yml
new file mode 100644
index 0000000..b38802b
--- /dev/null
+++ b/okd/inventory/host_vars/master-0-baremetal/vars.yml
@@ -0,0 +1,26 @@
+---
+ocloud_infra: baremetal
+ocloud_platform: okd
+ocloud_topology: aio
+bmc_address: "192.168.12.34"
+bmc_user: admin
+role: master
+installation_disk_path: "/dev/disk/by-path/pci-0000:c3:00.0-nvme-1"
+mac_addresses:
+  ens1f0: "12:34:56:78:9A:BC"
+network_config:
+  interfaces:
+    - name: ens1f0
+      type: ethernet
+      state: up
+      ipv4:
+        enabled: true
+        dhcp: false
+        address:
+        - ip: "192.168.45.67"
+          prefix-length: "24"
+  routes:
+    config:
+    - destination: 0.0.0.0/0
+      next-hop-address: "192.168.45.1"
+      next-hop-interface: "ens1f0"
diff --git a/okd/inventory/host_vars/master-0-baremetal/vault.yml b/okd/inventory/host_vars/master-0-baremetal/vault.yml
new file mode 100644
index 0000000..ac228b2
--- /dev/null
+++ b/okd/inventory/host_vars/master-0-baremetal/vault.yml
@@ -0,0 +1,3 @@
+---
+# Encrypt with `ansible-vault encrypt vault.yml` before adding secrets
+bmc_password: ~
diff --git a/okd/inventory/host_vars/master-0/vars.yml b/okd/inventory/host_vars/master-0-vm/vars.yml
similarity index 82%
rename from okd/inventory/host_vars/master-0/vars.yml
rename to okd/inventory/host_vars/master-0-vm/vars.yml
index db03d06..917fee1 100644
--- a/okd/inventory/host_vars/master-0/vars.yml
+++ b/okd/inventory/host_vars/master-0-vm/vars.yml
@@ -2,3 +2,4 @@
 ocloud_infra: vm
 ocloud_platform: okd
 ocloud_topology: aio
+role: master
diff --git a/okd/inventory/hosts.yml b/okd/inventory/hosts.yml
index 8d36d51..41c456e 100644
--- a/okd/inventory/hosts.yml
+++ b/okd/inventory/hosts.yml
@@ -8,6 +8,10 @@
     localhost:
       ansible_connection: local
 
+http_store:
+  hosts:
+    http_store:
+
 ocloud:
   hosts:
-    master-0:
+    master-0-vm:
diff --git a/okd/roles/ocloud/defaults/main.yml b/okd/roles/ocloud/defaults/main.yml
index fa0e81f..f3de43e 100644
--- a/okd/roles/ocloud/defaults/main.yml
+++ b/okd/roles/ocloud/defaults/main.yml
@@ -5,3 +5,9 @@
 ocloud_cluster_name: "ocloud-{{ ocloud_infra }}-{{ ocloud_platform }}-{{ ocloud_topology }}"
 ocloud_domain_name: "example.com"
 ocloud_net_cidr: "192.168.123.0/24"
+ocloud_cluster_net_cidr: "10.128.0.0/14"
+ocloud_cluster_net_hostprefix: "23"
+ocloud_service_net_cidr: "172.30.0.0/16"
+ocloud_network_type: "OVNKubernetes"
+ocloud_ntp_servers: []
+ocloud_dns_servers: []
diff --git a/okd/roles/ocloud/tasks/main.yml b/okd/roles/ocloud/tasks/main.yml
index c1a565e..3846997 100644
--- a/okd/roles/ocloud/tasks/main.yml
+++ b/okd/roles/ocloud/tasks/main.yml
@@ -11,6 +11,12 @@
   delegate_to: "{{ groups['kvm'][0] }}"
   when: ocloud_infra == "vm"
 
+- name: Include infra role - {{ ocloud_infra }}
+  ansible.builtin.import_role:
+    name: "ocloud_infra_baremetal"
+  delegate_to: "{{ groups['deployer'][0] }}"
+  when: ocloud_infra == "baremetal"
+
 - meta: flush_handlers
 
 - name: Include platform role - Stolostron
diff --git a/okd/roles/ocloud_infra_baremetal/tasks/main.yml b/okd/roles/ocloud_infra_baremetal/tasks/main.yml
new file mode 100644
index 0000000..6027a31
--- /dev/null
+++ b/okd/roles/ocloud_infra_baremetal/tasks/main.yml
@@ -0,0 +1,92 @@
+---
+- name: Get manager inventory information
+  community.general.redfish_info:
+    category: Manager
+    command: GetManagerInventory
+    baseuri: "{{ bmc_address }}"
+    username: "{{ bmc_user }}"
+    password: "{{ bmc_password }}"
+  register: redfish_manager_info
+
+- set_fact:
+    ocloud_infra_baremetal_manager_id: "{{ redfish_manager_info['redfish_facts']['manager']['entries'][0][1]['Id'] }}"
+    ocloud_infra_baremetal_image_url: "http://{{ hostvars['http_store']['ansible_host'] }}:{{ hostvars['http_store']['http_port'] }}/{{ ocloud_infra_baremetal_image }}"
+
+- name: Download platform boot image
+  ansible.builtin.fetch:
+    src: "{{ ocloud_platform_image }}"
+    dest: "/tmp/{{ ocloud_infra_baremetal_image }}"
+    flat: true
+  run_once: true
+
+- name: Copy platform boot image to HTTP store
+  ansible.builtin.copy:
+    src: "/tmp/{{ ocloud_infra_baremetal_image }}"
+    dest: "{{ hostvars['http_store']['http_store_dir'] }}/{{ ocloud_infra_baremetal_image }}"
+  delegate_to: http_store
+  run_once: true
+  become: true
+
+- name: Power off
+  community.general.redfish_command:
+    category: Systems
+    command: PowerForceOff
+    baseuri: "{{ bmc_address }}"
+    username: "{{ bmc_user }}"
+    password: "{{ bmc_password }}"
+
+- name: Get virtual media information
+  community.general.redfish_info:
+    category: Manager
+    command: GetVirtualMedia
+    baseuri: "{{ bmc_address }}"
+    username: "{{ bmc_user }}"
+    password: "{{ bmc_password }}"
+  register: virtual_media_info
+
+- name: Eject virtual media
+  community.general.redfish_command:
+    category: Manager
+    command: VirtualMediaEject
+    virtual_media:
+      image_url: "{{ item[1][0]['Image'] }}"
+    baseuri: "{{ bmc_address }}"
+    username: "{{ bmc_user }}"
+    password: "{{ bmc_password }}"
+    resource_id: "{{ ocloud_infra_baremetal_manager_id }}"
+  when: item[1][0]['ConnectedVia'] != "NotConnected" and item[1][0]['Image'] | length > 0
+  loop: "{{ virtual_media_info['redfish_facts']['virtual_media']['entries'] }}"
+
+- name: Configure one-time boot from virtual media
+  community.general.redfish_command:
+    category: Systems
+    command: SetOneTimeBoot
+    bootdevice: Cd
+    baseuri: "{{ bmc_address }}"
+    username: "{{ bmc_user }}"
+    password: "{{ bmc_password }}"
+    resource_id: "{{ ocloud_infra_baremetal_manager_id }}"
+
+- name: Insert virtual media
+  community.general.redfish_command:
+    category: Manager
+    command: VirtualMediaInsert
+    baseuri: "{{ bmc_address }}"
+    username: "{{ bmc_user }}"
+    password: "{{ bmc_password }}"
+    virtual_media:
+      image_url: "{{ ocloud_infra_baremetal_image_url }}"
+      media_types:
+        - CD
+        - DVD
+    resource_id: "{{ ocloud_infra_baremetal_manager_id }}"
+    timeout: 120
+
+- name: Power on
+  community.general.redfish_command:
+    category: Systems
+    command: PowerOn
+    baseuri: "{{ bmc_address }}"
+    username: "{{ bmc_user }}"
+    password: "{{ bmc_password }}"
+  notify: monitor_platform_deployment
diff --git a/okd/roles/ocloud_infra_baremetal/vars/main.yml b/okd/roles/ocloud_infra_baremetal/vars/main.yml
new file mode 100644
index 0000000..0d45992
--- /dev/null
+++ b/okd/roles/ocloud_infra_baremetal/vars/main.yml
@@ -0,0 +1,2 @@
+---
+ocloud_infra_baremetal_image: "{{ ocloud_cluster_name }}.iso"
diff --git a/okd/roles/ocloud_platform_o2ims/defaults/main.yml b/okd/roles/ocloud_platform_o2ims/defaults/main.yml
index 1b1d126..546295d 100644
--- a/okd/roles/ocloud_platform_o2ims/defaults/main.yml
+++ b/okd/roles/ocloud_platform_o2ims/defaults/main.yml
@@ -2,5 +2,5 @@
 ocloud_platform_o2ims_golang_url: "https://go.dev/dl/go1.23.2.linux-amd64.tar.gz"
 ocloud_platform_o2ims_kubeconfig: "{{ ocloud_platform_okd_kubeconfig }}"
 ocloud_platform_o2ims_repo_url: "https://github.com/openshift-kni/oran-o2ims.git"
-ocloud_platform_o2ims_repo_version: "main"
+ocloud_platform_o2ims_repo_version: "osc-k-release"
 ocloud_platform_o2ims_service: "o2ims"
diff --git a/okd/roles/ocloud_platform_o2ims/templates/v1alpha1_inventory.yaml.j2 b/okd/roles/ocloud_platform_o2ims/templates/v1alpha1_inventory.yaml.j2
index 284dbf5..ff87c2b 100644
--- a/okd/roles/ocloud_platform_o2ims/templates/v1alpha1_inventory.yaml.j2
+++ b/okd/roles/ocloud_platform_o2ims/templates/v1alpha1_inventory.yaml.j2
@@ -12,7 +12,7 @@
 spec:
   cloudID: {{ ocloud_platform_o2ims_ingress_host | to_uuid }}
   ingressHost: {{ ocloud_platform_o2ims_ingress_host }}
-  alarmSubscriptionServerConfig:
+  alarmServerConfig:
     enabled: false
   metadataServerConfig:
     enabled: true
diff --git a/okd/roles/ocloud_platform_okd/tasks/install.yml b/okd/roles/ocloud_platform_okd/tasks/install.yml
index 6896b88..5abb1db 100644
--- a/okd/roles/ocloud_platform_okd/tasks/install.yml
+++ b/okd/roles/ocloud_platform_okd/tasks/install.yml
@@ -37,6 +37,13 @@
     - agent-config.yaml
     - install-config.yaml
 
+- ansible.builtin.debug:
+    verbosity: 2
+    msg: "{{ lookup('file', [ocloud_platform_okd_staging_dir['path'], 'cfg', item] | path_join) }}"
+  loop:
+    - agent-config.yaml
+    - install-config.yaml
+
 - name: Generate OKD agent-based installer image
   ansible.builtin.shell:
     cmd: "openshift-install agent create image --dir {{ ocloud_platform_okd_staging_dir['path'] }}/cfg"
diff --git a/okd/roles/ocloud_platform_okd/templates/agent-config.yaml.j2 b/okd/roles/ocloud_platform_okd/templates/agent-config.yaml.j2
index db24c18..b8c30b5 100644
--- a/okd/roles/ocloud_platform_okd/templates/agent-config.yaml.j2
+++ b/okd/roles/ocloud_platform_okd/templates/agent-config.yaml.j2
@@ -1,5 +1,42 @@
+#jinja2:trim_blocks: True, lstrip_blocks: True
 apiVersion: v1beta1
 kind: AgentConfig
 metadata:
   name: {{ ocloud_cluster_name }}
+{% if hostvars[groups['ocloud'][0]]['ocloud_infra'] == "baremetal" %}
+rendezvousIP: {{ hostvars[groups['ocloud'][0]]['network_config']['interfaces'][0]['ipv4']['address'][0]['ip'] }}
+{% else %}
 rendezvousIP: {{ ocloud_net_cidr | ansible.utils.ipmath(11) }}
+{% endif %}
+{% if ocloud_ntp_servers is defined and ocloud_ntp_servers | length > 0 %}
+additionalNTPSources:
+{% for ntp_server in ocloud_ntp_servers %}
+- {{ ntp_server }}
+{% endfor %}
+{% endif %}
+{% if groups['ocloud'] | map('extract', hostvars, 'ocloud_infra') | select('equalto', 'baremetal') | length > 0 %}
+hosts:
+{% endif %}
+{% for hostname in groups['ocloud'] %}
+  {% if hostvars[hostname]['ocloud_infra'] == "baremetal" %}
+  - role: {{ hostvars[hostname]['role'] }}
+    hostname: {{ hostname }}
+    interfaces:
+    - name: {{ hostvars[hostname]['network_config']['interfaces'][0]['name'] }}
+      macAddress: {{ hostvars[hostname]['mac_addresses'][hostvars[hostname]['network_config']['interfaces'][0]['name']] }}
+    networkConfig:
+      {{ hostvars[hostname]['network_config'] | to_nice_yaml(indent=2) | trim | indent(6) }}
+      dns-resolver:
+        config:
+          search:
+          - {{ ocloud_domain_name }}
+          server:
+          {% for dns_server in ocloud_dns_servers %}
+          - {{ dns_server }}
+          {% endfor %}
+    {% if hostvars[hostname]['installation_disk_path'] is defined %}
+    rootDeviceHints:
+      deviceName: {{ hostvars[hostname]['installation_disk_path'] }}
+    {% endif %}
+  {% endif %}
+{% endfor %}
diff --git a/okd/roles/ocloud_platform_okd/templates/install-config.yaml.j2 b/okd/roles/ocloud_platform_okd/templates/install-config.yaml.j2
index 781122e..1a7eece 100644
--- a/okd/roles/ocloud_platform_okd/templates/install-config.yaml.j2
+++ b/okd/roles/ocloud_platform_okd/templates/install-config.yaml.j2
@@ -1,26 +1,48 @@
+#jinja2:trim_blocks: True, lstrip_blocks: True
 apiVersion: v1
 baseDomain: {{ ocloud_domain_name }}
-compute:
-- name: worker
-  replicas: 0
 controlPlane:
   name: master
-  replicas: 1
+  replicas: {{ groups['ocloud'] | map('extract', hostvars, 'role') | select('equalto', 'master') | length }}
+compute:
+  - name: worker
+    replicas: {{ groups['ocloud'] | map('extract', hostvars, 'role') | select('equalto', 'worker') | length }}
 metadata:
   name: {{ ocloud_cluster_name }}
 networking:
   clusterNetwork:
-  - cidr: 10.128.0.0/14
-    hostPrefix: 23
-  networkType: OVNKubernetes
+  - cidr: {{ ocloud_cluster_net_cidr }}
+    hostPrefix: {{ ocloud_cluster_net_hostprefix }}
+  networkType: {{ ocloud_network_type }}
   machineNetwork:
   - cidr: {{ ocloud_net_cidr }}
   serviceNetwork:
-  - 172.30.0.0/16
+  - {{ ocloud_service_net_cidr}}
 platform:
+  {% if groups['ocloud'] | length == 1 %}
   none: {}
-bootstrapInPlace:
-  installationDisk: /dev/vda
-fips: false
+  {% else %}
+  baremetal:
+    apiVips:
+      {% for vip in ocloud_platform_okd_api_vips %}
+      - {{ vip }}
+      {% endfor %}
+    ingressVips:
+      {% for vip in ocloud_platform_okd_ingress_vips %}
+      - {{ vip }}
+      {% endfor %}
+  {% endif %}
 pullSecret: '{{ ocloud_platform_okd_pull_secret }}'
 sshKey: '{{ ocloud_platform_okd_ssh_pubkey }}'
+{% if (http_proxy is defined and http_proxy != "") or (https_proxy is defined and https_proxy != "") or (no_proxy is defined and no_proxy != "") %}
+proxy:
+    {% if http_proxy is defined and http_proxy != "" %}
+    httpProxy: {{ http_proxy }}
+    {% endif %}
+    {% if https_proxy is defined and https_proxy != "" %}
+    httpsProxy: {{ https_proxy }}
+    {% endif %}
+    {% if no_proxy is defined and no_proxy != "" %}
+    noProxy: {{ no_proxy }}
+    {% endif %}
+{% endif %}