:_mod-docs-content-type: PROCEDURE
[id="adopting-compute-services-to-the-data-plane_{context}"]

= Adopting Compute services to the {rhos_acro} data plane

[role="_abstract"]
Adopt your Compute (nova) services to the {rhos_long} data plane.

.Prerequisites

* You have stopped the remaining control plane nodes, repositories, and packages on the {compute_service_first_ref} hosts. For more information, see xref:stopping-infrastructure-management-and-compute-services_{context}[Stopping infrastructure management and Compute services].
* You have configured the Ceph back end for the `NovaLibvirt` service. For more information, see xref:configuring-a-ceph-backend_migrating-databases[Configuring a Ceph back end].
* You have configured IP Address Management (IPAM):
+
----
$ oc apply -f - <<EOF
apiVersion: network.openstack.org/v1beta1
kind: NetConfig
metadata:
  name: netconfig
spec:
  networks:
  - name: ctlplane
    dnsDomain: ctlplane.example.com
    subnets:
    - name: subnet1
      allocationRanges:
      - end: 192.168.122.120
        start: 192.168.122.100
      - end: 192.168.122.200
        start: 192.168.122.150
      cidr: 192.168.122.0/24
      gateway: 192.168.122.1
  - name: internalapi
    dnsDomain: internalapi.example.com
    subnets:
    - name: subnet1
      allocationRanges:
      - end: 172.17.0.250
        start: 172.17.0.100
      cidr: 172.17.0.0/24
      vlan: 20
  - name: External
    dnsDomain: external.example.com
    subnets:
    - name: subnet1
      allocationRanges:
      - end: 10.0.0.250
        start: 10.0.0.100
      cidr: 10.0.0.0/24
      gateway: 10.0.0.1
  - name: storage
    dnsDomain: storage.example.com
    subnets:
    - name: subnet1
      allocationRanges:
      - end: 172.18.0.250
        start: 172.18.0.100
      cidr: 172.18.0.0/24
      vlan: 21
  - name: storagemgmt
    dnsDomain: storagemgmt.example.com
    subnets:
    - name: subnet1
      allocationRanges:
      - end: 172.20.0.250
        start: 172.20.0.100
      cidr: 172.20.0.0/24
      vlan: 23
  - name: tenant
    dnsDomain: tenant.example.com
    subnets:
    - name: subnet1
      allocationRanges:
      - end: 172.19.0.250
        start: 172.19.0.100
      cidr: 172.19.0.0/24
      vlan: 22
EOF
----
+
* If `neutron-sriov-nic-agent` is running on your {compute_service} nodes, ensure that the physical device mappings match the values that are defined in the `OpenStackDataPlaneNodeSet` custom resource (CR). For more information, see xref:pulling-configuration-from-tripleo-deployment_adopt-control-plane[Pulling the configuration from a {OpenStackPreviousInstaller} deployment].

* You have defined the shell variables to run the script that runs the upgrade:
+
[subs="+quotes"]
----
$ CEPH_FSID=$(oc get secret ceph-conf-files -o json | jq -r '.data."ceph.conf"' | base64 -d | grep fsid | sed -e 's/fsid = //')

$ alias openstack="oc exec -t openstackclient -- openstack"

$ *DEFAULT_CELL_NAME="cell3"*
$ RENAMED_CELLS="cell1 cell2 $DEFAULT_CELL_NAME"

$ declare -A COMPUTES_CELL1
$ *export COMPUTES_CELL1=(*
> *["standalone.localdomain"]="192.168.122.100"*
> # *<compute1>*
> # *<compute2>*
> # *<compute3>*
>)
$ declare -A COMPUTES_CELL2
$ export COMPUTES_CELL2=(
> # *<compute1>*
>)
$ declare -A COMPUTES_CELL3
$*export COMPUTES_CELL3=(*
> # *<compute1>*
> # *<compute2>*
>)

$ declare -A COMPUTES_API_CELL1
$*export COMPUTES_API_CELL1=(*
> ["standalone.localdomain"]="172.17.0.100"
> ["standalone2.localdomain"]="172.17.0.101"
>)

