Skip to content

Commit

Permalink
Assign ssh.localPort automatically to an available port
Browse files Browse the repository at this point in the history
- The hostagent now speaks REST API over `ha.sock` to provide the port information.
  See `pkg/hostagent/api`.

- For backward compatibility, the "default" instance uses port 60022 by default.

Close issue 131

Signed-off-by: Akihiro Suda <[email protected]>
  • Loading branch information
AkihiroSuda committed Oct 5, 2021
1 parent 1b260c2 commit e5da6ee
Show file tree
Hide file tree
Showing 29 changed files with 286 additions and 77 deletions.
35 changes: 35 additions & 0 deletions cmd/limactl/hostagent.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ import (
"errors"
"fmt"
"io"
"net"
"net/http"
"os"
"os/signal"
"strconv"

"github.com/gorilla/mux"
"github.com/lima-vm/lima/pkg/hostagent"
"github.com/lima-vm/lima/pkg/hostagent/api/server"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

Expand All @@ -21,6 +26,7 @@ func newHostagentCommand() *cobra.Command {
Hidden: true,
}
hostagentCommand.Flags().StringP("pidfile", "p", "", "write pid to file")
hostagentCommand.Flags().String("socket", "", "hostagent socket")
return hostagentCommand
}

Expand All @@ -38,6 +44,13 @@ func hostagentAction(cmd *cobra.Command, args []string) error {
}
defer os.RemoveAll(pidfile)
}
socket, err := cmd.Flags().GetString("socket")
if err != nil {
return err
}
if socket == "" {
return fmt.Errorf("socket must be specified (limactl version mismatch?)")
}

instName := args[0]

Expand All @@ -51,6 +64,28 @@ func hostagentAction(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}

backend := &server.Backend{
Agent: ha,
}
r := mux.NewRouter()
server.AddRoutes(r, backend)
srv := &http.Server{Handler: r}
err = os.RemoveAll(socket)
if err != nil {
return err
}
l, err := net.Listen("unix", socket)
if err != nil {
return err
}
go func() {
defer os.RemoveAll(socket)
defer srv.Close()
if serveErr := srv.Serve(l); serveErr != nil {
logrus.WithError(serveErr).Warn("hostagent API server exited with an error")
}
}()
return ha.Run(cmd.Context())
}

Expand Down
1 change: 1 addition & 0 deletions docs/internal.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Guest agent:

Host agent:
- `ha.pid`: hostagent PID
- `ha.sock`: hostagent REST API
- `ha.stdout.log`: hostagent stdout (JSON lines, see `pkg/hostagent/events.Event`)
- `ha.stderr.log`: hostagent stderr (human-readable messages)

Expand Down
6 changes: 1 addition & 5 deletions examples/alpine.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# This example requires Lima v0.7.0 or later.
images:
- location: https://github.com/lima-vm/alpine-lima/releases/download/v0.1.5/alpine-lima-std-3.13.5-x86_64.iso
arch: "x86_64"
Expand All @@ -9,11 +10,6 @@ mounts:
- location: "/tmp/lima"
writable: true

ssh:
# localPort is changed from 60022 to avoid conflicting with the default.
# (TODO: assign localPort automatically)
localPort: 60020

firmware:
legacyBIOS: true

