Skip to content

Commit 173ed86

Browse files
author
Mikhail Podtserkovskiy
committed
Really initial commit
1 parent f0a7ab4 commit 173ed86

File tree

329 files changed

+147749
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

329 files changed

+147749
-2
lines changed

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,14 @@
1212

1313
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
1414
.glide/
15+
16+
# Intellij
17+
.idea/
18+
*.iml
19+
*.iws
20+
21+
# Mac
22+
.DS_Store
23+
24+
# Your local config
25+
config.json

Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM golang:1.8.1
2+
3+
COPY ./ ${GOPATH}/src/jsonwire-grid
4+
5+
WORKDIR ${GOPATH}/src/jsonwire-grid
6+
ENV CONFIG_PATH ./config.json
7+
8+
RUN make
9+
10+
CMD ["service-entrypoint"]
11+
12+
EXPOSE 4444

Makefile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export CONFIG_PATH=./config.json
2+
all: build
3+
4+
.PHONY: help build fmt clean run test coverage check vet lint doc cfpush
5+
6+
help:
7+
@echo "build - build application from sources"
8+
@echo "fmt - format application sources"
9+
@echo "run - start application"
10+
@echo "gen - generate files for json rpc 2.0 service"
11+
12+
build: fmt
13+
go build -o ${GOPATH}/bin/service-entrypoint
14+
15+
fmt:
16+
go fmt
17+
18+
run: build
19+
${GOPATH}/bin/service-entrypoint

README.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,21 @@
1-
# jsonwire-grid
2-
Golang implementation of Selenium Grid
1+
# ~~jsonwire-grid~~WebDriverGrid
2+
Golang implementation of Selenium Grid (hub).
3+
4+
## HowTo
5+
#### Run binary file
6+
1. download binary file
7+
1. `export CONFIG_PATH=./config.json`
8+
1. `./webdriver-{platform_name}`
9+
10+
### Run From Source
11+
#### Requirements
12+
* Go >= 1.8.1
13+
1. `git clone https://github.com/qa-dev/webdriver-grid .`
14+
1. `cd webdriver-grid`
15+
1. `export CONFIG_PATH=./config.json`
16+
1. `go run main.go`
17+
18+
## HowToUse
19+
1. Run app
20+
1. `java -jar selenium-server-standalone-3.4.0.jar -role node -hub http://127.0.0.1:4444/grid/register`
21+
1. try create session `curl -X POST http://127.0.0.1:4444/wd/hub/session -d '{"desiredCapabilities":{"browserName": "firefox"}}'`

config-sample.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"logger": {
3+
"level": 6
4+
},
5+
"db": {
6+
"connection": "root:@(localhost:3306)/grid?tx_isolation=SERIALIZABLE&parseTime=true"
7+
},
8+
"grid": {
9+
"port": 4444,
10+
"strategy_list": [
11+
{
12+
"type": "default",
13+
"limit": 0
14+
}
15+
],
16+
"busy_node_duration": "15m",
17+
"reserved_node_duration": "5m"
18+
},
19+
"statsd": {
20+
"host": "statsd-host",
21+
"port": 8126,
22+
"protocol": "udp",
23+
"prefix": "products.tests.qa.debug-dev.jsonwire-grid.",
24+
"enable": false
25+
}
26+
}

config/config.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package config
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
log "github.com/Sirupsen/logrus"
7+
"os"
8+
"github.com/Sirupsen/logrus"
9+
)
10+
11+
type Config struct {
12+
Logger Logger `json:"logger"`
13+
DB DB `json:"db"`
14+
Grid Grid `json:"grid"`
15+
Statsd Statsd `json:"statsd"`
16+
}
17+
18+
type Grid struct {
19+
Port int `json:"port"`
20+
StrategyList []Strategy `json:"strategy_list"`
21+
BusyNodeDuration string `json:"busy_node_duration"` // duration string format ex. 12m, see time.ParseDuration()
22+
ReservedDuration string `json:"reserved_node_duration"` // duration string format ex. 12m, see time.ParseDuration()
23+
}
24+
25+
type Strategy struct {
26+
Type string `json:"type"`
27+
Limit int `json:"limit"`
28+
}
29+
30+
type Logger struct {
31+
Level logrus.Level `json:"level"`
32+
}
33+
34+
type DB struct {
35+
Connection string `json:"connection"`
36+
}
37+
38+
type Statsd struct {
39+
Host string `json:"host"`
40+
Port int `json:"port"`
41+
Protocol string `json:"protocol"`
42+
Prefix string `json:"prefix"`
43+
Enable bool `json:"enable"`
44+
}
45+
46+
func New() *Config {
47+
return &Config{}
48+
}
49+
50+
func (c *Config) LoadFromFile(path string) error {
51+
log.Printf(path)
52+
if path == "" {
53+
return errors.New("empty configuration file path")
54+
}
55+
56+
configFile, err := os.Open(path)
57+
if err != nil {
58+
return err
59+
}
60+
defer configFile.Close()
61+
62+
jsonParser := json.NewDecoder(configFile)
63+
64+
return jsonParser.Decode(&c)
65+
}