$ NODESETS=""
$ for CELL in $(echo $RENAMED_CELLS); do
> ref="COMPUTES_$(echo ${CELL}|tr '[:lower:]' '[:upper:]')"
> eval names=\${!${ref}[@]}
> [ -z "$names" ] && continue
> NODESETS="'openstack-${CELL}', $NODESETS"
>done
$ NODESETS="[${NODESETS%,*}]"
----
+
** `DEFAULT_CELL_NAME="cell3"` defines the source cloud `default` cell that acquires a new `DEFAULT_CELL_NAME` on the destination cloud after adoption.
In a multi-cell adoption scenario, you can retain the original name, `default`, or create a new cell default name by providing the incremented index of the last cell in the source cloud. For example, if the incremented index of the last cell is `cell5`, the new cell default name is `cell6`.
** `export COMPUTES_CELL1=` For each cell, update the `<["standalone.localdomain"]="x.x.x.x">` value and the `COMPUTES_CELL<X>` value with the names and IP addresses of the {compute_service} nodes that are connected to the `ctlplane` and `internalapi` networks. Do not specify a real FQDN defined for each network. Always use the same hostname for each connected network of a Compute node. Provide the IP addresses and the names of the hosts on the remaining networks of the source cloud as needed, or you can manually adjust the files that you generate in step 9 of this procedure.
** `<compute1>`, `<compute2>`, and `<compute3>` specifies the names of your {compute_service} nodes for each cell. Assign all {compute_service} nodes from the source cloud `cell1` cell into `COMPUTES_CELL1`, and so on.
** `export COMPUTES_CELL<X>=(` specifies all {compute_service} nodes that you assign from the source cloud `default` cell into `COMPUTES_CELL<X>` and `COMPUTES_API_CELL<X>`, where `<X>` is the `DEFAULT_CELL_NAME` environment variable value. In this example, the `DEFAULT_CELL_NAME` environment variable value equals `cell3`.
** `export COMPUTES_API_CELL1=(` For each cell, update the `<["standalone.localdomain"]="192.168.122.100">` value and the `COMPUTES_API_CELL<X>` value with the names and IP addresses of the {compute_service} nodes that are connected to the `ctlplane` and `internalapi` networks. `["standalone.localdomain"]="192.168.122.100"` defines the custom DNS domain in the FQDN value of the nodes. This value is used in the data plane node set `spec.nodes.<NODE NAME>.hostName`. Do not specify a real FQDN defined for each network. Use the same hostname for each of its connected networks. Provide the IP addresses and the names of the hosts on the remaining networks of the source cloud as needed, or you can manually adjust the files that you generate in step 9 of this procedure.
** `NODESETS="'openstack-${CELL}', $NODESETS"` specifies the cells that contain Compute nodes. Cells that do not contain Compute nodes are omitted from this template because no node sets are created for the cells.
+
[NOTE]
====
If you deployed the source cloud with a `default` cell, and want to rename it during adoption, define the new name that you want to use, as shown in the following example:
----
$ DEFAULT_CELL_NAME="cell1"
$ RENAMED_CELLS="cell1"
----
====

[NOTE]
====
Do not set a value for the `CEPH_FSID` parameter if the local storage back end is configured by the {compute_service} for libvirt. The storage back end must match the source cloud storage back end. You cannot change the storage back end during adoption.
====

.Procedure

ifeval::["{build}" != "downstream"]
. Create a https://kubernetes.io/docs/concepts/configuration/secret/#ssh-authentication-secrets[ssh authentication secret] for the data plane nodes:
//kgilliga:I need to check if we will document this in Red Hat docs.
endif::[]
ifeval::["{build}" != "upstream"]
. Create an SSH authentication secret for the data plane nodes:
endif::[]
+
[subs=+quotes]
----
$ oc apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
    name: dataplane-adoption-secret
data:
    ssh-privatekey: |
ifeval::["{build}" != "downstream"]
$(cat ~/install_yamls/out/edpm/ansibleee-ssh-key-id_rsa | base64 | sed \'s/^/        /')
endif::[]
ifeval::["{build}" == "downstream"]
$(cat <path_to_SSH_key> | base64 | sed \'s/^/        /')
endif::[]
EOF
----
+
ifeval::["{build}" == "downstream"]
* Replace `<path_to_SSH_key>` with the path to your SSH key.
endif::[]

. Generate an ssh key-pair `nova-migration-ssh-key` secret:
+
----
$ cd "$(mktemp -d)"
$ ssh-keygen -f ./id -t ecdsa-sha2-nistp521 -N ''
$ oc get secret nova-migration-ssh-key || oc create secret generic nova-migration-ssh-key \
  --from-file=ssh-privatekey=id \
  --from-file=ssh-publickey=id.pub \
  --type kubernetes.io/ssh-auth
$ rm -f id*
$ cd -
----

