Skip to content

Commit 6ab6873

Browse files
committed
Support Kata installation using RHCOS layered image
The functionality is exposed via feature gate. The feature gate is named as `layeredImageDeployment` and is managed via the `osc-feature-gates` ConfigMap. The actual MachineConfig specifying the image details is stored in `layered-image-deploy-cm` ConfigMap. Layered Image Deployment workflow: - Admin creates a ConfigMap named `layered-image-deploy-cm` with `osImageURL` and `kernelArguments` keys. Note that `osImageURL` is mandatory and `kernelArguments` is optional Example: ```sh apiVersion: v1 data: osImageURL: "quay.io/openshift_sandboxed_containers/kata-ocp415:tdx" kernelArguments: "kvm_intel.tdx=1" kind: ConfigMap metadata: name: layered-image-deploy-cm namespace: openshift-sandboxed-containers-operator ``` - The feature gate handling code in the OSC operator reads this ConfigMap, extracts the keys, and creates the MachineConfig object to kickstart the installation. - This feature is tied to KataConfig creation/deletion. So any changes to this feature gate config post KataConfig creation will be a no-op. IOW, the installation method - whether using extension (default) or layered image cannot be changed post KataConfig creation. For changing the install method, the KataConfig needs to be deleted and recreated. Since installation and uninstallation are time consuming operation, this restriction is in place. - The code limits the feature gate processing into a dedicated code separate from the general controller flow and publishes the results of the processing by storing them in the KataConfig reconciler struct, for the general controller code to use. Also rename createExtensionMc to createMc since the method is now handling both extension and image based MC. Signed-off-by: Pradipta Banerjee <[email protected]>
1 parent fd0c445 commit 6ab6873

File tree

5 files changed

+211
-18
lines changed

5 files changed

+211
-18
lines changed

config/samples/featuregates.yaml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ metadata:
44
name: osc-feature-gates
55
namespace: openshift-sandboxed-containers-operator
66
data:
7-
# timeTravel allows navigating through cluster states across time.
8-
# It is useful for scenarios where you want to view historical data or
9-
# predict future states based on current trends. Default is "false".
10-
# timeTravel: "false"
7+
# layeredImageDeployment allows deploying Kata using RHCOS layered image
8+
# This feature gate needs a ConfigMap named layered-image-deploy-cm
9+
layeredImageDeployment: "false"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
apiVersion: v1
2+
data:
3+
osImageURL: "quay.io/openshift_sandboxed_containers/kata-ocp415:tdx"
4+
kernelArguments: "kvm_intel.tdx=1"
5+
6+
kind: ConfigMap
7+
metadata:
8+
name: layered-image-deploy-cm
9+
namespace: openshift-sandboxed-containers-operator

controllers/fg_handler.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import (
1111
const (
1212
FgConfigMapName = "osc-feature-gates"
1313
ConfidentialFeatureGate = "confidential"
14+
LayeredImageDeployment = "layeredImageDeployment"
1415
)
1516

1617
var DefaultFeatureGates = map[string]bool{
1718
ConfidentialFeatureGate: false,
19+
LayeredImageDeployment: false,
1820
}
1921

2022
type FeatureGateStatus struct {
@@ -95,6 +97,15 @@ func (r *KataConfigOpenShiftReconciler) processFeatureGates() error {
9597
}
9698
}
9799

98-
return err
100+
// Check layered Image deployment FG
101+
if IsEnabled(fgStatus, LayeredImageDeployment) {
102+
r.Log.Info("Feature gate is enabled", "featuregate", LayeredImageDeployment)
103+
// Perform the necessary actions
104+
return r.handleLayeredImageDeploymentFeature(Enabled)
105+
} else {
106+
r.Log.Info("Feature gate is disabled", "featuregate", LayeredImageDeployment)
107+
// Perform the necessary actions
108+
return r.handleLayeredImageDeploymentFeature(Disabled)
109+
}
99110

100111
}

