~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
122 lines · 3.7 KB Raw History
package clusterprovider

import (
	"context"
	"errors"
	"fmt"
	"net"
	"strconv"
	"time"

	"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/apiserverfront"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/clusterstack"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/portallocator"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/procgroup"
)

const HoldTimeout = 15 * time.Second

func (cfg StackConfig) FrontStep(cluster string) (procgroup.Step, error) {
	dataDir, err := ClusterDataDir(cfg.StateDir, cluster)
	if err != nil {
		return procgroup.Step{}, err
	}
	return frontStep(clusterstack.Spec{
		DataDir:      dataDir,
		Cluster:      cluster,
		BindAddress:  cfg.BindAddress,
		FrontExec:    cfg.FrontExec,
		FrontControl: FrontControlPath(cfg.StateDir, dataDir, cluster),
		FrontState:   FrontStatePath(dataDir),
	})
}

func frontStep(spec clusterstack.Spec) (procgroup.Step, error) {
	argv, err := spec.FrontArgv()
	if err != nil {
		return procgroup.Step{}, err
	}
	log, err := componentLog(spec.DataDir, procgroup.ComponentApiserverFront)
	if err != nil {
		return procgroup.Step{}, err
	}
	control := spec.FrontControl
	return procgroup.Step{
		Name: procgroup.ComponentApiserverFront,
		Spec: procgroup.Spec{Argv: argv, Log: log},
		Ready: func(ctx context.Context, _ procgroup.Member) error {
			ctx, cancel := context.WithTimeout(ctx, GateTimeout)
			defer cancel()
			return waitFront(ctx, control)
		},
	}, nil
}

func (h *UnitsHost) Hold(ctx context.Context, key string, p portallocator.Purpose) (int, error) {
	ctx, cancel := context.WithTimeout(ctx, HoldTimeout)
	defer cancel()
	if p == portallocator.PurposeK3sSecure {
		return h.frontPort(ctx, key)
	}
	ctl, err := h.FrontControl(key)
	if err != nil {
		return 0, err
	}
	port, err := (apiserverfront.Client{Path: ctl}).Hold(ctx, string(p))
	if err != nil {
		return 0, fmt.Errorf("clusterprovider: the apiserver front of %s holds no port for %s: %w", key, p, err)
	}
	return port, nil
}

var ErrNoApiserverFront = errors.New("clusterprovider: the cluster has no apiserver front HostProcess")

func (h *UnitsHost) frontPort(ctx context.Context, key string) (int, error) {
	member := MemberName(procgroup.ComponentApiserverFront)
	last := "no answer"
	port := 0
	_, err := h.hps.Await(ctx, ObjectName(key, member), 0, func(o *v1alpha1.HostProcess) (bool, error) {
		switch {
		case o == nil:
			return false, fmt.Errorf("%w: %s, so nothing holds a port for its apiserver", ErrNoApiserverFront, key)
		case o.DeletionTimestamp != nil:
			return false, fmt.Errorf("clusterprovider: the apiserver front of %s is being deleted", key)
		case o.Status.Phase == v1alpha1.HostProcessPhaseFailed || o.Status.Phase == v1alpha1.HostProcessPhaseLost ||
			o.Status.Phase == v1alpha1.HostProcessPhaseStopped:
			return false, fmt.Errorf("clusterprovider: the apiserver front of %s is %s %s: %s", key, o.Status.Phase,
				o.Status.LostReason, o.Status.Message)
		}
		if p, ok := tcpListenerPort(o.Status.Listeners); ok {
			port = p
			return true, nil
		}
		last = fmt.Sprintf("phase %s, listeners %v", o.Status.Phase, o.Status.Listeners)
		return false, nil
	})
	if err == nil {
		return port, nil
	}
	if ctx.Err() != nil {
		return 0, fmt.Errorf("clusterprovider: the apiserver front of %s reported no listener into its HostProcess status (last: %s)", key, last)
	}
	return 0, err
}

func tcpListenerPort(ls []v1alpha1.HostProcessListener) (int, bool) {
	for _, l := range ls {
		if l.Name != "tcp" {
			continue
		}
		_, p, err := net.SplitHostPort(l.Address)
		if err != nil {
			return 0, false
		}
		port, err := strconv.Atoi(p)
		if err != nil || port <= 0 {
			return 0, false
		}
		return port, true
	}
	return 0, false
}