. If TLS Everywhere is enabled, set `LIBVIRT_PASSWORD` to match the existing {OpenStackShort} deployment password:
+
----
declare -A TRIPLEO_PASSWORDS
TRIPLEO_PASSWORDS[default]="$HOME/overcloud-passwords.yaml"
LIBVIRT_PASSWORD=$(cat ${TRIPLEO_PASSWORDS[default]} | grep ' LibvirtTLSPassword:' | awk -F ': ' '{ print $2; }')
LIBVIRT_PASSWORD_BASE64=$(echo -n "$LIBVIRT_PASSWORD" | base64)
----

.. Create libvirt-secret when TLS-e is enabled:
+
----
$ oc apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: libvirt-secret
type: Opaque
data:
  LibvirtPassword: ${LIBVIRT_PASSWORD_BASE64}
EOF
----

. Create a configuration map to use for all cells to configure a local storage back end for libvirt:
+
----
$ oc apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: nova-cells-global-config
data:
  99-nova-compute-cells-workarounds.conf: |
    [workarounds]
    disable_compute_service_check_for_ffu=true
EOF
----
+
* `data` provides the configuration files for all the cells.
* `99-nova-compute-cells-workarounds.conf: |` specifies the index of the `<*.conf>` files. There is a requirement to index the `<*.conf>` files from '03' to '99', based on precedence. A `<99-*.conf>` file takes the highest precedence, while indexes below '03' are reserved for internal use.
+
[NOTE]
If you adopt a live cloud, you might be required to carry over additional configurations for the default `nova` data plane services that are stored in the cell1 default `nova-extra-config` configuration map. Do not delete or overwrite the existing configuration in the `cell1` default `nova-extra-config` configuration map that is assigned to `nova`. Overwriting the configuration can break the data place services that rely on specific contents of the `nova-extra-config` configuration map.

. Configure a {Ceph} back end for libvirt:
+
----
$ oc apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: nova-cells-global-config
data:
  99-nova-compute-cells-workarounds.conf: |
    [workarounds]
    disable_compute_service_check_for_ffu=true
  03-ceph-nova.conf: |
    [libvirt]
    images_type=rbd
    images_rbd_pool=vms
    images_rbd_ceph_conf=/etc/ceph/ceph.conf
    images_rbd_glance_store_name=default_backend
    images_rbd_glance_copy_poll_interval=15
    images_rbd_glance_copy_timeout=600
    rbd_user=openstack
    rbd_secret_uuid=$CEPH_FSID
EOF
----
+
[NOTE]
For {Ceph} environments with multi-cell configurations, you must name configuration maps and {rhos_prev_long} data plane services similar to the following examples: `nova-custom-ceph-cellX` and `nova-compute-extraconfig-cellX`.

. Create the data plane services for {compute_service} cells to enable pre-upgrade workarounds, and to configure the Compute services for your chosen storage back end:
+
[subs="+quotes"]
----
for CELL in $(echo $RENAMED_CELLS); do
oc apply -f - <<EOF
apiVersion: dataplane.openstack.org/v1beta1
kind: OpenStackDataPlaneService
metadata:
 name: nova-$CELL
spec:
 dataSources:
   - secretRef:
       name: nova-$CELL-compute-config
   - secretRef:
       name: nova-migration-ssh-key
   - configMapRef:
       name: nova-cells-global-config
 playbook: osp.edpm.nova
 caCerts: combined-ca-bundle
 edpmServiceType: nova
 containerImageFields:
 - NovaComputeImage
 - EdpmIscsidImage
EOF
 done
----
+
* `spec.dataSources.secretRef` specifies an additional auto-generated `nova-cell<X>-metadata-neutron-config` secret to enable a local metadata service for cell<X>. You should also set
`spec.nova.template.cellTemplates.cell<X>.metadataServiceTemplate.enable` in the `OpenStackControlPlane/openstack` CR, as described in xref:adopting-the-compute-service_adopt-control-plane[Adopting the Compute service]. You can configure a single top-level metadata, or define the metadata per cell.
* `nova-$CELL-compute-config` specifies the secret that auto-generates for each `cell<X>`. You must append the `nova-cell<X>-compute-config` for each custom `OpenStackDataPlaneService` CR that is related to the {compute_service}.
* `nova-migration-ssh-key` specifies the secret that you must append for each custom `OpenStackDataPlaneService` CR that is related to the {compute_service}.
+
[NOTE]
====
When creating your data plane services for {compute_service} cells, review the following considerations:

