Skip to content
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

Import LXD changes #237

Merged
merged 17 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
89ffac4
[lxd-import] client: Use io.Writer for Stdout/Stderr in InstanceExecArgs
monstermunchkin Nov 2, 2023
2660594
[lxd-import] btrfs: Add function to check subvolumes in a given path
monstermunchkin Nov 7, 2023
dce1e73
[lxd-import] btrfs: Use `hasSubvolumes` when creating a new pool
monstermunchkin Nov 7, 2023
26cda45
[lxd-import] test: Btrfs pool with a subvolume as its source
monstermunchkin Nov 3, 2023
21e00b4
[lxd-import] client: Use io.Reader for Stdin in InstanceExecArgs
monstermunchkin Nov 16, 2023
3bd1a05
[lxd-import] Makefile: remove toolchain directive from go.mod for bac…
simondeziel Nov 17, 2023
62f4550
Makefile: Use GO env variable everywhere
stgraber Nov 18, 2023
e33835d
[lxd-import] github: remove Go tip tarball after extraction
simondeziel Nov 17, 2023
f6e0381
[lxd-import] config: Fix `acme.ca_url` short description
monstermunchkin Nov 20, 2023
5abe5f8
[lxd-import] Update metadata
monstermunchkin Nov 20, 2023
290cf6d
[lxd-import] lxd/instance/drivers/driver_qemu: factor out config volu…
mihalicyn Nov 21, 2023
9eba466
[lxd-import] shared/instance: correct volatile.apply_nvram type
mihalicyn Nov 22, 2023
958dbce
[lxd-import] client/lxd/instances: Close websocket as soon as channel…
tomponline Nov 22, 2023
556c9ff
[lxd-import] lxc/exec: No need to use io.ReadCloser anymore
tomponline Nov 22, 2023
f9cce03
[lxd-import] shared/ws/mirror: No need for defer in MirrorWrite and M…
tomponline Nov 22, 2023
87f2e08
[lxd-import] Revert "lxd/instance/exec: Only use keepalives on TCP so…
tomponline Nov 22, 2023
13781d6
[lxd-import] client/lxd/instances: Consume ping messages from server …
tomponline Nov 22, 2023
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
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ jobs:

mkdir -p ~/sdk/gotip
tar -C ~/sdk/gotip -xzf gotip.tar.gz
rm gotip.tar.gz
~/sdk/gotip/bin/go version
echo "PATH=$HOME/go/bin:$HOME/sdk/gotip/bin/:$PATH" >> $GITHUB_ENV
if: matrix.go == 'tip'
Expand Down
9 changes: 5 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,10 @@ ifneq "$(INCUS_OFFLINE)" ""
exit 1
endif
$(GO) get -t -v -d -u ./...
go get github.com/mdlayher/socket@v0.4.1
go get github.com/openfga/go-sdk@v0.2.2
$(GO) get github.com/mdlayher/socket@v0.4.1
$(GO) get github.com/openfga/go-sdk@v0.2.2
$(GO) mod tidy --go=1.20
$(GO) get toolchain@none

cd cmd/lxd-to-incus && $(GO) get -t -v -d -u ./...
cd cmd/lxd-to-incus && $(GO) mod tidy --go=1.20
Expand Down Expand Up @@ -126,7 +127,7 @@ endif
.PHONY: update-metadata
update-metadata: build
@echo "Generating golang documentation metadata"
cd internal/server/config/generate && CGO_ENABLED=0 go build -o $(GOPATH)/bin/incus-doc
cd internal/server/config/generate && CGO_ENABLED=0 $(GO) build -o $(GOPATH)/bin/incus-doc
$(GOPATH)/bin/incus-doc . --json ./internal/server/metadata/configuration.json --txt ./doc/config_options.txt

