Skip to content
This repository has been archived by the owner on Jun 26, 2024. It is now read-only.

Commit

Permalink
Merge pull request #8 from Unbabel/brunotm.replicant-executor
Browse files Browse the repository at this point in the history
Split replicant in server and test execution services, tag v0.2.0
  • Loading branch information
brunotm authored Feb 27, 2020
2 parents 11bb4ce + e599765 commit cdf9ade
Show file tree
Hide file tree
Showing 95 changed files with 10,620 additions and 864 deletions.
2 changes: 1 addition & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ build_and_push_provisioner:
- git pull origin master --tags --force
- export GIT_TAG=`git tag --sort=-version:refname | egrep -m 1 "v[0-9]+\.[0-9]+\.[0-9]+"`
script:
- docker build -f docker/replicant/Dockerfile . -t $REPLICANT_REGISTRY_URL:latest -t $REPLICANT_REGISTRY_URL:$CI_COMMIT_SHA -t $REPLICANT_REGISTRY_URL:$GIT_TAG
- docker build -f Dockerfile . -t $REPLICANT_REGISTRY_URL:latest -t $REPLICANT_REGISTRY_URL:$CI_COMMIT_SHA -t $REPLICANT_REGISTRY_URL:$GIT_TAG
- docker push $REPLICANT_REGISTRY_URL:latest
- docker push $REPLICANT_REGISTRY_URL:$CI_COMMIT_SHA
- docker push $REPLICANT_REGISTRY_URL:$GIT_TAG
Expand Down
16 changes: 16 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM golang:alpine AS builder
RUN apk --no-cache add git make
COPY . /src/replicant
WORKDIR /src/replicant

RUN make build

FROM chromedp/headless-shell:stable
RUN apt update \
&& apt install -y ca-certificates \
&& apt clean; apt clean \
&& rm -rf /var/lib/apt/lists/*

COPY --from=builder /src/replicant/replicant /app/
ENTRYPOINT []
CMD ["/app/replicant"]
5 changes: 2 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
GOLDFLAGS += -w -extldflags "-static"
GOLDFLAGS += -s -w -extldflags "-static"
GOLDFLAGS += -X main.Version=$(shell git describe)
GOLDFLAGS += -X main.GitCommit=$(shell git rev-parse HEAD)
GOLDFLAGS += -X main.BuildTime=$(shell date -u '+%Y-%m-%dT%H:%M:%SZ')
GOFLAGS = -mod=vendor -ldflags "$(GOLDFLAGS)"

build:
CGO_ENABLED=0 go build $(GOFLAGS) -o replicant cmd/replicant/*.go
CGO_ENABLED=0 go build $(GOFLAGS) -o replicant-cdp cmd/replicant-cdp/*.go
CGO_ENABLED=0 go build $(GOFLAGS) -o replicant main.go

test:
CGO_ENABLED=0 go vet -mod=vendor ./...
Expand Down
1 change: 1 addition & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func AddAllRoutes(prefix string, server *server.Server) {
server.AddServerHandler(http.MethodPost, prefix+`/v1/run/:name`, RunTransactionByName)
server.AddServerHandler(http.MethodGet, prefix+`/v1/result`, GetResults)
server.AddServerHandler(http.MethodGet, prefix+`/v1/result/:name`, GetResult)
server.AddServerHandler(http.MethodPost, prefix+`/v1/callback/:uuid`, CallbackRequest)
}

// httpError wraps http status codes and error messages as json responses
Expand Down
78 changes: 78 additions & 0 deletions api/callback.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package api

/*
Copyright 2019 Bruno Moura <[email protected]>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"

"github.com/Unbabel/replicant/server"
"github.com/Unbabel/replicant/transaction"
"github.com/Unbabel/replicant/transaction/callback"
)

// CallbackRequest services callback requests for transactions being run from executors
func CallbackRequest(srv *server.Server) (handle server.Handler) {
return func(w http.ResponseWriter, r *http.Request, p server.Params) {
defer r.Body.Close()
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")

var buf []byte
var err error

if buf, err = ioutil.ReadAll(r.Body); err != nil {
httpError(w, fmt.Errorf("error reading request body: %w", err), http.StatusBadRequest)
return
}

config := transaction.Config{}
if err = json.Unmarshal(buf, &config); err != nil {
httpError(w, fmt.Errorf("error deserializing json request body: %w", err), http.StatusBadRequest)
return
}

if config.CallBack == nil {
httpError(w, fmt.Errorf("no callback config found"), http.StatusBadRequest)
return
}

listener, err := callback.GetListener(config.CallBack.Type)
if err != nil {
httpError(w, err, http.StatusBadRequest)
return
}

handler, err := listener.Listen(context.WithValue(r.Context(), "transaction_uuid", p.ByName("uuid")))
if err != nil {
httpError(w, err, http.StatusInternalServerError)
return
}

resp := <-handler.Response
if resp.Error != nil {
httpError(w, resp.Error, http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusOK)
w.Write(resp.Data)
}
}
31 changes: 2 additions & 29 deletions api/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,13 @@ package api
*/

