diff --git a/internal/controllers/certmanager.go b/internal/controllers/certmanager.go index 8bc33542..107b73ef 100644 --- a/internal/controllers/certmanager.go +++ b/internal/controllers/certmanager.go @@ -109,8 +109,15 @@ func (r *CryostatReconciler) setupTLS(ctx context.Context, cr *operatorv1beta1.C return nil, err } + // Create a certificate for the reports generator signed by the Cryostat CA + reportsCert := resources.NewReportsCert(cr) + err = r.createOrUpdateCertificate(ctx, reportsCert, cr) + if err != nil { + return nil, err + } + // Update owner references of TLS secrets created by cert-manager to ensure proper cleanup - err = r.setCertSecretOwner(ctx, cr, caCert, cryostatCert, grafanaCert) + err = r.setCertSecretOwner(ctx, cr, caCert, cryostatCert, grafanaCert, reportsCert) if err != nil { return nil, err } @@ -118,6 +125,7 @@ func (r *CryostatReconciler) setupTLS(ctx context.Context, cr *operatorv1beta1.C return &resources.TLSConfig{ CryostatSecret: cryostatCert.Spec.SecretName, GrafanaSecret: grafanaCert.Spec.SecretName, + ReportsSecret: reportsCert.Spec.SecretName, KeystorePassSecret: cryostatCert.Spec.Keystores.PKCS12.PasswordSecretRef.Name, }, nil } diff --git a/internal/controllers/common/resource_definitions/certificates.go b/internal/controllers/common/resource_definitions/certificates.go index 243d41fd..5d858a69 100644 --- a/internal/controllers/common/resource_definitions/certificates.go +++ b/internal/controllers/common/resource_definitions/certificates.go @@ -155,3 +155,27 @@ func NewGrafanaCert(cr *operatorv1beta1.Cryostat) *certv1.Certificate { }, } } + +func NewReportsCert(cr *operatorv1beta1.Cryostat) *certv1.Certificate { + return &certv1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: cr.Name + "-reports", + Namespace: cr.Namespace, + }, + Spec: certv1.CertificateSpec{ + CommonName: fmt.Sprintf("%s-reports.%s.svc", cr.Name, cr.Namespace), + DNSNames: []string{ + cr.Name + "-reports", + fmt.Sprintf("%s-reports.%s.svc", cr.Name, cr.Namespace), + fmt.Sprintf("%s-reports.%s.svc.cluster.local", cr.Name, cr.Namespace), + }, + SecretName: cr.Name + "-reports-tls", + IssuerRef: certMeta.ObjectReference{ + Name: cr.Name + "-ca", + }, + Usages: append(certv1.DefaultKeyUsages(), + certv1.UsageServerAuth, + ), + }, + } +} diff --git a/internal/controllers/common/resource_definitions/resource_definitions.go b/internal/controllers/common/resource_definitions/resource_definitions.go index 1d6d1cbc..ff38e12c 100644 --- a/internal/controllers/common/resource_definitions/resource_definitions.go +++ b/internal/controllers/common/resource_definitions/resource_definitions.go @@ -81,6 +81,8 @@ type TLSConfig struct { CryostatSecret string // Name of the TLS secret for Grafana GrafanaSecret string + // Name of the TLS secret for Reports Generator + ReportsSecret string // Name of the secret containing the password for the keystore in CryostatSecret KeystorePassSecret string } @@ -175,7 +177,7 @@ func NewDeploymentForCR(cr *operatorv1beta1.Cryostat, specs *ServiceSpecs, image } } -func NewDeploymentForReports(cr *operatorv1beta1.Cryostat, imageTags *ImageTags) *appsv1.Deployment { +func NewDeploymentForReports(cr *operatorv1beta1.Cryostat, imageTags *ImageTags, tls *TLSConfig) *appsv1.Deployment { if cr.Spec.ReportOptions == nil { cr.Spec.ReportOptions = &operatorv1beta1.ReportConfiguration{Replicas: 0} } @@ -212,7 +214,7 @@ func NewDeploymentForReports(cr *operatorv1beta1.Cryostat, imageTags *ImageTags) "component": "reports", }, }, - Spec: *NewPodForReports(cr, imageTags), + Spec: *NewPodForReports(cr, imageTags, tls), }, Replicas: &replicas, }, @@ -369,7 +371,7 @@ func NewPodForCR(cr *operatorv1beta1.Cryostat, specs *ServiceSpecs, imageTags *I } } -func NewPodForReports(cr *operatorv1beta1.Cryostat, imageTags *ImageTags) *corev1.PodSpec { +func NewPodForReports(cr *operatorv1beta1.Cryostat, imageTags *ImageTags, tls *TLSConfig) *corev1.PodSpec { resources := corev1.ResourceRequirements{} if cr.Spec.ReportOptions != nil { resources = cr.Spec.ReportOptions.Resources @@ -387,10 +389,75 @@ func NewPodForReports(cr *operatorv1beta1.Cryostat, imageTags *ImageTags) *corev } javaOpts := fmt.Sprintf("-XX:+PrintCommandLineFlags -XX:ActiveProcessorCount=%d -Dorg.openjdk.jmc.flightrecorder.parser.singlethreaded=%t", cpus, cpus < 2) + envs := []corev1.EnvVar{ + { + Name: "QUARKUS_HTTP_HOST", + Value: "0.0.0.0", + }, + { + Name: "JAVA_OPTIONS", + Value: javaOpts, + }, + } + mounts := []corev1.VolumeMount{} + volumes := []corev1.Volume{} + + // Configure TLS key/cert if enabled + livenessProbeScheme := corev1.URISchemeHTTP + if tls != nil { + tlsEnvs := []corev1.EnvVar{ + { + Name: "QUARKUS_HTTP_SSL_PORT", + Value: strconv.Itoa(int(reportsContainerPort)), + }, + { + Name: "QUARKUS_HTTP_SSL_CERTIFICATE_KEY_FILE", + Value: fmt.Sprintf("/var/run/secrets/operator.cryostat.io/%s/%s", tls.ReportsSecret, corev1.TLSPrivateKeyKey), + }, + { + Name: "QUARKUS_HTTP_SSL_CERTIFICATE_FILE", + Value: fmt.Sprintf("/var/run/secrets/operator.cryostat.io/%s/%s", tls.ReportsSecret, corev1.TLSCertKey), + }, + { + Name: "QUARKUS_HTTP_INSECURE_REQUESTS", + Value: "disabled", + }, + } + + tlsSecretMount := corev1.VolumeMount{ + Name: "reports-tls-secret", + MountPath: "/var/run/secrets/operator.cryostat.io/" + tls.ReportsSecret, + ReadOnly: true, + } + + secretVolume := corev1.Volume{ + Name: "reports-tls-secret", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: tls.ReportsSecret, + }, + }, + } + + envs = append(envs, tlsEnvs...) + mounts = append(mounts, tlsSecretMount) + volumes = append(volumes, secretVolume) + + // Use HTTPS for liveness probe + livenessProbeScheme = corev1.URISchemeHTTPS + + } else { + envs = append(envs, corev1.EnvVar{ + Name: "QUARKUS_HTTP_PORT", + Value: strconv.Itoa(int(reportsContainerPort)), + }) + } + probeHandler := corev1.Handler{ HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.IntOrString{IntVal: reportsContainerPort}, - Path: "/health", + Scheme: livenessProbeScheme, + Port: intstr.IntOrString{IntVal: reportsContainerPort}, + Path: "/health", }, } return &corev1.PodSpec{ @@ -405,21 +472,9 @@ func NewPodForReports(cr *operatorv1beta1.Cryostat, imageTags *ImageTags) *corev ContainerPort: reportsContainerPort, }, }, - Env: []corev1.EnvVar{ - { - Name: "QUARKUS_HTTP_HOST", - Value: "0.0.0.0", - }, - { - Name: "QUARKUS_HTTP_PORT", - Value: strconv.Itoa(int(reportsContainerPort)), - }, - { - Name: "JAVA_OPTIONS", - Value: javaOpts, - }, - }, - Resources: resources, + Env: envs, + VolumeMounts: mounts, + Resources: resources, LivenessProbe: &corev1.Probe{ Handler: probeHandler, }, @@ -428,6 +483,7 @@ func NewPodForReports(cr *operatorv1beta1.Cryostat, imageTags *ImageTags) *corev }, }, }, + Volumes: volumes, } } diff --git a/internal/controllers/cryostat_controller.go b/internal/controllers/cryostat_controller.go index 5e2847f1..9bb5e731 100644 --- a/internal/controllers/cryostat_controller.go +++ b/internal/controllers/cryostat_controller.go @@ -376,7 +376,7 @@ func (r *CryostatReconciler) Reconcile(ctx context.Context, request ctrl.Request return reconcile.Result{}, err } - reportsResult, err := r.reconcileReports(ctx, reqLogger, instance, routeTLS, imageTags, serviceSpecs) + reportsResult, err := r.reconcileReports(ctx, reqLogger, instance, tlsConfig, imageTags, serviceSpecs) if err != nil { return reportsResult, err } @@ -453,7 +453,7 @@ func (r *CryostatReconciler) SetupWithManager(mgr ctrl.Manager) error { } func (r *CryostatReconciler) reconcileReports(ctx context.Context, reqLogger logr.Logger, instance *operatorv1beta1.Cryostat, - routeTLS *openshiftv1.TLSConfig, imageTags *resources.ImageTags, serviceSpecs *resources.ServiceSpecs) (reconcile.Result, error) { + tls *resources.TLSConfig, imageTags *resources.ImageTags, serviceSpecs *resources.ServiceSpecs) (reconcile.Result, error) { reqLogger.Info("Spec", "Reports", instance.Spec.ReportOptions) if instance.Spec.ReportOptions == nil { @@ -461,7 +461,7 @@ func (r *CryostatReconciler) reconcileReports(ctx context.Context, reqLogger log } desired := instance.Spec.ReportOptions.Replicas - deployment := resources.NewDeploymentForReports(instance, imageTags) + deployment := resources.NewDeploymentForReports(instance, imageTags, tls) if desired == 0 { svc := resources.NewReportService(instance) if err := r.Client.Delete(ctx, svc); err != nil && !errors.IsNotFound(err) { @@ -503,9 +503,14 @@ func (r *CryostatReconciler) reconcileReports(ctx context.Context, reqLogger log if err != nil { return reconcile.Result{}, err } + + scheme := "https" + if tls == nil { + scheme = "http" + } serviceSpecs.ReportsURL = &url.URL{ - Scheme: "http", - Host: deployment.ObjectMeta.Name + ":" + strconv.Itoa(int(svc.Spec.Ports[0].Port)), + Scheme: scheme, + Host: svc.Name + ":" + strconv.Itoa(int(svc.Spec.Ports[0].Port)), } reqLogger.Info(fmt.Sprintf("Reports Deployment %s", op)) diff --git a/internal/controllers/cryostat_controller_test.go b/internal/controllers/cryostat_controller_test.go index b1716149..5308c524 100644 --- a/internal/controllers/cryostat_controller_test.go +++ b/internal/controllers/cryostat_controller_test.go @@ -38,6 +38,7 @@ package controllers_test import ( "context" + "strconv" "time" certv1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" @@ -334,8 +335,10 @@ var _ = Describe("CryostatController", func() { }) }) Context("Switching from 0 report sidecars to 1", func() { + var cr *operatorv1beta1.Cryostat BeforeEach(func() { - t.objs = append(t.objs, test.NewCryostat()) + cr = test.NewCryostat() + t.objs = append(t.objs, cr) t.reportReplicas = 1 }) JustBeforeEach(func() { @@ -363,6 +366,28 @@ var _ = Describe("CryostatController", func() { t.checkReportsDeployment() t.checkService("cryostat-reports", test.NewReportsService()) }) + Context("with cert-manager disabled", func() { + BeforeEach(func() { + disable := false + cr.Spec.EnableCertManager = &disable + t.TLS = false + }) + It("should configure deployment appropriately", func() { + t.checkMainDeployment() + t.checkReportsDeployment() + t.checkService("cryostat-reports", test.NewReportsService()) + }) + }) + Context("with resource requirements", func() { + BeforeEach(func() { + *cr = *test.NewCryostatWithReportsResources() + }) + It("should configure deployment appropriately", func() { + t.checkMainDeployment() + t.checkReportsDeployment() + t.checkService("cryostat-reports", test.NewReportsService()) + }) + }) Context("deployment is progressing", func() { JustBeforeEach(func() { t.makeDeploymentProgress("cryostat-reports") @@ -664,14 +689,18 @@ var _ = Describe("CryostatController", func() { }) }) Context("with overriden image tags", func() { - var deploy *appsv1.Deployment + var mainDeploy, reportsDeploy *appsv1.Deployment BeforeEach(func() { - t.objs = append(t.objs, test.NewCryostat()) - deploy = &appsv1.Deployment{} + t.objs = append(t.objs, test.NewCryostatWithReportsSvc()) + t.reportReplicas = 1 + mainDeploy = &appsv1.Deployment{} + reportsDeploy = &appsv1.Deployment{} }) JustBeforeEach(func() { t.reconcileCryostatFully() - err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: "default"}, deploy) + err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: "default"}, mainDeploy) + Expect(err).ToNot(HaveOccurred()) + err = t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat-reports", Namespace: "default"}, reportsDeploy) Expect(err).ToNot(HaveOccurred()) }) Context("for development", func() { @@ -679,19 +708,25 @@ var _ = Describe("CryostatController", func() { coreImg := "my/core-image:1.0.0-SNAPSHOT" datasourceImg := "my/datasource-image:1.0.0-BETA25" grafanaImg := "my/grafana-image:1.0.0-dev" + reportsImg := "my/reports-image:1.0.0-SNAPSHOT" t.EnvCoreImageTag = &coreImg t.EnvDatasourceImageTag = &datasourceImg t.EnvGrafanaImageTag = &grafanaImg + t.EnvReportsImageTag = &reportsImg }) It("should create deployment with the expected tags", func() { t.checkMainDeployment() + t.checkReportsDeployment() }) It("should set ImagePullPolicy to Always", func() { - containers := deploy.Spec.Template.Spec.Containers + containers := mainDeploy.Spec.Template.Spec.Containers Expect(containers).To(HaveLen(3)) for _, container := range containers { Expect(container.ImagePullPolicy).To(Equal(corev1.PullAlways)) } + reportContainers := reportsDeploy.Spec.Template.Spec.Containers + Expect(reportContainers).To(HaveLen(1)) + Expect(reportContainers[0].ImagePullPolicy).To(Equal(corev1.PullAlways)) }) }) Context("for release", func() { @@ -699,19 +734,25 @@ var _ = Describe("CryostatController", func() { coreImg := "my/core-image:1.0.0" datasourceImg := "my/datasource-image:1.0.0" grafanaImg := "my/grafana-image:1.0.0" + reportsImg := "my/reports-image:1.0.0" t.EnvCoreImageTag = &coreImg t.EnvDatasourceImageTag = &datasourceImg t.EnvGrafanaImageTag = &grafanaImg + t.EnvReportsImageTag = &reportsImg }) It("should create deployment with the expected tags", func() { t.checkMainDeployment() + t.checkReportsDeployment() }) It("should set ImagePullPolicy to IfNotPresent", func() { - containers := deploy.Spec.Template.Spec.Containers + containers := mainDeploy.Spec.Template.Spec.Containers Expect(containers).To(HaveLen(3)) for _, container := range containers { Expect(container.ImagePullPolicy).To(Equal(corev1.PullIfNotPresent)) } + reportContainers := reportsDeploy.Spec.Template.Spec.Containers + Expect(reportContainers).To(HaveLen(1)) + Expect(reportContainers[0].ImagePullPolicy).To(Equal(corev1.PullIfNotPresent)) }) }) Context("by digest", func() { @@ -719,19 +760,25 @@ var _ = Describe("CryostatController", func() { coreImg := "my/core-image@sha256:99b57e9b8880bc5d4d799b508603628c37c3e6a0d4bdd0988e9dc3ad8e04c495" datasourceImg := "my/datasource-image@sha256:59ded87392077c2371b26e021aade0409855b597383fa78e549eefafab8fc90c" grafanaImg := "my/grafana-image@sha256:e5bc16c2c5b69cd6fd8fdf1381d0a8b6cc9e01d92b9e1bb0a61ed89196563c72" + reportsImg := "my/reports-image@sha256:8a23ca5e8c8a343789b8c14558a44a49d35ecd130c18e62edf0d1ad9ce88d37d" t.EnvCoreImageTag = &coreImg t.EnvDatasourceImageTag = &datasourceImg t.EnvGrafanaImageTag = &grafanaImg + t.EnvReportsImageTag = &reportsImg }) It("should create deployment with the expected tags", func() { t.checkMainDeployment() + t.checkReportsDeployment() }) It("should set ImagePullPolicy to IfNotPresent", func() { - containers := deploy.Spec.Template.Spec.Containers + containers := mainDeploy.Spec.Template.Spec.Containers Expect(containers).To(HaveLen(3)) for _, container := range containers { Expect(container.ImagePullPolicy).To(Equal(corev1.PullIfNotPresent)) } + reportContainers := reportsDeploy.Spec.Template.Spec.Containers + Expect(reportContainers).To(HaveLen(1)) + Expect(reportContainers[0].ImagePullPolicy).To(Equal(corev1.PullIfNotPresent)) }) }) Context("with latest", func() { @@ -739,19 +786,25 @@ var _ = Describe("CryostatController", func() { coreImg := "my/core-image:latest" datasourceImg := "my/datasource-image:latest" grafanaImg := "my/grafana-image:latest" + reportsImg := "my/reports-image:latest" t.EnvCoreImageTag = &coreImg t.EnvDatasourceImageTag = &datasourceImg t.EnvGrafanaImageTag = &grafanaImg + t.EnvReportsImageTag = &reportsImg }) It("should create deployment with the expected tags", func() { t.checkMainDeployment() + t.checkReportsDeployment() }) It("should set ImagePullPolicy to Always", func() { - containers := deploy.Spec.Template.Spec.Containers + containers := mainDeploy.Spec.Template.Spec.Containers Expect(containers).To(HaveLen(3)) for _, container := range containers { Expect(container.ImagePullPolicy).To(Equal(corev1.PullAlways)) } + reportContainers := reportsDeploy.Spec.Template.Spec.Containers + Expect(reportContainers).To(HaveLen(1)) + Expect(reportContainers[0].ImagePullPolicy).To(Equal(corev1.PullAlways)) }) }) }) @@ -1224,7 +1277,7 @@ func newFakeSecret(name string) *corev1.Secret { } func (t *cryostatTestInput) makeCertificatesReady() { - certNames := []string{"cryostat", "cryostat-ca", "cryostat-grafana"} + certNames := []string{"cryostat", "cryostat-ca", "cryostat-grafana", "cryostat-reports"} for _, certName := range certNames { cert := &certv1.Certificate{} err := t.Client.Get(context.Background(), types.NamespacedName{Name: certName, Namespace: "default"}, cert) @@ -1240,7 +1293,7 @@ func (t *cryostatTestInput) makeCertificatesReady() { func (t *cryostatTestInput) initializeSecrets() { // Create secrets - secretNames := []string{"cryostat-ca", "cryostat-tls", "cryostat-grafana-tls"} + secretNames := []string{"cryostat-ca", "cryostat-tls", "cryostat-grafana-tls", "cryostat-reports-tls"} for _, secretName := range secretNames { secret := newFakeSecret(secretName) err := t.Client.Create(context.Background(), secret) @@ -1378,7 +1431,7 @@ func (t *cryostatTestInput) expectCertificates() { func (t *cryostatTestInput) checkCertificates() { // Check certificates - certs := []*certv1.Certificate{test.NewCryostatCert(), test.NewCACert(), test.NewGrafanaCert()} + certs := []*certv1.Certificate{test.NewCryostatCert(), test.NewCACert(), test.NewGrafanaCert(), test.NewReportsCert()} for _, expected := range certs { actual := &certv1.Certificate{} err := t.Client.Get(context.Background(), types.NamespacedName{Name: expected.Name, Namespace: expected.Namespace}, actual) @@ -1742,11 +1795,18 @@ func (t *cryostatTestInput) checkMainDeployment() { // Check that the networking environment variables are set correctly coreContainer := template.Spec.Containers[0] + port := "10000" + if cr.Spec.ServiceOptions != nil && cr.Spec.ServiceOptions.ReportsConfig != nil && + cr.Spec.ServiceOptions.ReportsConfig.HTTPPort != nil { + port = strconv.Itoa(int(*cr.Spec.ServiceOptions.ReportsConfig.HTTPPort)) + } var reportsUrl string if t.reportReplicas == 0 { reportsUrl = "" + } else if t.TLS { + reportsUrl = "https://cryostat-reports:" + port } else { - reportsUrl = "http://cryostat-reports:10000" + reportsUrl = "http://cryostat-reports:" + port } checkCoreContainer(&coreContainer, t.minimal, t.TLS, t.externalTLS, t.EnvCoreImageTag, t.controller.IsOpenShift, reportsUrl, cr.Spec.Resources.CoreResources) @@ -1798,6 +1858,13 @@ func (t *cryostatTestInput) checkReportsDeployment() { "kind": "cryostat", "component": "reports", })) + Expect(template.Spec.Volumes).To(Equal(test.NewReportsVolumes(t.TLS))) + + var resources corev1.ResourceRequirements + if cr.Spec.ReportOptions != nil { + resources = cr.Spec.ReportOptions.Resources + } + checkReportsContainer(&template.Spec.Containers[0], t.TLS, t.EnvReportsImageTag, resources) // Check that the proper Service Account is set Expect(template.Spec.ServiceAccountName).To(Equal("cryostat")) } @@ -1863,6 +1930,20 @@ func checkDatasourceContainer(container *corev1.Container, tag *string, resource Expect(container.Resources).To(Equal(resources)) } +func checkReportsContainer(container *corev1.Container, tls bool, tag *string, resources corev1.ResourceRequirements) { + Expect(container.Name).To(Equal("cryostat-reports")) + if tag == nil { + Expect(container.Image).To(HavePrefix("quay.io/cryostat/cryostat-reports:")) + } else { + Expect(container.Image).To(Equal(*tag)) + } + Expect(container.Ports).To(ConsistOf(test.NewReportsPorts())) + Expect(container.Env).To(ConsistOf(test.NewReportsEnvironmentVariables(tls, resources))) + Expect(container.VolumeMounts).To(ConsistOf(test.NewReportsVolumeMounts(tls))) + Expect(container.LivenessProbe).To(Equal(test.NewReportsLivenessProbe(tls))) + Expect(container.Resources).To(Equal(resources)) +} + func (t *cryostatTestInput) checkEnvironmentVariables(expectedEnvVars []corev1.EnvVar) { c := &operatorv1beta1.Cryostat{} err := t.Client.Get(context.Background(), types.NamespacedName{Name: "cryostat", Namespace: "default"}, c) diff --git a/internal/test/reconciler.go b/internal/test/reconciler.go index 7c8b795b..cfb2c292 100644 --- a/internal/test/reconciler.go +++ b/internal/test/reconciler.go @@ -56,6 +56,7 @@ type TestReconcilerConfig struct { EnvCoreImageTag *string EnvDatasourceImageTag *string EnvGrafanaImageTag *string + EnvReportsImageTag *string } // NewTestReconciler returns a common.Reconciler for use by unit tests @@ -120,6 +121,9 @@ func newTestOSUtils(config *TestReconcilerConfig) *testOSUtils { if config.EnvGrafanaImageTag != nil { envs["RELATED_IMAGE_GRAFANA"] = *config.EnvGrafanaImageTag } + if config.EnvReportsImageTag != nil { + envs["RELATED_IMAGE_REPORTS"] = *config.EnvReportsImageTag + } return &testOSUtils{envs} } diff --git a/internal/test/resources.go b/internal/test/resources.go index 3a1f8f88..6868c0a0 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -280,6 +280,23 @@ func NewCryostatWithReportsSvc() *operatorv1beta1.Cryostat { return cr } +func NewCryostatWithReportsResources() *operatorv1beta1.Cryostat { + cr := NewCryostat() + cr.Spec.ReportOptions = &operatorv1beta1.ReportConfiguration{ + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1600m"), + corev1.ResourceMemory: resource.MustParse("1Gi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("800m"), + corev1.ResourceMemory: resource.MustParse("512Mi"), + }, + }, + } + return cr +} + func NewCryostatCertManagerDisabled() *operatorv1beta1.Cryostat { cr := NewCryostat() certManager := false @@ -926,6 +943,32 @@ func NewGrafanaCert() *certv1.Certificate { } } +func NewReportsCert() *certv1.Certificate { + return &certv1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cryostat-reports", + Namespace: "default", + }, + Spec: certv1.CertificateSpec{ + CommonName: "cryostat-reports.default.svc", + DNSNames: []string{ + "cryostat-reports", + "cryostat-reports.default.svc", + "cryostat-reports.default.svc.cluster.local", + }, + SecretName: "cryostat-reports-tls", + IssuerRef: certMeta.ObjectReference{ + Name: "cryostat-ca", + }, + Usages: []certv1.KeyUsage{ + certv1.UsageDigitalSignature, + certv1.UsageKeyEncipherment, + certv1.UsageServerAuth, + }, + }, + } +} + func NewCACert() *certv1.Certificate { return &certv1.Certificate{ ObjectMeta: metav1.ObjectMeta{ @@ -1126,6 +1169,14 @@ func NewDatasourcePorts() []corev1.ContainerPort { } } +func NewReportsPorts() []corev1.ContainerPort { + return []corev1.ContainerPort{ + { + ContainerPort: 10000, + }, + } +} + func NewCoreEnvironmentVariables(minimal bool, tls bool, externalTLS bool, openshift bool, reportsUrl string) []corev1.EnvVar { envs := []corev1.EnvVar{ { @@ -1302,6 +1353,45 @@ func NewDatasourceEnvironmentVariables() []corev1.EnvVar { } } +func NewReportsEnvironmentVariables(tls bool, resources corev1.ResourceRequirements) []corev1.EnvVar { + opts := "-XX:+PrintCommandLineFlags -XX:ActiveProcessorCount=1 -Dorg.openjdk.jmc.flightrecorder.parser.singlethreaded=true" + if !resources.Limits.Cpu().IsZero() { + // Assume 2 CPU limit + opts = "-XX:+PrintCommandLineFlags -XX:ActiveProcessorCount=2 -Dorg.openjdk.jmc.flightrecorder.parser.singlethreaded=false" + } + envs := []corev1.EnvVar{ + { + Name: "QUARKUS_HTTP_HOST", + Value: "0.0.0.0", + }, + { + Name: "JAVA_OPTIONS", + Value: opts, + }, + } + if tls { + envs = append(envs, corev1.EnvVar{ + Name: "QUARKUS_HTTP_SSL_PORT", + Value: "10000", + }, corev1.EnvVar{ + Name: "QUARKUS_HTTP_SSL_CERTIFICATE_KEY_FILE", + Value: "/var/run/secrets/operator.cryostat.io/cryostat-reports-tls/tls.key", + }, corev1.EnvVar{ + Name: "QUARKUS_HTTP_SSL_CERTIFICATE_FILE", + Value: "/var/run/secrets/operator.cryostat.io/cryostat-reports-tls/tls.crt", + }, corev1.EnvVar{ + Name: "QUARKUS_HTTP_INSECURE_REQUESTS", + Value: "disabled", + }) + } else { + envs = append(envs, corev1.EnvVar{ + Name: "QUARKUS_HTTP_PORT", + Value: "10000", + }) + } + return envs +} + func NewCoreEnvFromSource(tls bool) []corev1.EnvFromSource { envsFrom := []corev1.EnvFromSource{ { @@ -1435,6 +1525,19 @@ func NewGrafanaVolumeMounts(tls bool) []corev1.VolumeMount { return mounts } +func NewReportsVolumeMounts(tls bool) []corev1.VolumeMount { + mounts := []corev1.VolumeMount{} + if tls { + mounts = append(mounts, + corev1.VolumeMount{ + Name: "reports-tls-secret", + MountPath: "/var/run/secrets/operator.cryostat.io/cryostat-reports-tls", + ReadOnly: true, + }) + } + return mounts +} + func NewVolumeMountsWithTemplates(tls bool) []corev1.VolumeMount { return append(NewCoreVolumeMounts(tls), corev1.VolumeMount{ @@ -1504,6 +1607,22 @@ func NewDatasourceLivenessProbe() *corev1.Probe { } } +func NewReportsLivenessProbe(tls bool) *corev1.Probe { + protocol := corev1.URISchemeHTTPS + if !tls { + protocol = corev1.URISchemeHTTP + } + return &corev1.Probe{ + Handler: corev1.Handler{ + HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.IntOrString{IntVal: 10000}, + Path: "/health", + Scheme: protocol, + }, + }, + } +} + func NewMainDeploymentSelector() *metav1.LabelSelector { return &metav1.LabelSelector{ MatchLabels: map[string]string{ @@ -1673,6 +1792,22 @@ func newVolumes(minimal bool, tls bool, certProjections []corev1.VolumeProjectio return volumes } +func NewReportsVolumes(tls bool) []corev1.Volume { + if !tls { + return nil + } + return []corev1.Volume{ + { + Name: "reports-tls-secret", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "cryostat-reports-tls", + }, + }, + }, + } +} + func NewPodSecurityContext() *corev1.PodSecurityContext { fsGroup := int64(18500) return &corev1.PodSecurityContext{