Skip to content

Commit 14eaefc

Browse files
Merge pull request #494 from MahnoorAsghar/preprovisioning-4.19
OCPBUGS-87966: Fix preprovisioning network Secret lifecycle during BMH deletion
2 parents 2be798a + 9de01c4 commit 14eaefc

4 files changed

Lines changed: 145 additions & 14 deletions

File tree

controllers/metal3.io/baremetalhost_controller.go

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
k8serrors "k8s.io/apimachinery/pkg/api/errors"
3939
"k8s.io/apimachinery/pkg/api/meta"
4040
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
41+
"k8s.io/apimachinery/pkg/types"
4142
ctrl "sigs.k8s.io/controller-runtime"
4243
"sigs.k8s.io/controller-runtime/pkg/builder"
4344
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -69,13 +70,14 @@ type BareMetalHostReconciler struct {
6970
// Instead of passing a zillion arguments to the action of a phase,
7071
// hold them in a context.
7172
type reconcileInfo struct {
72-
ctx context.Context
73-
log logr.Logger
74-
host *metal3api.BareMetalHost
75-
request ctrl.Request
76-
bmcCredsSecret *corev1.Secret
77-
events []corev1.Event
78-
postSaveCallbacks []func()
73+
ctx context.Context
74+
log logr.Logger
75+
host *metal3api.BareMetalHost
76+
request ctrl.Request
77+
bmcCredsSecret *corev1.Secret
78+
preprovisioningNetworkDataSecret *corev1.Secret
79+
events []corev1.Event
80+
postSaveCallbacks []func()
7981
}
8082

8183
// match the provisioner.EventPublisher interface.
@@ -201,13 +203,30 @@ func (r *BareMetalHostReconciler) Reconcile(ctx context.Context, request ctrl.Re
201203
}
202204
}
203205

206+
var preprovisioningNetworkDataSecret *corev1.Secret
207+
if host.Spec.PreprovisioningNetworkDataName != "" &&
208+
host.Status.Provisioning.State != metal3api.StateNone &&
209+
host.Status.Provisioning.State != metal3api.StateUnmanaged {
210+
preprovisioningNetworkDataSecret, err = r.acquirePreprovisioningNetworkDataSecret(ctx, host)
211+
if err != nil {
212+
if hostInDeletionFlow(host) && k8serrors.IsNotFound(err) {
213+
preprovisioningNetworkDataSecret = &corev1.Secret{}
214+
} else if !hostInDeletionFlow(host) {
215+
reqLogger.Info("failed to acquire preprovisioning network data secret", "error", err)
216+
} else {
217+
return ctrl.Result{}, fmt.Errorf("failed to acquire preprovisioning network data secret during deletion: %w", err)
218+
}
219+
}
220+
}
221+
204222
initialState := host.Status.Provisioning.State
205223
info := &reconcileInfo{
206-
ctx: ctx,
207-
log: reqLogger.WithValues("provisioningState", initialState),
208-
host: host,
209-
request: request,
210-
bmcCredsSecret: bmcCredsSecret,
224+
ctx: ctx,
225+
log: reqLogger.WithValues("provisioningState", initialState),
226+
host: host,
227+
request: request,
228+
bmcCredsSecret: bmcCredsSecret,
229+
preprovisioningNetworkDataSecret: preprovisioningNetworkDataSecret,
211230
}
212231

213232
prov, err := r.ProvisionerFactory.NewProvisioner(ctx, provisioner.BuildHostData(*host, *bmcCreds), info.publishEvent)
@@ -554,6 +573,13 @@ func (r *BareMetalHostReconciler) actionDeleting(prov provisioner.Provisioner, i
554573
return actionError{err}
555574
}
556575