import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"

"github.com/Unbabel/replicant/server"
"github.com/Unbabel/replicant/transaction"
"github.com/Unbabel/replicant/transaction/callback"
"github.com/segmentio/ksuid"
"gopkg.in/yaml.v2"
)

Expand Down Expand Up @@ -63,29 +60,7 @@ func RunTransaction(srv *server.Server) (handle server.Handler) {
return
}

tx, err := srv.Manager().New(config)
if err != nil {
httpError(w, err, http.StatusInternalServerError)
return
}

u := ksuid.New()
uuid := u.String()

ctx := context.WithValue(context.Background(), "transaction_uuid", uuid)

if config.CallBack != nil {
listener, err := callback.GetListener(config.CallBack.Type)
if err != nil {
httpError(w, err, http.StatusBadRequest)
return
}

ctx = context.WithValue(ctx, config.CallBack.Type, listener)
}

res := tx.Run(ctx)
res.UUID = uuid
res := srv.Manager().Run(config)
result.Data = []transaction.Result{res}

buf, err = json.Marshal(&result)
Expand All @@ -110,9 +85,7 @@ func RunTransactionByName(srv *server.Server) (handle server.Handler) {
var err error
var buf []byte

name := p.ByName("name")

res, err := srv.Manager().Run(context.Background(), name)
res, err := srv.Manager().RunByName(p.ByName("name"))
if err != nil {
httpError(w, err, http.StatusNotFound)
return
Expand Down
10 changes: 4 additions & 6 deletions api/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func AddTransaction(srv *server.Server) (handle server.Handler) {
return
}

err = srv.Manager().AddFromConfig(config)
err = srv.Manager().Add(config)
if err != nil {
httpError(w, err, http.StatusBadRequest)
return
Expand Down Expand Up @@ -89,7 +89,7 @@ func GetTransaction(srv *server.Server) (handle server.Handler) {
var result Result

name := p.ByName("name")
config, err := srv.Manager().GetTransactionConfig(name)
config, err := srv.Manager().Get(name)

if err != nil {
httpError(w, err, http.StatusNotFound)
Expand All @@ -115,7 +115,7 @@ func GetTransactions(srv *server.Server) (handle server.Handler) {
w.Header().Set("X-Content-Type-Options", "nosniff")

var result Result
result.Data = srv.Manager().GetTransactionsConfig()
result.Data = srv.Manager().GetAll()
buf, err := json.Marshal(&result)
if err != nil {
httpError(w, fmt.Errorf("error serializing results: %w", err), http.StatusInternalServerError)
Expand All @@ -134,9 +134,7 @@ func RemoveTransaction(srv *server.Server) (handle server.Handler) {
w.Header().Set("X-Content-Type-Options", "nosniff")

var result Result

name := p.ByName("name")
err := srv.Manager().RemoveTransaction(name)
err := srv.Manager().Delete(p.ByName("name"))

if err != nil {
httpError(w, err, http.StatusNotFound)
Expand Down
148 changes: 148 additions & 0 deletions cmd/executor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package cmd

import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/signal"
"strings"
"time"

"github.com/Unbabel/replicant/internal/cmdutil"
"github.com/Unbabel/replicant/internal/executor"
"github.com/Unbabel/replicant/log"
"github.com/Unbabel/replicant/transaction"
"github.com/julienschmidt/httprouter"
"github.com/spf13/cobra"
)

func init() {
Executor.Flags().String("listen-address", "0.0.0.0:8081", "Address to for executor to listen on")
Executor.Flags().String("server-url", "http://127.0.0.1:8080", "Replicant server url")
Executor.Flags().Duration("max-runtime", time.Minute*5, "Maximum individual test runtime")
Executor.Flags().Bool("debug", false, "Expose a debug profile endpoint at /debug/pprof")
Executor.Flags().String("webhook-advertise-url", "http://localhost:8080", "URL to advertise when receiving webhook based async responses")
Executor.Flags().String("chrome-remote-url", "http://127.0.0.1:9222", "Chrome remote debugging protocol server. For using remote chrome process instead of a local managed process")
Executor.Flags().Bool("chrome-enable-local", true, "Enable running a local chrome worker process for web transactions")
Executor.Flags().String("chrome-local-command", "/headless-shell/headless-shell --headless --no-zygote --no-sandbox --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage --remote-debugging-address=127.0.0.1 --remote-debugging-port=9222 --incognito --disable-shared-workers --disable-remote-fonts --disable-background-networking --disable-crash-reporter --disable-default-apps --disable-domain-reliability --disable-extensions --disable-shared-workers --disable-setuid-sandbox", "Command for launching chrome with arguments included")
Executor.Flags().Duration("chrome-recycle-interval", time.Minute*5, "Chrome recycle interval for locally managed chrome process")
}

// Executor command
var Executor = &cobra.Command{
Use: "executor",
Short: "Start the replicant transaction execution service",
Run: func(cmd *cobra.Command, args []string) {

config := executor.Config{}
config.ServerURL = cmdutil.GetFlagString(cmd, "server-url")
config.AdvertiseURL = cmdutil.GetFlagString(cmd, "webhook-advertise-url")

// Setup chrome support for web applications
config.Web.ServerURL = cmdutil.GetFlagString(cmd, "chrome-remote-url")

if cmdutil.GetFlagBool(cmd, "chrome-enable-local") {
arguments := strings.Split(cmdutil.GetFlagString(cmd, "chrome-local-command"), " ")
config.Web.BinaryPath = arguments[:1][0]
config.Web.BinaryArgs = arguments[1:]
config.Web.RecycleInterval = cmdutil.GetFlagDuration(cmd, "chrome-recycle-interval")
}

server := &http.Server{}
server.Addr = cmdutil.GetFlagString(cmd, "listen-address")
server.ReadTimeout = cmdutil.GetFlagDuration(cmd, "max-runtime")
server.WriteTimeout = cmdutil.GetFlagDuration(cmd, "max-runtime")
server.ReadHeaderTimeout = cmdutil.GetFlagDuration(cmd, "max-runtime")
router := httprouter.New()
server.Handler = router

e, err := executor.New(config)
if err != nil {
log.Error("error creating replicant-executor").Error("error", err).Log()
os.Exit(1)
}

router.Handle(http.MethodPost, "/api/v1/run/:uuid", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
defer r.Body.Close()

uuid := p.ByName("uuid")
var err error
var buf []byte
var config transaction.Config

if buf, err = ioutil.ReadAll(r.Body); err != nil {
httpError(w, uuid, config, fmt.Errorf("error reading request body: %w", err), http.StatusBadRequest)
return
}

if err = json.Unmarshal(buf, &config); err != nil {
httpError(w, uuid, config, fmt.Errorf("error deserializing json request body: %w", err), http.StatusBadRequest)
return
}

if err = json.Unmarshal(buf, &config); err != nil {
httpError(w, uuid, config, err, http.StatusBadRequest)
return
}

result, err := e.Run(uuid, config)
if err != nil {
httpError(w, uuid, config, err, http.StatusBadRequest)
return
}

buf, err = json.Marshal(&result)
if err != nil {
httpError(w, uuid, config, fmt.Errorf("error serializing results: %w", err), http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusOK)
w.Write(buf)
})

signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, os.Interrupt)

// listen for stop signals
go func() {
<-signalCh
if err := server.Shutdown(context.Background()); err != nil {
log.Error("error stopping replicant-executor").Error("error", err).Log()
os.Exit(1)
}
}()

log.Info("starting replicant-executor").Log()
if err := server.ListenAndServe(); err != nil {
log.Error("error running replicant-cdp").Error("error", err).Log()
os.Exit(1)
}

log.Info("replicant-cdp stopped").Log()

},
}

// httpError wraps http status codes and error messages as json responses
func httpError(w http.ResponseWriter, uuid string, config transaction.Config, err error, code int) {
var result transaction.Result

result.Name = config.Name
result.Driver = config.Driver
result.Metadata = config.Metadata
result.Time = time.Now()
result.DurationSeconds = 0
result.Failed = true
result.Error = err

res, _ := json.Marshal(&result)

w.WriteHeader(code)
w.Write(res)

log.Error("handling web transaction request").Error("error", err).Log()
}
Loading

0 comments on commit cdf9ade

Please sign in to comment.