mirror of
https://github.com/DeBrosOfficial/network.git
synced 2026-01-30 08:33:04 +00:00
392 lines
13 KiB
Go
392 lines
13 KiB
Go
//go:build e2e
|
|
|
|
package e2e
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestNamespaceCluster_Provisioning tests that creating a new namespace
|
|
// triggers cluster provisioning with 202 Accepted response
|
|
func TestNamespaceCluster_Provisioning(t *testing.T) {
|
|
if !IsProductionMode() {
|
|
t.Skip("Namespace cluster provisioning only applies in production mode")
|
|
}
|
|
|
|
// This test requires a completely new namespace to trigger provisioning
|
|
newNamespace := fmt.Sprintf("test-ns-%d", time.Now().UnixNano())
|
|
|
|
env, err := LoadTestEnvWithNamespace(newNamespace)
|
|
require.NoError(t, err, "Should create test environment")
|
|
|
|
t.Run("New namespace triggers provisioning", func(t *testing.T) {
|
|
// If we got here with an API key, provisioning either completed or was not required
|
|
// The LoadTestEnvWithNamespace function handles the provisioning flow
|
|
require.NotEmpty(t, env.APIKey, "Should have received API key after provisioning")
|
|
t.Logf("Namespace %s provisioned successfully", newNamespace)
|
|
})
|
|
|
|
t.Run("Namespace gateway is accessible", func(t *testing.T) {
|
|
// Try to access the namespace gateway
|
|
// The URL should be ns-{namespace}.{baseDomain}
|
|
cfg, _ := LoadE2EConfig()
|
|
if cfg.BaseDomain == "" {
|
|
cfg.BaseDomain = "devnet-orama.network"
|
|
}
|
|
|
|
nsGatewayURL := fmt.Sprintf("https://ns-%s.%s", newNamespace, cfg.BaseDomain)
|
|
|
|
req, _ := http.NewRequest("GET", nsGatewayURL+"/v1/health", nil)
|
|
req.Header.Set("Authorization", "Bearer "+env.APIKey)
|
|
|
|
resp, err := env.HTTPClient.Do(req)
|
|
if err != nil {
|
|
t.Logf("Note: Namespace gateway not accessible (expected in local mode): %v", err)
|
|
t.Skip("Namespace gateway endpoint not available")
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode, "Namespace gateway should be healthy")
|
|
t.Logf("Namespace gateway %s is accessible", nsGatewayURL)
|
|
})
|
|
}
|
|
|
|
// TestNamespaceCluster_StatusPolling tests the /v1/namespace/status endpoint
|
|
func TestNamespaceCluster_StatusPolling(t *testing.T) {
|
|
env, err := LoadTestEnv()
|
|
require.NoError(t, err, "Should load test environment")
|
|
|
|
t.Run("Status endpoint returns valid response", func(t *testing.T) {
|
|
// Test with a non-existent cluster ID (should return 404)
|
|
req, _ := http.NewRequest("GET", env.GatewayURL+"/v1/namespace/status?id=non-existent-id", nil)
|
|
|
|
resp, err := env.HTTPClient.Do(req)
|
|
require.NoError(t, err, "Should execute request")
|
|
defer resp.Body.Close()
|
|
|
|
// Should return 404 for non-existent cluster
|
|
assert.Equal(t, http.StatusNotFound, resp.StatusCode, "Should return 404 for non-existent cluster")
|
|
})
|
|
}
|
|
|
|
// TestNamespaceCluster_CrossGatewayAccess tests that API keys from one namespace
|
|
// cannot access another namespace's dedicated gateway
|
|
func TestNamespaceCluster_CrossGatewayAccess(t *testing.T) {
|
|
if !IsProductionMode() {
|
|
t.Skip("Cross-gateway access control only applies in production mode")
|
|
}
|
|
|
|
// Create two namespaces
|
|
nsA := fmt.Sprintf("ns-a-%d", time.Now().Unix())
|
|
nsB := fmt.Sprintf("ns-b-%d", time.Now().Unix())
|
|
|
|
envA, err := LoadTestEnvWithNamespace(nsA)
|
|
require.NoError(t, err, "Should create test environment for namespace A")
|
|
|
|
envB, err := LoadTestEnvWithNamespace(nsB)
|
|
require.NoError(t, err, "Should create test environment for namespace B")
|
|
|
|
cfg, _ := LoadE2EConfig()
|
|
if cfg.BaseDomain == "" {
|
|
cfg.BaseDomain = "devnet-orama.network"
|
|
}
|
|
|
|
t.Run("Namespace A key cannot access Namespace B gateway", func(t *testing.T) {
|
|
// Try to use namespace A's key on namespace B's gateway
|
|
nsBGatewayURL := fmt.Sprintf("https://ns-%s.%s", nsB, cfg.BaseDomain)
|
|
|
|
req, _ := http.NewRequest("GET", nsBGatewayURL+"/v1/deployments/list", nil)
|
|
req.Header.Set("Authorization", "Bearer "+envA.APIKey) // Using A's key
|
|
|
|
resp, err := envA.HTTPClient.Do(req)
|
|
if err != nil {
|
|
t.Logf("Note: Gateway not accessible: %v", err)
|
|
t.Skip("Namespace gateway endpoint not available")
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
assert.Equal(t, http.StatusForbidden, resp.StatusCode,
|
|
"Should deny namespace A's key on namespace B's gateway")
|
|
t.Logf("Cross-namespace access correctly denied (status: %d)", resp.StatusCode)
|
|
})
|
|
|
|
t.Run("Namespace B key works on Namespace B gateway", func(t *testing.T) {
|
|
nsBGatewayURL := fmt.Sprintf("https://ns-%s.%s", nsB, cfg.BaseDomain)
|
|
|
|
req, _ := http.NewRequest("GET", nsBGatewayURL+"/v1/deployments/list", nil)
|
|
req.Header.Set("Authorization", "Bearer "+envB.APIKey) // Using B's key
|
|
|
|
resp, err := envB.HTTPClient.Do(req)
|
|
if err != nil {
|
|
t.Logf("Note: Gateway not accessible: %v", err)
|
|
t.Skip("Namespace gateway endpoint not available")
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode,
|
|
"Should allow namespace B's key on namespace B's gateway")
|
|
t.Logf("Same-namespace access correctly allowed")
|
|
})
|
|
}
|
|
|
|
// TestNamespaceCluster_DefaultNamespaceAccessible tests that the default namespace
|
|
// is accessible by any valid API key
|
|
func TestNamespaceCluster_DefaultNamespaceAccessible(t *testing.T) {
|
|
// Create a non-default namespace
|
|
customNS := fmt.Sprintf("custom-%d", time.Now().Unix())
|
|
env, err := LoadTestEnvWithNamespace(customNS)
|
|
require.NoError(t, err, "Should create test environment")
|
|
|
|
t.Run("Custom namespace key can access default gateway endpoints", func(t *testing.T) {
|
|
// The default gateway should accept keys from any namespace
|
|
req, _ := http.NewRequest("GET", env.GatewayURL+"/v1/health", nil)
|
|
req.Header.Set("Authorization", "Bearer "+env.APIKey)
|
|
|
|
resp, err := env.HTTPClient.Do(req)
|
|
require.NoError(t, err, "Should execute request")
|
|
defer resp.Body.Close()
|
|
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode,
|
|
"Default gateway should accept any valid API key")
|
|
})
|
|
}
|
|
|
|
// TestDeployment_RandomSubdomain tests that deployments get random subdomain suffix
|
|
func TestDeployment_RandomSubdomain(t *testing.T) {
|
|
env, err := LoadTestEnv()
|
|
require.NoError(t, err, "Should load test environment")
|
|
|
|
tarballPath := filepath.Join("../testdata/tarballs/react-vite.tar.gz")
|
|
|
|
// Create a deployment
|
|
deploymentName := "subdomain-test"
|
|
deploymentID := CreateTestDeployment(t, env, deploymentName, tarballPath)
|
|
defer func() {
|
|
if !env.SkipCleanup {
|
|
DeleteDeployment(t, env, deploymentID)
|
|
}
|
|
}()
|
|
|
|
t.Run("Deployment URL contains random suffix", func(t *testing.T) {
|
|
// Get deployment details
|
|
req, _ := http.NewRequest("GET", env.GatewayURL+"/v1/deployments/get?id="+deploymentID, nil)
|
|
req.Header.Set("Authorization", "Bearer "+env.APIKey)
|
|
|
|
resp, err := env.HTTPClient.Do(req)
|
|
require.NoError(t, err, "Should execute request")
|
|
defer resp.Body.Close()
|
|
|
|
require.Equal(t, http.StatusOK, resp.StatusCode, "Should get deployment")
|
|
|
|
var result map[string]interface{}
|
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
|
require.NoError(t, json.Unmarshal(bodyBytes, &result), "Should decode JSON")
|
|
|
|
deployment, ok := result["deployment"].(map[string]interface{})
|
|
if !ok {
|
|
deployment = result
|
|
}
|
|
|
|
// Check subdomain field
|
|
subdomain, _ := deployment["subdomain"].(string)
|
|
if subdomain != "" {
|
|
// Subdomain should follow format: {name}-{random}
|
|
// e.g., "subdomain-test-f3o4if"
|
|
assert.True(t, strings.HasPrefix(subdomain, deploymentName+"-"),
|
|
"Subdomain should start with deployment name followed by dash")
|
|
|
|
suffix := strings.TrimPrefix(subdomain, deploymentName+"-")
|
|
assert.Equal(t, 6, len(suffix), "Random suffix should be 6 characters")
|
|
|
|
t.Logf("Deployment subdomain: %s (suffix: %s)", subdomain, suffix)
|
|
} else {
|
|
t.Logf("Note: Subdomain field not set (may be using legacy format)")
|
|
}
|
|
|
|
// Check URLs
|
|
urls, ok := deployment["urls"].([]interface{})
|
|
if ok && len(urls) > 0 {
|
|
url := urls[0].(string)
|
|
t.Logf("Deployment URL: %s", url)
|
|
|
|
// URL should contain the subdomain with random suffix
|
|
if subdomain != "" {
|
|
assert.Contains(t, url, subdomain, "URL should contain the subdomain")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestDeployment_SubdomainUniqueness tests that two deployments with the same name
|
|
// get different subdomains
|
|
func TestDeployment_SubdomainUniqueness(t *testing.T) {
|
|
envA, err := LoadTestEnvWithNamespace("ns-unique-a-" + fmt.Sprintf("%d", time.Now().Unix()))
|
|
require.NoError(t, err, "Should create test environment A")
|
|
|
|
envB, err := LoadTestEnvWithNamespace("ns-unique-b-" + fmt.Sprintf("%d", time.Now().Unix()))
|
|
require.NoError(t, err, "Should create test environment B")
|
|
|
|
tarballPath := filepath.Join("../testdata/tarballs/react-vite.tar.gz")
|
|
deploymentName := "same-name-app"
|
|
|
|
// Create deployment in namespace A
|
|
deploymentIDA := CreateTestDeployment(t, envA, deploymentName, tarballPath)
|
|
defer func() {
|
|
if !envA.SkipCleanup {
|
|
DeleteDeployment(t, envA, deploymentIDA)
|
|
}
|
|
}()
|
|
|
|
// Create deployment with same name in namespace B
|
|
deploymentIDB := CreateTestDeployment(t, envB, deploymentName, tarballPath)
|
|
defer func() {
|
|
if !envB.SkipCleanup {
|
|
DeleteDeployment(t, envB, deploymentIDB)
|
|
}
|
|
}()
|
|
|
|
t.Run("Same name deployments have different subdomains", func(t *testing.T) {
|
|
// Get deployment A details
|
|
reqA, _ := http.NewRequest("GET", envA.GatewayURL+"/v1/deployments/get?id="+deploymentIDA, nil)
|
|
reqA.Header.Set("Authorization", "Bearer "+envA.APIKey)
|
|
respA, _ := envA.HTTPClient.Do(reqA)
|
|
defer respA.Body.Close()
|
|
|
|
var resultA map[string]interface{}
|
|
bodyBytesA, _ := io.ReadAll(respA.Body)
|
|
json.Unmarshal(bodyBytesA, &resultA)
|
|
|
|
deploymentA, ok := resultA["deployment"].(map[string]interface{})
|
|
if !ok {
|
|
deploymentA = resultA
|
|
}
|
|
subdomainA, _ := deploymentA["subdomain"].(string)
|
|
|
|
// Get deployment B details
|
|
reqB, _ := http.NewRequest("GET", envB.GatewayURL+"/v1/deployments/get?id="+deploymentIDB, nil)
|
|
reqB.Header.Set("Authorization", "Bearer "+envB.APIKey)
|
|
respB, _ := envB.HTTPClient.Do(reqB)
|
|
defer respB.Body.Close()
|
|
|
|
var resultB map[string]interface{}
|
|
bodyBytesB, _ := io.ReadAll(respB.Body)
|
|
json.Unmarshal(bodyBytesB, &resultB)
|
|
|
|
deploymentB, ok := resultB["deployment"].(map[string]interface{})
|
|
if !ok {
|
|
deploymentB = resultB
|
|
}
|
|
subdomainB, _ := deploymentB["subdomain"].(string)
|
|
|
|
// If subdomains are set, they should be different
|
|
if subdomainA != "" && subdomainB != "" {
|
|
assert.NotEqual(t, subdomainA, subdomainB,
|
|
"Same-name deployments in different namespaces should have different subdomains")
|
|
|
|
t.Logf("Namespace A subdomain: %s", subdomainA)
|
|
t.Logf("Namespace B subdomain: %s", subdomainB)
|
|
} else {
|
|
t.Logf("Note: Subdomains not set (may be using legacy format)")
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestNamespaceCluster_DNSFormat tests the DNS naming convention for namespaces
|
|
func TestNamespaceCluster_DNSFormat(t *testing.T) {
|
|
cfg, err := LoadE2EConfig()
|
|
if err != nil {
|
|
cfg = DefaultConfig()
|
|
}
|
|
|
|
if cfg.BaseDomain == "" {
|
|
cfg.BaseDomain = "devnet-orama.network"
|
|
}
|
|
|
|
t.Run("Namespace gateway DNS follows ns-{name}.{baseDomain} format", func(t *testing.T) {
|
|
namespace := "my-test-namespace"
|
|
expectedDomain := fmt.Sprintf("ns-%s.%s", namespace, cfg.BaseDomain)
|
|
|
|
t.Logf("Expected namespace gateway domain: %s", expectedDomain)
|
|
|
|
// Verify format
|
|
assert.True(t, strings.HasPrefix(expectedDomain, "ns-"),
|
|
"Namespace gateway domain should start with 'ns-'")
|
|
assert.True(t, strings.HasSuffix(expectedDomain, cfg.BaseDomain),
|
|
"Namespace gateway domain should end with base domain")
|
|
})
|
|
|
|
t.Run("Deployment DNS follows {name}-{random}.{baseDomain} format", func(t *testing.T) {
|
|
deploymentName := "my-app"
|
|
randomSuffix := "f3o4if"
|
|
expectedDomain := fmt.Sprintf("%s-%s.%s", deploymentName, randomSuffix, cfg.BaseDomain)
|
|
|
|
t.Logf("Expected deployment domain: %s", expectedDomain)
|
|
|
|
// Verify format
|
|
assert.Contains(t, expectedDomain, deploymentName,
|
|
"Deployment domain should contain the deployment name")
|
|
assert.True(t, strings.HasSuffix(expectedDomain, cfg.BaseDomain),
|
|
"Deployment domain should end with base domain")
|
|
})
|
|
}
|
|
|
|
// TestNamespaceCluster_PortAllocation tests the port allocation constraints
|
|
func TestNamespaceCluster_PortAllocation(t *testing.T) {
|
|
t.Run("Port range constants are correct", func(t *testing.T) {
|
|
// These constants are defined in pkg/namespace/types.go
|
|
const (
|
|
portRangeStart = 10000
|
|
portRangeEnd = 10099
|
|
portsPerNamespace = 5
|
|
maxNamespacesPerNode = 20
|
|
)
|
|
|
|
// Verify range calculation
|
|
totalPorts := portRangeEnd - portRangeStart + 1
|
|
assert.Equal(t, 100, totalPorts, "Port range should be 100 ports")
|
|
|
|
expectedMax := totalPorts / portsPerNamespace
|
|
assert.Equal(t, maxNamespacesPerNode, expectedMax,
|
|
"Max namespaces per node should be total ports / ports per namespace")
|
|
|
|
t.Logf("Port range: %d-%d (%d ports total)", portRangeStart, portRangeEnd, totalPorts)
|
|
t.Logf("Ports per namespace: %d", portsPerNamespace)
|
|
t.Logf("Max namespaces per node: %d", maxNamespacesPerNode)
|
|
})
|
|
|
|
t.Run("Port assignments within a block are sequential", func(t *testing.T) {
|
|
portStart := 10000
|
|
|
|
rqliteHTTP := portStart + 0
|
|
rqliteRaft := portStart + 1
|
|
olricHTTP := portStart + 2
|
|
olricMemberlist := portStart + 3
|
|
gatewayHTTP := portStart + 4
|
|
|
|
// All ports should be unique
|
|
ports := []int{rqliteHTTP, rqliteRaft, olricHTTP, olricMemberlist, gatewayHTTP}
|
|
seen := make(map[int]bool)
|
|
for _, port := range ports {
|
|
assert.False(t, seen[port], "Ports should be unique within a block")
|
|
seen[port] = true
|
|
}
|
|
|
|
t.Logf("Port assignments for block starting at %d:", portStart)
|
|
t.Logf(" RQLite HTTP: %d", rqliteHTTP)
|
|
t.Logf(" RQLite Raft: %d", rqliteRaft)
|
|
t.Logf(" Olric HTTP: %d", olricHTTP)
|
|
t.Logf(" Olric Memberlist: %d", olricMemberlist)
|
|
t.Logf(" Gateway HTTP: %d", gatewayHTTP)
|
|
})
|
|
}
|