* In this example, the same `nova-migration-ssh-key` key is shared across cells. However, you should use different keys for different cells.
* For simple configuration overrides, you do not need a custom data plane service. However, to reconfigure the cell, `cell1`,
the safest option is to create a custom service and a dedicated configuration map for it.
* The cell, `cell1`, is already managed with the default `OpenStackDataPlaneService` CR called `nova` and its `nova-extra-config` configuration map. Do not change the default data plane service `nova` definition. The changes are lost when the {rhos_acro} operator is updated with OLM.
* When a cell spans multiple node sets, give the custom `OpenStackDataPlaneService` resources a name that relates to the node set, for example, `nova-cell1-nfv` and `nova-cell1-enterprise`. The auto-generated configuration maps are then named `nova-cell1-nfv-extra-config` and `nova-cell1-enterprise-extra-config`.
* Different configurations for nodes in multiple node sets of the same cell are also supported, but are not covered in this guide.
====

. If TLS Everywhere is enabled, append the following content to the `OpenStackDataPlaneService` CR:
+
----
  tlsCerts:
    contents:
      - dnsnames
      - ips
    networks:
      - ctlplane
    issuer: osp-rootca-issuer-internal
    edpmRoleServiceName: nova
  caCerts: combined-ca-bundle
  edpmServiceType: nova
----

ifeval::["{build}" == "downstream"]
. Create a secret for the subscription manager:
+
----
$ oc create secret generic subscription-manager \
--from-literal rhc_auth='{"login": {"username": "<subscription_manager_username>", "password": "<subscription_manager_password>"}}'
----
+
* Replace `<subscription_manager_username>` with the applicable username.
* Replace `<subscription_manager_password>` with the applicable password.

. Create a secret for the Red Hat registry:
+
----
$ oc create secret generic redhat-registry \
--from-literal edpm_container_registry_logins='{"registry.redhat.io": {"<registry_username>": "<registry_password>"}}'
----
+
* Replace `<registry_username>` with the applicable username.
* Replace `<registry_password>` with the applicable password.
endif::[]
+

[NOTE]
You do not need to reference the `subscription-manager` secret in the `dataSources` field of the `OpenStackDataPlaneService` CR.
The secret is already passed in with a node-specific `OpenStackDataPlaneNodeSet` CR in the `ansibleVarsFrom` property in the `nodeTemplate` field.


. Create the data plane node set definitions for each cell:
+
[subs="+quotes"]
----
$ declare -A names
$ for CELL in $(echo $RENAMED_CELLS); do
  ref="COMPUTES_$(echo ${CELL}|tr '[:lower:]' '[:upper:]')"
  eval names=\${!${ref}[@]}
  ref_api="COMPUTES_API_$(echo ${CELL}|tr '[:lower:]' '[:upper:]')"
  [ -z "$names" ] && continue
  ind=0
  rm -f computes-$CELL
  for compute in $names; do
    ip="${ref}['$compute']"
    ip_api="${ref_api}['$compute']"
    cat >> computes-$CELL << EOF
    ${compute}:
     *hostName: $compute*
      ansible:
        ansibleHost: $compute
     *networks:*
      - defaultRoute: true
        fixedIP: ${!ip}
        name: ctlplane
        subnetName: subnet1
      - name: internalapi
        subnetName: subnet1
        fixedIP: ${!ip_api}
      - name: storage
        subnetName: subnet1
      - name: tenant
        subnetName: subnet1
EOF
    ind=$(( ind + 1 ))
  done

  test -f computes-$CELL || continue
  cat > nodeset-${CELL}.yaml <<EOF
apiVersion: dataplane.openstack.org/v1beta1
kind: OpenStackDataPlaneNodeSet
metadata:
 *name: openstack-$CELL*
spec:
  *tlsEnabled: false*
  networkAttachments:
      - ctlplane
  preProvisioned: true
  *services*:
ifeval::["{build}" == "downstream"]
    - redhat
endif::[]
    - bootstrap
    - download-cache
    - configure-network
    - validate-network
    - install-os
    - configure-os
    - ssh-known-hosts
    - run-os
    - reboot-os
    - install-certs
    - ovn
    - neutron-metadata
    - libvirt
    - nova-$CELL
    - telemetry
  env:
    - name: ANSIBLE_CALLBACKS_ENABLED
      value: "profile_tasks"
    - name: ANSIBLE_FORCE_COLOR
      value: "True"
    - name: ANSIBLE_VERBOSITY
      value: '3'
  nodeTemplate:
    ansibleSSHPrivateKeySecret: dataplane-adoption-secret
    ansible:
      ansibleUser: root
ifeval::["{build}" == "downstream"]
      ansibleVarsFrom:
      - secretRef:
          name: subscription-manager
      - secretRef:
          name: redhat-registry
endif::[]
      ansibleVars:
