Skip to content

Commit 7082cfe

Browse files
elmikoenxebre
authored andcommitted
Add the ability to override CAPI group via env variable and discover API version.
This change adds detection for an environment variable to specify the group for the clusterapi resources. If the environment variable `CAPI_GROUP` is specified, then it will be used instead of the default. This also decouples the API group from the version and let the latter to be discovered dynamically.
1 parent a469c85 commit 7082cfe

File tree

3 files changed

+196
-15
lines changed

3 files changed

+196
-15
lines changed

cluster-autoscaler/cloudprovider/clusterapi/clusterapi_controller.go

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ package clusterapi
1919
import (
2020
"context"
2121
"fmt"
22+
"os"
2223

2324
corev1 "k8s.io/api/core/v1"
2425
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2526
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2627
"k8s.io/apimachinery/pkg/labels"
2728
"k8s.io/apimachinery/pkg/runtime/schema"
29+
"k8s.io/client-go/discovery"
2830
"k8s.io/client-go/dynamic"
2931
"k8s.io/client-go/dynamic/dynamicinformer"
3032
"k8s.io/client-go/informers"
@@ -38,7 +40,9 @@ import (
3840
const (
3941
machineProviderIDIndex = "machineProviderIDIndex"
4042
nodeProviderIDIndex = "nodeProviderIDIndex"
41-
defaultMachineAPI = "v1alpha2.cluster.x-k8s.io"
43+
defaultCAPIGroup = "cluster.x-k8s.io"
44+
// CAPIGroupEnvVar contains the environment variable name which allows overriding defaultCAPIGroup.
45+
CAPIGroupEnvVar = "CAPI_GROUP"
4246
)
4347

4448
// machineController watches for Nodes, Machines, MachineSets and
@@ -271,37 +275,58 @@ func (c *machineController) machinesInMachineSet(machineSet *MachineSet) ([]*Mac
271275
return result, nil
272276
}
273277

278+
// getCAPIGroup returns a string that specifies the group for the API.
279+
// It will return either the value from the
280+
// CAPI_GROUP environment variable, or the default value i.e cluster.x-k8s.io.
281+
func getCAPIGroup() string {
282+
g := os.Getenv(CAPIGroupEnvVar)
283+
if g == "" {
284+
g = defaultCAPIGroup
285+
}
286+
klog.V(4).Infof("Using API Group %q", g)
287+
return g
288+
}
289+
274290
// newMachineController constructs a controller that watches Nodes,
275291
// Machines and MachineSet as they are added, updated and deleted on
276292
// the cluster.
277293
func newMachineController(
278294
dynamicclient dynamic.Interface,
279295
kubeclient kubeclient.Interface,
296+
discoveryclient discovery.DiscoveryInterface,
280297
) (*machineController, error) {
281298
kubeInformerFactory := kubeinformers.NewSharedInformerFactory(kubeclient, 0)
282299
informerFactory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(dynamicclient, 0, metav1.NamespaceAll, nil)
283300

284-
// TODO(alberto): let environment variable to override defaultMachineAPI
285-
machineDeploymentResource, _ := schema.ParseResourceArg(fmt.Sprintf("machinedeployments.%v", defaultMachineAPI))
301+
CAPIGroup := getCAPIGroup()
302+
CAPIVersion, err := getAPIGroupPreferredVersion(discoveryclient, CAPIGroup)
303+
if err != nil {
304+
panic("CAPIVersion")
305+
}
306+
klog.Infof("Using version %q for API group %q", CAPIVersion, CAPIGroup)
307+
308+
machineDeploymentResource, _ := schema.ParseResourceArg(fmt.Sprintf("machinedeployments.%v.%v", CAPIVersion, CAPIGroup))
309+
if machineDeploymentResource == nil {
310+
panic("MachineDeployment")
311+
}
286312

287-
machineSetResource, _ := schema.ParseResourceArg(fmt.Sprintf("machinesets.%v", defaultMachineAPI))
313+
machineSetResource, _ := schema.ParseResourceArg(fmt.Sprintf("machinesets.%v.%v", CAPIVersion, CAPIGroup))
288314
if machineSetResource == nil {
289315
panic("MachineSetResource")
290316
}
291317

292-
machineResource, _ := schema.ParseResourceArg(fmt.Sprintf("machines.%v", defaultMachineAPI))
318+
machineResource, _ := schema.ParseResourceArg(fmt.Sprintf("machines.%v.%v", CAPIVersion, CAPIGroup))
293319
if machineResource == nil {
294320
panic("machineResource")
295321
}
322+
296323
machineInformer := informerFactory.ForResource(*machineResource)
297324
machineSetInformer := informerFactory.ForResource(*machineSetResource)
298-
var machineDeploymentInformer informers.GenericInformer
299-
300-
machineDeploymentInformer = informerFactory.ForResource(*machineDeploymentResource)
301-
machineDeploymentInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{})
325+
machineDeploymentInformer := informerFactory.ForResource(*machineDeploymentResource)
302326

303327
machineInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{})
304328
machineSetInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{})
329+
machineDeploymentInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{})
305330

