Skip to content

Commit

Permalink
kola: implement brightbox platform
Browse files Browse the repository at this point in the history
Signed-off-by: Mathieu Tortuyaux <mtortuyaux@microsoft.com>
  • Loading branch information
tormath1 committed Nov 15, 2023
1 parent 955a0f7 commit 3f1dc1c
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 1 deletion.
8 changes: 7 additions & 1 deletion cmd/kola/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ var (
kolaOffering string
defaultTargetBoard = sdk.DefaultBoard()
kolaArchitectures = []string{"amd64"}
kolaPlatforms = []string{"aws", "azure", "do", "esx", "external", "gce", "openstack", "equinixmetal", "qemu", "qemu-unpriv"}
kolaPlatforms = []string{"aws", "azure", "brightbox", "do", "esx", "external", "gce", "openstack", "equinixmetal", "qemu", "qemu-unpriv"}
kolaDistros = []string{"cl", "fcos", "rhcos"}
kolaChannels = []string{"alpha", "beta", "stable", "edge", "lts"}
kolaOfferings = []string{"basic", "pro"}
Expand Down Expand Up @@ -227,6 +227,11 @@ func init() {
sv(&kola.QEMUOptions.BIOSImage, "qemu-bios", "", "BIOS to use for QEMU vm")
bv(&kola.QEMUOptions.UseVanillaImage, "qemu-skip-mangle", false, "don't modify CL disk image to capture console log")
sv(&kola.QEMUOptions.ExtraBaseDiskSize, "qemu-grow-base-disk-by", "", "grow base disk by the given size in bytes, following optional 1024-based suffixes are allowed: b (ignored), k, K, M, G, T")

// BrightBox specific options
sv(&kola.BrightboxOptions.ClientID, "brightbox-client-id", "", "Brightbox client ID")
sv(&kola.BrightboxOptions.ClientSecret, "brightbox-client-secret", "", "Brightbox client secret")
sv(&kola.BrightboxOptions.Image, "brightbox-image", "", "Brightbox image ref")
}

// Sync up the command line options if there is dependency
Expand All @@ -245,6 +250,7 @@ func syncOptions() error {
kola.AWSOptions.Board = board
kola.EquinixMetalOptions.Board = board
kola.EquinixMetalOptions.GSOptions = &kola.GCEOptions
kola.BrightboxOptions.Board = board

validateOption := func(name, item string, valid []string) error {
for _, v := range valid {
Expand Down
5 changes: 5 additions & 0 deletions kola/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
"github.com/flatcar/mantle/platform"
awsapi "github.com/flatcar/mantle/platform/api/aws"
azureapi "github.com/flatcar/mantle/platform/api/azure"
brightboxapi "github.com/flatcar/mantle/platform/api/brightbox"
doapi "github.com/flatcar/mantle/platform/api/do"
equinixmetalapi "github.com/flatcar/mantle/platform/api/equinixmetal"
esxapi "github.com/flatcar/mantle/platform/api/esx"
Expand All @@ -48,6 +49,7 @@ import (
"github.com/flatcar/mantle/platform/conf"
"github.com/flatcar/mantle/platform/machine/aws"
"github.com/flatcar/mantle/platform/machine/azure"
"github.com/flatcar/mantle/platform/machine/brightbox"
"github.com/flatcar/mantle/platform/machine/do"
"github.com/flatcar/mantle/platform/machine/equinixmetal"
"github.com/flatcar/mantle/platform/machine/esx"
Expand All @@ -65,6 +67,7 @@ var (
Options = platform.Options{}
AWSOptions = awsapi.Options{Options: &Options} // glue to set platform options from main
AzureOptions = azureapi.Options{Options: &Options} // glue to set platform options from main
BrightboxOptions = brightboxapi.Options{Options: &Options} // glue to set platform options from main
DOOptions = doapi.Options{Options: &Options} // glue to set platform options from main
ESXOptions = esxapi.Options{Options: &Options} // glue to set platform options from main
ExternalOptions = external.Options{Options: &Options} // glue to set platform options from main
Expand Down Expand Up @@ -225,6 +228,8 @@ func NewFlight(pltfrm string) (flight platform.Flight, err error) {
flight, err = aws.NewFlight(&AWSOptions)
case "azure":
flight, err = azure.NewFlight(&AzureOptions)
case "brightbox":
flight, err = brightbox.NewFlight(&BrightboxOptions)
case "do":
flight, err = do.NewFlight(&DOOptions)
case "esx":
Expand Down
80 changes: 80 additions & 0 deletions platform/machine/brightbox/cluster.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright The Mantle Authors.
// SPDX-License-Identifier: Apache-2.0

package brightbox

import (
"crypto/rand"
"fmt"
"os"
"path/filepath"

"github.com/flatcar/mantle/platform"
"github.com/flatcar/mantle/platform/conf"
)

type cluster struct {
*platform.BaseCluster
flight *flight
}

func (bc *cluster) NewMachine(userdata *conf.UserData) (platform.Machine, error) {
conf, err := bc.RenderUserData(userdata, map[string]string{
"$public_ipv4": "${COREOS_OPENSTACK_IPV4_PUBLIC}",
"$private_ipv4": "${COREOS_OPENSTACK_IPV4_LOCAL}",
})
if err != nil {
return nil, err
}

var keyname string
if !bc.RuntimeConf().NoSSHKeyInMetadata {
keyname = bc.flight.Name()
}
instance, err := bc.flight.api.CreateServer(bc.vmname(), keyname, conf.String())
if err != nil {
return nil, err
}

mach := &machine{
cluster: bc,
mach: instance,
}

mach.dir = filepath.Join(bc.RuntimeConf().OutputDir, mach.ID())
if err := os.Mkdir(mach.dir, 0777); err != nil {
mach.Destroy()
return nil, err
}

confPath := filepath.Join(mach.dir, "user-data")
if err := conf.WriteFile(confPath); err != nil {
mach.Destroy()
return nil, err
}

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

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

bc.AddMach(mach)

return mach, nil
}

func (bc *cluster) vmname() string {
b := make([]byte, 5)
rand.Read(b)
return fmt.Sprintf("%s-%x", bc.Name()[0:13], b)
}

func (bc *cluster) Destroy() {
bc.BaseCluster.Destroy()
bc.flight.DelCluster(bc)
}
68 changes: 68 additions & 0 deletions platform/machine/brightbox/flight.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright The Mantle Authors.
// SPDX-License-Identifier: Apache-2.0

package brightbox

import (
"fmt"

"github.com/coreos/pkg/capnslog"
ctplatform "github.com/flatcar/container-linux-config-transpiler/config/platform"

"github.com/flatcar/mantle/platform"
"github.com/flatcar/mantle/platform/api/brightbox"
)

const (
Platform platform.Name = "brightbox"
)

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

type flight struct {
*platform.BaseFlight
api *brightbox.API
}

func NewFlight(opts *brightbox.Options) (platform.Flight, error) {
api, err := brightbox.New(opts)
if err != nil {
return nil, fmt.Errorf("creating brightbox API client: %w", err)
}

base, err := platform.NewBaseFlight(opts.Options, Platform, ctplatform.OpenStackMetadata)
if err != nil {
return nil, fmt.Errorf("creating base flight: %w", err)
}

bf := &flight{
BaseFlight: base,
api: api,
}

return bf, nil
}

// NewCluster creates an instance of a Cluster suitable for spawning
// instances on the OpenStack platform.
func (bf *flight) NewCluster(rconf *platform.RuntimeConfig) (platform.Cluster, error) {
bc, err := platform.NewBaseCluster(bf.BaseFlight, rconf)
if err != nil {
return nil, fmt.Errorf("creating brightbox base cluster: %w", err)
}

c := &cluster{
BaseCluster: bc,
flight: bf,
}

bf.AddCluster(c)

return c, nil
}

func (bf *flight) Destroy() {
bf.BaseFlight.Destroy()
}
117 changes: 117 additions & 0 deletions platform/machine/brightbox/machine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright The Mantle Authors.
// SPDX-License-Identifier: Apache-2.0
package brightbox

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

"golang.org/x/crypto/ssh"

"github.com/flatcar/mantle/platform"
"github.com/flatcar/mantle/platform/api/brightbox"
)

type machine struct {
cluster *cluster
mach *brightbox.Server
dir string
journal *platform.Journal
console string
}

// ID returns the ID of the machine.
func (bm *machine) ID() string {
return bm.mach.Server.ID
}

// IP returns the IP of the machine.
// The machine should only get one "cloud" IP.
func (bm *machine) IP() string {
if bm.mach.Server != nil && len(bm.mach.Server.CloudIPs) >= 0 {
return bm.mach.Server.CloudIPs[0].PublicIPv4
}

return ""
}

func (bm *machine) PrivateIP() string {
// TODO: We can see a Private IP from the dasbhboard but I don't know how to get it
// from the API.
return ""
}

func (bm *machine) RuntimeConf() *platform.RuntimeConfig {
return bm.cluster.RuntimeConf()
}

func (bm *machine) SSHClient() (*ssh.Client, error) {
return bm.cluster.SSHClient(bm.IP())
}

func (bm *machine) PasswordSSHClient(user string, password string) (*ssh.Client, error) {
return bm.cluster.PasswordSSHClient(bm.IP(), user, password)
}

func (bm *machine) SSH(cmd string) ([]byte, []byte, error) {
return bm.cluster.SSH(bm, cmd)
}

func (bm *machine) Reboot() error {
return platform.RebootMachine(bm, bm.journal)
}

func (bm *machine) Destroy() {
if err := bm.saveConsole(); err != nil {
plog.Errorf("Error saving console for instance %v: %v", bm.ID(), err)
}

if err := bm.cluster.flight.api.DeleteServer(bm.ID()); err != nil {
plog.Errorf("deleting server %v: %v", bm.ID(), err)
}

if bm.journal != nil {
bm.journal.Destroy()
}

bm.cluster.DelMach(bm)
}

func (bm *machine) ConsoleOutput() string {
return bm.console
}

func (bm *machine) saveConsole() error {
var err error
bm.console, err = bm.cluster.flight.api.GetConsoleOutput(bm.ID())
if err != nil {
return fmt.Errorf("Error retrieving console log for %v: %v", bm.ID(), err)
}

path := filepath.Join(bm.dir, "console.txt")
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return err
}
defer f.Close()
f.WriteString(bm.console)

return nil
}

func (bm *machine) JournalOutput() string {
if bm.journal == nil {
return ""
}

data, err := bm.journal.Read()
if err != nil {
plog.Errorf("Reading journal for instance %v: %v", bm.ID(), err)
}
return string(data)
}

func (bm *machine) Board() string {
return bm.cluster.flight.Options().Board
}

0 comments on commit 3f1dc1c

Please sign in to comment.