ifeval::["{build}" == "downstream"]
        rhc_release: 9.2
        rhc_repositories:
            - {name: "*", state: disabled}
            - {name: "rhel-9-for-x86_64-baseos-eus-rpms", state: enabled}
            - {name: "rhel-9-for-x86_64-appstream-eus-rpms", state: enabled}
            - {name: "rhel-9-for-x86_64-highavailability-eus-rpms", state: enabled}
            - {name: "rhoso-18.0-for-rhel-9-x86_64-rpms", state: enabled}
            - {name: "fast-datapath-for-rhel-9-x86_64-rpms", state: enabled}
            - {name: "rhceph-7-tools-for-rhel-9-x86_64-rpms", state: enabled}
endif::[]
        edpm_bootstrap_release_version_package: []
        # edpm_network_config
        # Default nic config template for a EDPM node
        # These vars are edpm_network_config role vars
        edpm_network_config_template: |
           ---
           {% set mtu_list = [ctlplane_mtu] %}
           {% for network in nodeset_networks %}
           {% set _ = mtu_list.append(lookup('vars', networks_lower[network] ~ '_mtu')) %}
           {%- endfor %}
           {% set min_viable_mtu = mtu_list | max %}
           network_config:
           - type: ovs_bridge
             name: {{ neutron_physical_bridge_name }}
             mtu: {{ min_viable_mtu }}
             use_dhcp: false
             dns_servers: {{ ctlplane_dns_nameservers }}
             domain: {{ dns_search_domains }}
             addresses:
             - ip_netmask: {{ ctlplane_ip }}/{{ ctlplane_cidr }}
             routes: {{ ctlplane_host_routes }}
             members:
             - type: interface
               name: nic1
               mtu: {{ min_viable_mtu }}
               # force the MAC address of the bridge to this interface
               primary: true
           {% for network in nodeset_networks %}
             - type: vlan
               mtu: {{ lookup('vars', networks_lower[network] ~ '_mtu') }}
               vlan_id: {{ lookup('vars', networks_lower[network] ~ '_vlan_id') }}
               addresses:
               - ip_netmask:
                   {{ lookup('vars', networks_lower[network] ~ '_ip') }}/{{ lookup('vars', networks_lower[network] ~ '_cidr') }}
               routes: {{ lookup('vars', networks_lower[network] ~ '_host_routes') }}
           {% endfor %}

        edpm_network_config_nmstate: false
        # Control resolv.conf management by NetworkManager
        # false = disable NetworkManager resolv.conf update (default)
        # true = enable NetworkManager resolv.conf update
        edpm_bootstrap_network_resolvconf_update: false
        edpm_network_config_hide_sensitive_logs: false
        #
        # These vars are for the network config templates themselves and are
        # considered EDPM network defaults.
        *neutron_physical_bridge_name: br-ctlplane*
        neutron_public_interface_name: eth0

        # edpm_nodes_validation
        edpm_nodes_validation_validate_controllers_icmp: false
        edpm_nodes_validation_validate_gateway_icmp: false

        # edpm ovn-controller configuration
        *edpm_ovn_bridge_mappings: <bridge_mappings>*
        edpm_ovn_bridge: br-int
        edpm_ovn_encap_type: geneve
        ovn_monitor_all: true
        edpm_ovn_remote_probe_interval: 60000
        edpm_ovn_ofctrl_wait_before_clear: 8000

        timesync_ntp_servers:
ifeval::["{build}" != "downstream"]
        - hostname: pool.ntp.org
endif::[]
ifeval::["{build}" == "downstream"]
        - hostname: clock.redhat.com
        - hostname: clock2.redhat.com
endif::[]

ifeval::["{build}" != "downstream"]
        edpm_bootstrap_command: |
          # This is a hack to deploy RDO Delorean repos to RHEL as if it were Centos 9 Stream
          set -euxo pipefail
          curl -sL https://github.com/openstack-k8s-operators/repo-setup/archive/refs/heads/main.tar.gz | tar -xz
          python3 -m venv ./venv
          PBR_VERSION=0.0.0 ./venv/bin/pip install ./repo-setup-main
          # This is required for FIPS enabled until trunk.rdoproject.org
          # is not being served from a centos7 host, tracked by
          # https://issues.redhat.com/browse/RHOSZUUL-1517
          dnf -y install crypto-policies
          update-crypto-policies --set FIPS:NO-ENFORCE-EMS
          # FIXME: perform dnf upgrade for other packages in EDPM ansible
          # here we only ensuring that decontainerized libvirt can start
          ./venv/bin/repo-setup current-podified -b antelope -d centos9 --stream
          dnf -y upgrade openstack-selinux
          rm -f /run/virtlogd.pid
          rm -rf repo-setup-main
