Skip to content

Commit

Permalink
openstack: dynamically mount the config-drive
Browse files Browse the repository at this point in the history
When we want to use config-drive in immutable systems, very often the
config-drive is only used at boot and then umounted (e.g. ignition does
this).

Later when we want to fetch Metadata from the config drive, we actually
have to mount it.

In this PR, I'm adding similar code than coreos/ignition where we
dynamically mount the config-drive is the device was found with the
right label (config-2 or CONFIG-2 as documented in OpenStack). If the
device is found, we mount it, fetch the data and umount it.
  • Loading branch information
EmilienM committed Sep 23, 2024
1 parent 4bae6ce commit 084810a
Showing 1 changed file with 92 additions and 23 deletions.
115 changes: 92 additions & 23 deletions pkg/platforms/openstack/openstack.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"

Expand All @@ -21,15 +23,18 @@ import (
)

const (
ospHostMetaDataDir = "/host/var/config/openstack/2018-08-27"
ospMetaDataDir = "/var/config/openstack/2018-08-27"
ospMetaDataBaseURL = "http://169.254.169.254/openstack/2018-08-27"
ospNetworkDataJSON = "network_data.json"
ospMetaDataJSON = "meta_data.json"
ospHostNetworkDataFile = ospHostMetaDataDir + "/" + ospNetworkDataJSON
ospHostMetaDataFile = ospHostMetaDataDir + "/" + ospMetaDataJSON
ospNetworkDataURL = ospMetaDataBaseURL + "/" + ospNetworkDataJSON
ospMetaDataURL = ospMetaDataBaseURL + "/" + ospMetaDataJSON
varConfigPath = "/var/config"
ospMetaDataBaseDir = "/openstack/2018-08-27"
ospMetaDataDir = varConfigPath + ospMetaDataBaseDir
ospMetaDataBaseURL = "http://169.254.169.254" + ospMetaDataBaseDir
ospNetworkDataJSON = "network_data.json"
ospMetaDataJSON = "meta_data.json"
ospNetworkDataURL = ospMetaDataBaseURL + "/" + ospNetworkDataJSON
ospMetaDataURL = ospMetaDataBaseURL + "/" + ospMetaDataJSON
// Config drive is defined as an iso9660 or vfat (deprecated) drive
// with the "config-2" label.
//https://docs.openstack.org/nova/latest/user/config-drive.html
configDriveLabel = "config-2"
)

