diff --git a/README.md b/README.md index 2870ff4..203aade 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ sudo apt update sudo apt install qemu-kvm ``` -Create `img` directory in `cmd/manager`. +Create `img` directory in `cmd/manager`. Create `tmp` directory in `cmd/manager`. ### focal-server-cloudimg-amd64.img @@ -50,8 +50,7 @@ MANAGER_QEMU_OVMF_CODE_FILE=/usr/share/OVMF/OVMF_CODE.fd sudo find / -name OVMF_VARS.fd # => /usr/share/OVMF/OVMF_VARS.fd -cp /usr/share/OVMF/OVMF_VARS.fd . -MANAGER_QEMU_OVMF_VARS_FILE=img/OVMF_VARS.fd +MANAGER_QEMU_OVMF_VARS_FILE=/usr/share/OVMF/OVMF_VARS.fd ``` ## Run @@ -74,10 +73,14 @@ Manager will start an HTTP server on port `9021`, and a gRPC server on port `700 ### Create QEMU virtual machine (VM) +To create an instance of VM, run + ```sh curl -sSi -X GET http://localhost:9021/qemu ``` +You should be able to create multiple instances by reruning the command. + ### Verifying VM launch NB: To verify that the manager successfully launched the VM, you need to open two terminals on the same machine. In one terminal, you need to launch `go run main.go` (with the environment variables of choice) and in the other, you can run the verification commands. diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 73048ca..197ec52 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -111,12 +111,6 @@ func main() { logger.Fatal(fmt.Sprintf("failed to load %s QEMU configuration : %s", svcName, err)) } - // exe, args, err := qemu.ExecutableAndArgs(qemuCfg) - // if err != nil { - // logger.Fatal(fmt.Sprintf("failed to generate executable and arguments: %v", err)) - // } - // logger.Info(fmt.Sprintf("%s %s", exe, strings.Join(args, " "))) - //SVC svc := newService(libvirtConn, agentClient, logger, tracer, qemuCfg) @@ -151,6 +145,11 @@ func main() { if err := g.Wait(); err != nil { logger.Error(fmt.Sprintf("%s service terminated: %s", svcName, err)) } + + err = internal.DeleteFilesInDir("tmp/") + if err != nil { + logger.Error(fmt.Sprintf("%s", err)) + } } func newService(libvirtConn *libvirt.Libvirt, agent agent.AgentServiceClient, logger logger.Logger, tracer trace.Tracer, qemuCfg qemu.Config) manager.Service { diff --git a/internal/file.go b/internal/file.go new file mode 100644 index 0000000..ab7b9fa --- /dev/null +++ b/internal/file.go @@ -0,0 +1,44 @@ +package internal + +import ( + "io" + "os" + "path/filepath" +) + +func CopyFile(srcPath, dstPath string) error { + src, err := os.Open(srcPath) + if err != nil { + return err + } + defer src.Close() + + dst, err := os.Create(dstPath) + if err != nil { + return err + } + defer dst.Close() + + _, err = io.Copy(dst, src) + if err != nil { + return err + } + + return nil +} + +func DeleteFilesInDir(dirPath string) error { + files, err := filepath.Glob(filepath.Join(dirPath, "*")) + if err != nil { + return err + } + + for _, file := range files { + err := os.Remove(file) + if err != nil { + return err + } + } + + return nil +} diff --git a/manager/qemu/config.go b/manager/qemu/config.go index 2d37af6..8b00fc4 100644 --- a/manager/qemu/config.go +++ b/manager/qemu/config.go @@ -20,17 +20,17 @@ type OVMFVarsConfig struct { If string `env:"OVMF_VARS_IF" envDefault:"pflash"` Format string `env:"OVMF_VARS_FORMAT" envDefault:"raw"` Unit int `env:"OVMF_VARS_UNIT" envDefault:"1"` - File string `env:"OVMF_VARS_FILE" envDefault:"img/OVMF_VARS.fd"` + File string `env:"OVMF_VARS_FILE" envDefault:"/usr/share/OVMF/OVMF_VARS.fd"` } type NetDevConfig struct { ID string `env:"NETDEV_ID" envDefault:"vmnic"` - HostFwd1 string `env:"HOST_FWD_1" envDefault:"2222"` - GuestFwd1 string `env:"GUEST_FWD_1" envDefault:"22"` - HostFwd2 string `env:"HOST_FWD_2" envDefault:"9301"` - GuestFwd2 string `env:"GUEST_FWD_2" envDefault:"9031"` - HostFwd3 string `env:"HOST_FWD_3" envDefault:"7020"` - GuestFwd3 string `env:"GUEST_FWD_3" envDefault:"7002"` + HostFwd1 int `env:"HOST_FWD_1" envDefault:"2222"` + GuestFwd1 int `env:"GUEST_FWD_1" envDefault:"22"` + HostFwd2 int `env:"HOST_FWD_2" envDefault:"9301"` + GuestFwd2 int `env:"GUEST_FWD_2" envDefault:"9031"` + HostFwd3 int `env:"HOST_FWD_3" envDefault:"7020"` + GuestFwd3 int `env:"GUEST_FWD_3" envDefault:"7002"` } type VirtioNetPciConfig struct { @@ -150,7 +150,7 @@ func constructQemuArgs(config Config) []string { // network args = append(args, "-netdev", - fmt.Sprintf("user,id=%s,hostfwd=tcp::%s-:%s,hostfwd=tcp::%s-:%s,hostfwd=tcp::%s-:%s", + fmt.Sprintf("user,id=%s,hostfwd=tcp::%d-:%d,hostfwd=tcp::%d-:%d,hostfwd=tcp::%d-:%d", config.NetDevConfig.ID, config.NetDevConfig.HostFwd1, config.NetDevConfig.GuestFwd1, config.NetDevConfig.HostFwd2, config.NetDevConfig.GuestFwd2, diff --git a/manager/service.go b/manager/service.go index 1d1f9e8..9faa1ad 100644 --- a/manager/service.go +++ b/manager/service.go @@ -13,11 +13,15 @@ import ( "github.com/digitalocean/go-libvirt" "github.com/ultravioletrs/agent/agent" + "github.com/ultravioletrs/manager/internal" "github.com/ultravioletrs/manager/manager/qemu" "github.com/gofrs/uuid" ) +const firmwareVars = "OVMF_VARS" +const qcow2Img = "focal-server-cloudimg-amd64" + var ( // ErrMalformedEntity indicates malformed entity specification (e.g. // invalid username or password). @@ -89,6 +93,7 @@ func (ms *managerService) CreateLibvirtDomain(ctx context.Context, poolXML, volX } func (ms *managerService) CreateQemuVM(ctx context.Context) (*exec.Cmd, error) { + // create unique emu device identifiers id, err := uuid.NewV4() if err != nil { return &exec.Cmd{}, err @@ -99,15 +104,38 @@ func (ms *managerService) CreateQemuVM(ctx context.Context) (*exec.Cmd, error) { qemuCfg.VirtioScsiPciConfig.ID = fmt.Sprintf("%s-%s", qemuCfg.VirtioScsiPciConfig.ID, id) qemuCfg.SevConfig.ID = fmt.Sprintf("%s-%s", qemuCfg.SevConfig.ID, id) - exe, args, err := qemu.ExecutableAndArgs(qemuCfg) + // copy firmware vars file + srcFile := qemuCfg.OVMFVarsConfig.File + dstFile := fmt.Sprintf("tmp/%s-%s.fd", firmwareVars, id) + err = internal.CopyFile(srcFile, dstFile) + if err != nil { + return &exec.Cmd{}, err + } + qemuCfg.OVMFVarsConfig.File = dstFile + + // copy qcow2 img file + srcFile = qemuCfg.DiskImgConfig.File + dstFile = fmt.Sprintf("tmp/%s-%s.img", qcow2Img, id) + err = internal.CopyFile(srcFile, dstFile) if err != nil { return &exec.Cmd{}, err } + qemuCfg.DiskImgConfig.File = dstFile + exe, args, err := qemu.ExecutableAndArgs(qemuCfg) + if err != nil { + return &exec.Cmd{}, err + } cmd, err := qemu.RunQemuVM(exe, args) if err != nil { return cmd, err } + + // different VM guests can't forward ports to the same ports on the same host + ms.qemuCfg.NetDevConfig.HostFwd1++ + ms.qemuCfg.NetDevConfig.HostFwd2++ + ms.qemuCfg.NetDevConfig.HostFwd3++ + return cmd, nil }