OpenNebula / terraform-provider-opennebula

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

Issue with Services: Unable to Create Reserve from Network #527

Closed shurkus closed 2 months ago

shurkus commented 5 months ago

Description

I am facing an issue when trying to create a reserve from a network using Terraform. The network reserve is not even initiated; the corresponding step appears to be skipped entirely. However, when I use the same configuration files with the OpenNebula CLI (oneflow-template instantiate 95 talos-extra_template.json), everything works as expected.

Terraform and Provider version

Terraform v1.6.5 on linux_amd64

Affected resources and data sources

opennebula_service

Terraform configuration

./terraform/service.tf

resource "opennebula_service_template" "talos" {
  name        = "talos"
  template    = templatefile("${path.module}/../templates/service_template.json", {})
}

resource "opennebula_service" "talos" {
  name        = "talos"
  template_id = opennebula_service_template.talos.id
  extra_template = jsonencode(
    {
        "custom_attrs_values": {
        },
        "networks_values": [{
          "controlplane": {
            "reserve_from": "1",
            "extra": "NAME=CONTROLPLANE\nSIZE=3"
          },
          "worker": {
            "reserve_from": "1",
            "extra": "NAME=WORKER\nSIZE=5"
          }
        }]

    }
  )
}

./templates/service_template.json

{
  "TEMPLATE": {
    "BODY": {
      "name": "talos",
      "deployment": "straight",
      "description": "TALOS",
      "roles": [
        {
          "name": "controlplane",
          "cardinality": 1,
          "vm_template": 21,
          "shutdown_action": "terminate-hard",
          "vm_template_contents": "NIC = [\n  NAME = \"_NIC0\",\n  NETWORK_ID = \"$controlplane\" ]\n ",
          "min_vms": 1,
          "max_vms": 3,
          "cooldown": 5
        },
        {
          "name": "worker",
          "cardinality": 1,
          "vm_template": 21,
          "shutdown_action": "terminate-hard",
          "parents": [
            "controlplane"
          ],
          "vm_template_contents": "NIC = [\n  NAME = \"_NIC1\",\n  NETWORK_ID = \"$worker\" ]\n ",
          "min_vms": 1,
          "max_vms": 5,
          "cooldown": 5
        }
      ],
      "networks": {
        "controlplane": "M|network|Controlplane Reserved| |reserve_from:1:SIZE=3",
        "worker": "M|network|Worker Reserved| |reserve_from:1:SIZE=5"
      }      
    }
  }
}

Expected behavior

The network reserve should be successfully initiated and created

Actual behavior

The specified network reserve creation step is not being executed. Instead, it appears to be skipped or not processed as intended, resulting in the network reserve not being created successfully.

Steps to Reproduce

Deploy OneFlow service by terraform oneflow show 116 --json

{
  "DOCUMENT": {
    "ID": "116",
    "UID": "0",
    "GID": "0",
    "UNAME": "oneadmin",
    "GNAME": "oneadmin",
    "NAME": "talos",
    "TYPE": "100",
    "PERMISSIONS": {
      "OWNER_U": "1",
      "OWNER_M": "1",
      "OWNER_A": "0",
      "GROUP_U": "0",
      "GROUP_M": "0",
      "GROUP_A": "0",
      "OTHER_U": "0",
      "OTHER_M": "0",
      "OTHER_A": "0"
    },
    "TEMPLATE": {
      "BODY": {
        "deployment": "straight",
        "description": "TALOS",
        "name": "talos",
        "networks": {
          "controlplane": "M|network|Controlplane Reserved| |reserve_from:1:SIZE=3",
          "worker": "M|network|Worker Reserved| |reserve_from:1:SIZE=5"
        },
        "roles": [
          {
            "cardinality": 1,
            "cooldown": 5,
            "max_vms": 3,
            "min_vms": 1,
            "name": "controlplane",
            "shutdown_action": "terminate-hard",
            "vm_template": 21,
            "vm_template_contents": "NIC = [\n  NAME = \"_NIC0\",\n  NETWORK_ID = \"\" ]\n ",
            "state": 2,
            "nodes": [
              {
                "deploy_id": 101,
                "vm_info": {
                  "VM": {
                    "ID": "101",
                    "UID": "0",
                    "GID": "0",
                    "UNAME": "oneadmin",
                    "GNAME": "oneadmin",
                    "NAME": "controlplane_0_(service_116)"
                  }
                }
              }
            ],
            "on_hold": false,
            "last_vmname": 1
          },
          {
            "cardinality": 1,
            "cooldown": 5,
            "max_vms": 5,
            "min_vms": 1,
            "name": "worker",
            "parents": [
              "controlplane"
            ],
            "shutdown_action": "terminate-hard",
            "vm_template": 21,
            "vm_template_contents": "NIC = [\n  NAME = \"_NIC1\",\n  NETWORK_ID = \"\" ]\n ",
            "state": 2,
            "nodes": [
              {
                "deploy_id": 102,
                "vm_info": {
                  "VM": {
                    "ID": "102",
                    "UID": "0",
                    "GID": "0",
                    "UNAME": "oneadmin",
                    "GNAME": "oneadmin",
                    "NAME": "worker_0_(service_116)"
                  }
                }
              }
            ],
            "on_hold": false,
            "last_vmname": 1
          }
        ],
        "registration_time": 1706921141,
        "custom_attrs_values": {
        },
        "networks_values": [
          {
            "controlplane": {
              "extra": "NAME=CONTROLPLANE\nSIZE=3",
              "reserve_from": "1",
              "id": null
            },
            "worker": {
              "extra": "NAME=WORKER\nSIZE=5",
              "reserve_from": "1",
              "id": null
            }
          }
        ],
        "state": 2,
        "start_time": 1706922196,
        "log": [
          {
            "timestamp": 1706922196,
            "severity": "I",
            "message": "New state: DEPLOYING_NETS"
          },
          {
            "timestamp": 1706922196,
            "severity": "I",
            "message": "New state: DEPLOYING"
          },
          {
            "timestamp": 1706922780,
            "severity": "I",
            "message": "New state: RUNNING"
          }
        ]
      }
    }
  }
}