.PHONY: doc-setup
Expand Down Expand Up @@ -301,7 +302,7 @@ endif
.PHONY: staticcheck
staticcheck:
ifeq ($(shell command -v staticcheck),)
(cd / ; go install -v -x honnef.co/go/tools/cmd/staticcheck@latest)
(cd / ; $(GO) install -v -x honnef.co/go/tools/cmd/staticcheck@latest)
endif
# To get advance notice of deprecated function usage, consider running:
# sed -i 's/^go 1\.[0-9]\+$/go 1.18/' go.mod
Expand Down
52 changes: 33 additions & 19 deletions client/incus_instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -1197,6 +1197,10 @@ func (r *ProtocolIncus) ExecInstance(instanceName string, exec api.InstanceExecP
return nil, err
}

go func() {
_, _, _ = conn.ReadMessage() // Consume pings from server.
}()

go args.Control(conn)
}

Expand Down Expand Up @@ -1236,10 +1240,16 @@ func (r *ProtocolIncus) ExecInstance(instanceName string, exec api.InstanceExecP
return nil, err
}

go func() {
_, _, _ = conn.ReadMessage() // Consume pings from server.
}()

conns = append(conns, conn)
dones[0] = ws.MirrorRead(conn, args.Stdin)
}

waitConns := 0 // Used for keeping track of when stdout and stderr have finished.

