Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-pages.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVladimir Shushlin <vshushlin@gitlab.com>2020-07-28 17:40:49 +0300
committerVladimir Shushlin <vshushlin@gitlab.com>2020-07-28 17:40:49 +0300
commitf01b397d09073b393dcd7918d7163ed5032e36c4 (patch)
tree0c3441a103255da9ba240e606f41929b210c1ffc
parent7e2600ea4ad6a8ef2d19c07eadb8c7aa7b8c65e2 (diff)
parent98b61cf4663926bec3ae822f60cc34cce6e11b75 (diff)
Merge branch 'poll-pages-enabled-endpoint' into 'master'
Poll internal status API See merge request gitlab-org/gitlab-pages!304
-rw-r--r--internal/source/gitlab/api/client.go5
-rw-r--r--internal/source/gitlab/api/resolver.go5
-rw-r--r--internal/source/gitlab/cache/cache.go5
-rw-r--r--internal/source/gitlab/cache/cache_test.go4
-rw-r--r--internal/source/gitlab/client/client.go21
-rw-r--r--internal/source/gitlab/client/client_stub.go7
-rw-r--r--internal/source/gitlab/client/client_test.go82
-rw-r--r--internal/source/gitlab/gitlab.go14
-rw-r--r--internal/source/gitlab/gitlab_poll.go38
-rw-r--r--internal/source/gitlab/gitlab_poll_test.go79
10 files changed, 254 insertions, 6 deletions
diff --git a/internal/source/gitlab/api/client.go b/internal/source/gitlab/api/client.go
index 7206e25a..181c580b 100644
--- a/internal/source/gitlab/api/client.go
+++ b/internal/source/gitlab/api/client.go
@@ -6,6 +6,9 @@ import (
// Client represents an interface we use to retrieve information from GitLab
type Client interface {
- // GetLookup retrives an VirtualDomain from GitLab API and wraps it into Lookup
+ // Resolve retrieves an VirtualDomain from the GitLab API and wraps it into a Lookup
GetLookup(ctx context.Context, domain string) Lookup
+
+ // Status checks the connectivity with the GitLab API
+ Status() error
}
diff --git a/internal/source/gitlab/api/resolver.go b/internal/source/gitlab/api/resolver.go
index 061a1ddd..738278e2 100644
--- a/internal/source/gitlab/api/resolver.go
+++ b/internal/source/gitlab/api/resolver.go
@@ -7,6 +7,9 @@ import (
// Resolver represents an interface we use to retrieve information from GitLab
// in a more generic way. It can be a concrete API client or cached client.
type Resolver interface {
- // Resolve retrives an VirtualDomain from GitLab API and wraps it into Lookup
+ // Resolve retrieves an VirtualDomain from the GitLab API and wraps it into a Lookup
Resolve(ctx context.Context, domain string) *Lookup
+
+ // Status checks the connectivity with the GitLab API
+ Status() error
}
diff --git a/internal/source/gitlab/cache/cache.go b/internal/source/gitlab/cache/cache.go
index c8d166b5..37cef111 100644
--- a/internal/source/gitlab/cache/cache.go
+++ b/internal/source/gitlab/cache/cache.go
@@ -109,3 +109,8 @@ func (c *Cache) Resolve(ctx context.Context, domain string) *api.Lookup {
metrics.DomainsSourceCacheMiss.Inc()
return entry.Retrieve(ctx, c.client)
}
+
+// Status calls the client Status to check connectivity with the API
+func (c *Cache) Status() error {
+ return c.client.Status()
+}
diff --git a/internal/source/gitlab/cache/cache_test.go b/internal/source/gitlab/cache/cache_test.go
index 7a12cd3b..52a3a489 100644
--- a/internal/source/gitlab/cache/cache_test.go
+++ b/internal/source/gitlab/cache/cache_test.go
@@ -69,6 +69,10 @@ func (c *client) GetLookup(ctx context.Context, _ string) api.Lookup {
return lookup
}
+func (c *client) Status() error {
+ return nil
+}
+
func withTestCache(config resolverConfig, cacheConfig *cacheConfig, block func(*Cache, *client)) {
var chanSize int
diff --git a/internal/source/gitlab/client/client.go b/internal/source/gitlab/client/client.go
index 3a805ce9..e06a87d1 100644
--- a/internal/source/gitlab/client/client.go
+++ b/internal/source/gitlab/client/client.go
@@ -18,6 +18,11 @@ import (
"gitlab.com/gitlab-org/gitlab-pages/metrics"
)
+// ConnectionErrorMsg to be returned with `gc.Status` if Pages
+// fails to connect to the internal GitLab API, times out
+// or a 401 given that the credentials used are wrong
+const ConnectionErrorMsg = "failed to connect to internal Pages API"
+
// Client is a HTTP client to access Pages internal API
type Client struct {
secretKey []byte
@@ -100,6 +105,22 @@ func (gc *Client) GetLookup(ctx context.Context, host string) api.Lookup {
return lookup
}
+// Status checks that Pages can reach the rails internal Pages API
+// for source domain configuration.
+// Timeout is the same as -gitlab-client-http-timeout
+func (gc *Client) Status() error {
+ res, err := gc.get(context.Background(), "/api/v4/internal/pages/status", url.Values{})
+ if err != nil {
+ return fmt.Errorf("%s: %v", ConnectionErrorMsg, err)
+ }
+
+ if res != nil && res.Body != nil {
+ res.Body.Close()
+ }
+
+ return nil
+}
+
func (gc *Client) get(ctx context.Context, path string, params url.Values) (*http.Response, error) {
endpoint, err := gc.endpoint(path, params)
if err != nil {
diff --git a/internal/source/gitlab/client/client_stub.go b/internal/source/gitlab/client/client_stub.go
index 604f127b..de6161e6 100644
--- a/internal/source/gitlab/client/client_stub.go
+++ b/internal/source/gitlab/client/client_stub.go
@@ -10,7 +10,8 @@ import (
// StubClient is a stubbed client used for testing
type StubClient struct {
- File string
+ File string
+ StatusErr func() error
}
// Resolve implements api.Resolver
@@ -35,3 +36,7 @@ func (c StubClient) GetLookup(ctx context.Context, host string) api.Lookup {
return lookup
}
+
+func (c StubClient) Status() error {
+ return c.StatusErr()
+}
diff --git a/internal/source/gitlab/client/client_test.go b/internal/source/gitlab/client/client_test.go
index 02684860..ab90b474 100644
--- a/internal/source/gitlab/client/client_test.go
+++ b/internal/source/gitlab/client/client_test.go
@@ -10,7 +10,7 @@ import (
"testing"
"time"
- jwt "github.com/dgrijalva/jwt-go"
+ "github.com/dgrijalva/jwt-go"
"github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitlab-pages/internal/fixture"
@@ -231,6 +231,86 @@ func TestGetVirtualDomainAuthenticatedRequest(t *testing.T) {
require.Equal(t, "mygroup/myproject/public/", lookupPath.Source.Path)
}
+func TestClientStatus(t *testing.T) {
+ tests := []struct {
+ name string
+ status int
+ wantErr bool
+ }{
+ {
+ name: "api_enabled",
+ status: http.StatusNoContent,
+ },
+ {
+ name: "api_unauthorized",
+ status: http.StatusUnauthorized,
+ wantErr: true,
+ },
+ {
+ name: "server_error",
+ status: http.StatusInternalServerError,
+ wantErr: true,
+ },
+ {
+ name: "gateway_timeout",
+ status: http.StatusGatewayTimeout,
+ wantErr: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ mux := http.NewServeMux()
+ mux.HandleFunc("/api/v4/internal/pages/status", func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(tt.status)
+ })
+
+ server := httptest.NewServer(mux)
+ defer server.Close()
+
+ client := defaultClient(t, server.URL)
+
+ err := client.Status()
+ if tt.wantErr {
+ require.Error(t, err)
+ require.Contains(t, err.Error(), ConnectionErrorMsg)
+ return
+ }
+
+ require.NoError(t, err)
+ })
+ }
+}
+
+func TestClientStatusClientTimeout(t *testing.T) {
+ timeout := 3 * time.Millisecond
+
+ mux := http.NewServeMux()
+ mux.HandleFunc("/api/v4/internal/pages/status", func(w http.ResponseWriter, r *http.Request) {
+ time.Sleep(timeout * 3)
+
+ w.WriteHeader(http.StatusOK)
+ })
+
+ server := httptest.NewServer(mux)
+ defer server.Close()
+
+ client := defaultClient(t, server.URL)
+ client.httpClient.Timeout = timeout
+
+ err := client.Status()
+ require.Error(t, err)
+ require.Contains(t, err.Error(), "Client.Timeout")
+}
+
+func TestClientStatusConnectionRefused(t *testing.T) {
+ client := defaultClient(t, "http://localhost:1234")
+
+ err := client.Status()
+ require.Error(t, err)
+ require.Contains(t, err.Error(), "connection refused")
+}
+
func validateToken(t *testing.T, tokenString string) {
t.Helper()
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
diff --git a/internal/source/gitlab/gitlab.go b/internal/source/gitlab/gitlab.go
index 12da9af1..5bacb603 100644
--- a/internal/source/gitlab/gitlab.go
+++ b/internal/source/gitlab/gitlab.go
@@ -6,6 +6,7 @@ import (
"net/http"
"path"
"strings"
+ "sync"
"gitlab.com/gitlab-org/gitlab-pages/internal/domain"
"gitlab.com/gitlab-org/gitlab-pages/internal/request"
@@ -18,7 +19,9 @@ import (
// Gitlab source represent a new domains configuration source. We fetch all the
// information about domains from GitLab instance.
type Gitlab struct {
- client api.Resolver
+ client api.Resolver
+ mu *sync.RWMutex
+ isReady bool
}
// New returns a new instance of gitlab domain source.
@@ -28,8 +31,15 @@ func New(config client.Config) (*Gitlab, error) {
return nil, err
}
+ g := &Gitlab{
+ client: cache.NewCache(client, nil),
+ mu: &sync.RWMutex{},
+ }
+
+ go g.poll(defaultPollingMaxRetries, defaultPollingInterval)
+
// using nil for cache config will use the default values specified in internal/source/gitlab/cache/cache.go#12
- return &Gitlab{client: cache.NewCache(client, nil)}, nil
+ return g, nil
}
// GetDomain return a representation of a domain that we have fetched from
diff --git a/internal/source/gitlab/gitlab_poll.go b/internal/source/gitlab/gitlab_poll.go
new file mode 100644
index 00000000..70644822
--- /dev/null
+++ b/internal/source/gitlab/gitlab_poll.go
@@ -0,0 +1,38 @@
+package gitlab
+
+import (
+ "time"
+
+ log "github.com/sirupsen/logrus"
+)
+
+const (
+ // defaultPollingMaxRetries to be used by poll
+ defaultPollingMaxRetries = 30
+ // defaultPollingInterval to be used by poll
+ defaultPollingInterval = time.Minute
+)
+
+// poll tries to call the /internal/pages/status API endpoint once plus
+// `retries` every `interval`.
+// TODO: Remove in https://gitlab.com/gitlab-org/gitlab/-/issues/218357
+func (g *Gitlab) poll(retries int, interval time.Duration) {
+ var err error
+ for i := 0; i <= retries; i++ {
+ log.Info("Checking GitLab internal API availability")
+ err = g.client.Status()
+ if err == nil {
+ log.Info("GitLab internal pages status API connected successfully")
+ g.mu.Lock()
+ g.isReady = true
+ g.mu.Unlock()
+
+ // return as soon as we connect to the API
+ return
+ }
+
+ time.Sleep(interval)
+ }
+
+ log.WithError(err).Errorf("Failed to connect to the internal GitLab API after %d tries every %.2fs", retries+1, interval.Seconds())
+}
diff --git a/internal/source/gitlab/gitlab_poll_test.go b/internal/source/gitlab/gitlab_poll_test.go
new file mode 100644
index 00000000..01f13846
--- /dev/null
+++ b/internal/source/gitlab/gitlab_poll_test.go
@@ -0,0 +1,79 @@
+package gitlab
+
+import (
+ "fmt"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/sirupsen/logrus/hooks/test"
+ "github.com/stretchr/testify/require"
+
+ "gitlab.com/gitlab-org/gitlab-pages/internal/source/gitlab/client"
+)
+
+func TestClient_Poll(t *testing.T) {
+ hook := test.NewGlobal()
+ tests := []struct {
+ name string
+ retries int
+ interval time.Duration
+ expectedFail bool
+ }{
+ {
+ name: "success_with_no_retry",
+ retries: 0,
+ interval: 5 * time.Millisecond,
+ expectedFail: false,
+ },
+ {
+ name: "success_after_N_retries",
+ retries: 3,
+ interval: 10 * time.Millisecond,
+ expectedFail: false,
+ },
+ {
+ name: "fail_with_no_retries",
+ retries: 0,
+ interval: 5 * time.Millisecond,
+ expectedFail: true,
+ },
+ {
+ name: "fail_after_N_retries",
+ retries: 3,
+ interval: 5 * time.Millisecond,
+ expectedFail: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ defer hook.Reset()
+ var counter int
+ client := client.StubClient{StatusErr: func() error {
+ if tt.expectedFail {
+ return fmt.Errorf(client.ConnectionErrorMsg)
+ }
+
+ if counter < tt.retries {
+ counter++
+ return fmt.Errorf(client.ConnectionErrorMsg)
+ }
+
+ return nil
+ }}
+
+ glClient := Gitlab{client: client, mu: &sync.RWMutex{}}
+
+ glClient.poll(tt.retries, tt.interval)
+ if tt.expectedFail {
+ require.False(t, glClient.isReady)
+ s := fmt.Sprintf("Failed to connect to the internal GitLab API after %d tries every %.2fs", tt.retries+1, tt.interval.Seconds())
+ require.Equal(t, s, hook.LastEntry().Message)
+ return
+ }
+
+ require.True(t, glClient.isReady)
+ require.Equal(t, "GitLab internal pages status API connected successfully", hook.LastEntry().Message)
+ })
+ }
+}