Skip to content

Commit

Permalink
Update machine controller to implement provider opts:
Browse files Browse the repository at this point in the history
This makes the provider opts in the CRD operational
via the bmclib client.

Rename pkg; controllers -> controller: this is the
preferred practive in Go.

Added a requeue interval.

Signed-off-by: Jacob Weinstock <jakobweinstock@gmail.com>
  • Loading branch information
jacobweinstock committed Sep 20, 2023
1 parent 1656c1c commit 70c2f54
Show file tree
Hide file tree
Showing 18 changed files with 403 additions and 218 deletions.
7 changes: 7 additions & 0 deletions config/samples/hmac-secret1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: v1
kind: Secret
metadata:
name: secret1
type: Opaque
data: # echo -n 'superSecret1' | base64;
secret: c3VwZXJTZWNyZXQx
7 changes: 7 additions & 0 deletions config/samples/hmac-secret2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: v1
kind: Secret
metadata:
name: secret2
type: Opaque
data: # echo -n 'superSecret2' | base64;
secret: c3VwZXJTZWNyZXQy
3 changes: 2 additions & 1 deletion config/samples/machine_v1alpha1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ metadata:
spec:
connection:
host: 127.0.0.1
port: 623
authSecretRef:
name: sample-machine-auth
namespace: rufio-system
insecureTLS: true


25 changes: 25 additions & 0 deletions config/samples/machine_with_opts_v1alpha1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
apiVersion: bmc.tinkerbell.org/v1alpha1
kind: Machine
metadata:
name: machine-sample-with-opts
spec:
connection:
host: 127.0.0.1
insecureTLS: true
providerOptions:
rpc:
consumerURL: "https://example.com/rpc"
hmac:
secrets:
sha256:
- name: secret1
namespace: default
- name: secret2
namespace: default
sha512:
- name: secret1
namespace: default
- name: secret2
namespace: default


1 change: 0 additions & 1 deletion config/samples/task_v1alpha1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ metadata:
spec:
connection:
host: 127.0.0.1
port: 623
authSecretRef:
name: sample-machine-auth
namespace: rufio-system
Expand Down
142 changes: 142 additions & 0 deletions controller/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package controller

import (
"context"
"fmt"
"strconv"
"time"

"dario.cat/mergo"
bmclib "github.com/bmc-toolbox/bmclib/v2"
"github.com/bmc-toolbox/bmclib/v2/providers/rpc"
"github.com/go-logr/logr"
"github.com/tinkerbell/rufio/api/v1alpha1"
)

// ClientFunc defines a func that returns a bmclib.Client.
type ClientFunc func(ctx context.Context, log logr.Logger, hostIP, username, password string, opts *BMCOptions) (*bmclib.Client, error)

// NewClientFunc returns a new BMCClientFactoryFunc. The timeout parameter determines the
// maximum time to probe for compatible interfaces.
func NewClientFunc(timeout time.Duration) ClientFunc {
// Initializes a bmclib client based on input host and credentials
// Establishes a connection with the bmc with client.Open
// Returns a bmclib.Client.
return func(ctx context.Context, log logr.Logger, hostIP, username, password string, opts *BMCOptions) (*bmclib.Client, error) {
o := opts.translate(hostIP)
log = log.WithValues("host", hostIP, "username", username)
o = append(o, bmclib.WithLogger(log))
client := bmclib.NewClient(hostIP, username, password, o...)

ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

// TODO (pokearu): Make an option
client.Registry.Drivers = client.Registry.PreferProtocol("redfish")
if err := client.Open(ctx); err != nil {
md := client.GetMetadata()
log.Info("Failed to open connection to BMC", "error", err, "providersAttempted", md.ProvidersAttempted, "successfulProvider", md.SuccessfulOpenConns)

return nil, fmt.Errorf("failed to open connection to BMC: %w", err)
}
md := client.GetMetadata()
log.Info("Connected to BMC", "providersAttempted", md.ProvidersAttempted, "successfulProvider", md.SuccessfulOpenConns)

return client, nil
}
}

type BMCOptions struct {
*v1alpha1.ProviderOptions
rpcSecrets map[rpc.Algorithm][]string
}

func (b BMCOptions) translate(host string) []bmclib.Option {
o := []bmclib.Option{}

if b.ProviderOptions == nil {
return o
}

// redfish options
if b.Redfish != nil {
if b.Redfish.Port != 0 {
o = append(o, bmclib.WithRedfishPort(strconv.Itoa(b.Redfish.Port)))
}
}

// ipmitool options
if b.IPMITOOL != nil {
if b.IPMITOOL.Port != 0 {
o = append(o, bmclib.WithIpmitoolPort(strconv.Itoa(b.IPMITOOL.Port)))
}
if b.IPMITOOL.CipherSuite != "" {
o = append(o, bmclib.WithIpmitoolCipherSuite(b.IPMITOOL.CipherSuite))
}
}

// intelAmt options
if b.IntelAMT != nil {
amt := bmclib.WithIntelAMTPort(uint32(b.IntelAMT.Port))
o = append(o, amt)
}

// rpc options
if b.RPC != nil {
op := b.translateRPC(host)
o = append(o, bmclib.WithRPCOpt(op))
}

return o
}

