diff --git a/internal/clients/config_status.go b/internal/clients/config_status.go index a62da397f6..1f0e4977ca 100644 --- a/internal/clients/config_status.go +++ b/internal/clients/config_status.go @@ -10,16 +10,59 @@ import ( type ConfigStatus int const ( - // ConfigStatusOK: no error happens in translation from k8s objects to kong configuration + // ConfigStatusOK no error happens in translation from k8s objects to kong configuration // and succeeded to apply kong configuration to kong gateway. ConfigStatusOK ConfigStatus = iota - // ConfigStatusTranslationErrorHappened: error happened in translation of k8s objects + // ConfigStatusTranslationErrorHappened error happened in translation of k8s objects // but succeeded to apply kong configuration for remaining objects. ConfigStatusTranslationErrorHappened - // ConfigStatusApplyFailed: failed to apply kong configurations. + // ConfigStatusApplyFailed failed to apply kong configurations. ConfigStatusApplyFailed + // ConfigStatusOKKonnectApplyFailed no error happens in translation from k8s objects to kong configuration + // and succeeded to apply kong configuration to kong gateway. + ConfigStatusOKKonnectApplyFailed + // ConfigStatusTranslationErrorHappenedKonnectApplyFailed error happened in translation of k8s objects + // but succeeded to apply kong configuration for remaining objects. + ConfigStatusTranslationErrorHappenedKonnectApplyFailed + // ConfigStatusApplyFailedKonnectApplyFailed failed to apply kong configurations. + ConfigStatusApplyFailedKonnectApplyFailed + // ConfigStatusUnknown. + ConfigStatusUnknown ) +type CalculateConfigStatusInput struct { + // Any error occurred when syncing with Gateways. + GatewaysFailed bool + + // Any error occurred when syncing with Konnect, + KonnectFailed bool + + // translation of some of Kubernetes objects failed, and they were skipped. + TranslationFailuresOccurred bool +} + +// CalculateConfigStatus calculates a clients.ConfigStatus that sums up the configuration synchronisation result as +// a single enumerated value. +func CalculateConfigStatus(i CalculateConfigStatusInput) ConfigStatus { + switch { + case !i.GatewaysFailed && !i.KonnectFailed && !i.TranslationFailuresOccurred: + return ConfigStatusOK + case !i.GatewaysFailed && !i.KonnectFailed && i.TranslationFailuresOccurred: + return ConfigStatusTranslationErrorHappened + case i.GatewaysFailed && !i.KonnectFailed: + return ConfigStatusApplyFailed + case !i.GatewaysFailed && i.KonnectFailed && !i.TranslationFailuresOccurred: + return ConfigStatusOKKonnectApplyFailed + case !i.GatewaysFailed && i.KonnectFailed && i.TranslationFailuresOccurred: + return ConfigStatusTranslationErrorHappenedKonnectApplyFailed + case i.GatewaysFailed && i.KonnectFailed: + return ConfigStatusApplyFailedKonnectApplyFailed + } + + // shouldn't happen + return ConfigStatusUnknown +} + type ConfigStatusNotifier interface { NotifyConfigStatus(context.Context, ConfigStatus) } diff --git a/internal/clients/config_status_test.go b/internal/clients/config_status_test.go index 6d1a811572..6ed90e273c 100644 --- a/internal/clients/config_status_test.go +++ b/internal/clients/config_status_test.go @@ -1,4 +1,4 @@ -package clients +package clients_test import ( "context" @@ -6,11 +6,14 @@ import ( "time" "github.com/go-logr/logr/testr" + "github.com/stretchr/testify/require" + + "github.com/kong/kubernetes-ingress-controller/v2/internal/clients" ) func TestChannelConfigNotifier(t *testing.T) { logger := testr.New(t) - n := NewChannelConfigNotifier(logger) + n := clients.NewChannelConfigNotifier(logger) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -18,7 +21,7 @@ func TestChannelConfigNotifier(t *testing.T) { // Call NotifyConfigStatus 5 times to make sure that the method is non-blocking. for i := 0; i < 5; i++ { - n.NotifyConfigStatus(ctx, ConfigStatusOK) + n.NotifyConfigStatus(ctx, clients.ConfigStatusOK) } for i := 0; i < 5; i++ { @@ -29,3 +32,58 @@ func TestChannelConfigNotifier(t *testing.T) { } } } + +func TestCalculateConfigStatus(t *testing.T) { + testCases := []struct { + name string + + gatewayFailure bool + konnectFailure bool + translationFailures bool + + expectedConfigStatus clients.ConfigStatus + }{ + { + name: "success", + expectedConfigStatus: clients.ConfigStatusOK, + }, + { + name: "gateway failure", + gatewayFailure: true, + expectedConfigStatus: clients.ConfigStatusApplyFailed, + }, + { + name: "translation failures", + translationFailures: true, + expectedConfigStatus: clients.ConfigStatusTranslationErrorHappened, + }, + { + name: "konnect failure", + konnectFailure: true, + expectedConfigStatus: clients.ConfigStatusOKKonnectApplyFailed, + }, + { + name: "both gateway and konnect failure", + gatewayFailure: true, + konnectFailure: true, + expectedConfigStatus: clients.ConfigStatusApplyFailedKonnectApplyFailed, + }, + { + name: "translation failures and konnect failure", + translationFailures: true, + konnectFailure: true, + expectedConfigStatus: clients.ConfigStatusTranslationErrorHappenedKonnectApplyFailed, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := clients.CalculateConfigStatus(clients.CalculateConfigStatusInput{ + GatewaysFailed: tc.gatewayFailure, + KonnectFailed: tc.konnectFailure, + TranslationFailuresOccurred: tc.translationFailures, + }) + require.Equal(t, tc.expectedConfigStatus, result) + }) + } +} diff --git a/internal/dataplane/kong_client.go b/internal/dataplane/kong_client.go index e9f202f080..537725fc95 100644 --- a/internal/dataplane/kong_client.go +++ b/internal/dataplane/kong_client.go @@ -368,21 +368,22 @@ func (c *KongClient) Update(ctx context.Context) error { c.logger.Debug("successfully built data-plane configuration") } - shas, err := c.sendOutToGatewayClients(ctx, parsingResult.KongState, c.kongConfig) - if err != nil { - c.configStatusNotifier.NotifyConfigStatus(ctx, clients.ConfigStatusApplyFailed) - return err - } - - c.trySendOutToKonnectClient(ctx, parsingResult.KongState, c.kongConfig) - - // succeeded to apply configuration to Kong gateway. - // notify the receiver of config status that translation error happened when there are translation errors, - // otherwise notify that config status is OK. - if len(parsingResult.TranslationFailures) > 0 { - c.configStatusNotifier.NotifyConfigStatus(ctx, clients.ConfigStatusTranslationErrorHappened) - } else { - c.configStatusNotifier.NotifyConfigStatus(ctx, clients.ConfigStatusOK) + shas, gatewaysSyncErr := c.sendOutToGatewayClients(ctx, parsingResult.KongState, c.kongConfig) + konnectSyncErr := c.maybeSendOutToKonnectClient(ctx, parsingResult.KongState, c.kongConfig) + + // Taking into account the results of syncing configuration with Gateways and Konnect, and potential translation + // failures, calculate the config status and report it. + c.configStatusNotifier.NotifyConfigStatus(ctx, clients.CalculateConfigStatus( + clients.CalculateConfigStatusInput{ + GatewaysFailed: gatewaysSyncErr != nil, + KonnectFailed: konnectSyncErr != nil, + TranslationFailuresOccurred: len(parsingResult.TranslationFailures) > 0, + }, + )) + + // In case of a failure in syncing configuration with Gateways, propagate the error. + if gatewaysSyncErr != nil { + return gatewaysSyncErr } // report on configured Kubernetes objects if enabled @@ -420,12 +421,13 @@ func (c *KongClient) sendOutToGatewayClients( return previousSHAs, nil } -// It will try to send ignore errors that are returned from Konnect client. -func (c *KongClient) trySendOutToKonnectClient(ctx context.Context, s *kongstate.KongState, config sendconfig.Config) { +// maybeSendOutToKonnectClient sends out the configuration to Konnect when KonnectClient is provided. +// It's a noop when Konnect integration is not enabled. +func (c *KongClient) maybeSendOutToKonnectClient(ctx context.Context, s *kongstate.KongState, config sendconfig.Config) error { konnectClient := c.clientsProvider.KonnectClient() // There's no KonnectClient configured, that's totally fine. if konnectClient == nil { - return + return nil } if _, err := c.sendToClient(ctx, konnectClient, s, config); err != nil { @@ -434,11 +436,13 @@ func (c *KongClient) trySendOutToKonnectClient(ctx context.Context, s *kongstate if errors.Is(err, sendconfig.ErrUpdateSkippedDueToBackoffStrategy{}) { c.logger.WithError(err).Warn("Skipped pushing configuration to Konnect") - return } c.logger.WithError(err).Warn("Failed pushing configuration to Konnect") + return err } + + return nil } func (c *KongClient) sendToClient( diff --git a/internal/dataplane/kong_client_test.go b/internal/dataplane/kong_client_test.go index 7b2b855b02..c20b3b5fcb 100644 --- a/internal/dataplane/kong_client_test.go +++ b/internal/dataplane/kong_client_test.go @@ -23,6 +23,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/kong/kubernetes-ingress-controller/v2/internal/adminapi" + "github.com/kong/kubernetes-ingress-controller/v2/internal/clients" "github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane" "github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/failures" "github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/kongstate" @@ -143,7 +144,7 @@ func (f mockGatewayClientsProvider) GatewayClients() []*adminapi.Client { // mockUpdateStrategy is a mock implementation of sendconfig.UpdateStrategyResolver. type mockUpdateStrategyResolver struct { updateCalledForURLs []string - shouldReturnErrorOnUpdate map[string]struct{} + shouldReturnErrorOnUpdate map[string]bool t *testing.T lock sync.RWMutex } @@ -151,7 +152,7 @@ type mockUpdateStrategyResolver struct { func newMockUpdateStrategyResolver(t *testing.T) *mockUpdateStrategyResolver { return &mockUpdateStrategyResolver{ t: t, - shouldReturnErrorOnUpdate: map[string]struct{}{}, + shouldReturnErrorOnUpdate: map[string]bool{}, } } @@ -164,11 +165,11 @@ func (f *mockUpdateStrategyResolver) ResolveUpdateStrategy(c sendconfig.UpdateCl } // returnErrorOnUpdate will cause the mockUpdateStrategy with a given Admin API URL to return an error on Update(). -func (f *mockUpdateStrategyResolver) returnErrorOnUpdate(url string) { +func (f *mockUpdateStrategyResolver) returnErrorOnUpdate(url string, shouldReturnErr bool) { f.lock.Lock() defer f.lock.Unlock() - f.shouldReturnErrorOnUpdate[url] = struct{}{} + f.shouldReturnErrorOnUpdate[url] = shouldReturnErr } // updateCalledForURLCallback returns a function that will be called when the mockUpdateStrategy is called. @@ -179,7 +180,7 @@ func (f *mockUpdateStrategyResolver) updateCalledForURLCallback(url string) func defer f.lock.Unlock() f.updateCalledForURLs = append(f.updateCalledForURLs, url) - if _, ok := f.shouldReturnErrorOnUpdate[url]; ok { + if ok := f.shouldReturnErrorOnUpdate[url]; ok { return errors.New("error on update") } return nil @@ -203,7 +204,7 @@ func (f *mockUpdateStrategyResolver) assertNoUpdateCalled() { // mockUpdateStrategy is a mock implementation of sendconfig.UpdateStrategy. type mockUpdateStrategy struct { - onUpdate func() error + onUpdate func() (err error) } func (m *mockUpdateStrategy) Update(context.Context, sendconfig.ContentWithHash) ( @@ -234,14 +235,6 @@ func (m mockConfigurationChangeDetector) HasConfigurationChanged( return m.hasConfigurationChanged, nil } -type noopKongConfigBuilder struct{} - -func (p noopKongConfigBuilder) BuildKongConfig() parser.KongConfigBuildingResult { - return parser.KongConfigBuildingResult{ - KongState: &kongstate.KongState{}, - } -} - func TestKongClientUpdate_AllExpectedClientsAreCalled(t *testing.T) { t.Parallel() @@ -315,12 +308,13 @@ func TestKongClientUpdate_AllExpectedClientsAreCalled(t *testing.T) { } updateStrategyResolver := newMockUpdateStrategyResolver(t) for _, url := range tc.errorOnUpdateForURLs { - updateStrategyResolver.returnErrorOnUpdate(url) + updateStrategyResolver.returnErrorOnUpdate(url, true) } // always return true for HasConfigurationChanged to trigger an update configChangeDetector := mockConfigurationChangeDetector{hasConfigurationChanged: true} + configBuilder := newMockKongConfigBuilder() - kongClient := setupTestKongClient(t, updateStrategyResolver, clientsProvider, configChangeDetector) + kongClient := setupTestKongClient(t, updateStrategyResolver, clientsProvider, configChangeDetector, configBuilder) err := kongClient.Update(ctx) if tc.expectError { @@ -347,8 +341,9 @@ func TestKongClientUpdate_WhenNoChangeInConfigNoClientGetsCalled(t *testing.T) { // no change in config, we'll expect no update to be called configChangeDetector := mockConfigurationChangeDetector{hasConfigurationChanged: false} + configBuilder := newMockKongConfigBuilder() - kongClient := setupTestKongClient(t, updateStrategyResolver, clientsProvider, configChangeDetector) + kongClient := setupTestKongClient(t, updateStrategyResolver, clientsProvider, configChangeDetector, configBuilder) ctx := context.Background() err := kongClient.Update(ctx) @@ -357,19 +352,131 @@ func TestKongClientUpdate_WhenNoChangeInConfigNoClientGetsCalled(t *testing.T) { updateStrategyResolver.assertNoUpdateCalled() } +type mockConfigStatusQueue struct { + wasNotified bool +} + +func newMockConfigStatusNotifier() *mockConfigStatusQueue { + return &mockConfigStatusQueue{} +} + +func (m *mockConfigStatusQueue) NotifyConfigStatus(context.Context, clients.ConfigStatus) { + m.wasNotified = true +} + +func (m *mockConfigStatusQueue) WasNotified() bool { + return m.wasNotified +} + +type mockKongConfigBuilder struct { + translationFailuresToReturn []failures.ResourceFailure +} + +func newMockKongConfigBuilder() *mockKongConfigBuilder { + return &mockKongConfigBuilder{} +} + +func (p *mockKongConfigBuilder) BuildKongConfig() parser.KongConfigBuildingResult { + return parser.KongConfigBuildingResult{ + KongState: &kongstate.KongState{}, + TranslationFailures: p.translationFailuresToReturn, + } +} + +func (p *mockKongConfigBuilder) returnTranslationFailures(enabled bool) { + if enabled { + // Return some mocked translation failures. + p.translationFailuresToReturn = []failures.ResourceFailure{ + lo.Must(failures.NewResourceFailure("some reason", &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + Namespace: "namespace", + }, + TypeMeta: metav1.TypeMeta{ + Kind: "Pod", + APIVersion: "v1", + }, + }, + )), + } + } else { + p.translationFailuresToReturn = nil + } +} + +func TestKongClientUpdate_ConfigStatusIsAlwaysNotified(t *testing.T) { + testCases := []struct { + name string + gatewayFailure bool + konnectFailure bool + translationFailures bool + }{ + { + name: "success", + }, + { + name: "gateway failure", + gatewayFailure: true, + }, + { + name: "translation failures", + translationFailures: true, + }, + { + name: "konnect failure", + konnectFailure: true, + }, + { + name: "both gateway and konnect failure", + gatewayFailure: true, + konnectFailure: true, + }, + { + name: "translation failures and konnect failure", + translationFailures: true, + konnectFailure: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + gatewayClient := mustSampleGatewayClient(t) + konnectClient := mustSampleKonnectClient(t) + clientsProvider := mockGatewayClientsProvider{ + gatewayClients: []*adminapi.Client{gatewayClient}, + konnectClient: konnectClient, + } + updateStrategyResolver := newMockUpdateStrategyResolver(t) + configChangeDetector := mockConfigurationChangeDetector{hasConfigurationChanged: true} + configBuilder := newMockKongConfigBuilder() + kongClient := setupTestKongClient(t, updateStrategyResolver, clientsProvider, configChangeDetector, configBuilder) + statusQueue := newMockConfigStatusNotifier() + kongClient.SetConfigStatusNotifier(statusQueue) + + updateStrategyResolver.returnErrorOnUpdate(konnectClient.BaseRootURL(), tc.konnectFailure) + updateStrategyResolver.returnErrorOnUpdate(gatewayClient.BaseRootURL(), tc.gatewayFailure) + configBuilder.returnTranslationFailures(tc.translationFailures) + + _ = kongClient.Update(ctx) + require.True(t, statusQueue.WasNotified()) + }) + } +} + // setupTestKongClient creates a KongClient with mocked dependencies. func setupTestKongClient( t *testing.T, updateStrategyResolver *mockUpdateStrategyResolver, clientsProvider mockGatewayClientsProvider, configChangeDetector sendconfig.ConfigurationChangeDetector, + configBuilder *mockKongConfigBuilder, ) *dataplane.KongClient { logger := logrus.New() timeout := time.Second ingressClass := "kong" diagnostic := util.ConfigDumpDiagnostic{} config := sendconfig.Config{} - eventRecorder := record.NewFakeRecorder(0) dbMode := "off" kongClient, err := dataplane.NewKongClient( @@ -378,18 +485,31 @@ func setupTestKongClient( ingressClass, diagnostic, config, - eventRecorder, + newFakeEventsRecorder(t), dbMode, clientsProvider, updateStrategyResolver, configChangeDetector, - noopKongConfigBuilder{}, + configBuilder, store.NewCacheStores(), ) require.NoError(t, err) return kongClient } +func newFakeEventsRecorder(t *testing.T) *record.FakeRecorder { + eventRecorder := record.NewFakeRecorder(0) + + // Ingest events and log them. + go func() { + for e := range eventRecorder.Events { + t.Logf("Event recorded: %s", e) + } + }() + + return eventRecorder +} + func mustSampleGatewayClient(t *testing.T) *adminapi.Client { t.Helper() c, err := adminapi.NewTestClient(fmt.Sprintf("https://%s:8080", uuid.NewString())) diff --git a/internal/konnect/node_agent.go b/internal/konnect/node_agent.go index 2e2eacb756..add0155f57 100644 --- a/internal/konnect/node_agent.go +++ b/internal/konnect/node_agent.go @@ -146,6 +146,9 @@ func (a *NodeAgent) subscribeConfigStatus(ctx context.Context) { return case configStatus := <-ch: a.configStatus.Store(uint32(configStatus)) + if err := a.updateNodes(ctx); err != nil { + a.logger.Error(err, "failed to update nodes after config status changed") + } } } } @@ -203,6 +206,13 @@ func (a *NodeAgent) updateKICNode(ctx context.Context, existingNodes []*nodes.No ingressControllerStatus = nodes.IngressControllerStatePartialConfigFail case clients.ConfigStatusApplyFailed: ingressControllerStatus = nodes.IngressControllerStateInoperable + case clients.ConfigStatusOKKonnectApplyFailed: + ingressControllerStatus = nodes.IngressControllerStateOperationalKonnectOutOfSync + case clients.ConfigStatusTranslationErrorHappenedKonnectApplyFailed: + ingressControllerStatus = nodes.IngressControllerStatePartialConfigFailKonnectOutOfSync + case clients.ConfigStatusApplyFailedKonnectApplyFailed: + ingressControllerStatus = nodes.IngressControllerStateInoperableKonnectOutOfSync + case clients.ConfigStatusUnknown: default: ingressControllerStatus = nodes.IngressControllerStateUnknown } diff --git a/internal/konnect/node_agent_test.go b/internal/konnect/node_agent_test.go index 087fd92e7a..6dfdb95b79 100644 --- a/internal/konnect/node_agent_test.go +++ b/internal/konnect/node_agent_test.go @@ -2,6 +2,7 @@ package konnect_test import ( "context" + "fmt" "testing" "time" @@ -381,3 +382,82 @@ func TestNodeAgent_StartDoesntReturnUntilContextGetsCancelled(t *testing.T) { case <-agentReturned: } } + +func TestNodeAgent_ControllerNodeStatusGetsUpdated(t *testing.T) { + nodeClient := newMockNodeClient(nil) + configStatusQueue := newMockConfigStatusNotifier() + gatewayClientsChangesNotifier := newMockGatewayClientsNotifier() + + nodeAgent := konnect.NewNodeAgent( + testHostname, + testKicVersion, + konnect.DefaultRefreshNodePeriod, + logr.Discard(), + nodeClient, + configStatusQueue, + newMockGatewayInstanceGetter(nil), + gatewayClientsChangesNotifier, + newMockManagerInstanceIDProvider(uuid.New()), + ) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + agentReturned := make(chan struct{}) + go func() { + require.NoError(t, nodeAgent.Start(ctx)) + close(agentReturned) + }() + + testCases := []struct { + notifiedConfigStatus clients.ConfigStatus + expectedControllerState nodes.IngressControllerState + }{ + { + notifiedConfigStatus: clients.ConfigStatusOK, + expectedControllerState: nodes.IngressControllerStateOperational, + }, + { + notifiedConfigStatus: clients.ConfigStatusTranslationErrorHappened, + expectedControllerState: nodes.IngressControllerStatePartialConfigFail, + }, + { + notifiedConfigStatus: clients.ConfigStatusApplyFailed, + expectedControllerState: nodes.IngressControllerStateInoperable, + }, + { + notifiedConfigStatus: clients.ConfigStatusOKKonnectApplyFailed, + expectedControllerState: nodes.IngressControllerStateOperationalKonnectOutOfSync, + }, + { + notifiedConfigStatus: clients.ConfigStatusTranslationErrorHappenedKonnectApplyFailed, + expectedControllerState: nodes.IngressControllerStatePartialConfigFailKonnectOutOfSync, + }, + { + notifiedConfigStatus: clients.ConfigStatusApplyFailedKonnectApplyFailed, + expectedControllerState: nodes.IngressControllerStateInoperableKonnectOutOfSync, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprint(tc.notifiedConfigStatus), func(t *testing.T) { + configStatusQueue.NotifyConfigStatus(ctx, tc.notifiedConfigStatus) + + require.Eventually(t, func() bool { + controllerNode, ok := lo.Find(nodeClient.MustAllNodes(), func(n *nodes.NodeItem) bool { + return n.Type == nodes.NodeTypeIngressController + }) + if !ok { + t.Log("controller node not found") + return false + } + + if controllerNode.Status != string(tc.expectedControllerState) { + t.Logf("expected controller node status to be %q, got %q", tc.expectedControllerState, controllerNode.Status) + return false + } + + return true + }, time.Second, time.Millisecond*10) + }) + } +} diff --git a/internal/konnect/nodes/types.go b/internal/konnect/nodes/types.go index e89ddae623..15a6f229a3 100644 --- a/internal/konnect/nodes/types.go +++ b/internal/konnect/nodes/types.go @@ -50,11 +50,14 @@ type CompatibilityStatus struct { type IngressControllerState string const ( - IngressControllerStateUnspecified IngressControllerState = "INGRESS_CONTROLLER_STATE_UNSPECIFIED" - IngressControllerStateOperational IngressControllerState = "INGRESS_CONTROLLER_STATE_OPERATIONAL" - IngressControllerStatePartialConfigFail IngressControllerState = "INGRESS_CONTROLLER_STATE_PARTIAL_CONFIG_FAIL" - IngressControllerStateInoperable IngressControllerState = "INGRESS_CONTROLLER_STATE_INOPERABLE" - IngressControllerStateUnknown IngressControllerState = "INGRESS_CONTROLLER_STATE_UNKNOWN" + IngressControllerStateUnspecified IngressControllerState = "INGRESS_CONTROLLER_STATE_UNSPECIFIED" + IngressControllerStateOperational IngressControllerState = "INGRESS_CONTROLLER_STATE_OPERATIONAL" + IngressControllerStatePartialConfigFail IngressControllerState = "INGRESS_CONTROLLER_STATE_PARTIAL_CONFIG_FAIL" + IngressControllerStateInoperable IngressControllerState = "INGRESS_CONTROLLER_STATE_INOPERABLE" + IngressControllerStateOperationalKonnectOutOfSync IngressControllerState = "INGRESS_CONTROLLER_STATE_OPERATIONAL_KONNECT_OUT_OF_SYNC" + IngressControllerStatePartialConfigFailKonnectOutOfSync IngressControllerState = "INGRESS_CONTROLLER_STATE_PARTIAL_CONFIG_FAIL_KONNECT_OUT_OF_SYNC" + IngressControllerStateInoperableKonnectOutOfSync IngressControllerState = "INGRESS_CONTROLLER_STATE_INOPERABLE_KONNECT_OUT_OF_SYNC" + IngressControllerStateUnknown IngressControllerState = "INGRESS_CONTROLLER_STATE_UNKNOWN" ) type CreateNodeRequest struct {