NETWORK_ID = \"\" is empty Deploy OneFlow service by oneflow-template instantiate 95 talos-service.json

{
  "DOCUMENT": {
    "ID": "88",
    "UID": "0",
    "GID": "0",
    "UNAME": "oneadmin",
    "GNAME": "oneadmin",
    "NAME": "talos",
    "TYPE": "100",
    "PERMISSIONS": {
      "OWNER_U": "1",
      "OWNER_M": "1",
      "OWNER_A": "0",
      "GROUP_U": "0",
      "GROUP_M": "0",
      "GROUP_A": "0",
      "OTHER_U": "0",
      "OTHER_M": "0",
      "OTHER_A": "0"
    },
    "TEMPLATE": {
      "BODY": {
        "name": "talos",
        "deployment": "straight",
        "description": "TALOS",
        "roles": [
          {
            "name": "controlplane",
            "cardinality": 1,
            "vm_template": 21,
            "shutdown_action": "terminate-hard",
            "vm_template_contents": "NIC = [\n  NAME = \"_NIC0\",\n  NETWORK_ID = \"17\" ]\n",
            "min_vms": 1,
            "max_vms": 3,
            "cooldown": 5,
            "elasticity_policies": [

            ],
            "scheduled_policies": [

            ],
            "user_inputs_values": {
            },
            "state": 2,
            "nodes": [
              {
                "deploy_id": 70,
                "vm_info": {
                  "VM": {
                    "ID": "70",
                    "UID": "1",
                    "GID": "0",
                    "UNAME": "serveradmin",
                    "GNAME": "oneadmin",
                    "NAME": "controlplane_0_(service_88)"
                  }
                }
              }
            ],
            "on_hold": false,
            "last_vmname": 1
          },
          {
            "name": "worker",
            "cardinality": 1,
            "vm_template": 21,
            "shutdown_action": "terminate-hard",
            "parents": [
              "controlplane"
            ],
            "vm_template_contents": "NIC = [\n  NAME = \"_NIC0\",\n  NETWORK_ID = \"18\" ]\n",
            "min_vms": 1,
            "max_vms": 5,
            "cooldown": 5,
            "elasticity_policies": [

            ],
            "scheduled_policies": [

            ],
            "user_inputs_values": {
            },
            "state": 2,
            "nodes": [
              {
                "deploy_id": 71,
                "vm_info": {
                  "VM": {
                    "ID": "71",
                    "UID": "1",
                    "GID": "0",
                    "UNAME": "serveradmin",
                    "GNAME": "oneadmin",
                    "NAME": "worker_0_(service_88)"
                  }
                }
              }
            ],
            "on_hold": false,
            "last_vmname": 1
          }
        ],
        "ready_status_gate": false,
        "automatic_deletion": true,
        "networks": {
          "controlplane": "M|network|Controlplane Reserved| |reserve_from:1:SIZE=3",
          "worker": "M|network|Worker Reserved| |reserve_from:1:SIZE=5"
        },
        "shutdown_action": "terminate-hard",
        "registration_time": 1706913823,
        "custom_attrs_values": {
        },
        "networks_values": [
          {
            "controlplane": {
              "reserve_from": "1",
              "extra": "SIZE=3\nNAME=\"controlplane-88\"\n",
              "id": 17
            }
          },
          {
            "worker": {
              "reserve_from": "1",
              "extra": "SIZE=5\nNAME=\"worker-88\"\n",
              "id": 18
            }
          }
        ],
        "state": 2,
        "start_time": 1706914234,
        "log": [
          {
            "timestamp": 1706914234,
            "severity": "I",
            "message": "New state: DEPLOYING_NETS"
          },
          {
            "timestamp": 1706914271,
            "severity": "I",
            "message": "New state: DEPLOYING"
          },
          {
            "timestamp": 1706914303,
            "severity": "I",
            "message": "New state: RUNNING"
          }
        ]
      }
    }
  }
}

