Skip to content

Commit

Permalink
Stop command for stopping VMs (#236)
Browse files Browse the repository at this point in the history
  • Loading branch information
outofforest committed Jun 30, 2023
1 parent 1f3e427 commit 3e1dbfd
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 12 deletions.
3 changes: 2 additions & 1 deletion build/osman-autostart.service → build/osman.service
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
[Unit]
Description=Osman starts VM tagged with auto
Description=Osman starts and stops VMs
Requires=virtqemud.service virtnetworkd.service virtqemud-admin.socket
After=virtqemud.service virtnetworkd.service virtqemud-admin.socket

[Service]
Type=oneshot
Environment="HOME=/root"
ExecStart=/bin/sh -c "/usr/bin/osman drop --type=vm --all && /usr/bin/osman start :auto"
ExecStop=/bin/sh -c "/usr/bin/osman stop --all && /usr/bin/osman drop --type=vm --all"
RemainAfterExit=true

[Install]
Expand Down
6 changes: 3 additions & 3 deletions build/osman.spec
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Summary: Tool to manage OS images
URL: https://github.com/outofforest/osman
License: MIT

Requires: zfs
Requires: zfs libvirt

%description
Tool to manage OS images
Expand All @@ -20,10 +20,10 @@ mkdir -p %{buildroot}/usr/bin
mkdir -p %{buildroot}/usr/local/lib/systemd/system

cp ./bin/osman-app %{buildroot}/usr/bin/osman
cp ./build/osman-autostart.service %{buildroot}/usr/local/lib/systemd/system/osman-autostart.service
cp ./build/osman.service %{buildroot}/usr/local/lib/systemd/system/osman.service

%files
/usr/bin/osman
/usr/local/lib/systemd/system/osman-autostart.service
/usr/local/lib/systemd/system/osman.service

%post
1 change: 1 addition & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func iocBuilder(c *ioc.Container) {
c.SingletonNamed("build", commands.NewBuildCommand)
c.SingletonNamed("mount", commands.NewMountCommand)
c.SingletonNamed("start", commands.NewStartCommand)
c.SingletonNamed("stop", commands.NewStopCommand)
c.SingletonNamed("list", commands.NewListCommand)
c.SingletonNamed("drop", commands.NewDropCommand)
c.SingletonNamed("tag", commands.NewTagCommand)
Expand Down
6 changes: 3 additions & 3 deletions commands/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ func NewStartCommand(cmdF *CmdFactory) *cobra.Command {
startF := &config.StartFactory{}

cmd := &cobra.Command{
Short: "Starts VM",
Args: cobra.RangeArgs(1, 2),
Use: "start [flags] image [name][:tag]",
Short: "Starts VMs",
Args: cobra.MinimumNArgs(1),
Use: "start [flags] [name][:tag]",
RunE: cmdF.Cmd(func(c *ioc.Container) {
c.Singleton(storageF.Config)
c.Singleton(filterF.Config)
Expand Down
54 changes: 54 additions & 0 deletions commands/stop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package commands

import (
"fmt"

"github.com/outofforest/ioc/v2"
"github.com/pkg/errors"
"github.com/spf13/cobra"

"github.com/outofforest/osman"
"github.com/outofforest/osman/config"
"github.com/outofforest/osman/infra/format"
)

// NewStopCommand creates new stop command
func NewStopCommand(cmdF *CmdFactory) *cobra.Command {
var storageF *config.StorageFactory
var filterF *config.FilterFactory
var formatF *config.FormatFactory
stopF := &config.StopFactory{}

cmd := &cobra.Command{
Short: "Stops VMs",
Use: "stop [flags] [name][:tag]",
RunE: cmdF.Cmd(func(c *ioc.Container) {
c.Singleton(storageF.Config)
c.Singleton(filterF.Config)
c.Singleton(formatF.Config)
c.Singleton(stopF.Config)
}, func(c *ioc.Container, formatter format.Formatter) error {
var results []osman.Result
var err error
c.Call(osman.Stop, &results, &err)
if err != nil {
return err
}
err = nil
for _, r := range results {
if r.Result != nil {
err = errors.New("some stops failed")
break
}
}
fmt.Println(formatter.Format(results))
return nil
}),
}
storageF = cmdF.AddStorageFlags(cmd)
filterF = cmdF.AddFilterFlags(cmd, []string{config.BuildTypeVM})
formatF = cmdF.AddFormatFlags(cmd)
cmd.Flags().StringVar(&stopF.LibvirtAddr, "libvirt-addr", "unix:///var/run/libvirt/libvirt-sock", "Address libvirt listens on")
cmd.Flags().BoolVar(&stopF.All, "all", false, "It is required to set this flag to stop builds if no filters are provided")
return cmd
}
2 changes: 1 addition & 1 deletion config/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type StartFactory struct {
}

// Config returns new start config
func (f *StartFactory) Config(args Args) Start {
func (f *StartFactory) Config() Start {
config := Start{
XMLDir: f.XMLDir,
VolumeDir: f.VolumeDir,
Expand Down
28 changes: 28 additions & 0 deletions config/stop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package config

// StopFactory collects data for stop config
type StopFactory struct {
// If no filter is provided it is required to set this flag to stop builds
All bool

// LibvirtAddr is the address libvirt listens on
LibvirtAddr string
}

// Config returns new stop config
func (f *StopFactory) Config() Stop {
config := Stop{
All: f.All,
LibvirtAddr: f.LibvirtAddr,
}
return config
}

// Stop stores configuration for stop command
type Stop struct {
// If no filter is provided it is required to set this flag to stop builds
All bool

// LibvirtAddr is the address libvirt listens on
LibvirtAddr string
}
26 changes: 24 additions & 2 deletions functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func Mount(ctx context.Context, storage config.Storage, filtering config.Filter,
return mounts, nil
}

// Start starts VM
// Start starts VMs
func Start(ctx context.Context, storage config.Storage, filtering config.Filter, start config.Start, s storage.Driver) ([]types.BuildInfo, error) {
for i, key := range filtering.BuildKeys {
if key.Tag == "" {
Expand Down Expand Up @@ -153,6 +153,28 @@ func Start(ctx context.Context, storage config.Storage, filtering config.Filter,
return vms, nil
}

// Stop stops VMs
func Stop(ctx context.Context, filtering config.Filter, stop config.Stop, s storage.Driver) ([]Result, error) {
if !stop.All && len(filtering.BuildIDs) == 0 && len(filtering.BuildKeys) == 0 {
return nil, errors.New("neither filters are provided nor --all is set")
}

builds, err := List(ctx, filtering, s)
if err != nil {
return nil, err
}

l, err := libvirtConn(stop.LibvirtAddr)
if err != nil {
return nil, err
}
defer func() {
_ = l.Disconnect()
}()

return stopVMs(ctx, l, builds)
}

// List lists builds
func List(ctx context.Context, filtering config.Filter, s storage.Driver) ([]types.BuildInfo, error) {
buildTypes := map[types.BuildType]bool{}
Expand Down Expand Up @@ -203,7 +225,7 @@ type Result struct {
// Drop drops builds
func Drop(ctx context.Context, storage config.Storage, filtering config.Filter, drop config.Drop, s storage.Driver) ([]Result, error) {
if !drop.All && len(filtering.BuildIDs) == 0 && len(filtering.BuildKeys) == 0 {
return nil, errors.New("neither filters are provided nor All is set")
return nil, errors.New("neither filters are provided nor --all is set")
}

builds, err := List(ctx, filtering, s)
Expand Down
97 changes: 95 additions & 2 deletions vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,9 @@ func undeployVMs(ctx context.Context, l *libvirt.Libvirt, vmsToDelete map[types.
spawn(string(buildID), parallel.Continue, func(ctx context.Context) error {
active, err := l.DomainIsActive(d)
if err != nil {
if libvirt.IsNotFound(err) {
return nil
}
return errors.WithStack(err)
}

Expand All @@ -1082,11 +1085,10 @@ func undeployVMs(ctx context.Context, l *libvirt.Libvirt, vmsToDelete map[types.
mu.Lock()
defer mu.Unlock()

results[buildID] = err
if err == nil || libvirt.IsNotFound(err) {
deletedVMs[buildID] = domainDocsByUUID[d.UUID]
delete(domainDocsByUUID, d.UUID)
} else {
results[buildID] = err
}

return nil
Expand All @@ -1106,6 +1108,97 @@ func undeployVMs(ctx context.Context, l *libvirt.Libvirt, vmsToDelete map[types.
return results, nil
}

func stopVMs(ctx context.Context, l *libvirt.Libvirt, vmsToStop []types.BuildInfo) ([]Result, error) {
domains, _, err := l.ConnectListAllDomains(1, libvirt.ConnectListDomainsActive|libvirt.ConnectListDomainsInactive)
if err != nil {
return nil, errors.WithStack(err)
}

domainsByBuildID := map[types.BuildID]libvirt.Domain{}
for _, d := range domains {
domainXML, err := l.DomainGetXMLDesc(d, 0)
if err != nil {
return nil, errors.WithStack(err)
}

var domainDoc libvirtxml.Domain
if err := domainDoc.Unmarshal(domainXML); err != nil {
return nil, errors.WithStack(err)
}

meta, err := parseMetadata(domainDoc)
if err != nil {
return nil, err
}

if meta.BuildID != "" {
domainsByBuildID[meta.BuildID] = d
}
}

mu := sync.Mutex{}
results := make([]Result, 0, len(vmsToStop))
err = parallel.Run(ctx, func(ctx context.Context, spawn parallel.SpawnFn) error {
for _, build := range vmsToStop {
domain, exists := domainsByBuildID[build.BuildID]
if !exists {
continue
}

buildID := build.BuildID
spawn(string(buildID), parallel.Continue, func(ctx context.Context) error {
active, err := l.DomainIsActive(domain)
if err != nil {
if libvirt.IsNotFound(err) {
return nil
}
return errors.WithStack(err)
}
if active == 0 {
return nil
}

err = l.DomainShutdown(domain)

mu.Lock()
defer mu.Unlock()

results = append(results, Result{
BuildID: buildID,
Result: err,
})

for {
select {
case <-ctx.Done():
return errors.WithStack(ctx.Err())
case <-time.After(time.Second):
}

active, err := l.DomainIsActive(domain)
if err != nil {
if libvirt.IsNotFound(err) {
return nil
}
return errors.WithStack(err)
}
if active == 0 {
return nil
}
}
})
}

return nil
})

if err != nil {
return nil, err
}

return results, nil
}

type vmToDeploy struct {
Image types.BuildInfo
Mount types.BuildInfo
Expand Down

0 comments on commit 3e1dbfd

Please sign in to comment.