Skip to content

Commit

Permalink
WIP: Add auto capabilities:
Browse files Browse the repository at this point in the history
This adds the following auto capabilites:

discover: creates a Hardware object for each unknown worker.
enrollment: creates Hardware and Workflow objects for each unknown worker.
disabled: auto capabilities are disabled.

Signed-off-by: Jacob Weinstock <[email protected]>
  • Loading branch information
jacobweinstock committed Jun 3, 2024
1 parent adb13fb commit 58196fb
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 15 deletions.
44 changes: 42 additions & 2 deletions cmd/tink-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ type Config struct {
KubeconfigPath string
KubeAPI string
KubeNamespace string

AutoEnrollmentTemplate string
AutoCapMode string
}

const backendKubernetes = "kubernetes"
Expand All @@ -48,6 +51,8 @@ func (c *Config) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&c.KubeconfigPath, "kubeconfig", "", "The path to the Kubeconfig. Only takes effect if `--backend=kubernetes`")
fs.StringVar(&c.KubeAPI, "kubernetes", "", "The Kubernetes API URL, used for in-cluster client construction. Only takes effect if `--backend=kubernetes`")
fs.StringVar(&c.KubeNamespace, "kube-namespace", "", "The Kubernetes namespace to target")
fs.StringVar(&c.AutoEnrollmentTemplate, "auto-enrollment-template", "", "The Template to use for auto enrollment Workflows (only used when `--auto-mode=enrollment`). The Template must exist and is a user defined Template, there is no default.")
fs.Var(newAutoCapModeValue(AutoCapMode(string(server.AutoCapModeDisabled)), (*AutoCapMode)(&c.AutoCapMode)), "auto-cap-mode", "The mode to use for automatic capabilities. Must be one of 'discovery', 'enrollment' or 'disabled'. discovery: creates a Hardware object for each unknown worker, enrollment: creates Hardware and Workflow objects for each unknown worker, disabled: auto capabilities are disabled")

Check warning on line 55 in cmd/tink-server/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/tink-server/main.go#L54-L55

Added lines #L54 - L55 were not covered by tests
}

