Skip to content

Commit 837b07f

Browse files
authored
Merge pull request #633 from stuggi/OSPRH-18561
Add PodDisruptionBudget management functions to lib-common
2 parents a580748 + 017dadb commit 837b07f

File tree

7 files changed

+1186
-0
lines changed

7 files changed

+1186
-0
lines changed

modules/common/condition/conditions.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ const (
8989
// TopologyReadyCondition Status=True condition that indicates a CR
9090
// exists and is referenced by the Service
9191
TopologyReadyCondition Type = "TopologyReady"
92+
93+
// PDBReadyCondition Status=True condition which indicates if PodDisruptionBudget is configured and operational
94+
PDBReadyCondition Type = "PDBReady"
9295
)
9396

9497
// Common Reasons used by API objects.
@@ -379,6 +382,19 @@ const (
379382
TopologyReadyMessage = "Topology config create completed"
380383
// TopologyReadyErrorMessage
381384
TopologyReadyErrorMessage = "Topology config create error occurred %s"
385+
386+
//
387+
// PDBReady condition messages
388+
//
389+
390+
// PDBReadyInitMessage
391+
PDBReadyInitMessage = "PodDisruptionBudget not configured"
392+
393+
// PDBReadyMessage
394+
PDBReadyMessage = "PodDisruptionBudget completed"
395+
396+
// PDBReadyErrorMessage
397+
PDBReadyErrorMessage = "PodDisruptionBudget error occured %s"
382398
)
383399

384400
// Common Messages used for service accounts, roles, role bindings

