From 882c020451c1d3c943f3480019801785dd43c40d Mon Sep 17 00:00:00 2001 From: Danylo Kuvshynov Date: Wed, 6 Jan 2021 20:58:39 +0200 Subject: [PATCH 1/5] Added support for session local storage --- go.sum | 2 + handlers.go | 70 +++++++++++---------------- handlers_test.go | 5 ++ platform/kubernetes.go | 107 +++++++++++++++++++++++++++++++++-------- platform/platform.go | 25 +++++++++- selenosis.go | 34 +++++++++++++ storage/storage.go | 56 +++++++++++++++++++++ 7 files changed, 236 insertions(+), 63 deletions(-) create mode 100644 storage/storage.go diff --git a/go.sum b/go.sum index 6c80c66..2171596 100644 --- a/go.sum +++ b/go.sum @@ -89,6 +89,7 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekf github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -149,6 +150,7 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= diff --git a/handlers.go b/handlers.go index e3ebb9f..7bb29b5 100644 --- a/handlers.go +++ b/handlers.go @@ -41,15 +41,10 @@ func (app *App) CheckLimit(next http.HandlerFunc) http.HandlerFunc { "request": fmt.Sprintf("%s %s", r.Method, r.URL.Path), }) - l, err := app.client.List() - if err != nil { - logger.Errorf("failed to get active session list: %v", err) - tools.JSONError(w, "Failed to get browsers list", http.StatusInternalServerError) - return - } + total := app.stats.Len() - if len(l) >= app.sessionLimit { - logger.Warnf("active session limit reached: total %d, limit %d", len(l), app.sessionLimit) + if total >= app.sessionLimit { + logger.Warnf("active session limit reached: total %d, limit %d", total, app.sessionLimit) tools.JSONError(w, "session limit reached", http.StatusInternalServerError) return } @@ -243,21 +238,18 @@ func (app *App) HadleHubStatus(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - l, err := app.client.List() - if err != nil { - logger.Errorf("hub status: %v", err) - tools.JSONError(w, "Failed to get browsers list", http.StatusInternalServerError) - } + active, pending := getSessionStats(app.stats.List()) + total := len(active) + len(pending) json.NewEncoder(w).Encode( map[string]interface{}{ "value": map[string]interface{}{ "message": "selenosis up and running", - "ready": len(l), + "ready": total, }, }) - logger.WithField("active_sessions", len(l)).Infof("hub status") + logger.WithField("active_sessions", total).Infof("hub status") } //HandleReverseProxy ... @@ -373,7 +365,7 @@ func (app *App) HandleStatus(w http.ResponseWriter, r *http.Request) { Active int `json:"active"` Pending int `json:"pending"` Browsers map[string][]string `json:"config,omitempty"` - Sessions []*platform.Service `json:"sessions,omitempty"` + Sessions []platform.Service `json:"sessions,omitempty"` } type Response struct { @@ -382,40 +374,17 @@ func (app *App) HandleStatus(w http.ResponseWriter, r *http.Request) { Selenosis Status `json:"selenosis,omitempty"` } - sessions, err := app.client.List() - - if err != nil { - app.logger.Errorf("hub status: %v", err) - json.NewEncoder(w).Encode( - Response{ - Status: http.StatusInternalServerError, - Error: fmt.Sprintf("%v", err), - Selenosis: Status{ - Total: app.sessionLimit, - Browsers: app.browsers.GetBrowserVersions(), - }, - }, - ) - return - } - - ready := make([]*platform.Service, 0) - - for _, s := range sessions { - if s.Ready { - ready = append(ready, s) - } - } + active, pending := getSessionStats(app.stats.List()) json.NewEncoder(w).Encode( Response{ Status: http.StatusOK, Selenosis: Status{ Total: app.sessionLimit, - Active: len(ready), - Pending: len(sessions) - len(ready), + Active: len(active), + Pending: len(pending), Browsers: app.browsers.GetBrowserVersions(), - Sessions: ready, + Sessions: active, }, }, ) @@ -444,3 +413,18 @@ func statusOk(session, service, port string) bool { return true } + +func getSessionStats(sessions []platform.Service) (active []platform.Service, pending []platform.Service) { + active = make([]platform.Service, 0) + pending = make([]platform.Service, 0) + + for _, s := range sessions { + switch s.Status { + case platform.Running: + active = append(active, s) + case platform.Pending: + pending = append(pending, s) + } + } + return +} diff --git a/handlers_test.go b/handlers_test.go index afa32f8..5a07891 100644 --- a/handlers_test.go +++ b/handlers_test.go @@ -569,6 +569,11 @@ func (p *PlatformMock) List() ([]*platform.Service, error) { return nil, nil } +func (p *PlatformMock) Watch() <-chan platform.Event { + ch := make(chan platform.Event) + return ch +} + func (p *PlatformMock) Logs(ctx context.Context, name string) (io.ReadCloser, error) { return nil, nil } diff --git a/platform/kubernetes.go b/platform/kubernetes.go index cd7c745..2775252 100644 --- a/platform/kubernetes.go +++ b/platform/kubernetes.go @@ -16,9 +16,10 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" - v1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" "k8s.io/kubernetes/pkg/fields" "k8s.io/utils/pointer" ) @@ -65,7 +66,7 @@ type Client struct { proxyImage string readinessTimeout time.Duration iddleTimeout time.Duration - clientset v1.CoreV1Interface + clientset *kubernetes.Clientset } //NewClient ... @@ -83,7 +84,7 @@ func NewClient(c ClientConfig) (Platform, error) { return &Client{ ns: c.Namespace, - clientset: clientset.CoreV1(), + clientset: clientset, svc: c.Service, svcPort: intstr.FromString(c.ServicePort), imagePullSecretName: c.ImagePullSecretName, @@ -109,7 +110,7 @@ func NewDefaultClient(namespace string) (Platform, error) { return &Client{ ns: namespace, - clientset: clientset.CoreV1(), + clientset: clientset, }, nil } @@ -241,7 +242,7 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) { } context := context.Background() - pod, err := cl.clientset.Pods(cl.ns).Create(context, pod, metav1.CreateOptions{}) + pod, err := cl.clientset.CoreV1().Pods(cl.ns).Create(context, pod, metav1.CreateOptions{}) if err != nil { return nil, fmt.Errorf("failed to create pod %v", err) @@ -252,7 +253,7 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) { cl.Delete(podName) } - w, err := cl.clientset.Pods(cl.ns).Watch(context, metav1.ListOptions{ + w, err := cl.clientset.CoreV1().Pods(cl.ns).Watch(context, metav1.ListOptions{ FieldSelector: fields.OneTermEqualSelector("metadata.name", podName).String(), TimeoutSeconds: pointer.Int64Ptr(cl.readinessTimeout.Milliseconds()), }) @@ -261,7 +262,7 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) { return nil, fmt.Errorf("failed to watch pod status: %v", err) } - ready := func() error { + statusFn := func() error { defer w.Stop() var watchedPod *apiv1.Pod @@ -294,8 +295,7 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) { return fmt.Errorf("pod wasn't running") } - err = ready() - if err != nil { + if statusFn() != nil { cancel() return nil, fmt.Errorf("failed to create pod: %v", err) } @@ -330,7 +330,7 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) { func (cl *Client) Delete(name string) error { context := context.Background() - return cl.clientset.Pods(cl.ns).Delete(context, name, metav1.DeleteOptions{ + return cl.clientset.CoreV1().Pods(cl.ns).Delete(context, name, metav1.DeleteOptions{ GracePeriodSeconds: pointer.Int64Ptr(15), }) } @@ -338,7 +338,7 @@ func (cl *Client) Delete(name string) error { //List ... func (cl *Client) List() ([]*Service, error) { context := context.Background() - pods, err := cl.clientset.Pods(cl.ns).List(context, metav1.ListOptions{ + pods, err := cl.clientset.CoreV1().Pods(cl.ns).List(context, metav1.ListOptions{ LabelSelector: "type=browser", }) @@ -352,17 +352,17 @@ func (cl *Client) List() ([]*Service, error) { podName := pod.GetName() host := fmt.Sprintf("%s.%s", podName, cl.svc) - var ready bool + var status ServiceStatus switch pod.Status.Phase { case apiv1.PodRunning: - ready = true + status = Running case apiv1.PodPending: - ready = false + status = Pending default: - continue + status = Unknown } - s := &Service{ + service := &Service{ SessionID: podName, URL: &url.URL{ Scheme: "http", @@ -372,20 +372,89 @@ func (cl *Client) List() ([]*Service, error) { CancelFunc: func() { cl.Delete(podName) }, - Ready: ready, + Status: status, Started: pod.CreationTimestamp.Time, Uptime: tools.TimeElapsed(pod.CreationTimestamp.Time), } - services = append(services, s) + services = append(services, service) } return services, nil } +//Watch ... +func (cl Client) Watch() <-chan Event { + ch := make(chan Event) + + convert := func(obj interface{}) *Service { + pod := obj.(*apiv1.Pod) + podName := pod.GetName() + host := fmt.Sprintf("%s.%s", podName, cl.svc) + + var status ServiceStatus + switch pod.Status.Phase { + case apiv1.PodRunning: + status = Running + case apiv1.PodPending: + status = Pending + default: + status = Unknown + } + + return &Service{ + SessionID: podName, + URL: &url.URL{ + Scheme: "http", + Host: net.JoinHostPort(host, cl.svcPort.StrVal), + }, + Labels: pod.GetLabels(), + CancelFunc: func() { + cl.Delete(podName) + }, + Status: status, + Started: pod.CreationTimestamp.Time, + Uptime: tools.TimeElapsed(pod.CreationTimestamp.Time), + } + } + + namespace := informers.WithNamespace(cl.ns) + labels := informers.WithTweakListOptions(func(list *metav1.ListOptions) { + list.LabelSelector = "type=browser" + }) + + sharedIformer := informers.NewSharedInformerFactoryWithOptions(cl.clientset, 30*time.Second, namespace, labels) + sharedIformer.Core().V1().Pods().Informer().AddEventHandler( + cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + ch <- Event{ + Type: Added, + Service: convert(obj), + } + }, + UpdateFunc: func(old interface{}, new interface{}) { + ch <- Event{ + Type: Updated, + Service: convert(new), + } + }, + DeleteFunc: func(obj interface{}) { + ch <- Event{ + Type: Deleted, + Service: convert(obj), + } + }, + }, + ) + + var neverStop <-chan struct{} = make(chan struct{}) + sharedIformer.Start(neverStop) + return ch +} + //Logs ... func (cl *Client) Logs(ctx context.Context, name string) (io.ReadCloser, error) { - req := cl.clientset.Pods(cl.ns).GetLogs(name, &apiv1.PodLogOptions{ + req := cl.clientset.CoreV1().Pods(cl.ns).GetLogs(name, &apiv1.PodLogOptions{ Container: "browser", Follow: true, Previous: false, diff --git a/platform/platform.go b/platform/platform.go index 907a082..37adac1 100644 --- a/platform/platform.go +++ b/platform/platform.go @@ -50,15 +50,38 @@ type Service struct { Labels map[string]string `json:"labels"` OnTimeout chan struct{} `json:"-"` CancelFunc func() `json:"-"` - Ready bool `json:"-"` + Status ServiceStatus `json:"-"` Started time.Time `json:"started"` Uptime string `json:"uptime"` } +//ServiceStatus ... +type ServiceStatus string + +//Event ... +type Event struct { + Type EventType + Service *Service +} + +//EventType ... +type EventType string + +const ( + Added EventType = "Added" + Updated EventType = "Updated" + Deleted EventType = "Deleted" + + Pending ServiceStatus = "Pending" + Running ServiceStatus = "Running" + Unknown ServiceStatus = "Unknown" +) + //Platform ... type Platform interface { Create(*ServiceSpec) (*Service, error) Delete(string) error List() ([]*Service, error) + Watch() <-chan Event Logs(context.Context, string) (io.ReadCloser, error) } diff --git a/selenosis.go b/selenosis.go index 1cc2355..a7b421e 100644 --- a/selenosis.go +++ b/selenosis.go @@ -5,6 +5,7 @@ import ( "github.com/alcounit/selenosis/config" "github.com/alcounit/selenosis/platform" + "github.com/alcounit/selenosis/storage" log "github.com/sirupsen/logrus" ) @@ -31,10 +32,42 @@ type App struct { sessionRetryCount int sessionIddleTimeout time.Duration browserWaitTimeout time.Duration + stats *storage.Storage } //New ... func New(logger *log.Logger, client platform.Platform, browsers *config.BrowsersConfig, cfg Configuration) *App { + + storage := storage.New() + + services, err := client.List() + if err != nil { + logger.Errorf("failed to get list of active pods: %v", err) + } + + for _, service := range services { + storage.Put(service.SessionID, service) + } + + ch := client.Watch() + go func() { + for { + select { + case event := <-ch: + switch event.Type { + case platform.Added: + storage.Put(event.Service.SessionID, event.Service) + case platform.Updated: + storage.Put(event.Service.SessionID, event.Service) + case platform.Deleted: + storage.Delete(event.Service.SessionID) + } + default: + break + } + } + }() + return &App{ logger: logger, client: client, @@ -46,5 +79,6 @@ func New(logger *log.Logger, client platform.Platform, browsers *config.Browsers sessionRetryCount: cfg.SessionRetryCount, browserWaitTimeout: cfg.BrowserWaitTimeout, sessionIddleTimeout: cfg.SessionIddleTimeout, + stats: storage, } } diff --git a/storage/storage.go b/storage/storage.go new file mode 100644 index 0000000..4a409fd --- /dev/null +++ b/storage/storage.go @@ -0,0 +1,56 @@ +package storage + +import ( + "sync" + + "github.com/alcounit/selenosis/platform" +) + +//Storage ... +type Storage struct { + sessions map[string]*platform.Service + sync.RWMutex +} + +//New ... +func New() *Storage { + return &Storage{ + sessions: make(map[string]*platform.Service), + } +} + +//Put ... +func (s *Storage) Put(sessionID string, service *platform.Service) { + s.Lock() + defer s.Unlock() + s.sessions[sessionID] = service +} + +//Delete ... +func (s *Storage) Delete(sessionID string) { + s.Lock() + defer s.Unlock() + delete(s.sessions, sessionID) +} + +//List ... +func (s *Storage) List() []platform.Service { + s.Lock() + defer s.Unlock() + var l []platform.Service + + for _, p := range s.sessions { + c := *p + l = append(l, c) + } + return l + +} + +//Len ... +func (s *Storage) Len() int { + s.Lock() + defer s.Unlock() + + return len(s.sessions) +} From 2730ad0da98cfb11461b682740416bafa9306e41 Mon Sep 17 00:00:00 2001 From: Danylo Kuvshynov Date: Thu, 11 Feb 2021 10:35:42 +0200 Subject: [PATCH 2/5] Fix typos, small improvements --- README.md | 2 +- cmd/selenosis/main.go | 22 ++++++++-------- handlers.go | 33 +++++++++++++++++++----- handlers_test.go | 12 ++++----- platform/kubernetes.go | 8 +++--- selenosis.go | 58 +++++++++++++++++++++--------------------- 6 files changed, 77 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 4fb71ee..793dd9f 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Flags: --service-name string kubernetes service name for browsers (default "seleniferous") --browser-wait-timeout duration time in seconds that a browser will be ready (default 30s) --session-wait-timeout duration time in seconds that a session will be ready (default 1m0s) - --session-iddle-timeout duration time in seconds that a session will iddle (default 5m0s) + --session-idle-timeout duration time in seconds that a session will idle (default 5m0s) --session-retry-count int session retry count (default 3) --graceful-shutdown-timeout duration time in seconds gracefull shutdown timeout (default 30s) --image-pull-secret-name string secret name to private registry diff --git a/cmd/selenosis/main.go b/cmd/selenosis/main.go index 2ead281..bead920 100644 --- a/cmd/selenosis/main.go +++ b/cmd/selenosis/main.go @@ -38,7 +38,7 @@ func command() *cobra.Command { limit int browserWaitTimeout time.Duration sessionWaitTimeout time.Duration - sessionIddleTimeout time.Duration + sessionIdleTimeout time.Duration shutdownTimeout time.Duration ) @@ -65,7 +65,7 @@ func command() *cobra.Command { Namespace: namespace, Service: service, ReadinessTimeout: browserWaitTimeout, - IddleTimeout: sessionIddleTimeout, + IdleTimeout: sessionIdleTimeout, ServicePort: proxyPort, ImagePullSecretName: imagePullSecretName, ProxyImage: proxyImage, @@ -80,19 +80,19 @@ func command() *cobra.Command { hostname, _ := os.Hostname() app := selenosis.New(logger, client, browsers, selenosis.Configuration{ - SelenosisHost: hostname, - ServiceName: service, - SidecarPort: proxyPort, - SessionLimit: limit, - SessionRetryCount: sessionRetryCount, - BrowserWaitTimeout: browserWaitTimeout, - SessionIddleTimeout: sessionIddleTimeout, + SelenosisHost: hostname, + ServiceName: service, + SidecarPort: proxyPort, + SessionLimit: limit, + SessionRetryCount: sessionRetryCount, + BrowserWaitTimeout: browserWaitTimeout, + SessionIdleTimeout: sessionIdleTimeout, }) router := mux.NewRouter() router.HandleFunc("/wd/hub/session", app.CheckLimit(app.HandleSession)).Methods(http.MethodPost) router.PathPrefix("/wd/hub/session/{sessionId}").HandlerFunc(app.HandleProxy) - router.HandleFunc("/wd/hub/status", app.HadleHubStatus).Methods(http.MethodGet) + router.HandleFunc("/wd/hub/status", app.HandleHubStatus).Methods(http.MethodGet) router.PathPrefix("/vnc/{sessionId}").Handler(websocket.Handler(app.HandleVNC())) router.PathPrefix("/logs/{sessionId}").Handler(websocket.Handler(app.HandleLogs())) router.PathPrefix("/devtools/{sessionId}").HandlerFunc(app.HandleReverseProxy) @@ -140,7 +140,7 @@ func command() *cobra.Command { cmd.Flags().StringVar(&service, "service-name", "seleniferous", "kubernetes service name for browsers") cmd.Flags().DurationVar(&browserWaitTimeout, "browser-wait-timeout", 30*time.Second, "time in seconds that a browser will be ready") cmd.Flags().DurationVar(&sessionWaitTimeout, "session-wait-timeout", 60*time.Second, "time in seconds that a session will be ready") - cmd.Flags().DurationVar(&sessionIddleTimeout, "session-iddle-timeout", 5*time.Minute, "time in seconds that a session will iddle") + cmd.Flags().DurationVar(&sessionIdleTimeout, "session-idle-timeout", 5*time.Minute, "time in seconds that a session will idle") cmd.Flags().IntVar(&sessionRetryCount, "session-retry-count", 3, "session retry count") cmd.Flags().DurationVar(&shutdownTimeout, "graceful-shutdown-timeout", 30*time.Second, "time in seconds gracefull shutdown timeout") cmd.Flags().StringVar(&imagePullSecretName, "image-pull-secret-name", "", "secret name to private registry") diff --git a/handlers.go b/handlers.go index 7bb29b5..e122351 100644 --- a/handlers.go +++ b/handlers.go @@ -204,9 +204,14 @@ func (app *App) HandleSession(w http.ResponseWriter, r *http.Request) { //HandleProxy ... func (app *App) HandleProxy(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - sessionID := vars["sessionId"] - host := tools.BuildHostPort(sessionID, app.serviceName, app.sidecarPort) + sessionID, ok := vars["sessionId"] + if !ok { + app.logger.Error("session id not found") + tools.JSONError(w, "session id not found", http.StatusBadRequest) + return + } + host := tools.BuildHostPort(sessionID, app.serviceName, app.sidecarPort) logger := app.logger.WithFields(logrus.Fields{ "request_id": uuid.New(), "session_id": sessionID, @@ -229,8 +234,8 @@ func (app *App) HandleProxy(w http.ResponseWriter, r *http.Request) { } -//HadleHubStatus ... -func (app *App) HadleHubStatus(w http.ResponseWriter, r *http.Request) { +//HandleHubStatus ... +func (app *App) HandleHubStatus(w http.ResponseWriter, r *http.Request) { logger := app.logger.WithFields(logrus.Fields{ "request_id": uuid.New(), "request": fmt.Sprintf("%s %s", r.Method, r.URL.Path), @@ -255,7 +260,13 @@ func (app *App) HadleHubStatus(w http.ResponseWriter, r *http.Request) { //HandleReverseProxy ... func (app *App) HandleReverseProxy(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - sessionID := vars["sessionId"] + sessionID, ok := vars["sessionId"] + if !ok { + app.logger.Error("session id not found") + tools.JSONError(w, "session id not found", http.StatusBadRequest) + return + } + fragments := strings.Split(r.URL.Path, "/") logger := app.logger.WithFields(logrus.Fields{ "request_id": uuid.New(), @@ -283,7 +294,11 @@ func (app *App) HandleVNC() websocket.Handler { defer wsconn.Close() vars := mux.Vars(wsconn.Request()) - sessionID := vars["sessionId"] + sessionID, ok := vars["sessionId"] + if !ok { + app.logger.Error("session id not found") + return + } logger := app.logger.WithFields(logrus.Fields{ "request_id": uuid.New(), @@ -323,7 +338,11 @@ func (app *App) HandleLogs() websocket.Handler { defer wsconn.Close() vars := mux.Vars(wsconn.Request()) - sessionID := vars["sessionId"] + sessionID, ok := vars["sessionId"] + if !ok { + app.logger.Error("session id not found") + return + } logger := app.logger.WithFields(logrus.Fields{ "request_id": uuid.New(), diff --git a/handlers_test.go b/handlers_test.go index 5a07891..6d090c2 100644 --- a/handlers_test.go +++ b/handlers_test.go @@ -530,12 +530,12 @@ func initApp(p *PlatformMock) *App { logger := &logrus.Logger{} client := NewPlatformMock(p) conf := Configuration{ - SelenosisHost: "hostname", - ServiceName: "selenosis", - SidecarPort: "4445", - BrowserWaitTimeout: 300 * time.Millisecond, - SessionIddleTimeout: 600 * time.Millisecond, - SessionRetryCount: 2, + SelenosisHost: "hostname", + ServiceName: "selenosis", + SidecarPort: "4445", + BrowserWaitTimeout: 300 * time.Millisecond, + SessionIdleTimeout: 600 * time.Millisecond, + SessionRetryCount: 2, } browsersConfig, _ := config.NewBrowsersConfig("config/browsers.yaml") diff --git a/platform/kubernetes.go b/platform/kubernetes.go index 2775252..6e8d02c 100644 --- a/platform/kubernetes.go +++ b/platform/kubernetes.go @@ -54,7 +54,7 @@ type ClientConfig struct { ImagePullSecretName string ProxyImage string ReadinessTimeout time.Duration - IddleTimeout time.Duration + IdleTimeout time.Duration } //Client ... @@ -65,7 +65,7 @@ type Client struct { imagePullSecretName string proxyImage string readinessTimeout time.Duration - iddleTimeout time.Duration + idleTimeout time.Duration clientset *kubernetes.Clientset } @@ -90,7 +90,7 @@ func NewClient(c ClientConfig) (Platform, error) { imagePullSecretName: c.ImagePullSecretName, proxyImage: c.ProxyImage, readinessTimeout: c.ReadinessTimeout, - iddleTimeout: c.IddleTimeout, + idleTimeout: c.IdleTimeout, }, nil } @@ -217,7 +217,7 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) { }, Ports: getSidecarPorts(cl.svcPort), Command: []string{ - "/seleniferous", "--listhen-port", cl.svcPort.StrVal, "--proxy-default-path", path.Join(layout.Template.Path, "session"), "--iddle-timeout", cl.iddleTimeout.String(), "--namespace", cl.ns, + "/seleniferous", "--listhen-port", cl.svcPort.StrVal, "--proxy-default-path", path.Join(layout.Template.Path, "session"), "--idle-timeout", cl.idleTimeout.String(), "--namespace", cl.ns, }, ImagePullPolicy: apiv1.PullIfNotPresent, }, diff --git a/selenosis.go b/selenosis.go index a7b421e..624700f 100644 --- a/selenosis.go +++ b/selenosis.go @@ -11,28 +11,28 @@ import ( //Configuration .... type Configuration struct { - SelenosisHost string - ServiceName string - SidecarPort string - SessionLimit int - SessionRetryCount int - BrowserWaitTimeout time.Duration - SessionIddleTimeout time.Duration + SelenosisHost string + ServiceName string + SidecarPort string + SessionLimit int + SessionRetryCount int + BrowserWaitTimeout time.Duration + SessionIdleTimeout time.Duration } //App ... type App struct { - logger *log.Logger - client platform.Platform - browsers *config.BrowsersConfig - selenosisHost string - serviceName string - sidecarPort string - sessionLimit int - sessionRetryCount int - sessionIddleTimeout time.Duration - browserWaitTimeout time.Duration - stats *storage.Storage + logger *log.Logger + client platform.Platform + browsers *config.BrowsersConfig + selenosisHost string + serviceName string + sidecarPort string + sessionLimit int + sessionRetryCount int + sessionIdleTimeout time.Duration + browserWaitTimeout time.Duration + stats *storage.Storage } //New ... @@ -69,16 +69,16 @@ func New(logger *log.Logger, client platform.Platform, browsers *config.Browsers }() return &App{ - logger: logger, - client: client, - browsers: browsers, - selenosisHost: cfg.SelenosisHost, - serviceName: cfg.ServiceName, - sidecarPort: cfg.SidecarPort, - sessionLimit: cfg.SessionLimit, - sessionRetryCount: cfg.SessionRetryCount, - browserWaitTimeout: cfg.BrowserWaitTimeout, - sessionIddleTimeout: cfg.SessionIddleTimeout, - stats: storage, + logger: logger, + client: client, + browsers: browsers, + selenosisHost: cfg.SelenosisHost, + serviceName: cfg.ServiceName, + sidecarPort: cfg.SidecarPort, + sessionLimit: cfg.SessionLimit, + sessionRetryCount: cfg.SessionRetryCount, + browserWaitTimeout: cfg.BrowserWaitTimeout, + sessionIdleTimeout: cfg.SessionIdleTimeout, + stats: storage, } } From f5e7922197c0bd47e44b8fcf5d572e01d86ad99c Mon Sep 17 00:00:00 2001 From: Danylo Kuvshynov Date: Fri, 12 Feb 2021 12:48:11 +0200 Subject: [PATCH 3/5] Fix Issue https://github.com/alcounit/selenosis/issues/12 --- platform/kubernetes.go | 18 ++++++++++++------ platform/platform.go | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/platform/kubernetes.go b/platform/kubernetes.go index 6e8d02c..d853163 100644 --- a/platform/kubernetes.go +++ b/platform/kubernetes.go @@ -126,18 +126,17 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) { defaults.session: layout.SessionID, } - envVar := func(name, value string) (i int, b bool) { + envVar := func(name string) (i int, b bool) { for i, slice := range layout.Template.Spec.EnvVars { if slice.Name == name { - slice.Value = value return i, true } } return -1, false } + i, b := envVar(defaults.screenResolution) if layout.RequestedCapabilities.ScreenResolution != "" { - i, b := envVar(defaults.screenResolution, layout.RequestedCapabilities.ScreenResolution) if !b { layout.Template.Spec.EnvVars = append(layout.Template.Spec.EnvVars, apiv1.EnvVar{Name: defaults.screenResolution, @@ -146,27 +145,34 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) { layout.Template.Spec.EnvVars[i] = apiv1.EnvVar{Name: defaults.screenResolution, Value: layout.RequestedCapabilities.ScreenResolution} } labels[defaults.screenResolution] = layout.RequestedCapabilities.ScreenResolution + } else { + if b { + labels[defaults.screenResolution] = layout.Template.Spec.EnvVars[i].Value + } } + i, b = envVar(defaults.enableVNC) if layout.RequestedCapabilities.VNC { vnc := fmt.Sprintf("%v", layout.RequestedCapabilities.VNC) - i, b := envVar(defaults.enableVNC, vnc) if !b { layout.Template.Spec.EnvVars = append(layout.Template.Spec.EnvVars, apiv1.EnvVar{Name: defaults.enableVNC, Value: vnc}) } else { layout.Template.Spec.EnvVars[i] = apiv1.EnvVar{Name: defaults.enableVNC, Value: vnc} } labels[defaults.enableVNC] = vnc + } else { + if b { + labels[defaults.enableVNC] = layout.Template.Spec.EnvVars[i].Value + } } if layout.RequestedCapabilities.TimeZone != "" { - i, b := envVar(defaults.timeZone, layout.RequestedCapabilities.TimeZone) + i, b := envVar(defaults.timeZone) if !b { layout.Template.Spec.EnvVars = append(layout.Template.Spec.EnvVars, apiv1.EnvVar{Name: defaults.timeZone, Value: layout.RequestedCapabilities.TimeZone}) } else { layout.Template.Spec.EnvVars[i] = apiv1.EnvVar{Name: defaults.timeZone, Value: layout.RequestedCapabilities.TimeZone} } - labels[defaults.timeZone] = layout.RequestedCapabilities.TimeZone } if layout.Template.Meta.Labels == nil { diff --git a/platform/platform.go b/platform/platform.go index 37adac1..99d812f 100644 --- a/platform/platform.go +++ b/platform/platform.go @@ -20,7 +20,7 @@ type Meta struct { type Spec struct { Resources apiv1.ResourceRequirements `yaml:"resources,omitempty" json:"resources,omitempty"` HostAliases []apiv1.HostAlias `yaml:"hostAliases,omitempty" json:"hostAliases,omitempty"` - EnvVars []apiv1.EnvVar `yaml:"envVars,omitempty" json:"envVars,omitempty"` + EnvVars []apiv1.EnvVar `yaml:"env,omitempty" json:"env,omitempty"` NodeSelector map[string]string `yaml:"nodeSelector,omitempty" json:"nodeSelector,omitempty"` Affinity apiv1.Affinity `yaml:"affinity,omitempty" json:"affinity,omitempty"` DNSConfig apiv1.PodDNSConfig `yaml:"dnsConfig,omitempty" json:"dnsConfig,omitempty"` From 674e1f1f3c78a51dd8326d1564f2330f99da7d5f Mon Sep 17 00:00:00 2001 From: Danylo Kuvshynov Date: Thu, 18 Feb 2021 18:04:17 +0200 Subject: [PATCH 4/5] Fix https://github.com/alcounit/selenosis/issues/14 --- cmd/selenosis/main.go | 1 + handlers.go | 4 +++- platform/kubernetes.go | 8 ++------ platform/platform.go | 1 + selenosis.go | 3 +++ 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/cmd/selenosis/main.go b/cmd/selenosis/main.go index bead920..7f84dc5 100644 --- a/cmd/selenosis/main.go +++ b/cmd/selenosis/main.go @@ -87,6 +87,7 @@ func command() *cobra.Command { SessionRetryCount: sessionRetryCount, BrowserWaitTimeout: browserWaitTimeout, SessionIdleTimeout: sessionIdleTimeout, + BuildVersion: buildVersion, }) router := mux.NewRouter() diff --git a/handlers.go b/handlers.go index e122351..6c68b7c 100644 --- a/handlers.go +++ b/handlers.go @@ -389,6 +389,7 @@ func (app *App) HandleStatus(w http.ResponseWriter, r *http.Request) { type Response struct { Status int `json:"status"` + Version string `json:"version"` Error string `json:"err,omitempty"` Selenosis Status `json:"selenosis,omitempty"` } @@ -397,7 +398,8 @@ func (app *App) HandleStatus(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode( Response{ - Status: http.StatusOK, + Status: http.StatusOK, + Version: app.buildVersion, Selenosis: Status{ Total: app.sessionLimit, Active: len(active), diff --git a/platform/kubernetes.go b/platform/kubernetes.go index d853163..c21a6f7 100644 --- a/platform/kubernetes.go +++ b/platform/kubernetes.go @@ -197,7 +197,7 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) { Name: "browser", Image: layout.Template.Image, SecurityContext: &apiv1.SecurityContext{ - Privileged: pointer.BoolPtr(false), + Privileged: &layout.Template.Privileged, Capabilities: &apiv1.Capabilities{ Add: []apiv1.Capability{ "SYS_ADMIN", @@ -218,9 +218,6 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) { { Name: "seleniferous", Image: cl.proxyImage, - SecurityContext: &apiv1.SecurityContext{ - Privileged: pointer.BoolPtr(true), - }, Ports: getSidecarPorts(cl.svcPort), Command: []string{ "/seleniferous", "--listhen-port", cl.svcPort.StrVal, "--proxy-default-path", path.Join(layout.Template.Path, "session"), "--idle-timeout", cl.idleTimeout.String(), "--namespace", cl.ns, @@ -504,7 +501,6 @@ func getImagePullSecretList(secret string) []apiv1.LocalObjectReference { func waitForService(u url.URL, t time.Duration) error { up := make(chan struct{}) done := make(chan struct{}) - u.Path = "/status" go func() { for { select { @@ -513,7 +509,7 @@ func waitForService(u url.URL, t time.Duration) error { default: } - req, _ := http.NewRequest(http.MethodGet, u.String(), nil) + req, _ := http.NewRequest(http.MethodHead, u.String(), nil) req.Close = true resp, err := http.DefaultClient.Do(req) if resp != nil { diff --git a/platform/platform.go b/platform/platform.go index 99d812f..48e22b0 100644 --- a/platform/platform.go +++ b/platform/platform.go @@ -32,6 +32,7 @@ type BrowserSpec struct { BrowserVersion string `yaml:"-" json:"-"` Image string `yaml:"image" json:"image"` Path string `yaml:"path" json:"path"` + Privileged bool `yaml:"privileged" json:"privileged"` Meta Meta `yaml:"meta" json:"meta"` Spec Spec `yaml:"spec" json:"spec"` } diff --git a/selenosis.go b/selenosis.go index 624700f..04ec468 100644 --- a/selenosis.go +++ b/selenosis.go @@ -18,6 +18,7 @@ type Configuration struct { SessionRetryCount int BrowserWaitTimeout time.Duration SessionIdleTimeout time.Duration + BuildVersion string } //App ... @@ -32,6 +33,7 @@ type App struct { sessionRetryCount int sessionIdleTimeout time.Duration browserWaitTimeout time.Duration + buildVersion string stats *storage.Storage } @@ -79,6 +81,7 @@ func New(logger *log.Logger, client platform.Platform, browsers *config.Browsers sessionRetryCount: cfg.SessionRetryCount, browserWaitTimeout: cfg.BrowserWaitTimeout, sessionIdleTimeout: cfg.SessionIdleTimeout, + buildVersion: cfg.BuildVersion, stats: storage, } } From 2d7fc7b38167b774613093ade14df5b0fa92afe0 Mon Sep 17 00:00:00 2001 From: Danylo Kuvshynov Date: Tue, 23 Feb 2021 11:49:42 +0200 Subject: [PATCH 5/5] Implement https://github.com/alcounit/selenosis/issues/16 --- platform/kubernetes.go | 5 +---- platform/platform.go | 1 + storage/storage.go | 2 ++ 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/platform/kubernetes.go b/platform/kubernetes.go index c21a6f7..9417d19 100644 --- a/platform/kubernetes.go +++ b/platform/kubernetes.go @@ -11,7 +11,6 @@ import ( "path" "time" - "github.com/alcounit/selenosis/tools" apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" @@ -240,6 +239,7 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) { RestartPolicy: apiv1.RestartPolicyNever, Affinity: &layout.Template.Spec.Affinity, DNSConfig: &layout.Template.Spec.DNSConfig, + Tolerations: layout.Template.Spec.Tolerations, ImagePullSecrets: getImagePullSecretList(cl.imagePullSecretName), }, } @@ -323,7 +323,6 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) { cancel() }, Started: pod.CreationTimestamp.Time, - Uptime: tools.TimeElapsed(pod.CreationTimestamp.Time), } return svc, nil @@ -377,7 +376,6 @@ func (cl *Client) List() ([]*Service, error) { }, Status: status, Started: pod.CreationTimestamp.Time, - Uptime: tools.TimeElapsed(pod.CreationTimestamp.Time), } services = append(services, service) } @@ -417,7 +415,6 @@ func (cl Client) Watch() <-chan Event { }, Status: status, Started: pod.CreationTimestamp.Time, - Uptime: tools.TimeElapsed(pod.CreationTimestamp.Time), } } diff --git a/platform/platform.go b/platform/platform.go index 48e22b0..1571731 100644 --- a/platform/platform.go +++ b/platform/platform.go @@ -24,6 +24,7 @@ type Spec struct { NodeSelector map[string]string `yaml:"nodeSelector,omitempty" json:"nodeSelector,omitempty"` Affinity apiv1.Affinity `yaml:"affinity,omitempty" json:"affinity,omitempty"` DNSConfig apiv1.PodDNSConfig `yaml:"dnsConfig,omitempty" json:"dnsConfig,omitempty"` + Tolerations []apiv1.Toleration `yaml:"tolerations,omitempty" json:"tolerations,omitempty"` } //BrowserSpec describes settings for Service diff --git a/storage/storage.go b/storage/storage.go index 4a409fd..db0cae1 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -4,6 +4,7 @@ import ( "sync" "github.com/alcounit/selenosis/platform" + "github.com/alcounit/selenosis/tools" ) //Storage ... @@ -41,6 +42,7 @@ func (s *Storage) List() []platform.Service { for _, p := range s.sessions { c := *p + c.Uptime = tools.TimeElapsed(c.Started) l = append(l, c) } return l