Skip to content

Commit

Permalink
APPSRE-7875: Add request timeout handler
Browse files Browse the repository at this point in the history
Signed-off-by: Krzysztof Wilczyński <kwilczynski@redhat.com>
  • Loading branch information
Krzysztof Wilczyński committed Jul 9, 2023
1 parent a1eddba commit ac313fa
Show file tree
Hide file tree
Showing 17 changed files with 406 additions and 34 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/go-sql-driver/mysql v1.7.0
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
github.com/jackc/pgx/v4 v4.18.0
github.com/jackc/pgx/v4 v4.18.1
github.com/justinas/alice v1.2.0
github.com/orlangure/gnomock v0.24.0
github.com/stretchr/testify v1.8.1
Expand Down
5 changes: 2 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.18.0 h1:Ltaa1ePvc7msFGALnCrqKJVEByu/qYh5jJBYcDtAno4=
github.com/jackc/pgx/v4 v4.18.0/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE=
github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0=
github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
Expand Down Expand Up @@ -213,7 +213,6 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
Expand Down
10 changes: 9 additions & 1 deletion openshift/gabi.template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ objects:
- --provider=openshift
- --openshift-service-account=${GABI_INSTANCE}
- --upstream=http://localhost:8080
# Reserved for future use.
# - --upstream-timeout=${OAUTH_PROXY_UPSTREAM_TIMEOUT}
- '--openshift-delegate-urls={"/": {"resource": "namespaces", "verb": "get", "name": "${NAMESPACE}", "namespace": "${NAMESPACE}"}}'
- --tls-cert=/etc/tls/private/tls.crt
- --tls-key=/etc/tls/private/tls.key
Expand Down Expand Up @@ -154,6 +156,8 @@ objects:
name: ${AWS_RDS_SECRET_NAME}
- name: CONFIG_FILE_PATH
value: ${CONFIG_FILE_PATH}
- name: REQUEST_TIMEOUT
value: ${REQUEST_TIMEOUT}
resources:
requests:
cpu: ${CPU_REQUEST}
Expand Down Expand Up @@ -245,7 +249,9 @@ parameters:
- name: OAUTH_PROXY_IMAGE_NAME
value: quay.io/openshift/origin-oauth-proxy
- name: OAUTH_PROXY_IMAGE_TAG
value: "4.10.0"
value: "4.14.0"
- name: OAUTH_PROXY_UPSTREAM_TIMEOUT
value: "30s"
- name: DB_DRIVER
value: pgx
- name: DB_WRITE
Expand All @@ -264,6 +270,8 @@ parameters:
required: true
- name: CONFIG_FILE_PATH
value: /config/config.json
- name: REQUEST_TIMEOUT
value: "30s"
- name: GABI_INSTANCE
value: gabi-instance
- name: CERT_MANAGER_ISSUER_KIND
Expand Down
4 changes: 3 additions & 1 deletion pkg/audit/audit.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package audit

import "context"

