From d0f1c26d8b807a6100f4a28698020675708a0293 Mon Sep 17 00:00:00 2001 From: Luke Ridge Date: Wed, 12 Jan 2022 20:01:02 -0500 Subject: [PATCH 01/20] create a video container in the pod when it's requested --- platform/kubernetes.go | 116 ++++++++++++++++++++++-------------- platform/kubernetes_test.go | 77 ++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 45 deletions(-) diff --git a/platform/kubernetes.go b/platform/kubernetes.go index 205ec9a..1d7b6d5 100644 --- a/platform/kubernetes.go +++ b/platform/kubernetes.go @@ -37,7 +37,7 @@ var ( } defaultsAnnotations = struct { - testName, browserName, browserVersion, screenResolution, enableVNC, timeZone string + testName, browserName, browserVersion, screenResolution, enableVNC, videoName, timeZone string }{ testName: "testName", browserName: "browserName", @@ -45,6 +45,7 @@ var ( screenResolution: "SCREEN_RESOLUTION", enableVNC: "ENABLE_VNC", timeZone: "TZ", + videoName: "", } defaultLabels = struct { serviceType, appType, session string @@ -62,6 +63,7 @@ type ClientConfig struct { ServicePort string ImagePullSecretName string ProxyImage string + VideoImage string ReadinessTimeout time.Duration IdleTimeout time.Duration } @@ -297,6 +299,7 @@ type service struct { svcPort intstr.IntOrString imagePullSecretName string proxyImage string + videoImage string readinessTimeout time.Duration idleTimeout time.Duration clientset kubernetes.Interface @@ -308,6 +311,7 @@ func (cl *service) Create(layout ServiceSpec) (Service, error) { defaultsAnnotations.browserName: layout.Template.BrowserName, defaultsAnnotations.browserVersion: layout.Template.BrowserVersion, defaultsAnnotations.testName: layout.RequestedCapabilities.TestName, + defaultsAnnotations.videoName: layout.RequestedCapabilities.VideoName, } labels := map[string]string{ @@ -386,50 +390,7 @@ func (cl *service) Create(layout ServiceSpec) (Service, error) { layout.Template.Meta.Annotations["capabilities"] = string(caps) } - pod := &apiv1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: layout.SessionID, - Labels: layout.Template.Meta.Labels, - Annotations: layout.Template.Meta.Annotations, - }, - Spec: apiv1.PodSpec{ - Hostname: layout.SessionID, - Subdomain: cl.svc, - Containers: []apiv1.Container{ - { - Name: "browser", - Image: layout.Template.Image, - SecurityContext: &apiv1.SecurityContext{ - Privileged: layout.Template.Privileged, - Capabilities: getCapabilities(layout.Template.Capabilities), - }, - Env: layout.Template.Spec.EnvVars, - Ports: getBrowserPorts(), - Resources: layout.Template.Spec.Resources, - VolumeMounts: getVolumeMounts(layout.Template.Spec.VolumeMounts), - ImagePullPolicy: apiv1.PullIfNotPresent, - }, - { - Name: "seleniferous", - Image: cl.proxyImage, - 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, - }, - ImagePullPolicy: apiv1.PullIfNotPresent, - }, - }, - Volumes: getVolumes(layout.Template.Volumes), - NodeSelector: layout.Template.Spec.NodeSelector, - HostAliases: layout.Template.Spec.HostAliases, - RestartPolicy: apiv1.RestartPolicyNever, - Affinity: &layout.Template.Spec.Affinity, - DNSConfig: &layout.Template.Spec.DNSConfig, - Tolerations: layout.Template.Spec.Tolerations, - ImagePullSecrets: getImagePullSecretList(cl.imagePullSecretName), - SecurityContext: getSecurityContext(layout.Template.RunAs), - }, - } + pod := cl.BuildPod(layout) context := context.Background() pod, err := cl.clientset.CoreV1().Pods(cl.ns).Create(context, pod, metav1.CreateOptions{}) @@ -531,6 +492,66 @@ func (cl *service) Logs(ctx context.Context, name string) (io.ReadCloser, error) return req.Stream(ctx) } +func (cl *service) BuildPod(layout ServiceSpec) *apiv1.Pod { + pod := &apiv1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: layout.SessionID, + Labels: layout.Template.Meta.Labels, + Annotations: layout.Template.Meta.Annotations, + }, + Spec: apiv1.PodSpec{ + Hostname: layout.SessionID, + Subdomain: cl.svc, + Containers: []apiv1.Container{ + { + Name: "browser", + Image: layout.Template.Image, + SecurityContext: &apiv1.SecurityContext{ + Privileged: layout.Template.Privileged, + Capabilities: getCapabilities(layout.Template.Capabilities), + }, + Env: layout.Template.Spec.EnvVars, + Ports: getBrowserPorts(), + Resources: layout.Template.Spec.Resources, + VolumeMounts: getVolumeMounts(layout.Template.Spec.VolumeMounts), + ImagePullPolicy: apiv1.PullIfNotPresent, + }, + { + Name: "seleniferous", + Image: cl.proxyImage, + 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, + }, + ImagePullPolicy: apiv1.PullIfNotPresent, + }, + }, + Volumes: getVolumes(layout.Template.Volumes), + NodeSelector: layout.Template.Spec.NodeSelector, + HostAliases: layout.Template.Spec.HostAliases, + RestartPolicy: apiv1.RestartPolicyNever, + Affinity: &layout.Template.Spec.Affinity, + DNSConfig: &layout.Template.Spec.DNSConfig, + Tolerations: layout.Template.Spec.Tolerations, + ImagePullSecrets: getImagePullSecretList(cl.imagePullSecretName), + SecurityContext: getSecurityContext(layout.Template.RunAs), + }, + } + + if layout.RequestedCapabilities.Video { + videoContainer := apiv1.Container{ + Name: "video", + Image: cl.videoImage, + Ports: getVideoPorts(), + Command: []string{}, + VolumeMounts: getVolumeMounts(layout.Template.Spec.VolumeMounts), + ImagePullPolicy: apiv1.PullIfNotPresent, + } + pod.Spec.Containers = append(pod.Spec.Containers, videoContainer) + } + return pod +} + type quota struct { ns string clientset kubernetes.Interface @@ -631,6 +652,11 @@ func getSidecarPorts(p intstr.IntOrString) []apiv1.ContainerPort { return port } +func getVideoPorts() []apiv1.ContainerPort { + port := []apiv1.ContainerPort{} + return port +} + func getImagePullSecretList(secret string) []apiv1.LocalObjectReference { refList := make([]apiv1.LocalObjectReference, 0) if secret != "" { diff --git a/platform/kubernetes_test.go b/platform/kubernetes_test.go index cc5c0c6..524abcf 100644 --- a/platform/kubernetes_test.go +++ b/platform/kubernetes_test.go @@ -19,6 +19,83 @@ import ( "k8s.io/client-go/kubernetes/fake" ) +func TestPodRequestedWithVideo(t *testing.T) { + tests := map[string]struct { + ns string + layout ServiceSpec + }{ + "Verify pod spec containers includes a video container if video was requested as a capability": { + layout: ServiceSpec{ + SessionID: "chrome-85-0-de44c3c4-1a35-412b-b526-f5da802144911", + RequestedCapabilities: selenium.Capabilities{ + VNC: true, + Video: true, + }, + Template: BrowserSpec{ + BrowserName: "chrome", + BrowserVersion: "85.0", + Image: "selenoid/vnc:chrome_85.0", + Path: "/", + }, + }, + }, + } + + for name, test := range tests { + + t.Logf("TC: %s", name) + + mock := fake.NewSimpleClientset() + + service := &service{ + ns: test.ns, + clientset: mock, + } + + pod := service.BuildPod(test.layout) + + assert.Equal(t, "video", pod.Spec.Containers[2].Name) + } +} + +func TestPodWithoutVideo(t *testing.T) { + tests := map[string]struct { + ns string + layout ServiceSpec + }{ + "Verify that if video is not requested, the pod spec only includes 2 containers": { + layout: ServiceSpec{ + SessionID: "chrome-85-0-de44c3c4-1a35-412b-b526-f5da802144911", + RequestedCapabilities: selenium.Capabilities{ + VNC: true, + }, + Template: BrowserSpec{ + BrowserName: "chrome", + BrowserVersion: "85.0", + Image: "selenoid/vnc:chrome_85.0", + Path: "/", + }, + }, + }, + } + + for name, test := range tests { + + t.Logf("TC: %s", name) + + mock := fake.NewSimpleClientset() + + service := &service{ + ns: test.ns, + clientset: mock, + } + + pod := service.BuildPod(test.layout) + + assert.Equal(t, 2, len(pod.Spec.Containers)) + } +} + func TestErrorsOnServiceCreate(t *testing.T) { tests := map[string]struct { ns string From 9f0a86830aebb34f767b102089aa2db667b9c667 Mon Sep 17 00:00:00 2001 From: Luke Ridge Date: Wed, 12 Jan 2022 20:01:32 -0500 Subject: [PATCH 02/20] add a flag to override the video recorder image --- cmd/selenosis/main.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/selenosis/main.go b/cmd/selenosis/main.go index 83c01a2..c986720 100644 --- a/cmd/selenosis/main.go +++ b/cmd/selenosis/main.go @@ -34,6 +34,7 @@ func command() *cobra.Command { service string imagePullSecretName string proxyImage string + videoImage string sessionRetryCount int limit int browserWaitTimeout time.Duration @@ -69,6 +70,7 @@ func command() *cobra.Command { ServicePort: proxyPort, ImagePullSecretName: imagePullSecretName, ProxyImage: proxyImage, + VideoImage: videoImage, }) if err != nil { @@ -146,6 +148,7 @@ func command() *cobra.Command { 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") cmd.Flags().StringVar(&proxyImage, "proxy-image", "alcounit/seleniferous:latest", "in case you use private registry replace with image from private registry") + cmd.Flags().StringVar(&videoImage, "video-image", "selenoid/video-recorder:latest-release", "the image to use for video recording when it's requested") cmd.Flags().SortFlags = false return cmd From e1a377baef81aedaf8488cb6e676a6fc0ee7478a Mon Sep 17 00:00:00 2001 From: Luke Ridge Date: Wed, 12 Jan 2022 20:18:56 -0500 Subject: [PATCH 03/20] write tests without permutation since they're functional --- platform/kubernetes_test.go | 96 ++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 56 deletions(-) diff --git a/platform/kubernetes_test.go b/platform/kubernetes_test.go index 524abcf..389567f 100644 --- a/platform/kubernetes_test.go +++ b/platform/kubernetes_test.go @@ -20,80 +20,64 @@ import ( ) func TestPodRequestedWithVideo(t *testing.T) { - tests := map[string]struct { - ns string - layout ServiceSpec - }{ - "Verify pod spec containers includes a video container if video was requested as a capability": { - layout: ServiceSpec{ - SessionID: "chrome-85-0-de44c3c4-1a35-412b-b526-f5da802144911", - RequestedCapabilities: selenium.Capabilities{ - VNC: true, - Video: true, - }, - Template: BrowserSpec{ - BrowserName: "chrome", - BrowserVersion: "85.0", - Image: "selenoid/vnc:chrome_85.0", - Path: "/", - }, + t.Logf("TC: %s", "Verify pod spec containers includes a video container if video was requested as a capability") + params := struct{ ns string; layout ServiceSpec }{ + ns: "selenosis", + layout: ServiceSpec{ + SessionID: "chrome-85-0-de44c3c4-1a35-412b-b526-f5da802144911", + RequestedCapabilities: selenium.Capabilities{ + VNC: true, + Video: true, + }, + Template: BrowserSpec{ + BrowserName: "chrome", + BrowserVersion: "85.0", + Image: "selenoid/vnc:chrome_85.0", + Path: "/", }, }, } - for name, test := range tests { - - t.Logf("TC: %s", name) - - mock := fake.NewSimpleClientset() + mock := fake.NewSimpleClientset() - service := &service{ - ns: test.ns, - clientset: mock, - } + service := &service{ + ns: params.ns, + clientset: mock, + } - pod := service.BuildPod(test.layout) + pod := service.BuildPod(params.layout) - assert.Equal(t, "video", pod.Spec.Containers[2].Name) - } + assert.Equal(t, "video", pod.Spec.Containers[2].Name) } func TestPodWithoutVideo(t *testing.T) { - tests := map[string]struct { - ns string - layout ServiceSpec - }{ - "Verify that if video is not requested, the pod spec only includes 2 containers": { - layout: ServiceSpec{ - SessionID: "chrome-85-0-de44c3c4-1a35-412b-b526-f5da802144911", - RequestedCapabilities: selenium.Capabilities{ - VNC: true, - }, - Template: BrowserSpec{ - BrowserName: "chrome", - BrowserVersion: "85.0", - Image: "selenoid/vnc:chrome_85.0", - Path: "/", - }, + params := struct { ns string; layout ServiceSpec } { + layout: ServiceSpec{ + SessionID: "chrome-85-0-de44c3c4-1a35-412b-b526-f5da802144911", + RequestedCapabilities: selenium.Capabilities{ + VNC: true, + }, + Template: BrowserSpec{ + BrowserName: "chrome", + BrowserVersion: "85.0", + Image: "selenoid/vnc:chrome_85.0", + Path: "/", }, }, } - for name, test := range tests { - - t.Logf("TC: %s", name) + t.Logf("TC: %s", "Verify that if video is not requested, the pod spec only includes 2 containers") - mock := fake.NewSimpleClientset() + mock := fake.NewSimpleClientset() - service := &service{ - ns: test.ns, - clientset: mock, - } + service := &service{ + ns: params.ns, + clientset: mock, + } - pod := service.BuildPod(test.layout) + pod := service.BuildPod(params.layout) - assert.Equal(t, 2, len(pod.Spec.Containers)) - } + assert.Equal(t, 2, len(pod.Spec.Containers)) } func TestErrorsOnServiceCreate(t *testing.T) { From 32b0f935f777700760a6de1c852abfc2dec1e899 Mon Sep 17 00:00:00 2001 From: Luke Ridge Date: Wed, 12 Jan 2022 20:36:38 -0500 Subject: [PATCH 04/20] only check up to read error: open for config file existence test, as it was written the test fill fail on POSIX based systems. --- config/config_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/config_test.go b/config/config_test.go index d385e40..231b6a6 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -77,18 +77,18 @@ func TestConfigFile(t *testing.T) { tests := map[string]struct { data string - err error + err string }{ "verify config file not exist": { data: empty, - err: errors.New("failed to read config: read error: open : The system cannot find the file specified."), + err: "failed to read config: read error: open", }, } for name, test := range tests { t.Logf("TC: %s", name) _, err := NewBrowsersConfig(test.data) - assert.Equal(t, test.err, err) + assert.Contains(t, err.Error(), test.err) } } From 3fe05e880f9749d0e8d2827fe0d9b09883445d63 Mon Sep 17 00:00:00 2001 From: Luke Ridge Date: Wed, 12 Jan 2022 20:54:44 -0500 Subject: [PATCH 05/20] checksum file --- go.sum | 4 ---- 1 file changed, 4 deletions(-) diff --git a/go.sum b/go.sum index dd3ad9d..1de30ff 100644 --- a/go.sum +++ b/go.sum @@ -86,7 +86,6 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 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= @@ -261,7 +260,6 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -366,7 +364,6 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -458,7 +455,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= From dbcc77c1e41c27b879eac249e31e8ef0c7011b90 Mon Sep 17 00:00:00 2001 From: Luke Ridge Date: Tue, 18 Jan 2022 07:53:38 -0500 Subject: [PATCH 06/20] add a missing parameter binding, get a little more specific with the unit test --- platform/kubernetes.go | 1 + platform/kubernetes_test.go | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/platform/kubernetes.go b/platform/kubernetes.go index 1d7b6d5..eca4f87 100644 --- a/platform/kubernetes.go +++ b/platform/kubernetes.go @@ -98,6 +98,7 @@ func NewClient(c ClientConfig) (Platform, error) { svcPort: intstr.FromString(c.ServicePort), imagePullSecretName: c.ImagePullSecretName, proxyImage: c.ProxyImage, + videoImage: c.VideoImage, readinessTimeout: c.ReadinessTimeout, idleTimeout: c.IdleTimeout, } diff --git a/platform/kubernetes_test.go b/platform/kubernetes_test.go index 389567f..d7e25a8 100644 --- a/platform/kubernetes_test.go +++ b/platform/kubernetes_test.go @@ -21,8 +21,10 @@ import ( func TestPodRequestedWithVideo(t *testing.T) { t.Logf("TC: %s", "Verify pod spec containers includes a video container if video was requested as a capability") - params := struct{ ns string; layout ServiceSpec }{ + params := struct{ ns string; layout ServiceSpec; videoImage string; containerName string }{ ns: "selenosis", + videoImage: "selenoid/video-recorder", + containerName: "video", layout: ServiceSpec{ SessionID: "chrome-85-0-de44c3c4-1a35-412b-b526-f5da802144911", RequestedCapabilities: selenium.Capabilities{ @@ -43,11 +45,13 @@ func TestPodRequestedWithVideo(t *testing.T) { service := &service{ ns: params.ns, clientset: mock, + videoImage: params.videoImage, } pod := service.BuildPod(params.layout) - assert.Equal(t, "video", pod.Spec.Containers[2].Name) + assert.Equal(t, params.containerName, pod.Spec.Containers[2].Name) + assert.Equal(t, params.videoImage, pod.Spec.Containers[2].Image) } func TestPodWithoutVideo(t *testing.T) { From d0ee53fbb8090896ffb13d4498e4639b99fa80ad Mon Sep 17 00:00:00 2001 From: Luke Ridge Date: Thu, 3 Feb 2022 21:19:32 -0500 Subject: [PATCH 07/20] add missing environment variable references --- README.md | 1 + platform/kubernetes.go | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 07f24a6..fe9a85e 100644 --- a/README.md +++ b/README.md @@ -704,6 +704,7 @@ List of capabilities required for selenoid-ui compatibility: | enableVNC | boolean | enables VNC support | | name | string | name of test | | screenResolution | string | custom screen resolution | +| enableVideo | boolean | enables Video capture |
Note: you can omit browser version in your desired capabilities, make sure you set defaultVersion property in the config file. diff --git a/platform/kubernetes.go b/platform/kubernetes.go index eca4f87..a2e1610 100644 --- a/platform/kubernetes.go +++ b/platform/kubernetes.go @@ -37,13 +37,14 @@ var ( } defaultsAnnotations = struct { - testName, browserName, browserVersion, screenResolution, enableVNC, videoName, timeZone string + testName, browserName, browserVersion, screenResolution, enableVNC, enableVideo, videoName, timeZone string }{ testName: "testName", browserName: "browserName", browserVersion: "browserVersion", screenResolution: "SCREEN_RESOLUTION", enableVNC: "ENABLE_VNC", + enableVideo: "ENABLE_VIDEO" timeZone: "TZ", videoName: "", } @@ -361,6 +362,21 @@ func (cl *service) Create(layout ServiceSpec) (Service, error) { } } + i, b = envVar(defaultsAnnotations.enableVideo) + if layout.RequestedCapabilities.enableVideo { + vnc := fmt.Sprintf("%v", layout.RequestedCapabilities.VNC) + if !b { + layout.Template.Spec.EnvVars = append(layout.Template.Spec.EnvVars, apiv1.EnvVar{Name: defaultsAnnotations.enableVideo, Value: video}) + } else { + layout.Template.Spec.EnvVars[i] = apiv1.EnvVar{Name: defaultsAnnotations.enableVideo, Value: video} + } + annontations[defaultsAnnotations.enableVideo] = video + } else { + if b { + annontations[defaultsAnnotations.enableVideo] = layout.Template.Spec.EnvVars[i].Value + } + } + i, b = envVar(defaultsAnnotations.timeZone) if layout.RequestedCapabilities.TimeZone != "" { if !b { @@ -374,6 +390,7 @@ func (cl *service) Create(layout ServiceSpec) (Service, error) { annontations[defaultsAnnotations.timeZone] = layout.Template.Spec.EnvVars[i].Value } } + if layout.Template.Meta.Labels == nil { layout.Template.Meta.Labels = make(map[string]string) From 7df44654f3d5337391636ccb15525395a802999d Mon Sep 17 00:00:00 2001 From: Luke Ridge Date: Fri, 4 Feb 2022 10:21:25 -0500 Subject: [PATCH 08/20] missed a comma --- platform/kubernetes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/kubernetes.go b/platform/kubernetes.go index a2e1610..bc88a50 100644 --- a/platform/kubernetes.go +++ b/platform/kubernetes.go @@ -44,7 +44,7 @@ var ( browserVersion: "browserVersion", screenResolution: "SCREEN_RESOLUTION", enableVNC: "ENABLE_VNC", - enableVideo: "ENABLE_VIDEO" + enableVideo: "ENABLE_VIDEO", timeZone: "TZ", videoName: "", } From d9b59681093f2b4b4dc28e04b0efbfe826c13a2f Mon Sep 17 00:00:00 2001 From: Luke Ridge Date: Fri, 4 Feb 2022 10:24:12 -0500 Subject: [PATCH 09/20] fix various compile errors --- platform/kubernetes.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/kubernetes.go b/platform/kubernetes.go index bc88a50..6a74d9f 100644 --- a/platform/kubernetes.go +++ b/platform/kubernetes.go @@ -363,8 +363,8 @@ func (cl *service) Create(layout ServiceSpec) (Service, error) { } i, b = envVar(defaultsAnnotations.enableVideo) - if layout.RequestedCapabilities.enableVideo { - vnc := fmt.Sprintf("%v", layout.RequestedCapabilities.VNC) + if layout.RequestedCapabilities.Video { + video := fmt.Sprintf("%v", layout.RequestedCapabilities.Video) if !b { layout.Template.Spec.EnvVars = append(layout.Template.Spec.EnvVars, apiv1.EnvVar{Name: defaultsAnnotations.enableVideo, Value: video}) } else { From 3ebe10b67694a963949772989bd2520aca172ff5 Mon Sep 17 00:00:00 2001 From: Luke Ridge Date: Fri, 4 Feb 2022 10:47:49 -0500 Subject: [PATCH 10/20] expose xdisplay port (60+99) for ffmpeg --- platform/kubernetes.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/platform/kubernetes.go b/platform/kubernetes.go index 6a74d9f..a14da95 100644 --- a/platform/kubernetes.go +++ b/platform/kubernetes.go @@ -30,10 +30,11 @@ var ( label = "selenosis.app.type" quotaName = "selenosis-pod-limit" browserPorts = struct { - selenium, vnc intstr.IntOrString + selenium, vnc, video intstr.IntOrString }{ selenium: intstr.FromString("4444"), vnc: intstr.FromString("5900"), + video: intstr.FromString("6099"), } defaultsAnnotations = struct { @@ -657,6 +658,7 @@ func getBrowserPorts() []apiv1.ContainerPort { fn("vnc", browserPorts.vnc.IntValue()) fn("selenium", browserPorts.selenium.IntValue()) + fn("video", browserPorts.selenium.IntValue()) return port } From 39dcebdb141b2489795f5e6580a635de7cd98761 Mon Sep 17 00:00:00 2001 From: Luke Ridge Date: Fri, 4 Feb 2022 11:00:40 -0500 Subject: [PATCH 11/20] copy paste error --- platform/kubernetes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/kubernetes.go b/platform/kubernetes.go index a14da95..7cd5d35 100644 --- a/platform/kubernetes.go +++ b/platform/kubernetes.go @@ -658,7 +658,7 @@ func getBrowserPorts() []apiv1.ContainerPort { fn("vnc", browserPorts.vnc.IntValue()) fn("selenium", browserPorts.selenium.IntValue()) - fn("video", browserPorts.selenium.IntValue()) + fn("video", browserPorts.video.IntValue()) return port } From 0b173be2649d0cfa00e69319c65b9e75a9f39e95 Mon Sep 17 00:00:00 2001 From: Luke Ridge Date: Fri, 4 Feb 2022 11:51:58 -0500 Subject: [PATCH 12/20] default video name to session id --- platform/kubernetes.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/platform/kubernetes.go b/platform/kubernetes.go index 7cd5d35..39abcbc 100644 --- a/platform/kubernetes.go +++ b/platform/kubernetes.go @@ -47,7 +47,7 @@ var ( enableVNC: "ENABLE_VNC", enableVideo: "ENABLE_VIDEO", timeZone: "TZ", - videoName: "", + videoName: "VIDEO_NAME", } defaultLabels = struct { serviceType, appType, session string @@ -371,7 +371,18 @@ func (cl *service) Create(layout ServiceSpec) (Service, error) { } else { layout.Template.Spec.EnvVars[i] = apiv1.EnvVar{Name: defaultsAnnotations.enableVideo, Value: video} } + i, b = envVar(defaultsAnnotations.videoName) + videoName := fmt.Sprintf("%v", layout.RequestedCapabilities.VideoName) + if videoName == "" { + videoName = fmt.Sprintf("%v.mp4", layout.SessionID) + } + if !b { + layout.Template.Spec.EnvVars = append(layout.Template.Spec.EnvVars, apiv1.EnvVar{Name: defaultsAnnotations.videoName, Value: videoName}) + } else { + layout.Template.Spec.EnvVars[i] = apiv1.EnvVar{Name: defaultsAnnotations.videoName, Value: video} + } annontations[defaultsAnnotations.enableVideo] = video + annontations[defaultsAnnotations.videoName] = videoName } else { if b { annontations[defaultsAnnotations.enableVideo] = layout.Template.Spec.EnvVars[i].Value @@ -563,6 +574,7 @@ func (cl *service) BuildPod(layout ServiceSpec) *apiv1.Pod { Image: cl.videoImage, Ports: getVideoPorts(), Command: []string{}, + Env: layout.Template.Spec.EnvVars, VolumeMounts: getVolumeMounts(layout.Template.Spec.VolumeMounts), ImagePullPolicy: apiv1.PullIfNotPresent, } From 4eec9d4cdf5266819bde926fd0f126a117ca610c Mon Sep 17 00:00:00 2001 From: Luke Ridge Date: Fri, 4 Feb 2022 11:54:28 -0500 Subject: [PATCH 13/20] set browser container name to localhost (it must be this.) --- platform/kubernetes.go | 1 + 1 file changed, 1 insertion(+) diff --git a/platform/kubernetes.go b/platform/kubernetes.go index 39abcbc..0c7a8db 100644 --- a/platform/kubernetes.go +++ b/platform/kubernetes.go @@ -371,6 +371,7 @@ func (cl *service) Create(layout ServiceSpec) (Service, error) { } else { layout.Template.Spec.EnvVars[i] = apiv1.EnvVar{Name: defaultsAnnotations.enableVideo, Value: video} } + layout.Template.Spec.EnvVars = append(layout.Template.Spec.EnvVars, apiv1.EnvVar{Name: "BROWSER_CONTAINER_NAME", Value: "localhost"}) i, b = envVar(defaultsAnnotations.videoName) videoName := fmt.Sprintf("%v", layout.RequestedCapabilities.VideoName) if videoName == "" { From b7a7fffa255f6b86dcaaa6ba9a367fbaf6af04b7 Mon Sep 17 00:00:00 2001 From: Luke Ridge Date: Fri, 4 Feb 2022 13:36:33 -0500 Subject: [PATCH 14/20] use file_name instead of video_name --- platform/kubernetes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/kubernetes.go b/platform/kubernetes.go index 0c7a8db..06c90ce 100644 --- a/platform/kubernetes.go +++ b/platform/kubernetes.go @@ -47,7 +47,7 @@ var ( enableVNC: "ENABLE_VNC", enableVideo: "ENABLE_VIDEO", timeZone: "TZ", - videoName: "VIDEO_NAME", + videoName: "FILE_NAME", } defaultLabels = struct { serviceType, appType, session string From 0d24efb913f48e1fad4f14675e12e24dbcec9c19 Mon Sep 17 00:00:00 2001 From: Luke Ridge Date: Fri, 4 Feb 2022 14:10:48 -0500 Subject: [PATCH 15/20] terminate ffmpeg softly --- platform/kubernetes.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/platform/kubernetes.go b/platform/kubernetes.go index 06c90ce..6af0aad 100644 --- a/platform/kubernetes.go +++ b/platform/kubernetes.go @@ -578,6 +578,13 @@ func (cl *service) BuildPod(layout ServiceSpec) *apiv1.Pod { Env: layout.Template.Spec.EnvVars, VolumeMounts: getVolumeMounts(layout.Template.Spec.VolumeMounts), ImagePullPolicy: apiv1.PullIfNotPresent, + Lifecycle: &apiv1.Lifecycle{ + PreStop: &apiv1.Handler{ + Exec: &apiv1.ExecAction{ + Command: []string{"kill -15 1"}, + }, + }, + }, } pod.Spec.Containers = append(pod.Spec.Containers, videoContainer) } From c0551e30acd04531f59e75aa822d519f2ad205e0 Mon Sep 17 00:00:00 2001 From: lridge Date: Mon, 21 Feb 2022 06:48:43 -0500 Subject: [PATCH 16/20] add a pre-stop hook to ensure the browser is the last thing to exit so video output doesn't get corrupted. --- platform/kubernetes.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/platform/kubernetes.go b/platform/kubernetes.go index 6af0aad..15beae0 100644 --- a/platform/kubernetes.go +++ b/platform/kubernetes.go @@ -546,6 +546,13 @@ func (cl *service) BuildPod(layout ServiceSpec) *apiv1.Pod { Resources: layout.Template.Spec.Resources, VolumeMounts: getVolumeMounts(layout.Template.Spec.VolumeMounts), ImagePullPolicy: apiv1.PullIfNotPresent, + Lifecycle: &apiv1.Lifecycle{ + PreStop: &apiv1.Handler{ + Exec: &apiv1.ExecAction{ + Command: []string{"sh", "-c", "sleep 5"}, + }, + }, + }, }, { Name: "seleniferous", @@ -578,13 +585,6 @@ func (cl *service) BuildPod(layout ServiceSpec) *apiv1.Pod { Env: layout.Template.Spec.EnvVars, VolumeMounts: getVolumeMounts(layout.Template.Spec.VolumeMounts), ImagePullPolicy: apiv1.PullIfNotPresent, - Lifecycle: &apiv1.Lifecycle{ - PreStop: &apiv1.Handler{ - Exec: &apiv1.ExecAction{ - Command: []string{"kill -15 1"}, - }, - }, - }, } pod.Spec.Containers = append(pod.Spec.Containers, videoContainer) } From 8bd6b053554a7b489143f4f31e6c99ea050aefe0 Mon Sep 17 00:00:00 2001 From: lridge Date: Mon, 21 Feb 2022 16:38:12 -0500 Subject: [PATCH 17/20] only append lifecycle when video recording is in use --- platform/kubernetes.go | 32 ++++++++++++++++---------------- platform/kubernetes_test.go | 5 +++++ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/platform/kubernetes.go b/platform/kubernetes.go index 15beae0..ccf6ede 100644 --- a/platform/kubernetes.go +++ b/platform/kubernetes.go @@ -65,7 +65,7 @@ type ClientConfig struct { ServicePort string ImagePullSecretName string ProxyImage string - VideoImage string + VideoImage string ReadinessTimeout time.Duration IdleTimeout time.Duration } @@ -100,7 +100,7 @@ func NewClient(c ClientConfig) (Platform, error) { svcPort: intstr.FromString(c.ServicePort), imagePullSecretName: c.ImagePullSecretName, proxyImage: c.ProxyImage, - videoImage: c.VideoImage, + videoImage: c.VideoImage, readinessTimeout: c.ReadinessTimeout, idleTimeout: c.IdleTimeout, } @@ -403,7 +403,6 @@ func (cl *service) Create(layout ServiceSpec) (Service, error) { annontations[defaultsAnnotations.timeZone] = layout.Template.Spec.EnvVars[i].Value } } - if layout.Template.Meta.Labels == nil { layout.Template.Meta.Labels = make(map[string]string) @@ -546,13 +545,6 @@ func (cl *service) BuildPod(layout ServiceSpec) *apiv1.Pod { Resources: layout.Template.Spec.Resources, VolumeMounts: getVolumeMounts(layout.Template.Spec.VolumeMounts), ImagePullPolicy: apiv1.PullIfNotPresent, - Lifecycle: &apiv1.Lifecycle{ - PreStop: &apiv1.Handler{ - Exec: &apiv1.ExecAction{ - Command: []string{"sh", "-c", "sleep 5"}, - }, - }, - }, }, { Name: "seleniferous", @@ -578,14 +570,22 @@ func (cl *service) BuildPod(layout ServiceSpec) *apiv1.Pod { if layout.RequestedCapabilities.Video { videoContainer := apiv1.Container{ - Name: "video", - Image: cl.videoImage, - Ports: getVideoPorts(), - Command: []string{}, - Env: layout.Template.Spec.EnvVars, - VolumeMounts: getVolumeMounts(layout.Template.Spec.VolumeMounts), + Name: "video", + Image: cl.videoImage, + Ports: getVideoPorts(), + Command: []string{}, + Env: layout.Template.Spec.EnvVars, + VolumeMounts: getVolumeMounts(layout.Template.Spec.VolumeMounts), ImagePullPolicy: apiv1.PullIfNotPresent, } + lifecycle := &apiv1.Lifecycle{ + PreStop: &apiv1.Handler{ + Exec: &apiv1.ExecAction{ + Command: []string{"sh", "-c", "sleep 5"}, + }, + }, + } + pod.Spec.Containers[0].Lifecycle = lifecycle pod.Spec.Containers = append(pod.Spec.Containers, videoContainer) } return pod diff --git a/platform/kubernetes_test.go b/platform/kubernetes_test.go index d7e25a8..3b6e809 100644 --- a/platform/kubernetes_test.go +++ b/platform/kubernetes_test.go @@ -82,6 +82,11 @@ func TestPodWithoutVideo(t *testing.T) { pod := service.BuildPod(params.layout) assert.Equal(t, 2, len(pod.Spec.Containers)) + + t.Logf("TC: %s", "Verify that if video is not requested, the video container doesn't get the pre-stop lifecycle hook") + + var expected *apiv1.Lifecycle = nil + assert.Equal(t, pod.Spec.Containers[0].Lifecycle, expected) } func TestErrorsOnServiceCreate(t *testing.T) { From bf70ab4b01ae440dca92a39c118ab3bbb49fac9c Mon Sep 17 00:00:00 2001 From: lridge Date: Wed, 6 Apr 2022 14:54:58 -0400 Subject: [PATCH 18/20] follow the new sg4 spec that selenoid expects --- selenium/selenium.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/selenium/selenium.go b/selenium/selenium.go index 4a2eef1..6d7ffb5 100644 --- a/selenium/selenium.go +++ b/selenium/selenium.go @@ -1,5 +1,7 @@ package selenium +import "github.com/imdario/mergo" + //Capabilities ... type Capabilities struct { BrowserName string `json:"browserName,omitempty"` @@ -28,6 +30,7 @@ type Capabilities struct { DNSServers []string `json:"dnsServers,omitempty"` Labels map[string]string `json:"labels,omitempty"` SessionTimeout string `json:"sessionTimeout,omitempty"` + ExtensionCapabilities *Capabilities `json:"selenoid:options,onitemempty"` } //ValidateCapabilities ... @@ -39,6 +42,10 @@ func (c *Capabilities) ValidateCapabilities() { if c.WC3PlatformName != "" { c.Platform = c.WC3PlatformName } + + if c.ExtensionCapabilities != nil { + mergo.Merge(c, *c.ExtensionCapabilities, mergo.WithOverride) //We probably need to handle returned error + } } //GetBrowserName ... From bc0362f3bafc525b76b368f1f620cef0dbc76d0b Mon Sep 17 00:00:00 2001 From: lridge Date: Wed, 6 Apr 2022 14:57:28 -0400 Subject: [PATCH 19/20] be a little more patient with kubedns; add a more meaningful error on proxy failure --- handlers.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/handlers.go b/handlers.go index 953d4f2..b95d12d 100644 --- a/handlers.go +++ b/handlers.go @@ -164,6 +164,9 @@ func (app *App) HandleSession(w http.ResponseWriter, r *http.Request) { default: } if err != nil { + if strings.HasSuffix(err.Error(), ": no such host") { + continue + } logger.WithField("time_elapsed", tools.TimeElapsed(start)).Errorf("session failed: %v", err) tools.JSONError(w, "New session attempts retry count exceeded", http.StatusInternalServerError) cancel() @@ -225,7 +228,7 @@ func (app *App) HandleProxy(w http.ResponseWriter, r *http.Request) { }, ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { logger.Errorf("proxying session error: %v", err) - w.WriteHeader(http.StatusBadGateway) + tools.JSONError(w, fmt.Sprintf("proxying session error: %v", err), http.StatusBadGateway) }, }).ServeHTTP(w, r) From 749d85ae4ea527430c511665256f132340a2b355 Mon Sep 17 00:00:00 2001 From: lridge Date: Fri, 8 Apr 2022 10:59:38 -0400 Subject: [PATCH 20/20] oh hey, my go linter started working again --- cmd/selenosis/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/selenosis/main.go b/cmd/selenosis/main.go index c986720..3ad3ddf 100644 --- a/cmd/selenosis/main.go +++ b/cmd/selenosis/main.go @@ -34,7 +34,7 @@ func command() *cobra.Command { service string imagePullSecretName string proxyImage string - videoImage string + videoImage string sessionRetryCount int limit int browserWaitTimeout time.Duration @@ -70,7 +70,7 @@ func command() *cobra.Command { ServicePort: proxyPort, ImagePullSecretName: imagePullSecretName, ProxyImage: proxyImage, - VideoImage: videoImage, + VideoImage: videoImage, }) if err != nil {