handlers/apiProxy.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package handlers
2+
3+
import (
4+
"net/http"
5+
"net/url"
6+
7+
log "github.com/Sirupsen/logrus"
8+
"jsonwire-grid/pool"
9+
)
10+
11+
type ApiProxy struct {
12+
Pool *pool.Pool
13+
}
14+
15+
func (h *ApiProxy) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
16+
rw.Header().Add("Content-type", "application/json")
17+
18+
id := r.URL.Query().Get("id")
19+
nodeUrl, err := url.Parse(id) //todo: обработка ошибок
20+
21+
if err != nil {
22+
errorMessage := "Error get 'id' from url: " + r.URL.String()
23+
log.Warning(errorMessage)
24+
rw.Write([]byte(`{"id": "", "request": {}, "msg": "` + errorMessage + `": false}`))
25+
return
26+
}
27+
28+
node, err := h.Pool.GetNodeByAddress(nodeUrl.Host)
29+
30+
//todo: хардкод для ткста, сделать нормальный респонз обжекты
31+
if node == nil || err != nil {
32+
errorMessage := "api/proxy: Can't get node"
33+
if err != nil {
34+
errorMessage = err.Error()
35+
log.Warning(errorMessage)
36+
}
37+
rw.Write([]byte(`{"msg": "Cannot find proxy with ID =` + id + ` in the registry: ` + errorMessage + `", "success": false}`))
38+
} else {
39+
rw.Write([]byte(`{"id": "", "request": {}, "msg": "proxy found !", "success": true}`))
40+
}
41+
}

handlers/createSession.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package handlers
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"errors"
7+
log "github.com/Sirupsen/logrus"
8+
"jsonwire-grid/jsonwire"
9+
"jsonwire-grid/pool"
10+
"jsonwire-grid/proxy"
11+
"jsonwire-grid/selenium"
12+
"jsonwire-grid/wda"
13+
"io/ioutil"
14+
"net/http"
15+
"net/http/httputil"
16+
"net/url"
17+
)
18+
19+
type CreateSession struct {
20+
Pool *pool.Pool
21+
}
22+
23+
func (h *CreateSession) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
24+
if r.Method != http.MethodPost {
25+
http.Error(rw, "Method Not Allowed", http.StatusMethodNotAllowed)
26+
return
27+
}
28+
var capabilities map[string]Capabilities
29+
body, err := ioutil.ReadAll(r.Body)
30+
if err != nil {
31+
errorMessage := "Error reading request: " + err.Error()
32+
log.Error(errorMessage)
33+
http.Error(rw, errorMessage, http.StatusInternalServerError)
34+
return
35+
}
36+
err = r.Body.Close()
37+
if err != nil {
38+
log.Error(err.Error())
39+
http.Error(rw, err.Error(), http.StatusInternalServerError)
40+
return
41+
}
42+
rc := ioutil.NopCloser(bytes.NewReader(body))
43+
r.Body = rc
44+
log.Infof("requested session with params: %s", string(body))
45+
err = json.Unmarshal(body, &capabilities)
46+
if err != nil {
47+
errorMessage := "Error unmarshal json: " + err.Error()
48+
log.Error(errorMessage)
49+
http.Error(rw, errorMessage, http.StatusInternalServerError)
50+
return
51+
}
52+
desiredCapabilities, ok := capabilities["desiredCapabilities"]
53+
if !ok {
54+
errorMessage := "Not passed 'desiredCapabilities'"
55+
log.Error(errorMessage)
56+
http.Error(rw, errorMessage, http.StatusInternalServerError)
57+
return
58+
}
59+
poolCapabilities := pool.Capabilities(desiredCapabilities)
60+
tw, err := h.tryCreateSession(r, &poolCapabilities)
61+
if err != nil {
62+
http.Error(rw, "Can't create session: "+err.Error(), http.StatusInternalServerError)
63+
return
64+
}
65+
rw.WriteHeader(tw.StatusCode)
66+
rw.Write(tw.Output)
67+
}
68+
69+
func (h *CreateSession) tryCreateSession(r *http.Request, capabilities *pool.Capabilities) (*proxy.ResponseWriter, error) {
70+
//todo: если запрос отменить, все равно получение ноды будет повторяться, придумать как это предотвратить
71+
node, err := h.Pool.ReserveAvailableNode(*capabilities)
72+
if err != nil {
73+
return nil, errors.New("reserve node error: " + err.Error())
74+
}
75+
//todo: посылать в мониторинг событие, если вернулся не 0
76+
seleniumClient, err := createClient(node.Address, capabilities)
77+
if err != nil {
78+
return nil, errors.New("create Client error: " + err.Error())
79+
}
80+
seleniumNode := jsonwire.NewNode(seleniumClient)
81+
_, err = seleniumNode.RemoveAllSessions()
82+
if err != nil {
83+
log.Warn("Can't remove all sessions from node, go to next available node: " + node.String())
84+
h.Pool.Remove(node)
85+
return h.tryCreateSession(r, capabilities)
86+
}
87+
reverseProxy := httputil.NewSingleHostReverseProxy(&url.URL{
88+
Scheme: "http",
89+
Host: node.Address,
90+
})
91+
transport := proxy.NewCreateSessionTransport(h.Pool, node)
92+
reverseProxy.Transport = transport
93+
tw := proxy.NewResponseWriter()
94+
reverseProxy.ServeHTTP(tw, r)
95+
96+
if !transport.IsSuccess {
97+
log.Warn("Failure proxy request on node " + node.String() + ": " + string(tw.Output))
98+
h.Pool.Remove(node)
99+
return h.tryCreateSession(r, capabilities)
100+
}
101+
102+
return tw, nil
103+
}
104+
105+
func createClient(addr string, capabilities *pool.Capabilities) (jsonwire.ClientInterface, error) {
106+
if capabilities == nil {
107+
return nil, errors.New("capabilities must be not nil")
108+
}
109+
platformName := (*capabilities)["platformName"]
110+
switch platformName {
111+
case "WDA":
112+
return wda.NewClient(addr), nil
113+
default:
114+
return selenium.NewClient(addr), nil
115+
}
116+
}

0 commit comments

Comments
 (0)