OpenNebula / terraform-provider-opennebula

Terraform provider for OpenNebula
https://www.terraform.io/docs/providers/opennebula/
Mozilla Public License 2.0
63 stars 52 forks source link

`opennebula_service` does not honor idempotence (OneKE) #546

Closed sk4zuzu closed 2 months ago

sk4zuzu commented 4 months ago

Description

OneKE deployment example defined here is not idempotent, but it seems to be a problem in the Provider and not in the example itself. After applying it twice, second run yields (with horrible consequences :face_holding_back_tears:):

  # opennebula_service.oneke must be replaced
-/+ resource "opennebula_service" "oneke" {
      ~ gid            = 0 -> (known after apply)
      ~ gname          = "oneadmin" -> (known after apply)
      ~ id             = "1" -> (known after apply)
        name           = "Service OneKE 1.29"
      ~ networks       = {
          - "Private" = 1
          - "Public"  = 0
        } -> (known after apply)
      ~ permissions    = "600" -> (known after apply)
      ~ roles          = [
          - {
              - cardinality = 1
              - name        = "vnf"
              - nodes       = [
                  - 0,
                ]
              - state       = 2
            },
          - {
              - cardinality = 1
              - name        = "master"
              - nodes       = [
                  - 1,
                ]
              - state       = 2
            },
          - {
              - cardinality = 1
              - name        = "worker"
              - nodes       = [
                  - 2,
                ]
              - state       = 2
            },
          - {
              - cardinality = 1
              - name        = "storage"
              - nodes       = [
                  - 3,
                ]
              - state       = 2
            },
        ] -> (known after apply)
      ~ state          = 2 -> (known after apply)
      ~ template_id    = 0 # forces replacement -> (known after apply) # forces replacement
      ~ uid            = 0 -> (known after apply)
      ~ uname          = "oneadmin" -> (known after apply)
        # (1 unchanged attribute hidden)

        # (1 unchanged block hidden)
    }

  # opennebula_service_template.oneke must be replaced
-/+ resource "opennebula_service_template" "oneke" {
      ~ gid         = 0 -> (known after apply)
      ~ id          = "0" -> (known after apply)
        name        = "Service OneKE 1.29"
      ~ template    = jsonencode(
          ~ {
              ~ TEMPLATE = {
                  ~ BODY = {
                      + description       = ""
                        name              = "OneKE 1.29"
                      ~ roles             = [
                          ~ {
                              + elasticity_policies  = []
                                name                 = "vnf"
                              + scheduled_policies   = []
                                # (5 unchanged attributes hidden)
                            },
                          ~ {
                              + elasticity_policies  = []
                                name                 = "master"
                              + scheduled_policies   = []
                                # (6 unchanged attributes hidden)
                            },
                          ~ {
                              + elasticity_policies  = []
                                name                 = "worker"
                              + scheduled_policies   = []
                                # (5 unchanged attributes hidden)
                            },
                          ~ {
                              ~ cardinality          = 1 -> 0
                              + elasticity_policies  = []
                                name                 = "storage"
                              + scheduled_policies   = []
                                # (4 unchanged attributes hidden)
                            },
                        ]
                        # (4 unchanged attributes hidden)
                    }
                }
            } # forces replacement
        )
      ~ uid         = 0 -> (known after apply)
        # (3 unchanged attributes hidden)
    }

Terraform and Provider version

Terraform v1.7.5
on linux_amd64
+ provider registry.terraform.io/opennebula/opennebula v1.4.0

Affected resources and data sources

Terraform configuration

terraform {
  required_providers {
    opennebula = {
      source = "OpenNebula/opennebula"
      version = ">= 1.4.0"
    }
  }
}

variable "one_user" {
  type = string
  description = "User account name to authenticate with OpenNebula"
#  default = "oneadmin"
}

variable "one_password" {
  type = string
  description = "Password for the user account to authenticate with OpenNebula"
}

variable "one_host" {
  type = string
  description = "Hostname or IP of the host where OpenNebula is running"
#  default = "localhost"
}

data "http" "appliances" {
  url = "https://marketplace.opennebula.io/appliance"
  request_headers = {
    User-Agent = "OpenNebula 6.8.0"
    Accept     = "application/json"
  }
}

locals {
  appliances = {
    for a in jsondecode(data.http.appliances.response_body).appliances : a.name => a
  }

  name = "Service OneKE 1.29"

  service = local.appliances[local.name]

  roles = {
    for k, v in local.service.roles : k => merge(local.appliances[v], {
      opennebula_template = jsondecode(local.appliances[v].opennebula_template)
    })
  }

  md5_to_url = {
    for f in distinct(flatten([
      for r in values(local.roles) : concat(
        try(r.files, []),
        [for d in try(r.disks, []) : local.appliances[d].files]
      )
    ])) : f.md5 => f.url
  }

  role_to_md5 = {
    for k, v in local.roles : k => [
      for f in flatten(concat(
        try(v.files, []),
        [for d in try(v.disks, []) : local.appliances[d].files]
      )) : f.md5
    ]
  }
}

provider "opennebula" {
  endpoint      = "http://${var.one_host}:2633/RPC2"
  flow_endpoint = "http://${var.one_host}:2474"
  username      = var.one_user
  password      = var.one_password
}

