Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion cmd/cli/commands/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"time"

"github.com/docker/model-runner/cmd/cli/desktop"
"github.com/docker/model-runner/cmd/cli/pkg/types"
"github.com/docker/model-runner/pkg/distribution/builder"
"github.com/docker/model-runner/pkg/distribution/registry"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -117,7 +118,7 @@ func setupTestEnv(t *testing.T) *testEnv {
registryURL := ociRegistry(t, ctx, net)
dmrURL := dockerModelRunner(t, ctx, net)

modelRunnerCtx, err := desktop.NewContextForTest(dmrURL, nil)
modelRunnerCtx, err := desktop.NewContextForTest(dmrURL, nil, types.ModelRunnerEngineKindMoby)
require.NoError(t, err, "Failed to create model runner context")

client := desktop.New(modelRunnerCtx)
Expand Down
64 changes: 39 additions & 25 deletions cmd/cli/commands/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import (
"os"
"strconv"

"github.com/docker/model-runner/cmd/cli/pkg/types"

"github.com/docker/cli/cli-plugins/hooks"
"github.com/docker/model-runner/cmd/cli/commands/completion"
"github.com/docker/model-runner/cmd/cli/desktop"
"github.com/docker/model-runner/cmd/cli/pkg/standalone"
"github.com/docker/model-runner/cmd/cli/pkg/types"
"github.com/spf13/cobra"
)

Expand All @@ -21,7 +21,7 @@ func newStatusCmd() *cobra.Command {
Use: "status",
Short: "Check if the Docker Model Runner is running",
RunE: func(cmd *cobra.Command, args []string) error {
standalone, err := ensureStandaloneRunnerAvailable(cmd.Context(), asPrinter(cmd), false)
runner, err := ensureStandaloneRunnerAvailable(cmd.Context(), asPrinter(cmd), false)
if err != nil {
return fmt.Errorf("unable to initialize standalone model runner: %w", err)
}
Expand All @@ -40,7 +40,7 @@ func newStatusCmd() *cobra.Command {
}

if formatJson {
return jsonStatus(standalone, status, backendStatus)
return jsonStatus(asPrinter(cmd), runner, status, backendStatus)
} else {
textStatus(cmd, status, backendStatus)
}
Expand Down Expand Up @@ -69,44 +69,58 @@ func textStatus(cmd *cobra.Command, status desktop.Status, backendStatus map[str
}
}

func jsonStatus(standalone *standaloneRunner, status desktop.Status, backendStatus map[string]string) error {
func makeEndpoint(host string, port int) string {
return "http://" + net.JoinHostPort(host, strconv.Itoa(port)) + "/v1/"
}

func jsonStatus(printer standalone.StatusPrinter, runner *standaloneRunner, status desktop.Status, backendStatus map[string]string) error {
type Status struct {
Running bool `json:"running"`
Backends map[string]string `json:"backends"`
Endpoint string `json:"endpoint"`
Running bool `json:"running"`
Backends map[string]string `json:"backends"`
Kind string `json:"kind"`
Endpoint string `json:"endpoint"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we name it EndpointContainer to make it clear?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's currently used like this in Compose, that's why I didn't change it.

EndpointHost string `json:"endpointHost"`
}
var endpoint string
var endpoint, endpointHost string
kind := modelRunner.EngineKind()
switch kind {
case types.ModelRunnerEngineKindDesktop:
endpoint = "http://model-runner.docker.internal/engines/v1/"
endpoint = "http://model-runner.docker.internal/v1/"
endpointHost = modelRunner.URL("/v1/")
case types.ModelRunnerEngineKindMobyManual:
endpoint = modelRunner.URL("/engines/v1/")
endpoint = modelRunner.URL("/v1/")
endpointHost = endpoint
case types.ModelRunnerEngineKindCloud:
fallthrough
case types.ModelRunnerEngineKindMoby:
if standalone.gatewayIP == "" {
standalone.gatewayIP = "127.0.0.1"
}

if standalone.gatewayPort == 0 {
standalone.gatewayPort = 12434
gatewayIP := "127.0.0.1"
var gatewayPort uint16 = standalone.DefaultControllerPortCloud
if runner != nil {
if runner.gatewayIP != "" {
gatewayIP = runner.gatewayIP
}
if runner.gatewayPort != 0 {
gatewayPort = runner.gatewayPort
}
}

endpoint = "http://" + net.JoinHostPort(standalone.gatewayIP, strconv.Itoa(int(standalone.gatewayPort))) + "/engines/v1/"
endpoint = makeEndpoint(gatewayIP, int(gatewayPort))
endpointHost = makeEndpoint("127.0.0.1", standalone.DefaultControllerPortCloud)
case types.ModelRunnerEngineKindMoby:
endpoint = makeEndpoint("host.docker.internal", standalone.DefaultControllerPortMoby)
endpointHost = makeEndpoint("127.0.0.1", standalone.DefaultControllerPortMoby)
default:
return fmt.Errorf("unhandled engine kind: %v", kind)
}
s := Status{
Running: status.Running,
Backends: backendStatus,
Endpoint: endpoint,
Running: status.Running,
Backends: backendStatus,
Kind: kind.String(),
Endpoint: endpoint,
EndpointHost: endpointHost,
}
marshal, err := json.Marshal(s)
if err != nil {
return err
}
fmt.Println(string(marshal))
printer.Println(string(marshal))
return nil
}

Expand Down
81 changes: 81 additions & 0 deletions cmd/cli/commands/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ package commands

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"testing"

"github.com/docker/cli/cli-plugins/hooks"
"github.com/docker/model-runner/cmd/cli/desktop"
mockdesktop "github.com/docker/model-runner/cmd/cli/mocks"
"github.com/docker/model-runner/cmd/cli/pkg/standalone"
"github.com/docker/model-runner/cmd/cli/pkg/types"
"github.com/docker/model-runner/pkg/inference"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
Expand Down Expand Up @@ -126,3 +130,80 @@ func TestStatus(t *testing.T) {
})
}
}

func TestJsonStatus(t *testing.T) {
tests := []struct {
name string
engineKind types.ModelRunnerEngineKind
urlPrefix string
expectedKind string
expectedEndpoint string
expectedHostEnd string
}{
{
name: "Docker Desktop",
engineKind: types.ModelRunnerEngineKindDesktop,
urlPrefix: "http://localhost" + inference.ExperimentalEndpointsPrefix,
expectedKind: "Docker Desktop",
expectedEndpoint: "http://model-runner.docker.internal/v1/",
expectedHostEnd: "http://localhost" + inference.ExperimentalEndpointsPrefix + "/v1/",
},
{
name: "Docker Engine",
engineKind: types.ModelRunnerEngineKindMoby,
urlPrefix: "http://localhost:" + strconv.Itoa(standalone.DefaultControllerPortMoby),
expectedKind: "Docker Engine",
expectedEndpoint: makeEndpoint("host.docker.internal", standalone.DefaultControllerPortMoby),
expectedHostEnd: makeEndpoint("127.0.0.1", standalone.DefaultControllerPortMoby),
},
{
name: "Docker Cloud",
engineKind: types.ModelRunnerEngineKindCloud,
urlPrefix: "http://localhost:" + strconv.Itoa(standalone.DefaultControllerPortCloud),
expectedKind: "Docker Cloud",
expectedEndpoint: makeEndpoint("127.0.0.1", standalone.DefaultControllerPortCloud),
expectedHostEnd: makeEndpoint("127.0.0.1", standalone.DefaultControllerPortCloud),
},
{
name: "Docker Engine (Manual Install)",
engineKind: types.ModelRunnerEngineKindMobyManual,
urlPrefix: "http://localhost:8080",
expectedKind: "Docker Engine (Manual Install)",
expectedEndpoint: "http://localhost:8080/v1/",
expectedHostEnd: "http://localhost:8080/v1/",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx, err := desktop.NewContextForTest(test.urlPrefix, nil, test.engineKind)
require.NoError(t, err)
modelRunner = ctx

var output string
printer := desktop.NewSimplePrinter(func(msg string) {
output = msg
})
status := desktop.Status{Running: true}
backendStatus := map[string]string{"llama.cpp": "running"}

// Cloud kind needs a runner for gateway IP/port
var runner *standaloneRunner
if test.engineKind == types.ModelRunnerEngineKindCloud {
runner = &standaloneRunner{}
}

err = jsonStatus(printer, runner, status, backendStatus)
require.NoError(t, err)

var result map[string]any
err = json.Unmarshal([]byte(output), &result)
require.NoError(t, err)

require.Equal(t, test.expectedKind, result["kind"])
require.Equal(t, test.expectedEndpoint, result["endpoint"])
require.Equal(t, test.expectedHostEnd, result["endpointHost"])
require.Equal(t, true, result["running"])
})
}
}
8 changes: 4 additions & 4 deletions cmd/cli/desktop/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@ func NewContextForMock(client DockerHttpClient) *ModelRunnerContext {
}
}

// NewContextForTest creates a ModelRunnerContext for integration testing
// with a custom URL endpoint. This is intended for use in integration tests
// NewContextForTest creates a ModelRunnerContext for integration and mock testing
// with a custom URL endpoint and engine kind. This is intended for use in tests
// where the Model Runner endpoint is dynamically created (e.g., testcontainers).
func NewContextForTest(endpoint string, client DockerHttpClient) (*ModelRunnerContext, error) {
func NewContextForTest(endpoint string, client DockerHttpClient, kind types.ModelRunnerEngineKind) (*ModelRunnerContext, error) {
urlPrefix, err := url.Parse(endpoint)
if err != nil {
return nil, fmt.Errorf("invalid endpoint URL: %w", err)
Expand All @@ -127,7 +127,7 @@ func NewContextForTest(endpoint string, client DockerHttpClient) (*ModelRunnerCo
}

return &ModelRunnerContext{
kind: types.ModelRunnerEngineKindMoby,
kind: kind,
urlPrefix: urlPrefix,
client: client,
}, nil
Expand Down
Loading