From 3418735204c34b1ee1530c0a05ebe5c4b4cd25e1 Mon Sep 17 00:00:00 2001 From: LordMathis Date: Tue, 7 Oct 2025 20:27:31 +0200 Subject: [PATCH] Add stripNodesFromOptions function to prevent routing loops in remote requests --- pkg/manager/remote_ops.go | 21 ++++++++++++++++++ pkg/manager/remote_ops_test.go | 39 ++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 pkg/manager/remote_ops_test.go diff --git a/pkg/manager/remote_ops.go b/pkg/manager/remote_ops.go index e7b3dbb..49b24f1 100644 --- a/pkg/manager/remote_ops.go +++ b/pkg/manager/remote_ops.go @@ -10,10 +10,31 @@ import ( "net/http" ) +// stripNodesFromOptions creates a copy of the instance options without the Nodes field +// to prevent routing loops when sending requests to remote nodes +func (im *instanceManager) stripNodesFromOptions(options *instance.CreateInstanceOptions) *instance.CreateInstanceOptions { + if options == nil { + return nil + } + + // Create a copy of the options struct + optionsCopy := *options + + // Clear the Nodes field to prevent the remote node from trying to route further + optionsCopy.Nodes = nil + + return &optionsCopy +} + // makeRemoteRequest is a helper function to make HTTP requests to a remote node func (im *instanceManager) makeRemoteRequest(nodeConfig *config.NodeConfig, method, path string, body any) (*http.Response, error) { var reqBody io.Reader if body != nil { + // Strip nodes from CreateInstanceOptions to prevent routing loops + if options, ok := body.(*instance.CreateInstanceOptions); ok { + body = im.stripNodesFromOptions(options) + } + jsonData, err := json.Marshal(body) if err != nil { return nil, fmt.Errorf("failed to marshal request body: %w", err) diff --git a/pkg/manager/remote_ops_test.go b/pkg/manager/remote_ops_test.go new file mode 100644 index 0000000..94db40b --- /dev/null +++ b/pkg/manager/remote_ops_test.go @@ -0,0 +1,39 @@ +package manager + +import ( + "llamactl/pkg/backends" + "llamactl/pkg/instance" + "testing" +) + +func TestStripNodesFromOptions(t *testing.T) { + im := &instanceManager{} + + // Test nil case + if result := im.stripNodesFromOptions(nil); result != nil { + t.Errorf("Expected nil, got %+v", result) + } + + // Test main case: nodes should be stripped, other fields preserved + options := &instance.CreateInstanceOptions{ + BackendType: backends.BackendTypeLlamaCpp, + Nodes: []string{"node1", "node2"}, + Environment: map[string]string{"TEST": "value"}, + } + + result := im.stripNodesFromOptions(options) + + if result.Nodes != nil { + t.Errorf("Expected Nodes to be nil, got %+v", result.Nodes) + } + if result.BackendType != backends.BackendTypeLlamaCpp { + t.Errorf("Expected BackendType preserved") + } + if result.Environment["TEST"] != "value" { + t.Errorf("Expected Environment preserved") + } + // Original should not be modified + if len(options.Nodes) != 2 { + t.Errorf("Original options should not be modified") + } +}