Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VMware static IP metadata configuration #154

Open
labserviceshslu opened this issue Aug 12, 2024 · 7 comments
Open

VMware static IP metadata configuration #154

labserviceshslu opened this issue Aug 12, 2024 · 7 comments

Comments

@labserviceshslu
Copy link

labserviceshslu commented Aug 12, 2024

I am currently trying to get a working cloud-config metadata file for VMware to set a static IP. I am on v1.1.6 of cloudbase-init.

This is my metadata config:

#cloud-config
instance-id: test
local-hostname: test
admin-username: Admin
admin-password: Passw0rd
public-keys-data: ssh-ed25519 <key>
network:
  version: 2
  ethernets:
    Ethernet0:
      dhcp4: false
      addresses:
        - 10.0.0.10/24
      gateway4: 10.0.0.1
      nameservers:
        addresses:
          - 1.1.1.1
          - 1.0.0.1

My main question is about the "Ethernet0" part. In the example only matching the interface by MAC address is shown, however by cloud-init spec, matching the interface name directly (as shown above), matching the MAC and regex matching the name is supported.

Does cloudbase-init with the VMware metadata service only support matching by MAC address and would matching by name be something that could be added (if it isn't already)?

My use case is provisioning Windows VMs using terraform, so the metadata has to be generated/known at the same time as the VM is defined. I cannot wait for vSphere to assign a MAC address and then template the metadata after as metadata is written while the VM is created.

@labserviceshslu
Copy link
Author

labserviceshslu commented Aug 12, 2024

Just saw this issue which might resolve the problem described above: #134

However the example linked above does use version 2. And this line also looks like version 2 is supported.

These two PRs might be relevant as well (though I am not sure if the feature they want to add are already present?)

Edit: Just tried with version 1 config:

#cloud-config
instance-id: test
local-hostname: test
admin-username: Admin
admin-password: Passw0rd
public-keys-data: ssh-ed25519 <key>
network:
  version: 1
  config:
  - type: physical
    name: Ethernet0
    subnets:
      - type: static
        address: 10.0.0.10/24
        gateway: 10.0.0.1
        dns_nameservers: ["1.1.1.1","1.0.0.1"]

Version 1 or 2 does not seem to make a difference, I always get the same error message:

2024-08-12 22:29:37.768 3288 DEBUG cloudbaseinit.metadata.services.vmwareguestinfoservice [-] network data {'version': 1, 'config': [{'type': 'physical', 'name': 'Ethernet0', 'subnets': [{'type': 'static', 'address': '10.0.0.10/24', 'gateway': '10.0.0.1', 'dns_nameservers': ['1.1.1.1', '1.0.0.1']}]}]} _process_network_config C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\metadata\services\vmwareguestinfoservice.py:240
2024-08-12 22:29:37.768 3288 DEBUG cloudbaseinit.utils.classloader [-] Loading class 'cloudbaseinit.osutils.windows.WindowsUtils' load_class C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\utils\classloader.py:35
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init [-] plugin 'NetworkConfigPlugin' failed with error ''NoneType' object has no attribute 'lower''
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init [-] 'NoneType' object has no attribute 'lower': AttributeError: 'NoneType' object has no attribute 'lower'
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init Traceback (most recent call last):
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init   File "C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\init.py", line 66, in _exec_plugin
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init     (status, reboot_required) = plugin.execute(service,
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init                                 ^^^^^^^^^^^^^^^^^^^^^^^
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init   File "C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\plugins\common\networkconfig.py", line 307, in execute
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init     return self._process_network_details_v2(network_details)
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init   File "C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\plugins\common\networkconfig.py", line 295, in _process_network_details_v2
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init     NetworkConfigPlugin._process_physical_links(
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init   File "C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\plugins\common\networkconfig.py", line 197, in _process_physical_links
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init     adapter_name = osutils.get_network_adapter_name_by_mac_address(
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init   File "C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\osutils\windows.py", line 804, in get_network_adapter_name_by_mac_address
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init     iface_index_list = [
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init                        ^
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init   File "C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\osutils\windows.py", line 808, in <listcomp>
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init     net_addr["mac_address"].lower() == mac_address.lower()]
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init                                        ^^^^^^^^^^^^^^^^^
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init AttributeError: 'NoneType' object has no attribute 'lower'
2024-08-12 22:29:37.768 3288 ERROR cloudbaseinit.init

Relevant code:

def _process_network_details_v2(network_details):
osutils = osutils_factory.get_os_utils()
NetworkConfigPlugin._process_physical_links(
osutils, network_details)
NetworkConfigPlugin._process_bond_links(osutils, network_details)
NetworkConfigPlugin._process_vlan_links(osutils, network_details)
reboot_required = NetworkConfigPlugin._process_networks(
osutils, network_details)
return plugin_base.PLUGIN_EXECUTION_DONE, reboot_required
def execute(self, service, shared_data):
network_details = service.get_network_details_v2()
if network_details:
return self._process_network_details_v2(network_details)
network_details = service.get_network_details()
if network_details:
return self._process_network_details(network_details)
return plugin_base.PLUGIN_EXECUTION_DONE, False

@FlorianLaunay
Copy link
Contributor

Hi @labserviceshslu
Are you using the latest stable version ? Network config support was brought by f0fe66b in release 1.1.5

@labserviceshslu
Copy link
Author

Hi @labserviceshslu Are you using the latest stable version ? Network config support was brought by f0fe66b in release 1.1.5

Yes, we were using the latest version at the time (should have been 1.1.6 then).

@FlorianLaunay
Copy link
Contributor

My bad. I did not read all your error log.

It's not quite well documented but your network configuration must match a MAC address by using the match argument:

network:
  version: 2
  ethernets:
    Ethernet0:
      dhcp4: false
      match:
       macaddress: "xx:xx:xx:xx:xx:xx"
      addresses:
        - 10.0.0.10/24
      gateway4: 10.0.0.1
      nameservers:
        addresses:
          - 1.1.1.1
          - 1.0.0.1

Otherwise your configuration will not be applied

@FlorianLaunay
Copy link
Contributor

FlorianLaunay commented Sep 30, 2024

@labserviceshslu
Copy link
Author

According with @ader1990 said in this discussion : https://review.opendev.org/c/x/cloudbase-init/+/913734/5..8/cloudbaseinit/metadata/services/nocloudservice.py#b297

Thank you for the reply. Unfortunately this is not possible in our scenario as the MAC address is generated at the same time the cloud-init data is generated (VMware vSphere + Terraform).

Is there any way around this or is adding an option without this limitation possible?

@FlorianLaunay
Copy link
Contributor

Thank you for the reply. Unfortunately this is not possible in our scenario as the MAC address is generated at the same time the cloud-init data is generated (VMware vSphere + Terraform).

Is there any way around this or is adding an option without this limitation possible?

I'm also using Terraform with VMware vSphere and I solved this problem by generating static MAC adresses with random_integer and macaddress providers:

terraform {
  required_version = ">= 1.8.0"

  required_providers {
    vsphere = {
      source  = "hashicorp/vsphere"
      version = ">= 2.8.0"
    }

    random = {
      source = "hashicorp/random"
      version = ">= 3.6.1"
    }

    macaddress = {
      source = "ivoronin/macaddress"
      version = ">= 0.3.2"
    }
  }
}

resource "random_integer" "mac_prefixes" {
  # Generate random integer between 0 and 63 (00 and 3F in hexadecimal) for each network interface of each vm
  # Build a map to loop on with key corresponding to "vm_name.interface_name"
  for_each = tomap({
    for interface in flatten([
      for vm, vm_attr in local.virtual_machines : [
        for net, net_attr in vm_attr.networks : {
          vm_key  = vm
          net_key = net_attr.name
        }
      ]
    ]) : "${interface.vm_key}.${interface.net_key}" => interface
  })

  min = 0
  max = 63
}

resource "macaddress" "mac_addresses" {
  # Generate static mac address for each network interface of each vm
  # Build a map to loop on with key corresponding to "vm_name.interface_name"
  for_each = tomap({
    for interface in flatten([
      for vm, vm_attr in local.virtual_machines : [
        for net, net_attr in vm_attr.networks : {
          vm_key  = vm
          net_key = net_attr.name
        }
      ]
    ]) : "${interface.vm_key}.${interface.net_key}" => interface
  })

  # use 00:50:56 VMWare prefix followed by the random integer generated previously
  # https://docs.vmware.com/en/VMware-vSphere/8.0/vsphere-networking/GUID-F9243FED-F081-498F-B4A9-EF950292AF77.html#GUID-ADFECCE5-19E7-4A81-B706-171E279ACBCD__GUID-6A6CB8C9-B579-4AEA-867D-B5E448F65426
  prefix = [0, 80, 86, random_integer.mac_prefixes[each.key].result]

  lifecycle {
    # Ignore future changes on prefix
    ignore_changes = [
      prefix,
    ]
  }
}

In this example, the virtual machines list is provided by the virtual_machines local variable and this code is looping over it using for_each. Object key is corresponding to VM name and network interfaces are stored in a networks list of objects with a name attribute corresponding to interface name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants