Skip to content

Commit

Permalink
Add a qemu-iso platform
Browse files Browse the repository at this point in the history
Lower the bits to do "inject Ignition via coreos-installer embed iso"
into the qemu layer.  Copy the "qemu-unpriv" platform code into a
"qemu-iso" platform (both are mostly thin wrappers around `qemu.go`).

This makes it completely trivial to get a `ssh` shell in an ISO:
`cosa run -p qemu-iso --memory 8192`

And even better means we can start running a lot of the kola tests
directly against the ISO (though not everything will work, e.g.
tests that reboot can't persist state).   I did verify this works:
`kola run -p qemu-iso --qemu-memory 8192 basic`
  • Loading branch information
cgwalters authored and openshift-merge-robot committed Jul 13, 2020
1 parent 2d1366f commit 3b9bbdc
Show file tree
Hide file tree
Showing 9 changed files with 447 additions and 52 deletions.
8 changes: 7 additions & 1 deletion mantle/cmd/kola/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ var (
outputDir string
kolaPlatform string
kolaArchitectures = []string{"amd64"}
kolaPlatforms = []string{"aws", "azure", "do", "esx", "gce", "openstack", "packet", "qemu", "qemu-unpriv"}
kolaPlatforms = []string{"aws", "azure", "do", "esx", "gce", "openstack", "packet", "qemu", "qemu-unpriv", "qemu-iso"}
kolaDistros = []string{"fcos", "rhcos"}
kolaIgnitionVersionDefaults = map[string]string{
"cl": "v2",
Expand Down Expand Up @@ -144,6 +144,8 @@ func init() {
bv(&kola.QEMUOptions.Native4k, "qemu-native-4k", false, "Force 4k sectors for main disk")
bv(&kola.QEMUOptions.Nvme, "qemu-nvme", false, "Use NVMe for main disk")
bv(&kola.QEMUOptions.Swtpm, "qemu-swtpm", true, "Create temporary software TPM")

sv(&kola.QEMUIsoOptions.IsoPath, "qemu-iso", "", "path to CoreOS ISO image")
}

// Sync up the command line options if there is dependency
Expand Down Expand Up @@ -275,6 +277,10 @@ func syncCosaOptions() error {
if kola.QEMUOptions.DiskImage == "" && kola.CosaBuild.Meta.BuildArtifacts.Qemu != nil {
kola.QEMUOptions.DiskImage = filepath.Join(kola.CosaBuild.Dir, kola.CosaBuild.Meta.BuildArtifacts.Qemu.Path)
}
case "qemu-iso":
if kola.QEMUIsoOptions.IsoPath == "" && kola.CosaBuild.Meta.BuildArtifacts.LiveIso != nil {
kola.QEMUIsoOptions.IsoPath = filepath.Join(kola.CosaBuild.Dir, kola.CosaBuild.Meta.BuildArtifacts.LiveIso.Path)
}
}

if kola.Options.IgnitionVersion == "" && kola.QEMUOptions.DiskImage == "" {
Expand Down
5 changes: 4 additions & 1 deletion mantle/cmd/kola/qemuexec.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func runQemuExec(cmd *cobra.Command, args []string) error {
if directIgnition {
return fmt.Errorf("Cannot use devshell with direct ignition")
}
if kola.QEMUOptions.DiskImage == "" {
if kola.QEMUOptions.DiskImage == "" && kolaPlatform == "qemu" {
return fmt.Errorf("No disk image provided")
}
ignitionFragments = append(ignitionFragments, "autologin")
Expand Down Expand Up @@ -210,6 +210,9 @@ func runQemuExec(cmd *cobra.Command, args []string) error {
return errors.Wrapf(err, "adding primary disk")
}
}
if kola.QEMUIsoOptions.IsoPath != "" {
builder.AddIso(kola.QEMUIsoOptions.IsoPath, "")
}
builder.Hostname = hostname
if memory != 0 {
builder.Memory = memory
Expand Down
4 changes: 2 additions & 2 deletions mantle/cmd/kola/testiso.go
Original file line number Diff line number Diff line change
Expand Up @@ -582,8 +582,8 @@ func testLiveLogin(outdir string) error {
builddir := kola.CosaBuild.Dir
isopath := filepath.Join(builddir, kola.CosaBuild.Meta.BuildArtifacts.LiveIso.Path)
builder := newBaseQemuBuilder()
// See AddInstallISO, but drop the bootindex bit (applicable to all arches except s390x and ppc64le); we want it to be the default
builder.AddInstallIso(isopath, "")
// Drop the bootindex bit (applicable to all arches except s390x and ppc64le); we want it to be the default
builder.AddIso(isopath, "")

completionChannel, err := builder.VirtioChannelRead("coreos.liveiso-success")
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions mantle/kola/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import (
"github.com/coreos/mantle/platform/machine/gcloud"
"github.com/coreos/mantle/platform/machine/openstack"
"github.com/coreos/mantle/platform/machine/packet"
"github.com/coreos/mantle/platform/machine/qemuiso"
"github.com/coreos/mantle/platform/machine/unprivqemu"
"github.com/coreos/mantle/sdk"
"github.com/coreos/mantle/system"
Expand Down Expand Up @@ -83,6 +84,7 @@ var (
OpenStackOptions = openstackapi.Options{Options: &Options} // glue to set platform options from main
PacketOptions = packetapi.Options{Options: &Options} // glue to set platform options from main
QEMUOptions = unprivqemu.Options{Options: &Options} // glue to set platform options from main
QEMUIsoOptions = qemuiso.Options{Options: &Options} // glue to set platform options from main

CosaBuild *sdk.LocalBuild // this is a parsed cosa build

Expand Down Expand Up @@ -216,6 +218,8 @@ func NewFlight(pltfrm string) (flight platform.Flight, err error) {
flight, err = packet.NewFlight(&PacketOptions)
case "qemu-unpriv":
flight, err = unprivqemu.NewFlight(&QEMUOptions)
case "qemu-iso":
flight, err = qemuiso.NewFlight(&QEMUIsoOptions)
default:
err = fmt.Errorf("invalid platform %q", pltfrm)
}
Expand Down
165 changes: 165 additions & 0 deletions mantle/platform/machine/qemuiso/cluster.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// Copyright 2020 Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package qemuiso

import (
"fmt"
"os"
"path/filepath"
"strconv"

"sync"
"time"

"github.com/pborman/uuid"
"github.com/pkg/errors"

"github.com/coreos/mantle/platform"
"github.com/coreos/mantle/platform/conf"
"github.com/coreos/mantle/util"
)

// Cluster is a local cluster of QEMU-based virtual machines.
//
// XXX: must be exported so that certain QEMU tests can access struct members
// through type assertions.
type Cluster struct {
*platform.BaseCluster
flight *flight

mu sync.Mutex
}

func (qc *Cluster) NewMachine(userdata *conf.UserData) (platform.Machine, error) {
return qc.NewMachineWithOptions(userdata, platform.MachineOptions{})
}

func (qc *Cluster) NewMachineWithOptions(userdata *conf.UserData, options platform.MachineOptions) (platform.Machine, error) {
return qc.NewMachineWithQemuOptions(userdata, platform.QemuMachineOptions{
MachineOptions: options,
})
}

func (qc *Cluster) NewMachineWithQemuOptions(userdata *conf.UserData, options platform.QemuMachineOptions) (platform.Machine, error) {
id := uuid.New()

dir := filepath.Join(qc.RuntimeConf().OutputDir, id)
if err := os.Mkdir(dir, 0777); err != nil {
return nil, err
}

// hacky solution for cloud config ip substitution
// NOTE: escaping is not supported
qc.mu.Lock()

conf, err := qc.RenderUserData(userdata, map[string]string{})
if err != nil {
qc.mu.Unlock()
return nil, err
}
qc.mu.Unlock()

var confPath string
if conf.IsIgnition() {
confPath = filepath.Join(dir, "ignition.json")
if err := conf.WriteFile(confPath); err != nil {
return nil, err
}
} else if conf.IsEmpty() {
} else {
return nil, fmt.Errorf("qemuiso only supports Ignition or empty configs")
}

journal, err := platform.NewJournal(dir)
if err != nil {
return nil, err
}

qm := &machine{
qc: qc,
id: id,
journal: journal,
consolePath: filepath.Join(dir, "console.txt"),
}

builder := platform.NewBuilder()
builder.ConfigFile = confPath
defer builder.Close()
builder.Uuid = qm.id
if qc.flight.opts.Firmware != "" {
builder.Firmware = qc.flight.opts.Firmware
}
builder.Hostname = fmt.Sprintf("qemu%d", qc.BaseCluster.AllocateMachineSerial())
builder.ConsoleToFile(qm.consolePath)

if qc.flight.opts.Memory != "" {
memory, err := strconv.ParseInt(qc.flight.opts.Memory, 10, 32)
if err != nil {
return nil, errors.Wrapf(err, "parsing memory option")
}
builder.Memory = int(memory)
}

builder.AddIso(qc.flight.opts.IsoPath, "")

for _, disk := range options.AdditionalDisks {
if err = builder.AddDisk(&platform.Disk{
Size: disk,
}); err != nil {
return nil, errors.Wrapf(err, "adding additional disk")
}
}

if len(options.HostForwardPorts) > 0 {
builder.EnableUsermodeNetworking(options.HostForwardPorts)
} else {
h := []platform.HostForwardPort{
{Service: "ssh", HostPort: 0, GuestPort: 22},
}
builder.EnableUsermodeNetworking(h)
}

inst, err := builder.Exec()
if err != nil {
return nil, err
}
qm.inst = *inst

err = util.Retry(6, 5*time.Second, func() error {
var err error
qm.ip, err = inst.SSHAddress()
if err != nil {
return err
}
return nil
})
if err != nil {
return nil, err
}

if err := platform.StartMachine(qm, qm.journal); err != nil {
qm.Destroy()
return nil, err
}

qc.AddMach(qm)

return qm, nil
}

func (qc *Cluster) Destroy() {
qc.BaseCluster.Destroy()
qc.flight.DelCluster(qc)
}
76 changes: 76 additions & 0 deletions mantle/platform/machine/qemuiso/flight.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2020 Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package qemuiso

import (
"github.com/coreos/pkg/capnslog"

"github.com/coreos/mantle/platform"
)

const (
Platform platform.Name = "qemu-iso"
)

// Options contains QEMU-specific options for the flight.
type Options struct {
// IsoPath is the full path to the ISO image to boot in QEMU
IsoPath string
Firmware string
Memory string

*platform.Options
}

type flight struct {
*platform.BaseFlight
opts *Options
}

var (
plog = capnslog.NewPackageLogger("github.com/coreos/mantle", "platform/machine/qemuiso")
)

func NewFlight(opts *Options) (platform.Flight, error) {
bf, err := platform.NewBaseFlight(opts.Options, Platform, "")
if err != nil {
return nil, err
}

qf := &flight{
BaseFlight: bf,
opts: opts,
}

return qf, nil
}

// NewCluster creates a Cluster instance, suitable for running virtual
// machines in QEMU.
func (qf *flight) NewCluster(rconf *platform.RuntimeConfig) (platform.Cluster, error) {
bc, err := platform.NewBaseCluster(qf.BaseFlight, rconf)
if err != nil {
return nil, err
}

qc := &Cluster{
BaseCluster: bc,
flight: qf,
}

qf.AddCluster(qc)

return qc, nil
}
Loading

0 comments on commit 3b9bbdc

Please sign in to comment.