mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-28 03:53:33 +00:00
Adding machine.service.internal dns entries (#123)
This commit is contained in:
@@ -46,7 +46,7 @@ func list(ctx context.Context, uncli *cli.CLI, clusterName string) error {
|
|||||||
// Print the list of machines in a table format.
|
// Print the list of machines in a table format.
|
||||||
tw := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
|
tw := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
|
||||||
// Print header.
|
// Print header.
|
||||||
if _, err = fmt.Fprintln(tw, "NAME\tSTATE\tADDRESS\tPUBLIC IP\tWIREGUARD ENDPOINTS"); err != nil {
|
if _, err = fmt.Fprintln(tw, "NAME\tSTATE\tADDRESS\tPUBLIC IP\tWIREGUARD ENDPOINTS\tMACHINE ID"); err != nil {
|
||||||
return fmt.Errorf("write header: %w", err)
|
return fmt.Errorf("write header: %w", err)
|
||||||
}
|
}
|
||||||
// Print rows.
|
// Print rows.
|
||||||
@@ -68,8 +68,8 @@ func list(ctx context.Context, uncli *cli.CLI, clusterName string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _, err = fmt.Fprintf(
|
if _, err = fmt.Fprintf(
|
||||||
tw, "%s\t%s\t%s\t%s\t%s\n", m.Name, capitalise(member.State.String()), subnet, publicIP,
|
tw, "%s\t%s\t%s\t%s\t%s\t%s\n", m.Name, capitalise(member.State.String()), subnet, publicIP,
|
||||||
strings.Join(endpoints, ", "),
|
strings.Join(endpoints, ", "), member.Machine.Id,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return fmt.Errorf("write row: %w", err)
|
return fmt.Errorf("write row: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,11 @@ func (r *ClusterResolver) updateServiceIPs(containers []store.ContainerRecord) {
|
|||||||
newServiceIPs[ctr.ServiceName()] = append(newServiceIPs[ctr.ServiceName()], ip)
|
newServiceIPs[ctr.ServiceName()] = append(newServiceIPs[ctr.ServiceName()], ip)
|
||||||
// Also add the service ID as a valid lookup.
|
// Also add the service ID as a valid lookup.
|
||||||
newServiceIPs[ctr.ServiceID()] = append(newServiceIPs[ctr.ServiceID()], ip)
|
newServiceIPs[ctr.ServiceID()] = append(newServiceIPs[ctr.ServiceID()], ip)
|
||||||
|
|
||||||
|
// Add <machine-id>.m.<service-name> as a lookup
|
||||||
|
serviceNameWithMachineID := record.MachineID + ".m." + ctr.ServiceName()
|
||||||
|
newServiceIPs[serviceNameWithMachineID] = append(newServiceIPs[serviceNameWithMachineID], ip)
|
||||||
|
|
||||||
containersCount++
|
containersCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +104,7 @@ func (r *ClusterResolver) updateServiceIPs(containers []store.ContainerRecord) {
|
|||||||
r.serviceIPs = newServiceIPs
|
r.serviceIPs = newServiceIPs
|
||||||
r.mu.Unlock()
|
r.mu.Unlock()
|
||||||
|
|
||||||
r.log.Debug("DNS records updated.", "services", len(newServiceIPs)/2, "containers", containersCount)
|
r.log.Debug("DNS records updated.", "services", len(newServiceIPs)/3, "containers", containersCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve returns IP addresses of the service containers.
|
// Resolve returns IP addresses of the service containers.
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
package e2e
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
dockerclient "github.com/docker/docker/client"
|
||||||
|
"github.com/psviderski/uncloud/internal/ucind"
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestInternalDNS tests the internal DNS functionality including the new machine-specific service lookups
|
||||||
|
func TestInternalDNS(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
clusterName := "ucind-test.dns"
|
||||||
|
ctx := context.Background()
|
||||||
|
c, _ := createTestCluster(t, clusterName, ucind.CreateClusterOptions{Machines: 3}, true)
|
||||||
|
|
||||||
|
cli, err := c.Machines[0].Connect(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Create a test service with multiple replicas across machines
|
||||||
|
serviceName := "test-dns-service"
|
||||||
|
t.Cleanup(func() {
|
||||||
|
err := cli.RemoveService(ctx, serviceName)
|
||||||
|
if err != nil && !strings.Contains(err.Error(), "not found") {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Deploy a service across all machines in global mode using pause container
|
||||||
|
spec := api.ServiceSpec{
|
||||||
|
Name: serviceName,
|
||||||
|
Mode: api.ServiceModeGlobal,
|
||||||
|
Container: api.ContainerSpec{
|
||||||
|
Image: "portainer/pause:latest",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
deployment := cli.NewDeployment(spec, nil)
|
||||||
|
_, err = deployment.Run(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Wait for the service to be deployed
|
||||||
|
var svc api.Service
|
||||||
|
require.Eventually(t, func() bool {
|
||||||
|
svc, err = cli.InspectService(ctx, serviceName)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Should have 3 containers (one per machine) and all should be running
|
||||||
|
if len(svc.Containers) != 3 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, ctr := range svc.Containers {
|
||||||
|
if ctr.Container.State.Status != "running" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}, 30*time.Second, 1*time.Second, "Service should be deployed and running on all machines")
|
||||||
|
|
||||||
|
// Get Docker client to access ucind machine containers (shared across all DNS tests)
|
||||||
|
dockerCli, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv, dockerclient.WithAPIVersionNegotiation())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Helper function to create DNS query service spec
|
||||||
|
createDNSQuerySpec := func(name, dnsQuery, outputFile string) api.ServiceSpec {
|
||||||
|
return api.ServiceSpec{
|
||||||
|
Name: name,
|
||||||
|
Mode: api.ServiceModeReplicated,
|
||||||
|
Container: api.ContainerSpec{
|
||||||
|
Image: "wbitt/network-multitool",
|
||||||
|
Command: []string{"sh", "-c", fmt.Sprintf("nslookup %s > %s 2>&1 && echo 'DNS query completed' && sleep infinity", dnsQuery, outputFile)},
|
||||||
|
VolumeMounts: []api.VolumeMount{
|
||||||
|
{
|
||||||
|
VolumeName: "host-tmp",
|
||||||
|
ContainerPath: "/tmp",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Volumes: []api.VolumeSpec{
|
||||||
|
{
|
||||||
|
Name: "host-tmp",
|
||||||
|
Type: "bind",
|
||||||
|
BindOptions: &api.BindOptions{
|
||||||
|
HostPath: "/tmp",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Replicas: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to run DNS query and get results
|
||||||
|
runDNSQuery := func(t *testing.T, serviceName, dnsQuery, outputFile string) string {
|
||||||
|
spec := createDNSQuerySpec(serviceName, dnsQuery, outputFile)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
cli.RemoveService(ctx, spec.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Run the DNS query service
|
||||||
|
resp, err := cli.RunService(ctx, spec)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Wait for the DNS query to complete
|
||||||
|
var querySvc api.Service
|
||||||
|
require.Eventually(t, func() bool {
|
||||||
|
querySvc, err = cli.InspectService(ctx, resp.ID)
|
||||||
|
if err != nil || len(querySvc.Containers) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return querySvc.Containers[0].Container.State.Status == "running"
|
||||||
|
}, 30*time.Second, 1*time.Second, "DNS query container should be running")
|
||||||
|
|
||||||
|
// Give some time for DNS query to complete and write to file
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
// Find which ucind machine the DNS query container is running on
|
||||||
|
container := querySvc.Containers[0]
|
||||||
|
machineID := container.MachineID
|
||||||
|
|
||||||
|
// Get the ucind machine container name for this machine
|
||||||
|
var ucindMachineName string
|
||||||
|
for _, machine := range c.Machines {
|
||||||
|
if machine.ID == machineID {
|
||||||
|
ucindMachineName = machine.ContainerName
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.NotEmpty(t, ucindMachineName, "Should find ucind machine container name")
|
||||||
|
|
||||||
|
// Read the DNS results from the ucind machine's filesystem
|
||||||
|
exec, err := dockerCli.ContainerExecCreate(ctx, ucindMachineName, types.ExecConfig{
|
||||||
|
Cmd: []string{"cat", outputFile},
|
||||||
|
AttachStdout: true,
|
||||||
|
AttachStderr: true,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
resp2, err := dockerCli.ContainerExecAttach(ctx, exec.ID, types.ExecStartCheck{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer resp2.Close()
|
||||||
|
|
||||||
|
// Read the DNS output
|
||||||
|
buf := make([]byte, 4096)
|
||||||
|
n, _ := resp2.Reader.Read(buf)
|
||||||
|
return string(buf[:n])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to verify DNS output doesn't contain errors
|
||||||
|
assertNoDNSErrors := func(t *testing.T, dnsOutput string) {
|
||||||
|
assert.NotContains(t, dnsOutput, "can't resolve", "DNS query should not contain resolution errors")
|
||||||
|
assert.NotContains(t, dnsOutput, "Name or service not known", "DNS query should not contain unknown service errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("service name resolves to all container IPs", func(t *testing.T) {
|
||||||
|
dnsOutput := runDNSQuery(t, "dns-test-service-name", serviceName+".internal", "/tmp/dns_result.txt")
|
||||||
|
t.Logf("DNS query output:\n%s", dnsOutput)
|
||||||
|
|
||||||
|
// Verify that all service container IPs are in the DNS response
|
||||||
|
for _, ctr := range svc.Containers {
|
||||||
|
containerIP := ctr.Container.UncloudNetworkIP().String()
|
||||||
|
assert.Contains(t, dnsOutput, containerIP,
|
||||||
|
"Service DNS should resolve to container IP %s", containerIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertNoDNSErrors(t, dnsOutput)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("machine-specific service DNS lookups", func(t *testing.T) {
|
||||||
|
// Test the new <machine-id>.m.<service-name>.internal DNS feature
|
||||||
|
for i, targetContainer := range svc.Containers {
|
||||||
|
targetMachineID := targetContainer.MachineID
|
||||||
|
targetContainerIP := targetContainer.Container.UncloudNetworkIP().String()
|
||||||
|
|
||||||
|
// Construct the machine-specific DNS name
|
||||||
|
machineSpecificDNS := targetMachineID + ".m." + serviceName + ".internal"
|
||||||
|
outputFile := fmt.Sprintf("/tmp/dns_result_machine_%d.txt", i)
|
||||||
|
|
||||||
|
dnsOutput := runDNSQuery(t, fmt.Sprintf("dns-test-machine-%d", i), machineSpecificDNS, outputFile)
|
||||||
|
t.Logf("Machine-specific DNS query output for %s:\n%s", machineSpecificDNS, dnsOutput)
|
||||||
|
|
||||||
|
// Verify that the specific container IP is returned
|
||||||
|
assert.Contains(t, dnsOutput, targetContainerIP,
|
||||||
|
"Machine-specific DNS %s should resolve to container IP %s",
|
||||||
|
machineSpecificDNS, targetContainerIP)
|
||||||
|
|
||||||
|
// Verify that other container IPs are not returned (machine-specific should return only one IP)
|
||||||
|
for _, ctr := range svc.Containers {
|
||||||
|
if ctr.MachineID != targetMachineID {
|
||||||
|
otherContainerIP := ctr.Container.UncloudNetworkIP().String()
|
||||||
|
assert.NotContains(t, dnsOutput, otherContainerIP,
|
||||||
|
"Machine-specific DNS %s should not resolve to other container IP %s",
|
||||||
|
machineSpecificDNS, otherContainerIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertNoDNSErrors(t, dnsOutput)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("service ID DNS lookup", func(t *testing.T) {
|
||||||
|
// Test that service ID also resolves (existing functionality)
|
||||||
|
dnsOutput := runDNSQuery(t, "dns-test-service-id", svc.ID+".internal", "/tmp/dns_result_service_id.txt")
|
||||||
|
t.Logf("Service ID DNS query output:\n%s", dnsOutput)
|
||||||
|
|
||||||
|
// Verify that all service container IPs are in the DNS response
|
||||||
|
for _, ctr := range svc.Containers {
|
||||||
|
containerIP := ctr.Container.UncloudNetworkIP().String()
|
||||||
|
assert.Contains(t, dnsOutput, containerIP,
|
||||||
|
"Service ID DNS should resolve to container IP %s", containerIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertNoDNSErrors(t, dnsOutput)
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user