func (c *Config) PopulateFromLegacyEnvVar() {
Expand All @@ -62,7 +67,7 @@ func (c *Config) PopulateFromLegacyEnvVar() {

func main() {
if err := NewRootCommand().Execute(); err != nil {
fmt.Fprint(os.Stderr, err.Error())
fmt.Fprint(os.Stderr, err.Error(), "\n")

Check warning on line 70 in cmd/tink-server/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/tink-server/main.go#L70

Added line #L70 was not covered by tests
os.Exit(1)
}
}
Expand All @@ -88,7 +93,7 @@ func NewRootCommand() *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
// I am not sure if it is right for this to be here,
// but as last step I want to keep compatibility with
// what we have for a little bit and I thinik that's
// what we have for a little bit and I think that's
// the most aggressive way we have to guarantee that
// the old way works as before.
config.PopulateFromLegacyEnvVar()
Expand All @@ -108,6 +113,10 @@ func NewRootCommand() *cobra.Command {
errCh := make(chan error, 2)
var registrar grpcserver.Registrar

if server.AutoCapMode(config.AutoCapMode) == server.AutoCapModeEnrollment && config.AutoEnrollmentTemplate == "" {
return fmt.Errorf("auto-enrollment-template is required when auto-cap-mode is set to enrollment")

Check warning on line 117 in cmd/tink-server/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/tink-server/main.go#L116-L117

Added lines #L116 - L117 were not covered by tests
}

switch config.Backend {
case backendKubernetes:
var err error
Expand All @@ -116,6 +125,8 @@ func NewRootCommand() *cobra.Command {
config.KubeconfigPath,
config.KubeAPI,
config.KubeNamespace,
server.WithAutoCapMode(server.AutoCapMode(config.AutoCapMode)),
server.WithAutoEnrollmentTemplate(config.AutoEnrollmentTemplate),

Check warning on line 129 in cmd/tink-server/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/tink-server/main.go#L128-L129

Added lines #L128 - L129 were not covered by tests
)
if err != nil {
return err
Expand Down Expand Up @@ -206,3 +217,32 @@ func applyViper(v *viper.Viper, cmd *cobra.Command) error {

return nil
}

type AutoCapMode server.AutoCapMode

func (a *AutoCapMode) String() string {
return string(*a)

Check warning on line 224 in cmd/tink-server/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/tink-server/main.go#L223-L224

Added lines #L223 - L224 were not covered by tests
}

func (a *AutoCapMode) Set(value string) error {
v := server.AutoCapMode(value)
if v == "" {
v = server.AutoCapModeDisabled

Check warning on line 230 in cmd/tink-server/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/tink-server/main.go#L227-L230

Added lines #L227 - L230 were not covered by tests
}
switch v {
case server.AutoCapModeDiscovery, server.AutoCapModeEnrollment, server.AutoCapModeDisabled:
*a = AutoCapMode(v)
return nil

Check warning on line 235 in cmd/tink-server/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/tink-server/main.go#L232-L235

Added lines #L232 - L235 were not covered by tests
}

return fmt.Errorf("invalid value %q, must be one of %q, %q, or %q", value, server.AutoCapModeDiscovery, server.AutoCapModeEnrollment, server.AutoCapModeDisabled)

Check warning on line 238 in cmd/tink-server/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/tink-server/main.go#L238

Added line #L238 was not covered by tests
}

func (a *AutoCapMode) Type() string {
return "auto capabilities mode"

Check warning on line 242 in cmd/tink-server/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/tink-server/main.go#L241-L242

Added lines #L241 - L242 were not covered by tests
}

func newAutoCapModeValue(val AutoCapMode, p *AutoCapMode) *AutoCapMode {
*p = val
return p

Check warning on line 247 in cmd/tink-server/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/tink-server/main.go#L245-L247

Added lines #L245 - L247 were not covered by tests
}
65 changes: 65 additions & 0 deletions internal/server/auto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package server

import (
"context"
"strings"

"github.com/tinkerbell/tink/api/v1alpha1"
"github.com/tinkerbell/tink/internal/ptr"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)

type AutoCapMode string

var (
AutoCapModeDiscovery AutoCapMode = "discovery"
AutoCapModeEnrollment AutoCapMode = "enrollment"
AutoCapModeDisabled AutoCapMode = "disabled"
)

func (k *KubernetesBackedServer) hardwareObjectExists(ctx context.Context, workerID string) bool {
if err := k.ClientFunc().Get(ctx, types.NamespacedName{Name: strings.ReplaceAll(workerID, ":", "."), Namespace: k.namespace}, &v1alpha1.Hardware{}); err != nil {
return false

Check warning on line 23 in internal/server/auto.go

View check run for this annotation

Codecov / codecov/patch

internal/server/auto.go#L21-L23

Added lines #L21 - L23 were not covered by tests
}
return true

Check warning on line 25 in internal/server/auto.go

View check run for this annotation

Codecov / codecov/patch

internal/server/auto.go#L25

Added line #L25 was not covered by tests
}

func (k *KubernetesBackedServer) createHardwareObject(ctx context.Context, workerID string) error {
hw := &v1alpha1.Hardware{
ObjectMeta: metav1.ObjectMeta{
Name: strings.ReplaceAll(workerID, ":", "."),
Namespace: k.namespace,
},
Spec: v1alpha1.HardwareSpec{
Interfaces: []v1alpha1.Interface{

Check warning on line 35 in internal/server/auto.go

View check run for this annotation

Codecov / codecov/patch

internal/server/auto.go#L28-L35

Added lines #L28 - L35 were not covered by tests
{
DHCP: &v1alpha1.DHCP{
MAC: workerID,
},
Netboot: &v1alpha1.Netboot{
AllowPXE: ptr.Bool(true),
},
},
},
},

Check warning on line 45 in internal/server/auto.go

View check run for this annotation

Codecov / codecov/patch

internal/server/auto.go#L37-L45

Added lines #L37 - L45 were not covered by tests
}
return k.ClientFunc().Create(ctx, hw)

Check warning on line 47 in internal/server/auto.go

View check run for this annotation

Codecov / codecov/patch

internal/server/auto.go#L47

Added line #L47 was not covered by tests
}

func (k *KubernetesBackedServer) createWorkflowObject(ctx context.Context, workerID string) error {
wf := &v1alpha1.Workflow{
ObjectMeta: metav1.ObjectMeta{
Name: strings.ReplaceAll(workerID, ":", "."),
Namespace: k.namespace,
},
Spec: v1alpha1.WorkflowSpec{
HardwareRef: strings.ReplaceAll(workerID, ":", "."),
TemplateRef: k.AutoEnrollmentTemplate,
HardwareMap: map[string]string{
"device_1": workerID,
},
},

Check warning on line 62 in internal/server/auto.go

View check run for this annotation

Codecov / codecov/patch

internal/server/auto.go#L50-L62

Added lines #L50 - L62 were not covered by tests
}
return k.ClientFunc().Create(ctx, wf)

Check warning on line 64 in internal/server/auto.go

View check run for this annotation

Codecov / codecov/patch

internal/server/auto.go#L64

Added line #L64 was not covered by tests
}
48 changes: 35 additions & 13 deletions internal/server/kubernetes_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,38 @@ import (
"sigs.k8s.io/controller-runtime/pkg/cluster"
)

// Option for setting optional KubernetesBackedServer fields.
type Option func(*KubernetesBackedServer)

// KubernetesBackedServer is a server that implements a workflow API.
type KubernetesBackedServer struct {
logger logr.Logger
ClientFunc func() client.Client
namespace string
AutoCapMode AutoCapMode
AutoEnrollmentTemplate string

nowFunc func() time.Time
}

func WithAutoCapMode(mode AutoCapMode) Option {
return func(k *KubernetesBackedServer) {
k.AutoCapMode = mode

Check warning on line 39 in internal/server/kubernetes_api.go

View check run for this annotation

Codecov / codecov/patch

internal/server/kubernetes_api.go#L37-L39

Added lines #L37 - L39 were not covered by tests
}
}

func WithAutoEnrollmentTemplate(name string) Option {
return func(k *KubernetesBackedServer) {
k.AutoEnrollmentTemplate = name

Check warning on line 45 in internal/server/kubernetes_api.go

View check run for this annotation

Codecov / codecov/patch

internal/server/kubernetes_api.go#L43-L45

Added lines #L43 - L45 were not covered by tests
}
}

// +kubebuilder:rbac:groups=tinkerbell.org,resources=hardware;hardware/status,verbs=get;list;watch
// +kubebuilder:rbac:groups=tinkerbell.org,resources=templates;templates/status,verbs=get;list;watch
// +kubebuilder:rbac:groups=tinkerbell.org,resources=workflows;workflows/status,verbs=get;list;watch;update;patch

// NewKubeBackedServer returns a server that implements the Workflow server interface for a given kubeconfig.
func NewKubeBackedServer(logger logr.Logger, kubeconfig, apiserver, namespace string) (*KubernetesBackedServer, error) {
func NewKubeBackedServer(logger logr.Logger, kubeconfig, apiserver, namespace string, opts ...Option) (*KubernetesBackedServer, error) {

Check warning on line 54 in internal/server/kubernetes_api.go

View check run for this annotation

Codecov / codecov/patch

internal/server/kubernetes_api.go#L54

Added line #L54 was not covered by tests
ccfg := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig},
&clientcmd.ConfigOverrides{
Expand All @@ -52,12 +78,12 @@ func NewKubeBackedServer(logger logr.Logger, kubeconfig, apiserver, namespace st
return nil, err
}

return NewKubeBackedServerFromREST(logger, cfg, namespace)
return NewKubeBackedServerFromREST(logger, cfg, namespace, opts...)

Check warning on line 81 in internal/server/kubernetes_api.go

View check run for this annotation

Codecov / codecov/patch

internal/server/kubernetes_api.go#L81

Added line #L81 was not covered by tests
}

// NewKubeBackedServerFromREST returns a server that implements the Workflow
// server interface with the given Kubernetes rest client and namespace.
func NewKubeBackedServerFromREST(logger logr.Logger, config *rest.Config, namespace string) (*KubernetesBackedServer, error) {
func NewKubeBackedServerFromREST(logger logr.Logger, config *rest.Config, namespace string, opts ...Option) (*KubernetesBackedServer, error) {

Check warning on line 86 in internal/server/kubernetes_api.go

View check run for this annotation

Codecov / codecov/patch

internal/server/kubernetes_api.go#L86

Added line #L86 was not covered by tests
clstr, err := cluster.New(config, func(opts *cluster.Options) {
opts.Scheme = controller.DefaultScheme()
opts.Logger = zapr.NewLogger(zap.NewNop())
Expand Down Expand Up @@ -86,21 +112,17 @@ func NewKubeBackedServerFromREST(logger logr.Logger, config *rest.Config, namesp
}
}()

return &KubernetesBackedServer{
k := &KubernetesBackedServer{

Check warning on line 115 in internal/server/kubernetes_api.go

View check run for this annotation

Codecov / codecov/patch

internal/server/kubernetes_api.go#L115

Added line #L115 was not covered by tests
logger: logger,
ClientFunc: clstr.GetClient,
namespace: namespace,
nowFunc: time.Now,
}, nil
}

// KubernetesBackedServer is a server that implements a workflow API.
type KubernetesBackedServer struct {
logger logr.Logger
ClientFunc func() client.Client
namespace string
}
for _, opt := range opts {
opt(k)

Check warning on line 122 in internal/server/kubernetes_api.go

View check run for this annotation

Codecov / codecov/patch

internal/server/kubernetes_api.go#L121-L122

Added lines #L121 - L122 were not covered by tests
}

nowFunc func() time.Time
return k, nil

Check warning on line 125 in internal/server/kubernetes_api.go

View check run for this annotation

Codecov / codecov/patch

internal/server/kubernetes_api.go#L125

Added line #L125 was not covered by tests
}

// Register registers the service on the gRPC server.
Expand Down
23 changes: 23 additions & 0 deletions internal/server/kubernetes_api_workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,29 @@ func (s *KubernetesBackedServer) GetWorkflowContexts(req *proto.WorkflowContextR
if err != nil {
return err
}

ctx := context.TODO()
id := req.WorkerId
if s.AutoCapMode != AutoCapModeDisabled && len(wflows) == 0 && (s.AutoCapMode == AutoCapModeDiscovery || s.AutoCapMode == AutoCapModeEnrollment) && !s.hardwareObjectExists(ctx, id) {

Check warning on line 78 in internal/server/kubernetes_api_workflow.go

View check run for this annotation

Codecov / codecov/patch

internal/server/kubernetes_api_workflow.go#L76-L78

Added lines #L76 - L78 were not covered by tests
// In the future, the worker could be signaled to send hardware device information to be used in creation of the Hardware object.
// or the proto.WorkflowContextRequest could be extended to include Hardware information.
if err := s.createHardwareObject(ctx, id); err != nil {
s.logger.Error(err, "failed to create hardware object")
return err

Check warning on line 83 in internal/server/kubernetes_api_workflow.go

View check run for this annotation

Codecov / codecov/patch

internal/server/kubernetes_api_workflow.go#L81-L83

Added lines #L81 - L83 were not covered by tests
}

if s.AutoCapMode == AutoCapModeEnrollment {
if err := s.createWorkflowObject(ctx, id); err != nil {
s.logger.Error(err, "failed to create workflow object")
return err

Check warning on line 89 in internal/server/kubernetes_api_workflow.go

View check run for this annotation

Codecov / codecov/patch

internal/server/kubernetes_api_workflow.go#L86-L89

Added lines #L86 - L89 were not covered by tests
}
wflows, err = s.getCurrentAssignedNonTerminalWorkflowsForWorker(stream.Context(), req.WorkerId)
if err != nil {
return err

Check warning on line 93 in internal/server/kubernetes_api_workflow.go

View check run for this annotation

Codecov / codecov/patch

internal/server/kubernetes_api_workflow.go#L91-L93

Added lines #L91 - L93 were not covered by tests
}
}
}

for _, wf := range wflows {
if err := stream.Send(getWorkflowContext(wf)); err != nil {
return err
Expand Down

0 comments on commit 58196fb

Please sign in to comment.