modules/common/pdb/pdb.go

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
Copyright 2025 Red Hat
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package pdb
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"time"
23+
24+
"github.com/openstack-k8s-operators/lib-common/modules/common/helper"
25+
"github.com/openstack-k8s-operators/lib-common/modules/common/util"
26+
policyv1 "k8s.io/api/policy/v1"
27+
k8s_errors "k8s.io/apimachinery/pkg/api/errors"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/types"
30+
"k8s.io/apimachinery/pkg/util/intstr"
31+
ctrl "sigs.k8s.io/controller-runtime"
32+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
33+
)
34+
35+
// NewPDB returns an initialized PDB.
36+
func NewPDB(
37+
pdb *policyv1.PodDisruptionBudget,
38+
timeout time.Duration,
39+
) *PDB {
40+
return &PDB{
41+
pdb: pdb,
42+
timeout: timeout,
43+
}
44+
}
45+
46+
// MaxUnavailablePodDisruptionBudget returns a PodDisruptionBudget with the specified maxUnavailable and label selector
47+
func MaxUnavailablePodDisruptionBudget(name, namespace string, maxUnavailable intstr.IntOrString, labelSelector map[string]string) *policyv1.PodDisruptionBudget {
48+
return &policyv1.PodDisruptionBudget{
49+
ObjectMeta: metav1.ObjectMeta{
50+
Name: name,
51+
Namespace: namespace,
52+
},
53+
Spec: policyv1.PodDisruptionBudgetSpec{
54+
MaxUnavailable: &maxUnavailable,
55+
Selector: &metav1.LabelSelector{
56+
MatchLabels: labelSelector,
57+
},
58+
},
59+
}
60+
}
61+
62+
// MinAvailablePodDisruptionBudget returns a PodDisruptionBudget with the specified minAvailable and label selector
63+
func MinAvailablePodDisruptionBudget(name, namespace string, minAvailable intstr.IntOrString, labelSelector map[string]string) *policyv1.PodDisruptionBudget {
64+
return &policyv1.PodDisruptionBudget{
65+
ObjectMeta: metav1.ObjectMeta{
66+
Name: name,
67+
Namespace: namespace,
68+
},
69+
Spec: policyv1.PodDisruptionBudgetSpec{
70+
MinAvailable: &minAvailable,
71+
Selector: &metav1.LabelSelector{
72+
MatchLabels: labelSelector,
73+
},
74+
},
75+
}
76+
}
77+
78+
// CreateOrPatch - creates or patches a PodDisruptionBudget, reconciles after Xs if object won't exist.
79+
func (p *PDB) CreateOrPatch(
80+
ctx context.Context,
81+
h *helper.Helper,
82+
) (ctrl.Result, error) {
83+
pdb := &policyv1.PodDisruptionBudget{
84+
ObjectMeta: metav1.ObjectMeta{
85+
Name: p.pdb.Name,
86+
Namespace: p.pdb.Namespace,
87+
},
88+
}
89+
90+
op, err := controllerutil.CreateOrPatch(ctx, h.GetClient(), pdb, func() error {
91+
pdb.Labels = util.MergeStringMaps(pdb.Labels, p.pdb.Labels)
92+
pdb.Annotations = util.MergeStringMaps(pdb.Annotations, p.pdb.Annotations)
93+
pdb.Spec = p.pdb.Spec
94+
95+
err := controllerutil.SetControllerReference(h.GetBeforeObject(), pdb, h.GetScheme())
96+
if err != nil {
97+
return err
98+
}
99+
100+
return nil
101+
})
102+
if err != nil {
103+
if k8s_errors.IsNotFound(err) {
104+
h.GetLogger().Info(fmt.Sprintf("PodDisruptionBudget %s not found, reconcile in %s", pdb.Name, p.timeout))
105+
return ctrl.Result{RequeueAfter: p.timeout}, nil
106+
}
107+
return ctrl.Result{}, err
108+
}
109+
if op != controllerutil.OperationResultNone {
110+
h.GetLogger().Info(fmt.Sprintf("PodDisruptionBudget %s - %s", pdb.Name, op))
111+
}
112+
113+
// update the pdb object of the pdb type
114+
p.pdb, err = GetPDBWithName(ctx, h, pdb.GetName(), pdb.GetNamespace())
115+
if err != nil {
116+
return ctrl.Result{}, err
117+
}
118+
119+
return ctrl.Result{}, nil
120+
}
121+
122+
// Delete - delete a PodDisruptionBudget.
123+
func (p *PDB) Delete(
124+
ctx context.Context,
125+
h *helper.Helper,
126+
) error {
127+
err := h.GetClient().Delete(ctx, p.pdb)
128+
if err != nil && !k8s_errors.IsNotFound(err) {
129+
return fmt.Errorf("Error deleting PodDisruptionBudget %s: %w", p.pdb.Name, err)
130+
}
131+
132+
return nil
133+
}
134+
135+
// GetPDB - get the PodDisruptionBudget object.
136+
func (p *PDB) GetPDB() policyv1.PodDisruptionBudget {
137+
return *p.pdb
138+
}
139+
140+
// GetPDBWithName func
141+
func GetPDBWithName(
142+
ctx context.Context,
143+
h *helper.Helper,
144+
name string,
145+
namespace string,
146+
) (*policyv1.PodDisruptionBudget, error) {
147+
148+
pdb := &policyv1.PodDisruptionBudget{}
149+
err := h.GetClient().Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, pdb)
150+
if err != nil {
151+
return pdb, err
152+
}
153+
154+
return pdb, nil
155+
}
156+
157+
// DeletePDBWithName deletes a PodDisruptionBudget by name and namespace
158+
func DeletePDBWithName(
159+
ctx context.Context,
160+
h *helper.Helper,
161+
name string,
162+
namespace string,
163+
) error {
164+
pdb := &policyv1.PodDisruptionBudget{
165+
ObjectMeta: metav1.ObjectMeta{
166+
Name: name,
167+
Namespace: namespace,
168+
},
169+
}
170+
171+
err := h.GetClient().Delete(ctx, pdb)
172+
if err != nil && !k8s_errors.IsNotFound(err) {
173+
return fmt.Errorf("Error deleting PodDisruptionBudget %s/%s: %w", namespace, name, err)
174+
}
175+
176+
return nil
177+
}
178+
179+
// IsReady - validates when PodDisruptionBudget is ready
180+
// - returns true if the PDB has been processed by the controller and has valid status
181+
func IsReady(pdb policyv1.PodDisruptionBudget) bool {
182+
return pdb.Status.ObservedGeneration == pdb.Generation &&
183+
pdb.Status.ExpectedPods > 0 &&
184+
pdb.Status.CurrentHealthy >= 0
185+
}

0 commit comments

Comments
 (0)