Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion bindata/v3.11.0/openshift-apiserver/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,11 @@ spec:
- check-endpoints
args:
- --listen
- 0.0.0.0:17698
- '${CHECK_ENDPOINTS_BIND_IP}:17698'
- --namespace
- $(POD_NAMESPACE)
- --config
- /var/run/configmaps/config/config.yaml
- --v
- '2'
env:
Expand All @@ -176,6 +178,9 @@ spec:
requests:
memory: 50Mi
cpu: 10m
volumeMounts:
- mountPath: /var/run/configmaps/config
name: config
terminationGracePeriodSeconds: 120 # a bit more than the 60 seconds timeout of non-long-running requests + the shutdown delay
volumes:
- name: node-pullsecrets
Expand Down
7 changes: 6 additions & 1 deletion pkg/operator/v311_00_assets/bindata.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 26 additions & 4 deletions pkg/operator/workload/workload_openshiftapiserver_v311_00_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,21 @@ func loglevelToKlog(logLevel operatorv1.LogLevel) string {
}
}

// checkEndpointsBindIPFromConfig returns the bind ip address to be used by the
// check-endpoints container inside the apiserver pod. The bind IP is derived
// from the bindNetwork property of the config.
func checkEndpointsBindIPFromConfig(config map[string]any) (string, error) {
var bindNetworkPath = []string{"servingInfo", "bindNetwork"}
observedBindNetwork, _, err := unstructured.NestedString(config, bindNetworkPath...)
if err != nil {
return "", fmt.Errorf("unable to extract bindNetwork from the observed config: %v, path = %v", err, bindNetworkPath)
}
if observedBindNetwork == "tcp6" {
return "[::]", nil
}
return "0.0.0.0", nil
}

func manageOpenShiftAPIServerDeployment_v311_00_to_latest(
ctx context.Context,
kubeClient kubernetes.Interface,
Expand All @@ -380,12 +395,23 @@ func manageOpenShiftAPIServerDeployment_v311_00_to_latest(
) (*appsv1.Deployment, bool, error) {
tmpl := v311_00_assets.MustAsset("v3.11.0/openshift-apiserver/deploy.yaml")

var observedConfig map[string]interface{}
if err := yaml.Unmarshal(operatorConfig.Spec.ObservedConfig.Raw, &observedConfig); err != nil {
return nil, false, fmt.Errorf("failed to unmarshal the observedConfig: %v", err)
}

checkEndpointsBindIP, err := checkEndpointsBindIPFromConfig(observedConfig)
if err != nil {
return nil, false, err
}

r := strings.NewReplacer(
"${IMAGE}", imagePullSpec,
"${OPERATOR_IMAGE}", operatorImagePullSpec,
"${REVISION}", strconv.Itoa(int(operatorConfig.Status.LatestAvailableRevision)),
"${VERBOSITY}", loglevelToKlog(operatorConfig.Spec.LogLevel),
"${KUBE_APISERVER_OPERATOR_IMAGE}", os.Getenv("KUBE_APISERVER_OPERATOR_IMAGE"),
"${CHECK_ENDPOINTS_BIND_IP}", checkEndpointsBindIP,
)
tmpl = []byte(r.Replace(string(tmpl)))

Expand All @@ -408,10 +434,6 @@ func manageOpenShiftAPIServerDeployment_v311_00_to_latest(
required.Labels["revision"] = strconv.Itoa(int(operatorConfig.Status.LatestAvailableRevision))
required.Spec.Template.Labels["revision"] = strconv.Itoa(int(operatorConfig.Status.LatestAvailableRevision))

var observedConfig map[string]interface{}
if err := yaml.Unmarshal(operatorConfig.Spec.ObservedConfig.Raw, &observedConfig); err != nil {
return nil, false, fmt.Errorf("failed to unmarshal the observedConfig: %v", err)
}
proxyConfig, _, err := unstructured.NestedStringMap(observedConfig, "workloadcontroller", "proxy")
if err != nil {
return nil, false, fmt.Errorf("couldn't get the proxy config from observedConfig: %v", err)
Expand Down
262 changes: 262 additions & 0 deletions pkg/operator/workload/workload_openshiftapiserver_v311_00_sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,110 @@ func TestPreconditionFulfilled(t *testing.T) {
}
}

func TestCheckEndpointsBindIPFromConfig(t *testing.T) {
testCases := []struct {
name string
config map[string]any
expectedBindIP string
expectError bool
}{
{
name: "tcp6 network returns IPv6 bind address",
config: map[string]any{
"servingInfo": map[string]any{
"bindNetwork": "tcp6",
},
},
expectedBindIP: "[::]",
expectError: false,
},
{
name: "tcp4 network returns IPv4 bind address",
config: map[string]any{
"servingInfo": map[string]any{
"bindNetwork": "tcp4",
},
},
expectedBindIP: "0.0.0.0",
expectError: false,
},
{
name: "tcp network returns IPv4 bind address",
config: map[string]any{
"servingInfo": map[string]any{
"bindNetwork": "tcp",
},
},
expectedBindIP: "0.0.0.0",
expectError: false,
},
{
name: "missing bindNetwork returns IPv4 bind address",
config: map[string]any{
"servingInfo": map[string]any{},
},
expectedBindIP: "0.0.0.0",
expectError: false,
},
{
name: "empty string bindNetwork returns IPv4 bind address",
config: map[string]any{
"servingInfo": map[string]any{
"bindNetwork": "",
},
},
expectedBindIP: "0.0.0.0",
expectError: false,
},
{
name: "missing servingInfo returns IPv4 bind address",
config: map[string]any{},
expectedBindIP: "0.0.0.0",
expectError: false,
},
{
name: "nil config returns IPv4 bind address",
config: nil,
expectedBindIP: "0.0.0.0",
expectError: false,
},
{
name: "servingInfo as non-map returns error",
config: map[string]any{
"servingInfo": "not-a-map",
},
expectedBindIP: "",
expectError: true,
},
{
name: "bindNetwork as non-string returns error",
config: map[string]any{
"servingInfo": map[string]any{
"bindNetwork": map[string]any{"nested": "value"},
},
},
expectedBindIP: "",
expectError: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
bindIP, err := checkEndpointsBindIPFromConfig(tc.config)

if tc.expectError && err == nil {
t.Fatal("expected an error but got none")
}
if !tc.expectError && err != nil {
t.Fatalf("unexpected error: %v", err)
}
if bindIP != tc.expectedBindIP {
t.Errorf("expected bind IP %q, got %q", tc.expectedBindIP, bindIP)
}
})
}
}

func TestCapabilities(t *testing.T) {
testCases := []struct {
name string
Expand Down Expand Up @@ -436,3 +540,161 @@ func TestCapabilities(t *testing.T) {
})
}
}

func TestCheckEndpointsContainerRendering(t *testing.T) {
testCases := []struct {
name string
observedConfig string
expectedListenArg string
}{
{
name: "tcp6 network renders IPv6 bind address in check-endpoints container",
observedConfig: `{
"servingInfo": {
"bindNetwork": "tcp6"
}
}`,
expectedListenArg: "[::]:17698",
},
{
name: "tcp4 network renders IPv4 bind address in check-endpoints container",
observedConfig: `{
"servingInfo": {
"bindNetwork": "tcp4"
}
}`,
expectedListenArg: "0.0.0.0:17698",
},
{
name: "tcp network renders IPv4 bind address in check-endpoints container",
observedConfig: `{
"servingInfo": {
"bindNetwork": "tcp"
}
}`,
expectedListenArg: "0.0.0.0:17698",
},
{
name: "missing bindNetwork renders IPv4 bind address in check-endpoints container",
observedConfig: `{
"servingInfo": {}
}`,
expectedListenArg: "0.0.0.0:17698",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fakeKubeClient := fake.NewSimpleClientset(
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "serving-cert",
Namespace: "openshift-apiserver",
},
},
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "etcd-client",
Namespace: operatorclient.GlobalUserSpecifiedConfigNamespace,
},
},
)

