Skip to content

Commit d2d6e3a

Browse files
astefanuttiopenshift-merge-robot
authored andcommitted
Expose structured configuration
1 parent b0e6432 commit d2d6e3a

File tree

4 files changed

+119
-60
lines changed

4 files changed

+119
-60
lines changed

controllers/appwrapper_controller.go

Lines changed: 23 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,30 @@ package controllers
1818

1919
import (
2020
"context"
21+
"fmt"
2122
"os"
22-
"strconv"
2323
"strings"
2424
"time"
2525

2626
mapiclientset "github.com/openshift/client-go/machine/clientset/versioned"
2727
machineinformersv1beta1 "github.com/openshift/client-go/machine/informers/externalversions"
2828
"github.com/openshift/client-go/machine/listers/machine/v1beta1"
29-
30-
appwrapperClientSet "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/clientset/versioned"
29+
"github.com/project-codeflare/instascale/pkg/config"
3130

3231
arbv1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/controller/v1beta1"
32+
appwrapperClientSet "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/clientset/versioned"
33+
arbinformersFactory "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/informers/externalversions"
3334
appwrapperlisters "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/listers/controller/v1beta1"
3435

35-
arbinformersFactory "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/client/informers/externalversions"
36+
corev1 "k8s.io/api/core/v1"
3637
apierrors "k8s.io/apimachinery/pkg/api/errors"
3738
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3839
"k8s.io/apimachinery/pkg/labels"
3940
"k8s.io/apimachinery/pkg/runtime"
4041
"k8s.io/client-go/kubernetes"
4142
"k8s.io/client-go/tools/cache"
4243
"k8s.io/klog"
44+
4345
ctrl "sigs.k8s.io/controller-runtime"
4446
"sigs.k8s.io/controller-runtime/pkg/client"
4547
"sigs.k8s.io/controller-runtime/pkg/log"
@@ -48,9 +50,8 @@ import (
4850
// AppWrapperReconciler reconciles a AppWrapper object
4951
type AppWrapperReconciler struct {
5052
client.Client
51-
Scheme *runtime.Scheme
52-
ConfigsNamespace string
53-
OcmSecretNamespace string
53+
Scheme *runtime.Scheme
54+
Config config.InstaScaleConfiguration
5455
}
5556

5657
var (
@@ -137,7 +138,6 @@ func (r *AppWrapperReconciler) Reconcile(ctx context.Context, req ctrl.Request)
137138

138139
// SetupWithManager sets up the controller with the Manager.
139140
func (r *AppWrapperReconciler) SetupWithManager(mgr ctrl.Manager) error {
140-
141141
kubeconfig := os.Getenv("KUBECONFIG")
142142
cb, err := NewClientBuilder(kubeconfig)
143143
if err != nil {
@@ -150,31 +150,23 @@ func (r *AppWrapperReconciler) SetupWithManager(mgr ctrl.Manager) error {
150150

151151
machineClient = cb.MachineClientOrDie("machine-shared-informer")
152152
kubeClient, _ = kubernetes.NewForConfig(restConfig)
153-
instascaleConfigmap, err := kubeClient.CoreV1().ConfigMaps(r.ConfigsNamespace).Get(context.Background(), "instascale-config", metav1.GetOptions{})
154-
if err != nil {
155-
klog.Infof("Config map named instascale-config is not available in namespace %v", r.ConfigsNamespace)
156-
return err
157-
}
158153

159-
maxScaleNodesAllowed, err = strconv.Atoi(instascaleConfigmap.Data["maxScaleoutAllowed"])
160-
if err != nil {
161-
klog.Warningf("Error converting %v to int. Setting maxScaleNodesAllowed to 3", maxScaleNodesAllowed)
162-
maxScaleNodesAllowed = 3
163-
}
164-
if instascaleConfigmap != nil {
165-
klog.Errorf("Got config map named %v from namespace %v that configures max nodes in cluster to value %v", instascaleConfigmap.Name, instascaleConfigmap.Namespace, maxScaleNodesAllowed)
166-
}
154+
maxScaleNodesAllowed = int(r.Config.MaxScaleoutAllowed)
167155

168156
useMachineSets = true
169-
ocmSecretExists := ocmSecretExists(r.OcmSecretNamespace)
170-
if ocmSecretExists {
171-
machinePoolsExists := machinePoolExists()
172-
173-
if machinePoolsExists {
174-
useMachineSets = false
175-
klog.Infof("Using machine pools %v", machinePoolsExists)
157+
if ocmSecretRef := r.Config.OCMSecretRef; ocmSecretRef != nil {
158+
if ocmSecret, err := getOCMSecret(ocmSecretRef); err != nil {
159+
return fmt.Errorf("error reading OCM Secret from ref %q: %w", ocmSecretRef, err)
160+
} else if token := ocmSecret.Data["token"]; len(token) > 0 {
161+
ocmToken = string(token)
176162
} else {
177-
klog.Infof("Setting useMachineSets to %v", useMachineSets)
163+
return fmt.Errorf("token is missing from OCM Secret %q", ocmSecretRef)
164+
}
165+
if ok, err := machinePoolExists(); err != nil {
166+
return err
167+
} else if ok {
168+
useMachineSets = false
169+
klog.Info("Using machine pools for cluster auto-scaling")
178170
}
179171
}
180172

@@ -183,17 +175,8 @@ func (r *AppWrapperReconciler) SetupWithManager(mgr ctrl.Manager) error {
183175
Complete(r)
184176
}
185177

186-
func ocmSecretExists(namespace string) bool {
187-
instascaleOCMSecret, err := kubeClient.CoreV1().Secrets(namespace).Get(context.Background(), "instascale-ocm-secret", metav1.GetOptions{})
188-
if err != nil {
189-
klog.Errorf("Error getting instascale-ocm-secret from namespace %v: %v", namespace, err)
190-
klog.Infof("If you are looking to use OCM, ensure that the 'instascale-ocm-secret' secret is available on the cluster within namespace %v", namespace)
191-
klog.Infof("Setting useMachineSets to %v.", useMachineSets)
192-
return false
193-
}
194-
195-
ocmToken = string(instascaleOCMSecret.Data["token"])
196-
return true
178+
func getOCMSecret(secretRef *corev1.SecretReference) (*corev1.Secret, error) {
179+
return kubeClient.CoreV1().Secrets(secretRef.Namespace).Get(context.Background(), secretRef.Name, metav1.GetOptions{})
197180
}
198181

199182
func addAppwrappersThatNeedScaling() {

controllers/machinepools.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ package controllers
33
import (
44
"context"
55
"fmt"
6+
"os"
7+
"strings"
8+
69
ocmsdk "github.com/openshift-online/ocm-sdk-go"
710
cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
811
configv1 "github.com/openshift/api/config/v1"
912
arbv1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/controller/v1beta1"
13+
1014
"k8s.io/apimachinery/pkg/types"
1115
"k8s.io/klog"
12-
"os"
13-
"strings"
1416
)
1517

1618
func createOCMConnection() (*ocmsdk.Connection, error) {
@@ -82,17 +84,15 @@ func deleteMachinePool(aw *arbv1.AppWrapper) {
8284
})
8385
}
8486

85-
// Check if machine pools exist
86-
func machinePoolExists() bool {
87+
func machinePoolExists() (bool, error) {
8788
connection, err := createOCMConnection()
8889
if err != nil {
89-
fmt.Fprintf(os.Stderr, "Error creating OCM connection: %v", err)
90+
return false, fmt.Errorf("error creating OCM connection: %w", err)
9091
}
9192
defer connection.Close()
9293

9394
machinePools := connection.ClustersMgmt().V1().Clusters().Cluster(ocmClusterID).MachinePools()
94-
klog.Infof("Machine pools: %v", machinePools)
95-
return machinePools != nil
95+
return machinePools != nil, nil
9696
}
9797

9898
// getOCMClusterID determines the internal clusterID to be used for OCM API calls

main.go

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,28 @@ package main
1919
import (
2020
"flag"
2121
"os"
22-
23-
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
24-
// to ensure that exec-entrypoint and run can make use of them.
25-
_ "k8s.io/client-go/plugin/pkg/client/auth"
22+
"strconv"
2623

2724
configv1 "github.com/openshift/api/config/v1"
25+
26+
corev1 "k8s.io/api/core/v1"
27+
apierrors "k8s.io/apimachinery/pkg/api/errors"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2829
"k8s.io/apimachinery/pkg/runtime"
2930
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
31+
"k8s.io/client-go/kubernetes"
3032
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
33+
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
34+
// to ensure that exec-entrypoint and run can make use of them.
35+
_ "k8s.io/client-go/plugin/pkg/client/auth"
36+
3137
ctrl "sigs.k8s.io/controller-runtime"
3238
"sigs.k8s.io/controller-runtime/pkg/healthz"
3339
"sigs.k8s.io/controller-runtime/pkg/log/zap"
3440

3541
"github.com/project-codeflare/instascale/controllers"
36-
//+kubebuilder:scaffold:imports
42+
"github.com/project-codeflare/instascale/pkg/config"
43+
// +kubebuilder:scaffold:imports
3744
mcadv1beta1 "github.com/project-codeflare/multi-cluster-app-dispatcher/pkg/apis/controller/v1beta1"
3845
)
3946

@@ -46,7 +53,7 @@ func init() {
4653
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
4754
utilruntime.Must(configv1.Install(scheme))
4855

49-
//+kubebuilder:scaffold:scheme
56+
// +kubebuilder:scaffold:scheme
5057
_ = mcadv1beta1.AddToScheme(scheme)
5158
}
5259

@@ -71,7 +78,47 @@ func main() {
7178

7279
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
7380

74-
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
81+
ctx := ctrl.SetupSignalHandler()
82+
83+
restConfig := ctrl.GetConfigOrDie()
84+
kubeClient, err := kubernetes.NewForConfig(restConfig)
85+
if err != nil {
86+
setupLog.Error(err, "Error creating Kubernetes client")
87+
os.Exit(1)
88+
}
89+
90+
// InstaScale configuration
91+
cfg := config.InstaScaleConfiguration{
92+
MaxScaleoutAllowed: 3,
93+
}
94+
95+
// Source InstaScale ConfigMap
96+
if InstaScaleConfigMap, err := kubeClient.CoreV1().ConfigMaps(configsNamespace).Get(ctx, "instascale-config", metav1.GetOptions{}); err != nil {
97+
setupLog.Error(err, "Error reading 'instascale-config' ConfigMap")
98+
os.Exit(1)
99+
} else if maxScaleoutAllowed := InstaScaleConfigMap.Data["maxScaleoutAllowed"]; maxScaleoutAllowed != "" {
100+
if max, err := strconv.Atoi(maxScaleoutAllowed); err != nil {
101+
setupLog.Error(err, "Error converting 'maxScaleoutAllowed' to integer")
102+
os.Exit(1)
103+
} else {
104+
cfg.MaxScaleoutAllowed = int32(max)
105+
}
106+
}
107+
108+
// Source OCM Secret optionally
109+
if OCMSecret, err := kubeClient.CoreV1().Secrets(ocmSecretNamespace).Get(ctx, "instascale-ocm-secret", metav1.GetOptions{}); apierrors.IsNotFound(err) {
110+
setupLog.Info("If you are looking to use OCM, ensure the 'instascale-ocm-secret' Secret has been created")
111+
} else if err != nil {
112+
setupLog.Error(err, "Error checking if OCM Secret exists")
113+
os.Exit(1)
114+
} else {
115+
cfg.OCMSecretRef = &corev1.SecretReference{
116+
Namespace: OCMSecret.Namespace,
117+
Name: OCMSecret.Name,
118+
}
119+
}
120+
121+
mgr, err := ctrl.NewManager(restConfig, ctrl.Options{
75122
Scheme: scheme,
76123
MetricsBindAddress: metricsAddr,
77124
Port: 9443,
@@ -85,15 +132,14 @@ func main() {
85132
}
86133

87134
if err = (&controllers.AppWrapperReconciler{
88-
Client: mgr.GetClient(),
89-
Scheme: mgr.GetScheme(),
90-
ConfigsNamespace: configsNamespace,
91-
OcmSecretNamespace: ocmSecretNamespace,
135+
Client: mgr.GetClient(),
136+
Scheme: mgr.GetScheme(),
137+
Config: cfg,
92138
}).SetupWithManager(mgr); err != nil {
93139
setupLog.Error(err, "unable to create controller", "controller", "AppWrapper")
94140
os.Exit(1)
95141
}
96-
//+kubebuilder:scaffold:builder
142+
// +kubebuilder:scaffold:builder
97143

98144
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
99145
setupLog.Error(err, "unable to set up health check")
@@ -105,7 +151,7 @@ func main() {
105151
}
106152

107153
setupLog.Info("starting manager")
108-
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
154+
if err := mgr.Start(ctx); err != nil {
109155
setupLog.Error(err, "problem running manager")
110156
os.Exit(1)
111157
}

pkg/config/config.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
Copyright 2022.
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 config
18+
19+
import corev1 "k8s.io/api/core/v1"
20+
21+
// InstaScaleConfiguration defines the InstaScale configuration.
22+
type InstaScaleConfiguration struct {
23+
// ocmSecretRef is reference to the authentication Secret for connecting to OCM.
24+
// If provided, MachinePools are used to auto-scale the cluster.
25+
// +optional
26+
OCMSecretRef *corev1.SecretReference `json:"ocmSecretRef,omitempty"`
27+
// maxScaleoutAllowed defines the upper limit for the number of cluster nodes
28+
// that can be scaled out by InstaScale.
29+
MaxScaleoutAllowed int32 `json:"maxScaleoutAllowed"`
30+
}

0 commit comments

Comments
 (0)