endif::[]
ifeval::["{build}" == "downstream"]
        edpm_bootstrap_command: |
          # FIXME: perform dnf upgrade for other packages in EDPM ansible
          # here we only ensuring that decontainerized libvirt can start
          dnf -y upgrade openstack-selinux
          rm -f /run/virtlogd.pid
endif::[]

        gather_facts: false
        # edpm firewall, change the allowed CIDR if needed
        edpm_sshd_configure_firewall: true
        edpm_sshd_allowed_ranges: ['192.168.122.0/24']

        # Do not attempt OVS major upgrades here
        edpm_ovs_packages:
        - openvswitch3.3
        edpm_default_mounts:
          - *path: /dev/hugepages<size>*
            *opts: pagesize=<size>*
            fstype: hugetlbfs
            group: hugetlbfs
  nodes:
EOF
  cat computes-$CELL >> nodeset-${CELL}.yaml
done
----
+
* `${compute}.hostName` specifies the FQDN for the node if your deployment has a custom DNS Domain.
* `${compute}.networks` specifies the network composition. The network composition must match the source cloud configuration to avoid data plane connectivity downtime. The `ctlplane` network must come first. The commands only retain IP addresses for the hosts on the `ctlplane` and `internalapi` networks. Repeat this step for other isolated networks, or update the resulting files manually.
* `metadata.name:` specifies the node set names for each cell, for example, `openstack-cell1`, `openstack-cell2`. Only create node sets for cells that contain Compute nodes.
* `spec.tlsEnabled` specifies whether TLS Everywhere is enabled. If it is enabled, change `tlsEnabled` to `true`.
* `spec.services` specifies the services to be adopted. If you are not adopting telemetry services, omit it from the services list.
* `neutron_physical_bridge_name: br-ctlplane` specifies the bridge name. The bridge name and other OVN and {networking_service}-specific values must match the source cloud configuration to avoid data plane connectivity downtime.
*`edpm_ovn_bridge_mappings: <bridge_mappings>` specifies the value of the bridge mappings in your configuration, for example, `"datacentre:br-ctlplane"`.
* `path: /dev/hugepages<size>` and `opts: pagesize=<size>` configures huge pages. Replace `<size>` with the size of the page. To configure multi-sized huge pages, create more items in the list. Note that the mount points must match the source cloud configuration.
+
[NOTE]
====
Ensure that you use the same `ovn-controller` settings in the `OpenStackDataPlaneNodeSet` CR that you used in the {compute_service} nodes before adoption. This configuration is stored in the `external_ids` column in the `Open_vSwitch` table in the Open vSwitch database:

----
$ ovs-vsctl list Open .
...
external_ids        : {hostname=standalone.localdomain, ovn-bridge=br-int, ovn-bridge-mappings=<bridge_mappings>, ovn-chassis-mac-mappings="datacentre:1e:0a:bb:e6:7c:ad", ovn-encap-ip="172.19.0.100", ovn-encap-tos="0", ovn-encap-type=geneve, ovn-match-northd-version=False, ovn-monitor-all=True, ovn-ofctrl-wait-before-clear="8000", ovn-openflow-probe-interval="60", ovn-remote="tcp:ovsdbserver-sb.openstack.svc:6642", ovn-remote-probe-interval="60000", rundir="/var/run/openvswitch", system-id="2eec68e6-aa21-4c95-a868-31aeafc11736"}
...
----
====

. Deploy the `OpenStackDataPlaneNodeSet` CRs for each Compute cell:
+
----
for CELL in $(echo $RENAMED_CELLS); do
test -f nodeset-${CELL}.yaml || continue
oc apply -f nodeset-${CELL}.yaml
done
----

. If you use a {Ceph} back end for {block_storage_first_ref}, prepare the adopted data plane workloads:
+
----
for CELL in $(echo $RENAMED_CELLS); do
test -f nodeset-${CELL}.yaml || continue
oc patch osdpns/openstack-$CELL --type=merge --patch "
spec:
  services:
ifeval::["{build}" == "downstream"]
    - redhat
endif::[]
    - bootstrap
    - download-cache
    - configure-network
    - validate-network
    - install-os
    - configure-os
    - ssh-known-hosts
    - run-os
    - reboot-os
    - ceph-client
    - install-certs
    - ovn
    - neutron-metadata
    - libvirt
    - nova-$CELL
    - telemetry
  nodeTemplate:
    extraMounts:
    - extraVolType: Ceph
      volumes:
      - name: ceph
        secret:
          secretName: ceph-conf-files
      mounts:
      - name: ceph
        mountPath: "/etc/ceph"
        readOnly: true
  "