controllers/layered_image_handler.go

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package controllers
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"strings"
8+
9+
ignTypes "github.com/coreos/ignition/v2/config/v3_2/types"
10+
mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1"
11+
corev1 "k8s.io/api/core/v1"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/apimachinery/pkg/runtime"
14+
"k8s.io/apimachinery/pkg/types"
15+
)
16+
17+
const (
18+
LayeredImageDeployCm = "layered-image-deploy-cm"
19+
image_mc_name = "50-enable-sandboxed-containers-image"
20+
)
21+
22+
// Process the LayeredImageDeployment feature gate
23+
// The method will be entered in the beginning of the reconcile loop via processFeatureGates method
24+
// The method will check for any existing MachineConfig related to KataConfig and return to the caller
25+
// by setting r.ImgMc to imageMachineConfig if present. This will allow the reconcile loop to use the
26+
// r.ImgMc as needed
27+
// If no MachineConfig exists, and layeredImageDeployment feature is enabled, then the method will create
28+
// the MachineConfig from the ConfigMap and set r.ImgMc to the same
29+
// If layeredImageDeployment feature is disabled, then the method will reset r.ImgMc to nil
30+
// The key design aspect is that this featuregate has effect only during the creation of KataConfig.
31+
// Post that it has no effect.
32+
func (r *KataConfigOpenShiftReconciler) handleLayeredImageDeploymentFeature(state FeatureGateState) error {
33+
34+
// Check if MachineConfig exists and return the same without changing anything
35+
mc, err := r.getExistingMachineConfig()
36+
if err != nil {
37+
r.Log.Info("Error in getting existing MachineConfig", "err", err)
38+
return err
39+
}
40+
41+
if mc != nil {
42+
r.Log.Info("MachineConfig is already present. No changes will be done")
43+
// If the MachineConfig is imageMachineConfig, then set r.ImgMc to the same
44+
if mc.Name == image_mc_name {
45+
r.ImgMc = mc
46+
}
47+
return nil
48+
}
49+
50+
if state == Enabled {
51+
r.Log.Info("LayeredImageDeployment feature is enabled")
52+
53+
cm := &corev1.ConfigMap{}
54+
err := r.Client.Get(context.Background(), types.NamespacedName{
55+
Name: LayeredImageDeployCm,
56+
Namespace: OperatorNamespace,
57+
}, cm)
58+
if err != nil {
59+
r.Log.Info("Error in retrieving LayeredImageDeployment ConfigMap", "err", err)
60+
return err
61+
}
62+
63+
// Set the ImgMc here
64+
r.ImgMc, err = r.createMachineConfigFromConfigMap(cm)
65+
if err != nil {
66+
r.Log.Info("Error in creating MachineConfig for LayeredImageDeployment from ConfigMap", "err", err)
67+
return err
68+
}
69+
70+
} else {
71+
r.Log.Info("LayeredImageDeployment feature is disabled. Resetting ImgMc")
72+
// Reset ImgMc
73+
r.ImgMc = nil
74+
75+
}
76+
77+
return nil
78+
}
79+
80+
func (r *KataConfigOpenShiftReconciler) getExistingMachineConfig() (*mcfgv1.MachineConfig, error) {
81+
r.Log.Info("Getting any existing MachineConfigs related to KataConfig")
82+
83+
// Retrieve the existing MachineConfig
84+
// Check for label "app":r.kataConfig.Name
85+
// and name "50-enable-sandboxed-containers-extension" or name "50-enable-sandboxed-containers-image"
86+
mcList := &mcfgv1.MachineConfigList{}
87+
err := r.Client.List(context.Background(), mcList)
88+
if err != nil {
89+
r.Log.Info("Error in listing MachineConfigs", "err", err)
90+
return nil, err
91+
}
92+
93+
for _, mc := range mcList.Items {
94+
if mc.Labels["app"] == r.kataConfig.Name &&
95+
(mc.Name == extension_mc_name || mc.Name == image_mc_name) {
96+
return &mc, nil
97+
}
98+
}
99+
100+
r.Log.Info("No existing MachineConfigs related to KataConfig found")
101+
102+
return nil, nil
103+
}
104+
105+
// Method to create a new MachineConfig object from configMap data
106+
// The configMap data will have two keys: "osImageURL" and "kernelArgs"
107+
func (r *KataConfigOpenShiftReconciler) createMachineConfigFromConfigMap(cm *corev1.ConfigMap) (*mcfgv1.MachineConfig, error) {
108+
109+
// Get the osImageURL from the ConfigMap
110+
// osImageURL is mandatory for creating a MachineConfig
111+
osImageURL, exists := cm.Data["osImageURL"]
112+
if !exists {
113+
return nil, fmt.Errorf("osImageURL not found in ConfigMap")
114+
}
115+
116+
ic := ignTypes.Config{
117+
Ignition: ignTypes.Ignition{
118+
Version: "3.2.0",
119+
},
120+
}
121+
122+
icb, err := json.Marshal(ic)
123+
if err != nil {
124+
return nil, err
125+
}
126+
mc := &mcfgv1.MachineConfig{
127+
TypeMeta: metav1.TypeMeta{
128+
APIVersion: "machineconfiguration.openshift.io/v1",
129+
Kind: "MachineConfig",
130+
},
131+
ObjectMeta: metav1.ObjectMeta{
132+
Name: image_mc_name,
133+
Namespace: OperatorNamespace,
134+
},
135+
Spec: mcfgv1.MachineConfigSpec{
136+
Config: runtime.RawExtension{
137+
Raw: icb,
138+
},
139+
OSImageURL: osImageURL,
140+
},
141+
}
142+
143+
if kernelArguments, ok := cm.Data["kernelArguments"]; ok {
144+
// Parse the kernel arguments and set them in the MachineConfig
145+
// Note that in the configmap the kernel arguments are stored as a single string
146+
// eg. "a=b c=d ..." and we need to split them into individual arguments
147+
// eg ["a=b", "c=d", ...]
148+
// Split the kernel arguments string into individual arguments
149+
mc.Spec.KernelArguments = strings.Fields(kernelArguments)
150+
}
151+
152+
// Set the required labels
153+
mcp, err := r.getMcpName()
154+
if err != nil {
155+
return nil, err
156+
}
157+
mc.Labels = map[string]string{
158+
"machineconfiguration.openshift.io/role": mcp,
159+
"app": r.kataConfig.Name,
160+
}
161+
162+
return mc, nil
163+
}

