Skip to content
Open
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
16 changes: 16 additions & 0 deletions fleetshard/pkg/central/reconciler/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ type CentralReconciler struct {
environment string
auditLogging config.AuditLogging
encryptionKeyGenerator cipher.KeyGenerator
uiReachabilityChecker CentralUIReachabilityChecker

managedDbReconciler *managedDbReconciler
managedDBEnabled bool
Expand Down Expand Up @@ -245,6 +246,20 @@ func (r *CentralReconciler) Reconcile(ctx context.Context, remoteCentral private
return installingStatus(), nil
}

if r.useRoutes && !isRemoteCentralReady(&remoteCentral) {
// Check whether central UI host is reachable over HTTP.
centralUIReachable, err := r.uiReachabilityChecker.IsCentralUIHostReachable(ctx, remoteCentral.Spec.UiHost)
if err != nil {
return nil, err
}
if !centralUIReachable {
if isRemoteCentralProvisioning(remoteCentral) && !needsReconcile { // no changes detected, wait until central UI becomes reachable
return nil, ErrCentralNotChanged
}
return installingStatus(), nil
}
}

status, err := r.collectReconciliationStatus(ctx, &remoteCentral)
if err != nil {
return nil, err
Expand Down Expand Up @@ -1064,6 +1079,7 @@ func NewCentralReconciler(k8sClient ctrlClient.Client, fleetmanagerClient *fleet
environment: opts.Environment,
auditLogging: opts.AuditLogging,
encryptionKeyGenerator: encryptionKeyGenerator,
uiReachabilityChecker: NewHTTPCentralUIReachabilityChecker(),

managedDbReconciler: dbReconciler,
managedDBEnabled: opts.ManagedDBEnabled,
Expand Down
2 changes: 2 additions & 0 deletions fleetshard/pkg/central/reconciler/reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ func getClientTrackerAndReconciler(
cipher.AES256KeyGenerator{},
reconcilerOptions,
)
// Override with mock for testing - always return reachable
reconciler.uiReachabilityChecker = NewMockCentralUIReachabilityChecker(true, nil)
return fakeClient, tracker, reconciler
}

Expand Down
62 changes: 62 additions & 0 deletions fleetshard/pkg/central/reconciler/ui_reachability_checker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package reconciler

import (
"context"
"fmt"
"net/http"
"time"

"github.com/pkg/errors"
)

const (
httpCheckTimeout = 10 * time.Second
)

// CentralUIReachabilityChecker checks if a Central UI is reachable
type CentralUIReachabilityChecker interface {
IsCentralUIHostReachable(ctx context.Context, uiHost string) (bool, error)
}

// HTTPCentralUIReachabilityChecker is the default implementation that performs actual HTTP checks
type HTTPCentralUIReachabilityChecker struct {
httpClient *http.Client
}

// NewHTTPCentralUIReachabilityChecker creates a new HTTP-based reachability checker
func NewHTTPCentralUIReachabilityChecker() *HTTPCentralUIReachabilityChecker {
return &HTTPCentralUIReachabilityChecker{
httpClient: &http.Client{
Timeout: httpCheckTimeout,
},
}
}

// IsCentralUIHostReachable performs an HTTP check to verify if the Central UI host is reachable
func (c *HTTPCentralUIReachabilityChecker) IsCentralUIHostReachable(ctx context.Context, uiHost string) (bool, error) {
if uiHost == "" {
return false, errors.New("UI host is empty")
}

// Construct the URL with https scheme
url := fmt.Sprintf("https://%s", uiHost)

// Create request with context
req, err := http.NewRequestWithContext(ctx, "HEAD", url, nil)
if err != nil {
return false, errors.Wrapf(err, "creating HTTP request for %s", url)
}

// Perform the request
resp, err := c.httpClient.Do(req)
if err != nil {
return false, errors.Wrapf(err, "HTTP request failed for %s", url)
}
defer func() {
_ = resp.Body.Close()
}()

// Accept any response status code in the 2xx or 3xx range as reachable
// This allows for redirects and successful responses
return resp.StatusCode >= 200 && resp.StatusCode < 400, nil
}
34 changes: 34 additions & 0 deletions fleetshard/pkg/central/reconciler/ui_reachability_checker_mock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package reconciler

import (
"context"
)

// MockCentralUIReachabilityChecker is a mock implementation for testing
type MockCentralUIReachabilityChecker struct {
reachable bool
err error
}

// NewMockCentralUIReachabilityChecker creates a new mock checker
func NewMockCentralUIReachabilityChecker(reachable bool, err error) *MockCentralUIReachabilityChecker {
return &MockCentralUIReachabilityChecker{
reachable: reachable,
err: err,
}
}

// IsCentralUIHostReachable returns the mocked reachability status
func (m *MockCentralUIReachabilityChecker) IsCentralUIHostReachable(_ context.Context, _ string) (bool, error) {
return m.reachable, m.err
}

// SetReachable sets the reachability status for the mock
func (m *MockCentralUIReachabilityChecker) SetReachable(reachable bool) {
m.reachable = reachable
}

// SetError sets the error for the mock
func (m *MockCentralUIReachabilityChecker) SetError(err error) {
m.err = err
}
Loading