done
----
+
[NOTE]
====
Ensure that you use the same list of services from the original `OpenStackDataPlaneNodeSet` CR, except for the `ceph-client` and `ceph-hci-pre` services.

For environments that are enabled with border gateway protocol (BGP), you must add the following services to the list in the order shown:

* After `configure-network` and before `validate-network`: Add `frr` service for FRRouting BGP support
* After `neutron-metadata` and before `libvirt`: Add `ovn-bgp-agent` service for OVN BGP agent

You must also configure the following additional Ansible variables in the `nodeTemplate.ansible.ansibleVars` section:

* `edpm_frr_image`: The FRRouting container image
* `edpm_ovn_bgp_agent_image`: The OVN BGP agent container image
* `edpm_frr_bgp_ipv4_src_network`: The network name for BGP IPv4 source (for example, `bgpmainnet`)
* `edpm_frr_bgp_ipv6_src_network`: The network name for BGP IPv6 source (for example, `bgpmainnetv6`)
* `edpm_frr_bgp_neighbor_password`: The BGP neighbor password
* `edpm_ovn_encap_ip`: Set to the BGP main network IP (for example, `{{ lookup("vars", "bgpmainnet_ip") }}`)
====

. Optional: Enable `neutron-sriov-nic-agent` in the `OpenStackDataPlaneNodeSet` CR:
+
----
for CELL in $(echo $RENAMED_CELLS); do
test -f nodeset-${CELL}.yaml || continue
oc patch openstackdataplanenodeset openstack-$CELL --type='json' --patch='[
{
  "op": "add",
  "path": "/spec/services/-",
  "value": "neutron-sriov"
}, {
  "op": "add",
  "path": "/spec/nodeTemplate/ansible/ansibleVars/edpm_neutron_sriov_agent_SRIOV_NIC_physical_device_mappings",
  "value": "dummy_sriov_net:dummy-dev"
}, {
  "op": "add",
  "path": "/spec/nodeTemplate/ansible/ansibleVars/edpm_neutron_sriov_agent_SRIOV_NIC_resource_provider_bandwidths",
  "value": "dummy-dev:40000000:40000000"
}, {
  "op": "add",
  "path": "/spec/nodeTemplate/ansible/ansibleVars/edpm_neutron_sriov_agent_SRIOV_NIC_resource_provider_hypervisors",
  "value": "dummy-dev:standalone.localdomain"
}]'
done
----

. Optional: Enable `neutron-dhcp` in the `OpenStackDataPlaneNodeSet` CR:
+
----
for CELL in $(echo $RENAMED_CELLS); do
test -f nodeset-${CELL}.yaml || continue
oc patch openstackdataplanenodeset openstack-$CELL --type='json' --patch='[
{
  "op": "add",
  "path": "/spec/services/-",
  "value": "neutron-dhcp"
}]'
done
----
+
[NOTE]
====
To use `neutron-dhcp` with OVN for the {bare_metal_first_ref}, you must set the `disable_ovn_dhcp_for_baremetal_ports` configuration option for the {networking_first_ref}  to `true`.  You can set this configuration in the `NeutronAPI` spec:
----
..
spec:
  serviceUser: neutron
   ...
      customServiceConfig: |
          [DEFAULT]
          dhcp_agent_notification = True
          [ovn]
          disable_ovn_dhcp_for_baremetal_ports = true
----
====
. Run the pre-adoption validation:

.. Create the validation service:
+
----
$ oc apply -f - <<EOF
apiVersion: dataplane.openstack.org/v1beta1
kind: OpenStackDataPlaneService
metadata:
  name: pre-adoption-validation
spec:
  playbook: osp.edpm.pre_adoption_validation
EOF
----

.. Create a `OpenStackDataPlaneDeployment` CR that runs only the validation:
+
----
$ oc apply -f - <<EOF
apiVersion: dataplane.openstack.org/v1beta1
kind: OpenStackDataPlaneDeployment
metadata:
  name: openstack-pre-adoption
spec:
  nodeSets: $NODESETS
  servicesOverride:
  - pre-adoption-validation
EOF
----
+

[NOTE]
If you created different migration SSH keys for different `OpenStackDataPlaneService` CRs, you should also define a separate `OpenStackDataPlaneDeployment` CR for each node set or node sets that represent a cell.

.. When the validation is finished, confirm that the status of the Ansible EE pods is `Completed`:
+
----
$ watch oc get pod -l app=openstackansibleee
----
+
----
$ oc logs -l app=openstackansibleee -f --max-log-requests 20
----

