Files
uncloud/pkg/client/machine.go
T
2026-03-09 17:38:37 +10:00

150 lines
4.3 KiB
Go

package client
import (
"context"
"fmt"
"strings"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/psviderski/uncloud/internal/machine/api/pb"
"github.com/psviderski/uncloud/pkg/api"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)
func (cli *Client) InspectMachine(ctx context.Context, nameOrID string) (*pb.MachineMember, error) {
// TODO: refactor to use MachineClient.InspectMachine.
machines, err := cli.ListMachines(ctx, nil)
if err != nil {
return nil, err
}
for _, m := range machines {
if m.Machine.Id == nameOrID || m.Machine.Name == nameOrID {
return m, nil
}
}
return nil, api.ErrNotFound
}
// ListMachines returns a list of all machines registered in the cluster that match the filter.
func (cli *Client) ListMachines(ctx context.Context, filter *api.MachineFilter) (api.MachineMembersList, error) {
resp, err := cli.ClusterClient.ListMachines(ctx, &emptypb.Empty{})
if err != nil {
return nil, err
}
machines := api.MachineMembersList(resp.Machines)
if filter == nil {
return machines, nil
}
// Apply the filter.
if len(filter.NamesOrIDs) > 0 {
var matched api.MachineMembersList
var notFound []string
for _, nameOrID := range filter.NamesOrIDs {
if m := machines.FindByNameOrID(nameOrID); m != nil {
matched = append(matched, m)
} else {
notFound = append(notFound, nameOrID)
}
}
machines = matched
if len(notFound) > 0 {
return nil, fmt.Errorf("machines not found: %s", strings.Join(notFound, ", "))
}
}
if filter.Available {
var available api.MachineMembersList
for _, m := range machines {
if m.State != pb.MachineMember_DOWN {
available = append(available, m)
}
}
machines = available
}
return machines, nil
}
// UpdateMachine updates machine configuration in the cluster.
func (cli *Client) UpdateMachine(ctx context.Context, req *pb.UpdateMachineRequest) (*pb.MachineInfo, error) {
resp, err := cli.ClusterClient.UpdateMachine(ctx, req)
if err != nil {
if s, ok := status.FromError(err); ok && s.Code() == codes.NotFound {
return nil, api.ErrNotFound
}
return nil, err
}
return resp.Machine, nil
}
// RenameMachine renames an existing machine in the cluster.
func (cli *Client) RenameMachine(ctx context.Context, nameOrID, newName string) (*pb.MachineInfo, error) {
// First, resolve the machine to get its ID
machine, err := cli.InspectMachine(ctx, nameOrID)
if err != nil {
return nil, err
}
// Update the machine with the new name
req := &pb.UpdateMachineRequest{
MachineId: machine.Machine.Id,
Name: &newName,
}
return cli.UpdateMachine(ctx, req)
}
// WaitMachineReady waits for the machine API on the connected machine to respond.
func (cli *Client) WaitMachineReady(ctx context.Context, timeout time.Duration) error {
boff := backoff.WithContext(backoff.NewExponentialBackOff(
backoff.WithInitialInterval(100*time.Millisecond),
backoff.WithMaxInterval(1*time.Second),
backoff.WithMaxElapsedTime(timeout),
), ctx)
inspect := func() error {
if _, err := cli.Inspect(ctx, &emptypb.Empty{}); err != nil {
return fmt.Errorf("inspect machine: %w", err)
}
return nil
}
return backoff.Retry(inspect, boff)
}
// WaitClusterReady waits for the connected machine to be ready to serve cluster requests.
func (cli *Client) WaitClusterReady(ctx context.Context, timeout time.Duration) error {
// Backoff is not really needed here as the default service config for the gRPC client is already
// doing retries with backoff for Unavailable errors. However, it's still convenient to use backoff
// to control the overall timeout for the operation.
boff := backoff.WithContext(backoff.NewExponentialBackOff(
backoff.WithInitialInterval(100*time.Millisecond),
backoff.WithMaxInterval(1*time.Second),
backoff.WithMaxElapsedTime(timeout),
), ctx)
listMachines := func() error {
_, err := cli.ListMachines(ctx, nil)
if err != nil {
if s, ok := status.FromError(err); ok &&
// TODO: remove FailedPrecondition after releading 0.17.
(s.Code() == codes.Unavailable || s.Code() == codes.FailedPrecondition) {
// Machine is not ready yet, retry.
return err
}
// Other non-Unavailable errors should not be retried.
return backoff.Permanent(err)
}
return nil
}
return backoff.Retry(listMachines, boff)
}