576+
if info.preprovisioningNetworkDataSecret != nil && info.preprovisioningNetworkDataSecret.Name != "" {
577+
err = secretManager.ReleaseSecret(info.preprovisioningNetworkDataSecret)
578+
if err != nil {
579+
return actionError{err}
580+
}
581+
}
582+
557583
info.host.Finalizers = utils.FilterStringFromList(
558584
info.host.Finalizers, metal3api.BareMetalHostFinalizer)
559585
info.log.Info("cleanup is complete, removed finalizer",
@@ -2220,6 +2246,22 @@ func (r *BareMetalHostReconciler) getBMCSecretAndSetOwner(ctx context.Context, r
22202246
return bmcCredsSecret, nil
22212247
}
22222248

2249+
// acquirePreprovisioningNetworkDataSecret claims the Secret referenced by
2250+
// spec.preprovisioningNetworkDataName with a finalizer so it is not removed
2251+
// before the host finishes deletion. Callers must ensure
2252+
// spec.preprovisioningNetworkDataName is set.
2253+
func (r *BareMetalHostReconciler) acquirePreprovisioningNetworkDataSecret(ctx context.Context, host *metal3api.BareMetalHost) (*corev1.Secret, error) {
2254+
secretManager := r.secretManager(ctx, r.Log.WithValues(
2255+
"baremetalhost", types.NamespacedName{Namespace: host.Namespace, Name: host.Name},
2256+
))
2257+
key := types.NamespacedName{
2258+
Name: host.Spec.PreprovisioningNetworkDataName,
2259+
Namespace: host.Namespace,
2260+
}
2261+
2262+
return secretManager.ObtainSecretWithFinalizer(key, host.Status.Provisioning.State != metal3api.StateDeleting)
2263+
}
2264+
22232265
func credentialsFromSecret(bmcCredsSecret *corev1.Secret) *bmc.Credentials {
22242266
// We trim surrounding whitespace because those characters are
22252267
// unlikely to be part of the username or password and it is

controllers/metal3.io/host_config_data.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,24 @@ import (
66
"github.com/metal3-io/baremetal-operator/pkg/secretutils"
77
"github.com/pkg/errors"
88
corev1 "k8s.io/api/core/v1"
9+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
910
"k8s.io/apimachinery/pkg/types"
1011
)
1112

13+
// hostInDeletionFlow reports whether the host is being removed. During this
14+
// window a missing preprovisioning network Secret should not block progress.
15+
func hostInDeletionFlow(host *metal3api.BareMetalHost) bool {
16+
if !host.DeletionTimestamp.IsZero() {
17+
return true
18+
}
19+
switch host.Status.Provisioning.State {
20+
case metal3api.StateDeleting, metal3api.StatePoweringOffBeforeDelete:
21+
return true
22+
default:
23+
return false
24+
}
25+
}
26+
1227
// hostConfigData is an implementation of host configuration data interface.
1328
// Object is able to retrieve data from secrets referenced in a host spec.
1429
type hostConfigData struct {
@@ -20,7 +35,7 @@ type hostConfigData struct {
2035
// Generic method for data extraction from a Secret. Function uses dataKey
2136
// parameter to detirmine which data to return in case secret contins multiple
2237
// keys.
23-
func (hcd *hostConfigData) getSecretData(name, namespace, dataKey string) (string, error) {
38+
func (hcd *hostConfigData) getSecretData(name, namespace, dataKey string, addFinalizer bool) (string, error) {
2439
if namespace != hcd.host.Namespace {
2540
return "", errors.Errorf("%s secret must be in same namespace as host %s/%s", dataKey, hcd.host.Namespace, hcd.host.Name)
2641
}
@@ -30,7 +45,7 @@ func (hcd *hostConfigData) getSecretData(name, namespace, dataKey string) (strin
3045
Namespace: namespace,
3146
}
3247

33-
secret, err := hcd.secretManager.ObtainSecret(key)
48+
secret, err := hcd.secretManager.ObtainSecretWithFinalizer(key, addFinalizer)
3449
if err != nil {
3550
return "", err
3651
}
@@ -63,6 +78,7 @@ func (hcd *hostConfigData) UserData() (string, error) {
6378
hcd.host.Spec.UserData.Name,
6479
namespace,
6580
"userData",
81+
false,
6682
)
6783
}
6884

@@ -86,6 +102,7 @@ func (hcd *hostConfigData) NetworkData() (string, error) {
86102
networkData.Name,
87103
namespace,
88104
"networkData",
105+
false,
89106
)
90107
if err != nil {
91108
_, isNoDataErr := err.(NoDataInSecretError)
@@ -102,17 +119,23 @@ func (hcd *hostConfigData) PreprovisioningNetworkData() (string, error) {
102119
if hcd.host.Spec.PreprovisioningNetworkDataName == "" {
103120
return "", nil
104121
}
122+
addFinalizer := !hostInDeletionFlow(hcd.host)
105123
networkDataRaw, err := hcd.getSecretData(
106124
hcd.host.Spec.PreprovisioningNetworkDataName,
107125
hcd.host.Namespace,
108126
"networkData",
127+
addFinalizer,
109128
)
110129
if err != nil {
111130
_, isNoDataErr := err.(NoDataInSecretError)
112131
if isNoDataErr {
113132
hcd.log.Info("PreprovisioningNetworkData networkData key is not set, returning empty data")
114133
return "", nil
115134
}
135+
if k8serrors.IsNotFound(err) && hostInDeletionFlow(hcd.host) {
136+
hcd.log.Info("PreprovisioningNetworkData secret not found during host deletion, returning empty data")
137+
return "", nil
138+
}
116139
}
117140
return networkDataRaw, err
118141
}
@@ -131,5 +154,6 @@ func (hcd *hostConfigData) MetaData() (string, error) {
131154
hcd.host.Spec.MetaData.Name,
132155
namespace,
133156
"metaData",
157+
false,
134158
)
135159
}

controllers/metal3.io/host_config_data_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/stretchr/testify/assert"
1212
"github.com/stretchr/testify/require"
1313
corev1 "k8s.io/api/core/v1"
14+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1415
"k8s.io/apimachinery/pkg/types"
1516
ctrl "sigs.k8s.io/controller-runtime"
1617
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
@@ -58,6 +59,15 @@ func TestLabelSecrets(t *testing.T) {
5859
},
5960
},
6061
},
62+
{
63+
name: "preprovisioning-network-data",
64+
getter: func(hcd *hostConfigData) (string, error) {
65+
return hcd.PreprovisioningNetworkData()
66+
},
67+
hostSpec: &metal3api.BareMetalHostSpec{
68+
PreprovisioningNetworkDataName: "preprovisioning-network-data",
69+
},
70+
},
6171
}
6272
for _, tc := range testCases {
6373
t.Run(tc.name, func(t *testing.T) {
@@ -85,6 +95,54 @@ func TestLabelSecrets(t *testing.T) {
8595
}
8696
}
8797

98+
func TestAcquirePreprovisioningNetworkSecret(t *testing.T) {
99+
host := newHost("host", &metal3api.BareMetalHostSpec{
100+
PreprovisioningNetworkDataName: "preprov-net-data",
101+
})
102+
host.Status.Provisioning.State = metal3api.StateRegistering
103+
104+
secret := newSecret("preprov-net-data", map[string]string{"networkData": "key: value"})
105+
c := fakeclient.NewClientBuilder().WithObjects(host, secret).Build()
106+
baselog := ctrl.Log.WithName("controllers").WithName("BareMetalHost")
107+
hcd := &hostConfigData{
108+
host: host,
109+
log: baselog.WithName("host_config_data"),
110+
secretManager: secretutils.NewSecretManager(context.TODO(), baselog, c, c),
111+
}
112+
113+
_, err := hcd.PreprovisioningNetworkData()
114+
require.NoError(t, err)
115+
116+
actualSecret := &corev1.Secret{}
117+
err = c.Get(context.TODO(), types.NamespacedName{Name: "preprov-net-data", Namespace: namespace}, actualSecret)
118+
require.NoError(t, err)
119+
assert.Equal(t, secretutils.LabelEnvironmentValue, actualSecret.Labels[secretutils.LabelEnvironmentName])
120+
assert.Contains(t, actualSecret.Finalizers, secretutils.SecretsFinalizer)
121+
assert.Empty(t, actualSecret.OwnerReferences)
122+
}
123+
124+
func TestPreprovisioningNetworkSecretNotFoundDuringDeletion(t *testing.T) {
125+
host := newHost("host", &metal3api.BareMetalHostSpec{
126+
PreprovisioningNetworkDataName: "missing-preprov-net",
127+
})
128+
host.Status.Provisioning.State = metal3api.StatePoweringOffBeforeDelete
129+
now := metav1.Now()
130+
host.DeletionTimestamp = &now
131+
host.Finalizers = []string{metal3api.BareMetalHostFinalizer}
132+
133+
c := fakeclient.NewClientBuilder().WithObjects(host).Build()
134+
baselog := ctrl.Log.WithName("controllers").WithName("BareMetalHost")
135+
hcd := &hostConfigData{
136+
host: host,
137+
log: baselog.WithName("host_config_data"),
138+
secretManager: secretutils.NewSecretManager(context.TODO(), baselog, c, c),
139+
}
140+
141+
data, err := hcd.PreprovisioningNetworkData()
142+
require.NoError(t, err)
143+
assert.Empty(t, data)
144+
}
145+
88146
func TestProvisionWithHostConfig(t *testing.T) {
89147
testBMCSecret := newBMCCredsSecret(defaultSecretName, "User", "Pass")
90148

pkg/secretutils/secret_manager.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,13 @@ func (sm *SecretManager) ObtainSecret(key types.NamespacedName) (*corev1.Secret,
147147
return sm.obtainSecretForOwner(key, nil, false)
148148
}
149149

150+
// ObtainSecretWithFinalizer retrieves a Secret and ensures that it has a label
151+
// that will ensure it is present in the cache, and optionally adds the secrets
152+
// manager finalizer without setting an owner reference.
153+
func (sm *SecretManager) ObtainSecretWithFinalizer(key types.NamespacedName, addFinalizer bool) (*corev1.Secret, error) {
154+
return sm.obtainSecretForOwner(key, nil, addFinalizer)
155+
}
156+
150157
// ReleaseSecret removes secrets manager finalizer from specified secret when needed.
151158
func (sm *SecretManager) ReleaseSecret(secret *corev1.Secret) error {
152159
if !utils.StringInList(secret.Finalizers, SecretsFinalizer) {

0 commit comments

Comments
 (0)