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

feat: add back support for mount points #610

Merged
merged 1 commit into from
Jan 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions api/services/microvm/v1alpha1/microvms.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,10 @@
"type": "boolean",
"description": "IsReadOnly specifies that the volume is to be mounted readonly."
},
"mountPoint": {
"type": "string",
"description": "MountPoint allows you to optionally specify a mount point for the volume. This only\napplied to additional volumes and it will use cloud-init to mount the volumes."
},
"source": {
"$ref": "#/definitions/typesVolumeSource",
"description": "Source is where the volume will be sourced from."
Expand Down
37 changes: 25 additions & 12 deletions api/types/microvm.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion api/types/microvm.proto
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,13 @@ message StaticAddress {

// Volume represents the configuration for a volume to be attached to a microvm.
message Volume {
reserved 3;
// ID is the uinique identifier of the volume.
string id = 1;
// IsReadOnly specifies that the volume is to be mounted readonly.
bool is_read_only = 2;
// MountPoint allows you to optionally specify a mount point for the volume. This only
// applied to additional volumes and it will use cloud-init to mount the volumes.
optional string mount_point = 3;
// Source is where the volume will be sourced from.
VolumeSource source = 4;
// PartitionID is the uuid of the boot partition.
Expand Down
4 changes: 2 additions & 2 deletions buf.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ deps:
- remote: buf.build
owner: googleapis
repository: googleapis
commit: 783e4b5374fa488ab068d08af9658438
commit: 75b4300737fb4efca0831636be94e517
- remote: buf.build
owner: grpc-ecosystem
repository: grpc-gateway
commit: b96615cde70c403f8075c48e56178f88
commit: a1ecdc58eccd49aa8bea2a7a9022dc27
34 changes: 33 additions & 1 deletion client/cloudinit/userdata/userdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,41 @@ type UserData struct {
RunCommands []string `yaml:"runcmd,omitempty"`
// BootCommands are commands you want to run early on in the boot process. These should only
// be used for commands that are need early on and running them via RunCommands is too late.
BootCommands []string `yaml:"bootcmd,omitempty"`
BootCommands []string `yaml:"bootcmd,omitempty"`
Mounts []Mount `yaml:"mounts,omitempty"`
MountDefaultFields Mount `yaml:"mount_default_fields,omitempty,flow"`
}

func (u *UserData) HasMountByName(deviceName string) bool {
if len(u.Mounts) == 0 {
return false
}

for _, mount := range u.Mounts {
if mount[0] == deviceName {
return true
}
}

return false
}

func (u *UserData) HasMountByMountPoint(mountPoint string) bool {
if len(u.Mounts) == 0 {
return false
}

for _, mount := range u.Mounts {
if mount[1] == mountPoint {
return true
}
}

return false
}

type Mount []string

type User struct {
Name string `yaml:"name"`
Sudo string `yaml:"sudo,omitempty"`
Expand Down
2 changes: 1 addition & 1 deletion core/application/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,7 @@ func createTestSpecWithMetadata(name, ns, uid string, metadata map[string]string
{
AllowMetadataRequests: true,
GuestMAC: "AA:FF:00:00:00:01",
GuestDeviceName: "mmds",
GuestDeviceName: "eth0",
Type: models.IfaceTypeTap,
},
{
Expand Down
22 changes: 13 additions & 9 deletions core/application/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
)

const (
MetadataInterfaceName = "mmds"
MetadataInterfaceName = "eth0"
)

func (a *app) CreateMicroVM(ctx context.Context, mvm *models.MicroVM) (*models.MicroVM, error) {
Expand Down Expand Up @@ -192,15 +192,19 @@ func (a *app) addMetadataInterface(mvm *models.MicroVM) {
}
}

mvm.Spec.NetworkInterfaces = append(mvm.Spec.NetworkInterfaces, models.NetworkInterface{
GuestDeviceName: MetadataInterfaceName,
Type: models.IfaceTypeTap,
AllowMetadataRequests: true,
GuestMAC: "AA:FF:00:00:00:01",
StaticAddress: &models.StaticAddress{
Address: "169.254.0.1/16",
interfaces := []models.NetworkInterface{
{
GuestDeviceName: MetadataInterfaceName,
Type: models.IfaceTypeTap,
AllowMetadataRequests: true,
GuestMAC: "AA:FF:00:00:00:01",
StaticAddress: &models.StaticAddress{
Address: "169.254.0.1/16",
},
},
})
}
interfaces = append(interfaces, mvm.Spec.NetworkInterfaces...)
mvm.Spec.NetworkInterfaces = interfaces

return
}
15 changes: 15 additions & 0 deletions core/models/volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ type Volume struct {
PartitionID string `json:"partition_id,omitempty"`
// Size is the size to resize this volume to.
Size int32 `json:"size,omitempty"`
// MountPoint allows you to optionally specify a mount point for the volume. This only
// applied to additional volumes and it will use cloud-init to mount the volumes.
MountPoint string `json:"mount_point,omitempty"`
}

// Volumes represents a collection of volumes.
Expand All @@ -28,6 +31,18 @@ func (v Volumes) GetByID(id string) *Volume {
return nil
}

// HasMountableVolumes returns true if any of the volumes
// have a mount point defined
func (v Volumes) HasMountableVolumes() bool {
for _, vol := range v {
if vol.MountPoint != "" {
return true
}
}

return false
}

// VolumeSource is the source of a volume. Based loosely on the volumes in Kubernetes Pod specs.
type VolumeSource struct {
// Container is used to specify a source of a volume as a OCI container.
Expand Down
6 changes: 6 additions & 0 deletions core/plans/microvm_create_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package plans
import (
"context"
"fmt"
"github.com/weaveworks-liquidmetal/flintlock/core/steps/cloudinit"

"github.com/weaveworks-liquidmetal/flintlock/core/models"
"github.com/weaveworks-liquidmetal/flintlock/core/ports"
Expand Down Expand Up @@ -63,6 +64,11 @@ func (p *microvmCreateOrUpdatePlan) Create(ctx context.Context) ([]planner.Proce
if err := p.addImageSteps(ctx, p.vm, ports.ImageService); err != nil {
return nil, fmt.Errorf("adding image steps: %w", err)
}
if len(p.vm.Spec.AdditionalVolumes) > 0 {
if err := p.addStep(ctx, cloudinit.NewDiskMountStep(p.vm)); err != nil {
return nil, fmt.Errorf("adding mount step: %w", err)
}
}

// Network interfaces
if err := p.addNetworkSteps(ctx, p.vm, ports.NetworkService); err != nil {
Expand Down
142 changes: 142 additions & 0 deletions core/steps/cloudinit/disk_mount.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package cloudinit

import (
"context"
"encoding/base64"
"fmt"

"github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"

"github.com/weaveworks-liquidmetal/flintlock/client/cloudinit"
"github.com/weaveworks-liquidmetal/flintlock/client/cloudinit/userdata"
"github.com/weaveworks-liquidmetal/flintlock/core/models"
"github.com/weaveworks-liquidmetal/flintlock/pkg/log"
"github.com/weaveworks-liquidmetal/flintlock/pkg/planner"
)

func NewDiskMountStep(vm *models.MicroVM) planner.Procedure {
return &diskMountStep{
vm: vm,
}
}

type diskMountStep struct {
vm *models.MicroVM
}

// Name is the name of the procedure/operation.
func (s *diskMountStep) Name() string {
return "cloudinit_disk_mount"
}

func (s *diskMountStep) ShouldDo(ctx context.Context) (bool, error) {
logger := log.GetLogger(ctx).WithFields(logrus.Fields{
"step": s.Name(),
})
logger.Debug("checking if procedure should be run")

if !s.vm.Spec.AdditionalVolumes.HasMountableVolumes() {
return false, nil
}

for _, vol := range s.vm.Spec.AdditionalVolumes {
if vol.MountPoint == "" {
continue
}

status := s.vm.Status.Volumes[vol.ID]

if status == nil || status.Mount.Source == "" {
return true, nil
}
}

vendorData, err := s.getVendorData()
if err != nil {
return false, fmt.Errorf("getting vendor data: %w", err)
}
if vendorData == nil {
return true, nil
}

for _, vol := range s.vm.Spec.AdditionalVolumes {
if vol.MountPoint == "" {
continue
}

if !vendorData.HasMountByMountPoint(vol.MountPoint) {
yitsushi marked this conversation as resolved.
Show resolved Hide resolved
return true, nil
}
}

return false, nil
}

// Do will perform the operation/procedure.
func (s *diskMountStep) Do(ctx context.Context) ([]planner.Procedure, error) {
logger := log.GetLogger(ctx).WithFields(logrus.Fields{
"step": s.Name(),
})
logger.Debug("running step to mount additional disks via cloud-init")

vendorData, err := s.getVendorData()
if err != nil {
return nil, fmt.Errorf("getting vendor data: %w", err)
}
if vendorData == nil {
vendorData = &userdata.UserData{}
}

startingCode := int('b')
for i, vol := range s.vm.Spec.AdditionalVolumes {
if vol.MountPoint == "" {
continue
}

device := fmt.Sprintf("vd%c", rune(startingCode+i)) // Device number is always +1 as we have the root volume first
yitsushi marked this conversation as resolved.
Show resolved Hide resolved

if !vendorData.HasMountByName(device) {
vendorData.Mounts = append(vendorData.Mounts, userdata.Mount{
device,
vol.MountPoint,
})
}
}
vendorData.MountDefaultFields = userdata.Mount{"None", "None", "auto", "defaults,nofail", "0", "2"}
yitsushi marked this conversation as resolved.
Show resolved Hide resolved

data, err := yaml.Marshal(vendorData)
if err != nil {
return nil, fmt.Errorf("marshalling vendor-data to yaml: %w", err)
}
dataWithHeader := append([]byte("## template: jinja\n#cloud-config\n\n"), data...)

if s.vm.Spec.Metadata == nil {
s.vm.Spec.Metadata = map[string]string{}
}
s.vm.Spec.Metadata[cloudinit.VendorDataKey] = base64.StdEncoding.EncodeToString(dataWithHeader)

return nil, nil
}

func (s *diskMountStep) Verify(ctx context.Context) error {
return nil
}

func (s *diskMountStep) getVendorData() (*userdata.UserData, error) {
vendorDataRaw, ok := s.vm.Spec.Metadata[cloudinit.VendorDataKey]
if !ok {
return nil, nil
}

vendorData := &userdata.UserData{}
data, err := base64.StdEncoding.DecodeString(vendorDataRaw)
if err != nil {
return nil, fmt.Errorf("decoding vendor data: %w", err)
}
if marshalErr := yaml.Unmarshal(data, vendorData); marshalErr != nil {
return nil, fmt.Errorf("unmarshalling vendor-data yaml: %w", err)
}

return vendorData, nil
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ require (
github.com/weaveworks-liquidmetal/flintlock/api v0.0.0-00010101000000-000000000000
github.com/weaveworks-liquidmetal/flintlock/client v0.0.0-00010101000000-000000000000
github.com/yitsushi/file-tailor v1.0.0
gopkg.in/yaml.v2 v2.4.0
sigs.k8s.io/yaml v1.3.0
)

Expand Down Expand Up @@ -133,5 +134,4 @@ require (
gopkg.in/djherbis/times.v1 v1.2.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
Loading