operatorConfig := &operatorv1.OpenShiftAPIServer{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster",
Generation: 100,
},
Spec: operatorv1.OpenShiftAPIServerSpec{
OperatorSpec: operatorv1.OperatorSpec{
ObservedConfig: runtime.RawExtension{
Raw: []byte(tc.observedConfig),
},
},
},
Status: operatorv1.OpenShiftAPIServerStatus{
OperatorStatus: operatorv1.OperatorStatus{
ObservedGeneration: 100,
},
},
}
apiServiceOperatorClient := operatorfake.NewSimpleClientset(operatorConfig)

clusterVersion := &configv1.ClusterVersion{
ObjectMeta: metav1.ObjectMeta{
Name: "version",
},
}
openshiftConfigClient := configfake.NewSimpleClientset(
&configv1.Image{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster",
},
},
clusterVersion,
)

fakeOperatorClient := operatorv1helpers.NewFakeOperatorClient(
&operatorv1.OperatorSpec{ManagementState: operatorv1.Managed},
&operatorv1.OperatorStatus{},
nil,
)

indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
indexer.Add(clusterVersion)

target := OpenShiftAPIServerWorkload{
kubeClient: fakeKubeClient,
operatorClient: fakeOperatorClient,
operatorConfigClient: apiServiceOperatorClient.OperatorV1(),
openshiftConfigClient: openshiftConfigClient.ConfigV1(),
clusterVersionLister: configlistersv1.NewClusterVersionLister(indexer),
versionRecorder: status.NewVersionGetter(),
countNodes: fakeCountNodes,
ensureAtMostOnePodPerNode: func(spec *appsv1.DeploymentSpec, componentName string) error { return nil },
featureGateAccessor: featuregates.NewHardcodedFeatureGateAccessForTesting(nil, nil, make(chan struct{}), nil),
}

ctx := context.Background()
if _, _, err := target.Sync(ctx, factory.NewSyncContext("TestSyncContext", events.NewInMemoryRecorder("", clocktesting.NewFakePassiveClock(time.Now())))); len(err) > 0 {
t.Fatal(err)
}

deployment, err := fakeKubeClient.AppsV1().Deployments("openshift-apiserver").Get(ctx, "apiserver", metav1.GetOptions{})
if err != nil {
t.Fatalf("failed to get deployment: %v", err)
}

// find the check-endpoints container
var checkEndpointsContainer *corev1.Container
for i := range deployment.Spec.Template.Spec.Containers {
if deployment.Spec.Template.Spec.Containers[i].Name != "openshift-apiserver-check-endpoints" {
continue
}
checkEndpointsContainer = &deployment.Spec.Template.Spec.Containers[i]
break
}
if checkEndpointsContainer == nil {
t.Fatalf("check-endpoints container not found in deployment")
}

// verify the --listen argument
var actualListenArg string
for i, arg := range checkEndpointsContainer.Args {
if arg != "--listen" || i+1 >= len(checkEndpointsContainer.Args) {
continue
}
actualListenArg = checkEndpointsContainer.Args[i+1]
break
}

if actualListenArg == "" {
t.Fatalf("--listen argument not found in check-endpoints container args: %v", checkEndpointsContainer.Args)
}

if actualListenArg != tc.expectedListenArg {
t.Errorf("expected --listen arg %q, got %q", tc.expectedListenArg, actualListenArg)
}
})
}
}