.. Wait for the deployment to reach the `Ready` status:
+
----
$ oc wait --for condition=Ready openstackdataplanedeployment/openstack-pre-adoption --timeout=10m
----
+
[IMPORTANT]
====
If any openstack-pre-adoption validations fail, you must reference the Ansible logs to determine which ones were unsuccessful, and then try the following troubleshooting options:

* If the hostname validation failed, check that the hostname of the data plane
node is correctly listed in the `OpenStackDataPlaneNodeSet` CR.

* If the kernel argument check failed, ensure that the kernel argument configuration in the `edpm_kernel_args` and `edpm_kernel_hugepages` variables in the `OpenStackDataPlaneNodeSet` CR is the same as the kernel argument configuration that you used in the {rhos_prev_long} ({OpenStackShort}) {rhos_prev_ver} node.

* If the tuned profile check failed, ensure that the
`edpm_tuned_profile` variable in the `OpenStackDataPlaneNodeSet` CR is configured
to use the same profile as the one set on the {OpenStackShort} {rhos_prev_ver} node.
====

. Remove the remaining {OpenStackPreviousInstaller} services:

.. Create an `OpenStackDataPlaneService` CR to clean up the data plane services you are adopting:
+
----
$ oc apply -f - <<EOF
apiVersion: dataplane.openstack.org/v1beta1
kind: OpenStackDataPlaneService
metadata:
  name: tripleo-cleanup
spec:
  playbook: osp.edpm.tripleo_cleanup
EOF
----

.. Create the `OpenStackDataPlaneDeployment` CR to run the clean-up:
+
----
$ oc apply -f - <<EOF
apiVersion: dataplane.openstack.org/v1beta1
kind: OpenStackDataPlaneDeployment
metadata:
  name: tripleo-cleanup
spec:
  nodeSets: $NODESETS
  servicesOverride:
  - tripleo-cleanup
EOF
----

. When the clean-up is finished, deploy the `OpenStackDataPlaneDeployment` CR:
+
----
$ oc apply -f - <<EOF
apiVersion: dataplane.openstack.org/v1beta1
kind: OpenStackDataPlaneDeployment
metadata:
  name: openstack
spec:
  nodeSets: $NODESETS
EOF
----
+
[NOTE]
If you have other node sets to deploy, such as Networker nodes, you can
add them in the `nodeSets` list in this step, or create separate `OpenStackDataPlaneDeployment` CRs later. You cannot add new node sets to an `OpenStackDataPlaneDeployment` CR after deployment.

.Verification

. Confirm that all the Ansible EE pods reach a `Completed` status:
+
----
$ watch oc get pod -l app=openstackansibleee
----
+
----
$ oc logs -l app=openstackansibleee -f --max-log-requests 20
----

. Wait for the data plane node sets to reach the `Ready` status:
+
----
for CELL in $(echo $RENAMED_CELLS); do
oc wait --for condition=Ready osdpns/openstack-$CELL --timeout=30m
done
----

. Verify that the {networking_first_ref} agents are running:
+
----
$ oc exec openstackclient -- openstack network agent list
+--------------------------------------+------------------------------+------------------------+-------------------+-------+-------+----------------------------+
| ID                                   | Agent Type                   | Host                   | Availability Zone | Alive | State | Binary                     |
+--------------------------------------+------------------------------+------------------------+-------------------+-------+-------+----------------------------+
| 174fc099-5cc9-4348-b8fc-59ed44fcfb0e | DHCP agent                   | standalone.localdomain | nova              | :-)   | UP    | neutron-dhcp-agent         |
| 10482583-2130-5b0d-958f-3430da21b929 | OVN Metadata agent           | standalone.localdomain |                   | :-)   | UP    | neutron-ovn-metadata-agent |
| a4f1b584-16f1-4937-b2b0-28102a3f6eaa | OVN Controller agent         | standalone.localdomain |                   | :-)   | UP    | ovn-controller             |
+--------------------------------------+------------------------------+------------------------+-------------------+-------+-------+----------------------------+
----

[NOTE]
====
After you remove all the services from the {OpenStackPreviousInstaller} cell controllers, you can decomission the cell controllers.
To create new cell Compute nodes, you re-provision the decomissioned controllers as new data plane hosts and add them to the node sets of corresponding or new cells.
====

.Next steps

* You must perform a fast-forward upgrade on your Compute services. For more information, see xref:performing-a-fast-forward-upgrade-on-compute-services_{context}[Performing a fast-forward upgrade on Compute services].
