tinkerbell/actions

rootio does not process the returned metadata from hegel correctly

alienninja opened this issue · 4 comments

I am using the hardware and template examples as outlined on the tinkerbell.org documentation page. I have found that when rootio requests metadata from hegel, it is expecting it in a format that is different from the Tinkerbell hardware json format. Rootio is looking for types.Metadata which, when comparing it to a Tinkerbell hardware json, is an instance within the metadata structure.

As a test, I modified a local copy of rootio to read in a Tinkerbell hardware json and return the correct metadata instance.

Expected Behaviour

Rootio should correctly parse the returned metadata and process the storage structure correctly.

Current Behaviour

Unmodified, rootio states that zero disks were found in the metadata.

Possible Solution

Configure rootio to process the metadata as a hardware json. Or modify the call to hegel to return the correct structure via something like /metadata/storage.

Steps to Reproduce (for bugs)

  1. Set up a docker-compose instance of Tinkerbell
  2. Create a hardware json as outlined on https://docs.tinkerbell.org/hardware-data/
  3. Create a template yaml as outlined on the bottom of https://docs.tinkerbell.org/deploying-operating-systems/examples-ubuntu/
  4. Run a pxeboot on the machine to be provisioned, review the rootio logs to show that it states zero disks found.

Context

I am unable to use rootio to process my storage array, this prevents me from using Tinkerbell as intended.

Your Environment

  • Operating System and version (e.g. Linux, Windows, MacOS):
    The provisioner is running Ubuntu 20.04 on a VM on ESXi. The machine being provisioned is on the same ESXi server. So both VM's.
  • How are you running Tinkerbell? Using Vagrant & VirtualBox, Vagrant & Libvirt, on Packet using Terraform, or give details:
    Running Tinkerbell with docker-compose
  • Link to your project or a code example to reproduce issue:

Modifying rootio to read in the metadata into the following structure makes it work correctly:
I was able to limit all changes to rootio/v1/pkg/types.go/metadata.go


type ExportedCacher struct {
	ID                                 string                   `json:"id"`
	Metadata                           Metadata                 `jsonm:"metadata"`
}

type Metadata struct {
	Arch                               string                   `json:"arch"`
	State                              string                   `json:"state"`
	EFIBoot                            bool                     `json:"efi_boot"`
	Instance                           Instance                 `json:"instance,omitempty"`
	PreinstalledOperatingSystemVersion interface{}              `json:"preinstalled_operating_system_version"`
	NetworkPorts                       []map[string]interface{} `json:"network_ports"`
	PlanSlug                           string                   `json:"plan_slug"`
	Facility                           string                   `json:"facility_code"`
	Hostname                           string                   `json:"hostname"`
	BondingMode                        int                      `json:"bonding_mode"`
}


type Instance struct {
	ID       string `json:"id,omitempty"`
	State    string `json:"state,omitempty"`
	Hostname string `json:"hostname,omitempty"`
	AllowPXE bool   `json:"allow_pxe,omitempty"`
	Rescue   bool   `json:"rescue,omitempty"`

	IPAddresses []map[string]interface{} `json:"ip_addresses,omitempty"`
	OS          OperatingSystem         `json:"operating_system_version,omitempty"`
	UserData    string                   `json:"userdata,omitempty"`

	CryptedRootPassword string `json:"crypted_root_password,omitempty"`

	Storage      Storage `json:"storage,omitempty"`
	SSHKeys      []string `json:"ssh_keys,omitempty"`
	NetworkReady bool     `json:"network_ready,omitempty"`
}

type OperatingSystem struct {
	Slug     string `json:"slug"`
	Distro   string `json:"distro"`
	Version  string `json:"version"`
	ImageTag string `json:"image_tag"`
	OsSlug   string `json:"os_slug"`
}



type File struct {
	Path     string `json:"path"`
	Contents string `json:"contents,omitempty"`
	Mode     int    `json:"mode,omitempty"`
	UID      int    `json:"uid,omitempty"`
	GID      int    `json:"gid,omitempty"`
}

type FilesystemOptions struct {
	Force   bool     `json:"force,omitempty"`
	Options []string `json:"options,omitempty"`
}

type Raid struct {
	Name    string   `json:"name"`
	Level   string   `json:"level"`
	Devices []string `json:"devices"`
	Spares  int      `json:"spares,omitempty"`
}

type Storage struct {
	Disks       []Disk       `json:"disks,omitempty"`
	RAID        []Raid       `json:"raid,omitempty"`
	Filesystems []Filesystem `json:"filesystems,omitempty"`
}

//Filesystem defines the organisation of a filesystem
type Filesystem struct {
		Mount struct {
		Create struct {
			Options []string `json:"options"`
		} `json:"create"`
		Device string `json:"device"`
		Format string `json:"format"`
		Point  string `json:"point"`
	} `json:"mount"`
}

//Disk defines the configuration for a disk
type Disk struct {
	Device     string       `json:"device"`
	Partitions []Partitions `json:"partitions"`
	WipeTable  bool         `json:"wipe_table"`
}

//Partitions details the architecture
type Partitions struct {
	Label  string `json:"label"`
	Number int    `json:"number"`
	Size   uint64 `json:"size"`
}



//I also configured RetreiveData to return the appropriate nested object:
func RetreieveData() (*Instance, error) {
	metadataURL := os.Getenv("MIRROR_HOST")
	if metadataURL == "" {
		return nil, fmt.Errorf("Unable to discover the metadata server from environment variable [MIRROR_HOST]")
	}

	metadataClient := http.Client{
		Timeout: time.Second * 60, // Timeout after 60 seconds (seems massively long is this dial-up?)
	}

	req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s:50061/metadata", metadataURL), nil)
	if err != nil {
		return nil, err
	}

	req.Header.Set("User-Agent", "bootkit")

	res, getErr := metadataClient.Do(req)
	if getErr != nil {
		return nil, err
	}

	if res.Body != nil {
		defer res.Body.Close()
	}

	body, readErr := ioutil.ReadAll(res.Body)
	if readErr != nil {
		return nil, err
	}

	var exportedcacher ExportedCacher
	//var mdata Metadata

	jsonErr := json.Unmarshal(body, &exportedcacher)
	if jsonErr != nil {
		return nil, jsonErr
	}

	return &exportedcacher.Metadata.Instance, nil
}



mmlb commented

Hi @alienninja, what is rootio? Can you give me a link as I'm not familiar with it.

Hi @alienninja, what is rootio? Can you give me a link as I'm not familiar with it.

Sure! It's one of the actions provided in tinkerbell.

https://github.com/tinkerbell/hub/tree/main/actions/rootio/v1

mmlb commented

oh! 🤦 :D

Hi everyone, I really wanted to get this working so I forked and modified the rootio code to read in the template json file as it's spec'd from the Tinkerbell documentation. This is now working for me, but I think there may be a better way to reference the correct structure rather than manually copying into the code

Also -- rootio doesn't create /etc/fstab, which makes sense because this is before the archive2disk is run. I was thinking an fstab action is needed that could read in the metadata and generate fstab since all the needed data is in metadata. I realized this when my root was mounted as read only after the provisioning.

my fork:
https://github.com/alienninja/hub/tree/main/actions/rootio/v1