Skip to content

Add a qemu-iso platform #1571

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

Merged
merged 1 commit into from
Jul 13, 2020
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
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 @@ -218,6 +220,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