resource "opennebula_image" "oneke" {
  for_each     = local.md5_to_url
  name         = "${local.name} ${each.key}"
  datastore_id = 1
  persistent   = false
  permissions  = 642
  dev_prefix   = "vd"
  driver       = "qcow2"
  path         = each.value
}

resource "opennebula_template" "oneke" {
  for_each = local.roles
  name     = "${local.name} ${each.key}"
  cpu      = try(each.value["opennebula_template"].CPU, null)
  vcpu     = try(each.value["opennebula_template"].VCPU, null)
  memory   = try(each.value["opennebula_template"].MEMORY, null)
  context  = try(each.value["opennebula_template"].CONTEXT, null)

  dynamic "graphics" {
    for_each = try([each.value["opennebula_template"].GRAPHICS], [])
    content {
      type   = try(graphics.value.TYPE, null)
      listen = try(graphics.value.LISTEN, null)
    }
  }

  dynamic "os" {
    for_each = try([each.value["opennebula_template"].OS], [])
    content {
      arch = try(os.value.ARCH, null)
      boot = "disk0"
    }
  }

  dynamic "disk" {
    for_each = local.role_to_md5[each.key]
    content {
      image_id = opennebula_image.oneke[disk.value].id
    }
  }
}

resource "opennebula_service_template" "oneke" {
  name        = local.name
  permissions = 642
  uname       = "oneadmin"
  gname       = "oneadmin"
  template = jsonencode({ "TEMPLATE" = { "BODY" = merge(
    jsondecode(local.service["opennebula_template"]),
    {
      "roles" : [
        for r in jsondecode(local.service["opennebula_template"]).roles : merge(
          r,
          { vm_template = tonumber(opennebula_template.oneke[r.name].id) }
        )
      ]
    }
  ) } })
}

resource "opennebula_service" "oneke" {
  name        = local.name
  template_id = opennebula_service_template.oneke.id
  extra_template = jsonencode({
    networks_values = [
      { Public = { id = "0" } },
      { Private = { id = "1" } },
    ]
    custom_attrs_values = {
      ONEAPP_VROUTER_ETH0_VIP0 = ""
      ONEAPP_VROUTER_ETH1_VIP0 = ""

      ONEAPP_RKE2_SUPERVISOR_EP   = "ep0.eth0.vr:9345"
      ONEAPP_K8S_CONTROL_PLANE_EP = "ep0.eth0.vr:6443"
      ONEAPP_K8S_EXTRA_SANS       = "localhost,127.0.0.1,ep0.eth0.vr,$${vnf.TEMPLATE.CONTEXT.ETH0_IP}"

      ONEAPP_K8S_MULTUS_ENABLED = "NO"
      ONEAPP_K8S_MULTUS_CONFIG  = ""

      ONEAPP_K8S_CNI_PLUGIN   = "cilium"
      ONEAPP_K8S_CNI_CONFIG   = ""
      ONEAPP_K8S_CILIUM_RANGE = ""

      ONEAPP_K8S_METALLB_ENABLED = "NO"
      ONEAPP_K8S_METALLB_CONFIG  = ""
      ONEAPP_K8S_METALLB_RANGE   = ""

      ONEAPP_K8S_LONGHORN_ENABLED = "YES"
      ONEAPP_STORAGE_DEVICE       = "/dev/vdb"
      ONEAPP_STORAGE_FILESYSTEM   = "xfs"

      ONEAPP_K8S_TRAEFIK_ENABLED      = "YES"
      ONEAPP_VNF_HAPROXY_INTERFACES   = "eth0"
      ONEAPP_VNF_HAPROXY_REFRESH_RATE = "30"
      ONEAPP_VNF_HAPROXY_LB0_PORT     = "9345"
      ONEAPP_VNF_HAPROXY_LB1_PORT     = "6443"
      ONEAPP_VNF_HAPROXY_LB2_PORT     = "443"
      ONEAPP_VNF_HAPROXY_LB3_PORT     = "80"

      ONEAPP_VNF_DNS_ENABLED         = "YES"
      ONEAPP_VNF_DNS_INTERFACES      = "eth1"
      ONEAPP_VNF_DNS_NAMESERVERS     = "1.1.1.1,8.8.8.8"
      ONEAPP_VNF_NAT4_ENABLED        = "YES"
      ONEAPP_VNF_NAT4_INTERFACES_OUT = "eth0"
      ONEAPP_VNF_ROUTER4_ENABLED     = "YES"
      ONEAPP_VNF_ROUTER4_INTERFACES  = "eth0,eth1"
    }
  })
  timeouts {
    create = "10m"
    delete = "5m"
  }
}

Expected behavior

Second run of terraform apply should not destroy the service deployed on the first run.

Actual behavior

Provider destroys the service.

Steps to Reproduce

  1. Use the example code (after some obvious adjustments).
  2. terraform apply
  3. terraform apply

Debug output

N/A

Panic output

N/A

Important factoids

N/A

References

https://github.com/OpenNebula/one-apps/wiki/oneke_deploy#deploying-oneke

github-actions[bot] commented 3 months ago

This issue is stale because it has been open for 30 days with no activity and it has not the 'status: confirmed' label or it is not in a milestone. Remove the 'status: stale' label or comment, or this will be closed in 5 days.

github-actions[bot] commented 2 months ago

This issue is stale because it has been open for 30 days with no activity and it has not the 'status: confirmed' label or it is not in a milestone. Remove the 'status: stale' label or comment, or this will be closed in 5 days.