network is 17 and 18 created successfully

Debug output

No response

Panic output

No response

Important factoids

No response

References

No response

shurkus commented 4 months ago

Any news or need some additional information?

shurkus commented 4 months ago

An issue has been identified when using the goca library to create a service in OpenNebula. Code has been written to initialize service creation; however, it has been observed that networks, which should be created using reserve_from, are not being created.

Code Sample:

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"

    "github.com/OpenNebula/one/src/oca/go/src/goca"
)

func main() {
    // Get a Flow client
    client := goca.NewDefaultFlowClient(
        goca.NewFlowConfig("oneadmin", "oneadmin", "http://192.168.1.5:2474"),
    )

    // Get a Flow controller
    controller := goca.NewControllerFlow(client)

    // if template id is set, instantiate a Service from this template
    tc := controller.STemplate(172)

    // Read the content of extra_template.json
    extraTemplateContent, err := ioutil.ReadFile("extra_template.json")
    if err != nil {
        fmt.Println("Error reading extra_template.json:", err)
        return
    }

    // Decode the JSON content into a map[string]interface{}
    var data map[string]interface{}
    if err := json.Unmarshal(extraTemplateContent, &data); err != nil {
        fmt.Println("Error decoding JSON:", err)
        return
    }

    // Print the decoded JSON data for debugging
    fmt.Println("Decoded JSON data:", data)

    // Instantiate the service using the decoded JSON data
    service, err := tc.Instantiate(string(extraTemplateContent))
    if err != nil {
        fmt.Println("Error instantiating service:", err)
        return
    }

    // Print the instantiated service for debugging
    fmt.Println("Instantiated service:", service)
}

When running this code, it is expected that networks will be created according to the data passed in extra_template.json. However, it has been observed that networks are not being created, and it is suspected that the issue may be related to the goca library or some deeper level within OpenNebula.

shurkus commented 4 months ago

Understood. The issue lies within OpenNebula. Golang operates with maps that don't maintain order, resulting in JSON output sorted alphabetically. Consequently, a structure like:

"Controlplane": {
   "reserve_from": "1",
   "extra": "SIZE=3"
}

can be reordered to:

"Controlplane": {
   "extra": "SIZE=3",
   "reserve_from": "1"
}

However, OpenNebula has hardcoded logic (e.g., here) that expects specific first keys like 'id', 'template_id', or 'reserve_from', but encounters unexpected ones like 'extra' and consequently skips the processing step, which is incorrect.

A potential solution could look like this:

def deploy_networks(deploy = true)
  # Unpack template body if provided as JSON string
  template_body = deploy ? JSON.parse(self['TEMPLATE/BODY']) : @body

  # Return if no network information found in the template body
  return if template_body['networks_values'].nil?

  # Process each network in the networks_values list
  template_body['networks_values'].each do |network|
    network.each do |name, properties|
      # Skip iteration if the key is 'id'
      next if properties.key?('id')

      # Create or reserve the network based on the presence of 'template_id' or 'reserve_from' keys
      case
      when properties.key?('template_id')
        rc = create_vnet(name, properties)
      when properties.key?('reserve_from')
        rc = reserve(name, properties)
      end

      # Return error if something went wrong during network creation or reservation
      return rc if OpenNebula.is_error?(rc)

      # Update the 'id' value in the current network
      properties['id'] = rc
    end
  end if deploy

  # Replace variables in the template with corresponding values
  resolve_networks(template_body)

  # Update the template body
  update_body(template_body)
end

However, the hardcoded logic continues in delete_networks and networks functions (maybe somewhere else).

vickmp commented 4 months ago

Hi @shurkus, thank you very much for bringing this problem to our attention! We have been analyzing this case and we think you are absolutely right. As you point out, this bug comes from OpenNebula (specifically from Flow server), so it will be fixed in the next OpenNebula release.

shurkus commented 4 months ago

Apologies for creating the pull request from my other account. Additionally, I would like to bring your attention to packer-plugin-opennebula. Unfortunately, I don't have the time to continue its development, so it would be great if you could consider taking it over and providing further support.

rsmontero commented 2 months ago

Fix for OpenNebula OneFlow has been merged here THANKS!!