refactor RemoveService using InspectService

This commit is contained in:
Pavel Sviderski
2024-12-05 12:27:51 +10:00
parent 99c9aabdfd
commit a40fd613e8
2 changed files with 31 additions and 58 deletions
+27 -58
View File
@@ -299,6 +299,7 @@ func firstAvailableMachine(machines []*pb.MachineMember) (*pb.MachineMember, err
} }
// InspectService returns detailed information about a service and its containers. // InspectService returns detailed information about a service and its containers.
// The id parameter can be either a service ID or name.
func (cli *Client) InspectService(ctx context.Context, id string) (service.Service, error) { func (cli *Client) InspectService(ctx context.Context, id string) (service.Service, error) {
var svc service.Service var svc service.Service
@@ -415,6 +416,7 @@ func (cli *Client) InspectService(ctx context.Context, id string) (service.Servi
// InspectServiceFromStore returns detailed information about a service and its containers from the distributed store. // InspectServiceFromStore returns detailed information about a service and its containers from the distributed store.
// Due to eventual consistency of the store, the returned information may not reflect the most recent changes. // Due to eventual consistency of the store, the returned information may not reflect the most recent changes.
// The id parameter can be either a service ID or name.
func (cli *Client) InspectServiceFromStore(ctx context.Context, id string) (service.Service, error) { func (cli *Client) InspectServiceFromStore(ctx context.Context, id string) (service.Service, error) {
var svc service.Service var svc service.Service
@@ -438,77 +440,44 @@ func (cli *Client) InspectServiceFromStore(ctx context.Context, id string) (serv
// RemoveService removes all containers on all machines that belong to the specified service. // RemoveService removes all containers on all machines that belong to the specified service.
// The id parameter can be either a service ID or name. // The id parameter can be either a service ID or name.
func (cli *Client) RemoveService(ctx context.Context, id string) error { func (cli *Client) RemoveService(ctx context.Context, id string) error {
svc, err := cli.InspectService(ctx, id)
if err != nil {
return err
}
machines, err := cli.ListMachines(ctx) machines, err := cli.ListMachines(ctx)
if err != nil { if err != nil {
return fmt.Errorf("list machines: %w", err) return fmt.Errorf("list machines: %w", err)
} }
machineManagementIPByID := make(map[string]string)
// Broadcast the container list request to all available machines.
md := metadata.New(nil)
for _, m := range machines { for _, m := range machines {
if m.State == pb.MachineMember_UP || m.State == pb.MachineMember_SUSPECT { machineIP, _ := m.Machine.Network.ManagementIp.ToAddr()
machineIP, _ := m.Machine.Network.ManagementIp.ToAddr() machineManagementIPByID[m.Machine.Id] = machineIP.String()
md.Append("machines", machineIP.String())
}
// TODO: warning about machines that are DOWN.
}
listCtx := metadata.NewOutgoingContext(ctx, md)
// List only uncloud-managed containers that belong to some service.
opts := container.ListOptions{
All: true,
Filters: filters.NewArgs(
filters.Arg("label", service.LabelServiceID),
filters.Arg("label", service.LabelManaged),
),
}
machineContainers, err := cli.ListContainers(listCtx, opts)
if err != nil {
return fmt.Errorf("list containers: %w", err)
} }
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
errCh := make(chan error) errCh := make(chan error)
// Remove all containers on all machines that belong to the specified service. // Remove all containers on all machines that belong to the service.
for _, mc := range machineContainers { for _, mc := range svc.Containers {
// Metadata can be nil if the request was broadcasted to only one machine. wg.Add(1)
if mc.Metadata == nil && len(machineContainers) > 1 {
return errors.New("something went wrong with gRPC proxy: metadata is missing for a machine response")
}
if mc.Metadata != nil && mc.Metadata.Error != "" {
// TODO: return failed machines in the response.
fmt.Printf("WARNING: failed to list containers on machine '%s': %s\n",
mc.Metadata.Machine, mc.Metadata.Error)
continue
}
// removeCtx proxies remove requests to the machine that owns mc.Containers. go func() {
var removeCtx context.Context defer wg.Done()
if mc.Metadata == nil {
// ListContainers was proxied to only one machine. Proxy the remove request to the same machine.
removeCtx = listCtx
} else {
removeCtx = metadata.NewOutgoingContext(ctx, metadata.Pairs("machines", mc.Metadata.Machine))
}
for _, c := range mc.Containers { machineIP, ok := machineManagementIPByID[mc.MachineID]
ctr := service.Container{Container: c} if !ok {
if ctr.ServiceID() == id || ctr.ServiceName() == id { errCh <- fmt.Errorf("machine not found by ID: %s", mc.MachineID)
wg.Add(1) return
go func() {
defer wg.Done()
err := cli.RemoveContainer(removeCtx, ctr.ID, container.RemoveOptions{Force: true})
if err != nil {
if !dockerclient.IsErrNotFound(err) {
errCh <- fmt.Errorf("remove container '%s': %w", ctr.ID, err)
}
}
}()
} }
} removeCtx := metadata.NewOutgoingContext(ctx, metadata.Pairs("machines", machineIP))
err := cli.RemoveContainer(removeCtx, mc.Container.ID, container.RemoveOptions{Force: true})
if err != nil {
if !dockerclient.IsErrNotFound(err) {
errCh <- fmt.Errorf("remove container '%s': %w", mc.Container.ID, err)
}
}
}()
} }
go func() { go func() {
+4
View File
@@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"testing" "testing"
"uncloud/internal/api" "uncloud/internal/api"
"uncloud/internal/cli/client"
"uncloud/internal/ucind" "uncloud/internal/ucind"
) )
@@ -26,6 +27,9 @@ func TestRunService(t *testing.T) {
if !dockerclient.IsErrNotFound(err) { if !dockerclient.IsErrNotFound(err) {
require.NoError(t, err) require.NoError(t, err)
} }
_, err = cli.InspectService(ctx, "busybox-global")
require.ErrorIs(t, err, client.ErrNotFound)
}) })
resp, err := cli.RunService(ctx, api.ServiceSpec{ resp, err := cli.RunService(ctx, api.ServiceSpec{