Expand Down
6 changes: 1 addition & 5 deletions examples/archlinux.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# This example requires Lima v0.5.0 or later
# This example requires Lima v0.7.0 or later
arch: "x86_64"
images:
# NOTE: the image is periodically rotated, if you face 404, see https://mirror.pkgbuild.com/images/ to find the latest image.
Expand All @@ -10,9 +10,5 @@ mounts:
writable: false
- location: "/tmp/lima"
writable: true
ssh:
# localPort is changed from 60022 to avoid conflicting with the default.
# (TODO: assign localPort automatically)
localPort: 60050
firmware:
legacyBIOS: true
5 changes: 1 addition & 4 deletions examples/debian.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# This example requires Lima v0.7.0 or later
images:
- location: "https://cloud.debian.org/images/cloud/bullseye/daily/20210916-767/debian-11-generic-amd64-daily-20210916-767.qcow2"
arch: "x86_64"
Expand All @@ -10,7 +11,3 @@ mounts:
writable: false
- location: "/tmp/lima"
writable: true
ssh:
# localPort is changed from 60022 to avoid conflicting with the default.
# (TODO: assign localPort automatically)
localPort: 60030
6 changes: 1 addition & 5 deletions examples/fedora.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# This example requires Lima v0.7.0 or later.
arch: "x86_64"
images:
- location: "https://download.fedoraproject.org/pub/fedora/linux/releases/34/Cloud/x86_64/images/Fedora-Cloud-Base-34-1.2.x86_64.qcow2"
Expand All @@ -8,10 +9,5 @@ mounts:
writable: false
- location: "/tmp/lima"
writable: true
ssh:
# localPort is changed from 60022 to avoid conflicting with the default.
# (TODO: assign localPort automatically)
localPort: 60024

firmware:
legacyBIOS: true
5 changes: 2 additions & 3 deletions examples/k3s.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
# $ kubectl get no
# NAME STATUS ROLES AGE VERSION
# lima-k3s Ready control-plane,master 69s v1.21.1+k3s1
#
# This example requires Lima v0.7.0 or later.

images:
# Hint: run `limactl prune` to invalidate the "current" cache
Expand All @@ -19,9 +21,6 @@ images:
# Mounts are disabled in this example, but can be enabled optionally.
mounts: []

ssh:
localPort: 60022

# containerd is managed by k3s, not by Lima, so the values are set to false here.
containerd:
system: false
Expand Down
6 changes: 1 addition & 5 deletions examples/opensuse.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# This example requires Lima v0.5.0 or later
# This example requires Lima v0.7.0 or later
images:
# Hint: run `limactl prune` to invalidate the "Current" cache
- location: https://download.opensuse.org/distribution/leap/15.3/appliances/openSUSE-Leap-15.3-JeOS.x86_64-15.3-OpenStack-Cloud-Current.qcow2
Expand All @@ -9,7 +9,3 @@ mounts:
writable: false
- location: "/tmp/lima"
writable: true
ssh:
# localPort is changed from 60022 to avoid conflicting with the default.
# (TODO: assign localPort automatically)
localPort: 60044
5 changes: 1 addition & 4 deletions examples/ubuntu.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# This example requires Lima v0.7.0 or later.
images:
# Hint: run `limactl prune` to invalidate the "current" cache
- location: "https://cloud-images.ubuntu.com/hirsute/current/hirsute-server-cloudimg-amd64.img"
Expand All @@ -9,7 +10,3 @@ mounts:
writable: false
- location: "/tmp/lima"
writable: true
ssh:
# localPort is changed from 60022 to avoid conflicting with the default.
# (TODO: assign localPort automatically)
localPort: 60023
5 changes: 0 additions & 5 deletions examples/vmnet.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ mounts:
writable: false
- location: "/tmp/lima"
writable: true
ssh:
# localPort is changed from 60022 to avoid conflicting with the default.
# (TODO: assign localPort automatically)
localPort: 60105

networks:
# The instance can get routable IP addresses from the vmnet framework using
# https://github.com/lima-vm/vde_vmnet. Available networks are defined in
Expand Down
5 changes: 0 additions & 5 deletions pkg/guestagent/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ import (
"time"
)

// ErrorJSON is returned with "application/json" content type and non-2XX status code
type ErrorJSON struct {
Message string `json:"message"`
}

var (
IPv4loopback1 = net.IPv4(127, 0, 0, 1)
)
Expand Down
3 changes: 2 additions & 1 deletion pkg/guestagent/api/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/gorilla/mux"
"github.com/lima-vm/lima/pkg/guestagent"
"github.com/lima-vm/lima/pkg/guestagent/api"
"github.com/lima-vm/lima/pkg/httputil"
"github.com/sirupsen/logrus"
)