func (b BMCOptions) translateRPC(host string) rpc.Provider {
s := map[rpc.Algorithm][]string{}
if b.rpcSecrets != nil {
s = b.rpcSecrets
}

defaults := rpc.Provider{
Opts: rpc.Opts{
Request: rpc.RequestOpts{
TimestampHeader: "X-Rufio-Timestamp",
},
Signature: rpc.SignatureOpts{
HeaderName: "X-Rufio-Signature",
IncludedPayloadHeaders: []string{"X-Rufio-Timestamp"},
},
},
}
o := rpc.Provider{
ConsumerURL: b.RPC.ConsumerURL,
Host: host,
Opts: rpc.Opts{
Request: rpc.RequestOpts{
HTTPContentType: b.RPC.Request.HTTPContentType,
HTTPMethod: b.RPC.Request.HTTPMethod,
StaticHeaders: b.RPC.Request.StaticHeaders,
TimestampFormat: b.RPC.Request.TimestampFormat,
TimestampHeader: b.RPC.Request.TimestampHeader,
},
Signature: rpc.SignatureOpts{
HeaderName: b.RPC.Signature.HeaderName,
AppendAlgoToHeaderDisabled: b.RPC.Signature.AppendAlgoToHeaderDisabled,
IncludedPayloadHeaders: b.RPC.Signature.IncludedPayloadHeaders,
},
HMAC: rpc.HMACOpts{
PrefixSigDisabled: b.RPC.HMAC.PrefixSigDisabled,
Secrets: s,
},
Experimental: rpc.Experimental{
CustomRequestPayload: []byte(b.RPC.Experimental.CustomRequestPayload),
DotPath: b.RPC.Experimental.DotPath,
},
},
}

_ = mergo.Merge(&o, &defaults, mergo.WithOverride, mergo.WithTransformers(&rpc.Provider{}))

return o
}

// func (r BMCOptions)
8 changes: 4 additions & 4 deletions controllers/helpers_test.go → controller/helpers_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package controllers_test
package controller_test

import (
"context"
Expand All @@ -8,7 +8,7 @@ import (
"github.com/go-logr/logr"
"github.com/jacobweinstock/registrar"
"github.com/tinkerbell/rufio/api/v1alpha1"
"github.com/tinkerbell/rufio/controllers"
"github.com/tinkerbell/rufio/controller"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
Expand Down Expand Up @@ -95,8 +95,8 @@ func (t *testProvider) SetVirtualMedia(_ context.Context, _ string, _ string) (o
}

// newMockBMCClientFactoryFunc returns a new BMCClientFactoryFunc.
func newTestClient(provider *testProvider) controllers.ClientFunc {
return func(ctx context.Context, log logr.Logger, hostIP, port, username, password string) (*bmclib.Client, error) {
func newTestClient(provider *testProvider) controller.ClientFunc {
return func(ctx context.Context, log logr.Logger, hostIP, username, password string, opts *controller.BMCOptions) (*bmclib.Client, error) {
reg := registrar.NewRegistry(registrar.WithLogger(log))
reg.Register(provider.Name(), provider.Protocol(), provider.Features(), nil, provider)
cl := bmclib.NewClient(hostIP, username, password, bmclib.WithLogger(log), bmclib.WithRegistry(reg))
Expand Down
2 changes: 1 addition & 1 deletion controllers/job.go → controller/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package controllers
package controller

import (
"context"
Expand Down
8 changes: 4 additions & 4 deletions controllers/job_test.go → controller/job_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package controllers_test
package controller_test

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/tinkerbell/rufio/api/v1alpha1"
"github.com/tinkerbell/rufio/controllers"
"github.com/tinkerbell/rufio/controller"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
Expand Down Expand Up @@ -44,10 +44,10 @@ func TestJobReconcile(t *testing.T) {
clnt := newClientBuilder().
WithObjects(tt.job, tt.machine, tt.secret).
WithStatusSubresource(tt.job, tt.machine).
WithIndex(&v1alpha1.Task{}, ".metadata.controller", controllers.TaskOwnerIndexFunc).
WithIndex(&v1alpha1.Task{}, ".metadata.controller", controller.TaskOwnerIndexFunc).
Build()

reconciler := controllers.NewJobReconciler(clnt)
reconciler := controller.NewJobReconciler(clnt)

request := reconcile.Request{
NamespacedName: types.NamespacedName{
Expand Down
55 changes: 55 additions & 0 deletions controller/kube.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package controller

import (
"context"
"fmt"
"strings"

"github.com/tinkerbell/rufio/api/v1alpha1"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// resolveAuthSecretRef Gets the Secret from the SecretReference.
// Returns the username and password encoded in the Secret.
func resolveAuthSecretRef(ctx context.Context, c client.Client, secretRef v1.SecretReference) (string, string, error) {
secret := &v1.Secret{}
key := types.NamespacedName{Namespace: secretRef.Namespace, Name: secretRef.Name}

if err := c.Get(ctx, key, secret); err != nil {
if apierrors.IsNotFound(err) {
return "", "", fmt.Errorf("secret %s not found: %w", key, err)
}

return "", "", fmt.Errorf("failed to retrieve secret %s : %w", secretRef, err)
}

username, ok := secret.Data["username"]
if !ok {
return "", "", fmt.Errorf("'username' required in Machine secret")
}

password, ok := secret.Data["password"]
if !ok {
return "", "", fmt.Errorf("'password' required in Machine secret")
}

return string(username), string(password), nil
}

// toPowerState takes a raw BMC power state response and converts it to a v1alpha1.PowerState.
func toPowerState(state string) v1alpha1.PowerState {
// Normalize the response string for comparison.
state = strings.ToLower(state)

switch {
case strings.Contains(state, "on"):
return v1alpha1.On
case strings.Contains(state, "off"):
return v1alpha1.Off
default:
return v1alpha1.Unknown
}
}
Loading

0 comments on commit 70c2f54

Please sign in to comment.