Skip to content

Commit b853750

Browse files
committed
AGENT-1072: Apply operator manifests from ConfigMap for agent-installer
In the agent-based installer, when cluster installation is complete the assisted-installer cannot access assisted-service since it has terminated. Therefore any operator custom-manifests are not applied. Instead the assisted-service will create a ConfigMap with the manifests and operator info (openshift/assisted-service#7818) and in this PR the assisted-installer will retrieve the ConfigMap and apply the manifests. The intent is that this will only be done for the agent-based installer and not affect the normal path of applying manifests using the assisted-service API.
1 parent 5ca92bd commit b853750

File tree

7 files changed

+250
-20
lines changed

7 files changed

+250
-20
lines changed

src/assisted_installer_controller/assisted_installer_controller.go

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"github.com/openshift/assisted-installer/src/ops"
3939
"github.com/openshift/assisted-installer/src/utils"
4040
"github.com/openshift/assisted-service/models"
41+
"gopkg.in/yaml.v2"
4142
)
4243

4344
const (
@@ -112,6 +113,7 @@ type Controller interface {
112113
WaitAndUpdateNodesStatus(ctx context.Context, wg *sync.WaitGroup, removeUninitializedTaint bool)
113114
HackDNSAddressConflict(wg *sync.WaitGroup)
114115
PostInstallConfigs(ctx context.Context, wg *sync.WaitGroup)
116+
PostInstallConfigsK8sClient(ctx context.Context, wg *sync.WaitGroup, kubeconfigPath string)
115117
UpdateNodeLabels(ctx context.Context, wg *sync.WaitGroup)
116118
UpdateBMHs(ctx context.Context, wg *sync.WaitGroup)
117119
UploadLogs(ctx context.Context, wg *sync.WaitGroup, invoker string)
@@ -129,14 +131,21 @@ type controller struct {
129131
rebootsNotifier RebootsNotifier
130132
}
131133

132-
// manifest store the operator manifest used by assisted-installer to create CRs of the OLM:
133-
type manifest struct {
134+
// Manifest stores the operator manifest used by assisted-installer to create CRs of the OLM:
135+
type Manifest struct {
134136
// name of the operator the CR manifest we want create
135137
Name string
136-
// content of the manifest of the opreator
138+
// content of the manifest of the operator
137139
Content string
138140
}
139141

142+
// Contents of ConfigMap containing operator manifests
143+
type operatorMetadata struct {
144+
Namespace string `yaml:"namespace"`
145+
SubscriptionName string `yaml:"subscriptionName"`
146+
Manifests []string `yaml:"manifests"`
147+
}
148+
140149
func newController(log *logrus.Logger, cfg ControllerConfig, ops ops.Ops, ic inventory_client.InventoryClient, kc k8s_client.K8SClient, rebootsNotifier RebootsNotifier) *controller {
141150
return &controller{
142151
log: log,
@@ -487,6 +496,73 @@ func (c *controller) PostInstallConfigs(ctx context.Context, wg *sync.WaitGroup)
487496
c.sendCompleteInstallation(ctx, success, errMessage, data)
488497
}
489498

499+
func (c *controller) PostInstallConfigsK8sClient(ctx context.Context, wg *sync.WaitGroup, kubeconfigPath string) {
500+
defer func() {
501+
c.log.Infof("Finished PostInstallConfigsK8sClient")
502+
wg.Done()
503+
}()
504+
err := utils.WaitForeverForPredicate(ctx, GeneralWaitInterval, func() bool {
505+
// Get operator info from ConfigMap created by assisted-service
506+
cm, err := c.kc.GetConfigMap("assisted-installer", "olm-operator-manifests")
507+
if err != nil {
508+
c.log.Infof("Failed to get ConfigMap %s", err.Error())
509+
return true // configmap may not have been created
510+
}
511+
512+
var manifests []Manifest
513+
var olmOperators []models.MonitoredOperator
514+
// Iterate through the ConfigMap's Data to find metadata and manifests
515+
for key, value := range cm.Data {
516+
if strings.HasSuffix(key, ".metadata.yaml") {
517+
// This is an operator metadata entry
518+
var metadata operatorMetadata
519+
if err := yaml.Unmarshal([]byte(value), &metadata); err != nil {
520+
c.log.Infof("Failed to unmarshal metadata for %s: %s", key, err.Error())
521+
return false
522+
}
523+
524+
// Extract the operator name from the metadata key
525+
operatorName := strings.TrimSuffix(key, ".metadata.yaml")
526+
527+
// Get manifests associated with this operator
528+
for _, manifestFileName := range metadata.Manifests {
529+
if content, found := cm.Data[manifestFileName]; found {
530+
manifests = append(manifests, Manifest{operatorName, content})
531+
} else {
532+
c.log.Infof("Manifest file %s listed in metadata but not found in ConfigMap data", manifestFileName)
533+
}
534+
}
535+
536+
olmOperators = append(olmOperators, models.MonitoredOperator{
537+
Name: operatorName,
538+
Namespace: metadata.Namespace,
539+
SubscriptionName: metadata.SubscriptionName,
540+
OperatorType: models.OperatorTypeOlm,
541+
})
542+
}
543+
}
544+
545+
if kubeconfigPath == "" {
546+
kubeconfigPath, err = c.rebootsNotifier.GetKubeconfigPath(ctx)
547+
if err != nil {
548+
c.log.Infof("Failed to get kubeconfig path %s", err.Error())
549+
return false
550+
}
551+
}
552+
// Apply manifests from configMap if ready
553+
if !c.applyOperatorManifests(olmOperators, manifests, kubeconfigPath) {
554+
c.log.Infof("Failed to apply manifests")
555+
} else {
556+
c.log.Infof("Successfully applied manifests")
557+
return true
558+
}
559+
return false
560+
})
561+
if err != nil {
562+
c.log.Infof("Error in wait %s", err.Error())
563+
}
564+
}
565+
490566
func (c *controller) postInstallConfigs(ctx context.Context) (error, map[string]interface{}) {
491567
var err error
492568
var data map[string]interface{}
@@ -650,7 +726,7 @@ func (c *controller) applyPostInstallManifests(operators []models.MonitoredOpera
650726
}
651727

652728
// Unmarshall the content of the operators manifests:
653-
var manifests []manifest
729+
var manifests []Manifest
654730
data, err := os.ReadFile(customManifestPath)
655731
if err != nil {
656732
c.log.WithError(err).Errorf("Failed to read the custom manifests file.")
@@ -661,10 +737,24 @@ func (c *controller) applyPostInstallManifests(operators []models.MonitoredOpera
661737
return false
662738
}
663739

664-
// Create the manifests of the operators, which are properly initialized:
740+
if !c.applyOperatorManifests(operators, manifests, kubeconfigName) {
741+
c.log.WithError(err).Errorf("Failed to apply operators to cluster")
742+
return false
743+
}
744+
745+
return true
746+
}
747+
748+
// Apply the operator manifests to the cluster.
749+
func (c *controller) applyOperatorManifests(operators []models.MonitoredOperator, manifests []Manifest, kubeconfigName string) bool {
750+
665751
readyOperators, _, err := c.getReadyOperators(operators)
666752
if err != nil {
667-
c.log.WithError(err).Errorf("Failed to fetch operators from assisted-service")
753+
c.log.WithError(err).Errorf("Failed to fetch operators from cluster")
754+
return false
755+
}
756+
if len(operators) != len(readyOperators) {
757+
c.log.Infof("Not all operators are currently ready")
668758
return false
669759
}
670760

@@ -695,6 +785,7 @@ func (c *controller) applyPostInstallManifests(operators []models.MonitoredOpera
695785
}
696786
return false
697787
}() {
788+
c.log.Infof("Skipping operator %s as its not initialized by CSV", manifest.Name)
698789
continue
699790
}
700791

src/assisted_installer_controller/assisted_installer_controller_test.go

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
"github.com/openshift/assisted-installer/src/k8s_client"
4040
"github.com/openshift/assisted-installer/src/ops"
4141
"github.com/openshift/assisted-service/models"
42+
yaml "gopkg.in/yaml.v3"
4243
)
4344

4445
func TestValidator(t *testing.T) {
@@ -106,6 +107,15 @@ var (
106107
testIngressConfigMap = map[string]string{
107108
"ca-bundle.crt": "CA",
108109
}
110+
111+
testOperatorManifests = map[string]string{
112+
"kube-descheduler": "LS0tCmFwaVZlcnNpb246IG9wZXJhdG9yLm9wZW5zaGlmdC5pby92MQpraW5kOiBLdWJlRGVzY2hlZHVsZXIKbWV0YWRhdGE6CiAgbmFtZTogY2x1c3RlcgogIG5hbWVzcGFjZTogb3BlbnNoaWZ0LWt1YmUtZGVzY2hlZHVsZXItb3BlcmF0b3IKc3BlYzoKICBsb2dMZXZlbDogTm9ybWFsCiAgbW9kZTogUHJlZGljdGl2ZQogIG9wZXJhdG9yTG9nTGV2ZWw6IE5vcm1hbAogIHByb2ZpbGVzOgogICAgLSBBZmZpbml0eUFuZFRhaW50cwogIGRlc2NoZWR1bGluZ0ludGVydmFsU2Vjb25kczogMzYwMAogIG1hbmFnZW1lbnRTdGF0ZTogTWFuYWdlZAo=",
113+
"nmstate": "CmFwaVZlcnNpb246IG5tc3RhdGUuaW8vdjEKa2luZDogTk1TdGF0ZQptZXRhZGF0YToKICBuYW1lOiBubXN0YXRlCg==",
114+
}
115+
116+
testMonitoredOperators = []models.MonitoredOperator{
117+
{Name: "kube-descheduler", Namespace: "openshift-kube-descheduler-operator", OperatorType: "olm", SubscriptionName: "cluster-kube-descheduler-operator"},
118+
{Name: "nmstate", Namespace: "openshift-nmstate", OperatorType: "olm", SubscriptionName: "kubernetes-nmstate-operator"}}
109119
)
110120

111121
var _ = Describe("installer HostRoleMaster role", func() {
@@ -494,8 +504,7 @@ var _ = Describe("installer HostRoleMaster role", func() {
494504
mockk8sclient.EXPECT().ListNodes().Return(nodes, nil).Times(1)
495505
updateProgressSuccess(done, hosts)
496506
configuringSuccess()
497-
for name, v := range inventoryNamesIds {
498-
value := v
507+
for name, value := range inventoryNamesIds {
499508
mockRebootsNotifier.EXPECT().Start(gomock.Any(), name, value.Host.ID, &value.Host.InfraEnvID, gomock.Any()).Times(1)
500509
}
501510
exit := assistedController.waitAndUpdateNodesStatus(false)
@@ -1436,6 +1445,58 @@ var _ = Describe("installer HostRoleMaster role", func() {
14361445
})
14371446
})
14381447

1448+
Context("PostInstallConfigsK8sClient", func() {
1449+
Context("operator manifests", func() {
1450+
BeforeEach(func() {
1451+
GeneralWaitInterval = 1 * time.Millisecond
1452+
})
1453+
1454+
It("success", func() {
1455+
mockAllCapabilitiesEnabled()
1456+
1457+
operatorConfigMap := createConfigMap()
1458+
1459+
mockk8sclient.EXPECT().GetConfigMap("assisted-installer", "olm-operator-manifests").Return(operatorConfigMap, nil).Times(1)
1460+
mockk8sclient.EXPECT().ListJobs(gomock.Any()).Return(&batchV1.JobList{}, nil).AnyTimes()
1461+
mockk8sclient.EXPECT().GetAllInstallPlansOfSubscription(gomock.Any()).Return([]olmv1alpha1.InstallPlan{}, nil).Times(2)
1462+
mockRebootsNotifier.EXPECT().GetKubeconfigPath(gomock.Any()).Return("", nil).Times(1)
1463+
randomCSV := uuid.New().String()
1464+
mockk8sclient.EXPECT().GetCSVFromSubscription("openshift-kube-descheduler-operator", "cluster-kube-descheduler-operator").Return(randomCSV, nil).Times(1)
1465+
mockk8sclient.EXPECT().GetCSV("openshift-kube-descheduler-operator", gomock.Any()).Return(&olmv1alpha1.ClusterServiceVersion{Status: olmv1alpha1.ClusterServiceVersionStatus{Phase: olmv1alpha1.CSVPhaseNone}}, nil).Times(1)
1466+
mockk8sclient.EXPECT().GetCSVFromSubscription("openshift-nmstate", "kubernetes-nmstate-operator").Return(randomCSV, nil).Times(1)
1467+
mockk8sclient.EXPECT().GetCSV("openshift-nmstate", gomock.Any()).Return(&olmv1alpha1.ClusterServiceVersion{Status: olmv1alpha1.ClusterServiceVersionStatus{Phase: olmv1alpha1.CSVPhaseNone}}, nil).Times(1)
1468+
mockops.EXPECT().CreateManifests(gomock.Any(), gomock.Any()).Times(2)
1469+
1470+
wg.Add(1)
1471+
assistedController.PostInstallConfigsK8sClient(context.TODO(), &wg, "")
1472+
wg.Wait()
1473+
})
1474+
1475+
It("failure on CSVs", func() {
1476+
GeneralProgressUpdateInt = 1 * time.Millisecond
1477+
mockAllCapabilitiesEnabled()
1478+
1479+
operatorConfigMap := createConfigMap()
1480+
1481+
mockk8sclient.EXPECT().GetConfigMap("assisted-installer", "olm-operator-manifests").Return(operatorConfigMap, nil).AnyTimes()
1482+
mockk8sclient.EXPECT().ListJobs(gomock.Any()).Return(&batchV1.JobList{}, nil).AnyTimes()
1483+
mockk8sclient.EXPECT().GetAllInstallPlansOfSubscription(gomock.Any()).Return([]olmv1alpha1.InstallPlan{}, nil).AnyTimes()
1484+
mockRebootsNotifier.EXPECT().GetKubeconfigPath(gomock.Any()).Return("", nil).AnyTimes()
1485+
mockk8sclient.EXPECT().GetCSVFromSubscription("openshift-kube-descheduler-operator", "cluster-kube-descheduler-operator").Return("", fmt.Errorf("dummy")).AnyTimes()
1486+
mockk8sclient.EXPECT().GetCSVFromSubscription("openshift-nmstate", "kubernetes-nmstate-operator").Return("", fmt.Errorf("dummy")).AnyTimes()
1487+
1488+
wg.Add(1)
1489+
go func() {
1490+
defer GinkgoRecover()
1491+
ctx, cancel := context.WithTimeout(context.TODO(), 10*time.Millisecond)
1492+
defer cancel()
1493+
assistedController.PostInstallConfigsK8sClient(ctx, &wg, "")
1494+
}()
1495+
wg.Wait()
1496+
})
1497+
})
1498+
})
1499+
14391500
Context("update BMHs", func() {
14401501
t := metav1.Unix(98754, 0)
14411502
bmhStatus := metal3v1alpha1.BareMetalHostStatus{
@@ -2252,3 +2313,45 @@ func create3Hosts(currentStatus string, stage models.HostStage, nodeLabels strin
22522313
"node1": {Host: &models.Host{InfraEnvID: infraEnvId, ID: &node1Id, NodeLabels: nodeLabels, Progress: &currentState, Status: &currentStatus}},
22532314
"node2": {Host: &models.Host{InfraEnvID: infraEnvId, ID: &node2Id, NodeLabels: nodeLabels, Progress: &currentState, Status: &currentStatus}}}
22542315
}
2316+
2317+
func createConfigMap() *v1.ConfigMap {
2318+
type operatorMetadata struct {
2319+
Namespace string `yaml:"namespace"`
2320+
SubscriptionName string `yaml:"subscriptionName"`
2321+
Manifests []string `yaml:"manifests"`
2322+
}
2323+
2324+
configMap := v1.ConfigMap{
2325+
TypeMeta: metav1.TypeMeta{
2326+
APIVersion: "v1",
2327+
Kind: "ConfigMap",
2328+
},
2329+
ObjectMeta: metav1.ObjectMeta{
2330+
Name: "olm-operator-manifests",
2331+
Namespace: "assisted-installer",
2332+
},
2333+
Data: map[string]string{},
2334+
}
2335+
2336+
for _, operator := range testMonitoredOperators {
2337+
var manifestFileNames []string
2338+
manifestContent, _ := testOperatorManifests[operator.Name]
2339+
2340+
fileName := fmt.Sprintf("%s-1.yaml", operator.Name)
2341+
configMap.Data[fileName] = manifestContent
2342+
manifestFileNames = append(manifestFileNames, fileName)
2343+
2344+
metadata := operatorMetadata{
2345+
Namespace: operator.Namespace,
2346+
SubscriptionName: operator.SubscriptionName,
2347+
Manifests: manifestFileNames,
2348+
}
2349+
2350+
metadataYAML, _ := yaml.Marshal(metadata)
2351+
2352+
metadataKey := fmt.Sprintf("%s.metadata.yaml", operator.Name)
2353+
configMap.Data[metadataKey] = string(metadataYAML)
2354+
}
2355+
2356+
return &configMap
2357+
}

src/assisted_installer_controller/mock_controller.go

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/assisted_installer_controller/mock_reboots_notifier.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/assisted_installer_controller/reboots_notifier.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const (
3030
type RebootsNotifier interface {
3131
Start(ctx context.Context, nodeName string, hostId, infraenvId, clusterId *strfmt.UUID)
3232
Finalize()
33+
GetKubeconfigPath(ctx context.Context) (string, error)
3334
}
3435

3536
type rebootsNotifier struct {
@@ -52,7 +53,7 @@ func NewRebootsNotifier(ops ops.Ops, ic inventory_client.InventoryClient, enable
5253
}
5354
}
5455

55-
func (r *rebootsNotifier) getKubeconfigPath(ctx context.Context) (string, error) {
56+
func (r *rebootsNotifier) GetKubeconfigPath(ctx context.Context) (string, error) {
5657
if r.kubeconfigPath != "" {
5758
return r.kubeconfigPath, nil
5859
}
@@ -75,7 +76,7 @@ func (r *rebootsNotifier) getKubeconfigPath(ctx context.Context) (string, error)
7576

7677
func (r *rebootsNotifier) run(ctx context.Context, nodeName string, hostId, infraenvId, clusterId *strfmt.UUID) {
7778
defer r.wg.Done()
78-
kubeconfigPath, err := r.getKubeconfigPath(ctx)
79+
kubeconfigPath, err := r.GetKubeconfigPath(ctx)
7980
if err != nil {
8081
r.log.Warningf("failed to get kubeconfig. aborting notifying reboots for %s", nodeName)
8182
return

src/assisted_installer_controller/reboots_notifier_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ var _ = Describe("Reboots notifier", func() {
4848
}
4949
mockclient.EXPECT().DownloadClusterCredentials(context.TODO(), gomock.Any(), gomock.Any()).Return(errors.New("error"))
5050
mockclient.EXPECT().DownloadClusterCredentials(context.TODO(), gomock.Any(), gomock.Any()).Return(nil)
51-
kc, err := notifierImpl.getKubeconfigPath(context.TODO())
51+
kc, err := notifierImpl.GetKubeconfigPath(context.TODO())
5252
Expect(err).To(HaveOccurred())
5353
Expect(kc).To(BeEmpty())
54-
kc, err = notifierImpl.getKubeconfigPath(context.TODO())
54+
kc, err = notifierImpl.GetKubeconfigPath(context.TODO())
5555
Expect(err).ToNot(HaveOccurred())
5656
Expect(kc).ToNot(BeEmpty())
57-
kc2, err := notifierImpl.getKubeconfigPath(context.TODO())
57+
kc2, err := notifierImpl.GetKubeconfigPath(context.TODO())
5858
Expect(err).ToNot(HaveOccurred())
5959
Expect(kc).To(Equal(kc2))
6060
})

0 commit comments

Comments
 (0)