controllers/openshift_controller.go

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ type KataConfigOpenShiftReconciler struct {
6161
Scheme *runtime.Scheme
6262

6363
kataConfig *kataconfigurationv1.KataConfig
64+
65+
ImgMc *mcfgv1.MachineConfig
6466
}
6567

6668
const (
@@ -494,6 +496,12 @@ func getExtensionName() string {
494496
func (r *KataConfigOpenShiftReconciler) newMCForCR(machinePool string) (*mcfgv1.MachineConfig, error) {
495497
r.Log.Info("Creating MachineConfig for Custom Resource")
496498

499+
if r.ImgMc != nil {
500+
r.Log.Info("Image based MachineConfig", "MachineConfig", r.ImgMc)
501+
return r.ImgMc, nil
502+
}
503+
504+
// Create extension MachineConfig
497505
ic := ignTypes.Config{
498506
Ignition: ignTypes.Ignition{
499507
Version: "3.2.0",
@@ -528,6 +536,8 @@ func (r *KataConfigOpenShiftReconciler) newMCForCR(machinePool string) (*mcfgv1.
528536
},
529537
}
530538

539+
r.Log.Info("Extension based MachineConfig", "MachineConfig", mc)
540+
531541
return &mc, nil
532542
}
533543

@@ -1053,7 +1063,7 @@ func (r *KataConfigOpenShiftReconciler) processKataConfigInstallRequest() (ctrl.
10531063
r.Log.Info("SCNodeRole is: " + machinePool)
10541064
}
10551065

1056-
wasMcJustCreated, err := r.createExtensionMc(machinePool)
1066+
wasMcJustCreated, err := r.createMc(machinePool)
10571067
if err != nil {
10581068
return ctrl.Result{Requeue: true}, nil
10591069
}
@@ -1281,24 +1291,24 @@ func (r *KataConfigOpenShiftReconciler) processKataConfigInstallRequest() (ctrl.
12811291
// If the first return value is 'true' it means that the MC was just created
12821292
// by this call, 'false' means that it's already existed. As usual, the first
12831293
// return value is only valid if the second one is nil.
1284-
func (r *KataConfigOpenShiftReconciler) createExtensionMc(machinePool string) (bool, error) {
1294+
func (r *KataConfigOpenShiftReconciler) createMc(machinePool string) (bool, error) {
12851295

12861296
// In case we're returning an error we want to make it explicit that
12871297
// the first return value is "not care". Unfortunately golang seems
12881298
// to lack syntax for creating an expression with default bool value
12891299
// hence this work-around.
12901300
var dummy bool
12911301

1292-
/* Create Machine Config object to enable sandboxed containers RHCOS extension */
1293-
mc := &mcfgv1.MachineConfig{}
1294-
err := r.Client.Get(context.TODO(), types.NamespacedName{Name: extension_mc_name}, mc)
1295-
if err != nil && (k8serrors.IsNotFound(err) || k8serrors.IsGone(err)) {
1302+
/* Create Machine Config object to install sandboxed containers */
12961303

1297-
r.Log.Info("creating RHCOS extension MachineConfig")
1298-
mc, err = r.newMCForCR(machinePool)
1299-
if err != nil {
1300-
return dummy, err
1301-
}
1304+
r.Log.Info("creating RHCOS MachineConfig")
1305+
mc, err := r.newMCForCR(machinePool)
1306+
if err != nil {
1307+
return dummy, err
1308+
}
1309+
1310+
err = r.Client.Get(context.TODO(), types.NamespacedName{Name: mc.Name}, mc)
1311+
if err != nil && (k8serrors.IsNotFound(err) || k8serrors.IsGone(err)) {
13021312

13031313
err = r.Client.Create(context.TODO(), mc)
13041314
if err != nil {
@@ -1308,12 +1318,13 @@ func (r *KataConfigOpenShiftReconciler) createExtensionMc(machinePool string) (b
13081318
r.Log.Info("MachineConfig successfully created", "mc.Name", mc.Name)
13091319
return true, nil
13101320
} else if err != nil {
1311-
r.Log.Info("failed to retrieve extension MachineConfig", "err", err)
1321+
r.Log.Info("failed to retrieve MachineConfig", "err", err)
13121322
return dummy, err
13131323
} else {
1314-
r.Log.Info("extension MachineConfig already exists")
1324+
r.Log.Info("MachineConfig already exists")
13151325
return false, nil
13161326
}
1327+
13171328
}
13181329

13191330
func (r *KataConfigOpenShiftReconciler) makeReconcileRequest() reconcile.Request {

0 commit comments

Comments
 (0)