type QueryData struct {
Query string
User string
Expand All @@ -9,5 +11,5 @@ type QueryData struct {
}

type Audit interface {
Write(*QueryData) error
Write(context.Context, *QueryData) error
}
4 changes: 3 additions & 1 deletion pkg/audit/console.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package audit

import (
"context"

"go.uber.org/zap"
)

Expand All @@ -14,7 +16,7 @@ func NewLoggerAudit(logger *zap.SugaredLogger) *ConsoleAudit {
return &ConsoleAudit{Logger: logger}
}

func (d *ConsoleAudit) Write(q *QueryData) error {
func (d *ConsoleAudit) Write(_ context.Context, q *QueryData) error {
d.Logger.Infow("AUDIT",
"Query", q.Query,
"User", q.User,
Expand Down
3 changes: 2 additions & 1 deletion pkg/audit/console_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package audit

import (
"bytes"
"context"
"io"
"regexp"
"testing"
Expand Down Expand Up @@ -57,7 +58,7 @@ func TestLoggingAuditWrite(t *testing.T) {
logger := test.DummyLogger(&output).Sugar()

audit := &ConsoleAudit{Logger: logger}
err := audit.Write(&tc.given)
err := audit.Write(context.TODO(), &tc.given)

require.NoError(t, err)
assert.Regexp(t, tc.want, output.String())
Expand Down
4 changes: 2 additions & 2 deletions pkg/audit/splunk.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (d *SplunkAudit) SetHTTPClient(client *http.Client) {
d.client = client
}

func (d *SplunkAudit) Write(q *QueryData) error {
func (d *SplunkAudit) Write(ctx context.Context, q *QueryData) error {
query := &SplunkQueryData{
Index: d.SplunkEnv.Index,
Host: d.SplunkEnv.Host,
Expand All @@ -103,7 +103,7 @@ func (d *SplunkAudit) Write(q *QueryData) error {

url := fmt.Sprintf("%s/services/collector/event", d.SplunkEnv.Endpoint)

ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
ctx, cancel := context.WithTimeout(ctx, requestTimeout)
defer cancel()

req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(content))
Expand Down
3 changes: 2 additions & 1 deletion pkg/audit/splunk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package audit

import (
"bytes"
"context"
"fmt"
"io"
"net/http"
Expand Down Expand Up @@ -451,7 +452,7 @@ func TestSplunkAduitWrite(t *testing.T) {

actual := &SplunkAudit{SplunkEnv: tc.server(s)}
actual.SetHTTPClient(http.DefaultClient)
err := actual.Write(&tc.given)
err := actual.Write(context.TODO(), &tc.given)

if tc.error {
require.Error(t, err)
Expand Down
28 changes: 9 additions & 19 deletions pkg/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"net"
"net/http"
"strconv"
"time"

gorillahandlers "github.com/gorilla/handlers"
"github.com/gorilla/mux"
Expand All @@ -26,12 +25,6 @@ import (
"github.com/app-sre/gabi/pkg/version"
)

const (
readTimeout = 1 * time.Minute
readHeaderTimeout = 20 * time.Second
writeTimeout = 2 * time.Minute
)

func Run(logger *zap.SugaredLogger) error {
logger.Infof("Starting GABI version: %s", version.Version())

Expand Down Expand Up @@ -60,28 +53,25 @@ func Run(logger *zap.SugaredLogger) error {
defer db.Close()
logger.Debugf("Connected to database host: %s (port: %d)", dbe.Host, dbe.Port)

la := audit.NewLoggerAudit(logger)

se := splunk.NewSplunkEnv()
err = se.Populate()
if err != nil {
return fmt.Errorf("unable to configure Splunk: %w", err)
}
logger.Infof("Sending audit to Splunk endpoint: %s", se.Endpoint)

sa := audit.NewSplunkAudit(se)

cfg := &gabi.Config{
DB: db,
DBEnv: dbe,
UserEnv: usere,
LoggerAudit: la,
SplunkAudit: sa,
LoggerAudit: audit.NewLoggerAudit(logger),
SplunkAudit: audit.NewSplunkAudit(se),
Logger: logger,
Encoder: base64.StdEncoding,
}
timeout := gabi.RequestTimeout()

// Temp workaround for easy to access io.Writer.
// Temporary workaround for easy to access io.Writer.
defaultLogOutput := log.Default().Writer()

healthLogOutput := io.Discard
Expand All @@ -95,6 +85,7 @@ func Run(logger *zap.SugaredLogger) error {
alice.Constructor(middleware.Authorization(cfg)),
alice.Constructor(middleware.Expiration(cfg)),
alice.Constructor(middleware.Audit(cfg)),
alice.Constructor(middleware.Timeout(timeout)),
)
queryHandler := queryChain.Then(handlers.Query(cfg))

Expand All @@ -106,11 +97,10 @@ func Run(logger *zap.SugaredLogger) error {
logger.Infof("HTTP server starting on port: %d", port)

server := &http.Server{
Addr: net.JoinHostPort("", strconv.Itoa(port)),
Handler: r,
ReadTimeout: readTimeout,
ReadHeaderTimeout: readHeaderTimeout,
WriteTimeout: writeTimeout,
Addr: net.JoinHostPort("", strconv.Itoa(port)),
Handler: r,
ReadTimeout: gabi.DefaultReadTimeout,
WriteTimeout: gabi.DefaultWriteTimeout,
}
if err := server.ListenAndServe(); err != nil {
return fmt.Errorf("unable to start HTTP server: %w", err)
Expand Down
42 changes: 42 additions & 0 deletions pkg/gabi.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,28 @@ package gabi
import (
"database/sql"
"encoding/base64"
"fmt"
"os"
"strconv"
"time"

"github.com/app-sre/gabi/pkg/audit"
"github.com/app-sre/gabi/pkg/env/db"
"github.com/app-sre/gabi/pkg/env/user"
"go.uber.org/zap"
)

const (
// The total time it takes to read the request from the client.
DefaultReadTimeout = 1 * time.Minute

// The total time it takes to send a response back to the client.
DefaultWriteTimeout = 5 * time.Minute

// The total time it takes to execute the request.
DefaultRequestTimeout = 2 * time.Minute
)

type Config struct {
DB *sql.DB
DBEnv *db.Env
Expand All @@ -24,3 +38,31 @@ type Config struct {
func Production() bool {
return os.Getenv("ENVIRONMENT") == "production"
}

func RequestTimeout() time.Duration {
t, err := parseDuration(os.Getenv("REQUEST_TIMEOUT"))
if err != nil || t == 0 {
return DefaultRequestTimeout
}
return t
}

func parseDuration(duration string) (time.Duration, error) {
var t time.Duration

n, err := strconv.ParseInt(duration, 10, 64)
if err == nil {
t = time.Duration(n) * time.Second
} else {
t, err = time.ParseDuration(duration)
}
if err != nil {
return 0, fmt.Errorf("unable to parse duration: %w", err)
}

if t < 0 {
t = -t
}

return t, nil
}
Loading

0 comments on commit ac313fa

Please sign in to comment.