// Handle stdout
if fds["1"] != "" {
conn, err := r.GetOperationWebsocket(opAPI.ID, fds["1"])
Expand All @@ -1249,6 +1259,7 @@ func (r *ProtocolIncus) ExecInstance(instanceName string, exec api.InstanceExecP

conns = append(conns, conn)
dones[1] = ws.MirrorWrite(conn, args.Stdout)
waitConns++
}

// Handle stderr
Expand All @@ -1260,33 +1271,36 @@ func (r *ProtocolIncus) ExecInstance(instanceName string, exec api.InstanceExecP

conns = append(conns, conn)
dones[2] = ws.MirrorWrite(conn, args.Stderr)
waitConns++
}

// Wait for everything to be done
go func() {
for i, chDone := range dones {
// Skip stdin, dealing with it separately below
if i == 0 {
continue
for {
select {
case <-dones[0]:
// Handle stdin finish, but don't wait for it if output channels
// have all finished.
dones[0] = nil
_ = conns[0].Close()
case <-dones[1]:
dones[1] = nil
_ = conns[1].Close()
waitConns--
case <-dones[2]:
dones[2] = nil
_ = conns[2].Close()
waitConns--
}

<-chDone
}
if waitConns <= 0 {
// Close stdin websocket if defined and not already closed.
if dones[0] != nil {
conns[0].Close()
}

if fds["0"] != "" {
if args.Stdin != nil {
_ = args.Stdin.Close()
break
}

// Empty the stdin channel but don't block on it as
// stdin may be stuck in Read()
go func() {
<-dones[0]
}()
}

for _, conn := range conns {
_ = conn.Close()
}

if args.DataDone != nil {
Expand Down
6 changes: 3 additions & 3 deletions client/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -581,13 +581,13 @@ type InstanceConsoleLogArgs struct {
// The InstanceExecArgs struct is used to pass additional options during instance exec.
type InstanceExecArgs struct {
// Standard input
Stdin io.ReadCloser
Stdin io.Reader

// Standard output
Stdout io.WriteCloser
Stdout io.Writer

// Standard error
Stderr io.WriteCloser
Stderr io.Writer

// Control message handler (window resize, signals, ...)
Control func(conn *websocket.Conn)
Expand Down
4 changes: 2 additions & 2 deletions cmd/incus/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,10 @@ func (c *cmdExec) Run(cmd *cobra.Command, args []string) error {
}
}

var stdin io.ReadCloser
var stdin io.Reader
stdin = os.Stdin
if c.flagDisableStdin {
stdin = io.NopCloser(bytes.NewReader(nil))
stdin = bytes.NewReader(nil)
}

stdout := getStdout()
Expand Down
28 changes: 14 additions & 14 deletions cmd/incusd/instance_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,23 +103,23 @@ func (s *execWs) Connect(op *operations.Operation, r *http.Request, w http.Respo
if err != nil {
logger.Warn("Failed setting TCP timeouts on remote connection", logger.Ctx{"err": err})
}
}

// Start channel keep alive to run until channel is closed.
go func() {
pingInterval := time.Second * 10
t := time.NewTicker(pingInterval)
defer t.Stop()

for {
err := conn.WriteControl(websocket.PingMessage, []byte("keepalive"), time.Now().Add(5*time.Second))
if err != nil {
return
}
// Start channel keep alive to run until channel is closed.
go func() {
pingInterval := time.Second * 10
t := time.NewTicker(pingInterval)
defer t.Stop()

<-t.C
for {
err := conn.WriteControl(websocket.PingMessage, []byte("keepalive"), time.Now().Add(5*time.Second))
if err != nil {
return
}
}()
}

<-t.C
}
}()

if fd == execWSControl {
s.waitControlConnected.Cancel() // Control connection connected.
Expand Down
4 changes: 2 additions & 2 deletions doc/config_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -837,7 +837,7 @@ The original VLAN used when moving a VF into an instance.

```{config:option} volatile.apply_nvram instance-volatile
:shortdesc: "Whether to regenerate VM NVRAM the next time the instance starts"
:type: "string"
:type: "bool"

```

Expand Down Expand Up @@ -1284,7 +1284,7 @@ Specify the number of days after which the unused cached image expires.
```{config:option} acme.ca_url server-acme
:defaultdesc: "`https://acme-v02.api.letsencrypt.org/directory`"
:scope: "global"
:shortdesc: "Agree to ACME terms of service"
:shortdesc: "URL to the directory resource of the ACME service"
:type: "string"

```
Expand Down
2 changes: 1 addition & 1 deletion internal/instance/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -981,7 +981,7 @@ var InstanceConfigKeysVM = map[string]func(value string) error{
// gendoc:generate(entity=instance, group=volatile, key=volatile.apply_nvram)
//
// ---
// type: string
// type: bool
// shortdesc: Whether to regenerate VM NVRAM the next time the instance starts
"volatile.apply_nvram": validate.Optional(validate.IsBool),

Expand Down
2 changes: 1 addition & 1 deletion internal/server/cluster/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ var ConfigSchema = config.Schema{
// type: string
// scope: global
// defaultdesc: `https://acme-v02.api.letsencrypt.org/directory`
// shortdesc: Agree to ACME terms of service
// shortdesc: URL to the directory resource of the ACME service
"acme.ca_url": {},

// gendoc:generate(entity=server, group=acme, key=acme.domain)
Expand Down
23 changes: 15 additions & 8 deletions internal/server/instance/drivers/driver_qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -1933,15 +1933,9 @@ func (d *qemu) architectureSupportsUEFI(arch int) bool {
}

func (d *qemu) setupNvram() error {
d.logger.Debug("Generating NVRAM")

// Mount the instance's config volume.
_, err := d.mount()
if err != nil {
return err
}
var err error

defer func() { _ = d.unmount() }()
d.logger.Debug("Generating NVRAM")

// Cleanup existing variables.
for _, firmwares := range [][]ovmfFirmware{ovmfGenericFirmwares, ovmfSecurebootFirmwares, ovmfCSMFirmwares} {
Expand Down Expand Up @@ -5343,6 +5337,19 @@ func (d *qemu) Update(args db.InstanceArgs, userRequested bool) error {
}

if d.architectureSupportsUEFI(d.architecture) && (util.ValueInSlice("security.secureboot", changedConfig) || util.ValueInSlice("security.csm", changedConfig)) {
// setupNvram() requires instance's config volume to be mounted.
// The easiest way to detect that is to check if instance is running.
// TODO: extend storage API to be able to check if volume is already mounted?
if !isRunning {
// Mount the instance's config volume.
_, err := d.mount()
if err != nil {
return err
}

defer func() { _ = d.unmount() }()
}

// Re-generate the NVRAM.
err = d.setupNvram()
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions internal/server/metadata/configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@
"volatile.apply_nvram": {
"longdesc": "",
"shortdesc": "Whether to regenerate VM NVRAM the next time the instance starts",
"type": "string"
"type": "bool"
}
},
{
Expand Down Expand Up @@ -1398,7 +1398,7 @@
"defaultdesc": "`https://acme-v02.api.letsencrypt.org/directory`",
"longdesc": "",
"scope": "global",
"shortdesc": "Agree to ACME terms of service",
"shortdesc": "URL to the directory resource of the ACME service",
"type": "string"
}
},
Expand Down
4 changes: 2 additions & 2 deletions internal/server/storage/drivers/driver_btrfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,13 +198,13 @@ func (d *btrfs) Create() error {
hostPath := d.config["source"]
if d.isSubvolume(hostPath) {
// Existing btrfs subvolume.
subvols, err := d.getSubvolumes(hostPath)
hasSubvolumes, err := d.hasSubvolumes(hostPath)
if err != nil {
return fmt.Errorf("Could not determine if existing btrfs subvolume is empty: %w", err)
}

// Check that the provided subvolume is empty.
if len(subvols) > 0 {
if hasSubvolumes {
return fmt.Errorf("Requested btrfs subvolume exists but is not empty")
}
} else {
Expand Down
11 changes: 11 additions & 0 deletions internal/server/storage/drivers/driver_btrfs_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,17 @@ func (d *btrfs) isSubvolume(path string) bool {
return true
}

func (d *btrfs) hasSubvolumes(path string) (bool, error) {
var stdout strings.Builder

err := subprocess.RunCommandWithFds(d.state.ShutdownCtx, nil, &stdout, "btrfs", "subvolume", "list", "-o", path)
if err != nil {
return false, err
}

return stdout.Len() > 0, nil
}

func (d *btrfs) getSubvolumes(path string) ([]string, error) {
poolMountPath := GetPoolMountPath(d.name)
if !strings.HasPrefix(path, poolMountPath+"/") {
Expand Down
13 changes: 6 additions & 7 deletions shared/ws/mirror.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ func Mirror(conn *websocket.Conn, rwc io.ReadWriteCloser) (chan error, chan erro
return chRead, chWrite
}

// MirrorRead is a uni-directional mirror which replicates an io.ReadCloser to a websocket.
func MirrorRead(conn *websocket.Conn, rc io.ReadCloser) chan error {
// MirrorRead is a uni-directional mirror which replicates an io.Reader to a websocket.
func MirrorRead(conn *websocket.Conn, rc io.Reader) chan error {
chDone := make(chan error, 1)
if rc == nil {
close(chDone)
Expand All @@ -30,8 +30,6 @@ func MirrorRead(conn *websocket.Conn, rc io.ReadCloser) chan error {
connRWC := NewWrapper(conn)

go func() {
defer close(chDone)

_, err := io.Copy(connRWC, rc)

logger.Debug("Websocket: Stopped read mirror", logger.Ctx{"address": conn.RemoteAddr().String(), "err": err})
Expand All @@ -40,13 +38,14 @@ func MirrorRead(conn *websocket.Conn, rc io.ReadCloser) chan error {
connRWC.Close()

chDone <- err
close(chDone)
}()

return chDone
}

// MirrorWrite is a uni-directional mirror which replicates a websocket to an io.WriteCloser.
func MirrorWrite(conn *websocket.Conn, wc io.WriteCloser) chan error {
// MirrorWrite is a uni-directional mirror which replicates a websocket to an io.Writer.
func MirrorWrite(conn *websocket.Conn, wc io.Writer) chan error {
chDone := make(chan error, 1)
if wc == nil {
close(chDone)
Expand All @@ -58,11 +57,11 @@ func MirrorWrite(conn *websocket.Conn, wc io.WriteCloser) chan error {
connRWC := NewWrapper(conn)

go func() {
defer close(chDone)
_, err := io.Copy(wc, connRWC)

logger.Debug("Websocket: Stopped write mirror", logger.Ctx{"address": conn.RemoteAddr().String(), "err": err})
chDone <- err
close(chDone)
}()

return chDone
Expand Down
Loading
Loading