var (
Expand Down Expand Up @@ -109,9 +114,10 @@ func New(hostManager host.HostManagerInterface) OpenstackInterface {
}

// GetOpenstackData gets the metadata and network_data
func getOpenstackData(useHostPath bool) (metaData *OSPMetaData, networkData *OSPNetworkData, err error) {
metaData, networkData, err = getOpenstackDataFromConfigDrive(useHostPath)
func getOpenstackData(mountConfigDrive bool) (metaData *OSPMetaData, networkData *OSPNetworkData, err error) {
metaData, networkData, err = getOpenstackDataFromConfigDrive(mountConfigDrive)
if err != nil {
log.Log.Error(err, "GetOpenStackData(): non-fatal error getting OpenStack data from config drive")
metaData, networkData, err = getOpenstackDataFromMetadataService()
if err != nil {
return metaData, networkData, fmt.Errorf("GetOpenStackData(): error getting OpenStack data: %w", err)
Expand Down Expand Up @@ -153,46 +159,109 @@ func getOpenstackData(useHostPath bool) (metaData *OSPMetaData, networkData *OSP
return metaData, networkData, err
}

// getConfigDriveDevice returns the config drive device which was found
func getConfigDriveDevice() (string, error) {
dev := "/dev/disk/by-label/" + configDriveLabel
if _, err := os.Stat(dev); os.IsNotExist(err) {
out, err := exec.Command(
"blkid", "-l",
"-t", "LABEL="+configDriveLabel,
"-o", "device",
).CombinedOutput()
if err != nil {
return "", fmt.Errorf("unable to run blkid: %v", err)
}
dev = strings.TrimSpace(string(out))
}
log.Log.Info("found config drive device", "device", dev)
return dev, nil
}

// mountConfigDriveDevice mounts the config drive and return the path
func mountConfigDriveDevice(device string) (string, error) {
if device == "" {
return "", fmt.Errorf("device is empty")
}
tmpDir, err := os.MkdirTemp("", "sriov-configdrive")
if err != nil {
return "", fmt.Errorf("error creating temp directory: %w", err)
}
cmd := exec.Command("mount", "-o", "ro", "-t", "auto", device, tmpDir)
if err := cmd.Run(); err != nil {
return "", fmt.Errorf("error mounting config drive: %w", err)
}
log.Log.V(2).Info("mounted config drive device", "device", device, "path", tmpDir)
return tmpDir, nil
}

// ummountConfigDriveDevice ummounts the config drive device
func ummountConfigDriveDevice(path string) error {
if path == "" {
return fmt.Errorf("path is empty")
}
cmd := exec.Command("umount", path)
if err := cmd.Run(); err != nil {
return fmt.Errorf("error umounting config drive: %w", err)
}
log.Log.V(2).Info("umounted config drive", "path", path)
return nil
}

// getOpenstackDataFromConfigDrive reads the meta_data and network_data files
func getOpenstackDataFromConfigDrive(useHostPath bool) (metaData *OSPMetaData, networkData *OSPNetworkData, err error) {
func getOpenstackDataFromConfigDrive(mountConfigDrive bool) (metaData *OSPMetaData, networkData *OSPNetworkData, err error) {
metaData = &OSPMetaData{}
networkData = &OSPNetworkData{}
var configDrivePath string
log.Log.Info("reading OpenStack meta_data from config-drive")
var metadataf *os.File
ospMetaDataFilePath := ospMetaDataFile
if useHostPath {
ospMetaDataFilePath = ospHostMetaDataFile
if mountConfigDrive {
configDriveDevice, err := getConfigDriveDevice()
if err != nil {
return metaData, networkData, fmt.Errorf("error finding config drive device: %w", err)
}
configDrivePath, err = mountConfigDriveDevice(configDriveDevice)
if err != nil {
return metaData, networkData, fmt.Errorf("error mounting config drive device: %w", err)
}
defer func() {
if e := ummountConfigDriveDevice(configDrivePath); err == nil && e != nil {
err = fmt.Errorf("error umounting config drive device: %w", e)
}
if e := os.Remove(configDrivePath); err == nil && e != nil {
err = fmt.Errorf("error removing temp directory %s: %w", configDrivePath, e)
}
}()
ospMetaDataFilePath = filepath.Join(configDrivePath, ospMetaDataBaseDir, ospMetaDataJSON)
ospNetworkDataFile = filepath.Join(configDrivePath, ospMetaDataBaseDir, ospNetworkDataJSON)
}
metadataf, err = os.Open(ospMetaDataFilePath)
if err != nil {
return metaData, networkData, fmt.Errorf("error opening file %s: %w", ospHostMetaDataFile, err)
return metaData, networkData, fmt.Errorf("error opening file %s: %w", ospMetaDataFilePath, err)
}
defer func() {
if e := metadataf.Close(); err == nil && e != nil {
err = fmt.Errorf("error closing file %s: %w", ospHostMetaDataFile, e)
err = fmt.Errorf("error closing file %s: %w", ospMetaDataFilePath, e)
}
}()
if err = json.NewDecoder(metadataf).Decode(&metaData); err != nil {
return metaData, networkData, fmt.Errorf("error unmarshalling metadata from file %s: %w", ospHostMetaDataFile, err)
return metaData, networkData, fmt.Errorf("error unmarshalling metadata from file %s: %w", ospMetaDataFilePath, err)
}

log.Log.Info("reading OpenStack network_data from config-drive")
var networkDataf *os.File
ospNetworkDataFilePath := ospNetworkDataFile
if useHostPath {
ospNetworkDataFilePath = ospHostNetworkDataFile
}
networkDataf, err = os.Open(ospNetworkDataFilePath)
if err != nil {
return metaData, networkData, fmt.Errorf("error opening file %s: %w", ospHostNetworkDataFile, err)
return metaData, networkData, fmt.Errorf("error opening file %s: %w", ospNetworkDataFilePath, err)
}
defer func() {
if e := networkDataf.Close(); err == nil && e != nil {
err = fmt.Errorf("error closing file %s: %w", ospHostNetworkDataFile, e)
err = fmt.Errorf("error closing file %s: %w", ospNetworkDataFilePath, e)
}
}()
if err = json.NewDecoder(networkDataf).Decode(&networkData); err != nil {
return metaData, networkData, fmt.Errorf("error unmarshalling metadata from file %s: %w", ospHostNetworkDataFile, err)
return metaData, networkData, fmt.Errorf("error unmarshalling metadata from file %s: %w", ospNetworkDataFilePath, err)
}
return metaData, networkData, err
}
Expand Down

0 comments on commit 084810a

Please sign in to comment.