Expand All @@ -19,7 +20,7 @@ func (b *Backend) onError(w http.ResponseWriter, r *http.Request, err error, ec
w.WriteHeader(ec)
w.Header().Set("Content-Type", "application/json")
// it is safe to return the err to the client, because the client is reliable
e := api.ErrorJSON{
e := httputil.ErrorJSON{
Message: err.Error(),
}
_ = json.NewEncoder(w).Encode(e)
Expand Down
5 changes: 5 additions & 0 deletions pkg/hostagent/api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package api

type Info struct {
SSHLocalPort int `json:"sshLocalPort,omitempty"`
}
64 changes: 64 additions & 0 deletions pkg/hostagent/api/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package client

// Forked from https://github.com/rootless-containers/rootlesskit/blob/v0.14.2/pkg/api/client/client.go
// Apache License 2.0

import (
"context"
"encoding/json"
"fmt"
"net/http"

"github.com/lima-vm/lima/pkg/hostagent/api"
"github.com/lima-vm/lima/pkg/httpclientutil"
)

type HostAgentClient interface {
HTTPClient() *http.Client
Info(context.Context) (*api.Info, error)
}

// NewHostAgentClient creates a client.
// socketPath is a path to the UNIX socket, without unix:// prefix.
func NewHostAgentClient(socketPath string) (HostAgentClient, error) {
hc, err := httpclientutil.NewHTTPClientWithSocketPath(socketPath)
if err != nil {
return nil, err
}
return NewHostAgentClientWithHTTPClient(hc), nil
}

func NewHostAgentClientWithHTTPClient(hc *http.Client) HostAgentClient {
return &client{
Client: hc,
version: "v1",
dummyHost: "lima-hostagent",
}
}

type client struct {
*http.Client
// version is always "v1"
// TODO(AkihiroSuda): negotiate the version
version string
dummyHost string
}

func (c *client) HTTPClient() *http.Client {
return c.Client
}

func (c *client) Info(ctx context.Context) (*api.Info, error) {
u := fmt.Sprintf("http://%s/%s/info", c.dummyHost, c.version)
resp, err := httpclientutil.Get(ctx, c.HTTPClient(), u)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var info api.Info
dec := json.NewDecoder(resp.Body)
if err := dec.Decode(&info); err != nil {
return nil, err
}
return &info, nil
}
51 changes: 51 additions & 0 deletions pkg/hostagent/api/server/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package server

import (
"context"
"encoding/json"
"net/http"

"github.com/gorilla/mux"
"github.com/lima-vm/lima/pkg/hostagent"
"github.com/lima-vm/lima/pkg/httputil"
)

type Backend struct {
Agent *hostagent.HostAgent
}

func (b *Backend) onError(w http.ResponseWriter, r *http.Request, err error, ec int) {
w.WriteHeader(ec)
w.Header().Set("Content-Type", "application/json")
// it is safe to return the err to the client, because the client is reliable
e := httputil.ErrorJSON{
Message: err.Error(),
}
_ = json.NewEncoder(w).Encode(e)
}

// GetInfo is the handler for GET /v{N}/info
func (b *Backend) GetInfo(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx, cancel := context.WithCancel(ctx)
defer cancel()

info, err := b.Agent.Info(ctx)
if err != nil {
b.onError(w, r, err, http.StatusInternalServerError)
return
}
m, err := json.Marshal(info)
if err != nil {
b.onError(w, r, err, http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(m)
}

func AddRoutes(r *mux.Router, b *Backend) {
v1 := r.PathPrefix("/v1").Subrouter()
v1.Path("/info").Methods("GET").HandlerFunc(b.GetInfo)
}
2 changes: 1 addition & 1 deletion pkg/hostagent/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func (a *HostAgent) StartDNS() (*dns.Server, error) {
if err != nil {
panic(err)
}
addr := fmt.Sprintf("127.0.0.1:%d", a.y.SSH.LocalPort)
addr := fmt.Sprintf("127.0.0.1:%d", a.sshLocalPort)
server := &dns.Server{Net: "udp", Addr: addr, Handler: h}
go func() {
if e := server.ListenAndServe(); e != nil {
Expand Down
Loading

0 comments on commit e5da6ee

Please sign in to comment.