306331
nodeInformer := kubeInformerFactory.Core().V1().Nodes().Informer()
307332
nodeInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{})
@@ -332,6 +357,21 @@ func newMachineController(
332357
}, nil
333358
}
334359

360+
func getAPIGroupPreferredVersion(client discovery.DiscoveryInterface, APIGroup string) (string, error) {
361+
groupList, err := client.ServerGroups()
362+
if err != nil {
363+
return "", fmt.Errorf("failed to get ServerGroups: %v", err)
364+
}
365+
366+
for _, group := range groupList.Groups {
367+
if group.Name == APIGroup {
368+
return group.PreferredVersion.Version, nil
369+
}
370+
}
371+
372+
return "", fmt.Errorf("failed to find API group %q", APIGroup)
373+
}
374+
335375
func (c *machineController) machineSetProviderIDs(machineSet *MachineSet) ([]string, error) {
336376
machines, err := c.machinesInMachineSet(machineSet)
337377
if err != nil {

cluster-autoscaler/cloudprovider/clusterapi/clusterapi_controller_test.go

Lines changed: 139 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package clusterapi
1818

1919
import (
2020
"fmt"
21+
"os"
2122
"path"
2223
"reflect"
2324
"sort"
@@ -29,8 +30,10 @@ import (
2930
"k8s.io/apimachinery/pkg/labels"
3031
"k8s.io/apimachinery/pkg/runtime"
3132
"k8s.io/apimachinery/pkg/types"
33+
fakediscovery "k8s.io/client-go/discovery/fake"
3234
fakedynamic "k8s.io/client-go/dynamic/fake"
3335
fakekube "k8s.io/client-go/kubernetes/fake"
36+
clientgotesting "k8s.io/client-go/testing"
3437
"k8s.io/utils/pointer"
3538
)
3639

@@ -53,6 +56,8 @@ type testSpec struct {
5356
rootIsMachineDeployment bool
5457
}
5558

59+
const customCAPIGroup = "custom.x-k8s.io"
60+
5661
func mustCreateTestController(t *testing.T, testConfigs ...*testConfig) (*machineController, testControllerShutdownFunc) {
5762
t.Helper()
5863

@@ -76,7 +81,19 @@ func mustCreateTestController(t *testing.T, testConfigs ...*testConfig) (*machin
7681

7782
kubeclientSet := fakekube.NewSimpleClientset(nodeObjects...)
7883
dynamicClientset := fakedynamic.NewSimpleDynamicClient(runtime.NewScheme(), machineObjects...)
79-
controller, err := newMachineController(dynamicClientset, kubeclientSet)
84+
discoveryClient := &fakediscovery.FakeDiscovery{
85+
Fake: &clientgotesting.Fake{
86+
Resources: []*v1.APIResourceList{
87+
{
88+
GroupVersion: fmt.Sprintf("%s/v1beta1", customCAPIGroup),
89+
},
90+
{
91+
GroupVersion: fmt.Sprintf("%s/v1alpha3", defaultCAPIGroup),
92+
},
93+
},
94+
},
95+
}
96+
controller, err := newMachineController(dynamicClientset, kubeclientSet, discoveryClient)
8097
if err != nil {
8198
t.Fatal("failed to create test controller")
8299
}
@@ -136,7 +153,7 @@ func createTestConfigs(specs ...testSpec) []*testConfig {
136153

137154
config.machineSet = &MachineSet{
138155
TypeMeta: v1.TypeMeta{
139-
APIVersion: "cluster.x-k8s.io/v1alpha2",
156+
APIVersion: fmt.Sprintf("%s/v1alpha3", defaultCAPIGroup),
140157
Kind: "MachineSet",
141158
},
142159
ObjectMeta: v1.ObjectMeta{
@@ -152,7 +169,7 @@ func createTestConfigs(specs ...testSpec) []*testConfig {
152169
} else {
153170
config.machineDeployment = &MachineDeployment{
154171
TypeMeta: v1.TypeMeta{
155-
APIVersion: "cluster.x-k8s.io/v1alpha2",
172+
APIVersion: fmt.Sprintf("%s/v1alpha3", defaultCAPIGroup),
156173
Kind: "MachineDeployment",
157174
},
158175
ObjectMeta: v1.ObjectMeta{
@@ -211,7 +228,7 @@ func makeLinkedNodeAndMachine(i int, namespace string, owner v1.OwnerReference)
211228

212229
machine := &Machine{
213230
TypeMeta: v1.TypeMeta{
214-
APIVersion: "cluster.x-k8s.io/v1alpha2",
231+
APIVersion: fmt.Sprintf("%s/v1alpha3", defaultCAPIGroup),
215232
Kind: "Machine",
216233
},
217234
ObjectMeta: v1.ObjectMeta{
@@ -991,3 +1008,121 @@ func TestControllerMachineSetNodeNamesUsingStatusNodeRefName(t *testing.T) {
9911008
}
9921009
}
9931010
}
1011+
1012+
func TestControllerGetAPIVersionGroup(t *testing.T) {
1013+
expected := "mygroup"
1014+
if err := os.Setenv(CAPIGroupEnvVar, expected); err != nil {
1015+
t.Fatalf("unexpected error: %v", err)
1016+
}
1017+
observed := getCAPIGroup()
1018+
if observed != expected {
1019+
t.Fatalf("Wrong Version Group detected, expected %q, got %q", expected, observed)
1020+
}
1021+
1022+
expected = defaultCAPIGroup
1023+
if err := os.Setenv(CAPIGroupEnvVar, ""); err != nil {
1024+
t.Fatalf("unexpected error: %v", err)
1025+
}
1026+
observed = getCAPIGroup()
1027+
if observed != expected {
1028+
t.Fatalf("Wrong Version Group detected, expected %q, got %q", expected, observed)
1029+
}
1030+
}
1031+
1032+
func TestControllerGetAPIVersionGroupWithMachineDeployments(t *testing.T) {
1033+
testConfig := createMachineDeploymentTestConfig(testNamespace, 1, map[string]string{
1034+
nodeGroupMinSizeAnnotationKey: "1",
1035+
nodeGroupMaxSizeAnnotationKey: "1",
1036+
})
1037+
if err := os.Setenv(CAPIGroupEnvVar, customCAPIGroup); err != nil {
1038+
t.Fatalf("unexpected error: %v", err)
1039+
}
1040+
1041+
testConfig.machineDeployment.TypeMeta.APIVersion = fmt.Sprintf("%s/v1beta1", customCAPIGroup)
1042+
testConfig.machineSet.TypeMeta.APIVersion = fmt.Sprintf("%s/v1beta1", customCAPIGroup)
1043+
for _, machine := range testConfig.machines {
1044+
machine.TypeMeta.APIVersion = fmt.Sprintf("%s/v1beta1", customCAPIGroup)
1045+
}
1046+
controller, stop := mustCreateTestController(t, testConfig)
1047+
defer stop()
1048+
1049+
machineDeployments, err := controller.listMachineDeployments(testNamespace, labels.Everything())
1050+
if err != nil {
1051+
t.Fatalf("unexpected error: %v", err)
1052+
}
1053+
if l := len(machineDeployments); l != 1 {
1054+
t.Fatalf("Incorrect number of MachineDeployments, expected 1, got %d", l)
1055+
}
1056+
1057+
machineSets, err := controller.listMachineSets(testNamespace, labels.Everything())
1058+
if err != nil {
1059+
t.Fatalf("unexpected error: %v", err)
1060+
}
1061+
if l := len(machineSets); l != 1 {
1062+
t.Fatalf("Incorrect number of MachineSets, expected 1, got %d", l)
1063+
}
1064+
1065+
machines, err := controller.listMachines(testNamespace, labels.Everything())
1066+
if err != nil {
1067+
t.Fatalf("unexpected error: %v", err)
1068+
}
1069+
if l := len(machines); l != 1 {
1070+
t.Fatalf("Incorrect number of Machines, expected 1, got %d", l)
1071+
}
1072+
1073+
if err := os.Unsetenv(CAPIGroupEnvVar); err != nil {
1074+
t.Fatalf("unexpected error: %v", err)
1075+
}
1076+
}
1077+
1078+
func TestGetAPIGroupPreferredVersion(t *testing.T) {
1079+
testCases := []struct {
1080+
description string
1081+
APIGroup string
1082+
preferredVersion string
1083+
error bool
1084+
}{
1085+
{
1086+
description: "find version for default API group",
1087+
APIGroup: defaultCAPIGroup,
1088+
preferredVersion: "v1alpha3",
1089+
error: false,
1090+
},
1091+
{
1092+
description: "find version for another API group",
1093+
APIGroup: customCAPIGroup,
1094+
preferredVersion: "v1beta1",
1095+
error: false,
1096+
},
1097+
{
1098+
description: "API group does not exist",
1099+
APIGroup: "does.not.exist",
1100+
preferredVersion: "",
1101+
error: true,
1102+
},
1103+
}
1104+
1105+
discoveryClient := &fakediscovery.FakeDiscovery{
1106+
Fake: &clientgotesting.Fake{
1107+
Resources: []*v1.APIResourceList{
1108+
{
1109+
GroupVersion: fmt.Sprintf("%s/v1beta1", customCAPIGroup),
1110+
},
1111+
{
1112+
GroupVersion: fmt.Sprintf("%s/v1alpha3", defaultCAPIGroup),
1113+
},
1114+
},
1115+
},
1116+
}
1117+
for _, tc := range testCases {
1118+
t.Run(tc.description, func(t *testing.T) {
1119+
version, err := getAPIGroupPreferredVersion(discoveryClient, tc.APIGroup)
1120+
if (err != nil) != tc.error {
1121+
t.Errorf("expected to have error: %t. Had an error: %t", tc.error, err != nil)
1122+
}
1123+
if version != tc.preferredVersion {
1124+
t.Errorf("expected %v, got: %v", tc.preferredVersion, version)
1125+
}
1126+
})
1127+
}
1128+
}

cluster-autoscaler/cloudprovider/clusterapi/clusterapi_provider.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider"
2525
"k8s.io/autoscaler/cluster-autoscaler/config"
2626
"k8s.io/autoscaler/cluster-autoscaler/utils/errors"
27+
"k8s.io/client-go/discovery"
2728
"k8s.io/client-go/dynamic"
2829
"k8s.io/client-go/kubernetes"
2930
"k8s.io/client-go/tools/clientcmd"
@@ -146,12 +147,17 @@ func BuildClusterAPI(opts config.AutoscalingOptions, do cloudprovider.NodeGroupD
146147
klog.Fatalf("could not generate dynamic client for config")
147148
}
148149

149-
kubeclient, err := kubernetes.NewForConfig(externalConfig)
150+
kubeClient, err := kubernetes.NewForConfig(externalConfig)
150151
if err != nil {
151152
klog.Fatalf("create kube clientset failed: %v", err)
152153
}
153154

154-
controller, err := newMachineController(dc, kubeclient)
155+
discoveryClient, err := discovery.NewDiscoveryClientForConfig(externalConfig)
156+
if err != nil {
157+
klog.Fatalf("create discovery client failed: %v", err)
158+
}
159+
160+
controller, err := newMachineController(dc, kubeClient, discoveryClient)
155161
if err != nil {
156162
klog.Fatal(err)
157163
}

0 commit comments

Comments
 (0)